[
  {
    "path": ".dockerignore",
    "content": "dist/\n.venv/\n.conda/\nnode_modules/\n__pycache__/\nbuild/\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig: http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    es6: true,\n    browser: true,\n    node: true,\n    'jest/globals': true\n  },\n  globals: {\n    page: true,\n    browser: true,\n    context: true,\n    jestPuppeteer: true\n  },\n  parser: '@typescript-eslint/parser',\n  extends: ['eslint:recommended', 'plugin:prettier/recommended', 'plugin:react-hooks/recommended'],\n  plugins: ['babel', 'prettier', 'react', 'enzyme-deprecation'],\n  rules: {\n    'valid-jsdoc': 0,\n    'no-var': 0,\n    'max-len': 0,\n    'react/no-did-mount-set-state': 0,\n    'react/no-multi-comp': 0,\n    'react/sort-comp': 0,\n    'no-use-before-define': 'off',\n    'prefer-spread': 1,\n    'prefer-template': 1,\n    'prettier/prettier': 'error',\n    'quote-props': 0,\n    'spaced-comment': 1,\n    'max-params': 0,\n    'no-multiple-empty-lines': 1,\n    'no-process-env': 0,\n    'no-inline-comments': 0,\n    'no-invalid-this': 0,\n    'no-unused-expressions': 0,\n    'no-undef': 0,\n    camelcase: 0,\n    'consistent-return': 0,\n    'comma-dangle': 1,\n    'enzyme-deprecation/no-shallow': 2,\n    'enzyme-deprecation/no-mount': 2,\n    'no-constant-condition': ['error', {checkLoops: false}],\n    'no-unused-vars': ['warn', {argsIgnorePattern: '^_', varsIgnorePattern: '^_'}],\n    '@typescript-eslint/no-unused-vars': [\n      'warn',\n      {argsIgnorePattern: '^_', varsIgnorePattern: '^_'}\n    ]\n  },\n  overrides: [\n    {\n      files: ['*.ts', '*.tsx'],\n      parser: '@typescript-eslint/parser',\n      // Plugins like @typescript-eslint provide different ruleset configs that can be extended\n      plugins: ['@typescript-eslint'],\n      extends: [\n        'plugin:@typescript-eslint/eslint-recommended',\n        'plugin:@typescript-eslint/recommended',\n        // This doesn't enable extra rules, but rather just helps with TS settings\n        'plugin:import/typescript'\n      ],\n      rules: {\n        // TODO: Replace any declarations and enable\n        '@typescript-eslint/no-explicit-any': 'off',\n        // TODO: Enable this rule and provide description or fix the errors\n        '@typescript-eslint/ban-ts-comment': 'off',\n        '@typescript-eslint/no-var-requires': 'off'\n      }\n    },\n    {\n      files: ['src/**/src/**/*.spec.js'],\n      plugins: ['jest'],\n      extends: ['plugin:jest/recommended'],\n      rules: {'jest/prefer-expect-assertions': 'off'}\n    }\n  ],\n  settings: {\n    react: {\n      version: 'detect'\n    },\n    // Settings related to eslint-plugin-import\n    'import/resolver': {\n      // Settings for eslint-import-resolver-typescript which resolves\n      // typescript aliases based on tsconfig.json \"paths\"\n      typescript: {\n        project: './tsconfig.json'\n      }\n    }\n  }\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report--jupyter-widget.md",
    "content": "---\nname: 'Bug report: Jupyter widget'\nabout: Report Bug for keplergl Jupyter widget\ntitle: \"[Bug][Jupyter Widget]\"\nlabels: jupyter\nassignees: heshan0131\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. load kepler\n2. Add data & config\n3. Export map\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment (please complete the following information):**\n - Python version: [e.g. python2, python3]\n - keplergl Widget version [e.g. 0.1.0]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[Bug]\"\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request--jupyter-widget.md",
    "content": "---\nname: 'Feature request: Jupyter widget'\nabout: Suggest an idea for kepler.gl Jupyter Widget\ntitle: ''\nlabels: jupyter\nassignees: heshan0131\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for kepler.gl\ntitle: ''\nlabels: ''\nassignees: heshan0131\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nSecurity updates are applied only to the latest release.\n\n## Reporting a Vulnerability\n\nIf you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.\n\nPlease disclose it at [security advisory](https://github.com/keplergl/kepler.gl/security/advisories/new).\n\nThis project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.\n\n## Escalation\n\nIf you do not receive an acknowledgement of your report within 6 business days, or if you cannot find a private security contact for the project, you may escalate to the OpenJS Foundation CNA at `security@lists.openjsf.org`.\n\nIf the project acknowledges your report but does not provide any further response or engagement within 14 days, escalation is also appropriate.\n"
  },
  {
    "path": ".github/workflows/build-publish-pypi.yml",
    "content": "name: Build and Publish KeplerGL Python Package\n\non:\n  push:\n  workflow_dispatch:\n    inputs:\n      prerelease:\n        description: 'Publish as prerelease (uncheck for official release)'\n        required: false\n        type: boolean\n        default: true\n\njobs:\n  build_and_publish:\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v4\n\n      - name: Set up Python 3.11\n        run: uv python install 3.11\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Install Python dependencies\n        working-directory: bindings/python\n        run: uv sync --dev\n\n      - name: Install npm dependencies\n        working-directory: bindings/python\n        run: npm ci\n\n      - name: Build TypeScript\n        working-directory: bindings/python\n        env:\n          MapboxAccessTokenJupyter: ${{ secrets.mapbox_jupyter_token }}\n        run: npm run build\n\n      - name: Type check\n        working-directory: bindings/python\n        run: npm run typecheck\n\n      - name: Build Python package\n        working-directory: bindings/python\n        run: uv build\n\n      - name: Test KeplerGL\n        working-directory: bindings/python\n        run: |\n          uv pip install dist/*.whl\n          uv run pytest\n\n      - name: Create artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: keplergl-pypi\n          path: bindings/python/dist/\n\n      - name: Check version format\n        if: github.event_name == 'workflow_dispatch'\n        working-directory: bindings/python\n        run: |\n          VERSION=$(grep -Po '(?<=^version = \")[^\"]+' pyproject.toml)\n          echo \"Package version: $VERSION\"\n          \n          # Check if version contains prerelease indicators (a, b, rc, dev)\n          if [[ \"$VERSION\" =~ (a|b|rc|dev)[0-9]+ ]]; then\n            IS_PRERELEASE_VERSION=true\n          else\n            IS_PRERELEASE_VERSION=false\n          fi\n          \n          echo \"Is prerelease version: $IS_PRERELEASE_VERSION\"\n          echo \"Publishing as prerelease: ${{ inputs.prerelease }}\"\n          \n          # Fail if mismatch between version format and publish type\n          if [[ \"${{ inputs.prerelease }}\" == \"true\" && \"$IS_PRERELEASE_VERSION\" == \"false\" ]]; then\n            echo \"::error::Publishing as prerelease but version '$VERSION' does not have a prerelease suffix (e.g., 0.4.0a1, 0.4.0b1, 0.4.0rc1)\"\n            exit 1\n          fi\n          \n          if [[ \"${{ inputs.prerelease }}\" == \"false\" && \"$IS_PRERELEASE_VERSION\" == \"true\" ]]; then\n            echo \"::error::Cannot publish official release with prerelease version '$VERSION'. Please update the version in pyproject.toml.\"\n            exit 1\n          fi\n\n      - name: Publish to PyPI\n        if: github.event_name == 'workflow_dispatch'\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          packages-dir: bindings/python/dist/\n"
  },
  {
    "path": ".github/workflows/npmpublish.yml",
    "content": "name: Node.js Package\n\non:\n  release:\n    types: [created]\n\njobs:\n  publish-npm:\n    runs-on: ubuntu-latest\n\n    env:\n      NPM_AUTH_TOKEN: ${{secrets.npm_token}}\n\n    steps:\n      - uses: actions/checkout@v4\n\n      # use Volta to manage yarn/node versions\n      - uses: volta-cli/action@v4\n\n      - run: yarn install\n      - run: yarn bootstrap\n      - run: npm i -g npm@8.19.2\n\n      - name: Login to NPM\n        run: npm config set \"//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}\"\n\n      - name: Publish\n        run: npm publish --workspaces --access public\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [18.x]\n\n    steps:\n      - uses: actions/checkout@v4\n      # use Volta to manage yarn/node versions\n      - uses: volta-cli/action@v4\n\n      - name: Install XVFB\n        run: sudo apt-get install xvfb\n\n      - name: Install Dependencies\n        run: yarn install && yarn bootstrap\n\n      # - name: Install Puppeteer\n      #   run: yarn dlx \"puppeteer@23.1.0\"\n\n      - name: Lint\n        run: yarn lint\n\n      - name: Test\n        run: xvfb-run --auto-servernum yarn cover\n\n      - name: Coveralls\n        uses: coverallsapp/github-action@master\n        continue-on-error: true\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\n*/**/node_modules/\ncoverage/\n*-coverage/\n.nyc_output/\ndist/\nbuild/\numd/\n*/**/dist/\n\n__pycache__/\n.pytest_cache/\n.ipynb_checkpoints/\nbindings/python/keplergl/static/\ntypedoc/\n\nexamples/**/yarn.lock\n!examples/demo-app/yarn.lock\n\nyarn-error.log\n*/**/package-lock.json\npackage-lock.json\n\n.DS_Store\n.idea\n.vscode/\n.venv/\n\n/bundle*.js\n/favicon.png\n/index.html\n\nnpm-debug.log\n.history/\n.yarn\n.npmrc\n\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n\n# Local Netlify folder\n.netlify\nmeta.json\n.env\n"
  },
  {
    "path": ".nvmrc",
    "content": "18.18.2\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nmodule.exports = {\n  bracketSpacing: false,\n  printWidth: 100,\n  semi: true,\n  singleQuote: true,\n  trailingComma: 'none',\n  arrowParens: 'avoid'\n};\n"
  },
  {
    "path": ".stylelintrc",
    "content": "\n{\n  \"processors\": [\n    \"stylelint-processor-styled-components\"\n  ],\n  \"extends\": [\n    \"stylelint-config-recommended\",\n    \"stylelint-config-styled-components\"\n  ],\n  \"rules\": {\n    \"no-descending-specificity\": null,\n    \"selector-pseudo-class-no-unknowny\": null\n  }\n}\n"
  },
  {
    "path": ".yarnrc.yml",
    "content": "# https://yarnpkg.com/configuration/yarnrc\nnodeLinker: node-modules\n# Define the registry to use when fetching packages.\nnpmRegistryServer: 'https://registry.yarnpkg.com'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to kepler.gl will be documented in this file.\n\n<!--\nEach version should:\n  List its release date in the above format.\n  Group changes to describe their impact on the project, as follows:\n  Added for new features.\n  Changed for changes in existing functionality.\n  Deprecated for once-stable features removed in upcoming releases.\n  Removed for deprecated features removed in this release.\n  Fixed for any bug fixes.\n  Security to invite users to upgrade in case of vulnerabilities.\nRef: http://keepachangelog.com/en/0.3.0/\n-->\n\n## [3.2.6] - Mar 16 2026\n\n- b5ffed55b feat: add extra map export resolutions (#3357)\n- faa000c6c feat(kepler-jupyter): version 0.4.0rc1 (#3345)\n- efb072eb5 fix: colors not working in trip layer of TABLE mode (#3347)\n- ca30df0e1 fix: create trip layer from duckdb table (#3344)\n- cc33b0c8f feat: add support to DECIMAL column type (#3341)\n- 40ce323a8 docs(localization): add translation guide for contributors (#3335)\n- 35ab765d4 fix: tileset loading indicator improvements (#3331)\n- cec11f3cb fix: add security warning about Mapbox token in HTML exports (#3139) (#3330)\n- e2f672cdc fix: replace broken vis.academy link with docs.kepler.gl (#3309)\n- 8c5030c3e fix: export zoom icon (#3308)\n- 8cf4274bf fix: layer configurator icon update (#3306)\n- 192f0fd2b feat: getDuckDBColumnTypes improvements (#3304)\n- 3762a2b36 feat: make tile loading indicator more explicit (#3305)\n- e5b7df170 rollback change, and truncate tooltip (#3300)\n- cbb3204cf feat: Implement WKT validation in data-type.ts (#3298)\n- e705fc8aa fix: name new point layer using label if provided (follow-up) (#3297)\n- 4bdf8f4ff fix: name new point layer using label if provided\n- cf76bba68 fix: hide Kepler editor tooltip “top-left jump” on invalid hover coords (#3294)\n- 2ba9f6e22 fix: Clamp legend height if it exceeds available space (#3276)\n- 562cb1ba8 kepler.gl-jupyter: codespell (#3273)\n\n## [3.2.5] - Dec 24 2025\n\n- 81f490d94 fix: trigger a redraw from icon layer once the icons are loaded. (#3269)\n- 26e4a17d4 fix(ai-assistant): clear LLM history on restart chat (#3262)\n- 2d985982a fix: image export for non-webpack bundlers (#3266)\n\n## [3.2.4] - Dec 9 2025\n\n- 82630dee3 fix: ensure icon layer render with the latest geometry (#3259)\n- 422c1b347 fix: Avoid Monaco AMD bundle when importing `@kepler.gl/duckdb` (#3255)\n\n## [3.2.3] - Nov 28 2025\n\n- 2288bc324 fix: Allow passing arrow tables to ArrowDataContainer (#3247)\n- a0a4eefc1 fix: Yarn start failed (#3249)\n\n## [3.2.2] - Nov 25 2025\n\n- f66ab3c61 fix: Allow passing arrow tables to ArrowDataContainer (#3242)\n- e2efa50dd fix: copy geometry when geometry is of binary format (#3236)\n\n## [3.2.1] - Nov 3 2025\n\n- d2b130f95 fix: detect h3 column in arrow (#3230)\n- 2aa200913 fix: interaction panel causes layout shift (#3224)\n- 2e24bd207 feat: extend bigInt casting to support UBIGINT HUGEINT UHUGEINT in duckdb (#3227)\n- 79d745ae2 [fix] fix for wkb/wkt saved in DuckDB as varchar (#3208)\n- 24529655d fix: fixes to channel by value (#3216)\n- f211ccd0a [Bug]: Fix scrollTop reest when scrolling horizontally in data table (#3206)\n- b6aee95f3 docs: add security escalation policy (#3210)\n- a6e9cb998 feat: ai assistant support llm proxy server (#3188)\n- 2005927bd [fix] icon layer - render default icon in case svgIconUrl loading fails (#3204)\n- 64ec955ae [chore] Add missing release notes for 3.2 (#3200)\n- 70a129c4e [feat] vector tile layer - add feature uid selector (#3203)\n- 2b8af8260 [fix] vector tile layer - use highlightedFeatureId for hover (#3202)\n- 32fb77f42 [chore] bump demo-app example to kepler.gl 3.2 (#3201)\n- 96dcef6b9 [fix] fixes for legend (#3199)\n- 26dd6e832 [website] fix mobile layout (#3197)\n- 7be817789 [website] Add OpenJS Foundation copyright and logo (#3196)\n\n## [3.2.0] - Aug 21 2025\n\n- 3b0be2dda [chore] docs update (#3192)\n- 9c132de28 [chore] docs update (#3180)\n- d4d8d184b [chore] raster tile form - add link to docs (#3183)\n- f91564fb9 [fix] save raster layer config with layer, don't rely on app config (#3184)\n- 420bbf2ad [feat] add support for boolean filter in vector tiles (#3190)\n- e4b64a080 [chore] Replace Studio section with Desktop section (#3189)\n- 751148111 [chore] Fix and update examples to v3.1.10 (#3182)\n\n## [3.1.10] - Aug 14 2025\n\n- 09297acc0 [improvement] optimize speed of getCategoricalColorMap (#3178)\n- 1545fafe5 [chore] Update react-modal types version (#3173)\n- fc1b91fa9 [chore] Create props interface for LinkRenderer Component (#3172)\n- 5f664c3a5 [chore] pass through logoComponent to PlotContainer (#3176)\n- 46cc44109 [fix] fix for a crash in getBins when numeric strings are treated as numbers (#3175)\n- 0850ef2eb [chore] Update the Screenshot Image in Readme (#3171)\n- f2001983a [feat] export duckdb column logic (#3170)\n- 3df1c4ddf [chore] expose showDeleteDataset prop (#3166)\n- 2a666ae0c [chore] export dnd constants\n\n## [3.1.9] - July 28 2025\n\n- 0d6d5fd1a [chore] raster tile - hide server settings by default (#3163)\n- 7551f5d7d [fix] DuckDB mode: space in column name breaks file import (#3153) (#3156)\n- 047334712 [fix] fit to bounds - fix initial basemap and deck projections mismatch (#3155)\n- d43e8bbff [chore] replace ai-assistant model config file with ts for npm availability (#3154)\n- 1a93a2b99 [chore] raster tile layer tests (#3152)\n- 343b554db [feat] WMS layer improvements (#3151)\n- c1d2b8616 [fix] button spinner fix (#3150)\n- a05b3cf6d [fix] WMS layer fixes 2 (#3149)\n- 1a47e08a5 [fix] WMS layer fixes 1 (#3148)\n- 272fd1ae7 [Feat] WMS Layer - development (#3092)\n- 9f656e06c [Docs] Add tutorial Spatial Data Analysis with Kepler.gl AI Assistant (part1) (#3126)\n- ca628b523 [Chore] rewrite plot container for perf improvement (#3133)\n- 49f4c3de2 [chore] Update old imports (#3131)\n- 3b6e9049a [fix] improvements for raster tile layer (#3124)\n- 4237b0a0e [chore] migrate custom-palette from react-sortable-hoc to dnd-kit (#3128)\n- 49fbf8faa [fix] aggregation layers fixes for custom color scale (#3129)\n- edf1f1ddd [fix] spatial join ai instruction (#3127)\n\n## [3.1.8] - May 26 2025\n\n- 4fd570c3e [example] Kepler.gl getting started example with Vite (#3123)\n- f57173d35 [fix] DuckDB - cast BigInts to Double by default (#3120)\n- fd4702c4f [perf] disable strokes by default for polygons in geojson layer (#3118)\n- 1542bb6f7 [Feat] Add OSM road tool to support point analysis on road networks (#3117)\n- 9da33316c [feat] Generate \"idea\" buttons from LLM (#3115)\n- 330030185 [Fix] AI Connection to Ollama failed (#3113)\n- be6ee823a [Bug] fix update selected feature bbox (#3110)\n- 0dcacc66e [fix] Custom picker fix when called during initialization (#3107)\n- 4fc1344a1 [Chore] style tweak (#3109)\n- 3769aaf74 [Chore] Better handle add data to map error and loading indicator (#3106)\n\n## [3.1.7] - May 14 2025\n\n- 014059b97 [fix] fixes for raster tile layer (#3102)\n- 518c515d1 [feat] loading indicator improvements for tiled layers (#3097)\n- e290f281e [fix] fix for getFieldsFromTile regression (#3099)\n- 7087ffe15 [fix] fix tooltip crashing for trip layer (#3103)\n- a231ebfc1 [fix] fix for image export with effects (#3105)\n- da38e26b9 [chore] Update Comments in actions (#3098)\n- 21aac1c85 [fix] fixes for custom input (#3095)\n- 271b8cc98 [fix] fix types publishing for table module (#3096)\n- 3d3bb9b54 [chore] add cdnUrl option to application config (#3093)\n- 9cdf73ea4 [Bug] remove layer item z-index (#3091)\n\n## [3.1.6] - May 8 2025\n\n- 33203a6de [fix] fix loading indicotor not hidden regression (#3088)\n- 913176bb6 [bug] fix lodash imports regression (#3089)\n\n## [3.1.5] - May 8 2025\n\n- 223d14b60 [chore] ts fixes\n- 3570ac429 [feat] Raster Tile Layer - development in progress (#3048)\n- 07e5beae2 [chore] import add tileset dialog styling (#3085)\n- 6568c0c94 [Feat] Add Spatial Data Analysis tools to AI Assistants (#3057)\n- fd08fa1a9 [Chore] export more kepler-gl prop selector (#3081)\n\n## [3.1.4] - May 5 2025\n\n- 03b8ea0a9 [Chore] Use clonedeep in interaction config load (#3080)\n- a6104eafe [Chore] drop lodash per-method packages in favor of the main lodash (#3065)\n- abb3fd473 [feat] h3 layer from decimal format (#3066)\n- 191f66161 [fix] fix for exported maps. Change react-markdown to markdown-to-jsx (#3077)\n- 3c83e74b3 [chore] Added types in Action (#3075)\n- 8baddbbaf [Chore] export bottom widget field selector (#3078)\n- afc21c263 [chore] Fix TypeScript Errors (#3076)\n- 822cd0e32 [Style] align icon styles, use lucid icons (#3073)\n\n## [3.1.3] - May 2 2025\n\n- d5ef5f713 [Chore] remove dnd-kit from dependency of utils and reducer, remove use of withState in dnd context (#3067)\n\n## [3.1.2] - May 1 2025\n\n- fb3615b90 [chore] extra duckdb utils export (#3063)\n- a0d14b770 [fix] updateVisDataUpdater early exit (#3058)\n- 111b3180d [chore] pass props directly to Draggable legend, not withState (#3055)\n- 6bb6c98a1 [fix] Legend positioning fixes (#3052)\n- 7d601f110 [fix] Only open either the mapDraw dropdown or locale dropdown (#3056)\n- 52a2d2a2b [CHORE] disable auto lyaer creation based on color by int column (#3049)\n- e935ef51d [chore] extra exports from duckdb module (#3050)\n- 864dbe5c6 [Chore] Fix more style components warnings (#3047)\n- 9de30e2ba [Chore] Export duckdub utils, allow cols in validate dataset (#3042)\n- 6cc1ee4ef [Bug] Fix styled-components warnings for passing props to Dom (#3039)\n- cb5c14f81 [Bug] Add mapbox-gl dep to prevent Failed to resolve import in vite (#3036)\n- 7c3365fcf [chore] Get started minimal example with esbuild (#3028)\n- 224975a25 [chore] pass duckDB adapter via application config (#3023)\n\n## [3.1.1] - March 11 2025\n\n- e271c8f8c [fix] fix for potential freeze during add data pipeline (#3015)\n- 9eef01c48 [fix] make onFilteredItemsChange callback optional (#3016)\n- 7107e4177 [fix] fixes for vector-tile layer (#3013)\n- b4b979d59 [fix] schema panel displays temp table (#3014)\n- ed2b5f322 [chore] fix react deprecation warnings (#3011)\n- ba75087a1 [fix] DuckDB: update schema after running a query (#3009)\n- b57b1ff9a [chore] update umd example to latest stable release (#3010)\n- 9762dc379 [feat] DuckDb plugin: drag and drop file directly as table (#2952)\n- 8e737e8cc [chore] changes to webpack.config path separators (#2623)\n- 8fbb3b0b4 [fix] Fix Save map action for FSQ provider (overwrite logic) (#3006)\n- df829fbe9 [fix] fix for geocoder coordinates (#3002)\n- 44acecf2a [fix] adjust getZoomFactor for icon layer (#3004)\n- 69ea2a176 [fix] fix for Icon layer UI (#3003)\n- 702b49e3f [fix] Fix for More than one copy of react-palm was loaded error message (#3007)\n- a67a7fcab [Bug] make sure the RangeBrush updates on slider range changes (#2047)\n- 631f7a304 [fix] Update geojson-utils.ts to support GeometryCollections (#2059)\n- ce867606f [chore] Bump express from 4.19.2 to 4.21.0 (#2655)\n- c9dd05f32 [chore] Bump nanoid from 3.3.7 to 3.3.8 in /bindings/kepler.gl-jupyter/js (#2906)\n- b2e24345c [chore] Bump fast-xml-parser from 4.4.0 to 4.5.0 (#2688)\n- 976b079b3 [chore] Bump lodash from 4.17.19 to 4.17.21 in /src/deckgl-layers (#2858)\n- 4074b320f [chore] Bump esbuild from 0.23.1 to 0.25.0 in /examples/demo-app (#2994)\n- 57573d344 [chore] Bump elliptic from 6.6.0 to 6.6.1 (#2997)\n- c5dbd571d [chore] Fix lint issues displayed on GitHub's File Changes page (#3001)\n- b98a39def [fix] Transform binary buffers to hex wkb when saved to json/hmtl maps (#2998)\n- 221b243c2 [feat] improvements to duckDB column type handling (#2970)\n- d30a95bcd [fix] improvements for layer type change logic (#2995)\n- 547ffeb0c [fix] arrow text labels from non-string source vectors (#2990)\n- 7e2e619e2 [chore] updates to website (#2992)\n- 7e2db2b7d [chore] Improved Props and Gettings Started Docs (#2993)\n- 1216d235b [fix] export geoarrow to CSV as geojson (#2988)\n- 2c525ed8e [fix] restore suport for string wkb; save binary wkb as hex wkb (#2982)\n- e149384df [chore] update to hubble.gl 1.4 (#2987)\n- c39778ce9 [fix] AI Assistant sends messages to 127.0.0.1 instead of remote Ollama URL (#2985)\n- 81780f5ab [chore] Update README.md (#2981)\n- f8fbf2461 [fix] heatmap renders nothing with black color or duplicate color (#2978)\n- 11350eecb [chore] check for required env variables in demo-app and output a warning (#2977)\n- 0325ef6ee [fix] FSQ storage provider - use prompt instead of auto login after logout (#2975)\n- bbe51b980 [fix] fix for point column suggestion not working (#2974)\n- fa1cc4f1e [chore] Rename \".env.template \" to \".env.template\" to prevent git clone fail (#2976)\n- 47cd3da81 [keplergl-jupyter] Release v0.3.7 #2969\n- 7bbe0b839 [Jupyter][Fix] convert datetimes to str so they can convert to json (#2968)\n- 5367abaee [fix] fix geojson and trip layer crash without data (#2964)\n- e1c9f869c [fix] FSQ Storage provider temp fix (#2960)\n- 098ee9b42 [fix] fix for minzoom in examples (#2959)\n- f7f10379e [chore] update demo-app version (#2958)\n- ab17e7565 [chore] update banner mesasge (#2957)\n\n## [3.1.0] - January 29 2025\n\n- 089aa8cf8 [chore] vector tiles refactoring (#2945)\n- 405c36e23 [fix] DuckDB: make query result title more reusable (#2956)\n- 8033578f2 [docs] update docs for Kepler.gl release 3.1 (#2941)\n- b1953cff7 [feat] banner with extra release info (#2955)\n- e95c4e5a4 [fix] arrow tables - save timestamps as iso date string (#2953)\n- 4aef54a93 [fix] adjust margin for map save modal to show Save button without scrolling (#2954)\n- f00b4b88d [chore] testing mp4 embed with gitbook (#2951)\n- f292d6181 [Chore] Add DeepSeek in Ai Assistant (#2946)\n- c5484e1ae [fix] plumbing for DuckDB plugin support (#2949)\n- da9988532 [chore] demo-app bump kepler.gl version (#2944)\n\n## [3.1.0-alpha.7] - January 27 2025\n\n- 7356c5afe [fix] hotfix for arrow saving / loading, without support of binary data (#2943)\n- 4031451b0 [feat] duckdb plugin (#2798)\n- 029bcc548 [feat] loading indicator (#2936)\n- 1a68d1bd2 [Chore] Remove SQL plugin for AI Assistant (#2938)\n- 4be4b6987 [Misc] Update demo-app README.md (#2934)\n- b38054fa8 [Feat] AI Assistant Query (#2819)\n- 4cd912097 [fix] Added 24 limit for maximum zoom (#2635)\n- b1bddd5fb [chore] Fixes for using in a vite app (#2898)\n- 4d1bfb3d0 [feat] minZoom and maxZoom for examples (#2933)\n- 81be74920 [fix] don't auto create point layer from vector tiles (#2932)\n\n## [3.1.0-alpha.6] - January 22 2025\n\n- 803b2f540 [fix] remove dependencies from useEffect (prev componentDidMount in app.tsx) (#2930)\n- 57926442f [fix] use saved map config for saved maps instead of zoom in to data (#2929)\n- 4af609245 [chore] add dot.env, updates to demo-app build, update gitignore (#2928)\n- ce23c7668 [feat] duckdb module updates (#2927)\n- fc974d852 [feat] duckdb module placeholder (#2926)\n- d1b3c9799 [fix] demo-app resolve to proper node_modules (#2925)\n- 594250bc7 [fix] fix demo-app yarn start (#2924)\n- 913ba1ce0 [feat] support for table plugin in demo examples and privately stored datasets (#2923)\n- 630728190 [fix] fixes for style editor preview and error (#2919)\n- d6aa2757e [fix] fix neighbor mode hovering crash in arc/line layers (#2920)\n- e2bd71d4e [fix] fix csv table examples for layers (#2921)\n- a6e151a0f [fix] vector tile layer fixes (#2911)\n\n## [3.1.0-alpha.5] - January 15 2025\n\n- b117b08ab [fix] fix for exported html maps\n\n## [3.1.0-alpha.4] - January 15 2025\n\n- 933a91a2f [fix] check for geoarrow extension in geojson layer (#2916)\n- 0abe9979d [fix] opening multiple examples breaks url (#2915)\n- 49e7956e8 [fix] Don't show 0 when description is empty (#2913)\n- cb5d4ed42 [fix] fix styled-components version in exported map (#2907)\n- b1d243363 [fix] adjust tooltip style (#2910)\n- f0c57a869 [fix] fix website commit section\n- 2753321c5 [feat] Vector Tile layer fixes (#2899)\n- 073c585e4 [Fix] add_data() function failed in keplergl-jupyter\n- 6e425972e [feat] mapbox and maplibre simultaneous support (#2897)\n- 22f9ba14d [Chore] Fix lint error for Register actions (#2896)\n- ccfc2e2ad [fix] Image export legend positioning (#2895)\n- cf02a3ca6 [chore] Upgrade styled components to v6 (#2894)\n- 4c9ffe827 [fix] Prevent infinite useEffects loop in range-plot (#2892)\n- 0b67c5409 [fix] fixed onOptionSelected handler when option is null (#2891)\n- 40ba839e3 [fix] Add ellipsis in LayerTypeListItem (#2890)\n- d0d31bdfa [fix] Handle selecting geojson polygon with missing properties (#2889)\n- 79801bec0 [fix] Tippy tooltips in animation controls (#2888)\n- 0ad53723a [feat] Adding setAnimationConfig action (#2887)\n- 67b001980 [chore] export TimeFieldFilterProps (#2883)\n- 603fde8d1 [fix] Fixed issue when mapstate latitude or langitude are out of bounds (#2882)\n- 92c9e6abc [fix] Use floating-ui to deal with closing on click outside (#2886)\n- 4bcf55bd6 [Feat] Custom color scale for categorical/ordinal field (#2880)\n- 23f603428 [Bug] Prevent dragging legend component outside of container (#2885)\n- d549fcd4e [fix] copy map config style (#2878)\n- 34444fa54 [fix] check for allBins (#2876)\n- 8335ba410 [fix] Custom Color Scale fixes (#2875)\n- 141236513 [chore] Add several vis state mergers combineConfigs and improve TS (#2634)\n- 9f3f08944 [Chore] Add composedReducerSchema to schema manager (#2633)\n- e633177ef [chore] applyFilterConfig action (#2872)\n- ceb930e2d [Fix] incorrect multi-dataset filter domain (#2871)\n- 00dd00279 [fix] show scale options for Point Count in aggregation layers (#2870)\n- a39fcf8c4 [fix] pass strings to color palette inputs (#2873)\n- b89b19c6a [chore] Consolidate vis state tests (#2869)\n- 77e785713 [feat] Support custom ordinal color scale on string field in layer config (#2868)\n- cccc4be2b [feat] Free positioning of the legend (#2874)\n- 2d1d8e5f5 [Fix] add_data() function in keplergl-jupyter (#2867)\n- 3b46abd34 [feat] add LayerToggleVisibility for single splitMap (#2863)\n- b2b6f10c8 [Release] Candidate Kepler-Jupyter v0.3.4 (#2588)\n- 3bf006f41 [fix] Remove legend layer groups height capping (#2864)\n- f1245d7e5 [chore] ts refactoring (#2861)\n- a897715cc [Feat] Add custom color scale for aggregate layers (#2860)\n- 6bc594602 [Feat] replaceDataInMap action - save colorsByDataId (#2859)\n- be2f04e6d [chore] add fsq color palettes (#2862)\n- c7d3777fc [fix] change process to globalThis.process (#2865)\n- 5cb8a3431 [chore] Create SECURITY.md (#2845)\n\n## [3.1.0-alpha.3] - December 25 2024\n\n- 2cd7a859c [chore] fix missing dependencies in workspaces (#2856)\n\n## [3.1.0-alpha.2] - December 24 2024\n\n- f9b385a6e [fix] Fixed time filter bug where points located at the borders of the domains were not correctly displayed (#2854)\n- 59abc6a19 [fix] fix for custom color scales with tile layer (#2853)\n- 3a4bf667f [feat] histogram and point layer fixes (#2852)\n- f9c52e538 [feat] color scale histogram (#2851)\n- 3e7dc937a [fix] custom palette issues (#2850)\n- d55797991 [docs] Replace yarn global add with yarn dlx for puppeteer installation (#2849)\n- e106c1927 [feat] Plumbing for vector tile layer (#2839)\n- a2abbf72d [fix] fix yarn cover regression (#2846)\n- 6925bd429 [docs] update demo-app/README.md (#2847)\n- f05b6e37d [chore] Bump nanoid from 3.3.7 to 3.3.8 in /website (#2840)\n- 4baa44d9b [chore] Bump nanoid from 3.3.7 to 3.3.8 (#2837)\n- b09d7eb95 [Enhancement] support mapbox url protocol (#2693)\n- 215383661 [Feat] Redesign color range to use chormajs and d3 color function (#2835)\n- bded7af76 [Feat] handle layer color scale by field.domainQuantiles (#2829)\n- 5f7c26bd0 [fix] Prevent duplicate legend in line and arc layers (#2830)\n- 639c7a5b9 [feat] Apply new legend style (#2831)\n- f9c214dd5 [chore] getSelectedFeature (#2832)\n- efdf2ea8d [chore] ts fixes (#2827)\n- 074d123dd [Feat] Add display format setting for table/tooltip (#2826)\n- aec75d819 [Chore] Minor ts refactoring (#2825)\n- 1825b6572 [fix] Expand legend (#2824)\n- bb6a376e8 [fix] adjust time range filter on value set (#2823)\n- cef3faf19 [Feat] add h3 typed column (#2822)\n- c5d42ddc9 [fix] Fix multiple field filtering in setFilterUpdater (#2821)\n- c4d1cfff0 [FEAT] support domain.domainStops in layer color, render color legend based on zoom (#2815)\n- 3a4feac59 [fix] Line layer is not displayed for between hex ids (#2820)\n- 89411c820 [fix] Typescript 4.4 fixes (#2816)\n- 240289603 [feat] Show selected fields in the tooltip for aggregation layers (#2814)\n- 95c6ed14b [chore] Bump elliptic from 6.5.7 to 6.6.1 in /bindings/kepler.gl-jupyter/js (#2818)\n- fb3fa7b58 [chore] Bump cross-spawn from 6.0.5 to 6.0.6 in /website (#2817)\n- f476a1c4c [chore] Bump elliptic from 6.5.7 to 6.6.0 (#2720)\n- c727356f7 [chore] Bump cross-spawn from 6.0.5 to 6.0.6 (#2772)\n- 3950d73ab [chore] Fixes in README.md (#2810)\n- 786aa36cf [Fix] Don't modify height for with fixed height enabled; Height UI unification (#2804)\n- 2178d9057 [fix] show sync layer animations when there is one dataset (#2803)\n- 6f35313f7 [chore] timeline refactoring (#2802)\n- d50bbc831 [fix] Updated plot when changing cross filters (#2801)\n- b4dfa2fce [fix] disable Share Map for FSQ provider (#2808)\n- 86b5dda7e [Feat] AI Assistant [2] (#2777)\n- 5a0cbca66 [Bug] Fix issue with React StrictMode causing Dataset table to not display (#2683)\n- b147db8d5 [chore] Local Development Guide Update (#2806)\n- 6223be939 [fix] Foursquare storage provider improvements (#2800)\n\n## [3.1.0-alpha.1] - December 3 2024\n\n- f6b37c6af [fix] fixes for exported maps\n- 979c9a5a1 [fix] align upload icon (#2799)\n- 6862eb85c [fix] Fix for Try Sample Data (#2796)\n- f4f7fd2b4 [fix] fix examples - proper publicPath to include bundle chunks (#2795)\n- 7ca7f9178 [chore] prepublishOnly command fixes (#2793)\n- 40c6c8b09 [chore] npmpublish fixes - set npm registry manually (#2792)\n- f60b94f48 [chore] fix npmpublish action (#2791)\n- e77981e13 [chore] fix for package publishing (#2790)\n\n## [3.1.0-alpha.0] - November 26 2024\n\n- 5b4f6537 [feat] create new dataset action (#2778)\n- a253cae1 [chore] Update the keplergl processors update (#2776)\n- 931e2c6b [fix] Update the path to relative path in utils (#2775)\n- ac469c13 [chore] Updated imports for Kepler GL Reducers in docs (#2774)\n- 13b469d8 [chore] common-utils module (#2773)\n- 6fd4f884 [Feat] Kepler.gl AI Assistant [1] (#2735)\n- ab9e2530 [fix] Time Sync fixes and tests (#2771)\n- 1689ed68 [fix] Custom color scale fixes (#2770)\n- d0c9a3b9 [feat] Support custom breaks in color scale (#2739)\n- 3f645002 [fix] restore arc and line layers in non-geoarrow modes (#2732)\n- 966ee4c6 [Chore] Custom Initial State and Forward Actions Docs update (#2731)\n- e88577de [chore] Docs action page import updates (#2729)\n- d783b43c [feat] experimental support for ARROW:extension:point; support for arrrow:wkb for geojson layer (#2716)\n- 26687575 [chore] Update Code examples in API Reference Get Started page (#2727)\n- 8ea1cabe [fix] Fixed synced filter domain and interval calculation (#2725)\n- 695861b2 [Bug] fix yaxis chat doesn't update (#2724)\n- 8c37afaa [fix] time sync bugfixes (#2723)\n- 4c2a6b3c [Improvement] Improved radius legend number formatting (#2726)\n- c9658214 [Doc] Improve keplergl-jupyter documentation (#2697)\n- 934f8e89 [feat] Improve timeline sync filer UI (#2722)\n- d6f68379 [fix] Time Sync bugfixes (#2721)\n- 40f82127 [feat] Sync filter with layer timeline (#2718)\n- 0b6f320a [Enhancement] Synced filter small tuneup to synced filter panel (#2715)\n- caf6e485 [fix] filter fields based on timestamp (#2714)\n- c17dacf3 [feat] Layer animation (#2713)\n- 0507bd60 [faat] deckgl-arrow-layers module (#2680)\n- 8e4d723b [feat] Allow function return type of getData in getFilterValueAccessor (#2708)\n- e20d5e82 [BUG] fix gpu filter update trigger attribute update in every render (#2707)\n- 2d8161e3 [Feat] add color picker to single color selector (#2699)\n- b258e8a9 [Bug] Fix synced time filter loaded value not saved (#892) (#2706)\n- e5fe97be [feat] Updated time filter sync style (#2705)\n- cb705c63 [fix] Prevent bottom time widget crash (#895) (#2703)\n- ef2ac8f0 [chore] Add runGpuFilterForPlot to export, ts changes to KeplerTableModel (#2702)\n- ee695327 [fix] remove duplicate \"https:\" in example (#2711)\n- a743a276 [fix] add map control buttons back (#2709)\n- 97df4c94 [Feat] Replaced filter enlarged with view: side | enlarged | minified - part 2 (#2537)\n- 1c0ef9a9 [feat] add deck.gl onFilteredItemsChange callback to DeckGl overlays (#2691)\n- d6082fe6 [feat] Time filter syncing (#2690)\n- b28a263e [feat] Implemented ability to invert time series trend colors (#2692)\n- ecb5ed41 [feat] Edit color legend value (#2681)\n- 9c82daae [Enhancement] Add billboard and fadeTrail toggles (#2684)\n- 69fc6c65 [Feat] Dynamic map lib config (#2678)\n- 5764b069 [Chore] Remove default props and react-onclickoutside in react functional components (#2679)\n- 09e19f86 [Fix] Tooltip not working in exported HTML map (#2556)\n- a24ba5ec [Feat] Support radius legend (#2677)\n- 1e7415a3 [Enhancement] call layer methods to validate visconfig when switching dataset (#2676)\n- 25a5b60d [Chore] Adding application config (#2658)\n- a9135ac6 [Feat] add geojson column mode for point layer (#2666)\n- b6ac6540 [Feat] Add neighbor column mode to arc layer, support arc from hex (#2665)\n- 2bc59371 [Feat] support create geojson path from point csv in polygon layer (#2664)\n- 4c489940 [chore] Split out column mode config into separate component (#2663)\n- add6192b [feat] Layer Column Mode (#2662)\n- ef32f711 [fix] Fixed disappearing animation time control (#2625)\n- c70ae07e [chore] Update @loaders to 4.1.1 (#2638)\n- ad94d703 [Fix] legend wasn't interactive in shadow DOM (#2630)\n- 6ffb1dcb [chore] Move create or update filter action (#2636)\n- 16a3ac26 [fix] Improved map bounds calculation and handled latitude issues (#2632)\n- 7e3ea28b [fix] prevent second shadow effect (#2631)\n- f8e7b417 [fix] Upgrade react-router from 3.2.5 to 3.2.6 (#2637)\n- 56c9c3ed [fix] Updated type data-utils getColumnFormatter method (#2640)\n- 5d77b7ab [chore] Add className for LayerManager (#2629)\n- 6f45f1f0 [feat] add autoFocus prop in TypeHead (#2646)\n- 406b9787 [fix] Reset default values when DropdownList component unmounts (#2648)\n- cf39ab20 [fix] Map controls tooltips break drag event positioning (#2649)\n- e7deb4c6 [chore] Exporting missing types for PlaybackControls (#2650)\n- edd1fd98 [fix] Making sure animated spinner has border width CSS prop set (#2651)\n- b92b9707 [fix] Disable polygon filter menu for non-polygon features (#2652)\n- e40d9b6e [feat] Call get after inject to create full cache (#2647)\n- f15be57f [fix] Fixed effect panel width (#2644)\n- 04280b33 [fix] Hiding legend scrollbar when in image export (#2643)\n- 73704019 [chore] Update modal with test id (#2642)\n- 4f9d261c [fix] data table right margin in header (#2641)\n- 66b7fbdf [chore] Replaced deprecated \"assert\" with \"with\". (#2654)\n- fb7fd817 [fix] build_and_publish fix (#2645)\n- 9dbc80f1 [chore] migrate from webpack to esbuild to build demo-app locally (#2616)\n- 7b512cfa [chore]: Upgrade to yarn 4 (#2610)\n- a06d03c5 [chore] Bump setuptools from 69.5.1 to 70.0.0 in /bindings/kepler.gl-jupyter (#2587)\n- f977b4f2 [chore] Bump elliptic from 6.5.6 to 6.5.7 (#2608)\n- 40005446 [chore] Fix cover script generate cover report (#2609)\n- affc5b65 [Chore] Upgrade to eslint 8.53.0 and prettier 2.8.8, fix lint and type errors (#2607)\n- bc90b0e2 [Chore] fix tests (#2602)\n- e5111dad [Bug] Fixes a number of issues preventing Kepler from building on fresh checkout (#2596)\n- 9341911e [Bug] Fix custom map style input (#2564)\n- 89180277 [chore] update deps; update doc; update version (#2568)\n- ff52dda6 [fix] jupyter widget: don't take over <title> (#1723)\n- 739aed86 [deps] Bump ip from 1.1.5 to 1.1.9 (#2527)\n- 44526ebc [Feat] Kepler-Jupyter 0.3.4 with kepler v3 (#2565)\n- 6667a966 [Docs] Update node.js version in docs to v18 (#2558)\n- 4932e76a [Feat] use fixed height in geojson layer (#2533)\n- 400120f3 [Enhancement] call layer methods to validate visconfig when switching dataset (#2532)\n- 1f9757b8 [feat] Pass in custom transformRequest function (#2534)\n- b644f203 [Fix] layer popover mapIndex (#2535)\n- 4b3c950f [fix] Fix sample maps (#2529)\n- 55fb2426 [chore] Update COC to OpenJS (#2496)\n- 0959de6a [Feat]Support Zoom to layer in layer panel (#2516)\n- ac0d3575 [Chore] docs: Add GeoArrow to supported formats (#2503)\n- 084d807f [Chore] Bump path-parse from 1.0.6 to 1.0.7 (#1569)\n- 46086e88 [Chore] Bump cached-path-relative from 1.0.2 to 1.1.0 (#1687)\n- b8e5f865 [Chore] Bump ssri from 6.0.1 to 6.0.2 (#1866)\n- 48e5839f [Chore] Bump postcss from 7.0.35 to 7.0.39 (#1691)\n- 03d844c4 [Chore] Bump url-parse from 1.5.1 to 1.5.10 (#1724)\n- f5d3be2c [Chore] Bump async from 2.6.3 to 2.6.4 (#1810)\n- 012e9d7e [Chore] Bump shell-quote from 1.7.2 to 1.7.3 (#1847)\n- 3222fa11 [Chore] Bump minimist from 1.2.3 to 1.2.6 (#2520)\n- 248a759d [Chore] Bump hosted-git-info from 2.8.8 to 2.8.9 (#1865)\n- 8659d4c9 [Chore] Bump decode-uri-component from 0.2.0 to 0.2.2 (#2053)\n- 354fb8d2 [Chore] Bump browserify-sign from 4.2.1 to 4.2.2 (#2421)\n- 59d81ef8 [Chore] Bump @adobe/css-tools from 4.3.1 to 4.3.2 (#2464)\n- 776f11bc [Chore] Update docs to MapLibre and react-map-gl v7 (#2497)\n- 0ad17b50 [Chore] Bump follow-redirects from 1.15.1 to 1.15.4 (#2507)\n- b3be6c9e [Fix] fix example node-app arrow errors (#2508)\n- 24acc1a0 [Chore] Update Uber References (#2495)\n\n## [3.0.0] - December 21 2023\n\n- 21a445fd [chore] update readme, fix examples, show effects button (#2492)\n- de8cb971 [Fix] GeoArrow demo not working (#2491)\n\n## [3.0.0-alpha.2] - December 17 2023\n\n- 5264c5f5 [fix] add thumbnails (#2486)\n- 34bb812e [chore] Update all licenses to OpenJS recommendation (#2471)\n- df87781a [Feat] add polygon filter based on mean centers for GeoJsonLayer (#2476)\n- 50924867 [chore] Add file license header script (#2472)\n- f33b09f8 [Demo] Add GeoArrow sample dataset (#2483)\n- 09aee384 [feat] MapLibre basemap (#2461)\n- 1544e202 [Fix] basemap frozen when incrementally loading GeoArrow (#2474)\n- b290d871 [chore] pin luma.gl version to 8.5.21, to avoid mismatch (#2463)\n- 955633df [chore] bump loaders (#2480)\n- b481611c [fix] fix map import (#2479)\n- 2024a6d8 [Feat] GeoArrow incremental rendering (1) (#2459)\n- aa1c7d10 [chore] fix typo in landing page (#2402)\n- 155a5825 [fix] Fix cloud tile fetching logic (#2456)\n- 5eb62a9b [fix] Fixed website configuration to correctly import local kepler files (#2454)\n- 39494866 [fix] update min value for hexagonal pixelate effect (#2453)\n- 8e7b0ad1 [fix] Effects: fix possible 'undefined' in effect parameters (#2452)\n- 84053786 [chore] Validate parameters for effects (#2450)\n- d60ef31d [feat] Introduce Foursquare cloud provider (#2437)\n- 82d616e4 [fix] ScenegraphLayer has broken lighting and textures (#2443)\n- 110c2991 [chore] bump deck.gl, luma.gl, loaders.gl (#2442)\n- f70b20ea [fix] effects: prevent time reset with invalid valese (#2441)\n- 3ca8df02 [chore] Add effect MapControl test (#2440)\n- 68bff82a [fix] effect-related UI fixes (#2439)\n- 82fc69e2 [chore] Refactored cloud provider flow for performance and multi provider support (#2436)\n- d975ea1e [Feat] support GeoArrow format (#2385)\n- ee6f0754 [feat] Effect manager - UI improvements (timezone, time slider, time dropdown) (#2433)\n- b5a6e9ce [chore] Making EffectPanelHeader actions configurable (#2432)\n- 1ae4cd02 [feat] UI updates for effects (#2428)\n- a69b0878 [chore] Effects - config refactoring (#2422)\n- bfec82e5 [chore] Bump to loaders.gl@4.0.0 (#2424)\n- e6e5a4c9 [Chore] export LayerBlendingSelector (#2419)\n- a1878138 [chore] SplitMap type changes (#2418)\n- 5e0ad511 [fix] Legend is rendered outside of widget (#2417)\n- 473bd801 [fix] feature menu not working in shadow DOM (#2416)\n- b995c9b5 [fix] Hexbin layer color aggregation incorrect on load (#2415)\n- 58f0bb71 [Chore] merge other properties in splitMap merger (#2413)\n- bcb8c4e8 [fix] long name in filter panel header (#2412)\n- b8fa6ce1 [chore] Remove paths from tsconfig (#2414)\n- 79002ea6 [feat] Support customized ref in useDimensions (#2409)\n- 4d723317 [feat] Update Icon Layer to allow passing in svg icons as a prop to bypass remote resource fetching (#2410)\n- 2ff3738f [fix] Viewports not always locked (#2408)\n- 975a4762 [fix] Using resolution-corrected mapState for image export (#2407)\n- 7fae622e [chore] adds additional properties to mock basemaps (#2411)\n- df1397fd [fix] handle empty properties in GeoJson file (#2381)\n- c8e2a9f1 [chore] move dev env to Node.js 18 (#2399)\n- bb559750 [fix] long names in tooltips (#2405)\n- c9c34c86 [chore] add custom classes to dropdown (#2404)\n- 22dd6236 [chore] Remove unused deps (#2403)\n- a36ec68b [fix] effect related fixes (split maps, shadows, timeline) (#2396)\n- 5e7dd9b5 [fix] Upgrade Mapbox SDK (#2397)\n- b54c1739 [chore] Upgrade to loaders.gl@4.0 (#2394)\n- e47ccc07 [fix] Re-enabled plugin section in home page (#2400)\n- 81a6e1fa [fix] Update layer domain in addLayer (#2393)\n- bed4b7f8 [chore] Removed abs paths in mock state and layer utils (#2392)\n- f1e654d8 [fix] place null values at the end when sorting table (#2391)\n- 4f51abc3 [chore] extra typing for effects (#2390)\n- 459ae555 [chore] fix lint in cmpEffects (#2389)\n- 87df1197 [feat] Effects: shadow color picker; use animation & current time (#2387)\n- dde3a6e3 [chore] Fix ColorMap type (#2388)\n- 08492a8a [chore] Export effects types/utils and incapsulate dnd logic into new hooks (#2384)\n- 2500a277 [feat] reorder tooltips (#2378)\n- fdecb052 [fix] minor effect-related fixes (#2380)\n- 5c16027d [chore] Drag&Drop context: extra check for the object type (#2379)\n- a958586d [fix] fix for process is undefined (#2376)\n- 9eb6b328 [chore] bump examples (#2375)\n\n## [3.0.0-alpha.1] - October 17 2023\n\n- a3521948 [feat] introduction of deck.gl effects (#2372)\n- c798961d [feat] Introduced dnd-context factory to better override dnd properties (#2364)\n- 673646ac [fix] fix map dropbox share (#2370)\n- ec0881d7 [fix] Fix react-map-gl mapbox api props (#2362)\n- d0a86587 [chore] Avoid confusion in viewstate context (#2361)\n- 1fcdfde9 [fix] fix image export (#2368)\n- 89043bd0 [fix] Fixed load remote map dialog exception (#2367)\n- 7f9f211b [fix] Improved validation of field pairs suggestions for LayerColumnConfig (#2359)\n- fa1edab9 [fix] add autoCreateTooltips as a prop in AddDataToMapOptions (#2358)\n- e8220b0e [chore] pass custom classes to ListHeader (#2357)\n- 5a9fa5bd [fix] Stronger AnimationConfig types (#2356)\n- a2fd52ca [fix] Fix mapbox/deck syncing issue (#2355)\n- cfee75a2 [fix] Text labels: can't set prop to false/0 with multiple labels (#2354)\n- 357f77a8 [fix] text outlines are barely visible after upgrade to deck 8.9 (#2353)\n- 9d99f0b6 [chore] Upgrade deck.gl to 8.9 (#2352)\n- 032ad763 [fix] Layer column config: sometimes a suggested field pair will hard crash (#2351)\n- 56afb092 [fix] remove <img> from field name when show in tooltip (#2350)\n- a9181f69 [feat] Table widged: pass getRowCell as prop (#2349)\n- 1f169df1 [fix] Improve data table horizontal overflow and dataset tabs overflow (#2348)\n- f2559445 [chore] Bump react-virtualized (#2347)\n- ced842ea [chore] Update public CDN URL (#2346)\n- 6ef400d2 [Fix] Dispatch click event instead of click() (#2345)\n- cf9cf21a [fix] Add guard for null legend label (#2344)\n- b5405f52 [fix] serializeLayer fixes (#2343)\n- 4383bffd [feat] Text layer: add outline width, outline color, background color (#2342)\n- a59d8342 [Fix] Resize observer crashes when passed a non-Element target (#2340)\n- ec35ea97 [feat] introduced jest to replace tape/sinon/enzyme for browser tests; upgrade typescript to 4.5.5 (#2339)\n- 85fa66f3 [feat] Adding applyLayerConfig action (#2337)\n- ae26de55 [fix] Fix website kepler.gl example (#2338)\n- d14e7ff4 [chore] Updated more deps to be compatible with react 18 (#2335)\n- 70128119 [chore] updated modal and panel title types to react 18 (#2334)\n- a0e5db72 [chore] Upgrade to react 18 (#2323)\n- 52c69c54 [feat] Add Deck onAfterRender callback prop support (#2332)\n- 0b8ae8bc [feat] deck.gl render callbacks (#2330)\n- 6596187b [fix] Remove fixed height for list item (#2331)\n- bcd3ff1b [fix] dropdown in color scale does not work (#2324)\n- 203829aa [fix] dropdown list alignment and spacing (#2325)\n- ba6259d3 [Fix] polygon context menu is offscreen (#2326)\n- 6fd7f7a9 [fix] When editing a custom basemap style do not unintentionally drop extra properties (#2327)\n- b3472a37 [chore] Upgrade deck to 8.8.27, loaders to 3.4.14 (#2320)\n- d9c164bb [Feat] Support WKB geometry column in CSV (#2312)\n- cfada4d5 [Chore] delete typeahead mousedown listener, pass onOptionSelected to ListItem (#2319)\n- 2714c755 [fix] fix horizontal \"over scrolling\" and misalignment of header row vs. data cells (#2318)\n- d28674ea [feat] Add onMouseMove callback (#2317)\n- 66a6364f [feat] add prop to allow turning off custom webkit scrollbar CSS (#2316)\n- 69ce4d06 [Chore] export action creator (#2315)\n- e051eb55 [fix] Fix map attribution color (#2314)\n- 090ef0ba [fix] Conditionally apply escapeXhtml to prevent export image crash (#2313)\n- 8bb0d469 Introduce new fsq studio section in home page (#2308)\n- 3e39337e updated cdn from unfolded to fsq (#2307)\n- 5bae745b [chore] drill disabled prop to layer-type-selector (#2274)\n- b6a2b804 [feat] Edit a custom base map style redux (#2281)\n- 74bc22a6 [feat] add complimentary base map style property (#2280)\n- e056d01a [feat] Remove a custom map style from the base maps side panel (#2279)\n- e09ed287 [fix] map style selector: provide backup UI content (#2277)\n- 963df0cf [chore] Update SavedCustomMapStyle accessToken property to be defined as optional (#2278)\n- 46df6014 [Chore] improved saved layer and interaction type (#2275)\n- 2dff78ff [fix] Long field names in filter UI obscure the delete icon (#2273)\n- 32356b46 [chore] pass through className prop to TippyTooltip (#2272)\n- 52fb6844 [chore] Add nx module tag (#2271)\n- b255d60e [chore] Add tooltip format (#2269)\n- 7b45e4f1 [fix] collapsible layer config group ui improvements (#2268)\n- a1689540 [chore] update browserslist deps (#2267)\n- 5db83285 [chore] specify filter id in addFilter (#2266)\n- a8599dcf [feat] Update custom map style updater to support managed map style (#2264)\n- 84c07360 [feat] Support map overlays (#2260)\n- 8312d060 [Chore] Upgrade to Node 14 (#2257)\n- 23763f0b [Chore] Add layer header action component to deps (#2265)\n- 043db65f [Chore] export single color palette selector (#2262)\n- d362fc21 [feat] H3 Layer separate layer opacity into unique fill opacity and stroke opacity (#2261)\n- a1084016 [fix] Use auto width for pinned column in table preview (#2259)\n- c79e9f90 [Chore] rewrite stack overflow functions (#2258)\n- 9d57f575 [chore] upgrade gl dependency version (#2256)\n- 11242f01 [Chore] Added collapsed prop for layer config group (#2255)\n- 8d79f7d0 [chore] export types and components (#2254)\n- 4a659e84 [feat] H3 Layer: default text label anchor to middle position (#2252)\n- acd05e91 [chore] export more components and types (#2251)\n- f6be2491 [Chore] expose functions and types to fix deep import issues (#2250)\n- 5fcbcdab [feat] H3 Layer: Add fill transparency and stroke color settings (#2249)\n- 94cb2a15 [feat] Layer property additions: H3 Layer: Add text labels (#2243)\n- 9ba6bcdd [Chore] add exports to expose functions and components types (#2242)\n- 88dd4b36 [fix] exported image has a thin white bar at the bottom (#2241)\n- f562fbe0 [fix] range slider doesn't work when step < 1 in dataset filter (#2240)\n- fa3bb9c9 [fix] Overlapping column names in drop down menu (#2239)\n- 796a9d29 [fix] time ticks are the same when using Minute to set interval (#2238)\n- b9cd1ec4 [Fix] Map popover z-index less than size panel (#2237)\n- 8de7ae41 [Fix] mapbox logo has not been styled correctly (#2236)\n- ed5cb8ad [Chore]: Add onClickControlBtn prop to MapControlButton to pass additional callbacks (#2235)\n- 97126155 [fix] Remove split map controls from legend in exported image (#2234)\n- bc1cfc55 [Chore] use unfolded cdn for base map, layer type select and icon layer svg (#2233)\n- 07f8c9f9 [feat] Add extraReducers arg to keplerGlReducer.initialState (#2232)\n- a112c0e9 [Fix] Feature Action Panel menu and editing tooltip are cut-off in dual map mode (#2231)\n- 7fb4cada [fix] Fix types for Typescript 4.8 (#2229)\n- 41c80993 [Chore] Pass onBruch, filter and datasets through range slide to plot (#2220)\n- f80853b0 [Chore] add test for vis state schema column save undefined typeerror (#2219)\n- e1e165e6 [Feat] Added new options parameter to override single action reducer default behavior (#2217)\n- 1c1345b4 [Bug] preserveLayerOrder when replace data (#2214)\n- c06ceca7 [chore] Exported layer utils methods and added onDragStart onDragEnd props (#2210)\n- 7d3c6026 [fix] Fixed bug when switching to dataset layer view (#2209)\n- 2275b8e6 [chore] Make dataId non-optional in layer config (#2205)\n- c130a2f5 [Fix] vis state schema column save undefined typeerror (#2211)\n- d8a5defa [Fix] ColorBlock component TypeError: e.color.slice(...).join is not a function (#2212)\n- 1380644f [Fix] time widget animation: apply same duration for last time filter (#2218)\n- 1094e734 [BUG] fix dropdown list fail to update when prop change (#2213)\n- dafec9b8 [Chore] add exports for scenegraph to layers index (#2215)\n- 14c6d014 [chore] layer testing support (#2216)\n- e5686fda [Bug] Fix composer types, schema types (#2208)\n- 28fbcdbf [feat] Convert layer order from idx to layer IDs (#2203)\n- e1ccfdff [Enhancement] Allow empty column when layer created from config (#2206)\n- 30792f47 [Fix] Add selected style for light dropdowns (#2207)\n- 44aafd15 [Feat] add kepler.gl to info.source in exported kepler.gl.json (#2195)\n- 95fd2369 [fix] Empty cells with date time data are filled with Invalid date (#2201)\n- 3b73dc07 [Feat] Add display format setting for table/tooltip (#2199)\n- 87b79c3b [Feat] add replaceDataInMap action (#2198)\n- e9896def [Feat] add table config with custom number format (#2192)\n- e635e4cb [fix] Fixed crash when switching to dataset layer view mode (#2191)\n- a246574e [Fix] Auto-display legend in split mode + Fix legend and layer panel bugs (#2190)\n- 2d141ff5 [fix] Layer drag and drop label is barely visible on light map (#2189)\n- 70cde834 [Fix] Drop the same layer multiple times to one map (#2188)\n- 2f5da5ec [Chore] Removed unneeded preventDefault (#2177)\n- b364f3d8 [Fix] intervals rendered incorrectly in time widget (#2183)\n- c8475737 [feat] Create layer correctly from saved layer config (#2179)\n- 4c6e99e3 [fix] previous drawn-selected geometries are lost after click Select geometry (#2175)\n- 79d8c756 [fix] support Polygon and LineString mode in idToPolygonGeo (#2182)\n- 85897309 [Fix] hide pinned selection outline when layer is hidden (#2181)\n- d441d5fd [feat] three dots button change (#2180)\n- 4dd27abe [Feat] Drag and drop interaction for split map (#2172)\n- 485252ad [fix] Improved split+unsynced mode for better handling (#2176)\n- 90572720 [fix] handle undefined values in updateViewport (#2178)\n- afee4800 [fix] hide side panel close button when data preview is open (#2174)\n- 695bcccd [feat] Improve disabled zoom lock text styling (#2173)\n- 9fc98e86 [Feat] Unlocked split map viewports (#2170)\n- 8896dc13 [fix] fix visible layers toggle for split maps mode(#2168)\n- f0727c97 [fix] type fixes for map popover (#2169)\n- 04451827 [Feat] enhance mouse selection toolset (#2164)\n- f640822a [Fix] round the float number to up to 4 decimal places in table (#2163)\n- a41e0118 [Chore] Add more types for schema (#2162)\n- 502c1ba3 [fix] remove duplicates from changelog (#2145)\n- 7d996a68 [fix] Fix onViewStateChange callback (#2154)\n- 2e57238b [chore] Type and export fixups (#2152)\n- 245ac53b [chore] update filter types (#2153)\n- ce4e5c7e [Fix] Datasets and basemap attributions separated by \"|\" (#2150)\n- 1fd7bad0 [Fix] Datasets attribution width styling (#2149)\n- 06f085db [Feat] render dataset attributions in map container (#2148)\n- 425a6011 [chore] ts fixes (#2147)\n- abb0d1ce [fix] improve handling of \"interpolate\" mapbox colors during basemap switching (#2144)\n- a6a6b270 [fix] fixes to async merger (#2139)\n- 9d568af3 [Feat] Support async mergers (#2129)\n- 28c34901 [Chore] support offset in map legend panel (#2130)\n- 953711ac [feat] Introduced updateDatasetProps to update dataset information (#2133)\n- 332a94ad [Feat] Add arrow and light theme props to TippyTooltip (#2140)\n- c79896be [Chore] Export LayerGroupColorPickerFactory from kepler-wide components (#2138)\n- bf890fa9 [chore] Update react-modal version (#2131)\n- def2ce12 [fix] Basemap overlay blending updater must pass through entire payload (#2137)\n- e2848008 [Feat] Add \"No Basemap\" option with map background color control (#2136)\n- 5cc6faab [fix] fixes the logic to set map overlay type properly when switching layer type (#2135)\n- f605167f [Chore] Request map styles on demand (#2134)\n- fb829922 [Feat] Add list toggle to filters (#2115)\n- 20fcb662 [Bug] Object and array field types made numeric (#2127)\n- 31e44350 [Chore] export LayerTypeListItem type (#2122)\n- 390f5af8 [chore] changes to order layers by datasets (#2114)\n- 210af2b4 [fix] remove constant scroll around layer config group (#2116)\n- a438383b [feat] Add minZoom, maxZoom, maxBounds (#2124)\n- 0e5a4bbc [Bug] data modal and data table scrollbar style (#2123)\n- cdb69f4a [chore] Export parseGeoJsonRawFeature from utils (#2121)\n- 3d5db39e [feat] add support for object and array field type (#2120)\n- 1f20ef71 [Feat] Introduce MapPopoverContent (for tooltip charts) (#2119)\n- 918aaf98 [Enhancement] Render data table with smarter cell size, prevent scroll back (#2117)\n- b1d92c85 Bump ua-parser-js from 0.7.25 to 0.7.33 (#2112)\n- 630e8ede [Enhancement] Improve Feature action panel style (#2099)\n- 20134f01 [fix] Fixed time filter toggling and display the correct filter (#2098)\n- 83673fd5 [chore] bump nebula; add picking width for polygons; preserve rectangles; (#2097)\n- eeb50d6a [fix] Checking if drawing is active when delete an editor feature (#2093)\n- d1abf3ee [Enhancement] Fix dropdown list disabled color (#2094)\n- 943ee50a [Bug] fix update layer type reset layer dataId, new layer at the top (#2096)\n- ac5f490e [fix] fix layer config group collapsible content overflow (#2092)\n- 608fa0f3 [Feat] refactored AnimationControl to handle both layer and minified filter playback (#2079)\n- 409db23e [fix] CSS fixes to avoid conflicts with Jupyter styling when embedded without iframe (#2095)\n- e1b70000 [Enchancement] number formatting improvements (#2109)\n- cf8d3321 [Enchancement] number formatting improvements (#2106)\n- c9cc689c [fix] use dataset name as default h3 layer name (#2100)\n- 7f01ca1c [fix] Trip Layer: issues for path from 2 points (#2101)\n- 92bae8e0 [fix] Icon Layer - Labels are visible even if layer is hidden (#2102)\n- 47cc281c fix: Open map control and geocoder for extension (#2103)\n- 0cd0e379 [fix] Improve render cell size script perf for data table rendering (#2104)\n- 4e06992b [Fix] Image export change resolution (#2105)\n- 7d9d54b8 [Feat] Map overlay blending (#2086)\n- f4329fcc chore: more specific error message for context lost error (#2090)\n- 14ef4366 [Feat] Disable a layer after an error in Deck (#2072)\n- d24ea4a5 [fix] dont show hidden layers as options in polygon dropdown menu (#2085)\n- fd3a7a8b [fix] Prevent the app from crashing on geojson layer hover (#2087)\n- a66f98f9 fix(filters): fix for broken filter state, load crash (#2069)\n- 47b1124d fix 3d buildings rendering (#2080)\n- 8edb5b2e [fix] lock react-vis version to prevent CI fails (#2082)\n- 9416be4a save and merge editor features in map config (#2071)\n- 217b89e7 chore: Child support and type exports for FeatureActionPanel (#2070)\n- f53188b9 show filtered out and hidden layers as options in polygon filter menu (#2068)\n- b53a6b75 [fix] Move FeatureActionPanel to class component (#2067)\n- 0f7a4242 fix Cant right click on polygon or rectangle filters to get the menu (#2066)\n- db549742 bump licence year to 2023 (#2073)\n- a22e4259 [Feat](Editor) Replace react-map-gl-draw with Nebula.gl (#2054)\n- 3de77995 [fix] fix import in demo-app carto provider (#2050)\n- 3e7581b1 [Feat] Add hasStats prop to data table adjust first cell size (#2040)\n- 15d1426e FIX: Fix margin for style panel icons (#420) (#2041)\n- a865ce8b [fix] correct provider downloadMap type (#2049)\n- c53d81fd Bump moment-timezone from 0.5.33 to 0.5.35 (#1966)\n- efa32f75 [fix] include CenterFlexbox in common components (#2035)\n- 5f3d185f correct @kepler.gl/styles types file location (#2034)\n- 76e1a4d0 [fix] Updated dataset item cursor style (#2013)\n- d0bcaa89 [Fix][perf] String filter freezes browser when loading a large dataset (#2012)\n- 1214bd9d [fix] Time filter: Add padding if min/max values are the same (#2011)\n- 36657380 [fix] Fixed hex tile play animation (#2010)\n- 6c266665 [Fix] dropdown item title (#2009)\n- 81fcbb41 Bump loader-utils from 1.4.0 to 1.4.2 (#2025)\n- f1b7e1a8 [Fix] no aggregation options can be selected for date field when groupby (#2008)\n- b9a04468 [Feat] Replaced filter enlarged with view: side | enlarged | minified (#2007)\n- 6692585e Handle loading map style gracefully (#2005)\n- 920659ff Add header cell stats control toggle (#2004)\n- dbba7daa [Chore] bump and fix examples for v3.0.0.alpha.0 (#2030)\n\n## [3.0.0-alpha.0] - November 5 2022\n\n- 4eb6b24b [Chore] dependencies update + publish process update (#1978)\n- 72f201c9 kepler.gl-jupyter: Fixed wording in documentation (#1938)\n- 791bbe21 [Feat] make data table header cell overridable (#1995)\n- 77ba9509 deck upgrade fix (#1997)\n- 9b483b22 better regex for mapbox style boundary detection (#1996)\n- 306da3a2 add onClose for color picker (#1992)\n- 13bcaa06 update isRGBColor (#1991)\n- 2845432e Moved animation control button to the right (#1990)\n- 51a05ffe color picker crashes studio inside iframe (#1989)\n- 73dba52e [Chore] Extra memoization for components to prevent re-rendering (#1988)\n- 4e88e839 [Bug] \"load from storage\" and \"Share\" modals fix (#1976)\n- 9029b8ea [Feat] Hide Mapbox attribution when using non-Mapbox tiles (#1975)\n- d77ffcb4 [Feat] Improve fieldpair detection logic, add altitude (#1968)\n- b70c35c2 [Chore] refactor dynamic require (#1971)\n- 8878cff4 [Fix] polygon filter reload (#1970)\n- ea738594 [Chore]: Typescript 4.4 fixes (#1957)\n- 49321f87 [Feat] mobile bottom widget styling (#1930)\n- db39b496 [Chore]: Technical: Isolate components (#1967)\n- 90248326 [Chore] remove iconComponent from interactionConfig (#1973)\n- 64542aa2 [Chore] bump to deck 8.6.0 (#1959)\n- ab5f9f33 [Fix]: Item selector closeOnClickoutside conflict with portable (#1958)\n- 9b81e49f [Chore]: Technical: Isolate schemas (#1962)\n- 57dea6a3 [Chore]: Technical: Isolate reducers (#1961)\n- 28578e76 Import for filters fixed (#1965)\n- 359e0387 [Bug] Fix getSampleData import (#1964)\n- c2cb8213 [Chore]: Technical: Isolate table-utils (#1949)\n- af79e2e5 [Bug] fix layer order not correctly reloaded (#1956)\n- 47a184c6 [Bug] Fix Range brush maximum update exceeds crashes (#1955)\n- f9485018 [Enhancement] improve tooltip format label, make it more intuitive (#1954)\n- a42aae33 [Enhancement] use portable in item-selector (#1953)\n- 6e2fe3dd update layer selector types; get length for dc; (#1951)\n- 0630c8b7 fix deck.gl version for src utils (#1950)\n- d5f0f0cf [Docs] fix broken link (#1952)\n- 5e20ac68 [Chore]: add class names to map control (#1940)\n- c7ed4dbd [Chore]: change types for modal (#1939)\n- f53117fb [Chore]: pin browserlist (#1935)\n- 8ea93d40 [Chore]: Technical: Isolate actions (#1948)\n- f828f695 [Feat]: Passing root context to tippy\n- 34ebb889 [Chore] Fix debounce typing\n- 3db186e5 [Chore] bump deck to 8.5.7 (#1934)\n- 99b38d26 [Feat] Implemented new feature flag by passing features flags prop (#1933)\n- 50eda73f [fix] 3d buildings aren't rendered without layers (#1931)\n- f21afd8d [Chore]: Technical: Isolate tasks (#1941)\n- 88039cd3 [Chore]: Technical: Isolate cloud-providers (#1942)\n- a98a015b [Bug] Fix getSampleData util import (#1947)\n- 4615c480 [Fix]: Kepler.gl site issue fixed (#1944)\n- f2459c6c [Chore]: Technical: Isolate utils (#1876)\n- 88e15d5e [Fix] fix lint (#1932)\n- 3301a7c5 [Chore]: bump deck to 8.5.4, loaders to 3.0.9 (#1928)\n- 0889d0d1 [Enhancement] (Map Control) use lazy tippy to improve map legend rendering perf (#1924)\n- 82baedfb [Chore](Types) move howto button out, add layer conf types, yarn lint (#1926)\n- c9ef6972 [Chore]: extra export (#1925)\n- 4fc85960 [Chore]: layer-utils, map-utils refactor (#1923)\n- 5c38f851 [fix] prevent deck crash due to layer id duplicate\n- fb3f35ba [Chore]: Use relative import in test-utils (#1921)\n- eff5f902 Map Control: Use MapControlTooltip with TippyTooltip (#1920)\n- 5551abd6 [chore] Export IconButton type (#1919)\n- d358b3a8 fixed findMinFromSorted when list is null (#1918)\n- 3a3be58d [Chore] Upgrade to deck 8.5.2 (#1917)\n- 20d39b8c [Enhancement] add bin to filter hiitogram construct (#1673)\n- 41414ceb [Enhancement] change export video playback button order (#1916)\n- 38734422 fix color pick type using react-color types (#1915)\n- f739a499 chore: Updated filter-selector, item-selector, range-slider file typescript definitions (#1902)\n- 40ac3068 [chore] test valueAccessor in field (#1906)\n- f82494d6 [Feat] Use custom style token if available instead of the default token (#1913)\n- 77dc2560 [BUG] Fix crash after layer type change (#1912)\n- ac59ac7d [Bug] rename dataset should not use spread (#1911)\n- 486e3239 Prevent \"Cannot read property 'layers' of undefined\" error (#299) (#1910)\n- fae2058f [Bug] Fix map saved with empty filter cannt be load; validate empty filter.name when merging (#1909)\n- 26b5f849 add type to keplerTable (#1905)\n- bec013e5 improve reducer updater typing, change visstate to be more relaxed (#1908)\n- 6c51a2ae [feat] Hubble gl integration (#1899)\n- d31fe649 [Bug] Fix mouse event evt.point evt.lngLat undefined crash (#1903)\n- 39427d46 [Bug] fix trip layer timestamp check (#1904)\n- cb76ae0f [Enhancement] render warning in layer panel header (#1901)\n- 9d171c60 [Enhancement] set initial layer config when set layer type (#1898)\n- 8d35d9b8 [Chore] Export more type def (#1890)\n- d90cd188 [Chore] fix types and missing import (#1891)\n- 28cbb759 update shader modifications for deck 8.4.16 (#1892)\n- 66de62cf Fix crash: visualChannels: Cannot read property label of undefined (#1886)\n- 57f77dd2 deck to 8.4.16 (#1889)\n- 41dbd570 [Enhancement] add disableDataOperation to dataset (#1897)\n- 1f5e26c8 [Enhancement] pass schema to processKeplerGlDataset (#1885)\n- 156f898b [Bug] fix comparison tooltip color and position (#1887)\n- 6c99bb04 [Bug] Disable layer copy when layer is invalid (#1882)\n- dfd73a53 add supportedDatasetTypes to layer, show dataset selector even if there is only 1 or no option (#1883)\n- 40a82dfa [Enhancement] disable layer column selection if empty (#1888)\n- 9c042fe5 Bump follow-redirects from 1.13.3 to 1.15.1 (#1871)\n- 2a55a1e3 [Enhancement] Improve style of layer header panel (#1881)\n- ceb23e21 fix for cluster layer z-fighting; fix - render 3d building map style only once (#1874)\n- a983be75 [Bug] allow tooltip format to apply to aggregation layer hover (#1872)\n- 723e6050 FILED_TYPE_DISPLAY -> FIELD_TYPE_DISPLAY (#1879)\n- 7d328315 Chore: Fix lint script and issues (#1862)\n- 940f9aad [Chore]: Technical: Isolate styles (#1861)\n- ad7646ac [Chore]: Technical: Isolate localization (#1858)\n- e798f317 Middleware isolation (#1860)\n- 6c178d77 [Chore]: Technical: Isolate processors (#1857)\n- 9e315d25 [Chore]: Technical: Isolate layers (#1856)\n- c1e20348 [Feat] Upgrade deck.gl@8.4.11 luma.gl@8.4.3 loaders.gl@2.3.12 (#1674)\n- b668fd28 [Chore]: Technical: Isolate deckgl-layers (#1851)\n- 9feddc66 Fonts issue fix (#1846)\n- 9a3da3c0 [Chore]: Technical: Translate deckgl-layers/cluster-layer (#1815)\n- 10868ecf [Chore]: Technical: constants and types modules isolation (#1840)\n- fe293e71 [Chore]: Technical: js to ts convertion components root modals (#1801)\n- 55abc874 [Chore]: Technical: Notification item types added (#1824)\n- bd8c3327 [Chore]: Technical: Translate map components to typescript (#1803)\n- 371649c6 Debounce typings added (#1825)\n- 1034c33d Lodash.memoize typings added (#1827)\n- 69f8534d [Chore]: Technical: fix linting errors of @types/styled-components plugin (#1834)\n- 5ee0cd4f [Chore]: Technical: add types for side panel root components (#1822)\n- 9bee093e validate url of Add data modal (#1837)\n- b7d8edf4 [Chore]: Technical: add types for layer panel components (#1819)\n- 7b95c236 hide layer size legend with nullish label (#1836)\n- ecc743af [Chore]: Technical:layer base config data allow to be null (#1835)\n- 2b51c7bb [Chore]: Technical: Fixed errors happening in folders/files due to the addition of @types/styled-components: components/common/slider (#1831)\n- e27cf134 [Chore]: Technical: fix attributes of styled components animation-control (#1829)\n- 442d1b23 [Chore]: Technical: add types for filters (#1809)\n- fc8ab5af [Chore]: Technical: Translate deckgl-layers/hexagon-layer (#1818)\n- 959f1e0b [Chore]: Technical: Translate deckgl-layers/grid-layer (#1816)\n- cbd26743 add types for styled components in styles (#1830)\n- f7715892 [Chore]: Technical: Translate components to typescript (#1814)\n- a5a347ba [Chore]: Technical: Translate components to typescript (#1812)\n- 9225e005 Throttle typings added (#1826)\n- f0671f06 [Chore]: Technical: add types for editor component (#1797)\n- 4e8197d5 [Chore]: Technical: add types for processors (#1798)\n- 47e4963e [Chore]: Technical: add types for side panel common (#1807)\n- 0d3c98c8 [Chore]: Technical: add types for filters side panel (#1799)\n- 8c5e5075 [Chore]: Technical: Translate layers final changes (#1783)\n- e663bb16 [Chore] fix typo in docs (stule -> style) (#1823)\n- 2d557df3 Typings for some lodash packages added (#1817)\n- ca45cef8 [Bug] validate s2 token in s2 geometry layer (#1805)\n- 7453b951 [Chore]: Technical: components/geocoder translated to typescript (#1808)\n- 5b918e00 Review fixes (#1813)\n- ae1173ec [Chore]: Technical: Translate deckgl-layers/layer-utils typesfix (#1791)\n- 6a7d44bc [Bug] Build fix (#1811)\n- 8ac5bbc6 [Bug] visual channels cannot read property 'label' of undefined (#1804)\n- b7c6c8df Translate deckgl-layers/3d-building-layer to .ts (#1794)\n- a5bcd814 [Chore]: Technical: Translate root components to typescript (#1790)\n- 258c82da add types for svg-icon-layer (#1796)\n- 0de32bec [Chore]: Technical: Translate deckgl-layers/line-layer (#1792)\n- 013b9878 [Chore]: Technical: Translate deckgl-layers/column-layer (#1793)\n- f64b551f [Chore]: Technical: Translate tasks (#1779)\n- 65228a85 [Bug]: fix grid hexbin and cluster layer crash (#1795)\n- 7ada98a0 [Chore]: Technical: Translate examples/custom-map-style (#1780)\n- 84312384 [Chore]: Technical: Translate deckgl-layers/layer-utils (#1789)\n- ec3351b6 [Chore]: Technical: Translate cloud-providers (#1778)\n- 24e3549c Added deckgl-typings from community repo (#1787)\n- 68abc5b5 [Chore]: Technical: Translate geojson-layer (#1757)\n- 2d2ba1d7 [Chore]: Technical: Translate hexagon-layer (#1775)\n- 543045d0 [Chore]: Technical: Translate heatmap-layer (#1773)\n- cf57260a [Chore]: Technical: Translate trip-layer (#1777)\n- e80c18b1 [Chore]: Technical: Translate line-layer (#1776)\n- 9a0ad623 [Chore]: Technical: Translate cluster-layer (#1774)\n- bc18a6c4 [Chore]: Technical: Translate scenegraph-layer (#1768)\n- 831504f9 [Chore]: Technical: Translate icon-layer (#1763)\n- b87bba3a [Chore]: Technical: Translate grid-layer (#1761)\n- 079da4cc [Chore]: Technical: Translate h3-hexagon-layer (#1762)\n- cd05dd4b [Chore]: Technical: Translate point-layer (#1764)\n- 0b3f2c0c [Chore]: Technical: Translate s2-geometry-layer (#1765)\n- 18342926 [Chore]: Technical: Translate mapboxgl-layer (#1755)\n- 9b695f85 [Chore]: Technical: Translate aggregation-layer (#1753)\n- 13ba6bb7 [Chore]: Technical: Translate arc-layer (#1749)\n- a3ada4e9 UN-14 Technical: Translate components/[root files] to typescript: side-panel (#1712)\n- fb2190f1 [Bugfix]: Fixed Babel configuration (#1754)\n- d9e9d8aa [Chore]: Technical: Translate layer factory (#1748)\n- c0f75341 [Chore]: Technical: Translate components/common final part (#1750)\n- b06dfb1c [Chore] Typescript 'components/common/slider' (#1740)\n- 0057a1e4 [Chore]: Technical: Setup for different Visual channels per layer (#1751)\n- 1193258b UN-14 Technical: Translate components/[root files] to typescript: maps-layout (#1713)\n- 8a06f711 Moving bottom-widget to ts (#1710)\n- dd14702e [Chore]: Technical: Translate base-layer (#1746)\n- 4a687ed4 [Chore]: Technical: Translate index and other files (#1745)\n- 7a11260d [Chore]: Technical: Translate table utils (#1742)\n- 7ee74ebe [Chore]: Technical: Translate filter-utils and gpu-filter-utils (#1744)\n- e5d5d1ba [Chore]: Components/common 1st part (#1729)\n- d8abca9d [Chore]: Technical: Translate utils (color and data) (#1732)\n- 55a7b510 [Chore]: Technical: Translate utils (dataset-utils and export-utils) (#1734)\n- 0505fda4 [Chore]: Technical: Translate redusers (vis-state) (#1727)\n- 5304d4dc [Chore]: Technical: Translate utils (files without d.ts typings) (#1728)\n- 30616984 [Chore]: Technical: Translate redusers (UI-state and provider-states) (#1726)\n- 2ba94858 [Chore]: Technical: Translate actions to typescript part 2 (#1725)\n- e36cac5b UN-12 Technical: Translate redusers (main files) to typescript (#1722)\n- fb170ae0 [Feat]: Technical: Translate actions to typescript (#1704)\n- cb542853 UN-13 Technical: Translate schemas to typescript (#1721)\n- 8121893c [Feat]: Technical: Translate redusers (map-state and map-style) to typescript (#1717)\n- fbe626be [Feat]: Technical: Translate redusers (composers and combined-updaters) to typescript (#1711)\n- d8d7e44f [Feat]: Technical: Translate localization to typescript (#1705)\n- 614a5003 [Feat]: Technical: Translate templates to typescript (#1702)\n- 0ef5ccd8 [Feat]: Technical: Translate middleware to typescript (#1703)\n- 20ec6666 [Feat]: Technical: Translate styles to typescript (#1701)\n- 11c5b4cc [Feat]: Technical: Translate constants to typescript (#1697)\n- 283586d0 [Feat]: Technical: Translate connect to typescript (#1700)\n- 995f3f93 [Feat]: Setup build process for ts source code support (#1688)\n- b71dd6b4 [Chore] Update license year 2022 (#1689)\n- 0dfc7e1b [Bug] fix filtered datasets memoization (#1678)\n- 1e8b3c1a [Enhancement] order layers by dataset (#1675)\n- f9ae108a [Enhancement] extract layers list to a separate component (#1665)\n- 52993525 [chore] export types, add script to build types (#1636)\n- 6fb00fa0 [Bug] fix pin table column overide dataset (#1625)\n- 22ea7a9d [Bug] do not display geocoder dataset in side panel\n- a20db971 [Feat] allow custom value in layer slider (#1631)\n- 5e6b1c45 [Bug] allow empty data rows (#1624)\n- 612e18a9 [Feat] support pin map legend in map control (#1614)\n- bfcce3fd [Enhancement]Allow changing MAX_DEFAULT_TOOLTIPS (#1627)\n- a810ee13 [Chore] added more properties to export layer type (#1613)\n- 0931a55c [Enhancement] Render map control tooltip with TippyTooltip (#1612)\n- d0fb78de Add registry-url to avoid 404 issue when publishing keplergl npm package (#1623)\n- 9936b7b7 [Feat] add color picker to dataset tag (#1608)\n- 3e3d1631 [Jupyter] Update example versions\n- 5b442c5d [Jupyter] keplergl==0.3.2 (#1619)\n- a56206c8 keplergl-jupyter v0.3.1\n- e12039c6 [Feat] Add Copy Button to Export Map Dialog (#1609)\n- 3f876ac1 [Jupyter] bump kepler.gl js version release keplergl-jupyter=0.3.1 (#1617)\n\n## [2.5.5] - September 12 2021\n\n- 392e9a21 [Bug] lock deck.gl to 8.2.0 (#1602)\n- 6121a343 [Chore] Fix explicit src import (#1596)\n- 0b71f399 [Bug] fix locale panel (#1603)\n- 8b42be29 [Bug] Fix integration with CARTO (#1600)\n- e8ba7a05 [Feat] add setMapControlVisibility action to set mapControl visibility (#1590)\n- 78274562 [Feat] add supportedFilterTypes to dataset (#1594)\n- 41b364a6 [Enhancement] s2 updateLayerMeta: push instead of spread (#1593)\n- 1b5e0235 fix for long processing time of data-utils::unique (#1592)\n- 91a52b16 [Enhancement] Use layer.visible prop in deck.gl when toggle layer visibility (#1591)\n- c106ee06 [Chore] Create factory for LayerLegendHeader and LayerLegendContent (#1589)\n- 878750c4 [Feat] Add MapsLayoutFactory for custom split map layouts (#1588)\n- d8db8f6f [Chore] Refactored map control and decoupled action components (#1552)\n- 2f8b19f2 [Feat] update keplergl-jupyter widget for JupyterLab 3, add build for conda-forge (#1572)\n- 6947c8c8 [Feat] Added Russian localization (#1570)\n- 9726a400 [Docs] Data container upgrade notes (#1575)\n- 070b04b2 [Feature] Abstract Data Container (#1555)\n\n## [2.5.4] - July 31 2021\n\n- 62d03ab2 [Examples] update replace-component example (#1557)\n- 089bb7a9 [Jupyter] Make showing User Guide link optional for jupyter widget (#1559)\n- 5985d201 [Bug] Fix screenshot with images (#1558)\n\n## [2.5.3] - July 18 2021\n\n- a4a6734a [Docs] fix add data to map docs (#1551)\n- 8524061e [Enhancement] add displayName to field and show displayName whenever available (#1538)\n- a0d2a76b [Feat] Save and load highlightColor from layer config (#1550)\n- a9b2ba07 [Examples] fix panel toggle exmaple, add layer hove info demo (#1549)\n- 9bcb3415 [feat] Using tippy for map popover (#1539)\n- 2e6f8b79 [Chore] refactored side-panel from class to functional component (#1536)\n- 16fab11c [Bug] Geojson layer is not updated when dataset updated (#1533)\n- 29cf0829 [Enhancement] add toggleLayerAnimationControl action (#1537)\n- 01e93966 [Enhancement] add disableClose to map control (#1529)\n- c6e5b8a6 [Feat] use appName in exported image html json map and csv data (#1528)\n- 72354560 [Bug] Fix geojson layer duplicated index (#1530)\n- 1ed0fd6d [Bug] fix histogram in range (#1531)\n- 305edfcd [Docs] Update Map Styles Link (#1512)\n- 1890133d [Chore] Update peer dependencies for styled-components (#1527)\n\n## [2.5.2] - June 28 2021\n\n- 1c7521b1 [Bug] Fix center map accuracy (#1502)\n- b662892a [Bug] trim string value before passing to type analyzer (#1503)\n- d35ad489 [Website] Add ecosystem Section (#1491)\n- 1935c70a [Chore] Bump ini from 1.3.5 to 1.3.8 (#1385)\n- b7d333b4 [Chore] Bump y18n from 3.2.1 to 3.2.2 (#1449)\n- aeb8b45a [Chore] Bump ssri from 6.0.1 to 6.0.2 (#1460)\n- 86577263 [Chore] Bump ua-parser-js from 0.7.22 to 0.7.28 (#1471)\n- f0fda0e4 [Chore] Bump handlebars from 4.7.6 to 4.7.7 (#1472)\n- 027aecfa [Chore] Bump url-parse from 1.4.7 to 1.5.1 (#1473)\n- 6d5981a0 [Chore] Bump hosted-git-info from 2.8.8 to 2.8.9 (#1474)\n- 54690fc8 [Chore] Bump browserslist from 4.14.7 to 4.16.6 (#1494)\n- 846ec388 [Chore] Bump dns-packet from 1.3.1 to 1.3.4 (#1497)\n- c6def591 [Chore] Bump ws from 6.2.1 to 6.2.2 (#1500)\n- 614750f4 [Feat] Make keplergl-jupyter work with JupyterLab 3 (#1501)\n- b4fcf7be [Feature]: add copy geometry to feature action panel (#1495)\n- d786d0f3 [Bug] fix arc layer configurator render crash (#1490)\n- b24cc57a [Enhancement] Support elevation in Icon layer (#1483)\n- d51f3050 [Enhancement] Support elevation in Line layer (#1481)\n- a09cd589 [Enhancement] Elevation zoom factor toggle (#1478)\n- 8a6d2635 [Enhancement] add Japanese translation (#1469)\n- 910eb5e7 [Chore] Move 'uber-licence' to devDep (#1450)\n- 0b03f3a6 [Docs] fix typos on playback readme (#1482)\n- 14c35fc0 [Doc] Add example using none mapbox base map (#1440)\n\n## [2.5.1] - Mar 30 2021\n\n- 16703c0b [CHORE] add utils.js to package.json\n- a15109b3 [Feat] add timezone and timeFormat prop for time display in animation control and time - widget (#1411)\n- 13c6171e Bump elliptic from 6.5.3 to 6.5.4 (#1435)\n- cdcc0eea [Enhancement] make panel tab a factory (#172) (#1412)\n- 173811a3 [bug]: Fixed range slider null selection bug (#1413)\n- df3fee5c [Bug]: Updated babel dependencies (#1410)\n- 119c8933 [Bug] fix update dataId not update layer data (#1414)\n- b97b58a9 [Enhancement] Choose the default field to be integer if no reals are present (#1409)\n- 072876df [bug] upgrade colorbrewer to 1.5.0 (#1439)\n- d4698bb8 [Chore] add initial version of ts-smoosh (#1437)\n- 6b39c43f [Chore] reformat changelog\n\n## [2.5.0] - Mar 3 2021\n\n- 58af5b65 [bug] Set colorbrewer version to 1.4.0 #1416 (#1428)\n- a03250a4 CHORE: export processKeplerglDataset (#1422)\n- ddaa8bf7 FIX: incorrect type strin -> string (#1421)\n- 9e5bfdca [Feat] Duplicate layer and add layer from config (#1401)\n- 29bfa406 [Bug] Interval animation doesn't stop when speed is set to 0 (#1397)\n- 9476c293 feat: Converted dataset object to kepler table class (#1239)\n- 498305cc [Bug] save to map provider (#1399)\n- 6728b30f [Bug] Clamping slider values outside range (#1395)\n- f0e51743 [Enhancement] add changedFilters to datasets when filter data is called (#1396)\n- 8d68001d [Bug] Add style prop to kepler-gl container (#1398)\n- d295c762 [Enhancement]: Save filter speed to schema (#1394)\n- fb801d70 [Chore] Update license year (#1393)\n- fa6deff0 (0116-babel-deps) [Enhancement] Show an error notification for errors in deck (#1373)\n- 5d4b4547 [Bug] Bug fixes (#1388)\n- 35bf90a9 [Bug]: FIxed issue with map popover object being null (#1384)\n- fc2fb04d [CHORE] Typescript fixes (#1383)\n- d6e28377 [Bug] Fix 12350 format in tooltip (#1327)\n- 2ea82deb [Feat] fixed augumented numeric formats with ~ (#1369)\n- e88b4f19 [Bug] Fix speed button input on timeline (#1376)\n- 7aeca210 [Enhancement] bump loaders.gl to 2.3.3 (#1366)\n- eff0a15d [Enhancement] Choose layer color by default (point layer) (#1367)\n- 823405ab [Bug] fix arc layer configurator (#1375)\n- a11c63c3 [Enhancement] avoid calling mapPopover setstate infinitely (#1346)\n- ae234e72 [Bug] Prevent crash in react-map-gl when zoom cannot be calculated (#1365)\n- be61b70b [Enhancement] automatically re-project GeoDataFrame to EPSG:4326 (#1350)\n- 2aad97f3 [Bug] Added better check for bins in bottom widget (#1361)\n- ef8bdbaf [Chore]: Upgraded to node 12, migrate from TravisCi to Github actions (#1326)\n- c7726680 [Enhancement]: Added uiStateUpdater showDatasetTable in order to intercept showDatasetTable action (#1363)\n- f33c76b4 [FEAT] Add rename dataset reducer (#1362)\n- 027985af [Bug] Fixed color picker closure when selecting first custom palette value) (#1347)\n- 7f3be27f [Enhancement] check bounds before calling fitbounds (#1348)\n- f046ac1b [Enhancement] better arc layer column config layout (#1345)\n- 2ea853b1 [Bug] Fixed bug with fixed radius after remove size field in pointlayer (#1343)\n- 32d80182 [Bug] fixed geocoder crash and added ability to pass coordinates (#1342)\n- c2ba7f04 [Enhancement] Fix negative button border (#1344)\n- 55f74dcd [Enhancement] added check for oldLayerData (#1357)\n- 223af2b6 [Enhanment] extract valdiate layer and validate filter function (#1349)\n- 06ea669d [Enhancement] pass dataset to renderLayer function (#1341)\n- 524fc591 [Feat] Visual channel refactor generalize get accessor and updateTrigger (#1338)\n- c1d4943b [Enhancement] Adjust input light styles (#1340)\n- 5642ca8b [Chore] SidePanel panels are now passed through only through props or default ones (#1339)\n- f802f393 [Chore] Decouple table from dataset Id (#1337)\n- c7f50fdc [Chore] Export KeyEvent and downloadFile utils (#1335)\n- 335f82a3 [Enhancement] Added the ability to pass supported data types when exporting (#1336)\n- 239051f0 keplergl==0.2.2\n- 55053230 keplergl-jupyter@0.2.2\n- 1bac01ab update example app versions\n\n## [2.4.0] - Nov 30 2020\n\n- 259022ee [Upgrade] Support React 17 (#1323)\n- 6c48c422 [Enhancement] Export more utils (#1317)\n- 81bc6b37 [Enhancement] make provider injector function to get injectedApp back (#1318)\n- 5e2b8988 [Enhancement] update spanish and catalan translations (#1319)\n- 334f0b76 [Enhancement] extend template for light theme (#1305)\n- abbe032e [Chore] Dependency upgrade (#1314)\n- f0a966cd [Bug] check category (#1316)\n- 7f5282b4 [Feat] add incremental timeline animation (#1315)\n- c1a251de [Enhancement] make visConfigSwitch a factory (#1313)\n- 37cf1457 [Enhancement] Enable polygon filter on h3 layer (#1306)\n- bdbea264 [Feat] allow changing dataset in layer config (#1312)\n- 28f5204d [Bug] fix radio button style (#1310)\n- c990a477 [Enhancement] Upgrade d3-scale (#1311)\n- ea69da8a [Enhancement] fix item-selector dropdown value overflow nad tooltip pin color (#1309)\n- d94de814 [Chores] Exported default formatters (#1308)\n- 307cd3d4 [Bug] avoid duplicated h3 layer detection (#93) (#1307)\n- 8bc11a37 [Enhancement] Add inputBGdActive for light theme (#1301)\n- 3f0f7a6c [Bug] Check for valid layer pinned prop before performing comparison (#1297)\n- 42acc1cf [Bug] Fixed bug when reversing color schema (#1296)\n- 9949888f Table of content -> Table of contents\n- 9a13ce68 [Chores] Fixed security vulnerabilities and added new factories (#1294)\n- 3276cef3 Merge branch 'upwards_update'\n- 70687cab [Docs] Add usage example in doc for _repr_html_ method (#1282)\n- 32b519af [Chores] Updated yarn.lock and file license\n- aecbdc55 [Bug] Fixed typo in renderedSize cell-size (#90)\n- 9f8b84e1 upgrade react-palm to 3.3.7 (#89)\n- 7410cfa5 [Enhancement] Disable layer select option when no data is loaded (#88)\n- 7a69c865 data table style tiny adjustment\n- 21d09475 add fontFamily to input style\n- 96c37618 export renderSize from cell-size.js\n- f356fe43 [Enhancement] Added modalStyle prop Portaled to override default values (#83)\n- b6fd3916 [Enhancement] UI input style improvement (#1284)\n- 92a2bb65 [Enhancement] Add preserveLayerOrder to layer merger (#1288)\n- 480ead69 [Enhancement] Add a CTA button type (#80) (#1286)\n- d882ba09 [Enhancement] Layer config: Add column validators (#1287)\n- e8fc1c5e Export typeahead (#1289)\n- ad5ec020 [Enhancement] render last added filter first (#1285)\n- 42569ec3 [Enhancement] Export StyledDropdownSelect (#1283)\n- 1b748471 [Jupyter] add _repr_html_ method (#1202)\n- fbbd4c45 [Enhancement] export more utils and schema (#1280)\n- e5a6f9e8 [Enhancement] Improve schema and utils typing (#1279)\n- ad651700 [Enhancement] Create factory for histogram and line chart, add brush handle to range brush (#1274)\n- 6681d2e2 [Enhancement] pass light theme through to item selector (#1276)\n- 0184cf1e [Enhancement] add setTimeAnimation action (#70) (#1263)\n- 908a5e2b [Chores] Bump http-proxy from 1.18.0 to 1.18.1 (#1268)\n- 7acb3d66 [Auto] Bump elliptic from 6.5.2 to 6.5.3 (#1210)\n- 490cafb0 [Jupyter] Updated Docs for Jupyter (#1267)\n- a7865c8d [Enhancement] Added factory for the icons of the map control (#1273)\n- 77b4e018 [Enhancement] switch style tweak (#1262)\n- 9dbb9e73 [Bug] fix dropdown list item lineheight (#1261)\n- d677c18f [Feat] Move more css to theme and create more factories (#1248)\n- 2ebd1368 [Enhancement] Typescript improvement (#1254)\n- 959f1a33 [Bug] fix export image size not set (#1257)\n- 678aacc2 [Upgrade] upgrade react-palm to 3.3.6 (#1255)\n- f54d6afb [Enhancement] Map control style improve (#1253)\n- 3e40a48c [Website] disable banner (#1252)\n- 3b81b59f [Enhancement] Add new theme variables (#1245)\n- b09aa2e1 [Bug] Fix load data modal crash (#1244)\n- 42670d89 [Bug] Fix provider preview image during map save and share flow (#1243)\n- efd3676d [Bug] Fix component exports\n- 0b91f4d1 [Enhancement] Improve react intl support (#1237)\n- 7ff0c459 [Enhancement] Save merger and schema to visState (#1235)\n\n- ## [2.3.2] - Aug 16 2020\n- 10468e19 [Enhancement] Export more utils (#1233)\n- 242dcf99 [Enhancement] Upgrade dependencies and fix vulnerabilities (#1236)\n- 3d72066f [Bug] Fixed image export bug due to mapbox attrition logo (#1229)\n- f4951102 [Feat] add readonly prop to KeplerGl component (#1220)\n- 04991352 [Enhancement] Added props to panel-header iconComponent (#64) (#1219)\n- b91785ec [Feat] Auto detect h3 layer from h3 field data (#53) (#1218)\n\n## [2.3.1] - Aug 4 2020\n\n- [Bug] fix tooltip config, add boolean formatter (#1216)\n- [Enhancement] Geocoder interaction improvements (#1214)\n- [Enhancement] add options.autoCreateLayers to addDataToMap (#1215)\n- [Bug] Hide BottomWidgetContainer nothing to render (#1213)\n- [Enhancement] Cleanup unused babel plugins (#1211)\n- [Bug] fix file handler row parsing to support single geojson feature (#1212)\n- [Enhancement] Add KeplerGl.onDeckInitialized callback (#1193)\n- [Enhancement] Render geocode in readOnly mode (#1177)\n- [Feat] pass initialUiState to prop (#1187)\n- [Docs] Fix `replace-component` Readme (#1207)\n- [Jupyter] Convert to gdf to a dataframe instead of a copy (#1201)\n- New image export approach (#1199)\n- Add prop to disable file extension checking (#1195)\n- Load: extract extensions from loader objects (#1194)\n- Add `visState.loaders` to let app inject a list of loaders.gl loaders. (#1192)\n- Enable modal prop types (#1190)\n- Enable modal types (#1189)\n- Add types to top-level KeplerGl component (#1188)\n- Add typescript types for upload modal and components (#1185)\n- Add types for composer helpers (#1186)\n- [Feat] add zoom to coordinate tooltip (#1179)\n- [Enhancement] export more layer configurator components (#1176)\n- [Bug/Enhancement] Pass PanelHeader props to the onClick handler of action items (#1181)\n- [Bug] Fix import of the user guide link (#1182)\n- [examples] update example version to 2.3.0\n\n## [2.3.0] - July 6 2020\n\n- [Enhancement] Improve animation sliders (#1157)\n- [Enhancement] speed control step to 0.001 (#1155)\n- [website] remove unused env, relax on package engines requirement (#1173)\n- [Feat] Pinned tooltip + Compare (#1132)\n- [Feat] Integration with loaders.gl 2.2 (#1156)\n- [Feat] Bump deck.gl and luma.gl to v8.2 (#1166)\n- [Chore] Bump websocket-extensions from 0.1.3 to 0.1.4 (#1138)\n- [Website] Add 2020 Survey (#1154)\n- [Bug] Tooltip formatting (#1129)\n- [Jupyter] Default centerMap to False so that zoom map state configurations are not (#1142)\n- [Enhancement] close modal when press escape key (#1134)\n- [Enhancement] Export time widget factories (#1133)\n- [Enhancement] filter invalid value when calculate trip layer domain (#1131)\n- [Feat] enable tooltip formatting in interaction config (#1102)\n- [Feat] Add type definition (#1116)\n- [RFC] table class RFC (#1109)\n- [Docs] adding missing bracket (#1094)\n- add side-panel inner class (#1113)\n- [Bug] add hexagon layer translation (#1114)\n- [Jupyter] fix gitignore add missing files (#1118)\n- [Jupyter] Publish keplergl jupyter 0.2.0 (#1110)\n- [Enhancement] fix attribution color, add kepler smaller font (#1092)\n\n## [2.2.0] - May 10 2020\n\n- [Enhancement] Added Editor and FeatureActionPanel factories (#1093)\n- [Feat] Geocoder Search (#1068)\n- [Doc] Updated release docs with gh-release instructions (#1059)\n- [Bug] Aggregation layer fix out-of-domain coloring for valid strings (#1070)\n- [Feat] Add Spanish and Catalan translation (#1087)\n- [Doc] Update playback documentation (#1072)\n- [Bug] Fix link to umd folder\n- [Doc] Refactored doc files for better structure (#1084)\n- [Enhancement] Add Portuguese translations (#1063)\n- [Bug] Fixed download file for microsoft edge (#1074)\n- [Bug] Fix broken redirects in jupyter user guide (#1077)\n- [Docs] update upgrade guide (#1044)\n\n## [2.1.2] - April 3 2020\n\n- [Enhancement] Add support for localization and Finnish translations (#994)\n- [Bug] Fixes for case sensitive fields in CARTO storage (#1057)\n- [Chore] Removed engine requirements (#1049)\n- [Chore] Improve the secondary button color for base theme (#1048)\n- [Chore] Updated examples to v2.1.1 (#1043)\n\n## [2.1.1] - March 31 2020\n\n- [Chore] Updated example to 2.1.0 (#1041)\n\n## [2.1.0] - March 30 2020\n\n- [Enhancement] Remove table cell char limit and increased cell header height (#1038)\n- [Docs] CHANGELOG.md markup update (#1029)\n- [Enhancement] add classes to button for easier style override (#1035)\n- [Bugfix] Remove incorrect outlier calculation for better map centering (#1026)\n- [Bug] fix scatterplot stroke width in pixels (#1018)\n- [Test] e2e test (#940)\n- [Enhancement] Move layer panel visible toggle to end (#1017)\n- [Bug] export formatCsv (#1022)\n- [Enhancement] Refactor load file tasks to better handle multiple file types (#986)\n- [Bug] Fixed carto-provider example: importing the correct kepler.gl processor path (#1016)\n- [Feat] Add satellite basemap (#1007)\n- [Feat] Improved data table rendering (#1010)\n- [Chore] Upgrade to Node 10 (#1009)\n- [Feat] S2 layer (#800)\n- [BUG] Fix provider test (#1008)\n- [Enhancement] better handling provider tile update (#1000)\n- [Enhancement] Loading and error feedback for shared maps loaded from URL #1002 (#1003)\n- [Enhancement] adjust button color in light theme (#1004)\n- [Bug] Reset selected provider status after loading and before sharing (#999)\n- [Feat] Add more light themes (#1001)\n- [Bug] fix bug map loaded with custom map style not save correctly (#993)\n- [Bug] Fix username set to null after loading map from URL #995 (#996)\n- [Enhancement] Decrease filter step size for small domains (#958)\n\n## [2.0.1] - March 9 2020\n\n- [Bug] Add cloud-providers.js to package.json (#991)\n- [Feat] CARTO provider for cloud storage (#985)\n- [Bugfix] Fix typo on variable name (#987)\n- [Enhancement] pass appWebsite to logo component (#984)\n- [Chore] Removed testing from publish action (#980)\n- [Bug] remove console.log in filter.utils\n- [Feat] Load cloud map with provider (#947)\n\n## [2.0.0] - Feb 25 2020\n\n- [Enhancement] Independently customize Geojson layer fill stroke opacity (#966)\n- [Bug] Fix text collision on toggle input (#973)\n- [Chore] upgrade prettier to 1.19 to better handle single line function compositions (#971)\n- [Style] run prettier and lint on tests (#968)\n- [Bug] Select dataset filter bug (#965)\n- [Bug] fix hexagon layer hover crash (#964)\n- [Style] run prettier (#963)\n- [Feat] Allow adding custom side panel tabs\n- [Chore] Fix prettier update config (#767)\n- [Bug] Fixed json map export and added tests (#956)\n- [Bug] Resolve deck luma version conflict (#955)\n- [Feat] upgrade to deck.gl@8 (#889)\n- [Feat] UI for save map to backend storage (#906)\n- [Bug] Fixed geo-filter extra layer issue (#936)\n- [Bug] Fix low projection accuracy in higher zoom level (#946)\n- [Bug] fix hexagon layer hover cause app crash (#933)\n- [Bug] fix heatmap crash when there is no filter (#934)\n- [Bug] should add redux devtools in demo app by default (#932)\n- [Feat] Gpu data filter (#878)\n- [Feat] Global export of image export constants (#923)\n- [Bug] Fix mix int/float column interpreted as sting (#927)\n- [Chore] Correctly update the copy changes to actions.js (#914)\n- [Enhancement] Hide data modal in export map (#920)\n- [Chore] remove action to publish to github package repo (#919)\n- [Feat] Geo-Operations: create and apply polygon filters (#595)\n- [Bug] Fix h3 layer projection error at edge of world map (#918)\n\n## [1.1.13] - Jan 17 2020\n\n- [Enhancement] added coordinate to tooltip export configuration (#876)\n- [Bug] mapState not applied in exported map html (#913)\n- [Chore] Update grammar, cleanup whitespace, fix broken link (#912)\n- [Docs] add Upgrade-guide\n- [Docs] Remove hyperlink with \"Advanced Usage\" (#903)\n- [Docs] add initial cloud provider api (#868)\n- [Enhancement] treat type-analyzer type: NUMBER as strings (#891)\n- [Bug] remove argument.length check in injector (#899)\n- [Enhancement] add disabled to layer-configurator group (#897)\n- [Bug] Fix a bug in file-drop.js that causes error in server side render (#896)\n- [Bug] Ensure all colors returned from get3DBuildingColor are RGB arrays (#871)\n- [Chore] License 2020 (#883)\n- [Bug] Correctly copy over field.filterProps when merging multiple filters (#884)\n- [Bug] Fix newDateEntries typo and formatting fixes (#870)\n- [Bug] Fix multiple geojson layer found when properties contain object and array (#872)\n- [Bug] fix demo-app resolve react-redux (#866)\n\n## [1.1.12] - Dec 14 2019\n\n- [Bug] Remove sqrt, log from default color aggregation for count (#856)\n- [Bug] fix cluster point count, cluster layer failed to render on export image (#855)\n- [Style] Remove extra semicolon (#850)\n- [Docs] Update api-reference overview links\n- [Bug] Don't merge domain when update filter name (#841)\n- [Enhancement] React 17: replace componentWillReceiveProps and componentWillMount (#745)\n- [Bug] Fixed delete dataset action (#835)\n- [Chore] Github action to publish npm package (#825)\n- [Enhancement] Demo App Cloud provider refactor (#831)\n\n## [1.1.11] - Nov 13 2019\n\n- [Bug] Correctly save filterProps to field while merging filter from config (#829)\n- [Docs] fixing api reference broken link (#812)\n- [Bug] fix empty geometry causing trip layer detection to fail (#826)\n- [Docs] update a-add-data-to-the-map.md with embed geometries in CSV\n\n## [1.1.10] - Oct 30 2019\n\n- [Docs] Add instructions for image and weblink in tooltip (#797)\n- [Enhancement] Add Bug Report User Guides to demo app panel header (#787)\n- [Docs] Fix typos in add-data-workflow-user-guide (#807)\n- [Feat] add stdev and variance aggregators to aggregation layer (#809)\n- [Feat] Multiple datasets per filter (#773)\n- [Bug] Fixed loading urls with query params (#780)\n- [Jupyter] Publish keplergl jupyter 0.1.2 (#784)\n\n## [1.1.9] - Oct 11 2019\n\n- [Enhancement] improve Geojson processing performance and error handling (#781)\n- [Enhancement] add file format instruction to file upload (#770)\n- [Bug] Filter invalid H3 IDs (#775)\n- [Bug] fix readonly in addDataToMap (#783)\n- [Enhancement] Expose LayerHoverInfoFactory and CoordinateInfoFactory (#769)\n- [Bug] Fixed dropbox upload in Firefox. Passing explicit file name to upload function\n- [Enhancement] Demo app sample info (#758)\n- [Enhancement] Generate custom map style icon from style url (#762)\n- [Jupyter][bug] fix lab widget window responsiveness, add version to header (#771)\n- [Jupyter][docs] add installation instruction to jupyter widget user guide\n- [Docs] Update add data to map docs\n- [Jupyter] Publish keplergl-jupyter for Jupyter labs (#764)\n- [Jupyter][bug] fix flashing html export when open in window (#756)\n- [Enhancement] Add logo and GA to exported html (#757)\n- [Docs] update Trip Layer md\n\n## [1.1.8] - Sep 30 2019\n\n- [Bug] Fix saving animation speed (#752)\n- [Feat] Add Trip Layer - Final (#699)\n- [Feat] add custom color editor (#601)\n- [Chore] add coverall (#748)\n- [Docs] mapboxApiUrl usage examples (#737)\n- [Feat] Support Policy page (#724)\n\n## [1.1.7] - Sep 11 2019\n\n- [Enhancement] Create more factories from SourceDataCatalog, add onClickTitle (#720)\n- [Enhancement] Express example (#704)\n- [Bug] check new layers based on new dataset id (#721)\n- [Feat] Add Log and Sqrt scale (#670)\n- [Chore] Add a script to automatically edit kepler.gl version (#714)\n\n## [1.1.6] - Sep 5 2019\n\n- [Bug] Upgrade to deck 7.1.11 (#715)\n\n## [1.1.5] - Sep 4 2019\n\n- [Bug] Unlock luma.gl version (#713)\n- [Bug] fix heatmap getBounds (#711)\n- [Feat] HTML Export: provide read only mode (#709)\n\n## [1.1.4] - Sep 3 2019\n\n- [Bug] Lock deck.gl to version 7.1.5 (#688)\n- [Enhancement] add keepExistingConfig option to addDataToMap (#619)\n- [Bug] Fixed issue with geojson fields (#683)\n- [Enhancement] Switch from callback refs to createRef (#622)\n- [Bug] Fix uglify error compiling dom-to-image in prod (#682)\n- [Enhancement] pass set useDevicePixels to deck.gl to plot container (#663)\n- [jupyter] Upgrade to kepler.gl v1.1.3 (#660)\n- [Chore] use xvfb as a service in travis-ci (#669)\n\n## [1.1.3] - Aug 5 2019\n\n- [Enhancement] Use preserved state to apply keplerGlInit. when mint=false (#649)\n- [Enhancement] Replace react-data-grid with react-virtualized (#629)\n\n## [1.1.2] - Aug 1 2019\n\n- [Bug] Fix issue in Layer.registerVisConfig preventing custom boolean properties\n- [Enhancement] Simplify map layer visible logic in splitMaps and deck, mapbox overlay renders (#642)\n- Netlify badge (#641)\n- [Enhancement] Add 3d building color editor (#633)\n- [Enhancement] Update mapbox-gl css version (#634)\n- [Bug] fix SolidPolygonLayer import causing 3d building layer crash (#625)\n- [Bug] Don't show null for labels if there is no data (#626)\n- [Bug] add deckGlProps to pass preserveDrawingBuffer to plot container (#624)\n- [Enhancement] DemoApp: explicitly pass window.fetch to Dropbox to suppress warning (#621)\n- [Enhancement] Use theme in histogram plot color (#607)\n- [Enhancement] Bump supercluster version (#590)\n- [Feat] Add mapboxApiUrl to `KeplerGL` (#554)\n- [Docs] Update link to the GitHub repo (#589)\n- Fixed python3 compatiability and wrong variable in string format (#587)\n- [Bug] Remove isMouseOver state from MapPopover (#577)\n- [Docs] fix: Correct Custom Theme Example Link (#578)\n- [Bug][jupyter] Replacing print statement with () to make it Python 3 compatible (#582)\n- Update build command: remove yarn since netlify runs yarn by default (#585)\n- [Jupyter] cleanup examples (#574)\n- [Feat] Publish keplergl jupyter 0.1.0a5 (#572)\n- [Chore] Add issue template for kepler.gl Jupyter\n- [Bug] Solve issue #547 avoid crash application (#564)\n\n## [1.1.1] - Jun 24 2019\n\n- [Bug] Fix radius rendering when value = 0 (#551)\n- [Docs] Updating Layer User Guides (#373)\n- [Feat] Display mouse coordinate (#550)\n- [Docs] Replace CLA with DCO (162a9f7)\n- [Style] fix README typo (c1fafbf)\n- [Docs] Add jupyter widget user guide link o README (17d3ec8)\n- [Chore] Add jupyter widget issue templates (a40c1fe)\n- [Feat] Bump deck.gl to v7.1.5 (#568)\n- [Feat] Add ScenegraphLayer (#540)\n- [Feat] Add kepler.gl-jupyter python package (#543)\n\n## [1.1.0] - Jun 15 2019\n\n- Upgrade to deck.gl 7.1 (#559)\n- [Docs] update user documentation with newer layers and features (#552)\n- Upgrade to deck.gl 7 and luma.gl 7 (#544)\n- [Bug] Display color legend for stroke color scale (#546)\n- [Enhancement] Image export error handling (#538)\n- [Bug] Fix typo on layer-configurator.js (#549)\n\n## [1.0.0] - May 23 2019\n\n- [Enhancement] Detecting mapbox token validity (#513)\n- [Enhancement] Netlify webpack optimization (#525)\n- [Feat] More control over point label (#515)\n- [Enhancement] Applied changes for enable netlify deployment (#516)\n- [Enhancement] Refactored modal dialog to be more responsive (#501)\n- [Bug] fix side panel unnecessary rerender (#512)\n- [Feat] Upgrade deck.gl to 6.4 (#456)\n- [BUG] Fixed layer list sorting dnd effect (#509)\n- [Feat] add onViewStateChange callback to KeplerGl (#506)\n- [Enhancement] More granular speed control (#500)\n- [Docs] update all uber links to keplergl org (#502)\n\n## [1.0.0-2] - May 2 2019\n\n- [Bug] Fix missing default map styles after loading custom map style from saved json (#490)\n- [Bug] Fix `fix radius` in point layer unclickable (#491)\n- [Bug] fix image export doesnt get called when map rendered (#494)\n- [Enhancement] Merge export config and map into one interaction (#488)\n\n## [1.0.0-1] - Apr 23 2019\n\n- [Bug] Fix point layer brushing and highlight (#487)\n- [Feat] Add a light theme to KeplerGl Prop (#489)\n- [Bug] Fix browse for file upload (#486)\n- [Enhancement] Cleanup load map style tasks (#472)\n- [Enhancement] load svg icons from aws, add bundle analyzer, reduce bundle size -1mb (#479)\n- [Bug] upgrade kepler.gl version in examples\n- [Docs] Fixed link to addDataToMap (#459)\n- [Enhancement] expand bottom widget to full length if in read only mode(#465)\n\n## [1.0.0-0] - Apr 2 2019\n\n- [Enhancement] Replace react anything sortable with React-Sortable-Hoc\n- [Enhancement] Replaced DI object storage with an actual Map\n- [Feat] Able to overwrite custom theme\n- [Chore] Upgraded waypoint library to support react16\n- [Chore] Dropbox UI enhancements\n- [Bug] Fix points disappear while panning across 180th meridian\n- [Chore] Tweak save and export documentation\n- [Chore] Add oss header and middleware.js\n- [Chore] Added file header for user-guide.js\n- [Feat] Single map page export\n- [Chore] Upgraded libraries: react, styled-components\n\n##### BREAKING CHANGES\n\n- React 15 is no longer supported\n- Style components v4+ is now required because is now a peer dependency\n\n## [0.2.4] - Mar 13 2019\n\n- [Enhancement] Slider: use clientX to calculate delta to support windows IE and Tableau kepler.gl (#431)\n- [Bug] Range slider: correctly setting ranch brush selection when mount (#433)\n- [Feat] Add getMapboxRef prop (#372)\n- [Enhancement] Automatically loading custom dependencies when inject custom component factor (#430)\n- [Bug] Range brush width change should not trigger onBrush callback (#432)\n- [Bug] fix processor export, support previous (#428)\n\n## [0.2.3] - Mar 3 2019\n\n- [Docs] Export processors and Add Docs (#421)\n- [Docs] Add docs for actions and updaters (#368)\n- [Bug] Fix image export component failed to render (#418)\n\n## [0.2.2] - Feb 26 2019\n\n- (HEAD -> master, origin/master, origin/HEAD) [Bug] Fixed web doc link (#369)\n- [Bug]: Fixed example dependencies (#362)\n- [Bug] Fix missing 3d building layer in image export (#361)\n- [Bug] fix 3d building layer missing mapbox token, fix image export (#360)\n- [Docs] Add API Docs (#279)\n- [Feature] UMD module in unpkg (#349)\n- Disabled banner (#352)\n\n## [0.2.1] - Feb 6 2019\n\n- (HEAD -> master, origin/master) [Feature] Collapsible layer group (#350)\n- [Enhancement] Added default feature flags to disable dropbox (#338)\n- [Bug]: fix alias and module resolve in webpack.config.local (#348)\n- [Enhancement] Upgraded Webpack, Babel and Eslint (#342)\n- [Feature] Notification systems with new UI panel and helpers to generate messages (#333)\n- GitHub browser history (#321)\n- [Bug] Fix Maximum call stack size exceeded when double click (#323)\n- [Docs] Export identity actions individually and add JSDocs (#290)\n- [Docs] Edit PR guidance in contribution guidelines (#320)\n- [Docs] Add Contribution Guidelines (#261)\n- (overide-style) [Enhancement] Upgrade type-analyzer to pass 0/1 as integer (#317)\n- [Typo] Misspellings in comments (#314)\n- [Housekeeping] Update Copyright header to 2019, Happy New Year (#316)\n- Feat: Implemented Dropbox integration (#312)\n\n## [0.2.1-beta.1] - Dec 17 2018\n\n- [Feature] Added a Tiled 3D Building Deck.gl Layer (#270)\n- [Enhancement] Fossa Integration (#309)\n- [Enhancement] Change BottomWidget to pure functional component (#249)\n- [Docs]: updated docs for better readability(alignments) (#255)\n- [Enhancement] export processKeplerglJSON from processors (#299)\n- [website] BugFix: missing tracking payload (#311)\n- [Enhancement] Hexbin Layer: smaller radius step and dynamic hover (#310)\n- [Bug] remove unpm from yarn.lock (#303)\n- [Enhancement] use mapbox style url for default (published) uber map styles (#292)\n- [Feature] Load data and kepler.gl file using URLs (#260)\n\n## [0.2.1-beta.0] - Nov 16 2018\n\n- [Bug] Fixing global color issue #130 for the heat map (#277)\n- [Enhancement] More exports (#284)\n\n## [0.2.0] - Nov 16 2018\n\n- [Enhancement] Export side panel component factories (#282)\n- [Feature] Upgrade to deck.gl v6 (#272)\n- [Refactor] Small update of readability (#250)\n- [website] Click logo should go to kepler.gl website (#251)\n- [Enhancement] Add contribution guidelines on contributing.md file (#108)\n- [Enhancement] Scan through all text labels to get the entire character set (#245)\n\n## [0.1.6] - Oct 3 2018\n\n- [Enhancement] save and load text label config (#242)\n\n## [0.1.5] - Oct 2 2018\n\n- [Enhancement] Fix z-fighting issue between text label and scatter plot (#234)\n- [Bug] Sort color steps (#241)\n- [Bug] fix a bug where field is valid is always false (#240)\n\n## [0.1.4] - Sep 15 2018\n\n- [Enhancement] Null check for missing arc column (#235)\n\n## [0.1.3] - Sep 10 2018\n\n- [Enhancement] Add H3 layer (#217) (#198)\n- [Enhancement] Add text label in Point layer (#166)\n\n## [0.1.2] - Aug 24 2018\n\n- [Bug] Fix server render error, remove react-ace (#206)\n\n## [0.1.1] - Aug 24 2018\n\n- [Enhancement] Bump react-palm@1.1.2 (#215)\n\n## [0.1.0] - Aug 21 2018\n\n- Upgrade to Deck.gl v5.3.4 (#153)\n\n## [0.0.28] - Aug 8 2018\n\n- Fix cluster layer label rendering\n\n## [0.0.27] - Aug 3 2018\n\n- Fix unable to fetch external stylesheets when taking the screenshot (#187)\n- [Bug] Avoid repeatedly calling HIDE_EXPORT_DROPDOWN (#180)\n\n## [0.0.26] - Aug 3 2018\n\n- [Bug] fix mapStyles loaded as an empty object after load map from config (#169)\n\n## [0.0.25] - Jul 10 2018\n\n- [Bug] Create ellipsis when dataset name is a long name (#109)\n- [Enhancement] Save custom reducer initialState, add custom-reducer example (#159)\n\n## [0.0.24] - Jul 5 2018\n\n- [Bug] fix image export failing (#155)\n- [Enhancement] Add default map styles to mapStyle reducer initial state (#147)\n\n## [0.0.23] - Jun 28 2018\n\n- [Enhancement] Consider all mew layers when calculating the map bounds (#142)\n- [Bug] Fix icon layer instructions (#131)\n- [Website] add banner to demo app for survey (#117)\n\n## [0.0.22] - Jun 10 2018\n\n- [Bug] new filter shouldn't be enlarged if there is already an enlarged filter (#93)\n- [Enhancement] Enable ordinal aggregation in aggregation layer (hex, grid, cluster) (#29)\n\n## [0.0.21][0.0.20] - Jun 4 2018\n\n- [Bug] TimeRangeSlider should not cache props.onChange (#100)\n"
  },
  {
    "path": "FILE-HEADER",
    "content": "SPDX-License-Identifier: MIT\nCopyright contributors to the kepler.gl project"
  },
  {
    "path": "LICENSE",
    "content": "Copyright contributors to the kepler.gl project\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"right\">\n  <a href=\"https://npmjs.org/package/kepler.gl\">\n    <img src=\"https://img.shields.io/npm/v/kepler.gl.svg?style=flat\" alt=\"version\" />\n  </a>\n  <a href=\"https://travis-ci.com/keplergl/kepler.gl\">\n    <img src=\"https://api.travis-ci.com/keplergl/kepler.gl.svg?branch=master\" alt=\"build\" />\n  </a>\n  <a href=\"https://github.com/keplergl/kepler.gl\">\n    <img src=\"https://img.shields.io/github/stars/keplergl/kepler.gl.svg?style=flat\" alt=\"stars\" />\n  </a>\n  <a href='https://opensource.org/licenses/MIT'>\n    <img src='https://img.shields.io/badge/License-MIT-blue.svg' alt='MIT License' />\n  </a>\n  <a href='https://app.fossa.com/projects/custom%2B4458%2Fgithub.com%2Fkeplergl%2Fkepler.gl?ref=badge_shield'>\n    <img src='https://app.fossa.com/api/projects/custom%2B4458%2Fgithub.com%2Fkeplergl%2Fkepler.gl.svg?type=shield' alt='Fossa' />\n  </a>\n  <a href=\"https://app.netlify.com/sites/keplergl/deploys\">\n    <img src=\"https://api.netlify.com/api/v1/badges/0c9b895c-acd0-43fd-8af7-fe960181b686/deploy-status\" alt=\"Netlify Status\"/>\n  </a>\n  <a href='https://coveralls.io/github/keplergl/kepler.gl?branch=master'>\n    <img src='https://coveralls.io/repos/github/keplergl/kepler.gl/badge.svg?branch=master' alt='Coverage Status' />\n  </a>\n</p>\n\n<h1 align=\"center\">\n  kepler.gl | <a href=\"https://kepler.gl\">Website</a> | <a href=\"https://kepler.gl/#/demo\">Demo App</a> | <a href=\"https://docs.kepler.gl/\">Docs</a>\n</h1>\n<h3></h3>\n\n[<img width=\"120\" alt=\"Kepler.gl\" src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/website/icons/kepler.gl-logo.png\">](http://kepler.gl)\n\n[<img width=\"600\" alt=\"Kepler.gl Demo\" src=\"./screenshots/screenshot.png\">](https://kepler.gl/demo)\n\n[Kepler.gl][web] is a data-agnostic, high-performance web-based application for visual exploration of large-scale geolocation data sets. Built on top of [MapLibre GL](https://maplibre.org/) and [deck.gl](https://deck.gl/), kepler.gl can render millions of points representing thousands of trips and perform spatial aggregations on the fly.\n\nKepler.gl is also a React component that uses [Redux](https://redux.js.org/) to manage its state and data flow. It can be embedded into other React-Redux applications and is highly customizable. For information on how to embed kepler.gl in your app take a look at the [documentation](https://docs.kepler.gl/).\n\n## Links\n\n- [Website][web]\n- [Demo][demo-app]\n- [Examples][examples]\n- [Get Started][get-started]\n- [App User Guide][user-guide]\n- [Jupyter Widget User Guide][user-guide-jupyter]\n- [Documentation][docs]\n- [Stack Overflow][stack]\n- [Contribution Guidelines][contributing]\n- [Api Reference][api-reference]\n- [Roadmap][roadmap]\n\n## Env\n\nUse Node 18.18.2 or above, older node versions have not been supported/ tested.\nFor best results, use [nvm](https://github.com/creationix/nvm) `nvm install`.\n\n## Install kepler.gl modules\n\nKepler.gl consists of different modules. Each module can be added to the project like this:\n\n```sh\nnpm install --save @kepler.gl/components\n// or\nyarn add @kepler.gl/components\n```\n\nkepler.gl is built upon [mapbox][mapbox]. You will need a [Mapbox Access Token][mapbox-token] to use it.\n\nIf you don't use a module bundler, it's also fine. Kepler.gl npm package includes precompiled production UMD builds in the [umd folder](https://unpkg.com/kepler.gl/umd).\nYou can add the script tag to your html file as it follows (latest version of Kepler.gl):\n\n```html\n<script src=\"https://unpkg.com/kepler.gl/umd/keplergl.min.js\" />\n```\n\nor if you would like, you can load a specific version:\n\n```html\n<script src=\"https://unpkg.com/kepler.gl@3.0.0/umd/keplergl.min.js\" />\n```\n\n## Develop kepler.gl\n\nTake a look at the [development guide][developers] to develop kepler.gl locally.\n\n## Basic Usage\n\nHere are the basic steps to import kepler.gl into your app. You also take a look at the examples folder. Each example in the folder can be installed and run locally.\n\n### 1. Mount reducer\n\nKepler.gl uses Redux to manage its internal state, along with [react-palm][react-palm] middleware to handle side effects.\n\nYou need to add `taskMiddleware` of `react-palm` to your store too. We are actively working on a solution where\n`react-palm` will not be required, however it is still a very lightweight side effects management tool that is easier to test than react-thunk.\n\n```js\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {enhanceReduxMiddleware} from '@kepler.gl/middleware';\n\nconst initialState = {};\nconst reducers = combineReducers({\n  // <-- mount kepler.gl reducer in your app\n  keplerGl: keplerGlReducer,\n\n  // Your other reducers here\n  app: appReducer\n});\n\n// using createStore\nexport default createStore(\n  reducer,\n  initialState,\n  applyMiddleware(\n    enhanceReduxMiddleware([\n      /* Add other middlewares here */\n    ])\n  )\n);\n```\n\nOr if use enhancer:\n\n```js\n// using enhancers\nconst initialState = {};\nconst middlewares = enhanceReduxMiddleware([\n  // Add other middlewares here\n]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nexport default createStore(reducer, initialState, compose(...enhancers));\n```\n\nIf you mount kepler.gl reducer in another address instead of `keplerGl`, or the kepler.gl reducer is not\nmounted at root of your state, you will need to specify the path to it when you mount the component\nwith the `getState` prop.\n\nRead more about [Reducers][reducers].\n\n### 2. Mount Component\n\n```js\nimport KeplerGl from '@kepler.gl/components';\n\nconst Map = props => (\n  <KeplerGl id=\"foo\" width={width} mapboxApiAccessToken={token} height={height} />\n);\n```\n\n### Props\n\n| Prop Name                     | Type          | Default Value             | Description                                                                                                                                                                                                             |\n| ----------------------------- | ------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`                          | String        | `map`                     | The unique identifier for the KeplerGl instance. Required when multiple KeplerGl instances exist. It maps to the state in the reducer (e.g. component with id `foo` can be found in`state.keplerGl.foo`).               |\n| `mapboxApiAccessToken`        | String        | `undefined`               | API token for Mapbox, used for rendering base maps. Create a free token at [Mapbox](https://www.mapbox.com).                                                                                                            |\n| `getState`                    | Function      | `state => state.keplerGl` | Function that specifies the path to the root KeplerGl state in the reducer.                                                                                                                                             |\n| `width`                       | Number        | `800`                     | The width of the KeplerGl UI in pixels.                                                                                                                                                                                 |\n| `height`                      | Number        | `800`                     | The height of the KeplerGl UI in pixels.                                                                                                                                                                                |\n| `appName`                     | String        | `Kepler.Gl`               | The app name displayed in the side panel header.                                                                                                                                                                        |\n| `version`                     | String        | `v1.0`                    | The version displayed in the side panel header.                                                                                                                                                                         |\n| `onSaveMap`                   | Function      | `undefined`               | A function called when the \"Save Map URL\" in side panel header is clicked.                                                                                                                                              |\n| `onViewStateChange`           | Function      | `undefined`               | Triggered when the map viewport is updated. Receives `viewState` parameter with updated values like longitude, latitude, zoom, etc.                                                                                     |\n| `getMapboxRef(mapbox, index)` | Function      | `undefined`               | Called when `KeplerGl` adds or removes a MapContainer with an inner Mapbox map. `mapbox` is a `MapRef` when added, or `null` when removed. `index` is `0` for the first map and `1` for the second map in a split view. |\n| `actions`                     | Object        | `{}`                      | Custom action creators to override the default KeplerGl action creators. Only use custom action when you want to modify action payload.                                                                                 |\n| `mint`                        | Boolean       | `true`                    | Determines whether to load a fresh empty state when mounted. When `false`, the state persists across remounts. Useful for modal use cases.                                                                              |\n| `theme`                       | Object/String | `null`                    | Set to `\"dark\"`, `\"light\"`, or `\"base\"`, or pass a theme object to customize KeplerGl’s style.                                                                                                                          |\n| `mapboxApiUrl`                | String        | `https://api.mapbox.com`  | The Mapbox API URL if you are using a custom Mapbox tile server.                                                                                                                                                        |\n| `mapStylesReplaceDefault`     | Boolean       | `false`                   | Set to `true` to replace default map styles with custom ones. (see `mapStyles` prop)                                                                                                                                    |\n| `mapStyles`                   | Array         | `[]`                      | An array of [custom map styles](#example-custom-map-style) for the map style selection panel. Styles replace the default ones if `mapStylesReplaceDefault` is `true`.                                                   |\n| `initialUiState`              | Object        | `undefined`               | The initial UI state applied to the `uiState` reducer.                                                                                                                                                                  |\n| `localeMessages`              | Object        | `undefined`               | Used to modify or add new translations. Read more about [Localization][localization].                                                                                                                                   |\n\n#### Example Custom Map Style\n\nYou can supply additional map styles to be displayed in [map style selection panel](https://github.com/keplergl/kepler.gl/blob/master/docs/user-guides/f-map-styles/1-base-map-styles.md). By default, additional map styles will be added to default map styles. If you pass `mapStylesReplaceDefault: true`, they will replace the default ones. kepler.gl will attempt to group layers of your style based on its `id` naming convention and use it to allow toggle visibility of [base map layers](https://github.com/keplergl/kepler.gl/blob/master/docs/user-guides/f-map-styles/2-map-layers.md). Supply your own `layerGroups` to override default for more accurate layer grouping.\n\nEach `mapStyles` should has the following properties:\n\n- `id` (String, required) unique string that should **not** be one of these reserved `dark` `light` `muted`. `muted_night`\n- `label` (String, required) name to be displayed in map style selection panel\n- `url` (String, required) mapbox style url or a url pointing to the map style json object written in [Mapbox GL Style Spec](https://docs.mapbox.com/mapbox-gl-js/style-spec/).\n- `icon` (String, optional) image icon of the style, it can be a url, or an [image data url](https://flaviocopes.com/data-urls/#how-does-a-data-url-look)\n- `layerGroups` (Array, optional)\n\n```js\nconst mapStyles = [\n  {\n    id: 'my_dark_map',\n    label: 'Dark Streets 9',\n    url: 'mapbox://styles/mapbox/dark-v9',\n    icon: `${apiHost}/styles/v1/mapbox/dark-v9/static/-122.3391,37.7922,9.19,0,0/400x300?access_token=${accessToken}&logo=false&attribution=false`,\n    layerGroups: [\n      {\n        slug: 'label',\n        filter: ({id}) => id.match(/(?=(label|place-|poi-))/),\n        defaultVisibility: true\n      },\n      {\n        slug: '3d building',\n        filter: () => false,\n        defaultVisibility: false\n      }\n    ]\n  }\n];\n```\n\n### 3. Dispatch custom actions to `keplerGl` reducer.\n\nOne advantage of using the reducer over React component state to handle keplerGl state is the flexibility\nto customize its behavior. If you only have one `KeplerGl` instance in your app or never intend to dispatch actions to KeplerGl from outside the component itself,\nyou don’t need to worry about forwarding dispatch and can move on to the next section. But life is full of customizations, and we want to make yours as enjoyable as possible.\n\nThere are multiple ways to dispatch actions to a specific `KeplerGl` instance.\n\n- In the root reducer, with reducer updaters.\n\nEach action is mapped to a reducer updater in kepler.gl. You can import the reducer updater corresponding to a specific action, and call it with the previous state and action payload to get the updated state.\ne.g. `updateVisDataUpdater` is the updater for `ActionTypes.UPDATE_VIS_DATA` (take a look at each reducer `reducers/vis-state.js` for action to updater mapping).\nHere is an example how you can listen to an app action `QUERY_SUCCESS` and call `updateVisDataUpdater` to load data into Kepler.Gl.\n\n```js\nimport {keplerGlReducer, visStateUpdaters} from '@kepler.gl/reducers';\n\n// Root Reducer\nconst reducers = combineReducers({\n  keplerGl: keplerGlReducer,\n\n  app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n  switch (action.type) {\n    case 'QUERY_SUCCESS':\n      return {\n        ...state,\n        keplerGl: {\n          ...state.keplerGl,\n\n          // 'map' is the id of the keplerGl instance\n          map: {\n            ...state.keplerGl.map,\n            visState: visStateUpdaters.updateVisDataUpdater(state.keplerGl.map.visState, {\n              datasets: action.payload\n            })\n          }\n        }\n      };\n  }\n  return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\nRead more about [using updaters to modify kepler.gl state][using-updaters]\n\n- Using redux `connect`\n\nYou can add a dispatch function to your component that dispatches actions to a specific `keplerGl` component,\nusing connect.\n\n```js\n// component\nimport KeplerGl from '@kepler.gl/components';\n\n// action and forward dispatcher\nimport {toggleFullScreen, forwardTo} from '@kepler.gl/actions';\nimport {connect} from 'react-redux';\n\nconst MapContainer = props => (\n  <div>\n    <button onClick={() => props.keplerGlDispatch(toggleFullScreen())}/>\n    <KeplerGl\n      id=\"foo\"\n    />\n  </div>\n)\n\nconst mapStateToProps = state => state\nconst mapDispatchToProps = (dispatch, props) => ({\n dispatch,\n keplerGlDispatch: forwardTo(‘foo’, dispatch)\n});\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps\n)(MapContainer);\n```\n\n- Wrap action payload\n\nYou can also simply wrap an action into a forward action with the `wrapTo` helper\n\n```js\n// component\nimport KeplerGl from '@kepler.gl/components';\n\n// action and forward dispatcher\nimport {toggleFullScreen, wrapTo} from '@kepler.gl/actions';\n\n// create a function to wrapper action payload to 'foo'\nconst wrapToMap = wrapTo('foo');\nconst MapContainer = ({dispatch}) => (\n  <div>\n    <button onClick={() => dispatch(wrapToMap(toggleFullScreen())} />\n    <KeplerGl\n      id=\"foo\"\n    />\n  </div>\n);\n\n```\n\nRead more about [forward dispatching actions][forward-actions]\n\n### 4. Customize style.\n\nKepler.gl implements css styling using [Styled-Components](https://www.styled-components.com/). By using said framework Kepler.gl offers the ability to customize its style/theme using the following approaches:\n\n- Passing a Theme prop\n- Styled-Components ThemeProvider\n\nThe available properties to customize are listed here [theme](https://github.com/keplergl/kepler.gl/blob/master/src/styles/base.js).\n\n[Custom theme example](https://github.com/keplergl/kepler.gl/tree/master/examples/custom-theme).\n\n#### Passing a Theme prop.\n\nYou can customize Kepler.gl theme by passing a **theme** props to Kepler.gl react component as it follows:\n\n```javascript\nconst white = '#ffffff';\nconst customTheme = {\n  sidePanelBg: white,\n  titleTextColor: '#000000',\n  sidePanelHeaderBg: '#f7f7F7',\n  subtextColorActive: '#2473bd'\n};\n\nreturn (\n  <KeplerGl\n    mapboxApiAccessToken={MAPBOX_TOKEN}\n    id=\"map\"\n    width={800}\n    height={800}\n    theme={customTheme}\n  />\n);\n```\n\nAs you can see the customTheme object defines certain properties which will override Kepler.gl default style rules.\n\n#### Styled-Components Theme Provider.\n\nIn order to customize Kepler.gl theme using [ThemeProvider](https://www.styled-components.com/docs/api#themeprovider) you can simply wrap Kepler.gl using ThemeProvider as it follows:\n\n```javascript\nimport {ThemeProvider} from 'styled-components';\n\nconst white = '#ffffff';\nconst customTheme = {\n  sidePanelBg: white,\n  titleTextColor: '#000000',\n  sidePanelHeaderBg: '#f7f7F7',\n  subtextColorActive: '#2473bd'\n};\n\nreturn (\n  <ThemeProvider theme={customTheme}>\n    <KeplerGl mapboxApiAccessToken={MAPBOX_TOKEN} id=\"map\" width={800} height={800} />\n  </ThemeProvider>\n);\n```\n\n### 5. Render Custom UI components.\n\nEveryone wants the flexibility to render custom kepler.gl components. Kepler.gl has a dependency injection system that allow you to inject\ncomponents to KeplerGl replacing existing ones. All you need to do is to create a component factory for the one you want to replace, import the original component factory\nand call `injectComponents` at the root component of your app where `KeplerGl` is mounted.\nTake a look at `examples/demo-app/src/app.js` and see how it renders a custom side panel header in kepler.gl\n\n```javascript\nimport {injectComponents, PanelHeaderFactory} from '@kepler.gl/components';\n\n// define custom header\nconst CustomHeader = () => <div>My kepler.gl app</div>;\nconst myCustomHeaderFactory = () => CustomHeader;\n\n// Inject custom header into Kepler.gl, replacing default\nconst KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n// render KeplerGl, it will render your custom header instead of the default\nconst MapContainer = () => (\n  <div>\n    <KeplerGl id=\"foo\" />\n  </div>\n);\n```\n\nUsing `withState` helper to add reducer state and actions to customized component as additional props.\n\n```js\nimport {withState, injectComponents, PanelHeaderFactory} from '@kepler.gl/components';\nimport {visStateLens} from '@kepler.gl/reducers';\n\n// custom action wrap to mounted instance\nconst addTodo = text =>\n  wrapTo('map', {\n    type: 'ADD_TODO',\n    text\n  });\n\n// define custom header\nconst CustomHeader = ({visState, addTodo}) => (\n  <div onClick={() => addTodo('hello')}>{`${\n    Object.keys(visState.datasets).length\n  } dataset loaded`}</div>\n);\n\n// now CustomHeader will receive `visState` and `addTodo` as additional props.\nconst myCustomHeaderFactory = () =>\n  withState(\n    // keplerGl state lenses\n    [visStateLens],\n    // customMapStateToProps\n    headerStateToProps,\n    // actions\n    {addTodo}\n  )(CustomHeader);\n```\n\nRead more about [replacing UI component][replace-ui-component]\n\n### 6. How to add data to map\n\nTo interact with a kepler.gl instance and add new data to it, you can dispatch the **`addDataToMap`** action from anywhere inside your app. It adds a dataset or multiple datasets to a kepler.gl instance and updates the full configuration (mapState, mapStyle, visState).\n\n#### Parameters\n\n- `data` **[Object][40]** **\\*required**\n\n  - `datasets` **([Array][41]&lt;[Object][40]> | [Object][40])** **\\*required** datasets can be a dataset or an array of datasets\n    Each dataset object needs to have `info` and `data` property.\n    - `datasets.info` **[Object][40]** \\-info of a dataset\n      - `datasets.info.id` **[string][42]** id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n      - `datasets.info.label` **[string][42]** A display name of this dataset\n    - `datasets.data` **[Object][40]** **\\*required** The data object, in a tabular format with 2 properties `fields` and `rows`\n      - `datasets.data.fields` **[Array][41]&lt;[Object][40]>** **\\*required** Array of fields,\n        - `datasets.data.fields.name` **[string][42]** **\\*required** Name of the field,\n      - `datasets.data.rows` **[Array][41]&lt;[Array][41]>** **\\*required** Array of rows, in a tabular format with `fields` and `rows`\n  - `options` **[Object][40]**\n\n    - `options.centerMap` **[boolean][43]** `default: true` if `centerMap` is set to `true` kepler.gl will place the map view within the data points boundaries\n    - `options.readOnly` **[boolean][43]** `default: false` if `readOnly` is set to `true`\n      the left setting panel will be hidden\n    - `options.keepExistingConfig` **[boolean][43]** `default: false` whether to keep exiting map config, including layers, filters and splitMaps.\n\n- `config` **[Object][40]** this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n\nKepler.gl provides an easy API `KeplerGlSchema.getConfigToSave` to generate a json blob of the current kepler instance configuration.\n\n#### Examples\n\n```javascript\n// app.js\nimport {addDataToMap} from '@kepler.gl/actions';\n\nconst sampleTripData = {\n  fields: [\n    {name: 'tpep_pickup_datetime', format: 'YYYY-M-D H:m:s', type: 'timestamp'},\n    {name: 'pickup_longitude', format: '', type: 'real'},\n    {name: 'pickup_latitude', format: '', type: 'real'}\n  ],\n  rows: [\n    ['2015-01-15 19:05:39 +00:00', -73.99389648, 40.75011063],\n    ['2015-01-15 19:05:39 +00:00', -73.97642517, 40.73981094],\n    ['2015-01-15 19:05:40 +00:00', -73.96870422, 40.75424576]\n  ]\n};\n\nconst sampleConfig = {\n  visState: {\n    filters: [\n      {\n        id: 'me',\n        dataId: 'test_trip_data',\n        name: 'tpep_pickup_datetime',\n        type: 'timeRange',\n        view: 'enlarged'\n      }\n    ]\n  }\n};\n\nthis.props.dispatch(\n  addDataToMap({\n    datasets: {\n      info: {\n        label: 'Sample Taxi Trips in New York City',\n        id: 'test_trip_data'\n      },\n      data: sampleTripData\n    },\n    option: {\n      centerMap: true,\n      readOnly: false\n    },\n    config: sampleConfig\n  })\n);\n```\n\nRead more about [addDataToMap](./docs/api-reference/actions/actions.md#adddatatomap) and [Saving and loading maps with schema manager][saving-loading-w-schema].\n\n[contributing]: contributing/README.md\n[demo-app]: http://kepler.gl/#/demo\n[github]: https://github.com/keplergl/kepler.gl\n[github-pr]: https://help.github.com/articles/creating-a-pull-request/\n[mapbox]: https://www.mapbox.com\n[mapbox-token]: https://www.mapbox.com/help/define-access-token/\n[developers]: contributing/DEVELOPERS.md\n[examples]: https://github.com/keplergl/kepler.gl/tree/master/examples\n[react-palm]: https://github.com/btford/react-palm\n[roadmap]: https://github.com/keplergl/kepler.gl/wiki/Kepler.gl-2019-Roadmap\n[stack]: https://stackoverflow.com/questions/tagged/kepler.gl\n[web]: http://www.kepler.gl/\n[docs]: https://docs.kepler.gl/\n[user-guide]: docs/user-guides/README.md\n[user-guide-jupyter]: docs/keplergl-jupyter/README.md\n[api-reference]: docs/api-reference/README.md\n[get-started]: ./docs/api-reference/get-started.md\n[reducers]: docs/api-reference/reducers/README.md\n[components]: docs/api-reference/components/README.md\n[custom-theme]: docs/api-reference/custom-theme/README.md\n[reducers]: docs/api-reference/reducers/README.md\n[actions-updaters]: docs/api-reference/actions/README.md\n[processors]: docs/api-reference/processors/README.md\n[schemas]: docs/api-reference/schemas/README.md\n[using-updaters]: ./docs/api-reference/advanced-usages/using-updaters.md\n[custom-map-styles]: ./docs/api-reference/advanced-usages/custom-map-styles.md\n[forward-actions]: ./docs/api-reference/advanced-usages/forward-actions.md\n[replace-ui-component]: ./docs/api-reference/advanced-usages/replace-ui-component.md\n[saving-loading-w-schema]: ./docs/api-reference/advanced-usages/saving-loading-w-schema.md\n[localization]: ./docs/api-reference/localization/README.md\n[40]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n[41]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n[42]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n[43]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n[44]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n[45]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function\n"
  },
  {
    "path": "SUMMARY.md",
    "content": "# Table of contents\n\n* [Welcome](README.md)\n* [What's new?](docs/release-notes.md)\n* [Docs](docs/README.md)\n  * [User guides](docs/user-guides/README.md)\n    * [Get Started](docs/user-guides/j-get-started.md)\n    * [Kepler.gl workflow](docs/user-guides/b-kepler-gl-workflow/README.md)\n      * [Add data to layers](docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/README.md)\n        * [Adding Data Layers](docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/a-adding-data-layers.md)\n        * [Create a Layer](docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/b-create-a-layer.md)\n        * [Blend and Rearrange Layers](docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/d-blend-and-rearrange-layers.md)\n        * [Hide, Edit and Delete Layers](docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/c-hide-edit-and-delete-layers.md)\n      * [Add Data to the Map](docs/user-guides/b-kepler-gl-workflow/a-add-data-to-the-map.md)\n    * [Layers](docs/user-guides/c-types-of-layers/README.md)\n      * [Point](docs/user-guides/c-types-of-layers/a-point.md)\n      * [S2 Layer](docs/user-guides/c-types-of-layers/l-s2.md)\n      * [Icon](docs/user-guides/c-types-of-layers/g-icon.md)\n      * [Line](docs/user-guides/c-types-of-layers/c-line.md)\n      * [Cluster](docs/user-guides/c-types-of-layers/f-cluster.md)\n      * [Polygon](docs/user-guides/c-types-of-layers/e-polygon.md)\n      * [Hexbin](docs/user-guides/c-types-of-layers/h-hexbin.md)\n      * [Grid](docs/user-guides/c-types-of-layers/d-grid.md)\n      * [H3](docs/user-guides/c-types-of-layers/j-h3.md)\n      * [Heatmap](docs/user-guides/c-types-of-layers/i-heatmap.md)\n      * [Arc](docs/user-guides/c-types-of-layers/b-arc.md)\n      * [Trip layer](docs/user-guides/c-types-of-layers/k-trip.md)\n      * [Vector Tile Layer](docs/user-guides/c-types-of-layers/m-vector-tile-layer.md)\n      * [Raster Tile layer](docs/user-guides/c-types-of-layers/n-raster-tile-layer.md)\n      * [WMS layer](docs/user-guides/c-types-of-layers/o-wms-layer.md)\n    * [Layer Attributes](docs/user-guides/d-layer-attributes.md)\n    * [Color Palettes](docs/user-guides/l-color-attributes.md)\n    * [Filters](docs/user-guides/e-filters.md)\n    * [Map Styles](docs/user-guides/f-map-styles.md)\n    * [Interactions](docs/user-guides/g-interactions.md)\n    * [Map Settings](docs/user-guides/m-map-settings.md)\n    * [Time Playback](docs/user-guides/h-playback.md)\n    * [Save and Export](docs/user-guides/k-save-and-export.md)\n    * [FAQ](docs/user-guides/i-faq.md)\n  * [API Reference](docs/api-reference/README.md)\n    * [ecosystem](docs/api-reference/ecosystem.md)\n    * [Get Started](docs/api-reference/get-started.md)\n    * [Advanced usages](docs/api-reference/advanced-usages/README.md)\n      * [Saving and Loading Maps with Schema Manager](docs/api-reference/advanced-usages/saving-loading-w-schema.md)\n      * [Replace UI Component with Component Dependency Injection](docs/api-reference/advanced-usages/replace-ui-component.md)\n      * [Forward Dispatch Actions](docs/api-reference/advanced-usages/forward-actions.md)\n      * [Reducer Plugin](docs/api-reference/advanced-usages/reducer-plugin.md)\n      * [Using Updaters](docs/api-reference/advanced-usages/using-updaters.md)\n      * [Custom reducer initial state](docs/api-reference/advanced-usages/custom-initial-state.md)\n      * [custom-mapbox-host](docs/api-reference/advanced-usages/custom-mapbox-host.md)\n    * [Components](docs/api-reference/components/README.md)\n    * [Reducers](docs/api-reference/reducers/README.md)\n      * [reducers](docs/api-reference/reducers/reducers.md)\n      * [map-style](docs/api-reference/reducers/map-style.md)\n      * [map-state](docs/api-reference/reducers/map-state.md)\n      * [combine](docs/api-reference/reducers/combine.md)\n      * [overview](docs/api-reference/reducers/README.md)\n      * [ui-state](docs/api-reference/reducers/ui-state.md)\n      * [vis-state](docs/api-reference/reducers/vis-state.md)\n    * [Processors](docs/api-reference/processors/README.md)\n      * [All processors](docs/api-reference/processors/processors.md)\n    * [Schemas](docs/api-reference/schemas/README.md)\n    * [Actions](docs/api-reference/actions/README.md)\n      * [All actions](docs/api-reference/actions/actions.md)\n    * [Cloud providers](docs/api-reference/cloud-providers/README.md)\n      * [Provider](docs/api-reference/cloud-providers/cloud-provider.md)\n    * [Custom theme](docs/api-reference/custom-theme/README.md)\n    * [Localization](docs/api-reference/localization/README.md)\n  * [Jupyter Notebook](docs/keplergl-jupyter/README.md)\n* [Examples](examples/README.md)\n  * [Node/Express](examples/node-app/README.md)\n  * [Demo App](examples/demo-app/README.md)\n  * [Open modal](examples/replace-component/README.md)\n  * [Open modal](examples/open-modal/README.md)\n  * [UMD client](examples/umd-client/README.md)\n  * [Customize kepler.gl Theme](examples/custom-theme/README.md)\n  * [Customize kepler.gl Reducer](examples/custom-reducer/README.md)\n* [Contributing](contributing/README.md)\n  * [Developing Kepler.gl](contributing/DEVELOPERS.md)\n  * [Contributor Covenant Code of Conduct](contributing/CODE_OF_CONDUCT.md)\n* [Change Log](CHANGELOG.md)\n* [Upgrade Guide](UPGRADE-GUIDE.md)\n"
  },
  {
    "path": "TODO.md",
    "content": "# TODO\n\n### Bugs\n- Time playback slider sticking out after resize\n- Heatmap frozen\n\n### Refactor Tasks\n- Refactor Layer configurator component so it works better with the layer API [shan]\n\n### Feature\n- Re-implement file upload to use event streaming (no react-palm) [shan]\n- Input map style from mapbox style url [shan]\n- Map style API to add custom map style [shan]\n- Field type editor [shan]\n- Export map [javid, shan]\n\n### Remaining UI Update\n- Free form color picker [shan]\n- Vis config by channel UI (add value switch) [shan]\n\n### Design Request\n- More map styles [erik]\n"
  },
  {
    "path": "UPGRADE-GUIDE.md",
    "content": "# Upgrade Guide\n\n## Table of Content\n\n- [v2.3 to v2.4](#upgrade-from-v23-to-v24)\n\n- [v2.2 to v2.3](#upgrade-from-v22-to-v23)\n- [v2.1 to v2.2](#upgrade-from-v21-to-v22)\n- [v2.0 to v2.1](#upgrade-from-v20-to-v21)\n- [v1.1.12 to v2.0](#upgrade-from-v1112-to-v20)\n- [v1.1.11 to v1.1.12](#upgrade-from-v1111-to-v1112)\n\n## Upgrade from v2.4 to v3.0\n\n- TBD\n\n## Upgrade from v2.3 to v2.4\n\n### Breaking Changes\n\n- Supports React 17\n- Dependency Upgrades, major ones: `d3-xxx@^2`, `redux@4.0.5`, `type-analyzer@0.3.0`, `react-palm@~3.3.7`\n\n### New Features\n\n- Support incremental timeline animation\n- Allow changing dataset in layer config\n- Enable polygon filter for h3 layer\n- Show last added filter at the top\n\n### Bug Fixes\n\n- Avoid duplicated h3 layer detection\n- Fixed bug when reversing color palette not update\n\n## Upgrade from v2.2 to v2.3\n\n- Upgrade dependencies to `deck.gl@8.2.0`, `loaders.gl@2.2.5` and `luma.gl@8.2.0`. This should only affects projects with the above libraries in its dependencies.\n\n## Upgrade from v2.1 to v2.2\n\n### New Features\n\n- **Interaction** - Added Geocoder in the interactin panel\n\n### Improvements\n\n- **Localization** - Added Spanish, Catalan, and Portuguese translations\n\n### Bug Fixes\n\n- **Layer** - Aggregation layer fix out-of-domain coloring for valid strings\n- **Export** - Fixed download file for microsoft edge\n\n### API Update\n\n- **Components** - Exported map drawing editor factories\n\n## Upgrade from v2.0 to v2.1\n\n### Breaking Changes\n\n- Upgrade Node v10 for dev development, node requirement is now at `>=10.15.0`\n\n### New Features\n\n- **Provider** - Add cloud provider API\n- **Layer** - Added S2 Layer\n- **Basemap** - Added satellite to base map styles options\n- **Theme** - Added base UI theme to theme option as `base`\n\n### Improvements\n\n- **UI** - Improved data table and layer panel header\n- **Filter** - Better handle filter steps for small domains\n\n### Bug Fixes\n\n- **Layer** - Remove incorrect outlier for better map center detection\n- **Layer** - Fix point layer stroke width\n- **Basemap** - Fix bug custom map style not saved correctly\n- **Export** - Fix bug exported html blank\n\n---\n\n## Upgrade from v1.1.12 to v2.0\n\n### Breaking Changes\n\n- Upgrade deck.gl to `8.0.15`, this only affects projects with deck.gl in its dependencies. Because only one version of deck.gl can be loaded.\n\n### New Features\n\n- **GPU Filter** - Improved time and numeric filter performance by moving calculation to GPU\n- **Geo Fitler** - Added drawing polygon function, allow filter layer based on polygon\n\n### Improvements\n\n- **Layer** - Improved GeoJson and H3 layer geometry rendering\n- **UI** - Support custom side panel tabs. [example](https://github.com/keplergl/kepler.gl/tree/master/examples/replace-component)\n\n### Bug Fixes\n\n---\n\n## Upgrade from v1.1.11 to v1.1.12\n\n### Breaking Changes\n\n#### Dependency Upgrade\n\n- **react** and **react-dom**: minimum required version is now `^16.3`\n- **react-redux** is upgraded to `^7.1.3`. If you have older version of `react-redux` in your app. You will have error loading kepler.gl, likely due to multiple version of `react-redux` installed.\n- **react-palm**: required version is now `^3.1.2`.\n- **react-route**: if you are using `react-router`, we suggest using `^3.2.5` to avoid `React 16.8` lifecycle deprecation warning in the console.\n\n### Bug Fixes\n\n- **Cluster Layer**: Fix incorrect cluster point count. Fix cluster layer missing in exported image.\n\n### Moved from `kepler.gl/utils` to `@kepler.gl/table`\n\n- `maybeToDate`\n- `getNewDatasetColor`\n- `createNewDataEntry`\n- `setFilterGpuMode`\n- `assignGpuChannels`\n- `assignGpuChannel`\n- `resetFilterGpuMode`\n- `getGpuFilterProps`\n- `getDatasetFieldIndexForFilter`\n\n### Moved from `kepler.gl/utils` to `@kepler.gl/reducers`\n\n- `findMapBounds`\n- `exportData`\n- `TOOLTIP_MINUS_SIGN`\n- `getDefaultInteraction`\n- `BRUSH_CONFIG`\n- `findFieldsToShow`\n- `getTooltipDisplayDeltaValue`\n- `getTooltipDisplayValue`\n- `LayersToRender`\n- `AggregationLayerHoverData`\n- `LayerHoverProp`\n- `findDefaultLayer`\n- `calculateLayerData`\n- `getLayerHoverProp`\n- `renderDeckGlLayer`\n- `isLayerRenderable`\n- `isLayerVisible`\n- `prepareLayersForDeck`\n- `prepareLayersToRender`\n- `getCustomDeckLayers`\n- `ComputeDeckLayersProps`\n- `computeDeckLayers`\n\n### Moved from `kepler.gl/processors` to `@kepler.gl/utils`\n\n- `ACCEPTED_ANALYZER_TYPES`\n- `validateInputData`\n- `getSampleForTypeAnalyze`\n- `getFieldsFromData`\n- `renameDuplicateFields`\n- `analyzerTypeToFieldType`\n\n### Moved from `kepler.gl/templates` to `@kepler.gl/utils`\n\n- `exportMapToHTML`\n\n### Moved from `kepler.gl/layers` to `@kepler.gl/utils`\n\n- `getCentroid`\n- `idToPolygonGeo`\n- `h3IsValid`\n- `getHexFields`\n"
  },
  {
    "path": "babel-register.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst forceTranspile = [\n  // ESM libraries that require transpilation\n  /@deck.gl\\/layers/,\n  /@loaders.gl\\/polyfills/,\n  // For some reason babel crashes even before trying to transpile this library\n  // Instead we force transpile @deck.gl/layers which includes it, and alias to a transpiled version in babel.config.js\n  /@mapbox\\/tiny-sdf/\n];\n\nrequire('@babel/register')({\n  // This tells babel where to look for `babel.config.js` file\n  root: __dirname,\n  ignore: [\n    filepath => {\n      return forceTranspile.some(patt => patt.test(filepath))\n        ? false\n        : Boolean(filepath.match(/node_modules/));\n    }\n  ],\n  only: [__dirname],\n  extensions: ['.ts', '.js', '.tsx', '.json']\n});\n\nrequire('@babel/polyfill');\nvar path = require('path');\nvar glob = require('glob');\n\n// Requiring mapbox-gl here prevents polyfill errors during tests.\nrequire('mapbox-gl');\n\n// eslint-disable-next-line func-names\nprocess.argv.slice(2).forEach(function (arg) {\n  // eslint-disable-next-line func-names\n  glob(arg, function (er, files) {\n    if (er) throw er;\n\n    // eslint-disable-next-line func-names\n    files.forEach(function (file) {\n      require(path.resolve(process.cwd(), file));\n    });\n  });\n});\n"
  },
  {
    "path": "babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst {resolve} = require('path');\nconst KeplerPackage = require('./package');\nconst {RESOLVE_ALIASES} = require('./webpack/shared-webpack-configuration');\n\nconst srcDir = resolve(__dirname, 'src');\nconst nodeModules = resolve(__dirname, 'node_modules');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\n\nconst getPlugins = isTest => {\n  const PLUGINS = [\n    ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n    '@babel/plugin-transform-modules-commonjs',\n    '@babel/plugin-transform-class-properties',\n    '@babel/plugin-transform-optional-chaining',\n    '@babel/plugin-transform-logical-assignment-operators',\n    '@babel/plugin-transform-nullish-coalescing-operator',\n    '@babel/plugin-transform-export-namespace-from',\n    [\n      '@babel/transform-runtime',\n      {\n        regenerator: true\n      }\n    ],\n    [\n      'module-resolver',\n      {\n        extensions: ['.js', '.ts', '.tsx', '.json'],\n        root: ['./src'],\n        alias: {\n          test: './test',\n          // We explicitly transpile this ESM library in scripts/fix-dependencies.js and consume the transpiled version here\n          // This may not be needed once switch to Jest is complete as it is handled by transformIgnorePatterns\n          '@mapbox/tiny-sdf': `${nodeModules}/@mapbox/tiny-sdf/index.cjs`,\n          // fix ERR_REQUIRE_ESM in yarn cover\n          'maplibregl-mapbox-request-transformer': `${nodeModules}/maplibregl-mapbox-request-transformer/src/index.cjs`,\n          // compile from @kepler.gl src\n          ...RESOLVE_ALIASES,\n          // loaders.gl cjs bundle of polyfills is not transpiled properly, use esm instead\n          '@loaders.gl/polyfills': `${nodeModules}/@loaders.gl/polyfills/src`\n        }\n      }\n    ],\n    [\n      'search-and-replace',\n      {\n        rules: [\n          {\n            search: '__PACKAGE_VERSION__',\n            replace: KeplerPackage.version\n          }\n        ]\n      }\n    ]\n  ];\n  return PLUGINS;\n};\n\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  const isTest = api.env('test'); // Check if running in test mode\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: getPlugins(isTest),\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/.gitignore",
    "content": "*.egg-info/\n*.py[cod]\n*/__pycache__/\n.ipynb_checkpoints/\n\ndist/\n# labextension build\nkeplergl-jupyter/\nbuild/\n*.py[cod]\nnode_modules/\nenv/\nENV/\nENV3/\n\n# OS X\n.DS_Store\n.vs_code/\n\n# Compiled javascript\nkeplergl/static/*\n\nsetup.orig.py\nsetup.orig2.py\n\nnotebooks/*.html\nnotebooks/*.sh\nnotebooks/*.js\nnotebooks/ignore_*\n\n*/*.tgz\npackage/\n\n*.old*\n*test*\njs/temp.*\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/Dockerfile",
    "content": "# NOTE: PLEASE run `yarn build` before running this dockerfile\nFROM ubuntu:latest\n\n# Install python3\nRUN apt-get update && apt-get install -y python3 python3-pip python3-venv\n\n# Install gdal\nRUN apt-get install -y gdal-bin\n\n# Fix: No such file or directory: 'gdal-config'\nRUN apt-get install -y libgdal-dev\n\n# Make the current directory the working directory\nWORKDIR /kepler.gl\n\n# The current directory is <Root>/bindings/kepler.gl-jupyter\n# Copy the root directory contents into the working directory\nCOPY . .\n\n# Create a virtual environment .venv\nRUN python3 -m venv .venv\n\n# Activate the virtual environment\nRUN . .venv/bin/activate\n\n# Install jupyter_packaging using pip\nRUN .venv/bin/pip install jupyter_packaging\n\n\n# Install keplergl-jupyter in the virtual environment from the current directory\nRUN .venv/bin/pip install .\n\n# Run jupyter notebook with token exposed in logs\nCMD [\".venv/bin/jupyter\", \"notebook\", \"--ip=0.0.0.0\", \"--port=8888\", \"--no-browser\", \"--allow-root\", \"--NotebookApp.log_level='INFO'\"]\n\nEXPOSE 8888\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/MANIFEST.in",
    "content": "recursive-include keplergl/static *.*\nrecursive-include keplergl-jupyter/labextension *.*\ninclude keplergl-jupyter.json\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/README.md",
    "content": "# kepler.gl for Jupyter\n\nThis is the [kepler.gl](http://kepler.gl) jupyter widget, an advanced geospatial visualization tool, to render large-scale interactive maps in Jupyter Notebook.\n\n![Kepler.gl for Jupyter][jupyter_widget]\n\nTable of contacts\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Demo Notebooks](#demo-notebooks)\n- [Usage](#usage)\n- [Local Development Setup](#local-development-setup)\n- [FAQ & Troubleshoot](#faq--troubleshoot)\n\n## Installation\n\n[![Anaconda-Server Badge](https://anaconda.org/conda-forge/keplergl/badges/version.svg)](https://anaconda.org/conda-forge/keplergl) [![PyPI version](https://badge.fury.io/py/keplergl.svg)](https://badge.fury.io/py/keplergl)\n\n### 1. For Jupyter Notebook\n\n#### Using conda:\n\n```shell\nconda install -c conda-forge keplergl\n```\n\n##### Prerequisites\n  - Python >= 3.7\n\n#### Using pip:\n\n```shell\npip install keplergl\n```\n\n##### Prerequisites\n  - For kelplergl <= 0.3.0\n    - Python >= 2\n    - ipywidgets >= 7.0.0\n\nIf you're on Mac, used `pip install`, and you're running Notebook 5.3 and above, you don't need to run the following:\n\n```shell\njupyter nbextension install --py --sys-prefix keplergl # can be skipped for notebook 5.3 and above\njupyter nbextension enable --py --sys-prefix keplergl # can be skipped for notebook 5.3 and above\n```\n\nNOTE: For No Module named 'keplergl' error, please make sure your virtual environment is activated and has been added to the Jupyter kernel.\n\nUse the following command to check the kernel list:\n```shell\njupyter kernelspec list\n```\n\nUse the following command to add the virtual environment to the Jupyter kernel:\n```shell\npython -m ipykernel install --user --name=myenv\n```\n\nThe `--name` parameter is your preferred name to identify the virtual environment\n\n\n### 2. For Google Colab:\n\n`keplergl` (>0.3.0) works with Google Colab. You can install it using pip.\n\n```python\n# Install keplergl (>0.3.0)\n!pip install keplergl\n```\n\n### 3. For JupyterLab\n\n#### JupyterLab 3\n\nNOTE: `keplergl` <=0.3.0 doesn't work with JupyterLab 3. You need to make sure the python package `keplergl` > 0.3.0 is installed.\n\nInstallation using pip:\n```shell\npip install keplergl\n```\nInstallation using conda:\n```shell\nconda install keplergl\n```\n\nThere is no need to use `jupyter labextension install` for `keplergl` > 0.3.0 with JupyterLab3.\n\n#### JupyterLab 1\n\nFor JupyterLab1, you need to install `keplergl-jupyter` labextension from NPM registry. There is no need to install `keplergl` python package.\n\nFirst, install `jupyterlab-manager` for JupyterLab1:\n```shell\njupyter labextension install @jupyter-widgets/jupyterlab-manager@1.1\n```\n\nThen, install `keplergl-jupyter` labextension from NPM registry:\n```shell\njupyter labextension install keplergl-jupyter\n```\n\n##### Prerequisites:\n  - Node >= 12\n  - Python 3\n\n#### JupyterLab 2\n\nFor JupyterLab2, you need to install `keplergl-jupyter` labextension from NPM registry. There is no need to install `keplergl` python package.\n\nFirst, install `jupyterlab-manager` for JupyterLab2:\n```shell\njupyter labextension install @jupyter-widgets/jupyterlab-manager@2\n```\n\nTo install `keplergl-jupyter` from NPM registry, JupyterLab2 has following requirements of dependencies:\n```\nJupyterLab             Extension      Package\n>=16.9.0 <16.10.0   >=17.0.0 <18.0.0  react\n>=16.9.0 <16.10.0   >=17.0.0 <18.0.0  react-dom\n```\n\nHowever, `keplergl-jupyter`<=0.3.0 depends on `react` >= 17.0.2. Therefore, the latest `keplergl-jupyter` can’t be installed with JupyterLab2: if you use `jupyter labextension install keplergl-jupyter`, the version 0.2.2 as a fallback will be installed. Unfortunately, version 0.2.2 does NOT work with JupyterLab2.\n\nA workaround is to modify the file `lib/python3.x/site-packages/jupyterlab/staging/package.json` and remove “react” and “react-dom” from “singletonPackages” object. Then, install keplergl-jupyter using this command:\n```\njupyter labextension install keplergl-jupyter\n```\n\n##### Prerequisites:\n  - Node >= 12\n  - Python 3\n\n## Quick Start\n\n### For Jupyter Notebook and JupyterLab:\n\nNOTE: please make sure the python kernel is correctly specified in the notebook.\n\n```python\n# Load kepler.gl with an empty map\nfrom keplergl import KeplerGl\nmap_1 = KeplerGl(height=400)\nmap_1\n\n# Load kepler.gl with map data and config\n# Since keplergl 0.3.4, you can pass `use_arrow=True` to load and render data faster using GeoArrow, e.g. `KeplerGl(data={'data_1': df}, config=config, use_arrow=True)`\nmap_2 = KeplerGl(height=400, data={'data_1': df}, config=config)\nmap_2\n\n# Add data to map\n# Since keplergl 0.3.4, you can pass `use_arrow=True` to load and render data faster using GeoArrow, e.g. `map_1.add_data(df, 'data_1', use_arrow=True)`\nmap_1.add_data(df, 'data_1')\n\n# Apply config\nmap_1.config(config)\n\n# print data and config\nmap_1.data\nmap_1.config\n\n# save map to html\nmap_1.save_to_html(file_name='keplergl_map.html')\n```\n\n### For Google Colab:\n\nKeplergl (>0.3.0) works with Google Colab. You can install it using pip.\n\n```python\n# Install keplergl (>0.3.0)\n!pip install keplergl\n\n# Load Kepler.gl with an empty map\nfrom keplergl import KeplerGl\nmap_1 = KeplerGl()\n\n# Display map\nmap_1.show()\n```\n\nThe function `show()` is newly introduced for displaying map in Google Colab. The function is defined as:\n```python\ndef show(self, data=None, config=None, read_only=False, center_map=False)\n```\nwith input parameters:\n- data: a data dictionary {\"name\": data}, if not provided, will use current map data\n- config: map config dictionary, if not provided, will use current map config\n- read_only: if read_only is True, hide side panel to disable map customization\n- center_map: if center_map is True, the bound of the map will be updated according to the current map data\n\nPlease note that the map is not interactive due to the limitation of Google Colab. For example, when applying config to the map in Colab, the map won't be updated and one needs to call `show()` again to render a new map in a new cell.\n\n## Demo Notebooks\n- [Load kepler.gl](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/Load%20kepler.gl.ipynb): Load kepler.gl widget, add data and config\n- [Geometry as String](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/Geometry%20as%20String.ipynb): Embed Polygon geometries as `GeoJson` and `WKT` inside a `CSV`\n- [GeoJSON](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/GeoJSON.ipynb): Load GeoJSON to kepler.gl\n- [DataFrame](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/DataFrame.ipynb): Load DataFrame to kepler.gl\n- [GeoDataFrame](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/GeoDataFrame.ipynb): Load GeoDataFrame to kepler.gl\n\n\nhttps://docs.kepler.gl/docs/keplergl-jupyter#1-load-keplergl-map\n## Usage\n  - [1. Load kepler.gl](https://docs.kepler.gl/docs/keplergl-jupyter#1-load-keplergl-map)\n    - [`keplergl.KeplerGl()`](https://docs.kepler.gl/docs/keplergl-jupyter#keplergl)\n  - [2. Add Data](https://docs.kepler.gl/docs/keplergl-jupyter#2-add-data)\n    - [`.add_data()`](https://docs.kepler.gl/docs/keplergl-jupyter#add_data)\n    - [`.data`](https://docs.kepler.gl/docs/keplergl-jupyter#data)\n  - [3. Data Format](https://docs.kepler.gl/docs/keplergl-jupyter#3-data-format)\n    - [`CSV`](https://docs.kepler.gl/docs/keplergl-jupyter#csv)\n    - [`GeoJSON`](https://docs.kepler.gl/docs/keplergl-jupyter#geojson)\n    - [`DataFrame`](https://docs.kepler.gl/docs/keplergl-jupyter#dataframe)\n    - [`GeoDataFrame`](https://docs.kepler.gl/docs/keplergl-jupyter#geodataframe)\n    - [`WKT`](https://docs.kepler.gl/docs/keplergl-jupyter#wkt)\n  - [4. Customize the map](https://docs.kepler.gl/docs/keplergl-jupyter#4-customize-the-map)\n  - [5. Save and load config](https://docs.kepler.gl/docs/keplergl-jupyter#5-save-and-load-config)\n    - [`.config`](https://docs.kepler.gl/docs/keplergl-jupyter#config)\n  - [6. Match config with data](https://docs.kepler.gl/docs/keplergl-jupyter#6-match-config-with-data)\n  - [7. Save Map](https://docs.kepler.gl/docs/keplergl-jupyter#7-save-map)\n    - [`.save_to_html()`](https://docs.kepler.gl/docs/keplergl-jupyter#save_to_html)\n\n## Local Development Setup\n\n### Environment Setup\nYou will need to install node, yarn and Jupyter Notebook.\n\n#### 1. Node and Yarn\nInstall [node](https://nodejs.org/en/download/package-manager/#macos) `> 12`, and [yarn](https://yarnpkg.com/en/docs/install#mac-stable). Use [nvm](https://github.com/creationix/nvm) for better node version management e.g. `nvm install 12`.\n\n\n#### 2. Install Jupyter\n\n- Using conda\n```shell\nconda install jupyter\nconda install notebook 6.0.1\n```\n\n- Using pip\n\n```shell\npip install jupyter\npip install notebook==6.0.1\n```\n\n#### 3. Install GeoPandas\n\n- Using conda\n```shell\nconda install geopandas\n```\n\n- Using pip\n\n```shell\npip install geopandas\n```\n\n### Download and run keplergl in your local Jupyter Notebook\n\n#### Clone Repo\n```shell\ngit clone https://github.com/keplergl/kepler.gl.git\n```\n\n### Setup JS\n#### 1. Install Js module\n```sh\ncd bindings/kepler.gl-jupyter\ncd js\nyarn\n```\n\n#### 2. Load mapbox token\nAdd [Mapbox access token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/) to Node env.\n\n```sh\nexport MapboxAccessTokenJupyter=<your_mapbox_token>\n```\n\n#### 3. Build js module, start a local server to watch for changes\n\n```shell\nnpm start\n```\n\nYou need to run step 2 and 3 to restart the js program. And step 1-3 if any js dependency has changed (Usually after pulling new changes from master).\n\n### Setup jupyter\n\n#### 1. Install python module and enable extension from local files\nThis command must be run **AFTER** the `js` setup, and folder `static/` was created. It only needs to be run once.\n\n```sh\n# dev install from folder containing setup.py\npip install -e .\n\n# only needed in dev mode, not in normal mode.\njupyter nbextension install --py --symlink --sys-prefix keplergl\n\n# only needed in dev mode, not in normal mode.\njupyter nbextension enable --py --sys-prefix keplergl\n```\n\nNOTE: The above command `jupyter nbextension install -py --symlink --sys-prefix keplergl` is trying to create a symbolic link of the folder `bindings/kepler.gl-jupyter/keplergl/static` under the jupyter's folder `nbextensions`. Please check if there is already a folder \"nbextensions/kepler-jupyter\" existed, and you might need to remove it first.\n\nTo find the location of `nbextensions` folder, you can use the following command:\n```shell\n$ where jupyter\n/Users/test/opt/anaconda3/envs/test37/bin/jupyter\n\n# the nbextensions should be at: /Users/test/opt/anaconda3/envs/test37/share/jupyter/nbextensions\n```\n\n\n#### 2. Start jupyter notebook\n\n```shell\ncd notebooks\njupyter notebook\n```\n\n\n#### Have fun!\n\nYou can now start editing the .js and .py files to see changes reflected in your local notebook. After changing files in the js folder, the local start script will recompile the js files and put them in to `keplergl/static` folder. You need to reload the jupyter notebook page to reload the files.\n\n\n#### 3. Development for JupyterLab\n\nTo test the development work in previous 2 steps for JupyterLab. You can build the `keplergl labextension` under the `js` directory:\n\n```shell\njupyter labextension build .\n```\n\nThe output of the jupyter labextension is defined in the file `js/package.json`:\n```javascript\n...\n\"jupyterlab\": {\n  \"extension\": \"babel/labplugin\",\n  \"outputDir\": \"../keplergl-jupyter/labextension\",\n  \"sharedPackages\": {\n    \"@jupyter-widgets/base\": {\n      \"bundled\": false,\n      \"singleton\": true\n    }\n  }\n}\n```\n\nThen, you can either install this labextension to test it:\n```shell\njupyter labextension install .\n```\nor, you can manually create a symbolic link for the folder `bindings/kepler.gl-jupyter/kepler-jupyter/labextension` under the jupyter's folder `labextensions`, e.g. `/Users/test/opt/anaconda3/envs/test37/share/jupyter/labextensions`. You will need to reload the jupyter lab page.\n\n[jupyter_widget]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_widget.png\n[empty_map]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_empty_map.png\n[geodataframe_map]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_geodataframe.png\n[map_interaction]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_custom_map.gif\n[load_map_w_data]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_load_map_w_data.gif\n[map_add_data]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_add_data.png\n[connect_data_config]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_connect_data_w_config.png\n[save_widget_state]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_save_state.png\n\n[wkt]: https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format\n[geojson]: https://tools.ietf.org/html/rfc7946\n[feature_collection]: https://tools.ietf.org/html/rfc7946#section-3.3\n[features]: https://tools.ietf.org/html/rfc7946#section-3.2\n[data_frame]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html\n[geo_data_frame]: https://geopandas.readthedocs.io/en/latest/data_structures.html#geodataframe\n\n[match-config-w-data]: #match-config-with-data\n[data_format]: #3-data-format\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/RELEASE.md",
    "content": "# Kepler.gl Jupyter Releases\n\n## Release a new version\n\nWhen release a new version, the `keplergl-jupyter` js module will be published on NPM and the `keplergl` python module will be published on PyPI.\n\nNOTE: __Version number of the js module **`kelergl-jupyter`** and the python module **`keplergl`** should match__\n\n### Step1:\n\nUpdate `version_info` in keplergl/_version.py in bindings/kepler.gl-jupyter folder.\nUpdate `\"version\": \"0.x.x\"` to match the version info in js/package.json in bindings/kepler.gl-jupyter folder.\nUpdate `EXTENSION_SPEC_VERSION` to match the js module version.\n\n```\ngit add keplergl/_version.py\ngit add js/package.json\ngit commit -am \"keplergl==<version>\"\n```\n\n\n### Step2:\n\nCreate a tag: `<version>-jupyter` e.g. v0.3.4-jupyter\n\n```\ngit tag -a <version>-jupyter -m \"<version>-jupyter\"\ngit push origin <version>-jupyter\n```\n\nThe new tag will trigger the Github Action `build-publish-pypi.yml`: __\"Build KeplerGL Python and NPM packages\"__. The packages will be built and tested, then published to NPM and PyPI using the secret tokens.\n\n### Step3:\n\nFor conda-forge release, please use the repo: https://github.com/conda-forge/keplergl-feedstock\n\nThe new version should be automatically picked and built from PyPi by conda-forge. If you want to submit a manual build:\n\nEdit `meta.yaml` under directory `recipes/`:\n\n* Update the version number\n\n```python\n{% set version = \"0.3.0\" %}\n```\n\n* Update the sha256 value of the latest tarball in PyPi that is published in Step2.\n\n```python\nsource:\n  url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz\n  sha256: cb21047b2104413af1c00ef1ac75794a19e0b578e51c69c86713911d97370167\n```\n\n* Create a pull request and wait for checking from conda-forge team.\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/.gitignore",
    "content": "node_modules/\ndist/\ntemp.*\nbabel/\n\nyarn-error.log\npackage-lock.json\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/README.md",
    "content": "# keplergl-jupyter\n\nThis is a simple jupyter widget for kepler.gl, an advanced geospatial visualization tool, to render large-scale interactive maps.\n\n## Package Install\n\n---\n\n**Prerequisites**\n\n- [node](http://nodejs.org/) >=8.15.0\n- [yarn](https://yarnpkg.com/en/docs/install#mac-stable) >=1.6.0\n\n**More links**\n\n- [Jupyter Widget](https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Custom.html)\n- [Widget Cookiecutter](https://github.com/jupyter-widgets/widget-cookiecutter)\n\n## Local Dev\n\n---\n\n### Local Dev Installation for Classic Notebook\n\nTo develop this package against the classic notebook, run:\n\n- `pip install -e .` (installs python package for development, runs `npm install` and `npm run build`)\n- `jupyter nbextension install --py --symlink --sys-prefix keplergl`\\\n  (symlinks `static/` directory into `<jupyter path>/nbextensions/keplergl/`). Now the notebook has access to the frontend code.\n- `jupyter nbextension enable --py --sys-prefix keplergl`\\\n  (copies `<npm_package_name>.json` into `<environment path>/etc/jupyter/nbconfig/notebook.d/` directory). Now the notebook will load your frontend code on page load.\n\nNow make some changes to your source code. Then:\n\n- After making Python code changes, restarting the notebook kernel will be enough to reflect changes\n- After making JavaScript code changes:\n  - `cd js`\n  - `npm run build`\n  - Refresh browser to reflect changes\n\n### Local Dev Installation for JupyterLab\n\nTo develop this package against JupyterLab, run:\n\n- `pip install -e .` (installs python package for development, runs `npm install` and `npm run build`)\n- `jupyter labextension install @jupyter-widgets/jupyterlab-manager`: this install lab widgets manager.\n- `jupyter labextension install js`: this installs the current labextension into JupyterLab and enables it.\n- `jupyter lab --watch` starts JupyterLab, but in `--watch` mode: it will rebuild itself incrementally if it detects changes.\n\nNow make some changes to your source code. Then:\n\n- After making Python code changes, restarting the notebook kernel will be enough to reflect changes\n- After making JavaScript code changes:\n\n  - `cd js`\n  - `npm run build:lab`\n  - Refresh browser to reflect changes\n\n- By default, the application will load from the JupyterLab staging directory (default is <sys-prefix>/share/jupyter/lab/staging. Check the correct version of `@jupyter-widgets/jupyterlab-manager` and `@jupyter-widgets/base` is install in `yarn.lock`\n\n#### JupyterLab widget Dependencies\n\nInstall correct version of jupyterlab-manager based on your Jupyter Lab version. Make sure `@jupyter-widgets/base` version in the widget does not conflict with requirements in `jupyterlab-manager`.\n\n- [@jupyter-widgets/jupyterlab-manager](https://github.com/jupyter-widgets/ipywidgets/tree/master/packages/jupyterlab-manager)\n- [@jupyter-widgets/base](https://github.com/jupyter-widgets/ipywidgets/tree/master/packages/base)\n- [@jupyter-widgets/base-manager](https://github.com/jupyter-widgets/ipywidgets/tree/master/packages/base-manager)\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  const presets = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\n  const plugins = [\n    '@babel/plugin-transform-modules-commonjs',\n    '@babel/plugin-transform-class-properties',\n    '@babel/plugin-transform-optional-chaining',\n    '@babel/plugin-transform-logical-assignment-operators',\n    '@babel/plugin-transform-nullish-coalescing-operator',\n    '@babel/plugin-transform-export-namespace-from',\n    'transform-inline-environment-variables',\n    [\n      'search-and-replace',\n      {\n        rules: [\n          {\n            search: '__PACKAGE_VERSION__',\n            replace: KeplerPackage.version\n          }\n        ]\n      }\n    ]\n  ];\n\n  return {\n    presets,\n    plugins\n  };\n};\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/embed.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Entry point for the unpkg bundle containing custom model definitions.\n//\n// It differs from the notebook bundle in that it does not need to define a\n// dynamic baseURL for the static assets and may load some css that would\n// already be loaded by the notebook otherwise.\n\n// Export widget models and views, and the npm package version number.\nmodule.exports = require('./keplergl-plugin');\nmodule.exports.version = require('../package.json').version;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/extension.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// This file contains the javascript that is run when the notebook is loaded.\n// It contains some requirejs configuration and the `load_ipython_extension`\n// which is required for any notebook extension.\n//\n// Some static assets may be required by the custom widget javascript. The base\n// url for the notebook is not known at build time and is therefore computed\n// dynamically.\n\n/* eslint-disable no-undef */\n__webpack_public_path__ = `${document.querySelector('body').getAttribute('data-base-url')  }nbextensions/keplergl-jupyter`;\n/* eslint-enable no-undef */\n\n// Configure requirejs\nif (window.require) {\n    window.require.config({\n        map: {\n            \"*\" : {\n                \"keplergl-jupyter\": \"nbextensions/keplergl-jupyter/index\"\n            }\n        }\n    });\n}\n\n// Export the required load_ipython_extension\nmodule.exports = {\n    load_ipython_extension() {}\n};\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Export widget models and views, and the npm package version number.\nmodule.exports = require('./keplergl-plugin');\nmodule.exports.version = require('../package.json').version;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useState, useRef} from 'react';\nimport styled from 'styled-components';\nconst ReactHelmet = require('react-helmet');\nconst Helmet = ReactHelmet ? ReactHelmet.Helmet : null;\n\nimport {\n  SidebarFactory,\n  AddDataButtonFactory,\n  PanelHeaderFactory,\n  CustomPanelsFactory,\n  injectComponents\n} from '@kepler.gl/components';\n\nimport CustomPanelHeaderFactory from './panel-header';\nimport CustomSidebarFactory from './side-bar';\nimport CustomCustomPanelsFactory from './config-panel';\nexport const KEPLER_GL_JUPYTER_VERSION = '__PACKAGE_VERSION__';\n\nconst CustomAddDataButtonFactory = () => {\n  const CustomAddDataButton = () => <div />;\n  return CustomAddDataButton;\n};\n\nconst KeplerGl = injectComponents([\n  [AddDataButtonFactory, CustomAddDataButtonFactory],\n  [SidebarFactory, CustomSidebarFactory],\n  [PanelHeaderFactory, CustomPanelHeaderFactory],\n  [CustomPanelsFactory, CustomCustomPanelsFactory]\n]);\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessTokenJupyter; // eslint-disable-line\n\nconst StyledContainer = styled.div`\n  width: 100%;\n  height: 100%;\n  .kepler-gl .ReactModal__Overlay.ReactModal__Overlay--after-open {\n    position: absolute !important;\n  }\n\n  .kepler-gl .side-panel__content > div {\n    display: flex;\n    height: 100%;\n    flex-direction: column;\n  }\n`;\n\nfunction App() {\n  const rootElm = useRef(null);\n  const [windowDimension, setDimension] = useState({});\n\n  const handleResize = () => {\n    if (!rootElm.current) {\n      return;\n    }\n\n    const width = rootElm.current.offsetWidth;\n    const height = rootElm.current.offsetHeight;\n    const dimensionToSet = {\n      ...(width && width !== windowDimension.width ? {width} : {}),\n      ...(height && height !== windowDimension.height ? {height} : {})\n    };\n\n    setDimension(dimensionToSet);\n  };\n\n  // in Jupyter Lab,  parent component has transition when window resize.\n  // need to delay call to get the final parent width,\n  const resizeDelay = () => window.setTimeout(handleResize, 500);\n\n  useEffect(() => {\n    window.addEventListener('resize', resizeDelay);\n    return () => window.removeEventListener('resize', resizeDelay);\n  }, []);\n\n  return (\n    <StyledContainer ref={rootElm} className=\"keplergl-widget-container\">\n      {Helmet ? (\n        <Helmet>\n          <meta charSet=\"utf-8\" />\n          <link\n            rel=\"stylesheet\"\n            href=\"http://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\"\n          />\n          <link\n            rel=\"stylesheet\"\n            href=\"http://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\"\n          />\n          <link\n            rel=\"stylesheet\"\n            href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\"\n          />\n          <style type=\"text/css\">\n            {`font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;\n                font-weight: 400;\n                font-size: 0.875em;\n                line-height: 1.71429;\n\n                *,\n                *:before,\n                *:after {\n                  -webkit-box-sizing: border-box;\n                  -moz-box-sizing: border-box;\n                  box-sizing: border-box;\n                }\n                body {\n                  margin: 0; padding: 0;\n                }\n                .jupyter-widgets.keplergl-jupyter-widgets {\n                  overflow: hidden;\n                }\n                .p-Widget.p-Panel.jp-OutputArea-output.jupyter-widgets {\n                  overflow: hidden\n                }\n                `}\n          </style>\n          <script async src=\"https://www.googletagmanager.com/gtag/js?id=UA-64694404-19\" />\n          <script>{`window.dataLayer=window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-64694404-19', {page_path: '/keplergl-jupyter-widget'});`}</script>\n        </Helmet>\n      ) : null}\n      <KeplerGl\n        mapboxApiAccessToken={MAPBOX_TOKEN}\n        width={windowDimension.width || 800}\n        height={windowDimension.height || 400}\n        appName=\"Kepler.gl Jupyter\"\n        version={KEPLER_GL_JUPYTER_VERSION}\n        getMapboxRef={handleResize}\n      />\n    </StyledContainer>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/components/config-panel.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState} from 'react';\n\nimport {Button, Icons, TextArea, withState} from '@kepler.gl/components';\nimport {KeplerGlSchema} from '@kepler.gl/schemas';\nimport {visStateLens, mapStateLens, mapStyleLens} from '@kepler.gl/reducers';\nimport {CopyToClipboard} from 'react-copy-to-clipboard';\nimport styled from 'styled-components';\n\nconst Params = {\n  true: 'True',\n  false: 'False',\n  null: 'None'\n};\n\nexport const StyleCopyConfig = styled.div.attrs({\n  className: 'copy-config'\n})`\n  width: 100%;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  .copy-button {\n    position: absolute;\n    margin-top: -45px;\n    right: 28px;\n  }\n  textarea {\n    overflow-y: scroll;\n    white-space: pre-wrap;\n    width: 100%;\n    height: 100%;\n    resize: none;\n  }\n`;\n\n// convert config from string to pything dictionary\nfunction configStringify(config) {\n  // replace true => True; false => False; null => None\n  const configStr = JSON.stringify(config, null, 2);\n  return configStr.replace(/: ([a-z]+)/g, function(_, key) {\n    return ': ' + Params[key] || ': ' + key;\n  });\n}\n\nexport const CopyConfig = ({config}) => {\n  const [copied, setCopy] = useState(false);\n  const value = configStringify(config);\n  return (\n    <StyleCopyConfig>\n      <CopyToClipboard text={value} onCopy={() => setCopy(true)} className=\"copy-button\">\n        <Button width=\"100px\">\n          <Icons.Clipboard height=\"16px\" />\n          {copied ? 'Copied!' : 'Copy'}\n        </Button>\n      </CopyToClipboard>\n      <TextArea value={value} readOnly selected />\n    </StyleCopyConfig>\n  );\n};\n\nfunction CustomSidePanelsFactory() {\n  const CustomPanels = ({activeSidePanel, visState, mapState, mapStyle}) => {\n    const config = KeplerGlSchema.getConfigToSave({visState, mapState, mapStyle});\n    if (activeSidePanel === 'config') {\n      return <CopyConfig config={config} />;\n    }\n\n    return null;\n  };\n\n  CustomPanels.defaultProps = {\n    panels: [\n      {\n        id: 'config',\n        label: 'Config',\n        iconComponent: Icons.CodeAlt\n      }\n    ]\n  };\n\n  const ConnectedCustomPanels = withState(\n    [visStateLens, mapStateLens, mapStyleLens],\n    state => state\n  )(CustomPanels);\n\n  ConnectedCustomPanels.defaultProps = {\n    panels: [\n      {\n        id: 'config',\n        label: 'modal.exportMap.json.configTitle',\n        iconComponent: Icons.CodeAlt\n      }\n    ]\n  };\n\n  return ConnectedCustomPanels;\n}\n\nexport default CustomSidePanelsFactory;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/components/panel-header.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {PanelHeaderFactory, Icons, withState} from '@kepler.gl/components';\nimport {toggleModal} from '@kepler.gl/actions';\nimport React from 'react';\nimport {IntlProvider} from 'react-intl';\n\nconst KEPLER_DOC = 'https://docs.kepler.gl/docs/keplergl-jupyter';\n\nexport function CustomPanelHeaderFactory() {\n  const PanelHeader = PanelHeaderFactory();\n\n  const actionItems = props => [\n    {\n      id: 'docs',\n      iconComponent: Icons.Docs,\n      href: KEPLER_DOC,\n      blank: true,\n      tooltip: 'tooltip.documentation',\n      onClick: () => {}\n    }\n  ];\n\n  const JupyterPanelHeader = props => (\n    <IntlProvider locale=\"en\" messages={{'tooltip.documentation': 'Documentation'}}>\n      <PanelHeader {...props} actionItems={actionItems(props)} />\n    </IntlProvider>\n  );\n  return withState([], state => state, {\n    toggleModal\n  })(JupyterPanelHeader);\n}\n\nexport default CustomPanelHeaderFactory;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {Provider} from 'react-redux';\nimport App from './app';\n\nfunction renderRoot({id, store, ele}) {\n  const Root = () => (\n    <Provider store={store}>\n      <App />\n    </Provider>\n  );\n\n  ReactDOM.render(<Root />, ele);\n}\n\nexport default renderRoot;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/components/side-bar.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {SidebarFactory, CollapseButtonFactory} from '@kepler.gl/components';\nimport styled from 'styled-components';\n\nconst StyledSideBarContainer = styled.div`\n  .side-panel--container {\n    transform:scale(0.85);\n    transform-origin: top left;\n    height: 117.64%;\n    padding-top: 0;\n    padding-right: 0;\n    padding-bottom: 0;\n    padding-left: 0;\n\n    .side-bar {\n      height: 100%;\n    }\n    .side-bar__close {\n      right: -30px;\n      top: 14px;\n    }\n  }\n`;\n\n// Custom sidebar will render kepler.gl default side bar\n// adding a wrapper component to edit its style\nfunction CustomSidebarFactory() {\n  const CloseButton = CollapseButtonFactory();\n  const Sidebar = SidebarFactory(CloseButton);\n  const CustomSidebar = (props) => (\n    <StyledSideBarContainer>\n      <Sidebar {...props}/>\n    </StyledSideBarContainer>\n  );\n  return CustomSidebar;\n}\n\nexport default CustomSidebarFactory;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/kepler.gl.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {addDataToMap, ActionTypes} from '@kepler.gl/actions';\nimport {KeplerGlSchema} from '@kepler.gl/schemas';\nimport document from 'global/document';\n\nimport renderRoot from './components/root';\nimport createAppStore from './store';\nimport {loadJupyterData} from './utils';\nimport log from '../log';\n\nconst getData = that => that.model.get('data');\nconst getConfig = that => that.model.get('config');\nconst getHeight = that => that.model.get('height');\n\nconst DOM_EL_ID = 'keplergl';\nlet counter = 0;\n\nconst NONE_UPDATE_ACTIONS = [\n  ActionTypes.REGISTER_ENTRY,\n  ActionTypes.DELETE_ENTRY,\n  ActionTypes.RENAME_ENTRY,\n  ActionTypes.LOAD_MAP_STYLES,\n  ActionTypes.LAYER_HOVER\n];\n\nfunction getConfigInStore({hash = true, store} = {}) {\n  if (store) {\n    const currentState = store.getState().keplerGl.map;\n    const currentValue = KeplerGlSchema.getConfigToSave(currentState);\n    return hash ? JSON.stringify(currentValue) : currentValue;\n  }\n  return {};\n}\n\nfunction getDatasetsInStore(store) {\n  if (store) {\n    return store.getState().keplerGl.map.visState.datasets;\n  }\n}\n\nclass KeplerGlJupyter {\n  constructor() {\n    this.id = `${DOM_EL_ID}-${counter}`;\n    counter++;\n    this.mapUpdateCounter = 0;\n  }\n\n  create(that) {\n    log('kepler.gl create');\n    let previousValue;\n\n    function handleStoreChange(action, nextStore) {\n      log(action);\n\n      if (!action || NONE_UPDATE_ACTIONS.includes(action.type)) {\n        return;\n      }\n\n      const saveState = getConfigInStore({hash: false, store: nextStore});\n      const hash = JSON.stringify(saveState);\n\n      // should not update model after first UPDATE_MAP action\n      // when component first mounted\n      if (previousValue !== hash && this.mapUpdateCounter > 2) {\n        // keplerGl State has changed\n        log('store state has changed, update model');\n        log(previousValue);\n        log(hash);\n\n        previousValue = hash;\n        that.model.set({config: saveState});\n\n        // that.model.save_changes();\n        that.touch();\n      }\n      if (action.type === ActionTypes.UPDATE_MAP) {\n        this.mapUpdateCounter++;\n      }\n    }\n\n    this.store = createAppStore(handleStoreChange.bind(this));\n\n    const height = getHeight(that);\n\n    that.el.classList.add('jupyter-widgets');\n    that.el.classList.add('keplergl-jupyter-widgets');\n\n    const divElmt = document.createElement('div');\n    divElmt.setAttribute('id', this.id);\n    divElmt.classList.add('kepler-gl');\n    divElmt.setAttribute('style', ` width: 100%; height: ${height}px;`);\n    that.el.appendChild(divElmt);\n\n    renderRoot({id: this.id, store: this.store, ele: divElmt});\n    const data = getData(that);\n    const config = getConfig(that);\n    log('<<<<<<<< render finished! >>>>>>>>>');\n\n    // After rendering the component,\n    // we add the data that's already in the model\n    const hasData = data && Object.keys(data).length;\n    const hasConfig = config && config.version;\n\n    if (hasData) {\n      log('data already in model');\n      addDataConfigToKeplerGl({data, config, store: this.store});\n    } else if (hasConfig) {\n      log('config already in model');\n      this.onConfigChange(that);\n    }\n  }\n\n  onDataChange(that) {\n    log('kepler.gl onDataChange');\n    const data = getData(that);\n\n    addDataConfigToKeplerGl({data, store: this.store});\n  }\n\n  onConfigChange(that) {\n    log('kepler.gl onConfigChange');\n    const config = getConfig(that);\n\n    const currentValue = getConfigInStore({hash: true, store: this.store});\n    if (currentValue === JSON.stringify(config)) {\n      // calling model.set('config') inside the js component will trigger another onConfigChange\n      log('onConfigChange: config is the same as saved in store');\n      return;\n    }\n\n    this.store.dispatch(\n      addDataToMap({\n        // reuse datasets in state\n        // a hack to apply config to existing data\n        datasets: Object.values(getDatasetsInStore(this.store)).map(d => ({\n          info: {\n            id: d.id,\n            label: d.label,\n            color: d.color\n          },\n          data: {\n            fields: d.fields,\n            ...(d.dataContainer instanceof ArrowDataContainer\n              ? {cols: d.dataContainer._cols}\n              : {rows: d.allData})\n          }\n        })),\n        config,\n        options: {centerMap: false}\n      })\n    );\n  }\n}\n\nexport function addDataConfigToKeplerGl({data: inputData, config, options, store}) {\n  const data = inputData ? dataToDatasets(inputData) : [];\n  log(data);\n\n  const results = loadJupyterData(data);\n  const succeeded = results.filter(r => r && r.data);\n  log('addDataConfigToKeplerGl');\n  log(succeeded);\n  log(config);\n  const hasMapState = Boolean(config && config.config && config.config.mapState);\n\n  store.dispatch(\n    addDataToMap({\n      datasets: succeeded,\n      config,\n      options: options || {centerMap: !hasMapState}\n    })\n  );\n}\nexport function dataToDatasets(data) {\n  return Object.keys(data).map(key => ({\n    id: key,\n    data: data[key]\n  }));\n}\n\nexport default KeplerGlJupyter;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// NOTE: this is only used for exporting html template\nimport createAppStore from './store';\nimport renderRoot from './components/root';\nimport document from 'global/document';\nimport Window from 'global/window';\nimport {addDataConfigToKeplerGl} from './kepler.gl';\n\nconst map = (function initKeplerGl() {\n  const id = 'keplergl-0';\n  const store = createAppStore();\n\n  const divElmt = document.createElement('div');\n  divElmt.setAttribute('style', 'width: 100vw; height: 100vh; position: absolute');\n  document.body.appendChild(divElmt);\n\n  return {\n    render: () => {\n      renderRoot({id, store, ele: divElmt});\n    },\n    store\n  };\n})();\n\nmap.render();\n\n(function loadDataConfig(keplerGlMap) {\n  const {data, config, options} = Window.__keplerglDataConfig || {};\n  addDataConfigToKeplerGl({data, config, options, store: keplerGlMap.store});\n})(map);\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {applyMiddleware, compose, createStore} from 'redux';\nimport {combineReducers} from 'redux';\nimport {keplerGlReducer, enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  uiState: {\n    currentModal: null,\n    activeSidePanel: null\n  }\n});\n\nconst reducers = combineReducers({\n  // mount keplerGl reducer\n  keplerGl: customizedKeplerGlReducer\n});\n\nconst createAppStore = onChangeHandler => {\n  const updatesMiddleware = store => next => action => {\n    // exclude some actions\n    // Call the next dispatch method in the middleware chain.\n\n    /* eslint-disable callback-return */\n    const returnValue = next(action);\n    /* eslint-enable callback-return */\n\n    // state after dispatch\n    if (typeof onChangeHandler === 'function') {\n      onChangeHandler(action, store);\n    }\n\n    // This will likely be the action itself, unless\n    // a middleware further in chain changed it.\n    return returnValue;\n  };\n\n  const middlewares = enhanceReduxMiddleware([updatesMiddleware]);\n  const enhancers = [applyMiddleware(...middlewares)];\n\n  const store = createStore(reducers, {}, compose(...enhancers));\n\n  return store;\n};\n\nexport default createAppStore;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl/utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {tableFromIPC} from 'apache-arrow';\nimport {processCsvData, processGeojson, processArrowBatches} from '@kepler.gl/processors';\nimport log from '../log';\nimport console from 'global/console';\n\nfunction handleJuptyerDataFormat(dataEntry) {\n  // This makes passing data between Jupyter the iframe easier\n  // detect data type here\n  log('handleJuptyerDataFormat');\n  const {data, id} = dataEntry;\n  let parsed = data;\n  let type = 'csv';\n  if (typeof data === 'object') {\n    if (data.columns && data.data && data.index) {\n      // Data is parsed as a Dataframe\n      log('data is a dataframe');\n      type = 'df';\n      // parsed = {fields: data.columns, data: data.data};\n    } else {\n      // assume is geojson\n      type = 'json';\n    }\n  } else if (typeof data === 'string') {\n    // check if js string is json string\n    try {\n      parsed = JSON.parse(data);\n      type = 'json';\n    } catch (e) {\n      // assume it is base64 string represents arrow table\n      try {\n        console.log('parse base64string arrow tabl');\n        // convert arrowTable from base64 string to ArrayBuffer\n        const arrowTableBuffer = Buffer.from(data, 'base64').buffer;\n        // create arrow table from ArrayBuffer\n        parsed = tableFromIPC([new Uint8Array(arrowTableBuffer)]);\n        type = 'arrow';\n      } catch (e) {\n        // now we can assume it is csv\n      }\n    }\n  }\n\n  return {data: parsed, type, id};\n}\n\nfunction processReceivedData({data, info}) {\n  // assume there is only 1 file\n  log('processReceivedData');\n  let processed;\n\n  try {\n    processed =\n      info.queryType === 'csv'\n        ? processCsvData(data)\n        : info.queryType === 'json'\n        ? processGeojson(data)\n        : info.queryType === 'df'\n        ? processDataFrame(data)\n        : info.queryType === 'arrow'\n        ? processArrowBatches(data.batches)\n        : null;\n  } catch (e) {\n    console.log(\n      `Kepler.gl fails to parse data, detected data\n    format is ${info.queryType}`,\n      e\n    );\n  }\n\n  return {data: processed, info};\n}\n\nfunction processDataFrame(data) {\n  const fields = data.columns.map(name => ({name}));\n  const rows = data.data;\n\n  // kepler.gl will detect field types\n  return {fields, rows};\n}\n\nexport function loadJupyterData(rawData) {\n  const dataToLoad = rawData.map(handleJuptyerDataFormat).map(rd => ({\n    data: rd.data,\n    info: {\n      id: rd.id,\n      label: rd.id,\n      queryType: rd.type,\n      queryOption: 'jupyter'\n    }\n  }));\n\n  return dataToLoad.map(processReceivedData);\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/keplergl-plugin.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as widgets from '@jupyter-widgets/base';\nimport KeplerGlJupyter from './keplergl/kepler.gl';\nimport log from './log';\n\n// Custom Model. Custom widgets models must at least provide default values\n// for model attributes, including\n//\n//  - `_view_name`\n//  - `_view_module`\n//  - `_view_module_version`\n//\n//  - `_model_name`\n//  - `_model_module`\n//  - `_model_module_version`\n//\n//  when different from the base class.\n\n// When serializing the entire widget state for embedding, only values that\n// differ from the defaults will be specified.\n// Note: in JavaScript, class.extend does not inherently inherit static functions.\nexport class KeplerGlModal extends widgets.DOMWidgetModel {\n  defaults() {\n    return {\n      ...super.defaults(),\n      _model_name: 'KeplerGlModal',\n      _model_module: 'keplergl-jupyter',\n      _view_name: 'KeplerGlView',\n      _view_module: 'keplergl-jupyter',\n      data: {},\n      config: {}\n    };\n  }\n}\n\nexport class KeplerGlView extends widgets.DOMWidgetView {\n  render() {\n    log('KeplerGlModal start render');\n    this.keplergl = new KeplerGlJupyter();\n\n    this.keplergl.create(this);\n\n    // event listener\n    this.model.on('change:data', this.data_changed, this);\n    this.model.on('change:config', this.config_changed, this);\n\n    window.dom = this.el;\n  }\n\n  data_changed() {\n    log('KeplerGlModal start data_changed');\n\n    this.keplergl.onDataChange(this);\n  }\n\n  config_changed() {\n    log('KeplerGlModal start config_change');\n\n    this.keplergl.onConfigChange(this);\n  }\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/labplugin.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nvar keplerglJupyter = require('./index');\nvar base = require('@jupyter-widgets/base');\n\nmodule.exports = {\n  id: 'keplergl-jupyter',\n  requires: [base.IJupyterWidgetRegistry],\n  activate(app, widgets) {\n      widgets.registerWidget({\n          name: 'keplergl-jupyter',\n          version: keplerglJupyter.version,\n          exports: keplerglJupyter\n      });\n  },\n  autoStart: true\n};\n\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/lib/log.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport console from 'global/console';\n\nfunction log(...args) {\n  if (process.env.NODE_ENV === 'development') {\n    console.log(...args);\n  }\n}\n\nexport default log;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/package.json",
    "content": "{\n  \"name\": \"keplergl-jupyter\",\n  \"version\": \"0.3.7\",\n  \"description\": \"This is a simple jupyter widget for kepler.gl, an advanced geo-spatial visualization tool, to render large-scale interactive maps.\",\n  \"author\": \"Shan He\",\n  \"license\": \"MIT\",\n  \"main\": \"babel/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"keywords\": [\n    \"jupyter\",\n    \"jupyterlab\",\n    \"jupyterlab-extension\",\n    \"widgets\",\n    \"ipython\",\n    \"ipywidgets\"\n  ],\n  \"files\": [\n    \"lib/**/*.js\",\n    \"dist/*.js\",\n    \"babel/**\"\n  ],\n  \"scripts\": {\n    \"start\": \"NODE_ENV=development webpack --config ./webpack/dev.js --mode development --watch --progress\",\n    \"clean\": \"rimraf dist/ && rimraf ../keplergl/static/\",\n    \"cleanall\": \"npm run clean && rimraf node_modules/\",\n    \"prepublishOnly\": \"NODE_OPTIONS=--openssl-legacy-provider yarn build && yarn build:lab\",\n    \"build\": \"NODE_OPTIONS=--openssl-legacy-provider npm run clean && npm run build:lab && webpack --config ./webpack/build.js && jupyter labextension build .\",\n    \"build:lab\": \"NODE_OPTIONS=--openssl-legacy-provider rimraf babel/ && mkdir babel && babel lib --out-dir babel\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"lint\": \"eslint lib webpack --fix\",\n    \"prettier\": \"prettier --config ./.prettierrc --print-width 80 --single-quote --write lib/**/*.js\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"7.4.4\",\n    \"@babel/core\": \"^7.12.1\",\n    \"@babel/plugin-transform-class-properties\": \"^7.12.1\",\n    \"@babel/plugin-transform-export-namespace-from\": \"^7.12.1\",\n    \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n    \"@babel/plugin-transform-optional-chaining\": \"^7.12.1\",\n    \"@babel/plugin-transform-runtime\": \"^7.12.1\",\n    \"@babel/plugin-transform-typescript\": \"^7.16.8\",\n    \"@babel/preset-env\": \"^7.0.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@babel/preset-typescript\": \"^7.16.7\",\n    \"@jupyterlab/builder\": \"^4.0.0\",\n    \"apache-arrow\": \"^13.0.0\",\n    \"babel-eslint\": \"^9.0.0\",\n    \"babel-loader\": \"^8.0.0\",\n    \"babel-plugin-search-and-replace\": \"^1.0.0\",\n    \"babel-plugin-transform-inline-environment-variables\": \"0.4.3\",\n    \"eslint\": \"^5.12.1\",\n    \"eslint-config-prettier\": \"^3.6.0\",\n    \"eslint-config-uber-es2015\": \"^3.1.2\",\n    \"eslint-config-uber-jsx\": \"^3.3.3\",\n    \"eslint-plugin-babel\": \"^5.3.0\",\n    \"eslint-plugin-prettier\": \"^3.0.1\",\n    \"eslint-plugin-react\": \"~7.12.4\",\n    \"html-webpack-plugin\": \"^4.3.0\",\n    \"react-dev-utils\": \"^10.2.1\",\n    \"rimraf\": \"^2.6.1\",\n    \"webpack\": \"^4.29.0\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-middleware\": \"^3.5.1\",\n    \"webpack-dev-server\": \"^3.1.14\",\n    \"webpack-hot-middleware\": \"^2.24.3\",\n    \"webpack-stats-plugin\": \"^0.2.1\"\n  },\n  \"dependencies\": {\n    \"@jupyter-widgets/base\": \"^6\",\n    \"@jupyterlab/builder\": \"^4.0.0\",\n    \"@kepler.gl/actions\": \"^3.0.0\",\n    \"@kepler.gl/components\": \"^3.0.0\",\n    \"@kepler.gl/processors\": \"^3.0.0\",\n    \"@kepler.gl/reducers\": \"^3.0.0\",\n    \"@kepler.gl/schemas\": \"^3.0.0\",\n    \"@kepler.gl/styles\": \"^3.0.0\",\n    \"@loaders.gl/arrow\": \"^4.1.0\",\n    \"@loaders.gl/core\": \"^4.1.0\",\n    \"@loaders.gl/csv\": \"^4.1.0\",\n    \"@loaders.gl/json\": \"^4.1.0\",\n    \"global\": \"^4.3.0\",\n    \"node-polyfill-webpack-plugin\": \"^1.1.2\",\n    \"querystring\": \"0.2.1\",\n    \"react\": \"^18.2.0\",\n    \"react-copy-to-clipboard\": \"^5.0.2\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-helmet\": \"^5.2.0\",\n    \"react-intl\": \"^6.3.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"redux\": \"^4.2.1\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"resolutions\": {\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/react\": \"^8.9.27\",\n    \"@deck.gl/aggregation-layers\": \"^8.9.27\",\n    \"@deck.gl/geo-layers\": \"^8.9.27\",\n    \"@deck.gl/layers\": \"^8.9.27\"\n  },\n  \"jupyterlab\": {\n    \"extension\": \"babel/labplugin\",\n    \"outputDir\": \"../keplergl-jupyter/labextension\",\n    \"sharedPackages\": {\n      \"@jupyter-widgets/base\": {\n        \"bundled\": false,\n        \"singleton\": true\n      }\n    }\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/template/keplergl-html.ejs",
    "content": "<% var item, key %>\n\n<% htmlWebpackPlugin.options.appMountIds = htmlWebpackPlugin.options.appMountIds || [] %>\n<% htmlWebpackPlugin.options.lang = htmlWebpackPlugin.options.lang || \"en\" %>\n<% htmlWebpackPlugin.options.links = htmlWebpackPlugin.options.links || [] %>\n<% htmlWebpackPlugin.options.meta = htmlWebpackPlugin.options.meta || [] %>\n<% htmlWebpackPlugin.options.scripts = htmlWebpackPlugin.options.scripts || [] %>\n\n<!DOCTYPE html>\n<html lang=\"<%= htmlWebpackPlugin.options.lang %>\" <% if (htmlWebpackPlugin.files.manifest) { %> manifest=\"<%= htmlWebpackPlugin.files.manifest %>\"<% } %>>\n<head>\n  <meta charset=\"utf-8\">\n  <meta content=\"ie=edge\" http-equiv=\"x-ua-compatible\">\n\n  <% if (htmlWebpackPlugin.options.baseHref) { %>\n  <base href=\"<%= htmlWebpackPlugin.options.baseHref %>\">\n  <% } %>\n\n  <% if (Array.isArray(htmlWebpackPlugin.options.meta)) { %>\n  <% for (item of htmlWebpackPlugin.options.meta) { %>\n  <meta<% for (key in item) { %> <%= key %>=\"<%= item[key] %>\"<% } %>>\n  <% } %>\n  <% } %>\n\n  <title><%= htmlWebpackPlugin.options.title %></title>\n\n  <% if (htmlWebpackPlugin.files.favicon) { %>\n  <link href=\"<%= htmlWebpackPlugin.files.favicon %>\" rel=\"shortcut icon\">\n  <% } %>\n\n  <% if (htmlWebpackPlugin.options.mobile) { %>\n  <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\">\n  <% } %>\n\n  <% for (item of htmlWebpackPlugin.options.links) { %>\n  <% if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>\n  <link<% for (key in item) { %> <%= key %>=\"<%= item[key] %>\"<% } %>>\n  <% } %>\n\n  <% for (item of htmlWebpackPlugin.options.scripts) { %>\n    <% if (typeof item === 'string' || item instanceof String) { item = { src: item, type: 'text/javascript' } } %>\n    <script<% for (key in item) { %> <%= key %>=\"<%= item[key] %>\"<% } %> crossorigin></script>\n  <% } %>\n  <style type=\"text/css\">\n    font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;\n    font-weight: 400;\n    font-size: 0.875em;\n    line-height: 1.71429;\n\n    *,\n    *:before,\n    *:after {\n      -webkit-box-sizing: border-box;\n      -moz-box-sizing: border-box;\n      box-sizing: border-box;\n    }\n    body {\n      margin: 0; padding: 0;\n    }\n  </style>\n</head>\n<body>\n<% if (htmlWebpackPlugin.options.unsupportedBrowser) { %>\n<style> .unsupported-browser { display: none; }</style>\n<div class=\"unsupported-browser\">\n  Sorry, your browser is not supported. Please upgrade to the latest version or switch your browser to use this\n  site. See <a href=\"http://outdatedbrowser.com/\">outdatedbrowser.com</a> for options.\n</div>\n<% } %>\n\n<% if (htmlWebpackPlugin.options.appMountId) { %>\n<div id=\"<%= htmlWebpackPlugin.options.appMountId %>\"></div>\n<% } %>\n<% for (item of htmlWebpackPlugin.options.appMountIds) { %>\n<div id=\"<%= item %>\"></div>\n<% } %>\n\n<% if (htmlWebpackPlugin.options.window) { %>\n<script type=\"text/javascript\">\n  <% for (key in htmlWebpackPlugin.options.window) { %>\n  window['<%= key %>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[key]) %>;\n  <% } %>\n</script>\n<% } %>\n\n<% if (htmlWebpackPlugin.options.inlineManifestWebpackName) { %>\n<%= htmlWebpackPlugin.files[htmlWebpackPlugin.options.inlineManifestWebpackName] %>\n<% } %>\n\n<% if (htmlWebpackPlugin.options.devServer) { %>\n<script src=\"<%= htmlWebpackPlugin.options.devServer %>/webpack-dev-server.js\" type=\"text/javascript\"></script>\n<% } %>\n\n<script>\n  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n\n  ga('create', 'UA-64694404-19', {\n    'storage': 'none',\n    'clientId': localStorage.getItem('ga:clientId')\n  });\n  ga(function(tracker) {\n      localStorage.setItem('ga:clientId', tracker.get('clientId'));\n  });\n  ga('set', 'checkProtocolTask', null); // Disable file protocol checking.\n  ga('set', 'checkStorageTask', null); // Disable cookie storage checking.\n  ga('set', 'historyImportTask', null); // Disable history checking (requires reading from cookies).\n  ga('set', 'page', 'keplergl-jupyter-html');\n\n  ga('send', 'pageview');\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/webpack/build-html.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst path = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');\n\nconst packageJson = require('../package.json');\nconst {dependencies} = packageJson;\n\nconst entry = path.resolve(__dirname, '../lib/keplergl/main.js');\nconst template = path.resolve(__dirname, '../template/keplergl-html.ejs');\n\nfunction clearCarats(version) {\n  return version && version.startsWith('^') ? version.substring(1) : version;\n}\n\nconst VERSIONS = {\n  react: clearCarats(dependencies.react),\n  reactDom: clearCarats(dependencies['react-dom']),\n  redux: clearCarats(dependencies.redux),\n  reactRedux: clearCarats(dependencies['react-redux']),\n  // reactIntl UMD build is not available after 5.x\n  reactIntl: '4.7.6',\n  reactCopyToClipboard: clearCarats(dependencies['react-copy-to-clipboard']),\n  styledComponents: clearCarats(dependencies['styled-components']),\n  keplergl: clearCarats(dependencies['@kepler.gl/components'])\n};\n\nconst externals = [\n  {react: 'React'},\n  {'react-dom': 'ReactDOM'},\n  {redux: 'Redux'},\n  {'react-redux': 'ReactRedux'},\n  {'react-intl': 'ReactIntl'},\n  {'react-copy-to-clipboard': 'CopyToClipboard'},\n  {'styled-components': 'styled'},\n  {'kepler.gl/reducers': 'KeplerGl'},\n  {'kepler.gl/components': 'KeplerGl'},\n  {'kepler.gl/actions': 'KeplerGl'},\n  {'kepler.gl/processors': 'KeplerGl'},\n  {'kepler.gl/schemas': 'KeplerGl'},\n  {'kepler.gl/middleware': 'KeplerGl'},\n  {'react-helmet': 'Helmet'}\n].reduce((accu, ext) => ({\n  ...accu,\n  [Object.keys(ext)[0]]: {\n    root: Object.values(ext)[0],\n    commonjs2: Object.keys(ext)[0],\n    commonjs: Object.keys(ext)[0]\n  }\n}), {});\n\nmodule.exports = (rules, plugins) => ({\n  entry,\n\n  output: {\n    path: path.resolve(__dirname, '../..', 'keplergl', 'static'),\n    libraryTarget: 'umd'\n  },\n\n  externals,\n\n  module: {\n    rules\n  },\n  plugins: [\n    // ...webpackConfig.plugins,\n    // new webpack.optimize.UglifyJsPlugin({sourceMap: true, compressor: {comparisons: false, warnings: false}}),\n    ...plugins,\n    new HtmlWebpackPlugin({\n      template,\n      appMountId: 'app-content',\n      filename: 'keplergl.html',\n      inject: true,\n      links: [\n        'https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css',\n        'https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css',\n        'https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css'\n      ],\n      scripts: [\n\n        `https://unpkg.com/react@${VERSIONS.react}/umd/react.production.min.js`,\n        `https://unpkg.com/react-dom@${VERSIONS.reactDom}/umd/react-dom.production.min.js`,\n        `https://unpkg.com/redux@${VERSIONS.redux}/dist/redux.js`,\n        `https://unpkg.com/react-redux@${VERSIONS.reactRedux}/dist/react-redux.min.js`,\n        `https://unpkg.com/react-intl@${VERSIONS.reactIntl}/dist/react-intl.min.js`,\n        `https://unpkg.com/react-copy-to-clipboard@${VERSIONS.reactCopyToClipboard}/build/react-copy-to-clipboard.min.js`,\n\n        `https://unpkg.com/styled-components@${VERSIONS.styledComponents}/dist/styled-components.min.js`,\n\n        // load kepler.gl last\n        // `https://unpkg.com/kepler.gl@${VERSIONS.keplergl}/umd/keplergl.min.js`\n      ],\n      title: 'Kepler.gl'\n    }),\n    new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/main/])\n  ],\n\n  node: {\n    fs: 'empty'\n  }\n});\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/webpack/build.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst config = require('./config');\n\nconst mode = 'production';\nmodule.exports = [\n  {\n    ...config.extension,\n    devtool: 'source-map',\n    mode\n  },\n  {\n    ...config.widget,\n    devtool: 'source-map',\n    mode\n  },\n  config.umd,\n  config.html\n ];\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/webpack/config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst path = require('path');\nconst version = require('../package.json').version;\nconst webpack = require('webpack');\nconst buildHtml = require('./build-html');\n\nconst rules = [\n  {\n    test: /\\.(js|jsx|ts|tsx)$/,\n    loader: 'babel-loader',\n    include: path.join(__dirname, '../lib', 'keplergl'),\n    exclude: [/node_modules/]\n  },\n  // fix for arrow-related errors\n  {\n    test: /\\.mjs$/,\n    // include: /node_modules[\\\\/]apache-arrow/,\n    include: /node_modules/,\n    type: 'javascript/auto'\n  },\n  // for compiling @probe.gl, website build started to fail (March, 2024)\n  {\n    test: /\\.(js|ts)$/,\n    loader: 'babel-loader',\n    include: [\n      /node_modules[\\\\/]@probe.gl/,\n      /node_modules[\\\\/]@loaders.gl/,\n      /node_modules[\\\\/]@math.gl/\n    ]\n  }\n];\n\nconst plugins = [\n  new webpack.EnvironmentPlugin(['MapboxAccessTokenJupyter'])\n];\n\nmodule.exports = {\n  extension: {\n    // Notebook extension\n    //\n    // This bundle only contains the part of the JavaScript that is run on\n    // load of the notebook. This section generally only performs\n    // some configuration for requirejs, and provides the legacy\n    // \"load_ipython_extension\" function which is required for any notebook\n    // extension.\n    //\n    entry: path.resolve(__dirname, '../lib/extension.js'),\n    output: {\n      filename: 'extension.js',\n      path: path.resolve(__dirname, '../..', 'keplergl', 'static'),\n      libraryTarget: 'amd'\n    },\n    node: {\n      fs: 'empty'\n    },\n    plugins\n  },\n\n  widget: {\n    // Bundle for the notebook containing the custom widget views and models\n    //\n    // This bundle contains the implementation for the custom widget views and\n    // custom widget.\n    // It must be an amd module\n    //\n    entry: path.resolve(__dirname, '../lib/index.js'),\n    // watch: true,\n    watchOptions: {\n      ignored: ['dist', 'node_modules']\n    },\n    output: {\n      filename: 'index.js',\n      path: path.resolve(__dirname, '../..', 'keplergl', 'static'),\n      libraryTarget: 'amd'\n    },\n    // adding source map significantly slows down the\n    // devtool: 'source-map',\n    module: {\n      rules\n    },\n    externals: ['@jupyter-widgets/base'],\n    node: {\n      fs: 'empty'\n    },\n    plugins\n  },\n\n  html: buildHtml(rules, plugins),\n\n  umd: {\n    // Embeddable {{ cookiecutter.npm_package_name }} bundle\n\n    //  This bundle is generally almost identical to the notebook bundle\n    //  containing the custom widget views and models.\n\n    //  The only difference is in the configuration of the webpack public path\n    //  for the static assets.\n\n    //  It will be automatically distributed by unpkg to work with the static\n    //  widget embedder.\n\n    //  The target bundle is always `dist/index.js`, which is the path required\n    //  by the custom widget embedder.\n\n    entry: path.resolve(__dirname, '../lib/embed.js'),\n    output: {\n      filename: 'index.js',\n      path: path.resolve(__dirname, '../dist'),\n      libraryTarget: 'amd',\n      publicPath: `https://unpkg.com/keplergl-jupyter@${version}/dist/`\n    },\n    mode: 'production',\n    module: {\n      rules\n    },\n    externals: ['@jupyter-widgets/base'],\n    node: {\n      fs: 'empty'\n    },\n    plugins\n  }\n};\n\nmodule.exports.rules = rules;\nmodule.exports.plugins = plugins;\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/js/webpack/dev.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst config = require('./config');\n\nmodule.exports = [\n\n  // Notebook extension\n  config.extension,\n\n  // Notebook widget\n  {\n    ...config.widget,\n    devtool: 'source-map',\n    watch: true,\n    watchOptions: {\n      ignored: ['dist', 'node_modules']\n    }\n  },\n\n  // Html template\n  config.html\n];\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/keplergl/__init__.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\nfrom ._version import version_info, __version__\n\nfrom .keplergl import *\n\ndef _jupyter_nbextension_paths():\n    return [{\n        'section': 'notebook',\n        'src': 'static',\n        'dest': 'keplergl-jupyter',\n        'require': 'keplergl-jupyter/extension'\n    }]\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/keplergl/_version.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\nversion_info = (0, 3, 7, 'final', 0)\n\n_specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''}\n\nmajor, minor, patch, release, serial = version_info\n__version__ = '%s.%s.%s%s'%(major, minor, patch,\n  '' if release=='final' else '-' + _specifier_[release]+str(serial))\n# The version of the attribute spec that this package\n# implements. This is the value used in\n# _model_module_version/_view_module_version.\n#\n# Update this value when attributes are added/removed from\n# the widget models, or if the serialized format changes.\n#\n# The major version needs to match that of the JS package.\n# Note: this follows the semver format, which is used to match the JS package version in keplergl-plugin.js\nEXTENSION_SPEC_VERSION = '0.3.7'\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/keplergl/keplergl.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n# Ignore warnings from IPython.core.formatters for _repr_html_ that returns different types in different versions of IPyWidgets\nimport warnings\nwarnings.filterwarnings('ignore', category=UserWarning,\n                        module='IPython.core.formatters')\n\nimport base64\nimport sys\nimport json\nimport ipywidgets as widgets\nfrom pkg_resources import resource_string\nfrom traitlets import Unicode, Dict, Int, validate, TraitError\nimport pandas as pd\nimport geopandas\nimport pyarrow\nimport geoarrow.pyarrow as ga\nimport geoarrow.pandas as _\nimport shapely.wkt\nfrom ._version import EXTENSION_SPEC_VERSION\n\ndocumentation = 'https://docs.kepler.gl/docs/keplergl-jupyter'\n\n# global variable use_arrow, which can be set by parameter use_arrow in KeplerGl\ng_use_arrow = False\n\ndef _arrow_table_to_base64(arrow_table):\n    '''Convert an arrow table to a base64 string'''\n    batches = arrow_table.to_batches()\n    sink = pyarrow.BufferOutputStream()\n    writer = pyarrow.ipc.new_stream(sink, arrow_table.schema)\n    for batch in batches:\n        writer.write_batch(batch)\n    arrow_buf = sink.getvalue()\n\n    # TODO: we could send the bytes directly to the frontend using traitlets.Bytes\n    base64_string = base64.b64encode(arrow_buf.to_pybytes()).decode()\n\n    return base64_string\n\ndef _df_to_dict(df):\n    ''' Create an input dict for Kepler.gl using a DataFrame object\n\n    Inputs:\n    - df: a DataFrame object\n\n    Returns:\n    - dictionary: a dictionary variable that can be used in Kepler.gl\n\n    '''\n    df_copy = df.copy()\n    # Convert all columns that aren't JSON serializable to strings\n    for col in df_copy.columns:\n        try:\n            # just check the first item in the column\n            json.dumps(df_copy[col].iloc[0] if len(df_copy) > 0 else None)\n        except (TypeError, OverflowError):\n            df_copy[col] = df_copy[col].astype(str)\n\n    return df_copy.to_dict('split')\n\n\ndef _df_to_arrow(df: pd.DataFrame):\n    ''' Create an arrow base64string for Kepler.gl using a DataFrame object\n\n    Inputs:\n    - df: a DataFrame object\n\n    Returns:\n    - string: a base64 string that can be used in Kepler.gl\n    '''\n    arrow_table = pyarrow.Table.from_pandas(df)\n    base64_string = _arrow_table_to_base64(arrow_table)\n\n    return base64_string\n\ndef _gdf_to_dict(gdf):\n    ''' Create an input dict for kepler.gl using a GeoDataFrame object\n\n    Inputs:\n    - gdf: a GeoDataFrame object\n\n    Returns:\n    - dictionary: a dictionary variable that can be used in Kepler.gl\n    '''\n    # reproject to 4326 if needed\n    if gdf.crs and not gdf.crs == 4326:\n        gdf = gdf.to_crs(4326)\n\n    # get name of the geometry column\n    # will cause error if data frame has no geometry column\n    name = gdf.geometry.name\n\n    # convert geodataframe to dataframe\n    df = pd.DataFrame(gdf)\n    # convert geometry to wkt\n    df[name] = df.geometry.apply(lambda x: shapely.wkt.dumps(x))\n    #  df[name] = shapely.wkt.dumps(df.geometry)\n\n    return _df_to_dict(df)\n\n\ndef _gdf_to_arrow(gdf):\n    ''' Create an arrow base64string for Kepler.gl using a GeoDataFrame object'''\n    # reproject to 4326 if needed\n    if gdf.crs and not gdf.crs == 4326:\n        gdf = gdf.to_crs(4326)\n\n    # get name of the geometry column\n    # will cause error if data frame has no geometry column\n    name = gdf.geometry.name\n\n    array = ga.as_geoarrow(gdf.geometry, coord_type=ga.CoordType.INTERLEAVED)\n\n    table = pyarrow.Table.from_pandas(gdf.drop(columns=[name]))\n    arrow_table = table.append_column(name, array)\n    base64_string = _arrow_table_to_base64(arrow_table)\n\n    return base64_string\n\n\ndef _normalize_data(data, use_arrow=False):\n    if isinstance(data, pd.DataFrame):\n        if use_arrow:\n            return _gdf_to_arrow(data) if isinstance(data, geopandas.GeoDataFrame) else _df_to_arrow(data)\n        else:\n            return _gdf_to_dict(data) if isinstance(data, geopandas.GeoDataFrame) else _df_to_dict(data)\n    return data\n\n\ndef data_to_json(data, manager):\n    '''Serialize a Python data object.\n    Attributes of this dictionary are to be passed to the JavaScript side.\n    '''\n    if data is None:\n        return None\n    else:\n        if not isinstance(data, dict):\n            print(data)\n            raise TraitError(\n                f\"data type incorrect expecting a dictionary mapping from data id to value, but got {type(data)}\")\n        else:\n            dataset = {}\n            # use g_use_arrow to determine if we should use arrow\n            for key, value in data.items():\n                normalized = _normalize_data(value, g_use_arrow)\n                dataset.update({key: normalized})\n\n            return dataset\n\n\ndef data_from_json(js, manager):\n    '''Deserialize a Javascript date.'''\n    return js\n\n\ndata_serialization = {\n    'from_json': data_from_json,\n    'to_json': data_to_json\n}\n\n\nclass TraitError(Exception):\n    pass\n\n\nclass DataException(TraitError):\n    pass\n\n\n@widgets.register\nclass KeplerGl(widgets.DOMWidget):\n    \"\"\"An example widget.\"\"\"\n    _view_name = Unicode('KeplerGlView').tag(sync=True)\n    _model_name = Unicode('KeplerGlModal').tag(sync=True)\n    _view_module = Unicode('keplergl-jupyter').tag(sync=True)\n    _model_module = Unicode('keplergl-jupyter').tag(sync=True)\n    _view_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True)\n    _model_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True)\n\n    # Attributes\n    value = Unicode('Hello World!').tag(sync=True)\n    data = Dict({}).tag(sync=True, **data_serialization)\n    config = Dict({}).tag(sync=True)\n    height = Int(400).tag(sync=True)\n\n    def __init__(self, **kwargs):\n        if 'show_docs' not in kwargs:\n            kwargs['show_docs'] = True\n        if kwargs['show_docs']:\n            print(f\"User Guide: {documentation}\")\n        kwargs.pop('show_docs')\n        # assign use_arrow to global variable\n        global g_use_arrow\n        g_use_arrow = kwargs.get('use_arrow', False)\n\n        super(KeplerGl, self).__init__(**kwargs)\n\n    @validate('data')\n    def _validate_data(self, proposal):\n        '''Validate data input (return from data_to_json)\n\n        Makes sure data is a dict, and each value should be either a df, a geojson dictionary / string or csv string\n        layers list.\n        '''\n\n        if not isinstance(proposal.value, dict):\n            raise DataException(f\"[data type error]: Expecting a dictionary mapping from id to value, but got {type(proposal.value)}\")\n        else:\n            for key, value in proposal.value.items():\n                if not isinstance(value, pd.DataFrame) and not isinstance(value, str) and not isinstance(value, dict):\n                    raise DataException(f\"[data type error]: value of {key} should be a DataFrame, a Geojson Dictionary or String, a csv String, but got {type(value)}\")\n\n        return proposal.value\n\n    def add_data(self, data, name=\"unnamed\", use_arrow=False):\n        ''' Send data to Voyager\n\n        Inputs:\n        - data string, can be a dataframe, csv string or json string\n        - name string\n\n        Example of use:\n            keplergl.add_data(data_string, name=\"data_1\")\n        '''\n        normalized = _normalize_data(data, use_arrow)\n        copy = self.data.copy()\n        copy.update({name: normalized})\n\n        self.data = copy\n\n    def show(self, data=None, config=None, read_only=False, center_map=False):\n        ''' Display current map in Google Colab\n\n        Inputs:\n        - data: a data dictionary {\"name\": data}, if not provided, will use current map data\n        - config: map config dictionary, if not provided, will use current map config\n        - read_only: if read_only is True, hide side panel to disable map customization\n        - center_map: if center_map is True, the bound of the map will be updated according to the current map data\n\n        Example of use:\n            # this will display map in Google Colab\n            from keplergl import KeplerGL\n            map1 = KeplerGL()\n            map1.show()\n\n        '''\n        keplergl_html = resource_string(\n            __name__, 'static/keplergl.html').decode('utf-8')\n        # find open of body\n        k = keplergl_html.find(\"<body>\")\n\n        data_to_add = data_to_json(self.data if data is None else data, None)\n\n        config_to_add = self.config if config is None else config\n\n        keplergl_data = json.dumps({\"config\": config_to_add, \"data\": data_to_add, \"options\": {\n                                   \"readOnly\": read_only, \"centerMap\": center_map}})\n\n        cmd = f\"window.__keplerglDataConfig = {keplergl_data};\"\n        frame_txt = keplergl_html[:k] + \"<body><script>\" + \\\n            cmd + \"</script>\" + keplergl_html[k+6:]\n\n        if \"google.colab\" in sys.modules:\n            from IPython.display import HTML, Javascript\n            display(HTML(frame_txt))\n            display(Javascript(\n                f\"google.colab.output.setIframeHeight('{self.height}');\"))\n\n    def _repr_html_(self, data=None, config=None, read_only=False, center_map=False):\n        ''' Return current map in an html encoded string\n\n        Inputs:\n        - data: a data dictionary {\"name\": data}, if not provided, will use current map data\n        - config: map config dictionary, if not provided, will use current map config\n        - read_only: if read_only is True, hide side panel to disable map customization\n        - center_map: if center_map is True, the bound of the map will be updated according to the current map data\n\n        Returns:\n        - a html encoded string\n\n        Example of use:\n            # this will save map with provided data and config\n            keplergl._repr_html_(data={\"data_1\": df}, config=config)\n\n            # this will save current map\n            keplergl._repr_html_()\n\n        '''\n        keplergl_html = resource_string(\n            __name__, 'static/keplergl.html').decode('utf-8')\n        # find open of body\n        k = keplergl_html.find(\"<body>\")\n\n        data_to_add = data_to_json(self.data if data is None else data, None)\n        config_to_add = self.config if config is None else config\n\n        # for key in data_to_add:\n        #     print(type(data_to_add[key]))\n\n        keplergl_data = json.dumps({\"config\": config_to_add, \"data\": data_to_add, \"options\": {\n                                   \"readOnly\": read_only, \"centerMap\": center_map}})\n\n        cmd = f\"window.__keplerglDataConfig = {keplergl_data};\"\n        frame_txt = keplergl_html[:k] + \"<body><script>\" + \\\n            cmd + \"</script>\" + keplergl_html[k+6:]\n\n        return frame_txt.encode('utf-8')\n\n    def save_to_html(self, data=None, config=None, file_name='keplergl_map.html', read_only=False, center_map=False):\n        ''' Save current map to an interactive html\n\n        Inputs:\n        - data: a data dictionary {\"name\": data}, if not provided, will use current map data\n        - config: map config dictionary, if not provided, will use current map config\n        - file_name: the html file name, default is keplergl_map.html\n        - read_only: if read_only is True, hide side panel to disable map customization\n\n        Returns:\n        - an html file will be saved to your notebook\n\n        Example of use:\n            # this will save map with provided data and config\n            keplergl.save_to_html(data={\"data_1\": df}, config=config, file_name='first_map.html')\n\n            # this will save current map\n            keplergl.save_to_html(file_name='first_map.html')\n\n        '''\n        frame_txt = self._repr_html_(\n            data=data, config=config, read_only=read_only, center_map=center_map)\n\n        with open(file_name, 'wb') as f:\n            f.write(frame_txt)\n\n        print(f\"Map saved to {file_name}!\")\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/keplergl-jupyter.json",
    "content": "{\n  \"load_extensions\": {\n    \"keplergl-jupyter/extension\": true\n  }\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/DataFrame.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import pandas as pd\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"df = pd.DataFrame(\\n\",\n    \"    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\\n\",\n    \"     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\\n\",\n    \"     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\\n\",\n    \"     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86],\\n\",\n    \"     'Time': ['2019-09-01 08:00','2019-09-01 09:00','2019-09-01 10:00','2019-09-01 11:00', '2019-09-01 12:00']\\n\",\n    \"    })\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"User Guide: https://docs.kepler.gl/docs/keplergl-jupyter\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"b85241da03b2474bb2c5cb6cef155a80\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"KeplerGl(data={'data_1':            City    Country  Latitude  Longitude              Time\\n\",\n       \"0  Buenos Aires  Ar…\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=600, data={'data_1': df})\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Map saved to keplergl_map.html!\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"w1.save_to_html(file_name='keplergl_map.html')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/GeoDataFrame.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import pandas as pd\\n\",\n    \"import geopandas\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"df = pd.DataFrame(\\n\",\n    \"    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\\n\",\n    \"     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\\n\",\n    \"     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\\n\",\n    \"     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86]})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"point_gdf = geopandas.GeoDataFrame(df, geometry=geopandas.points_from_xy(df.Longitude, df.Latitude))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"zipcode_gdf = geopandas.read_file('sf_zip_geo.json')\\n\",\n    \"display(zipcode_gdf.head(5))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"scrolled\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=500)\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(data=zipcode_gdf, name=\\\"zipcode\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(data=point_gdf, name='cities')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.save_to_html(file_name='city_map.html', read_only=True)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/GeoJSON.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with open('geojson-data.json', 'r') as f:\\n\",\n    \"    geojson = f.read()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"map_1 = keplergl.KeplerGl(height=600)\\n\",\n    \"map_1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"map_1.add_data(geojson, \\\"geojson\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"map_1.save_to_html(file_name=\\\"geojson_map.html\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/Load kepler.gl.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import pandas as pd\\n\",\n    \"df = pd.read_csv('hex-data.csv')\\n\",\n    \"df = df.fillna('')\\n\",\n    \"print(df)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import json\\n\",\n    \"with open('sf_zip_geo.json', 'r') as f:\\n\",\n    \"    geojson = f.read()\\n\",\n    \"\\n\",\n    \"json_data=json.loads(geojson)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# assign config = hex_config\\n\",\n    \"%run hex_config.py\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=500, data={\\\"data_1\\\": df}, config=config)\\n\",\n    \"# w1 = keplergl.KeplerGl(height=500)\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(df, 'data_1')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.config\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(json_data, 'geojson')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.save_to_html(file_name='first_map.html', data={\\\"data_1\\\": df}, config=config)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.4\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/geojson-data.json",
    "content": "{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"properties\": {\n        \"fill\": true\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          [\n            [\n              2.331032753,\n              48.8411359942\n            ],\n            [\n              2.3269987106,\n              48.8425764529\n            ],\n            [\n              2.3253250122,\n              48.8431130838\n            ],\n            [\n              2.3240375519,\n              48.8435367357\n            ],\n            [\n              2.3217630386,\n              48.8443840288\n            ],\n            [\n              2.3203468323,\n              48.8448923977\n            ],\n            [\n              2.3189735413,\n              48.8453160346\n            ],\n            [\n              2.3167848587,\n              48.846558682\n            ],\n            [\n              2.3136520386,\n              48.8456831836\n            ],\n            [\n              2.3111844063,\n              48.8450618529\n            ],\n            [\n              2.3070859909,\n              48.8438897762\n            ],\n            [\n              2.2969740629,\n              48.8408994443\n            ],\n            [\n              2.2952359915,\n              48.8427106112\n            ],\n            [\n              2.2949945927,\n              48.84285183\n            ],\n            [\n              2.2928863764,\n              48.8436249961\n            ],\n            [\n              2.2912180424,\n              48.844327543\n            ],\n            [\n              2.2873932123,\n              48.8458173336\n            ],\n            [\n              2.2863632441,\n              48.8462833253\n            ],\n            [\n              2.2854137421,\n              48.8467704938\n            ],\n            [\n              2.2820448875,\n              48.849008584\n            ],\n            [\n              2.2804355621,\n              48.8498063649\n            ],\n            [\n              2.2793841362,\n              48.8498204848\n            ],\n            [\n              2.2790408134,\n              48.8506535519\n            ],\n            [\n              2.2756505013,\n              48.8526585039\n            ],\n            [\n              2.2742128372,\n              48.8531667889\n            ],\n            [\n              2.2726249695,\n              48.8535197615\n            ],\n            [\n              2.2703289986,\n              48.8542539366\n            ],\n            [\n              2.2671747208,\n              48.8551434035\n            ],\n            [\n              2.2649002075,\n              48.855694018\n            ],\n            [\n              2.2637414932,\n              48.8544233601\n            ],\n            [\n              2.2617030144,\n              48.8551010483\n            ],\n            [\n              2.2633981705,\n              48.8576564152\n            ],\n            [\n              2.2649002075,\n              48.8599434285\n            ],\n            [\n              2.2664451599,\n              48.8626538275\n            ],\n            [\n              2.2666811943,\n              48.8631902432\n            ],\n            [\n              2.267947197,\n              48.8647006461\n            ],\n            [\n              2.2694063187,\n              48.8667897264\n            ],\n            [\n              2.2713589668,\n              48.8690480934\n            ],\n            [\n              2.2736120224,\n              48.871009967\n            ],\n            [\n              2.2753071785,\n              48.8723507717\n            ],\n            [\n              2.2773456573,\n              48.8737197667\n            ],\n            [\n              2.2782897949,\n              48.8750040476\n            ],\n            [\n              2.2804999352,\n              48.8774737258\n            ],\n            [\n              2.2818946838,\n              48.8795058267\n            ],\n            [\n              2.2835040092,\n              48.8808605148\n            ],\n            [\n              2.2857570648,\n              48.882229277\n            ],\n            [\n              2.2899627686,\n              48.8848396989\n            ],\n            [\n              2.2913146019,\n              48.8857568418\n            ],\n            [\n              2.294511795,\n              48.8876475141\n            ],\n            [\n              2.2955846786,\n              48.8878732613\n            ],\n            [\n              2.2964859009,\n              48.8878027154\n            ],\n            [\n              2.297065258,\n              48.8876192956\n            ],\n            [\n              2.2975587845,\n              48.8874923124\n            ],\n            [\n              2.298116684,\n              48.8875064216\n            ],\n            [\n              2.298438549,\n              48.8875910772\n            ],\n            [\n              2.2989320755,\n              48.8879296979\n            ],\n            [\n              2.2997903824,\n              48.8882965344\n            ],\n            [\n              2.3006701469,\n              48.8886210414\n            ],\n            [\n              2.3015713692,\n              48.889270049\n            ],\n            [\n              2.3037815094,\n              48.8903423041\n            ],\n            [\n              2.3057985306,\n              48.8900742425\n            ],\n            [\n              2.3074293137,\n              48.8893405928\n            ],\n            [\n              2.3093390465,\n              48.8887762396\n            ],\n            [\n              2.311205864,\n              48.8880566801\n            ],\n            [\n              2.314145565,\n              48.8876051864\n            ],\n            [\n              2.3184370995,\n              48.8917954552\n            ],\n            [\n              2.3220419884,\n              48.8897497449\n            ],\n            [\n              2.325668335,\n              48.887703951\n            ],\n            [\n              2.3268485069,\n              48.8851924481\n            ],\n            [\n              2.3274922371,\n              48.8838378774\n            ],\n            [\n              2.328063794,\n              48.883946667\n            ],\n            [\n              2.329651662,\n              48.88493438\n            ],\n            [\n              2.336046047,\n              48.88318471\n            ],\n            [\n              2.339479275,\n              48.882422738\n            ],\n            [\n              2.342912503,\n              48.882987163\n            ],\n            [\n              2.345744913,\n              48.883551581\n            ],\n            [\n              2.34951366,\n              48.884013132\n            ],\n            [\n              2.35071529,\n              48.882827858\n            ],\n            [\n              2.351959835,\n              48.881501444\n            ],\n            [\n              2.352954691,\n              48.880503646\n            ],\n            [\n              2.354234348,\n              48.8790743\n            ],\n            [\n              2.355393062,\n              48.877752921\n            ],\n            [\n              2.35625137,\n              48.876623947\n            ],\n            [\n              2.3603439331,\n              48.8759495959\n            ],\n            [\n              2.3634767532,\n              48.875413317\n            ],\n            [\n              2.3630905151,\n              48.8741996118\n            ],\n            [\n              2.3636484146,\n              48.8733528233\n            ],\n            [\n              2.3643350601,\n              48.8725060204\n            ],\n            [\n              2.3652791977,\n              48.8711511061\n            ],\n            [\n              2.3658370972,\n              48.8704171788\n            ],\n            [\n              2.3668670654,\n              48.8691468946\n            ],\n            [\n              2.3678970337,\n              48.8694432972\n            ],\n            [\n              2.3685836792,\n              48.8686105428\n            ],\n            [\n              2.368991375,\n              48.8680459556\n            ],\n            [\n              2.3677682877,\n              48.8677636597\n            ],\n            [\n              2.3687553406,\n              48.8666626902\n            ],\n            [\n              2.3702573776,\n              48.8647994559\n            ],\n            [\n              2.371544838,\n              48.8632184755\n            ],\n            [\n              2.3724031448,\n              48.8618350766\n            ],\n            [\n              2.3724031448,\n              48.8604516395\n            ],\n            [\n              2.3719739914,\n              48.8594634467\n            ],\n            [\n              2.371544838,\n              48.8586164088\n            ],\n            [\n              2.3711585999,\n              48.8575434736\n            ],\n            [\n              2.3706436157,\n              48.8566681672\n            ],\n            [\n              2.3701715469,\n              48.8555951902\n            ],\n            [\n              2.3698282242,\n              48.8546916128\n            ],\n            [\n              2.3700428009,\n              48.8529126471\n            ],\n            [\n              2.3684120178,\n              48.8526302657\n            ],\n            [\n              2.3662662506,\n              48.8520654983\n            ],\n            [\n              2.3646354675,\n              48.8515007245\n            ],\n            [\n              2.3644798994,\n              48.8514583662\n            ],\n            [\n              2.3643136024,\n              48.8514195377\n            ],\n            [\n              2.3640614748,\n              48.8513312911\n            ],\n            [\n              2.3639059067,\n              48.8512854028\n            ],\n            [\n              2.3637878895,\n              48.8512395145\n            ],\n            [\n              2.3636591434,\n              48.8511759767\n            ],\n            [\n              2.3634928465,\n              48.8511159687\n            ],\n            [\n              2.363294363,\n              48.8510594905\n            ],\n            [\n              2.3630422354,\n              48.8509888927\n            ],\n            [\n              2.3628437519,\n              48.8509394742\n            ],\n            [\n              2.3625648022,\n              48.8507806287\n            ],\n            [\n              2.3624360561,\n              48.8507100305\n            ],\n            [\n              2.3620980978,\n              48.8506641416\n            ],\n            [\n              2.3619157076,\n              48.8506394322\n            ],\n            [\n              2.3615992069,\n              48.8506217826\n            ],\n            [\n              2.361395359,\n              48.8505829535\n            ],\n            [\n              2.3611968756,\n              48.8505441243\n            ],\n            [\n              2.3611646891,\n              48.8504982353\n            ],\n            [\n              2.361100316,\n              48.8504452864\n            ],\n            [\n              2.3609608412,\n              48.8503888075\n            ],\n            [\n              2.3608535528,\n              48.8503287985\n            ],\n            [\n              2.3607730865,\n              48.8502970291\n            ],\n            [\n              2.3605155945,\n              48.8502299602\n            ],\n            [\n              2.360419035,\n              48.8501805409\n            ],\n            [\n              2.3603439331,\n              48.8501523013\n            ],\n            [\n              2.3601508141,\n              48.8500287529\n            ],\n            [\n              2.3589706421,\n              48.8495804459\n            ],\n            [\n              2.3583698273,\n              48.8491850853\n            ],\n            [\n              2.365322113,\n              48.8442710572\n            ],\n            [\n              2.3629188538,\n              48.8400909296\n            ],\n            [\n              2.3513317108,\n              48.8363624133\n            ],\n            [\n              2.3368692398,\n              48.8393000554\n            ],\n            [\n              2.3351955414,\n              48.8398367214\n            ],\n            [\n              2.3342084885,\n              48.8402039106\n            ],\n            [\n              2.3331356049,\n              48.8405146071\n            ],\n            [\n              2.331032753,\n              48.8411359942\n            ]\n          ]\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/hex-data.csv",
    "content": "hex_id,value,is_true,float_value,empty,time\n89283082c2fffff,64,true,64.1,,11/1/17 11:00\n8928308288fffff,73,true,73.1,,11/1/17 11:00\n89283082c07ffff,65,true,65.1,,11/1/17 11:00\n89283082817ffff,74,true,74.1,,11/1/17 11:00\n89283082c3bffff,66,true,66.1,,11/1/17 11:00\n89283082883ffff,76,true,76.1,,11/1/17 11:00\n89283082c03ffff,60,true,60.1,,11/1/17 11:00\n89283082807ffff,68,true,68.1,,11/1/17 10:00\n8928308289bffff,49,true,49.1,,11/1/17 11:00\n89283082c0fffff,41,true,41.1,,11/1/17 11:00\n89283082c87ffff,50,true,50.1,,11/1/17 11:00\n89283082d4fffff,45,true,45.1,,11/1/17 11:00\n89283082c77ffff,41,true,41.1,,11/1/17 11:00\n89283082c2bffff,53,true,53.1,,11/1/17 11:00\n89283082803ffff,41,true,41.1,,11/1/17 11:00\n89283082813ffff,43,true,43.1,,11/1/17 11:00\n89283082d5bffff,45,true,45.1,,11/1/17 11:00\n89283082897ffff,40,true,40.1,,11/1/17 11:00\n89283082c67ffff,42,true,42.1,,11/1/17 11:00\n89283082d47ffff,51,true,51.1,,11/1/17 11:00\n89283082dc3ffff,52,true,52.1,,11/1/17 11:00\n89283082c33ffff,43,true,43.1,,11/1/17 11:00\n89283082c23ffff,40,true,40.1,,11/1/17 11:00\n89283082887ffff,36,true,36.1,,11/1/17 11:00\n89283082d4bffff,36,true,36.1,,11/1/17 11:00\n892830828bbffff,48,true,48.1,,11/1/17 11:00\n892830828b7ffff,28,true,28.1,,11/1/17 11:00\n89283082c17ffff,34,true,34.1,,11/1/17 11:00\n89283082c6fffff,21,true,21.1,,11/1/17 12:00\n8928308288bffff,25,true,25.1,,11/1/17 11:00\n892830828abffff,26,true,26.1,,11/1/17 11:00\n89283082c27ffff,27,true,27.1,,11/1/17 11:00\n89283082c8fffff,33,true,33.1,,11/1/17 11:00\n89283082cafffff,29,true,29.1,,11/1/17 11:00\n89283082c13ffff,27,true,27.1,,11/1/17 11:00\n89283082cabffff,22,true,22.1,,11/1/17 11:00\n89283082c63ffff,26,true,26.1,,11/1/17 11:00\n89283082d43ffff,30,true,30.1,,11/1/17 11:00\n89283082d53ffff,19,true,19.1,,11/1/17 11:00\n892830828a3ffff,28,false,28.1,,11/1/17 11:00\n89283082d1bffff,20,false,20.1,,11/1/17 11:00\n89283095367ffff,17,false,17.1,,11/1/17 11:00\n8928309536bffff,26,false,26.1,,11/1/17 11:00\n89283082c37ffff,16,false,16.1,,11/1/17 11:00\n89283082c73ffff,17,false,17.1,,11/1/17 11:00\n89283082c8bffff,15,false,15.1,,11/1/17 11:00\n89283082ca7ffff,27,false,27.1,,11/1/17 11:00\n89283082cb3ffff,32,false,32.1,,11/1/17 11:00\n89283082c0bffff,26,false,26.1,,11/1/17 11:00\n89283082ca3ffff,19,false,19.1,,11/1/17 11:00\n89283082dcfffff,18,false,18.1,,11/1/17 11:00\n89283082c1bffff,20,false,20.1,,11/1/17 15:00\n89283082ddbffff,18,false,18.1,,11/1/17 11:00\n8928309534fffff,16,false,16.1,,11/1/17 11:00\n89283082d03ffff,15,false,15.1,,11/1/17 11:00\n89283082cbbffff,21,false,21.1,,11/1/17 11:00\n89283082cd7ffff,9,true,9.1,,11/1/17 11:00\n8928309534bffff,9,true,9.1,,11/1/17 11:00\n892830828c7ffff,13,true,13.1,,11/1/17 11:00\n89283082cc7ffff,12,true,12.1,,11/1/17 11:00\n89283082d0bffff,19,true,19.1,,11/1/17 11:00\n89283082dcbffff,19,true,19.1,,11/1/17 11:00\n89283082dd3ffff,15,true,15.1,,11/1/17 11:00\n89283082dd7ffff,15,true,15.1,,11/1/17 11:00\n892830828d7ffff,13,true,13.1,,11/1/17 11:00\n89283082d17ffff,5,true,5.1,,11/1/17 11:00\n8928309536fffff,8,true,8.1,,11/1/17 11:00\n89283095373ffff,6,true,6.1,,11/1/17 11:00\n89283082cb7ffff,15,true,15.1,,11/1/17 11:00\n89283082d83ffff,9,true,9.1,,11/1/17 11:00\n89283082d07ffff,4,true,4.1,,11/1/17 11:00\n89283082d0fffff,3,true,3.1,,11/1/17 11:00\n89283082d13ffff,6,true,6.1,,11/1/17 11:00\n89283082d9bffff,5,true,5.1,,11/1/17 11:00\n89283082c83ffff,11,true,11.1,,11/1/17 11:00\n89283082d8bffff,4,true,4.1,,11/1/17 11:00\n89283082dc7ffff,5,true,5.1,,11/1/17 11:00\n89283095377ffff,5,true,5.1,,11/1/17 11:00\n89283082c97ffff,4,true,4.1,,11/1/17 11:00\n89283082d7bffff,2,true,2.1,,11/1/17 11:00\n89283082d8fffff,1,true,1.1,,11/1/17 11:00\n89283095347ffff,3,true,3.1,,11/1/17 11:00\n89283095363ffff,2,true,2.1,,11/1/17 11:00\n8928309537bffff,4,true,4.1,,11/1/17 11:00\n89283082d93ffff,6,true,6.1,,11/1/17 11:00\n89283082d73ffff,1,true,1.1,,11/1/17 13:00\n8928309530bffff,1,true,1.1,,11/1/17 11:00\n8928309532bffff,1,true,1.1,,11/1/17 11:00"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/hex_config.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\nconfig = {u'version': u'v1', u'config': {u'visState': {u'layers': [{u'type': u'hexagonId', u'visualChannels': {u'sizeField': {u'type': u'integer', u'name': u'value'}, u'coverageField': None, u'colorScale': u'quantize', u'coverageScale': u'linear', u'colorField': {u'type': u'integer', u'name': u'value'}, u'sizeScale': u'linear'}, u'config': {u'dataId': u'data_1', u'color': [250, 116, 0], u'textLabel': {u'color': [255, 255, 255], u'field': None, u'size': 50, u'anchor': u'middle', u'offset': [0, 0]}, u'label': u'H3 Hexagon', u'isVisible': True, u'visConfig': {u'coverageRange': [0, 1], u'opacity': 0.8, u'elevationScale': 5, u'hi-precision': False, u'coverage': 1, u'enable3d': True, u'sizeRange': [0, 500], u'colorRange': {u'category': u'Uber', u'type': u'sequential', u'colors': [u'#194266', u'#355C7D', u'#63617F', u'#916681', u'#C06C84', u'#D28389', u'#E59A8F', u'#F8B195'], u'reversed': False, u'name': u'Sunrise 8'}}, u'columns': {u'hex_id': u'hex_id'}}, u'id': u'jdys7lp'}], u'interactionConfig': {u'brush': {u'enabled': False, u'size': 0.5}, u'tooltip': {u'fieldsToShow': {u'data_1': [u'hex_id', u'value']}, u'enabled': True}}, u'splitMaps': [], u'layerBlending': u'normal', u'filters': []}, u'mapState': {u'bearing': 2.6192893401015205, u'dragRotate': True, u'zoom': 12.32053899007826, u'longitude': -122.42590232651203, u'isSplit': False, u'pitch': 37.374216241015446, u'latitude': 37.76209132041332}, u'mapStyle': {u'mapStyles': {}, u'topLayerGroups': {}, u'styleType': u'dark', u'visibleLayerGroups': {u'building': True, u'land': True, u'3d building': False, u'label': True, u'water': True, u'border': False, u'road': True}}}}"
  },
  {
    "path": "bindings/kepler.gl-jupyter/notebooks/sf_zip_geo.json",
    "content": "{\"type\": \"FeatureCollection\",\"features\": [\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":1,\"ZIP_CODE\":94107,\"ID\":94107},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.401159718585049, 37.782024266952142 ], [ -122.400374366843309, 37.782644515545172 ], [ -122.400019020063766, 37.782925153640136 ], [ -122.399891477967842, 37.783025880124256 ], [ -122.398930331092998, 37.783784933304034 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.396705177550587, 37.785542130425938 ], [ -122.395895701657864, 37.784896929203114 ], [ -122.395160622349934, 37.78431101230386 ], [ -122.394398389309941, 37.783701667981575 ], [ -122.39343711789931, 37.784459123881106 ], [ -122.39286959393705, 37.78478280203197 ], [ -122.39285700396303, 37.784773072758867 ], [ -122.392216553530702, 37.784278123341721 ], [ -122.392215026316663, 37.784279352365694 ], [ -122.392118766536498, 37.784205306785267 ], [ -122.392032919786118, 37.784168354830349 ], [ -122.391600764141245, 37.783823989838545 ], [ -122.391599105853345, 37.783822668268179 ], [ -122.390856721023212, 37.784410569188324 ], [ -122.389846392025618, 37.783609176617361 ], [ -122.389843136047602, 37.783611740842574 ], [ -122.388306939892615, 37.782385221520776 ], [ -122.388304301898813, 37.782356717503951 ], [ -122.388231340039766, 37.782146697467269 ], [ -122.388106738902522, 37.781931906493995 ], [ -122.388094863959623, 37.781887241816527 ], [ -122.387765464889313, 37.78194244138043 ], [ -122.387754811744713, 37.781795652877442 ], [ -122.385560654233757, 37.781935833609538 ], [ -122.385544999019174, 37.781932650370386 ], [ -122.38553439339303, 37.781923892586839 ], [ -122.385511671392734, 37.781778670688965 ], [ -122.384922372713746, 37.781810068903333 ], [ -122.384901998602373, 37.781825502544642 ], [ -122.384858074907243, 37.781799422577222 ], [ -122.384893494062709, 37.781763146995921 ], [ -122.38491634743859, 37.781777202531266 ], [ -122.38550727073536, 37.781741658296774 ], [ -122.385491676395333, 37.781604563077408 ], [ -122.385493075388183, 37.781591492690396 ], [ -122.38550321006835, 37.781581716485675 ], [ -122.385518655734231, 37.781576662678262 ], [ -122.38762837564704, 37.781450881969413 ], [ -122.387583140105036, 37.780964719298666 ], [ -122.387467577483136, 37.780979617487112 ], [ -122.387479366190874, 37.781103038700273 ], [ -122.387462086323822, 37.781104002067416 ], [ -122.387450297298699, 37.780980580858106 ], [ -122.387337984465276, 37.780987185935416 ], [ -122.387348026094102, 37.781109948402708 ], [ -122.387330746223768, 37.781110911750616 ], [ -122.387320704623349, 37.780988149281868 ], [ -122.387206644553231, 37.780994095205941 ], [ -122.387216703416328, 37.781117544403045 ], [ -122.387199423542512, 37.781118507731705 ], [ -122.387189364714985, 37.780995058807726 ], [ -122.387068420460224, 37.78100248851576 ], [ -122.387080191758074, 37.781125223313062 ], [ -122.387062911534741, 37.78112618662724 ], [ -122.387051140611234, 37.78100345182272 ], [ -122.386937097943829, 37.781010084204837 ], [ -122.386948869025886, 37.781132819015049 ], [ -122.386931588799087, 37.78113378231 ], [ -122.386919818091386, 37.781011047492527 ], [ -122.386812677010425, 37.7810168822962 ], [ -122.386824465334769, 37.781140303837503 ], [ -122.386807185104601, 37.781141267114201 ], [ -122.386795397161777, 37.781017845840225 ], [ -122.386676165495402, 37.781024560738594 ], [ -122.386687953248156, 37.781147982298883 ], [ -122.386670672668515, 37.781148945561135 ], [ -122.386658885297223, 37.781025524268173 ], [ -122.386543112789369, 37.781032183675542 ], [ -122.386554900668202, 37.781155605243434 ], [ -122.386537620431014, 37.781156568480647 ], [ -122.386525832933643, 37.781033147180089 ], [ -122.386411790166775, 37.781039778779515 ], [ -122.386423577828609, 37.781163200360375 ], [ -122.386406297587911, 37.781164163578339 ], [ -122.386394510307582, 37.7810407422648 ], [ -122.386282162758775, 37.781045973167615 ], [ -122.386293984730017, 37.781170767655468 ], [ -122.386276704139945, 37.78117173085996 ], [ -122.386264917419695, 37.781048309528096 ], [ -122.386147415421732, 37.781054996216575 ], [ -122.386159201954754, 37.781178417834589 ], [ -122.386141922053085, 37.781179381008279 ], [ -122.386130135209612, 37.78105595966862 ], [ -122.386019552249252, 37.781062535536719 ], [ -122.386031321484509, 37.781185270717479 ], [ -122.386014040887659, 37.781186233883489 ], [ -122.386002272033735, 37.781063498970049 ], [ -122.385879580345062, 37.78107026841095 ], [ -122.385891367127385, 37.781193690044354 ], [ -122.385874086872718, 37.781194653184308 ], [ -122.385862300471871, 37.781071231818238 ], [ -122.385755176650932, 37.781077752105325 ], [ -122.385766945797698, 37.781200487306606 ], [ -122.385749665539848, 37.78120145042832 ], [ -122.385737896774387, 37.781078715494381 ], [ -122.385622107018349, 37.781084687547477 ], [ -122.385633892683231, 37.781208109217374 ], [ -122.385616612767805, 37.781209072314041 ], [ -122.385604844221021, 37.781086337366979 ], [ -122.385487342103474, 37.781093023397624 ], [ -122.385499110464124, 37.781215758630843 ], [ -122.38548182985329, 37.781216721718849 ], [ -122.385470061873946, 37.781093986752957 ], [ -122.385352559732169, 37.781100672649266 ], [ -122.38536432752538, 37.781223407901315 ], [ -122.385347047602892, 37.781224370958491 ], [ -122.385335279499117, 37.781101635984861 ], [ -122.385219507099919, 37.781108294087041 ], [ -122.385231275020445, 37.781231029346657 ], [ -122.385213994748526, 37.781231992389863 ], [ -122.385202226517421, 37.781109257408659 ], [ -122.385086454440369, 37.781115915374649 ], [ -122.385098222142247, 37.781238650647374 ], [ -122.385080941520883, 37.781239613676604 ], [ -122.385069174200339, 37.781116878671227 ], [ -122.38495511409829, 37.781122822411653 ], [ -122.384966899006869, 37.781246244141819 ], [ -122.384949618382038, 37.781247207151821 ], [ -122.384937851277286, 37.781124472133506 ], [ -122.384823791153082, 37.781130415746894 ], [ -122.384835575844662, 37.781253837490027 ], [ -122.384818295216348, 37.781254800480774 ], [ -122.384792264836477, 37.780979154238715 ], [ -122.384809545055035, 37.780978191257269 ], [ -122.384831743135436, 37.781102819695278 ], [ -122.384925045670656, 37.781097207947639 ], [ -122.384923587517477, 37.780971560938383 ], [ -122.384940867732567, 37.780970597937682 ], [ -122.384963066031062, 37.781095226351141 ], [ -122.385056368546813, 37.781089614499592 ], [ -122.385054910171618, 37.780963967491779 ], [ -122.385072190383269, 37.780963004471836 ], [ -122.385094388899816, 37.781087632860746 ], [ -122.385189421508272, 37.781081993240349 ], [ -122.385187962562227, 37.780956346239556 ], [ -122.385205242424405, 37.780955383205644 ], [ -122.38522744150788, 37.781080011564157 ], [ -122.385322474096498, 37.781074371836517 ], [ -122.385321014925523, 37.780948724837195 ], [ -122.385338295130154, 37.780947761778229 ], [ -122.385360493742681, 37.78107239012293 ], [ -122.385457256077231, 37.781066722624757 ], [ -122.385455797024363, 37.780941075621371 ], [ -122.38547307722547, 37.78094011254268 ], [ -122.385495276407823, 37.781064740856642 ], [ -122.385592038722095, 37.781059073247853 ], [ -122.385590579095435, 37.780933426251508 ], [ -122.385607859293003, 37.780932463153057 ], [ -122.385630058353271, 37.781057091447337 ], [ -122.38572509122794, 37.781051451389651 ], [ -122.385723631030388, 37.780925804400319 ], [ -122.385740911570423, 37.780924841276843 ], [ -122.385763111197619, 37.781049469540704 ], [ -122.385849512311808, 37.781044654171389 ], [ -122.385848051896943, 37.78091900690886 ], [ -122.385865332440758, 37.780918044041726 ], [ -122.385887514843574, 37.781041985837916 ], [ -122.385989466046908, 37.781036234899503 ], [ -122.385988006094351, 37.780910587902042 ], [ -122.386005286281474, 37.780909624745334 ], [ -122.386027486347686, 37.781034252959756 ], [ -122.38611734695634, 37.781029382050761 ], [ -122.386115886434652, 37.78090373478566 ], [ -122.386133166625513, 37.780902771884811 ], [ -122.386155349124707, 37.781026713636507 ], [ -122.386250399355106, 37.78102175959976 ], [ -122.386248938262554, 37.780896112341686 ], [ -122.386266218795839, 37.780895149415798 ], [ -122.386288401860739, 37.781019091137082 ], [ -122.386381721962337, 37.781014164675518 ], [ -122.386380260647812, 37.780888517418902 ], [ -122.386397541177601, 37.780887554473743 ], [ -122.386419741895395, 37.781012182614816 ], [ -122.386513044542724, 37.781006569605005 ], [ -122.386511583006197, 37.780880922349844 ], [ -122.386528863532533, 37.780879959385452 ], [ -122.386551064468406, 37.781004587501954 ], [ -122.386646096514042, 37.780998946712984 ], [ -122.386644635444497, 37.780873299448245 ], [ -122.386661915621403, 37.780872336469891 ], [ -122.386684116778227, 37.780996964561524 ], [ -122.386782608677009, 37.780991268294322 ], [ -122.386781146684797, 37.780865621042153 ], [ -122.386798427204084, 37.780864658038261 ], [ -122.386820628587657, 37.780989286104372 ], [ -122.386907012122748, 37.780983783515829 ], [ -122.386905549920243, 37.780858136265046 ], [ -122.386922830436163, 37.780857173242921 ], [ -122.386945032026247, 37.780981801285769 ], [ -122.387038334595715, 37.780976187860247 ], [ -122.387036872171194, 37.780850540610921 ], [ -122.387054152683675, 37.780849577569541 ], [ -122.387076354491839, 37.780974205587825 ], [ -122.387174846330339, 37.780968508992949 ], [ -122.387173384021025, 37.7808428617396 ], [ -122.387190664183962, 37.780841898683754 ], [ -122.387212865872911, 37.78096652668205 ], [ -122.387306168748907, 37.78096091303906 ], [ -122.387304706217577, 37.780835265787175 ], [ -122.387321986377088, 37.780834302712059 ], [ -122.387344188284104, 37.780958930685784 ], [ -122.387435761378228, 37.780953344631229 ], [ -122.387434316073197, 37.780828383824961 ], [ -122.387451578437933, 37.780826734292219 ], [ -122.387473781252041, 37.780951362230645 ], [ -122.387582651602727, 37.780945499137275 ], [ -122.38754001837809, 37.780493630652451 ], [ -122.38744131821305, 37.780491090765793 ], [ -122.387419058221624, 37.780500374265543 ], [ -122.387330736209734, 37.780497667864211 ], [ -122.387308476209526, 37.780506951617653 ], [ -122.387208063026961, 37.780505125406954 ], [ -122.387184073602839, 37.780514436820333 ], [ -122.387081931006932, 37.780512638185321 ], [ -122.387059670971126, 37.780521921891747 ], [ -122.386964429936867, 37.780519325963184 ], [ -122.386942169884378, 37.780528609647433 ], [ -122.386852118103619, 37.780525930572708 ], [ -122.386829875474817, 37.780535900680007 ], [ -122.386736346744712, 37.780532590439464 ], [ -122.386712374345294, 37.780542588206671 ], [ -122.386618863049748, 37.780539964317235 ], [ -122.386594873201545, 37.780549275890777 ], [ -122.386499632141522, 37.780546679312224 ], [ -122.386475659366695, 37.780556677311395 ], [ -122.386383878154234, 37.780554025277233 ], [ -122.386359888272054, 37.780563336802956 ], [ -122.386266376955888, 37.780560712359396 ], [ -122.386242404491071, 37.780570710305611 ], [ -122.386143703562112, 37.780568168794694 ], [ -122.386119713984229, 37.780577479991436 ], [ -122.386022743164474, 37.78057491124985 ], [ -122.386000500402702, 37.780584880926071 ], [ -122.385901799477139, 37.780582339761835 ], [ -122.385877827288269, 37.780592337079185 ], [ -122.385784298190572, 37.780589026363579 ], [ -122.38576205573851, 37.780598995989244 ], [ -122.385668544412525, 37.780596371620753 ], [ -122.385646283822041, 37.780605654791167 ], [ -122.385554502585677, 37.780603002386016 ], [ -122.385532242331863, 37.780612285803919 ], [ -122.385433541724893, 37.780609743967645 ], [ -122.385411298534606, 37.78061971381274 ], [ -122.385310868512505, 37.780617199530866 ], [ -122.385288625649494, 37.780627169347277 ], [ -122.385191654786624, 37.780624599647666 ], [ -122.38516766472523, 37.780633910656128 ], [ -122.385079342290652, 37.780631202574007 ], [ -122.385057082314987, 37.780640485896626 ], [ -122.384960111442751, 37.78063791600659 ], [ -122.384936138770243, 37.780647913412501 ], [ -122.384835917442729, 37.780653636341071 ], [ -122.384842007573795, 37.78075723388941 ], [ -122.384817825799914, 37.78075899421075 ], [ -122.38480325384981, 37.78052574104445 ], [ -122.384827435209161, 37.780523981006105 ], [ -122.384843799331136, 37.780623294238858 ], [ -122.384923281326039, 37.780618589884874 ], [ -122.384925926962097, 37.780518286009212 ], [ -122.384957841591557, 37.780616663880899 ], [ -122.385044241908545, 37.780611849106826 ], [ -122.385045157967028, 37.780511572605498 ], [ -122.385078784737303, 37.780609236348262 ], [ -122.385154824277294, 37.780605273878329 ], [ -122.385157469939188, 37.780504969717121 ], [ -122.38518938452367, 37.780603347531915 ], [ -122.385275768076681, 37.780597846132551 ], [ -122.385276683130513, 37.780497569640254 ], [ -122.385310328316436, 37.780595919750695 ], [ -122.385398458680143, 37.78059107677575 ], [ -122.385399356494702, 37.780490114106904 ], [ -122.385433018927529, 37.78058915090709 ], [ -122.385519402086118, 37.780583648785594 ], [ -122.385520317170744, 37.78048337283478 ], [ -122.385553962326881, 37.780581722881479 ], [ -122.385633443879414, 37.780577017779677 ], [ -122.385634358810009, 37.780476741827854 ], [ -122.385666274013943, 37.780575119514069 ], [ -122.385749215060514, 37.780570359001402 ], [ -122.385750112750941, 37.780469396598548 ], [ -122.385783775282164, 37.780568432755352 ], [ -122.385864986912736, 37.780563700098369 ], [ -122.385865884092837, 37.780462737425502 ], [ -122.38589954712829, 37.780561773818384 ], [ -122.385987659981154, 37.780556243958578 ], [ -122.385988556648442, 37.780455281290152 ], [ -122.386022220190043, 37.78055431764264 ], [ -122.386106890951012, 37.780549529481313 ], [ -122.386107787802118, 37.780448566806321 ], [ -122.386141451153719, 37.780547603130415 ], [ -122.386229563971753, 37.780542073089819 ], [ -122.386232190406915, 37.780441082739081 ], [ -122.386264124167781, 37.780540146702961 ], [ -122.386347065143937, 37.780535386044313 ], [ -122.386349691419113, 37.78043439569084 ], [ -122.386381625333826, 37.780533459623001 ], [ -122.386462836549086, 37.780528726834362 ], [ -122.386465444877402, 37.780427049764754 ], [ -122.386497396725886, 37.780526800104532 ], [ -122.386582067431817, 37.780522011876563 ], [ -122.386584675942643, 37.78042033479867 ], [ -122.386616627602407, 37.780520085111789 ], [ -122.386699551102055, 37.78051463803542 ], [ -122.386702159451801, 37.78041296095477 ], [ -122.386734111266165, 37.780512711236199 ], [ -122.386817052181556, 37.780507950246935 ], [ -122.386817930281353, 37.780406301126988 ], [ -122.386851612353468, 37.780506023962438 ], [ -122.386929363990731, 37.7805013456708 ], [ -122.386930242282716, 37.780399696544364 ], [ -122.386963924156646, 37.780499419353383 ], [ -122.387046865035501, 37.780494657927875 ], [ -122.387047743173568, 37.78039300907502 ], [ -122.387081425188285, 37.780492731301436 ], [ -122.387171267622861, 37.780487172869918 ], [ -122.387173875339855, 37.780385496327504 ], [ -122.387205827768923, 37.780485246207022 ], [ -122.387295687629134, 37.780480374124949 ], [ -122.387296547982558, 37.780378038825731 ], [ -122.387330247768816, 37.780478447425573 ], [ -122.387406269602806, 37.780473796784797 ], [ -122.38740712980379, 37.780371461484656 ], [ -122.38744082973659, 37.78047187005302 ], [ -122.387527230063114, 37.780467053179258 ], [ -122.38749949992625, 37.780056836734332 ], [ -122.387400782538194, 37.780053609825671 ], [ -122.387378540131436, 37.780063580311925 ], [ -122.387290201196578, 37.780060187435744 ], [ -122.387267941315415, 37.780069470907662 ], [ -122.387167511635454, 37.780066958487033 ], [ -122.387143539437361, 37.780076956068172 ], [ -122.387041380009435, 37.780074471228168 ], [ -122.387019120092901, 37.780083754653084 ], [ -122.386922167327057, 37.780081873095298 ], [ -122.386901619695664, 37.780090442368873 ], [ -122.386811551008179, 37.78008707681861 ], [ -122.386787578762636, 37.780097074601919 ], [ -122.386695780329632, 37.780093736646045 ], [ -122.386671790289242, 37.780103048241536 ], [ -122.386578279887487, 37.780100424039603 ], [ -122.386554289830215, 37.78010973561117 ], [ -122.386459049681108, 37.780107138994076 ], [ -122.386435059945711, 37.780116450261254 ], [ -122.386343278946669, 37.780113798749966 ], [ -122.386319288841676, 37.780123109724549 ], [ -122.386225778441045, 37.780120485792217 ], [ -122.386201788319156, 37.780129796742877 ], [ -122.38610135893272, 37.780127283134078 ], [ -122.386079098889809, 37.780136566656026 ], [ -122.385982128640109, 37.780133997606264 ], [ -122.38595813883623, 37.780143308776367 ], [ -122.385859438837869, 37.78014076729638 ], [ -122.385837196191162, 37.780150737217127 ], [ -122.385743667990269, 37.780147426188911 ], [ -122.385719678159148, 37.780156737585067 ], [ -122.385627896776001, 37.780154084973319 ], [ -122.385603907274145, 37.780163396340377 ], [ -122.385513855977493, 37.78016071596884 ], [ -122.385489865760334, 37.780170027049152 ], [ -122.385392895494292, 37.780167457789418 ], [ -122.385368905944745, 37.780176768559457 ], [ -122.385270205929046, 37.780174226860836 ], [ -122.385246216015972, 37.78018353761145 ], [ -122.385150975133058, 37.78018094022346 ], [ -122.385126985555786, 37.780190251218855 ], [ -122.385038663996198, 37.78018754310051 ], [ -122.385014674056748, 37.780196854078561 ], [ -122.384919433510831, 37.780194256498035 ], [ -122.38489544320835, 37.780203567457356 ], [ -122.384795222823172, 37.780209290346271 ], [ -122.384801312515108, 37.780312887910483 ], [ -122.384777113810173, 37.780313961773459 ], [ -122.384762559495741, 37.780081395028191 ], [ -122.384786741057312, 37.780079634992532 ], [ -122.384803105027004, 37.780178948238863 ], [ -122.384882586203048, 37.780174243917422 ], [ -122.384885231532309, 37.780073940040232 ], [ -122.384917146262893, 37.780172317925249 ], [ -122.385001816666616, 37.780167530831704 ], [ -122.385004462173583, 37.780067226671626 ], [ -122.385036376713245, 37.780165604530019 ], [ -122.38511414553922, 37.780161614154224 ], [ -122.385116790908242, 37.780061310540667 ], [ -122.385148705594133, 37.780159688368748 ], [ -122.385233375608649, 37.780154900565314 ], [ -122.385236021162356, 37.780054596943422 ], [ -122.385267935657353, 37.780152974744887 ], [ -122.385356065837257, 37.780148131521123 ], [ -122.385358710872126, 37.780047827627314 ], [ -122.385390625872631, 37.780146205390146 ], [ -122.385477025953321, 37.780141390018422 ], [ -122.385479653051561, 37.780040399682747 ], [ -122.385511585982442, 37.780139463852002 ], [ -122.385591067071402, 37.78013475905297 ], [ -122.385593711788431, 37.78003445515364 ], [ -122.38562562709447, 37.78013283285312 ], [ -122.385706855349099, 37.780128787029341 ], [ -122.385709482473402, 37.780027796408206 ], [ -122.385741415359448, 37.780126860520987 ], [ -122.385824355914565, 37.780122100504215 ], [ -122.385825252791648, 37.780021137554606 ], [ -122.38585891591876, 37.780120173961421 ], [ -122.385945315592835, 37.780115358252047 ], [ -122.385947942745361, 37.780014367894388 ], [ -122.38597987560469, 37.780113432222926 ], [ -122.386066275948011, 37.78010861613928 ], [ -122.38606717251001, 37.780007653736739 ], [ -122.38610083595357, 37.78010669007471 ], [ -122.386188965678286, 37.780101846233919 ], [ -122.386191574380462, 37.780000169711762 ], [ -122.386223525670573, 37.780099919858799 ], [ -122.386306466157762, 37.780095159228338 ], [ -122.386309092126623, 37.77999416887333 ], [ -122.386341026143853, 37.780093232818778 ], [ -122.386422253963318, 37.780089186233084 ], [ -122.386424863030612, 37.77998750941989 ], [ -122.386456813943639, 37.780087259789596 ], [ -122.386541484497073, 37.780082471584841 ], [ -122.386544092702067, 37.77998079450537 ], [ -122.386576044464221, 37.780080544831833 ], [ -122.386658984914163, 37.780075784227932 ], [ -122.386661592958049, 37.779974107145748 ], [ -122.386693544875143, 37.780073857440478 ], [ -122.386776485303088, 37.780069096479352 ], [ -122.386777363799169, 37.779967447346195 ], [ -122.386809315183925, 37.780067197895306 ], [ -122.386888813892412, 37.780063178385696 ], [ -122.386889674448653, 37.77996084281277 ], [ -122.38692337384856, 37.780061251805442 ], [ -122.387006314247685, 37.780056490682639 ], [ -122.387007174648929, 37.779954155383322 ], [ -122.38704087419768, 37.780054564067953 ], [ -122.387130733547679, 37.78004969211122 ], [ -122.387133323862429, 37.779947329117419 ], [ -122.38716529349125, 37.780047765460061 ], [ -122.387255152824864, 37.780042893408485 ], [ -122.387256012876136, 37.779940557832525 ], [ -122.387289712762055, 37.78004096672084 ], [ -122.387365734157797, 37.780036316380411 ], [ -122.387366594049666, 37.779933980528931 ], [ -122.387400294082084, 37.78003438938579 ], [ -122.387486693563403, 37.780029572821469 ], [ -122.387461810378028, 37.779663260524117 ], [ -122.387378661112308, 37.779659784635669 ], [ -122.387356418813553, 37.779669754843859 ], [ -122.387276728664503, 37.779666223506673 ], [ -122.387252739526915, 37.779675535209975 ], [ -122.387173049376599, 37.779672003802716 ], [ -122.387150789943092, 37.779681287247612 ], [ -122.387067640671745, 37.779677811139862 ], [ -122.387043668592767, 37.779687808975972 ], [ -122.386969150543408, 37.779683507921064 ], [ -122.386945178103403, 37.779693505742671 ], [ -122.386868930320873, 37.779689232309579 ], [ -122.386844958564382, 37.779699230374298 ], [ -122.386765268401049, 37.779695698416859 ], [ -122.386741279182857, 37.779705009741512 ], [ -122.386659859644624, 37.77970150593859 ], [ -122.386635887496141, 37.77971150341719 ], [ -122.386556180245535, 37.77970728514304 ], [ -122.386533937822136, 37.779717255195997 ], [ -122.386452518273302, 37.779713750975354 ], [ -122.386428528664126, 37.77972306224185 ], [ -122.38635402805015, 37.779719447242712 ], [ -122.386331768168034, 37.779728731087609 ], [ -122.386245159413448, 37.779725309752138 ], [ -122.386222916935935, 37.779735279471808 ], [ -122.386151858355305, 37.779730922551266 ], [ -122.386127868702886, 37.77974023375657 ], [ -122.386048178882604, 37.779736701583559 ], [ -122.386025936036276, 37.779746671546171 ], [ -122.385946246559868, 37.779743139298724 ], [ -122.385922256538748, 37.779752450742315 ], [ -122.385842567054112, 37.779748918150233 ], [ -122.385818577011165, 37.779758229298103 ], [ -122.385742346663463, 37.779754641855682 ], [ -122.385718357291026, 37.779763952697529 ], [ -122.385635207994184, 37.779760475579685 ], [ -122.385611235349785, 37.779770473129936 ], [ -122.385528086050684, 37.779766995936541 ], [ -122.385504096654358, 37.779776307009364 ], [ -122.38542267708938, 37.779772802077623 ], [ -122.385400434501221, 37.779782772191204 ], [ -122.385319014927063, 37.779779266913316 ], [ -122.385296754890703, 37.779788550288139 ], [ -122.385225696313427, 37.779784192809487 ], [ -122.385201723952207, 37.779794190270913 ], [ -122.385120304035951, 37.779790685135936 ], [ -122.38509804431726, 37.779799968467685 ], [ -122.385020084218212, 37.779796407941809 ], [ -122.38499609406378, 37.779805719196837 ], [ -122.384916404566042, 37.779802185978852 ], [ -122.384894161901101, 37.779812155996879 ], [ -122.38480257260197, 37.779817053634027 ], [ -122.384809835241697, 37.779898657585051 ], [ -122.384782194261888, 37.779900472932383 ], [ -122.384761383067243, 37.779694102231538 ], [ -122.384789023963819, 37.779692286614427 ], [ -122.384808725038937, 37.779786739726958 ], [ -122.384879557453601, 37.779782173395887 ], [ -122.384880595342565, 37.779686702548538 ], [ -122.384914117331078, 37.779780247404453 ], [ -122.384983237080689, 37.779776395391224 ], [ -122.384984274829336, 37.77968092426827 ], [ -122.385017796952795, 37.779774469369393 ], [ -122.385085186616962, 37.779770645233363 ], [ -122.38508622457347, 37.779675173829325 ], [ -122.385119746476775, 37.779768718907071 ], [ -122.385188848787607, 37.77976418060323 ], [ -122.385189886610789, 37.779668709198148 ], [ -122.385223408641721, 37.779762254246563 ], [ -122.385283897452879, 37.779759226795328 ], [ -122.385284934821826, 37.779663755944007 ], [ -122.385318457309211, 37.779757300685375 ], [ -122.385385847281285, 37.779753476097383 ], [ -122.385386866740049, 37.779657318531406 ], [ -122.3854203897058, 37.77975086351293 ], [ -122.385491239136826, 37.779746983539091 ], [ -122.385492276232114, 37.779651512411178 ], [ -122.385525798982087, 37.779745057368359 ], [ -122.385598378143868, 37.779741149940357 ], [ -122.385599415094418, 37.779645678536831 ], [ -122.385632937976666, 37.779739223463643 ], [ -122.385705499691042, 37.779734629250548 ], [ -122.385706536510781, 37.779639158120546 ], [ -122.385740059531955, 37.779732703291586 ], [ -122.385805719718547, 37.779728905856508 ], [ -122.385806756416358, 37.779633435000086 ], [ -122.385840279547224, 37.779726979593605 ], [ -122.385909399199221, 37.7797231270374 ], [ -122.385910435756728, 37.779627655905408 ], [ -122.385943959022555, 37.779721200744085 ], [ -122.386013061231836, 37.779716661682606 ], [ -122.386014097656016, 37.779621190549562 ], [ -122.386047621049443, 37.779714735358894 ], [ -122.38611501060636, 37.779710910634236 ], [ -122.386116047238417, 37.779615439220123 ], [ -122.386149570411703, 37.779708984006099 ], [ -122.386208311974158, 37.779705297309562 ], [ -122.386211077885747, 37.779609798774842 ], [ -122.386242871788397, 37.779703371203219 ], [ -122.386317180591391, 37.779699435108718 ], [ -122.38631821662446, 37.77960396397274 ], [ -122.386351740393124, 37.779697508695882 ], [ -122.386415670790853, 37.779693738872112 ], [ -122.386416706351284, 37.779598267740703 ], [ -122.386450230587357, 37.779691812430414 ], [ -122.386519350181956, 37.779687959791211 ], [ -122.386520385602097, 37.779592488384218 ], [ -122.386553892529193, 37.77968534660009 ], [ -122.386623012105417, 37.779681493625532 ], [ -122.386624047399195, 37.779586022492111 ], [ -122.386657571897828, 37.779679567397658 ], [ -122.386728421189233, 37.779675686405767 ], [ -122.386729456700351, 37.779580215540342 ], [ -122.386762980969223, 37.779673759872402 ], [ -122.386832100523861, 37.779669906775261 ], [ -122.386833135894662, 37.779574435634252 ], [ -122.386866660298466, 37.779667980211499 ], [ -122.386932302936756, 37.779663495979172 ], [ -122.386933338178679, 37.779568024837168 ], [ -122.386966862705819, 37.779661569386036 ], [ -122.387030793048368, 37.779657799503305 ], [ -122.38704566565184, 37.779562106599087 ], [ -122.387054991513921, 37.779656725452249 ], [ -122.387136202059708, 37.779651991644378 ], [ -122.387139193519673, 37.77956541658935 ], [ -122.387170761825075, 37.779650065266061 ], [ -122.387239881329535, 37.779646211655297 ], [ -122.387241125495137, 37.779558977842669 ], [ -122.387274441082525, 37.779644284971994 ], [ -122.387341830851355, 37.779640459266005 ], [ -122.387344804633926, 37.779553198035785 ], [ -122.387376373154481, 37.7796378461085 ], [ -122.387448951768221, 37.779633937546841 ], [ -122.387428243590108, 37.779295714050406 ], [ -122.387339905566151, 37.779292321211003 ], [ -122.38731766337223, 37.779302291412563 ], [ -122.387236244241279, 37.77929878773282 ], [ -122.387212254525394, 37.779308099439604 ], [ -122.387132565463105, 37.779304567993648 ], [ -122.387108575718415, 37.779313879130164 ], [ -122.387028886654846, 37.779310347614164 ], [ -122.387004914343294, 37.779320345448582 ], [ -122.386932126735232, 37.779316016678415 ], [ -122.386908154408829, 37.779326014493151 ], [ -122.386828447902374, 37.779321796397241 ], [ -122.386804475567118, 37.779331794465456 ], [ -122.386726516216612, 37.779328234787421 ], [ -122.3867025267663, 37.779337546110341 ], [ -122.386624566736728, 37.779333986925117 ], [ -122.386600577610722, 37.779343297947158 ], [ -122.386520887847865, 37.7793397660989 ], [ -122.386496916150335, 37.779349763818921 ], [ -122.386417208949837, 37.779345545456096 ], [ -122.386393237236504, 37.77935554315502 ], [ -122.386318719558147, 37.779351241688801 ], [ -122.386296477216476, 37.779361211972173 ], [ -122.386211598617734, 37.779357762936172 ], [ -122.386187609086349, 37.779367073879669 ], [ -122.386113108495195, 37.779363458733613 ], [ -122.386089119302554, 37.779372769926113 ], [ -122.386011159604379, 37.779369210055243 ], [ -122.385987187482783, 37.779379207677103 ], [ -122.385909210351812, 37.779374961294259 ], [ -122.385885237875669, 37.779384959175481 ], [ -122.385802088988314, 37.779381481900515 ], [ -122.385778099750866, 37.779390793029705 ], [ -122.385698409983121, 37.779387260900187 ], [ -122.385676150450635, 37.779396544067176 ], [ -122.385598190747288, 37.779392983923266 ], [ -122.385574218216505, 37.779402981466639 ], [ -122.3854945117032, 37.779398762469064 ], [ -122.3854705391566, 37.779408759991362 ], [ -122.38538912034204, 37.779405255030689 ], [ -122.385366860087544, 37.779414538699463 ], [ -122.385285441271535, 37.779411033667223 ], [ -122.385261469031803, 37.779421030866885 ], [ -122.385190410808178, 37.779416673366669 ], [ -122.38516642079081, 37.779425984382435 ], [ -122.385088461426918, 37.77942242389598 ], [ -122.38506448916398, 37.779432421330149 ], [ -122.384984782302112, 37.779428201993547 ], [ -122.384960810030293, 37.779438199681202 ], [ -122.384881120582591, 37.779434666444651 ], [ -122.38485713086655, 37.779443977391921 ], [ -122.384765559446308, 37.779449561719943 ], [ -122.384771214222269, 37.779535998445006 ], [ -122.384747015071127, 37.779537072038458 ], [ -122.38473308847739, 37.779329217537743 ], [ -122.384757287215081, 37.779328143952647 ], [ -122.384773494195869, 37.779421279213679 ], [ -122.384846073080865, 37.779417372248531 ], [ -122.384848858140316, 37.779322559636441 ], [ -122.384880632782526, 37.779415445992221 ], [ -122.384949752194402, 37.77941159399834 ], [ -122.384952537135234, 37.779316781932884 ], [ -122.384984294475274, 37.779408981541515 ], [ -122.385051684148522, 37.779405157144254 ], [ -122.385056216100068, 37.779311003588973 ], [ -122.385086243853095, 37.779403231376833 ], [ -122.385155363235981, 37.779399378987847 ], [ -122.385158147906921, 37.779304566642743 ], [ -122.38518992292822, 37.779397452915447 ], [ -122.385248663600038, 37.779393766707848 ], [ -122.385253195301217, 37.779299613144822 ], [ -122.385283223287274, 37.779391840608099 ], [ -122.385354072735922, 37.779387960985474 ], [ -122.385358586870339, 37.779293120699045 ], [ -122.385388632410709, 37.779386034580241 ], [ -122.385459481495417, 37.779382154625232 ], [ -122.385462265784483, 37.779287342547207 ], [ -122.385494023751633, 37.779379542293618 ], [ -122.385561413364954, 37.77937571733046 ], [ -122.385565944668585, 37.779281563755077 ], [ -122.385595973035649, 37.779373791139029 ], [ -122.385663362299539, 37.779369966397688 ], [ -122.385667876044934, 37.779275126373676 ], [ -122.385697921964919, 37.779368040176379 ], [ -122.385765311917211, 37.779364215640285 ], [ -122.385769825525102, 37.779269375337698 ], [ -122.385799871570299, 37.779362289114509 ], [ -122.385874162638899, 37.779357667403531 ], [ -122.38587696382136, 37.779263541485342 ], [ -122.385908722286104, 37.779355740845865 ], [ -122.385976111863471, 37.779351915920223 ], [ -122.385978912570593, 37.779257790005062 ], [ -122.386010671519301, 37.779349989881837 ], [ -122.386078060733283, 37.77934616462889 ], [ -122.386080844570401, 37.779251352255542 ], [ -122.386112620376821, 37.779344238286029 ], [ -122.386174820757347, 37.779340496265526 ], [ -122.386179333847906, 37.779245656221427 ], [ -122.386209380395755, 37.779338569894314 ], [ -122.386283671761092, 37.779333947919731 ], [ -122.386288202139696, 37.779239794041324 ], [ -122.386318231386639, 37.779332021242034 ], [ -122.386380431746588, 37.779328279113052 ], [ -122.386384962016137, 37.779234125779993 ], [ -122.386414991380946, 37.77932635295614 ], [ -122.38648584000218, 37.779322472115325 ], [ -122.386488640754166, 37.779228346176467 ], [ -122.386520399624118, 37.779320545652922 ], [ -122.386589518862621, 37.779316692697755 ], [ -122.386592302045827, 37.779221880311852 ], [ -122.386624078479215, 37.779314766204962 ], [ -122.386691468335471, 37.779310941139499 ], [ -122.386694251035379, 37.779216128482069 ], [ -122.386726027939744, 37.77930901434226 ], [ -122.386793400000514, 37.779304502779596 ], [ -122.38679620001615, 37.779210376838677 ], [ -122.386827959599159, 37.779302575952464 ], [ -122.386897078804992, 37.779298722816996 ], [ -122.386901608410881, 37.779204569189005 ], [ -122.386931638412193, 37.779296796508632 ], [ -122.386992108980877, 37.779293081467337 ], [ -122.386996638465945, 37.779198927835616 ], [ -122.387026668576013, 37.779291154856551 ], [ -122.387095787760913, 37.779287301604562 ], [ -122.38710029967234, 37.779192461524332 ], [ -122.387130347350677, 37.779285374963401 ], [ -122.387199466531811, 37.779281521925199 ], [ -122.387204309717603, 37.779199724010219 ], [ -122.387234026109212, 37.779279594979052 ], [ -122.387304857552181, 37.779275027470717 ], [ -122.387309700628478, 37.779193229825893 ], [ -122.387339417137795, 37.779273101042833 ], [ -122.387415455175415, 37.779269136564935 ], [ -122.387394991136247, 37.778940523542531 ], [ -122.387315302113649, 37.778936992225411 ], [ -122.387291312863496, 37.778946303668732 ], [ -122.387218525259328, 37.778941975080627 ], [ -122.38719628281504, 37.778951945540015 ], [ -122.38712349555685, 37.77894761688772 ], [ -122.387099505926273, 37.778956928022971 ], [ -122.387025006394012, 37.778953313441747 ], [ -122.387002746472433, 37.778962597145551 ], [ -122.386929976655324, 37.778958954818265 ], [ -122.386905987003843, 37.778968266188713 ], [ -122.386836658833175, 37.778963881995679 ], [ -122.386812687306417, 37.778973880055133 ], [ -122.386736440270226, 37.778969606535817 ], [ -122.386712468022296, 37.778979604036806 ], [ -122.386637951048542, 37.77897530276654 ], [ -122.386613979138446, 37.778985300516517 ], [ -122.386549822682738, 37.778980146669305 ], [ -122.386527580483204, 37.778990116996944 ], [ -122.386449603383866, 37.778985870976705 ], [ -122.386425631783965, 37.778995868408273 ], [ -122.386356303615884, 37.778991483932835 ], [ -122.386332313881979, 37.779000795186512 ], [ -122.386256084279836, 37.778997207801154 ], [ -122.386233824940902, 37.779006491348653 ], [ -122.386164496774228, 37.779002106760423 ], [ -122.386140524798748, 37.779012104688697 ], [ -122.386067737197138, 37.779007775390234 ], [ -122.386043765192852, 37.779017772749661 ], [ -122.385962346433857, 37.779014268190103 ], [ -122.385938356643209, 37.779023579363574 ], [ -122.385865569388116, 37.779019249934791 ], [ -122.385841597013936, 37.779029247533202 ], [ -122.385768809758915, 37.779024918044691 ], [ -122.385744837376848, 37.779034915898023 ], [ -122.385675509212518, 37.779030531022293 ], [ -122.385651520065593, 37.779039841851741 ], [ -122.385578749547165, 37.77903619870164 ], [ -122.385554760393234, 37.779045509785973 ], [ -122.385481989874023, 37.779041866576151 ], [ -122.385458000713072, 37.779051177915363 ], [ -122.385385212767062, 37.779046848201126 ], [ -122.385361223578457, 37.779056158971507 ], [ -122.385286723685297, 37.779052543297539 ], [ -122.385262734143524, 37.779061854327978 ], [ -122.385195135701593, 37.77905744150933 ], [ -122.385171163224882, 37.779067438971381 ], [ -122.385094916187072, 37.779063164390635 ], [ -122.385070944393846, 37.779073162095791 ], [ -122.385001616233609, 37.779068776823863 ], [ -122.38497935636353, 37.77907805986537 ], [ -122.384906586186588, 37.779074416294911 ], [ -122.384884326309916, 37.779083699593059 ], [ -122.384796197708141, 37.779088542459625 ], [ -122.384801451464668, 37.77915919068996 ], [ -122.384780712232896, 37.779160209251955 ], [ -122.384765734942761, 37.778979153747279 ], [ -122.384786456703722, 37.778977448743071 ], [ -122.384804149136215, 37.779060946405302 ], [ -122.384871538841949, 37.779057121830682 ], [ -122.384874585188271, 37.778972606432866 ], [ -122.384906098382288, 37.779055195841352 ], [ -122.384966568862538, 37.779051482113282 ], [ -122.384969632515677, 37.778967652883104 ], [ -122.385001128397789, 37.779049556096091 ], [ -122.385058156860921, 37.779046584353601 ], [ -122.385061202980566, 37.778962068401654 ], [ -122.385092716384577, 37.779044658035005 ], [ -122.385158376003019, 37.779040861234485 ], [ -122.38516315173905, 37.77895631817001 ], [ -122.385192918111116, 37.779038248990936 ], [ -122.385249946543951, 37.779035276606521 ], [ -122.385253009876607, 37.778951447368748 ], [ -122.385284506064338, 37.779033350506261 ], [ -122.385350165317007, 37.779029553604488 ], [ -122.385353228882295, 37.778945724358508 ], [ -122.385382995459778, 37.779027655131593 ], [ -122.385445195618914, 37.779023913770359 ], [ -122.385448258723784, 37.778940084252753 ], [ -122.385479755121978, 37.779021987338304 ], [ -122.385541972691954, 37.779018932096108 ], [ -122.385545018273902, 37.778934416680386 ], [ -122.385576532197149, 37.779017005910269 ], [ -122.385638732330676, 37.779013264172356 ], [ -122.385641777795314, 37.77892874847948 ], [ -122.385673291830699, 37.779011337958153 ], [ -122.38573203252615, 37.779007651778535 ], [ -122.385735094960694, 37.77892382225879 ], [ -122.385766592014193, 37.779005725262394 ], [ -122.385828792128677, 37.779001983424223 ], [ -122.385831854467739, 37.778918154451013 ], [ -122.385863351625531, 37.779000057428874 ], [ -122.385925569154423, 37.778997001709705 ], [ -122.385930344007448, 37.778912458338894 ], [ -122.385960128639468, 37.778995075411409 ], [ -122.386030959889439, 37.778990508668088 ], [ -122.386034022338819, 37.778906679409381 ], [ -122.386065519368728, 37.778988582338918 ], [ -122.386127736893627, 37.778985527062204 ], [ -122.386132511508848, 37.778900983408484 ], [ -122.386162278927955, 37.778982913985502 ], [ -122.386221036657432, 37.7789799137378 ], [ -122.386224081811065, 37.778895398298431 ], [ -122.386255596133822, 37.778977987627471 ], [ -122.386319526256145, 37.778974217575232 ], [ -122.386322588033238, 37.778890388314295 ], [ -122.38635408572037, 37.778972291161459 ], [ -122.386412826344156, 37.778968604368089 ], [ -122.386417618076294, 37.778884747421706 ], [ -122.386447385803351, 37.778966677926974 ], [ -122.386514775006972, 37.77896285296778 ], [ -122.386517836902243, 37.778879023421496 ], [ -122.386549334453889, 37.778960926222204 ], [ -122.386602903347566, 37.778958008816609 ], [ -122.386605964804218, 37.778874179548069 ], [ -122.386637462796912, 37.778956082319787 ], [ -122.386699662825961, 37.778952340022151 ], [ -122.386702724173148, 37.778868510751018 ], [ -122.386734222270135, 37.778950413496958 ], [ -122.386799881728336, 37.778946616056871 ], [ -122.386804673015121, 37.778862758819848 ], [ -122.386834441160303, 37.778944689227728 ], [ -122.386894928894066, 37.778941660955091 ], [ -122.38689797293344, 37.778857145503274 ], [ -122.386929488335198, 37.77893973464726 ], [ -122.386989958613299, 37.77893601960681 ], [ -122.386993002191389, 37.778851503883409 ], [ -122.387024518042367, 37.778934092996543 ], [ -122.387086718033501, 37.778930350494704 ], [ -122.387091509002346, 37.778846493520362 ], [ -122.387121277457396, 37.778928423856051 ], [ -122.387183477445888, 37.778924681577777 ], [ -122.387186764992791, 37.77884977579707 ], [ -122.387218036857675, 37.778922754636191 ], [ -122.387280254273051, 37.778919698476713 ], [ -122.387283524285621, 37.778844106523373 ], [ -122.387314813687013, 37.77891777178133 ], [ -122.387382202448663, 37.778913946057301 ], [ -122.387364166474967, 37.778612763090031 ], [ -122.387286207520319, 37.778609204336291 ], [ -122.387262218365407, 37.778618515499687 ], [ -122.387185989110648, 37.778614928715285 ], [ -122.387161999948319, 37.778624240132871 ], [ -122.387092672083611, 37.778619856090089 ], [ -122.387070429718904, 37.778629826526299 ], [ -122.386994183021031, 37.778625553173391 ], [ -122.386971941319302, 37.778635523030793 ], [ -122.386900883746804, 37.778631166560793 ], [ -122.386876911637231, 37.778641164370391 ], [ -122.38679893557611, 37.778636918569845 ], [ -122.386774963796881, 37.778646916353182 ], [ -122.386707365642749, 37.778642504401184 ], [ -122.386683376072511, 37.77865181600145 ], [ -122.386614065647606, 37.778648118121843 ], [ -122.386590076741811, 37.778657429142889 ], [ -122.386510387715276, 37.778653897287107 ], [ -122.386486398801594, 37.778663208561632 ], [ -122.386417070940411, 37.778658824121642 ], [ -122.386393098756784, 37.778668821832838 ], [ -122.38631686949239, 37.77866523448656 ], [ -122.386292880557747, 37.778674545996267 ], [ -122.38622009293212, 37.778670216797138 ], [ -122.386197851113138, 37.778680186508431 ], [ -122.386125063833461, 37.778675857245105 ], [ -122.38610282131512, 37.778685827224102 ], [ -122.386028304671399, 37.778681525568288 ], [ -122.38600433277405, 37.778691523194873 ], [ -122.385931545494543, 37.77868719381209 ], [ -122.385909285516078, 37.778696477309893 ], [ -122.385834786309914, 37.778692862251056 ], [ -122.385810796946046, 37.778702173118987 ], [ -122.385741486517588, 37.778698474726497 ], [ -122.385717497147184, 37.778707785850045 ], [ -122.385642980157812, 37.778703483956278 ], [ -122.385619007862559, 37.778713481784578 ], [ -122.385549680005639, 37.778709096834618 ], [ -122.385525708373962, 37.778719094083733 ], [ -122.385451191384348, 37.778714792068747 ], [ -122.385428949109837, 37.778724761915029 ], [ -122.385354432120124, 37.778720459838901 ], [ -122.385330442694098, 37.778729770883643 ], [ -122.385257672841163, 37.778726127529652 ], [ -122.385235413119702, 37.778735411169237 ], [ -122.385147302363819, 37.778740940194126 ], [ -122.385154355852308, 37.778814307082541 ], [ -122.385130156934849, 37.778815380480168 ], [ -122.385113449499144, 37.778634352661491 ], [ -122.385137630588289, 37.778632592828004 ], [ -122.385155236200447, 37.778712657669637 ], [ -122.385222625586081, 37.778708833443275 ], [ -122.385225758626859, 37.778627749705855 ], [ -122.38525718494833, 37.778706907076334 ], [ -122.38531938483824, 37.778703165506691 ], [ -122.385322517780125, 37.778622082041188 ], [ -122.385352214498553, 37.778701267322347 ], [ -122.385416144082598, 37.778697497765265 ], [ -122.385419276918626, 37.778616414297126 ], [ -122.385450703441506, 37.77869557161619 ], [ -122.38551290331219, 37.778691829944421 ], [ -122.385517765757882, 37.778610719084014 ], [ -122.38554746266594, 37.778689903767003 ], [ -122.385606220534413, 37.778686903820002 ], [ -122.385611082870923, 37.77860579268107 ], [ -122.385640779890451, 37.778684977889775 ], [ -122.385704709445832, 37.778681208176224 ], [ -122.385707841966195, 37.778600124700183 ], [ -122.385739268789678, 37.778679281942566 ], [ -122.385798008866331, 37.778675595460832 ], [ -122.385801141637501, 37.77859451225126 ], [ -122.385832568205146, 37.778673669199819 ], [ -122.385896498100962, 37.77866989992588 ], [ -122.385899630058546, 37.778588816175542 ], [ -122.385931057427598, 37.778667973361443 ], [ -122.385991527195401, 37.778664259112972 ], [ -122.385994659747908, 37.778583175623524 ], [ -122.386026086530904, 37.778662333069811 ], [ -122.386090016393183, 37.778658563141803 ], [ -122.386093148146045, 37.778577479660726 ], [ -122.386124575716508, 37.778656636795191 ], [ -122.386185062898718, 37.778653608892128 ], [ -122.386202015472293, 37.778571617834082 ], [ -122.386209244307707, 37.778651848558759 ], [ -122.386280092656236, 37.778647968384718 ], [ -122.386284954247373, 37.778566856942867 ], [ -122.386314651962735, 37.778646041707809 ], [ -122.386380311166334, 37.778642244226567 ], [ -122.386385172654656, 37.778561133055092 ], [ -122.386414870481516, 37.778640318069463 ], [ -122.386473610832795, 37.778636630970986 ], [ -122.386476742511945, 37.778555547473879 ], [ -122.386508170135954, 37.778634704511973 ], [ -122.386577288736902, 37.778630851563484 ], [ -122.386580420309613, 37.778549768338095 ], [ -122.386611848034704, 37.778628925074045 ], [ -122.386670588381151, 37.778625238426613 ], [ -122.386673719491867, 37.778544154655052 ], [ -122.386705147666916, 37.778623311635243 ], [ -122.386762158289017, 37.778619652349228 ], [ -122.386765307091096, 37.778539255288756 ], [ -122.386796717583763, 37.778617726080171 ], [ -122.386864106438878, 37.778613900371958 ], [ -122.386867237344845, 37.778532816869664 ], [ -122.386898665721333, 37.778611973798455 ], [ -122.386959152842579, 37.77860894577281 ], [ -122.386962284329485, 37.778527861982276 ], [ -122.38699369467254, 37.778606332452348 ], [ -122.387057641885832, 37.778603248718746 ], [ -122.387060772580114, 37.778522165211164 ], [ -122.387092183716675, 37.778600635643997 ], [ -122.38715094145212, 37.778597634927166 ], [ -122.387154334023649, 37.778526848079068 ], [ -122.387183771011991, 37.778595735958248 ], [ -122.38724942978078, 37.778591937998534 ], [ -122.387254551963991, 37.778521123457843 ], [ -122.387283989043027, 37.778590011312069 ], [ -122.387351378207768, 37.778586185868939 ], [ -122.387340432224974, 37.778359741993853 ], [ -122.385336224880277, 37.778480397809368 ], [ -122.385292215885698, 37.77845088576418 ], [ -122.385332752982109, 37.778411780987071 ], [ -122.385371486072913, 37.77843794377425 ], [ -122.387505436887736, 37.778315896876499 ], [ -122.387507690304943, 37.778336462786434 ], [ -122.387691125877438, 37.77833695908997 ], [ -122.387623881872372, 37.778210305157842 ], [ -122.390410335866818, 37.777142437885963 ], [ -122.390374260520773, 37.777113759158638 ], [ -122.390348522259259, 37.777093298197975 ], [ -122.389857561580598, 37.776499598895249 ], [ -122.389810930692391, 37.776503093591124 ], [ -122.389821814335647, 37.776590819837281 ], [ -122.38767762612953, 37.776716506820485 ], [ -122.387616143310936, 37.776748393866363 ], [ -122.387584974466847, 37.776747519757691 ], [ -122.387567625478937, 37.776745737093393 ], [ -122.387552006163176, 37.776743927007061 ], [ -122.387536369402838, 37.776741430474004 ], [ -122.387505026105131, 37.776733691622944 ], [ -122.387489337013818, 37.776729135749626 ], [ -122.387475360143554, 37.776723865736592 ], [ -122.387461365830632, 37.776717909277224 ], [ -122.387447354082425, 37.776711266646082 ], [ -122.387419295684069, 37.77669660766567 ], [ -122.387406978711525, 37.776688564173618 ], [ -122.387394643958672, 37.776679834515676 ], [ -122.387373399569938, 37.776660946088832 ], [ -122.387362760285328, 37.776650815560998 ], [ -122.38735385031967, 37.776640657346583 ], [ -122.387344922566982, 37.776629812692306 ], [ -122.387335978065309, 37.776618281581548 ], [ -122.387328762881694, 37.776606722784834 ], [ -122.387323277015341, 37.77659513630249 ], [ -122.387316044392662, 37.776582891060237 ], [ -122.387312271446064, 37.776570590705511 ], [ -122.387306768480713, 37.776558317497482 ], [ -122.387304707400645, 37.776545302738185 ], [ -122.387302663771393, 37.776532974698164 ], [ -122.387298541607819, 37.776506944904682 ], [ -122.387299939508821, 37.776493875044565 ], [ -122.387299608083069, 37.776480832320061 ], [ -122.387311076202096, 37.776319268586136 ], [ -122.387103587414643, 37.776325337046984 ], [ -122.387082386717438, 37.776103864569507 ], [ -122.385027425864848, 37.776335891081246 ], [ -122.385011788936296, 37.776333394493314 ], [ -122.384999507467228, 37.776326723359297 ], [ -122.384988868215274, 37.776316592896777 ], [ -122.385025875302759, 37.776274797468226 ], [ -122.384827030762366, 37.775394163410851 ], [ -122.384880527469605, 37.775388501004997 ], [ -122.384877317104468, 37.775330181090084 ], [ -122.386995863440688, 37.775082030997162 ], [ -122.386923061819843, 37.774327799469994 ], [ -122.381813822024597, 37.774615486663201 ], [ -122.381456516831207, 37.771917559332238 ], [ -122.384671101595274, 37.773340605158566 ], [ -122.384690336061794, 37.773348538332996 ], [ -122.384706059111224, 37.773354467747083 ], [ -122.38472349432422, 37.773359682786399 ], [ -122.384740912113955, 37.773364211103086 ], [ -122.384758312493929, 37.773368053246259 ], [ -122.384775695456668, 37.773371208941398 ], [ -122.384793061001673, 37.77337367818847 ], [ -122.384812120957065, 37.773374747168276 ], [ -122.384829452003999, 37.773375843238945 ], [ -122.384848494879705, 37.773376225487283 ], [ -122.38486577332894, 37.77337526249628 ], [ -122.386759568868584, 37.773272861257844 ], [ -122.386575835242937, 37.772851405934894 ], [ -122.38657206225551, 37.772839105279289 ], [ -122.386575190127743, 37.772826007459408 ], [ -122.386581812023351, 37.772814227444925 ], [ -122.386593692364443, 37.772805109622055 ], [ -122.386717419519911, 37.772771540111776 ], [ -122.386708370613761, 37.772755890279406 ], [ -122.386704597609267, 37.772743589627844 ], [ -122.386699094688865, 37.772731316663254 ], [ -122.386691548686684, 37.772706715359256 ], [ -122.38668746217553, 37.772682058686371 ], [ -122.386685401318488, 37.772669044181889 ], [ -122.386683357538772, 37.772656715579039 ], [ -122.386682694959006, 37.772630630932561 ], [ -122.386684111009814, 37.772618246955815 ], [ -122.386683779716421, 37.772605204495207 ], [ -122.386685195434609, 37.772592821073083 ], [ -122.386688340360166, 37.772580409425913 ], [ -122.386661929793348, 37.772494305123679 ], [ -122.386659886368264, 37.772481976514491 ], [ -122.386661302081109, 37.772469592817821 ], [ -122.38666965348915, 37.772457784842032 ], [ -122.386681551212874, 37.772449354004202 ], [ -122.386714186256512, 37.772439903990914 ], [ -122.386679336788333, 37.772362175448528 ], [ -122.386644902453284, 37.772368906802633 ], [ -122.386631976023693, 37.772336837636807 ], [ -122.386620901217455, 37.772309545906367 ], [ -122.386592865526183, 37.772227587668034 ], [ -122.386585249868389, 37.772200240573625 ], [ -122.386577616780784, 37.772172207033208 ], [ -122.386570001134302, 37.772144859937534 ], [ -122.386558193761417, 37.772088737774723 ], [ -122.386541497954923, 37.771976381892166 ], [ -122.386539053911875, 37.771948265304715 ], [ -122.386438565499603, 37.771943006477983 ], [ -122.386459543589496, 37.771678968587629 ], [ -122.386414488196351, 37.771676255706971 ], [ -122.386416883827636, 37.771634327179655 ], [ -122.386370064064607, 37.77163026961771 ], [ -122.386370799901215, 37.771591114275026 ], [ -122.386346586274058, 37.771591501747267 ], [ -122.386349295737674, 37.771561929239198 ], [ -122.386373509355195, 37.77156154176641 ], [ -122.386383018231356, 37.77145906765827 ], [ -122.386277185313745, 37.771447713434846 ], [ -122.386286481744634, 37.771404987761201 ], [ -122.386248187810011, 37.771395986654248 ], [ -122.386213474980906, 37.771391735029944 ], [ -122.386175916991888, 37.77134357828146 ], [ -122.386158586399219, 37.771342482408336 ], [ -122.386142916299207, 37.771338612516935 ], [ -122.386128940538697, 37.771333342339254 ], [ -122.386116624249169, 37.771325298710295 ], [ -122.38610598590013, 37.771315168059481 ], [ -122.386097059315517, 37.771304323294956 ], [ -122.386091556948031, 37.771292050293354 ], [ -122.386142815223778, 37.771266508342599 ], [ -122.38608534348306, 37.771183647247369 ], [ -122.386039082134474, 37.771201555805888 ], [ -122.386023428808656, 37.771198372355109 ], [ -122.386011095814837, 37.771189642258307 ], [ -122.386041843996964, 37.771174042639558 ], [ -122.38589549059256, 37.770997835524099 ], [ -122.385920430782591, 37.770821635056869 ], [ -122.385847920885212, 37.770691630725352 ], [ -122.385837299399952, 37.770682186505887 ], [ -122.385823306356514, 37.770676229845748 ], [ -122.385795755972097, 37.770681477661917 ], [ -122.3857856231634, 37.770691253905198 ], [ -122.385782477468226, 37.770703665268918 ], [ -122.385798079922523, 37.771250048587049 ], [ -122.385622255652152, 37.772162083932052 ], [ -122.385554576344688, 37.772154239361129 ], [ -122.385681891861481, 37.771444190077446 ], [ -122.385515420224039, 37.771429684819722 ], [ -122.38546780756441, 37.771530707856108 ], [ -122.385335917119292, 37.771651620630855 ], [ -122.385287036807085, 37.771634547559174 ], [ -122.385642517132254, 37.770847369439849 ], [ -122.385683664105002, 37.770764304300435 ], [ -122.385720236601344, 37.770637361956886 ], [ -122.385724274638122, 37.77059197382043 ], [ -122.385559320924514, 37.770637189122013 ], [ -122.383154992189901, 37.769708715839762 ], [ -122.383375582677616, 37.76933298733065 ], [ -122.385570199525418, 37.770179656618097 ], [ -122.385492733315886, 37.769990673321502 ], [ -122.38547497668678, 37.769836444052352 ], [ -122.38501929687861, 37.768856908704215 ], [ -122.384893636964136, 37.768882266758567 ], [ -122.384787561607197, 37.76865665734794 ], [ -122.38500549801104, 37.768585873156887 ], [ -122.385007528170945, 37.768529529274218 ], [ -122.385037840293677, 37.768496768478933 ], [ -122.385006697245259, 37.768428594043755 ], [ -122.38508052413836, 37.768406124957501 ], [ -122.385042550658142, 37.768341493386622 ], [ -122.38510771303342, 37.768318476117585 ], [ -122.385167686616882, 37.76829554179551 ], [ -122.385149073018809, 37.768175662662372 ], [ -122.385147029970554, 37.768163334292169 ], [ -122.385144812710706, 37.768144141461285 ], [ -122.38470084491486, 37.768171155877575 ], [ -122.384624005099198, 37.768074869544797 ], [ -122.383881223535184, 37.768121766230543 ], [ -122.383842264982363, 37.767745376829602 ], [ -122.382456893000523, 37.767832748125578 ], [ -122.382439473722869, 37.767691561099248 ], [ -122.384901003015941, 37.7675361686969 ], [ -122.384848518174209, 37.767018283287165 ], [ -122.384847918040649, 37.767012360130963 ], [ -122.382854528282635, 37.767138299649837 ], [ -122.382800567665527, 37.766784124750316 ], [ -122.385859942984411, 37.766599245791156 ], [ -122.385894343938588, 37.766523155929399 ], [ -122.385924379700455, 37.76641142564803 ], [ -122.385933483994975, 37.766361149056699 ], [ -122.385914237836445, 37.766284543788501 ], [ -122.385992644580284, 37.766169979928648 ], [ -122.386291877422522, 37.765689977677845 ], [ -122.386372587962043, 37.765598038596792 ], [ -122.386520103480208, 37.765411635088689 ], [ -122.386541125110512, 37.765285627921941 ], [ -122.386469993174757, 37.765277839225583 ], [ -122.386436744413672, 37.76505861928149 ], [ -122.386447302231559, 37.764929345689509 ], [ -122.386483096429387, 37.76490817106005 ], [ -122.386501478211272, 37.764814482226171 ], [ -122.386491269379732, 37.764616868735338 ], [ -122.38644793023208, 37.764613442288407 ], [ -122.386451155646682, 37.764399819097633 ], [ -122.386482284438713, 37.7643993208899 ], [ -122.386481482527714, 37.764367744084808 ], [ -122.386603169586195, 37.764322532741204 ], [ -122.386601548243974, 37.76425869323451 ], [ -122.386585931922852, 37.764256883010674 ], [ -122.386571940029569, 37.764250926432666 ], [ -122.38656131971895, 37.764241482262236 ], [ -122.38655581779787, 37.764229209267128 ], [ -122.38655550400469, 37.764216853507875 ], [ -122.386720358990686, 37.764100218114386 ], [ -122.386739211018067, 37.764025063281011 ], [ -122.386733691302794, 37.764012103852934 ], [ -122.38672476584992, 37.764001259116526 ], [ -122.386714128120815, 37.763991128513133 ], [ -122.38670010140504, 37.763983799331974 ], [ -122.386686126988522, 37.763978529213567 ], [ -122.386670492591179, 37.763976032291019 ], [ -122.386649740182264, 37.763976364463595 ], [ -122.386634036749314, 37.763971122014347 ], [ -122.386621721632579, 37.763963078702353 ], [ -122.386611066835059, 37.763952261637705 ], [ -122.386603852994298, 37.763940702770881 ], [ -122.386600062668251, 37.763927715381541 ], [ -122.386601461140231, 37.763914645215138 ], [ -122.386604606072254, 37.763902234094914 ], [ -122.386738907102767, 37.763808749639459 ], [ -122.386828889531799, 37.763741383598706 ], [ -122.386840716091143, 37.763730206671987 ], [ -122.386855983933643, 37.763718287929905 ], [ -122.386869539846316, 37.763707083316298 ], [ -122.386884824769794, 37.763695851022327 ], [ -122.386900145253236, 37.763685991607943 ], [ -122.38691721218359, 37.763676790958648 ], [ -122.386934296546428, 37.763668276753165 ], [ -122.386951398349183, 37.763660449266119 ], [ -122.386970246931185, 37.763653279988162 ], [ -122.386989112953756, 37.763646797428088 ], [ -122.387007996064526, 37.763641001316863 ], [ -122.387026897301666, 37.763635891637833 ], [ -122.387047562432002, 37.763632127441824 ], [ -122.387066515625051, 37.76362907682639 ], [ -122.387271786163467, 37.763605188503739 ], [ -122.387241692331358, 37.763510215504958 ], [ -122.386905339639924, 37.763549936808673 ], [ -122.386894896912324, 37.763479371256736 ], [ -122.386775626938885, 37.763415354890846 ], [ -122.386725141086345, 37.763471101073094 ], [ -122.386629276523749, 37.763443106355105 ], [ -122.386635775530237, 37.763426520918784 ], [ -122.386503316054672, 37.763388124513462 ], [ -122.386596764100844, 37.763184731253212 ], [ -122.386314395824243, 37.763102036396099 ], [ -122.386214308919619, 37.763316522997265 ], [ -122.385510514730285, 37.763329843761532 ], [ -122.385502904472929, 37.763234510683318 ], [ -122.385278141497096, 37.763240166132988 ], [ -122.385288368966343, 37.763370480347639 ], [ -122.38518118352205, 37.763373568175126 ], [ -122.385104258477085, 37.762319301701545 ], [ -122.384682038971576, 37.762111108737756 ], [ -122.384291646501694, 37.762134518628663 ], [ -122.384461265879253, 37.764047083641032 ], [ -122.384304084745608, 37.764057150214732 ], [ -122.384132119672074, 37.762256559028991 ], [ -122.383427389000644, 37.762300785113965 ], [ -122.383450606038608, 37.762465914870035 ], [ -122.383630300057192, 37.762456862777178 ], [ -122.3836690703355, 37.762553071423532 ], [ -122.383744969614881, 37.762544304446941 ], [ -122.383904907669475, 37.764143878058199 ], [ -122.383832343655854, 37.764147784454543 ], [ -122.383810360659879, 37.764236036587789 ], [ -122.383580701961776, 37.764253441592125 ], [ -122.383538646212543, 37.764164152408867 ], [ -122.383462623420698, 37.764168114122675 ], [ -122.383459954192517, 37.764131073630139 ], [ -122.383299279201026, 37.764139821513076 ], [ -122.383109557752064, 37.762320284660944 ], [ -122.382290828789408, 37.762371818989514 ], [ -122.38251353675102, 37.764606298459128 ], [ -122.382344144468703, 37.764612437347246 ], [ -122.382121545934865, 37.762382076243206 ], [ -122.38174844490716, 37.762405201559091 ], [ -122.381695087947634, 37.764738170521944 ], [ -122.381631135065035, 37.764740565182315 ], [ -122.381630936164214, 37.764801000118787 ], [ -122.381492672008349, 37.764806640579245 ], [ -122.381450941763589, 37.76493503772344 ], [ -122.381172352882004, 37.76493330344676 ], [ -122.381123981911756, 37.764799477425129 ], [ -122.380983831900423, 37.764798967139676 ], [ -122.381029346103958, 37.762223710202747 ], [ -122.38330764917707, 37.762081573209855 ], [ -122.382185556454559, 37.760879873475361 ], [ -122.382168402620636, 37.760885641207267 ], [ -122.382152908506868, 37.760888635257665 ], [ -122.382137362223574, 37.760889570238476 ], [ -122.382121780810863, 37.760889132328003 ], [ -122.38210441862546, 37.760886662951151 ], [ -122.382090445117498, 37.760881392285128 ], [ -122.382076454213362, 37.760875434895482 ], [ -122.382064140442054, 37.760867390822959 ], [ -122.382053521188098, 37.760857946240883 ], [ -122.382044596105573, 37.76084710115525 ], [ -122.382023253267107, 37.760824093302155 ], [ -122.382014294110562, 37.760811875582888 ], [ -122.382005317206719, 37.760798970871889 ], [ -122.381998069607818, 37.76078603882209 ], [ -122.381986997879679, 37.76075874660679 ], [ -122.381983174441103, 37.760744386430567 ], [ -122.381981079958692, 37.760729998921875 ], [ -122.381980366958516, 37.760701854577775 ], [ -122.381981731049137, 37.76068741129496 ], [ -122.381984824092612, 37.760672940679711 ], [ -122.38198793521687, 37.760659156500736 ], [ -122.381992775285752, 37.760645344714604 ], [ -122.381695267558513, 37.760647347915544 ], [ -122.381838070964363, 37.762118779804872 ], [ -122.381335375976406, 37.762148093200558 ], [ -122.381148296266858, 37.760157516712624 ], [ -122.380611046370689, 37.760188065470565 ], [ -122.380912187127535, 37.763336013717719 ], [ -122.380701275367471, 37.763342126118935 ], [ -122.380400282533174, 37.760199669192616 ], [ -122.379507164112297, 37.760250310896886 ], [ -122.379808005517475, 37.763456637815253 ], [ -122.379779318273378, 37.763485250755494 ], [ -122.379565016753276, 37.763494162073435 ], [ -122.379533176547326, 37.763466514378344 ], [ -122.379201704975202, 37.759937914686475 ], [ -122.381256986630419, 37.759667520373661 ], [ -122.381256577752055, 37.759583059710486 ], [ -122.381469121143184, 37.759573486743299 ], [ -122.381446760180623, 37.75930533388199 ], [ -122.381389798546991, 37.759310363785765 ], [ -122.381303248927566, 37.759308311423226 ], [ -122.381202252005025, 37.758325846274602 ], [ -122.381200105587652, 37.758309399127988 ], [ -122.381197941449869, 37.758292265813587 ], [ -122.381197089710227, 37.758258629870305 ], [ -122.381199679398975, 37.758224251997007 ], [ -122.381200991455458, 37.758207749648136 ], [ -122.38120401536365, 37.75819053325035 ], [ -122.381207056652954, 37.758174003300283 ], [ -122.381221367866601, 37.758124330651064 ], [ -122.381234402126012, 37.75809253323947 ], [ -122.381242648836448, 37.758076607200529 ], [ -122.381262669528837, 37.75804744489573 ], [ -122.381289433303067, 37.758011307974613 ], [ -122.381302797797105, 37.757992553063836 ], [ -122.381314432708805, 37.757973825759706 ], [ -122.381327797196747, 37.757955071120548 ], [ -122.381337703552219, 37.757936371131194 ], [ -122.381360938912749, 37.75789754361324 ], [ -122.381380716140868, 37.757858771292213 ], [ -122.381390587715856, 37.757838698951588 ], [ -122.381398729699725, 37.757818653670306 ], [ -122.381408600911726, 37.757798581058942 ], [ -122.381416742893265, 37.757778536050878 ], [ -122.381423155645805, 37.757758518646433 ], [ -122.381431297626008, 37.7577384739118 ], [ -122.381444088336707, 37.757697065930351 ], [ -122.381450501074838, 37.757677048524144 ], [ -122.381464499104396, 37.757615019779642 ], [ -122.381469148185403, 37.757593657075738 ], [ -122.381475021977749, 37.757552359783979 ], [ -122.381477941486636, 37.757531024690024 ], [ -122.381480843608969, 37.757509003148037 ], [ -122.381427480867771, 37.75751946944429 ], [ -122.381405721286413, 37.757343328372727 ], [ -122.381468641764485, 37.757300433411309 ], [ -122.381446638421224, 37.75711468207534 ], [ -122.381323864533158, 37.757116641937891 ], [ -122.381323169199888, 37.757089184020202 ], [ -122.381364426846005, 37.75707891127486 ], [ -122.381321992818584, 37.756564544822638 ], [ -122.381321662531036, 37.756551502035926 ], [ -122.38132307883005, 37.756539118369027 ], [ -122.381326206602509, 37.756526020931318 ], [ -122.381331099013835, 37.756514268226432 ], [ -122.381339449129754, 37.756502460600949 ], [ -122.381347834701543, 37.756492025859821 ], [ -122.381357966510507, 37.756482250242897 ], [ -122.381369844881263, 37.756473132920476 ], [ -122.381383504601786, 37.756466047611781 ], [ -122.381397181710526, 37.756459649024094 ], [ -122.381399266420004, 37.75640536415446 ], [ -122.381383669266881, 37.756404239961626 ], [ -122.381368019603769, 37.756401055879024 ], [ -122.381354081758502, 37.756397158017677 ], [ -122.381338379955181, 37.756391914861666 ], [ -122.381324389963666, 37.756385957652711 ], [ -122.38129976407464, 37.756369869336808 ], [ -122.381289145560629, 37.756360424678547 ], [ -122.381280238512659, 37.756350265974667 ], [ -122.381265813968113, 37.756327147557755 ], [ -122.381260331236177, 37.756315560741236 ], [ -122.381258271437829, 37.756302546110341 ], [ -122.381256229700497, 37.756290217367201 ], [ -122.381255916815178, 37.756277861302344 ], [ -122.381240855709805, 37.755888041607285 ], [ -122.381237067430561, 37.75587505429101 ], [ -122.381229855216972, 37.755863495353786 ], [ -122.381219219049328, 37.755853363971241 ], [ -122.381205211793798, 37.755846720574091 ], [ -122.381189545219783, 37.75584285001387 ], [ -122.381023178934683, 37.755831084166076 ], [ -122.381005052890941, 37.755798410637965 ], [ -122.380939344081298, 37.755799459348452 ], [ -122.380938179657051, 37.755753467322073 ], [ -122.381031311951133, 37.755742366765169 ], [ -122.381010248974547, 37.755730341878809 ], [ -122.38098743910173, 37.755717658143908 ], [ -122.380924145958886, 37.755677464760758 ], [ -122.380881915817497, 37.755649296531168 ], [ -122.380862530269653, 37.755635184397235 ], [ -122.380841397836633, 37.755620413687737 ], [ -122.380821994593731, 37.755605615653565 ], [ -122.380802573973469, 37.755590130893374 ], [ -122.38078488252836, 37.755574618260482 ], [ -122.380758354080839, 37.755551693216646 ], [ -122.380751124581494, 37.755539447252119 ], [ -122.380745589509232, 37.755525801070959 ], [ -122.38074007147074, 37.755512841343261 ], [ -122.380736283298162, 37.75549985401004 ], [ -122.38073074823852, 37.755486208102681 ], [ -122.380728671510397, 37.755472506457778 ], [ -122.380724865279745, 37.755458832961516 ], [ -122.380718635462969, 37.755417728569704 ], [ -122.380718270538722, 37.755403313156876 ], [ -122.380719652161702, 37.755389556598288 ], [ -122.380719304614729, 37.755375827633614 ], [ -122.380722067512963, 37.755348314521783 ], [ -122.380728289091294, 37.755320746216135 ], [ -122.380731417602021, 37.755307648505891 ], [ -122.380736257562816, 37.755293837033165 ], [ -122.38074597221329, 37.755267586159846 ], [ -122.380759145196464, 37.755241280371166 ], [ -122.380767425749198, 37.755226726990642 ], [ -122.380777557807846, 37.755216951415953 ], [ -122.380789436047536, 37.755207834150042 ], [ -122.3808030608416, 37.755199376285184 ], [ -122.380814973485883, 37.755191631918663 ], [ -122.380828633023739, 37.75518454667273 ], [ -122.380844056826504, 37.755178806720394 ], [ -122.380857751116352, 37.755173094367549 ], [ -122.380873209326097, 37.755168727313375 ], [ -122.380888684919853, 37.755165046980004 ], [ -122.380905907041537, 37.755162024947012 ], [ -122.380921434762783, 37.75516040367944 ], [ -122.380952524623297, 37.755158534314724 ], [ -122.380976698502963, 37.755156774772679 ], [ -122.380999125498533, 37.755154356655588 ], [ -122.381021534766717, 37.755151252091551 ], [ -122.381043927344422, 37.755147461063977 ], [ -122.381066302193489, 37.755142983589487 ], [ -122.38108865965917, 37.755137819662529 ], [ -122.38111099939502, 37.755131969288627 ], [ -122.38113332243789, 37.755125432451251 ], [ -122.381153915967687, 37.755118923215505 ], [ -122.381176203900949, 37.755111013753655 ], [ -122.38119676265265, 37.755103131339425 ], [ -122.381225828164943, 37.755089619604064 ], [ -122.381246943141264, 37.755103703797808 ], [ -122.381242963358787, 37.755083165829973 ], [ -122.38126015068255, 37.755078770848776 ], [ -122.381277355393678, 37.755075062587856 ], [ -122.381292865706087, 37.755072754823154 ], [ -122.38131010484075, 37.755070419733926 ], [ -122.381327362035009, 37.75506877053013 ], [ -122.381361945276268, 37.755068218467898 ], [ -122.381396598051168, 37.755070412188203 ], [ -122.381413958860932, 37.755072881946376 ], [ -122.381448715950555, 37.755079194614936 ], [ -122.381480084590436, 37.755088307721437 ], [ -122.381497532329476, 37.755094209708098 ], [ -122.381511539483228, 37.755100853620512 ], [ -122.381527276134307, 37.755107469370813 ], [ -122.381548321633034, 37.755118807717949 ], [ -122.381562363566104, 37.755126824520609 ], [ -122.381579829044853, 37.755133412663042 ], [ -122.381595565024199, 37.755140028689837 ], [ -122.381612996090695, 37.755145244200165 ], [ -122.381630391696547, 37.755149086822691 ], [ -122.381647770609987, 37.755152242983492 ], [ -122.381665131447221, 37.755154712704652 ], [ -122.381684187368847, 37.755155781907895 ], [ -122.38170149639484, 37.755156192273823 ], [ -122.382833235564902, 37.755035795269471 ], [ -122.383668861276263, 37.754971624407645 ], [ -122.383685978819074, 37.754964483555838 ], [ -122.38372028351472, 37.754952947636127 ], [ -122.383737470668777, 37.75494855256818 ], [ -122.383756404038564, 37.75494481631263 ], [ -122.383773626345388, 37.754941794129564 ], [ -122.383792594868737, 37.754939430758334 ], [ -122.383811580457817, 37.754937754111957 ], [ -122.38384268824322, 37.754936570147059 ], [ -122.383861726388119, 37.754936952556008 ], [ -122.383880781586896, 37.754938021140639 ], [ -122.383896396575224, 37.754939831714687 ], [ -122.384073136269635, 37.754951427650568 ], [ -122.384099862744918, 37.754913917176538 ], [ -122.384076095308828, 37.754658835300546 ], [ -122.383998950777894, 37.754618177983438 ], [ -122.383899356302948, 37.754647239093948 ], [ -122.383797997440993, 37.754674954869053 ], [ -122.383696604385719, 37.754701297650087 ], [ -122.383569360630759, 37.75473148712782 ], [ -122.383548680403266, 37.754734564546567 ], [ -122.3835279827709, 37.754736955513799 ], [ -122.383505538587656, 37.754738687938882 ], [ -122.38348480614728, 37.754739706002738 ], [ -122.383441577306456, 37.754740396857535 ], [ -122.383400008003704, 37.754738313997002 ], [ -122.38337920595491, 37.754736586250608 ], [ -122.383356657006146, 37.754734199690795 ], [ -122.383335820163737, 37.754731099315492 ], [ -122.383314965907985, 37.754727311939398 ], [ -122.383295841160447, 37.754723497196949 ], [ -122.383254063070865, 37.754713177184357 ], [ -122.383234885782983, 37.75470730309371 ], [ -122.383213961945074, 37.754700770182538 ], [ -122.383194767270453, 37.754694209911989 ], [ -122.383175537439726, 37.754686276198775 ], [ -122.383158037470992, 37.754678315390812 ], [ -122.383138807994129, 37.754670381666052 ], [ -122.383103737780345, 37.754651713981168 ], [ -122.383086185277548, 37.75464169354953 ], [ -122.383068615380594, 37.754630986667287 ], [ -122.383052774639708, 37.754620252154247 ], [ -122.383036916504807, 37.754608831191064 ], [ -122.383015731729387, 37.754592001250259 ], [ -122.38299259130558, 37.754566275663684 ], [ -122.382971180378888, 37.754540521890775 ], [ -122.382949752076243, 37.754514081940449 ], [ -122.382930053282664, 37.754487614354218 ], [ -122.382908625009904, 37.754461174396191 ], [ -122.382888908501968, 37.754434020360335 ], [ -122.382870920808742, 37.754406838701115 ], [ -122.382842850708954, 37.754363390808408 ], [ -122.382816907001839, 37.754323234339928 ], [ -122.382797016608123, 37.754289216082142 ], [ -122.382791480900465, 37.754275569725095 ], [ -122.382785928504248, 37.754261237457797 ], [ -122.38278210489284, 37.754246877022375 ], [ -122.382781722177867, 37.754231775164968 ], [ -122.382784449823419, 37.754202889097733 ], [ -122.382789271581657, 37.754188390821014 ], [ -122.382795840220822, 37.754174551362205 ], [ -122.382804155740061, 37.754161370721022 ], [ -122.382814217447034, 37.754148848908173 ], [ -122.382826043083483, 37.754137672365715 ], [ -122.382839615252593, 37.75412715464509 ], [ -122.382853204821586, 37.754117323645531 ], [ -122.382868575702929, 37.754109523814002 ], [ -122.382885693124038, 37.754102383077345 ], [ -122.382904591526582, 37.754097274061955 ], [ -122.382896892098685, 37.754066494402807 ], [ -122.382890922160428, 37.754035687111681 ], [ -122.382884969278336, 37.754005566273698 ], [ -122.382876487005475, 37.753943896449059 ], [ -122.382873975008934, 37.75391303391072 ], [ -122.382869733880582, 37.753882198997694 ], [ -122.382868951022118, 37.753851308833418 ], [ -122.382866421635015, 37.753819759846344 ], [ -122.382865638778583, 37.753788869681728 ], [ -122.38286753098933, 37.753727034381221 ], [ -122.382869667430043, 37.75367480879347 ], [ -122.382867485984363, 37.753656988767865 ], [ -122.382865286797127, 37.753638482299571 ], [ -122.382861376571569, 37.753620689893637 ], [ -122.382850096841622, 37.753585160622798 ], [ -122.382842745420064, 37.753568109920103 ], [ -122.382833665214591, 37.753551086561764 ], [ -122.382822855548582, 37.753534091107603 ], [ -122.382812063283737, 37.753517782100509 ], [ -122.382801288419827, 37.753502159540567 ], [ -122.382788801838672, 37.753487251326646 ], [ -122.382774586124242, 37.75347237018628 ], [ -122.382742765715449, 37.753445409495008 ], [ -122.382726873440859, 37.753432615860532 ], [ -122.382709269084074, 37.753420535751609 ], [ -122.382691682135089, 37.753409142362763 ], [ -122.382672383117907, 37.753398463047603 ], [ -122.382653119587403, 37.753389156614546 ], [ -122.382611168393765, 37.753371971889877 ], [ -122.382590227592559, 37.7533647524183 ], [ -122.382569321590282, 37.753358906113988 ], [ -122.382546703510585, 37.753353773605845 ], [ -122.382524103512239, 37.753349327255975 ], [ -122.382501520571083, 37.75334556763007 ], [ -122.382478972425403, 37.753343181170841 ], [ -122.382456441314361, 37.753341480611873 ], [ -122.382433945688362, 37.753341153208574 ], [ -122.382402821501856, 37.753341650348268 ], [ -122.38237864858533, 37.753343409903252 ], [ -122.382356222188207, 37.753345828558139 ], [ -122.382297501266095, 37.753349513077993 ], [ -122.38226810654271, 37.753349982559506 ], [ -122.382240423203967, 37.753349737974666 ], [ -122.382210993352999, 37.753348834550927 ], [ -122.382181546112378, 37.753347244671602 ], [ -122.382152081137065, 37.753344968342162 ], [ -122.382124328241176, 37.753341977937026 ], [ -122.382094811792641, 37.753337642237426 ], [ -122.38206700673382, 37.753332592473967 ], [ -122.382039184289724, 37.753326856255676 ], [ -122.38201134446112, 37.753320433582523 ], [ -122.38200001736864, 37.75331754288009 ], [ -122.381983487255724, 37.753313324729064 ], [ -122.381957341428489, 37.753305501265011 ], [ -122.3819294494578, 37.75329701950217 ], [ -122.38187705455077, 37.753277254112369 ], [ -122.381850839550978, 37.753266685100435 ], [ -122.3818017982653, 37.753242746046119 ], [ -122.381777242861062, 37.753229403614625 ], [ -122.381754416238635, 37.753216033574361 ], [ -122.381731572936843, 37.753201977344837 ], [ -122.381708677132991, 37.753185861496782 ], [ -122.381985040410456, 37.753169774197133 ], [ -122.382560765120331, 37.753136419940226 ], [ -122.382560989570067, 37.75313671714089 ], [ -122.382951309683904, 37.753114103639163 ], [ -122.383438933023925, 37.753085850281579 ], [ -122.383914427464433, 37.753058600498669 ], [ -122.384879974685347, 37.753002645165687 ], [ -122.385845520438053, 37.752946681373402 ], [ -122.385845731512404, 37.752946669205699 ], [ -122.386811064397406, 37.752890709951139 ], [ -122.386811676199414, 37.752890674609837 ], [ -122.387755875750031, 37.752835749697695 ], [ -122.387756291326852, 37.752835725460692 ], [ -122.387756280477618, 37.752835611637821 ], [ -122.387633974640565, 37.751559603455405 ], [ -122.387585270262662, 37.750263378063039 ], [ -122.388507729357968, 37.750205975353602 ], [ -122.389478017653872, 37.750145107673561 ], [ -122.390433038183829, 37.750118341633915 ], [ -122.391418367221192, 37.750140606282606 ], [ -122.391693805117924, 37.750111668488358 ], [ -122.391705979739527, 37.750110389401705 ], [ -122.391714686151033, 37.750109474739723 ], [ -122.391929934247997, 37.750086859865874 ], [ -122.392350740047519, 37.750026024134087 ], [ -122.392354048972692, 37.750025545503377 ], [ -122.392381992909165, 37.750021506018193 ], [ -122.393314555211816, 37.749918012051069 ], [ -122.393322792145199, 37.749917097944952 ], [ -122.393340265733656, 37.749915158351328 ], [ -122.395231400136637, 37.749808114846253 ], [ -122.395307862010839, 37.749803786070743 ], [ -122.396273544250747, 37.74974560058007 ], [ -122.397285415820363, 37.749684622841229 ], [ -122.40206163398004, 37.749396678541473 ], [ -122.402063015501824, 37.749396333205695 ], [ -122.402215286387033, 37.749358244852793 ], [ -122.402746245055127, 37.749375020002113 ], [ -122.402866303074774, 37.749478453789486 ], [ -122.402866309087059, 37.749478458911533 ], [ -122.402968925612569, 37.749550036933734 ], [ -122.403054692816028, 37.74954427306843 ], [ -122.403203674195439, 37.749534260068266 ], [ -122.403260968692152, 37.749530409553117 ], [ -122.403568849778438, 37.749475672986911 ], [ -122.403592456970202, 37.749471475909672 ], [ -122.403667613571599, 37.749458113882532 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403735650894049, 37.749583872707518 ], [ -122.403514246938869, 37.750281563827826 ], [ -122.403378511001179, 37.750679445903444 ], [ -122.403214572776321, 37.751164169122518 ], [ -122.403175027775376, 37.751281093014263 ], [ -122.403072643662085, 37.751794247946641 ], [ -122.403060331379024, 37.751915861053995 ], [ -122.403006670294943, 37.752445905732571 ], [ -122.402978169178084, 37.75275045704997 ], [ -122.40301031565609, 37.753201877425177 ], [ -122.403011715723153, 37.753221541143297 ], [ -122.403127143734139, 37.75447773377249 ], [ -122.403244936870763, 37.755759623462495 ], [ -122.403245024221292, 37.755760575231484 ], [ -122.403305530792395, 37.756165877853867 ], [ -122.403395700993769, 37.756471262485057 ], [ -122.403543859417937, 37.75680412126377 ], [ -122.403689260967766, 37.757015002579273 ], [ -122.403774084903432, 37.757138026202746 ], [ -122.403774405770974, 37.757138490742172 ], [ -122.404168659910013, 37.75752977302043 ], [ -122.404441730762329, 37.757748221369937 ], [ -122.40472107938929, 37.757950649275713 ], [ -122.404835145723425, 37.7580339263735 ], [ -122.405048169961901, 37.758189448369819 ], [ -122.405545221503274, 37.758536929330724 ], [ -122.405903135660481, 37.758934413706449 ], [ -122.406103779268008, 37.759196479511928 ], [ -122.406247868764623, 37.759429354319998 ], [ -122.406261953936678, 37.75945211819198 ], [ -122.406389357641601, 37.759804405761585 ], [ -122.406492391998796, 37.760255279712112 ], [ -122.406492697683589, 37.760686469836571 ], [ -122.406479479731502, 37.760730932047331 ], [ -122.4062826701159, 37.761392945250627 ], [ -122.406147061407296, 37.7616371808829 ], [ -122.406021608630979, 37.761863123291867 ], [ -122.405925510731336, 37.762011914887523 ], [ -122.405653838302513, 37.762432552128487 ], [ -122.405384490436404, 37.76297005284507 ], [ -122.40532469627189, 37.76313750682079 ], [ -122.405258413683796, 37.763323129887908 ], [ -122.405203088479681, 37.763478066930332 ], [ -122.405104592203571, 37.763852244172895 ], [ -122.405059622300016, 37.764310557738277 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.404841911264157, 37.764643492872132 ], [ -122.404496977488066, 37.764664317561468 ], [ -122.403527034519726, 37.764722870371436 ], [ -122.402563396844656, 37.764781034314389 ], [ -122.401598999326723, 37.764839236400867 ], [ -122.401604204317508, 37.764894634517518 ], [ -122.401720515816834, 37.766132520250999 ], [ -122.40075504647362, 37.766190486570189 ], [ -122.399760960198876, 37.766250252443854 ], [ -122.39980174760619, 37.766587745781216 ], [ -122.399677637243116, 37.766695444602661 ], [ -122.4004285874814, 37.76730030349627 ], [ -122.400680751363893, 37.767503407870436 ], [ -122.400877239583821, 37.76748837440357 ], [ -122.401842503677628, 37.76743077711658 ], [ -122.401962350744, 37.768706209224241 ], [ -122.402926809989424, 37.76864812762463 ], [ -122.403037758616776, 37.769828657390953 ], [ -122.403406518421917, 37.769807099957397 ], [ -122.403615112128733, 37.769893190404822 ], [ -122.403877699904385, 37.770062866265505 ], [ -122.403531054507056, 37.770336670466769 ], [ -122.401656665936628, 37.77181711392474 ], [ -122.399647849152032, 37.773403860176856 ], [ -122.399516440682405, 37.773507653276255 ], [ -122.399433130909728, 37.773573455617395 ], [ -122.400212340411557, 37.77419510487973 ], [ -122.400592319049849, 37.774498244536282 ], [ -122.400981567656032, 37.774808775969753 ], [ -122.402524096584528, 37.776039321403807 ], [ -122.40337073826143, 37.776714700215486 ], [ -122.403505778866929, 37.776822422431877 ], [ -122.403673439982725, 37.776956165214415 ], [ -122.404070256642925, 37.777272702482797 ], [ -122.404605505853894, 37.777699659794813 ], [ -122.405068508898097, 37.778068981419388 ], [ -122.405615266425855, 37.778505104539292 ], [ -122.404431250807633, 37.779440334605447 ], [ -122.403387209275081, 37.78026497235011 ], [ -122.401159718585049, 37.782024266952142 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":2,\"ZIP_CODE\":94105,\"ID\":94105},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.39249932896719, 37.793768814133983 ], [ -122.391890260341384, 37.794278544568918 ], [ -122.391788865572423, 37.794170982455135 ], [ -122.39173429034625, 37.79420276052317 ], [ -122.391666728649753, 37.794132425256194 ], [ -122.391723034266192, 37.79410061945832 ], [ -122.391673228351905, 37.794047854124599 ], [ -122.391982015107928, 37.793871906128679 ], [ -122.391589022782, 37.793527982873798 ], [ -122.391723231555744, 37.793429001692992 ], [ -122.390805927608938, 37.792657675413892 ], [ -122.388808599369057, 37.790978116182679 ], [ -122.388930714411984, 37.790880018842998 ], [ -122.388611692601799, 37.790313089306778 ], [ -122.388048971491713, 37.790507516256234 ], [ -122.388012417064502, 37.790431188672287 ], [ -122.388369268465311, 37.790308044637399 ], [ -122.388285919009036, 37.790161047422416 ], [ -122.388320274159014, 37.790150883121463 ], [ -122.388243932142601, 37.790007207265845 ], [ -122.388219415789919, 37.789995925591761 ], [ -122.388076857804336, 37.790038725053051 ], [ -122.38779803320179, 37.790149885537438 ], [ -122.385731305877229, 37.790973806479329 ], [ -122.385715648397209, 37.790970622998962 ], [ -122.385703294724394, 37.790961206457723 ], [ -122.385509561738004, 37.790556391724344 ], [ -122.385507518014222, 37.790544063409705 ], [ -122.385514141658973, 37.790532283492631 ], [ -122.385686917933171, 37.79045260764569 ], [ -122.387606646474296, 37.789787359176458 ], [ -122.387348795424046, 37.789311467853956 ], [ -122.38734757309517, 37.78931176210844 ], [ -122.385363735345749, 37.789788908162748 ], [ -122.385346470166525, 37.789790557672781 ], [ -122.385330865935444, 37.789789433734306 ], [ -122.3853151742195, 37.789784877582385 ], [ -122.385301142770246, 37.789777548268411 ], [ -122.385290501190326, 37.789767417313975 ], [ -122.385283285054584, 37.789755858694043 ], [ -122.385108047186435, 37.789329458915937 ], [ -122.385106003217075, 37.789317130597112 ], [ -122.385109132387825, 37.789304032849302 ], [ -122.385115773404124, 37.789292939120834 ], [ -122.385127656568173, 37.78928382174719 ], [ -122.385141339390941, 37.789277422487608 ], [ -122.385148542800621, 37.789220309385662 ], [ -122.387188020139192, 37.788773590697055 ], [ -122.387049561519532, 37.788228488916552 ], [ -122.385265755459955, 37.788520039162925 ], [ -122.385250134063114, 37.78851822849537 ], [ -122.385234425193858, 37.78851298588809 ], [ -122.385222089078539, 37.788504256015905 ], [ -122.385214890495845, 37.788493383558084 ], [ -122.385126670096767, 37.788154179856562 ], [ -122.385126356426341, 37.788141823596902 ], [ -122.385129502623784, 37.788129412294914 ], [ -122.385137873116122, 37.788118290909864 ], [ -122.385149773868434, 37.788109860244717 ], [ -122.385165203482913, 37.788104119771369 ], [ -122.38712311120571, 37.787785749180493 ], [ -122.387152415559953, 37.787781160094404 ], [ -122.38716964498991, 37.787778137163613 ], [ -122.38718858690207, 37.78777440037284 ], [ -122.387205798545097, 37.787770691273494 ], [ -122.387224740799539, 37.787766954471365 ], [ -122.387241935339802, 37.787762558917869 ], [ -122.387260859452994, 37.787758135677656 ], [ -122.387278054335084, 37.787753740113331 ], [ -122.387312408509445, 37.787743576101576 ], [ -122.387329567801103, 37.787737807654125 ], [ -122.387346745228243, 37.787732725636424 ], [ -122.387363887068403, 37.787726270740649 ], [ -122.38738104704403, 37.787720502274603 ], [ -122.38740694457185, 37.787241442038656 ], [ -122.387019444956891, 37.787247644385083 ], [ -122.384558944804354, 37.787400993480382 ], [ -122.384543323667074, 37.787399182993411 ], [ -122.384527632511421, 37.787394627009306 ], [ -122.384515296650974, 37.787385896512234 ], [ -122.384504655873897, 37.787375765751548 ], [ -122.384499135159118, 37.787362806267062 ], [ -122.384342210870813, 37.785748083262355 ], [ -122.384341897348037, 37.785735727269817 ], [ -122.384346773449252, 37.785723288336335 ], [ -122.38435689108006, 37.785712825798925 ], [ -122.384370521056837, 37.785704367292539 ], [ -122.384385968349036, 37.785699313626168 ], [ -122.387217967726542, 37.785525599931667 ], [ -122.387242186237785, 37.785525212275893 ], [ -122.387269846695702, 37.785524082797316 ], [ -122.387295742393547, 37.78552160811531 ], [ -122.387323367958444, 37.785519105736952 ], [ -122.387349245861117, 37.785515944605116 ], [ -122.387375089561743, 37.785511410569285 ], [ -122.387402662781668, 37.785506848841521 ], [ -122.387454279688384, 37.785495034977437 ], [ -122.387478323373855, 37.785487782842686 ], [ -122.387504079475306, 37.785479816566138 ], [ -122.387528105357717, 37.785471877983142 ], [ -122.387553826899222, 37.785462538802953 ], [ -122.387580894103834, 37.785438070169313 ], [ -122.387630346985745, 37.78486455148672 ], [ -122.385458308223278, 37.78499338709468 ], [ -122.385413881876744, 37.784606785946664 ], [ -122.387221366105805, 37.784501641660896 ], [ -122.387418079949683, 37.784479264504995 ], [ -122.38743876829875, 37.784476186423468 ], [ -122.387483534893533, 37.784467229088612 ], [ -122.387528231689217, 37.784455525961853 ], [ -122.387550562288524, 37.78444898795393 ], [ -122.387571145938807, 37.78444179118685 ], [ -122.387593442333781, 37.784433880546885 ], [ -122.387613991073025, 37.784425310610295 ], [ -122.38765505365474, 37.784406798388098 ], [ -122.387673819852623, 37.78439619708557 ], [ -122.387677823231684, 37.784349435820396 ], [ -122.387709046749492, 37.783875783688671 ], [ -122.386286930602978, 37.783958976243902 ], [ -122.386236533710786, 37.783405597391891 ], [ -122.387525601081578, 37.78333071590594 ], [ -122.387695596747974, 37.783278550154108 ], [ -122.387751878035047, 37.783246059476667 ], [ -122.387776877347846, 37.78286796141245 ], [ -122.3875502225155, 37.782801544578085 ], [ -122.387525883466154, 37.782797127453357 ], [ -122.387484228568439, 37.78279230054234 ], [ -122.387463418560699, 37.782790573250665 ], [ -122.387440913647538, 37.782790246810194 ], [ -122.387420138539767, 37.78278989267335 ], [ -122.387388984601131, 37.782789704683182 ], [ -122.387368261829351, 37.782791409869013 ], [ -122.387347521611616, 37.782792428607252 ], [ -122.384758972173714, 37.782940280456494 ], [ -122.384743316428853, 37.782937096842417 ], [ -122.384732711445736, 37.782928338977264 ], [ -122.384679972094801, 37.782554918115103 ], [ -122.384681388329753, 37.782542534464426 ], [ -122.384691505480149, 37.782532071892298 ], [ -122.384705187096728, 37.782525672675533 ], [ -122.387509249137921, 37.78234690330077 ], [ -122.387528224534123, 37.782344539065186 ], [ -122.387554136561661, 37.78234275076899 ], [ -122.38762294594062, 37.782322839482177 ], [ -122.387787765874378, 37.782275144853472 ], [ -122.387765464889313, 37.78194244138043 ], [ -122.388106738902522, 37.781931906493995 ], [ -122.388231340039766, 37.782146697467269 ], [ -122.388304301898813, 37.782356717503951 ], [ -122.388306939892615, 37.782385221520776 ], [ -122.389843136047602, 37.783611740842574 ], [ -122.389846392025618, 37.783609176617361 ], [ -122.390856721023212, 37.784410569188324 ], [ -122.391599105853345, 37.783822668268179 ], [ -122.391600764141245, 37.783823989838545 ], [ -122.392032919786118, 37.784168354830349 ], [ -122.392118766536498, 37.784205306785267 ], [ -122.392215026316663, 37.784279352365694 ], [ -122.392216553530702, 37.784278123341721 ], [ -122.39285700396303, 37.784773072758867 ], [ -122.39286959393705, 37.78478280203197 ], [ -122.39343711789931, 37.784459123881106 ], [ -122.394398389309941, 37.783701667981575 ], [ -122.395160622349934, 37.78431101230386 ], [ -122.395895701657864, 37.784896929203114 ], [ -122.396705177550587, 37.785542130425938 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.399369533643153, 37.785899247741973 ], [ -122.400468483886854, 37.785030285141588 ], [ -122.400994549885382, 37.785441790992316 ], [ -122.400998469557948, 37.785444857061968 ], [ -122.401480059865179, 37.785821567351917 ], [ -122.401489835489045, 37.785829214187501 ], [ -122.402025419766176, 37.786248152290575 ], [ -122.402575227394536, 37.786678208153539 ], [ -122.403028193190352, 37.787024418822838 ], [ -122.403028734856406, 37.78702485040828 ], [ -122.403342641535644, 37.787274627660381 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403239953883428, 37.787802352067843 ], [ -122.40290309900054, 37.788085647297414 ], [ -122.402903089488873, 37.788085654318181 ], [ -122.402404665507532, 37.788463925757384 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.401375490979433, 37.789264321921429 ], [ -122.400067228055164, 37.790303858609981 ], [ -122.399919790020746, 37.790414442896498 ], [ -122.399480001759599, 37.790744297277485 ], [ -122.399222136170906, 37.790956214990921 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399148598683425, 37.79101664916432 ], [ -122.398264448861653, 37.791716058450703 ], [ -122.39826444614279, 37.791716060417308 ], [ -122.397840810091907, 37.792047595331439 ], [ -122.39738344591872, 37.792405521295422 ], [ -122.396504486670068, 37.793097082520163 ], [ -122.396376076969489, 37.793198086984006 ], [ -122.396302004629632, 37.793256350149633 ], [ -122.396302004290646, 37.793256350429786 ], [ -122.395983670970253, 37.793501461560965 ], [ -122.395622922555773, 37.793779228585429 ], [ -122.39533276386878, 37.794011429145591 ], [ -122.395317307728533, 37.794023797689903 ], [ -122.394971722038662, 37.794300351169447 ], [ -122.394747579547172, 37.794479718347191 ], [ -122.394745733667804, 37.794478246556643 ], [ -122.394745701490038, 37.794478274267938 ], [ -122.394150899798674, 37.794987788593353 ], [ -122.39270797067924, 37.793614415765255 ], [ -122.392702063215523, 37.793608792659974 ], [ -122.39249932896719, 37.793768814133983 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":3,\"ZIP_CODE\":94129,\"ID\":94129},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.470990721149789, 37.787534455433345 ], [ -122.472290534181823, 37.787395917162272 ], [ -122.472401902490546, 37.787384046501884 ], [ -122.47254342327507, 37.787368961705944 ], [ -122.472782224474642, 37.787331534391747 ], [ -122.473316434470121, 37.787247805418339 ], [ -122.474432824368861, 37.787072820298278 ], [ -122.474584006514732, 37.787049123020516 ], [ -122.47616220885665, 37.786978730627588 ], [ -122.478707989065171, 37.78691405835 ], [ -122.481375583614479, 37.787198893328011 ], [ -122.483910676418589, 37.787705653817092 ], [ -122.483911841345289, 37.78771598691722 ], [ -122.484019551359538, 37.788671434543716 ], [ -122.484071292407094, 37.789571562974182 ], [ -122.484214987310182, 37.789767611906584 ], [ -122.484758163249651, 37.789969442365155 ], [ -122.485095392905748, 37.790094746949087 ], [ -122.486221786002261, 37.790513270391187 ], [ -122.486196663014411, 37.790544596769486 ], [ -122.486148110204482, 37.790605847513199 ], [ -122.486108757859654, 37.790687545376898 ], [ -122.486034586054117, 37.790761588869977 ], [ -122.48595999185838, 37.790819844471812 ], [ -122.485896199163861, 37.790893713055688 ], [ -122.485740384038749, 37.791021323419344 ], [ -122.48558351329909, 37.791238913899868 ], [ -122.485411383576832, 37.791468435611826 ], [ -122.485284654257228, 37.791712987869268 ], [ -122.484994993901111, 37.791948608146861 ], [ -122.484752903676167, 37.79221570331633 ], [ -122.484541285320944, 37.792522116372695 ], [ -122.484363647892437, 37.79274005429609 ], [ -122.484183607122475, 37.793062416612024 ], [ -122.484072129345151, 37.793295035940602 ], [ -122.483858237133077, 37.793646123471206 ], [ -122.483593432919847, 37.794035150786577 ], [ -122.48329368676049, 37.794671303481294 ], [ -122.483518531231283, 37.794730014502065 ], [ -122.48308859662535, 37.79561146099627 ], [ -122.482999467368202, 37.795903449272551 ], [ -122.48285101972256, 37.796177893057802 ], [ -122.482765752291186, 37.796484924353678 ], [ -122.482667211606753, 37.796813467545597 ], [ -122.482504820847737, 37.797149264997877 ], [ -122.482349642968302, 37.797495928150028 ], [ -122.482180347467406, 37.797832527598885 ], [ -122.482169137762298, 37.797996159399602 ], [ -122.482136359830861, 37.798129936813986 ], [ -122.482085493238017, 37.798234488898757 ], [ -122.482121580114068, 37.798289507917929 ], [ -122.482129545147487, 37.798328517967903 ], [ -122.482111649834962, 37.798371396374968 ], [ -122.482102148276397, 37.798404519393337 ], [ -122.482087585279373, 37.798442534909526 ], [ -122.482079722552115, 37.798472196426395 ], [ -122.482040900643639, 37.798509246213612 ], [ -122.481958513784505, 37.798535353148957 ], [ -122.481870239865685, 37.798535463289312 ], [ -122.481831454802517, 37.798573885582073 ], [ -122.48191348856912, 37.79872908258961 ], [ -122.481933747767386, 37.798774753652147 ], [ -122.481894064444091, 37.798779540742402 ], [ -122.481782518807989, 37.79868595916038 ], [ -122.481759479485277, 37.798795537302446 ], [ -122.481735532663109, 37.798806240772073 ], [ -122.481729674725472, 37.79884616987701 ], [ -122.481697076819628, 37.79885701872184 ], [ -122.481553967429107, 37.798748172710248 ], [ -122.481544740366218, 37.798791592096414 ], [ -122.481675391067768, 37.798887599511232 ], [ -122.481685177680191, 37.798930012650303 ], [ -122.481641997341171, 37.798933485499177 ], [ -122.481619981772425, 37.798951710306021 ], [ -122.481601609693158, 37.798976741512377 ], [ -122.481583384160913, 37.799007264136819 ], [ -122.481561643359143, 37.799035785621555 ], [ -122.481552599461594, 37.799086069282282 ], [ -122.48153672724159, 37.799139901359283 ], [ -122.481507471077649, 37.799211126714575 ], [ -122.481470873600131, 37.799266680501994 ], [ -122.481417267273812, 37.799333507835762 ], [ -122.481380082795965, 37.799367095921824 ], [ -122.481394839952742, 37.799401184738606 ], [ -122.48138749011072, 37.799450066445814 ], [ -122.481313103352264, 37.799516556146521 ], [ -122.481199920275785, 37.799556228466017 ], [ -122.481106226731853, 37.799612741364335 ], [ -122.481068868952889, 37.799704704484391 ], [ -122.481063386681015, 37.799823601791012 ], [ -122.481042139949025, 37.799870656721012 ], [ -122.48098682060791, 37.799938199625082 ], [ -122.480980184705871, 37.800013851703859 ], [ -122.480977156432658, 37.800224730569639 ], [ -122.480974804661557, 37.800331213761126 ], [ -122.480860274222692, 37.800385329797535 ], [ -122.480757965088657, 37.800443360304584 ], [ -122.48067857879505, 37.800582040732152 ], [ -122.48065611449222, 37.800648344612128 ], [ -122.480592381330325, 37.800724956178229 ], [ -122.480537665874181, 37.800815150409605 ], [ -122.480468568377589, 37.800950224199951 ], [ -122.480375485908439, 37.801094628286094 ], [ -122.480295978813317, 37.801293743123836 ], [ -122.480202328037436, 37.801416867810858 ], [ -122.480181840275407, 37.801557305763211 ], [ -122.480071801739967, 37.801844835145566 ], [ -122.480027318791514, 37.802059156846411 ], [ -122.480044134726015, 37.802235365083838 ], [ -122.480004943470632, 37.80238847850972 ], [ -122.479965705600534, 37.802604771748101 ], [ -122.479928024203545, 37.802749618690633 ], [ -122.479899085376999, 37.802897752511299 ], [ -122.479813341330441, 37.803057827736559 ], [ -122.479734279947053, 37.803208863153962 ], [ -122.479678141843124, 37.803310755542739 ], [ -122.479612025097651, 37.803427923840943 ], [ -122.479496444899141, 37.803572704736546 ], [ -122.479357004588351, 37.803861413494445 ], [ -122.479173928312875, 37.80433146553451 ], [ -122.479146809127258, 37.804483001950985 ], [ -122.479060667102701, 37.804693214811543 ], [ -122.478953175659996, 37.804881810346664 ], [ -122.478815220557294, 37.805291358623961 ], [ -122.47872883882674, 37.805492647931004 ], [ -122.478723432948883, 37.805679530333919 ], [ -122.47869664200617, 37.805843422292654 ], [ -122.478685946257201, 37.805961720018743 ], [ -122.478628322791295, 37.806008011253461 ], [ -122.478654742843744, 37.806089976088074 ], [ -122.478631304779441, 37.806119899160173 ], [ -122.478660343597923, 37.806170230012903 ], [ -122.478638955177018, 37.806277032642242 ], [ -122.478627125019798, 37.806352771870088 ], [ -122.478625044107972, 37.806469551574978 ], [ -122.47857586751384, 37.806637939992576 ], [ -122.478562516078711, 37.80678649853526 ], [ -122.478544650108333, 37.806960541692234 ], [ -122.478526703332861, 37.807066600081164 ], [ -122.478526389161814, 37.807184723319764 ], [ -122.478521297177736, 37.807253482414033 ], [ -122.478488208711909, 37.807311036148292 ], [ -122.478483958170003, 37.807411370608691 ], [ -122.478496304822514, 37.807549883734964 ], [ -122.478476156109295, 37.807833156107016 ], [ -122.478405186887699, 37.808093245296376 ], [ -122.478343748883177, 37.808126216988313 ], [ -122.478060427750734, 37.808278265895638 ], [ -122.477897062640963, 37.80844925522419 ], [ -122.477885395973601, 37.808531172189731 ], [ -122.477923697644769, 37.808669250052077 ], [ -122.477956528906134, 37.809251736581921 ], [ -122.477922196862778, 37.80978247039468 ], [ -122.477956931601639, 37.810111519960003 ], [ -122.477924513902849, 37.810324264541741 ], [ -122.477975357212003, 37.810543166576302 ], [ -122.477691959274622, 37.810627579828761 ], [ -122.477738440983671, 37.81094269802653 ], [ -122.477669662799912, 37.810960332923507 ], [ -122.477465352217308, 37.810997758254302 ], [ -122.4773147668257, 37.811025342092201 ], [ -122.476951827952391, 37.810983355163785 ], [ -122.476621797907725, 37.810876948869407 ], [ -122.47656163509636, 37.810893065263848 ], [ -122.476466638674822, 37.81083628492037 ], [ -122.47639876264958, 37.810757761311969 ], [ -122.476244453139699, 37.810423848047549 ], [ -122.476192907539087, 37.810243414175943 ], [ -122.47615497008627, 37.809858792499412 ], [ -122.47604874198008, 37.809640130705603 ], [ -122.475967371125094, 37.809574880888988 ], [ -122.475790153494444, 37.809485827869665 ], [ -122.475539966877108, 37.809387008587656 ], [ -122.474949730564461, 37.809260233883315 ], [ -122.474802168498073, 37.809179610065499 ], [ -122.474477490769161, 37.809143842083898 ], [ -122.474059299537672, 37.809106891534391 ], [ -122.473248913495638, 37.809099164155811 ], [ -122.472667246163553, 37.80903404071298 ], [ -122.472248345244111, 37.80897031306057 ], [ -122.471807567969009, 37.808865058985567 ], [ -122.471692590128313, 37.808861156356251 ], [ -122.471577117792876, 37.808857237159089 ], [ -122.471241422813662, 37.80879760884919 ], [ -122.470932885927255, 37.808717610499883 ], [ -122.47066006806898, 37.808613665617798 ], [ -122.470600816238615, 37.80859886080146 ], [ -122.470032562336371, 37.809430372877515 ], [ -122.469989867152222, 37.809452374737276 ], [ -122.469923620349192, 37.809434939483481 ], [ -122.469403337873828, 37.809203958897747 ], [ -122.469386781047135, 37.809167151824653 ], [ -122.469446470968407, 37.809067952374448 ], [ -122.469513627885206, 37.809054469737255 ], [ -122.470046113471852, 37.809288679655658 ], [ -122.470536336868321, 37.808582769832967 ], [ -122.47042036446561, 37.808452854807648 ], [ -122.470227327454069, 37.808354443608728 ], [ -122.470098624189163, 37.80826663136105 ], [ -122.470013817898405, 37.808202121652926 ], [ -122.469815463486142, 37.808098991213633 ], [ -122.469586546186093, 37.807953106986339 ], [ -122.469305300471973, 37.807727061403774 ], [ -122.469233592741006, 37.807568936815628 ], [ -122.469112342826094, 37.807370435141976 ], [ -122.46886886007195, 37.807066844008062 ], [ -122.468789265438318, 37.807002933173059 ], [ -122.468641769161479, 37.806924361175163 ], [ -122.468442727148087, 37.806860383444018 ], [ -122.468303117397753, 37.806818075943092 ], [ -122.46814816670377, 37.806980670547468 ], [ -122.46804250856195, 37.806913074050627 ], [ -122.468204198687829, 37.806743499513175 ], [ -122.468154584727131, 37.80670037662253 ], [ -122.467927641872478, 37.806563383834956 ], [ -122.4677672527434, 37.806455496134681 ], [ -122.467355591928154, 37.806141659736177 ], [ -122.467203291926239, 37.806012347645776 ], [ -122.466996296046148, 37.805909356729394 ], [ -122.466671493417238, 37.805802836197401 ], [ -122.466463320033583, 37.805982110961864 ], [ -122.466652133036774, 37.805790797867139 ], [ -122.466274152924925, 37.805653990321176 ], [ -122.466075349709101, 37.80558203392561 ], [ -122.465895016150796, 37.805570618742252 ], [ -122.465648753037854, 37.805488195293123 ], [ -122.46546102999136, 37.805393808021854 ], [ -122.464551731906468, 37.805113664168701 ], [ -122.464230324554308, 37.805004333527904 ], [ -122.463987997870177, 37.804939695859083 ], [ -122.463579617827847, 37.804879883064366 ], [ -122.463271699005631, 37.804887756693624 ], [ -122.462952230341088, 37.804917109899002 ], [ -122.4624302743036, 37.805013698161765 ], [ -122.462214023782167, 37.805018669869817 ], [ -122.462027122615794, 37.804955166469341 ], [ -122.461896643188638, 37.804930554615567 ], [ -122.461601325019004, 37.804956755736356 ], [ -122.461272453201914, 37.805023344559039 ], [ -122.461007547032963, 37.805086808651389 ], [ -122.459772513833471, 37.805253612450713 ], [ -122.45957795006224, 37.805293241948746 ], [ -122.459540191073017, 37.805305543773983 ], [ -122.45950068434351, 37.805317187894794 ], [ -122.459462907558404, 37.805328803259258 ], [ -122.459423363879012, 37.805339074505127 ], [ -122.45938555118417, 37.805349316978521 ], [ -122.459306427886148, 37.805368486538775 ], [ -122.459227232780066, 37.805384910313265 ], [ -122.459185886621981, 37.805392464497949 ], [ -122.459106618316682, 37.805406142481793 ], [ -122.459065217794489, 37.805411637333286 ], [ -122.459025547917307, 37.805417103422087 ], [ -122.458984129270405, 37.80542191181479 ], [ -122.458944422811243, 37.805426005021417 ], [ -122.458902967930442, 37.80542944052501 ], [ -122.458861494934339, 37.805432189583698 ], [ -122.458788912310027, 37.805436828799479 ], [ -122.458735345621889, 37.80544046541047 ], [ -122.458683526998115, 37.80544475969581 ], [ -122.458631726477662, 37.805449740388731 ], [ -122.458578213760234, 37.805455436499734 ], [ -122.458526449095928, 37.805461790012899 ], [ -122.458474703215558, 37.805468829647623 ], [ -122.458371283182728, 37.805485655405761 ], [ -122.458319609004732, 37.805495440705506 ], [ -122.458269665819927, 37.805505197514321 ], [ -122.45821802818655, 37.805516356173719 ], [ -122.458168120492331, 37.805527485811837 ], [ -122.458116519376176, 37.805540017007239 ], [ -122.458066648197928, 37.805552519182882 ], [ -122.457989327072951, 37.805574405126777 ], [ -122.45793619470966, 37.805594515430641 ], [ -122.457883043876322, 37.805613939560359 ], [ -122.457829874920449, 37.805632677510189 ], [ -122.457774957510779, 37.805650757180459 ], [ -122.457721751946011, 37.805668121951648 ], [ -122.457666780196845, 37.805684142829186 ], [ -122.457610078443324, 37.805700191847833 ], [ -122.457500062415335, 37.80572948720183 ], [ -122.457443287865871, 37.805742790973085 ], [ -122.457386495190917, 37.805755408011883 ], [ -122.457329684053221, 37.805767338598564 ], [ -122.457272855492562, 37.805778582715845 ], [ -122.457186701695477, 37.805793746855791 ], [ -122.457077909022615, 37.805803793319789 ], [ -122.456970882463025, 37.805815183288864 ], [ -122.456862180533747, 37.805828661426311 ], [ -122.456755226634087, 37.80584279746536 ], [ -122.456648326952561, 37.805858992150092 ], [ -122.456541463411881, 37.805876559873688 ], [ -122.456434635998889, 37.805895500361686 ], [ -122.45632959276756, 37.805916471347693 ], [ -122.456224568256204, 37.805938128659257 ], [ -122.456119597596228, 37.80596184517497 ], [ -122.456014644961741, 37.805986248302439 ], [ -122.45591147681688, 37.806012681382633 ], [ -122.455806614105612, 37.806040516209428 ], [ -122.455705248815832, 37.806069666405428 ], [ -122.455564429228914, 37.806112518618505 ], [ -122.45530695988451, 37.806195761893825 ], [ -122.454600415883462, 37.806430130022392 ], [ -122.454483234784888, 37.806468999744823 ], [ -122.454353658700001, 37.806478701096999 ], [ -122.454308145719423, 37.806459540393945 ], [ -122.454293829263207, 37.806375996256122 ], [ -122.454085665791084, 37.806359530611644 ], [ -122.454069929877903, 37.806353610767474 ], [ -122.453815604080873, 37.806293271483817 ], [ -122.453295386032664, 37.806259311412973 ], [ -122.452781551105744, 37.806270567825713 ], [ -122.452438711570082, 37.806267316310901 ], [ -122.451653324871984, 37.806353109244483 ], [ -122.450906745259303, 37.80653233722969 ], [ -122.450459653590798, 37.806646175036533 ], [ -122.44991286747026, 37.806721142700994 ], [ -122.449819483247254, 37.806724746830461 ], [ -122.449720673642986, 37.806719513093618 ], [ -122.449631038513971, 37.806734042766898 ], [ -122.449554038474531, 37.806768278664137 ], [ -122.44947869660497, 37.806799740182214 ], [ -122.449399659671798, 37.806822335243758 ], [ -122.449304851007483, 37.806837636909691 ], [ -122.449171524916196, 37.806836407131136 ], [ -122.449109070409847, 37.806831258752986 ], [ -122.449048183710957, 37.806819903592029 ], [ -122.449010891791161, 37.806784123494651 ], [ -122.448891660899136, 37.806726348099637 ], [ -122.448806459828916, 37.806711961234221 ], [ -122.448728288255808, 37.806701578532227 ], [ -122.448565239272398, 37.806755090545202 ], [ -122.448529100304583, 37.806763241657009 ], [ -122.448455091339923, 37.806133486140737 ], [ -122.448455258275729, 37.806133277363898 ], [ -122.448323332142422, 37.805007750605469 ], [ -122.448321161555853, 37.804993824977359 ], [ -122.448279145759457, 37.80472422506687 ], [ -122.44824552349904, 37.804508484024247 ], [ -122.448240728857812, 37.804477717258706 ], [ -122.448240723025904, 37.804477679722268 ], [ -122.448245764546613, 37.8044754914729 ], [ -122.448316879890498, 37.804444627382082 ], [ -122.448814547024654, 37.804228638340902 ], [ -122.448954929708933, 37.80412855834809 ], [ -122.449124213448584, 37.804007874392056 ], [ -122.449253815658196, 37.803861977230383 ], [ -122.449388062152011, 37.803710849923448 ], [ -122.449495662264184, 37.803470319431604 ], [ -122.449583722697156, 37.803153095621241 ], [ -122.449583713807527, 37.803152915295549 ], [ -122.449565462756809, 37.802791419060213 ], [ -122.449536106109235, 37.802697377072583 ], [ -122.449460220896157, 37.80245428555321 ], [ -122.449178591741017, 37.802073857413127 ], [ -122.449043488821943, 37.801965383441527 ], [ -122.448802640091088, 37.801772003872607 ], [ -122.448359651469772, 37.801586782043685 ], [ -122.447921599691895, 37.801532283988202 ], [ -122.447857007904148, 37.801596177616553 ], [ -122.447819731578562, 37.801633051558142 ], [ -122.447819572978844, 37.801633208279924 ], [ -122.447802865090509, 37.801525678628579 ], [ -122.447614227653389, 37.80032479990944 ], [ -122.447613279369918, 37.80032370169468 ], [ -122.447466030932901, 37.799389886853575 ], [ -122.447400275900733, 37.798948048802856 ], [ -122.447341437979304, 37.798552685345861 ], [ -122.447327769221047, 37.798519415660031 ], [ -122.447303043858923, 37.798459232650295 ], [ -122.447209582420982, 37.797849561838987 ], [ -122.44715829626513, 37.797515005478481 ], [ -122.44715829625072, 37.79751500492933 ], [ -122.447014738232738, 37.796578510197754 ], [ -122.447014738218328, 37.796578509648619 ], [ -122.446928896449293, 37.796018515191513 ], [ -122.44687098056005, 37.795640691997477 ], [ -122.446751148304088, 37.794858932799087 ], [ -122.446727777275527, 37.794706465308671 ], [ -122.446727777261131, 37.794706464759514 ], [ -122.446587862952981, 37.793765120337646 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446446033268856, 37.792810866507708 ], [ -122.446299573716246, 37.791878694429421 ], [ -122.446299547924625, 37.791878529215651 ], [ -122.44720637942703, 37.791763091830468 ], [ -122.447679208068394, 37.79170289872647 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.449385188501296, 37.791484927305568 ], [ -122.450995459060991, 37.791209906278311 ], [ -122.452035629818212, 37.790998907609811 ], [ -122.454248357567437, 37.79059310209788 ], [ -122.455878319348855, 37.790294143976332 ], [ -122.457498733292852, 37.789996912625632 ], [ -122.459011873160264, 37.789719335764374 ], [ -122.459028627866218, 37.789715983297555 ], [ -122.459250455476877, 37.789713291862398 ], [ -122.45947565027717, 37.789710559160014 ], [ -122.459781586189493, 37.789596250165474 ], [ -122.459925547070256, 37.789542753604124 ], [ -122.468653534340191, 37.787783523544832 ], [ -122.470990721149789, 37.787534455433345 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":4,\"ZIP_CODE\":94121,\"ID\":94121},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.504456509027335, 37.788067554312491 ], [ -122.504150949920984, 37.788096097763379 ], [ -122.504066815467581, 37.788120877294119 ], [ -122.504049145771674, 37.78810744254686 ], [ -122.503997231067004, 37.788107638193928 ], [ -122.503846231021797, 37.788091662877633 ], [ -122.503713944876083, 37.788063694207068 ], [ -122.503612889329972, 37.788038628655784 ], [ -122.503526395190363, 37.78804009845387 ], [ -122.503485137277139, 37.78805041389267 ], [ -122.503463545305024, 37.78801987747579 ], [ -122.503373498826377, 37.788017973822534 ], [ -122.503335534005146, 37.788022052592311 ], [ -122.503263415949803, 37.788043193399247 ], [ -122.503234378285072, 37.788057421563131 ], [ -122.503243398050046, 37.788071003120102 ], [ -122.503209152201507, 37.78808463302547 ], [ -122.503179577522019, 37.788078954832578 ], [ -122.503144887269229, 37.788076110778647 ], [ -122.503077958752755, 37.78809716302807 ], [ -122.503049013609541, 37.788114823274398 ], [ -122.503006581160705, 37.78814576070333 ], [ -122.502956321560973, 37.788143180813591 ], [ -122.502925535751373, 37.788156752131144 ], [ -122.502902751007483, 37.788146151342978 ], [ -122.502865053026014, 37.788095972678335 ], [ -122.502836115946863, 37.78804976589835 ], [ -122.502789020665148, 37.78803614434397 ], [ -122.502704311593945, 37.788039643712104 ], [ -122.502589076168022, 37.788066323379901 ], [ -122.502515190951499, 37.788086120259166 ], [ -122.502425496104195, 37.788097257971025 ], [ -122.502376106845503, 37.788126939838207 ], [ -122.502316846531585, 37.7881114644726 ], [ -122.502255070395776, 37.788131055545016 ], [ -122.502169528699341, 37.788103665363288 ], [ -122.502105689513968, 37.788110929794158 ], [ -122.50207398762231, 37.788154733004774 ], [ -122.502071249254328, 37.788181562090124 ], [ -122.501989944924048, 37.788182942633284 ], [ -122.501951813392949, 37.78818084312524 ], [ -122.501912664088508, 37.788140990459205 ], [ -122.501912016401619, 37.788116965280736 ], [ -122.501877326179624, 37.788114120853955 ], [ -122.501877973840479, 37.788138145483494 ], [ -122.501858399507697, 37.788182429188559 ], [ -122.501842192404723, 37.788223222019695 ], [ -122.501801304245816, 37.788247265660054 ], [ -122.501766151357245, 37.788227260005755 ], [ -122.501756965310037, 37.788207500503631 ], [ -122.50172600293584, 37.788150340007959 ], [ -122.501700517223497, 37.788167941185236 ], [ -122.50167957356264, 37.788161429621908 ], [ -122.501619212555269, 37.788169321478662 ], [ -122.501569767420492, 37.788196943730178 ], [ -122.501591358665465, 37.788227480492822 ], [ -122.501533078675976, 37.788248385310276 ], [ -122.501514596042369, 37.788204747707923 ], [ -122.50145942465268, 37.788212551659868 ], [ -122.501437657642668, 37.788239704012419 ], [ -122.501335150234794, 37.788224962263008 ], [ -122.501268313574613, 37.788249445888859 ], [ -122.50115878458611, 37.788295256277053 ], [ -122.501091780980232, 37.788313561975109 ], [ -122.501021937979715, 37.788290711431223 ], [ -122.501006184350814, 37.78828411139213 ], [ -122.50097195648992, 37.788298427073251 ], [ -122.500993621505359, 37.788331709648567 ], [ -122.500947376526028, 37.788349663239131 ], [ -122.500879097304491, 37.788320605139283 ], [ -122.500861336013799, 37.788303738053742 ], [ -122.500826830696198, 37.788307757577726 ], [ -122.500806349431429, 37.788318405959657 ], [ -122.500754452719278, 37.788319286610971 ], [ -122.500738328529678, 37.78829895802815 ], [ -122.500712935348687, 37.788319991115557 ], [ -122.500640723790397, 37.788337698187455 ], [ -122.500593831459696, 37.788331626454848 ], [ -122.500550140713713, 37.78831588602435 ], [ -122.500538567416584, 37.788335997845735 ], [ -122.50048939950284, 37.78837391630227 ], [ -122.500458095017024, 37.788368266503305 ], [ -122.500446429574566, 37.788384946179491 ], [ -122.50034045177307, 37.788434129242091 ], [ -122.500272801380206, 37.788428409551436 ], [ -122.500256870701349, 37.788479498553457 ], [ -122.500250772830086, 37.788574372083048 ], [ -122.500192399744307, 37.788591844106463 ], [ -122.500174472488098, 37.788568799078057 ], [ -122.500178293975864, 37.788517915512088 ], [ -122.500130319126384, 37.788535897847389 ], [ -122.500091521884229, 37.788509086399898 ], [ -122.500099172636652, 37.788471872673249 ], [ -122.500066212558522, 37.788468998362944 ], [ -122.50006658231419, 37.788482726609828 ], [ -122.500011040812936, 37.788476801362307 ], [ -122.499966794796208, 37.788440467945314 ], [ -122.499878115891249, 37.788489357153694 ], [ -122.499810631863213, 37.788489815307436 ], [ -122.499785570748472, 37.78852320387022 ], [ -122.499760324750355, 37.788549727891812 ], [ -122.499797151337432, 37.788567645255235 ], [ -122.499803450517931, 37.788608742793357 ], [ -122.49976162645244, 37.788662331112889 ], [ -122.499733393517658, 37.788642207768127 ], [ -122.499727335024716, 37.788610033760072 ], [ -122.49968724080243, 37.78866359271359 ], [ -122.499634371443463, 37.788692645687142 ], [ -122.499577562358439, 37.788703910220832 ], [ -122.499554630056736, 37.788687817377856 ], [ -122.499511356576448, 37.788623310977023 ], [ -122.499481164550005, 37.788530426400044 ], [ -122.499432590648397, 37.788461889419303 ], [ -122.499336411156918, 37.788425062960037 ], [ -122.499266771879846, 37.788409762065221 ], [ -122.499202877060384, 37.788414965914313 ], [ -122.499128103233858, 37.788401812217707 ], [ -122.499099500985864, 37.788367960195586 ], [ -122.499041638784547, 37.788340098176221 ], [ -122.498955421371278, 37.788351861015244 ], [ -122.498920128036829, 37.788390916780045 ], [ -122.498889600061332, 37.788414096736403 ], [ -122.498846352726645, 37.788414829911119 ], [ -122.498829249780883, 37.788358120749585 ], [ -122.498858133988094, 37.78827384858316 ], [ -122.49881028141921, 37.788232082242921 ], [ -122.498779365121052, 37.788240846950437 ], [ -122.498721638945739, 37.788282343161484 ], [ -122.498684745303493, 37.788326233159403 ], [ -122.498610415395817, 37.78832955335983 ], [ -122.498598336042235, 37.788266578082698 ], [ -122.498619439354101, 37.78821471490064 ], [ -122.498602392043921, 37.788160064708592 ], [ -122.498537684539031, 37.788135065438439 ], [ -122.498508608985759, 37.788147919585676 ], [ -122.498480069151427, 37.788180680090115 ], [ -122.498393075984339, 37.788163612611328 ], [ -122.498359007071954, 37.788119551978269 ], [ -122.498367465612489, 37.788047987710556 ], [ -122.498258002596998, 37.788031987711385 ], [ -122.498205083784299, 37.787930560386634 ], [ -122.498141496631646, 37.787882879514228 ], [ -122.498020127555392, 37.787874635204304 ], [ -122.497965657850202, 37.7879085217278 ], [ -122.497937733854386, 37.787835513791961 ], [ -122.497862794972676, 37.787816181447418 ], [ -122.497833152644503, 37.787872309616162 ], [ -122.497858996475657, 37.787932304773527 ], [ -122.497838976582202, 37.787960113577462 ], [ -122.497779058543074, 37.787984477979037 ], [ -122.497727525397508, 37.787934532426668 ], [ -122.4976806523715, 37.787929145955836 ], [ -122.497620549305267, 37.78794664601778 ], [ -122.497594065916346, 37.787927179270163 ], [ -122.49754290189, 37.78789096244769 ], [ -122.49750382929264, 37.787853853467084 ], [ -122.497544349339876, 37.787816083058736 ], [ -122.497526053110533, 37.787779309105126 ], [ -122.497500930399411, 37.787746084497506 ], [ -122.497517139436695, 37.787705292265386 ], [ -122.497507659501551, 37.787674549599402 ], [ -122.497485331532715, 37.787616555022737 ], [ -122.497458570647495, 37.787586791856477 ], [ -122.497406323956653, 37.787574629197884 ], [ -122.49735786900257, 37.787574763042187 ], [ -122.497315176016556, 37.787596088453697 ], [ -122.497275259657627, 37.787591957457558 ], [ -122.497216443565691, 37.787592953772126 ], [ -122.497168648080745, 37.787553245760044 ], [ -122.497138187607533, 37.787514617578381 ], [ -122.497106127216909, 37.787480823939255 ], [ -122.497044990964866, 37.787459883810421 ], [ -122.496999885071403, 37.787455840347057 ], [ -122.496923235404381, 37.787437223130333 ], [ -122.496894579157143, 37.787401311287425 ], [ -122.49685734707576, 37.787368291637833 ], [ -122.496811927818939, 37.787352579117055 ], [ -122.496751769988833, 37.787368019451996 ], [ -122.496728617227618, 37.787343688934776 ], [ -122.496685721170834, 37.787357463425209 ], [ -122.496639544906756, 37.787313607347343 ], [ -122.496583690806744, 37.787296011229486 ], [ -122.496547105206147, 37.787287016686854 ], [ -122.496516354783793, 37.787301958649365 ], [ -122.49646797383916, 37.787304838384266 ], [ -122.496443903661216, 37.787310739875238 ], [ -122.496383616604291, 37.787321374760424 ], [ -122.496337075877918, 37.787328343443079 ], [ -122.496276530804948, 37.787329368567015 ], [ -122.496247565545247, 37.787346340725328 ], [ -122.496197934785357, 37.787367096454552 ], [ -122.496141439524379, 37.787390028597343 ], [ -122.496086747911505, 37.787415677408667 ], [ -122.496068420456311, 37.787442083500181 ], [ -122.496016690354622, 37.787449139908034 ], [ -122.495972174940263, 37.787467062259353 ], [ -122.495951842506244, 37.787478615416269 ], [ -122.495939786703687, 37.787485465489738 ], [ -122.495905650481646, 37.787503211836551 ], [ -122.495879794980965, 37.787507083489736 ], [ -122.495820979283494, 37.78750807910243 ], [ -122.49579201419597, 37.787525051142431 ], [ -122.495771716907484, 37.787542562920031 ], [ -122.495716453167731, 37.787546932051697 ], [ -122.495690597288615, 37.78755080339463 ], [ -122.495618108578682, 37.787558210990248 ], [ -122.495602871400223, 37.787570830192877 ], [ -122.495572047584162, 37.787583026463722 ], [ -122.495539604212951, 37.787599370571847 ], [ -122.495514135330879, 37.787617657121146 ], [ -122.495483182354334, 37.787625048105859 ], [ -122.495437195053938, 37.787652609233902 ], [ -122.495395954684781, 37.787663608544698 ], [ -122.495370190617706, 37.787670911683094 ], [ -122.495333314088882, 37.787715487058371 ], [ -122.495282044474621, 37.787739703822119 ], [ -122.495193913200154, 37.787744628911668 ], [ -122.495147390885393, 37.787752283545302 ], [ -122.495107603658113, 37.787752956804084 ], [ -122.495081894872186, 37.787762319709344 ], [ -122.495049506978233, 37.787780722682484 ], [ -122.494942531016747, 37.787792833838672 ], [ -122.494936422880045, 37.787823153702263 ], [ -122.49487624549289, 37.787837906657984 ], [ -122.494850021018962, 37.787828049271276 ], [ -122.494803406419749, 37.787832271634258 ], [ -122.494754880927445, 37.787894212652994 ], [ -122.494747761584406, 37.787951332156055 ], [ -122.494723178126165, 37.788002566741419 ], [ -122.494687204176572, 37.788080776838861 ], [ -122.494700862218465, 37.788138231843071 ], [ -122.494641953469724, 37.788135794738686 ], [ -122.494554921507401, 37.788052798168373 ], [ -122.494515868416002, 37.788016374906285 ], [ -122.4945350783522, 37.787958364153376 ], [ -122.494513047598829, 37.787911351574522 ], [ -122.494517883781597, 37.787833668269109 ], [ -122.494492854234224, 37.787803875161934 ], [ -122.494463538439888, 37.787807804761236 ], [ -122.494432676774579, 37.787818627891369 ], [ -122.494408993418205, 37.787838943934148 ], [ -122.494413448366132, 37.787875952476604 ], [ -122.494396887310245, 37.787903702167263 ], [ -122.49434981175834, 37.787890763675087 ], [ -122.494353194757167, 37.787823406042577 ], [ -122.494335895597104, 37.78782369865479 ], [ -122.49430957902598, 37.787810409012593 ], [ -122.494266331651687, 37.787811140513561 ], [ -122.49416034840992, 37.787795764612234 ], [ -122.494129594818148, 37.787746152815927 ], [ -122.494106737724252, 37.787732804619111 ], [ -122.494080878906914, 37.78767212220432 ], [ -122.494005791021607, 37.787582742860096 ], [ -122.49397989817011, 37.78758524069908 ], [ -122.493937148914341, 37.787604505606133 ], [ -122.49390314117592, 37.787627056362858 ], [ -122.493853140270886, 37.787634082573049 ], [ -122.493776804515477, 37.787627132573334 ], [ -122.493743568607016, 37.787613959813378 ], [ -122.493738061048077, 37.787613306047696 ], [ -122.493682801772195, 37.787606746485388 ], [ -122.493586205100101, 37.787618680905503 ], [ -122.493496509486093, 37.787629812101237 ], [ -122.493461856653141, 37.787628337503875 ], [ -122.493419162517711, 37.787649661504638 ], [ -122.493384362282285, 37.787642696015901 ], [ -122.493336275791464, 37.787656556752133 ], [ -122.493293636533849, 37.787679939994845 ], [ -122.493241832649318, 37.78768424947036 ], [ -122.493193617141287, 37.7876933054322 ], [ -122.493182042295089, 37.787713416527986 ], [ -122.493127220447846, 37.787734258699906 ], [ -122.493090653466112, 37.787725949238315 ], [ -122.493056424336842, 37.787740262623288 ], [ -122.493004582859115, 37.787743199151691 ], [ -122.492969929990394, 37.787741724684366 ], [ -122.492955061461288, 37.787768072078173 ], [ -122.492913931184304, 37.787783188809769 ], [ -122.492855170492433, 37.787786242224989 ], [ -122.492860417128782, 37.787852767193691 ], [ -122.492830071355925, 37.787882810136729 ], [ -122.492771255320051, 37.787883804228279 ], [ -122.492769800087729, 37.787829576197716 ], [ -122.492735920840914, 37.787856931882516 ], [ -122.49268687433684, 37.78777054473629 ], [ -122.492679439061774, 37.787751441716487 ], [ -122.492627892968244, 37.787765361179233 ], [ -122.492565672509713, 37.787768472646512 ], [ -122.492506929887313, 37.787772212321414 ], [ -122.492446513322577, 37.787778040470201 ], [ -122.492381054094224, 37.787789447708391 ], [ -122.492305602297094, 37.78781544526975 ], [ -122.492216274554934, 37.787840303764064 ], [ -122.492178548608294, 37.787853302497936 ], [ -122.492118555140365, 37.787874918314273 ], [ -122.492079412316457, 37.787899615509929 ], [ -122.492057605141056, 37.787925393277668 ], [ -122.491876207129678, 37.788001938945712 ], [ -122.491812827679937, 37.788026359033061 ], [ -122.491772083659185, 37.788055890066879 ], [ -122.491719745560601, 37.788104846164785 ], [ -122.491654323091851, 37.788117625573307 ], [ -122.491646041850075, 37.7881315002275 ], [ -122.491614039214184, 37.788164317512276 ], [ -122.491501375628616, 37.788222533071625 ], [ -122.491447583651606, 37.788217260993214 ], [ -122.491404851437281, 37.788237211413325 ], [ -122.491281422016286, 37.788281187084038 ], [ -122.491218428658357, 37.788320021552913 ], [ -122.491190014384841, 37.788357585284182 ], [ -122.491137087049111, 37.788384575161345 ], [ -122.491061983912687, 37.788423614320799 ], [ -122.490946338544006, 37.788499734606098 ], [ -122.490859530409139, 37.788554079246722 ], [ -122.490843207112334, 37.788590751992317 ], [ -122.490818381167585, 37.788633062206074 ], [ -122.490696422877051, 37.78873195173508 ], [ -122.490605105739689, 37.78881178134435 ], [ -122.490527498874997, 37.788886572527467 ], [ -122.490413287423564, 37.788951680519325 ], [ -122.490407103746506, 37.78897925443183 ], [ -122.490354139232181, 37.78900487109366 ], [ -122.490306437661019, 37.789033145843504 ], [ -122.490251889996458, 37.789064283086574 ], [ -122.490252239550742, 37.789077325209703 ], [ -122.490220015123754, 37.789101904980171 ], [ -122.490046709329732, 37.789222262378196 ], [ -122.489918657120555, 37.789352156846768 ], [ -122.489828681483118, 37.789482095324693 ], [ -122.489700630133797, 37.789482882790033 ], [ -122.489592566089883, 37.789454489856745 ], [ -122.489300670464104, 37.789412030065776 ], [ -122.489191447548507, 37.789340391834585 ], [ -122.488990889258972, 37.789283342302205 ], [ -122.48848285911852, 37.789442306771541 ], [ -122.488100013149761, 37.789428847345718 ], [ -122.488028309941384, 37.789530320047362 ], [ -122.487863707315668, 37.789588034282978 ], [ -122.487736999824023, 37.789574375465854 ], [ -122.487584402186499, 37.78949866010057 ], [ -122.487564674856429, 37.789472896340307 ], [ -122.487563664349921, 37.78943514280558 ], [ -122.487464302267156, 37.789473215069947 ], [ -122.48744759425297, 37.789430918694578 ], [ -122.487369692668253, 37.789430171560369 ], [ -122.487296628705792, 37.789480848156245 ], [ -122.487120114954962, 37.789610869819036 ], [ -122.487051777894393, 37.789708851205745 ], [ -122.4869376141597, 37.789840568479555 ], [ -122.486809390152729, 37.789964281705743 ], [ -122.486730121786451, 37.790041845235905 ], [ -122.486553036429143, 37.790150586495734 ], [ -122.486429009493079, 37.790237144752382 ], [ -122.486335272657072, 37.790356155991162 ], [ -122.486245109599949, 37.790414674064778 ], [ -122.486236878145661, 37.790495160989501 ], [ -122.486221786002261, 37.790513270391187 ], [ -122.485095392905748, 37.790094746949087 ], [ -122.484316748868366, 37.789805424389257 ], [ -122.484214987310182, 37.789767611906584 ], [ -122.484071292407094, 37.789571562974182 ], [ -122.484019551359538, 37.788671434543716 ], [ -122.483911841345289, 37.78771598691722 ], [ -122.483910676418589, 37.787705653817092 ], [ -122.481375583614479, 37.787198893328011 ], [ -122.479861181106074, 37.787037199203986 ], [ -122.478707989065171, 37.78691405835 ], [ -122.477707716230128, 37.786939475526701 ], [ -122.476637454822679, 37.786966661899442 ], [ -122.476572244768761, 37.786128658440397 ], [ -122.476575166016133, 37.786128513873635 ], [ -122.476575148769683, 37.786128282045887 ], [ -122.476436803741322, 37.784271125057622 ], [ -122.476306551695615, 37.782405954593735 ], [ -122.476169188584777, 37.78047585000003 ], [ -122.476025278884237, 37.778553841183147 ], [ -122.475888270951586, 37.776692974324831 ], [ -122.475750968701433, 37.774828017958995 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475613625589148, 37.772962410314676 ], [ -122.476694205661246, 37.772912858094969 ], [ -122.477069824745385, 37.772895630732648 ], [ -122.477766947236773, 37.772863655180586 ], [ -122.478836951668541, 37.772814568639937 ], [ -122.47883762078969, 37.772814536530767 ], [ -122.478848715172788, 37.772814028898644 ], [ -122.479904580478745, 37.772765580938412 ], [ -122.480978851643016, 37.772716079930653 ], [ -122.481947659007389, 37.77267180789157 ], [ -122.482048961146063, 37.772667157434498 ], [ -122.483119121668807, 37.772618024364625 ], [ -122.483119139145145, 37.772618264154566 ], [ -122.484194219251819, 37.772569315707138 ], [ -122.484194201052048, 37.772569061919981 ], [ -122.485152053625171, 37.772525429693054 ], [ -122.485261332297384, 37.772520422109807 ], [ -122.485262022888591, 37.772520390701253 ], [ -122.486334053115812, 37.772471462244795 ], [ -122.486678527930636, 37.772455879683093 ], [ -122.487398288926627, 37.772423078182946 ], [ -122.487398306387334, 37.772423316873983 ], [ -122.487399123482518, 37.77242327974907 ], [ -122.488470602075608, 37.772374307055074 ], [ -122.488471323394165, 37.772374169626119 ], [ -122.489539486385709, 37.772325343661755 ], [ -122.49061689309373, 37.772276279654356 ], [ -122.490617013718605, 37.772276274046327 ], [ -122.491688031137627, 37.772227293012065 ], [ -122.492761363210761, 37.772178246338406 ], [ -122.493316307377469, 37.772153213706439 ], [ -122.493832479293559, 37.77212966191987 ], [ -122.493832498174271, 37.772129926957135 ], [ -122.493833180817205, 37.772129896182108 ], [ -122.494902921073546, 37.772080894286702 ], [ -122.494903299427449, 37.772080795311069 ], [ -122.495971990075716, 37.772031913255347 ], [ -122.497046812008847, 37.771982808835908 ], [ -122.497412740335307, 37.771966238858134 ], [ -122.498113882732468, 37.771934487449556 ], [ -122.499183042122894, 37.771885392463027 ], [ -122.500200291234236, 37.771838923903807 ], [ -122.500257545202601, 37.771836308607973 ], [ -122.501327700223527, 37.771787274197017 ], [ -122.502399595949868, 37.771738155153713 ], [ -122.502399732131124, 37.771738148994636 ], [ -122.503472688521128, 37.771689380410749 ], [ -122.503472690942445, 37.771689380369601 ], [ -122.504544396347384, 37.771640577944709 ], [ -122.505611140464779, 37.771591516134151 ], [ -122.50668530503512, 37.771542352938617 ], [ -122.506685577396198, 37.771542340610623 ], [ -122.506991972993774, 37.771528822999358 ], [ -122.507750664399296, 37.771495034126787 ], [ -122.507750665782908, 37.771495034103232 ], [ -122.508780633594597, 37.771447480819759 ], [ -122.508823169175031, 37.771445503888728 ], [ -122.508823330922013, 37.771445496187766 ], [ -122.508867990729883, 37.771443447055226 ], [ -122.509894749698645, 37.771396032095495 ], [ -122.509894781266311, 37.771396329054475 ], [ -122.511053819156686, 37.771347546731022 ], [ -122.511055400217217, 37.771347479926334 ], [ -122.512571364940968, 37.771283657974607 ], [ -122.51313192303401, 37.771260053409627 ], [ -122.51314077587368, 37.771331323581165 ], [ -122.513103639776702, 37.771429475690731 ], [ -122.513116974838326, 37.771538439951165 ], [ -122.5131111559662, 37.771706791559524 ], [ -122.513101727800077, 37.771741976505247 ], [ -122.513108516285712, 37.771800920539754 ], [ -122.513097415135022, 37.771902061732895 ], [ -122.513158379126494, 37.77223615155534 ], [ -122.513150048202618, 37.772247968474822 ], [ -122.513145175599931, 37.772259726057939 ], [ -122.513142070416649, 37.772272827219133 ], [ -122.513140694405962, 37.772285899124284 ], [ -122.513142777404809, 37.77229891140734 ], [ -122.51314831943732, 37.772311864891819 ], [ -122.513155553774638, 37.772323415987366 ], [ -122.513162806726825, 37.772335653782996 ], [ -122.513171807785298, 37.772348547918142 ], [ -122.513179470052052, 37.772375886809904 ], [ -122.513181590285754, 37.772390272493183 ], [ -122.513180232870297, 37.772404030275354 ], [ -122.513177164205956, 37.772418504300951 ], [ -122.513164035088622, 37.772444825057953 ], [ -122.513153956021, 37.772455985088065 ], [ -122.513145624725894, 37.77246780201267 ], [ -122.513139041542303, 37.772480275551658 ], [ -122.513134206485844, 37.772493406254469 ], [ -122.513132830469672, 37.772506478159016 ], [ -122.513133202560653, 37.772520206404018 ], [ -122.513137015097556, 37.772533189699409 ], [ -122.513142557139105, 37.772546142909071 ], [ -122.513149791498321, 37.772557694004483 ], [ -122.513128650263695, 37.772862969521874 ], [ -122.513143974877181, 37.772917647305441 ], [ -122.513134546565453, 37.772952831971672 ], [ -122.513140759176039, 37.773054363934854 ], [ -122.513127629925961, 37.773080684410282 ], [ -122.513121083909837, 37.773094531347873 ], [ -122.513117996938973, 37.773108318663766 ], [ -122.513113180104355, 37.773122136070647 ], [ -122.513110502783931, 37.773151024750284 ], [ -122.513109145348821, 37.773164782804365 ], [ -122.513109926760848, 37.773193612966814 ], [ -122.513112046989562, 37.773207998100119 ], [ -122.513115877809639, 37.773221667551859 ], [ -122.513119727927929, 37.77323602341751 ], [ -122.513123558750877, 37.773249692868944 ], [ -122.513130848990954, 37.773263303241755 ], [ -122.513136372841529, 37.773275570567371 ], [ -122.513136837962776, 37.773292730939929 ], [ -122.513139032281771, 37.773309862056486 ], [ -122.513139962541018, 37.773344183350439 ], [ -122.513137434071865, 37.77337856371112 ], [ -122.513137899208886, 37.773395724632501 ], [ -122.51313663480029, 37.773412914815644 ], [ -122.513133622247153, 37.773429448109219 ], [ -122.513132357829548, 37.773446638017631 ], [ -122.513129363533338, 37.773463857742755 ], [ -122.513124956674076, 37.773492776231812 ], [ -122.513123636099678, 37.773507907142864 ], [ -122.513125756700802, 37.773522292818349 ], [ -122.513127895547086, 37.773537364376381 ], [ -122.513130108829742, 37.773555182186904 ], [ -122.51313457258631, 37.77359218983559 ], [ -122.513137102897502, 37.773685543750098 ], [ -122.51313464849045, 37.773722669817865 ], [ -122.513133402680765, 37.77374054642582 ], [ -122.513130445590789, 37.773759139276294 ], [ -122.513129218723165, 37.773777702029449 ], [ -122.513126261969134, 37.773796294599215 ], [ -122.513121575665878, 37.773814916705028 ], [ -122.513118599957409, 37.773832822854594 ], [ -122.513113913995852, 37.773851444954062 ], [ -122.513110938637425, 37.773869351371971 ], [ -122.513106251972374, 37.773887973208282 ], [ -122.513098199929146, 37.77391008650784 ], [ -122.513115558504083, 37.774550522030069 ], [ -122.513135161617555, 37.77469921031755 ], [ -122.513164585532962, 37.774890995995399 ], [ -122.513170483591551, 37.775108592933201 ], [ -122.513148799533084, 37.77520236034195 ], [ -122.513166877896921, 37.775294761967629 ], [ -122.513138108336719, 37.775382469681844 ], [ -122.513154511511672, 37.775413093012311 ], [ -122.513118656446565, 37.775494741304421 ], [ -122.513116560550898, 37.775991978980805 ], [ -122.513220716743945, 37.77664260637399 ], [ -122.513262136135666, 37.776766199546913 ], [ -122.513273147342474, 37.776853227885702 ], [ -122.513301250635152, 37.776996277263876 ], [ -122.513336088063554, 37.777196211308791 ], [ -122.513350113009579, 37.777330573444466 ], [ -122.513418927986152, 37.77744339753194 ], [ -122.513485622738756, 37.777541836186963 ], [ -122.513511493535475, 37.777602514461641 ], [ -122.513605787519069, 37.777633867571893 ], [ -122.513789576977686, 37.777711077145469 ], [ -122.513970390041706, 37.777742325491253 ], [ -122.514130469394843, 37.777902348781886 ], [ -122.514271487815051, 37.77812519117407 ], [ -122.514413862528215, 37.778270408245596 ], [ -122.514604595643718, 37.77847591891544 ], [ -122.51462964838835, 37.778506394234036 ], [ -122.514661065627067, 37.778643892998048 ], [ -122.514636184118331, 37.778747329407956 ], [ -122.514564452996041, 37.778846072735902 ], [ -122.514578405995934, 37.778977688986053 ], [ -122.51449838155861, 37.779153489214309 ], [ -122.5144938833634, 37.779242842682805 ], [ -122.514567855611702, 37.77941807161681 ], [ -122.514670725915678, 37.779446530659044 ], [ -122.514854558211539, 37.779525111397355 ], [ -122.514948079566921, 37.779719234525949 ], [ -122.514897941861832, 37.779848512679017 ], [ -122.514749765647835, 37.779872333834206 ], [ -122.514641072918508, 37.779820625636908 ], [ -122.514565266573783, 37.77989677577483 ], [ -122.514533951466817, 37.780082044762075 ], [ -122.514511340895595, 37.780269225221183 ], [ -122.514474054726847, 37.780425753002312 ], [ -122.51446673230646, 37.780602371059075 ], [ -122.514648527113849, 37.780860913612862 ], [ -122.51472341591213, 37.781133644263413 ], [ -122.514586047991969, 37.781300809991649 ], [ -122.514468426928062, 37.781302819849991 ], [ -122.514292998946104, 37.781279034300063 ], [ -122.514303621052633, 37.781351647553429 ], [ -122.514284968188448, 37.781429568185636 ], [ -122.514242655950966, 37.781464628517519 ], [ -122.513867881152649, 37.781428452669296 ], [ -122.513760174171992, 37.78147699096975 ], [ -122.513883322187127, 37.781614982927096 ], [ -122.513812238767187, 37.78173775061245 ], [ -122.513697000270085, 37.781891489394212 ], [ -122.513670014693673, 37.78198122658408 ], [ -122.513522169917209, 37.782081269425682 ], [ -122.513466336763443, 37.782192101792845 ], [ -122.513546762127802, 37.782286185176545 ], [ -122.513589561336119, 37.782396706497991 ], [ -122.513504004159799, 37.782432504856651 ], [ -122.51345966482603, 37.782520478406092 ], [ -122.513490615272289, 37.782576949453883 ], [ -122.51349907897729, 37.782633804536587 ], [ -122.513499154475824, 37.782700417272132 ], [ -122.513444212427984, 37.782780331251374 ], [ -122.513311970586869, 37.782817613200059 ], [ -122.513176844047379, 37.782748499450129 ], [ -122.513099674895386, 37.782774539876293 ], [ -122.513082211268937, 37.782896391754747 ], [ -122.512985625061674, 37.783036075867919 ], [ -122.513019551763222, 37.783138507955194 ], [ -122.513026396706579, 37.783199511429288 ], [ -122.512878567398928, 37.783236372275823 ], [ -122.512854035372925, 37.783352850568228 ], [ -122.512805434943459, 37.783475233699562 ], [ -122.512705462306386, 37.783553855572684 ], [ -122.512518627902452, 37.783620225547125 ], [ -122.512466567525493, 37.783742667306889 ], [ -122.512644903191074, 37.783873537806549 ], [ -122.512713964283492, 37.783931418677646 ], [ -122.512652381073622, 37.784021746473734 ], [ -122.512681006779928, 37.784120148680003 ], [ -122.512565948104239, 37.784153016218092 ], [ -122.512295468592697, 37.783942683042248 ], [ -122.512111442218099, 37.7837933668308 ], [ -122.511916645881882, 37.783949148187006 ], [ -122.511936379260646, 37.784102641671865 ], [ -122.512002036912364, 37.7841626409828 ], [ -122.51201098319315, 37.784237343198853 ], [ -122.511976089219957, 37.784290817818842 ], [ -122.511911287292179, 37.784326260764388 ], [ -122.511796674312308, 37.784311734732256 ], [ -122.511643280924901, 37.784334954352396 ], [ -122.511486279442096, 37.784288874386938 ], [ -122.511300968574147, 37.78428379521467 ], [ -122.511334223422679, 37.784425383212891 ], [ -122.511306545033449, 37.784553589399493 ], [ -122.511229614678896, 37.784588552121491 ], [ -122.51102434727656, 37.784485608568794 ], [ -122.510971002039966, 37.784496819600761 ], [ -122.510838401528204, 37.784584923981406 ], [ -122.510747296660981, 37.784671633775218 ], [ -122.510491862112929, 37.784697278857706 ], [ -122.510370776300974, 37.784699343616424 ], [ -122.510236724687886, 37.784733906185757 ], [ -122.510168389422006, 37.784702794445394 ], [ -122.510020323714343, 37.784723420804994 ], [ -122.509994254968106, 37.784727052267002 ], [ -122.509808028924709, 37.784816069670107 ], [ -122.509716922917022, 37.784902778658349 ], [ -122.509665899527562, 37.784999792217178 ], [ -122.509574233553352, 37.78512977535155 ], [ -122.509483405510977, 37.78522678080374 ], [ -122.50935080157798, 37.785314883231258 ], [ -122.509270075574662, 37.785337547981477 ], [ -122.509233168789549, 37.785444621876991 ], [ -122.509117710773396, 37.785590805131108 ], [ -122.508796514633275, 37.785744614237508 ], [ -122.508661532747013, 37.785744853745925 ], [ -122.508565980602214, 37.785795240243836 ], [ -122.508486275275231, 37.785855657828165 ], [ -122.50845217664569, 37.785938647661681 ], [ -122.508258558155333, 37.786074486851589 ], [ -122.50824179847983, 37.786094687819457 ], [ -122.508218230136194, 37.78611912520401 ], [ -122.508132741023516, 37.786157665329803 ], [ -122.508102962554631, 37.786144437693665 ], [ -122.508035220497703, 37.78613529033418 ], [ -122.507963402235234, 37.786167416830274 ], [ -122.507918608664951, 37.786238914034136 ], [ -122.507884921314982, 37.786273138055307 ], [ -122.507810998339579, 37.786355432403496 ], [ -122.507791149628716, 37.786389420730835 ], [ -122.507786702460081, 37.786416966109108 ], [ -122.507753742643061, 37.786414093677742 ], [ -122.507723143657557, 37.786434530234523 ], [ -122.507686905785704, 37.786502447942446 ], [ -122.50771513615085, 37.786586436657288 ], [ -122.507634122264022, 37.786726537606768 ], [ -122.507529970492527, 37.786842996798164 ], [ -122.507416802174745, 37.786882007667536 ], [ -122.507337150418977, 37.786944484013162 ], [ -122.507251469686537, 37.787040025961154 ], [ -122.50719904571541, 37.787149423517022 ], [ -122.507107186903028, 37.787272540712486 ], [ -122.507053654233644, 37.787276885429115 ], [ -122.506971891727119, 37.787453396158277 ], [ -122.506896872053431, 37.78749519083452 ], [ -122.50673936089315, 37.787494438084011 ], [ -122.506717248362293, 37.787444682355584 ], [ -122.506653688773341, 37.787462245895419 ], [ -122.506526049480186, 37.787478152978885 ], [ -122.506492546406875, 37.787519241120947 ], [ -122.506427084531083, 37.787594522862221 ], [ -122.506343785965981, 37.787650192878154 ], [ -122.506284872420196, 37.787711628562818 ], [ -122.506271997783998, 37.787747558158287 ], [ -122.506258863502353, 37.787773877799495 ], [ -122.50624035413243, 37.787793421492793 ], [ -122.506219873800518, 37.787804071365088 ], [ -122.506177628287418, 37.787841873914068 ], [ -122.506131292484596, 37.787856397132735 ], [ -122.506163876746712, 37.787909408764172 ], [ -122.506122372558949, 37.787974668305665 ], [ -122.506121008614969, 37.788052293211784 ], [ -122.506089600492331, 37.788170947004069 ], [ -122.506052098981115, 37.788192187283286 ], [ -122.505852340805589, 37.788229236102978 ], [ -122.505747263058424, 37.788247505451196 ], [ -122.505657587040744, 37.788259332046266 ], [ -122.505605226952582, 37.788243054476361 ], [ -122.505520554840288, 37.788247928168715 ], [ -122.505423328958457, 37.788236533825454 ], [ -122.505330267621474, 37.78825116471306 ], [ -122.505202440987361, 37.788260206114757 ], [ -122.505107748523827, 37.788214431400775 ], [ -122.505021260444238, 37.788152035322256 ], [ -122.50498134335804, 37.788147906935059 ], [ -122.504941407743146, 37.788143092108974 ], [ -122.504896245414301, 37.788136992665024 ], [ -122.504877161086682, 37.788135256957062 ], [ -122.504859843370596, 37.788134864962309 ], [ -122.504835625333357, 37.788135276762382 ], [ -122.504801138826153, 37.7881399833256 ], [ -122.504785514269201, 37.788138188774688 ], [ -122.504771508408268, 37.788132246255898 ], [ -122.504759158308161, 37.788123528620481 ], [ -122.5047502309164, 37.788113379306509 ], [ -122.504739555111996, 37.788102572979582 ], [ -122.504727204674595, 37.788093855346673 ], [ -122.504711432227211, 37.788086569376937 ], [ -122.504695715029484, 37.788081342687157 ], [ -122.504680053426, 37.788078175271465 ], [ -122.50466269900177, 37.788076410116517 ], [ -122.50464541869988, 37.788077390660611 ], [ -122.504626408521617, 37.788078400888217 ], [ -122.504609090805303, 37.788078008307494 ], [ -122.504591755258659, 37.788076929561612 ], [ -122.504576093667978, 37.788073762406697 ], [ -122.504558683651112, 37.788069937685613 ], [ -122.504543003526479, 37.788066083826294 ], [ -122.504525593174549, 37.788062259380773 ], [ -122.504508220574564, 37.788059807771766 ], [ -122.50449088434911, 37.788058729297397 ], [ -122.504468396217547, 37.788059111612547 ], [ -122.504456509027335, 37.788067554312491 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":5,\"ZIP_CODE\":94118,\"ID\":94118},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.448885763577039, 37.778388598994987 ], [ -122.449767079183658, 37.778277008331997 ], [ -122.450654812483819, 37.778164598609756 ], [ -122.450826415883952, 37.778142868140662 ], [ -122.451543139139602, 37.778052106408275 ], [ -122.452431111623397, 37.777939652339285 ], [ -122.453384996187339, 37.777826863341367 ], [ -122.45318832797058, 37.776853425180988 ], [ -122.452963825070867, 37.775750129254348 ], [ -122.452810237317621, 37.774995318605924 ], [ -122.453003330637316, 37.774970754669006 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.454683295611602, 37.774755406979118 ], [ -122.455033749574639, 37.77471323536281 ], [ -122.45525254468275, 37.774686906456331 ], [ -122.455898178089939, 37.774604132119862 ], [ -122.456283639851293, 37.774554711848936 ], [ -122.456283671603558, 37.774554866249353 ], [ -122.456283917173579, 37.774554834704738 ], [ -122.457134751103212, 37.774454490912483 ], [ -122.458371099123696, 37.774308895694055 ], [ -122.458795325509186, 37.774254885425997 ], [ -122.459486118442314, 37.77416693399686 ], [ -122.459486145343419, 37.77416729779403 ], [ -122.459486938645284, 37.774167197254911 ], [ -122.460552700773292, 37.774031358657659 ], [ -122.460552661960989, 37.774031133779268 ], [ -122.461619755154771, 37.7738952534419 ], [ -122.461619775372085, 37.773895534117642 ], [ -122.461620188227315, 37.77389548164836 ], [ -122.462535340858068, 37.773778657528119 ], [ -122.462683796526321, 37.773759751788596 ], [ -122.463749210177951, 37.773624065149079 ], [ -122.464811670328331, 37.773488744378348 ], [ -122.465331676538511, 37.773422509669793 ], [ -122.465883163277454, 37.773398204721545 ], [ -122.466373346760548, 37.773376599548058 ], [ -122.466951728424362, 37.773351356945881 ], [ -122.466952500298319, 37.773351323464517 ], [ -122.468023916207073, 37.773303833556405 ], [ -122.468348575135167, 37.773289518371953 ], [ -122.469092496140973, 37.773256712542192 ], [ -122.469517084069324, 37.773237987003441 ], [ -122.470162692059887, 37.773209510586071 ], [ -122.470162712316579, 37.773209791534974 ], [ -122.471240089434815, 37.773162280380667 ], [ -122.47124006836178, 37.773161981865066 ], [ -122.471762122569857, 37.773138947752848 ], [ -122.472219951938214, 37.773117969260745 ], [ -122.472297341321692, 37.773114423074823 ], [ -122.473427836194134, 37.773062613687692 ], [ -122.473737577092777, 37.773048416620476 ], [ -122.474550910293516, 37.773011133644289 ], [ -122.474550931211951, 37.773011425844139 ], [ -122.47455168367884, 37.773011391539193 ], [ -122.475612802305008, 37.772962709520556 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475750968701433, 37.774828017958995 ], [ -122.475888270951586, 37.776692974324831 ], [ -122.476025278884237, 37.778553841183147 ], [ -122.476169188584777, 37.78047585000003 ], [ -122.476306551695615, 37.782405954593735 ], [ -122.476436803741322, 37.784271125057622 ], [ -122.476575148769683, 37.786128282045887 ], [ -122.476575166016133, 37.786128513873635 ], [ -122.476572244768761, 37.786128658440397 ], [ -122.476637454822679, 37.786966661899442 ], [ -122.47616220885665, 37.786978730627588 ], [ -122.475567725323828, 37.787005248954515 ], [ -122.475136959985235, 37.787024462123846 ], [ -122.474584006514732, 37.787049123020516 ], [ -122.474432824368861, 37.787072820298278 ], [ -122.473333621918172, 37.787245111436654 ], [ -122.473316434470121, 37.787247805418339 ], [ -122.472782224474642, 37.787331534391747 ], [ -122.47254342327507, 37.787368961705944 ], [ -122.472401902490546, 37.787384046501884 ], [ -122.472290534181823, 37.787395917162272 ], [ -122.468653534340191, 37.787783523544832 ], [ -122.466942641213976, 37.788133153852705 ], [ -122.465882841138949, 37.788349715761562 ], [ -122.4648365613821, 37.788563504283978 ], [ -122.463771243725489, 37.788781172337565 ], [ -122.459781586189493, 37.789596250165474 ], [ -122.45947565027717, 37.789710559160014 ], [ -122.459250455476877, 37.789713291862398 ], [ -122.459028627866218, 37.789715983297555 ], [ -122.459011873160264, 37.789719335764374 ], [ -122.457498733292852, 37.789996912625632 ], [ -122.455878319348855, 37.790294143976332 ], [ -122.454248357567437, 37.79059310209788 ], [ -122.452035629818212, 37.790998907609811 ], [ -122.450995459060991, 37.791209906278311 ], [ -122.449385188501296, 37.791484927305568 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.449189583391288, 37.790608108274284 ], [ -122.449011564525605, 37.789726786022129 ], [ -122.448835781299294, 37.788856511426609 ], [ -122.448656220796209, 37.787977164570449 ], [ -122.4469741664482, 37.788186460801001 ], [ -122.446792779960703, 37.787261853518181 ], [ -122.446610465639836, 37.786302298311156 ], [ -122.446738337622591, 37.786243163870765 ], [ -122.446849274413296, 37.786186897081585 ], [ -122.447533801639068, 37.785411415165207 ], [ -122.447605133356163, 37.785275579171099 ], [ -122.447621599012791, 37.785194684567394 ], [ -122.447620107068687, 37.785083635258417 ], [ -122.447578549915917, 37.784998355250835 ], [ -122.447549635601661, 37.784906798974511 ], [ -122.447149436147441, 37.783030677550911 ], [ -122.447173033991248, 37.782710075581413 ], [ -122.447225622636566, 37.782575775503396 ], [ -122.447283893261542, 37.782434393634787 ], [ -122.447301513844337, 37.78239164221629 ], [ -122.447450898997985, 37.782195217632704 ], [ -122.447637300982407, 37.781719177617617 ], [ -122.447655108274333, 37.781537309189886 ], [ -122.447576361567485, 37.781175778551805 ], [ -122.447517982733274, 37.781069510753063 ], [ -122.447351927163439, 37.780152061280916 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.447997721370214, 37.778501033647089 ], [ -122.448879206948348, 37.778389428728616 ], [ -122.448885763577039, 37.778388598994987 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":6,\"ZIP_CODE\":94123,\"ID\":94123},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.438402484723895, 37.794806973138577 ], [ -122.440045216960385, 37.794597761146619 ], [ -122.440868123190498, 37.794493055920753 ], [ -122.440870170501768, 37.794492795601883 ], [ -122.441712771485612, 37.794385578392699 ], [ -122.443384198876302, 37.794172877898092 ], [ -122.445035345747925, 37.79396273368863 ], [ -122.446587034424141, 37.793765298555968 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446587862952981, 37.793765120337646 ], [ -122.446727777261131, 37.794706464759514 ], [ -122.446727777275527, 37.794706465308671 ], [ -122.446751148304088, 37.794858932799087 ], [ -122.44687098056005, 37.795640691997477 ], [ -122.446928896449293, 37.796018515191513 ], [ -122.447014738218328, 37.796578509648619 ], [ -122.447014738232738, 37.796578510197754 ], [ -122.44715829625072, 37.79751500492933 ], [ -122.44715829626513, 37.797515005478481 ], [ -122.447209582420982, 37.797849561838987 ], [ -122.447303043858923, 37.798459232650295 ], [ -122.447327769221047, 37.798519415660031 ], [ -122.447341437979304, 37.798552685345861 ], [ -122.447400275900733, 37.798948048802856 ], [ -122.447466030932901, 37.799389886853575 ], [ -122.447613279369918, 37.80032370169468 ], [ -122.447614227653389, 37.80032479990944 ], [ -122.447802865090509, 37.801525678628579 ], [ -122.447819572978844, 37.801633208279924 ], [ -122.447819731578562, 37.801633051558142 ], [ -122.447857007904148, 37.801596177616553 ], [ -122.447921599691895, 37.801532283988202 ], [ -122.448359651469772, 37.801586782043685 ], [ -122.448802640091088, 37.801772003872607 ], [ -122.449043488821943, 37.801965383441527 ], [ -122.449178591741017, 37.802073857413127 ], [ -122.449460220896157, 37.80245428555321 ], [ -122.449536106109235, 37.802697377072583 ], [ -122.449565462756809, 37.802791419060213 ], [ -122.449583713807527, 37.803152915295549 ], [ -122.449583722697156, 37.803153095621241 ], [ -122.449495662264184, 37.803470319431604 ], [ -122.449388062152011, 37.803710849923448 ], [ -122.449253815658196, 37.803861977230383 ], [ -122.449124213448584, 37.804007874392056 ], [ -122.448954929708933, 37.80412855834809 ], [ -122.448814547024654, 37.804228638340902 ], [ -122.448316879890498, 37.804444627382082 ], [ -122.448245764546613, 37.8044754914729 ], [ -122.448240723025904, 37.804477679722268 ], [ -122.448240728857812, 37.804477717258706 ], [ -122.44824552349904, 37.804508484024247 ], [ -122.448279145759457, 37.80472422506687 ], [ -122.448321161555853, 37.804993824977359 ], [ -122.448323332142422, 37.805007750605469 ], [ -122.448455258275729, 37.806133277363898 ], [ -122.448456716038393, 37.806145711376217 ], [ -122.448529100304583, 37.806763241657009 ], [ -122.448398315024306, 37.806792871575105 ], [ -122.448372486009177, 37.806798105392311 ], [ -122.448348405000459, 37.806803997055574 ], [ -122.44832261235274, 37.806810603719661 ], [ -122.448274523063262, 37.806825132736471 ], [ -122.448250514438882, 37.806833770101044 ], [ -122.448204281209826, 37.806853075532878 ], [ -122.448182039290586, 37.806863057432189 ], [ -122.448159833037835, 37.806874411921982 ], [ -122.448139357121818, 37.80688573782485 ], [ -122.448117186873532, 37.806898464895561 ], [ -122.448103560271292, 37.806906930735266 ], [ -122.447833857922845, 37.807052164926837 ], [ -122.447528276286675, 37.807250818574119 ], [ -122.447180260711605, 37.80747705551984 ], [ -122.446637954280746, 37.807525151443585 ], [ -122.446476742583258, 37.807516824572637 ], [ -122.445255735074412, 37.807626931568663 ], [ -122.444832951299077, 37.807611928844082 ], [ -122.444812115030089, 37.807609525524043 ], [ -122.444789548054757, 37.807607150739592 ], [ -122.443882698723485, 37.807700913966812 ], [ -122.443762319418525, 37.807713359922936 ], [ -122.443684632386478, 37.807721507559208 ], [ -122.442739197031372, 37.807965235732532 ], [ -122.442711389346584, 37.807972404225552 ], [ -122.442625660786931, 37.808004032657479 ], [ -122.442524054141913, 37.808024247618199 ], [ -122.442354908341343, 37.808043514915532 ], [ -122.441767928096084, 37.808104685164672 ], [ -122.44153654297466, 37.808127036445633 ], [ -122.441200035212404, 37.808167598495942 ], [ -122.44095148917647, 37.808195724629321 ], [ -122.44065270005224, 37.808288542686931 ], [ -122.440627067703531, 37.808301325835579 ], [ -122.440575873699515, 37.808329637315019 ], [ -122.440528212527582, 37.808360637280757 ], [ -122.440504418161254, 37.808377510253422 ], [ -122.440482353473598, 37.808394354762264 ], [ -122.440462037446096, 37.808411857223902 ], [ -122.440440008975642, 37.808430074859068 ], [ -122.440419728804599, 37.808448949904253 ], [ -122.440401196955307, 37.808468483183979 ], [ -122.440382683383717, 37.808488702887828 ], [ -122.440369270902693, 37.808505405024498 ], [ -122.440221248842576, 37.808670595014306 ], [ -122.440123020723803, 37.808753931536479 ], [ -122.440031607015868, 37.808833035512706 ], [ -122.439928060968157, 37.808779113775664 ], [ -122.440270312303127, 37.808229595265985 ], [ -122.440282155842453, 37.808219099502388 ], [ -122.440294017657806, 37.80820928989008 ], [ -122.440307609508267, 37.808199452089056 ], [ -122.440319488926107, 37.808190329461823 ], [ -122.440334864961784, 37.808182522213436 ], [ -122.440348510624844, 37.808174743706033 ], [ -122.440379298231178, 37.808160502620083 ], [ -122.440394728431826, 37.80815475493111 ], [ -122.440410176225527, 37.808149693678615 ], [ -122.440427371995767, 37.808145290667582 ], [ -122.440444586383592, 37.808141573526278 ], [ -122.440470613795014, 37.808143892481645 ], [ -122.440632371579539, 37.808106894262245 ], [ -122.440777543613365, 37.808097638147814 ], [ -122.441785658321223, 37.807988336182923 ], [ -122.441808099289275, 37.807985906519384 ], [ -122.441799274649981, 37.807913258896662 ], [ -122.441490202960892, 37.807943755875684 ], [ -122.441489843938655, 37.807930027497129 ], [ -122.441472521937172, 37.807929625920565 ], [ -122.441480832705722, 37.807916441000579 ], [ -122.441399010156047, 37.807832633902123 ], [ -122.441414331805831, 37.807822767220159 ], [ -122.441512177188741, 37.807923479293905 ], [ -122.44162439956051, 37.807912017357232 ], [ -122.441545678197272, 37.807814424464247 ], [ -122.441557557740268, 37.807805301431408 ], [ -122.441658935146762, 37.807908701878773 ], [ -122.441779792066384, 37.807896411186228 ], [ -122.441788084829597, 37.807882540086432 ], [ -122.441704477043643, 37.807796702127824 ], [ -122.441718086585212, 37.807787550596132 ], [ -122.44181950044613, 37.807892323765437 ], [ -122.441935183814792, 37.807880804821842 ], [ -122.441943476554428, 37.807866933985672 ], [ -122.441859851343693, 37.807780409692931 ], [ -122.441873460855945, 37.807771257868716 ], [ -122.441976640855827, 37.807877375557908 ], [ -122.441977107703579, 37.807895222530654 ], [ -122.441816542778795, 37.807911600852826 ], [ -122.441823637077619, 37.807984277513498 ], [ -122.442320834639688, 37.807932139640414 ], [ -122.442377595612399, 37.807918156721222 ], [ -122.442423345751081, 37.807880319904761 ], [ -122.442454552098894, 37.807815940203007 ], [ -122.442446930178193, 37.807789283331502 ], [ -122.442372794146465, 37.807800805256427 ], [ -122.442345413582302, 37.807812930576347 ], [ -122.442346006240967, 37.807835582850217 ], [ -122.442033707111619, 37.807875062158132 ], [ -122.442009243703154, 37.807733999234173 ], [ -122.442027954502791, 37.807721329750571 ], [ -122.442048400903616, 37.807841170516582 ], [ -122.442059070073554, 37.807851982482966 ], [ -122.442207469058346, 37.807833743991878 ], [ -122.442217473805144, 37.807819158213746 ], [ -122.442189585584913, 37.807679524627673 ], [ -122.44220852946296, 37.807675779293724 ], [ -122.442236490235743, 37.807818158319698 ], [ -122.442248872177956, 37.807828255059988 ], [ -122.442314446214397, 37.807820307862364 ], [ -122.442321008509211, 37.807806465496753 ], [ -122.442293084181387, 37.807665459069689 ], [ -122.442311884371406, 37.807656221984004 ], [ -122.442337612108403, 37.807779409372287 ], [ -122.442355041151117, 37.807783929703042 ], [ -122.442395115206907, 37.807727644711157 ], [ -122.442384613878218, 37.807657084644539 ], [ -122.44241012033585, 37.80763949635341 ], [ -122.442685954576561, 37.807595809313327 ], [ -122.442711892175168, 37.807793159839584 ], [ -122.442714242657956, 37.807793886686525 ], [ -122.442762503926971, 37.807808807618663 ], [ -122.442779771325021, 37.807807149719757 ], [ -122.442902339949939, 37.807794142945916 ], [ -122.442998755417392, 37.807774012900218 ], [ -122.44305537242154, 37.807754538469467 ], [ -122.44312779660163, 37.807743731320251 ], [ -122.443201717191158, 37.807723971451296 ], [ -122.443260064122327, 37.807704468417526 ], [ -122.443291085252142, 37.807699150180341 ], [ -122.443284905918816, 37.807661481932094 ], [ -122.443264195467847, 37.807663883640146 ], [ -122.443274020298077, 37.807642433173122 ], [ -122.443270056810988, 37.807623269807593 ], [ -122.44314383017317, 37.807628783225987 ], [ -122.443254016387542, 37.807605679164965 ], [ -122.44326198542305, 37.807579452177009 ], [ -122.443135758859114, 37.807584965586791 ], [ -122.443242483942598, 37.807561918560637 ], [ -122.443256021380762, 37.807550021121365 ], [ -122.443252111820613, 37.807532917601364 ], [ -122.443237999416624, 37.807522848927505 ], [ -122.443246112189684, 37.807502113399792 ], [ -122.443244040453081, 37.807489099968812 ], [ -122.443229963993403, 37.807480404158795 ], [ -122.443238040829058, 37.80745829576658 ], [ -122.443234130578546, 37.807441191982505 ], [ -122.443218162072228, 37.807426347064215 ], [ -122.443264702406339, 37.807418712940589 ], [ -122.443256517776859, 37.807436702740098 ], [ -122.443274647533997, 37.807467993526231 ], [ -122.44326455321054, 37.80747914750733 ], [ -122.443284610988869, 37.807517960537304 ], [ -122.443274498692247, 37.807528428086727 ], [ -122.443278390296598, 37.807544845448142 ], [ -122.443294466994871, 37.807563808946817 ], [ -122.443284389944367, 37.807575649647809 ], [ -122.443286551537625, 37.807592095514579 ], [ -122.443302628241014, 37.807611058737429 ], [ -122.443292551178217, 37.80762289916445 ], [ -122.443296353650368, 37.807635884626045 ], [ -122.443302173270908, 37.807659823956676 ], [ -122.443308298708217, 37.807695432906783 ], [ -122.443434058250361, 37.807672072344801 ], [ -122.443672727711146, 37.807597406163168 ], [ -122.443654441112585, 37.80749401124762 ], [ -122.443659326683914, 37.80748225662078 ], [ -122.443872884696731, 37.807440280106206 ], [ -122.443843293810986, 37.807367974319583 ], [ -122.44370731452436, 37.807397684591926 ], [ -122.443671444290359, 37.807416130974829 ], [ -122.443653888762242, 37.807406805822907 ], [ -122.443615857187666, 37.807408806048031 ], [ -122.443601726794256, 37.807398051259632 ], [ -122.443646374280576, 37.807384267621238 ], [ -122.443657912040536, 37.807362102160489 ], [ -122.443677395248955, 37.807378949282132 ], [ -122.443708397548889, 37.807372944513865 ], [ -122.443719917316642, 37.807350092614648 ], [ -122.443739400535989, 37.807366939726066 ], [ -122.443768690788104, 37.807361649886793 ], [ -122.443780246134452, 37.8073401708521 ], [ -122.443799711046125, 37.807356331526904 ], [ -122.443829001635834, 37.807351041666962 ], [ -122.443838790315425, 37.807328218286891 ], [ -122.443858291875273, 37.807345751805421 ], [ -122.443944431867095, 37.807329910704055 ], [ -122.443955915261199, 37.807305685922557 ], [ -122.44397716482635, 37.807323877338213 ], [ -122.444023687043469, 37.8073155564826 ], [ -122.444033511615373, 37.807294106225484 ], [ -122.444052976916183, 37.807310266852966 ], [ -122.444104672197867, 37.807301173690028 ], [ -122.444114316981555, 37.807272858828142 ], [ -122.444139117510247, 37.80729442532332 ], [ -122.444173580792466, 37.807288363378881 ], [ -122.444194201234893, 37.80728252962394 ], [ -122.444071355206816, 37.80715270277112 ], [ -122.444216516082363, 37.807275294750141 ], [ -122.444262912050363, 37.807262168504906 ], [ -122.444272682607831, 37.807238658656139 ], [ -122.444290381984246, 37.807253474900222 ], [ -122.444329892128806, 37.807241835878372 ], [ -122.444341267517572, 37.807213492465038 ], [ -122.444362535128633, 37.807232370243902 ], [ -122.44440375797123, 37.807220016231902 ], [ -122.444415151652876, 37.807192359237938 ], [ -122.444436401305339, 37.807210550845831 ], [ -122.444546334776021, 37.807177835248929 ], [ -122.444557764011307, 37.807151551111282 ], [ -122.444577265002437, 37.807169084522954 ], [ -122.444645975607855, 37.807148723180354 ], [ -122.444656807573693, 37.807165712792234 ], [ -122.444442095756031, 37.807229685090327 ], [ -122.444483781319875, 37.807301104324431 ], [ -122.444798026960797, 37.807203901149613 ], [ -122.445256739502199, 37.807070664930905 ], [ -122.445225848727588, 37.807014862399406 ], [ -122.445091259282876, 37.807031503517287 ], [ -122.445058382323012, 37.807032045740513 ], [ -122.44505631062799, 37.807019032334502 ], [ -122.445158131368714, 37.807007052095038 ], [ -122.44517833798561, 37.806985429952121 ], [ -122.445116858617496, 37.806951420767781 ], [ -122.445030629048986, 37.806963830570979 ], [ -122.445058875964975, 37.806918727340673 ], [ -122.445006173970214, 37.806889380441703 ], [ -122.444888888156058, 37.806905735960221 ], [ -122.444874654156408, 37.80682493696483 ], [ -122.444919463246819, 37.806817330453363 ], [ -122.445007311205075, 37.806866699909307 ], [ -122.444974402625263, 37.806799942930326 ], [ -122.444993292526533, 37.806794137571423 ], [ -122.445038942963123, 37.806884719639513 ], [ -122.445121510119179, 37.806930742460146 ], [ -122.445046609432765, 37.806780897144854 ], [ -122.445065498977499, 37.806775091780075 ], [ -122.445153141586133, 37.806948762165227 ], [ -122.44523217658535, 37.806992095976476 ], [ -122.44535586222473, 37.80695571941591 ], [ -122.445247620593648, 37.806722643522114 ], [ -122.445266510453877, 37.806716838119371 ], [ -122.445374751789728, 37.806949914001784 ], [ -122.445469219634987, 37.806921573283674 ], [ -122.445352433919155, 37.8066927587953 ], [ -122.445373036116507, 37.806686238401404 ], [ -122.44548810952854, 37.806915767845673 ], [ -122.445561992695104, 37.80689463390474 ], [ -122.445445188748025, 37.806665133074851 ], [ -122.445464060244248, 37.8066586412139 ], [ -122.445580882228484, 37.806888828457545 ], [ -122.44570454953481, 37.806851765098479 ], [ -122.445651671462073, 37.806749628119896 ], [ -122.44565282578796, 37.806727633749716 ], [ -122.445895754405569, 37.806815647477016 ], [ -122.445736019894767, 37.806863607021306 ], [ -122.445754222526631, 37.806897643432812 ], [ -122.446384760166481, 37.806715556411739 ], [ -122.446258463322081, 37.806718327435846 ], [ -122.446193213892712, 37.806738632616792 ], [ -122.446170395914976, 37.806726648309962 ], [ -122.445981443875837, 37.806782644077451 ], [ -122.445970540045622, 37.80676290912637 ], [ -122.445598235532685, 37.806626211997774 ], [ -122.445606509444289, 37.806611653919497 ], [ -122.446001524256076, 37.806756217316881 ], [ -122.446149253941641, 37.806712575605459 ], [ -122.445708764584751, 37.806550221539489 ], [ -122.445717038134546, 37.806535664008109 ], [ -122.446176741453044, 37.806704568267236 ], [ -122.446283248720263, 37.80667328102048 ], [ -122.446005194935296, 37.806500006546784 ], [ -122.446020461726192, 37.80648808024214 ], [ -122.446305581169554, 37.806666731908173 ], [ -122.446420668040176, 37.806632556303882 ], [ -122.446151409366294, 37.806464630848126 ], [ -122.446166694812902, 37.806453390945222 ], [ -122.446443000465095, 37.806626007165498 ], [ -122.446554662537906, 37.806593261684583 ], [ -122.446391023698155, 37.806492266360543 ], [ -122.446406290771549, 37.806480340000242 ], [ -122.446524184932926, 37.806553247425605 ], [ -122.446620237835958, 37.806519385773647 ], [ -122.446642693746213, 37.806583567659246 ], [ -122.44670978090322, 37.806567352265269 ], [ -122.446939773843297, 37.806492135834091 ], [ -122.446948929215651, 37.806511213110113 ], [ -122.446720738906919, 37.806589146719396 ], [ -122.446733553252031, 37.806615717634571 ], [ -122.446996260681743, 37.80653378052115 ], [ -122.44699045975274, 37.806444601492437 ], [ -122.446974562378131, 37.806432503088267 ], [ -122.446987792972394, 37.806408935599343 ], [ -122.446983774594216, 37.806387713332704 ], [ -122.44696966191627, 37.806377645652319 ], [ -122.446979503500458, 37.806356881028954 ], [ -122.446973448766144, 37.806324017983009 ], [ -122.446942374816217, 37.806327277888172 ], [ -122.44695713738291, 37.806296131639499 ], [ -122.446941332573715, 37.806221538794638 ], [ -122.446922047288439, 37.806212242955326 ], [ -122.446935223870369, 37.806186616724325 ], [ -122.446919473110199, 37.806114083171188 ], [ -122.446903683817155, 37.806106103074534 ], [ -122.446913508081977, 37.806084652286692 ], [ -122.446909453048207, 37.80606205716353 ], [ -122.446700318081568, 37.806074437190453 ], [ -122.446888239127603, 37.80604523913599 ], [ -122.446903488381423, 37.806032626547122 ], [ -122.446899451700631, 37.806010717849745 ], [ -122.446881914446337, 37.806002079602202 ], [ -122.446893486335398, 37.805981286694831 ], [ -122.446879430339621, 37.805907352248376 ], [ -122.446670313096035, 37.805920418116813 ], [ -122.446859964791457, 37.805891192083081 ], [ -122.446873519346127, 37.805879980657039 ], [ -122.446859427027746, 37.80580467279912 ], [ -122.446845404479788, 37.805798037263322 ], [ -122.446855228367411, 37.805776586210264 ], [ -122.446849425403357, 37.805753333484326 ], [ -122.446649086514796, 37.805771061515294 ], [ -122.446833420536592, 37.805737116186556 ], [ -122.446845280397781, 37.805727305922368 ], [ -122.446841244096817, 37.805705397216052 ], [ -122.446823724582785, 37.805697445671754 ], [ -122.446835297139998, 37.805676653032066 ], [ -122.446831332492664, 37.805657489784885 ], [ -122.446817327967949, 37.805651540403126 ], [ -122.446825403536536, 37.805629431757723 ], [ -122.446805830072663, 37.805609152989824 ], [ -122.446817348267402, 37.805586300786253 ], [ -122.446809113007006, 37.805536305492353 ], [ -122.446789863569947, 37.805528382500277 ], [ -122.446803112068949, 37.805505501734793 ], [ -122.446787107265692, 37.805489284429996 ], [ -122.446700807332093, 37.805498949734627 ], [ -122.446698645389645, 37.80548250391827 ], [ -122.446795308825045, 37.805471980811141 ], [ -122.446771068810406, 37.805405768207393 ], [ -122.446462225224153, 37.805444515694951 ], [ -122.445989505497494, 37.805505194954378 ], [ -122.446003707846955, 37.805518695189633 ], [ -122.4460087518259, 37.805579044127001 ], [ -122.446547025898894, 37.805509728799308 ], [ -122.446550972467278, 37.805528205624398 ], [ -122.446518204506802, 37.805532866567553 ], [ -122.446489185128556, 37.80554845357441 ], [ -122.446462996481614, 37.80553995860523 ], [ -122.446437419711515, 37.805554801776253 ], [ -122.446402597499727, 37.805547136014376 ], [ -122.446377002372941, 37.805561292745764 ], [ -122.446359483641928, 37.805553341395452 ], [ -122.446335312891549, 37.805555800213412 ], [ -122.446311412770896, 37.805568555772986 ], [ -122.446295642000578, 37.805561262020625 ], [ -122.44627666253902, 37.805563635428271 ], [ -122.446249319434344, 37.805577134798497 ], [ -122.446231800006771, 37.805569182891531 ], [ -122.446148996869667, 37.805580163541499 ], [ -122.446123402382483, 37.805594320480907 ], [ -122.446104135009989, 37.805585710944754 ], [ -122.44608171290129, 37.805588827858948 ], [ -122.44605783038071, 37.805602270079603 ], [ -122.446038580653479, 37.805594346696139 ], [ -122.446017870868147, 37.805596748888597 ], [ -122.445994006657656, 37.805610876973709 ], [ -122.445976469264281, 37.80560223887116 ], [ -122.445950586862267, 37.805605412856671 ], [ -122.445928452266116, 37.805619512663846 ], [ -122.445909185255772, 37.805610903090184 ], [ -122.445885032816534, 37.805614048516972 ], [ -122.445861132260902, 37.80562680426543 ], [ -122.445845361169205, 37.805619510184115 ], [ -122.445814305801534, 37.805623456219109 ], [ -122.44579215352951, 37.805636869562669 ], [ -122.445772886191605, 37.805628260247033 ], [ -122.445750464048956, 37.80563137682369 ], [ -122.445724833141924, 37.805644160817579 ], [ -122.445709062413187, 37.805636866986909 ], [ -122.445683179641605, 37.805640040919442 ], [ -122.445655872372569, 37.805654912741964 ], [ -122.445638335008098, 37.805646274589208 ], [ -122.445605549302542, 37.805650249115246 ], [ -122.445581648680047, 37.80566300453242 ], [ -122.445565859972788, 37.805655024250306 ], [ -122.445531361592955, 37.805659714013281 ], [ -122.445505730631226, 37.805672497684945 ], [ -122.445489959918007, 37.805665203825008 ], [ -122.445462346810999, 37.80566840625292 ], [ -122.445436715841595, 37.805681190184124 ], [ -122.445420945133293, 37.805673896314971 ], [ -122.445391619690682, 37.8056778137015 ], [ -122.445362527733508, 37.805690654981625 ], [ -122.445346757369364, 37.8056833608223 ], [ -122.44531225895534, 37.805688050246587 ], [ -122.445290052615164, 37.805699404198599 ], [ -122.445276030220143, 37.805692768201546 ], [ -122.445243244128591, 37.805696742632556 ], [ -122.445217576794818, 37.805708153657022 ], [ -122.445203554404628, 37.805701517651372 ], [ -122.445170786636353, 37.80570617848889 ], [ -122.445148507984058, 37.805714786690388 ], [ -122.445105232410981, 37.805714813715646 ], [ -122.445083008046993, 37.805725481196227 ], [ -122.44507071598305, 37.80571881663689 ], [ -122.445037947513853, 37.80572347772349 ], [ -122.445017471439073, 37.805734803086857 ], [ -122.445001701099429, 37.805727508881461 ], [ -122.444908551660831, 37.805740032744836 ], [ -122.44488456097811, 37.805749355858453 ], [ -122.444846440036883, 37.805747924599146 ], [ -122.444825945937126, 37.805758563222234 ], [ -122.444811923572402, 37.805751927170057 ], [ -122.44478259772076, 37.805755844410804 ], [ -122.44476037365142, 37.805766512099389 ], [ -122.444713600749751, 37.805765222919185 ], [ -122.444691394641026, 37.805776576752621 ], [ -122.444677372284929, 37.805769940684463 ], [ -122.444653201761611, 37.805772399426502 ], [ -122.444630958989393, 37.805782380395051 ], [ -122.444585917098436, 37.805781062896116 ], [ -122.444563710619391, 37.805792416711171 ], [ -122.444549688278883, 37.80578578090244 ], [ -122.444518632419573, 37.805789726327305 ], [ -122.444494659650545, 37.805799735794025 ], [ -122.444451329400607, 37.805797703293351 ], [ -122.444429141227388, 37.805809743784408 ], [ -122.444413370583575, 37.805802449506146 ], [ -122.444390948345657, 37.805805566373394 ], [ -122.44436699318581, 37.805816262252314 ], [ -122.444352970844008, 37.805809625871085 ], [ -122.444327105931421, 37.805813485938437 ], [ -122.44430666567736, 37.805826184043205 ], [ -122.444289129128734, 37.805817545953296 ], [ -122.444266706866628, 37.805820662247768 ], [ -122.444242769660434, 37.805832044533943 ], [ -122.444228747333952, 37.805825408412545 ], [ -122.444206307444119, 37.80582783853189 ], [ -122.444182406178044, 37.805840593670816 ], [ -122.44416661758342, 37.805832613201659 ], [ -122.44414592563939, 37.805835701223295 ], [ -122.444121953151352, 37.805845710608445 ], [ -122.444025145055349, 37.805850740063669 ], [ -122.444001189835404, 37.805861436142784 ], [ -122.443959572249256, 37.805858688226643 ], [ -122.443935671622995, 37.805871443304085 ], [ -122.443919900675667, 37.805864149240215 ], [ -122.443902651728223, 37.805866493739174 ], [ -122.443880499045548, 37.805879906997674 ], [ -122.443864710121758, 37.805871926219289 ], [ -122.44378192426916, 37.805883591639486 ], [ -122.443756257338379, 37.805895002334175 ], [ -122.443714639749658, 37.805892254606199 ], [ -122.443692432758084, 37.80590360826271 ], [ -122.4436766621739, 37.805896314160641 ], [ -122.443654240222742, 37.805899430882093 ], [ -122.443630320868039, 37.805911499476395 ], [ -122.443614550281069, 37.80590420509148 ], [ -122.44359211035453, 37.805906635368508 ], [ -122.443568208950381, 37.805919390108215 ], [ -122.443552420405013, 37.80591140955697 ], [ -122.443535171434974, 37.805913754002177 ], [ -122.443511270025937, 37.805926509004905 ], [ -122.443495481838426, 37.805918528714912 ], [ -122.443473041543214, 37.805920958425858 ], [ -122.443450852475209, 37.80593299846943 ], [ -122.443435081915695, 37.805925704609677 ], [ -122.443407468616002, 37.805928906281792 ], [ -122.443388776471963, 37.805942262421105 ], [ -122.443369509665189, 37.805933652158522 ], [ -122.443347069022408, 37.805936082125754 ], [ -122.443328395172557, 37.80595012440785 ], [ -122.443309128031871, 37.805941514415672 ], [ -122.443291878362203, 37.805943859111288 ], [ -122.443267959626397, 37.805955927620595 ], [ -122.443252189065603, 37.805948633187285 ], [ -122.443233209414942, 37.805951006379615 ], [ -122.443207595577817, 37.805964475989526 ], [ -122.443174557723637, 37.805958839788218 ], [ -122.443148908639841, 37.80597093678324 ], [ -122.443134868425886, 37.805963614373887 ], [ -122.443058967715231, 37.805973792109128 ], [ -122.443036832460166, 37.805987891372702 ], [ -122.443017547724423, 37.805978594894604 ], [ -122.442981318355606, 37.805983312150161 ], [ -122.442948532359026, 37.805987285937427 ], [ -122.442926379114695, 37.806000698747546 ], [ -122.44290884267825, 37.805992060178127 ], [ -122.442839827294776, 37.806000751431881 ], [ -122.442815943694384, 37.806014192451634 ], [ -122.442798425230706, 37.806006240298515 ], [ -122.442734582834518, 37.806014159544731 ], [ -122.442707256867862, 37.806028343976273 ], [ -122.44268971976544, 37.806019705660304 ], [ -122.442636241331058, 37.806026767128834 ], [ -122.442610646012994, 37.806040923030253 ], [ -122.442591378939156, 37.80603231319558 ], [ -122.442551688858117, 37.806037087047187 ], [ -122.442547725650329, 37.806017923926198 ], [ -122.443029120691975, 37.805957802366279 ], [ -122.443387985849299, 37.805912059110995 ], [ -122.443753825765924, 37.805868259987832 ], [ -122.443769345089194, 37.805865944017846 ], [ -122.443957339015583, 37.805839496905513 ], [ -122.444492227815289, 37.805772993462419 ], [ -122.444668235229059, 37.805751549942215 ], [ -122.445284142224111, 37.805672032526566 ], [ -122.445279062855903, 37.805610310694497 ], [ -122.44527006920417, 37.805597411188494 ], [ -122.444483346869873, 37.805698286438428 ], [ -122.443744945559246, 37.805793553184138 ], [ -122.443018600699929, 37.805886555905765 ], [ -122.442516549650193, 37.805951138305055 ], [ -122.442427410705633, 37.806050808392641 ], [ -122.442522464016989, 37.806507977467341 ], [ -122.442586642075881, 37.806579026869471 ], [ -122.443131937777821, 37.806513045572309 ], [ -122.443499493172467, 37.806468532301039 ], [ -122.443667282141945, 37.806463706682081 ], [ -122.443846679856065, 37.806439461357343 ], [ -122.443959061743129, 37.806434175357609 ], [ -122.443938050605254, 37.806358981524156 ], [ -122.443116244562106, 37.806442570813587 ], [ -122.442613848569025, 37.80649411140103 ], [ -122.442611615303633, 37.806474919513256 ], [ -122.442654765933469, 37.806470088368528 ], [ -122.442661399886077, 37.806458991436592 ], [ -122.442687570467214, 37.806466801090252 ], [ -122.442708298579689, 37.80646508646474 ], [ -122.442716537115061, 37.806449155723413 ], [ -122.442746276492144, 37.806461026947929 ], [ -122.442770051771902, 37.806443467084335 ], [ -122.44279979080919, 37.80645533830107 ], [ -122.442874033560784, 37.806447934694987 ], [ -122.442882218173921, 37.806429944918698 ], [ -122.442912011462568, 37.806443875399388 ], [ -122.442927548550728, 37.806442245978758 ], [ -122.442937481103471, 37.806424914130673 ], [ -122.442965543726487, 37.806438873384522 ], [ -122.442990995725921, 37.806419225391323 ], [ -122.443019058349662, 37.806433184357886 ], [ -122.443039786445084, 37.806431469674095 ], [ -122.443047971009094, 37.806413479611678 ], [ -122.443077764322211, 37.806427410050546 ], [ -122.443098474448703, 37.80642500892381 ], [ -122.443108388985991, 37.806406990354105 ], [ -122.443136451627183, 37.806420949292765 ], [ -122.443157179716479, 37.806419234588326 ], [ -122.443165418150457, 37.806403303815522 ], [ -122.443193426900422, 37.806415203442874 ], [ -122.443215885660081, 37.806413459937652 ], [ -122.443222483596756, 37.806400990383416 ], [ -122.443248689797912, 37.806410172509004 ], [ -122.443329817859791, 37.806401281706691 ], [ -122.443338074230084, 37.806386037628911 ], [ -122.443366083001223, 37.806397937215287 ], [ -122.443447229004846, 37.806389732764778 ], [ -122.443455467380517, 37.806373802246185 ], [ -122.443480033821288, 37.806386445534933 ], [ -122.443505916940026, 37.806383271811711 ], [ -122.443512496848314, 37.806370115534087 ], [ -122.443538721058857, 37.806379984576957 ], [ -122.443562892155768, 37.806377525783311 ], [ -122.443569490023762, 37.806365055935018 ], [ -122.44359742694175, 37.80637420973634 ], [ -122.443676842221919, 37.806366033924192 ], [ -122.443672164594389, 37.806253487231395 ], [ -122.443695462542081, 37.806349932284753 ], [ -122.443707916670277, 37.806362774599044 ], [ -122.44379596596373, 37.806353769679554 ], [ -122.443802527838344, 37.806339926678575 ], [ -122.443830500721077, 37.806350453289745 ], [ -122.443861574478419, 37.806347194484687 ], [ -122.443868190266627, 37.806335411052103 ], [ -122.443896109229783, 37.80634387807568 ], [ -122.443922010642723, 37.806341390962501 ], [ -122.443913871646629, 37.806228901600704 ], [ -122.443940541034891, 37.806321857121844 ], [ -122.443956527422003, 37.806337388377891 ], [ -122.44398415881686, 37.80633487273537 ], [ -122.444004419522869, 37.806315310362876 ], [ -122.444018675589732, 37.806330870132562 ], [ -122.444044576647997, 37.806328382998174 ], [ -122.444051174437902, 37.806315913397398 ], [ -122.444077344761538, 37.806323722474062 ], [ -122.444106688857119, 37.806320492116114 ], [ -122.444113268646205, 37.806307335530214 ], [ -122.444137744599871, 37.806316546256639 ], [ -122.444163610043603, 37.806312686225482 ], [ -122.444171919814423, 37.806299501392182 ], [ -122.444198126803698, 37.806308683844726 ], [ -122.44422572221977, 37.806304795006142 ], [ -122.444232302000771, 37.806291638962733 ], [ -122.444258508634277, 37.806300820858432 ], [ -122.444287834382735, 37.806296903754081 ], [ -122.44429446807581, 37.806285806729939 ], [ -122.444320620790435, 37.806292929589091 ], [ -122.444410328338193, 37.806281149679826 ], [ -122.444398674018615, 37.806166658108886 ], [ -122.444430732743257, 37.806267078693303 ], [ -122.444443114393508, 37.806277175761053 ], [ -122.44453626493052, 37.806264652260793 ], [ -122.444546322932027, 37.806252125028941 ], [ -122.444572511964111, 37.80626062069139 ], [ -122.44466566211247, 37.806248097094617 ], [ -122.444654277108668, 37.8061439020343 ], [ -122.444684317803762, 37.806233368169231 ], [ -122.444698448132328, 37.806244122830272 ], [ -122.444729504511017, 37.806240177078124 ], [ -122.444737832507585, 37.806227678906012 ], [ -122.444765733548039, 37.806235459291536 ], [ -122.444782982907213, 37.806233114935374 ], [ -122.44479131087958, 37.806220616210318 ], [ -122.444819211930195, 37.806228396857783 ], [ -122.444886496978683, 37.806219733530369 ], [ -122.44488089641429, 37.806138105148783 ], [ -122.444906901269448, 37.806205662186763 ], [ -122.444919282967007, 37.806215758930051 ], [ -122.444986568338436, 37.806207095539854 ], [ -122.444994931901647, 37.806195969945342 ], [ -122.445021067006977, 37.806202405936702 ], [ -122.445093525002505, 37.8061929701732 ], [ -122.445087942204026, 37.80611202850794 ], [ -122.445113929253864, 37.806178899068257 ], [ -122.445128041656801, 37.806188967520207 ], [ -122.445202229600525, 37.806179502882479 ], [ -122.445210557849578, 37.806167004396592 ], [ -122.445236728249824, 37.806174813490792 ], [ -122.44525743822885, 37.806172411710314 ], [ -122.445265766467799, 37.80615991322049 ], [ -122.445267302625481, 37.806086407881679 ], [ -122.445285033545986, 37.806168522898403 ], [ -122.445310916864869, 37.806165349047845 ], [ -122.445305315838027, 37.806083720685585 ], [ -122.445331267107818, 37.80614921860834 ], [ -122.445347145482373, 37.806160631088311 ], [ -122.445366125472304, 37.806158257824791 ], [ -122.445374453006437, 37.806145759613244 ], [ -122.44540062410141, 37.806153568385149 ], [ -122.445424776743849, 37.806150423332426 ], [ -122.445433086611402, 37.80613723812958 ], [ -122.445434730189021, 37.806067851662334 ], [ -122.445459293346119, 37.806146420033315 ], [ -122.445479985322095, 37.806143331781477 ], [ -122.445489989537165, 37.806128745440887 ], [ -122.445516214271365, 37.806138614038851 ], [ -122.445543827543716, 37.806135411324078 ], [ -122.445560788683153, 37.80612208366891 ], [ -122.445567131287589, 37.806165929693421 ], [ -122.445546061556797, 37.806154603157502 ], [ -122.445523639245678, 37.806157719690731 ], [ -122.445501522738013, 37.806172505842895 ], [ -122.445489122634442, 37.806161722734075 ], [ -122.44546670032598, 37.806164839531021 ], [ -122.445442817508763, 37.806178281625698 ], [ -122.445432201695837, 37.806169528989997 ], [ -122.445352822460961, 37.806179079403755 ], [ -122.445327226916191, 37.806193235905667 ], [ -122.445316593481564, 37.806183797096139 ], [ -122.445244135177305, 37.806193233002396 ], [ -122.44522027063509, 37.806207361203953 ], [ -122.445207906544567, 37.80619795120483 ], [ -122.445188926532239, 37.806200324168309 ], [ -122.445161708883106, 37.806218628036419 ], [ -122.445135448551525, 37.806207386764015 ], [ -122.445111565653477, 37.806220828516935 ], [ -122.44509921920563, 37.806212104669896 ], [ -122.445030203866864, 37.80622079693034 ], [ -122.445008123555382, 37.806236955848796 ], [ -122.444978438202398, 37.806227144485362 ], [ -122.444954644838973, 37.806244018373903 ], [ -122.444924959836314, 37.806234206991327 ], [ -122.444902861169254, 37.806249679463384 ], [ -122.444888730810021, 37.806238924826829 ], [ -122.444823175735436, 37.806247559901692 ], [ -122.444801113352611, 37.806264405213646 ], [ -122.444785216712205, 37.806252306233745 ], [ -122.444723104639422, 37.806260197747797 ], [ -122.444699383130299, 37.80627981731422 ], [ -122.444681702577071, 37.806265687549761 ], [ -122.444619590489268, 37.806273579283847 ], [ -122.444597491739671, 37.806289051423988 ], [ -122.444583361417187, 37.806278296750953 ], [ -122.444519536610485, 37.806286903400981 ], [ -122.444493959540637, 37.806301746142793 ], [ -122.44447984720945, 37.80629167788976 ], [ -122.444386678651156, 37.806303514912976 ], [ -122.444361209083098, 37.806322476775328 ], [ -122.444343546554336, 37.806309033118175 ], [ -122.444324566500356, 37.806311406491666 ], [ -122.444297366567298, 37.806330396317819 ], [ -122.444279722039454, 37.806317639632702 ], [ -122.444255568948051, 37.806320784456545 ], [ -122.444233506414719, 37.806337629662124 ], [ -122.444217609851108, 37.806325530605875 ], [ -122.444126171547992, 37.806337339172615 ], [ -122.444102396615747, 37.806354899035341 ], [ -122.444084751416256, 37.806342142057929 ], [ -122.444034733554687, 37.806349147388161 ], [ -122.443955318332002, 37.806357323441311 ], [ -122.443976328801966, 37.806432517557667 ], [ -122.444009133253417, 37.806429229637253 ], [ -122.44401497169018, 37.80645385590833 ], [ -122.443983915176943, 37.806457801197638 ], [ -122.443991501564284, 37.806483085111559 ], [ -122.444006566508079, 37.806529534342857 ], [ -122.443998364182022, 37.80654683776438 ], [ -122.444012889909018, 37.806572694020851 ], [ -122.444137456251781, 37.806569953925113 ], [ -122.444004705900142, 37.806590683869416 ], [ -122.444008597616772, 37.806607101209622 ], [ -122.444028224510816, 37.8066294400098 ], [ -122.444014687364202, 37.806641337539759 ], [ -122.444016813135761, 37.806656410256579 ], [ -122.444032907804939, 37.806676060090197 ], [ -122.4440210291, 37.806685183643154 ], [ -122.444025100564104, 37.806708465032258 ], [ -122.444042961537718, 37.806729459206764 ], [ -122.444031118428768, 37.806739955631187 ], [ -122.444035226540592, 37.806764609873134 ], [ -122.444052799240296, 37.806774621138679 ], [ -122.444173599989711, 37.806760268693552 ], [ -122.444175761756156, 37.806776714546551 ], [ -122.443903104192728, 37.806809364848277 ], [ -122.443899212488802, 37.80679294750523 ], [ -122.443999302417467, 37.806780996791801 ], [ -122.444012857566491, 37.806769785696332 ], [ -122.443998457621589, 37.806748734467796 ], [ -122.444004821781832, 37.806727340974426 ], [ -122.443985267126379, 37.806707748443934 ], [ -122.443996768032036, 37.80668420981921 ], [ -122.443979015619519, 37.806667334220748 ], [ -122.443988858092823, 37.806646570397213 ], [ -122.443972709865449, 37.806624861254427 ], [ -122.443980822328029, 37.806604125398131 ], [ -122.443978768461875, 37.806591798410317 ], [ -122.443966422424324, 37.806583074165033 ], [ -122.443972768605505, 37.806560994240272 ], [ -122.443949501347888, 37.806531848697659 ], [ -122.443975330928197, 37.806526615843026 ], [ -122.443974234501226, 37.806484743188264 ], [ -122.443966666100792, 37.806460145980267 ], [ -122.443854499850644, 37.806473669177059 ], [ -122.443860373479154, 37.806499667782823 ], [ -122.443717402901086, 37.806526746415607 ], [ -122.443667420469765, 37.806535124185942 ], [ -122.443645087827377, 37.806541672798915 ], [ -122.443622719585392, 37.806546848536961 ], [ -122.443600333023568, 37.806551337843935 ], [ -122.443577910179286, 37.806554454561891 ], [ -122.443581820462299, 37.80657155833741 ], [ -122.443586556699174, 37.806620238019541 ], [ -122.443637961503612, 37.806864552713115 ], [ -122.443633273319165, 37.806883858652405 ], [ -122.443607138425932, 37.806877421799527 ], [ -122.443529794028962, 37.806898611546323 ], [ -122.443526675114754, 37.806911710787858 ], [ -122.443502306167488, 37.806906618541966 ], [ -122.443424944087113, 37.80692712178169 ], [ -122.443421824461069, 37.806940221031809 ], [ -122.443397438234925, 37.806934442320348 ], [ -122.443318380993759, 37.806956346879609 ], [ -122.443315262039832, 37.806969446115438 ], [ -122.443289162740911, 37.806964382337512 ], [ -122.443211818508658, 37.806985571869724 ], [ -122.443208734784008, 37.807000044253662 ], [ -122.443180869538779, 37.806993635816845 ], [ -122.443103524899072, 37.807014825283645 ], [ -122.443102154219687, 37.807028582437368 ], [ -122.443076036948625, 37.80702283217979 ], [ -122.442998692594642, 37.807044021572032 ], [ -122.442995572888506, 37.807057120810583 ], [ -122.442969456307765, 37.807051370518039 ], [ -122.442895554290288, 37.807071816676462 ], [ -122.442876807520932, 37.807083113165248 ], [ -122.442860372447001, 37.807050420945309 ], [ -122.44288643546534, 37.807054111684081 ], [ -122.442950027240173, 37.807036582576075 ], [ -122.442956535452808, 37.807020680875418 ], [ -122.44298955579778, 37.807025630704103 ], [ -122.443044549671924, 37.807010303103709 ], [ -122.443051093786181, 37.806995773988241 ], [ -122.443080653430101, 37.80700078080757 ], [ -122.443137377283094, 37.806985425210136 ], [ -122.443143903403268, 37.806970209382371 ], [ -122.443176924084369, 37.806975159152948 ], [ -122.443230187542511, 37.806959860250316 ], [ -122.443236731623188, 37.806945331399028 ], [ -122.443266291259548, 37.806950338171859 ], [ -122.443323015021249, 37.806934981935846 ], [ -122.443327829074263, 37.806920481311195 ], [ -122.443311249747254, 37.806882297698685 ], [ -122.443357388360823, 37.806925488066938 ], [ -122.443415825168074, 37.806909417378883 ], [ -122.443402365072131, 37.806858134260111 ], [ -122.443446755408701, 37.806900666674323 ], [ -122.443506922515155, 37.806884567152593 ], [ -122.443513466527847, 37.806870038011155 ], [ -122.443541295807862, 37.80687507323001 ], [ -122.443611772372606, 37.806856057118701 ], [ -122.443568098366953, 37.806642517527813 ], [ -122.443490717910933, 37.80666233411602 ], [ -122.443464637369544, 37.806657956797729 ], [ -122.443447387531151, 37.806660301241159 ], [ -122.443394142301429, 37.806676286675255 ], [ -122.443369792101194, 37.80667188082289 ], [ -122.443307715462936, 37.806681144456071 ], [ -122.443302883453555, 37.806694958647995 ], [ -122.443274946458374, 37.806685805052041 ], [ -122.443204469968975, 37.806704820962679 ], [ -122.443178370770624, 37.806699757160004 ], [ -122.443107894250431, 37.806718773287464 ], [ -122.44308352539521, 37.806713680954857 ], [ -122.443013048491437, 37.806732697031315 ], [ -122.442985237265603, 37.806728348120288 ], [ -122.442950738216922, 37.806733036852982 ], [ -122.44291647270785, 37.806746649473865 ], [ -122.442888625547027, 37.806740927400313 ], [ -122.442821555362244, 37.806757827053026 ], [ -122.442793726174969, 37.806752791664266 ], [ -122.442731613479054, 37.806760682403457 ], [ -122.442721537132741, 37.806772522772178 ], [ -122.442695384049358, 37.806765399290242 ], [ -122.442678134861055, 37.806767743884308 ], [ -122.442671608648851, 37.806782959137742 ], [ -122.442640174735473, 37.806772489262421 ], [ -122.44262290758212, 37.806774147415787 ], [ -122.442611226258151, 37.806790821310969 ], [ -122.442619566601024, 37.806844935481941 ], [ -122.4425572741916, 37.806845961528367 ], [ -122.442543113877818, 37.806767907443565 ], [ -122.442812274930034, 37.80673394443496 ], [ -122.442895043918895, 37.806721593264598 ], [ -122.44288439854499, 37.806645541768312 ], [ -122.44286390392358, 37.80665618032328 ], [ -122.442620734223425, 37.806691089066426 ], [ -122.442586432743397, 37.80670332845169 ], [ -122.442569254711358, 37.806708418497294 ], [ -122.442553843632766, 37.806714852893514 ], [ -122.44253845015912, 37.806721973451488 ], [ -122.442524957636692, 37.80672882453954 ], [ -122.442523074304333, 37.806729780720396 ], [ -122.44251132146853, 37.806743708869504 ], [ -122.442504741349737, 37.806756865090371 ], [ -122.442501639536246, 37.806770651023797 ], [ -122.442501980779383, 37.80678369296961 ], [ -122.442511153361352, 37.806803457290563 ], [ -122.442513800037688, 37.806838436574978 ], [ -122.442498190669355, 37.806837320216118 ], [ -122.442500262610309, 37.806850333930662 ], [ -122.442500855308538, 37.806872986207821 ], [ -122.442513308640088, 37.806885829207545 ], [ -122.442543448720272, 37.807045340228143 ], [ -122.442550657494735, 37.807056209147177 ], [ -122.442552729104918, 37.807069222865962 ], [ -122.442549574089071, 37.807080949216051 ], [ -122.442542975993348, 37.807093419280186 ], [ -122.442532880918762, 37.80710457321085 ], [ -122.442521020253452, 37.807114382489758 ], [ -122.442507375020014, 37.80712216152461 ], [ -122.442493693848277, 37.807128567143486 ], [ -122.442476479790699, 37.807132284309965 ], [ -122.44245921255002, 37.807133942439243 ], [ -122.442443603113219, 37.807132825798654 ], [ -122.442426210144987, 37.807129678620242 ], [ -122.442401877121071, 37.807125959012424 ], [ -122.442394668361601, 37.807115090084459 ], [ -122.442382322776425, 37.807106365943092 ], [ -122.442371670941014, 37.807096240723055 ], [ -122.442357217513063, 37.80707312972298 ], [ -122.442353415933297, 37.807060144492915 ], [ -122.442352751441661, 37.807034746485243 ], [ -122.442355888527402, 37.807022333707671 ], [ -122.442327317913012, 37.80685661596673 ], [ -122.442325228043273, 37.806842915816503 ], [ -122.442319678177554, 37.80682927264953 ], [ -122.442314163892661, 37.806817002627895 ], [ -122.442303476176264, 37.806805503986638 ], [ -122.442292807121106, 37.806794692040256 ], [ -122.442278730911681, 37.806785996390175 ], [ -122.44225259583861, 37.806779559791913 ], [ -122.442263912071212, 37.806815083032639 ], [ -122.442334351220893, 37.807191623601256 ], [ -122.442574008114292, 37.807154713430769 ], [ -122.442579917970377, 37.807182085225314 ], [ -122.442316125837024, 37.807222826535309 ], [ -122.442296128149565, 37.807120146892402 ], [ -122.442249660075092, 37.807130526084627 ], [ -122.442243205590515, 37.807148487593025 ], [ -122.442218675723581, 37.80713721717698 ], [ -122.442177362768064, 37.807146137987679 ], [ -122.442169249735414, 37.807166873443258 ], [ -122.44214120495144, 37.807153600709071 ], [ -122.442110219881215, 37.807160291509724 ], [ -122.442102143088249, 37.807182399820256 ], [ -122.442074080353734, 37.807168440637511 ], [ -122.441951871381676, 37.807195175211561 ], [ -122.441943758626778, 37.807215910645752 ], [ -122.441915713871779, 37.807202638132658 ], [ -122.441829644720443, 37.807221223495034 ], [ -122.441823243975179, 37.807241243729116 ], [ -122.441796947879098, 37.807228628850183 ], [ -122.441714321769382, 37.807246470710545 ], [ -122.441706226203735, 37.807267892572305 ], [ -122.441678163511327, 37.807253933295577 ], [ -122.441590382530393, 37.807273233392451 ], [ -122.441583945818934, 37.807291881022437 ], [ -122.441559397367783, 37.80727992432282 ], [ -122.441528412884566, 37.807286614959068 ], [ -122.441522065914484, 37.807308694749302 ], [ -122.4414922725324, 37.807294763645217 ], [ -122.441459575632209, 37.807302169181312 ], [ -122.441451444085047, 37.807322218159875 ], [ -122.441425148030646, 37.807309603472838 ], [ -122.441347676557456, 37.807325986488941 ], [ -122.441341185930639, 37.807342574807244 ], [ -122.441318422046436, 37.807332648592578 ], [ -122.441240951224984, 37.807349031801699 ], [ -122.441234550317162, 37.807369052277899 ], [ -122.441208253583667, 37.807356437279445 ], [ -122.44112907030356, 37.807373535328814 ], [ -122.44112095660725, 37.807394270717083 ], [ -122.44109464263579, 37.807380969523756 ], [ -122.440999993407189, 37.807402442127575 ], [ -122.440987126791967, 37.807373811300785 ], [ -122.441006340540554, 37.807380362365826 ], [ -122.441088966969374, 37.807362521001856 ], [ -122.441071293907683, 37.807282464951484 ], [ -122.441105839449861, 37.807345761826411 ], [ -122.441121664279194, 37.807355115562864 ], [ -122.441199135479366, 37.807338732426437 ], [ -122.441181426400448, 37.807257303252058 ], [ -122.441215936141603, 37.807319227778606 ], [ -122.441235275185775, 37.807330583566994 ], [ -122.44130240044376, 37.80731574411066 ], [ -122.44131244119076, 37.80730253099884 ], [ -122.441289899930467, 37.807234916217887 ], [ -122.441336846017577, 37.807308996562313 ], [ -122.441403970896872, 37.807294157328393 ], [ -122.441389758204977, 37.807214043807704 ], [ -122.441424286396526, 37.807276654700864 ], [ -122.441440110897986, 37.807286008125857 ], [ -122.441505522979043, 37.807271883482372 ], [ -122.441489561884524, 37.807191112300579 ], [ -122.441524179900497, 37.807257155053932 ], [ -122.441538238159126, 37.807265164360558 ], [ -122.441610518456187, 37.807248866405487 ], [ -122.441598394609557, 37.807182453357271 ], [ -122.441630905705154, 37.807234109473121 ], [ -122.441643233270554, 37.80724214726019 ], [ -122.441715531475523, 37.807226535673394 ], [ -122.441722129729229, 37.807214065930424 ], [ -122.441749959016704, 37.807219101571583 ], [ -122.44181537098703, 37.807204977030693 ], [ -122.441821969214871, 37.8071925070075 ], [ -122.441804977662258, 37.807138535507519 ], [ -122.441848068147848, 37.807197571115559 ], [ -122.441911750066581, 37.807183474731303 ], [ -122.441901356658576, 37.80711703321635 ], [ -122.441942735164389, 37.807176783982825 ], [ -122.442002974305893, 37.807163431240092 ], [ -122.44201133876993, 37.807152305853435 ], [ -122.442037401781846, 37.80715599677972 ], [ -122.442094216092357, 37.807144073567486 ], [ -122.44210250836673, 37.807130202449336 ], [ -122.442130373562975, 37.807136610866159 ], [ -122.442190612977058, 37.807123257747243 ], [ -122.4421768301127, 37.807059618983033 ], [ -122.442207521070316, 37.807107871278774 ], [ -122.442221597332434, 37.80711656693682 ], [ -122.442292164942742, 37.807100983492141 ], [ -122.442243704644795, 37.806836704402386 ], [ -122.442167767011739, 37.806845508971712 ], [ -122.442147308147, 37.806857520267137 ], [ -122.442072831477645, 37.806855999790052 ], [ -122.442050696118415, 37.806870098860529 ], [ -122.442038314362932, 37.806860002097636 ], [ -122.44197618353418, 37.806867205731571 ], [ -122.44195577849186, 37.806881276017322 ], [ -122.441943396394166, 37.806871178975598 ], [ -122.441881247592036, 37.806877696126804 ], [ -122.441859148445275, 37.806893168021055 ], [ -122.44184673044974, 37.806881698103723 ], [ -122.441786329930238, 37.806888873147223 ], [ -122.441762464513715, 37.80690300092192 ], [ -122.441750082438688, 37.806892903859939 ], [ -122.441684509153674, 37.806900850470612 ], [ -122.441658930973958, 37.80691569315082 ], [ -122.441646530958224, 37.806904909645212 ], [ -122.441572305572137, 37.806912998636243 ], [ -122.441548386244634, 37.806925066794669 ], [ -122.441465311637984, 37.806925747662177 ], [ -122.441443158191802, 37.806939160185685 ], [ -122.44142908206166, 37.806930464709019 ], [ -122.44136529221457, 37.806940441967662 ], [ -122.441344958843104, 37.806957258153332 ], [ -122.441327368515545, 37.806946560051927 ], [ -122.441311849243505, 37.806948875968999 ], [ -122.441293246205632, 37.806965663663163 ], [ -122.441275637234966, 37.806954278858043 ], [ -122.44126011795251, 37.806956594493755 ], [ -122.441239730706059, 37.80697135136279 ], [ -122.441223906642946, 37.806961997904651 ], [ -122.441151465000161, 37.806972117453661 ], [ -122.441129311490656, 37.806985530192655 ], [ -122.441115235374099, 37.806976834129472 ], [ -122.441037621302598, 37.806987725743447 ], [ -122.44101719810962, 37.807001109708246 ], [ -122.441003139964337, 37.806993100613703 ], [ -122.440922064462001, 37.807004048844455 ], [ -122.440901659189493, 37.807018119222057 ], [ -122.440895696481135, 37.806988688312863 ], [ -122.441218230630383, 37.806943549393232 ], [ -122.441263058469275, 37.806936630972665 ], [ -122.441435464921327, 37.80690975751326 ], [ -122.441433016200833, 37.806882328691337 ], [ -122.441372490336036, 37.806884698487615 ], [ -122.441304935046745, 37.806883063866231 ], [ -122.441274237201611, 37.806900737372722 ], [ -122.441242947719005, 37.806895758576474 ], [ -122.44119330612007, 37.806917177241274 ], [ -122.441110393048419, 37.806924035754299 ], [ -122.441042945411667, 37.806926519579946 ], [ -122.440998117551175, 37.806933437642307 ], [ -122.440918593700744, 37.806937493327403 ], [ -122.440899792827196, 37.80694673020367 ], [ -122.440782200958282, 37.806951412279737 ], [ -122.440680218094201, 37.806957210845738 ], [ -122.44054721454367, 37.806968326842338 ], [ -122.440468121234176, 37.806988856613863 ], [ -122.440371365223044, 37.806995942367976 ], [ -122.440134737571853, 37.807016316747912 ], [ -122.43989673886324, 37.807050447763743 ], [ -122.43967267172718, 37.807087782808438 ], [ -122.439538386124013, 37.807116087220599 ], [ -122.439408914197855, 37.80712989048353 ], [ -122.439239537222377, 37.807140229675355 ], [ -122.439104802429114, 37.807151372502958 ], [ -122.438766800452754, 37.807200879974303 ], [ -122.438299128879564, 37.807256638299236 ], [ -122.437960892936687, 37.807297219553782 ], [ -122.437688625577763, 37.807344957007658 ], [ -122.437514504314919, 37.807372539848515 ], [ -122.437376577168436, 37.807394034049501 ], [ -122.437259217227975, 37.807407636246566 ], [ -122.437146815479664, 37.80741222942855 ], [ -122.437031382840118, 37.807433353744614 ], [ -122.436885215537345, 37.80747077743743 ], [ -122.436714393247598, 37.807492124635075 ], [ -122.436614801865048, 37.807523289038706 ], [ -122.436563088290782, 37.807531692199284 ], [ -122.436507376472989, 37.80751955916309 ], [ -122.436298557575597, 37.807544276277241 ], [ -122.436240083275962, 37.807558970839189 ], [ -122.436155205939471, 37.807556930640864 ], [ -122.435982097955772, 37.80755702547102 ], [ -122.435828364056036, 37.807569849863164 ], [ -122.435859235962624, 37.807691580598309 ], [ -122.433925269093152, 37.807945122059223 ], [ -122.433817271048738, 37.80791942385212 ], [ -122.433787568621852, 37.807908923241605 ], [ -122.433755903391244, 37.807889527355464 ], [ -122.43376416154652, 37.807874283957823 ], [ -122.433933527911989, 37.807929878638532 ], [ -122.435838185171932, 37.807680938490563 ], [ -122.435834169572829, 37.807659716129535 ], [ -122.435737591220459, 37.807673662142683 ], [ -122.433917274710794, 37.807904049527295 ], [ -122.433913385157453, 37.807887631858222 ], [ -122.434041074707324, 37.807871803440726 ], [ -122.434044035083929, 37.807852526536877 ], [ -122.434070401961165, 37.807867888872153 ], [ -122.434160130921143, 37.807856803195897 ], [ -122.434164803061421, 37.807836811488876 ], [ -122.434189457816743, 37.807852888603414 ], [ -122.434284359969311, 37.807841031264509 ], [ -122.434289032075128, 37.807821039552501 ], [ -122.434313686852093, 37.80783711664111 ], [ -122.434406858247897, 37.807825287589175 ], [ -122.434411548897913, 37.80780598229466 ], [ -122.434437915834692, 37.807821344547961 ], [ -122.434520741089443, 37.807811058557668 ], [ -122.434525413482959, 37.8077910671051 ], [ -122.434551797979211, 37.80780711577237 ], [ -122.434636336023075, 37.807796114603541 ], [ -122.434641025919433, 37.807776809311157 ], [ -122.434665680757917, 37.807792886326304 ], [ -122.434750218436946, 37.807781885630661 ], [ -122.434754908641708, 37.807762580053435 ], [ -122.434779544918584, 37.807777970622496 ], [ -122.434871004379232, 37.807766856012883 ], [ -122.434875676329611, 37.807746864551945 ], [ -122.434900331208965, 37.807762941518149 ], [ -122.434976252743482, 37.807753454920828 ], [ -122.434979194289198, 37.807733491570779 ], [ -122.435005579555238, 37.807749540125371 ], [ -122.435084943914418, 37.807739310518855 ], [ -122.43508961579829, 37.807719318774772 ], [ -122.435116018975634, 37.807736053738346 ], [ -122.435191922576223, 37.807725881117662 ], [ -122.435196576876137, 37.807705202655406 ], [ -122.43522297972649, 37.807721937600746 ], [ -122.435302361927597, 37.807712394556091 ], [ -122.435306998308789, 37.807691029930353 ], [ -122.435333419066353, 37.8077084510101 ], [ -122.435402436929863, 37.807699764700374 ], [ -122.435407055040258, 37.807677713642711 ], [ -122.435433494058017, 37.807695821128007 ], [ -122.435507685095942, 37.807686362859421 ], [ -122.435514015990663, 37.807663596960829 ], [ -122.43554045469773, 37.807681704977007 ], [ -122.435609472497859, 37.807673017996969 ], [ -122.435614072648008, 37.807650280497519 ], [ -122.435642259638655, 37.807669046525525 ], [ -122.435711277415109, 37.807660359485872 ], [ -122.43572966745738, 37.807635335465775 ], [ -122.435735467093281, 37.807658589004994 ], [ -122.435830350851532, 37.807646043988242 ], [ -122.435804210559851, 37.807572993499143 ], [ -122.435788619382777, 37.807572562680427 ], [ -122.435772938706066, 37.807568699417203 ], [ -122.435758952605468, 37.807563435158237 ], [ -122.435746625277645, 37.807555396762375 ], [ -122.435735992180923, 37.807545957377542 ], [ -122.435727054339125, 37.807535116438025 ], [ -122.435721558628927, 37.807523531999962 ], [ -122.435719488549438, 37.807510518156661 ], [ -122.435678807510797, 37.807410923656249 ], [ -122.435671271850225, 37.807321086052454 ], [ -122.435659314380757, 37.807260850297496 ], [ -122.435635232905554, 37.807266739353338 ], [ -122.435635716129568, 37.807285273061382 ], [ -122.435609349135348, 37.807269911074314 ], [ -122.435543792417761, 37.807278540984541 ], [ -122.43552006850399, 37.80729815869541 ], [ -122.435504101570785, 37.807283312732451 ], [ -122.435441987649398, 37.807291199357643 ], [ -122.435419994063011, 37.807310788651876 ], [ -122.43540402680101, 37.807295942681101 ], [ -122.435341912858448, 37.807303829253563 ], [ -122.435318189229335, 37.807323446918055 ], [ -122.435302222331032, 37.807308600927854 ], [ -122.435234935185079, 37.80731725906076 ], [ -122.435212941536335, 37.807336848316268 ], [ -122.435196974310131, 37.807322002317541 ], [ -122.435127956788023, 37.807330689056798 ], [ -122.435105963445992, 37.807350278012088 ], [ -122.435089996584352, 37.807335431993209 ], [ -122.435019248678017, 37.807344147060114 ], [ -122.4349972725025, 37.807364422440152 ], [ -122.434981288114457, 37.807348889967201 ], [ -122.434910540175395, 37.807357604694324 ], [ -122.434888564319408, 37.807377880322939 ], [ -122.434872579266241, 37.807362348121238 ], [ -122.434798388810123, 37.807371805710694 ], [ -122.434776412580376, 37.807392081598607 ], [ -122.434758697865377, 37.807376577207712 ], [ -122.434691410594411, 37.807385235030431 ], [ -122.434667704309632, 37.80740553900322 ], [ -122.434651719289548, 37.807390006497108 ], [ -122.434575798078043, 37.807399492611601 ], [ -122.434552092103161, 37.807419796555429 ], [ -122.434536125337331, 37.807404950461788 ], [ -122.434461916577604, 37.807413721960565 ], [ -122.434439940575373, 37.80743399723081 ], [ -122.434423973489999, 37.807419151402279 ], [ -122.434349765034597, 37.807427922275664 ], [ -122.43432605864993, 37.807448226454213 ], [ -122.434310074038208, 37.807432693896239 ], [ -122.434230709546782, 37.807442922713086 ], [ -122.434208733825983, 37.807463198209049 ], [ -122.434192749235578, 37.807447665635237 ], [ -122.434111654710136, 37.807457923293533 ], [ -122.434087948236439, 37.807478226874963 ], [ -122.434071963321998, 37.807462694290493 ], [ -122.433992599465014, 37.807472923210291 ], [ -122.433970605449531, 37.807492512508169 ], [ -122.433954638431118, 37.807477666067122 ], [ -122.433870083452888, 37.807487980028228 ], [ -122.433846376910722, 37.807508283835588 ], [ -122.433830409920873, 37.80749343765234 ], [ -122.43374412454402, 37.807503779895939 ], [ -122.433722148333416, 37.807524055581261 ], [ -122.433706163485638, 37.807508522947344 ], [ -122.433606088798641, 37.807521151358706 ], [ -122.433584112530909, 37.807541426469101 ], [ -122.433574191011218, 37.807492831512612 ], [ -122.433602216513336, 37.807505419848866 ], [ -122.433678120078412, 37.807495247927321 ], [ -122.433686343139499, 37.80747863165486 ], [ -122.433714368662436, 37.807491220239072 ], [ -122.433795462933048, 37.807480962859643 ], [ -122.433803685966325, 37.807464346578953 ], [ -122.433831693616042, 37.80747624842688 ], [ -122.433911075733292, 37.807466706318117 ], [ -122.433919280165583, 37.807449403606796 ], [ -122.433947305702674, 37.807461991586599 ], [ -122.434024939884253, 37.807451791061574 ], [ -122.434034892878572, 37.807435146394688 ], [ -122.434062900561514, 37.80744704846235 ], [ -122.434135379039915, 37.807438305591944 ], [ -122.434143601646298, 37.807421689293108 ], [ -122.434171609334427, 37.807433591060409 ], [ -122.434245800271043, 37.807424133585066 ], [ -122.434254022851178, 37.80740751727847 ], [ -122.434282048436359, 37.807420105453431 ], [ -122.434352778623364, 37.807410704669572 ], [ -122.434361000831984, 37.807394088361185 ], [ -122.434389026430239, 37.807406676510716 ], [ -122.43446148730483, 37.807397247549531 ], [ -122.434471439847925, 37.807380602851318 ], [ -122.43449944756594, 37.807392504266289 ], [ -122.434571926300066, 37.807383761671048 ], [ -122.434581860921966, 37.807366430255094 ], [ -122.434609886547221, 37.807379018352258 ], [ -122.434682347359626, 37.807369588980769 ], [ -122.434690569836022, 37.807352972643585 ], [ -122.434718577593046, 37.807364874555745 ], [ -122.434784152364955, 37.807356931263477 ], [ -122.43479237481715, 37.807340314919166 ], [ -122.434820382585727, 37.807352216807175 ], [ -122.434884226976308, 37.807344301846562 ], [ -122.434894179763575, 37.807327657106839 ], [ -122.434922187543705, 37.807339558970696 ], [ -122.434987762271462, 37.807331615565204 ], [ -122.434997697142578, 37.807314284382969 ], [ -122.435025722825799, 37.807326872656006 ], [ -122.43509127963911, 37.807318242759216 ], [ -122.435101232376525, 37.807301598001935 ], [ -122.435129240172827, 37.807313499542126 ], [ -122.43519308450432, 37.807305584688869 ], [ -122.435201306859327, 37.807288968315881 ], [ -122.435231044678986, 37.807300841444686 ], [ -122.435296619692778, 37.807292898136311 ], [ -122.4353048420232, 37.807276281756089 ], [ -122.435332849842581, 37.807288183247969 ], [ -122.435398406593208, 37.807279553455011 ], [ -122.435406628899386, 37.80726293706762 ], [ -122.435436384982893, 37.8072754965727 ], [ -122.435451904167508, 37.80727318170905 ], [ -122.435460126807058, 37.807256565312237 ], [ -122.435488134643933, 37.807268466767304 ], [ -122.435508433635007, 37.807250278724965 ], [ -122.435536459373509, 37.807262866602237 ], [ -122.435602016078107, 37.80725423669606 ], [ -122.435615572581426, 37.80724302656666 ], [ -122.435634803373304, 37.807250264946127 ], [ -122.435655496065394, 37.807247178418407 ], [ -122.435588180541416, 37.806922774713847 ], [ -122.435582667693723, 37.80691050382292 ], [ -122.435534182131477, 37.806909926104069 ], [ -122.435571873987683, 37.806894886513319 ], [ -122.435559492755871, 37.80688478880149 ], [ -122.435545453103273, 37.806877464940861 ], [ -122.435529719250937, 37.806871542612598 ], [ -122.435514074542013, 37.806869052731017 ], [ -122.435493292470525, 37.806868707065597 ], [ -122.435344768504805, 37.806882132076758 ], [ -122.435121911744176, 37.806899523218981 ], [ -122.434871780421872, 37.806933156410132 ], [ -122.434433666315243, 37.806993908011997 ], [ -122.434416381034183, 37.806994878255509 ], [ -122.434278149661878, 37.807004699734037 ], [ -122.434260827915324, 37.807004297098374 ], [ -122.434243471085438, 37.807002521306472 ], [ -122.434227808507927, 37.806999344820447 ], [ -122.434210379805293, 37.806994823568417 ], [ -122.434194645688478, 37.806988901067868 ], [ -122.434180606150846, 37.806981577044944 ], [ -122.434166548746745, 37.806973567135586 ], [ -122.434154185915276, 37.806964155430045 ], [ -122.434143535561248, 37.806954029186485 ], [ -122.434134579447857, 37.806942501702544 ], [ -122.434125606153387, 37.806930288047226 ], [ -122.434123142595496, 37.806902172634167 ], [ -122.434101162359013, 37.806855835376808 ], [ -122.434090404384904, 37.806841590530418 ], [ -122.434081359579054, 37.806826631135792 ], [ -122.43406669419322, 37.806795281911626 ], [ -122.434061091508866, 37.806779579065555 ], [ -122.434055471636611, 37.80676318977391 ], [ -122.434051599648825, 37.806747458545502 ], [ -122.434049475890433, 37.80673238537485 ], [ -122.434047316368137, 37.806715939336222 ], [ -122.434046440132462, 37.806682304072865 ], [ -122.43401902773472, 37.806493903623902 ], [ -122.434011784506907, 37.806481661588982 ], [ -122.434004523385283, 37.806468732570444 ], [ -122.433998992958308, 37.806455775445059 ], [ -122.433995192532976, 37.806442790224423 ], [ -122.433964135838224, 37.806446732869858 ], [ -122.433932703641176, 37.806436260667759 ], [ -122.433886127202001, 37.806442518242726 ], [ -122.433858853036313, 37.806458760199753 ], [ -122.433839551447093, 37.806448775787949 ], [ -122.433791244999313, 37.806455061692688 ], [ -122.4337639708083, 37.80647130362771 ], [ -122.433744668882483, 37.806461319206093 ], [ -122.433694632079209, 37.806467633442772 ], [ -122.433667357862433, 37.806483875355433 ], [ -122.43364805594635, 37.806473890918021 ], [ -122.433596288440668, 37.806480233488799 ], [ -122.433569014197744, 37.806496475378673 ], [ -122.433549712637699, 37.806486490919518 ], [ -122.433503136480809, 37.806492748337497 ], [ -122.433477574676203, 37.806508275404134 ], [ -122.433458272433256, 37.806498290941384 ], [ -122.433404793122563, 37.806505348218124 ], [ -122.43337751882855, 37.806521590063653 ], [ -122.433358216941642, 37.806511605578869 ], [ -122.4333047372658, 37.806518662815925 ], [ -122.433279192939949, 37.806534876279215 ], [ -122.433259891408895, 37.80652489177271 ], [ -122.433206393839583, 37.806531262531053 ], [ -122.433179119492635, 37.806547504330624 ], [ -122.433159817625679, 37.806537519813411 ], [ -122.43309943408164, 37.806545377123399 ], [ -122.43307215969898, 37.806561618623626 ], [ -122.433052858195936, 37.806551634357852 ], [ -122.432990744619175, 37.806559519418741 ], [ -122.432963470221836, 37.806575761442943 ], [ -122.43294416836936, 37.80656577661594 ], [ -122.432885515114464, 37.806573605181946 ], [ -122.432858276424469, 37.806591219775477 ], [ -122.432851977723161, 37.806548745928424 ], [ -122.432876452705358, 37.806557958985486 ], [ -122.432950642939176, 37.806548502597778 ], [ -122.432958937194059, 37.806534631840726 ], [ -122.432985142526235, 37.806543816515223 ], [ -122.433055872042729, 37.806534416507489 ], [ -122.433064219557167, 37.806522605325611 ], [ -122.433088659156226, 37.806530445464773 ], [ -122.43316111864155, 37.80652101703766 ], [ -122.433169413207281, 37.806507146534614 ], [ -122.433195618216885, 37.806516331442637 ], [ -122.433259444532922, 37.80650773092048 ], [ -122.433269469079946, 37.806493832326176 ], [ -122.433293944088703, 37.806503045022133 ], [ -122.433357787918965, 37.806495131161043 ], [ -122.433366082106133, 37.806481260924372 ], [ -122.433392287462695, 37.806490445233919 ], [ -122.433454400931879, 37.806482559686899 ], [ -122.433462695099607, 37.806468689443392 ], [ -122.433488900463672, 37.806477873731517 ], [ -122.433552726373762, 37.806469273330841 ], [ -122.433561038053369, 37.806456089520083 ], [ -122.433587243771214, 37.806465273780709 ], [ -122.433649339320041, 37.806456701696959 ], [ -122.43365765098109, 37.806443517879387 ], [ -122.433683838827505, 37.806452015684521 ], [ -122.433745952233608, 37.806444129983937 ], [ -122.433754245996482, 37.806430259725488 ], [ -122.43378045172912, 37.806439443943233 ], [ -122.433842565114432, 37.806431558191733 ], [ -122.433850858857866, 37.806417687926462 ], [ -122.433877064598036, 37.806426872122742 ], [ -122.433932274491454, 37.806419786251801 ], [ -122.433954393037553, 37.806405002549944 ], [ -122.433958408377592, 37.806426224974288 ], [ -122.43399121327684, 37.806422940114579 ], [ -122.433989124957407, 37.806409239815579 ], [ -122.433988785204093, 37.806396197569725 ], [ -122.433991530954188, 37.80636868345389 ], [ -122.433981985185667, 37.806334503629429 ], [ -122.433942822534163, 37.806226642930824 ], [ -122.433950330141514, 37.806182569274938 ], [ -122.433940283395017, 37.806129169298679 ], [ -122.433873424076822, 37.805821925113698 ], [ -122.433853509955952, 37.80572198947479 ], [ -122.433844483207295, 37.805707716218421 ], [ -122.433823004146618, 37.805680599328511 ], [ -122.433808821860836, 37.805667784061214 ], [ -122.433796405313814, 37.805656313293774 ], [ -122.433780528829729, 37.805644899532474 ], [ -122.433766418068998, 37.805634829721896 ], [ -122.43374886455932, 37.805625503361618 ], [ -122.433731328933206, 37.805616863432853 ], [ -122.433713811190202, 37.805608909935621 ], [ -122.433675422356743, 37.805597178553839 ], [ -122.433656263344702, 37.805592685320363 ], [ -122.433637140790879, 37.805589565215314 ], [ -122.433616323330028, 37.805587846350818 ], [ -122.43359552375523, 37.805586814191379 ], [ -122.433576472363242, 37.805586439544221 ], [ -122.433535016220674, 37.805589866131839 ], [ -122.433516072105618, 37.805593610629053 ], [ -122.433498858293476, 37.805597326206154 ], [ -122.432719648836795, 37.805721348282255 ], [ -122.432704236424613, 37.805727781678655 ], [ -122.432688842557212, 37.805734900946824 ], [ -122.432673484086777, 37.805743393361759 ], [ -122.432659891674504, 37.805753230287664 ], [ -122.432648029581884, 37.805763038856455 ], [ -122.432637933547895, 37.805774191937303 ], [ -122.432627855379891, 37.805786031451547 ], [ -122.432619525401748, 37.805798529044132 ], [ -122.432612943621251, 37.805811684990026 ], [ -122.432606379692928, 37.805825526820627 ], [ -122.432603276080599, 37.805839312496055 ], [ -122.432600137407348, 37.805851724742368 ], [ -122.432500317433352, 37.805940575058216 ], [ -122.432391913920043, 37.805965699763121 ], [ -122.432397426855275, 37.805977970519415 ], [ -122.432516390332239, 37.805959539768239 ], [ -122.432628376560629, 37.805872550799698 ], [ -122.432642540805432, 37.805884679499613 ], [ -122.432527165744716, 37.805974471194816 ], [ -122.432403189584164, 37.805999851361079 ], [ -122.43238951051228, 37.806072868560939 ], [ -122.432404443045669, 37.806114514237365 ], [ -122.432490452847844, 37.806359639916195 ], [ -122.43244910342851, 37.806367184726966 ], [ -122.432520431119073, 37.806580274784345 ], [ -122.432498061398036, 37.806585448423995 ], [ -122.432424985907915, 37.806371700265615 ], [ -122.432400868391511, 37.806376216073893 ], [ -122.432301460176419, 37.806081865240721 ], [ -122.43236869294482, 37.806071149476409 ], [ -122.432380802509087, 37.806004338539104 ], [ -122.43235326017701, 37.806010283635004 ], [ -122.432347175872593, 37.805976047248159 ], [ -122.432319651407369, 37.805982678495994 ], [ -122.432302633632929, 37.805993944955773 ], [ -122.432282173360747, 37.806005686719168 ], [ -122.432280461289963, 37.806006669320666 ], [ -122.432265120589491, 37.806015848390899 ], [ -122.432249654811855, 37.806020221869829 ], [ -122.432227231644546, 37.806023336155739 ], [ -122.432194784491401, 37.806040349205666 ], [ -122.432179693529363, 37.806059138076137 ], [ -122.432171560304809, 37.806079186408191 ], [ -122.432165157068255, 37.806099206671476 ], [ -122.432169117939694, 37.806118369860684 ], [ -122.432192469776069, 37.806150950175152 ], [ -122.432209844223067, 37.806153412703416 ], [ -122.432227007583123, 37.806214250207013 ], [ -122.432213468614208, 37.806226146108848 ], [ -122.432203354593938, 37.806236612991817 ], [ -122.432202178080502, 37.806257920803567 ], [ -122.43220649593593, 37.806290812956746 ], [ -122.432221571326565, 37.806337949854225 ], [ -122.432306430556167, 37.806804907302421 ], [ -122.432383452345448, 37.807103744994606 ], [ -122.432564682589728, 37.808479722911237 ], [ -122.432615131844727, 37.808489197136744 ], [ -122.432610261989339, 37.808501638018015 ], [ -122.432598381927761, 37.808510760137189 ], [ -122.432584628175405, 37.80851441914389 ], [ -122.432342320320956, 37.808516329227118 ], [ -122.432078432185492, 37.807223425689031 ], [ -122.431999351719028, 37.807178023752513 ], [ -122.431443837686672, 37.8072509882197 ], [ -122.431386630568454, 37.807314417209206 ], [ -122.431727121174518, 37.8090235974717 ], [ -122.431341938476805, 37.809056687748011 ], [ -122.430994999022246, 37.807365467556572 ], [ -122.430915919059899, 37.807320064620313 ], [ -122.430360402907354, 37.807393024257635 ], [ -122.430303194747026, 37.8074564524454 ], [ -122.43062118366457, 37.809166690185819 ], [ -122.430108306409053, 37.8092156017048 ], [ -122.429835000067499, 37.807825332620489 ], [ -122.429264030219372, 37.807903346686288 ], [ -122.429270969868725, 37.807970532619663 ], [ -122.428236680346103, 37.808405663457563 ], [ -122.427001622006514, 37.808109268493716 ], [ -122.426995537909036, 37.808141644021028 ], [ -122.427545144293163, 37.808640840008444 ], [ -122.428014153033857, 37.808569308327186 ], [ -122.428043639051282, 37.808704798333089 ], [ -122.427533242566099, 37.808782500069931 ], [ -122.427523467767188, 37.808739396087624 ], [ -122.427301030953672, 37.808773247159571 ], [ -122.427287243070779, 37.808708920235645 ], [ -122.427490716316399, 37.808678126013014 ], [ -122.426909232318224, 37.808151294840016 ], [ -122.426839001555635, 37.808113298884024 ], [ -122.426613230594512, 37.808152010516238 ], [ -122.426560904719622, 37.807943556174827 ], [ -122.426488519796493, 37.807651861239542 ], [ -122.426357658303132, 37.807388412718332 ], [ -122.426257507799036, 37.807185417079722 ], [ -122.426066893674061, 37.80699787612533 ], [ -122.4257796482039, 37.806715259689653 ], [ -122.425627533230582, 37.806456787332273 ], [ -122.425531817297653, 37.806150610629167 ], [ -122.425487611207828, 37.806009959663712 ], [ -122.425486832357592, 37.806006120121552 ], [ -122.425298563577385, 37.80507790289635 ], [ -122.425204919000421, 37.804616199985951 ], [ -122.425109521046082, 37.804145845932808 ], [ -122.424918804152185, 37.803205508171892 ], [ -122.424808684502352, 37.802662664019884 ], [ -122.424731259133807, 37.802280983275736 ], [ -122.424731635083759, 37.802281009278651 ], [ -122.424538324517613, 37.801303719934985 ], [ -122.424517597070277, 37.801201189595353 ], [ -122.424358667137113, 37.800415027771606 ], [ -122.424242907462499, 37.799842398528341 ], [ -122.42417056511286, 37.799484537126212 ], [ -122.42417056430692, 37.799484532744323 ], [ -122.423982145098208, 37.798552453684835 ], [ -122.423982396975646, 37.798552421556877 ], [ -122.423793785438747, 37.7976164776058 ], [ -122.423792964234153, 37.797616581376253 ], [ -122.423601296960271, 37.796691058659476 ], [ -122.42360129621845, 37.796691056748735 ], [ -122.423601876988513, 37.796690945913937 ], [ -122.423601877370103, 37.796690947281171 ], [ -122.425249874473522, 37.79648130315109 ], [ -122.426892728667355, 37.796272251947869 ], [ -122.428537813517494, 37.796062892623418 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.431827832563371, 37.79564412085616 ], [ -122.433469062960725, 37.795435179810816 ], [ -122.435113289920963, 37.795225833115033 ], [ -122.436760975027326, 37.795016021968365 ], [ -122.438402484723895, 37.794806973138577 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":7,\"ZIP_CODE\":94133,\"ID\":94133},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.418530631419571, 37.804999043218864 ], [ -122.418718265666911, 37.805931520041717 ], [ -122.418781807492508, 37.806241018338142 ], [ -122.418781854642702, 37.806241248585302 ], [ -122.418856183917853, 37.806607498073362 ], [ -122.418908043871269, 37.806863030947284 ], [ -122.419086067910683, 37.807803117635643 ], [ -122.419223966222532, 37.808453096037454 ], [ -122.419103158585543, 37.808467422639801 ], [ -122.419122467317635, 37.808544708340989 ], [ -122.418980965776527, 37.808562118993599 ], [ -122.418965099874526, 37.808484090255142 ], [ -122.417886530686005, 37.80861494396968 ], [ -122.417923033396889, 37.808821741173873 ], [ -122.417843612318336, 37.80882989992265 ], [ -122.417811191268285, 37.808647071958433 ], [ -122.417726330574766, 37.808645704666795 ], [ -122.417731886802656, 37.808592736810425 ], [ -122.417624531730937, 37.808591735208473 ], [ -122.417601699719071, 37.808579058949235 ], [ -122.41748929463067, 37.808583633129054 ], [ -122.417485092698755, 37.808420948020817 ], [ -122.417586902421718, 37.808408305143338 ], [ -122.417575917233663, 37.808385134844094 ], [ -122.417669145522396, 37.808375378317514 ], [ -122.41765313920358, 37.808291858459306 ], [ -122.41737529132557, 37.808325217992042 ], [ -122.417344584298249, 37.808141675039998 ], [ -122.416347061956941, 37.808260209523006 ], [ -122.416354024690548, 37.808328768820694 ], [ -122.416305697821443, 37.808334361125667 ], [ -122.416338782629509, 37.80847597548388 ], [ -122.41718410046191, 37.808496573566089 ], [ -122.417183326975007, 37.808533669258267 ], [ -122.416327431803367, 37.808505689336052 ], [ -122.416281962820022, 37.808487886292838 ], [ -122.416150789583881, 37.80850306555427 ], [ -122.416144807019506, 37.808539558863203 ], [ -122.416207242955323, 37.808544038257011 ], [ -122.416187547333237, 37.808854071166344 ], [ -122.41742892570683, 37.808927290365972 ], [ -122.417412600540189, 37.80916653558441 ], [ -122.418193962887401, 37.809680962781762 ], [ -122.41929170565983, 37.810403664792204 ], [ -122.41931534577914, 37.810380618458765 ], [ -122.419422885954745, 37.810454266609824 ], [ -122.419833484960733, 37.810735462513406 ], [ -122.419807989843818, 37.810753732347422 ], [ -122.420595101766708, 37.811266948659416 ], [ -122.420353825913267, 37.811509857466909 ], [ -122.420001661760068, 37.81128073132367 ], [ -122.419845148459302, 37.811320361929653 ], [ -122.419951835473171, 37.811563099184333 ], [ -122.419915762044113, 37.811573987538154 ], [ -122.419724362099529, 37.811134852199615 ], [ -122.41972295750638, 37.81113418613959 ], [ -122.419216088646508, 37.810893843097361 ], [ -122.419185739151644, 37.810925239548666 ], [ -122.419097184597589, 37.810915006165231 ], [ -122.418987802638568, 37.810835752698928 ], [ -122.418935347953351, 37.810882616404598 ], [ -122.419125945102621, 37.811023040645175 ], [ -122.419048154317267, 37.811094352280243 ], [ -122.419465867632354, 37.811381473037407 ], [ -122.419350858543552, 37.811486353332427 ], [ -122.418846744437559, 37.811473267417028 ], [ -122.416831664498375, 37.810133272229294 ], [ -122.415352404138332, 37.809160867759147 ], [ -122.415205531523597, 37.809171493557926 ], [ -122.415205867990196, 37.809184535850846 ], [ -122.415200978155667, 37.80919628956385 ], [ -122.415187329670815, 37.809204065160294 ], [ -122.415151663626474, 37.809230739831797 ], [ -122.415171052667063, 37.809244159741915 ], [ -122.415194871121983, 37.809227978313764 ], [ -122.415413019588954, 37.809363153673537 ], [ -122.415370856947149, 37.809406415569271 ], [ -122.415142166917732, 37.809265230241984 ], [ -122.415155744585249, 37.809254708904987 ], [ -122.415139781310558, 37.809239860465802 ], [ -122.415039713249854, 37.809185860695372 ], [ -122.41488880052961, 37.80924118848273 ], [ -122.414968316086458, 37.80930376277346 ], [ -122.414992913705149, 37.809317784591791 ], [ -122.415006420575594, 37.809304517526144 ], [ -122.415222785388849, 37.809437662297633 ], [ -122.415180622939999, 37.809480924119129 ], [ -122.41496425806919, 37.809347778996823 ], [ -122.414981136896941, 37.8093310235652 ], [ -122.41495316698277, 37.809320490118694 ], [ -122.414845930117963, 37.809256992445782 ], [ -122.414780776445994, 37.809281398852669 ], [ -122.414774461081151, 37.809372149219335 ], [ -122.415340036749569, 37.80975508488244 ], [ -122.415288518799699, 37.809838328383556 ], [ -122.41456302792659, 37.80956443044294 ], [ -122.414023978762671, 37.809471544827012 ], [ -122.413420668373689, 37.809236173514122 ], [ -122.41337430449579, 37.809317959368158 ], [ -122.413395600212141, 37.809338215320878 ], [ -122.413411510435665, 37.809351004954635 ], [ -122.413425672211702, 37.809363136228626 ], [ -122.413441564407123, 37.809375239427176 ], [ -122.413455726199771, 37.809387370972054 ], [ -122.413471618398191, 37.809399473891929 ], [ -122.413487492569899, 37.80941089065314 ], [ -122.413505097854454, 37.809422279326348 ], [ -122.413520972382244, 37.809433696077356 ], [ -122.413538559637843, 37.80944439831444 ], [ -122.413554416481929, 37.809455128624236 ], [ -122.413572003401356, 37.809465830861896 ], [ -122.413607142559428, 37.809485862444731 ], [ -122.413624694804525, 37.809495192064489 ], [ -122.414282035042135, 37.809946003161961 ], [ -122.414200903764723, 37.81002217256772 ], [ -122.414027337823839, 37.810138985552911 ], [ -122.413780738047123, 37.809907441000853 ], [ -122.413857049972947, 37.809845771015681 ], [ -122.412616832608151, 37.809078904365926 ], [ -122.412498775807535, 37.80906571088461 ], [ -122.412398060995869, 37.809120908451149 ], [ -122.412400109349747, 37.809133236262518 ], [ -122.412396967167027, 37.809145648237823 ], [ -122.412390346141294, 37.809157430172931 ], [ -122.412380210884578, 37.809167208369729 ], [ -122.412426492762847, 37.809216588736774 ], [ -122.412450629760414, 37.809212763732177 ], [ -122.41262839277087, 37.809393236325491 ], [ -122.412573868329858, 37.809427083257212 ], [ -122.412396105370902, 37.809246610582868 ], [ -122.412412861666652, 37.809225050449037 ], [ -122.412356250722411, 37.809177897728922 ], [ -122.41234073008107, 37.809180209833173 ], [ -122.412323390603305, 37.809179117259049 ], [ -122.412307675451856, 37.809173878278465 ], [ -122.412099340782589, 37.809285071655154 ], [ -122.412099676704088, 37.809298113956672 ], [ -122.412096516784118, 37.809309839486673 ], [ -122.412089895696084, 37.809321621404543 ], [ -122.412078012629308, 37.8093307411864 ], [ -122.412124294062423, 37.809380121676121 ], [ -122.412148413801376, 37.809375610565525 ], [ -122.412315935400727, 37.809561743172587 ], [ -122.412252793608843, 37.809597103119742 ], [ -122.412093889154491, 37.809409457275514 ], [ -122.41211066290181, 37.809388583353197 ], [ -122.412057512853792, 37.809341374382768 ], [ -122.412040244067796, 37.809343027789311 ], [ -122.412024634631933, 37.809341907398313 ], [ -122.412008919813204, 37.809336668372488 ], [ -122.411894272206808, 37.809388657747697 ], [ -122.412861765933144, 37.810522545447967 ], [ -122.412718336607824, 37.810599724430624 ], [ -122.412705816724809, 37.810584132828346 ], [ -122.411509089676429, 37.81123738012537 ], [ -122.41148957739901, 37.811219154555417 ], [ -122.412686215557258, 37.810562475550952 ], [ -122.411452462026304, 37.809105334882879 ], [ -122.411423380532113, 37.809118854220145 ], [ -122.411248622782082, 37.808920476073887 ], [ -122.411085557936261, 37.808907323817536 ], [ -122.411283571142818, 37.809134854169606 ], [ -122.411256255700124, 37.809149718017444 ], [ -122.411031438641331, 37.808889659210472 ], [ -122.410955336187001, 37.808892265747417 ], [ -122.410765573350616, 37.808850703243849 ], [ -122.410736033164511, 37.808846374759867 ], [ -122.410731512966407, 37.808872543480319 ], [ -122.410649866328171, 37.808861505447226 ], [ -122.410652655797975, 37.808835364493682 ], [ -122.410546641899074, 37.808819227353638 ], [ -122.410441958004114, 37.809325664485378 ], [ -122.410406413493931, 37.809289843904054 ], [ -122.410401787512413, 37.809311893981061 ], [ -122.410630331289113, 37.809716792194763 ], [ -122.410709259423612, 37.809689418065965 ], [ -122.410633083668301, 37.809554681184579 ], [ -122.41064851611452, 37.809548937394226 ], [ -122.410729547478269, 37.809670547859675 ], [ -122.410757004560026, 37.809661175631867 ], [ -122.410712947997226, 37.809563688219221 ], [ -122.410728380093076, 37.809557944424022 ], [ -122.410791312651469, 37.80964894551029 ], [ -122.410822177556227, 37.809637457886339 ], [ -122.410778085579025, 37.80953859734884 ], [ -122.410793518010678, 37.809532853539501 ], [ -122.410858216047515, 37.809625199709956 ], [ -122.410889098254941, 37.809614398511123 ], [ -122.410843240792332, 37.809514193154044 ], [ -122.410858672867818, 37.809508449341749 ], [ -122.410923406302985, 37.809602168351013 ], [ -122.410950845665482, 37.809592109640889 ], [ -122.410904952795121, 37.809490531433248 ], [ -122.410920384860916, 37.809484787612853 ], [ -122.410985153693048, 37.809579879462774 ], [ -122.411024617975485, 37.809566192280315 ], [ -122.410978689334627, 37.809463241232329 ], [ -122.410994104065225, 37.809456810959603 ], [ -122.411058925978935, 37.809553962080649 ], [ -122.411089808473605, 37.809543160823544 ], [ -122.411042113998747, 37.809438864964896 ], [ -122.411057546043196, 37.809433121126496 ], [ -122.411195348876859, 37.80967536169041 ], [ -122.41117818636998, 37.809681133587063 ], [ -122.411116453591873, 37.809569511282646 ], [ -122.41104955067469, 37.80959325722408 ], [ -122.41111476155065, 37.809705510201134 ], [ -122.411097598684464, 37.809711282091641 ], [ -122.411034100233891, 37.80959831463543 ], [ -122.410977509113948, 37.809619146556997 ], [ -122.411041007143098, 37.809732113774253 ], [ -122.411025592359422, 37.809738544052792 ], [ -122.410960346257056, 37.809624918427609 ], [ -122.410907162593759, 37.809643635209717 ], [ -122.410972426300958, 37.809757947300923 ], [ -122.410956993836351, 37.80976369113344 ], [ -122.410891730147881, 37.809649379033807 ], [ -122.410835138249837, 37.809670210623501 ], [ -122.410900419538606, 37.809785209191027 ], [ -122.410884987408849, 37.809790953008481 ], [ -122.410819706138938, 37.809675954432528 ], [ -122.410764809648242, 37.809695385622547 ], [ -122.410830108511632, 37.809811070390921 ], [ -122.41081467602497, 37.809816814204737 ], [ -122.410747646757216, 37.80970115746215 ], [ -122.410639354408318, 37.809731067210933 ], [ -122.410896947885561, 37.810188372007936 ], [ -122.410975841429376, 37.810159624823598 ], [ -122.410901536485113, 37.810030351311333 ], [ -122.410916969009435, 37.810024607486383 ], [ -122.410997965998874, 37.810144845157168 ], [ -122.41103912533255, 37.810129757339681 ], [ -122.410977003992286, 37.810003033077805 ], [ -122.410992436505126, 37.809997289242951 ], [ -122.411075146003029, 37.810116812392209 ], [ -122.411112880121649, 37.810103153220581 ], [ -122.411050758345823, 37.809976429551675 ], [ -122.411066208857108, 37.809971371863952 ], [ -122.411147188358683, 37.810090922996501 ], [ -122.411190078057032, 37.810075806811575 ], [ -122.411127956511336, 37.809949082902484 ], [ -122.411143389001253, 37.809943339047805 ], [ -122.411224368596535, 37.81006289012803 ], [ -122.411256964037719, 37.810051374350031 ], [ -122.411194842059288, 37.809924650755768 ], [ -122.41121027488505, 37.809918906886708 ], [ -122.4112929849924, 37.810038429879377 ], [ -122.411332431435511, 37.81002405588923 ], [ -122.411270309344076, 37.809897332059471 ], [ -122.411285742158299, 37.809891588180513 ], [ -122.411366721926342, 37.810011139164168 ], [ -122.411401047752832, 37.809999595302976 ], [ -122.411337195490745, 37.809872899820583 ], [ -122.411354358375661, 37.809867127895387 ], [ -122.411435338227506, 37.809986678832487 ], [ -122.411471359105562, 37.809973733758071 ], [ -122.411409237165202, 37.809847009994684 ], [ -122.411424669612117, 37.809841266103092 ], [ -122.41150566722375, 37.80996150342942 ], [ -122.411546826352634, 37.809946415434261 ], [ -122.411484704299241, 37.809819691435401 ], [ -122.411500136734659, 37.809813947533918 ], [ -122.411582846845619, 37.809933470331323 ], [ -122.4116188680253, 37.809920525480692 ], [ -122.411556745877689, 37.809793801793724 ], [ -122.411572177956074, 37.809788057888397 ], [ -122.411653176094603, 37.809908295109032 ], [ -122.4116909096619, 37.809894636032141 ], [ -122.411628787405917, 37.809767912107915 ], [ -122.411644219819436, 37.809762168187518 ], [ -122.411725217707357, 37.809882405639449 ], [ -122.411759525734439, 37.809870174962185 ], [ -122.411697403388885, 37.809743451348098 ], [ -122.411712835792031, 37.809737707418712 ], [ -122.411881278427131, 37.810026834486933 ], [ -122.41186411554628, 37.81003260648864 ], [ -122.411782586611622, 37.809891776308959 ], [ -122.411719126796399, 37.80991478009306 ], [ -122.411802385721074, 37.810055582271964 ], [ -122.411785222827419, 37.810061354262146 ], [ -122.411703694356945, 37.809920524023092 ], [ -122.411645389734517, 37.809942070760108 ], [ -122.411726900787329, 37.810082214596392 ], [ -122.411711467967621, 37.810087958532876 ], [ -122.411628227200978, 37.809947842721932 ], [ -122.41156305384375, 37.809971560917056 ], [ -122.411644564773539, 37.810111704809465 ], [ -122.411629131941311, 37.810117448735141 ], [ -122.411547621726527, 37.809977304820983 ], [ -122.411485874174801, 37.809999594002001 ], [ -122.411567384297499, 37.810139737958238 ], [ -122.411551952145643, 37.810145481862563 ], [ -122.411470441699677, 37.81000533790143 ], [ -122.411408676427612, 37.810026940604999 ], [ -122.411490204455376, 37.81016777104513 ], [ -122.41147477194562, 37.81017351494495 ], [ -122.411393243940836, 37.810032684494281 ], [ -122.411328070780158, 37.81005640255357 ], [ -122.41140959834128, 37.810197233054254 ], [ -122.411394148491496, 37.81020229050096 ], [ -122.41131090784944, 37.810062174474744 ], [ -122.411249177828495, 37.810085149968437 ], [ -122.411330687944414, 37.810225294080347 ], [ -122.4113152554104, 37.810231037959227 ], [ -122.411232014885002, 37.810090921878086 ], [ -122.411175405434363, 37.810111067735896 ], [ -122.411256933104966, 37.81025189806045 ], [ -122.411241500559697, 37.810257641929645 ], [ -122.411159972912074, 37.810116811594561 ], [ -122.411103380733024, 37.810137643311123 ], [ -122.411184890977466, 37.810277787516789 ], [ -122.411169458075165, 37.810283531382133 ], [ -122.411087948545713, 37.810143387154753 ], [ -122.411017619235537, 37.810168562307894 ], [ -122.411099128998927, 37.810308706303054 ], [ -122.411083696429642, 37.810314450151544 ], [ -122.411004005467134, 37.81017771001823 ], [ -122.410905971459442, 37.810202646996828 ], [ -122.411216101543474, 37.810750434096292 ], [ -122.411307020324173, 37.810718058221234 ], [ -122.411214614556201, 37.8105581756828 ], [ -122.411231794935318, 37.81055309021869 ], [ -122.411325542368957, 37.810697843090367 ], [ -122.411366719609191, 37.810683441322411 ], [ -122.411291830600106, 37.810531515646439 ], [ -122.411307262852333, 37.810525771776568 ], [ -122.411402864908169, 37.810675301597549 ], [ -122.411522935608915, 37.810632152260467 ], [ -122.411446209498621, 37.810476136111049 ], [ -122.411461659747943, 37.810471078652185 ], [ -122.411555407533228, 37.810615831617966 ], [ -122.41160859172949, 37.810597114547022 ], [ -122.411624041992908, 37.810592057067005 ], [ -122.411677226161913, 37.810573340239586 ], [ -122.411600605842565, 37.810421442809904 ], [ -122.411617768833821, 37.810415670846979 ], [ -122.411711534523249, 37.810561109852458 ], [ -122.411738974133357, 37.810551050960818 ], [ -122.411686385419046, 37.810391210096554 ], [ -122.411701817957677, 37.810385466169308 ], [ -122.411764700631707, 37.810541706828857 ], [ -122.411829892102887, 37.810518674961713 ], [ -122.41176529620391, 37.810363148827776 ], [ -122.411780746061879, 37.81035809133261 ], [ -122.411847054752684, 37.810512902971148 ], [ -122.411915671345156, 37.810488441813206 ], [ -122.411840781416544, 37.810336516754752 ], [ -122.411856213585779, 37.81033077281284 ], [ -122.411935941632692, 37.810468884965481 ], [ -122.411921351800245, 37.810305681309181 ], [ -122.411938638145514, 37.810304714362765 ], [ -122.411953405498025, 37.81047478237366 ], [ -122.412071780474818, 37.810433033401743 ], [ -122.412010591984938, 37.810275392256564 ], [ -122.412115642263785, 37.810455670909619 ], [ -122.412100192408317, 37.810460728448561 ], [ -122.412084406280471, 37.810452743692409 ], [ -122.41176878056207, 37.810565675738737 ], [ -122.41175334799118, 37.810571419674538 ], [ -122.411684748938015, 37.810596566899896 ], [ -122.411669298331702, 37.810601624393506 ], [ -122.411598969130708, 37.810626799885483 ], [ -122.411583536533968, 37.810632543799024 ], [ -122.411518344886403, 37.810655575529537 ], [ -122.411502912623547, 37.810661319426885 ], [ -122.411432582949345, 37.810686494275835 ], [ -122.411417150327338, 37.81069223816754 ], [ -122.411223323763636, 37.810761991358582 ], [ -122.41132311146211, 37.810939609065692 ], [ -122.411244573940635, 37.811116682909216 ], [ -122.410949479920689, 37.811220352571489 ], [ -122.410930216342933, 37.811279036146971 ], [ -122.410681455051716, 37.81136684679597 ], [ -122.410613578346386, 37.811352838185535 ], [ -122.410419696661208, 37.811420531005773 ], [ -122.410148367227194, 37.811505958616635 ], [ -122.408511879957302, 37.811283856992631 ], [ -122.407577358643394, 37.808803424612513 ], [ -122.407596251976869, 37.808797625245276 ], [ -122.408535735474004, 37.811269050069917 ], [ -122.410153064417187, 37.811486654300801 ], [ -122.41040703579597, 37.811399447392162 ], [ -122.410397994853795, 37.811384485925792 ], [ -122.410199185064457, 37.81132796106332 ], [ -122.410033026138677, 37.811396577420801 ], [ -122.410028523199003, 37.811423432538689 ], [ -122.408569038736545, 37.811217693403208 ], [ -122.408604939400348, 37.811065346992223 ], [ -122.408622314546491, 37.811067812437848 ], [ -122.408601547208733, 37.811202746444437 ], [ -122.408639776140404, 37.811208308039326 ], [ -122.408681379486623, 37.811075784005041 ], [ -122.408700502750108, 37.811078908147074 ], [ -122.408681466003685, 37.811213813888727 ], [ -122.408723173522517, 37.811220006160561 ], [ -122.408763046269982, 37.811087510102226 ], [ -122.408782169539933, 37.811090634230986 ], [ -122.40876313259281, 37.811225539991135 ], [ -122.408803092361268, 37.811231073796982 ], [ -122.408842964625947, 37.811098577717516 ], [ -122.408862088248199, 37.811101701827724 ], [ -122.408843051796623, 37.811236607869454 ], [ -122.408881280424794, 37.811242169666208 ], [ -122.408921153248102, 37.811109673549396 ], [ -122.408942006980325, 37.811112769370261 ], [ -122.408921240203938, 37.811247703157434 ], [ -122.408964677875915, 37.811253867342053 ], [ -122.409004568208587, 37.811122057634492 ], [ -122.409025404309773, 37.811124467552588 ], [ -122.409006385440833, 37.811260059513721 ], [ -122.409042866672721, 37.811264962818356 ], [ -122.409082738873835, 37.811132466653085 ], [ -122.409101862168811, 37.811135590729982 ], [ -122.409082825791046, 37.81127049654058 ], [ -122.409126264189027, 37.81127666092906 ], [ -122.409166136247578, 37.811144164735886 ], [ -122.409185259542042, 37.811147288524687 ], [ -122.409166223312823, 37.811282194348451 ], [ -122.409211409832309, 37.811289016857089 ], [ -122.409251281745554, 37.811156520635443 ], [ -122.40927213551177, 37.811159616672512 ], [ -122.40925136932222, 37.811294550516585 ], [ -122.409279162626277, 37.81129822094033 ], [ -122.40932078253644, 37.811166383119904 ], [ -122.409339888202226, 37.811168820995697 ], [ -122.409320870242325, 37.811304413000762 ], [ -122.409364308333835, 37.811310577307111 ], [ -122.409405910098016, 37.811178053025465 ], [ -122.409425033757373, 37.811181176769772 ], [ -122.409405998308287, 37.811316082900326 ], [ -122.40944074878638, 37.811321014101964 ], [ -122.40948063795679, 37.811189203966087 ], [ -122.409501474085218, 37.811191613525516 ], [ -122.409480725959895, 37.811327233846249 ], [ -122.409527624648632, 37.811333341792235 ], [ -122.409567496367472, 37.811200845459226 ], [ -122.409588350159311, 37.811203941440354 ], [ -122.40956758453207, 37.81133887533899 ], [ -122.409607544075371, 37.811344408877801 ], [ -122.409647432976385, 37.811212599235269 ], [ -122.409666538650669, 37.811215036508962 ], [ -122.4096475212896, 37.811350629114692 ], [ -122.409687480492039, 37.811356162357413 ], [ -122.409729082397121, 37.811223637951706 ], [ -122.409748205742758, 37.811226761923784 ], [ -122.40972917051603, 37.811361667836366 ], [ -122.40976739996718, 37.811367229334621 ], [ -122.409807270930216, 37.811234732926998 ], [ -122.409826394289098, 37.811237857160975 ], [ -122.409807359540622, 37.811372762805725 ], [ -122.409849067242604, 37.811378954679277 ], [ -122.409888938066231, 37.811246458244362 ], [ -122.409909791884289, 37.81124955416864 ], [ -122.409890757298484, 37.811384460375677 ], [ -122.409934195469972, 37.811390623922954 ], [ -122.409972335687982, 37.811258155482349 ], [ -122.409991388408557, 37.811258533667043 ], [ -122.410051001542016, 37.811355083215751 ], [ -122.410080225111116, 37.811347055723829 ], [ -122.410039452733983, 37.811242647397464 ], [ -122.410060076955276, 37.811236819614464 ], [ -122.410111355764599, 37.81134586484395 ], [ -122.410186754248173, 37.811315801374953 ], [ -122.410002034752523, 37.810999466686859 ], [ -122.410015948336749, 37.810934689402742 ], [ -122.410036236914948, 37.810915819594705 ], [ -122.410045418985376, 37.810868973454134 ], [ -122.40995683043738, 37.810857360334786 ], [ -122.409929090780366, 37.810990347084882 ], [ -122.409911715601922, 37.810987881557118 ], [ -122.409936471227283, 37.810873484930354 ], [ -122.409873927744698, 37.810864883322409 ], [ -122.409849172039969, 37.810979280485419 ], [ -122.409831796859692, 37.810976814671307 ], [ -122.409859730943055, 37.810851378751366 ], [ -122.409793708942289, 37.810842147255194 ], [ -122.409767522509881, 37.810968241308018 ], [ -122.409751878141279, 37.810965747731309 ], [ -122.409778063900148, 37.81083965341854 ], [ -122.409713790367476, 37.810831080014538 ], [ -122.409685856045158, 37.810956515900401 ], [ -122.409668480889763, 37.810954050611365 ], [ -122.409696415233427, 37.810828614454991 ], [ -122.40963387182353, 37.810820012994292 ], [ -122.409609415933446, 37.810946079263672 ], [ -122.409592040775962, 37.810943613688799 ], [ -122.409616496701801, 37.810817547697539 ], [ -122.409552222853762, 37.810808973936524 ], [ -122.409527766835737, 37.810935040463818 ], [ -122.4095103913304, 37.810932574607932 ], [ -122.409534847384165, 37.81080650835878 ], [ -122.409470556252117, 37.810797248385086 ], [ -122.4094443696399, 37.810923342636215 ], [ -122.409426994147125, 37.810920877042648 ], [ -122.409453181134111, 37.810794782789721 ], [ -122.40938717688249, 37.810786237228463 ], [ -122.409360972477401, 37.810911645024227 ], [ -122.409343596990126, 37.810909179418339 ], [ -122.409371532226004, 37.810783743881395 ], [ -122.409307258435504, 37.810775169986833 ], [ -122.409279323454484, 37.810900606052307 ], [ -122.409261948311553, 37.810898140154237 ], [ -122.409289883328043, 37.810772704367388 ], [ -122.409222130673314, 37.810763500569543 ], [ -122.409194195884822, 37.810888936060401 ], [ -122.409178551206097, 37.810886442413199 ], [ -122.409204755910437, 37.810761034657382 ], [ -122.409142194961319, 37.810751746498703 ], [ -122.409114277009948, 37.810877868693844 ], [ -122.409096902238019, 37.810875403314995 ], [ -122.409124837171674, 37.81074996729771 ], [ -122.409062293894237, 37.810741365533815 ], [ -122.409034358851102, 37.810866801261916 ], [ -122.409016983385186, 37.810864335607896 ], [ -122.409044919155974, 37.810738900147257 ], [ -122.408982375890048, 37.810730298066382 ], [ -122.408954440030968, 37.81085573406159 ], [ -122.408937065255401, 37.810853268110044 ], [ -122.408965000457812, 37.810727832404666 ], [ -122.408898979017181, 37.810718600127345 ], [ -122.408871043014514, 37.810844035828495 ], [ -122.408853668258587, 37.8108415704138 ], [ -122.408881603590444, 37.810716134453337 ], [ -122.408815582170575, 37.810706902129297 ], [ -122.408787646031413, 37.810832337810972 ], [ -122.408770288923606, 37.810830558546641 ], [ -122.408798206756401, 37.810704436717558 ], [ -122.408730454910014, 37.810695232351428 ], [ -122.408702518970472, 37.810820667733054 ], [ -122.408686891971527, 37.810818860457857 ], [ -122.408713079833362, 37.810692766372398 ], [ -122.408650536651805, 37.810684164390111 ], [ -122.408622600588473, 37.810809600027646 ], [ -122.408605243143839, 37.810807820744628 ], [ -122.408633161587474, 37.810681698673903 ], [ -122.408556510134844, 37.810663024107164 ], [ -122.408588984612706, 37.810512106294745 ], [ -122.408606359289806, 37.810514572023159 ], [ -122.408587340511048, 37.810650164195629 ], [ -122.408629047705375, 37.810656356501411 ], [ -122.408668902323384, 37.810523174029143 ], [ -122.408686277704945, 37.81052564000916 ], [ -122.40866552792356, 37.81066125993452 ], [ -122.408708965930458, 37.810667424477344 ], [ -122.408748820750048, 37.810534241698186 ], [ -122.40876619543765, 37.81053670740306 ], [ -122.40874717695219, 37.810672299875314 ], [ -122.408794093155748, 37.810679094270796 ], [ -122.408833947835987, 37.810545911737734 ], [ -122.408849592085303, 37.810548405436371 ], [ -122.40883057374208, 37.810683997647232 ], [ -122.408875741869977, 37.810690133853562 ], [ -122.408915614060632, 37.81055763773044 ], [ -122.408932971115192, 37.810559417248072 ], [ -122.408913970565308, 37.81069569563472 ], [ -122.408960869161646, 37.810701804075741 ], [ -122.409000741206668, 37.810569307924148 ], [ -122.409018098597954, 37.810571086874475 ], [ -122.40899736742189, 37.810707393837795 ], [ -122.409040787124908, 37.810712871283229 ], [ -122.409080641733894, 37.810579688936649 ], [ -122.409096286337629, 37.810582182322406 ], [ -122.409077268090201, 37.810717774577761 ], [ -122.40911897536337, 37.810723966710214 ], [ -122.409158829484909, 37.81059078406853 ], [ -122.409176204199355, 37.810593249712966 ], [ -122.409157186441121, 37.810728841975312 ], [ -122.409200623829136, 37.810735006073635 ], [ -122.409240478156264, 37.81060182339904 ], [ -122.409257853229136, 37.810604289300421 ], [ -122.409238852918037, 37.810740567743849 ], [ -122.409285751569428, 37.810746676055665 ], [ -122.409325605404021, 37.810613493358183 ], [ -122.409341250023104, 37.81061598671144 ], [ -122.40932224986912, 37.810752265442567 ], [ -122.4093656699755, 37.81075774303735 ], [ -122.40940554098205, 37.810625246755905 ], [ -122.409422898749284, 37.810627025915643 ], [ -122.409403898742326, 37.810763304659638 ], [ -122.409447336517104, 37.810769468661483 ], [ -122.40948544197218, 37.810635627488516 ], [ -122.409502816715019, 37.810638093359387 ], [ -122.409485547293727, 37.810774343825763 ], [ -122.409528985434875, 37.8107805080665 ], [ -122.409565359949454, 37.810646694615322 ], [ -122.409582735382628, 37.810649160188639 ], [ -122.409567196568673, 37.810785383198734 ], [ -122.409612364812048, 37.81079151884834 ], [ -122.409648756853613, 37.810658392357979 ], [ -122.409666132285238, 37.810660857644436 ], [ -122.409648863165444, 37.8107971084087 ], [ -122.409690552872675, 37.810802613901892 ], [ -122.409730423669671, 37.810670117506156 ], [ -122.409747781109076, 37.81067189662366 ], [ -122.409728781340874, 37.810808175424455 ], [ -122.409772201856015, 37.810813652863935 ], [ -122.409810324407417, 37.810680498024219 ], [ -122.409827699517621, 37.810682963841622 ], [ -122.409810430682086, 37.810819214354417 ], [ -122.4098555989803, 37.810825350459979 ], [ -122.409895451834942, 37.810692167571958 ], [ -122.409912826590485, 37.810694632833282 ], [ -122.409895222374288, 37.810817841328777 ], [ -122.409936876790525, 37.81082197386079 ], [ -122.409973639826163, 37.810703262163521 ], [ -122.409991014940061, 37.810705727682269 ], [ -122.409959938125212, 37.81084357554878 ], [ -122.410048526318235, 37.810855188946014 ], [ -122.41005622518567, 37.810817980946432 ], [ -122.410043511556253, 37.810794838529432 ], [ -122.410054194116455, 37.810739040961913 ], [ -122.410078049482479, 37.810724233171044 ], [ -122.410094488114893, 37.810623018930904 ], [ -122.410081703845862, 37.810597130219826 ], [ -122.410092509988729, 37.810546137980772 ], [ -122.410117989755761, 37.810527183534326 ], [ -122.410136070412563, 37.810422508799753 ], [ -122.410119930917134, 37.810400795044814 ], [ -122.41012762970874, 37.810363587311961 ], [ -122.410156217048225, 37.810330848613937 ], [ -122.41017620465729, 37.81023300994017 ], [ -122.410158299784257, 37.810209951610261 ], [ -122.410179752853338, 37.810101788351538 ], [ -122.410076402542387, 37.809920107344034 ], [ -122.410041882266327, 37.809924099736733 ], [ -122.410019086454341, 37.809912794612728 ], [ -122.409979357246016, 37.809916185169463 ], [ -122.409958274206915, 37.809904165299031 ], [ -122.409922023510461, 37.809908185953802 ], [ -122.409899227375504, 37.80989688081219 ], [ -122.409856019993072, 37.80989964065347 ], [ -122.409833224216882, 37.809888335493483 ], [ -122.409796955869524, 37.80989166994722 ], [ -122.409774159751635, 37.809880364506853 ], [ -122.40973616131653, 37.809883726681605 ], [ -122.409715095991231, 37.809872393479395 ], [ -122.409673618697951, 37.809875125240453 ], [ -122.409650822957047, 37.809863820045173 ], [ -122.409611093758144, 37.809867210477904 ], [ -122.409590028449543, 37.809855876978752 ], [ -122.409552029326491, 37.809859239105428 ], [ -122.40952923395507, 37.809847933880995 ], [ -122.409489504405954, 37.809851324003851 ], [ -122.409466709046484, 37.809840018767318 ], [ -122.409426961850301, 37.809842722706414 ], [ -122.409405896576772, 37.809831389174342 ], [ -122.409362689214092, 37.809834148834945 ], [ -122.409339893540192, 37.80982284385405 ], [ -122.40930537326976, 37.809826836031277 ], [ -122.409282577599768, 37.809815530764716 ], [ -122.409239369556644, 37.809818290665945 ], [ -122.409216574238357, 37.809806985106434 ], [ -122.409183784056154, 37.809810949516532 ], [ -122.409160988755644, 37.809799644220845 ], [ -122.409117781056253, 37.80980240379737 ], [ -122.409096715848435, 37.809791070484557 ], [ -122.409062177941692, 37.809794376702477 ], [ -122.40903938231871, 37.80978307138885 ], [ -122.409001401198537, 37.80978711949534 ], [ -122.408980336012803, 37.809775786161715 ], [ -122.408940588826241, 37.809778489662385 ], [ -122.408917793572726, 37.809767184319618 ], [ -122.408872855464537, 37.809769972088361 ], [ -122.408850059531787, 37.809758666743676 ], [ -122.408812061110055, 37.80976202834627 ], [ -122.408789265535077, 37.809750722984248 ], [ -122.40874778827569, 37.809753454419798 ], [ -122.408724992367041, 37.80974214905094 ], [ -122.408688724387432, 37.809745483158423 ], [ -122.408665928836285, 37.809734177772519 ], [ -122.408627929726592, 37.809737539327024 ], [ -122.408605134533261, 37.809726233923747 ], [ -122.40856886620287, 37.809729567725441 ], [ -122.408546070674888, 37.809718262316345 ], [ -122.408502862660981, 37.80972102194783 ], [ -122.408480067491766, 37.809709716520373 ], [ -122.408442068732256, 37.809713078009395 ], [ -122.408419272882526, 37.809701772581377 ], [ -122.40838475263304, 37.809705765038423 ], [ -122.408363687557454, 37.809694431319926 ], [ -122.408318732173385, 37.809696532434543 ], [ -122.408295936693619, 37.809685226977031 ], [ -122.408254459445629, 37.809687957964584 ], [ -122.408238053815026, 37.809790545390122 ], [ -122.40821721811372, 37.809788135332319 ], [ -122.408256439046454, 37.809562942360742 ], [ -122.408275562248491, 37.809566066566916 ], [ -122.408269450984477, 37.809665053549438 ], [ -122.408286825452777, 37.809667519325309 ], [ -122.40832766898086, 37.809572777430049 ], [ -122.408321557439706, 37.809671764420713 ], [ -122.408342410750109, 37.809674860622998 ], [ -122.408383254218379, 37.809580118983206 ], [ -122.408377142750709, 37.809679105976613 ], [ -122.408394517910963, 37.809681571450852 ], [ -122.408435360963296, 37.809586829524143 ], [ -122.408429249910625, 37.809685816514552 ], [ -122.408451833660507, 37.80968888497187 ], [ -122.408492676297627, 37.809594143031056 ], [ -122.408484852889558, 37.809693844457087 ], [ -122.408503958134872, 37.809696281919784 ], [ -122.408544801055655, 37.809601539955445 ], [ -122.408536959377372, 37.809700554958617 ], [ -122.408557795064482, 37.809702964959982 ], [ -122.408598638259278, 37.809608222696959 ], [ -122.408592544731249, 37.809707896412988 ], [ -122.408615128826568, 37.809710964284278 ], [ -122.40865597161357, 37.809616222281704 ], [ -122.408639231230907, 37.809705767179551 ], [ -122.408663757193779, 37.809717044567101 ], [ -122.408704599922103, 37.80962230254778 ], [ -122.408696758454738, 37.809721317561177 ], [ -122.408722821063833, 37.809725016115578 ], [ -122.408763663375069, 37.80963027408157 ], [ -122.408757553102589, 37.809729261082566 ], [ -122.408776676029532, 37.8097323854876 ], [ -122.408817518615137, 37.809637643154893 ], [ -122.408811408067763, 37.809736630164153 ], [ -122.408839165466787, 37.809738927817939 ], [ -122.408880043284086, 37.809645558612985 ], [ -122.408873932819418, 37.809744545625371 ], [ -122.40889130733342, 37.809747011586715 ], [ -122.408932150126873, 37.809652269208989 ], [ -122.408926057042962, 37.809751942941425 ], [ -122.408946892742989, 37.809754352324887 ], [ -122.408987735476614, 37.809659610202615 ], [ -122.408979894728986, 37.80975862522854 ], [ -122.409002478177683, 37.809761693586005 ], [ -122.409043320837327, 37.809666951170023 ], [ -122.40903548016307, 37.809765966199507 ], [ -122.409052855026459, 37.809768431856902 ], [ -122.409093697286551, 37.809673689703786 ], [ -122.409085856332652, 37.809772704742095 ], [ -122.409106709703295, 37.809775800809213 ], [ -122.409147552244775, 37.809681058631973 ], [ -122.409141441787753, 37.809780045663132 ], [ -122.409162295162758, 37.809783141720409 ], [ -122.409203137637348, 37.809688399524063 ], [ -122.409195297173937, 37.809787414563765 ], [ -122.409214402473708, 37.809789852185943 ], [ -122.409255244539409, 37.809695109977277 ], [ -122.409249134570899, 37.809794097008144 ], [ -122.409268257528154, 37.809797221058851 ], [ -122.409309099536273, 37.809702479106257 ], [ -122.40930298963896, 37.809801466139774 ], [ -122.409325573445983, 37.809804533880602 ], [ -122.409366415377917, 37.809709791633715 ], [ -122.409360305556405, 37.809808778670053 ], [ -122.409379410874138, 37.809811216540062 ], [ -122.409420252741057, 37.809716474274637 ], [ -122.409412429873498, 37.809816175771125 ], [ -122.409431535526792, 37.809818613077915 ], [ -122.409472376985178, 37.809723870800191 ], [ -122.409466267649847, 37.80982285783616 ], [ -122.409488851138192, 37.809825926100473 ], [ -122.409529693219639, 37.809731183791826 ], [ -122.409521853187186, 37.809830198852438 ], [ -122.409544436665897, 37.809833266556943 ], [ -122.409585278680453, 37.809738524229203 ], [ -122.409577438721357, 37.809837539293362 ], [ -122.409600022218754, 37.809840607536366 ], [ -122.409640864166391, 37.809745865189505 ], [ -122.409624124935263, 37.809835409950374 ], [ -122.409648668402042, 37.809847373575664 ], [ -122.409694718878129, 37.809753233598798 ], [ -122.409683400550946, 37.809851618269946 ], [ -122.40970946326199, 37.809855316880828 ], [ -122.409750304385923, 37.809760574507528 ], [ -122.409744195418114, 37.809859561557211 ], [ -122.409763300747386, 37.809861998815862 ], [ -122.409800681301817, 37.809767312459051 ], [ -122.409798050568668, 37.809866929913561 ], [ -122.409813677394268, 37.809868737041889 ], [ -122.409859727674416, 37.809774597001002 ], [ -122.409848409562613, 37.809872981687576 ], [ -122.409872741508423, 37.809876708014343 ], [ -122.409896207540612, 37.809779500054304 ], [ -122.409902917913584, 37.809838449581335 ], [ -122.409927956256155, 37.809869633383848 ], [ -122.410058214833967, 37.809886065459693 ], [ -122.40978805552237, 37.809411107578008 ], [ -122.409692176324654, 37.809452490025258 ], [ -122.409753394315103, 37.809476907533615 ], [ -122.409795083288799, 37.80948241299123 ], [ -122.409790439454952, 37.809503776606206 ], [ -122.409767643810284, 37.809492471432705 ], [ -122.409733123723498, 37.809496464009918 ], [ -122.40971032808983, 37.809485158825325 ], [ -122.40967926884106, 37.809489095348837 ], [ -122.409656473217836, 37.809477790153814 ], [ -122.409630622889722, 37.80948232932171 ], [ -122.409609557687361, 37.809470995825244 ], [ -122.409573289526762, 37.809474329935902 ], [ -122.409550493924201, 37.809463024720365 ], [ -122.40952291317636, 37.809467591881273 ], [ -122.409500117230408, 37.809456286387039 ], [ -122.409465597146067, 37.809460278886 ], [ -122.409442784254765, 37.809448287206727 ], [ -122.409413473087668, 37.809452882357093 ], [ -122.409390677508952, 37.809441576836036 ], [ -122.409354409006824, 37.809444910885041 ], [ -122.409331613792688, 37.809433605621535 ], [ -122.409304033042332, 37.809438172731298 ], [ -122.409281237484791, 37.809426867189075 ], [ -122.409251926309096, 37.809431462024762 ], [ -122.40922913042256, 37.809420156752616 ], [ -122.409192844962547, 37.809422804303409 ], [ -122.409170049433754, 37.809411499014253 ], [ -122.409133798935159, 37.809415519427169 ], [ -122.409110985426068, 37.809403527969394 ], [ -122.409076465338302, 37.809407520079958 ], [ -122.409053670177968, 37.809396214762678 ], [ -122.409027819488273, 37.809400753529559 ], [ -122.409005024337446, 37.809389448202872 ], [ -122.408973965091334, 37.809393384540734 ], [ -122.408951169258785, 37.809382079214799 ], [ -122.40891837994225, 37.80938604354025 ], [ -122.408895584120444, 37.809374738203552 ], [ -122.408862794804193, 37.809378702513534 ], [ -122.408839998993102, 37.809367397166127 ], [ -122.408808940093522, 37.809371333454934 ], [ -122.408786144638981, 37.809360028091476 ], [ -122.408756815463747, 37.809363936372364 ], [ -122.4087340203655, 37.80935263099321 ], [ -122.408701230358204, 37.809356595269499 ], [ -122.408678435270645, 37.809345289879595 ], [ -122.408645645263661, 37.809349254140457 ], [ -122.408622850186802, 37.809337948739781 ], [ -122.408591790941983, 37.809341884977052 ], [ -122.408568995183444, 37.809330579577171 ], [ -122.408536205869268, 37.809334543796403 ], [ -122.408513410121444, 37.809323238385751 ], [ -122.408482351223043, 37.809327174588631 ], [ -122.408459555831755, 37.809315869161935 ], [ -122.408426766172141, 37.809319833356362 ], [ -122.408403970791582, 37.809308527918944 ], [ -122.408371181132239, 37.809312492097938 ], [ -122.408350116177374, 37.809301158650754 ], [ -122.408315578458229, 37.809304464375806 ], [ -122.408292783099327, 37.809293158916844 ], [ -122.408263471569185, 37.80929775351261 ], [ -122.408240676566351, 37.80928644803798 ], [ -122.408207886561954, 37.809290412177198 ], [ -122.408185073925949, 37.809278420254287 ], [ -122.408155762741032, 37.809283014817716 ], [ -122.408132967413039, 37.809271709327838 ], [ -122.408100177755486, 37.809275673431507 ], [ -122.408077382438208, 37.809264367930865 ], [ -122.408039154542493, 37.809258805877114 ], [ -122.408058756181504, 37.809145866471148 ], [ -122.408066594168815, 37.809248747837302 ], [ -122.408087429706811, 37.809251157647353 ], [ -122.408126507222988, 37.809155071214583 ], [ -122.408120448716886, 37.809256117235002 ], [ -122.408137823084502, 37.809258583032992 ], [ -122.408178631290198, 37.80916246830698 ], [ -122.408172554871641, 37.809262828172606 ], [ -122.408196869235468, 37.809265868402413 ], [ -122.408235946617765, 37.809169781933626 ], [ -122.408229888259399, 37.809270827959395 ], [ -122.408254202289157, 37.809273868457552 ], [ -122.408293279587355, 37.809177781420765 ], [ -122.408287221320464, 37.809278827998476 ], [ -122.408308056867014, 37.809281237494979 ], [ -122.408347134113498, 37.809185150989656 ], [ -122.408332158807269, 37.809276040458201 ], [ -122.408356702573101, 37.809288004342378 ], [ -122.408397510165557, 37.809191889546774 ], [ -122.408391452032305, 37.809292935854977 ], [ -122.408412287593251, 37.809295345607659 ], [ -122.408453095124827, 37.809199231067545 ], [ -122.408447037059574, 37.809300277103894 ], [ -122.408471350761801, 37.809303317562879 ], [ -122.408512158553279, 37.809207202447709 ], [ -122.408506100235769, 37.809308249041685 ], [ -122.408521727251397, 37.8093100560612 ], [ -122.408560804236558, 37.809213969485583 ], [ -122.408554745970605, 37.809315015532796 ], [ -122.408577329958945, 37.809318083960704 ], [ -122.408618119627377, 37.809221282377308 ], [ -122.40861206179197, 37.80932232869629 ], [ -122.408636375847209, 37.809325368841058 ], [ -122.408677182759092, 37.809229254229557 ], [ -122.408671125342451, 37.80933030027127 ], [ -122.40868850009889, 37.809332766257086 ], [ -122.408727576866028, 37.809236679077507 ], [ -122.408721501183379, 37.80933703897 ], [ -122.408740606697776, 37.80933947666351 ], [ -122.408781413482515, 37.80924336201619 ], [ -122.408775356206561, 37.809344408062977 ], [ -122.40879620945104, 37.809347504459829 ], [ -122.408837016154081, 37.809251389244281 ], [ -122.408830941310313, 37.809351749130926 ], [ -122.408848316070006, 37.809354214818633 ], [ -122.4088891230695, 37.809258100128716 ], [ -122.40888306559286, 37.80935914618636 ], [ -122.408902170426074, 37.809361583864899 ], [ -122.408942977698928, 37.809265468876312 ], [ -122.408936919955934, 37.809366515216752 ], [ -122.408959503968703, 37.809369583296906 ], [ -122.408998562767209, 37.809272810139959 ], [ -122.408992505092215, 37.809373856208545 ], [ -122.409013358354017, 37.809376952566957 ], [ -122.409054147832379, 37.809280150828236 ], [ -122.409048090239509, 37.809381197174098 ], [ -122.409070674261315, 37.809384265232971 ], [ -122.409109750575112, 37.809288178476827 ], [ -122.40910369305017, 37.809389224550813 ], [ -122.409122798597593, 37.809391662456981 ], [ -122.409161857180791, 37.809294888697217 ], [ -122.409155800079347, 37.809395935042737 ], [ -122.409180113834879, 37.809398975080981 ], [ -122.409220920775525, 37.809302860271487 ], [ -122.409214863400692, 37.809403906350916 ], [ -122.409233968955562, 37.809406344239079 ], [ -122.409273045055528, 37.809310256880117 ], [ -122.409266988111227, 37.80941130350562 ], [ -122.40929130186926, 37.809414343246381 ], [ -122.409332108667385, 37.80931822812412 ], [ -122.409326033795196, 37.809418588046228 ], [ -122.409343408940103, 37.809421053929945 ], [ -122.40938421532131, 37.809324938520824 ], [ -122.409378158527119, 37.809425985151776 ], [ -122.40939901180549, 37.809429080892826 ], [ -122.409438070062492, 37.809332307591447 ], [ -122.409432013326835, 37.809433353675857 ], [ -122.409477180743622, 37.809439489653357 ], [ -122.409529287502835, 37.809446200260403 ], [ -122.409588351243272, 37.809454171101315 ], [ -122.40962830977378, 37.809459704635067 ], [ -122.409632794735771, 37.809432163090825 ], [ -122.409672911837845, 37.809443874825128 ], [ -122.409782563727632, 37.809399522210484 ], [ -122.409759009148317, 37.809358700227392 ], [ -122.409646479104083, 37.809089266648215 ], [ -122.409583548723262, 37.809065563811615 ], [ -122.409522825494292, 37.80906036618412 ], [ -122.409518181262428, 37.809081729795679 ], [ -122.409495386134864, 37.809070424563046 ], [ -122.409466075113144, 37.809075019452258 ], [ -122.409443262001091, 37.809063028052371 ], [ -122.409410472498593, 37.80906699224586 ], [ -122.409387677045885, 37.809055686998015 ], [ -122.409356617959673, 37.80905962343688 ], [ -122.409333822524488, 37.809048318453144 ], [ -122.409299302613846, 37.809052310629191 ], [ -122.40927823759111, 37.809040977347202 ], [ -122.409247178159163, 37.809044913762847 ], [ -122.409226113153551, 37.80903358074611 ], [ -122.409193341651701, 37.809038231311064 ], [ -122.409172276649144, 37.80902689801011 ], [ -122.409141217563558, 37.809030834392281 ], [ -122.409118422162948, 37.809019529092303 ], [ -122.409083884609018, 37.809022835042484 ], [ -122.409061089219591, 37.809011529731414 ], [ -122.409026569318456, 37.809015522102243 ], [ -122.409005504354852, 37.809004189046036 ], [ -122.408972714854954, 37.80900815311788 ], [ -122.408949919486972, 37.808996847785302 ], [ -122.408918860402167, 37.809000784108946 ], [ -122.408896065051678, 37.808989479040513 ], [ -122.408865005613961, 37.808993415081005 ], [ -122.408842210612988, 37.80898210972196 ], [ -122.408807673071223, 37.808985415865877 ], [ -122.408784877735343, 37.808974110501353 ], [ -122.408753818298166, 37.808978046512571 ], [ -122.408732753379752, 37.808966713133003 ], [ -122.40870344269787, 37.808971307827179 ], [ -122.408680647389247, 37.80896000271705 ], [ -122.408653066760635, 37.80896456912587 ], [ -122.408630271454712, 37.808953263731418 ], [ -122.408595751557471, 37.808957255976196 ], [ -122.408574686669112, 37.808945922568363 ], [ -122.40853841837243, 37.808949256366759 ], [ -122.4085173534951, 37.808937922948687 ], [ -122.408484564351539, 37.808941887153871 ], [ -122.408461768739215, 37.80893058200698 ], [ -122.408432440401654, 37.808934489921761 ], [ -122.408409645138534, 37.808923184484605 ], [ -122.408375125243424, 37.808927176664845 ], [ -122.408354060404321, 37.808915843492116 ], [ -122.408319522857553, 37.808919148943978 ], [ -122.408296727616332, 37.808907843484988 ], [ -122.408265686178666, 37.808912466074247 ], [ -122.408242890954767, 37.808901160879408 ], [ -122.408208353409876, 37.808904466298728 ], [ -122.408185558190112, 37.808893160818222 ], [ -122.408154499115199, 37.808897097215251 ], [ -122.40813170390588, 37.808885791724293 ], [ -122.40810064447102, 37.808889727563603 ], [ -122.408077849618252, 37.808878422056637 ], [ -122.408048538584595, 37.808883016593732 ], [ -122.408027473807891, 37.80887168336259 ], [ -122.407989475110696, 37.808875044711961 ], [ -122.407968410338185, 37.808863711195677 ], [ -122.40793908201519, 37.808867619537004 ], [ -122.407916286847737, 37.808856314004366 ], [ -122.407867659098386, 37.808850233137854 ], [ -122.407892470076433, 37.808737895932389 ], [ -122.407895080955839, 37.808839488699526 ], [ -122.407919412118858, 37.808843215435566 ], [ -122.407965428881198, 37.808747703240833 ], [ -122.407954143699186, 37.808847460639832 ], [ -122.407971518664866, 37.808849926725884 ], [ -122.408015804954957, 37.808754442230494 ], [ -122.408004537488765, 37.808854886346353 ], [ -122.408030581417677, 37.80885789835331 ], [ -122.408074867989683, 37.808762414104869 ], [ -122.408063600595128, 37.808862857951631 ], [ -122.408080957579173, 37.808864637589593 ], [ -122.408126991783163, 37.808769811495232 ], [ -122.408115706821903, 37.808869569183827 ], [ -122.408134811496907, 37.808872006712299 ], [ -122.408175637137717, 37.80877657841792 ], [ -122.408167830687617, 37.808876966281417 ], [ -122.408188666131636, 37.808879376348344 ], [ -122.408234682562991, 37.808783863774664 ], [ -122.40822341538842, 37.808884307910851 ], [ -122.4082459985258, 37.808887375864508 ], [ -122.408292014895409, 37.808791863543163 ], [ -122.408280730145464, 37.80889162097256 ], [ -122.408299853182712, 37.808894745449784 ], [ -122.408345869480527, 37.808799232832989 ], [ -122.408334584808884, 37.808898990541991 ], [ -122.40835716795533, 37.808902058474338 ], [ -122.408401454133696, 37.808806574104075 ], [ -122.408390187174248, 37.808907017981177 ], [ -122.408412770332475, 37.808910086177463 ], [ -122.408457056451198, 37.808814602061034 ], [ -122.408447501979367, 37.808914331510991 ], [ -122.408464876962967, 37.808916796975197 ], [ -122.408510893069277, 37.808821284568992 ], [ -122.408499626255633, 37.808921728456291 ], [ -122.408520461722972, 37.808924138464597 ], [ -122.408564747351377, 37.808828653764436 ], [ -122.408553480616533, 37.808929097931319 ], [ -122.40857606412726, 37.808932165816167 ], [ -122.408622080100045, 37.808836653366868 ], [ -122.408610795786757, 37.808936410826988 ], [ -122.408633379309109, 37.808939478975418 ], [ -122.408677664815599, 37.808843994782293 ], [ -122.408666398217051, 37.808944438410528 ], [ -122.408683754881451, 37.808946217690789 ], [ -122.408728058314978, 37.808851419636184 ], [ -122.408718504202398, 37.808951149107379 ], [ -122.40873414881105, 37.808953643085459 ], [ -122.40878016458673, 37.808858130300308 ], [ -122.408768880489617, 37.80895788804974 ], [ -122.408788002858174, 37.808961011909815 ], [ -122.408832288517658, 37.808865527378856 ], [ -122.408822734543293, 37.808965256858244 ], [ -122.40884531843632, 37.808968325235043 ], [ -122.408891334078092, 37.80887281240679 ], [ -122.408880067778369, 37.808973256604034 ], [ -122.40889917250513, 37.808975694008623 ], [ -122.408943458030421, 37.808880209436197 ], [ -122.408933921854199, 37.808980625361755 ], [ -122.408953027290934, 37.808983063295585 ], [ -122.408997312398199, 37.808887578434081 ], [ -122.408987758995764, 37.808987308195377 ], [ -122.409008612134684, 37.808990404005712 ], [ -122.409052897181908, 37.808894919398057 ], [ -122.409041631090304, 37.808995363335725 ], [ -122.40906421464841, 37.808998431401648 ], [ -122.409110230041378, 37.808902919037678 ], [ -122.409098946373874, 37.809002676543273 ], [ -122.409121529583416, 37.809005744329234 ], [ -122.409165814840676, 37.808910259673837 ], [ -122.409154548899522, 37.809010703622 ], [ -122.409175384406026, 37.80901311324002 ], [ -122.4092196692594, 37.808917628844746 ], [ -122.409208385736662, 37.80901738636053 ], [ -122.409229238893218, 37.809020482131849 ], [ -122.409273523674656, 37.808924997441885 ], [ -122.4092622402232, 37.809024754962692 ], [ -122.40927961492774, 37.809027220861601 ], [ -122.409325630047334, 37.808931707864964 ], [ -122.409314364326178, 37.80903215210256 ], [ -122.409336947553612, 37.809035219847274 ], [ -122.409381232551411, 37.808939735111537 ], [ -122.409369949242404, 37.809039492642384 ], [ -122.409390802418955, 37.809042588659686 ], [ -122.409422921195898, 37.808945240701227 ], [ -122.409422903348997, 37.809011853140163 ], [ -122.40946679914822, 37.809035864508125 ], [ -122.409564019832658, 37.809046651478369 ], [ -122.409549629214183, 37.809025596054688 ], [ -122.409548887643922, 37.80899676568643 ], [ -122.409387695818651, 37.808719879048233 ], [ -122.409342422574227, 37.808709624416799 ], [ -122.409302270626966, 37.808696540242821 ], [ -122.409262612300481, 37.808769974638814 ], [ -122.409243366033195, 37.808762045524638 ], [ -122.409286290635848, 37.808681004303558 ], [ -122.409251523749987, 37.808675386616557 ], [ -122.409211865384634, 37.808748820995767 ], [ -122.409192619474325, 37.8087408918677 ], [ -122.409232277847408, 37.808667457220274 ], [ -122.409218046276052, 37.808652579691525 ], [ -122.409199082493714, 37.808655633568947 ], [ -122.409183155837965, 37.808642156922303 ], [ -122.409146746495978, 37.80863999940842 ], [ -122.409132550252011, 37.808626494744182 ], [ -122.409097906968512, 37.808625682074002 ], [ -122.409081980690672, 37.808612205682593 ], [ -122.409045571705349, 37.808610047857293 ], [ -122.409029645094293, 37.808596571464371 ], [ -122.40899498416762, 37.808595072051922 ], [ -122.408979057222851, 37.808581595657749 ], [ -122.408939170155094, 37.808578807372939 ], [ -122.408923243223967, 37.808565330971156 ], [ -122.40887636397639, 37.80855990894463 ], [ -122.408862167796897, 37.808546404247657 ], [ -122.40882402846016, 37.808544274635025 ], [ -122.408771338418944, 37.808649509314336 ], [ -122.408752144836185, 37.80864363943796 ], [ -122.408804834900437, 37.808538404767106 ], [ -122.408755995512777, 37.808524087565431 ], [ -122.40870503613472, 37.808629294204955 ], [ -122.408684130157809, 37.808624138753608 ], [ -122.408736802648747, 37.808518217400646 ], [ -122.408686214896832, 37.808503241742777 ], [ -122.408633542337299, 37.808609162798888 ], [ -122.40861434946801, 37.808603292888833 ], [ -122.408665291301503, 37.808497399575863 ], [ -122.408618182709631, 37.808483054307779 ], [ -122.408565527707552, 37.808589661496853 ], [ -122.408546334502589, 37.808583791581334 ], [ -122.408598989528059, 37.808477184400701 ], [ -122.408550150206281, 37.808462866564483 ], [ -122.408494192805264, 37.808575707944065 ], [ -122.408475017602044, 37.808570524448861 ], [ -122.408530957042373, 37.808456996920889 ], [ -122.408475143235307, 37.808440732021886 ], [ -122.408419203721877, 37.80855425952376 ], [ -122.408400010537662, 37.808548389584381 ], [ -122.40845594972285, 37.808434861822526 ], [ -122.408401884334964, 37.808419255320196 ], [ -122.408345944732787, 37.808532783062383 ], [ -122.408326768858856, 37.808527599554232 ], [ -122.408382691187029, 37.808413385377861 ], [ -122.408326877445205, 37.808397120408593 ], [ -122.40827095504342, 37.808511334558837 ], [ -122.408251779872415, 37.808506151027288 ], [ -122.408325818753553, 37.80835593442692 ], [ -122.408307261690325, 37.808307477072752 ], [ -122.408233773949888, 37.808277076846032 ], [ -122.408190831757594, 37.808290132648246 ], [ -122.408103813680896, 37.80847420912815 ], [ -122.408070707145512, 37.808465817066804 ], [ -122.408167524728952, 37.808258920751598 ], [ -122.408046540290897, 37.80819907276836 ], [ -122.408024204007958, 37.808205614634559 ], [ -122.407919104249203, 37.808427066268948 ], [ -122.407885997416685, 37.80841867443587 ], [ -122.407996041444051, 37.808187528721767 ], [ -122.407913795821727, 37.808153149917878 ], [ -122.407807565173499, 37.808397968053718 ], [ -122.407786659324017, 37.808392812443081 ], [ -122.407868398033912, 37.808205388267609 ], [ -122.407660185387385, 37.808254079991599 ], [ -122.407706584592447, 37.808375566178441 ], [ -122.407613672832028, 37.80839767070411 ], [ -122.407507720833991, 37.808114394328115 ], [ -122.407549056121539, 37.808106171825735 ], [ -122.407536061536263, 37.808072046122568 ], [ -122.407462591282297, 37.808042331588446 ], [ -122.40744611868692, 37.808007575403529 ], [ -122.407487420240358, 37.807930680888951 ], [ -122.407431324879042, 37.807903432764398 ], [ -122.407355431147764, 37.80804887173035 ], [ -122.406644940711814, 37.807817946705647 ], [ -122.406816836689401, 37.809994826134172 ], [ -122.406786252768356, 37.810017295716854 ], [ -122.40617819806161, 37.810068327706666 ], [ -122.406151660268563, 37.810046094760864 ], [ -122.406135997898389, 37.810042914277616 ], [ -122.406123656094778, 37.810034186621294 ], [ -122.405904441804637, 37.807159674056429 ], [ -122.405322208784199, 37.806866923600786 ], [ -122.404400300125417, 37.808853394598287 ], [ -122.404390181819835, 37.80886385881621 ], [ -122.404376497046869, 37.808870260285914 ], [ -122.404359263409262, 37.808873285169796 ], [ -122.404316325011152, 37.808819040682266 ], [ -122.403937477028578, 37.80876197892308 ], [ -122.403923457745378, 37.80875533773979 ], [ -122.403912828869224, 37.808745895483341 ], [ -122.403907320429198, 37.80873362312807 ], [ -122.403906985949291, 37.808720580801918 ], [ -122.404717282916977, 37.806971462592401 ], [ -122.403955936424254, 37.806579276399596 ], [ -122.402761932327437, 37.808014567794245 ], [ -122.402746534430648, 37.808021683436984 ], [ -122.402731014070881, 37.808023993997161 ], [ -122.402715370212889, 37.808021499491424 ], [ -122.40235585639897, 37.807840510524542 ], [ -122.402346922703828, 37.807829667068134 ], [ -122.402341415289797, 37.807817394901065 ], [ -122.402339367950134, 37.807805066923208 ], [ -122.402340764477955, 37.807791996673913 ], [ -122.402345656255733, 37.807780243491045 ], [ -122.403421726415289, 37.806471156550586 ], [ -122.402804368682325, 37.806023074735968 ], [ -122.400943288973451, 37.807638041928655 ], [ -122.40092780379058, 37.807641725129315 ], [ -122.400912142497489, 37.807638543945465 ], [ -122.400398744529411, 37.807261566606726 ], [ -122.400572958659495, 37.806120993590262 ], [ -122.401732349384048, 37.805192215433102 ], [ -122.403194419486127, 37.805118563841738 ], [ -122.403587277644846, 37.805412029499024 ], [ -122.404128809815958, 37.80582849738515 ], [ -122.404696069838451, 37.806264743038192 ], [ -122.405495723806581, 37.806640585378162 ], [ -122.405545441190654, 37.806634177056651 ], [ -122.405362337774832, 37.805695301990511 ], [ -122.405357241074242, 37.805695943874476 ], [ -122.405356257037084, 37.805690948342658 ], [ -122.405173536628851, 37.804763212222298 ], [ -122.404986245080067, 37.803832670552438 ], [ -122.404798316633176, 37.802900762043983 ], [ -122.40352117840483, 37.80305477917063 ], [ -122.40315960682554, 37.803098380641011 ], [ -122.402973676735002, 37.802166749844929 ], [ -122.402973611142329, 37.802166419353441 ], [ -122.402883459886269, 37.801713453423041 ], [ -122.402789664410264, 37.801242172237629 ], [ -122.402604124590809, 37.800307129616797 ], [ -122.402603736644934, 37.800305175692849 ], [ -122.402420666593613, 37.799382141273902 ], [ -122.402228639238132, 37.798429701815799 ], [ -122.402040983242472, 37.797504944420098 ], [ -122.401906946643678, 37.796875708196644 ], [ -122.401853390100442, 37.796626165074152 ], [ -122.402402444702886, 37.796554566975963 ], [ -122.40291796673857, 37.796490379340391 ], [ -122.403496709817034, 37.796417485859841 ], [ -122.404412160626791, 37.796302098196776 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.405862652252878, 37.796122174218517 ], [ -122.406224736410849, 37.796077505131322 ], [ -122.406350669109713, 37.796061968858353 ], [ -122.406652352369846, 37.796024750165536 ], [ -122.407083680819795, 37.795970583772274 ], [ -122.407319877605971, 37.795940921047873 ], [ -122.407432089697608, 37.795926828732469 ], [ -122.407563768850565, 37.795910291726024 ], [ -122.40779730388941, 37.795880962818103 ], [ -122.408253202051654, 37.795823706218044 ], [ -122.408704654154022, 37.795766841471867 ], [ -122.408743234497422, 37.795761981540487 ], [ -122.409075520379659, 37.795720125786943 ], [ -122.409077868767497, 37.795719830108119 ], [ -122.409343544204319, 37.795686363949223 ], [ -122.40954722057397, 37.795660707190791 ], [ -122.40989875147848, 37.795616424537307 ], [ -122.411081667103218, 37.795467382264249 ], [ -122.411541226025591, 37.795408552082151 ], [ -122.411541229133036, 37.795408565216896 ], [ -122.411541531721369, 37.795408526524874 ], [ -122.411893657029495, 37.795363458292385 ], [ -122.412335878555311, 37.795306857217497 ], [ -122.413187771447625, 37.795197816725128 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.415005455310151, 37.795877318912275 ], [ -122.415094279286492, 37.796316514471208 ], [ -122.415172033219093, 37.796700969266084 ], [ -122.41519297245145, 37.796804503853558 ], [ -122.415290513107706, 37.797286788953464 ], [ -122.415384105347158, 37.79774954140936 ], [ -122.415384693800732, 37.797752451808172 ], [ -122.415571551794869, 37.798676329254079 ], [ -122.415572319635913, 37.798680127283284 ], [ -122.415667503417794, 37.799150732537029 ], [ -122.41576151132729, 37.799616465113374 ], [ -122.416491256558686, 37.799525268171301 ], [ -122.417385454528372, 37.799413513042644 ], [ -122.417492539304973, 37.799906258247788 ], [ -122.417586974866069, 37.800340791478632 ], [ -122.417798642975782, 37.801267347943607 ], [ -122.41787748762404, 37.801706751150704 ], [ -122.417966229723234, 37.802201308768375 ], [ -122.418151092101994, 37.803135399210959 ], [ -122.418255886912107, 37.803650215613203 ], [ -122.418215611082957, 37.803687299847837 ], [ -122.418280954876565, 37.804023161939504 ], [ -122.418341342522965, 37.804070022432718 ], [ -122.418530631419571, 37.804999043218864 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":8,\"ZIP_CODE\":94109,\"ID\":94109},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.413915879652706, 37.790463115032836 ], [ -122.412266187164306, 37.790672783986288 ], [ -122.412154318600955, 37.79012444884593 ], [ -122.412075867445836, 37.789739906579548 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.413541442066688, 37.788598204045492 ], [ -122.415185269301574, 37.788387930678724 ], [ -122.414995423274036, 37.787453843784689 ], [ -122.414806647966373, 37.786519819325378 ], [ -122.414617207572036, 37.785582474657787 ], [ -122.414430198422409, 37.784657140813145 ], [ -122.414241572919337, 37.783723783021379 ], [ -122.415882538254209, 37.783515640872785 ], [ -122.417528773439201, 37.78331088958533 ], [ -122.419181703937824, 37.783101400150407 ], [ -122.420816620257895, 37.78289417046458 ], [ -122.420817002825672, 37.782894121654586 ], [ -122.422463744273344, 37.782685368786026 ], [ -122.422597534917514, 37.782659914421728 ], [ -122.424108200948226, 37.782477051425644 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.427490665181679, 37.782522998381459 ], [ -122.427584325166535, 37.78298604740867 ], [ -122.427778871361184, 37.783924735360486 ], [ -122.427988927379005, 37.784953879052303 ], [ -122.428148186422831, 37.785784488173462 ], [ -122.428241496400901, 37.78624657727314 ], [ -122.428241829261111, 37.786248226022003 ], [ -122.428335613076769, 37.786712655890966 ], [ -122.428525011003728, 37.78765056176524 ], [ -122.428712111452299, 37.788577067507021 ], [ -122.428905308896674, 37.789533740830485 ], [ -122.429092268257477, 37.790459499306614 ], [ -122.429269817037863, 37.791338636935983 ], [ -122.429447192175289, 37.79221689778025 ], [ -122.429625242377327, 37.793098475947545 ], [ -122.429803981887915, 37.793977244761322 ], [ -122.429989276973558, 37.794894937362344 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.428537813517494, 37.796062892623418 ], [ -122.426892728667355, 37.796272251947869 ], [ -122.425249874473522, 37.79648130315109 ], [ -122.423601877370103, 37.796690947281171 ], [ -122.423601876988513, 37.796690945913937 ], [ -122.42360129621845, 37.796691056748735 ], [ -122.423601296960271, 37.796691058659476 ], [ -122.423792964234153, 37.797616581376253 ], [ -122.423793785438747, 37.7976164776058 ], [ -122.423982396975646, 37.798552421556877 ], [ -122.423982145098208, 37.798552453684835 ], [ -122.42417056430692, 37.799484532744323 ], [ -122.42417056511286, 37.799484537126212 ], [ -122.424242907462499, 37.799842398528341 ], [ -122.424358667137113, 37.800415027771606 ], [ -122.424517597070277, 37.801201189595353 ], [ -122.424538324517613, 37.801303719934985 ], [ -122.424731635083759, 37.802281009278651 ], [ -122.424731259133807, 37.802280983275736 ], [ -122.424808684502352, 37.802662664019884 ], [ -122.424918804152185, 37.803205508171892 ], [ -122.425109521046082, 37.804145845932808 ], [ -122.425204919000421, 37.804616199985951 ], [ -122.425298563577385, 37.80507790289635 ], [ -122.425486832357592, 37.806006120121552 ], [ -122.425487611207828, 37.806009959663712 ], [ -122.425531817297653, 37.806150610629167 ], [ -122.425627533230582, 37.806456787332273 ], [ -122.4257796482039, 37.806715259689653 ], [ -122.426066893674061, 37.80699787612533 ], [ -122.426256728575282, 37.807185221316665 ], [ -122.426357658303132, 37.807388412718332 ], [ -122.426488519796493, 37.807651861239542 ], [ -122.426560904719622, 37.807943556174827 ], [ -122.426613230594512, 37.808152010516238 ], [ -122.426749407153778, 37.80832970781308 ], [ -122.426815901093192, 37.808623913624309 ], [ -122.42681099272437, 37.808701593834883 ], [ -122.426830737473963, 37.808795352699491 ], [ -122.426826276753118, 37.808823581609779 ], [ -122.426828951398363, 37.808859934107588 ], [ -122.426829913470996, 37.808897001584555 ], [ -122.426829127336148, 37.808933411171161 ], [ -122.426830071586494, 37.80896979193826 ], [ -122.426848658020631, 37.809018932542891 ], [ -122.426823913850725, 37.809066034426849 ], [ -122.426821397293551, 37.809102471733105 ], [ -122.426817149990782, 37.809138937589218 ], [ -122.426811172632014, 37.809175431983689 ], [ -122.42680517743706, 37.809211239393754 ], [ -122.426799200066583, 37.80924773378711 ], [ -122.426791474798563, 37.809283569459971 ], [ -122.426783766999961, 37.80932009184702 ], [ -122.426764854928408, 37.809391820287793 ], [ -122.426753650652387, 37.809427026340984 ], [ -122.426742463842274, 37.809462919107801 ], [ -122.42673125988388, 37.809498124878161 ], [ -122.426718325162497, 37.809533359195932 ], [ -122.426703641860442, 37.809567935625843 ], [ -122.426690707106644, 37.80960316966555 ], [ -122.426674294047132, 37.809637774073536 ], [ -122.426659593228109, 37.809671663782375 ], [ -122.426641449382515, 37.809706296733168 ], [ -122.426624999956104, 37.809739528275571 ], [ -122.426606838595305, 37.809773474231129 ], [ -122.426586928644923, 37.809806762295736 ], [ -122.426568749097328, 37.809840022090548 ], [ -122.426547090883986, 37.809872652254199 ], [ -122.426527163066623, 37.80990525387346 ], [ -122.426505504455619, 37.809937883485567 ], [ -122.426482098287991, 37.809969855187106 ], [ -122.426435249574482, 37.810032425717694 ], [ -122.426410094762232, 37.810063739239673 ], [ -122.426384921777455, 37.810094366601653 ], [ -122.426359731637717, 37.810124306962969 ], [ -122.426305889769012, 37.810184245028879 ], [ -122.426277202393095, 37.810212869038814 ], [ -122.426248533505472, 37.810242179739554 ], [ -122.426219828627396, 37.810270117569488 ], [ -122.426165541280753, 37.810312894703067 ], [ -122.426148806543424, 37.810335143220563 ], [ -122.426077517869416, 37.810389871747489 ], [ -122.426029956191201, 37.810424984999884 ], [ -122.426004427087875, 37.810441883304833 ], [ -122.425978879818175, 37.810458095175186 ], [ -122.425953351037933, 37.810474993463338 ], [ -122.425902221511379, 37.810506044020848 ], [ -122.425874926298846, 37.810521597684627 ], [ -122.425847613267436, 37.810536464907365 ], [ -122.425792951557781, 37.810564826464294 ], [ -122.425738254193391, 37.810591815126337 ], [ -122.425709157251291, 37.810604651266786 ], [ -122.425621812948378, 37.810641100340696 ], [ -122.425563512153047, 37.810662654222938 ], [ -122.425532631291532, 37.810673458992433 ], [ -122.425501732623886, 37.810683577593657 ], [ -122.425472546238581, 37.810692981507543 ], [ -122.425441630096913, 37.810702413652621 ], [ -122.425410695451745, 37.810711159366136 ], [ -122.425348791232992, 37.810727278162595 ], [ -122.425317821646317, 37.810734650696368 ], [ -122.42528512127241, 37.810742051749649 ], [ -122.425254133532235, 37.81074873811216 ], [ -122.425221397542259, 37.810754766278279 ], [ -122.425190391643511, 37.810760766194903 ], [ -122.425157620386827, 37.810765421468112 ], [ -122.425124866580816, 37.810770763172656 ], [ -122.425093807273001, 37.810774704034145 ], [ -122.425028228777975, 37.810782641379291 ], [ -122.424962578393604, 37.810787833509224 ], [ -122.424931484149809, 37.810790400897922 ], [ -122.424898623523902, 37.810791623930832 ], [ -122.424876305744775, 37.810798855419677 ], [ -122.424848476260408, 37.810793815800935 ], [ -122.424819040922628, 37.810793609459935 ], [ -122.42478787514149, 37.810793431351755 ], [ -122.424758404216234, 37.810791852400563 ], [ -122.42472891548077, 37.810789586458021 ], [ -122.424699408618324, 37.810786634628094 ], [ -122.424640324731897, 37.810777984365998 ], [ -122.424610747039267, 37.810772286768845 ], [ -122.424582881984065, 37.810765873943147 ], [ -122.42455326835632, 37.810758803192932 ], [ -122.424525367734788, 37.810751018033073 ], [ -122.424497431514737, 37.810741859447681 ], [ -122.42447122609704, 37.810732672889145 ], [ -122.424443254308628, 37.810722141695749 ], [ -122.424406843510837, 37.810719989167595 ], [ -122.424404739729965, 37.810705601989262 ], [ -122.424392430564922, 37.810698249126709 ], [ -122.424380086492334, 37.810689522832604 ], [ -122.424367724289866, 37.810680110382542 ], [ -122.424357092185232, 37.810670669703008 ], [ -122.424346425188247, 37.810659856141378 ], [ -122.424337488288586, 37.810649014350894 ], [ -122.424328533252009, 37.810637486130467 ], [ -122.424321309004071, 37.810625929670387 ], [ -122.424315797750651, 37.810613658536091 ], [ -122.424310250566421, 37.810600014537307 ], [ -122.42430472117745, 37.810587056973155 ], [ -122.4243009222361, 37.810574071450297 ], [ -122.424298835580885, 37.810560370715905 ], [ -122.424298497510776, 37.810547328452351 ], [ -122.424299872431348, 37.810533571515215 ], [ -122.424301264798501, 37.810520501018615 ], [ -122.424304387263248, 37.810507402569137 ], [ -122.424309258634793, 37.810494961760924 ], [ -122.424315860102183, 37.810482492999512 ], [ -122.424324191657433, 37.810469996010013 ], [ -122.424332559488974, 37.810458871878474 ], [ -122.42434440529064, 37.810448377725301 ], [ -122.424354539139387, 37.810438598228359 ], [ -122.424368151310617, 37.810429448977779 ], [ -122.424380050823189, 37.810421013845534 ], [ -122.424395446797789, 37.810413895387818 ], [ -122.424410860570717, 37.810407463637567 ], [ -122.424426309569299, 37.810402404211651 ], [ -122.424441794853934, 37.810398717916826 ], [ -122.42445902802703, 37.810395690099803 ], [ -122.424474548198873, 37.810393376407241 ], [ -122.424495313410532, 37.810393037578869 ], [ -122.424512653696851, 37.810394128357849 ], [ -122.424528280984248, 37.810395933262043 ], [ -122.424545674311858, 37.810399083346539 ], [ -122.424561354987802, 37.810402947551076 ], [ -122.424577071263883, 37.810408184897881 ], [ -122.424591092706251, 37.810414823624235 ], [ -122.424605131932822, 37.810422148234672 ], [ -122.424619206768625, 37.810430846262342 ], [ -122.424631551158143, 37.810439571977035 ], [ -122.424642200368709, 37.810449699078426 ], [ -122.424652868070766, 37.810460512602432 ], [ -122.424661822790284, 37.810472040804292 ], [ -122.424669047076236, 37.810483597243831 ], [ -122.424676306964855, 37.810496526827265 ], [ -122.424683904979332, 37.810522498123575 ], [ -122.424685973208497, 37.810535512153116 ], [ -122.424686685078399, 37.810562969547746 ], [ -122.424688754001352, 37.810575983565833 ], [ -122.424694265313605, 37.810588254682798 ], [ -122.424703220408176, 37.810599783150167 ], [ -122.424713852193449, 37.810609223529688 ], [ -122.424727909273159, 37.81061723483468 ], [ -122.424743643057568, 37.81062315859959 ], [ -122.424759306354005, 37.810626336886287 ], [ -122.424774915898695, 37.810627455322866 ], [ -122.424844204676688, 37.810629071443891 ], [ -122.424883986990039, 37.810627735460123 ], [ -122.424942750746894, 37.810624029768022 ], [ -122.424970402172974, 37.810622204741975 ], [ -122.424999748098813, 37.810618978876803 ], [ -122.425029112521329, 37.810616439702436 ], [ -122.42505844098109, 37.810612527107622 ], [ -122.425086039005805, 37.810608643024629 ], [ -122.425115367473737, 37.810604730964819 ], [ -122.425142947684478, 37.810600160159147 ], [ -122.425172240190349, 37.810594874946659 ], [ -122.42519980259955, 37.810589617967473 ], [ -122.425229077987609, 37.810583646294816 ], [ -122.425311658363697, 37.810563756689383 ], [ -122.425339167350202, 37.810556440647844 ], [ -122.425394149679065, 37.810540434852271 ], [ -122.425449096383744, 37.810523056436082 ], [ -122.425474821139559, 37.810513709039498 ], [ -122.42550225886724, 37.810503646946287 ], [ -122.425553673774345, 37.810483579237776 ], [ -122.425579363239436, 37.810472858943186 ], [ -122.425656378173159, 37.810438638721514 ], [ -122.425680301215294, 37.81042657379389 ], [ -122.425705937577163, 37.810413794436336 ], [ -122.425729843135883, 37.81040104278366 ], [ -122.425777618636445, 37.81037416741794 ], [ -122.425799775930017, 37.810360757570926 ], [ -122.425823645502135, 37.810346633309891 ], [ -122.425845767517558, 37.810331850853636 ], [ -122.425869619617202, 37.810317040417416 ], [ -122.425891740915375, 37.81030225768918 ], [ -122.425912114318606, 37.810286817047619 ], [ -122.425934218490383, 37.810271348139892 ], [ -122.42595457407505, 37.810255221330763 ], [ -122.425976660073871, 37.81023906598594 ], [ -122.426033069704985, 37.810211362243855 ], [ -122.426044381296308, 37.810180274872565 ], [ -122.426071409213066, 37.810154424922551 ], [ -122.426098419287058, 37.810127887982347 ], [ -122.426123698921984, 37.810101379570156 ], [ -122.42617422251692, 37.810046989859728 ], [ -122.426197736051108, 37.810019136822319 ], [ -122.426222979645814, 37.809991255524551 ], [ -122.426244745255502, 37.809962744298282 ], [ -122.426268240925296, 37.809934204811682 ], [ -122.426289987997293, 37.809905007153375 ], [ -122.426310004983833, 37.8098758380223 ], [ -122.426331734895541, 37.809845953635381 ], [ -122.426350003264446, 37.809816126056347 ], [ -122.426370002384175, 37.809786270205322 ], [ -122.42640650446053, 37.80972524214534 ], [ -122.426423024539801, 37.809694756383479 ], [ -122.426439527484902, 37.809663584173109 ], [ -122.426456011918219, 37.809631725811407 ], [ -122.426470784413283, 37.809600581586317 ], [ -122.426485538743833, 37.809568751204715 ], [ -122.426498562648177, 37.809536949086315 ], [ -122.426511568735094, 37.809504460806153 ], [ -122.426524592609866, 37.809472658409931 ], [ -122.426535868255684, 37.809440198117713 ], [ -122.426547125731901, 37.809407051395155 ], [ -122.426556671294222, 37.80937461936086 ], [ -122.42657572607385, 37.809308382431155 ], [ -122.426583523043476, 37.809275292231341 ], [ -122.426589589599459, 37.809242230297407 ], [ -122.426597386209863, 37.809209140101942 ], [ -122.426601704881108, 37.809175419993217 ], [ -122.42660775360666, 37.809141671622967 ], [ -122.426612089737361, 37.809108637953877 ], [ -122.42661985311382, 37.809007562446936 ], [ -122.426622425964752, 37.808906571727555 ], [ -122.426632418849564, 37.808824688117525 ], [ -122.426616150524296, 37.80879817156422 ], [ -122.426607002185577, 37.808712480555535 ], [ -122.426596123115345, 37.808626817267942 ], [ -122.42658353220007, 37.808541869217166 ], [ -122.426569192751245, 37.808456262451642 ], [ -122.426553140769713, 37.808371370658513 ], [ -122.426535358440475, 37.808286507127633 ], [ -122.426521448780022, 37.808150762619441 ], [ -122.42648716178519, 37.808163683754294 ], [ -122.426478316482672, 37.808089661844356 ], [ -122.426472145992449, 37.808051992713068 ], [ -122.426465992974997, 37.808015010021762 ], [ -122.42645810959354, 37.807978055869093 ], [ -122.426450244018099, 37.807941787601465 ], [ -122.426431016184523, 37.807867935277343 ], [ -122.426421420613309, 37.807831695540209 ], [ -122.426410094332368, 37.807795484071661 ], [ -122.426383981060937, 37.807723117657737 ], [ -122.426370941909852, 37.807687620888885 ], [ -122.426356154936116, 37.8076514659409 ], [ -122.42634140359948, 37.807616683860772 ], [ -122.426324904443447, 37.807581243600652 ], [ -122.426289370917274, 37.807546114229226 ], [ -122.426116990256872, 37.807640950874436 ], [ -122.426004575599805, 37.807511622087709 ], [ -122.425948221432193, 37.807541385135394 ], [ -122.426072979790121, 37.807679439549148 ], [ -122.426043902201883, 37.807692962208542 ], [ -122.425788908967789, 37.807405954455511 ], [ -122.425811101339534, 37.807393917738004 ], [ -122.425841407866244, 37.807427759425885 ], [ -122.425860156968014, 37.807416465651144 ], [ -122.425910013941177, 37.807469902573231 ], [ -122.425894653990497, 37.807478394108394 ], [ -122.425932202930852, 37.807524478297722 ], [ -122.425969647726248, 37.80749983141159 ], [ -122.425933954139211, 37.807458524016887 ], [ -122.426097611558333, 37.807361083192824 ], [ -122.42613200511353, 37.807352281054179 ], [ -122.426091315966914, 37.80731860900228 ], [ -122.4260559073373, 37.80728828460429 ], [ -122.425981701022749, 37.807230438026032 ], [ -122.425942884837923, 37.807202229419872 ], [ -122.425865288869659, 37.807147185302298 ], [ -122.425824778720127, 37.807120377771732 ], [ -122.425784286059837, 37.807094256667916 ], [ -122.425743811226624, 37.807068821710658 ], [ -122.425659436564132, 37.807019381954113 ], [ -122.425575133216185, 37.806972687328404 ], [ -122.425531269042693, 37.806950055090923 ], [ -122.425487422692115, 37.806928108997688 ], [ -122.425396304969212, 37.806885646130702 ], [ -122.425305258549756, 37.806845928659463 ], [ -122.425259770989555, 37.806827443179728 ], [ -122.425212552741186, 37.806808985385324 ], [ -122.425165353022919, 37.806791214545072 ], [ -122.425118188573762, 37.806774816287508 ], [ -122.425069293788937, 37.80675844598079 ], [ -122.4250204168317, 37.806742762363598 ], [ -122.424973288049415, 37.806727737193157 ], [ -122.42492444672159, 37.806713426132198 ], [ -122.424873892869243, 37.806699830002309 ], [ -122.424802432493848, 37.806681081528858 ], [ -122.424772802670631, 37.806673324118421 ], [ -122.424744921357572, 37.806666225439869 ], [ -122.424689194317793, 37.806653400109965 ], [ -122.424659600807814, 37.806647015805055 ], [ -122.424631755098972, 37.806641289695882 ], [ -122.42460219684277, 37.806636278252959 ], [ -122.42457436893713, 37.806631238565636 ], [ -122.424544828483292, 37.806626913543731 ], [ -122.424517018379291, 37.806622560278207 ], [ -122.424487495380134, 37.806618921682983 ], [ -122.424428486015572, 37.806613017324239 ], [ -122.424398998964037, 37.806610751846591 ], [ -122.42437124224621, 37.806608457578399 ], [ -122.424341772644766, 37.806606878527504 ], [ -122.424282870046667, 37.806605092433877 ], [ -122.424224002349433, 37.806604679467583 ], [ -122.424165170926941, 37.806605639331515 ], [ -122.424135772832813, 37.80660680569077 ], [ -122.424077012229233, 37.806610511532789 ], [ -122.424018287184794, 37.806615589667317 ], [ -122.42398895989578, 37.806619501737501 ], [ -122.423959615506732, 37.806622727353634 ], [ -122.423932018904594, 37.806626611175993 ], [ -122.423873400581527, 37.806635808400436 ], [ -122.423845839538984, 37.806641064799031 ], [ -122.423816565594137, 37.806647036132823 ], [ -122.423789022338056, 37.806652979228197 ], [ -122.423759766870432, 37.806659637246682 ], [ -122.423706445903605, 37.806672867782268 ], [ -122.423653321266002, 37.806693649347707 ], [ -122.423589920527235, 37.806718718849552 ], [ -122.423526538222887, 37.806744474741464 ], [ -122.423399843925978, 37.80679873217619 ], [ -122.423338262626473, 37.806827205494514 ], [ -122.423215171014846, 37.806886897776501 ], [ -122.423155391052063, 37.806918088524029 ], [ -122.423095628820221, 37.806949965676615 ], [ -122.423037614673063, 37.806982501019867 ], [ -122.422979636035322, 37.807016409205524 ], [ -122.422923387701616, 37.807050289149693 ], [ -122.422867174874767, 37.807085541937973 ], [ -122.422807430162962, 37.807118105379153 ], [ -122.422669617719095, 37.80721100017012 ], [ -122.422538512716017, 37.807295544473774 ], [ -122.422410922154015, 37.807382091240839 ], [ -122.422406568873129, 37.807385156047935 ], [ -122.422285114644055, 37.807470669252126 ], [ -122.422159360151895, 37.8075613064347 ], [ -122.422037101638551, 37.807653259953362 ], [ -122.421916608736879, 37.807746558017001 ], [ -122.421799629592272, 37.807841858870269 ], [ -122.42168270310637, 37.807939218918449 ], [ -122.421532057745097, 37.808071464473507 ], [ -122.421789175548739, 37.808374241220797 ], [ -122.421758561641454, 37.808395342089796 ], [ -122.421493253559859, 37.808110553294661 ], [ -122.421324496714121, 37.808278803348081 ], [ -122.421367127445279, 37.808320685807196 ], [ -122.421400626951964, 37.808344175435167 ], [ -122.421417984134223, 37.808345953110788 ], [ -122.421538848573221, 37.808467594219884 ], [ -122.421521846706554, 37.808479545548913 ], [ -122.421585810928775, 37.808543055557919 ], [ -122.421558551068415, 37.808559981099798 ], [ -122.421494604621685, 37.808497157511823 ], [ -122.421482722503114, 37.808506278522707 ], [ -122.42136546133365, 37.808390072182092 ], [ -122.421343808401488, 37.808356088677073 ], [ -122.421300875659668, 37.808302536803509 ], [ -122.421061625998746, 37.808556401980894 ], [ -122.421102646347336, 37.808803641534986 ], [ -122.421495500883367, 37.809133737012722 ], [ -122.421447795704026, 37.809163356667042 ], [ -122.422886026597482, 37.810219448665336 ], [ -122.422843690020329, 37.8102558485987 ], [ -122.42136637775458, 37.809161249445332 ], [ -122.421209585435776, 37.809189899457976 ], [ -122.421035643651535, 37.808957186122555 ], [ -122.420944163231766, 37.808967603286121 ], [ -122.420910323787112, 37.808797159929568 ], [ -122.420858358549751, 37.808795946109164 ], [ -122.420717381312784, 37.80890056409217 ], [ -122.420760227934167, 37.809084595303112 ], [ -122.420694330112809, 37.809080174685604 ], [ -122.420677247269424, 37.809022081268409 ], [ -122.420415813879998, 37.809020844655912 ], [ -122.420359979289643, 37.809003898946663 ], [ -122.420384124514499, 37.808933459866985 ], [ -122.420668674045118, 37.808958355470025 ], [ -122.420635748727079, 37.808756307812423 ], [ -122.420540860637956, 37.808768840609403 ], [ -122.420461300526242, 37.808303163202581 ], [ -122.419223966222532, 37.808453096037454 ], [ -122.419086067910683, 37.807803117635643 ], [ -122.418908043871269, 37.806863030947284 ], [ -122.418856183917853, 37.806607498073362 ], [ -122.418781854642702, 37.806241248585302 ], [ -122.418781807492508, 37.806241018338142 ], [ -122.418718265666911, 37.805931520041717 ], [ -122.418530631419571, 37.804999043218864 ], [ -122.418341342522965, 37.804070022432718 ], [ -122.418280954876565, 37.804023161939504 ], [ -122.418215611082957, 37.803687299847837 ], [ -122.418255886912107, 37.803650215613203 ], [ -122.418151092101994, 37.803135399210959 ], [ -122.417966229723234, 37.802201308768375 ], [ -122.41787748762404, 37.801706751150704 ], [ -122.417798642975782, 37.801267347943607 ], [ -122.417586974866069, 37.800340791478632 ], [ -122.417492539304973, 37.799906258247788 ], [ -122.417385454528372, 37.799413513042644 ], [ -122.416491256558686, 37.799525268171301 ], [ -122.41576151132729, 37.799616465113374 ], [ -122.415667503417794, 37.799150732537029 ], [ -122.415572319635913, 37.798680127283284 ], [ -122.415571551794869, 37.798676329254079 ], [ -122.415384693800732, 37.797752451808172 ], [ -122.415384105347158, 37.79774954140936 ], [ -122.415290513107706, 37.797286788953464 ], [ -122.41519297245145, 37.796804503853558 ], [ -122.415172033219093, 37.796700969266084 ], [ -122.415094279286492, 37.796316514471208 ], [ -122.415005455310151, 37.795877318912275 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.414647632176155, 37.794109807074861 ], [ -122.414470074462415, 37.79322314646798 ], [ -122.414381908270244, 37.792784070108439 ], [ -122.414293264567746, 37.792342610236801 ], [ -122.414107303557273, 37.79141647884336 ], [ -122.413915879652706, 37.790463115032836 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":9,\"ZIP_CODE\":94111,\"ID\":94111},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.400058259594957, 37.793693587424592 ], [ -122.400148778887171, 37.794134234665414 ], [ -122.400149325778898, 37.794134165695652 ], [ -122.401314504615144, 37.793987063215781 ], [ -122.402133431503088, 37.793883778773662 ], [ -122.40215951272063, 37.793880489466119 ], [ -122.402957360909014, 37.793779857671261 ], [ -122.40308689535884, 37.793763518993806 ], [ -122.404016114128936, 37.793646309359175 ], [ -122.404370134182003, 37.793598168205968 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404710582177955, 37.794021380197684 ], [ -122.404796472837063, 37.794453004909116 ], [ -122.404875610619072, 37.794859533373135 ], [ -122.404957208224928, 37.795326691921687 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.404412160626791, 37.796302098196776 ], [ -122.403496709817034, 37.796417485859841 ], [ -122.40291796673857, 37.796490379340391 ], [ -122.402402444702886, 37.796554566975963 ], [ -122.401853390100442, 37.796626165074152 ], [ -122.401906946643678, 37.796875708196644 ], [ -122.402040983242472, 37.797504944420098 ], [ -122.402228639238132, 37.798429701815799 ], [ -122.402420666593613, 37.799382141273902 ], [ -122.402603736644934, 37.800305175692849 ], [ -122.402604124590809, 37.800307129616797 ], [ -122.402789664410264, 37.801242172237629 ], [ -122.402883459886269, 37.801713453423041 ], [ -122.402973611142329, 37.802166419353441 ], [ -122.402973676735002, 37.802166749844929 ], [ -122.40315960682554, 37.803098380641011 ], [ -122.40352117840483, 37.80305477917063 ], [ -122.404798316633176, 37.802900762043983 ], [ -122.404986245080067, 37.803832670552438 ], [ -122.405173536628851, 37.804763212222298 ], [ -122.405356257037084, 37.805690948342658 ], [ -122.405357241074242, 37.805695943874476 ], [ -122.405362337774832, 37.805695301990511 ], [ -122.405545441190654, 37.806634177056651 ], [ -122.405495723806581, 37.806640585378162 ], [ -122.404696069838451, 37.806264743038192 ], [ -122.404128809815958, 37.80582849738515 ], [ -122.403587277644846, 37.805412029499024 ], [ -122.403194419486127, 37.805118563841738 ], [ -122.401732349384048, 37.805192215433102 ], [ -122.400572958659495, 37.806120993590262 ], [ -122.400963593501643, 37.803563391134098 ], [ -122.400842454396695, 37.803429372376385 ], [ -122.400479886428016, 37.803466118528789 ], [ -122.398551197769294, 37.804602806142064 ], [ -122.398535712754665, 37.804606489036345 ], [ -122.398520052204233, 37.804603307533085 ], [ -122.398559631153262, 37.804526444219782 ], [ -122.398227927335071, 37.804281130126874 ], [ -122.398222420518948, 37.804268857764917 ], [ -122.398225564864617, 37.80425644615601 ], [ -122.399971727587683, 37.803225025895152 ], [ -122.399602741790233, 37.802807948916175 ], [ -122.397824683987196, 37.803810348092696 ], [ -122.397809199101829, 37.803814030892141 ], [ -122.397793538749667, 37.803810849291956 ], [ -122.397517150597622, 37.803494597437428 ], [ -122.397511661501127, 37.80348301148031 ], [ -122.3975148062854, 37.803470600157546 ], [ -122.399615259663278, 37.802282404799911 ], [ -122.39972332623249, 37.802176282179168 ], [ -122.399409670069147, 37.801824238674413 ], [ -122.399371692841143, 37.801828283680663 ], [ -122.399347556878283, 37.801832106025373 ], [ -122.399325151849467, 37.801835900487347 ], [ -122.399302764032285, 37.801840381390434 ], [ -122.39928039377223, 37.801845548729034 ], [ -122.399258040722614, 37.801851402508781 ], [ -122.399235705920944, 37.801857942712921 ], [ -122.399215101014406, 37.801864455053376 ], [ -122.399192800984025, 37.8018723684091 ], [ -122.399172231180287, 37.801880253347079 ], [ -122.399151696503523, 37.801889511710215 ], [ -122.397115576147101, 37.803021039584841 ], [ -122.397100091038084, 37.803024722022734 ], [ -122.397084431235825, 37.80302154059693 ], [ -122.396146888856933, 37.801962584949095 ], [ -122.396143112804936, 37.801950284664713 ], [ -122.396146275276891, 37.801938559540567 ], [ -122.396156183429241, 37.801919858733392 ], [ -122.398383194135846, 37.80067265166084 ], [ -122.398279805658603, 37.800555512940733 ], [ -122.397989357773142, 37.800230561089471 ], [ -122.395767421385884, 37.801472872307706 ], [ -122.395751936546873, 37.801476555118818 ], [ -122.395736277114864, 37.801473372963947 ], [ -122.395444597217249, 37.801167663046662 ], [ -122.395439108270097, 37.801156076993678 ], [ -122.395442253641278, 37.801143665440662 ], [ -122.397525741108808, 37.799970200398498 ], [ -122.397542727338319, 37.799957565794791 ], [ -122.397573204312565, 37.79993098020509 ], [ -122.397588407347527, 37.799916314120374 ], [ -122.397601862985724, 37.799900989703779 ], [ -122.397613588764841, 37.799885693121588 ], [ -122.397625296656045, 37.799869710378033 ], [ -122.397635256446222, 37.799853068765884 ], [ -122.397583045956239, 37.799774248814408 ], [ -122.397381692278955, 37.799548810060863 ], [ -122.397362923291766, 37.799559412884541 ], [ -122.397350478314067, 37.799546565387622 ], [ -122.39725607725606, 37.79944232919879 ], [ -122.396973022275191, 37.799609635784066 ], [ -122.396895724416169, 37.799632854304804 ], [ -122.396944532454214, 37.799578505110418 ], [ -122.39725156444139, 37.799401198172731 ], [ -122.397069823583166, 37.79919810547964 ], [ -122.39674205306757, 37.798834607866056 ], [ -122.396702784737215, 37.798855840868825 ], [ -122.396669034992286, 37.798822047566397 ], [ -122.396624664146429, 37.798846796498019 ], [ -122.396609180236553, 37.798850479143326 ], [ -122.396595251158345, 37.798847269554258 ], [ -122.396579311359616, 37.798833104740751 ], [ -122.395709347701867, 37.79933397933474 ], [ -122.395681634538477, 37.799401037179429 ], [ -122.395581007954107, 37.799459652414193 ], [ -122.395491073098484, 37.799462471471649 ], [ -122.39520444855259, 37.799625711063314 ], [ -122.394470973718072, 37.800049529845779 ], [ -122.394485183177821, 37.80006372301127 ], [ -122.394463863656298, 37.80011007561265 ], [ -122.394495883468409, 37.800143897322769 ], [ -122.394436254505337, 37.800181251519263 ], [ -122.394338714865782, 37.800157409688907 ], [ -122.39425176311174, 37.800073652886503 ], [ -122.394244541007424, 37.799994109133792 ], [ -122.39430588261277, 37.79995604107372 ], [ -122.39433954456824, 37.799986402561352 ], [ -122.394405256913004, 37.79998397351315 ], [ -122.394419466347642, 37.799998166686734 ], [ -122.395383240540369, 37.799442229567333 ], [ -122.3954446687179, 37.79940759283992 ], [ -122.395460481077649, 37.799348967284097 ], [ -122.395564480002861, 37.799286864055659 ], [ -122.395650954616428, 37.799284100562168 ], [ -122.396137291028239, 37.799011206820815 ], [ -122.39652596834469, 37.798777651667436 ], [ -122.396513506102295, 37.798764117365188 ], [ -122.396508017858096, 37.798752531345663 ], [ -122.396511162609784, 37.798740120038758 ], [ -122.396521298172757, 37.798730342643047 ], [ -122.396562296308161, 37.798709081605075 ], [ -122.396530276519627, 37.798675260441449 ], [ -122.396574647301449, 37.798650511819105 ], [ -122.396522999163949, 37.798593657491452 ], [ -122.396499841034597, 37.79856793447253 ], [ -122.396467423337157, 37.798586310883579 ], [ -122.396357190239343, 37.798472027219738 ], [ -122.396393032843889, 37.798452222590186 ], [ -122.395860656588496, 37.797870888391557 ], [ -122.395839947322898, 37.79787328122778 ], [ -122.395814065475506, 37.797876444215788 ], [ -122.395788200806479, 37.797880293644518 ], [ -122.395762353661098, 37.797884829508355 ], [ -122.395738254199486, 37.7978900242679 ], [ -122.395712442099452, 37.797895933002444 ], [ -122.395664348290325, 37.797910440317423 ], [ -122.395640318568425, 37.797918380825799 ], [ -122.395616307044421, 37.797927007209815 ], [ -122.39557006603053, 37.797946292034375 ], [ -122.395547836878862, 37.797956950195463 ], [ -122.395525625244971, 37.797968294793179 ], [ -122.395505143760289, 37.797979611576075 ], [ -122.393842000273821, 37.798938215357374 ], [ -122.393826533722603, 37.798942584352829 ], [ -122.39381087421539, 37.798939401951962 ], [ -122.393557994085199, 37.798661220282753 ], [ -122.393552505963697, 37.798649634131031 ], [ -122.393557381363038, 37.798637194571512 ], [ -122.395454327884067, 37.797548480176857 ], [ -122.395493853667872, 37.797469558194479 ], [ -122.395422650268614, 37.797392416327632 ], [ -122.395305000587612, 37.797394307309993 ], [ -122.395131013605095, 37.797494618186263 ], [ -122.394995545751499, 37.797340909474201 ], [ -122.395176347980751, 37.797236368651362 ], [ -122.395203657816268, 37.797153523030403 ], [ -122.395175134630833, 37.797121018758673 ], [ -122.395015962104765, 37.797123576784635 ], [ -122.394842063300572, 37.797227319439131 ], [ -122.394877471902745, 37.797258339703205 ], [ -122.393211663425149, 37.798247880172283 ], [ -122.393197909634068, 37.798251534593128 ], [ -122.393182250998592, 37.798248352371843 ], [ -122.392924377254829, 37.797977803222231 ], [ -122.392918871782612, 37.79796553059834 ], [ -122.392922034593937, 37.797953805551998 ], [ -122.392933760337485, 37.797938509440698 ], [ -122.393022573378389, 37.797891760094423 ], [ -122.393418332763261, 37.797664281733844 ], [ -122.39349443929828, 37.797594387482981 ], [ -122.393614723501102, 37.797492194551531 ], [ -122.39379620770903, 37.797346441866999 ], [ -122.393858891962495, 37.797293244534764 ], [ -122.39453577454303, 37.796890939592352 ], [ -122.394518070397197, 37.796875429409333 ], [ -122.394797677744066, 37.796708870398604 ], [ -122.394681926486655, 37.796581626428896 ], [ -122.394272580881463, 37.796818941592662 ], [ -122.39351702175675, 37.797258217667746 ], [ -122.39355080472113, 37.797293385033349 ], [ -122.39316857384685, 37.797508285701419 ], [ -122.393092042374221, 37.797425733952373 ], [ -122.393468994757654, 37.797207484670842 ], [ -122.393490421626595, 37.797233236341413 ], [ -122.39412658804784, 37.796863176781166 ], [ -122.393932453058198, 37.796648603921177 ], [ -122.393850369991199, 37.796687691964301 ], [ -122.393741908884792, 37.796574751186853 ], [ -122.393880385256736, 37.796507288896663 ], [ -122.393597295890373, 37.796198002709581 ], [ -122.393523863725633, 37.796236951615114 ], [ -122.393397437145097, 37.796098203833878 ], [ -122.393481197043371, 37.796057028727645 ], [ -122.393264277786542, 37.795831146645604 ], [ -122.39319930347132, 37.795862405337601 ], [ -122.393014017140644, 37.7956552428926 ], [ -122.39305487457618, 37.795628491529783 ], [ -122.392997761288825, 37.795560736430133 ], [ -122.392816418294032, 37.795643993697439 ], [ -122.391589731865537, 37.796205501614423 ], [ -122.391196361068637, 37.795779176166889 ], [ -122.39241116638668, 37.794819563891082 ], [ -122.39226709396803, 37.794667363398794 ], [ -122.391941265746169, 37.794853886332461 ], [ -122.391649609982679, 37.794547480215279 ], [ -122.391828732520565, 37.794445031825084 ], [ -122.391802064349278, 37.794417304057546 ], [ -122.391945361729881, 37.794335345459139 ], [ -122.391890260341384, 37.794278544568918 ], [ -122.392507683750154, 37.79377708635252 ], [ -122.39249932896719, 37.793768814133983 ], [ -122.392702063215523, 37.793608792659974 ], [ -122.39270797067924, 37.793614415765255 ], [ -122.394150899798674, 37.794987788593353 ], [ -122.394745701490038, 37.794478274267938 ], [ -122.394745733667804, 37.794478246556643 ], [ -122.394747579547172, 37.794479718347191 ], [ -122.394971722038662, 37.794300351169447 ], [ -122.395317307728533, 37.794023797689903 ], [ -122.39533276386878, 37.794011429145591 ], [ -122.395622922555773, 37.793779228585429 ], [ -122.395983670970253, 37.793501461560965 ], [ -122.396302004290646, 37.793256350429786 ], [ -122.396302004629632, 37.793256350149633 ], [ -122.396376076969489, 37.793198086984006 ], [ -122.396504486670068, 37.793097082520163 ], [ -122.39738344591872, 37.792405521295422 ], [ -122.397840810091907, 37.792047595331439 ], [ -122.39826444614279, 37.791716060417308 ], [ -122.398264448861653, 37.791716058450703 ], [ -122.399148598683425, 37.79101664916432 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399148705293825, 37.791016743863253 ], [ -122.399148704954868, 37.791016744143391 ], [ -122.399494165511513, 37.791324916192721 ], [ -122.399572029409299, 37.791326533708947 ], [ -122.399763353084495, 37.792257948007119 ], [ -122.399958735737897, 37.793209101900771 ], [ -122.400058259594957, 37.793693587424592 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":10,\"ZIP_CODE\":94104,\"ID\":94104},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.400067228055164, 37.790303858609981 ], [ -122.401375490979433, 37.789264321921429 ], [ -122.4018939793784, 37.788856309111516 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.401960433993054, 37.788922171387867 ], [ -122.401997470431979, 37.789102650051042 ], [ -122.402189645264357, 37.790039096806382 ], [ -122.402532679537515, 37.789995540560724 ], [ -122.40273920526738, 37.789969317269424 ], [ -122.403841563913829, 37.789829338872408 ], [ -122.403934377264903, 37.790286026286218 ], [ -122.404030724679231, 37.790760095776051 ], [ -122.404219835191427, 37.791690583910508 ], [ -122.40441842761642, 37.79263865633952 ], [ -122.404613389070732, 37.793564931583006 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404370134182003, 37.793598168205968 ], [ -122.404016114128936, 37.793646309359175 ], [ -122.40308689535884, 37.793763518993806 ], [ -122.402957360909014, 37.793779857671261 ], [ -122.40215951272063, 37.793880489466119 ], [ -122.402133431503088, 37.793883778773662 ], [ -122.401314504615144, 37.793987063215781 ], [ -122.400149325778898, 37.794134165695652 ], [ -122.400148778887171, 37.794134234665414 ], [ -122.400058259594957, 37.793693587424592 ], [ -122.399958735737897, 37.793209101900771 ], [ -122.399763353084495, 37.792257948007119 ], [ -122.399572029409299, 37.791326533708947 ], [ -122.399494165511513, 37.791324916192721 ], [ -122.399148704954868, 37.791016744143391 ], [ -122.399148705293825, 37.791016743863253 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399222136170906, 37.790956214990921 ], [ -122.399480001759599, 37.790744297277485 ], [ -122.399919790020746, 37.790414442896498 ], [ -122.400024135710936, 37.790336179680637 ], [ -122.400067228055164, 37.790303858609981 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":11,\"ZIP_CODE\":94108,\"ID\":94108},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.404219835191427, 37.791690583910508 ], [ -122.404030724679231, 37.790760095776051 ], [ -122.403934377264903, 37.790286026286218 ], [ -122.403841563913829, 37.789829338872408 ], [ -122.40273920526738, 37.789969317269424 ], [ -122.402532679537515, 37.789995540560724 ], [ -122.402189645264357, 37.790039096806382 ], [ -122.401997470431979, 37.789102650051042 ], [ -122.401960433993054, 37.788922171387867 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.402404665507532, 37.788463925757384 ], [ -122.402903089488873, 37.788085654318181 ], [ -122.40290309900054, 37.788085647297414 ], [ -122.403239953883428, 37.787802352067843 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403925775648347, 37.787250481113581 ], [ -122.404583316787637, 37.786730565897571 ], [ -122.404583317133643, 37.786730565891986 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.4045836593057, 37.786730668317922 ], [ -122.404583659644615, 37.786730668037741 ], [ -122.404849901045083, 37.78680995410096 ], [ -122.405346079234604, 37.786747618574395 ], [ -122.40639887788349, 37.786615347203238 ], [ -122.408036236552462, 37.786409613336666 ], [ -122.408226725993813, 37.78735926231473 ], [ -122.408401550373739, 37.788293193957799 ], [ -122.408595045382015, 37.789225614946417 ], [ -122.408595692092703, 37.789225606675593 ], [ -122.410242438640608, 37.789016337723744 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.412075867445836, 37.789739906579548 ], [ -122.412154318600955, 37.79012444884593 ], [ -122.412266187164306, 37.790672783986288 ], [ -122.413915879652706, 37.790463115032836 ], [ -122.414107303557273, 37.79141647884336 ], [ -122.414293264567746, 37.792342610236801 ], [ -122.414381908270244, 37.792784070108439 ], [ -122.414470074462415, 37.79322314646798 ], [ -122.414647632176155, 37.794109807074861 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.413187771447625, 37.795197816725128 ], [ -122.412335878555311, 37.795306857217497 ], [ -122.411893657029495, 37.795363458292385 ], [ -122.411541531721369, 37.795408526524874 ], [ -122.411081667103218, 37.795467382264249 ], [ -122.40989875147848, 37.795616424537307 ], [ -122.40954722057397, 37.795660707190791 ], [ -122.409343544204319, 37.795686363949223 ], [ -122.409077868767497, 37.795719830108119 ], [ -122.409075520379659, 37.795720125786943 ], [ -122.408743234497422, 37.795761981540487 ], [ -122.408704654154022, 37.795766841471867 ], [ -122.408253202051654, 37.795823706218044 ], [ -122.40779730388941, 37.795880962818103 ], [ -122.407563768850565, 37.795910291726024 ], [ -122.407432089697608, 37.795926828732469 ], [ -122.407319877605971, 37.795940921047873 ], [ -122.407083680819795, 37.795970583772274 ], [ -122.406652352369846, 37.796024750165536 ], [ -122.406350669109713, 37.796061968858353 ], [ -122.406224736410849, 37.796077505131322 ], [ -122.405862652252878, 37.796122174218517 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.404957208224928, 37.795326691921687 ], [ -122.404875610619072, 37.794859533373135 ], [ -122.404796472837063, 37.794453004909116 ], [ -122.404710582177955, 37.794021380197684 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404613389070732, 37.793564931583006 ], [ -122.40441842761642, 37.79263865633952 ], [ -122.404219835191427, 37.791690583910508 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":12,\"ZIP_CODE\":94103,\"ID\":94103},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.403877699904385, 37.770062866265505 ], [ -122.403615112128733, 37.769893190404822 ], [ -122.403406518421917, 37.769807099957397 ], [ -122.403037758616776, 37.769828657390953 ], [ -122.402926809989424, 37.76864812762463 ], [ -122.401962350744, 37.768706209224241 ], [ -122.401842503677628, 37.76743077711658 ], [ -122.400877239583821, 37.76748837440357 ], [ -122.400680751363893, 37.767503407870436 ], [ -122.4004285874814, 37.76730030349627 ], [ -122.399677637243116, 37.766695444602661 ], [ -122.39980174760619, 37.766587745781216 ], [ -122.399760960198876, 37.766250252443854 ], [ -122.40075504647362, 37.766190486570189 ], [ -122.401720515816834, 37.766132520250999 ], [ -122.401604204317508, 37.764894634517518 ], [ -122.401598999326723, 37.764839236400867 ], [ -122.402563396844656, 37.764781034314389 ], [ -122.403527034519726, 37.764722870371436 ], [ -122.404496977488066, 37.764664317561468 ], [ -122.404841911264157, 37.764643492872132 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.405089603673972, 37.764628538361961 ], [ -122.405463420216606, 37.764605967981389 ], [ -122.406005237363416, 37.764573252226405 ], [ -122.406429152848446, 37.764547653527842 ], [ -122.406429154577822, 37.764547653499882 ], [ -122.406859230067056, 37.764521681123377 ], [ -122.407419737953433, 37.764487829924626 ], [ -122.407534229395537, 37.765783299286987 ], [ -122.408544242939584, 37.765722599679115 ], [ -122.410486689274492, 37.765605838622655 ], [ -122.411455262151748, 37.765547605227276 ], [ -122.412416426589019, 37.765489809283601 ], [ -122.413105243007919, 37.765447608576679 ], [ -122.415308388038014, 37.765314639624691 ], [ -122.416387522888556, 37.765249494040667 ], [ -122.41748659577631, 37.765183134636899 ], [ -122.418579138459435, 37.765117159398407 ], [ -122.418698425401715, 37.765109955145007 ], [ -122.419668972681833, 37.765051337054757 ], [ -122.420482492120144, 37.765002197009743 ], [ -122.420580607922048, 37.764996270018614 ], [ -122.420901270224292, 37.764976898858535 ], [ -122.421239593012103, 37.764956459387825 ], [ -122.421381475659516, 37.764947887797014 ], [ -122.421886445840627, 37.764917378530825 ], [ -122.42285341443899, 37.764858950163855 ], [ -122.423112421134832, 37.764843298770074 ], [ -122.424102683503691, 37.764783452170008 ], [ -122.424574375550648, 37.764754942537614 ], [ -122.426381062356072, 37.764645726997848 ], [ -122.426381062758907, 37.764645729188807 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426490697526233, 37.765773605772118 ], [ -122.426518991136163, 37.766066400051031 ], [ -122.426538465047457, 37.766267922745747 ], [ -122.426572006355102, 37.766615035704866 ], [ -122.426615944414863, 37.767069730096857 ], [ -122.426693183296933, 37.76786901846797 ], [ -122.426693183304053, 37.767869018742559 ], [ -122.426902347012131, 37.769049368265463 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.424852598682307, 37.770747745074537 ], [ -122.423971653706317, 37.771401668385572 ], [ -122.423707555870649, 37.771638138620823 ], [ -122.42361952356508, 37.771716961429995 ], [ -122.423479344850293, 37.771827141530693 ], [ -122.423258913662679, 37.772000399157754 ], [ -122.422619690282815, 37.772502817311128 ], [ -122.422169936976104, 37.77285593639396 ], [ -122.421941247178026, 37.773036050819961 ], [ -122.421284370393934, 37.77356039048216 ], [ -122.421008229272687, 37.773780811646674 ], [ -122.420910608715644, 37.773852419132908 ], [ -122.420699239041511, 37.774007568561714 ], [ -122.420698854806645, 37.774007872313192 ], [ -122.42069877383058, 37.774007805509001 ], [ -122.420254278900828, 37.774358674888319 ], [ -122.420254205644639, 37.774358732668077 ], [ -122.419544527888206, 37.774918988504218 ], [ -122.419256088091217, 37.775146694295351 ], [ -122.419256086565611, 37.775146688826375 ], [ -122.419255604623004, 37.775147068879761 ], [ -122.419255606501622, 37.775147074617692 ], [ -122.419255600381447, 37.775147078837719 ], [ -122.418683590110874, 37.775586568849455 ], [ -122.417757669457401, 37.776334062520824 ], [ -122.417501462863584, 37.776540893581355 ], [ -122.416757677810395, 37.777126789723972 ], [ -122.416291701725271, 37.777493842582118 ], [ -122.416291701393504, 37.777493843136881 ], [ -122.416024573316548, 37.777713893977598 ], [ -122.415925978170975, 37.777795112873314 ], [ -122.414741221622947, 37.778719429321896 ], [ -122.412512255967954, 37.78047851222005 ], [ -122.412243715889375, 37.780689421480552 ], [ -122.411972359306191, 37.780902540886757 ], [ -122.411972358960256, 37.78090254089237 ], [ -122.411625130786987, 37.781189627190479 ], [ -122.410716598656492, 37.781899054565059 ], [ -122.410292026384454, 37.782230575136197 ], [ -122.40895215871069, 37.783287852576514 ], [ -122.408597310055299, 37.783569788681966 ], [ -122.408066548174432, 37.783991486066526 ], [ -122.408066547510714, 37.783991487176031 ], [ -122.407625723544982, 37.784337071806426 ], [ -122.406821367207598, 37.78496763543707 ], [ -122.405831011063668, 37.78574398921409 ], [ -122.405371564397683, 37.786107287119592 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.404583317133643, 37.786730565891986 ], [ -122.404583316787637, 37.786730565897571 ], [ -122.403925775648347, 37.787250481113581 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403342641535644, 37.787274627660381 ], [ -122.403028734856406, 37.78702485040828 ], [ -122.403028193190352, 37.787024418822838 ], [ -122.402575227394536, 37.786678208153539 ], [ -122.402025419766176, 37.786248152290575 ], [ -122.401489835489045, 37.785829214187501 ], [ -122.401480059865179, 37.785821567351917 ], [ -122.400998469557948, 37.785444857061968 ], [ -122.400994549885382, 37.785441790992316 ], [ -122.400468483886854, 37.785030285141588 ], [ -122.399369533643153, 37.785899247741973 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.398930331092998, 37.783784933304034 ], [ -122.399891477967842, 37.783025880124256 ], [ -122.400019020063766, 37.782925153640136 ], [ -122.400374366843309, 37.782644515545172 ], [ -122.401159718585049, 37.782024266952142 ], [ -122.403387209275081, 37.78026497235011 ], [ -122.404431250807633, 37.779440334605447 ], [ -122.405615266425855, 37.778505104539292 ], [ -122.405068508898097, 37.778068981419388 ], [ -122.404605505853894, 37.777699659794813 ], [ -122.404070256642925, 37.777272702482797 ], [ -122.403673439982725, 37.776956165214415 ], [ -122.403505778866929, 37.776822422431877 ], [ -122.40337073826143, 37.776714700215486 ], [ -122.402524096584528, 37.776039321403807 ], [ -122.400981567656032, 37.774808775969753 ], [ -122.400592319049849, 37.774498244536282 ], [ -122.400212340771517, 37.774195105423317 ], [ -122.400212340411557, 37.77419510487973 ], [ -122.399433130909728, 37.773573455617395 ], [ -122.401656680421155, 37.7718171255028 ], [ -122.403877699904385, 37.770062866265505 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":13,\"ZIP_CODE\":94115,\"ID\":94115},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.447679208068394, 37.79170289872647 ], [ -122.44720637942703, 37.791763091830468 ], [ -122.446299547924625, 37.791878529215651 ], [ -122.446299573716246, 37.791878694429421 ], [ -122.446446033268856, 37.792810866507708 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446587034424141, 37.793765298555968 ], [ -122.445035345747925, 37.79396273368863 ], [ -122.443384198876302, 37.794172877898092 ], [ -122.441712771485612, 37.794385578392699 ], [ -122.440870170501768, 37.794492795601883 ], [ -122.440868123190498, 37.794493055920753 ], [ -122.440045216960385, 37.794597761146619 ], [ -122.438402484723895, 37.794806973138577 ], [ -122.436760975027326, 37.795016021968365 ], [ -122.435113289920963, 37.795225833115033 ], [ -122.433469062960725, 37.795435179810816 ], [ -122.431827832563371, 37.79564412085616 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.429989276973558, 37.794894937362344 ], [ -122.429803981887915, 37.793977244761322 ], [ -122.429625242377327, 37.793098475947545 ], [ -122.429447192175289, 37.79221689778025 ], [ -122.429269817037863, 37.791338636935983 ], [ -122.429092268257477, 37.790459499306614 ], [ -122.428905308896674, 37.789533740830485 ], [ -122.428712111452299, 37.788577067507021 ], [ -122.428525011003728, 37.78765056176524 ], [ -122.428335613076769, 37.786712655890966 ], [ -122.428241829261111, 37.786248226022003 ], [ -122.428241496400901, 37.78624657727314 ], [ -122.428148186422831, 37.785784488173462 ], [ -122.427988927379005, 37.784953879052303 ], [ -122.427778871361184, 37.783924735360486 ], [ -122.427584325166535, 37.78298604740867 ], [ -122.427490665181679, 37.782522998381459 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.42720275579525, 37.781117127328834 ], [ -122.427016756808811, 37.780193030481158 ], [ -122.42868554474839, 37.779980201690215 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.431951802315041, 37.779564148987781 ], [ -122.433595938040227, 37.779354585436536 ], [ -122.435241012398947, 37.779144878458894 ], [ -122.436884629197735, 37.778937753600381 ], [ -122.437707163233824, 37.778832380147421 ], [ -122.43855227772093, 37.778724107938281 ], [ -122.440213334218711, 37.778511463388014 ], [ -122.441865640580161, 37.778299553615973 ], [ -122.441865640558632, 37.778299552792262 ], [ -122.441865967212379, 37.778299511152262 ], [ -122.441865967226732, 37.778299511701405 ], [ -122.443506919728407, 37.778089213415292 ], [ -122.445155752336433, 37.777886506805444 ], [ -122.446846469920573, 37.777669108909684 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.447351927163439, 37.780152061280916 ], [ -122.447517982733274, 37.781069510753063 ], [ -122.447576361567485, 37.781175778551805 ], [ -122.447655108274333, 37.781537309189886 ], [ -122.447637300982407, 37.781719177617617 ], [ -122.447450898997985, 37.782195217632704 ], [ -122.447301513844337, 37.78239164221629 ], [ -122.447283893261542, 37.782434393634787 ], [ -122.447225622636566, 37.782575775503396 ], [ -122.447173033991248, 37.782710075581413 ], [ -122.447149436147441, 37.783030677550911 ], [ -122.447549635601661, 37.784906798974511 ], [ -122.447578549915917, 37.784998355250835 ], [ -122.447620107068687, 37.785083635258417 ], [ -122.447621599012791, 37.785194684567394 ], [ -122.447605133356163, 37.785275579171099 ], [ -122.447533801639068, 37.785411415165207 ], [ -122.446849274413296, 37.786186897081585 ], [ -122.446738337622591, 37.786243163870765 ], [ -122.446610465639836, 37.786302298311156 ], [ -122.446792779960703, 37.787261853518181 ], [ -122.4469741664482, 37.788186460801001 ], [ -122.448656220796209, 37.787977164570449 ], [ -122.448835781299294, 37.788856511426609 ], [ -122.449011564525605, 37.789726786022129 ], [ -122.449189583391288, 37.790608108274284 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.447679208068394, 37.79170289872647 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":14,\"ZIP_CODE\":94102,\"ID\":94102},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.419255600381447, 37.775147078837719 ], [ -122.419255606501622, 37.775147074617692 ], [ -122.419255604623004, 37.775147068879761 ], [ -122.419256086565611, 37.775146688826375 ], [ -122.419256088091217, 37.775146694295351 ], [ -122.419544527888206, 37.774918988504218 ], [ -122.420254205644639, 37.774358732668077 ], [ -122.420254278900828, 37.774358674888319 ], [ -122.42069877383058, 37.774007805509001 ], [ -122.420698854806645, 37.774007872313192 ], [ -122.420699239041511, 37.774007568561714 ], [ -122.420910608715644, 37.773852419132908 ], [ -122.421008229272687, 37.773780811646674 ], [ -122.421284370393934, 37.77356039048216 ], [ -122.421941247178026, 37.773036050819961 ], [ -122.422169936976104, 37.77285593639396 ], [ -122.422619690282815, 37.772502817311128 ], [ -122.423258913662679, 37.772000399157754 ], [ -122.423479344850293, 37.771827141530693 ], [ -122.42361952356508, 37.771716961429995 ], [ -122.423707555870649, 37.771638138620823 ], [ -122.423971653706317, 37.771401668385572 ], [ -122.424852598682307, 37.770747745074537 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.426683978032827, 37.769533626427062 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.428428952948465, 37.770451409238284 ], [ -122.428426180541749, 37.770451760600665 ], [ -122.428520173517541, 37.770917853912216 ], [ -122.428614043687816, 37.771383330257258 ], [ -122.428708105393241, 37.771849753810166 ], [ -122.428802101899663, 37.772315846507482 ], [ -122.428990051967787, 37.773247800757233 ], [ -122.42917834319843, 37.774181422747475 ], [ -122.429178342852524, 37.774181422753124 ], [ -122.429178383795261, 37.774181626728783 ], [ -122.429178384487102, 37.774181626717471 ], [ -122.429272630354163, 37.774648924607646 ], [ -122.429365859793393, 37.775111180032617 ], [ -122.42955405641294, 37.77604428584678 ], [ -122.429622603726443, 37.776331628240193 ], [ -122.429626330769736, 37.776513151917612 ], [ -122.429686883301301, 37.776976022351889 ], [ -122.429849199803343, 37.777919295570904 ], [ -122.430047265291023, 37.778850928515844 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.42868554474839, 37.779980201690215 ], [ -122.427016756808811, 37.780193030481158 ], [ -122.42720275579525, 37.781117127328834 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.424108200948226, 37.782477051425644 ], [ -122.422597534917514, 37.782659914421728 ], [ -122.422463744273344, 37.782685368786026 ], [ -122.420817002825672, 37.782894121654586 ], [ -122.420816620257895, 37.78289417046458 ], [ -122.419181703937824, 37.783101400150407 ], [ -122.417528773439201, 37.78331088958533 ], [ -122.415882538254209, 37.783515640872785 ], [ -122.414241572919337, 37.783723783021379 ], [ -122.414430198422409, 37.784657140813145 ], [ -122.414617207572036, 37.785582474657787 ], [ -122.414806647966373, 37.786519819325378 ], [ -122.414995423274036, 37.787453843784689 ], [ -122.414995029297003, 37.787453894134082 ], [ -122.415185269301574, 37.788387930678724 ], [ -122.413541442066688, 37.788598204045492 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.410242438640608, 37.789016337723744 ], [ -122.408595692092703, 37.789225606675593 ], [ -122.408595045382015, 37.789225614946417 ], [ -122.408401550373739, 37.788293193957799 ], [ -122.408226725993813, 37.78735926231473 ], [ -122.408036236552462, 37.786409613336666 ], [ -122.40639887788349, 37.786615347203238 ], [ -122.405346079234604, 37.786747618574395 ], [ -122.404849901045083, 37.78680995410096 ], [ -122.404583659644615, 37.786730668037741 ], [ -122.4045836593057, 37.786730668317922 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.405371564397683, 37.786107287119592 ], [ -122.405831011063668, 37.78574398921409 ], [ -122.406821367207598, 37.78496763543707 ], [ -122.407625723544982, 37.784337071806426 ], [ -122.408066547510714, 37.783991487176031 ], [ -122.408066548174432, 37.783991486066526 ], [ -122.408597310055299, 37.783569788681966 ], [ -122.40895215871069, 37.783287852576514 ], [ -122.410292026384454, 37.782230575136197 ], [ -122.410716598656492, 37.781899054565059 ], [ -122.411625130786987, 37.781189627190479 ], [ -122.411972358960256, 37.78090254089237 ], [ -122.411972359306191, 37.780902540886757 ], [ -122.412243715889375, 37.780689421480552 ], [ -122.412512255967954, 37.78047851222005 ], [ -122.413308361492682, 37.779850246537862 ], [ -122.414741221622947, 37.778719429321896 ], [ -122.415925978170975, 37.777795112873314 ], [ -122.416024573316548, 37.777713893977598 ], [ -122.416291701393504, 37.777493843136881 ], [ -122.416291701725271, 37.777493842582118 ], [ -122.416757677810395, 37.777126789723972 ], [ -122.417501462863584, 37.776540893581355 ], [ -122.417757669457401, 37.776334062520824 ], [ -122.418683590110874, 37.775586568849455 ], [ -122.419255600381447, 37.775147078837719 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":15,\"ZIP_CODE\":94124,\"ID\":94124},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.398991043085772, 37.715338797679301 ], [ -122.398953101659146, 37.715385829745912 ], [ -122.398839335741798, 37.715526852955328 ], [ -122.398735527529269, 37.715727131371494 ], [ -122.398656056102226, 37.71588045500463 ], [ -122.398643015232707, 37.715917722950913 ], [ -122.398565370170317, 37.716139616445538 ], [ -122.398550700112352, 37.7161815389411 ], [ -122.398499278161879, 37.716543657492224 ], [ -122.398561414050022, 37.716897923924947 ], [ -122.398668885707423, 37.717503589023465 ], [ -122.398686680058731, 37.717603871431201 ], [ -122.39871606167118, 37.717769450850419 ], [ -122.398759937061044, 37.718047446028777 ], [ -122.398787867001658, 37.718224410399863 ], [ -122.398799134608808, 37.718275024173522 ], [ -122.398913161799925, 37.718792447247658 ], [ -122.398923285260594, 37.718836507697276 ], [ -122.398942440206895, 37.718921151552976 ], [ -122.398943601880902, 37.718926286066001 ], [ -122.399066465573654, 37.719469200866385 ], [ -122.399125486878972, 37.719730004239857 ], [ -122.399208340593788, 37.719991530079234 ], [ -122.399309038031902, 37.720278334103305 ], [ -122.399419264352986, 37.720532021245042 ], [ -122.399600300155441, 37.720920363637823 ], [ -122.399830553211288, 37.721427080386505 ], [ -122.399925016822507, 37.721517272170509 ], [ -122.40038213245046, 37.722633285332655 ], [ -122.400661931084002, 37.723316373994798 ], [ -122.400784747286778, 37.723616209251418 ], [ -122.40078474798517, 37.723616209514844 ], [ -122.401182917574161, 37.724588254281116 ], [ -122.401471218295981, 37.725516183407578 ], [ -122.401474642450822, 37.725528485745301 ], [ -122.401822425920514, 37.726775260854211 ], [ -122.401875118236148, 37.726964759399728 ], [ -122.402039596395539, 37.727554690472438 ], [ -122.402105120777605, 37.727789703519925 ], [ -122.402209197740476, 37.728101973566574 ], [ -122.402752244737584, 37.729357163240891 ], [ -122.403021622188092, 37.729901901370539 ], [ -122.403400462440644, 37.730658352585962 ], [ -122.403961731325225, 37.731610333558521 ], [ -122.40405482060109, 37.731789516245591 ], [ -122.40465709692171, 37.732948781873532 ], [ -122.404657097641305, 37.732948782960683 ], [ -122.404878800551302, 37.733336356125804 ], [ -122.405096042524178, 37.733716127227488 ], [ -122.405494571184249, 37.734287050530888 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.406454646881144, 37.735510813404687 ], [ -122.406618861734884, 37.735896873766244 ], [ -122.40670383539134, 37.73630860402433 ], [ -122.406910504104971, 37.737965866913434 ], [ -122.406900092501772, 37.738009155282221 ], [ -122.406867742024218, 37.738143654094841 ], [ -122.406895428755547, 37.738323651840822 ], [ -122.406915744052654, 37.738455725148341 ], [ -122.40693392611557, 37.738601770051126 ], [ -122.406945393527934, 37.738693880058896 ], [ -122.406939830707117, 37.738876165961258 ], [ -122.406936851773693, 37.739087891690275 ], [ -122.406930338448149, 37.739188255204461 ], [ -122.40692734433064, 37.739234393770168 ], [ -122.406923419099328, 37.739294876190314 ], [ -122.406917382338506, 37.739360326809205 ], [ -122.40691098434678, 37.739429695993088 ], [ -122.406910847801768, 37.73943120901172 ], [ -122.406977761294414, 37.739566302699039 ], [ -122.406889400847547, 37.739668564915668 ], [ -122.407641980575562, 37.73960607595005 ], [ -122.407891817991583, 37.739621065115479 ], [ -122.407910235902733, 37.739622169745928 ], [ -122.408137030577961, 37.739635775942595 ], [ -122.408114071971212, 37.739818086787388 ], [ -122.408049229318891, 37.740339110042903 ], [ -122.407964646391505, 37.740662552272049 ], [ -122.407846654253319, 37.741024367104636 ], [ -122.407757062965644, 37.741253133394146 ], [ -122.407625696256517, 37.741528038897464 ], [ -122.407448073794797, 37.741860521932459 ], [ -122.407209491524853, 37.742235666028684 ], [ -122.407179681106143, 37.742274728523746 ], [ -122.406881612449212, 37.742665295031905 ], [ -122.406149889909912, 37.743534947555823 ], [ -122.406047831839984, 37.743656241756561 ], [ -122.405778655290121, 37.743989539741165 ], [ -122.405747110957691, 37.744028598841339 ], [ -122.405475695814175, 37.744427001636382 ], [ -122.405444172050537, 37.744486004341084 ], [ -122.405202201087945, 37.744938889063008 ], [ -122.405179630488661, 37.744981134097749 ], [ -122.405051159825348, 37.745369647465395 ], [ -122.40491012189095, 37.745826558312224 ], [ -122.40472494664624, 37.746505061967419 ], [ -122.404496510435962, 37.747208430674682 ], [ -122.404390053649081, 37.747536214374357 ], [ -122.404249303912664, 37.747969580021021 ], [ -122.404105247604519, 37.748421906188959 ], [ -122.404104297626958, 37.748424888760844 ], [ -122.404103617259366, 37.748427025595028 ], [ -122.404051662308461, 37.74859015799958 ], [ -122.403990987296382, 37.74878066971192 ], [ -122.403865672790147, 37.749174139069758 ], [ -122.403826366530197, 37.749298003881435 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403667613571599, 37.749458113882532 ], [ -122.403568849778438, 37.749475672986911 ], [ -122.403260968692152, 37.749530409553117 ], [ -122.403203674195439, 37.749534260068266 ], [ -122.402968925612569, 37.749550036933734 ], [ -122.402866309087059, 37.749478458911533 ], [ -122.402866303074774, 37.749478453789486 ], [ -122.402746245055127, 37.749375020002113 ], [ -122.402215286387033, 37.749358244852793 ], [ -122.402063015501824, 37.749396333205695 ], [ -122.40206163398004, 37.749396678541473 ], [ -122.397285415820363, 37.749684622841229 ], [ -122.396273544250747, 37.74974560058007 ], [ -122.395307862010839, 37.749803786070743 ], [ -122.395231400136637, 37.749808114846253 ], [ -122.393340265733656, 37.749915158351328 ], [ -122.393322792145199, 37.749917097944952 ], [ -122.393314555211816, 37.749918012051069 ], [ -122.392381992909165, 37.750021506018193 ], [ -122.392354048972692, 37.750025545503377 ], [ -122.392350740047519, 37.750026024134087 ], [ -122.391929934247997, 37.750086859865874 ], [ -122.391714686151033, 37.750109474739723 ], [ -122.391705979739527, 37.750110389401705 ], [ -122.391693805117924, 37.750111668488358 ], [ -122.391418367221192, 37.750140606282606 ], [ -122.390433038183829, 37.750118341633915 ], [ -122.389478017653872, 37.750145107673561 ], [ -122.388507729357968, 37.750205975353602 ], [ -122.387585270262662, 37.750263378063039 ], [ -122.387633974640565, 37.751559603455405 ], [ -122.387756280477618, 37.752835611637821 ], [ -122.386811646507837, 37.752890377320497 ], [ -122.385845701796796, 37.752946370817803 ], [ -122.38487975596891, 37.753002356118301 ], [ -122.383913808692512, 37.753058333776714 ], [ -122.383438933023925, 37.753085850281579 ], [ -122.382951309683904, 37.753114103639163 ], [ -122.382560989570067, 37.75313671714089 ], [ -122.382560765120331, 37.753136419940226 ], [ -122.381985040410456, 37.753169774197133 ], [ -122.381708677132991, 37.753185861496782 ], [ -122.38168223616799, 37.75316636834409 ], [ -122.381661069503124, 37.753150224878624 ], [ -122.381641614233217, 37.753133367358956 ], [ -122.381622142285593, 37.753115823651214 ], [ -122.381604399103537, 37.753098251789652 ], [ -122.381556374012021, 37.753046140647704 ], [ -122.381542176020147, 37.753031946080576 ], [ -122.381526284033754, 37.753019152008761 ], [ -122.381510409098041, 37.753007044663264 ], [ -122.381492804694034, 37.752994964377287 ], [ -122.381475235761897, 37.75298425724899 ], [ -122.381436673423011, 37.752964271381444 ], [ -122.381417427536135, 37.752955650919624 ], [ -122.381375546404385, 37.752941211547565 ], [ -122.381354640609899, 37.75293536475278 ], [ -122.381333752201158, 37.75293020440273 ], [ -122.381311151719544, 37.752925758105562 ], [ -122.381288586703278, 37.752922684964496 ], [ -122.381266038711004, 37.75292029772389 ], [ -122.381243508115659, 37.752918597476601 ], [ -122.381221011931387, 37.752918269852813 ], [ -122.381184718304496, 37.752919535917627 ], [ -122.381162274601664, 37.752921267347901 ], [ -122.381139865666057, 37.752924371945412 ], [ -122.381117473763211, 37.752928162992639 ], [ -122.381095099929965, 37.752932640473013 ], [ -122.381072743135178, 37.752937804677757 ], [ -122.381050368943576, 37.752942281880571 ], [ -122.381027977030755, 37.752946072910795 ], [ -122.38100556843483, 37.752949177751802 ], [ -122.38098141298515, 37.752951623194257 ], [ -122.380958968929022, 37.752953354865902 ], [ -122.380936508184703, 37.752954400073762 ], [ -122.380889821786923, 37.752955145192665 ], [ -122.380865579793621, 37.752954158637948 ], [ -122.380843066568161, 37.752953144488032 ], [ -122.380797970609038, 37.752948370381233 ], [ -122.380773676141516, 37.752945324467625 ], [ -122.380728475930468, 37.752936431643164 ], [ -122.380707587564899, 37.752931271182163 ], [ -122.380684952713352, 37.752925451862353 ], [ -122.380667539609021, 37.752920922661339 ], [ -122.380641464261203, 37.752915844962516 ], [ -122.380615405953847, 37.752911453986556 ], [ -122.380589365011275, 37.752907748904235 ], [ -122.38056161234158, 37.752904758130924 ], [ -122.38053562319034, 37.752903112662466 ], [ -122.380507905274172, 37.752901494773603 ], [ -122.380480204733729, 37.752900563326783 ], [ -122.380433519056851, 37.752901308253911 ], [ -122.380238268028847, 37.752909917328637 ], [ -122.380029271599042, 37.752922178983432 ], [ -122.379994654613483, 37.752921358032168 ], [ -122.379959933403185, 37.752916418104356 ], [ -122.379944267166053, 37.752912547382195 ], [ -122.379912864887501, 37.752902060691724 ], [ -122.379897129538875, 37.752895444712365 ], [ -122.379883105576184, 37.752888114156903 ], [ -122.37987077565819, 37.752879383675683 ], [ -122.379861869913029, 37.752869224847544 ], [ -122.379854658198184, 37.752857665545626 ], [ -122.379850887331656, 37.752845364357682 ], [ -122.379850574694558, 37.752833008281776 ], [ -122.379853720278177, 37.752820597043424 ], [ -122.37985861234651, 37.752808844946877 ], [ -122.379865250884947, 37.752797751442777 ], [ -122.379882315832674, 37.752788551525661 ], [ -122.379911450501524, 37.752777785898218 ], [ -122.379938873076583, 37.752767734300228 ], [ -122.379980076404905, 37.752755402685878 ], [ -122.379993770314556, 37.752749690431031 ], [ -122.380005682657426, 37.752741946417643 ], [ -122.380015797100285, 37.752731484181091 ], [ -122.380022435608339, 37.752720390667974 ], [ -122.380025580812656, 37.752707979430312 ], [ -122.380025233406471, 37.752694250457282 ], [ -122.380007137303139, 37.752457618747144 ], [ -122.379959987158614, 37.752029854003567 ], [ -122.379883834201038, 37.752034160974858 ], [ -122.376087329752295, 37.752248827801239 ], [ -122.375771286398191, 37.74870623521705 ], [ -122.382681120134976, 37.748282212078522 ], [ -122.384914143794745, 37.748144880283171 ], [ -122.384920340493579, 37.748184611607194 ], [ -122.384650947212847, 37.748201967818915 ], [ -122.384670831989453, 37.748303972394424 ], [ -122.384688156495528, 37.748305068501672 ], [ -122.384721007906435, 37.748304543099884 ], [ -122.384738280527714, 37.748303580123398 ], [ -122.384755518322564, 37.748301244247457 ], [ -122.384771026767297, 37.748298936027936 ], [ -122.384788230079309, 37.748295227244739 ], [ -122.384803704040692, 37.748291546118445 ], [ -122.384819160241221, 37.748287178547208 ], [ -122.384850004018801, 37.748275697588227 ], [ -122.384865408317154, 37.748269270660025 ], [ -122.384882471958647, 37.748260070280949 ], [ -122.384896077598697, 37.748250925484513 ], [ -122.384911429637739, 37.748242438930326 ], [ -122.384926799094799, 37.748234639097134 ], [ -122.384942203039174, 37.748228212438939 ], [ -122.384959354081715, 37.748222444285403 ], [ -122.384976521838055, 37.748217362314264 ], [ -122.384993725120907, 37.748213653501011 ], [ -122.385009181639873, 37.748209285899271 ], [ -122.385022856909728, 37.748202886607288 ], [ -122.385036497346164, 37.748195114416852 ], [ -122.385046610116049, 37.748184651746037 ], [ -122.385053264882572, 37.748174244389467 ], [ -122.385059867399946, 37.748161777687201 ], [ -122.385062977084218, 37.748147993403109 ], [ -122.385067868014104, 37.74813624080646 ], [ -122.38507799784864, 37.748126464861393 ], [ -122.385089892189129, 37.748118033594025 ], [ -122.38510529608827, 37.748111606639817 ], [ -122.385120787757828, 37.748108611914709 ], [ -122.385138077744813, 37.748108335328041 ], [ -122.385155454814836, 37.748111490980968 ], [ -122.385169426138901, 37.748116761295172 ], [ -122.385181755211505, 37.748125491789409 ], [ -122.385192373382367, 37.748134935830123 ], [ -122.385204720216592, 37.748144352490229 ], [ -122.385218761226213, 37.748152368866798 ], [ -122.385234478972677, 37.748158297687098 ], [ -122.385250161900544, 37.748162854157655 ], [ -122.385267521563421, 37.748165323071063 ], [ -122.385284846398747, 37.748166419359649 ], [ -122.385307218551446, 37.748161941354901 ], [ -122.385322623132453, 37.748155514361287 ], [ -122.385336298358496, 37.748149114758291 ], [ -122.385351667751635, 37.748141314869592 ], [ -122.385370425465482, 37.748130713860917 ], [ -122.385385847107514, 37.748124973312947 ], [ -122.38540299740022, 37.748119205105795 ], [ -122.385420183219992, 37.748114809781839 ], [ -122.385437403184611, 37.748111787363236 ], [ -122.385454641265781, 37.748109451653981 ], [ -122.385471896059883, 37.748107801852534 ], [ -122.385494373386805, 37.748107442216863 ], [ -122.38551168079205, 37.748107852023772 ], [ -122.385530752036246, 37.748109607060321 ], [ -122.385548128777415, 37.748112762660895 ], [ -122.385617567456492, 37.748122639221975 ], [ -122.385652269381737, 37.748126891038808 ], [ -122.385685225236202, 37.748130484058379 ], [ -122.385719909747124, 37.748134049406957 ], [ -122.385785855632406, 37.748142608581347 ], [ -122.385801503731642, 37.748145791532721 ], [ -122.385816588465147, 37.748146745865732 ], [ -122.385818828224473, 37.748146887748376 ], [ -122.385834354718469, 37.748145265826139 ], [ -122.38585155760299, 37.748141557167763 ], [ -122.385865250211054, 37.74813584422629 ], [ -122.385878890540098, 37.748128071663686 ], [ -122.3858907496043, 37.748118267698949 ], [ -122.385900844474122, 37.748107118511911 ], [ -122.385902260194882, 37.748094734766624 ], [ -122.385907133219177, 37.748082295692257 ], [ -122.385917263620584, 37.748072519389318 ], [ -122.385930956210871, 37.748066806440157 ], [ -122.385953433177363, 37.748066446722468 ], [ -122.385965727831334, 37.748073804232597 ], [ -122.385981463045027, 37.748080419401688 ], [ -122.385998909841959, 37.748086320723502 ], [ -122.38601630437492, 37.74809016297241 ], [ -122.386033664046266, 37.748092631772991 ], [ -122.386050971451041, 37.748093041500702 ], [ -122.386068226580846, 37.748091391880983 ], [ -122.386087192943762, 37.74808902841783 ], [ -122.386104378709106, 37.748084632719518 ], [ -122.386119835135943, 37.748080264972018 ], [ -122.386135256363829, 37.74807452433145 ], [ -122.386150643429275, 37.748067410781196 ], [ -122.386162536953989, 37.748058979691152 ], [ -122.386174396322502, 37.748049175966834 ], [ -122.386184491486219, 37.748038026749811 ], [ -122.386194569220663, 37.74802619108366 ], [ -122.386201171526452, 37.748013724042565 ], [ -122.386204298419131, 37.748000626176299 ], [ -122.386207059358142, 37.747973112895849 ], [ -122.386211932676204, 37.74796067407766 ], [ -122.386218534973267, 37.747948207035471 ], [ -122.386226883695826, 37.747936399040974 ], [ -122.386236996262895, 37.747925936267578 ], [ -122.386250601668095, 37.747916791039664 ], [ -122.38626425935739, 37.747909705429613 ], [ -122.38627966345058, 37.747903278041591 ], [ -122.386295119836348, 37.747898910545544 ], [ -122.386310628142297, 37.747896601848709 ], [ -122.386338292035447, 37.747896159029516 ], [ -122.386357415873647, 37.747899973271664 ], [ -122.386373115890919, 37.747905215772221 ], [ -122.386388851457951, 37.747911831156145 ], [ -122.386404621538247, 37.747919819440014 ], [ -122.386418680056124, 37.747928521847626 ], [ -122.386427603879639, 37.747939366631606 ], [ -122.386439915980972, 37.747947410266093 ], [ -122.386455564081089, 37.74795059367991 ], [ -122.386471037535358, 37.747946912065444 ], [ -122.386789455008412, 37.747884816204298 ], [ -122.3868315118753, 37.747838131816167 ], [ -122.386850251869276, 37.747826844123239 ], [ -122.386870720848805, 37.747815528743104 ], [ -122.386889443052766, 37.747803554601212 ], [ -122.386908148509548, 37.747790893997063 ], [ -122.386942065866194, 37.74776425526499 ], [ -122.386965801028097, 37.74774533356738 ], [ -122.386981135299891, 37.747736160569232 ], [ -122.386994723145222, 37.747726328806948 ], [ -122.387010022542214, 37.747715782908614 ], [ -122.387037163346392, 37.747694746479311 ], [ -122.387050698881637, 37.747682855640441 ], [ -122.38706252285192, 37.747671678660687 ], [ -122.38707605836494, 37.747659787269711 ], [ -122.387087812590266, 37.747645864494586 ], [ -122.387097942431041, 37.747636088094978 ], [ -122.387109818697425, 37.747626970729563 ], [ -122.387123441368203, 37.747618511574053 ], [ -122.387137098912476, 37.747611425587927 ], [ -122.387154231515737, 37.747604970677259 ], [ -122.387171469418391, 37.747602634442131 ], [ -122.387190435960278, 37.747600270521147 ], [ -122.387209419590647, 37.747598593050817 ], [ -122.387226657490075, 37.747596256807626 ], [ -122.387245658900213, 37.747595265774123 ], [ -122.387264642528066, 37.747593588294905 ], [ -122.387281915296896, 37.747592624939976 ], [ -122.387300916705343, 37.747591633897549 ], [ -122.387316296380376, 37.747591387583803 ], [ -122.38733895439411, 37.747591024699865 ], [ -122.387356261689263, 37.747591434235915 ], [ -122.387375280187655, 37.747591129635204 ], [ -122.387394334251837, 37.747592197916667 ], [ -122.387413370533409, 37.74759257975245 ], [ -122.387430694921051, 37.74759367573126 ], [ -122.387449748986683, 37.747594744003763 ], [ -122.38746882014496, 37.747596498726871 ], [ -122.38748616197239, 37.747598281145677 ], [ -122.387505250916419, 37.747600722305499 ], [ -122.387538363228003, 37.747610492846007 ], [ -122.387550745149639, 37.747621282157453 ], [ -122.387563144167856, 37.747632757921238 ], [ -122.38758451981073, 37.747657137719408 ], [ -122.387602472716026, 37.747682945794239 ], [ -122.387611466439651, 37.747696536281474 ], [ -122.387617002189558, 37.747710182158386 ], [ -122.387624284369664, 37.747724486787845 ], [ -122.387629837563409, 37.747738819112165 ], [ -122.387633661769783, 37.747753179131621 ], [ -122.387635983354997, 37.747776490677239 ], [ -122.387636332144282, 37.747790219639235 ], [ -122.387640121821377, 37.747803206756615 ], [ -122.387649063247679, 37.747814737896427 ], [ -122.38765798689127, 37.747825582592974 ], [ -122.38767204553811, 37.747834284851223 ], [ -122.387686034428512, 37.747840241315451 ], [ -122.387703429329918, 37.74784408303541 ], [ -122.387720806101257, 37.747847238315771 ], [ -122.387738148684832, 37.747849020686374 ], [ -122.387757219915159, 37.747850775362728 ], [ -122.387776255919036, 37.747851157145398 ], [ -122.387793563622978, 37.747851566611679 ], [ -122.38781256509084, 37.747850575486822 ], [ -122.387829820125248, 37.747848925609141 ], [ -122.387848786709029, 37.747846561582286 ], [ -122.387866006512709, 37.747843538808695 ], [ -122.387888379097262, 37.747839060028085 ], [ -122.387903835368149, 37.747834692046453 ], [ -122.387921107500148, 37.747833728883549 ], [ -122.38793675597158, 37.747836911546052 ], [ -122.387950727441307, 37.7478421815308 ], [ -122.387968122011031, 37.747846023217228 ], [ -122.387985481697157, 37.747848492005005 ], [ -122.388002823941576, 37.747850274342177 ], [ -122.388020131300777, 37.747850683780733 ], [ -122.388039132419564, 37.747849692624868 ], [ -122.38805637035118, 37.747847356260422 ], [ -122.388073590146575, 37.74784433345652 ], [ -122.388089046753478, 37.747839965445046 ], [ -122.388109620211083, 37.747832768537954 ], [ -122.388109062008056, 37.747810802201109 ], [ -122.388077940175876, 37.747811300839857 ], [ -122.38807409843713, 37.74779625438692 ], [ -122.388108678243654, 37.747795700344511 ], [ -122.388099616701311, 37.74771137804548 ], [ -122.388148028374744, 37.747710602372756 ], [ -122.388152687991848, 37.747825897938156 ], [ -122.388277000893524, 37.747817039049515 ], [ -122.388487762928946, 37.747806794121068 ], [ -122.388506886854486, 37.747810608014518 ], [ -122.388525975547779, 37.747813049289029 ], [ -122.388546793218978, 37.747815462302533 ], [ -122.388565847011719, 37.747816530400094 ], [ -122.388605613450011, 37.747815893090063 ], [ -122.388626326439876, 37.747814187676354 ], [ -122.388645293335259, 37.747811823515768 ], [ -122.388665971425112, 37.747808745199229 ], [ -122.388684903080716, 37.747805008416485 ], [ -122.388703817271136, 37.747800584633623 ], [ -122.388722696221464, 37.747794788231978 ], [ -122.388738135330243, 37.747789733687412 ], [ -122.388758743610779, 37.74778390956282 ], [ -122.388800029962908, 37.747775007094489 ], [ -122.38882243702561, 37.74777190103795 ], [ -122.38884313255457, 37.74776950941277 ], [ -122.388865591959274, 37.747768462417191 ], [ -122.388886322380927, 37.747767443405706 ], [ -122.388908833806198, 37.747768456300328 ], [ -122.388929616574529, 37.747769496350386 ], [ -122.388945212727961, 37.747770619809877 ], [ -122.388964301079568, 37.747773061018961 ], [ -122.388983355206264, 37.747774128768917 ], [ -122.38900242644678, 37.747775883243818 ], [ -122.389021462783674, 37.747776264819827 ], [ -122.389042245569726, 37.747777305399197 ], [ -122.389080318237873, 37.747778068260708 ], [ -122.389137374546209, 37.7477771536037 ], [ -122.389175377760296, 37.747775171186596 ], [ -122.389196090718727, 37.747773465398161 ], [ -122.38926533756586, 37.747775788884091 ], [ -122.389334549507055, 37.747776739433625 ], [ -122.3894037440012, 37.747777003769272 ], [ -122.389542063148724, 37.747774785978237 ], [ -122.389680277530928, 37.747768449063493 ], [ -122.389818387138831, 37.74775799357456 ], [ -122.38988740700772, 37.747751392873859 ], [ -122.38995468041503, 37.747744133414443 ], [ -122.390039173851804, 37.747733851094473 ], [ -122.390066855117027, 37.747734093573278 ], [ -122.390085856531513, 37.747733102356463 ], [ -122.390103128943423, 37.747732138319044 ], [ -122.390122130349909, 37.747731146821835 ], [ -122.390141096830732, 37.747728782426051 ], [ -122.390158334329385, 37.747726446034243 ], [ -122.390177283337934, 37.747723394910331 ], [ -122.390194485555256, 37.747719685348756 ], [ -122.390213417450141, 37.74771594804033 ], [ -122.390230602546296, 37.747711552020206 ], [ -122.390249481354928, 37.747705755373893 ], [ -122.3902666667924, 37.747701359342962 ], [ -122.390283903927951, 37.747699022663753 ], [ -122.390311567723842, 37.747698578911937 ], [ -122.390328910334858, 37.747700360901341 ], [ -122.390344558542523, 37.747703543524345 ], [ -122.390363664733329, 37.747706670948986 ], [ -122.390381024113694, 37.747709139114932 ], [ -122.390398366728832, 37.747710921094118 ], [ -122.390415726118349, 37.747713389529537 ], [ -122.390433121131167, 37.747717230846696 ], [ -122.390448804284702, 37.74772178662581 ], [ -122.390464522011555, 37.747727714754603 ], [ -122.390476782463509, 37.747733698619065 ], [ -122.390494212070166, 37.747738912828105 ], [ -122.390511589281189, 37.747742067691526 ], [ -122.390527184761183, 37.747743190952775 ], [ -122.390653785454447, 37.747824253927604 ], [ -122.390638206813605, 37.747755831079004 ], [ -122.390653872859318, 37.747759700103025 ], [ -122.390669573495856, 37.747764942025796 ], [ -122.390681886034457, 37.74777298549197 ], [ -122.39069250450811, 37.747782429043276 ], [ -122.390701428591996, 37.747793273509437 ], [ -122.390706912858818, 37.747804859892483 ], [ -122.390710737663227, 37.747819219811937 ], [ -122.390717985513646, 37.7478321513547 ], [ -122.390725250834635, 37.747845769344615 ], [ -122.39073249869017, 37.747858700886397 ], [ -122.390741475546321, 37.747871604961333 ], [ -122.390750434923376, 37.747883822038752 ], [ -122.390771776747243, 37.747906828362581 ], [ -122.390782430195571, 37.747917645075368 ], [ -122.39079481229362, 37.747928434050969 ], [ -122.39082296530907, 37.747947210986922 ], [ -122.390837024342559, 37.747955912592879 ], [ -122.390849337269742, 37.747963955761179 ], [ -122.390865002685743, 37.747967825042672 ], [ -122.390880686250455, 37.747972380209411 ], [ -122.3909120177685, 37.747980118201738 ], [ -122.390943314352953, 37.747986483290504 ], [ -122.390960691640942, 37.747989638087581 ], [ -122.390976339590807, 37.747992820632099 ], [ -122.391007601590601, 37.747997812803085 ], [ -122.391024961067359, 37.748000281148649 ], [ -122.391056188138322, 37.748003900686179 ], [ -122.391073512668584, 37.74800499585487 ], [ -122.391089126375263, 37.748006805478177 ], [ -122.391122029339527, 37.748008337920567 ], [ -122.39113764304112, 37.74801014726286 ], [ -122.391155055286944, 37.748014674926367 ], [ -122.3911690619626, 37.748021317424552 ], [ -122.391183121057651, 37.748030019263688 ], [ -122.391193757118117, 37.748040149766119 ], [ -122.391202716589845, 37.748052366808828 ], [ -122.391208200608148, 37.748063953173585 ], [ -122.391241557893906, 37.74808333321856 ], [ -122.391295995830475, 37.748115422483572 ], [ -122.391962189342451, 37.748397274549454 ], [ -122.391983042262396, 37.748401060399672 ], [ -122.392002166513251, 37.748404873451001 ], [ -122.392021325387503, 37.7484100596741 ], [ -122.392042213267686, 37.748415218134042 ], [ -122.392078889429584, 37.748429050561562 ], [ -122.392113924006139, 37.748446342975576 ], [ -122.392131528713293, 37.748458421690032 ], [ -122.39214384116525, 37.748466464458922 ], [ -122.392159577364851, 37.748473079071736 ], [ -122.392175243295199, 37.748476947898624 ], [ -122.392194332280141, 37.748479388578737 ], [ -122.392215132775206, 37.748481114496656 ], [ -122.392234186445535, 37.748482182006597 ], [ -122.392254951984697, 37.748482535297406 ], [ -122.392294719439334, 37.748481896731604 ], [ -122.392336145620007, 37.748478484604256 ], [ -122.392355112221097, 37.748476119852498 ], [ -122.392375790342101, 37.748473040885386 ], [ -122.392394739456975, 37.748469989679933 ], [ -122.392410230561637, 37.748466994002406 ], [ -122.392430943652641, 37.748465288194843 ], [ -122.392449945215546, 37.748464296322425 ], [ -122.392477609283375, 37.748463852062244 ], [ -122.392496645468526, 37.748464233082473 ], [ -122.392534753839612, 37.748466367717036 ], [ -122.392553842833664, 37.748468808338913 ], [ -122.392571185325409, 37.748470589729116 ], [ -122.392588457883463, 37.748469625602191 ], [ -122.392596639186095, 37.748469494208877 ], [ -122.392610934939555, 37.748469264614883 ], [ -122.39262824212247, 37.74846967338226 ], [ -122.392662927124746, 37.748473236687367 ], [ -122.392680287459143, 37.74847570477781 ], [ -122.392695935930789, 37.748478887088204 ], [ -122.392713348381406, 37.748483414521154 ], [ -122.392729031836424, 37.748487969996233 ], [ -122.39274473277321, 37.748493211641936 ], [ -122.392760450846382, 37.748499139463782 ], [ -122.392774457756872, 37.748505781770611 ], [ -122.392790211508157, 37.748513082746427 ], [ -122.392804253398694, 37.748521097944213 ], [ -122.392825263777468, 37.748531061396008 ], [ -122.392842711223665, 37.748536961704282 ], [ -122.39286010621592, 37.748540802942827 ], [ -122.392879212368058, 37.748543929415632 ], [ -122.392898284250904, 37.748545683528903 ], [ -122.392917320797991, 37.748546064201044 ], [ -122.392936339863667, 37.748545758697475 ], [ -122.392953524990631, 37.748541362280918 ], [ -122.392966938977182, 37.748536367729422 ], [ -122.392968945786251, 37.748535620746658 ], [ -122.392982602941643, 37.748528534084265 ], [ -122.392994513945695, 37.748520788741565 ], [ -122.393006389623011, 37.748511670508776 ], [ -122.393016518457316, 37.748501893607475 ], [ -122.39303324918852, 37.748479649545963 ], [ -122.393038138879703, 37.74846789661926 ], [ -122.393043010740925, 37.74845545752526 ], [ -122.393046136785017, 37.748442359198343 ], [ -122.393047533485998, 37.748429288926303 ], [ -122.393054199696095, 37.748283595463981 ], [ -122.393077928476629, 37.748128700431906 ], [ -122.392979444727942, 37.747588651673503 ], [ -122.39297940856946, 37.747588454476606 ], [ -122.392907366368178, 37.747544287652012 ], [ -122.39290993009746, 37.747441237230703 ], [ -122.392849293374297, 37.747437404131951 ], [ -122.392809823765376, 37.747449712490337 ], [ -122.39276850310074, 37.747457243469029 ], [ -122.392735233137245, 37.747441296365679 ], [ -122.392709245973222, 37.747439653574382 ], [ -122.392606956367715, 37.747430308793902 ], [ -122.392589631603045, 37.747429213856059 ], [ -122.392573983004198, 37.747426031261952 ], [ -122.392558282650754, 37.747420789587025 ], [ -122.392545987120343, 37.747413433318528 ], [ -122.392533639816108, 37.747404017146245 ], [ -122.392522986186506, 37.747393200590786 ], [ -122.392515773033679, 37.747381642052915 ], [ -122.392369749857508, 37.747285098201544 ], [ -122.392309356598403, 37.747222889020321 ], [ -122.391563535628293, 37.747006182915136 ], [ -122.391515981789823, 37.747108581714961 ], [ -122.391549059112705, 37.747116978239831 ], [ -122.39154102623057, 37.747141142664127 ], [ -122.391633378020288, 37.747167816090183 ], [ -122.391610658187417, 37.747226552683614 ], [ -122.391284893471251, 37.747135639523535 ], [ -122.391304260773893, 37.747081077170833 ], [ -122.391400018273714, 37.747105635933018 ], [ -122.391414984907726, 37.747082046956919 ], [ -122.391449669204121, 37.747085610619905 ], [ -122.391496995599269, 37.746974288032909 ], [ -122.390841889233045, 37.746787710251105 ], [ -122.390710603266925, 37.746750702923116 ], [ -122.390692063133784, 37.7467454771745 ], [ -122.390583853899699, 37.746707382782333 ], [ -122.390491852370616, 37.746694437487911 ], [ -122.389167167584475, 37.74677199330884 ], [ -122.389007159419478, 37.746805461394153 ], [ -122.38886264165825, 37.746835933506375 ], [ -122.388798600052468, 37.746834213094125 ], [ -122.388745420841317, 37.746851546922535 ], [ -122.388650571528245, 37.746862681307142 ], [ -122.388568244062029, 37.746890096400243 ], [ -122.388501285688704, 37.746909711129156 ], [ -122.388430153337836, 37.746901236873853 ], [ -122.388403030664151, 37.746922960074855 ], [ -122.388383874714961, 37.746923267046867 ], [ -122.388364993671857, 37.746923569610487 ], [ -122.388206416880053, 37.746945339015134 ], [ -122.388203900596949, 37.746982462626271 ], [ -122.388121154182954, 37.746993402651967 ], [ -122.388116562743861, 37.746948838923466 ], [ -122.387896826491271, 37.746946178902249 ], [ -122.387881213437979, 37.746944369124783 ], [ -122.387867242141994, 37.746939099130984 ], [ -122.387854912604922, 37.746930368921483 ], [ -122.387704648799442, 37.746938956647774 ], [ -122.387598167965407, 37.746968818149767 ], [ -122.387589223776885, 37.747025273076481 ], [ -122.387549736768761, 37.747036893214862 ], [ -122.387544863742193, 37.747049332364881 ], [ -122.387534786009468, 37.747061168154005 ], [ -122.38752465665732, 37.747070944861157 ], [ -122.387511034118646, 37.747079403514647 ], [ -122.387495647733758, 37.747086517238138 ], [ -122.387478445182509, 37.747090226411743 ], [ -122.387461451881805, 37.747102172961299 ], [ -122.387268251932909, 37.747106390211968 ], [ -122.387107064056366, 37.74710990881966 ], [ -122.387091486195246, 37.747109471553998 ], [ -122.387075890554399, 37.747108347843543 ], [ -122.387060207744099, 37.747103791889728 ], [ -122.387046201608655, 37.747097148902206 ], [ -122.387033854715085, 37.747087732433343 ], [ -122.387024930905355, 37.74707688769373 ], [ -122.387021159152255, 37.747064586997716 ], [ -122.387005168327306, 37.746911702839881 ], [ -122.385924087590695, 37.746978453708003 ], [ -122.382764926423121, 37.74715258139431 ], [ -122.382708148125133, 37.747164476159931 ], [ -122.382642308174283, 37.747160034224571 ], [ -122.382018857926923, 37.747061489736957 ], [ -122.379659819974563, 37.747207644757545 ], [ -122.379607354913716, 37.746979801452689 ], [ -122.379559186732763, 37.746990183908736 ], [ -122.379533356297898, 37.746994716256566 ], [ -122.379509237470685, 37.74699853457313 ], [ -122.379483389665921, 37.747002380460607 ], [ -122.379459270487843, 37.747006198772453 ], [ -122.379433405660123, 37.74700935819407 ], [ -122.379407523121102, 37.747011831166098 ], [ -122.379383369216924, 37.747014276838193 ], [ -122.379357487013394, 37.747016749519133 ], [ -122.379331587106719, 37.747018536025081 ], [ -122.379307398466224, 37.747019608508175 ], [ -122.379255564276221, 37.747021808862577 ], [ -122.379229629642097, 37.747022222447264 ], [ -122.379144857745686, 37.747021513827882 ], [ -122.379072223061954, 37.747021985338478 ], [ -122.37899789481213, 37.747023857260928 ], [ -122.378925329913201, 37.747027074747926 ], [ -122.378851035672341, 37.747030319213138 ], [ -122.378706009969704, 37.747040872109451 ], [ -122.378631820547639, 37.747048235671635 ], [ -122.378559359727831, 37.747055571081212 ], [ -122.378486933610205, 37.74706427961987 ], [ -122.378414542183847, 37.747074361013084 ], [ -122.378342168091294, 37.747085128811385 ], [ -122.378269828683315, 37.747097269464334 ], [ -122.378197506610775, 37.747110096797023 ], [ -122.378126930483418, 37.747123582435272 ], [ -122.377700185384086, 37.747142744068015 ], [ -122.377451264431812, 37.747148770308641 ], [ -122.377202256724516, 37.747151363774975 ], [ -122.376953162296658, 37.747150524466448 ], [ -122.376703981182786, 37.747146252382528 ], [ -122.376633214042542, 37.747152186453157 ], [ -122.376586583950242, 37.747154989206692 ], [ -122.376541665151564, 37.747157077964779 ], [ -122.376496729013979, 37.747158480255926 ], [ -122.376406822062478, 37.747159911887174 ], [ -122.3763168457685, 37.747158597650483 ], [ -122.376226800142021, 37.747154537545661 ], [ -122.376181759998943, 37.747151821017589 ], [ -122.376136702533032, 37.747148418296995 ], [ -122.376091627724676, 37.747144328560168 ], [ -122.376046535596004, 37.747139552630784 ], [ -122.376003154781813, 37.747134062989552 ], [ -122.375912901223032, 37.747121764971595 ], [ -122.375869469128858, 37.747114215920412 ], [ -122.375824307704988, 37.747106693831739 ], [ -122.375780857600304, 37.747098458034387 ], [ -122.375737390867457, 37.747089535760047 ], [ -122.375693906470275, 37.747079927025247 ], [ -122.375650404755746, 37.747069631824438 ], [ -122.375606885379298, 37.74705865016309 ], [ -122.375565078011306, 37.747046954511831 ], [ -122.375521542024956, 37.747035286357942 ], [ -122.375484921614671, 37.747023508124926 ], [ -122.375446537240038, 37.747010384502651 ], [ -122.375427328249984, 37.747003136228599 ], [ -122.375409847894304, 37.746995860440805 ], [ -122.375390638220011, 37.746988612171734 ], [ -122.375373140891156, 37.746980649922982 ], [ -122.375353914244343, 37.746972715192399 ], [ -122.375283854298274, 37.746938120381792 ], [ -122.375266321996932, 37.746928785222565 ], [ -122.375250518328926, 37.746919422552502 ], [ -122.375232969057322, 37.746909400932758 ], [ -122.375217148425804, 37.746899352077222 ], [ -122.375199598810724, 37.746889330183407 ], [ -122.375183760856842, 37.746878594598741 ], [ -122.37516794023162, 37.746868545461957 ], [ -122.375152084617042, 37.746857123428462 ], [ -122.375137975649949, 37.746846360325264 ], [ -122.375122120743228, 37.74683493855126 ], [ -122.37510799445424, 37.746823488719798 ], [ -122.37509213920427, 37.746812066672646 ], [ -122.375063852016254, 37.746787794649727 ], [ -122.375051437407549, 37.746775631399466 ], [ -122.375037293817897, 37.746763495109327 ], [ -122.375023132917065, 37.746750672641994 ], [ -122.374998269070602, 37.746724972682053 ], [ -122.374960921021042, 37.746684363387303 ], [ -122.374939480511969, 37.746657235495441 ], [ -122.374896530254759, 37.746600233899152 ], [ -122.374887504692197, 37.746585269533512 ], [ -122.374878496108821, 37.746570991622754 ], [ -122.374851418423177, 37.746526098810385 ], [ -122.374837344197417, 37.746516708296284 ], [ -122.374824981280824, 37.746506604102919 ], [ -122.374812601738441, 37.746495813447105 ], [ -122.374801933498034, 37.746484308838191 ], [ -122.374792994225416, 37.746472776721248 ], [ -122.374785749278871, 37.74645984419638 ], [ -122.374780233298765, 37.746446884164222 ], [ -122.374774699654409, 37.746433237687107 ], [ -122.374770895666941, 37.746419563692001 ], [ -122.374768802977741, 37.746405175745409 ], [ -122.374768439251895, 37.74639076029208 ], [ -122.374769821808755, 37.746377003782186 ], [ -122.374772932981244, 37.74636321977102 ], [ -122.374777773805775, 37.74634940824194 ], [ -122.374790947320705, 37.746323103080407 ], [ -122.374915153489738, 37.745898790876815 ], [ -122.374925284686768, 37.745889015246057 ], [ -122.374935381244654, 37.745877866988621 ], [ -122.374943731527424, 37.745866059789378 ], [ -122.374948606585662, 37.745853621157977 ], [ -122.374953463974592, 37.745840496081698 ], [ -122.374954864154603, 37.745827426013051 ], [ -122.374954517717399, 37.745813697009091 ], [ -122.374952459307565, 37.745800681970202 ], [ -122.37494867264158, 37.745787694429573 ], [ -122.374943174004656, 37.745775420853761 ], [ -122.374925295588895, 37.745752356637837 ], [ -122.374169821595103, 37.745013096467311 ], [ -122.374159170981372, 37.745002278247725 ], [ -122.374148554999209, 37.744992832927991 ], [ -122.374136227057974, 37.744984101560632 ], [ -122.374123917125218, 37.74497605663138 ], [ -122.374109912547326, 37.744969412104211 ], [ -122.374095925285644, 37.7449634540258 ], [ -122.374080226064535, 37.744958209898222 ], [ -122.37406629074772, 37.744954311167561 ], [ -122.374048932546074, 37.744951840322948 ], [ -122.374033320242248, 37.744950028435788 ], [ -122.374017742219408, 37.744949589452929 ], [ -122.373998724342499, 37.744949891898301 ], [ -122.37398319825796, 37.744951512262205 ], [ -122.373967707489385, 37.744954505513952 ], [ -122.373952250998556, 37.744958871670036 ], [ -122.373936811825118, 37.74496392454914 ], [ -122.373923118884619, 37.74496963610779 ], [ -122.373909460559076, 37.744976720291163 ], [ -122.373895819549759, 37.744984491197997 ], [ -122.373883942091524, 37.744993607510423 ], [ -122.373863679651151, 37.745013158045801 ], [ -122.373855311981089, 37.745024278719775 ], [ -122.373848673239934, 37.745035371899974 ], [ -122.373842051808168, 37.745047151530216 ], [ -122.37385341216104, 37.745086114521243 ], [ -122.373444301009826, 37.74532404571449 ], [ -122.373430677195785, 37.74533250301748 ], [ -122.373418781971495, 37.745340932837536 ], [ -122.373405123536585, 37.745348017236601 ], [ -122.373391482406376, 37.745355788084566 ], [ -122.373377806658937, 37.745362186029979 ], [ -122.373364148216169, 37.745369270424256 ], [ -122.373350455156967, 37.745374981916001 ], [ -122.373319611498488, 37.745386459860086 ], [ -122.37330588381964, 37.745390798445655 ], [ -122.373274970231165, 37.745399530587065 ], [ -122.373259496653688, 37.745403210470592 ], [ -122.373244005409106, 37.745406203357881 ], [ -122.373226784891003, 37.745409224281886 ], [ -122.373211276683975, 37.7454115307089 ], [ -122.373195750831584, 37.745413150963429 ], [ -122.373178495692287, 37.7454147987048 ], [ -122.373162952885991, 37.745415732773601 ], [ -122.373130102670643, 37.745416254953994 ], [ -122.373114525240069, 37.745415815566226 ], [ -122.373097201225704, 37.745414717488131 ], [ -122.37308160580649, 37.745413591931054 ], [ -122.373065993781964, 37.745411780184988 ], [ -122.373048652458152, 37.745409995374658 ], [ -122.373033005135582, 37.745406810734146 ], [ -122.373017375795953, 37.745404311981943 ], [ -122.372986047234718, 37.74539656950391 ], [ -122.372970365296936, 37.745392011404839 ], [ -122.372956412653693, 37.745387426092684 ], [ -122.372762203193631, 37.745505195897991 ], [ -122.372470578027816, 37.745199439060684 ], [ -122.369079539125323, 37.741643837671482 ], [ -122.367630267152407, 37.740133359644659 ], [ -122.367867173476, 37.739994319504163 ], [ -122.368285236674282, 37.739975330499902 ], [ -122.368274610769845, 37.73982785299647 ], [ -122.371253871422496, 37.739663833776014 ], [ -122.371264036490402, 37.739792777100682 ], [ -122.373407014055843, 37.739660522763742 ], [ -122.373411889309949, 37.739648084177503 ], [ -122.37342198524702, 37.739636936038039 ], [ -122.373432115793747, 37.739627160799976 ], [ -122.373447518947913, 37.739620735634887 ], [ -122.373462992003653, 37.739617055712138 ], [ -122.373518088785445, 37.739607252232659 ], [ -122.373566287640358, 37.739598245135006 ], [ -122.373590404371257, 37.739594428029896 ], [ -122.373616249911393, 37.739590583704633 ], [ -122.373640366284548, 37.739586766320215 ], [ -122.373666246782193, 37.739584294880729 ], [ -122.373690380805215, 37.739581163931668 ], [ -122.373716260600844, 37.739578692217606 ], [ -122.37374042959101, 37.739576934429572 ], [ -122.373766327038652, 37.739575149150085 ], [ -122.373790512990382, 37.739574077808385 ], [ -122.373816427745723, 37.739572978968987 ], [ -122.373890766330462, 37.739571796757524 ], [ -122.373921919428824, 37.739572674766521 ], [ -122.373937443699504, 37.739571054420232 ], [ -122.373952916727561, 37.739567374707711 ], [ -122.373966608670429, 37.739561663140428 ], [ -122.373980265988422, 37.739554578669413 ], [ -122.373992124908327, 37.7395447758934 ], [ -122.374000509598304, 37.739534341645076 ], [ -122.37400713051376, 37.739522561995074 ], [ -122.374012004966914, 37.739510123394709 ], [ -122.37401135577899, 37.739484381479812 ], [ -122.374005858115922, 37.739472108116395 ], [ -122.373998648611916, 37.739460548430131 ], [ -122.373977383644629, 37.739440284566498 ], [ -122.373964927156109, 37.739426404784517 ], [ -122.373936539281573, 37.739398013754517 ], [ -122.373924065496922, 37.739383447517028 ], [ -122.373886618206512, 37.739338719119822 ], [ -122.373874092510491, 37.73932209352359 ], [ -122.373863330238677, 37.739306813334778 ], [ -122.373841788398252, 37.73927556650299 ], [ -122.373820229273036, 37.739243633490574 ], [ -122.373811178157524, 37.739227639083794 ], [ -122.373793059679699, 37.739194964349537 ], [ -122.37378399162607, 37.739178284033066 ], [ -122.373776661019065, 37.739161919174691 ], [ -122.37376932175367, 37.739145210815664 ], [ -122.373761973843884, 37.739128159505157 ], [ -122.373754634592061, 37.73911145141971 ], [ -122.373747286688968, 37.739094400108158 ], [ -122.373741667582067, 37.739077321304478 ], [ -122.373730515575119, 37.739046595957468 ], [ -122.373715767869712, 37.739010433977029 ], [ -122.373701037487336, 37.738974958445688 ], [ -122.373691943857139, 37.738957248164674 ], [ -122.373664687572003, 37.73890514701592 ], [ -122.373643076699025, 37.738871154340984 ], [ -122.373610686734082, 37.738821194992092 ], [ -122.373585652607346, 37.73878863019344 ], [ -122.373573144731495, 37.738772691284012 ], [ -122.373560645155081, 37.738757095055156 ], [ -122.373546408152151, 37.738741183362713 ], [ -122.373533917247101, 37.738725930631098 ], [ -122.373505477884322, 37.738695480140251 ], [ -122.373491266871952, 37.738680598392399 ], [ -122.373459404583869, 37.738651575494011 ], [ -122.373445210895355, 37.73863738019174 ], [ -122.373429288070284, 37.738623211831154 ], [ -122.3734116544736, 37.738609757670034 ], [ -122.37339574066641, 37.738595932799527 ], [ -122.37337984382502, 37.738582794383632 ], [ -122.373362218906536, 37.738569683715227 ], [ -122.373326985681061, 37.738544148008963 ], [ -122.373309377387599, 37.738531723520261 ], [ -122.373290049663908, 37.73851966972861 ], [ -122.37327245903279, 37.738507931680125 ], [ -122.373253139281985, 37.738496221119 ], [ -122.373235557660152, 37.738484826285116 ], [ -122.37321625557793, 37.738473802438271 ], [ -122.373195233367738, 37.738463149022955 ], [ -122.373175939596308, 37.738452468126042 ], [ -122.37315665516681, 37.738442130166042 ], [ -122.373135650277447, 37.738432163191355 ], [ -122.373116382823142, 37.738422511956102 ], [ -122.373074407684399, 37.738403951165793 ], [ -122.373053428763811, 37.738395013578788 ], [ -122.373032458506373, 37.738386419488315 ], [ -122.373011496911786, 37.738378168894329 ], [ -122.372988815182367, 37.738370288453737 ], [ -122.372967870892026, 37.738362724028732 ], [ -122.372945197483872, 37.738355187085482 ], [ -122.372924270503603, 37.738348309104055 ], [ -122.372901615103871, 37.738341458867346 ], [ -122.372878967999199, 37.738334951308275 ], [ -122.372856329555759, 37.738328787245123 ], [ -122.372833699427503, 37.738322966683377 ], [ -122.372811078630633, 37.738317488782812 ], [ -122.372788466148293, 37.738312354383659 ], [ -122.372765862325963, 37.738307563480433 ], [ -122.372743266796775, 37.738303115254908 ], [ -122.372718943193647, 37.738298694765277 ], [ -122.372673787106677, 37.738291171738332 ], [ -122.372649498109226, 37.738288124137178 ], [ -122.372626937542805, 37.738285049061219 ], [ -122.372602656852706, 37.7382823446816 ], [ -122.372578393462533, 37.738280326748381 ], [ -122.372555858502594, 37.738278281341429 ], [ -122.372531604109625, 37.73827660661879 ], [ -122.372507357674976, 37.738275275127862 ], [ -122.37248484935597, 37.738274259373469 ], [ -122.372460620565761, 37.738273614318871 ], [ -122.372436400424391, 37.738273312484985 ], [ -122.37241391770614, 37.738273326399707 ], [ -122.372389714515705, 37.738273711013171 ], [ -122.372365520664644, 37.738274438836385 ], [ -122.372341334769544, 37.738275509891366 ], [ -122.371417572864843, 37.73819782064686 ], [ -122.371336052232962, 37.738188471129725 ], [ -122.371292703605505, 37.738184009138322 ], [ -122.371247643157346, 37.738180261317709 ], [ -122.371204337055175, 37.738177515159229 ], [ -122.371159311524337, 37.738175139928011 ], [ -122.371116023065696, 37.738173080457798 ], [ -122.37107103176993, 37.738172078376202 ], [ -122.371027777876208, 37.738171391502313 ], [ -122.370982812155077, 37.738171418795432 ], [ -122.370937864408404, 37.738172132512091 ], [ -122.370894662370532, 37.738173505218874 ], [ -122.370849740199262, 37.738175248584916 ], [ -122.370806564080581, 37.738177650936933 ], [ -122.370761676121674, 37.738180767178264 ], [ -122.370718534904626, 37.738184542395913 ], [ -122.370673681844693, 37.738189031501626 ], [ -122.370630565847108, 37.738193836375615 ], [ -122.370585739048508, 37.738199355394492 ], [ -122.37054305620137, 37.738152648632216 ], [ -122.370404226449224, 37.73820257996838 ], [ -122.370322939528947, 37.738202496843449 ], [ -122.370241678523712, 37.738203443340097 ], [ -122.37016044308433, 37.738205419463867 ], [ -122.370080971656222, 37.738208740986281 ], [ -122.369999797012667, 37.738213119575086 ], [ -122.369918647931073, 37.738218528065744 ], [ -122.369839254199036, 37.738224938183691 ], [ -122.369758165884022, 37.738232748865137 ], [ -122.369678823605085, 37.738241218785156 ], [ -122.369597795695725, 37.738251431663087 ], [ -122.369570247557576, 37.738256332765289 ], [ -122.369551300103424, 37.738259380031664 ], [ -122.369532343673868, 37.738262084349124 ], [ -122.369494414578227, 37.738266806506552 ], [ -122.369475441227863, 37.738268824632087 ], [ -122.369456458882269, 37.738270498984953 ], [ -122.369437468599656, 37.738271830372412 ], [ -122.369399470072068, 37.738273806697272 ], [ -122.369380461827461, 37.738274451634652 ], [ -122.369342429106254, 37.738275055031856 ], [ -122.369304361157091, 37.738274285523985 ], [ -122.36926627663955, 37.738272829815514 ], [ -122.369228156883779, 37.738270000652726 ], [ -122.369209088902451, 37.738268243244413 ], [ -122.369170934963606, 37.738264041153812 ], [ -122.369151849020255, 37.738261597020717 ], [ -122.369067113471218, 37.738261911373165 ], [ -122.368994202750471, 37.738251049936494 ], [ -122.368911109321047, 37.73824790450054 ], [ -122.36882805041877, 37.73824613163535 ], [ -122.368745017407647, 37.738245388114983 ], [ -122.368662018929072, 37.738246017714694 ], [ -122.36857731722661, 37.738247704629927 ], [ -122.368494370518377, 37.738250393192978 ], [ -122.368411458330385, 37.738254454876298 ], [ -122.368328572028588, 37.738259546453904 ], [ -122.368245711588798, 37.738265667102112 ], [ -122.36816287702834, 37.738272817644742 ], [ -122.368080076965157, 37.738281341033293 ], [ -122.367798933766665, 37.738311550246465 ], [ -122.367519570533148, 37.738343791024903 ], [ -122.367503864887638, 37.738338202509389 ], [ -122.367488228255993, 37.73833536007497 ], [ -122.367472548152278, 37.738330801513577 ], [ -122.367458562672155, 37.738324842640814 ], [ -122.367444551318144, 37.738317854088145 ], [ -122.367432225963285, 37.738309121998832 ], [ -122.367421611792139, 37.73829967606261 ], [ -122.367412700524838, 37.738289173048365 ], [ -122.367405492161083, 37.738277612956487 ], [ -122.367400003950721, 37.738265682239444 ], [ -122.367396227274725, 37.738253037945974 ], [ -122.367394179016159, 37.738240365710134 ], [ -122.367393859886334, 37.738227666344848 ], [ -122.367395269877491, 37.738214939575492 ], [ -122.367400145351439, 37.738202501245944 ], [ -122.367413544289079, 37.738185120751645 ], [ -122.367428767218343, 37.73817148833826 ], [ -122.367445744452965, 37.738158858481917 ], [ -122.367462730293482, 37.738146571300049 ], [ -122.367481470439287, 37.738135286674201 ], [ -122.36750022852209, 37.738124688486586 ], [ -122.367520732277939, 37.73811474935323 ], [ -122.367541261905686, 37.738105839894565 ], [ -122.367561808780295, 37.738097616884474 ], [ -122.367582381528294, 37.738090423549032 ], [ -122.367604708583698, 37.738084232766695 ], [ -122.367627053565442, 37.738078727872157 ], [ -122.367649424083893, 37.738074252931682 ], [ -122.367766437068838, 37.738050766275428 ], [ -122.367843903943097, 37.738036490753885 ], [ -122.367921396310663, 37.738023244315926 ], [ -122.367998923860597, 37.738011370994791 ], [ -122.368076468296522, 37.738000184080214 ], [ -122.368154047223541, 37.737990370018814 ], [ -122.368231651678144, 37.737981585864489 ], [ -122.368311011449208, 37.737973803365058 ], [ -122.368388676621564, 37.737967421684075 ], [ -122.368447213120319, 37.737956879019784 ], [ -122.368493984461352, 37.737959914324698 ], [ -122.368511237973692, 37.737958267267153 ], [ -122.368526753740483, 37.737956304401699 ], [ -122.368543998275101, 37.737954314118795 ], [ -122.368559514040228, 37.737952351249042 ], [ -122.368575012543431, 37.737949701925182 ], [ -122.36859223981395, 37.737947025183189 ], [ -122.368607738314864, 37.737944375855051 ], [ -122.368624947975661, 37.737941012661715 ], [ -122.368655910793635, 37.737934341085214 ], [ -122.368671383403552, 37.737930662345228 ], [ -122.368688576135014, 37.737926612410497 ], [ -122.368719486808203, 37.737917881466693 ], [ -122.368750380211424, 37.737908464062734 ], [ -122.368765818278675, 37.737903412131701 ], [ -122.368781247712349, 37.737898016972643 ], [ -122.368794948383581, 37.737892649505248 ], [ -122.368810368835511, 37.737886911121841 ], [ -122.368825781337947, 37.737880829224856 ], [ -122.368839464732346, 37.737874775025681 ], [ -122.368854868259206, 37.737868350178935 ], [ -122.368868543017058, 37.737861952750357 ], [ -122.368883937913694, 37.737855184948373 ], [ -122.368897604027694, 37.737848444015853 ], [ -122.368911261520921, 37.737841360404865 ], [ -122.368938559214428, 37.737826505902383 ], [ -122.368952199435157, 37.737818735834644 ], [ -122.368960558042616, 37.737807272000303 ], [ -122.368970654743052, 37.73779612423332 ], [ -122.368982505756449, 37.737785978727096 ], [ -122.368996120067735, 37.737777178976209 ], [ -122.369011506288842, 37.737770067382321 ], [ -122.369028672734785, 37.73776498827452 ], [ -122.369045883019595, 37.737761624734098 ], [ -122.369078712268717, 37.737760417244921 ], [ -122.369096068971245, 37.73776288881907 ], [ -122.369113468840041, 37.737767076520647 ], [ -122.369161848333334, 37.73776527896748 ], [ -122.369191211420912, 37.737763783069852 ], [ -122.369249902712198, 37.73775941835428 ], [ -122.369279231606328, 37.737756549525336 ], [ -122.369306822754865, 37.737753364895219 ], [ -122.369336134037468, 37.737749809880221 ], [ -122.369365436669298, 37.73774591108279 ], [ -122.369394721690568, 37.73774132610621 ], [ -122.369422278297634, 37.737736768819744 ], [ -122.369451546734155, 37.737731497366127 ], [ -122.369479077416202, 37.737725909839305 ], [ -122.369508328229003, 37.737719951925108 ], [ -122.369535841646382, 37.737713678482258 ], [ -122.369565066537618, 37.737706690601534 ], [ -122.369592562667947, 37.737699730418903 ], [ -122.36964752037818, 37.737684437405164 ], [ -122.369674981943263, 37.737676104024878 ], [ -122.369702434873361, 37.737667427686809 ], [ -122.369729879161198, 37.737658408116381 ], [ -122.369755577409364, 37.737648729520856 ], [ -122.369783004411687, 37.737639023486132 ], [ -122.369808694010004, 37.737629001653048 ], [ -122.36983436598301, 37.737618293367909 ], [ -122.369861767054431, 37.73760755763697 ], [ -122.369877075781517, 37.737597357165455 ], [ -122.369888995386347, 37.737589957380784 ], [ -122.369902652730687, 37.737582873380028 ], [ -122.369916335982509, 37.737576819055491 ], [ -122.369931756973841, 37.737571080514087 ], [ -122.369945457494808, 37.737565712638037 ], [ -122.369960912683439, 37.737561347002035 ], [ -122.369976376507211, 37.737557324589964 ], [ -122.369991865894718, 37.737554331859151 ], [ -122.370007364616541, 37.737551682615816 ], [ -122.370022880252051, 37.737549719278675 ], [ -122.370060808948153, 37.737544996950575 ], [ -122.370098721055868, 37.737539588147442 ], [ -122.370119388417834, 37.737536169853662 ], [ -122.370138327019674, 37.737532778995096 ], [ -122.370157256636233, 37.737529044913018 ], [ -122.37017617830449, 37.737524967591014 ], [ -122.37021400367405, 37.737516126771574 ], [ -122.370232907361171, 37.737511362724987 ], [ -122.370251803106058, 37.737506255713058 ], [ -122.370270689864256, 37.737500805477644 ], [ -122.370287847523556, 37.73749538296029 ], [ -122.370306725983596, 37.737489589487708 ], [ -122.370325595449458, 37.737483452517054 ], [ -122.370342727177842, 37.737477000039355 ], [ -122.370361588351471, 37.737470520106122 ], [ -122.370378711780674, 37.737463724391766 ], [ -122.370412941358637, 37.737449446778328 ], [ -122.370464233848367, 37.737425970297551 ], [ -122.370498394270641, 37.737408946577332 ], [ -122.370513745375163, 37.737400462163315 ], [ -122.37052385906631, 37.737390000714299 ], [ -122.370534015260773, 37.737381255404934 ], [ -122.370547663865693, 37.737373828103252 ], [ -122.370561364305786, 37.737368460155437 ], [ -122.370576845682166, 37.737365124110752 ], [ -122.370606243142632, 37.737365000764086 ], [ -122.370621879689367, 37.737367842785233 ], [ -122.370637533178453, 37.737371371536177 ], [ -122.370653178706647, 37.737374556499006 ], [ -122.370670526731246, 37.737376684615533 ], [ -122.370687848836141, 37.737377783051812 ], [ -122.370705153660893, 37.737378195033756 ], [ -122.370720694824087, 37.737377261562422 ], [ -122.370737948151799, 37.737375614178688 ], [ -122.370755183506148, 37.737373280351626 ], [ -122.370770656234214, 37.73736960105559 ], [ -122.370787840087672, 37.737365207862915 ], [ -122.370803277557826, 37.737360155669997 ], [ -122.370818689794845, 37.737354073786427 ], [ -122.370832347014144, 37.737346989952236 ], [ -122.370845986935052, 37.737339219115491 ], [ -122.370859609577963, 37.737330762099973 ], [ -122.37087148583683, 37.737321646086173 ], [ -122.370883345501966, 37.737311843608374 ], [ -122.370893458790221, 37.73730138240758 ], [ -122.370905214055995, 37.73728746095329 ], [ -122.370915232622579, 37.737273223985788 ], [ -122.370938760775033, 37.737246068067016 ], [ -122.370952262410057, 37.737232805878257 ], [ -122.370964052918382, 37.737220257585115 ], [ -122.370979300231937, 37.737207654399519 ], [ -122.370992836419262, 37.737195765109583 ], [ -122.371008110346423, 37.737184191861481 ], [ -122.371021663808406, 37.737172989019946 ], [ -122.371038683399732, 37.737162074224159 ], [ -122.371053991182038, 37.737151873609982 ], [ -122.371071037042427, 37.737141988756214 ], [ -122.371088090848829, 37.737132447136844 ], [ -122.371105162626606, 37.737123591955786 ], [ -122.371122242351063, 37.73711508000909 ], [ -122.371141068795652, 37.737107227047161 ], [ -122.371159903532885, 37.737099717313619 ], [ -122.37117875555127, 37.737092894028791 ], [ -122.371197615863039, 37.737086413972314 ], [ -122.371216494148271, 37.737080620353588 ], [ -122.371237109474734, 37.737075142508274 ], [ -122.371256013343697, 37.737070378840897 ], [ -122.371276663229182, 37.737066273617579 ], [ -122.371297321762341, 37.737062511891089 ], [ -122.371316268832857, 37.737059464068786 ], [ -122.371347308485454, 37.737055880822481 ], [ -122.371363160732827, 37.737067303397573 ], [ -122.371385487850503, 37.737061111612277 ], [ -122.371409819958558, 37.737065875598411 ], [ -122.371432414333356, 37.737070323816937 ], [ -122.371456712218176, 37.737073714884438 ], [ -122.371480992125981, 37.737076419506209 ], [ -122.371505255439089, 37.737078437660294 ], [ -122.371529509424818, 37.737080112869066 ], [ -122.371555466903885, 37.73708073037561 ], [ -122.371579678008715, 37.737080689164763 ], [ -122.371603880469053, 37.737080304723065 ], [ -122.371628056995533, 37.737078890598873 ], [ -122.371652224876399, 37.737077133243893 ], [ -122.371676366821816, 37.737074346206427 ], [ -122.371700500120539, 37.737071215938158 ], [ -122.37172460748225, 37.737067055987431 ], [ -122.371748706203064, 37.737062553080534 ], [ -122.371772778971504, 37.737057019942043 ], [ -122.371791673803557, 37.737051912693794 ], [ -122.371807120477257, 37.737047203583948 ], [ -122.371822584094161, 37.737043180929248 ], [ -122.371838065007339, 37.737039844998783 ], [ -122.371853562850688, 37.737037194974249 ], [ -122.371869078682678, 37.737035231662922 ], [ -122.371884611459564, 37.737033954806734 ], [ -122.371907076518241, 37.737033254815913 ], [ -122.371922652170909, 37.737033693814745 ], [ -122.371938237168152, 37.737034476300948 ], [ -122.371953839112081, 37.737035945242191 ], [ -122.371969466994983, 37.737038443858872 ], [ -122.371985103179384, 37.737041285704763 ], [ -122.372000757348985, 37.737044813989243 ], [ -122.372020067187322, 37.7370561815437 ], [ -122.372035876997472, 37.737065887888214 ], [ -122.372049966365637, 37.737075964928742 ], [ -122.372064081677323, 37.737087071645014 ], [ -122.372076467900058, 37.737098205832325 ], [ -122.372088880758042, 37.737110369684729 ], [ -122.372099573180051, 37.737122904509278 ], [ -122.37211027423858, 37.737135782009496 ], [ -122.372119254860294, 37.737149030482428 ], [ -122.372128244823543, 37.737162622169478 ], [ -122.372145506378345, 37.737161318085484 ], [ -122.372167936525273, 37.737159244598097 ], [ -122.372188603686311, 37.73715582594123 ], [ -122.372209253557884, 37.737151721103693 ], [ -122.372228157715924, 37.737146956725766 ], [ -122.372248764338792, 37.737141135477749 ], [ -122.372267624915224, 37.737134655244574 ], [ -122.372283045287503, 37.737128916400529 ], [ -122.372293192944326, 37.737119827982468 ], [ -122.372299822556045, 37.737108391374868 ], [ -122.372301239987792, 37.73709600777574 ], [ -122.372373414633373, 37.737077692981821 ], [ -122.372418232665979, 37.737071830045089 ], [ -122.372464787747589, 37.737066283124271 ], [ -122.372509631380041, 37.737061450384822 ], [ -122.37255449229049, 37.737057303530676 ], [ -122.372599370500893, 37.737053843385567 ], [ -122.372645994751693, 37.737051042199361 ], [ -122.372690898556655, 37.737048611702214 ], [ -122.372735828999197, 37.737047210854271 ], [ -122.372782496496995, 37.737046126017439 ], [ -122.372808401383125, 37.737044683904415 ], [ -122.372830858125084, 37.737043640231676 ], [ -122.372853323170872, 37.737042939785901 ], [ -122.372899999307762, 37.737042197853711 ], [ -122.37292249030402, 37.737042527072028 ], [ -122.372944989951108, 37.737043199511795 ], [ -122.372969218351344, 37.737043844741514 ], [ -122.373014252244417, 37.737046562227299 ], [ -122.373036786152014, 37.737048607558009 ], [ -122.373061049498276, 37.737050625391774 ], [ -122.373083592412328, 37.73705301420835 ], [ -122.37312871282991, 37.73705916390778 ], [ -122.37317385056528, 37.737066000315984 ], [ -122.373196436400818, 37.737070105245131 ], [ -122.373219031568397, 37.737074552835523 ], [ -122.373239897653349, 37.737079028185967 ], [ -122.373285121933037, 37.737089296808776 ], [ -122.373306013982969, 37.737094801824675 ], [ -122.373328651749006, 37.737100966082771 ], [ -122.373370470468529, 37.737113348723689 ], [ -122.373393126235527, 37.737120199135234 ], [ -122.373434996907847, 37.737134641656631 ], [ -122.373455940892711, 37.737142205725576 ], [ -122.373476893541877, 37.737150113291072 ], [ -122.373496125754656, 37.737158391572102 ], [ -122.373538065688621, 37.73717557958782 ], [ -122.373776518618286, 37.737372998626824 ], [ -122.373949450475536, 37.737509653888004 ], [ -122.374125779894442, 37.737643851318353 ], [ -122.374316078561918, 37.737783320091772 ], [ -122.374328378667045, 37.737791021785021 ], [ -122.374342390568998, 37.737798009519793 ], [ -122.374356385166024, 37.737804311076282 ], [ -122.374372082895462, 37.737809555173321 ], [ -122.374387762959685, 37.737814112547987 ], [ -122.374403417067299, 37.737817640792912 ], [ -122.374419053501768, 37.737820482040924 ], [ -122.374434673327585, 37.73782263737381 ], [ -122.374450266821725, 37.73782376248419 ], [ -122.374467562767649, 37.7378238306935 ], [ -122.374483104668272, 37.737822896715045 ], [ -122.3745003576703, 37.737821248786098 ], [ -122.374522744667573, 37.737817458997327 ], [ -122.374650468637824, 37.737875858898875 ], [ -122.374739734976373, 37.737918045764403 ], [ -122.374827316638459, 37.737961976186384 ], [ -122.374913186617448, 37.738006620783011 ], [ -122.374999090980893, 37.738052637675224 ], [ -122.375048414507546, 37.73808824928939 ], [ -122.375108232298459, 37.738128500964891 ], [ -122.375139852148493, 37.738147912836176 ], [ -122.375171464045508, 37.73816698146242 ], [ -122.375203066606062, 37.738185706865586 ], [ -122.375234661212687, 37.738204089023711 ], [ -122.375266237820483, 37.738221784733298 ], [ -122.375299535245929, 37.738239109683342 ], [ -122.375332823679372, 37.738256091403926 ], [ -122.375366094804107, 37.738272386664057 ], [ -122.375399356935574, 37.738288338694765 ], [ -122.375432611110568, 37.738303947479515 ], [ -122.37546757640348, 37.738318842292436 ], [ -122.375502533047822, 37.738333393869439 ], [ -122.375537481043125, 37.738347602210531 ], [ -122.375572411725926, 37.738361124090282 ], [ -122.375609062188403, 37.738374275220636 ], [ -122.375643966907035, 37.738386767402702 ], [ -122.37568059209606, 37.738398888823312 ], [ -122.375717199624106, 37.7384103237871 ], [ -122.375753798499645, 37.738421415514019 ], [ -122.375790388383365, 37.73843216428417 ], [ -122.375828690404418, 37.738442198509603 ], [ -122.375865245990923, 37.738451574074567 ], [ -122.375903521699868, 37.738460578879355 ], [ -122.375941788753465, 37.738469240446264 ], [ -122.375992156916524, 37.738477709334177 ], [ -122.376007733601398, 37.738478148331104 ], [ -122.376025030039884, 37.738478216031723 ], [ -122.376042317820222, 37.738477940778964 ], [ -122.376057859492079, 37.73847700659843 ], [ -122.376075120930508, 37.738475701670041 ], [ -122.376092365729193, 37.738473710277233 ], [ -122.376107872737933, 37.738471403188342 ], [ -122.376125099858029, 37.738468725345385 ], [ -122.376140580868082, 37.738465388575889 ], [ -122.376157790308596, 37.738462024282761 ], [ -122.376188700679592, 37.738453291649634 ], [ -122.376205866785881, 37.738448211222327 ], [ -122.376221296131305, 37.738442814809545 ], [ -122.376265793698082, 37.738424251349997 ], [ -122.376279450587873, 37.738417166611207 ], [ -122.37629309880883, 37.738409738645394 ], [ -122.37632036057704, 37.738393509807281 ], [ -122.376333982797163, 37.738385052434992 ], [ -122.376357734990449, 37.738366819020342 ], [ -122.376369602073851, 37.738357359228573 ], [ -122.376381452512433, 37.738347212973743 ], [ -122.376391573827405, 37.738337094253737 ], [ -122.376401686479809, 37.738326632582023 ], [ -122.376411790448728, 37.738315827134819 ], [ -122.376421885754795, 37.738304678735901 ], [ -122.376430251938501, 37.738293557872694 ], [ -122.376446950667884, 37.738269943226257 ], [ -122.376453571082962, 37.738258163436321 ], [ -122.376017920759935, 37.737991096533946 ], [ -122.37598688059623, 37.737994681015465 ], [ -122.375962730194601, 37.737997125704617 ], [ -122.375940290890341, 37.737998856694041 ], [ -122.375916122804242, 37.738000614653615 ], [ -122.375891946066474, 37.73800202993192 ], [ -122.375869489077928, 37.738003074187837 ], [ -122.375845295342131, 37.738003802450933 ], [ -122.37582109225724, 37.738004187769143 ], [ -122.375798609973742, 37.738004202598695 ], [ -122.375774389906638, 37.738003901450845 ], [ -122.375750161169094, 37.738003256797974 ], [ -122.375727661205985, 37.738002584893898 ], [ -122.37570341480351, 37.738001254060514 ], [ -122.375679160430039, 37.737999579985654 ], [ -122.375656634485836, 37.737997878666114 ], [ -122.375632362435553, 37.737995517861528 ], [ -122.375609819167565, 37.737993130082067 ], [ -122.375585529448685, 37.737990082822328 ], [ -122.37556296919702, 37.737987008302788 ], [ -122.375540391282939, 37.737983247608021 ], [ -122.37551432983625, 37.737978511718602 ], [ -122.375486496320931, 37.737972087487712 ], [ -122.375460382568676, 37.737965292513373 ], [ -122.375432514421007, 37.737957495642242 ], [ -122.375406358052871, 37.737948984242749 ], [ -122.375380184027932, 37.737939786666473 ], [ -122.375353992685788, 37.737929902633333 ], [ -122.375329512792433, 37.737919304628377 ], [ -122.375305015582782, 37.737908020167239 ], [ -122.37528050105756, 37.737896049249933 ], [ -122.375255969224384, 37.737883392151048 ], [ -122.375233148827334, 37.737870020533251 ], [ -122.375210319784216, 37.737856305960051 ], [ -122.375189193529863, 37.73784153419291 ], [ -122.37516805862289, 37.737826419196367 ], [ -122.375146906409697, 37.737810618019523 ], [ -122.375127474300257, 37.737794445826715 ], [ -122.375108024870912, 37.737777586905018 ], [ -122.375090295558422, 37.737760357517622 ], [ -122.375072548933076, 37.737742441676424 ], [ -122.37505479366277, 37.737724182881529 ], [ -122.375038749481291, 37.737705209578436 ], [ -122.375024434427573, 37.737686209306304 ], [ -122.375010102046858, 37.737666522032093 ], [ -122.374995761020472, 37.737646491805087 ], [ -122.374983140446119, 37.737626090835334 ], [ -122.374961338821961, 37.737584547432398 ], [ -122.374950420699136, 37.737563089278161 ], [ -122.374942960095922, 37.737541576104334 ], [ -122.374933762081014, 37.737519747213518 ], [ -122.374928021582775, 37.737497863303837 ], [ -122.374916523276909, 37.737453409032263 ], [ -122.374908906819144, 37.737425717795361 ], [ -122.374901394283526, 37.737402145264888 ], [ -122.374889826731234, 37.737354945185302 ], [ -122.374887491804515, 37.737330946902453 ], [ -122.3748834277893, 37.737306976407964 ], [ -122.374882821609901, 37.737282950341836 ], [ -122.374880487033479, 37.737258952053075 ], [ -122.374882732189576, 37.737210845452722 ], [ -122.374885583514967, 37.737186764643965 ], [ -122.374888443498122, 37.737163027060632 ], [ -122.374893031881015, 37.737139261974114 ], [ -122.37489934935347, 37.73711546937318 ], [ -122.3749056754816, 37.737092019997398 ], [ -122.374921802888267, 37.737045752947004 ], [ -122.374931603466351, 37.737022935008291 ], [ -122.374941413382857, 37.737000460008709 ], [ -122.37495296035803, 37.736978301004505 ], [ -122.374966236069355, 37.736956114489473 ], [ -122.374979529092755, 37.736934614424079 ], [ -122.375009607247023, 37.736892932445095 ], [ -122.375026392362614, 37.736872749981458 ], [ -122.375043186136296, 37.736852911015554 ], [ -122.37504977201327, 37.736839758673845 ], [ -122.375040807874441, 37.736827196605702 ], [ -122.375029742270328, 37.736799903626249 ], [ -122.375025921408564, 37.736785543166128 ], [ -122.37502556634017, 37.73677147091604 ], [ -122.375026931350064, 37.736757027929769 ], [ -122.375028304673904, 37.736742928174571 ], [ -122.375033136164149, 37.736728773387163 ], [ -122.3750397133647, 37.736715277545557 ], [ -122.37504803628174, 37.736702440924056 ], [ -122.375058114591354, 37.736690605907675 ], [ -122.375071641371591, 37.736678372922562 ], [ -122.375085194128815, 37.73666716961273 ], [ -122.375098729567796, 37.736655280124666 ], [ -122.375110518931734, 37.736642731146382 ], [ -122.375122290631623, 37.736629495995757 ], [ -122.375132325623056, 37.736615945119389 ], [ -122.375140614208931, 37.736601735308589 ], [ -122.375147156735764, 37.736586866558262 ], [ -122.375155332379663, 37.736568194818872 ], [ -122.375160085887146, 37.736550950995301 ], [ -122.375166117416924, 37.736515831932394 ], [ -122.375169124525783, 37.736497929449705 ], [ -122.375168232453305, 37.736462576936368 ], [ -122.375163874266519, 37.736426936496883 ], [ -122.375219437861915, 37.736298664625259 ], [ -122.375236617355156, 37.736225597875112 ], [ -122.375493436762397, 37.736054292153611 ], [ -122.375564665724326, 37.73599856352913 ], [ -122.375665547467477, 37.735884334377992 ], [ -122.375736776137472, 37.735828605647917 ], [ -122.375805543594495, 37.735812402863488 ], [ -122.37590130745248, 37.735838004215466 ], [ -122.375948138753657, 37.735843438862048 ], [ -122.375966461137224, 37.735815678053854 ], [ -122.375965386747623, 37.735773118079564 ], [ -122.375936427668094, 37.735722074898895 ], [ -122.375905272384003, 37.735652524496416 ], [ -122.375880455265374, 37.735628540802992 ], [ -122.375898405065016, 37.735586021302886 ], [ -122.375909891231828, 37.735561459614892 ], [ -122.375897018810662, 37.735531105204046 ], [ -122.375855226545227, 37.735519752858821 ], [ -122.375828603150225, 37.735492707640475 ], [ -122.37586986690205, 37.735483123506768 ], [ -122.375876469825457, 37.735470657017984 ], [ -122.375850080018679, 37.735452878901683 ], [ -122.375875629426574, 37.73543736440692 ], [ -122.375902751563189, 37.735415643772811 ], [ -122.37592791070513, 37.735384683844323 ], [ -122.375963949778878, 37.735305136416706 ], [ -122.375989997452365, 37.735240856058539 ], [ -122.376000720711573, 37.735186090510247 ], [ -122.375916941813387, 37.735087162261337 ], [ -122.375874456680464, 37.735048351870503 ], [ -122.37583223555086, 37.735088510886278 ], [ -122.375790902711827, 37.735095349476808 ], [ -122.375731004666164, 37.735120338792314 ], [ -122.375697972699726, 37.73518198337095 ], [ -122.375629431028628, 37.735207109686826 ], [ -122.375573154825574, 37.735238564895731 ], [ -122.37553454675556, 37.735284846828293 ], [ -122.375502844795335, 37.735330675452381 ], [ -122.375460320132888, 37.735358821434986 ], [ -122.375407345243758, 37.735384043468251 ], [ -122.375373074275174, 37.73539660668353 ], [ -122.375319406071526, 37.735394370629407 ], [ -122.375255063184753, 37.735380286724919 ], [ -122.375204090598487, 37.735347791725545 ], [ -122.375165443829161, 37.735324028264564 ], [ -122.37512657258921, 37.735291340362735 ], [ -122.375075747344539, 37.735264680144091 ], [ -122.375062572554327, 37.735222312735722 ], [ -122.375055042872361, 37.735198053755049 ], [ -122.375042101935179, 37.735164953712463 ], [ -122.375037873785985, 37.735134461646254 ], [ -122.375036955812135, 37.735098079446828 ], [ -122.375058051950944, 37.735043149193146 ], [ -122.375007538650593, 37.735028844794947 ], [ -122.3749660498938, 37.735029505033381 ], [ -122.374934543651534, 37.735014555319857 ], [ -122.374908309749543, 37.735002954785188 ], [ -122.374876500793206, 37.734975991875665 ], [ -122.374871744160856, 37.734924563038625 ], [ -122.374892528974101, 37.73485727640653 ], [ -122.374913772355171, 37.7348081807394 ], [ -122.374946666252242, 37.734741044765023 ], [ -122.374988965601105, 37.734703975095854 ], [ -122.375029917257038, 37.734682034854018 ], [ -122.375070713005826, 37.734653916534754 ], [ -122.375104671911529, 37.734628997279636 ], [ -122.375115698854458, 37.734586244981536 ], [ -122.375146199993921, 37.734561380743287 ], [ -122.375129646198275, 37.734522157113517 ], [ -122.375090930945873, 37.734495647813482 ], [ -122.375036267841509, 37.734453940390075 ], [ -122.374981985833443, 37.734427335148574 ], [ -122.374938543580228, 37.734419099017749 ], [ -122.374912994333698, 37.734434613586529 ], [ -122.374893666730827, 37.734422560335261 ], [ -122.374893060571651, 37.734398534257735 ], [ -122.374907614581076, 37.734358472486285 ], [ -122.374894292846008, 37.734310270489409 ], [ -122.374881421402023, 37.734279915955867 ], [ -122.37487704615539, 37.734243588766795 ], [ -122.374869213561681, 37.734207316868407 ], [ -122.374868295688771, 37.734170934936692 ], [ -122.374926339800538, 37.734072496020858 ], [ -122.374952543818168, 37.734014394217503 ], [ -122.374954780508872, 37.73396594408409 ], [ -122.374957241667502, 37.733926418380705 ], [ -122.374963229775304, 37.733889582909434 ], [ -122.374983289721541, 37.733862137955839 ], [ -122.375012218505518, 37.733843479312256 ], [ -122.375069108753891, 37.733836393394171 ], [ -122.375122463708166, 37.733826273440556 ], [ -122.375153432543385, 37.73381994338358 ], [ -122.375171833015472, 37.733795271716147 ], [ -122.375171062228418, 37.733764724622461 ], [ -122.375229769357603, 37.733624041261521 ], [ -122.375288514453459, 37.733553403441164 ], [ -122.375324856119903, 37.733485869092775 ], [ -122.375361353624825, 37.733424512796418 ], [ -122.375375681648336, 37.73337552709593 ], [ -122.375382713915272, 37.733311549522142 ], [ -122.375463619327064, 37.733228197775681 ], [ -122.375509608742206, 37.733200339972562 ], [ -122.375523633451934, 37.73313934134989 ], [ -122.37550722668901, 37.733105952882383 ], [ -122.375651602500071, 37.732933689521552 ], [ -122.375685404494646, 37.732902592027898 ], [ -122.375688325042901, 37.732881256983333 ], [ -122.375650684266844, 37.732897307862871 ], [ -122.375550384562601, 37.732828858101236 ], [ -122.375617693995721, 37.732754993758505 ], [ -122.375573481700997, 37.732716210488881 ], [ -122.375197427066922, 37.732959117287578 ], [ -122.375009435995196, 37.732773602543226 ], [ -122.374988848118562, 37.732780110729863 ], [ -122.374969139909751, 37.732752955276041 ], [ -122.374890774233947, 37.732799869727202 ], [ -122.374867609109913, 37.732772769268017 ], [ -122.374833347919932, 37.732785675832154 ], [ -122.374804918949508, 37.732755568576273 ], [ -122.374609189594366, 37.732880233977106 ], [ -122.374557526391285, 37.732820280340718 ], [ -122.37490487958712, 37.732604958073601 ], [ -122.374821719829242, 37.732530398040169 ], [ -122.37467574043562, 37.732639163620497 ], [ -122.37439309494539, 37.732540308159429 ], [ -122.374310276013688, 37.732547806255845 ], [ -122.374229713731395, 37.732576213850287 ], [ -122.374176359200263, 37.732586333132673 ], [ -122.374120153956625, 37.732620533463049 ], [ -122.374114165533243, 37.73265736889919 ], [ -122.374052159400549, 37.732667282379175 ], [ -122.374002184525267, 37.73267425782587 ], [ -122.373911483788106, 37.732712096999926 ], [ -122.373893083165825, 37.732736768474979 ], [ -122.373883628328045, 37.732773315665987 ], [ -122.373790281780344, 37.732774800286634 ], [ -122.373761509214617, 37.732799636420175 ], [ -122.373753549290342, 37.732826888733754 ], [ -122.373747327007891, 37.732854456775314 ], [ -122.373725625111732, 37.732885361268295 ], [ -122.373694734433712, 37.732894780258938 ], [ -122.373637610789032, 37.732892598419873 ], [ -122.373568854206965, 37.732909142898315 ], [ -122.37354165442683, 37.732927773970779 ], [ -122.373512804196523, 37.732949521278634 ], [ -122.37350146473058, 37.732979917309954 ], [ -122.373503074145475, 37.733043757608968 ], [ -122.373500309404122, 37.733071270654619 ], [ -122.373481674928072, 37.73308667468919 ], [ -122.373435459514155, 37.733105607832456 ], [ -122.373412831615411, 37.733099787091327 ], [ -122.373389322638175, 37.733127629980004 ], [ -122.373370999608952, 37.73315539040528 ], [ -122.373326815604614, 37.733186309201884 ], [ -122.373292553942591, 37.733199214767993 ], [ -122.373255060257023, 37.73322109971145 ], [ -122.373217868644616, 37.733254997012843 ], [ -122.373184748909821, 37.733313208646514 ], [ -122.373147255135009, 37.733335093555283 ], [ -122.373108032325476, 37.733357005665319 ], [ -122.373055135575555, 37.733385315688466 ], [ -122.373026509933439, 37.733415986475677 ], [ -122.372989171414929, 37.733444049131762 ], [ -122.372947104996001, 37.733490385196987 ], [ -122.372903457602277, 37.733542583556414 ], [ -122.372843059429968, 37.73361633669991 ], [ -122.372787475219468, 37.733675248937253 ], [ -122.372660653342237, 37.733789544305665 ], [ -122.372615406285462, 37.73384691842643 ], [ -122.372571792697812, 37.733900489569244 ], [ -122.372542285196062, 37.733964824408801 ], [ -122.372515465283058, 37.733998556633921 ], [ -122.372467598558771, 37.73402060589649 ], [ -122.372390422139858, 37.734046211198297 ], [ -122.372337516324379, 37.734074177664638 ], [ -122.372263676850309, 37.734163595440648 ], [ -122.372229942026564, 37.734197437754013 ], [ -122.37219905926051, 37.734207199576488 ], [ -122.372135400372358, 37.734220228553554 ], [ -122.372066183983833, 37.734218581449781 ], [ -122.37202454003129, 37.734213062859681 ], [ -122.371977485584694, 37.734198702222137 ], [ -122.371931036017173, 37.73420836793948 ], [ -122.371912020534467, 37.734208670054151 ], [ -122.371892468688813, 37.734187692159942 ], [ -122.371850522169254, 37.73417016004948 ], [ -122.371815256975381, 37.734143251243232 ], [ -122.37178360511767, 37.734122465563011 ], [ -122.371757069813924, 37.734098851958706 ], [ -122.371718425134958, 37.734075087082275 ], [ -122.371658811600753, 37.734042727577382 ], [ -122.37159046796063, 37.734007073362946 ], [ -122.371530163390418, 37.733947255979729 ], [ -122.371503472637912, 37.733917463974464 ], [ -122.371452035953411, 37.733866433138324 ], [ -122.371412863920071, 37.733821731652299 ], [ -122.371365343126712, 37.733788836837448 ], [ -122.371306006337434, 37.733698787998705 ], [ -122.37124803587426, 37.73366296856193 ], [ -122.371181508213965, 37.733630718908138 ], [ -122.371142786185317, 37.733603864806447 ], [ -122.371119319714566, 37.733564750701184 ], [ -122.371068652635969, 37.733544266816153 ], [ -122.37104196222009, 37.733514474978968 ], [ -122.371009541074471, 37.733463141702856 ], [ -122.370970292434578, 37.733415350746299 ], [ -122.370945321772282, 37.733385187940364 ], [ -122.370906375642221, 37.733349409875622 ], [ -122.370882676078168, 37.733301028616602 ], [ -122.370853876981954, 37.733256162236223 ], [ -122.370812000355784, 37.733241375562343 ], [ -122.370762102212552, 37.733251438949395 ], [ -122.3707392498319, 37.733236693541137 ], [ -122.370742015947201, 37.733209180555527 ], [ -122.370734029961071, 37.733166730305776 ], [ -122.370713641007328, 37.733112459546398 ], [ -122.370604476976737, 37.73303521894465 ], [ -122.370512089823777, 37.733006126395267 ], [ -122.370444949736466, 37.73294950753936 ], [ -122.370336702208434, 37.732908648655865 ], [ -122.370231912105666, 37.732867735066925 ], [ -122.37017410658585, 37.732838436949166 ], [ -122.370077726716076, 37.732788118918357 ], [ -122.369991114296425, 37.732713610079365 ], [ -122.369927812152071, 37.732672038006164 ], [ -122.369888943911135, 37.73263934837334 ], [ -122.369823835861169, 37.732594714362811 ], [ -122.369764448866945, 37.732571278048333 ], [ -122.369664767427579, 37.732527192896548 ], [ -122.369608556728025, 37.732490658570462 ], [ -122.369587411983062, 37.732476915661685 ], [ -122.369536133374979, 37.732432062044303 ], [ -122.369522426580374, 37.732368414248455 ], [ -122.369524131442745, 37.732298684450967 ], [ -122.369528168991479, 37.732252953067388 ], [ -122.369514764766407, 37.732201317633788 ], [ -122.369460037420595, 37.732156862346898 ], [ -122.369435499832107, 37.732143860523472 ], [ -122.369412699166176, 37.732131174223944 ], [ -122.369391627493243, 37.732118460761214 ], [ -122.369368818907404, 37.732105431490474 ], [ -122.369347729982366, 37.732092031567248 ], [ -122.369305534888653, 37.732064545256826 ], [ -122.36928614837251, 37.732050088222451 ], [ -122.369265033578955, 37.732035658605412 ], [ -122.369208612399049, 37.731992603257169 ], [ -122.36919310687361, 37.731994909435159 ], [ -122.369177583743095, 37.731996529438547 ], [ -122.369162034700395, 37.731997119211783 ], [ -122.369144739776431, 37.731997050503772 ], [ -122.369129147571257, 37.731995924141103 ], [ -122.369113529476451, 37.731993768371993 ], [ -122.369097893772235, 37.731990926153657 ], [ -122.369082241496287, 37.731987397469659 ], [ -122.369068291609253, 37.731982811686983 ], [ -122.369052587205204, 37.731977223921163 ], [ -122.369038602793921, 37.731971265229653 ], [ -122.369026321118199, 37.73196424943503 ], [ -122.36900353818659, 37.731952249503117 ], [ -122.368984376529525, 37.731946716297898 ], [ -122.368965231792913, 37.731941869547697 ], [ -122.368946113651845, 37.731938052736979 ], [ -122.368927003791569, 37.731934578880427 ], [ -122.368907928458668, 37.731932477926101 ], [ -122.368887133122826, 37.731930747340918 ], [ -122.368868091978015, 37.73193001956556 ], [ -122.368847348437356, 37.731930348605978 ], [ -122.368826613520639, 37.731931020594487 ], [ -122.368807642108294, 37.731933038334546 ], [ -122.368788678980067, 37.731935399303374 ], [ -122.368768013114249, 37.731938817366917 ], [ -122.368749101767222, 37.731943237687446 ], [ -122.368730207333343, 37.731948344463113 ], [ -122.368711330849322, 37.731954137677484 ], [ -122.368694217167373, 37.731961276382087 ], [ -122.368680664304932, 37.731972478965581 ], [ -122.368668865613287, 37.731984683814787 ], [ -122.368657084876617, 37.73199757537909 ], [ -122.368648777958526, 37.732011098297363 ], [ -122.368640488989229, 37.732025307656784 ], [ -122.368633936925264, 37.73203983255609 ], [ -122.368629130411534, 37.732055016771099 ], [ -122.368626053219003, 37.732070173558292 ], [ -122.368624712941653, 37.732085646160606 ], [ -122.368625101296246, 37.732101091346223 ], [ -122.368627218283763, 37.732116509115187 ], [ -122.368631063559619, 37.732131899472847 ], [ -122.368636620901697, 37.732146575949926 ], [ -122.368642134756229, 37.73215953657504 ], [ -122.36864418270045, 37.732172208258227 ], [ -122.368644510648082, 37.732185250859203 ], [ -122.368643101337838, 37.732197977925267 ], [ -122.368638226113163, 37.732210416049476 ], [ -122.368633341924749, 37.732222511501767 ], [ -122.368624983881077, 37.732233974774296 ], [ -122.368616599605744, 37.732244408647276 ], [ -122.368606469086217, 37.732254183488791 ], [ -122.368594576099341, 37.732262612829288 ], [ -122.368580928237833, 37.732270039911 ], [ -122.368565516878832, 37.732276121781609 ], [ -122.368551791336984, 37.732280459548285 ], [ -122.368534574353816, 37.732283479507736 ], [ -122.368503527583357, 37.732286718795137 ], [ -122.368474192879688, 37.732289244196302 ], [ -122.368444831940408, 37.732290739916586 ], [ -122.368417182378593, 37.732291521763315 ], [ -122.368387786921161, 37.732291644563965 ], [ -122.368358365231217, 37.732290737683726 ], [ -122.368330655267144, 37.732289116925642 ], [ -122.368301199755848, 37.732286837114728 ], [ -122.368273446651969, 37.732283500211402 ], [ -122.368243956285454, 37.732279847486197 ], [ -122.368216168673811, 37.732275137663905 ], [ -122.368188355181374, 37.73226939815595 ], [ -122.368160533065264, 37.732263315415068 ], [ -122.368141380018642, 37.732258125299396 ], [ -122.368113230055812, 37.732238999945096 ], [ -122.36808681703171, 37.732220190681751 ], [ -122.368058684352292, 37.732201751767072 ], [ -122.36803056030729, 37.7321836557974 ], [ -122.368002444910147, 37.732165903321956 ], [ -122.367974346780599, 37.732148837292527 ], [ -122.367944520376312, 37.73213179865752 ], [ -122.367916439533545, 37.732115419341561 ], [ -122.367886638690265, 37.732099410376478 ], [ -122.367832352327795, 37.732072458330258 ], [ -122.367819906502248, 37.73205892111374 ], [ -122.367804021362375, 37.732046125146958 ], [ -122.367788153134725, 37.73203401563628 ], [ -122.367772302171431, 37.732022592850853 ], [ -122.36775646811256, 37.732011856247048 ], [ -122.36773719473392, 37.732001860615576 ], [ -122.367719666903668, 37.731992524310549 ], [ -122.367700427353682, 37.731983901864261 ], [ -122.367681205751111, 37.731975965856719 ], [ -122.367660281054071, 37.731969086933262 ], [ -122.367641110867112, 37.73196321028319 ], [ -122.367620229304634, 37.731958047484753 ], [ -122.367599364995712, 37.731953571135463 ], [ -122.367576797935783, 37.73195015186301 ], [ -122.367555976757444, 37.731947391638229 ], [ -122.367533461105907, 37.731946031721549 ], [ -122.367512683055082, 37.731944987621269 ], [ -122.367483296703725, 37.731945453417282 ], [ -122.367460850404399, 37.731946839567364 ], [ -122.367440158593624, 37.731949227444112 ], [ -122.367417755407999, 37.731952329443608 ], [ -122.367397106730152, 37.731956433994185 ], [ -122.367376475284885, 37.731961224444852 ], [ -122.367355869716533, 37.731967044845845 ], [ -122.367335281393224, 37.731973551696129 ], [ -122.367316447568555, 37.731981060824729 ], [ -122.367295902357668, 37.73198928380036 ], [ -122.367278831305327, 37.731998138437078 ], [ -122.367261967911546, 37.732015230495207 ], [ -122.367245095194775, 37.732031979335368 ], [ -122.3672282059146, 37.732048041709206 ], [ -122.367209570402153, 37.732063445029034 ], [ -122.36719092591278, 37.732078505124896 ], [ -122.367170544505385, 37.732093249381123 ], [ -122.367151866198881, 37.732106936553961 ], [ -122.367109296757391, 37.732133363410995 ], [ -122.367093954975161, 37.732142190615349 ], [ -122.367075008646182, 37.732145238041312 ], [ -122.367056088868793, 37.732149314858027 ], [ -122.367037194611967, 37.732154421356583 ], [ -122.367020054502376, 37.732160529869304 ], [ -122.367002940956212, 37.732167668322553 ], [ -122.366985852582928, 37.732175836463647 ], [ -122.366968782142251, 37.732184691044218 ], [ -122.366955194489435, 37.732194520522967 ], [ -122.366939895442201, 37.732205063845008 ], [ -122.366921113252403, 37.732214632268928 ], [ -122.366907922208441, 37.732240250159542 ], [ -122.366903055326574, 37.732253031987497 ], [ -122.36689644254804, 37.732265154204406 ], [ -122.366886346633592, 37.732276301798386 ], [ -122.366876207612776, 37.732285733533743 ], [ -122.366862576812395, 37.732293846594757 ], [ -122.366847157024765, 37.732299584734065 ], [ -122.366829956520121, 37.73230329090832 ], [ -122.366814424990764, 37.732304567093777 ], [ -122.366797104132374, 37.732303468361799 ], [ -122.366781459823102, 37.732300282335181 ], [ -122.366765755862758, 37.732294693984812 ], [ -122.366677289399263, 37.732215062245395 ], [ -122.365834112470068, 37.733139703386506 ], [ -122.365856312715465, 37.733197380427903 ], [ -122.365316308712423, 37.733799605173139 ], [ -122.365299125359911, 37.733803997570533 ], [ -122.365281864478533, 37.733805301201542 ], [ -122.365264543279295, 37.733804201695385 ], [ -122.365248890111999, 37.733800672514192 ], [ -122.365233177335227, 37.733794740734439 ], [ -122.365220860913354, 37.733786351644746 ], [ -122.36520850211312, 37.733776246410308 ], [ -122.365199556897281, 37.733764370320301 ], [ -122.365194052497998, 37.733751753307722 ], [ -122.365191987516951, 37.733738394845759 ], [ -122.366541619606664, 37.732183905191341 ], [ -122.366457610712786, 37.732144032787161 ], [ -122.366314210759612, 37.732287082999953 ], [ -122.3662299863946, 37.73223862977143 ], [ -122.365558974179407, 37.732995040740846 ], [ -122.365446339012479, 37.733054852496743 ], [ -122.365330791791507, 37.732998653575926 ], [ -122.365348830518258, 37.732890552295949 ], [ -122.366182259071422, 37.731990790641241 ], [ -122.366150729675482, 37.731974808623058 ], [ -122.366190760220135, 37.731916146203794 ], [ -122.366000047621938, 37.731827832578617 ], [ -122.365157289100338, 37.732769287028631 ], [ -122.365013538239467, 37.732829590952356 ], [ -122.364937337201866, 37.732756287397883 ], [ -122.364966161751269, 37.732664496598098 ], [ -122.365779063978152, 37.731773647061175 ], [ -122.365808723306458, 37.731715149013056 ], [ -122.36565085480737, 37.73162631442078 ], [ -122.364828209515139, 37.732542382802549 ], [ -122.364733415884473, 37.732486198184951 ], [ -122.36510850587311, 37.732065821885968 ], [ -122.363700263748257, 37.731284294325405 ], [ -122.362990310438022, 37.732074612690823 ], [ -122.362876152103269, 37.73214268678057 ], [ -122.362621964702214, 37.732005583479989 ], [ -122.363403772813015, 37.731115242143439 ], [ -122.362852893371198, 37.730800849481362 ], [ -122.362060297076368, 37.731674875973241 ], [ -122.361775005493385, 37.731538605834466 ], [ -122.362457618945527, 37.730761087830871 ], [ -122.36256707070703, 37.730643300895977 ], [ -122.361976423103286, 37.73032884619532 ], [ -122.362085849184908, 37.730210029743688 ], [ -122.362069397962841, 37.730199017017426 ], [ -122.361906454524132, 37.730089941350215 ], [ -122.361876896198638, 37.7300835409455 ], [ -122.361847355773691, 37.730077827250902 ], [ -122.361816112221945, 37.7300731708266 ], [ -122.361786605500626, 37.73006882976096 ], [ -122.361755405286033, 37.7300658891753 ], [ -122.361724221585348, 37.730063635320825 ], [ -122.361693055759957, 37.73006206735193 ], [ -122.361661915051329, 37.730061529615817 ], [ -122.361630792215976, 37.730061677765306 ], [ -122.36159969448822, 37.730062855873037 ], [ -122.361570343233169, 37.730064693102427 ], [ -122.361539288834621, 37.730067587323632 ], [ -122.361508260224696, 37.730071511217581 ], [ -122.361487577348555, 37.730074241551335 ], [ -122.361465200270104, 37.730078372100991 ], [ -122.36144452599406, 37.730081445928768 ], [ -122.361422114524615, 37.730084203562313 ], [ -122.361399676911404, 37.730085931241504 ], [ -122.361377231405925, 37.730087316227653 ], [ -122.361354776950733, 37.730088357713434 ], [ -122.361334025287775, 37.730088341929708 ], [ -122.361311536447275, 37.730088010499266 ], [ -122.361266507191544, 37.730085288263524 ], [ -122.36124396677755, 37.73008289745821 ], [ -122.361223137761826, 37.730079792886535 ], [ -122.361200571564154, 37.73007637239192 ], [ -122.361179716772469, 37.730072238406137 ], [ -122.361157115844946, 37.730067444726025 ], [ -122.361136235265462, 37.730062280776941 ], [ -122.361115337498504, 37.730056430370134 ], [ -122.361094422544639, 37.73004989350558 ], [ -122.361073490411371, 37.73004267045787 ], [ -122.361054278613267, 37.730035076593815 ], [ -122.361035057871703, 37.730027139230472 ], [ -122.361015811356936, 37.73001817245752 ], [ -122.360991310330121, 37.730006541779254 ], [ -122.360878119757459, 37.729906007842899 ], [ -122.360753350678607, 37.729964633086148 ], [ -122.360767411397134, 37.729973682080846 ], [ -122.360779769645887, 37.729983787501872 ], [ -122.360792153340185, 37.729994922882732 ], [ -122.360802816699632, 37.730006428796685 ], [ -122.360811777948271, 37.730018991681831 ], [ -122.360819018860866, 37.730031925100903 ], [ -122.360824539436592, 37.730045229054205 ], [ -122.360830077893482, 37.730059219450361 ], [ -122.360833887418579, 37.730073237154031 ], [ -122.360834600711755, 37.730101724997503 ], [ -122.360833232727501, 37.73011616811506 ], [ -122.360830128237026, 37.730130294747774 ], [ -122.360825286222877, 37.73014410573559 ], [ -122.360820435267328, 37.730157573501607 ], [ -122.360812119233699, 37.73017075290813 ], [ -122.360803785650091, 37.730183245316248 ], [ -122.36079369735269, 37.730194735624472 ], [ -122.360781872558661, 37.730205909995462 ], [ -122.360768293049901, 37.730216082265592 ], [ -122.360754687743039, 37.730225224303872 ], [ -122.360741056659265, 37.73023333693407 ], [ -122.360665462649848, 37.730253072686551 ], [ -122.360627639840885, 37.730261910451617 ], [ -122.360550239838858, 37.730278584111481 ], [ -122.360510654054025, 37.730286076778114 ], [ -122.360471050717251, 37.730292882433631 ], [ -122.360433168073058, 37.730299317816012 ], [ -122.360393539299068, 37.730305094033461 ], [ -122.360353901588383, 37.730310527290548 ], [ -122.36031424736656, 37.730315273520141 ], [ -122.360274575618163, 37.730319333562264 ], [ -122.36023489527426, 37.730323050363999 ], [ -122.360195197398738, 37.730326080703698 ], [ -122.360155491620617, 37.730328767792003 ], [ -122.36011403972519, 37.730330795984656 ], [ -122.360074299227378, 37.730332109868229 ], [ -122.360034550144817, 37.730333080785911 ], [ -122.359994783535413, 37.730333365241471 ], [ -122.359955000437409, 37.730332963218565 ], [ -122.359915208404431, 37.73033221796058 ], [ -122.359854003265156, 37.730305028433676 ], [ -122.359236184689337, 37.730009873800213 ], [ -122.358789557472875, 37.729783434605018 ], [ -122.358957232902597, 37.729711773403928 ], [ -122.358843527027545, 37.729659659273459 ], [ -122.358967948048644, 37.72951794751873 ], [ -122.361421463309, 37.72895351973942 ], [ -122.361888691367042, 37.728828706580281 ], [ -122.361912779413899, 37.728823862451726 ], [ -122.361935121341887, 37.72881835863901 ], [ -122.361959166726493, 37.728811798085822 ], [ -122.361981474603837, 37.72880492162534 ], [ -122.362003764939914, 37.728797358986661 ], [ -122.3620260473499, 37.728789452556619 ], [ -122.362046575060617, 37.728780544041506 ], [ -122.36206882272441, 37.728771264975364 ], [ -122.362089324633246, 37.728761327046321 ], [ -122.362109817923212, 37.728751045337532 ], [ -122.362128556858551, 37.728739761540034 ], [ -122.362149024344589, 37.728728450417933 ], [ -122.362167737129766, 37.728716137213183 ], [ -122.362184713430636, 37.728703507540494 ], [ -122.36220340934922, 37.728690507870553 ], [ -122.362220359498949, 37.7286768487917 ], [ -122.362237301035364, 37.728662846208799 ], [ -122.362250811264033, 37.72864992822533 ], [ -122.362262661390673, 37.728639783385113 ], [ -122.36227275785032, 37.728628636175188 ], [ -122.362281099606278, 37.728616486612424 ], [ -122.362287695612011, 37.728603678193288 ], [ -122.362292545854572, 37.728590210368907 ], [ -122.362295667196847, 37.728576769873541 ], [ -122.36229531457775, 37.728562697566197 ], [ -122.362293242345899, 37.72854899552955 ], [ -122.362289449824587, 37.728535664323587 ], [ -122.362282165799911, 37.728521014856675 ], [ -122.36224367326102, 37.72850308198516 ], [ -122.36222272423764, 37.72849517213659 ], [ -122.362203512030703, 37.728487578468915 ], [ -122.362182580222708, 37.728480355341809 ], [ -122.362161665625351, 37.728473818939591 ], [ -122.36213904001275, 37.728467995754578 ], [ -122.362118159814372, 37.728462831978135 ], [ -122.362095568274697, 37.728458382247503 ], [ -122.36207471387344, 37.728454247869884 ], [ -122.362052148130076, 37.728450827537444 ], [ -122.362029590642308, 37.728447750433155 ], [ -122.362007059652058, 37.728445703269323 ], [ -122.361984536902597, 37.72844399878452 ], [ -122.361962039957774, 37.728443324251103 ], [ -122.361939551266488, 37.728442992945915 ], [ -122.361917071865491, 37.728443004852515 ], [ -122.361894617914601, 37.72844404644146 ], [ -122.361872181160052, 37.728445774480214 ], [ -122.361849752657179, 37.728447845747191 ], [ -122.361830790542825, 37.728450205592154 ], [ -122.36144874592047, 37.728523885066956 ], [ -122.359253585410741, 37.729046466872113 ], [ -122.358871094316868, 37.729171649615353 ], [ -122.35773138480485, 37.729443027877707 ], [ -122.357479163493281, 37.729245451178592 ], [ -122.357874687244575, 37.728949759623752 ], [ -122.360022133800982, 37.7284550828631 ], [ -122.360041044772572, 37.728450663666095 ], [ -122.360061675709247, 37.728445874222452 ], [ -122.360082280872575, 37.728440055093671 ], [ -122.360102877442358, 37.728433892734103 ], [ -122.360121736868166, 37.728427414435657 ], [ -122.360140578770867, 37.728420249959875 ], [ -122.360159404166396, 37.728412398466652 ], [ -122.36017822062847, 37.728404204023278 ], [ -122.360197019904888, 37.728395323122534 ], [ -122.360214082043697, 37.728386126559464 ], [ -122.360231126982725, 37.728376242990407 ], [ -122.360248163333068, 37.728366016466275 ], [ -122.36026519109457, 37.728355446987074 ], [ -122.360282201662045, 37.728344190776504 ], [ -122.36029747473269, 37.728332618361613 ], [ -122.360312739905595, 37.728320702981229 ], [ -122.36033977808566, 37.728295553906939 ], [ -122.360351645599536, 37.728286095712676 ], [ -122.360365302143265, 37.728279012806425 ], [ -122.360378915726486, 37.728270213762769 ], [ -122.360389029601677, 37.728259753167933 ], [ -122.360397388807456, 37.728248290192134 ], [ -122.360404002281641, 37.728236168057407 ], [ -122.360407132887943, 37.728223070833039 ], [ -122.360408534948149, 37.728210000904646 ], [ -122.360406479925388, 37.728196985842978 ], [ -122.360402696345233, 37.728183997528113 ], [ -122.360395481466924, 37.728172094035642 ], [ -122.360384800897904, 37.728159901357934 ], [ -122.360355252076161, 37.728153844345819 ], [ -122.360324001174106, 37.728148844017177 ], [ -122.360292766760338, 37.728144529870931 ], [ -122.360263287371851, 37.728141218642236 ], [ -122.360232104855839, 37.72813896383753 ], [ -122.360200939536938, 37.728137396028139 ], [ -122.360168062497735, 37.728136541413733 ], [ -122.360136940472302, 37.728136689443289 ], [ -122.360105834935908, 37.728137523929909 ], [ -122.360074755867601, 37.72813938835359 ], [ -122.3600437018695, 37.728142282186987 ], [ -122.360012665740157, 37.728145862455676 ], [ -122.359981646088997, 37.728150128906918 ], [ -122.359962709760239, 37.728153518132949 ], [ -122.357157402740128, 37.728825440588068 ], [ -122.357120443323197, 37.728661209162873 ], [ -122.356966039195882, 37.728710340572775 ], [ -122.357700794819991, 37.726282170189641 ], [ -122.358012892774155, 37.726039297958998 ], [ -122.361640904109578, 37.725218727031361 ], [ -122.361254856267323, 37.72416520811381 ], [ -122.357961297733482, 37.724943754212269 ], [ -122.357837451289754, 37.724623976380613 ], [ -122.361242175061918, 37.723839816434847 ], [ -122.361523474456462, 37.723775023602428 ], [ -122.362006156184762, 37.724061314272362 ], [ -122.362085553062059, 37.723986579862476 ], [ -122.364138473489092, 37.725245158496556 ], [ -122.364903800810126, 37.725700019832075 ], [ -122.364922994995325, 37.725706926339846 ], [ -122.364942154395848, 37.72571246021662 ], [ -122.364961297265722, 37.725717307625388 ], [ -122.364980405349158, 37.725720782403215 ], [ -122.364999487582764, 37.725723226947871 ], [ -122.365020272471824, 37.725724614719347 ], [ -122.365041031523262, 37.725724972806269 ], [ -122.365060035904975, 37.725724328578444 ], [ -122.365080734672432, 37.725722284344187 ], [ -122.365099679446047, 37.725719237235722 ], [ -122.365120326525016, 37.725715133358143 ], [ -122.365139219276756, 37.72571002716159 ], [ -122.365158077572545, 37.72570354805417 ], [ -122.365175190160244, 37.725696410130439 ], [ -122.365192276890355, 37.725688241974169 ], [ -122.365209337782844, 37.725679044409119 ], [ -122.365224644000321, 37.725668844532997 ], [ -122.365239933677543, 37.725657958189977 ], [ -122.365253477293393, 37.725646412763744 ], [ -122.365266995070428, 37.725633837929628 ], [ -122.365277047249194, 37.725620974052937 ], [ -122.365288802072243, 37.725607053401404 ], [ -122.365297091312215, 37.725592844257413 ], [ -122.365305371596236, 37.725578292165878 ], [ -122.365311906499173, 37.725563080433261 ], [ -122.365316704663556, 37.725547553385105 ], [ -122.365321493859298, 37.725531682840639 ], [ -122.365324157971415, 37.725500050955006 ], [ -122.365323753109038, 37.725483919287925 ], [ -122.365321628734094, 37.725468158485079 ], [ -122.365319495386515, 37.725452053911503 ], [ -122.365308348911952, 37.725421327973351 ], [ -122.36530105597663, 37.725406335184097 ], [ -122.365293771671986, 37.725391686170454 ], [ -122.365052724436055, 37.725226224132847 ], [ -122.362366609167395, 37.723678947002448 ], [ -122.362750319199407, 37.723258784478411 ], [ -122.359975808223695, 37.721629071078588 ], [ -122.360235314638061, 37.721358523571041 ], [ -122.362997490302945, 37.722979154853967 ], [ -122.363810654823226, 37.722102046578357 ], [ -122.359221672042082, 37.719406711581087 ], [ -122.359190415962686, 37.719401367722426 ], [ -122.359178102953109, 37.719392977976057 ], [ -122.359165764191417, 37.719383558545161 ], [ -122.359156864943031, 37.719373398101943 ], [ -122.359147948527337, 37.719362551202536 ], [ -122.359140734699196, 37.719350647341678 ], [ -122.359135249212571, 37.719338716202891 ], [ -122.359131474551347, 37.719326071336383 ], [ -122.359129420336572, 37.719313055953705 ], [ -122.359129102699868, 37.719300356526816 ], [ -122.359130504470841, 37.719287286600235 ], [ -122.359133643855102, 37.719274532613021 ], [ -122.359136791138639, 37.719262122138936 ], [ -122.359143412603785, 37.719250343285097 ], [ -122.359151771327333, 37.719238880100924 ], [ -122.359815079156419, 37.718522454511614 ], [ -122.359825208614936, 37.718512680409624 ], [ -122.359837083220526, 37.718503565479963 ], [ -122.359848984278528, 37.718495480221421 ], [ -122.359862639071579, 37.718488397362229 ], [ -122.359878056187881, 37.718482660129546 ], [ -122.359891771100081, 37.718477980135759 ], [ -122.359908985921422, 37.718474961146882 ], [ -122.359924497848894, 37.718472999407822 ], [ -122.359940043783581, 37.718472410583125 ], [ -122.359957353082052, 37.718473167366923 ], [ -122.359972959482164, 37.71847498112659 ], [ -122.359988608480634, 37.718478511028351 ], [ -122.360004292526796, 37.718483413828061 ], [ -122.360018273683508, 37.718489373879351 ], [ -122.360032280595263, 37.718496363062989 ], [ -122.360394084962891, 37.718708825628347 ], [ -122.36271365411622, 37.720070891670908 ], [ -122.36303235937622, 37.719710951141145 ], [ -122.363036490990609, 37.719706284726648 ], [ -122.363048348187263, 37.719696483020158 ], [ -122.363061977094034, 37.719688370383778 ], [ -122.363077377690658, 37.719681945993095 ], [ -122.363092812365167, 37.719676895064836 ], [ -122.363110026642786, 37.719673875619748 ], [ -122.363137662964078, 37.719672751789325 ], [ -122.363153270039291, 37.719674565121885 ], [ -122.363173373533897, 37.719648838527299 ], [ -122.363166176233463, 37.71963762109376 ], [ -122.363158953131787, 37.71962537425204 ], [ -122.363155178117267, 37.719612729509564 ], [ -122.363154851195048, 37.719599687141276 ], [ -122.363156260860393, 37.719586960120296 ], [ -122.363161127909819, 37.719574178689925 ], [ -122.363167748765107, 37.719562399610538 ], [ -122.363176106218731, 37.71955093642692 ], [ -122.363656438242259, 37.719054731797513 ], [ -122.362565556645677, 37.718438823432876 ], [ -122.362551540892355, 37.718431490773646 ], [ -122.362539227318422, 37.718423101382719 ], [ -122.362526897237885, 37.718414025524474 ], [ -122.362514558214372, 37.718404606442824 ], [ -122.36250392996385, 37.718394473583345 ], [ -122.36249501352286, 37.718383626930098 ], [ -122.362486088138667, 37.718372437054043 ], [ -122.362478882472217, 37.718360876623265 ], [ -122.362471659607849, 37.718348629736866 ], [ -122.362466165060482, 37.718336355523718 ], [ -122.362462381629214, 37.718323367528868 ], [ -122.362458606799294, 37.718310722761444 ], [ -122.362456543084349, 37.718297364212575 ], [ -122.362455889483016, 37.71827127891747 ], [ -122.362457290995351, 37.718258208943666 ], [ -122.362463550642957, 37.718232014343421 ], [ -122.362473301309748, 37.71820713800021 ], [ -122.362481650470514, 37.718195331904944 ], [ -122.362488271303789, 37.718183552586531 ], [ -122.362498365618194, 37.718172405349378 ], [ -122.362508477828271, 37.718161944830108 ], [ -122.358583730913253, 37.71588288357988 ], [ -122.358723442672868, 37.715731315969869 ], [ -122.362644996220368, 37.718021067053435 ], [ -122.362786346352422, 37.717866035066777 ], [ -122.363866062384275, 37.716726843144961 ], [ -122.359920053051226, 37.714425844522381 ], [ -122.3600361482696, 37.714297997140655 ], [ -122.36397478584415, 37.716580910244524 ], [ -122.364251293111423, 37.716300126312575 ], [ -122.364263175435909, 37.716291354435697 ], [ -122.364276820788504, 37.716283927560291 ], [ -122.364290517794743, 37.716278560323119 ], [ -122.364305977843998, 37.71627453863573 ], [ -122.364321497804667, 37.716272919544586 ], [ -122.364340525947497, 37.716273305108999 ], [ -122.364356149216732, 37.716275805018348 ], [ -122.364370087250265, 37.716280047868423 ], [ -122.364395976814166, 37.716278264915964 ], [ -122.365216452571488, 37.715351584174513 ], [ -122.365237670931208, 37.715301460414111 ], [ -122.365239473467412, 37.715235505999942 ], [ -122.365193066829221, 37.715177525619964 ], [ -122.364773705990487, 37.714932479640773 ], [ -122.364736623334764, 37.714970493365534 ], [ -122.364580089435904, 37.714864468359181 ], [ -122.364552651047106, 37.714804470653064 ], [ -122.364268407924328, 37.714638661242532 ], [ -122.364188831965635, 37.714636830395087 ], [ -122.364013771393857, 37.714550326239625 ], [ -122.364049082949748, 37.714510623951149 ], [ -122.363306902062419, 37.714077710648965 ], [ -122.363268090521188, 37.714115751260778 ], [ -122.363113289075798, 37.714009696947421 ], [ -122.363085852186202, 37.713949698891518 ], [ -122.362801615602848, 37.713783885919405 ], [ -122.362722040595187, 37.71378205408088 ], [ -122.362546983742845, 37.713695547734027 ], [ -122.362582295682941, 37.71365584615441 ], [ -122.361848935833351, 37.713229308027309 ], [ -122.361811844287786, 37.713266977882924 ], [ -122.361655318485532, 37.713160948681605 ], [ -122.361629610933349, 37.71310092296946 ], [ -122.361345380842891, 37.712935106461451 ], [ -122.361265806779613, 37.712933273639017 ], [ -122.361094407643563, 37.712854605021889 ], [ -122.361124337741387, 37.712807091000208 ], [ -122.361139435636801, 37.712788654437141 ], [ -122.361171360606775, 37.712751753157583 ], [ -122.36134313774096, 37.71284552396822 ], [ -122.361365062017143, 37.71289256197857 ], [ -122.361673856292811, 37.713072754562113 ], [ -122.361737876782101, 37.71307483295093 ], [ -122.361918064233436, 37.713159199186485 ], [ -122.361884368625439, 37.713194411838948 ], [ -122.362612508613324, 37.713619658283974 ], [ -122.362644433215863, 37.713582756884641 ], [ -122.362801089987357, 37.713693933186988 ], [ -122.362823024023626, 37.713741313598298 ], [ -122.363131825365173, 37.713921502610397 ], [ -122.363194118414285, 37.713923607543634 ], [ -122.3633743099209, 37.714007971534919 ], [ -122.36334061461109, 37.714043184324609 ], [ -122.364079295520412, 37.714474435984016 ], [ -122.364111219761938, 37.714437534192207 ], [ -122.364267880521169, 37.714548708254327 ], [ -122.36428981575132, 37.714596088660656 ], [ -122.364598624198337, 37.714776273803345 ], [ -122.3646609180052, 37.714778377960677 ], [ -122.364841113248019, 37.714862739696905 ], [ -122.364807418245547, 37.714897952901332 ], [ -122.365388188712686, 37.715236584611482 ], [ -122.364417662979704, 37.716315691686994 ], [ -122.364424868749381, 37.716327252003005 ], [ -122.364428635197243, 37.716339553209288 ], [ -122.36443069025762, 37.716352568505734 ], [ -122.364429280864911, 37.716365295273981 ], [ -122.364446761552514, 37.716372915954452 ], [ -122.364465978782491, 37.716380852784248 ], [ -122.364483468425433, 37.716388816406734 ], [ -122.364500965996967, 37.716397123539672 ], [ -122.364516744604018, 37.716405801242992 ], [ -122.364534251138338, 37.716414451592989 ], [ -122.364551766293047, 37.716423445442487 ], [ -122.364583357274199, 37.716442173479727 ], [ -122.364621964676815, 37.71646491108153 ], [ -122.364637708534104, 37.71647221613857 ], [ -122.364653409330941, 37.716477804506788 ], [ -122.364670803974548, 37.716481993153423 ], [ -122.36468815555574, 37.716484465110796 ], [ -122.364705455483815, 37.716484877975191 ], [ -122.364722720971017, 37.716483917927022 ], [ -122.364739943405979, 37.716481241738855 ], [ -122.364757122787552, 37.716476849410668 ], [ -122.364774250504041, 37.716470397714986 ], [ -122.364791343775195, 37.716462573106803 ], [ -122.364806691205743, 37.716454089407321 ], [ -122.364822022102643, 37.716444919239876 ], [ -122.364837335428504, 37.716435062620917 ], [ -122.364850911876999, 37.716424890408376 ], [ -122.364864471086165, 37.716414031190091 ], [ -122.364878013083185, 37.716402486064425 ], [ -122.364901605717435, 37.716378076804247 ], [ -122.364911648449322, 37.716364869981213 ], [ -122.364916549084953, 37.716353461387165 ], [ -122.364917950128515, 37.716340391380108 ], [ -122.364921087704161, 37.716327637243623 ], [ -122.364924234581224, 37.716315226323587 ], [ -122.364933983827555, 37.716290349774575 ], [ -122.36494722438934, 37.71626679140374 ], [ -122.364953853285272, 37.716255355719817 ], [ -122.364962219394371, 37.716244235619989 ], [ -122.364972322024684, 37.716233431114844 ], [ -122.364982433270342, 37.716222970110884 ], [ -122.364992553124679, 37.716212852333562 ], [ -122.365002690199319, 37.716203421010327 ], [ -122.365014563801225, 37.716194305555454 ], [ -122.365026455321683, 37.716185876818017 ], [ -122.36504008335524, 37.716177763399081 ], [ -122.365052000352904, 37.716170364072397 ], [ -122.365065654230065, 37.716163280882121 ], [ -122.365094741897337, 37.716151145947677 ], [ -122.365110106446139, 37.716143348388663 ], [ -122.365113201618499, 37.716128878372835 ], [ -122.365116304696627, 37.716114751046177 ], [ -122.365122873261583, 37.716100912485444 ], [ -122.365129459055197, 37.716087760653849 ], [ -122.365139510312659, 37.716074896763658 ], [ -122.365149578106625, 37.716062719613106 ], [ -122.365161392080509, 37.716051201814444 ], [ -122.365176644641494, 37.716038942289195 ], [ -122.365188518887109, 37.71602982680561 ], [ -122.365202138268813, 37.716021370414808 ], [ -122.365217494173905, 37.716013229888688 ], [ -122.365231148006444, 37.716006146679064 ], [ -122.365246539040172, 37.715999378773581 ], [ -122.365263666604179, 37.715992927006241 ], [ -122.365279091398932, 37.715987532291955 ], [ -122.365294534108472, 37.71598282401969 ], [ -122.365311721955024, 37.715978774837772 ], [ -122.365328927025729, 37.715975412108293 ], [ -122.365346149321098, 37.715972735831272 ], [ -122.365363388841544, 37.715970746006697 ], [ -122.365380645587578, 37.715969442634552 ], [ -122.365410025637061, 37.715968977361726 ], [ -122.365427334408309, 37.715969733342163 ], [ -122.365444651102152, 37.715970832558476 ], [ -122.365461994328726, 37.715972961443697 ], [ -122.365479354092372, 37.715975776792263 ], [ -122.365495012131262, 37.715979649180539 ], [ -122.365512406698826, 37.715983837428631 ], [ -122.365528089891654, 37.715988739505875 ], [ -122.365543791005862, 37.715994328024976 ], [ -122.36555950900491, 37.716000603002399 ], [ -122.36557523562081, 37.716007221205196 ], [ -122.365589259822642, 37.716014896460429 ], [ -122.365603292648544, 37.71602291521603 ], [ -122.365617342692417, 37.7160316198757 ], [ -122.365631409975279, 37.716041011263187 ], [ -122.365643757615857, 37.716050773249407 ], [ -122.365654385267973, 37.716060905840358 ], [ -122.365666759104258, 37.716071697501043 ], [ -122.365675684345334, 37.716082887145667 ], [ -122.365686355771075, 37.716094735860018 ], [ -122.36572383810848, 37.716141526396989 ], [ -122.365761311532381, 37.716187973699725 ], [ -122.365800496728525, 37.716233707149122 ], [ -122.365839664050739, 37.716278754141264 ], [ -122.365880551762743, 37.716323430505255 ], [ -122.365923158830057, 37.716367736255741 ], [ -122.365965740100123, 37.716411012308278 ], [ -122.366010040727502, 37.716453917744857 ], [ -122.366059638577141, 37.716501202978222 ], [ -122.366081205013529, 37.716533824258086 ], [ -122.366090251696093, 37.716549819043124 ], [ -122.366102754235541, 37.716565759079053 ], [ -122.366113529199154, 37.716581726482502 ], [ -122.366126023477833, 37.716597323283004 ], [ -122.366136789488536, 37.716612947462366 ], [ -122.366161760837542, 37.716643454602135 ], [ -122.366175966176996, 37.71665833756186 ], [ -122.366188442901887, 37.716673247906257 ], [ -122.366216836381071, 37.716702327637208 ], [ -122.36623102450136, 37.716716523860804 ], [ -122.366245204016678, 37.716730377129863 ], [ -122.366261112159151, 37.716744203009718 ], [ -122.366275291685326, 37.716758056275097 ], [ -122.366322963076229, 37.716797474553488 ], [ -122.366340573678841, 37.716810243632011 ], [ -122.36635644703216, 37.716822696320264 ], [ -122.366374040064969, 37.716834778944495 ], [ -122.366389904811129, 37.716846888400809 ], [ -122.366425073654625, 37.716870366632683 ], [ -122.366444377745808, 37.716881735132603 ], [ -122.36646194494493, 37.716892787786946 ], [ -122.366493510758119, 37.716910485903114 ], [ -122.366623549631328, 37.71699392291945 ], [ -122.366755273981596, 37.717075616265298 ], [ -122.367009737276547, 37.717225410385531 ], [ -122.367367636576219, 37.717431936135505 ], [ -122.367722202011066, 37.717643320779338 ], [ -122.367734498100774, 37.717651023186086 ], [ -122.367746786266892, 37.717658382627967 ], [ -122.36776079374863, 37.717665370893641 ], [ -122.367773072942654, 37.717672386836448 ], [ -122.367787080436401, 37.717679375373592 ], [ -122.367799351010589, 37.717686048086428 ], [ -122.367813349884699, 37.717692693393225 ], [ -122.367827331518725, 37.717698652518493 ], [ -122.367841321766008, 37.717704954320148 ], [ -122.367855303397675, 37.717710913167537 ], [ -122.367883249424608, 37.717722144677452 ], [ -122.367898942111211, 37.717727389659409 ], [ -122.367916388621566, 37.717733636645491 ], [ -122.367933775456365, 37.71773748130223 ], [ -122.367951144357875, 37.717740639787564 ], [ -122.367968522563814, 37.717744140937377 ], [ -122.368003243800771, 37.717749770880694 ], [ -122.368020595449934, 37.717752242626815 ], [ -122.368037939165831, 37.717754371132209 ], [ -122.368062204013668, 37.717756733239703 ], [ -122.368254623550371, 37.717776687237738 ], [ -122.368448797013713, 37.717797642645102 ], [ -122.368467860079036, 37.717799400451433 ], [ -122.368488660073098, 37.717801474067137 ], [ -122.368509442812467, 37.717802861224882 ], [ -122.368528488624563, 37.717803932567065 ], [ -122.368549262744196, 37.717804976765201 ], [ -122.368570028222393, 37.717805677183392 ], [ -122.368590785079604, 37.717806034645328 ], [ -122.368609804662938, 37.717806076298423 ], [ -122.368630552892, 37.717806090526203 ], [ -122.368672014835468, 37.717804746062299 ], [ -122.368691009223568, 37.717803758009893 ], [ -122.36871172294353, 37.717802399589416 ], [ -122.368753133102373, 37.717798995459539 ], [ -122.368773820932461, 37.717796607346749 ], [ -122.368818653167253, 37.717791775447523 ], [ -122.368861774362671, 37.71778765768024 ], [ -122.368906640766554, 37.717784198936094 ], [ -122.368949796818114, 37.717781454039603 ], [ -122.368994689457168, 37.717779025212373 ], [ -122.369037870695294, 37.717777309701347 ], [ -122.369082798189922, 37.717776253468948 ], [ -122.369126014299994, 37.717775911103146 ], [ -122.369170967339741, 37.717775884523917 ], [ -122.369202706794994, 37.71780044645373 ], [ -122.369235000726789, 37.717778302338516 ], [ -122.369269687549945, 37.717782559000163 ], [ -122.36928530265682, 37.717784714478952 ], [ -122.369302663340889, 37.717787529532423 ], [ -122.369353034337394, 37.717796687460655 ], [ -122.369368683984078, 37.717800216111328 ], [ -122.369384341918476, 37.717804087992491 ], [ -122.369401728852594, 37.717807932707132 ], [ -122.369417395761204, 37.717812147530914 ], [ -122.36943307096503, 37.717816705859768 ], [ -122.369450475169117, 37.717821237021361 ], [ -122.36948184319013, 37.717831039842252 ], [ -122.369497544990651, 37.717836627832767 ], [ -122.369513237815312, 37.717841872599593 ], [ -122.369527219606752, 37.717847831248413 ], [ -122.36954292970124, 37.717853762465438 ], [ -122.369558648085317, 37.71786003691291 ], [ -122.36957263852355, 37.71786633905797 ], [ -122.369588366230118, 37.717872956442982 ], [ -122.369602365299343, 37.717879601536971 ], [ -122.369618101305662, 37.71788656242515 ], [ -122.369646133989733, 37.717901225512634 ], [ -122.369674183958367, 37.717916575322121 ], [ -122.369702251198532, 37.717932611304448 ], [ -122.369714565153885, 37.717940999952816 ], [ -122.369728616041627, 37.717949704119498 ], [ -122.369740938290732, 37.717958435997602 ], [ -122.369754989198668, 37.717967140710293 ], [ -122.369767320771899, 37.717976215527116 ], [ -122.369804340049498, 37.718004470496631 ], [ -122.369814960605339, 37.718014259471069 ], [ -122.369827317757029, 37.718024364242766 ], [ -122.369837946607149, 37.718034496447622 ], [ -122.369850303765318, 37.718044601216874 ], [ -122.369882215899651, 37.718076027508822 ], [ -122.369892862378947, 37.71808684615722 ], [ -122.36990178884372, 37.718098035472593 ], [ -122.369912443617807, 37.718109197351708 ], [ -122.369930296561293, 37.718131575978724 ], [ -122.36994816678471, 37.718154641056984 ], [ -122.369957110535211, 37.718166516822102 ], [ -122.369967859963296, 37.718181454198835 ], [ -122.36997176593016, 37.718199247125732 ], [ -122.369979594447685, 37.718235519443965 ], [ -122.370002130211063, 37.718306581422858 ], [ -122.370009483835318, 37.718323976252783 ], [ -122.370015117785968, 37.718341741746322 ], [ -122.370022471423681, 37.718359136849926 ], [ -122.370031544728775, 37.71837616073956 ], [ -122.370038898366815, 37.718393555567452 ], [ -122.370047971693708, 37.718410580004878 ], [ -122.370057036030119, 37.718427260670879 ], [ -122.370066109704041, 37.718444284826738 ], [ -122.370075174401222, 37.718460965760372 ], [ -122.370085967071944, 37.718477619260632 ], [ -122.370107466765518, 37.718507493977114 ], [ -122.370126944899795, 37.718525726405055 ], [ -122.370169322597192, 37.718560763475431 ], [ -122.370190485553579, 37.718577252323918 ], [ -122.370213359219747, 37.718593027280086 ], [ -122.370236224957864, 37.718608459268403 ], [ -122.370260801392277, 37.718623176813935 ], [ -122.370285360570904, 37.718637208174954 ], [ -122.370309902493091, 37.718650553351452 ], [ -122.370336155456755, 37.71866318407794 ], [ -122.370362391163113, 37.718675128619182 ], [ -122.370390329287673, 37.718686016031121 ], [ -122.370416530117836, 37.718696587657909 ], [ -122.370444433710631, 37.71870610214912 ], [ -122.370472320036399, 37.718714930179793 ], [ -122.370501917760691, 37.718723044300269 ], [ -122.370529760919823, 37.718730156457141 ], [ -122.370559323753966, 37.718736897386265 ], [ -122.370588852049892, 37.718742265674756 ], [ -122.370618371366675, 37.718747290734377 ], [ -122.370647865465088, 37.718751286094815 ], [ -122.370679070273212, 37.718754567827233 ], [ -122.370708520488662, 37.718756846774177 ], [ -122.370739682448956, 37.718758412075765 ], [ -122.37076908982813, 37.718758975142002 ], [ -122.370800208258856, 37.718758824572994 ], [ -122.370829572444975, 37.718757671489442 ], [ -122.370860647321152, 37.718755804226205 ], [ -122.370891705615989, 37.718753250764479 ], [ -122.37090882433975, 37.718746454957831 ], [ -122.370925978308406, 37.718741032045394 ], [ -122.370944886167507, 37.718736611366033 ], [ -122.370963836875418, 37.718733906823715 ], [ -122.370982814191592, 37.718732231948131 ], [ -122.371005290654622, 37.718732218311914 ], [ -122.371024337093374, 37.718733289519548 ], [ -122.37104342637177, 37.71873607631499 ], [ -122.371062541577928, 37.718739893062697 ], [ -122.371079963024641, 37.718745110167617 ], [ -122.371097419033674, 37.718751700177599 ], [ -122.371113164004626, 37.718759004092 ], [ -122.371128943539134, 37.718767680911924 ], [ -122.371144757299319, 37.718777730917395 ], [ -122.371169489840725, 37.718798626351749 ], [ -122.371180101358092, 37.718808071985954 ], [ -122.371190696288693, 37.71881683115442 ], [ -122.371201281889995, 37.71882524710599 ], [ -122.371213579227003, 37.718832949137195 ], [ -122.37122759590342, 37.718840280490852 ], [ -122.371239875618699, 37.718847296070983 ], [ -122.371253857737614, 37.718853254513917 ], [ -122.371267831218006, 37.718858869728294 ], [ -122.371281796059677, 37.718864141714128 ], [ -122.371297471952232, 37.718868700063226 ], [ -122.37131140222705, 37.718872598863427 ], [ -122.371327043206463, 37.718875784031958 ], [ -122.371342667596267, 37.718878282733641 ], [ -122.371358283000305, 37.718880438211883 ], [ -122.371373872488391, 37.718881564281979 ], [ -122.371383664521929, 37.718882055893282 ], [ -122.371389452976118, 37.718882346579448 ], [ -122.371405016879592, 37.71888244268473 ], [ -122.371420554513023, 37.718881509112791 ], [ -122.371437794199721, 37.718879518405728 ], [ -122.371451465242302, 37.718873120657591 ], [ -122.371465170849277, 37.718868095815367 ], [ -122.371480639690603, 37.718864416415016 ], [ -122.371497879724302, 37.718862425968247 ], [ -122.371520347584678, 37.71886206900686 ], [ -122.371535954687204, 37.718863880954615 ], [ -122.371551596026961, 37.718867066362435 ], [ -122.371567280564975, 37.718871967353337 ], [ -122.371581271363922, 37.718878269258852 ], [ -122.371591935137346, 37.718889773936951 ], [ -122.371604326899728, 37.718901251433785 ], [ -122.371614991032587, 37.718912756378863 ], [ -122.371627374512514, 37.718923890915455 ], [ -122.37163802965712, 37.718935052362468 ], [ -122.371650413137317, 37.718946186622013 ], [ -122.371662779335963, 37.718956634426526 ], [ -122.371676882506705, 37.718967397995137 ], [ -122.371689248373897, 37.718977846076911 ], [ -122.371706869050627, 37.71899095730106 ], [ -122.371720799369825, 37.718994856052753 ], [ -122.371736440738147, 37.718998041161377 ], [ -122.371767706192628, 37.7190037249187 ], [ -122.371798919792468, 37.719007349306651 ], [ -122.371814508961251, 37.719008475049307 ], [ -122.371830090178449, 37.719009257552102 ], [ -122.371845662406855, 37.719009696831492 ], [ -122.371878500616674, 37.719009175012339 ], [ -122.371894038270653, 37.719008241377971 ], [ -122.371925078656488, 37.719005001201246 ], [ -122.371940582079091, 37.719002694647912 ], [ -122.37195607651114, 37.71900004487118 ], [ -122.371971553653992, 37.71899670863877 ], [ -122.371985302469042, 37.7189933998703 ], [ -122.372011244653535, 37.718993674331507 ], [ -122.372032105590733, 37.718998149908998 ], [ -122.372051212270634, 37.719001623269286 ], [ -122.372072029989994, 37.71900438270557 ], [ -122.372092830421707, 37.719006455684564 ], [ -122.372113604920486, 37.719007498979387 ], [ -122.372134362130282, 37.719007855816933 ], [ -122.372155093405652, 37.71900718297038 ], [ -122.372175798752636, 37.719005480714287 ], [ -122.372196486794678, 37.719003091451768 ], [ -122.372215428886747, 37.719000043756004 ], [ -122.372242952424102, 37.718994455566381 ], [ -122.372228495782323, 37.719038292867332 ], [ -122.372270562418365, 37.718992299913033 ], [ -122.372296686505393, 37.718999782068892 ], [ -122.372315810496232, 37.719003942114455 ], [ -122.372334917183963, 37.719007415154145 ], [ -122.372354014888927, 37.719010545244004 ], [ -122.372373104647863, 37.719013332367552 ], [ -122.372411231581538, 37.719016846431828 ], [ -122.372432006094613, 37.719017889945107 ], [ -122.372451034636384, 37.719018274204856 ], [ -122.372470046576993, 37.719017971996969 ], [ -122.372489049518421, 37.7190173262901 ], [ -122.372508043819963, 37.719016337627984 ], [ -122.372528749154483, 37.719014635035279 ], [ -122.372547708522333, 37.719012273465104 ], [ -122.372566659933526, 37.719009568654101 ], [ -122.372585593703946, 37.719006177391961 ], [ -122.372604518825696, 37.719002442899999 ], [ -122.372623426305779, 37.718998021956914 ], [ -122.372640597508592, 37.718993285523204 ], [ -122.372659479381312, 37.718987834613905 ], [ -122.372676624285006, 37.718982068225515 ], [ -122.372693803775505, 37.718977674741716 ], [ -122.372709280880628, 37.718974338412373 ], [ -122.372726511906819, 37.718972004289988 ], [ -122.372754147821666, 37.718970878203621 ], [ -122.372769737688571, 37.718972003808915 ], [ -122.372787072838463, 37.718973788668045 ], [ -122.372804451214719, 37.718977289109326 ], [ -122.372823557579665, 37.718980762349858 ], [ -122.37284089307164, 37.718982546920991 ], [ -122.372859921952113, 37.718982931109217 ], [ -122.372877179265899, 37.718981626639597 ], [ -122.372896130311645, 37.71897892178108 ], [ -122.372913318098071, 37.718974871772126 ], [ -122.372930471966299, 37.718969448293443 ], [ -122.372945853925259, 37.718962336438636 ], [ -122.372961200941234, 37.718953851680254 ], [ -122.37297652269902, 37.718944337228557 ], [ -122.372988361166023, 37.718933848067003 ], [ -122.373153843085163, 37.718881530021171 ], [ -122.373238802575472, 37.718854668953604 ], [ -122.373255964357725, 37.718849588939769 ], [ -122.37327313547901, 37.718844852139163 ], [ -122.373307494324067, 37.718836064994925 ], [ -122.373341870461658, 37.718827964294142 ], [ -122.373376281193742, 37.718821236490271 ], [ -122.37339522352984, 37.718818188324654 ], [ -122.373412437542669, 37.718815167642909 ], [ -122.37344690016738, 37.718810499178815 ], [ -122.37346585979968, 37.718808137455291 ], [ -122.373483108072492, 37.718806489950246 ], [ -122.373502085343517, 37.718804814394282 ], [ -122.373536599865218, 37.718802205264303 ], [ -122.373555594097851, 37.718801216433278 ], [ -122.373572868314369, 37.718800598320655 ], [ -122.373591880194738, 37.718800295931722 ], [ -122.373626463924381, 37.718800432589219 ], [ -122.373645492769242, 37.718800816925153 ], [ -122.373662792940365, 37.718801228479528 ], [ -122.373681839421252, 37.718802298708354 ], [ -122.373699157242569, 37.718803396705297 ], [ -122.373716483716436, 37.718804837926434 ], [ -122.373735538512435, 37.718806251653383 ], [ -122.373752881946515, 37.718808379328088 ], [ -122.373777165315929, 37.718811426700512 ], [ -122.373794552014559, 37.718815270502617 ], [ -122.373811948058801, 37.718819457517874 ], [ -122.373829360718858, 37.718824330994956 ], [ -122.373845054403262, 37.718829575178432 ], [ -122.373862484719027, 37.718835135098523 ], [ -122.373878195029278, 37.718841066016566 ], [ -122.373893923325326, 37.718847682825754 ], [ -122.373909659945298, 37.71885464341419 ], [ -122.373925413867383, 37.718862290179331 ], [ -122.373939439462958, 37.718869964162415 ], [ -122.373953482381623, 37.71887832514637 ], [ -122.373967533942846, 37.718887028806179 ], [ -122.373981594167574, 37.718896075965546 ], [ -122.373995663049072, 37.718905466349945 ], [ -122.374008012263729, 37.718915227454971 ], [ -122.374020370135099, 37.718925331785385 ], [ -122.374032736317702, 37.718935779346666 ], [ -122.374045111848744, 37.718946570122313 ], [ -122.374055767366514, 37.718957731625189 ], [ -122.374066431541209, 37.71896923635375 ], [ -122.374075367055113, 37.718980768858067 ], [ -122.374086048882475, 37.71899295975809 ], [ -122.374103954872169, 37.719017397114996 ], [ -122.374125681340118, 37.719056194989058 ], [ -122.374131204173437, 37.719069498331429 ], [ -122.374142266462783, 37.719096791479657 ], [ -122.374149872102961, 37.719124139622245 ], [ -122.374153683579266, 37.719138156919946 ], [ -122.3741578498839, 37.719166246785271 ], [ -122.374159941681143, 37.71918063453262 ], [ -122.374161014797295, 37.71922319463841 ], [ -122.374159641286965, 37.719237294429277 ], [ -122.374154923616729, 37.719255911162307 ], [ -122.374151882005847, 37.719272441042889 ], [ -122.374149256482738, 37.719305445791825 ], [ -122.374150087278892, 37.719338395550594 ], [ -122.374152239665406, 37.719355186158822 ], [ -122.374156103078988, 37.719371262815855 ], [ -122.374159975148416, 37.71938768269937 ], [ -122.374165558246304, 37.71940338863147 ], [ -122.374172878343202, 37.719419410566459 ], [ -122.374175234931528, 37.719424358448258 ], [ -122.374180181120835, 37.719434745498482 ], [ -122.374187466599793, 37.719449394251313 ], [ -122.374214300358844, 37.719485020571611 ], [ -122.374235611602799, 37.719507343543832 ], [ -122.374246258574956, 37.719518161801837 ], [ -122.374256896895602, 37.719528636832223 ], [ -122.374269254564041, 37.719538741415626 ], [ -122.374281604258542, 37.719548502210912 ], [ -122.374293944962744, 37.719557920058328 ], [ -122.374308005354067, 37.719566967177798 ], [ -122.374322057100812, 37.719575671343499 ], [ -122.374337854486313, 37.719585033911152 ], [ -122.3744195305989, 37.719669918743051 ], [ -122.374421587846186, 37.719682933852702 ], [ -122.374425381745823, 37.719696264692658 ], [ -122.374436392649173, 37.719721498445345 ], [ -122.374441889282195, 37.71973377237201 ], [ -122.374450833933636, 37.719745647518387 ], [ -122.374459769939222, 37.719757179712012 ], [ -122.374468688290634, 37.719768025457228 ], [ -122.37447931836833, 37.719778157235005 ], [ -122.374491667798353, 37.71978791856256 ], [ -122.374503990904699, 37.719796649665334 ], [ -122.374518034054987, 37.71980501030616 ], [ -122.374532059896538, 37.719812684492133 ], [ -122.374546059779846, 37.719819329271196 ], [ -122.374561771037051, 37.719825259811579 ], [ -122.374577455975697, 37.719830160400981 ], [ -122.374594868929591, 37.719835034038724 ], [ -122.374610562881386, 37.719840278119385 ], [ -122.374626299764742, 37.719847238061817 ], [ -122.374642062627359, 37.719855227956486 ], [ -122.37465612311729, 37.71986427503397 ], [ -122.374668489891121, 37.719874722521432 ], [ -122.374679145289875, 37.719885883971763 ], [ -122.374688099006775, 37.719898102595501 ], [ -122.374697069701313, 37.719911007951758 ], [ -122.374702592354737, 37.719924311271022 ], [ -122.374739822583123, 37.719960801838624 ], [ -122.37475222401271, 37.719972622223445 ], [ -122.374766345137616, 37.719984071873611 ], [ -122.374778737572512, 37.719995549034522 ], [ -122.374806962529064, 37.720017761873571 ], [ -122.374835170187708, 37.720039288252636 ], [ -122.374849265365711, 37.720049708213061 ], [ -122.374865089243499, 37.72006010065806 ], [ -122.37487917577208, 37.720070177388315 ], [ -122.374910788226885, 37.720089589372847 ], [ -122.374942383382447, 37.720108314895747 ], [ -122.374958163650305, 37.720116991200911 ], [ -122.37497395257985, 37.720126010730475 ], [ -122.374991452548869, 37.720134316294761 ], [ -122.375007224169792, 37.720142649366764 ], [ -122.375024715142516, 37.720150611705058 ], [ -122.375040478457933, 37.72015860154054 ], [ -122.375057961125222, 37.720166220641822 ], [ -122.375092909153366, 37.7201807723835 ], [ -122.375127822560231, 37.72019395120865 ], [ -122.375147007628541, 37.720200513379581 ], [ -122.375173261961393, 37.720213143025013 ], [ -122.375207993556202, 37.720219114069216 ], [ -122.375227160967086, 37.720224989504935 ], [ -122.375248074399238, 37.720231523871263 ], [ -122.375269005153854, 37.720238744686874 ], [ -122.375280076764881, 37.720243315652858 ], [ -122.375288225222903, 37.720246679460772 ], [ -122.375307453609949, 37.720254957463524 ], [ -122.375326699320624, 37.720263921916157 ], [ -122.375345953350021, 37.720273229597673 ], [ -122.375365216735446, 37.720282880491496 ], [ -122.3753827687447, 37.72029324535653 ], [ -122.37540033807889, 37.720304296671941 ], [ -122.375416187376842, 37.720315718733033 ], [ -122.375433774387304, 37.720327456490914 ], [ -122.375449641361485, 37.720339564994973 ], [ -122.375463797304775, 37.720352387466882 ], [ -122.375479689931566, 37.720365525926404 ], [ -122.375493863198955, 37.720379034573064 ], [ -122.375506317126977, 37.720392914231198 ], [ -122.375520507732517, 37.720407109602149 ], [ -122.375532978645822, 37.720421675715869 ], [ -122.375543729520388, 37.720436612578425 ], [ -122.375556287403313, 37.720454611223573 ], [ -122.375591629223138, 37.720553280549268 ], [ -122.375625259756632, 37.720652664115185 ], [ -122.375655442655059, 37.720752445930557 ], [ -122.375683906273295, 37.720852598477748 ], [ -122.375710658575045, 37.720953464995517 ], [ -122.375751172610691, 37.721120038334149 ], [ -122.375761845674063, 37.721131886137194 ], [ -122.375772510777281, 37.721143390976373 ], [ -122.375784886582679, 37.721154181570967 ], [ -122.375797236053231, 37.721163942215831 ], [ -122.375811288276211, 37.721172646201708 ], [ -122.375827043223865, 37.721180292429615 ], [ -122.375842875795513, 37.721191027973333 ], [ -122.375853514234834, 37.721201502862243 ], [ -122.37586245097873, 37.721213034941265 ], [ -122.375869667665128, 37.721224937774515 ], [ -122.375880696264161, 37.72125085784436 ], [ -122.375882762127716, 37.72126421615711 ], [ -122.375882828626871, 37.721266851037683 ], [ -122.375883125946686, 37.721278631666706 ], [ -122.37587827898912, 37.721292100060857 ], [ -122.375871711967719, 37.721305939209678 ], [ -122.375866881992167, 37.721320094336214 ], [ -122.37586031495924, 37.721333933209785 ], [ -122.375855484972746, 37.721348088061191 ], [ -122.375852374709183, 37.721361872437875 ], [ -122.375847544719377, 37.721376027288905 ], [ -122.375842723382974, 37.721390525091536 ], [ -122.375836520156881, 37.721418779747594 ], [ -122.375830334257259, 37.721447721130588 ], [ -122.375828969677642, 37.721462163887708 ], [ -122.375825876376013, 37.721476634447164 ], [ -122.375821782997832, 37.721519963536267 ], [ -122.375822146806243, 37.721534379045465 ], [ -122.375820790553732, 37.721549165583347 ], [ -122.375822254450057, 37.721607170846063 ], [ -122.375824355645946, 37.721621901779116 ], [ -122.375824719455352, 37.721636317288059 ], [ -122.375830995701023, 37.721679481253183 ], [ -122.375834816633215, 37.721693841711655 ], [ -122.375836908840029, 37.721708229972535 ], [ -122.375840729422237, 37.721722590161676 ], [ -122.375846503274232, 37.721745846994416 ], [ -122.375861785654777, 37.721803288846772 ], [ -122.375867698808605, 37.72183203728941 ], [ -122.375873620283713, 37.721861128963518 ], [ -122.375886199210129, 37.721948486558446 ], [ -122.375891128963588, 37.722006779991005 ], [ -122.37589187393715, 37.722036297459432 ], [ -122.375894433932729, 37.722069219668136 ], [ -122.375899939635701, 37.722081836473912 ], [ -122.375903725267278, 37.722094824309316 ], [ -122.37590923997503, 37.722107784060896 ], [ -122.375913025948279, 37.722120771615856 ], [ -122.375915092187512, 37.722134129920661 ], [ -122.375918886480093, 37.722147460707269 ], [ -122.375920953066782, 37.722160819006419 ], [ -122.375921290909631, 37.722174204834722 ], [ -122.375923357151819, 37.722187563139258 ], [ -122.375923694995222, 37.722200948967497 ], [ -122.375922304438902, 37.722214362319441 ], [ -122.375922642282234, 37.722227748147617 ], [ -122.375921251379538, 37.722241161504989 ], [ -122.375918210730603, 37.722257691412231 ], [ -122.375915160726379, 37.722273878103998 ], [ -122.375910383010535, 37.722290092308178 ], [ -122.375909052749606, 37.722305908524369 ], [ -122.3759060120953, 37.722322438431114 ], [ -122.375902962078825, 37.722338624847808 ], [ -122.375900319899742, 37.722370943166766 ], [ -122.375899007299083, 37.722387445555256 ], [ -122.375899414439516, 37.72240357719398 ], [ -122.375898101492368, 37.722420079587863 ], [ -122.375899331576733, 37.722468817730004 ], [ -122.3759014761321, 37.722485265065941 ], [ -122.375901883280534, 37.722501396978991 ], [ -122.375904027491728, 37.722517844320301 ], [ -122.375908298246003, 37.722550052555519 ], [ -122.375912171213727, 37.722566472367539 ], [ -122.375914306759483, 37.722582576207579 ], [ -122.375918170721874, 37.722598652798467 ], [ -122.375922026023247, 37.722614386162874 ], [ -122.375925889988949, 37.722630462753436 ], [ -122.375931482020547, 37.722646511825495 ], [ -122.3759353376729, 37.72266224518382 ], [ -122.375946504767398, 37.722693656868394 ], [ -122.37595381690933, 37.722709335457893 ], [ -122.375959392311572, 37.72272469779017 ], [ -122.375988606240981, 37.722786038138061 ], [ -122.37599590106781, 37.72280102999747 ], [ -122.376004915302616, 37.722815651110672 ], [ -122.376013938895468, 37.722830615438454 ], [ -122.376030187680101, 37.722857825817606 ], [ -122.376039254253797, 37.722874506280462 ], [ -122.376050040583777, 37.722890815990674 ], [ -122.376059098502594, 37.722907153225577 ], [ -122.376080671185491, 37.722939772641169 ], [ -122.376093177287544, 37.722955711594906 ], [ -122.376103946313975, 37.722971334847365 ], [ -122.376128941223584, 37.723002526570319 ], [ -122.376153918812676, 37.723033031286057 ], [ -122.376166398954496, 37.723047940552881 ], [ -122.376180598856877, 37.723062479064545 ], [ -122.376194807428803, 37.723077360800765 ], [ -122.376223189933157, 37.723105751362802 ], [ -122.37623737252926, 37.723119603414908 ], [ -122.376253283551748, 37.72313342793673 ], [ -122.376285088285584, 37.723160390521265 ], [ -122.37630098199665, 37.723173528583985 ], [ -122.376332751760657, 37.723199118255842 ], [ -122.376350356927531, 37.723211542324023 ], [ -122.37636795274409, 37.723223623174263 ], [ -122.376420715615595, 37.723258836008263 ], [ -122.376438285466264, 37.723269887443685 ], [ -122.376457584424074, 37.723280910784744 ], [ -122.376475145958864, 37.723291618708295 ], [ -122.376513708562953, 37.723312293030197 ], [ -122.376564456486463, 37.723336207150773 ], [ -122.376580211970165, 37.72334385327531 ], [ -122.376595984457055, 37.723352186404895 ], [ -122.376611774265584, 37.723361205435687 ], [ -122.376625852990443, 37.723370938725466 ], [ -122.37663994038509, 37.723381015239809 ], [ -122.37665232535511, 37.723392148965601 ], [ -122.376666438756033, 37.723403255155475 ], [ -122.376677112293109, 37.723415102871243 ], [ -122.376698494734327, 37.723440171193744 ], [ -122.376707475210537, 37.723453419335712 ], [ -122.376716464010698, 37.72346701070871 ], [ -122.376720284514462, 37.723481371145333 ], [ -122.37671721799309, 37.723496871391177 ], [ -122.376707592383568, 37.723526554064598 ], [ -122.376699305900857, 37.723540764010522 ], [ -122.376689273305857, 37.723554315043828 ], [ -122.376679223374595, 37.723567179623807 ], [ -122.376667418664113, 37.723579042064337 ], [ -122.376655545318613, 37.723588158957362 ], [ -122.377080106636114, 37.724240997344516 ], [ -122.377095367932696, 37.724229079787627 ], [ -122.377110646561107, 37.724217848681064 ], [ -122.37712595084453, 37.724207647256506 ], [ -122.377143001604594, 37.724198105004248 ], [ -122.377161806451838, 37.724189564342446 ], [ -122.377180636962038, 37.724182053636206 ], [ -122.377199502834941, 37.724175915820602 ], [ -122.377218385696736, 37.724170464459789 ], [ -122.377239031676282, 37.724166358457559 ], [ -122.377259703667661, 37.724163282404746 ], [ -122.377280401650879, 37.724161235477631 ], [ -122.377302862761439, 37.724160534182147 ], [ -122.377323664781798, 37.724162606235865 ], [ -122.377346194902287, 37.724164650746829 ], [ -122.377366988255417, 37.724166379566732 ], [ -122.377389501730306, 37.724167737606116 ], [ -122.377412006197545, 37.724168752695128 ], [ -122.377432782206725, 37.724169794776564 ], [ -122.377477756104398, 37.724170451485548 ], [ -122.377520967568458, 37.724169762808955 ], [ -122.377543428679331, 37.724169061467578 ], [ -122.377564152674637, 37.724168044443623 ], [ -122.377586596450428, 37.724166656916339 ], [ -122.377609031195107, 37.724164925615 ], [ -122.37762973784686, 37.724163222127238 ], [ -122.377674573698229, 37.724158387131986 ], [ -122.377695245662423, 37.724155310728172 ], [ -122.377717646069129, 37.724152206770718 ], [ -122.377738309358563, 37.724148787133252 ], [ -122.377760692073096, 37.724144996720867 ], [ -122.377801983955891, 37.724136784522699 ], [ -122.377824341339959, 37.724131964408841 ], [ -122.377861378513643, 37.724092230490051 ], [ -122.37790330341987, 37.724109074037422 ], [ -122.377953072363383, 37.724094202731443 ], [ -122.378002859323317, 37.724080017845608 ], [ -122.378052663257023, 37.724066519121855 ], [ -122.378102484525442, 37.724053707103792 ], [ -122.378152322784231, 37.724041581796904 ], [ -122.378202179051058, 37.724030142360959 ], [ -122.378253789425585, 37.724019705304357 ], [ -122.378303679673081, 37.724009639015357 ], [ -122.378375979604911, 37.723996811837594 ], [ -122.378413918203691, 37.723992773262658 ], [ -122.378431150905413, 37.723990438296809 ], [ -122.378450111707437, 37.723988076047718 ], [ -122.378467326705092, 37.723985054355325 ], [ -122.378486270155207, 37.72398200564826 ], [ -122.378503476136444, 37.723978641004621 ], [ -122.378522411247346, 37.723975248785557 ], [ -122.378573978156382, 37.723963095183649 ], [ -122.378592877874951, 37.723958330334597 ], [ -122.378610049829263, 37.723953592485465 ], [ -122.378644375709996, 37.723943431162333 ], [ -122.378661520946679, 37.723937663913084 ], [ -122.378678675540712, 37.723932239601808 ], [ -122.378695812103444, 37.723926129396091 ], [ -122.378711220902375, 37.723920046192298 ], [ -122.378728349122568, 37.723913592475675 ], [ -122.378745468663908, 37.723906795530496 ], [ -122.378760859771077, 37.723900026422747 ], [ -122.378776242185538, 37.723892913537782 ], [ -122.378793344365533, 37.723885430133492 ], [ -122.378808718111543, 37.723877974567344 ], [ -122.378824083164403, 37.723870175223993 ], [ -122.378854795921512, 37.723853890628249 ], [ -122.378885491312118, 37.723836919572342 ], [ -122.378899101889331, 37.723828118382741 ], [ -122.378914431877035, 37.723818946403391 ], [ -122.378928033770805, 37.723809801984345 ], [ -122.378943355765742, 37.723800286764138 ], [ -122.378956948975699, 37.723790799115662 ], [ -122.378970533505296, 37.723780968239559 ], [ -122.378997685199607, 37.72376062003049 ], [ -122.37900953226108, 37.723750473498114 ], [ -122.379023090751858, 37.723739613212317 ], [ -122.37903492981377, 37.723729123165697 ], [ -122.379050155637117, 37.723715832455909 ], [ -122.379063557916822, 37.723698793821882 ], [ -122.379075214748525, 37.72368109629867 ], [ -122.379086879906424, 37.723663742005776 ], [ -122.379098527703903, 37.723645701259585 ], [ -122.379110183827592, 37.723628003743777 ], [ -122.379120103528408, 37.723609990560298 ], [ -122.37913174263177, 37.723591606584641 ], [ -122.379141653299143, 37.723573250178703 ], [ -122.379151555283926, 37.723554550545835 ], [ -122.379159737512083, 37.723536221709345 ], [ -122.379169630463892, 37.723517178854308 ], [ -122.379177804358278, 37.72349850705946 ], [ -122.379185969211122, 37.723479491494309 ], [ -122.379192405639117, 37.723460503774781 ], [ -122.379200561813079, 37.723441145257027 ], [ -122.37921341729573, 37.723402483363635 ], [ -122.379218107928253, 37.723382836762347 ], [ -122.379224535661635, 37.723363505814724 ], [ -122.379233916574677, 37.723324212890873 ], [ -122.379236870433516, 37.723304250354701 ], [ -122.379241561053661, 37.72328460375217 ], [ -122.379250421598357, 37.723224716982607 ], [ -122.379252871777865, 37.723184847614071 ], [ -122.379253905947536, 37.723157361956225 ], [ -122.379253532794493, 37.723142603509906 ], [ -122.379254879369441, 37.723127473715763 ], [ -122.37925797172656, 37.723113003075639 ], [ -122.379262792162919, 37.723098505142787 ], [ -122.379267621960011, 37.723084350150188 ], [ -122.379275925257346, 37.723070826474732 ], [ -122.379284246592633, 37.723057988965195 ], [ -122.379294304689481, 37.723045467662629 ], [ -122.379306117221759, 37.723033947914878 ], [ -122.379319675538511, 37.723023087593674 ], [ -122.379333259872894, 37.723013256399945 ], [ -122.379348598663228, 37.723004427583476 ], [ -122.379363963140094, 37.722996628448691 ], [ -122.379381091436528, 37.722990174630688 ], [ -122.379398245074995, 37.722984750499421 ], [ -122.379417153855258, 37.722980328457844 ], [ -122.379434368594119, 37.722977307172805 ], [ -122.379455083545309, 37.722975946315401 ], [ -122.37947750088145, 37.722973528474888 ], [ -122.379499892178387, 37.722970080951924 ], [ -122.379523994530487, 37.722965919396081 ], [ -122.37954461400976, 37.722960783310853 ], [ -122.379566935863238, 37.722954589966704 ], [ -122.379589240353653, 37.72294771016621 ], [ -122.379609790385288, 37.722939828261154 ], [ -122.379630314379966, 37.722930916948918 ], [ -122.379649092581033, 37.722921346210235 ], [ -122.379669581838826, 37.72291106171236 ], [ -122.379686587876122, 37.72289980269619 ], [ -122.37970530565336, 37.722887829635219 ], [ -122.379722277304509, 37.722875197704134 ], [ -122.379737503175733, 37.722861906898203 ], [ -122.379752711680695, 37.722847929638164 ], [ -122.379767911153763, 37.722833609155607 ], [ -122.379779628445803, 37.722818314142543 ], [ -122.379793065118861, 37.722802648327246 ], [ -122.379812990079841, 37.722770054111137 ], [ -122.379822935192337, 37.722753070549636 ], [ -122.379814676042812, 37.722699980835564 ], [ -122.379867320110051, 37.722593727839261 ], [ -122.379934313833843, 37.722371188860151 ], [ -122.37993733657764, 37.72235397239195 ], [ -122.379942087368576, 37.72233672807188 ], [ -122.379948575592024, 37.722319799663914 ], [ -122.379956801239857, 37.722303186893228 ], [ -122.379965035219655, 37.722286917353493 ], [ -122.37997327788419, 37.722270991313792 ], [ -122.379983257611954, 37.722255380367073 ], [ -122.379994974077178, 37.722240085342186 ], [ -122.380006708591978, 37.72222547675721 ], [ -122.380020188517861, 37.72221152704482 ], [ -122.380035405172549, 37.7221978929784 ], [ -122.380048911140065, 37.722184973215455 ], [ -122.380065900274673, 37.722173027405468 ], [ -122.380082897401252, 37.72216142510468 ], [ -122.38009991257772, 37.722150509242532 ], [ -122.380116944421857, 37.722140279841042 ], [ -122.380135722713177, 37.722130709292628 ], [ -122.380156255096665, 37.722122140839112 ], [ -122.38018021822036, 37.722112487531497 ], [ -122.380200637722609, 37.722099457131471 ], [ -122.380221048535219, 37.722086083501829 ], [ -122.380241450657905, 37.722072366642536 ], [ -122.380260115357089, 37.722058334421121 ], [ -122.38028050009946, 37.72204393110291 ], [ -122.380299156785668, 37.722029555363576 ], [ -122.38031780442914, 37.722014836126135 ], [ -122.380336443389169, 37.721999773934229 ], [ -122.380353345265689, 37.721984396102023 ], [ -122.380371975527709, 37.721968990678377 ], [ -122.380388868360967, 37.721953269620464 ], [ -122.380405753195106, 37.721937205323023 ], [ -122.38043950478324, 37.721904390279654 ], [ -122.380454643491987, 37.721887667118459 ], [ -122.38046977317164, 37.721870601009314 ], [ -122.380498331188051, 37.721837525213786 ], [ -122.38051707392718, 37.721826581710999 ], [ -122.380535826036024, 37.721815981420185 ], [ -122.380554603852772, 37.721806411084629 ], [ -122.380573407703011, 37.721797869875026 ], [ -122.380593957312428, 37.721789987796839 ], [ -122.380614524292966, 37.721782792441729 ], [ -122.380636845696628, 37.721776598619094 ], [ -122.380657456091583, 37.721771119386688 ], [ -122.380679829595536, 37.721766984912172 ], [ -122.380702211442937, 37.721763193939509 ], [ -122.380724628718468, 37.721760775855728 ], [ -122.380747063016599, 37.721759044225237 ], [ -122.380776445603402, 37.721758575115111 ], [ -122.380798931664657, 37.721758902836477 ], [ -122.380821453165254, 37.721760603721208 ], [ -122.380843991677978, 37.721762990510022 ], [ -122.380866547569951, 37.721766064021175 ], [ -122.380887409825149, 37.721770538303247 ], [ -122.380910017489256, 37.721775671167713 ], [ -122.380930914478753, 37.721781517796998 ], [ -122.380951846221805, 37.721788737601223 ], [ -122.380969304166271, 37.72179532613972 ], [ -122.381209663710422, 37.721932611167148 ], [ -122.381595340270607, 37.722141054219321 ], [ -122.381616306935143, 37.722149646808873 ], [ -122.381637264915469, 37.722157896168731 ], [ -122.381659933573118, 37.722165431470607 ], [ -122.381680856812778, 37.722172308193493 ], [ -122.381703500095611, 37.72217881352352 ], [ -122.381718196203238, 37.722182371993462 ], [ -122.381726116975372, 37.722184289451477 ], [ -122.381748725168919, 37.72218942214915 ], [ -122.381771315640577, 37.722193868396147 ], [ -122.38179561782411, 37.722197600566012 ], [ -122.381818182577149, 37.722201017120689 ], [ -122.381842449666692, 37.722203376657269 ], [ -122.381864970966248, 37.722205076798758 ], [ -122.381889211635382, 37.722206406378888 ], [ -122.38191343562309, 37.7222070497657 ], [ -122.381935913473328, 37.722207033764029 ], [ -122.381960093656375, 37.722205960742578 ], [ -122.381984265148091, 37.722204544490268 ], [ -122.382006690514388, 37.722202469399647 ], [ -122.382131855667623, 37.722160639374017 ], [ -122.382053374975868, 37.722202410560982 ], [ -122.382081246609474, 37.722210549386944 ], [ -122.382107407226997, 37.722219401998011 ], [ -122.382135313638244, 37.722228913714801 ], [ -122.38216150904843, 37.7222391397666 ], [ -122.382186002128265, 37.722250422556726 ], [ -122.38221223231281, 37.72226202122625 ], [ -122.382231488067504, 37.722271327773925 ], [ -122.382247234884744, 37.722278630470704 ], [ -122.382264666284939, 37.722284188875534 ], [ -122.382282081008995, 37.722289061364116 ], [ -122.382301180314499, 37.722292189559724 ], [ -122.382316796753315, 37.722294343584274 ], [ -122.382332404500787, 37.722296154380857 ], [ -122.382348055371921, 37.7222996815849 ], [ -122.382362004260912, 37.722304265802613 ], [ -122.382377707967407, 37.722309851797831 ], [ -122.382389988962331, 37.722316866217334 ], [ -122.382402296728699, 37.722324910302142 ], [ -122.382412893137086, 37.722333668461893 ], [ -122.38242351562603, 37.722343456298312 ], [ -122.382432426756893, 37.722353958210256 ], [ -122.382443110446403, 37.72236614862048 ], [ -122.38245541788794, 37.722374192979785 ], [ -122.382471103884711, 37.722379092516256 ], [ -122.382486702960776, 37.722380560066497 ], [ -122.382502223461259, 37.722378938861951 ], [ -122.382529930032874, 37.722380556015445 ], [ -122.382545607695334, 37.722385112585165 ], [ -122.382559626159036, 37.72239244258634 ], [ -122.382571959345043, 37.722401516341982 ], [ -122.382580879888579, 37.72241236145743 ], [ -122.382586386406132, 37.722424977955527 ], [ -122.382590173914764, 37.722437965564282 ], [ -122.382588775856519, 37.722451035493926 ], [ -122.382590834611065, 37.722464050455052 ], [ -122.382598043802759, 37.722475609647852 ], [ -122.382610377359867, 37.722484683668391 ], [ -122.382624352373682, 37.722490297257906 ], [ -122.382641931997384, 37.722501690715291 ], [ -122.38265777418269, 37.72251276857228 ], [ -122.382675345468627, 37.72252381879327 ], [ -122.382692907374491, 37.722534525796839 ], [ -122.382708732877717, 37.722544917184152 ], [ -122.382726277750848, 37.722554937725484 ], [ -122.382745551041126, 37.722564930914963 ], [ -122.382763086877006, 37.722574607955885 ], [ -122.382780614728389, 37.722583942306464 ], [ -122.382799870630677, 37.722593248486184 ], [ -122.382827994635349, 37.722611340684225 ], [ -122.382840310501109, 37.722619727959788 ], [ -122.382856023011854, 37.722625657391347 ], [ -122.382871674315936, 37.722629184245534 ], [ -122.382901057233255, 37.722628714605619 ], [ -122.382920165354051, 37.722632186201139 ], [ -122.382932481579374, 37.722640573461497 ], [ -122.382939690848261, 37.722652132907662 ], [ -122.382943451985241, 37.722664090283487 ], [ -122.382954144152635, 37.722676623878094 ], [ -122.382966546995732, 37.722688443398582 ], [ -122.382978924448281, 37.722699233229207 ], [ -122.382994731944478, 37.722708938137238 ], [ -122.383008785302238, 37.722717641262442 ], [ -122.383026278422236, 37.722725602123852 ], [ -122.383042008015664, 37.722732217987662 ], [ -122.383057685092282, 37.722736774500049 ], [ -122.38307338894937, 37.722742360676712 ], [ -122.38308910115893, 37.722748290082635 ], [ -122.383103093664104, 37.722754590615885 ], [ -122.383131113444662, 37.722768563756851 ], [ -122.383145140734484, 37.722776236913717 ], [ -122.383173212715235, 37.722792269674038 ], [ -122.383185537691887, 37.722801000133508 ], [ -122.383197871022062, 37.722810073823027 ], [ -122.383210213742984, 37.722819490725982 ], [ -122.383220827716869, 37.72282893553853 ], [ -122.383231459072476, 37.722839066252533 ], [ -122.383252652240699, 37.722856582695023 ], [ -122.383275469111652, 37.722869952249027 ], [ -122.383296549234032, 37.722883006473765 ], [ -122.383319349425548, 37.722895689831176 ], [ -122.383342123188228, 37.72290734351234 ], [ -122.383366616328686, 37.722918626335904 ], [ -122.383389364359843, 37.722929250325038 ], [ -122.383413814380745, 37.722938817278958 ], [ -122.383431263763498, 37.722945061956594 ], [ -122.383518693416903, 37.722983494694461 ], [ -122.38360968436416, 37.72302599052874 ], [ -122.383623746538206, 37.723035036256725 ], [ -122.383636106408431, 37.723045139846334 ], [ -122.383646772299514, 37.723056643431036 ], [ -122.383655727872323, 37.723068861366919 ], [ -122.383661243319395, 37.723081821039202 ], [ -122.383666776856799, 37.723095467151623 ], [ -122.383670581632771, 37.723109140906679 ], [ -122.383676097085413, 37.723122100578188 ], [ -122.383683315208287, 37.723134002929086 ], [ -122.383695684140434, 37.723144449457912 ], [ -122.383708017935533, 37.723153523087994 ], [ -122.383722027960033, 37.72316050972421 ], [ -122.383732835965688, 37.72316905039996 ], [ -122.383734379158653, 37.723170269802985 ], [ -122.38373469231658, 37.723182625929454 ], [ -122.383748702349067, 37.723189612562479 ], [ -122.383764362513205, 37.723193482523506 ], [ -122.383781689859717, 37.723194922268853 ], [ -122.383795343242184, 37.723187836915955 ], [ -122.383821435159561, 37.723193943333669 ], [ -122.383828644613416, 37.723205502449808 ], [ -122.383835888866727, 37.723218434468372 ], [ -122.383841404702011, 37.72323139412638 ], [ -122.383848657659414, 37.723244669369848 ], [ -122.383852462137753, 37.723258343398804 ], [ -122.383857987017038, 37.723271646001557 ], [ -122.383861791837163, 37.723285319750062 ], [ -122.383865605358181, 37.723299336724111 ], [ -122.383867681419986, 37.723313038392696 ], [ -122.38387185196801, 37.723341127615335 ], [ -122.383872226038946, 37.723355886046527 ], [ -122.383879757398802, 37.723380144510621 ], [ -122.383887280063988, 37.723404059748461 ], [ -122.383896426763826, 37.723423828636541 ], [ -122.383903627551987, 37.723435044521956 ], [ -122.383914233002187, 37.723444146045374 ], [ -122.383928243093862, 37.723451132382195 ], [ -122.383956011009261, 37.72345515232913 ], [ -122.383982041442252, 37.723458856142138 ], [ -122.384009791963194, 37.723462189624996 ], [ -122.384035805692207, 37.723465206963674 ], [ -122.384063530109799, 37.723467510482209 ], [ -122.384089526103082, 37.723469841637751 ], [ -122.384136280611983, 37.723472527510232 ], [ -122.384158819757118, 37.723474913938858 ], [ -122.384181376305577, 37.723477986814515 ], [ -122.384203958613327, 37.723482089368453 ], [ -122.384224830245174, 37.723486906009427 ], [ -122.38424745675556, 37.723492724672354 ], [ -122.384268380608177, 37.72349960093424 ], [ -122.384289321860564, 37.723507163369192 ], [ -122.384308551747594, 37.72351543990402 ], [ -122.384327808428154, 37.723524745827085 ], [ -122.384347090879899, 37.723535081704128 ], [ -122.384364653265465, 37.723545788456732 ], [ -122.384377022361861, 37.723556234913332 ], [ -122.38438594261595, 37.723567079901137 ], [ -122.384394846160177, 37.723577238425868 ], [ -122.384405469087795, 37.72358702608161 ], [ -122.384417785645866, 37.723595413459996 ], [ -122.384430084788605, 37.72360311383661 ], [ -122.384444086263514, 37.723609757160801 ], [ -122.384458052932075, 37.723615027580706 ], [ -122.384473782841141, 37.723621643252429 ], [ -122.38448780172817, 37.72362897302272 ], [ -122.384501846725257, 37.723637332468307 ], [ -122.384515900428013, 37.723646035137868 ], [ -122.384528251811574, 37.723655795132643 ], [ -122.384538892173168, 37.723666269227522 ], [ -122.384549541240418, 37.723677086547099 ], [ -122.38456021641889, 37.723688933542604 ], [ -122.384570943824258, 37.723702840165593 ], [ -122.384581645111282, 37.723715716561451 ], [ -122.384595785866907, 37.72372785147779 ], [ -122.384609917924237, 37.723739643166766 ], [ -122.384624023876995, 37.723750405177114 ], [ -122.384639831811498, 37.723760109862766 ], [ -122.384657351467936, 37.723769100432129 ], [ -122.384674853030802, 37.723777404558668 ], [ -122.384688837160766, 37.723783361676922 ], [ -122.384702838685783, 37.723790004695601 ], [ -122.384716875034059, 37.72379802088971 ], [ -122.384727472270853, 37.723806778833662 ], [ -122.38473981501231, 37.723816195854965 ], [ -122.384750446723075, 37.723826326704874 ], [ -122.384759367064078, 37.723837171663916 ], [ -122.38476655931882, 37.723848044270227 ], [ -122.384773760272395, 37.723859259827094 ], [ -122.38478439233964, 37.723869390668334 ], [ -122.384795006310796, 37.723878835068398 ], [ -122.384807314596031, 37.723886878899947 ], [ -122.384821316158124, 37.723893522179033 ], [ -122.384835282906707, 37.723898792553967 ], [ -122.384850942931138, 37.723902662375643 ], [ -122.384889238702442, 37.723912694229192 ], [ -122.384901537953482, 37.723920394830778 ], [ -122.38491387167187, 37.723929468064433 ], [ -122.384922801444858, 37.72394065622548 ], [ -122.384935378901247, 37.72395934004836 ], [ -122.384946228617792, 37.723978051514877 ], [ -122.384957095057587, 37.723997449442578 ], [ -122.384966251165253, 37.724017561465779 ], [ -122.384978611028657, 37.724027664643877 ], [ -122.384989269262093, 37.72403882514314 ], [ -122.384998216130384, 37.724050700029551 ], [ -122.385005451957767, 37.724063288474291 ], [ -122.385012696499402, 37.724076220418716 ], [ -122.385016466753711, 37.724088521225767 ], [ -122.38502538751068, 37.724099366158697 ], [ -122.385037712928536, 37.724108096697165 ], [ -122.385053408173405, 37.724113339388623 ], [ -122.385069016362493, 37.724115149547565 ], [ -122.385084528456687, 37.724113184503125 ], [ -122.385101700039968, 37.72410844598258 ], [ -122.385118888340344, 37.724104393921756 ], [ -122.385137831886752, 37.724101343865193 ], [ -122.385156818620402, 37.72410001021359 ], [ -122.385179288343707, 37.72409965064211 ], [ -122.385196616267564, 37.724101089899079 ], [ -122.385215698411002, 37.724103531450261 ], [ -122.385233087637133, 37.724107373824481 ], [ -122.385250510990446, 37.724112588834913 ], [ -122.385267961148685, 37.724118833233817 ], [ -122.385283708655592, 37.724126135522596 ], [ -122.385301210711177, 37.724134439275502 ], [ -122.385316914686456, 37.724140025157403 ], [ -122.38533260125142, 37.724144924586071 ], [ -122.385348261353599, 37.724148794341509 ], [ -122.385363904735826, 37.724151977632673 ], [ -122.385381250095534, 37.724154103588141 ], [ -122.385396840549205, 37.724155227532641 ], [ -122.38541414272278, 37.724155637349959 ], [ -122.385429689989081, 37.72415504515677 ], [ -122.385446939582977, 37.72415339589606 ], [ -122.385464163048326, 37.724150716681571 ], [ -122.385486461018303, 37.724147348627724 ], [ -122.385517561871112, 37.724142651083937 ], [ -122.385562344261174, 37.724135753744811 ], [ -122.385579620308761, 37.724135133861189 ], [ -122.385595245944231, 37.724137630950658 ], [ -122.38560923890941, 37.724143930635698 ], [ -122.385619835954955, 37.724152688504432 ], [ -122.38562873939226, 37.72416284694048 ], [ -122.385639388343563, 37.724173664166216 ], [ -122.38565002928155, 37.724184138154342 ], [ -122.385662389611269, 37.724194241254921 ], [ -122.385674732182835, 37.724203657908639 ], [ -122.385688794837932, 37.724212703663014 ], [ -122.385702840080384, 37.724221062964645 ], [ -122.385716876624841, 37.724229079313623 ], [ -122.385730887033873, 37.724236065435178 ], [ -122.385746617533329, 37.724242680930779 ], [ -122.385762330273252, 37.724248609978716 ], [ -122.385778025598896, 37.724253852573533 ], [ -122.385798932390358, 37.72426004211173 ], [ -122.385816364866457, 37.72426559998376 ], [ -122.385832086325422, 37.724271872247911 ], [ -122.38584781649574, 37.724278487735482 ], [ -122.38586357244958, 37.724286132903025 ], [ -122.385877617725541, 37.724294492183716 ], [ -122.38589340051098, 37.724303167287317 ], [ -122.385905743122947, 37.724312583642103 ], [ -122.385919823251839, 37.72432231609433 ], [ -122.385932192689026, 37.724332762111779 ], [ -122.38594985163823, 37.724347244101757 ], [ -122.385963914360929, 37.724356290097674 ], [ -122.385979679391255, 37.72436427819499 ], [ -122.385995409594287, 37.724370893662766 ], [ -122.386012824706015, 37.72437576560376 ], [ -122.386028493940515, 37.724379978214039 ], [ -122.38604418931267, 37.724385220773271 ], [ -122.386058173659734, 37.724391177453832 ], [ -122.386072175428609, 37.724397820583704 ], [ -122.386086194619651, 37.724405150162887 ], [ -122.386098511495945, 37.724413537089887 ], [ -122.386110837084971, 37.724422267241074 ], [ -122.386121460359277, 37.724432054740767 ], [ -122.386155140875701, 37.724396492269392 ], [ -122.386142558844625, 37.724445794903964 ], [ -122.386158314864247, 37.724453440031894 ], [ -122.386174045447717, 37.724460055470189 ], [ -122.386191469295412, 37.724465270335628 ], [ -122.386208867014332, 37.724469455522041 ], [ -122.386227957996084, 37.724472240134631 ], [ -122.386245295090333, 37.724474022731769 ], [ -122.38626432544568, 37.724474404754595 ], [ -122.386288541146868, 37.724474703747106 ], [ -122.386305877897456, 37.724476486340883 ], [ -122.386321468785383, 37.724477610432025 ], [ -122.386338779051741, 37.724478363075484 ], [ -122.386354352857126, 37.724478800431207 ], [ -122.386371637343302, 37.724478523662484 ], [ -122.386387184323809, 37.724477931348545 ], [ -122.38640445173273, 37.72447696811853 ], [ -122.386419972931165, 37.7244753463929 ], [ -122.386437213858997, 37.724473353487241 ], [ -122.386452717618937, 37.724471044757188 ], [ -122.386469932064344, 37.724468022175898 ], [ -122.386485410039811, 37.724464684034231 ], [ -122.386500878956426, 37.724461002670601 ], [ -122.38651633915967, 37.724456978079473 ], [ -122.386531781598762, 37.724452267315506 ], [ -122.386547242137226, 37.724448242440204 ], [ -122.386564474343317, 37.72444590629047 ], [ -122.386579960337627, 37.724442911647458 ], [ -122.386595429583735, 37.724439229991283 ], [ -122.386610881065096, 37.724434862162283 ], [ -122.386626315119955, 37.724429807880355 ], [ -122.38664173140225, 37.724424067151048 ], [ -122.386657130948578, 37.724417639957707 ], [ -122.386670792994096, 37.724410897498451 ], [ -122.386684437604913, 37.724403468312111 ], [ -122.386698064780759, 37.724395352398709 ], [ -122.386709954455839, 37.724386921220365 ], [ -122.386721827401047, 37.724377803853415 ], [ -122.386733682558045, 37.724367999490731 ], [ -122.386743808927534, 37.724358223089439 ], [ -122.386753901134114, 37.724347073774396 ], [ -122.386760492536993, 37.724334263702232 ], [ -122.386768838521292, 37.724322455623209 ], [ -122.386780684957245, 37.724312308304832 ], [ -122.386796092835809, 37.724306224324238 ], [ -122.386814983740024, 37.72430111465097 ], [ -122.386833900781468, 37.724297034651016 ], [ -122.38685286104193, 37.724294670780772 ], [ -122.386873576231366, 37.724293308893905 ], [ -122.386890817450478, 37.724291315916268 ], [ -122.38690624274291, 37.724285918372097 ], [ -122.386918141436212, 37.724277830392509 ], [ -122.386928250341739, 37.724267367524348 ], [ -122.386934850421213, 37.724254900667695 ], [ -122.386936248094955, 37.724241830684782 ], [ -122.386934179470913, 37.724228472583448 ], [ -122.386932102818292, 37.724214770970946 ], [ -122.386933491079674, 37.72420135749902 ], [ -122.386933168672044, 37.724188658157658 ], [ -122.386929398441154, 37.724176357406598 ], [ -122.386923908138428, 37.72416442757148 ], [ -122.386916706823882, 37.724153211871986 ], [ -122.386907811932304, 37.724143397033281 ], [ -122.38689548628895, 37.724134666690496 ], [ -122.386883203877176, 37.724127652753772 ], [ -122.386867500139488, 37.724122067349683 ], [ -122.386837811685524, 37.724110524813227 ], [ -122.386792314266742, 37.724089278160726 ], [ -122.386769557372673, 37.724078311594091 ], [ -122.386748519865691, 37.724066974120603 ], [ -122.386725745213752, 37.72405532110006 ], [ -122.386704690294678, 37.724043297167768 ], [ -122.386683626669864, 37.724030930006215 ], [ -122.386641482023506, 37.72400550949537 ], [ -122.386620400988434, 37.723992455596928 ], [ -122.386599311255594, 37.723979058743758 ], [ -122.386579941254908, 37.723965290981276 ], [ -122.386544701706455, 37.723939416215046 ], [ -122.386297403385385, 37.72380190986933 ], [ -122.386051808287206, 37.723663345927129 ], [ -122.386034340737112, 37.723656414643614 ], [ -122.385999441189313, 37.723643925509059 ], [ -122.385964559071653, 37.723632122815218 ], [ -122.385936677777778, 37.723623641958845 ], [ -122.385920974203756, 37.723618055878958 ], [ -122.385906946468538, 37.723610383053618 ], [ -122.385894611983545, 37.723601309659934 ], [ -122.385883963077092, 37.723590492456275 ], [ -122.385875024838413, 37.723578961136361 ], [ -122.38586953474902, 37.723567031250973 ], [ -122.385860622648437, 37.723556529881158 ], [ -122.385846603632743, 37.723549199999539 ], [ -122.385827460305421, 37.723544355976919 ], [ -122.385811791258462, 37.723540143339349 ], [ -122.385796095735444, 37.723534900753975 ], [ -122.385780392190739, 37.723529314655281 ], [ -122.385764662184897, 37.723522699157897 ], [ -122.385750651900182, 37.723515712764971 ], [ -122.385738361343229, 37.723508355751683 ], [ -122.385731151556087, 37.723496796478393 ], [ -122.385722248191556, 37.723486638323564 ], [ -122.385708220501641, 37.723478965474406 ], [ -122.385692534045461, 37.723474065820646 ], [ -122.385680182539247, 37.72346430594763 ], [ -122.385666111318983, 37.723454916965551 ], [ -122.385652048810016, 37.723445871207367 ], [ -122.385637977596772, 37.723436482221878 ], [ -122.385609870011095, 37.723419077148201 ], [ -122.38559409650658, 37.723410745500026 ], [ -122.385580051429457, 37.723402386184311 ], [ -122.385548521852172, 37.723386409328988 ], [ -122.385534494199106, 37.723378736459033 ], [ -122.385510008939335, 37.723367797318083 ], [ -122.385494374422848, 37.723364957271542 ], [ -122.385459753720795, 37.72336345119556 ], [ -122.385444119898509, 37.723360611131262 ], [ -122.385430153207864, 37.723355340828022 ], [ -122.385416142986116, 37.723348354395121 ], [ -122.385403808604508, 37.723339280949688 ], [ -122.385391492330285, 37.723330893943086 ], [ -122.385375701117368, 37.723321876094445 ], [ -122.385359927998408, 37.723313544134655 ], [ -122.385342434828871, 37.723305583340405 ], [ -122.385326695857486, 37.723298624564244 ], [ -122.385309246571239, 37.723292379887567 ], [ -122.385291822714692, 37.723287164896192 ], [ -122.385263732678311, 37.723270446190739 ], [ -122.38524797700147, 37.723262800941718 ], [ -122.385232229687915, 37.723255498921759 ], [ -122.385216491083142, 37.723248540125276 ], [ -122.385185048704614, 37.723235995428482 ], [ -122.385169353635689, 37.723230752753828 ], [ -122.385151929803385, 37.723225537741719 ], [ -122.385134524075497, 37.723221009167226 ], [ -122.385117126370531, 37.723216824101435 ], [ -122.385101466469777, 37.723212954040122 ], [ -122.385082366457212, 37.723209826029851 ], [ -122.385064995564079, 37.723206670622204 ], [ -122.385047580442674, 37.723201798545674 ], [ -122.38502841148815, 37.723195924710531 ], [ -122.385010953200691, 37.723189336769465 ], [ -122.384993460097732, 37.72318137592324 ], [ -122.384977677662462, 37.723172700972079 ], [ -122.384946044204682, 37.723152605241658 ], [ -122.384924989829145, 37.723140580989593 ], [ -122.384905663878186, 37.723128529078224 ], [ -122.384884618220184, 37.723116848044533 ], [ -122.384842544338781, 37.723094172691695 ], [ -122.384800505291054, 37.723072869951821 ], [ -122.384779494472909, 37.7230625615273 ], [ -122.384742788674473, 37.723047010916545 ], [ -122.384730489577436, 37.723039310022727 ], [ -122.384716444674581, 37.723030950878183 ], [ -122.384704119127363, 37.723022220310114 ], [ -122.38469349658682, 37.723012432674857 ], [ -122.384682864654536, 37.723002301824017 ], [ -122.38467394442489, 37.722991456857699 ], [ -122.384663217109889, 37.722977550517093 ], [ -122.384654296194697, 37.722966705560324 ], [ -122.384643682371646, 37.722957261146163 ], [ -122.384633093968816, 37.722948846419143 ], [ -122.384619101304366, 37.722942546342381 ], [ -122.384596345081988, 37.722931579353954 ], [ -122.384578721543591, 37.722918470060556 ], [ -122.384546896507018, 37.722890823537078 ], [ -122.384530966582233, 37.722876313408833 ], [ -122.384518537006088, 37.722863464381859 ], [ -122.38443411130757, 37.722807129900666 ], [ -122.384347983418735, 37.722751852133769 ], [ -122.384239081476991, 37.722684921079754 ], [ -122.384207605050619, 37.722671003487143 ], [ -122.384176119229906, 37.722656742122247 ], [ -122.384144625418941, 37.722642137786686 ], [ -122.384113113527988, 37.722626847002253 ], [ -122.384083322047019, 37.722611185329136 ], [ -122.384051793126986, 37.722595208071212 ], [ -122.384021983580084, 37.722578859942075 ], [ -122.38399213993786, 37.722561138891301 ], [ -122.383974473094511, 37.722546313377947 ], [ -122.383955077860932, 37.722531515778066 ], [ -122.383937428078781, 37.722517376441559 ], [ -122.383918050598027, 37.722503265006914 ], [ -122.383884593268888, 37.722479421152507 ], [ -122.383868863653973, 37.722472805400912 ], [ -122.383854862444593, 37.722466162007116 ], [ -122.383839115090979, 37.722458859805528 ], [ -122.383825087796367, 37.722451187005468 ], [ -122.383809323735065, 37.722443198062706 ], [ -122.383795270342418, 37.722434495307176 ], [ -122.383782945009273, 37.722425764916487 ], [ -122.383768882931093, 37.722416719206592 ], [ -122.383744181117109, 37.722397198772512 ], [ -122.383731812655114, 37.72238675251657 ], [ -122.38372613997241, 37.722367614518575 ], [ -122.383705303481563, 37.722364170691534 ], [ -122.383691206623396, 37.722353751794557 ], [ -122.383675390420279, 37.722343704028198 ], [ -122.383659608655421, 37.72233502861922 ], [ -122.383642115552547, 37.722327067576785 ], [ -122.3836246492464, 37.722320136472625 ], [ -122.383605471580864, 37.722313918910281 ], [ -122.383586328372033, 37.722309074528077 ], [ -122.383570607863348, 37.722302801956744 ], [ -122.383553080331694, 37.722293467992337 ], [ -122.383537272159586, 37.722283763169607 ], [ -122.383521447287507, 37.722273371882082 ], [ -122.383507350472314, 37.722262952962978 ], [ -122.383491507867944, 37.722251875225332 ], [ -122.383477384968415, 37.722240426625198 ], [ -122.38346325337578, 37.72222863479751 ], [ -122.383445508101758, 37.722210720451898 ], [ -122.383434894533281, 37.722201275653106 ], [ -122.383422587021329, 37.722193231670488 ], [ -122.383410305264974, 37.722186217644108 ], [ -122.38339632153793, 37.722180260372568 ], [ -122.383380653254989, 37.722176047130667 ], [ -122.383364967234144, 37.722171147440534 ], [ -122.383350966126613, 37.722164503986562 ], [ -122.38333693893793, 37.722156831128018 ], [ -122.383322885647786, 37.722148128041155 ], [ -122.383310552066504, 37.722139054369293 ], [ -122.383298209445869, 37.722129637475824 ], [ -122.383282367246764, 37.722118559704285 ], [ -122.38326655080327, 37.722108511888187 ], [ -122.383249023002975, 37.722099177609202 ], [ -122.383231530690935, 37.722091216494434 ], [ -122.383212327035437, 37.722083969464926 ], [ -122.383193157822831, 37.722078095341168 ], [ -122.383172277957286, 37.72207293529052 ], [ -122.383156635797775, 37.722069751696246 ], [ -122.383140949814091, 37.722064851976235 ], [ -122.383123517701506, 37.722059293437333 ], [ -122.383107814683271, 37.722053707530257 ], [ -122.383076390550301, 37.722041848445443 ], [ -122.383058941409402, 37.722035603714303 ], [ -122.383027482510172, 37.72202237171301 ], [ -122.383013472765484, 37.722015384992908 ], [ -122.382981979115939, 37.722000780625166 ], [ -122.382967960677476, 37.721993450399175 ], [ -122.382952196466633, 37.721985461621244 ], [ -122.382938160650923, 37.72197744521457 ], [ -122.382922396446673, 37.721969456432632 ], [ -122.382897807697603, 37.721954398035997 ], [ -122.382881913081334, 37.721941260860625 ], [ -122.382867729471926, 37.721927409605762 ], [ -122.38285353717319, 37.721913215123287 ], [ -122.382839327490771, 37.72189833418733 ], [ -122.38282855685857, 37.721882711553377 ], [ -122.382816049838553, 37.721866773306338 ], [ -122.382806990215869, 37.721850436593726 ], [ -122.382797922594165, 37.721833756643434 ], [ -122.382790609101846, 37.721818078751127 ], [ -122.382783313008133, 37.721803087584632 ], [ -122.382772577517116, 37.721788837568752 ], [ -122.382763570078581, 37.721774560207898 ], [ -122.382752843642237, 37.721760653685102 ], [ -122.382740405865931, 37.721747461242323 ], [ -122.382731494418593, 37.721736959358161 ], [ -122.382724293971776, 37.721725743398046 ], [ -122.382720523870901, 37.721713442518464 ], [ -122.382720202193767, 37.721700743160483 ], [ -122.382723346326571, 37.72168833177593 ], [ -122.382726481072993, 37.721675577176406 ], [ -122.382729581733628, 37.721661449662143 ], [ -122.38273230785866, 37.721632563445375 ], [ -122.382731603643052, 37.721604762147862 ], [ -122.382733369644583, 37.721586614972047 ], [ -122.382734347844632, 37.721576562371538 ], [ -122.38274367451254, 37.721535209801523 ], [ -122.382746792545248, 37.72152176846371 ], [ -122.382751629924911, 37.721507956556103 ], [ -122.382756484345322, 37.721494831105609 ], [ -122.382763041407543, 37.721480648347587 ], [ -122.382782164842624, 37.721416476839273 ], [ -122.382798370882199, 37.721373640576992 ], [ -122.382806482416456, 37.721352565673556 ], [ -122.382814602294815, 37.721331834000935 ], [ -122.382824442541889, 37.721310731465771 ], [ -122.382834299480933, 37.721290315392572 ], [ -122.382844148411394, 37.721269556081531 ], [ -122.382854005339539, 37.721249140006542 ], [ -122.382865600022583, 37.721229039519947 ], [ -122.382877185659027, 37.721208595811795 ], [ -122.382900408735608, 37.721169767752777 ], [ -122.38290693965736, 37.721154555308274 ], [ -122.382913479271252, 37.721139686089295 ], [ -122.382921747600108, 37.721124789237969 ], [ -122.382931761341311, 37.72111055121686 ], [ -122.382941783773603, 37.721096656420762 ], [ -122.382951814897126, 37.721083104849697 ], [ -122.382963583082613, 37.721069868876654 ], [ -122.382977097024437, 37.721057291727 ], [ -122.382988899981882, 37.721045428655017 ], [ -122.383004168370334, 37.721033853552548 ], [ -122.383019462839854, 37.721023308125787 ], [ -122.383034765654699, 37.721013105928478 ], [ -122.383050086547513, 37.721003590169914 ], [ -122.383070574806197, 37.7209933050774 ], [ -122.383087649800032, 37.720984791368565 ], [ -122.383102978684576, 37.720975618840036 ], [ -122.383116562158349, 37.720965787756029 ], [ -122.383131839552021, 37.72095455585724 ], [ -122.383143650811689, 37.7209430357264 ], [ -122.383155445374214, 37.720930829406058 ], [ -122.383167222202275, 37.720917936912819 ], [ -122.383198904587886, 37.720871762618749 ], [ -122.38322729544646, 37.720832165418933 ], [ -122.383257414279257, 37.720792540037081 ], [ -122.383305971799075, 37.720729958050264 ], [ -122.38333258199772, 37.720688328826199 ], [ -122.383344140985599, 37.72066685539815 ], [ -122.383355691961356, 37.720645038731924 ], [ -122.383365505527735, 37.720622906477054 ], [ -122.383373590728709, 37.720600801854324 ], [ -122.383381667228008, 37.720578354005049 ], [ -122.383389734679795, 37.720555562934777 ], [ -122.383394337405079, 37.720532483893628 ], [ -122.383400668139274, 37.720509377224332 ], [ -122.383403542162711, 37.720486326096186 ], [ -122.383407970241009, 37.720456382272516 ], [ -122.38340677875901, 37.720409360597294 ], [ -122.383402539659841, 37.720378525251974 ], [ -122.383398682545547, 37.720362792132271 ], [ -122.383398428098104, 37.720361252706262 ], [ -122.383396641447277, 37.720350463627156 ], [ -122.383387721377815, 37.72033961856436 ], [ -122.383358200220172, 37.720334596711098 ], [ -122.383344547327056, 37.720341682291192 ], [ -122.38333615741675, 37.720351774011149 ], [ -122.383322539304771, 37.720360232492368 ], [ -122.383310275938157, 37.720353904619685 ], [ -122.383304777801996, 37.720341631382666 ], [ -122.38331139553371, 37.720329851172949 ], [ -122.383307625413451, 37.720317550583985 ], [ -122.383293624653192, 37.720310906846613 ], [ -122.383277974162851, 37.720307380041632 ], [ -122.383265710124405, 37.720301052449905 ], [ -122.383253377195246, 37.720291978489243 ], [ -122.38324619420213, 37.720281449010265 ], [ -122.383240704778117, 37.720269518996041 ], [ -122.38324039171458, 37.720257162862467 ], [ -122.383243535008134, 37.72024475147218 ], [ -122.383250135348419, 37.720232284813868 ], [ -122.383254981599535, 37.720218816377724 ], [ -122.383258090442638, 37.720205031803125 ], [ -122.38325945354886, 37.720190588682506 ], [ -122.383259097001115, 37.720176516419151 ], [ -122.383255283408985, 37.720162499423914 ], [ -122.383249759553038, 37.720149196500621 ], [ -122.3832425157008, 37.720136264439603 ], [ -122.383233560549201, 37.720124046466523 ], [ -122.38322290383195, 37.720112885790329 ], [ -122.383210553208443, 37.720103125653154 ], [ -122.383196395976469, 37.720090304111586 ], [ -122.383073889931538, 37.72003217363779 ], [ -122.382849809825458, 37.71992313205017 ], [ -122.382825447811499, 37.719916997234385 ], [ -122.38279933973088, 37.719910203866348 ], [ -122.382774952002876, 37.719903039905788 ], [ -122.382750537499561, 37.719894845998688 ], [ -122.382727834641599, 37.719885937725351 ], [ -122.382703394420673, 37.719876714399525 ], [ -122.382680656108263, 37.719866433224148 ], [ -122.382657909806966, 37.719855809081899 ], [ -122.382635145779759, 37.719844498488762 ], [ -122.382614092714974, 37.719832473817299 ], [ -122.382593030963804, 37.71982010591605 ], [ -122.382571951833512, 37.71980705155903 ], [ -122.382552583665785, 37.719793283125171 ], [ -122.382533206466661, 37.719779171467643 ], [ -122.382517347496844, 37.719767407137013 ], [ -122.382329598519163, 37.719659157154716 ], [ -122.382140156862491, 37.71955230738498 ], [ -122.382084189621324, 37.71952710586983 ], [ -122.382026520507765, 37.719502961888537 ], [ -122.382009045965773, 37.719495686772042 ], [ -122.381991562743806, 37.719488068701423 ], [ -122.381975799169183, 37.719480079789612 ], [ -122.381960018217541, 37.719471404423579 ], [ -122.381944228233777, 37.719462385834824 ], [ -122.381928430255144, 37.719453024006853 ], [ -122.381912623244801, 37.719443318956195 ], [ -122.381898535881675, 37.719433243065936 ], [ -122.381884431142637, 37.719422480721789 ], [ -122.381872072119776, 37.719412377217125 ], [ -122.381859773582747, 37.719404676299234 ], [ -122.381847484429258, 37.719397318595092 ], [ -122.381833501358983, 37.719391361409976 ], [ -122.381802095947549, 37.719380188981518 ], [ -122.381786358477399, 37.719373229447925 ], [ -122.381770594949188, 37.719365240508537 ], [ -122.381756533687849, 37.719356194279435 ], [ -122.38174418338258, 37.719346433987333 ], [ -122.381731807013125, 37.719335644015629 ], [ -122.381721141599613, 37.719324139981623 ], [ -122.381712186795909, 37.719311921891311 ], [ -122.381704943292732, 37.719298989734028 ], [ -122.381695910985357, 37.719283682596497 ], [ -122.381688693554523, 37.719271780116451 ], [ -122.381681484814919, 37.719260220862054 ], [ -122.381663628063038, 37.719237844020014 ], [ -122.381645788699373, 37.719216153627443 ], [ -122.381624527445553, 37.719195891351333 ], [ -122.381612168150028, 37.71918578782455 ], [ -122.381601529187748, 37.719175313452254 ], [ -122.381592592151165, 37.719163782073863 ], [ -122.381583646069373, 37.719151906924999 ], [ -122.381576411292528, 37.719139317985587 ], [ -122.381570896501387, 37.71912635820749 ], [ -122.381567092315478, 37.719112684375858 ], [ -122.381559735915829, 37.719095290544253 ], [ -122.38155422912088, 37.719082673727875 ], [ -122.381548731721296, 37.719070400675434 ], [ -122.381541505639575, 37.719058154685285 ], [ -122.38153600789127, 37.719045881363151 ], [ -122.38150713837662, 37.718998271399208 ], [ -122.381498201369013, 37.718986739738739 ], [ -122.381490992686665, 37.71897518047215 ], [ -122.381473118684809, 37.718952117147957 ], [ -122.381464190375866, 37.718940928710964 ], [ -122.381455270757456, 37.718930083499401 ], [ -122.381446342115154, 37.718918895341112 ], [ -122.38143742284764, 37.718908050122621 ], [ -122.381416144356564, 37.718887101081044 ], [ -122.381407233439191, 37.718876599091892 ], [ -122.381385972354465, 37.718856337046212 ], [ -122.381373613486502, 37.71884623321376 ], [ -122.381352369787336, 37.718826657339626 ], [ -122.381327686843449, 37.718807823394606 ], [ -122.38131535405293, 37.718798749234402 ], [ -122.38129421463421, 37.718783292338109 ], [ -122.381275038076012, 37.718777074392875 ], [ -122.381252413579233, 37.718771255149179 ], [ -122.381231552150709, 37.71876678120416 ], [ -122.381208979780382, 37.718763021309094 ], [ -122.381188161789552, 37.718760263487212 ], [ -122.381165632510005, 37.718758219720186 ], [ -122.381143129982078, 37.718757205616299 ], [ -122.381106826627473, 37.718757441991308 ], [ -122.381084384557454, 37.718758830464949 ], [ -122.380998020790415, 37.718762269763332 ], [ -122.380954864960344, 37.718765019067327 ], [ -122.380911726497644, 37.718768454807901 ], [ -122.380818588404082, 37.718777496255818 ], [ -122.380801331270163, 37.71877880161415 ], [ -122.380782345824386, 37.718780134839598 ], [ -122.380765070988232, 37.718780754294905 ], [ -122.380746051141955, 37.718780714329604 ], [ -122.380728750590777, 37.7187803038209 ], [ -122.380709712691541, 37.718779577682966 ], [ -122.380692386433651, 37.718778137484804 ], [ -122.380673314144289, 37.718776038430633 ], [ -122.380655961489964, 37.718773568553999 ], [ -122.380638600152778, 37.718770755448638 ], [ -122.380621221455968, 37.718767256162863 ], [ -122.380603834063052, 37.71876341309914 ], [ -122.380569024561694, 37.718754354608322 ], [ -122.380515107712881, 37.718741824128088 ], [ -122.380459506326716, 37.71873103733904 ], [ -122.380442119303254, 37.718727194520504 ], [ -122.380426442886844, 37.718722637662651 ], [ -122.380409012106568, 37.718717078713624 ], [ -122.380393292279919, 37.718710805720228 ], [ -122.380374046360402, 37.718701842093019 ], [ -122.380356511396428, 37.718692164421441 ], [ -122.38034421348118, 37.718684463338931 ], [ -122.380331889520718, 37.718675732576422 ], [ -122.380321267831235, 37.718665944545648 ], [ -122.380312339384403, 37.718654756026375 ], [ -122.380301657268987, 37.718642565404615 ], [ -122.380291009195389, 37.71863174769787 ], [ -122.380280353133841, 37.718620586752877 ], [ -122.380271407679629, 37.71860871177239 ], [ -122.380262453207322, 37.71859649384507 ], [ -122.380255227383529, 37.718584247775084 ], [ -122.380248018933486, 37.718572688431699 ], [ -122.380240793121359, 37.718560442635365 ], [ -122.380231848026369, 37.718548567646252 ], [ -122.38022120032204, 37.718537749927506 ], [ -122.380208832990292, 37.718527303020664 ], [ -122.380196491707878, 37.718517885791279 ], [ -122.380182448162131, 37.718509525825596 ], [ -122.380168421983186, 37.718501852310723 ], [ -122.380152693540609, 37.718495236058757 ], [ -122.380136999827513, 37.718489992709728 ], [ -122.380119595168225, 37.718485463396448 ], [ -122.380103953548627, 37.718482279400568 ], [ -122.380086617999027, 37.718480495897992 ], [ -122.380053754382303, 37.718479990320766 ], [ -122.380034708231022, 37.718478920841939 ], [ -122.380017337959387, 37.718475764424113 ], [ -122.37999994233742, 37.718471578313881 ], [ -122.379982511302273, 37.718466019307044 ], [ -122.379966765867707, 37.718458716572137 ], [ -122.379950994048144, 37.718450384161848 ], [ -122.379936915819798, 37.718440651261687 ], [ -122.379924539863453, 37.718429861098564 ], [ -122.379912120161947, 37.71841735480826 ], [ -122.379903227227103, 37.718407539426167 ], [ -122.379890903011727, 37.718398808348084 ], [ -122.379878596858461, 37.718390763984829 ], [ -122.379866307723006, 37.718383406078345 ], [ -122.379852316321873, 37.718377105430825 ], [ -122.379838350963936, 37.718371834460527 ], [ -122.379822683339711, 37.718367620748374 ], [ -122.379808770066504, 37.718364409132271 ], [ -122.379793145500187, 37.71836191155316 ], [ -122.379777565026231, 37.718361130092418 ], [ -122.379746464146947, 37.718361969759833 ], [ -122.379729146346136, 37.71836087292629 ], [ -122.379715198352471, 37.718356288119374 ], [ -122.379701189609946, 37.718349301001297 ], [ -122.379690576729757, 37.718339856139472 ], [ -122.379683377077711, 37.718328640261959 ], [ -122.3796778796832, 37.718316366577113 ], [ -122.37967239098387, 37.718304436667452 ], [ -122.37966348037574, 37.718293934270854 ], [ -122.379652884528355, 37.718285176138238 ], [ -122.379638884477515, 37.718278531964394 ], [ -122.379623199871318, 37.718273631767524 ], [ -122.379609182480792, 37.718266301686668 ], [ -122.379598560938021, 37.718256513315531 ], [ -122.379589606611773, 37.718244295061801 ], [ -122.379568259413119, 37.718220600422896 ], [ -122.379555875206677, 37.718209466714256 ], [ -122.379543499344152, 37.718198676510717 ], [ -122.3795311408429, 37.718188572758478 ], [ -122.379517062719074, 37.718178839807734 ], [ -122.379503001963329, 37.71816979358254 ], [ -122.379487230257041, 37.718161461109773 ], [ -122.379473169494304, 37.718152414331819 ], [ -122.379462538975446, 37.718142283276592 ], [ -122.3794518917869, 37.718131465482102 ], [ -122.37944294651696, 37.718119590163212 ], [ -122.379435729559873, 37.718107687542933 ], [ -122.379424700187641, 37.718081767792796 ], [ -122.379422659465391, 37.718069439219867 ], [ -122.379417153461716, 37.718056822570574 ], [ -122.379409936521796, 37.718044920223178 ], [ -122.37940101731057, 37.718034074854529 ], [ -122.379390386804573, 37.718023943243416 ], [ -122.379378072089409, 37.71801555560107 ], [ -122.37936745929791, 37.718006110709609 ], [ -122.379346164299292, 37.71798447511302 ], [ -122.379333796843824, 37.717974028393506 ], [ -122.379323167053869, 37.717963897039802 ], [ -122.379310817292421, 37.717954136215845 ], [ -122.379298475880205, 37.717944719171612 ], [ -122.379286143826207, 37.717935644792249 ], [ -122.379272083137607, 37.717926598264555 ], [ -122.379261435662016, 37.717915780457993 ], [ -122.379250796867041, 37.717905305876819 ], [ -122.379240166752524, 37.717895174521026 ], [ -122.379229553995728, 37.71788572961696 ], [ -122.379217221621232, 37.717876655510366 ], [ -122.379203178306753, 37.717868295427088 ], [ -122.379190871970053, 37.717860250996829 ], [ -122.379175109041569, 37.717852261708209 ], [ -122.379164469922188, 37.717841787124641 ], [ -122.379152137908591, 37.717832713005684 ], [ -122.379138137994644, 37.717826069046431 ], [ -122.379125875056587, 37.717819740741156 ], [ -122.379111857793731, 37.717812410326019 ], [ -122.379101270749828, 37.717803995094904 ], [ -122.379090667046114, 37.717794893399073 ], [ -122.379078291320113, 37.717784103145959 ], [ -122.379062641223001, 37.717780576059667 ], [ -122.379047051849412, 37.717779451006763 ], [ -122.379033086660868, 37.717774179940491 ], [ -122.379022465271206, 37.717764391791221 ], [ -122.379016976365222, 37.717752461580901 ], [ -122.379009785541115, 37.717741588612981 ], [ -122.378990609802273, 37.71773537083854 ], [ -122.378971451059471, 37.717729838970079 ], [ -122.378953994246785, 37.717723250131236 ], [ -122.378936512100381, 37.717715631599603 ], [ -122.37891900323838, 37.7177069833972 ], [ -122.378903197337848, 37.717697277934541 ], [ -122.37888387343952, 37.717685225304997 ], [ -122.378864602279094, 37.717675231470452 ], [ -122.378847076429409, 37.717665896799019 ], [ -122.378827830621148, 37.717656932923234 ], [ -122.378808602859749, 37.717648655486073 ], [ -122.378778909235265, 37.717636768235245 ], [ -122.378761435795425, 37.717629492629342 ], [ -122.378726453548339, 37.717613569064291 ], [ -122.378708953409784, 37.717605264056957 ], [ -122.378693173583002, 37.717596588245186 ], [ -122.378675665123012, 37.717587940000953 ], [ -122.378659867262996, 37.717578577742898 ], [ -122.37864235048211, 37.71756958626176 ], [ -122.378594931951909, 37.717540469771926 ], [ -122.378579117109084, 37.717530421044614 ], [ -122.3785509613637, 37.717510954911866 ], [ -122.378491401169214, 37.717480315188894 ], [ -122.37843182436778, 37.717448988971746 ], [ -122.37837222957846, 37.717416976282458 ], [ -122.378314346472195, 37.717384249540096 ], [ -122.378221298936523, 37.717328391303198 ], [ -122.378163571723618, 37.717301842568197 ], [ -122.378104150956304, 37.717276694266459 ], [ -122.37804646718476, 37.717251861606229 ], [ -122.377987080837755, 37.717228086156695 ], [ -122.377882351983473, 37.717188895399786 ], [ -122.377451299846371, 37.716962278497022 ], [ -122.377409136350764, 37.716935824700762 ], [ -122.377366990222555, 37.71691005734241 ], [ -122.37732312451665, 37.716884660739723 ], [ -122.37727928449992, 37.716860293805809 ], [ -122.377192593728324, 37.716850687700834 ], [ -122.377174053804566, 37.716801195276375 ], [ -122.377135442741164, 37.716778462058606 ], [ -122.377117882304205, 37.716767754227781 ], [ -122.377098594291084, 37.716757073924335 ], [ -122.377077577665531, 37.716746421163919 ], [ -122.377058306652586, 37.716736427312505 ], [ -122.377035544432545, 37.716725115631768 ], [ -122.377004002182673, 37.716708449744992 ], [ -122.376974170891216, 37.716691070132001 ], [ -122.376942602330686, 37.716673374829035 ], [ -122.376912754080166, 37.716655308741679 ], [ -122.376882896832043, 37.716636899425616 ], [ -122.376853030939387, 37.716618147149923 ], [ -122.376824875984553, 37.716598680327252 ], [ -122.376794983771219, 37.716578898087818 ], [ -122.376766811521605, 37.716558745072504 ], [ -122.376740359241694, 37.716538221557123 ], [ -122.376712169684112, 37.71651738180077 ], [ -122.376685699750382, 37.716496171550673 ], [ -122.376645065755298, 37.716461795731668 ], [ -122.376629260077664, 37.716452090242093 ], [ -122.376613463055463, 37.71644272742784 ], [ -122.376597691693803, 37.716434394571444 ], [ -122.376583692278402, 37.716427750581751 ], [ -122.376567912942647, 37.716419074209107 ], [ -122.376553835548819, 37.716409341176096 ], [ -122.376541451404279, 37.716398207158385 ], [ -122.376530778896253, 37.716386359416987 ], [ -122.376520053711957, 37.71637245232575 ], [ -122.376509415869748, 37.716361977488937 ], [ -122.376498794675413, 37.716352189390022 ], [ -122.37648645456278, 37.716342771488634 ], [ -122.376474122785964, 37.71633369736724 ], [ -122.37646180833417, 37.716325309423283 ], [ -122.376449502196934, 37.716317264435602 ], [ -122.376435485481252, 37.716309933700259 ], [ -122.376365488584341, 37.716276712265071 ], [ -122.376304211373494, 37.7162464421762 ], [ -122.376232373365212, 37.716208786249759 ], [ -122.376216559470649, 37.716198737197637 ], [ -122.376202464831138, 37.716188317394391 ], [ -122.376190080782948, 37.716177183613944 ], [ -122.37617768876666, 37.716165706594467 ], [ -122.376165279082414, 37.716153543125806 ], [ -122.376154588659077, 37.716141009182088 ], [ -122.3761438902538, 37.71612813145051 ], [ -122.376134902445671, 37.716114540017813 ], [ -122.376127616916719, 37.716099891652185 ], [ -122.376121444571496, 37.716060846115461 ], [ -122.376121115383214, 37.71604780350129 ], [ -122.376125312585643, 37.716008593078577 ], [ -122.376128439914226, 37.715995495685426 ], [ -122.376133304145654, 37.715982713442855 ], [ -122.376143049597545, 37.715957835965099 ], [ -122.376159466184788, 37.715923238019322 ], [ -122.376179356242673, 37.715889271474957 ], [ -122.376189309582273, 37.715872631433676 ], [ -122.376201000523864, 37.715856307079349 ], [ -122.37621269978483, 37.715840326230605 ], [ -122.376226135941366, 37.715824660529996 ], [ -122.376239589425921, 37.715809681555776 ], [ -122.376254771155516, 37.715794675051384 ], [ -122.376271646475075, 37.715778268109382 ], [ -122.376283440655172, 37.715766062477954 ], [ -122.376310486193518, 37.715741596141669 ], [ -122.376322263732177, 37.715728704316476 ], [ -122.376334049571057, 37.715716155173126 ], [ -122.376345826755966, 37.715703263351031 ], [ -122.376369363764837, 37.715676792426088 ], [ -122.376381123602442, 37.715663213872432 ], [ -122.376401186771318, 37.715636111823081 ], [ -122.376411209340475, 37.715622217575891 ], [ -122.376441252415276, 37.715579505126925 ], [ -122.376454523861156, 37.715557318367054 ], [ -122.37646974881784, 37.715544027967823 ], [ -122.376484964758959, 37.715530394345414 ], [ -122.376515380332151, 37.715502440624682 ], [ -122.376528859694105, 37.715488491296732 ], [ -122.376542321721558, 37.715473855513849 ], [ -122.376557511986263, 37.71545919219632 ], [ -122.37656924576747, 37.715444584217934 ], [ -122.376582699107203, 37.715429604929049 ], [ -122.37659614379038, 37.715414282961056 ], [ -122.376619576292114, 37.715383693270141 ], [ -122.376631284392559, 37.715368055325236 ], [ -122.376654682556946, 37.715336092989432 ], [ -122.376664653392297, 37.715320139354375 ], [ -122.376682927469105, 37.715290662203351 ], [ -122.376687808896733, 37.715278566664409 ], [ -122.376690936088593, 37.715265468980213 ], [ -122.376688878566242, 37.715252453901009 ], [ -122.376681679659114, 37.715241237559951 ], [ -122.376662556398827, 37.715237078495178 ], [ -122.37664688961523, 37.715232864358583 ], [ -122.376639690716317, 37.715221648014925 ], [ -122.376644563481292, 37.715209209250951 ], [ -122.376652953380841, 37.715199118279322 ], [ -122.376664860393532, 37.715191374275555 ], [ -122.376687422784229, 37.715194791816721 ], [ -122.376687067504449, 37.715180719522259 ], [ -122.376702422323845, 37.715172577492581 ], [ -122.376716031573253, 37.715163776542923 ], [ -122.376729623488316, 37.715154289138269 ], [ -122.376741469833547, 37.715144142814424 ], [ -122.376755035744793, 37.715133625726743 ], [ -122.37676513617842, 37.715122820763163 ], [ -122.376776948189416, 37.715111301248839 ], [ -122.376787014292304, 37.715099123096479 ], [ -122.376797028396751, 37.715084885583089 ], [ -122.376822345285078, 37.715060446663202 ], [ -122.376847653490671, 37.715035664511078 ], [ -122.376871224781212, 37.715010566664418 ], [ -122.376894778723212, 37.714984782359565 ], [ -122.376916604418071, 37.714959025588243 ], [ -122.376960238083626, 37.714906825585267 ], [ -122.376997080800365, 37.714859540886621 ], [ -122.377010663964072, 37.714849710496978 ], [ -122.37702423844344, 37.714839536329912 ], [ -122.377036076031359, 37.714829046749252 ], [ -122.377059716529033, 37.714806694677584 ], [ -122.377071528105475, 37.714795175413244 ], [ -122.377101725785408, 37.714758640881662 ], [ -122.377110046572668, 37.71474580378316 ], [ -122.37712317899306, 37.714718125317994 ], [ -122.377131300077295, 37.714697394009583 ], [ -122.377141149035509, 37.714676635164444 ], [ -122.377151007346924, 37.714656219534078 ], [ -122.377160873636086, 37.714636147415113 ], [ -122.377172468820078, 37.71461604719299 ], [ -122.377182335436814, 37.714595974791997 ], [ -122.377193938592001, 37.714576218079934 ], [ -122.377207270307977, 37.714556433818551 ], [ -122.377218882809402, 37.714537020319568 ], [ -122.377232223179533, 37.714517579281953 ], [ -122.37725892123467, 37.71447938365538 ], [ -122.377277350893181, 37.714456084488674 ], [ -122.377283856371662, 37.71443984265948 ], [ -122.377292090065524, 37.714423573286055 ], [ -122.377300340753251, 37.714407990645519 ], [ -122.377310329000593, 37.714392723126743 ], [ -122.377322053797371, 37.714377771844191 ], [ -122.37733379557335, 37.714363506744739 ], [ -122.377347274568308, 37.714349557046042 ], [ -122.377360770908837, 37.714336294348335 ], [ -122.377376004460587, 37.714323346775984 ], [ -122.37739125500535, 37.714311085935115 ], [ -122.37740648820791, 37.714298138638888 ], [ -122.377421729728198, 37.714285534572831 ], [ -122.377436980603278, 37.714273273720465 ], [ -122.377453959342105, 37.714260985325403 ], [ -122.377469218191379, 37.7142490679811 ], [ -122.377486214595891, 37.714237465754614 ], [ -122.3775014911119, 37.714226234579385 ], [ -122.37751849583654, 37.714214975854986 ], [ -122.377549126867919, 37.714195602534218 ], [ -122.377599993609977, 37.714155991753785 ], [ -122.377650877623338, 37.714117066855323 ], [ -122.37770177893016, 37.714078828662608 ], [ -122.377754425395366, 37.714041249630476 ], [ -122.377808826358589, 37.714004672148562 ], [ -122.377878556048842, 37.713958923045773 ], [ -122.37803805055384, 37.713839292710567 ], [ -122.378311443052539, 37.713642992366779 ], [ -122.378326710994585, 37.713631417851289 ], [ -122.37834196991345, 37.713619500112657 ], [ -122.378355483282817, 37.713606923478181 ], [ -122.378367251103185, 37.713593687948453 ], [ -122.378379009900755, 37.713580109196364 ], [ -122.378389032514391, 37.713566214765343 ], [ -122.378399037086837, 37.713551633891143 ], [ -122.378409033673563, 37.71353670977841 ], [ -122.378415564843792, 37.713521497564329 ], [ -122.378423815534646, 37.713505914562745 ], [ -122.378428575143985, 37.713489014049621 ], [ -122.378438562344869, 37.713473746443995 ], [ -122.378446830724755, 37.713458850163093 ], [ -122.378458563459517, 37.713444241448215 ], [ -122.378468586039958, 37.713430347010117 ], [ -122.378482073340123, 37.713416740955331 ], [ -122.378495569294984, 37.713403477576485 ], [ -122.37854455933747, 37.713358058816908 ], [ -122.378586757641585, 37.713317555466887 ], [ -122.378660976502815, 37.71324426535471 ], [ -122.378686622123027, 37.713232868634442 ], [ -122.378712250040763, 37.713220785460798 ], [ -122.378736140733594, 37.713208386350885 ], [ -122.378760006099483, 37.713194958094263 ], [ -122.378783853401998, 37.713180842841368 ], [ -122.378805955855427, 37.713166068959936 ], [ -122.378828040611339, 37.71315060890111 ], [ -122.378850116669099, 37.713134805062303 ], [ -122.378897499585833, 37.713094218893794 ], [ -122.378902259061135, 37.713077318360412 ], [ -122.378913939946415, 37.713060650508481 ], [ -122.378933827973057, 37.713026683209065 ], [ -122.378963633962542, 37.712974702985242 ], [ -122.3789800308884, 37.712939418456834 ], [ -122.378988220659366, 37.712921432553166 ], [ -122.378994682251076, 37.712903474492308 ], [ -122.379002872021474, 37.712885488862113 ], [ -122.379010931305004, 37.712862354837071 ], [ -122.37901923419868, 37.712848831146545 ], [ -122.379027554095487, 37.712835993914254 ], [ -122.379037619176003, 37.712823815570687 ], [ -122.379047693627413, 37.712811980716452 ], [ -122.379059495902879, 37.712800118022507 ], [ -122.37908614901481, 37.712760205563576 ], [ -122.379114530283019, 37.712720265802361 ], [ -122.379142928880469, 37.712681012761955 ], [ -122.379173064293639, 37.712642075095069 ], [ -122.379203208344947, 37.71260348037233 ], [ -122.379256878689148, 37.712538071449124 ], [ -122.379268637502918, 37.712524492600444 ], [ -122.379278650439701, 37.712510254875795 ], [ -122.379298659638835, 37.712481092959536 ], [ -122.379306927729047, 37.71246619634131 ], [ -122.379315186793136, 37.712450956501357 ], [ -122.379321709005382, 37.712435401007788 ], [ -122.37932825724657, 37.712420875193772 ], [ -122.379336368787747, 37.712399800499135 ], [ -122.379346199816894, 37.712378355003281 ], [ -122.379365879212997, 37.712336150462143 ], [ -122.379377455409823, 37.712315364122553 ], [ -122.379389040962025, 37.71229492072279 ], [ -122.379400617477728, 37.712274133826106 ], [ -122.379412211349106, 37.712254033656116 ], [ -122.37943383579011, 37.712220382468807 ], [ -122.379443961457966, 37.712210606401335 ], [ -122.379452350984806, 37.712200515220019 ], [ -122.379460722794221, 37.71218973704115 ], [ -122.379467357765066, 37.712178643485373 ], [ -122.379472247213258, 37.71216689105173 ], [ -122.379480610345027, 37.712155769919313 ], [ -122.379490683937206, 37.712143934762395 ], [ -122.379499030060259, 37.712132127444399 ], [ -122.379507358465077, 37.712119633128815 ], [ -122.379513950039424, 37.712106823711594 ], [ -122.3795205329195, 37.712093670518172 ], [ -122.379525370285052, 37.712079858722241 ], [ -122.379530172590734, 37.712064674025001 ], [ -122.379539812945708, 37.712035677519864 ], [ -122.379548106627041, 37.712021810569162 ], [ -122.379556408637853, 37.712008286850036 ], [ -122.379568185022265, 37.711995394692678 ], [ -122.379580030469796, 37.711985247803753 ], [ -122.379591867242681, 37.711974757961542 ], [ -122.37960195849513, 37.711963609242559 ], [ -122.379612041066054, 37.71195211729605 ], [ -122.379622114962288, 37.711940282396583 ], [ -122.379630451657874, 37.711928131304077 ], [ -122.379638780377292, 37.711915637522473 ], [ -122.379645371896999, 37.711902827548315 ], [ -122.379650226245587, 37.711889702480249 ], [ -122.379656800410586, 37.711876206326664 ], [ -122.379659918603238, 37.711862765324433 ], [ -122.379666440684801, 37.711847209535733 ], [ -122.379678077818568, 37.711828825746096 ], [ -122.379688004133726, 37.711811155438397 ], [ -122.37969966729311, 37.7117938013265 ], [ -122.379726467250435, 37.711759723844118 ], [ -122.379741621412023, 37.711743687200766 ], [ -122.379749992782422, 37.711732909006074 ], [ -122.379758304094665, 37.711719728487729 ], [ -122.379768369250002, 37.711707550074067 ], [ -122.379780179921312, 37.711696030807062 ], [ -122.379792016612967, 37.71168554066967 ], [ -122.379807344332391, 37.711676368550826 ], [ -122.379822698086315, 37.711668226109936 ], [ -122.379838086216196, 37.711661456853747 ], [ -122.379855237891832, 37.711656032360494 ], [ -122.379872414919589, 37.711651637830329 ], [ -122.379889557917664, 37.711645870380011 ], [ -122.379908402687462, 37.71163904594512 ], [ -122.379923765095569, 37.711631246443076 ], [ -122.379940837953981, 37.711622733183106 ], [ -122.379956157314652, 37.71161321808723 ], [ -122.379969722126845, 37.711602700623473 ], [ -122.379983278248133, 37.711591839656897 ], [ -122.37999507983497, 37.71157997687245 ], [ -122.380006864748808, 37.711567427622455 ], [ -122.380016912482191, 37.711554562733966 ], [ -122.380025223388486, 37.711541382476511 ], [ -122.380036947182674, 37.711526430642408 ], [ -122.38004519730535, 37.711510847247801 ], [ -122.380061679847557, 37.711478994558078 ], [ -122.380068175776742, 37.711462409340825 ], [ -122.380072943900714, 37.711445851702329 ], [ -122.380077702996331, 37.711428950842468 ], [ -122.380080724916397, 37.711411734346029 ], [ -122.380091216381629, 37.711348044590686 ], [ -122.38010580681599, 37.711241368202799 ], [ -122.380111373878989, 37.711188057730013 ], [ -122.380119034347572, 37.71108080577428 ], [ -122.380121136424947, 37.711027206969042 ], [ -122.38012151036925, 37.71097363602329 ], [ -122.380118923524051, 37.71087135474918 ], [ -122.380110379014539, 37.710806938200108 ], [ -122.380106523093488, 37.71079120494867 ], [ -122.380104387317047, 37.710775100874208 ], [ -122.380100531751495, 37.710759367891555 ], [ -122.380096667493106, 37.710743291132871 ], [ -122.380092811923888, 37.710727557875288 ], [ -122.380087228225392, 37.710711852202415 ], [ -122.380081653209785, 37.710696489755954 ], [ -122.380077797646152, 37.710680756497752 ], [ -122.380070494504992, 37.710665421635404 ], [ -122.38005934448995, 37.710634696740314 ], [ -122.380050209064109, 37.710615270740703 ], [ -122.380041299331083, 37.710604768634425 ], [ -122.380032397935238, 37.710594609759674 ], [ -122.38002177779228, 37.710584821958371 ], [ -122.38001116562711, 37.710575376844702 ], [ -122.379989959370192, 37.710557173605956 ], [ -122.379977637144137, 37.710548442789609 ], [ -122.379965323255277, 37.71054005520417 ], [ -122.379940729859655, 37.710524652941764 ], [ -122.379928442370812, 37.710517295301628 ], [ -122.379914435085283, 37.710510308200178 ], [ -122.379886455233503, 37.710497706624587 ], [ -122.379872473994169, 37.710491749198276 ], [ -122.379856825041841, 37.710488221944338 ], [ -122.379827508004084, 37.710491093407484 ], [ -122.379813839223587, 37.710497492136078 ], [ -122.379798416283975, 37.71050288903816 ], [ -122.379782897858064, 37.710504509895109 ], [ -122.379767309667471, 37.710503385490924 ], [ -122.379749941954842, 37.710500228744046 ], [ -122.379735969404862, 37.71049461452828 ], [ -122.379723664231022, 37.710486570144241 ], [ -122.37971305247035, 37.710477125272625 ], [ -122.37970236260378, 37.710464591359482 ], [ -122.37969344425926, 37.710453745999907 ], [ -122.379682806125587, 37.710443271450828 ], [ -122.379672177364711, 37.710433140116493 ], [ -122.379661556600993, 37.710423352293517 ], [ -122.379649225750256, 37.710414277941311 ], [ -122.379636903242243, 37.710405547094624 ], [ -122.379624598093869, 37.71039750270014 ], [ -122.379610573502859, 37.710389829108948 ], [ -122.379596566278167, 37.710382842244165 ], [ -122.379582567720405, 37.710376198055286 ], [ -122.379566867083199, 37.710370611396797 ], [ -122.37955117512638, 37.710365367962943 ], [ -122.379535500182058, 37.71036081098601 ], [ -122.379504186047228, 37.710353069916337 ], [ -122.379490239583589, 37.71034848562612 ], [ -122.37947454763686, 37.710343242182041 ], [ -122.379460575130224, 37.710337627658895 ], [ -122.379444865487201, 37.710331697762861 ], [ -122.379430875636984, 37.710325397057247 ], [ -122.379416868434376, 37.710318409896438 ], [ -122.379404589708813, 37.710311395428178 ], [ -122.379390565156754, 37.710303721810703 ], [ -122.379378268729113, 37.710296020617108 ], [ -122.379364226828372, 37.710287660542946 ], [ -122.379351913051934, 37.710279272893054 ], [ -122.379341310038157, 37.710270170939822 ], [ -122.379328978913207, 37.71026109683396 ], [ -122.379307738212361, 37.710241520839027 ], [ -122.37929710884022, 37.710231389481542 ], [ -122.379288208275057, 37.710221230264345 ], [ -122.379275772698335, 37.710208037437084 ], [ -122.379261548615801, 37.710192469588307 ], [ -122.379247341892423, 37.710177588191321 ], [ -122.379231407401903, 37.710162734359407 ], [ -122.379215489925031, 37.710148566984365 ], [ -122.379199589806944, 37.710135086060731 ], [ -122.379181961577132, 37.710121632706517 ], [ -122.379164350706091, 37.710108865803193 ], [ -122.379145029076398, 37.710096812921883 ], [ -122.379125716135945, 37.710085103538738 ], [ -122.379106411517995, 37.710073736835547 ], [ -122.379081871200697, 37.710060393742431 ], [ -122.379067829377789, 37.710052033632635 ], [ -122.379053770213616, 37.710042987342149 ], [ -122.37904143915452, 37.710033912931294 ], [ -122.379027362638922, 37.710024179909425 ], [ -122.379015013896463, 37.710014419322256 ], [ -122.379004376612457, 37.710003944700169 ], [ -122.378992010871215, 37.709993497651439 ], [ -122.378981364572482, 37.709982679805925 ], [ -122.378970709601532, 37.709971518732665 ], [ -122.378961774072408, 37.709959986863232 ], [ -122.378943885326493, 37.709936236674181 ], [ -122.378934932808065, 37.709924018618082 ], [ -122.378927716720895, 37.709912115676609 ], [ -122.378920448592652, 37.709898153648354 ], [ -122.378914899558595, 37.709883820830655 ], [ -122.378911070654652, 37.709869117207219 ], [ -122.378908969518022, 37.709854386021547 ], [ -122.378906859361621, 37.709839311614516 ], [ -122.37890825816865, 37.709826241422213 ], [ -122.378906182713109, 37.709812539922396 ], [ -122.378898593625834, 37.709785878500057 ], [ -122.37889311469489, 37.709774291484649 ], [ -122.378882494466538, 37.709764503310346 ], [ -122.378852995161623, 37.709760166773435 ], [ -122.378837381455881, 37.709758012285448 ], [ -122.378821663310447, 37.709751739078932 ], [ -122.378811043440322, 37.709741950892628 ], [ -122.378800232043204, 37.709724611725996 ], [ -122.378791192829524, 37.709708961390213 ], [ -122.378764047778134, 37.709660979621674 ], [ -122.378756718985287, 37.709644614994865 ], [ -122.378747662435089, 37.709628277927287 ], [ -122.378733004866319, 37.709595548670947 ], [ -122.378727395179027, 37.709578813530399 ], [ -122.378716350309503, 37.709552206945055 ], [ -122.378707440892299, 37.709541704736111 ], [ -122.378700242233577, 37.709530488508285 ], [ -122.378694754332813, 37.709518558261863 ], [ -122.378690976843657, 37.709505914002577 ], [ -122.378687217401875, 37.709493956460406 ], [ -122.378676596888084, 37.709484167998021 ], [ -122.378662607269405, 37.709477867474909 ], [ -122.378646941924686, 37.709473653320337 ], [ -122.378632917608854, 37.70946597961165 ], [ -122.378620586358139, 37.709456905436006 ], [ -122.37861166863793, 37.709446059987314 ], [ -122.378602715880561, 37.709433841635963 ], [ -122.378598947091831, 37.709421540874992 ], [ -122.378593433533567, 37.709408580662952 ], [ -122.378584489118467, 37.709396705817099 ], [ -122.378573843310107, 37.709385887928249 ], [ -122.378559775644788, 37.709376497801813 ], [ -122.378545751356285, 37.709368824082631 ], [ -122.378531770436666, 37.709362866496143 ], [ -122.37851595734179, 37.709352817747309 ], [ -122.378503583104418, 37.709342027419368 ], [ -122.378491191524603, 37.709330550636324 ], [ -122.378478773584177, 37.709318044176833 ], [ -122.378469795190981, 37.709304796129004 ], [ -122.3784625272081, 37.709290834071133 ], [ -122.378455250555135, 37.709276528785921 ], [ -122.378449692984333, 37.709261852718043 ], [ -122.378447583629097, 37.709246778290328 ], [ -122.37844546525632, 37.709231360641176 ], [ -122.378445040296995, 37.709214542798215 ], [ -122.378446447493673, 37.70920181556793 ], [ -122.378445805707855, 37.709176416778014 ], [ -122.378447212565007, 37.70916368982774 ], [ -122.378446882999313, 37.709150647205846 ], [ -122.378444834357168, 37.709137975366076 ], [ -122.378444192579394, 37.709112576850501 ], [ -122.378433947648276, 37.709049217403461 ], [ -122.378430178900032, 37.709036916361775 ], [ -122.378428130263899, 37.709024244521487 ], [ -122.378424361863509, 37.709011943474067 ], [ -122.378420584791698, 37.708999299199597 ], [ -122.378413047997185, 37.708974697103997 ], [ -122.378407551172273, 37.708962423896338 ], [ -122.378403791789637, 37.708950465794935 ], [ -122.37839829531363, 37.708938192581272 ], [ -122.37839280715248, 37.708926262050589 ], [ -122.378381883920852, 37.708904460882493 ], [ -122.37837105540082, 37.708886435220357 ], [ -122.37835849948857, 37.708868437105671 ], [ -122.378344223820761, 37.708850809781346 ], [ -122.378331693592017, 37.708833841349971 ], [ -122.378315707193991, 37.708816928035041 ], [ -122.378301466234504, 37.708800673613098 ], [ -122.378285505520822, 37.708784789980257 ], [ -122.378262511753192, 37.708764211389848 ], [ -122.378217043865007, 37.708743647822331 ], [ -122.378194318256277, 37.708733709264415 ], [ -122.378169882609328, 37.708724484976287 ], [ -122.378147174694149, 37.708715232583032 ], [ -122.378108766623569, 37.708700393880235 ], [ -122.378089549231376, 37.708692459277344 ], [ -122.378072043283993, 37.708683810925692 ], [ -122.378056239066638, 37.708674105341039 ], [ -122.378040425836915, 37.708664056532776 ], [ -122.378029762871009, 37.708652552139604 ], [ -122.378012273597136, 37.70864459051851 ], [ -122.377996469734569, 37.708634884645676 ], [ -122.37798069155609, 37.708626209006241 ], [ -122.377964922044868, 37.708617876317049 ], [ -122.377947441795172, 37.708610257633161 ], [ -122.377929987553088, 37.708603668352943 ], [ -122.377912541998256, 37.708597422846282 ], [ -122.3778951224499, 37.708592206743326 ], [ -122.377875983484813, 37.708587361142648 ], [ -122.37785687054604, 37.708583545768889 ], [ -122.377837783259466, 37.708580759529298 ], [ -122.377818705342307, 37.708578316777135 ], [ -122.377799653089951, 37.708576903708312 ], [ -122.37778062684724, 37.708576520317315 ], [ -122.377751275466451, 37.708578018636814 ], [ -122.37773407231694, 37.708581382915298 ], [ -122.377718632623825, 37.708586092812681 ], [ -122.377701481846131, 37.708591516991923 ], [ -122.377687821894156, 37.70859825842814 ], [ -122.377674179284952, 37.708605686591305 ], [ -122.377657141190483, 37.70861557244023 ], [ -122.377638365993562, 37.708625142614181 ], [ -122.377621302228775, 37.708633998771425 ], [ -122.37760422910678, 37.708642511984849 ], [ -122.377585411236822, 37.708650365729795 ], [ -122.377547740133352, 37.708664700862904 ], [ -122.377527150486301, 37.708670866291307 ], [ -122.377508279906351, 37.708676660947582 ], [ -122.377487664247838, 37.708681796688104 ], [ -122.377468768348663, 37.708686561646054 ], [ -122.377448126680292, 37.708690667698683 ], [ -122.377432642914897, 37.708693661434552 ], [ -122.377360540414671, 37.708713695820649 ], [ -122.377290182950702, 37.708734389078231 ], [ -122.37721985977025, 37.708756455207251 ], [ -122.377122130511736, 37.708788572304698 ], [ -122.377099960038464, 37.708800600058829 ], [ -122.377076061124569, 37.708812655354983 ], [ -122.377053899648615, 37.708825026321882 ], [ -122.377031746485883, 37.70883774051714 ], [ -122.376984053287501, 37.708865969789912 ], [ -122.376966919370915, 37.708872080046966 ], [ -122.376949742820514, 37.708876474429971 ], [ -122.37693425933503, 37.708879467820125 ], [ -122.376896466671624, 37.708888997566206 ], [ -122.376879298426118, 37.708893734896868 ], [ -122.376863901249948, 37.70890016082808 ], [ -122.376850267175342, 37.708907932397054 ], [ -122.376836667745323, 37.708917076323402 ], [ -122.376824822757797, 37.708927222935884 ], [ -122.376814748831492, 37.70893905787635 ], [ -122.376802877136896, 37.70894817426737 ], [ -122.376791014809598, 37.708957634422354 ], [ -122.376755452394264, 37.708987043484797 ], [ -122.376745335469863, 37.7089971622786 ], [ -122.376733490104598, 37.709007308612669 ], [ -122.376716755060258, 37.709029207270859 ], [ -122.376711908706653, 37.709042675731048 ], [ -122.376710535868128, 37.709056775574453 ], [ -122.37670739161139, 37.709069186817985 ], [ -122.37670251024899, 37.709081282375145 ], [ -122.376694138728411, 37.709092060083194 ], [ -122.376684004106508, 37.709101492422995 ], [ -122.376670361304676, 37.709108920469312 ], [ -122.376656744500607, 37.709117378469699 ], [ -122.376650118041127, 37.709128814824965 ], [ -122.376645245682482, 37.709141253875728 ], [ -122.376642127052065, 37.709154694529374 ], [ -122.376632417316515, 37.709180944983316 ], [ -122.376619233705384, 37.709206564055854 ], [ -122.376610896477217, 37.709218714945962 ], [ -122.376602550229492, 37.709230522339361 ], [ -122.376592476565378, 37.709242356980148 ], [ -122.376582384885396, 37.709253505451606 ], [ -122.376570661100516, 37.709268456941743 ], [ -122.376557330163322, 37.709288241147618 ], [ -122.376542262116743, 37.709307709937875 ], [ -122.376527177411774, 37.709326491711906 ], [ -122.376510355603088, 37.709344958344019 ], [ -122.37649688600834, 37.709359250635856 ], [ -122.376465576896948, 37.709420181699592 ], [ -122.37643425907072, 37.709480769527424 ], [ -122.376389592497333, 37.709560455358435 ], [ -122.376388193936847, 37.709573525235768 ], [ -122.376391962028805, 37.709585826347244 ], [ -122.376399160344661, 37.709597042716048 ], [ -122.376402928439646, 37.70960934382714 ], [ -122.376401512215111, 37.709621727530589 ], [ -122.376396631112286, 37.709633823068486 ], [ -122.376388241812876, 37.709643914305353 ], [ -122.376374607552179, 37.709651685818365 ], [ -122.376360903976007, 37.709656711239063 ], [ -122.376340270633293, 37.709661160049372 ], [ -122.376319628632046, 37.709665265903617 ], [ -122.376300706073636, 37.70966900099792 ], [ -122.376281774504847, 37.709672392867645 ], [ -122.376261106515926, 37.709675469305139 ], [ -122.376242166965341, 37.709678517655895 ], [ -122.376221481647946, 37.709680907632375 ], [ -122.376202524423789, 37.709683269528199 ], [ -122.376181830441382, 37.709685316270686 ], [ -122.376162855890229, 37.709686991706079 ], [ -122.376142153236586, 37.70968869493997 ], [ -122.37612144191985, 37.709690054943209 ], [ -122.376102441041382, 37.709690700967826 ], [ -122.376060983764248, 37.70969204832312 ], [ -122.376041966245552, 37.709692007598299 ], [ -122.376021220279497, 37.709691994675758 ], [ -122.375997000772998, 37.709691350345274 ], [ -122.375979685047099, 37.709690252959057 ], [ -122.375962360653403, 37.709688812068713 ], [ -122.375946764362084, 37.709687343377091 ], [ -122.375929430969819, 37.709685559534897 ], [ -122.375913817710057, 37.709683404653902 ], [ -122.375896475997124, 37.709681277299751 ], [ -122.375880853380082, 37.709678778923745 ], [ -122.375863494692936, 37.709675965105127 ], [ -122.375830496078862, 37.709669966721727 ], [ -122.375814856497584, 37.709666782151935 ], [ -122.375799199596415, 37.709662911125946 ], [ -122.375781814588834, 37.709659067619612 ], [ -122.375766157697925, 37.7096551968638 ], [ -122.375750492134401, 37.709650982329691 ], [ -122.37571914370605, 37.709641867350243 ], [ -122.37570345217442, 37.709636623403227 ], [ -122.375687769305102, 37.709631722681216 ], [ -122.375672068772147, 37.709626135508472 ], [ -122.375626688017107, 37.709609003209117 ], [ -122.375612698633404, 37.70960270232392 ], [ -122.375586491256968, 37.709591788341328 ], [ -122.375574203661955, 37.709584430256932 ], [ -122.375561908094213, 37.709576728658583 ], [ -122.375549603516987, 37.7095686835628 ], [ -122.375539018050162, 37.709560268000558 ], [ -122.375526696511997, 37.709551536717242 ], [ -122.375505491645058, 37.709533332668286 ], [ -122.375494871897089, 37.709523544188201 ], [ -122.375485980256698, 37.709513728189755 ], [ -122.375468179326063, 37.709493410016719 ], [ -122.375457507973294, 37.709481561890541 ], [ -122.375445047568775, 37.709467338970569 ], [ -122.375432604141196, 37.709453802508946 ], [ -122.37541844161953, 37.70944063678396 ], [ -122.375402568663233, 37.709428185022126 ], [ -122.375386712684005, 37.709416419717819 ], [ -122.375370874027013, 37.709405340865558 ], [ -122.375353324589767, 37.709394975980821 ], [ -122.375334064026845, 37.709385325068311 ], [ -122.3753165495785, 37.709376333080996 ], [ -122.375297332662143, 37.709368398292526 ], [ -122.375276404964978, 37.709361177469411 ], [ -122.37525895944836, 37.709354931295927 ], [ -122.375243233032052, 37.709348314384421 ], [ -122.375227498651867, 37.709341354232684 ], [ -122.375196011900385, 37.709326747754233 ], [ -122.375180259522494, 37.709319101152943 ], [ -122.375166235593625, 37.709311427031338 ], [ -122.375150475248759, 37.709303436913331 ], [ -122.375122392776206, 37.709286715752803 ], [ -122.375108342887259, 37.709278011942899 ], [ -122.375092564902388, 37.709269335642915 ], [ -122.375080234468939, 37.709260261365458 ], [ -122.375066167268656, 37.709250870821613 ], [ -122.375046820209405, 37.709237787590567 ], [ -122.374989178668883, 37.709214326304803 ], [ -122.374933247603437, 37.709190151032686 ], [ -122.374831814752426, 37.709144037952456 ], [ -122.374807448289985, 37.709137558790196 ], [ -122.374784792602327, 37.709130365112337 ], [ -122.374762119621366, 37.709122485524915 ], [ -122.374739428974408, 37.709113918935145 ], [ -122.374716721719778, 37.709104666150246 ], [ -122.374694005469692, 37.70909507013927 ], [ -122.374673000016784, 37.709084760438706 ], [ -122.374651977245378, 37.709073763730864 ], [ -122.374630937176775, 37.709062080839466 ], [ -122.374611616554063, 37.709050027212591 ], [ -122.374592286937002, 37.709037630360818 ], [ -122.374572932059905, 37.709024204087896 ], [ -122.37455530492376, 37.709010749764424 ], [ -122.374537660498376, 37.708996609533031 ], [ -122.374519998756611, 37.708981782295425 ], [ -122.374504056473583, 37.708966584874155 ], [ -122.37448810553569, 37.708951043948915 ], [ -122.374473874034791, 37.708935132017182 ], [ -122.374445393756574, 37.708902622243208 ], [ -122.37441087248213, 37.708836215748391 ], [ -122.374376333954515, 37.708769122513743 ], [ -122.374372557576578, 37.708756477834406 ], [ -122.374371908539288, 37.708730736067942 ], [ -122.374373316424055, 37.708718008885349 ], [ -122.374376452056026, 37.708705254481714 ], [ -122.374381325118193, 37.708692815793029 ], [ -122.374386206487088, 37.708680720336844 ], [ -122.374392824593912, 37.708668940606316 ], [ -122.374401188091994, 37.708657819828403 ], [ -122.374411296980753, 37.708647358002679 ], [ -122.374421423174923, 37.708637582630523 ], [ -122.374446851154033, 37.708617606150035 ], [ -122.374463828756802, 37.708605318455056 ], [ -122.374482543776409, 37.708593346196544 ], [ -122.374499530354001, 37.708581401168907 ], [ -122.374518253325192, 37.708569772142909 ], [ -122.374535257553518, 37.708558513833601 ], [ -122.374562508759965, 37.708542285323816 ], [ -122.374586545781455, 37.70853572218347 ], [ -122.374610592144961, 37.708529502254436 ], [ -122.374634646468323, 37.708523625558676 ], [ -122.374658710134312, 37.7085180920742 ], [ -122.374682781760811, 37.708512901823013 ], [ -122.374706862737568, 37.708508055057699 ], [ -122.374732662777703, 37.708502837010279 ], [ -122.374853593864671, 37.708499538871287 ], [ -122.374919053196592, 37.70849025581817 ], [ -122.374984547145971, 37.708482345911996 ], [ -122.375051786483155, 37.708475095187325 ], [ -122.375117314688779, 37.708468558121517 ], [ -122.375182869540055, 37.708463050415169 ], [ -122.375307152797603, 37.708455577784022 ], [ -122.375370892619628, 37.708446665496233 ], [ -122.375436334529738, 37.708436695975841 ], [ -122.375501750445025, 37.708425696737649 ], [ -122.375594677055204, 37.708408765528524 ], [ -122.375613633621171, 37.708406403460977 ], [ -122.375651599745083, 37.708403738663016 ], [ -122.375689617134853, 37.708403133226682 ], [ -122.375705187517212, 37.708403572265745 ], [ -122.375746670063037, 37.708403254716799 ], [ -122.375789863380334, 37.70840222372631 ], [ -122.375831311282568, 37.708400533239043 ], [ -122.375872750535976, 37.708398500059232 ], [ -122.375914172451246, 37.708395779861441 ], [ -122.375955577054981, 37.708392373743997 ], [ -122.376026281020401, 37.708385410092347 ], [ -122.376043553148961, 37.708384791885535 ], [ -122.376081570871463, 37.708384186317168 ], [ -122.376098868977905, 37.708384597508989 ], [ -122.376117894810292, 37.708384980902565 ], [ -122.376136930009963, 37.708385708058287 ], [ -122.376154236772265, 37.708386462194504 ], [ -122.376192341117545, 37.708389288861419 ], [ -122.376221804728033, 37.708392253162685 ], [ -122.376289356067019, 37.708397357901482 ], [ -122.376356881066656, 37.708401432376654 ], [ -122.376424388753932, 37.708404820633483 ], [ -122.37649187011074, 37.708407179175936 ], [ -122.376561053904155, 37.708408480459845 ], [ -122.376628483974059, 37.708408779550602 ], [ -122.376747729724002, 37.70840722314199 ], [ -122.376763213450403, 37.708404229221443 ], [ -122.376778714166832, 37.708401922033076 ], [ -122.376794232219382, 37.708400301571331 ], [ -122.376809767242023, 37.708399367018018 ], [ -122.376832232606262, 37.708399009032 ], [ -122.376847802648896, 37.708399447925551 ], [ -122.376863390008594, 37.708400572721949 ], [ -122.376878994361022, 37.708402384250476 ], [ -122.376901555046999, 37.708405801748796 ], [ -122.376925852133638, 37.708409534936841 ], [ -122.376951859969793, 37.708412554126937 ], [ -122.376977850476194, 37.708414886857213 ], [ -122.37700382331353, 37.708416533407728 ], [ -122.377029779497036, 37.708417492938302 ], [ -122.377055709343722, 37.708417423062144 ], [ -122.377081630523975, 37.70841700995323 ], [ -122.377110981861648, 37.708415512075135 ], [ -122.377140437198108, 37.708418132914076 ], [ -122.377199313216423, 37.708422001936356 ], [ -122.377228733890291, 37.708423249845112 ], [ -122.377258145890991, 37.708424154244938 ], [ -122.377289277310766, 37.70842468814115 ], [ -122.37734804933055, 37.708424438364865 ], [ -122.377379146073082, 37.708423599054704 ], [ -122.37742059395805, 37.708421908282823 ], [ -122.377622787821593, 37.70841902848273 ], [ -122.377643542101708, 37.708419384350222 ], [ -122.377662585641644, 37.708420454217361 ], [ -122.377704146218846, 37.708423225296556 ], [ -122.37772493517798, 37.708424954057655 ], [ -122.37774400438326, 37.708427053598058 ], [ -122.377768215169155, 37.708427354337005 ], [ -122.378000106843373, 37.708436705199382 ], [ -122.37830619390914, 37.708440408092009 ], [ -122.37833376512657, 37.708436878380915 ], [ -122.378363081750692, 37.708434007008506 ], [ -122.378390687645833, 37.708431849917247 ], [ -122.378420038963, 37.708430351712977 ], [ -122.378463249251951, 37.708430005927042 ], [ -122.378492652956893, 37.708430567335327 ], [ -122.378520354243449, 37.708432185435299 ], [ -122.378549800963029, 37.708434462694896 ], [ -122.37857753695242, 37.708437453963704 ], [ -122.378607018369593, 37.70844110411668 ], [ -122.378634797731564, 37.7084458115066 ], [ -122.378662594444492, 37.708451205343785 ], [ -122.378692136242478, 37.70845725806938 ], [ -122.378711232196324, 37.708460387122912 ], [ -122.378730310465116, 37.708462829999633 ], [ -122.378768431943271, 37.708466342017964 ], [ -122.378787467182775, 37.708467068470803 ], [ -122.378808221481876, 37.708467424133083 ], [ -122.37882723903445, 37.708467464405793 ], [ -122.378846230556476, 37.708466474720126 ], [ -122.378866950150183, 37.708465457189874 ], [ -122.378885915308302, 37.708463438097219 ], [ -122.378904872467956, 37.708461075214444 ], [ -122.378923820612812, 37.708458369381802 ], [ -122.378949637703641, 37.708453837413678 ], [ -122.378975437088471, 37.708448618717021 ], [ -122.379001245829258, 37.708443742956021 ], [ -122.379028799657206, 37.708439526353885 ], [ -122.379054625403654, 37.708435337315045 ], [ -122.379080468498444, 37.708431834724301 ], [ -122.379108048344705, 37.708428647784281 ], [ -122.379133899764923, 37.708425488414207 ], [ -122.379161496965466, 37.708422988190016 ], [ -122.379201182372455, 37.708419951449862 ], [ -122.379266840650459, 37.708418560198723 ], [ -122.379358446131135, 37.708417785256238 ], [ -122.379379209096214, 37.708418484044785 ], [ -122.37939999809295, 37.708420212510255 ], [ -122.379420813122252, 37.708422970652627 ], [ -122.379439934791137, 37.708427129548738 ], [ -122.379460810565476, 37.708432290272043 ], [ -122.379479983944307, 37.708438507979139 ], [ -122.379497455985685, 37.70844578347802 ], [ -122.379516681782491, 37.708454060534727 ], [ -122.379532477472964, 37.708463422971533 ], [ -122.379550018242554, 37.708473443739265 ], [ -122.379562332087886, 37.708481831365319 ], [ -122.379578066691991, 37.708488790938986 ], [ -122.379593775956252, 37.708494720818962 ], [ -122.37961118692283, 37.708499593443946 ], [ -122.379628563179082, 37.708503093158996 ], [ -122.379654614203474, 37.708507827892014 ], [ -122.379678955199253, 37.708513276640822 ], [ -122.379705041297058, 37.708519384539102 ], [ -122.379729416669221, 37.708526206190314 ], [ -122.379762215471303, 37.708524308990917 ], [ -122.379786946339379, 37.708545202935731 ], [ -122.379809654745344, 37.70855445473304 ], [ -122.379834099571553, 37.70856402217715 ], [ -122.379856842014789, 37.70857464688364 ], [ -122.379891828930511, 37.70859091336267 ], [ -122.379907538241355, 37.708596843200731 ], [ -122.37992494057292, 37.708601372552437 ], [ -122.379945781718234, 37.708605160557006 ], [ -122.379964929495586, 37.708610348773604 ], [ -122.379985822717444, 37.708616195857346 ], [ -122.380006741975436, 37.708623072343208 ], [ -122.380025941841694, 37.708630319910732 ], [ -122.380045167406379, 37.708638597161041 ], [ -122.380062691631167, 37.708647931936689 ], [ -122.380080223835478, 37.708657609398351 ], [ -122.380097782783579, 37.708668316801365 ], [ -122.380111807100121, 37.70867599033663 ], [ -122.380127559503322, 37.708683636284732 ], [ -122.380141575145288, 37.708690966589629 ], [ -122.380173045244604, 37.708704885568586 ], [ -122.380187043533539, 37.708711529414458 ], [ -122.380202769909488, 37.708718145672115 ], [ -122.380218478926409, 37.708724075474095 ], [ -122.380234196281208, 37.708730348506272 ], [ -122.380249896974746, 37.708735935346311 ], [ -122.380267325396161, 37.708741494053129 ], [ -122.380283025396338, 37.708747080625194 ], [ -122.380298717408834, 37.708752323957377 ], [ -122.380316128481965, 37.708757196477961 ], [ -122.380331802444843, 37.708761753363198 ], [ -122.380349205186434, 37.708766282646621 ], [ -122.380364870817246, 37.708770496295159 ], [ -122.380399640892449, 37.708778181953583 ], [ -122.380415288819378, 37.708781709147395 ], [ -122.380450024177094, 37.708788021883869 ], [ -122.380467383521719, 37.708790835015982 ], [ -122.380502084159204, 37.708795774830023 ], [ -122.380519425451766, 37.708797901511986 ], [ -122.380554091366136, 37.708801468403678 ], [ -122.380571407305098, 37.708802565386677 ], [ -122.38058873123596, 37.708804005604904 ], [ -122.38062334505976, 37.708805513115699 ], [ -122.380640643641911, 37.708805923909587 ], [ -122.380657932836172, 37.708805990936078 ], [ -122.380699406909102, 37.70880532869085 ], [ -122.380744406973307, 37.708807357325021 ], [ -122.38079115213398, 37.708810044256637 ], [ -122.380837914326918, 37.708813417903293 ], [ -122.38088296649677, 37.70881750557038 ], [ -122.380929755096815, 37.708821908855221 ], [ -122.380974841668888, 37.708827369399565 ], [ -122.381019936932205, 37.708833173153394 ], [ -122.38105859232931, 37.708838506457845 ], [ -122.381066777313606, 37.708839635749648 ], [ -122.381111907677337, 37.708846812369579 ], [ -122.381195264075259, 37.708861619200029 ], [ -122.381216079294731, 37.708864377024973 ], [ -122.381238613917688, 37.708866764017813 ], [ -122.381259411768738, 37.708868835381693 ], [ -122.381280183563462, 37.708869877061808 ], [ -122.381300946672837, 37.708870575511568 ], [ -122.381323420152597, 37.708870559906792 ], [ -122.381342429462208, 37.708870256266636 ], [ -122.381404605870372, 37.708867889625047 ], [ -122.381505512521713, 37.708893060219943 ], [ -122.381586019961446, 37.708863618143027 ], [ -122.381762346029831, 37.708863204692321 ], [ -122.381781407118979, 37.708864960346808 ], [ -122.381802213679833, 37.708867375116121 ], [ -122.381821317860044, 37.708870846628407 ], [ -122.381842167859006, 37.708874977249344 ], [ -122.381859579414566, 37.708879849810799 ], [ -122.381875262525128, 37.708884749437118 ], [ -122.381890971365024, 37.708890679021408 ], [ -122.381904969842452, 37.708897322662949 ], [ -122.38191898569994, 37.708904652756104 ], [ -122.381933027626729, 37.708913012527532 ], [ -122.381945359191491, 37.708922086357219 ], [ -122.381957699102628, 37.708931503417737 ], [ -122.381968327967215, 37.70894163482285 ], [ -122.381980685608269, 37.708951738328693 ], [ -122.381993034210964, 37.708961498337537 ], [ -122.382005374141627, 37.708970915667607 ], [ -122.382017705026641, 37.70897998922608 ], [ -122.382045788934036, 37.708996708743506 ], [ -122.382059822203018, 37.709004725273097 ], [ -122.382073838096019, 37.709012055347692 ], [ -122.382089582428222, 37.709019357800898 ], [ -122.382105309038593, 37.709025973804216 ], [ -122.382136727169851, 37.709037833178158 ], [ -122.382152419374435, 37.709043076263157 ], [ -122.382168102538699, 37.709047975850353 ], [ -122.382185504767932, 37.709052504873313 ], [ -122.382201170908758, 37.709056718271931 ], [ -122.382218547417111, 37.709060217604623 ], [ -122.382234178116121, 37.709063058378014 ], [ -122.382251537592452, 37.70906587124707 ], [ -122.382268878998048, 37.70906799767139 ], [ -122.382286203707906, 37.709069437354287 ], [ -122.382303519050552, 37.709070534368244 ], [ -122.382320826380379, 37.709071287592842 ], [ -122.382341580865045, 37.709071642632544 ], [ -122.382365730359041, 37.709069540125881 ], [ -122.382388178510126, 37.709068494352529 ], [ -122.382412363104777, 37.709067764462617 ], [ -122.38243483664391, 37.709067748645822 ], [ -122.382459065046888, 37.709068735148399 ], [ -122.382481573697518, 37.709070092223556 ], [ -122.382505827816885, 37.709072107852691 ], [ -122.382528371242771, 37.709074838099873 ], [ -122.38255092334812, 37.709077911020174 ], [ -122.382573501192169, 37.709082013896044 ], [ -122.382596088421835, 37.709086459983112 ], [ -122.382618692692688, 37.709091592524558 ], [ -122.382641314350906, 37.70909741151484 ], [ -122.382662225311549, 37.709103944851186 ], [ -122.382677899491284, 37.709108501423991 ], [ -122.382700504120081, 37.709113633944185 ], [ -122.382724827799791, 37.709118395340859 ], [ -122.382747414358349, 37.709122841409943 ], [ -122.382771729703904, 37.70912725983942 ], [ -122.382794299234817, 37.7091310197154 ], [ -122.382818588161342, 37.709134408460983 ], [ -122.382842868390057, 37.709137453700393 ], [ -122.38286542018875, 37.709140526840578 ], [ -122.382889683387347, 37.709142885886266 ], [ -122.382938191710934, 37.709146917520584 ], [ -122.38296070039398, 37.709148274504173 ], [ -122.382986665264852, 37.70914957622869 ], [ -122.383007411087661, 37.709149588199111 ], [ -122.383029884990066, 37.709149571988654 ], [ -122.383050648193986, 37.709150270129996 ], [ -122.383073139146191, 37.709150940644498 ], [ -122.383093919739352, 37.709152325231301 ], [ -122.383116428772482, 37.709153682179597 ], [ -122.383158007351824, 37.709157137787358 ], [ -122.383180542125842, 37.709159524408562 ], [ -122.38322217288038, 37.709165039352904 ], [ -122.383244725048755, 37.709168112414922 ], [ -122.383265557820081, 37.709171556330674 ], [ -122.38330031099926, 37.709178554953908 ], [ -122.383319389589161, 37.709180996537953 ], [ -122.383354090598218, 37.709185935511563 ], [ -122.383373151814638, 37.709187691182962 ], [ -122.383407818040695, 37.709191256960054 ], [ -122.383426861522594, 37.709192326175256 ], [ -122.38344417758799, 37.709193422461318 ], [ -122.383463212713679, 37.709194148164016 ], [ -122.383497809392168, 37.709194968651609 ], [ -122.383516826773814, 37.709195007623549 ], [ -122.38353410806306, 37.709194731264901 ], [ -122.38355310841149, 37.709194084321595 ], [ -122.383570380651364, 37.709193464462061 ], [ -122.383603179656703, 37.709191566467979 ], [ -122.383625618791854, 37.709190177512419 ], [ -122.383660189372748, 37.709189967998213 ], [ -122.383684400085329, 37.709190267526687 ], [ -122.383706900098034, 37.709191281141187 ], [ -122.383729426202848, 37.709193324430878 ], [ -122.383751960667553, 37.709195711222868 ], [ -122.383774513205751, 37.709198783903318 ], [ -122.383797074104677, 37.709202200086082 ], [ -122.383819661098414, 37.709206645943979 ], [ -122.383842256453889, 37.709211435304148 ], [ -122.383863141444834, 37.709216938198551 ], [ -122.383887517772791, 37.709223758983043 ], [ -122.383918823190285, 37.709231155662387 ], [ -122.383950120607025, 37.709238209095858 ], [ -122.384012679973182, 37.709250943042996 ], [ -122.384043941921391, 37.709256623556676 ], [ -122.38407519587382, 37.709261961099109 ], [ -122.384137668283756, 37.709271262440524 ], [ -122.384168886754026, 37.709275226788684 ], [ -122.384201816616198, 37.709278477019389 ], [ -122.384233018039538, 37.70928175489211 ], [ -122.384265929816848, 37.709284318663009 ], [ -122.384297105498632, 37.709286567108421 ], [ -122.384329991519081, 37.709288100901972 ], [ -122.384361149451607, 37.709289662608477 ], [ -122.384394018088159, 37.7092905104804 ], [ -122.384452807809836, 37.709290943068623 ], [ -122.384475299515429, 37.709291613578934 ], [ -122.384496080167082, 37.709292997643757 ], [ -122.384516886928907, 37.709295411658836 ], [ -122.384534272309892, 37.7092992541508 ], [ -122.384549981442476, 37.709305183105528 ], [ -122.384600688269401, 37.709327720626654 ], [ -122.384651368684075, 37.709349228726715 ], [ -122.384702031710773, 37.709370049802885 ], [ -122.384754405467419, 37.709390157025624 ], [ -122.384806761855017, 37.709409578046774 ], [ -122.384859100858179, 37.709428312317186 ], [ -122.384913150236486, 37.709446332461404 ], [ -122.384967174216499, 37.709463322615903 ], [ -122.385028162503517, 37.709482261754836 ], [ -122.385061327139894, 37.709494778863174 ], [ -122.385077071522119, 37.709502080922022 ], [ -122.385092833321409, 37.709510069706113 ], [ -122.385106884415976, 37.70951877205021 ], [ -122.385135021443745, 37.709537550188017 ], [ -122.38516131723388, 37.709551894035449 ], [ -122.385185875889178, 37.70956592259045 ], [ -122.385212163333733, 37.709579922920014 ], [ -122.385240161144154, 37.709593209410606 ], [ -122.385266422163951, 37.7096061806036 ], [ -122.385294402578083, 37.709618780353921 ], [ -122.38532064620027, 37.709631064807468 ], [ -122.385376554860102, 37.709654205466649 ], [ -122.385427183484424, 37.709673653599111 ], [ -122.385474486504066, 37.709698305432774 ], [ -122.385520087565695, 37.709724014590961 ], [ -122.385565697364569, 37.70975006695771 ], [ -122.385611324607254, 37.709776805759368 ], [ -122.385656969295042, 37.709804230995893 ], [ -122.385720426192663, 37.709852316346286 ], [ -122.38574142470155, 37.709862281398394 ], [ -122.38576260604151, 37.709879453650188 ], [ -122.385783769984741, 37.709895939720084 ], [ -122.385806662054534, 37.709912398391943 ], [ -122.385829545420535, 37.709928513558424 ], [ -122.385852419736807, 37.709944285225092 ], [ -122.385908633553967, 37.709979438551144 ], [ -122.385924326223261, 37.709984681132887 ], [ -122.385940009841704, 37.709989580491779 ], [ -122.385955658638977, 37.709993107217954 ], [ -122.385971289669953, 37.709995947220456 ], [ -122.38598863209009, 37.709998073085792 ], [ -122.386004211215408, 37.709998853446052 ], [ -122.386023237864435, 37.70999923550869 ], [ -122.386043957793987, 37.709998216990876 ], [ -122.386064686430998, 37.709997541695692 ], [ -122.386085397651371, 37.709996179944298 ], [ -122.386126802680138, 37.709992770252597 ], [ -122.386147487765797, 37.709990378536858 ], [ -122.386169900968881, 37.70998795941798 ], [ -122.386190577350149, 37.709985224742994 ], [ -122.386231912690491, 37.709979068929542 ], [ -122.386250834828786, 37.709975332240148 ], [ -122.386271485083554, 37.709971568146692 ], [ -122.386292126613185, 37.709967460274193 ], [ -122.386312759445275, 37.709963009720973 ], [ -122.386333383551687, 37.709958215388703 ], [ -122.386352270849088, 37.709953106052509 ], [ -122.386372886247685, 37.709947968761632 ], [ -122.386393492926942, 37.709942487966288 ], [ -122.386412353741648, 37.709936348947188 ], [ -122.386432951711981, 37.709930525193258 ], [ -122.386451804494854, 37.709924042655906 ], [ -122.386489491932494, 37.709910390856137 ], [ -122.386508326593912, 37.709903221868281 ], [ -122.386545979522182, 37.709888197414408 ], [ -122.386564797097478, 37.709880341959447 ], [ -122.386583605612742, 37.709872143280762 ], [ -122.386602406104558, 37.709863601361754 ], [ -122.386624601143666, 37.709852601505517 ], [ -122.386648585957673, 37.709843976811243 ], [ -122.386694774483402, 37.709824694919647 ], [ -122.386716987264919, 37.709814381492933 ], [ -122.386739173906818, 37.709803038383235 ], [ -122.386759623723719, 37.709791379725885 ], [ -122.386781783879641, 37.709779007209534 ], [ -122.386809006718778, 37.709761745916552 ], [ -122.386819140491724, 37.709752312701141 ], [ -122.386825765435574, 37.70974087550043 ], [ -122.386828898974457, 37.709728120767494 ], [ -122.386826848894813, 37.709715449349289 ], [ -122.386819640282908, 37.709703890391729 ], [ -122.386817598557442, 37.709691561655937 ], [ -122.386819004336886, 37.70967883460122 ], [ -122.386823901170089, 37.709667425084099 ], [ -122.386834025525516, 37.709657648652097 ], [ -122.386847676124134, 37.709650562655604 ], [ -122.386868282692944, 37.709645082051537 ], [ -122.386895766825091, 37.709638117800075 ], [ -122.386923224816215, 37.709630123863455 ], [ -122.386948937619863, 37.709621471147983 ], [ -122.386974623942265, 37.70961178902828 ], [ -122.387000301531728, 37.709601763127544 ], [ -122.387025961350744, 37.709591051048854 ], [ -122.387051595722909, 37.709579309549376 ], [ -122.387075492913056, 37.709567251961815 ], [ -122.387099363971075, 37.709554165239872 ], [ -122.38712149784719, 37.709540762431281 ], [ -122.387143614994557, 37.709526673704133 ], [ -122.387172522192103, 37.709507668506873 ], [ -122.387191252472988, 37.709496380689082 ], [ -122.38723213436316, 37.70947237676026 ], [ -122.387250838140105, 37.709460059259776 ], [ -122.387271261641757, 37.709447370834297 ], [ -122.387308635352881, 37.709421363170897 ], [ -122.38732731330154, 37.709408015699388 ], [ -122.387344254439029, 37.70939435296458 ], [ -122.387362914960519, 37.709380319584099 ], [ -122.387379847018508, 37.709366313348852 ], [ -122.387396771054185, 37.709351964148425 ], [ -122.387413685684905, 37.70933727200498 ], [ -122.387447498167859, 37.709307200423432 ], [ -122.387477837364472, 37.709276498309869 ], [ -122.387504772282938, 37.709247910664018 ], [ -122.387518335595743, 37.709237392327147 ], [ -122.387531890183013, 37.709226530487904 ], [ -122.387555525398113, 37.709204176295842 ], [ -122.387565597996186, 37.70919234043189 ], [ -122.387577389623033, 37.709180133651593 ], [ -122.387587444438822, 37.709167611338806 ], [ -122.387595770813661, 37.709155116725697 ], [ -122.387605817253487, 37.709142251179543 ], [ -122.387614126544804, 37.709129070381714 ], [ -122.387625726420353, 37.709109312619674 ], [ -122.38763581573356, 37.709098163213035 ], [ -122.387647651248827, 37.709087672276503 ], [ -122.387659495137399, 37.7090775248451 ], [ -122.387671356453438, 37.709068063864919 ], [ -122.387684954578489, 37.709058918688022 ], [ -122.387698570117223, 37.709050459412794 ], [ -122.387713905033536, 37.709041629487615 ], [ -122.387727520579759, 37.70903317075819 ], [ -122.387741127400233, 37.709024368526379 ], [ -122.387754716779028, 37.70901487956597 ], [ -122.38776828872976, 37.709004704426171 ], [ -122.387780123872801, 37.708994213756277 ], [ -122.387790213492849, 37.708983064330674 ], [ -122.387800294400918, 37.708971571952546 ], [ -122.387820421322658, 37.708947213465045 ], [ -122.387827011176498, 37.708934403301733 ], [ -122.387835268108802, 37.708919163130588 ], [ -122.387845322493249, 37.708906640526244 ], [ -122.387855368863313, 37.708893775232887 ], [ -122.387863686784897, 37.708880937093966 ], [ -122.387870259192908, 37.708867440475665 ], [ -122.387876822882276, 37.708853600630782 ], [ -122.387881684640561, 37.70884081843792 ], [ -122.387890037417037, 37.708829353201914 ], [ -122.387898407291956, 37.708818574972398 ], [ -122.387908532074746, 37.708808798161094 ], [ -122.387920428182127, 37.708800710060288 ], [ -122.387934078526868, 37.708793624211502 ], [ -122.387949491811682, 37.708787883291222 ], [ -122.387963124704001, 37.708780110437473 ], [ -122.387974994657682, 37.708770992927079 ], [ -122.387986838098541, 37.708760845193225 ], [ -122.38799691929168, 37.70874935279231 ], [ -122.388003535248131, 37.708737572297345 ], [ -122.388025477290896, 37.708716618335004 ], [ -122.388016549225114, 37.708705430376419 ], [ -122.388024919406419, 37.708694651857606 ], [ -122.388035061251031, 37.708685561767766 ], [ -122.388048711556735, 37.70867847563099 ], [ -122.388064142243053, 37.708673421147999 ], [ -122.388079642671812, 37.708671112747226 ], [ -122.388100414499817, 37.708672152950157 ], [ -122.388116071494025, 37.708676022621809 ], [ -122.388131781471785, 37.708681951088387 ], [ -122.388159762030639, 37.708694550434529 ], [ -122.388173769748903, 37.708701536557498 ], [ -122.388201802628828, 37.708716195250794 ], [ -122.388214108081286, 37.708724238756957 ], [ -122.388226422945536, 37.708732625476948 ], [ -122.388238746184868, 37.708741355427378 ], [ -122.388251078145075, 37.708750428602698 ], [ -122.388266944735889, 37.708762535402698 ], [ -122.388279294146272, 37.708772295301998 ], [ -122.388293362910787, 37.708781683719387 ], [ -122.38830914233246, 37.708790358251797 ], [ -122.388323176244768, 37.708798374310007 ], [ -122.388338920792876, 37.708805675659121 ], [ -122.388354647568491, 37.708812290833862 ], [ -122.388372085684821, 37.70881819183667 ], [ -122.388387786655741, 37.70882377732292 ], [ -122.388405189556963, 37.70882830542164 ], [ -122.388422575029637, 37.70883214734004 ], [ -122.388439943052433, 37.70883530225435 ], [ -122.388457293652749, 37.708837771262928 ], [ -122.388476354886436, 37.708839525559505 ], [ -122.388493670259848, 37.708840621388994 ], [ -122.388512688251666, 37.708840659817909 ], [ -122.38852998653293, 37.708841069184182 ], [ -122.388547319691853, 37.708842851452602 ], [ -122.388564687729385, 37.708846006623098 ], [ -122.388580362562081, 37.708850562405146 ], [ -122.388611781644997, 37.70886241977783 ], [ -122.38865901579085, 37.708884324521037 ], [ -122.38867304103168, 37.70889199703673 ], [ -122.388704565133736, 37.708907973092835 ], [ -122.388718607824089, 37.708916332055367 ], [ -122.38873438732395, 37.708925006530642 ], [ -122.388748438741189, 37.708933708715719 ], [ -122.388776559026368, 37.708951799533132 ], [ -122.388804696766371, 37.708970576796062 ], [ -122.388818774369312, 37.708980308925696 ], [ -122.38883113260205, 37.708990411718339 ], [ -122.38884521891903, 37.709000486521759 ], [ -122.388857576813109, 37.70901058931716 ], [ -122.388869944129638, 37.709021035600863 ], [ -122.388894695509904, 37.709042614078506 ], [ -122.388910640750538, 37.709057809551815 ], [ -122.388972423353252, 37.709107980227031 ], [ -122.389081727973931, 37.709191381909299 ], [ -122.389101024813158, 37.709202403484106 ], [ -122.389122041028727, 37.709213054112247 ], [ -122.389141320435243, 37.709223389228221 ], [ -122.389162319218002, 37.709233353396939 ], [ -122.389183309291042, 37.709242974610376 ], [ -122.389223526102853, 37.709260871022344 ], [ -122.389313124685856, 37.70931711930168 ], [ -122.389481987883741, 37.709431155180901 ], [ -122.389492653071912, 37.709442658255767 ], [ -122.389505019851313, 37.709453104208613 ], [ -122.389517378601511, 37.709463206922976 ], [ -122.38952971956148, 37.709472623189463 ], [ -122.389543753727182, 37.709480638826818 ], [ -122.389557892592236, 37.709492773449732 ], [ -122.389586187763769, 37.709517728318737 ], [ -122.389598615991517, 37.709530577112993 ], [ -122.389612772309832, 37.709543397632146 ], [ -122.389625208918304, 37.709556589380568 ], [ -122.389644750565139, 37.709577221189377 ], [ -122.389655398357277, 37.709588038071757 ], [ -122.389667747741427, 37.709597797829758 ], [ -122.389681790670352, 37.709606156676571 ], [ -122.389697518418998, 37.709612771385522 ], [ -122.389713219657295, 37.709618356694392 ], [ -122.389730605029726, 37.709622198150228 ], [ -122.389747947470156, 37.709624323462215 ], [ -122.389765245594901, 37.709624732652614 ], [ -122.389782500792251, 37.709623425973788 ], [ -122.389799720389789, 37.709620746124841 ], [ -122.389816888318393, 37.709616006631549 ], [ -122.389835749113217, 37.709609867058475 ], [ -122.389863163150096, 37.709600156306408 ], [ -122.389890559721536, 37.709589758821316 ], [ -122.389917938840725, 37.709578675152393 ], [ -122.389945300499932, 37.709566905025042 ], [ -122.389970907526418, 37.709554132948654 ], [ -122.389996506509206, 37.709541017629583 ], [ -122.390020350865882, 37.709526900637606 ], [ -122.39004417774683, 37.709512096639571 ], [ -122.390067995898818, 37.709496949685239 ], [ -122.390091787861351, 37.709480773048014 ], [ -122.390113842650052, 37.709464281192048 ], [ -122.390139232126714, 37.709442928142842 ], [ -122.390159716218918, 37.709432641798621 ], [ -122.390178454754334, 37.709421696731738 ], [ -122.3901989126541, 37.709410380702636 ], [ -122.39021589655448, 37.70939843369046 ], [ -122.390234600516706, 37.709386115979576 ], [ -122.390251558562568, 37.709373139004065 ], [ -122.39026850788207, 37.709359819074663 ], [ -122.390285439740609, 37.709345812690756 ], [ -122.390300643497767, 37.709331834039105 ], [ -122.390315821066409, 37.709316825707447 ], [ -122.390329270534636, 37.709301845108946 ], [ -122.390344421562602, 37.709285807101061 ], [ -122.390361327552213, 37.709270771022531 ], [ -122.39037995255471, 37.709255363985214 ], [ -122.39039175211181, 37.709243500154884 ], [ -122.390403604724284, 37.709233695668296 ], [ -122.390415483172134, 37.70922492086406 ], [ -122.390429115893767, 37.709217148000036 ], [ -122.390442766075893, 37.709210061861029 ], [ -122.390456442426085, 37.709204004849276 ], [ -122.390471873057805, 37.709198950051309 ], [ -122.390487338262247, 37.709195268435437 ], [ -122.390504549691016, 37.709192244971604 ], [ -122.39052006760221, 37.7091906224273 ], [ -122.390549453543258, 37.709190494110466 ], [ -122.390566778448914, 37.709191932850082 ], [ -122.390582401104382, 37.709194429009507 ], [ -122.390598058337744, 37.709198298350977 ], [ -122.390613742436898, 37.709203196808069 ], [ -122.390626048230899, 37.70921124006167 ], [ -122.390650826717163, 37.709233847842071 ], [ -122.390663202699358, 37.709244636894077 ], [ -122.390679017412822, 37.709254684012365 ], [ -122.390694814671889, 37.709264044676523 ], [ -122.390710585401109, 37.709272375666167 ], [ -122.39072806745979, 37.70927999244968 ], [ -122.390745513912535, 37.709286236337753 ], [ -122.39076120677656, 37.709291478275702 ], [ -122.390820887340325, 37.709326917004176 ], [ -122.390880550507589, 37.709361669524867 ], [ -122.390955975777842, 37.709404409471233 ], [ -122.390984166616704, 37.709425245293367 ], [ -122.391043969973325, 37.709465489065806 ], [ -122.391073854047875, 37.709484924766009 ], [ -122.39110372905418, 37.709504016963635 ], [ -122.391135324127831, 37.70952273789505 ], [ -122.391198478718835, 37.709558807388859 ], [ -122.391230038228016, 37.709576155676679 ], [ -122.391263317811934, 37.70959313297017 ], [ -122.391294851500007, 37.709609451557228 ], [ -122.391328104225451, 37.709625399165667 ], [ -122.391384067146049, 37.709650596329034 ], [ -122.391403364798364, 37.709661617794914 ], [ -122.39142438147357, 37.709672268011516 ], [ -122.391443670051999, 37.70968294597585 ], [ -122.391473405843797, 37.709696546184524 ], [ -122.391490739272712, 37.709698328019577 ], [ -122.391506239801757, 37.709696018893702 ], [ -122.39153878556094, 37.709684165139144 ], [ -122.391567892571629, 37.709673053334001 ], [ -122.391595262737894, 37.70966162604946 ], [ -122.391624352264017, 37.709649827778549 ], [ -122.391716621681155, 37.709607142187203 ], [ -122.39187696505121, 37.709454172098226 ], [ -122.392088496086032, 37.709274627293134 ], [ -122.392100330316836, 37.709264136195202 ], [ -122.392112147763783, 37.709252958633222 ], [ -122.392122219297448, 37.709241122385279 ], [ -122.392130554000019, 37.709228970672129 ], [ -122.3921406164459, 37.70921679120223 ], [ -122.392147205586284, 37.70920398107237 ], [ -122.392155514411016, 37.709190799399607 ], [ -122.392160357630971, 37.709177330310062 ], [ -122.392166946757442, 37.709164519904434 ], [ -122.392190188120423, 37.709126719606594 ], [ -122.392215174675471, 37.709089577998483 ], [ -122.392281983011671, 37.708997512609393 ], [ -122.392295589286405, 37.70898870985144 ], [ -122.392309213031425, 37.708980593543679 ], [ -122.39232284551727, 37.708972820734765 ], [ -122.392336495460057, 37.708965733826894 ], [ -122.3923518734928, 37.7089586194272 ], [ -122.392365540911214, 37.708952219242256 ], [ -122.392380936412536, 37.708945791290532 ], [ -122.392396349039899, 37.708940049794052 ], [ -122.392411779830567, 37.708934994736204 ], [ -122.39242892962109, 37.708929568690095 ], [ -122.392432645117751, 37.708928100915109 ], [ -122.392656793824756, 37.709119608424643 ], [ -122.391332097355985, 37.710140738310088 ], [ -122.390887824662656, 37.71036523380814 ], [ -122.390835880106437, 37.711169771421694 ], [ -122.392609027560681, 37.711272141276474 ], [ -122.393916838957196, 37.711294747317396 ], [ -122.394130547970391, 37.711308547209811 ], [ -122.394301164504668, 37.711283892996612 ], [ -122.394415018271332, 37.711243467479711 ], [ -122.394533636579439, 37.711181739957787 ], [ -122.394789228222862, 37.711029491236808 ], [ -122.395699524288574, 37.710911289111898 ], [ -122.395804130724926, 37.711357784012904 ], [ -122.395984840101519, 37.711902635542479 ], [ -122.396209567629995, 37.712429108802013 ], [ -122.396615767431044, 37.713094016747043 ], [ -122.396615774145118, 37.713094103716635 ], [ -122.396885665233555, 37.713468431896139 ], [ -122.397834560573045, 37.714650610374164 ], [ -122.398259710629063, 37.715141974251068 ], [ -122.398471400839512, 37.715455433381891 ], [ -122.398552555430172, 37.715443409305585 ], [ -122.398990734294756, 37.715338865287599 ], [ -122.398990733617495, 37.715338865847912 ], [ -122.398991048786399, 37.715338790720075 ], [ -122.398991043085772, 37.715338797679301 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":16,\"ZIP_CODE\":94110,\"ID\":94110},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.4217053213399, 37.731806501069016 ], [ -122.422820482616018, 37.731641033131751 ], [ -122.423920024119482, 37.731552895324626 ], [ -122.424553925961277, 37.731549131439351 ], [ -122.424645313557747, 37.731548588455745 ], [ -122.424987445865384, 37.731559842008934 ], [ -122.425563267495079, 37.731578779884757 ], [ -122.426103813784607, 37.731651546856703 ], [ -122.426095455082049, 37.731670597839134 ], [ -122.425919712835267, 37.7318157721811 ], [ -122.425781691782262, 37.731929785903453 ], [ -122.425500227991904, 37.7320527573848 ], [ -122.42519868259302, 37.732129150959032 ], [ -122.425046994836066, 37.732129947854176 ], [ -122.424277944077048, 37.732133983506472 ], [ -122.423769382874056, 37.732162707171447 ], [ -122.423486414351174, 37.732261254158729 ], [ -122.423194763566187, 37.732223135593344 ], [ -122.422597636657429, 37.732308431174374 ], [ -122.421964308435761, 37.732433222436505 ], [ -122.421769540807844, 37.732442879558889 ], [ -122.421628644882077, 37.732486581446111 ], [ -122.421573075628018, 37.732579492315836 ], [ -122.421610219449562, 37.732670894611623 ], [ -122.421740792022078, 37.732743282983918 ], [ -122.421779146191113, 37.732885343728064 ], [ -122.42184863663951, 37.732986304256819 ], [ -122.42202732889227, 37.733140443779334 ], [ -122.422262570568407, 37.733475728240066 ], [ -122.42233700854446, 37.733668492264307 ], [ -122.422378321374794, 37.733842795338198 ], [ -122.42251406401526, 37.734049477037892 ], [ -122.422640968014662, 37.73418217013328 ], [ -122.422876476168085, 37.734369596551609 ], [ -122.422737437389159, 37.734490301194434 ], [ -122.422547222688962, 37.734619826084632 ], [ -122.422284286448345, 37.734779711262206 ], [ -122.422205425171413, 37.73485685013258 ], [ -122.422146910860818, 37.734962831393027 ], [ -122.422120806983799, 37.735152847768823 ], [ -122.42260172446116, 37.735176855221177 ], [ -122.422687909096169, 37.735204969342369 ], [ -122.424702562314437, 37.735369870957172 ], [ -122.424930534158477, 37.735266285585048 ], [ -122.427103368742522, 37.735457096504888 ], [ -122.427838803054428, 37.734693898279502 ], [ -122.428436980709748, 37.734950949626743 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.42783624298761, 37.735347761815902 ], [ -122.427738169173168, 37.735412543347451 ], [ -122.42729729676671, 37.735729149898575 ], [ -122.426544994076067, 37.736269396547655 ], [ -122.426500890836181, 37.73630106743056 ], [ -122.425847678890904, 37.736860666965818 ], [ -122.425439441891498, 37.737210392933726 ], [ -122.425110714349969, 37.737550673136937 ], [ -122.424904804743889, 37.737763817504508 ], [ -122.424711669489241, 37.738076607801148 ], [ -122.424696469855888, 37.738101223886673 ], [ -122.424531564221411, 37.738368292265967 ], [ -122.424284283357963, 37.738949691036076 ], [ -122.424063599996956, 37.739784297520274 ], [ -122.424147154179678, 37.739869229183775 ], [ -122.424147154546787, 37.739869230001858 ], [ -122.42414746787631, 37.739869414698418 ], [ -122.424258152196032, 37.739934707389949 ], [ -122.42413709700044, 37.740335654620864 ], [ -122.42417135664968, 37.740713439585669 ], [ -122.424301394893192, 37.742119600447609 ], [ -122.424233478510288, 37.742235956906946 ], [ -122.424233472639315, 37.742235957277522 ], [ -122.424233272287097, 37.742236301993223 ], [ -122.424233277120777, 37.742236301639601 ], [ -122.424309369085989, 37.7430347552692 ], [ -122.424384862816794, 37.743838216035606 ], [ -122.424459208282684, 37.744635998834639 ], [ -122.424538964982034, 37.745435717069675 ], [ -122.424602793343411, 37.746241400149849 ], [ -122.424689793548367, 37.747033402901096 ], [ -122.424768280884535, 37.747830304166378 ], [ -122.424841979744244, 37.748600757493776 ], [ -122.424845018024243, 37.748632519532975 ], [ -122.424845024283727, 37.748632587554638 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.424999224133984, 37.75023271753119 ], [ -122.424999224155329, 37.750232718354916 ], [ -122.425075700414453, 37.751033151258099 ], [ -122.425122809405835, 37.751528900123922 ], [ -122.4251525416649, 37.751841785070944 ], [ -122.425152541686245, 37.751841785894676 ], [ -122.425302616566313, 37.753431589741957 ], [ -122.425302616947704, 37.753431591109177 ], [ -122.425425666412067, 37.754703378159874 ], [ -122.425457179889037, 37.755029078716305 ], [ -122.425491892915787, 37.755387845874338 ], [ -122.425612216172169, 37.756631383038098 ], [ -122.425645855643012, 37.756979041458848 ], [ -122.42568945931761, 37.757429671011749 ], [ -122.425689459367419, 37.757429672933782 ], [ -122.425766807341631, 37.758226828700685 ], [ -122.425843422635126, 37.759029397589842 ], [ -122.425900090784694, 37.759619242549249 ], [ -122.425920302092379, 37.759829613405913 ], [ -122.425985721165645, 37.760498692937027 ], [ -122.426076501462219, 37.761427133668562 ], [ -122.426076501815189, 37.761427133937481 ], [ -122.426145984060597, 37.762160358437526 ], [ -122.426149283375466, 37.762227539381186 ], [ -122.426191463523168, 37.762538363286502 ], [ -122.426204067323269, 37.762631239367813 ], [ -122.426224290129298, 37.762962298008539 ], [ -122.426228478902843, 37.763030870557735 ], [ -122.426317477898522, 37.763970002371671 ], [ -122.426381708399177, 37.764645687871273 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426381062758907, 37.764645729188807 ], [ -122.426381062356072, 37.764645726997848 ], [ -122.424574375550648, 37.764754942537614 ], [ -122.424102683503691, 37.764783452170008 ], [ -122.423112421134832, 37.764843298770074 ], [ -122.42285341443899, 37.764858950163855 ], [ -122.421886445840627, 37.764917378530825 ], [ -122.421381475659516, 37.764947887797014 ], [ -122.421239593012103, 37.764956459387825 ], [ -122.420901270224292, 37.764976898858535 ], [ -122.420580607922048, 37.764996270018614 ], [ -122.420482492120144, 37.765002197009743 ], [ -122.419668972681833, 37.765051337054757 ], [ -122.418698425401715, 37.765109955145007 ], [ -122.418579138459435, 37.765117159398407 ], [ -122.41748659577631, 37.765183134636899 ], [ -122.416387522888556, 37.765249494040667 ], [ -122.415308388038014, 37.765314639624691 ], [ -122.413105243007919, 37.765447608576679 ], [ -122.412416426589019, 37.765489809283601 ], [ -122.411455262151748, 37.765547605227276 ], [ -122.410486689274492, 37.765605838622655 ], [ -122.408544242939584, 37.765722599679115 ], [ -122.407534229395537, 37.765783299286987 ], [ -122.407419737953433, 37.764487829924626 ], [ -122.406859230067056, 37.764521681123377 ], [ -122.406429154577822, 37.764547653499882 ], [ -122.406429152848446, 37.764547653527842 ], [ -122.406005237363416, 37.764573252226405 ], [ -122.405463420216606, 37.764605967981389 ], [ -122.405089603673972, 37.764628538361961 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.405059622300016, 37.764310557738277 ], [ -122.405104592203571, 37.763852244172895 ], [ -122.405203088479681, 37.763478066930332 ], [ -122.405258413683796, 37.763323129887908 ], [ -122.40532469627189, 37.76313750682079 ], [ -122.405384490436404, 37.76297005284507 ], [ -122.405653838302513, 37.762432552128487 ], [ -122.405925510731336, 37.762011914887523 ], [ -122.406021608630979, 37.761863123291867 ], [ -122.406147061407296, 37.7616371808829 ], [ -122.4062826701159, 37.761392945250627 ], [ -122.406479479731502, 37.760730932047331 ], [ -122.406492697683589, 37.760686469836571 ], [ -122.406492391998796, 37.760255279712112 ], [ -122.406389357641601, 37.759804405761585 ], [ -122.406261953936678, 37.75945211819198 ], [ -122.406247868764623, 37.759429354319998 ], [ -122.406103779268008, 37.759196479511928 ], [ -122.405903135660481, 37.758934413706449 ], [ -122.405545221503274, 37.758536929330724 ], [ -122.405048169961901, 37.758189448369819 ], [ -122.404835145723425, 37.7580339263735 ], [ -122.40472107938929, 37.757950649275713 ], [ -122.404441730762329, 37.757748221369937 ], [ -122.404168659910013, 37.75752977302043 ], [ -122.403774405770974, 37.757138490742172 ], [ -122.403774084903432, 37.757138026202746 ], [ -122.403689260967766, 37.757015002579273 ], [ -122.403543859417937, 37.75680412126377 ], [ -122.403395700993769, 37.756471262485057 ], [ -122.403305530792395, 37.756165877853867 ], [ -122.403245024221292, 37.755760575231484 ], [ -122.403244936870763, 37.755759623462495 ], [ -122.403127143734139, 37.75447773377249 ], [ -122.403011715723153, 37.753221541143297 ], [ -122.40301031565609, 37.753201877425177 ], [ -122.402978169178084, 37.75275045704997 ], [ -122.403006670294943, 37.752445905732571 ], [ -122.403060331379024, 37.751915861053995 ], [ -122.403072643662085, 37.751794247946641 ], [ -122.403175027775376, 37.751281093014263 ], [ -122.403214572776321, 37.751164169122518 ], [ -122.403378511001179, 37.750679445903444 ], [ -122.403514246938869, 37.750281563827826 ], [ -122.403735650894049, 37.749583872707518 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403826366530197, 37.749298003881435 ], [ -122.403865672790147, 37.749174139069758 ], [ -122.403990987296382, 37.74878066971192 ], [ -122.404051662308461, 37.74859015799958 ], [ -122.404103617259366, 37.748427025595028 ], [ -122.404104297626958, 37.748424888760844 ], [ -122.404105247604519, 37.748421906188959 ], [ -122.404249303912664, 37.747969580021021 ], [ -122.404390053649081, 37.747536214374357 ], [ -122.404496510435962, 37.747208430674682 ], [ -122.40472494664624, 37.746505061967419 ], [ -122.40491012189095, 37.745826558312224 ], [ -122.405051159825348, 37.745369647465395 ], [ -122.405179630488661, 37.744981134097749 ], [ -122.405202201087945, 37.744938889063008 ], [ -122.405444172050537, 37.744486004341084 ], [ -122.405475695814175, 37.744427001636382 ], [ -122.405747110957691, 37.744028598841339 ], [ -122.405778655290121, 37.743989539741165 ], [ -122.406047831839984, 37.743656241756561 ], [ -122.406149889909912, 37.743534947555823 ], [ -122.406881612449212, 37.742665295031905 ], [ -122.407179681106143, 37.742274728523746 ], [ -122.407209491524853, 37.742235666028684 ], [ -122.407448073794797, 37.741860521932459 ], [ -122.407625696256517, 37.741528038897464 ], [ -122.407757062965644, 37.741253133394146 ], [ -122.407846654253319, 37.741024367104636 ], [ -122.407964646391505, 37.740662552272049 ], [ -122.408049229318891, 37.740339110042903 ], [ -122.408114071971212, 37.739818086787388 ], [ -122.408137030577961, 37.739635775942595 ], [ -122.407910235902733, 37.739622169745928 ], [ -122.407891817991583, 37.739621065115479 ], [ -122.407641980575562, 37.73960607595005 ], [ -122.406978585498265, 37.739566272603653 ], [ -122.40691098434678, 37.739429695993088 ], [ -122.406917382338506, 37.739360326809205 ], [ -122.406923419099328, 37.739294876190314 ], [ -122.40692734433064, 37.739234393770168 ], [ -122.406930338448149, 37.739188255204461 ], [ -122.406936851773693, 37.739087891690275 ], [ -122.406939830707117, 37.738876165961258 ], [ -122.406945393527934, 37.738693880058896 ], [ -122.40693392611557, 37.738601770051126 ], [ -122.406915744052654, 37.738455725148341 ], [ -122.406895428755547, 37.738323651840822 ], [ -122.406867742024218, 37.738143654094841 ], [ -122.406900092501772, 37.738009155282221 ], [ -122.406910504104971, 37.737965866913434 ], [ -122.406734307244236, 37.736552960385914 ], [ -122.40670383539134, 37.73630860402433 ], [ -122.406618861734884, 37.735896873766244 ], [ -122.406454646881144, 37.735510813404687 ], [ -122.406382307240833, 37.735418606514493 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.406543556705884, 37.735230631177984 ], [ -122.406646483954859, 37.73519837501037 ], [ -122.406814899797354, 37.735145595835078 ], [ -122.406847917074074, 37.735135248636119 ], [ -122.407130003639637, 37.735046845926902 ], [ -122.407348459866085, 37.73497838329498 ], [ -122.408033938982058, 37.734745520041734 ], [ -122.408486051038935, 37.734591930477059 ], [ -122.409312695053686, 37.734284112011252 ], [ -122.410241243396115, 37.733882628159428 ], [ -122.41165477705286, 37.733366974354652 ], [ -122.413331412339687, 37.732631849917702 ], [ -122.413420514856341, 37.732598164337901 ], [ -122.414376034430035, 37.732236918329072 ], [ -122.414530950551168, 37.732192436697261 ], [ -122.415052879313009, 37.73204257200301 ], [ -122.415897414400035, 37.731880953714693 ], [ -122.416651654342999, 37.731819145397338 ], [ -122.417401517276474, 37.731856484581833 ], [ -122.41846272371707, 37.73186673188021 ], [ -122.419454332152029, 37.731867094633678 ], [ -122.420326686201122, 37.731814347002349 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.4217053213399, 37.731806501069016 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":17,\"ZIP_CODE\":94134,\"ID\":94134},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.423335646405121, 37.727792365353856 ], [ -122.423335646419318, 37.727792365903014 ], [ -122.423508669179441, 37.728206721890601 ], [ -122.423584360438809, 37.728387748823138 ], [ -122.423739981620528, 37.728759936840348 ], [ -122.423740585686062, 37.728759904726353 ], [ -122.424198685585807, 37.728735553475715 ], [ -122.424688687008342, 37.728709478304708 ], [ -122.425289939392513, 37.728677480232541 ], [ -122.425963703408243, 37.728641021813154 ], [ -122.426024704407865, 37.729396982427588 ], [ -122.423797638913399, 37.729509407965139 ], [ -122.422942671063694, 37.729241733452504 ], [ -122.422503353186897, 37.729261991833724 ], [ -122.421440856780521, 37.729310979302717 ], [ -122.421182810911759, 37.729745420447983 ], [ -122.421083540431724, 37.729975107309578 ], [ -122.421017374424935, 37.730233164434182 ], [ -122.420964111358728, 37.730677319690216 ], [ -122.420944905836123, 37.73130857523288 ], [ -122.421753180203225, 37.731084669073972 ], [ -122.421730502033157, 37.73135209792499 ], [ -122.421736572397847, 37.731350897661805 ], [ -122.421730663628679, 37.731437047083624 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.420326686201122, 37.731814347002349 ], [ -122.419454332152029, 37.731867094633678 ], [ -122.41846272371707, 37.73186673188021 ], [ -122.417401517276474, 37.731856484581833 ], [ -122.416651654342999, 37.731819145397338 ], [ -122.415897414400035, 37.731880953714693 ], [ -122.415052879313009, 37.73204257200301 ], [ -122.414530950551168, 37.732192436697261 ], [ -122.414376034430035, 37.732236918329072 ], [ -122.413420514856341, 37.732598164337901 ], [ -122.413331412339687, 37.732631849917702 ], [ -122.41165477705286, 37.733366974354652 ], [ -122.410241243396115, 37.733882628159428 ], [ -122.409312695053686, 37.734284112011252 ], [ -122.408486051038935, 37.734591930477059 ], [ -122.408033938982058, 37.734745520041734 ], [ -122.407348459866085, 37.73497838329498 ], [ -122.407130003639637, 37.735046845926902 ], [ -122.406847917074074, 37.735135248636119 ], [ -122.406814899797354, 37.735145595835078 ], [ -122.406646483954859, 37.73519837501037 ], [ -122.406543556705884, 37.735230631177984 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.405494571184249, 37.734287050530888 ], [ -122.405096042524178, 37.733716127227488 ], [ -122.405059585407599, 37.733652395233406 ], [ -122.404878800551302, 37.733336356125804 ], [ -122.404657097641305, 37.732948782960683 ], [ -122.40465709692171, 37.732948781873532 ], [ -122.40405482060109, 37.731789516245591 ], [ -122.403961731325225, 37.731610333558521 ], [ -122.403400462440644, 37.730658352585962 ], [ -122.403021622188092, 37.729901901370539 ], [ -122.402752244737584, 37.729357163240891 ], [ -122.402209197740476, 37.728101973566574 ], [ -122.402105120777605, 37.727789703519925 ], [ -122.402039596395539, 37.727554690472438 ], [ -122.401875118236148, 37.726964759399728 ], [ -122.401822425920514, 37.726775260854211 ], [ -122.401474642450822, 37.725528485745301 ], [ -122.401471218295981, 37.725516183407578 ], [ -122.401182917574161, 37.724588254281116 ], [ -122.40078474798517, 37.723616209514844 ], [ -122.400784747286778, 37.723616209251418 ], [ -122.400661931084002, 37.723316373994798 ], [ -122.40038213245046, 37.722633285332655 ], [ -122.399925016822507, 37.721517272170509 ], [ -122.399830553211288, 37.721427080386505 ], [ -122.399600300155441, 37.720920363637823 ], [ -122.399419264352986, 37.720532021245042 ], [ -122.399309038031902, 37.720278334103305 ], [ -122.399208340593788, 37.719991530079234 ], [ -122.399125486878972, 37.719730004239857 ], [ -122.399066465573654, 37.719469200866385 ], [ -122.398943601880902, 37.718926286066001 ], [ -122.398942440206895, 37.718921151552976 ], [ -122.398923285260594, 37.718836507697276 ], [ -122.398913161799925, 37.718792447247658 ], [ -122.398799134608808, 37.718275024173522 ], [ -122.398787867001658, 37.718224410399863 ], [ -122.398759937061044, 37.718047446028777 ], [ -122.39871606167118, 37.717769450850419 ], [ -122.398686680058731, 37.717603871431201 ], [ -122.398668885707423, 37.717503589023465 ], [ -122.398561414050022, 37.716897923924947 ], [ -122.398499278161879, 37.716543657492224 ], [ -122.398550700112352, 37.7161815389411 ], [ -122.398565370170317, 37.716139616445538 ], [ -122.398643015232707, 37.715917722950913 ], [ -122.398656056102226, 37.71588045500463 ], [ -122.398735527529269, 37.715727131371494 ], [ -122.398839335741798, 37.715526852955328 ], [ -122.398953101659146, 37.715385829745912 ], [ -122.398991043085772, 37.715338797679301 ], [ -122.398991048786399, 37.715338790720075 ], [ -122.398990733617495, 37.715338865847912 ], [ -122.398990734294756, 37.715338865287599 ], [ -122.398552555430172, 37.715443409305585 ], [ -122.398471400839512, 37.715455433381891 ], [ -122.398259710629063, 37.715141974251068 ], [ -122.397834560573045, 37.714650610374164 ], [ -122.396885665233555, 37.713468431896139 ], [ -122.396615774145118, 37.713094103716635 ], [ -122.396615767431044, 37.713094016747043 ], [ -122.396209567629995, 37.712429108802013 ], [ -122.395984840101519, 37.711902635542479 ], [ -122.395804130724926, 37.711357784012904 ], [ -122.395699524288574, 37.710911289111898 ], [ -122.394789228222862, 37.711029491236808 ], [ -122.394533636579439, 37.711181739957787 ], [ -122.394415018271332, 37.711243467479711 ], [ -122.394301164504668, 37.711283892996612 ], [ -122.394130547970391, 37.711308547209811 ], [ -122.393916838957196, 37.711294747317396 ], [ -122.392609027560681, 37.711272141276474 ], [ -122.390835880106437, 37.711169771421694 ], [ -122.390887824662656, 37.71036523380814 ], [ -122.391332097355985, 37.710140738310088 ], [ -122.392656793824756, 37.709119608424643 ], [ -122.392432645117751, 37.708928100915109 ], [ -122.392444333503988, 37.708923483961478 ], [ -122.392459711870288, 37.708916369816677 ], [ -122.392473335584143, 37.708908253489831 ], [ -122.392486941819897, 37.70889945070968 ], [ -122.392498784665378, 37.708889302522401 ], [ -122.392510610730511, 37.708878468145663 ], [ -122.392524068414929, 37.708863830521395 ], [ -122.392559440339639, 37.708827208691588 ], [ -122.392572967908777, 37.708815316868559 ], [ -122.392584776482323, 37.708803796307102 ], [ -122.392598312774226, 37.708792247432399 ], [ -122.392611857807054, 37.708781042056593 ], [ -122.392623674754688, 37.708769864447994 ], [ -122.39263895660568, 37.708758974800666 ], [ -122.392652510357621, 37.708748112371396 ], [ -122.392666072850986, 37.708737593441008 ], [ -122.39268137181341, 37.708727389970818 ], [ -122.39269494304439, 37.708717214537501 ], [ -122.392710242682909, 37.70870701077785 ], [ -122.392756192651433, 37.708678459962563 ], [ -122.392771518490107, 37.708669285872482 ], [ -122.392791975788157, 37.708657969391702 ], [ -122.392832794235133, 37.708631560935011 ], [ -122.392860041474023, 37.708615328192714 ], [ -122.392875393166236, 37.708607184046727 ], [ -122.392889025524426, 37.708599411171832 ], [ -122.392904394682077, 37.708591953199182 ], [ -122.392918035766968, 37.708584523272208 ], [ -122.392941949172211, 37.708573151218971 ], [ -122.392977635730745, 37.708548885387124 ], [ -122.393011585450822, 37.708524304093004 ], [ -122.393045517668241, 37.708499036337294 ], [ -122.393079441122353, 37.708473425345893 ], [ -122.393113355819651, 37.708447471393349 ], [ -122.393167526617077, 37.70840230614084 ], [ -122.393582832216993, 37.708417948939243 ], [ -122.395113504570858, 37.708409867431477 ], [ -122.397085902715403, 37.708399425270557 ], [ -122.399684603917876, 37.708385616773455 ], [ -122.401309241246423, 37.708378554199705 ], [ -122.402497775210733, 37.708373373547339 ], [ -122.40338621311453, 37.708369492971435 ], [ -122.40510200377139, 37.708361979821355 ], [ -122.405343853958243, 37.708354977289922 ], [ -122.405521792033781, 37.708350038400255 ], [ -122.406475878190662, 37.708342152666113 ], [ -122.406591468680915, 37.708342377473876 ], [ -122.407290175418893, 37.708343733713313 ], [ -122.407753446712761, 37.708342067925997 ], [ -122.408110927534096, 37.708327349450251 ], [ -122.40829882656611, 37.708327401816014 ], [ -122.412359860013211, 37.708328460170456 ], [ -122.413340535135291, 37.70832869518982 ], [ -122.413594573052677, 37.708328955439633 ], [ -122.414219950784457, 37.708329593757632 ], [ -122.414374376554221, 37.708329750560253 ], [ -122.415233045955674, 37.708329360822155 ], [ -122.415330384121233, 37.708329316229424 ], [ -122.416223848405949, 37.708328179819176 ], [ -122.417093688537392, 37.708327067084589 ], [ -122.417207468396128, 37.708327057129232 ], [ -122.418047976840626, 37.708326981775464 ], [ -122.418165047871298, 37.708327396090446 ], [ -122.419017914810595, 37.708330411075799 ], [ -122.419120269682026, 37.708330983731479 ], [ -122.419237502940533, 37.708331640189144 ], [ -122.419704274317439, 37.708331586927457 ], [ -122.420073510611971, 37.708332523899031 ], [ -122.420159361284988, 37.708332741426119 ], [ -122.423764571500399, 37.708341826236627 ], [ -122.423361076063088, 37.709234055437079 ], [ -122.423353359735231, 37.709250935907697 ], [ -122.423353331596459, 37.709250997624174 ], [ -122.423820286343911, 37.709573973454503 ], [ -122.423971204711407, 37.709678357430306 ], [ -122.424550951226621, 37.710101873835256 ], [ -122.425118753539664, 37.710464462528329 ], [ -122.425572654326643, 37.710701178146003 ], [ -122.426490332391396, 37.711021642816618 ], [ -122.426849963523566, 37.711156197850272 ], [ -122.425769401653085, 37.714425227128565 ], [ -122.427973857260341, 37.715161057331514 ], [ -122.427571950566247, 37.71699744604517 ], [ -122.426961271013212, 37.717805116783104 ], [ -122.426390269927637, 37.718560725227924 ], [ -122.426390269596226, 37.718560725782737 ], [ -122.426390032692737, 37.718560604670934 ], [ -122.425834816801412, 37.718279388717512 ], [ -122.425605873732934, 37.718131895102509 ], [ -122.425411706342686, 37.717880972693159 ], [ -122.425177444570053, 37.717485960998928 ], [ -122.424942097378548, 37.717261064626705 ], [ -122.424687869969304, 37.717146210464122 ], [ -122.424656181661859, 37.717131894726805 ], [ -122.424528018591516, 37.717064916531086 ], [ -122.424258262597263, 37.717024059120966 ], [ -122.424022457403083, 37.717036533105109 ], [ -122.423774201550728, 37.717092319144726 ], [ -122.423530052223612, 37.717201923868423 ], [ -122.422472988486305, 37.717887379027822 ], [ -122.422642040011837, 37.718030648532753 ], [ -122.422885734123284, 37.71809195842259 ], [ -122.423023908872437, 37.718162895921928 ], [ -122.423452239422744, 37.718705588384864 ], [ -122.423536512782078, 37.718956983348477 ], [ -122.42348866361003, 37.719439263071187 ], [ -122.423554916906838, 37.719668150420404 ], [ -122.423736904433326, 37.71982328246721 ], [ -122.424032169669715, 37.719926258473294 ], [ -122.424800888544937, 37.7200238938456 ], [ -122.425478982693178, 37.720232473267444 ], [ -122.425478983024618, 37.720232472712645 ], [ -122.425479352540037, 37.720232586714161 ], [ -122.425479352547157, 37.72023258698875 ], [ -122.425664999767108, 37.720321846926637 ], [ -122.426022740865662, 37.720493848462766 ], [ -122.42602274262245, 37.720493849532829 ], [ -122.426022741606815, 37.720493850373522 ], [ -122.424918832446721, 37.722087021186233 ], [ -122.424897104558099, 37.722118378784295 ], [ -122.423806549912683, 37.723691155552039 ], [ -122.423577946908821, 37.723750386689403 ], [ -122.423577951775229, 37.723750454459399 ], [ -122.422830067678831, 37.723944194056337 ], [ -122.421846961042462, 37.724198859168652 ], [ -122.421846961049567, 37.724198859443213 ], [ -122.422043387171286, 37.724673029193113 ], [ -122.422342507091699, 37.725395088531037 ], [ -122.422342507113001, 37.725395089354791 ], [ -122.422840471972961, 37.726597115803735 ], [ -122.422840471987186, 37.726597116352892 ], [ -122.42312222730105, 37.727277221300007 ], [ -122.423246600371399, 37.727577430218268 ], [ -122.423335646405121, 37.727792365353856 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":18,\"ZIP_CODE\":94112,\"ID\":94112},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.427838803054428, 37.734693898279502 ], [ -122.427103368742522, 37.735457096504888 ], [ -122.424930534158477, 37.735266285585048 ], [ -122.424702562314437, 37.735369870957172 ], [ -122.422687909096169, 37.735204969342369 ], [ -122.42260172446116, 37.735176855221177 ], [ -122.422120806983799, 37.735152847768823 ], [ -122.422146910860818, 37.734962831393027 ], [ -122.422205425171413, 37.73485685013258 ], [ -122.422284286448345, 37.734779711262206 ], [ -122.422547222688962, 37.734619826084632 ], [ -122.422737437389159, 37.734490301194434 ], [ -122.422876476168085, 37.734369596551609 ], [ -122.422640968014662, 37.73418217013328 ], [ -122.42251406401526, 37.734049477037892 ], [ -122.422378321374794, 37.733842795338198 ], [ -122.42233700854446, 37.733668492264307 ], [ -122.422262570568407, 37.733475728240066 ], [ -122.42202732889227, 37.733140443779334 ], [ -122.42184863663951, 37.732986304256819 ], [ -122.421779146191113, 37.732885343728064 ], [ -122.421740792022078, 37.732743282983918 ], [ -122.421610219449562, 37.732670894611623 ], [ -122.421573075628018, 37.732579492315836 ], [ -122.421628644882077, 37.732486581446111 ], [ -122.421769540807844, 37.732442879558889 ], [ -122.421964308435761, 37.732433222436505 ], [ -122.422597636657429, 37.732308431174374 ], [ -122.423194763566187, 37.732223135593344 ], [ -122.423486414351174, 37.732261254158729 ], [ -122.423769382874056, 37.732162707171447 ], [ -122.424277944077048, 37.732133983506472 ], [ -122.425046994836066, 37.732129947854176 ], [ -122.42519868259302, 37.732129150959032 ], [ -122.425500227991904, 37.7320527573848 ], [ -122.425781691782262, 37.731929785903453 ], [ -122.425919712835267, 37.7318157721811 ], [ -122.426095455082049, 37.731670597839134 ], [ -122.426103813784607, 37.731651546856703 ], [ -122.425563267495079, 37.731578779884757 ], [ -122.424987445865384, 37.731559842008934 ], [ -122.424645313557747, 37.731548588455745 ], [ -122.424553925961277, 37.731549131439351 ], [ -122.423920024119482, 37.731552895324626 ], [ -122.422820482616018, 37.731641033131751 ], [ -122.4217053213399, 37.731806501069016 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.421730663628679, 37.731437047083624 ], [ -122.421736572397847, 37.731350897661805 ], [ -122.421730502033157, 37.73135209792499 ], [ -122.421753180203225, 37.731084669073972 ], [ -122.420944905836123, 37.73130857523288 ], [ -122.420964111358728, 37.730677319690216 ], [ -122.421017374424935, 37.730233164434182 ], [ -122.421083540431724, 37.729975107309578 ], [ -122.421182810911759, 37.729745420447983 ], [ -122.421440856780521, 37.729310979302717 ], [ -122.422503353186897, 37.729261991833724 ], [ -122.422942671063694, 37.729241733452504 ], [ -122.423797638913399, 37.729509407965139 ], [ -122.426024704407865, 37.729396982427588 ], [ -122.425963703408243, 37.728641021813154 ], [ -122.425289939392513, 37.728677480232541 ], [ -122.424688687008342, 37.728709478304708 ], [ -122.424198685585807, 37.728735553475715 ], [ -122.423740585686062, 37.728759904726353 ], [ -122.423739981620528, 37.728759936840348 ], [ -122.423584360438809, 37.728387748823138 ], [ -122.423508669179441, 37.728206721890601 ], [ -122.423335646405121, 37.727792365353856 ], [ -122.423246600371399, 37.727577430218268 ], [ -122.42312222730105, 37.727277221300007 ], [ -122.422840471987186, 37.726597116352892 ], [ -122.422840471972961, 37.726597115803735 ], [ -122.422342507113001, 37.725395089354791 ], [ -122.422342507091699, 37.725395088531037 ], [ -122.422043387171286, 37.724673029193113 ], [ -122.421846961042462, 37.724198859168652 ], [ -122.422830067678831, 37.723944194056337 ], [ -122.423577951775229, 37.723750454459399 ], [ -122.423577946908821, 37.723750386689403 ], [ -122.423806549912683, 37.723691155552039 ], [ -122.424897104558099, 37.722118378784295 ], [ -122.424918832446721, 37.722087021186233 ], [ -122.426022741606815, 37.720493850373522 ], [ -122.42602274262245, 37.720493849532829 ], [ -122.426022740865662, 37.720493848462766 ], [ -122.425664999767108, 37.720321846926637 ], [ -122.425479352547157, 37.72023258698875 ], [ -122.425478983024618, 37.720232472712645 ], [ -122.425478982693178, 37.720232473267444 ], [ -122.424800888544937, 37.7200238938456 ], [ -122.424032169669715, 37.719926258473294 ], [ -122.423736904433326, 37.71982328246721 ], [ -122.423554916906838, 37.719668150420404 ], [ -122.42348866361003, 37.719439263071187 ], [ -122.423536512782078, 37.718956983348477 ], [ -122.423452239422744, 37.718705588384864 ], [ -122.423023908872437, 37.718162895921928 ], [ -122.422885734123284, 37.71809195842259 ], [ -122.422642040011837, 37.718030648532753 ], [ -122.422472988486305, 37.717887379027822 ], [ -122.423530052223612, 37.717201923868423 ], [ -122.423774201550728, 37.717092319144726 ], [ -122.424022457403083, 37.717036533105109 ], [ -122.424258262597263, 37.717024059120966 ], [ -122.424528018591516, 37.717064916531086 ], [ -122.424656181661859, 37.717131894726805 ], [ -122.424687869969304, 37.717146210464122 ], [ -122.424942097378548, 37.717261064626705 ], [ -122.425177444570053, 37.717485960998928 ], [ -122.425411706342686, 37.717880972693159 ], [ -122.425605873732934, 37.718131895102509 ], [ -122.425834816801412, 37.718279388717512 ], [ -122.426390032692737, 37.718560604670934 ], [ -122.426390269596226, 37.718560725782737 ], [ -122.426390269927637, 37.718560725227924 ], [ -122.426961271013212, 37.717805116783104 ], [ -122.42757190808544, 37.716997501404578 ], [ -122.427571950566247, 37.71699744604517 ], [ -122.427580759648123, 37.716957196731435 ], [ -122.427810303733594, 37.715908376197518 ], [ -122.427973857260341, 37.715161057331514 ], [ -122.425769401653085, 37.714425227128565 ], [ -122.42684686757103, 37.711165565601846 ], [ -122.426849963523566, 37.711156197850272 ], [ -122.426490332391396, 37.711021642816618 ], [ -122.425572654326643, 37.710701178146003 ], [ -122.425118753539664, 37.710464462528329 ], [ -122.424550951226621, 37.710101873835256 ], [ -122.423971204711407, 37.709678357430306 ], [ -122.423353331596459, 37.709250997624174 ], [ -122.423361076063088, 37.709234055437079 ], [ -122.423764571500399, 37.708341826236627 ], [ -122.423768921768058, 37.708341837314315 ], [ -122.423877843148816, 37.708342118381125 ], [ -122.425994336403065, 37.708310781158126 ], [ -122.426202061368684, 37.708307703589938 ], [ -122.427674537396697, 37.708285876646727 ], [ -122.428354189600114, 37.708305089572242 ], [ -122.429105296742478, 37.708326317449469 ], [ -122.429339593931815, 37.708323251263963 ], [ -122.430449087529411, 37.70830872506928 ], [ -122.43129493119001, 37.708297643647327 ], [ -122.432343143904646, 37.708295922670345 ], [ -122.433778966251097, 37.708293550010708 ], [ -122.434148404329434, 37.708292936416406 ], [ -122.434985771421609, 37.708291542195809 ], [ -122.436248762964979, 37.708289427645774 ], [ -122.438728105043637, 37.708285237562031 ], [ -122.440497203989452, 37.708282215784038 ], [ -122.440711209177138, 37.708281848403743 ], [ -122.441349676712647, 37.708280750220105 ], [ -122.442106190726975, 37.708279444504022 ], [ -122.444671046569766, 37.708274980891133 ], [ -122.44478757213254, 37.708274776994109 ], [ -122.445691081512706, 37.708249897066402 ], [ -122.445824359823575, 37.708249470051783 ], [ -122.447227656612327, 37.708244965564717 ], [ -122.449798855530389, 37.708236668750466 ], [ -122.452354883587788, 37.708228365382816 ], [ -122.454322408844959, 37.70822193637261 ], [ -122.454560786889971, 37.708221155056798 ], [ -122.455565069973886, 37.708215472275576 ], [ -122.455732056485743, 37.708217630626116 ], [ -122.455902300127136, 37.708219830519191 ], [ -122.457184544230842, 37.70823461994447 ], [ -122.457915234279184, 37.708243041121499 ], [ -122.458505268317239, 37.708230883488149 ], [ -122.459062187698038, 37.708219405843458 ], [ -122.459727762092839, 37.708205685584353 ], [ -122.459848666151245, 37.708200651285779 ], [ -122.460029970068362, 37.708193102258491 ], [ -122.46108479110967, 37.708202670088042 ], [ -122.461227604731107, 37.708198848520283 ], [ -122.46139057300131, 37.708194487863473 ], [ -122.461950847900269, 37.70819992107895 ], [ -122.462093547900324, 37.708193760482068 ], [ -122.462235775065267, 37.708187619950934 ], [ -122.462590335310921, 37.70818746608122 ], [ -122.462764782467445, 37.70818738953367 ], [ -122.463939046778805, 37.708202838503169 ], [ -122.464345228799317, 37.708207403972743 ], [ -122.464812351274205, 37.708212652628795 ], [ -122.465768040492705, 37.708210264664245 ], [ -122.466623471256085, 37.708208120693818 ], [ -122.468612008025019, 37.708203113190002 ], [ -122.468634481973396, 37.708203081190824 ], [ -122.468943918748693, 37.708202372165289 ], [ -122.468469108419114, 37.708682445380624 ], [ -122.468442186669975, 37.708710365091008 ], [ -122.46815544621434, 37.708936581998181 ], [ -122.467717389066479, 37.709282172736899 ], [ -122.467202952111606, 37.709607827013201 ], [ -122.467161376112116, 37.709634146029515 ], [ -122.466791287837083, 37.709825747176509 ], [ -122.465994326901708, 37.710153924678359 ], [ -122.464766855714529, 37.710481381431755 ], [ -122.464363457907425, 37.710527001097098 ], [ -122.464175624997623, 37.710548242222522 ], [ -122.462893196389857, 37.710562754126848 ], [ -122.462561257753663, 37.710567393100142 ], [ -122.462558461843784, 37.710567432270715 ], [ -122.462554670587338, 37.71117015272295 ], [ -122.462545712573657, 37.711171028559598 ], [ -122.462063256954337, 37.711372729296876 ], [ -122.462570051320355, 37.711370301468136 ], [ -122.462581299448402, 37.712273520359908 ], [ -122.462580658473911, 37.712523033743715 ], [ -122.46256899895387, 37.713150165038506 ], [ -122.462554985317752, 37.713903926321834 ], [ -122.462588724439357, 37.714029538822885 ], [ -122.462602039439204, 37.714307653173123 ], [ -122.462607827629938, 37.714883950429027 ], [ -122.462615213395566, 37.71561930379395 ], [ -122.462620481465109, 37.716143794339722 ], [ -122.46262477066405, 37.716570820282072 ], [ -122.462633312624419, 37.717421207682776 ], [ -122.462638771382373, 37.717866433871905 ], [ -122.462639625007327, 37.717936035524978 ], [ -122.46263854285489, 37.718211149542661 ], [ -122.462244936309219, 37.718219524954932 ], [ -122.462245294921956, 37.718767790464547 ], [ -122.46226246554707, 37.719928593578096 ], [ -122.462254399326852, 37.72000857322422 ], [ -122.462264896007014, 37.721694985883083 ], [ -122.46226548731488, 37.721827794893429 ], [ -122.462270801151405, 37.723021081969392 ], [ -122.462273544585969, 37.723637216450626 ], [ -122.462276364972297, 37.724270607125867 ], [ -122.462276854232826, 37.724380477322995 ], [ -122.462279017236384, 37.724866230039204 ], [ -122.462279803400207, 37.725042766873585 ], [ -122.462280269039496, 37.725147301470763 ], [ -122.462212628293415, 37.725265491161082 ], [ -122.462561310804759, 37.725391067720956 ], [ -122.462815474472038, 37.725482412532848 ], [ -122.462686726995429, 37.725774916753664 ], [ -122.46262180837526, 37.726335258944708 ], [ -122.462565711889994, 37.726822959746293 ], [ -122.462545638153259, 37.726997475605309 ], [ -122.462546035395761, 37.727106268284892 ], [ -122.462547067236983, 37.727225353979733 ], [ -122.462615761322709, 37.727529244060726 ], [ -122.462714226912738, 37.727738412010282 ], [ -122.462888304160828, 37.72805552868347 ], [ -122.46288814852555, 37.72805550243347 ], [ -122.462888197868679, 37.72805558978888 ], [ -122.462703368909061, 37.728024299159358 ], [ -122.462378284725844, 37.728011300874932 ], [ -122.462089038987699, 37.728047063364642 ], [ -122.461824751209633, 37.728118912555594 ], [ -122.46142222273329, 37.728330263725837 ], [ -122.460815856121073, 37.72868098095028 ], [ -122.461139723962631, 37.728765209783546 ], [ -122.461378769207158, 37.728787306916686 ], [ -122.461378549868385, 37.728787330621202 ], [ -122.461139150327014, 37.728765372338408 ], [ -122.460815517748841, 37.728680936862041 ], [ -122.460412720770037, 37.728739179843977 ], [ -122.460002483633559, 37.728739437629635 ], [ -122.460023966205156, 37.729195728883113 ], [ -122.459983752683073, 37.729522472170189 ], [ -122.459930163903692, 37.729706628889033 ], [ -122.459878403068345, 37.729884503080157 ], [ -122.459783802497654, 37.730109540441724 ], [ -122.459563710785162, 37.730368588301999 ], [ -122.459352156941819, 37.730515761910091 ], [ -122.459193144317396, 37.730602954490251 ], [ -122.458688935885434, 37.730751520241796 ], [ -122.458472470688633, 37.730752827866375 ], [ -122.458222500186622, 37.73077738189027 ], [ -122.457474238952088, 37.730997495241198 ], [ -122.456607498028021, 37.731187733113579 ], [ -122.455762555159509, 37.731348802743547 ], [ -122.454915878760758, 37.731464997636976 ], [ -122.453590911574565, 37.731488405776453 ], [ -122.453411343594681, 37.731530646828205 ], [ -122.451146666771351, 37.731536846888879 ], [ -122.448866265260094, 37.731542959168735 ], [ -122.447291821211039, 37.731547153756608 ], [ -122.446578400413941, 37.7315490470612 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.444294941941365, 37.731069863889161 ], [ -122.444298409699798, 37.730708221303239 ], [ -122.444291303797655, 37.729896860703782 ], [ -122.444284269547836, 37.729093639959018 ], [ -122.44427751391413, 37.728322242080615 ], [ -122.444276984301908, 37.728261755504967 ], [ -122.444131311025359, 37.72831838217401 ], [ -122.443973112888827, 37.728379877183251 ], [ -122.442954953118544, 37.728915555981267 ], [ -122.442595296877187, 37.729101680603421 ], [ -122.442082956691792, 37.729308363921405 ], [ -122.442006614261629, 37.729375267903364 ], [ -122.442010214558891, 37.729910949382834 ], [ -122.440664990612632, 37.729919329476168 ], [ -122.43969146797258, 37.730323477880063 ], [ -122.439686065703896, 37.730325720705899 ], [ -122.438692929843569, 37.730737998359416 ], [ -122.437166749161392, 37.731365897534992 ], [ -122.436835967120587, 37.731467212874229 ], [ -122.436678949019353, 37.731538395224071 ], [ -122.436668162612392, 37.731543298105045 ], [ -122.435457687984979, 37.732093484109448 ], [ -122.434992781477959, 37.732295287547394 ], [ -122.434989951388332, 37.732296516038524 ], [ -122.434823956379304, 37.732249430441868 ], [ -122.434644432402948, 37.732322139093768 ], [ -122.434512093568429, 37.732323526771246 ], [ -122.434440716858973, 37.732534804028113 ], [ -122.432720773871381, 37.733187185661393 ], [ -122.432572047381726, 37.733243596430682 ], [ -122.43132522808267, 37.733716495194997 ], [ -122.430931197463138, 37.733865940717834 ], [ -122.430928172484656, 37.733867087971888 ], [ -122.430400796753801, 37.734118700718604 ], [ -122.429799382942633, 37.734405632509876 ], [ -122.429797927680781, 37.734406452684077 ], [ -122.429606994304805, 37.734482866043706 ], [ -122.429365174143101, 37.734579643964238 ], [ -122.428766264762999, 37.73481932892178 ], [ -122.42843713263963, 37.734951046304573 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.428436980709748, 37.734950949626743 ], [ -122.427838803054428, 37.734693898279502 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":19,\"ZIP_CODE\":94116,\"ID\":94116},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.491334899611488, 37.737293023539365 ], [ -122.491211452414888, 37.737221069184358 ], [ -122.491162508402653, 37.737157782139001 ], [ -122.490686267381363, 37.736541968564268 ], [ -122.490552748713441, 37.736251597078756 ], [ -122.490521485540071, 37.736028873151284 ], [ -122.490460674310157, 37.735842274774221 ], [ -122.490270108429897, 37.735606337180243 ], [ -122.490526224357907, 37.735470000646821 ], [ -122.490725734916396, 37.735399781312303 ], [ -122.490973609504408, 37.735348171552815 ], [ -122.491320518441114, 37.735327019185135 ], [ -122.491730212101302, 37.735301758325164 ], [ -122.491805731315921, 37.735279805981911 ], [ -122.492142715245791, 37.735181846965403 ], [ -122.492330945602859, 37.73504482611267 ], [ -122.492843999903116, 37.734842083695071 ], [ -122.493274340840912, 37.734785121286002 ], [ -122.493406966558723, 37.734856121008917 ], [ -122.493407285267097, 37.734856242527158 ], [ -122.493857076611491, 37.73502785037892 ], [ -122.493857150107345, 37.735028823489081 ], [ -122.4940149282546, 37.735024137316628 ], [ -122.494014877350082, 37.735023413250019 ], [ -122.493941607430031, 37.733981341508411 ], [ -122.493941605376719, 37.733981316545631 ], [ -122.494403119131789, 37.733960247035334 ], [ -122.496448643026866, 37.733877460645239 ], [ -122.496635991376749, 37.733770846301013 ], [ -122.496635997172902, 37.733770843181105 ], [ -122.496635997165527, 37.733770842906516 ], [ -122.496636262010625, 37.733770813420726 ], [ -122.496636264393828, 37.733770812006838 ], [ -122.496807213285692, 37.733750829171804 ], [ -122.4970611531011, 37.733738715676616 ], [ -122.497251243304476, 37.7337296478455 ], [ -122.497675024009141, 37.73378860832171 ], [ -122.497707486783483, 37.733793124978028 ], [ -122.498050148013945, 37.73385852689691 ], [ -122.49837749236211, 37.733942575660954 ], [ -122.498483756692679, 37.733969859923228 ], [ -122.498948533172893, 37.734128137325406 ], [ -122.499086082983126, 37.734239092175393 ], [ -122.499180283, 37.734301056816975 ], [ -122.499196335607635, 37.734311616338935 ], [ -122.499430330098079, 37.734471850699563 ], [ -122.500140914702612, 37.734827630984967 ], [ -122.500289079616721, 37.734896880574155 ], [ -122.500571951887039, 37.735029088020617 ], [ -122.500650675572956, 37.735059919015036 ], [ -122.500888239775747, 37.73515295885273 ], [ -122.500970549161394, 37.735174012383368 ], [ -122.501097483002411, 37.735206481213844 ], [ -122.501097483348147, 37.735206481207975 ], [ -122.501214897179494, 37.735242075875206 ], [ -122.501291847576297, 37.735265403511711 ], [ -122.501555214836827, 37.735330188314435 ], [ -122.501556481916552, 37.735330499723666 ], [ -122.502021315734993, 37.73539219398544 ], [ -122.50206735537283, 37.735396211199387 ], [ -122.50249812714712, 37.735433799348435 ], [ -122.503104759187963, 37.735466491452826 ], [ -122.504189206002195, 37.735470928034005 ], [ -122.505257352091178, 37.735475287872006 ], [ -122.505711404999388, 37.735473569905054 ], [ -122.506142139643117, 37.735471938745221 ], [ -122.506541776696594, 37.735470423847289 ], [ -122.506717419598758, 37.735469757763546 ], [ -122.506718392445464, 37.735463437680252 ], [ -122.508528202252251, 37.735448935792135 ], [ -122.50851723304288, 37.735491014374531 ], [ -122.508503948199092, 37.735575367390467 ], [ -122.508509635115971, 37.735593812897228 ], [ -122.508534142132689, 37.735604726176433 ], [ -122.508524280726746, 37.73562377983793 ], [ -122.508510961647445, 37.73564289244193 ], [ -122.50850628586997, 37.735661857701452 ], [ -122.508508623546277, 37.735748348011157 ], [ -122.508514421788533, 37.735770911828716 ], [ -122.508520015957387, 37.735785925179286 ], [ -122.508510052128926, 37.735801203199763 ], [ -122.508477309195854, 37.735805538751634 ], [ -122.508464092111296, 37.735828426720389 ], [ -122.508469983447981, 37.735854422414455 ], [ -122.508474053027456, 37.735877015976307 ], [ -122.508461856322953, 37.735937657650084 ], [ -122.508465576123427, 37.736075286791404 ], [ -122.508449773613393, 37.7361304959501 ], [ -122.508448690994484, 37.73621841805182 ], [ -122.508451371852146, 37.73631760760221 ], [ -122.508490175464019, 37.736473524223285 ], [ -122.508516049607124, 37.736662968752803 ], [ -122.508603414533738, 37.736823895128929 ], [ -122.508662443511639, 37.736960238247853 ], [ -122.50867888000765, 37.737056446014627 ], [ -122.508719731680841, 37.737160135129862 ], [ -122.508734334501312, 37.737316464525655 ], [ -122.508765426520924, 37.737442982668369 ], [ -122.508785682509867, 37.737552516867808 ], [ -122.508770696382811, 37.737637929009765 ], [ -122.508786455872283, 37.737709082028033 ], [ -122.508760949000333, 37.737789179556565 ], [ -122.508763268491435, 37.737874983674544 ], [ -122.50874159008174, 37.737968750866465 ], [ -122.5087476021437, 37.738191154145746 ], [ -122.508756496465509, 37.738392219395308 ], [ -122.508803998140891, 37.738613915588019 ], [ -122.508803138582451, 37.738710074824468 ], [ -122.508864043688376, 37.738915747292424 ], [ -122.5088940788266, 37.739003138823669 ], [ -122.508897419160689, 37.739126696329038 ], [ -122.50889009011712, 37.73923944778879 ], [ -122.508822474607925, 37.739425335507839 ], [ -122.508791610932704, 37.73956321107876 ], [ -122.508779986132339, 37.739708999607267 ], [ -122.508780357262495, 37.739722728217863 ], [ -122.50877604314833, 37.739755078870481 ], [ -122.508764442526711, 37.739773818808054 ], [ -122.508752731253111, 37.739788440149496 ], [ -122.508741502089606, 37.739820908689246 ], [ -122.508753863186314, 37.739830312411385 ], [ -122.508778826631357, 37.739858043722968 ], [ -122.508779958572916, 37.739899915709536 ], [ -122.508761814014846, 37.7399325021469 ], [ -122.508762704722017, 37.739965450811162 ], [ -122.508770491923499, 37.739997595154925 ], [ -122.508776179583251, 37.740016040354824 ], [ -122.508747792652912, 37.740053608631989 ], [ -122.508748423567027, 37.740076947268811 ], [ -122.508737212914269, 37.74011010223709 ], [ -122.508737713932007, 37.740128635860302 ], [ -122.508745501147203, 37.740160780479421 ], [ -122.50873947678167, 37.740193847030952 ], [ -122.508723191132987, 37.740231208721582 ], [ -122.508723822048324, 37.740254547632397 ], [ -122.508732128829621, 37.740305911755783 ], [ -122.508742872573777, 37.740319463533034 ], [ -122.50877339326972, 37.74036083476765 ], [ -122.508810829778682, 37.740402088090121 ], [ -122.508829008239559, 37.740434742019097 ], [ -122.508813223629531, 37.740490637616951 ], [ -122.508861162085935, 37.740536519108112 ], [ -122.508850322907236, 37.740583402687079 ], [ -122.508858110211349, 37.74061554702174 ], [ -122.508869614883267, 37.740657242433585 ], [ -122.508870616992439, 37.740694309675469 ], [ -122.508866692231578, 37.740741075644983 ], [ -122.508879295085322, 37.740759402667216 ], [ -122.508914260413761, 37.740773228492962 ], [ -122.508932680259875, 37.740814805724348 ], [ -122.508928625962866, 37.740856766401905 ], [ -122.508929256940604, 37.740880105034634 ], [ -122.508935445433963, 37.740917083849979 ], [ -122.508953234348851, 37.740935322719352 ], [ -122.508983384240864, 37.740962965288134 ], [ -122.509008589756519, 37.740999619859899 ], [ -122.509026879459341, 37.741036392343048 ], [ -122.509023085376484, 37.741087963041082 ], [ -122.509036559926997, 37.741138552557295 ], [ -122.509054739012925, 37.741171206441557 ], [ -122.509067601488525, 37.741199144021785 ], [ -122.5090958931589, 37.741222010744757 ], [ -122.509133200021253, 37.741258459227247 ], [ -122.50918506342488, 37.741257574887214 ], [ -122.509205212680868, 37.74129912258816 ], [ -122.509246237071991, 37.741345121825837 ], [ -122.509242554147008, 37.741400811115767 ], [ -122.509226769642837, 37.741456706760005 ], [ -122.50922790187596, 37.741498579280254 ], [ -122.509216431361367, 37.741522123988162 ], [ -122.509199662741182, 37.741541638830299 ], [ -122.509177093246976, 37.741602457387373 ], [ -122.509208116202714, 37.741662362111221 ], [ -122.509186903920153, 37.741709422591256 ], [ -122.509189298261234, 37.741797972091135 ], [ -122.509154236925113, 37.741844581962283 ], [ -122.509149811487788, 37.741872814035773 ], [ -122.509151073599824, 37.741919491290815 ], [ -122.509123057336595, 37.741970788250718 ], [ -122.509100375943362, 37.742027488213331 ], [ -122.509101006984949, 37.742050826840561 ], [ -122.509092137481943, 37.742106604551111 ], [ -122.509063378372446, 37.742130444293039 ], [ -122.50905227925395, 37.742167717855253 ], [ -122.509053541309754, 37.742214395109038 ], [ -122.509066774807934, 37.742256061019717 ], [ -122.509073353587084, 37.742307454847364 ], [ -122.509057438852423, 37.742358545723746 ], [ -122.509051785633744, 37.74240534060808 ], [ -122.509053177611264, 37.742456822871205 ], [ -122.509073957471671, 37.742521709498256 ], [ -122.509091988431635, 37.742548871927937 ], [ -122.509110538400861, 37.742595254410702 ], [ -122.509106614374701, 37.74264202008699 ], [ -122.509124533674893, 37.742665064210797 ], [ -122.50913085233006, 37.742706847741204 ], [ -122.509143845016951, 37.742739590042241 ], [ -122.509166932263881, 37.742761859020533 ], [ -122.50919239764383, 37.742808123575315 ], [ -122.509181187122891, 37.742841278564811 ], [ -122.509159213148223, 37.742860195402194 ], [ -122.509185214866079, 37.742990233896222 ], [ -122.509180659445065, 37.743013660954873 ], [ -122.509194023430766, 37.743060131850363 ], [ -122.509212184156269, 37.743092099550239 ], [ -122.509235029879775, 37.74310544465424 ], [ -122.509248023028022, 37.743138186935724 ], [ -122.509243207416404, 37.743152004255805 ], [ -122.509249786389432, 37.743203397791333 ], [ -122.509267947166521, 37.743235365207241 ], [ -122.50930010317154, 37.743337142122911 ], [ -122.509302627635748, 37.743430496606479 ], [ -122.509334412528517, 37.743518545183825 ], [ -122.509352573411604, 37.743550512310456 ], [ -122.509365807315376, 37.74359217817824 ], [ -122.509378299405142, 37.743606386832958 ], [ -122.50939077259082, 37.743619909062254 ], [ -122.509397091484274, 37.74366169284626 ], [ -122.509409824223226, 37.743684825098981 ], [ -122.509422427717567, 37.743703152328656 ], [ -122.509416403366075, 37.743736218895179 ], [ -122.509417405789478, 37.743773286113324 ], [ -122.509442110915671, 37.74379140724664 ], [ -122.509460290482991, 37.743824061059733 ], [ -122.509467837052512, 37.743847281472782 ], [ -122.509478581442835, 37.743860833174935 ], [ -122.509497132326601, 37.74390721558072 ], [ -122.509505050216035, 37.743944165138849 ], [ -122.509522969938899, 37.743967208647867 ], [ -122.509535572817498, 37.743985535876377 ], [ -122.509565966198053, 37.744022101859116 ], [ -122.509567729859413, 37.744087312700294 ], [ -122.50958033276946, 37.744105639923646 ], [ -122.509604330613158, 37.744161543870057 ], [ -122.509617063502262, 37.744184676098449 ], [ -122.509642400382361, 37.744226135524023 ], [ -122.50963585625108, 37.74423998206241 ], [ -122.509631802164265, 37.744281942740074 ], [ -122.509637749945398, 37.744309998183795 ], [ -122.509655669451874, 37.744333041677137 ], [ -122.509651355796905, 37.744365392330948 ], [ -122.509652358345974, 37.744402459543146 ], [ -122.509682770174749, 37.744439711928955 ], [ -122.509684274023797, 37.744495312746096 ], [ -122.509661202059561, 37.74453759777208 ], [ -122.509656906960942, 37.744570634854803 ], [ -122.509652462986452, 37.744598180504454 ], [ -122.509634336575232, 37.744631453474433 ], [ -122.509635079200464, 37.744658910667582 ], [ -122.509630653432438, 37.744687142751694 ], [ -122.509607841992747, 37.744739038046106 ], [ -122.509616019927876, 37.744785597059909 ], [ -122.509592687920644, 37.744818272053067 ], [ -122.509529982316536, 37.744866040185691 ], [ -122.509506149350315, 37.744880181550315 ], [ -122.509466886438972, 37.744899393264149 ], [ -122.509443442946818, 37.744927949648201 ], [ -122.509461493197179, 37.744955798714784 ], [ -122.509462365703229, 37.744988060641646 ], [ -122.50945818152897, 37.745025216299389 ], [ -122.509441783806565, 37.745058459754802 ], [ -122.509435740789897, 37.745090839884973 ], [ -122.509432447662917, 37.745160944171666 ], [ -122.509397957547932, 37.745292700871097 ], [ -122.509353566936483, 37.745697951409504 ], [ -122.509335859850864, 37.745938614544613 ], [ -122.509427065710852, 37.746369022956515 ], [ -122.509460469864493, 37.746452922841264 ], [ -122.509505472532595, 37.746581950726871 ], [ -122.509517033840751, 37.746625705328505 ], [ -122.509530306206301, 37.746668744003671 ], [ -122.509540138624189, 37.746712528086029 ], [ -122.50955170066463, 37.746756282671512 ], [ -122.509559804532088, 37.746800096229578 ], [ -122.509567926974654, 37.746844596216341 ], [ -122.509574301938841, 37.746888439256921 ], [ -122.50958069512302, 37.74693296845772 ], [ -122.509587070448092, 37.746976811491038 ], [ -122.509590005803872, 37.747021399934077 ], [ -122.509594670781297, 37.747065958880377 ], [ -122.509597084346751, 37.747155194721792 ], [ -122.509772363824212, 37.747496265262193 ], [ -122.509783516941724, 37.747524918382545 ], [ -122.509806418075982, 37.747604190625147 ], [ -122.509810433332206, 37.747624724253029 ], [ -122.509816158624176, 37.747644542243904 ], [ -122.509820173546927, 37.747665076151769 ], [ -122.509822459192577, 37.747685639827814 ], [ -122.509826455889467, 37.747705487025407 ], [ -122.509833313854301, 37.747767177210982 ], [ -122.509835523477918, 37.747848862313226 ], [ -122.509834351235696, 37.747869484689943 ], [ -122.509833161123524, 37.747889420899924 ], [ -122.509831989218355, 37.747910042996011 ], [ -122.509829088027573, 37.747930694860493 ], [ -122.509827897905609, 37.74795063079565 ], [ -122.509824997065294, 37.747971282928546 ], [ -122.509778471867705, 37.748297593791527 ], [ -122.509717126110829, 37.748715495135052 ], [ -122.509711528533515, 37.748764349563523 ], [ -122.509708924691878, 37.748795984010961 ], [ -122.509706339422777, 37.748828305162085 ], [ -122.509705464552383, 37.748859910397449 ], [ -122.509704608248597, 37.748892202061995 ], [ -122.50970373303835, 37.748923807577505 ], [ -122.509705738305371, 37.748997941669188 ], [ -122.508363946491727, 37.74907680068609 ], [ -122.507940323213063, 37.749101694546454 ], [ -122.507941547211104, 37.749110937340276 ], [ -122.507539948063609, 37.749128748547342 ], [ -122.50729515180268, 37.749139604462776 ], [ -122.507286878880223, 37.749139971457424 ], [ -122.50728687853443, 37.749139971463322 ], [ -122.507286886587565, 37.749140090270473 ], [ -122.507286887279179, 37.749140090258685 ], [ -122.507416228284868, 37.751003284446853 ], [ -122.506347526734444, 37.751051944046772 ], [ -122.505276576525688, 37.751093151070108 ], [ -122.504203312772219, 37.751143789774702 ], [ -122.503133002600279, 37.751190357338089 ], [ -122.502060320882052, 37.751239765057655 ], [ -122.500987488435712, 37.75128639412403 ], [ -122.499914456955352, 37.751333766429582 ], [ -122.499914122812314, 37.751333781439868 ], [ -122.49937768349335, 37.751357494487337 ], [ -122.498842248697912, 37.751381160579498 ], [ -122.498442545251379, 37.751398004902029 ], [ -122.497770416046876, 37.751426327269549 ], [ -122.497770415009455, 37.751426327287128 ], [ -122.496702184257288, 37.751476079301845 ], [ -122.49581055415868, 37.751518288226762 ], [ -122.495630257074851, 37.751526822572394 ], [ -122.49509879355503, 37.751544749172808 ], [ -122.495098259533435, 37.751544767553312 ], [ -122.49456080780611, 37.751568259028382 ], [ -122.493488045146222, 37.751613904571322 ], [ -122.492417458516726, 37.751662024433998 ], [ -122.491344845233968, 37.751710225381885 ], [ -122.490270381873529, 37.751758499707634 ], [ -122.48919356840311, 37.751806869260896 ], [ -122.488143190546495, 37.751854042116229 ], [ -122.487053067380344, 37.751903269835523 ], [ -122.48598818277614, 37.751950794949664 ], [ -122.48491485069583, 37.751998192013865 ], [ -122.483844155588841, 37.75204546291927 ], [ -122.48276958366516, 37.752092894785363 ], [ -122.48170210782861, 37.752140004119113 ], [ -122.480628878786973, 37.752187357305665 ], [ -122.479557564859533, 37.752234616335052 ], [ -122.478486061226221, 37.752281873458429 ], [ -122.4774149778289, 37.752329102778496 ], [ -122.476292240229284, 37.752378599147249 ], [ -122.476292239846927, 37.752378597780194 ], [ -122.476291890478677, 37.752378612978674 ], [ -122.476291890529794, 37.752378614900692 ], [ -122.475276421588944, 37.752423947680597 ], [ -122.474740669236724, 37.752447861779451 ], [ -122.474205182950101, 37.752471761013865 ], [ -122.473133075589757, 37.752519603580488 ], [ -122.472007574280582, 37.752569817871482 ], [ -122.470907529168414, 37.752615836450325 ], [ -122.471122146869178, 37.753216025593119 ], [ -122.471174197654705, 37.753356133265093 ], [ -122.471187692098596, 37.753493865266243 ], [ -122.471190660490961, 37.753605743703318 ], [ -122.471171093123701, 37.753733616626192 ], [ -122.471132268263645, 37.753877430013475 ], [ -122.471079638048607, 37.753995444111091 ], [ -122.471036498728921, 37.754100284913058 ], [ -122.471016495996267, 37.754217122306045 ], [ -122.471019321832557, 37.754265617625428 ], [ -122.471049094699751, 37.75451479556704 ], [ -122.471016758783364, 37.754797170870887 ], [ -122.470916907310823, 37.75477064959378 ], [ -122.470428100059209, 37.754750285613063 ], [ -122.470075601814969, 37.754705621480454 ], [ -122.470018818714578, 37.754692394187302 ], [ -122.469719537880266, 37.754630546382018 ], [ -122.469359681800768, 37.754536443616175 ], [ -122.469229538348586, 37.754492641945887 ], [ -122.469187312711114, 37.754478429881637 ], [ -122.469043420308736, 37.754428685878558 ], [ -122.468949194118821, 37.754383817642378 ], [ -122.468837396357713, 37.754298754678686 ], [ -122.46875232271276, 37.754192126033097 ], [ -122.468629004224212, 37.753985873398783 ], [ -122.46853368295227, 37.753778395652951 ], [ -122.468440863346331, 37.753541228150766 ], [ -122.468335071492149, 37.753243730756957 ], [ -122.468181079371874, 37.752955569756089 ], [ -122.468031369650532, 37.75278739056494 ], [ -122.467915171465805, 37.752734664618352 ], [ -122.467804242122298, 37.752723264514827 ], [ -122.467642627702773, 37.752756208616617 ], [ -122.46660049988607, 37.752803078204508 ], [ -122.465533177566329, 37.752851071595934 ], [ -122.464462034762747, 37.752899226938901 ], [ -122.464462034791779, 37.752899228037201 ], [ -122.464462004720289, 37.752899229087859 ], [ -122.463398931733735, 37.75298135079364 ], [ -122.463398833027838, 37.752981215914545 ], [ -122.463165642273253, 37.752743295475163 ], [ -122.462772959403608, 37.752449458308092 ], [ -122.462467546932245, 37.752187424267689 ], [ -122.462176881380387, 37.751866525322576 ], [ -122.461851812215116, 37.751624201717213 ], [ -122.461621806880288, 37.751517360697065 ], [ -122.461523602837886, 37.75150356127773 ], [ -122.461357402458418, 37.751255115112428 ], [ -122.461130726036544, 37.75091814728394 ], [ -122.460646598340617, 37.749905586886342 ], [ -122.460488100882344, 37.749710225074402 ], [ -122.459910793377546, 37.749171745450226 ], [ -122.459625716264128, 37.748941671345293 ], [ -122.459200706652084, 37.748504001538066 ], [ -122.458941590023073, 37.748201275736406 ], [ -122.458816156308444, 37.748040115735854 ], [ -122.458785941599359, 37.747811900102363 ], [ -122.458856680033747, 37.7476570668547 ], [ -122.458970102890007, 37.747514391535915 ], [ -122.459174106111192, 37.747286620942795 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.461381097416904, 37.745569497508143 ], [ -122.463685995946705, 37.743749409879626 ], [ -122.464736798394597, 37.743658625608425 ], [ -122.465806989998114, 37.743574050343277 ], [ -122.466043187720601, 37.743555382675872 ], [ -122.4668594971902, 37.743490862582995 ], [ -122.467747743753378, 37.743412622582262 ], [ -122.468561098363182, 37.741503345390441 ], [ -122.468671688881855, 37.741487738762387 ], [ -122.46903011455143, 37.741472060437133 ], [ -122.470104939284042, 37.741425112172415 ], [ -122.471225939871744, 37.741383106823285 ], [ -122.471094271054184, 37.739515149668783 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.472088826157801, 37.737598476381557 ], [ -122.473161338869033, 37.737550437503423 ], [ -122.47327754880429, 37.737518149435381 ], [ -122.474232714891599, 37.737476308128976 ], [ -122.475288554960258, 37.737454742960537 ], [ -122.476368270747585, 37.737407342172453 ], [ -122.477443897267278, 37.737360110526431 ], [ -122.478500149938654, 37.73731300537402 ], [ -122.479601690542523, 37.737266045133403 ], [ -122.480654887744791, 37.73721905491567 ], [ -122.481727269492282, 37.73717192680251 ], [ -122.484981330342137, 37.737106613405395 ], [ -122.485055057260084, 37.737081691563745 ], [ -122.485398323256604, 37.736886930845941 ], [ -122.485767077957547, 37.736757243866982 ], [ -122.486239320534182, 37.736758103009961 ], [ -122.486865148034397, 37.736893072836374 ], [ -122.487473746906744, 37.73696000969079 ], [ -122.487883946457515, 37.736972903025247 ], [ -122.488261361358497, 37.736979897727672 ], [ -122.488518054177234, 37.737001890327612 ], [ -122.488933343471572, 37.73715804150379 ], [ -122.48917282500021, 37.737387715271872 ], [ -122.489310346380378, 37.737544144541232 ], [ -122.489559638156635, 37.737725144708122 ], [ -122.489794342484018, 37.737821521499782 ], [ -122.489970938868055, 37.737956983380037 ], [ -122.490364187143001, 37.737929559098433 ], [ -122.490475969759899, 37.737832474268195 ], [ -122.490695166321217, 37.737732995785407 ], [ -122.490894410355324, 37.737610557460023 ], [ -122.491113859834144, 37.737397356998116 ], [ -122.491198309070924, 37.737311342176547 ], [ -122.491334899611488, 37.737293023539365 ] ], [ [ -122.459625733970427, 37.748941660612438 ], [ -122.459625856985753, 37.74894158961829 ], [ -122.459625856632712, 37.748941589349464 ], [ -122.459625733970427, 37.748941660612438 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":20,\"ZIP_CODE\":94114,\"ID\":94114},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.451366939667807, 37.758575670531805 ], [ -122.451372847861364, 37.758604122239966 ], [ -122.451553087455522, 37.759472365763493 ], [ -122.449214958651069, 37.759610853827809 ], [ -122.448576884007139, 37.759648639321185 ], [ -122.448534508559618, 37.759045234831405 ], [ -122.448080121328474, 37.759090506458826 ], [ -122.447681986744499, 37.759189835272018 ], [ -122.447394862769713, 37.759372842727622 ], [ -122.447157396221257, 37.759660681065917 ], [ -122.446952816919733, 37.760170129150154 ], [ -122.446866213158088, 37.76042156830691 ], [ -122.446579328750119, 37.76090155472 ], [ -122.446433636964471, 37.761006719141555 ], [ -122.446409895656728, 37.761094344377824 ], [ -122.44652973647915, 37.761396213495424 ], [ -122.446783403627563, 37.761781413728215 ], [ -122.446783402935864, 37.761781413739634 ], [ -122.44642928686055, 37.76181245987528 ], [ -122.446384044294348, 37.761816426422072 ], [ -122.446401427721412, 37.762069683149655 ], [ -122.446391621016517, 37.76223195154369 ], [ -122.44628722735257, 37.762310164748847 ], [ -122.446246565080784, 37.762246873735464 ], [ -122.446167833254592, 37.762219287792306 ], [ -122.445930590545728, 37.762233223779305 ], [ -122.445825769194499, 37.762264137786076 ], [ -122.445692026290132, 37.762347953618033 ], [ -122.444663866724483, 37.763326501903514 ], [ -122.44441170822752, 37.76398692869914 ], [ -122.443434935848089, 37.763875622244342 ], [ -122.443247528217242, 37.763690608015906 ], [ -122.442951036556252, 37.763677959114567 ], [ -122.44294975946913, 37.764109072128207 ], [ -122.443199827408847, 37.764468898600072 ], [ -122.443215275433289, 37.764557545459958 ], [ -122.443278634379993, 37.764726164248771 ], [ -122.443263297846215, 37.765217752592115 ], [ -122.443347278899211, 37.765332794099429 ], [ -122.443210588891787, 37.765339994614024 ], [ -122.442086687309455, 37.765324419445463 ], [ -122.441931912880605, 37.765295099009755 ], [ -122.441241683591315, 37.765270858517731 ], [ -122.439501839688162, 37.766575524599709 ], [ -122.438555364477878, 37.766297386980668 ], [ -122.438386133953244, 37.766664738151036 ], [ -122.438291383601225, 37.766725780787596 ], [ -122.438219625977609, 37.766701399454838 ], [ -122.437879374385105, 37.76661079550852 ], [ -122.437684727134425, 37.766587115085294 ], [ -122.4374853951944, 37.766644323028956 ], [ -122.437333800177683, 37.766762171805773 ], [ -122.437288601235366, 37.766922046842083 ], [ -122.437291829491301, 37.767045826510717 ], [ -122.43729899343451, 37.767221930124791 ], [ -122.436980399947345, 37.767244348801164 ], [ -122.436478247038266, 37.767274662557512 ], [ -122.436625219679314, 37.768918977544402 ], [ -122.436588267341477, 37.76900080227221 ], [ -122.435794213398353, 37.769058453615109 ], [ -122.43489319213792, 37.769113797429938 ], [ -122.433572207534368, 37.769178369516744 ], [ -122.432464341208131, 37.769249650838944 ], [ -122.431570380896062, 37.769307161387864 ], [ -122.431356624058139, 37.76932091157898 ], [ -122.430247361256178, 37.769392261627225 ], [ -122.429905643091914, 37.769407921549679 ], [ -122.429127985682982, 37.769456128057627 ], [ -122.428991181191208, 37.769394223715665 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.426683978032827, 37.769533626427062 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.426902347012131, 37.769049368265463 ], [ -122.426693183304053, 37.767869018742559 ], [ -122.426693183296933, 37.76786901846797 ], [ -122.426615944414863, 37.767069730096857 ], [ -122.426572006355102, 37.766615035704866 ], [ -122.426538465047457, 37.766267922745747 ], [ -122.426518991136163, 37.766066400051031 ], [ -122.426490697526233, 37.765773605772118 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426381708399177, 37.764645687871273 ], [ -122.426317477898522, 37.763970002371671 ], [ -122.426228478902843, 37.763030870557735 ], [ -122.426224290129298, 37.762962298008539 ], [ -122.426204067323269, 37.762631239367813 ], [ -122.426191463523168, 37.762538363286502 ], [ -122.426149283375466, 37.762227539381186 ], [ -122.426149283368346, 37.762227539106618 ], [ -122.426145984060597, 37.762160358437526 ], [ -122.426076501815189, 37.761427133937481 ], [ -122.426076501462219, 37.761427133668562 ], [ -122.425985721165645, 37.760498692937027 ], [ -122.425920302092379, 37.759829613405913 ], [ -122.425900090784694, 37.759619242549249 ], [ -122.425843422635126, 37.759029397589842 ], [ -122.425766807348751, 37.758226828975253 ], [ -122.425766807341631, 37.758226828700685 ], [ -122.425689459367419, 37.757429672933782 ], [ -122.42568945931761, 37.757429671011749 ], [ -122.425645855643012, 37.756979041458848 ], [ -122.425612216172169, 37.756631383038098 ], [ -122.425491892915787, 37.755387845874338 ], [ -122.425457179889037, 37.755029078716305 ], [ -122.425425666412067, 37.754703378159874 ], [ -122.425302616947704, 37.753431591109177 ], [ -122.425302616566313, 37.753431589741957 ], [ -122.425152541686245, 37.751841785894676 ], [ -122.4251525416649, 37.751841785070944 ], [ -122.425122809405835, 37.751528900123922 ], [ -122.425075700414453, 37.751033151258099 ], [ -122.424999224155329, 37.750232718354916 ], [ -122.424999224133984, 37.75023271753119 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.427200037712353, 37.749295720374967 ], [ -122.429422761644645, 37.749157853016314 ], [ -122.431639579453815, 37.749028214467948 ], [ -122.433851103876819, 37.748895972089151 ], [ -122.436067172923103, 37.748762035575652 ], [ -122.438139804541592, 37.748636730271706 ], [ -122.438246126408771, 37.748664500736062 ], [ -122.440402009224243, 37.748642072742477 ], [ -122.440693299152798, 37.748639039270962 ], [ -122.4414080949674, 37.748610013081198 ], [ -122.441477149443557, 37.748591599630018 ], [ -122.441916701553239, 37.748474391279807 ], [ -122.442519008868075, 37.748183215938361 ], [ -122.442584207288078, 37.748079345098553 ], [ -122.443161821262549, 37.747504352694399 ], [ -122.443453448246288, 37.747477579218923 ], [ -122.443473512241141, 37.748002572121706 ], [ -122.443473812008847, 37.74801042178742 ], [ -122.443486020026867, 37.748345516454542 ], [ -122.443434635522365, 37.749091646853778 ], [ -122.44259888979461, 37.749156165729936 ], [ -122.442599224378554, 37.749160242994655 ], [ -122.442583534001741, 37.749266790173017 ], [ -122.442693363008104, 37.750413947729008 ], [ -122.442776988488347, 37.750697651678763 ], [ -122.442782695565214, 37.750773110847675 ], [ -122.442782889125965, 37.750775670827849 ], [ -122.442832455660607, 37.751431039472649 ], [ -122.442817470436609, 37.751454127059596 ], [ -122.442982766064986, 37.751545025906275 ], [ -122.442775383396778, 37.752377050296218 ], [ -122.442775038632988, 37.752378432471772 ], [ -122.442524333279991, 37.752382565152622 ], [ -122.442492558984341, 37.752606697266998 ], [ -122.442239114579749, 37.752829681730105 ], [ -122.442056693562165, 37.753070846058556 ], [ -122.441886398393621, 37.753365106672398 ], [ -122.441807410207034, 37.753470934450007 ], [ -122.441501225348532, 37.753721419417111 ], [ -122.441307496038533, 37.753879905149773 ], [ -122.441162634059779, 37.754090414508028 ], [ -122.44107583131526, 37.754178484324051 ], [ -122.440897310558796, 37.754492600197032 ], [ -122.440449630137792, 37.755065428273227 ], [ -122.440344935902942, 37.755227929193452 ], [ -122.440113197925641, 37.755606296679595 ], [ -122.440032113187002, 37.755796618022245 ], [ -122.440028760145466, 37.755956541681734 ], [ -122.440133892987774, 37.756422684783992 ], [ -122.440296908436025, 37.756652260712109 ], [ -122.440975109290761, 37.756855155327102 ], [ -122.441311420390406, 37.75688501757589 ], [ -122.441891917804512, 37.756707900118627 ], [ -122.442226754965375, 37.756642418026821 ], [ -122.442229654337552, 37.756643729156551 ], [ -122.442234277014876, 37.756645819486522 ], [ -122.442241604390318, 37.756649132680884 ], [ -122.442338618241578, 37.756692996803793 ], [ -122.442524865881779, 37.756832757000566 ], [ -122.442618313376656, 37.756939880143335 ], [ -122.442822513313288, 37.757228212937811 ], [ -122.442914855315252, 37.757360460271151 ], [ -122.443513115466942, 37.757015898193345 ], [ -122.44359095735247, 37.756973190948777 ], [ -122.443933292768804, 37.756707199362893 ], [ -122.44469570432544, 37.75722410905459 ], [ -122.444812173070659, 37.757253442709903 ], [ -122.444964327713464, 37.757272877138014 ], [ -122.444969690519684, 37.757271714308636 ], [ -122.444978623456407, 37.757199356159234 ], [ -122.44531471170977, 37.756562327523987 ], [ -122.445646089047287, 37.756449470405634 ], [ -122.445946403378542, 37.756413329216429 ], [ -122.446950354992993, 37.75655039856936 ], [ -122.446777335972257, 37.756299129719707 ], [ -122.446339300582281, 37.75591138652841 ], [ -122.446125719389443, 37.755803738269194 ], [ -122.445929097870021, 37.755830086405965 ], [ -122.445464111666666, 37.75598407248431 ], [ -122.445268371613437, 37.755920565713375 ], [ -122.445221502655059, 37.755736527336694 ], [ -122.445347807952828, 37.755618935076164 ], [ -122.44578153535943, 37.755506535020608 ], [ -122.446152412695255, 37.755464475455021 ], [ -122.446560265846969, 37.755346452369793 ], [ -122.446809203706863, 37.755349269900414 ], [ -122.447004077099621, 37.75543789136227 ], [ -122.447530410096462, 37.755821232451311 ], [ -122.447682922964361, 37.755868400984937 ], [ -122.447851945395087, 37.755820566236046 ], [ -122.447961600338971, 37.755673235404537 ], [ -122.44794315940031, 37.755531524105997 ], [ -122.447698643463639, 37.754829811793044 ], [ -122.447375994048841, 37.754217662794012 ], [ -122.44720983000947, 37.754042891582813 ], [ -122.447450790660298, 37.753903429730649 ], [ -122.447546138718849, 37.753815754469287 ], [ -122.447656226877982, 37.753773037573623 ], [ -122.447842792647691, 37.753750582173005 ], [ -122.447974401539952, 37.753701051764345 ], [ -122.448075396501437, 37.753621892463592 ], [ -122.448145439689242, 37.753500194902855 ], [ -122.448141772510084, 37.753360343523973 ], [ -122.448073149249666, 37.75322587034826 ], [ -122.44795103635677, 37.753120264118444 ], [ -122.447707056312382, 37.752702854501173 ], [ -122.447536095260375, 37.752481955041475 ], [ -122.447740900123705, 37.752288718875811 ], [ -122.448209648267067, 37.752018216178747 ], [ -122.448345547044426, 37.751925564530218 ], [ -122.44842671498688, 37.751814445339285 ], [ -122.448475010516702, 37.751690954837258 ], [ -122.448479024859196, 37.751533756090204 ], [ -122.448535342970942, 37.751302507611712 ], [ -122.448730034866358, 37.750901860878599 ], [ -122.448837529908516, 37.750828266590112 ], [ -122.449185119457937, 37.750637403226534 ], [ -122.449304804219054, 37.750547171075254 ], [ -122.449423303862474, 37.750411755760133 ], [ -122.449497860416159, 37.750255542747738 ], [ -122.449681795644992, 37.749823083722987 ], [ -122.449716548818643, 37.74953978039732 ], [ -122.449640314591349, 37.749314114769071 ], [ -122.449543092528529, 37.749181799119881 ], [ -122.449400479496887, 37.749106035740034 ], [ -122.449245295819139, 37.74910598186397 ], [ -122.449051151579454, 37.749182809813966 ], [ -122.448885055528038, 37.749338875396077 ], [ -122.448264206892432, 37.750168319632976 ], [ -122.448133451017753, 37.75025012344306 ], [ -122.447957681483132, 37.750270249048668 ], [ -122.447060946228802, 37.749998786557995 ], [ -122.446924377309614, 37.749962298069669 ], [ -122.445803050365413, 37.749296324298363 ], [ -122.445657849639957, 37.749137284365275 ], [ -122.445634816186484, 37.748982685093061 ], [ -122.445696556781456, 37.748854668447294 ], [ -122.445900228666389, 37.74876090078547 ], [ -122.446146312903167, 37.748733159469879 ], [ -122.446321171479013, 37.748781931740275 ], [ -122.446513423639288, 37.748873466020136 ], [ -122.44714942379963, 37.749338660628091 ], [ -122.447347720899344, 37.749453771264633 ], [ -122.447558108041335, 37.749512717159995 ], [ -122.447695695115499, 37.749484613169258 ], [ -122.447794029968421, 37.749407650434719 ], [ -122.448271537838281, 37.748586113005615 ], [ -122.448649270998317, 37.747987545863559 ], [ -122.44873793021948, 37.747816100622572 ], [ -122.449048273698679, 37.747075129958219 ], [ -122.449145702641545, 37.746860421020024 ], [ -122.449310056560847, 37.746715637417083 ], [ -122.44944655863172, 37.746660402786539 ], [ -122.449658366925917, 37.746790485969299 ], [ -122.449829421216677, 37.7468448367704 ], [ -122.449121036812883, 37.748486108456696 ], [ -122.450376213037472, 37.748836617132255 ], [ -122.450378440966844, 37.74915691493446 ], [ -122.450359832675659, 37.74936931962273 ], [ -122.450278577377304, 37.749812923634934 ], [ -122.450120041484055, 37.750377135131941 ], [ -122.450039762259507, 37.750789548444018 ], [ -122.450020192405731, 37.751134853236394 ], [ -122.449818467802629, 37.751170814837231 ], [ -122.44965771853829, 37.751260239674203 ], [ -122.449537611901278, 37.75139818204417 ], [ -122.449421587723563, 37.751919823116488 ], [ -122.449322640472062, 37.752393440459898 ], [ -122.449260062142201, 37.752640188644804 ], [ -122.449222626079077, 37.752826418335388 ], [ -122.44920235250531, 37.75298761609767 ], [ -122.449195253073, 37.753141525321581 ], [ -122.44921167048372, 37.753342773463253 ], [ -122.449250446345715, 37.75354718765756 ], [ -122.449338624202724, 37.75376669417966 ], [ -122.449567372082669, 37.754139767157625 ], [ -122.449714539832698, 37.754072287920813 ], [ -122.45001235604478, 37.753935733822416 ], [ -122.450708861489431, 37.753616367449496 ], [ -122.450797400040912, 37.753766170392268 ], [ -122.450940567642817, 37.75387933530601 ], [ -122.451110760292124, 37.753931215767722 ], [ -122.451360262589361, 37.75396141724395 ], [ -122.451594424355719, 37.753898832915297 ], [ -122.452570281407503, 37.75354206761132 ], [ -122.452736650019233, 37.753666208179531 ], [ -122.453410821175368, 37.753818557980566 ], [ -122.454229599548796, 37.754062132001749 ], [ -122.454309928578283, 37.75416682276385 ], [ -122.454154362960978, 37.755029376887862 ], [ -122.454089386335994, 37.755285428850698 ], [ -122.453643273006946, 37.755857139298925 ], [ -122.453512429965073, 37.756126075057203 ], [ -122.453345167458252, 37.756697980973108 ], [ -122.453533591760348, 37.756819465356074 ], [ -122.453617386887871, 37.756948631540013 ], [ -122.453769459328285, 37.75746785416225 ], [ -122.453652871332849, 37.757570195093017 ], [ -122.453594261080696, 37.757621283813904 ], [ -122.453326709060633, 37.757812928758618 ], [ -122.453317408957801, 37.757819578594621 ], [ -122.452876092298638, 37.758073671104697 ], [ -122.45283476350059, 37.758097464572913 ], [ -122.45229781656198, 37.758277073170099 ], [ -122.452047941226652, 37.758343705002019 ], [ -122.451408535854284, 37.758561502025017 ], [ -122.45136693942257, 37.758575661196261 ], [ -122.451366939667807, 37.758575670531805 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":21,\"ZIP_CODE\":94131,\"ID\":94131},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.453769459328285, 37.75746785416225 ], [ -122.453617386887871, 37.756948631540013 ], [ -122.453533591760348, 37.756819465356074 ], [ -122.453345167458252, 37.756697980973108 ], [ -122.453512429965073, 37.756126075057203 ], [ -122.453643273006946, 37.755857139298925 ], [ -122.454089386335994, 37.755285428850698 ], [ -122.454154362960978, 37.755029376887862 ], [ -122.454309928578283, 37.75416682276385 ], [ -122.454229599548796, 37.754062132001749 ], [ -122.453410821175368, 37.753818557980566 ], [ -122.452736650019233, 37.753666208179531 ], [ -122.452570281407503, 37.75354206761132 ], [ -122.451594424355719, 37.753898832915297 ], [ -122.451360262589361, 37.75396141724395 ], [ -122.451110760292124, 37.753931215767722 ], [ -122.450940567642817, 37.75387933530601 ], [ -122.450797400040912, 37.753766170392268 ], [ -122.450708861489431, 37.753616367449496 ], [ -122.45001235604478, 37.753935733822416 ], [ -122.449714539832698, 37.754072287920813 ], [ -122.449567372082669, 37.754139767157625 ], [ -122.449338624202724, 37.75376669417966 ], [ -122.449250446345715, 37.75354718765756 ], [ -122.44921167048372, 37.753342773463253 ], [ -122.449195253073, 37.753141525321581 ], [ -122.44920235250531, 37.75298761609767 ], [ -122.449222626079077, 37.752826418335388 ], [ -122.449260062142201, 37.752640188644804 ], [ -122.449322640472062, 37.752393440459898 ], [ -122.449421587723563, 37.751919823116488 ], [ -122.449537611901278, 37.75139818204417 ], [ -122.44965771853829, 37.751260239674203 ], [ -122.449818467802629, 37.751170814837231 ], [ -122.450020192405731, 37.751134853236394 ], [ -122.450039762259507, 37.750789548444018 ], [ -122.450120041484055, 37.750377135131941 ], [ -122.450278577377304, 37.749812923634934 ], [ -122.450359832675659, 37.74936931962273 ], [ -122.450378440966844, 37.74915691493446 ], [ -122.450376213037472, 37.748836617132255 ], [ -122.449121036812883, 37.748486108456696 ], [ -122.449829421216677, 37.7468448367704 ], [ -122.449658366925917, 37.746790485969299 ], [ -122.44944655863172, 37.746660402786539 ], [ -122.449310056560847, 37.746715637417083 ], [ -122.449145702641545, 37.746860421020024 ], [ -122.449048273698679, 37.747075129958219 ], [ -122.44873793021948, 37.747816100622572 ], [ -122.448649270998317, 37.747987545863559 ], [ -122.448271537838281, 37.748586113005615 ], [ -122.447794029968421, 37.749407650434719 ], [ -122.447695695115499, 37.749484613169258 ], [ -122.447558108041335, 37.749512717159995 ], [ -122.447347720899344, 37.749453771264633 ], [ -122.44714942379963, 37.749338660628091 ], [ -122.446513423639288, 37.748873466020136 ], [ -122.446321171479013, 37.748781931740275 ], [ -122.446146312903167, 37.748733159469879 ], [ -122.445900228666389, 37.74876090078547 ], [ -122.445696556781456, 37.748854668447294 ], [ -122.445634816186484, 37.748982685093061 ], [ -122.445657849639957, 37.749137284365275 ], [ -122.445803050365413, 37.749296324298363 ], [ -122.446924377309614, 37.749962298069669 ], [ -122.447060946228802, 37.749998786557995 ], [ -122.447957681483132, 37.750270249048668 ], [ -122.448133451017753, 37.75025012344306 ], [ -122.448264206892432, 37.750168319632976 ], [ -122.448885055528038, 37.749338875396077 ], [ -122.449051151579454, 37.749182809813966 ], [ -122.449245295819139, 37.74910598186397 ], [ -122.449400479496887, 37.749106035740034 ], [ -122.449543092528529, 37.749181799119881 ], [ -122.449640314591349, 37.749314114769071 ], [ -122.449716548818643, 37.74953978039732 ], [ -122.449681795644992, 37.749823083722987 ], [ -122.449497860416159, 37.750255542747738 ], [ -122.449423303862474, 37.750411755760133 ], [ -122.449304804219054, 37.750547171075254 ], [ -122.449185119457937, 37.750637403226534 ], [ -122.448837529908516, 37.750828266590112 ], [ -122.448730034866358, 37.750901860878599 ], [ -122.448535342970942, 37.751302507611712 ], [ -122.448479024859196, 37.751533756090204 ], [ -122.448475010516702, 37.751690954837258 ], [ -122.44842671498688, 37.751814445339285 ], [ -122.448345547044426, 37.751925564530218 ], [ -122.448209648267067, 37.752018216178747 ], [ -122.447740900123705, 37.752288718875811 ], [ -122.447536095260375, 37.752481955041475 ], [ -122.447707056312382, 37.752702854501173 ], [ -122.44795103635677, 37.753120264118444 ], [ -122.448073149249666, 37.75322587034826 ], [ -122.448141772510084, 37.753360343523973 ], [ -122.448145439689242, 37.753500194902855 ], [ -122.448075396501437, 37.753621892463592 ], [ -122.447974401539952, 37.753701051764345 ], [ -122.447842792647691, 37.753750582173005 ], [ -122.447656226877982, 37.753773037573623 ], [ -122.447546138718849, 37.753815754469287 ], [ -122.447450790660298, 37.753903429730649 ], [ -122.44720983000947, 37.754042891582813 ], [ -122.447375994048841, 37.754217662794012 ], [ -122.447698643463639, 37.754829811793044 ], [ -122.44794315940031, 37.755531524105997 ], [ -122.447961600338971, 37.755673235404537 ], [ -122.447851945395087, 37.755820566236046 ], [ -122.447682922964361, 37.755868400984937 ], [ -122.447530410096462, 37.755821232451311 ], [ -122.447004077099621, 37.75543789136227 ], [ -122.446809203706863, 37.755349269900414 ], [ -122.446560265846969, 37.755346452369793 ], [ -122.446152412695255, 37.755464475455021 ], [ -122.44578153535943, 37.755506535020608 ], [ -122.445347807952828, 37.755618935076164 ], [ -122.445221502655059, 37.755736527336694 ], [ -122.445268371613437, 37.755920565713375 ], [ -122.445464111666666, 37.75598407248431 ], [ -122.445929097870021, 37.755830086405965 ], [ -122.446125719389443, 37.755803738269194 ], [ -122.446339300582281, 37.75591138652841 ], [ -122.446777335972257, 37.756299129719707 ], [ -122.446950354992993, 37.75655039856936 ], [ -122.445946403378542, 37.756413329216429 ], [ -122.445646089047287, 37.756449470405634 ], [ -122.44531471170977, 37.756562327523987 ], [ -122.444978623456407, 37.757199356159234 ], [ -122.444969690519684, 37.757271714308636 ], [ -122.444964327713464, 37.757272877138014 ], [ -122.444812173070659, 37.757253442709903 ], [ -122.44469570432544, 37.75722410905459 ], [ -122.443933292768804, 37.756707199362893 ], [ -122.44359095735247, 37.756973190948777 ], [ -122.443513115466942, 37.757015898193345 ], [ -122.442914855315252, 37.757360460271151 ], [ -122.442822513313288, 37.757228212937811 ], [ -122.442618313376656, 37.756939880143335 ], [ -122.442524865881779, 37.756832757000566 ], [ -122.442338618241578, 37.756692996803793 ], [ -122.442241604390318, 37.756649132680884 ], [ -122.442234277014876, 37.756645819486522 ], [ -122.442229654337552, 37.756643729156551 ], [ -122.442226754965375, 37.756642418026821 ], [ -122.441891917804512, 37.756707900118627 ], [ -122.441311420390406, 37.75688501757589 ], [ -122.440975109290761, 37.756855155327102 ], [ -122.440296908436025, 37.756652260712109 ], [ -122.440133892987774, 37.756422684783992 ], [ -122.440028760145466, 37.755956541681734 ], [ -122.440032113187002, 37.755796618022245 ], [ -122.440113197925641, 37.755606296679595 ], [ -122.440344935902942, 37.755227929193452 ], [ -122.440449630137792, 37.755065428273227 ], [ -122.440897310558796, 37.754492600197032 ], [ -122.44107583131526, 37.754178484324051 ], [ -122.441162634059779, 37.754090414508028 ], [ -122.441307496038533, 37.753879905149773 ], [ -122.441501225348532, 37.753721419417111 ], [ -122.441807410207034, 37.753470934450007 ], [ -122.441886398393621, 37.753365106672398 ], [ -122.442056693562165, 37.753070846058556 ], [ -122.442239114579749, 37.752829681730105 ], [ -122.442492558984341, 37.752606697266998 ], [ -122.442524333279991, 37.752382565152622 ], [ -122.442775038632988, 37.752378432471772 ], [ -122.442775383396778, 37.752377050296218 ], [ -122.442982766064986, 37.751545025906275 ], [ -122.442817470436609, 37.751454127059596 ], [ -122.442832455660607, 37.751431039472649 ], [ -122.442782889125965, 37.750775670827849 ], [ -122.442782695565214, 37.750773110847675 ], [ -122.442776988488347, 37.750697651678763 ], [ -122.442693363008104, 37.750413947729008 ], [ -122.442583534001741, 37.749266790173017 ], [ -122.442599224378554, 37.749160242994655 ], [ -122.44259888979461, 37.749156165729936 ], [ -122.443434635522365, 37.749091646853778 ], [ -122.443486020026867, 37.748345516454542 ], [ -122.443473812008847, 37.74801042178742 ], [ -122.443473512241141, 37.748002572121706 ], [ -122.443453448246288, 37.747477579218923 ], [ -122.443161821262549, 37.747504352694399 ], [ -122.442584207288078, 37.748079345098553 ], [ -122.442519008868075, 37.748183215938361 ], [ -122.441916701553239, 37.748474391279807 ], [ -122.441477149443557, 37.748591599630018 ], [ -122.4414080949674, 37.748610013081198 ], [ -122.440693299152798, 37.748639039270962 ], [ -122.440402009224243, 37.748642072742477 ], [ -122.438246126408771, 37.748664500736062 ], [ -122.438139804541592, 37.748636730271706 ], [ -122.436067172923103, 37.748762035575652 ], [ -122.433851103876819, 37.748895972089151 ], [ -122.431639579453815, 37.749028214467948 ], [ -122.429422761644645, 37.749157853016314 ], [ -122.427200037712353, 37.749295720374967 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.424845024283727, 37.748632587554638 ], [ -122.424845018024243, 37.748632519532975 ], [ -122.424841979744244, 37.748600757493776 ], [ -122.424768284589831, 37.747830340365347 ], [ -122.424768280884535, 37.747830304166378 ], [ -122.424689793548367, 37.747033402901096 ], [ -122.424602793343411, 37.746241400149849 ], [ -122.424538964982034, 37.745435717069675 ], [ -122.424459208282684, 37.744635998834639 ], [ -122.424384862816794, 37.743838216035606 ], [ -122.424309369085989, 37.7430347552692 ], [ -122.424233277120777, 37.742236301639601 ], [ -122.424233272287097, 37.742236301993223 ], [ -122.424233472639315, 37.742235957277522 ], [ -122.424233478510288, 37.742235956906946 ], [ -122.424301394893192, 37.742119600447609 ], [ -122.42417135664968, 37.740713439585669 ], [ -122.42413709700044, 37.740335654620864 ], [ -122.424258152196032, 37.739934707389949 ], [ -122.42414746787631, 37.739869414698418 ], [ -122.424147154546787, 37.739869230001858 ], [ -122.424147154179678, 37.739869229183775 ], [ -122.424063599996956, 37.739784297520274 ], [ -122.424284283357963, 37.738949691036076 ], [ -122.424531564221411, 37.738368292265967 ], [ -122.424696469855888, 37.738101223886673 ], [ -122.424711669489241, 37.738076607801148 ], [ -122.424904804743889, 37.737763817504508 ], [ -122.425110714349969, 37.737550673136937 ], [ -122.425230653676863, 37.737426519232308 ], [ -122.425439441891498, 37.737210392933726 ], [ -122.425832974995387, 37.736873263301263 ], [ -122.426500890836181, 37.73630106743056 ], [ -122.426544994076067, 37.736269396547655 ], [ -122.42729729676671, 37.735729149898575 ], [ -122.427738169173168, 37.735412543347451 ], [ -122.42783624298761, 37.735347761815902 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.42843713263963, 37.734951046304573 ], [ -122.428766264762999, 37.73481932892178 ], [ -122.429365174143101, 37.734579643964238 ], [ -122.429606994304805, 37.734482866043706 ], [ -122.429797927680781, 37.734406452684077 ], [ -122.429799382942633, 37.734405632509876 ], [ -122.430400796753801, 37.734118700718604 ], [ -122.430928172484656, 37.733867087971888 ], [ -122.430931197463138, 37.733865940717834 ], [ -122.43132522808267, 37.733716495194997 ], [ -122.432572047381726, 37.733243596430682 ], [ -122.432720773871381, 37.733187185661393 ], [ -122.434440716858973, 37.732534804028113 ], [ -122.434512093568429, 37.732323526771246 ], [ -122.434644432402948, 37.732322139093768 ], [ -122.434823956379304, 37.732249430441868 ], [ -122.434989951388332, 37.732296516038524 ], [ -122.434992781477959, 37.732295287547394 ], [ -122.435457687984979, 37.732093484109448 ], [ -122.436668162612392, 37.731543298105045 ], [ -122.436678949019353, 37.731538395224071 ], [ -122.436835967120587, 37.731467212874229 ], [ -122.437166749161392, 37.731365897534992 ], [ -122.438692929843569, 37.730737998359416 ], [ -122.439686065703896, 37.730325720705899 ], [ -122.43969146797258, 37.730323477880063 ], [ -122.440664990612632, 37.729919329476168 ], [ -122.442010214558891, 37.729910949382834 ], [ -122.442006614261629, 37.729375267903364 ], [ -122.442082956691792, 37.729308363921405 ], [ -122.442595296877187, 37.729101680603421 ], [ -122.442954953118544, 37.728915555981267 ], [ -122.443973112888827, 37.728379877183251 ], [ -122.444131311025359, 37.72831838217401 ], [ -122.444276984301908, 37.728261755504967 ], [ -122.44427751391413, 37.728322242080615 ], [ -122.444284269547836, 37.729093639959018 ], [ -122.444291303797655, 37.729896860703782 ], [ -122.444298409699798, 37.730708221303239 ], [ -122.444294941941365, 37.731069863889161 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.444299518643774, 37.732277163251723 ], [ -122.44431434932487, 37.732346901474607 ], [ -122.444320397855094, 37.733087687518228 ], [ -122.444320121334613, 37.733811072362919 ], [ -122.445605378551107, 37.733800711254226 ], [ -122.445782219446897, 37.733850288395629 ], [ -122.445695180894745, 37.733970768223749 ], [ -122.445385609625191, 37.734245207403205 ], [ -122.445350300553969, 37.734322189420951 ], [ -122.445353637361492, 37.734548793463254 ], [ -122.444336617762318, 37.734557437927307 ], [ -122.442971182760076, 37.734564427784264 ], [ -122.44298838327488, 37.734915120161688 ], [ -122.443241645458286, 37.734935120886668 ], [ -122.443498916758756, 37.735041781619209 ], [ -122.443868794328736, 37.73522293273804 ], [ -122.444190291728404, 37.735407445883219 ], [ -122.444280987817251, 37.735484218369663 ], [ -122.444414926545633, 37.735547154436588 ], [ -122.444456723960371, 37.735604611251425 ], [ -122.444478073435704, 37.735720552668923 ], [ -122.444433832419023, 37.736070356056409 ], [ -122.444405709994072, 37.736218559056141 ], [ -122.444306066715512, 37.736409368208768 ], [ -122.444187688286519, 37.736528330103852 ], [ -122.444038854214782, 37.736575290313169 ], [ -122.443842427221611, 37.736579809812575 ], [ -122.443683487927487, 37.73653803376142 ], [ -122.443602235926093, 37.736502583404238 ], [ -122.442657322787326, 37.737118881702763 ], [ -122.442488947503193, 37.737289169592358 ], [ -122.442421017736137, 37.737438198043598 ], [ -122.442403843641571, 37.737639851367689 ], [ -122.44244617419956, 37.737799536516135 ], [ -122.442579349780331, 37.738171569110513 ], [ -122.442625226229453, 37.738313911390271 ], [ -122.442627783322195, 37.738564600951854 ], [ -122.44250398997913, 37.739238469269011 ], [ -122.442525191527608, 37.739448400155375 ], [ -122.442597107319372, 37.739547858254923 ], [ -122.44271760477325, 37.739682237717481 ], [ -122.442856748556963, 37.739767263698369 ], [ -122.443019593631774, 37.739814475654143 ], [ -122.44341918472557, 37.739902333376541 ], [ -122.443694223373313, 37.739971345925731 ], [ -122.443922038304507, 37.740088462716592 ], [ -122.444098337188322, 37.740234069945657 ], [ -122.444209640252396, 37.740380617798955 ], [ -122.444366170223589, 37.740575544006177 ], [ -122.444518249198694, 37.740692998244214 ], [ -122.444681667799003, 37.740793092051724 ], [ -122.444911120433531, 37.740872114678645 ], [ -122.445195592772691, 37.740920623913645 ], [ -122.445523457193502, 37.740972470791057 ], [ -122.445897881975142, 37.741116602917764 ], [ -122.445964326755799, 37.741198730714288 ], [ -122.445964300022951, 37.741198753406074 ], [ -122.44605943275991, 37.741274575873817 ], [ -122.44609965968975, 37.741357693405078 ], [ -122.446156199006268, 37.741535311478742 ], [ -122.44619443459527, 37.741872554265036 ], [ -122.446256735689062, 37.742072052701658 ], [ -122.446323687641325, 37.742184944882247 ], [ -122.446410971977912, 37.742281706494659 ], [ -122.446516770315029, 37.742358933848621 ], [ -122.446657325560608, 37.742442453734959 ], [ -122.446840940400392, 37.742519081676662 ], [ -122.447110638216571, 37.742580552169777 ], [ -122.447384659150103, 37.742608987228614 ], [ -122.44848892294759, 37.742704730693937 ], [ -122.448820428983325, 37.742748693538836 ], [ -122.44913731277687, 37.742828607669701 ], [ -122.449355958412127, 37.74292182098899 ], [ -122.449582834428256, 37.743065029012413 ], [ -122.449790244582587, 37.743257317336237 ], [ -122.449918612648133, 37.7432716748716 ], [ -122.450592179132485, 37.744090792307063 ], [ -122.450586742649079, 37.744213121547148 ], [ -122.451624634588612, 37.745534387540218 ], [ -122.45169226258335, 37.745606748366605 ], [ -122.45181666119403, 37.745671246259803 ], [ -122.451828632556612, 37.745671851179097 ], [ -122.452091117231191, 37.74568511552728 ], [ -122.45222754649042, 37.74569200904012 ], [ -122.452386953750349, 37.745691424670198 ], [ -122.453376023619995, 37.74568779337109 ], [ -122.453495886255311, 37.745691252478657 ], [ -122.453645157964232, 37.745695560010326 ], [ -122.453916002908102, 37.745699915076791 ], [ -122.453916429980183, 37.745699922279499 ], [ -122.454184426826714, 37.745845539203287 ], [ -122.454664096869323, 37.746106168082655 ], [ -122.454664097215115, 37.746106168076913 ], [ -122.455466732538028, 37.746327980692172 ], [ -122.455687935660293, 37.746380796419501 ], [ -122.456314708164584, 37.746530446031059 ], [ -122.457089836487938, 37.746682896092409 ], [ -122.457166741019165, 37.746698021157933 ], [ -122.457301186299645, 37.746714064689691 ], [ -122.457457161051892, 37.746732677593577 ], [ -122.457582024204598, 37.746744476612157 ], [ -122.457949639713704, 37.746779214661537 ], [ -122.458499844031735, 37.746781514247793 ], [ -122.458509634961302, 37.746778882011192 ], [ -122.458610345848541, 37.746751806518411 ], [ -122.45872851739027, 37.746755429911865 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.459174106111192, 37.747286620942795 ], [ -122.458970102890007, 37.747514391535915 ], [ -122.458856680033747, 37.7476570668547 ], [ -122.458785941599359, 37.747811900102363 ], [ -122.458816156308444, 37.748040115735854 ], [ -122.458941590023073, 37.748201275736406 ], [ -122.459200706652084, 37.748504001538066 ], [ -122.459625716264128, 37.748941671345293 ], [ -122.459724182852014, 37.749021140146965 ], [ -122.459910557162914, 37.749171866124314 ], [ -122.460150106375522, 37.749394963907797 ], [ -122.460488100882344, 37.749710225074402 ], [ -122.460574698392477, 37.749816963786891 ], [ -122.460646032705213, 37.749905429557089 ], [ -122.460872188804359, 37.750377416930355 ], [ -122.461130726036544, 37.75091814728394 ], [ -122.461357402458418, 37.751255115112428 ], [ -122.461523490818323, 37.75150339365527 ], [ -122.461523490133942, 37.751503393941363 ], [ -122.461523602837886, 37.75150356127773 ], [ -122.461621806880288, 37.751517360697065 ], [ -122.461851812215116, 37.751624201717213 ], [ -122.462014174664134, 37.751745235464952 ], [ -122.462176783557467, 37.751866685725204 ], [ -122.462467214248704, 37.7521873638808 ], [ -122.462572755978741, 37.752277690485144 ], [ -122.462772959403608, 37.752449458308092 ], [ -122.463129733530764, 37.752716425489766 ], [ -122.463165176689103, 37.75274298458443 ], [ -122.463398089008777, 37.752981523058104 ], [ -122.463399006031565, 37.752981452291863 ], [ -122.463640406958334, 37.753310851043111 ], [ -122.463777683069083, 37.753601657228067 ], [ -122.463842129576008, 37.753808596342253 ], [ -122.463838893966539, 37.75414750585891 ], [ -122.463722821262465, 37.754601920791259 ], [ -122.46367889740506, 37.754738571878264 ], [ -122.463627289771182, 37.754788065181785 ], [ -122.463580718915097, 37.755014148901829 ], [ -122.463554499932727, 37.755298102249476 ], [ -122.463651620775778, 37.756677450683235 ], [ -122.463781528402038, 37.758538181378015 ], [ -122.463911891482155, 37.760405344350922 ], [ -122.46284191307069, 37.760452254741963 ], [ -122.461770200794135, 37.760499231230455 ], [ -122.461770397151241, 37.760502041661596 ], [ -122.460684658382874, 37.760546523892096 ], [ -122.46084509456081, 37.762627108799968 ], [ -122.460842964058216, 37.762627642539876 ], [ -122.459790374597731, 37.762925938047687 ], [ -122.457827081738984, 37.763482288038738 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.456669912105653, 37.763814688946653 ], [ -122.455781065686594, 37.764070070210742 ], [ -122.455466091062405, 37.764160565952579 ], [ -122.454217265424234, 37.764310945798535 ], [ -122.454179864833876, 37.763912200042988 ], [ -122.454845151642289, 37.763732313486969 ], [ -122.454945541962573, 37.76366305305779 ], [ -122.454946081907835, 37.7631787014639 ], [ -122.454009611320302, 37.762667869264533 ], [ -122.453744673977482, 37.761878784498755 ], [ -122.453114639516684, 37.762361563728888 ], [ -122.45351591398105, 37.764395394431268 ], [ -122.452569575903098, 37.764509334437378 ], [ -122.452569519449398, 37.764509056008684 ], [ -122.452400386663143, 37.763669233221897 ], [ -122.45223333012629, 37.762842423991209 ], [ -122.452096203587132, 37.762166887302627 ], [ -122.451957007208804, 37.761478336720671 ], [ -122.45155310706329, 37.759472203369178 ], [ -122.45136693770776, 37.758575661774039 ], [ -122.451408535854284, 37.758561502025017 ], [ -122.452047941226652, 37.758343705002019 ], [ -122.45229781656198, 37.758277073170099 ], [ -122.45283476350059, 37.758097464572913 ], [ -122.452876092298638, 37.758073671104697 ], [ -122.453317408957801, 37.757819578594621 ], [ -122.453326709060633, 37.757812928758618 ], [ -122.453652871332849, 37.757570195093017 ], [ -122.453769459328285, 37.75746785416225 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":22,\"ZIP_CODE\":94122,\"ID\":94122},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.463399006031565, 37.752981452291863 ], [ -122.464461497345539, 37.752899248258529 ], [ -122.464461497323754, 37.752899247434819 ], [ -122.464462034791779, 37.752899228037201 ], [ -122.464462034762747, 37.752899226938901 ], [ -122.465533177566329, 37.752851071595934 ], [ -122.46660049988607, 37.752803078204508 ], [ -122.467642627702773, 37.752756208616617 ], [ -122.467804242122298, 37.752723264514827 ], [ -122.467915171465805, 37.752734664618352 ], [ -122.468031369650532, 37.75278739056494 ], [ -122.468181079371874, 37.752955569756089 ], [ -122.468335071492149, 37.753243730756957 ], [ -122.468440863346331, 37.753541228150766 ], [ -122.46853368295227, 37.753778395652951 ], [ -122.468629004224212, 37.753985873398783 ], [ -122.46875232271276, 37.754192126033097 ], [ -122.468837396357713, 37.754298754678686 ], [ -122.468949194118821, 37.754383817642378 ], [ -122.469043420308736, 37.754428685878558 ], [ -122.469187312711114, 37.754478429881637 ], [ -122.469229538348586, 37.754492641945887 ], [ -122.469359681800768, 37.754536443616175 ], [ -122.469719537880266, 37.754630546382018 ], [ -122.470018818714578, 37.754692394187302 ], [ -122.470075601814969, 37.754705621480454 ], [ -122.470428100059209, 37.754750285613063 ], [ -122.470916907310823, 37.75477064959378 ], [ -122.471016758783364, 37.754797170870887 ], [ -122.471049094699751, 37.75451479556704 ], [ -122.471019321832557, 37.754265617625428 ], [ -122.471016495996267, 37.754217122306045 ], [ -122.471036498728921, 37.754100284913058 ], [ -122.471079638048607, 37.753995444111091 ], [ -122.471132268263645, 37.753877430013475 ], [ -122.471171093123701, 37.753733616626192 ], [ -122.471190660490961, 37.753605743703318 ], [ -122.471187692098596, 37.753493865266243 ], [ -122.471174197654705, 37.753356133265093 ], [ -122.471122146869178, 37.753216025593119 ], [ -122.470907529168414, 37.752615836450325 ], [ -122.472007574280582, 37.752569817871482 ], [ -122.473133075589757, 37.752519603580488 ], [ -122.474205182950101, 37.752471761013865 ], [ -122.474740669236724, 37.752447861779451 ], [ -122.475276421588944, 37.752423947680597 ], [ -122.476291890529794, 37.752378614900692 ], [ -122.476291890478677, 37.752378612978674 ], [ -122.476292239846927, 37.752378597780194 ], [ -122.476292240229284, 37.752378599147249 ], [ -122.4774149778289, 37.752329102778496 ], [ -122.478486061226221, 37.752281873458429 ], [ -122.479557564859533, 37.752234616335052 ], [ -122.480628878786973, 37.752187357305665 ], [ -122.48170210782861, 37.752140004119113 ], [ -122.48276958366516, 37.752092894785363 ], [ -122.483844155588841, 37.75204546291927 ], [ -122.48491485069583, 37.751998192013865 ], [ -122.48598818277614, 37.751950794949664 ], [ -122.487053067380344, 37.751903269835523 ], [ -122.488143190546495, 37.751854042116229 ], [ -122.48919356840311, 37.751806869260896 ], [ -122.490270381873529, 37.751758499707634 ], [ -122.491344845233968, 37.751710225381885 ], [ -122.492417458516726, 37.751662024433998 ], [ -122.493488045146222, 37.751613904571322 ], [ -122.49456080780611, 37.751568259028382 ], [ -122.495098259533435, 37.751544767553312 ], [ -122.49509879355503, 37.751544749172808 ], [ -122.495630257074851, 37.751526822572394 ], [ -122.49581055415868, 37.751518288226762 ], [ -122.496702184257288, 37.751476079301845 ], [ -122.497770415009455, 37.751426327287128 ], [ -122.497770416046876, 37.751426327269549 ], [ -122.498442545251379, 37.751398004902029 ], [ -122.498842248697912, 37.751381160579498 ], [ -122.49937768349335, 37.751357494487337 ], [ -122.499914122812314, 37.751333781439868 ], [ -122.499914456955352, 37.751333766429582 ], [ -122.500987488435712, 37.75128639412403 ], [ -122.502060320882052, 37.751239765057655 ], [ -122.503133002600279, 37.751190357338089 ], [ -122.504203312772219, 37.751143789774702 ], [ -122.505276576525688, 37.751093151070108 ], [ -122.506347526734444, 37.751051944046772 ], [ -122.507416228284868, 37.751003284446853 ], [ -122.507286887279179, 37.749140090258685 ], [ -122.507286886587565, 37.749140090270473 ], [ -122.50728687853443, 37.749139971463322 ], [ -122.507286878880223, 37.749139971457424 ], [ -122.50729515180268, 37.749139604462776 ], [ -122.507539948063609, 37.749128748547342 ], [ -122.507941547211104, 37.749110937340276 ], [ -122.507940323213063, 37.749101694546454 ], [ -122.508363946491727, 37.74907680068609 ], [ -122.509705738305371, 37.748997941669188 ], [ -122.509710663420336, 37.749052110606286 ], [ -122.509713246503537, 37.749083657142556 ], [ -122.509729549623728, 37.749110848942742 ], [ -122.509729362100501, 37.749167851780079 ], [ -122.509735273166726, 37.749194534062582 ], [ -122.509750516130779, 37.749246466794162 ], [ -122.509778567561426, 37.749324277699941 ], [ -122.509789628452893, 37.749349498384071 ], [ -122.509798960033251, 37.749374748835095 ], [ -122.509811768849048, 37.749400627001869 ], [ -122.509822811194553, 37.749425161253306 ], [ -122.509835600759672, 37.749450352725269 ], [ -122.509848372456204, 37.749474857754677 ], [ -122.509862872798479, 37.749499333300058 ], [ -122.509875644166641, 37.74952383833223 ], [ -122.509890144527432, 37.749548313874023 ], [ -122.509938776915888, 37.749619592731861 ], [ -122.50997116142004, 37.749665739100415 ], [ -122.509989082682068, 37.749688783065459 ], [ -122.51000698537004, 37.749711140049378 ], [ -122.510074896863927, 37.74979170473398 ], [ -122.510112524214264, 37.749839821874914 ], [ -122.510150170185028, 37.749888625432611 ], [ -122.510222041423717, 37.749987664081871 ], [ -122.510254537700391, 37.750037928944337 ], [ -122.510319567532292, 37.750139831499247 ], [ -122.510350372089775, 37.750191498687315 ], [ -122.510379447689871, 37.750243195362941 ], [ -122.510406812903227, 37.750295607956453 ], [ -122.510434196383827, 37.75034870697808 ], [ -122.510450965102024, 37.750393059386766 ], [ -122.51046407081715, 37.750429920079867 ], [ -122.510475448231503, 37.75046681025696 ], [ -122.510486843547952, 37.750504387148105 ], [ -122.510515843893401, 37.750617205185918 ], [ -122.510523781572246, 37.750654840788101 ], [ -122.510529990248557, 37.75069250588782 ], [ -122.510536217506655, 37.75073085741591 ], [ -122.510540697183259, 37.750768552012921 ], [ -122.510545195439974, 37.750806933038461 ], [ -122.510549675125716, 37.750844627634628 ], [ -122.510552444030878, 37.750883038163828 ], [ -122.510555546179489, 37.75099767207351 ], [ -122.510554168969648, 37.751074611106283 ], [ -122.510551732248842, 37.751112423700818 ], [ -122.510547585771619, 37.751150952210686 ], [ -122.510543420361202, 37.751188794022582 ], [ -122.510539273529616, 37.751227322537567 ], [ -122.510534624806738, 37.751247317477244 ], [ -122.51052982817599, 37.751261821248107 ], [ -122.510526778782435, 37.751276981406193 ], [ -122.510523710819541, 37.75129145540982 ], [ -122.510522371541924, 37.751305900195682 ], [ -122.510519303915359, 37.751320373918666 ], [ -122.510517983550216, 37.751335504852847 ], [ -122.510514915590633, 37.75134997913068 ], [ -122.510513595216636, 37.751365109790179 ], [ -122.510513985300733, 37.75137952479723 ], [ -122.510512664933444, 37.751394655731197 ], [ -122.510513055017512, 37.751409070738212 ], [ -122.510511734303734, 37.751424201677992 ], [ -122.510512533054808, 37.751453718395311 ], [ -122.510514671081651, 37.751468790052584 ], [ -122.510515061166473, 37.751483205059415 ], [ -122.51051719886371, 37.751498277271601 ], [ -122.510519317971443, 37.751512662505696 ], [ -122.510521455317175, 37.751527734449049 ], [ -122.510523574787243, 37.751542120226226 ], [ -122.510527441503768, 37.751557162390775 ], [ -122.51052956027678, 37.751571547905016 ], [ -122.510541105080137, 37.751614615928503 ], [ -122.510546682275447, 37.751628942714049 ], [ -122.51055053042532, 37.751643298448748 ], [ -122.510567261661492, 37.751686277985172 ], [ -122.51057282063698, 37.751699918334559 ], [ -122.510580126864298, 37.751714215070294 ], [ -122.510587414533077, 37.751727825925812 ], [ -122.510592973154687, 37.751741465730937 ], [ -122.510600260828497, 37.751755076585596 ], [ -122.510609277525731, 37.751768657391231 ], [ -122.510616565205183, 37.751782268244845 ], [ -122.510623834295913, 37.751795192119978 ], [ -122.510632850671371, 37.75180877347875 ], [ -122.510650847293675, 37.751834562495105 ], [ -122.510666910213118, 37.751852830846786 ], [ -122.510679329278858, 37.751864293359795 ], [ -122.510690018971971, 37.751875785652558 ], [ -122.510697288092032, 37.751888709797527 ], [ -122.510704575791962, 37.751902320370817 ], [ -122.51070842433974, 37.751916676368367 ], [ -122.510710543503862, 37.751931061872575 ], [ -122.51070920459685, 37.751945506653065 ], [ -122.510724225875535, 37.751989202088765 ], [ -122.510730100360817, 37.752014511176412 ], [ -122.510737685321715, 37.752039104608144 ], [ -122.510743559477149, 37.752064413975368 ], [ -122.510825714104442, 37.752223710988055 ], [ -122.510880260867467, 37.752321671638327 ], [ -122.510889277338805, 37.75233525242713 ], [ -122.51089827594069, 37.752348147323836 ], [ -122.510919693380217, 37.752372504437815 ], [ -122.510932112233405, 37.752383967203627 ], [ -122.510956913829162, 37.752405519856168 ], [ -122.510971006350204, 37.752414893821253 ], [ -122.510979967462688, 37.75242641558544 ], [ -122.510987199526895, 37.75243796685389 ], [ -122.510992721122207, 37.752450234055644 ], [ -122.510999971778375, 37.752462472026544 ], [ -122.511005493370178, 37.752474738953069 ], [ -122.511011033205719, 37.752487692588488 ], [ -122.511014844689754, 37.752500675991762 ], [ -122.511020366286843, 37.752512942917527 ], [ -122.511024177420595, 37.752525926051767 ], [ -122.511026259163927, 37.752538938971881 ], [ -122.511028360165199, 37.752552637759649 ], [ -122.511032524332322, 37.752578663038605 ], [ -122.511033248973078, 37.752605433757282 ], [ -122.511030496930232, 37.752631577332984 ], [ -122.511029139131608, 37.75264533514347 ], [ -122.511030233600934, 37.752813569447106 ], [ -122.511027591948121, 37.752907698414738 ], [ -122.511023202651316, 37.753001170732233 ], [ -122.511017102857721, 37.753095358982122 ], [ -122.511007544911607, 37.753189606240724 ], [ -122.511045097099611, 37.75342657943726 ], [ -122.511071439929921, 37.753568973002693 ], [ -122.511099511595958, 37.7537113370581 ], [ -122.511110666857206, 37.753739990285347 ], [ -122.51113816393935, 37.753797207403004 ], [ -122.511167297244739, 37.753850963138049 ], [ -122.511191001766875, 37.753895883818785 ], [ -122.511212977231722, 37.753940834002691 ], [ -122.511234971305953, 37.753986470610634 ], [ -122.511275501018872, 37.754077803112054 ], [ -122.511285596295252, 37.754131196705785 ], [ -122.511275500500716, 37.754141670188282 ], [ -122.511267152377243, 37.754152800589303 ], [ -122.511258841417288, 37.754165303846627 ], [ -122.51125398864481, 37.754177748360249 ], [ -122.511249154438758, 37.754190878752958 ], [ -122.511246439571536, 37.754218394908946 ], [ -122.511248521739574, 37.754231407540907 ], [ -122.511252351238468, 37.754245077098325 ], [ -122.511256162847417, 37.754258060215321 ], [ -122.511263413723356, 37.754270298166247 ], [ -122.511270646011127, 37.75428184941368 ], [ -122.511281336136037, 37.754293341373469 ], [ -122.511290279285674, 37.754304176676023 ], [ -122.511297511580892, 37.754315727921757 ], [ -122.511301304272081, 37.754328024614594 ], [ -122.51130511555543, 37.7543410080103 ], [ -122.511305450050784, 37.754353363447571 ], [ -122.511302344945619, 37.754366464607017 ], [ -122.511299221255896, 37.754378879337885 ], [ -122.511292620796496, 37.754390666933737 ], [ -122.511284272301779, 37.754401797341401 ], [ -122.511274158225476, 37.754411584114365 ], [ -122.511262296127953, 37.754420714247495 ], [ -122.51124864918124, 37.754427814602913 ], [ -122.511234965066222, 37.754433542099882 ], [ -122.511205457998457, 37.754493792524343 ], [ -122.511174612954918, 37.754504620093513 ], [ -122.511188723552863, 37.754578547809551 ], [ -122.511196847828685, 37.754623047899784 ], [ -122.511172119152874, 37.754668108364662 ], [ -122.511186378431859, 37.754747527231643 ], [ -122.511164047430796, 37.754817269614293 ], [ -122.511168079798153, 37.754966224575647 ], [ -122.511187639253137, 37.754985806682214 ], [ -122.511182209413036, 37.755040838983689 ], [ -122.511152553395789, 37.755095597687138 ], [ -122.511140988488648, 37.755115710660888 ], [ -122.511131170290469, 37.755200347899923 ], [ -122.511137026593502, 37.755224970799595 ], [ -122.511151546101601, 37.755313999934657 ], [ -122.511172052652412, 37.755368589625292 ], [ -122.511185234625046, 37.75540819590843 ], [ -122.511212230658316, 37.755446879976844 ], [ -122.511220633780624, 37.755501676208965 ], [ -122.511245788883045, 37.755536270664308 ], [ -122.511246718045257, 37.755570592079458 ], [ -122.511273603014288, 37.755605157557042 ], [ -122.511268303269489, 37.755664994580059 ], [ -122.511302921291062, 37.755793511928204 ], [ -122.51129135632344, 37.755813625188885 ], [ -122.511297751657864, 37.755858154497574 ], [ -122.51132993529248, 37.755896749458273 ], [ -122.511344307286322, 37.755916419796833 ], [ -122.51135098142602, 37.755971245525423 ], [ -122.511358975860517, 37.756010940596234 ], [ -122.511372157693486, 37.756050546585421 ], [ -122.51138067251722, 37.756109461644371 ], [ -122.51138760653366, 37.756173897095294 ], [ -122.511415290932803, 37.756237978675102 ], [ -122.51144193355438, 37.756327487882515 ], [ -122.511450857327816, 37.756401503804241 ], [ -122.511444739922965, 37.756431138262307 ], [ -122.511465507194529, 37.756495337885667 ], [ -122.511475248786056, 37.756599556916427 ], [ -122.511489639220827, 37.756683781260584 ], [ -122.511510945933878, 37.756767887008252 ], [ -122.511505516202988, 37.756822919307787 ], [ -122.511507114604001, 37.756881952399702 ], [ -122.511540803935873, 37.756976147977831 ], [ -122.51151021846853, 37.756996585892097 ], [ -122.511537215240679, 37.757035269600948 ], [ -122.511550805913657, 37.75708997726138 ], [ -122.511552014025696, 37.757134594811419 ], [ -122.51153474073044, 37.7571994439623 ], [ -122.511545151615906, 37.757328374109882 ], [ -122.511565380212119, 37.7573726672926 ], [ -122.511548775980131, 37.757462227577349 ], [ -122.511570621660383, 37.757566239995974 ], [ -122.511588991755985, 37.757605757952987 ], [ -122.511636236094873, 37.757689420961349 ], [ -122.511627217721312, 37.75780357460139 ], [ -122.511645866365058, 37.757853388698848 ], [ -122.511694113049955, 37.758229588585031 ], [ -122.511742993592506, 37.758437524775317 ], [ -122.511846760303001, 37.759011245676845 ], [ -122.511857097632088, 37.75913743032006 ], [ -122.511865631081335, 37.759260898772276 ], [ -122.511870706825988, 37.759384426245269 ], [ -122.511867805759621, 37.759468945686322 ], [ -122.511886956563586, 37.759537293011533 ], [ -122.511894319592614, 37.759553649204342 ], [ -122.511899972344324, 37.759570721336786 ], [ -122.511907335379505, 37.759587077528693 ], [ -122.511912969200509, 37.759603463238605 ], [ -122.511924273692642, 37.75963760779338 ], [ -122.511928197239044, 37.759654709442891 ], [ -122.511933831069797, 37.759671095151603 ], [ -122.511941677473203, 37.759705298186844 ], [ -122.511943890051924, 37.759723115788141 ], [ -122.511947812915679, 37.759740217448552 ], [ -122.511954395243222, 37.75979161123707 ], [ -122.511954878597919, 37.75980945808243 ], [ -122.51195707224619, 37.759826589261372 ], [ -122.511958020381513, 37.759861597073268 ], [ -122.511956756283539, 37.759878787553092 ], [ -122.511957221055894, 37.759895948245067 ], [ -122.511955975187334, 37.759913824609214 ], [ -122.511953445935674, 37.759948205037048 ], [ -122.511904504571447, 37.760440750181104 ], [ -122.511896527982401, 37.760465608886555 ], [ -122.511890205925951, 37.76048769264321 ], [ -122.511883865275365, 37.760509089971798 ], [ -122.511879272104622, 37.760531144215527 ], [ -122.511874661032579, 37.760552512019558 ], [ -122.511865475369248, 37.760596620493857 ], [ -122.511856884580055, 37.760662694650442 ], [ -122.511852347147268, 37.760750675453501 ], [ -122.51185415039285, 37.760817258929578 ], [ -122.51185647452489, 37.76083919509459 ], [ -122.511857069411604, 37.760861160777161 ], [ -122.511859412136133, 37.760883783369522 ], [ -122.511863465519539, 37.760905690016429 ], [ -122.511865789657108, 37.760927626180887 ], [ -122.511869842698943, 37.760949532833308 ], [ -122.511875625684155, 37.760971409955765 ], [ -122.511879679077154, 37.760993316601748 ], [ -122.511885442786451, 37.761014507307813 ], [ -122.511897008433721, 37.761058261556201 ], [ -122.511926981734348, 37.761142906550255 ], [ -122.511936204314608, 37.76116403793533 ], [ -122.511954649505725, 37.76120630125228 ], [ -122.511963853517884, 37.761226746482016 ], [ -122.511996653008623, 37.761287993613372 ], [ -122.51202705467847, 37.761324558853467 ], [ -122.512064168550651, 37.761353455306384 ], [ -122.512085012456453, 37.761420400744697 ], [ -122.512126086793671, 37.761531638678171 ], [ -122.51214611271017, 37.76156838129576 ], [ -122.512202471774316, 37.761605190090663 ], [ -122.51220306676943, 37.761627155768551 ], [ -122.512286833872011, 37.761909350986301 ], [ -122.512330121233489, 37.762102273976545 ], [ -122.512359816513452, 37.762176622160425 ], [ -122.512400687481886, 37.762280309551663 ], [ -122.512412755489066, 37.762406464549109 ], [ -122.512472852658917, 37.762517377837653 ], [ -122.51242023100265, 37.762682408319826 ], [ -122.512395388918449, 37.762787217676667 ], [ -122.5123251575382, 37.763004741639399 ], [ -122.512318128822102, 37.763064608432281 ], [ -122.512275063725824, 37.763199258620396 ], [ -122.512347657434475, 37.763452114765002 ], [ -122.512370305614013, 37.763585643570899 ], [ -122.51245827791935, 37.763703634850181 ], [ -122.51249862900157, 37.763851969475809 ], [ -122.512536692779477, 37.763915873550189 ], [ -122.512547533618658, 37.763932857061576 ], [ -122.512582157093107, 37.763997506608895 ], [ -122.512647704491329, 37.764117941165772 ], [ -122.512695196290764, 37.764146660498668 ], [ -122.512734264345909, 37.764183764321103 ], [ -122.512737091420831, 37.764288101230889 ], [ -122.512775416013923, 37.764361614927097 ], [ -122.512777034178313, 37.764421334077248 ], [ -122.512751987430036, 37.764518593055108 ], [ -122.512781479493952, 37.764585390130065 ], [ -122.512774860188117, 37.764660358327284 ], [ -122.51278551563432, 37.764734344784699 ], [ -122.512761491760273, 37.764869356967338 ], [ -122.512743882579386, 37.764921850254765 ], [ -122.512717422071987, 37.76496694049434 ], [ -122.512684621435739, 37.765161162553468 ], [ -122.512669634563863, 37.765310442023306 ], [ -122.512654461644431, 37.765452857218328 ], [ -122.512646410035117, 37.765474970525723 ], [ -122.51263958593637, 37.765542388282462 ], [ -122.512631627607334, 37.765631800678314 ], [ -122.512652064044445, 37.76568364430058 ], [ -122.512693345840233, 37.765802432929902 ], [ -122.512704615499302, 37.765899071734495 ], [ -122.512725051707562, 37.765950915073049 ], [ -122.512700618145246, 37.766070825824592 ], [ -122.512768157254058, 37.76613697348678 ], [ -122.512789393732405, 37.766218333432967 ], [ -122.512782253924286, 37.766337948881301 ], [ -122.512802690660749, 37.766389792471095 ], [ -122.512831774306832, 37.766441488397447 ], [ -122.512880476379308, 37.766514825114292 ], [ -122.512887749430135, 37.76678321796404 ], [ -122.512892604360289, 37.766962375332177 ], [ -122.512931655400493, 37.766998792918841 ], [ -122.512961259283941, 37.767005841271946 ], [ -122.513017009002525, 37.767019997837075 ], [ -122.513065321321278, 37.767078919504563 ], [ -122.513078915989027, 37.767197493825428 ], [ -122.513107814109816, 37.767242325410123 ], [ -122.513081743662724, 37.767301830672046 ], [ -122.51310340834894, 37.767398978362927 ], [ -122.513123716454487, 37.7675098841043 ], [ -122.513078250436607, 37.767555985727952 ], [ -122.513071017262462, 37.76760830182581 ], [ -122.513091249913373, 37.767652594661705 ], [ -122.513076058720628, 37.767794323426109 ], [ -122.513058653709876, 37.76785436770038 ], [ -122.513040634413983, 37.767891759352018 ], [ -122.513052220422765, 37.767936200135772 ], [ -122.513081118807335, 37.767981031446666 ], [ -122.513072546502798, 37.768047792039297 ], [ -122.513084307659341, 37.768800949561466 ], [ -122.513105545245466, 37.768882309412746 ], [ -122.513112428659511, 37.76913628718308 ], [ -122.513087380753205, 37.769233545877434 ], [ -122.51312531757408, 37.76929264499158 ], [ -122.513091827049578, 37.769397602054994 ], [ -122.513085691671151, 37.769554284209093 ], [ -122.513145317112162, 37.770030553014749 ], [ -122.513104072344461, 37.770232473669928 ], [ -122.513083675037265, 37.770501338904026 ], [ -122.513146087813496, 37.77076123543462 ], [ -122.513127217898258, 37.771214121925311 ], [ -122.51313192303401, 37.771260053409627 ], [ -122.511055400217217, 37.771347479926334 ], [ -122.510495195112867, 37.771371059784514 ], [ -122.509894781266311, 37.771396329054475 ], [ -122.509894749698645, 37.771396032095495 ], [ -122.508867990729883, 37.771443447055226 ], [ -122.508823330922013, 37.771445496187766 ], [ -122.508780633594597, 37.771447480819759 ], [ -122.507750665782908, 37.771495034103232 ], [ -122.507750664399296, 37.771495034126787 ], [ -122.506991972993774, 37.771528822999358 ], [ -122.506685577396198, 37.771542340610623 ], [ -122.505610663486351, 37.771591537709703 ], [ -122.504544396347384, 37.771640577944709 ], [ -122.503472690942445, 37.771689380369601 ], [ -122.503472688521128, 37.771689380410749 ], [ -122.502399732131124, 37.771738148994636 ], [ -122.502399595949868, 37.771738155153713 ], [ -122.501328271899965, 37.77178724800941 ], [ -122.50025681025997, 37.771836337560785 ], [ -122.499183617695707, 37.771885446705888 ], [ -122.498113882732468, 37.771934487449556 ], [ -122.497047586931203, 37.771982773731914 ], [ -122.495972660610278, 37.77203188267292 ], [ -122.495971990075716, 37.772031913255347 ], [ -122.495972015995989, 37.772032015003532 ], [ -122.494903299427449, 37.772080795311069 ], [ -122.494902921073546, 37.772080894286702 ], [ -122.493833180817205, 37.772129896182108 ], [ -122.493832498174271, 37.772129926957135 ], [ -122.493832479293559, 37.77212966191987 ], [ -122.493316307377469, 37.772153213706439 ], [ -122.492761690869486, 37.772178231184974 ], [ -122.491688488076463, 37.772227272380952 ], [ -122.490617013718605, 37.772276274046327 ], [ -122.489539486385709, 37.772325343661755 ], [ -122.489539480863627, 37.772325473411478 ], [ -122.488471323394165, 37.772374169626119 ], [ -122.488470602075608, 37.772374307055074 ], [ -122.487399123482518, 37.77242327974907 ], [ -122.487398306387334, 37.772423316873983 ], [ -122.487398288926627, 37.772423078182946 ], [ -122.486678527930636, 37.772455879683093 ], [ -122.486334543235188, 37.772471439976606 ], [ -122.485261332297384, 37.772520422109807 ], [ -122.485152053625171, 37.772525429693054 ], [ -122.484194201052048, 37.772569061919981 ], [ -122.484194219251819, 37.772569315707138 ], [ -122.483119139145145, 37.772618264154566 ], [ -122.483119121668807, 37.772618024364625 ], [ -122.482048961146063, 37.772667157434498 ], [ -122.481947659007389, 37.77267180789157 ], [ -122.480978851643016, 37.772716079930653 ], [ -122.479904580478745, 37.772765580938412 ], [ -122.478848714480989, 37.772814028910268 ], [ -122.47883762078969, 37.772814536530767 ], [ -122.477766129509902, 37.772863421949047 ], [ -122.477069824745385, 37.772895630732648 ], [ -122.476694205661246, 37.772912858094969 ], [ -122.475613625589148, 37.772962410314676 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475612802305008, 37.772962709520556 ], [ -122.47455168367884, 37.773011391539193 ], [ -122.474550931211951, 37.773011425844139 ], [ -122.474550910293516, 37.773011133644289 ], [ -122.473737577092777, 37.773048416620476 ], [ -122.473427836194134, 37.773062613687692 ], [ -122.472297341321692, 37.773114423074823 ], [ -122.472219951938214, 37.773117969260745 ], [ -122.471762122569857, 37.773138947752848 ], [ -122.47124006836178, 37.773161981865066 ], [ -122.471240089434815, 37.773162280380667 ], [ -122.470162712316579, 37.773209791534974 ], [ -122.470162692059887, 37.773209510586071 ], [ -122.469517084069324, 37.773237987003441 ], [ -122.469092496140973, 37.773256712542192 ], [ -122.468348575135167, 37.773289518371953 ], [ -122.468023916207073, 37.773303833556405 ], [ -122.466952500298319, 37.773351323464517 ], [ -122.466951728424362, 37.773351356945881 ], [ -122.466373346760548, 37.773376599548058 ], [ -122.465883163277454, 37.773398204721545 ], [ -122.465331676538511, 37.773422509669793 ], [ -122.464811670328331, 37.773488744378348 ], [ -122.463755675044126, 37.773623241600568 ], [ -122.463749210177951, 37.773624065149079 ], [ -122.462683796526321, 37.773759751788596 ], [ -122.462535340858068, 37.773778657528119 ], [ -122.461620188227315, 37.77389548164836 ], [ -122.461619775372085, 37.773895534117642 ], [ -122.461619755154771, 37.7738952534419 ], [ -122.460552661960989, 37.774031133779268 ], [ -122.460552700773292, 37.774031358657659 ], [ -122.459486938645284, 37.774167197254911 ], [ -122.459486145343419, 37.77416729779403 ], [ -122.459486118442314, 37.77416693399686 ], [ -122.458795325509186, 37.774254885425997 ], [ -122.458371099123696, 37.774308895694055 ], [ -122.457134751103212, 37.774454490912483 ], [ -122.456283917173579, 37.774554834704738 ], [ -122.456283671603558, 37.774554866249353 ], [ -122.456283639851293, 37.774554711848936 ], [ -122.455898178089939, 37.774604132119862 ], [ -122.45525254468275, 37.774686906456331 ], [ -122.455033749574639, 37.77471323536281 ], [ -122.454683295611602, 37.774755406979118 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.454642052912618, 37.774646438033749 ], [ -122.454474386066082, 37.77382381381878 ], [ -122.454383433186777, 37.773377561015685 ], [ -122.454285116857562, 37.772895177759331 ], [ -122.454083217765842, 37.771930404296491 ], [ -122.454018233133866, 37.771616238745679 ], [ -122.453979522461594, 37.771428395389663 ], [ -122.453902454621286, 37.771053837806122 ], [ -122.453902388799008, 37.771053479872258 ], [ -122.453726227138517, 37.770098080109292 ], [ -122.45361853828534, 37.769567547950977 ], [ -122.453536875229432, 37.769165225996225 ], [ -122.453536875207789, 37.7691652251725 ], [ -122.453445551985865, 37.76871045824668 ], [ -122.453352918070834, 37.768246336273791 ], [ -122.453352855873561, 37.76824602387866 ], [ -122.453165568246916, 37.767306553969036 ], [ -122.453075053112784, 37.766861619151193 ], [ -122.452996675368553, 37.766476342546461 ], [ -122.452947274458694, 37.766374150789915 ], [ -122.454592351139084, 37.766165584782662 ], [ -122.454592931223331, 37.766165511159706 ], [ -122.455121527013688, 37.766098468178491 ], [ -122.456616092088495, 37.765909015262118 ], [ -122.456823880275138, 37.765884275950789 ], [ -122.456913422826119, 37.76587361486397 ], [ -122.457189364808954, 37.765880549298529 ], [ -122.457438759694597, 37.76592231265932 ], [ -122.457489671310725, 37.765930838098136 ], [ -122.45751185755033, 37.765937046103922 ], [ -122.457789624362519, 37.766014767234282 ], [ -122.457788627971766, 37.765820072733575 ], [ -122.457756133601251, 37.765354151194842 ], [ -122.457718187977903, 37.764810068512688 ], [ -122.457700034267219, 37.764549766072378 ], [ -122.457702640928829, 37.764546749210723 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.457827081738984, 37.763482288038738 ], [ -122.459790374597731, 37.762925938047687 ], [ -122.460842964058216, 37.762627642539876 ], [ -122.46084509456081, 37.762627108799968 ], [ -122.460684658382874, 37.760546523892096 ], [ -122.461770397151241, 37.760502041661596 ], [ -122.461770200794135, 37.760499231230455 ], [ -122.46284191307069, 37.760452254741963 ], [ -122.463911891482155, 37.760405344350922 ], [ -122.463781528402038, 37.758538181378015 ], [ -122.463651620775778, 37.756677450683235 ], [ -122.463554499932727, 37.755298102249476 ], [ -122.463580718915097, 37.755014148901829 ], [ -122.463627289771182, 37.754788065181785 ], [ -122.46367889740506, 37.754738571878264 ], [ -122.463722821262465, 37.754601920791259 ], [ -122.463838893966539, 37.75414750585891 ], [ -122.463842129576008, 37.753808596342253 ], [ -122.463777683069083, 37.753601657228067 ], [ -122.463640406958334, 37.753310851043111 ], [ -122.463399006031565, 37.752981452291863 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":23,\"ZIP_CODE\":94127,\"ID\":94127},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.458688935885434, 37.730751520241796 ], [ -122.459193144317396, 37.730602954490251 ], [ -122.459352156941819, 37.730515761910091 ], [ -122.459563710785162, 37.730368588301999 ], [ -122.459783802497654, 37.730109540441724 ], [ -122.459878403068345, 37.729884503080157 ], [ -122.459930163903692, 37.729706628889033 ], [ -122.459983752683073, 37.729522472170189 ], [ -122.460023966205156, 37.729195728883113 ], [ -122.460002483633559, 37.728739437629635 ], [ -122.460412720770037, 37.728739179843977 ], [ -122.460815517748841, 37.728680936862041 ], [ -122.460816315573013, 37.728681144164341 ], [ -122.460816314413918, 37.72868110023223 ], [ -122.460815856121073, 37.72868098095028 ], [ -122.46142222273329, 37.728330263725837 ], [ -122.461824751209633, 37.728118912555594 ], [ -122.462068476892256, 37.72804961327914 ], [ -122.462089038987699, 37.728047063364642 ], [ -122.462378284725844, 37.728011300874932 ], [ -122.462703368909061, 37.728024299159358 ], [ -122.462888197868679, 37.72805558978888 ], [ -122.46288814852555, 37.72805550243347 ], [ -122.462888304160828, 37.72805552868347 ], [ -122.462735876702908, 37.727784058577058 ], [ -122.462714226912738, 37.727738412010282 ], [ -122.462615761322709, 37.727529244060726 ], [ -122.462547067236983, 37.727225353979733 ], [ -122.462546035395761, 37.727106268284892 ], [ -122.462545638153259, 37.726997475605309 ], [ -122.46262180837526, 37.726335258944708 ], [ -122.462686726995429, 37.725774916753664 ], [ -122.462815474472038, 37.725482412532848 ], [ -122.462561310804759, 37.725391067720956 ], [ -122.46221285915388, 37.725265563681397 ], [ -122.462212628293415, 37.725265491161082 ], [ -122.462280269039496, 37.725147301470763 ], [ -122.462279803400207, 37.725042766873585 ], [ -122.462279017236384, 37.724866230039204 ], [ -122.462276854232826, 37.724380477322995 ], [ -122.462276505002905, 37.72427060589213 ], [ -122.462276504226139, 37.724270445482276 ], [ -122.46227362197132, 37.723637318172813 ], [ -122.462271183905713, 37.723021052793868 ], [ -122.462431367748181, 37.72300886812026 ], [ -122.462654991355222, 37.722947907918154 ], [ -122.46284032675716, 37.72280173266644 ], [ -122.463189003103793, 37.722604614839412 ], [ -122.463751413929884, 37.722395206040865 ], [ -122.463981569397532, 37.722327034291936 ], [ -122.464293670852172, 37.722113739171022 ], [ -122.464434759676408, 37.721907819106278 ], [ -122.464484214240997, 37.721680008850619 ], [ -122.465390989272279, 37.721673877359507 ], [ -122.46556367354151, 37.721672709140641 ], [ -122.4659783260431, 37.721669902411627 ], [ -122.466277766029194, 37.721667874912988 ], [ -122.46658847710232, 37.721665769467251 ], [ -122.467028877336375, 37.721662784936917 ], [ -122.467185158975667, 37.721661725062667 ], [ -122.467615322899292, 37.721658807641923 ], [ -122.467922730856742, 37.721656721708847 ], [ -122.468076091423683, 37.72165575669883 ], [ -122.468660023251203, 37.721651832680671 ], [ -122.468660429184538, 37.721651830291869 ], [ -122.468975070634144, 37.721649661930378 ], [ -122.469739202269693, 37.721644441148221 ], [ -122.469739672850537, 37.721644437949429 ], [ -122.469877506730057, 37.721643501943454 ], [ -122.470778213987501, 37.721637364239811 ], [ -122.47077894458495, 37.721637359433757 ], [ -122.47167373604637, 37.72163130643554 ], [ -122.471759517799796, 37.721671075214203 ], [ -122.471759949192219, 37.721671054807977 ], [ -122.471760271986454, 37.721671203510112 ], [ -122.471983398728909, 37.721660603850793 ], [ -122.472161906508475, 37.721641204494453 ], [ -122.472321961375684, 37.721623810905996 ], [ -122.472322354823248, 37.721623768058436 ], [ -122.472214746614711, 37.723731792113433 ], [ -122.472056885104323, 37.726824049746483 ], [ -122.471915497296948, 37.728810174564472 ], [ -122.47190716730239, 37.728927188202036 ], [ -122.471594286340178, 37.728902143540118 ], [ -122.47154865397853, 37.729783977184617 ], [ -122.471866468726006, 37.729784522464136 ], [ -122.471849871701991, 37.730103151702181 ], [ -122.471838445445158, 37.730322499137451 ], [ -122.471797435951885, 37.731032354724164 ], [ -122.471741907147091, 37.731926920887339 ], [ -122.471660927786132, 37.733231459533314 ], [ -122.47162189337304, 37.733860262567148 ], [ -122.471569341481938, 37.734706810815084 ], [ -122.471400819161929, 37.735001415396148 ], [ -122.471068098407827, 37.73560670794447 ], [ -122.470574087980822, 37.736589028767121 ], [ -122.470679955794623, 37.736646992229609 ], [ -122.470805946330771, 37.736773656566506 ], [ -122.470869802376484, 37.736951620802394 ], [ -122.470924096654457, 37.737574149498428 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.471094271054184, 37.739515149668783 ], [ -122.471225939871744, 37.741383106823285 ], [ -122.470104939284042, 37.741425112172415 ], [ -122.46903011455143, 37.741472060437133 ], [ -122.468671688881855, 37.741487738762387 ], [ -122.468561098363182, 37.741503345390441 ], [ -122.467747743753378, 37.743412622582262 ], [ -122.4668594971902, 37.743490862582995 ], [ -122.466043187720601, 37.743555382675872 ], [ -122.465806989998114, 37.743574050343277 ], [ -122.464736798394597, 37.743658625608425 ], [ -122.463685995946705, 37.743749409879626 ], [ -122.461381097416904, 37.745569497508143 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.45872851739027, 37.746755429911865 ], [ -122.458610345848541, 37.746751806518411 ], [ -122.458509634961302, 37.746778882011192 ], [ -122.458499844031735, 37.746781514247793 ], [ -122.457949639713704, 37.746779214661537 ], [ -122.457582024204598, 37.746744476612157 ], [ -122.457457161051892, 37.746732677593577 ], [ -122.457301186299645, 37.746714064689691 ], [ -122.457166741019165, 37.746698021157933 ], [ -122.457089836487938, 37.746682896092409 ], [ -122.456314708164584, 37.746530446031059 ], [ -122.455687935660293, 37.746380796419501 ], [ -122.455466732538028, 37.746327980692172 ], [ -122.454664097215115, 37.746106168076913 ], [ -122.454664096869323, 37.746106168082655 ], [ -122.454184426826714, 37.745845539203287 ], [ -122.453916429980183, 37.745699922279499 ], [ -122.453916002908102, 37.745699915076791 ], [ -122.453645157964232, 37.745695560010326 ], [ -122.453495886255311, 37.745691252478657 ], [ -122.453376023619995, 37.74568779337109 ], [ -122.452386953750349, 37.745691424670198 ], [ -122.45222754649042, 37.74569200904012 ], [ -122.452091117231191, 37.74568511552728 ], [ -122.451828632556612, 37.745671851179097 ], [ -122.45181666119403, 37.745671246259803 ], [ -122.45169226258335, 37.745606748366605 ], [ -122.451624634588612, 37.745534387540218 ], [ -122.451624359716789, 37.745534037461439 ], [ -122.450586742649079, 37.744213121547148 ], [ -122.450592179132485, 37.744090792307063 ], [ -122.449918612648133, 37.7432716748716 ], [ -122.449790244582587, 37.743257317336237 ], [ -122.449582834428256, 37.743065029012413 ], [ -122.449355958412127, 37.74292182098899 ], [ -122.44913731277687, 37.742828607669701 ], [ -122.448820428983325, 37.742748693538836 ], [ -122.44848892294759, 37.742704730693937 ], [ -122.447384659150103, 37.742608987228614 ], [ -122.447110638216571, 37.742580552169777 ], [ -122.446840940400392, 37.742519081676662 ], [ -122.446657325560608, 37.742442453734959 ], [ -122.446516770315029, 37.742358933848621 ], [ -122.446410971977912, 37.742281706494659 ], [ -122.446323687641325, 37.742184944882247 ], [ -122.446256735689062, 37.742072052701658 ], [ -122.44619443459527, 37.741872554265036 ], [ -122.446156199006268, 37.741535311478742 ], [ -122.44609965968975, 37.741357693405078 ], [ -122.44605943275991, 37.741274575873817 ], [ -122.445964300022951, 37.741198753406074 ], [ -122.445964326755799, 37.741198730714288 ], [ -122.445897881975142, 37.741116602917764 ], [ -122.445523457193502, 37.740972470791057 ], [ -122.445195592772691, 37.740920623913645 ], [ -122.444911120433531, 37.740872114678645 ], [ -122.444681667799003, 37.740793092051724 ], [ -122.444518249198694, 37.740692998244214 ], [ -122.444366170223589, 37.740575544006177 ], [ -122.444209640252396, 37.740380617798955 ], [ -122.444098337188322, 37.740234069945657 ], [ -122.443922038304507, 37.740088462716592 ], [ -122.443694223373313, 37.739971345925731 ], [ -122.44341918472557, 37.739902333376541 ], [ -122.443019593631774, 37.739814475654143 ], [ -122.442856748556963, 37.739767263698369 ], [ -122.44271760477325, 37.739682237717481 ], [ -122.442597107319372, 37.739547858254923 ], [ -122.442525191527608, 37.739448400155375 ], [ -122.44250398997913, 37.739238469269011 ], [ -122.442627783322195, 37.738564600951854 ], [ -122.442625226229453, 37.738313911390271 ], [ -122.442579349780331, 37.738171569110513 ], [ -122.44244617419956, 37.737799536516135 ], [ -122.442403843641571, 37.737639851367689 ], [ -122.442421017736137, 37.737438198043598 ], [ -122.442488947503193, 37.737289169592358 ], [ -122.442657322787326, 37.737118881702763 ], [ -122.443602235926093, 37.736502583404238 ], [ -122.443683487927487, 37.73653803376142 ], [ -122.443842427221611, 37.736579809812575 ], [ -122.444038854214782, 37.736575290313169 ], [ -122.444187688286519, 37.736528330103852 ], [ -122.444306066715512, 37.736409368208768 ], [ -122.444405709994072, 37.736218559056141 ], [ -122.444433832419023, 37.736070356056409 ], [ -122.444478073435704, 37.735720552668923 ], [ -122.444456723960371, 37.735604611251425 ], [ -122.444414926545633, 37.735547154436588 ], [ -122.444280987817251, 37.735484218369663 ], [ -122.444190291728404, 37.735407445883219 ], [ -122.443868794328736, 37.73522293273804 ], [ -122.443498916758756, 37.735041781619209 ], [ -122.443241645458286, 37.734935120886668 ], [ -122.44298838327488, 37.734915120161688 ], [ -122.442971182760076, 37.734564427784264 ], [ -122.444336617762318, 37.734557437927307 ], [ -122.445353637361492, 37.734548793463254 ], [ -122.445350300553969, 37.734322189420951 ], [ -122.445385609625191, 37.734245207403205 ], [ -122.445695180894745, 37.733970768223749 ], [ -122.445782219446897, 37.733850288395629 ], [ -122.445605378551107, 37.733800711254226 ], [ -122.444320121334613, 37.733811072362919 ], [ -122.444320397855094, 37.733087687518228 ], [ -122.44431434932487, 37.732346901474607 ], [ -122.444299518643774, 37.732277163251723 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.446578400413941, 37.7315490470612 ], [ -122.447291821211039, 37.731547153756608 ], [ -122.448866265260094, 37.731542959168735 ], [ -122.451146666771351, 37.731536846888879 ], [ -122.453411343594681, 37.731530646828205 ], [ -122.453590911574565, 37.731488405776453 ], [ -122.454915878760758, 37.731464997636976 ], [ -122.455762555159509, 37.731348802743547 ], [ -122.456607498028021, 37.731187733113579 ], [ -122.457474238952088, 37.730997495241198 ], [ -122.458222500186622, 37.73077738189027 ], [ -122.458472470688633, 37.730752827866375 ], [ -122.458688935885434, 37.730751520241796 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":24,\"ZIP_CODE\":94117,\"ID\":94117},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.456669912105653, 37.763814688946653 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.457702640928829, 37.764546749210723 ], [ -122.457700034267219, 37.764549766072378 ], [ -122.457718187977903, 37.764810068512688 ], [ -122.457756133601251, 37.765354151194842 ], [ -122.457788627971766, 37.765820072733575 ], [ -122.457789624362519, 37.766014767234282 ], [ -122.45751185755033, 37.765937046103922 ], [ -122.457489349789753, 37.765930717627668 ], [ -122.457438759694597, 37.76592231265932 ], [ -122.457189364808954, 37.765880549298529 ], [ -122.456913422826119, 37.76587361486397 ], [ -122.456823880275138, 37.765884275950789 ], [ -122.456616092088495, 37.765909015262118 ], [ -122.455121527013688, 37.766098468178491 ], [ -122.454592923214804, 37.766165469813686 ], [ -122.452947253369459, 37.766374046206209 ], [ -122.452946199816822, 37.766368851617294 ], [ -122.452945976378615, 37.766368855319321 ], [ -122.452947033686613, 37.766374074293772 ], [ -122.452996080750566, 37.76647606094825 ], [ -122.453164789642727, 37.767306895129927 ], [ -122.453352288400112, 37.768246025041712 ], [ -122.453445551985865, 37.76871045824668 ], [ -122.453536875207789, 37.7691652251725 ], [ -122.453536875229432, 37.769165225996225 ], [ -122.45361853828534, 37.769567547950977 ], [ -122.453726227138517, 37.770098080109292 ], [ -122.453902388799008, 37.771053479872258 ], [ -122.453902454621286, 37.771053837806122 ], [ -122.453979522461594, 37.771428395389663 ], [ -122.454018233133866, 37.771616238745679 ], [ -122.454083217765842, 37.771930404296491 ], [ -122.454285116857562, 37.772895177759331 ], [ -122.454383433186777, 37.773377561015685 ], [ -122.454474386066082, 37.77382381381878 ], [ -122.454642052912618, 37.774646438033749 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.453003330637316, 37.774970754669006 ], [ -122.452810237317621, 37.774995318605924 ], [ -122.452963825070867, 37.775750129254348 ], [ -122.45318832797058, 37.776853425180988 ], [ -122.453384996187339, 37.777826863341367 ], [ -122.452431111623397, 37.777939652339285 ], [ -122.451543139139602, 37.778052106408275 ], [ -122.450826415883952, 37.778142868140662 ], [ -122.450654812483819, 37.778164598609756 ], [ -122.449767079183658, 37.778277008331997 ], [ -122.448885763577039, 37.778388598994987 ], [ -122.448879206948348, 37.778389428728616 ], [ -122.447997721370214, 37.778501033647089 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.446846469920573, 37.777669108909684 ], [ -122.445155752336433, 37.777886506805444 ], [ -122.443506919728407, 37.778089213415292 ], [ -122.441865967226732, 37.778299511701405 ], [ -122.441865967212379, 37.778299511152262 ], [ -122.441865640558632, 37.778299552792262 ], [ -122.441865640580161, 37.778299553615973 ], [ -122.440213334218711, 37.778511463388014 ], [ -122.43855227772093, 37.778724107938281 ], [ -122.437707163233824, 37.778832380147421 ], [ -122.436884629197735, 37.778937753600381 ], [ -122.435241012398947, 37.779144878458894 ], [ -122.433595938040227, 37.779354585436536 ], [ -122.431951802315041, 37.779564148987781 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.430047265291023, 37.778850928515844 ], [ -122.429849199803343, 37.777919295570904 ], [ -122.429686883301301, 37.776976022351889 ], [ -122.429626330769736, 37.776513151917612 ], [ -122.429622603726443, 37.776331628240193 ], [ -122.42955405641294, 37.77604428584678 ], [ -122.429365859793393, 37.775111180032617 ], [ -122.429272630354163, 37.774648924607646 ], [ -122.429178384487102, 37.774181626717471 ], [ -122.429178383795261, 37.774181626728783 ], [ -122.42917834319843, 37.774181422747475 ], [ -122.428990051967787, 37.773247800757233 ], [ -122.428802101899663, 37.772315846507482 ], [ -122.428708105393241, 37.771849753810166 ], [ -122.428614043687816, 37.771383330257258 ], [ -122.428520173517541, 37.770917853912216 ], [ -122.428426180541749, 37.770451760600665 ], [ -122.428428952948465, 37.770451409238284 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.428991181191208, 37.769394223715665 ], [ -122.429127985682982, 37.769456128057627 ], [ -122.429905643091914, 37.769407921549679 ], [ -122.430247361256178, 37.769392261627225 ], [ -122.431356624058139, 37.76932091157898 ], [ -122.431570380896062, 37.769307161387864 ], [ -122.432464341208131, 37.769249650838944 ], [ -122.433572207534368, 37.769178369516744 ], [ -122.43489319213792, 37.769113797429938 ], [ -122.435794213398353, 37.769058453615109 ], [ -122.436588267341477, 37.76900080227221 ], [ -122.436625219679314, 37.768918977544402 ], [ -122.436478247038266, 37.767274662557512 ], [ -122.436980399947345, 37.767244348801164 ], [ -122.43729899343451, 37.767221930124791 ], [ -122.437291829491301, 37.767045826510717 ], [ -122.437288601235366, 37.766922046842083 ], [ -122.437333800177683, 37.766762171805773 ], [ -122.4374853951944, 37.766644323028956 ], [ -122.437684727134425, 37.766587115085294 ], [ -122.437879374385105, 37.76661079550852 ], [ -122.438219625977609, 37.766701399454838 ], [ -122.438291383601225, 37.766725780787596 ], [ -122.438386133953244, 37.766664738151036 ], [ -122.438555364477878, 37.766297386980668 ], [ -122.439501839688162, 37.766575524599709 ], [ -122.441241683591315, 37.765270858517731 ], [ -122.441931912880605, 37.765295099009755 ], [ -122.442086687309455, 37.765324419445463 ], [ -122.443210588891787, 37.765339994614024 ], [ -122.443347278899211, 37.765332794099429 ], [ -122.443263297846215, 37.765217752592115 ], [ -122.443278634379993, 37.764726164248771 ], [ -122.443215275433289, 37.764557545459958 ], [ -122.443199827408847, 37.764468898600072 ], [ -122.44294975946913, 37.764109072128207 ], [ -122.442951036556252, 37.763677959114567 ], [ -122.443247528217242, 37.763690608015906 ], [ -122.443434935848089, 37.763875622244342 ], [ -122.44441170822752, 37.76398692869914 ], [ -122.444663866724483, 37.763326501903514 ], [ -122.445692026290132, 37.762347953618033 ], [ -122.445825769194499, 37.762264137786076 ], [ -122.445930590545728, 37.762233223779305 ], [ -122.446167833254592, 37.762219287792306 ], [ -122.446246565080784, 37.762246873735464 ], [ -122.44628722735257, 37.762310164748847 ], [ -122.446391621016517, 37.76223195154369 ], [ -122.446401427721412, 37.762069683149655 ], [ -122.446384044294348, 37.761816426422072 ], [ -122.44642928686055, 37.76181245987528 ], [ -122.446783402935864, 37.761781413739634 ], [ -122.446783403627563, 37.761781413728215 ], [ -122.44652973647915, 37.761396213495424 ], [ -122.446409895656728, 37.761094344377824 ], [ -122.446433636964471, 37.761006719141555 ], [ -122.446579328750119, 37.76090155472 ], [ -122.446866213158088, 37.76042156830691 ], [ -122.446952816919733, 37.760170129150154 ], [ -122.447157396221257, 37.759660681065917 ], [ -122.447394862769713, 37.759372842727622 ], [ -122.447681986744499, 37.759189835272018 ], [ -122.448080121328474, 37.759090506458826 ], [ -122.448534508559618, 37.759045234831405 ], [ -122.448576884007139, 37.759648639321185 ], [ -122.449214958651069, 37.759610853827809 ], [ -122.451553087455522, 37.759472365763493 ], [ -122.451578365088082, 37.759597660221687 ], [ -122.451956989358152, 37.761478249938222 ], [ -122.451957007208804, 37.761478336720671 ], [ -122.452096188532394, 37.76216681475799 ], [ -122.452096203587132, 37.762166887302627 ], [ -122.452181289886042, 37.762586053251901 ], [ -122.452233043763783, 37.762842560862168 ], [ -122.452399889409662, 37.763669482365366 ], [ -122.452569353429794, 37.764509361471809 ], [ -122.452569580477117, 37.764510482582956 ], [ -122.452569807022613, 37.764510478830069 ], [ -122.452569575903098, 37.764509334437378 ], [ -122.45351591398105, 37.764395394431268 ], [ -122.453114639516684, 37.762361563728888 ], [ -122.453744673977482, 37.761878784498755 ], [ -122.454009611320302, 37.762667869264533 ], [ -122.454946081907835, 37.7631787014639 ], [ -122.454945541962573, 37.76366305305779 ], [ -122.454845151642289, 37.763732313486969 ], [ -122.454179864833876, 37.763912200042988 ], [ -122.454217265424234, 37.764310945798535 ], [ -122.455466091062405, 37.764160565952579 ], [ -122.455781065686594, 37.764070070210742 ], [ -122.456669912105653, 37.763814688946653 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":25,\"ZIP_CODE\":94132,\"ID\":94132},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.508287627239582, 37.733266432915535 ], [ -122.508283610098232, 37.733309766760698 ], [ -122.508258422649121, 37.733337665998171 ], [ -122.50825465624375, 37.73339026610973 ], [ -122.508261381386347, 37.733447151520778 ], [ -122.508260632832361, 37.733547429417492 ], [ -122.508268595371646, 37.733586094926054 ], [ -122.508272540744613, 37.733668094001438 ], [ -122.508265010681669, 37.733709427205042 ], [ -122.508286802179072, 37.733747856989588 ], [ -122.508296363517715, 37.733781688010566 ], [ -122.508300683356651, 37.733813548133803 ], [ -122.508285681955428, 37.733834406267761 ], [ -122.508267344574577, 37.733859785121595 ], [ -122.508247213289522, 37.733882790933777 ], [ -122.508237692618366, 37.73429843541993 ], [ -122.508237373460645, 37.734350633938469 ], [ -122.5082471948962, 37.734394074716711 ], [ -122.508256635722688, 37.734423443934411 ], [ -122.508245332959405, 37.734453167005526 ], [ -122.50826415897464, 37.734509845871564 ], [ -122.508256016313609, 37.734528526845153 ], [ -122.50825693455738, 37.734562505189558 ], [ -122.508280334704608, 37.734596443696226 ], [ -122.508271010987045, 37.734635403850064 ], [ -122.508266062787172, 37.734708283416225 ], [ -122.508267732334318, 37.734770062222154 ], [ -122.508272423230224, 37.734815650964592 ], [ -122.508314825176313, 37.734912789977905 ], [ -122.508324461220354, 37.734949366434215 ], [ -122.508350693626355, 37.735024118117309 ], [ -122.508408601293198, 37.735055064766357 ], [ -122.508454835865138, 37.735102005566297 ], [ -122.508462371966132, 37.735124883136479 ], [ -122.508476933993705, 37.735151761694588 ], [ -122.508477796681021, 37.735183680465923 ], [ -122.508497058717865, 37.735256490973484 ], [ -122.508537000388259, 37.735326545054548 ], [ -122.508539031954456, 37.735401708981378 ], [ -122.508528202252251, 37.735448935792135 ], [ -122.506718399360096, 37.735463437562494 ], [ -122.506718392445464, 37.735463437680252 ], [ -122.506717419598758, 37.735469757763546 ], [ -122.506541776696594, 37.735470423847289 ], [ -122.506142139643117, 37.735471938745221 ], [ -122.505711404999388, 37.735473569905054 ], [ -122.505257352091178, 37.735475287872006 ], [ -122.504189206002195, 37.735470928034005 ], [ -122.503104759187963, 37.735466491452826 ], [ -122.50249812714712, 37.735433799348435 ], [ -122.50206735537283, 37.735396211199387 ], [ -122.502021315734993, 37.73539219398544 ], [ -122.501556481916552, 37.735330499723666 ], [ -122.501555214836827, 37.735330188314435 ], [ -122.501291847576297, 37.735265403511711 ], [ -122.501214897179494, 37.735242075875206 ], [ -122.501097483348147, 37.735206481207975 ], [ -122.501097483002411, 37.735206481213844 ], [ -122.500970549161394, 37.735174012383368 ], [ -122.500888239775747, 37.73515295885273 ], [ -122.500650675572956, 37.735059919015036 ], [ -122.500571951887039, 37.735029088020617 ], [ -122.500289079616721, 37.734896880574155 ], [ -122.500140914702612, 37.734827630984967 ], [ -122.499430330098079, 37.734471850699563 ], [ -122.499196335607635, 37.734311616338935 ], [ -122.499180283, 37.734301056816975 ], [ -122.499086082983126, 37.734239092175393 ], [ -122.498948533172893, 37.734128137325406 ], [ -122.498483756692679, 37.733969859923228 ], [ -122.49837749236211, 37.733942575660954 ], [ -122.498050148013945, 37.73385852689691 ], [ -122.497707486783483, 37.733793124978028 ], [ -122.497675024009141, 37.73378860832171 ], [ -122.497251243304476, 37.7337296478455 ], [ -122.4970611531011, 37.733738715676616 ], [ -122.496807213285692, 37.733750829171804 ], [ -122.496636264393828, 37.733770812006838 ], [ -122.496636262010625, 37.733770813420726 ], [ -122.496635997165527, 37.733770842906516 ], [ -122.496635997172902, 37.733770843181105 ], [ -122.496635991376749, 37.733770846301013 ], [ -122.496448643026866, 37.733877460645239 ], [ -122.494403119131789, 37.733960247035334 ], [ -122.493941605376719, 37.733981316545631 ], [ -122.493941607430031, 37.733981341508411 ], [ -122.494014877350082, 37.735023413250019 ], [ -122.4940149282546, 37.735024137316628 ], [ -122.493857150107345, 37.735028823489081 ], [ -122.493857076611491, 37.73502785037892 ], [ -122.493407285267097, 37.734856242527158 ], [ -122.493406966558723, 37.734856121008917 ], [ -122.493274340840912, 37.734785121286002 ], [ -122.492843999903116, 37.734842083695071 ], [ -122.492330945602859, 37.73504482611267 ], [ -122.492142715245791, 37.735181846965403 ], [ -122.491805731315921, 37.735279805981911 ], [ -122.491730212101302, 37.735301758325164 ], [ -122.491320518441114, 37.735327019185135 ], [ -122.490973609504408, 37.735348171552815 ], [ -122.490725734916396, 37.735399781312303 ], [ -122.490526224357907, 37.735470000646821 ], [ -122.490270108429897, 37.735606337180243 ], [ -122.490460674310157, 37.735842274774221 ], [ -122.490521485540071, 37.736028873151284 ], [ -122.490552748713441, 37.736251597078756 ], [ -122.490686267381363, 37.736541968564268 ], [ -122.491162508402653, 37.737157782139001 ], [ -122.491211452414888, 37.737221069184358 ], [ -122.491334899611488, 37.737293023539365 ], [ -122.491198309070924, 37.737311342176547 ], [ -122.491113859834144, 37.737397356998116 ], [ -122.490894410355324, 37.737610557460023 ], [ -122.490695166321217, 37.737732995785407 ], [ -122.490475969759899, 37.737832474268195 ], [ -122.490364187143001, 37.737929559098433 ], [ -122.489970938868055, 37.737956983380037 ], [ -122.489794342484018, 37.737821521499782 ], [ -122.489559638156635, 37.737725144708122 ], [ -122.489310346380378, 37.737544144541232 ], [ -122.48917282500021, 37.737387715271872 ], [ -122.488933343471572, 37.73715804150379 ], [ -122.488518054177234, 37.737001890327612 ], [ -122.488261361358497, 37.736979897727672 ], [ -122.487883946457515, 37.736972903025247 ], [ -122.487473746906744, 37.73696000969079 ], [ -122.486865148034397, 37.736893072836374 ], [ -122.486239320534182, 37.736758103009961 ], [ -122.485767077957547, 37.736757243866982 ], [ -122.485398323256604, 37.736886930845941 ], [ -122.485055057260084, 37.737081691563745 ], [ -122.484981330342137, 37.737106613405395 ], [ -122.481727269492282, 37.73717192680251 ], [ -122.480654887744791, 37.73721905491567 ], [ -122.479601690542523, 37.737266045133403 ], [ -122.478500149938654, 37.73731300537402 ], [ -122.477443897267278, 37.737360110526431 ], [ -122.476368270747585, 37.737407342172453 ], [ -122.475288554960258, 37.737454742960537 ], [ -122.474232714891599, 37.737476308128976 ], [ -122.47327754880429, 37.737518149435381 ], [ -122.473161338869033, 37.737550437503423 ], [ -122.472088826157801, 37.737598476381557 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.470924096654457, 37.737574149498428 ], [ -122.470869802376484, 37.736951620802394 ], [ -122.470805946330771, 37.736773656566506 ], [ -122.470679955794623, 37.736646992229609 ], [ -122.470574087980822, 37.736589028767121 ], [ -122.471068098407827, 37.73560670794447 ], [ -122.471400819161929, 37.735001415396148 ], [ -122.471569341481938, 37.734706810815084 ], [ -122.47162189337304, 37.733860262567148 ], [ -122.471660927786132, 37.733231459533314 ], [ -122.471741907147091, 37.731926920887339 ], [ -122.471797435951885, 37.731032354724164 ], [ -122.471838445445158, 37.730322499137451 ], [ -122.471849871701991, 37.730103151702181 ], [ -122.471866468726006, 37.729784522464136 ], [ -122.47154865397853, 37.729783977184617 ], [ -122.471594286340178, 37.728902143540118 ], [ -122.47190716730239, 37.728927188202036 ], [ -122.471915497296948, 37.728810174564472 ], [ -122.472056885104323, 37.726824049746483 ], [ -122.472214746614711, 37.723731792113433 ], [ -122.472322354823248, 37.721623768058436 ], [ -122.472321961375684, 37.721623810905996 ], [ -122.472161906508475, 37.721641204494453 ], [ -122.471983398728909, 37.721660603850793 ], [ -122.471760271986454, 37.721671203510112 ], [ -122.471759949192219, 37.721671054807977 ], [ -122.471673586030391, 37.721631204836193 ], [ -122.470779028465699, 37.721637301442819 ], [ -122.469876768913451, 37.721643443134909 ], [ -122.469739691960783, 37.721644376097736 ], [ -122.46897449401726, 37.721649578720722 ], [ -122.468660059213846, 37.721651715058712 ], [ -122.46807558495307, 37.721655684399515 ], [ -122.467922730856742, 37.721656721708847 ], [ -122.467615322899292, 37.721658807641923 ], [ -122.467185158975667, 37.721661725062667 ], [ -122.467028877336375, 37.721662784936917 ], [ -122.46658847710232, 37.721665769467251 ], [ -122.466277766029194, 37.721667874912988 ], [ -122.4659783260431, 37.721669902411627 ], [ -122.465564132461637, 37.72167263116085 ], [ -122.465391251749992, 37.721673798812127 ], [ -122.465390987937923, 37.721673800741335 ], [ -122.46448421418296, 37.721680006654026 ], [ -122.464483630192618, 37.721680010624304 ], [ -122.464483033412463, 37.721680014807887 ], [ -122.464479920172778, 37.721680035685075 ], [ -122.464479920230843, 37.721680037881669 ], [ -122.464483033470529, 37.721680017004481 ], [ -122.464484214240997, 37.721680008850619 ], [ -122.464434759676408, 37.721907819106278 ], [ -122.464293670852172, 37.722113739171022 ], [ -122.463981569397532, 37.722327034291936 ], [ -122.463751413929884, 37.722395206040865 ], [ -122.463189003103793, 37.722604614839412 ], [ -122.46284032675716, 37.72280173266644 ], [ -122.462654991355222, 37.722947907918154 ], [ -122.462431367748181, 37.72300886812026 ], [ -122.462271183905713, 37.723021052793868 ], [ -122.462271183761132, 37.723021008020723 ], [ -122.462265621163738, 37.721827887159478 ], [ -122.46226557117231, 37.721695003479546 ], [ -122.462267174469247, 37.721694992704748 ], [ -122.462267173874807, 37.721694970189525 ], [ -122.462264896007014, 37.721694985883083 ], [ -122.462254399326852, 37.72000857322422 ], [ -122.46226246554707, 37.719928593578096 ], [ -122.462245294921956, 37.718767790464547 ], [ -122.462244936309219, 37.718219524954932 ], [ -122.46263854285489, 37.718211149542661 ], [ -122.462639625007327, 37.717936035524978 ], [ -122.462638771382373, 37.717866433871905 ], [ -122.462633312624419, 37.717421207682776 ], [ -122.46262477066405, 37.716570820282072 ], [ -122.462620481465109, 37.716143794339722 ], [ -122.462615213395566, 37.71561930379395 ], [ -122.462607827629938, 37.714883950429027 ], [ -122.462602039439204, 37.714307653173123 ], [ -122.462588724439357, 37.714029538822885 ], [ -122.462554985317752, 37.713903926321834 ], [ -122.46256899895387, 37.713150165038506 ], [ -122.462580658473911, 37.712523033743715 ], [ -122.462581299448402, 37.712273520359908 ], [ -122.462570051320355, 37.711370301468136 ], [ -122.462063256954337, 37.711372729296876 ], [ -122.462545712573657, 37.711171028559598 ], [ -122.462554670587338, 37.71117015272295 ], [ -122.462558461843784, 37.710567432270715 ], [ -122.462893196389857, 37.710562754126848 ], [ -122.464175624997623, 37.710548242222522 ], [ -122.464766855714529, 37.710481381431755 ], [ -122.465994326901708, 37.710153924678359 ], [ -122.466791287837083, 37.709825747176509 ], [ -122.467161376112116, 37.709634146029515 ], [ -122.467717389066479, 37.709282172736899 ], [ -122.468442186669975, 37.708710365091008 ], [ -122.468469108419114, 37.708682445380624 ], [ -122.468919327757234, 37.708227236298555 ], [ -122.468936765178938, 37.708230944115137 ], [ -122.469206218211681, 37.708201815905348 ], [ -122.469249900139204, 37.708201720375776 ], [ -122.469298321590273, 37.708202284245147 ], [ -122.470831636978247, 37.708197238849166 ], [ -122.471323400170547, 37.708198733212157 ], [ -122.471333181497599, 37.708198762882461 ], [ -122.471352032181699, 37.708198820402657 ], [ -122.472828567745367, 37.708203294119777 ], [ -122.475753134738511, 37.70818173979459 ], [ -122.478975890370975, 37.708191500643373 ], [ -122.481681250730091, 37.708183096324262 ], [ -122.482448808625776, 37.708181853858243 ], [ -122.484067695750653, 37.708165047020167 ], [ -122.485639305786634, 37.708148710071598 ], [ -122.485678792700227, 37.708148575886831 ], [ -122.485857818531997, 37.708147968478251 ], [ -122.493439707434533, 37.708121990011122 ], [ -122.498399514345707, 37.70810473139138 ], [ -122.502773696653762, 37.708089337426266 ], [ -122.502794335644097, 37.708342053909199 ], [ -122.502870002532575, 37.708519665129593 ], [ -122.502919824088835, 37.708764674445675 ], [ -122.502952891728171, 37.708965329397188 ], [ -122.502970627807798, 37.709110275269509 ], [ -122.503182081004425, 37.70964337370075 ], [ -122.503220569589601, 37.710045154472041 ], [ -122.503285115419118, 37.710323219970874 ], [ -122.503377921169559, 37.710623467751866 ], [ -122.503454573963339, 37.710901670868985 ], [ -122.50338876186548, 37.711025718699396 ], [ -122.503480367063446, 37.71128134783477 ], [ -122.503538681827621, 37.71139229600675 ], [ -122.503607152393855, 37.711815885652477 ], [ -122.50392442753342, 37.711977024740428 ], [ -122.50398926023928, 37.712137307747028 ], [ -122.504018884937224, 37.712338364595105 ], [ -122.504050960348835, 37.712566162951994 ], [ -122.504083766615167, 37.712821075451494 ], [ -122.50415623472351, 37.713008011664755 ], [ -122.504154839245047, 37.713276897967987 ], [ -122.504262407999079, 37.713483152309799 ], [ -122.504310222769533, 37.713717550242542 ], [ -122.504341216541519, 37.713905192080865 ], [ -122.504415861257201, 37.714172784107312 ], [ -122.504527957268053, 37.714546871585753 ], [ -122.504619249107378, 37.714854698366167 ], [ -122.504734681128681, 37.715160054242361 ], [ -122.50484307775433, 37.715396854580646 ], [ -122.504983687289311, 37.715738179098203 ], [ -122.505015183177221, 37.715944354142479 ], [ -122.505064940330996, 37.716122405267939 ], [ -122.505277983345735, 37.716713159786387 ], [ -122.505378665806077, 37.716920217389642 ], [ -122.50544954257559, 37.71711198714339 ], [ -122.505534236338548, 37.717303178231596 ], [ -122.505550939969169, 37.717537761949714 ], [ -122.505627533936476, 37.717813217613234 ], [ -122.505733499538735, 37.718087829999391 ], [ -122.505795361000153, 37.718329885441641 ], [ -122.50575895814147, 37.718518674479142 ], [ -122.505846981170734, 37.71870500140907 ], [ -122.505947348818779, 37.718836178428148 ], [ -122.50603889798937, 37.719089059528848 ], [ -122.50604605833162, 37.719354365984017 ], [ -122.506067045042258, 37.719619436964734 ], [ -122.506098310388239, 37.719752820508575 ], [ -122.506085112584401, 37.71990447335908 ], [ -122.506073138870676, 37.719973352141601 ], [ -122.506058509098679, 37.720136017347265 ], [ -122.506066050462266, 37.720287316996625 ], [ -122.506093067115529, 37.720455453693035 ], [ -122.506119272327297, 37.720657598256366 ], [ -122.506215620201345, 37.720895976024821 ], [ -122.50629167450829, 37.721023102978798 ], [ -122.506401700968937, 37.721383832251519 ], [ -122.506521510466683, 37.721658550935217 ], [ -122.507655277014294, 37.728423963999113 ], [ -122.507663331414165, 37.728466062015535 ], [ -122.507703574614794, 37.728547442310145 ], [ -122.507748947428013, 37.728626675201951 ], [ -122.507757146370523, 37.728738132019352 ], [ -122.507784794329567, 37.728865395952631 ], [ -122.50778664879924, 37.728934039418284 ], [ -122.507794848177895, 37.729045496221595 ], [ -122.507815341525188, 37.729163954079155 ], [ -122.50786205937635, 37.729292953229624 ], [ -122.507900514994574, 37.72943617151067 ], [ -122.507937978539388, 37.729542665673854 ], [ -122.507979524242614, 37.729608229023043 ], [ -122.508001314382625, 37.729646658611678 ], [ -122.508029211854279, 37.729655111115953 ], [ -122.5080286514508, 37.729698385786008 ], [ -122.508045757198687, 37.729755437677454 ], [ -122.508054266358684, 37.729814352687157 ], [ -122.508053396658397, 37.729910168841855 ], [ -122.508060436991443, 37.729978723905262 ], [ -122.508062096980282, 37.730040159271667 ], [ -122.508075550679465, 37.730090062827138 ], [ -122.508077646560039, 37.730167629612303 ], [ -122.508084862782624, 37.730242705229408 ], [ -122.508102701340974, 37.730326871159861 ], [ -122.508101275259605, 37.730402094356123 ], [ -122.508118432954731, 37.730525073140598 ], [ -122.508154289380698, 37.730636058484791 ], [ -122.508159291744406, 37.730757184482151 ], [ -122.508147188358009, 37.730821258058114 ], [ -122.508157490852682, 37.730882546377835 ], [ -122.508176687224534, 37.73095295419229 ], [ -122.508155505168105, 37.731001044217557 ], [ -122.508070616958207, 37.731059491187089 ], [ -122.508063151964294, 37.731103226906185 ], [ -122.50803827155984, 37.731142452222485 ], [ -122.508010240050936, 37.731193062852363 ], [ -122.508020236798259, 37.731243025052876 ], [ -122.508042824992458, 37.73131097145712 ], [ -122.508040601277514, 37.731356677807476 ], [ -122.508006517533232, 37.731439325082015 ], [ -122.508021273414599, 37.731473410969826 ], [ -122.50803497807641, 37.731532581064045 ], [ -122.508027744858111, 37.731584897172127 ], [ -122.508025706262629, 37.731637468116226 ], [ -122.508040152865036, 37.731724095470533 ], [ -122.508058978047401, 37.731780774943978 ], [ -122.508063043647368, 37.731867235581163 ], [ -122.508091810132555, 37.731971817943496 ], [ -122.508115264680967, 37.732007815523275 ], [ -122.508158104591416, 37.732057217905187 ], [ -122.508170145127792, 37.732118820238121 ], [ -122.508180754565259, 37.732191434375828 ], [ -122.508206800359858, 37.732259321810233 ], [ -122.508252110705484, 37.732336151955884 ], [ -122.508247422336822, 37.732418641589092 ], [ -122.508261368501763, 37.73248673525643 ], [ -122.508297786010743, 37.732554445876552 ], [ -122.50831883177085, 37.732629285992957 ], [ -122.508349701883319, 37.732683699580726 ], [ -122.508381054023317, 37.73275596038183 ], [ -122.508401673597731, 37.732815012827984 ], [ -122.508412291945035, 37.732887970164001 ], [ -122.508377095537298, 37.732929431631767 ], [ -122.50834351603902, 37.732966745040173 ], [ -122.508312026503646, 37.733017414665078 ], [ -122.508294180910255, 37.733060983682996 ], [ -122.508292383963067, 37.733122478220693 ], [ -122.508302816757165, 37.733188571798578 ], [ -122.50828645856835, 37.733223187740961 ], [ -122.508287627239582, 37.733266432915535 ] ] ] }}\n]}\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/pyproject.toml",
    "content": "[build-system]\nrequires = [\"geopandas~=0.14.3\", \"ipywidgets~=8.1.5\", \"notebook~=6.0.1\", \"jupyter_packaging~=0.12.3\", \"jupyter-contrib-nbextensions~=0.7.0\", \"jupyterlab~=4.1.6\", \"pyarrow~=16.0.0\", \"setuptools>=69.5.1\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/requirements.txt",
    "content": "geopandas==0.14.3\nipywidgets==7.8.1\nipykernel==6.29.4\nShapely==2.0.4\njupyter_packaging==0.12.3\njupyter-contrib-nbextensions==0.7.0\nnotebook==6.0.1\nsetuptools==70.0.0\njupyter==1.0.0\njupyterlab==4.1.6\npyarrow==16.0.0\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/setup.cfg",
    "content": "[bdist_wheel]\nuniversal=1\n"
  },
  {
    "path": "bindings/kepler.gl-jupyter/setup.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\nfrom __future__ import print_function\nfrom distutils import log\nfrom setuptools import setup, find_packages\nimport os\n\nfrom jupyter_packaging import (\n    create_cmdclass,\n    install_npm,\n    ensure_targets,\n    combine_commands,\n    get_version,\n    skip_if_exists\n)\n\n# Name of the project\nname = 'keplergl'\n\nhere = os.path.dirname(os.path.abspath(__file__))\nlong_description = 'Keplergl Jupyter Extension'\n\nlog.info('setup.py entered')\nlog.info('$PATH=%s' % os.environ['PATH'])\n\n# Get version\nversion = get_version(os.path.join(name, '_version.py'))\n\njs_dir = os.path.join(here, 'js')\n\n# Representative files that should exist after a successful build\njstargets = [\n    os.path.join('keplergl', 'static', 'index.js'),\n    os.path.join('keplergl-jupyter', 'labextension', 'package.json'),\n]\n\ndata_files_spec = [\n    ('share/jupyter/nbextensions/keplergl-jupyter',\n     'keplergl/static', '**'),\n    ('share/jupyter/labextensions/keplergl-jupyter',\n     'keplergl-jupyter/labextension', \"**\"),\n    ('etc/jupyter/nbconfig/notebook.d', '.', 'keplergl-jupyter.json'),\n]\n\ncmdclass = create_cmdclass('jsdeps', data_files_spec=data_files_spec)\njs_command = combine_commands(\n    install_npm(js_dir, npm=[\"yarn\"], build_cmd='build'), ensure_targets(jstargets),\n)\n\nis_repo = os.path.exists(os.path.join(here, '.git'))\nif is_repo:\n    cmdclass['jsdeps'] = js_command\nelse:\n    cmdclass['jsdeps'] = skip_if_exists(jstargets, js_command)\n\n\nLONG_DESCRIPTION = 'A jupyter widget for kepler.gl, an advanced geospatial visualization tool, to render large-scale interactive maps.'\n\nsetup_args = {\n    'name': 'keplergl',\n    'version': version,\n    'description': 'This is a simple jupyter widget for kepler.gl, an advanced geospatial visualization tool, to render large-scale interactive maps.',\n    'long_description': LONG_DESCRIPTION,\n    'include_package_data': True,\n    'install_requires': [\n        'ipywidgets>=8.1.5',\n        'traittypes>=0.2.1',\n        'traitlets>=4.3.2',\n        'geopandas>=0.14.3',\n        'Shapely>=1.6.4.post2',\n        'jupyter_packaging>=0.12.3',\n        'jupyter>=1.0.0',\n        'jupyterlab>=4.1.6',\n        'notebook>=6.0.1',\n        'pyarrow>=16.0.0',\n        'geoarrow-pyarrow>=0.1.2',\n        'geoarrow-pandas>=0.1.1',\n    ],\n    'packages': find_packages(),\n    'zip_safe': False,\n    'cmdclass': cmdclass,\n    'author': 'Shan He',\n    'author_email': 'shan@uber.com',\n    'url': 'https://github.com/keplergl/kepler.gl/tree/master/bindings/kepler.gl-jupyter',\n    'keywords': [\n        'ipython',\n        'jupyter',\n        'widgets',\n        'geospatial',\n        'visualization',\n        'webGL'\n    ],\n    'classifiers': [\n        'Development Status :: 4 - Beta',\n        'Framework :: IPython',\n        'Intended Audience :: Developers',\n        'Intended Audience :: Science/Research',\n        'Topic :: Multimedia :: Graphics',\n        'Programming Language :: Python :: 2',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n        'Programming Language :: Python :: 3.12',\n    ],\n}\n\nsetup(**setup_args)\n"
  },
  {
    "path": "bindings/python/DEVELOPMENT.md",
    "content": "# Development Guide\n\nThis guide explains how to set up kepler.gl for Jupyter for local development.\n\n## Prerequisites\n\n- Python >= 3.9\n- Node.js (for building the frontend)\n- JupyterLab >= 4.0 or Notebook >= 7.0\n\n## Installation\n\nNavigate to the `bindings/python` directory:\n\n```bash\ncd bindings/python\n```\n\n### 1. Install JavaScript dependencies and build the frontend\n\n```bash\nnpm install\nnpm run build\n```\n\n### 2. Install Python package in development mode\n\nUsing **uv** (recommended):\n\n```bash\nuv sync --dev\n```\n\nOr using **pip**:\n\n```bash\npip install -e \".[dev]\"\n```\n\n### 3. Start Jupyter\n\n```bash\n# With uv\nuv run jupyter lab\n\n# Or with pip\njupyter lab\n```\n\n## Development with Hot Reload\n\nFor active development with automatic rebuilding of the TypeScript/JavaScript:\n\n```bash\n# Terminal 1: Watch for TypeScript changes\nnpm run dev\n\n# Terminal 2: Run Jupyter\nuv run jupyter lab\n```\n\nThe `npm run dev` command watches for changes in the `src/` directory and automatically rebuilds the widget JavaScript.\n\n## Quick Test\n\n```python\nfrom keplergl import KeplerGl\n\n# Create a map\nmap = KeplerGl(height=400)\nmap\n```\n\n## Running Tests\n\n```bash\nuv run pytest\n```\n\n## Available npm Scripts\n\n- `npm run build` - Build TypeScript to `keplergl/static/`\n- `npm run dev` - Build with watch mode for development\n- `npm run typecheck` - Run TypeScript type checking\n- `npm run lint` - Run ESLint on source files\n\n## Publishing to PyPI\n\nPublishing is done manually via GitHub Actions using the \"Run workflow\" button.\n\n### Version Format\n\nThe version in `pyproject.toml` determines what type of release you can publish:\n\n- **Prerelease versions**: Must have a suffix like `a`, `b`, `rc`, or `dev` followed by a number\n  - Examples: `0.4.0a1` (alpha), `0.4.0b1` (beta), `0.4.0rc1` (release candidate), `0.4.0.dev1` (development)\n- **Official versions**: Clean semantic versions without any suffix\n  - Examples: `0.4.0`, `1.0.0`\n\n### Publishing Steps\n\n1. **Update the version** in both files:\n   - `pyproject.toml` (line: `version = \"x.x.x\"`)\n   - `keplergl/_version.py` (line: `__version__ = \"x.x.x\"`)\n\n2. **Go to GitHub Actions** → \"Build and Publish KeplerGL Python Package\" workflow\n\n3. **Click \"Run workflow\"** and select the appropriate option:\n   - **Prerelease** (checked by default): Publishes to PyPI as a prerelease. Version must have a prerelease suffix.\n   - **Official release** (unchecked): Publishes to PyPI as an official release. Version must be a clean version.\n\n4. The workflow will:\n   - Build and test the package\n   - Validate that the version format matches the publish type\n   - Publish to PyPI if validation passes\n\n### Validation Rules\n\n| Checkbox | Version | Result |\n|----------|---------|--------|\n| ✓ Prerelease | `0.4.0a1` | ✅ Publishes |\n| ✓ Prerelease | `0.4.0` | ❌ Fails (version must have prerelease suffix) |\n| ☐ Official | `0.4.0` | ✅ Publishes |\n| ☐ Official | `0.4.0a1` | ❌ Fails (version must not have prerelease suffix) |\n"
  },
  {
    "path": "bindings/python/README.md",
    "content": "# kepler.gl for Jupyter\n\n[![PyPI version](https://img.shields.io/pypi/v/keplergl.svg)](https://pypi.org/project/keplergl/)\n[![PyPI prerelease](https://img.shields.io/pypi/v/keplergl.svg?include_prereleases&label=prerelease)](https://pypi.org/project/keplergl/#history)\n\nThis is the [kepler.gl](http://kepler.gl) Jupyter widget, an advanced geospatial visualization tool for rendering large-scale interactive maps in Jupyter Notebook and JupyterLab.\n\n\n\n## Installation\n\n```shell\npip install keplergl\n```\n\n### Prerequisites\n- Python >= 3.9\n- JupyterLab >= 4.0 or Notebook >= 7.0\n\n## Quick Start\n\n```python\nfrom keplergl import KeplerGl\n\n# Create a map\nmap = KeplerGl(height=400)\n\n# Add data\nmap.add_data(data=df, name='my_data')\n\n# Display the map\nmap\n```\n\n## Documentation\n\nFor full documentation, visit [https://docs.kepler.gl/docs/keplergl-jupyter](https://docs.kepler.gl/docs/keplergl-jupyter).\n\n## License\n\nMIT\n"
  },
  {
    "path": "bindings/python/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as esbuild from 'esbuild';\nimport path from 'path';\nimport {fileURLToPath} from 'url';\nimport {createRequire} from 'module';\n\nconst require = createRequire(import.meta.url);\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst isWatch = process.argv.includes('--watch');\n\n// Resolve apache-arrow to a single location\nconst arrowPath = path.dirname(require.resolve('apache-arrow'));\n\n// Resolve browser polyfills for Node.js built-ins (point to actual entry files)\nconst assertPath = require.resolve('assert/');\nconst eventsPath = require.resolve('events/');\n\nconst buildOptions = {\n  entryPoints: {widget: 'src/index.ts'},\n  bundle: true,\n  format: 'esm',\n  outdir: 'keplergl/static',\n  loader: {\n    '.ts': 'ts',\n    '.tsx': 'tsx',\n    '.css': 'css'\n  },\n  external: [],\n  minify: !isWatch,\n  sourcemap: isWatch,\n  target: ['es2020'],\n  define: {\n    'process.env.NODE_ENV': isWatch ? '\"development\"' : '\"production\"',\n    'global': 'globalThis'\n  },\n  inject: ['./src/process-shim.js'],\n  // Use alias to ensure single instance of apache-arrow and provide browser polyfills\n  alias: {\n    'apache-arrow': arrowPath,\n    'assert': assertPath,\n    'events': eventsPath\n  }\n};\n\nasync function build() {\n  if (isWatch) {\n    const ctx = await esbuild.context(buildOptions);\n    await ctx.watch();\n    console.log('Watching for changes...');\n  } else {\n    await esbuild.build(buildOptions);\n    console.log('Build complete!');\n  }\n}\n\nbuild().catch(() => process.exit(1));\n"
  },
  {
    "path": "bindings/python/keplergl/__init__.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Kepler.gl Jupyter Widget.\"\"\"\n\nfrom .widget import KeplerGl\nfrom ._version import __version__\n\n__all__ = [\"KeplerGl\", \"__version__\"]\n"
  },
  {
    "path": "bindings/python/keplergl/_version.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Version information.\"\"\"\n\n__version__ = \"0.4.0rc1\"\n"
  },
  {
    "path": "bindings/python/keplergl/serializers.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Data serialization for Python <-> JavaScript communication.\"\"\"\n\nimport base64\nfrom typing import Any, Optional\n\nimport pandas as pd\nimport geopandas as gpd\nimport pyarrow as pa\n\nDEBUG = False\n\ndef _debug(msg):\n    if DEBUG:\n        print(f\"[keplergl-serializer] {msg}\")\n\n\ndef _try_parse_geojson(data: str) -> Optional[dict]:\n    \"\"\"Try to parse a string as GeoJSON. Returns parsed dict if valid GeoJSON, None otherwise.\"\"\"\n    import json\n    try:\n        parsed = json.loads(data)\n        if isinstance(parsed, dict):\n            # Check for GeoJSON indicators\n            geojson_types = {\"FeatureCollection\", \"Feature\", \"Point\", \"MultiPoint\",\n                           \"LineString\", \"MultiLineString\", \"Polygon\", \"MultiPolygon\",\n                           \"GeometryCollection\"}\n            if parsed.get(\"type\") in geojson_types:\n                return parsed\n    except (json.JSONDecodeError, TypeError):\n        pass\n    return None\n\n\ndef data_to_json(data: dict, widget) -> dict:\n    \"\"\"Serialize Python data for JavaScript consumption.\"\"\"\n    _debug(f\"data_to_json called with {len(data)} datasets\")\n    result = {}\n    for name, dataset in data.items():\n        _debug(f\"  Serializing dataset '{name}' of type {type(dataset).__name__}\")\n        result[name] = serialize_dataset(dataset, name)\n        _debug(f\"  Result format: {result[name].get('format')}\")\n    _debug(f\"data_to_json result: {list(result.keys())}\")\n    return result\n\n\ndef data_from_json(data: dict, widget) -> dict:\n    \"\"\"Deserialize JavaScript data to Python (passthrough).\"\"\"\n    return data\n\n\ndef serialize_dataset(data: Any, name: str) -> dict:\n    \"\"\"Serialize a single dataset.\"\"\"\n    if isinstance(data, gpd.GeoDataFrame):\n        return serialize_geodataframe(data, name)\n    elif isinstance(data, pd.DataFrame):\n        return serialize_dataframe(data, name)\n    elif isinstance(data, str):\n        # Try to detect if the string is GeoJSON\n        geojson_data = _try_parse_geojson(data)\n        if geojson_data is not None:\n            _debug(f\"  Detected GeoJSON string for '{name}'\")\n            return {\"id\": name, \"data\": geojson_data, \"format\": \"geojson\"}\n        # Otherwise treat as CSV\n        return {\"id\": name, \"data\": data, \"format\": \"csv\"}\n    elif isinstance(data, dict):\n        return {\"id\": name, \"data\": data, \"format\": \"geojson\"}\n    else:\n        raise ValueError(f\"Unsupported data type: {type(data)}\")\n\n\ndef serialize_dataframe(df: pd.DataFrame, name: str) -> dict:\n    \"\"\"Serialize DataFrame to JSON format for kepler.gl.\n    \n    We use JSON format instead of Arrow to avoid version mismatch issues\n    between the apache-arrow package used here and the one bundled with kepler.gl.\n    \"\"\"\n    columns = df.columns.tolist()\n    rows = df.values.tolist()\n    _debug(f\"serialize_dataframe: {len(columns)} columns, {len(rows)} rows\")\n    _debug(f\"  columns: {columns}\")\n    _debug(f\"  first row: {rows[0] if rows else 'empty'}\")\n    return {\n        \"id\": name,\n        \"data\": {\n            \"columns\": columns,\n            \"data\": rows,\n        },\n        \"format\": \"df\",\n    }\n\n\ndef serialize_geodataframe(gdf: gpd.GeoDataFrame, name: str) -> dict:\n    \"\"\"Serialize GeoDataFrame to GeoArrow format.\"\"\"\n    import geoarrow.pyarrow as ga\n\n    # Convert geometry column to geoarrow format\n    geom_col = gdf.geometry.name\n    geom_array = ga.as_geoarrow(gdf.geometry)\n\n    # Build table with non-geometry columns as regular Arrow arrays\n    non_geom_cols = [c for c in gdf.columns if c != geom_col]\n    arrays = [pa.array(gdf[c]) for c in non_geom_cols]\n    arrays.append(geom_array)\n    col_names = non_geom_cols + [geom_col]\n\n    table = pa.table(dict(zip(col_names, arrays)))\n    sink = pa.BufferOutputStream()\n    with pa.ipc.new_stream(sink, table.schema) as writer:\n        writer.write_table(table)\n    arrow_bytes = sink.getvalue().to_pybytes()\n    return {\n        \"id\": name,\n        \"data\": base64.b64encode(arrow_bytes).decode(\"utf-8\"),\n        \"format\": \"geoarrow\",\n    }\n"
  },
  {
    "path": "bindings/python/keplergl/widget.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Kepler.gl Jupyter Widget using anywidget.\"\"\"\n\nimport pathlib\nimport copy\nimport anywidget\nimport traitlets\n\nfrom .serializers import data_to_json, data_from_json\n\n_STATIC_DIR = pathlib.Path(__file__).parent / \"static\"\n\n\nclass KeplerGl(anywidget.AnyWidget):\n    \"\"\"Kepler.gl Jupyter Widget using anywidget.\"\"\"\n\n    _esm = _STATIC_DIR / \"widget.js\"\n    _css = _STATIC_DIR / \"widget.css\"\n\n    data = traitlets.Dict({}).tag(sync=True, to_json=data_to_json, from_json=data_from_json)\n    config = traitlets.Dict({}).tag(sync=True)\n    height = traitlets.Int(400).tag(sync=True)\n\n    def __init__(self, data=None, config=None, height=400, show_docs=False, **kwargs):\n        \"\"\"\n        Initialize KeplerGl widget.\n\n        Args:\n            data: Dict of dataset name to data (DataFrame, GeoDataFrame, CSV, GeoJSON)\n            config: Kepler.gl config dict\n            height: Widget height in pixels\n            show_docs: Deprecated, kept for compatibility\n        \"\"\"\n        super().__init__(**kwargs)\n        self.height = height\n\n        if config:\n            self.config = config\n\n        if data:\n            self._add_data_dict(data)\n\n    def add_data(self, data, name=\"data\"):\n        \"\"\"Add data to the map.\"\"\"\n        updated = copy.deepcopy(self.data)\n        updated[name] = data\n        self.data = updated\n\n    def _add_data_dict(self, data_dict):\n        \"\"\"Add multiple datasets from a dict.\"\"\"\n        updated = copy.deepcopy(self.data)\n        for name, data in data_dict.items():\n            updated[name] = data\n        self.data = updated\n\n    def save_to_html(\n        self,\n        file_name=\"keplergl_map.html\",\n        data=None,\n        config=None,\n        read_only=False,\n        center_map=False,\n    ):\n        \"\"\"Export the map to a standalone HTML file.\n\n        Note: This method is no longer supported in the new anywidget-based implementation.\n        Please use the \"Share\" button in the map config panel to export your map.\n        \"\"\"\n        print(\n            \"save_to_html() is no longer supported in this version.\\n\"\n            \"Please use the 'Share' button in the map config panel to export your map to HTML.\"\n        )\n"
  },
  {
    "path": "bindings/python/notebooks/DataFrame.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import pandas as pd\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"df = pd.DataFrame(\\n\",\n    \"    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\\n\",\n    \"     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\\n\",\n    \"     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\\n\",\n    \"     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86],\\n\",\n    \"     'Time': ['2019-09-01 08:00','2019-09-01 09:00','2019-09-01 10:00','2019-09-01 11:00', '2019-09-01 12:00']\\n\",\n    \"    })\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"37198f4ae24b463db826f3e81de6d4b3\",\n       \"version_major\": 2,\n       \"version_minor\": 1\n      },\n      \"text/plain\": [\n       \"<keplergl.widget.KeplerGl object at 0x13470de50>\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=600, data={'data_1': df})\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"save_to_html() is no longer supported in this version.\\n\",\n      \"Please use the 'Share' button in the map config panel to export your map to HTML.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"w1.save_to_html(file_name='keplergl_map.html')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/python/notebooks/GeoDataFrame.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import pandas as pd\\n\",\n    \"import geopandas\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"df = pd.DataFrame(\\n\",\n    \"    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\\n\",\n    \"     'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\\n\",\n    \"     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\\n\",\n    \"     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86]})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"point_gdf = geopandas.GeoDataFrame(df, geometry=geopandas.points_from_xy(df.Longitude, df.Latitude))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div>\\n\",\n       \"<style scoped>\\n\",\n       \"    .dataframe tbody tr th:only-of-type {\\n\",\n       \"        vertical-align: middle;\\n\",\n       \"    }\\n\",\n       \"\\n\",\n       \"    .dataframe tbody tr th {\\n\",\n       \"        vertical-align: top;\\n\",\n       \"    }\\n\",\n       \"\\n\",\n       \"    .dataframe thead th {\\n\",\n       \"        text-align: right;\\n\",\n       \"    }\\n\",\n       \"</style>\\n\",\n       \"<table border=\\\"1\\\" class=\\\"dataframe\\\">\\n\",\n       \"  <thead>\\n\",\n       \"    <tr style=\\\"text-align: right;\\\">\\n\",\n       \"      <th></th>\\n\",\n       \"      <th>OBJECTID</th>\\n\",\n       \"      <th>ZIP_CODE</th>\\n\",\n       \"      <th>ID</th>\\n\",\n       \"      <th>geometry</th>\\n\",\n       \"    </tr>\\n\",\n       \"  </thead>\\n\",\n       \"  <tbody>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>0</th>\\n\",\n       \"      <td>1</td>\\n\",\n       \"      <td>94107</td>\\n\",\n       \"      <td>94107</td>\\n\",\n       \"      <td>POLYGON ((-122.40116 37.78202, -122.40037 37.7...</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>1</th>\\n\",\n       \"      <td>2</td>\\n\",\n       \"      <td>94105</td>\\n\",\n       \"      <td>94105</td>\\n\",\n       \"      <td>POLYGON ((-122.3925 37.79377, -122.39189 37.79...</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>2</th>\\n\",\n       \"      <td>3</td>\\n\",\n       \"      <td>94129</td>\\n\",\n       \"      <td>94129</td>\\n\",\n       \"      <td>POLYGON ((-122.47099 37.78753, -122.47229 37.7...</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>3</th>\\n\",\n       \"      <td>4</td>\\n\",\n       \"      <td>94121</td>\\n\",\n       \"      <td>94121</td>\\n\",\n       \"      <td>POLYGON ((-122.50446 37.78807, -122.50415 37.7...</td>\\n\",\n       \"    </tr>\\n\",\n       \"    <tr>\\n\",\n       \"      <th>4</th>\\n\",\n       \"      <td>5</td>\\n\",\n       \"      <td>94118</td>\\n\",\n       \"      <td>94118</td>\\n\",\n       \"      <td>POLYGON ((-122.44889 37.77839, -122.44977 37.7...</td>\\n\",\n       \"    </tr>\\n\",\n       \"  </tbody>\\n\",\n       \"</table>\\n\",\n       \"</div>\"\n      ],\n      \"text/plain\": [\n       \"   OBJECTID  ZIP_CODE     ID  \\\\\\n\",\n       \"0         1     94107  94107   \\n\",\n       \"1         2     94105  94105   \\n\",\n       \"2         3     94129  94129   \\n\",\n       \"3         4     94121  94121   \\n\",\n       \"4         5     94118  94118   \\n\",\n       \"\\n\",\n       \"                                            geometry  \\n\",\n       \"0  POLYGON ((-122.40116 37.78202, -122.40037 37.7...  \\n\",\n       \"1  POLYGON ((-122.3925 37.79377, -122.39189 37.79...  \\n\",\n       \"2  POLYGON ((-122.47099 37.78753, -122.47229 37.7...  \\n\",\n       \"3  POLYGON ((-122.50446 37.78807, -122.50415 37.7...  \\n\",\n       \"4  POLYGON ((-122.44889 37.77839, -122.44977 37.7...  \"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"zipcode_gdf = geopandas.read_file('sf_zip_geo.json')\\n\",\n    \"display(zipcode_gdf.head(5))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"0de352dc6ee744e7b073abff5b4d2d8c\",\n       \"version_major\": 2,\n       \"version_minor\": 1\n      },\n      \"text/plain\": [\n       \"<keplergl.widget.KeplerGl object at 0x11f7d56a0>\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=500)\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(data=zipcode_gdf, name=\\\"zipcode\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(data=point_gdf, name='cities')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"save_to_html() is no longer supported in this version.\\n\",\n      \"Please use the 'Share' button in the map config panel to export your map to HTML.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"w1.save_to_html(file_name='city_map.html', read_only=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/python/notebooks/GeoJSON.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with open('geojson-data.json', 'r') as f:\\n\",\n    \"    geojson = f.read()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"4e568feadd104e7fa7b957693328369c\",\n       \"version_major\": 2,\n       \"version_minor\": 1\n      },\n      \"text/plain\": [\n       \"<keplergl.widget.KeplerGl object at 0x10641c110>\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"map_1 = keplergl.KeplerGl(height=600)\\n\",\n    \"map_1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"map_1.add_data(geojson, \\\"geojson\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"save_to_html() is no longer supported in this version.\\n\",\n      \"Please use the 'Share' button in the map config panel to export your map to HTML.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"map_1.save_to_html(file_name=\\\"geojson_map.html\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/python/notebooks/Load kepler.gl.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"             hex_id  value  is_true  float_value empty           time\\n\",\n      \"0   89283082c2fffff     64     True         64.1        11/1/17 11:00\\n\",\n      \"1   8928308288fffff     73     True         73.1        11/1/17 11:00\\n\",\n      \"2   89283082c07ffff     65     True         65.1        11/1/17 11:00\\n\",\n      \"3   89283082817ffff     74     True         74.1        11/1/17 11:00\\n\",\n      \"4   89283082c3bffff     66     True         66.1        11/1/17 11:00\\n\",\n      \"..              ...    ...      ...          ...   ...            ...\\n\",\n      \"83  8928309537bffff      4     True          4.1        11/1/17 11:00\\n\",\n      \"84  89283082d93ffff      6     True          6.1        11/1/17 11:00\\n\",\n      \"85  89283082d73ffff      1     True          1.1        11/1/17 13:00\\n\",\n      \"86  8928309530bffff      1     True          1.1        11/1/17 11:00\\n\",\n      \"87  8928309532bffff      1     True          1.1        11/1/17 11:00\\n\",\n      \"\\n\",\n      \"[88 rows x 6 columns]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import pandas as pd\\n\",\n    \"df = pd.read_csv('hex-data.csv')\\n\",\n    \"df = df.fillna('')\\n\",\n    \"print(df)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import json\\n\",\n    \"with open('sf_zip_geo.json', 'r') as f:\\n\",\n    \"    geojson = f.read()\\n\",\n    \"\\n\",\n    \"json_data=json.loads(geojson)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# assign config = hex_config\\n\",\n    \"%run hex_config.py\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"bb5e76638fab46b39b476ad048984097\",\n       \"version_major\": 2,\n       \"version_minor\": 1\n      },\n      \"text/plain\": [\n       \"<keplergl.widget.KeplerGl object at 0x11adf8c80>\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import keplergl\\n\",\n    \"w1 = keplergl.KeplerGl(height=500, data={\\\"data_1\\\": df}, config=config)\\n\",\n    \"# w1 = keplergl.KeplerGl(height=500)\\n\",\n    \"w1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(df, 'data_1')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'version': 'v1',\\n\",\n       \" 'config': {'visState': {'layers': [{'type': 'hexagonId',\\n\",\n       \"     'visualChannels': {'sizeField': {'type': 'integer', 'name': 'value'},\\n\",\n       \"      'coverageField': None,\\n\",\n       \"      'colorScale': 'quantize',\\n\",\n       \"      'coverageScale': 'linear',\\n\",\n       \"      'colorField': {'type': 'integer', 'name': 'value'},\\n\",\n       \"      'sizeScale': 'linear'},\\n\",\n       \"     'config': {'dataId': 'data_1',\\n\",\n       \"      'color': [250, 116, 0],\\n\",\n       \"      'textLabel': {'color': [255, 255, 255],\\n\",\n       \"       'field': None,\\n\",\n       \"       'size': 50,\\n\",\n       \"       'anchor': 'middle',\\n\",\n       \"       'offset': [0, 0]},\\n\",\n       \"      'label': 'H3 Hexagon',\\n\",\n       \"      'isVisible': True,\\n\",\n       \"      'visConfig': {'coverageRange': [0, 1],\\n\",\n       \"       'opacity': 0.8,\\n\",\n       \"       'elevationScale': 5,\\n\",\n       \"       'hi-precision': False,\\n\",\n       \"       'coverage': 1,\\n\",\n       \"       'enable3d': True,\\n\",\n       \"       'sizeRange': [0, 500],\\n\",\n       \"       'colorRange': {'category': 'Uber',\\n\",\n       \"        'type': 'sequential',\\n\",\n       \"        'colors': ['#194266',\\n\",\n       \"         '#355C7D',\\n\",\n       \"         '#63617F',\\n\",\n       \"         '#916681',\\n\",\n       \"         '#C06C84',\\n\",\n       \"         '#D28389',\\n\",\n       \"         '#E59A8F',\\n\",\n       \"         '#F8B195'],\\n\",\n       \"        'reversed': False,\\n\",\n       \"        'name': 'Sunrise 8'}},\\n\",\n       \"      'columns': {'hex_id': 'hex_id'}},\\n\",\n       \"     'id': 'jdys7lp'}],\\n\",\n       \"   'interactionConfig': {'brush': {'enabled': False, 'size': 0.5},\\n\",\n       \"    'tooltip': {'fieldsToShow': {'data_1': ['hex_id', 'value']},\\n\",\n       \"     'enabled': True}},\\n\",\n       \"   'splitMaps': [],\\n\",\n       \"   'layerBlending': 'normal',\\n\",\n       \"   'filters': []},\\n\",\n       \"  'mapState': {'bearing': 2.6192893401015205,\\n\",\n       \"   'dragRotate': True,\\n\",\n       \"   'zoom': 12.32053899007826,\\n\",\n       \"   'longitude': -122.42590232651203,\\n\",\n       \"   'isSplit': False,\\n\",\n       \"   'pitch': 37.374216241015446,\\n\",\n       \"   'latitude': 37.76209132041332},\\n\",\n       \"  'mapStyle': {'mapStyles': {},\\n\",\n       \"   'topLayerGroups': {},\\n\",\n       \"   'styleType': 'dark',\\n\",\n       \"   'visibleLayerGroups': {'building': True,\\n\",\n       \"    'land': True,\\n\",\n       \"    '3d building': False,\\n\",\n       \"    'label': True,\\n\",\n       \"    'water': True,\\n\",\n       \"    'border': False,\\n\",\n       \"    'road': True}}}}\"\n      ]\n     },\n     \"execution_count\": 6,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"w1.config\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.add_data(json_data, 'geojson')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w1.save_to_html(file_name='first_map.html', data={\\\"data_1\\\": df}, config=config)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "bindings/python/notebooks/geojson-data.json",
    "content": "{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"properties\": {\n        \"fill\": true\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          [\n            [\n              2.331032753,\n              48.8411359942\n            ],\n            [\n              2.3269987106,\n              48.8425764529\n            ],\n            [\n              2.3253250122,\n              48.8431130838\n            ],\n            [\n              2.3240375519,\n              48.8435367357\n            ],\n            [\n              2.3217630386,\n              48.8443840288\n            ],\n            [\n              2.3203468323,\n              48.8448923977\n            ],\n            [\n              2.3189735413,\n              48.8453160346\n            ],\n            [\n              2.3167848587,\n              48.846558682\n            ],\n            [\n              2.3136520386,\n              48.8456831836\n            ],\n            [\n              2.3111844063,\n              48.8450618529\n            ],\n            [\n              2.3070859909,\n              48.8438897762\n            ],\n            [\n              2.2969740629,\n              48.8408994443\n            ],\n            [\n              2.2952359915,\n              48.8427106112\n            ],\n            [\n              2.2949945927,\n              48.84285183\n            ],\n            [\n              2.2928863764,\n              48.8436249961\n            ],\n            [\n              2.2912180424,\n              48.844327543\n            ],\n            [\n              2.2873932123,\n              48.8458173336\n            ],\n            [\n              2.2863632441,\n              48.8462833253\n            ],\n            [\n              2.2854137421,\n              48.8467704938\n            ],\n            [\n              2.2820448875,\n              48.849008584\n            ],\n            [\n              2.2804355621,\n              48.8498063649\n            ],\n            [\n              2.2793841362,\n              48.8498204848\n            ],\n            [\n              2.2790408134,\n              48.8506535519\n            ],\n            [\n              2.2756505013,\n              48.8526585039\n            ],\n            [\n              2.2742128372,\n              48.8531667889\n            ],\n            [\n              2.2726249695,\n              48.8535197615\n            ],\n            [\n              2.2703289986,\n              48.8542539366\n            ],\n            [\n              2.2671747208,\n              48.8551434035\n            ],\n            [\n              2.2649002075,\n              48.855694018\n            ],\n            [\n              2.2637414932,\n              48.8544233601\n            ],\n            [\n              2.2617030144,\n              48.8551010483\n            ],\n            [\n              2.2633981705,\n              48.8576564152\n            ],\n            [\n              2.2649002075,\n              48.8599434285\n            ],\n            [\n              2.2664451599,\n              48.8626538275\n            ],\n            [\n              2.2666811943,\n              48.8631902432\n            ],\n            [\n              2.267947197,\n              48.8647006461\n            ],\n            [\n              2.2694063187,\n              48.8667897264\n            ],\n            [\n              2.2713589668,\n              48.8690480934\n            ],\n            [\n              2.2736120224,\n              48.871009967\n            ],\n            [\n              2.2753071785,\n              48.8723507717\n            ],\n            [\n              2.2773456573,\n              48.8737197667\n            ],\n            [\n              2.2782897949,\n              48.8750040476\n            ],\n            [\n              2.2804999352,\n              48.8774737258\n            ],\n            [\n              2.2818946838,\n              48.8795058267\n            ],\n            [\n              2.2835040092,\n              48.8808605148\n            ],\n            [\n              2.2857570648,\n              48.882229277\n            ],\n            [\n              2.2899627686,\n              48.8848396989\n            ],\n            [\n              2.2913146019,\n              48.8857568418\n            ],\n            [\n              2.294511795,\n              48.8876475141\n            ],\n            [\n              2.2955846786,\n              48.8878732613\n            ],\n            [\n              2.2964859009,\n              48.8878027154\n            ],\n            [\n              2.297065258,\n              48.8876192956\n            ],\n            [\n              2.2975587845,\n              48.8874923124\n            ],\n            [\n              2.298116684,\n              48.8875064216\n            ],\n            [\n              2.298438549,\n              48.8875910772\n            ],\n            [\n              2.2989320755,\n              48.8879296979\n            ],\n            [\n              2.2997903824,\n              48.8882965344\n            ],\n            [\n              2.3006701469,\n              48.8886210414\n            ],\n            [\n              2.3015713692,\n              48.889270049\n            ],\n            [\n              2.3037815094,\n              48.8903423041\n            ],\n            [\n              2.3057985306,\n              48.8900742425\n            ],\n            [\n              2.3074293137,\n              48.8893405928\n            ],\n            [\n              2.3093390465,\n              48.8887762396\n            ],\n            [\n              2.311205864,\n              48.8880566801\n            ],\n            [\n              2.314145565,\n              48.8876051864\n            ],\n            [\n              2.3184370995,\n              48.8917954552\n            ],\n            [\n              2.3220419884,\n              48.8897497449\n            ],\n            [\n              2.325668335,\n              48.887703951\n            ],\n            [\n              2.3268485069,\n              48.8851924481\n            ],\n            [\n              2.3274922371,\n              48.8838378774\n            ],\n            [\n              2.328063794,\n              48.883946667\n            ],\n            [\n              2.329651662,\n              48.88493438\n            ],\n            [\n              2.336046047,\n              48.88318471\n            ],\n            [\n              2.339479275,\n              48.882422738\n            ],\n            [\n              2.342912503,\n              48.882987163\n            ],\n            [\n              2.345744913,\n              48.883551581\n            ],\n            [\n              2.34951366,\n              48.884013132\n            ],\n            [\n              2.35071529,\n              48.882827858\n            ],\n            [\n              2.351959835,\n              48.881501444\n            ],\n            [\n              2.352954691,\n              48.880503646\n            ],\n            [\n              2.354234348,\n              48.8790743\n            ],\n            [\n              2.355393062,\n              48.877752921\n            ],\n            [\n              2.35625137,\n              48.876623947\n            ],\n            [\n              2.3603439331,\n              48.8759495959\n            ],\n            [\n              2.3634767532,\n              48.875413317\n            ],\n            [\n              2.3630905151,\n              48.8741996118\n            ],\n            [\n              2.3636484146,\n              48.8733528233\n            ],\n            [\n              2.3643350601,\n              48.8725060204\n            ],\n            [\n              2.3652791977,\n              48.8711511061\n            ],\n            [\n              2.3658370972,\n              48.8704171788\n            ],\n            [\n              2.3668670654,\n              48.8691468946\n            ],\n            [\n              2.3678970337,\n              48.8694432972\n            ],\n            [\n              2.3685836792,\n              48.8686105428\n            ],\n            [\n              2.368991375,\n              48.8680459556\n            ],\n            [\n              2.3677682877,\n              48.8677636597\n            ],\n            [\n              2.3687553406,\n              48.8666626902\n            ],\n            [\n              2.3702573776,\n              48.8647994559\n            ],\n            [\n              2.371544838,\n              48.8632184755\n            ],\n            [\n              2.3724031448,\n              48.8618350766\n            ],\n            [\n              2.3724031448,\n              48.8604516395\n            ],\n            [\n              2.3719739914,\n              48.8594634467\n            ],\n            [\n              2.371544838,\n              48.8586164088\n            ],\n            [\n              2.3711585999,\n              48.8575434736\n            ],\n            [\n              2.3706436157,\n              48.8566681672\n            ],\n            [\n              2.3701715469,\n              48.8555951902\n            ],\n            [\n              2.3698282242,\n              48.8546916128\n            ],\n            [\n              2.3700428009,\n              48.8529126471\n            ],\n            [\n              2.3684120178,\n              48.8526302657\n            ],\n            [\n              2.3662662506,\n              48.8520654983\n            ],\n            [\n              2.3646354675,\n              48.8515007245\n            ],\n            [\n              2.3644798994,\n              48.8514583662\n            ],\n            [\n              2.3643136024,\n              48.8514195377\n            ],\n            [\n              2.3640614748,\n              48.8513312911\n            ],\n            [\n              2.3639059067,\n              48.8512854028\n            ],\n            [\n              2.3637878895,\n              48.8512395145\n            ],\n            [\n              2.3636591434,\n              48.8511759767\n            ],\n            [\n              2.3634928465,\n              48.8511159687\n            ],\n            [\n              2.363294363,\n              48.8510594905\n            ],\n            [\n              2.3630422354,\n              48.8509888927\n            ],\n            [\n              2.3628437519,\n              48.8509394742\n            ],\n            [\n              2.3625648022,\n              48.8507806287\n            ],\n            [\n              2.3624360561,\n              48.8507100305\n            ],\n            [\n              2.3620980978,\n              48.8506641416\n            ],\n            [\n              2.3619157076,\n              48.8506394322\n            ],\n            [\n              2.3615992069,\n              48.8506217826\n            ],\n            [\n              2.361395359,\n              48.8505829535\n            ],\n            [\n              2.3611968756,\n              48.8505441243\n            ],\n            [\n              2.3611646891,\n              48.8504982353\n            ],\n            [\n              2.361100316,\n              48.8504452864\n            ],\n            [\n              2.3609608412,\n              48.8503888075\n            ],\n            [\n              2.3608535528,\n              48.8503287985\n            ],\n            [\n              2.3607730865,\n              48.8502970291\n            ],\n            [\n              2.3605155945,\n              48.8502299602\n            ],\n            [\n              2.360419035,\n              48.8501805409\n            ],\n            [\n              2.3603439331,\n              48.8501523013\n            ],\n            [\n              2.3601508141,\n              48.8500287529\n            ],\n            [\n              2.3589706421,\n              48.8495804459\n            ],\n            [\n              2.3583698273,\n              48.8491850853\n            ],\n            [\n              2.365322113,\n              48.8442710572\n            ],\n            [\n              2.3629188538,\n              48.8400909296\n            ],\n            [\n              2.3513317108,\n              48.8363624133\n            ],\n            [\n              2.3368692398,\n              48.8393000554\n            ],\n            [\n              2.3351955414,\n              48.8398367214\n            ],\n            [\n              2.3342084885,\n              48.8402039106\n            ],\n            [\n              2.3331356049,\n              48.8405146071\n            ],\n            [\n              2.331032753,\n              48.8411359942\n            ]\n          ]\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "bindings/python/notebooks/hex-data.csv",
    "content": "hex_id,value,is_true,float_value,empty,time\n89283082c2fffff,64,true,64.1,,11/1/17 11:00\n8928308288fffff,73,true,73.1,,11/1/17 11:00\n89283082c07ffff,65,true,65.1,,11/1/17 11:00\n89283082817ffff,74,true,74.1,,11/1/17 11:00\n89283082c3bffff,66,true,66.1,,11/1/17 11:00\n89283082883ffff,76,true,76.1,,11/1/17 11:00\n89283082c03ffff,60,true,60.1,,11/1/17 11:00\n89283082807ffff,68,true,68.1,,11/1/17 10:00\n8928308289bffff,49,true,49.1,,11/1/17 11:00\n89283082c0fffff,41,true,41.1,,11/1/17 11:00\n89283082c87ffff,50,true,50.1,,11/1/17 11:00\n89283082d4fffff,45,true,45.1,,11/1/17 11:00\n89283082c77ffff,41,true,41.1,,11/1/17 11:00\n89283082c2bffff,53,true,53.1,,11/1/17 11:00\n89283082803ffff,41,true,41.1,,11/1/17 11:00\n89283082813ffff,43,true,43.1,,11/1/17 11:00\n89283082d5bffff,45,true,45.1,,11/1/17 11:00\n89283082897ffff,40,true,40.1,,11/1/17 11:00\n89283082c67ffff,42,true,42.1,,11/1/17 11:00\n89283082d47ffff,51,true,51.1,,11/1/17 11:00\n89283082dc3ffff,52,true,52.1,,11/1/17 11:00\n89283082c33ffff,43,true,43.1,,11/1/17 11:00\n89283082c23ffff,40,true,40.1,,11/1/17 11:00\n89283082887ffff,36,true,36.1,,11/1/17 11:00\n89283082d4bffff,36,true,36.1,,11/1/17 11:00\n892830828bbffff,48,true,48.1,,11/1/17 11:00\n892830828b7ffff,28,true,28.1,,11/1/17 11:00\n89283082c17ffff,34,true,34.1,,11/1/17 11:00\n89283082c6fffff,21,true,21.1,,11/1/17 12:00\n8928308288bffff,25,true,25.1,,11/1/17 11:00\n892830828abffff,26,true,26.1,,11/1/17 11:00\n89283082c27ffff,27,true,27.1,,11/1/17 11:00\n89283082c8fffff,33,true,33.1,,11/1/17 11:00\n89283082cafffff,29,true,29.1,,11/1/17 11:00\n89283082c13ffff,27,true,27.1,,11/1/17 11:00\n89283082cabffff,22,true,22.1,,11/1/17 11:00\n89283082c63ffff,26,true,26.1,,11/1/17 11:00\n89283082d43ffff,30,true,30.1,,11/1/17 11:00\n89283082d53ffff,19,true,19.1,,11/1/17 11:00\n892830828a3ffff,28,false,28.1,,11/1/17 11:00\n89283082d1bffff,20,false,20.1,,11/1/17 11:00\n89283095367ffff,17,false,17.1,,11/1/17 11:00\n8928309536bffff,26,false,26.1,,11/1/17 11:00\n89283082c37ffff,16,false,16.1,,11/1/17 11:00\n89283082c73ffff,17,false,17.1,,11/1/17 11:00\n89283082c8bffff,15,false,15.1,,11/1/17 11:00\n89283082ca7ffff,27,false,27.1,,11/1/17 11:00\n89283082cb3ffff,32,false,32.1,,11/1/17 11:00\n89283082c0bffff,26,false,26.1,,11/1/17 11:00\n89283082ca3ffff,19,false,19.1,,11/1/17 11:00\n89283082dcfffff,18,false,18.1,,11/1/17 11:00\n89283082c1bffff,20,false,20.1,,11/1/17 15:00\n89283082ddbffff,18,false,18.1,,11/1/17 11:00\n8928309534fffff,16,false,16.1,,11/1/17 11:00\n89283082d03ffff,15,false,15.1,,11/1/17 11:00\n89283082cbbffff,21,false,21.1,,11/1/17 11:00\n89283082cd7ffff,9,true,9.1,,11/1/17 11:00\n8928309534bffff,9,true,9.1,,11/1/17 11:00\n892830828c7ffff,13,true,13.1,,11/1/17 11:00\n89283082cc7ffff,12,true,12.1,,11/1/17 11:00\n89283082d0bffff,19,true,19.1,,11/1/17 11:00\n89283082dcbffff,19,true,19.1,,11/1/17 11:00\n89283082dd3ffff,15,true,15.1,,11/1/17 11:00\n89283082dd7ffff,15,true,15.1,,11/1/17 11:00\n892830828d7ffff,13,true,13.1,,11/1/17 11:00\n89283082d17ffff,5,true,5.1,,11/1/17 11:00\n8928309536fffff,8,true,8.1,,11/1/17 11:00\n89283095373ffff,6,true,6.1,,11/1/17 11:00\n89283082cb7ffff,15,true,15.1,,11/1/17 11:00\n89283082d83ffff,9,true,9.1,,11/1/17 11:00\n89283082d07ffff,4,true,4.1,,11/1/17 11:00\n89283082d0fffff,3,true,3.1,,11/1/17 11:00\n89283082d13ffff,6,true,6.1,,11/1/17 11:00\n89283082d9bffff,5,true,5.1,,11/1/17 11:00\n89283082c83ffff,11,true,11.1,,11/1/17 11:00\n89283082d8bffff,4,true,4.1,,11/1/17 11:00\n89283082dc7ffff,5,true,5.1,,11/1/17 11:00\n89283095377ffff,5,true,5.1,,11/1/17 11:00\n89283082c97ffff,4,true,4.1,,11/1/17 11:00\n89283082d7bffff,2,true,2.1,,11/1/17 11:00\n89283082d8fffff,1,true,1.1,,11/1/17 11:00\n89283095347ffff,3,true,3.1,,11/1/17 11:00\n89283095363ffff,2,true,2.1,,11/1/17 11:00\n8928309537bffff,4,true,4.1,,11/1/17 11:00\n89283082d93ffff,6,true,6.1,,11/1/17 11:00\n89283082d73ffff,1,true,1.1,,11/1/17 13:00\n8928309530bffff,1,true,1.1,,11/1/17 11:00\n8928309532bffff,1,true,1.1,,11/1/17 11:00"
  },
  {
    "path": "bindings/python/notebooks/hex_config.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\nconfig = {u'version': u'v1', u'config': {u'visState': {u'layers': [{u'type': u'hexagonId', u'visualChannels': {u'sizeField': {u'type': u'integer', u'name': u'value'}, u'coverageField': None, u'colorScale': u'quantize', u'coverageScale': u'linear', u'colorField': {u'type': u'integer', u'name': u'value'}, u'sizeScale': u'linear'}, u'config': {u'dataId': u'data_1', u'color': [250, 116, 0], u'textLabel': {u'color': [255, 255, 255], u'field': None, u'size': 50, u'anchor': u'middle', u'offset': [0, 0]}, u'label': u'H3 Hexagon', u'isVisible': True, u'visConfig': {u'coverageRange': [0, 1], u'opacity': 0.8, u'elevationScale': 5, u'hi-precision': False, u'coverage': 1, u'enable3d': True, u'sizeRange': [0, 500], u'colorRange': {u'category': u'Uber', u'type': u'sequential', u'colors': [u'#194266', u'#355C7D', u'#63617F', u'#916681', u'#C06C84', u'#D28389', u'#E59A8F', u'#F8B195'], u'reversed': False, u'name': u'Sunrise 8'}}, u'columns': {u'hex_id': u'hex_id'}}, u'id': u'jdys7lp'}], u'interactionConfig': {u'brush': {u'enabled': False, u'size': 0.5}, u'tooltip': {u'fieldsToShow': {u'data_1': [u'hex_id', u'value']}, u'enabled': True}}, u'splitMaps': [], u'layerBlending': u'normal', u'filters': []}, u'mapState': {u'bearing': 2.6192893401015205, u'dragRotate': True, u'zoom': 12.32053899007826, u'longitude': -122.42590232651203, u'isSplit': False, u'pitch': 37.374216241015446, u'latitude': 37.76209132041332}, u'mapStyle': {u'mapStyles': {}, u'topLayerGroups': {}, u'styleType': u'dark', u'visibleLayerGroups': {u'building': True, u'land': True, u'3d building': False, u'label': True, u'water': True, u'border': False, u'road': True}}}}"
  },
  {
    "path": "bindings/python/notebooks/sf_zip_geo.json",
    "content": "{\"type\": \"FeatureCollection\",\"features\": [\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":1,\"ZIP_CODE\":94107,\"ID\":94107},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.401159718585049, 37.782024266952142 ], [ -122.400374366843309, 37.782644515545172 ], [ -122.400019020063766, 37.782925153640136 ], [ -122.399891477967842, 37.783025880124256 ], [ -122.398930331092998, 37.783784933304034 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.396705177550587, 37.785542130425938 ], [ -122.395895701657864, 37.784896929203114 ], [ -122.395160622349934, 37.78431101230386 ], [ -122.394398389309941, 37.783701667981575 ], [ -122.39343711789931, 37.784459123881106 ], [ -122.39286959393705, 37.78478280203197 ], [ -122.39285700396303, 37.784773072758867 ], [ -122.392216553530702, 37.784278123341721 ], [ -122.392215026316663, 37.784279352365694 ], [ -122.392118766536498, 37.784205306785267 ], [ -122.392032919786118, 37.784168354830349 ], [ -122.391600764141245, 37.783823989838545 ], [ -122.391599105853345, 37.783822668268179 ], [ -122.390856721023212, 37.784410569188324 ], [ -122.389846392025618, 37.783609176617361 ], [ -122.389843136047602, 37.783611740842574 ], [ -122.388306939892615, 37.782385221520776 ], [ -122.388304301898813, 37.782356717503951 ], [ -122.388231340039766, 37.782146697467269 ], [ -122.388106738902522, 37.781931906493995 ], [ -122.388094863959623, 37.781887241816527 ], [ -122.387765464889313, 37.78194244138043 ], [ -122.387754811744713, 37.781795652877442 ], [ -122.385560654233757, 37.781935833609538 ], [ -122.385544999019174, 37.781932650370386 ], [ -122.38553439339303, 37.781923892586839 ], [ -122.385511671392734, 37.781778670688965 ], [ -122.384922372713746, 37.781810068903333 ], [ -122.384901998602373, 37.781825502544642 ], [ -122.384858074907243, 37.781799422577222 ], [ -122.384893494062709, 37.781763146995921 ], [ -122.38491634743859, 37.781777202531266 ], [ -122.38550727073536, 37.781741658296774 ], [ -122.385491676395333, 37.781604563077408 ], [ -122.385493075388183, 37.781591492690396 ], [ -122.38550321006835, 37.781581716485675 ], [ -122.385518655734231, 37.781576662678262 ], [ -122.38762837564704, 37.781450881969413 ], [ -122.387583140105036, 37.780964719298666 ], [ -122.387467577483136, 37.780979617487112 ], [ -122.387479366190874, 37.781103038700273 ], [ -122.387462086323822, 37.781104002067416 ], [ -122.387450297298699, 37.780980580858106 ], [ -122.387337984465276, 37.780987185935416 ], [ -122.387348026094102, 37.781109948402708 ], [ -122.387330746223768, 37.781110911750616 ], [ -122.387320704623349, 37.780988149281868 ], [ -122.387206644553231, 37.780994095205941 ], [ -122.387216703416328, 37.781117544403045 ], [ -122.387199423542512, 37.781118507731705 ], [ -122.387189364714985, 37.780995058807726 ], [ -122.387068420460224, 37.78100248851576 ], [ -122.387080191758074, 37.781125223313062 ], [ -122.387062911534741, 37.78112618662724 ], [ -122.387051140611234, 37.78100345182272 ], [ -122.386937097943829, 37.781010084204837 ], [ -122.386948869025886, 37.781132819015049 ], [ -122.386931588799087, 37.78113378231 ], [ -122.386919818091386, 37.781011047492527 ], [ -122.386812677010425, 37.7810168822962 ], [ -122.386824465334769, 37.781140303837503 ], [ -122.386807185104601, 37.781141267114201 ], [ -122.386795397161777, 37.781017845840225 ], [ -122.386676165495402, 37.781024560738594 ], [ -122.386687953248156, 37.781147982298883 ], [ -122.386670672668515, 37.781148945561135 ], [ -122.386658885297223, 37.781025524268173 ], [ -122.386543112789369, 37.781032183675542 ], [ -122.386554900668202, 37.781155605243434 ], [ -122.386537620431014, 37.781156568480647 ], [ -122.386525832933643, 37.781033147180089 ], [ -122.386411790166775, 37.781039778779515 ], [ -122.386423577828609, 37.781163200360375 ], [ -122.386406297587911, 37.781164163578339 ], [ -122.386394510307582, 37.7810407422648 ], [ -122.386282162758775, 37.781045973167615 ], [ -122.386293984730017, 37.781170767655468 ], [ -122.386276704139945, 37.78117173085996 ], [ -122.386264917419695, 37.781048309528096 ], [ -122.386147415421732, 37.781054996216575 ], [ -122.386159201954754, 37.781178417834589 ], [ -122.386141922053085, 37.781179381008279 ], [ -122.386130135209612, 37.78105595966862 ], [ -122.386019552249252, 37.781062535536719 ], [ -122.386031321484509, 37.781185270717479 ], [ -122.386014040887659, 37.781186233883489 ], [ -122.386002272033735, 37.781063498970049 ], [ -122.385879580345062, 37.78107026841095 ], [ -122.385891367127385, 37.781193690044354 ], [ -122.385874086872718, 37.781194653184308 ], [ -122.385862300471871, 37.781071231818238 ], [ -122.385755176650932, 37.781077752105325 ], [ -122.385766945797698, 37.781200487306606 ], [ -122.385749665539848, 37.78120145042832 ], [ -122.385737896774387, 37.781078715494381 ], [ -122.385622107018349, 37.781084687547477 ], [ -122.385633892683231, 37.781208109217374 ], [ -122.385616612767805, 37.781209072314041 ], [ -122.385604844221021, 37.781086337366979 ], [ -122.385487342103474, 37.781093023397624 ], [ -122.385499110464124, 37.781215758630843 ], [ -122.38548182985329, 37.781216721718849 ], [ -122.385470061873946, 37.781093986752957 ], [ -122.385352559732169, 37.781100672649266 ], [ -122.38536432752538, 37.781223407901315 ], [ -122.385347047602892, 37.781224370958491 ], [ -122.385335279499117, 37.781101635984861 ], [ -122.385219507099919, 37.781108294087041 ], [ -122.385231275020445, 37.781231029346657 ], [ -122.385213994748526, 37.781231992389863 ], [ -122.385202226517421, 37.781109257408659 ], [ -122.385086454440369, 37.781115915374649 ], [ -122.385098222142247, 37.781238650647374 ], [ -122.385080941520883, 37.781239613676604 ], [ -122.385069174200339, 37.781116878671227 ], [ -122.38495511409829, 37.781122822411653 ], [ -122.384966899006869, 37.781246244141819 ], [ -122.384949618382038, 37.781247207151821 ], [ -122.384937851277286, 37.781124472133506 ], [ -122.384823791153082, 37.781130415746894 ], [ -122.384835575844662, 37.781253837490027 ], [ -122.384818295216348, 37.781254800480774 ], [ -122.384792264836477, 37.780979154238715 ], [ -122.384809545055035, 37.780978191257269 ], [ -122.384831743135436, 37.781102819695278 ], [ -122.384925045670656, 37.781097207947639 ], [ -122.384923587517477, 37.780971560938383 ], [ -122.384940867732567, 37.780970597937682 ], [ -122.384963066031062, 37.781095226351141 ], [ -122.385056368546813, 37.781089614499592 ], [ -122.385054910171618, 37.780963967491779 ], [ -122.385072190383269, 37.780963004471836 ], [ -122.385094388899816, 37.781087632860746 ], [ -122.385189421508272, 37.781081993240349 ], [ -122.385187962562227, 37.780956346239556 ], [ -122.385205242424405, 37.780955383205644 ], [ -122.38522744150788, 37.781080011564157 ], [ -122.385322474096498, 37.781074371836517 ], [ -122.385321014925523, 37.780948724837195 ], [ -122.385338295130154, 37.780947761778229 ], [ -122.385360493742681, 37.78107239012293 ], [ -122.385457256077231, 37.781066722624757 ], [ -122.385455797024363, 37.780941075621371 ], [ -122.38547307722547, 37.78094011254268 ], [ -122.385495276407823, 37.781064740856642 ], [ -122.385592038722095, 37.781059073247853 ], [ -122.385590579095435, 37.780933426251508 ], [ -122.385607859293003, 37.780932463153057 ], [ -122.385630058353271, 37.781057091447337 ], [ -122.38572509122794, 37.781051451389651 ], [ -122.385723631030388, 37.780925804400319 ], [ -122.385740911570423, 37.780924841276843 ], [ -122.385763111197619, 37.781049469540704 ], [ -122.385849512311808, 37.781044654171389 ], [ -122.385848051896943, 37.78091900690886 ], [ -122.385865332440758, 37.780918044041726 ], [ -122.385887514843574, 37.781041985837916 ], [ -122.385989466046908, 37.781036234899503 ], [ -122.385988006094351, 37.780910587902042 ], [ -122.386005286281474, 37.780909624745334 ], [ -122.386027486347686, 37.781034252959756 ], [ -122.38611734695634, 37.781029382050761 ], [ -122.386115886434652, 37.78090373478566 ], [ -122.386133166625513, 37.780902771884811 ], [ -122.386155349124707, 37.781026713636507 ], [ -122.386250399355106, 37.78102175959976 ], [ -122.386248938262554, 37.780896112341686 ], [ -122.386266218795839, 37.780895149415798 ], [ -122.386288401860739, 37.781019091137082 ], [ -122.386381721962337, 37.781014164675518 ], [ -122.386380260647812, 37.780888517418902 ], [ -122.386397541177601, 37.780887554473743 ], [ -122.386419741895395, 37.781012182614816 ], [ -122.386513044542724, 37.781006569605005 ], [ -122.386511583006197, 37.780880922349844 ], [ -122.386528863532533, 37.780879959385452 ], [ -122.386551064468406, 37.781004587501954 ], [ -122.386646096514042, 37.780998946712984 ], [ -122.386644635444497, 37.780873299448245 ], [ -122.386661915621403, 37.780872336469891 ], [ -122.386684116778227, 37.780996964561524 ], [ -122.386782608677009, 37.780991268294322 ], [ -122.386781146684797, 37.780865621042153 ], [ -122.386798427204084, 37.780864658038261 ], [ -122.386820628587657, 37.780989286104372 ], [ -122.386907012122748, 37.780983783515829 ], [ -122.386905549920243, 37.780858136265046 ], [ -122.386922830436163, 37.780857173242921 ], [ -122.386945032026247, 37.780981801285769 ], [ -122.387038334595715, 37.780976187860247 ], [ -122.387036872171194, 37.780850540610921 ], [ -122.387054152683675, 37.780849577569541 ], [ -122.387076354491839, 37.780974205587825 ], [ -122.387174846330339, 37.780968508992949 ], [ -122.387173384021025, 37.7808428617396 ], [ -122.387190664183962, 37.780841898683754 ], [ -122.387212865872911, 37.78096652668205 ], [ -122.387306168748907, 37.78096091303906 ], [ -122.387304706217577, 37.780835265787175 ], [ -122.387321986377088, 37.780834302712059 ], [ -122.387344188284104, 37.780958930685784 ], [ -122.387435761378228, 37.780953344631229 ], [ -122.387434316073197, 37.780828383824961 ], [ -122.387451578437933, 37.780826734292219 ], [ -122.387473781252041, 37.780951362230645 ], [ -122.387582651602727, 37.780945499137275 ], [ -122.38754001837809, 37.780493630652451 ], [ -122.38744131821305, 37.780491090765793 ], [ -122.387419058221624, 37.780500374265543 ], [ -122.387330736209734, 37.780497667864211 ], [ -122.387308476209526, 37.780506951617653 ], [ -122.387208063026961, 37.780505125406954 ], [ -122.387184073602839, 37.780514436820333 ], [ -122.387081931006932, 37.780512638185321 ], [ -122.387059670971126, 37.780521921891747 ], [ -122.386964429936867, 37.780519325963184 ], [ -122.386942169884378, 37.780528609647433 ], [ -122.386852118103619, 37.780525930572708 ], [ -122.386829875474817, 37.780535900680007 ], [ -122.386736346744712, 37.780532590439464 ], [ -122.386712374345294, 37.780542588206671 ], [ -122.386618863049748, 37.780539964317235 ], [ -122.386594873201545, 37.780549275890777 ], [ -122.386499632141522, 37.780546679312224 ], [ -122.386475659366695, 37.780556677311395 ], [ -122.386383878154234, 37.780554025277233 ], [ -122.386359888272054, 37.780563336802956 ], [ -122.386266376955888, 37.780560712359396 ], [ -122.386242404491071, 37.780570710305611 ], [ -122.386143703562112, 37.780568168794694 ], [ -122.386119713984229, 37.780577479991436 ], [ -122.386022743164474, 37.78057491124985 ], [ -122.386000500402702, 37.780584880926071 ], [ -122.385901799477139, 37.780582339761835 ], [ -122.385877827288269, 37.780592337079185 ], [ -122.385784298190572, 37.780589026363579 ], [ -122.38576205573851, 37.780598995989244 ], [ -122.385668544412525, 37.780596371620753 ], [ -122.385646283822041, 37.780605654791167 ], [ -122.385554502585677, 37.780603002386016 ], [ -122.385532242331863, 37.780612285803919 ], [ -122.385433541724893, 37.780609743967645 ], [ -122.385411298534606, 37.78061971381274 ], [ -122.385310868512505, 37.780617199530866 ], [ -122.385288625649494, 37.780627169347277 ], [ -122.385191654786624, 37.780624599647666 ], [ -122.38516766472523, 37.780633910656128 ], [ -122.385079342290652, 37.780631202574007 ], [ -122.385057082314987, 37.780640485896626 ], [ -122.384960111442751, 37.78063791600659 ], [ -122.384936138770243, 37.780647913412501 ], [ -122.384835917442729, 37.780653636341071 ], [ -122.384842007573795, 37.78075723388941 ], [ -122.384817825799914, 37.78075899421075 ], [ -122.38480325384981, 37.78052574104445 ], [ -122.384827435209161, 37.780523981006105 ], [ -122.384843799331136, 37.780623294238858 ], [ -122.384923281326039, 37.780618589884874 ], [ -122.384925926962097, 37.780518286009212 ], [ -122.384957841591557, 37.780616663880899 ], [ -122.385044241908545, 37.780611849106826 ], [ -122.385045157967028, 37.780511572605498 ], [ -122.385078784737303, 37.780609236348262 ], [ -122.385154824277294, 37.780605273878329 ], [ -122.385157469939188, 37.780504969717121 ], [ -122.38518938452367, 37.780603347531915 ], [ -122.385275768076681, 37.780597846132551 ], [ -122.385276683130513, 37.780497569640254 ], [ -122.385310328316436, 37.780595919750695 ], [ -122.385398458680143, 37.78059107677575 ], [ -122.385399356494702, 37.780490114106904 ], [ -122.385433018927529, 37.78058915090709 ], [ -122.385519402086118, 37.780583648785594 ], [ -122.385520317170744, 37.78048337283478 ], [ -122.385553962326881, 37.780581722881479 ], [ -122.385633443879414, 37.780577017779677 ], [ -122.385634358810009, 37.780476741827854 ], [ -122.385666274013943, 37.780575119514069 ], [ -122.385749215060514, 37.780570359001402 ], [ -122.385750112750941, 37.780469396598548 ], [ -122.385783775282164, 37.780568432755352 ], [ -122.385864986912736, 37.780563700098369 ], [ -122.385865884092837, 37.780462737425502 ], [ -122.38589954712829, 37.780561773818384 ], [ -122.385987659981154, 37.780556243958578 ], [ -122.385988556648442, 37.780455281290152 ], [ -122.386022220190043, 37.78055431764264 ], [ -122.386106890951012, 37.780549529481313 ], [ -122.386107787802118, 37.780448566806321 ], [ -122.386141451153719, 37.780547603130415 ], [ -122.386229563971753, 37.780542073089819 ], [ -122.386232190406915, 37.780441082739081 ], [ -122.386264124167781, 37.780540146702961 ], [ -122.386347065143937, 37.780535386044313 ], [ -122.386349691419113, 37.78043439569084 ], [ -122.386381625333826, 37.780533459623001 ], [ -122.386462836549086, 37.780528726834362 ], [ -122.386465444877402, 37.780427049764754 ], [ -122.386497396725886, 37.780526800104532 ], [ -122.386582067431817, 37.780522011876563 ], [ -122.386584675942643, 37.78042033479867 ], [ -122.386616627602407, 37.780520085111789 ], [ -122.386699551102055, 37.78051463803542 ], [ -122.386702159451801, 37.78041296095477 ], [ -122.386734111266165, 37.780512711236199 ], [ -122.386817052181556, 37.780507950246935 ], [ -122.386817930281353, 37.780406301126988 ], [ -122.386851612353468, 37.780506023962438 ], [ -122.386929363990731, 37.7805013456708 ], [ -122.386930242282716, 37.780399696544364 ], [ -122.386963924156646, 37.780499419353383 ], [ -122.387046865035501, 37.780494657927875 ], [ -122.387047743173568, 37.78039300907502 ], [ -122.387081425188285, 37.780492731301436 ], [ -122.387171267622861, 37.780487172869918 ], [ -122.387173875339855, 37.780385496327504 ], [ -122.387205827768923, 37.780485246207022 ], [ -122.387295687629134, 37.780480374124949 ], [ -122.387296547982558, 37.780378038825731 ], [ -122.387330247768816, 37.780478447425573 ], [ -122.387406269602806, 37.780473796784797 ], [ -122.38740712980379, 37.780371461484656 ], [ -122.38744082973659, 37.78047187005302 ], [ -122.387527230063114, 37.780467053179258 ], [ -122.38749949992625, 37.780056836734332 ], [ -122.387400782538194, 37.780053609825671 ], [ -122.387378540131436, 37.780063580311925 ], [ -122.387290201196578, 37.780060187435744 ], [ -122.387267941315415, 37.780069470907662 ], [ -122.387167511635454, 37.780066958487033 ], [ -122.387143539437361, 37.780076956068172 ], [ -122.387041380009435, 37.780074471228168 ], [ -122.387019120092901, 37.780083754653084 ], [ -122.386922167327057, 37.780081873095298 ], [ -122.386901619695664, 37.780090442368873 ], [ -122.386811551008179, 37.78008707681861 ], [ -122.386787578762636, 37.780097074601919 ], [ -122.386695780329632, 37.780093736646045 ], [ -122.386671790289242, 37.780103048241536 ], [ -122.386578279887487, 37.780100424039603 ], [ -122.386554289830215, 37.78010973561117 ], [ -122.386459049681108, 37.780107138994076 ], [ -122.386435059945711, 37.780116450261254 ], [ -122.386343278946669, 37.780113798749966 ], [ -122.386319288841676, 37.780123109724549 ], [ -122.386225778441045, 37.780120485792217 ], [ -122.386201788319156, 37.780129796742877 ], [ -122.38610135893272, 37.780127283134078 ], [ -122.386079098889809, 37.780136566656026 ], [ -122.385982128640109, 37.780133997606264 ], [ -122.38595813883623, 37.780143308776367 ], [ -122.385859438837869, 37.78014076729638 ], [ -122.385837196191162, 37.780150737217127 ], [ -122.385743667990269, 37.780147426188911 ], [ -122.385719678159148, 37.780156737585067 ], [ -122.385627896776001, 37.780154084973319 ], [ -122.385603907274145, 37.780163396340377 ], [ -122.385513855977493, 37.78016071596884 ], [ -122.385489865760334, 37.780170027049152 ], [ -122.385392895494292, 37.780167457789418 ], [ -122.385368905944745, 37.780176768559457 ], [ -122.385270205929046, 37.780174226860836 ], [ -122.385246216015972, 37.78018353761145 ], [ -122.385150975133058, 37.78018094022346 ], [ -122.385126985555786, 37.780190251218855 ], [ -122.385038663996198, 37.78018754310051 ], [ -122.385014674056748, 37.780196854078561 ], [ -122.384919433510831, 37.780194256498035 ], [ -122.38489544320835, 37.780203567457356 ], [ -122.384795222823172, 37.780209290346271 ], [ -122.384801312515108, 37.780312887910483 ], [ -122.384777113810173, 37.780313961773459 ], [ -122.384762559495741, 37.780081395028191 ], [ -122.384786741057312, 37.780079634992532 ], [ -122.384803105027004, 37.780178948238863 ], [ -122.384882586203048, 37.780174243917422 ], [ -122.384885231532309, 37.780073940040232 ], [ -122.384917146262893, 37.780172317925249 ], [ -122.385001816666616, 37.780167530831704 ], [ -122.385004462173583, 37.780067226671626 ], [ -122.385036376713245, 37.780165604530019 ], [ -122.38511414553922, 37.780161614154224 ], [ -122.385116790908242, 37.780061310540667 ], [ -122.385148705594133, 37.780159688368748 ], [ -122.385233375608649, 37.780154900565314 ], [ -122.385236021162356, 37.780054596943422 ], [ -122.385267935657353, 37.780152974744887 ], [ -122.385356065837257, 37.780148131521123 ], [ -122.385358710872126, 37.780047827627314 ], [ -122.385390625872631, 37.780146205390146 ], [ -122.385477025953321, 37.780141390018422 ], [ -122.385479653051561, 37.780040399682747 ], [ -122.385511585982442, 37.780139463852002 ], [ -122.385591067071402, 37.78013475905297 ], [ -122.385593711788431, 37.78003445515364 ], [ -122.38562562709447, 37.78013283285312 ], [ -122.385706855349099, 37.780128787029341 ], [ -122.385709482473402, 37.780027796408206 ], [ -122.385741415359448, 37.780126860520987 ], [ -122.385824355914565, 37.780122100504215 ], [ -122.385825252791648, 37.780021137554606 ], [ -122.38585891591876, 37.780120173961421 ], [ -122.385945315592835, 37.780115358252047 ], [ -122.385947942745361, 37.780014367894388 ], [ -122.38597987560469, 37.780113432222926 ], [ -122.386066275948011, 37.78010861613928 ], [ -122.38606717251001, 37.780007653736739 ], [ -122.38610083595357, 37.78010669007471 ], [ -122.386188965678286, 37.780101846233919 ], [ -122.386191574380462, 37.780000169711762 ], [ -122.386223525670573, 37.780099919858799 ], [ -122.386306466157762, 37.780095159228338 ], [ -122.386309092126623, 37.77999416887333 ], [ -122.386341026143853, 37.780093232818778 ], [ -122.386422253963318, 37.780089186233084 ], [ -122.386424863030612, 37.77998750941989 ], [ -122.386456813943639, 37.780087259789596 ], [ -122.386541484497073, 37.780082471584841 ], [ -122.386544092702067, 37.77998079450537 ], [ -122.386576044464221, 37.780080544831833 ], [ -122.386658984914163, 37.780075784227932 ], [ -122.386661592958049, 37.779974107145748 ], [ -122.386693544875143, 37.780073857440478 ], [ -122.386776485303088, 37.780069096479352 ], [ -122.386777363799169, 37.779967447346195 ], [ -122.386809315183925, 37.780067197895306 ], [ -122.386888813892412, 37.780063178385696 ], [ -122.386889674448653, 37.77996084281277 ], [ -122.38692337384856, 37.780061251805442 ], [ -122.387006314247685, 37.780056490682639 ], [ -122.387007174648929, 37.779954155383322 ], [ -122.38704087419768, 37.780054564067953 ], [ -122.387130733547679, 37.78004969211122 ], [ -122.387133323862429, 37.779947329117419 ], [ -122.38716529349125, 37.780047765460061 ], [ -122.387255152824864, 37.780042893408485 ], [ -122.387256012876136, 37.779940557832525 ], [ -122.387289712762055, 37.78004096672084 ], [ -122.387365734157797, 37.780036316380411 ], [ -122.387366594049666, 37.779933980528931 ], [ -122.387400294082084, 37.78003438938579 ], [ -122.387486693563403, 37.780029572821469 ], [ -122.387461810378028, 37.779663260524117 ], [ -122.387378661112308, 37.779659784635669 ], [ -122.387356418813553, 37.779669754843859 ], [ -122.387276728664503, 37.779666223506673 ], [ -122.387252739526915, 37.779675535209975 ], [ -122.387173049376599, 37.779672003802716 ], [ -122.387150789943092, 37.779681287247612 ], [ -122.387067640671745, 37.779677811139862 ], [ -122.387043668592767, 37.779687808975972 ], [ -122.386969150543408, 37.779683507921064 ], [ -122.386945178103403, 37.779693505742671 ], [ -122.386868930320873, 37.779689232309579 ], [ -122.386844958564382, 37.779699230374298 ], [ -122.386765268401049, 37.779695698416859 ], [ -122.386741279182857, 37.779705009741512 ], [ -122.386659859644624, 37.77970150593859 ], [ -122.386635887496141, 37.77971150341719 ], [ -122.386556180245535, 37.77970728514304 ], [ -122.386533937822136, 37.779717255195997 ], [ -122.386452518273302, 37.779713750975354 ], [ -122.386428528664126, 37.77972306224185 ], [ -122.38635402805015, 37.779719447242712 ], [ -122.386331768168034, 37.779728731087609 ], [ -122.386245159413448, 37.779725309752138 ], [ -122.386222916935935, 37.779735279471808 ], [ -122.386151858355305, 37.779730922551266 ], [ -122.386127868702886, 37.77974023375657 ], [ -122.386048178882604, 37.779736701583559 ], [ -122.386025936036276, 37.779746671546171 ], [ -122.385946246559868, 37.779743139298724 ], [ -122.385922256538748, 37.779752450742315 ], [ -122.385842567054112, 37.779748918150233 ], [ -122.385818577011165, 37.779758229298103 ], [ -122.385742346663463, 37.779754641855682 ], [ -122.385718357291026, 37.779763952697529 ], [ -122.385635207994184, 37.779760475579685 ], [ -122.385611235349785, 37.779770473129936 ], [ -122.385528086050684, 37.779766995936541 ], [ -122.385504096654358, 37.779776307009364 ], [ -122.38542267708938, 37.779772802077623 ], [ -122.385400434501221, 37.779782772191204 ], [ -122.385319014927063, 37.779779266913316 ], [ -122.385296754890703, 37.779788550288139 ], [ -122.385225696313427, 37.779784192809487 ], [ -122.385201723952207, 37.779794190270913 ], [ -122.385120304035951, 37.779790685135936 ], [ -122.38509804431726, 37.779799968467685 ], [ -122.385020084218212, 37.779796407941809 ], [ -122.38499609406378, 37.779805719196837 ], [ -122.384916404566042, 37.779802185978852 ], [ -122.384894161901101, 37.779812155996879 ], [ -122.38480257260197, 37.779817053634027 ], [ -122.384809835241697, 37.779898657585051 ], [ -122.384782194261888, 37.779900472932383 ], [ -122.384761383067243, 37.779694102231538 ], [ -122.384789023963819, 37.779692286614427 ], [ -122.384808725038937, 37.779786739726958 ], [ -122.384879557453601, 37.779782173395887 ], [ -122.384880595342565, 37.779686702548538 ], [ -122.384914117331078, 37.779780247404453 ], [ -122.384983237080689, 37.779776395391224 ], [ -122.384984274829336, 37.77968092426827 ], [ -122.385017796952795, 37.779774469369393 ], [ -122.385085186616962, 37.779770645233363 ], [ -122.38508622457347, 37.779675173829325 ], [ -122.385119746476775, 37.779768718907071 ], [ -122.385188848787607, 37.77976418060323 ], [ -122.385189886610789, 37.779668709198148 ], [ -122.385223408641721, 37.779762254246563 ], [ -122.385283897452879, 37.779759226795328 ], [ -122.385284934821826, 37.779663755944007 ], [ -122.385318457309211, 37.779757300685375 ], [ -122.385385847281285, 37.779753476097383 ], [ -122.385386866740049, 37.779657318531406 ], [ -122.3854203897058, 37.77975086351293 ], [ -122.385491239136826, 37.779746983539091 ], [ -122.385492276232114, 37.779651512411178 ], [ -122.385525798982087, 37.779745057368359 ], [ -122.385598378143868, 37.779741149940357 ], [ -122.385599415094418, 37.779645678536831 ], [ -122.385632937976666, 37.779739223463643 ], [ -122.385705499691042, 37.779734629250548 ], [ -122.385706536510781, 37.779639158120546 ], [ -122.385740059531955, 37.779732703291586 ], [ -122.385805719718547, 37.779728905856508 ], [ -122.385806756416358, 37.779633435000086 ], [ -122.385840279547224, 37.779726979593605 ], [ -122.385909399199221, 37.7797231270374 ], [ -122.385910435756728, 37.779627655905408 ], [ -122.385943959022555, 37.779721200744085 ], [ -122.386013061231836, 37.779716661682606 ], [ -122.386014097656016, 37.779621190549562 ], [ -122.386047621049443, 37.779714735358894 ], [ -122.38611501060636, 37.779710910634236 ], [ -122.386116047238417, 37.779615439220123 ], [ -122.386149570411703, 37.779708984006099 ], [ -122.386208311974158, 37.779705297309562 ], [ -122.386211077885747, 37.779609798774842 ], [ -122.386242871788397, 37.779703371203219 ], [ -122.386317180591391, 37.779699435108718 ], [ -122.38631821662446, 37.77960396397274 ], [ -122.386351740393124, 37.779697508695882 ], [ -122.386415670790853, 37.779693738872112 ], [ -122.386416706351284, 37.779598267740703 ], [ -122.386450230587357, 37.779691812430414 ], [ -122.386519350181956, 37.779687959791211 ], [ -122.386520385602097, 37.779592488384218 ], [ -122.386553892529193, 37.77968534660009 ], [ -122.386623012105417, 37.779681493625532 ], [ -122.386624047399195, 37.779586022492111 ], [ -122.386657571897828, 37.779679567397658 ], [ -122.386728421189233, 37.779675686405767 ], [ -122.386729456700351, 37.779580215540342 ], [ -122.386762980969223, 37.779673759872402 ], [ -122.386832100523861, 37.779669906775261 ], [ -122.386833135894662, 37.779574435634252 ], [ -122.386866660298466, 37.779667980211499 ], [ -122.386932302936756, 37.779663495979172 ], [ -122.386933338178679, 37.779568024837168 ], [ -122.386966862705819, 37.779661569386036 ], [ -122.387030793048368, 37.779657799503305 ], [ -122.38704566565184, 37.779562106599087 ], [ -122.387054991513921, 37.779656725452249 ], [ -122.387136202059708, 37.779651991644378 ], [ -122.387139193519673, 37.77956541658935 ], [ -122.387170761825075, 37.779650065266061 ], [ -122.387239881329535, 37.779646211655297 ], [ -122.387241125495137, 37.779558977842669 ], [ -122.387274441082525, 37.779644284971994 ], [ -122.387341830851355, 37.779640459266005 ], [ -122.387344804633926, 37.779553198035785 ], [ -122.387376373154481, 37.7796378461085 ], [ -122.387448951768221, 37.779633937546841 ], [ -122.387428243590108, 37.779295714050406 ], [ -122.387339905566151, 37.779292321211003 ], [ -122.38731766337223, 37.779302291412563 ], [ -122.387236244241279, 37.77929878773282 ], [ -122.387212254525394, 37.779308099439604 ], [ -122.387132565463105, 37.779304567993648 ], [ -122.387108575718415, 37.779313879130164 ], [ -122.387028886654846, 37.779310347614164 ], [ -122.387004914343294, 37.779320345448582 ], [ -122.386932126735232, 37.779316016678415 ], [ -122.386908154408829, 37.779326014493151 ], [ -122.386828447902374, 37.779321796397241 ], [ -122.386804475567118, 37.779331794465456 ], [ -122.386726516216612, 37.779328234787421 ], [ -122.3867025267663, 37.779337546110341 ], [ -122.386624566736728, 37.779333986925117 ], [ -122.386600577610722, 37.779343297947158 ], [ -122.386520887847865, 37.7793397660989 ], [ -122.386496916150335, 37.779349763818921 ], [ -122.386417208949837, 37.779345545456096 ], [ -122.386393237236504, 37.77935554315502 ], [ -122.386318719558147, 37.779351241688801 ], [ -122.386296477216476, 37.779361211972173 ], [ -122.386211598617734, 37.779357762936172 ], [ -122.386187609086349, 37.779367073879669 ], [ -122.386113108495195, 37.779363458733613 ], [ -122.386089119302554, 37.779372769926113 ], [ -122.386011159604379, 37.779369210055243 ], [ -122.385987187482783, 37.779379207677103 ], [ -122.385909210351812, 37.779374961294259 ], [ -122.385885237875669, 37.779384959175481 ], [ -122.385802088988314, 37.779381481900515 ], [ -122.385778099750866, 37.779390793029705 ], [ -122.385698409983121, 37.779387260900187 ], [ -122.385676150450635, 37.779396544067176 ], [ -122.385598190747288, 37.779392983923266 ], [ -122.385574218216505, 37.779402981466639 ], [ -122.3854945117032, 37.779398762469064 ], [ -122.3854705391566, 37.779408759991362 ], [ -122.38538912034204, 37.779405255030689 ], [ -122.385366860087544, 37.779414538699463 ], [ -122.385285441271535, 37.779411033667223 ], [ -122.385261469031803, 37.779421030866885 ], [ -122.385190410808178, 37.779416673366669 ], [ -122.38516642079081, 37.779425984382435 ], [ -122.385088461426918, 37.77942242389598 ], [ -122.38506448916398, 37.779432421330149 ], [ -122.384984782302112, 37.779428201993547 ], [ -122.384960810030293, 37.779438199681202 ], [ -122.384881120582591, 37.779434666444651 ], [ -122.38485713086655, 37.779443977391921 ], [ -122.384765559446308, 37.779449561719943 ], [ -122.384771214222269, 37.779535998445006 ], [ -122.384747015071127, 37.779537072038458 ], [ -122.38473308847739, 37.779329217537743 ], [ -122.384757287215081, 37.779328143952647 ], [ -122.384773494195869, 37.779421279213679 ], [ -122.384846073080865, 37.779417372248531 ], [ -122.384848858140316, 37.779322559636441 ], [ -122.384880632782526, 37.779415445992221 ], [ -122.384949752194402, 37.77941159399834 ], [ -122.384952537135234, 37.779316781932884 ], [ -122.384984294475274, 37.779408981541515 ], [ -122.385051684148522, 37.779405157144254 ], [ -122.385056216100068, 37.779311003588973 ], [ -122.385086243853095, 37.779403231376833 ], [ -122.385155363235981, 37.779399378987847 ], [ -122.385158147906921, 37.779304566642743 ], [ -122.38518992292822, 37.779397452915447 ], [ -122.385248663600038, 37.779393766707848 ], [ -122.385253195301217, 37.779299613144822 ], [ -122.385283223287274, 37.779391840608099 ], [ -122.385354072735922, 37.779387960985474 ], [ -122.385358586870339, 37.779293120699045 ], [ -122.385388632410709, 37.779386034580241 ], [ -122.385459481495417, 37.779382154625232 ], [ -122.385462265784483, 37.779287342547207 ], [ -122.385494023751633, 37.779379542293618 ], [ -122.385561413364954, 37.77937571733046 ], [ -122.385565944668585, 37.779281563755077 ], [ -122.385595973035649, 37.779373791139029 ], [ -122.385663362299539, 37.779369966397688 ], [ -122.385667876044934, 37.779275126373676 ], [ -122.385697921964919, 37.779368040176379 ], [ -122.385765311917211, 37.779364215640285 ], [ -122.385769825525102, 37.779269375337698 ], [ -122.385799871570299, 37.779362289114509 ], [ -122.385874162638899, 37.779357667403531 ], [ -122.38587696382136, 37.779263541485342 ], [ -122.385908722286104, 37.779355740845865 ], [ -122.385976111863471, 37.779351915920223 ], [ -122.385978912570593, 37.779257790005062 ], [ -122.386010671519301, 37.779349989881837 ], [ -122.386078060733283, 37.77934616462889 ], [ -122.386080844570401, 37.779251352255542 ], [ -122.386112620376821, 37.779344238286029 ], [ -122.386174820757347, 37.779340496265526 ], [ -122.386179333847906, 37.779245656221427 ], [ -122.386209380395755, 37.779338569894314 ], [ -122.386283671761092, 37.779333947919731 ], [ -122.386288202139696, 37.779239794041324 ], [ -122.386318231386639, 37.779332021242034 ], [ -122.386380431746588, 37.779328279113052 ], [ -122.386384962016137, 37.779234125779993 ], [ -122.386414991380946, 37.77932635295614 ], [ -122.38648584000218, 37.779322472115325 ], [ -122.386488640754166, 37.779228346176467 ], [ -122.386520399624118, 37.779320545652922 ], [ -122.386589518862621, 37.779316692697755 ], [ -122.386592302045827, 37.779221880311852 ], [ -122.386624078479215, 37.779314766204962 ], [ -122.386691468335471, 37.779310941139499 ], [ -122.386694251035379, 37.779216128482069 ], [ -122.386726027939744, 37.77930901434226 ], [ -122.386793400000514, 37.779304502779596 ], [ -122.38679620001615, 37.779210376838677 ], [ -122.386827959599159, 37.779302575952464 ], [ -122.386897078804992, 37.779298722816996 ], [ -122.386901608410881, 37.779204569189005 ], [ -122.386931638412193, 37.779296796508632 ], [ -122.386992108980877, 37.779293081467337 ], [ -122.386996638465945, 37.779198927835616 ], [ -122.387026668576013, 37.779291154856551 ], [ -122.387095787760913, 37.779287301604562 ], [ -122.38710029967234, 37.779192461524332 ], [ -122.387130347350677, 37.779285374963401 ], [ -122.387199466531811, 37.779281521925199 ], [ -122.387204309717603, 37.779199724010219 ], [ -122.387234026109212, 37.779279594979052 ], [ -122.387304857552181, 37.779275027470717 ], [ -122.387309700628478, 37.779193229825893 ], [ -122.387339417137795, 37.779273101042833 ], [ -122.387415455175415, 37.779269136564935 ], [ -122.387394991136247, 37.778940523542531 ], [ -122.387315302113649, 37.778936992225411 ], [ -122.387291312863496, 37.778946303668732 ], [ -122.387218525259328, 37.778941975080627 ], [ -122.38719628281504, 37.778951945540015 ], [ -122.38712349555685, 37.77894761688772 ], [ -122.387099505926273, 37.778956928022971 ], [ -122.387025006394012, 37.778953313441747 ], [ -122.387002746472433, 37.778962597145551 ], [ -122.386929976655324, 37.778958954818265 ], [ -122.386905987003843, 37.778968266188713 ], [ -122.386836658833175, 37.778963881995679 ], [ -122.386812687306417, 37.778973880055133 ], [ -122.386736440270226, 37.778969606535817 ], [ -122.386712468022296, 37.778979604036806 ], [ -122.386637951048542, 37.77897530276654 ], [ -122.386613979138446, 37.778985300516517 ], [ -122.386549822682738, 37.778980146669305 ], [ -122.386527580483204, 37.778990116996944 ], [ -122.386449603383866, 37.778985870976705 ], [ -122.386425631783965, 37.778995868408273 ], [ -122.386356303615884, 37.778991483932835 ], [ -122.386332313881979, 37.779000795186512 ], [ -122.386256084279836, 37.778997207801154 ], [ -122.386233824940902, 37.779006491348653 ], [ -122.386164496774228, 37.779002106760423 ], [ -122.386140524798748, 37.779012104688697 ], [ -122.386067737197138, 37.779007775390234 ], [ -122.386043765192852, 37.779017772749661 ], [ -122.385962346433857, 37.779014268190103 ], [ -122.385938356643209, 37.779023579363574 ], [ -122.385865569388116, 37.779019249934791 ], [ -122.385841597013936, 37.779029247533202 ], [ -122.385768809758915, 37.779024918044691 ], [ -122.385744837376848, 37.779034915898023 ], [ -122.385675509212518, 37.779030531022293 ], [ -122.385651520065593, 37.779039841851741 ], [ -122.385578749547165, 37.77903619870164 ], [ -122.385554760393234, 37.779045509785973 ], [ -122.385481989874023, 37.779041866576151 ], [ -122.385458000713072, 37.779051177915363 ], [ -122.385385212767062, 37.779046848201126 ], [ -122.385361223578457, 37.779056158971507 ], [ -122.385286723685297, 37.779052543297539 ], [ -122.385262734143524, 37.779061854327978 ], [ -122.385195135701593, 37.77905744150933 ], [ -122.385171163224882, 37.779067438971381 ], [ -122.385094916187072, 37.779063164390635 ], [ -122.385070944393846, 37.779073162095791 ], [ -122.385001616233609, 37.779068776823863 ], [ -122.38497935636353, 37.77907805986537 ], [ -122.384906586186588, 37.779074416294911 ], [ -122.384884326309916, 37.779083699593059 ], [ -122.384796197708141, 37.779088542459625 ], [ -122.384801451464668, 37.77915919068996 ], [ -122.384780712232896, 37.779160209251955 ], [ -122.384765734942761, 37.778979153747279 ], [ -122.384786456703722, 37.778977448743071 ], [ -122.384804149136215, 37.779060946405302 ], [ -122.384871538841949, 37.779057121830682 ], [ -122.384874585188271, 37.778972606432866 ], [ -122.384906098382288, 37.779055195841352 ], [ -122.384966568862538, 37.779051482113282 ], [ -122.384969632515677, 37.778967652883104 ], [ -122.385001128397789, 37.779049556096091 ], [ -122.385058156860921, 37.779046584353601 ], [ -122.385061202980566, 37.778962068401654 ], [ -122.385092716384577, 37.779044658035005 ], [ -122.385158376003019, 37.779040861234485 ], [ -122.38516315173905, 37.77895631817001 ], [ -122.385192918111116, 37.779038248990936 ], [ -122.385249946543951, 37.779035276606521 ], [ -122.385253009876607, 37.778951447368748 ], [ -122.385284506064338, 37.779033350506261 ], [ -122.385350165317007, 37.779029553604488 ], [ -122.385353228882295, 37.778945724358508 ], [ -122.385382995459778, 37.779027655131593 ], [ -122.385445195618914, 37.779023913770359 ], [ -122.385448258723784, 37.778940084252753 ], [ -122.385479755121978, 37.779021987338304 ], [ -122.385541972691954, 37.779018932096108 ], [ -122.385545018273902, 37.778934416680386 ], [ -122.385576532197149, 37.779017005910269 ], [ -122.385638732330676, 37.779013264172356 ], [ -122.385641777795314, 37.77892874847948 ], [ -122.385673291830699, 37.779011337958153 ], [ -122.38573203252615, 37.779007651778535 ], [ -122.385735094960694, 37.77892382225879 ], [ -122.385766592014193, 37.779005725262394 ], [ -122.385828792128677, 37.779001983424223 ], [ -122.385831854467739, 37.778918154451013 ], [ -122.385863351625531, 37.779000057428874 ], [ -122.385925569154423, 37.778997001709705 ], [ -122.385930344007448, 37.778912458338894 ], [ -122.385960128639468, 37.778995075411409 ], [ -122.386030959889439, 37.778990508668088 ], [ -122.386034022338819, 37.778906679409381 ], [ -122.386065519368728, 37.778988582338918 ], [ -122.386127736893627, 37.778985527062204 ], [ -122.386132511508848, 37.778900983408484 ], [ -122.386162278927955, 37.778982913985502 ], [ -122.386221036657432, 37.7789799137378 ], [ -122.386224081811065, 37.778895398298431 ], [ -122.386255596133822, 37.778977987627471 ], [ -122.386319526256145, 37.778974217575232 ], [ -122.386322588033238, 37.778890388314295 ], [ -122.38635408572037, 37.778972291161459 ], [ -122.386412826344156, 37.778968604368089 ], [ -122.386417618076294, 37.778884747421706 ], [ -122.386447385803351, 37.778966677926974 ], [ -122.386514775006972, 37.77896285296778 ], [ -122.386517836902243, 37.778879023421496 ], [ -122.386549334453889, 37.778960926222204 ], [ -122.386602903347566, 37.778958008816609 ], [ -122.386605964804218, 37.778874179548069 ], [ -122.386637462796912, 37.778956082319787 ], [ -122.386699662825961, 37.778952340022151 ], [ -122.386702724173148, 37.778868510751018 ], [ -122.386734222270135, 37.778950413496958 ], [ -122.386799881728336, 37.778946616056871 ], [ -122.386804673015121, 37.778862758819848 ], [ -122.386834441160303, 37.778944689227728 ], [ -122.386894928894066, 37.778941660955091 ], [ -122.38689797293344, 37.778857145503274 ], [ -122.386929488335198, 37.77893973464726 ], [ -122.386989958613299, 37.77893601960681 ], [ -122.386993002191389, 37.778851503883409 ], [ -122.387024518042367, 37.778934092996543 ], [ -122.387086718033501, 37.778930350494704 ], [ -122.387091509002346, 37.778846493520362 ], [ -122.387121277457396, 37.778928423856051 ], [ -122.387183477445888, 37.778924681577777 ], [ -122.387186764992791, 37.77884977579707 ], [ -122.387218036857675, 37.778922754636191 ], [ -122.387280254273051, 37.778919698476713 ], [ -122.387283524285621, 37.778844106523373 ], [ -122.387314813687013, 37.77891777178133 ], [ -122.387382202448663, 37.778913946057301 ], [ -122.387364166474967, 37.778612763090031 ], [ -122.387286207520319, 37.778609204336291 ], [ -122.387262218365407, 37.778618515499687 ], [ -122.387185989110648, 37.778614928715285 ], [ -122.387161999948319, 37.778624240132871 ], [ -122.387092672083611, 37.778619856090089 ], [ -122.387070429718904, 37.778629826526299 ], [ -122.386994183021031, 37.778625553173391 ], [ -122.386971941319302, 37.778635523030793 ], [ -122.386900883746804, 37.778631166560793 ], [ -122.386876911637231, 37.778641164370391 ], [ -122.38679893557611, 37.778636918569845 ], [ -122.386774963796881, 37.778646916353182 ], [ -122.386707365642749, 37.778642504401184 ], [ -122.386683376072511, 37.77865181600145 ], [ -122.386614065647606, 37.778648118121843 ], [ -122.386590076741811, 37.778657429142889 ], [ -122.386510387715276, 37.778653897287107 ], [ -122.386486398801594, 37.778663208561632 ], [ -122.386417070940411, 37.778658824121642 ], [ -122.386393098756784, 37.778668821832838 ], [ -122.38631686949239, 37.77866523448656 ], [ -122.386292880557747, 37.778674545996267 ], [ -122.38622009293212, 37.778670216797138 ], [ -122.386197851113138, 37.778680186508431 ], [ -122.386125063833461, 37.778675857245105 ], [ -122.38610282131512, 37.778685827224102 ], [ -122.386028304671399, 37.778681525568288 ], [ -122.38600433277405, 37.778691523194873 ], [ -122.385931545494543, 37.77868719381209 ], [ -122.385909285516078, 37.778696477309893 ], [ -122.385834786309914, 37.778692862251056 ], [ -122.385810796946046, 37.778702173118987 ], [ -122.385741486517588, 37.778698474726497 ], [ -122.385717497147184, 37.778707785850045 ], [ -122.385642980157812, 37.778703483956278 ], [ -122.385619007862559, 37.778713481784578 ], [ -122.385549680005639, 37.778709096834618 ], [ -122.385525708373962, 37.778719094083733 ], [ -122.385451191384348, 37.778714792068747 ], [ -122.385428949109837, 37.778724761915029 ], [ -122.385354432120124, 37.778720459838901 ], [ -122.385330442694098, 37.778729770883643 ], [ -122.385257672841163, 37.778726127529652 ], [ -122.385235413119702, 37.778735411169237 ], [ -122.385147302363819, 37.778740940194126 ], [ -122.385154355852308, 37.778814307082541 ], [ -122.385130156934849, 37.778815380480168 ], [ -122.385113449499144, 37.778634352661491 ], [ -122.385137630588289, 37.778632592828004 ], [ -122.385155236200447, 37.778712657669637 ], [ -122.385222625586081, 37.778708833443275 ], [ -122.385225758626859, 37.778627749705855 ], [ -122.38525718494833, 37.778706907076334 ], [ -122.38531938483824, 37.778703165506691 ], [ -122.385322517780125, 37.778622082041188 ], [ -122.385352214498553, 37.778701267322347 ], [ -122.385416144082598, 37.778697497765265 ], [ -122.385419276918626, 37.778616414297126 ], [ -122.385450703441506, 37.77869557161619 ], [ -122.38551290331219, 37.778691829944421 ], [ -122.385517765757882, 37.778610719084014 ], [ -122.38554746266594, 37.778689903767003 ], [ -122.385606220534413, 37.778686903820002 ], [ -122.385611082870923, 37.77860579268107 ], [ -122.385640779890451, 37.778684977889775 ], [ -122.385704709445832, 37.778681208176224 ], [ -122.385707841966195, 37.778600124700183 ], [ -122.385739268789678, 37.778679281942566 ], [ -122.385798008866331, 37.778675595460832 ], [ -122.385801141637501, 37.77859451225126 ], [ -122.385832568205146, 37.778673669199819 ], [ -122.385896498100962, 37.77866989992588 ], [ -122.385899630058546, 37.778588816175542 ], [ -122.385931057427598, 37.778667973361443 ], [ -122.385991527195401, 37.778664259112972 ], [ -122.385994659747908, 37.778583175623524 ], [ -122.386026086530904, 37.778662333069811 ], [ -122.386090016393183, 37.778658563141803 ], [ -122.386093148146045, 37.778577479660726 ], [ -122.386124575716508, 37.778656636795191 ], [ -122.386185062898718, 37.778653608892128 ], [ -122.386202015472293, 37.778571617834082 ], [ -122.386209244307707, 37.778651848558759 ], [ -122.386280092656236, 37.778647968384718 ], [ -122.386284954247373, 37.778566856942867 ], [ -122.386314651962735, 37.778646041707809 ], [ -122.386380311166334, 37.778642244226567 ], [ -122.386385172654656, 37.778561133055092 ], [ -122.386414870481516, 37.778640318069463 ], [ -122.386473610832795, 37.778636630970986 ], [ -122.386476742511945, 37.778555547473879 ], [ -122.386508170135954, 37.778634704511973 ], [ -122.386577288736902, 37.778630851563484 ], [ -122.386580420309613, 37.778549768338095 ], [ -122.386611848034704, 37.778628925074045 ], [ -122.386670588381151, 37.778625238426613 ], [ -122.386673719491867, 37.778544154655052 ], [ -122.386705147666916, 37.778623311635243 ], [ -122.386762158289017, 37.778619652349228 ], [ -122.386765307091096, 37.778539255288756 ], [ -122.386796717583763, 37.778617726080171 ], [ -122.386864106438878, 37.778613900371958 ], [ -122.386867237344845, 37.778532816869664 ], [ -122.386898665721333, 37.778611973798455 ], [ -122.386959152842579, 37.77860894577281 ], [ -122.386962284329485, 37.778527861982276 ], [ -122.38699369467254, 37.778606332452348 ], [ -122.387057641885832, 37.778603248718746 ], [ -122.387060772580114, 37.778522165211164 ], [ -122.387092183716675, 37.778600635643997 ], [ -122.38715094145212, 37.778597634927166 ], [ -122.387154334023649, 37.778526848079068 ], [ -122.387183771011991, 37.778595735958248 ], [ -122.38724942978078, 37.778591937998534 ], [ -122.387254551963991, 37.778521123457843 ], [ -122.387283989043027, 37.778590011312069 ], [ -122.387351378207768, 37.778586185868939 ], [ -122.387340432224974, 37.778359741993853 ], [ -122.385336224880277, 37.778480397809368 ], [ -122.385292215885698, 37.77845088576418 ], [ -122.385332752982109, 37.778411780987071 ], [ -122.385371486072913, 37.77843794377425 ], [ -122.387505436887736, 37.778315896876499 ], [ -122.387507690304943, 37.778336462786434 ], [ -122.387691125877438, 37.77833695908997 ], [ -122.387623881872372, 37.778210305157842 ], [ -122.390410335866818, 37.777142437885963 ], [ -122.390374260520773, 37.777113759158638 ], [ -122.390348522259259, 37.777093298197975 ], [ -122.389857561580598, 37.776499598895249 ], [ -122.389810930692391, 37.776503093591124 ], [ -122.389821814335647, 37.776590819837281 ], [ -122.38767762612953, 37.776716506820485 ], [ -122.387616143310936, 37.776748393866363 ], [ -122.387584974466847, 37.776747519757691 ], [ -122.387567625478937, 37.776745737093393 ], [ -122.387552006163176, 37.776743927007061 ], [ -122.387536369402838, 37.776741430474004 ], [ -122.387505026105131, 37.776733691622944 ], [ -122.387489337013818, 37.776729135749626 ], [ -122.387475360143554, 37.776723865736592 ], [ -122.387461365830632, 37.776717909277224 ], [ -122.387447354082425, 37.776711266646082 ], [ -122.387419295684069, 37.77669660766567 ], [ -122.387406978711525, 37.776688564173618 ], [ -122.387394643958672, 37.776679834515676 ], [ -122.387373399569938, 37.776660946088832 ], [ -122.387362760285328, 37.776650815560998 ], [ -122.38735385031967, 37.776640657346583 ], [ -122.387344922566982, 37.776629812692306 ], [ -122.387335978065309, 37.776618281581548 ], [ -122.387328762881694, 37.776606722784834 ], [ -122.387323277015341, 37.77659513630249 ], [ -122.387316044392662, 37.776582891060237 ], [ -122.387312271446064, 37.776570590705511 ], [ -122.387306768480713, 37.776558317497482 ], [ -122.387304707400645, 37.776545302738185 ], [ -122.387302663771393, 37.776532974698164 ], [ -122.387298541607819, 37.776506944904682 ], [ -122.387299939508821, 37.776493875044565 ], [ -122.387299608083069, 37.776480832320061 ], [ -122.387311076202096, 37.776319268586136 ], [ -122.387103587414643, 37.776325337046984 ], [ -122.387082386717438, 37.776103864569507 ], [ -122.385027425864848, 37.776335891081246 ], [ -122.385011788936296, 37.776333394493314 ], [ -122.384999507467228, 37.776326723359297 ], [ -122.384988868215274, 37.776316592896777 ], [ -122.385025875302759, 37.776274797468226 ], [ -122.384827030762366, 37.775394163410851 ], [ -122.384880527469605, 37.775388501004997 ], [ -122.384877317104468, 37.775330181090084 ], [ -122.386995863440688, 37.775082030997162 ], [ -122.386923061819843, 37.774327799469994 ], [ -122.381813822024597, 37.774615486663201 ], [ -122.381456516831207, 37.771917559332238 ], [ -122.384671101595274, 37.773340605158566 ], [ -122.384690336061794, 37.773348538332996 ], [ -122.384706059111224, 37.773354467747083 ], [ -122.38472349432422, 37.773359682786399 ], [ -122.384740912113955, 37.773364211103086 ], [ -122.384758312493929, 37.773368053246259 ], [ -122.384775695456668, 37.773371208941398 ], [ -122.384793061001673, 37.77337367818847 ], [ -122.384812120957065, 37.773374747168276 ], [ -122.384829452003999, 37.773375843238945 ], [ -122.384848494879705, 37.773376225487283 ], [ -122.38486577332894, 37.77337526249628 ], [ -122.386759568868584, 37.773272861257844 ], [ -122.386575835242937, 37.772851405934894 ], [ -122.38657206225551, 37.772839105279289 ], [ -122.386575190127743, 37.772826007459408 ], [ -122.386581812023351, 37.772814227444925 ], [ -122.386593692364443, 37.772805109622055 ], [ -122.386717419519911, 37.772771540111776 ], [ -122.386708370613761, 37.772755890279406 ], [ -122.386704597609267, 37.772743589627844 ], [ -122.386699094688865, 37.772731316663254 ], [ -122.386691548686684, 37.772706715359256 ], [ -122.38668746217553, 37.772682058686371 ], [ -122.386685401318488, 37.772669044181889 ], [ -122.386683357538772, 37.772656715579039 ], [ -122.386682694959006, 37.772630630932561 ], [ -122.386684111009814, 37.772618246955815 ], [ -122.386683779716421, 37.772605204495207 ], [ -122.386685195434609, 37.772592821073083 ], [ -122.386688340360166, 37.772580409425913 ], [ -122.386661929793348, 37.772494305123679 ], [ -122.386659886368264, 37.772481976514491 ], [ -122.386661302081109, 37.772469592817821 ], [ -122.38666965348915, 37.772457784842032 ], [ -122.386681551212874, 37.772449354004202 ], [ -122.386714186256512, 37.772439903990914 ], [ -122.386679336788333, 37.772362175448528 ], [ -122.386644902453284, 37.772368906802633 ], [ -122.386631976023693, 37.772336837636807 ], [ -122.386620901217455, 37.772309545906367 ], [ -122.386592865526183, 37.772227587668034 ], [ -122.386585249868389, 37.772200240573625 ], [ -122.386577616780784, 37.772172207033208 ], [ -122.386570001134302, 37.772144859937534 ], [ -122.386558193761417, 37.772088737774723 ], [ -122.386541497954923, 37.771976381892166 ], [ -122.386539053911875, 37.771948265304715 ], [ -122.386438565499603, 37.771943006477983 ], [ -122.386459543589496, 37.771678968587629 ], [ -122.386414488196351, 37.771676255706971 ], [ -122.386416883827636, 37.771634327179655 ], [ -122.386370064064607, 37.77163026961771 ], [ -122.386370799901215, 37.771591114275026 ], [ -122.386346586274058, 37.771591501747267 ], [ -122.386349295737674, 37.771561929239198 ], [ -122.386373509355195, 37.77156154176641 ], [ -122.386383018231356, 37.77145906765827 ], [ -122.386277185313745, 37.771447713434846 ], [ -122.386286481744634, 37.771404987761201 ], [ -122.386248187810011, 37.771395986654248 ], [ -122.386213474980906, 37.771391735029944 ], [ -122.386175916991888, 37.77134357828146 ], [ -122.386158586399219, 37.771342482408336 ], [ -122.386142916299207, 37.771338612516935 ], [ -122.386128940538697, 37.771333342339254 ], [ -122.386116624249169, 37.771325298710295 ], [ -122.38610598590013, 37.771315168059481 ], [ -122.386097059315517, 37.771304323294956 ], [ -122.386091556948031, 37.771292050293354 ], [ -122.386142815223778, 37.771266508342599 ], [ -122.38608534348306, 37.771183647247369 ], [ -122.386039082134474, 37.771201555805888 ], [ -122.386023428808656, 37.771198372355109 ], [ -122.386011095814837, 37.771189642258307 ], [ -122.386041843996964, 37.771174042639558 ], [ -122.38589549059256, 37.770997835524099 ], [ -122.385920430782591, 37.770821635056869 ], [ -122.385847920885212, 37.770691630725352 ], [ -122.385837299399952, 37.770682186505887 ], [ -122.385823306356514, 37.770676229845748 ], [ -122.385795755972097, 37.770681477661917 ], [ -122.3857856231634, 37.770691253905198 ], [ -122.385782477468226, 37.770703665268918 ], [ -122.385798079922523, 37.771250048587049 ], [ -122.385622255652152, 37.772162083932052 ], [ -122.385554576344688, 37.772154239361129 ], [ -122.385681891861481, 37.771444190077446 ], [ -122.385515420224039, 37.771429684819722 ], [ -122.38546780756441, 37.771530707856108 ], [ -122.385335917119292, 37.771651620630855 ], [ -122.385287036807085, 37.771634547559174 ], [ -122.385642517132254, 37.770847369439849 ], [ -122.385683664105002, 37.770764304300435 ], [ -122.385720236601344, 37.770637361956886 ], [ -122.385724274638122, 37.77059197382043 ], [ -122.385559320924514, 37.770637189122013 ], [ -122.383154992189901, 37.769708715839762 ], [ -122.383375582677616, 37.76933298733065 ], [ -122.385570199525418, 37.770179656618097 ], [ -122.385492733315886, 37.769990673321502 ], [ -122.38547497668678, 37.769836444052352 ], [ -122.38501929687861, 37.768856908704215 ], [ -122.384893636964136, 37.768882266758567 ], [ -122.384787561607197, 37.76865665734794 ], [ -122.38500549801104, 37.768585873156887 ], [ -122.385007528170945, 37.768529529274218 ], [ -122.385037840293677, 37.768496768478933 ], [ -122.385006697245259, 37.768428594043755 ], [ -122.38508052413836, 37.768406124957501 ], [ -122.385042550658142, 37.768341493386622 ], [ -122.38510771303342, 37.768318476117585 ], [ -122.385167686616882, 37.76829554179551 ], [ -122.385149073018809, 37.768175662662372 ], [ -122.385147029970554, 37.768163334292169 ], [ -122.385144812710706, 37.768144141461285 ], [ -122.38470084491486, 37.768171155877575 ], [ -122.384624005099198, 37.768074869544797 ], [ -122.383881223535184, 37.768121766230543 ], [ -122.383842264982363, 37.767745376829602 ], [ -122.382456893000523, 37.767832748125578 ], [ -122.382439473722869, 37.767691561099248 ], [ -122.384901003015941, 37.7675361686969 ], [ -122.384848518174209, 37.767018283287165 ], [ -122.384847918040649, 37.767012360130963 ], [ -122.382854528282635, 37.767138299649837 ], [ -122.382800567665527, 37.766784124750316 ], [ -122.385859942984411, 37.766599245791156 ], [ -122.385894343938588, 37.766523155929399 ], [ -122.385924379700455, 37.76641142564803 ], [ -122.385933483994975, 37.766361149056699 ], [ -122.385914237836445, 37.766284543788501 ], [ -122.385992644580284, 37.766169979928648 ], [ -122.386291877422522, 37.765689977677845 ], [ -122.386372587962043, 37.765598038596792 ], [ -122.386520103480208, 37.765411635088689 ], [ -122.386541125110512, 37.765285627921941 ], [ -122.386469993174757, 37.765277839225583 ], [ -122.386436744413672, 37.76505861928149 ], [ -122.386447302231559, 37.764929345689509 ], [ -122.386483096429387, 37.76490817106005 ], [ -122.386501478211272, 37.764814482226171 ], [ -122.386491269379732, 37.764616868735338 ], [ -122.38644793023208, 37.764613442288407 ], [ -122.386451155646682, 37.764399819097633 ], [ -122.386482284438713, 37.7643993208899 ], [ -122.386481482527714, 37.764367744084808 ], [ -122.386603169586195, 37.764322532741204 ], [ -122.386601548243974, 37.76425869323451 ], [ -122.386585931922852, 37.764256883010674 ], [ -122.386571940029569, 37.764250926432666 ], [ -122.38656131971895, 37.764241482262236 ], [ -122.38655581779787, 37.764229209267128 ], [ -122.38655550400469, 37.764216853507875 ], [ -122.386720358990686, 37.764100218114386 ], [ -122.386739211018067, 37.764025063281011 ], [ -122.386733691302794, 37.764012103852934 ], [ -122.38672476584992, 37.764001259116526 ], [ -122.386714128120815, 37.763991128513133 ], [ -122.38670010140504, 37.763983799331974 ], [ -122.386686126988522, 37.763978529213567 ], [ -122.386670492591179, 37.763976032291019 ], [ -122.386649740182264, 37.763976364463595 ], [ -122.386634036749314, 37.763971122014347 ], [ -122.386621721632579, 37.763963078702353 ], [ -122.386611066835059, 37.763952261637705 ], [ -122.386603852994298, 37.763940702770881 ], [ -122.386600062668251, 37.763927715381541 ], [ -122.386601461140231, 37.763914645215138 ], [ -122.386604606072254, 37.763902234094914 ], [ -122.386738907102767, 37.763808749639459 ], [ -122.386828889531799, 37.763741383598706 ], [ -122.386840716091143, 37.763730206671987 ], [ -122.386855983933643, 37.763718287929905 ], [ -122.386869539846316, 37.763707083316298 ], [ -122.386884824769794, 37.763695851022327 ], [ -122.386900145253236, 37.763685991607943 ], [ -122.38691721218359, 37.763676790958648 ], [ -122.386934296546428, 37.763668276753165 ], [ -122.386951398349183, 37.763660449266119 ], [ -122.386970246931185, 37.763653279988162 ], [ -122.386989112953756, 37.763646797428088 ], [ -122.387007996064526, 37.763641001316863 ], [ -122.387026897301666, 37.763635891637833 ], [ -122.387047562432002, 37.763632127441824 ], [ -122.387066515625051, 37.76362907682639 ], [ -122.387271786163467, 37.763605188503739 ], [ -122.387241692331358, 37.763510215504958 ], [ -122.386905339639924, 37.763549936808673 ], [ -122.386894896912324, 37.763479371256736 ], [ -122.386775626938885, 37.763415354890846 ], [ -122.386725141086345, 37.763471101073094 ], [ -122.386629276523749, 37.763443106355105 ], [ -122.386635775530237, 37.763426520918784 ], [ -122.386503316054672, 37.763388124513462 ], [ -122.386596764100844, 37.763184731253212 ], [ -122.386314395824243, 37.763102036396099 ], [ -122.386214308919619, 37.763316522997265 ], [ -122.385510514730285, 37.763329843761532 ], [ -122.385502904472929, 37.763234510683318 ], [ -122.385278141497096, 37.763240166132988 ], [ -122.385288368966343, 37.763370480347639 ], [ -122.38518118352205, 37.763373568175126 ], [ -122.385104258477085, 37.762319301701545 ], [ -122.384682038971576, 37.762111108737756 ], [ -122.384291646501694, 37.762134518628663 ], [ -122.384461265879253, 37.764047083641032 ], [ -122.384304084745608, 37.764057150214732 ], [ -122.384132119672074, 37.762256559028991 ], [ -122.383427389000644, 37.762300785113965 ], [ -122.383450606038608, 37.762465914870035 ], [ -122.383630300057192, 37.762456862777178 ], [ -122.3836690703355, 37.762553071423532 ], [ -122.383744969614881, 37.762544304446941 ], [ -122.383904907669475, 37.764143878058199 ], [ -122.383832343655854, 37.764147784454543 ], [ -122.383810360659879, 37.764236036587789 ], [ -122.383580701961776, 37.764253441592125 ], [ -122.383538646212543, 37.764164152408867 ], [ -122.383462623420698, 37.764168114122675 ], [ -122.383459954192517, 37.764131073630139 ], [ -122.383299279201026, 37.764139821513076 ], [ -122.383109557752064, 37.762320284660944 ], [ -122.382290828789408, 37.762371818989514 ], [ -122.38251353675102, 37.764606298459128 ], [ -122.382344144468703, 37.764612437347246 ], [ -122.382121545934865, 37.762382076243206 ], [ -122.38174844490716, 37.762405201559091 ], [ -122.381695087947634, 37.764738170521944 ], [ -122.381631135065035, 37.764740565182315 ], [ -122.381630936164214, 37.764801000118787 ], [ -122.381492672008349, 37.764806640579245 ], [ -122.381450941763589, 37.76493503772344 ], [ -122.381172352882004, 37.76493330344676 ], [ -122.381123981911756, 37.764799477425129 ], [ -122.380983831900423, 37.764798967139676 ], [ -122.381029346103958, 37.762223710202747 ], [ -122.38330764917707, 37.762081573209855 ], [ -122.382185556454559, 37.760879873475361 ], [ -122.382168402620636, 37.760885641207267 ], [ -122.382152908506868, 37.760888635257665 ], [ -122.382137362223574, 37.760889570238476 ], [ -122.382121780810863, 37.760889132328003 ], [ -122.38210441862546, 37.760886662951151 ], [ -122.382090445117498, 37.760881392285128 ], [ -122.382076454213362, 37.760875434895482 ], [ -122.382064140442054, 37.760867390822959 ], [ -122.382053521188098, 37.760857946240883 ], [ -122.382044596105573, 37.76084710115525 ], [ -122.382023253267107, 37.760824093302155 ], [ -122.382014294110562, 37.760811875582888 ], [ -122.382005317206719, 37.760798970871889 ], [ -122.381998069607818, 37.76078603882209 ], [ -122.381986997879679, 37.76075874660679 ], [ -122.381983174441103, 37.760744386430567 ], [ -122.381981079958692, 37.760729998921875 ], [ -122.381980366958516, 37.760701854577775 ], [ -122.381981731049137, 37.76068741129496 ], [ -122.381984824092612, 37.760672940679711 ], [ -122.38198793521687, 37.760659156500736 ], [ -122.381992775285752, 37.760645344714604 ], [ -122.381695267558513, 37.760647347915544 ], [ -122.381838070964363, 37.762118779804872 ], [ -122.381335375976406, 37.762148093200558 ], [ -122.381148296266858, 37.760157516712624 ], [ -122.380611046370689, 37.760188065470565 ], [ -122.380912187127535, 37.763336013717719 ], [ -122.380701275367471, 37.763342126118935 ], [ -122.380400282533174, 37.760199669192616 ], [ -122.379507164112297, 37.760250310896886 ], [ -122.379808005517475, 37.763456637815253 ], [ -122.379779318273378, 37.763485250755494 ], [ -122.379565016753276, 37.763494162073435 ], [ -122.379533176547326, 37.763466514378344 ], [ -122.379201704975202, 37.759937914686475 ], [ -122.381256986630419, 37.759667520373661 ], [ -122.381256577752055, 37.759583059710486 ], [ -122.381469121143184, 37.759573486743299 ], [ -122.381446760180623, 37.75930533388199 ], [ -122.381389798546991, 37.759310363785765 ], [ -122.381303248927566, 37.759308311423226 ], [ -122.381202252005025, 37.758325846274602 ], [ -122.381200105587652, 37.758309399127988 ], [ -122.381197941449869, 37.758292265813587 ], [ -122.381197089710227, 37.758258629870305 ], [ -122.381199679398975, 37.758224251997007 ], [ -122.381200991455458, 37.758207749648136 ], [ -122.38120401536365, 37.75819053325035 ], [ -122.381207056652954, 37.758174003300283 ], [ -122.381221367866601, 37.758124330651064 ], [ -122.381234402126012, 37.75809253323947 ], [ -122.381242648836448, 37.758076607200529 ], [ -122.381262669528837, 37.75804744489573 ], [ -122.381289433303067, 37.758011307974613 ], [ -122.381302797797105, 37.757992553063836 ], [ -122.381314432708805, 37.757973825759706 ], [ -122.381327797196747, 37.757955071120548 ], [ -122.381337703552219, 37.757936371131194 ], [ -122.381360938912749, 37.75789754361324 ], [ -122.381380716140868, 37.757858771292213 ], [ -122.381390587715856, 37.757838698951588 ], [ -122.381398729699725, 37.757818653670306 ], [ -122.381408600911726, 37.757798581058942 ], [ -122.381416742893265, 37.757778536050878 ], [ -122.381423155645805, 37.757758518646433 ], [ -122.381431297626008, 37.7577384739118 ], [ -122.381444088336707, 37.757697065930351 ], [ -122.381450501074838, 37.757677048524144 ], [ -122.381464499104396, 37.757615019779642 ], [ -122.381469148185403, 37.757593657075738 ], [ -122.381475021977749, 37.757552359783979 ], [ -122.381477941486636, 37.757531024690024 ], [ -122.381480843608969, 37.757509003148037 ], [ -122.381427480867771, 37.75751946944429 ], [ -122.381405721286413, 37.757343328372727 ], [ -122.381468641764485, 37.757300433411309 ], [ -122.381446638421224, 37.75711468207534 ], [ -122.381323864533158, 37.757116641937891 ], [ -122.381323169199888, 37.757089184020202 ], [ -122.381364426846005, 37.75707891127486 ], [ -122.381321992818584, 37.756564544822638 ], [ -122.381321662531036, 37.756551502035926 ], [ -122.38132307883005, 37.756539118369027 ], [ -122.381326206602509, 37.756526020931318 ], [ -122.381331099013835, 37.756514268226432 ], [ -122.381339449129754, 37.756502460600949 ], [ -122.381347834701543, 37.756492025859821 ], [ -122.381357966510507, 37.756482250242897 ], [ -122.381369844881263, 37.756473132920476 ], [ -122.381383504601786, 37.756466047611781 ], [ -122.381397181710526, 37.756459649024094 ], [ -122.381399266420004, 37.75640536415446 ], [ -122.381383669266881, 37.756404239961626 ], [ -122.381368019603769, 37.756401055879024 ], [ -122.381354081758502, 37.756397158017677 ], [ -122.381338379955181, 37.756391914861666 ], [ -122.381324389963666, 37.756385957652711 ], [ -122.38129976407464, 37.756369869336808 ], [ -122.381289145560629, 37.756360424678547 ], [ -122.381280238512659, 37.756350265974667 ], [ -122.381265813968113, 37.756327147557755 ], [ -122.381260331236177, 37.756315560741236 ], [ -122.381258271437829, 37.756302546110341 ], [ -122.381256229700497, 37.756290217367201 ], [ -122.381255916815178, 37.756277861302344 ], [ -122.381240855709805, 37.755888041607285 ], [ -122.381237067430561, 37.75587505429101 ], [ -122.381229855216972, 37.755863495353786 ], [ -122.381219219049328, 37.755853363971241 ], [ -122.381205211793798, 37.755846720574091 ], [ -122.381189545219783, 37.75584285001387 ], [ -122.381023178934683, 37.755831084166076 ], [ -122.381005052890941, 37.755798410637965 ], [ -122.380939344081298, 37.755799459348452 ], [ -122.380938179657051, 37.755753467322073 ], [ -122.381031311951133, 37.755742366765169 ], [ -122.381010248974547, 37.755730341878809 ], [ -122.38098743910173, 37.755717658143908 ], [ -122.380924145958886, 37.755677464760758 ], [ -122.380881915817497, 37.755649296531168 ], [ -122.380862530269653, 37.755635184397235 ], [ -122.380841397836633, 37.755620413687737 ], [ -122.380821994593731, 37.755605615653565 ], [ -122.380802573973469, 37.755590130893374 ], [ -122.38078488252836, 37.755574618260482 ], [ -122.380758354080839, 37.755551693216646 ], [ -122.380751124581494, 37.755539447252119 ], [ -122.380745589509232, 37.755525801070959 ], [ -122.38074007147074, 37.755512841343261 ], [ -122.380736283298162, 37.75549985401004 ], [ -122.38073074823852, 37.755486208102681 ], [ -122.380728671510397, 37.755472506457778 ], [ -122.380724865279745, 37.755458832961516 ], [ -122.380718635462969, 37.755417728569704 ], [ -122.380718270538722, 37.755403313156876 ], [ -122.380719652161702, 37.755389556598288 ], [ -122.380719304614729, 37.755375827633614 ], [ -122.380722067512963, 37.755348314521783 ], [ -122.380728289091294, 37.755320746216135 ], [ -122.380731417602021, 37.755307648505891 ], [ -122.380736257562816, 37.755293837033165 ], [ -122.38074597221329, 37.755267586159846 ], [ -122.380759145196464, 37.755241280371166 ], [ -122.380767425749198, 37.755226726990642 ], [ -122.380777557807846, 37.755216951415953 ], [ -122.380789436047536, 37.755207834150042 ], [ -122.3808030608416, 37.755199376285184 ], [ -122.380814973485883, 37.755191631918663 ], [ -122.380828633023739, 37.75518454667273 ], [ -122.380844056826504, 37.755178806720394 ], [ -122.380857751116352, 37.755173094367549 ], [ -122.380873209326097, 37.755168727313375 ], [ -122.380888684919853, 37.755165046980004 ], [ -122.380905907041537, 37.755162024947012 ], [ -122.380921434762783, 37.75516040367944 ], [ -122.380952524623297, 37.755158534314724 ], [ -122.380976698502963, 37.755156774772679 ], [ -122.380999125498533, 37.755154356655588 ], [ -122.381021534766717, 37.755151252091551 ], [ -122.381043927344422, 37.755147461063977 ], [ -122.381066302193489, 37.755142983589487 ], [ -122.38108865965917, 37.755137819662529 ], [ -122.38111099939502, 37.755131969288627 ], [ -122.38113332243789, 37.755125432451251 ], [ -122.381153915967687, 37.755118923215505 ], [ -122.381176203900949, 37.755111013753655 ], [ -122.38119676265265, 37.755103131339425 ], [ -122.381225828164943, 37.755089619604064 ], [ -122.381246943141264, 37.755103703797808 ], [ -122.381242963358787, 37.755083165829973 ], [ -122.38126015068255, 37.755078770848776 ], [ -122.381277355393678, 37.755075062587856 ], [ -122.381292865706087, 37.755072754823154 ], [ -122.38131010484075, 37.755070419733926 ], [ -122.381327362035009, 37.75506877053013 ], [ -122.381361945276268, 37.755068218467898 ], [ -122.381396598051168, 37.755070412188203 ], [ -122.381413958860932, 37.755072881946376 ], [ -122.381448715950555, 37.755079194614936 ], [ -122.381480084590436, 37.755088307721437 ], [ -122.381497532329476, 37.755094209708098 ], [ -122.381511539483228, 37.755100853620512 ], [ -122.381527276134307, 37.755107469370813 ], [ -122.381548321633034, 37.755118807717949 ], [ -122.381562363566104, 37.755126824520609 ], [ -122.381579829044853, 37.755133412663042 ], [ -122.381595565024199, 37.755140028689837 ], [ -122.381612996090695, 37.755145244200165 ], [ -122.381630391696547, 37.755149086822691 ], [ -122.381647770609987, 37.755152242983492 ], [ -122.381665131447221, 37.755154712704652 ], [ -122.381684187368847, 37.755155781907895 ], [ -122.38170149639484, 37.755156192273823 ], [ -122.382833235564902, 37.755035795269471 ], [ -122.383668861276263, 37.754971624407645 ], [ -122.383685978819074, 37.754964483555838 ], [ -122.38372028351472, 37.754952947636127 ], [ -122.383737470668777, 37.75494855256818 ], [ -122.383756404038564, 37.75494481631263 ], [ -122.383773626345388, 37.754941794129564 ], [ -122.383792594868737, 37.754939430758334 ], [ -122.383811580457817, 37.754937754111957 ], [ -122.38384268824322, 37.754936570147059 ], [ -122.383861726388119, 37.754936952556008 ], [ -122.383880781586896, 37.754938021140639 ], [ -122.383896396575224, 37.754939831714687 ], [ -122.384073136269635, 37.754951427650568 ], [ -122.384099862744918, 37.754913917176538 ], [ -122.384076095308828, 37.754658835300546 ], [ -122.383998950777894, 37.754618177983438 ], [ -122.383899356302948, 37.754647239093948 ], [ -122.383797997440993, 37.754674954869053 ], [ -122.383696604385719, 37.754701297650087 ], [ -122.383569360630759, 37.75473148712782 ], [ -122.383548680403266, 37.754734564546567 ], [ -122.3835279827709, 37.754736955513799 ], [ -122.383505538587656, 37.754738687938882 ], [ -122.38348480614728, 37.754739706002738 ], [ -122.383441577306456, 37.754740396857535 ], [ -122.383400008003704, 37.754738313997002 ], [ -122.38337920595491, 37.754736586250608 ], [ -122.383356657006146, 37.754734199690795 ], [ -122.383335820163737, 37.754731099315492 ], [ -122.383314965907985, 37.754727311939398 ], [ -122.383295841160447, 37.754723497196949 ], [ -122.383254063070865, 37.754713177184357 ], [ -122.383234885782983, 37.75470730309371 ], [ -122.383213961945074, 37.754700770182538 ], [ -122.383194767270453, 37.754694209911989 ], [ -122.383175537439726, 37.754686276198775 ], [ -122.383158037470992, 37.754678315390812 ], [ -122.383138807994129, 37.754670381666052 ], [ -122.383103737780345, 37.754651713981168 ], [ -122.383086185277548, 37.75464169354953 ], [ -122.383068615380594, 37.754630986667287 ], [ -122.383052774639708, 37.754620252154247 ], [ -122.383036916504807, 37.754608831191064 ], [ -122.383015731729387, 37.754592001250259 ], [ -122.38299259130558, 37.754566275663684 ], [ -122.382971180378888, 37.754540521890775 ], [ -122.382949752076243, 37.754514081940449 ], [ -122.382930053282664, 37.754487614354218 ], [ -122.382908625009904, 37.754461174396191 ], [ -122.382888908501968, 37.754434020360335 ], [ -122.382870920808742, 37.754406838701115 ], [ -122.382842850708954, 37.754363390808408 ], [ -122.382816907001839, 37.754323234339928 ], [ -122.382797016608123, 37.754289216082142 ], [ -122.382791480900465, 37.754275569725095 ], [ -122.382785928504248, 37.754261237457797 ], [ -122.38278210489284, 37.754246877022375 ], [ -122.382781722177867, 37.754231775164968 ], [ -122.382784449823419, 37.754202889097733 ], [ -122.382789271581657, 37.754188390821014 ], [ -122.382795840220822, 37.754174551362205 ], [ -122.382804155740061, 37.754161370721022 ], [ -122.382814217447034, 37.754148848908173 ], [ -122.382826043083483, 37.754137672365715 ], [ -122.382839615252593, 37.75412715464509 ], [ -122.382853204821586, 37.754117323645531 ], [ -122.382868575702929, 37.754109523814002 ], [ -122.382885693124038, 37.754102383077345 ], [ -122.382904591526582, 37.754097274061955 ], [ -122.382896892098685, 37.754066494402807 ], [ -122.382890922160428, 37.754035687111681 ], [ -122.382884969278336, 37.754005566273698 ], [ -122.382876487005475, 37.753943896449059 ], [ -122.382873975008934, 37.75391303391072 ], [ -122.382869733880582, 37.753882198997694 ], [ -122.382868951022118, 37.753851308833418 ], [ -122.382866421635015, 37.753819759846344 ], [ -122.382865638778583, 37.753788869681728 ], [ -122.38286753098933, 37.753727034381221 ], [ -122.382869667430043, 37.75367480879347 ], [ -122.382867485984363, 37.753656988767865 ], [ -122.382865286797127, 37.753638482299571 ], [ -122.382861376571569, 37.753620689893637 ], [ -122.382850096841622, 37.753585160622798 ], [ -122.382842745420064, 37.753568109920103 ], [ -122.382833665214591, 37.753551086561764 ], [ -122.382822855548582, 37.753534091107603 ], [ -122.382812063283737, 37.753517782100509 ], [ -122.382801288419827, 37.753502159540567 ], [ -122.382788801838672, 37.753487251326646 ], [ -122.382774586124242, 37.75347237018628 ], [ -122.382742765715449, 37.753445409495008 ], [ -122.382726873440859, 37.753432615860532 ], [ -122.382709269084074, 37.753420535751609 ], [ -122.382691682135089, 37.753409142362763 ], [ -122.382672383117907, 37.753398463047603 ], [ -122.382653119587403, 37.753389156614546 ], [ -122.382611168393765, 37.753371971889877 ], [ -122.382590227592559, 37.7533647524183 ], [ -122.382569321590282, 37.753358906113988 ], [ -122.382546703510585, 37.753353773605845 ], [ -122.382524103512239, 37.753349327255975 ], [ -122.382501520571083, 37.75334556763007 ], [ -122.382478972425403, 37.753343181170841 ], [ -122.382456441314361, 37.753341480611873 ], [ -122.382433945688362, 37.753341153208574 ], [ -122.382402821501856, 37.753341650348268 ], [ -122.38237864858533, 37.753343409903252 ], [ -122.382356222188207, 37.753345828558139 ], [ -122.382297501266095, 37.753349513077993 ], [ -122.38226810654271, 37.753349982559506 ], [ -122.382240423203967, 37.753349737974666 ], [ -122.382210993352999, 37.753348834550927 ], [ -122.382181546112378, 37.753347244671602 ], [ -122.382152081137065, 37.753344968342162 ], [ -122.382124328241176, 37.753341977937026 ], [ -122.382094811792641, 37.753337642237426 ], [ -122.38206700673382, 37.753332592473967 ], [ -122.382039184289724, 37.753326856255676 ], [ -122.38201134446112, 37.753320433582523 ], [ -122.38200001736864, 37.75331754288009 ], [ -122.381983487255724, 37.753313324729064 ], [ -122.381957341428489, 37.753305501265011 ], [ -122.3819294494578, 37.75329701950217 ], [ -122.38187705455077, 37.753277254112369 ], [ -122.381850839550978, 37.753266685100435 ], [ -122.3818017982653, 37.753242746046119 ], [ -122.381777242861062, 37.753229403614625 ], [ -122.381754416238635, 37.753216033574361 ], [ -122.381731572936843, 37.753201977344837 ], [ -122.381708677132991, 37.753185861496782 ], [ -122.381985040410456, 37.753169774197133 ], [ -122.382560765120331, 37.753136419940226 ], [ -122.382560989570067, 37.75313671714089 ], [ -122.382951309683904, 37.753114103639163 ], [ -122.383438933023925, 37.753085850281579 ], [ -122.383914427464433, 37.753058600498669 ], [ -122.384879974685347, 37.753002645165687 ], [ -122.385845520438053, 37.752946681373402 ], [ -122.385845731512404, 37.752946669205699 ], [ -122.386811064397406, 37.752890709951139 ], [ -122.386811676199414, 37.752890674609837 ], [ -122.387755875750031, 37.752835749697695 ], [ -122.387756291326852, 37.752835725460692 ], [ -122.387756280477618, 37.752835611637821 ], [ -122.387633974640565, 37.751559603455405 ], [ -122.387585270262662, 37.750263378063039 ], [ -122.388507729357968, 37.750205975353602 ], [ -122.389478017653872, 37.750145107673561 ], [ -122.390433038183829, 37.750118341633915 ], [ -122.391418367221192, 37.750140606282606 ], [ -122.391693805117924, 37.750111668488358 ], [ -122.391705979739527, 37.750110389401705 ], [ -122.391714686151033, 37.750109474739723 ], [ -122.391929934247997, 37.750086859865874 ], [ -122.392350740047519, 37.750026024134087 ], [ -122.392354048972692, 37.750025545503377 ], [ -122.392381992909165, 37.750021506018193 ], [ -122.393314555211816, 37.749918012051069 ], [ -122.393322792145199, 37.749917097944952 ], [ -122.393340265733656, 37.749915158351328 ], [ -122.395231400136637, 37.749808114846253 ], [ -122.395307862010839, 37.749803786070743 ], [ -122.396273544250747, 37.74974560058007 ], [ -122.397285415820363, 37.749684622841229 ], [ -122.40206163398004, 37.749396678541473 ], [ -122.402063015501824, 37.749396333205695 ], [ -122.402215286387033, 37.749358244852793 ], [ -122.402746245055127, 37.749375020002113 ], [ -122.402866303074774, 37.749478453789486 ], [ -122.402866309087059, 37.749478458911533 ], [ -122.402968925612569, 37.749550036933734 ], [ -122.403054692816028, 37.74954427306843 ], [ -122.403203674195439, 37.749534260068266 ], [ -122.403260968692152, 37.749530409553117 ], [ -122.403568849778438, 37.749475672986911 ], [ -122.403592456970202, 37.749471475909672 ], [ -122.403667613571599, 37.749458113882532 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403735650894049, 37.749583872707518 ], [ -122.403514246938869, 37.750281563827826 ], [ -122.403378511001179, 37.750679445903444 ], [ -122.403214572776321, 37.751164169122518 ], [ -122.403175027775376, 37.751281093014263 ], [ -122.403072643662085, 37.751794247946641 ], [ -122.403060331379024, 37.751915861053995 ], [ -122.403006670294943, 37.752445905732571 ], [ -122.402978169178084, 37.75275045704997 ], [ -122.40301031565609, 37.753201877425177 ], [ -122.403011715723153, 37.753221541143297 ], [ -122.403127143734139, 37.75447773377249 ], [ -122.403244936870763, 37.755759623462495 ], [ -122.403245024221292, 37.755760575231484 ], [ -122.403305530792395, 37.756165877853867 ], [ -122.403395700993769, 37.756471262485057 ], [ -122.403543859417937, 37.75680412126377 ], [ -122.403689260967766, 37.757015002579273 ], [ -122.403774084903432, 37.757138026202746 ], [ -122.403774405770974, 37.757138490742172 ], [ -122.404168659910013, 37.75752977302043 ], [ -122.404441730762329, 37.757748221369937 ], [ -122.40472107938929, 37.757950649275713 ], [ -122.404835145723425, 37.7580339263735 ], [ -122.405048169961901, 37.758189448369819 ], [ -122.405545221503274, 37.758536929330724 ], [ -122.405903135660481, 37.758934413706449 ], [ -122.406103779268008, 37.759196479511928 ], [ -122.406247868764623, 37.759429354319998 ], [ -122.406261953936678, 37.75945211819198 ], [ -122.406389357641601, 37.759804405761585 ], [ -122.406492391998796, 37.760255279712112 ], [ -122.406492697683589, 37.760686469836571 ], [ -122.406479479731502, 37.760730932047331 ], [ -122.4062826701159, 37.761392945250627 ], [ -122.406147061407296, 37.7616371808829 ], [ -122.406021608630979, 37.761863123291867 ], [ -122.405925510731336, 37.762011914887523 ], [ -122.405653838302513, 37.762432552128487 ], [ -122.405384490436404, 37.76297005284507 ], [ -122.40532469627189, 37.76313750682079 ], [ -122.405258413683796, 37.763323129887908 ], [ -122.405203088479681, 37.763478066930332 ], [ -122.405104592203571, 37.763852244172895 ], [ -122.405059622300016, 37.764310557738277 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.404841911264157, 37.764643492872132 ], [ -122.404496977488066, 37.764664317561468 ], [ -122.403527034519726, 37.764722870371436 ], [ -122.402563396844656, 37.764781034314389 ], [ -122.401598999326723, 37.764839236400867 ], [ -122.401604204317508, 37.764894634517518 ], [ -122.401720515816834, 37.766132520250999 ], [ -122.40075504647362, 37.766190486570189 ], [ -122.399760960198876, 37.766250252443854 ], [ -122.39980174760619, 37.766587745781216 ], [ -122.399677637243116, 37.766695444602661 ], [ -122.4004285874814, 37.76730030349627 ], [ -122.400680751363893, 37.767503407870436 ], [ -122.400877239583821, 37.76748837440357 ], [ -122.401842503677628, 37.76743077711658 ], [ -122.401962350744, 37.768706209224241 ], [ -122.402926809989424, 37.76864812762463 ], [ -122.403037758616776, 37.769828657390953 ], [ -122.403406518421917, 37.769807099957397 ], [ -122.403615112128733, 37.769893190404822 ], [ -122.403877699904385, 37.770062866265505 ], [ -122.403531054507056, 37.770336670466769 ], [ -122.401656665936628, 37.77181711392474 ], [ -122.399647849152032, 37.773403860176856 ], [ -122.399516440682405, 37.773507653276255 ], [ -122.399433130909728, 37.773573455617395 ], [ -122.400212340411557, 37.77419510487973 ], [ -122.400592319049849, 37.774498244536282 ], [ -122.400981567656032, 37.774808775969753 ], [ -122.402524096584528, 37.776039321403807 ], [ -122.40337073826143, 37.776714700215486 ], [ -122.403505778866929, 37.776822422431877 ], [ -122.403673439982725, 37.776956165214415 ], [ -122.404070256642925, 37.777272702482797 ], [ -122.404605505853894, 37.777699659794813 ], [ -122.405068508898097, 37.778068981419388 ], [ -122.405615266425855, 37.778505104539292 ], [ -122.404431250807633, 37.779440334605447 ], [ -122.403387209275081, 37.78026497235011 ], [ -122.401159718585049, 37.782024266952142 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":2,\"ZIP_CODE\":94105,\"ID\":94105},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.39249932896719, 37.793768814133983 ], [ -122.391890260341384, 37.794278544568918 ], [ -122.391788865572423, 37.794170982455135 ], [ -122.39173429034625, 37.79420276052317 ], [ -122.391666728649753, 37.794132425256194 ], [ -122.391723034266192, 37.79410061945832 ], [ -122.391673228351905, 37.794047854124599 ], [ -122.391982015107928, 37.793871906128679 ], [ -122.391589022782, 37.793527982873798 ], [ -122.391723231555744, 37.793429001692992 ], [ -122.390805927608938, 37.792657675413892 ], [ -122.388808599369057, 37.790978116182679 ], [ -122.388930714411984, 37.790880018842998 ], [ -122.388611692601799, 37.790313089306778 ], [ -122.388048971491713, 37.790507516256234 ], [ -122.388012417064502, 37.790431188672287 ], [ -122.388369268465311, 37.790308044637399 ], [ -122.388285919009036, 37.790161047422416 ], [ -122.388320274159014, 37.790150883121463 ], [ -122.388243932142601, 37.790007207265845 ], [ -122.388219415789919, 37.789995925591761 ], [ -122.388076857804336, 37.790038725053051 ], [ -122.38779803320179, 37.790149885537438 ], [ -122.385731305877229, 37.790973806479329 ], [ -122.385715648397209, 37.790970622998962 ], [ -122.385703294724394, 37.790961206457723 ], [ -122.385509561738004, 37.790556391724344 ], [ -122.385507518014222, 37.790544063409705 ], [ -122.385514141658973, 37.790532283492631 ], [ -122.385686917933171, 37.79045260764569 ], [ -122.387606646474296, 37.789787359176458 ], [ -122.387348795424046, 37.789311467853956 ], [ -122.38734757309517, 37.78931176210844 ], [ -122.385363735345749, 37.789788908162748 ], [ -122.385346470166525, 37.789790557672781 ], [ -122.385330865935444, 37.789789433734306 ], [ -122.3853151742195, 37.789784877582385 ], [ -122.385301142770246, 37.789777548268411 ], [ -122.385290501190326, 37.789767417313975 ], [ -122.385283285054584, 37.789755858694043 ], [ -122.385108047186435, 37.789329458915937 ], [ -122.385106003217075, 37.789317130597112 ], [ -122.385109132387825, 37.789304032849302 ], [ -122.385115773404124, 37.789292939120834 ], [ -122.385127656568173, 37.78928382174719 ], [ -122.385141339390941, 37.789277422487608 ], [ -122.385148542800621, 37.789220309385662 ], [ -122.387188020139192, 37.788773590697055 ], [ -122.387049561519532, 37.788228488916552 ], [ -122.385265755459955, 37.788520039162925 ], [ -122.385250134063114, 37.78851822849537 ], [ -122.385234425193858, 37.78851298588809 ], [ -122.385222089078539, 37.788504256015905 ], [ -122.385214890495845, 37.788493383558084 ], [ -122.385126670096767, 37.788154179856562 ], [ -122.385126356426341, 37.788141823596902 ], [ -122.385129502623784, 37.788129412294914 ], [ -122.385137873116122, 37.788118290909864 ], [ -122.385149773868434, 37.788109860244717 ], [ -122.385165203482913, 37.788104119771369 ], [ -122.38712311120571, 37.787785749180493 ], [ -122.387152415559953, 37.787781160094404 ], [ -122.38716964498991, 37.787778137163613 ], [ -122.38718858690207, 37.78777440037284 ], [ -122.387205798545097, 37.787770691273494 ], [ -122.387224740799539, 37.787766954471365 ], [ -122.387241935339802, 37.787762558917869 ], [ -122.387260859452994, 37.787758135677656 ], [ -122.387278054335084, 37.787753740113331 ], [ -122.387312408509445, 37.787743576101576 ], [ -122.387329567801103, 37.787737807654125 ], [ -122.387346745228243, 37.787732725636424 ], [ -122.387363887068403, 37.787726270740649 ], [ -122.38738104704403, 37.787720502274603 ], [ -122.38740694457185, 37.787241442038656 ], [ -122.387019444956891, 37.787247644385083 ], [ -122.384558944804354, 37.787400993480382 ], [ -122.384543323667074, 37.787399182993411 ], [ -122.384527632511421, 37.787394627009306 ], [ -122.384515296650974, 37.787385896512234 ], [ -122.384504655873897, 37.787375765751548 ], [ -122.384499135159118, 37.787362806267062 ], [ -122.384342210870813, 37.785748083262355 ], [ -122.384341897348037, 37.785735727269817 ], [ -122.384346773449252, 37.785723288336335 ], [ -122.38435689108006, 37.785712825798925 ], [ -122.384370521056837, 37.785704367292539 ], [ -122.384385968349036, 37.785699313626168 ], [ -122.387217967726542, 37.785525599931667 ], [ -122.387242186237785, 37.785525212275893 ], [ -122.387269846695702, 37.785524082797316 ], [ -122.387295742393547, 37.78552160811531 ], [ -122.387323367958444, 37.785519105736952 ], [ -122.387349245861117, 37.785515944605116 ], [ -122.387375089561743, 37.785511410569285 ], [ -122.387402662781668, 37.785506848841521 ], [ -122.387454279688384, 37.785495034977437 ], [ -122.387478323373855, 37.785487782842686 ], [ -122.387504079475306, 37.785479816566138 ], [ -122.387528105357717, 37.785471877983142 ], [ -122.387553826899222, 37.785462538802953 ], [ -122.387580894103834, 37.785438070169313 ], [ -122.387630346985745, 37.78486455148672 ], [ -122.385458308223278, 37.78499338709468 ], [ -122.385413881876744, 37.784606785946664 ], [ -122.387221366105805, 37.784501641660896 ], [ -122.387418079949683, 37.784479264504995 ], [ -122.38743876829875, 37.784476186423468 ], [ -122.387483534893533, 37.784467229088612 ], [ -122.387528231689217, 37.784455525961853 ], [ -122.387550562288524, 37.78444898795393 ], [ -122.387571145938807, 37.78444179118685 ], [ -122.387593442333781, 37.784433880546885 ], [ -122.387613991073025, 37.784425310610295 ], [ -122.38765505365474, 37.784406798388098 ], [ -122.387673819852623, 37.78439619708557 ], [ -122.387677823231684, 37.784349435820396 ], [ -122.387709046749492, 37.783875783688671 ], [ -122.386286930602978, 37.783958976243902 ], [ -122.386236533710786, 37.783405597391891 ], [ -122.387525601081578, 37.78333071590594 ], [ -122.387695596747974, 37.783278550154108 ], [ -122.387751878035047, 37.783246059476667 ], [ -122.387776877347846, 37.78286796141245 ], [ -122.3875502225155, 37.782801544578085 ], [ -122.387525883466154, 37.782797127453357 ], [ -122.387484228568439, 37.78279230054234 ], [ -122.387463418560699, 37.782790573250665 ], [ -122.387440913647538, 37.782790246810194 ], [ -122.387420138539767, 37.78278989267335 ], [ -122.387388984601131, 37.782789704683182 ], [ -122.387368261829351, 37.782791409869013 ], [ -122.387347521611616, 37.782792428607252 ], [ -122.384758972173714, 37.782940280456494 ], [ -122.384743316428853, 37.782937096842417 ], [ -122.384732711445736, 37.782928338977264 ], [ -122.384679972094801, 37.782554918115103 ], [ -122.384681388329753, 37.782542534464426 ], [ -122.384691505480149, 37.782532071892298 ], [ -122.384705187096728, 37.782525672675533 ], [ -122.387509249137921, 37.78234690330077 ], [ -122.387528224534123, 37.782344539065186 ], [ -122.387554136561661, 37.78234275076899 ], [ -122.38762294594062, 37.782322839482177 ], [ -122.387787765874378, 37.782275144853472 ], [ -122.387765464889313, 37.78194244138043 ], [ -122.388106738902522, 37.781931906493995 ], [ -122.388231340039766, 37.782146697467269 ], [ -122.388304301898813, 37.782356717503951 ], [ -122.388306939892615, 37.782385221520776 ], [ -122.389843136047602, 37.783611740842574 ], [ -122.389846392025618, 37.783609176617361 ], [ -122.390856721023212, 37.784410569188324 ], [ -122.391599105853345, 37.783822668268179 ], [ -122.391600764141245, 37.783823989838545 ], [ -122.392032919786118, 37.784168354830349 ], [ -122.392118766536498, 37.784205306785267 ], [ -122.392215026316663, 37.784279352365694 ], [ -122.392216553530702, 37.784278123341721 ], [ -122.39285700396303, 37.784773072758867 ], [ -122.39286959393705, 37.78478280203197 ], [ -122.39343711789931, 37.784459123881106 ], [ -122.394398389309941, 37.783701667981575 ], [ -122.395160622349934, 37.78431101230386 ], [ -122.395895701657864, 37.784896929203114 ], [ -122.396705177550587, 37.785542130425938 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.399369533643153, 37.785899247741973 ], [ -122.400468483886854, 37.785030285141588 ], [ -122.400994549885382, 37.785441790992316 ], [ -122.400998469557948, 37.785444857061968 ], [ -122.401480059865179, 37.785821567351917 ], [ -122.401489835489045, 37.785829214187501 ], [ -122.402025419766176, 37.786248152290575 ], [ -122.402575227394536, 37.786678208153539 ], [ -122.403028193190352, 37.787024418822838 ], [ -122.403028734856406, 37.78702485040828 ], [ -122.403342641535644, 37.787274627660381 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403239953883428, 37.787802352067843 ], [ -122.40290309900054, 37.788085647297414 ], [ -122.402903089488873, 37.788085654318181 ], [ -122.402404665507532, 37.788463925757384 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.401375490979433, 37.789264321921429 ], [ -122.400067228055164, 37.790303858609981 ], [ -122.399919790020746, 37.790414442896498 ], [ -122.399480001759599, 37.790744297277485 ], [ -122.399222136170906, 37.790956214990921 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399148598683425, 37.79101664916432 ], [ -122.398264448861653, 37.791716058450703 ], [ -122.39826444614279, 37.791716060417308 ], [ -122.397840810091907, 37.792047595331439 ], [ -122.39738344591872, 37.792405521295422 ], [ -122.396504486670068, 37.793097082520163 ], [ -122.396376076969489, 37.793198086984006 ], [ -122.396302004629632, 37.793256350149633 ], [ -122.396302004290646, 37.793256350429786 ], [ -122.395983670970253, 37.793501461560965 ], [ -122.395622922555773, 37.793779228585429 ], [ -122.39533276386878, 37.794011429145591 ], [ -122.395317307728533, 37.794023797689903 ], [ -122.394971722038662, 37.794300351169447 ], [ -122.394747579547172, 37.794479718347191 ], [ -122.394745733667804, 37.794478246556643 ], [ -122.394745701490038, 37.794478274267938 ], [ -122.394150899798674, 37.794987788593353 ], [ -122.39270797067924, 37.793614415765255 ], [ -122.392702063215523, 37.793608792659974 ], [ -122.39249932896719, 37.793768814133983 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":3,\"ZIP_CODE\":94129,\"ID\":94129},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.470990721149789, 37.787534455433345 ], [ -122.472290534181823, 37.787395917162272 ], [ -122.472401902490546, 37.787384046501884 ], [ -122.47254342327507, 37.787368961705944 ], [ -122.472782224474642, 37.787331534391747 ], [ -122.473316434470121, 37.787247805418339 ], [ -122.474432824368861, 37.787072820298278 ], [ -122.474584006514732, 37.787049123020516 ], [ -122.47616220885665, 37.786978730627588 ], [ -122.478707989065171, 37.78691405835 ], [ -122.481375583614479, 37.787198893328011 ], [ -122.483910676418589, 37.787705653817092 ], [ -122.483911841345289, 37.78771598691722 ], [ -122.484019551359538, 37.788671434543716 ], [ -122.484071292407094, 37.789571562974182 ], [ -122.484214987310182, 37.789767611906584 ], [ -122.484758163249651, 37.789969442365155 ], [ -122.485095392905748, 37.790094746949087 ], [ -122.486221786002261, 37.790513270391187 ], [ -122.486196663014411, 37.790544596769486 ], [ -122.486148110204482, 37.790605847513199 ], [ -122.486108757859654, 37.790687545376898 ], [ -122.486034586054117, 37.790761588869977 ], [ -122.48595999185838, 37.790819844471812 ], [ -122.485896199163861, 37.790893713055688 ], [ -122.485740384038749, 37.791021323419344 ], [ -122.48558351329909, 37.791238913899868 ], [ -122.485411383576832, 37.791468435611826 ], [ -122.485284654257228, 37.791712987869268 ], [ -122.484994993901111, 37.791948608146861 ], [ -122.484752903676167, 37.79221570331633 ], [ -122.484541285320944, 37.792522116372695 ], [ -122.484363647892437, 37.79274005429609 ], [ -122.484183607122475, 37.793062416612024 ], [ -122.484072129345151, 37.793295035940602 ], [ -122.483858237133077, 37.793646123471206 ], [ -122.483593432919847, 37.794035150786577 ], [ -122.48329368676049, 37.794671303481294 ], [ -122.483518531231283, 37.794730014502065 ], [ -122.48308859662535, 37.79561146099627 ], [ -122.482999467368202, 37.795903449272551 ], [ -122.48285101972256, 37.796177893057802 ], [ -122.482765752291186, 37.796484924353678 ], [ -122.482667211606753, 37.796813467545597 ], [ -122.482504820847737, 37.797149264997877 ], [ -122.482349642968302, 37.797495928150028 ], [ -122.482180347467406, 37.797832527598885 ], [ -122.482169137762298, 37.797996159399602 ], [ -122.482136359830861, 37.798129936813986 ], [ -122.482085493238017, 37.798234488898757 ], [ -122.482121580114068, 37.798289507917929 ], [ -122.482129545147487, 37.798328517967903 ], [ -122.482111649834962, 37.798371396374968 ], [ -122.482102148276397, 37.798404519393337 ], [ -122.482087585279373, 37.798442534909526 ], [ -122.482079722552115, 37.798472196426395 ], [ -122.482040900643639, 37.798509246213612 ], [ -122.481958513784505, 37.798535353148957 ], [ -122.481870239865685, 37.798535463289312 ], [ -122.481831454802517, 37.798573885582073 ], [ -122.48191348856912, 37.79872908258961 ], [ -122.481933747767386, 37.798774753652147 ], [ -122.481894064444091, 37.798779540742402 ], [ -122.481782518807989, 37.79868595916038 ], [ -122.481759479485277, 37.798795537302446 ], [ -122.481735532663109, 37.798806240772073 ], [ -122.481729674725472, 37.79884616987701 ], [ -122.481697076819628, 37.79885701872184 ], [ -122.481553967429107, 37.798748172710248 ], [ -122.481544740366218, 37.798791592096414 ], [ -122.481675391067768, 37.798887599511232 ], [ -122.481685177680191, 37.798930012650303 ], [ -122.481641997341171, 37.798933485499177 ], [ -122.481619981772425, 37.798951710306021 ], [ -122.481601609693158, 37.798976741512377 ], [ -122.481583384160913, 37.799007264136819 ], [ -122.481561643359143, 37.799035785621555 ], [ -122.481552599461594, 37.799086069282282 ], [ -122.48153672724159, 37.799139901359283 ], [ -122.481507471077649, 37.799211126714575 ], [ -122.481470873600131, 37.799266680501994 ], [ -122.481417267273812, 37.799333507835762 ], [ -122.481380082795965, 37.799367095921824 ], [ -122.481394839952742, 37.799401184738606 ], [ -122.48138749011072, 37.799450066445814 ], [ -122.481313103352264, 37.799516556146521 ], [ -122.481199920275785, 37.799556228466017 ], [ -122.481106226731853, 37.799612741364335 ], [ -122.481068868952889, 37.799704704484391 ], [ -122.481063386681015, 37.799823601791012 ], [ -122.481042139949025, 37.799870656721012 ], [ -122.48098682060791, 37.799938199625082 ], [ -122.480980184705871, 37.800013851703859 ], [ -122.480977156432658, 37.800224730569639 ], [ -122.480974804661557, 37.800331213761126 ], [ -122.480860274222692, 37.800385329797535 ], [ -122.480757965088657, 37.800443360304584 ], [ -122.48067857879505, 37.800582040732152 ], [ -122.48065611449222, 37.800648344612128 ], [ -122.480592381330325, 37.800724956178229 ], [ -122.480537665874181, 37.800815150409605 ], [ -122.480468568377589, 37.800950224199951 ], [ -122.480375485908439, 37.801094628286094 ], [ -122.480295978813317, 37.801293743123836 ], [ -122.480202328037436, 37.801416867810858 ], [ -122.480181840275407, 37.801557305763211 ], [ -122.480071801739967, 37.801844835145566 ], [ -122.480027318791514, 37.802059156846411 ], [ -122.480044134726015, 37.802235365083838 ], [ -122.480004943470632, 37.80238847850972 ], [ -122.479965705600534, 37.802604771748101 ], [ -122.479928024203545, 37.802749618690633 ], [ -122.479899085376999, 37.802897752511299 ], [ -122.479813341330441, 37.803057827736559 ], [ -122.479734279947053, 37.803208863153962 ], [ -122.479678141843124, 37.803310755542739 ], [ -122.479612025097651, 37.803427923840943 ], [ -122.479496444899141, 37.803572704736546 ], [ -122.479357004588351, 37.803861413494445 ], [ -122.479173928312875, 37.80433146553451 ], [ -122.479146809127258, 37.804483001950985 ], [ -122.479060667102701, 37.804693214811543 ], [ -122.478953175659996, 37.804881810346664 ], [ -122.478815220557294, 37.805291358623961 ], [ -122.47872883882674, 37.805492647931004 ], [ -122.478723432948883, 37.805679530333919 ], [ -122.47869664200617, 37.805843422292654 ], [ -122.478685946257201, 37.805961720018743 ], [ -122.478628322791295, 37.806008011253461 ], [ -122.478654742843744, 37.806089976088074 ], [ -122.478631304779441, 37.806119899160173 ], [ -122.478660343597923, 37.806170230012903 ], [ -122.478638955177018, 37.806277032642242 ], [ -122.478627125019798, 37.806352771870088 ], [ -122.478625044107972, 37.806469551574978 ], [ -122.47857586751384, 37.806637939992576 ], [ -122.478562516078711, 37.80678649853526 ], [ -122.478544650108333, 37.806960541692234 ], [ -122.478526703332861, 37.807066600081164 ], [ -122.478526389161814, 37.807184723319764 ], [ -122.478521297177736, 37.807253482414033 ], [ -122.478488208711909, 37.807311036148292 ], [ -122.478483958170003, 37.807411370608691 ], [ -122.478496304822514, 37.807549883734964 ], [ -122.478476156109295, 37.807833156107016 ], [ -122.478405186887699, 37.808093245296376 ], [ -122.478343748883177, 37.808126216988313 ], [ -122.478060427750734, 37.808278265895638 ], [ -122.477897062640963, 37.80844925522419 ], [ -122.477885395973601, 37.808531172189731 ], [ -122.477923697644769, 37.808669250052077 ], [ -122.477956528906134, 37.809251736581921 ], [ -122.477922196862778, 37.80978247039468 ], [ -122.477956931601639, 37.810111519960003 ], [ -122.477924513902849, 37.810324264541741 ], [ -122.477975357212003, 37.810543166576302 ], [ -122.477691959274622, 37.810627579828761 ], [ -122.477738440983671, 37.81094269802653 ], [ -122.477669662799912, 37.810960332923507 ], [ -122.477465352217308, 37.810997758254302 ], [ -122.4773147668257, 37.811025342092201 ], [ -122.476951827952391, 37.810983355163785 ], [ -122.476621797907725, 37.810876948869407 ], [ -122.47656163509636, 37.810893065263848 ], [ -122.476466638674822, 37.81083628492037 ], [ -122.47639876264958, 37.810757761311969 ], [ -122.476244453139699, 37.810423848047549 ], [ -122.476192907539087, 37.810243414175943 ], [ -122.47615497008627, 37.809858792499412 ], [ -122.47604874198008, 37.809640130705603 ], [ -122.475967371125094, 37.809574880888988 ], [ -122.475790153494444, 37.809485827869665 ], [ -122.475539966877108, 37.809387008587656 ], [ -122.474949730564461, 37.809260233883315 ], [ -122.474802168498073, 37.809179610065499 ], [ -122.474477490769161, 37.809143842083898 ], [ -122.474059299537672, 37.809106891534391 ], [ -122.473248913495638, 37.809099164155811 ], [ -122.472667246163553, 37.80903404071298 ], [ -122.472248345244111, 37.80897031306057 ], [ -122.471807567969009, 37.808865058985567 ], [ -122.471692590128313, 37.808861156356251 ], [ -122.471577117792876, 37.808857237159089 ], [ -122.471241422813662, 37.80879760884919 ], [ -122.470932885927255, 37.808717610499883 ], [ -122.47066006806898, 37.808613665617798 ], [ -122.470600816238615, 37.80859886080146 ], [ -122.470032562336371, 37.809430372877515 ], [ -122.469989867152222, 37.809452374737276 ], [ -122.469923620349192, 37.809434939483481 ], [ -122.469403337873828, 37.809203958897747 ], [ -122.469386781047135, 37.809167151824653 ], [ -122.469446470968407, 37.809067952374448 ], [ -122.469513627885206, 37.809054469737255 ], [ -122.470046113471852, 37.809288679655658 ], [ -122.470536336868321, 37.808582769832967 ], [ -122.47042036446561, 37.808452854807648 ], [ -122.470227327454069, 37.808354443608728 ], [ -122.470098624189163, 37.80826663136105 ], [ -122.470013817898405, 37.808202121652926 ], [ -122.469815463486142, 37.808098991213633 ], [ -122.469586546186093, 37.807953106986339 ], [ -122.469305300471973, 37.807727061403774 ], [ -122.469233592741006, 37.807568936815628 ], [ -122.469112342826094, 37.807370435141976 ], [ -122.46886886007195, 37.807066844008062 ], [ -122.468789265438318, 37.807002933173059 ], [ -122.468641769161479, 37.806924361175163 ], [ -122.468442727148087, 37.806860383444018 ], [ -122.468303117397753, 37.806818075943092 ], [ -122.46814816670377, 37.806980670547468 ], [ -122.46804250856195, 37.806913074050627 ], [ -122.468204198687829, 37.806743499513175 ], [ -122.468154584727131, 37.80670037662253 ], [ -122.467927641872478, 37.806563383834956 ], [ -122.4677672527434, 37.806455496134681 ], [ -122.467355591928154, 37.806141659736177 ], [ -122.467203291926239, 37.806012347645776 ], [ -122.466996296046148, 37.805909356729394 ], [ -122.466671493417238, 37.805802836197401 ], [ -122.466463320033583, 37.805982110961864 ], [ -122.466652133036774, 37.805790797867139 ], [ -122.466274152924925, 37.805653990321176 ], [ -122.466075349709101, 37.80558203392561 ], [ -122.465895016150796, 37.805570618742252 ], [ -122.465648753037854, 37.805488195293123 ], [ -122.46546102999136, 37.805393808021854 ], [ -122.464551731906468, 37.805113664168701 ], [ -122.464230324554308, 37.805004333527904 ], [ -122.463987997870177, 37.804939695859083 ], [ -122.463579617827847, 37.804879883064366 ], [ -122.463271699005631, 37.804887756693624 ], [ -122.462952230341088, 37.804917109899002 ], [ -122.4624302743036, 37.805013698161765 ], [ -122.462214023782167, 37.805018669869817 ], [ -122.462027122615794, 37.804955166469341 ], [ -122.461896643188638, 37.804930554615567 ], [ -122.461601325019004, 37.804956755736356 ], [ -122.461272453201914, 37.805023344559039 ], [ -122.461007547032963, 37.805086808651389 ], [ -122.459772513833471, 37.805253612450713 ], [ -122.45957795006224, 37.805293241948746 ], [ -122.459540191073017, 37.805305543773983 ], [ -122.45950068434351, 37.805317187894794 ], [ -122.459462907558404, 37.805328803259258 ], [ -122.459423363879012, 37.805339074505127 ], [ -122.45938555118417, 37.805349316978521 ], [ -122.459306427886148, 37.805368486538775 ], [ -122.459227232780066, 37.805384910313265 ], [ -122.459185886621981, 37.805392464497949 ], [ -122.459106618316682, 37.805406142481793 ], [ -122.459065217794489, 37.805411637333286 ], [ -122.459025547917307, 37.805417103422087 ], [ -122.458984129270405, 37.80542191181479 ], [ -122.458944422811243, 37.805426005021417 ], [ -122.458902967930442, 37.80542944052501 ], [ -122.458861494934339, 37.805432189583698 ], [ -122.458788912310027, 37.805436828799479 ], [ -122.458735345621889, 37.80544046541047 ], [ -122.458683526998115, 37.80544475969581 ], [ -122.458631726477662, 37.805449740388731 ], [ -122.458578213760234, 37.805455436499734 ], [ -122.458526449095928, 37.805461790012899 ], [ -122.458474703215558, 37.805468829647623 ], [ -122.458371283182728, 37.805485655405761 ], [ -122.458319609004732, 37.805495440705506 ], [ -122.458269665819927, 37.805505197514321 ], [ -122.45821802818655, 37.805516356173719 ], [ -122.458168120492331, 37.805527485811837 ], [ -122.458116519376176, 37.805540017007239 ], [ -122.458066648197928, 37.805552519182882 ], [ -122.457989327072951, 37.805574405126777 ], [ -122.45793619470966, 37.805594515430641 ], [ -122.457883043876322, 37.805613939560359 ], [ -122.457829874920449, 37.805632677510189 ], [ -122.457774957510779, 37.805650757180459 ], [ -122.457721751946011, 37.805668121951648 ], [ -122.457666780196845, 37.805684142829186 ], [ -122.457610078443324, 37.805700191847833 ], [ -122.457500062415335, 37.80572948720183 ], [ -122.457443287865871, 37.805742790973085 ], [ -122.457386495190917, 37.805755408011883 ], [ -122.457329684053221, 37.805767338598564 ], [ -122.457272855492562, 37.805778582715845 ], [ -122.457186701695477, 37.805793746855791 ], [ -122.457077909022615, 37.805803793319789 ], [ -122.456970882463025, 37.805815183288864 ], [ -122.456862180533747, 37.805828661426311 ], [ -122.456755226634087, 37.80584279746536 ], [ -122.456648326952561, 37.805858992150092 ], [ -122.456541463411881, 37.805876559873688 ], [ -122.456434635998889, 37.805895500361686 ], [ -122.45632959276756, 37.805916471347693 ], [ -122.456224568256204, 37.805938128659257 ], [ -122.456119597596228, 37.80596184517497 ], [ -122.456014644961741, 37.805986248302439 ], [ -122.45591147681688, 37.806012681382633 ], [ -122.455806614105612, 37.806040516209428 ], [ -122.455705248815832, 37.806069666405428 ], [ -122.455564429228914, 37.806112518618505 ], [ -122.45530695988451, 37.806195761893825 ], [ -122.454600415883462, 37.806430130022392 ], [ -122.454483234784888, 37.806468999744823 ], [ -122.454353658700001, 37.806478701096999 ], [ -122.454308145719423, 37.806459540393945 ], [ -122.454293829263207, 37.806375996256122 ], [ -122.454085665791084, 37.806359530611644 ], [ -122.454069929877903, 37.806353610767474 ], [ -122.453815604080873, 37.806293271483817 ], [ -122.453295386032664, 37.806259311412973 ], [ -122.452781551105744, 37.806270567825713 ], [ -122.452438711570082, 37.806267316310901 ], [ -122.451653324871984, 37.806353109244483 ], [ -122.450906745259303, 37.80653233722969 ], [ -122.450459653590798, 37.806646175036533 ], [ -122.44991286747026, 37.806721142700994 ], [ -122.449819483247254, 37.806724746830461 ], [ -122.449720673642986, 37.806719513093618 ], [ -122.449631038513971, 37.806734042766898 ], [ -122.449554038474531, 37.806768278664137 ], [ -122.44947869660497, 37.806799740182214 ], [ -122.449399659671798, 37.806822335243758 ], [ -122.449304851007483, 37.806837636909691 ], [ -122.449171524916196, 37.806836407131136 ], [ -122.449109070409847, 37.806831258752986 ], [ -122.449048183710957, 37.806819903592029 ], [ -122.449010891791161, 37.806784123494651 ], [ -122.448891660899136, 37.806726348099637 ], [ -122.448806459828916, 37.806711961234221 ], [ -122.448728288255808, 37.806701578532227 ], [ -122.448565239272398, 37.806755090545202 ], [ -122.448529100304583, 37.806763241657009 ], [ -122.448455091339923, 37.806133486140737 ], [ -122.448455258275729, 37.806133277363898 ], [ -122.448323332142422, 37.805007750605469 ], [ -122.448321161555853, 37.804993824977359 ], [ -122.448279145759457, 37.80472422506687 ], [ -122.44824552349904, 37.804508484024247 ], [ -122.448240728857812, 37.804477717258706 ], [ -122.448240723025904, 37.804477679722268 ], [ -122.448245764546613, 37.8044754914729 ], [ -122.448316879890498, 37.804444627382082 ], [ -122.448814547024654, 37.804228638340902 ], [ -122.448954929708933, 37.80412855834809 ], [ -122.449124213448584, 37.804007874392056 ], [ -122.449253815658196, 37.803861977230383 ], [ -122.449388062152011, 37.803710849923448 ], [ -122.449495662264184, 37.803470319431604 ], [ -122.449583722697156, 37.803153095621241 ], [ -122.449583713807527, 37.803152915295549 ], [ -122.449565462756809, 37.802791419060213 ], [ -122.449536106109235, 37.802697377072583 ], [ -122.449460220896157, 37.80245428555321 ], [ -122.449178591741017, 37.802073857413127 ], [ -122.449043488821943, 37.801965383441527 ], [ -122.448802640091088, 37.801772003872607 ], [ -122.448359651469772, 37.801586782043685 ], [ -122.447921599691895, 37.801532283988202 ], [ -122.447857007904148, 37.801596177616553 ], [ -122.447819731578562, 37.801633051558142 ], [ -122.447819572978844, 37.801633208279924 ], [ -122.447802865090509, 37.801525678628579 ], [ -122.447614227653389, 37.80032479990944 ], [ -122.447613279369918, 37.80032370169468 ], [ -122.447466030932901, 37.799389886853575 ], [ -122.447400275900733, 37.798948048802856 ], [ -122.447341437979304, 37.798552685345861 ], [ -122.447327769221047, 37.798519415660031 ], [ -122.447303043858923, 37.798459232650295 ], [ -122.447209582420982, 37.797849561838987 ], [ -122.44715829626513, 37.797515005478481 ], [ -122.44715829625072, 37.79751500492933 ], [ -122.447014738232738, 37.796578510197754 ], [ -122.447014738218328, 37.796578509648619 ], [ -122.446928896449293, 37.796018515191513 ], [ -122.44687098056005, 37.795640691997477 ], [ -122.446751148304088, 37.794858932799087 ], [ -122.446727777275527, 37.794706465308671 ], [ -122.446727777261131, 37.794706464759514 ], [ -122.446587862952981, 37.793765120337646 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446446033268856, 37.792810866507708 ], [ -122.446299573716246, 37.791878694429421 ], [ -122.446299547924625, 37.791878529215651 ], [ -122.44720637942703, 37.791763091830468 ], [ -122.447679208068394, 37.79170289872647 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.449385188501296, 37.791484927305568 ], [ -122.450995459060991, 37.791209906278311 ], [ -122.452035629818212, 37.790998907609811 ], [ -122.454248357567437, 37.79059310209788 ], [ -122.455878319348855, 37.790294143976332 ], [ -122.457498733292852, 37.789996912625632 ], [ -122.459011873160264, 37.789719335764374 ], [ -122.459028627866218, 37.789715983297555 ], [ -122.459250455476877, 37.789713291862398 ], [ -122.45947565027717, 37.789710559160014 ], [ -122.459781586189493, 37.789596250165474 ], [ -122.459925547070256, 37.789542753604124 ], [ -122.468653534340191, 37.787783523544832 ], [ -122.470990721149789, 37.787534455433345 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":4,\"ZIP_CODE\":94121,\"ID\":94121},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.504456509027335, 37.788067554312491 ], [ -122.504150949920984, 37.788096097763379 ], [ -122.504066815467581, 37.788120877294119 ], [ -122.504049145771674, 37.78810744254686 ], [ -122.503997231067004, 37.788107638193928 ], [ -122.503846231021797, 37.788091662877633 ], [ -122.503713944876083, 37.788063694207068 ], [ -122.503612889329972, 37.788038628655784 ], [ -122.503526395190363, 37.78804009845387 ], [ -122.503485137277139, 37.78805041389267 ], [ -122.503463545305024, 37.78801987747579 ], [ -122.503373498826377, 37.788017973822534 ], [ -122.503335534005146, 37.788022052592311 ], [ -122.503263415949803, 37.788043193399247 ], [ -122.503234378285072, 37.788057421563131 ], [ -122.503243398050046, 37.788071003120102 ], [ -122.503209152201507, 37.78808463302547 ], [ -122.503179577522019, 37.788078954832578 ], [ -122.503144887269229, 37.788076110778647 ], [ -122.503077958752755, 37.78809716302807 ], [ -122.503049013609541, 37.788114823274398 ], [ -122.503006581160705, 37.78814576070333 ], [ -122.502956321560973, 37.788143180813591 ], [ -122.502925535751373, 37.788156752131144 ], [ -122.502902751007483, 37.788146151342978 ], [ -122.502865053026014, 37.788095972678335 ], [ -122.502836115946863, 37.78804976589835 ], [ -122.502789020665148, 37.78803614434397 ], [ -122.502704311593945, 37.788039643712104 ], [ -122.502589076168022, 37.788066323379901 ], [ -122.502515190951499, 37.788086120259166 ], [ -122.502425496104195, 37.788097257971025 ], [ -122.502376106845503, 37.788126939838207 ], [ -122.502316846531585, 37.7881114644726 ], [ -122.502255070395776, 37.788131055545016 ], [ -122.502169528699341, 37.788103665363288 ], [ -122.502105689513968, 37.788110929794158 ], [ -122.50207398762231, 37.788154733004774 ], [ -122.502071249254328, 37.788181562090124 ], [ -122.501989944924048, 37.788182942633284 ], [ -122.501951813392949, 37.78818084312524 ], [ -122.501912664088508, 37.788140990459205 ], [ -122.501912016401619, 37.788116965280736 ], [ -122.501877326179624, 37.788114120853955 ], [ -122.501877973840479, 37.788138145483494 ], [ -122.501858399507697, 37.788182429188559 ], [ -122.501842192404723, 37.788223222019695 ], [ -122.501801304245816, 37.788247265660054 ], [ -122.501766151357245, 37.788227260005755 ], [ -122.501756965310037, 37.788207500503631 ], [ -122.50172600293584, 37.788150340007959 ], [ -122.501700517223497, 37.788167941185236 ], [ -122.50167957356264, 37.788161429621908 ], [ -122.501619212555269, 37.788169321478662 ], [ -122.501569767420492, 37.788196943730178 ], [ -122.501591358665465, 37.788227480492822 ], [ -122.501533078675976, 37.788248385310276 ], [ -122.501514596042369, 37.788204747707923 ], [ -122.50145942465268, 37.788212551659868 ], [ -122.501437657642668, 37.788239704012419 ], [ -122.501335150234794, 37.788224962263008 ], [ -122.501268313574613, 37.788249445888859 ], [ -122.50115878458611, 37.788295256277053 ], [ -122.501091780980232, 37.788313561975109 ], [ -122.501021937979715, 37.788290711431223 ], [ -122.501006184350814, 37.78828411139213 ], [ -122.50097195648992, 37.788298427073251 ], [ -122.500993621505359, 37.788331709648567 ], [ -122.500947376526028, 37.788349663239131 ], [ -122.500879097304491, 37.788320605139283 ], [ -122.500861336013799, 37.788303738053742 ], [ -122.500826830696198, 37.788307757577726 ], [ -122.500806349431429, 37.788318405959657 ], [ -122.500754452719278, 37.788319286610971 ], [ -122.500738328529678, 37.78829895802815 ], [ -122.500712935348687, 37.788319991115557 ], [ -122.500640723790397, 37.788337698187455 ], [ -122.500593831459696, 37.788331626454848 ], [ -122.500550140713713, 37.78831588602435 ], [ -122.500538567416584, 37.788335997845735 ], [ -122.50048939950284, 37.78837391630227 ], [ -122.500458095017024, 37.788368266503305 ], [ -122.500446429574566, 37.788384946179491 ], [ -122.50034045177307, 37.788434129242091 ], [ -122.500272801380206, 37.788428409551436 ], [ -122.500256870701349, 37.788479498553457 ], [ -122.500250772830086, 37.788574372083048 ], [ -122.500192399744307, 37.788591844106463 ], [ -122.500174472488098, 37.788568799078057 ], [ -122.500178293975864, 37.788517915512088 ], [ -122.500130319126384, 37.788535897847389 ], [ -122.500091521884229, 37.788509086399898 ], [ -122.500099172636652, 37.788471872673249 ], [ -122.500066212558522, 37.788468998362944 ], [ -122.50006658231419, 37.788482726609828 ], [ -122.500011040812936, 37.788476801362307 ], [ -122.499966794796208, 37.788440467945314 ], [ -122.499878115891249, 37.788489357153694 ], [ -122.499810631863213, 37.788489815307436 ], [ -122.499785570748472, 37.78852320387022 ], [ -122.499760324750355, 37.788549727891812 ], [ -122.499797151337432, 37.788567645255235 ], [ -122.499803450517931, 37.788608742793357 ], [ -122.49976162645244, 37.788662331112889 ], [ -122.499733393517658, 37.788642207768127 ], [ -122.499727335024716, 37.788610033760072 ], [ -122.49968724080243, 37.78866359271359 ], [ -122.499634371443463, 37.788692645687142 ], [ -122.499577562358439, 37.788703910220832 ], [ -122.499554630056736, 37.788687817377856 ], [ -122.499511356576448, 37.788623310977023 ], [ -122.499481164550005, 37.788530426400044 ], [ -122.499432590648397, 37.788461889419303 ], [ -122.499336411156918, 37.788425062960037 ], [ -122.499266771879846, 37.788409762065221 ], [ -122.499202877060384, 37.788414965914313 ], [ -122.499128103233858, 37.788401812217707 ], [ -122.499099500985864, 37.788367960195586 ], [ -122.499041638784547, 37.788340098176221 ], [ -122.498955421371278, 37.788351861015244 ], [ -122.498920128036829, 37.788390916780045 ], [ -122.498889600061332, 37.788414096736403 ], [ -122.498846352726645, 37.788414829911119 ], [ -122.498829249780883, 37.788358120749585 ], [ -122.498858133988094, 37.78827384858316 ], [ -122.49881028141921, 37.788232082242921 ], [ -122.498779365121052, 37.788240846950437 ], [ -122.498721638945739, 37.788282343161484 ], [ -122.498684745303493, 37.788326233159403 ], [ -122.498610415395817, 37.78832955335983 ], [ -122.498598336042235, 37.788266578082698 ], [ -122.498619439354101, 37.78821471490064 ], [ -122.498602392043921, 37.788160064708592 ], [ -122.498537684539031, 37.788135065438439 ], [ -122.498508608985759, 37.788147919585676 ], [ -122.498480069151427, 37.788180680090115 ], [ -122.498393075984339, 37.788163612611328 ], [ -122.498359007071954, 37.788119551978269 ], [ -122.498367465612489, 37.788047987710556 ], [ -122.498258002596998, 37.788031987711385 ], [ -122.498205083784299, 37.787930560386634 ], [ -122.498141496631646, 37.787882879514228 ], [ -122.498020127555392, 37.787874635204304 ], [ -122.497965657850202, 37.7879085217278 ], [ -122.497937733854386, 37.787835513791961 ], [ -122.497862794972676, 37.787816181447418 ], [ -122.497833152644503, 37.787872309616162 ], [ -122.497858996475657, 37.787932304773527 ], [ -122.497838976582202, 37.787960113577462 ], [ -122.497779058543074, 37.787984477979037 ], [ -122.497727525397508, 37.787934532426668 ], [ -122.4976806523715, 37.787929145955836 ], [ -122.497620549305267, 37.78794664601778 ], [ -122.497594065916346, 37.787927179270163 ], [ -122.49754290189, 37.78789096244769 ], [ -122.49750382929264, 37.787853853467084 ], [ -122.497544349339876, 37.787816083058736 ], [ -122.497526053110533, 37.787779309105126 ], [ -122.497500930399411, 37.787746084497506 ], [ -122.497517139436695, 37.787705292265386 ], [ -122.497507659501551, 37.787674549599402 ], [ -122.497485331532715, 37.787616555022737 ], [ -122.497458570647495, 37.787586791856477 ], [ -122.497406323956653, 37.787574629197884 ], [ -122.49735786900257, 37.787574763042187 ], [ -122.497315176016556, 37.787596088453697 ], [ -122.497275259657627, 37.787591957457558 ], [ -122.497216443565691, 37.787592953772126 ], [ -122.497168648080745, 37.787553245760044 ], [ -122.497138187607533, 37.787514617578381 ], [ -122.497106127216909, 37.787480823939255 ], [ -122.497044990964866, 37.787459883810421 ], [ -122.496999885071403, 37.787455840347057 ], [ -122.496923235404381, 37.787437223130333 ], [ -122.496894579157143, 37.787401311287425 ], [ -122.49685734707576, 37.787368291637833 ], [ -122.496811927818939, 37.787352579117055 ], [ -122.496751769988833, 37.787368019451996 ], [ -122.496728617227618, 37.787343688934776 ], [ -122.496685721170834, 37.787357463425209 ], [ -122.496639544906756, 37.787313607347343 ], [ -122.496583690806744, 37.787296011229486 ], [ -122.496547105206147, 37.787287016686854 ], [ -122.496516354783793, 37.787301958649365 ], [ -122.49646797383916, 37.787304838384266 ], [ -122.496443903661216, 37.787310739875238 ], [ -122.496383616604291, 37.787321374760424 ], [ -122.496337075877918, 37.787328343443079 ], [ -122.496276530804948, 37.787329368567015 ], [ -122.496247565545247, 37.787346340725328 ], [ -122.496197934785357, 37.787367096454552 ], [ -122.496141439524379, 37.787390028597343 ], [ -122.496086747911505, 37.787415677408667 ], [ -122.496068420456311, 37.787442083500181 ], [ -122.496016690354622, 37.787449139908034 ], [ -122.495972174940263, 37.787467062259353 ], [ -122.495951842506244, 37.787478615416269 ], [ -122.495939786703687, 37.787485465489738 ], [ -122.495905650481646, 37.787503211836551 ], [ -122.495879794980965, 37.787507083489736 ], [ -122.495820979283494, 37.78750807910243 ], [ -122.49579201419597, 37.787525051142431 ], [ -122.495771716907484, 37.787542562920031 ], [ -122.495716453167731, 37.787546932051697 ], [ -122.495690597288615, 37.78755080339463 ], [ -122.495618108578682, 37.787558210990248 ], [ -122.495602871400223, 37.787570830192877 ], [ -122.495572047584162, 37.787583026463722 ], [ -122.495539604212951, 37.787599370571847 ], [ -122.495514135330879, 37.787617657121146 ], [ -122.495483182354334, 37.787625048105859 ], [ -122.495437195053938, 37.787652609233902 ], [ -122.495395954684781, 37.787663608544698 ], [ -122.495370190617706, 37.787670911683094 ], [ -122.495333314088882, 37.787715487058371 ], [ -122.495282044474621, 37.787739703822119 ], [ -122.495193913200154, 37.787744628911668 ], [ -122.495147390885393, 37.787752283545302 ], [ -122.495107603658113, 37.787752956804084 ], [ -122.495081894872186, 37.787762319709344 ], [ -122.495049506978233, 37.787780722682484 ], [ -122.494942531016747, 37.787792833838672 ], [ -122.494936422880045, 37.787823153702263 ], [ -122.49487624549289, 37.787837906657984 ], [ -122.494850021018962, 37.787828049271276 ], [ -122.494803406419749, 37.787832271634258 ], [ -122.494754880927445, 37.787894212652994 ], [ -122.494747761584406, 37.787951332156055 ], [ -122.494723178126165, 37.788002566741419 ], [ -122.494687204176572, 37.788080776838861 ], [ -122.494700862218465, 37.788138231843071 ], [ -122.494641953469724, 37.788135794738686 ], [ -122.494554921507401, 37.788052798168373 ], [ -122.494515868416002, 37.788016374906285 ], [ -122.4945350783522, 37.787958364153376 ], [ -122.494513047598829, 37.787911351574522 ], [ -122.494517883781597, 37.787833668269109 ], [ -122.494492854234224, 37.787803875161934 ], [ -122.494463538439888, 37.787807804761236 ], [ -122.494432676774579, 37.787818627891369 ], [ -122.494408993418205, 37.787838943934148 ], [ -122.494413448366132, 37.787875952476604 ], [ -122.494396887310245, 37.787903702167263 ], [ -122.49434981175834, 37.787890763675087 ], [ -122.494353194757167, 37.787823406042577 ], [ -122.494335895597104, 37.78782369865479 ], [ -122.49430957902598, 37.787810409012593 ], [ -122.494266331651687, 37.787811140513561 ], [ -122.49416034840992, 37.787795764612234 ], [ -122.494129594818148, 37.787746152815927 ], [ -122.494106737724252, 37.787732804619111 ], [ -122.494080878906914, 37.78767212220432 ], [ -122.494005791021607, 37.787582742860096 ], [ -122.49397989817011, 37.78758524069908 ], [ -122.493937148914341, 37.787604505606133 ], [ -122.49390314117592, 37.787627056362858 ], [ -122.493853140270886, 37.787634082573049 ], [ -122.493776804515477, 37.787627132573334 ], [ -122.493743568607016, 37.787613959813378 ], [ -122.493738061048077, 37.787613306047696 ], [ -122.493682801772195, 37.787606746485388 ], [ -122.493586205100101, 37.787618680905503 ], [ -122.493496509486093, 37.787629812101237 ], [ -122.493461856653141, 37.787628337503875 ], [ -122.493419162517711, 37.787649661504638 ], [ -122.493384362282285, 37.787642696015901 ], [ -122.493336275791464, 37.787656556752133 ], [ -122.493293636533849, 37.787679939994845 ], [ -122.493241832649318, 37.78768424947036 ], [ -122.493193617141287, 37.7876933054322 ], [ -122.493182042295089, 37.787713416527986 ], [ -122.493127220447846, 37.787734258699906 ], [ -122.493090653466112, 37.787725949238315 ], [ -122.493056424336842, 37.787740262623288 ], [ -122.493004582859115, 37.787743199151691 ], [ -122.492969929990394, 37.787741724684366 ], [ -122.492955061461288, 37.787768072078173 ], [ -122.492913931184304, 37.787783188809769 ], [ -122.492855170492433, 37.787786242224989 ], [ -122.492860417128782, 37.787852767193691 ], [ -122.492830071355925, 37.787882810136729 ], [ -122.492771255320051, 37.787883804228279 ], [ -122.492769800087729, 37.787829576197716 ], [ -122.492735920840914, 37.787856931882516 ], [ -122.49268687433684, 37.78777054473629 ], [ -122.492679439061774, 37.787751441716487 ], [ -122.492627892968244, 37.787765361179233 ], [ -122.492565672509713, 37.787768472646512 ], [ -122.492506929887313, 37.787772212321414 ], [ -122.492446513322577, 37.787778040470201 ], [ -122.492381054094224, 37.787789447708391 ], [ -122.492305602297094, 37.78781544526975 ], [ -122.492216274554934, 37.787840303764064 ], [ -122.492178548608294, 37.787853302497936 ], [ -122.492118555140365, 37.787874918314273 ], [ -122.492079412316457, 37.787899615509929 ], [ -122.492057605141056, 37.787925393277668 ], [ -122.491876207129678, 37.788001938945712 ], [ -122.491812827679937, 37.788026359033061 ], [ -122.491772083659185, 37.788055890066879 ], [ -122.491719745560601, 37.788104846164785 ], [ -122.491654323091851, 37.788117625573307 ], [ -122.491646041850075, 37.7881315002275 ], [ -122.491614039214184, 37.788164317512276 ], [ -122.491501375628616, 37.788222533071625 ], [ -122.491447583651606, 37.788217260993214 ], [ -122.491404851437281, 37.788237211413325 ], [ -122.491281422016286, 37.788281187084038 ], [ -122.491218428658357, 37.788320021552913 ], [ -122.491190014384841, 37.788357585284182 ], [ -122.491137087049111, 37.788384575161345 ], [ -122.491061983912687, 37.788423614320799 ], [ -122.490946338544006, 37.788499734606098 ], [ -122.490859530409139, 37.788554079246722 ], [ -122.490843207112334, 37.788590751992317 ], [ -122.490818381167585, 37.788633062206074 ], [ -122.490696422877051, 37.78873195173508 ], [ -122.490605105739689, 37.78881178134435 ], [ -122.490527498874997, 37.788886572527467 ], [ -122.490413287423564, 37.788951680519325 ], [ -122.490407103746506, 37.78897925443183 ], [ -122.490354139232181, 37.78900487109366 ], [ -122.490306437661019, 37.789033145843504 ], [ -122.490251889996458, 37.789064283086574 ], [ -122.490252239550742, 37.789077325209703 ], [ -122.490220015123754, 37.789101904980171 ], [ -122.490046709329732, 37.789222262378196 ], [ -122.489918657120555, 37.789352156846768 ], [ -122.489828681483118, 37.789482095324693 ], [ -122.489700630133797, 37.789482882790033 ], [ -122.489592566089883, 37.789454489856745 ], [ -122.489300670464104, 37.789412030065776 ], [ -122.489191447548507, 37.789340391834585 ], [ -122.488990889258972, 37.789283342302205 ], [ -122.48848285911852, 37.789442306771541 ], [ -122.488100013149761, 37.789428847345718 ], [ -122.488028309941384, 37.789530320047362 ], [ -122.487863707315668, 37.789588034282978 ], [ -122.487736999824023, 37.789574375465854 ], [ -122.487584402186499, 37.78949866010057 ], [ -122.487564674856429, 37.789472896340307 ], [ -122.487563664349921, 37.78943514280558 ], [ -122.487464302267156, 37.789473215069947 ], [ -122.48744759425297, 37.789430918694578 ], [ -122.487369692668253, 37.789430171560369 ], [ -122.487296628705792, 37.789480848156245 ], [ -122.487120114954962, 37.789610869819036 ], [ -122.487051777894393, 37.789708851205745 ], [ -122.4869376141597, 37.789840568479555 ], [ -122.486809390152729, 37.789964281705743 ], [ -122.486730121786451, 37.790041845235905 ], [ -122.486553036429143, 37.790150586495734 ], [ -122.486429009493079, 37.790237144752382 ], [ -122.486335272657072, 37.790356155991162 ], [ -122.486245109599949, 37.790414674064778 ], [ -122.486236878145661, 37.790495160989501 ], [ -122.486221786002261, 37.790513270391187 ], [ -122.485095392905748, 37.790094746949087 ], [ -122.484316748868366, 37.789805424389257 ], [ -122.484214987310182, 37.789767611906584 ], [ -122.484071292407094, 37.789571562974182 ], [ -122.484019551359538, 37.788671434543716 ], [ -122.483911841345289, 37.78771598691722 ], [ -122.483910676418589, 37.787705653817092 ], [ -122.481375583614479, 37.787198893328011 ], [ -122.479861181106074, 37.787037199203986 ], [ -122.478707989065171, 37.78691405835 ], [ -122.477707716230128, 37.786939475526701 ], [ -122.476637454822679, 37.786966661899442 ], [ -122.476572244768761, 37.786128658440397 ], [ -122.476575166016133, 37.786128513873635 ], [ -122.476575148769683, 37.786128282045887 ], [ -122.476436803741322, 37.784271125057622 ], [ -122.476306551695615, 37.782405954593735 ], [ -122.476169188584777, 37.78047585000003 ], [ -122.476025278884237, 37.778553841183147 ], [ -122.475888270951586, 37.776692974324831 ], [ -122.475750968701433, 37.774828017958995 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475613625589148, 37.772962410314676 ], [ -122.476694205661246, 37.772912858094969 ], [ -122.477069824745385, 37.772895630732648 ], [ -122.477766947236773, 37.772863655180586 ], [ -122.478836951668541, 37.772814568639937 ], [ -122.47883762078969, 37.772814536530767 ], [ -122.478848715172788, 37.772814028898644 ], [ -122.479904580478745, 37.772765580938412 ], [ -122.480978851643016, 37.772716079930653 ], [ -122.481947659007389, 37.77267180789157 ], [ -122.482048961146063, 37.772667157434498 ], [ -122.483119121668807, 37.772618024364625 ], [ -122.483119139145145, 37.772618264154566 ], [ -122.484194219251819, 37.772569315707138 ], [ -122.484194201052048, 37.772569061919981 ], [ -122.485152053625171, 37.772525429693054 ], [ -122.485261332297384, 37.772520422109807 ], [ -122.485262022888591, 37.772520390701253 ], [ -122.486334053115812, 37.772471462244795 ], [ -122.486678527930636, 37.772455879683093 ], [ -122.487398288926627, 37.772423078182946 ], [ -122.487398306387334, 37.772423316873983 ], [ -122.487399123482518, 37.77242327974907 ], [ -122.488470602075608, 37.772374307055074 ], [ -122.488471323394165, 37.772374169626119 ], [ -122.489539486385709, 37.772325343661755 ], [ -122.49061689309373, 37.772276279654356 ], [ -122.490617013718605, 37.772276274046327 ], [ -122.491688031137627, 37.772227293012065 ], [ -122.492761363210761, 37.772178246338406 ], [ -122.493316307377469, 37.772153213706439 ], [ -122.493832479293559, 37.77212966191987 ], [ -122.493832498174271, 37.772129926957135 ], [ -122.493833180817205, 37.772129896182108 ], [ -122.494902921073546, 37.772080894286702 ], [ -122.494903299427449, 37.772080795311069 ], [ -122.495971990075716, 37.772031913255347 ], [ -122.497046812008847, 37.771982808835908 ], [ -122.497412740335307, 37.771966238858134 ], [ -122.498113882732468, 37.771934487449556 ], [ -122.499183042122894, 37.771885392463027 ], [ -122.500200291234236, 37.771838923903807 ], [ -122.500257545202601, 37.771836308607973 ], [ -122.501327700223527, 37.771787274197017 ], [ -122.502399595949868, 37.771738155153713 ], [ -122.502399732131124, 37.771738148994636 ], [ -122.503472688521128, 37.771689380410749 ], [ -122.503472690942445, 37.771689380369601 ], [ -122.504544396347384, 37.771640577944709 ], [ -122.505611140464779, 37.771591516134151 ], [ -122.50668530503512, 37.771542352938617 ], [ -122.506685577396198, 37.771542340610623 ], [ -122.506991972993774, 37.771528822999358 ], [ -122.507750664399296, 37.771495034126787 ], [ -122.507750665782908, 37.771495034103232 ], [ -122.508780633594597, 37.771447480819759 ], [ -122.508823169175031, 37.771445503888728 ], [ -122.508823330922013, 37.771445496187766 ], [ -122.508867990729883, 37.771443447055226 ], [ -122.509894749698645, 37.771396032095495 ], [ -122.509894781266311, 37.771396329054475 ], [ -122.511053819156686, 37.771347546731022 ], [ -122.511055400217217, 37.771347479926334 ], [ -122.512571364940968, 37.771283657974607 ], [ -122.51313192303401, 37.771260053409627 ], [ -122.51314077587368, 37.771331323581165 ], [ -122.513103639776702, 37.771429475690731 ], [ -122.513116974838326, 37.771538439951165 ], [ -122.5131111559662, 37.771706791559524 ], [ -122.513101727800077, 37.771741976505247 ], [ -122.513108516285712, 37.771800920539754 ], [ -122.513097415135022, 37.771902061732895 ], [ -122.513158379126494, 37.77223615155534 ], [ -122.513150048202618, 37.772247968474822 ], [ -122.513145175599931, 37.772259726057939 ], [ -122.513142070416649, 37.772272827219133 ], [ -122.513140694405962, 37.772285899124284 ], [ -122.513142777404809, 37.77229891140734 ], [ -122.51314831943732, 37.772311864891819 ], [ -122.513155553774638, 37.772323415987366 ], [ -122.513162806726825, 37.772335653782996 ], [ -122.513171807785298, 37.772348547918142 ], [ -122.513179470052052, 37.772375886809904 ], [ -122.513181590285754, 37.772390272493183 ], [ -122.513180232870297, 37.772404030275354 ], [ -122.513177164205956, 37.772418504300951 ], [ -122.513164035088622, 37.772444825057953 ], [ -122.513153956021, 37.772455985088065 ], [ -122.513145624725894, 37.77246780201267 ], [ -122.513139041542303, 37.772480275551658 ], [ -122.513134206485844, 37.772493406254469 ], [ -122.513132830469672, 37.772506478159016 ], [ -122.513133202560653, 37.772520206404018 ], [ -122.513137015097556, 37.772533189699409 ], [ -122.513142557139105, 37.772546142909071 ], [ -122.513149791498321, 37.772557694004483 ], [ -122.513128650263695, 37.772862969521874 ], [ -122.513143974877181, 37.772917647305441 ], [ -122.513134546565453, 37.772952831971672 ], [ -122.513140759176039, 37.773054363934854 ], [ -122.513127629925961, 37.773080684410282 ], [ -122.513121083909837, 37.773094531347873 ], [ -122.513117996938973, 37.773108318663766 ], [ -122.513113180104355, 37.773122136070647 ], [ -122.513110502783931, 37.773151024750284 ], [ -122.513109145348821, 37.773164782804365 ], [ -122.513109926760848, 37.773193612966814 ], [ -122.513112046989562, 37.773207998100119 ], [ -122.513115877809639, 37.773221667551859 ], [ -122.513119727927929, 37.77323602341751 ], [ -122.513123558750877, 37.773249692868944 ], [ -122.513130848990954, 37.773263303241755 ], [ -122.513136372841529, 37.773275570567371 ], [ -122.513136837962776, 37.773292730939929 ], [ -122.513139032281771, 37.773309862056486 ], [ -122.513139962541018, 37.773344183350439 ], [ -122.513137434071865, 37.77337856371112 ], [ -122.513137899208886, 37.773395724632501 ], [ -122.51313663480029, 37.773412914815644 ], [ -122.513133622247153, 37.773429448109219 ], [ -122.513132357829548, 37.773446638017631 ], [ -122.513129363533338, 37.773463857742755 ], [ -122.513124956674076, 37.773492776231812 ], [ -122.513123636099678, 37.773507907142864 ], [ -122.513125756700802, 37.773522292818349 ], [ -122.513127895547086, 37.773537364376381 ], [ -122.513130108829742, 37.773555182186904 ], [ -122.51313457258631, 37.77359218983559 ], [ -122.513137102897502, 37.773685543750098 ], [ -122.51313464849045, 37.773722669817865 ], [ -122.513133402680765, 37.77374054642582 ], [ -122.513130445590789, 37.773759139276294 ], [ -122.513129218723165, 37.773777702029449 ], [ -122.513126261969134, 37.773796294599215 ], [ -122.513121575665878, 37.773814916705028 ], [ -122.513118599957409, 37.773832822854594 ], [ -122.513113913995852, 37.773851444954062 ], [ -122.513110938637425, 37.773869351371971 ], [ -122.513106251972374, 37.773887973208282 ], [ -122.513098199929146, 37.77391008650784 ], [ -122.513115558504083, 37.774550522030069 ], [ -122.513135161617555, 37.77469921031755 ], [ -122.513164585532962, 37.774890995995399 ], [ -122.513170483591551, 37.775108592933201 ], [ -122.513148799533084, 37.77520236034195 ], [ -122.513166877896921, 37.775294761967629 ], [ -122.513138108336719, 37.775382469681844 ], [ -122.513154511511672, 37.775413093012311 ], [ -122.513118656446565, 37.775494741304421 ], [ -122.513116560550898, 37.775991978980805 ], [ -122.513220716743945, 37.77664260637399 ], [ -122.513262136135666, 37.776766199546913 ], [ -122.513273147342474, 37.776853227885702 ], [ -122.513301250635152, 37.776996277263876 ], [ -122.513336088063554, 37.777196211308791 ], [ -122.513350113009579, 37.777330573444466 ], [ -122.513418927986152, 37.77744339753194 ], [ -122.513485622738756, 37.777541836186963 ], [ -122.513511493535475, 37.777602514461641 ], [ -122.513605787519069, 37.777633867571893 ], [ -122.513789576977686, 37.777711077145469 ], [ -122.513970390041706, 37.777742325491253 ], [ -122.514130469394843, 37.777902348781886 ], [ -122.514271487815051, 37.77812519117407 ], [ -122.514413862528215, 37.778270408245596 ], [ -122.514604595643718, 37.77847591891544 ], [ -122.51462964838835, 37.778506394234036 ], [ -122.514661065627067, 37.778643892998048 ], [ -122.514636184118331, 37.778747329407956 ], [ -122.514564452996041, 37.778846072735902 ], [ -122.514578405995934, 37.778977688986053 ], [ -122.51449838155861, 37.779153489214309 ], [ -122.5144938833634, 37.779242842682805 ], [ -122.514567855611702, 37.77941807161681 ], [ -122.514670725915678, 37.779446530659044 ], [ -122.514854558211539, 37.779525111397355 ], [ -122.514948079566921, 37.779719234525949 ], [ -122.514897941861832, 37.779848512679017 ], [ -122.514749765647835, 37.779872333834206 ], [ -122.514641072918508, 37.779820625636908 ], [ -122.514565266573783, 37.77989677577483 ], [ -122.514533951466817, 37.780082044762075 ], [ -122.514511340895595, 37.780269225221183 ], [ -122.514474054726847, 37.780425753002312 ], [ -122.51446673230646, 37.780602371059075 ], [ -122.514648527113849, 37.780860913612862 ], [ -122.51472341591213, 37.781133644263413 ], [ -122.514586047991969, 37.781300809991649 ], [ -122.514468426928062, 37.781302819849991 ], [ -122.514292998946104, 37.781279034300063 ], [ -122.514303621052633, 37.781351647553429 ], [ -122.514284968188448, 37.781429568185636 ], [ -122.514242655950966, 37.781464628517519 ], [ -122.513867881152649, 37.781428452669296 ], [ -122.513760174171992, 37.78147699096975 ], [ -122.513883322187127, 37.781614982927096 ], [ -122.513812238767187, 37.78173775061245 ], [ -122.513697000270085, 37.781891489394212 ], [ -122.513670014693673, 37.78198122658408 ], [ -122.513522169917209, 37.782081269425682 ], [ -122.513466336763443, 37.782192101792845 ], [ -122.513546762127802, 37.782286185176545 ], [ -122.513589561336119, 37.782396706497991 ], [ -122.513504004159799, 37.782432504856651 ], [ -122.51345966482603, 37.782520478406092 ], [ -122.513490615272289, 37.782576949453883 ], [ -122.51349907897729, 37.782633804536587 ], [ -122.513499154475824, 37.782700417272132 ], [ -122.513444212427984, 37.782780331251374 ], [ -122.513311970586869, 37.782817613200059 ], [ -122.513176844047379, 37.782748499450129 ], [ -122.513099674895386, 37.782774539876293 ], [ -122.513082211268937, 37.782896391754747 ], [ -122.512985625061674, 37.783036075867919 ], [ -122.513019551763222, 37.783138507955194 ], [ -122.513026396706579, 37.783199511429288 ], [ -122.512878567398928, 37.783236372275823 ], [ -122.512854035372925, 37.783352850568228 ], [ -122.512805434943459, 37.783475233699562 ], [ -122.512705462306386, 37.783553855572684 ], [ -122.512518627902452, 37.783620225547125 ], [ -122.512466567525493, 37.783742667306889 ], [ -122.512644903191074, 37.783873537806549 ], [ -122.512713964283492, 37.783931418677646 ], [ -122.512652381073622, 37.784021746473734 ], [ -122.512681006779928, 37.784120148680003 ], [ -122.512565948104239, 37.784153016218092 ], [ -122.512295468592697, 37.783942683042248 ], [ -122.512111442218099, 37.7837933668308 ], [ -122.511916645881882, 37.783949148187006 ], [ -122.511936379260646, 37.784102641671865 ], [ -122.512002036912364, 37.7841626409828 ], [ -122.51201098319315, 37.784237343198853 ], [ -122.511976089219957, 37.784290817818842 ], [ -122.511911287292179, 37.784326260764388 ], [ -122.511796674312308, 37.784311734732256 ], [ -122.511643280924901, 37.784334954352396 ], [ -122.511486279442096, 37.784288874386938 ], [ -122.511300968574147, 37.78428379521467 ], [ -122.511334223422679, 37.784425383212891 ], [ -122.511306545033449, 37.784553589399493 ], [ -122.511229614678896, 37.784588552121491 ], [ -122.51102434727656, 37.784485608568794 ], [ -122.510971002039966, 37.784496819600761 ], [ -122.510838401528204, 37.784584923981406 ], [ -122.510747296660981, 37.784671633775218 ], [ -122.510491862112929, 37.784697278857706 ], [ -122.510370776300974, 37.784699343616424 ], [ -122.510236724687886, 37.784733906185757 ], [ -122.510168389422006, 37.784702794445394 ], [ -122.510020323714343, 37.784723420804994 ], [ -122.509994254968106, 37.784727052267002 ], [ -122.509808028924709, 37.784816069670107 ], [ -122.509716922917022, 37.784902778658349 ], [ -122.509665899527562, 37.784999792217178 ], [ -122.509574233553352, 37.78512977535155 ], [ -122.509483405510977, 37.78522678080374 ], [ -122.50935080157798, 37.785314883231258 ], [ -122.509270075574662, 37.785337547981477 ], [ -122.509233168789549, 37.785444621876991 ], [ -122.509117710773396, 37.785590805131108 ], [ -122.508796514633275, 37.785744614237508 ], [ -122.508661532747013, 37.785744853745925 ], [ -122.508565980602214, 37.785795240243836 ], [ -122.508486275275231, 37.785855657828165 ], [ -122.50845217664569, 37.785938647661681 ], [ -122.508258558155333, 37.786074486851589 ], [ -122.50824179847983, 37.786094687819457 ], [ -122.508218230136194, 37.78611912520401 ], [ -122.508132741023516, 37.786157665329803 ], [ -122.508102962554631, 37.786144437693665 ], [ -122.508035220497703, 37.78613529033418 ], [ -122.507963402235234, 37.786167416830274 ], [ -122.507918608664951, 37.786238914034136 ], [ -122.507884921314982, 37.786273138055307 ], [ -122.507810998339579, 37.786355432403496 ], [ -122.507791149628716, 37.786389420730835 ], [ -122.507786702460081, 37.786416966109108 ], [ -122.507753742643061, 37.786414093677742 ], [ -122.507723143657557, 37.786434530234523 ], [ -122.507686905785704, 37.786502447942446 ], [ -122.50771513615085, 37.786586436657288 ], [ -122.507634122264022, 37.786726537606768 ], [ -122.507529970492527, 37.786842996798164 ], [ -122.507416802174745, 37.786882007667536 ], [ -122.507337150418977, 37.786944484013162 ], [ -122.507251469686537, 37.787040025961154 ], [ -122.50719904571541, 37.787149423517022 ], [ -122.507107186903028, 37.787272540712486 ], [ -122.507053654233644, 37.787276885429115 ], [ -122.506971891727119, 37.787453396158277 ], [ -122.506896872053431, 37.78749519083452 ], [ -122.50673936089315, 37.787494438084011 ], [ -122.506717248362293, 37.787444682355584 ], [ -122.506653688773341, 37.787462245895419 ], [ -122.506526049480186, 37.787478152978885 ], [ -122.506492546406875, 37.787519241120947 ], [ -122.506427084531083, 37.787594522862221 ], [ -122.506343785965981, 37.787650192878154 ], [ -122.506284872420196, 37.787711628562818 ], [ -122.506271997783998, 37.787747558158287 ], [ -122.506258863502353, 37.787773877799495 ], [ -122.50624035413243, 37.787793421492793 ], [ -122.506219873800518, 37.787804071365088 ], [ -122.506177628287418, 37.787841873914068 ], [ -122.506131292484596, 37.787856397132735 ], [ -122.506163876746712, 37.787909408764172 ], [ -122.506122372558949, 37.787974668305665 ], [ -122.506121008614969, 37.788052293211784 ], [ -122.506089600492331, 37.788170947004069 ], [ -122.506052098981115, 37.788192187283286 ], [ -122.505852340805589, 37.788229236102978 ], [ -122.505747263058424, 37.788247505451196 ], [ -122.505657587040744, 37.788259332046266 ], [ -122.505605226952582, 37.788243054476361 ], [ -122.505520554840288, 37.788247928168715 ], [ -122.505423328958457, 37.788236533825454 ], [ -122.505330267621474, 37.78825116471306 ], [ -122.505202440987361, 37.788260206114757 ], [ -122.505107748523827, 37.788214431400775 ], [ -122.505021260444238, 37.788152035322256 ], [ -122.50498134335804, 37.788147906935059 ], [ -122.504941407743146, 37.788143092108974 ], [ -122.504896245414301, 37.788136992665024 ], [ -122.504877161086682, 37.788135256957062 ], [ -122.504859843370596, 37.788134864962309 ], [ -122.504835625333357, 37.788135276762382 ], [ -122.504801138826153, 37.7881399833256 ], [ -122.504785514269201, 37.788138188774688 ], [ -122.504771508408268, 37.788132246255898 ], [ -122.504759158308161, 37.788123528620481 ], [ -122.5047502309164, 37.788113379306509 ], [ -122.504739555111996, 37.788102572979582 ], [ -122.504727204674595, 37.788093855346673 ], [ -122.504711432227211, 37.788086569376937 ], [ -122.504695715029484, 37.788081342687157 ], [ -122.504680053426, 37.788078175271465 ], [ -122.50466269900177, 37.788076410116517 ], [ -122.50464541869988, 37.788077390660611 ], [ -122.504626408521617, 37.788078400888217 ], [ -122.504609090805303, 37.788078008307494 ], [ -122.504591755258659, 37.788076929561612 ], [ -122.504576093667978, 37.788073762406697 ], [ -122.504558683651112, 37.788069937685613 ], [ -122.504543003526479, 37.788066083826294 ], [ -122.504525593174549, 37.788062259380773 ], [ -122.504508220574564, 37.788059807771766 ], [ -122.50449088434911, 37.788058729297397 ], [ -122.504468396217547, 37.788059111612547 ], [ -122.504456509027335, 37.788067554312491 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":5,\"ZIP_CODE\":94118,\"ID\":94118},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.448885763577039, 37.778388598994987 ], [ -122.449767079183658, 37.778277008331997 ], [ -122.450654812483819, 37.778164598609756 ], [ -122.450826415883952, 37.778142868140662 ], [ -122.451543139139602, 37.778052106408275 ], [ -122.452431111623397, 37.777939652339285 ], [ -122.453384996187339, 37.777826863341367 ], [ -122.45318832797058, 37.776853425180988 ], [ -122.452963825070867, 37.775750129254348 ], [ -122.452810237317621, 37.774995318605924 ], [ -122.453003330637316, 37.774970754669006 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.454683295611602, 37.774755406979118 ], [ -122.455033749574639, 37.77471323536281 ], [ -122.45525254468275, 37.774686906456331 ], [ -122.455898178089939, 37.774604132119862 ], [ -122.456283639851293, 37.774554711848936 ], [ -122.456283671603558, 37.774554866249353 ], [ -122.456283917173579, 37.774554834704738 ], [ -122.457134751103212, 37.774454490912483 ], [ -122.458371099123696, 37.774308895694055 ], [ -122.458795325509186, 37.774254885425997 ], [ -122.459486118442314, 37.77416693399686 ], [ -122.459486145343419, 37.77416729779403 ], [ -122.459486938645284, 37.774167197254911 ], [ -122.460552700773292, 37.774031358657659 ], [ -122.460552661960989, 37.774031133779268 ], [ -122.461619755154771, 37.7738952534419 ], [ -122.461619775372085, 37.773895534117642 ], [ -122.461620188227315, 37.77389548164836 ], [ -122.462535340858068, 37.773778657528119 ], [ -122.462683796526321, 37.773759751788596 ], [ -122.463749210177951, 37.773624065149079 ], [ -122.464811670328331, 37.773488744378348 ], [ -122.465331676538511, 37.773422509669793 ], [ -122.465883163277454, 37.773398204721545 ], [ -122.466373346760548, 37.773376599548058 ], [ -122.466951728424362, 37.773351356945881 ], [ -122.466952500298319, 37.773351323464517 ], [ -122.468023916207073, 37.773303833556405 ], [ -122.468348575135167, 37.773289518371953 ], [ -122.469092496140973, 37.773256712542192 ], [ -122.469517084069324, 37.773237987003441 ], [ -122.470162692059887, 37.773209510586071 ], [ -122.470162712316579, 37.773209791534974 ], [ -122.471240089434815, 37.773162280380667 ], [ -122.47124006836178, 37.773161981865066 ], [ -122.471762122569857, 37.773138947752848 ], [ -122.472219951938214, 37.773117969260745 ], [ -122.472297341321692, 37.773114423074823 ], [ -122.473427836194134, 37.773062613687692 ], [ -122.473737577092777, 37.773048416620476 ], [ -122.474550910293516, 37.773011133644289 ], [ -122.474550931211951, 37.773011425844139 ], [ -122.47455168367884, 37.773011391539193 ], [ -122.475612802305008, 37.772962709520556 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475750968701433, 37.774828017958995 ], [ -122.475888270951586, 37.776692974324831 ], [ -122.476025278884237, 37.778553841183147 ], [ -122.476169188584777, 37.78047585000003 ], [ -122.476306551695615, 37.782405954593735 ], [ -122.476436803741322, 37.784271125057622 ], [ -122.476575148769683, 37.786128282045887 ], [ -122.476575166016133, 37.786128513873635 ], [ -122.476572244768761, 37.786128658440397 ], [ -122.476637454822679, 37.786966661899442 ], [ -122.47616220885665, 37.786978730627588 ], [ -122.475567725323828, 37.787005248954515 ], [ -122.475136959985235, 37.787024462123846 ], [ -122.474584006514732, 37.787049123020516 ], [ -122.474432824368861, 37.787072820298278 ], [ -122.473333621918172, 37.787245111436654 ], [ -122.473316434470121, 37.787247805418339 ], [ -122.472782224474642, 37.787331534391747 ], [ -122.47254342327507, 37.787368961705944 ], [ -122.472401902490546, 37.787384046501884 ], [ -122.472290534181823, 37.787395917162272 ], [ -122.468653534340191, 37.787783523544832 ], [ -122.466942641213976, 37.788133153852705 ], [ -122.465882841138949, 37.788349715761562 ], [ -122.4648365613821, 37.788563504283978 ], [ -122.463771243725489, 37.788781172337565 ], [ -122.459781586189493, 37.789596250165474 ], [ -122.45947565027717, 37.789710559160014 ], [ -122.459250455476877, 37.789713291862398 ], [ -122.459028627866218, 37.789715983297555 ], [ -122.459011873160264, 37.789719335764374 ], [ -122.457498733292852, 37.789996912625632 ], [ -122.455878319348855, 37.790294143976332 ], [ -122.454248357567437, 37.79059310209788 ], [ -122.452035629818212, 37.790998907609811 ], [ -122.450995459060991, 37.791209906278311 ], [ -122.449385188501296, 37.791484927305568 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.449189583391288, 37.790608108274284 ], [ -122.449011564525605, 37.789726786022129 ], [ -122.448835781299294, 37.788856511426609 ], [ -122.448656220796209, 37.787977164570449 ], [ -122.4469741664482, 37.788186460801001 ], [ -122.446792779960703, 37.787261853518181 ], [ -122.446610465639836, 37.786302298311156 ], [ -122.446738337622591, 37.786243163870765 ], [ -122.446849274413296, 37.786186897081585 ], [ -122.447533801639068, 37.785411415165207 ], [ -122.447605133356163, 37.785275579171099 ], [ -122.447621599012791, 37.785194684567394 ], [ -122.447620107068687, 37.785083635258417 ], [ -122.447578549915917, 37.784998355250835 ], [ -122.447549635601661, 37.784906798974511 ], [ -122.447149436147441, 37.783030677550911 ], [ -122.447173033991248, 37.782710075581413 ], [ -122.447225622636566, 37.782575775503396 ], [ -122.447283893261542, 37.782434393634787 ], [ -122.447301513844337, 37.78239164221629 ], [ -122.447450898997985, 37.782195217632704 ], [ -122.447637300982407, 37.781719177617617 ], [ -122.447655108274333, 37.781537309189886 ], [ -122.447576361567485, 37.781175778551805 ], [ -122.447517982733274, 37.781069510753063 ], [ -122.447351927163439, 37.780152061280916 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.447997721370214, 37.778501033647089 ], [ -122.448879206948348, 37.778389428728616 ], [ -122.448885763577039, 37.778388598994987 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":6,\"ZIP_CODE\":94123,\"ID\":94123},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.438402484723895, 37.794806973138577 ], [ -122.440045216960385, 37.794597761146619 ], [ -122.440868123190498, 37.794493055920753 ], [ -122.440870170501768, 37.794492795601883 ], [ -122.441712771485612, 37.794385578392699 ], [ -122.443384198876302, 37.794172877898092 ], [ -122.445035345747925, 37.79396273368863 ], [ -122.446587034424141, 37.793765298555968 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446587862952981, 37.793765120337646 ], [ -122.446727777261131, 37.794706464759514 ], [ -122.446727777275527, 37.794706465308671 ], [ -122.446751148304088, 37.794858932799087 ], [ -122.44687098056005, 37.795640691997477 ], [ -122.446928896449293, 37.796018515191513 ], [ -122.447014738218328, 37.796578509648619 ], [ -122.447014738232738, 37.796578510197754 ], [ -122.44715829625072, 37.79751500492933 ], [ -122.44715829626513, 37.797515005478481 ], [ -122.447209582420982, 37.797849561838987 ], [ -122.447303043858923, 37.798459232650295 ], [ -122.447327769221047, 37.798519415660031 ], [ -122.447341437979304, 37.798552685345861 ], [ -122.447400275900733, 37.798948048802856 ], [ -122.447466030932901, 37.799389886853575 ], [ -122.447613279369918, 37.80032370169468 ], [ -122.447614227653389, 37.80032479990944 ], [ -122.447802865090509, 37.801525678628579 ], [ -122.447819572978844, 37.801633208279924 ], [ -122.447819731578562, 37.801633051558142 ], [ -122.447857007904148, 37.801596177616553 ], [ -122.447921599691895, 37.801532283988202 ], [ -122.448359651469772, 37.801586782043685 ], [ -122.448802640091088, 37.801772003872607 ], [ -122.449043488821943, 37.801965383441527 ], [ -122.449178591741017, 37.802073857413127 ], [ -122.449460220896157, 37.80245428555321 ], [ -122.449536106109235, 37.802697377072583 ], [ -122.449565462756809, 37.802791419060213 ], [ -122.449583713807527, 37.803152915295549 ], [ -122.449583722697156, 37.803153095621241 ], [ -122.449495662264184, 37.803470319431604 ], [ -122.449388062152011, 37.803710849923448 ], [ -122.449253815658196, 37.803861977230383 ], [ -122.449124213448584, 37.804007874392056 ], [ -122.448954929708933, 37.80412855834809 ], [ -122.448814547024654, 37.804228638340902 ], [ -122.448316879890498, 37.804444627382082 ], [ -122.448245764546613, 37.8044754914729 ], [ -122.448240723025904, 37.804477679722268 ], [ -122.448240728857812, 37.804477717258706 ], [ -122.44824552349904, 37.804508484024247 ], [ -122.448279145759457, 37.80472422506687 ], [ -122.448321161555853, 37.804993824977359 ], [ -122.448323332142422, 37.805007750605469 ], [ -122.448455258275729, 37.806133277363898 ], [ -122.448456716038393, 37.806145711376217 ], [ -122.448529100304583, 37.806763241657009 ], [ -122.448398315024306, 37.806792871575105 ], [ -122.448372486009177, 37.806798105392311 ], [ -122.448348405000459, 37.806803997055574 ], [ -122.44832261235274, 37.806810603719661 ], [ -122.448274523063262, 37.806825132736471 ], [ -122.448250514438882, 37.806833770101044 ], [ -122.448204281209826, 37.806853075532878 ], [ -122.448182039290586, 37.806863057432189 ], [ -122.448159833037835, 37.806874411921982 ], [ -122.448139357121818, 37.80688573782485 ], [ -122.448117186873532, 37.806898464895561 ], [ -122.448103560271292, 37.806906930735266 ], [ -122.447833857922845, 37.807052164926837 ], [ -122.447528276286675, 37.807250818574119 ], [ -122.447180260711605, 37.80747705551984 ], [ -122.446637954280746, 37.807525151443585 ], [ -122.446476742583258, 37.807516824572637 ], [ -122.445255735074412, 37.807626931568663 ], [ -122.444832951299077, 37.807611928844082 ], [ -122.444812115030089, 37.807609525524043 ], [ -122.444789548054757, 37.807607150739592 ], [ -122.443882698723485, 37.807700913966812 ], [ -122.443762319418525, 37.807713359922936 ], [ -122.443684632386478, 37.807721507559208 ], [ -122.442739197031372, 37.807965235732532 ], [ -122.442711389346584, 37.807972404225552 ], [ -122.442625660786931, 37.808004032657479 ], [ -122.442524054141913, 37.808024247618199 ], [ -122.442354908341343, 37.808043514915532 ], [ -122.441767928096084, 37.808104685164672 ], [ -122.44153654297466, 37.808127036445633 ], [ -122.441200035212404, 37.808167598495942 ], [ -122.44095148917647, 37.808195724629321 ], [ -122.44065270005224, 37.808288542686931 ], [ -122.440627067703531, 37.808301325835579 ], [ -122.440575873699515, 37.808329637315019 ], [ -122.440528212527582, 37.808360637280757 ], [ -122.440504418161254, 37.808377510253422 ], [ -122.440482353473598, 37.808394354762264 ], [ -122.440462037446096, 37.808411857223902 ], [ -122.440440008975642, 37.808430074859068 ], [ -122.440419728804599, 37.808448949904253 ], [ -122.440401196955307, 37.808468483183979 ], [ -122.440382683383717, 37.808488702887828 ], [ -122.440369270902693, 37.808505405024498 ], [ -122.440221248842576, 37.808670595014306 ], [ -122.440123020723803, 37.808753931536479 ], [ -122.440031607015868, 37.808833035512706 ], [ -122.439928060968157, 37.808779113775664 ], [ -122.440270312303127, 37.808229595265985 ], [ -122.440282155842453, 37.808219099502388 ], [ -122.440294017657806, 37.80820928989008 ], [ -122.440307609508267, 37.808199452089056 ], [ -122.440319488926107, 37.808190329461823 ], [ -122.440334864961784, 37.808182522213436 ], [ -122.440348510624844, 37.808174743706033 ], [ -122.440379298231178, 37.808160502620083 ], [ -122.440394728431826, 37.80815475493111 ], [ -122.440410176225527, 37.808149693678615 ], [ -122.440427371995767, 37.808145290667582 ], [ -122.440444586383592, 37.808141573526278 ], [ -122.440470613795014, 37.808143892481645 ], [ -122.440632371579539, 37.808106894262245 ], [ -122.440777543613365, 37.808097638147814 ], [ -122.441785658321223, 37.807988336182923 ], [ -122.441808099289275, 37.807985906519384 ], [ -122.441799274649981, 37.807913258896662 ], [ -122.441490202960892, 37.807943755875684 ], [ -122.441489843938655, 37.807930027497129 ], [ -122.441472521937172, 37.807929625920565 ], [ -122.441480832705722, 37.807916441000579 ], [ -122.441399010156047, 37.807832633902123 ], [ -122.441414331805831, 37.807822767220159 ], [ -122.441512177188741, 37.807923479293905 ], [ -122.44162439956051, 37.807912017357232 ], [ -122.441545678197272, 37.807814424464247 ], [ -122.441557557740268, 37.807805301431408 ], [ -122.441658935146762, 37.807908701878773 ], [ -122.441779792066384, 37.807896411186228 ], [ -122.441788084829597, 37.807882540086432 ], [ -122.441704477043643, 37.807796702127824 ], [ -122.441718086585212, 37.807787550596132 ], [ -122.44181950044613, 37.807892323765437 ], [ -122.441935183814792, 37.807880804821842 ], [ -122.441943476554428, 37.807866933985672 ], [ -122.441859851343693, 37.807780409692931 ], [ -122.441873460855945, 37.807771257868716 ], [ -122.441976640855827, 37.807877375557908 ], [ -122.441977107703579, 37.807895222530654 ], [ -122.441816542778795, 37.807911600852826 ], [ -122.441823637077619, 37.807984277513498 ], [ -122.442320834639688, 37.807932139640414 ], [ -122.442377595612399, 37.807918156721222 ], [ -122.442423345751081, 37.807880319904761 ], [ -122.442454552098894, 37.807815940203007 ], [ -122.442446930178193, 37.807789283331502 ], [ -122.442372794146465, 37.807800805256427 ], [ -122.442345413582302, 37.807812930576347 ], [ -122.442346006240967, 37.807835582850217 ], [ -122.442033707111619, 37.807875062158132 ], [ -122.442009243703154, 37.807733999234173 ], [ -122.442027954502791, 37.807721329750571 ], [ -122.442048400903616, 37.807841170516582 ], [ -122.442059070073554, 37.807851982482966 ], [ -122.442207469058346, 37.807833743991878 ], [ -122.442217473805144, 37.807819158213746 ], [ -122.442189585584913, 37.807679524627673 ], [ -122.44220852946296, 37.807675779293724 ], [ -122.442236490235743, 37.807818158319698 ], [ -122.442248872177956, 37.807828255059988 ], [ -122.442314446214397, 37.807820307862364 ], [ -122.442321008509211, 37.807806465496753 ], [ -122.442293084181387, 37.807665459069689 ], [ -122.442311884371406, 37.807656221984004 ], [ -122.442337612108403, 37.807779409372287 ], [ -122.442355041151117, 37.807783929703042 ], [ -122.442395115206907, 37.807727644711157 ], [ -122.442384613878218, 37.807657084644539 ], [ -122.44241012033585, 37.80763949635341 ], [ -122.442685954576561, 37.807595809313327 ], [ -122.442711892175168, 37.807793159839584 ], [ -122.442714242657956, 37.807793886686525 ], [ -122.442762503926971, 37.807808807618663 ], [ -122.442779771325021, 37.807807149719757 ], [ -122.442902339949939, 37.807794142945916 ], [ -122.442998755417392, 37.807774012900218 ], [ -122.44305537242154, 37.807754538469467 ], [ -122.44312779660163, 37.807743731320251 ], [ -122.443201717191158, 37.807723971451296 ], [ -122.443260064122327, 37.807704468417526 ], [ -122.443291085252142, 37.807699150180341 ], [ -122.443284905918816, 37.807661481932094 ], [ -122.443264195467847, 37.807663883640146 ], [ -122.443274020298077, 37.807642433173122 ], [ -122.443270056810988, 37.807623269807593 ], [ -122.44314383017317, 37.807628783225987 ], [ -122.443254016387542, 37.807605679164965 ], [ -122.44326198542305, 37.807579452177009 ], [ -122.443135758859114, 37.807584965586791 ], [ -122.443242483942598, 37.807561918560637 ], [ -122.443256021380762, 37.807550021121365 ], [ -122.443252111820613, 37.807532917601364 ], [ -122.443237999416624, 37.807522848927505 ], [ -122.443246112189684, 37.807502113399792 ], [ -122.443244040453081, 37.807489099968812 ], [ -122.443229963993403, 37.807480404158795 ], [ -122.443238040829058, 37.80745829576658 ], [ -122.443234130578546, 37.807441191982505 ], [ -122.443218162072228, 37.807426347064215 ], [ -122.443264702406339, 37.807418712940589 ], [ -122.443256517776859, 37.807436702740098 ], [ -122.443274647533997, 37.807467993526231 ], [ -122.44326455321054, 37.80747914750733 ], [ -122.443284610988869, 37.807517960537304 ], [ -122.443274498692247, 37.807528428086727 ], [ -122.443278390296598, 37.807544845448142 ], [ -122.443294466994871, 37.807563808946817 ], [ -122.443284389944367, 37.807575649647809 ], [ -122.443286551537625, 37.807592095514579 ], [ -122.443302628241014, 37.807611058737429 ], [ -122.443292551178217, 37.80762289916445 ], [ -122.443296353650368, 37.807635884626045 ], [ -122.443302173270908, 37.807659823956676 ], [ -122.443308298708217, 37.807695432906783 ], [ -122.443434058250361, 37.807672072344801 ], [ -122.443672727711146, 37.807597406163168 ], [ -122.443654441112585, 37.80749401124762 ], [ -122.443659326683914, 37.80748225662078 ], [ -122.443872884696731, 37.807440280106206 ], [ -122.443843293810986, 37.807367974319583 ], [ -122.44370731452436, 37.807397684591926 ], [ -122.443671444290359, 37.807416130974829 ], [ -122.443653888762242, 37.807406805822907 ], [ -122.443615857187666, 37.807408806048031 ], [ -122.443601726794256, 37.807398051259632 ], [ -122.443646374280576, 37.807384267621238 ], [ -122.443657912040536, 37.807362102160489 ], [ -122.443677395248955, 37.807378949282132 ], [ -122.443708397548889, 37.807372944513865 ], [ -122.443719917316642, 37.807350092614648 ], [ -122.443739400535989, 37.807366939726066 ], [ -122.443768690788104, 37.807361649886793 ], [ -122.443780246134452, 37.8073401708521 ], [ -122.443799711046125, 37.807356331526904 ], [ -122.443829001635834, 37.807351041666962 ], [ -122.443838790315425, 37.807328218286891 ], [ -122.443858291875273, 37.807345751805421 ], [ -122.443944431867095, 37.807329910704055 ], [ -122.443955915261199, 37.807305685922557 ], [ -122.44397716482635, 37.807323877338213 ], [ -122.444023687043469, 37.8073155564826 ], [ -122.444033511615373, 37.807294106225484 ], [ -122.444052976916183, 37.807310266852966 ], [ -122.444104672197867, 37.807301173690028 ], [ -122.444114316981555, 37.807272858828142 ], [ -122.444139117510247, 37.80729442532332 ], [ -122.444173580792466, 37.807288363378881 ], [ -122.444194201234893, 37.80728252962394 ], [ -122.444071355206816, 37.80715270277112 ], [ -122.444216516082363, 37.807275294750141 ], [ -122.444262912050363, 37.807262168504906 ], [ -122.444272682607831, 37.807238658656139 ], [ -122.444290381984246, 37.807253474900222 ], [ -122.444329892128806, 37.807241835878372 ], [ -122.444341267517572, 37.807213492465038 ], [ -122.444362535128633, 37.807232370243902 ], [ -122.44440375797123, 37.807220016231902 ], [ -122.444415151652876, 37.807192359237938 ], [ -122.444436401305339, 37.807210550845831 ], [ -122.444546334776021, 37.807177835248929 ], [ -122.444557764011307, 37.807151551111282 ], [ -122.444577265002437, 37.807169084522954 ], [ -122.444645975607855, 37.807148723180354 ], [ -122.444656807573693, 37.807165712792234 ], [ -122.444442095756031, 37.807229685090327 ], [ -122.444483781319875, 37.807301104324431 ], [ -122.444798026960797, 37.807203901149613 ], [ -122.445256739502199, 37.807070664930905 ], [ -122.445225848727588, 37.807014862399406 ], [ -122.445091259282876, 37.807031503517287 ], [ -122.445058382323012, 37.807032045740513 ], [ -122.44505631062799, 37.807019032334502 ], [ -122.445158131368714, 37.807007052095038 ], [ -122.44517833798561, 37.806985429952121 ], [ -122.445116858617496, 37.806951420767781 ], [ -122.445030629048986, 37.806963830570979 ], [ -122.445058875964975, 37.806918727340673 ], [ -122.445006173970214, 37.806889380441703 ], [ -122.444888888156058, 37.806905735960221 ], [ -122.444874654156408, 37.80682493696483 ], [ -122.444919463246819, 37.806817330453363 ], [ -122.445007311205075, 37.806866699909307 ], [ -122.444974402625263, 37.806799942930326 ], [ -122.444993292526533, 37.806794137571423 ], [ -122.445038942963123, 37.806884719639513 ], [ -122.445121510119179, 37.806930742460146 ], [ -122.445046609432765, 37.806780897144854 ], [ -122.445065498977499, 37.806775091780075 ], [ -122.445153141586133, 37.806948762165227 ], [ -122.44523217658535, 37.806992095976476 ], [ -122.44535586222473, 37.80695571941591 ], [ -122.445247620593648, 37.806722643522114 ], [ -122.445266510453877, 37.806716838119371 ], [ -122.445374751789728, 37.806949914001784 ], [ -122.445469219634987, 37.806921573283674 ], [ -122.445352433919155, 37.8066927587953 ], [ -122.445373036116507, 37.806686238401404 ], [ -122.44548810952854, 37.806915767845673 ], [ -122.445561992695104, 37.80689463390474 ], [ -122.445445188748025, 37.806665133074851 ], [ -122.445464060244248, 37.8066586412139 ], [ -122.445580882228484, 37.806888828457545 ], [ -122.44570454953481, 37.806851765098479 ], [ -122.445651671462073, 37.806749628119896 ], [ -122.44565282578796, 37.806727633749716 ], [ -122.445895754405569, 37.806815647477016 ], [ -122.445736019894767, 37.806863607021306 ], [ -122.445754222526631, 37.806897643432812 ], [ -122.446384760166481, 37.806715556411739 ], [ -122.446258463322081, 37.806718327435846 ], [ -122.446193213892712, 37.806738632616792 ], [ -122.446170395914976, 37.806726648309962 ], [ -122.445981443875837, 37.806782644077451 ], [ -122.445970540045622, 37.80676290912637 ], [ -122.445598235532685, 37.806626211997774 ], [ -122.445606509444289, 37.806611653919497 ], [ -122.446001524256076, 37.806756217316881 ], [ -122.446149253941641, 37.806712575605459 ], [ -122.445708764584751, 37.806550221539489 ], [ -122.445717038134546, 37.806535664008109 ], [ -122.446176741453044, 37.806704568267236 ], [ -122.446283248720263, 37.80667328102048 ], [ -122.446005194935296, 37.806500006546784 ], [ -122.446020461726192, 37.80648808024214 ], [ -122.446305581169554, 37.806666731908173 ], [ -122.446420668040176, 37.806632556303882 ], [ -122.446151409366294, 37.806464630848126 ], [ -122.446166694812902, 37.806453390945222 ], [ -122.446443000465095, 37.806626007165498 ], [ -122.446554662537906, 37.806593261684583 ], [ -122.446391023698155, 37.806492266360543 ], [ -122.446406290771549, 37.806480340000242 ], [ -122.446524184932926, 37.806553247425605 ], [ -122.446620237835958, 37.806519385773647 ], [ -122.446642693746213, 37.806583567659246 ], [ -122.44670978090322, 37.806567352265269 ], [ -122.446939773843297, 37.806492135834091 ], [ -122.446948929215651, 37.806511213110113 ], [ -122.446720738906919, 37.806589146719396 ], [ -122.446733553252031, 37.806615717634571 ], [ -122.446996260681743, 37.80653378052115 ], [ -122.44699045975274, 37.806444601492437 ], [ -122.446974562378131, 37.806432503088267 ], [ -122.446987792972394, 37.806408935599343 ], [ -122.446983774594216, 37.806387713332704 ], [ -122.44696966191627, 37.806377645652319 ], [ -122.446979503500458, 37.806356881028954 ], [ -122.446973448766144, 37.806324017983009 ], [ -122.446942374816217, 37.806327277888172 ], [ -122.44695713738291, 37.806296131639499 ], [ -122.446941332573715, 37.806221538794638 ], [ -122.446922047288439, 37.806212242955326 ], [ -122.446935223870369, 37.806186616724325 ], [ -122.446919473110199, 37.806114083171188 ], [ -122.446903683817155, 37.806106103074534 ], [ -122.446913508081977, 37.806084652286692 ], [ -122.446909453048207, 37.80606205716353 ], [ -122.446700318081568, 37.806074437190453 ], [ -122.446888239127603, 37.80604523913599 ], [ -122.446903488381423, 37.806032626547122 ], [ -122.446899451700631, 37.806010717849745 ], [ -122.446881914446337, 37.806002079602202 ], [ -122.446893486335398, 37.805981286694831 ], [ -122.446879430339621, 37.805907352248376 ], [ -122.446670313096035, 37.805920418116813 ], [ -122.446859964791457, 37.805891192083081 ], [ -122.446873519346127, 37.805879980657039 ], [ -122.446859427027746, 37.80580467279912 ], [ -122.446845404479788, 37.805798037263322 ], [ -122.446855228367411, 37.805776586210264 ], [ -122.446849425403357, 37.805753333484326 ], [ -122.446649086514796, 37.805771061515294 ], [ -122.446833420536592, 37.805737116186556 ], [ -122.446845280397781, 37.805727305922368 ], [ -122.446841244096817, 37.805705397216052 ], [ -122.446823724582785, 37.805697445671754 ], [ -122.446835297139998, 37.805676653032066 ], [ -122.446831332492664, 37.805657489784885 ], [ -122.446817327967949, 37.805651540403126 ], [ -122.446825403536536, 37.805629431757723 ], [ -122.446805830072663, 37.805609152989824 ], [ -122.446817348267402, 37.805586300786253 ], [ -122.446809113007006, 37.805536305492353 ], [ -122.446789863569947, 37.805528382500277 ], [ -122.446803112068949, 37.805505501734793 ], [ -122.446787107265692, 37.805489284429996 ], [ -122.446700807332093, 37.805498949734627 ], [ -122.446698645389645, 37.80548250391827 ], [ -122.446795308825045, 37.805471980811141 ], [ -122.446771068810406, 37.805405768207393 ], [ -122.446462225224153, 37.805444515694951 ], [ -122.445989505497494, 37.805505194954378 ], [ -122.446003707846955, 37.805518695189633 ], [ -122.4460087518259, 37.805579044127001 ], [ -122.446547025898894, 37.805509728799308 ], [ -122.446550972467278, 37.805528205624398 ], [ -122.446518204506802, 37.805532866567553 ], [ -122.446489185128556, 37.80554845357441 ], [ -122.446462996481614, 37.80553995860523 ], [ -122.446437419711515, 37.805554801776253 ], [ -122.446402597499727, 37.805547136014376 ], [ -122.446377002372941, 37.805561292745764 ], [ -122.446359483641928, 37.805553341395452 ], [ -122.446335312891549, 37.805555800213412 ], [ -122.446311412770896, 37.805568555772986 ], [ -122.446295642000578, 37.805561262020625 ], [ -122.44627666253902, 37.805563635428271 ], [ -122.446249319434344, 37.805577134798497 ], [ -122.446231800006771, 37.805569182891531 ], [ -122.446148996869667, 37.805580163541499 ], [ -122.446123402382483, 37.805594320480907 ], [ -122.446104135009989, 37.805585710944754 ], [ -122.44608171290129, 37.805588827858948 ], [ -122.44605783038071, 37.805602270079603 ], [ -122.446038580653479, 37.805594346696139 ], [ -122.446017870868147, 37.805596748888597 ], [ -122.445994006657656, 37.805610876973709 ], [ -122.445976469264281, 37.80560223887116 ], [ -122.445950586862267, 37.805605412856671 ], [ -122.445928452266116, 37.805619512663846 ], [ -122.445909185255772, 37.805610903090184 ], [ -122.445885032816534, 37.805614048516972 ], [ -122.445861132260902, 37.80562680426543 ], [ -122.445845361169205, 37.805619510184115 ], [ -122.445814305801534, 37.805623456219109 ], [ -122.44579215352951, 37.805636869562669 ], [ -122.445772886191605, 37.805628260247033 ], [ -122.445750464048956, 37.80563137682369 ], [ -122.445724833141924, 37.805644160817579 ], [ -122.445709062413187, 37.805636866986909 ], [ -122.445683179641605, 37.805640040919442 ], [ -122.445655872372569, 37.805654912741964 ], [ -122.445638335008098, 37.805646274589208 ], [ -122.445605549302542, 37.805650249115246 ], [ -122.445581648680047, 37.80566300453242 ], [ -122.445565859972788, 37.805655024250306 ], [ -122.445531361592955, 37.805659714013281 ], [ -122.445505730631226, 37.805672497684945 ], [ -122.445489959918007, 37.805665203825008 ], [ -122.445462346810999, 37.80566840625292 ], [ -122.445436715841595, 37.805681190184124 ], [ -122.445420945133293, 37.805673896314971 ], [ -122.445391619690682, 37.8056778137015 ], [ -122.445362527733508, 37.805690654981625 ], [ -122.445346757369364, 37.8056833608223 ], [ -122.44531225895534, 37.805688050246587 ], [ -122.445290052615164, 37.805699404198599 ], [ -122.445276030220143, 37.805692768201546 ], [ -122.445243244128591, 37.805696742632556 ], [ -122.445217576794818, 37.805708153657022 ], [ -122.445203554404628, 37.805701517651372 ], [ -122.445170786636353, 37.80570617848889 ], [ -122.445148507984058, 37.805714786690388 ], [ -122.445105232410981, 37.805714813715646 ], [ -122.445083008046993, 37.805725481196227 ], [ -122.44507071598305, 37.80571881663689 ], [ -122.445037947513853, 37.80572347772349 ], [ -122.445017471439073, 37.805734803086857 ], [ -122.445001701099429, 37.805727508881461 ], [ -122.444908551660831, 37.805740032744836 ], [ -122.44488456097811, 37.805749355858453 ], [ -122.444846440036883, 37.805747924599146 ], [ -122.444825945937126, 37.805758563222234 ], [ -122.444811923572402, 37.805751927170057 ], [ -122.44478259772076, 37.805755844410804 ], [ -122.44476037365142, 37.805766512099389 ], [ -122.444713600749751, 37.805765222919185 ], [ -122.444691394641026, 37.805776576752621 ], [ -122.444677372284929, 37.805769940684463 ], [ -122.444653201761611, 37.805772399426502 ], [ -122.444630958989393, 37.805782380395051 ], [ -122.444585917098436, 37.805781062896116 ], [ -122.444563710619391, 37.805792416711171 ], [ -122.444549688278883, 37.80578578090244 ], [ -122.444518632419573, 37.805789726327305 ], [ -122.444494659650545, 37.805799735794025 ], [ -122.444451329400607, 37.805797703293351 ], [ -122.444429141227388, 37.805809743784408 ], [ -122.444413370583575, 37.805802449506146 ], [ -122.444390948345657, 37.805805566373394 ], [ -122.44436699318581, 37.805816262252314 ], [ -122.444352970844008, 37.805809625871085 ], [ -122.444327105931421, 37.805813485938437 ], [ -122.44430666567736, 37.805826184043205 ], [ -122.444289129128734, 37.805817545953296 ], [ -122.444266706866628, 37.805820662247768 ], [ -122.444242769660434, 37.805832044533943 ], [ -122.444228747333952, 37.805825408412545 ], [ -122.444206307444119, 37.80582783853189 ], [ -122.444182406178044, 37.805840593670816 ], [ -122.44416661758342, 37.805832613201659 ], [ -122.44414592563939, 37.805835701223295 ], [ -122.444121953151352, 37.805845710608445 ], [ -122.444025145055349, 37.805850740063669 ], [ -122.444001189835404, 37.805861436142784 ], [ -122.443959572249256, 37.805858688226643 ], [ -122.443935671622995, 37.805871443304085 ], [ -122.443919900675667, 37.805864149240215 ], [ -122.443902651728223, 37.805866493739174 ], [ -122.443880499045548, 37.805879906997674 ], [ -122.443864710121758, 37.805871926219289 ], [ -122.44378192426916, 37.805883591639486 ], [ -122.443756257338379, 37.805895002334175 ], [ -122.443714639749658, 37.805892254606199 ], [ -122.443692432758084, 37.80590360826271 ], [ -122.4436766621739, 37.805896314160641 ], [ -122.443654240222742, 37.805899430882093 ], [ -122.443630320868039, 37.805911499476395 ], [ -122.443614550281069, 37.80590420509148 ], [ -122.44359211035453, 37.805906635368508 ], [ -122.443568208950381, 37.805919390108215 ], [ -122.443552420405013, 37.80591140955697 ], [ -122.443535171434974, 37.805913754002177 ], [ -122.443511270025937, 37.805926509004905 ], [ -122.443495481838426, 37.805918528714912 ], [ -122.443473041543214, 37.805920958425858 ], [ -122.443450852475209, 37.80593299846943 ], [ -122.443435081915695, 37.805925704609677 ], [ -122.443407468616002, 37.805928906281792 ], [ -122.443388776471963, 37.805942262421105 ], [ -122.443369509665189, 37.805933652158522 ], [ -122.443347069022408, 37.805936082125754 ], [ -122.443328395172557, 37.80595012440785 ], [ -122.443309128031871, 37.805941514415672 ], [ -122.443291878362203, 37.805943859111288 ], [ -122.443267959626397, 37.805955927620595 ], [ -122.443252189065603, 37.805948633187285 ], [ -122.443233209414942, 37.805951006379615 ], [ -122.443207595577817, 37.805964475989526 ], [ -122.443174557723637, 37.805958839788218 ], [ -122.443148908639841, 37.80597093678324 ], [ -122.443134868425886, 37.805963614373887 ], [ -122.443058967715231, 37.805973792109128 ], [ -122.443036832460166, 37.805987891372702 ], [ -122.443017547724423, 37.805978594894604 ], [ -122.442981318355606, 37.805983312150161 ], [ -122.442948532359026, 37.805987285937427 ], [ -122.442926379114695, 37.806000698747546 ], [ -122.44290884267825, 37.805992060178127 ], [ -122.442839827294776, 37.806000751431881 ], [ -122.442815943694384, 37.806014192451634 ], [ -122.442798425230706, 37.806006240298515 ], [ -122.442734582834518, 37.806014159544731 ], [ -122.442707256867862, 37.806028343976273 ], [ -122.44268971976544, 37.806019705660304 ], [ -122.442636241331058, 37.806026767128834 ], [ -122.442610646012994, 37.806040923030253 ], [ -122.442591378939156, 37.80603231319558 ], [ -122.442551688858117, 37.806037087047187 ], [ -122.442547725650329, 37.806017923926198 ], [ -122.443029120691975, 37.805957802366279 ], [ -122.443387985849299, 37.805912059110995 ], [ -122.443753825765924, 37.805868259987832 ], [ -122.443769345089194, 37.805865944017846 ], [ -122.443957339015583, 37.805839496905513 ], [ -122.444492227815289, 37.805772993462419 ], [ -122.444668235229059, 37.805751549942215 ], [ -122.445284142224111, 37.805672032526566 ], [ -122.445279062855903, 37.805610310694497 ], [ -122.44527006920417, 37.805597411188494 ], [ -122.444483346869873, 37.805698286438428 ], [ -122.443744945559246, 37.805793553184138 ], [ -122.443018600699929, 37.805886555905765 ], [ -122.442516549650193, 37.805951138305055 ], [ -122.442427410705633, 37.806050808392641 ], [ -122.442522464016989, 37.806507977467341 ], [ -122.442586642075881, 37.806579026869471 ], [ -122.443131937777821, 37.806513045572309 ], [ -122.443499493172467, 37.806468532301039 ], [ -122.443667282141945, 37.806463706682081 ], [ -122.443846679856065, 37.806439461357343 ], [ -122.443959061743129, 37.806434175357609 ], [ -122.443938050605254, 37.806358981524156 ], [ -122.443116244562106, 37.806442570813587 ], [ -122.442613848569025, 37.80649411140103 ], [ -122.442611615303633, 37.806474919513256 ], [ -122.442654765933469, 37.806470088368528 ], [ -122.442661399886077, 37.806458991436592 ], [ -122.442687570467214, 37.806466801090252 ], [ -122.442708298579689, 37.80646508646474 ], [ -122.442716537115061, 37.806449155723413 ], [ -122.442746276492144, 37.806461026947929 ], [ -122.442770051771902, 37.806443467084335 ], [ -122.44279979080919, 37.80645533830107 ], [ -122.442874033560784, 37.806447934694987 ], [ -122.442882218173921, 37.806429944918698 ], [ -122.442912011462568, 37.806443875399388 ], [ -122.442927548550728, 37.806442245978758 ], [ -122.442937481103471, 37.806424914130673 ], [ -122.442965543726487, 37.806438873384522 ], [ -122.442990995725921, 37.806419225391323 ], [ -122.443019058349662, 37.806433184357886 ], [ -122.443039786445084, 37.806431469674095 ], [ -122.443047971009094, 37.806413479611678 ], [ -122.443077764322211, 37.806427410050546 ], [ -122.443098474448703, 37.80642500892381 ], [ -122.443108388985991, 37.806406990354105 ], [ -122.443136451627183, 37.806420949292765 ], [ -122.443157179716479, 37.806419234588326 ], [ -122.443165418150457, 37.806403303815522 ], [ -122.443193426900422, 37.806415203442874 ], [ -122.443215885660081, 37.806413459937652 ], [ -122.443222483596756, 37.806400990383416 ], [ -122.443248689797912, 37.806410172509004 ], [ -122.443329817859791, 37.806401281706691 ], [ -122.443338074230084, 37.806386037628911 ], [ -122.443366083001223, 37.806397937215287 ], [ -122.443447229004846, 37.806389732764778 ], [ -122.443455467380517, 37.806373802246185 ], [ -122.443480033821288, 37.806386445534933 ], [ -122.443505916940026, 37.806383271811711 ], [ -122.443512496848314, 37.806370115534087 ], [ -122.443538721058857, 37.806379984576957 ], [ -122.443562892155768, 37.806377525783311 ], [ -122.443569490023762, 37.806365055935018 ], [ -122.44359742694175, 37.80637420973634 ], [ -122.443676842221919, 37.806366033924192 ], [ -122.443672164594389, 37.806253487231395 ], [ -122.443695462542081, 37.806349932284753 ], [ -122.443707916670277, 37.806362774599044 ], [ -122.44379596596373, 37.806353769679554 ], [ -122.443802527838344, 37.806339926678575 ], [ -122.443830500721077, 37.806350453289745 ], [ -122.443861574478419, 37.806347194484687 ], [ -122.443868190266627, 37.806335411052103 ], [ -122.443896109229783, 37.80634387807568 ], [ -122.443922010642723, 37.806341390962501 ], [ -122.443913871646629, 37.806228901600704 ], [ -122.443940541034891, 37.806321857121844 ], [ -122.443956527422003, 37.806337388377891 ], [ -122.44398415881686, 37.80633487273537 ], [ -122.444004419522869, 37.806315310362876 ], [ -122.444018675589732, 37.806330870132562 ], [ -122.444044576647997, 37.806328382998174 ], [ -122.444051174437902, 37.806315913397398 ], [ -122.444077344761538, 37.806323722474062 ], [ -122.444106688857119, 37.806320492116114 ], [ -122.444113268646205, 37.806307335530214 ], [ -122.444137744599871, 37.806316546256639 ], [ -122.444163610043603, 37.806312686225482 ], [ -122.444171919814423, 37.806299501392182 ], [ -122.444198126803698, 37.806308683844726 ], [ -122.44422572221977, 37.806304795006142 ], [ -122.444232302000771, 37.806291638962733 ], [ -122.444258508634277, 37.806300820858432 ], [ -122.444287834382735, 37.806296903754081 ], [ -122.44429446807581, 37.806285806729939 ], [ -122.444320620790435, 37.806292929589091 ], [ -122.444410328338193, 37.806281149679826 ], [ -122.444398674018615, 37.806166658108886 ], [ -122.444430732743257, 37.806267078693303 ], [ -122.444443114393508, 37.806277175761053 ], [ -122.44453626493052, 37.806264652260793 ], [ -122.444546322932027, 37.806252125028941 ], [ -122.444572511964111, 37.80626062069139 ], [ -122.44466566211247, 37.806248097094617 ], [ -122.444654277108668, 37.8061439020343 ], [ -122.444684317803762, 37.806233368169231 ], [ -122.444698448132328, 37.806244122830272 ], [ -122.444729504511017, 37.806240177078124 ], [ -122.444737832507585, 37.806227678906012 ], [ -122.444765733548039, 37.806235459291536 ], [ -122.444782982907213, 37.806233114935374 ], [ -122.44479131087958, 37.806220616210318 ], [ -122.444819211930195, 37.806228396857783 ], [ -122.444886496978683, 37.806219733530369 ], [ -122.44488089641429, 37.806138105148783 ], [ -122.444906901269448, 37.806205662186763 ], [ -122.444919282967007, 37.806215758930051 ], [ -122.444986568338436, 37.806207095539854 ], [ -122.444994931901647, 37.806195969945342 ], [ -122.445021067006977, 37.806202405936702 ], [ -122.445093525002505, 37.8061929701732 ], [ -122.445087942204026, 37.80611202850794 ], [ -122.445113929253864, 37.806178899068257 ], [ -122.445128041656801, 37.806188967520207 ], [ -122.445202229600525, 37.806179502882479 ], [ -122.445210557849578, 37.806167004396592 ], [ -122.445236728249824, 37.806174813490792 ], [ -122.44525743822885, 37.806172411710314 ], [ -122.445265766467799, 37.80615991322049 ], [ -122.445267302625481, 37.806086407881679 ], [ -122.445285033545986, 37.806168522898403 ], [ -122.445310916864869, 37.806165349047845 ], [ -122.445305315838027, 37.806083720685585 ], [ -122.445331267107818, 37.80614921860834 ], [ -122.445347145482373, 37.806160631088311 ], [ -122.445366125472304, 37.806158257824791 ], [ -122.445374453006437, 37.806145759613244 ], [ -122.44540062410141, 37.806153568385149 ], [ -122.445424776743849, 37.806150423332426 ], [ -122.445433086611402, 37.80613723812958 ], [ -122.445434730189021, 37.806067851662334 ], [ -122.445459293346119, 37.806146420033315 ], [ -122.445479985322095, 37.806143331781477 ], [ -122.445489989537165, 37.806128745440887 ], [ -122.445516214271365, 37.806138614038851 ], [ -122.445543827543716, 37.806135411324078 ], [ -122.445560788683153, 37.80612208366891 ], [ -122.445567131287589, 37.806165929693421 ], [ -122.445546061556797, 37.806154603157502 ], [ -122.445523639245678, 37.806157719690731 ], [ -122.445501522738013, 37.806172505842895 ], [ -122.445489122634442, 37.806161722734075 ], [ -122.44546670032598, 37.806164839531021 ], [ -122.445442817508763, 37.806178281625698 ], [ -122.445432201695837, 37.806169528989997 ], [ -122.445352822460961, 37.806179079403755 ], [ -122.445327226916191, 37.806193235905667 ], [ -122.445316593481564, 37.806183797096139 ], [ -122.445244135177305, 37.806193233002396 ], [ -122.44522027063509, 37.806207361203953 ], [ -122.445207906544567, 37.80619795120483 ], [ -122.445188926532239, 37.806200324168309 ], [ -122.445161708883106, 37.806218628036419 ], [ -122.445135448551525, 37.806207386764015 ], [ -122.445111565653477, 37.806220828516935 ], [ -122.44509921920563, 37.806212104669896 ], [ -122.445030203866864, 37.80622079693034 ], [ -122.445008123555382, 37.806236955848796 ], [ -122.444978438202398, 37.806227144485362 ], [ -122.444954644838973, 37.806244018373903 ], [ -122.444924959836314, 37.806234206991327 ], [ -122.444902861169254, 37.806249679463384 ], [ -122.444888730810021, 37.806238924826829 ], [ -122.444823175735436, 37.806247559901692 ], [ -122.444801113352611, 37.806264405213646 ], [ -122.444785216712205, 37.806252306233745 ], [ -122.444723104639422, 37.806260197747797 ], [ -122.444699383130299, 37.80627981731422 ], [ -122.444681702577071, 37.806265687549761 ], [ -122.444619590489268, 37.806273579283847 ], [ -122.444597491739671, 37.806289051423988 ], [ -122.444583361417187, 37.806278296750953 ], [ -122.444519536610485, 37.806286903400981 ], [ -122.444493959540637, 37.806301746142793 ], [ -122.44447984720945, 37.80629167788976 ], [ -122.444386678651156, 37.806303514912976 ], [ -122.444361209083098, 37.806322476775328 ], [ -122.444343546554336, 37.806309033118175 ], [ -122.444324566500356, 37.806311406491666 ], [ -122.444297366567298, 37.806330396317819 ], [ -122.444279722039454, 37.806317639632702 ], [ -122.444255568948051, 37.806320784456545 ], [ -122.444233506414719, 37.806337629662124 ], [ -122.444217609851108, 37.806325530605875 ], [ -122.444126171547992, 37.806337339172615 ], [ -122.444102396615747, 37.806354899035341 ], [ -122.444084751416256, 37.806342142057929 ], [ -122.444034733554687, 37.806349147388161 ], [ -122.443955318332002, 37.806357323441311 ], [ -122.443976328801966, 37.806432517557667 ], [ -122.444009133253417, 37.806429229637253 ], [ -122.44401497169018, 37.80645385590833 ], [ -122.443983915176943, 37.806457801197638 ], [ -122.443991501564284, 37.806483085111559 ], [ -122.444006566508079, 37.806529534342857 ], [ -122.443998364182022, 37.80654683776438 ], [ -122.444012889909018, 37.806572694020851 ], [ -122.444137456251781, 37.806569953925113 ], [ -122.444004705900142, 37.806590683869416 ], [ -122.444008597616772, 37.806607101209622 ], [ -122.444028224510816, 37.8066294400098 ], [ -122.444014687364202, 37.806641337539759 ], [ -122.444016813135761, 37.806656410256579 ], [ -122.444032907804939, 37.806676060090197 ], [ -122.4440210291, 37.806685183643154 ], [ -122.444025100564104, 37.806708465032258 ], [ -122.444042961537718, 37.806729459206764 ], [ -122.444031118428768, 37.806739955631187 ], [ -122.444035226540592, 37.806764609873134 ], [ -122.444052799240296, 37.806774621138679 ], [ -122.444173599989711, 37.806760268693552 ], [ -122.444175761756156, 37.806776714546551 ], [ -122.443903104192728, 37.806809364848277 ], [ -122.443899212488802, 37.80679294750523 ], [ -122.443999302417467, 37.806780996791801 ], [ -122.444012857566491, 37.806769785696332 ], [ -122.443998457621589, 37.806748734467796 ], [ -122.444004821781832, 37.806727340974426 ], [ -122.443985267126379, 37.806707748443934 ], [ -122.443996768032036, 37.80668420981921 ], [ -122.443979015619519, 37.806667334220748 ], [ -122.443988858092823, 37.806646570397213 ], [ -122.443972709865449, 37.806624861254427 ], [ -122.443980822328029, 37.806604125398131 ], [ -122.443978768461875, 37.806591798410317 ], [ -122.443966422424324, 37.806583074165033 ], [ -122.443972768605505, 37.806560994240272 ], [ -122.443949501347888, 37.806531848697659 ], [ -122.443975330928197, 37.806526615843026 ], [ -122.443974234501226, 37.806484743188264 ], [ -122.443966666100792, 37.806460145980267 ], [ -122.443854499850644, 37.806473669177059 ], [ -122.443860373479154, 37.806499667782823 ], [ -122.443717402901086, 37.806526746415607 ], [ -122.443667420469765, 37.806535124185942 ], [ -122.443645087827377, 37.806541672798915 ], [ -122.443622719585392, 37.806546848536961 ], [ -122.443600333023568, 37.806551337843935 ], [ -122.443577910179286, 37.806554454561891 ], [ -122.443581820462299, 37.80657155833741 ], [ -122.443586556699174, 37.806620238019541 ], [ -122.443637961503612, 37.806864552713115 ], [ -122.443633273319165, 37.806883858652405 ], [ -122.443607138425932, 37.806877421799527 ], [ -122.443529794028962, 37.806898611546323 ], [ -122.443526675114754, 37.806911710787858 ], [ -122.443502306167488, 37.806906618541966 ], [ -122.443424944087113, 37.80692712178169 ], [ -122.443421824461069, 37.806940221031809 ], [ -122.443397438234925, 37.806934442320348 ], [ -122.443318380993759, 37.806956346879609 ], [ -122.443315262039832, 37.806969446115438 ], [ -122.443289162740911, 37.806964382337512 ], [ -122.443211818508658, 37.806985571869724 ], [ -122.443208734784008, 37.807000044253662 ], [ -122.443180869538779, 37.806993635816845 ], [ -122.443103524899072, 37.807014825283645 ], [ -122.443102154219687, 37.807028582437368 ], [ -122.443076036948625, 37.80702283217979 ], [ -122.442998692594642, 37.807044021572032 ], [ -122.442995572888506, 37.807057120810583 ], [ -122.442969456307765, 37.807051370518039 ], [ -122.442895554290288, 37.807071816676462 ], [ -122.442876807520932, 37.807083113165248 ], [ -122.442860372447001, 37.807050420945309 ], [ -122.44288643546534, 37.807054111684081 ], [ -122.442950027240173, 37.807036582576075 ], [ -122.442956535452808, 37.807020680875418 ], [ -122.44298955579778, 37.807025630704103 ], [ -122.443044549671924, 37.807010303103709 ], [ -122.443051093786181, 37.806995773988241 ], [ -122.443080653430101, 37.80700078080757 ], [ -122.443137377283094, 37.806985425210136 ], [ -122.443143903403268, 37.806970209382371 ], [ -122.443176924084369, 37.806975159152948 ], [ -122.443230187542511, 37.806959860250316 ], [ -122.443236731623188, 37.806945331399028 ], [ -122.443266291259548, 37.806950338171859 ], [ -122.443323015021249, 37.806934981935846 ], [ -122.443327829074263, 37.806920481311195 ], [ -122.443311249747254, 37.806882297698685 ], [ -122.443357388360823, 37.806925488066938 ], [ -122.443415825168074, 37.806909417378883 ], [ -122.443402365072131, 37.806858134260111 ], [ -122.443446755408701, 37.806900666674323 ], [ -122.443506922515155, 37.806884567152593 ], [ -122.443513466527847, 37.806870038011155 ], [ -122.443541295807862, 37.80687507323001 ], [ -122.443611772372606, 37.806856057118701 ], [ -122.443568098366953, 37.806642517527813 ], [ -122.443490717910933, 37.80666233411602 ], [ -122.443464637369544, 37.806657956797729 ], [ -122.443447387531151, 37.806660301241159 ], [ -122.443394142301429, 37.806676286675255 ], [ -122.443369792101194, 37.80667188082289 ], [ -122.443307715462936, 37.806681144456071 ], [ -122.443302883453555, 37.806694958647995 ], [ -122.443274946458374, 37.806685805052041 ], [ -122.443204469968975, 37.806704820962679 ], [ -122.443178370770624, 37.806699757160004 ], [ -122.443107894250431, 37.806718773287464 ], [ -122.44308352539521, 37.806713680954857 ], [ -122.443013048491437, 37.806732697031315 ], [ -122.442985237265603, 37.806728348120288 ], [ -122.442950738216922, 37.806733036852982 ], [ -122.44291647270785, 37.806746649473865 ], [ -122.442888625547027, 37.806740927400313 ], [ -122.442821555362244, 37.806757827053026 ], [ -122.442793726174969, 37.806752791664266 ], [ -122.442731613479054, 37.806760682403457 ], [ -122.442721537132741, 37.806772522772178 ], [ -122.442695384049358, 37.806765399290242 ], [ -122.442678134861055, 37.806767743884308 ], [ -122.442671608648851, 37.806782959137742 ], [ -122.442640174735473, 37.806772489262421 ], [ -122.44262290758212, 37.806774147415787 ], [ -122.442611226258151, 37.806790821310969 ], [ -122.442619566601024, 37.806844935481941 ], [ -122.4425572741916, 37.806845961528367 ], [ -122.442543113877818, 37.806767907443565 ], [ -122.442812274930034, 37.80673394443496 ], [ -122.442895043918895, 37.806721593264598 ], [ -122.44288439854499, 37.806645541768312 ], [ -122.44286390392358, 37.80665618032328 ], [ -122.442620734223425, 37.806691089066426 ], [ -122.442586432743397, 37.80670332845169 ], [ -122.442569254711358, 37.806708418497294 ], [ -122.442553843632766, 37.806714852893514 ], [ -122.44253845015912, 37.806721973451488 ], [ -122.442524957636692, 37.80672882453954 ], [ -122.442523074304333, 37.806729780720396 ], [ -122.44251132146853, 37.806743708869504 ], [ -122.442504741349737, 37.806756865090371 ], [ -122.442501639536246, 37.806770651023797 ], [ -122.442501980779383, 37.80678369296961 ], [ -122.442511153361352, 37.806803457290563 ], [ -122.442513800037688, 37.806838436574978 ], [ -122.442498190669355, 37.806837320216118 ], [ -122.442500262610309, 37.806850333930662 ], [ -122.442500855308538, 37.806872986207821 ], [ -122.442513308640088, 37.806885829207545 ], [ -122.442543448720272, 37.807045340228143 ], [ -122.442550657494735, 37.807056209147177 ], [ -122.442552729104918, 37.807069222865962 ], [ -122.442549574089071, 37.807080949216051 ], [ -122.442542975993348, 37.807093419280186 ], [ -122.442532880918762, 37.80710457321085 ], [ -122.442521020253452, 37.807114382489758 ], [ -122.442507375020014, 37.80712216152461 ], [ -122.442493693848277, 37.807128567143486 ], [ -122.442476479790699, 37.807132284309965 ], [ -122.44245921255002, 37.807133942439243 ], [ -122.442443603113219, 37.807132825798654 ], [ -122.442426210144987, 37.807129678620242 ], [ -122.442401877121071, 37.807125959012424 ], [ -122.442394668361601, 37.807115090084459 ], [ -122.442382322776425, 37.807106365943092 ], [ -122.442371670941014, 37.807096240723055 ], [ -122.442357217513063, 37.80707312972298 ], [ -122.442353415933297, 37.807060144492915 ], [ -122.442352751441661, 37.807034746485243 ], [ -122.442355888527402, 37.807022333707671 ], [ -122.442327317913012, 37.80685661596673 ], [ -122.442325228043273, 37.806842915816503 ], [ -122.442319678177554, 37.80682927264953 ], [ -122.442314163892661, 37.806817002627895 ], [ -122.442303476176264, 37.806805503986638 ], [ -122.442292807121106, 37.806794692040256 ], [ -122.442278730911681, 37.806785996390175 ], [ -122.44225259583861, 37.806779559791913 ], [ -122.442263912071212, 37.806815083032639 ], [ -122.442334351220893, 37.807191623601256 ], [ -122.442574008114292, 37.807154713430769 ], [ -122.442579917970377, 37.807182085225314 ], [ -122.442316125837024, 37.807222826535309 ], [ -122.442296128149565, 37.807120146892402 ], [ -122.442249660075092, 37.807130526084627 ], [ -122.442243205590515, 37.807148487593025 ], [ -122.442218675723581, 37.80713721717698 ], [ -122.442177362768064, 37.807146137987679 ], [ -122.442169249735414, 37.807166873443258 ], [ -122.44214120495144, 37.807153600709071 ], [ -122.442110219881215, 37.807160291509724 ], [ -122.442102143088249, 37.807182399820256 ], [ -122.442074080353734, 37.807168440637511 ], [ -122.441951871381676, 37.807195175211561 ], [ -122.441943758626778, 37.807215910645752 ], [ -122.441915713871779, 37.807202638132658 ], [ -122.441829644720443, 37.807221223495034 ], [ -122.441823243975179, 37.807241243729116 ], [ -122.441796947879098, 37.807228628850183 ], [ -122.441714321769382, 37.807246470710545 ], [ -122.441706226203735, 37.807267892572305 ], [ -122.441678163511327, 37.807253933295577 ], [ -122.441590382530393, 37.807273233392451 ], [ -122.441583945818934, 37.807291881022437 ], [ -122.441559397367783, 37.80727992432282 ], [ -122.441528412884566, 37.807286614959068 ], [ -122.441522065914484, 37.807308694749302 ], [ -122.4414922725324, 37.807294763645217 ], [ -122.441459575632209, 37.807302169181312 ], [ -122.441451444085047, 37.807322218159875 ], [ -122.441425148030646, 37.807309603472838 ], [ -122.441347676557456, 37.807325986488941 ], [ -122.441341185930639, 37.807342574807244 ], [ -122.441318422046436, 37.807332648592578 ], [ -122.441240951224984, 37.807349031801699 ], [ -122.441234550317162, 37.807369052277899 ], [ -122.441208253583667, 37.807356437279445 ], [ -122.44112907030356, 37.807373535328814 ], [ -122.44112095660725, 37.807394270717083 ], [ -122.44109464263579, 37.807380969523756 ], [ -122.440999993407189, 37.807402442127575 ], [ -122.440987126791967, 37.807373811300785 ], [ -122.441006340540554, 37.807380362365826 ], [ -122.441088966969374, 37.807362521001856 ], [ -122.441071293907683, 37.807282464951484 ], [ -122.441105839449861, 37.807345761826411 ], [ -122.441121664279194, 37.807355115562864 ], [ -122.441199135479366, 37.807338732426437 ], [ -122.441181426400448, 37.807257303252058 ], [ -122.441215936141603, 37.807319227778606 ], [ -122.441235275185775, 37.807330583566994 ], [ -122.44130240044376, 37.80731574411066 ], [ -122.44131244119076, 37.80730253099884 ], [ -122.441289899930467, 37.807234916217887 ], [ -122.441336846017577, 37.807308996562313 ], [ -122.441403970896872, 37.807294157328393 ], [ -122.441389758204977, 37.807214043807704 ], [ -122.441424286396526, 37.807276654700864 ], [ -122.441440110897986, 37.807286008125857 ], [ -122.441505522979043, 37.807271883482372 ], [ -122.441489561884524, 37.807191112300579 ], [ -122.441524179900497, 37.807257155053932 ], [ -122.441538238159126, 37.807265164360558 ], [ -122.441610518456187, 37.807248866405487 ], [ -122.441598394609557, 37.807182453357271 ], [ -122.441630905705154, 37.807234109473121 ], [ -122.441643233270554, 37.80724214726019 ], [ -122.441715531475523, 37.807226535673394 ], [ -122.441722129729229, 37.807214065930424 ], [ -122.441749959016704, 37.807219101571583 ], [ -122.44181537098703, 37.807204977030693 ], [ -122.441821969214871, 37.8071925070075 ], [ -122.441804977662258, 37.807138535507519 ], [ -122.441848068147848, 37.807197571115559 ], [ -122.441911750066581, 37.807183474731303 ], [ -122.441901356658576, 37.80711703321635 ], [ -122.441942735164389, 37.807176783982825 ], [ -122.442002974305893, 37.807163431240092 ], [ -122.44201133876993, 37.807152305853435 ], [ -122.442037401781846, 37.80715599677972 ], [ -122.442094216092357, 37.807144073567486 ], [ -122.44210250836673, 37.807130202449336 ], [ -122.442130373562975, 37.807136610866159 ], [ -122.442190612977058, 37.807123257747243 ], [ -122.4421768301127, 37.807059618983033 ], [ -122.442207521070316, 37.807107871278774 ], [ -122.442221597332434, 37.80711656693682 ], [ -122.442292164942742, 37.807100983492141 ], [ -122.442243704644795, 37.806836704402386 ], [ -122.442167767011739, 37.806845508971712 ], [ -122.442147308147, 37.806857520267137 ], [ -122.442072831477645, 37.806855999790052 ], [ -122.442050696118415, 37.806870098860529 ], [ -122.442038314362932, 37.806860002097636 ], [ -122.44197618353418, 37.806867205731571 ], [ -122.44195577849186, 37.806881276017322 ], [ -122.441943396394166, 37.806871178975598 ], [ -122.441881247592036, 37.806877696126804 ], [ -122.441859148445275, 37.806893168021055 ], [ -122.44184673044974, 37.806881698103723 ], [ -122.441786329930238, 37.806888873147223 ], [ -122.441762464513715, 37.80690300092192 ], [ -122.441750082438688, 37.806892903859939 ], [ -122.441684509153674, 37.806900850470612 ], [ -122.441658930973958, 37.80691569315082 ], [ -122.441646530958224, 37.806904909645212 ], [ -122.441572305572137, 37.806912998636243 ], [ -122.441548386244634, 37.806925066794669 ], [ -122.441465311637984, 37.806925747662177 ], [ -122.441443158191802, 37.806939160185685 ], [ -122.44142908206166, 37.806930464709019 ], [ -122.44136529221457, 37.806940441967662 ], [ -122.441344958843104, 37.806957258153332 ], [ -122.441327368515545, 37.806946560051927 ], [ -122.441311849243505, 37.806948875968999 ], [ -122.441293246205632, 37.806965663663163 ], [ -122.441275637234966, 37.806954278858043 ], [ -122.44126011795251, 37.806956594493755 ], [ -122.441239730706059, 37.80697135136279 ], [ -122.441223906642946, 37.806961997904651 ], [ -122.441151465000161, 37.806972117453661 ], [ -122.441129311490656, 37.806985530192655 ], [ -122.441115235374099, 37.806976834129472 ], [ -122.441037621302598, 37.806987725743447 ], [ -122.44101719810962, 37.807001109708246 ], [ -122.441003139964337, 37.806993100613703 ], [ -122.440922064462001, 37.807004048844455 ], [ -122.440901659189493, 37.807018119222057 ], [ -122.440895696481135, 37.806988688312863 ], [ -122.441218230630383, 37.806943549393232 ], [ -122.441263058469275, 37.806936630972665 ], [ -122.441435464921327, 37.80690975751326 ], [ -122.441433016200833, 37.806882328691337 ], [ -122.441372490336036, 37.806884698487615 ], [ -122.441304935046745, 37.806883063866231 ], [ -122.441274237201611, 37.806900737372722 ], [ -122.441242947719005, 37.806895758576474 ], [ -122.44119330612007, 37.806917177241274 ], [ -122.441110393048419, 37.806924035754299 ], [ -122.441042945411667, 37.806926519579946 ], [ -122.440998117551175, 37.806933437642307 ], [ -122.440918593700744, 37.806937493327403 ], [ -122.440899792827196, 37.80694673020367 ], [ -122.440782200958282, 37.806951412279737 ], [ -122.440680218094201, 37.806957210845738 ], [ -122.44054721454367, 37.806968326842338 ], [ -122.440468121234176, 37.806988856613863 ], [ -122.440371365223044, 37.806995942367976 ], [ -122.440134737571853, 37.807016316747912 ], [ -122.43989673886324, 37.807050447763743 ], [ -122.43967267172718, 37.807087782808438 ], [ -122.439538386124013, 37.807116087220599 ], [ -122.439408914197855, 37.80712989048353 ], [ -122.439239537222377, 37.807140229675355 ], [ -122.439104802429114, 37.807151372502958 ], [ -122.438766800452754, 37.807200879974303 ], [ -122.438299128879564, 37.807256638299236 ], [ -122.437960892936687, 37.807297219553782 ], [ -122.437688625577763, 37.807344957007658 ], [ -122.437514504314919, 37.807372539848515 ], [ -122.437376577168436, 37.807394034049501 ], [ -122.437259217227975, 37.807407636246566 ], [ -122.437146815479664, 37.80741222942855 ], [ -122.437031382840118, 37.807433353744614 ], [ -122.436885215537345, 37.80747077743743 ], [ -122.436714393247598, 37.807492124635075 ], [ -122.436614801865048, 37.807523289038706 ], [ -122.436563088290782, 37.807531692199284 ], [ -122.436507376472989, 37.80751955916309 ], [ -122.436298557575597, 37.807544276277241 ], [ -122.436240083275962, 37.807558970839189 ], [ -122.436155205939471, 37.807556930640864 ], [ -122.435982097955772, 37.80755702547102 ], [ -122.435828364056036, 37.807569849863164 ], [ -122.435859235962624, 37.807691580598309 ], [ -122.433925269093152, 37.807945122059223 ], [ -122.433817271048738, 37.80791942385212 ], [ -122.433787568621852, 37.807908923241605 ], [ -122.433755903391244, 37.807889527355464 ], [ -122.43376416154652, 37.807874283957823 ], [ -122.433933527911989, 37.807929878638532 ], [ -122.435838185171932, 37.807680938490563 ], [ -122.435834169572829, 37.807659716129535 ], [ -122.435737591220459, 37.807673662142683 ], [ -122.433917274710794, 37.807904049527295 ], [ -122.433913385157453, 37.807887631858222 ], [ -122.434041074707324, 37.807871803440726 ], [ -122.434044035083929, 37.807852526536877 ], [ -122.434070401961165, 37.807867888872153 ], [ -122.434160130921143, 37.807856803195897 ], [ -122.434164803061421, 37.807836811488876 ], [ -122.434189457816743, 37.807852888603414 ], [ -122.434284359969311, 37.807841031264509 ], [ -122.434289032075128, 37.807821039552501 ], [ -122.434313686852093, 37.80783711664111 ], [ -122.434406858247897, 37.807825287589175 ], [ -122.434411548897913, 37.80780598229466 ], [ -122.434437915834692, 37.807821344547961 ], [ -122.434520741089443, 37.807811058557668 ], [ -122.434525413482959, 37.8077910671051 ], [ -122.434551797979211, 37.80780711577237 ], [ -122.434636336023075, 37.807796114603541 ], [ -122.434641025919433, 37.807776809311157 ], [ -122.434665680757917, 37.807792886326304 ], [ -122.434750218436946, 37.807781885630661 ], [ -122.434754908641708, 37.807762580053435 ], [ -122.434779544918584, 37.807777970622496 ], [ -122.434871004379232, 37.807766856012883 ], [ -122.434875676329611, 37.807746864551945 ], [ -122.434900331208965, 37.807762941518149 ], [ -122.434976252743482, 37.807753454920828 ], [ -122.434979194289198, 37.807733491570779 ], [ -122.435005579555238, 37.807749540125371 ], [ -122.435084943914418, 37.807739310518855 ], [ -122.43508961579829, 37.807719318774772 ], [ -122.435116018975634, 37.807736053738346 ], [ -122.435191922576223, 37.807725881117662 ], [ -122.435196576876137, 37.807705202655406 ], [ -122.43522297972649, 37.807721937600746 ], [ -122.435302361927597, 37.807712394556091 ], [ -122.435306998308789, 37.807691029930353 ], [ -122.435333419066353, 37.8077084510101 ], [ -122.435402436929863, 37.807699764700374 ], [ -122.435407055040258, 37.807677713642711 ], [ -122.435433494058017, 37.807695821128007 ], [ -122.435507685095942, 37.807686362859421 ], [ -122.435514015990663, 37.807663596960829 ], [ -122.43554045469773, 37.807681704977007 ], [ -122.435609472497859, 37.807673017996969 ], [ -122.435614072648008, 37.807650280497519 ], [ -122.435642259638655, 37.807669046525525 ], [ -122.435711277415109, 37.807660359485872 ], [ -122.43572966745738, 37.807635335465775 ], [ -122.435735467093281, 37.807658589004994 ], [ -122.435830350851532, 37.807646043988242 ], [ -122.435804210559851, 37.807572993499143 ], [ -122.435788619382777, 37.807572562680427 ], [ -122.435772938706066, 37.807568699417203 ], [ -122.435758952605468, 37.807563435158237 ], [ -122.435746625277645, 37.807555396762375 ], [ -122.435735992180923, 37.807545957377542 ], [ -122.435727054339125, 37.807535116438025 ], [ -122.435721558628927, 37.807523531999962 ], [ -122.435719488549438, 37.807510518156661 ], [ -122.435678807510797, 37.807410923656249 ], [ -122.435671271850225, 37.807321086052454 ], [ -122.435659314380757, 37.807260850297496 ], [ -122.435635232905554, 37.807266739353338 ], [ -122.435635716129568, 37.807285273061382 ], [ -122.435609349135348, 37.807269911074314 ], [ -122.435543792417761, 37.807278540984541 ], [ -122.43552006850399, 37.80729815869541 ], [ -122.435504101570785, 37.807283312732451 ], [ -122.435441987649398, 37.807291199357643 ], [ -122.435419994063011, 37.807310788651876 ], [ -122.43540402680101, 37.807295942681101 ], [ -122.435341912858448, 37.807303829253563 ], [ -122.435318189229335, 37.807323446918055 ], [ -122.435302222331032, 37.807308600927854 ], [ -122.435234935185079, 37.80731725906076 ], [ -122.435212941536335, 37.807336848316268 ], [ -122.435196974310131, 37.807322002317541 ], [ -122.435127956788023, 37.807330689056798 ], [ -122.435105963445992, 37.807350278012088 ], [ -122.435089996584352, 37.807335431993209 ], [ -122.435019248678017, 37.807344147060114 ], [ -122.4349972725025, 37.807364422440152 ], [ -122.434981288114457, 37.807348889967201 ], [ -122.434910540175395, 37.807357604694324 ], [ -122.434888564319408, 37.807377880322939 ], [ -122.434872579266241, 37.807362348121238 ], [ -122.434798388810123, 37.807371805710694 ], [ -122.434776412580376, 37.807392081598607 ], [ -122.434758697865377, 37.807376577207712 ], [ -122.434691410594411, 37.807385235030431 ], [ -122.434667704309632, 37.80740553900322 ], [ -122.434651719289548, 37.807390006497108 ], [ -122.434575798078043, 37.807399492611601 ], [ -122.434552092103161, 37.807419796555429 ], [ -122.434536125337331, 37.807404950461788 ], [ -122.434461916577604, 37.807413721960565 ], [ -122.434439940575373, 37.80743399723081 ], [ -122.434423973489999, 37.807419151402279 ], [ -122.434349765034597, 37.807427922275664 ], [ -122.43432605864993, 37.807448226454213 ], [ -122.434310074038208, 37.807432693896239 ], [ -122.434230709546782, 37.807442922713086 ], [ -122.434208733825983, 37.807463198209049 ], [ -122.434192749235578, 37.807447665635237 ], [ -122.434111654710136, 37.807457923293533 ], [ -122.434087948236439, 37.807478226874963 ], [ -122.434071963321998, 37.807462694290493 ], [ -122.433992599465014, 37.807472923210291 ], [ -122.433970605449531, 37.807492512508169 ], [ -122.433954638431118, 37.807477666067122 ], [ -122.433870083452888, 37.807487980028228 ], [ -122.433846376910722, 37.807508283835588 ], [ -122.433830409920873, 37.80749343765234 ], [ -122.43374412454402, 37.807503779895939 ], [ -122.433722148333416, 37.807524055581261 ], [ -122.433706163485638, 37.807508522947344 ], [ -122.433606088798641, 37.807521151358706 ], [ -122.433584112530909, 37.807541426469101 ], [ -122.433574191011218, 37.807492831512612 ], [ -122.433602216513336, 37.807505419848866 ], [ -122.433678120078412, 37.807495247927321 ], [ -122.433686343139499, 37.80747863165486 ], [ -122.433714368662436, 37.807491220239072 ], [ -122.433795462933048, 37.807480962859643 ], [ -122.433803685966325, 37.807464346578953 ], [ -122.433831693616042, 37.80747624842688 ], [ -122.433911075733292, 37.807466706318117 ], [ -122.433919280165583, 37.807449403606796 ], [ -122.433947305702674, 37.807461991586599 ], [ -122.434024939884253, 37.807451791061574 ], [ -122.434034892878572, 37.807435146394688 ], [ -122.434062900561514, 37.80744704846235 ], [ -122.434135379039915, 37.807438305591944 ], [ -122.434143601646298, 37.807421689293108 ], [ -122.434171609334427, 37.807433591060409 ], [ -122.434245800271043, 37.807424133585066 ], [ -122.434254022851178, 37.80740751727847 ], [ -122.434282048436359, 37.807420105453431 ], [ -122.434352778623364, 37.807410704669572 ], [ -122.434361000831984, 37.807394088361185 ], [ -122.434389026430239, 37.807406676510716 ], [ -122.43446148730483, 37.807397247549531 ], [ -122.434471439847925, 37.807380602851318 ], [ -122.43449944756594, 37.807392504266289 ], [ -122.434571926300066, 37.807383761671048 ], [ -122.434581860921966, 37.807366430255094 ], [ -122.434609886547221, 37.807379018352258 ], [ -122.434682347359626, 37.807369588980769 ], [ -122.434690569836022, 37.807352972643585 ], [ -122.434718577593046, 37.807364874555745 ], [ -122.434784152364955, 37.807356931263477 ], [ -122.43479237481715, 37.807340314919166 ], [ -122.434820382585727, 37.807352216807175 ], [ -122.434884226976308, 37.807344301846562 ], [ -122.434894179763575, 37.807327657106839 ], [ -122.434922187543705, 37.807339558970696 ], [ -122.434987762271462, 37.807331615565204 ], [ -122.434997697142578, 37.807314284382969 ], [ -122.435025722825799, 37.807326872656006 ], [ -122.43509127963911, 37.807318242759216 ], [ -122.435101232376525, 37.807301598001935 ], [ -122.435129240172827, 37.807313499542126 ], [ -122.43519308450432, 37.807305584688869 ], [ -122.435201306859327, 37.807288968315881 ], [ -122.435231044678986, 37.807300841444686 ], [ -122.435296619692778, 37.807292898136311 ], [ -122.4353048420232, 37.807276281756089 ], [ -122.435332849842581, 37.807288183247969 ], [ -122.435398406593208, 37.807279553455011 ], [ -122.435406628899386, 37.80726293706762 ], [ -122.435436384982893, 37.8072754965727 ], [ -122.435451904167508, 37.80727318170905 ], [ -122.435460126807058, 37.807256565312237 ], [ -122.435488134643933, 37.807268466767304 ], [ -122.435508433635007, 37.807250278724965 ], [ -122.435536459373509, 37.807262866602237 ], [ -122.435602016078107, 37.80725423669606 ], [ -122.435615572581426, 37.80724302656666 ], [ -122.435634803373304, 37.807250264946127 ], [ -122.435655496065394, 37.807247178418407 ], [ -122.435588180541416, 37.806922774713847 ], [ -122.435582667693723, 37.80691050382292 ], [ -122.435534182131477, 37.806909926104069 ], [ -122.435571873987683, 37.806894886513319 ], [ -122.435559492755871, 37.80688478880149 ], [ -122.435545453103273, 37.806877464940861 ], [ -122.435529719250937, 37.806871542612598 ], [ -122.435514074542013, 37.806869052731017 ], [ -122.435493292470525, 37.806868707065597 ], [ -122.435344768504805, 37.806882132076758 ], [ -122.435121911744176, 37.806899523218981 ], [ -122.434871780421872, 37.806933156410132 ], [ -122.434433666315243, 37.806993908011997 ], [ -122.434416381034183, 37.806994878255509 ], [ -122.434278149661878, 37.807004699734037 ], [ -122.434260827915324, 37.807004297098374 ], [ -122.434243471085438, 37.807002521306472 ], [ -122.434227808507927, 37.806999344820447 ], [ -122.434210379805293, 37.806994823568417 ], [ -122.434194645688478, 37.806988901067868 ], [ -122.434180606150846, 37.806981577044944 ], [ -122.434166548746745, 37.806973567135586 ], [ -122.434154185915276, 37.806964155430045 ], [ -122.434143535561248, 37.806954029186485 ], [ -122.434134579447857, 37.806942501702544 ], [ -122.434125606153387, 37.806930288047226 ], [ -122.434123142595496, 37.806902172634167 ], [ -122.434101162359013, 37.806855835376808 ], [ -122.434090404384904, 37.806841590530418 ], [ -122.434081359579054, 37.806826631135792 ], [ -122.43406669419322, 37.806795281911626 ], [ -122.434061091508866, 37.806779579065555 ], [ -122.434055471636611, 37.80676318977391 ], [ -122.434051599648825, 37.806747458545502 ], [ -122.434049475890433, 37.80673238537485 ], [ -122.434047316368137, 37.806715939336222 ], [ -122.434046440132462, 37.806682304072865 ], [ -122.43401902773472, 37.806493903623902 ], [ -122.434011784506907, 37.806481661588982 ], [ -122.434004523385283, 37.806468732570444 ], [ -122.433998992958308, 37.806455775445059 ], [ -122.433995192532976, 37.806442790224423 ], [ -122.433964135838224, 37.806446732869858 ], [ -122.433932703641176, 37.806436260667759 ], [ -122.433886127202001, 37.806442518242726 ], [ -122.433858853036313, 37.806458760199753 ], [ -122.433839551447093, 37.806448775787949 ], [ -122.433791244999313, 37.806455061692688 ], [ -122.4337639708083, 37.80647130362771 ], [ -122.433744668882483, 37.806461319206093 ], [ -122.433694632079209, 37.806467633442772 ], [ -122.433667357862433, 37.806483875355433 ], [ -122.43364805594635, 37.806473890918021 ], [ -122.433596288440668, 37.806480233488799 ], [ -122.433569014197744, 37.806496475378673 ], [ -122.433549712637699, 37.806486490919518 ], [ -122.433503136480809, 37.806492748337497 ], [ -122.433477574676203, 37.806508275404134 ], [ -122.433458272433256, 37.806498290941384 ], [ -122.433404793122563, 37.806505348218124 ], [ -122.43337751882855, 37.806521590063653 ], [ -122.433358216941642, 37.806511605578869 ], [ -122.4333047372658, 37.806518662815925 ], [ -122.433279192939949, 37.806534876279215 ], [ -122.433259891408895, 37.80652489177271 ], [ -122.433206393839583, 37.806531262531053 ], [ -122.433179119492635, 37.806547504330624 ], [ -122.433159817625679, 37.806537519813411 ], [ -122.43309943408164, 37.806545377123399 ], [ -122.43307215969898, 37.806561618623626 ], [ -122.433052858195936, 37.806551634357852 ], [ -122.432990744619175, 37.806559519418741 ], [ -122.432963470221836, 37.806575761442943 ], [ -122.43294416836936, 37.80656577661594 ], [ -122.432885515114464, 37.806573605181946 ], [ -122.432858276424469, 37.806591219775477 ], [ -122.432851977723161, 37.806548745928424 ], [ -122.432876452705358, 37.806557958985486 ], [ -122.432950642939176, 37.806548502597778 ], [ -122.432958937194059, 37.806534631840726 ], [ -122.432985142526235, 37.806543816515223 ], [ -122.433055872042729, 37.806534416507489 ], [ -122.433064219557167, 37.806522605325611 ], [ -122.433088659156226, 37.806530445464773 ], [ -122.43316111864155, 37.80652101703766 ], [ -122.433169413207281, 37.806507146534614 ], [ -122.433195618216885, 37.806516331442637 ], [ -122.433259444532922, 37.80650773092048 ], [ -122.433269469079946, 37.806493832326176 ], [ -122.433293944088703, 37.806503045022133 ], [ -122.433357787918965, 37.806495131161043 ], [ -122.433366082106133, 37.806481260924372 ], [ -122.433392287462695, 37.806490445233919 ], [ -122.433454400931879, 37.806482559686899 ], [ -122.433462695099607, 37.806468689443392 ], [ -122.433488900463672, 37.806477873731517 ], [ -122.433552726373762, 37.806469273330841 ], [ -122.433561038053369, 37.806456089520083 ], [ -122.433587243771214, 37.806465273780709 ], [ -122.433649339320041, 37.806456701696959 ], [ -122.43365765098109, 37.806443517879387 ], [ -122.433683838827505, 37.806452015684521 ], [ -122.433745952233608, 37.806444129983937 ], [ -122.433754245996482, 37.806430259725488 ], [ -122.43378045172912, 37.806439443943233 ], [ -122.433842565114432, 37.806431558191733 ], [ -122.433850858857866, 37.806417687926462 ], [ -122.433877064598036, 37.806426872122742 ], [ -122.433932274491454, 37.806419786251801 ], [ -122.433954393037553, 37.806405002549944 ], [ -122.433958408377592, 37.806426224974288 ], [ -122.43399121327684, 37.806422940114579 ], [ -122.433989124957407, 37.806409239815579 ], [ -122.433988785204093, 37.806396197569725 ], [ -122.433991530954188, 37.80636868345389 ], [ -122.433981985185667, 37.806334503629429 ], [ -122.433942822534163, 37.806226642930824 ], [ -122.433950330141514, 37.806182569274938 ], [ -122.433940283395017, 37.806129169298679 ], [ -122.433873424076822, 37.805821925113698 ], [ -122.433853509955952, 37.80572198947479 ], [ -122.433844483207295, 37.805707716218421 ], [ -122.433823004146618, 37.805680599328511 ], [ -122.433808821860836, 37.805667784061214 ], [ -122.433796405313814, 37.805656313293774 ], [ -122.433780528829729, 37.805644899532474 ], [ -122.433766418068998, 37.805634829721896 ], [ -122.43374886455932, 37.805625503361618 ], [ -122.433731328933206, 37.805616863432853 ], [ -122.433713811190202, 37.805608909935621 ], [ -122.433675422356743, 37.805597178553839 ], [ -122.433656263344702, 37.805592685320363 ], [ -122.433637140790879, 37.805589565215314 ], [ -122.433616323330028, 37.805587846350818 ], [ -122.43359552375523, 37.805586814191379 ], [ -122.433576472363242, 37.805586439544221 ], [ -122.433535016220674, 37.805589866131839 ], [ -122.433516072105618, 37.805593610629053 ], [ -122.433498858293476, 37.805597326206154 ], [ -122.432719648836795, 37.805721348282255 ], [ -122.432704236424613, 37.805727781678655 ], [ -122.432688842557212, 37.805734900946824 ], [ -122.432673484086777, 37.805743393361759 ], [ -122.432659891674504, 37.805753230287664 ], [ -122.432648029581884, 37.805763038856455 ], [ -122.432637933547895, 37.805774191937303 ], [ -122.432627855379891, 37.805786031451547 ], [ -122.432619525401748, 37.805798529044132 ], [ -122.432612943621251, 37.805811684990026 ], [ -122.432606379692928, 37.805825526820627 ], [ -122.432603276080599, 37.805839312496055 ], [ -122.432600137407348, 37.805851724742368 ], [ -122.432500317433352, 37.805940575058216 ], [ -122.432391913920043, 37.805965699763121 ], [ -122.432397426855275, 37.805977970519415 ], [ -122.432516390332239, 37.805959539768239 ], [ -122.432628376560629, 37.805872550799698 ], [ -122.432642540805432, 37.805884679499613 ], [ -122.432527165744716, 37.805974471194816 ], [ -122.432403189584164, 37.805999851361079 ], [ -122.43238951051228, 37.806072868560939 ], [ -122.432404443045669, 37.806114514237365 ], [ -122.432490452847844, 37.806359639916195 ], [ -122.43244910342851, 37.806367184726966 ], [ -122.432520431119073, 37.806580274784345 ], [ -122.432498061398036, 37.806585448423995 ], [ -122.432424985907915, 37.806371700265615 ], [ -122.432400868391511, 37.806376216073893 ], [ -122.432301460176419, 37.806081865240721 ], [ -122.43236869294482, 37.806071149476409 ], [ -122.432380802509087, 37.806004338539104 ], [ -122.43235326017701, 37.806010283635004 ], [ -122.432347175872593, 37.805976047248159 ], [ -122.432319651407369, 37.805982678495994 ], [ -122.432302633632929, 37.805993944955773 ], [ -122.432282173360747, 37.806005686719168 ], [ -122.432280461289963, 37.806006669320666 ], [ -122.432265120589491, 37.806015848390899 ], [ -122.432249654811855, 37.806020221869829 ], [ -122.432227231644546, 37.806023336155739 ], [ -122.432194784491401, 37.806040349205666 ], [ -122.432179693529363, 37.806059138076137 ], [ -122.432171560304809, 37.806079186408191 ], [ -122.432165157068255, 37.806099206671476 ], [ -122.432169117939694, 37.806118369860684 ], [ -122.432192469776069, 37.806150950175152 ], [ -122.432209844223067, 37.806153412703416 ], [ -122.432227007583123, 37.806214250207013 ], [ -122.432213468614208, 37.806226146108848 ], [ -122.432203354593938, 37.806236612991817 ], [ -122.432202178080502, 37.806257920803567 ], [ -122.43220649593593, 37.806290812956746 ], [ -122.432221571326565, 37.806337949854225 ], [ -122.432306430556167, 37.806804907302421 ], [ -122.432383452345448, 37.807103744994606 ], [ -122.432564682589728, 37.808479722911237 ], [ -122.432615131844727, 37.808489197136744 ], [ -122.432610261989339, 37.808501638018015 ], [ -122.432598381927761, 37.808510760137189 ], [ -122.432584628175405, 37.80851441914389 ], [ -122.432342320320956, 37.808516329227118 ], [ -122.432078432185492, 37.807223425689031 ], [ -122.431999351719028, 37.807178023752513 ], [ -122.431443837686672, 37.8072509882197 ], [ -122.431386630568454, 37.807314417209206 ], [ -122.431727121174518, 37.8090235974717 ], [ -122.431341938476805, 37.809056687748011 ], [ -122.430994999022246, 37.807365467556572 ], [ -122.430915919059899, 37.807320064620313 ], [ -122.430360402907354, 37.807393024257635 ], [ -122.430303194747026, 37.8074564524454 ], [ -122.43062118366457, 37.809166690185819 ], [ -122.430108306409053, 37.8092156017048 ], [ -122.429835000067499, 37.807825332620489 ], [ -122.429264030219372, 37.807903346686288 ], [ -122.429270969868725, 37.807970532619663 ], [ -122.428236680346103, 37.808405663457563 ], [ -122.427001622006514, 37.808109268493716 ], [ -122.426995537909036, 37.808141644021028 ], [ -122.427545144293163, 37.808640840008444 ], [ -122.428014153033857, 37.808569308327186 ], [ -122.428043639051282, 37.808704798333089 ], [ -122.427533242566099, 37.808782500069931 ], [ -122.427523467767188, 37.808739396087624 ], [ -122.427301030953672, 37.808773247159571 ], [ -122.427287243070779, 37.808708920235645 ], [ -122.427490716316399, 37.808678126013014 ], [ -122.426909232318224, 37.808151294840016 ], [ -122.426839001555635, 37.808113298884024 ], [ -122.426613230594512, 37.808152010516238 ], [ -122.426560904719622, 37.807943556174827 ], [ -122.426488519796493, 37.807651861239542 ], [ -122.426357658303132, 37.807388412718332 ], [ -122.426257507799036, 37.807185417079722 ], [ -122.426066893674061, 37.80699787612533 ], [ -122.4257796482039, 37.806715259689653 ], [ -122.425627533230582, 37.806456787332273 ], [ -122.425531817297653, 37.806150610629167 ], [ -122.425487611207828, 37.806009959663712 ], [ -122.425486832357592, 37.806006120121552 ], [ -122.425298563577385, 37.80507790289635 ], [ -122.425204919000421, 37.804616199985951 ], [ -122.425109521046082, 37.804145845932808 ], [ -122.424918804152185, 37.803205508171892 ], [ -122.424808684502352, 37.802662664019884 ], [ -122.424731259133807, 37.802280983275736 ], [ -122.424731635083759, 37.802281009278651 ], [ -122.424538324517613, 37.801303719934985 ], [ -122.424517597070277, 37.801201189595353 ], [ -122.424358667137113, 37.800415027771606 ], [ -122.424242907462499, 37.799842398528341 ], [ -122.42417056511286, 37.799484537126212 ], [ -122.42417056430692, 37.799484532744323 ], [ -122.423982145098208, 37.798552453684835 ], [ -122.423982396975646, 37.798552421556877 ], [ -122.423793785438747, 37.7976164776058 ], [ -122.423792964234153, 37.797616581376253 ], [ -122.423601296960271, 37.796691058659476 ], [ -122.42360129621845, 37.796691056748735 ], [ -122.423601876988513, 37.796690945913937 ], [ -122.423601877370103, 37.796690947281171 ], [ -122.425249874473522, 37.79648130315109 ], [ -122.426892728667355, 37.796272251947869 ], [ -122.428537813517494, 37.796062892623418 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.431827832563371, 37.79564412085616 ], [ -122.433469062960725, 37.795435179810816 ], [ -122.435113289920963, 37.795225833115033 ], [ -122.436760975027326, 37.795016021968365 ], [ -122.438402484723895, 37.794806973138577 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":7,\"ZIP_CODE\":94133,\"ID\":94133},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.418530631419571, 37.804999043218864 ], [ -122.418718265666911, 37.805931520041717 ], [ -122.418781807492508, 37.806241018338142 ], [ -122.418781854642702, 37.806241248585302 ], [ -122.418856183917853, 37.806607498073362 ], [ -122.418908043871269, 37.806863030947284 ], [ -122.419086067910683, 37.807803117635643 ], [ -122.419223966222532, 37.808453096037454 ], [ -122.419103158585543, 37.808467422639801 ], [ -122.419122467317635, 37.808544708340989 ], [ -122.418980965776527, 37.808562118993599 ], [ -122.418965099874526, 37.808484090255142 ], [ -122.417886530686005, 37.80861494396968 ], [ -122.417923033396889, 37.808821741173873 ], [ -122.417843612318336, 37.80882989992265 ], [ -122.417811191268285, 37.808647071958433 ], [ -122.417726330574766, 37.808645704666795 ], [ -122.417731886802656, 37.808592736810425 ], [ -122.417624531730937, 37.808591735208473 ], [ -122.417601699719071, 37.808579058949235 ], [ -122.41748929463067, 37.808583633129054 ], [ -122.417485092698755, 37.808420948020817 ], [ -122.417586902421718, 37.808408305143338 ], [ -122.417575917233663, 37.808385134844094 ], [ -122.417669145522396, 37.808375378317514 ], [ -122.41765313920358, 37.808291858459306 ], [ -122.41737529132557, 37.808325217992042 ], [ -122.417344584298249, 37.808141675039998 ], [ -122.416347061956941, 37.808260209523006 ], [ -122.416354024690548, 37.808328768820694 ], [ -122.416305697821443, 37.808334361125667 ], [ -122.416338782629509, 37.80847597548388 ], [ -122.41718410046191, 37.808496573566089 ], [ -122.417183326975007, 37.808533669258267 ], [ -122.416327431803367, 37.808505689336052 ], [ -122.416281962820022, 37.808487886292838 ], [ -122.416150789583881, 37.80850306555427 ], [ -122.416144807019506, 37.808539558863203 ], [ -122.416207242955323, 37.808544038257011 ], [ -122.416187547333237, 37.808854071166344 ], [ -122.41742892570683, 37.808927290365972 ], [ -122.417412600540189, 37.80916653558441 ], [ -122.418193962887401, 37.809680962781762 ], [ -122.41929170565983, 37.810403664792204 ], [ -122.41931534577914, 37.810380618458765 ], [ -122.419422885954745, 37.810454266609824 ], [ -122.419833484960733, 37.810735462513406 ], [ -122.419807989843818, 37.810753732347422 ], [ -122.420595101766708, 37.811266948659416 ], [ -122.420353825913267, 37.811509857466909 ], [ -122.420001661760068, 37.81128073132367 ], [ -122.419845148459302, 37.811320361929653 ], [ -122.419951835473171, 37.811563099184333 ], [ -122.419915762044113, 37.811573987538154 ], [ -122.419724362099529, 37.811134852199615 ], [ -122.41972295750638, 37.81113418613959 ], [ -122.419216088646508, 37.810893843097361 ], [ -122.419185739151644, 37.810925239548666 ], [ -122.419097184597589, 37.810915006165231 ], [ -122.418987802638568, 37.810835752698928 ], [ -122.418935347953351, 37.810882616404598 ], [ -122.419125945102621, 37.811023040645175 ], [ -122.419048154317267, 37.811094352280243 ], [ -122.419465867632354, 37.811381473037407 ], [ -122.419350858543552, 37.811486353332427 ], [ -122.418846744437559, 37.811473267417028 ], [ -122.416831664498375, 37.810133272229294 ], [ -122.415352404138332, 37.809160867759147 ], [ -122.415205531523597, 37.809171493557926 ], [ -122.415205867990196, 37.809184535850846 ], [ -122.415200978155667, 37.80919628956385 ], [ -122.415187329670815, 37.809204065160294 ], [ -122.415151663626474, 37.809230739831797 ], [ -122.415171052667063, 37.809244159741915 ], [ -122.415194871121983, 37.809227978313764 ], [ -122.415413019588954, 37.809363153673537 ], [ -122.415370856947149, 37.809406415569271 ], [ -122.415142166917732, 37.809265230241984 ], [ -122.415155744585249, 37.809254708904987 ], [ -122.415139781310558, 37.809239860465802 ], [ -122.415039713249854, 37.809185860695372 ], [ -122.41488880052961, 37.80924118848273 ], [ -122.414968316086458, 37.80930376277346 ], [ -122.414992913705149, 37.809317784591791 ], [ -122.415006420575594, 37.809304517526144 ], [ -122.415222785388849, 37.809437662297633 ], [ -122.415180622939999, 37.809480924119129 ], [ -122.41496425806919, 37.809347778996823 ], [ -122.414981136896941, 37.8093310235652 ], [ -122.41495316698277, 37.809320490118694 ], [ -122.414845930117963, 37.809256992445782 ], [ -122.414780776445994, 37.809281398852669 ], [ -122.414774461081151, 37.809372149219335 ], [ -122.415340036749569, 37.80975508488244 ], [ -122.415288518799699, 37.809838328383556 ], [ -122.41456302792659, 37.80956443044294 ], [ -122.414023978762671, 37.809471544827012 ], [ -122.413420668373689, 37.809236173514122 ], [ -122.41337430449579, 37.809317959368158 ], [ -122.413395600212141, 37.809338215320878 ], [ -122.413411510435665, 37.809351004954635 ], [ -122.413425672211702, 37.809363136228626 ], [ -122.413441564407123, 37.809375239427176 ], [ -122.413455726199771, 37.809387370972054 ], [ -122.413471618398191, 37.809399473891929 ], [ -122.413487492569899, 37.80941089065314 ], [ -122.413505097854454, 37.809422279326348 ], [ -122.413520972382244, 37.809433696077356 ], [ -122.413538559637843, 37.80944439831444 ], [ -122.413554416481929, 37.809455128624236 ], [ -122.413572003401356, 37.809465830861896 ], [ -122.413607142559428, 37.809485862444731 ], [ -122.413624694804525, 37.809495192064489 ], [ -122.414282035042135, 37.809946003161961 ], [ -122.414200903764723, 37.81002217256772 ], [ -122.414027337823839, 37.810138985552911 ], [ -122.413780738047123, 37.809907441000853 ], [ -122.413857049972947, 37.809845771015681 ], [ -122.412616832608151, 37.809078904365926 ], [ -122.412498775807535, 37.80906571088461 ], [ -122.412398060995869, 37.809120908451149 ], [ -122.412400109349747, 37.809133236262518 ], [ -122.412396967167027, 37.809145648237823 ], [ -122.412390346141294, 37.809157430172931 ], [ -122.412380210884578, 37.809167208369729 ], [ -122.412426492762847, 37.809216588736774 ], [ -122.412450629760414, 37.809212763732177 ], [ -122.41262839277087, 37.809393236325491 ], [ -122.412573868329858, 37.809427083257212 ], [ -122.412396105370902, 37.809246610582868 ], [ -122.412412861666652, 37.809225050449037 ], [ -122.412356250722411, 37.809177897728922 ], [ -122.41234073008107, 37.809180209833173 ], [ -122.412323390603305, 37.809179117259049 ], [ -122.412307675451856, 37.809173878278465 ], [ -122.412099340782589, 37.809285071655154 ], [ -122.412099676704088, 37.809298113956672 ], [ -122.412096516784118, 37.809309839486673 ], [ -122.412089895696084, 37.809321621404543 ], [ -122.412078012629308, 37.8093307411864 ], [ -122.412124294062423, 37.809380121676121 ], [ -122.412148413801376, 37.809375610565525 ], [ -122.412315935400727, 37.809561743172587 ], [ -122.412252793608843, 37.809597103119742 ], [ -122.412093889154491, 37.809409457275514 ], [ -122.41211066290181, 37.809388583353197 ], [ -122.412057512853792, 37.809341374382768 ], [ -122.412040244067796, 37.809343027789311 ], [ -122.412024634631933, 37.809341907398313 ], [ -122.412008919813204, 37.809336668372488 ], [ -122.411894272206808, 37.809388657747697 ], [ -122.412861765933144, 37.810522545447967 ], [ -122.412718336607824, 37.810599724430624 ], [ -122.412705816724809, 37.810584132828346 ], [ -122.411509089676429, 37.81123738012537 ], [ -122.41148957739901, 37.811219154555417 ], [ -122.412686215557258, 37.810562475550952 ], [ -122.411452462026304, 37.809105334882879 ], [ -122.411423380532113, 37.809118854220145 ], [ -122.411248622782082, 37.808920476073887 ], [ -122.411085557936261, 37.808907323817536 ], [ -122.411283571142818, 37.809134854169606 ], [ -122.411256255700124, 37.809149718017444 ], [ -122.411031438641331, 37.808889659210472 ], [ -122.410955336187001, 37.808892265747417 ], [ -122.410765573350616, 37.808850703243849 ], [ -122.410736033164511, 37.808846374759867 ], [ -122.410731512966407, 37.808872543480319 ], [ -122.410649866328171, 37.808861505447226 ], [ -122.410652655797975, 37.808835364493682 ], [ -122.410546641899074, 37.808819227353638 ], [ -122.410441958004114, 37.809325664485378 ], [ -122.410406413493931, 37.809289843904054 ], [ -122.410401787512413, 37.809311893981061 ], [ -122.410630331289113, 37.809716792194763 ], [ -122.410709259423612, 37.809689418065965 ], [ -122.410633083668301, 37.809554681184579 ], [ -122.41064851611452, 37.809548937394226 ], [ -122.410729547478269, 37.809670547859675 ], [ -122.410757004560026, 37.809661175631867 ], [ -122.410712947997226, 37.809563688219221 ], [ -122.410728380093076, 37.809557944424022 ], [ -122.410791312651469, 37.80964894551029 ], [ -122.410822177556227, 37.809637457886339 ], [ -122.410778085579025, 37.80953859734884 ], [ -122.410793518010678, 37.809532853539501 ], [ -122.410858216047515, 37.809625199709956 ], [ -122.410889098254941, 37.809614398511123 ], [ -122.410843240792332, 37.809514193154044 ], [ -122.410858672867818, 37.809508449341749 ], [ -122.410923406302985, 37.809602168351013 ], [ -122.410950845665482, 37.809592109640889 ], [ -122.410904952795121, 37.809490531433248 ], [ -122.410920384860916, 37.809484787612853 ], [ -122.410985153693048, 37.809579879462774 ], [ -122.411024617975485, 37.809566192280315 ], [ -122.410978689334627, 37.809463241232329 ], [ -122.410994104065225, 37.809456810959603 ], [ -122.411058925978935, 37.809553962080649 ], [ -122.411089808473605, 37.809543160823544 ], [ -122.411042113998747, 37.809438864964896 ], [ -122.411057546043196, 37.809433121126496 ], [ -122.411195348876859, 37.80967536169041 ], [ -122.41117818636998, 37.809681133587063 ], [ -122.411116453591873, 37.809569511282646 ], [ -122.41104955067469, 37.80959325722408 ], [ -122.41111476155065, 37.809705510201134 ], [ -122.411097598684464, 37.809711282091641 ], [ -122.411034100233891, 37.80959831463543 ], [ -122.410977509113948, 37.809619146556997 ], [ -122.411041007143098, 37.809732113774253 ], [ -122.411025592359422, 37.809738544052792 ], [ -122.410960346257056, 37.809624918427609 ], [ -122.410907162593759, 37.809643635209717 ], [ -122.410972426300958, 37.809757947300923 ], [ -122.410956993836351, 37.80976369113344 ], [ -122.410891730147881, 37.809649379033807 ], [ -122.410835138249837, 37.809670210623501 ], [ -122.410900419538606, 37.809785209191027 ], [ -122.410884987408849, 37.809790953008481 ], [ -122.410819706138938, 37.809675954432528 ], [ -122.410764809648242, 37.809695385622547 ], [ -122.410830108511632, 37.809811070390921 ], [ -122.41081467602497, 37.809816814204737 ], [ -122.410747646757216, 37.80970115746215 ], [ -122.410639354408318, 37.809731067210933 ], [ -122.410896947885561, 37.810188372007936 ], [ -122.410975841429376, 37.810159624823598 ], [ -122.410901536485113, 37.810030351311333 ], [ -122.410916969009435, 37.810024607486383 ], [ -122.410997965998874, 37.810144845157168 ], [ -122.41103912533255, 37.810129757339681 ], [ -122.410977003992286, 37.810003033077805 ], [ -122.410992436505126, 37.809997289242951 ], [ -122.411075146003029, 37.810116812392209 ], [ -122.411112880121649, 37.810103153220581 ], [ -122.411050758345823, 37.809976429551675 ], [ -122.411066208857108, 37.809971371863952 ], [ -122.411147188358683, 37.810090922996501 ], [ -122.411190078057032, 37.810075806811575 ], [ -122.411127956511336, 37.809949082902484 ], [ -122.411143389001253, 37.809943339047805 ], [ -122.411224368596535, 37.81006289012803 ], [ -122.411256964037719, 37.810051374350031 ], [ -122.411194842059288, 37.809924650755768 ], [ -122.41121027488505, 37.809918906886708 ], [ -122.4112929849924, 37.810038429879377 ], [ -122.411332431435511, 37.81002405588923 ], [ -122.411270309344076, 37.809897332059471 ], [ -122.411285742158299, 37.809891588180513 ], [ -122.411366721926342, 37.810011139164168 ], [ -122.411401047752832, 37.809999595302976 ], [ -122.411337195490745, 37.809872899820583 ], [ -122.411354358375661, 37.809867127895387 ], [ -122.411435338227506, 37.809986678832487 ], [ -122.411471359105562, 37.809973733758071 ], [ -122.411409237165202, 37.809847009994684 ], [ -122.411424669612117, 37.809841266103092 ], [ -122.41150566722375, 37.80996150342942 ], [ -122.411546826352634, 37.809946415434261 ], [ -122.411484704299241, 37.809819691435401 ], [ -122.411500136734659, 37.809813947533918 ], [ -122.411582846845619, 37.809933470331323 ], [ -122.4116188680253, 37.809920525480692 ], [ -122.411556745877689, 37.809793801793724 ], [ -122.411572177956074, 37.809788057888397 ], [ -122.411653176094603, 37.809908295109032 ], [ -122.4116909096619, 37.809894636032141 ], [ -122.411628787405917, 37.809767912107915 ], [ -122.411644219819436, 37.809762168187518 ], [ -122.411725217707357, 37.809882405639449 ], [ -122.411759525734439, 37.809870174962185 ], [ -122.411697403388885, 37.809743451348098 ], [ -122.411712835792031, 37.809737707418712 ], [ -122.411881278427131, 37.810026834486933 ], [ -122.41186411554628, 37.81003260648864 ], [ -122.411782586611622, 37.809891776308959 ], [ -122.411719126796399, 37.80991478009306 ], [ -122.411802385721074, 37.810055582271964 ], [ -122.411785222827419, 37.810061354262146 ], [ -122.411703694356945, 37.809920524023092 ], [ -122.411645389734517, 37.809942070760108 ], [ -122.411726900787329, 37.810082214596392 ], [ -122.411711467967621, 37.810087958532876 ], [ -122.411628227200978, 37.809947842721932 ], [ -122.41156305384375, 37.809971560917056 ], [ -122.411644564773539, 37.810111704809465 ], [ -122.411629131941311, 37.810117448735141 ], [ -122.411547621726527, 37.809977304820983 ], [ -122.411485874174801, 37.809999594002001 ], [ -122.411567384297499, 37.810139737958238 ], [ -122.411551952145643, 37.810145481862563 ], [ -122.411470441699677, 37.81000533790143 ], [ -122.411408676427612, 37.810026940604999 ], [ -122.411490204455376, 37.81016777104513 ], [ -122.41147477194562, 37.81017351494495 ], [ -122.411393243940836, 37.810032684494281 ], [ -122.411328070780158, 37.81005640255357 ], [ -122.41140959834128, 37.810197233054254 ], [ -122.411394148491496, 37.81020229050096 ], [ -122.41131090784944, 37.810062174474744 ], [ -122.411249177828495, 37.810085149968437 ], [ -122.411330687944414, 37.810225294080347 ], [ -122.4113152554104, 37.810231037959227 ], [ -122.411232014885002, 37.810090921878086 ], [ -122.411175405434363, 37.810111067735896 ], [ -122.411256933104966, 37.81025189806045 ], [ -122.411241500559697, 37.810257641929645 ], [ -122.411159972912074, 37.810116811594561 ], [ -122.411103380733024, 37.810137643311123 ], [ -122.411184890977466, 37.810277787516789 ], [ -122.411169458075165, 37.810283531382133 ], [ -122.411087948545713, 37.810143387154753 ], [ -122.411017619235537, 37.810168562307894 ], [ -122.411099128998927, 37.810308706303054 ], [ -122.411083696429642, 37.810314450151544 ], [ -122.411004005467134, 37.81017771001823 ], [ -122.410905971459442, 37.810202646996828 ], [ -122.411216101543474, 37.810750434096292 ], [ -122.411307020324173, 37.810718058221234 ], [ -122.411214614556201, 37.8105581756828 ], [ -122.411231794935318, 37.81055309021869 ], [ -122.411325542368957, 37.810697843090367 ], [ -122.411366719609191, 37.810683441322411 ], [ -122.411291830600106, 37.810531515646439 ], [ -122.411307262852333, 37.810525771776568 ], [ -122.411402864908169, 37.810675301597549 ], [ -122.411522935608915, 37.810632152260467 ], [ -122.411446209498621, 37.810476136111049 ], [ -122.411461659747943, 37.810471078652185 ], [ -122.411555407533228, 37.810615831617966 ], [ -122.41160859172949, 37.810597114547022 ], [ -122.411624041992908, 37.810592057067005 ], [ -122.411677226161913, 37.810573340239586 ], [ -122.411600605842565, 37.810421442809904 ], [ -122.411617768833821, 37.810415670846979 ], [ -122.411711534523249, 37.810561109852458 ], [ -122.411738974133357, 37.810551050960818 ], [ -122.411686385419046, 37.810391210096554 ], [ -122.411701817957677, 37.810385466169308 ], [ -122.411764700631707, 37.810541706828857 ], [ -122.411829892102887, 37.810518674961713 ], [ -122.41176529620391, 37.810363148827776 ], [ -122.411780746061879, 37.81035809133261 ], [ -122.411847054752684, 37.810512902971148 ], [ -122.411915671345156, 37.810488441813206 ], [ -122.411840781416544, 37.810336516754752 ], [ -122.411856213585779, 37.81033077281284 ], [ -122.411935941632692, 37.810468884965481 ], [ -122.411921351800245, 37.810305681309181 ], [ -122.411938638145514, 37.810304714362765 ], [ -122.411953405498025, 37.81047478237366 ], [ -122.412071780474818, 37.810433033401743 ], [ -122.412010591984938, 37.810275392256564 ], [ -122.412115642263785, 37.810455670909619 ], [ -122.412100192408317, 37.810460728448561 ], [ -122.412084406280471, 37.810452743692409 ], [ -122.41176878056207, 37.810565675738737 ], [ -122.41175334799118, 37.810571419674538 ], [ -122.411684748938015, 37.810596566899896 ], [ -122.411669298331702, 37.810601624393506 ], [ -122.411598969130708, 37.810626799885483 ], [ -122.411583536533968, 37.810632543799024 ], [ -122.411518344886403, 37.810655575529537 ], [ -122.411502912623547, 37.810661319426885 ], [ -122.411432582949345, 37.810686494275835 ], [ -122.411417150327338, 37.81069223816754 ], [ -122.411223323763636, 37.810761991358582 ], [ -122.41132311146211, 37.810939609065692 ], [ -122.411244573940635, 37.811116682909216 ], [ -122.410949479920689, 37.811220352571489 ], [ -122.410930216342933, 37.811279036146971 ], [ -122.410681455051716, 37.81136684679597 ], [ -122.410613578346386, 37.811352838185535 ], [ -122.410419696661208, 37.811420531005773 ], [ -122.410148367227194, 37.811505958616635 ], [ -122.408511879957302, 37.811283856992631 ], [ -122.407577358643394, 37.808803424612513 ], [ -122.407596251976869, 37.808797625245276 ], [ -122.408535735474004, 37.811269050069917 ], [ -122.410153064417187, 37.811486654300801 ], [ -122.41040703579597, 37.811399447392162 ], [ -122.410397994853795, 37.811384485925792 ], [ -122.410199185064457, 37.81132796106332 ], [ -122.410033026138677, 37.811396577420801 ], [ -122.410028523199003, 37.811423432538689 ], [ -122.408569038736545, 37.811217693403208 ], [ -122.408604939400348, 37.811065346992223 ], [ -122.408622314546491, 37.811067812437848 ], [ -122.408601547208733, 37.811202746444437 ], [ -122.408639776140404, 37.811208308039326 ], [ -122.408681379486623, 37.811075784005041 ], [ -122.408700502750108, 37.811078908147074 ], [ -122.408681466003685, 37.811213813888727 ], [ -122.408723173522517, 37.811220006160561 ], [ -122.408763046269982, 37.811087510102226 ], [ -122.408782169539933, 37.811090634230986 ], [ -122.40876313259281, 37.811225539991135 ], [ -122.408803092361268, 37.811231073796982 ], [ -122.408842964625947, 37.811098577717516 ], [ -122.408862088248199, 37.811101701827724 ], [ -122.408843051796623, 37.811236607869454 ], [ -122.408881280424794, 37.811242169666208 ], [ -122.408921153248102, 37.811109673549396 ], [ -122.408942006980325, 37.811112769370261 ], [ -122.408921240203938, 37.811247703157434 ], [ -122.408964677875915, 37.811253867342053 ], [ -122.409004568208587, 37.811122057634492 ], [ -122.409025404309773, 37.811124467552588 ], [ -122.409006385440833, 37.811260059513721 ], [ -122.409042866672721, 37.811264962818356 ], [ -122.409082738873835, 37.811132466653085 ], [ -122.409101862168811, 37.811135590729982 ], [ -122.409082825791046, 37.81127049654058 ], [ -122.409126264189027, 37.81127666092906 ], [ -122.409166136247578, 37.811144164735886 ], [ -122.409185259542042, 37.811147288524687 ], [ -122.409166223312823, 37.811282194348451 ], [ -122.409211409832309, 37.811289016857089 ], [ -122.409251281745554, 37.811156520635443 ], [ -122.40927213551177, 37.811159616672512 ], [ -122.40925136932222, 37.811294550516585 ], [ -122.409279162626277, 37.81129822094033 ], [ -122.40932078253644, 37.811166383119904 ], [ -122.409339888202226, 37.811168820995697 ], [ -122.409320870242325, 37.811304413000762 ], [ -122.409364308333835, 37.811310577307111 ], [ -122.409405910098016, 37.811178053025465 ], [ -122.409425033757373, 37.811181176769772 ], [ -122.409405998308287, 37.811316082900326 ], [ -122.40944074878638, 37.811321014101964 ], [ -122.40948063795679, 37.811189203966087 ], [ -122.409501474085218, 37.811191613525516 ], [ -122.409480725959895, 37.811327233846249 ], [ -122.409527624648632, 37.811333341792235 ], [ -122.409567496367472, 37.811200845459226 ], [ -122.409588350159311, 37.811203941440354 ], [ -122.40956758453207, 37.81133887533899 ], [ -122.409607544075371, 37.811344408877801 ], [ -122.409647432976385, 37.811212599235269 ], [ -122.409666538650669, 37.811215036508962 ], [ -122.4096475212896, 37.811350629114692 ], [ -122.409687480492039, 37.811356162357413 ], [ -122.409729082397121, 37.811223637951706 ], [ -122.409748205742758, 37.811226761923784 ], [ -122.40972917051603, 37.811361667836366 ], [ -122.40976739996718, 37.811367229334621 ], [ -122.409807270930216, 37.811234732926998 ], [ -122.409826394289098, 37.811237857160975 ], [ -122.409807359540622, 37.811372762805725 ], [ -122.409849067242604, 37.811378954679277 ], [ -122.409888938066231, 37.811246458244362 ], [ -122.409909791884289, 37.81124955416864 ], [ -122.409890757298484, 37.811384460375677 ], [ -122.409934195469972, 37.811390623922954 ], [ -122.409972335687982, 37.811258155482349 ], [ -122.409991388408557, 37.811258533667043 ], [ -122.410051001542016, 37.811355083215751 ], [ -122.410080225111116, 37.811347055723829 ], [ -122.410039452733983, 37.811242647397464 ], [ -122.410060076955276, 37.811236819614464 ], [ -122.410111355764599, 37.81134586484395 ], [ -122.410186754248173, 37.811315801374953 ], [ -122.410002034752523, 37.810999466686859 ], [ -122.410015948336749, 37.810934689402742 ], [ -122.410036236914948, 37.810915819594705 ], [ -122.410045418985376, 37.810868973454134 ], [ -122.40995683043738, 37.810857360334786 ], [ -122.409929090780366, 37.810990347084882 ], [ -122.409911715601922, 37.810987881557118 ], [ -122.409936471227283, 37.810873484930354 ], [ -122.409873927744698, 37.810864883322409 ], [ -122.409849172039969, 37.810979280485419 ], [ -122.409831796859692, 37.810976814671307 ], [ -122.409859730943055, 37.810851378751366 ], [ -122.409793708942289, 37.810842147255194 ], [ -122.409767522509881, 37.810968241308018 ], [ -122.409751878141279, 37.810965747731309 ], [ -122.409778063900148, 37.81083965341854 ], [ -122.409713790367476, 37.810831080014538 ], [ -122.409685856045158, 37.810956515900401 ], [ -122.409668480889763, 37.810954050611365 ], [ -122.409696415233427, 37.810828614454991 ], [ -122.40963387182353, 37.810820012994292 ], [ -122.409609415933446, 37.810946079263672 ], [ -122.409592040775962, 37.810943613688799 ], [ -122.409616496701801, 37.810817547697539 ], [ -122.409552222853762, 37.810808973936524 ], [ -122.409527766835737, 37.810935040463818 ], [ -122.4095103913304, 37.810932574607932 ], [ -122.409534847384165, 37.81080650835878 ], [ -122.409470556252117, 37.810797248385086 ], [ -122.4094443696399, 37.810923342636215 ], [ -122.409426994147125, 37.810920877042648 ], [ -122.409453181134111, 37.810794782789721 ], [ -122.40938717688249, 37.810786237228463 ], [ -122.409360972477401, 37.810911645024227 ], [ -122.409343596990126, 37.810909179418339 ], [ -122.409371532226004, 37.810783743881395 ], [ -122.409307258435504, 37.810775169986833 ], [ -122.409279323454484, 37.810900606052307 ], [ -122.409261948311553, 37.810898140154237 ], [ -122.409289883328043, 37.810772704367388 ], [ -122.409222130673314, 37.810763500569543 ], [ -122.409194195884822, 37.810888936060401 ], [ -122.409178551206097, 37.810886442413199 ], [ -122.409204755910437, 37.810761034657382 ], [ -122.409142194961319, 37.810751746498703 ], [ -122.409114277009948, 37.810877868693844 ], [ -122.409096902238019, 37.810875403314995 ], [ -122.409124837171674, 37.81074996729771 ], [ -122.409062293894237, 37.810741365533815 ], [ -122.409034358851102, 37.810866801261916 ], [ -122.409016983385186, 37.810864335607896 ], [ -122.409044919155974, 37.810738900147257 ], [ -122.408982375890048, 37.810730298066382 ], [ -122.408954440030968, 37.81085573406159 ], [ -122.408937065255401, 37.810853268110044 ], [ -122.408965000457812, 37.810727832404666 ], [ -122.408898979017181, 37.810718600127345 ], [ -122.408871043014514, 37.810844035828495 ], [ -122.408853668258587, 37.8108415704138 ], [ -122.408881603590444, 37.810716134453337 ], [ -122.408815582170575, 37.810706902129297 ], [ -122.408787646031413, 37.810832337810972 ], [ -122.408770288923606, 37.810830558546641 ], [ -122.408798206756401, 37.810704436717558 ], [ -122.408730454910014, 37.810695232351428 ], [ -122.408702518970472, 37.810820667733054 ], [ -122.408686891971527, 37.810818860457857 ], [ -122.408713079833362, 37.810692766372398 ], [ -122.408650536651805, 37.810684164390111 ], [ -122.408622600588473, 37.810809600027646 ], [ -122.408605243143839, 37.810807820744628 ], [ -122.408633161587474, 37.810681698673903 ], [ -122.408556510134844, 37.810663024107164 ], [ -122.408588984612706, 37.810512106294745 ], [ -122.408606359289806, 37.810514572023159 ], [ -122.408587340511048, 37.810650164195629 ], [ -122.408629047705375, 37.810656356501411 ], [ -122.408668902323384, 37.810523174029143 ], [ -122.408686277704945, 37.81052564000916 ], [ -122.40866552792356, 37.81066125993452 ], [ -122.408708965930458, 37.810667424477344 ], [ -122.408748820750048, 37.810534241698186 ], [ -122.40876619543765, 37.81053670740306 ], [ -122.40874717695219, 37.810672299875314 ], [ -122.408794093155748, 37.810679094270796 ], [ -122.408833947835987, 37.810545911737734 ], [ -122.408849592085303, 37.810548405436371 ], [ -122.40883057374208, 37.810683997647232 ], [ -122.408875741869977, 37.810690133853562 ], [ -122.408915614060632, 37.81055763773044 ], [ -122.408932971115192, 37.810559417248072 ], [ -122.408913970565308, 37.81069569563472 ], [ -122.408960869161646, 37.810701804075741 ], [ -122.409000741206668, 37.810569307924148 ], [ -122.409018098597954, 37.810571086874475 ], [ -122.40899736742189, 37.810707393837795 ], [ -122.409040787124908, 37.810712871283229 ], [ -122.409080641733894, 37.810579688936649 ], [ -122.409096286337629, 37.810582182322406 ], [ -122.409077268090201, 37.810717774577761 ], [ -122.40911897536337, 37.810723966710214 ], [ -122.409158829484909, 37.81059078406853 ], [ -122.409176204199355, 37.810593249712966 ], [ -122.409157186441121, 37.810728841975312 ], [ -122.409200623829136, 37.810735006073635 ], [ -122.409240478156264, 37.81060182339904 ], [ -122.409257853229136, 37.810604289300421 ], [ -122.409238852918037, 37.810740567743849 ], [ -122.409285751569428, 37.810746676055665 ], [ -122.409325605404021, 37.810613493358183 ], [ -122.409341250023104, 37.81061598671144 ], [ -122.40932224986912, 37.810752265442567 ], [ -122.4093656699755, 37.81075774303735 ], [ -122.40940554098205, 37.810625246755905 ], [ -122.409422898749284, 37.810627025915643 ], [ -122.409403898742326, 37.810763304659638 ], [ -122.409447336517104, 37.810769468661483 ], [ -122.40948544197218, 37.810635627488516 ], [ -122.409502816715019, 37.810638093359387 ], [ -122.409485547293727, 37.810774343825763 ], [ -122.409528985434875, 37.8107805080665 ], [ -122.409565359949454, 37.810646694615322 ], [ -122.409582735382628, 37.810649160188639 ], [ -122.409567196568673, 37.810785383198734 ], [ -122.409612364812048, 37.81079151884834 ], [ -122.409648756853613, 37.810658392357979 ], [ -122.409666132285238, 37.810660857644436 ], [ -122.409648863165444, 37.8107971084087 ], [ -122.409690552872675, 37.810802613901892 ], [ -122.409730423669671, 37.810670117506156 ], [ -122.409747781109076, 37.81067189662366 ], [ -122.409728781340874, 37.810808175424455 ], [ -122.409772201856015, 37.810813652863935 ], [ -122.409810324407417, 37.810680498024219 ], [ -122.409827699517621, 37.810682963841622 ], [ -122.409810430682086, 37.810819214354417 ], [ -122.4098555989803, 37.810825350459979 ], [ -122.409895451834942, 37.810692167571958 ], [ -122.409912826590485, 37.810694632833282 ], [ -122.409895222374288, 37.810817841328777 ], [ -122.409936876790525, 37.81082197386079 ], [ -122.409973639826163, 37.810703262163521 ], [ -122.409991014940061, 37.810705727682269 ], [ -122.409959938125212, 37.81084357554878 ], [ -122.410048526318235, 37.810855188946014 ], [ -122.41005622518567, 37.810817980946432 ], [ -122.410043511556253, 37.810794838529432 ], [ -122.410054194116455, 37.810739040961913 ], [ -122.410078049482479, 37.810724233171044 ], [ -122.410094488114893, 37.810623018930904 ], [ -122.410081703845862, 37.810597130219826 ], [ -122.410092509988729, 37.810546137980772 ], [ -122.410117989755761, 37.810527183534326 ], [ -122.410136070412563, 37.810422508799753 ], [ -122.410119930917134, 37.810400795044814 ], [ -122.41012762970874, 37.810363587311961 ], [ -122.410156217048225, 37.810330848613937 ], [ -122.41017620465729, 37.81023300994017 ], [ -122.410158299784257, 37.810209951610261 ], [ -122.410179752853338, 37.810101788351538 ], [ -122.410076402542387, 37.809920107344034 ], [ -122.410041882266327, 37.809924099736733 ], [ -122.410019086454341, 37.809912794612728 ], [ -122.409979357246016, 37.809916185169463 ], [ -122.409958274206915, 37.809904165299031 ], [ -122.409922023510461, 37.809908185953802 ], [ -122.409899227375504, 37.80989688081219 ], [ -122.409856019993072, 37.80989964065347 ], [ -122.409833224216882, 37.809888335493483 ], [ -122.409796955869524, 37.80989166994722 ], [ -122.409774159751635, 37.809880364506853 ], [ -122.40973616131653, 37.809883726681605 ], [ -122.409715095991231, 37.809872393479395 ], [ -122.409673618697951, 37.809875125240453 ], [ -122.409650822957047, 37.809863820045173 ], [ -122.409611093758144, 37.809867210477904 ], [ -122.409590028449543, 37.809855876978752 ], [ -122.409552029326491, 37.809859239105428 ], [ -122.40952923395507, 37.809847933880995 ], [ -122.409489504405954, 37.809851324003851 ], [ -122.409466709046484, 37.809840018767318 ], [ -122.409426961850301, 37.809842722706414 ], [ -122.409405896576772, 37.809831389174342 ], [ -122.409362689214092, 37.809834148834945 ], [ -122.409339893540192, 37.80982284385405 ], [ -122.40930537326976, 37.809826836031277 ], [ -122.409282577599768, 37.809815530764716 ], [ -122.409239369556644, 37.809818290665945 ], [ -122.409216574238357, 37.809806985106434 ], [ -122.409183784056154, 37.809810949516532 ], [ -122.409160988755644, 37.809799644220845 ], [ -122.409117781056253, 37.80980240379737 ], [ -122.409096715848435, 37.809791070484557 ], [ -122.409062177941692, 37.809794376702477 ], [ -122.40903938231871, 37.80978307138885 ], [ -122.409001401198537, 37.80978711949534 ], [ -122.408980336012803, 37.809775786161715 ], [ -122.408940588826241, 37.809778489662385 ], [ -122.408917793572726, 37.809767184319618 ], [ -122.408872855464537, 37.809769972088361 ], [ -122.408850059531787, 37.809758666743676 ], [ -122.408812061110055, 37.80976202834627 ], [ -122.408789265535077, 37.809750722984248 ], [ -122.40874778827569, 37.809753454419798 ], [ -122.408724992367041, 37.80974214905094 ], [ -122.408688724387432, 37.809745483158423 ], [ -122.408665928836285, 37.809734177772519 ], [ -122.408627929726592, 37.809737539327024 ], [ -122.408605134533261, 37.809726233923747 ], [ -122.40856886620287, 37.809729567725441 ], [ -122.408546070674888, 37.809718262316345 ], [ -122.408502862660981, 37.80972102194783 ], [ -122.408480067491766, 37.809709716520373 ], [ -122.408442068732256, 37.809713078009395 ], [ -122.408419272882526, 37.809701772581377 ], [ -122.40838475263304, 37.809705765038423 ], [ -122.408363687557454, 37.809694431319926 ], [ -122.408318732173385, 37.809696532434543 ], [ -122.408295936693619, 37.809685226977031 ], [ -122.408254459445629, 37.809687957964584 ], [ -122.408238053815026, 37.809790545390122 ], [ -122.40821721811372, 37.809788135332319 ], [ -122.408256439046454, 37.809562942360742 ], [ -122.408275562248491, 37.809566066566916 ], [ -122.408269450984477, 37.809665053549438 ], [ -122.408286825452777, 37.809667519325309 ], [ -122.40832766898086, 37.809572777430049 ], [ -122.408321557439706, 37.809671764420713 ], [ -122.408342410750109, 37.809674860622998 ], [ -122.408383254218379, 37.809580118983206 ], [ -122.408377142750709, 37.809679105976613 ], [ -122.408394517910963, 37.809681571450852 ], [ -122.408435360963296, 37.809586829524143 ], [ -122.408429249910625, 37.809685816514552 ], [ -122.408451833660507, 37.80968888497187 ], [ -122.408492676297627, 37.809594143031056 ], [ -122.408484852889558, 37.809693844457087 ], [ -122.408503958134872, 37.809696281919784 ], [ -122.408544801055655, 37.809601539955445 ], [ -122.408536959377372, 37.809700554958617 ], [ -122.408557795064482, 37.809702964959982 ], [ -122.408598638259278, 37.809608222696959 ], [ -122.408592544731249, 37.809707896412988 ], [ -122.408615128826568, 37.809710964284278 ], [ -122.40865597161357, 37.809616222281704 ], [ -122.408639231230907, 37.809705767179551 ], [ -122.408663757193779, 37.809717044567101 ], [ -122.408704599922103, 37.80962230254778 ], [ -122.408696758454738, 37.809721317561177 ], [ -122.408722821063833, 37.809725016115578 ], [ -122.408763663375069, 37.80963027408157 ], [ -122.408757553102589, 37.809729261082566 ], [ -122.408776676029532, 37.8097323854876 ], [ -122.408817518615137, 37.809637643154893 ], [ -122.408811408067763, 37.809736630164153 ], [ -122.408839165466787, 37.809738927817939 ], [ -122.408880043284086, 37.809645558612985 ], [ -122.408873932819418, 37.809744545625371 ], [ -122.40889130733342, 37.809747011586715 ], [ -122.408932150126873, 37.809652269208989 ], [ -122.408926057042962, 37.809751942941425 ], [ -122.408946892742989, 37.809754352324887 ], [ -122.408987735476614, 37.809659610202615 ], [ -122.408979894728986, 37.80975862522854 ], [ -122.409002478177683, 37.809761693586005 ], [ -122.409043320837327, 37.809666951170023 ], [ -122.40903548016307, 37.809765966199507 ], [ -122.409052855026459, 37.809768431856902 ], [ -122.409093697286551, 37.809673689703786 ], [ -122.409085856332652, 37.809772704742095 ], [ -122.409106709703295, 37.809775800809213 ], [ -122.409147552244775, 37.809681058631973 ], [ -122.409141441787753, 37.809780045663132 ], [ -122.409162295162758, 37.809783141720409 ], [ -122.409203137637348, 37.809688399524063 ], [ -122.409195297173937, 37.809787414563765 ], [ -122.409214402473708, 37.809789852185943 ], [ -122.409255244539409, 37.809695109977277 ], [ -122.409249134570899, 37.809794097008144 ], [ -122.409268257528154, 37.809797221058851 ], [ -122.409309099536273, 37.809702479106257 ], [ -122.40930298963896, 37.809801466139774 ], [ -122.409325573445983, 37.809804533880602 ], [ -122.409366415377917, 37.809709791633715 ], [ -122.409360305556405, 37.809808778670053 ], [ -122.409379410874138, 37.809811216540062 ], [ -122.409420252741057, 37.809716474274637 ], [ -122.409412429873498, 37.809816175771125 ], [ -122.409431535526792, 37.809818613077915 ], [ -122.409472376985178, 37.809723870800191 ], [ -122.409466267649847, 37.80982285783616 ], [ -122.409488851138192, 37.809825926100473 ], [ -122.409529693219639, 37.809731183791826 ], [ -122.409521853187186, 37.809830198852438 ], [ -122.409544436665897, 37.809833266556943 ], [ -122.409585278680453, 37.809738524229203 ], [ -122.409577438721357, 37.809837539293362 ], [ -122.409600022218754, 37.809840607536366 ], [ -122.409640864166391, 37.809745865189505 ], [ -122.409624124935263, 37.809835409950374 ], [ -122.409648668402042, 37.809847373575664 ], [ -122.409694718878129, 37.809753233598798 ], [ -122.409683400550946, 37.809851618269946 ], [ -122.40970946326199, 37.809855316880828 ], [ -122.409750304385923, 37.809760574507528 ], [ -122.409744195418114, 37.809859561557211 ], [ -122.409763300747386, 37.809861998815862 ], [ -122.409800681301817, 37.809767312459051 ], [ -122.409798050568668, 37.809866929913561 ], [ -122.409813677394268, 37.809868737041889 ], [ -122.409859727674416, 37.809774597001002 ], [ -122.409848409562613, 37.809872981687576 ], [ -122.409872741508423, 37.809876708014343 ], [ -122.409896207540612, 37.809779500054304 ], [ -122.409902917913584, 37.809838449581335 ], [ -122.409927956256155, 37.809869633383848 ], [ -122.410058214833967, 37.809886065459693 ], [ -122.40978805552237, 37.809411107578008 ], [ -122.409692176324654, 37.809452490025258 ], [ -122.409753394315103, 37.809476907533615 ], [ -122.409795083288799, 37.80948241299123 ], [ -122.409790439454952, 37.809503776606206 ], [ -122.409767643810284, 37.809492471432705 ], [ -122.409733123723498, 37.809496464009918 ], [ -122.40971032808983, 37.809485158825325 ], [ -122.40967926884106, 37.809489095348837 ], [ -122.409656473217836, 37.809477790153814 ], [ -122.409630622889722, 37.80948232932171 ], [ -122.409609557687361, 37.809470995825244 ], [ -122.409573289526762, 37.809474329935902 ], [ -122.409550493924201, 37.809463024720365 ], [ -122.40952291317636, 37.809467591881273 ], [ -122.409500117230408, 37.809456286387039 ], [ -122.409465597146067, 37.809460278886 ], [ -122.409442784254765, 37.809448287206727 ], [ -122.409413473087668, 37.809452882357093 ], [ -122.409390677508952, 37.809441576836036 ], [ -122.409354409006824, 37.809444910885041 ], [ -122.409331613792688, 37.809433605621535 ], [ -122.409304033042332, 37.809438172731298 ], [ -122.409281237484791, 37.809426867189075 ], [ -122.409251926309096, 37.809431462024762 ], [ -122.40922913042256, 37.809420156752616 ], [ -122.409192844962547, 37.809422804303409 ], [ -122.409170049433754, 37.809411499014253 ], [ -122.409133798935159, 37.809415519427169 ], [ -122.409110985426068, 37.809403527969394 ], [ -122.409076465338302, 37.809407520079958 ], [ -122.409053670177968, 37.809396214762678 ], [ -122.409027819488273, 37.809400753529559 ], [ -122.409005024337446, 37.809389448202872 ], [ -122.408973965091334, 37.809393384540734 ], [ -122.408951169258785, 37.809382079214799 ], [ -122.40891837994225, 37.80938604354025 ], [ -122.408895584120444, 37.809374738203552 ], [ -122.408862794804193, 37.809378702513534 ], [ -122.408839998993102, 37.809367397166127 ], [ -122.408808940093522, 37.809371333454934 ], [ -122.408786144638981, 37.809360028091476 ], [ -122.408756815463747, 37.809363936372364 ], [ -122.4087340203655, 37.80935263099321 ], [ -122.408701230358204, 37.809356595269499 ], [ -122.408678435270645, 37.809345289879595 ], [ -122.408645645263661, 37.809349254140457 ], [ -122.408622850186802, 37.809337948739781 ], [ -122.408591790941983, 37.809341884977052 ], [ -122.408568995183444, 37.809330579577171 ], [ -122.408536205869268, 37.809334543796403 ], [ -122.408513410121444, 37.809323238385751 ], [ -122.408482351223043, 37.809327174588631 ], [ -122.408459555831755, 37.809315869161935 ], [ -122.408426766172141, 37.809319833356362 ], [ -122.408403970791582, 37.809308527918944 ], [ -122.408371181132239, 37.809312492097938 ], [ -122.408350116177374, 37.809301158650754 ], [ -122.408315578458229, 37.809304464375806 ], [ -122.408292783099327, 37.809293158916844 ], [ -122.408263471569185, 37.80929775351261 ], [ -122.408240676566351, 37.80928644803798 ], [ -122.408207886561954, 37.809290412177198 ], [ -122.408185073925949, 37.809278420254287 ], [ -122.408155762741032, 37.809283014817716 ], [ -122.408132967413039, 37.809271709327838 ], [ -122.408100177755486, 37.809275673431507 ], [ -122.408077382438208, 37.809264367930865 ], [ -122.408039154542493, 37.809258805877114 ], [ -122.408058756181504, 37.809145866471148 ], [ -122.408066594168815, 37.809248747837302 ], [ -122.408087429706811, 37.809251157647353 ], [ -122.408126507222988, 37.809155071214583 ], [ -122.408120448716886, 37.809256117235002 ], [ -122.408137823084502, 37.809258583032992 ], [ -122.408178631290198, 37.80916246830698 ], [ -122.408172554871641, 37.809262828172606 ], [ -122.408196869235468, 37.809265868402413 ], [ -122.408235946617765, 37.809169781933626 ], [ -122.408229888259399, 37.809270827959395 ], [ -122.408254202289157, 37.809273868457552 ], [ -122.408293279587355, 37.809177781420765 ], [ -122.408287221320464, 37.809278827998476 ], [ -122.408308056867014, 37.809281237494979 ], [ -122.408347134113498, 37.809185150989656 ], [ -122.408332158807269, 37.809276040458201 ], [ -122.408356702573101, 37.809288004342378 ], [ -122.408397510165557, 37.809191889546774 ], [ -122.408391452032305, 37.809292935854977 ], [ -122.408412287593251, 37.809295345607659 ], [ -122.408453095124827, 37.809199231067545 ], [ -122.408447037059574, 37.809300277103894 ], [ -122.408471350761801, 37.809303317562879 ], [ -122.408512158553279, 37.809207202447709 ], [ -122.408506100235769, 37.809308249041685 ], [ -122.408521727251397, 37.8093100560612 ], [ -122.408560804236558, 37.809213969485583 ], [ -122.408554745970605, 37.809315015532796 ], [ -122.408577329958945, 37.809318083960704 ], [ -122.408618119627377, 37.809221282377308 ], [ -122.40861206179197, 37.80932232869629 ], [ -122.408636375847209, 37.809325368841058 ], [ -122.408677182759092, 37.809229254229557 ], [ -122.408671125342451, 37.80933030027127 ], [ -122.40868850009889, 37.809332766257086 ], [ -122.408727576866028, 37.809236679077507 ], [ -122.408721501183379, 37.80933703897 ], [ -122.408740606697776, 37.80933947666351 ], [ -122.408781413482515, 37.80924336201619 ], [ -122.408775356206561, 37.809344408062977 ], [ -122.40879620945104, 37.809347504459829 ], [ -122.408837016154081, 37.809251389244281 ], [ -122.408830941310313, 37.809351749130926 ], [ -122.408848316070006, 37.809354214818633 ], [ -122.4088891230695, 37.809258100128716 ], [ -122.40888306559286, 37.80935914618636 ], [ -122.408902170426074, 37.809361583864899 ], [ -122.408942977698928, 37.809265468876312 ], [ -122.408936919955934, 37.809366515216752 ], [ -122.408959503968703, 37.809369583296906 ], [ -122.408998562767209, 37.809272810139959 ], [ -122.408992505092215, 37.809373856208545 ], [ -122.409013358354017, 37.809376952566957 ], [ -122.409054147832379, 37.809280150828236 ], [ -122.409048090239509, 37.809381197174098 ], [ -122.409070674261315, 37.809384265232971 ], [ -122.409109750575112, 37.809288178476827 ], [ -122.40910369305017, 37.809389224550813 ], [ -122.409122798597593, 37.809391662456981 ], [ -122.409161857180791, 37.809294888697217 ], [ -122.409155800079347, 37.809395935042737 ], [ -122.409180113834879, 37.809398975080981 ], [ -122.409220920775525, 37.809302860271487 ], [ -122.409214863400692, 37.809403906350916 ], [ -122.409233968955562, 37.809406344239079 ], [ -122.409273045055528, 37.809310256880117 ], [ -122.409266988111227, 37.80941130350562 ], [ -122.40929130186926, 37.809414343246381 ], [ -122.409332108667385, 37.80931822812412 ], [ -122.409326033795196, 37.809418588046228 ], [ -122.409343408940103, 37.809421053929945 ], [ -122.40938421532131, 37.809324938520824 ], [ -122.409378158527119, 37.809425985151776 ], [ -122.40939901180549, 37.809429080892826 ], [ -122.409438070062492, 37.809332307591447 ], [ -122.409432013326835, 37.809433353675857 ], [ -122.409477180743622, 37.809439489653357 ], [ -122.409529287502835, 37.809446200260403 ], [ -122.409588351243272, 37.809454171101315 ], [ -122.40962830977378, 37.809459704635067 ], [ -122.409632794735771, 37.809432163090825 ], [ -122.409672911837845, 37.809443874825128 ], [ -122.409782563727632, 37.809399522210484 ], [ -122.409759009148317, 37.809358700227392 ], [ -122.409646479104083, 37.809089266648215 ], [ -122.409583548723262, 37.809065563811615 ], [ -122.409522825494292, 37.80906036618412 ], [ -122.409518181262428, 37.809081729795679 ], [ -122.409495386134864, 37.809070424563046 ], [ -122.409466075113144, 37.809075019452258 ], [ -122.409443262001091, 37.809063028052371 ], [ -122.409410472498593, 37.80906699224586 ], [ -122.409387677045885, 37.809055686998015 ], [ -122.409356617959673, 37.80905962343688 ], [ -122.409333822524488, 37.809048318453144 ], [ -122.409299302613846, 37.809052310629191 ], [ -122.40927823759111, 37.809040977347202 ], [ -122.409247178159163, 37.809044913762847 ], [ -122.409226113153551, 37.80903358074611 ], [ -122.409193341651701, 37.809038231311064 ], [ -122.409172276649144, 37.80902689801011 ], [ -122.409141217563558, 37.809030834392281 ], [ -122.409118422162948, 37.809019529092303 ], [ -122.409083884609018, 37.809022835042484 ], [ -122.409061089219591, 37.809011529731414 ], [ -122.409026569318456, 37.809015522102243 ], [ -122.409005504354852, 37.809004189046036 ], [ -122.408972714854954, 37.80900815311788 ], [ -122.408949919486972, 37.808996847785302 ], [ -122.408918860402167, 37.809000784108946 ], [ -122.408896065051678, 37.808989479040513 ], [ -122.408865005613961, 37.808993415081005 ], [ -122.408842210612988, 37.80898210972196 ], [ -122.408807673071223, 37.808985415865877 ], [ -122.408784877735343, 37.808974110501353 ], [ -122.408753818298166, 37.808978046512571 ], [ -122.408732753379752, 37.808966713133003 ], [ -122.40870344269787, 37.808971307827179 ], [ -122.408680647389247, 37.80896000271705 ], [ -122.408653066760635, 37.80896456912587 ], [ -122.408630271454712, 37.808953263731418 ], [ -122.408595751557471, 37.808957255976196 ], [ -122.408574686669112, 37.808945922568363 ], [ -122.40853841837243, 37.808949256366759 ], [ -122.4085173534951, 37.808937922948687 ], [ -122.408484564351539, 37.808941887153871 ], [ -122.408461768739215, 37.80893058200698 ], [ -122.408432440401654, 37.808934489921761 ], [ -122.408409645138534, 37.808923184484605 ], [ -122.408375125243424, 37.808927176664845 ], [ -122.408354060404321, 37.808915843492116 ], [ -122.408319522857553, 37.808919148943978 ], [ -122.408296727616332, 37.808907843484988 ], [ -122.408265686178666, 37.808912466074247 ], [ -122.408242890954767, 37.808901160879408 ], [ -122.408208353409876, 37.808904466298728 ], [ -122.408185558190112, 37.808893160818222 ], [ -122.408154499115199, 37.808897097215251 ], [ -122.40813170390588, 37.808885791724293 ], [ -122.40810064447102, 37.808889727563603 ], [ -122.408077849618252, 37.808878422056637 ], [ -122.408048538584595, 37.808883016593732 ], [ -122.408027473807891, 37.80887168336259 ], [ -122.407989475110696, 37.808875044711961 ], [ -122.407968410338185, 37.808863711195677 ], [ -122.40793908201519, 37.808867619537004 ], [ -122.407916286847737, 37.808856314004366 ], [ -122.407867659098386, 37.808850233137854 ], [ -122.407892470076433, 37.808737895932389 ], [ -122.407895080955839, 37.808839488699526 ], [ -122.407919412118858, 37.808843215435566 ], [ -122.407965428881198, 37.808747703240833 ], [ -122.407954143699186, 37.808847460639832 ], [ -122.407971518664866, 37.808849926725884 ], [ -122.408015804954957, 37.808754442230494 ], [ -122.408004537488765, 37.808854886346353 ], [ -122.408030581417677, 37.80885789835331 ], [ -122.408074867989683, 37.808762414104869 ], [ -122.408063600595128, 37.808862857951631 ], [ -122.408080957579173, 37.808864637589593 ], [ -122.408126991783163, 37.808769811495232 ], [ -122.408115706821903, 37.808869569183827 ], [ -122.408134811496907, 37.808872006712299 ], [ -122.408175637137717, 37.80877657841792 ], [ -122.408167830687617, 37.808876966281417 ], [ -122.408188666131636, 37.808879376348344 ], [ -122.408234682562991, 37.808783863774664 ], [ -122.40822341538842, 37.808884307910851 ], [ -122.4082459985258, 37.808887375864508 ], [ -122.408292014895409, 37.808791863543163 ], [ -122.408280730145464, 37.80889162097256 ], [ -122.408299853182712, 37.808894745449784 ], [ -122.408345869480527, 37.808799232832989 ], [ -122.408334584808884, 37.808898990541991 ], [ -122.40835716795533, 37.808902058474338 ], [ -122.408401454133696, 37.808806574104075 ], [ -122.408390187174248, 37.808907017981177 ], [ -122.408412770332475, 37.808910086177463 ], [ -122.408457056451198, 37.808814602061034 ], [ -122.408447501979367, 37.808914331510991 ], [ -122.408464876962967, 37.808916796975197 ], [ -122.408510893069277, 37.808821284568992 ], [ -122.408499626255633, 37.808921728456291 ], [ -122.408520461722972, 37.808924138464597 ], [ -122.408564747351377, 37.808828653764436 ], [ -122.408553480616533, 37.808929097931319 ], [ -122.40857606412726, 37.808932165816167 ], [ -122.408622080100045, 37.808836653366868 ], [ -122.408610795786757, 37.808936410826988 ], [ -122.408633379309109, 37.808939478975418 ], [ -122.408677664815599, 37.808843994782293 ], [ -122.408666398217051, 37.808944438410528 ], [ -122.408683754881451, 37.808946217690789 ], [ -122.408728058314978, 37.808851419636184 ], [ -122.408718504202398, 37.808951149107379 ], [ -122.40873414881105, 37.808953643085459 ], [ -122.40878016458673, 37.808858130300308 ], [ -122.408768880489617, 37.80895788804974 ], [ -122.408788002858174, 37.808961011909815 ], [ -122.408832288517658, 37.808865527378856 ], [ -122.408822734543293, 37.808965256858244 ], [ -122.40884531843632, 37.808968325235043 ], [ -122.408891334078092, 37.80887281240679 ], [ -122.408880067778369, 37.808973256604034 ], [ -122.40889917250513, 37.808975694008623 ], [ -122.408943458030421, 37.808880209436197 ], [ -122.408933921854199, 37.808980625361755 ], [ -122.408953027290934, 37.808983063295585 ], [ -122.408997312398199, 37.808887578434081 ], [ -122.408987758995764, 37.808987308195377 ], [ -122.409008612134684, 37.808990404005712 ], [ -122.409052897181908, 37.808894919398057 ], [ -122.409041631090304, 37.808995363335725 ], [ -122.40906421464841, 37.808998431401648 ], [ -122.409110230041378, 37.808902919037678 ], [ -122.409098946373874, 37.809002676543273 ], [ -122.409121529583416, 37.809005744329234 ], [ -122.409165814840676, 37.808910259673837 ], [ -122.409154548899522, 37.809010703622 ], [ -122.409175384406026, 37.80901311324002 ], [ -122.4092196692594, 37.808917628844746 ], [ -122.409208385736662, 37.80901738636053 ], [ -122.409229238893218, 37.809020482131849 ], [ -122.409273523674656, 37.808924997441885 ], [ -122.4092622402232, 37.809024754962692 ], [ -122.40927961492774, 37.809027220861601 ], [ -122.409325630047334, 37.808931707864964 ], [ -122.409314364326178, 37.80903215210256 ], [ -122.409336947553612, 37.809035219847274 ], [ -122.409381232551411, 37.808939735111537 ], [ -122.409369949242404, 37.809039492642384 ], [ -122.409390802418955, 37.809042588659686 ], [ -122.409422921195898, 37.808945240701227 ], [ -122.409422903348997, 37.809011853140163 ], [ -122.40946679914822, 37.809035864508125 ], [ -122.409564019832658, 37.809046651478369 ], [ -122.409549629214183, 37.809025596054688 ], [ -122.409548887643922, 37.80899676568643 ], [ -122.409387695818651, 37.808719879048233 ], [ -122.409342422574227, 37.808709624416799 ], [ -122.409302270626966, 37.808696540242821 ], [ -122.409262612300481, 37.808769974638814 ], [ -122.409243366033195, 37.808762045524638 ], [ -122.409286290635848, 37.808681004303558 ], [ -122.409251523749987, 37.808675386616557 ], [ -122.409211865384634, 37.808748820995767 ], [ -122.409192619474325, 37.8087408918677 ], [ -122.409232277847408, 37.808667457220274 ], [ -122.409218046276052, 37.808652579691525 ], [ -122.409199082493714, 37.808655633568947 ], [ -122.409183155837965, 37.808642156922303 ], [ -122.409146746495978, 37.80863999940842 ], [ -122.409132550252011, 37.808626494744182 ], [ -122.409097906968512, 37.808625682074002 ], [ -122.409081980690672, 37.808612205682593 ], [ -122.409045571705349, 37.808610047857293 ], [ -122.409029645094293, 37.808596571464371 ], [ -122.40899498416762, 37.808595072051922 ], [ -122.408979057222851, 37.808581595657749 ], [ -122.408939170155094, 37.808578807372939 ], [ -122.408923243223967, 37.808565330971156 ], [ -122.40887636397639, 37.80855990894463 ], [ -122.408862167796897, 37.808546404247657 ], [ -122.40882402846016, 37.808544274635025 ], [ -122.408771338418944, 37.808649509314336 ], [ -122.408752144836185, 37.80864363943796 ], [ -122.408804834900437, 37.808538404767106 ], [ -122.408755995512777, 37.808524087565431 ], [ -122.40870503613472, 37.808629294204955 ], [ -122.408684130157809, 37.808624138753608 ], [ -122.408736802648747, 37.808518217400646 ], [ -122.408686214896832, 37.808503241742777 ], [ -122.408633542337299, 37.808609162798888 ], [ -122.40861434946801, 37.808603292888833 ], [ -122.408665291301503, 37.808497399575863 ], [ -122.408618182709631, 37.808483054307779 ], [ -122.408565527707552, 37.808589661496853 ], [ -122.408546334502589, 37.808583791581334 ], [ -122.408598989528059, 37.808477184400701 ], [ -122.408550150206281, 37.808462866564483 ], [ -122.408494192805264, 37.808575707944065 ], [ -122.408475017602044, 37.808570524448861 ], [ -122.408530957042373, 37.808456996920889 ], [ -122.408475143235307, 37.808440732021886 ], [ -122.408419203721877, 37.80855425952376 ], [ -122.408400010537662, 37.808548389584381 ], [ -122.40845594972285, 37.808434861822526 ], [ -122.408401884334964, 37.808419255320196 ], [ -122.408345944732787, 37.808532783062383 ], [ -122.408326768858856, 37.808527599554232 ], [ -122.408382691187029, 37.808413385377861 ], [ -122.408326877445205, 37.808397120408593 ], [ -122.40827095504342, 37.808511334558837 ], [ -122.408251779872415, 37.808506151027288 ], [ -122.408325818753553, 37.80835593442692 ], [ -122.408307261690325, 37.808307477072752 ], [ -122.408233773949888, 37.808277076846032 ], [ -122.408190831757594, 37.808290132648246 ], [ -122.408103813680896, 37.80847420912815 ], [ -122.408070707145512, 37.808465817066804 ], [ -122.408167524728952, 37.808258920751598 ], [ -122.408046540290897, 37.80819907276836 ], [ -122.408024204007958, 37.808205614634559 ], [ -122.407919104249203, 37.808427066268948 ], [ -122.407885997416685, 37.80841867443587 ], [ -122.407996041444051, 37.808187528721767 ], [ -122.407913795821727, 37.808153149917878 ], [ -122.407807565173499, 37.808397968053718 ], [ -122.407786659324017, 37.808392812443081 ], [ -122.407868398033912, 37.808205388267609 ], [ -122.407660185387385, 37.808254079991599 ], [ -122.407706584592447, 37.808375566178441 ], [ -122.407613672832028, 37.80839767070411 ], [ -122.407507720833991, 37.808114394328115 ], [ -122.407549056121539, 37.808106171825735 ], [ -122.407536061536263, 37.808072046122568 ], [ -122.407462591282297, 37.808042331588446 ], [ -122.40744611868692, 37.808007575403529 ], [ -122.407487420240358, 37.807930680888951 ], [ -122.407431324879042, 37.807903432764398 ], [ -122.407355431147764, 37.80804887173035 ], [ -122.406644940711814, 37.807817946705647 ], [ -122.406816836689401, 37.809994826134172 ], [ -122.406786252768356, 37.810017295716854 ], [ -122.40617819806161, 37.810068327706666 ], [ -122.406151660268563, 37.810046094760864 ], [ -122.406135997898389, 37.810042914277616 ], [ -122.406123656094778, 37.810034186621294 ], [ -122.405904441804637, 37.807159674056429 ], [ -122.405322208784199, 37.806866923600786 ], [ -122.404400300125417, 37.808853394598287 ], [ -122.404390181819835, 37.80886385881621 ], [ -122.404376497046869, 37.808870260285914 ], [ -122.404359263409262, 37.808873285169796 ], [ -122.404316325011152, 37.808819040682266 ], [ -122.403937477028578, 37.80876197892308 ], [ -122.403923457745378, 37.80875533773979 ], [ -122.403912828869224, 37.808745895483341 ], [ -122.403907320429198, 37.80873362312807 ], [ -122.403906985949291, 37.808720580801918 ], [ -122.404717282916977, 37.806971462592401 ], [ -122.403955936424254, 37.806579276399596 ], [ -122.402761932327437, 37.808014567794245 ], [ -122.402746534430648, 37.808021683436984 ], [ -122.402731014070881, 37.808023993997161 ], [ -122.402715370212889, 37.808021499491424 ], [ -122.40235585639897, 37.807840510524542 ], [ -122.402346922703828, 37.807829667068134 ], [ -122.402341415289797, 37.807817394901065 ], [ -122.402339367950134, 37.807805066923208 ], [ -122.402340764477955, 37.807791996673913 ], [ -122.402345656255733, 37.807780243491045 ], [ -122.403421726415289, 37.806471156550586 ], [ -122.402804368682325, 37.806023074735968 ], [ -122.400943288973451, 37.807638041928655 ], [ -122.40092780379058, 37.807641725129315 ], [ -122.400912142497489, 37.807638543945465 ], [ -122.400398744529411, 37.807261566606726 ], [ -122.400572958659495, 37.806120993590262 ], [ -122.401732349384048, 37.805192215433102 ], [ -122.403194419486127, 37.805118563841738 ], [ -122.403587277644846, 37.805412029499024 ], [ -122.404128809815958, 37.80582849738515 ], [ -122.404696069838451, 37.806264743038192 ], [ -122.405495723806581, 37.806640585378162 ], [ -122.405545441190654, 37.806634177056651 ], [ -122.405362337774832, 37.805695301990511 ], [ -122.405357241074242, 37.805695943874476 ], [ -122.405356257037084, 37.805690948342658 ], [ -122.405173536628851, 37.804763212222298 ], [ -122.404986245080067, 37.803832670552438 ], [ -122.404798316633176, 37.802900762043983 ], [ -122.40352117840483, 37.80305477917063 ], [ -122.40315960682554, 37.803098380641011 ], [ -122.402973676735002, 37.802166749844929 ], [ -122.402973611142329, 37.802166419353441 ], [ -122.402883459886269, 37.801713453423041 ], [ -122.402789664410264, 37.801242172237629 ], [ -122.402604124590809, 37.800307129616797 ], [ -122.402603736644934, 37.800305175692849 ], [ -122.402420666593613, 37.799382141273902 ], [ -122.402228639238132, 37.798429701815799 ], [ -122.402040983242472, 37.797504944420098 ], [ -122.401906946643678, 37.796875708196644 ], [ -122.401853390100442, 37.796626165074152 ], [ -122.402402444702886, 37.796554566975963 ], [ -122.40291796673857, 37.796490379340391 ], [ -122.403496709817034, 37.796417485859841 ], [ -122.404412160626791, 37.796302098196776 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.405862652252878, 37.796122174218517 ], [ -122.406224736410849, 37.796077505131322 ], [ -122.406350669109713, 37.796061968858353 ], [ -122.406652352369846, 37.796024750165536 ], [ -122.407083680819795, 37.795970583772274 ], [ -122.407319877605971, 37.795940921047873 ], [ -122.407432089697608, 37.795926828732469 ], [ -122.407563768850565, 37.795910291726024 ], [ -122.40779730388941, 37.795880962818103 ], [ -122.408253202051654, 37.795823706218044 ], [ -122.408704654154022, 37.795766841471867 ], [ -122.408743234497422, 37.795761981540487 ], [ -122.409075520379659, 37.795720125786943 ], [ -122.409077868767497, 37.795719830108119 ], [ -122.409343544204319, 37.795686363949223 ], [ -122.40954722057397, 37.795660707190791 ], [ -122.40989875147848, 37.795616424537307 ], [ -122.411081667103218, 37.795467382264249 ], [ -122.411541226025591, 37.795408552082151 ], [ -122.411541229133036, 37.795408565216896 ], [ -122.411541531721369, 37.795408526524874 ], [ -122.411893657029495, 37.795363458292385 ], [ -122.412335878555311, 37.795306857217497 ], [ -122.413187771447625, 37.795197816725128 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.415005455310151, 37.795877318912275 ], [ -122.415094279286492, 37.796316514471208 ], [ -122.415172033219093, 37.796700969266084 ], [ -122.41519297245145, 37.796804503853558 ], [ -122.415290513107706, 37.797286788953464 ], [ -122.415384105347158, 37.79774954140936 ], [ -122.415384693800732, 37.797752451808172 ], [ -122.415571551794869, 37.798676329254079 ], [ -122.415572319635913, 37.798680127283284 ], [ -122.415667503417794, 37.799150732537029 ], [ -122.41576151132729, 37.799616465113374 ], [ -122.416491256558686, 37.799525268171301 ], [ -122.417385454528372, 37.799413513042644 ], [ -122.417492539304973, 37.799906258247788 ], [ -122.417586974866069, 37.800340791478632 ], [ -122.417798642975782, 37.801267347943607 ], [ -122.41787748762404, 37.801706751150704 ], [ -122.417966229723234, 37.802201308768375 ], [ -122.418151092101994, 37.803135399210959 ], [ -122.418255886912107, 37.803650215613203 ], [ -122.418215611082957, 37.803687299847837 ], [ -122.418280954876565, 37.804023161939504 ], [ -122.418341342522965, 37.804070022432718 ], [ -122.418530631419571, 37.804999043218864 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":8,\"ZIP_CODE\":94109,\"ID\":94109},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.413915879652706, 37.790463115032836 ], [ -122.412266187164306, 37.790672783986288 ], [ -122.412154318600955, 37.79012444884593 ], [ -122.412075867445836, 37.789739906579548 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.413541442066688, 37.788598204045492 ], [ -122.415185269301574, 37.788387930678724 ], [ -122.414995423274036, 37.787453843784689 ], [ -122.414806647966373, 37.786519819325378 ], [ -122.414617207572036, 37.785582474657787 ], [ -122.414430198422409, 37.784657140813145 ], [ -122.414241572919337, 37.783723783021379 ], [ -122.415882538254209, 37.783515640872785 ], [ -122.417528773439201, 37.78331088958533 ], [ -122.419181703937824, 37.783101400150407 ], [ -122.420816620257895, 37.78289417046458 ], [ -122.420817002825672, 37.782894121654586 ], [ -122.422463744273344, 37.782685368786026 ], [ -122.422597534917514, 37.782659914421728 ], [ -122.424108200948226, 37.782477051425644 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.427490665181679, 37.782522998381459 ], [ -122.427584325166535, 37.78298604740867 ], [ -122.427778871361184, 37.783924735360486 ], [ -122.427988927379005, 37.784953879052303 ], [ -122.428148186422831, 37.785784488173462 ], [ -122.428241496400901, 37.78624657727314 ], [ -122.428241829261111, 37.786248226022003 ], [ -122.428335613076769, 37.786712655890966 ], [ -122.428525011003728, 37.78765056176524 ], [ -122.428712111452299, 37.788577067507021 ], [ -122.428905308896674, 37.789533740830485 ], [ -122.429092268257477, 37.790459499306614 ], [ -122.429269817037863, 37.791338636935983 ], [ -122.429447192175289, 37.79221689778025 ], [ -122.429625242377327, 37.793098475947545 ], [ -122.429803981887915, 37.793977244761322 ], [ -122.429989276973558, 37.794894937362344 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.428537813517494, 37.796062892623418 ], [ -122.426892728667355, 37.796272251947869 ], [ -122.425249874473522, 37.79648130315109 ], [ -122.423601877370103, 37.796690947281171 ], [ -122.423601876988513, 37.796690945913937 ], [ -122.42360129621845, 37.796691056748735 ], [ -122.423601296960271, 37.796691058659476 ], [ -122.423792964234153, 37.797616581376253 ], [ -122.423793785438747, 37.7976164776058 ], [ -122.423982396975646, 37.798552421556877 ], [ -122.423982145098208, 37.798552453684835 ], [ -122.42417056430692, 37.799484532744323 ], [ -122.42417056511286, 37.799484537126212 ], [ -122.424242907462499, 37.799842398528341 ], [ -122.424358667137113, 37.800415027771606 ], [ -122.424517597070277, 37.801201189595353 ], [ -122.424538324517613, 37.801303719934985 ], [ -122.424731635083759, 37.802281009278651 ], [ -122.424731259133807, 37.802280983275736 ], [ -122.424808684502352, 37.802662664019884 ], [ -122.424918804152185, 37.803205508171892 ], [ -122.425109521046082, 37.804145845932808 ], [ -122.425204919000421, 37.804616199985951 ], [ -122.425298563577385, 37.80507790289635 ], [ -122.425486832357592, 37.806006120121552 ], [ -122.425487611207828, 37.806009959663712 ], [ -122.425531817297653, 37.806150610629167 ], [ -122.425627533230582, 37.806456787332273 ], [ -122.4257796482039, 37.806715259689653 ], [ -122.426066893674061, 37.80699787612533 ], [ -122.426256728575282, 37.807185221316665 ], [ -122.426357658303132, 37.807388412718332 ], [ -122.426488519796493, 37.807651861239542 ], [ -122.426560904719622, 37.807943556174827 ], [ -122.426613230594512, 37.808152010516238 ], [ -122.426749407153778, 37.80832970781308 ], [ -122.426815901093192, 37.808623913624309 ], [ -122.42681099272437, 37.808701593834883 ], [ -122.426830737473963, 37.808795352699491 ], [ -122.426826276753118, 37.808823581609779 ], [ -122.426828951398363, 37.808859934107588 ], [ -122.426829913470996, 37.808897001584555 ], [ -122.426829127336148, 37.808933411171161 ], [ -122.426830071586494, 37.80896979193826 ], [ -122.426848658020631, 37.809018932542891 ], [ -122.426823913850725, 37.809066034426849 ], [ -122.426821397293551, 37.809102471733105 ], [ -122.426817149990782, 37.809138937589218 ], [ -122.426811172632014, 37.809175431983689 ], [ -122.42680517743706, 37.809211239393754 ], [ -122.426799200066583, 37.80924773378711 ], [ -122.426791474798563, 37.809283569459971 ], [ -122.426783766999961, 37.80932009184702 ], [ -122.426764854928408, 37.809391820287793 ], [ -122.426753650652387, 37.809427026340984 ], [ -122.426742463842274, 37.809462919107801 ], [ -122.42673125988388, 37.809498124878161 ], [ -122.426718325162497, 37.809533359195932 ], [ -122.426703641860442, 37.809567935625843 ], [ -122.426690707106644, 37.80960316966555 ], [ -122.426674294047132, 37.809637774073536 ], [ -122.426659593228109, 37.809671663782375 ], [ -122.426641449382515, 37.809706296733168 ], [ -122.426624999956104, 37.809739528275571 ], [ -122.426606838595305, 37.809773474231129 ], [ -122.426586928644923, 37.809806762295736 ], [ -122.426568749097328, 37.809840022090548 ], [ -122.426547090883986, 37.809872652254199 ], [ -122.426527163066623, 37.80990525387346 ], [ -122.426505504455619, 37.809937883485567 ], [ -122.426482098287991, 37.809969855187106 ], [ -122.426435249574482, 37.810032425717694 ], [ -122.426410094762232, 37.810063739239673 ], [ -122.426384921777455, 37.810094366601653 ], [ -122.426359731637717, 37.810124306962969 ], [ -122.426305889769012, 37.810184245028879 ], [ -122.426277202393095, 37.810212869038814 ], [ -122.426248533505472, 37.810242179739554 ], [ -122.426219828627396, 37.810270117569488 ], [ -122.426165541280753, 37.810312894703067 ], [ -122.426148806543424, 37.810335143220563 ], [ -122.426077517869416, 37.810389871747489 ], [ -122.426029956191201, 37.810424984999884 ], [ -122.426004427087875, 37.810441883304833 ], [ -122.425978879818175, 37.810458095175186 ], [ -122.425953351037933, 37.810474993463338 ], [ -122.425902221511379, 37.810506044020848 ], [ -122.425874926298846, 37.810521597684627 ], [ -122.425847613267436, 37.810536464907365 ], [ -122.425792951557781, 37.810564826464294 ], [ -122.425738254193391, 37.810591815126337 ], [ -122.425709157251291, 37.810604651266786 ], [ -122.425621812948378, 37.810641100340696 ], [ -122.425563512153047, 37.810662654222938 ], [ -122.425532631291532, 37.810673458992433 ], [ -122.425501732623886, 37.810683577593657 ], [ -122.425472546238581, 37.810692981507543 ], [ -122.425441630096913, 37.810702413652621 ], [ -122.425410695451745, 37.810711159366136 ], [ -122.425348791232992, 37.810727278162595 ], [ -122.425317821646317, 37.810734650696368 ], [ -122.42528512127241, 37.810742051749649 ], [ -122.425254133532235, 37.81074873811216 ], [ -122.425221397542259, 37.810754766278279 ], [ -122.425190391643511, 37.810760766194903 ], [ -122.425157620386827, 37.810765421468112 ], [ -122.425124866580816, 37.810770763172656 ], [ -122.425093807273001, 37.810774704034145 ], [ -122.425028228777975, 37.810782641379291 ], [ -122.424962578393604, 37.810787833509224 ], [ -122.424931484149809, 37.810790400897922 ], [ -122.424898623523902, 37.810791623930832 ], [ -122.424876305744775, 37.810798855419677 ], [ -122.424848476260408, 37.810793815800935 ], [ -122.424819040922628, 37.810793609459935 ], [ -122.42478787514149, 37.810793431351755 ], [ -122.424758404216234, 37.810791852400563 ], [ -122.42472891548077, 37.810789586458021 ], [ -122.424699408618324, 37.810786634628094 ], [ -122.424640324731897, 37.810777984365998 ], [ -122.424610747039267, 37.810772286768845 ], [ -122.424582881984065, 37.810765873943147 ], [ -122.42455326835632, 37.810758803192932 ], [ -122.424525367734788, 37.810751018033073 ], [ -122.424497431514737, 37.810741859447681 ], [ -122.42447122609704, 37.810732672889145 ], [ -122.424443254308628, 37.810722141695749 ], [ -122.424406843510837, 37.810719989167595 ], [ -122.424404739729965, 37.810705601989262 ], [ -122.424392430564922, 37.810698249126709 ], [ -122.424380086492334, 37.810689522832604 ], [ -122.424367724289866, 37.810680110382542 ], [ -122.424357092185232, 37.810670669703008 ], [ -122.424346425188247, 37.810659856141378 ], [ -122.424337488288586, 37.810649014350894 ], [ -122.424328533252009, 37.810637486130467 ], [ -122.424321309004071, 37.810625929670387 ], [ -122.424315797750651, 37.810613658536091 ], [ -122.424310250566421, 37.810600014537307 ], [ -122.42430472117745, 37.810587056973155 ], [ -122.4243009222361, 37.810574071450297 ], [ -122.424298835580885, 37.810560370715905 ], [ -122.424298497510776, 37.810547328452351 ], [ -122.424299872431348, 37.810533571515215 ], [ -122.424301264798501, 37.810520501018615 ], [ -122.424304387263248, 37.810507402569137 ], [ -122.424309258634793, 37.810494961760924 ], [ -122.424315860102183, 37.810482492999512 ], [ -122.424324191657433, 37.810469996010013 ], [ -122.424332559488974, 37.810458871878474 ], [ -122.42434440529064, 37.810448377725301 ], [ -122.424354539139387, 37.810438598228359 ], [ -122.424368151310617, 37.810429448977779 ], [ -122.424380050823189, 37.810421013845534 ], [ -122.424395446797789, 37.810413895387818 ], [ -122.424410860570717, 37.810407463637567 ], [ -122.424426309569299, 37.810402404211651 ], [ -122.424441794853934, 37.810398717916826 ], [ -122.42445902802703, 37.810395690099803 ], [ -122.424474548198873, 37.810393376407241 ], [ -122.424495313410532, 37.810393037578869 ], [ -122.424512653696851, 37.810394128357849 ], [ -122.424528280984248, 37.810395933262043 ], [ -122.424545674311858, 37.810399083346539 ], [ -122.424561354987802, 37.810402947551076 ], [ -122.424577071263883, 37.810408184897881 ], [ -122.424591092706251, 37.810414823624235 ], [ -122.424605131932822, 37.810422148234672 ], [ -122.424619206768625, 37.810430846262342 ], [ -122.424631551158143, 37.810439571977035 ], [ -122.424642200368709, 37.810449699078426 ], [ -122.424652868070766, 37.810460512602432 ], [ -122.424661822790284, 37.810472040804292 ], [ -122.424669047076236, 37.810483597243831 ], [ -122.424676306964855, 37.810496526827265 ], [ -122.424683904979332, 37.810522498123575 ], [ -122.424685973208497, 37.810535512153116 ], [ -122.424686685078399, 37.810562969547746 ], [ -122.424688754001352, 37.810575983565833 ], [ -122.424694265313605, 37.810588254682798 ], [ -122.424703220408176, 37.810599783150167 ], [ -122.424713852193449, 37.810609223529688 ], [ -122.424727909273159, 37.81061723483468 ], [ -122.424743643057568, 37.81062315859959 ], [ -122.424759306354005, 37.810626336886287 ], [ -122.424774915898695, 37.810627455322866 ], [ -122.424844204676688, 37.810629071443891 ], [ -122.424883986990039, 37.810627735460123 ], [ -122.424942750746894, 37.810624029768022 ], [ -122.424970402172974, 37.810622204741975 ], [ -122.424999748098813, 37.810618978876803 ], [ -122.425029112521329, 37.810616439702436 ], [ -122.42505844098109, 37.810612527107622 ], [ -122.425086039005805, 37.810608643024629 ], [ -122.425115367473737, 37.810604730964819 ], [ -122.425142947684478, 37.810600160159147 ], [ -122.425172240190349, 37.810594874946659 ], [ -122.42519980259955, 37.810589617967473 ], [ -122.425229077987609, 37.810583646294816 ], [ -122.425311658363697, 37.810563756689383 ], [ -122.425339167350202, 37.810556440647844 ], [ -122.425394149679065, 37.810540434852271 ], [ -122.425449096383744, 37.810523056436082 ], [ -122.425474821139559, 37.810513709039498 ], [ -122.42550225886724, 37.810503646946287 ], [ -122.425553673774345, 37.810483579237776 ], [ -122.425579363239436, 37.810472858943186 ], [ -122.425656378173159, 37.810438638721514 ], [ -122.425680301215294, 37.81042657379389 ], [ -122.425705937577163, 37.810413794436336 ], [ -122.425729843135883, 37.81040104278366 ], [ -122.425777618636445, 37.81037416741794 ], [ -122.425799775930017, 37.810360757570926 ], [ -122.425823645502135, 37.810346633309891 ], [ -122.425845767517558, 37.810331850853636 ], [ -122.425869619617202, 37.810317040417416 ], [ -122.425891740915375, 37.81030225768918 ], [ -122.425912114318606, 37.810286817047619 ], [ -122.425934218490383, 37.810271348139892 ], [ -122.42595457407505, 37.810255221330763 ], [ -122.425976660073871, 37.81023906598594 ], [ -122.426033069704985, 37.810211362243855 ], [ -122.426044381296308, 37.810180274872565 ], [ -122.426071409213066, 37.810154424922551 ], [ -122.426098419287058, 37.810127887982347 ], [ -122.426123698921984, 37.810101379570156 ], [ -122.42617422251692, 37.810046989859728 ], [ -122.426197736051108, 37.810019136822319 ], [ -122.426222979645814, 37.809991255524551 ], [ -122.426244745255502, 37.809962744298282 ], [ -122.426268240925296, 37.809934204811682 ], [ -122.426289987997293, 37.809905007153375 ], [ -122.426310004983833, 37.8098758380223 ], [ -122.426331734895541, 37.809845953635381 ], [ -122.426350003264446, 37.809816126056347 ], [ -122.426370002384175, 37.809786270205322 ], [ -122.42640650446053, 37.80972524214534 ], [ -122.426423024539801, 37.809694756383479 ], [ -122.426439527484902, 37.809663584173109 ], [ -122.426456011918219, 37.809631725811407 ], [ -122.426470784413283, 37.809600581586317 ], [ -122.426485538743833, 37.809568751204715 ], [ -122.426498562648177, 37.809536949086315 ], [ -122.426511568735094, 37.809504460806153 ], [ -122.426524592609866, 37.809472658409931 ], [ -122.426535868255684, 37.809440198117713 ], [ -122.426547125731901, 37.809407051395155 ], [ -122.426556671294222, 37.80937461936086 ], [ -122.42657572607385, 37.809308382431155 ], [ -122.426583523043476, 37.809275292231341 ], [ -122.426589589599459, 37.809242230297407 ], [ -122.426597386209863, 37.809209140101942 ], [ -122.426601704881108, 37.809175419993217 ], [ -122.42660775360666, 37.809141671622967 ], [ -122.426612089737361, 37.809108637953877 ], [ -122.42661985311382, 37.809007562446936 ], [ -122.426622425964752, 37.808906571727555 ], [ -122.426632418849564, 37.808824688117525 ], [ -122.426616150524296, 37.80879817156422 ], [ -122.426607002185577, 37.808712480555535 ], [ -122.426596123115345, 37.808626817267942 ], [ -122.42658353220007, 37.808541869217166 ], [ -122.426569192751245, 37.808456262451642 ], [ -122.426553140769713, 37.808371370658513 ], [ -122.426535358440475, 37.808286507127633 ], [ -122.426521448780022, 37.808150762619441 ], [ -122.42648716178519, 37.808163683754294 ], [ -122.426478316482672, 37.808089661844356 ], [ -122.426472145992449, 37.808051992713068 ], [ -122.426465992974997, 37.808015010021762 ], [ -122.42645810959354, 37.807978055869093 ], [ -122.426450244018099, 37.807941787601465 ], [ -122.426431016184523, 37.807867935277343 ], [ -122.426421420613309, 37.807831695540209 ], [ -122.426410094332368, 37.807795484071661 ], [ -122.426383981060937, 37.807723117657737 ], [ -122.426370941909852, 37.807687620888885 ], [ -122.426356154936116, 37.8076514659409 ], [ -122.42634140359948, 37.807616683860772 ], [ -122.426324904443447, 37.807581243600652 ], [ -122.426289370917274, 37.807546114229226 ], [ -122.426116990256872, 37.807640950874436 ], [ -122.426004575599805, 37.807511622087709 ], [ -122.425948221432193, 37.807541385135394 ], [ -122.426072979790121, 37.807679439549148 ], [ -122.426043902201883, 37.807692962208542 ], [ -122.425788908967789, 37.807405954455511 ], [ -122.425811101339534, 37.807393917738004 ], [ -122.425841407866244, 37.807427759425885 ], [ -122.425860156968014, 37.807416465651144 ], [ -122.425910013941177, 37.807469902573231 ], [ -122.425894653990497, 37.807478394108394 ], [ -122.425932202930852, 37.807524478297722 ], [ -122.425969647726248, 37.80749983141159 ], [ -122.425933954139211, 37.807458524016887 ], [ -122.426097611558333, 37.807361083192824 ], [ -122.42613200511353, 37.807352281054179 ], [ -122.426091315966914, 37.80731860900228 ], [ -122.4260559073373, 37.80728828460429 ], [ -122.425981701022749, 37.807230438026032 ], [ -122.425942884837923, 37.807202229419872 ], [ -122.425865288869659, 37.807147185302298 ], [ -122.425824778720127, 37.807120377771732 ], [ -122.425784286059837, 37.807094256667916 ], [ -122.425743811226624, 37.807068821710658 ], [ -122.425659436564132, 37.807019381954113 ], [ -122.425575133216185, 37.806972687328404 ], [ -122.425531269042693, 37.806950055090923 ], [ -122.425487422692115, 37.806928108997688 ], [ -122.425396304969212, 37.806885646130702 ], [ -122.425305258549756, 37.806845928659463 ], [ -122.425259770989555, 37.806827443179728 ], [ -122.425212552741186, 37.806808985385324 ], [ -122.425165353022919, 37.806791214545072 ], [ -122.425118188573762, 37.806774816287508 ], [ -122.425069293788937, 37.80675844598079 ], [ -122.4250204168317, 37.806742762363598 ], [ -122.424973288049415, 37.806727737193157 ], [ -122.42492444672159, 37.806713426132198 ], [ -122.424873892869243, 37.806699830002309 ], [ -122.424802432493848, 37.806681081528858 ], [ -122.424772802670631, 37.806673324118421 ], [ -122.424744921357572, 37.806666225439869 ], [ -122.424689194317793, 37.806653400109965 ], [ -122.424659600807814, 37.806647015805055 ], [ -122.424631755098972, 37.806641289695882 ], [ -122.42460219684277, 37.806636278252959 ], [ -122.42457436893713, 37.806631238565636 ], [ -122.424544828483292, 37.806626913543731 ], [ -122.424517018379291, 37.806622560278207 ], [ -122.424487495380134, 37.806618921682983 ], [ -122.424428486015572, 37.806613017324239 ], [ -122.424398998964037, 37.806610751846591 ], [ -122.42437124224621, 37.806608457578399 ], [ -122.424341772644766, 37.806606878527504 ], [ -122.424282870046667, 37.806605092433877 ], [ -122.424224002349433, 37.806604679467583 ], [ -122.424165170926941, 37.806605639331515 ], [ -122.424135772832813, 37.80660680569077 ], [ -122.424077012229233, 37.806610511532789 ], [ -122.424018287184794, 37.806615589667317 ], [ -122.42398895989578, 37.806619501737501 ], [ -122.423959615506732, 37.806622727353634 ], [ -122.423932018904594, 37.806626611175993 ], [ -122.423873400581527, 37.806635808400436 ], [ -122.423845839538984, 37.806641064799031 ], [ -122.423816565594137, 37.806647036132823 ], [ -122.423789022338056, 37.806652979228197 ], [ -122.423759766870432, 37.806659637246682 ], [ -122.423706445903605, 37.806672867782268 ], [ -122.423653321266002, 37.806693649347707 ], [ -122.423589920527235, 37.806718718849552 ], [ -122.423526538222887, 37.806744474741464 ], [ -122.423399843925978, 37.80679873217619 ], [ -122.423338262626473, 37.806827205494514 ], [ -122.423215171014846, 37.806886897776501 ], [ -122.423155391052063, 37.806918088524029 ], [ -122.423095628820221, 37.806949965676615 ], [ -122.423037614673063, 37.806982501019867 ], [ -122.422979636035322, 37.807016409205524 ], [ -122.422923387701616, 37.807050289149693 ], [ -122.422867174874767, 37.807085541937973 ], [ -122.422807430162962, 37.807118105379153 ], [ -122.422669617719095, 37.80721100017012 ], [ -122.422538512716017, 37.807295544473774 ], [ -122.422410922154015, 37.807382091240839 ], [ -122.422406568873129, 37.807385156047935 ], [ -122.422285114644055, 37.807470669252126 ], [ -122.422159360151895, 37.8075613064347 ], [ -122.422037101638551, 37.807653259953362 ], [ -122.421916608736879, 37.807746558017001 ], [ -122.421799629592272, 37.807841858870269 ], [ -122.42168270310637, 37.807939218918449 ], [ -122.421532057745097, 37.808071464473507 ], [ -122.421789175548739, 37.808374241220797 ], [ -122.421758561641454, 37.808395342089796 ], [ -122.421493253559859, 37.808110553294661 ], [ -122.421324496714121, 37.808278803348081 ], [ -122.421367127445279, 37.808320685807196 ], [ -122.421400626951964, 37.808344175435167 ], [ -122.421417984134223, 37.808345953110788 ], [ -122.421538848573221, 37.808467594219884 ], [ -122.421521846706554, 37.808479545548913 ], [ -122.421585810928775, 37.808543055557919 ], [ -122.421558551068415, 37.808559981099798 ], [ -122.421494604621685, 37.808497157511823 ], [ -122.421482722503114, 37.808506278522707 ], [ -122.42136546133365, 37.808390072182092 ], [ -122.421343808401488, 37.808356088677073 ], [ -122.421300875659668, 37.808302536803509 ], [ -122.421061625998746, 37.808556401980894 ], [ -122.421102646347336, 37.808803641534986 ], [ -122.421495500883367, 37.809133737012722 ], [ -122.421447795704026, 37.809163356667042 ], [ -122.422886026597482, 37.810219448665336 ], [ -122.422843690020329, 37.8102558485987 ], [ -122.42136637775458, 37.809161249445332 ], [ -122.421209585435776, 37.809189899457976 ], [ -122.421035643651535, 37.808957186122555 ], [ -122.420944163231766, 37.808967603286121 ], [ -122.420910323787112, 37.808797159929568 ], [ -122.420858358549751, 37.808795946109164 ], [ -122.420717381312784, 37.80890056409217 ], [ -122.420760227934167, 37.809084595303112 ], [ -122.420694330112809, 37.809080174685604 ], [ -122.420677247269424, 37.809022081268409 ], [ -122.420415813879998, 37.809020844655912 ], [ -122.420359979289643, 37.809003898946663 ], [ -122.420384124514499, 37.808933459866985 ], [ -122.420668674045118, 37.808958355470025 ], [ -122.420635748727079, 37.808756307812423 ], [ -122.420540860637956, 37.808768840609403 ], [ -122.420461300526242, 37.808303163202581 ], [ -122.419223966222532, 37.808453096037454 ], [ -122.419086067910683, 37.807803117635643 ], [ -122.418908043871269, 37.806863030947284 ], [ -122.418856183917853, 37.806607498073362 ], [ -122.418781854642702, 37.806241248585302 ], [ -122.418781807492508, 37.806241018338142 ], [ -122.418718265666911, 37.805931520041717 ], [ -122.418530631419571, 37.804999043218864 ], [ -122.418341342522965, 37.804070022432718 ], [ -122.418280954876565, 37.804023161939504 ], [ -122.418215611082957, 37.803687299847837 ], [ -122.418255886912107, 37.803650215613203 ], [ -122.418151092101994, 37.803135399210959 ], [ -122.417966229723234, 37.802201308768375 ], [ -122.41787748762404, 37.801706751150704 ], [ -122.417798642975782, 37.801267347943607 ], [ -122.417586974866069, 37.800340791478632 ], [ -122.417492539304973, 37.799906258247788 ], [ -122.417385454528372, 37.799413513042644 ], [ -122.416491256558686, 37.799525268171301 ], [ -122.41576151132729, 37.799616465113374 ], [ -122.415667503417794, 37.799150732537029 ], [ -122.415572319635913, 37.798680127283284 ], [ -122.415571551794869, 37.798676329254079 ], [ -122.415384693800732, 37.797752451808172 ], [ -122.415384105347158, 37.79774954140936 ], [ -122.415290513107706, 37.797286788953464 ], [ -122.41519297245145, 37.796804503853558 ], [ -122.415172033219093, 37.796700969266084 ], [ -122.415094279286492, 37.796316514471208 ], [ -122.415005455310151, 37.795877318912275 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.414647632176155, 37.794109807074861 ], [ -122.414470074462415, 37.79322314646798 ], [ -122.414381908270244, 37.792784070108439 ], [ -122.414293264567746, 37.792342610236801 ], [ -122.414107303557273, 37.79141647884336 ], [ -122.413915879652706, 37.790463115032836 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":9,\"ZIP_CODE\":94111,\"ID\":94111},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.400058259594957, 37.793693587424592 ], [ -122.400148778887171, 37.794134234665414 ], [ -122.400149325778898, 37.794134165695652 ], [ -122.401314504615144, 37.793987063215781 ], [ -122.402133431503088, 37.793883778773662 ], [ -122.40215951272063, 37.793880489466119 ], [ -122.402957360909014, 37.793779857671261 ], [ -122.40308689535884, 37.793763518993806 ], [ -122.404016114128936, 37.793646309359175 ], [ -122.404370134182003, 37.793598168205968 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404710582177955, 37.794021380197684 ], [ -122.404796472837063, 37.794453004909116 ], [ -122.404875610619072, 37.794859533373135 ], [ -122.404957208224928, 37.795326691921687 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.404412160626791, 37.796302098196776 ], [ -122.403496709817034, 37.796417485859841 ], [ -122.40291796673857, 37.796490379340391 ], [ -122.402402444702886, 37.796554566975963 ], [ -122.401853390100442, 37.796626165074152 ], [ -122.401906946643678, 37.796875708196644 ], [ -122.402040983242472, 37.797504944420098 ], [ -122.402228639238132, 37.798429701815799 ], [ -122.402420666593613, 37.799382141273902 ], [ -122.402603736644934, 37.800305175692849 ], [ -122.402604124590809, 37.800307129616797 ], [ -122.402789664410264, 37.801242172237629 ], [ -122.402883459886269, 37.801713453423041 ], [ -122.402973611142329, 37.802166419353441 ], [ -122.402973676735002, 37.802166749844929 ], [ -122.40315960682554, 37.803098380641011 ], [ -122.40352117840483, 37.80305477917063 ], [ -122.404798316633176, 37.802900762043983 ], [ -122.404986245080067, 37.803832670552438 ], [ -122.405173536628851, 37.804763212222298 ], [ -122.405356257037084, 37.805690948342658 ], [ -122.405357241074242, 37.805695943874476 ], [ -122.405362337774832, 37.805695301990511 ], [ -122.405545441190654, 37.806634177056651 ], [ -122.405495723806581, 37.806640585378162 ], [ -122.404696069838451, 37.806264743038192 ], [ -122.404128809815958, 37.80582849738515 ], [ -122.403587277644846, 37.805412029499024 ], [ -122.403194419486127, 37.805118563841738 ], [ -122.401732349384048, 37.805192215433102 ], [ -122.400572958659495, 37.806120993590262 ], [ -122.400963593501643, 37.803563391134098 ], [ -122.400842454396695, 37.803429372376385 ], [ -122.400479886428016, 37.803466118528789 ], [ -122.398551197769294, 37.804602806142064 ], [ -122.398535712754665, 37.804606489036345 ], [ -122.398520052204233, 37.804603307533085 ], [ -122.398559631153262, 37.804526444219782 ], [ -122.398227927335071, 37.804281130126874 ], [ -122.398222420518948, 37.804268857764917 ], [ -122.398225564864617, 37.80425644615601 ], [ -122.399971727587683, 37.803225025895152 ], [ -122.399602741790233, 37.802807948916175 ], [ -122.397824683987196, 37.803810348092696 ], [ -122.397809199101829, 37.803814030892141 ], [ -122.397793538749667, 37.803810849291956 ], [ -122.397517150597622, 37.803494597437428 ], [ -122.397511661501127, 37.80348301148031 ], [ -122.3975148062854, 37.803470600157546 ], [ -122.399615259663278, 37.802282404799911 ], [ -122.39972332623249, 37.802176282179168 ], [ -122.399409670069147, 37.801824238674413 ], [ -122.399371692841143, 37.801828283680663 ], [ -122.399347556878283, 37.801832106025373 ], [ -122.399325151849467, 37.801835900487347 ], [ -122.399302764032285, 37.801840381390434 ], [ -122.39928039377223, 37.801845548729034 ], [ -122.399258040722614, 37.801851402508781 ], [ -122.399235705920944, 37.801857942712921 ], [ -122.399215101014406, 37.801864455053376 ], [ -122.399192800984025, 37.8018723684091 ], [ -122.399172231180287, 37.801880253347079 ], [ -122.399151696503523, 37.801889511710215 ], [ -122.397115576147101, 37.803021039584841 ], [ -122.397100091038084, 37.803024722022734 ], [ -122.397084431235825, 37.80302154059693 ], [ -122.396146888856933, 37.801962584949095 ], [ -122.396143112804936, 37.801950284664713 ], [ -122.396146275276891, 37.801938559540567 ], [ -122.396156183429241, 37.801919858733392 ], [ -122.398383194135846, 37.80067265166084 ], [ -122.398279805658603, 37.800555512940733 ], [ -122.397989357773142, 37.800230561089471 ], [ -122.395767421385884, 37.801472872307706 ], [ -122.395751936546873, 37.801476555118818 ], [ -122.395736277114864, 37.801473372963947 ], [ -122.395444597217249, 37.801167663046662 ], [ -122.395439108270097, 37.801156076993678 ], [ -122.395442253641278, 37.801143665440662 ], [ -122.397525741108808, 37.799970200398498 ], [ -122.397542727338319, 37.799957565794791 ], [ -122.397573204312565, 37.79993098020509 ], [ -122.397588407347527, 37.799916314120374 ], [ -122.397601862985724, 37.799900989703779 ], [ -122.397613588764841, 37.799885693121588 ], [ -122.397625296656045, 37.799869710378033 ], [ -122.397635256446222, 37.799853068765884 ], [ -122.397583045956239, 37.799774248814408 ], [ -122.397381692278955, 37.799548810060863 ], [ -122.397362923291766, 37.799559412884541 ], [ -122.397350478314067, 37.799546565387622 ], [ -122.39725607725606, 37.79944232919879 ], [ -122.396973022275191, 37.799609635784066 ], [ -122.396895724416169, 37.799632854304804 ], [ -122.396944532454214, 37.799578505110418 ], [ -122.39725156444139, 37.799401198172731 ], [ -122.397069823583166, 37.79919810547964 ], [ -122.39674205306757, 37.798834607866056 ], [ -122.396702784737215, 37.798855840868825 ], [ -122.396669034992286, 37.798822047566397 ], [ -122.396624664146429, 37.798846796498019 ], [ -122.396609180236553, 37.798850479143326 ], [ -122.396595251158345, 37.798847269554258 ], [ -122.396579311359616, 37.798833104740751 ], [ -122.395709347701867, 37.79933397933474 ], [ -122.395681634538477, 37.799401037179429 ], [ -122.395581007954107, 37.799459652414193 ], [ -122.395491073098484, 37.799462471471649 ], [ -122.39520444855259, 37.799625711063314 ], [ -122.394470973718072, 37.800049529845779 ], [ -122.394485183177821, 37.80006372301127 ], [ -122.394463863656298, 37.80011007561265 ], [ -122.394495883468409, 37.800143897322769 ], [ -122.394436254505337, 37.800181251519263 ], [ -122.394338714865782, 37.800157409688907 ], [ -122.39425176311174, 37.800073652886503 ], [ -122.394244541007424, 37.799994109133792 ], [ -122.39430588261277, 37.79995604107372 ], [ -122.39433954456824, 37.799986402561352 ], [ -122.394405256913004, 37.79998397351315 ], [ -122.394419466347642, 37.799998166686734 ], [ -122.395383240540369, 37.799442229567333 ], [ -122.3954446687179, 37.79940759283992 ], [ -122.395460481077649, 37.799348967284097 ], [ -122.395564480002861, 37.799286864055659 ], [ -122.395650954616428, 37.799284100562168 ], [ -122.396137291028239, 37.799011206820815 ], [ -122.39652596834469, 37.798777651667436 ], [ -122.396513506102295, 37.798764117365188 ], [ -122.396508017858096, 37.798752531345663 ], [ -122.396511162609784, 37.798740120038758 ], [ -122.396521298172757, 37.798730342643047 ], [ -122.396562296308161, 37.798709081605075 ], [ -122.396530276519627, 37.798675260441449 ], [ -122.396574647301449, 37.798650511819105 ], [ -122.396522999163949, 37.798593657491452 ], [ -122.396499841034597, 37.79856793447253 ], [ -122.396467423337157, 37.798586310883579 ], [ -122.396357190239343, 37.798472027219738 ], [ -122.396393032843889, 37.798452222590186 ], [ -122.395860656588496, 37.797870888391557 ], [ -122.395839947322898, 37.79787328122778 ], [ -122.395814065475506, 37.797876444215788 ], [ -122.395788200806479, 37.797880293644518 ], [ -122.395762353661098, 37.797884829508355 ], [ -122.395738254199486, 37.7978900242679 ], [ -122.395712442099452, 37.797895933002444 ], [ -122.395664348290325, 37.797910440317423 ], [ -122.395640318568425, 37.797918380825799 ], [ -122.395616307044421, 37.797927007209815 ], [ -122.39557006603053, 37.797946292034375 ], [ -122.395547836878862, 37.797956950195463 ], [ -122.395525625244971, 37.797968294793179 ], [ -122.395505143760289, 37.797979611576075 ], [ -122.393842000273821, 37.798938215357374 ], [ -122.393826533722603, 37.798942584352829 ], [ -122.39381087421539, 37.798939401951962 ], [ -122.393557994085199, 37.798661220282753 ], [ -122.393552505963697, 37.798649634131031 ], [ -122.393557381363038, 37.798637194571512 ], [ -122.395454327884067, 37.797548480176857 ], [ -122.395493853667872, 37.797469558194479 ], [ -122.395422650268614, 37.797392416327632 ], [ -122.395305000587612, 37.797394307309993 ], [ -122.395131013605095, 37.797494618186263 ], [ -122.394995545751499, 37.797340909474201 ], [ -122.395176347980751, 37.797236368651362 ], [ -122.395203657816268, 37.797153523030403 ], [ -122.395175134630833, 37.797121018758673 ], [ -122.395015962104765, 37.797123576784635 ], [ -122.394842063300572, 37.797227319439131 ], [ -122.394877471902745, 37.797258339703205 ], [ -122.393211663425149, 37.798247880172283 ], [ -122.393197909634068, 37.798251534593128 ], [ -122.393182250998592, 37.798248352371843 ], [ -122.392924377254829, 37.797977803222231 ], [ -122.392918871782612, 37.79796553059834 ], [ -122.392922034593937, 37.797953805551998 ], [ -122.392933760337485, 37.797938509440698 ], [ -122.393022573378389, 37.797891760094423 ], [ -122.393418332763261, 37.797664281733844 ], [ -122.39349443929828, 37.797594387482981 ], [ -122.393614723501102, 37.797492194551531 ], [ -122.39379620770903, 37.797346441866999 ], [ -122.393858891962495, 37.797293244534764 ], [ -122.39453577454303, 37.796890939592352 ], [ -122.394518070397197, 37.796875429409333 ], [ -122.394797677744066, 37.796708870398604 ], [ -122.394681926486655, 37.796581626428896 ], [ -122.394272580881463, 37.796818941592662 ], [ -122.39351702175675, 37.797258217667746 ], [ -122.39355080472113, 37.797293385033349 ], [ -122.39316857384685, 37.797508285701419 ], [ -122.393092042374221, 37.797425733952373 ], [ -122.393468994757654, 37.797207484670842 ], [ -122.393490421626595, 37.797233236341413 ], [ -122.39412658804784, 37.796863176781166 ], [ -122.393932453058198, 37.796648603921177 ], [ -122.393850369991199, 37.796687691964301 ], [ -122.393741908884792, 37.796574751186853 ], [ -122.393880385256736, 37.796507288896663 ], [ -122.393597295890373, 37.796198002709581 ], [ -122.393523863725633, 37.796236951615114 ], [ -122.393397437145097, 37.796098203833878 ], [ -122.393481197043371, 37.796057028727645 ], [ -122.393264277786542, 37.795831146645604 ], [ -122.39319930347132, 37.795862405337601 ], [ -122.393014017140644, 37.7956552428926 ], [ -122.39305487457618, 37.795628491529783 ], [ -122.392997761288825, 37.795560736430133 ], [ -122.392816418294032, 37.795643993697439 ], [ -122.391589731865537, 37.796205501614423 ], [ -122.391196361068637, 37.795779176166889 ], [ -122.39241116638668, 37.794819563891082 ], [ -122.39226709396803, 37.794667363398794 ], [ -122.391941265746169, 37.794853886332461 ], [ -122.391649609982679, 37.794547480215279 ], [ -122.391828732520565, 37.794445031825084 ], [ -122.391802064349278, 37.794417304057546 ], [ -122.391945361729881, 37.794335345459139 ], [ -122.391890260341384, 37.794278544568918 ], [ -122.392507683750154, 37.79377708635252 ], [ -122.39249932896719, 37.793768814133983 ], [ -122.392702063215523, 37.793608792659974 ], [ -122.39270797067924, 37.793614415765255 ], [ -122.394150899798674, 37.794987788593353 ], [ -122.394745701490038, 37.794478274267938 ], [ -122.394745733667804, 37.794478246556643 ], [ -122.394747579547172, 37.794479718347191 ], [ -122.394971722038662, 37.794300351169447 ], [ -122.395317307728533, 37.794023797689903 ], [ -122.39533276386878, 37.794011429145591 ], [ -122.395622922555773, 37.793779228585429 ], [ -122.395983670970253, 37.793501461560965 ], [ -122.396302004290646, 37.793256350429786 ], [ -122.396302004629632, 37.793256350149633 ], [ -122.396376076969489, 37.793198086984006 ], [ -122.396504486670068, 37.793097082520163 ], [ -122.39738344591872, 37.792405521295422 ], [ -122.397840810091907, 37.792047595331439 ], [ -122.39826444614279, 37.791716060417308 ], [ -122.398264448861653, 37.791716058450703 ], [ -122.399148598683425, 37.79101664916432 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399148705293825, 37.791016743863253 ], [ -122.399148704954868, 37.791016744143391 ], [ -122.399494165511513, 37.791324916192721 ], [ -122.399572029409299, 37.791326533708947 ], [ -122.399763353084495, 37.792257948007119 ], [ -122.399958735737897, 37.793209101900771 ], [ -122.400058259594957, 37.793693587424592 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":10,\"ZIP_CODE\":94104,\"ID\":94104},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.400067228055164, 37.790303858609981 ], [ -122.401375490979433, 37.789264321921429 ], [ -122.4018939793784, 37.788856309111516 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.401960433993054, 37.788922171387867 ], [ -122.401997470431979, 37.789102650051042 ], [ -122.402189645264357, 37.790039096806382 ], [ -122.402532679537515, 37.789995540560724 ], [ -122.40273920526738, 37.789969317269424 ], [ -122.403841563913829, 37.789829338872408 ], [ -122.403934377264903, 37.790286026286218 ], [ -122.404030724679231, 37.790760095776051 ], [ -122.404219835191427, 37.791690583910508 ], [ -122.40441842761642, 37.79263865633952 ], [ -122.404613389070732, 37.793564931583006 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404370134182003, 37.793598168205968 ], [ -122.404016114128936, 37.793646309359175 ], [ -122.40308689535884, 37.793763518993806 ], [ -122.402957360909014, 37.793779857671261 ], [ -122.40215951272063, 37.793880489466119 ], [ -122.402133431503088, 37.793883778773662 ], [ -122.401314504615144, 37.793987063215781 ], [ -122.400149325778898, 37.794134165695652 ], [ -122.400148778887171, 37.794134234665414 ], [ -122.400058259594957, 37.793693587424592 ], [ -122.399958735737897, 37.793209101900771 ], [ -122.399763353084495, 37.792257948007119 ], [ -122.399572029409299, 37.791326533708947 ], [ -122.399494165511513, 37.791324916192721 ], [ -122.399148704954868, 37.791016744143391 ], [ -122.399148705293825, 37.791016743863253 ], [ -122.39914859901539, 37.791016648609592 ], [ -122.399222136170906, 37.790956214990921 ], [ -122.399480001759599, 37.790744297277485 ], [ -122.399919790020746, 37.790414442896498 ], [ -122.400024135710936, 37.790336179680637 ], [ -122.400067228055164, 37.790303858609981 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":11,\"ZIP_CODE\":94108,\"ID\":94108},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.404219835191427, 37.791690583910508 ], [ -122.404030724679231, 37.790760095776051 ], [ -122.403934377264903, 37.790286026286218 ], [ -122.403841563913829, 37.789829338872408 ], [ -122.40273920526738, 37.789969317269424 ], [ -122.402532679537515, 37.789995540560724 ], [ -122.402189645264357, 37.790039096806382 ], [ -122.401997470431979, 37.789102650051042 ], [ -122.401960433993054, 37.788922171387867 ], [ -122.402065730098272, 37.788721152097366 ], [ -122.402404665507532, 37.788463925757384 ], [ -122.402903089488873, 37.788085654318181 ], [ -122.40290309900054, 37.788085647297414 ], [ -122.403239953883428, 37.787802352067843 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403925775648347, 37.787250481113581 ], [ -122.404583316787637, 37.786730565897571 ], [ -122.404583317133643, 37.786730565891986 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.4045836593057, 37.786730668317922 ], [ -122.404583659644615, 37.786730668037741 ], [ -122.404849901045083, 37.78680995410096 ], [ -122.405346079234604, 37.786747618574395 ], [ -122.40639887788349, 37.786615347203238 ], [ -122.408036236552462, 37.786409613336666 ], [ -122.408226725993813, 37.78735926231473 ], [ -122.408401550373739, 37.788293193957799 ], [ -122.408595045382015, 37.789225614946417 ], [ -122.408595692092703, 37.789225606675593 ], [ -122.410242438640608, 37.789016337723744 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.412075867445836, 37.789739906579548 ], [ -122.412154318600955, 37.79012444884593 ], [ -122.412266187164306, 37.790672783986288 ], [ -122.413915879652706, 37.790463115032836 ], [ -122.414107303557273, 37.79141647884336 ], [ -122.414293264567746, 37.792342610236801 ], [ -122.414381908270244, 37.792784070108439 ], [ -122.414470074462415, 37.79322314646798 ], [ -122.414647632176155, 37.794109807074861 ], [ -122.414825631965456, 37.794988155997579 ], [ -122.413187771447625, 37.795197816725128 ], [ -122.412335878555311, 37.795306857217497 ], [ -122.411893657029495, 37.795363458292385 ], [ -122.411541531721369, 37.795408526524874 ], [ -122.411081667103218, 37.795467382264249 ], [ -122.40989875147848, 37.795616424537307 ], [ -122.40954722057397, 37.795660707190791 ], [ -122.409343544204319, 37.795686363949223 ], [ -122.409077868767497, 37.795719830108119 ], [ -122.409075520379659, 37.795720125786943 ], [ -122.408743234497422, 37.795761981540487 ], [ -122.408704654154022, 37.795766841471867 ], [ -122.408253202051654, 37.795823706218044 ], [ -122.40779730388941, 37.795880962818103 ], [ -122.407563768850565, 37.795910291726024 ], [ -122.407432089697608, 37.795926828732469 ], [ -122.407319877605971, 37.795940921047873 ], [ -122.407083680819795, 37.795970583772274 ], [ -122.406652352369846, 37.796024750165536 ], [ -122.406350669109713, 37.796061968858353 ], [ -122.406224736410849, 37.796077505131322 ], [ -122.405862652252878, 37.796122174218517 ], [ -122.405142977212691, 37.796210954635839 ], [ -122.404957208224928, 37.795326691921687 ], [ -122.404875610619072, 37.794859533373135 ], [ -122.404796472837063, 37.794453004909116 ], [ -122.404710582177955, 37.794021380197684 ], [ -122.404613421373455, 37.793565084338127 ], [ -122.404613389070732, 37.793564931583006 ], [ -122.40441842761642, 37.79263865633952 ], [ -122.404219835191427, 37.791690583910508 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":12,\"ZIP_CODE\":94103,\"ID\":94103},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.403877699904385, 37.770062866265505 ], [ -122.403615112128733, 37.769893190404822 ], [ -122.403406518421917, 37.769807099957397 ], [ -122.403037758616776, 37.769828657390953 ], [ -122.402926809989424, 37.76864812762463 ], [ -122.401962350744, 37.768706209224241 ], [ -122.401842503677628, 37.76743077711658 ], [ -122.400877239583821, 37.76748837440357 ], [ -122.400680751363893, 37.767503407870436 ], [ -122.4004285874814, 37.76730030349627 ], [ -122.399677637243116, 37.766695444602661 ], [ -122.39980174760619, 37.766587745781216 ], [ -122.399760960198876, 37.766250252443854 ], [ -122.40075504647362, 37.766190486570189 ], [ -122.401720515816834, 37.766132520250999 ], [ -122.401604204317508, 37.764894634517518 ], [ -122.401598999326723, 37.764839236400867 ], [ -122.402563396844656, 37.764781034314389 ], [ -122.403527034519726, 37.764722870371436 ], [ -122.404496977488066, 37.764664317561468 ], [ -122.404841911264157, 37.764643492872132 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.405089603673972, 37.764628538361961 ], [ -122.405463420216606, 37.764605967981389 ], [ -122.406005237363416, 37.764573252226405 ], [ -122.406429152848446, 37.764547653527842 ], [ -122.406429154577822, 37.764547653499882 ], [ -122.406859230067056, 37.764521681123377 ], [ -122.407419737953433, 37.764487829924626 ], [ -122.407534229395537, 37.765783299286987 ], [ -122.408544242939584, 37.765722599679115 ], [ -122.410486689274492, 37.765605838622655 ], [ -122.411455262151748, 37.765547605227276 ], [ -122.412416426589019, 37.765489809283601 ], [ -122.413105243007919, 37.765447608576679 ], [ -122.415308388038014, 37.765314639624691 ], [ -122.416387522888556, 37.765249494040667 ], [ -122.41748659577631, 37.765183134636899 ], [ -122.418579138459435, 37.765117159398407 ], [ -122.418698425401715, 37.765109955145007 ], [ -122.419668972681833, 37.765051337054757 ], [ -122.420482492120144, 37.765002197009743 ], [ -122.420580607922048, 37.764996270018614 ], [ -122.420901270224292, 37.764976898858535 ], [ -122.421239593012103, 37.764956459387825 ], [ -122.421381475659516, 37.764947887797014 ], [ -122.421886445840627, 37.764917378530825 ], [ -122.42285341443899, 37.764858950163855 ], [ -122.423112421134832, 37.764843298770074 ], [ -122.424102683503691, 37.764783452170008 ], [ -122.424574375550648, 37.764754942537614 ], [ -122.426381062356072, 37.764645726997848 ], [ -122.426381062758907, 37.764645729188807 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426490697526233, 37.765773605772118 ], [ -122.426518991136163, 37.766066400051031 ], [ -122.426538465047457, 37.766267922745747 ], [ -122.426572006355102, 37.766615035704866 ], [ -122.426615944414863, 37.767069730096857 ], [ -122.426693183296933, 37.76786901846797 ], [ -122.426693183304053, 37.767869018742559 ], [ -122.426902347012131, 37.769049368265463 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.424852598682307, 37.770747745074537 ], [ -122.423971653706317, 37.771401668385572 ], [ -122.423707555870649, 37.771638138620823 ], [ -122.42361952356508, 37.771716961429995 ], [ -122.423479344850293, 37.771827141530693 ], [ -122.423258913662679, 37.772000399157754 ], [ -122.422619690282815, 37.772502817311128 ], [ -122.422169936976104, 37.77285593639396 ], [ -122.421941247178026, 37.773036050819961 ], [ -122.421284370393934, 37.77356039048216 ], [ -122.421008229272687, 37.773780811646674 ], [ -122.420910608715644, 37.773852419132908 ], [ -122.420699239041511, 37.774007568561714 ], [ -122.420698854806645, 37.774007872313192 ], [ -122.42069877383058, 37.774007805509001 ], [ -122.420254278900828, 37.774358674888319 ], [ -122.420254205644639, 37.774358732668077 ], [ -122.419544527888206, 37.774918988504218 ], [ -122.419256088091217, 37.775146694295351 ], [ -122.419256086565611, 37.775146688826375 ], [ -122.419255604623004, 37.775147068879761 ], [ -122.419255606501622, 37.775147074617692 ], [ -122.419255600381447, 37.775147078837719 ], [ -122.418683590110874, 37.775586568849455 ], [ -122.417757669457401, 37.776334062520824 ], [ -122.417501462863584, 37.776540893581355 ], [ -122.416757677810395, 37.777126789723972 ], [ -122.416291701725271, 37.777493842582118 ], [ -122.416291701393504, 37.777493843136881 ], [ -122.416024573316548, 37.777713893977598 ], [ -122.415925978170975, 37.777795112873314 ], [ -122.414741221622947, 37.778719429321896 ], [ -122.412512255967954, 37.78047851222005 ], [ -122.412243715889375, 37.780689421480552 ], [ -122.411972359306191, 37.780902540886757 ], [ -122.411972358960256, 37.78090254089237 ], [ -122.411625130786987, 37.781189627190479 ], [ -122.410716598656492, 37.781899054565059 ], [ -122.410292026384454, 37.782230575136197 ], [ -122.40895215871069, 37.783287852576514 ], [ -122.408597310055299, 37.783569788681966 ], [ -122.408066548174432, 37.783991486066526 ], [ -122.408066547510714, 37.783991487176031 ], [ -122.407625723544982, 37.784337071806426 ], [ -122.406821367207598, 37.78496763543707 ], [ -122.405831011063668, 37.78574398921409 ], [ -122.405371564397683, 37.786107287119592 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.404583317133643, 37.786730565891986 ], [ -122.404583316787637, 37.786730565897571 ], [ -122.403925775648347, 37.787250481113581 ], [ -122.403430800369534, 37.787641848645734 ], [ -122.403342641535644, 37.787274627660381 ], [ -122.403028734856406, 37.78702485040828 ], [ -122.403028193190352, 37.787024418822838 ], [ -122.402575227394536, 37.786678208153539 ], [ -122.402025419766176, 37.786248152290575 ], [ -122.401489835489045, 37.785829214187501 ], [ -122.401480059865179, 37.785821567351917 ], [ -122.400998469557948, 37.785444857061968 ], [ -122.400994549885382, 37.785441790992316 ], [ -122.400468483886854, 37.785030285141588 ], [ -122.399369533643153, 37.785899247741973 ], [ -122.397811613142864, 37.784666586003652 ], [ -122.398930331092998, 37.783784933304034 ], [ -122.399891477967842, 37.783025880124256 ], [ -122.400019020063766, 37.782925153640136 ], [ -122.400374366843309, 37.782644515545172 ], [ -122.401159718585049, 37.782024266952142 ], [ -122.403387209275081, 37.78026497235011 ], [ -122.404431250807633, 37.779440334605447 ], [ -122.405615266425855, 37.778505104539292 ], [ -122.405068508898097, 37.778068981419388 ], [ -122.404605505853894, 37.777699659794813 ], [ -122.404070256642925, 37.777272702482797 ], [ -122.403673439982725, 37.776956165214415 ], [ -122.403505778866929, 37.776822422431877 ], [ -122.40337073826143, 37.776714700215486 ], [ -122.402524096584528, 37.776039321403807 ], [ -122.400981567656032, 37.774808775969753 ], [ -122.400592319049849, 37.774498244536282 ], [ -122.400212340771517, 37.774195105423317 ], [ -122.400212340411557, 37.77419510487973 ], [ -122.399433130909728, 37.773573455617395 ], [ -122.401656680421155, 37.7718171255028 ], [ -122.403877699904385, 37.770062866265505 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":13,\"ZIP_CODE\":94115,\"ID\":94115},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.447679208068394, 37.79170289872647 ], [ -122.44720637942703, 37.791763091830468 ], [ -122.446299547924625, 37.791878529215651 ], [ -122.446299573716246, 37.791878694429421 ], [ -122.446446033268856, 37.792810866507708 ], [ -122.446446456540414, 37.792813716046261 ], [ -122.446587034424141, 37.793765298555968 ], [ -122.445035345747925, 37.79396273368863 ], [ -122.443384198876302, 37.794172877898092 ], [ -122.441712771485612, 37.794385578392699 ], [ -122.440870170501768, 37.794492795601883 ], [ -122.440868123190498, 37.794493055920753 ], [ -122.440045216960385, 37.794597761146619 ], [ -122.438402484723895, 37.794806973138577 ], [ -122.436760975027326, 37.795016021968365 ], [ -122.435113289920963, 37.795225833115033 ], [ -122.433469062960725, 37.795435179810816 ], [ -122.431827832563371, 37.79564412085616 ], [ -122.430182832162615, 37.795853517593606 ], [ -122.429989276973558, 37.794894937362344 ], [ -122.429803981887915, 37.793977244761322 ], [ -122.429625242377327, 37.793098475947545 ], [ -122.429447192175289, 37.79221689778025 ], [ -122.429269817037863, 37.791338636935983 ], [ -122.429092268257477, 37.790459499306614 ], [ -122.428905308896674, 37.789533740830485 ], [ -122.428712111452299, 37.788577067507021 ], [ -122.428525011003728, 37.78765056176524 ], [ -122.428335613076769, 37.786712655890966 ], [ -122.428241829261111, 37.786248226022003 ], [ -122.428241496400901, 37.78624657727314 ], [ -122.428148186422831, 37.785784488173462 ], [ -122.427988927379005, 37.784953879052303 ], [ -122.427778871361184, 37.783924735360486 ], [ -122.427584325166535, 37.78298604740867 ], [ -122.427490665181679, 37.782522998381459 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.42720275579525, 37.781117127328834 ], [ -122.427016756808811, 37.780193030481158 ], [ -122.42868554474839, 37.779980201690215 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.431951802315041, 37.779564148987781 ], [ -122.433595938040227, 37.779354585436536 ], [ -122.435241012398947, 37.779144878458894 ], [ -122.436884629197735, 37.778937753600381 ], [ -122.437707163233824, 37.778832380147421 ], [ -122.43855227772093, 37.778724107938281 ], [ -122.440213334218711, 37.778511463388014 ], [ -122.441865640580161, 37.778299553615973 ], [ -122.441865640558632, 37.778299552792262 ], [ -122.441865967212379, 37.778299511152262 ], [ -122.441865967226732, 37.778299511701405 ], [ -122.443506919728407, 37.778089213415292 ], [ -122.445155752336433, 37.777886506805444 ], [ -122.446846469920573, 37.777669108909684 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.447351927163439, 37.780152061280916 ], [ -122.447517982733274, 37.781069510753063 ], [ -122.447576361567485, 37.781175778551805 ], [ -122.447655108274333, 37.781537309189886 ], [ -122.447637300982407, 37.781719177617617 ], [ -122.447450898997985, 37.782195217632704 ], [ -122.447301513844337, 37.78239164221629 ], [ -122.447283893261542, 37.782434393634787 ], [ -122.447225622636566, 37.782575775503396 ], [ -122.447173033991248, 37.782710075581413 ], [ -122.447149436147441, 37.783030677550911 ], [ -122.447549635601661, 37.784906798974511 ], [ -122.447578549915917, 37.784998355250835 ], [ -122.447620107068687, 37.785083635258417 ], [ -122.447621599012791, 37.785194684567394 ], [ -122.447605133356163, 37.785275579171099 ], [ -122.447533801639068, 37.785411415165207 ], [ -122.446849274413296, 37.786186897081585 ], [ -122.446738337622591, 37.786243163870765 ], [ -122.446610465639836, 37.786302298311156 ], [ -122.446792779960703, 37.787261853518181 ], [ -122.4469741664482, 37.788186460801001 ], [ -122.448656220796209, 37.787977164570449 ], [ -122.448835781299294, 37.788856511426609 ], [ -122.449011564525605, 37.789726786022129 ], [ -122.449189583391288, 37.790608108274284 ], [ -122.449367313308201, 37.791487980191171 ], [ -122.447679208068394, 37.79170289872647 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":14,\"ZIP_CODE\":94102,\"ID\":94102},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.419255600381447, 37.775147078837719 ], [ -122.419255606501622, 37.775147074617692 ], [ -122.419255604623004, 37.775147068879761 ], [ -122.419256086565611, 37.775146688826375 ], [ -122.419256088091217, 37.775146694295351 ], [ -122.419544527888206, 37.774918988504218 ], [ -122.420254205644639, 37.774358732668077 ], [ -122.420254278900828, 37.774358674888319 ], [ -122.42069877383058, 37.774007805509001 ], [ -122.420698854806645, 37.774007872313192 ], [ -122.420699239041511, 37.774007568561714 ], [ -122.420910608715644, 37.773852419132908 ], [ -122.421008229272687, 37.773780811646674 ], [ -122.421284370393934, 37.77356039048216 ], [ -122.421941247178026, 37.773036050819961 ], [ -122.422169936976104, 37.77285593639396 ], [ -122.422619690282815, 37.772502817311128 ], [ -122.423258913662679, 37.772000399157754 ], [ -122.423479344850293, 37.771827141530693 ], [ -122.42361952356508, 37.771716961429995 ], [ -122.423707555870649, 37.771638138620823 ], [ -122.423971653706317, 37.771401668385572 ], [ -122.424852598682307, 37.770747745074537 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.426683978032827, 37.769533626427062 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.428428952948465, 37.770451409238284 ], [ -122.428426180541749, 37.770451760600665 ], [ -122.428520173517541, 37.770917853912216 ], [ -122.428614043687816, 37.771383330257258 ], [ -122.428708105393241, 37.771849753810166 ], [ -122.428802101899663, 37.772315846507482 ], [ -122.428990051967787, 37.773247800757233 ], [ -122.42917834319843, 37.774181422747475 ], [ -122.429178342852524, 37.774181422753124 ], [ -122.429178383795261, 37.774181626728783 ], [ -122.429178384487102, 37.774181626717471 ], [ -122.429272630354163, 37.774648924607646 ], [ -122.429365859793393, 37.775111180032617 ], [ -122.42955405641294, 37.77604428584678 ], [ -122.429622603726443, 37.776331628240193 ], [ -122.429626330769736, 37.776513151917612 ], [ -122.429686883301301, 37.776976022351889 ], [ -122.429849199803343, 37.777919295570904 ], [ -122.430047265291023, 37.778850928515844 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.42868554474839, 37.779980201690215 ], [ -122.427016756808811, 37.780193030481158 ], [ -122.42720275579525, 37.781117127328834 ], [ -122.427396397680198, 37.782056939506091 ], [ -122.424108200948226, 37.782477051425644 ], [ -122.422597534917514, 37.782659914421728 ], [ -122.422463744273344, 37.782685368786026 ], [ -122.420817002825672, 37.782894121654586 ], [ -122.420816620257895, 37.78289417046458 ], [ -122.419181703937824, 37.783101400150407 ], [ -122.417528773439201, 37.78331088958533 ], [ -122.415882538254209, 37.783515640872785 ], [ -122.414241572919337, 37.783723783021379 ], [ -122.414430198422409, 37.784657140813145 ], [ -122.414617207572036, 37.785582474657787 ], [ -122.414806647966373, 37.786519819325378 ], [ -122.414995423274036, 37.787453843784689 ], [ -122.414995029297003, 37.787453894134082 ], [ -122.415185269301574, 37.788387930678724 ], [ -122.413541442066688, 37.788598204045492 ], [ -122.411885657146456, 37.788807543703371 ], [ -122.410242438640608, 37.789016337723744 ], [ -122.408595692092703, 37.789225606675593 ], [ -122.408595045382015, 37.789225614946417 ], [ -122.408401550373739, 37.788293193957799 ], [ -122.408226725993813, 37.78735926231473 ], [ -122.408036236552462, 37.786409613336666 ], [ -122.40639887788349, 37.786615347203238 ], [ -122.405346079234604, 37.786747618574395 ], [ -122.404849901045083, 37.78680995410096 ], [ -122.404583659644615, 37.786730668037741 ], [ -122.4045836593057, 37.786730668317922 ], [ -122.404583317818549, 37.786730565606241 ], [ -122.405371564397683, 37.786107287119592 ], [ -122.405831011063668, 37.78574398921409 ], [ -122.406821367207598, 37.78496763543707 ], [ -122.407625723544982, 37.784337071806426 ], [ -122.408066547510714, 37.783991487176031 ], [ -122.408066548174432, 37.783991486066526 ], [ -122.408597310055299, 37.783569788681966 ], [ -122.40895215871069, 37.783287852576514 ], [ -122.410292026384454, 37.782230575136197 ], [ -122.410716598656492, 37.781899054565059 ], [ -122.411625130786987, 37.781189627190479 ], [ -122.411972358960256, 37.78090254089237 ], [ -122.411972359306191, 37.780902540886757 ], [ -122.412243715889375, 37.780689421480552 ], [ -122.412512255967954, 37.78047851222005 ], [ -122.413308361492682, 37.779850246537862 ], [ -122.414741221622947, 37.778719429321896 ], [ -122.415925978170975, 37.777795112873314 ], [ -122.416024573316548, 37.777713893977598 ], [ -122.416291701393504, 37.777493843136881 ], [ -122.416291701725271, 37.777493842582118 ], [ -122.416757677810395, 37.777126789723972 ], [ -122.417501462863584, 37.776540893581355 ], [ -122.417757669457401, 37.776334062520824 ], [ -122.418683590110874, 37.775586568849455 ], [ -122.419255600381447, 37.775147078837719 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":15,\"ZIP_CODE\":94124,\"ID\":94124},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.398991043085772, 37.715338797679301 ], [ -122.398953101659146, 37.715385829745912 ], [ -122.398839335741798, 37.715526852955328 ], [ -122.398735527529269, 37.715727131371494 ], [ -122.398656056102226, 37.71588045500463 ], [ -122.398643015232707, 37.715917722950913 ], [ -122.398565370170317, 37.716139616445538 ], [ -122.398550700112352, 37.7161815389411 ], [ -122.398499278161879, 37.716543657492224 ], [ -122.398561414050022, 37.716897923924947 ], [ -122.398668885707423, 37.717503589023465 ], [ -122.398686680058731, 37.717603871431201 ], [ -122.39871606167118, 37.717769450850419 ], [ -122.398759937061044, 37.718047446028777 ], [ -122.398787867001658, 37.718224410399863 ], [ -122.398799134608808, 37.718275024173522 ], [ -122.398913161799925, 37.718792447247658 ], [ -122.398923285260594, 37.718836507697276 ], [ -122.398942440206895, 37.718921151552976 ], [ -122.398943601880902, 37.718926286066001 ], [ -122.399066465573654, 37.719469200866385 ], [ -122.399125486878972, 37.719730004239857 ], [ -122.399208340593788, 37.719991530079234 ], [ -122.399309038031902, 37.720278334103305 ], [ -122.399419264352986, 37.720532021245042 ], [ -122.399600300155441, 37.720920363637823 ], [ -122.399830553211288, 37.721427080386505 ], [ -122.399925016822507, 37.721517272170509 ], [ -122.40038213245046, 37.722633285332655 ], [ -122.400661931084002, 37.723316373994798 ], [ -122.400784747286778, 37.723616209251418 ], [ -122.40078474798517, 37.723616209514844 ], [ -122.401182917574161, 37.724588254281116 ], [ -122.401471218295981, 37.725516183407578 ], [ -122.401474642450822, 37.725528485745301 ], [ -122.401822425920514, 37.726775260854211 ], [ -122.401875118236148, 37.726964759399728 ], [ -122.402039596395539, 37.727554690472438 ], [ -122.402105120777605, 37.727789703519925 ], [ -122.402209197740476, 37.728101973566574 ], [ -122.402752244737584, 37.729357163240891 ], [ -122.403021622188092, 37.729901901370539 ], [ -122.403400462440644, 37.730658352585962 ], [ -122.403961731325225, 37.731610333558521 ], [ -122.40405482060109, 37.731789516245591 ], [ -122.40465709692171, 37.732948781873532 ], [ -122.404657097641305, 37.732948782960683 ], [ -122.404878800551302, 37.733336356125804 ], [ -122.405096042524178, 37.733716127227488 ], [ -122.405494571184249, 37.734287050530888 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.406454646881144, 37.735510813404687 ], [ -122.406618861734884, 37.735896873766244 ], [ -122.40670383539134, 37.73630860402433 ], [ -122.406910504104971, 37.737965866913434 ], [ -122.406900092501772, 37.738009155282221 ], [ -122.406867742024218, 37.738143654094841 ], [ -122.406895428755547, 37.738323651840822 ], [ -122.406915744052654, 37.738455725148341 ], [ -122.40693392611557, 37.738601770051126 ], [ -122.406945393527934, 37.738693880058896 ], [ -122.406939830707117, 37.738876165961258 ], [ -122.406936851773693, 37.739087891690275 ], [ -122.406930338448149, 37.739188255204461 ], [ -122.40692734433064, 37.739234393770168 ], [ -122.406923419099328, 37.739294876190314 ], [ -122.406917382338506, 37.739360326809205 ], [ -122.40691098434678, 37.739429695993088 ], [ -122.406910847801768, 37.73943120901172 ], [ -122.406977761294414, 37.739566302699039 ], [ -122.406889400847547, 37.739668564915668 ], [ -122.407641980575562, 37.73960607595005 ], [ -122.407891817991583, 37.739621065115479 ], [ -122.407910235902733, 37.739622169745928 ], [ -122.408137030577961, 37.739635775942595 ], [ -122.408114071971212, 37.739818086787388 ], [ -122.408049229318891, 37.740339110042903 ], [ -122.407964646391505, 37.740662552272049 ], [ -122.407846654253319, 37.741024367104636 ], [ -122.407757062965644, 37.741253133394146 ], [ -122.407625696256517, 37.741528038897464 ], [ -122.407448073794797, 37.741860521932459 ], [ -122.407209491524853, 37.742235666028684 ], [ -122.407179681106143, 37.742274728523746 ], [ -122.406881612449212, 37.742665295031905 ], [ -122.406149889909912, 37.743534947555823 ], [ -122.406047831839984, 37.743656241756561 ], [ -122.405778655290121, 37.743989539741165 ], [ -122.405747110957691, 37.744028598841339 ], [ -122.405475695814175, 37.744427001636382 ], [ -122.405444172050537, 37.744486004341084 ], [ -122.405202201087945, 37.744938889063008 ], [ -122.405179630488661, 37.744981134097749 ], [ -122.405051159825348, 37.745369647465395 ], [ -122.40491012189095, 37.745826558312224 ], [ -122.40472494664624, 37.746505061967419 ], [ -122.404496510435962, 37.747208430674682 ], [ -122.404390053649081, 37.747536214374357 ], [ -122.404249303912664, 37.747969580021021 ], [ -122.404105247604519, 37.748421906188959 ], [ -122.404104297626958, 37.748424888760844 ], [ -122.404103617259366, 37.748427025595028 ], [ -122.404051662308461, 37.74859015799958 ], [ -122.403990987296382, 37.74878066971192 ], [ -122.403865672790147, 37.749174139069758 ], [ -122.403826366530197, 37.749298003881435 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403667613571599, 37.749458113882532 ], [ -122.403568849778438, 37.749475672986911 ], [ -122.403260968692152, 37.749530409553117 ], [ -122.403203674195439, 37.749534260068266 ], [ -122.402968925612569, 37.749550036933734 ], [ -122.402866309087059, 37.749478458911533 ], [ -122.402866303074774, 37.749478453789486 ], [ -122.402746245055127, 37.749375020002113 ], [ -122.402215286387033, 37.749358244852793 ], [ -122.402063015501824, 37.749396333205695 ], [ -122.40206163398004, 37.749396678541473 ], [ -122.397285415820363, 37.749684622841229 ], [ -122.396273544250747, 37.74974560058007 ], [ -122.395307862010839, 37.749803786070743 ], [ -122.395231400136637, 37.749808114846253 ], [ -122.393340265733656, 37.749915158351328 ], [ -122.393322792145199, 37.749917097944952 ], [ -122.393314555211816, 37.749918012051069 ], [ -122.392381992909165, 37.750021506018193 ], [ -122.392354048972692, 37.750025545503377 ], [ -122.392350740047519, 37.750026024134087 ], [ -122.391929934247997, 37.750086859865874 ], [ -122.391714686151033, 37.750109474739723 ], [ -122.391705979739527, 37.750110389401705 ], [ -122.391693805117924, 37.750111668488358 ], [ -122.391418367221192, 37.750140606282606 ], [ -122.390433038183829, 37.750118341633915 ], [ -122.389478017653872, 37.750145107673561 ], [ -122.388507729357968, 37.750205975353602 ], [ -122.387585270262662, 37.750263378063039 ], [ -122.387633974640565, 37.751559603455405 ], [ -122.387756280477618, 37.752835611637821 ], [ -122.386811646507837, 37.752890377320497 ], [ -122.385845701796796, 37.752946370817803 ], [ -122.38487975596891, 37.753002356118301 ], [ -122.383913808692512, 37.753058333776714 ], [ -122.383438933023925, 37.753085850281579 ], [ -122.382951309683904, 37.753114103639163 ], [ -122.382560989570067, 37.75313671714089 ], [ -122.382560765120331, 37.753136419940226 ], [ -122.381985040410456, 37.753169774197133 ], [ -122.381708677132991, 37.753185861496782 ], [ -122.38168223616799, 37.75316636834409 ], [ -122.381661069503124, 37.753150224878624 ], [ -122.381641614233217, 37.753133367358956 ], [ -122.381622142285593, 37.753115823651214 ], [ -122.381604399103537, 37.753098251789652 ], [ -122.381556374012021, 37.753046140647704 ], [ -122.381542176020147, 37.753031946080576 ], [ -122.381526284033754, 37.753019152008761 ], [ -122.381510409098041, 37.753007044663264 ], [ -122.381492804694034, 37.752994964377287 ], [ -122.381475235761897, 37.75298425724899 ], [ -122.381436673423011, 37.752964271381444 ], [ -122.381417427536135, 37.752955650919624 ], [ -122.381375546404385, 37.752941211547565 ], [ -122.381354640609899, 37.75293536475278 ], [ -122.381333752201158, 37.75293020440273 ], [ -122.381311151719544, 37.752925758105562 ], [ -122.381288586703278, 37.752922684964496 ], [ -122.381266038711004, 37.75292029772389 ], [ -122.381243508115659, 37.752918597476601 ], [ -122.381221011931387, 37.752918269852813 ], [ -122.381184718304496, 37.752919535917627 ], [ -122.381162274601664, 37.752921267347901 ], [ -122.381139865666057, 37.752924371945412 ], [ -122.381117473763211, 37.752928162992639 ], [ -122.381095099929965, 37.752932640473013 ], [ -122.381072743135178, 37.752937804677757 ], [ -122.381050368943576, 37.752942281880571 ], [ -122.381027977030755, 37.752946072910795 ], [ -122.38100556843483, 37.752949177751802 ], [ -122.38098141298515, 37.752951623194257 ], [ -122.380958968929022, 37.752953354865902 ], [ -122.380936508184703, 37.752954400073762 ], [ -122.380889821786923, 37.752955145192665 ], [ -122.380865579793621, 37.752954158637948 ], [ -122.380843066568161, 37.752953144488032 ], [ -122.380797970609038, 37.752948370381233 ], [ -122.380773676141516, 37.752945324467625 ], [ -122.380728475930468, 37.752936431643164 ], [ -122.380707587564899, 37.752931271182163 ], [ -122.380684952713352, 37.752925451862353 ], [ -122.380667539609021, 37.752920922661339 ], [ -122.380641464261203, 37.752915844962516 ], [ -122.380615405953847, 37.752911453986556 ], [ -122.380589365011275, 37.752907748904235 ], [ -122.38056161234158, 37.752904758130924 ], [ -122.38053562319034, 37.752903112662466 ], [ -122.380507905274172, 37.752901494773603 ], [ -122.380480204733729, 37.752900563326783 ], [ -122.380433519056851, 37.752901308253911 ], [ -122.380238268028847, 37.752909917328637 ], [ -122.380029271599042, 37.752922178983432 ], [ -122.379994654613483, 37.752921358032168 ], [ -122.379959933403185, 37.752916418104356 ], [ -122.379944267166053, 37.752912547382195 ], [ -122.379912864887501, 37.752902060691724 ], [ -122.379897129538875, 37.752895444712365 ], [ -122.379883105576184, 37.752888114156903 ], [ -122.37987077565819, 37.752879383675683 ], [ -122.379861869913029, 37.752869224847544 ], [ -122.379854658198184, 37.752857665545626 ], [ -122.379850887331656, 37.752845364357682 ], [ -122.379850574694558, 37.752833008281776 ], [ -122.379853720278177, 37.752820597043424 ], [ -122.37985861234651, 37.752808844946877 ], [ -122.379865250884947, 37.752797751442777 ], [ -122.379882315832674, 37.752788551525661 ], [ -122.379911450501524, 37.752777785898218 ], [ -122.379938873076583, 37.752767734300228 ], [ -122.379980076404905, 37.752755402685878 ], [ -122.379993770314556, 37.752749690431031 ], [ -122.380005682657426, 37.752741946417643 ], [ -122.380015797100285, 37.752731484181091 ], [ -122.380022435608339, 37.752720390667974 ], [ -122.380025580812656, 37.752707979430312 ], [ -122.380025233406471, 37.752694250457282 ], [ -122.380007137303139, 37.752457618747144 ], [ -122.379959987158614, 37.752029854003567 ], [ -122.379883834201038, 37.752034160974858 ], [ -122.376087329752295, 37.752248827801239 ], [ -122.375771286398191, 37.74870623521705 ], [ -122.382681120134976, 37.748282212078522 ], [ -122.384914143794745, 37.748144880283171 ], [ -122.384920340493579, 37.748184611607194 ], [ -122.384650947212847, 37.748201967818915 ], [ -122.384670831989453, 37.748303972394424 ], [ -122.384688156495528, 37.748305068501672 ], [ -122.384721007906435, 37.748304543099884 ], [ -122.384738280527714, 37.748303580123398 ], [ -122.384755518322564, 37.748301244247457 ], [ -122.384771026767297, 37.748298936027936 ], [ -122.384788230079309, 37.748295227244739 ], [ -122.384803704040692, 37.748291546118445 ], [ -122.384819160241221, 37.748287178547208 ], [ -122.384850004018801, 37.748275697588227 ], [ -122.384865408317154, 37.748269270660025 ], [ -122.384882471958647, 37.748260070280949 ], [ -122.384896077598697, 37.748250925484513 ], [ -122.384911429637739, 37.748242438930326 ], [ -122.384926799094799, 37.748234639097134 ], [ -122.384942203039174, 37.748228212438939 ], [ -122.384959354081715, 37.748222444285403 ], [ -122.384976521838055, 37.748217362314264 ], [ -122.384993725120907, 37.748213653501011 ], [ -122.385009181639873, 37.748209285899271 ], [ -122.385022856909728, 37.748202886607288 ], [ -122.385036497346164, 37.748195114416852 ], [ -122.385046610116049, 37.748184651746037 ], [ -122.385053264882572, 37.748174244389467 ], [ -122.385059867399946, 37.748161777687201 ], [ -122.385062977084218, 37.748147993403109 ], [ -122.385067868014104, 37.74813624080646 ], [ -122.38507799784864, 37.748126464861393 ], [ -122.385089892189129, 37.748118033594025 ], [ -122.38510529608827, 37.748111606639817 ], [ -122.385120787757828, 37.748108611914709 ], [ -122.385138077744813, 37.748108335328041 ], [ -122.385155454814836, 37.748111490980968 ], [ -122.385169426138901, 37.748116761295172 ], [ -122.385181755211505, 37.748125491789409 ], [ -122.385192373382367, 37.748134935830123 ], [ -122.385204720216592, 37.748144352490229 ], [ -122.385218761226213, 37.748152368866798 ], [ -122.385234478972677, 37.748158297687098 ], [ -122.385250161900544, 37.748162854157655 ], [ -122.385267521563421, 37.748165323071063 ], [ -122.385284846398747, 37.748166419359649 ], [ -122.385307218551446, 37.748161941354901 ], [ -122.385322623132453, 37.748155514361287 ], [ -122.385336298358496, 37.748149114758291 ], [ -122.385351667751635, 37.748141314869592 ], [ -122.385370425465482, 37.748130713860917 ], [ -122.385385847107514, 37.748124973312947 ], [ -122.38540299740022, 37.748119205105795 ], [ -122.385420183219992, 37.748114809781839 ], [ -122.385437403184611, 37.748111787363236 ], [ -122.385454641265781, 37.748109451653981 ], [ -122.385471896059883, 37.748107801852534 ], [ -122.385494373386805, 37.748107442216863 ], [ -122.38551168079205, 37.748107852023772 ], [ -122.385530752036246, 37.748109607060321 ], [ -122.385548128777415, 37.748112762660895 ], [ -122.385617567456492, 37.748122639221975 ], [ -122.385652269381737, 37.748126891038808 ], [ -122.385685225236202, 37.748130484058379 ], [ -122.385719909747124, 37.748134049406957 ], [ -122.385785855632406, 37.748142608581347 ], [ -122.385801503731642, 37.748145791532721 ], [ -122.385816588465147, 37.748146745865732 ], [ -122.385818828224473, 37.748146887748376 ], [ -122.385834354718469, 37.748145265826139 ], [ -122.38585155760299, 37.748141557167763 ], [ -122.385865250211054, 37.74813584422629 ], [ -122.385878890540098, 37.748128071663686 ], [ -122.3858907496043, 37.748118267698949 ], [ -122.385900844474122, 37.748107118511911 ], [ -122.385902260194882, 37.748094734766624 ], [ -122.385907133219177, 37.748082295692257 ], [ -122.385917263620584, 37.748072519389318 ], [ -122.385930956210871, 37.748066806440157 ], [ -122.385953433177363, 37.748066446722468 ], [ -122.385965727831334, 37.748073804232597 ], [ -122.385981463045027, 37.748080419401688 ], [ -122.385998909841959, 37.748086320723502 ], [ -122.38601630437492, 37.74809016297241 ], [ -122.386033664046266, 37.748092631772991 ], [ -122.386050971451041, 37.748093041500702 ], [ -122.386068226580846, 37.748091391880983 ], [ -122.386087192943762, 37.74808902841783 ], [ -122.386104378709106, 37.748084632719518 ], [ -122.386119835135943, 37.748080264972018 ], [ -122.386135256363829, 37.74807452433145 ], [ -122.386150643429275, 37.748067410781196 ], [ -122.386162536953989, 37.748058979691152 ], [ -122.386174396322502, 37.748049175966834 ], [ -122.386184491486219, 37.748038026749811 ], [ -122.386194569220663, 37.74802619108366 ], [ -122.386201171526452, 37.748013724042565 ], [ -122.386204298419131, 37.748000626176299 ], [ -122.386207059358142, 37.747973112895849 ], [ -122.386211932676204, 37.74796067407766 ], [ -122.386218534973267, 37.747948207035471 ], [ -122.386226883695826, 37.747936399040974 ], [ -122.386236996262895, 37.747925936267578 ], [ -122.386250601668095, 37.747916791039664 ], [ -122.38626425935739, 37.747909705429613 ], [ -122.38627966345058, 37.747903278041591 ], [ -122.386295119836348, 37.747898910545544 ], [ -122.386310628142297, 37.747896601848709 ], [ -122.386338292035447, 37.747896159029516 ], [ -122.386357415873647, 37.747899973271664 ], [ -122.386373115890919, 37.747905215772221 ], [ -122.386388851457951, 37.747911831156145 ], [ -122.386404621538247, 37.747919819440014 ], [ -122.386418680056124, 37.747928521847626 ], [ -122.386427603879639, 37.747939366631606 ], [ -122.386439915980972, 37.747947410266093 ], [ -122.386455564081089, 37.74795059367991 ], [ -122.386471037535358, 37.747946912065444 ], [ -122.386789455008412, 37.747884816204298 ], [ -122.3868315118753, 37.747838131816167 ], [ -122.386850251869276, 37.747826844123239 ], [ -122.386870720848805, 37.747815528743104 ], [ -122.386889443052766, 37.747803554601212 ], [ -122.386908148509548, 37.747790893997063 ], [ -122.386942065866194, 37.74776425526499 ], [ -122.386965801028097, 37.74774533356738 ], [ -122.386981135299891, 37.747736160569232 ], [ -122.386994723145222, 37.747726328806948 ], [ -122.387010022542214, 37.747715782908614 ], [ -122.387037163346392, 37.747694746479311 ], [ -122.387050698881637, 37.747682855640441 ], [ -122.38706252285192, 37.747671678660687 ], [ -122.38707605836494, 37.747659787269711 ], [ -122.387087812590266, 37.747645864494586 ], [ -122.387097942431041, 37.747636088094978 ], [ -122.387109818697425, 37.747626970729563 ], [ -122.387123441368203, 37.747618511574053 ], [ -122.387137098912476, 37.747611425587927 ], [ -122.387154231515737, 37.747604970677259 ], [ -122.387171469418391, 37.747602634442131 ], [ -122.387190435960278, 37.747600270521147 ], [ -122.387209419590647, 37.747598593050817 ], [ -122.387226657490075, 37.747596256807626 ], [ -122.387245658900213, 37.747595265774123 ], [ -122.387264642528066, 37.747593588294905 ], [ -122.387281915296896, 37.747592624939976 ], [ -122.387300916705343, 37.747591633897549 ], [ -122.387316296380376, 37.747591387583803 ], [ -122.38733895439411, 37.747591024699865 ], [ -122.387356261689263, 37.747591434235915 ], [ -122.387375280187655, 37.747591129635204 ], [ -122.387394334251837, 37.747592197916667 ], [ -122.387413370533409, 37.74759257975245 ], [ -122.387430694921051, 37.74759367573126 ], [ -122.387449748986683, 37.747594744003763 ], [ -122.38746882014496, 37.747596498726871 ], [ -122.38748616197239, 37.747598281145677 ], [ -122.387505250916419, 37.747600722305499 ], [ -122.387538363228003, 37.747610492846007 ], [ -122.387550745149639, 37.747621282157453 ], [ -122.387563144167856, 37.747632757921238 ], [ -122.38758451981073, 37.747657137719408 ], [ -122.387602472716026, 37.747682945794239 ], [ -122.387611466439651, 37.747696536281474 ], [ -122.387617002189558, 37.747710182158386 ], [ -122.387624284369664, 37.747724486787845 ], [ -122.387629837563409, 37.747738819112165 ], [ -122.387633661769783, 37.747753179131621 ], [ -122.387635983354997, 37.747776490677239 ], [ -122.387636332144282, 37.747790219639235 ], [ -122.387640121821377, 37.747803206756615 ], [ -122.387649063247679, 37.747814737896427 ], [ -122.38765798689127, 37.747825582592974 ], [ -122.38767204553811, 37.747834284851223 ], [ -122.387686034428512, 37.747840241315451 ], [ -122.387703429329918, 37.74784408303541 ], [ -122.387720806101257, 37.747847238315771 ], [ -122.387738148684832, 37.747849020686374 ], [ -122.387757219915159, 37.747850775362728 ], [ -122.387776255919036, 37.747851157145398 ], [ -122.387793563622978, 37.747851566611679 ], [ -122.38781256509084, 37.747850575486822 ], [ -122.387829820125248, 37.747848925609141 ], [ -122.387848786709029, 37.747846561582286 ], [ -122.387866006512709, 37.747843538808695 ], [ -122.387888379097262, 37.747839060028085 ], [ -122.387903835368149, 37.747834692046453 ], [ -122.387921107500148, 37.747833728883549 ], [ -122.38793675597158, 37.747836911546052 ], [ -122.387950727441307, 37.7478421815308 ], [ -122.387968122011031, 37.747846023217228 ], [ -122.387985481697157, 37.747848492005005 ], [ -122.388002823941576, 37.747850274342177 ], [ -122.388020131300777, 37.747850683780733 ], [ -122.388039132419564, 37.747849692624868 ], [ -122.38805637035118, 37.747847356260422 ], [ -122.388073590146575, 37.74784433345652 ], [ -122.388089046753478, 37.747839965445046 ], [ -122.388109620211083, 37.747832768537954 ], [ -122.388109062008056, 37.747810802201109 ], [ -122.388077940175876, 37.747811300839857 ], [ -122.38807409843713, 37.74779625438692 ], [ -122.388108678243654, 37.747795700344511 ], [ -122.388099616701311, 37.74771137804548 ], [ -122.388148028374744, 37.747710602372756 ], [ -122.388152687991848, 37.747825897938156 ], [ -122.388277000893524, 37.747817039049515 ], [ -122.388487762928946, 37.747806794121068 ], [ -122.388506886854486, 37.747810608014518 ], [ -122.388525975547779, 37.747813049289029 ], [ -122.388546793218978, 37.747815462302533 ], [ -122.388565847011719, 37.747816530400094 ], [ -122.388605613450011, 37.747815893090063 ], [ -122.388626326439876, 37.747814187676354 ], [ -122.388645293335259, 37.747811823515768 ], [ -122.388665971425112, 37.747808745199229 ], [ -122.388684903080716, 37.747805008416485 ], [ -122.388703817271136, 37.747800584633623 ], [ -122.388722696221464, 37.747794788231978 ], [ -122.388738135330243, 37.747789733687412 ], [ -122.388758743610779, 37.74778390956282 ], [ -122.388800029962908, 37.747775007094489 ], [ -122.38882243702561, 37.74777190103795 ], [ -122.38884313255457, 37.74776950941277 ], [ -122.388865591959274, 37.747768462417191 ], [ -122.388886322380927, 37.747767443405706 ], [ -122.388908833806198, 37.747768456300328 ], [ -122.388929616574529, 37.747769496350386 ], [ -122.388945212727961, 37.747770619809877 ], [ -122.388964301079568, 37.747773061018961 ], [ -122.388983355206264, 37.747774128768917 ], [ -122.38900242644678, 37.747775883243818 ], [ -122.389021462783674, 37.747776264819827 ], [ -122.389042245569726, 37.747777305399197 ], [ -122.389080318237873, 37.747778068260708 ], [ -122.389137374546209, 37.7477771536037 ], [ -122.389175377760296, 37.747775171186596 ], [ -122.389196090718727, 37.747773465398161 ], [ -122.38926533756586, 37.747775788884091 ], [ -122.389334549507055, 37.747776739433625 ], [ -122.3894037440012, 37.747777003769272 ], [ -122.389542063148724, 37.747774785978237 ], [ -122.389680277530928, 37.747768449063493 ], [ -122.389818387138831, 37.74775799357456 ], [ -122.38988740700772, 37.747751392873859 ], [ -122.38995468041503, 37.747744133414443 ], [ -122.390039173851804, 37.747733851094473 ], [ -122.390066855117027, 37.747734093573278 ], [ -122.390085856531513, 37.747733102356463 ], [ -122.390103128943423, 37.747732138319044 ], [ -122.390122130349909, 37.747731146821835 ], [ -122.390141096830732, 37.747728782426051 ], [ -122.390158334329385, 37.747726446034243 ], [ -122.390177283337934, 37.747723394910331 ], [ -122.390194485555256, 37.747719685348756 ], [ -122.390213417450141, 37.74771594804033 ], [ -122.390230602546296, 37.747711552020206 ], [ -122.390249481354928, 37.747705755373893 ], [ -122.3902666667924, 37.747701359342962 ], [ -122.390283903927951, 37.747699022663753 ], [ -122.390311567723842, 37.747698578911937 ], [ -122.390328910334858, 37.747700360901341 ], [ -122.390344558542523, 37.747703543524345 ], [ -122.390363664733329, 37.747706670948986 ], [ -122.390381024113694, 37.747709139114932 ], [ -122.390398366728832, 37.747710921094118 ], [ -122.390415726118349, 37.747713389529537 ], [ -122.390433121131167, 37.747717230846696 ], [ -122.390448804284702, 37.74772178662581 ], [ -122.390464522011555, 37.747727714754603 ], [ -122.390476782463509, 37.747733698619065 ], [ -122.390494212070166, 37.747738912828105 ], [ -122.390511589281189, 37.747742067691526 ], [ -122.390527184761183, 37.747743190952775 ], [ -122.390653785454447, 37.747824253927604 ], [ -122.390638206813605, 37.747755831079004 ], [ -122.390653872859318, 37.747759700103025 ], [ -122.390669573495856, 37.747764942025796 ], [ -122.390681886034457, 37.74777298549197 ], [ -122.39069250450811, 37.747782429043276 ], [ -122.390701428591996, 37.747793273509437 ], [ -122.390706912858818, 37.747804859892483 ], [ -122.390710737663227, 37.747819219811937 ], [ -122.390717985513646, 37.7478321513547 ], [ -122.390725250834635, 37.747845769344615 ], [ -122.39073249869017, 37.747858700886397 ], [ -122.390741475546321, 37.747871604961333 ], [ -122.390750434923376, 37.747883822038752 ], [ -122.390771776747243, 37.747906828362581 ], [ -122.390782430195571, 37.747917645075368 ], [ -122.39079481229362, 37.747928434050969 ], [ -122.39082296530907, 37.747947210986922 ], [ -122.390837024342559, 37.747955912592879 ], [ -122.390849337269742, 37.747963955761179 ], [ -122.390865002685743, 37.747967825042672 ], [ -122.390880686250455, 37.747972380209411 ], [ -122.3909120177685, 37.747980118201738 ], [ -122.390943314352953, 37.747986483290504 ], [ -122.390960691640942, 37.747989638087581 ], [ -122.390976339590807, 37.747992820632099 ], [ -122.391007601590601, 37.747997812803085 ], [ -122.391024961067359, 37.748000281148649 ], [ -122.391056188138322, 37.748003900686179 ], [ -122.391073512668584, 37.74800499585487 ], [ -122.391089126375263, 37.748006805478177 ], [ -122.391122029339527, 37.748008337920567 ], [ -122.39113764304112, 37.74801014726286 ], [ -122.391155055286944, 37.748014674926367 ], [ -122.3911690619626, 37.748021317424552 ], [ -122.391183121057651, 37.748030019263688 ], [ -122.391193757118117, 37.748040149766119 ], [ -122.391202716589845, 37.748052366808828 ], [ -122.391208200608148, 37.748063953173585 ], [ -122.391241557893906, 37.74808333321856 ], [ -122.391295995830475, 37.748115422483572 ], [ -122.391962189342451, 37.748397274549454 ], [ -122.391983042262396, 37.748401060399672 ], [ -122.392002166513251, 37.748404873451001 ], [ -122.392021325387503, 37.7484100596741 ], [ -122.392042213267686, 37.748415218134042 ], [ -122.392078889429584, 37.748429050561562 ], [ -122.392113924006139, 37.748446342975576 ], [ -122.392131528713293, 37.748458421690032 ], [ -122.39214384116525, 37.748466464458922 ], [ -122.392159577364851, 37.748473079071736 ], [ -122.392175243295199, 37.748476947898624 ], [ -122.392194332280141, 37.748479388578737 ], [ -122.392215132775206, 37.748481114496656 ], [ -122.392234186445535, 37.748482182006597 ], [ -122.392254951984697, 37.748482535297406 ], [ -122.392294719439334, 37.748481896731604 ], [ -122.392336145620007, 37.748478484604256 ], [ -122.392355112221097, 37.748476119852498 ], [ -122.392375790342101, 37.748473040885386 ], [ -122.392394739456975, 37.748469989679933 ], [ -122.392410230561637, 37.748466994002406 ], [ -122.392430943652641, 37.748465288194843 ], [ -122.392449945215546, 37.748464296322425 ], [ -122.392477609283375, 37.748463852062244 ], [ -122.392496645468526, 37.748464233082473 ], [ -122.392534753839612, 37.748466367717036 ], [ -122.392553842833664, 37.748468808338913 ], [ -122.392571185325409, 37.748470589729116 ], [ -122.392588457883463, 37.748469625602191 ], [ -122.392596639186095, 37.748469494208877 ], [ -122.392610934939555, 37.748469264614883 ], [ -122.39262824212247, 37.74846967338226 ], [ -122.392662927124746, 37.748473236687367 ], [ -122.392680287459143, 37.74847570477781 ], [ -122.392695935930789, 37.748478887088204 ], [ -122.392713348381406, 37.748483414521154 ], [ -122.392729031836424, 37.748487969996233 ], [ -122.39274473277321, 37.748493211641936 ], [ -122.392760450846382, 37.748499139463782 ], [ -122.392774457756872, 37.748505781770611 ], [ -122.392790211508157, 37.748513082746427 ], [ -122.392804253398694, 37.748521097944213 ], [ -122.392825263777468, 37.748531061396008 ], [ -122.392842711223665, 37.748536961704282 ], [ -122.39286010621592, 37.748540802942827 ], [ -122.392879212368058, 37.748543929415632 ], [ -122.392898284250904, 37.748545683528903 ], [ -122.392917320797991, 37.748546064201044 ], [ -122.392936339863667, 37.748545758697475 ], [ -122.392953524990631, 37.748541362280918 ], [ -122.392966938977182, 37.748536367729422 ], [ -122.392968945786251, 37.748535620746658 ], [ -122.392982602941643, 37.748528534084265 ], [ -122.392994513945695, 37.748520788741565 ], [ -122.393006389623011, 37.748511670508776 ], [ -122.393016518457316, 37.748501893607475 ], [ -122.39303324918852, 37.748479649545963 ], [ -122.393038138879703, 37.74846789661926 ], [ -122.393043010740925, 37.74845545752526 ], [ -122.393046136785017, 37.748442359198343 ], [ -122.393047533485998, 37.748429288926303 ], [ -122.393054199696095, 37.748283595463981 ], [ -122.393077928476629, 37.748128700431906 ], [ -122.392979444727942, 37.747588651673503 ], [ -122.39297940856946, 37.747588454476606 ], [ -122.392907366368178, 37.747544287652012 ], [ -122.39290993009746, 37.747441237230703 ], [ -122.392849293374297, 37.747437404131951 ], [ -122.392809823765376, 37.747449712490337 ], [ -122.39276850310074, 37.747457243469029 ], [ -122.392735233137245, 37.747441296365679 ], [ -122.392709245973222, 37.747439653574382 ], [ -122.392606956367715, 37.747430308793902 ], [ -122.392589631603045, 37.747429213856059 ], [ -122.392573983004198, 37.747426031261952 ], [ -122.392558282650754, 37.747420789587025 ], [ -122.392545987120343, 37.747413433318528 ], [ -122.392533639816108, 37.747404017146245 ], [ -122.392522986186506, 37.747393200590786 ], [ -122.392515773033679, 37.747381642052915 ], [ -122.392369749857508, 37.747285098201544 ], [ -122.392309356598403, 37.747222889020321 ], [ -122.391563535628293, 37.747006182915136 ], [ -122.391515981789823, 37.747108581714961 ], [ -122.391549059112705, 37.747116978239831 ], [ -122.39154102623057, 37.747141142664127 ], [ -122.391633378020288, 37.747167816090183 ], [ -122.391610658187417, 37.747226552683614 ], [ -122.391284893471251, 37.747135639523535 ], [ -122.391304260773893, 37.747081077170833 ], [ -122.391400018273714, 37.747105635933018 ], [ -122.391414984907726, 37.747082046956919 ], [ -122.391449669204121, 37.747085610619905 ], [ -122.391496995599269, 37.746974288032909 ], [ -122.390841889233045, 37.746787710251105 ], [ -122.390710603266925, 37.746750702923116 ], [ -122.390692063133784, 37.7467454771745 ], [ -122.390583853899699, 37.746707382782333 ], [ -122.390491852370616, 37.746694437487911 ], [ -122.389167167584475, 37.74677199330884 ], [ -122.389007159419478, 37.746805461394153 ], [ -122.38886264165825, 37.746835933506375 ], [ -122.388798600052468, 37.746834213094125 ], [ -122.388745420841317, 37.746851546922535 ], [ -122.388650571528245, 37.746862681307142 ], [ -122.388568244062029, 37.746890096400243 ], [ -122.388501285688704, 37.746909711129156 ], [ -122.388430153337836, 37.746901236873853 ], [ -122.388403030664151, 37.746922960074855 ], [ -122.388383874714961, 37.746923267046867 ], [ -122.388364993671857, 37.746923569610487 ], [ -122.388206416880053, 37.746945339015134 ], [ -122.388203900596949, 37.746982462626271 ], [ -122.388121154182954, 37.746993402651967 ], [ -122.388116562743861, 37.746948838923466 ], [ -122.387896826491271, 37.746946178902249 ], [ -122.387881213437979, 37.746944369124783 ], [ -122.387867242141994, 37.746939099130984 ], [ -122.387854912604922, 37.746930368921483 ], [ -122.387704648799442, 37.746938956647774 ], [ -122.387598167965407, 37.746968818149767 ], [ -122.387589223776885, 37.747025273076481 ], [ -122.387549736768761, 37.747036893214862 ], [ -122.387544863742193, 37.747049332364881 ], [ -122.387534786009468, 37.747061168154005 ], [ -122.38752465665732, 37.747070944861157 ], [ -122.387511034118646, 37.747079403514647 ], [ -122.387495647733758, 37.747086517238138 ], [ -122.387478445182509, 37.747090226411743 ], [ -122.387461451881805, 37.747102172961299 ], [ -122.387268251932909, 37.747106390211968 ], [ -122.387107064056366, 37.74710990881966 ], [ -122.387091486195246, 37.747109471553998 ], [ -122.387075890554399, 37.747108347843543 ], [ -122.387060207744099, 37.747103791889728 ], [ -122.387046201608655, 37.747097148902206 ], [ -122.387033854715085, 37.747087732433343 ], [ -122.387024930905355, 37.74707688769373 ], [ -122.387021159152255, 37.747064586997716 ], [ -122.387005168327306, 37.746911702839881 ], [ -122.385924087590695, 37.746978453708003 ], [ -122.382764926423121, 37.74715258139431 ], [ -122.382708148125133, 37.747164476159931 ], [ -122.382642308174283, 37.747160034224571 ], [ -122.382018857926923, 37.747061489736957 ], [ -122.379659819974563, 37.747207644757545 ], [ -122.379607354913716, 37.746979801452689 ], [ -122.379559186732763, 37.746990183908736 ], [ -122.379533356297898, 37.746994716256566 ], [ -122.379509237470685, 37.74699853457313 ], [ -122.379483389665921, 37.747002380460607 ], [ -122.379459270487843, 37.747006198772453 ], [ -122.379433405660123, 37.74700935819407 ], [ -122.379407523121102, 37.747011831166098 ], [ -122.379383369216924, 37.747014276838193 ], [ -122.379357487013394, 37.747016749519133 ], [ -122.379331587106719, 37.747018536025081 ], [ -122.379307398466224, 37.747019608508175 ], [ -122.379255564276221, 37.747021808862577 ], [ -122.379229629642097, 37.747022222447264 ], [ -122.379144857745686, 37.747021513827882 ], [ -122.379072223061954, 37.747021985338478 ], [ -122.37899789481213, 37.747023857260928 ], [ -122.378925329913201, 37.747027074747926 ], [ -122.378851035672341, 37.747030319213138 ], [ -122.378706009969704, 37.747040872109451 ], [ -122.378631820547639, 37.747048235671635 ], [ -122.378559359727831, 37.747055571081212 ], [ -122.378486933610205, 37.74706427961987 ], [ -122.378414542183847, 37.747074361013084 ], [ -122.378342168091294, 37.747085128811385 ], [ -122.378269828683315, 37.747097269464334 ], [ -122.378197506610775, 37.747110096797023 ], [ -122.378126930483418, 37.747123582435272 ], [ -122.377700185384086, 37.747142744068015 ], [ -122.377451264431812, 37.747148770308641 ], [ -122.377202256724516, 37.747151363774975 ], [ -122.376953162296658, 37.747150524466448 ], [ -122.376703981182786, 37.747146252382528 ], [ -122.376633214042542, 37.747152186453157 ], [ -122.376586583950242, 37.747154989206692 ], [ -122.376541665151564, 37.747157077964779 ], [ -122.376496729013979, 37.747158480255926 ], [ -122.376406822062478, 37.747159911887174 ], [ -122.3763168457685, 37.747158597650483 ], [ -122.376226800142021, 37.747154537545661 ], [ -122.376181759998943, 37.747151821017589 ], [ -122.376136702533032, 37.747148418296995 ], [ -122.376091627724676, 37.747144328560168 ], [ -122.376046535596004, 37.747139552630784 ], [ -122.376003154781813, 37.747134062989552 ], [ -122.375912901223032, 37.747121764971595 ], [ -122.375869469128858, 37.747114215920412 ], [ -122.375824307704988, 37.747106693831739 ], [ -122.375780857600304, 37.747098458034387 ], [ -122.375737390867457, 37.747089535760047 ], [ -122.375693906470275, 37.747079927025247 ], [ -122.375650404755746, 37.747069631824438 ], [ -122.375606885379298, 37.74705865016309 ], [ -122.375565078011306, 37.747046954511831 ], [ -122.375521542024956, 37.747035286357942 ], [ -122.375484921614671, 37.747023508124926 ], [ -122.375446537240038, 37.747010384502651 ], [ -122.375427328249984, 37.747003136228599 ], [ -122.375409847894304, 37.746995860440805 ], [ -122.375390638220011, 37.746988612171734 ], [ -122.375373140891156, 37.746980649922982 ], [ -122.375353914244343, 37.746972715192399 ], [ -122.375283854298274, 37.746938120381792 ], [ -122.375266321996932, 37.746928785222565 ], [ -122.375250518328926, 37.746919422552502 ], [ -122.375232969057322, 37.746909400932758 ], [ -122.375217148425804, 37.746899352077222 ], [ -122.375199598810724, 37.746889330183407 ], [ -122.375183760856842, 37.746878594598741 ], [ -122.37516794023162, 37.746868545461957 ], [ -122.375152084617042, 37.746857123428462 ], [ -122.375137975649949, 37.746846360325264 ], [ -122.375122120743228, 37.74683493855126 ], [ -122.37510799445424, 37.746823488719798 ], [ -122.37509213920427, 37.746812066672646 ], [ -122.375063852016254, 37.746787794649727 ], [ -122.375051437407549, 37.746775631399466 ], [ -122.375037293817897, 37.746763495109327 ], [ -122.375023132917065, 37.746750672641994 ], [ -122.374998269070602, 37.746724972682053 ], [ -122.374960921021042, 37.746684363387303 ], [ -122.374939480511969, 37.746657235495441 ], [ -122.374896530254759, 37.746600233899152 ], [ -122.374887504692197, 37.746585269533512 ], [ -122.374878496108821, 37.746570991622754 ], [ -122.374851418423177, 37.746526098810385 ], [ -122.374837344197417, 37.746516708296284 ], [ -122.374824981280824, 37.746506604102919 ], [ -122.374812601738441, 37.746495813447105 ], [ -122.374801933498034, 37.746484308838191 ], [ -122.374792994225416, 37.746472776721248 ], [ -122.374785749278871, 37.74645984419638 ], [ -122.374780233298765, 37.746446884164222 ], [ -122.374774699654409, 37.746433237687107 ], [ -122.374770895666941, 37.746419563692001 ], [ -122.374768802977741, 37.746405175745409 ], [ -122.374768439251895, 37.74639076029208 ], [ -122.374769821808755, 37.746377003782186 ], [ -122.374772932981244, 37.74636321977102 ], [ -122.374777773805775, 37.74634940824194 ], [ -122.374790947320705, 37.746323103080407 ], [ -122.374915153489738, 37.745898790876815 ], [ -122.374925284686768, 37.745889015246057 ], [ -122.374935381244654, 37.745877866988621 ], [ -122.374943731527424, 37.745866059789378 ], [ -122.374948606585662, 37.745853621157977 ], [ -122.374953463974592, 37.745840496081698 ], [ -122.374954864154603, 37.745827426013051 ], [ -122.374954517717399, 37.745813697009091 ], [ -122.374952459307565, 37.745800681970202 ], [ -122.37494867264158, 37.745787694429573 ], [ -122.374943174004656, 37.745775420853761 ], [ -122.374925295588895, 37.745752356637837 ], [ -122.374169821595103, 37.745013096467311 ], [ -122.374159170981372, 37.745002278247725 ], [ -122.374148554999209, 37.744992832927991 ], [ -122.374136227057974, 37.744984101560632 ], [ -122.374123917125218, 37.74497605663138 ], [ -122.374109912547326, 37.744969412104211 ], [ -122.374095925285644, 37.7449634540258 ], [ -122.374080226064535, 37.744958209898222 ], [ -122.37406629074772, 37.744954311167561 ], [ -122.374048932546074, 37.744951840322948 ], [ -122.374033320242248, 37.744950028435788 ], [ -122.374017742219408, 37.744949589452929 ], [ -122.373998724342499, 37.744949891898301 ], [ -122.37398319825796, 37.744951512262205 ], [ -122.373967707489385, 37.744954505513952 ], [ -122.373952250998556, 37.744958871670036 ], [ -122.373936811825118, 37.74496392454914 ], [ -122.373923118884619, 37.74496963610779 ], [ -122.373909460559076, 37.744976720291163 ], [ -122.373895819549759, 37.744984491197997 ], [ -122.373883942091524, 37.744993607510423 ], [ -122.373863679651151, 37.745013158045801 ], [ -122.373855311981089, 37.745024278719775 ], [ -122.373848673239934, 37.745035371899974 ], [ -122.373842051808168, 37.745047151530216 ], [ -122.37385341216104, 37.745086114521243 ], [ -122.373444301009826, 37.74532404571449 ], [ -122.373430677195785, 37.74533250301748 ], [ -122.373418781971495, 37.745340932837536 ], [ -122.373405123536585, 37.745348017236601 ], [ -122.373391482406376, 37.745355788084566 ], [ -122.373377806658937, 37.745362186029979 ], [ -122.373364148216169, 37.745369270424256 ], [ -122.373350455156967, 37.745374981916001 ], [ -122.373319611498488, 37.745386459860086 ], [ -122.37330588381964, 37.745390798445655 ], [ -122.373274970231165, 37.745399530587065 ], [ -122.373259496653688, 37.745403210470592 ], [ -122.373244005409106, 37.745406203357881 ], [ -122.373226784891003, 37.745409224281886 ], [ -122.373211276683975, 37.7454115307089 ], [ -122.373195750831584, 37.745413150963429 ], [ -122.373178495692287, 37.7454147987048 ], [ -122.373162952885991, 37.745415732773601 ], [ -122.373130102670643, 37.745416254953994 ], [ -122.373114525240069, 37.745415815566226 ], [ -122.373097201225704, 37.745414717488131 ], [ -122.37308160580649, 37.745413591931054 ], [ -122.373065993781964, 37.745411780184988 ], [ -122.373048652458152, 37.745409995374658 ], [ -122.373033005135582, 37.745406810734146 ], [ -122.373017375795953, 37.745404311981943 ], [ -122.372986047234718, 37.74539656950391 ], [ -122.372970365296936, 37.745392011404839 ], [ -122.372956412653693, 37.745387426092684 ], [ -122.372762203193631, 37.745505195897991 ], [ -122.372470578027816, 37.745199439060684 ], [ -122.369079539125323, 37.741643837671482 ], [ -122.367630267152407, 37.740133359644659 ], [ -122.367867173476, 37.739994319504163 ], [ -122.368285236674282, 37.739975330499902 ], [ -122.368274610769845, 37.73982785299647 ], [ -122.371253871422496, 37.739663833776014 ], [ -122.371264036490402, 37.739792777100682 ], [ -122.373407014055843, 37.739660522763742 ], [ -122.373411889309949, 37.739648084177503 ], [ -122.37342198524702, 37.739636936038039 ], [ -122.373432115793747, 37.739627160799976 ], [ -122.373447518947913, 37.739620735634887 ], [ -122.373462992003653, 37.739617055712138 ], [ -122.373518088785445, 37.739607252232659 ], [ -122.373566287640358, 37.739598245135006 ], [ -122.373590404371257, 37.739594428029896 ], [ -122.373616249911393, 37.739590583704633 ], [ -122.373640366284548, 37.739586766320215 ], [ -122.373666246782193, 37.739584294880729 ], [ -122.373690380805215, 37.739581163931668 ], [ -122.373716260600844, 37.739578692217606 ], [ -122.37374042959101, 37.739576934429572 ], [ -122.373766327038652, 37.739575149150085 ], [ -122.373790512990382, 37.739574077808385 ], [ -122.373816427745723, 37.739572978968987 ], [ -122.373890766330462, 37.739571796757524 ], [ -122.373921919428824, 37.739572674766521 ], [ -122.373937443699504, 37.739571054420232 ], [ -122.373952916727561, 37.739567374707711 ], [ -122.373966608670429, 37.739561663140428 ], [ -122.373980265988422, 37.739554578669413 ], [ -122.373992124908327, 37.7395447758934 ], [ -122.374000509598304, 37.739534341645076 ], [ -122.37400713051376, 37.739522561995074 ], [ -122.374012004966914, 37.739510123394709 ], [ -122.37401135577899, 37.739484381479812 ], [ -122.374005858115922, 37.739472108116395 ], [ -122.373998648611916, 37.739460548430131 ], [ -122.373977383644629, 37.739440284566498 ], [ -122.373964927156109, 37.739426404784517 ], [ -122.373936539281573, 37.739398013754517 ], [ -122.373924065496922, 37.739383447517028 ], [ -122.373886618206512, 37.739338719119822 ], [ -122.373874092510491, 37.73932209352359 ], [ -122.373863330238677, 37.739306813334778 ], [ -122.373841788398252, 37.73927556650299 ], [ -122.373820229273036, 37.739243633490574 ], [ -122.373811178157524, 37.739227639083794 ], [ -122.373793059679699, 37.739194964349537 ], [ -122.37378399162607, 37.739178284033066 ], [ -122.373776661019065, 37.739161919174691 ], [ -122.37376932175367, 37.739145210815664 ], [ -122.373761973843884, 37.739128159505157 ], [ -122.373754634592061, 37.73911145141971 ], [ -122.373747286688968, 37.739094400108158 ], [ -122.373741667582067, 37.739077321304478 ], [ -122.373730515575119, 37.739046595957468 ], [ -122.373715767869712, 37.739010433977029 ], [ -122.373701037487336, 37.738974958445688 ], [ -122.373691943857139, 37.738957248164674 ], [ -122.373664687572003, 37.73890514701592 ], [ -122.373643076699025, 37.738871154340984 ], [ -122.373610686734082, 37.738821194992092 ], [ -122.373585652607346, 37.73878863019344 ], [ -122.373573144731495, 37.738772691284012 ], [ -122.373560645155081, 37.738757095055156 ], [ -122.373546408152151, 37.738741183362713 ], [ -122.373533917247101, 37.738725930631098 ], [ -122.373505477884322, 37.738695480140251 ], [ -122.373491266871952, 37.738680598392399 ], [ -122.373459404583869, 37.738651575494011 ], [ -122.373445210895355, 37.73863738019174 ], [ -122.373429288070284, 37.738623211831154 ], [ -122.3734116544736, 37.738609757670034 ], [ -122.37339574066641, 37.738595932799527 ], [ -122.37337984382502, 37.738582794383632 ], [ -122.373362218906536, 37.738569683715227 ], [ -122.373326985681061, 37.738544148008963 ], [ -122.373309377387599, 37.738531723520261 ], [ -122.373290049663908, 37.73851966972861 ], [ -122.37327245903279, 37.738507931680125 ], [ -122.373253139281985, 37.738496221119 ], [ -122.373235557660152, 37.738484826285116 ], [ -122.37321625557793, 37.738473802438271 ], [ -122.373195233367738, 37.738463149022955 ], [ -122.373175939596308, 37.738452468126042 ], [ -122.37315665516681, 37.738442130166042 ], [ -122.373135650277447, 37.738432163191355 ], [ -122.373116382823142, 37.738422511956102 ], [ -122.373074407684399, 37.738403951165793 ], [ -122.373053428763811, 37.738395013578788 ], [ -122.373032458506373, 37.738386419488315 ], [ -122.373011496911786, 37.738378168894329 ], [ -122.372988815182367, 37.738370288453737 ], [ -122.372967870892026, 37.738362724028732 ], [ -122.372945197483872, 37.738355187085482 ], [ -122.372924270503603, 37.738348309104055 ], [ -122.372901615103871, 37.738341458867346 ], [ -122.372878967999199, 37.738334951308275 ], [ -122.372856329555759, 37.738328787245123 ], [ -122.372833699427503, 37.738322966683377 ], [ -122.372811078630633, 37.738317488782812 ], [ -122.372788466148293, 37.738312354383659 ], [ -122.372765862325963, 37.738307563480433 ], [ -122.372743266796775, 37.738303115254908 ], [ -122.372718943193647, 37.738298694765277 ], [ -122.372673787106677, 37.738291171738332 ], [ -122.372649498109226, 37.738288124137178 ], [ -122.372626937542805, 37.738285049061219 ], [ -122.372602656852706, 37.7382823446816 ], [ -122.372578393462533, 37.738280326748381 ], [ -122.372555858502594, 37.738278281341429 ], [ -122.372531604109625, 37.73827660661879 ], [ -122.372507357674976, 37.738275275127862 ], [ -122.37248484935597, 37.738274259373469 ], [ -122.372460620565761, 37.738273614318871 ], [ -122.372436400424391, 37.738273312484985 ], [ -122.37241391770614, 37.738273326399707 ], [ -122.372389714515705, 37.738273711013171 ], [ -122.372365520664644, 37.738274438836385 ], [ -122.372341334769544, 37.738275509891366 ], [ -122.371417572864843, 37.73819782064686 ], [ -122.371336052232962, 37.738188471129725 ], [ -122.371292703605505, 37.738184009138322 ], [ -122.371247643157346, 37.738180261317709 ], [ -122.371204337055175, 37.738177515159229 ], [ -122.371159311524337, 37.738175139928011 ], [ -122.371116023065696, 37.738173080457798 ], [ -122.37107103176993, 37.738172078376202 ], [ -122.371027777876208, 37.738171391502313 ], [ -122.370982812155077, 37.738171418795432 ], [ -122.370937864408404, 37.738172132512091 ], [ -122.370894662370532, 37.738173505218874 ], [ -122.370849740199262, 37.738175248584916 ], [ -122.370806564080581, 37.738177650936933 ], [ -122.370761676121674, 37.738180767178264 ], [ -122.370718534904626, 37.738184542395913 ], [ -122.370673681844693, 37.738189031501626 ], [ -122.370630565847108, 37.738193836375615 ], [ -122.370585739048508, 37.738199355394492 ], [ -122.37054305620137, 37.738152648632216 ], [ -122.370404226449224, 37.73820257996838 ], [ -122.370322939528947, 37.738202496843449 ], [ -122.370241678523712, 37.738203443340097 ], [ -122.37016044308433, 37.738205419463867 ], [ -122.370080971656222, 37.738208740986281 ], [ -122.369999797012667, 37.738213119575086 ], [ -122.369918647931073, 37.738218528065744 ], [ -122.369839254199036, 37.738224938183691 ], [ -122.369758165884022, 37.738232748865137 ], [ -122.369678823605085, 37.738241218785156 ], [ -122.369597795695725, 37.738251431663087 ], [ -122.369570247557576, 37.738256332765289 ], [ -122.369551300103424, 37.738259380031664 ], [ -122.369532343673868, 37.738262084349124 ], [ -122.369494414578227, 37.738266806506552 ], [ -122.369475441227863, 37.738268824632087 ], [ -122.369456458882269, 37.738270498984953 ], [ -122.369437468599656, 37.738271830372412 ], [ -122.369399470072068, 37.738273806697272 ], [ -122.369380461827461, 37.738274451634652 ], [ -122.369342429106254, 37.738275055031856 ], [ -122.369304361157091, 37.738274285523985 ], [ -122.36926627663955, 37.738272829815514 ], [ -122.369228156883779, 37.738270000652726 ], [ -122.369209088902451, 37.738268243244413 ], [ -122.369170934963606, 37.738264041153812 ], [ -122.369151849020255, 37.738261597020717 ], [ -122.369067113471218, 37.738261911373165 ], [ -122.368994202750471, 37.738251049936494 ], [ -122.368911109321047, 37.73824790450054 ], [ -122.36882805041877, 37.73824613163535 ], [ -122.368745017407647, 37.738245388114983 ], [ -122.368662018929072, 37.738246017714694 ], [ -122.36857731722661, 37.738247704629927 ], [ -122.368494370518377, 37.738250393192978 ], [ -122.368411458330385, 37.738254454876298 ], [ -122.368328572028588, 37.738259546453904 ], [ -122.368245711588798, 37.738265667102112 ], [ -122.36816287702834, 37.738272817644742 ], [ -122.368080076965157, 37.738281341033293 ], [ -122.367798933766665, 37.738311550246465 ], [ -122.367519570533148, 37.738343791024903 ], [ -122.367503864887638, 37.738338202509389 ], [ -122.367488228255993, 37.73833536007497 ], [ -122.367472548152278, 37.738330801513577 ], [ -122.367458562672155, 37.738324842640814 ], [ -122.367444551318144, 37.738317854088145 ], [ -122.367432225963285, 37.738309121998832 ], [ -122.367421611792139, 37.73829967606261 ], [ -122.367412700524838, 37.738289173048365 ], [ -122.367405492161083, 37.738277612956487 ], [ -122.367400003950721, 37.738265682239444 ], [ -122.367396227274725, 37.738253037945974 ], [ -122.367394179016159, 37.738240365710134 ], [ -122.367393859886334, 37.738227666344848 ], [ -122.367395269877491, 37.738214939575492 ], [ -122.367400145351439, 37.738202501245944 ], [ -122.367413544289079, 37.738185120751645 ], [ -122.367428767218343, 37.73817148833826 ], [ -122.367445744452965, 37.738158858481917 ], [ -122.367462730293482, 37.738146571300049 ], [ -122.367481470439287, 37.738135286674201 ], [ -122.36750022852209, 37.738124688486586 ], [ -122.367520732277939, 37.73811474935323 ], [ -122.367541261905686, 37.738105839894565 ], [ -122.367561808780295, 37.738097616884474 ], [ -122.367582381528294, 37.738090423549032 ], [ -122.367604708583698, 37.738084232766695 ], [ -122.367627053565442, 37.738078727872157 ], [ -122.367649424083893, 37.738074252931682 ], [ -122.367766437068838, 37.738050766275428 ], [ -122.367843903943097, 37.738036490753885 ], [ -122.367921396310663, 37.738023244315926 ], [ -122.367998923860597, 37.738011370994791 ], [ -122.368076468296522, 37.738000184080214 ], [ -122.368154047223541, 37.737990370018814 ], [ -122.368231651678144, 37.737981585864489 ], [ -122.368311011449208, 37.737973803365058 ], [ -122.368388676621564, 37.737967421684075 ], [ -122.368447213120319, 37.737956879019784 ], [ -122.368493984461352, 37.737959914324698 ], [ -122.368511237973692, 37.737958267267153 ], [ -122.368526753740483, 37.737956304401699 ], [ -122.368543998275101, 37.737954314118795 ], [ -122.368559514040228, 37.737952351249042 ], [ -122.368575012543431, 37.737949701925182 ], [ -122.36859223981395, 37.737947025183189 ], [ -122.368607738314864, 37.737944375855051 ], [ -122.368624947975661, 37.737941012661715 ], [ -122.368655910793635, 37.737934341085214 ], [ -122.368671383403552, 37.737930662345228 ], [ -122.368688576135014, 37.737926612410497 ], [ -122.368719486808203, 37.737917881466693 ], [ -122.368750380211424, 37.737908464062734 ], [ -122.368765818278675, 37.737903412131701 ], [ -122.368781247712349, 37.737898016972643 ], [ -122.368794948383581, 37.737892649505248 ], [ -122.368810368835511, 37.737886911121841 ], [ -122.368825781337947, 37.737880829224856 ], [ -122.368839464732346, 37.737874775025681 ], [ -122.368854868259206, 37.737868350178935 ], [ -122.368868543017058, 37.737861952750357 ], [ -122.368883937913694, 37.737855184948373 ], [ -122.368897604027694, 37.737848444015853 ], [ -122.368911261520921, 37.737841360404865 ], [ -122.368938559214428, 37.737826505902383 ], [ -122.368952199435157, 37.737818735834644 ], [ -122.368960558042616, 37.737807272000303 ], [ -122.368970654743052, 37.73779612423332 ], [ -122.368982505756449, 37.737785978727096 ], [ -122.368996120067735, 37.737777178976209 ], [ -122.369011506288842, 37.737770067382321 ], [ -122.369028672734785, 37.73776498827452 ], [ -122.369045883019595, 37.737761624734098 ], [ -122.369078712268717, 37.737760417244921 ], [ -122.369096068971245, 37.73776288881907 ], [ -122.369113468840041, 37.737767076520647 ], [ -122.369161848333334, 37.73776527896748 ], [ -122.369191211420912, 37.737763783069852 ], [ -122.369249902712198, 37.73775941835428 ], [ -122.369279231606328, 37.737756549525336 ], [ -122.369306822754865, 37.737753364895219 ], [ -122.369336134037468, 37.737749809880221 ], [ -122.369365436669298, 37.73774591108279 ], [ -122.369394721690568, 37.73774132610621 ], [ -122.369422278297634, 37.737736768819744 ], [ -122.369451546734155, 37.737731497366127 ], [ -122.369479077416202, 37.737725909839305 ], [ -122.369508328229003, 37.737719951925108 ], [ -122.369535841646382, 37.737713678482258 ], [ -122.369565066537618, 37.737706690601534 ], [ -122.369592562667947, 37.737699730418903 ], [ -122.36964752037818, 37.737684437405164 ], [ -122.369674981943263, 37.737676104024878 ], [ -122.369702434873361, 37.737667427686809 ], [ -122.369729879161198, 37.737658408116381 ], [ -122.369755577409364, 37.737648729520856 ], [ -122.369783004411687, 37.737639023486132 ], [ -122.369808694010004, 37.737629001653048 ], [ -122.36983436598301, 37.737618293367909 ], [ -122.369861767054431, 37.73760755763697 ], [ -122.369877075781517, 37.737597357165455 ], [ -122.369888995386347, 37.737589957380784 ], [ -122.369902652730687, 37.737582873380028 ], [ -122.369916335982509, 37.737576819055491 ], [ -122.369931756973841, 37.737571080514087 ], [ -122.369945457494808, 37.737565712638037 ], [ -122.369960912683439, 37.737561347002035 ], [ -122.369976376507211, 37.737557324589964 ], [ -122.369991865894718, 37.737554331859151 ], [ -122.370007364616541, 37.737551682615816 ], [ -122.370022880252051, 37.737549719278675 ], [ -122.370060808948153, 37.737544996950575 ], [ -122.370098721055868, 37.737539588147442 ], [ -122.370119388417834, 37.737536169853662 ], [ -122.370138327019674, 37.737532778995096 ], [ -122.370157256636233, 37.737529044913018 ], [ -122.37017617830449, 37.737524967591014 ], [ -122.37021400367405, 37.737516126771574 ], [ -122.370232907361171, 37.737511362724987 ], [ -122.370251803106058, 37.737506255713058 ], [ -122.370270689864256, 37.737500805477644 ], [ -122.370287847523556, 37.73749538296029 ], [ -122.370306725983596, 37.737489589487708 ], [ -122.370325595449458, 37.737483452517054 ], [ -122.370342727177842, 37.737477000039355 ], [ -122.370361588351471, 37.737470520106122 ], [ -122.370378711780674, 37.737463724391766 ], [ -122.370412941358637, 37.737449446778328 ], [ -122.370464233848367, 37.737425970297551 ], [ -122.370498394270641, 37.737408946577332 ], [ -122.370513745375163, 37.737400462163315 ], [ -122.37052385906631, 37.737390000714299 ], [ -122.370534015260773, 37.737381255404934 ], [ -122.370547663865693, 37.737373828103252 ], [ -122.370561364305786, 37.737368460155437 ], [ -122.370576845682166, 37.737365124110752 ], [ -122.370606243142632, 37.737365000764086 ], [ -122.370621879689367, 37.737367842785233 ], [ -122.370637533178453, 37.737371371536177 ], [ -122.370653178706647, 37.737374556499006 ], [ -122.370670526731246, 37.737376684615533 ], [ -122.370687848836141, 37.737377783051812 ], [ -122.370705153660893, 37.737378195033756 ], [ -122.370720694824087, 37.737377261562422 ], [ -122.370737948151799, 37.737375614178688 ], [ -122.370755183506148, 37.737373280351626 ], [ -122.370770656234214, 37.73736960105559 ], [ -122.370787840087672, 37.737365207862915 ], [ -122.370803277557826, 37.737360155669997 ], [ -122.370818689794845, 37.737354073786427 ], [ -122.370832347014144, 37.737346989952236 ], [ -122.370845986935052, 37.737339219115491 ], [ -122.370859609577963, 37.737330762099973 ], [ -122.37087148583683, 37.737321646086173 ], [ -122.370883345501966, 37.737311843608374 ], [ -122.370893458790221, 37.73730138240758 ], [ -122.370905214055995, 37.73728746095329 ], [ -122.370915232622579, 37.737273223985788 ], [ -122.370938760775033, 37.737246068067016 ], [ -122.370952262410057, 37.737232805878257 ], [ -122.370964052918382, 37.737220257585115 ], [ -122.370979300231937, 37.737207654399519 ], [ -122.370992836419262, 37.737195765109583 ], [ -122.371008110346423, 37.737184191861481 ], [ -122.371021663808406, 37.737172989019946 ], [ -122.371038683399732, 37.737162074224159 ], [ -122.371053991182038, 37.737151873609982 ], [ -122.371071037042427, 37.737141988756214 ], [ -122.371088090848829, 37.737132447136844 ], [ -122.371105162626606, 37.737123591955786 ], [ -122.371122242351063, 37.73711508000909 ], [ -122.371141068795652, 37.737107227047161 ], [ -122.371159903532885, 37.737099717313619 ], [ -122.37117875555127, 37.737092894028791 ], [ -122.371197615863039, 37.737086413972314 ], [ -122.371216494148271, 37.737080620353588 ], [ -122.371237109474734, 37.737075142508274 ], [ -122.371256013343697, 37.737070378840897 ], [ -122.371276663229182, 37.737066273617579 ], [ -122.371297321762341, 37.737062511891089 ], [ -122.371316268832857, 37.737059464068786 ], [ -122.371347308485454, 37.737055880822481 ], [ -122.371363160732827, 37.737067303397573 ], [ -122.371385487850503, 37.737061111612277 ], [ -122.371409819958558, 37.737065875598411 ], [ -122.371432414333356, 37.737070323816937 ], [ -122.371456712218176, 37.737073714884438 ], [ -122.371480992125981, 37.737076419506209 ], [ -122.371505255439089, 37.737078437660294 ], [ -122.371529509424818, 37.737080112869066 ], [ -122.371555466903885, 37.73708073037561 ], [ -122.371579678008715, 37.737080689164763 ], [ -122.371603880469053, 37.737080304723065 ], [ -122.371628056995533, 37.737078890598873 ], [ -122.371652224876399, 37.737077133243893 ], [ -122.371676366821816, 37.737074346206427 ], [ -122.371700500120539, 37.737071215938158 ], [ -122.37172460748225, 37.737067055987431 ], [ -122.371748706203064, 37.737062553080534 ], [ -122.371772778971504, 37.737057019942043 ], [ -122.371791673803557, 37.737051912693794 ], [ -122.371807120477257, 37.737047203583948 ], [ -122.371822584094161, 37.737043180929248 ], [ -122.371838065007339, 37.737039844998783 ], [ -122.371853562850688, 37.737037194974249 ], [ -122.371869078682678, 37.737035231662922 ], [ -122.371884611459564, 37.737033954806734 ], [ -122.371907076518241, 37.737033254815913 ], [ -122.371922652170909, 37.737033693814745 ], [ -122.371938237168152, 37.737034476300948 ], [ -122.371953839112081, 37.737035945242191 ], [ -122.371969466994983, 37.737038443858872 ], [ -122.371985103179384, 37.737041285704763 ], [ -122.372000757348985, 37.737044813989243 ], [ -122.372020067187322, 37.7370561815437 ], [ -122.372035876997472, 37.737065887888214 ], [ -122.372049966365637, 37.737075964928742 ], [ -122.372064081677323, 37.737087071645014 ], [ -122.372076467900058, 37.737098205832325 ], [ -122.372088880758042, 37.737110369684729 ], [ -122.372099573180051, 37.737122904509278 ], [ -122.37211027423858, 37.737135782009496 ], [ -122.372119254860294, 37.737149030482428 ], [ -122.372128244823543, 37.737162622169478 ], [ -122.372145506378345, 37.737161318085484 ], [ -122.372167936525273, 37.737159244598097 ], [ -122.372188603686311, 37.73715582594123 ], [ -122.372209253557884, 37.737151721103693 ], [ -122.372228157715924, 37.737146956725766 ], [ -122.372248764338792, 37.737141135477749 ], [ -122.372267624915224, 37.737134655244574 ], [ -122.372283045287503, 37.737128916400529 ], [ -122.372293192944326, 37.737119827982468 ], [ -122.372299822556045, 37.737108391374868 ], [ -122.372301239987792, 37.73709600777574 ], [ -122.372373414633373, 37.737077692981821 ], [ -122.372418232665979, 37.737071830045089 ], [ -122.372464787747589, 37.737066283124271 ], [ -122.372509631380041, 37.737061450384822 ], [ -122.37255449229049, 37.737057303530676 ], [ -122.372599370500893, 37.737053843385567 ], [ -122.372645994751693, 37.737051042199361 ], [ -122.372690898556655, 37.737048611702214 ], [ -122.372735828999197, 37.737047210854271 ], [ -122.372782496496995, 37.737046126017439 ], [ -122.372808401383125, 37.737044683904415 ], [ -122.372830858125084, 37.737043640231676 ], [ -122.372853323170872, 37.737042939785901 ], [ -122.372899999307762, 37.737042197853711 ], [ -122.37292249030402, 37.737042527072028 ], [ -122.372944989951108, 37.737043199511795 ], [ -122.372969218351344, 37.737043844741514 ], [ -122.373014252244417, 37.737046562227299 ], [ -122.373036786152014, 37.737048607558009 ], [ -122.373061049498276, 37.737050625391774 ], [ -122.373083592412328, 37.73705301420835 ], [ -122.37312871282991, 37.73705916390778 ], [ -122.37317385056528, 37.737066000315984 ], [ -122.373196436400818, 37.737070105245131 ], [ -122.373219031568397, 37.737074552835523 ], [ -122.373239897653349, 37.737079028185967 ], [ -122.373285121933037, 37.737089296808776 ], [ -122.373306013982969, 37.737094801824675 ], [ -122.373328651749006, 37.737100966082771 ], [ -122.373370470468529, 37.737113348723689 ], [ -122.373393126235527, 37.737120199135234 ], [ -122.373434996907847, 37.737134641656631 ], [ -122.373455940892711, 37.737142205725576 ], [ -122.373476893541877, 37.737150113291072 ], [ -122.373496125754656, 37.737158391572102 ], [ -122.373538065688621, 37.73717557958782 ], [ -122.373776518618286, 37.737372998626824 ], [ -122.373949450475536, 37.737509653888004 ], [ -122.374125779894442, 37.737643851318353 ], [ -122.374316078561918, 37.737783320091772 ], [ -122.374328378667045, 37.737791021785021 ], [ -122.374342390568998, 37.737798009519793 ], [ -122.374356385166024, 37.737804311076282 ], [ -122.374372082895462, 37.737809555173321 ], [ -122.374387762959685, 37.737814112547987 ], [ -122.374403417067299, 37.737817640792912 ], [ -122.374419053501768, 37.737820482040924 ], [ -122.374434673327585, 37.73782263737381 ], [ -122.374450266821725, 37.73782376248419 ], [ -122.374467562767649, 37.7378238306935 ], [ -122.374483104668272, 37.737822896715045 ], [ -122.3745003576703, 37.737821248786098 ], [ -122.374522744667573, 37.737817458997327 ], [ -122.374650468637824, 37.737875858898875 ], [ -122.374739734976373, 37.737918045764403 ], [ -122.374827316638459, 37.737961976186384 ], [ -122.374913186617448, 37.738006620783011 ], [ -122.374999090980893, 37.738052637675224 ], [ -122.375048414507546, 37.73808824928939 ], [ -122.375108232298459, 37.738128500964891 ], [ -122.375139852148493, 37.738147912836176 ], [ -122.375171464045508, 37.73816698146242 ], [ -122.375203066606062, 37.738185706865586 ], [ -122.375234661212687, 37.738204089023711 ], [ -122.375266237820483, 37.738221784733298 ], [ -122.375299535245929, 37.738239109683342 ], [ -122.375332823679372, 37.738256091403926 ], [ -122.375366094804107, 37.738272386664057 ], [ -122.375399356935574, 37.738288338694765 ], [ -122.375432611110568, 37.738303947479515 ], [ -122.37546757640348, 37.738318842292436 ], [ -122.375502533047822, 37.738333393869439 ], [ -122.375537481043125, 37.738347602210531 ], [ -122.375572411725926, 37.738361124090282 ], [ -122.375609062188403, 37.738374275220636 ], [ -122.375643966907035, 37.738386767402702 ], [ -122.37568059209606, 37.738398888823312 ], [ -122.375717199624106, 37.7384103237871 ], [ -122.375753798499645, 37.738421415514019 ], [ -122.375790388383365, 37.73843216428417 ], [ -122.375828690404418, 37.738442198509603 ], [ -122.375865245990923, 37.738451574074567 ], [ -122.375903521699868, 37.738460578879355 ], [ -122.375941788753465, 37.738469240446264 ], [ -122.375992156916524, 37.738477709334177 ], [ -122.376007733601398, 37.738478148331104 ], [ -122.376025030039884, 37.738478216031723 ], [ -122.376042317820222, 37.738477940778964 ], [ -122.376057859492079, 37.73847700659843 ], [ -122.376075120930508, 37.738475701670041 ], [ -122.376092365729193, 37.738473710277233 ], [ -122.376107872737933, 37.738471403188342 ], [ -122.376125099858029, 37.738468725345385 ], [ -122.376140580868082, 37.738465388575889 ], [ -122.376157790308596, 37.738462024282761 ], [ -122.376188700679592, 37.738453291649634 ], [ -122.376205866785881, 37.738448211222327 ], [ -122.376221296131305, 37.738442814809545 ], [ -122.376265793698082, 37.738424251349997 ], [ -122.376279450587873, 37.738417166611207 ], [ -122.37629309880883, 37.738409738645394 ], [ -122.37632036057704, 37.738393509807281 ], [ -122.376333982797163, 37.738385052434992 ], [ -122.376357734990449, 37.738366819020342 ], [ -122.376369602073851, 37.738357359228573 ], [ -122.376381452512433, 37.738347212973743 ], [ -122.376391573827405, 37.738337094253737 ], [ -122.376401686479809, 37.738326632582023 ], [ -122.376411790448728, 37.738315827134819 ], [ -122.376421885754795, 37.738304678735901 ], [ -122.376430251938501, 37.738293557872694 ], [ -122.376446950667884, 37.738269943226257 ], [ -122.376453571082962, 37.738258163436321 ], [ -122.376017920759935, 37.737991096533946 ], [ -122.37598688059623, 37.737994681015465 ], [ -122.375962730194601, 37.737997125704617 ], [ -122.375940290890341, 37.737998856694041 ], [ -122.375916122804242, 37.738000614653615 ], [ -122.375891946066474, 37.73800202993192 ], [ -122.375869489077928, 37.738003074187837 ], [ -122.375845295342131, 37.738003802450933 ], [ -122.37582109225724, 37.738004187769143 ], [ -122.375798609973742, 37.738004202598695 ], [ -122.375774389906638, 37.738003901450845 ], [ -122.375750161169094, 37.738003256797974 ], [ -122.375727661205985, 37.738002584893898 ], [ -122.37570341480351, 37.738001254060514 ], [ -122.375679160430039, 37.737999579985654 ], [ -122.375656634485836, 37.737997878666114 ], [ -122.375632362435553, 37.737995517861528 ], [ -122.375609819167565, 37.737993130082067 ], [ -122.375585529448685, 37.737990082822328 ], [ -122.37556296919702, 37.737987008302788 ], [ -122.375540391282939, 37.737983247608021 ], [ -122.37551432983625, 37.737978511718602 ], [ -122.375486496320931, 37.737972087487712 ], [ -122.375460382568676, 37.737965292513373 ], [ -122.375432514421007, 37.737957495642242 ], [ -122.375406358052871, 37.737948984242749 ], [ -122.375380184027932, 37.737939786666473 ], [ -122.375353992685788, 37.737929902633333 ], [ -122.375329512792433, 37.737919304628377 ], [ -122.375305015582782, 37.737908020167239 ], [ -122.37528050105756, 37.737896049249933 ], [ -122.375255969224384, 37.737883392151048 ], [ -122.375233148827334, 37.737870020533251 ], [ -122.375210319784216, 37.737856305960051 ], [ -122.375189193529863, 37.73784153419291 ], [ -122.37516805862289, 37.737826419196367 ], [ -122.375146906409697, 37.737810618019523 ], [ -122.375127474300257, 37.737794445826715 ], [ -122.375108024870912, 37.737777586905018 ], [ -122.375090295558422, 37.737760357517622 ], [ -122.375072548933076, 37.737742441676424 ], [ -122.37505479366277, 37.737724182881529 ], [ -122.375038749481291, 37.737705209578436 ], [ -122.375024434427573, 37.737686209306304 ], [ -122.375010102046858, 37.737666522032093 ], [ -122.374995761020472, 37.737646491805087 ], [ -122.374983140446119, 37.737626090835334 ], [ -122.374961338821961, 37.737584547432398 ], [ -122.374950420699136, 37.737563089278161 ], [ -122.374942960095922, 37.737541576104334 ], [ -122.374933762081014, 37.737519747213518 ], [ -122.374928021582775, 37.737497863303837 ], [ -122.374916523276909, 37.737453409032263 ], [ -122.374908906819144, 37.737425717795361 ], [ -122.374901394283526, 37.737402145264888 ], [ -122.374889826731234, 37.737354945185302 ], [ -122.374887491804515, 37.737330946902453 ], [ -122.3748834277893, 37.737306976407964 ], [ -122.374882821609901, 37.737282950341836 ], [ -122.374880487033479, 37.737258952053075 ], [ -122.374882732189576, 37.737210845452722 ], [ -122.374885583514967, 37.737186764643965 ], [ -122.374888443498122, 37.737163027060632 ], [ -122.374893031881015, 37.737139261974114 ], [ -122.37489934935347, 37.73711546937318 ], [ -122.3749056754816, 37.737092019997398 ], [ -122.374921802888267, 37.737045752947004 ], [ -122.374931603466351, 37.737022935008291 ], [ -122.374941413382857, 37.737000460008709 ], [ -122.37495296035803, 37.736978301004505 ], [ -122.374966236069355, 37.736956114489473 ], [ -122.374979529092755, 37.736934614424079 ], [ -122.375009607247023, 37.736892932445095 ], [ -122.375026392362614, 37.736872749981458 ], [ -122.375043186136296, 37.736852911015554 ], [ -122.37504977201327, 37.736839758673845 ], [ -122.375040807874441, 37.736827196605702 ], [ -122.375029742270328, 37.736799903626249 ], [ -122.375025921408564, 37.736785543166128 ], [ -122.37502556634017, 37.73677147091604 ], [ -122.375026931350064, 37.736757027929769 ], [ -122.375028304673904, 37.736742928174571 ], [ -122.375033136164149, 37.736728773387163 ], [ -122.3750397133647, 37.736715277545557 ], [ -122.37504803628174, 37.736702440924056 ], [ -122.375058114591354, 37.736690605907675 ], [ -122.375071641371591, 37.736678372922562 ], [ -122.375085194128815, 37.73666716961273 ], [ -122.375098729567796, 37.736655280124666 ], [ -122.375110518931734, 37.736642731146382 ], [ -122.375122290631623, 37.736629495995757 ], [ -122.375132325623056, 37.736615945119389 ], [ -122.375140614208931, 37.736601735308589 ], [ -122.375147156735764, 37.736586866558262 ], [ -122.375155332379663, 37.736568194818872 ], [ -122.375160085887146, 37.736550950995301 ], [ -122.375166117416924, 37.736515831932394 ], [ -122.375169124525783, 37.736497929449705 ], [ -122.375168232453305, 37.736462576936368 ], [ -122.375163874266519, 37.736426936496883 ], [ -122.375219437861915, 37.736298664625259 ], [ -122.375236617355156, 37.736225597875112 ], [ -122.375493436762397, 37.736054292153611 ], [ -122.375564665724326, 37.73599856352913 ], [ -122.375665547467477, 37.735884334377992 ], [ -122.375736776137472, 37.735828605647917 ], [ -122.375805543594495, 37.735812402863488 ], [ -122.37590130745248, 37.735838004215466 ], [ -122.375948138753657, 37.735843438862048 ], [ -122.375966461137224, 37.735815678053854 ], [ -122.375965386747623, 37.735773118079564 ], [ -122.375936427668094, 37.735722074898895 ], [ -122.375905272384003, 37.735652524496416 ], [ -122.375880455265374, 37.735628540802992 ], [ -122.375898405065016, 37.735586021302886 ], [ -122.375909891231828, 37.735561459614892 ], [ -122.375897018810662, 37.735531105204046 ], [ -122.375855226545227, 37.735519752858821 ], [ -122.375828603150225, 37.735492707640475 ], [ -122.37586986690205, 37.735483123506768 ], [ -122.375876469825457, 37.735470657017984 ], [ -122.375850080018679, 37.735452878901683 ], [ -122.375875629426574, 37.73543736440692 ], [ -122.375902751563189, 37.735415643772811 ], [ -122.37592791070513, 37.735384683844323 ], [ -122.375963949778878, 37.735305136416706 ], [ -122.375989997452365, 37.735240856058539 ], [ -122.376000720711573, 37.735186090510247 ], [ -122.375916941813387, 37.735087162261337 ], [ -122.375874456680464, 37.735048351870503 ], [ -122.37583223555086, 37.735088510886278 ], [ -122.375790902711827, 37.735095349476808 ], [ -122.375731004666164, 37.735120338792314 ], [ -122.375697972699726, 37.73518198337095 ], [ -122.375629431028628, 37.735207109686826 ], [ -122.375573154825574, 37.735238564895731 ], [ -122.37553454675556, 37.735284846828293 ], [ -122.375502844795335, 37.735330675452381 ], [ -122.375460320132888, 37.735358821434986 ], [ -122.375407345243758, 37.735384043468251 ], [ -122.375373074275174, 37.73539660668353 ], [ -122.375319406071526, 37.735394370629407 ], [ -122.375255063184753, 37.735380286724919 ], [ -122.375204090598487, 37.735347791725545 ], [ -122.375165443829161, 37.735324028264564 ], [ -122.37512657258921, 37.735291340362735 ], [ -122.375075747344539, 37.735264680144091 ], [ -122.375062572554327, 37.735222312735722 ], [ -122.375055042872361, 37.735198053755049 ], [ -122.375042101935179, 37.735164953712463 ], [ -122.375037873785985, 37.735134461646254 ], [ -122.375036955812135, 37.735098079446828 ], [ -122.375058051950944, 37.735043149193146 ], [ -122.375007538650593, 37.735028844794947 ], [ -122.3749660498938, 37.735029505033381 ], [ -122.374934543651534, 37.735014555319857 ], [ -122.374908309749543, 37.735002954785188 ], [ -122.374876500793206, 37.734975991875665 ], [ -122.374871744160856, 37.734924563038625 ], [ -122.374892528974101, 37.73485727640653 ], [ -122.374913772355171, 37.7348081807394 ], [ -122.374946666252242, 37.734741044765023 ], [ -122.374988965601105, 37.734703975095854 ], [ -122.375029917257038, 37.734682034854018 ], [ -122.375070713005826, 37.734653916534754 ], [ -122.375104671911529, 37.734628997279636 ], [ -122.375115698854458, 37.734586244981536 ], [ -122.375146199993921, 37.734561380743287 ], [ -122.375129646198275, 37.734522157113517 ], [ -122.375090930945873, 37.734495647813482 ], [ -122.375036267841509, 37.734453940390075 ], [ -122.374981985833443, 37.734427335148574 ], [ -122.374938543580228, 37.734419099017749 ], [ -122.374912994333698, 37.734434613586529 ], [ -122.374893666730827, 37.734422560335261 ], [ -122.374893060571651, 37.734398534257735 ], [ -122.374907614581076, 37.734358472486285 ], [ -122.374894292846008, 37.734310270489409 ], [ -122.374881421402023, 37.734279915955867 ], [ -122.37487704615539, 37.734243588766795 ], [ -122.374869213561681, 37.734207316868407 ], [ -122.374868295688771, 37.734170934936692 ], [ -122.374926339800538, 37.734072496020858 ], [ -122.374952543818168, 37.734014394217503 ], [ -122.374954780508872, 37.73396594408409 ], [ -122.374957241667502, 37.733926418380705 ], [ -122.374963229775304, 37.733889582909434 ], [ -122.374983289721541, 37.733862137955839 ], [ -122.375012218505518, 37.733843479312256 ], [ -122.375069108753891, 37.733836393394171 ], [ -122.375122463708166, 37.733826273440556 ], [ -122.375153432543385, 37.73381994338358 ], [ -122.375171833015472, 37.733795271716147 ], [ -122.375171062228418, 37.733764724622461 ], [ -122.375229769357603, 37.733624041261521 ], [ -122.375288514453459, 37.733553403441164 ], [ -122.375324856119903, 37.733485869092775 ], [ -122.375361353624825, 37.733424512796418 ], [ -122.375375681648336, 37.73337552709593 ], [ -122.375382713915272, 37.733311549522142 ], [ -122.375463619327064, 37.733228197775681 ], [ -122.375509608742206, 37.733200339972562 ], [ -122.375523633451934, 37.73313934134989 ], [ -122.37550722668901, 37.733105952882383 ], [ -122.375651602500071, 37.732933689521552 ], [ -122.375685404494646, 37.732902592027898 ], [ -122.375688325042901, 37.732881256983333 ], [ -122.375650684266844, 37.732897307862871 ], [ -122.375550384562601, 37.732828858101236 ], [ -122.375617693995721, 37.732754993758505 ], [ -122.375573481700997, 37.732716210488881 ], [ -122.375197427066922, 37.732959117287578 ], [ -122.375009435995196, 37.732773602543226 ], [ -122.374988848118562, 37.732780110729863 ], [ -122.374969139909751, 37.732752955276041 ], [ -122.374890774233947, 37.732799869727202 ], [ -122.374867609109913, 37.732772769268017 ], [ -122.374833347919932, 37.732785675832154 ], [ -122.374804918949508, 37.732755568576273 ], [ -122.374609189594366, 37.732880233977106 ], [ -122.374557526391285, 37.732820280340718 ], [ -122.37490487958712, 37.732604958073601 ], [ -122.374821719829242, 37.732530398040169 ], [ -122.37467574043562, 37.732639163620497 ], [ -122.37439309494539, 37.732540308159429 ], [ -122.374310276013688, 37.732547806255845 ], [ -122.374229713731395, 37.732576213850287 ], [ -122.374176359200263, 37.732586333132673 ], [ -122.374120153956625, 37.732620533463049 ], [ -122.374114165533243, 37.73265736889919 ], [ -122.374052159400549, 37.732667282379175 ], [ -122.374002184525267, 37.73267425782587 ], [ -122.373911483788106, 37.732712096999926 ], [ -122.373893083165825, 37.732736768474979 ], [ -122.373883628328045, 37.732773315665987 ], [ -122.373790281780344, 37.732774800286634 ], [ -122.373761509214617, 37.732799636420175 ], [ -122.373753549290342, 37.732826888733754 ], [ -122.373747327007891, 37.732854456775314 ], [ -122.373725625111732, 37.732885361268295 ], [ -122.373694734433712, 37.732894780258938 ], [ -122.373637610789032, 37.732892598419873 ], [ -122.373568854206965, 37.732909142898315 ], [ -122.37354165442683, 37.732927773970779 ], [ -122.373512804196523, 37.732949521278634 ], [ -122.37350146473058, 37.732979917309954 ], [ -122.373503074145475, 37.733043757608968 ], [ -122.373500309404122, 37.733071270654619 ], [ -122.373481674928072, 37.73308667468919 ], [ -122.373435459514155, 37.733105607832456 ], [ -122.373412831615411, 37.733099787091327 ], [ -122.373389322638175, 37.733127629980004 ], [ -122.373370999608952, 37.73315539040528 ], [ -122.373326815604614, 37.733186309201884 ], [ -122.373292553942591, 37.733199214767993 ], [ -122.373255060257023, 37.73322109971145 ], [ -122.373217868644616, 37.733254997012843 ], [ -122.373184748909821, 37.733313208646514 ], [ -122.373147255135009, 37.733335093555283 ], [ -122.373108032325476, 37.733357005665319 ], [ -122.373055135575555, 37.733385315688466 ], [ -122.373026509933439, 37.733415986475677 ], [ -122.372989171414929, 37.733444049131762 ], [ -122.372947104996001, 37.733490385196987 ], [ -122.372903457602277, 37.733542583556414 ], [ -122.372843059429968, 37.73361633669991 ], [ -122.372787475219468, 37.733675248937253 ], [ -122.372660653342237, 37.733789544305665 ], [ -122.372615406285462, 37.73384691842643 ], [ -122.372571792697812, 37.733900489569244 ], [ -122.372542285196062, 37.733964824408801 ], [ -122.372515465283058, 37.733998556633921 ], [ -122.372467598558771, 37.73402060589649 ], [ -122.372390422139858, 37.734046211198297 ], [ -122.372337516324379, 37.734074177664638 ], [ -122.372263676850309, 37.734163595440648 ], [ -122.372229942026564, 37.734197437754013 ], [ -122.37219905926051, 37.734207199576488 ], [ -122.372135400372358, 37.734220228553554 ], [ -122.372066183983833, 37.734218581449781 ], [ -122.37202454003129, 37.734213062859681 ], [ -122.371977485584694, 37.734198702222137 ], [ -122.371931036017173, 37.73420836793948 ], [ -122.371912020534467, 37.734208670054151 ], [ -122.371892468688813, 37.734187692159942 ], [ -122.371850522169254, 37.73417016004948 ], [ -122.371815256975381, 37.734143251243232 ], [ -122.37178360511767, 37.734122465563011 ], [ -122.371757069813924, 37.734098851958706 ], [ -122.371718425134958, 37.734075087082275 ], [ -122.371658811600753, 37.734042727577382 ], [ -122.37159046796063, 37.734007073362946 ], [ -122.371530163390418, 37.733947255979729 ], [ -122.371503472637912, 37.733917463974464 ], [ -122.371452035953411, 37.733866433138324 ], [ -122.371412863920071, 37.733821731652299 ], [ -122.371365343126712, 37.733788836837448 ], [ -122.371306006337434, 37.733698787998705 ], [ -122.37124803587426, 37.73366296856193 ], [ -122.371181508213965, 37.733630718908138 ], [ -122.371142786185317, 37.733603864806447 ], [ -122.371119319714566, 37.733564750701184 ], [ -122.371068652635969, 37.733544266816153 ], [ -122.37104196222009, 37.733514474978968 ], [ -122.371009541074471, 37.733463141702856 ], [ -122.370970292434578, 37.733415350746299 ], [ -122.370945321772282, 37.733385187940364 ], [ -122.370906375642221, 37.733349409875622 ], [ -122.370882676078168, 37.733301028616602 ], [ -122.370853876981954, 37.733256162236223 ], [ -122.370812000355784, 37.733241375562343 ], [ -122.370762102212552, 37.733251438949395 ], [ -122.3707392498319, 37.733236693541137 ], [ -122.370742015947201, 37.733209180555527 ], [ -122.370734029961071, 37.733166730305776 ], [ -122.370713641007328, 37.733112459546398 ], [ -122.370604476976737, 37.73303521894465 ], [ -122.370512089823777, 37.733006126395267 ], [ -122.370444949736466, 37.73294950753936 ], [ -122.370336702208434, 37.732908648655865 ], [ -122.370231912105666, 37.732867735066925 ], [ -122.37017410658585, 37.732838436949166 ], [ -122.370077726716076, 37.732788118918357 ], [ -122.369991114296425, 37.732713610079365 ], [ -122.369927812152071, 37.732672038006164 ], [ -122.369888943911135, 37.73263934837334 ], [ -122.369823835861169, 37.732594714362811 ], [ -122.369764448866945, 37.732571278048333 ], [ -122.369664767427579, 37.732527192896548 ], [ -122.369608556728025, 37.732490658570462 ], [ -122.369587411983062, 37.732476915661685 ], [ -122.369536133374979, 37.732432062044303 ], [ -122.369522426580374, 37.732368414248455 ], [ -122.369524131442745, 37.732298684450967 ], [ -122.369528168991479, 37.732252953067388 ], [ -122.369514764766407, 37.732201317633788 ], [ -122.369460037420595, 37.732156862346898 ], [ -122.369435499832107, 37.732143860523472 ], [ -122.369412699166176, 37.732131174223944 ], [ -122.369391627493243, 37.732118460761214 ], [ -122.369368818907404, 37.732105431490474 ], [ -122.369347729982366, 37.732092031567248 ], [ -122.369305534888653, 37.732064545256826 ], [ -122.36928614837251, 37.732050088222451 ], [ -122.369265033578955, 37.732035658605412 ], [ -122.369208612399049, 37.731992603257169 ], [ -122.36919310687361, 37.731994909435159 ], [ -122.369177583743095, 37.731996529438547 ], [ -122.369162034700395, 37.731997119211783 ], [ -122.369144739776431, 37.731997050503772 ], [ -122.369129147571257, 37.731995924141103 ], [ -122.369113529476451, 37.731993768371993 ], [ -122.369097893772235, 37.731990926153657 ], [ -122.369082241496287, 37.731987397469659 ], [ -122.369068291609253, 37.731982811686983 ], [ -122.369052587205204, 37.731977223921163 ], [ -122.369038602793921, 37.731971265229653 ], [ -122.369026321118199, 37.73196424943503 ], [ -122.36900353818659, 37.731952249503117 ], [ -122.368984376529525, 37.731946716297898 ], [ -122.368965231792913, 37.731941869547697 ], [ -122.368946113651845, 37.731938052736979 ], [ -122.368927003791569, 37.731934578880427 ], [ -122.368907928458668, 37.731932477926101 ], [ -122.368887133122826, 37.731930747340918 ], [ -122.368868091978015, 37.73193001956556 ], [ -122.368847348437356, 37.731930348605978 ], [ -122.368826613520639, 37.731931020594487 ], [ -122.368807642108294, 37.731933038334546 ], [ -122.368788678980067, 37.731935399303374 ], [ -122.368768013114249, 37.731938817366917 ], [ -122.368749101767222, 37.731943237687446 ], [ -122.368730207333343, 37.731948344463113 ], [ -122.368711330849322, 37.731954137677484 ], [ -122.368694217167373, 37.731961276382087 ], [ -122.368680664304932, 37.731972478965581 ], [ -122.368668865613287, 37.731984683814787 ], [ -122.368657084876617, 37.73199757537909 ], [ -122.368648777958526, 37.732011098297363 ], [ -122.368640488989229, 37.732025307656784 ], [ -122.368633936925264, 37.73203983255609 ], [ -122.368629130411534, 37.732055016771099 ], [ -122.368626053219003, 37.732070173558292 ], [ -122.368624712941653, 37.732085646160606 ], [ -122.368625101296246, 37.732101091346223 ], [ -122.368627218283763, 37.732116509115187 ], [ -122.368631063559619, 37.732131899472847 ], [ -122.368636620901697, 37.732146575949926 ], [ -122.368642134756229, 37.73215953657504 ], [ -122.36864418270045, 37.732172208258227 ], [ -122.368644510648082, 37.732185250859203 ], [ -122.368643101337838, 37.732197977925267 ], [ -122.368638226113163, 37.732210416049476 ], [ -122.368633341924749, 37.732222511501767 ], [ -122.368624983881077, 37.732233974774296 ], [ -122.368616599605744, 37.732244408647276 ], [ -122.368606469086217, 37.732254183488791 ], [ -122.368594576099341, 37.732262612829288 ], [ -122.368580928237833, 37.732270039911 ], [ -122.368565516878832, 37.732276121781609 ], [ -122.368551791336984, 37.732280459548285 ], [ -122.368534574353816, 37.732283479507736 ], [ -122.368503527583357, 37.732286718795137 ], [ -122.368474192879688, 37.732289244196302 ], [ -122.368444831940408, 37.732290739916586 ], [ -122.368417182378593, 37.732291521763315 ], [ -122.368387786921161, 37.732291644563965 ], [ -122.368358365231217, 37.732290737683726 ], [ -122.368330655267144, 37.732289116925642 ], [ -122.368301199755848, 37.732286837114728 ], [ -122.368273446651969, 37.732283500211402 ], [ -122.368243956285454, 37.732279847486197 ], [ -122.368216168673811, 37.732275137663905 ], [ -122.368188355181374, 37.73226939815595 ], [ -122.368160533065264, 37.732263315415068 ], [ -122.368141380018642, 37.732258125299396 ], [ -122.368113230055812, 37.732238999945096 ], [ -122.36808681703171, 37.732220190681751 ], [ -122.368058684352292, 37.732201751767072 ], [ -122.36803056030729, 37.7321836557974 ], [ -122.368002444910147, 37.732165903321956 ], [ -122.367974346780599, 37.732148837292527 ], [ -122.367944520376312, 37.73213179865752 ], [ -122.367916439533545, 37.732115419341561 ], [ -122.367886638690265, 37.732099410376478 ], [ -122.367832352327795, 37.732072458330258 ], [ -122.367819906502248, 37.73205892111374 ], [ -122.367804021362375, 37.732046125146958 ], [ -122.367788153134725, 37.73203401563628 ], [ -122.367772302171431, 37.732022592850853 ], [ -122.36775646811256, 37.732011856247048 ], [ -122.36773719473392, 37.732001860615576 ], [ -122.367719666903668, 37.731992524310549 ], [ -122.367700427353682, 37.731983901864261 ], [ -122.367681205751111, 37.731975965856719 ], [ -122.367660281054071, 37.731969086933262 ], [ -122.367641110867112, 37.73196321028319 ], [ -122.367620229304634, 37.731958047484753 ], [ -122.367599364995712, 37.731953571135463 ], [ -122.367576797935783, 37.73195015186301 ], [ -122.367555976757444, 37.731947391638229 ], [ -122.367533461105907, 37.731946031721549 ], [ -122.367512683055082, 37.731944987621269 ], [ -122.367483296703725, 37.731945453417282 ], [ -122.367460850404399, 37.731946839567364 ], [ -122.367440158593624, 37.731949227444112 ], [ -122.367417755407999, 37.731952329443608 ], [ -122.367397106730152, 37.731956433994185 ], [ -122.367376475284885, 37.731961224444852 ], [ -122.367355869716533, 37.731967044845845 ], [ -122.367335281393224, 37.731973551696129 ], [ -122.367316447568555, 37.731981060824729 ], [ -122.367295902357668, 37.73198928380036 ], [ -122.367278831305327, 37.731998138437078 ], [ -122.367261967911546, 37.732015230495207 ], [ -122.367245095194775, 37.732031979335368 ], [ -122.3672282059146, 37.732048041709206 ], [ -122.367209570402153, 37.732063445029034 ], [ -122.36719092591278, 37.732078505124896 ], [ -122.367170544505385, 37.732093249381123 ], [ -122.367151866198881, 37.732106936553961 ], [ -122.367109296757391, 37.732133363410995 ], [ -122.367093954975161, 37.732142190615349 ], [ -122.367075008646182, 37.732145238041312 ], [ -122.367056088868793, 37.732149314858027 ], [ -122.367037194611967, 37.732154421356583 ], [ -122.367020054502376, 37.732160529869304 ], [ -122.367002940956212, 37.732167668322553 ], [ -122.366985852582928, 37.732175836463647 ], [ -122.366968782142251, 37.732184691044218 ], [ -122.366955194489435, 37.732194520522967 ], [ -122.366939895442201, 37.732205063845008 ], [ -122.366921113252403, 37.732214632268928 ], [ -122.366907922208441, 37.732240250159542 ], [ -122.366903055326574, 37.732253031987497 ], [ -122.36689644254804, 37.732265154204406 ], [ -122.366886346633592, 37.732276301798386 ], [ -122.366876207612776, 37.732285733533743 ], [ -122.366862576812395, 37.732293846594757 ], [ -122.366847157024765, 37.732299584734065 ], [ -122.366829956520121, 37.73230329090832 ], [ -122.366814424990764, 37.732304567093777 ], [ -122.366797104132374, 37.732303468361799 ], [ -122.366781459823102, 37.732300282335181 ], [ -122.366765755862758, 37.732294693984812 ], [ -122.366677289399263, 37.732215062245395 ], [ -122.365834112470068, 37.733139703386506 ], [ -122.365856312715465, 37.733197380427903 ], [ -122.365316308712423, 37.733799605173139 ], [ -122.365299125359911, 37.733803997570533 ], [ -122.365281864478533, 37.733805301201542 ], [ -122.365264543279295, 37.733804201695385 ], [ -122.365248890111999, 37.733800672514192 ], [ -122.365233177335227, 37.733794740734439 ], [ -122.365220860913354, 37.733786351644746 ], [ -122.36520850211312, 37.733776246410308 ], [ -122.365199556897281, 37.733764370320301 ], [ -122.365194052497998, 37.733751753307722 ], [ -122.365191987516951, 37.733738394845759 ], [ -122.366541619606664, 37.732183905191341 ], [ -122.366457610712786, 37.732144032787161 ], [ -122.366314210759612, 37.732287082999953 ], [ -122.3662299863946, 37.73223862977143 ], [ -122.365558974179407, 37.732995040740846 ], [ -122.365446339012479, 37.733054852496743 ], [ -122.365330791791507, 37.732998653575926 ], [ -122.365348830518258, 37.732890552295949 ], [ -122.366182259071422, 37.731990790641241 ], [ -122.366150729675482, 37.731974808623058 ], [ -122.366190760220135, 37.731916146203794 ], [ -122.366000047621938, 37.731827832578617 ], [ -122.365157289100338, 37.732769287028631 ], [ -122.365013538239467, 37.732829590952356 ], [ -122.364937337201866, 37.732756287397883 ], [ -122.364966161751269, 37.732664496598098 ], [ -122.365779063978152, 37.731773647061175 ], [ -122.365808723306458, 37.731715149013056 ], [ -122.36565085480737, 37.73162631442078 ], [ -122.364828209515139, 37.732542382802549 ], [ -122.364733415884473, 37.732486198184951 ], [ -122.36510850587311, 37.732065821885968 ], [ -122.363700263748257, 37.731284294325405 ], [ -122.362990310438022, 37.732074612690823 ], [ -122.362876152103269, 37.73214268678057 ], [ -122.362621964702214, 37.732005583479989 ], [ -122.363403772813015, 37.731115242143439 ], [ -122.362852893371198, 37.730800849481362 ], [ -122.362060297076368, 37.731674875973241 ], [ -122.361775005493385, 37.731538605834466 ], [ -122.362457618945527, 37.730761087830871 ], [ -122.36256707070703, 37.730643300895977 ], [ -122.361976423103286, 37.73032884619532 ], [ -122.362085849184908, 37.730210029743688 ], [ -122.362069397962841, 37.730199017017426 ], [ -122.361906454524132, 37.730089941350215 ], [ -122.361876896198638, 37.7300835409455 ], [ -122.361847355773691, 37.730077827250902 ], [ -122.361816112221945, 37.7300731708266 ], [ -122.361786605500626, 37.73006882976096 ], [ -122.361755405286033, 37.7300658891753 ], [ -122.361724221585348, 37.730063635320825 ], [ -122.361693055759957, 37.73006206735193 ], [ -122.361661915051329, 37.730061529615817 ], [ -122.361630792215976, 37.730061677765306 ], [ -122.36159969448822, 37.730062855873037 ], [ -122.361570343233169, 37.730064693102427 ], [ -122.361539288834621, 37.730067587323632 ], [ -122.361508260224696, 37.730071511217581 ], [ -122.361487577348555, 37.730074241551335 ], [ -122.361465200270104, 37.730078372100991 ], [ -122.36144452599406, 37.730081445928768 ], [ -122.361422114524615, 37.730084203562313 ], [ -122.361399676911404, 37.730085931241504 ], [ -122.361377231405925, 37.730087316227653 ], [ -122.361354776950733, 37.730088357713434 ], [ -122.361334025287775, 37.730088341929708 ], [ -122.361311536447275, 37.730088010499266 ], [ -122.361266507191544, 37.730085288263524 ], [ -122.36124396677755, 37.73008289745821 ], [ -122.361223137761826, 37.730079792886535 ], [ -122.361200571564154, 37.73007637239192 ], [ -122.361179716772469, 37.730072238406137 ], [ -122.361157115844946, 37.730067444726025 ], [ -122.361136235265462, 37.730062280776941 ], [ -122.361115337498504, 37.730056430370134 ], [ -122.361094422544639, 37.73004989350558 ], [ -122.361073490411371, 37.73004267045787 ], [ -122.361054278613267, 37.730035076593815 ], [ -122.361035057871703, 37.730027139230472 ], [ -122.361015811356936, 37.73001817245752 ], [ -122.360991310330121, 37.730006541779254 ], [ -122.360878119757459, 37.729906007842899 ], [ -122.360753350678607, 37.729964633086148 ], [ -122.360767411397134, 37.729973682080846 ], [ -122.360779769645887, 37.729983787501872 ], [ -122.360792153340185, 37.729994922882732 ], [ -122.360802816699632, 37.730006428796685 ], [ -122.360811777948271, 37.730018991681831 ], [ -122.360819018860866, 37.730031925100903 ], [ -122.360824539436592, 37.730045229054205 ], [ -122.360830077893482, 37.730059219450361 ], [ -122.360833887418579, 37.730073237154031 ], [ -122.360834600711755, 37.730101724997503 ], [ -122.360833232727501, 37.73011616811506 ], [ -122.360830128237026, 37.730130294747774 ], [ -122.360825286222877, 37.73014410573559 ], [ -122.360820435267328, 37.730157573501607 ], [ -122.360812119233699, 37.73017075290813 ], [ -122.360803785650091, 37.730183245316248 ], [ -122.36079369735269, 37.730194735624472 ], [ -122.360781872558661, 37.730205909995462 ], [ -122.360768293049901, 37.730216082265592 ], [ -122.360754687743039, 37.730225224303872 ], [ -122.360741056659265, 37.73023333693407 ], [ -122.360665462649848, 37.730253072686551 ], [ -122.360627639840885, 37.730261910451617 ], [ -122.360550239838858, 37.730278584111481 ], [ -122.360510654054025, 37.730286076778114 ], [ -122.360471050717251, 37.730292882433631 ], [ -122.360433168073058, 37.730299317816012 ], [ -122.360393539299068, 37.730305094033461 ], [ -122.360353901588383, 37.730310527290548 ], [ -122.36031424736656, 37.730315273520141 ], [ -122.360274575618163, 37.730319333562264 ], [ -122.36023489527426, 37.730323050363999 ], [ -122.360195197398738, 37.730326080703698 ], [ -122.360155491620617, 37.730328767792003 ], [ -122.36011403972519, 37.730330795984656 ], [ -122.360074299227378, 37.730332109868229 ], [ -122.360034550144817, 37.730333080785911 ], [ -122.359994783535413, 37.730333365241471 ], [ -122.359955000437409, 37.730332963218565 ], [ -122.359915208404431, 37.73033221796058 ], [ -122.359854003265156, 37.730305028433676 ], [ -122.359236184689337, 37.730009873800213 ], [ -122.358789557472875, 37.729783434605018 ], [ -122.358957232902597, 37.729711773403928 ], [ -122.358843527027545, 37.729659659273459 ], [ -122.358967948048644, 37.72951794751873 ], [ -122.361421463309, 37.72895351973942 ], [ -122.361888691367042, 37.728828706580281 ], [ -122.361912779413899, 37.728823862451726 ], [ -122.361935121341887, 37.72881835863901 ], [ -122.361959166726493, 37.728811798085822 ], [ -122.361981474603837, 37.72880492162534 ], [ -122.362003764939914, 37.728797358986661 ], [ -122.3620260473499, 37.728789452556619 ], [ -122.362046575060617, 37.728780544041506 ], [ -122.36206882272441, 37.728771264975364 ], [ -122.362089324633246, 37.728761327046321 ], [ -122.362109817923212, 37.728751045337532 ], [ -122.362128556858551, 37.728739761540034 ], [ -122.362149024344589, 37.728728450417933 ], [ -122.362167737129766, 37.728716137213183 ], [ -122.362184713430636, 37.728703507540494 ], [ -122.36220340934922, 37.728690507870553 ], [ -122.362220359498949, 37.7286768487917 ], [ -122.362237301035364, 37.728662846208799 ], [ -122.362250811264033, 37.72864992822533 ], [ -122.362262661390673, 37.728639783385113 ], [ -122.36227275785032, 37.728628636175188 ], [ -122.362281099606278, 37.728616486612424 ], [ -122.362287695612011, 37.728603678193288 ], [ -122.362292545854572, 37.728590210368907 ], [ -122.362295667196847, 37.728576769873541 ], [ -122.36229531457775, 37.728562697566197 ], [ -122.362293242345899, 37.72854899552955 ], [ -122.362289449824587, 37.728535664323587 ], [ -122.362282165799911, 37.728521014856675 ], [ -122.36224367326102, 37.72850308198516 ], [ -122.36222272423764, 37.72849517213659 ], [ -122.362203512030703, 37.728487578468915 ], [ -122.362182580222708, 37.728480355341809 ], [ -122.362161665625351, 37.728473818939591 ], [ -122.36213904001275, 37.728467995754578 ], [ -122.362118159814372, 37.728462831978135 ], [ -122.362095568274697, 37.728458382247503 ], [ -122.36207471387344, 37.728454247869884 ], [ -122.362052148130076, 37.728450827537444 ], [ -122.362029590642308, 37.728447750433155 ], [ -122.362007059652058, 37.728445703269323 ], [ -122.361984536902597, 37.72844399878452 ], [ -122.361962039957774, 37.728443324251103 ], [ -122.361939551266488, 37.728442992945915 ], [ -122.361917071865491, 37.728443004852515 ], [ -122.361894617914601, 37.72844404644146 ], [ -122.361872181160052, 37.728445774480214 ], [ -122.361849752657179, 37.728447845747191 ], [ -122.361830790542825, 37.728450205592154 ], [ -122.36144874592047, 37.728523885066956 ], [ -122.359253585410741, 37.729046466872113 ], [ -122.358871094316868, 37.729171649615353 ], [ -122.35773138480485, 37.729443027877707 ], [ -122.357479163493281, 37.729245451178592 ], [ -122.357874687244575, 37.728949759623752 ], [ -122.360022133800982, 37.7284550828631 ], [ -122.360041044772572, 37.728450663666095 ], [ -122.360061675709247, 37.728445874222452 ], [ -122.360082280872575, 37.728440055093671 ], [ -122.360102877442358, 37.728433892734103 ], [ -122.360121736868166, 37.728427414435657 ], [ -122.360140578770867, 37.728420249959875 ], [ -122.360159404166396, 37.728412398466652 ], [ -122.36017822062847, 37.728404204023278 ], [ -122.360197019904888, 37.728395323122534 ], [ -122.360214082043697, 37.728386126559464 ], [ -122.360231126982725, 37.728376242990407 ], [ -122.360248163333068, 37.728366016466275 ], [ -122.36026519109457, 37.728355446987074 ], [ -122.360282201662045, 37.728344190776504 ], [ -122.36029747473269, 37.728332618361613 ], [ -122.360312739905595, 37.728320702981229 ], [ -122.36033977808566, 37.728295553906939 ], [ -122.360351645599536, 37.728286095712676 ], [ -122.360365302143265, 37.728279012806425 ], [ -122.360378915726486, 37.728270213762769 ], [ -122.360389029601677, 37.728259753167933 ], [ -122.360397388807456, 37.728248290192134 ], [ -122.360404002281641, 37.728236168057407 ], [ -122.360407132887943, 37.728223070833039 ], [ -122.360408534948149, 37.728210000904646 ], [ -122.360406479925388, 37.728196985842978 ], [ -122.360402696345233, 37.728183997528113 ], [ -122.360395481466924, 37.728172094035642 ], [ -122.360384800897904, 37.728159901357934 ], [ -122.360355252076161, 37.728153844345819 ], [ -122.360324001174106, 37.728148844017177 ], [ -122.360292766760338, 37.728144529870931 ], [ -122.360263287371851, 37.728141218642236 ], [ -122.360232104855839, 37.72813896383753 ], [ -122.360200939536938, 37.728137396028139 ], [ -122.360168062497735, 37.728136541413733 ], [ -122.360136940472302, 37.728136689443289 ], [ -122.360105834935908, 37.728137523929909 ], [ -122.360074755867601, 37.72813938835359 ], [ -122.3600437018695, 37.728142282186987 ], [ -122.360012665740157, 37.728145862455676 ], [ -122.359981646088997, 37.728150128906918 ], [ -122.359962709760239, 37.728153518132949 ], [ -122.357157402740128, 37.728825440588068 ], [ -122.357120443323197, 37.728661209162873 ], [ -122.356966039195882, 37.728710340572775 ], [ -122.357700794819991, 37.726282170189641 ], [ -122.358012892774155, 37.726039297958998 ], [ -122.361640904109578, 37.725218727031361 ], [ -122.361254856267323, 37.72416520811381 ], [ -122.357961297733482, 37.724943754212269 ], [ -122.357837451289754, 37.724623976380613 ], [ -122.361242175061918, 37.723839816434847 ], [ -122.361523474456462, 37.723775023602428 ], [ -122.362006156184762, 37.724061314272362 ], [ -122.362085553062059, 37.723986579862476 ], [ -122.364138473489092, 37.725245158496556 ], [ -122.364903800810126, 37.725700019832075 ], [ -122.364922994995325, 37.725706926339846 ], [ -122.364942154395848, 37.72571246021662 ], [ -122.364961297265722, 37.725717307625388 ], [ -122.364980405349158, 37.725720782403215 ], [ -122.364999487582764, 37.725723226947871 ], [ -122.365020272471824, 37.725724614719347 ], [ -122.365041031523262, 37.725724972806269 ], [ -122.365060035904975, 37.725724328578444 ], [ -122.365080734672432, 37.725722284344187 ], [ -122.365099679446047, 37.725719237235722 ], [ -122.365120326525016, 37.725715133358143 ], [ -122.365139219276756, 37.72571002716159 ], [ -122.365158077572545, 37.72570354805417 ], [ -122.365175190160244, 37.725696410130439 ], [ -122.365192276890355, 37.725688241974169 ], [ -122.365209337782844, 37.725679044409119 ], [ -122.365224644000321, 37.725668844532997 ], [ -122.365239933677543, 37.725657958189977 ], [ -122.365253477293393, 37.725646412763744 ], [ -122.365266995070428, 37.725633837929628 ], [ -122.365277047249194, 37.725620974052937 ], [ -122.365288802072243, 37.725607053401404 ], [ -122.365297091312215, 37.725592844257413 ], [ -122.365305371596236, 37.725578292165878 ], [ -122.365311906499173, 37.725563080433261 ], [ -122.365316704663556, 37.725547553385105 ], [ -122.365321493859298, 37.725531682840639 ], [ -122.365324157971415, 37.725500050955006 ], [ -122.365323753109038, 37.725483919287925 ], [ -122.365321628734094, 37.725468158485079 ], [ -122.365319495386515, 37.725452053911503 ], [ -122.365308348911952, 37.725421327973351 ], [ -122.36530105597663, 37.725406335184097 ], [ -122.365293771671986, 37.725391686170454 ], [ -122.365052724436055, 37.725226224132847 ], [ -122.362366609167395, 37.723678947002448 ], [ -122.362750319199407, 37.723258784478411 ], [ -122.359975808223695, 37.721629071078588 ], [ -122.360235314638061, 37.721358523571041 ], [ -122.362997490302945, 37.722979154853967 ], [ -122.363810654823226, 37.722102046578357 ], [ -122.359221672042082, 37.719406711581087 ], [ -122.359190415962686, 37.719401367722426 ], [ -122.359178102953109, 37.719392977976057 ], [ -122.359165764191417, 37.719383558545161 ], [ -122.359156864943031, 37.719373398101943 ], [ -122.359147948527337, 37.719362551202536 ], [ -122.359140734699196, 37.719350647341678 ], [ -122.359135249212571, 37.719338716202891 ], [ -122.359131474551347, 37.719326071336383 ], [ -122.359129420336572, 37.719313055953705 ], [ -122.359129102699868, 37.719300356526816 ], [ -122.359130504470841, 37.719287286600235 ], [ -122.359133643855102, 37.719274532613021 ], [ -122.359136791138639, 37.719262122138936 ], [ -122.359143412603785, 37.719250343285097 ], [ -122.359151771327333, 37.719238880100924 ], [ -122.359815079156419, 37.718522454511614 ], [ -122.359825208614936, 37.718512680409624 ], [ -122.359837083220526, 37.718503565479963 ], [ -122.359848984278528, 37.718495480221421 ], [ -122.359862639071579, 37.718488397362229 ], [ -122.359878056187881, 37.718482660129546 ], [ -122.359891771100081, 37.718477980135759 ], [ -122.359908985921422, 37.718474961146882 ], [ -122.359924497848894, 37.718472999407822 ], [ -122.359940043783581, 37.718472410583125 ], [ -122.359957353082052, 37.718473167366923 ], [ -122.359972959482164, 37.71847498112659 ], [ -122.359988608480634, 37.718478511028351 ], [ -122.360004292526796, 37.718483413828061 ], [ -122.360018273683508, 37.718489373879351 ], [ -122.360032280595263, 37.718496363062989 ], [ -122.360394084962891, 37.718708825628347 ], [ -122.36271365411622, 37.720070891670908 ], [ -122.36303235937622, 37.719710951141145 ], [ -122.363036490990609, 37.719706284726648 ], [ -122.363048348187263, 37.719696483020158 ], [ -122.363061977094034, 37.719688370383778 ], [ -122.363077377690658, 37.719681945993095 ], [ -122.363092812365167, 37.719676895064836 ], [ -122.363110026642786, 37.719673875619748 ], [ -122.363137662964078, 37.719672751789325 ], [ -122.363153270039291, 37.719674565121885 ], [ -122.363173373533897, 37.719648838527299 ], [ -122.363166176233463, 37.71963762109376 ], [ -122.363158953131787, 37.71962537425204 ], [ -122.363155178117267, 37.719612729509564 ], [ -122.363154851195048, 37.719599687141276 ], [ -122.363156260860393, 37.719586960120296 ], [ -122.363161127909819, 37.719574178689925 ], [ -122.363167748765107, 37.719562399610538 ], [ -122.363176106218731, 37.71955093642692 ], [ -122.363656438242259, 37.719054731797513 ], [ -122.362565556645677, 37.718438823432876 ], [ -122.362551540892355, 37.718431490773646 ], [ -122.362539227318422, 37.718423101382719 ], [ -122.362526897237885, 37.718414025524474 ], [ -122.362514558214372, 37.718404606442824 ], [ -122.36250392996385, 37.718394473583345 ], [ -122.36249501352286, 37.718383626930098 ], [ -122.362486088138667, 37.718372437054043 ], [ -122.362478882472217, 37.718360876623265 ], [ -122.362471659607849, 37.718348629736866 ], [ -122.362466165060482, 37.718336355523718 ], [ -122.362462381629214, 37.718323367528868 ], [ -122.362458606799294, 37.718310722761444 ], [ -122.362456543084349, 37.718297364212575 ], [ -122.362455889483016, 37.71827127891747 ], [ -122.362457290995351, 37.718258208943666 ], [ -122.362463550642957, 37.718232014343421 ], [ -122.362473301309748, 37.71820713800021 ], [ -122.362481650470514, 37.718195331904944 ], [ -122.362488271303789, 37.718183552586531 ], [ -122.362498365618194, 37.718172405349378 ], [ -122.362508477828271, 37.718161944830108 ], [ -122.358583730913253, 37.71588288357988 ], [ -122.358723442672868, 37.715731315969869 ], [ -122.362644996220368, 37.718021067053435 ], [ -122.362786346352422, 37.717866035066777 ], [ -122.363866062384275, 37.716726843144961 ], [ -122.359920053051226, 37.714425844522381 ], [ -122.3600361482696, 37.714297997140655 ], [ -122.36397478584415, 37.716580910244524 ], [ -122.364251293111423, 37.716300126312575 ], [ -122.364263175435909, 37.716291354435697 ], [ -122.364276820788504, 37.716283927560291 ], [ -122.364290517794743, 37.716278560323119 ], [ -122.364305977843998, 37.71627453863573 ], [ -122.364321497804667, 37.716272919544586 ], [ -122.364340525947497, 37.716273305108999 ], [ -122.364356149216732, 37.716275805018348 ], [ -122.364370087250265, 37.716280047868423 ], [ -122.364395976814166, 37.716278264915964 ], [ -122.365216452571488, 37.715351584174513 ], [ -122.365237670931208, 37.715301460414111 ], [ -122.365239473467412, 37.715235505999942 ], [ -122.365193066829221, 37.715177525619964 ], [ -122.364773705990487, 37.714932479640773 ], [ -122.364736623334764, 37.714970493365534 ], [ -122.364580089435904, 37.714864468359181 ], [ -122.364552651047106, 37.714804470653064 ], [ -122.364268407924328, 37.714638661242532 ], [ -122.364188831965635, 37.714636830395087 ], [ -122.364013771393857, 37.714550326239625 ], [ -122.364049082949748, 37.714510623951149 ], [ -122.363306902062419, 37.714077710648965 ], [ -122.363268090521188, 37.714115751260778 ], [ -122.363113289075798, 37.714009696947421 ], [ -122.363085852186202, 37.713949698891518 ], [ -122.362801615602848, 37.713783885919405 ], [ -122.362722040595187, 37.71378205408088 ], [ -122.362546983742845, 37.713695547734027 ], [ -122.362582295682941, 37.71365584615441 ], [ -122.361848935833351, 37.713229308027309 ], [ -122.361811844287786, 37.713266977882924 ], [ -122.361655318485532, 37.713160948681605 ], [ -122.361629610933349, 37.71310092296946 ], [ -122.361345380842891, 37.712935106461451 ], [ -122.361265806779613, 37.712933273639017 ], [ -122.361094407643563, 37.712854605021889 ], [ -122.361124337741387, 37.712807091000208 ], [ -122.361139435636801, 37.712788654437141 ], [ -122.361171360606775, 37.712751753157583 ], [ -122.36134313774096, 37.71284552396822 ], [ -122.361365062017143, 37.71289256197857 ], [ -122.361673856292811, 37.713072754562113 ], [ -122.361737876782101, 37.71307483295093 ], [ -122.361918064233436, 37.713159199186485 ], [ -122.361884368625439, 37.713194411838948 ], [ -122.362612508613324, 37.713619658283974 ], [ -122.362644433215863, 37.713582756884641 ], [ -122.362801089987357, 37.713693933186988 ], [ -122.362823024023626, 37.713741313598298 ], [ -122.363131825365173, 37.713921502610397 ], [ -122.363194118414285, 37.713923607543634 ], [ -122.3633743099209, 37.714007971534919 ], [ -122.36334061461109, 37.714043184324609 ], [ -122.364079295520412, 37.714474435984016 ], [ -122.364111219761938, 37.714437534192207 ], [ -122.364267880521169, 37.714548708254327 ], [ -122.36428981575132, 37.714596088660656 ], [ -122.364598624198337, 37.714776273803345 ], [ -122.3646609180052, 37.714778377960677 ], [ -122.364841113248019, 37.714862739696905 ], [ -122.364807418245547, 37.714897952901332 ], [ -122.365388188712686, 37.715236584611482 ], [ -122.364417662979704, 37.716315691686994 ], [ -122.364424868749381, 37.716327252003005 ], [ -122.364428635197243, 37.716339553209288 ], [ -122.36443069025762, 37.716352568505734 ], [ -122.364429280864911, 37.716365295273981 ], [ -122.364446761552514, 37.716372915954452 ], [ -122.364465978782491, 37.716380852784248 ], [ -122.364483468425433, 37.716388816406734 ], [ -122.364500965996967, 37.716397123539672 ], [ -122.364516744604018, 37.716405801242992 ], [ -122.364534251138338, 37.716414451592989 ], [ -122.364551766293047, 37.716423445442487 ], [ -122.364583357274199, 37.716442173479727 ], [ -122.364621964676815, 37.71646491108153 ], [ -122.364637708534104, 37.71647221613857 ], [ -122.364653409330941, 37.716477804506788 ], [ -122.364670803974548, 37.716481993153423 ], [ -122.36468815555574, 37.716484465110796 ], [ -122.364705455483815, 37.716484877975191 ], [ -122.364722720971017, 37.716483917927022 ], [ -122.364739943405979, 37.716481241738855 ], [ -122.364757122787552, 37.716476849410668 ], [ -122.364774250504041, 37.716470397714986 ], [ -122.364791343775195, 37.716462573106803 ], [ -122.364806691205743, 37.716454089407321 ], [ -122.364822022102643, 37.716444919239876 ], [ -122.364837335428504, 37.716435062620917 ], [ -122.364850911876999, 37.716424890408376 ], [ -122.364864471086165, 37.716414031190091 ], [ -122.364878013083185, 37.716402486064425 ], [ -122.364901605717435, 37.716378076804247 ], [ -122.364911648449322, 37.716364869981213 ], [ -122.364916549084953, 37.716353461387165 ], [ -122.364917950128515, 37.716340391380108 ], [ -122.364921087704161, 37.716327637243623 ], [ -122.364924234581224, 37.716315226323587 ], [ -122.364933983827555, 37.716290349774575 ], [ -122.36494722438934, 37.71626679140374 ], [ -122.364953853285272, 37.716255355719817 ], [ -122.364962219394371, 37.716244235619989 ], [ -122.364972322024684, 37.716233431114844 ], [ -122.364982433270342, 37.716222970110884 ], [ -122.364992553124679, 37.716212852333562 ], [ -122.365002690199319, 37.716203421010327 ], [ -122.365014563801225, 37.716194305555454 ], [ -122.365026455321683, 37.716185876818017 ], [ -122.36504008335524, 37.716177763399081 ], [ -122.365052000352904, 37.716170364072397 ], [ -122.365065654230065, 37.716163280882121 ], [ -122.365094741897337, 37.716151145947677 ], [ -122.365110106446139, 37.716143348388663 ], [ -122.365113201618499, 37.716128878372835 ], [ -122.365116304696627, 37.716114751046177 ], [ -122.365122873261583, 37.716100912485444 ], [ -122.365129459055197, 37.716087760653849 ], [ -122.365139510312659, 37.716074896763658 ], [ -122.365149578106625, 37.716062719613106 ], [ -122.365161392080509, 37.716051201814444 ], [ -122.365176644641494, 37.716038942289195 ], [ -122.365188518887109, 37.71602982680561 ], [ -122.365202138268813, 37.716021370414808 ], [ -122.365217494173905, 37.716013229888688 ], [ -122.365231148006444, 37.716006146679064 ], [ -122.365246539040172, 37.715999378773581 ], [ -122.365263666604179, 37.715992927006241 ], [ -122.365279091398932, 37.715987532291955 ], [ -122.365294534108472, 37.71598282401969 ], [ -122.365311721955024, 37.715978774837772 ], [ -122.365328927025729, 37.715975412108293 ], [ -122.365346149321098, 37.715972735831272 ], [ -122.365363388841544, 37.715970746006697 ], [ -122.365380645587578, 37.715969442634552 ], [ -122.365410025637061, 37.715968977361726 ], [ -122.365427334408309, 37.715969733342163 ], [ -122.365444651102152, 37.715970832558476 ], [ -122.365461994328726, 37.715972961443697 ], [ -122.365479354092372, 37.715975776792263 ], [ -122.365495012131262, 37.715979649180539 ], [ -122.365512406698826, 37.715983837428631 ], [ -122.365528089891654, 37.715988739505875 ], [ -122.365543791005862, 37.715994328024976 ], [ -122.36555950900491, 37.716000603002399 ], [ -122.36557523562081, 37.716007221205196 ], [ -122.365589259822642, 37.716014896460429 ], [ -122.365603292648544, 37.71602291521603 ], [ -122.365617342692417, 37.7160316198757 ], [ -122.365631409975279, 37.716041011263187 ], [ -122.365643757615857, 37.716050773249407 ], [ -122.365654385267973, 37.716060905840358 ], [ -122.365666759104258, 37.716071697501043 ], [ -122.365675684345334, 37.716082887145667 ], [ -122.365686355771075, 37.716094735860018 ], [ -122.36572383810848, 37.716141526396989 ], [ -122.365761311532381, 37.716187973699725 ], [ -122.365800496728525, 37.716233707149122 ], [ -122.365839664050739, 37.716278754141264 ], [ -122.365880551762743, 37.716323430505255 ], [ -122.365923158830057, 37.716367736255741 ], [ -122.365965740100123, 37.716411012308278 ], [ -122.366010040727502, 37.716453917744857 ], [ -122.366059638577141, 37.716501202978222 ], [ -122.366081205013529, 37.716533824258086 ], [ -122.366090251696093, 37.716549819043124 ], [ -122.366102754235541, 37.716565759079053 ], [ -122.366113529199154, 37.716581726482502 ], [ -122.366126023477833, 37.716597323283004 ], [ -122.366136789488536, 37.716612947462366 ], [ -122.366161760837542, 37.716643454602135 ], [ -122.366175966176996, 37.71665833756186 ], [ -122.366188442901887, 37.716673247906257 ], [ -122.366216836381071, 37.716702327637208 ], [ -122.36623102450136, 37.716716523860804 ], [ -122.366245204016678, 37.716730377129863 ], [ -122.366261112159151, 37.716744203009718 ], [ -122.366275291685326, 37.716758056275097 ], [ -122.366322963076229, 37.716797474553488 ], [ -122.366340573678841, 37.716810243632011 ], [ -122.36635644703216, 37.716822696320264 ], [ -122.366374040064969, 37.716834778944495 ], [ -122.366389904811129, 37.716846888400809 ], [ -122.366425073654625, 37.716870366632683 ], [ -122.366444377745808, 37.716881735132603 ], [ -122.36646194494493, 37.716892787786946 ], [ -122.366493510758119, 37.716910485903114 ], [ -122.366623549631328, 37.71699392291945 ], [ -122.366755273981596, 37.717075616265298 ], [ -122.367009737276547, 37.717225410385531 ], [ -122.367367636576219, 37.717431936135505 ], [ -122.367722202011066, 37.717643320779338 ], [ -122.367734498100774, 37.717651023186086 ], [ -122.367746786266892, 37.717658382627967 ], [ -122.36776079374863, 37.717665370893641 ], [ -122.367773072942654, 37.717672386836448 ], [ -122.367787080436401, 37.717679375373592 ], [ -122.367799351010589, 37.717686048086428 ], [ -122.367813349884699, 37.717692693393225 ], [ -122.367827331518725, 37.717698652518493 ], [ -122.367841321766008, 37.717704954320148 ], [ -122.367855303397675, 37.717710913167537 ], [ -122.367883249424608, 37.717722144677452 ], [ -122.367898942111211, 37.717727389659409 ], [ -122.367916388621566, 37.717733636645491 ], [ -122.367933775456365, 37.71773748130223 ], [ -122.367951144357875, 37.717740639787564 ], [ -122.367968522563814, 37.717744140937377 ], [ -122.368003243800771, 37.717749770880694 ], [ -122.368020595449934, 37.717752242626815 ], [ -122.368037939165831, 37.717754371132209 ], [ -122.368062204013668, 37.717756733239703 ], [ -122.368254623550371, 37.717776687237738 ], [ -122.368448797013713, 37.717797642645102 ], [ -122.368467860079036, 37.717799400451433 ], [ -122.368488660073098, 37.717801474067137 ], [ -122.368509442812467, 37.717802861224882 ], [ -122.368528488624563, 37.717803932567065 ], [ -122.368549262744196, 37.717804976765201 ], [ -122.368570028222393, 37.717805677183392 ], [ -122.368590785079604, 37.717806034645328 ], [ -122.368609804662938, 37.717806076298423 ], [ -122.368630552892, 37.717806090526203 ], [ -122.368672014835468, 37.717804746062299 ], [ -122.368691009223568, 37.717803758009893 ], [ -122.36871172294353, 37.717802399589416 ], [ -122.368753133102373, 37.717798995459539 ], [ -122.368773820932461, 37.717796607346749 ], [ -122.368818653167253, 37.717791775447523 ], [ -122.368861774362671, 37.71778765768024 ], [ -122.368906640766554, 37.717784198936094 ], [ -122.368949796818114, 37.717781454039603 ], [ -122.368994689457168, 37.717779025212373 ], [ -122.369037870695294, 37.717777309701347 ], [ -122.369082798189922, 37.717776253468948 ], [ -122.369126014299994, 37.717775911103146 ], [ -122.369170967339741, 37.717775884523917 ], [ -122.369202706794994, 37.71780044645373 ], [ -122.369235000726789, 37.717778302338516 ], [ -122.369269687549945, 37.717782559000163 ], [ -122.36928530265682, 37.717784714478952 ], [ -122.369302663340889, 37.717787529532423 ], [ -122.369353034337394, 37.717796687460655 ], [ -122.369368683984078, 37.717800216111328 ], [ -122.369384341918476, 37.717804087992491 ], [ -122.369401728852594, 37.717807932707132 ], [ -122.369417395761204, 37.717812147530914 ], [ -122.36943307096503, 37.717816705859768 ], [ -122.369450475169117, 37.717821237021361 ], [ -122.36948184319013, 37.717831039842252 ], [ -122.369497544990651, 37.717836627832767 ], [ -122.369513237815312, 37.717841872599593 ], [ -122.369527219606752, 37.717847831248413 ], [ -122.36954292970124, 37.717853762465438 ], [ -122.369558648085317, 37.71786003691291 ], [ -122.36957263852355, 37.71786633905797 ], [ -122.369588366230118, 37.717872956442982 ], [ -122.369602365299343, 37.717879601536971 ], [ -122.369618101305662, 37.71788656242515 ], [ -122.369646133989733, 37.717901225512634 ], [ -122.369674183958367, 37.717916575322121 ], [ -122.369702251198532, 37.717932611304448 ], [ -122.369714565153885, 37.717940999952816 ], [ -122.369728616041627, 37.717949704119498 ], [ -122.369740938290732, 37.717958435997602 ], [ -122.369754989198668, 37.717967140710293 ], [ -122.369767320771899, 37.717976215527116 ], [ -122.369804340049498, 37.718004470496631 ], [ -122.369814960605339, 37.718014259471069 ], [ -122.369827317757029, 37.718024364242766 ], [ -122.369837946607149, 37.718034496447622 ], [ -122.369850303765318, 37.718044601216874 ], [ -122.369882215899651, 37.718076027508822 ], [ -122.369892862378947, 37.71808684615722 ], [ -122.36990178884372, 37.718098035472593 ], [ -122.369912443617807, 37.718109197351708 ], [ -122.369930296561293, 37.718131575978724 ], [ -122.36994816678471, 37.718154641056984 ], [ -122.369957110535211, 37.718166516822102 ], [ -122.369967859963296, 37.718181454198835 ], [ -122.36997176593016, 37.718199247125732 ], [ -122.369979594447685, 37.718235519443965 ], [ -122.370002130211063, 37.718306581422858 ], [ -122.370009483835318, 37.718323976252783 ], [ -122.370015117785968, 37.718341741746322 ], [ -122.370022471423681, 37.718359136849926 ], [ -122.370031544728775, 37.71837616073956 ], [ -122.370038898366815, 37.718393555567452 ], [ -122.370047971693708, 37.718410580004878 ], [ -122.370057036030119, 37.718427260670879 ], [ -122.370066109704041, 37.718444284826738 ], [ -122.370075174401222, 37.718460965760372 ], [ -122.370085967071944, 37.718477619260632 ], [ -122.370107466765518, 37.718507493977114 ], [ -122.370126944899795, 37.718525726405055 ], [ -122.370169322597192, 37.718560763475431 ], [ -122.370190485553579, 37.718577252323918 ], [ -122.370213359219747, 37.718593027280086 ], [ -122.370236224957864, 37.718608459268403 ], [ -122.370260801392277, 37.718623176813935 ], [ -122.370285360570904, 37.718637208174954 ], [ -122.370309902493091, 37.718650553351452 ], [ -122.370336155456755, 37.71866318407794 ], [ -122.370362391163113, 37.718675128619182 ], [ -122.370390329287673, 37.718686016031121 ], [ -122.370416530117836, 37.718696587657909 ], [ -122.370444433710631, 37.71870610214912 ], [ -122.370472320036399, 37.718714930179793 ], [ -122.370501917760691, 37.718723044300269 ], [ -122.370529760919823, 37.718730156457141 ], [ -122.370559323753966, 37.718736897386265 ], [ -122.370588852049892, 37.718742265674756 ], [ -122.370618371366675, 37.718747290734377 ], [ -122.370647865465088, 37.718751286094815 ], [ -122.370679070273212, 37.718754567827233 ], [ -122.370708520488662, 37.718756846774177 ], [ -122.370739682448956, 37.718758412075765 ], [ -122.37076908982813, 37.718758975142002 ], [ -122.370800208258856, 37.718758824572994 ], [ -122.370829572444975, 37.718757671489442 ], [ -122.370860647321152, 37.718755804226205 ], [ -122.370891705615989, 37.718753250764479 ], [ -122.37090882433975, 37.718746454957831 ], [ -122.370925978308406, 37.718741032045394 ], [ -122.370944886167507, 37.718736611366033 ], [ -122.370963836875418, 37.718733906823715 ], [ -122.370982814191592, 37.718732231948131 ], [ -122.371005290654622, 37.718732218311914 ], [ -122.371024337093374, 37.718733289519548 ], [ -122.37104342637177, 37.71873607631499 ], [ -122.371062541577928, 37.718739893062697 ], [ -122.371079963024641, 37.718745110167617 ], [ -122.371097419033674, 37.718751700177599 ], [ -122.371113164004626, 37.718759004092 ], [ -122.371128943539134, 37.718767680911924 ], [ -122.371144757299319, 37.718777730917395 ], [ -122.371169489840725, 37.718798626351749 ], [ -122.371180101358092, 37.718808071985954 ], [ -122.371190696288693, 37.71881683115442 ], [ -122.371201281889995, 37.71882524710599 ], [ -122.371213579227003, 37.718832949137195 ], [ -122.37122759590342, 37.718840280490852 ], [ -122.371239875618699, 37.718847296070983 ], [ -122.371253857737614, 37.718853254513917 ], [ -122.371267831218006, 37.718858869728294 ], [ -122.371281796059677, 37.718864141714128 ], [ -122.371297471952232, 37.718868700063226 ], [ -122.37131140222705, 37.718872598863427 ], [ -122.371327043206463, 37.718875784031958 ], [ -122.371342667596267, 37.718878282733641 ], [ -122.371358283000305, 37.718880438211883 ], [ -122.371373872488391, 37.718881564281979 ], [ -122.371383664521929, 37.718882055893282 ], [ -122.371389452976118, 37.718882346579448 ], [ -122.371405016879592, 37.71888244268473 ], [ -122.371420554513023, 37.718881509112791 ], [ -122.371437794199721, 37.718879518405728 ], [ -122.371451465242302, 37.718873120657591 ], [ -122.371465170849277, 37.718868095815367 ], [ -122.371480639690603, 37.718864416415016 ], [ -122.371497879724302, 37.718862425968247 ], [ -122.371520347584678, 37.71886206900686 ], [ -122.371535954687204, 37.718863880954615 ], [ -122.371551596026961, 37.718867066362435 ], [ -122.371567280564975, 37.718871967353337 ], [ -122.371581271363922, 37.718878269258852 ], [ -122.371591935137346, 37.718889773936951 ], [ -122.371604326899728, 37.718901251433785 ], [ -122.371614991032587, 37.718912756378863 ], [ -122.371627374512514, 37.718923890915455 ], [ -122.37163802965712, 37.718935052362468 ], [ -122.371650413137317, 37.718946186622013 ], [ -122.371662779335963, 37.718956634426526 ], [ -122.371676882506705, 37.718967397995137 ], [ -122.371689248373897, 37.718977846076911 ], [ -122.371706869050627, 37.71899095730106 ], [ -122.371720799369825, 37.718994856052753 ], [ -122.371736440738147, 37.718998041161377 ], [ -122.371767706192628, 37.7190037249187 ], [ -122.371798919792468, 37.719007349306651 ], [ -122.371814508961251, 37.719008475049307 ], [ -122.371830090178449, 37.719009257552102 ], [ -122.371845662406855, 37.719009696831492 ], [ -122.371878500616674, 37.719009175012339 ], [ -122.371894038270653, 37.719008241377971 ], [ -122.371925078656488, 37.719005001201246 ], [ -122.371940582079091, 37.719002694647912 ], [ -122.37195607651114, 37.71900004487118 ], [ -122.371971553653992, 37.71899670863877 ], [ -122.371985302469042, 37.7189933998703 ], [ -122.372011244653535, 37.718993674331507 ], [ -122.372032105590733, 37.718998149908998 ], [ -122.372051212270634, 37.719001623269286 ], [ -122.372072029989994, 37.71900438270557 ], [ -122.372092830421707, 37.719006455684564 ], [ -122.372113604920486, 37.719007498979387 ], [ -122.372134362130282, 37.719007855816933 ], [ -122.372155093405652, 37.71900718297038 ], [ -122.372175798752636, 37.719005480714287 ], [ -122.372196486794678, 37.719003091451768 ], [ -122.372215428886747, 37.719000043756004 ], [ -122.372242952424102, 37.718994455566381 ], [ -122.372228495782323, 37.719038292867332 ], [ -122.372270562418365, 37.718992299913033 ], [ -122.372296686505393, 37.718999782068892 ], [ -122.372315810496232, 37.719003942114455 ], [ -122.372334917183963, 37.719007415154145 ], [ -122.372354014888927, 37.719010545244004 ], [ -122.372373104647863, 37.719013332367552 ], [ -122.372411231581538, 37.719016846431828 ], [ -122.372432006094613, 37.719017889945107 ], [ -122.372451034636384, 37.719018274204856 ], [ -122.372470046576993, 37.719017971996969 ], [ -122.372489049518421, 37.7190173262901 ], [ -122.372508043819963, 37.719016337627984 ], [ -122.372528749154483, 37.719014635035279 ], [ -122.372547708522333, 37.719012273465104 ], [ -122.372566659933526, 37.719009568654101 ], [ -122.372585593703946, 37.719006177391961 ], [ -122.372604518825696, 37.719002442899999 ], [ -122.372623426305779, 37.718998021956914 ], [ -122.372640597508592, 37.718993285523204 ], [ -122.372659479381312, 37.718987834613905 ], [ -122.372676624285006, 37.718982068225515 ], [ -122.372693803775505, 37.718977674741716 ], [ -122.372709280880628, 37.718974338412373 ], [ -122.372726511906819, 37.718972004289988 ], [ -122.372754147821666, 37.718970878203621 ], [ -122.372769737688571, 37.718972003808915 ], [ -122.372787072838463, 37.718973788668045 ], [ -122.372804451214719, 37.718977289109326 ], [ -122.372823557579665, 37.718980762349858 ], [ -122.37284089307164, 37.718982546920991 ], [ -122.372859921952113, 37.718982931109217 ], [ -122.372877179265899, 37.718981626639597 ], [ -122.372896130311645, 37.71897892178108 ], [ -122.372913318098071, 37.718974871772126 ], [ -122.372930471966299, 37.718969448293443 ], [ -122.372945853925259, 37.718962336438636 ], [ -122.372961200941234, 37.718953851680254 ], [ -122.37297652269902, 37.718944337228557 ], [ -122.372988361166023, 37.718933848067003 ], [ -122.373153843085163, 37.718881530021171 ], [ -122.373238802575472, 37.718854668953604 ], [ -122.373255964357725, 37.718849588939769 ], [ -122.37327313547901, 37.718844852139163 ], [ -122.373307494324067, 37.718836064994925 ], [ -122.373341870461658, 37.718827964294142 ], [ -122.373376281193742, 37.718821236490271 ], [ -122.37339522352984, 37.718818188324654 ], [ -122.373412437542669, 37.718815167642909 ], [ -122.37344690016738, 37.718810499178815 ], [ -122.37346585979968, 37.718808137455291 ], [ -122.373483108072492, 37.718806489950246 ], [ -122.373502085343517, 37.718804814394282 ], [ -122.373536599865218, 37.718802205264303 ], [ -122.373555594097851, 37.718801216433278 ], [ -122.373572868314369, 37.718800598320655 ], [ -122.373591880194738, 37.718800295931722 ], [ -122.373626463924381, 37.718800432589219 ], [ -122.373645492769242, 37.718800816925153 ], [ -122.373662792940365, 37.718801228479528 ], [ -122.373681839421252, 37.718802298708354 ], [ -122.373699157242569, 37.718803396705297 ], [ -122.373716483716436, 37.718804837926434 ], [ -122.373735538512435, 37.718806251653383 ], [ -122.373752881946515, 37.718808379328088 ], [ -122.373777165315929, 37.718811426700512 ], [ -122.373794552014559, 37.718815270502617 ], [ -122.373811948058801, 37.718819457517874 ], [ -122.373829360718858, 37.718824330994956 ], [ -122.373845054403262, 37.718829575178432 ], [ -122.373862484719027, 37.718835135098523 ], [ -122.373878195029278, 37.718841066016566 ], [ -122.373893923325326, 37.718847682825754 ], [ -122.373909659945298, 37.71885464341419 ], [ -122.373925413867383, 37.718862290179331 ], [ -122.373939439462958, 37.718869964162415 ], [ -122.373953482381623, 37.71887832514637 ], [ -122.373967533942846, 37.718887028806179 ], [ -122.373981594167574, 37.718896075965546 ], [ -122.373995663049072, 37.718905466349945 ], [ -122.374008012263729, 37.718915227454971 ], [ -122.374020370135099, 37.718925331785385 ], [ -122.374032736317702, 37.718935779346666 ], [ -122.374045111848744, 37.718946570122313 ], [ -122.374055767366514, 37.718957731625189 ], [ -122.374066431541209, 37.71896923635375 ], [ -122.374075367055113, 37.718980768858067 ], [ -122.374086048882475, 37.71899295975809 ], [ -122.374103954872169, 37.719017397114996 ], [ -122.374125681340118, 37.719056194989058 ], [ -122.374131204173437, 37.719069498331429 ], [ -122.374142266462783, 37.719096791479657 ], [ -122.374149872102961, 37.719124139622245 ], [ -122.374153683579266, 37.719138156919946 ], [ -122.3741578498839, 37.719166246785271 ], [ -122.374159941681143, 37.71918063453262 ], [ -122.374161014797295, 37.71922319463841 ], [ -122.374159641286965, 37.719237294429277 ], [ -122.374154923616729, 37.719255911162307 ], [ -122.374151882005847, 37.719272441042889 ], [ -122.374149256482738, 37.719305445791825 ], [ -122.374150087278892, 37.719338395550594 ], [ -122.374152239665406, 37.719355186158822 ], [ -122.374156103078988, 37.719371262815855 ], [ -122.374159975148416, 37.71938768269937 ], [ -122.374165558246304, 37.71940338863147 ], [ -122.374172878343202, 37.719419410566459 ], [ -122.374175234931528, 37.719424358448258 ], [ -122.374180181120835, 37.719434745498482 ], [ -122.374187466599793, 37.719449394251313 ], [ -122.374214300358844, 37.719485020571611 ], [ -122.374235611602799, 37.719507343543832 ], [ -122.374246258574956, 37.719518161801837 ], [ -122.374256896895602, 37.719528636832223 ], [ -122.374269254564041, 37.719538741415626 ], [ -122.374281604258542, 37.719548502210912 ], [ -122.374293944962744, 37.719557920058328 ], [ -122.374308005354067, 37.719566967177798 ], [ -122.374322057100812, 37.719575671343499 ], [ -122.374337854486313, 37.719585033911152 ], [ -122.3744195305989, 37.719669918743051 ], [ -122.374421587846186, 37.719682933852702 ], [ -122.374425381745823, 37.719696264692658 ], [ -122.374436392649173, 37.719721498445345 ], [ -122.374441889282195, 37.71973377237201 ], [ -122.374450833933636, 37.719745647518387 ], [ -122.374459769939222, 37.719757179712012 ], [ -122.374468688290634, 37.719768025457228 ], [ -122.37447931836833, 37.719778157235005 ], [ -122.374491667798353, 37.71978791856256 ], [ -122.374503990904699, 37.719796649665334 ], [ -122.374518034054987, 37.71980501030616 ], [ -122.374532059896538, 37.719812684492133 ], [ -122.374546059779846, 37.719819329271196 ], [ -122.374561771037051, 37.719825259811579 ], [ -122.374577455975697, 37.719830160400981 ], [ -122.374594868929591, 37.719835034038724 ], [ -122.374610562881386, 37.719840278119385 ], [ -122.374626299764742, 37.719847238061817 ], [ -122.374642062627359, 37.719855227956486 ], [ -122.37465612311729, 37.71986427503397 ], [ -122.374668489891121, 37.719874722521432 ], [ -122.374679145289875, 37.719885883971763 ], [ -122.374688099006775, 37.719898102595501 ], [ -122.374697069701313, 37.719911007951758 ], [ -122.374702592354737, 37.719924311271022 ], [ -122.374739822583123, 37.719960801838624 ], [ -122.37475222401271, 37.719972622223445 ], [ -122.374766345137616, 37.719984071873611 ], [ -122.374778737572512, 37.719995549034522 ], [ -122.374806962529064, 37.720017761873571 ], [ -122.374835170187708, 37.720039288252636 ], [ -122.374849265365711, 37.720049708213061 ], [ -122.374865089243499, 37.72006010065806 ], [ -122.37487917577208, 37.720070177388315 ], [ -122.374910788226885, 37.720089589372847 ], [ -122.374942383382447, 37.720108314895747 ], [ -122.374958163650305, 37.720116991200911 ], [ -122.37497395257985, 37.720126010730475 ], [ -122.374991452548869, 37.720134316294761 ], [ -122.375007224169792, 37.720142649366764 ], [ -122.375024715142516, 37.720150611705058 ], [ -122.375040478457933, 37.72015860154054 ], [ -122.375057961125222, 37.720166220641822 ], [ -122.375092909153366, 37.7201807723835 ], [ -122.375127822560231, 37.72019395120865 ], [ -122.375147007628541, 37.720200513379581 ], [ -122.375173261961393, 37.720213143025013 ], [ -122.375207993556202, 37.720219114069216 ], [ -122.375227160967086, 37.720224989504935 ], [ -122.375248074399238, 37.720231523871263 ], [ -122.375269005153854, 37.720238744686874 ], [ -122.375280076764881, 37.720243315652858 ], [ -122.375288225222903, 37.720246679460772 ], [ -122.375307453609949, 37.720254957463524 ], [ -122.375326699320624, 37.720263921916157 ], [ -122.375345953350021, 37.720273229597673 ], [ -122.375365216735446, 37.720282880491496 ], [ -122.3753827687447, 37.72029324535653 ], [ -122.37540033807889, 37.720304296671941 ], [ -122.375416187376842, 37.720315718733033 ], [ -122.375433774387304, 37.720327456490914 ], [ -122.375449641361485, 37.720339564994973 ], [ -122.375463797304775, 37.720352387466882 ], [ -122.375479689931566, 37.720365525926404 ], [ -122.375493863198955, 37.720379034573064 ], [ -122.375506317126977, 37.720392914231198 ], [ -122.375520507732517, 37.720407109602149 ], [ -122.375532978645822, 37.720421675715869 ], [ -122.375543729520388, 37.720436612578425 ], [ -122.375556287403313, 37.720454611223573 ], [ -122.375591629223138, 37.720553280549268 ], [ -122.375625259756632, 37.720652664115185 ], [ -122.375655442655059, 37.720752445930557 ], [ -122.375683906273295, 37.720852598477748 ], [ -122.375710658575045, 37.720953464995517 ], [ -122.375751172610691, 37.721120038334149 ], [ -122.375761845674063, 37.721131886137194 ], [ -122.375772510777281, 37.721143390976373 ], [ -122.375784886582679, 37.721154181570967 ], [ -122.375797236053231, 37.721163942215831 ], [ -122.375811288276211, 37.721172646201708 ], [ -122.375827043223865, 37.721180292429615 ], [ -122.375842875795513, 37.721191027973333 ], [ -122.375853514234834, 37.721201502862243 ], [ -122.37586245097873, 37.721213034941265 ], [ -122.375869667665128, 37.721224937774515 ], [ -122.375880696264161, 37.72125085784436 ], [ -122.375882762127716, 37.72126421615711 ], [ -122.375882828626871, 37.721266851037683 ], [ -122.375883125946686, 37.721278631666706 ], [ -122.37587827898912, 37.721292100060857 ], [ -122.375871711967719, 37.721305939209678 ], [ -122.375866881992167, 37.721320094336214 ], [ -122.37586031495924, 37.721333933209785 ], [ -122.375855484972746, 37.721348088061191 ], [ -122.375852374709183, 37.721361872437875 ], [ -122.375847544719377, 37.721376027288905 ], [ -122.375842723382974, 37.721390525091536 ], [ -122.375836520156881, 37.721418779747594 ], [ -122.375830334257259, 37.721447721130588 ], [ -122.375828969677642, 37.721462163887708 ], [ -122.375825876376013, 37.721476634447164 ], [ -122.375821782997832, 37.721519963536267 ], [ -122.375822146806243, 37.721534379045465 ], [ -122.375820790553732, 37.721549165583347 ], [ -122.375822254450057, 37.721607170846063 ], [ -122.375824355645946, 37.721621901779116 ], [ -122.375824719455352, 37.721636317288059 ], [ -122.375830995701023, 37.721679481253183 ], [ -122.375834816633215, 37.721693841711655 ], [ -122.375836908840029, 37.721708229972535 ], [ -122.375840729422237, 37.721722590161676 ], [ -122.375846503274232, 37.721745846994416 ], [ -122.375861785654777, 37.721803288846772 ], [ -122.375867698808605, 37.72183203728941 ], [ -122.375873620283713, 37.721861128963518 ], [ -122.375886199210129, 37.721948486558446 ], [ -122.375891128963588, 37.722006779991005 ], [ -122.37589187393715, 37.722036297459432 ], [ -122.375894433932729, 37.722069219668136 ], [ -122.375899939635701, 37.722081836473912 ], [ -122.375903725267278, 37.722094824309316 ], [ -122.37590923997503, 37.722107784060896 ], [ -122.375913025948279, 37.722120771615856 ], [ -122.375915092187512, 37.722134129920661 ], [ -122.375918886480093, 37.722147460707269 ], [ -122.375920953066782, 37.722160819006419 ], [ -122.375921290909631, 37.722174204834722 ], [ -122.375923357151819, 37.722187563139258 ], [ -122.375923694995222, 37.722200948967497 ], [ -122.375922304438902, 37.722214362319441 ], [ -122.375922642282234, 37.722227748147617 ], [ -122.375921251379538, 37.722241161504989 ], [ -122.375918210730603, 37.722257691412231 ], [ -122.375915160726379, 37.722273878103998 ], [ -122.375910383010535, 37.722290092308178 ], [ -122.375909052749606, 37.722305908524369 ], [ -122.3759060120953, 37.722322438431114 ], [ -122.375902962078825, 37.722338624847808 ], [ -122.375900319899742, 37.722370943166766 ], [ -122.375899007299083, 37.722387445555256 ], [ -122.375899414439516, 37.72240357719398 ], [ -122.375898101492368, 37.722420079587863 ], [ -122.375899331576733, 37.722468817730004 ], [ -122.3759014761321, 37.722485265065941 ], [ -122.375901883280534, 37.722501396978991 ], [ -122.375904027491728, 37.722517844320301 ], [ -122.375908298246003, 37.722550052555519 ], [ -122.375912171213727, 37.722566472367539 ], [ -122.375914306759483, 37.722582576207579 ], [ -122.375918170721874, 37.722598652798467 ], [ -122.375922026023247, 37.722614386162874 ], [ -122.375925889988949, 37.722630462753436 ], [ -122.375931482020547, 37.722646511825495 ], [ -122.3759353376729, 37.72266224518382 ], [ -122.375946504767398, 37.722693656868394 ], [ -122.37595381690933, 37.722709335457893 ], [ -122.375959392311572, 37.72272469779017 ], [ -122.375988606240981, 37.722786038138061 ], [ -122.37599590106781, 37.72280102999747 ], [ -122.376004915302616, 37.722815651110672 ], [ -122.376013938895468, 37.722830615438454 ], [ -122.376030187680101, 37.722857825817606 ], [ -122.376039254253797, 37.722874506280462 ], [ -122.376050040583777, 37.722890815990674 ], [ -122.376059098502594, 37.722907153225577 ], [ -122.376080671185491, 37.722939772641169 ], [ -122.376093177287544, 37.722955711594906 ], [ -122.376103946313975, 37.722971334847365 ], [ -122.376128941223584, 37.723002526570319 ], [ -122.376153918812676, 37.723033031286057 ], [ -122.376166398954496, 37.723047940552881 ], [ -122.376180598856877, 37.723062479064545 ], [ -122.376194807428803, 37.723077360800765 ], [ -122.376223189933157, 37.723105751362802 ], [ -122.37623737252926, 37.723119603414908 ], [ -122.376253283551748, 37.72313342793673 ], [ -122.376285088285584, 37.723160390521265 ], [ -122.37630098199665, 37.723173528583985 ], [ -122.376332751760657, 37.723199118255842 ], [ -122.376350356927531, 37.723211542324023 ], [ -122.37636795274409, 37.723223623174263 ], [ -122.376420715615595, 37.723258836008263 ], [ -122.376438285466264, 37.723269887443685 ], [ -122.376457584424074, 37.723280910784744 ], [ -122.376475145958864, 37.723291618708295 ], [ -122.376513708562953, 37.723312293030197 ], [ -122.376564456486463, 37.723336207150773 ], [ -122.376580211970165, 37.72334385327531 ], [ -122.376595984457055, 37.723352186404895 ], [ -122.376611774265584, 37.723361205435687 ], [ -122.376625852990443, 37.723370938725466 ], [ -122.37663994038509, 37.723381015239809 ], [ -122.37665232535511, 37.723392148965601 ], [ -122.376666438756033, 37.723403255155475 ], [ -122.376677112293109, 37.723415102871243 ], [ -122.376698494734327, 37.723440171193744 ], [ -122.376707475210537, 37.723453419335712 ], [ -122.376716464010698, 37.72346701070871 ], [ -122.376720284514462, 37.723481371145333 ], [ -122.37671721799309, 37.723496871391177 ], [ -122.376707592383568, 37.723526554064598 ], [ -122.376699305900857, 37.723540764010522 ], [ -122.376689273305857, 37.723554315043828 ], [ -122.376679223374595, 37.723567179623807 ], [ -122.376667418664113, 37.723579042064337 ], [ -122.376655545318613, 37.723588158957362 ], [ -122.377080106636114, 37.724240997344516 ], [ -122.377095367932696, 37.724229079787627 ], [ -122.377110646561107, 37.724217848681064 ], [ -122.37712595084453, 37.724207647256506 ], [ -122.377143001604594, 37.724198105004248 ], [ -122.377161806451838, 37.724189564342446 ], [ -122.377180636962038, 37.724182053636206 ], [ -122.377199502834941, 37.724175915820602 ], [ -122.377218385696736, 37.724170464459789 ], [ -122.377239031676282, 37.724166358457559 ], [ -122.377259703667661, 37.724163282404746 ], [ -122.377280401650879, 37.724161235477631 ], [ -122.377302862761439, 37.724160534182147 ], [ -122.377323664781798, 37.724162606235865 ], [ -122.377346194902287, 37.724164650746829 ], [ -122.377366988255417, 37.724166379566732 ], [ -122.377389501730306, 37.724167737606116 ], [ -122.377412006197545, 37.724168752695128 ], [ -122.377432782206725, 37.724169794776564 ], [ -122.377477756104398, 37.724170451485548 ], [ -122.377520967568458, 37.724169762808955 ], [ -122.377543428679331, 37.724169061467578 ], [ -122.377564152674637, 37.724168044443623 ], [ -122.377586596450428, 37.724166656916339 ], [ -122.377609031195107, 37.724164925615 ], [ -122.37762973784686, 37.724163222127238 ], [ -122.377674573698229, 37.724158387131986 ], [ -122.377695245662423, 37.724155310728172 ], [ -122.377717646069129, 37.724152206770718 ], [ -122.377738309358563, 37.724148787133252 ], [ -122.377760692073096, 37.724144996720867 ], [ -122.377801983955891, 37.724136784522699 ], [ -122.377824341339959, 37.724131964408841 ], [ -122.377861378513643, 37.724092230490051 ], [ -122.37790330341987, 37.724109074037422 ], [ -122.377953072363383, 37.724094202731443 ], [ -122.378002859323317, 37.724080017845608 ], [ -122.378052663257023, 37.724066519121855 ], [ -122.378102484525442, 37.724053707103792 ], [ -122.378152322784231, 37.724041581796904 ], [ -122.378202179051058, 37.724030142360959 ], [ -122.378253789425585, 37.724019705304357 ], [ -122.378303679673081, 37.724009639015357 ], [ -122.378375979604911, 37.723996811837594 ], [ -122.378413918203691, 37.723992773262658 ], [ -122.378431150905413, 37.723990438296809 ], [ -122.378450111707437, 37.723988076047718 ], [ -122.378467326705092, 37.723985054355325 ], [ -122.378486270155207, 37.72398200564826 ], [ -122.378503476136444, 37.723978641004621 ], [ -122.378522411247346, 37.723975248785557 ], [ -122.378573978156382, 37.723963095183649 ], [ -122.378592877874951, 37.723958330334597 ], [ -122.378610049829263, 37.723953592485465 ], [ -122.378644375709996, 37.723943431162333 ], [ -122.378661520946679, 37.723937663913084 ], [ -122.378678675540712, 37.723932239601808 ], [ -122.378695812103444, 37.723926129396091 ], [ -122.378711220902375, 37.723920046192298 ], [ -122.378728349122568, 37.723913592475675 ], [ -122.378745468663908, 37.723906795530496 ], [ -122.378760859771077, 37.723900026422747 ], [ -122.378776242185538, 37.723892913537782 ], [ -122.378793344365533, 37.723885430133492 ], [ -122.378808718111543, 37.723877974567344 ], [ -122.378824083164403, 37.723870175223993 ], [ -122.378854795921512, 37.723853890628249 ], [ -122.378885491312118, 37.723836919572342 ], [ -122.378899101889331, 37.723828118382741 ], [ -122.378914431877035, 37.723818946403391 ], [ -122.378928033770805, 37.723809801984345 ], [ -122.378943355765742, 37.723800286764138 ], [ -122.378956948975699, 37.723790799115662 ], [ -122.378970533505296, 37.723780968239559 ], [ -122.378997685199607, 37.72376062003049 ], [ -122.37900953226108, 37.723750473498114 ], [ -122.379023090751858, 37.723739613212317 ], [ -122.37903492981377, 37.723729123165697 ], [ -122.379050155637117, 37.723715832455909 ], [ -122.379063557916822, 37.723698793821882 ], [ -122.379075214748525, 37.72368109629867 ], [ -122.379086879906424, 37.723663742005776 ], [ -122.379098527703903, 37.723645701259585 ], [ -122.379110183827592, 37.723628003743777 ], [ -122.379120103528408, 37.723609990560298 ], [ -122.37913174263177, 37.723591606584641 ], [ -122.379141653299143, 37.723573250178703 ], [ -122.379151555283926, 37.723554550545835 ], [ -122.379159737512083, 37.723536221709345 ], [ -122.379169630463892, 37.723517178854308 ], [ -122.379177804358278, 37.72349850705946 ], [ -122.379185969211122, 37.723479491494309 ], [ -122.379192405639117, 37.723460503774781 ], [ -122.379200561813079, 37.723441145257027 ], [ -122.37921341729573, 37.723402483363635 ], [ -122.379218107928253, 37.723382836762347 ], [ -122.379224535661635, 37.723363505814724 ], [ -122.379233916574677, 37.723324212890873 ], [ -122.379236870433516, 37.723304250354701 ], [ -122.379241561053661, 37.72328460375217 ], [ -122.379250421598357, 37.723224716982607 ], [ -122.379252871777865, 37.723184847614071 ], [ -122.379253905947536, 37.723157361956225 ], [ -122.379253532794493, 37.723142603509906 ], [ -122.379254879369441, 37.723127473715763 ], [ -122.37925797172656, 37.723113003075639 ], [ -122.379262792162919, 37.723098505142787 ], [ -122.379267621960011, 37.723084350150188 ], [ -122.379275925257346, 37.723070826474732 ], [ -122.379284246592633, 37.723057988965195 ], [ -122.379294304689481, 37.723045467662629 ], [ -122.379306117221759, 37.723033947914878 ], [ -122.379319675538511, 37.723023087593674 ], [ -122.379333259872894, 37.723013256399945 ], [ -122.379348598663228, 37.723004427583476 ], [ -122.379363963140094, 37.722996628448691 ], [ -122.379381091436528, 37.722990174630688 ], [ -122.379398245074995, 37.722984750499421 ], [ -122.379417153855258, 37.722980328457844 ], [ -122.379434368594119, 37.722977307172805 ], [ -122.379455083545309, 37.722975946315401 ], [ -122.37947750088145, 37.722973528474888 ], [ -122.379499892178387, 37.722970080951924 ], [ -122.379523994530487, 37.722965919396081 ], [ -122.37954461400976, 37.722960783310853 ], [ -122.379566935863238, 37.722954589966704 ], [ -122.379589240353653, 37.72294771016621 ], [ -122.379609790385288, 37.722939828261154 ], [ -122.379630314379966, 37.722930916948918 ], [ -122.379649092581033, 37.722921346210235 ], [ -122.379669581838826, 37.72291106171236 ], [ -122.379686587876122, 37.72289980269619 ], [ -122.37970530565336, 37.722887829635219 ], [ -122.379722277304509, 37.722875197704134 ], [ -122.379737503175733, 37.722861906898203 ], [ -122.379752711680695, 37.722847929638164 ], [ -122.379767911153763, 37.722833609155607 ], [ -122.379779628445803, 37.722818314142543 ], [ -122.379793065118861, 37.722802648327246 ], [ -122.379812990079841, 37.722770054111137 ], [ -122.379822935192337, 37.722753070549636 ], [ -122.379814676042812, 37.722699980835564 ], [ -122.379867320110051, 37.722593727839261 ], [ -122.379934313833843, 37.722371188860151 ], [ -122.37993733657764, 37.72235397239195 ], [ -122.379942087368576, 37.72233672807188 ], [ -122.379948575592024, 37.722319799663914 ], [ -122.379956801239857, 37.722303186893228 ], [ -122.379965035219655, 37.722286917353493 ], [ -122.37997327788419, 37.722270991313792 ], [ -122.379983257611954, 37.722255380367073 ], [ -122.379994974077178, 37.722240085342186 ], [ -122.380006708591978, 37.72222547675721 ], [ -122.380020188517861, 37.72221152704482 ], [ -122.380035405172549, 37.7221978929784 ], [ -122.380048911140065, 37.722184973215455 ], [ -122.380065900274673, 37.722173027405468 ], [ -122.380082897401252, 37.72216142510468 ], [ -122.38009991257772, 37.722150509242532 ], [ -122.380116944421857, 37.722140279841042 ], [ -122.380135722713177, 37.722130709292628 ], [ -122.380156255096665, 37.722122140839112 ], [ -122.38018021822036, 37.722112487531497 ], [ -122.380200637722609, 37.722099457131471 ], [ -122.380221048535219, 37.722086083501829 ], [ -122.380241450657905, 37.722072366642536 ], [ -122.380260115357089, 37.722058334421121 ], [ -122.38028050009946, 37.72204393110291 ], [ -122.380299156785668, 37.722029555363576 ], [ -122.38031780442914, 37.722014836126135 ], [ -122.380336443389169, 37.721999773934229 ], [ -122.380353345265689, 37.721984396102023 ], [ -122.380371975527709, 37.721968990678377 ], [ -122.380388868360967, 37.721953269620464 ], [ -122.380405753195106, 37.721937205323023 ], [ -122.38043950478324, 37.721904390279654 ], [ -122.380454643491987, 37.721887667118459 ], [ -122.38046977317164, 37.721870601009314 ], [ -122.380498331188051, 37.721837525213786 ], [ -122.38051707392718, 37.721826581710999 ], [ -122.380535826036024, 37.721815981420185 ], [ -122.380554603852772, 37.721806411084629 ], [ -122.380573407703011, 37.721797869875026 ], [ -122.380593957312428, 37.721789987796839 ], [ -122.380614524292966, 37.721782792441729 ], [ -122.380636845696628, 37.721776598619094 ], [ -122.380657456091583, 37.721771119386688 ], [ -122.380679829595536, 37.721766984912172 ], [ -122.380702211442937, 37.721763193939509 ], [ -122.380724628718468, 37.721760775855728 ], [ -122.380747063016599, 37.721759044225237 ], [ -122.380776445603402, 37.721758575115111 ], [ -122.380798931664657, 37.721758902836477 ], [ -122.380821453165254, 37.721760603721208 ], [ -122.380843991677978, 37.721762990510022 ], [ -122.380866547569951, 37.721766064021175 ], [ -122.380887409825149, 37.721770538303247 ], [ -122.380910017489256, 37.721775671167713 ], [ -122.380930914478753, 37.721781517796998 ], [ -122.380951846221805, 37.721788737601223 ], [ -122.380969304166271, 37.72179532613972 ], [ -122.381209663710422, 37.721932611167148 ], [ -122.381595340270607, 37.722141054219321 ], [ -122.381616306935143, 37.722149646808873 ], [ -122.381637264915469, 37.722157896168731 ], [ -122.381659933573118, 37.722165431470607 ], [ -122.381680856812778, 37.722172308193493 ], [ -122.381703500095611, 37.72217881352352 ], [ -122.381718196203238, 37.722182371993462 ], [ -122.381726116975372, 37.722184289451477 ], [ -122.381748725168919, 37.72218942214915 ], [ -122.381771315640577, 37.722193868396147 ], [ -122.38179561782411, 37.722197600566012 ], [ -122.381818182577149, 37.722201017120689 ], [ -122.381842449666692, 37.722203376657269 ], [ -122.381864970966248, 37.722205076798758 ], [ -122.381889211635382, 37.722206406378888 ], [ -122.38191343562309, 37.7222070497657 ], [ -122.381935913473328, 37.722207033764029 ], [ -122.381960093656375, 37.722205960742578 ], [ -122.381984265148091, 37.722204544490268 ], [ -122.382006690514388, 37.722202469399647 ], [ -122.382131855667623, 37.722160639374017 ], [ -122.382053374975868, 37.722202410560982 ], [ -122.382081246609474, 37.722210549386944 ], [ -122.382107407226997, 37.722219401998011 ], [ -122.382135313638244, 37.722228913714801 ], [ -122.38216150904843, 37.7222391397666 ], [ -122.382186002128265, 37.722250422556726 ], [ -122.38221223231281, 37.72226202122625 ], [ -122.382231488067504, 37.722271327773925 ], [ -122.382247234884744, 37.722278630470704 ], [ -122.382264666284939, 37.722284188875534 ], [ -122.382282081008995, 37.722289061364116 ], [ -122.382301180314499, 37.722292189559724 ], [ -122.382316796753315, 37.722294343584274 ], [ -122.382332404500787, 37.722296154380857 ], [ -122.382348055371921, 37.7222996815849 ], [ -122.382362004260912, 37.722304265802613 ], [ -122.382377707967407, 37.722309851797831 ], [ -122.382389988962331, 37.722316866217334 ], [ -122.382402296728699, 37.722324910302142 ], [ -122.382412893137086, 37.722333668461893 ], [ -122.38242351562603, 37.722343456298312 ], [ -122.382432426756893, 37.722353958210256 ], [ -122.382443110446403, 37.72236614862048 ], [ -122.38245541788794, 37.722374192979785 ], [ -122.382471103884711, 37.722379092516256 ], [ -122.382486702960776, 37.722380560066497 ], [ -122.382502223461259, 37.722378938861951 ], [ -122.382529930032874, 37.722380556015445 ], [ -122.382545607695334, 37.722385112585165 ], [ -122.382559626159036, 37.72239244258634 ], [ -122.382571959345043, 37.722401516341982 ], [ -122.382580879888579, 37.72241236145743 ], [ -122.382586386406132, 37.722424977955527 ], [ -122.382590173914764, 37.722437965564282 ], [ -122.382588775856519, 37.722451035493926 ], [ -122.382590834611065, 37.722464050455052 ], [ -122.382598043802759, 37.722475609647852 ], [ -122.382610377359867, 37.722484683668391 ], [ -122.382624352373682, 37.722490297257906 ], [ -122.382641931997384, 37.722501690715291 ], [ -122.38265777418269, 37.72251276857228 ], [ -122.382675345468627, 37.72252381879327 ], [ -122.382692907374491, 37.722534525796839 ], [ -122.382708732877717, 37.722544917184152 ], [ -122.382726277750848, 37.722554937725484 ], [ -122.382745551041126, 37.722564930914963 ], [ -122.382763086877006, 37.722574607955885 ], [ -122.382780614728389, 37.722583942306464 ], [ -122.382799870630677, 37.722593248486184 ], [ -122.382827994635349, 37.722611340684225 ], [ -122.382840310501109, 37.722619727959788 ], [ -122.382856023011854, 37.722625657391347 ], [ -122.382871674315936, 37.722629184245534 ], [ -122.382901057233255, 37.722628714605619 ], [ -122.382920165354051, 37.722632186201139 ], [ -122.382932481579374, 37.722640573461497 ], [ -122.382939690848261, 37.722652132907662 ], [ -122.382943451985241, 37.722664090283487 ], [ -122.382954144152635, 37.722676623878094 ], [ -122.382966546995732, 37.722688443398582 ], [ -122.382978924448281, 37.722699233229207 ], [ -122.382994731944478, 37.722708938137238 ], [ -122.383008785302238, 37.722717641262442 ], [ -122.383026278422236, 37.722725602123852 ], [ -122.383042008015664, 37.722732217987662 ], [ -122.383057685092282, 37.722736774500049 ], [ -122.38307338894937, 37.722742360676712 ], [ -122.38308910115893, 37.722748290082635 ], [ -122.383103093664104, 37.722754590615885 ], [ -122.383131113444662, 37.722768563756851 ], [ -122.383145140734484, 37.722776236913717 ], [ -122.383173212715235, 37.722792269674038 ], [ -122.383185537691887, 37.722801000133508 ], [ -122.383197871022062, 37.722810073823027 ], [ -122.383210213742984, 37.722819490725982 ], [ -122.383220827716869, 37.72282893553853 ], [ -122.383231459072476, 37.722839066252533 ], [ -122.383252652240699, 37.722856582695023 ], [ -122.383275469111652, 37.722869952249027 ], [ -122.383296549234032, 37.722883006473765 ], [ -122.383319349425548, 37.722895689831176 ], [ -122.383342123188228, 37.72290734351234 ], [ -122.383366616328686, 37.722918626335904 ], [ -122.383389364359843, 37.722929250325038 ], [ -122.383413814380745, 37.722938817278958 ], [ -122.383431263763498, 37.722945061956594 ], [ -122.383518693416903, 37.722983494694461 ], [ -122.38360968436416, 37.72302599052874 ], [ -122.383623746538206, 37.723035036256725 ], [ -122.383636106408431, 37.723045139846334 ], [ -122.383646772299514, 37.723056643431036 ], [ -122.383655727872323, 37.723068861366919 ], [ -122.383661243319395, 37.723081821039202 ], [ -122.383666776856799, 37.723095467151623 ], [ -122.383670581632771, 37.723109140906679 ], [ -122.383676097085413, 37.723122100578188 ], [ -122.383683315208287, 37.723134002929086 ], [ -122.383695684140434, 37.723144449457912 ], [ -122.383708017935533, 37.723153523087994 ], [ -122.383722027960033, 37.72316050972421 ], [ -122.383732835965688, 37.72316905039996 ], [ -122.383734379158653, 37.723170269802985 ], [ -122.38373469231658, 37.723182625929454 ], [ -122.383748702349067, 37.723189612562479 ], [ -122.383764362513205, 37.723193482523506 ], [ -122.383781689859717, 37.723194922268853 ], [ -122.383795343242184, 37.723187836915955 ], [ -122.383821435159561, 37.723193943333669 ], [ -122.383828644613416, 37.723205502449808 ], [ -122.383835888866727, 37.723218434468372 ], [ -122.383841404702011, 37.72323139412638 ], [ -122.383848657659414, 37.723244669369848 ], [ -122.383852462137753, 37.723258343398804 ], [ -122.383857987017038, 37.723271646001557 ], [ -122.383861791837163, 37.723285319750062 ], [ -122.383865605358181, 37.723299336724111 ], [ -122.383867681419986, 37.723313038392696 ], [ -122.38387185196801, 37.723341127615335 ], [ -122.383872226038946, 37.723355886046527 ], [ -122.383879757398802, 37.723380144510621 ], [ -122.383887280063988, 37.723404059748461 ], [ -122.383896426763826, 37.723423828636541 ], [ -122.383903627551987, 37.723435044521956 ], [ -122.383914233002187, 37.723444146045374 ], [ -122.383928243093862, 37.723451132382195 ], [ -122.383956011009261, 37.72345515232913 ], [ -122.383982041442252, 37.723458856142138 ], [ -122.384009791963194, 37.723462189624996 ], [ -122.384035805692207, 37.723465206963674 ], [ -122.384063530109799, 37.723467510482209 ], [ -122.384089526103082, 37.723469841637751 ], [ -122.384136280611983, 37.723472527510232 ], [ -122.384158819757118, 37.723474913938858 ], [ -122.384181376305577, 37.723477986814515 ], [ -122.384203958613327, 37.723482089368453 ], [ -122.384224830245174, 37.723486906009427 ], [ -122.38424745675556, 37.723492724672354 ], [ -122.384268380608177, 37.72349960093424 ], [ -122.384289321860564, 37.723507163369192 ], [ -122.384308551747594, 37.72351543990402 ], [ -122.384327808428154, 37.723524745827085 ], [ -122.384347090879899, 37.723535081704128 ], [ -122.384364653265465, 37.723545788456732 ], [ -122.384377022361861, 37.723556234913332 ], [ -122.38438594261595, 37.723567079901137 ], [ -122.384394846160177, 37.723577238425868 ], [ -122.384405469087795, 37.72358702608161 ], [ -122.384417785645866, 37.723595413459996 ], [ -122.384430084788605, 37.72360311383661 ], [ -122.384444086263514, 37.723609757160801 ], [ -122.384458052932075, 37.723615027580706 ], [ -122.384473782841141, 37.723621643252429 ], [ -122.38448780172817, 37.72362897302272 ], [ -122.384501846725257, 37.723637332468307 ], [ -122.384515900428013, 37.723646035137868 ], [ -122.384528251811574, 37.723655795132643 ], [ -122.384538892173168, 37.723666269227522 ], [ -122.384549541240418, 37.723677086547099 ], [ -122.38456021641889, 37.723688933542604 ], [ -122.384570943824258, 37.723702840165593 ], [ -122.384581645111282, 37.723715716561451 ], [ -122.384595785866907, 37.72372785147779 ], [ -122.384609917924237, 37.723739643166766 ], [ -122.384624023876995, 37.723750405177114 ], [ -122.384639831811498, 37.723760109862766 ], [ -122.384657351467936, 37.723769100432129 ], [ -122.384674853030802, 37.723777404558668 ], [ -122.384688837160766, 37.723783361676922 ], [ -122.384702838685783, 37.723790004695601 ], [ -122.384716875034059, 37.72379802088971 ], [ -122.384727472270853, 37.723806778833662 ], [ -122.38473981501231, 37.723816195854965 ], [ -122.384750446723075, 37.723826326704874 ], [ -122.384759367064078, 37.723837171663916 ], [ -122.38476655931882, 37.723848044270227 ], [ -122.384773760272395, 37.723859259827094 ], [ -122.38478439233964, 37.723869390668334 ], [ -122.384795006310796, 37.723878835068398 ], [ -122.384807314596031, 37.723886878899947 ], [ -122.384821316158124, 37.723893522179033 ], [ -122.384835282906707, 37.723898792553967 ], [ -122.384850942931138, 37.723902662375643 ], [ -122.384889238702442, 37.723912694229192 ], [ -122.384901537953482, 37.723920394830778 ], [ -122.38491387167187, 37.723929468064433 ], [ -122.384922801444858, 37.72394065622548 ], [ -122.384935378901247, 37.72395934004836 ], [ -122.384946228617792, 37.723978051514877 ], [ -122.384957095057587, 37.723997449442578 ], [ -122.384966251165253, 37.724017561465779 ], [ -122.384978611028657, 37.724027664643877 ], [ -122.384989269262093, 37.72403882514314 ], [ -122.384998216130384, 37.724050700029551 ], [ -122.385005451957767, 37.724063288474291 ], [ -122.385012696499402, 37.724076220418716 ], [ -122.385016466753711, 37.724088521225767 ], [ -122.38502538751068, 37.724099366158697 ], [ -122.385037712928536, 37.724108096697165 ], [ -122.385053408173405, 37.724113339388623 ], [ -122.385069016362493, 37.724115149547565 ], [ -122.385084528456687, 37.724113184503125 ], [ -122.385101700039968, 37.72410844598258 ], [ -122.385118888340344, 37.724104393921756 ], [ -122.385137831886752, 37.724101343865193 ], [ -122.385156818620402, 37.72410001021359 ], [ -122.385179288343707, 37.72409965064211 ], [ -122.385196616267564, 37.724101089899079 ], [ -122.385215698411002, 37.724103531450261 ], [ -122.385233087637133, 37.724107373824481 ], [ -122.385250510990446, 37.724112588834913 ], [ -122.385267961148685, 37.724118833233817 ], [ -122.385283708655592, 37.724126135522596 ], [ -122.385301210711177, 37.724134439275502 ], [ -122.385316914686456, 37.724140025157403 ], [ -122.38533260125142, 37.724144924586071 ], [ -122.385348261353599, 37.724148794341509 ], [ -122.385363904735826, 37.724151977632673 ], [ -122.385381250095534, 37.724154103588141 ], [ -122.385396840549205, 37.724155227532641 ], [ -122.38541414272278, 37.724155637349959 ], [ -122.385429689989081, 37.72415504515677 ], [ -122.385446939582977, 37.72415339589606 ], [ -122.385464163048326, 37.724150716681571 ], [ -122.385486461018303, 37.724147348627724 ], [ -122.385517561871112, 37.724142651083937 ], [ -122.385562344261174, 37.724135753744811 ], [ -122.385579620308761, 37.724135133861189 ], [ -122.385595245944231, 37.724137630950658 ], [ -122.38560923890941, 37.724143930635698 ], [ -122.385619835954955, 37.724152688504432 ], [ -122.38562873939226, 37.72416284694048 ], [ -122.385639388343563, 37.724173664166216 ], [ -122.38565002928155, 37.724184138154342 ], [ -122.385662389611269, 37.724194241254921 ], [ -122.385674732182835, 37.724203657908639 ], [ -122.385688794837932, 37.724212703663014 ], [ -122.385702840080384, 37.724221062964645 ], [ -122.385716876624841, 37.724229079313623 ], [ -122.385730887033873, 37.724236065435178 ], [ -122.385746617533329, 37.724242680930779 ], [ -122.385762330273252, 37.724248609978716 ], [ -122.385778025598896, 37.724253852573533 ], [ -122.385798932390358, 37.72426004211173 ], [ -122.385816364866457, 37.72426559998376 ], [ -122.385832086325422, 37.724271872247911 ], [ -122.38584781649574, 37.724278487735482 ], [ -122.38586357244958, 37.724286132903025 ], [ -122.385877617725541, 37.724294492183716 ], [ -122.38589340051098, 37.724303167287317 ], [ -122.385905743122947, 37.724312583642103 ], [ -122.385919823251839, 37.72432231609433 ], [ -122.385932192689026, 37.724332762111779 ], [ -122.38594985163823, 37.724347244101757 ], [ -122.385963914360929, 37.724356290097674 ], [ -122.385979679391255, 37.72436427819499 ], [ -122.385995409594287, 37.724370893662766 ], [ -122.386012824706015, 37.72437576560376 ], [ -122.386028493940515, 37.724379978214039 ], [ -122.38604418931267, 37.724385220773271 ], [ -122.386058173659734, 37.724391177453832 ], [ -122.386072175428609, 37.724397820583704 ], [ -122.386086194619651, 37.724405150162887 ], [ -122.386098511495945, 37.724413537089887 ], [ -122.386110837084971, 37.724422267241074 ], [ -122.386121460359277, 37.724432054740767 ], [ -122.386155140875701, 37.724396492269392 ], [ -122.386142558844625, 37.724445794903964 ], [ -122.386158314864247, 37.724453440031894 ], [ -122.386174045447717, 37.724460055470189 ], [ -122.386191469295412, 37.724465270335628 ], [ -122.386208867014332, 37.724469455522041 ], [ -122.386227957996084, 37.724472240134631 ], [ -122.386245295090333, 37.724474022731769 ], [ -122.38626432544568, 37.724474404754595 ], [ -122.386288541146868, 37.724474703747106 ], [ -122.386305877897456, 37.724476486340883 ], [ -122.386321468785383, 37.724477610432025 ], [ -122.386338779051741, 37.724478363075484 ], [ -122.386354352857126, 37.724478800431207 ], [ -122.386371637343302, 37.724478523662484 ], [ -122.386387184323809, 37.724477931348545 ], [ -122.38640445173273, 37.72447696811853 ], [ -122.386419972931165, 37.7244753463929 ], [ -122.386437213858997, 37.724473353487241 ], [ -122.386452717618937, 37.724471044757188 ], [ -122.386469932064344, 37.724468022175898 ], [ -122.386485410039811, 37.724464684034231 ], [ -122.386500878956426, 37.724461002670601 ], [ -122.38651633915967, 37.724456978079473 ], [ -122.386531781598762, 37.724452267315506 ], [ -122.386547242137226, 37.724448242440204 ], [ -122.386564474343317, 37.72444590629047 ], [ -122.386579960337627, 37.724442911647458 ], [ -122.386595429583735, 37.724439229991283 ], [ -122.386610881065096, 37.724434862162283 ], [ -122.386626315119955, 37.724429807880355 ], [ -122.38664173140225, 37.724424067151048 ], [ -122.386657130948578, 37.724417639957707 ], [ -122.386670792994096, 37.724410897498451 ], [ -122.386684437604913, 37.724403468312111 ], [ -122.386698064780759, 37.724395352398709 ], [ -122.386709954455839, 37.724386921220365 ], [ -122.386721827401047, 37.724377803853415 ], [ -122.386733682558045, 37.724367999490731 ], [ -122.386743808927534, 37.724358223089439 ], [ -122.386753901134114, 37.724347073774396 ], [ -122.386760492536993, 37.724334263702232 ], [ -122.386768838521292, 37.724322455623209 ], [ -122.386780684957245, 37.724312308304832 ], [ -122.386796092835809, 37.724306224324238 ], [ -122.386814983740024, 37.72430111465097 ], [ -122.386833900781468, 37.724297034651016 ], [ -122.38685286104193, 37.724294670780772 ], [ -122.386873576231366, 37.724293308893905 ], [ -122.386890817450478, 37.724291315916268 ], [ -122.38690624274291, 37.724285918372097 ], [ -122.386918141436212, 37.724277830392509 ], [ -122.386928250341739, 37.724267367524348 ], [ -122.386934850421213, 37.724254900667695 ], [ -122.386936248094955, 37.724241830684782 ], [ -122.386934179470913, 37.724228472583448 ], [ -122.386932102818292, 37.724214770970946 ], [ -122.386933491079674, 37.72420135749902 ], [ -122.386933168672044, 37.724188658157658 ], [ -122.386929398441154, 37.724176357406598 ], [ -122.386923908138428, 37.72416442757148 ], [ -122.386916706823882, 37.724153211871986 ], [ -122.386907811932304, 37.724143397033281 ], [ -122.38689548628895, 37.724134666690496 ], [ -122.386883203877176, 37.724127652753772 ], [ -122.386867500139488, 37.724122067349683 ], [ -122.386837811685524, 37.724110524813227 ], [ -122.386792314266742, 37.724089278160726 ], [ -122.386769557372673, 37.724078311594091 ], [ -122.386748519865691, 37.724066974120603 ], [ -122.386725745213752, 37.72405532110006 ], [ -122.386704690294678, 37.724043297167768 ], [ -122.386683626669864, 37.724030930006215 ], [ -122.386641482023506, 37.72400550949537 ], [ -122.386620400988434, 37.723992455596928 ], [ -122.386599311255594, 37.723979058743758 ], [ -122.386579941254908, 37.723965290981276 ], [ -122.386544701706455, 37.723939416215046 ], [ -122.386297403385385, 37.72380190986933 ], [ -122.386051808287206, 37.723663345927129 ], [ -122.386034340737112, 37.723656414643614 ], [ -122.385999441189313, 37.723643925509059 ], [ -122.385964559071653, 37.723632122815218 ], [ -122.385936677777778, 37.723623641958845 ], [ -122.385920974203756, 37.723618055878958 ], [ -122.385906946468538, 37.723610383053618 ], [ -122.385894611983545, 37.723601309659934 ], [ -122.385883963077092, 37.723590492456275 ], [ -122.385875024838413, 37.723578961136361 ], [ -122.38586953474902, 37.723567031250973 ], [ -122.385860622648437, 37.723556529881158 ], [ -122.385846603632743, 37.723549199999539 ], [ -122.385827460305421, 37.723544355976919 ], [ -122.385811791258462, 37.723540143339349 ], [ -122.385796095735444, 37.723534900753975 ], [ -122.385780392190739, 37.723529314655281 ], [ -122.385764662184897, 37.723522699157897 ], [ -122.385750651900182, 37.723515712764971 ], [ -122.385738361343229, 37.723508355751683 ], [ -122.385731151556087, 37.723496796478393 ], [ -122.385722248191556, 37.723486638323564 ], [ -122.385708220501641, 37.723478965474406 ], [ -122.385692534045461, 37.723474065820646 ], [ -122.385680182539247, 37.72346430594763 ], [ -122.385666111318983, 37.723454916965551 ], [ -122.385652048810016, 37.723445871207367 ], [ -122.385637977596772, 37.723436482221878 ], [ -122.385609870011095, 37.723419077148201 ], [ -122.38559409650658, 37.723410745500026 ], [ -122.385580051429457, 37.723402386184311 ], [ -122.385548521852172, 37.723386409328988 ], [ -122.385534494199106, 37.723378736459033 ], [ -122.385510008939335, 37.723367797318083 ], [ -122.385494374422848, 37.723364957271542 ], [ -122.385459753720795, 37.72336345119556 ], [ -122.385444119898509, 37.723360611131262 ], [ -122.385430153207864, 37.723355340828022 ], [ -122.385416142986116, 37.723348354395121 ], [ -122.385403808604508, 37.723339280949688 ], [ -122.385391492330285, 37.723330893943086 ], [ -122.385375701117368, 37.723321876094445 ], [ -122.385359927998408, 37.723313544134655 ], [ -122.385342434828871, 37.723305583340405 ], [ -122.385326695857486, 37.723298624564244 ], [ -122.385309246571239, 37.723292379887567 ], [ -122.385291822714692, 37.723287164896192 ], [ -122.385263732678311, 37.723270446190739 ], [ -122.38524797700147, 37.723262800941718 ], [ -122.385232229687915, 37.723255498921759 ], [ -122.385216491083142, 37.723248540125276 ], [ -122.385185048704614, 37.723235995428482 ], [ -122.385169353635689, 37.723230752753828 ], [ -122.385151929803385, 37.723225537741719 ], [ -122.385134524075497, 37.723221009167226 ], [ -122.385117126370531, 37.723216824101435 ], [ -122.385101466469777, 37.723212954040122 ], [ -122.385082366457212, 37.723209826029851 ], [ -122.385064995564079, 37.723206670622204 ], [ -122.385047580442674, 37.723201798545674 ], [ -122.38502841148815, 37.723195924710531 ], [ -122.385010953200691, 37.723189336769465 ], [ -122.384993460097732, 37.72318137592324 ], [ -122.384977677662462, 37.723172700972079 ], [ -122.384946044204682, 37.723152605241658 ], [ -122.384924989829145, 37.723140580989593 ], [ -122.384905663878186, 37.723128529078224 ], [ -122.384884618220184, 37.723116848044533 ], [ -122.384842544338781, 37.723094172691695 ], [ -122.384800505291054, 37.723072869951821 ], [ -122.384779494472909, 37.7230625615273 ], [ -122.384742788674473, 37.723047010916545 ], [ -122.384730489577436, 37.723039310022727 ], [ -122.384716444674581, 37.723030950878183 ], [ -122.384704119127363, 37.723022220310114 ], [ -122.38469349658682, 37.723012432674857 ], [ -122.384682864654536, 37.723002301824017 ], [ -122.38467394442489, 37.722991456857699 ], [ -122.384663217109889, 37.722977550517093 ], [ -122.384654296194697, 37.722966705560324 ], [ -122.384643682371646, 37.722957261146163 ], [ -122.384633093968816, 37.722948846419143 ], [ -122.384619101304366, 37.722942546342381 ], [ -122.384596345081988, 37.722931579353954 ], [ -122.384578721543591, 37.722918470060556 ], [ -122.384546896507018, 37.722890823537078 ], [ -122.384530966582233, 37.722876313408833 ], [ -122.384518537006088, 37.722863464381859 ], [ -122.38443411130757, 37.722807129900666 ], [ -122.384347983418735, 37.722751852133769 ], [ -122.384239081476991, 37.722684921079754 ], [ -122.384207605050619, 37.722671003487143 ], [ -122.384176119229906, 37.722656742122247 ], [ -122.384144625418941, 37.722642137786686 ], [ -122.384113113527988, 37.722626847002253 ], [ -122.384083322047019, 37.722611185329136 ], [ -122.384051793126986, 37.722595208071212 ], [ -122.384021983580084, 37.722578859942075 ], [ -122.38399213993786, 37.722561138891301 ], [ -122.383974473094511, 37.722546313377947 ], [ -122.383955077860932, 37.722531515778066 ], [ -122.383937428078781, 37.722517376441559 ], [ -122.383918050598027, 37.722503265006914 ], [ -122.383884593268888, 37.722479421152507 ], [ -122.383868863653973, 37.722472805400912 ], [ -122.383854862444593, 37.722466162007116 ], [ -122.383839115090979, 37.722458859805528 ], [ -122.383825087796367, 37.722451187005468 ], [ -122.383809323735065, 37.722443198062706 ], [ -122.383795270342418, 37.722434495307176 ], [ -122.383782945009273, 37.722425764916487 ], [ -122.383768882931093, 37.722416719206592 ], [ -122.383744181117109, 37.722397198772512 ], [ -122.383731812655114, 37.72238675251657 ], [ -122.38372613997241, 37.722367614518575 ], [ -122.383705303481563, 37.722364170691534 ], [ -122.383691206623396, 37.722353751794557 ], [ -122.383675390420279, 37.722343704028198 ], [ -122.383659608655421, 37.72233502861922 ], [ -122.383642115552547, 37.722327067576785 ], [ -122.3836246492464, 37.722320136472625 ], [ -122.383605471580864, 37.722313918910281 ], [ -122.383586328372033, 37.722309074528077 ], [ -122.383570607863348, 37.722302801956744 ], [ -122.383553080331694, 37.722293467992337 ], [ -122.383537272159586, 37.722283763169607 ], [ -122.383521447287507, 37.722273371882082 ], [ -122.383507350472314, 37.722262952962978 ], [ -122.383491507867944, 37.722251875225332 ], [ -122.383477384968415, 37.722240426625198 ], [ -122.38346325337578, 37.72222863479751 ], [ -122.383445508101758, 37.722210720451898 ], [ -122.383434894533281, 37.722201275653106 ], [ -122.383422587021329, 37.722193231670488 ], [ -122.383410305264974, 37.722186217644108 ], [ -122.38339632153793, 37.722180260372568 ], [ -122.383380653254989, 37.722176047130667 ], [ -122.383364967234144, 37.722171147440534 ], [ -122.383350966126613, 37.722164503986562 ], [ -122.38333693893793, 37.722156831128018 ], [ -122.383322885647786, 37.722148128041155 ], [ -122.383310552066504, 37.722139054369293 ], [ -122.383298209445869, 37.722129637475824 ], [ -122.383282367246764, 37.722118559704285 ], [ -122.38326655080327, 37.722108511888187 ], [ -122.383249023002975, 37.722099177609202 ], [ -122.383231530690935, 37.722091216494434 ], [ -122.383212327035437, 37.722083969464926 ], [ -122.383193157822831, 37.722078095341168 ], [ -122.383172277957286, 37.72207293529052 ], [ -122.383156635797775, 37.722069751696246 ], [ -122.383140949814091, 37.722064851976235 ], [ -122.383123517701506, 37.722059293437333 ], [ -122.383107814683271, 37.722053707530257 ], [ -122.383076390550301, 37.722041848445443 ], [ -122.383058941409402, 37.722035603714303 ], [ -122.383027482510172, 37.72202237171301 ], [ -122.383013472765484, 37.722015384992908 ], [ -122.382981979115939, 37.722000780625166 ], [ -122.382967960677476, 37.721993450399175 ], [ -122.382952196466633, 37.721985461621244 ], [ -122.382938160650923, 37.72197744521457 ], [ -122.382922396446673, 37.721969456432632 ], [ -122.382897807697603, 37.721954398035997 ], [ -122.382881913081334, 37.721941260860625 ], [ -122.382867729471926, 37.721927409605762 ], [ -122.38285353717319, 37.721913215123287 ], [ -122.382839327490771, 37.72189833418733 ], [ -122.38282855685857, 37.721882711553377 ], [ -122.382816049838553, 37.721866773306338 ], [ -122.382806990215869, 37.721850436593726 ], [ -122.382797922594165, 37.721833756643434 ], [ -122.382790609101846, 37.721818078751127 ], [ -122.382783313008133, 37.721803087584632 ], [ -122.382772577517116, 37.721788837568752 ], [ -122.382763570078581, 37.721774560207898 ], [ -122.382752843642237, 37.721760653685102 ], [ -122.382740405865931, 37.721747461242323 ], [ -122.382731494418593, 37.721736959358161 ], [ -122.382724293971776, 37.721725743398046 ], [ -122.382720523870901, 37.721713442518464 ], [ -122.382720202193767, 37.721700743160483 ], [ -122.382723346326571, 37.72168833177593 ], [ -122.382726481072993, 37.721675577176406 ], [ -122.382729581733628, 37.721661449662143 ], [ -122.38273230785866, 37.721632563445375 ], [ -122.382731603643052, 37.721604762147862 ], [ -122.382733369644583, 37.721586614972047 ], [ -122.382734347844632, 37.721576562371538 ], [ -122.38274367451254, 37.721535209801523 ], [ -122.382746792545248, 37.72152176846371 ], [ -122.382751629924911, 37.721507956556103 ], [ -122.382756484345322, 37.721494831105609 ], [ -122.382763041407543, 37.721480648347587 ], [ -122.382782164842624, 37.721416476839273 ], [ -122.382798370882199, 37.721373640576992 ], [ -122.382806482416456, 37.721352565673556 ], [ -122.382814602294815, 37.721331834000935 ], [ -122.382824442541889, 37.721310731465771 ], [ -122.382834299480933, 37.721290315392572 ], [ -122.382844148411394, 37.721269556081531 ], [ -122.382854005339539, 37.721249140006542 ], [ -122.382865600022583, 37.721229039519947 ], [ -122.382877185659027, 37.721208595811795 ], [ -122.382900408735608, 37.721169767752777 ], [ -122.38290693965736, 37.721154555308274 ], [ -122.382913479271252, 37.721139686089295 ], [ -122.382921747600108, 37.721124789237969 ], [ -122.382931761341311, 37.72111055121686 ], [ -122.382941783773603, 37.721096656420762 ], [ -122.382951814897126, 37.721083104849697 ], [ -122.382963583082613, 37.721069868876654 ], [ -122.382977097024437, 37.721057291727 ], [ -122.382988899981882, 37.721045428655017 ], [ -122.383004168370334, 37.721033853552548 ], [ -122.383019462839854, 37.721023308125787 ], [ -122.383034765654699, 37.721013105928478 ], [ -122.383050086547513, 37.721003590169914 ], [ -122.383070574806197, 37.7209933050774 ], [ -122.383087649800032, 37.720984791368565 ], [ -122.383102978684576, 37.720975618840036 ], [ -122.383116562158349, 37.720965787756029 ], [ -122.383131839552021, 37.72095455585724 ], [ -122.383143650811689, 37.7209430357264 ], [ -122.383155445374214, 37.720930829406058 ], [ -122.383167222202275, 37.720917936912819 ], [ -122.383198904587886, 37.720871762618749 ], [ -122.38322729544646, 37.720832165418933 ], [ -122.383257414279257, 37.720792540037081 ], [ -122.383305971799075, 37.720729958050264 ], [ -122.38333258199772, 37.720688328826199 ], [ -122.383344140985599, 37.72066685539815 ], [ -122.383355691961356, 37.720645038731924 ], [ -122.383365505527735, 37.720622906477054 ], [ -122.383373590728709, 37.720600801854324 ], [ -122.383381667228008, 37.720578354005049 ], [ -122.383389734679795, 37.720555562934777 ], [ -122.383394337405079, 37.720532483893628 ], [ -122.383400668139274, 37.720509377224332 ], [ -122.383403542162711, 37.720486326096186 ], [ -122.383407970241009, 37.720456382272516 ], [ -122.38340677875901, 37.720409360597294 ], [ -122.383402539659841, 37.720378525251974 ], [ -122.383398682545547, 37.720362792132271 ], [ -122.383398428098104, 37.720361252706262 ], [ -122.383396641447277, 37.720350463627156 ], [ -122.383387721377815, 37.72033961856436 ], [ -122.383358200220172, 37.720334596711098 ], [ -122.383344547327056, 37.720341682291192 ], [ -122.38333615741675, 37.720351774011149 ], [ -122.383322539304771, 37.720360232492368 ], [ -122.383310275938157, 37.720353904619685 ], [ -122.383304777801996, 37.720341631382666 ], [ -122.38331139553371, 37.720329851172949 ], [ -122.383307625413451, 37.720317550583985 ], [ -122.383293624653192, 37.720310906846613 ], [ -122.383277974162851, 37.720307380041632 ], [ -122.383265710124405, 37.720301052449905 ], [ -122.383253377195246, 37.720291978489243 ], [ -122.38324619420213, 37.720281449010265 ], [ -122.383240704778117, 37.720269518996041 ], [ -122.38324039171458, 37.720257162862467 ], [ -122.383243535008134, 37.72024475147218 ], [ -122.383250135348419, 37.720232284813868 ], [ -122.383254981599535, 37.720218816377724 ], [ -122.383258090442638, 37.720205031803125 ], [ -122.38325945354886, 37.720190588682506 ], [ -122.383259097001115, 37.720176516419151 ], [ -122.383255283408985, 37.720162499423914 ], [ -122.383249759553038, 37.720149196500621 ], [ -122.3832425157008, 37.720136264439603 ], [ -122.383233560549201, 37.720124046466523 ], [ -122.38322290383195, 37.720112885790329 ], [ -122.383210553208443, 37.720103125653154 ], [ -122.383196395976469, 37.720090304111586 ], [ -122.383073889931538, 37.72003217363779 ], [ -122.382849809825458, 37.71992313205017 ], [ -122.382825447811499, 37.719916997234385 ], [ -122.38279933973088, 37.719910203866348 ], [ -122.382774952002876, 37.719903039905788 ], [ -122.382750537499561, 37.719894845998688 ], [ -122.382727834641599, 37.719885937725351 ], [ -122.382703394420673, 37.719876714399525 ], [ -122.382680656108263, 37.719866433224148 ], [ -122.382657909806966, 37.719855809081899 ], [ -122.382635145779759, 37.719844498488762 ], [ -122.382614092714974, 37.719832473817299 ], [ -122.382593030963804, 37.71982010591605 ], [ -122.382571951833512, 37.71980705155903 ], [ -122.382552583665785, 37.719793283125171 ], [ -122.382533206466661, 37.719779171467643 ], [ -122.382517347496844, 37.719767407137013 ], [ -122.382329598519163, 37.719659157154716 ], [ -122.382140156862491, 37.71955230738498 ], [ -122.382084189621324, 37.71952710586983 ], [ -122.382026520507765, 37.719502961888537 ], [ -122.382009045965773, 37.719495686772042 ], [ -122.381991562743806, 37.719488068701423 ], [ -122.381975799169183, 37.719480079789612 ], [ -122.381960018217541, 37.719471404423579 ], [ -122.381944228233777, 37.719462385834824 ], [ -122.381928430255144, 37.719453024006853 ], [ -122.381912623244801, 37.719443318956195 ], [ -122.381898535881675, 37.719433243065936 ], [ -122.381884431142637, 37.719422480721789 ], [ -122.381872072119776, 37.719412377217125 ], [ -122.381859773582747, 37.719404676299234 ], [ -122.381847484429258, 37.719397318595092 ], [ -122.381833501358983, 37.719391361409976 ], [ -122.381802095947549, 37.719380188981518 ], [ -122.381786358477399, 37.719373229447925 ], [ -122.381770594949188, 37.719365240508537 ], [ -122.381756533687849, 37.719356194279435 ], [ -122.38174418338258, 37.719346433987333 ], [ -122.381731807013125, 37.719335644015629 ], [ -122.381721141599613, 37.719324139981623 ], [ -122.381712186795909, 37.719311921891311 ], [ -122.381704943292732, 37.719298989734028 ], [ -122.381695910985357, 37.719283682596497 ], [ -122.381688693554523, 37.719271780116451 ], [ -122.381681484814919, 37.719260220862054 ], [ -122.381663628063038, 37.719237844020014 ], [ -122.381645788699373, 37.719216153627443 ], [ -122.381624527445553, 37.719195891351333 ], [ -122.381612168150028, 37.71918578782455 ], [ -122.381601529187748, 37.719175313452254 ], [ -122.381592592151165, 37.719163782073863 ], [ -122.381583646069373, 37.719151906924999 ], [ -122.381576411292528, 37.719139317985587 ], [ -122.381570896501387, 37.71912635820749 ], [ -122.381567092315478, 37.719112684375858 ], [ -122.381559735915829, 37.719095290544253 ], [ -122.38155422912088, 37.719082673727875 ], [ -122.381548731721296, 37.719070400675434 ], [ -122.381541505639575, 37.719058154685285 ], [ -122.38153600789127, 37.719045881363151 ], [ -122.38150713837662, 37.718998271399208 ], [ -122.381498201369013, 37.718986739738739 ], [ -122.381490992686665, 37.71897518047215 ], [ -122.381473118684809, 37.718952117147957 ], [ -122.381464190375866, 37.718940928710964 ], [ -122.381455270757456, 37.718930083499401 ], [ -122.381446342115154, 37.718918895341112 ], [ -122.38143742284764, 37.718908050122621 ], [ -122.381416144356564, 37.718887101081044 ], [ -122.381407233439191, 37.718876599091892 ], [ -122.381385972354465, 37.718856337046212 ], [ -122.381373613486502, 37.71884623321376 ], [ -122.381352369787336, 37.718826657339626 ], [ -122.381327686843449, 37.718807823394606 ], [ -122.38131535405293, 37.718798749234402 ], [ -122.38129421463421, 37.718783292338109 ], [ -122.381275038076012, 37.718777074392875 ], [ -122.381252413579233, 37.718771255149179 ], [ -122.381231552150709, 37.71876678120416 ], [ -122.381208979780382, 37.718763021309094 ], [ -122.381188161789552, 37.718760263487212 ], [ -122.381165632510005, 37.718758219720186 ], [ -122.381143129982078, 37.718757205616299 ], [ -122.381106826627473, 37.718757441991308 ], [ -122.381084384557454, 37.718758830464949 ], [ -122.380998020790415, 37.718762269763332 ], [ -122.380954864960344, 37.718765019067327 ], [ -122.380911726497644, 37.718768454807901 ], [ -122.380818588404082, 37.718777496255818 ], [ -122.380801331270163, 37.71877880161415 ], [ -122.380782345824386, 37.718780134839598 ], [ -122.380765070988232, 37.718780754294905 ], [ -122.380746051141955, 37.718780714329604 ], [ -122.380728750590777, 37.7187803038209 ], [ -122.380709712691541, 37.718779577682966 ], [ -122.380692386433651, 37.718778137484804 ], [ -122.380673314144289, 37.718776038430633 ], [ -122.380655961489964, 37.718773568553999 ], [ -122.380638600152778, 37.718770755448638 ], [ -122.380621221455968, 37.718767256162863 ], [ -122.380603834063052, 37.71876341309914 ], [ -122.380569024561694, 37.718754354608322 ], [ -122.380515107712881, 37.718741824128088 ], [ -122.380459506326716, 37.71873103733904 ], [ -122.380442119303254, 37.718727194520504 ], [ -122.380426442886844, 37.718722637662651 ], [ -122.380409012106568, 37.718717078713624 ], [ -122.380393292279919, 37.718710805720228 ], [ -122.380374046360402, 37.718701842093019 ], [ -122.380356511396428, 37.718692164421441 ], [ -122.38034421348118, 37.718684463338931 ], [ -122.380331889520718, 37.718675732576422 ], [ -122.380321267831235, 37.718665944545648 ], [ -122.380312339384403, 37.718654756026375 ], [ -122.380301657268987, 37.718642565404615 ], [ -122.380291009195389, 37.71863174769787 ], [ -122.380280353133841, 37.718620586752877 ], [ -122.380271407679629, 37.71860871177239 ], [ -122.380262453207322, 37.71859649384507 ], [ -122.380255227383529, 37.718584247775084 ], [ -122.380248018933486, 37.718572688431699 ], [ -122.380240793121359, 37.718560442635365 ], [ -122.380231848026369, 37.718548567646252 ], [ -122.38022120032204, 37.718537749927506 ], [ -122.380208832990292, 37.718527303020664 ], [ -122.380196491707878, 37.718517885791279 ], [ -122.380182448162131, 37.718509525825596 ], [ -122.380168421983186, 37.718501852310723 ], [ -122.380152693540609, 37.718495236058757 ], [ -122.380136999827513, 37.718489992709728 ], [ -122.380119595168225, 37.718485463396448 ], [ -122.380103953548627, 37.718482279400568 ], [ -122.380086617999027, 37.718480495897992 ], [ -122.380053754382303, 37.718479990320766 ], [ -122.380034708231022, 37.718478920841939 ], [ -122.380017337959387, 37.718475764424113 ], [ -122.37999994233742, 37.718471578313881 ], [ -122.379982511302273, 37.718466019307044 ], [ -122.379966765867707, 37.718458716572137 ], [ -122.379950994048144, 37.718450384161848 ], [ -122.379936915819798, 37.718440651261687 ], [ -122.379924539863453, 37.718429861098564 ], [ -122.379912120161947, 37.71841735480826 ], [ -122.379903227227103, 37.718407539426167 ], [ -122.379890903011727, 37.718398808348084 ], [ -122.379878596858461, 37.718390763984829 ], [ -122.379866307723006, 37.718383406078345 ], [ -122.379852316321873, 37.718377105430825 ], [ -122.379838350963936, 37.718371834460527 ], [ -122.379822683339711, 37.718367620748374 ], [ -122.379808770066504, 37.718364409132271 ], [ -122.379793145500187, 37.71836191155316 ], [ -122.379777565026231, 37.718361130092418 ], [ -122.379746464146947, 37.718361969759833 ], [ -122.379729146346136, 37.71836087292629 ], [ -122.379715198352471, 37.718356288119374 ], [ -122.379701189609946, 37.718349301001297 ], [ -122.379690576729757, 37.718339856139472 ], [ -122.379683377077711, 37.718328640261959 ], [ -122.3796778796832, 37.718316366577113 ], [ -122.37967239098387, 37.718304436667452 ], [ -122.37966348037574, 37.718293934270854 ], [ -122.379652884528355, 37.718285176138238 ], [ -122.379638884477515, 37.718278531964394 ], [ -122.379623199871318, 37.718273631767524 ], [ -122.379609182480792, 37.718266301686668 ], [ -122.379598560938021, 37.718256513315531 ], [ -122.379589606611773, 37.718244295061801 ], [ -122.379568259413119, 37.718220600422896 ], [ -122.379555875206677, 37.718209466714256 ], [ -122.379543499344152, 37.718198676510717 ], [ -122.3795311408429, 37.718188572758478 ], [ -122.379517062719074, 37.718178839807734 ], [ -122.379503001963329, 37.71816979358254 ], [ -122.379487230257041, 37.718161461109773 ], [ -122.379473169494304, 37.718152414331819 ], [ -122.379462538975446, 37.718142283276592 ], [ -122.3794518917869, 37.718131465482102 ], [ -122.37944294651696, 37.718119590163212 ], [ -122.379435729559873, 37.718107687542933 ], [ -122.379424700187641, 37.718081767792796 ], [ -122.379422659465391, 37.718069439219867 ], [ -122.379417153461716, 37.718056822570574 ], [ -122.379409936521796, 37.718044920223178 ], [ -122.37940101731057, 37.718034074854529 ], [ -122.379390386804573, 37.718023943243416 ], [ -122.379378072089409, 37.71801555560107 ], [ -122.37936745929791, 37.718006110709609 ], [ -122.379346164299292, 37.71798447511302 ], [ -122.379333796843824, 37.717974028393506 ], [ -122.379323167053869, 37.717963897039802 ], [ -122.379310817292421, 37.717954136215845 ], [ -122.379298475880205, 37.717944719171612 ], [ -122.379286143826207, 37.717935644792249 ], [ -122.379272083137607, 37.717926598264555 ], [ -122.379261435662016, 37.717915780457993 ], [ -122.379250796867041, 37.717905305876819 ], [ -122.379240166752524, 37.717895174521026 ], [ -122.379229553995728, 37.71788572961696 ], [ -122.379217221621232, 37.717876655510366 ], [ -122.379203178306753, 37.717868295427088 ], [ -122.379190871970053, 37.717860250996829 ], [ -122.379175109041569, 37.717852261708209 ], [ -122.379164469922188, 37.717841787124641 ], [ -122.379152137908591, 37.717832713005684 ], [ -122.379138137994644, 37.717826069046431 ], [ -122.379125875056587, 37.717819740741156 ], [ -122.379111857793731, 37.717812410326019 ], [ -122.379101270749828, 37.717803995094904 ], [ -122.379090667046114, 37.717794893399073 ], [ -122.379078291320113, 37.717784103145959 ], [ -122.379062641223001, 37.717780576059667 ], [ -122.379047051849412, 37.717779451006763 ], [ -122.379033086660868, 37.717774179940491 ], [ -122.379022465271206, 37.717764391791221 ], [ -122.379016976365222, 37.717752461580901 ], [ -122.379009785541115, 37.717741588612981 ], [ -122.378990609802273, 37.71773537083854 ], [ -122.378971451059471, 37.717729838970079 ], [ -122.378953994246785, 37.717723250131236 ], [ -122.378936512100381, 37.717715631599603 ], [ -122.37891900323838, 37.7177069833972 ], [ -122.378903197337848, 37.717697277934541 ], [ -122.37888387343952, 37.717685225304997 ], [ -122.378864602279094, 37.717675231470452 ], [ -122.378847076429409, 37.717665896799019 ], [ -122.378827830621148, 37.717656932923234 ], [ -122.378808602859749, 37.717648655486073 ], [ -122.378778909235265, 37.717636768235245 ], [ -122.378761435795425, 37.717629492629342 ], [ -122.378726453548339, 37.717613569064291 ], [ -122.378708953409784, 37.717605264056957 ], [ -122.378693173583002, 37.717596588245186 ], [ -122.378675665123012, 37.717587940000953 ], [ -122.378659867262996, 37.717578577742898 ], [ -122.37864235048211, 37.71756958626176 ], [ -122.378594931951909, 37.717540469771926 ], [ -122.378579117109084, 37.717530421044614 ], [ -122.3785509613637, 37.717510954911866 ], [ -122.378491401169214, 37.717480315188894 ], [ -122.37843182436778, 37.717448988971746 ], [ -122.37837222957846, 37.717416976282458 ], [ -122.378314346472195, 37.717384249540096 ], [ -122.378221298936523, 37.717328391303198 ], [ -122.378163571723618, 37.717301842568197 ], [ -122.378104150956304, 37.717276694266459 ], [ -122.37804646718476, 37.717251861606229 ], [ -122.377987080837755, 37.717228086156695 ], [ -122.377882351983473, 37.717188895399786 ], [ -122.377451299846371, 37.716962278497022 ], [ -122.377409136350764, 37.716935824700762 ], [ -122.377366990222555, 37.71691005734241 ], [ -122.37732312451665, 37.716884660739723 ], [ -122.37727928449992, 37.716860293805809 ], [ -122.377192593728324, 37.716850687700834 ], [ -122.377174053804566, 37.716801195276375 ], [ -122.377135442741164, 37.716778462058606 ], [ -122.377117882304205, 37.716767754227781 ], [ -122.377098594291084, 37.716757073924335 ], [ -122.377077577665531, 37.716746421163919 ], [ -122.377058306652586, 37.716736427312505 ], [ -122.377035544432545, 37.716725115631768 ], [ -122.377004002182673, 37.716708449744992 ], [ -122.376974170891216, 37.716691070132001 ], [ -122.376942602330686, 37.716673374829035 ], [ -122.376912754080166, 37.716655308741679 ], [ -122.376882896832043, 37.716636899425616 ], [ -122.376853030939387, 37.716618147149923 ], [ -122.376824875984553, 37.716598680327252 ], [ -122.376794983771219, 37.716578898087818 ], [ -122.376766811521605, 37.716558745072504 ], [ -122.376740359241694, 37.716538221557123 ], [ -122.376712169684112, 37.71651738180077 ], [ -122.376685699750382, 37.716496171550673 ], [ -122.376645065755298, 37.716461795731668 ], [ -122.376629260077664, 37.716452090242093 ], [ -122.376613463055463, 37.71644272742784 ], [ -122.376597691693803, 37.716434394571444 ], [ -122.376583692278402, 37.716427750581751 ], [ -122.376567912942647, 37.716419074209107 ], [ -122.376553835548819, 37.716409341176096 ], [ -122.376541451404279, 37.716398207158385 ], [ -122.376530778896253, 37.716386359416987 ], [ -122.376520053711957, 37.71637245232575 ], [ -122.376509415869748, 37.716361977488937 ], [ -122.376498794675413, 37.716352189390022 ], [ -122.37648645456278, 37.716342771488634 ], [ -122.376474122785964, 37.71633369736724 ], [ -122.37646180833417, 37.716325309423283 ], [ -122.376449502196934, 37.716317264435602 ], [ -122.376435485481252, 37.716309933700259 ], [ -122.376365488584341, 37.716276712265071 ], [ -122.376304211373494, 37.7162464421762 ], [ -122.376232373365212, 37.716208786249759 ], [ -122.376216559470649, 37.716198737197637 ], [ -122.376202464831138, 37.716188317394391 ], [ -122.376190080782948, 37.716177183613944 ], [ -122.37617768876666, 37.716165706594467 ], [ -122.376165279082414, 37.716153543125806 ], [ -122.376154588659077, 37.716141009182088 ], [ -122.3761438902538, 37.71612813145051 ], [ -122.376134902445671, 37.716114540017813 ], [ -122.376127616916719, 37.716099891652185 ], [ -122.376121444571496, 37.716060846115461 ], [ -122.376121115383214, 37.71604780350129 ], [ -122.376125312585643, 37.716008593078577 ], [ -122.376128439914226, 37.715995495685426 ], [ -122.376133304145654, 37.715982713442855 ], [ -122.376143049597545, 37.715957835965099 ], [ -122.376159466184788, 37.715923238019322 ], [ -122.376179356242673, 37.715889271474957 ], [ -122.376189309582273, 37.715872631433676 ], [ -122.376201000523864, 37.715856307079349 ], [ -122.37621269978483, 37.715840326230605 ], [ -122.376226135941366, 37.715824660529996 ], [ -122.376239589425921, 37.715809681555776 ], [ -122.376254771155516, 37.715794675051384 ], [ -122.376271646475075, 37.715778268109382 ], [ -122.376283440655172, 37.715766062477954 ], [ -122.376310486193518, 37.715741596141669 ], [ -122.376322263732177, 37.715728704316476 ], [ -122.376334049571057, 37.715716155173126 ], [ -122.376345826755966, 37.715703263351031 ], [ -122.376369363764837, 37.715676792426088 ], [ -122.376381123602442, 37.715663213872432 ], [ -122.376401186771318, 37.715636111823081 ], [ -122.376411209340475, 37.715622217575891 ], [ -122.376441252415276, 37.715579505126925 ], [ -122.376454523861156, 37.715557318367054 ], [ -122.37646974881784, 37.715544027967823 ], [ -122.376484964758959, 37.715530394345414 ], [ -122.376515380332151, 37.715502440624682 ], [ -122.376528859694105, 37.715488491296732 ], [ -122.376542321721558, 37.715473855513849 ], [ -122.376557511986263, 37.71545919219632 ], [ -122.37656924576747, 37.715444584217934 ], [ -122.376582699107203, 37.715429604929049 ], [ -122.37659614379038, 37.715414282961056 ], [ -122.376619576292114, 37.715383693270141 ], [ -122.376631284392559, 37.715368055325236 ], [ -122.376654682556946, 37.715336092989432 ], [ -122.376664653392297, 37.715320139354375 ], [ -122.376682927469105, 37.715290662203351 ], [ -122.376687808896733, 37.715278566664409 ], [ -122.376690936088593, 37.715265468980213 ], [ -122.376688878566242, 37.715252453901009 ], [ -122.376681679659114, 37.715241237559951 ], [ -122.376662556398827, 37.715237078495178 ], [ -122.37664688961523, 37.715232864358583 ], [ -122.376639690716317, 37.715221648014925 ], [ -122.376644563481292, 37.715209209250951 ], [ -122.376652953380841, 37.715199118279322 ], [ -122.376664860393532, 37.715191374275555 ], [ -122.376687422784229, 37.715194791816721 ], [ -122.376687067504449, 37.715180719522259 ], [ -122.376702422323845, 37.715172577492581 ], [ -122.376716031573253, 37.715163776542923 ], [ -122.376729623488316, 37.715154289138269 ], [ -122.376741469833547, 37.715144142814424 ], [ -122.376755035744793, 37.715133625726743 ], [ -122.37676513617842, 37.715122820763163 ], [ -122.376776948189416, 37.715111301248839 ], [ -122.376787014292304, 37.715099123096479 ], [ -122.376797028396751, 37.715084885583089 ], [ -122.376822345285078, 37.715060446663202 ], [ -122.376847653490671, 37.715035664511078 ], [ -122.376871224781212, 37.715010566664418 ], [ -122.376894778723212, 37.714984782359565 ], [ -122.376916604418071, 37.714959025588243 ], [ -122.376960238083626, 37.714906825585267 ], [ -122.376997080800365, 37.714859540886621 ], [ -122.377010663964072, 37.714849710496978 ], [ -122.37702423844344, 37.714839536329912 ], [ -122.377036076031359, 37.714829046749252 ], [ -122.377059716529033, 37.714806694677584 ], [ -122.377071528105475, 37.714795175413244 ], [ -122.377101725785408, 37.714758640881662 ], [ -122.377110046572668, 37.71474580378316 ], [ -122.37712317899306, 37.714718125317994 ], [ -122.377131300077295, 37.714697394009583 ], [ -122.377141149035509, 37.714676635164444 ], [ -122.377151007346924, 37.714656219534078 ], [ -122.377160873636086, 37.714636147415113 ], [ -122.377172468820078, 37.71461604719299 ], [ -122.377182335436814, 37.714595974791997 ], [ -122.377193938592001, 37.714576218079934 ], [ -122.377207270307977, 37.714556433818551 ], [ -122.377218882809402, 37.714537020319568 ], [ -122.377232223179533, 37.714517579281953 ], [ -122.37725892123467, 37.71447938365538 ], [ -122.377277350893181, 37.714456084488674 ], [ -122.377283856371662, 37.71443984265948 ], [ -122.377292090065524, 37.714423573286055 ], [ -122.377300340753251, 37.714407990645519 ], [ -122.377310329000593, 37.714392723126743 ], [ -122.377322053797371, 37.714377771844191 ], [ -122.37733379557335, 37.714363506744739 ], [ -122.377347274568308, 37.714349557046042 ], [ -122.377360770908837, 37.714336294348335 ], [ -122.377376004460587, 37.714323346775984 ], [ -122.37739125500535, 37.714311085935115 ], [ -122.37740648820791, 37.714298138638888 ], [ -122.377421729728198, 37.714285534572831 ], [ -122.377436980603278, 37.714273273720465 ], [ -122.377453959342105, 37.714260985325403 ], [ -122.377469218191379, 37.7142490679811 ], [ -122.377486214595891, 37.714237465754614 ], [ -122.3775014911119, 37.714226234579385 ], [ -122.37751849583654, 37.714214975854986 ], [ -122.377549126867919, 37.714195602534218 ], [ -122.377599993609977, 37.714155991753785 ], [ -122.377650877623338, 37.714117066855323 ], [ -122.37770177893016, 37.714078828662608 ], [ -122.377754425395366, 37.714041249630476 ], [ -122.377808826358589, 37.714004672148562 ], [ -122.377878556048842, 37.713958923045773 ], [ -122.37803805055384, 37.713839292710567 ], [ -122.378311443052539, 37.713642992366779 ], [ -122.378326710994585, 37.713631417851289 ], [ -122.37834196991345, 37.713619500112657 ], [ -122.378355483282817, 37.713606923478181 ], [ -122.378367251103185, 37.713593687948453 ], [ -122.378379009900755, 37.713580109196364 ], [ -122.378389032514391, 37.713566214765343 ], [ -122.378399037086837, 37.713551633891143 ], [ -122.378409033673563, 37.71353670977841 ], [ -122.378415564843792, 37.713521497564329 ], [ -122.378423815534646, 37.713505914562745 ], [ -122.378428575143985, 37.713489014049621 ], [ -122.378438562344869, 37.713473746443995 ], [ -122.378446830724755, 37.713458850163093 ], [ -122.378458563459517, 37.713444241448215 ], [ -122.378468586039958, 37.713430347010117 ], [ -122.378482073340123, 37.713416740955331 ], [ -122.378495569294984, 37.713403477576485 ], [ -122.37854455933747, 37.713358058816908 ], [ -122.378586757641585, 37.713317555466887 ], [ -122.378660976502815, 37.71324426535471 ], [ -122.378686622123027, 37.713232868634442 ], [ -122.378712250040763, 37.713220785460798 ], [ -122.378736140733594, 37.713208386350885 ], [ -122.378760006099483, 37.713194958094263 ], [ -122.378783853401998, 37.713180842841368 ], [ -122.378805955855427, 37.713166068959936 ], [ -122.378828040611339, 37.71315060890111 ], [ -122.378850116669099, 37.713134805062303 ], [ -122.378897499585833, 37.713094218893794 ], [ -122.378902259061135, 37.713077318360412 ], [ -122.378913939946415, 37.713060650508481 ], [ -122.378933827973057, 37.713026683209065 ], [ -122.378963633962542, 37.712974702985242 ], [ -122.3789800308884, 37.712939418456834 ], [ -122.378988220659366, 37.712921432553166 ], [ -122.378994682251076, 37.712903474492308 ], [ -122.379002872021474, 37.712885488862113 ], [ -122.379010931305004, 37.712862354837071 ], [ -122.37901923419868, 37.712848831146545 ], [ -122.379027554095487, 37.712835993914254 ], [ -122.379037619176003, 37.712823815570687 ], [ -122.379047693627413, 37.712811980716452 ], [ -122.379059495902879, 37.712800118022507 ], [ -122.37908614901481, 37.712760205563576 ], [ -122.379114530283019, 37.712720265802361 ], [ -122.379142928880469, 37.712681012761955 ], [ -122.379173064293639, 37.712642075095069 ], [ -122.379203208344947, 37.71260348037233 ], [ -122.379256878689148, 37.712538071449124 ], [ -122.379268637502918, 37.712524492600444 ], [ -122.379278650439701, 37.712510254875795 ], [ -122.379298659638835, 37.712481092959536 ], [ -122.379306927729047, 37.71246619634131 ], [ -122.379315186793136, 37.712450956501357 ], [ -122.379321709005382, 37.712435401007788 ], [ -122.37932825724657, 37.712420875193772 ], [ -122.379336368787747, 37.712399800499135 ], [ -122.379346199816894, 37.712378355003281 ], [ -122.379365879212997, 37.712336150462143 ], [ -122.379377455409823, 37.712315364122553 ], [ -122.379389040962025, 37.71229492072279 ], [ -122.379400617477728, 37.712274133826106 ], [ -122.379412211349106, 37.712254033656116 ], [ -122.37943383579011, 37.712220382468807 ], [ -122.379443961457966, 37.712210606401335 ], [ -122.379452350984806, 37.712200515220019 ], [ -122.379460722794221, 37.71218973704115 ], [ -122.379467357765066, 37.712178643485373 ], [ -122.379472247213258, 37.71216689105173 ], [ -122.379480610345027, 37.712155769919313 ], [ -122.379490683937206, 37.712143934762395 ], [ -122.379499030060259, 37.712132127444399 ], [ -122.379507358465077, 37.712119633128815 ], [ -122.379513950039424, 37.712106823711594 ], [ -122.3795205329195, 37.712093670518172 ], [ -122.379525370285052, 37.712079858722241 ], [ -122.379530172590734, 37.712064674025001 ], [ -122.379539812945708, 37.712035677519864 ], [ -122.379548106627041, 37.712021810569162 ], [ -122.379556408637853, 37.712008286850036 ], [ -122.379568185022265, 37.711995394692678 ], [ -122.379580030469796, 37.711985247803753 ], [ -122.379591867242681, 37.711974757961542 ], [ -122.37960195849513, 37.711963609242559 ], [ -122.379612041066054, 37.71195211729605 ], [ -122.379622114962288, 37.711940282396583 ], [ -122.379630451657874, 37.711928131304077 ], [ -122.379638780377292, 37.711915637522473 ], [ -122.379645371896999, 37.711902827548315 ], [ -122.379650226245587, 37.711889702480249 ], [ -122.379656800410586, 37.711876206326664 ], [ -122.379659918603238, 37.711862765324433 ], [ -122.379666440684801, 37.711847209535733 ], [ -122.379678077818568, 37.711828825746096 ], [ -122.379688004133726, 37.711811155438397 ], [ -122.37969966729311, 37.7117938013265 ], [ -122.379726467250435, 37.711759723844118 ], [ -122.379741621412023, 37.711743687200766 ], [ -122.379749992782422, 37.711732909006074 ], [ -122.379758304094665, 37.711719728487729 ], [ -122.379768369250002, 37.711707550074067 ], [ -122.379780179921312, 37.711696030807062 ], [ -122.379792016612967, 37.71168554066967 ], [ -122.379807344332391, 37.711676368550826 ], [ -122.379822698086315, 37.711668226109936 ], [ -122.379838086216196, 37.711661456853747 ], [ -122.379855237891832, 37.711656032360494 ], [ -122.379872414919589, 37.711651637830329 ], [ -122.379889557917664, 37.711645870380011 ], [ -122.379908402687462, 37.71163904594512 ], [ -122.379923765095569, 37.711631246443076 ], [ -122.379940837953981, 37.711622733183106 ], [ -122.379956157314652, 37.71161321808723 ], [ -122.379969722126845, 37.711602700623473 ], [ -122.379983278248133, 37.711591839656897 ], [ -122.37999507983497, 37.71157997687245 ], [ -122.380006864748808, 37.711567427622455 ], [ -122.380016912482191, 37.711554562733966 ], [ -122.380025223388486, 37.711541382476511 ], [ -122.380036947182674, 37.711526430642408 ], [ -122.38004519730535, 37.711510847247801 ], [ -122.380061679847557, 37.711478994558078 ], [ -122.380068175776742, 37.711462409340825 ], [ -122.380072943900714, 37.711445851702329 ], [ -122.380077702996331, 37.711428950842468 ], [ -122.380080724916397, 37.711411734346029 ], [ -122.380091216381629, 37.711348044590686 ], [ -122.38010580681599, 37.711241368202799 ], [ -122.380111373878989, 37.711188057730013 ], [ -122.380119034347572, 37.71108080577428 ], [ -122.380121136424947, 37.711027206969042 ], [ -122.38012151036925, 37.71097363602329 ], [ -122.380118923524051, 37.71087135474918 ], [ -122.380110379014539, 37.710806938200108 ], [ -122.380106523093488, 37.71079120494867 ], [ -122.380104387317047, 37.710775100874208 ], [ -122.380100531751495, 37.710759367891555 ], [ -122.380096667493106, 37.710743291132871 ], [ -122.380092811923888, 37.710727557875288 ], [ -122.380087228225392, 37.710711852202415 ], [ -122.380081653209785, 37.710696489755954 ], [ -122.380077797646152, 37.710680756497752 ], [ -122.380070494504992, 37.710665421635404 ], [ -122.38005934448995, 37.710634696740314 ], [ -122.380050209064109, 37.710615270740703 ], [ -122.380041299331083, 37.710604768634425 ], [ -122.380032397935238, 37.710594609759674 ], [ -122.38002177779228, 37.710584821958371 ], [ -122.38001116562711, 37.710575376844702 ], [ -122.379989959370192, 37.710557173605956 ], [ -122.379977637144137, 37.710548442789609 ], [ -122.379965323255277, 37.71054005520417 ], [ -122.379940729859655, 37.710524652941764 ], [ -122.379928442370812, 37.710517295301628 ], [ -122.379914435085283, 37.710510308200178 ], [ -122.379886455233503, 37.710497706624587 ], [ -122.379872473994169, 37.710491749198276 ], [ -122.379856825041841, 37.710488221944338 ], [ -122.379827508004084, 37.710491093407484 ], [ -122.379813839223587, 37.710497492136078 ], [ -122.379798416283975, 37.71050288903816 ], [ -122.379782897858064, 37.710504509895109 ], [ -122.379767309667471, 37.710503385490924 ], [ -122.379749941954842, 37.710500228744046 ], [ -122.379735969404862, 37.71049461452828 ], [ -122.379723664231022, 37.710486570144241 ], [ -122.37971305247035, 37.710477125272625 ], [ -122.37970236260378, 37.710464591359482 ], [ -122.37969344425926, 37.710453745999907 ], [ -122.379682806125587, 37.710443271450828 ], [ -122.379672177364711, 37.710433140116493 ], [ -122.379661556600993, 37.710423352293517 ], [ -122.379649225750256, 37.710414277941311 ], [ -122.379636903242243, 37.710405547094624 ], [ -122.379624598093869, 37.71039750270014 ], [ -122.379610573502859, 37.710389829108948 ], [ -122.379596566278167, 37.710382842244165 ], [ -122.379582567720405, 37.710376198055286 ], [ -122.379566867083199, 37.710370611396797 ], [ -122.37955117512638, 37.710365367962943 ], [ -122.379535500182058, 37.71036081098601 ], [ -122.379504186047228, 37.710353069916337 ], [ -122.379490239583589, 37.71034848562612 ], [ -122.37947454763686, 37.710343242182041 ], [ -122.379460575130224, 37.710337627658895 ], [ -122.379444865487201, 37.710331697762861 ], [ -122.379430875636984, 37.710325397057247 ], [ -122.379416868434376, 37.710318409896438 ], [ -122.379404589708813, 37.710311395428178 ], [ -122.379390565156754, 37.710303721810703 ], [ -122.379378268729113, 37.710296020617108 ], [ -122.379364226828372, 37.710287660542946 ], [ -122.379351913051934, 37.710279272893054 ], [ -122.379341310038157, 37.710270170939822 ], [ -122.379328978913207, 37.71026109683396 ], [ -122.379307738212361, 37.710241520839027 ], [ -122.37929710884022, 37.710231389481542 ], [ -122.379288208275057, 37.710221230264345 ], [ -122.379275772698335, 37.710208037437084 ], [ -122.379261548615801, 37.710192469588307 ], [ -122.379247341892423, 37.710177588191321 ], [ -122.379231407401903, 37.710162734359407 ], [ -122.379215489925031, 37.710148566984365 ], [ -122.379199589806944, 37.710135086060731 ], [ -122.379181961577132, 37.710121632706517 ], [ -122.379164350706091, 37.710108865803193 ], [ -122.379145029076398, 37.710096812921883 ], [ -122.379125716135945, 37.710085103538738 ], [ -122.379106411517995, 37.710073736835547 ], [ -122.379081871200697, 37.710060393742431 ], [ -122.379067829377789, 37.710052033632635 ], [ -122.379053770213616, 37.710042987342149 ], [ -122.37904143915452, 37.710033912931294 ], [ -122.379027362638922, 37.710024179909425 ], [ -122.379015013896463, 37.710014419322256 ], [ -122.379004376612457, 37.710003944700169 ], [ -122.378992010871215, 37.709993497651439 ], [ -122.378981364572482, 37.709982679805925 ], [ -122.378970709601532, 37.709971518732665 ], [ -122.378961774072408, 37.709959986863232 ], [ -122.378943885326493, 37.709936236674181 ], [ -122.378934932808065, 37.709924018618082 ], [ -122.378927716720895, 37.709912115676609 ], [ -122.378920448592652, 37.709898153648354 ], [ -122.378914899558595, 37.709883820830655 ], [ -122.378911070654652, 37.709869117207219 ], [ -122.378908969518022, 37.709854386021547 ], [ -122.378906859361621, 37.709839311614516 ], [ -122.37890825816865, 37.709826241422213 ], [ -122.378906182713109, 37.709812539922396 ], [ -122.378898593625834, 37.709785878500057 ], [ -122.37889311469489, 37.709774291484649 ], [ -122.378882494466538, 37.709764503310346 ], [ -122.378852995161623, 37.709760166773435 ], [ -122.378837381455881, 37.709758012285448 ], [ -122.378821663310447, 37.709751739078932 ], [ -122.378811043440322, 37.709741950892628 ], [ -122.378800232043204, 37.709724611725996 ], [ -122.378791192829524, 37.709708961390213 ], [ -122.378764047778134, 37.709660979621674 ], [ -122.378756718985287, 37.709644614994865 ], [ -122.378747662435089, 37.709628277927287 ], [ -122.378733004866319, 37.709595548670947 ], [ -122.378727395179027, 37.709578813530399 ], [ -122.378716350309503, 37.709552206945055 ], [ -122.378707440892299, 37.709541704736111 ], [ -122.378700242233577, 37.709530488508285 ], [ -122.378694754332813, 37.709518558261863 ], [ -122.378690976843657, 37.709505914002577 ], [ -122.378687217401875, 37.709493956460406 ], [ -122.378676596888084, 37.709484167998021 ], [ -122.378662607269405, 37.709477867474909 ], [ -122.378646941924686, 37.709473653320337 ], [ -122.378632917608854, 37.70946597961165 ], [ -122.378620586358139, 37.709456905436006 ], [ -122.37861166863793, 37.709446059987314 ], [ -122.378602715880561, 37.709433841635963 ], [ -122.378598947091831, 37.709421540874992 ], [ -122.378593433533567, 37.709408580662952 ], [ -122.378584489118467, 37.709396705817099 ], [ -122.378573843310107, 37.709385887928249 ], [ -122.378559775644788, 37.709376497801813 ], [ -122.378545751356285, 37.709368824082631 ], [ -122.378531770436666, 37.709362866496143 ], [ -122.37851595734179, 37.709352817747309 ], [ -122.378503583104418, 37.709342027419368 ], [ -122.378491191524603, 37.709330550636324 ], [ -122.378478773584177, 37.709318044176833 ], [ -122.378469795190981, 37.709304796129004 ], [ -122.3784625272081, 37.709290834071133 ], [ -122.378455250555135, 37.709276528785921 ], [ -122.378449692984333, 37.709261852718043 ], [ -122.378447583629097, 37.709246778290328 ], [ -122.37844546525632, 37.709231360641176 ], [ -122.378445040296995, 37.709214542798215 ], [ -122.378446447493673, 37.70920181556793 ], [ -122.378445805707855, 37.709176416778014 ], [ -122.378447212565007, 37.70916368982774 ], [ -122.378446882999313, 37.709150647205846 ], [ -122.378444834357168, 37.709137975366076 ], [ -122.378444192579394, 37.709112576850501 ], [ -122.378433947648276, 37.709049217403461 ], [ -122.378430178900032, 37.709036916361775 ], [ -122.378428130263899, 37.709024244521487 ], [ -122.378424361863509, 37.709011943474067 ], [ -122.378420584791698, 37.708999299199597 ], [ -122.378413047997185, 37.708974697103997 ], [ -122.378407551172273, 37.708962423896338 ], [ -122.378403791789637, 37.708950465794935 ], [ -122.37839829531363, 37.708938192581272 ], [ -122.37839280715248, 37.708926262050589 ], [ -122.378381883920852, 37.708904460882493 ], [ -122.37837105540082, 37.708886435220357 ], [ -122.37835849948857, 37.708868437105671 ], [ -122.378344223820761, 37.708850809781346 ], [ -122.378331693592017, 37.708833841349971 ], [ -122.378315707193991, 37.708816928035041 ], [ -122.378301466234504, 37.708800673613098 ], [ -122.378285505520822, 37.708784789980257 ], [ -122.378262511753192, 37.708764211389848 ], [ -122.378217043865007, 37.708743647822331 ], [ -122.378194318256277, 37.708733709264415 ], [ -122.378169882609328, 37.708724484976287 ], [ -122.378147174694149, 37.708715232583032 ], [ -122.378108766623569, 37.708700393880235 ], [ -122.378089549231376, 37.708692459277344 ], [ -122.378072043283993, 37.708683810925692 ], [ -122.378056239066638, 37.708674105341039 ], [ -122.378040425836915, 37.708664056532776 ], [ -122.378029762871009, 37.708652552139604 ], [ -122.378012273597136, 37.70864459051851 ], [ -122.377996469734569, 37.708634884645676 ], [ -122.37798069155609, 37.708626209006241 ], [ -122.377964922044868, 37.708617876317049 ], [ -122.377947441795172, 37.708610257633161 ], [ -122.377929987553088, 37.708603668352943 ], [ -122.377912541998256, 37.708597422846282 ], [ -122.3778951224499, 37.708592206743326 ], [ -122.377875983484813, 37.708587361142648 ], [ -122.37785687054604, 37.708583545768889 ], [ -122.377837783259466, 37.708580759529298 ], [ -122.377818705342307, 37.708578316777135 ], [ -122.377799653089951, 37.708576903708312 ], [ -122.37778062684724, 37.708576520317315 ], [ -122.377751275466451, 37.708578018636814 ], [ -122.37773407231694, 37.708581382915298 ], [ -122.377718632623825, 37.708586092812681 ], [ -122.377701481846131, 37.708591516991923 ], [ -122.377687821894156, 37.70859825842814 ], [ -122.377674179284952, 37.708605686591305 ], [ -122.377657141190483, 37.70861557244023 ], [ -122.377638365993562, 37.708625142614181 ], [ -122.377621302228775, 37.708633998771425 ], [ -122.37760422910678, 37.708642511984849 ], [ -122.377585411236822, 37.708650365729795 ], [ -122.377547740133352, 37.708664700862904 ], [ -122.377527150486301, 37.708670866291307 ], [ -122.377508279906351, 37.708676660947582 ], [ -122.377487664247838, 37.708681796688104 ], [ -122.377468768348663, 37.708686561646054 ], [ -122.377448126680292, 37.708690667698683 ], [ -122.377432642914897, 37.708693661434552 ], [ -122.377360540414671, 37.708713695820649 ], [ -122.377290182950702, 37.708734389078231 ], [ -122.37721985977025, 37.708756455207251 ], [ -122.377122130511736, 37.708788572304698 ], [ -122.377099960038464, 37.708800600058829 ], [ -122.377076061124569, 37.708812655354983 ], [ -122.377053899648615, 37.708825026321882 ], [ -122.377031746485883, 37.70883774051714 ], [ -122.376984053287501, 37.708865969789912 ], [ -122.376966919370915, 37.708872080046966 ], [ -122.376949742820514, 37.708876474429971 ], [ -122.37693425933503, 37.708879467820125 ], [ -122.376896466671624, 37.708888997566206 ], [ -122.376879298426118, 37.708893734896868 ], [ -122.376863901249948, 37.70890016082808 ], [ -122.376850267175342, 37.708907932397054 ], [ -122.376836667745323, 37.708917076323402 ], [ -122.376824822757797, 37.708927222935884 ], [ -122.376814748831492, 37.70893905787635 ], [ -122.376802877136896, 37.70894817426737 ], [ -122.376791014809598, 37.708957634422354 ], [ -122.376755452394264, 37.708987043484797 ], [ -122.376745335469863, 37.7089971622786 ], [ -122.376733490104598, 37.709007308612669 ], [ -122.376716755060258, 37.709029207270859 ], [ -122.376711908706653, 37.709042675731048 ], [ -122.376710535868128, 37.709056775574453 ], [ -122.37670739161139, 37.709069186817985 ], [ -122.37670251024899, 37.709081282375145 ], [ -122.376694138728411, 37.709092060083194 ], [ -122.376684004106508, 37.709101492422995 ], [ -122.376670361304676, 37.709108920469312 ], [ -122.376656744500607, 37.709117378469699 ], [ -122.376650118041127, 37.709128814824965 ], [ -122.376645245682482, 37.709141253875728 ], [ -122.376642127052065, 37.709154694529374 ], [ -122.376632417316515, 37.709180944983316 ], [ -122.376619233705384, 37.709206564055854 ], [ -122.376610896477217, 37.709218714945962 ], [ -122.376602550229492, 37.709230522339361 ], [ -122.376592476565378, 37.709242356980148 ], [ -122.376582384885396, 37.709253505451606 ], [ -122.376570661100516, 37.709268456941743 ], [ -122.376557330163322, 37.709288241147618 ], [ -122.376542262116743, 37.709307709937875 ], [ -122.376527177411774, 37.709326491711906 ], [ -122.376510355603088, 37.709344958344019 ], [ -122.37649688600834, 37.709359250635856 ], [ -122.376465576896948, 37.709420181699592 ], [ -122.37643425907072, 37.709480769527424 ], [ -122.376389592497333, 37.709560455358435 ], [ -122.376388193936847, 37.709573525235768 ], [ -122.376391962028805, 37.709585826347244 ], [ -122.376399160344661, 37.709597042716048 ], [ -122.376402928439646, 37.70960934382714 ], [ -122.376401512215111, 37.709621727530589 ], [ -122.376396631112286, 37.709633823068486 ], [ -122.376388241812876, 37.709643914305353 ], [ -122.376374607552179, 37.709651685818365 ], [ -122.376360903976007, 37.709656711239063 ], [ -122.376340270633293, 37.709661160049372 ], [ -122.376319628632046, 37.709665265903617 ], [ -122.376300706073636, 37.70966900099792 ], [ -122.376281774504847, 37.709672392867645 ], [ -122.376261106515926, 37.709675469305139 ], [ -122.376242166965341, 37.709678517655895 ], [ -122.376221481647946, 37.709680907632375 ], [ -122.376202524423789, 37.709683269528199 ], [ -122.376181830441382, 37.709685316270686 ], [ -122.376162855890229, 37.709686991706079 ], [ -122.376142153236586, 37.70968869493997 ], [ -122.37612144191985, 37.709690054943209 ], [ -122.376102441041382, 37.709690700967826 ], [ -122.376060983764248, 37.70969204832312 ], [ -122.376041966245552, 37.709692007598299 ], [ -122.376021220279497, 37.709691994675758 ], [ -122.375997000772998, 37.709691350345274 ], [ -122.375979685047099, 37.709690252959057 ], [ -122.375962360653403, 37.709688812068713 ], [ -122.375946764362084, 37.709687343377091 ], [ -122.375929430969819, 37.709685559534897 ], [ -122.375913817710057, 37.709683404653902 ], [ -122.375896475997124, 37.709681277299751 ], [ -122.375880853380082, 37.709678778923745 ], [ -122.375863494692936, 37.709675965105127 ], [ -122.375830496078862, 37.709669966721727 ], [ -122.375814856497584, 37.709666782151935 ], [ -122.375799199596415, 37.709662911125946 ], [ -122.375781814588834, 37.709659067619612 ], [ -122.375766157697925, 37.7096551968638 ], [ -122.375750492134401, 37.709650982329691 ], [ -122.37571914370605, 37.709641867350243 ], [ -122.37570345217442, 37.709636623403227 ], [ -122.375687769305102, 37.709631722681216 ], [ -122.375672068772147, 37.709626135508472 ], [ -122.375626688017107, 37.709609003209117 ], [ -122.375612698633404, 37.70960270232392 ], [ -122.375586491256968, 37.709591788341328 ], [ -122.375574203661955, 37.709584430256932 ], [ -122.375561908094213, 37.709576728658583 ], [ -122.375549603516987, 37.7095686835628 ], [ -122.375539018050162, 37.709560268000558 ], [ -122.375526696511997, 37.709551536717242 ], [ -122.375505491645058, 37.709533332668286 ], [ -122.375494871897089, 37.709523544188201 ], [ -122.375485980256698, 37.709513728189755 ], [ -122.375468179326063, 37.709493410016719 ], [ -122.375457507973294, 37.709481561890541 ], [ -122.375445047568775, 37.709467338970569 ], [ -122.375432604141196, 37.709453802508946 ], [ -122.37541844161953, 37.70944063678396 ], [ -122.375402568663233, 37.709428185022126 ], [ -122.375386712684005, 37.709416419717819 ], [ -122.375370874027013, 37.709405340865558 ], [ -122.375353324589767, 37.709394975980821 ], [ -122.375334064026845, 37.709385325068311 ], [ -122.3753165495785, 37.709376333080996 ], [ -122.375297332662143, 37.709368398292526 ], [ -122.375276404964978, 37.709361177469411 ], [ -122.37525895944836, 37.709354931295927 ], [ -122.375243233032052, 37.709348314384421 ], [ -122.375227498651867, 37.709341354232684 ], [ -122.375196011900385, 37.709326747754233 ], [ -122.375180259522494, 37.709319101152943 ], [ -122.375166235593625, 37.709311427031338 ], [ -122.375150475248759, 37.709303436913331 ], [ -122.375122392776206, 37.709286715752803 ], [ -122.375108342887259, 37.709278011942899 ], [ -122.375092564902388, 37.709269335642915 ], [ -122.375080234468939, 37.709260261365458 ], [ -122.375066167268656, 37.709250870821613 ], [ -122.375046820209405, 37.709237787590567 ], [ -122.374989178668883, 37.709214326304803 ], [ -122.374933247603437, 37.709190151032686 ], [ -122.374831814752426, 37.709144037952456 ], [ -122.374807448289985, 37.709137558790196 ], [ -122.374784792602327, 37.709130365112337 ], [ -122.374762119621366, 37.709122485524915 ], [ -122.374739428974408, 37.709113918935145 ], [ -122.374716721719778, 37.709104666150246 ], [ -122.374694005469692, 37.70909507013927 ], [ -122.374673000016784, 37.709084760438706 ], [ -122.374651977245378, 37.709073763730864 ], [ -122.374630937176775, 37.709062080839466 ], [ -122.374611616554063, 37.709050027212591 ], [ -122.374592286937002, 37.709037630360818 ], [ -122.374572932059905, 37.709024204087896 ], [ -122.37455530492376, 37.709010749764424 ], [ -122.374537660498376, 37.708996609533031 ], [ -122.374519998756611, 37.708981782295425 ], [ -122.374504056473583, 37.708966584874155 ], [ -122.37448810553569, 37.708951043948915 ], [ -122.374473874034791, 37.708935132017182 ], [ -122.374445393756574, 37.708902622243208 ], [ -122.37441087248213, 37.708836215748391 ], [ -122.374376333954515, 37.708769122513743 ], [ -122.374372557576578, 37.708756477834406 ], [ -122.374371908539288, 37.708730736067942 ], [ -122.374373316424055, 37.708718008885349 ], [ -122.374376452056026, 37.708705254481714 ], [ -122.374381325118193, 37.708692815793029 ], [ -122.374386206487088, 37.708680720336844 ], [ -122.374392824593912, 37.708668940606316 ], [ -122.374401188091994, 37.708657819828403 ], [ -122.374411296980753, 37.708647358002679 ], [ -122.374421423174923, 37.708637582630523 ], [ -122.374446851154033, 37.708617606150035 ], [ -122.374463828756802, 37.708605318455056 ], [ -122.374482543776409, 37.708593346196544 ], [ -122.374499530354001, 37.708581401168907 ], [ -122.374518253325192, 37.708569772142909 ], [ -122.374535257553518, 37.708558513833601 ], [ -122.374562508759965, 37.708542285323816 ], [ -122.374586545781455, 37.70853572218347 ], [ -122.374610592144961, 37.708529502254436 ], [ -122.374634646468323, 37.708523625558676 ], [ -122.374658710134312, 37.7085180920742 ], [ -122.374682781760811, 37.708512901823013 ], [ -122.374706862737568, 37.708508055057699 ], [ -122.374732662777703, 37.708502837010279 ], [ -122.374853593864671, 37.708499538871287 ], [ -122.374919053196592, 37.70849025581817 ], [ -122.374984547145971, 37.708482345911996 ], [ -122.375051786483155, 37.708475095187325 ], [ -122.375117314688779, 37.708468558121517 ], [ -122.375182869540055, 37.708463050415169 ], [ -122.375307152797603, 37.708455577784022 ], [ -122.375370892619628, 37.708446665496233 ], [ -122.375436334529738, 37.708436695975841 ], [ -122.375501750445025, 37.708425696737649 ], [ -122.375594677055204, 37.708408765528524 ], [ -122.375613633621171, 37.708406403460977 ], [ -122.375651599745083, 37.708403738663016 ], [ -122.375689617134853, 37.708403133226682 ], [ -122.375705187517212, 37.708403572265745 ], [ -122.375746670063037, 37.708403254716799 ], [ -122.375789863380334, 37.70840222372631 ], [ -122.375831311282568, 37.708400533239043 ], [ -122.375872750535976, 37.708398500059232 ], [ -122.375914172451246, 37.708395779861441 ], [ -122.375955577054981, 37.708392373743997 ], [ -122.376026281020401, 37.708385410092347 ], [ -122.376043553148961, 37.708384791885535 ], [ -122.376081570871463, 37.708384186317168 ], [ -122.376098868977905, 37.708384597508989 ], [ -122.376117894810292, 37.708384980902565 ], [ -122.376136930009963, 37.708385708058287 ], [ -122.376154236772265, 37.708386462194504 ], [ -122.376192341117545, 37.708389288861419 ], [ -122.376221804728033, 37.708392253162685 ], [ -122.376289356067019, 37.708397357901482 ], [ -122.376356881066656, 37.708401432376654 ], [ -122.376424388753932, 37.708404820633483 ], [ -122.37649187011074, 37.708407179175936 ], [ -122.376561053904155, 37.708408480459845 ], [ -122.376628483974059, 37.708408779550602 ], [ -122.376747729724002, 37.70840722314199 ], [ -122.376763213450403, 37.708404229221443 ], [ -122.376778714166832, 37.708401922033076 ], [ -122.376794232219382, 37.708400301571331 ], [ -122.376809767242023, 37.708399367018018 ], [ -122.376832232606262, 37.708399009032 ], [ -122.376847802648896, 37.708399447925551 ], [ -122.376863390008594, 37.708400572721949 ], [ -122.376878994361022, 37.708402384250476 ], [ -122.376901555046999, 37.708405801748796 ], [ -122.376925852133638, 37.708409534936841 ], [ -122.376951859969793, 37.708412554126937 ], [ -122.376977850476194, 37.708414886857213 ], [ -122.37700382331353, 37.708416533407728 ], [ -122.377029779497036, 37.708417492938302 ], [ -122.377055709343722, 37.708417423062144 ], [ -122.377081630523975, 37.70841700995323 ], [ -122.377110981861648, 37.708415512075135 ], [ -122.377140437198108, 37.708418132914076 ], [ -122.377199313216423, 37.708422001936356 ], [ -122.377228733890291, 37.708423249845112 ], [ -122.377258145890991, 37.708424154244938 ], [ -122.377289277310766, 37.70842468814115 ], [ -122.37734804933055, 37.708424438364865 ], [ -122.377379146073082, 37.708423599054704 ], [ -122.37742059395805, 37.708421908282823 ], [ -122.377622787821593, 37.70841902848273 ], [ -122.377643542101708, 37.708419384350222 ], [ -122.377662585641644, 37.708420454217361 ], [ -122.377704146218846, 37.708423225296556 ], [ -122.37772493517798, 37.708424954057655 ], [ -122.37774400438326, 37.708427053598058 ], [ -122.377768215169155, 37.708427354337005 ], [ -122.378000106843373, 37.708436705199382 ], [ -122.37830619390914, 37.708440408092009 ], [ -122.37833376512657, 37.708436878380915 ], [ -122.378363081750692, 37.708434007008506 ], [ -122.378390687645833, 37.708431849917247 ], [ -122.378420038963, 37.708430351712977 ], [ -122.378463249251951, 37.708430005927042 ], [ -122.378492652956893, 37.708430567335327 ], [ -122.378520354243449, 37.708432185435299 ], [ -122.378549800963029, 37.708434462694896 ], [ -122.37857753695242, 37.708437453963704 ], [ -122.378607018369593, 37.70844110411668 ], [ -122.378634797731564, 37.7084458115066 ], [ -122.378662594444492, 37.708451205343785 ], [ -122.378692136242478, 37.70845725806938 ], [ -122.378711232196324, 37.708460387122912 ], [ -122.378730310465116, 37.708462829999633 ], [ -122.378768431943271, 37.708466342017964 ], [ -122.378787467182775, 37.708467068470803 ], [ -122.378808221481876, 37.708467424133083 ], [ -122.37882723903445, 37.708467464405793 ], [ -122.378846230556476, 37.708466474720126 ], [ -122.378866950150183, 37.708465457189874 ], [ -122.378885915308302, 37.708463438097219 ], [ -122.378904872467956, 37.708461075214444 ], [ -122.378923820612812, 37.708458369381802 ], [ -122.378949637703641, 37.708453837413678 ], [ -122.378975437088471, 37.708448618717021 ], [ -122.379001245829258, 37.708443742956021 ], [ -122.379028799657206, 37.708439526353885 ], [ -122.379054625403654, 37.708435337315045 ], [ -122.379080468498444, 37.708431834724301 ], [ -122.379108048344705, 37.708428647784281 ], [ -122.379133899764923, 37.708425488414207 ], [ -122.379161496965466, 37.708422988190016 ], [ -122.379201182372455, 37.708419951449862 ], [ -122.379266840650459, 37.708418560198723 ], [ -122.379358446131135, 37.708417785256238 ], [ -122.379379209096214, 37.708418484044785 ], [ -122.37939999809295, 37.708420212510255 ], [ -122.379420813122252, 37.708422970652627 ], [ -122.379439934791137, 37.708427129548738 ], [ -122.379460810565476, 37.708432290272043 ], [ -122.379479983944307, 37.708438507979139 ], [ -122.379497455985685, 37.70844578347802 ], [ -122.379516681782491, 37.708454060534727 ], [ -122.379532477472964, 37.708463422971533 ], [ -122.379550018242554, 37.708473443739265 ], [ -122.379562332087886, 37.708481831365319 ], [ -122.379578066691991, 37.708488790938986 ], [ -122.379593775956252, 37.708494720818962 ], [ -122.37961118692283, 37.708499593443946 ], [ -122.379628563179082, 37.708503093158996 ], [ -122.379654614203474, 37.708507827892014 ], [ -122.379678955199253, 37.708513276640822 ], [ -122.379705041297058, 37.708519384539102 ], [ -122.379729416669221, 37.708526206190314 ], [ -122.379762215471303, 37.708524308990917 ], [ -122.379786946339379, 37.708545202935731 ], [ -122.379809654745344, 37.70855445473304 ], [ -122.379834099571553, 37.70856402217715 ], [ -122.379856842014789, 37.70857464688364 ], [ -122.379891828930511, 37.70859091336267 ], [ -122.379907538241355, 37.708596843200731 ], [ -122.37992494057292, 37.708601372552437 ], [ -122.379945781718234, 37.708605160557006 ], [ -122.379964929495586, 37.708610348773604 ], [ -122.379985822717444, 37.708616195857346 ], [ -122.380006741975436, 37.708623072343208 ], [ -122.380025941841694, 37.708630319910732 ], [ -122.380045167406379, 37.708638597161041 ], [ -122.380062691631167, 37.708647931936689 ], [ -122.380080223835478, 37.708657609398351 ], [ -122.380097782783579, 37.708668316801365 ], [ -122.380111807100121, 37.70867599033663 ], [ -122.380127559503322, 37.708683636284732 ], [ -122.380141575145288, 37.708690966589629 ], [ -122.380173045244604, 37.708704885568586 ], [ -122.380187043533539, 37.708711529414458 ], [ -122.380202769909488, 37.708718145672115 ], [ -122.380218478926409, 37.708724075474095 ], [ -122.380234196281208, 37.708730348506272 ], [ -122.380249896974746, 37.708735935346311 ], [ -122.380267325396161, 37.708741494053129 ], [ -122.380283025396338, 37.708747080625194 ], [ -122.380298717408834, 37.708752323957377 ], [ -122.380316128481965, 37.708757196477961 ], [ -122.380331802444843, 37.708761753363198 ], [ -122.380349205186434, 37.708766282646621 ], [ -122.380364870817246, 37.708770496295159 ], [ -122.380399640892449, 37.708778181953583 ], [ -122.380415288819378, 37.708781709147395 ], [ -122.380450024177094, 37.708788021883869 ], [ -122.380467383521719, 37.708790835015982 ], [ -122.380502084159204, 37.708795774830023 ], [ -122.380519425451766, 37.708797901511986 ], [ -122.380554091366136, 37.708801468403678 ], [ -122.380571407305098, 37.708802565386677 ], [ -122.38058873123596, 37.708804005604904 ], [ -122.38062334505976, 37.708805513115699 ], [ -122.380640643641911, 37.708805923909587 ], [ -122.380657932836172, 37.708805990936078 ], [ -122.380699406909102, 37.70880532869085 ], [ -122.380744406973307, 37.708807357325021 ], [ -122.38079115213398, 37.708810044256637 ], [ -122.380837914326918, 37.708813417903293 ], [ -122.38088296649677, 37.70881750557038 ], [ -122.380929755096815, 37.708821908855221 ], [ -122.380974841668888, 37.708827369399565 ], [ -122.381019936932205, 37.708833173153394 ], [ -122.38105859232931, 37.708838506457845 ], [ -122.381066777313606, 37.708839635749648 ], [ -122.381111907677337, 37.708846812369579 ], [ -122.381195264075259, 37.708861619200029 ], [ -122.381216079294731, 37.708864377024973 ], [ -122.381238613917688, 37.708866764017813 ], [ -122.381259411768738, 37.708868835381693 ], [ -122.381280183563462, 37.708869877061808 ], [ -122.381300946672837, 37.708870575511568 ], [ -122.381323420152597, 37.708870559906792 ], [ -122.381342429462208, 37.708870256266636 ], [ -122.381404605870372, 37.708867889625047 ], [ -122.381505512521713, 37.708893060219943 ], [ -122.381586019961446, 37.708863618143027 ], [ -122.381762346029831, 37.708863204692321 ], [ -122.381781407118979, 37.708864960346808 ], [ -122.381802213679833, 37.708867375116121 ], [ -122.381821317860044, 37.708870846628407 ], [ -122.381842167859006, 37.708874977249344 ], [ -122.381859579414566, 37.708879849810799 ], [ -122.381875262525128, 37.708884749437118 ], [ -122.381890971365024, 37.708890679021408 ], [ -122.381904969842452, 37.708897322662949 ], [ -122.38191898569994, 37.708904652756104 ], [ -122.381933027626729, 37.708913012527532 ], [ -122.381945359191491, 37.708922086357219 ], [ -122.381957699102628, 37.708931503417737 ], [ -122.381968327967215, 37.70894163482285 ], [ -122.381980685608269, 37.708951738328693 ], [ -122.381993034210964, 37.708961498337537 ], [ -122.382005374141627, 37.708970915667607 ], [ -122.382017705026641, 37.70897998922608 ], [ -122.382045788934036, 37.708996708743506 ], [ -122.382059822203018, 37.709004725273097 ], [ -122.382073838096019, 37.709012055347692 ], [ -122.382089582428222, 37.709019357800898 ], [ -122.382105309038593, 37.709025973804216 ], [ -122.382136727169851, 37.709037833178158 ], [ -122.382152419374435, 37.709043076263157 ], [ -122.382168102538699, 37.709047975850353 ], [ -122.382185504767932, 37.709052504873313 ], [ -122.382201170908758, 37.709056718271931 ], [ -122.382218547417111, 37.709060217604623 ], [ -122.382234178116121, 37.709063058378014 ], [ -122.382251537592452, 37.70906587124707 ], [ -122.382268878998048, 37.70906799767139 ], [ -122.382286203707906, 37.709069437354287 ], [ -122.382303519050552, 37.709070534368244 ], [ -122.382320826380379, 37.709071287592842 ], [ -122.382341580865045, 37.709071642632544 ], [ -122.382365730359041, 37.709069540125881 ], [ -122.382388178510126, 37.709068494352529 ], [ -122.382412363104777, 37.709067764462617 ], [ -122.38243483664391, 37.709067748645822 ], [ -122.382459065046888, 37.709068735148399 ], [ -122.382481573697518, 37.709070092223556 ], [ -122.382505827816885, 37.709072107852691 ], [ -122.382528371242771, 37.709074838099873 ], [ -122.38255092334812, 37.709077911020174 ], [ -122.382573501192169, 37.709082013896044 ], [ -122.382596088421835, 37.709086459983112 ], [ -122.382618692692688, 37.709091592524558 ], [ -122.382641314350906, 37.70909741151484 ], [ -122.382662225311549, 37.709103944851186 ], [ -122.382677899491284, 37.709108501423991 ], [ -122.382700504120081, 37.709113633944185 ], [ -122.382724827799791, 37.709118395340859 ], [ -122.382747414358349, 37.709122841409943 ], [ -122.382771729703904, 37.70912725983942 ], [ -122.382794299234817, 37.7091310197154 ], [ -122.382818588161342, 37.709134408460983 ], [ -122.382842868390057, 37.709137453700393 ], [ -122.38286542018875, 37.709140526840578 ], [ -122.382889683387347, 37.709142885886266 ], [ -122.382938191710934, 37.709146917520584 ], [ -122.38296070039398, 37.709148274504173 ], [ -122.382986665264852, 37.70914957622869 ], [ -122.383007411087661, 37.709149588199111 ], [ -122.383029884990066, 37.709149571988654 ], [ -122.383050648193986, 37.709150270129996 ], [ -122.383073139146191, 37.709150940644498 ], [ -122.383093919739352, 37.709152325231301 ], [ -122.383116428772482, 37.709153682179597 ], [ -122.383158007351824, 37.709157137787358 ], [ -122.383180542125842, 37.709159524408562 ], [ -122.38322217288038, 37.709165039352904 ], [ -122.383244725048755, 37.709168112414922 ], [ -122.383265557820081, 37.709171556330674 ], [ -122.38330031099926, 37.709178554953908 ], [ -122.383319389589161, 37.709180996537953 ], [ -122.383354090598218, 37.709185935511563 ], [ -122.383373151814638, 37.709187691182962 ], [ -122.383407818040695, 37.709191256960054 ], [ -122.383426861522594, 37.709192326175256 ], [ -122.38344417758799, 37.709193422461318 ], [ -122.383463212713679, 37.709194148164016 ], [ -122.383497809392168, 37.709194968651609 ], [ -122.383516826773814, 37.709195007623549 ], [ -122.38353410806306, 37.709194731264901 ], [ -122.38355310841149, 37.709194084321595 ], [ -122.383570380651364, 37.709193464462061 ], [ -122.383603179656703, 37.709191566467979 ], [ -122.383625618791854, 37.709190177512419 ], [ -122.383660189372748, 37.709189967998213 ], [ -122.383684400085329, 37.709190267526687 ], [ -122.383706900098034, 37.709191281141187 ], [ -122.383729426202848, 37.709193324430878 ], [ -122.383751960667553, 37.709195711222868 ], [ -122.383774513205751, 37.709198783903318 ], [ -122.383797074104677, 37.709202200086082 ], [ -122.383819661098414, 37.709206645943979 ], [ -122.383842256453889, 37.709211435304148 ], [ -122.383863141444834, 37.709216938198551 ], [ -122.383887517772791, 37.709223758983043 ], [ -122.383918823190285, 37.709231155662387 ], [ -122.383950120607025, 37.709238209095858 ], [ -122.384012679973182, 37.709250943042996 ], [ -122.384043941921391, 37.709256623556676 ], [ -122.38407519587382, 37.709261961099109 ], [ -122.384137668283756, 37.709271262440524 ], [ -122.384168886754026, 37.709275226788684 ], [ -122.384201816616198, 37.709278477019389 ], [ -122.384233018039538, 37.70928175489211 ], [ -122.384265929816848, 37.709284318663009 ], [ -122.384297105498632, 37.709286567108421 ], [ -122.384329991519081, 37.709288100901972 ], [ -122.384361149451607, 37.709289662608477 ], [ -122.384394018088159, 37.7092905104804 ], [ -122.384452807809836, 37.709290943068623 ], [ -122.384475299515429, 37.709291613578934 ], [ -122.384496080167082, 37.709292997643757 ], [ -122.384516886928907, 37.709295411658836 ], [ -122.384534272309892, 37.7092992541508 ], [ -122.384549981442476, 37.709305183105528 ], [ -122.384600688269401, 37.709327720626654 ], [ -122.384651368684075, 37.709349228726715 ], [ -122.384702031710773, 37.709370049802885 ], [ -122.384754405467419, 37.709390157025624 ], [ -122.384806761855017, 37.709409578046774 ], [ -122.384859100858179, 37.709428312317186 ], [ -122.384913150236486, 37.709446332461404 ], [ -122.384967174216499, 37.709463322615903 ], [ -122.385028162503517, 37.709482261754836 ], [ -122.385061327139894, 37.709494778863174 ], [ -122.385077071522119, 37.709502080922022 ], [ -122.385092833321409, 37.709510069706113 ], [ -122.385106884415976, 37.70951877205021 ], [ -122.385135021443745, 37.709537550188017 ], [ -122.38516131723388, 37.709551894035449 ], [ -122.385185875889178, 37.70956592259045 ], [ -122.385212163333733, 37.709579922920014 ], [ -122.385240161144154, 37.709593209410606 ], [ -122.385266422163951, 37.7096061806036 ], [ -122.385294402578083, 37.709618780353921 ], [ -122.38532064620027, 37.709631064807468 ], [ -122.385376554860102, 37.709654205466649 ], [ -122.385427183484424, 37.709673653599111 ], [ -122.385474486504066, 37.709698305432774 ], [ -122.385520087565695, 37.709724014590961 ], [ -122.385565697364569, 37.70975006695771 ], [ -122.385611324607254, 37.709776805759368 ], [ -122.385656969295042, 37.709804230995893 ], [ -122.385720426192663, 37.709852316346286 ], [ -122.38574142470155, 37.709862281398394 ], [ -122.38576260604151, 37.709879453650188 ], [ -122.385783769984741, 37.709895939720084 ], [ -122.385806662054534, 37.709912398391943 ], [ -122.385829545420535, 37.709928513558424 ], [ -122.385852419736807, 37.709944285225092 ], [ -122.385908633553967, 37.709979438551144 ], [ -122.385924326223261, 37.709984681132887 ], [ -122.385940009841704, 37.709989580491779 ], [ -122.385955658638977, 37.709993107217954 ], [ -122.385971289669953, 37.709995947220456 ], [ -122.38598863209009, 37.709998073085792 ], [ -122.386004211215408, 37.709998853446052 ], [ -122.386023237864435, 37.70999923550869 ], [ -122.386043957793987, 37.709998216990876 ], [ -122.386064686430998, 37.709997541695692 ], [ -122.386085397651371, 37.709996179944298 ], [ -122.386126802680138, 37.709992770252597 ], [ -122.386147487765797, 37.709990378536858 ], [ -122.386169900968881, 37.70998795941798 ], [ -122.386190577350149, 37.709985224742994 ], [ -122.386231912690491, 37.709979068929542 ], [ -122.386250834828786, 37.709975332240148 ], [ -122.386271485083554, 37.709971568146692 ], [ -122.386292126613185, 37.709967460274193 ], [ -122.386312759445275, 37.709963009720973 ], [ -122.386333383551687, 37.709958215388703 ], [ -122.386352270849088, 37.709953106052509 ], [ -122.386372886247685, 37.709947968761632 ], [ -122.386393492926942, 37.709942487966288 ], [ -122.386412353741648, 37.709936348947188 ], [ -122.386432951711981, 37.709930525193258 ], [ -122.386451804494854, 37.709924042655906 ], [ -122.386489491932494, 37.709910390856137 ], [ -122.386508326593912, 37.709903221868281 ], [ -122.386545979522182, 37.709888197414408 ], [ -122.386564797097478, 37.709880341959447 ], [ -122.386583605612742, 37.709872143280762 ], [ -122.386602406104558, 37.709863601361754 ], [ -122.386624601143666, 37.709852601505517 ], [ -122.386648585957673, 37.709843976811243 ], [ -122.386694774483402, 37.709824694919647 ], [ -122.386716987264919, 37.709814381492933 ], [ -122.386739173906818, 37.709803038383235 ], [ -122.386759623723719, 37.709791379725885 ], [ -122.386781783879641, 37.709779007209534 ], [ -122.386809006718778, 37.709761745916552 ], [ -122.386819140491724, 37.709752312701141 ], [ -122.386825765435574, 37.70974087550043 ], [ -122.386828898974457, 37.709728120767494 ], [ -122.386826848894813, 37.709715449349289 ], [ -122.386819640282908, 37.709703890391729 ], [ -122.386817598557442, 37.709691561655937 ], [ -122.386819004336886, 37.70967883460122 ], [ -122.386823901170089, 37.709667425084099 ], [ -122.386834025525516, 37.709657648652097 ], [ -122.386847676124134, 37.709650562655604 ], [ -122.386868282692944, 37.709645082051537 ], [ -122.386895766825091, 37.709638117800075 ], [ -122.386923224816215, 37.709630123863455 ], [ -122.386948937619863, 37.709621471147983 ], [ -122.386974623942265, 37.70961178902828 ], [ -122.387000301531728, 37.709601763127544 ], [ -122.387025961350744, 37.709591051048854 ], [ -122.387051595722909, 37.709579309549376 ], [ -122.387075492913056, 37.709567251961815 ], [ -122.387099363971075, 37.709554165239872 ], [ -122.38712149784719, 37.709540762431281 ], [ -122.387143614994557, 37.709526673704133 ], [ -122.387172522192103, 37.709507668506873 ], [ -122.387191252472988, 37.709496380689082 ], [ -122.38723213436316, 37.70947237676026 ], [ -122.387250838140105, 37.709460059259776 ], [ -122.387271261641757, 37.709447370834297 ], [ -122.387308635352881, 37.709421363170897 ], [ -122.38732731330154, 37.709408015699388 ], [ -122.387344254439029, 37.70939435296458 ], [ -122.387362914960519, 37.709380319584099 ], [ -122.387379847018508, 37.709366313348852 ], [ -122.387396771054185, 37.709351964148425 ], [ -122.387413685684905, 37.70933727200498 ], [ -122.387447498167859, 37.709307200423432 ], [ -122.387477837364472, 37.709276498309869 ], [ -122.387504772282938, 37.709247910664018 ], [ -122.387518335595743, 37.709237392327147 ], [ -122.387531890183013, 37.709226530487904 ], [ -122.387555525398113, 37.709204176295842 ], [ -122.387565597996186, 37.70919234043189 ], [ -122.387577389623033, 37.709180133651593 ], [ -122.387587444438822, 37.709167611338806 ], [ -122.387595770813661, 37.709155116725697 ], [ -122.387605817253487, 37.709142251179543 ], [ -122.387614126544804, 37.709129070381714 ], [ -122.387625726420353, 37.709109312619674 ], [ -122.38763581573356, 37.709098163213035 ], [ -122.387647651248827, 37.709087672276503 ], [ -122.387659495137399, 37.7090775248451 ], [ -122.387671356453438, 37.709068063864919 ], [ -122.387684954578489, 37.709058918688022 ], [ -122.387698570117223, 37.709050459412794 ], [ -122.387713905033536, 37.709041629487615 ], [ -122.387727520579759, 37.70903317075819 ], [ -122.387741127400233, 37.709024368526379 ], [ -122.387754716779028, 37.70901487956597 ], [ -122.38776828872976, 37.709004704426171 ], [ -122.387780123872801, 37.708994213756277 ], [ -122.387790213492849, 37.708983064330674 ], [ -122.387800294400918, 37.708971571952546 ], [ -122.387820421322658, 37.708947213465045 ], [ -122.387827011176498, 37.708934403301733 ], [ -122.387835268108802, 37.708919163130588 ], [ -122.387845322493249, 37.708906640526244 ], [ -122.387855368863313, 37.708893775232887 ], [ -122.387863686784897, 37.708880937093966 ], [ -122.387870259192908, 37.708867440475665 ], [ -122.387876822882276, 37.708853600630782 ], [ -122.387881684640561, 37.70884081843792 ], [ -122.387890037417037, 37.708829353201914 ], [ -122.387898407291956, 37.708818574972398 ], [ -122.387908532074746, 37.708808798161094 ], [ -122.387920428182127, 37.708800710060288 ], [ -122.387934078526868, 37.708793624211502 ], [ -122.387949491811682, 37.708787883291222 ], [ -122.387963124704001, 37.708780110437473 ], [ -122.387974994657682, 37.708770992927079 ], [ -122.387986838098541, 37.708760845193225 ], [ -122.38799691929168, 37.70874935279231 ], [ -122.388003535248131, 37.708737572297345 ], [ -122.388025477290896, 37.708716618335004 ], [ -122.388016549225114, 37.708705430376419 ], [ -122.388024919406419, 37.708694651857606 ], [ -122.388035061251031, 37.708685561767766 ], [ -122.388048711556735, 37.70867847563099 ], [ -122.388064142243053, 37.708673421147999 ], [ -122.388079642671812, 37.708671112747226 ], [ -122.388100414499817, 37.708672152950157 ], [ -122.388116071494025, 37.708676022621809 ], [ -122.388131781471785, 37.708681951088387 ], [ -122.388159762030639, 37.708694550434529 ], [ -122.388173769748903, 37.708701536557498 ], [ -122.388201802628828, 37.708716195250794 ], [ -122.388214108081286, 37.708724238756957 ], [ -122.388226422945536, 37.708732625476948 ], [ -122.388238746184868, 37.708741355427378 ], [ -122.388251078145075, 37.708750428602698 ], [ -122.388266944735889, 37.708762535402698 ], [ -122.388279294146272, 37.708772295301998 ], [ -122.388293362910787, 37.708781683719387 ], [ -122.38830914233246, 37.708790358251797 ], [ -122.388323176244768, 37.708798374310007 ], [ -122.388338920792876, 37.708805675659121 ], [ -122.388354647568491, 37.708812290833862 ], [ -122.388372085684821, 37.70881819183667 ], [ -122.388387786655741, 37.70882377732292 ], [ -122.388405189556963, 37.70882830542164 ], [ -122.388422575029637, 37.70883214734004 ], [ -122.388439943052433, 37.70883530225435 ], [ -122.388457293652749, 37.708837771262928 ], [ -122.388476354886436, 37.708839525559505 ], [ -122.388493670259848, 37.708840621388994 ], [ -122.388512688251666, 37.708840659817909 ], [ -122.38852998653293, 37.708841069184182 ], [ -122.388547319691853, 37.708842851452602 ], [ -122.388564687729385, 37.708846006623098 ], [ -122.388580362562081, 37.708850562405146 ], [ -122.388611781644997, 37.70886241977783 ], [ -122.38865901579085, 37.708884324521037 ], [ -122.38867304103168, 37.70889199703673 ], [ -122.388704565133736, 37.708907973092835 ], [ -122.388718607824089, 37.708916332055367 ], [ -122.38873438732395, 37.708925006530642 ], [ -122.388748438741189, 37.708933708715719 ], [ -122.388776559026368, 37.708951799533132 ], [ -122.388804696766371, 37.708970576796062 ], [ -122.388818774369312, 37.708980308925696 ], [ -122.38883113260205, 37.708990411718339 ], [ -122.38884521891903, 37.709000486521759 ], [ -122.388857576813109, 37.70901058931716 ], [ -122.388869944129638, 37.709021035600863 ], [ -122.388894695509904, 37.709042614078506 ], [ -122.388910640750538, 37.709057809551815 ], [ -122.388972423353252, 37.709107980227031 ], [ -122.389081727973931, 37.709191381909299 ], [ -122.389101024813158, 37.709202403484106 ], [ -122.389122041028727, 37.709213054112247 ], [ -122.389141320435243, 37.709223389228221 ], [ -122.389162319218002, 37.709233353396939 ], [ -122.389183309291042, 37.709242974610376 ], [ -122.389223526102853, 37.709260871022344 ], [ -122.389313124685856, 37.70931711930168 ], [ -122.389481987883741, 37.709431155180901 ], [ -122.389492653071912, 37.709442658255767 ], [ -122.389505019851313, 37.709453104208613 ], [ -122.389517378601511, 37.709463206922976 ], [ -122.38952971956148, 37.709472623189463 ], [ -122.389543753727182, 37.709480638826818 ], [ -122.389557892592236, 37.709492773449732 ], [ -122.389586187763769, 37.709517728318737 ], [ -122.389598615991517, 37.709530577112993 ], [ -122.389612772309832, 37.709543397632146 ], [ -122.389625208918304, 37.709556589380568 ], [ -122.389644750565139, 37.709577221189377 ], [ -122.389655398357277, 37.709588038071757 ], [ -122.389667747741427, 37.709597797829758 ], [ -122.389681790670352, 37.709606156676571 ], [ -122.389697518418998, 37.709612771385522 ], [ -122.389713219657295, 37.709618356694392 ], [ -122.389730605029726, 37.709622198150228 ], [ -122.389747947470156, 37.709624323462215 ], [ -122.389765245594901, 37.709624732652614 ], [ -122.389782500792251, 37.709623425973788 ], [ -122.389799720389789, 37.709620746124841 ], [ -122.389816888318393, 37.709616006631549 ], [ -122.389835749113217, 37.709609867058475 ], [ -122.389863163150096, 37.709600156306408 ], [ -122.389890559721536, 37.709589758821316 ], [ -122.389917938840725, 37.709578675152393 ], [ -122.389945300499932, 37.709566905025042 ], [ -122.389970907526418, 37.709554132948654 ], [ -122.389996506509206, 37.709541017629583 ], [ -122.390020350865882, 37.709526900637606 ], [ -122.39004417774683, 37.709512096639571 ], [ -122.390067995898818, 37.709496949685239 ], [ -122.390091787861351, 37.709480773048014 ], [ -122.390113842650052, 37.709464281192048 ], [ -122.390139232126714, 37.709442928142842 ], [ -122.390159716218918, 37.709432641798621 ], [ -122.390178454754334, 37.709421696731738 ], [ -122.3901989126541, 37.709410380702636 ], [ -122.39021589655448, 37.70939843369046 ], [ -122.390234600516706, 37.709386115979576 ], [ -122.390251558562568, 37.709373139004065 ], [ -122.39026850788207, 37.709359819074663 ], [ -122.390285439740609, 37.709345812690756 ], [ -122.390300643497767, 37.709331834039105 ], [ -122.390315821066409, 37.709316825707447 ], [ -122.390329270534636, 37.709301845108946 ], [ -122.390344421562602, 37.709285807101061 ], [ -122.390361327552213, 37.709270771022531 ], [ -122.39037995255471, 37.709255363985214 ], [ -122.39039175211181, 37.709243500154884 ], [ -122.390403604724284, 37.709233695668296 ], [ -122.390415483172134, 37.70922492086406 ], [ -122.390429115893767, 37.709217148000036 ], [ -122.390442766075893, 37.709210061861029 ], [ -122.390456442426085, 37.709204004849276 ], [ -122.390471873057805, 37.709198950051309 ], [ -122.390487338262247, 37.709195268435437 ], [ -122.390504549691016, 37.709192244971604 ], [ -122.39052006760221, 37.7091906224273 ], [ -122.390549453543258, 37.709190494110466 ], [ -122.390566778448914, 37.709191932850082 ], [ -122.390582401104382, 37.709194429009507 ], [ -122.390598058337744, 37.709198298350977 ], [ -122.390613742436898, 37.709203196808069 ], [ -122.390626048230899, 37.70921124006167 ], [ -122.390650826717163, 37.709233847842071 ], [ -122.390663202699358, 37.709244636894077 ], [ -122.390679017412822, 37.709254684012365 ], [ -122.390694814671889, 37.709264044676523 ], [ -122.390710585401109, 37.709272375666167 ], [ -122.39072806745979, 37.70927999244968 ], [ -122.390745513912535, 37.709286236337753 ], [ -122.39076120677656, 37.709291478275702 ], [ -122.390820887340325, 37.709326917004176 ], [ -122.390880550507589, 37.709361669524867 ], [ -122.390955975777842, 37.709404409471233 ], [ -122.390984166616704, 37.709425245293367 ], [ -122.391043969973325, 37.709465489065806 ], [ -122.391073854047875, 37.709484924766009 ], [ -122.39110372905418, 37.709504016963635 ], [ -122.391135324127831, 37.70952273789505 ], [ -122.391198478718835, 37.709558807388859 ], [ -122.391230038228016, 37.709576155676679 ], [ -122.391263317811934, 37.70959313297017 ], [ -122.391294851500007, 37.709609451557228 ], [ -122.391328104225451, 37.709625399165667 ], [ -122.391384067146049, 37.709650596329034 ], [ -122.391403364798364, 37.709661617794914 ], [ -122.39142438147357, 37.709672268011516 ], [ -122.391443670051999, 37.70968294597585 ], [ -122.391473405843797, 37.709696546184524 ], [ -122.391490739272712, 37.709698328019577 ], [ -122.391506239801757, 37.709696018893702 ], [ -122.39153878556094, 37.709684165139144 ], [ -122.391567892571629, 37.709673053334001 ], [ -122.391595262737894, 37.70966162604946 ], [ -122.391624352264017, 37.709649827778549 ], [ -122.391716621681155, 37.709607142187203 ], [ -122.39187696505121, 37.709454172098226 ], [ -122.392088496086032, 37.709274627293134 ], [ -122.392100330316836, 37.709264136195202 ], [ -122.392112147763783, 37.709252958633222 ], [ -122.392122219297448, 37.709241122385279 ], [ -122.392130554000019, 37.709228970672129 ], [ -122.3921406164459, 37.70921679120223 ], [ -122.392147205586284, 37.70920398107237 ], [ -122.392155514411016, 37.709190799399607 ], [ -122.392160357630971, 37.709177330310062 ], [ -122.392166946757442, 37.709164519904434 ], [ -122.392190188120423, 37.709126719606594 ], [ -122.392215174675471, 37.709089577998483 ], [ -122.392281983011671, 37.708997512609393 ], [ -122.392295589286405, 37.70898870985144 ], [ -122.392309213031425, 37.708980593543679 ], [ -122.39232284551727, 37.708972820734765 ], [ -122.392336495460057, 37.708965733826894 ], [ -122.3923518734928, 37.7089586194272 ], [ -122.392365540911214, 37.708952219242256 ], [ -122.392380936412536, 37.708945791290532 ], [ -122.392396349039899, 37.708940049794052 ], [ -122.392411779830567, 37.708934994736204 ], [ -122.39242892962109, 37.708929568690095 ], [ -122.392432645117751, 37.708928100915109 ], [ -122.392656793824756, 37.709119608424643 ], [ -122.391332097355985, 37.710140738310088 ], [ -122.390887824662656, 37.71036523380814 ], [ -122.390835880106437, 37.711169771421694 ], [ -122.392609027560681, 37.711272141276474 ], [ -122.393916838957196, 37.711294747317396 ], [ -122.394130547970391, 37.711308547209811 ], [ -122.394301164504668, 37.711283892996612 ], [ -122.394415018271332, 37.711243467479711 ], [ -122.394533636579439, 37.711181739957787 ], [ -122.394789228222862, 37.711029491236808 ], [ -122.395699524288574, 37.710911289111898 ], [ -122.395804130724926, 37.711357784012904 ], [ -122.395984840101519, 37.711902635542479 ], [ -122.396209567629995, 37.712429108802013 ], [ -122.396615767431044, 37.713094016747043 ], [ -122.396615774145118, 37.713094103716635 ], [ -122.396885665233555, 37.713468431896139 ], [ -122.397834560573045, 37.714650610374164 ], [ -122.398259710629063, 37.715141974251068 ], [ -122.398471400839512, 37.715455433381891 ], [ -122.398552555430172, 37.715443409305585 ], [ -122.398990734294756, 37.715338865287599 ], [ -122.398990733617495, 37.715338865847912 ], [ -122.398991048786399, 37.715338790720075 ], [ -122.398991043085772, 37.715338797679301 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":16,\"ZIP_CODE\":94110,\"ID\":94110},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.4217053213399, 37.731806501069016 ], [ -122.422820482616018, 37.731641033131751 ], [ -122.423920024119482, 37.731552895324626 ], [ -122.424553925961277, 37.731549131439351 ], [ -122.424645313557747, 37.731548588455745 ], [ -122.424987445865384, 37.731559842008934 ], [ -122.425563267495079, 37.731578779884757 ], [ -122.426103813784607, 37.731651546856703 ], [ -122.426095455082049, 37.731670597839134 ], [ -122.425919712835267, 37.7318157721811 ], [ -122.425781691782262, 37.731929785903453 ], [ -122.425500227991904, 37.7320527573848 ], [ -122.42519868259302, 37.732129150959032 ], [ -122.425046994836066, 37.732129947854176 ], [ -122.424277944077048, 37.732133983506472 ], [ -122.423769382874056, 37.732162707171447 ], [ -122.423486414351174, 37.732261254158729 ], [ -122.423194763566187, 37.732223135593344 ], [ -122.422597636657429, 37.732308431174374 ], [ -122.421964308435761, 37.732433222436505 ], [ -122.421769540807844, 37.732442879558889 ], [ -122.421628644882077, 37.732486581446111 ], [ -122.421573075628018, 37.732579492315836 ], [ -122.421610219449562, 37.732670894611623 ], [ -122.421740792022078, 37.732743282983918 ], [ -122.421779146191113, 37.732885343728064 ], [ -122.42184863663951, 37.732986304256819 ], [ -122.42202732889227, 37.733140443779334 ], [ -122.422262570568407, 37.733475728240066 ], [ -122.42233700854446, 37.733668492264307 ], [ -122.422378321374794, 37.733842795338198 ], [ -122.42251406401526, 37.734049477037892 ], [ -122.422640968014662, 37.73418217013328 ], [ -122.422876476168085, 37.734369596551609 ], [ -122.422737437389159, 37.734490301194434 ], [ -122.422547222688962, 37.734619826084632 ], [ -122.422284286448345, 37.734779711262206 ], [ -122.422205425171413, 37.73485685013258 ], [ -122.422146910860818, 37.734962831393027 ], [ -122.422120806983799, 37.735152847768823 ], [ -122.42260172446116, 37.735176855221177 ], [ -122.422687909096169, 37.735204969342369 ], [ -122.424702562314437, 37.735369870957172 ], [ -122.424930534158477, 37.735266285585048 ], [ -122.427103368742522, 37.735457096504888 ], [ -122.427838803054428, 37.734693898279502 ], [ -122.428436980709748, 37.734950949626743 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.42783624298761, 37.735347761815902 ], [ -122.427738169173168, 37.735412543347451 ], [ -122.42729729676671, 37.735729149898575 ], [ -122.426544994076067, 37.736269396547655 ], [ -122.426500890836181, 37.73630106743056 ], [ -122.425847678890904, 37.736860666965818 ], [ -122.425439441891498, 37.737210392933726 ], [ -122.425110714349969, 37.737550673136937 ], [ -122.424904804743889, 37.737763817504508 ], [ -122.424711669489241, 37.738076607801148 ], [ -122.424696469855888, 37.738101223886673 ], [ -122.424531564221411, 37.738368292265967 ], [ -122.424284283357963, 37.738949691036076 ], [ -122.424063599996956, 37.739784297520274 ], [ -122.424147154179678, 37.739869229183775 ], [ -122.424147154546787, 37.739869230001858 ], [ -122.42414746787631, 37.739869414698418 ], [ -122.424258152196032, 37.739934707389949 ], [ -122.42413709700044, 37.740335654620864 ], [ -122.42417135664968, 37.740713439585669 ], [ -122.424301394893192, 37.742119600447609 ], [ -122.424233478510288, 37.742235956906946 ], [ -122.424233472639315, 37.742235957277522 ], [ -122.424233272287097, 37.742236301993223 ], [ -122.424233277120777, 37.742236301639601 ], [ -122.424309369085989, 37.7430347552692 ], [ -122.424384862816794, 37.743838216035606 ], [ -122.424459208282684, 37.744635998834639 ], [ -122.424538964982034, 37.745435717069675 ], [ -122.424602793343411, 37.746241400149849 ], [ -122.424689793548367, 37.747033402901096 ], [ -122.424768280884535, 37.747830304166378 ], [ -122.424841979744244, 37.748600757493776 ], [ -122.424845018024243, 37.748632519532975 ], [ -122.424845024283727, 37.748632587554638 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.424999224133984, 37.75023271753119 ], [ -122.424999224155329, 37.750232718354916 ], [ -122.425075700414453, 37.751033151258099 ], [ -122.425122809405835, 37.751528900123922 ], [ -122.4251525416649, 37.751841785070944 ], [ -122.425152541686245, 37.751841785894676 ], [ -122.425302616566313, 37.753431589741957 ], [ -122.425302616947704, 37.753431591109177 ], [ -122.425425666412067, 37.754703378159874 ], [ -122.425457179889037, 37.755029078716305 ], [ -122.425491892915787, 37.755387845874338 ], [ -122.425612216172169, 37.756631383038098 ], [ -122.425645855643012, 37.756979041458848 ], [ -122.42568945931761, 37.757429671011749 ], [ -122.425689459367419, 37.757429672933782 ], [ -122.425766807341631, 37.758226828700685 ], [ -122.425843422635126, 37.759029397589842 ], [ -122.425900090784694, 37.759619242549249 ], [ -122.425920302092379, 37.759829613405913 ], [ -122.425985721165645, 37.760498692937027 ], [ -122.426076501462219, 37.761427133668562 ], [ -122.426076501815189, 37.761427133937481 ], [ -122.426145984060597, 37.762160358437526 ], [ -122.426149283375466, 37.762227539381186 ], [ -122.426191463523168, 37.762538363286502 ], [ -122.426204067323269, 37.762631239367813 ], [ -122.426224290129298, 37.762962298008539 ], [ -122.426228478902843, 37.763030870557735 ], [ -122.426317477898522, 37.763970002371671 ], [ -122.426381708399177, 37.764645687871273 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426381062758907, 37.764645729188807 ], [ -122.426381062356072, 37.764645726997848 ], [ -122.424574375550648, 37.764754942537614 ], [ -122.424102683503691, 37.764783452170008 ], [ -122.423112421134832, 37.764843298770074 ], [ -122.42285341443899, 37.764858950163855 ], [ -122.421886445840627, 37.764917378530825 ], [ -122.421381475659516, 37.764947887797014 ], [ -122.421239593012103, 37.764956459387825 ], [ -122.420901270224292, 37.764976898858535 ], [ -122.420580607922048, 37.764996270018614 ], [ -122.420482492120144, 37.765002197009743 ], [ -122.419668972681833, 37.765051337054757 ], [ -122.418698425401715, 37.765109955145007 ], [ -122.418579138459435, 37.765117159398407 ], [ -122.41748659577631, 37.765183134636899 ], [ -122.416387522888556, 37.765249494040667 ], [ -122.415308388038014, 37.765314639624691 ], [ -122.413105243007919, 37.765447608576679 ], [ -122.412416426589019, 37.765489809283601 ], [ -122.411455262151748, 37.765547605227276 ], [ -122.410486689274492, 37.765605838622655 ], [ -122.408544242939584, 37.765722599679115 ], [ -122.407534229395537, 37.765783299286987 ], [ -122.407419737953433, 37.764487829924626 ], [ -122.406859230067056, 37.764521681123377 ], [ -122.406429154577822, 37.764547653499882 ], [ -122.406429152848446, 37.764547653527842 ], [ -122.406005237363416, 37.764573252226405 ], [ -122.405463420216606, 37.764605967981389 ], [ -122.405089603673972, 37.764628538361961 ], [ -122.405089602982216, 37.764628538373131 ], [ -122.405059622300016, 37.764310557738277 ], [ -122.405104592203571, 37.763852244172895 ], [ -122.405203088479681, 37.763478066930332 ], [ -122.405258413683796, 37.763323129887908 ], [ -122.40532469627189, 37.76313750682079 ], [ -122.405384490436404, 37.76297005284507 ], [ -122.405653838302513, 37.762432552128487 ], [ -122.405925510731336, 37.762011914887523 ], [ -122.406021608630979, 37.761863123291867 ], [ -122.406147061407296, 37.7616371808829 ], [ -122.4062826701159, 37.761392945250627 ], [ -122.406479479731502, 37.760730932047331 ], [ -122.406492697683589, 37.760686469836571 ], [ -122.406492391998796, 37.760255279712112 ], [ -122.406389357641601, 37.759804405761585 ], [ -122.406261953936678, 37.75945211819198 ], [ -122.406247868764623, 37.759429354319998 ], [ -122.406103779268008, 37.759196479511928 ], [ -122.405903135660481, 37.758934413706449 ], [ -122.405545221503274, 37.758536929330724 ], [ -122.405048169961901, 37.758189448369819 ], [ -122.404835145723425, 37.7580339263735 ], [ -122.40472107938929, 37.757950649275713 ], [ -122.404441730762329, 37.757748221369937 ], [ -122.404168659910013, 37.75752977302043 ], [ -122.403774405770974, 37.757138490742172 ], [ -122.403774084903432, 37.757138026202746 ], [ -122.403689260967766, 37.757015002579273 ], [ -122.403543859417937, 37.75680412126377 ], [ -122.403395700993769, 37.756471262485057 ], [ -122.403305530792395, 37.756165877853867 ], [ -122.403245024221292, 37.755760575231484 ], [ -122.403244936870763, 37.755759623462495 ], [ -122.403127143734139, 37.75447773377249 ], [ -122.403011715723153, 37.753221541143297 ], [ -122.40301031565609, 37.753201877425177 ], [ -122.402978169178084, 37.75275045704997 ], [ -122.403006670294943, 37.752445905732571 ], [ -122.403060331379024, 37.751915861053995 ], [ -122.403072643662085, 37.751794247946641 ], [ -122.403175027775376, 37.751281093014263 ], [ -122.403214572776321, 37.751164169122518 ], [ -122.403378511001179, 37.750679445903444 ], [ -122.403514246938869, 37.750281563827826 ], [ -122.403735650894049, 37.749583872707518 ], [ -122.403783562755194, 37.749432891426196 ], [ -122.403826366530197, 37.749298003881435 ], [ -122.403865672790147, 37.749174139069758 ], [ -122.403990987296382, 37.74878066971192 ], [ -122.404051662308461, 37.74859015799958 ], [ -122.404103617259366, 37.748427025595028 ], [ -122.404104297626958, 37.748424888760844 ], [ -122.404105247604519, 37.748421906188959 ], [ -122.404249303912664, 37.747969580021021 ], [ -122.404390053649081, 37.747536214374357 ], [ -122.404496510435962, 37.747208430674682 ], [ -122.40472494664624, 37.746505061967419 ], [ -122.40491012189095, 37.745826558312224 ], [ -122.405051159825348, 37.745369647465395 ], [ -122.405179630488661, 37.744981134097749 ], [ -122.405202201087945, 37.744938889063008 ], [ -122.405444172050537, 37.744486004341084 ], [ -122.405475695814175, 37.744427001636382 ], [ -122.405747110957691, 37.744028598841339 ], [ -122.405778655290121, 37.743989539741165 ], [ -122.406047831839984, 37.743656241756561 ], [ -122.406149889909912, 37.743534947555823 ], [ -122.406881612449212, 37.742665295031905 ], [ -122.407179681106143, 37.742274728523746 ], [ -122.407209491524853, 37.742235666028684 ], [ -122.407448073794797, 37.741860521932459 ], [ -122.407625696256517, 37.741528038897464 ], [ -122.407757062965644, 37.741253133394146 ], [ -122.407846654253319, 37.741024367104636 ], [ -122.407964646391505, 37.740662552272049 ], [ -122.408049229318891, 37.740339110042903 ], [ -122.408114071971212, 37.739818086787388 ], [ -122.408137030577961, 37.739635775942595 ], [ -122.407910235902733, 37.739622169745928 ], [ -122.407891817991583, 37.739621065115479 ], [ -122.407641980575562, 37.73960607595005 ], [ -122.406978585498265, 37.739566272603653 ], [ -122.40691098434678, 37.739429695993088 ], [ -122.406917382338506, 37.739360326809205 ], [ -122.406923419099328, 37.739294876190314 ], [ -122.40692734433064, 37.739234393770168 ], [ -122.406930338448149, 37.739188255204461 ], [ -122.406936851773693, 37.739087891690275 ], [ -122.406939830707117, 37.738876165961258 ], [ -122.406945393527934, 37.738693880058896 ], [ -122.40693392611557, 37.738601770051126 ], [ -122.406915744052654, 37.738455725148341 ], [ -122.406895428755547, 37.738323651840822 ], [ -122.406867742024218, 37.738143654094841 ], [ -122.406900092501772, 37.738009155282221 ], [ -122.406910504104971, 37.737965866913434 ], [ -122.406734307244236, 37.736552960385914 ], [ -122.40670383539134, 37.73630860402433 ], [ -122.406618861734884, 37.735896873766244 ], [ -122.406454646881144, 37.735510813404687 ], [ -122.406382307240833, 37.735418606514493 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.406543556705884, 37.735230631177984 ], [ -122.406646483954859, 37.73519837501037 ], [ -122.406814899797354, 37.735145595835078 ], [ -122.406847917074074, 37.735135248636119 ], [ -122.407130003639637, 37.735046845926902 ], [ -122.407348459866085, 37.73497838329498 ], [ -122.408033938982058, 37.734745520041734 ], [ -122.408486051038935, 37.734591930477059 ], [ -122.409312695053686, 37.734284112011252 ], [ -122.410241243396115, 37.733882628159428 ], [ -122.41165477705286, 37.733366974354652 ], [ -122.413331412339687, 37.732631849917702 ], [ -122.413420514856341, 37.732598164337901 ], [ -122.414376034430035, 37.732236918329072 ], [ -122.414530950551168, 37.732192436697261 ], [ -122.415052879313009, 37.73204257200301 ], [ -122.415897414400035, 37.731880953714693 ], [ -122.416651654342999, 37.731819145397338 ], [ -122.417401517276474, 37.731856484581833 ], [ -122.41846272371707, 37.73186673188021 ], [ -122.419454332152029, 37.731867094633678 ], [ -122.420326686201122, 37.731814347002349 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.4217053213399, 37.731806501069016 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":17,\"ZIP_CODE\":94134,\"ID\":94134},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.423335646405121, 37.727792365353856 ], [ -122.423335646419318, 37.727792365903014 ], [ -122.423508669179441, 37.728206721890601 ], [ -122.423584360438809, 37.728387748823138 ], [ -122.423739981620528, 37.728759936840348 ], [ -122.423740585686062, 37.728759904726353 ], [ -122.424198685585807, 37.728735553475715 ], [ -122.424688687008342, 37.728709478304708 ], [ -122.425289939392513, 37.728677480232541 ], [ -122.425963703408243, 37.728641021813154 ], [ -122.426024704407865, 37.729396982427588 ], [ -122.423797638913399, 37.729509407965139 ], [ -122.422942671063694, 37.729241733452504 ], [ -122.422503353186897, 37.729261991833724 ], [ -122.421440856780521, 37.729310979302717 ], [ -122.421182810911759, 37.729745420447983 ], [ -122.421083540431724, 37.729975107309578 ], [ -122.421017374424935, 37.730233164434182 ], [ -122.420964111358728, 37.730677319690216 ], [ -122.420944905836123, 37.73130857523288 ], [ -122.421753180203225, 37.731084669073972 ], [ -122.421730502033157, 37.73135209792499 ], [ -122.421736572397847, 37.731350897661805 ], [ -122.421730663628679, 37.731437047083624 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.420326686201122, 37.731814347002349 ], [ -122.419454332152029, 37.731867094633678 ], [ -122.41846272371707, 37.73186673188021 ], [ -122.417401517276474, 37.731856484581833 ], [ -122.416651654342999, 37.731819145397338 ], [ -122.415897414400035, 37.731880953714693 ], [ -122.415052879313009, 37.73204257200301 ], [ -122.414530950551168, 37.732192436697261 ], [ -122.414376034430035, 37.732236918329072 ], [ -122.413420514856341, 37.732598164337901 ], [ -122.413331412339687, 37.732631849917702 ], [ -122.41165477705286, 37.733366974354652 ], [ -122.410241243396115, 37.733882628159428 ], [ -122.409312695053686, 37.734284112011252 ], [ -122.408486051038935, 37.734591930477059 ], [ -122.408033938982058, 37.734745520041734 ], [ -122.407348459866085, 37.73497838329498 ], [ -122.407130003639637, 37.735046845926902 ], [ -122.406847917074074, 37.735135248636119 ], [ -122.406814899797354, 37.735145595835078 ], [ -122.406646483954859, 37.73519837501037 ], [ -122.406543556705884, 37.735230631177984 ], [ -122.406294659725731, 37.735306888886043 ], [ -122.405494571184249, 37.734287050530888 ], [ -122.405096042524178, 37.733716127227488 ], [ -122.405059585407599, 37.733652395233406 ], [ -122.404878800551302, 37.733336356125804 ], [ -122.404657097641305, 37.732948782960683 ], [ -122.40465709692171, 37.732948781873532 ], [ -122.40405482060109, 37.731789516245591 ], [ -122.403961731325225, 37.731610333558521 ], [ -122.403400462440644, 37.730658352585962 ], [ -122.403021622188092, 37.729901901370539 ], [ -122.402752244737584, 37.729357163240891 ], [ -122.402209197740476, 37.728101973566574 ], [ -122.402105120777605, 37.727789703519925 ], [ -122.402039596395539, 37.727554690472438 ], [ -122.401875118236148, 37.726964759399728 ], [ -122.401822425920514, 37.726775260854211 ], [ -122.401474642450822, 37.725528485745301 ], [ -122.401471218295981, 37.725516183407578 ], [ -122.401182917574161, 37.724588254281116 ], [ -122.40078474798517, 37.723616209514844 ], [ -122.400784747286778, 37.723616209251418 ], [ -122.400661931084002, 37.723316373994798 ], [ -122.40038213245046, 37.722633285332655 ], [ -122.399925016822507, 37.721517272170509 ], [ -122.399830553211288, 37.721427080386505 ], [ -122.399600300155441, 37.720920363637823 ], [ -122.399419264352986, 37.720532021245042 ], [ -122.399309038031902, 37.720278334103305 ], [ -122.399208340593788, 37.719991530079234 ], [ -122.399125486878972, 37.719730004239857 ], [ -122.399066465573654, 37.719469200866385 ], [ -122.398943601880902, 37.718926286066001 ], [ -122.398942440206895, 37.718921151552976 ], [ -122.398923285260594, 37.718836507697276 ], [ -122.398913161799925, 37.718792447247658 ], [ -122.398799134608808, 37.718275024173522 ], [ -122.398787867001658, 37.718224410399863 ], [ -122.398759937061044, 37.718047446028777 ], [ -122.39871606167118, 37.717769450850419 ], [ -122.398686680058731, 37.717603871431201 ], [ -122.398668885707423, 37.717503589023465 ], [ -122.398561414050022, 37.716897923924947 ], [ -122.398499278161879, 37.716543657492224 ], [ -122.398550700112352, 37.7161815389411 ], [ -122.398565370170317, 37.716139616445538 ], [ -122.398643015232707, 37.715917722950913 ], [ -122.398656056102226, 37.71588045500463 ], [ -122.398735527529269, 37.715727131371494 ], [ -122.398839335741798, 37.715526852955328 ], [ -122.398953101659146, 37.715385829745912 ], [ -122.398991043085772, 37.715338797679301 ], [ -122.398991048786399, 37.715338790720075 ], [ -122.398990733617495, 37.715338865847912 ], [ -122.398990734294756, 37.715338865287599 ], [ -122.398552555430172, 37.715443409305585 ], [ -122.398471400839512, 37.715455433381891 ], [ -122.398259710629063, 37.715141974251068 ], [ -122.397834560573045, 37.714650610374164 ], [ -122.396885665233555, 37.713468431896139 ], [ -122.396615774145118, 37.713094103716635 ], [ -122.396615767431044, 37.713094016747043 ], [ -122.396209567629995, 37.712429108802013 ], [ -122.395984840101519, 37.711902635542479 ], [ -122.395804130724926, 37.711357784012904 ], [ -122.395699524288574, 37.710911289111898 ], [ -122.394789228222862, 37.711029491236808 ], [ -122.394533636579439, 37.711181739957787 ], [ -122.394415018271332, 37.711243467479711 ], [ -122.394301164504668, 37.711283892996612 ], [ -122.394130547970391, 37.711308547209811 ], [ -122.393916838957196, 37.711294747317396 ], [ -122.392609027560681, 37.711272141276474 ], [ -122.390835880106437, 37.711169771421694 ], [ -122.390887824662656, 37.71036523380814 ], [ -122.391332097355985, 37.710140738310088 ], [ -122.392656793824756, 37.709119608424643 ], [ -122.392432645117751, 37.708928100915109 ], [ -122.392444333503988, 37.708923483961478 ], [ -122.392459711870288, 37.708916369816677 ], [ -122.392473335584143, 37.708908253489831 ], [ -122.392486941819897, 37.70889945070968 ], [ -122.392498784665378, 37.708889302522401 ], [ -122.392510610730511, 37.708878468145663 ], [ -122.392524068414929, 37.708863830521395 ], [ -122.392559440339639, 37.708827208691588 ], [ -122.392572967908777, 37.708815316868559 ], [ -122.392584776482323, 37.708803796307102 ], [ -122.392598312774226, 37.708792247432399 ], [ -122.392611857807054, 37.708781042056593 ], [ -122.392623674754688, 37.708769864447994 ], [ -122.39263895660568, 37.708758974800666 ], [ -122.392652510357621, 37.708748112371396 ], [ -122.392666072850986, 37.708737593441008 ], [ -122.39268137181341, 37.708727389970818 ], [ -122.39269494304439, 37.708717214537501 ], [ -122.392710242682909, 37.70870701077785 ], [ -122.392756192651433, 37.708678459962563 ], [ -122.392771518490107, 37.708669285872482 ], [ -122.392791975788157, 37.708657969391702 ], [ -122.392832794235133, 37.708631560935011 ], [ -122.392860041474023, 37.708615328192714 ], [ -122.392875393166236, 37.708607184046727 ], [ -122.392889025524426, 37.708599411171832 ], [ -122.392904394682077, 37.708591953199182 ], [ -122.392918035766968, 37.708584523272208 ], [ -122.392941949172211, 37.708573151218971 ], [ -122.392977635730745, 37.708548885387124 ], [ -122.393011585450822, 37.708524304093004 ], [ -122.393045517668241, 37.708499036337294 ], [ -122.393079441122353, 37.708473425345893 ], [ -122.393113355819651, 37.708447471393349 ], [ -122.393167526617077, 37.70840230614084 ], [ -122.393582832216993, 37.708417948939243 ], [ -122.395113504570858, 37.708409867431477 ], [ -122.397085902715403, 37.708399425270557 ], [ -122.399684603917876, 37.708385616773455 ], [ -122.401309241246423, 37.708378554199705 ], [ -122.402497775210733, 37.708373373547339 ], [ -122.40338621311453, 37.708369492971435 ], [ -122.40510200377139, 37.708361979821355 ], [ -122.405343853958243, 37.708354977289922 ], [ -122.405521792033781, 37.708350038400255 ], [ -122.406475878190662, 37.708342152666113 ], [ -122.406591468680915, 37.708342377473876 ], [ -122.407290175418893, 37.708343733713313 ], [ -122.407753446712761, 37.708342067925997 ], [ -122.408110927534096, 37.708327349450251 ], [ -122.40829882656611, 37.708327401816014 ], [ -122.412359860013211, 37.708328460170456 ], [ -122.413340535135291, 37.70832869518982 ], [ -122.413594573052677, 37.708328955439633 ], [ -122.414219950784457, 37.708329593757632 ], [ -122.414374376554221, 37.708329750560253 ], [ -122.415233045955674, 37.708329360822155 ], [ -122.415330384121233, 37.708329316229424 ], [ -122.416223848405949, 37.708328179819176 ], [ -122.417093688537392, 37.708327067084589 ], [ -122.417207468396128, 37.708327057129232 ], [ -122.418047976840626, 37.708326981775464 ], [ -122.418165047871298, 37.708327396090446 ], [ -122.419017914810595, 37.708330411075799 ], [ -122.419120269682026, 37.708330983731479 ], [ -122.419237502940533, 37.708331640189144 ], [ -122.419704274317439, 37.708331586927457 ], [ -122.420073510611971, 37.708332523899031 ], [ -122.420159361284988, 37.708332741426119 ], [ -122.423764571500399, 37.708341826236627 ], [ -122.423361076063088, 37.709234055437079 ], [ -122.423353359735231, 37.709250935907697 ], [ -122.423353331596459, 37.709250997624174 ], [ -122.423820286343911, 37.709573973454503 ], [ -122.423971204711407, 37.709678357430306 ], [ -122.424550951226621, 37.710101873835256 ], [ -122.425118753539664, 37.710464462528329 ], [ -122.425572654326643, 37.710701178146003 ], [ -122.426490332391396, 37.711021642816618 ], [ -122.426849963523566, 37.711156197850272 ], [ -122.425769401653085, 37.714425227128565 ], [ -122.427973857260341, 37.715161057331514 ], [ -122.427571950566247, 37.71699744604517 ], [ -122.426961271013212, 37.717805116783104 ], [ -122.426390269927637, 37.718560725227924 ], [ -122.426390269596226, 37.718560725782737 ], [ -122.426390032692737, 37.718560604670934 ], [ -122.425834816801412, 37.718279388717512 ], [ -122.425605873732934, 37.718131895102509 ], [ -122.425411706342686, 37.717880972693159 ], [ -122.425177444570053, 37.717485960998928 ], [ -122.424942097378548, 37.717261064626705 ], [ -122.424687869969304, 37.717146210464122 ], [ -122.424656181661859, 37.717131894726805 ], [ -122.424528018591516, 37.717064916531086 ], [ -122.424258262597263, 37.717024059120966 ], [ -122.424022457403083, 37.717036533105109 ], [ -122.423774201550728, 37.717092319144726 ], [ -122.423530052223612, 37.717201923868423 ], [ -122.422472988486305, 37.717887379027822 ], [ -122.422642040011837, 37.718030648532753 ], [ -122.422885734123284, 37.71809195842259 ], [ -122.423023908872437, 37.718162895921928 ], [ -122.423452239422744, 37.718705588384864 ], [ -122.423536512782078, 37.718956983348477 ], [ -122.42348866361003, 37.719439263071187 ], [ -122.423554916906838, 37.719668150420404 ], [ -122.423736904433326, 37.71982328246721 ], [ -122.424032169669715, 37.719926258473294 ], [ -122.424800888544937, 37.7200238938456 ], [ -122.425478982693178, 37.720232473267444 ], [ -122.425478983024618, 37.720232472712645 ], [ -122.425479352540037, 37.720232586714161 ], [ -122.425479352547157, 37.72023258698875 ], [ -122.425664999767108, 37.720321846926637 ], [ -122.426022740865662, 37.720493848462766 ], [ -122.42602274262245, 37.720493849532829 ], [ -122.426022741606815, 37.720493850373522 ], [ -122.424918832446721, 37.722087021186233 ], [ -122.424897104558099, 37.722118378784295 ], [ -122.423806549912683, 37.723691155552039 ], [ -122.423577946908821, 37.723750386689403 ], [ -122.423577951775229, 37.723750454459399 ], [ -122.422830067678831, 37.723944194056337 ], [ -122.421846961042462, 37.724198859168652 ], [ -122.421846961049567, 37.724198859443213 ], [ -122.422043387171286, 37.724673029193113 ], [ -122.422342507091699, 37.725395088531037 ], [ -122.422342507113001, 37.725395089354791 ], [ -122.422840471972961, 37.726597115803735 ], [ -122.422840471987186, 37.726597116352892 ], [ -122.42312222730105, 37.727277221300007 ], [ -122.423246600371399, 37.727577430218268 ], [ -122.423335646405121, 37.727792365353856 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":18,\"ZIP_CODE\":94112,\"ID\":94112},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.427838803054428, 37.734693898279502 ], [ -122.427103368742522, 37.735457096504888 ], [ -122.424930534158477, 37.735266285585048 ], [ -122.424702562314437, 37.735369870957172 ], [ -122.422687909096169, 37.735204969342369 ], [ -122.42260172446116, 37.735176855221177 ], [ -122.422120806983799, 37.735152847768823 ], [ -122.422146910860818, 37.734962831393027 ], [ -122.422205425171413, 37.73485685013258 ], [ -122.422284286448345, 37.734779711262206 ], [ -122.422547222688962, 37.734619826084632 ], [ -122.422737437389159, 37.734490301194434 ], [ -122.422876476168085, 37.734369596551609 ], [ -122.422640968014662, 37.73418217013328 ], [ -122.42251406401526, 37.734049477037892 ], [ -122.422378321374794, 37.733842795338198 ], [ -122.42233700854446, 37.733668492264307 ], [ -122.422262570568407, 37.733475728240066 ], [ -122.42202732889227, 37.733140443779334 ], [ -122.42184863663951, 37.732986304256819 ], [ -122.421779146191113, 37.732885343728064 ], [ -122.421740792022078, 37.732743282983918 ], [ -122.421610219449562, 37.732670894611623 ], [ -122.421573075628018, 37.732579492315836 ], [ -122.421628644882077, 37.732486581446111 ], [ -122.421769540807844, 37.732442879558889 ], [ -122.421964308435761, 37.732433222436505 ], [ -122.422597636657429, 37.732308431174374 ], [ -122.423194763566187, 37.732223135593344 ], [ -122.423486414351174, 37.732261254158729 ], [ -122.423769382874056, 37.732162707171447 ], [ -122.424277944077048, 37.732133983506472 ], [ -122.425046994836066, 37.732129947854176 ], [ -122.42519868259302, 37.732129150959032 ], [ -122.425500227991904, 37.7320527573848 ], [ -122.425781691782262, 37.731929785903453 ], [ -122.425919712835267, 37.7318157721811 ], [ -122.426095455082049, 37.731670597839134 ], [ -122.426103813784607, 37.731651546856703 ], [ -122.425563267495079, 37.731578779884757 ], [ -122.424987445865384, 37.731559842008934 ], [ -122.424645313557747, 37.731548588455745 ], [ -122.424553925961277, 37.731549131439351 ], [ -122.423920024119482, 37.731552895324626 ], [ -122.422820482616018, 37.731641033131751 ], [ -122.4217053213399, 37.731806501069016 ], [ -122.421718952826723, 37.731607777327895 ], [ -122.421730663628679, 37.731437047083624 ], [ -122.421736572397847, 37.731350897661805 ], [ -122.421730502033157, 37.73135209792499 ], [ -122.421753180203225, 37.731084669073972 ], [ -122.420944905836123, 37.73130857523288 ], [ -122.420964111358728, 37.730677319690216 ], [ -122.421017374424935, 37.730233164434182 ], [ -122.421083540431724, 37.729975107309578 ], [ -122.421182810911759, 37.729745420447983 ], [ -122.421440856780521, 37.729310979302717 ], [ -122.422503353186897, 37.729261991833724 ], [ -122.422942671063694, 37.729241733452504 ], [ -122.423797638913399, 37.729509407965139 ], [ -122.426024704407865, 37.729396982427588 ], [ -122.425963703408243, 37.728641021813154 ], [ -122.425289939392513, 37.728677480232541 ], [ -122.424688687008342, 37.728709478304708 ], [ -122.424198685585807, 37.728735553475715 ], [ -122.423740585686062, 37.728759904726353 ], [ -122.423739981620528, 37.728759936840348 ], [ -122.423584360438809, 37.728387748823138 ], [ -122.423508669179441, 37.728206721890601 ], [ -122.423335646405121, 37.727792365353856 ], [ -122.423246600371399, 37.727577430218268 ], [ -122.42312222730105, 37.727277221300007 ], [ -122.422840471987186, 37.726597116352892 ], [ -122.422840471972961, 37.726597115803735 ], [ -122.422342507113001, 37.725395089354791 ], [ -122.422342507091699, 37.725395088531037 ], [ -122.422043387171286, 37.724673029193113 ], [ -122.421846961042462, 37.724198859168652 ], [ -122.422830067678831, 37.723944194056337 ], [ -122.423577951775229, 37.723750454459399 ], [ -122.423577946908821, 37.723750386689403 ], [ -122.423806549912683, 37.723691155552039 ], [ -122.424897104558099, 37.722118378784295 ], [ -122.424918832446721, 37.722087021186233 ], [ -122.426022741606815, 37.720493850373522 ], [ -122.42602274262245, 37.720493849532829 ], [ -122.426022740865662, 37.720493848462766 ], [ -122.425664999767108, 37.720321846926637 ], [ -122.425479352547157, 37.72023258698875 ], [ -122.425478983024618, 37.720232472712645 ], [ -122.425478982693178, 37.720232473267444 ], [ -122.424800888544937, 37.7200238938456 ], [ -122.424032169669715, 37.719926258473294 ], [ -122.423736904433326, 37.71982328246721 ], [ -122.423554916906838, 37.719668150420404 ], [ -122.42348866361003, 37.719439263071187 ], [ -122.423536512782078, 37.718956983348477 ], [ -122.423452239422744, 37.718705588384864 ], [ -122.423023908872437, 37.718162895921928 ], [ -122.422885734123284, 37.71809195842259 ], [ -122.422642040011837, 37.718030648532753 ], [ -122.422472988486305, 37.717887379027822 ], [ -122.423530052223612, 37.717201923868423 ], [ -122.423774201550728, 37.717092319144726 ], [ -122.424022457403083, 37.717036533105109 ], [ -122.424258262597263, 37.717024059120966 ], [ -122.424528018591516, 37.717064916531086 ], [ -122.424656181661859, 37.717131894726805 ], [ -122.424687869969304, 37.717146210464122 ], [ -122.424942097378548, 37.717261064626705 ], [ -122.425177444570053, 37.717485960998928 ], [ -122.425411706342686, 37.717880972693159 ], [ -122.425605873732934, 37.718131895102509 ], [ -122.425834816801412, 37.718279388717512 ], [ -122.426390032692737, 37.718560604670934 ], [ -122.426390269596226, 37.718560725782737 ], [ -122.426390269927637, 37.718560725227924 ], [ -122.426961271013212, 37.717805116783104 ], [ -122.42757190808544, 37.716997501404578 ], [ -122.427571950566247, 37.71699744604517 ], [ -122.427580759648123, 37.716957196731435 ], [ -122.427810303733594, 37.715908376197518 ], [ -122.427973857260341, 37.715161057331514 ], [ -122.425769401653085, 37.714425227128565 ], [ -122.42684686757103, 37.711165565601846 ], [ -122.426849963523566, 37.711156197850272 ], [ -122.426490332391396, 37.711021642816618 ], [ -122.425572654326643, 37.710701178146003 ], [ -122.425118753539664, 37.710464462528329 ], [ -122.424550951226621, 37.710101873835256 ], [ -122.423971204711407, 37.709678357430306 ], [ -122.423353331596459, 37.709250997624174 ], [ -122.423361076063088, 37.709234055437079 ], [ -122.423764571500399, 37.708341826236627 ], [ -122.423768921768058, 37.708341837314315 ], [ -122.423877843148816, 37.708342118381125 ], [ -122.425994336403065, 37.708310781158126 ], [ -122.426202061368684, 37.708307703589938 ], [ -122.427674537396697, 37.708285876646727 ], [ -122.428354189600114, 37.708305089572242 ], [ -122.429105296742478, 37.708326317449469 ], [ -122.429339593931815, 37.708323251263963 ], [ -122.430449087529411, 37.70830872506928 ], [ -122.43129493119001, 37.708297643647327 ], [ -122.432343143904646, 37.708295922670345 ], [ -122.433778966251097, 37.708293550010708 ], [ -122.434148404329434, 37.708292936416406 ], [ -122.434985771421609, 37.708291542195809 ], [ -122.436248762964979, 37.708289427645774 ], [ -122.438728105043637, 37.708285237562031 ], [ -122.440497203989452, 37.708282215784038 ], [ -122.440711209177138, 37.708281848403743 ], [ -122.441349676712647, 37.708280750220105 ], [ -122.442106190726975, 37.708279444504022 ], [ -122.444671046569766, 37.708274980891133 ], [ -122.44478757213254, 37.708274776994109 ], [ -122.445691081512706, 37.708249897066402 ], [ -122.445824359823575, 37.708249470051783 ], [ -122.447227656612327, 37.708244965564717 ], [ -122.449798855530389, 37.708236668750466 ], [ -122.452354883587788, 37.708228365382816 ], [ -122.454322408844959, 37.70822193637261 ], [ -122.454560786889971, 37.708221155056798 ], [ -122.455565069973886, 37.708215472275576 ], [ -122.455732056485743, 37.708217630626116 ], [ -122.455902300127136, 37.708219830519191 ], [ -122.457184544230842, 37.70823461994447 ], [ -122.457915234279184, 37.708243041121499 ], [ -122.458505268317239, 37.708230883488149 ], [ -122.459062187698038, 37.708219405843458 ], [ -122.459727762092839, 37.708205685584353 ], [ -122.459848666151245, 37.708200651285779 ], [ -122.460029970068362, 37.708193102258491 ], [ -122.46108479110967, 37.708202670088042 ], [ -122.461227604731107, 37.708198848520283 ], [ -122.46139057300131, 37.708194487863473 ], [ -122.461950847900269, 37.70819992107895 ], [ -122.462093547900324, 37.708193760482068 ], [ -122.462235775065267, 37.708187619950934 ], [ -122.462590335310921, 37.70818746608122 ], [ -122.462764782467445, 37.70818738953367 ], [ -122.463939046778805, 37.708202838503169 ], [ -122.464345228799317, 37.708207403972743 ], [ -122.464812351274205, 37.708212652628795 ], [ -122.465768040492705, 37.708210264664245 ], [ -122.466623471256085, 37.708208120693818 ], [ -122.468612008025019, 37.708203113190002 ], [ -122.468634481973396, 37.708203081190824 ], [ -122.468943918748693, 37.708202372165289 ], [ -122.468469108419114, 37.708682445380624 ], [ -122.468442186669975, 37.708710365091008 ], [ -122.46815544621434, 37.708936581998181 ], [ -122.467717389066479, 37.709282172736899 ], [ -122.467202952111606, 37.709607827013201 ], [ -122.467161376112116, 37.709634146029515 ], [ -122.466791287837083, 37.709825747176509 ], [ -122.465994326901708, 37.710153924678359 ], [ -122.464766855714529, 37.710481381431755 ], [ -122.464363457907425, 37.710527001097098 ], [ -122.464175624997623, 37.710548242222522 ], [ -122.462893196389857, 37.710562754126848 ], [ -122.462561257753663, 37.710567393100142 ], [ -122.462558461843784, 37.710567432270715 ], [ -122.462554670587338, 37.71117015272295 ], [ -122.462545712573657, 37.711171028559598 ], [ -122.462063256954337, 37.711372729296876 ], [ -122.462570051320355, 37.711370301468136 ], [ -122.462581299448402, 37.712273520359908 ], [ -122.462580658473911, 37.712523033743715 ], [ -122.46256899895387, 37.713150165038506 ], [ -122.462554985317752, 37.713903926321834 ], [ -122.462588724439357, 37.714029538822885 ], [ -122.462602039439204, 37.714307653173123 ], [ -122.462607827629938, 37.714883950429027 ], [ -122.462615213395566, 37.71561930379395 ], [ -122.462620481465109, 37.716143794339722 ], [ -122.46262477066405, 37.716570820282072 ], [ -122.462633312624419, 37.717421207682776 ], [ -122.462638771382373, 37.717866433871905 ], [ -122.462639625007327, 37.717936035524978 ], [ -122.46263854285489, 37.718211149542661 ], [ -122.462244936309219, 37.718219524954932 ], [ -122.462245294921956, 37.718767790464547 ], [ -122.46226246554707, 37.719928593578096 ], [ -122.462254399326852, 37.72000857322422 ], [ -122.462264896007014, 37.721694985883083 ], [ -122.46226548731488, 37.721827794893429 ], [ -122.462270801151405, 37.723021081969392 ], [ -122.462273544585969, 37.723637216450626 ], [ -122.462276364972297, 37.724270607125867 ], [ -122.462276854232826, 37.724380477322995 ], [ -122.462279017236384, 37.724866230039204 ], [ -122.462279803400207, 37.725042766873585 ], [ -122.462280269039496, 37.725147301470763 ], [ -122.462212628293415, 37.725265491161082 ], [ -122.462561310804759, 37.725391067720956 ], [ -122.462815474472038, 37.725482412532848 ], [ -122.462686726995429, 37.725774916753664 ], [ -122.46262180837526, 37.726335258944708 ], [ -122.462565711889994, 37.726822959746293 ], [ -122.462545638153259, 37.726997475605309 ], [ -122.462546035395761, 37.727106268284892 ], [ -122.462547067236983, 37.727225353979733 ], [ -122.462615761322709, 37.727529244060726 ], [ -122.462714226912738, 37.727738412010282 ], [ -122.462888304160828, 37.72805552868347 ], [ -122.46288814852555, 37.72805550243347 ], [ -122.462888197868679, 37.72805558978888 ], [ -122.462703368909061, 37.728024299159358 ], [ -122.462378284725844, 37.728011300874932 ], [ -122.462089038987699, 37.728047063364642 ], [ -122.461824751209633, 37.728118912555594 ], [ -122.46142222273329, 37.728330263725837 ], [ -122.460815856121073, 37.72868098095028 ], [ -122.461139723962631, 37.728765209783546 ], [ -122.461378769207158, 37.728787306916686 ], [ -122.461378549868385, 37.728787330621202 ], [ -122.461139150327014, 37.728765372338408 ], [ -122.460815517748841, 37.728680936862041 ], [ -122.460412720770037, 37.728739179843977 ], [ -122.460002483633559, 37.728739437629635 ], [ -122.460023966205156, 37.729195728883113 ], [ -122.459983752683073, 37.729522472170189 ], [ -122.459930163903692, 37.729706628889033 ], [ -122.459878403068345, 37.729884503080157 ], [ -122.459783802497654, 37.730109540441724 ], [ -122.459563710785162, 37.730368588301999 ], [ -122.459352156941819, 37.730515761910091 ], [ -122.459193144317396, 37.730602954490251 ], [ -122.458688935885434, 37.730751520241796 ], [ -122.458472470688633, 37.730752827866375 ], [ -122.458222500186622, 37.73077738189027 ], [ -122.457474238952088, 37.730997495241198 ], [ -122.456607498028021, 37.731187733113579 ], [ -122.455762555159509, 37.731348802743547 ], [ -122.454915878760758, 37.731464997636976 ], [ -122.453590911574565, 37.731488405776453 ], [ -122.453411343594681, 37.731530646828205 ], [ -122.451146666771351, 37.731536846888879 ], [ -122.448866265260094, 37.731542959168735 ], [ -122.447291821211039, 37.731547153756608 ], [ -122.446578400413941, 37.7315490470612 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.444294941941365, 37.731069863889161 ], [ -122.444298409699798, 37.730708221303239 ], [ -122.444291303797655, 37.729896860703782 ], [ -122.444284269547836, 37.729093639959018 ], [ -122.44427751391413, 37.728322242080615 ], [ -122.444276984301908, 37.728261755504967 ], [ -122.444131311025359, 37.72831838217401 ], [ -122.443973112888827, 37.728379877183251 ], [ -122.442954953118544, 37.728915555981267 ], [ -122.442595296877187, 37.729101680603421 ], [ -122.442082956691792, 37.729308363921405 ], [ -122.442006614261629, 37.729375267903364 ], [ -122.442010214558891, 37.729910949382834 ], [ -122.440664990612632, 37.729919329476168 ], [ -122.43969146797258, 37.730323477880063 ], [ -122.439686065703896, 37.730325720705899 ], [ -122.438692929843569, 37.730737998359416 ], [ -122.437166749161392, 37.731365897534992 ], [ -122.436835967120587, 37.731467212874229 ], [ -122.436678949019353, 37.731538395224071 ], [ -122.436668162612392, 37.731543298105045 ], [ -122.435457687984979, 37.732093484109448 ], [ -122.434992781477959, 37.732295287547394 ], [ -122.434989951388332, 37.732296516038524 ], [ -122.434823956379304, 37.732249430441868 ], [ -122.434644432402948, 37.732322139093768 ], [ -122.434512093568429, 37.732323526771246 ], [ -122.434440716858973, 37.732534804028113 ], [ -122.432720773871381, 37.733187185661393 ], [ -122.432572047381726, 37.733243596430682 ], [ -122.43132522808267, 37.733716495194997 ], [ -122.430931197463138, 37.733865940717834 ], [ -122.430928172484656, 37.733867087971888 ], [ -122.430400796753801, 37.734118700718604 ], [ -122.429799382942633, 37.734405632509876 ], [ -122.429797927680781, 37.734406452684077 ], [ -122.429606994304805, 37.734482866043706 ], [ -122.429365174143101, 37.734579643964238 ], [ -122.428766264762999, 37.73481932892178 ], [ -122.42843713263963, 37.734951046304573 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.428436980709748, 37.734950949626743 ], [ -122.427838803054428, 37.734693898279502 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":19,\"ZIP_CODE\":94116,\"ID\":94116},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.491334899611488, 37.737293023539365 ], [ -122.491211452414888, 37.737221069184358 ], [ -122.491162508402653, 37.737157782139001 ], [ -122.490686267381363, 37.736541968564268 ], [ -122.490552748713441, 37.736251597078756 ], [ -122.490521485540071, 37.736028873151284 ], [ -122.490460674310157, 37.735842274774221 ], [ -122.490270108429897, 37.735606337180243 ], [ -122.490526224357907, 37.735470000646821 ], [ -122.490725734916396, 37.735399781312303 ], [ -122.490973609504408, 37.735348171552815 ], [ -122.491320518441114, 37.735327019185135 ], [ -122.491730212101302, 37.735301758325164 ], [ -122.491805731315921, 37.735279805981911 ], [ -122.492142715245791, 37.735181846965403 ], [ -122.492330945602859, 37.73504482611267 ], [ -122.492843999903116, 37.734842083695071 ], [ -122.493274340840912, 37.734785121286002 ], [ -122.493406966558723, 37.734856121008917 ], [ -122.493407285267097, 37.734856242527158 ], [ -122.493857076611491, 37.73502785037892 ], [ -122.493857150107345, 37.735028823489081 ], [ -122.4940149282546, 37.735024137316628 ], [ -122.494014877350082, 37.735023413250019 ], [ -122.493941607430031, 37.733981341508411 ], [ -122.493941605376719, 37.733981316545631 ], [ -122.494403119131789, 37.733960247035334 ], [ -122.496448643026866, 37.733877460645239 ], [ -122.496635991376749, 37.733770846301013 ], [ -122.496635997172902, 37.733770843181105 ], [ -122.496635997165527, 37.733770842906516 ], [ -122.496636262010625, 37.733770813420726 ], [ -122.496636264393828, 37.733770812006838 ], [ -122.496807213285692, 37.733750829171804 ], [ -122.4970611531011, 37.733738715676616 ], [ -122.497251243304476, 37.7337296478455 ], [ -122.497675024009141, 37.73378860832171 ], [ -122.497707486783483, 37.733793124978028 ], [ -122.498050148013945, 37.73385852689691 ], [ -122.49837749236211, 37.733942575660954 ], [ -122.498483756692679, 37.733969859923228 ], [ -122.498948533172893, 37.734128137325406 ], [ -122.499086082983126, 37.734239092175393 ], [ -122.499180283, 37.734301056816975 ], [ -122.499196335607635, 37.734311616338935 ], [ -122.499430330098079, 37.734471850699563 ], [ -122.500140914702612, 37.734827630984967 ], [ -122.500289079616721, 37.734896880574155 ], [ -122.500571951887039, 37.735029088020617 ], [ -122.500650675572956, 37.735059919015036 ], [ -122.500888239775747, 37.73515295885273 ], [ -122.500970549161394, 37.735174012383368 ], [ -122.501097483002411, 37.735206481213844 ], [ -122.501097483348147, 37.735206481207975 ], [ -122.501214897179494, 37.735242075875206 ], [ -122.501291847576297, 37.735265403511711 ], [ -122.501555214836827, 37.735330188314435 ], [ -122.501556481916552, 37.735330499723666 ], [ -122.502021315734993, 37.73539219398544 ], [ -122.50206735537283, 37.735396211199387 ], [ -122.50249812714712, 37.735433799348435 ], [ -122.503104759187963, 37.735466491452826 ], [ -122.504189206002195, 37.735470928034005 ], [ -122.505257352091178, 37.735475287872006 ], [ -122.505711404999388, 37.735473569905054 ], [ -122.506142139643117, 37.735471938745221 ], [ -122.506541776696594, 37.735470423847289 ], [ -122.506717419598758, 37.735469757763546 ], [ -122.506718392445464, 37.735463437680252 ], [ -122.508528202252251, 37.735448935792135 ], [ -122.50851723304288, 37.735491014374531 ], [ -122.508503948199092, 37.735575367390467 ], [ -122.508509635115971, 37.735593812897228 ], [ -122.508534142132689, 37.735604726176433 ], [ -122.508524280726746, 37.73562377983793 ], [ -122.508510961647445, 37.73564289244193 ], [ -122.50850628586997, 37.735661857701452 ], [ -122.508508623546277, 37.735748348011157 ], [ -122.508514421788533, 37.735770911828716 ], [ -122.508520015957387, 37.735785925179286 ], [ -122.508510052128926, 37.735801203199763 ], [ -122.508477309195854, 37.735805538751634 ], [ -122.508464092111296, 37.735828426720389 ], [ -122.508469983447981, 37.735854422414455 ], [ -122.508474053027456, 37.735877015976307 ], [ -122.508461856322953, 37.735937657650084 ], [ -122.508465576123427, 37.736075286791404 ], [ -122.508449773613393, 37.7361304959501 ], [ -122.508448690994484, 37.73621841805182 ], [ -122.508451371852146, 37.73631760760221 ], [ -122.508490175464019, 37.736473524223285 ], [ -122.508516049607124, 37.736662968752803 ], [ -122.508603414533738, 37.736823895128929 ], [ -122.508662443511639, 37.736960238247853 ], [ -122.50867888000765, 37.737056446014627 ], [ -122.508719731680841, 37.737160135129862 ], [ -122.508734334501312, 37.737316464525655 ], [ -122.508765426520924, 37.737442982668369 ], [ -122.508785682509867, 37.737552516867808 ], [ -122.508770696382811, 37.737637929009765 ], [ -122.508786455872283, 37.737709082028033 ], [ -122.508760949000333, 37.737789179556565 ], [ -122.508763268491435, 37.737874983674544 ], [ -122.50874159008174, 37.737968750866465 ], [ -122.5087476021437, 37.738191154145746 ], [ -122.508756496465509, 37.738392219395308 ], [ -122.508803998140891, 37.738613915588019 ], [ -122.508803138582451, 37.738710074824468 ], [ -122.508864043688376, 37.738915747292424 ], [ -122.5088940788266, 37.739003138823669 ], [ -122.508897419160689, 37.739126696329038 ], [ -122.50889009011712, 37.73923944778879 ], [ -122.508822474607925, 37.739425335507839 ], [ -122.508791610932704, 37.73956321107876 ], [ -122.508779986132339, 37.739708999607267 ], [ -122.508780357262495, 37.739722728217863 ], [ -122.50877604314833, 37.739755078870481 ], [ -122.508764442526711, 37.739773818808054 ], [ -122.508752731253111, 37.739788440149496 ], [ -122.508741502089606, 37.739820908689246 ], [ -122.508753863186314, 37.739830312411385 ], [ -122.508778826631357, 37.739858043722968 ], [ -122.508779958572916, 37.739899915709536 ], [ -122.508761814014846, 37.7399325021469 ], [ -122.508762704722017, 37.739965450811162 ], [ -122.508770491923499, 37.739997595154925 ], [ -122.508776179583251, 37.740016040354824 ], [ -122.508747792652912, 37.740053608631989 ], [ -122.508748423567027, 37.740076947268811 ], [ -122.508737212914269, 37.74011010223709 ], [ -122.508737713932007, 37.740128635860302 ], [ -122.508745501147203, 37.740160780479421 ], [ -122.50873947678167, 37.740193847030952 ], [ -122.508723191132987, 37.740231208721582 ], [ -122.508723822048324, 37.740254547632397 ], [ -122.508732128829621, 37.740305911755783 ], [ -122.508742872573777, 37.740319463533034 ], [ -122.50877339326972, 37.74036083476765 ], [ -122.508810829778682, 37.740402088090121 ], [ -122.508829008239559, 37.740434742019097 ], [ -122.508813223629531, 37.740490637616951 ], [ -122.508861162085935, 37.740536519108112 ], [ -122.508850322907236, 37.740583402687079 ], [ -122.508858110211349, 37.74061554702174 ], [ -122.508869614883267, 37.740657242433585 ], [ -122.508870616992439, 37.740694309675469 ], [ -122.508866692231578, 37.740741075644983 ], [ -122.508879295085322, 37.740759402667216 ], [ -122.508914260413761, 37.740773228492962 ], [ -122.508932680259875, 37.740814805724348 ], [ -122.508928625962866, 37.740856766401905 ], [ -122.508929256940604, 37.740880105034634 ], [ -122.508935445433963, 37.740917083849979 ], [ -122.508953234348851, 37.740935322719352 ], [ -122.508983384240864, 37.740962965288134 ], [ -122.509008589756519, 37.740999619859899 ], [ -122.509026879459341, 37.741036392343048 ], [ -122.509023085376484, 37.741087963041082 ], [ -122.509036559926997, 37.741138552557295 ], [ -122.509054739012925, 37.741171206441557 ], [ -122.509067601488525, 37.741199144021785 ], [ -122.5090958931589, 37.741222010744757 ], [ -122.509133200021253, 37.741258459227247 ], [ -122.50918506342488, 37.741257574887214 ], [ -122.509205212680868, 37.74129912258816 ], [ -122.509246237071991, 37.741345121825837 ], [ -122.509242554147008, 37.741400811115767 ], [ -122.509226769642837, 37.741456706760005 ], [ -122.50922790187596, 37.741498579280254 ], [ -122.509216431361367, 37.741522123988162 ], [ -122.509199662741182, 37.741541638830299 ], [ -122.509177093246976, 37.741602457387373 ], [ -122.509208116202714, 37.741662362111221 ], [ -122.509186903920153, 37.741709422591256 ], [ -122.509189298261234, 37.741797972091135 ], [ -122.509154236925113, 37.741844581962283 ], [ -122.509149811487788, 37.741872814035773 ], [ -122.509151073599824, 37.741919491290815 ], [ -122.509123057336595, 37.741970788250718 ], [ -122.509100375943362, 37.742027488213331 ], [ -122.509101006984949, 37.742050826840561 ], [ -122.509092137481943, 37.742106604551111 ], [ -122.509063378372446, 37.742130444293039 ], [ -122.50905227925395, 37.742167717855253 ], [ -122.509053541309754, 37.742214395109038 ], [ -122.509066774807934, 37.742256061019717 ], [ -122.509073353587084, 37.742307454847364 ], [ -122.509057438852423, 37.742358545723746 ], [ -122.509051785633744, 37.74240534060808 ], [ -122.509053177611264, 37.742456822871205 ], [ -122.509073957471671, 37.742521709498256 ], [ -122.509091988431635, 37.742548871927937 ], [ -122.509110538400861, 37.742595254410702 ], [ -122.509106614374701, 37.74264202008699 ], [ -122.509124533674893, 37.742665064210797 ], [ -122.50913085233006, 37.742706847741204 ], [ -122.509143845016951, 37.742739590042241 ], [ -122.509166932263881, 37.742761859020533 ], [ -122.50919239764383, 37.742808123575315 ], [ -122.509181187122891, 37.742841278564811 ], [ -122.509159213148223, 37.742860195402194 ], [ -122.509185214866079, 37.742990233896222 ], [ -122.509180659445065, 37.743013660954873 ], [ -122.509194023430766, 37.743060131850363 ], [ -122.509212184156269, 37.743092099550239 ], [ -122.509235029879775, 37.74310544465424 ], [ -122.509248023028022, 37.743138186935724 ], [ -122.509243207416404, 37.743152004255805 ], [ -122.509249786389432, 37.743203397791333 ], [ -122.509267947166521, 37.743235365207241 ], [ -122.50930010317154, 37.743337142122911 ], [ -122.509302627635748, 37.743430496606479 ], [ -122.509334412528517, 37.743518545183825 ], [ -122.509352573411604, 37.743550512310456 ], [ -122.509365807315376, 37.74359217817824 ], [ -122.509378299405142, 37.743606386832958 ], [ -122.50939077259082, 37.743619909062254 ], [ -122.509397091484274, 37.74366169284626 ], [ -122.509409824223226, 37.743684825098981 ], [ -122.509422427717567, 37.743703152328656 ], [ -122.509416403366075, 37.743736218895179 ], [ -122.509417405789478, 37.743773286113324 ], [ -122.509442110915671, 37.74379140724664 ], [ -122.509460290482991, 37.743824061059733 ], [ -122.509467837052512, 37.743847281472782 ], [ -122.509478581442835, 37.743860833174935 ], [ -122.509497132326601, 37.74390721558072 ], [ -122.509505050216035, 37.743944165138849 ], [ -122.509522969938899, 37.743967208647867 ], [ -122.509535572817498, 37.743985535876377 ], [ -122.509565966198053, 37.744022101859116 ], [ -122.509567729859413, 37.744087312700294 ], [ -122.50958033276946, 37.744105639923646 ], [ -122.509604330613158, 37.744161543870057 ], [ -122.509617063502262, 37.744184676098449 ], [ -122.509642400382361, 37.744226135524023 ], [ -122.50963585625108, 37.74423998206241 ], [ -122.509631802164265, 37.744281942740074 ], [ -122.509637749945398, 37.744309998183795 ], [ -122.509655669451874, 37.744333041677137 ], [ -122.509651355796905, 37.744365392330948 ], [ -122.509652358345974, 37.744402459543146 ], [ -122.509682770174749, 37.744439711928955 ], [ -122.509684274023797, 37.744495312746096 ], [ -122.509661202059561, 37.74453759777208 ], [ -122.509656906960942, 37.744570634854803 ], [ -122.509652462986452, 37.744598180504454 ], [ -122.509634336575232, 37.744631453474433 ], [ -122.509635079200464, 37.744658910667582 ], [ -122.509630653432438, 37.744687142751694 ], [ -122.509607841992747, 37.744739038046106 ], [ -122.509616019927876, 37.744785597059909 ], [ -122.509592687920644, 37.744818272053067 ], [ -122.509529982316536, 37.744866040185691 ], [ -122.509506149350315, 37.744880181550315 ], [ -122.509466886438972, 37.744899393264149 ], [ -122.509443442946818, 37.744927949648201 ], [ -122.509461493197179, 37.744955798714784 ], [ -122.509462365703229, 37.744988060641646 ], [ -122.50945818152897, 37.745025216299389 ], [ -122.509441783806565, 37.745058459754802 ], [ -122.509435740789897, 37.745090839884973 ], [ -122.509432447662917, 37.745160944171666 ], [ -122.509397957547932, 37.745292700871097 ], [ -122.509353566936483, 37.745697951409504 ], [ -122.509335859850864, 37.745938614544613 ], [ -122.509427065710852, 37.746369022956515 ], [ -122.509460469864493, 37.746452922841264 ], [ -122.509505472532595, 37.746581950726871 ], [ -122.509517033840751, 37.746625705328505 ], [ -122.509530306206301, 37.746668744003671 ], [ -122.509540138624189, 37.746712528086029 ], [ -122.50955170066463, 37.746756282671512 ], [ -122.509559804532088, 37.746800096229578 ], [ -122.509567926974654, 37.746844596216341 ], [ -122.509574301938841, 37.746888439256921 ], [ -122.50958069512302, 37.74693296845772 ], [ -122.509587070448092, 37.746976811491038 ], [ -122.509590005803872, 37.747021399934077 ], [ -122.509594670781297, 37.747065958880377 ], [ -122.509597084346751, 37.747155194721792 ], [ -122.509772363824212, 37.747496265262193 ], [ -122.509783516941724, 37.747524918382545 ], [ -122.509806418075982, 37.747604190625147 ], [ -122.509810433332206, 37.747624724253029 ], [ -122.509816158624176, 37.747644542243904 ], [ -122.509820173546927, 37.747665076151769 ], [ -122.509822459192577, 37.747685639827814 ], [ -122.509826455889467, 37.747705487025407 ], [ -122.509833313854301, 37.747767177210982 ], [ -122.509835523477918, 37.747848862313226 ], [ -122.509834351235696, 37.747869484689943 ], [ -122.509833161123524, 37.747889420899924 ], [ -122.509831989218355, 37.747910042996011 ], [ -122.509829088027573, 37.747930694860493 ], [ -122.509827897905609, 37.74795063079565 ], [ -122.509824997065294, 37.747971282928546 ], [ -122.509778471867705, 37.748297593791527 ], [ -122.509717126110829, 37.748715495135052 ], [ -122.509711528533515, 37.748764349563523 ], [ -122.509708924691878, 37.748795984010961 ], [ -122.509706339422777, 37.748828305162085 ], [ -122.509705464552383, 37.748859910397449 ], [ -122.509704608248597, 37.748892202061995 ], [ -122.50970373303835, 37.748923807577505 ], [ -122.509705738305371, 37.748997941669188 ], [ -122.508363946491727, 37.74907680068609 ], [ -122.507940323213063, 37.749101694546454 ], [ -122.507941547211104, 37.749110937340276 ], [ -122.507539948063609, 37.749128748547342 ], [ -122.50729515180268, 37.749139604462776 ], [ -122.507286878880223, 37.749139971457424 ], [ -122.50728687853443, 37.749139971463322 ], [ -122.507286886587565, 37.749140090270473 ], [ -122.507286887279179, 37.749140090258685 ], [ -122.507416228284868, 37.751003284446853 ], [ -122.506347526734444, 37.751051944046772 ], [ -122.505276576525688, 37.751093151070108 ], [ -122.504203312772219, 37.751143789774702 ], [ -122.503133002600279, 37.751190357338089 ], [ -122.502060320882052, 37.751239765057655 ], [ -122.500987488435712, 37.75128639412403 ], [ -122.499914456955352, 37.751333766429582 ], [ -122.499914122812314, 37.751333781439868 ], [ -122.49937768349335, 37.751357494487337 ], [ -122.498842248697912, 37.751381160579498 ], [ -122.498442545251379, 37.751398004902029 ], [ -122.497770416046876, 37.751426327269549 ], [ -122.497770415009455, 37.751426327287128 ], [ -122.496702184257288, 37.751476079301845 ], [ -122.49581055415868, 37.751518288226762 ], [ -122.495630257074851, 37.751526822572394 ], [ -122.49509879355503, 37.751544749172808 ], [ -122.495098259533435, 37.751544767553312 ], [ -122.49456080780611, 37.751568259028382 ], [ -122.493488045146222, 37.751613904571322 ], [ -122.492417458516726, 37.751662024433998 ], [ -122.491344845233968, 37.751710225381885 ], [ -122.490270381873529, 37.751758499707634 ], [ -122.48919356840311, 37.751806869260896 ], [ -122.488143190546495, 37.751854042116229 ], [ -122.487053067380344, 37.751903269835523 ], [ -122.48598818277614, 37.751950794949664 ], [ -122.48491485069583, 37.751998192013865 ], [ -122.483844155588841, 37.75204546291927 ], [ -122.48276958366516, 37.752092894785363 ], [ -122.48170210782861, 37.752140004119113 ], [ -122.480628878786973, 37.752187357305665 ], [ -122.479557564859533, 37.752234616335052 ], [ -122.478486061226221, 37.752281873458429 ], [ -122.4774149778289, 37.752329102778496 ], [ -122.476292240229284, 37.752378599147249 ], [ -122.476292239846927, 37.752378597780194 ], [ -122.476291890478677, 37.752378612978674 ], [ -122.476291890529794, 37.752378614900692 ], [ -122.475276421588944, 37.752423947680597 ], [ -122.474740669236724, 37.752447861779451 ], [ -122.474205182950101, 37.752471761013865 ], [ -122.473133075589757, 37.752519603580488 ], [ -122.472007574280582, 37.752569817871482 ], [ -122.470907529168414, 37.752615836450325 ], [ -122.471122146869178, 37.753216025593119 ], [ -122.471174197654705, 37.753356133265093 ], [ -122.471187692098596, 37.753493865266243 ], [ -122.471190660490961, 37.753605743703318 ], [ -122.471171093123701, 37.753733616626192 ], [ -122.471132268263645, 37.753877430013475 ], [ -122.471079638048607, 37.753995444111091 ], [ -122.471036498728921, 37.754100284913058 ], [ -122.471016495996267, 37.754217122306045 ], [ -122.471019321832557, 37.754265617625428 ], [ -122.471049094699751, 37.75451479556704 ], [ -122.471016758783364, 37.754797170870887 ], [ -122.470916907310823, 37.75477064959378 ], [ -122.470428100059209, 37.754750285613063 ], [ -122.470075601814969, 37.754705621480454 ], [ -122.470018818714578, 37.754692394187302 ], [ -122.469719537880266, 37.754630546382018 ], [ -122.469359681800768, 37.754536443616175 ], [ -122.469229538348586, 37.754492641945887 ], [ -122.469187312711114, 37.754478429881637 ], [ -122.469043420308736, 37.754428685878558 ], [ -122.468949194118821, 37.754383817642378 ], [ -122.468837396357713, 37.754298754678686 ], [ -122.46875232271276, 37.754192126033097 ], [ -122.468629004224212, 37.753985873398783 ], [ -122.46853368295227, 37.753778395652951 ], [ -122.468440863346331, 37.753541228150766 ], [ -122.468335071492149, 37.753243730756957 ], [ -122.468181079371874, 37.752955569756089 ], [ -122.468031369650532, 37.75278739056494 ], [ -122.467915171465805, 37.752734664618352 ], [ -122.467804242122298, 37.752723264514827 ], [ -122.467642627702773, 37.752756208616617 ], [ -122.46660049988607, 37.752803078204508 ], [ -122.465533177566329, 37.752851071595934 ], [ -122.464462034762747, 37.752899226938901 ], [ -122.464462034791779, 37.752899228037201 ], [ -122.464462004720289, 37.752899229087859 ], [ -122.463398931733735, 37.75298135079364 ], [ -122.463398833027838, 37.752981215914545 ], [ -122.463165642273253, 37.752743295475163 ], [ -122.462772959403608, 37.752449458308092 ], [ -122.462467546932245, 37.752187424267689 ], [ -122.462176881380387, 37.751866525322576 ], [ -122.461851812215116, 37.751624201717213 ], [ -122.461621806880288, 37.751517360697065 ], [ -122.461523602837886, 37.75150356127773 ], [ -122.461357402458418, 37.751255115112428 ], [ -122.461130726036544, 37.75091814728394 ], [ -122.460646598340617, 37.749905586886342 ], [ -122.460488100882344, 37.749710225074402 ], [ -122.459910793377546, 37.749171745450226 ], [ -122.459625716264128, 37.748941671345293 ], [ -122.459200706652084, 37.748504001538066 ], [ -122.458941590023073, 37.748201275736406 ], [ -122.458816156308444, 37.748040115735854 ], [ -122.458785941599359, 37.747811900102363 ], [ -122.458856680033747, 37.7476570668547 ], [ -122.458970102890007, 37.747514391535915 ], [ -122.459174106111192, 37.747286620942795 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.461381097416904, 37.745569497508143 ], [ -122.463685995946705, 37.743749409879626 ], [ -122.464736798394597, 37.743658625608425 ], [ -122.465806989998114, 37.743574050343277 ], [ -122.466043187720601, 37.743555382675872 ], [ -122.4668594971902, 37.743490862582995 ], [ -122.467747743753378, 37.743412622582262 ], [ -122.468561098363182, 37.741503345390441 ], [ -122.468671688881855, 37.741487738762387 ], [ -122.46903011455143, 37.741472060437133 ], [ -122.470104939284042, 37.741425112172415 ], [ -122.471225939871744, 37.741383106823285 ], [ -122.471094271054184, 37.739515149668783 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.472088826157801, 37.737598476381557 ], [ -122.473161338869033, 37.737550437503423 ], [ -122.47327754880429, 37.737518149435381 ], [ -122.474232714891599, 37.737476308128976 ], [ -122.475288554960258, 37.737454742960537 ], [ -122.476368270747585, 37.737407342172453 ], [ -122.477443897267278, 37.737360110526431 ], [ -122.478500149938654, 37.73731300537402 ], [ -122.479601690542523, 37.737266045133403 ], [ -122.480654887744791, 37.73721905491567 ], [ -122.481727269492282, 37.73717192680251 ], [ -122.484981330342137, 37.737106613405395 ], [ -122.485055057260084, 37.737081691563745 ], [ -122.485398323256604, 37.736886930845941 ], [ -122.485767077957547, 37.736757243866982 ], [ -122.486239320534182, 37.736758103009961 ], [ -122.486865148034397, 37.736893072836374 ], [ -122.487473746906744, 37.73696000969079 ], [ -122.487883946457515, 37.736972903025247 ], [ -122.488261361358497, 37.736979897727672 ], [ -122.488518054177234, 37.737001890327612 ], [ -122.488933343471572, 37.73715804150379 ], [ -122.48917282500021, 37.737387715271872 ], [ -122.489310346380378, 37.737544144541232 ], [ -122.489559638156635, 37.737725144708122 ], [ -122.489794342484018, 37.737821521499782 ], [ -122.489970938868055, 37.737956983380037 ], [ -122.490364187143001, 37.737929559098433 ], [ -122.490475969759899, 37.737832474268195 ], [ -122.490695166321217, 37.737732995785407 ], [ -122.490894410355324, 37.737610557460023 ], [ -122.491113859834144, 37.737397356998116 ], [ -122.491198309070924, 37.737311342176547 ], [ -122.491334899611488, 37.737293023539365 ] ], [ [ -122.459625733970427, 37.748941660612438 ], [ -122.459625856985753, 37.74894158961829 ], [ -122.459625856632712, 37.748941589349464 ], [ -122.459625733970427, 37.748941660612438 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":20,\"ZIP_CODE\":94114,\"ID\":94114},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.451366939667807, 37.758575670531805 ], [ -122.451372847861364, 37.758604122239966 ], [ -122.451553087455522, 37.759472365763493 ], [ -122.449214958651069, 37.759610853827809 ], [ -122.448576884007139, 37.759648639321185 ], [ -122.448534508559618, 37.759045234831405 ], [ -122.448080121328474, 37.759090506458826 ], [ -122.447681986744499, 37.759189835272018 ], [ -122.447394862769713, 37.759372842727622 ], [ -122.447157396221257, 37.759660681065917 ], [ -122.446952816919733, 37.760170129150154 ], [ -122.446866213158088, 37.76042156830691 ], [ -122.446579328750119, 37.76090155472 ], [ -122.446433636964471, 37.761006719141555 ], [ -122.446409895656728, 37.761094344377824 ], [ -122.44652973647915, 37.761396213495424 ], [ -122.446783403627563, 37.761781413728215 ], [ -122.446783402935864, 37.761781413739634 ], [ -122.44642928686055, 37.76181245987528 ], [ -122.446384044294348, 37.761816426422072 ], [ -122.446401427721412, 37.762069683149655 ], [ -122.446391621016517, 37.76223195154369 ], [ -122.44628722735257, 37.762310164748847 ], [ -122.446246565080784, 37.762246873735464 ], [ -122.446167833254592, 37.762219287792306 ], [ -122.445930590545728, 37.762233223779305 ], [ -122.445825769194499, 37.762264137786076 ], [ -122.445692026290132, 37.762347953618033 ], [ -122.444663866724483, 37.763326501903514 ], [ -122.44441170822752, 37.76398692869914 ], [ -122.443434935848089, 37.763875622244342 ], [ -122.443247528217242, 37.763690608015906 ], [ -122.442951036556252, 37.763677959114567 ], [ -122.44294975946913, 37.764109072128207 ], [ -122.443199827408847, 37.764468898600072 ], [ -122.443215275433289, 37.764557545459958 ], [ -122.443278634379993, 37.764726164248771 ], [ -122.443263297846215, 37.765217752592115 ], [ -122.443347278899211, 37.765332794099429 ], [ -122.443210588891787, 37.765339994614024 ], [ -122.442086687309455, 37.765324419445463 ], [ -122.441931912880605, 37.765295099009755 ], [ -122.441241683591315, 37.765270858517731 ], [ -122.439501839688162, 37.766575524599709 ], [ -122.438555364477878, 37.766297386980668 ], [ -122.438386133953244, 37.766664738151036 ], [ -122.438291383601225, 37.766725780787596 ], [ -122.438219625977609, 37.766701399454838 ], [ -122.437879374385105, 37.76661079550852 ], [ -122.437684727134425, 37.766587115085294 ], [ -122.4374853951944, 37.766644323028956 ], [ -122.437333800177683, 37.766762171805773 ], [ -122.437288601235366, 37.766922046842083 ], [ -122.437291829491301, 37.767045826510717 ], [ -122.43729899343451, 37.767221930124791 ], [ -122.436980399947345, 37.767244348801164 ], [ -122.436478247038266, 37.767274662557512 ], [ -122.436625219679314, 37.768918977544402 ], [ -122.436588267341477, 37.76900080227221 ], [ -122.435794213398353, 37.769058453615109 ], [ -122.43489319213792, 37.769113797429938 ], [ -122.433572207534368, 37.769178369516744 ], [ -122.432464341208131, 37.769249650838944 ], [ -122.431570380896062, 37.769307161387864 ], [ -122.431356624058139, 37.76932091157898 ], [ -122.430247361256178, 37.769392261627225 ], [ -122.429905643091914, 37.769407921549679 ], [ -122.429127985682982, 37.769456128057627 ], [ -122.428991181191208, 37.769394223715665 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.426683978032827, 37.769533626427062 ], [ -122.426309438735913, 37.769602597220121 ], [ -122.426902347012131, 37.769049368265463 ], [ -122.426693183304053, 37.767869018742559 ], [ -122.426693183296933, 37.76786901846797 ], [ -122.426615944414863, 37.767069730096857 ], [ -122.426572006355102, 37.766615035704866 ], [ -122.426538465047457, 37.766267922745747 ], [ -122.426518991136163, 37.766066400051031 ], [ -122.426490697526233, 37.765773605772118 ], [ -122.426381708477521, 37.764645690891633 ], [ -122.426381708399177, 37.764645687871273 ], [ -122.426317477898522, 37.763970002371671 ], [ -122.426228478902843, 37.763030870557735 ], [ -122.426224290129298, 37.762962298008539 ], [ -122.426204067323269, 37.762631239367813 ], [ -122.426191463523168, 37.762538363286502 ], [ -122.426149283375466, 37.762227539381186 ], [ -122.426149283368346, 37.762227539106618 ], [ -122.426145984060597, 37.762160358437526 ], [ -122.426076501815189, 37.761427133937481 ], [ -122.426076501462219, 37.761427133668562 ], [ -122.425985721165645, 37.760498692937027 ], [ -122.425920302092379, 37.759829613405913 ], [ -122.425900090784694, 37.759619242549249 ], [ -122.425843422635126, 37.759029397589842 ], [ -122.425766807348751, 37.758226828975253 ], [ -122.425766807341631, 37.758226828700685 ], [ -122.425689459367419, 37.757429672933782 ], [ -122.42568945931761, 37.757429671011749 ], [ -122.425645855643012, 37.756979041458848 ], [ -122.425612216172169, 37.756631383038098 ], [ -122.425491892915787, 37.755387845874338 ], [ -122.425457179889037, 37.755029078716305 ], [ -122.425425666412067, 37.754703378159874 ], [ -122.425302616947704, 37.753431591109177 ], [ -122.425302616566313, 37.753431589741957 ], [ -122.425152541686245, 37.751841785894676 ], [ -122.4251525416649, 37.751841785070944 ], [ -122.425122809405835, 37.751528900123922 ], [ -122.425075700414453, 37.751033151258099 ], [ -122.424999224155329, 37.750232718354916 ], [ -122.424999224133984, 37.75023271753119 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.427200037712353, 37.749295720374967 ], [ -122.429422761644645, 37.749157853016314 ], [ -122.431639579453815, 37.749028214467948 ], [ -122.433851103876819, 37.748895972089151 ], [ -122.436067172923103, 37.748762035575652 ], [ -122.438139804541592, 37.748636730271706 ], [ -122.438246126408771, 37.748664500736062 ], [ -122.440402009224243, 37.748642072742477 ], [ -122.440693299152798, 37.748639039270962 ], [ -122.4414080949674, 37.748610013081198 ], [ -122.441477149443557, 37.748591599630018 ], [ -122.441916701553239, 37.748474391279807 ], [ -122.442519008868075, 37.748183215938361 ], [ -122.442584207288078, 37.748079345098553 ], [ -122.443161821262549, 37.747504352694399 ], [ -122.443453448246288, 37.747477579218923 ], [ -122.443473512241141, 37.748002572121706 ], [ -122.443473812008847, 37.74801042178742 ], [ -122.443486020026867, 37.748345516454542 ], [ -122.443434635522365, 37.749091646853778 ], [ -122.44259888979461, 37.749156165729936 ], [ -122.442599224378554, 37.749160242994655 ], [ -122.442583534001741, 37.749266790173017 ], [ -122.442693363008104, 37.750413947729008 ], [ -122.442776988488347, 37.750697651678763 ], [ -122.442782695565214, 37.750773110847675 ], [ -122.442782889125965, 37.750775670827849 ], [ -122.442832455660607, 37.751431039472649 ], [ -122.442817470436609, 37.751454127059596 ], [ -122.442982766064986, 37.751545025906275 ], [ -122.442775383396778, 37.752377050296218 ], [ -122.442775038632988, 37.752378432471772 ], [ -122.442524333279991, 37.752382565152622 ], [ -122.442492558984341, 37.752606697266998 ], [ -122.442239114579749, 37.752829681730105 ], [ -122.442056693562165, 37.753070846058556 ], [ -122.441886398393621, 37.753365106672398 ], [ -122.441807410207034, 37.753470934450007 ], [ -122.441501225348532, 37.753721419417111 ], [ -122.441307496038533, 37.753879905149773 ], [ -122.441162634059779, 37.754090414508028 ], [ -122.44107583131526, 37.754178484324051 ], [ -122.440897310558796, 37.754492600197032 ], [ -122.440449630137792, 37.755065428273227 ], [ -122.440344935902942, 37.755227929193452 ], [ -122.440113197925641, 37.755606296679595 ], [ -122.440032113187002, 37.755796618022245 ], [ -122.440028760145466, 37.755956541681734 ], [ -122.440133892987774, 37.756422684783992 ], [ -122.440296908436025, 37.756652260712109 ], [ -122.440975109290761, 37.756855155327102 ], [ -122.441311420390406, 37.75688501757589 ], [ -122.441891917804512, 37.756707900118627 ], [ -122.442226754965375, 37.756642418026821 ], [ -122.442229654337552, 37.756643729156551 ], [ -122.442234277014876, 37.756645819486522 ], [ -122.442241604390318, 37.756649132680884 ], [ -122.442338618241578, 37.756692996803793 ], [ -122.442524865881779, 37.756832757000566 ], [ -122.442618313376656, 37.756939880143335 ], [ -122.442822513313288, 37.757228212937811 ], [ -122.442914855315252, 37.757360460271151 ], [ -122.443513115466942, 37.757015898193345 ], [ -122.44359095735247, 37.756973190948777 ], [ -122.443933292768804, 37.756707199362893 ], [ -122.44469570432544, 37.75722410905459 ], [ -122.444812173070659, 37.757253442709903 ], [ -122.444964327713464, 37.757272877138014 ], [ -122.444969690519684, 37.757271714308636 ], [ -122.444978623456407, 37.757199356159234 ], [ -122.44531471170977, 37.756562327523987 ], [ -122.445646089047287, 37.756449470405634 ], [ -122.445946403378542, 37.756413329216429 ], [ -122.446950354992993, 37.75655039856936 ], [ -122.446777335972257, 37.756299129719707 ], [ -122.446339300582281, 37.75591138652841 ], [ -122.446125719389443, 37.755803738269194 ], [ -122.445929097870021, 37.755830086405965 ], [ -122.445464111666666, 37.75598407248431 ], [ -122.445268371613437, 37.755920565713375 ], [ -122.445221502655059, 37.755736527336694 ], [ -122.445347807952828, 37.755618935076164 ], [ -122.44578153535943, 37.755506535020608 ], [ -122.446152412695255, 37.755464475455021 ], [ -122.446560265846969, 37.755346452369793 ], [ -122.446809203706863, 37.755349269900414 ], [ -122.447004077099621, 37.75543789136227 ], [ -122.447530410096462, 37.755821232451311 ], [ -122.447682922964361, 37.755868400984937 ], [ -122.447851945395087, 37.755820566236046 ], [ -122.447961600338971, 37.755673235404537 ], [ -122.44794315940031, 37.755531524105997 ], [ -122.447698643463639, 37.754829811793044 ], [ -122.447375994048841, 37.754217662794012 ], [ -122.44720983000947, 37.754042891582813 ], [ -122.447450790660298, 37.753903429730649 ], [ -122.447546138718849, 37.753815754469287 ], [ -122.447656226877982, 37.753773037573623 ], [ -122.447842792647691, 37.753750582173005 ], [ -122.447974401539952, 37.753701051764345 ], [ -122.448075396501437, 37.753621892463592 ], [ -122.448145439689242, 37.753500194902855 ], [ -122.448141772510084, 37.753360343523973 ], [ -122.448073149249666, 37.75322587034826 ], [ -122.44795103635677, 37.753120264118444 ], [ -122.447707056312382, 37.752702854501173 ], [ -122.447536095260375, 37.752481955041475 ], [ -122.447740900123705, 37.752288718875811 ], [ -122.448209648267067, 37.752018216178747 ], [ -122.448345547044426, 37.751925564530218 ], [ -122.44842671498688, 37.751814445339285 ], [ -122.448475010516702, 37.751690954837258 ], [ -122.448479024859196, 37.751533756090204 ], [ -122.448535342970942, 37.751302507611712 ], [ -122.448730034866358, 37.750901860878599 ], [ -122.448837529908516, 37.750828266590112 ], [ -122.449185119457937, 37.750637403226534 ], [ -122.449304804219054, 37.750547171075254 ], [ -122.449423303862474, 37.750411755760133 ], [ -122.449497860416159, 37.750255542747738 ], [ -122.449681795644992, 37.749823083722987 ], [ -122.449716548818643, 37.74953978039732 ], [ -122.449640314591349, 37.749314114769071 ], [ -122.449543092528529, 37.749181799119881 ], [ -122.449400479496887, 37.749106035740034 ], [ -122.449245295819139, 37.74910598186397 ], [ -122.449051151579454, 37.749182809813966 ], [ -122.448885055528038, 37.749338875396077 ], [ -122.448264206892432, 37.750168319632976 ], [ -122.448133451017753, 37.75025012344306 ], [ -122.447957681483132, 37.750270249048668 ], [ -122.447060946228802, 37.749998786557995 ], [ -122.446924377309614, 37.749962298069669 ], [ -122.445803050365413, 37.749296324298363 ], [ -122.445657849639957, 37.749137284365275 ], [ -122.445634816186484, 37.748982685093061 ], [ -122.445696556781456, 37.748854668447294 ], [ -122.445900228666389, 37.74876090078547 ], [ -122.446146312903167, 37.748733159469879 ], [ -122.446321171479013, 37.748781931740275 ], [ -122.446513423639288, 37.748873466020136 ], [ -122.44714942379963, 37.749338660628091 ], [ -122.447347720899344, 37.749453771264633 ], [ -122.447558108041335, 37.749512717159995 ], [ -122.447695695115499, 37.749484613169258 ], [ -122.447794029968421, 37.749407650434719 ], [ -122.448271537838281, 37.748586113005615 ], [ -122.448649270998317, 37.747987545863559 ], [ -122.44873793021948, 37.747816100622572 ], [ -122.449048273698679, 37.747075129958219 ], [ -122.449145702641545, 37.746860421020024 ], [ -122.449310056560847, 37.746715637417083 ], [ -122.44944655863172, 37.746660402786539 ], [ -122.449658366925917, 37.746790485969299 ], [ -122.449829421216677, 37.7468448367704 ], [ -122.449121036812883, 37.748486108456696 ], [ -122.450376213037472, 37.748836617132255 ], [ -122.450378440966844, 37.74915691493446 ], [ -122.450359832675659, 37.74936931962273 ], [ -122.450278577377304, 37.749812923634934 ], [ -122.450120041484055, 37.750377135131941 ], [ -122.450039762259507, 37.750789548444018 ], [ -122.450020192405731, 37.751134853236394 ], [ -122.449818467802629, 37.751170814837231 ], [ -122.44965771853829, 37.751260239674203 ], [ -122.449537611901278, 37.75139818204417 ], [ -122.449421587723563, 37.751919823116488 ], [ -122.449322640472062, 37.752393440459898 ], [ -122.449260062142201, 37.752640188644804 ], [ -122.449222626079077, 37.752826418335388 ], [ -122.44920235250531, 37.75298761609767 ], [ -122.449195253073, 37.753141525321581 ], [ -122.44921167048372, 37.753342773463253 ], [ -122.449250446345715, 37.75354718765756 ], [ -122.449338624202724, 37.75376669417966 ], [ -122.449567372082669, 37.754139767157625 ], [ -122.449714539832698, 37.754072287920813 ], [ -122.45001235604478, 37.753935733822416 ], [ -122.450708861489431, 37.753616367449496 ], [ -122.450797400040912, 37.753766170392268 ], [ -122.450940567642817, 37.75387933530601 ], [ -122.451110760292124, 37.753931215767722 ], [ -122.451360262589361, 37.75396141724395 ], [ -122.451594424355719, 37.753898832915297 ], [ -122.452570281407503, 37.75354206761132 ], [ -122.452736650019233, 37.753666208179531 ], [ -122.453410821175368, 37.753818557980566 ], [ -122.454229599548796, 37.754062132001749 ], [ -122.454309928578283, 37.75416682276385 ], [ -122.454154362960978, 37.755029376887862 ], [ -122.454089386335994, 37.755285428850698 ], [ -122.453643273006946, 37.755857139298925 ], [ -122.453512429965073, 37.756126075057203 ], [ -122.453345167458252, 37.756697980973108 ], [ -122.453533591760348, 37.756819465356074 ], [ -122.453617386887871, 37.756948631540013 ], [ -122.453769459328285, 37.75746785416225 ], [ -122.453652871332849, 37.757570195093017 ], [ -122.453594261080696, 37.757621283813904 ], [ -122.453326709060633, 37.757812928758618 ], [ -122.453317408957801, 37.757819578594621 ], [ -122.452876092298638, 37.758073671104697 ], [ -122.45283476350059, 37.758097464572913 ], [ -122.45229781656198, 37.758277073170099 ], [ -122.452047941226652, 37.758343705002019 ], [ -122.451408535854284, 37.758561502025017 ], [ -122.45136693942257, 37.758575661196261 ], [ -122.451366939667807, 37.758575670531805 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":21,\"ZIP_CODE\":94131,\"ID\":94131},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.453769459328285, 37.75746785416225 ], [ -122.453617386887871, 37.756948631540013 ], [ -122.453533591760348, 37.756819465356074 ], [ -122.453345167458252, 37.756697980973108 ], [ -122.453512429965073, 37.756126075057203 ], [ -122.453643273006946, 37.755857139298925 ], [ -122.454089386335994, 37.755285428850698 ], [ -122.454154362960978, 37.755029376887862 ], [ -122.454309928578283, 37.75416682276385 ], [ -122.454229599548796, 37.754062132001749 ], [ -122.453410821175368, 37.753818557980566 ], [ -122.452736650019233, 37.753666208179531 ], [ -122.452570281407503, 37.75354206761132 ], [ -122.451594424355719, 37.753898832915297 ], [ -122.451360262589361, 37.75396141724395 ], [ -122.451110760292124, 37.753931215767722 ], [ -122.450940567642817, 37.75387933530601 ], [ -122.450797400040912, 37.753766170392268 ], [ -122.450708861489431, 37.753616367449496 ], [ -122.45001235604478, 37.753935733822416 ], [ -122.449714539832698, 37.754072287920813 ], [ -122.449567372082669, 37.754139767157625 ], [ -122.449338624202724, 37.75376669417966 ], [ -122.449250446345715, 37.75354718765756 ], [ -122.44921167048372, 37.753342773463253 ], [ -122.449195253073, 37.753141525321581 ], [ -122.44920235250531, 37.75298761609767 ], [ -122.449222626079077, 37.752826418335388 ], [ -122.449260062142201, 37.752640188644804 ], [ -122.449322640472062, 37.752393440459898 ], [ -122.449421587723563, 37.751919823116488 ], [ -122.449537611901278, 37.75139818204417 ], [ -122.44965771853829, 37.751260239674203 ], [ -122.449818467802629, 37.751170814837231 ], [ -122.450020192405731, 37.751134853236394 ], [ -122.450039762259507, 37.750789548444018 ], [ -122.450120041484055, 37.750377135131941 ], [ -122.450278577377304, 37.749812923634934 ], [ -122.450359832675659, 37.74936931962273 ], [ -122.450378440966844, 37.74915691493446 ], [ -122.450376213037472, 37.748836617132255 ], [ -122.449121036812883, 37.748486108456696 ], [ -122.449829421216677, 37.7468448367704 ], [ -122.449658366925917, 37.746790485969299 ], [ -122.44944655863172, 37.746660402786539 ], [ -122.449310056560847, 37.746715637417083 ], [ -122.449145702641545, 37.746860421020024 ], [ -122.449048273698679, 37.747075129958219 ], [ -122.44873793021948, 37.747816100622572 ], [ -122.448649270998317, 37.747987545863559 ], [ -122.448271537838281, 37.748586113005615 ], [ -122.447794029968421, 37.749407650434719 ], [ -122.447695695115499, 37.749484613169258 ], [ -122.447558108041335, 37.749512717159995 ], [ -122.447347720899344, 37.749453771264633 ], [ -122.44714942379963, 37.749338660628091 ], [ -122.446513423639288, 37.748873466020136 ], [ -122.446321171479013, 37.748781931740275 ], [ -122.446146312903167, 37.748733159469879 ], [ -122.445900228666389, 37.74876090078547 ], [ -122.445696556781456, 37.748854668447294 ], [ -122.445634816186484, 37.748982685093061 ], [ -122.445657849639957, 37.749137284365275 ], [ -122.445803050365413, 37.749296324298363 ], [ -122.446924377309614, 37.749962298069669 ], [ -122.447060946228802, 37.749998786557995 ], [ -122.447957681483132, 37.750270249048668 ], [ -122.448133451017753, 37.75025012344306 ], [ -122.448264206892432, 37.750168319632976 ], [ -122.448885055528038, 37.749338875396077 ], [ -122.449051151579454, 37.749182809813966 ], [ -122.449245295819139, 37.74910598186397 ], [ -122.449400479496887, 37.749106035740034 ], [ -122.449543092528529, 37.749181799119881 ], [ -122.449640314591349, 37.749314114769071 ], [ -122.449716548818643, 37.74953978039732 ], [ -122.449681795644992, 37.749823083722987 ], [ -122.449497860416159, 37.750255542747738 ], [ -122.449423303862474, 37.750411755760133 ], [ -122.449304804219054, 37.750547171075254 ], [ -122.449185119457937, 37.750637403226534 ], [ -122.448837529908516, 37.750828266590112 ], [ -122.448730034866358, 37.750901860878599 ], [ -122.448535342970942, 37.751302507611712 ], [ -122.448479024859196, 37.751533756090204 ], [ -122.448475010516702, 37.751690954837258 ], [ -122.44842671498688, 37.751814445339285 ], [ -122.448345547044426, 37.751925564530218 ], [ -122.448209648267067, 37.752018216178747 ], [ -122.447740900123705, 37.752288718875811 ], [ -122.447536095260375, 37.752481955041475 ], [ -122.447707056312382, 37.752702854501173 ], [ -122.44795103635677, 37.753120264118444 ], [ -122.448073149249666, 37.75322587034826 ], [ -122.448141772510084, 37.753360343523973 ], [ -122.448145439689242, 37.753500194902855 ], [ -122.448075396501437, 37.753621892463592 ], [ -122.447974401539952, 37.753701051764345 ], [ -122.447842792647691, 37.753750582173005 ], [ -122.447656226877982, 37.753773037573623 ], [ -122.447546138718849, 37.753815754469287 ], [ -122.447450790660298, 37.753903429730649 ], [ -122.44720983000947, 37.754042891582813 ], [ -122.447375994048841, 37.754217662794012 ], [ -122.447698643463639, 37.754829811793044 ], [ -122.44794315940031, 37.755531524105997 ], [ -122.447961600338971, 37.755673235404537 ], [ -122.447851945395087, 37.755820566236046 ], [ -122.447682922964361, 37.755868400984937 ], [ -122.447530410096462, 37.755821232451311 ], [ -122.447004077099621, 37.75543789136227 ], [ -122.446809203706863, 37.755349269900414 ], [ -122.446560265846969, 37.755346452369793 ], [ -122.446152412695255, 37.755464475455021 ], [ -122.44578153535943, 37.755506535020608 ], [ -122.445347807952828, 37.755618935076164 ], [ -122.445221502655059, 37.755736527336694 ], [ -122.445268371613437, 37.755920565713375 ], [ -122.445464111666666, 37.75598407248431 ], [ -122.445929097870021, 37.755830086405965 ], [ -122.446125719389443, 37.755803738269194 ], [ -122.446339300582281, 37.75591138652841 ], [ -122.446777335972257, 37.756299129719707 ], [ -122.446950354992993, 37.75655039856936 ], [ -122.445946403378542, 37.756413329216429 ], [ -122.445646089047287, 37.756449470405634 ], [ -122.44531471170977, 37.756562327523987 ], [ -122.444978623456407, 37.757199356159234 ], [ -122.444969690519684, 37.757271714308636 ], [ -122.444964327713464, 37.757272877138014 ], [ -122.444812173070659, 37.757253442709903 ], [ -122.44469570432544, 37.75722410905459 ], [ -122.443933292768804, 37.756707199362893 ], [ -122.44359095735247, 37.756973190948777 ], [ -122.443513115466942, 37.757015898193345 ], [ -122.442914855315252, 37.757360460271151 ], [ -122.442822513313288, 37.757228212937811 ], [ -122.442618313376656, 37.756939880143335 ], [ -122.442524865881779, 37.756832757000566 ], [ -122.442338618241578, 37.756692996803793 ], [ -122.442241604390318, 37.756649132680884 ], [ -122.442234277014876, 37.756645819486522 ], [ -122.442229654337552, 37.756643729156551 ], [ -122.442226754965375, 37.756642418026821 ], [ -122.441891917804512, 37.756707900118627 ], [ -122.441311420390406, 37.75688501757589 ], [ -122.440975109290761, 37.756855155327102 ], [ -122.440296908436025, 37.756652260712109 ], [ -122.440133892987774, 37.756422684783992 ], [ -122.440028760145466, 37.755956541681734 ], [ -122.440032113187002, 37.755796618022245 ], [ -122.440113197925641, 37.755606296679595 ], [ -122.440344935902942, 37.755227929193452 ], [ -122.440449630137792, 37.755065428273227 ], [ -122.440897310558796, 37.754492600197032 ], [ -122.44107583131526, 37.754178484324051 ], [ -122.441162634059779, 37.754090414508028 ], [ -122.441307496038533, 37.753879905149773 ], [ -122.441501225348532, 37.753721419417111 ], [ -122.441807410207034, 37.753470934450007 ], [ -122.441886398393621, 37.753365106672398 ], [ -122.442056693562165, 37.753070846058556 ], [ -122.442239114579749, 37.752829681730105 ], [ -122.442492558984341, 37.752606697266998 ], [ -122.442524333279991, 37.752382565152622 ], [ -122.442775038632988, 37.752378432471772 ], [ -122.442775383396778, 37.752377050296218 ], [ -122.442982766064986, 37.751545025906275 ], [ -122.442817470436609, 37.751454127059596 ], [ -122.442832455660607, 37.751431039472649 ], [ -122.442782889125965, 37.750775670827849 ], [ -122.442782695565214, 37.750773110847675 ], [ -122.442776988488347, 37.750697651678763 ], [ -122.442693363008104, 37.750413947729008 ], [ -122.442583534001741, 37.749266790173017 ], [ -122.442599224378554, 37.749160242994655 ], [ -122.44259888979461, 37.749156165729936 ], [ -122.443434635522365, 37.749091646853778 ], [ -122.443486020026867, 37.748345516454542 ], [ -122.443473812008847, 37.74801042178742 ], [ -122.443473512241141, 37.748002572121706 ], [ -122.443453448246288, 37.747477579218923 ], [ -122.443161821262549, 37.747504352694399 ], [ -122.442584207288078, 37.748079345098553 ], [ -122.442519008868075, 37.748183215938361 ], [ -122.441916701553239, 37.748474391279807 ], [ -122.441477149443557, 37.748591599630018 ], [ -122.4414080949674, 37.748610013081198 ], [ -122.440693299152798, 37.748639039270962 ], [ -122.440402009224243, 37.748642072742477 ], [ -122.438246126408771, 37.748664500736062 ], [ -122.438139804541592, 37.748636730271706 ], [ -122.436067172923103, 37.748762035575652 ], [ -122.433851103876819, 37.748895972089151 ], [ -122.431639579453815, 37.749028214467948 ], [ -122.429422761644645, 37.749157853016314 ], [ -122.427200037712353, 37.749295720374967 ], [ -122.424922999639648, 37.749434901505936 ], [ -122.424845024283727, 37.748632587554638 ], [ -122.424845018024243, 37.748632519532975 ], [ -122.424841979744244, 37.748600757493776 ], [ -122.424768284589831, 37.747830340365347 ], [ -122.424768280884535, 37.747830304166378 ], [ -122.424689793548367, 37.747033402901096 ], [ -122.424602793343411, 37.746241400149849 ], [ -122.424538964982034, 37.745435717069675 ], [ -122.424459208282684, 37.744635998834639 ], [ -122.424384862816794, 37.743838216035606 ], [ -122.424309369085989, 37.7430347552692 ], [ -122.424233277120777, 37.742236301639601 ], [ -122.424233272287097, 37.742236301993223 ], [ -122.424233472639315, 37.742235957277522 ], [ -122.424233478510288, 37.742235956906946 ], [ -122.424301394893192, 37.742119600447609 ], [ -122.42417135664968, 37.740713439585669 ], [ -122.42413709700044, 37.740335654620864 ], [ -122.424258152196032, 37.739934707389949 ], [ -122.42414746787631, 37.739869414698418 ], [ -122.424147154546787, 37.739869230001858 ], [ -122.424147154179678, 37.739869229183775 ], [ -122.424063599996956, 37.739784297520274 ], [ -122.424284283357963, 37.738949691036076 ], [ -122.424531564221411, 37.738368292265967 ], [ -122.424696469855888, 37.738101223886673 ], [ -122.424711669489241, 37.738076607801148 ], [ -122.424904804743889, 37.737763817504508 ], [ -122.425110714349969, 37.737550673136937 ], [ -122.425230653676863, 37.737426519232308 ], [ -122.425439441891498, 37.737210392933726 ], [ -122.425832974995387, 37.736873263301263 ], [ -122.426500890836181, 37.73630106743056 ], [ -122.426544994076067, 37.736269396547655 ], [ -122.42729729676671, 37.735729149898575 ], [ -122.427738169173168, 37.735412543347451 ], [ -122.42783624298761, 37.735347761815902 ], [ -122.428436951461066, 37.734950968784666 ], [ -122.42843713263963, 37.734951046304573 ], [ -122.428766264762999, 37.73481932892178 ], [ -122.429365174143101, 37.734579643964238 ], [ -122.429606994304805, 37.734482866043706 ], [ -122.429797927680781, 37.734406452684077 ], [ -122.429799382942633, 37.734405632509876 ], [ -122.430400796753801, 37.734118700718604 ], [ -122.430928172484656, 37.733867087971888 ], [ -122.430931197463138, 37.733865940717834 ], [ -122.43132522808267, 37.733716495194997 ], [ -122.432572047381726, 37.733243596430682 ], [ -122.432720773871381, 37.733187185661393 ], [ -122.434440716858973, 37.732534804028113 ], [ -122.434512093568429, 37.732323526771246 ], [ -122.434644432402948, 37.732322139093768 ], [ -122.434823956379304, 37.732249430441868 ], [ -122.434989951388332, 37.732296516038524 ], [ -122.434992781477959, 37.732295287547394 ], [ -122.435457687984979, 37.732093484109448 ], [ -122.436668162612392, 37.731543298105045 ], [ -122.436678949019353, 37.731538395224071 ], [ -122.436835967120587, 37.731467212874229 ], [ -122.437166749161392, 37.731365897534992 ], [ -122.438692929843569, 37.730737998359416 ], [ -122.439686065703896, 37.730325720705899 ], [ -122.43969146797258, 37.730323477880063 ], [ -122.440664990612632, 37.729919329476168 ], [ -122.442010214558891, 37.729910949382834 ], [ -122.442006614261629, 37.729375267903364 ], [ -122.442082956691792, 37.729308363921405 ], [ -122.442595296877187, 37.729101680603421 ], [ -122.442954953118544, 37.728915555981267 ], [ -122.443973112888827, 37.728379877183251 ], [ -122.444131311025359, 37.72831838217401 ], [ -122.444276984301908, 37.728261755504967 ], [ -122.44427751391413, 37.728322242080615 ], [ -122.444284269547836, 37.729093639959018 ], [ -122.444291303797655, 37.729896860703782 ], [ -122.444298409699798, 37.730708221303239 ], [ -122.444294941941365, 37.731069863889161 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.444299518643774, 37.732277163251723 ], [ -122.44431434932487, 37.732346901474607 ], [ -122.444320397855094, 37.733087687518228 ], [ -122.444320121334613, 37.733811072362919 ], [ -122.445605378551107, 37.733800711254226 ], [ -122.445782219446897, 37.733850288395629 ], [ -122.445695180894745, 37.733970768223749 ], [ -122.445385609625191, 37.734245207403205 ], [ -122.445350300553969, 37.734322189420951 ], [ -122.445353637361492, 37.734548793463254 ], [ -122.444336617762318, 37.734557437927307 ], [ -122.442971182760076, 37.734564427784264 ], [ -122.44298838327488, 37.734915120161688 ], [ -122.443241645458286, 37.734935120886668 ], [ -122.443498916758756, 37.735041781619209 ], [ -122.443868794328736, 37.73522293273804 ], [ -122.444190291728404, 37.735407445883219 ], [ -122.444280987817251, 37.735484218369663 ], [ -122.444414926545633, 37.735547154436588 ], [ -122.444456723960371, 37.735604611251425 ], [ -122.444478073435704, 37.735720552668923 ], [ -122.444433832419023, 37.736070356056409 ], [ -122.444405709994072, 37.736218559056141 ], [ -122.444306066715512, 37.736409368208768 ], [ -122.444187688286519, 37.736528330103852 ], [ -122.444038854214782, 37.736575290313169 ], [ -122.443842427221611, 37.736579809812575 ], [ -122.443683487927487, 37.73653803376142 ], [ -122.443602235926093, 37.736502583404238 ], [ -122.442657322787326, 37.737118881702763 ], [ -122.442488947503193, 37.737289169592358 ], [ -122.442421017736137, 37.737438198043598 ], [ -122.442403843641571, 37.737639851367689 ], [ -122.44244617419956, 37.737799536516135 ], [ -122.442579349780331, 37.738171569110513 ], [ -122.442625226229453, 37.738313911390271 ], [ -122.442627783322195, 37.738564600951854 ], [ -122.44250398997913, 37.739238469269011 ], [ -122.442525191527608, 37.739448400155375 ], [ -122.442597107319372, 37.739547858254923 ], [ -122.44271760477325, 37.739682237717481 ], [ -122.442856748556963, 37.739767263698369 ], [ -122.443019593631774, 37.739814475654143 ], [ -122.44341918472557, 37.739902333376541 ], [ -122.443694223373313, 37.739971345925731 ], [ -122.443922038304507, 37.740088462716592 ], [ -122.444098337188322, 37.740234069945657 ], [ -122.444209640252396, 37.740380617798955 ], [ -122.444366170223589, 37.740575544006177 ], [ -122.444518249198694, 37.740692998244214 ], [ -122.444681667799003, 37.740793092051724 ], [ -122.444911120433531, 37.740872114678645 ], [ -122.445195592772691, 37.740920623913645 ], [ -122.445523457193502, 37.740972470791057 ], [ -122.445897881975142, 37.741116602917764 ], [ -122.445964326755799, 37.741198730714288 ], [ -122.445964300022951, 37.741198753406074 ], [ -122.44605943275991, 37.741274575873817 ], [ -122.44609965968975, 37.741357693405078 ], [ -122.446156199006268, 37.741535311478742 ], [ -122.44619443459527, 37.741872554265036 ], [ -122.446256735689062, 37.742072052701658 ], [ -122.446323687641325, 37.742184944882247 ], [ -122.446410971977912, 37.742281706494659 ], [ -122.446516770315029, 37.742358933848621 ], [ -122.446657325560608, 37.742442453734959 ], [ -122.446840940400392, 37.742519081676662 ], [ -122.447110638216571, 37.742580552169777 ], [ -122.447384659150103, 37.742608987228614 ], [ -122.44848892294759, 37.742704730693937 ], [ -122.448820428983325, 37.742748693538836 ], [ -122.44913731277687, 37.742828607669701 ], [ -122.449355958412127, 37.74292182098899 ], [ -122.449582834428256, 37.743065029012413 ], [ -122.449790244582587, 37.743257317336237 ], [ -122.449918612648133, 37.7432716748716 ], [ -122.450592179132485, 37.744090792307063 ], [ -122.450586742649079, 37.744213121547148 ], [ -122.451624634588612, 37.745534387540218 ], [ -122.45169226258335, 37.745606748366605 ], [ -122.45181666119403, 37.745671246259803 ], [ -122.451828632556612, 37.745671851179097 ], [ -122.452091117231191, 37.74568511552728 ], [ -122.45222754649042, 37.74569200904012 ], [ -122.452386953750349, 37.745691424670198 ], [ -122.453376023619995, 37.74568779337109 ], [ -122.453495886255311, 37.745691252478657 ], [ -122.453645157964232, 37.745695560010326 ], [ -122.453916002908102, 37.745699915076791 ], [ -122.453916429980183, 37.745699922279499 ], [ -122.454184426826714, 37.745845539203287 ], [ -122.454664096869323, 37.746106168082655 ], [ -122.454664097215115, 37.746106168076913 ], [ -122.455466732538028, 37.746327980692172 ], [ -122.455687935660293, 37.746380796419501 ], [ -122.456314708164584, 37.746530446031059 ], [ -122.457089836487938, 37.746682896092409 ], [ -122.457166741019165, 37.746698021157933 ], [ -122.457301186299645, 37.746714064689691 ], [ -122.457457161051892, 37.746732677593577 ], [ -122.457582024204598, 37.746744476612157 ], [ -122.457949639713704, 37.746779214661537 ], [ -122.458499844031735, 37.746781514247793 ], [ -122.458509634961302, 37.746778882011192 ], [ -122.458610345848541, 37.746751806518411 ], [ -122.45872851739027, 37.746755429911865 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.459174106111192, 37.747286620942795 ], [ -122.458970102890007, 37.747514391535915 ], [ -122.458856680033747, 37.7476570668547 ], [ -122.458785941599359, 37.747811900102363 ], [ -122.458816156308444, 37.748040115735854 ], [ -122.458941590023073, 37.748201275736406 ], [ -122.459200706652084, 37.748504001538066 ], [ -122.459625716264128, 37.748941671345293 ], [ -122.459724182852014, 37.749021140146965 ], [ -122.459910557162914, 37.749171866124314 ], [ -122.460150106375522, 37.749394963907797 ], [ -122.460488100882344, 37.749710225074402 ], [ -122.460574698392477, 37.749816963786891 ], [ -122.460646032705213, 37.749905429557089 ], [ -122.460872188804359, 37.750377416930355 ], [ -122.461130726036544, 37.75091814728394 ], [ -122.461357402458418, 37.751255115112428 ], [ -122.461523490818323, 37.75150339365527 ], [ -122.461523490133942, 37.751503393941363 ], [ -122.461523602837886, 37.75150356127773 ], [ -122.461621806880288, 37.751517360697065 ], [ -122.461851812215116, 37.751624201717213 ], [ -122.462014174664134, 37.751745235464952 ], [ -122.462176783557467, 37.751866685725204 ], [ -122.462467214248704, 37.7521873638808 ], [ -122.462572755978741, 37.752277690485144 ], [ -122.462772959403608, 37.752449458308092 ], [ -122.463129733530764, 37.752716425489766 ], [ -122.463165176689103, 37.75274298458443 ], [ -122.463398089008777, 37.752981523058104 ], [ -122.463399006031565, 37.752981452291863 ], [ -122.463640406958334, 37.753310851043111 ], [ -122.463777683069083, 37.753601657228067 ], [ -122.463842129576008, 37.753808596342253 ], [ -122.463838893966539, 37.75414750585891 ], [ -122.463722821262465, 37.754601920791259 ], [ -122.46367889740506, 37.754738571878264 ], [ -122.463627289771182, 37.754788065181785 ], [ -122.463580718915097, 37.755014148901829 ], [ -122.463554499932727, 37.755298102249476 ], [ -122.463651620775778, 37.756677450683235 ], [ -122.463781528402038, 37.758538181378015 ], [ -122.463911891482155, 37.760405344350922 ], [ -122.46284191307069, 37.760452254741963 ], [ -122.461770200794135, 37.760499231230455 ], [ -122.461770397151241, 37.760502041661596 ], [ -122.460684658382874, 37.760546523892096 ], [ -122.46084509456081, 37.762627108799968 ], [ -122.460842964058216, 37.762627642539876 ], [ -122.459790374597731, 37.762925938047687 ], [ -122.457827081738984, 37.763482288038738 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.456669912105653, 37.763814688946653 ], [ -122.455781065686594, 37.764070070210742 ], [ -122.455466091062405, 37.764160565952579 ], [ -122.454217265424234, 37.764310945798535 ], [ -122.454179864833876, 37.763912200042988 ], [ -122.454845151642289, 37.763732313486969 ], [ -122.454945541962573, 37.76366305305779 ], [ -122.454946081907835, 37.7631787014639 ], [ -122.454009611320302, 37.762667869264533 ], [ -122.453744673977482, 37.761878784498755 ], [ -122.453114639516684, 37.762361563728888 ], [ -122.45351591398105, 37.764395394431268 ], [ -122.452569575903098, 37.764509334437378 ], [ -122.452569519449398, 37.764509056008684 ], [ -122.452400386663143, 37.763669233221897 ], [ -122.45223333012629, 37.762842423991209 ], [ -122.452096203587132, 37.762166887302627 ], [ -122.451957007208804, 37.761478336720671 ], [ -122.45155310706329, 37.759472203369178 ], [ -122.45136693770776, 37.758575661774039 ], [ -122.451408535854284, 37.758561502025017 ], [ -122.452047941226652, 37.758343705002019 ], [ -122.45229781656198, 37.758277073170099 ], [ -122.45283476350059, 37.758097464572913 ], [ -122.452876092298638, 37.758073671104697 ], [ -122.453317408957801, 37.757819578594621 ], [ -122.453326709060633, 37.757812928758618 ], [ -122.453652871332849, 37.757570195093017 ], [ -122.453769459328285, 37.75746785416225 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":22,\"ZIP_CODE\":94122,\"ID\":94122},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.463399006031565, 37.752981452291863 ], [ -122.464461497345539, 37.752899248258529 ], [ -122.464461497323754, 37.752899247434819 ], [ -122.464462034791779, 37.752899228037201 ], [ -122.464462034762747, 37.752899226938901 ], [ -122.465533177566329, 37.752851071595934 ], [ -122.46660049988607, 37.752803078204508 ], [ -122.467642627702773, 37.752756208616617 ], [ -122.467804242122298, 37.752723264514827 ], [ -122.467915171465805, 37.752734664618352 ], [ -122.468031369650532, 37.75278739056494 ], [ -122.468181079371874, 37.752955569756089 ], [ -122.468335071492149, 37.753243730756957 ], [ -122.468440863346331, 37.753541228150766 ], [ -122.46853368295227, 37.753778395652951 ], [ -122.468629004224212, 37.753985873398783 ], [ -122.46875232271276, 37.754192126033097 ], [ -122.468837396357713, 37.754298754678686 ], [ -122.468949194118821, 37.754383817642378 ], [ -122.469043420308736, 37.754428685878558 ], [ -122.469187312711114, 37.754478429881637 ], [ -122.469229538348586, 37.754492641945887 ], [ -122.469359681800768, 37.754536443616175 ], [ -122.469719537880266, 37.754630546382018 ], [ -122.470018818714578, 37.754692394187302 ], [ -122.470075601814969, 37.754705621480454 ], [ -122.470428100059209, 37.754750285613063 ], [ -122.470916907310823, 37.75477064959378 ], [ -122.471016758783364, 37.754797170870887 ], [ -122.471049094699751, 37.75451479556704 ], [ -122.471019321832557, 37.754265617625428 ], [ -122.471016495996267, 37.754217122306045 ], [ -122.471036498728921, 37.754100284913058 ], [ -122.471079638048607, 37.753995444111091 ], [ -122.471132268263645, 37.753877430013475 ], [ -122.471171093123701, 37.753733616626192 ], [ -122.471190660490961, 37.753605743703318 ], [ -122.471187692098596, 37.753493865266243 ], [ -122.471174197654705, 37.753356133265093 ], [ -122.471122146869178, 37.753216025593119 ], [ -122.470907529168414, 37.752615836450325 ], [ -122.472007574280582, 37.752569817871482 ], [ -122.473133075589757, 37.752519603580488 ], [ -122.474205182950101, 37.752471761013865 ], [ -122.474740669236724, 37.752447861779451 ], [ -122.475276421588944, 37.752423947680597 ], [ -122.476291890529794, 37.752378614900692 ], [ -122.476291890478677, 37.752378612978674 ], [ -122.476292239846927, 37.752378597780194 ], [ -122.476292240229284, 37.752378599147249 ], [ -122.4774149778289, 37.752329102778496 ], [ -122.478486061226221, 37.752281873458429 ], [ -122.479557564859533, 37.752234616335052 ], [ -122.480628878786973, 37.752187357305665 ], [ -122.48170210782861, 37.752140004119113 ], [ -122.48276958366516, 37.752092894785363 ], [ -122.483844155588841, 37.75204546291927 ], [ -122.48491485069583, 37.751998192013865 ], [ -122.48598818277614, 37.751950794949664 ], [ -122.487053067380344, 37.751903269835523 ], [ -122.488143190546495, 37.751854042116229 ], [ -122.48919356840311, 37.751806869260896 ], [ -122.490270381873529, 37.751758499707634 ], [ -122.491344845233968, 37.751710225381885 ], [ -122.492417458516726, 37.751662024433998 ], [ -122.493488045146222, 37.751613904571322 ], [ -122.49456080780611, 37.751568259028382 ], [ -122.495098259533435, 37.751544767553312 ], [ -122.49509879355503, 37.751544749172808 ], [ -122.495630257074851, 37.751526822572394 ], [ -122.49581055415868, 37.751518288226762 ], [ -122.496702184257288, 37.751476079301845 ], [ -122.497770415009455, 37.751426327287128 ], [ -122.497770416046876, 37.751426327269549 ], [ -122.498442545251379, 37.751398004902029 ], [ -122.498842248697912, 37.751381160579498 ], [ -122.49937768349335, 37.751357494487337 ], [ -122.499914122812314, 37.751333781439868 ], [ -122.499914456955352, 37.751333766429582 ], [ -122.500987488435712, 37.75128639412403 ], [ -122.502060320882052, 37.751239765057655 ], [ -122.503133002600279, 37.751190357338089 ], [ -122.504203312772219, 37.751143789774702 ], [ -122.505276576525688, 37.751093151070108 ], [ -122.506347526734444, 37.751051944046772 ], [ -122.507416228284868, 37.751003284446853 ], [ -122.507286887279179, 37.749140090258685 ], [ -122.507286886587565, 37.749140090270473 ], [ -122.50728687853443, 37.749139971463322 ], [ -122.507286878880223, 37.749139971457424 ], [ -122.50729515180268, 37.749139604462776 ], [ -122.507539948063609, 37.749128748547342 ], [ -122.507941547211104, 37.749110937340276 ], [ -122.507940323213063, 37.749101694546454 ], [ -122.508363946491727, 37.74907680068609 ], [ -122.509705738305371, 37.748997941669188 ], [ -122.509710663420336, 37.749052110606286 ], [ -122.509713246503537, 37.749083657142556 ], [ -122.509729549623728, 37.749110848942742 ], [ -122.509729362100501, 37.749167851780079 ], [ -122.509735273166726, 37.749194534062582 ], [ -122.509750516130779, 37.749246466794162 ], [ -122.509778567561426, 37.749324277699941 ], [ -122.509789628452893, 37.749349498384071 ], [ -122.509798960033251, 37.749374748835095 ], [ -122.509811768849048, 37.749400627001869 ], [ -122.509822811194553, 37.749425161253306 ], [ -122.509835600759672, 37.749450352725269 ], [ -122.509848372456204, 37.749474857754677 ], [ -122.509862872798479, 37.749499333300058 ], [ -122.509875644166641, 37.74952383833223 ], [ -122.509890144527432, 37.749548313874023 ], [ -122.509938776915888, 37.749619592731861 ], [ -122.50997116142004, 37.749665739100415 ], [ -122.509989082682068, 37.749688783065459 ], [ -122.51000698537004, 37.749711140049378 ], [ -122.510074896863927, 37.74979170473398 ], [ -122.510112524214264, 37.749839821874914 ], [ -122.510150170185028, 37.749888625432611 ], [ -122.510222041423717, 37.749987664081871 ], [ -122.510254537700391, 37.750037928944337 ], [ -122.510319567532292, 37.750139831499247 ], [ -122.510350372089775, 37.750191498687315 ], [ -122.510379447689871, 37.750243195362941 ], [ -122.510406812903227, 37.750295607956453 ], [ -122.510434196383827, 37.75034870697808 ], [ -122.510450965102024, 37.750393059386766 ], [ -122.51046407081715, 37.750429920079867 ], [ -122.510475448231503, 37.75046681025696 ], [ -122.510486843547952, 37.750504387148105 ], [ -122.510515843893401, 37.750617205185918 ], [ -122.510523781572246, 37.750654840788101 ], [ -122.510529990248557, 37.75069250588782 ], [ -122.510536217506655, 37.75073085741591 ], [ -122.510540697183259, 37.750768552012921 ], [ -122.510545195439974, 37.750806933038461 ], [ -122.510549675125716, 37.750844627634628 ], [ -122.510552444030878, 37.750883038163828 ], [ -122.510555546179489, 37.75099767207351 ], [ -122.510554168969648, 37.751074611106283 ], [ -122.510551732248842, 37.751112423700818 ], [ -122.510547585771619, 37.751150952210686 ], [ -122.510543420361202, 37.751188794022582 ], [ -122.510539273529616, 37.751227322537567 ], [ -122.510534624806738, 37.751247317477244 ], [ -122.51052982817599, 37.751261821248107 ], [ -122.510526778782435, 37.751276981406193 ], [ -122.510523710819541, 37.75129145540982 ], [ -122.510522371541924, 37.751305900195682 ], [ -122.510519303915359, 37.751320373918666 ], [ -122.510517983550216, 37.751335504852847 ], [ -122.510514915590633, 37.75134997913068 ], [ -122.510513595216636, 37.751365109790179 ], [ -122.510513985300733, 37.75137952479723 ], [ -122.510512664933444, 37.751394655731197 ], [ -122.510513055017512, 37.751409070738212 ], [ -122.510511734303734, 37.751424201677992 ], [ -122.510512533054808, 37.751453718395311 ], [ -122.510514671081651, 37.751468790052584 ], [ -122.510515061166473, 37.751483205059415 ], [ -122.51051719886371, 37.751498277271601 ], [ -122.510519317971443, 37.751512662505696 ], [ -122.510521455317175, 37.751527734449049 ], [ -122.510523574787243, 37.751542120226226 ], [ -122.510527441503768, 37.751557162390775 ], [ -122.51052956027678, 37.751571547905016 ], [ -122.510541105080137, 37.751614615928503 ], [ -122.510546682275447, 37.751628942714049 ], [ -122.51055053042532, 37.751643298448748 ], [ -122.510567261661492, 37.751686277985172 ], [ -122.51057282063698, 37.751699918334559 ], [ -122.510580126864298, 37.751714215070294 ], [ -122.510587414533077, 37.751727825925812 ], [ -122.510592973154687, 37.751741465730937 ], [ -122.510600260828497, 37.751755076585596 ], [ -122.510609277525731, 37.751768657391231 ], [ -122.510616565205183, 37.751782268244845 ], [ -122.510623834295913, 37.751795192119978 ], [ -122.510632850671371, 37.75180877347875 ], [ -122.510650847293675, 37.751834562495105 ], [ -122.510666910213118, 37.751852830846786 ], [ -122.510679329278858, 37.751864293359795 ], [ -122.510690018971971, 37.751875785652558 ], [ -122.510697288092032, 37.751888709797527 ], [ -122.510704575791962, 37.751902320370817 ], [ -122.51070842433974, 37.751916676368367 ], [ -122.510710543503862, 37.751931061872575 ], [ -122.51070920459685, 37.751945506653065 ], [ -122.510724225875535, 37.751989202088765 ], [ -122.510730100360817, 37.752014511176412 ], [ -122.510737685321715, 37.752039104608144 ], [ -122.510743559477149, 37.752064413975368 ], [ -122.510825714104442, 37.752223710988055 ], [ -122.510880260867467, 37.752321671638327 ], [ -122.510889277338805, 37.75233525242713 ], [ -122.51089827594069, 37.752348147323836 ], [ -122.510919693380217, 37.752372504437815 ], [ -122.510932112233405, 37.752383967203627 ], [ -122.510956913829162, 37.752405519856168 ], [ -122.510971006350204, 37.752414893821253 ], [ -122.510979967462688, 37.75242641558544 ], [ -122.510987199526895, 37.75243796685389 ], [ -122.510992721122207, 37.752450234055644 ], [ -122.510999971778375, 37.752462472026544 ], [ -122.511005493370178, 37.752474738953069 ], [ -122.511011033205719, 37.752487692588488 ], [ -122.511014844689754, 37.752500675991762 ], [ -122.511020366286843, 37.752512942917527 ], [ -122.511024177420595, 37.752525926051767 ], [ -122.511026259163927, 37.752538938971881 ], [ -122.511028360165199, 37.752552637759649 ], [ -122.511032524332322, 37.752578663038605 ], [ -122.511033248973078, 37.752605433757282 ], [ -122.511030496930232, 37.752631577332984 ], [ -122.511029139131608, 37.75264533514347 ], [ -122.511030233600934, 37.752813569447106 ], [ -122.511027591948121, 37.752907698414738 ], [ -122.511023202651316, 37.753001170732233 ], [ -122.511017102857721, 37.753095358982122 ], [ -122.511007544911607, 37.753189606240724 ], [ -122.511045097099611, 37.75342657943726 ], [ -122.511071439929921, 37.753568973002693 ], [ -122.511099511595958, 37.7537113370581 ], [ -122.511110666857206, 37.753739990285347 ], [ -122.51113816393935, 37.753797207403004 ], [ -122.511167297244739, 37.753850963138049 ], [ -122.511191001766875, 37.753895883818785 ], [ -122.511212977231722, 37.753940834002691 ], [ -122.511234971305953, 37.753986470610634 ], [ -122.511275501018872, 37.754077803112054 ], [ -122.511285596295252, 37.754131196705785 ], [ -122.511275500500716, 37.754141670188282 ], [ -122.511267152377243, 37.754152800589303 ], [ -122.511258841417288, 37.754165303846627 ], [ -122.51125398864481, 37.754177748360249 ], [ -122.511249154438758, 37.754190878752958 ], [ -122.511246439571536, 37.754218394908946 ], [ -122.511248521739574, 37.754231407540907 ], [ -122.511252351238468, 37.754245077098325 ], [ -122.511256162847417, 37.754258060215321 ], [ -122.511263413723356, 37.754270298166247 ], [ -122.511270646011127, 37.75428184941368 ], [ -122.511281336136037, 37.754293341373469 ], [ -122.511290279285674, 37.754304176676023 ], [ -122.511297511580892, 37.754315727921757 ], [ -122.511301304272081, 37.754328024614594 ], [ -122.51130511555543, 37.7543410080103 ], [ -122.511305450050784, 37.754353363447571 ], [ -122.511302344945619, 37.754366464607017 ], [ -122.511299221255896, 37.754378879337885 ], [ -122.511292620796496, 37.754390666933737 ], [ -122.511284272301779, 37.754401797341401 ], [ -122.511274158225476, 37.754411584114365 ], [ -122.511262296127953, 37.754420714247495 ], [ -122.51124864918124, 37.754427814602913 ], [ -122.511234965066222, 37.754433542099882 ], [ -122.511205457998457, 37.754493792524343 ], [ -122.511174612954918, 37.754504620093513 ], [ -122.511188723552863, 37.754578547809551 ], [ -122.511196847828685, 37.754623047899784 ], [ -122.511172119152874, 37.754668108364662 ], [ -122.511186378431859, 37.754747527231643 ], [ -122.511164047430796, 37.754817269614293 ], [ -122.511168079798153, 37.754966224575647 ], [ -122.511187639253137, 37.754985806682214 ], [ -122.511182209413036, 37.755040838983689 ], [ -122.511152553395789, 37.755095597687138 ], [ -122.511140988488648, 37.755115710660888 ], [ -122.511131170290469, 37.755200347899923 ], [ -122.511137026593502, 37.755224970799595 ], [ -122.511151546101601, 37.755313999934657 ], [ -122.511172052652412, 37.755368589625292 ], [ -122.511185234625046, 37.75540819590843 ], [ -122.511212230658316, 37.755446879976844 ], [ -122.511220633780624, 37.755501676208965 ], [ -122.511245788883045, 37.755536270664308 ], [ -122.511246718045257, 37.755570592079458 ], [ -122.511273603014288, 37.755605157557042 ], [ -122.511268303269489, 37.755664994580059 ], [ -122.511302921291062, 37.755793511928204 ], [ -122.51129135632344, 37.755813625188885 ], [ -122.511297751657864, 37.755858154497574 ], [ -122.51132993529248, 37.755896749458273 ], [ -122.511344307286322, 37.755916419796833 ], [ -122.51135098142602, 37.755971245525423 ], [ -122.511358975860517, 37.756010940596234 ], [ -122.511372157693486, 37.756050546585421 ], [ -122.51138067251722, 37.756109461644371 ], [ -122.51138760653366, 37.756173897095294 ], [ -122.511415290932803, 37.756237978675102 ], [ -122.51144193355438, 37.756327487882515 ], [ -122.511450857327816, 37.756401503804241 ], [ -122.511444739922965, 37.756431138262307 ], [ -122.511465507194529, 37.756495337885667 ], [ -122.511475248786056, 37.756599556916427 ], [ -122.511489639220827, 37.756683781260584 ], [ -122.511510945933878, 37.756767887008252 ], [ -122.511505516202988, 37.756822919307787 ], [ -122.511507114604001, 37.756881952399702 ], [ -122.511540803935873, 37.756976147977831 ], [ -122.51151021846853, 37.756996585892097 ], [ -122.511537215240679, 37.757035269600948 ], [ -122.511550805913657, 37.75708997726138 ], [ -122.511552014025696, 37.757134594811419 ], [ -122.51153474073044, 37.7571994439623 ], [ -122.511545151615906, 37.757328374109882 ], [ -122.511565380212119, 37.7573726672926 ], [ -122.511548775980131, 37.757462227577349 ], [ -122.511570621660383, 37.757566239995974 ], [ -122.511588991755985, 37.757605757952987 ], [ -122.511636236094873, 37.757689420961349 ], [ -122.511627217721312, 37.75780357460139 ], [ -122.511645866365058, 37.757853388698848 ], [ -122.511694113049955, 37.758229588585031 ], [ -122.511742993592506, 37.758437524775317 ], [ -122.511846760303001, 37.759011245676845 ], [ -122.511857097632088, 37.75913743032006 ], [ -122.511865631081335, 37.759260898772276 ], [ -122.511870706825988, 37.759384426245269 ], [ -122.511867805759621, 37.759468945686322 ], [ -122.511886956563586, 37.759537293011533 ], [ -122.511894319592614, 37.759553649204342 ], [ -122.511899972344324, 37.759570721336786 ], [ -122.511907335379505, 37.759587077528693 ], [ -122.511912969200509, 37.759603463238605 ], [ -122.511924273692642, 37.75963760779338 ], [ -122.511928197239044, 37.759654709442891 ], [ -122.511933831069797, 37.759671095151603 ], [ -122.511941677473203, 37.759705298186844 ], [ -122.511943890051924, 37.759723115788141 ], [ -122.511947812915679, 37.759740217448552 ], [ -122.511954395243222, 37.75979161123707 ], [ -122.511954878597919, 37.75980945808243 ], [ -122.51195707224619, 37.759826589261372 ], [ -122.511958020381513, 37.759861597073268 ], [ -122.511956756283539, 37.759878787553092 ], [ -122.511957221055894, 37.759895948245067 ], [ -122.511955975187334, 37.759913824609214 ], [ -122.511953445935674, 37.759948205037048 ], [ -122.511904504571447, 37.760440750181104 ], [ -122.511896527982401, 37.760465608886555 ], [ -122.511890205925951, 37.76048769264321 ], [ -122.511883865275365, 37.760509089971798 ], [ -122.511879272104622, 37.760531144215527 ], [ -122.511874661032579, 37.760552512019558 ], [ -122.511865475369248, 37.760596620493857 ], [ -122.511856884580055, 37.760662694650442 ], [ -122.511852347147268, 37.760750675453501 ], [ -122.51185415039285, 37.760817258929578 ], [ -122.51185647452489, 37.76083919509459 ], [ -122.511857069411604, 37.760861160777161 ], [ -122.511859412136133, 37.760883783369522 ], [ -122.511863465519539, 37.760905690016429 ], [ -122.511865789657108, 37.760927626180887 ], [ -122.511869842698943, 37.760949532833308 ], [ -122.511875625684155, 37.760971409955765 ], [ -122.511879679077154, 37.760993316601748 ], [ -122.511885442786451, 37.761014507307813 ], [ -122.511897008433721, 37.761058261556201 ], [ -122.511926981734348, 37.761142906550255 ], [ -122.511936204314608, 37.76116403793533 ], [ -122.511954649505725, 37.76120630125228 ], [ -122.511963853517884, 37.761226746482016 ], [ -122.511996653008623, 37.761287993613372 ], [ -122.51202705467847, 37.761324558853467 ], [ -122.512064168550651, 37.761353455306384 ], [ -122.512085012456453, 37.761420400744697 ], [ -122.512126086793671, 37.761531638678171 ], [ -122.51214611271017, 37.76156838129576 ], [ -122.512202471774316, 37.761605190090663 ], [ -122.51220306676943, 37.761627155768551 ], [ -122.512286833872011, 37.761909350986301 ], [ -122.512330121233489, 37.762102273976545 ], [ -122.512359816513452, 37.762176622160425 ], [ -122.512400687481886, 37.762280309551663 ], [ -122.512412755489066, 37.762406464549109 ], [ -122.512472852658917, 37.762517377837653 ], [ -122.51242023100265, 37.762682408319826 ], [ -122.512395388918449, 37.762787217676667 ], [ -122.5123251575382, 37.763004741639399 ], [ -122.512318128822102, 37.763064608432281 ], [ -122.512275063725824, 37.763199258620396 ], [ -122.512347657434475, 37.763452114765002 ], [ -122.512370305614013, 37.763585643570899 ], [ -122.51245827791935, 37.763703634850181 ], [ -122.51249862900157, 37.763851969475809 ], [ -122.512536692779477, 37.763915873550189 ], [ -122.512547533618658, 37.763932857061576 ], [ -122.512582157093107, 37.763997506608895 ], [ -122.512647704491329, 37.764117941165772 ], [ -122.512695196290764, 37.764146660498668 ], [ -122.512734264345909, 37.764183764321103 ], [ -122.512737091420831, 37.764288101230889 ], [ -122.512775416013923, 37.764361614927097 ], [ -122.512777034178313, 37.764421334077248 ], [ -122.512751987430036, 37.764518593055108 ], [ -122.512781479493952, 37.764585390130065 ], [ -122.512774860188117, 37.764660358327284 ], [ -122.51278551563432, 37.764734344784699 ], [ -122.512761491760273, 37.764869356967338 ], [ -122.512743882579386, 37.764921850254765 ], [ -122.512717422071987, 37.76496694049434 ], [ -122.512684621435739, 37.765161162553468 ], [ -122.512669634563863, 37.765310442023306 ], [ -122.512654461644431, 37.765452857218328 ], [ -122.512646410035117, 37.765474970525723 ], [ -122.51263958593637, 37.765542388282462 ], [ -122.512631627607334, 37.765631800678314 ], [ -122.512652064044445, 37.76568364430058 ], [ -122.512693345840233, 37.765802432929902 ], [ -122.512704615499302, 37.765899071734495 ], [ -122.512725051707562, 37.765950915073049 ], [ -122.512700618145246, 37.766070825824592 ], [ -122.512768157254058, 37.76613697348678 ], [ -122.512789393732405, 37.766218333432967 ], [ -122.512782253924286, 37.766337948881301 ], [ -122.512802690660749, 37.766389792471095 ], [ -122.512831774306832, 37.766441488397447 ], [ -122.512880476379308, 37.766514825114292 ], [ -122.512887749430135, 37.76678321796404 ], [ -122.512892604360289, 37.766962375332177 ], [ -122.512931655400493, 37.766998792918841 ], [ -122.512961259283941, 37.767005841271946 ], [ -122.513017009002525, 37.767019997837075 ], [ -122.513065321321278, 37.767078919504563 ], [ -122.513078915989027, 37.767197493825428 ], [ -122.513107814109816, 37.767242325410123 ], [ -122.513081743662724, 37.767301830672046 ], [ -122.51310340834894, 37.767398978362927 ], [ -122.513123716454487, 37.7675098841043 ], [ -122.513078250436607, 37.767555985727952 ], [ -122.513071017262462, 37.76760830182581 ], [ -122.513091249913373, 37.767652594661705 ], [ -122.513076058720628, 37.767794323426109 ], [ -122.513058653709876, 37.76785436770038 ], [ -122.513040634413983, 37.767891759352018 ], [ -122.513052220422765, 37.767936200135772 ], [ -122.513081118807335, 37.767981031446666 ], [ -122.513072546502798, 37.768047792039297 ], [ -122.513084307659341, 37.768800949561466 ], [ -122.513105545245466, 37.768882309412746 ], [ -122.513112428659511, 37.76913628718308 ], [ -122.513087380753205, 37.769233545877434 ], [ -122.51312531757408, 37.76929264499158 ], [ -122.513091827049578, 37.769397602054994 ], [ -122.513085691671151, 37.769554284209093 ], [ -122.513145317112162, 37.770030553014749 ], [ -122.513104072344461, 37.770232473669928 ], [ -122.513083675037265, 37.770501338904026 ], [ -122.513146087813496, 37.77076123543462 ], [ -122.513127217898258, 37.771214121925311 ], [ -122.51313192303401, 37.771260053409627 ], [ -122.511055400217217, 37.771347479926334 ], [ -122.510495195112867, 37.771371059784514 ], [ -122.509894781266311, 37.771396329054475 ], [ -122.509894749698645, 37.771396032095495 ], [ -122.508867990729883, 37.771443447055226 ], [ -122.508823330922013, 37.771445496187766 ], [ -122.508780633594597, 37.771447480819759 ], [ -122.507750665782908, 37.771495034103232 ], [ -122.507750664399296, 37.771495034126787 ], [ -122.506991972993774, 37.771528822999358 ], [ -122.506685577396198, 37.771542340610623 ], [ -122.505610663486351, 37.771591537709703 ], [ -122.504544396347384, 37.771640577944709 ], [ -122.503472690942445, 37.771689380369601 ], [ -122.503472688521128, 37.771689380410749 ], [ -122.502399732131124, 37.771738148994636 ], [ -122.502399595949868, 37.771738155153713 ], [ -122.501328271899965, 37.77178724800941 ], [ -122.50025681025997, 37.771836337560785 ], [ -122.499183617695707, 37.771885446705888 ], [ -122.498113882732468, 37.771934487449556 ], [ -122.497047586931203, 37.771982773731914 ], [ -122.495972660610278, 37.77203188267292 ], [ -122.495971990075716, 37.772031913255347 ], [ -122.495972015995989, 37.772032015003532 ], [ -122.494903299427449, 37.772080795311069 ], [ -122.494902921073546, 37.772080894286702 ], [ -122.493833180817205, 37.772129896182108 ], [ -122.493832498174271, 37.772129926957135 ], [ -122.493832479293559, 37.77212966191987 ], [ -122.493316307377469, 37.772153213706439 ], [ -122.492761690869486, 37.772178231184974 ], [ -122.491688488076463, 37.772227272380952 ], [ -122.490617013718605, 37.772276274046327 ], [ -122.489539486385709, 37.772325343661755 ], [ -122.489539480863627, 37.772325473411478 ], [ -122.488471323394165, 37.772374169626119 ], [ -122.488470602075608, 37.772374307055074 ], [ -122.487399123482518, 37.77242327974907 ], [ -122.487398306387334, 37.772423316873983 ], [ -122.487398288926627, 37.772423078182946 ], [ -122.486678527930636, 37.772455879683093 ], [ -122.486334543235188, 37.772471439976606 ], [ -122.485261332297384, 37.772520422109807 ], [ -122.485152053625171, 37.772525429693054 ], [ -122.484194201052048, 37.772569061919981 ], [ -122.484194219251819, 37.772569315707138 ], [ -122.483119139145145, 37.772618264154566 ], [ -122.483119121668807, 37.772618024364625 ], [ -122.482048961146063, 37.772667157434498 ], [ -122.481947659007389, 37.77267180789157 ], [ -122.480978851643016, 37.772716079930653 ], [ -122.479904580478745, 37.772765580938412 ], [ -122.478848714480989, 37.772814028910268 ], [ -122.47883762078969, 37.772814536530767 ], [ -122.477766129509902, 37.772863421949047 ], [ -122.477069824745385, 37.772895630732648 ], [ -122.476694205661246, 37.772912858094969 ], [ -122.475613625589148, 37.772962410314676 ], [ -122.475613644972711, 37.772962670675476 ], [ -122.475612802305008, 37.772962709520556 ], [ -122.47455168367884, 37.773011391539193 ], [ -122.474550931211951, 37.773011425844139 ], [ -122.474550910293516, 37.773011133644289 ], [ -122.473737577092777, 37.773048416620476 ], [ -122.473427836194134, 37.773062613687692 ], [ -122.472297341321692, 37.773114423074823 ], [ -122.472219951938214, 37.773117969260745 ], [ -122.471762122569857, 37.773138947752848 ], [ -122.47124006836178, 37.773161981865066 ], [ -122.471240089434815, 37.773162280380667 ], [ -122.470162712316579, 37.773209791534974 ], [ -122.470162692059887, 37.773209510586071 ], [ -122.469517084069324, 37.773237987003441 ], [ -122.469092496140973, 37.773256712542192 ], [ -122.468348575135167, 37.773289518371953 ], [ -122.468023916207073, 37.773303833556405 ], [ -122.466952500298319, 37.773351323464517 ], [ -122.466951728424362, 37.773351356945881 ], [ -122.466373346760548, 37.773376599548058 ], [ -122.465883163277454, 37.773398204721545 ], [ -122.465331676538511, 37.773422509669793 ], [ -122.464811670328331, 37.773488744378348 ], [ -122.463755675044126, 37.773623241600568 ], [ -122.463749210177951, 37.773624065149079 ], [ -122.462683796526321, 37.773759751788596 ], [ -122.462535340858068, 37.773778657528119 ], [ -122.461620188227315, 37.77389548164836 ], [ -122.461619775372085, 37.773895534117642 ], [ -122.461619755154771, 37.7738952534419 ], [ -122.460552661960989, 37.774031133779268 ], [ -122.460552700773292, 37.774031358657659 ], [ -122.459486938645284, 37.774167197254911 ], [ -122.459486145343419, 37.77416729779403 ], [ -122.459486118442314, 37.77416693399686 ], [ -122.458795325509186, 37.774254885425997 ], [ -122.458371099123696, 37.774308895694055 ], [ -122.457134751103212, 37.774454490912483 ], [ -122.456283917173579, 37.774554834704738 ], [ -122.456283671603558, 37.774554866249353 ], [ -122.456283639851293, 37.774554711848936 ], [ -122.455898178089939, 37.774604132119862 ], [ -122.45525254468275, 37.774686906456331 ], [ -122.455033749574639, 37.77471323536281 ], [ -122.454683295611602, 37.774755406979118 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.454642052912618, 37.774646438033749 ], [ -122.454474386066082, 37.77382381381878 ], [ -122.454383433186777, 37.773377561015685 ], [ -122.454285116857562, 37.772895177759331 ], [ -122.454083217765842, 37.771930404296491 ], [ -122.454018233133866, 37.771616238745679 ], [ -122.453979522461594, 37.771428395389663 ], [ -122.453902454621286, 37.771053837806122 ], [ -122.453902388799008, 37.771053479872258 ], [ -122.453726227138517, 37.770098080109292 ], [ -122.45361853828534, 37.769567547950977 ], [ -122.453536875229432, 37.769165225996225 ], [ -122.453536875207789, 37.7691652251725 ], [ -122.453445551985865, 37.76871045824668 ], [ -122.453352918070834, 37.768246336273791 ], [ -122.453352855873561, 37.76824602387866 ], [ -122.453165568246916, 37.767306553969036 ], [ -122.453075053112784, 37.766861619151193 ], [ -122.452996675368553, 37.766476342546461 ], [ -122.452947274458694, 37.766374150789915 ], [ -122.454592351139084, 37.766165584782662 ], [ -122.454592931223331, 37.766165511159706 ], [ -122.455121527013688, 37.766098468178491 ], [ -122.456616092088495, 37.765909015262118 ], [ -122.456823880275138, 37.765884275950789 ], [ -122.456913422826119, 37.76587361486397 ], [ -122.457189364808954, 37.765880549298529 ], [ -122.457438759694597, 37.76592231265932 ], [ -122.457489671310725, 37.765930838098136 ], [ -122.45751185755033, 37.765937046103922 ], [ -122.457789624362519, 37.766014767234282 ], [ -122.457788627971766, 37.765820072733575 ], [ -122.457756133601251, 37.765354151194842 ], [ -122.457718187977903, 37.764810068512688 ], [ -122.457700034267219, 37.764549766072378 ], [ -122.457702640928829, 37.764546749210723 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.457827081738984, 37.763482288038738 ], [ -122.459790374597731, 37.762925938047687 ], [ -122.460842964058216, 37.762627642539876 ], [ -122.46084509456081, 37.762627108799968 ], [ -122.460684658382874, 37.760546523892096 ], [ -122.461770397151241, 37.760502041661596 ], [ -122.461770200794135, 37.760499231230455 ], [ -122.46284191307069, 37.760452254741963 ], [ -122.463911891482155, 37.760405344350922 ], [ -122.463781528402038, 37.758538181378015 ], [ -122.463651620775778, 37.756677450683235 ], [ -122.463554499932727, 37.755298102249476 ], [ -122.463580718915097, 37.755014148901829 ], [ -122.463627289771182, 37.754788065181785 ], [ -122.46367889740506, 37.754738571878264 ], [ -122.463722821262465, 37.754601920791259 ], [ -122.463838893966539, 37.75414750585891 ], [ -122.463842129576008, 37.753808596342253 ], [ -122.463777683069083, 37.753601657228067 ], [ -122.463640406958334, 37.753310851043111 ], [ -122.463399006031565, 37.752981452291863 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":23,\"ZIP_CODE\":94127,\"ID\":94127},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.458688935885434, 37.730751520241796 ], [ -122.459193144317396, 37.730602954490251 ], [ -122.459352156941819, 37.730515761910091 ], [ -122.459563710785162, 37.730368588301999 ], [ -122.459783802497654, 37.730109540441724 ], [ -122.459878403068345, 37.729884503080157 ], [ -122.459930163903692, 37.729706628889033 ], [ -122.459983752683073, 37.729522472170189 ], [ -122.460023966205156, 37.729195728883113 ], [ -122.460002483633559, 37.728739437629635 ], [ -122.460412720770037, 37.728739179843977 ], [ -122.460815517748841, 37.728680936862041 ], [ -122.460816315573013, 37.728681144164341 ], [ -122.460816314413918, 37.72868110023223 ], [ -122.460815856121073, 37.72868098095028 ], [ -122.46142222273329, 37.728330263725837 ], [ -122.461824751209633, 37.728118912555594 ], [ -122.462068476892256, 37.72804961327914 ], [ -122.462089038987699, 37.728047063364642 ], [ -122.462378284725844, 37.728011300874932 ], [ -122.462703368909061, 37.728024299159358 ], [ -122.462888197868679, 37.72805558978888 ], [ -122.46288814852555, 37.72805550243347 ], [ -122.462888304160828, 37.72805552868347 ], [ -122.462735876702908, 37.727784058577058 ], [ -122.462714226912738, 37.727738412010282 ], [ -122.462615761322709, 37.727529244060726 ], [ -122.462547067236983, 37.727225353979733 ], [ -122.462546035395761, 37.727106268284892 ], [ -122.462545638153259, 37.726997475605309 ], [ -122.46262180837526, 37.726335258944708 ], [ -122.462686726995429, 37.725774916753664 ], [ -122.462815474472038, 37.725482412532848 ], [ -122.462561310804759, 37.725391067720956 ], [ -122.46221285915388, 37.725265563681397 ], [ -122.462212628293415, 37.725265491161082 ], [ -122.462280269039496, 37.725147301470763 ], [ -122.462279803400207, 37.725042766873585 ], [ -122.462279017236384, 37.724866230039204 ], [ -122.462276854232826, 37.724380477322995 ], [ -122.462276505002905, 37.72427060589213 ], [ -122.462276504226139, 37.724270445482276 ], [ -122.46227362197132, 37.723637318172813 ], [ -122.462271183905713, 37.723021052793868 ], [ -122.462431367748181, 37.72300886812026 ], [ -122.462654991355222, 37.722947907918154 ], [ -122.46284032675716, 37.72280173266644 ], [ -122.463189003103793, 37.722604614839412 ], [ -122.463751413929884, 37.722395206040865 ], [ -122.463981569397532, 37.722327034291936 ], [ -122.464293670852172, 37.722113739171022 ], [ -122.464434759676408, 37.721907819106278 ], [ -122.464484214240997, 37.721680008850619 ], [ -122.465390989272279, 37.721673877359507 ], [ -122.46556367354151, 37.721672709140641 ], [ -122.4659783260431, 37.721669902411627 ], [ -122.466277766029194, 37.721667874912988 ], [ -122.46658847710232, 37.721665769467251 ], [ -122.467028877336375, 37.721662784936917 ], [ -122.467185158975667, 37.721661725062667 ], [ -122.467615322899292, 37.721658807641923 ], [ -122.467922730856742, 37.721656721708847 ], [ -122.468076091423683, 37.72165575669883 ], [ -122.468660023251203, 37.721651832680671 ], [ -122.468660429184538, 37.721651830291869 ], [ -122.468975070634144, 37.721649661930378 ], [ -122.469739202269693, 37.721644441148221 ], [ -122.469739672850537, 37.721644437949429 ], [ -122.469877506730057, 37.721643501943454 ], [ -122.470778213987501, 37.721637364239811 ], [ -122.47077894458495, 37.721637359433757 ], [ -122.47167373604637, 37.72163130643554 ], [ -122.471759517799796, 37.721671075214203 ], [ -122.471759949192219, 37.721671054807977 ], [ -122.471760271986454, 37.721671203510112 ], [ -122.471983398728909, 37.721660603850793 ], [ -122.472161906508475, 37.721641204494453 ], [ -122.472321961375684, 37.721623810905996 ], [ -122.472322354823248, 37.721623768058436 ], [ -122.472214746614711, 37.723731792113433 ], [ -122.472056885104323, 37.726824049746483 ], [ -122.471915497296948, 37.728810174564472 ], [ -122.47190716730239, 37.728927188202036 ], [ -122.471594286340178, 37.728902143540118 ], [ -122.47154865397853, 37.729783977184617 ], [ -122.471866468726006, 37.729784522464136 ], [ -122.471849871701991, 37.730103151702181 ], [ -122.471838445445158, 37.730322499137451 ], [ -122.471797435951885, 37.731032354724164 ], [ -122.471741907147091, 37.731926920887339 ], [ -122.471660927786132, 37.733231459533314 ], [ -122.47162189337304, 37.733860262567148 ], [ -122.471569341481938, 37.734706810815084 ], [ -122.471400819161929, 37.735001415396148 ], [ -122.471068098407827, 37.73560670794447 ], [ -122.470574087980822, 37.736589028767121 ], [ -122.470679955794623, 37.736646992229609 ], [ -122.470805946330771, 37.736773656566506 ], [ -122.470869802376484, 37.736951620802394 ], [ -122.470924096654457, 37.737574149498428 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.471094271054184, 37.739515149668783 ], [ -122.471225939871744, 37.741383106823285 ], [ -122.470104939284042, 37.741425112172415 ], [ -122.46903011455143, 37.741472060437133 ], [ -122.468671688881855, 37.741487738762387 ], [ -122.468561098363182, 37.741503345390441 ], [ -122.467747743753378, 37.743412622582262 ], [ -122.4668594971902, 37.743490862582995 ], [ -122.466043187720601, 37.743555382675872 ], [ -122.465806989998114, 37.743574050343277 ], [ -122.464736798394597, 37.743658625608425 ], [ -122.463685995946705, 37.743749409879626 ], [ -122.461381097416904, 37.745569497508143 ], [ -122.459174375277186, 37.74728632007151 ], [ -122.45872851739027, 37.746755429911865 ], [ -122.458610345848541, 37.746751806518411 ], [ -122.458509634961302, 37.746778882011192 ], [ -122.458499844031735, 37.746781514247793 ], [ -122.457949639713704, 37.746779214661537 ], [ -122.457582024204598, 37.746744476612157 ], [ -122.457457161051892, 37.746732677593577 ], [ -122.457301186299645, 37.746714064689691 ], [ -122.457166741019165, 37.746698021157933 ], [ -122.457089836487938, 37.746682896092409 ], [ -122.456314708164584, 37.746530446031059 ], [ -122.455687935660293, 37.746380796419501 ], [ -122.455466732538028, 37.746327980692172 ], [ -122.454664097215115, 37.746106168076913 ], [ -122.454664096869323, 37.746106168082655 ], [ -122.454184426826714, 37.745845539203287 ], [ -122.453916429980183, 37.745699922279499 ], [ -122.453916002908102, 37.745699915076791 ], [ -122.453645157964232, 37.745695560010326 ], [ -122.453495886255311, 37.745691252478657 ], [ -122.453376023619995, 37.74568779337109 ], [ -122.452386953750349, 37.745691424670198 ], [ -122.45222754649042, 37.74569200904012 ], [ -122.452091117231191, 37.74568511552728 ], [ -122.451828632556612, 37.745671851179097 ], [ -122.45181666119403, 37.745671246259803 ], [ -122.45169226258335, 37.745606748366605 ], [ -122.451624634588612, 37.745534387540218 ], [ -122.451624359716789, 37.745534037461439 ], [ -122.450586742649079, 37.744213121547148 ], [ -122.450592179132485, 37.744090792307063 ], [ -122.449918612648133, 37.7432716748716 ], [ -122.449790244582587, 37.743257317336237 ], [ -122.449582834428256, 37.743065029012413 ], [ -122.449355958412127, 37.74292182098899 ], [ -122.44913731277687, 37.742828607669701 ], [ -122.448820428983325, 37.742748693538836 ], [ -122.44848892294759, 37.742704730693937 ], [ -122.447384659150103, 37.742608987228614 ], [ -122.447110638216571, 37.742580552169777 ], [ -122.446840940400392, 37.742519081676662 ], [ -122.446657325560608, 37.742442453734959 ], [ -122.446516770315029, 37.742358933848621 ], [ -122.446410971977912, 37.742281706494659 ], [ -122.446323687641325, 37.742184944882247 ], [ -122.446256735689062, 37.742072052701658 ], [ -122.44619443459527, 37.741872554265036 ], [ -122.446156199006268, 37.741535311478742 ], [ -122.44609965968975, 37.741357693405078 ], [ -122.44605943275991, 37.741274575873817 ], [ -122.445964300022951, 37.741198753406074 ], [ -122.445964326755799, 37.741198730714288 ], [ -122.445897881975142, 37.741116602917764 ], [ -122.445523457193502, 37.740972470791057 ], [ -122.445195592772691, 37.740920623913645 ], [ -122.444911120433531, 37.740872114678645 ], [ -122.444681667799003, 37.740793092051724 ], [ -122.444518249198694, 37.740692998244214 ], [ -122.444366170223589, 37.740575544006177 ], [ -122.444209640252396, 37.740380617798955 ], [ -122.444098337188322, 37.740234069945657 ], [ -122.443922038304507, 37.740088462716592 ], [ -122.443694223373313, 37.739971345925731 ], [ -122.44341918472557, 37.739902333376541 ], [ -122.443019593631774, 37.739814475654143 ], [ -122.442856748556963, 37.739767263698369 ], [ -122.44271760477325, 37.739682237717481 ], [ -122.442597107319372, 37.739547858254923 ], [ -122.442525191527608, 37.739448400155375 ], [ -122.44250398997913, 37.739238469269011 ], [ -122.442627783322195, 37.738564600951854 ], [ -122.442625226229453, 37.738313911390271 ], [ -122.442579349780331, 37.738171569110513 ], [ -122.44244617419956, 37.737799536516135 ], [ -122.442403843641571, 37.737639851367689 ], [ -122.442421017736137, 37.737438198043598 ], [ -122.442488947503193, 37.737289169592358 ], [ -122.442657322787326, 37.737118881702763 ], [ -122.443602235926093, 37.736502583404238 ], [ -122.443683487927487, 37.73653803376142 ], [ -122.443842427221611, 37.736579809812575 ], [ -122.444038854214782, 37.736575290313169 ], [ -122.444187688286519, 37.736528330103852 ], [ -122.444306066715512, 37.736409368208768 ], [ -122.444405709994072, 37.736218559056141 ], [ -122.444433832419023, 37.736070356056409 ], [ -122.444478073435704, 37.735720552668923 ], [ -122.444456723960371, 37.735604611251425 ], [ -122.444414926545633, 37.735547154436588 ], [ -122.444280987817251, 37.735484218369663 ], [ -122.444190291728404, 37.735407445883219 ], [ -122.443868794328736, 37.73522293273804 ], [ -122.443498916758756, 37.735041781619209 ], [ -122.443241645458286, 37.734935120886668 ], [ -122.44298838327488, 37.734915120161688 ], [ -122.442971182760076, 37.734564427784264 ], [ -122.444336617762318, 37.734557437927307 ], [ -122.445353637361492, 37.734548793463254 ], [ -122.445350300553969, 37.734322189420951 ], [ -122.445385609625191, 37.734245207403205 ], [ -122.445695180894745, 37.733970768223749 ], [ -122.445782219446897, 37.733850288395629 ], [ -122.445605378551107, 37.733800711254226 ], [ -122.444320121334613, 37.733811072362919 ], [ -122.444320397855094, 37.733087687518228 ], [ -122.44431434932487, 37.732346901474607 ], [ -122.444299518643774, 37.732277163251723 ], [ -122.444288854528963, 37.731568915105107 ], [ -122.446578400413941, 37.7315490470612 ], [ -122.447291821211039, 37.731547153756608 ], [ -122.448866265260094, 37.731542959168735 ], [ -122.451146666771351, 37.731536846888879 ], [ -122.453411343594681, 37.731530646828205 ], [ -122.453590911574565, 37.731488405776453 ], [ -122.454915878760758, 37.731464997636976 ], [ -122.455762555159509, 37.731348802743547 ], [ -122.456607498028021, 37.731187733113579 ], [ -122.457474238952088, 37.730997495241198 ], [ -122.458222500186622, 37.73077738189027 ], [ -122.458472470688633, 37.730752827866375 ], [ -122.458688935885434, 37.730751520241796 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":24,\"ZIP_CODE\":94117,\"ID\":94117},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.456669912105653, 37.763814688946653 ], [ -122.45765013969168, 37.76353311613984 ], [ -122.457702640928829, 37.764546749210723 ], [ -122.457700034267219, 37.764549766072378 ], [ -122.457718187977903, 37.764810068512688 ], [ -122.457756133601251, 37.765354151194842 ], [ -122.457788627971766, 37.765820072733575 ], [ -122.457789624362519, 37.766014767234282 ], [ -122.45751185755033, 37.765937046103922 ], [ -122.457489349789753, 37.765930717627668 ], [ -122.457438759694597, 37.76592231265932 ], [ -122.457189364808954, 37.765880549298529 ], [ -122.456913422826119, 37.76587361486397 ], [ -122.456823880275138, 37.765884275950789 ], [ -122.456616092088495, 37.765909015262118 ], [ -122.455121527013688, 37.766098468178491 ], [ -122.454592923214804, 37.766165469813686 ], [ -122.452947253369459, 37.766374046206209 ], [ -122.452946199816822, 37.766368851617294 ], [ -122.452945976378615, 37.766368855319321 ], [ -122.452947033686613, 37.766374074293772 ], [ -122.452996080750566, 37.76647606094825 ], [ -122.453164789642727, 37.767306895129927 ], [ -122.453352288400112, 37.768246025041712 ], [ -122.453445551985865, 37.76871045824668 ], [ -122.453536875207789, 37.7691652251725 ], [ -122.453536875229432, 37.769165225996225 ], [ -122.45361853828534, 37.769567547950977 ], [ -122.453726227138517, 37.770098080109292 ], [ -122.453902388799008, 37.771053479872258 ], [ -122.453902454621286, 37.771053837806122 ], [ -122.453979522461594, 37.771428395389663 ], [ -122.454018233133866, 37.771616238745679 ], [ -122.454083217765842, 37.771930404296491 ], [ -122.454285116857562, 37.772895177759331 ], [ -122.454383433186777, 37.773377561015685 ], [ -122.454474386066082, 37.77382381381878 ], [ -122.454642052912618, 37.774646438033749 ], [ -122.45468264773983, 37.77475548474694 ], [ -122.453003330637316, 37.774970754669006 ], [ -122.452810237317621, 37.774995318605924 ], [ -122.452963825070867, 37.775750129254348 ], [ -122.45318832797058, 37.776853425180988 ], [ -122.453384996187339, 37.777826863341367 ], [ -122.452431111623397, 37.777939652339285 ], [ -122.451543139139602, 37.778052106408275 ], [ -122.450826415883952, 37.778142868140662 ], [ -122.450654812483819, 37.778164598609756 ], [ -122.449767079183658, 37.778277008331997 ], [ -122.448885763577039, 37.778388598994987 ], [ -122.448879206948348, 37.778389428728616 ], [ -122.447997721370214, 37.778501033647089 ], [ -122.447039806751391, 37.778622307248391 ], [ -122.446846469920573, 37.777669108909684 ], [ -122.445155752336433, 37.777886506805444 ], [ -122.443506919728407, 37.778089213415292 ], [ -122.441865967226732, 37.778299511701405 ], [ -122.441865967212379, 37.778299511152262 ], [ -122.441865640558632, 37.778299552792262 ], [ -122.441865640580161, 37.778299553615973 ], [ -122.440213334218711, 37.778511463388014 ], [ -122.43855227772093, 37.778724107938281 ], [ -122.437707163233824, 37.778832380147421 ], [ -122.436884629197735, 37.778937753600381 ], [ -122.435241012398947, 37.779144878458894 ], [ -122.433595938040227, 37.779354585436536 ], [ -122.431951802315041, 37.779564148987781 ], [ -122.430233236584385, 37.779782794230314 ], [ -122.430047265291023, 37.778850928515844 ], [ -122.429849199803343, 37.777919295570904 ], [ -122.429686883301301, 37.776976022351889 ], [ -122.429626330769736, 37.776513151917612 ], [ -122.429622603726443, 37.776331628240193 ], [ -122.42955405641294, 37.77604428584678 ], [ -122.429365859793393, 37.775111180032617 ], [ -122.429272630354163, 37.774648924607646 ], [ -122.429178384487102, 37.774181626717471 ], [ -122.429178383795261, 37.774181626728783 ], [ -122.42917834319843, 37.774181422747475 ], [ -122.428990051967787, 37.773247800757233 ], [ -122.428802101899663, 37.772315846507482 ], [ -122.428708105393241, 37.771849753810166 ], [ -122.428614043687816, 37.771383330257258 ], [ -122.428520173517541, 37.770917853912216 ], [ -122.428426180541749, 37.770451760600665 ], [ -122.428428952948465, 37.770451409238284 ], [ -122.428218351470321, 37.769440923680406 ], [ -122.428991181191208, 37.769394223715665 ], [ -122.429127985682982, 37.769456128057627 ], [ -122.429905643091914, 37.769407921549679 ], [ -122.430247361256178, 37.769392261627225 ], [ -122.431356624058139, 37.76932091157898 ], [ -122.431570380896062, 37.769307161387864 ], [ -122.432464341208131, 37.769249650838944 ], [ -122.433572207534368, 37.769178369516744 ], [ -122.43489319213792, 37.769113797429938 ], [ -122.435794213398353, 37.769058453615109 ], [ -122.436588267341477, 37.76900080227221 ], [ -122.436625219679314, 37.768918977544402 ], [ -122.436478247038266, 37.767274662557512 ], [ -122.436980399947345, 37.767244348801164 ], [ -122.43729899343451, 37.767221930124791 ], [ -122.437291829491301, 37.767045826510717 ], [ -122.437288601235366, 37.766922046842083 ], [ -122.437333800177683, 37.766762171805773 ], [ -122.4374853951944, 37.766644323028956 ], [ -122.437684727134425, 37.766587115085294 ], [ -122.437879374385105, 37.76661079550852 ], [ -122.438219625977609, 37.766701399454838 ], [ -122.438291383601225, 37.766725780787596 ], [ -122.438386133953244, 37.766664738151036 ], [ -122.438555364477878, 37.766297386980668 ], [ -122.439501839688162, 37.766575524599709 ], [ -122.441241683591315, 37.765270858517731 ], [ -122.441931912880605, 37.765295099009755 ], [ -122.442086687309455, 37.765324419445463 ], [ -122.443210588891787, 37.765339994614024 ], [ -122.443347278899211, 37.765332794099429 ], [ -122.443263297846215, 37.765217752592115 ], [ -122.443278634379993, 37.764726164248771 ], [ -122.443215275433289, 37.764557545459958 ], [ -122.443199827408847, 37.764468898600072 ], [ -122.44294975946913, 37.764109072128207 ], [ -122.442951036556252, 37.763677959114567 ], [ -122.443247528217242, 37.763690608015906 ], [ -122.443434935848089, 37.763875622244342 ], [ -122.44441170822752, 37.76398692869914 ], [ -122.444663866724483, 37.763326501903514 ], [ -122.445692026290132, 37.762347953618033 ], [ -122.445825769194499, 37.762264137786076 ], [ -122.445930590545728, 37.762233223779305 ], [ -122.446167833254592, 37.762219287792306 ], [ -122.446246565080784, 37.762246873735464 ], [ -122.44628722735257, 37.762310164748847 ], [ -122.446391621016517, 37.76223195154369 ], [ -122.446401427721412, 37.762069683149655 ], [ -122.446384044294348, 37.761816426422072 ], [ -122.44642928686055, 37.76181245987528 ], [ -122.446783402935864, 37.761781413739634 ], [ -122.446783403627563, 37.761781413728215 ], [ -122.44652973647915, 37.761396213495424 ], [ -122.446409895656728, 37.761094344377824 ], [ -122.446433636964471, 37.761006719141555 ], [ -122.446579328750119, 37.76090155472 ], [ -122.446866213158088, 37.76042156830691 ], [ -122.446952816919733, 37.760170129150154 ], [ -122.447157396221257, 37.759660681065917 ], [ -122.447394862769713, 37.759372842727622 ], [ -122.447681986744499, 37.759189835272018 ], [ -122.448080121328474, 37.759090506458826 ], [ -122.448534508559618, 37.759045234831405 ], [ -122.448576884007139, 37.759648639321185 ], [ -122.449214958651069, 37.759610853827809 ], [ -122.451553087455522, 37.759472365763493 ], [ -122.451578365088082, 37.759597660221687 ], [ -122.451956989358152, 37.761478249938222 ], [ -122.451957007208804, 37.761478336720671 ], [ -122.452096188532394, 37.76216681475799 ], [ -122.452096203587132, 37.762166887302627 ], [ -122.452181289886042, 37.762586053251901 ], [ -122.452233043763783, 37.762842560862168 ], [ -122.452399889409662, 37.763669482365366 ], [ -122.452569353429794, 37.764509361471809 ], [ -122.452569580477117, 37.764510482582956 ], [ -122.452569807022613, 37.764510478830069 ], [ -122.452569575903098, 37.764509334437378 ], [ -122.45351591398105, 37.764395394431268 ], [ -122.453114639516684, 37.762361563728888 ], [ -122.453744673977482, 37.761878784498755 ], [ -122.454009611320302, 37.762667869264533 ], [ -122.454946081907835, 37.7631787014639 ], [ -122.454945541962573, 37.76366305305779 ], [ -122.454845151642289, 37.763732313486969 ], [ -122.454179864833876, 37.763912200042988 ], [ -122.454217265424234, 37.764310945798535 ], [ -122.455466091062405, 37.764160565952579 ], [ -122.455781065686594, 37.764070070210742 ], [ -122.456669912105653, 37.763814688946653 ] ] ] }},\n{\"type\": \"Feature\", \"properties\": {\"OBJECTID\":25,\"ZIP_CODE\":94132,\"ID\":94132},\"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -122.508287627239582, 37.733266432915535 ], [ -122.508283610098232, 37.733309766760698 ], [ -122.508258422649121, 37.733337665998171 ], [ -122.50825465624375, 37.73339026610973 ], [ -122.508261381386347, 37.733447151520778 ], [ -122.508260632832361, 37.733547429417492 ], [ -122.508268595371646, 37.733586094926054 ], [ -122.508272540744613, 37.733668094001438 ], [ -122.508265010681669, 37.733709427205042 ], [ -122.508286802179072, 37.733747856989588 ], [ -122.508296363517715, 37.733781688010566 ], [ -122.508300683356651, 37.733813548133803 ], [ -122.508285681955428, 37.733834406267761 ], [ -122.508267344574577, 37.733859785121595 ], [ -122.508247213289522, 37.733882790933777 ], [ -122.508237692618366, 37.73429843541993 ], [ -122.508237373460645, 37.734350633938469 ], [ -122.5082471948962, 37.734394074716711 ], [ -122.508256635722688, 37.734423443934411 ], [ -122.508245332959405, 37.734453167005526 ], [ -122.50826415897464, 37.734509845871564 ], [ -122.508256016313609, 37.734528526845153 ], [ -122.50825693455738, 37.734562505189558 ], [ -122.508280334704608, 37.734596443696226 ], [ -122.508271010987045, 37.734635403850064 ], [ -122.508266062787172, 37.734708283416225 ], [ -122.508267732334318, 37.734770062222154 ], [ -122.508272423230224, 37.734815650964592 ], [ -122.508314825176313, 37.734912789977905 ], [ -122.508324461220354, 37.734949366434215 ], [ -122.508350693626355, 37.735024118117309 ], [ -122.508408601293198, 37.735055064766357 ], [ -122.508454835865138, 37.735102005566297 ], [ -122.508462371966132, 37.735124883136479 ], [ -122.508476933993705, 37.735151761694588 ], [ -122.508477796681021, 37.735183680465923 ], [ -122.508497058717865, 37.735256490973484 ], [ -122.508537000388259, 37.735326545054548 ], [ -122.508539031954456, 37.735401708981378 ], [ -122.508528202252251, 37.735448935792135 ], [ -122.506718399360096, 37.735463437562494 ], [ -122.506718392445464, 37.735463437680252 ], [ -122.506717419598758, 37.735469757763546 ], [ -122.506541776696594, 37.735470423847289 ], [ -122.506142139643117, 37.735471938745221 ], [ -122.505711404999388, 37.735473569905054 ], [ -122.505257352091178, 37.735475287872006 ], [ -122.504189206002195, 37.735470928034005 ], [ -122.503104759187963, 37.735466491452826 ], [ -122.50249812714712, 37.735433799348435 ], [ -122.50206735537283, 37.735396211199387 ], [ -122.502021315734993, 37.73539219398544 ], [ -122.501556481916552, 37.735330499723666 ], [ -122.501555214836827, 37.735330188314435 ], [ -122.501291847576297, 37.735265403511711 ], [ -122.501214897179494, 37.735242075875206 ], [ -122.501097483348147, 37.735206481207975 ], [ -122.501097483002411, 37.735206481213844 ], [ -122.500970549161394, 37.735174012383368 ], [ -122.500888239775747, 37.73515295885273 ], [ -122.500650675572956, 37.735059919015036 ], [ -122.500571951887039, 37.735029088020617 ], [ -122.500289079616721, 37.734896880574155 ], [ -122.500140914702612, 37.734827630984967 ], [ -122.499430330098079, 37.734471850699563 ], [ -122.499196335607635, 37.734311616338935 ], [ -122.499180283, 37.734301056816975 ], [ -122.499086082983126, 37.734239092175393 ], [ -122.498948533172893, 37.734128137325406 ], [ -122.498483756692679, 37.733969859923228 ], [ -122.49837749236211, 37.733942575660954 ], [ -122.498050148013945, 37.73385852689691 ], [ -122.497707486783483, 37.733793124978028 ], [ -122.497675024009141, 37.73378860832171 ], [ -122.497251243304476, 37.7337296478455 ], [ -122.4970611531011, 37.733738715676616 ], [ -122.496807213285692, 37.733750829171804 ], [ -122.496636264393828, 37.733770812006838 ], [ -122.496636262010625, 37.733770813420726 ], [ -122.496635997165527, 37.733770842906516 ], [ -122.496635997172902, 37.733770843181105 ], [ -122.496635991376749, 37.733770846301013 ], [ -122.496448643026866, 37.733877460645239 ], [ -122.494403119131789, 37.733960247035334 ], [ -122.493941605376719, 37.733981316545631 ], [ -122.493941607430031, 37.733981341508411 ], [ -122.494014877350082, 37.735023413250019 ], [ -122.4940149282546, 37.735024137316628 ], [ -122.493857150107345, 37.735028823489081 ], [ -122.493857076611491, 37.73502785037892 ], [ -122.493407285267097, 37.734856242527158 ], [ -122.493406966558723, 37.734856121008917 ], [ -122.493274340840912, 37.734785121286002 ], [ -122.492843999903116, 37.734842083695071 ], [ -122.492330945602859, 37.73504482611267 ], [ -122.492142715245791, 37.735181846965403 ], [ -122.491805731315921, 37.735279805981911 ], [ -122.491730212101302, 37.735301758325164 ], [ -122.491320518441114, 37.735327019185135 ], [ -122.490973609504408, 37.735348171552815 ], [ -122.490725734916396, 37.735399781312303 ], [ -122.490526224357907, 37.735470000646821 ], [ -122.490270108429897, 37.735606337180243 ], [ -122.490460674310157, 37.735842274774221 ], [ -122.490521485540071, 37.736028873151284 ], [ -122.490552748713441, 37.736251597078756 ], [ -122.490686267381363, 37.736541968564268 ], [ -122.491162508402653, 37.737157782139001 ], [ -122.491211452414888, 37.737221069184358 ], [ -122.491334899611488, 37.737293023539365 ], [ -122.491198309070924, 37.737311342176547 ], [ -122.491113859834144, 37.737397356998116 ], [ -122.490894410355324, 37.737610557460023 ], [ -122.490695166321217, 37.737732995785407 ], [ -122.490475969759899, 37.737832474268195 ], [ -122.490364187143001, 37.737929559098433 ], [ -122.489970938868055, 37.737956983380037 ], [ -122.489794342484018, 37.737821521499782 ], [ -122.489559638156635, 37.737725144708122 ], [ -122.489310346380378, 37.737544144541232 ], [ -122.48917282500021, 37.737387715271872 ], [ -122.488933343471572, 37.73715804150379 ], [ -122.488518054177234, 37.737001890327612 ], [ -122.488261361358497, 37.736979897727672 ], [ -122.487883946457515, 37.736972903025247 ], [ -122.487473746906744, 37.73696000969079 ], [ -122.486865148034397, 37.736893072836374 ], [ -122.486239320534182, 37.736758103009961 ], [ -122.485767077957547, 37.736757243866982 ], [ -122.485398323256604, 37.736886930845941 ], [ -122.485055057260084, 37.737081691563745 ], [ -122.484981330342137, 37.737106613405395 ], [ -122.481727269492282, 37.73717192680251 ], [ -122.480654887744791, 37.73721905491567 ], [ -122.479601690542523, 37.737266045133403 ], [ -122.478500149938654, 37.73731300537402 ], [ -122.477443897267278, 37.737360110526431 ], [ -122.476368270747585, 37.737407342172453 ], [ -122.475288554960258, 37.737454742960537 ], [ -122.474232714891599, 37.737476308128976 ], [ -122.47327754880429, 37.737518149435381 ], [ -122.473161338869033, 37.737550437503423 ], [ -122.472088826157801, 37.737598476381557 ], [ -122.470962730039105, 37.737648904688974 ], [ -122.470924096654457, 37.737574149498428 ], [ -122.470869802376484, 37.736951620802394 ], [ -122.470805946330771, 37.736773656566506 ], [ -122.470679955794623, 37.736646992229609 ], [ -122.470574087980822, 37.736589028767121 ], [ -122.471068098407827, 37.73560670794447 ], [ -122.471400819161929, 37.735001415396148 ], [ -122.471569341481938, 37.734706810815084 ], [ -122.47162189337304, 37.733860262567148 ], [ -122.471660927786132, 37.733231459533314 ], [ -122.471741907147091, 37.731926920887339 ], [ -122.471797435951885, 37.731032354724164 ], [ -122.471838445445158, 37.730322499137451 ], [ -122.471849871701991, 37.730103151702181 ], [ -122.471866468726006, 37.729784522464136 ], [ -122.47154865397853, 37.729783977184617 ], [ -122.471594286340178, 37.728902143540118 ], [ -122.47190716730239, 37.728927188202036 ], [ -122.471915497296948, 37.728810174564472 ], [ -122.472056885104323, 37.726824049746483 ], [ -122.472214746614711, 37.723731792113433 ], [ -122.472322354823248, 37.721623768058436 ], [ -122.472321961375684, 37.721623810905996 ], [ -122.472161906508475, 37.721641204494453 ], [ -122.471983398728909, 37.721660603850793 ], [ -122.471760271986454, 37.721671203510112 ], [ -122.471759949192219, 37.721671054807977 ], [ -122.471673586030391, 37.721631204836193 ], [ -122.470779028465699, 37.721637301442819 ], [ -122.469876768913451, 37.721643443134909 ], [ -122.469739691960783, 37.721644376097736 ], [ -122.46897449401726, 37.721649578720722 ], [ -122.468660059213846, 37.721651715058712 ], [ -122.46807558495307, 37.721655684399515 ], [ -122.467922730856742, 37.721656721708847 ], [ -122.467615322899292, 37.721658807641923 ], [ -122.467185158975667, 37.721661725062667 ], [ -122.467028877336375, 37.721662784936917 ], [ -122.46658847710232, 37.721665769467251 ], [ -122.466277766029194, 37.721667874912988 ], [ -122.4659783260431, 37.721669902411627 ], [ -122.465564132461637, 37.72167263116085 ], [ -122.465391251749992, 37.721673798812127 ], [ -122.465390987937923, 37.721673800741335 ], [ -122.46448421418296, 37.721680006654026 ], [ -122.464483630192618, 37.721680010624304 ], [ -122.464483033412463, 37.721680014807887 ], [ -122.464479920172778, 37.721680035685075 ], [ -122.464479920230843, 37.721680037881669 ], [ -122.464483033470529, 37.721680017004481 ], [ -122.464484214240997, 37.721680008850619 ], [ -122.464434759676408, 37.721907819106278 ], [ -122.464293670852172, 37.722113739171022 ], [ -122.463981569397532, 37.722327034291936 ], [ -122.463751413929884, 37.722395206040865 ], [ -122.463189003103793, 37.722604614839412 ], [ -122.46284032675716, 37.72280173266644 ], [ -122.462654991355222, 37.722947907918154 ], [ -122.462431367748181, 37.72300886812026 ], [ -122.462271183905713, 37.723021052793868 ], [ -122.462271183761132, 37.723021008020723 ], [ -122.462265621163738, 37.721827887159478 ], [ -122.46226557117231, 37.721695003479546 ], [ -122.462267174469247, 37.721694992704748 ], [ -122.462267173874807, 37.721694970189525 ], [ -122.462264896007014, 37.721694985883083 ], [ -122.462254399326852, 37.72000857322422 ], [ -122.46226246554707, 37.719928593578096 ], [ -122.462245294921956, 37.718767790464547 ], [ -122.462244936309219, 37.718219524954932 ], [ -122.46263854285489, 37.718211149542661 ], [ -122.462639625007327, 37.717936035524978 ], [ -122.462638771382373, 37.717866433871905 ], [ -122.462633312624419, 37.717421207682776 ], [ -122.46262477066405, 37.716570820282072 ], [ -122.462620481465109, 37.716143794339722 ], [ -122.462615213395566, 37.71561930379395 ], [ -122.462607827629938, 37.714883950429027 ], [ -122.462602039439204, 37.714307653173123 ], [ -122.462588724439357, 37.714029538822885 ], [ -122.462554985317752, 37.713903926321834 ], [ -122.46256899895387, 37.713150165038506 ], [ -122.462580658473911, 37.712523033743715 ], [ -122.462581299448402, 37.712273520359908 ], [ -122.462570051320355, 37.711370301468136 ], [ -122.462063256954337, 37.711372729296876 ], [ -122.462545712573657, 37.711171028559598 ], [ -122.462554670587338, 37.71117015272295 ], [ -122.462558461843784, 37.710567432270715 ], [ -122.462893196389857, 37.710562754126848 ], [ -122.464175624997623, 37.710548242222522 ], [ -122.464766855714529, 37.710481381431755 ], [ -122.465994326901708, 37.710153924678359 ], [ -122.466791287837083, 37.709825747176509 ], [ -122.467161376112116, 37.709634146029515 ], [ -122.467717389066479, 37.709282172736899 ], [ -122.468442186669975, 37.708710365091008 ], [ -122.468469108419114, 37.708682445380624 ], [ -122.468919327757234, 37.708227236298555 ], [ -122.468936765178938, 37.708230944115137 ], [ -122.469206218211681, 37.708201815905348 ], [ -122.469249900139204, 37.708201720375776 ], [ -122.469298321590273, 37.708202284245147 ], [ -122.470831636978247, 37.708197238849166 ], [ -122.471323400170547, 37.708198733212157 ], [ -122.471333181497599, 37.708198762882461 ], [ -122.471352032181699, 37.708198820402657 ], [ -122.472828567745367, 37.708203294119777 ], [ -122.475753134738511, 37.70818173979459 ], [ -122.478975890370975, 37.708191500643373 ], [ -122.481681250730091, 37.708183096324262 ], [ -122.482448808625776, 37.708181853858243 ], [ -122.484067695750653, 37.708165047020167 ], [ -122.485639305786634, 37.708148710071598 ], [ -122.485678792700227, 37.708148575886831 ], [ -122.485857818531997, 37.708147968478251 ], [ -122.493439707434533, 37.708121990011122 ], [ -122.498399514345707, 37.70810473139138 ], [ -122.502773696653762, 37.708089337426266 ], [ -122.502794335644097, 37.708342053909199 ], [ -122.502870002532575, 37.708519665129593 ], [ -122.502919824088835, 37.708764674445675 ], [ -122.502952891728171, 37.708965329397188 ], [ -122.502970627807798, 37.709110275269509 ], [ -122.503182081004425, 37.70964337370075 ], [ -122.503220569589601, 37.710045154472041 ], [ -122.503285115419118, 37.710323219970874 ], [ -122.503377921169559, 37.710623467751866 ], [ -122.503454573963339, 37.710901670868985 ], [ -122.50338876186548, 37.711025718699396 ], [ -122.503480367063446, 37.71128134783477 ], [ -122.503538681827621, 37.71139229600675 ], [ -122.503607152393855, 37.711815885652477 ], [ -122.50392442753342, 37.711977024740428 ], [ -122.50398926023928, 37.712137307747028 ], [ -122.504018884937224, 37.712338364595105 ], [ -122.504050960348835, 37.712566162951994 ], [ -122.504083766615167, 37.712821075451494 ], [ -122.50415623472351, 37.713008011664755 ], [ -122.504154839245047, 37.713276897967987 ], [ -122.504262407999079, 37.713483152309799 ], [ -122.504310222769533, 37.713717550242542 ], [ -122.504341216541519, 37.713905192080865 ], [ -122.504415861257201, 37.714172784107312 ], [ -122.504527957268053, 37.714546871585753 ], [ -122.504619249107378, 37.714854698366167 ], [ -122.504734681128681, 37.715160054242361 ], [ -122.50484307775433, 37.715396854580646 ], [ -122.504983687289311, 37.715738179098203 ], [ -122.505015183177221, 37.715944354142479 ], [ -122.505064940330996, 37.716122405267939 ], [ -122.505277983345735, 37.716713159786387 ], [ -122.505378665806077, 37.716920217389642 ], [ -122.50544954257559, 37.71711198714339 ], [ -122.505534236338548, 37.717303178231596 ], [ -122.505550939969169, 37.717537761949714 ], [ -122.505627533936476, 37.717813217613234 ], [ -122.505733499538735, 37.718087829999391 ], [ -122.505795361000153, 37.718329885441641 ], [ -122.50575895814147, 37.718518674479142 ], [ -122.505846981170734, 37.71870500140907 ], [ -122.505947348818779, 37.718836178428148 ], [ -122.50603889798937, 37.719089059528848 ], [ -122.50604605833162, 37.719354365984017 ], [ -122.506067045042258, 37.719619436964734 ], [ -122.506098310388239, 37.719752820508575 ], [ -122.506085112584401, 37.71990447335908 ], [ -122.506073138870676, 37.719973352141601 ], [ -122.506058509098679, 37.720136017347265 ], [ -122.506066050462266, 37.720287316996625 ], [ -122.506093067115529, 37.720455453693035 ], [ -122.506119272327297, 37.720657598256366 ], [ -122.506215620201345, 37.720895976024821 ], [ -122.50629167450829, 37.721023102978798 ], [ -122.506401700968937, 37.721383832251519 ], [ -122.506521510466683, 37.721658550935217 ], [ -122.507655277014294, 37.728423963999113 ], [ -122.507663331414165, 37.728466062015535 ], [ -122.507703574614794, 37.728547442310145 ], [ -122.507748947428013, 37.728626675201951 ], [ -122.507757146370523, 37.728738132019352 ], [ -122.507784794329567, 37.728865395952631 ], [ -122.50778664879924, 37.728934039418284 ], [ -122.507794848177895, 37.729045496221595 ], [ -122.507815341525188, 37.729163954079155 ], [ -122.50786205937635, 37.729292953229624 ], [ -122.507900514994574, 37.72943617151067 ], [ -122.507937978539388, 37.729542665673854 ], [ -122.507979524242614, 37.729608229023043 ], [ -122.508001314382625, 37.729646658611678 ], [ -122.508029211854279, 37.729655111115953 ], [ -122.5080286514508, 37.729698385786008 ], [ -122.508045757198687, 37.729755437677454 ], [ -122.508054266358684, 37.729814352687157 ], [ -122.508053396658397, 37.729910168841855 ], [ -122.508060436991443, 37.729978723905262 ], [ -122.508062096980282, 37.730040159271667 ], [ -122.508075550679465, 37.730090062827138 ], [ -122.508077646560039, 37.730167629612303 ], [ -122.508084862782624, 37.730242705229408 ], [ -122.508102701340974, 37.730326871159861 ], [ -122.508101275259605, 37.730402094356123 ], [ -122.508118432954731, 37.730525073140598 ], [ -122.508154289380698, 37.730636058484791 ], [ -122.508159291744406, 37.730757184482151 ], [ -122.508147188358009, 37.730821258058114 ], [ -122.508157490852682, 37.730882546377835 ], [ -122.508176687224534, 37.73095295419229 ], [ -122.508155505168105, 37.731001044217557 ], [ -122.508070616958207, 37.731059491187089 ], [ -122.508063151964294, 37.731103226906185 ], [ -122.50803827155984, 37.731142452222485 ], [ -122.508010240050936, 37.731193062852363 ], [ -122.508020236798259, 37.731243025052876 ], [ -122.508042824992458, 37.73131097145712 ], [ -122.508040601277514, 37.731356677807476 ], [ -122.508006517533232, 37.731439325082015 ], [ -122.508021273414599, 37.731473410969826 ], [ -122.50803497807641, 37.731532581064045 ], [ -122.508027744858111, 37.731584897172127 ], [ -122.508025706262629, 37.731637468116226 ], [ -122.508040152865036, 37.731724095470533 ], [ -122.508058978047401, 37.731780774943978 ], [ -122.508063043647368, 37.731867235581163 ], [ -122.508091810132555, 37.731971817943496 ], [ -122.508115264680967, 37.732007815523275 ], [ -122.508158104591416, 37.732057217905187 ], [ -122.508170145127792, 37.732118820238121 ], [ -122.508180754565259, 37.732191434375828 ], [ -122.508206800359858, 37.732259321810233 ], [ -122.508252110705484, 37.732336151955884 ], [ -122.508247422336822, 37.732418641589092 ], [ -122.508261368501763, 37.73248673525643 ], [ -122.508297786010743, 37.732554445876552 ], [ -122.50831883177085, 37.732629285992957 ], [ -122.508349701883319, 37.732683699580726 ], [ -122.508381054023317, 37.73275596038183 ], [ -122.508401673597731, 37.732815012827984 ], [ -122.508412291945035, 37.732887970164001 ], [ -122.508377095537298, 37.732929431631767 ], [ -122.50834351603902, 37.732966745040173 ], [ -122.508312026503646, 37.733017414665078 ], [ -122.508294180910255, 37.733060983682996 ], [ -122.508292383963067, 37.733122478220693 ], [ -122.508302816757165, 37.733188571798578 ], [ -122.50828645856835, 37.733223187740961 ], [ -122.508287627239582, 37.733266432915535 ] ] ] }}\n]}\n"
  },
  {
    "path": "bindings/python/package.json",
    "content": "{\n  \"name\": \"keplergl-jupyter\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"build\": \"node esbuild.config.mjs\",\n    \"dev\": \"node esbuild.config.mjs --watch\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"lint\": \"eslint src/\",\n    \"prettier\": \"prettier --write 'src/**/*.{ts,tsx}'\"\n  },\n  \"devDependencies\": {\n    \"@anywidget/types\": \"^0.2.0\",\n    \"@types/react\": \"^18.2.0\",\n    \"@types/react-dom\": \"^18.2.0\",\n    \"esbuild\": \"^0.20.0\",\n    \"typescript\": \"^5.4.0\",\n    \"eslint\": \"^8.57.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.0.0\",\n    \"@typescript-eslint/parser\": \"^7.0.0\",\n    \"prettier\": \"^3.2.0\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/actions\": \"^3.2.0\",\n    \"@kepler.gl/components\": \"^3.2.0\",\n    \"@kepler.gl/processors\": \"^3.2.0\",\n    \"@kepler.gl/reducers\": \"^3.2.0\",\n    \"@kepler.gl/schemas\": \"^3.2.0\",\n    \"@kepler.gl/styles\": \"^3.2.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"redux\": \"^4.2.1\",\n    \"react-redux\": \"^8.0.5\",\n    \"styled-components\": \"^6.1.8\",\n    \"apache-arrow\": \"^21.0.0\",\n    \"assert\": \"^2.1.0\",\n    \"events\": \"^3.3.0\"\n  }\n}\n"
  },
  {
    "path": "bindings/python/pyproject.toml",
    "content": "[project]\nname = \"keplergl\"\nversion = \"0.4.0rc1\"\ndescription = \"Kepler.gl Jupyter Widget\"\nreadme = \"README.md\"\nrequires-python = \">=3.9\"\nlicense = {text = \"MIT\"}\nauthors = [{name = \"Uber Technologies, Inc.\"}]\nclassifiers = [\n    \"Framework :: Jupyter\",\n    \"Programming Language :: Python :: 3\",\n]\ndependencies = [\n    \"anywidget>=0.9.0\",\n    \"traitlets>=5.0\",\n    \"geopandas>=0.14.3\",\n    \"pyarrow>=16.0.0\",\n    \"geoarrow-pyarrow>=0.1.2\",\n    \"geoarrow-pandas>=0.1.1\",\n]\n\n[project.optional-dependencies]\ndev = [\n    \"pytest>=8.0\",\n    \"pytest-cov>=4.0\",\n    \"jupyterlab>=4.0\",\n    \"notebook>=7.0\",\n]\n\n[build-system]\nrequires = [\"hatchling\", \"hatch-jupyter-builder>=0.9.0\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"keplergl\"]\n\n[tool.hatch.build.targets.wheel.shared-data]\n\"keplergl/static\" = \"share/jupyter/nbextensions/keplergl-jupyter\"\n\n[tool.hatch.build.hooks.jupyter-builder]\ndependencies = [\"hatch-jupyter-builder>=0.9.0\"]\nbuild-function = \"hatch_jupyter_builder.npm_builder\"\nensured-targets = [\"keplergl/static/widget.js\", \"keplergl/static/widget.css\"]\n\n[tool.hatch.build.hooks.jupyter-builder.build-kwargs]\nnpm = [\"npm\"]\nbuild_cmd = \"build\"\n\n[tool.uv]\ndev-dependencies = [\n    \"pytest>=8.0\",\n    \"pytest-cov>=4.0\",\n    \"jupyterlab>=4.0\",\n    \"notebook>=7.0\",\n]\n"
  },
  {
    "path": "bindings/python/src/components/App.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useState, useRef} from 'react';\nimport {injectComponents} from '@kepler.gl/components';\nimport {KEPLER_ID} from '../store';\nimport type {WidgetModel} from '../types';\n\n// Get the KeplerGl container component via dependency injection\nconst KeplerGl = injectComponents();\n\ninterface AppProps {\n  model: WidgetModel;\n}\n\nexport function App({model}: AppProps) {\n  const [height, setHeight] = useState(model.get('height'));\n  const [width, setWidth] = useState(800);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const handleHeightChange = () => setHeight(model.get('height'));\n    model.on('change:height', handleHeightChange);\n    return () => model.off('change:height', handleHeightChange);\n  }, [model]);\n\n  // Observe container width changes\n  useEffect(() => {\n    if (!containerRef.current) return;\n\n    const resizeObserver = new ResizeObserver(entries => {\n      for (const entry of entries) {\n        if (entry.contentRect.width > 0) {\n          setWidth(entry.contentRect.width);\n        }\n      }\n    });\n\n    resizeObserver.observe(containerRef.current);\n    return () => resizeObserver.disconnect();\n  }, []);\n\n  return (\n    <div ref={containerRef} style={{width: '100%', height}}>\n      <KeplerGl id={KEPLER_ID} width={width} height={height} mapboxApiAccessToken=\"\" />\n    </div>\n  );\n}\n"
  },
  {
    "path": "bindings/python/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport type {WidgetModel} from './types';\nimport {KeplerGlWidget} from './widget';\nimport './styles.css';\n\nexport default {\n  initialize({model}: {model: WidgetModel}) {\n    return () => {};\n  },\n\n  render({model, el}: {model: WidgetModel; el: HTMLElement}) {\n    const widget = new KeplerGlWidget(model, el);\n    widget.mount();\n\n    model.on('change:data', () => widget.onDataChange());\n    model.on('change:config', () => widget.onConfigChange());\n    model.on('change:height', () => widget.onHeightChange());\n\n    return () => {\n      widget.unmount();\n    };\n  }\n};\n"
  },
  {
    "path": "bindings/python/src/process-shim.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const process = {\n  env: {\n    NODE_ENV: typeof window !== 'undefined' ? 'production' : 'development'\n  },\n  browser: true,\n  version: '',\n  platform: 'browser',\n  nextTick: (fn, ...args) => queueMicrotask(() => fn(...args)),\n  cwd: () => '/',\n  argv: []\n};\n"
  },
  {
    "path": "bindings/python/src/store.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore as createReduxStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport {keplerGlReducer, enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\nexport const KEPLER_ID = 'jupyter_kepler';\n\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  uiState: {\n    currentModal: null,\n    activeSidePanel: null\n  }\n});\n\nconst reducers = combineReducers({\n  keplerGl: customizedKeplerGlReducer\n});\n\nexport function createStore() {\n  const middlewares = enhanceReduxMiddleware([]);\n  const enhancers = [applyMiddleware(...middlewares)];\n\n  // @ts-expect-error Redux types are not compatible with TypeScript\n  return createReduxStore(reducers, {}, compose(...enhancers));\n}\n"
  },
  {
    "path": "bindings/python/src/styles.css",
    "content": ".kepler-gl {\n  font-family: ff-clan-web-pro, \"Helvetica Neue\", Helvetica, sans-serif;\n  width: 100%;\n  height: 100%;\n}\n\n/* Ensure the map container has proper dimensions */\n.kepler-gl .map-container {\n  width: 100%;\n  height: 100%;\n}\n\n/* Fix for maplibre-gl canvas */\n.maplibregl-canvas {\n  position: absolute;\n  top: 0;\n  left: 0;\n}\n\n.maplibregl-map {\n  width: 100%;\n  height: 100%;\n}\n"
  },
  {
    "path": "bindings/python/src/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport type {AnyModel} from '@anywidget/types';\n\nexport interface KeplerGlWidgetModel {\n  data: Record<string, DatasetPayload>;\n  config: KeplerGlConfig;\n  height: number;\n}\n\nexport interface DatasetPayload {\n  id: string;\n  data: unknown;\n  format: 'csv' | 'json' | 'df' | 'arrow' | 'geojson' | 'geoarrow';\n}\n\nexport interface KeplerGlConfig {\n  version?: string;\n  config?: {\n    visState?: Record<string, unknown>;\n    mapState?: Record<string, unknown>;\n    mapStyle?: Record<string, unknown>;\n  };\n}\n\nexport type WidgetModel = AnyModel<KeplerGlWidgetModel>;\n"
  },
  {
    "path": "bindings/python/src/utils/data.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {tableFromIPC} from 'apache-arrow';\nimport {processCsvData, processGeojson} from '@kepler.gl/processors';\nimport type {ProcessorResult} from '@kepler.gl/types';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport type {DatasetPayload} from '../types';\n\nconst DEBUG = true;\nfunction debug(...args: unknown[]) {\n  if (DEBUG) {\n    console.log('[keplergl-data]', ...args);\n  }\n}\n\n// GeoArrow extension metadata key\nconst GEOARROW_METADATA_KEY = 'ARROW:extension:name';\n\nexport async function processDataset(payload: DatasetPayload): Promise<ProcessorResult | null> {\n  const {id, data, format} = payload;\n  debug('processDataset called:', {id, format, dataType: typeof data});\n\n  try {\n    let result: ProcessorResult | null = null;\n    switch (format) {\n      case 'arrow':\n      case 'geoarrow':\n        debug('Processing as arrow/geoarrow');\n        result = processGeoArrowData(data as string);\n        break;\n      case 'csv':\n        debug('Processing as CSV');\n        result = processCsvData(data as string);\n        break;\n      case 'geojson':\n        debug('Processing as GeoJSON');\n        result = processGeojson(data as object);\n        break;\n      case 'json':\n      case 'df':\n        debug('Processing as JSON/DataFrame', data);\n        result = processJsonData(data);\n        break;\n      default:\n        throw new Error(`Unknown data format: ${format}`);\n    }\n    debug(\n      'processDataset result:',\n      result ? {fields: result.fields?.length, rows: result.rows?.length} : null\n    );\n    return result;\n  } catch (error) {\n    console.error('[keplergl-data] Error processing dataset:', error);\n    throw error;\n  }\n}\n\n/**\n * Process GeoArrow data by converting to row-based format.\n * This avoids Arrow version mismatch issues between our code and kepler.gl's internal Arrow.\n */\nfunction processGeoArrowData(base64Data: string): ProcessorResult | null {\n  debug('processGeoArrowData: decoding base64 data, length:', base64Data.length);\n  const bytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));\n  debug('processGeoArrowData: decoded to', bytes.length, 'bytes');\n  \n  const table = tableFromIPC(bytes);\n  debug('processGeoArrowData: created table with', table.numRows, 'rows and', table.numCols, 'columns');\n  \n  if (table.numRows === 0) {\n    return null;\n  }\n\n  // Build fields from schema, detecting geoarrow columns from metadata\n  const fields = table.schema.fields.map((field, fieldIndex) => {\n    const extensionName = field.metadata?.get(GEOARROW_METADATA_KEY);\n    const isGeoArrow = extensionName?.startsWith('geoarrow');\n    \n    debug(`Field ${field.name}: type=${field.type.toString()}, extensionName=${extensionName}, isGeoArrow=${isGeoArrow}`);\n\n    let type: string;\n    let analyzerType: string;\n\n    if (isGeoArrow) {\n      type = ALL_FIELD_TYPES.geoarrow;\n      analyzerType = 'GEOMETRY';\n    } else {\n      type = arrowTypeToKeplerType(field.type);\n      analyzerType = arrowTypeToAnalyzerType(field.type);\n    }\n\n    return {\n      name: field.name,\n      id: field.name,\n      displayName: field.name,\n      format: '',\n      fieldIdx: fieldIndex,\n      type,\n      analyzerType\n    };\n  });\n\n  // Convert Arrow table to row-based format\n  // For geometry columns, convert to GeoJSON strings that kepler.gl can parse\n  const rows: any[][] = [];\n  for (let rowIdx = 0; rowIdx < table.numRows; rowIdx++) {\n    const row: any[] = [];\n    for (let colIdx = 0; colIdx < table.numCols; colIdx++) {\n      const field = fields[colIdx];\n      const value = table.getChildAt(colIdx)?.get(rowIdx);\n      \n      if (field.type === ALL_FIELD_TYPES.geoarrow && value !== null) {\n        // Convert geoarrow geometry to GeoJSON string\n        row.push(arrowGeometryToGeoJSON(value));\n      } else {\n        row.push(value);\n      }\n    }\n    rows.push(row);\n  }\n\n  debug('processGeoArrowData: converted to', rows.length, 'rows');\n  debug('processGeoArrowData: first row sample:', rows[0]?.slice(0, 3));\n\n  // Update field types for geometry columns to geojson since we converted them\n  const updatedFields = fields.map(f => {\n    if (f.type === ALL_FIELD_TYPES.geoarrow) {\n      return {...f, type: ALL_FIELD_TYPES.geojson, analyzerType: 'GEOMETRY'};\n    }\n    return f;\n  });\n\n  return {\n    fields: updatedFields,\n    rows\n  };\n}\n\n/**\n * Convert Arrow geometry value to GeoJSON string.\n * Handles various geoarrow geometry types (point, polygon, etc.)\n */\nfunction arrowGeometryToGeoJSON(geom: any): string {\n  try {\n    // The geometry is stored as nested Arrow structures\n    // We need to extract coordinates and build GeoJSON\n    \n    if (geom === null || geom === undefined) {\n      return '';\n    }\n\n    // For geoarrow polygon type, the structure is:\n    // List<List<Struct<x: Float64, y: Float64>>> for polygon rings\n    // We need to recursively extract the coordinates\n    \n    const coords = extractCoordinates(geom);\n    \n    if (!coords || coords.length === 0) {\n      return '';\n    }\n\n    // Determine geometry type based on structure\n    let geojson: any;\n    \n    if (typeof coords[0] === 'number') {\n      // Point: [x, y]\n      geojson = {type: 'Point', coordinates: coords};\n    } else if (typeof coords[0][0] === 'number') {\n      // LineString or Polygon ring: [[x, y], ...]\n      // Check if it's closed (polygon) or open (linestring)\n      const first = coords[0];\n      const last = coords[coords.length - 1];\n      if (coords.length >= 4 && first[0] === last[0] && first[1] === last[1]) {\n        geojson = {type: 'Polygon', coordinates: [coords]};\n      } else {\n        geojson = {type: 'LineString', coordinates: coords};\n      }\n    } else if (Array.isArray(coords[0][0])) {\n      // Polygon with rings or MultiLineString\n      if (typeof coords[0][0][0] === 'number') {\n        geojson = {type: 'Polygon', coordinates: coords};\n      } else {\n        geojson = {type: 'MultiPolygon', coordinates: coords};\n      }\n    } else {\n      // Fallback - try to make a polygon\n      geojson = {type: 'Polygon', coordinates: coords};\n    }\n\n    return JSON.stringify(geojson);\n  } catch (e) {\n    debug('arrowGeometryToGeoJSON error:', e);\n    return '';\n  }\n}\n\n/**\n * Recursively extract coordinates from Arrow geometry structure\n */\nfunction extractCoordinates(value: any): any {\n  if (value === null || value === undefined) {\n    return null;\n  }\n\n  // If it's a simple object with x, y properties (Point struct)\n  if (typeof value === 'object' && 'x' in value && 'y' in value) {\n    return [value.x, value.y];\n  }\n\n  // If it's an array-like structure (Arrow Vector or Array)\n  if (Array.isArray(value) || (typeof value === 'object' && typeof value.length === 'number')) {\n    const result: any[] = [];\n    const len = value.length;\n    \n    for (let i = 0; i < len; i++) {\n      const item = Array.isArray(value) ? value[i] : value.get?.(i) ?? value[i];\n      const extracted = extractCoordinates(item);\n      if (extracted !== null) {\n        result.push(extracted);\n      }\n    }\n    return result;\n  }\n\n  // If it has a toArray method (Arrow Vector)\n  if (typeof value.toArray === 'function') {\n    return extractCoordinates(value.toArray());\n  }\n\n  // If it has a get method but no length (might be a struct)\n  if (typeof value.get === 'function') {\n    // Try to get x, y from struct\n    const x = value.get('x') ?? value.get(0);\n    const y = value.get('y') ?? value.get(1);\n    if (x !== undefined && y !== undefined) {\n      return [x, y];\n    }\n  }\n\n  return null;\n}\n\n/**\n * Map Arrow data type to Kepler field type\n */\nfunction arrowTypeToKeplerType(arrowType: any): string {\n  const typeStr = arrowType.toString();\n  \n  if (typeStr.startsWith('Int') || typeStr.startsWith('Uint')) {\n    return ALL_FIELD_TYPES.integer;\n  }\n  if (typeStr.startsWith('Float') || typeStr === 'Decimal') {\n    return ALL_FIELD_TYPES.real;\n  }\n  if (typeStr === 'Bool') {\n    return ALL_FIELD_TYPES.boolean;\n  }\n  if (typeStr.startsWith('Timestamp') || typeStr.startsWith('Date')) {\n    return ALL_FIELD_TYPES.timestamp;\n  }\n  if (typeStr === 'Utf8' || typeStr === 'LargeUtf8') {\n    return ALL_FIELD_TYPES.string;\n  }\n  \n  return ALL_FIELD_TYPES.string;\n}\n\n/**\n * Map Arrow data type to type-analyzer type\n */\nfunction arrowTypeToAnalyzerType(arrowType: any): string {\n  const typeStr = arrowType.toString();\n  \n  if (typeStr.startsWith('Int') || typeStr.startsWith('Uint')) {\n    return 'INT';\n  }\n  if (typeStr.startsWith('Float') || typeStr === 'Decimal') {\n    return 'FLOAT';\n  }\n  if (typeStr === 'Bool') {\n    return 'BOOLEAN';\n  }\n  if (typeStr.startsWith('Timestamp') || typeStr.startsWith('Date')) {\n    return 'DATETIME';\n  }\n  \n  return 'STRING';\n}\n\nfunction processJsonData(data: unknown): ProcessorResult | null {\n  debug('processJsonData input:', data);\n  if (typeof data === 'object' && data !== null && 'columns' in data) {\n    const {columns, data: rows} = data as {columns: string[]; data: unknown[][]};\n    debug('processJsonData parsed:', {columns, rowCount: rows?.length, firstRow: rows?.[0]});\n    const result = {\n      fields: columns.map(name => ({name})),\n      rows: rows as any[][]\n    };\n    debug('processJsonData result:', {\n      fieldCount: result.fields.length,\n      rowCount: result.rows.length\n    });\n    return result;\n  }\n  debug('processJsonData: data does not have expected structure');\n  return null;\n}\n"
  },
  {
    "path": "bindings/python/src/utils/serialization.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport function decodeBase64ToBytes(base64: string): Uint8Array {\n  const binaryString = atob(base64);\n  const bytes = new Uint8Array(binaryString.length);\n  for (let i = 0; i < binaryString.length; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return bytes;\n}\n\nexport function encodeToBase64(bytes: Uint8Array): string {\n  let binary = '';\n  for (let i = 0; i < bytes.byteLength; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return btoa(binary);\n}\n"
  },
  {
    "path": "bindings/python/src/widget.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createRoot, Root} from 'react-dom/client';\nimport React from 'react';\nimport {Provider} from 'react-redux';\nimport {createStore, KEPLER_ID} from './store';\nimport {App} from './components/App';\nimport {processDataset} from './utils/data';\nimport type {WidgetModel, DatasetPayload} from './types';\nimport type {ParsedConfig} from '@kepler.gl/types';\nimport {addDataToMap, receiveMapConfig, wrapTo} from '@kepler.gl/actions';\n\nconst DEBUG = false;\nfunction debug(...args: unknown[]) {\n  if (DEBUG) {\n    console.log('[keplergl-widget]', ...args);\n  }\n}\n\nexport class KeplerGlWidget {\n  private model: WidgetModel;\n  private el: HTMLElement;\n  private root: Root | null = null;\n  private store: ReturnType<typeof createStore>;\n\n  constructor(model: WidgetModel, el: HTMLElement) {\n    debug('KeplerGlWidget constructor');\n    this.model = model;\n    this.el = el;\n    this.store = createStore();\n  }\n\n  mount() {\n    debug('mount() called');\n    this.el.style.height = `${this.model.get('height')}px`;\n    this.root = createRoot(this.el);\n    this.root.render(\n      React.createElement(Provider, {\n        store: this.store,\n        children: React.createElement(App, {model: this.model})\n      })\n    );\n\n    this.loadData();\n    this.loadConfig();\n    this.store.subscribe(() => this.syncConfigToPython());\n  }\n\n  unmount() {\n    debug('unmount() called');\n    this.root?.unmount();\n    this.root = null;\n  }\n\n  onDataChange() {\n    debug('onDataChange() called');\n    this.loadData();\n  }\n\n  onConfigChange() {\n    debug('onConfigChange() called');\n    this.loadConfig();\n  }\n\n  onHeightChange() {\n    this.el.style.height = `${this.model.get('height')}px`;\n  }\n\n  private async loadData() {\n    const data = this.model.get('data');\n    debug('loadData() called, data keys:', Object.keys(data));\n    debug('loadData() raw data:', JSON.stringify(data, null, 2));\n\n    // Wait for the KeplerGl component to register itself\n    await this.waitForInstance();\n\n    for (const [name, payload] of Object.entries(data)) {\n      debug(`Processing dataset '${name}'...`);\n      try {\n        const processed = await processDataset(payload as DatasetPayload);\n        debug(\n          `Dataset '${name}' processed:`,\n          processed ? {fields: processed.fields, rowCount: processed.rows?.length} : null\n        );\n\n        if (!processed) {\n          debug(`Dataset '${name}' returned null, skipping`);\n          continue;\n        }\n\n        const action = addDataToMap({\n          datasets: {\n            info: {id: name, label: name},\n            data: processed\n          }\n        });\n        // Wrap the action to target the specific kepler.gl instance\n        const wrappedAction = wrapTo(KEPLER_ID, action);\n        debug(`Dispatching addDataToMap for '${name}'`, wrappedAction);\n        this.store.dispatch(wrappedAction);\n        debug(`addDataToMap dispatched for '${name}'`);\n      } catch (error) {\n        console.error(`[keplergl-widget] Error processing dataset '${name}':`, error);\n      }\n    }\n\n    debug('loadData() complete, store state:', this.store.getState());\n  }\n\n  private waitForInstance(): Promise<void> {\n    return new Promise(resolve => {\n      const checkInstance = () => {\n        const state = this.store.getState() as {keplerGl?: Record<string, unknown>};\n        if (state.keplerGl && state.keplerGl[KEPLER_ID]) {\n          debug('Instance registered:', KEPLER_ID);\n          resolve();\n          return true;\n        }\n        return false;\n      };\n\n      // Check immediately\n      if (checkInstance()) return;\n\n      // Otherwise subscribe and wait\n      const unsubscribe = this.store.subscribe(() => {\n        if (checkInstance()) {\n          unsubscribe();\n        }\n      });\n\n      // Timeout after 5 seconds\n      setTimeout(() => {\n        unsubscribe();\n        console.warn('[keplergl-widget] Timeout waiting for instance registration');\n        resolve();\n      }, 5000);\n    });\n  }\n\n  private loadConfig() {\n    const config = this.model.get('config');\n    debug('loadConfig() called, config:', config);\n    if (config && Object.keys(config).length > 0) {\n      this.store.dispatch(wrapTo(KEPLER_ID, receiveMapConfig(config as ParsedConfig, {})));\n    }\n  }\n\n  private syncConfigToPython() {\n    // TODO: Extract config from store and sync back\n  }\n}\n"
  },
  {
    "path": "bindings/python/tests/conftest.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Pytest configuration.\"\"\"\n\nimport pytest\nimport pandas as pd\nimport geopandas as gpd\nfrom shapely.geometry import Point\n\n\n@pytest.fixture\ndef sample_df():\n    return pd.DataFrame({\n        \"lat\": [37.7749, 34.0522],\n        \"lng\": [-122.4194, -118.2437],\n        \"value\": [100, 200],\n    })\n\n\n@pytest.fixture\ndef sample_gdf():\n    return gpd.GeoDataFrame(\n        {\"name\": [\"SF\", \"LA\"]},\n        geometry=[Point(-122.4194, 37.7749), Point(-118.2437, 34.0522)],\n        crs=\"EPSG:4326\",\n    )\n"
  },
  {
    "path": "bindings/python/tests/test_serializers.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Serializer tests.\"\"\"\n\nimport json\nimport pytest\nimport pandas as pd\nimport geopandas as gpd\nfrom shapely.geometry import Point, Polygon\nfrom keplergl.serializers import serialize_dataset\n\n\nclass TestDataFrameSerialization:\n    \"\"\"Tests for DataFrame serialization (from DataFrame.ipynb).\"\"\"\n\n    def test_serialize_dataframe(self, sample_df):\n        result = serialize_dataset(sample_df, \"test\")\n        assert result[\"id\"] == \"test\"\n        assert result[\"format\"] == \"df\"\n        assert \"data\" in result\n\n    def test_serialize_dataframe_with_cities(self):\n        \"\"\"Test DataFrame with city data (from DataFrame.ipynb).\"\"\"\n        df = pd.DataFrame({\n            'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\n            'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\n            'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\n            'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86],\n            'Time': ['2019-09-01 08:00', '2019-09-01 09:00', '2019-09-01 10:00',\n                     '2019-09-01 11:00', '2019-09-01 12:00'],\n        })\n        result = serialize_dataset(df, \"data_1\")\n        assert result[\"id\"] == \"data_1\"\n        assert result[\"format\"] == \"df\"\n        assert result[\"data\"][\"columns\"] == ['City', 'Country', 'Latitude', 'Longitude', 'Time']\n        assert len(result[\"data\"][\"data\"]) == 5\n\n    def test_serialize_dataframe_with_hex_data(self):\n        \"\"\"Test DataFrame with H3 hex IDs and mixed types (from Load kepler.gl.ipynb).\"\"\"\n        df = pd.DataFrame({\n            'hex_id': ['89283082c2fffff', '8928308288fffff', '89283082c07ffff'],\n            'value': [64, 73, 65],\n            'is_true': [True, True, True],\n            'float_value': [64.1, 73.1, 65.1],\n            'empty': ['', '', ''],\n            'time': ['11/1/17 11:00', '11/1/17 11:00', '11/1/17 11:00'],\n        })\n        result = serialize_dataset(df, \"data_1\")\n        assert result[\"id\"] == \"data_1\"\n        assert result[\"format\"] == \"df\"\n        assert 'hex_id' in result[\"data\"][\"columns\"]\n        assert 'value' in result[\"data\"][\"columns\"]\n        assert 'is_true' in result[\"data\"][\"columns\"]\n\n    def test_serialize_dataframe_with_nan_filled(self):\n        \"\"\"Test DataFrame with NaN values filled with empty string.\"\"\"\n        df = pd.DataFrame({\n            'col1': [1, 2, 3],\n            'col2': ['a', None, 'c'],\n        })\n        df = df.fillna('')\n        result = serialize_dataset(df, \"test\")\n        assert result[\"format\"] == \"df\"\n        assert result[\"data\"][\"data\"][1][1] == ''\n\n\nclass TestGeoDataFrameSerialization:\n    \"\"\"Tests for GeoDataFrame serialization (from GeoDataFrame.ipynb).\"\"\"\n\n    def test_serialize_geodataframe(self, sample_gdf):\n        result = serialize_dataset(sample_gdf, \"test\")\n        assert result[\"id\"] == \"test\"\n        assert result[\"format\"] == \"geoarrow\"\n        assert \"data\" in result\n\n    def test_serialize_geodataframe_with_timestamp(self):\n        \"\"\"Test GeoDataFrame with pd.Timestamp column.\n\n        Regression test for issue where GeoDataFrame containing Timestamp\n        columns would fail during Arrow serialization.\n        \"\"\"\n        df = pd.DataFrame({\n            'City': ['Buenos Aires'],\n            'Country': ['Argentina'],\n            'Latitude': [-34.58],\n            'Longitude': [-58.66],\n            'Timestamp': pd.Timestamp(2002, 3, 3),\n        })\n        gdf = gpd.GeoDataFrame(\n            df,\n            geometry=gpd.points_from_xy(df.Longitude, df.Latitude),\n        )\n        result = serialize_dataset(gdf, \"cities\")\n        assert result[\"id\"] == \"cities\"\n        assert result[\"format\"] == \"geoarrow\"\n        assert \"data\" in result\n\n    def test_serialize_geodataframe_points_from_xy(self):\n        \"\"\"Test GeoDataFrame created with points_from_xy (from GeoDataFrame.ipynb).\"\"\"\n        df = pd.DataFrame({\n            'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\n            'Country': ['Argentina', 'Brazil', 'Chile', 'Colombia', 'Venezuela'],\n            'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\n            'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86],\n        })\n        gdf = gpd.GeoDataFrame(\n            df,\n            geometry=gpd.points_from_xy(df.Longitude, df.Latitude),\n        )\n        result = serialize_dataset(gdf, \"cities\")\n        assert result[\"id\"] == \"cities\"\n        assert result[\"format\"] == \"geoarrow\"\n        assert \"data\" in result\n\n    def test_serialize_geodataframe_with_polygons(self):\n        \"\"\"Test GeoDataFrame with Polygon geometries (like zipcode boundaries).\"\"\"\n        gdf = gpd.GeoDataFrame({\n            'ZIP_CODE': ['94107', '94105'],\n            'geometry': [\n                Polygon([(-122.40, 37.78), (-122.39, 37.78),\n                         (-122.39, 37.77), (-122.40, 37.77)]),\n                Polygon([(-122.39, 37.79), (-122.38, 37.79),\n                         (-122.38, 37.78), (-122.39, 37.78)]),\n            ],\n        })\n        result = serialize_dataset(gdf, \"zipcode\")\n        assert result[\"id\"] == \"zipcode\"\n        assert result[\"format\"] == \"geoarrow\"\n        assert \"data\" in result\n\n    def test_serialize_geodataframe_with_crs(self):\n        \"\"\"Test GeoDataFrame with explicit CRS.\"\"\"\n        gdf = gpd.GeoDataFrame(\n            {\"name\": [\"SF\", \"LA\"]},\n            geometry=[Point(-122.4194, 37.7749), Point(-118.2437, 34.0522)],\n            crs=\"EPSG:4326\",\n        )\n        result = serialize_dataset(gdf, \"test\")\n        assert result[\"format\"] == \"geoarrow\"\n        assert \"data\" in result\n\n\nclass TestGeoJSONSerialization:\n    \"\"\"Tests for GeoJSON serialization (from GeoJSON.ipynb).\"\"\"\n\n    def test_serialize_geojson_dict(self):\n        \"\"\"Test GeoJSON as dict.\"\"\"\n        geojson = {\"type\": \"FeatureCollection\", \"features\": []}\n        result = serialize_dataset(geojson, \"test\")\n        assert result[\"format\"] == \"geojson\"\n        assert result[\"data\"] == geojson\n\n    def test_serialize_geojson_feature_collection(self):\n        \"\"\"Test GeoJSON FeatureCollection with features.\"\"\"\n        geojson = {\n            \"type\": \"FeatureCollection\",\n            \"features\": [\n                {\n                    \"type\": \"Feature\",\n                    \"geometry\": {\"type\": \"Point\", \"coordinates\": [-122.4, 37.8]},\n                    \"properties\": {\"name\": \"San Francisco\"},\n                },\n                {\n                    \"type\": \"Feature\",\n                    \"geometry\": {\"type\": \"Point\", \"coordinates\": [-118.2, 34.0]},\n                    \"properties\": {\"name\": \"Los Angeles\"},\n                },\n            ],\n        }\n        result = serialize_dataset(geojson, \"geojson\")\n        assert result[\"id\"] == \"geojson\"\n        assert result[\"format\"] == \"geojson\"\n        assert result[\"data\"][\"type\"] == \"FeatureCollection\"\n        assert len(result[\"data\"][\"features\"]) == 2\n\n    def test_serialize_geojson_string(self):\n        \"\"\"Test GeoJSON as string (from GeoJSON.ipynb - reading from file).\"\"\"\n        geojson_str = json.dumps({\n            \"type\": \"FeatureCollection\",\n            \"features\": [\n                {\n                    \"type\": \"Feature\",\n                    \"geometry\": {\"type\": \"Point\", \"coordinates\": [-122.4, 37.8]},\n                    \"properties\": {\"name\": \"Test\"},\n                },\n            ],\n        })\n        result = serialize_dataset(geojson_str, \"geojson\")\n        assert result[\"id\"] == \"geojson\"\n        assert result[\"format\"] == \"geojson\"\n        assert result[\"data\"][\"type\"] == \"FeatureCollection\"\n\n    def test_serialize_geojson_polygon(self):\n        \"\"\"Test GeoJSON with Polygon geometry.\"\"\"\n        geojson = {\n            \"type\": \"Feature\",\n            \"geometry\": {\n                \"type\": \"Polygon\",\n                \"coordinates\": [[\n                    [-122.4, 37.8], [-122.3, 37.8],\n                    [-122.3, 37.7], [-122.4, 37.7], [-122.4, 37.8],\n                ]],\n            },\n            \"properties\": {\"name\": \"Test Area\"},\n        }\n        result = serialize_dataset(geojson, \"polygon\")\n        assert result[\"format\"] == \"geojson\"\n\n\nclass TestCSVSerialization:\n    \"\"\"Tests for CSV string serialization.\"\"\"\n\n    def test_serialize_csv(self):\n        csv_data = \"lat,lng\\n37.7749,-122.4194\"\n        result = serialize_dataset(csv_data, \"test\")\n        assert result[\"format\"] == \"csv\"\n        assert result[\"data\"] == csv_data\n\n    def test_serialize_csv_multirow(self):\n        \"\"\"Test CSV with multiple rows.\"\"\"\n        csv_data = \"City,Latitude,Longitude\\nSF,37.77,-122.42\\nLA,34.05,-118.24\"\n        result = serialize_dataset(csv_data, \"cities\")\n        assert result[\"id\"] == \"cities\"\n        assert result[\"format\"] == \"csv\"\n        assert result[\"data\"] == csv_data\n\n\nclass TestEdgeCases:\n    \"\"\"Tests for edge cases and error handling.\"\"\"\n\n    def test_serialize_unsupported_type(self):\n        \"\"\"Test that unsupported types raise ValueError.\"\"\"\n        with pytest.raises(ValueError, match=\"Unsupported data type\"):\n            serialize_dataset([1, 2, 3], \"test\")\n\n    def test_serialize_empty_dataframe(self):\n        \"\"\"Test serializing empty DataFrame.\"\"\"\n        df = pd.DataFrame({'col1': [], 'col2': []})\n        result = serialize_dataset(df, \"empty\")\n        assert result[\"format\"] == \"df\"\n        assert result[\"data\"][\"columns\"] == ['col1', 'col2']\n        assert result[\"data\"][\"data\"] == []\n\n    def test_serialize_single_row_dataframe(self):\n        \"\"\"Test serializing single-row DataFrame.\"\"\"\n        df = pd.DataFrame({'lat': [37.77], 'lng': [-122.42]})\n        result = serialize_dataset(df, \"single\")\n        assert result[\"format\"] == \"df\"\n        assert len(result[\"data\"][\"data\"]) == 1\n\n    def test_serialize_dataframe_with_various_dtypes(self):\n        \"\"\"Test DataFrame with various data types.\"\"\"\n        df = pd.DataFrame({\n            'int_col': [1, 2, 3],\n            'float_col': [1.1, 2.2, 3.3],\n            'str_col': ['a', 'b', 'c'],\n            'bool_col': [True, False, True],\n        })\n        result = serialize_dataset(df, \"mixed\")\n        assert result[\"format\"] == \"df\"\n        assert len(result[\"data\"][\"columns\"]) == 4\n"
  },
  {
    "path": "bindings/python/tests/test_widget.py",
    "content": "# SPDX-License-Identifier: MIT\n# Copyright contributors to the kepler.gl project\n\n\"\"\"Widget tests.\"\"\"\n\nimport pytest\nfrom keplergl import KeplerGl\n\n\ndef test_widget_creation():\n    widget = KeplerGl()\n    assert widget.height == 400\n    assert widget.data == {}\n    assert widget.config == {}\n\n\ndef test_widget_with_height():\n    widget = KeplerGl(height=600)\n    assert widget.height == 600\n\n\ndef test_widget_with_dataframe(sample_df):\n    widget = KeplerGl(data={\"cities\": sample_df})\n    assert \"cities\" in widget.data\n\n\ndef test_widget_with_geodataframe(sample_gdf):\n    widget = KeplerGl(data={\"points\": sample_gdf})\n    assert \"points\" in widget.data\n\n\ndef test_add_data(sample_df):\n    widget = KeplerGl()\n    widget.add_data(sample_df, name=\"test\")\n    assert \"test\" in widget.data\n\n\ndef test_widget_with_config():\n    config = {\"version\": \"v1\", \"config\": {\"mapState\": {\"zoom\": 10}}}\n    widget = KeplerGl(config=config)\n    assert widget.config == config\n"
  },
  {
    "path": "bindings/python/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"keplergl/static\"]\n}\n"
  },
  {
    "path": "contributing/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nkepler.gl is an [OpenJS Foundation](https://openjsf.org/) project. Please be mindful of and adhere to the OpenJS Foundation's [Code of Conduct](https://github.com/openjs-foundation/cross-project-council/blob/main/CODE_OF_CONDUCT.md) when contributing to kepler.gl."
  },
  {
    "path": "contributing/DEVELOPERS.md",
    "content": "# Developing Kepler.gl\n\n## Table of contents\n\n- [Development Setup](./#development-setup)\n- [Running Tests](./#running-tests)\n- [Coding Rules](./#coding-rules)\n- [Commit Message Guidelines](./#git-commit-guidelines)\n- [Writing Documentation](./#writing-documentation-this-part-is-not-available-yet)\n- [Developing kepler.gl Website](./#develop-the-kepler-gl-website)\n- [Publish the website](./#publish-the-website)\n- [Publish a new version](./#publish-kepler-gl-package-to-npm)\n\n## Development Setup\n\nThis document describes how to set up your development environment to build and test Kepler.gl, and\nexplains the basic mechanics of using `git`, `node`, `yarn`.\n\n### Installing Dependencies\n\nBefore you can build Kepler.gl, you must install and configure the following dependencies on your machine:\n\n- [Git](http://git-scm.com/): The [Github Guide to Installing Git][git-setup] is a good source of information.\n\n- [Node.js ^18.x](http://nodejs.org): We use Node to generate the documentation, run a\n  development web server, run tests, and generate distributable files. Depending on your system,\n  you can install Node either from source or as a pre-packaged bundle.\n\n  We recommend using [nvm](https://github.com/creationix/nvm) (or\n  [nvm-windows](https://github.com/coreybutler/nvm-windows))\n  to manage and install Node.js, which makes it easy to change the version of Node.js per project.\n\n- [Yarn 4.4.0](https://yarnpkg.com): We use Yarn to install our Node.js module dependencies\n  (rather than using npm). See the detailed [installation instructions][yarn-install].\n\n- [Volta](https://volta.sh/): We use Volta to manage Node and Yarn versions without you manually switching them\n\n#### Fork Kepler.gl Repo\n\nIf you plan to contribute code to kepler.gl, you must have a [GitHub account](https://github.com/signup/free) so you can push code and open Pull Requests in the [GitHub Repository][github]. You must [fork](http://help.github.com/forking) the\n[main kepler.gl repository][github] to [create a Pull Request][github-pr].\n\n#### Developing kepler.gl\n\nIf you are using Windows then using `WSL (Windows Subsystem for Linux)` is recommended. You can download a Linux Distribution like e.g. `Ubuntu` and inside of that distribution you can follow along with the next steps. You can find the detailed instructions about `WSL` [here](https://learn.microsoft.com/en-us/windows/wsl/).\n\nIf you are using MacOS or Linux then you can follow along.\n\nAlso please make sure the code editor you are using it has proper support for [EditorConfig](https://editorconfig.org/).VSCode has the [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) Plugin. Please install necessary support for EditorConfig for your editor so that other code formatters do not have an effect on the Kepler.GL code.\n\nTo develop features, debug code, run tests, we use webpack to start a local web server and serve the kepler.gl demo app from the src directory.\n\n```bash\n# Clone your kepler.gl fork repository:\ngit clone git@github.com:<github username>/kepler.gl.git\n\n# Go to the kepler.gl directory:\ncd kepler.gl\n\n# Add the main kepler.gl repository as an upstream remote to your repository:\ngit remote add upstream \"git@github.com:keplergl/kepler.gl.git\"\n```\n\nInstall [volta](https://docs.volta.sh/guide/getting-started)\nOn Unix, MacOS\n\n```bash\n# install Volta on Unix\ncurl https://get.volta.sh | bash\n```\nOn Windows\n\n```bash\nwinget install Volta.Volta\n```\n\nInstall `nvm` to set the proper Node.js version for the project. Follow instructions to install nvm [here](https://github.com/nvm-sh/nvm).\n\n```bash\n# Install the proper Node.js version for the Kepler.gl project\nnvm install\n\n# Use the downloaded Node.js version for the Kepler.gl project\nnvm use\n\n# Enable Yarn\ncorepack enable\n```\n\nInstall dependencies with Yarn\n\n```bash\n# Install Puppeteer\nyarn dlx puppeteer\n\n\n# Install JavaScript dependencies:\nyarn install\nyarn bootstrap\n\n# Setup Mapbox access token locally\nexport MapboxAccessToken=<MapboxAccessToken>\n# Set up other environment variables\nexport DropboxClientId=<DropboxClientId>\nexport MapboxExportToken=<MapboxExportToken>\nexport CartoClientId=<CartoClientId>\nexport FoursquareClientId=<FoursquareClientId>\nexport FoursquareDomain=<FoursquareDomain>\nexport FoursquareAPIURL=<FoursquareAPIURL>\nexport FoursquareUserMapsURL=<FoursquareUserMapsURL>\n\n# Start the kepler.gl demo app\nyarn start\n```\n\nAn demo app will be served at `http://localhost:8080/`\n\nThis is the demo app we hosted on [http://kepler.gl/#/demo][demo-app]. By default, it serves non-minified source code inside the src directory.\n\n#### Develop with deck.gl\n\nWhen develop, upgrade, debug deck.gl, Demo app can load deck.gl directly from src\n\n```\n// load deck.gl from node_modules/deck.gl/src, sub-modules from node_modules/@deck.gl/<module>/src\nnpm run start:deck\n\n// load deck.gl src from the deck.gl folder parallel to kepler.gl\nnpm run start:deck-src\n```\n\n## Running Tests\n\n- We write node and browser tests with [Tape][tape], [Enzyme][enzyme], [jsDom](https://www.npmjs.com/package/jsdom) and [@probe.gl/test-util](https://uber-web.github.io/probe.gl/docs/modules/test-utils/browser-driver), and lint with [ESLint][eslint]. Make sure to run test before submitting your PR. To run all of the tests once\n\n```bash\nyarn test\n```\n\n- Yarn test runs lint and 3 tests in different env. To run them separately\n\n```bash\n# lint\nyarn lint\n\n# node tests\nyarn test-node\n\n# jsdom tests\nyarn test-browser\n\n# headless browser tests, uses probe.gl to run browser tests with puppeteer\nyarn test-headless\n```\n\n- Here are some handy scripts / tricks for debugging tests\n\n1. add `.only` to errored tests to only run 1 test at a time\n\n```js\ntest.only('MapContainerFactory', t => {\n  // tests\n}\n```\n\n2. run all tests in chromium browser. This runs node, browser and headless browser tests in chromium browser and logs the output, you can step through the code with chrome developer tools\n\n```bash\nyarn test-browser-drive\n```\n\n3. Fast tests, runs node and browser tests without tap-spec output\n\n```bash\nyarn test-fast\n```\n\nTo generate a coverage report\n\n```bash\nyarn cover\n```\n\n## Test React components\n\nEnzyme is no longer supported therefore we are now transitioning to [testing library](https://testing-library.com/).\n\nWe have introduced an eslint rule to deprecate the usage of enzyme so if you attempt to create new tests using enzyme\nit will throw an error when running lint.\n\nIn order to create new tests cases please take advantage of [Testing Library](https://testing-library.com/).\nAll necessary dependencies are already installed, you can start testing your React components by following this\n[doc](https://testing-library.com/docs/react-testing-library/intro);\n\n### Migrating enzyme to React testing library\n\nIf you are interested in migrating enzyme tests to RTL (react testing library) feel free to check\nthe [official migration guidelines](https://testing-library.com/docs/react-testing-library/migrate-from-enzyme/)\n\n## Coding Rules\n\nTo ensure consistency throughout the source code, keep these rules in mind as you are working:\n\n- All features or bug fixes **must be tested** by one or more [specs][unit-testing].\n- All public API methods **must be documented** with using jsdoc. To see how we document our APIs, please check\n  out the existing source code and see the section about [writing documentation](#documentation)\n\nThis project use Eslint together Prettier. The linter should automatically inform you if you break any rules (like incorrect indenting, line breaking or if you forget a semicolon). Before doing a pull request, make sure to run the linter.\n\n```bash\n# To run the linter\nyarn lint\n```\n\n## Git Commit Guidelines\n\nTo commit your changes, please follow our rules over how our git commit messages can be formatted. This leads to **more readable and unified messages** that are easy to follow. But also,\nwe use the git commit messages to **generate the kepler.gl change log**.\n\n### Commit Message Format\n\nEach commit message consists of a **header** and a **body**. The header has a special\nformat that includes a **type** and a **subject**. The **PR** # will be auto-generated once the PR is merged.\n\n```\n[<type>]<subject>(<pr>)\n<BLANK LINE>\n<body>\n\n#e.g.\n[Enhancement] Upgrade type-analyzer to pass 0/1 as integer (#317)\n\n* Upgrade to type-analyzer@0.2.1\n* Add test\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\nAny line of the commit message cannot be longer 100 characters! This allows the message to be easier\nto read on GitHub as well as in various git tools.\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header\nof the reverted commit.\nIn the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit\nbeing reverted.\nA commit with this format is automatically created by the [`git revert`][git-revert] command.\n\n### Type\n\nMust be one of the following, capitalized.\n\n- **[Feat]**: A new feature\n- **[Enhancement]**: An update of a existing feature\n- **[Bug]**: A bug fix\n- **[Docs]**: Documentation only changes\n- **[Style]**: Changes that do not affect the meaning of the code (white-space, formatting, missing\n  semi-colons, typos, etc)\n- **[Refactor]**: A code change that neither fixes a bug nor adds a feature\n- **[Perf]**: A code change that improves performance\n- **[Test]**: Adding missing or correcting existing tests\n- **[Chore]**: Changes to the build process or auxiliary tools and libraries such as documentation\n  generation\n\n### Subject\n\nThe subject contains succinct description of the change:\n\n- use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- don't capitalize first letter\n- no dot (.) at the end\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines.\nThe rest of the commit message is then used for this.\n\n## Writing Documentation (THIS PART IS NOT AVAILABLE YET)\n\nThe Kepler.gl project uses [jsdoc](http://usejsdoc.org/)\n\nThis means that all the docs are stored inline in the source code and so are kept in sync as it\nchanges.\n\nThere is also extra content (the developer guide, error pages, the tutorial,\nand misceallenous pages) that live inside the Kepler.gl repository as markdown files.\n\nThis means that since we generate the documentation from the source code, we can easily provide\nversion-specific documentation by simply checking out a version of Kepler.gl and running the build.\n\n### Building and viewing the docs locally\n\nWe build Api docs from scratch using [documentation.js][documentationjs]. It generates docs from jsdoc:\n\n```bash\nyarn docs\n```\n\n### Writing jsdoc\n\nYou can find JSDoc instructions [here][jsDoc]. Documentation.js is interested in the following block tags:\n\n- `@param {type} name description` - describes a parameter of a function\n- `@returns {type} description` - describes what a function returns\n- `@property` - describes a property of an object\n- `@description` - used to provide a description of a component in markdown\n\n- `@example` - specifies an example.\n- `@public` - Only methods with @public tag will be included in the docs\n\nThe `type` in `@param` and `@returns` must be wrapped in `{}` curly braces, e.g. `{Object|Array}`.\nParameters can be made optional by _either_ appending a `=` to the type, e.g. `{Object=}`, _or_ by\nputting the `[name]` in square brackets.\nDefault values are only possible with the second syntax by appending `=<value>` to the parameter\nname, e.g. `@param {boolean} [ownPropsOnly=false]`.\n\n## Develop The kepler.gl Website\n\nMake sure to export mapbox token in the same terminal before start the server.\n\n```bash\n$ export MapboxAccessToken=<insert_your_token>\n```\n\nIn order to start\n\n```bash\n$ yarn web\n```\n\nTo checkout the build\n\n```bash\n$ cd website && yarn build\n```\n\n## Publish the website\n\n[Netlify](https://www.netlify.com/) is used to support kepler.gl demo website.\n\nNetlify is connected to the following github triggers:\n\n- Create a new PR\n- Updated an existing PR\n- Merge PR onto master\n\nA new production version of kepler.gl website is automatically created and deployed every time a PR is merged onto master.\n\nIn order to support testing environment, Netlify is setup to generate build every time a PR is created or updated.\nBy generating builds for new and updated PRs we support CI/CD so developers can test their own build in a production like environment\n\n### Publish kepler.gl package to NPM\n\n#### Requirements\n\nTo prepare a new release you need the following tool:\n\n- [gh-release](https://www.npmjs.com/package/gh-release): this tool facilitates the creation of a new git tag (using package.json version number) and a github release (different from npm release)\n\nSetup `gh-release` with your github api token ([instructions](https://www.npmjs.com/package/gh-release#command-line-interface))\n\n### Push a new release\n\nIn order to publish a new version of kepler.gl a developer must perform the following steps:\n\n1. Update **package.json** file with the new version value. Run `npm version major | minor | patch` to update version accordingly.\n2. Update **CHANGELOG.md** with the latest commit changes. Print commits with `git log --pretty=oneline --abbrev-commit`\n3. Create a new PR for review.\n4. Once the PR is reviewed and merged, pull the latest changes locally.\n5. Run `gh-release`: this command will create a new Github Release with the new updated CHANGELOG.md section.\n6. Once the new Github Release is created, Github will automatically trigger a new Github Action flow that will automatically build and publish the new package version to NPM registry.\n\n**After Release is completed and pushed**\n\n- Update each of the example folder package.json kepler.gl dependency with the newer. To update all examples, run\n\n```bash\nnpm run example-version\n```\n\nThis step is required after the new version is published otherwise it would fail.\n\n## Gitbook documentation\n\nKepler.gl documentation is hosted on [gitbook](https://kepler-gl.gitbook.io/kepler-gl/). For more information [read here](https://docs.gitbook.com/)\n\n### Documentation structure\n\nThe documentation layout is defined by **SUMMARY.md** file where the table of contents define each entry has the following structure\n\n```markdown\n- [ENTRY_LABEL](FILE_PATH)\n  e.g.\n- [Welcome](README.md)\n```\n\nThe above file is used by Gitbook to generate the doc navigation visible on the left-hand side of Kepler.gl doc website.\nGitbook also has the ability to show description for each folder/section of the documentation by creating an entry in **SUMMARY.md**\nand create a new **README.md** file within said folder. The README.md file is a Gitbook convention that treats README files as if they were the main entry file for each folder.\n\nThe following is an example of doc section in SUMMARY.md file:\n\n```markdown\n- [User guides](docs/user-guides/README.md)\n```\n\n### Update Documentation\n\nThe integration with Gitbook allows to update the documentation in two different ways:\n\n- Update doc files in the Kepler.gl repo. Follow the PR flow like any other changes\n- Update documentation directly on Gitbook.\n\nFor both scenarios, changes will be propagated from one system to the other and vice versa. When updating Gitbook, a new git commit will be push to the Kepler.gl master branch.\n\n[demo-app]: http://kepler.gl/#/demo\n[documentationjs]: https://documentation.js.org/\n[eslint]: https://eslint.org/\n[enzyme]: https://airbnb.io/enzyme/\n[git-revert]: https://git-scm.com/docs/git-revert\n[git-setup]: https://help.github.com/articles/set-up-git\n[github]: https://github.com/keplergl/kepler.gl\n[github-pr]: https://help.github.com/articles/creating-a-pull-request/\n[jsDoc]: http://usejsdoc.org/\n[tape]: https://github.com/substack/tape\n[yarn-install]: https://yarnpkg.com/getting-started/install\n"
  },
  {
    "path": "contributing/README.md",
    "content": "# CONTRIBUTING\n\nGreat to have you here. Here are a few ways you can help make kepler.gl even better!\n\n* [Developer Certification of Origin \\(DCO\\)](./#developer-certification-of-origin-dco)\n* [Code of Conduct](./#code-of-conduct)\n* [Questions and Problems](./#questions-and-problems)\n* [Issues and Bugs](./#issues-and-bugs)\n* [Feature Requests](./#feature-requests)\n* [Improving Documentation](./#improving-documentation)\n* [Submitting Pull Request](./#submit-pr)\n\n## Developer Certification of Origin (DCO)\n\nWhen committing code, kepler.gl requires [Developer Certificate of Origin (DCO)][dco] process to be followed.\n\nThe DCO is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the DCO, reformatted for readability:\n\n```\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or\n\n(c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.\n\n(d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.\n```\n\n### DCO Sign-Off Methods\nContributors sign-off that they adhere to these requirements by adding a Signed-off-by line to commit messages.\n\n```\nSigned-off-by: Shan He <heshan0131@gmail.com>\n```\n\nUse the `-s` or `--signoff` command line to append this automatically to your commit message:\n\n```\n$ git commit -s -m 'This is my commit message'\n```\n\n## Code of Conduct\nHelp us keep kepler.gl open and inclusive. Please read and follow our [Code of Conduct](./CODE_OF_CONDUCT.md).\n\n## Questions and Problems\nWe are trying to keep our Github page for issues, bugs and feature requests only. You've got much better chances of getting supports on [Stack Overflow][stack]. Many people including our engineers are ready to answer questions on Stack Overflow. Your question might already been answered there.\n\n## Issues and Bugs\nIf you find a bug, you can help us by submitting an [Issue][git-iss] to our GitHub Repository. Please use the github [Bug Report Template][git-bug] and fill in as much as information as possible. Even better, you can submit a [Pull Request][git-pr] with a fix.\n\n# Feature Requests\n\nIf you want to contribute or add new features, please use [Issue][git-iss] on github projects to start a new discussion using the [Feature Request Template][git-feature]. If this receive a Go ahead, you can submit your patch as PR to the repository.\n\nIf you would like to implement a new feature then consider what kind of change it is:\n\n* **Take a look at our [roadmap][roadmap]** It lists out the items  we are planning to work on\n* **Pick your item** Pick an item to execute\n* **Claim the item** Reply in the ticket linked in the roadmap to claim the item, one of the member of the technical team will respond\n* **Major Changes** that you wish to contribute to the project should be discussed first in an\n  [GitHub issue][github-issues] that clearly outlines the changes and benefits of the feature.\n* **Small Changes** can directly be crafted and submitted to the [GitHub Repository][github]\n  as a Pull Request. See the section about [Pull Request Submission Guidelines](#submit-pr), and\n  for detailed information the [core development documentation][developers].\n* **Let's review your code** Create a pull request\n\n## Improving Documentation\n\nQuestions about kepler.gl? you can checkout the examples and medium articles on [kepler.gl][website].\n\n[User Guides][user-guide] and API Docs are saved in the [docs][api-docs] folder on Github. Help us improve documentation here by submitting a Pull Request.\n\n## Submitting Pull Request\n<b>First, follow the [development documentation][developers] for detailed guidance on environment setup, code style, testing and commit message conventions.</b>\n\n* Search [GitHub][git-pr] for an open or closed Pull Request\n  that relates to your submission. You don't want to duplicate effort.\n* Create the [development environment][developers.setup]\n* Make your changes in a new git branch:\n\n```bash\n$ git checkout -b my-fix-branch master\n```\n\n* Create your patch commit, **including appropriate test cases**.\n* If the changes affect public APIs, change or add relevant [documentation][developers.documentation].\n* Run [tests][developers.tests], and ensure that all tests pass.\n* Commit your changes using a descriptive commit message that follows our\n  [commit message conventions][developers.commits]. Adherence to the conventions is required, because release notes are automatically generated from these messages.\n\n[cla]: https://cla-assistant.io/keplergl/kepler.gl\n[github]: https://github.com/keplergl/kepler.gl\n[git-iss]: https://github.com/keplergl/kepler.gl/issues\n[git-pr]: https://github.com/keplergl/kepler.gl/pulls\n[git-feature]: https://github.com/keplergl/kepler.gl/issues/new?template=feature_request.md\n[git-bug]: https://github.com/keplergl/kepler.gl/issues/new?template=bug_report.md\n[stack]: https://stackoverflow.com/questions/tagged/kepler.gl\n[api-docs]: https://github.com/keplergl/kepler.gl/tree/master/docs\n[website]: https://keplergl.github.io/kepler.gl\n[user-guide]: https://github.com/keplergl/kepler.gl/blob/master/docs/a-introduction.md\n[roadmap]: https://github.com/keplergl/kepler.gl/wiki/Roadmap\n[developers]: DEVELOPERS.md\n[developers.commits]: ./#commits\n[developers.documentation]: ./#documentation\n[developers.rules]: ./#rules\n[developers.setup]: ./#setup\n[developers.tests]: ./#tests\n[dco]: https://probot.github.io/apps/dco/\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Docs\n\n## Table of contents\n- [What's new?](./release-notes.md)\n- [API References](./api-reference/README.md)\n- [User Guides](./user-guides/README.md)\n- [Jupyter Notebook](./keplergl-jupyter/README.md)\n"
  },
  {
    "path": "docs/RFC/table-class.md",
    "content": "Replace kepler.gl dataset with table class\n\n## Current Dataset structure\n\n```js\nexport type KeplerDataset = {\n  id: string;\n  label?: string;\n  color: RGBColor;\n\n  // fields and data\n  fields: KeplerField[];\n  dataContainer: DataContainer;\n\n  allIndexes: number[];\n  filteredIndex: number[];\n  filteredIdxCPU: number[];\n  filteredIndexForDomain: number[];\n  fieldPairs: {\n    defaultName: string;\n    pair: any;\n    suffix: string[];\n  }[];\n  gpuFilter: {\n    filterRange: number[][];\n    filterValueUpdateTriggers: any;\n    filterValueAccessor: any;\n  };\n  filterRecord: FilterRecord;\n  filterRecordCPU: FilterRecord;\n  changedFilters: any;\n\n  // table-injected metadata\n  sortColumn?: {\n    // column name: sorted idx\n    [key: string]: number[];\n  };\n  sortOrder?: string; // ASCENDING | DESCENDING | UNSORT\n\n  pinnedColumns?: string[];\n  // table-injected metadata\n  metadata?: object;\n};\n\nexport type KeplerField = {\n  analyzerType: string;\n  id: string;\n  name: string;\n  format: string;\n  fieldIdx: number;\n  type: string;\n\n  // meta data, storing domain and mappedValues\n  filterProps?: any;\n};\n```\n\n### Table class should handle:\n\n#### Add and remove columns (partially supported)\n\nMissing features\n\n- Copy over column meta, if replacing an existing column\n- Recompute fieldPairs\n\n#### Current kepler.gl methods:\n\n1. `initalize`\n\nto replace utils/dataset-utils `createNewDataEntry` This table method takes a `ProtoTable` and returns a `Table` to be saved in kepler state.\n\n```js\ntable.initalize(protoTable);\n\n// following meta data should be initialized\ntable.allIndexes,\n  table.filteredIndex,\n  table.filteredIndexForDomain,\n  table.fieldPairs,\n  table.gpuFilter;\n```\n\n2. `getValue(columnName, rowIdx)`\n   Get the value of a cell\n   Should be a curried function\n   getValue(columName) = idx => value\n\n3. `getColumnDomain(columnName)`\n   to replace `filter-utils.js` `getFieldDomain`\n   Get the value domain of a specific column used in filter\n\n4. `getColumnScaleDomain(columnName, scaleType)`\n   to replace `base-layer.js` `layer.calculateLayerDomain`\n   Get the domain of this column based on scale type\n\n5. `getSampleData(rows)`\n   Get a sample of rows to calculate layer boundaries\n\n6. `parseFieldValue(value, type) => string`\n   Parse cell value based on column type and return a string representation\n   Value the field value, type the field type\n\n7. `sortDatasetByColumn`\n   Sort a column, return an sorted index, assign to `sortColumn`, `sortOrder`\n\n8. `filterDataset(filters, layers, opt)`\n  to replace `filter-utils.js` `filterDataset`\n  Apply filters to dataset, return the filtered dataset with updated `gpuFilter`, `filterRecord`, `filteredIndex`, `filteredIndexForDomain`\n\n9. `filterDatasetCPU(filters)`\n   to replace `filter-utils.js` `filterDatasetCPU`\n   Apply filters to a dataset all on CPU, assign to `filteredIdxCPU`, `filterRecordCPU`\n"
  },
  {
    "path": "docs/api-reference/README.md",
    "content": "# API Reference\n\n## Table of Contents\n\n-  [Overview](./#overview)\n-  [Ecosystem](./ecosystem.md)\n    - [Component](./ecosystem.md#component)\n    - [Reducer and Forward Dispatcher](./ecosystem.md#reducer-and-forward-dispatcher)\n    - [Actions and Updaters](./ecosystem.md#actions-and-updaters)\n    - [Processors and Schema Manager](./ecosystem.md#processors-and-schema-manager)\n\n-  [Get Started](./get-started.md)\n\n-  Advanced Usage\n    - [Using reducer plugin](./advanced-usages/reducer-plugin.md)\n    - [Custom reducer initial state](./advanced-usages/custom-initial-state.md)\n    - [Using updaters to modify kepler.gl state](./advanced-usages/using-updaters.md)\n    - [Forward actions](./advanced-usages/forward-actions.md)\n    - [Saving and loading maps with schema manager](./advanced-usages/saving-loading-w-schema.md)\n    - [Replace UI component](./advanced-usages/replace-ui-component.md)\n    - [Custom Mapbox Host](./advanced-usages/custom-mapbox-host.md)\n    - [Custom Map Styles](./advanced-usages/custom-map-styles.md)\n    - [Localization](./localization/README.md)\n\n-  API\n    - [Components](./components/README.md)\n    - [Reducers](./reducers/README.md)\n    - [Actions and Updaters](./actions/actions.md)\n    - [Data Processor](./processors/processors.md)\n    - [Schemas](./schemas/README.md)\n\n## Overview\n\nKepler.gl is a __Redux-connected__ component. You can embed kepler.gl in your App, which uses redux to manage its state. The basic implementation of kepler.gl reducer is simple. However, to make the most of it, it's recommended to have basic knowledge on:\n\n- [React](https://reactjs.org/)\n- [Redux](https://redux.js.org/) state container\n- [React Redux connect](https://react-redux.js.org/)\n\nTo start out with kepler.gl, you simply need to add the Kepler.gl UI component and mount the Kepler.gl reducer. To give the user full access of all the functionalities of kepler.gl, this package also includes actions, schema managers and a set of utilities to load and save map data.\n"
  },
  {
    "path": "docs/api-reference/actions/README.md",
    "content": "# Actions\n\n...Coming soon\n"
  },
  {
    "path": "docs/api-reference/actions/actions.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [forwardActions][1]\n  - [forwardTo][2]\n  - [isForwardAction][5]\n  - [unwrap][7]\n  - [wrapTo][9]\n- [ActionTypes][12]\n- [mapStyleActions][14]\n  - [addCustomMapStyle][15]\n  - [inputMapStyle][16]\n  - [loadCustomMapStyle][18]\n  - [loadMapStyleErr][20]\n  - [loadMapStyles][22]\n  - [mapConfigChange][24]\n  - [mapStyleChange][26]\n  - [requestMapStyles][28]\n  - [set3dBuildingColor][30]\n- [main][32]\n  - [addDataToMap][33]\n  - [keplerGlInit][36]\n  - [receiveMapConfig][38]\n  - [resetMapConfig][41]\n- [visStateActions][42]\n  - [addFilter][43]\n  - [addLayer][45]\n  - [applyCPUFilter][47]\n  - [enlargeFilter][49]\n  - [interactionConfigChange][51]\n  - [layerConfigChange][53]\n  - [layerTextLabelChange][55]\n  - [layerTypeChange][57]\n  - [layerVisConfigChange][59]\n  - [layerVisualChannelConfigChange][61]\n  - [loadFiles][63]\n  - [loadFilesErr][65]\n  - [onLayerClick][67]\n  - [onLayerHover][69]\n  - [onMapClick][71]\n  - [onMouseMove][72]\n  - [removeDataset][74]\n  - [removeFilter][76]\n  - [removeLayer][78]\n  - [reorderLayer][80]\n  - [setEditorMode][83]\n  - [setFilter][86]\n  - [setFilterPlot][88]\n  - [setMapInfo][90]\n  - [showDatasetTable][92]\n  - [toggleFilterAnimation][94]\n  - [toggleLayerForMap][96]\n  - [updateAnimationTime][98]\n  - [updateFilterAnimationSpeed][100]\n  - [updateLayerAnimationSpeed][102]\n  - [updateLayerBlending][104]\n  - [updateVisData][106]\n- [uiStateActions][108]\n  - [addNotification][109]\n  - [cleanupExportImage][111]\n  - [hideExportDropdown][112]\n  - [openDeleteModal][113]\n  - [removeNotification][115]\n  - [setExportData][117]\n  - [setExportDataType][118]\n  - [setExportFiltered][120]\n  - [setExportImageDataUri][122]\n  - [setExportImageSetting][124]\n  - [setExportSelectedDataset][126]\n  - [setUserMapboxAccessToken][128]\n  - [showExportDropdown][130]\n  - [startExportingImage][132]\n  - [toggleMapControl][133]\n  - [toggleModal][135]\n  - [toggleSidePanel][137]\n- [rootActions][139]\n  - [deleteEntry][140]\n  - [registerEntry][142]\n  - [renameEntry][144]\n- [mapStateActions][146]\n  - [fitBounds][147]\n  - [togglePerspective][150]\n  - [toggleSplitMap][152]\n  - [updateMap][155]\n- [layerColorUIChange][158]\n- [setExportMapFormat][160]\n\n## forwardActions\n\nA set of helpers to forward dispatch actions to a specific instance reducer\n\n### forwardTo\n\nReturns an action dispatcher that wraps and forwards the actions to a specific instance\n\n**Parameters**\n\n- `id` **[string][162]** instance id\n- `dispatch` **[Function][163]** action dispatcher\n\n**Examples**\n\n```javascript\n// action and forward dispatcher\nimport {toggleSplitMap, forwardTo} from '@kepler.gl/actions';\nimport {connect} from 'react-redux';\n\nconst MapContainer = props => (\n <div>\n  <button onClick={() => props.keplerGlDispatch(toggleSplitMap())}/>\n </div>\n)\n\nconst mapDispatchToProps = (dispatch, props) => ({\n dispatch,\n keplerGlDispatch: forwardTo(‘foo’, dispatch)\n});\n\nexport default connect(\n state => state,\n mapDispatchToProps\n)(MapContainer);\n```\n\n### isForwardAction\n\nWhether an action is a forward action\n\n**Parameters**\n\n- `action` **[Object][164]** the action object\n\nReturns **[boolean][165]** boolean - whether the action is a forward action\n\n### unwrap\n\nUnwrap an action\n\n**Parameters**\n\n- `action` **[Object][164]** the action object\n\nReturns **[Object][164]** unwrapped action\n\n### wrapTo\n\nWrap an action into a forward action that only modify the state of a specific\nkepler.gl instance. kepler.gl reducer will look for signatures in the action to\ndetermine whether it needs to be forwarded to a specific instance reducer.\n\nwrapTo can be curried. You can create a curried action wrapper by only supply the `id` argument\n\nA forward action looks like this\n\n```js\n {\n   type: \"@@kepler.gl/LAYER_CONFIG_CHANGE\",\n   payload: {\n     type: '@@kepler.gl/LAYER_CONFIG_CHANGE',\n     payload: {},\n     meta: {\n      // id of instance\n       _id_: id\n      // other meta\n     }\n   },\n   meta: {\n     _forward_: '@redux-forward/FORWARD',\n     _addr_: '@@KG_id'\n   }\n };\n```\n\n**Parameters**\n\n- `id` **[string][162]** The id to forward to\n- `action` **[Object][164]** the action object {type: string, payload: \\*}\n\n**Examples**\n\n```javascript\nimport {wrapTo, togglePerspective} from '@kepler.gl/actions';\n\n// This action will only dispatch to the KeplerGl instance with `id: map_1`\nthis.props.dispatch(wrapTo('map_1', togglePerspective()));\n\n// You can also create a curried action for each instance\nconst wrapToMap1 = wrapTo('map_1');\nthis.props.dispatch(wrapToMap1(togglePerspective()));\n```\n\n## ActionTypes\n\nKepler.gl action types, can be listened by reducers to perform additional tasks whenever an action is called in kepler.gl\n\nType: [Object][164]\n\n**Examples**\n\n```javascript\n// store.js\nimport {handleActions} from 'redux-actions';\nimport {createStore, combineReducers, applyMiddleware} from 'redux';\nimport {taskMiddleware} from 'react-palm/tasks';\n\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {ActionTypes} from '@kepler.gl/actions';\n\nconst appReducer = handleActions(\n  {\n    // listen on kepler.gl map update action to store a copy of viewport in app state\n    [ActionTypes.UPDATE_MAP]: (state, action) => ({\n      ...state,\n      viewport: action.payload\n    })\n  },\n  {}\n);\n\nconst reducers = combineReducers({\n  app: appReducer,\n  keplerGl: keplerGlReducer\n});\n\nexport default createStore(reducers, {}, applyMiddleware(taskMiddleware));\n```\n\n## mapStyleActions\n\nActions handled mostly by `mapStyle` reducer.\nThey manage the display of base map, such as loading and receiving base map styles,\nhiding and showing map layers, user input of custom map style url.\n\n### addCustomMapStyle\n\nAdd map style from user input to reducer and set it to current style\nThis action is called when user click confirm after putting in a valid style url in the custom map style dialog.\nIt should not be called from outside kepler.gl without a valid `inputStyle` in the `mapStyle` reducer.\nparam {void}\n\n- **ActionTypes**: [`ActionTypes.ADD_CUSTOM_MAP_STYLE`][12]\n- **Updaters**: [`mapStyleUpdaters.addCustomMapStyleUpdater`][166]\n\n### inputMapStyle\n\nInput a custom map style object\n\n- **ActionTypes**: [`ActionTypes.INPUT_MAP_STYLE`][12]\n- **Updaters**: [`mapStyleUpdaters.inputMapStyleUpdater`][167]\n\n**Parameters**\n\n- `inputStyle` **[Object][164]**\n  - `inputStyle.url` **[string][162]** style url e.g. `'mapbox://styles/heshan/xxxxxyyyyzzz'`\n  - `inputStyle.id` **[string][162]** style url e.g. `'custom_style_1'`\n  - `inputStyle.style` **[Object][164]** actual mapbox style json\n  - `inputStyle.name` **[string][162]** style name\n  - `inputStyle.layerGroups` **[Object][164]** layer groups that can be used to set map layer visibility\n  - `inputStyle.icon` **[Object][164]** icon image data url\n- `mapState` **[Object][164]** mapState is optional\n\n### loadCustomMapStyle\n\nCallback when a custom map style object is received\n\n- **ActionTypes**: [`ActionTypes.LOAD_CUSTOM_MAP_STYLE`][12]\n- **Updaters**: [`mapStyleUpdaters.loadCustomMapStyleUpdater`][168]\n\n**Parameters**\n\n- `customMapStyle` **[Object][164]**\n  - `customMapStyle.icon` **[string][162]**\n  - `customMapStyle.style` **[Object][164]**\n  - `customMapStyle.error` **any**\n\n### loadMapStyleErr\n\nCallback when load map style error\n\n- **ActionTypes**: [`ActionTypes.LOAD_MAP_STYLE_ERR`][12]\n- **Updaters**: [`mapStyleUpdaters.loadMapStyleErrUpdater`][169]\n\n**Parameters**\n\n- `error` **any**\n\n### loadMapStyles\n\nCallback when load map style success\n\n- **ActionTypes**: [`ActionTypes.LOAD_MAP_STYLES`][12]\n- **Updaters**: [`mapStyleUpdaters.loadMapStylesUpdater`][170]\n\n**Parameters**\n\n- `newStyles` **[Object][164]** a `{[id]: style}` mapping\n\n### mapConfigChange\n\nUpdate `visibleLayerGroups`to change layer group visibility\n\n- **ActionTypes**: [`ActionTypes.MAP_CONFIG_CHANGE`][12]\n- **Updaters**: [`mapStyleUpdaters.mapConfigChangeUpdater`][171]\n\n**Parameters**\n\n- `mapStyle` **[Object][164]** new config `{visibleLayerGroups: {label: false, road: true, background: true}}`\n\n### mapStyleChange\n\nChange to another map style. The selected style should already been loaded into `mapStyle.mapStyles`\n\n- **ActionTypes**: [`ActionTypes.MAP_STYLE_CHANGE`][12]\n- **Updaters**: [`mapStyleUpdaters.mapStyleChangeUpdater`][172]\n\n**Parameters**\n\n- `styleType` **[string][162]** the style to change to\n\n### requestMapStyles\n\nRequest map style style object based on style.url.\n\n- **ActionTypes**: [`ActionTypes.REQUEST_MAP_STYLES`][12]\n- **Updaters**: [`mapStyleUpdaters.requestMapStylesUpdater`][173]\n\n**Parameters**\n\n- `mapStyles` **[Array][174]&lt;[Object][164]>**\n\n### set3dBuildingColor\n\nSet 3d building layer group color\n\n- **ActionTypes**: [`ActionTypes.SET_3D_BUILDING_COLOR`][12]\n- **Updaters**: [`mapStyleUpdaters.set3dBuildingColorUpdater`][175]\n\n**Parameters**\n\n- `color` **[Array][174]** [r, g, b]\n\n## main\n\nMain kepler.gl actions, these actions handles loading data and config into kepler.gl reducer. These actions\nis listened by all subreducers,\n\n### addDataToMap\n\nAdd data to kepler.gl reducer, prepare map with preset configuration if config is passed.\nKepler.gl provides a handy set of utils to parse data from different formats to the `data` object required in dataset. You rarely need to manually format the data obejct.\n\nUse `KeplerGlSchema.getConfigToSave` to generate a json blob of the currents instance config.\nThe config object value will always have higher precedence than the options properties.\n\nKepler.gl uses `dataId` in the config to match with loaded dataset. If you pass a config object, you need\nto match the `info.id` of your dataset to the `dataId` in each `layer`, `filter` and `interactionConfig.tooltips.fieldsToShow`\n\n- **ActionTypes**: [`ActionTypes.ADD_DATA_TO_MAP`][12]\n- **Updaters**: [`combinedUpdaters.addDataToMapUpdater`][176]\n\n**Parameters**\n\n- `data` **[Object][164]**\n  - `data.datasets` **([Array][174]&lt;[Object][164]> | [Object][164])** **\\*required** datasets can be a dataset or an array of datasets\n    Each dataset object needs to have `info` and `data` property.\n    - `data.datasets.info` **[Object][164]** \\-info of a dataset\n      - `data.datasets.info.id` **[string][162]** id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n      - `data.datasets.info.label` **[string][162]** A display name of this dataset\n    - `data.datasets.data` **[Object][164]** **\\*required** The data object, in a tabular format with 2 properties `fields` and `rows`\n      - `data.datasets.data.fields` **[Array][174]&lt;[Object][164]>** **\\*required** Array of fields,\n        - `data.datasets.data.fields.name` **[string][162]** **\\*required** Name of the field,\n      - `data.datasets.data.rows` **[Array][174]&lt;[Array][174]>** **\\*required** Array of rows, in a tabular format with `fields` and `rows`\n  - `data.options` **[Object][164]**\n    - `data.options.centerMap` **[boolean][165]** `default: true` if `centerMap` is set to `true` kepler.gl will\n      place the map view within the data points boundaries. `options.centerMap` will override `config.mapState` if passed in.\n    - `data.options.readOnly` **[boolean][165]** `default: false` if `readOnly` is set to `true`\n      the left setting panel will be hidden\n    - `data.options.keepExistingConfig` **[boolean][165]** whether to keep exiting map data and associated layer filter interaction config `default: false`.\n  - `data.config` **[Object][164]** this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n\n**Examples**\n\n```javascript\n// app.js\nimport {addDataToMap} from '@kepler.gl/actions';\n\nconst sampleTripData = {\n  fields: [\n    {name: 'tpep_pickup_datetime', format: 'YYYY-M-D H:m:s', type: 'timestamp'},\n    {name: 'pickup_longitude', format: '', type: 'real'},\n    {name: 'pickup_latitude', format: '', type: 'real'}\n  ],\n  rows: [\n    ['2015-01-15 19:05:39 +00:00', -73.99389648, 40.75011063],\n    ['2015-01-15 19:05:39 +00:00', -73.97642517, 40.73981094],\n    ['2015-01-15 19:05:40 +00:00', -73.96870422, 40.75424576]\n  ]\n};\n\nconst sampleConfig = {\n  visState: {\n    filters: [\n      {\n        id: 'me',\n        dataId: 'test_trip_data',\n        name: 'tpep_pickup_datetime',\n        type: 'timeRange',\n        view: 'enlarged'\n      }\n    ]\n  }\n};\n\nthis.props.dispatch(\n  addDataToMap({\n    datasets: {\n      info: {\n        label: 'Sample Taxi Trips in New York City',\n        id: 'test_trip_data'\n      },\n      data: sampleTripData\n    },\n    options: {\n      centerMap: true,\n      readOnly: false,\n      keepExistingConfig: false\n    },\n    info: {\n      title: 'Taro and Blue',\n      description: 'This is my map'\n    },\n    config: sampleConfig\n  })\n);\n```\n\n### keplerGlInit\n\nInitialize kepler.gl reducer. It is used to pass in `mapboxApiAccessToken` to `mapStyle` reducer.\n\n- **ActionTypes**: [`ActionTypes.INIT`][12]\n- **Updaters**: [`mapStyleUpdaters.initMapStyleUpdater`][177]\n\n**Parameters**\n\n- `payload` **[Object][164]**\n  - `payload.mapboxApiAccessToken` **[string][162]** mapboxApiAccessToken to be saved to mapStyle reducer\n  - `payload.mapboxApiUrl` **[string][162]** mapboxApiUrl to be saved to mapStyle reducer.\n  - `payload.mapStylesReplaceDefault` **[Boolean][165]** mapStylesReplaceDefault to be saved to mapStyle reducer\n\n### receiveMapConfig\n\nPass config to kepler.gl instance, prepare the state with preset configs.\nCalling `KeplerGlSchema.parseSavedConfig` to convert saved config before passing it in is required.\n\nYou can call `receiveMapConfig` before passing in any data. The reducer will store layer and filter config, waiting for\ndata to come in. When data arrives, you can call `addDataToMap` without passing any config, and the reducer will try to match\npreloaded configs. This behavior is designed to allow asynchronous data loading.\n\nIt is also useful when you want to prepare the kepler.gl instance with some preset layer and filter settings.\n**Note** Sequence is important, `receiveMapConfig` needs to be called **before** data is loaded. Currently kepler.gl doesn't allow calling `receiveMapConfig` after data is loaded.\nIt will reset current configuration first then apply config to it.\n\n- **ActionTypes**: [`ActionTypes.RECEIVE_MAP_CONFIG`][12]\n- **Updaters**: [`mapStateUpdaters.receiveMapConfigUpdater`][178], [`mapStyleUpdaters.receiveMapConfigUpdater`][179], [`visStateUpdaters.receiveMapConfigUpdater`][180]\n\n**Parameters**\n\n- `config` **[Object][164]** **\\*required** The Config Object\n- `options` **[Object][164]** **\\*optional** The Option object\n  - `options.centerMap` **[boolean][165]** `default: true` if `centerMap` is set to `true` kepler.gl will\n    place the map view within the data points boundaries\n  - `options.readOnly` **[boolean][165]** `default: false` if `readOnly` is set to `true`\n    the left setting panel will be hidden\n  - `options.keepExistingConfig` **[boolean][165]** whether to keep exiting layer filter and interaction config `default: false`.\n\n**Examples**\n\n```javascript\nimport {receiveMapConfig} from '@kepler.gl/actions';\nimport KeplerGlSchema from '@kepler.gl/schemas';\n\nconst parsedConfig = KeplerGlSchema.parseSavedConfig(config);\nthis.props.dispatch(receiveMapConfig(parsedConfig));\n```\n\n### resetMapConfig\n\nReset all sub-reducers to its initial state. This can be used to clear out all configuration in the reducer.\n\n- **ActionTypes**: [`ActionTypes.RESET_MAP_CONFIG`][12]\n- **Updaters**: [`mapStateUpdaters.resetMapConfigUpdater`][181], [`mapStyleUpdaters.resetMapConfigMapStyleUpdater`][182], [`mapStyleUpdaters.resetMapConfigMapStyleUpdater`][182], [`visStateUpdaters.resetMapConfigUpdater`][183]\n\n## visStateActions\n\nActions handled mostly by `visState` reducer.\nThey manage how data is processed, filtered and displayed on the map by operates on layers,\nfilters and interaction settings.\n\n### addFilter\n\nAdd a new filter\n\n- **ActionTypes**: [`ActionTypes.ADD_FILTER`][12]\n- **Updaters**: [`visStateUpdaters.addFilterUpdater`][184]\n\n**Parameters**\n\n- `dataId` **[string][162]** dataset `id` this new filter is associated with\n\nReturns **{type: ActionTypes.ADD_FILTER, dataId: dataId}**\n\n### addLayer\n\nAdd a new layer\n\n- **ActionTypes**: [`ActionTypes.ADD_LAYER`][12]\n- **Updaters**: [`visStateUpdaters.addLayerUpdater`][185]\n\n**Parameters**\n\n- `props` **[Object][164]** new layer props\n\nReturns **{type: ActionTypes.ADD_LAYER, props: props}**\n\n### applyCPUFilter\n\nTrigger CPU filter of selected dataset\n\n- **ActionTypes**: [`ActionTypes.APPLY_CPU_FILTER`][12]\n- **Updaters**: [`visStateUpdaters.applyCPUFilterUpdater`][186]\n\n**Parameters**\n\n- `dataId` **([string][162] | Arrary&lt;[string][162]>)** single dataId or an array of dataIds\n\nReturns **{type: ActionTypes.APPLY_CPU_FILTER, dataId: [string][162]}**\n\n### enlargeFilter\n\nShow larger time filter at bottom for time playback (apply to time filter only)\n\n- **ActionTypes**: [`ActionTypes.ENLARGE_FILTER`][12]\n- **Updaters**: [`visStateUpdaters.enlargeFilterUpdater`][187]\n\n**Parameters**\n\n- `idx` **[Number][188]** index of filter to enlarge\n\nReturns **{type: ActionTypes.ENLARGE_FILTER, idx: idx}**\n\n### interactionConfigChange\n\nUpdate `interactionConfig`\n\n- **ActionTypes**: [`ActionTypes.INTERACTION_CONFIG_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.interactionConfigChangeUpdater`][189]\n\n**Parameters**\n\n- `config` **[Object][164]** new config as key value map: `{tooltip: {enabled: true}}`\n\nReturns **{type: ActionTypes.INTERACTION_CONFIG_CHANGE, config: config}**\n\n### layerConfigChange\n\nUpdate layer base config: dataId, label, column, isVisible\n\n- **ActionTypes**: [`ActionTypes.LAYER_CONFIG_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerConfigChangeUpdater`][190]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `newConfig` **[Object][164]** new config\n\nReturns **{type: ActionTypes.LAYER_CONFIG_CHANGE, oldLayer: oldLayer, newConfig: newConfig}**\n\n### layerTextLabelChange\n\nUpdate layer text label\n\n- **ActionTypes**: [`ActionTypes.LAYER_TEXT_LABEL_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerTextLabelChangeUpdater`][191]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `idx` **[Number][188]** \\-`idx` of text label to be updated\n- `prop` **[string][162]** `prop` of text label, e,g, `anchor`, `alignment`, `color`, `size`, `field`\n- `value` **any** new value\n\n### layerTypeChange\n\nUpdate layer type. Previews layer config will be copied if applicable.\n\n- **ActionTypes**: [`ActionTypes.LAYER_TYPE_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerTypeChangeUpdater`][192]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `newType` **[string][162]** new type\n\nReturns **{type: ActionTypes.LAYER_TYPE_CHANGE, oldLayer: oldLayer, newType: newType}**\n\n### layerVisConfigChange\n\nUpdate layer `visConfig`\n\n- **ActionTypes**: [`ActionTypes.LAYER_VIS_CONFIG_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerVisConfigChangeUpdater`][193]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `newVisConfig` **[Object][164]** new visConfig as a key value map: e.g. `{opacity: 0.8}`\n\nReturns **{type: ActionTypes.LAYER_VIS_CONFIG_CHANGE, oldLayer: oldLayer, newVisConfig: newVisConfig}**\n\n### layerVisualChannelConfigChange\n\nUpdate layer visual channel\n\n- **ActionTypes**: [`ActionTypes.LAYER_VISUAL_CHANNEL_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerVisualChannelChangeUpdater`][194]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `newConfig` **[Object][164]** new visual channel config\n- `channel` **[string][162]** channel to be updated\n\nReturns **{type: ActionTypes.LAYER_VISUAL_CHANNEL_CHANGE, oldLayer: oldLayer, newConfig: newConfig, channel: channel}**\n\n### loadFiles\n\nTrigger file loading dispatch `addDataToMap` if succeed, or `loadFilesErr` if failed\n\n- **ActionTypes**: [`ActionTypes.LOAD_FILES`][12]\n- **Updaters**: [`uiStateUpdaters.loadFilesUpdater`][195], [`visStateUpdaters.loadFilesUpdater`][196]\n\n**Parameters**\n\n- `files` **[Array][174]&lt;[Object][164]>** array of fileblob\n\nReturns **{type: ActionTypes.LOAD_FILES, files: any}**\n\n### loadFilesErr\n\nTrigger loading file error\n\n- **ActionTypes**: [`ActionTypes.LOAD_FILES_ERR`][12]\n- **Updaters**: [`uiStateUpdaters.loadFilesErrUpdater`][197], [`visStateUpdaters.loadFilesErrUpdater`][198]\n\n**Parameters**\n\n- `error` **any**\n\nReturns **{type: ActionTypes.LOAD_FILES_ERR, error: [Object][164]}**\n\n### onLayerClick\n\nTrigger layer click event with clicked object\n\n- **ActionTypes**: [`ActionTypes.LAYER_CLICK`][12]\n- **Updaters**: [`visStateUpdaters.layerClickUpdater`][199]\n\n**Parameters**\n\n- `info` **[Object][164]** Object clicked, returned by deck.gl\n\nReturns **{type: ActionTypes.LAYER_CLICK, info: info}**\n\n### onLayerHover\n\nTrigger layer hover event with hovered object\n\n- **ActionTypes**: [`ActionTypes.LAYER_HOVER`][12]\n- **Updaters**: [`visStateUpdaters.layerHoverUpdater`][200]\n\n**Parameters**\n\n- `info` **[Object][164]** Object hovered, returned by deck.gl\n\nReturns **{type: ActionTypes.LAYER_HOVER, info: info}**\n\n### onMapClick\n\nTrigger map click event, unselect clicked object\n\n- **ActionTypes**: [`ActionTypes.MAP_CLICK`][12]\n- **Updaters**: [`visStateUpdaters.mapClickUpdater`][201]\n\nReturns **{type: ActionTypes.MAP_CLICK}**\n\n### onMouseMove\n\nTrigger map mouse moveevent, payload would be\nReact-map-gl MapLayerMouseEvent\n[https://visgl.github.io/react-map-gl/docs/api-reference/types#maplayermouseevent][202]\n\n- **ActionTypes**: [`ActionTypes.MOUSE_MOVE`][12]\n- **Updaters**: [`visStateUpdaters.mouseMoveUpdater`][203]\n\n**Parameters**\n\n- `evt` **[Object][164]** MapLayerMouseEvent\n\nReturns **{type: ActionTypes.MAP_CLICK}**\n\n### removeDataset\n\nRemove a dataset and all layers, filters, tooltip configs that based on it\n\n- **ActionTypes**: [`ActionTypes.REMOVE_DATASET`][12]\n- **Updaters**: [`visStateUpdaters.removeDatasetUpdater`][204]\n\n**Parameters**\n\n- `key` **[string][162]** dataset id\n\nReturns **{type: ActionTypes.REMOVE_DATASET, key: key}**\n\n### removeFilter\n\nRemove a filter from `visState.filters`, once a filter is removed, data will be re-filtered and layer will be updated\n\n- **ActionTypes**: [`ActionTypes.REMOVE_FILTER`][12]\n- **Updaters**: [`visStateUpdaters.removeFilterUpdater`][205]\n\n**Parameters**\n\n- `idx` **[Number][188]** idx of filter to be removed\n\nReturns **{type: ActionTypes.REMOVE_FILTER, idx: idx}**\n\n### removeLayer\n\nRemove a layer\n\n- **ActionTypes**: [`ActionTypes.REMOVE_LAYER`][12]\n- **Updaters**: [`visStateUpdaters.removeLayerUpdater`][206]\n\n**Parameters**\n\n- `idx` **[Number][188]** idx of layer to be removed\n\nReturns **{type: ActionTypes.REMOVE_LAYER, idx: idx}**\n\n### reorderLayer\n\nReorder layer, order is an array of layer indexes, index 0 will be the one at the bottom\n\n- **ActionTypes**: [`ActionTypes.REORDER_LAYER`][12]\n- **Updaters**: [`visStateUpdaters.reorderLayerUpdater`][207]\n\n**Parameters**\n\n- `order` **[Array][174]&lt;[Number][188]>** an array of layer indexes\n\n**Examples**\n\n```javascript\n// bring `layers[1]` below `layers[0]`, the sequence layers will be rendered is `1`, `0`, `2`, `3`.\n// `1` will be at the bottom, `3` will be at the top.\nthis.props.dispatch(reorderLayer([1, 0, 2, 3]));\n```\n\nReturns **{type: ActionTypes.REORDER_LAYER, order: order}**\n\n### setEditorMode\n\nSet the map mode\n\n- **ActionTypes**: [`ActionTypes.SET_EDITOR_MODE`][12]\n- **Updaters**: [`visStateUpdaters.setEditorModeUpdater`][208]\n\n**Parameters**\n\n- `mode` **[string][162]** one of EDITOR_MODES\n\n**Examples**\n\n```javascript\nimport {setMapMode} from '@kepler.gl/actions';\nimport {EDITOR_MODES} from '@kepler.gl/constants';\n\nthis.props.dispatch(setMapMode(EDITOR_MODES.DRAW_POLYGON));\n```\n\n### setFilter\n\nUpdate filter property\n\n- **ActionTypes**: [`ActionTypes.SET_FILTER`][12]\n- **Updaters**: [`visStateUpdaters.setFilterUpdater`][209]\n\n**Parameters**\n\n- `idx` **[Number][188]** \\-`idx` of filter to be updated\n- `prop` **[string][162]** `prop` of filter, e,g, `dataId`, `name`, `value`\n- `value` **any** new value\n- `valueIndex` **[Number][188]** array properties like dataset require index in order to improve performance\n\nReturns **{type: ActionTypes.SET_FILTER, idx: idx, prop: prop, value: value}**\n\n### setFilterPlot\n\nSet the property of a filter plot\n\n- **ActionTypes**: [`ActionTypes.SET_FILTER_PLOT`][12]\n- **Updaters**: [`visStateUpdaters.setFilterPlotUpdater`][210]\n\n**Parameters**\n\n- `idx` **[Number][188]**\n- `newProp` **[Object][164]** key value mapping of new prop `{yAxis: 'histogram'}`\n\nReturns **{type: ActionTypes.SET_FILTER_PLOT, idx: any, newProp: any}**\n\n### setMapInfo\n\nSet the property of a filter plot\n\n- **ActionTypes**: [`ActionTypes.SET_MAP_INFO`][12]\n- **Updaters**: [`visStateUpdaters.setMapInfoUpdater`][211]\n\n**Parameters**\n\n- `info`\n- `idx` **[Number][188]**\n- `newProp` **[Object][164]** key value mapping of new prop `{yAxis: 'histogram'}`\n\nReturns **{type: ActionTypes.SET_FILTER_PLOT, idx: any, newProp: any}**\n\n### showDatasetTable\n\nDisplay dataset table in a modal\n\n- **ActionTypes**: [`ActionTypes.SHOW_DATASET_TABLE`][12]\n- **Updaters**: [`visStateUpdaters.showDatasetTableUpdater`][212]\n\n**Parameters**\n\n- `dataId` **[string][162]** dataset id to show in table\n\nReturns **{type: ActionTypes.SHOW_DATASET_TABLE, dataId: dataId}**\n\n### toggleFilterAnimation\n\nStart and end filter animation\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_FILTER_ANIMATION`][12]\n- **Updaters**: [`visStateUpdaters.toggleFilterAnimationUpdater`][213]\n\n**Parameters**\n\n- `idx` **[Number][188]** idx of filter\n\nReturns **{type: ActionTypes.TOGGLE_FILTER_ANIMATION, idx: idx}**\n\n### toggleLayerForMap\n\nToggle visibility of a layer in a split map\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_LAYER_FOR_MAP`][12]\n- **Updaters**: [`visStateUpdaters.toggleLayerForMapUpdater`][214]\n\n**Parameters**\n\n- `mapIndex` **[Number][188]** index of the split map\n- `layerId` **[string][162]** id of the layer\n\nReturns **{type: ActionTypes.TOGGLE_LAYER_FOR_MAP, mapIndex: any, layerId: any}**\n\n### updateAnimationTime\n\nReset animation\n\n- **ActionTypes**: [`ActionTypes.UPDATE_ANIMATION_TIME`][12]\n- **Updaters**: [`visStateUpdaters.updateAnimationTimeUpdater`][215]\n\n**Parameters**\n\n- `value` **[Number][188]** Current value of the slider\n\nReturns **{type: ActionTypes.UPDATE_ANIMATION_TIME, value: value}**\n\n### updateFilterAnimationSpeed\n\nChange filter animation speed\n\n- **ActionTypes**: [`ActionTypes.UPDATE_FILTER_ANIMATION_SPEED`][12]\n- **Updaters**: [`visStateUpdaters.updateFilterAnimationSpeedUpdater`][216]\n\n**Parameters**\n\n- `idx` **[Number][188]** `idx` of filter\n- `speed` **[Number][188]** `speed` to change it to. `speed` is a multiplier\n\nReturns **{type: ActionTypes.UPDATE_FILTER_ANIMATION_SPEED, idx: idx, speed: speed}**\n\n### updateLayerAnimationSpeed\n\nupdate trip layer animation speed\n\n- **ActionTypes**: [`ActionTypes.UPDATE_LAYER_ANIMATION_SPEED`][12]\n- **Updaters**: [`visStateUpdaters.updateLayerAnimationSpeedUpdater`][217]\n\n**Parameters**\n\n- `speed` **[Number][188]** `speed` to change it to. `speed` is a multiplier\n\nReturns **{type: ActionTypes.UPDATE_LAYER_ANIMATION_SPEED, speed: speed}**\n\n### updateLayerBlending\n\nUpdate layer blending mode\n\n- **ActionTypes**: [`ActionTypes.UPDATE_LAYER_BLENDING`][12]\n- **Updaters**: [`visStateUpdaters.updateLayerBlendingUpdater`][218]\n\n**Parameters**\n\n- `mode` **[string][162]** one of `additive`, `normal` and `subtractive`\n\nReturns **{type: ActionTypes.UPDATE_LAYER_BLENDING, mode: mode}**\n\n### updateVisData\n\nAdd new dataset to `visState`, with option to load a map config along with the datasets\n\n- **ActionTypes**: [`ActionTypes.UPDATE_VIS_DATA`][12]\n- **Updaters**: [`visStateUpdaters.updateVisDataUpdater`][219]\n\n**Parameters**\n\n- `datasets` **([Array][174]&lt;[Object][164]> | [Object][164])** **\\*required** datasets can be a dataset or an array of datasets\n  Each dataset object needs to have `info` and `data` property.\n  - `datasets.info` **[Object][164]** \\-info of a dataset\n    - `datasets.info.id` **[string][162]** id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n    - `datasets.info.label` **[string][162]** A display name of this dataset\n  - `datasets.data` **[Object][164]** **\\*required** The data object, in a tabular format with 2 properties `fields` and `rows`\n    - `datasets.data.fields` **[Array][174]&lt;[Object][164]>** **\\*required** Array of fields,\n      - `datasets.data.fields.name` **[string][162]** **\\*required** Name of the field,\n    - `datasets.data.rows` **[Array][174]&lt;[Array][174]>** **\\*required** Array of rows, in a tabular format with `fields` and `rows`\n- `options` **[Object][164]**\n  - `options.centerMap` **[boolean][165]** `default: true` if `centerMap` is set to `true` kepler.gl will\n    place the map view within the data points boundaries\n  - `options.readOnly` **[boolean][165]** `default: false` if `readOnly` is set to `true`\n    the left setting panel will be hidden\n- `config` **[Object][164]** this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n\nReturns **{type: ActionTypes.UPDATE_VIS_DATA, datasets: datasets, options: options, config: config}**\n\n## uiStateActions\n\nActions handled mostly by `uiState` reducer.\nThey manage UI changes in tha app, such as open and close side panel,\nswitch between tabs in the side panel, open and close modal dialog for exporting data / images etc.\nIt also manges which settings are selected during image and map export\n\n### addNotification\n\nAdd a notification to be displayed.\nExisting notification is going to be updated in case of matching ids.\n\n- **ActionTypes**: [`ActionTypes.ADD_NOTIFICATION`][12]\n- **Updaters**: [`uiStateUpdaters.addNotificationUpdater`][220]\n\n**Parameters**\n\n- `notification` **[Object][164]** The `notification` object to be added\n\n### cleanupExportImage\n\nDelete cached export image\n\n- **ActionTypes**: [`ActionTypes.CLEANUP_EXPORT_IMAGE`][12]\n- **Updaters**: [`uiStateUpdaters.cleanupExportImage`][221]\n\n### hideExportDropdown\n\nHide side panel header dropdown, activated by clicking the share link on top of the side panel\n\n- **ActionTypes**: [`ActionTypes.HIDE_EXPORT_DROPDOWN`][12]\n- **Updaters**: [`uiStateUpdaters.hideExportDropdownUpdater`][222]\n\n### openDeleteModal\n\nToggle active map control panel\n\n- **ActionTypes**: [`ActionTypes.OPEN_DELETE_MODAL`][12]\n- **Updaters**: [`uiStateUpdaters.openDeleteModalUpdater`][223]\n\n**Parameters**\n\n- `datasetId` **[string][162]** `id` of the dataset to be deleted\n\n### removeNotification\n\nRemove a notification\n\n- **ActionTypes**: [`ActionTypes.REMOVE_NOTIFICATION`][12]\n- **Updaters**: [`uiStateUpdaters.removeNotificationUpdater`][224]\n\n**Parameters**\n\n- `id` **[string][162]** `id` of the notification to be removed\n\n### setExportData\n\nWhether to including data in map config, toggle between `true` or `false`\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_DATA`][12]\n- **Updaters**: [`uiStateUpdaters.setExportDataUpdater`][225]\n\n### setExportDataType\n\nSet data format for exporting data\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_DATA_TYPE`][12]\n- **Updaters**: [`uiStateUpdaters.setExportDataTypeUpdater`][226]\n\n**Parameters**\n\n- `dataType` **[string][162]** one of `'text/csv'`\n\n### setExportFiltered\n\nWhether to export filtered data, `true` or `false`\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_FILTERED`][12]\n- **Updaters**: [`uiStateUpdaters.setExportFilteredUpdater`][227]\n\n**Parameters**\n\n- `payload` **[boolean][165]** set `true` to ony export filtered data\n\n### setExportImageDataUri\n\nSet `exportImage.setExportImageDataUri` to a dataUri\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_IMAGE_DATA_URI`][12]\n- **Updaters**: [`uiStateUpdaters.setExportImageDataUri`][228]\n\n**Parameters**\n\n- `dataUri` **[string][162]** export image data uri\n\n### setExportImageSetting\n\nSet `exportImage` settings: ratio, resolution, legend\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_IMAGE_SETTING`][12]\n- **Updaters**: [`uiStateUpdaters.setExportImageSetting`][229]\n\n**Parameters**\n\n- `newSetting` **[Object][164]** {ratio: '1x'}\n\n### setExportSelectedDataset\n\nSet selected dataset for export\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_SELECTED_DATASET`][12]\n- **Updaters**: [`uiStateUpdaters.setExportSelectedDatasetUpdater`][230]\n\n**Parameters**\n\n- `datasetId` **[string][162]** dataset id\n\n### setUserMapboxAccessToken\n\nWhether we export a mapbox access token used to create a single map html file\n\n- **ActionTypes**: [`ActionTypes.SET_USER_MAPBOX_ACCESS_TOKEN`][12]\n- **Updaters**: [`uiStateUpdaters.setUserMapboxAccessTokenUpdater`][231]\n\n**Parameters**\n\n- `payload` **[string][162]** mapbox access token\n\n### showExportDropdown\n\nHide and show side panel header dropdown, activated by clicking the share link on top of the side panel\n\n- **ActionTypes**: [`ActionTypes.SHOW_EXPORT_DROPDOWN`][12]\n- **Updaters**: [`uiStateUpdaters.showExportDropdownUpdater`][232]\n\n**Parameters**\n\n- `id` **[string][162]** id of the dropdown\n\n### startExportingImage\n\nSet `exportImage.exporting` to true\n\n- **ActionTypes**: [`ActionTypes.START_EXPORTING_IMAGE`][12]\n- **Updaters**: [`uiStateUpdaters.startExportingImage`][233]\n\n### toggleMapControl\n\nToggle active map control panel\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_MAP_CONTROL`][12]\n- **Updaters**: [`uiStateUpdaters.toggleMapControlUpdater`][234]\n\n**Parameters**\n\n- `panelId` **[string][162]** map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`][235]\n\n### toggleModal\n\nShow and hide modal dialog\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_MODAL`][12]\n- **Updaters**: [`uiStateUpdaters.toggleModalUpdater`][236]\n\n**Parameters**\n\n- `id` **([string][162] | null)** id of modal to be shown, null to hide modals. One of:- [`DATA_TABLE_ID`][237]\n  - [`DELETE_DATA_ID`][238]\n  - [`ADD_DATA_ID`][239]\n  - [`EXPORT_IMAGE_ID`][240]\n  - [`EXPORT_DATA_ID`][241]\n  - [`ADD_MAP_STYLE_ID`][242]\n\n### toggleSidePanel\n\nToggle active side panel\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_SIDE_PANEL`][12]\n- **Updaters**: [`uiStateUpdaters.toggleSidePanelUpdater`][243]\n\n**Parameters**\n\n- `id` **[string][162]** id of side panel to be shown, one of `layer`, `filter`, `interaction`, `map`\n\n## rootActions\n\nRoot actions managers adding and removing instances in root reducer.\nUnder-the-hood, when a `KeplerGl` component is mounted or unmounted,\nit will automatically calls these actions to add itself to the root reducer.\nHowever, sometimes the data is ready before the component is registered in the reducer,\nin this case, you can manually call these actions or the corresponding updater to add it to the reducer.\n\n### deleteEntry\n\nDelete an instance from `keplerGlReducer`. This action is called under-the-hood when a `KeplerGl` component is **un-mounted** to the dom.\nIf `mint` is set to be `true` in the component prop, the instance state will be deleted from the root reducer. Otherwise, the root reducer will keep\nthe instance state and later transfer it to a newly mounted component with the same `id`\n\n- **ActionTypes**: [`ActionTypes.DELETE_ENTRY`][12]\n- **Updaters**:\n\n**Parameters**\n\n- `id` **[string][162]** the id of the instance to be deleted\n\n### registerEntry\n\nAdd a new kepler.gl instance in `keplerGlReducer`. This action is called under-the-hood when a `KeplerGl` component is **mounted** to the dom.\nNote that if you dispatch actions such as adding data to a kepler.gl instance before the React component is mounted, the action will not be\nperformed. Instance reducer can only handle actions when it is instantiated.\n\n- **ActionTypes**: [`ActionTypes.REGISTER_ENTRY`][12]\n- **Updaters**:\n\n**Parameters**\n\n- `payload` **[Object][164]**\n  - `payload.id` **[string][162]** **\\*required** The id of the instance\n  - `payload.mint` **[boolean][165]** Whether to use a fresh empty state, when `mint: true` it will _always_ load a fresh state when the component is re-mounted.\n    When `mint: false` it will register with existing instance state under the same `id`, when the component is unmounted then mounted again. Default: `true`\n  - `payload.mapboxApiAccessToken` **[string][162]** mapboxApiAccessToken to be saved in `map-style` reducer.\n  - `payload.mapboxApiUrl` **[string][162]** mapboxApiUrl to be saved in `map-style` reducer.\n  - `payload.mapStylesReplaceDefault` **[Boolean][165]** mapStylesReplaceDefault to be saved in `map-style` reducer.\n\n### renameEntry\n\nRename an instance in the root reducer, keep its entire state\n\n- **ActionTypes**: [`ActionTypes.RENAME_ENTRY`][12]\n- **Updaters**:\n\n**Parameters**\n\n- `oldId` **[string][162]** **\\*required** old id\n- `newId` **[string][162]** **\\*required** new id\n\n## mapStateActions\n\nActions handled mostly by `mapState` reducer.\nThey manage map viewport update, toggle between 2d and 3d map,\ntoggle between single and split maps.\n\n### fitBounds\n\nFit map viewport to bounds\n\n- **ActionTypes**: [`ActionTypes.FIT_BOUNDS`][12]\n- **Updaters**: [`mapStateUpdaters.fitBoundsUpdater`][244]\n\n**Parameters**\n\n- `bounds` **[Array][174]&lt;[Number][188]>** as `[lngMin, latMin, lngMax, latMax]`\n\n**Examples**\n\n```javascript\nimport {fitBounds} from '@kepler.gl/actions';\nthis.props.dispatch(fitBounds([-122.23, 37.127, -122.11, 37.456]));\n```\n\n### togglePerspective\n\nToggle between 3d and 2d map.\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_PERSPECTIVE`][12]\n- **Updaters**: [`mapStateUpdaters.togglePerspectiveUpdater`][245]\n\n**Examples**\n\n```javascript\nimport {togglePerspective} from '@kepler.gl/actions';\nthis.props.dispatch(togglePerspective());\n```\n\n### toggleSplitMap\n\nToggle between single map or split maps\n\n- **ActionTypes**: [`ActionTypes.TOGGLE_SPLIT_MAP`][12]\n- **Updaters**: [`mapStateUpdaters.toggleSplitMapUpdater`][246], [`uiStateUpdaters.toggleSplitMapUpdater`][247], [`visStateUpdaters.toggleSplitMapUpdater`][248]\n\n**Parameters**\n\n- `index` **[Number][188]?** index is provided, close split map at index\n\n**Examples**\n\n```javascript\nimport {toggleSplitMap} from '@kepler.gl/actions';\nthis.props.dispatch(toggleSplitMap());\n```\n\n### updateMap\n\nUpdate map viewport\n\n- **ActionTypes**: [`ActionTypes.UPDATE_MAP`][12]\n- **Updaters**: [`mapStateUpdaters.updateMapUpdater`][249]\n\n**Parameters**\n\n- `viewport` **[Object][164]** viewport object container one or any of these properties `width`, `height`, `latitude` `longitude`, `zoom`, `pitch`, `bearing`, `dragRotate`\n  - `viewport.width` **[Number][188]?** Width of viewport\n  - `viewport.height` **[Number][188]?** Height of viewport\n  - `viewport.zoom` **[Number][188]?** Zoom of viewport\n  - `viewport.pitch` **[Number][188]?** Camera angle in degrees (0 is straight down)\n  - `viewport.bearing` **[Number][188]?** Map rotation in degrees (0 means north is up)\n  - `viewport.latitude` **[Number][188]?** Latitude center of viewport on map in mercator projection\n  - `viewport.longitude` **[Number][188]?** Longitude Center of viewport on map in mercator projection\n  - `viewport.dragRotate` **[boolean][165]?** Whether to enable drag and rotate map into perspective viewport\n\n**Examples**\n\n```javascript\nimport {updateMap} from '@kepler.gl/actions';\nthis.props.dispatch(\n  updateMap({latitude: 37.75043, longitude: -122.34679, width: 800, height: 1200})\n);\n```\n\n## layerColorUIChange\n\nSet the color palette ui for layer color\n\n- **ActionTypes**: [`ActionTypes.LAYER_COLOR_UI_CHANGE`][12]\n- **Updaters**: [`visStateUpdaters.layerColorUIChangeUpdater`][250]\n\n**Parameters**\n\n- `oldLayer` **[Object][164]** layer to be updated\n- `prop` **[String][162]** which color prop\n- `newConfig` **[object][164]** to be merged\n\n## setExportMapFormat\n\nSet the export map format (html, json)\n\n- **ActionTypes**: [`ActionTypes.SET_EXPORT_MAP_FORMAT`][12]\n- **Updaters**: [`uiStateUpdaters.setExportMapFormatUpdater`][251]\n\n**Parameters**\n\n- `payload` **[string][162]** map format\n\n[1]: #forwardactions\n[2]: #forwardto\n[3]: #parameters\n[4]: #examples\n[5]: #isforwardaction\n[6]: #parameters-1\n[7]: #unwrap\n[8]: #parameters-2\n[9]: #wrapto\n[10]: #parameters-3\n[11]: #examples-1\n[12]: #actiontypes\n[13]: #examples-2\n[14]: #mapstyleactions\n[15]: #addcustommapstyle\n[16]: #inputmapstyle\n[17]: #parameters-4\n[18]: #loadcustommapstyle\n[19]: #parameters-5\n[20]: #loadmapstyleerr\n[21]: #parameters-6\n[22]: #loadmapstyles\n[23]: #parameters-7\n[24]: #mapconfigchange\n[25]: #parameters-8\n[26]: #mapstylechange\n[27]: #parameters-9\n[28]: #requestmapstyles\n[29]: #parameters-10\n[30]: #set3dbuildingcolor\n[31]: #parameters-11\n[32]: #main\n[33]: #adddatatomap\n[34]: #parameters-12\n[35]: #examples-3\n[36]: #keplerglinit\n[37]: #parameters-13\n[38]: #receivemapconfig\n[39]: #parameters-14\n[40]: #examples-4\n[41]: #resetmapconfig\n[42]: #visstateactions\n[43]: #addfilter\n[44]: #parameters-15\n[45]: #addlayer\n[46]: #parameters-16\n[47]: #applycpufilter\n[48]: #parameters-17\n[49]: #enlargefilter\n[50]: #parameters-18\n[51]: #interactionconfigchange\n[52]: #parameters-19\n[53]: #layerconfigchange\n[54]: #parameters-20\n[55]: #layertextlabelchange\n[56]: #parameters-21\n[57]: #layertypechange\n[58]: #parameters-22\n[59]: #layervisconfigchange\n[60]: #parameters-23\n[61]: #layervisualchannelconfigchange\n[62]: #parameters-24\n[63]: #loadfiles\n[64]: #parameters-25\n[65]: #loadfileserr\n[66]: #parameters-26\n[67]: #onlayerclick\n[68]: #parameters-27\n[69]: #onlayerhover\n[70]: #parameters-28\n[71]: #onmapclick\n[72]: #onmousemove\n[73]: #parameters-29\n[74]: #removedataset\n[75]: #parameters-30\n[76]: #removefilter\n[77]: #parameters-31\n[78]: #removelayer\n[79]: #parameters-32\n[80]: #reorderlayer\n[81]: #parameters-33\n[82]: #examples-5\n[83]: #seteditormode\n[84]: #parameters-34\n[85]: #examples-6\n[86]: #setfilter\n[87]: #parameters-35\n[88]: #setfilterplot\n[89]: #parameters-36\n[90]: #setmapinfo\n[91]: #parameters-37\n[92]: #showdatasettable\n[93]: #parameters-38\n[94]: #togglefilteranimation\n[95]: #parameters-39\n[96]: #togglelayerformap\n[97]: #parameters-40\n[98]: #updateanimationtime\n[99]: #parameters-41\n[100]: #updatefilteranimationspeed\n[101]: #parameters-42\n[102]: #updatelayeranimationspeed\n[103]: #parameters-43\n[104]: #updatelayerblending\n[105]: #parameters-44\n[106]: #updatevisdata\n[107]: #parameters-45\n[108]: #uistateactions\n[109]: #addnotification\n[110]: #parameters-46\n[111]: #cleanupexportimage\n[112]: #hideexportdropdown\n[113]: #opendeletemodal\n[114]: #parameters-47\n[115]: #removenotification\n[116]: #parameters-48\n[117]: #setexportdata\n[118]: #setexportdatatype\n[119]: #parameters-49\n[120]: #setexportfiltered\n[121]: #parameters-50\n[122]: #setexportimagedatauri\n[123]: #parameters-51\n[124]: #setexportimagesetting\n[125]: #parameters-52\n[126]: #setexportselecteddataset\n[127]: #parameters-53\n[128]: #setusermapboxaccesstoken\n[129]: #parameters-54\n[130]: #showexportdropdown\n[131]: #parameters-55\n[132]: #startexportingimage\n[133]: #togglemapcontrol\n[134]: #parameters-56\n[135]: #togglemodal\n[136]: #parameters-57\n[137]: #togglesidepanel\n[138]: #parameters-58\n[139]: #rootactions\n[140]: #deleteentry\n[141]: #parameters-59\n[142]: #registerentry\n[143]: #parameters-60\n[144]: #renameentry\n[145]: #parameters-61\n[146]: #mapstateactions\n[147]: #fitbounds\n[148]: #parameters-62\n[149]: #examples-7\n[150]: #toggleperspective\n[151]: #examples-8\n[152]: #togglesplitmap\n[153]: #parameters-63\n[154]: #examples-9\n[155]: #updatemap\n[156]: #parameters-64\n[157]: #examples-10\n[158]: #layercoloruichange\n[159]: #parameters-65\n[160]: #setexportmapformat\n[161]: #parameters-66\n[162]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n[163]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function\n[164]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n[165]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n[166]: ../reducers/map-style.md#mapstyleupdatersaddcustommapstyleupdater\n[167]: ../reducers/map-style.md#mapstyleupdatersinputmapstyleupdater\n[168]: ../reducers/map-style.md#mapstyleupdatersloadcustommapstyleupdater\n[169]: ../reducers/map-style.md#mapstyleupdatersloadmapstyleerrupdater\n[170]: ../reducers/map-style.md#mapstyleupdatersloadmapstylesupdater\n[171]: ../reducers/map-style.md#mapstyleupdatersmapconfigchangeupdater\n[172]: ../reducers/map-style.md#mapstyleupdatersmapstylechangeupdater\n[173]: ../reducers/map-style.md#mapstyleupdatersrequestmapstylesupdater\n[174]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n[175]: ../reducers/map-style.md#mapstyleupdatersset3dbuildingcolorupdater\n[176]: ../reducers/composers.md#combinedupdatersadddatatomapupdater\n[177]: ../reducers/map-style.md#mapstyleupdatersinitmapstyleupdater\n[178]: ../reducers/map-state.md#mapstateupdatersreceivemapconfigupdater\n[179]: ../reducers/map-style.md#mapstyleupdatersreceivemapconfigupdater\n[180]: ../reducers/vis-state.md#visstateupdatersreceivemapconfigupdater\n[181]: ../reducers/map-state.md#mapstateupdatersresetmapconfigupdater\n[182]: ../reducers/map-style.md#mapstyleupdatersresetmapconfigmapstyleupdater\n[183]: ../reducers/vis-state.md#visstateupdatersresetmapconfigupdater\n[184]: ../reducers/vis-state.md#visstateupdatersaddfilterupdater\n[185]: ../reducers/vis-state.md#visstateupdatersaddlayerupdater\n[186]: ../reducers/vis-state.md#visstateupdatersapplycpufilterupdater\n[187]: ../reducers/vis-state.md#visstateupdatersenlargefilterupdater\n[188]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n[189]: ../reducers/vis-state.md#visstateupdatersinteractionconfigchangeupdater\n[190]: ../reducers/vis-state.md#visstateupdaterslayerconfigchangeupdater\n[191]: ../reducers/vis-state.md#visstateupdaterslayertextlabelchangeupdater\n[192]: ../reducers/vis-state.md#visstateupdaterslayertypechangeupdater\n[193]: ../reducers/vis-state.md#visstateupdaterslayervisconfigchangeupdater\n[194]: ../reducers/vis-state.md#visstateupdaterslayervisualchannelchangeupdater\n[195]: ../reducers/ui-state.md#uistateupdatersloadfilesupdater\n[196]: ../reducers/vis-state.md#visstateupdatersloadfilesupdater\n[197]: ../reducers/ui-state.md#uistateupdatersloadfileserrupdater\n[198]: ../reducers/vis-state.md#visstateupdatersloadfileserrupdater\n[199]: ../reducers/vis-state.md#visstateupdaterslayerclickupdater\n[200]: ../reducers/vis-state.md#visstateupdaterslayerhoverupdater\n[201]: ../reducers/vis-state.md#visstateupdatersmapclickupdater\n[202]: https://visgl.github.io/react-map-gl/docs/api-reference/types#maplayermouseevent\n[203]: ../reducers/vis-state.md#visstateupdatersmousemoveupdater\n[204]: ../reducers/vis-state.md#visstateupdatersremovedatasetupdater\n[205]: ../reducers/vis-state.md#visstateupdatersremovefilterupdater\n[206]: ../reducers/vis-state.md#visstateupdatersremovelayerupdater\n[207]: ../reducers/vis-state.md#visstateupdatersreorderlayerupdater\n[208]: ../reducers/vis-state.md#visstateupdatersseteditormodeupdater\n[209]: ../reducers/vis-state.md#visstateupdaterssetfilterupdater\n[210]: ../reducers/vis-state.md#visstateupdaterssetfilterplotupdater\n[211]: ../reducers/vis-state.md#visstateupdaterssetmapinfoupdater\n[212]: ../reducers/vis-state.md#visstateupdatersshowdatasettableupdater\n[213]: ../reducers/vis-state.md#visstateupdaterstogglefilteranimationupdater\n[214]: ../reducers/vis-state.md#visstateupdaterstogglelayerformapupdater\n[215]: ../reducers/vis-state.md#visstateupdatersupdateanimationtimeupdater\n[216]: ../reducers/vis-state.md#visstateupdatersupdatefilteranimationspeedupdater\n[217]: ../reducers/vis-state.md#visstateupdatersupdatelayeranimationspeedupdater\n[218]: ../reducers/vis-state.md#visstateupdatersupdatelayerblendingupdater\n[219]: ../reducers/vis-state.md#visstateupdatersupdatevisdataupdater\n[220]: ../reducers/ui-state.md#uistateupdatersaddnotificationupdater\n[221]: ../reducers/ui-state.md#uistateupdaterscleanupexportimage\n[222]: ../reducers/ui-state.md#uistateupdatershideexportdropdownupdater\n[223]: ../reducers/ui-state.md#uistateupdatersopendeletemodalupdater\n[224]: ../reducers/ui-state.md#uistateupdatersremovenotificationupdater\n[225]: ../reducers/ui-state.md#uistateupdaterssetexportdataupdater\n[226]: ../reducers/ui-state.md#uistateupdaterssetexportdatatypeupdater\n[227]: ../reducers/ui-state.md#uistateupdaterssetexportfilteredupdater\n[228]: ../reducers/ui-state.md#uistateupdaterssetexportimagedatauri\n[229]: ../reducers/ui-state.md#uistateupdaterssetexportimagesetting\n[230]: ../reducers/ui-state.md#uistateupdaterssetexportselecteddatasetupdater\n[231]: ../reducers/ui-state.md#uistateupdaterssetusermapboxaccesstokenupdater\n[232]: ../reducers/ui-state.md#uistateupdatersshowexportdropdownupdater\n[233]: ../reducers/ui-state.md#uistateupdatersstartexportingimage\n[234]: ../reducers/ui-state.md#uistateupdaterstogglemapcontrolupdater\n[235]: #default_map_controls\n[236]: ../reducers/ui-state.md#uistateupdaterstogglemodalupdater\n[237]: ../constants/default-settings.md#data_table_id\n[238]: ../constants/default-settings.md#delete_data_id\n[239]: ../constants/default-settings.md#add_data_id\n[240]: ../constants/default-settings.md#export_image_id\n[241]: ../constants/default-settings.md#export_data_id\n[242]: ../constants/default-settings.md#add_map_style_id\n[243]: ../reducers/ui-state.md#uistateupdaterstogglesidepanelupdater\n[244]: ../reducers/map-state.md#mapstateupdatersfitboundsupdater\n[245]: ../reducers/map-state.md#mapstateupdaterstoggleperspectiveupdater\n[246]: ../reducers/map-state.md#mapstateupdaterstogglesplitmapupdater\n[247]: ../reducers/ui-state.md#uistateupdaterstogglesplitmapupdater\n[248]: ../reducers/vis-state.md#visstateupdaterstogglesplitmapupdater\n[249]: ../reducers/map-state.md#mapstateupdatersupdatemapupdater\n[250]: ../reducers/vis-state.md#visstateupdaterslayercoloruichangeupdater\n[251]: ../reducers/ui-state.md#uistateupdaterssetexportmapformatupdater\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/custom-initial-state.md",
    "content": "# Custom reducer initial state\n\nFor advanced users who wish to modify the initial state of kepler.gl reducer, kepler.gl provides a reducer `initialState` function. `Reducer.initialState` will take the custom state and return a new reducer function. `initialState` is only meant to be called where the store is initialized. The custom state passed in will be shallow merged with the default `initialState`.\n\nHere is an example modify `uiState` `initialState` to hide side panel, and selectively display map control button.\n\n```js\nimport {combineReducers} from 'redux';\nimport {keplerGlReducer} from '@kepler.gl/reducers';\n\nconst customizedKeplerGlReducer = keplerGlReducer\n  .initialState({\n    uiState: {\n      // hide side panel to disallow user customize the map\n      readOnly: true,\n\n      // customize which map control button to show\n      mapControls: {\n        visibleLayers: {\n          show: false\n        },\n        mapLegend: {\n          show: true,\n          active: true\n        },\n        toggle3d: {\n          show: false\n        },\n        splitMap: {\n          show: false\n        }\n      }\n    }\n  });\n\nconst reducers = combineReducers({\n keplerGl: customizedKeplerGlReducer,\n app: appReducer\n});\n```\n\nFor full implementation, take a look at the [custom reducer example][custom-reducer-example]\n\n[custom-reducer-example]: https://github.com/keplergl/kepler.gl/tree/master/examples/custom-reducer\n\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/custom-map-styles.md",
    "content": "# Using kepler.gl with basemap services other than Mapbox\n\nBy default, kepler.gl uses mapbox-gl.js to render its base maps, displayed in [map style selection panel](https://github.com/keplergl/kepler.gl/blob/master/docs/user-guides/f-map-styles/1-base-map-styles.md).\n\n![base map panel](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-1.png \"base map panel\")\n\nYou can custom kepler.gl to use other base map services, by passing in style.json written in [Mapbox GL Style Spec](https://docs.mapbox.com/mapbox-gl-js/style-spec/). With custom style.json kepler.gl can render base map independent of mapbox vector tile service.\n\n\nFor instance. there is a example [style.json](https://raw.githubusercontent.com/heshan0131/kepler.gl-data/master/style/basic.json).  It points to the tile server described in the `sources` field.\n\n```json\n  \"sources\": {\n    \"openmaptiles\": {\n      \"url\": \"https://api.maptiler.com/tiles/v3/tiles.json?key=xxxx\",\n      \"type\": \"vector\"\n    }\n  }\n```\n\n### 1. The `mapStyle` object.\nYour custom map style should be an object as below\n```js\n// custom map style\n{\n  id: 'voyager',\n  label: 'Voyager',\n  url: 'https://api.maptiler.com/maps/voyager/style.json?key=xxxx',\n  icon: 'https://api.maptiler.com/maps/voyager/256/0/0/0.png?key=xxx',\n  layerGroups: [\n    {\n      slug: 'label',\n      filter: ({id}) => id.match(/(?=(label|place-|poi-))/),\n      defaultVisibility: true\n    },\n    {\n      slug: 'road',\n      filter: ({id}) => id.match(/(?=(road|railway|tunnel|street|bridge))(?!.*label)/),\n      defaultVisibility: true\n    }\n  ]\n}\n```\n\n__style properties__\n\n- `id` (String, required) unique string.\n- `label` (String, required) name to be displayed in map style selection panel\n- `url` (String, required) a url pointing to the map style json object written in [Mapbox GL Style Spec](https://docs.mapbox.com/mapbox-gl-js/style-spec/). \n- `icon` (String, optional) image icon of the style, it can be a url, or an [image data url](https://flaviocopes.com/data-urls/#how-does-a-data-url-look)\n- `layerGroups` (Array, optional) Supply your own `layerGroups` to override default for more accurate layer grouping. When `undefined` kepler.gl will attempt to group layers of your style based on its `id` naming convention and use it to allow toggle visibility of [base map layers](https://github.com/keplergl/kepler.gl/blob/master/docs/user-guides/f-map-styles/2-map-layers.md). \n\n### 2. Two Ways to supply kepler.gl with custom base map styles\n\n\n#### Option 1. `mapStyles` prop\nPass `mapStyles` and `mapStylesReplaceDefault` prop to `KeplerGl` component.\n\n```js\nconst mapStyles = [{\n  id: 'voyager',\n  label: 'Voyager',\n  url: 'https://api.maptiler.com/maps/voyager/style.json?key=xxxx',\n  icon: 'https://api.maptiler.com/maps/voyager/256/0/0/0.png?key=xxx'\n}];\n\nconst App = () => (\n  <KeplerGl \n    mapboxApiAccessToken=\"\" \n    mapStyles={mapStyles}\n    mapStylesReplaceDefault={true} \n    id=\"map\"\n  />\n)\n```\n\n- `mapStyles` (Array) array of custom map styles.\n- `mapStylesReplaceDefault` (Boolean) pass `true` if you want to replace all default kepler.gl base map options.\n- `mapboxApiAccessToken`. Optional if `mapStylesReplaceDefault` is `true` and your `mapStyles` does not use Mapbox services\n\n#### Option 2. Reducer `initialState`\nPass custom `mapStyles` to kepler.gl `mapStyle` reducer using the `initialState` plugin. And set default style by passing `styleType`.\n\nThis method is demoed in the example app [Custom Map Style](https://github.com/keplergl/kepler.gl/tree/master/examples/custom-map-style)\n\n```js\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  mapStyle: {\n    mapStyles: {\n      voyager: {\n        id: 'voyager',\n        label: 'Voyager',\n        url: 'https://api.maptiler.com/maps/voyager/style.json?key=xxxx',\n        icon: 'https://api.maptiler.com/maps/voyager/256/0/0/0.png?key=xxx'\n      },\n      terrain: {\n        id: 'terrain',\n        label: 'Outdoor',\n        url: 'https://api.maptiler.com/maps/outdoor/style.json?key=xxx',\n        icon: 'https://openmaptiles.org/img/styles/terrain.jpg',\n        layerGroups: [\n          {\n            slug: 'label',\n            filter: ({id}) => id.match(/(?=(label|place-|poi-))/),\n            defaultVisibility: true\n          },\n          {\n            slug: 'road',\n            filter: ({id}) => id.match(/(?=(road|railway|tunnel|street|bridge))(?!.*label)/),\n            defaultVisibility: true\n          }\n        ]\n      }\n    },\n    // Set initial map style \n    styleType: 'voyager'\n  }\n});\n\n```\n- `mapStyles` (Object) - `id` as key, and `style` as value. \n- `styleType` (string) - Initial map style. \n\n__Which option is for me?__ If you want to replace all basemap styles, we recommends __Option 2__. So you can also set the initial style with `styleType`. If you are adding more options as basemaps, __Option 1__ is ideal.\n\n### 3. `mapboxApiAccessToken`\nIf your map styles are not using Mapbox services, and you replaced all kepler.gl default map styles. `mapboxApiAccessToken` will not be required in `KeplerGl` component.\n\n\n#### 4. `DEFAULT_LAYER_GROUPS`\n\nIf `layerGroups` is not suppied, kepler.gl uses the default layer groups below. \n\n\n```js\nexport const DEFAULT_LAYER_GROUPS = [\n  {\n    slug: 'label',\n    filter: ({id}) => id.match(/(?=(label|place-|poi-))/),\n    defaultVisibility: true\n  },\n  {\n    slug: 'road',\n    filter: ({id}) => id.match(/(?=(road|railway|tunnel|street|bridge))(?!.*label)/),\n    defaultVisibility: true\n  },\n  {\n    slug: 'border',\n    filter: ({id}) => id.match(/border|boundaries/),\n    defaultVisibility: false\n  },\n  {\n    slug: 'building',\n    filter: ({id}) => id.match(/building/),\n    defaultVisibility: true\n  },\n  {\n    slug: 'water',\n    filter: ({id}) => id.match(/(?=(water|stream|ferry))/),\n    defaultVisibility: true\n  },\n  {\n    slug: 'land',\n    filter: ({id}) => id.match(/(?=(parks|landcover|industrial|sand|hillshade))/),\n    defaultVisibility: true\n  },\n  {\n    slug: '3d building',\n    filter: () => false,\n    defaultVisibility: false\n  }\n];\n```\n\n\n#### 5. Geocoder\n\n\n#### "
  },
  {
    "path": "docs/api-reference/advanced-usages/custom-mapbox-host.md",
    "content": "### 1. Configuring Mapbox API hostname\nThe KeplerGL component accepts an optional parameter `mapboxApiUrl` to override the default value of `https://api.mapbox.com`.\n\n```js\n  <KeplerGl\n      id=\"foo\"\n      mapboxApiAccessToken={token}\n      mapboxApiUrl={\"https://api.mapbox.cn\"}\n      width={width}\n      height={height}/>\n```\n\n### 2. Overriding the default MapStyles\nThe default MapStyles KeplerGL uses might not be accessible to you, in this case you will need to provide MapStyle overrides. During construction of your component:\n```js\n  this.token = '';\n  this.apiHost = \"https://api.mapbox.cn\";\n  this.mapStyles = [\n    {\n      id: 'dark',\n      label: 'Dark Streets 9',\n      url: 'mapbox://styles/mapbox/dark-v9',\n      icon: `${this.apiHost}/styles/v1/mapbox/dark-v9/static/-122.3391,37.7922,9.19,0,0/400x300?access_token=${this.token}&logo=false&attribution=false`,\n      layerGroups: [] // DEFAULT_LAYER_GROUPS\n    },\n    {\n      id: 'light',\n      label: 'Light Streets 9',\n      url: 'mapbox://styles/mapbox/light-v9',\n      icon: `${this.apiHost}/styles/v1/mapbox/light-v9/static/-122.3391,37.7922,9.19,0,0/400x300?access_token=${this.token}&logo=false&attribution=false`,\n      layerGroups: [] // DEFAULT_LAYER_GROUPS\n    }\n  ];\n```\n\nand In render:\n```js\n  <KeplerGl\n      id=\"foo\"\n      mapboxApiAccessToken={this.token}\n      mapboxApiUrl={}\n      mapStyles={this.mapStyles}\n      width={width}\n      height={height}/>\n```\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/forward-actions.md",
    "content": "# Forward Dispatch Actions\n\nOne of the biggest challenge of using local state is to dispatch actions that only modify a specific instance of the state. For instance, if we have 2 kepler.gl components in our app, one with id `foo` other with id `bar`. Our keplerGl reducer is going to be `keplerGl: {foo: …, bar …}`. When `foo` dispatches an action, it only needs to update the state of `foo`, hence we need a way to decorate the action that the root reducer only pass it down to instance reducer `foo`.  To solve this, we provide a set of forward functions called `wrapTo`, `forwardTo` and `unwrap`. `wrapTo` wraps an action payload into an forward action by adding an address `_addr_` and a `_forward_` signature to its `meta`. The root reducer will check if the given action has that address and if so, `unwrap` the action and pass it to the correct instance reducer.\n\n**Here are the different options to dispatch forwarded actions to kepler.gl reducer.**\n\n### 1. Use `forwardTo` to add a dispatch function to your component\n\nYou can add a dispatch function to your component that dispatches actions to a specific kepler.gl instance using connect.\n\n```js\n// component\nimport {KeplerGl} from '@kepler.gl/components';\nimport {connect} from 'react-redux';\n\n// import action and forward dispatcher\nimport {toggleFullScreen, forwardTo} from '@kepler.gl/actions';\n\n\nconst MapContainer = props => (\n  <div>\n    <button onClick={() => props.keplerGlDispatch(toggleFullScreen())}/>\n    <KeplerGl\n      id=\"foo\"\n    />\n  </div>\n)\n\nconst mapStateToProps = state => state\nconst mapDispatchToProps = (dispatch, props) => ({\n dispatch,\n keplerGlDispatch: forwardTo(‘foo’, dispatch)\n});\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps\n)(MapContainer);\n```\n\n- 2. Use `wrapTo`  to wrap action creator\n\nYou can also simply wrap an action into a forward action with the `wrapTo` helper\n\n```js\n// component\nimport {KeplerGl} from '@kepler.gl/components';\n\n// action and wrapper\nimport {toggleFullScreen, wrapTo} from '@kepler.gl/actions';\n\n// create a function to wrapper action payload to 'foo'\nconst wrapToMap = wrapTo('foo');\nconst MapContainer = ({dispatch}) => (\n  <div>\n    <button onClick={() => dispatch(wrapToMap(toggleFullScreen())} />\n    <KeplerGl id=\"foo\"/>\n  </div>\n);\n\n```\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/reducer-plugin.md",
    "content": "# Reducer Plugin\n\nFor advanced users, who want to add additional action handler to kepler.gl reducer, kepler.gl provides a reducer plugin function. `Reducer.plugin` will take additional action handlers and return a new reducer function. Plugin is only meant to be called where the store is initialized. The state passed into the additional action handler is the instance state.\n\n`Reducer.plugin` will allow advanced users to extend the kepler.gl reducer behavior. Here is an example of adding an additional action `HIDE_AND_SHOW_SIDE_PANEL` handler that modifies the `uiState`.\n\n```js\nimport {combineReducers} from 'redux';\nimport keplerGlReducer from '@kepler.gl/reducers';\n\nconst customizedKeplerGlReducer = keplerGlReducer\n .plugin({\n   HIDE_AND_SHOW_SIDE_PANEL: (state, action) => ({\n     ...state,\n     uiState: {\n       ...state.uiState,\n       readOnly: !state.uiState.readOnly\n     }\n   })\n });\n\nconst reducers = combineReducers({\n keplerGl: customizedKeplerGlReducer,\n app: appReducer\n});\n```\n\nNote that, reducer plugin **should not be used to override default kepler.gl actions** The following code will not change `SET_FILTER`, because plugins are handled after default actions.\n\n```js\nconst customizedKeplerGlReducer = keplerGlReducer\n .plugin({\n   [ActionTypes.SET_FILTER]: (state, action) => state\n });\n```\n\nFor full implementation, take a look at the [custom reducer example][custom-reducer-example]\n\n[custom-reducer-example]: https://github.com/keplergl/kepler.gl/tree/master/examples/custom-reducer\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/replace-ui-component.md",
    "content": "# Replace UI Component with Component Dependency Injection\n\nTo allow customize a child component, the library author usually has to pass the child component down as a prop from top of the component tree. This approach will work for component that are relatively small, but it won’t scale for kepler.gl because it has hundreds of child components. To give user the flexibility to render certain component differently. Kepler.gl has a dependency injection system that allows user to inject custom components to kepler.gl UI replacing the default ones at bootstrap time.\n\nAll you need to do is to create a component factory for the one you wish to replace, import the original component factory and call `injectComponents` at where `KeplerGl` is mounted. `injectComponents` will return a new `KeplerGl` component instance that renders the custom child component. This way we don’t have to keep track of hundreds of component as props and pass them all the way down. Dependency injection only happens once when `keplerGl` component is imported.\n\n## Factory\nFor each high level component in kepler.gl, we export a `factory`. A `factory` is a function that takes a set of `dependencies` and return a component instance. In this example below, the `MapContainerFactory` takes `MapPopover` and `MapControl` as dependencies and returns the `MapContainer` component instance. Not all components are exported as factories in kepler.gl at the moment, we are still testing this feature.\n\n```js\nimport MapPopoverFactory from 'components/map/map-popover';\nimport MapControlFactory from 'components/map/map-control';\n\nfunction MapContainerFactory(MapPopover, MapControl) {\n return class MapContainer extends Component {\n   render() {\n     return (\n       <div>\n         <MapPopover {...popoverProps} />\n         <MapControl {...controlProps} />\n       </div>\n      );\n   }\n }\n}\n\nMapContainerFactory.deps = [MapPopoverFactory, MapControlFactory];\n```\n\n## Recipes\n\nA recipe is an array of default factory, and the one to replace it. `[defaultFactory, customFactory]`. To replace default component, user can import the existing component factory, call `injectComponents` and pass in the new recipe to get a new `KeplerGl` instance.\n\n\n### Inject Components\n\nIn kepler.gl, we create the app injector by calling provide with an array of default recipes. We then export a `injectComponents` function that user can call to inject a different recipe and returns a new kepler.gl instance.\n\nHere is an example of how to use `injectComponents` to replace default `PanelHeader`.\n\n```js\nimport {injectComponents, PanelHeaderFactory} from '@kepler.gl/components';\n\n// define custom header\nconst CustomHeader = () => (<div>My kepler.gl app</div>);\n\n// create a factory\nconst myCustomHeaderFactory = () => CustomHeader;\n\n// Inject custom header into Kepler.gl,\nconst KeplerGl = injectComponents([\n  [PanelHeaderFactory, myCustomHeaderFactory]\n]);\n\n// render KeplerGl, it will render your custom header\nconst MapContainer = () => <KeplerGl id=\"foo\"/>;\n```\n\n##  Pass custom component props\n`injectComponents` allows user to render custom component, however, they usually also want to pass additional props to the customized component which current component injector doesn’t support. To enable passing additional props, we implemented a `withState`helper that passes additional props to the customized component. `withState` takes 3 arguments: `lenses`, `mapStateToProps` and `actionCreators`,  They allows user to pass in kepler.gl instance state, state from other part of the app, and custom actions.\n\n- `lense` - A getter function to get a piece of kepler.gl subreducer state. Kepler.gl exports lenses for all its sub-reducers. For instance when pass `mapStateLens` to `withState`, the component will receive `mapState` of current kepler.gl instance as a prop.\n\n- `mapStateToProps` - A wild card to play. You can pass a `mapStateToProps` function to get the state from any part of the app. If the lenses aren’t enough, use `mapStateToProps`.\n\n- `actions` - action creators that will be passed to `bindActionCreators`.\n\nHere is an example of using `withState` helper to add reducer state and actions to customized component as additional props.\n\n```js\nimport {withState, injectComponents, PanelHeaderFactory} from '@kepler.gl/components';\nimport {visStateLens} from '@kepler.gl/reducers';\n\n// custom action wrap to mounted instance\nconst addTodo = (text) => ({\n    type: 'ADD_TODO',\n    text\n});\n\n// define custom header\nconst CustomHeader = ({visState, todos, addTodo}) => (\n  <div onClick={() => addTodo('say hello')}>{`${Object.keys(visState.datasets).length} dataset loaded`}</div>\n);\n\n// now CustomHeader will receive `visState` `todos` and `addTodo` as additional props.\nconst myCustomHeaderFactory = () => withState(\n  // subreducer lenses\n  [visStateLens],\n\n  // mapStateToProps\n  state => ({\n     todos: state.todos\n  }),\n\n  // actions\n  {addTodo}\n)(CustomHeader);\n```\n\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/saving-loading-w-schema.md",
    "content": "# Saving and Loading Maps with Schema Manager\n\n![Processor and Schema][processor-schema]\n\nKelper.gl provides a schema manager to save and load maps. It converts current map data and configuration into a smaller JSON blob. You can then load that JSON blob into an empty map by passing it to `addDataToMap`.\n\nThe reason kepler.gl provides a Schema manager is to make it easy for users to connect the kepler.gl client app to any database, saving map data / config and later load it back. With the schema manager, a map saved in an older version can still be parsed and loaded with the latest kepler.gl library.\n\n### Save map\n\nPass the **instanceState** to `SchemaManager.save()`\n\n- `SchemaManager.save()` will output a JSON blob including data and config.\n\nUnder the hood, `SchemaManager.save()` calls `SchemaManager.getDatasetToSave()` and `SchemaManager.getConfigToSave()`\n- `SchemaManager.getDatasetToSave()` will output an array of dataset.\n- `SchemaManager.getConfigToSave()` will output a JSON blob of the current config.\n\nIn the example blow, `foo` is the id of the KeplerGl instance to be save.\n\n```js\nimport KeplerGlSchema from '@kepler.gl/schemas';\n\nconst mapToSave = KeplerGlSchema.save(state.keplerGl.foo);\n// mapToSave = {datasets: [], config: {}, info: {}};\n\nconst dataToSave = KeplerGlSchema.getDatasetToSave(state.keplerGl.foo);\n// dataToSave = [{version: '', data: {id, label, color, allData, fields}}]\n\nconst configToSave = KeplerGlSchema.getConfigToSave(state.keplerGl.foo);\n// configToSave = {version: '', config: {}}\n```\n\n### Load map\nPass saved data and config to `SchemaManager.load()`\n- `SchemaManager.load()` will parsed saved config and data, apply version control, the output can then be passed to `addDataToMap` directly.\n\nUnder the hood, `SchemaManager.load()` calls `SchemaManager.parseSavedData()` and `SchemaManager.parseSavedConfig()`\n\n- `SchemaManager.parseSavedData()` will output an array of parsed dataset.\n- `SchemaManager.parseSavedConfig()` will output a JSON blob of the parsed config.\n\n```js\nimport KeplerGlSchema from '@kepler.gl/schemas';\nimport {addDataToMap} from '@kepler.gl/actions';\n\nconst mapToLoad = KeplerGlSchema.load(savedDatasets, savedConfig);\n// mapToLoad = {datasets: [], config: {}};\n\nthis.props.dispatch(addDataToMap(mapToLoad));\n```\n\n### Match config with another dataset\n\nOften times, people want to keep a map config as template, then load it with different datasets. To match a config with a different dataset, you need to make sure `data.id` in the new dataset matches the old one.\n\n```js\nimport KeplerGlSchema from '@kepler.gl/schemas';\nimport {addDataToMap} from '@kepler.gl/actions';\n\n// save current map data and config\nconst {datasets, config} = KeplerGlSchema.save(state.keplerGl.foo);\n// mapToLoad = {datasets: [], config: {}};\n\n// receive some new data\nconst newData = someNewData;\n// newData = [{rows, fields}]\n\n// match id with old datasets\nconst newDatasets = newData.map((d, i) => ({\n  version: datasets[i].version,\n  data: {\n    ...datasets[i].data,\n    allData: d.rows,\n    fields: d.fields\n  }\n}));\n\n// load config with new datasets\nconst mapToLoad = KeplerGlSchema.load(newDatasets, config);\n\nthis.props.dispatch(addDataToMap(mapToLoad));\n```\n\n[processor-schema]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_load-save.png\n"
  },
  {
    "path": "docs/api-reference/advanced-usages/using-updaters.md",
    "content": "# Using Updaters\nUpdaters are state transition functions that mapped to actions. One action can map to multiple state updaters, each belongs to a subreducer.\n\n\nThis action-updater pattern allows a user to import a specific action updater in the app's root reducer and use it to directly modify kepler.gl’s state without dispatching the action. This will give user a lot of freedom to control over kepler.gl's state transition.\n\nTo achieve the same result with `togglePerspective` updating kepler.gl's map perspective mode. You can import and dispatch kepler.gl action `togglePerspective`:\n\n```js\n// action and forward dispatcher\nimport {togglePerspective} from '@kepler.gl/actions';\n\nconst MapContainer = ({dispatch}) => (\n  <div>\n    <button onClick={() => dispatch(togglePerspective())} />\n    <KeplerGl id=\"foo\"/>\n  </div>\n);\n```\n\nor import the corresponding updater `mapStateUpdaters.togglePerspectiveUpdater` and call it inside the root reducer. The example below demos how to add a button outside kepler.gl component, and update the map perspective when click it.\n\n```js\nimport keplerGlReducer, {mapStateUpdaters} from '@kepler.gl/reducers';\n\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   case 'CLICK_BUTTON':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         foo: {\n            ...state.keplerGl.foo,\n            mapState: mapStateUpdaters.togglePerspectiveUpdater(\n            t  state.keplerGl.foo.mapState\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n"
  },
  {
    "path": "docs/api-reference/cloud-providers/README.md",
    "content": "# Cloud Providers\n\nThe kepler.gl application does not have a backend, however it offers integration point for optional commercial backends. Each backend can integrate with kepler by adding a \"cloud provider\" object to kepler's global list of cloud providers.\n\nThese objects must implement certain minimal set of methods, and can optionally immplement others, depending on the capability of the backend.\n\nThe set of methods available for cloud providers to implement is subject to change as new features are added to the front-end.\n\n\n## Cloud Provider Object\n\nA \"cloud provider\" object provides:\n- a name and an icon\n- any service specific methods (such as `uploadFile`)\n- a set of oauth2 methods to plug into the authentication flow and get access tokens\n\nCloud-providers providers can implement the following properties\n\n| Field/method | Description | Required? |\n| --- | --- | --- |\n| `name` | Name of the provider | required |\n| `displayName` | Display name |\n| `icon` | React Element to render as Icon |\n| `thumbnail` | Size of the thumbnail image of the map that required by the provider |\n| `hasPrivateStorage` | To participate in kepler's build-in private map saving function | required |\n| `hasSharingUrl` | To participate in kepler's build-in share map via URL function | required |\n| `getShareUrl` | To show user the shared Url of the map |\n| `getMapUrl` | To update browser location once a map has been saved / loaded |\n| `getAccessToken` | To participate in kepler's built-in oauth login routes |\n| `getUserName` | To display user name of the logged in user |\n| `login` | Method called to perform user login | required |\n| `logout` | Method called to logout an user | required |\n| `uploadMap` | Method called to upload map to storage | required |\n| `listMaps` | Method called to load a catalog of maps saved by the current user | required |\n| `downloadMap` | MEthod called to download a specific map | required |\n\n\n## Adding a new Cloud Provider\n\nAn instance of the provider is added to array of cloud providers in the file `src/cloud-providers/providers.js` then passed to kepler.gl demo app. An example provider: [Dropbox Provider](https://github.com/keplergl/kepler.gl/blob/master/examples/demo-app/src/cloud-providers/dropbox-provider.js)\n\n```js\nimport {Provider} from '@kepler.gl/cloud-providers';\n\nclass MyProvider extends Provider {\n    constructor() {\n        this.name = 'foo';\n        this.displayName = 'My Provider';\n    }\n\n    // ... other required methods below\n}\n\nconst myProvider = new MyProvider();\nconst App = () =>\n    <KeplerGl\n        mapboxApiAccessToken={CLOUD_PROVIDERS_CONFIGURATION.MAPBOX_TOKEN}\n        id=\"map\"\n        cloudProviders={[myProvider]}\n    />\n```\n\n\n## Cloud Provider Instance Fields and Methods\n\nSee [Cloud Provider API](./cloud-provider.md)\n"
  },
  {
    "path": "docs/api-reference/cloud-providers/cloud-provider.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n-   [Provider][1]\n    -   [downloadMap][4]\n    -   [getAccessToken][7]\n    -   [getMapUrl][8]\n    -   [getShareUrl][10]\n    -   [getUserName][12]\n    -   [hasPrivateStorage][13]\n    -   [hasSharingUrl][14]\n    -   [listMaps][15]\n    -   [login][17]\n    -   [logout][19]\n    -   [uploadMap][21]\n-   [MapResponse][23]\n-   [Viz][25]\n\n## Provider\n\nThe default provider class\n\n**Parameters**\n\n-   `props` **[object][27]**\n    -   `props.name` **[string][28]**\n    -   `props.displayName` **[string][28]**\n    -   `props.icon` **ReactElement** React element\n    -   `props.thumbnail` **[object][27]** thumbnail size object\n        -   `props.thumbnail.width` **[number][29]** thumbnail width in pixels\n        -   `props.thumbnail.height` **[number][29]** thumbnail height in pixels\n\n**Examples**\n\n```javascript\nconst myProvider = new Provider({\n name: 'foo',\n displayName: 'Foo Storage'\n icon: Icon,\n thumbnail: {width: 300, height: 200}\n})\n```\n\n### downloadMap\n\nThis method will be called when user select a map to load from the storage map viewer\n\n**Parameters**\n\n-   `loadParams` **any** the loadParams property of each visualization object\n\n**Examples**\n\n```javascript\nasync downloadMap(loadParams) {\n const mockResponse = {\n   map: {\n     datasets: [],\n     config: {},\n     info: {\n       app: 'kepler.gl',\n       created_at: ''\n       title: 'test map',\n       description: 'Hello this is my test dropbox map'\n     }\n   },\n   // pass csv here if your provider currently only support save / load file as csv\n   format: 'keplergl'\n };\n\n return downloadMap;\n}\n```\n\nReturns **[MapResponse][30]** the map object containing dataset config info and format option\n\n### getAccessToken\n\nThis method is called to determine whether user already logged in to this provider\n\nReturns **[boolean][31]** true if a user already logged in\n\n### getMapUrl\n\nThis method is called by kepler.gl demo app to pushes a new location to history, becoming the current location.\n\n**Parameters**\n\n-   `fullURL` **[boolean][31]** Whether to return the full url with domain, or just the location (optional, default `true`)\n\nReturns **[string][28]** mapUrl\n\n### getShareUrl\n\nThis method is called after user share a map, to display the share url.\n\n**Parameters**\n\n-   `fullUrl` **[boolean][31]** Whether to return the full url with domain, or just the location (optional, default `false`)\n\nReturns **[string][28]** shareUrl\n\n### getUserName\n\nThis method is called to get the user name of the current user. It will be displayed in the cloud provider tile.\n\nReturns **[string][28]** true if a user already logged in\n\n### hasPrivateStorage\n\nWhether this provider support upload map to a private storage. If truthy, user will be displayed with the storage save icon on the top right of the side bar.\n\nReturns **[boolean][31]**\n\n### hasSharingUrl\n\nWhether this provider support share map via a public url, if truthy, user will be displayed with a share map via url under the export map option on the top right of the side bar\n\nReturns **[boolean][31]**\n\n### listMaps\n\nThis method is called to get a list of maps saved by the current logged in user.\n\n**Examples**\n\n```javascript\nasync listMaps() {\n   return [\n     {\n       id: 'a',\n       title: 'My map',\n       description: 'My first kepler map',\n       imageUrl: 'http://',\n       udpatedAt: 1582677787000,\n       privateMap: false,\n       loadParams: {}\n     }\n   ];\n }\n```\n\nReturns **[Array][32]&lt;[Viz][33]>** an array of Viz objects\n\n### login\n\nThis method will be called when user click the login button in the cloud provider tile.\nUpon login success, `onCloudLoginSuccess` has to be called to notify kepler.gl UI\n\n**Parameters**\n\n-   `onCloudLoginSuccess` **[function][34]** callbacks to be called after login success\n\n### logout\n\nThis method will be called when user click the logout button under the cloud provider tile.\nUpon login success, `onCloudLoginSuccess` has to be called to notify kepler.gl UI\n\n**Parameters**\n\n-   `onCloudLogoutSuccess` **[function][34]** callbacks to be called after logout success\n\n### uploadMap\n\nThis method will be called to upload map for saving and sharing. Kepler.gl will package map data, config, title, description and thumbnail for upload to storage.\nWith the option to overwrite already saved map, and upload as private or public map.\n\n**Parameters**\n\n-   `param` **[Object][27]**\n    -   `param.mapData` **[Object][27]** the map object\n        -   `param.mapData.map` **[Object][27]** {datasets. config, info: {title, description}}\n        -   `param.mapData.thumbnail` **[Blob][35]** A thumbnail of current map. thumbnail size can be defined by provider by this.thumbnail\n    -   `param.options` **[Object][27]**  (optional, default `{}`)\n        -   `param.options.overwrite` **[boolean][31]** whether user choose to overwrite already saved map under the same name\n        -   `param.options.isPublic` **[boolean][31]** whether user wish to share the map with others. if isPublic is truthy, kepler will call this.getShareUrl() to display an URL they can share with others\n\n## MapResponse\n\nThe returned object of `downloadMap`. The response object should contain: datasets: \\[], config: {}, and info: {}\neach dataset object should be {info: {id, label}, data: {...}}\nto inform how kepler should process your data object, pass in `format`\n\nType: [Object][27]\n\n### Properties\n\n-   `map` **[Object][27]**\n    -   `map.datasets` **[Array][32]&lt;[Object][27]>**\n    -   `map.config` **[Object][27]**\n    -   `map.info` **[Object][27]**\n-   `format` **[string][28]** one of 'csv': csv file string, 'geojson': geojson object, 'row': row object, 'keplergl': datasets array saved using KeplerGlSchema.save\n\n## Viz\n\nType: [Object][27]\n\n### Properties\n\n-   `id` **[string][28]** An unique id\n-   `title` **[string][28]** The title of the map\n-   `description` **[string][28]** The description of the map\n-   `imageUrl` **[string][28]** The imageUrl of the map\n-   `lastModification` **[number][29]** An epoch timestamp in milliseconds\n-   `privateMap` **[boolean][31]** Optional, whether if this map is private to the user, or can be accessed by others via URL\n-   `loadParams` **any** A property to be passed to `downloadMap`\n\n[1]: #provider\n\n[2]: #parameters\n\n[3]: #examples\n\n[4]: #downloadmap\n\n[5]: #parameters-1\n\n[6]: #examples-1\n\n[7]: #getaccesstoken\n\n[8]: #getmapurl\n\n[9]: #parameters-2\n\n[10]: #getshareurl\n\n[11]: #parameters-3\n\n[12]: #getusername\n\n[13]: #hasprivatestorage\n\n[14]: #hassharingurl\n\n[15]: #listmaps\n\n[16]: #examples-2\n\n[17]: #login\n\n[18]: #parameters-4\n\n[19]: #logout\n\n[20]: #parameters-5\n\n[21]: #uploadmap\n\n[22]: #parameters-6\n\n[23]: #mapresponse\n\n[24]: #properties\n\n[25]: #viz\n\n[26]: #properties-1\n\n[27]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[28]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n\n[29]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n\n[30]: #mapresponse\n\n[31]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n\n[32]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[33]: #viz\n\n[34]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function\n\n[35]: https://developer.mozilla.org/docs/Web/API/Blob\n"
  },
  {
    "path": "docs/api-reference/components/README.md",
    "content": "# Components\n\n...Coming soon\n"
  },
  {
    "path": "docs/api-reference/custom-theme/README.md",
    "content": "# Custom Theme\n\nYou can pass theme name or object used to customize Kepler.gl style. Kepler.gl provide an `'light'` theme besides the default 'dark' theme. When pass in a theme object Kepler.gl will use the value passed as input to overwrite values from [theme](https://github.com/keplergl/kepler.gl/blob/master/src/styles/src/base.ts).\n\n\n```js\nimport KeplerGl from '@kepler.gl/components';\n\nconst Map = props => (\n  <KeplerGl\n    id=\"foo\"\n    width={width}\n    mapboxApiAccessToken={token}\n    height={height}\n    theme=\"light\"\n  />\n);\n```\n\n### Available Themes\n| theme | |\n| ------- | ------- |\n| `dark` (default) | ![Screen Shot 2020-03-11 at 2 11 45 PM](https://user-images.githubusercontent.com/3605556/76464370-78c13080-63a2-11ea-977e-9678a25580f9.png) |\n| `light`  | ![Screen Shot 2020-03-11 at 2 10 15 PM](https://user-images.githubusercontent.com/3605556/76464360-74951300-63a2-11ea-82fe-3d055dc0b8dd.png)  |\n| `base`  | ![Screen Shot 2020-03-11 at 2 10 49 PM](https://user-images.githubusercontent.com/3605556/76464366-78289a00-63a2-11ea-944b-e5a9208bacde.png) |\n"
  },
  {
    "path": "docs/api-reference/ecosystem.md",
    "content": "## Ecosystem\n\nThe diagram below represents the data flow. Note that in most cases, you don't have to worry about action creators, forward dispatcher and state updaters, as they are handled under-the-hood by kepler.gl\n\n![Data flow][data-flow]\n\n## Component\nThe Kepler.gl component will call redux connect under the hood, and dispatch to the corresponding reducer instance.\n\nTo allow mounting of multiple instance of kepler.gl components in the same app, we implemented a local selector with forward dispatch system. The local selector will pass down the knowledge where the state of this instance lives, and the forward dispatch system will pass down a dispatch function that knows to dispatch action to the correct reducer instance. __Each kepler.gl component instance needs to have an unique id__.\n\nRead more about [Component][components].\n\n\n## Reducer and Forward Dispatcher\n\n![Forward Dispatcher][forward-dispatcher]\n\nThe kepler.gl root reducer that user mounted in their app is in fact a wrapper reducer that stored the child state and update them based on forwarded actions. If An action is not a forwarded action, it pass down to all child reducers.\n\nWhen a KeplerGl component instance is mounted with the id `foo`, the wrapper reducer will  add a kepler.gl local state in the root state at key `foo`.\n\nOne of the biggest challenge of using local state is to dispatch actions that only modify a specific local state. For instance, if we have 2 kepler.gl components in our app, one with id `foo` other with id `bar`. Our keplerGl reducer is going to be `keplerGl: {foo: …, bar …}`. When foo dispatches an action, it only needs to update the state of foo, we need a way to decorate the action that the root reducer only pass it down to subreducer `foo`.\n\nTo solve this, kepler.gl has a forward dispatching system. It consists of a set of forward functions including `wrapTo`, `forwardTo` and `unwrap`.   `wrapTo` wraps an action payload into an forward action by adding an address `_addr_` and a `_forward_` signature to its meta.\n\nEach kepler.gl component receives a forward dispatcher as a prop, which dispatches a forwarded action to the root reducer. The root reducer will check if the given action has that address and if so, unwrap the action and pass it to the child reducer.\n\nRead more about [Reducers][reducers].\n\n\n## Actions and Updaters\nActions in reducers are mapped to state transition functions. `UPDATE_MAP` is mapped to `updateMapUpdater`. An updater is the backbone of the redux reducer. It is a pure function that takes the previous state and an action, and returns the next state. `(oldState, action) => newState`. It describes how the state should transition upon receiving that action.\n\nhere is a snippet of the map state reducer in kepler.gl.\n\n```js\n/* Action Handlers */\nconst actionHandler = {\n [ActionTypes.FIT_BOUNDS]: fitBoundsUpdater,\n [ActionTypes.TOGGLE_PERSPECTIVE]: togglePerspectiveUpdater\n};\n\n/* Reducer */\nexport default handleActions(actionHandler, INITIAL_MAP_STATE);\n```\n\nThis pattern allows a user to import a specific action updater in the app's root reducer and use it to directly modify kepler.gl’s state without dispatching the action. This will give user a lot of freedom to control over kepler.gl's state transition.\n\nRead more about [Actions and Updaters][actions-updaters].\n\n\n## Processors and Schema Manager\n\n![Processor and Schema][processor-schema]\n\nProcessors and schema manager are useful helpers to get data in and out of kepler.gl. You can use `processCsvData(csv)` and `processGeojson(geojson)` to parse csv or geoJson file and pass it to `addDataToMap()` action.\n\nTo save and reload the current map, you can call `KeplerGlSchema.save()` and pass it the instant state. It will return a json output containing map data and config. Pass this json file to `processKeplerglJSON()` and then `addDataToMap()` will reproduce the same map.\n\nRead more about [Processors][processors] and [Schema Manager][schemas].\n\n\n<!--  -->\n\n[basic-usage]: ./basic-usage.md\n[advanced-usage]: ./advanced-usage.md\n[components]: components/README.md\n[reducers]: reducers/README.md\n[actions-updaters]: actions/README.md\n[processors]: processors/README.md\n[schemas]: schemas/README.md\n[data-flow]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_data-flow.png\n[forward-dispatcher]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_forward-dispatch.png\n[processor-schema]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_load-save.png\n"
  },
  {
    "path": "docs/api-reference/get-started.md",
    "content": "## Get Started\n\n### Installation\n\nUse <b>Node v18</b> and above, older node versions have not been tested\n\n```sh\nnpm install --save kepler.gl @kepler.gl/components @kepler.gl/reducers\n```\n\n### Get Mapbox Token\n\nKepler.gl is built on top of [Mapbox GL](https://www.mapbox.com). A mapbox account and an access token are needed to use kepler.gl in your app. Get a [Mapbox Access Token](https://www.mapbox.com/help/define-access-token/) at mapbox.com.\n\n### Basic Usage\n\n![Basic Usage][basic-usage]\n\n#### 0. Working Template\n\nCheck full example on [Github](https://github.com/keplergl/kepler.gl/tree/master/examples/get-started).\n\n```js\nimport * as React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport document from \"global/document\";\n\nimport { applyMiddleware, combineReducers, compose, createStore } from \"redux\";\nimport { connect, Provider } from \"react-redux\";\n\nimport keplerGlReducer, { enhanceReduxMiddleware } from \"@kepler.gl/reducers\";\nimport KeplerGl from \"@kepler.gl/components\";\n\nimport AutoSizer from \"react-virtualized/dist/commonjs/AutoSizer\";\n\nconst reducers = combineReducers({\n  keplerGl: keplerGlReducer.initialState({\n    uiState: {\n      readOnly: false,\n      currentModal: null,\n    },\n  }),\n});\n\nconst middleWares = enhanceReduxMiddleware([\n  // Add other middlewares here\n]);\n\nconst enhancers = applyMiddleware(...middleWares);\n\nconst initialState = {};\nconst store = createStore(reducers, initialState, compose(enhancers));\n\nconst App = () => (\n  <div\n    style={{\n      position: \"absolute\",\n      top: \"0px\",\n      left: \"0px\",\n      width: \"100%\",\n      height: \"100%\",\n    }}\n  >\n    <AutoSizer>\n      {({ height, width }) => (\n        <KeplerGl\n          mapboxApiAccessToken=\"xxx\" // Replace with your mapbox token\n          id=\"map\"\n          width={width}\n          height={height}\n        />\n      )}\n    </AutoSizer>\n  </div>\n);\n\nconst mapStateToProps = (state) => state;\nconst dispatchToProps = (dispatch) => ({ dispatch });\nconst ConnectedApp = connect(mapStateToProps, dispatchToProps)(App);\nconst Root = () => (\n  <Provider store={store}>\n    <ConnectedApp />\n  </Provider>\n);\n\nexport default Root;\n```\n\n\n\n\n#### 1. Mount reducer\n\nKepler.gl uses [Redux](https://redux.js.org/) to manage its internal state, along with [react-palm](https://github.com/btford/react-palm) middleware to handle side effects. Mount kepler.gl reducer in your store, apply  `taskMiddleware`.\n\n```js\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {createStore, combineReducers, applyMiddleware} from 'redux';\nimport {taskMiddleware} from 'react-palm/tasks';\n\nconst reducer = combineReducers({\n  // <-- mount kepler.gl reducer in your app\n  keplerGl: keplerGlReducer,\n\n  // Your other reducers here\n  app: appReducer\n});\n\n// create store\nconst store = createStore(reducer, {}, applyMiddleware(taskMiddleware));\n```\nIf you mount `keplerGlReducer` in another address instead of `keplerGl`, or it is not\nmounted at root of your reducer, you will need to specify the path to it when you mount the component with the `getState` prop.\n\n#### 2. Mount component\n\n```js\nimport KeplerGl from '@kepler.gl/components';\n\nconst Map = props => (\n  <KeplerGl\n      id=\"foo\"\n      mapboxApiAccessToken={token}\n      width={width}\n      height={height}/>\n);\n```\n\n#### 3. Add data to map\n\nIn order to interact with a kepler.gl instance and add new data to it, you can dispatch the __`addDataToMap`__ action from anywhere inside your app. It adds dataset(s) to a kepler.gl instance and updates the full configuration (mapState, mapStyle, visState).\n\nRead more about [addDataToMap](./actions/actions.md#adddatatomap)\n\n\n```js\nimport {addDataToMap} from '@kepler.gl/actions';\n\nthis.props.dispatch(\n  addDataToMap({\n    // datasets\n    datasets: {\n      info: {\n        label: 'Sample Taxi Trips in New York City',\n        id: 'test_trip_data'\n      },\n      data: sampleTripData\n    },\n    // option\n    option: {\n      centerMap: true,\n      readOnly: false\n    },\n    // config\n    config: {\n      mapStyle: {styleType: 'light'}\n    }\n  })\n);\n```\n\n[basic-usage]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_basic-usage.png\n"
  },
  {
    "path": "docs/api-reference/localization/README.md",
    "content": "# Localization\n\nKepler.gl supports localization through [react-intl]. Locale is determined by `uiState.locale` value.\nCurrent supported languages are:\n\n| locale code | Language   | Default? |\n|-------------|------------|----------|\n| en          | English    | default  |\n| fi          | Finnish    |          |\n| pt          | Portuguese |          |\n| ca          | Catalan    |          |\n| es          | Spanish    |          |\n| ja          | Japanese   |          |\n| cn          | Chinese    |          |\n| ru          | Русский    |          |\n\n## Changing default language\n\nBy default the first language is English `en`. The default language can be changed by giving locale value to uiState:\n\n```js\nimport {combineReducers} from 'redux';\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {LOCALE_CODES} from '@kepler.gl/localization';\n\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  uiState: {\n    // use Finnish locale\n    locale: LOCALE_CODES.fi\n  }\n});\n\nconst reducers = combineReducers({\n  keplerGl: customizedKeplerGlReducer,\n  app: appReducer\n});\n```\n\n## Adding new language\n\nLet's say we want to add the Swedish language to kepler.gl. Easiest way to add translation of new language is to follow these 3 steps:\n\n- Find out the [language code][language-codes] for Swedish: `sv`\n- Add new translation file `src/localization/translations/sv.js` by copying `src/localization/translations/en.js` and translating the strings\n\n- Update _LOCALES_ in `src/localization/locales.js` to include new language translation:\n  ```javascript\n  export const LOCALES = {\n    en : 'English',\n    fi : 'Suomi',\n    pt: 'Português',\n    // add Swedish language\n    sv: 'Svenska'\n  }\n  ```\n\n## Modify default translation or add new translation\nthe `localeMessages` prop of `KeplerGl` takes additional translations and merge with default translation.\n\n#### Example 1. Update default translation\nTo update the english translation of `layerManager.addData`, pass `localeMessages` like this.\n\n```javascript\nconst localeMessages = {\n  en: {\n    ['layerManager.addData']: 'Add Data to Layer'\n  }\n};\n\nconst App = () => (\n    <KeplerGl\n      id=\"map\"\n      localeMessages={messages}\n      mapboxApiAccessToken={Token}\n    />\n);\n```\n#### Example 2. Pass additional translation\nSometimes together with dependency injection, you might need to add additional translations to the customized component. For example, adding an additional `settings` panel in the side panel, you will need to provide a translation for the panel name assigned to `sidebar.panels.settings`\n\n```javascript\nconst localeMessages = {\n  en: {\n    ['sidebar.panels.settings']: 'Settings'\n  }\n};\n\nconst App = () => (\n    <KeplerGl\n      id=\"map\"\n      localeMessages={messages}\n      mapboxApiAccessToken={Token}\n    />\n);\n```\n\n[react-intl]: https://github.com/formatjs/react-intl\n[language-codes]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes\n"
  },
  {
    "path": "docs/api-reference/processors/README.md",
    "content": "# Processors\n\n...Coming soon\n"
  },
  {
    "path": "docs/api-reference/processors/processors.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [getFieldsFromData](#getfieldsfromdata)\n- [processCsvData](#processcsvdata)\n- [processGeojson](#processgeojson)\n- [processKeplerglJSON](#processkeplergljson)\n- [processRowObject](#processrowobject)\n\n## getFieldsFromData\n\nAnalyze field types from data in `string` format, e.g. uploaded csv.\nAssign `type`, `tableFieldIndex` and `format` (timestamp only) to each field\n\n**Parameters**\n\n-   `data` **[Array][16]&lt;[Object][17]>** array of row object\n-   `fieldOrder` **[Array][16]** array of field names as string\n\n**Examples**\n\n```javascript\nimport {getFieldsFromData} from '@kepler.gl/processors';\nconst data = [{\n  time: '2016-09-17 00:09:55',\n  value: '4',\n  surge: '1.2',\n  isTrip: 'true',\n  zeroOnes: '0'\n}, {\n  time: '2016-09-17 00:30:08',\n  value: '3',\n  surge: null,\n  isTrip: 'false',\n  zeroOnes: '1'\n}, {\n  time: null,\n  value: '2',\n  surge: '1.3',\n  isTrip: null,\n  zeroOnes: '1'\n}];\n\nconst fieldOrder = ['time', 'value', 'surge', 'isTrip', 'zeroOnes'];\nconst fields = getFieldsFromData(data, fieldOrder);\n// fields = [\n// {name: 'time', format: 'YYYY-M-D H:m:s', tableFieldIndex: 1, type: 'timestamp'},\n// {name: 'value', format: '', tableFieldIndex: 4, type: 'integer'},\n// {name: 'surge', format: '', tableFieldIndex: 5, type: 'real'},\n// {name: 'isTrip', format: '', tableFieldIndex: 6, type: 'boolean'},\n// {name: 'zeroOnes', format: '', tableFieldIndex: 7, type: 'integer'}];\n```\n\nReturns **[Array][16]&lt;[Object][17]>** formatted fields\n\n## processCsvData\n\nProcess csv data, output a data object with `{fields: [], rows: []}`.\nThe data object can be wrapped in a `dataset` and pass to [`addDataToMap`][18]\n\n**Parameters**\n\n-   `rawData` **[string][19]** raw csv string\n\n**Examples**\n\n```javascript\nimport {processCsvData} from '@kepler.gl/processors';\n\nconst testData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date\n2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23\n2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23\n2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23\n2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23`\n\nconst dataset = {\n info: {id: 'test_data', label: 'My Csv'},\n data: processCsvData(testData)\n};\n\ndispatch(addDataToMap({\n datasets: [dataset],\n options: {centerMap: true, readOnly: true}\n}));\n```\n\nReturns **[Object][17]** data object `{fields: [], rows: []}`\n\n## processGeojson\n\nProcess GeoJSON [`FeatureCollection`][20],\noutput a data object with `{fields: [], rows: []}`.\nThe data object can be wrapped in a `dataset` and pass to [`addDataToMap`][18]\n\n**Parameters**\n\n-   `rawData` **[Object][17]** raw geojson feature collection\n\n**Examples**\n\n```javascript\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {processGeojson} from '@kepler.gl/processors';\n\nconst geojson = {\n\t\"type\" : \"FeatureCollection\",\n\t\"features\" : [{\n\t\t\"type\" : \"Feature\",\n\t\t\"properties\" : {\n\t\t\t\"capacity\" : \"10\",\n\t\t\t\"type\" : \"U-Rack\"\n\t\t},\n\t\t\"geometry\" : {\n\t\t\t\"type\" : \"Point\",\n\t\t\t\"coordinates\" : [ -71.073283, 42.417500 ]\n\t\t}\n\t}]\n};\n\ndispatch(addDataToMap({\n datasets: {\n   info: {\n     label: 'Sample Taxi Trips in New York City',\n     id: 'test_trip_data'\n   },\n   data: processGeojson(geojson)\n }\n}));\n```\n\nReturns **[Object][17]** dataset containing `fields` and `rows`\n\n## processKeplerglJSON\n\nProcess saved kepler.gl json to be pass to [`addDataToMap`][18].\nThe json object should contain `datasets` and `config`.\n\n**Parameters**\n\n-   `rawData` **[Object][17]**\n    -   `rawData.datasets` **[Array][16]**\n    -   `rawData.config` **[Object][17]**\n\n**Examples**\n\n```javascript\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {processKeplerglJSON} from '@kepler.gl/processors';\n\ndispatch(addDataToMap(processKeplerglJSON(keplerGlJson)));\n```\n\nReturns **[Object][17]** datasets and config `{datasets: {}, config: {}}`\n\n## processRowObject\n\nProcess data where each row is an object, output can be passed to [`addDataToMap`][18]\n\n**Parameters**\n\n-   `rawData` **[Array][16]&lt;[Object][17]>** an array of row object, each object should have the same number of keys\n\n**Examples**\n\n```javascript\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {processRowObject} from '@kepler.gl/processors';\n\nconst data = [\n {lat: 31.27, lng: 127.56, value: 3},\n {lat: 31.22, lng: 126.26, value: 1}\n];\n\ndispatch(addDataToMap({\n datasets: {\n   info: {label: 'My Data', id: 'my_data'},\n   data: processRowObject(data)\n }\n}));\n```\n\nReturns **[Object][17]** dataset containing `fields` and `rows`\n\n[1]: #getfieldsfromdata\n\n[2]: #parameters\n\n[3]: #examples\n\n[4]: #processcsvdata\n\n[5]: #parameters-1\n\n[6]: #examples-1\n\n[7]: #processgeojson\n\n[8]: #parameters-2\n\n[9]: #examples-2\n\n[10]: #processkeplergljson\n\n[11]: #parameters-3\n\n[12]: #examples-3\n\n[13]: #processrowobject\n\n[14]: #parameters-4\n\n[15]: #examples-4\n\n[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[18]: ../actions/actions.md#adddatatomap\n\n[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n\n[20]: http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection\n"
  },
  {
    "path": "docs/api-reference/reducers/README.md",
    "content": "## Reducers\n\nKepler.gl is a redux-connected component that utilizes redux to manage its state. The basic implementation of kepler.gl reducer is simple. However, to make the most of it, it's recommended to have basic knowledge on:\n\n- [Redux][redux] state container\n- [React][react]\n- [React Redux connect][react-redux]\n\n\n![Compose-reducer][diagram-1]\n\nIt is immportant to understand the relationship between __kepler.gl reducer__, __instance reducer__ and __subreducer__. Kepler.gl reducer is the root reducer that combines multiple instance reducer, which manages the state of each individual kepler.gl component. The instance reducer is consists of 4 subreducers, each manages an independent part of the state.\n\n## KeplerGl Reducer\n\nTo connect kepler.gl components to your Redux app you'll need the following pieces from the kepler.gl package:\n- Redux Reducer: `keplerGlReducer` imported from `@kepler.gl/reducers`\n- React Component: `KeplerGl` imported from `@kepler.gl/components`\n\nThese are the only 2 pieces you need to get kepler.gl up and running in your app. When you mount kepler.gl reducer in your app reducer (with `combineReducers`), it will then managers __ALL__ KeplerGl component instances that you add to your app. Each kepler.gl instance state is stored in a instance reduccer.\n\nFor instance, if you have 2 kepler.gl components in your App:\n```js\nimport KeplerGl from '@kepler.gl/components';\n\nconst MapApp = () => (\n  <div>\n    <KeplerGl id=\"foo\"/>\n    <KeplerGl id=\"bar\"/>\n  </div>\n);\n```\n\nYour redux state will be:\n```js\nstate = {\n  keplerGl: {\n    foo: {},\n    bar: {}\n  },\n  // ... other app state\n  app: {}\n}\n```\n\n## Instance Reducer\n\nEach kepler.gl component state is stored in a instance reduccer. A instance reducer has 4 subreducers. __`visState`__, __`mapState`__, __`mapStyle`__ and __`uiState`__. Each of them managers a piece of state that is mostly self contained.\n- __visState__ - Manages all data and visualization related state, including datasets, layers, filters and interaction configs. Some of the key updaters are `updateVisDataUpdater`,  `layerConfigChangeUpdater`, `setFilterUpdater`, `interactionConfigChangeUpdater`.\n\n- __mapState__ - Manages base map behavior including the viewport, drag rotate and toggle split maps. Key updates are `updateMapUpdater`, `toggleSplitMapUpdater` and `togglePerspectiveUpdater`.\n\n- __mapStyle__ - Managers base map style, including setting base map style, toggling base map layers and adding custom base map style.\n\n- __uiState__ - Managers all UI component transition state, including open / close side panel, current displayed panel etc. Note, ui state reducer is the only reducer that’s not saved in kepler.gl schema.\n\n\n## Subreducer\n\nThe subreducers - __`visState`__, __`mapState`__, __`mapStyle`__ and __`uiState`__ - are assembled by a list of action handlers, each handler mapped to a state transition function named xxUpdater. For instance, here is a snippet of the map state reducer in kepler.gl:\n\n```js\n/* Action Handlers */\nconst actionHandler = {\n [ActionTypes.UPDATE_MAP]: updateMapUpdater,\n [ActionTypes.FIT_BOUNDS]: fitBoundsUpdater,\n [ActionTypes.TOGGLE_PERSPECTIVE]: togglePerspectiveUpdater\n};\n```\n\nUser can import a specific action handler in their root reducer and use it to directly modify kepler.gl’s state (without dispathcing a kepler.gl action). This will give user the full control over kepler.gl’s component state.\n\nHere is an example how you can listen to an app action `QUERY_SUCCESS` and call `updateVisDataUpdater` to load data into kepler.gl.\n\n```js\nimport keplerGlReducer, {visStateUpdaters} from '@kepler.gl/reducers';\n\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   case 'QUERY_SUCCESS':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n\n         // 'map' is the id of the keplerGl instance\n         map: {\n            ...state.keplerGl.map,\n            visState: visStateUpdaters.updateVisDataUpdater(\n              // you have to pass the subreducer state that the updater is associated with\n              state.keplerGl.map.visState,\n              {datasets: action.payload}\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n[redux]: https://redux.js.org/\n[react]: https://reactjs.org/\n[react-redux]: https://react-redux.js.org/\n[diagram-1]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/api_reducers_compose-reducers.png\n"
  },
  {
    "path": "docs/api-reference/reducers/combine.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [combinedUpdaters](#combinedupdaters)\n  - [addDataToMapUpdater](#adddatatomapupdater)\n\n## combinedUpdaters\n\nSome actions will affect the entire kepler.lg instance state.\nThe updaters for these actions is exported as `combinedUpdaters`. These updater take the entire instance state\nas the first argument. Read more about [Using updaters][5]\n\n**Examples**\n\n```javascript\nimport keplerGlReducer, {combinedUpdaters} from '@kepler.gl/reducers';\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   // add data to map after receiving data from remote sources\n   case 'LOAD_REMOTE_RESOURCE_SUCCESS':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         // pass in kepler.gl instance state to combinedUpdaters\n         map:  combinedUpdaters.addDataToMapUpdater(\n          state.keplerGl.map,\n          {\n            payload: {\n              datasets: action.datasets,\n              options: {readOnly: true},\n              config: action.config\n             }\n           }\n         )\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\n### addDataToMapUpdater\n\nCombine data and full configuration update in a single action\n\n-   **Action**: [`addDataToMap`][6]\n\n**Parameters**\n\n-   `state` **[Object][7]** kepler.gl instance state, containing all subreducer state\n-   `action` **[Object][7]**\n    -   `action.payload` **[Object][7]** `{datasets, options, config}`\n        -   `action.payload.datasets` **([Array][8]&lt;[Object][7]> | [Object][7])** **\\*required** datasets can be a dataset or an array of datasets\n            Each dataset object needs to have `info` and `data` property.\n            -   `action.payload.datasets.info` **[Object][7]** \\-info of a dataset\n                -   `action.payload.datasets.info.id` **[string][9]** id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n                -   `action.payload.datasets.info.label` **[string][9]** A display name of this dataset\n            -   `action.payload.datasets.data` **[Object][7]** **\\*required** The data object, in a tabular format with 2 properties `fields` and `rows`\n                -   `action.payload.datasets.data.fields` **[Array][8]&lt;[Object][7]>** **\\*required** Array of fields,\n                    -   `action.payload.datasets.data.fields.name` **[string][9]** **\\*required** Name of the field,\n                -   `action.payload.datasets.data.rows` **[Array][8]&lt;[Array][8]>** **\\*required** Array of rows, in a tabular format with `fields` and `rows`\n        -   `action.payload.options` **[Object][7]** option object `{centerMap: true}`\n        -   `action.payload.config` **[Object][7]** map config\n\nReturns **[Object][7]** nextState\n\n[1]: #combinedupdaters\n\n[2]: #examples\n\n[3]: #adddatatomapupdater\n\n[4]: #parameters\n\n[5]: ../advanced-usage/using-updaters.md\n\n[6]: ../actions/actions.md#adddatatomap\n\n[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n"
  },
  {
    "path": "docs/api-reference/reducers/map-state.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [mapStateUpdaters](#mapstateupdaters)\n  - [fitBoundsUpdater](#fitboundsupdater)\n  - [INITIAL\\_MAP\\_STATE](#initial_map_state)\n    - [Properties](#properties)\n  - [receiveMapConfigUpdater](#receivemapconfigupdater)\n  - [resetMapConfigUpdater](#resetmapconfigupdater)\n  - [togglePerspectiveUpdater](#toggleperspectiveupdater)\n  - [toggleSplitMapUpdater](#togglesplitmapupdater)\n  - [updateMapUpdater](#updatemapupdater)\n\n## mapStateUpdaters\n\nUpdaters for `mapState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\nRead more about [Using updaters][17]\n\n**Examples**\n\n```javascript\nimport keplerGlReducer, {mapStateUpdaters} from '@kepler.gl/reducers';\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   // click button to close side panel\n   case 'CLICK_BUTTON':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         foo: {\n            ...state.keplerGl.foo,\n            mapState: mapStateUpdaters.fitBoundsUpdater(\n              mapState, {payload: [127.34, 31.09, 127.56, 31.59]]}\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\n### fitBoundsUpdater\n\nFit map viewport to bounds\n\n-   **Action**: [`fitBounds`][18]\n\n**Parameters**\n\n-   `state` **[Object][19]**\n-   `action` **[Object][19]**\n    -   `action.payload` **[Array][20]&lt;[number][21]>** bounds as `[lngMin, latMin, lngMax, latMax]`\n\nReturns **[Object][19]** nextState\n\n### INITIAL_MAP_STATE\n\nDefault initial `mapState`\n\n#### Properties\n\n-   `pitch` **[number][21]** Default: `0`\n-   `bearing` **[number][21]** Default: `0`\n-   `latitude` **[number][21]** Default: `37.75043`\n-   `longitude` **[number][21]** Default: `-122.34679`\n-   `zoom` **[number][21]** Default: `9`\n-   `dragRotate` **[boolean][22]** Default: `false`\n-   `width` **[number][21]** Default: `800`\n-   `height` **[number][21]** Default: `800`\n-   `isSplit` **[boolean][22]** Default: `false`\n\n### receiveMapConfigUpdater\n\nUpdate `mapState` to propagate a new config\n\n-   **Action**: [`receiveMapConfig`][23]\n\n**Parameters**\n\n-   `state` **[Object][19]**\n-   `action` **[Object][19]**\n    -   `action.payload` **[Object][19]** saved map config\n    -   `action.payload.config`   (optional, default `{}`)\n    -   `action.payload.options`   (optional, default `{}`)\n    -   `action.payload.bounds`   (optional, default `null`)\n\nReturns **[Object][19]** nextState\n\n### resetMapConfigUpdater\n\nreset mapState to initial State\n\n-   **Action**: [`resetMapConfig`][24]\n\n**Parameters**\n\n-   `state` **[Object][19]** `mapState`\n\nReturns **[Object][19]** nextState\n\n### togglePerspectiveUpdater\n\nToggle between 3d and 2d map.\n\n-   **Action**: [`togglePerspective`][25]\n\n**Parameters**\n\n-   `state` **[Object][19]**\n\nReturns **[Object][19]** nextState\n\n### toggleSplitMapUpdater\n\nToggle between one or split maps\n\n-   **Action**: [`toggleSplitMap`][26]\n\n**Parameters**\n\n-   `state` **[Object][19]**\n\nReturns **[Object][19]** nextState\n\n### updateMapUpdater\n\nUpdate map viewport\n\n-   **Action**: [`updateMap`][27]\n\n**Parameters**\n\n-   `state` **[Object][19]**\n-   `action` **[Object][19]**\n    -   `action.payload` **[Object][19]** viewport\n\nReturns **[Object][19]** nextState\n\n[1]: #mapstateupdaters\n\n[2]: #examples\n\n[3]: #fitboundsupdater\n\n[4]: #parameters\n\n[5]: #initial_map_state\n\n[6]: #properties\n\n[7]: #receivemapconfigupdater\n\n[8]: #parameters-1\n\n[9]: #resetmapconfigupdater\n\n[10]: #parameters-2\n\n[11]: #toggleperspectiveupdater\n\n[12]: #parameters-3\n\n[13]: #togglesplitmapupdater\n\n[14]: #parameters-4\n\n[15]: #updatemapupdater\n\n[16]: #parameters-5\n\n[17]: ../advanced-usage/using-updaters.md\n\n[18]: ../actions/actions.md#fitbounds\n\n[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n\n[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n\n[23]: ../actions/actions.md#receivemapconfig\n\n[24]: ../actions/actions.md#resetmapconfig\n\n[25]: ../actions/actions.md#toggleperspective\n\n[26]: ../actions/actions.md#togglesplitmap\n\n[27]: ../actions/actions.md#updatemap\n"
  },
  {
    "path": "docs/api-reference/reducers/map-style.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [mapStyleUpdaters](#mapstyleupdaters)\n  - [INITIAL\\_MAP\\_STYLE](#initial_map_style)\n    - [Properties](#properties)\n  - [initMapStyleUpdater](#initmapstyleupdater)\n  - [inputMapStyleUpdater](#inputmapstyleupdater)\n  - [loadCustomMapStyleUpdater](#loadcustommapstyleupdater)\n  - [loadMapStyleErrUpdater](#loadmapstyleerrupdater)\n  - [loadMapStylesUpdater](#loadmapstylesupdater)\n  - [mapConfigChangeUpdater](#mapconfigchangeupdater)\n  - [mapStyleChangeUpdater](#mapstylechangeupdater)\n  - [resetMapConfigMapStyleUpdater](#resetmapconfigmapstyleupdater)\n\n## mapStyleUpdaters\n\nUpdaters for `mapStyle`. Can be used in your root reducer to directly modify kepler.gl's state.\nRead more about [Using updaters][21]\n\n**Examples**\n\n```javascript\nimport keplerGlReducer, {mapStyleUpdaters} from '@kepler.gl/reducers';\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   // click button to hide label from background map\n   case 'CLICK_BUTTON':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         foo: {\n            ...state.keplerGl.foo,\n            mapStyle: mapStyleUpdaters.mapConfigChangeUpdater(\n              mapStyle,\n              {payload: {visibleLayerGroups: {label: false, road: true, background: true}}}\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\n### INITIAL_MAP_STYLE\n\nDefault initial `mapStyle`\n\n#### Properties\n\n-   `styleType` **[string][22]** Default: `'dark'`\n-   `visibleLayerGroups` **[Object][23]** Default: `{}`\n-   `topLayerGroups` **[Object][23]** Default: `{}`\n-   `mapStyles` **[Object][23]** mapping from style key to style object\n-   `mapboxApiAccessToken` **[string][22]** Default: `null`\n-   `inputStyle` **[Object][23]** Default: `{}`\n-   `threeDBuildingColor` **[Array][24]** Default: `[r, g, b]`\n\n### initMapStyleUpdater\n\nPropagate `mapStyle` reducer with `mapboxApiAccessToken` and `mapStylesReplaceDefault`.\nif mapStylesReplaceDefault is true mapStyles is emptied; loadMapStylesUpdater() will\npopulate mapStyles.\n\n-   **Action**: [`keplerGlInit`][25]\n\n**Parameters**\n\n-   `state` **[Object][23]**\n-   `action` **[Object][23]**\n    -   `action.payload` **[Object][23]**\n        -   `action.payload.mapboxApiAccessToken` **[string][22]**\n\nReturns **[Object][23]** nextState\n\n### inputMapStyleUpdater\n\nInput a custom map style object\n\n-   **Action**: [`inputMapStyle`][26]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]** action object\n    -   `action.payload` **[Object][23]** inputStyle\n        -   `action.payload.url` **[string][22]** style url e.g. `'mapbox://styles/heshan/xxxxxyyyyzzz'`\n        -   `action.payload.id` **[string][22]** style url e.g. `'custom_style_1'`\n        -   `action.payload.style` **[Object][23]** actual mapbox style json\n        -   `action.payload.name` **[string][22]** style name\n        -   `action.payload.layerGroups` **[Object][23]** layer groups that can be used to set map layer visibility\n        -   `action.payload.icon` **[Object][23]** icon image data url\n    -   `action.payload.inputStyle`\n    -   `action.payload.mapState`\n\nReturns **[Object][23]** nextState\n\n### loadCustomMapStyleUpdater\n\nCallback when a custom map style object is received\n\n-   **Action**: [`loadCustomMapStyle`][27]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]**\n    -   `action.payload` **[Object][23]**\n        -   `action.payload.icon` **[string][22]**\n        -   `action.payload.style` **[Object][23]**\n        -   `action.payload.error` **any**\n    -   `action.payload.icon`\n    -   `action.payload.style`\n    -   `action.payload.error`\n\nReturns **[Object][23]** nextState\n\n### loadMapStyleErrUpdater\n\nCallback when load map style error\n\n-   **Action**: [`loadMapStyleErr`][28]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]**\n    -   `action.payload` **any** error\n\nReturns **[Object][23]** nextState\n\n### loadMapStylesUpdater\n\nCallback when load map style success\n\n-   **Action**: [`loadMapStyles`][29]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]**\n    -   `action.payload` **[Object][23]** a `{[id]: style}` mapping\n\nReturns **[Object][23]** nextState\n\n### mapConfigChangeUpdater\n\nUpdate `visibleLayerGroups`to change layer group visibility\n\n-   **Action**: [`mapConfigChange`][30]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]**\n    -   `action.payload` **[Object][23]** new config `{visibleLayerGroups: {label: false, road: true, background: true}}`\n\nReturns **[Object][23]** nextState\n\n### mapStyleChangeUpdater\n\nChange to another map style. The selected style should already been loaded into `mapStyle.mapStyles`\n\n-   **Action**: [`mapStyleChange`][31]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n-   `action` **[Object][23]**\n    -   `action.payload` **[string][22]**\n\nReturns **[Object][23]** nextState\n\n### resetMapConfigMapStyleUpdater\n\nReset map style config to initial state\n\n-   **Action**: [`resetMapConfig`][32]\n\n**Parameters**\n\n-   `state` **[Object][23]** `mapStyle`\n\nReturns **[Object][23]** nextState\n\n[1]: #mapstyleupdaters\n\n[2]: #examples\n\n[3]: #initial_map_style\n\n[4]: #properties\n\n[5]: #initmapstyleupdater\n\n[6]: #parameters\n\n[7]: #inputmapstyleupdater\n\n[8]: #parameters-1\n\n[9]: #loadcustommapstyleupdater\n\n[10]: #parameters-2\n\n[11]: #loadmapstyleerrupdater\n\n[12]: #parameters-3\n\n[13]: #loadmapstylesupdater\n\n[14]: #parameters-4\n\n[15]: #mapconfigchangeupdater\n\n[16]: #parameters-5\n\n[17]: #mapstylechangeupdater\n\n[18]: #parameters-6\n\n[19]: #resetmapconfigmapstyleupdater\n\n[20]: #parameters-7\n\n[21]: ../advanced-usage/using-updaters.md\n\n[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n\n[23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[24]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[25]: ../actions/actions.md#keplerglinit\n\n[26]: ../actions/actions.md#inputmapstyle\n\n[27]: ../actions/actions.md#loadcustommapstyle\n\n[28]: ../actions/actions.md#loadmapstyleerr\n\n[29]: ../actions/actions.md#loadmapstyles\n\n[30]: ../actions/actions.md#mapconfigchange\n\n[31]: ../actions/actions.md#mapstylechange\n\n[32]: ../actions/actions.md#resetmapconfig\n"
  },
  {
    "path": "docs/api-reference/reducers/reducers.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [keplerGlReducer](#keplerglreducer)\n  - [keplerGlReducer.initialState](#keplerglreducerinitialstate)\n  - [keplerGlReducer.plugin](#keplerglreducerplugin)\n- [mapStateLens](#mapstatelens)\n- [mapStyleLens](#mapstylelens)\n- [providerStateLens](#providerstatelens)\n- [uiStateLens](#uistatelens)\n- [visStateLens](#visstatelens)\n\n## keplerGlReducer\n\nKepler.gl reducer to be mounted to your store. You can mount `keplerGlReducer` at property `keplerGl`, if you choose\nto mount it at another address e.g. `foo` you will need to specify it when you mount `KeplerGl` component in your app with `getState: state => state.foo`\n\n**Examples**\n\n```javascript\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport {taskMiddleware} from 'react-palm/tasks';\n\nconst initialState = {};\nconst reducers = combineReducers({\n  // <-- mount kepler.gl reducer in your app\n  keplerGl: keplerGlReducer,\n\n  // Your other reducers here\n  app: appReducer\n});\n\n// using createStore\nexport default createStore(reducer, initialState, applyMiddleware(taskMiddleware));\n```\n\n### keplerGlReducer.initialState\n\nReturn a reducer that initiated with custom initial state.\nThe parameter should be an object mapping from `subreducer` name to custom subreducer state,\nwhich will be shallow **merged** with default initial state.\n\nDefault subreducer state:\n\n-   [`visState`][19]\n-   [`mapState`][20]\n-   [`mapStyle`][21]\n-   [`uiState`][22]\n\n**Parameters**\n\n-   `iniSt` **[Object][23]** custom state to be merged with default initial state\n\n**Examples**\n\n```javascript\nconst myKeplerGlReducer = keplerGlReducer\n .initialState({\n   uiState: {readOnly: true}\n });\n```\n\n### keplerGlReducer.plugin\n\nReturns a kepler.gl reducer that will also pass each action through additional reducers spiecified.\nThe parameter should be either a reducer map or a reducer function.\nThe state passed into the additional action handler is the instance state.\nIt will include all the subreducers `visState`, `uiState`, `mapState` and `mapStyle`.\n`.plugin` is only meant to be called once when mounting the keplerGlReducer to the store.\n**Note** This is an advanced option to give you more freedom to modify the internal state of the kepler.gl instance.\nYou should only use this to adding additional actions instead of replacing default actions.\n\n**Parameters**\n\n-   `customReducer` **([Object][23] \\| [Function][24])** A reducer map or a reducer\n\n**Examples**\n\n```javascript\nconst myKeplerGlReducer = keplerGlReducer\n .plugin({\n   // 1. as reducer map\n   HIDE_AND_SHOW_SIDE_PANEL: (state, action) => ({\n     ...state,\n     uiState: {\n       ...state.uiState,\n       readOnly: !state.uiState.readOnly\n     }\n   })\n })\n.plugin(handleActions({\n  // 2. as reducer\n  'HIDE_MAP_CONTROLS': (state, action) => ({\n    ...state,\n    uiState: {\n      ...state.uiState,\n      mapControls: hiddenMapControl\n    }\n  })\n}, {}));\n```\n\n## mapStateLens\n\nConnect subreducer `mapState`, used with `injectComponents`. Learn more at\n[Replace UI Component][25]\n\n**Parameters**\n\n-   `reduxState` **any**\n\n## mapStyleLens\n\nConnect subreducer `mapStyle`, used with `injectComponents`. Learn more at\n[Replace UI Component][25]\n\n**Parameters**\n\n-   `reduxState` **any**\n\n## providerStateLens\n\nConnect subreducer `providerState`, used with `injectComponents`. Learn more at\n[Replace UI Component][25]\n\n**Parameters**\n\n-   `reduxState` **any**\n\n## uiStateLens\n\nConnect subreducer `uiState`, used with `injectComponents`. Learn more at\n[Replace UI Component][25]\n\n**Parameters**\n\n-   `reduxState` **any**\n\n## visStateLens\n\nConnect subreducer `visState`, used with `injectComponents`. Learn more at\n[Replace UI Component][25]\n\n**Parameters**\n\n-   `reduxState` **any**\n\n[1]: #keplerglreducer\n\n[2]: #examples\n\n[3]: #keplerglreducerinitialstate\n\n[4]: #parameters\n\n[5]: #examples-1\n\n[6]: #keplerglreducerplugin\n\n[7]: #parameters-1\n\n[8]: #examples-2\n\n[9]: #mapstatelens\n\n[10]: #parameters-2\n\n[11]: #mapstylelens\n\n[12]: #parameters-3\n\n[13]: #providerstatelens\n\n[14]: #parameters-4\n\n[15]: #uistatelens\n\n[16]: #parameters-5\n\n[17]: #visstatelens\n\n[18]: #parameters-6\n\n[19]: ./vis-state.md#INITIAL_VIS_STATE\n\n[20]: ./map-state.md#INITIAL_MAP_STATE\n\n[21]: ./map-style.md#INITIAL_MAP_STYLE\n\n[22]: ./ui-state.md#INITIAL_UI_STATE\n\n[23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[24]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function\n\n[25]: ../advanced-usages/replace-ui-component.md#pass-custom-component-props\n"
  },
  {
    "path": "docs/api-reference/reducers/ui-state.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [uiStateUpdaters](#uistateupdaters)\n  - [addNotificationUpdater](#addnotificationupdater)\n  - [cleanupExportImage](#cleanupexportimage)\n  - [DEFAULT\\_EXPORT\\_DATA](#default_export_data)\n    - [Properties](#properties)\n  - [DEFAULT\\_EXPORT\\_IMAGE](#default_export_image)\n    - [Properties](#properties-1)\n  - [DEFAULT\\_MAP\\_CONTROLS\\_FEATURES](#default_map_controls_features)\n    - [Properties](#properties-2)\n  - [hideExportDropdownUpdater](#hideexportdropdownupdater)\n  - [INITIAL\\_UI\\_STATE](#initial_ui_state)\n    - [Properties](#properties-3)\n  - [loadFilesErrUpdater](#loadfileserrupdater)\n  - [loadFilesUpdater](#loadfilesupdater)\n  - [openDeleteModalUpdater](#opendeletemodalupdater)\n  - [removeNotificationUpdater](#removenotificationupdater)\n  - [setExportDataTypeUpdater](#setexportdatatypeupdater)\n  - [setExportDataUpdater](#setexportdataupdater)\n  - [setExportFilteredUpdater](#setexportfilteredupdater)\n  - [setExportImageDataUri](#setexportimagedatauri)\n  - [setExportImageSetting](#setexportimagesetting)\n  - [setExportSelectedDatasetUpdater](#setexportselecteddatasetupdater)\n  - [showExportDropdownUpdater](#showexportdropdownupdater)\n  - [startExportingImage](#startexportingimage)\n  - [toggleMapControlUpdater](#togglemapcontrolupdater)\n  - [toggleModalUpdater](#togglemodalupdater)\n  - [toggleSidePanelUpdater](#togglesidepanelupdater)\n  - [toggleSplitMapUpdater](#togglesplitmapupdater)\n- [DEFAULT\\_EXPORT\\_HTML](#default_export_html)\n  - [Properties](#properties-4)\n- [setUserMapboxAccessTokenUpdater](#setusermapboxaccesstokenupdater)\n\n## uiStateUpdaters\n\nUpdaters for `uiState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\nRead more about [Using updaters][53]\n\n**Examples**\n\n```javascript\nimport keplerGlReducer, {uiStateUpdaters} from '@kepler.gl/reducers';\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   // click button to close side panel\n   case 'CLICK_BUTTON':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         foo: {\n            ...state.keplerGl.foo,\n            uiState: uiStateUpdaters.toggleSidePanelUpdater(\n              uiState, {payload: null}\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\n### addNotificationUpdater\n\nAdd a notification to be displayed.\nExisting notification is going to be updated in case of matching ids.\n\n-   **Action**: [`addNotification`][54]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[Object][55]**\n\nReturns **[Object][55]** nextState\n\n### cleanupExportImage\n\nDelete cached export image\n\n-   **Action**: [`cleanupExportImage`][56]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n\nReturns **[Object][55]** nextState\n\n### DEFAULT_EXPORT_DATA\n\nDefault initial `exportData` settings\n\nType: [Object][55]\n\n#### Properties\n\n-   `selectedDataset` **[string][57]** Default: `''`,\n-   `dataType` **[string][57]** Default: `'csv'`,\n-   `filtered` **[boolean][58]** Default: `true`,\n-   `config` **[boolean][58]** deprecated\n-   `data` **[boolean][58]** used in modal config export. Default: `false`\n\n### DEFAULT_EXPORT_IMAGE\n\nDefault image export config\n\nType: [Object][55]\n\n#### Properties\n\n-   `ratio` **[string][57]** Default: `'SCREEN'`,\n-   `resolution` **[string][57]** Default: `'ONE_X'`,\n-   `legend` **[boolean][58]** Default: `false`,\n-   `imageDataUri` **[string][57]** Default: `''`,\n-   `exporting` **[boolean][58]** Default: `false`\n-   `error` **[boolean][58]** Default: `false`\n\n### DEFAULT_MAP_CONTROLS_FEATURES\n\nA list of map control visibility and whether is it active.\n\nType: [Object][55]\n\n#### Properties\n\n-   `visibleLayers` **[Object][55]** Default: `{show: true, active: false}`\n-   `mapLegend` **[Object][55]** Default: `{show: true, active: false}`\n-   `toggle3d` **[Object][55]** Default: `{show: true}`\n-   `splitMap` **[Object][55]** Default: `{show: true}`\n\n### hideExportDropdownUpdater\n\nHide side panel header dropdown, activated by clicking the share link on top of the side panel\n\n-   **Action**: [`hideExportDropdown`][59]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n\nReturns **[Object][55]** nextState\n\n### INITIAL_UI_STATE\n\nDefault initial `uiState`\n\nType: [Object][55]\n\n#### Properties\n\n-   `readOnly` **[boolean][58]** Default: `false`\n-   `activeSidePanel` **[string][57]** Default: `'layer'`\n-   `currentModal` **([string][57] | null)** Default: `'addData'`\n-   `datasetKeyToRemove` **([string][57] | null)** Default: `null`\n-   `visibleDropdown` **([string][57] | null)** Default: `null`\n-   `exportImage` **[Object][55]** Default: [`DEFAULT_EXPORT_IMAGE`][9]\n-   `exportData` **[Object][55]** Default: [`DEFAULT_EXPORT_DATA`][7]\n-   `mapControls` **[Object][55]** Default: [`DEFAULT_MAP_CONTROLS`][60]\n-   `activeMapIndex` **[number][61]** defines which map the user clicked on. Default: 0\n\n### loadFilesErrUpdater\n\nHandles load file error and set fileLoading property to false\n\n-   **Action**: [`loadFilesErr`][62]\n\n**Parameters**\n\n-   `state`\n-   `error` **[Object][55]**\n    -   `error.error`\n\nReturns **[Object][55]** nextState\n\n### loadFilesUpdater\n\nFired when file loading begin\n\n-   **Action**: [`loadFiles`][63]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n\nReturns **[Object][55]** nextState\n\n### openDeleteModalUpdater\n\nToggle active map control panel\n\n-   **Action**: [`openDeleteModal`][64]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]** dataset id\n\nReturns **[Object][55]** nextState\n\n### removeNotificationUpdater\n\nRemove a notification\n\n-   **Action**: [`removeNotification`][65]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[String][57]** id of the notification to be removed\n\nReturns **[Object][55]** nextState\n\n### setExportDataTypeUpdater\n\nSet data format for exporting data\n\n-   **Action**: [`setExportDataType`][66]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]** one of `'text/csv'`\n\nReturns **[Object][55]** nextState\n\n### setExportDataUpdater\n\nWhether to including data in map config, toggle between `true` or `false`\n\n-   **Action**: [`setExportData`][67]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n\nReturns **[Object][55]** nextState\n\n### setExportFilteredUpdater\n\nWhether to export filtered data, `true` or `false`\n\n-   **Action**: [`setExportFiltered`][68]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[boolean][58]**\n\nReturns **[Object][55]** nextState\n\n### setExportImageDataUri\n\nSet `exportImage.setExportImageDataUri` to a image dataUri\n\n-   **Action**: [`setExportImageDataUri`][69]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]** export image data uri\n\nReturns **[Object][55]** nextState\n\n### setExportImageSetting\n\nSet `exportImage.legend` to `true` or `false`\n\n-   **Action**: [`setExportImageSetting`][70]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `$1` **[Object][55]**\n    -   `$1.payload`\n\nReturns **[Object][55]** nextState\n\n### setExportSelectedDatasetUpdater\n\nSet selected dataset for export\n\n-   **Action**: [`setExportSelectedDataset`][71]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]** dataset id\n\nReturns **[Object][55]** nextState\n\n### showExportDropdownUpdater\n\nHide and show side panel header dropdown, activated by clicking the share link on top of the side panel\n\n-   **Action**: [`showExportDropdown`][72]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]** id of the dropdown\n\nReturns **[Object][55]** nextState\n\n### startExportingImage\n\nSet `exportImage.exporting` to `true`\n\n-   **Action**: [`startExportingImage`][73]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n\nReturns **[Object][55]** nextState\n\n### toggleMapControlUpdater\n\nToggle active map control panel\n\n-   **Action**: [`toggleMapControl`][74]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]** action\n    -   `action.payload` **[string][57]** map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`][60]\n    -   `action.payload.panelId`\n    -   `action.payload.index`   (optional, default `0`)\n\nReturns **[Object][55]** nextState\n\n### toggleModalUpdater\n\nShow and hide modal dialog\n\n-   **Action**: [`toggleModal`][75]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **([string][57] | null)** id of modal to be shown, null to hide modals. One of:-   [`DATA_TABLE_ID`][76]\n        -   [`DELETE_DATA_ID`][77]\n        -   [`ADD_DATA_ID`][78]\n        -   [`EXPORT_IMAGE_ID`][79]\n        -   [`EXPORT_DATA_ID`][80]\n        -   [`ADD_MAP_STYLE_ID`][81]\n\nReturns **[Object][55]** nextState\n\n### toggleSidePanelUpdater\n\nToggle active side panel\n\n-   **Action**: [`toggleSidePanel`][82]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **([string][57] | null)** id of side panel to be shown, one of `layer`, `filter`, `interaction`, `map`. close side panel if `null`\n\nReturns **[Object][55]** nextState\n\n### toggleSplitMapUpdater\n\nHandles toggle map split and reset all map control index to 0\n\n-   **Action**: [`toggleSplitMap`][83]\n\n**Parameters**\n\n-   `state`\n\nReturns **[Object][55]** nextState\n\n## DEFAULT_EXPORT_HTML\n\nType: [Object][55]\n\n### Properties\n\n-   `exportMapboxAccessToken` **[string][57]** Default: null, this is used when we provide a default mapbox token for users to take advantage of\n-   `userMapboxToken` **[string][57]** Default: '', mapbox token provided by user through input field\n\n## setUserMapboxAccessTokenUpdater\n\nwhether to export a mapbox access to HTML single page\n\n-   **Action**: [`setUserMapboxAccessToken`][84]\n\n**Parameters**\n\n-   `state` **[Object][55]** `uiState`\n-   `action` **[Object][55]**\n    -   `action.payload` **[string][57]**\n\nReturns **[Object][55]** nextState\n\n[1]: #uistateupdaters\n\n[2]: #examples\n\n[3]: #addnotificationupdater\n\n[4]: #parameters\n\n[5]: #cleanupexportimage\n\n[6]: #parameters-1\n\n[7]: #default_export_data\n\n[8]: #properties\n\n[9]: #default_export_image\n\n[10]: #properties-1\n\n[11]: #default_map_controls_features\n\n[12]: #properties-2\n\n[13]: #hideexportdropdownupdater\n\n[14]: #parameters-2\n\n[15]: #initial_ui_state\n\n[16]: #properties-3\n\n[17]: #loadfileserrupdater\n\n[18]: #parameters-3\n\n[19]: #loadfilesupdater\n\n[20]: #parameters-4\n\n[21]: #opendeletemodalupdater\n\n[22]: #parameters-5\n\n[23]: #removenotificationupdater\n\n[24]: #parameters-6\n\n[25]: #setexportdatatypeupdater\n\n[26]: #parameters-7\n\n[27]: #setexportdataupdater\n\n[28]: #parameters-8\n\n[29]: #setexportfilteredupdater\n\n[30]: #parameters-9\n\n[31]: #setexportimagedatauri\n\n[32]: #parameters-10\n\n[33]: #setexportimagesetting\n\n[34]: #parameters-11\n\n[35]: #setexportselecteddatasetupdater\n\n[36]: #parameters-12\n\n[37]: #showexportdropdownupdater\n\n[38]: #parameters-13\n\n[39]: #startexportingimage\n\n[40]: #parameters-14\n\n[41]: #togglemapcontrolupdater\n\n[42]: #parameters-15\n\n[43]: #togglemodalupdater\n\n[44]: #parameters-16\n\n[45]: #togglesidepanelupdater\n\n[46]: #parameters-17\n\n[47]: #togglesplitmapupdater\n\n[48]: #parameters-18\n\n[49]: #default_export_html\n\n[50]: #properties-4\n\n[51]: #setusermapboxaccesstokenupdater\n\n[52]: #parameters-19\n\n[53]: ../advanced-usage/using-updaters.md\n\n[54]: ../actions/actions.md#addnotification\n\n[55]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[56]: ../actions/actions.md#cleanupexportimage\n\n[57]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n\n[58]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n\n[59]: ../actions/actions.md#hideexportdropdown\n\n[60]: #default_map_controls\n\n[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n\n[62]: ../actions/actions.md#loadfileserr\n\n[63]: ../actions/actions.md#loadfiles\n\n[64]: ../actions/actions.md#opendeletemodal\n\n[65]: ../actions/actions.md#removenotification\n\n[66]: ../actions/actions.md#setexportdatatype\n\n[67]: ../actions/actions.md#setexportdata\n\n[68]: ../actions/actions.md#setexportfiltered\n\n[69]: ../actions/actions.md#setexportimagedatauri\n\n[70]: ../actions/actions.md#setexportimagesetting\n\n[71]: ../actions/actions.md#setexportselecteddataset\n\n[72]: ../actions/actions.md#showexportdropdown\n\n[73]: ../actions/actions.md#startexportingimage\n\n[74]: ../actions/actions.md#togglemapcontrol\n\n[75]: ../actions/actions.md#togglemodal\n\n[76]: ../constants/default-settings.md#data_table_id\n\n[77]: ../constants/default-settings.md#delete_data_id\n\n[78]: ../constants/default-settings.md#add_data_id\n\n[79]: ../constants/default-settings.md#export_image_id\n\n[80]: ../constants/default-settings.md#export_data_id\n\n[81]: ../constants/default-settings.md#add_map_style_id\n\n[82]: ../actions/actions.md#togglesidepanel\n\n[83]: ../actions/actions.md#togglesplitmap\n\n[84]: ../actions/actions.md#setusermapboxaccesstoken\n"
  },
  {
    "path": "docs/api-reference/reducers/vis-state.md",
    "content": "<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n### Table of Contents\n\n- [visStateUpdaters](#visstateupdaters)\n  - [addFilterUpdater](#addfilterupdater)\n  - [addLayerUpdater](#addlayerupdater)\n  - [applyCPUFilterUpdater](#applycpufilterupdater)\n  - [enlargeFilterUpdater](#enlargefilterupdater)\n  - [INITIAL\\_VIS\\_STATE](#initial_vis_state)\n    - [Properties](#properties)\n  - [interactionConfigChangeUpdater](#interactionconfigchangeupdater)\n  - [layerClickUpdater](#layerclickupdater)\n  - [layerHoverUpdater](#layerhoverupdater)\n  - [layerTypeChangeUpdater](#layertypechangeupdater)\n  - [layerVisConfigChangeUpdater](#layervisconfigchangeupdater)\n  - [layerVisualChannelChangeUpdater](#layervisualchannelchangeupdater)\n  - [loadFilesErrUpdater](#loadfileserrupdater)\n  - [loadFilesUpdater](#loadfilesupdater)\n  - [mapClickUpdater](#mapclickupdater)\n  - [receiveMapConfigUpdater](#receivemapconfigupdater)\n  - [removeDatasetUpdater](#removedatasetupdater)\n  - [removeFilterUpdater](#removefilterupdater)\n  - [removeLayerUpdater](#removelayerupdater)\n  - [reorderLayerUpdater](#reorderlayerupdater)\n  - [resetMapConfigUpdater](#resetmapconfigupdater)\n  - [setFilterPlotUpdater](#setfilterplotupdater)\n  - [setFilterUpdater](#setfilterupdater)\n  - [setMapInfoUpdater](#setmapinfoupdater)\n  - [showDatasetTableUpdater](#showdatasettableupdater)\n  - [toggleFilterAnimationUpdater](#togglefilteranimationupdater)\n  - [toggleLayerForMapUpdater](#togglelayerformapupdater)\n  - [toggleSplitMapUpdater](#togglesplitmapupdater)\n  - [updateAnimationTimeUpdater](#updateanimationtimeupdater)\n  - [updateFilterAnimationSpeedUpdater](#updatefilteranimationspeedupdater)\n  - [updateLayerAnimationSpeedUpdater](#updatelayeranimationspeedupdater)\n  - [updateLayerBlendingUpdater](#updatelayerblendingupdater)\n  - [updateVisDataUpdater](#updatevisdataupdater)\n\n## visStateUpdaters\n\nUpdaters for `visState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\nRead more about [Using updaters][67]\n\n**Examples**\n\n```javascript\nimport keplerGlReducer, {visStateUpdaters} from '@kepler.gl/reducers';\n// Root Reducer\nconst reducers = combineReducers({\n keplerGl: keplerGlReducer,\n app: appReducer\n});\n\nconst composedReducer = (state, action) => {\n switch (action.type) {\n   case 'CLICK_BUTTON':\n     return {\n       ...state,\n       keplerGl: {\n         ...state.keplerGl,\n         foo: {\n            ...state.keplerGl.foo,\n            visState: visStateUpdaters.enlargeFilterUpdater(\n              state.keplerGl.foo.visState,\n              {idx: 0}\n            )\n         }\n       }\n     };\n }\n return reducers(state, action);\n};\n\nexport default composedReducer;\n```\n\n### addFilterUpdater\n\nAdd a new filter\n\n-   **Action**: [`addFilter`][68]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.dataId` **[string][70]** dataset `id` this new filter is associated with\n\nReturns **[Object][69]** nextState\n\n### addLayerUpdater\n\nAdd a new layer\n\n-   **Action**: [`addLayer`][71]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.props` **[Object][69]** new layer props\n\nReturns **[Object][69]** nextState\n\n### applyCPUFilterUpdater\n\nWhen select dataset for export, apply cpu filter to selected dataset\n\n-   **Action**: [`applyCPUFilter`][72]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]**\n    -   `action.dataId` **[string][70]** dataset id\n\nReturns **[Object][69]** nextState\n\n### enlargeFilterUpdater\n\nShow larger time filter at bottom for time playback (apply to time filter only)\n\n-   **Action**: [`enlargeFilter`][73]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** index of filter to enlarge\n\nReturns **[Object][69]** nextState\n\n### INITIAL_VIS_STATE\n\nDefault initial `visState`\n\nType: [Object][69]\n\n#### Properties\n\n-   `layers` **[Array][75]**\n-   `layerData` **[Array][75]**\n-   `layerToBeMerged` **[Array][75]**\n-   `layerOrder` **[Array][75]**\n-   `filters` **[Array][75]**\n-   `filterToBeMerged` **[Array][75]**\n-   `datasets` **[Array][75]**\n-   `editingDataset` **[string][70]**\n-   `interactionConfig` **[Object][69]**\n-   `interactionToBeMerged` **[Object][69]**\n-   `layerBlending` **[string][70]**\n-   `hoverInfo` **[Object][69]**\n-   `clicked` **[Object][69]**\n-   `mousePos` **[Object][69]**\n-   `splitMaps` **[Array][75]** a list of objects of layer availabilities and visibilities for each map\n-   `layerClasses` **[Object][69]**\n-   `animationConfig` **[Object][69]**\n-   `editor` **[Object][69]**\n\n### interactionConfigChangeUpdater\n\nUpdate `interactionConfig`\n\n-   **Action**: [`interactionConfigChange`][76]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.config` **[Object][69]** new config as key value map: `{tooltip: {enabled: true}}`\n\nReturns **[Object][69]** nextState\n\n### layerClickUpdater\n\nTrigger layer click event with clicked object\n\n-   **Action**: [`onLayerClick`][77]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.info` **[Object][69]** Object clicked, returned by deck.gl\n\nReturns **[Object][69]** nextState\n\n### layerHoverUpdater\n\nTrigger layer hover event with hovered object\n\n-   **Action**: [`onLayerHover`][78]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.info` **[Object][69]** Object hovered, returned by deck.gl\n\nReturns **[Object][69]** nextState\n\n### layerTypeChangeUpdater\n\nUpdate layer type. Previews layer config will be copied if applicable.\n\n-   **Action**: [`layerTypeChange`][79]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.oldLayer` **[Object][69]** layer to be updated\n    -   `action.newType` **[string][70]** new type\n\nReturns **[Object][69]** nextState\n\n### layerVisConfigChangeUpdater\n\nUpdate layer `visConfig`\n\n-   **Action**: [`layerVisConfigChange`][80]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.oldLayer` **[Object][69]** layer to be updated\n    -   `action.newVisConfig` **[Object][69]** new visConfig as a key value map: e.g. `{opacity: 0.8}`\n\nReturns **[Object][69]** nextState\n\n### layerVisualChannelChangeUpdater\n\nUpdate layer visual channel\n\n-   **Action**: [`layerVisualChannelConfigChange`][81]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.oldLayer` **[Object][69]** layer to be updated\n    -   `action.newConfig` **[Object][69]** new visual channel config\n    -   `action.channel` **[string][70]** channel to be updated\n\nReturns **[Object][69]** nextState\n\n### loadFilesErrUpdater\n\nTrigger loading file error\n\n-   **Action**: [`loadFilesErr`][82]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.error` **any**\n\nReturns **[Object][69]** nextState\n\n### loadFilesUpdater\n\nTrigger file loading dispatch `addDataToMap` if succeed, or `loadFilesErr` if failed\n\n-   **Action**: [`loadFiles`][83]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.files` **[Array][75]&lt;[Object][69]>** array of fileblob\n\nReturns **[Object][69]** nextState\n\n### mapClickUpdater\n\nTrigger map click event, unselect clicked object\n\n-   **Action**: [`onMapClick`][84]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n\nReturns **[Object][69]** nextState\n\n### receiveMapConfigUpdater\n\nPropagate `visState` reducer with a new configuration. Current config will be override.\n\n-   **Action**: [`receiveMapConfig`][85]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.payload` **[Object][69]** map config to be propagated\n        -   `action.payload.config` **[Object][69]** map config to be propagated\n        -   `action.payload.option` **[Object][69]** {keepExistingConfig: true | false}\n    -   `action.payload.config`   (optional, default `{}`)\n    -   `action.payload.options`   (optional, default `{}`)\n\nReturns **[Object][69]** nextState\n\n### removeDatasetUpdater\n\nRemove a dataset and all layers, filters, tooltip configs that based on it\n\n-   **Action**: [`removeDataset`][86]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.key` **[string][70]** dataset id\n\nReturns **[Object][69]** nextState\n\n### removeFilterUpdater\n\nRemove a filter\n\n-   **Action**: [`removeFilter`][87]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** index of filter to b e removed\n\nReturns **[Object][69]** nextState\n\n### removeLayerUpdater\n\nremove layer\n\n-   **Action**: [`removeLayer`][88]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** index of layer to b e removed\n\nReturns **[Object][69]** nextState\n\n### reorderLayerUpdater\n\nReorder layer\n\n-   **Action**: [`reorderLayer`][89]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.order` **[Array][75]&lt;[Number][74]>** an array of layer indexes\n\nReturns **[Object][69]** nextState\n\n### resetMapConfigUpdater\n\nreset visState to initial State\n\n-   **Action**: [`resetMapConfig`][90]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n\nReturns **[Object][69]** nextState\n\n### setFilterPlotUpdater\n\nSet the property of a filter plot\n\n-   **Action**: [`setFilterPlot`][91]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]**\n    -   `action.newProp` **[Object][69]** key value mapping of new prop `{yAxis: 'histogram'}`\n\nReturns **[Object][69]** nextState\n\n### setFilterUpdater\n\nUpdate filter property\n\n-   **Action**: [`setFilter`][92]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** `idx` of filter to be updated\n    -   `action.prop` **[string][70]** `prop` of filter, e,g, `dataId`, `name`, `value`\n    -   `action.value` **any** new value\n-   `datasetId` **[string][70]** used when updating a prop (dataId, name) that can be linked to multiple datasets\n\nReturns **[Object][69]** nextState\n\n### setMapInfoUpdater\n\nUser input to update the info of the map\n\n-   **Action**: [`setMapInfo`][93]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.info` **[Object][69]** {title: 'hello'}\n\nReturns **[Object][69]** nextState\n\n### showDatasetTableUpdater\n\nDisplay dataset table in a modal\n\n-   **Action**: [`showDatasetTable`][94]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.dataId` **[string][70]** dataset id to show in table\n\nReturns **[Object][69]** nextState\n\n### toggleFilterAnimationUpdater\n\nStart and end filter animation\n\n-   **Action**: [`toggleFilterAnimation`][95]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** idx of filter\n\nReturns **[Object][69]** nextState\n\n### toggleLayerForMapUpdater\n\nToggle visibility of a layer in a split map\n\n-   **Action**: [`toggleLayerForMap`][96]\n\n**Parameters**\n\n-   `state` **[Object][69]**\n-   `action` **[Object][69]**\n    -   `action.mapIndex` **[Number][74]** index of the split map\n    -   `action.layerId` **[string][70]** id of the layer\n\nReturns **[Object][69]** nextState\n\n### toggleSplitMapUpdater\n\nToggle visibility of a layer for a split map\n\n-   **Action**: [`toggleSplitMap`][97]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.payload` **([Number][74] \\| [undefined][98])** index of the split map\n\nReturns **[Object][69]** nextState\n\n### updateAnimationTimeUpdater\n\nReset animation config current time to a specified value\n\n-   **Action**: [`updateAnimationTime`][99]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.value` **[Number][74]** the value current time will be set to\n\nReturns **[Object][69]** nextState\n\n### updateFilterAnimationSpeedUpdater\n\nChange filter animation speed\n\n-   **Action**: [`updateFilterAnimationSpeed`][100]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.idx` **[Number][74]** `idx` of filter\n    -   `action.speed` **[Number][74]** `speed` to change it to. `speed` is a multiplier\n\nReturns **[Object][69]** nextState\n\n### updateLayerAnimationSpeedUpdater\n\nUpdate animation speed with the vertical speed slider\n\n-   **Action**: [`updateLayerAnimationSpeed`][101]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.speed` **[Number][74]** the updated speed of the animation\n\nReturns **[Object][69]** nextState\n\n### updateLayerBlendingUpdater\n\nupdate layer blending mode\n\n-   **Action**: [`updateLayerBlending`][102]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.mode` **[string][70]** one of `additive`, `normal` and `subtractive`\n\nReturns **[Object][69]** nextState\n\n### updateVisDataUpdater\n\nAdd new dataset to `visState`, with option to load a map config along with the datasets\n\n-   **Action**: [`updateVisData`][103]\n\n**Parameters**\n\n-   `state` **[Object][69]** `visState`\n-   `action` **[Object][69]** action\n    -   `action.datasets` **([Array][75]&lt;[Object][69]> | [Object][69])** **\\*required** datasets can be a dataset or an array of datasets\n        Each dataset object needs to have `info` and `data` property.\n        -   `action.datasets.info` **[Object][69]** \\-info of a dataset\n            -   `action.datasets.info.id` **[string][70]** id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n            -   `action.datasets.info.label` **[string][70]** A display name of this dataset\n        -   `action.datasets.data` **[Object][69]** **\\*required** The data object, in a tabular format with 2 properties `fields` and `rows`\n            -   `action.datasets.data.fields` **[Array][75]&lt;[Object][69]>** **\\*required** Array of fields,\n                -   `action.datasets.data.fields.name` **[string][70]** **\\*required** Name of the field,\n            -   `action.datasets.data.rows` **[Array][75]&lt;[Array][75]>** **\\*required** Array of rows, in a tabular format with `fields` and `rows`\n    -   `action.options` **[Object][69]** option object `{centerMap: true, keepExistingConfig: false}`\n    -   `action.config` **[Object][69]** map config\n\nReturns **[Object][69]** nextState\n\n[1]: #visstateupdaters\n\n[2]: #examples\n\n[3]: #addfilterupdater\n\n[4]: #parameters\n\n[5]: #addlayerupdater\n\n[6]: #parameters-1\n\n[7]: #applycpufilterupdater\n\n[8]: #parameters-2\n\n[9]: #enlargefilterupdater\n\n[10]: #parameters-3\n\n[11]: #initial_vis_state\n\n[12]: #properties\n\n[13]: #interactionconfigchangeupdater\n\n[14]: #parameters-4\n\n[15]: #layerclickupdater\n\n[16]: #parameters-5\n\n[17]: #layerhoverupdater\n\n[18]: #parameters-6\n\n[19]: #layertypechangeupdater\n\n[20]: #parameters-7\n\n[21]: #layervisconfigchangeupdater\n\n[22]: #parameters-8\n\n[23]: #layervisualchannelchangeupdater\n\n[24]: #parameters-9\n\n[25]: #loadfileserrupdater\n\n[26]: #parameters-10\n\n[27]: #loadfilesupdater\n\n[28]: #parameters-11\n\n[29]: #mapclickupdater\n\n[30]: #parameters-12\n\n[31]: #receivemapconfigupdater\n\n[32]: #parameters-13\n\n[33]: #removedatasetupdater\n\n[34]: #parameters-14\n\n[35]: #removefilterupdater\n\n[36]: #parameters-15\n\n[37]: #removelayerupdater\n\n[38]: #parameters-16\n\n[39]: #reorderlayerupdater\n\n[40]: #parameters-17\n\n[41]: #resetmapconfigupdater\n\n[42]: #parameters-18\n\n[43]: #setfilterplotupdater\n\n[44]: #parameters-19\n\n[45]: #setfilterupdater\n\n[46]: #parameters-20\n\n[47]: #setmapinfoupdater\n\n[48]: #parameters-21\n\n[49]: #showdatasettableupdater\n\n[50]: #parameters-22\n\n[51]: #togglefilteranimationupdater\n\n[52]: #parameters-23\n\n[53]: #togglelayerformapupdater\n\n[54]: #parameters-24\n\n[55]: #togglesplitmapupdater\n\n[56]: #parameters-25\n\n[57]: #updateanimationtimeupdater\n\n[58]: #parameters-26\n\n[59]: #updatefilteranimationspeedupdater\n\n[60]: #parameters-27\n\n[61]: #updatelayeranimationspeedupdater\n\n[62]: #parameters-28\n\n[63]: #updatelayerblendingupdater\n\n[64]: #parameters-29\n\n[65]: #updatevisdataupdater\n\n[66]: #parameters-30\n\n[67]: ../advanced-usage/using-updaters.md\n\n[68]: ../actions/actions.md#addfilter\n\n[69]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n[70]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String\n\n[71]: ../actions/actions.md#addlayer\n\n[72]: ../actions/actions.md#applycpufilter\n\n[73]: ../actions/actions.md#enlargefilter\n\n[74]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number\n\n[75]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array\n\n[76]: ../actions/actions.md#interactionconfigchange\n\n[77]: ../actions/actions.md#onlayerclick\n\n[78]: ../actions/actions.md#onlayerhover\n\n[79]: ../actions/actions.md#layertypechange\n\n[80]: ../actions/actions.md#layervisconfigchange\n\n[81]: ../actions/actions.md#layervisualchannelconfigchange\n\n[82]: ../actions/actions.md#loadfileserr\n\n[83]: ../actions/actions.md#loadfiles\n\n[84]: ../actions/actions.md#onmapclick\n\n[85]: ../actions/actions.md#receivemapconfig\n\n[86]: ../actions/actions.md#removedataset\n\n[87]: ../actions/actions.md#removefilter\n\n[88]: ../actions/actions.md#removelayer\n\n[89]: ../actions/actions.md#reorderlayer\n\n[90]: ../actions/actions.md#resetmapconfig\n\n[91]: ../actions/actions.md#setfilterplot\n\n[92]: ../actions/actions.md#setfilter\n\n[93]: ../actions/actions.md#setmapinfo\n\n[94]: ../actions/actions.md#showdatasettable\n\n[95]: ../actions/actions.md#togglefilteranimation\n\n[96]: ../actions/actions.md#togglelayerformap\n\n[97]: ../actions/actions.md#togglesplitmap\n\n[98]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined\n\n[99]: ../actions/actions.md#updateanimationtime\n\n[100]: ../actions/actions.md#updatefilteranimationspeed\n\n[101]: ../actions/actions.md#updatelayeranimationspeed\n\n[102]: ../actions/actions.md#updatelayerblending\n\n[103]: ../actions/actions.md#updatevisdata\n"
  },
  {
    "path": "docs/api-reference/schemas/README.md",
    "content": "# Schemas\n\n...Coming Soon\n"
  },
  {
    "path": "docs/keplergl-jupyter/README.md",
    "content": "# Jupyter Notebook\n\n## kepler.gl for Jupyter User Guide\n\n### Table of contents\n- [Install](#install)\n- [1. Load kepler.gl Map](#1-load-keplergl-map)\n  - [`KeplerGl()`](#keplergl)\n- [2. Add Data](#2-add-data)\n  - [`.add_data()`](#add_data)\n  - [`.data`](#data)\n- [3. Data Format](#3-data-format)\n  - [`CSV`](#csv)\n  - [`GeoJSON`](#geojson)\n  - [`DataFrame`](#dataframe)\n  - [`GeoDataFrame`](#geodataframe)\n  - [`WKT`](#wkt)\n- [4. Customize the map](#4-customize-the-map)\n- [5. Save and load config](#5-save-and-load-config)\n  - [`.config`](#config)\n- [6. Match config with data](#6-match-config-with-data)\n- [7. Save Map](#7-save-map)\n  - [`.save_to_html()`](#save_to_html)\n  - [`._repr_html_()`](#_repr_html_)\n- [Demo Notebooks](#demo-notebooks)\n- [FAQ & Troubleshoot](#faq--troubleshoot)\n\n\n## Install\n\n### Prerequisites\n\n- Python >= 2\n- ipywidgets >= 7.0.0\n\nTo install use pip:\n```bash\n$ pip install keplergl\n```\n\nIf you're on Mac, used `pip install`, and you're running Notebook 5.3 and above, you don't need to run the following:\n\n```bash\n$ jupyter nbextension install --py --sys-prefix keplergl # can be skipped for notebook 5.3 and above\n$ jupyter nbextension enable --py --sys-prefix keplergl # can be skipped for notebook 5.3 and above\n```\n\nIf you are using Jupyter Lab, you will also need to install the JupyterLab extension. This require [node](https://nodejs.org/en/download/package-manager/#macos) `> 10.15.0`\n\nIf you use [Homebrew](https://brew.sh/) on Mac:\n```bash\n$ brew install node@10\n```\n\nThen install jupyter labextension.\n\n```bash\n$ jupyter labextension install @jupyter-widgets/jupyterlab-manager keplergl-jupyter\n```\n\n### Prerequisites for JupyterLab\n- Node > 10.15.0\n- Python 3\n- JupyterLab>=1.0.0\n\n## 1. Load keplergl map\n### `KeplerGl()`\n\n- Input:\n  - __`height`__  _optional_ default: `400`\n\n      Height of the map display\n\n  - __`data`__ `dict` _optional_\n\n      Datasets as a dictionary, key is the name of the dataset. Read more on [Accepted data format][data_format]\n\n  - __`use_arrow`__ `bool` _optional_ default: `False`\n\n      Allow load and render data faster using GeoArrow\n\n  - __`config`__ `dict` _optional_\n\n      Map config as a dictionary. The `dataId` in the layer and filter settings should match the `name` of the dataset they are created under\n\n  - __`show_docs`__ `bool` _optional_\n\n      By default, the User Guide URL (<https://docs.kepler.gl/docs/keplergl-jupyter>) will be printed when a map is created. To hide the User Guide URL, set `show_docs=False`.\n\nThe following command will load kepler.gl widget below a cell.\n**The map object created here is `map_1` it will be used throughout the code example in this doc.**\n\n\n```python\n# Load an empty map\nfrom keplergl import KeplerGl\nmap_1 = KeplerGl()\nmap_1\n```\n\n![empty map][empty_map]\n\n\nYou can also create the map and pass in the data or data and config at the same time. Follow the instruction to [match config with data][match-config-w-data]\n\n```python\n# Load a map with data and config and height\nfrom keplergl import KeplerGl\nmap_2 = KeplerGl(height=400, data={\"data_1\": my_df}, config=config)\nmap_2\n```\n\n![Load map with data and config][load_map_w_data]\n\n## 2. Add Data\n### `.add_data()`\n- Inputs\n    - __`data`__ _required_ CSV, GeoJSON or DataFrame. Read more on [Accepted data format][data_format]\n    - __`name`__ _required_ Name of the data entry.\n    - __`use_arrow`__ _optional_ Allow load and render data faster using GeoArrow.\n\n`name` of the dataset will be the saved to the `dataId` property of each `layer`, `filter` and `interactionConfig` in the config.\n\nkepler.gl expected the data to be **CSV**,  **GeoJSON**, **DataFrame** or **GeoDataFrame**. You can call __`add_data`__ multiple times to add multiple datasets to kepler.gl\n\n```python\n# DataFrame\ndf = pd.read_csv('hex-data.csv')\nmap_1.add_data(data=df, name='data_1')\n\n# CSV\nwith open('csv-data.csv', 'r') as f:\n    csvData = f.read()\nmap_1.add_data(data=csvData, name='data_2')\n\n# GeoJSON as string\nwith open('sf_zip_geo.json', 'r') as f:\n    geojson = f.read()\n\nmap_1.add_data(data=geojson, name='geojson')\n```\n\n![Add data to map][map_add_data]\n\n### `.data`\nPrint the current data added to the map. As a `Dict`\n\n```python\nmap_1.data\n# {'data_1': 'hex_id,value\\n89283082c2fffff,64\\n8928308288fffff,73\\n89283082c07ffff,65\\n89283082817ffff,74\\n89283082c3bffff,66\\n8...`,\n#  'data_3': 'location, lat, lng, name\\n..',\n#  'data_3': '{\"type\": \"FeatureCollecti...'}\n```\n\n## 3. Data Format\nkepler.gl supports **CSV**,  **GeoJSON**, Pandas **DataFrame** or GeoPandas **GeoDataFrame**.\n\n### `CSV`\nYou can create a `CSV` string by reading from a CSV file.\n```python\nwith open('csv-data.csv', 'r') as f:\n    csvData = f.read()\n# csvData = \"hex_id,value\\n89283082c2fffff,64\\n8928308288fffff,73\\n89283082c07ffff,65\\n89283082817ffff,74\\n89283082c3bffff,66\\n8...\"\nmap_1.add_data(data=csvData, name='data_2')\n```\n\n### `GeoJSON`\nAccording to [GeoJSON Specification (RFC 7946)][geojson]: GeoJSON is a format for encoding a variety of geographic data structures. A GeoJSON object may represent a region of space (a `Geometry`), a spatially bounded entity (a Feature), or a list of Features (a `FeatureCollection`). GeoJSON supports the following geometry types: `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`, and `GeometryCollection`. Features in GeoJSON contain a Geometry object and additional properties, and a FeatureCollection contains a list of Features.\n\nkepler.gl supports all the GeoJSON types above excepts `GeometryCollection`. You can pass in either a single [`Feature`][features] or a [`FeatureCollection`][feature_collection]. You can format the  `GeoJSON` either as a `string` or a `dict` type\n\n\n```python\nfeature = {\n    \"type\": \"Feature\",\n    \"properties\": {\"name\": \"Coors Field\"},\n    \"geometry\": {\"type\": \"Point\", \"coordinates\": [-104.99404, 39.75621]}\n}\n\nfeatureCollection = {\n    \"type\": \"FeatureCollection\",\n    \"features\": [{\n        \"type\": \"Feature\",\n        \"geometry\": {\"type\": \"Point\", \"coordinates\": [102.0, 0.5]},\n        \"properties\": {\"prop0\": \"value0\"}\n    }]\n}\n\nmap_1.add_data(data=feature, name=\"feature\")\nmap_1.add_data(data=featureCollection, name=\"feature_collection\")\n```\n\nGeometries (Polygons, LindStrings) can be embedded into CSV or DataFrame with a [`GeoJSON`][geojson] Json string. Use the `geometry` property of a [`Feature`][features], which includes `type` and `coordinates`.\n\n```python\n# GeoJson Feature geometry\ngeometryString = {\n    'type': 'Polygon',\n    'coordinates': [[[-74.158491,40.835947],[-74.148473,40.834522],[-74.142598,40.833128],[-74.151923,40.832074],[-74.158491,40.835947]]]\n}\n\n# create json string\njson_str = json.dumps(geometryString)\n\n# create data frame\ndf_with_geometry = pd.DataFrame({\n    'id': [1],\n    'geometry_string': [json_str]\n})\n\n# add to map\nmap_1.add_data(df_with_geometry, \"df_with_geometry\")\n```\n\n### `DataFrame`\nkepler.gl accepts [pandas.DataFrame][data_frame]\n```python\ndf = pd.DataFrame(\n    {'City': ['Buenos Aires', 'Brasilia', 'Santiago', 'Bogota', 'Caracas'],\n     'Latitude': [-34.58, -15.78, -33.45, 4.60, 10.48],\n     'Longitude': [-58.66, -47.91, -70.66, -74.08, -66.86]})\n\nw1.add_data(data=df, name='cities')\n```\n\n### `GeoDataFrame`\nkepler.gl accepts [geopandas.GeoDataFrame][geo_data_frame], it automatically converts the current `geometry` column from shapely to wkt string and re-projects geometries to latitude and longitude (EPSG:4326) if the active `geometry` column is in a different projection.\n```python\nurl = 'http://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_040_00_500k.json'\ncountry_gdf = geopandas.read_file(url)\nw1.add_data(data=country_gdf, name=\"state\")\n```\n\n![US state][geodataframe_map]\n\n### `WKT`\n\nYou can embed geometries (Polygon, LineStrings etc) into CSV or DataFrame using [`WKT`][wkt]\n\n```python\n# WKT\nwkt_str = 'POLYGON ((-74.158491 40.835947, -74.130031 40.819962, -74.148818 40.830916, -74.151923 40.832074, -74.158491 40.835947))'\n\ndf_w_wkt = pd.DataFrame({\n    'id': [1],\n    'wkt_string': [wkt_str]\n})\n\nmap_1.add_data(df_w_wkt, \"df_w_wkt\")\n```\n\n## 4. Customize the map\n\nInteract with kepler.gl and customize layers and filters. Map data and config will be stored locally to the widget state. To make sure the map state is saved, select `Widgets > Save Notebook Widget State`, before shutting down the kernel.\n\n![Map interaction][map_interaction]\n\n## 5. Save and load config\n\n### `.config`\nyou can print your current map configuration at any time in the notebook\n```python\nmap_1.config\n## {u'config': {u'mapState': {u'bearing': 2.6192893401015205,\n#  u'dragRotate': True,\n#   u'isSplit': False,\n#   u'latitude': 37.76209132041332,\n#   u'longitude': -122.42590232651203,\n```\n\nWhen the map is final, you can copy this config and load it later to reproduce the same map. Follow the instruction to [match config with data][match-config-w-data].\n\n#### Apply config to a map:\n\n 1. Directly apply config to the map.\n```python\nconfig = {\n    'version': 'v1',\n    'config': {\n        'mapState': {\n            'latitude': 37.76209132041332,\n            'longitude': -122.42590232651203,\n            'zoom': 12.32053899007826\n        }\n        ...\n    }\n},\nmap_1.add_data(data=df, name='data_1')\nmap_1.config = config\n```\n 2. Load it when creating the map\n```python\nmap_1 = KeplerGl(height=400, data={'data_1': my_df}, config=config)\n```\n\nIf want to load the map next time with this saved config, the easiest way to do is to save the it to a file and use the magic command **%run** to load it w/o cluttering up your notebook.\n```python\n# Save map_1 config to a file\nwith open('hex_config.py', 'w') as f:\n   f.write('config = {}'.format(map_1.config))\n\n# load the config\n%run hex_config.py\n```\n\n## 6. Match config with data\nAll layers, filters and tooltips are associated with a specific dataset. Therefore the `data` and `config` in the map has to be able to match each other. The `name` of the dataset is assigned to:\n\n  - `dataId` of `layer.config`,\n  - `dataId` of `filter`\n  - key in `interactionConfig.tooltip.fieldToShow`.\n\n![Connect data and config][connect_data_config]\n\nYou can use the same config on another dataset with the same name and schema.\n\n## 7. Save Map\n\nWhen you click in the map and change settings, config is saved to widget state. Closing the notebook and reopen it will reload current map. However, you need to manually select `Widget > Save Notebook Widget State` before shut downing the kernel to make sure it will be reloaded.\n\n![Save Widget State][save_widget_state]\n\n### `.save_to_html()`\n\n- input\n  - **`data`**: _optional_  A data dictionary {\"name\": data}, if not provided, will use current map data\n  - **`config`**: _optional_ map config dictionary, if not provided, will use current map config\n  - **`file_name`**: _optional_ the html file name, default is `keplergl_map.html`\n  - **`read_only`**: _optional_ if `read_only` is `True`, hide side panel to disable map customization\n\nYou can export your current map as an interactive html file.\n\n```python\n# this will save current map\nmap_1.save_to_html(file_name='first_map.html')\n\n# this will save map with provided data and config\nmap_1.save_to_html(data={'data_1': df}, config=config, file_name='first_map.html')\n\n# this will save map with the interaction panel disabled\nmap_1.save_to_html(file_name='first_map.html', read_only=True)\n```\n\n### `._repr_html_()`\n\n- input\n  - **`data`**: _optional_  A data dictionary {\"name\": data}, if not provided, will use current map data\n  - **`config`**: _optional_ map config dictionary, if not provided, will use current map config\n  - **`read_only`**: _optional_ if `read_only` is `True`, hide side panel to disable map customization\n\nYou can also directly serve the current map via a flask app. To do that return kepler’s map HTML representation. Here is an example on how to do that:\n\n```python\nfrom flask import Flask\n\napp = Flask(__name__)\n\n@app.route('/')\ndef index():\n    return map_1._repr_html_()\n\nif __name__ == '__main__':\n    app.run(debug=True)\n```\n\n# Demo Notebooks\n- [Load kepler.gl](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/Load%20kepler.gl.ipynb): Load kepler.gl widget, add data and config\n- [Geometry as String](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/Geometry%20as%20String.ipynb): Embed Polygon geometries as `GeoJson` and `WKT` inside a `CSV`\n- [GeoJSON](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/GeoJSON.ipynb): Load GeoJSON to kepler.gl\n- [DataFrame](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/DataFrame.ipynb): Load DataFrame to kepler.gl\n- [GeoDataFrame](https://github.com/keplergl/kepler.gl/blob/master/bindings/kepler.gl-jupyter/notebooks/GeoDataFrame.ipynb): Load GeoDataFrame to kepler.gl\n\n# FAQ & Troubleshoot\n\n#### 1. What about Microsoft Windows?\nkeplergl is currently only published to PyPI, and unfortunately I use a Mac. If you encounter errors installing it on windows, [this issue](https://github.com/keplergl/kepler.gl/issues/557) might shed some light. Follow this issue for [conda](https://github.com/keplergl/kepler.gl/issues/646) support.\n\n#### 2. Install keplergl-jupyter on Jupyter Lab failed?\n\nMake sure you are using node 8.15.0. and you have installed `@jupyter-widgets/jupyterlab-manager`. Depends on your JupyterLab version. You might need to install the specific version of [jupyterlab-manager](https://github.com/jupyter-widgets/ipywidgets/tree/master/packages/jupyterlab-manager). with `jupyter labextension install @jupyter-widgets/jupyterlab-manager@0.31`. When use it in Jupyter lab, keplergl is only supported in JupyterLab > 1.0 and Python 3.\n\nRun `jupyter labextension install keplergl-jupyter --debug` and copy console output before creating an issue.\n\nIf you are running `install` and `uninstall` several times. You should run.\n\n```\njupyter lab clean\njupyter lab build\n```\n\n#### 2.1 JavaScript heap out of memory when installing lab extension\nIf you see this error during install labextension\n\n```bash\n$ FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory\n```\n\nrun\n\n```bash\n$ export NODE_OPTIONS=--max-old-space-size=4096\n```\n\n#### 3. Is my lab extension successfully installed?\nRun `jupyter labextension list` You should see below. (Version may vary)\n\n```bash\nJupyterLab v1.1.4\nKnown labextensions:\n   app dir: /Users/xxx/jupyter-python3/ENV3/share/jupyter/lab\n        @jupyter-widgets/jupyterlab-manager v1.0.2  enabled  OK\n        keplergl-jupyter v0.1.0  enabled  OK\n```\n\n#### 4. What's your python and node env\n\nPython\n```text\npython==3.7.4\nnotebook==6.0.3\njupyterlab==2.1.2\nipywidgets==7.5.1\n```\n\nNode (Only for JupyterLab)\n\n```text\nnode==8.15.0\nyarn==1.7.0\n```\n\n[jupyter_widget]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_widget.png\n[empty_map]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_empty_map.png\n[geodataframe_map]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_geodataframe.png\n[map_interaction]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_custom_map.gif\n[load_map_w_data]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_widget.png\n[map_add_data]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_add_data.png\n[connect_data_config]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_connect_data_w_config.png\n[save_widget_state]: https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/jupyter_save_state.png\n\n[wkt]: https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format\n[geojson]: https://tools.ietf.org/html/rfc7946\n[feature_collection]: https://tools.ietf.org/html/rfc7946#section-3.3\n[features]: https://tools.ietf.org/html/rfc7946#section-3.2\n[data_frame]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html\n[geo_data_frame]: https://geopandas.readthedocs.io/en/latest/data_structures.html#geodataframe\n\n[match-config-w-data]: #6-match-config-with-data\n[data_format]: #3-data-format\n\n"
  },
  {
    "path": "docs/release-notes.md",
    "content": "# What's New?\n\nThis page shows features that have landed to kepler.gl in major versions. For a complete list of changes to kepler.gl including each minor version, please [visit the full change log](../CHANGELOG.md).\n\n## 3.2\n\n_Released August 21st, 2025_\n\n### Raster Tile Layer\n\nThe new [Raster Tile layer](/docs/user-guides/c-types-of-layers/n-raster-tile-layer.md) enables visualization of satellite and aerial imagery from raster PMTiles and Cloud-Optimized GeoTIFFs (via STAC). Use it to bring large imagery datasets into kepler.gl without dedicated tile infrastructure for raster PMTiles, or connect through a compatible raster tile server for COGs and elevation.\n\n### WMS Layer\n\nThe new WMS layer adds support for rendering imagery and map tiles from OGC Web Map Service endpoints, allowing you to integrate enterprise or public WMS sources directly in kepler.gl.\n\n### AI Assistant\n\n- Generate idea buttons from the LLM to speed up workflows.\n- More reliable local model connectivity (fix for Ollama connection issues).\n- Configuration migrated to TypeScript for better npm consumption.\n\n### More Bug Fixes and Improvements\n\n- Fit-to-bounds: fix initial basemap and deck projections mismatch.\n- Aggregation layers: fixes for custom color scales.\n- Vector tiles: regression fixes for field extraction and setup flow.\n- Image export: fixes when effects are enabled.\n- Loading indicator: behavior and visibility improvements.\n- DuckDB: fix importing files with spaces in column names.\n\nFor the complete list of commits, see the [full change log](../CHANGELOG.md).\n\n## 3.1.1\n\n_Released March 10th, 2025_\n\n### DuckDB\n\nThe DuckDB integration has been updated in response to feedback and requests, speeding up workflows for projects with local data. Notable changes include:\n\n- Users can now drag and drop files directly in kepler.gl to create a DuckDB table.\n- The schema panel now always updates when running a query.\n- Improved handling of DuckDB column types.\n\n![DuckDB Drag and Drop](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-gl-duckdb-drag-drop.gif)\n\n### Vector Tiles\n\nThe Vector Tile layer has received a number of optimizations, bug fixes, and quality-of-life improvements. Notable changes include:\n\n- For older tilesets without fields in the metadata file, kepler.gl now attempts to retrieve fields from the tile data.\n- Automatically center the map to the tileset bounds.\n- Fix for UI freezes during initial Tileset setup.\n\n### More Bug Fixes\n\nA number of bug fixes have been deployed in response to community feedback. The most notable bug fixes are listed below, but you can view a full list of changes [in the full change log](../CHANGELOG.md).\n\n- Fix for geocoder coordinates, allowing users to enter coordinates directly.\n- Fix for icon layers at higher zoom levels—icons now remain the same size.\n- Fix for a broken section of the Icon Layer UI.\n- Ensure the RangeBrush updates when the slider range changes.\n- Transform binary buffers to hex WKB when saving to JSON/HTML maps.\n- Improved logic for changing layer types.\n- Support arrow text labels from non-string vector sources.\n- Export GeoArrow columns to CSV as GeoJSON.\n- Restore support for string WKB data; save binary WKB as hex WKB.\n- AI Assistant now sends messages to 127.0.0.1 instead of a remote Ollama URL.\n- Fix for disappearing heatmaps when rendering black or duplicate colors.\n- Fix for point column suggestions not working.\n- Fix for crashes in GeoJSON and Trip layers when no data is present.\n- Fix for the \"Save Map\" action when using the FSQ provider (overwrite logic).\n- FSQ storage provider now prompts for login instead of auto-login after logout.\n\n## 3.1\n\n_Released January 29th, 2025_\n\n### Vector Tiles\n\nThe new [Vector Tile layer](/docs/user-guides/c-types-of-layers/vector.md) allows the map to dynamically retrieve data based on the user's viewport and zoom level. This initial release supports both Mapbox Vector Tiles and PMTiles.\n\nBy leveraging the efficiency of vector tiles, users can visualize complex, large-scale datasets without compromising performance, making it easier to explore and analyze geospatial data.\n\n![Vector layer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-vector.gif)\n\n### DuckDB Support & SQL Explorer\n\nLeverage DuckDB directly within kepler.gl your geospatial projects with big data. Write and execute SQL queries to perform custom analyses, visualizing the results on your map.\n\nDuckDB enables in-browser data processing, allowing you to work with large datasets without the need for external infrastructure.\n\n![SQL Data Explorer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-duck-db.png)\n\n### AI Assistant\n\nKepler’s AI assistant can edit the map, including filters, base map customization, and a variety of layer configurations. Accessible via text chat, voice chat, and screenshot. The assistant can also produce SQL from natural language, which can be passed to DuckDB.\n\n![AI Assistant](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant.png)\n\n### Base Map Updates: MapLibre + Mapbox\n\nMapbox and MapLibre base maps are now simultaneously supported.\n\n### Color Scale Improvements\n\nCustom color scale is now supported in categorial/ordinal fields, aggregate layers, and other layer components. In addition, custom breaks are now supported within the color scales.\n\n### Value Formatting\n\nFormatting for numeric values (e.g. 10,000 can be formatted 10k, $10,000.00, etc; .42 can be formatted as 42%).\n\n### Animation Improvements\n\nIncludes various updates to the user interface for animation (for both time filters and the trip layer). You may also sync the layers (such as the trip layer) with filters, and conversely sync filters with the layer.\n\n### Legend Improvements\n\nThe legend is now both movable and resizable, supports the editing of legend values, and offers a scale for radius scaling.\n\n### Various Layer Improvements\n\nA number of improvements to layers, including:\n\n- Zoom to layer button lets users center their viewport on the layer’s data\n- Point layer now supports geojson\n- Arc layer supports creation from h3\n- A vast number of other layer improvements\n\nThis release also includes a wide range of bug fixes and performance improvements, which can be viewed in the [full change log.](../CHANGELOG.md)\n"
  },
  {
    "path": "docs/spatial-analysis-tutorial/README.md",
    "content": "# Tutorial: Spatial Data Analysis using Kepler.gl AI Assistant\n\nThis tutorial will guide you through the process of using Kepler.gl AI Assistant to perform spatial data analysis.\n\nThe spatial data analysis tools are powered by [Geoda](https://geodacenter.github.io/geoda-lib/), which is a free and open source software tool that serves as an introduction to spatial data science for students and researchers. To make it easier for users to go through the spatial data analysis features, we will try to replicate the [Geoda workbook](https://geodacenter.github.io/documentation.html) using Kepler.gl AI Assistant.\n\n## Table of Contents\n\n#### [Get Started](./get-started.md)\n\n#### [Spatial Data Wrangling - Basic Operations](./spatial-data-wragling.md)\n\n#### [Spatial Data Wrangling - GIS Operations](./spatial-data-gis.md)\n\n#### [Basic Mapping](./basic-mapping.md)\n\n#### [Rate Mapping](./rate-mapping.md)\n\n#### [Exploratory Data Analysis](./exploratory-data-analysis.md)\n\n- [Univariate and Bivariate Analysis](./univariate-and-bivariate-analysis.md)\n- [Multivariate Analysis](./multivariate-analysis.md)\n\n#### [Contiguity-Based Spatial Weights](./contiguity-based-spatial-weights.md)\n\n#### [Distance-Based Spatial Weights](./distance-based-spatial-weights.md)\n\n#### [Global Spatial Autocorrelation](./global-spatial-autocorrelation.md)\n\n- [Moran Scatter Plot](./moran-scatter-plot.md)\n- Correlogram\n- Bivariate, Differential and Empirical Bayes\n\n#### [Local Spatial Autocorrelation](./local-spatial-autocorrelation.md)\n\n- [LISA and Local Moran](./local-moran-i.md)\n- Other Local Spatial Autorrelation\n- Multivariate Local Spatial Autorrelation\n- LISA for Discrete Variables\n\n#### [Spatial Regression](./spatial-regression.md)\n\n- [Ordinary Least Squares Regression](./ordinary-least-squares-regression.md)\n- [Regression Diagnostics](./regression-diagnostics.md)\n- [Spatial Lag Model](./spatial-lag-model.md)\n- [Spatial Error Model](./spatial-error-model.md)\n"
  },
  {
    "path": "docs/spatial-analysis-tutorial/basic-mapping.md",
    "content": "# Basic Mapping\n\nOriginal GeoDa lab by Luc Anselin: https://geodacenter.github.io/workbook/3a_mapping/lab3a.html\n\n## Introduction\n\nIn this Chapter, we will explore a range of mapping and geovisualization options. We start with a review of common thematic map classifications. We next focus on different statistical maps, in particular maps that are designed to highight extreme values or outliers. We also illustrate maps for categorical variables (unique value maps), and their extension to multiple categories in the form of co-location maps. We close with a review of some special approaches to geovisualization, i.e., ~~conditional maps~~, the cartogram and map animation.\n\n The main objective is to use the maps to interact with the data as part of the exploration process.\n\n## Preliminaries\n\nWe will illustrate the various operations by means of the data set with demographic and socio-economic information for 55 New York City sub-boroughs.\n\n- [nyc](https://geodacenter.github.io/data-and-lab/nyc/) socio-economic data for 55 New York City sub-boroughs: https://geodacenter.github.io/data-and-lab/data/nyc.geojson\n\n## Thematic Maps – Overview\n\nWe start by loading the NYC sub-boroughs dataset:\n\n```\nLoad the dataset https://geodacenter.github.io/data-and-lab/data/nyc.geojson\n```\n\nCommon map classifications include the Quantile Map, Natural Breaks Map, and Equal Intervals Map. Specialized classifications that are designed to bring out extreme values include the Percentile Map, Box Map (with two options for the hinge), and the Standard Deviation Map. The Unique Values Map does not involve a classification algorithm, since it uses the integer/string values of a categorical variable itself as the map categories. The Co-location Map is an extension of this principle to multiple categorical variables. Finally, Custom Breaks allows for the use of customized classifications.\n\n### Common map classifications\n\n#### Quantile Map\n\nA quantile map is based on sorted values for a variable that are then grouped into bins that each have the same number of observations, the so-called quantiles. The number of bins corresponds to the particular quantile, e.g., five bins for a quintile map, or four bins for a quartile map, two of the most commonly used categories.\n\nFor example, we can create a quantile map for the variable `rent2008` with 4 categories (quartile map):\n\n```\nCan you create a quantile map for the variable 'rent2008' with 4 categories?\n```\n\nor\n\n```\nCan you create a quartile map for the variable 'rent2008'?\n```\n\n<img width=\"1042\" alt=\"Screenshot 2025-06-12 at 12 09 10 PM\" src=\"https://github.com/user-attachments/assets/a0638653-2c12-4510-9176-64e66df0f6f2\" />\n\n:::tip\nLLM has the 'knowledge' that to create a quartile map, it will use the quantile map with 4 categories.\n:::\n\nThe quantile map has been created with a default color scheme. If you want to replicate the same result in original GeoDa lab which use the colorbrewer2.org color scheme 'YlOrBr', you can use the following prompt:\n\n```\nCan you update the colors of the layers using colorBrewer's 4-class YlOrBr color scheme?\n```\n\n<img width=\"1068\" alt=\"Screenshot 2025-06-12 at 2 37 01 PM\" src=\"https://github.com/user-attachments/assets/44dbf94c-314a-4798-98db-b576763c3b91\" />\n\n\nYou can further explore the quantile results by querying the number of areas in each category:\n\n```\nHow many areas are in each category?\n```\n\n<img width=\"1069\" alt=\"Screenshot 2025-06-12 at 2 39 19 PM\" src=\"https://github.com/user-attachments/assets/18aa104a-fa0c-4734-b628-4b49967267d2\" />\n\nUpon closer examination from the screenshot, something doesn’t seem to be quite right. With 55 total observations, we should expect roughly 14 (55/4 = 13.75) observations in each group. But the first group only has 7, and the third group has 19! If we open up the Table and sort on the variable `RENT2008`, we can see where the problem lies.\n\nIgnoring the zero entries for now (those are a potential problem in their own right), we see that observations starting in row 8 up to row 21 all have a value of 1000. The cut-off for the first quartile is at 14-th row. In a non-spatial analysis, this is not an issue, the first quartile value is given as 1000. But in a map, the observations are locations that need to be assigned to a group (with a separate color). Other than an arbitrary assignment, there is no way to classify observations with a rent of 1000 in either category 1 or category 2. To deal with these ties, then quantile algorithm (implemented in [geoda-lib](https://github.com/geodacenter/geoda-lib)) moves all the observations with a value of 1000 to the second category. As a result, even though the value of the first quartile is given as 1000 in the map legend, only those observations with rents less than 1000 are included in the first quartile category. As we see from the table, there are seven such observations.\n\nAny time there are ties in the ranking of observations that align with the values for the breakpoints, the classification in a quantile map will be problematic and result in categories with an unequal number of observations.\n\nA natural (Jenks) breaks map uses a nonlinear algorithm to group observations such that the within-group homogeneity is maximized, following the pathbreaking work of Fisher ([1958](https://www.jstor.org/stable/2281952)) and Jenks ([1977](https://books.google.com/books/about/Optimal_Data_Classification_for_Chorople.html?id=HvAENQAACAAJ)). In essence, this is a clustering algorithm in one dimension to determine the break points that yield groups with the largest internal similarity.\n\nTo create such a map with four categories, you can use the following prompt:\n\n```\nCan you create a natural breaks map for the variable 'rent2008' with 4 categories using the same YlOrBr color scheme?\n```\n\n<img width=\"1067\" alt=\"Screenshot 2025-06-12 at 3 03 58 PM\" src=\"https://github.com/user-attachments/assets/8ce1b2e1-8e23-487a-9239-65e22644275d\" />\n\n:::tip\nYou will see there is a new map layer been created. But it has the same name as the previous one. You can click on the label of the map layer in the Layers panel to edit the name of the layer.\n:::\n\nIf you want to compare the results of natural breaks map with the quartile map, you can click 'Switch to dual map view' button on the top right corner of the map to compare the two layers side by side.\n\n<img width=\"1069\" alt=\"Screenshot 2025-06-12 at 3 02 54 PM\" src=\"https://github.com/user-attachments/assets/bd276c8a-e374-4571-b6f4-22958cc7d1be\" />\n\nIn comparison to the quartile map, the natural breaks criterion is better at grouping the extreme observations. The three observations with zero values make up the first category, whereas the five high rent areas in Manhattan make up the top category. Note also that in contrast to the quantile maps, the number of observations in each category can be highly unequal.\n\n#### Equal Intervals Map\n\nAn equal intervals (quantize) map uses the same principle as a histogram to organize the observations into categories that divide the range of the variable into equal interval bins. This contrasts with the quantile map, where the number of observations in each bin is equal, but the range for each bin is not. For the equal interval classification, the value range between the lower and upper bound in each bin is constant across bins, but the number of observations in each bin is typically not equal.\n\n:::tip\nIn Kepler.gl, the equal intervals method is also callled 'quantize'. See documentation for more details: https://docs.kepler.gl/docs/user-guides/d-layer-attributes\n:::\n\nTo create an equal intervals map for the variable `rent2008` with 4 categories, you can use the following prompt:\n\n```\nCan you create a equal intervals map for the variable 'rent2008' with 4 categories using the same YlOrBr color scheme?\n```\n\n<img width=\"1068\" alt=\"Screenshot 2025-06-12 at 3 16 40 PM\" src=\"https://github.com/user-attachments/assets/d985a359-f053-4a3e-bdcf-f3e015adc70d\" />\n\nAs in the case of natural breaks, the equal interval approach can yield categories with highly unequal numbers of observations. In our example, the three zero observations again get grouped in the first category, but the second range (from 725 to 1450) contains the bulk of the spatial units (42).\n\nTo illustrate the similarity with the histogram, you can use the following prompt:\n\n```\nCan you create a histogram for the variable 'rent2008' with 4 bins?\n```\n\n<img width=\"1067\" alt=\"Screenshot 2025-06-12 at 3 18 18 PM\" src=\"https://github.com/user-attachments/assets/455c5402-d8be-4dd6-89ae-837f5cc8f5e3\" />\n\nThe resulting histogram has the exact same ranges for each interval as the equal intervals map. The number of observations in each category is the same as well. The values for the number of observations in the descriptive statistics below the graph match the values given in the map legend.\n\nFinally, we illustrate the equivalence between the two graphs by selecting a category in the map legend. To accomplish this, we select the highest category in the histogram. This selects will highlights the related observations in the map, through linking.\n\n![equal-map-histogram-link](https://github.com/user-attachments/assets/e60c9d4b-4c56-4993-8b65-6c9ca5325ce0)\n\n:::note\nThe linking is now way one for now: from plot to map. The two-way linking is not supported yet.\n:::\n\n\n### Map options\n\nIn Kepler.gl, there are several options to customize the map.\n\n- change base map [doc](https://docs.kepler.gl/docs/user-guides/f-map-styles)\n- chang layer order [doc](https://docs.kepler.gl/docs/user-guides/b-kepler-gl-workflow/add-data-to-layers/d-blend-and-rearrange-layers)\n- configure layer [doc](https://docs.kepler.gl/docs/user-guides/d-layer-attributes)\n- configure color palettes [doc](https://docs.kepler.gl/docs/user-guides/l-color-attributes)\n\nFor saving and exporting the map, please refer to the following documentation:\n\n- save and export [doc](https://docs.kepler.gl/docs/user-guides/k-save-and-export)\n\n### Extreme Value Maps\n\nExtreme value maps are variations of common choropleth maps where the classification is designed to highlight extreme values at the lower and upper end of the scale, with the goal of identifying outliers. These maps were developed in the spirit of spatializing EDA, i.e., adding spatial features to commonly used approaches in non-spatial EDA ([Anselin 1994](https://scholar.google.com/scholar?hl=en&as_sdt=0%2C3&q=Exploratory+Spatial+Data+Analysis+and+Geographic+Information+Systems&btnG=)).\n\nThere are three types of extreme value maps:\n\n- Percentile Map\n- Box Map\n- Standard Deviation Map\n\nThese are briefly described below. Only their distinctive features are highlighted, since they share all the same options with the other choropleth map types.\n\n#### Percentile Map\n\nThe percentile map is a variant of a quantile map that would start off with 100 categories. However, rather than having these 100 categories, the map classification is reduced to six ranges, the lowest 1%, 1-10%, 10-50%, 50-90%, 90-99% and the top 1%.\n\nFor example, you can create a percentile map for the variable `rent2008`, using the following prompt:\n\n```\nCan you create a percentile map for the variable 'rent2008' with color scheme: BuRd?\n```\n\n<img width=\"1072\" alt=\"Screenshot 2025-06-12 at 10 46 35 PM\" src=\"https://github.com/user-attachments/assets/3d4e0384-dd9f-4b7a-95bc-acb99bbbec31\" />\n\n:::tip\nThe percentile has been defined as 6 categories mentioned above, so there is no need to specify the number of categories in the prompt.\n:::\n\nNote how the extreme values are much better highlighted, especially at the upper end of the distribution. The classification also illustrates some common problems with this type of map. First of all, since there are fewer than 100 observations, in a strict sense there is no 1% of the distribution. This is handled (arbitrarily) by rounding, so that the highest category has one observation, but the lowest does not have any.\n\nIn addition, since the values are sorted from low to high to determine the cut points, there can be an issue with ties. As we have seen, this is a generic problem for all quantile maps. As pointed out, the [algorithm](https://github.com/GeoDaCenter/geoda-lib/blob/main/cpp/src/mapping/percentile-breaks.cpp) handles ties by moving observations to the next highest category. For example, when there are a lot of observations with zero values (e.g., in the crime rate map for the U.S. counties), the lowest percentile can easily end up without observations, since all the zeros will be moved to the next category.\n\n#### Box Map\n\nA box map ([Anselin 1994](https://scholar.google.com/scholar?hl=en&as_sdt=0%2C3&q=Exploratory+Spatial+Data+Analysis+and+Geographic+Information+Systems&btnG=)) is the mapping counterpart of the idea behind a box plot. The point of departure is again a quantile map, more specifically, a quartile map. But the four categories are extended to six bins, to separately identify the lower and upper outliers. The definition of outliers is a function of a multiple of the inter-quartile range (IQR), the difference between the values for the 75 and 25 percentile. As we will see in a later chapter in our discussion of the box plot, we use two options for these cut-off values, or hinges, 1.5 and 3.0. The box map uses the same convention.\n\nTo create a box map for the variable `rent2008`, you can use the following prompt:\n\n```\nCan you create a box map for the variable 'rent2008' using BuRd color scheme?\n```\n\n<img width=\"1130\" alt=\"Screenshot 2025-06-13 at 12 06 55 PM\" src=\"https://github.com/user-attachments/assets/1f0118e4-8212-4ae1-a58f-244d3f88a226\" />\n\nCompared to the quartile map, the box map separates the three lower outliers (the observations with zero values) from the other four observations in the first quartile. They are depicted in dark blue. Similarly, it separates the six outliers in Manhattan from the eight other observations in the upper quartile. The upper outliers are colored dark red.\n\nTo illustrate the correspondence between the box plot and the box map, we create a box plot for the variable `rent2008`.\n\n```\nCan you create a box plot for the variable 'rent2008'?\n```\n\n<img width=\"1128\" alt=\"Screenshot 2025-06-13 at 12 07 25 PM\" src=\"https://github.com/user-attachments/assets/75d5b420-d984-4d8d-84e3-50d1a8d0c23a\" />\n\nIn the box plot, we select the upper outliers, the matching six outlier locations in the box map are highlighted. Note that the outliers in the box plot do not provide any information on the fact that these locations are also adjoining in space. This the spatial perspective that the box map adds to the data exploration.\n\n![box-map-plot-link](https://github.com/user-attachments/assets/5d02795e-abe1-452a-9fd2-e32aff8d4f53)\n\nThe default box map uses the hinge criterion of 1.5. You can change the hinge criterion to 3.0 by using the following prompt:\n\n```\nCan you create a box map for the variable 'rent2008' with the hinge criterion of 3.0?\n```\n\n<img width=\"1113\" alt=\"Screenshot 2025-06-13 at 3 28 52 PM\" src=\"https://github.com/user-attachments/assets/8eabac38-0a7b-4139-bc6f-6ae3afeacd57\" />\n\nThe resulting box map, shown in screenshot, no longer has lower outliers and has only five upper outliers (compared to six for the 1.5 hinge).\n\nThe box map is the preferred method to quickly and efficiently identify outliers and broad spatial patterns in a data set.\n\n#### Standard Deviation Map\n\nThe third type of extreme values map is a standard deviation map. In some way, this is a parametric counterpart to the box map, in that the standard deviation is used as the criterion to identify outliers, instead of the inter-quartile range.\n\nIn a standard deviation map, the variable under consideration is transformed to standard deviational units (with mean 0 and standard deviation 1). This is equivalent to the z-standardization we have seen before.\n\nThe number of categories in the classification depends on the range of values, i.e., how many standard deviational units cover the range from lowest to highest. It is also quite common that some categories do not contain any observations (as in the example in the screenshot below).\n\nTo create a standard deviation map for the variable `rent2008`, you can use the following prompt:\n\n```\nCan you create a standard deviation map for the variable 'rent2008'?\n```\n\n<img width=\"1113\" alt=\"Screenshot 2025-06-13 at 3 30 28 PM\" src=\"https://github.com/user-attachments/assets/a96ab4cc-6180-4019-a00e-10483f942d75\" />\n\nIn the screenshot above, there are five neighborhoods with median rent more than two standard deviations above the mean, and three with a median rent less than two standard deviations below the mean. Both sets would be labeled outliers in standard statistical practice. Also, note how the second lowest category does not contain any observations (so the corresponding color is not present in the map).\n\n## Mapping Categorical Variables\n\nSo far, our maps have pertained to continuous variables, with a clear order from low to high. The AI assistant in Kepler.gl also contains some functionality to map categorical variables, for which the numerical values are distinct, but not necessarily meaningful in and of themselves. Most importantly, the numerical values typically do not imply any ordering of the categories. The two relevant functions are the unique value map, for a single variable, and the co-location map, where the categories for multiple variables are compared.\n\n### Unique Value Map\n\nTo illustrate the categorical map, we first create a box map (with hinge 1.5) for each of the variables `kids2000` (the percentage households with kids under 18) and `pubast00` (the percentage households receiving public assistance). In each of these maps, we save the categories, respectively as `kidscat` and `asstcat`.\n\nThe category labels go from 1 to 6, but not all categories necessarily have observations. For example, the map for public assistance does not have either lower (label 1) or higher (label 6), outliers, only observations for 2–5. For the kids map, we have categories 1–5.\n\n```\nCan you create a box map for the variable 'kids2000' with the hinge criterion of 1.5 using BuRd color scheme?\n```\n\n<img width=\"1115\" alt=\"Screenshot 2025-06-13 at 3 37 59 PM\" src=\"https://github.com/user-attachments/assets/8a98b93a-16ce-422a-bd31-3a89d9b31da8\" />\n\n\nThen, you can save the categories from the box map result into a new column. For example:\n\n```\nCan you save the 6 categories (index starts from 1) into a new column \"kidscat\" from the box map result?\n```\n\n<img width=\"1117\" alt=\"Screenshot 2025-06-13 at 6 23 32 PM\" src=\"https://github.com/user-attachments/assets/4d8d61a7-4e5d-488b-a729-193b7f530a22\" />\n\n\n:::tip\nExpand the 'tableTool' function calling to see the result. The AI Assistant uses the following SQL query to get the categories:\n\nSELECT ..., CASE\n  WHEN KIDS2000 < 10.8864 THEN 1\n  WHEN KIDS2000 >= 10.8864 AND KIDS2000 < 30.090975 THEN 2\n  WHEN KIDS2000 >= 30.090975 AND KIDS2000 < 38.2278 THEN 3\n  WHEN KIDS2000 >= 38.2278 AND KIDS2000 < 42.894025 THEN 4\n  WHEN KIDS2000 >= 42.894025 AND KIDS2000 < 62.0986 THEN 5\n  WHEN KIDS2000 >= 62.0986 THEN 6\nEND AS kidscat FROM nyctable;\n:::\n\nTo replicate the same result in the original [GeoDa lab](https://geodacenter.github.io/workbook/3a_mapping/lab3a.html), you can use the following prompt:\n\n```\nCan you create a unique values map using 'kidscat' in the new dataset with Paired color scheme?\n```\n\n<img width=\"1116\" alt=\"Screenshot 2025-06-13 at 6 31 11 PM\" src=\"https://github.com/user-attachments/assets/b98ae8d4-a4a2-49e3-92b9-a482cef840c4\" />\n\nThen, you can do the same for the variable `pubast00` to save the categories into a new column. For example:\n\n```\nCan you create a box map for the variable 'pubast00' with the hinge criterion of 1.5 using BuRd color scheme?\n```\n<img width=\"1115\" alt=\"Screenshot 2025-06-13 at 6 32 29 PM\" src=\"https://github.com/user-attachments/assets/64ce96f9-bf52-4358-9dc4-7ad97e1f0030\" />\n\n```\nCan you save the categories (index starts from 1) into a new column \"asstcat\" from the box map result?\n```\n<img width=\"1115\" alt=\"Screenshot 2025-06-13 at 6 34 19 PM\" src=\"https://github.com/user-attachments/assets/0fdca743-5756-4342-bdbb-ead587477c7b\" />\n\nTo replicate the same result in the original GeoDa lab, you can use the following prompt:\n\n```\nCan you create a unique values map using 'asstcat' in the new dataset with Paired color scheme?\n```\n<img width=\"1112\" alt=\"Screenshot 2025-06-13 at 6 35 39 PM\" src=\"https://github.com/user-attachments/assets/ad9f7a0e-5491-43a8-bb6e-2502a9061303\" />\n\n### Co-location Map\n\nThe co-location map is a map that shows the co-location of two categorical variables. It is a variant of the unique value map, where the categories for multiple variables are compared.\n\nTo create a co-location map for the variables `kidscat` and `asstcat`, you can use the following prompt:\n\n```\nCan you compare the two categorical values 'asstcat' and 'kidscat' (keep the value if they are the same, 0 if they are different) and save the result into a new column 'co_location' to a new dataset?\n```\n\n```\nCan you create a unique values map using 'co_location' in the new dataset with Paired color scheme? please assign gray color to value 0.\n```\n\n<img width=\"1114\" alt=\"Screenshot 2025-06-13 at 6 52 16 PM\" src=\"https://github.com/user-attachments/assets/a31d0c07-3003-4a88-beae-b61f8f20ed83\" />\n\nThe above steps show how to create a co-location map of two variables step by step:\n- create a categorical variable from a continuous variable A using e.g. box map\n- create a categorical variable from a continuous variable B using the same method\n- compare the two categorical variables and save the result into a new column 'co_location' to a new dataset\n- create a unique values map using 'co_location' in the new dataset with Paired color scheme\n\nIn Kepler.gl AI Assistant, we create a specific 'colocation' tool, so you can create a co-location map just with the following prompt:\n\n```\nCan you create a co-location map for the variables 'kids2000' and 'pubast00' using quantile breaks (k=5)?\n```\n\n<img width=\"1156\" alt=\"Screenshot 2025-06-16 at 12 42 22 PM\" src=\"https://github.com/user-attachments/assets/b72ac213-ffc0-4cba-97ac-b41d49db71d8\" />\n\nFrom the screenshot, you can see how AI Assistant plans the similar steps to create the co-location map:\n\n- Classify 'kids2000' into 5 quantile bins.\n- Classify 'pubast00' into 5 quantile bins.\n- Create a new dataset with both categorical variables.\n- Compare the two categorical variables:\n  - Assign the same value if they match.\n  - Assign -1 if they differ.\n- Visualize the result with a unique color for each match and gray for mismatches.\n\n### Custom Classification\n\nIn addition to the range of pre-defined classifications available for choropleth maps, it is also possible to create a custom classification using AI Assistant. This is often useful when substantive concerns dictate the cut points, rather than data driven criteria. For example, this may be appropriate when specific income categories are specified for certain government programs.\n\nA custom classification may also be useful when comparing the spatial distribution of a variable over time. All included pre-defined classifications are relative and would be re-computed for each time period. For example, when mapping crime rates over time, in an era of declining rates, the observations in the upper quartile in a later period may have crime rates that correspond to a much lower category in an earlier period. This would not be apparent using the pre-defined approaches, but could be visualized by setting the same break points for each time period.\n\nFor example, you can create a custom classification for the variable `rent2008` with the following prompt:\n\n```\nCan you create a new map layer using the variable 'kids2000' with the break points: [20, 30, 40, 45, 50] and color scheme YlOrBr?\n```\n\n<img width=\"1152\" alt=\"Screenshot 2025-06-16 at 11 53 44 AM\" src=\"https://github.com/user-attachments/assets/eef1834e-e4c0-4ab1-84b1-50a8937b1a86\" />\n\n\nTip: to create a custom classification, what you need to prompt includes:\n- the variable name\n- the custom break values\n- the color scheme (optional)\n\nKepler.gl has a custom classification tool that allows you to adjust the break points by simply dragging the break points. See [Custom Breaks Editor](https://docs.foursquare.com/analytics-products/docs/layers-color-scale-and-palettes#custom-breaks-editor).\n\n## ~~Conditional Map~~\n\n## Cartogram\n\nA cartogram is a map type where the original layout of the areal unit is replaced by a geometric form (usually a circle, rectangle, or hexagon) that is proportional to the value of the variable for the location. This is in contrast to a standard choropleth map, where the size of the polygon corresponds to the area of the location in question. The cartogram has a long history and many variants have been suggested, some quite creative. In essence, the construction of a cartogram is an example of a nonlinear optimization problem, where the geometric forms have to be located such that they reflect the topology (spatial arrangement) of the locations as closely as possible (see [Tobler 2004](https://www.jstor.org/stable/3694068), for an extensive discussion of various aspects of the cartogram).\n\nThe AI assistant in Kepler.gl implements a circular cartogram [algorithm](https://github.com/GeoDaCenter/geoda-lib/blob/main/cpp/src/geometry/cartogram.h), in which the areal units are represented as circles, whose size (and color) is proportional to the value observed at that location. The changed shapes remove the misleading effect that the area of the unit might have on perception of magnitude. This circular cartogram is implemented as the `cartogram` tool in the AI assistant.\n\nTo create a cartogram for the variable `rent2008`, you can use the following prompt:\n\n```\nCan you create a cartogram for the variable 'rent2008' and create a box map using the cartogram with color scheme BuRd?\n```\n\n<img width=\"1152\" alt=\"Screenshot 2025-06-16 at 11 53 44 AM\" src=\"https://github.com/user-attachments/assets/eef1834e-e4c0-4ab1-84b1-50a8937b1a86\" />\n\nThe cartogram is most insightful when used in conjunction with a regular choropleth map. Selecting an observation in the cartogram then immediately links it with the corresponding area in the choropleth map.\n\nExcept for multi-layer and base map, the cartogram has all the same options as a regular choropleth map, with one addition. The positioning of the circles in the cartogram is the result of a non-linear optimization algorithm. This tries to locate the center of the circle as close as possible to the centroid of the areal unit with which it corresponds, while respecting the contiguity structure as much as possible. There is no unique solution to this problem, and it is often good practice to experiment with further iterations that will slightly reposition the circles. This is implemented in the Improve Cartogram by specify the number of different iterations.\n\n```\nCan you create a cartogram for the variable 'rent2008' with 200 iterations and create a box map using the cartogram with color scheme BuRd?\n```\n\nYou can use split map in Kepler.gl to compare the cartogram and the box map.\n\n<img width=\"1081\" alt=\"Screenshot 2025-06-17 at 4 46 59 PM\" src=\"https://github.com/user-attachments/assets/7a34d390-7424-4a89-a483-488f9d1e0285\" />\n\n## ~~Map Animation~~\n"
  },
  {
    "path": "docs/spatial-analysis-tutorial/get-started.md",
    "content": "# Get Started\n\nKepler.gl AI Assistant is a plugin for Kepler.gl that allows you to perform spatial data analysis and visualization using AI.\n\n## Start AI Assistant\n\nTo use AI Assistant, you can click the \"AI Assistant\" button in the top right corner of the Kepler.gl UI.\n\n<img width=\"334\" alt=\"Screenshot 2025-05-30 at 12 48 08 PM\" src=\"https://github.com/user-attachments/assets/2ac83229-bb2b-4009-a857-7fb9922b15cb\" />\n\n## Configure AI Assistant\n\nYou will see the AI Assistant configuration panel on the right side of the Kepler.gl UI. You can adjust the width of the panel by dragging the border between the panel and the Kepler.gl UI.\n\n<img width=\"481\" alt=\"Screenshot 2025-05-30 at 12 49 15 PM\" src=\"https://github.com/user-attachments/assets/0cf9282d-b949-457a-ac35-e64306509869\" />\n\nThe configuration panel includes several important settings:\n\n### AI Provider\n\nSelect your preferred AI provider from the dropdown menu. Currently supports:\n\n- **OpenAI** - OpenAI's GPT models\n- **Google** - Google's Gemini models\n- **Anthropic** - Anthropic's Claude models\n- **Deepseek** - Deepseek's chat models\n- **XAI** - xAI's Grok models\n- **Ollama** - Local models via Ollama\n\n### Select LLM Model\n\nChoose the specific language model that supports tools to use for AI interactions. Available options vary by provider:\n\n| Provider      | Available Models                                                                                                 |\n| ------------- | ---------------------------------------------------------------------------------------------------------------- |\n| **OpenAI**    | gpt-4.1, gpt-4o, gpt-4o-mini, gpt-4, gpt-3.5-turbo, o1, o1-preview, o1-mini                                      |\n| **Google**    | gemini-2.5-flash-preview-04-17, gemini-2.5-pro-preview-05-06, gemini-2.0-flash, gemini-1.5-flash, gemini-1.5-pro |\n| **Anthropic** | claude-3.7-sonnet, claude-3.5-sonnet, claude-3.5-haiku, claude-3-opus, claude-3-sonnet, claude-3-haiku           |\n| **XAI**       | grok-3, grok-3-fast, grok-3-mini, grok-3-mini-fast                                                               |\n| **Deepseek**  | deepseek-chat                                                                                                    |\n| **Ollama**    | Custom models (check \"Input Model Name\" to manually enter a custom model name)                                   |\n\nFor more details, please visit the documentation [here](https://github.com/geodacenter/openassistant).\n\n### Authentication\n\n**For Cloud Providers (OpenAI, Google, Anthropic, Deepseek, XAI):**\n\n- **Enter Your API Key** - Provide your API key for the selected provider. This is required to authenticate and use the AI services. Your API key is stored locally and used only for making requests to the AI provider.\n\nFor example: visit [https://platform.openai.com/docs/api-reference](https://platform.openai.com/docs/api-reference) to get your OpenAI API key, or visit [https://docs.anthropic.com/en/api/getting-started](https://docs.anthropic.com/en/api/getting-started) to get your Anthropic API key.\n\n**For Ollama (Local):**\n\n- **Base URL** - Enter the base URL for your Ollama instance (default: `http://localhost:11434/api`)\n- **Input Model Name** - Optionally check this to manually enter a model name instead of selecting from the dropdown\n\nSee Ollama documentation for more details: [https://ollama.com/docs](https://ollama.com/docs)\n\n### Temperature\n\nControl the creativity and randomness of AI responses using the temperature slider:\n\n- **0** (left) - More focused and deterministic responses\n- **2** (right) - More creative and varied responses\n- Range: 0.0 to 2.0 with 0.1 increments\n- Default setting is **0** for more precise analytical responses\n\n### Top P\n\nFine-tune the diversity of AI responses with the Top P parameter:\n\n- **0** (left) - More focused on the most likely responses\n- **1** (right) - Consider a broader range of possible responses\n- Range: 0.0 to 1.0 with 0.1 increments\n- Default setting is **1.0** for comprehensive responses\n\n### Mapbox Token (Optional For Route/Isochrone)\n\nIf you plan to use routing or isochrone analysis features, you can optionally provide your Mapbox access token. This enables advanced spatial analysis capabilities like:\n\n- Route e.g. \"What is the driving route from 123 Main St to 456 Main St in San Francisco?\"\n- Isochrone e.g. \"What is the 30-minute travel time isochrone from 123 Main St in San Francisco?\"\n\n## Use AI Assistant\n\nIf the connection to the selected AI provider is successful, you will see the AI Assistant chat interface.\n\n**Welcome Message**: You'll be greeted with \"Hi, I am Kepler.gl AI Assistant!\" confirming that the assistant is ready to help with your spatial analysis tasks.\n\n**Suggested Actions**: The interface displays helpful suggestion buttons to get you started quickly based on the dataset loaded in Kepler.gl. For example, in the screenshot above:\n\n- **Create a Bubble Chart**: \"Visualize the relationship between AGE_2...\" - Helps you create scatter plots and bubble charts to explore relationships between variables\n- **Generate a Histogram**: \"Show the distribution of AGE_LT_21...\" - Assists in creating histograms to understand the distribution of your data\n\n**Prompt Input**: Use the \"Enter a prompt here\" field to type your questions, requests, or commands in natural language.\n\n:::tip\nsome useful prompts:\nwhat datasets are available to use?\nwhat tools are available to use?\n:::\n\n#### Tips for Effective Prompting\n\n- **Be specific**: Include details about the data columns, analysis type, or visualization you want\n- **Use examples**: Reference specific field names or geographic areas in your dataset\n- **Ask follow-up questions**: Build on previous responses to refine your analysis\n- **Experiment**: Try different phrasings if the first attempt doesn't give you the desired result\n\n### Additional Features\n\nThe AI Assistant includes several advanced features to enhance your interaction experience:\n\n#### Screenshot to Ask\n\nClick the **\"Screenshot to Ask\"** button to capture specific areas of your map or interface and ask questions about them.\n\n<img width=\"334\" alt=\"Screenshot 2025-05-30 at 12 48 08 PM\" src=\"https://openassistant-doc.vercel.app/img/screenshot-dark.png\" />\n\n##### How to use screenshot to ask?\n\n**Taking Screenshots**\n\n1. Click the \"Screenshot to Ask\" button in the chat interface\n2. A semi-transparent overlay will appear\n3. Click and drag to select the area you want to capture\n4. Release to complete the capture\n\n**Asking Questions**\n\n1. After capturing, the screenshot will be attached to your next message\n2. Type your question about the captured area\n3. Send your message to get AI assistance\n\n**Managing Screenshots**\n\n1. Click the \"X\" button on the screenshot preview to remove it\n2. Use onRemoveScreenshot callback for programmatic removal\n\n#### Talk to Ask (Voice-to-Text)\n\nThe voice-to-text feature allows users to record their voice, which will be converted to text using the LLM.\n\n<img width=\"334\" alt=\"Screenshot 2025-05-30 at 12 48 08 PM\" src=\"https://openassistant-doc.vercel.app/img/voice-light.png\" />\n\n##### How to use voice-to-text?\n\nWhen using the voice-to-text feature for the first time, users will be prompted to grant microphone access. The browser will display a permission dialog that looks like this:\n\nUsers can choose from three options:\n\n- **Allow while visiting the site**: Grants temporary microphone access\n- **Allow this time**: Grants one-time microphone access\n- **Never allow**: Blocks microphone access\n\nThen, user can start recording their voice. User can stop recording by clicking the stop button or by clicking the microphone icon again. The text will be translated by LLM and displayed in the input box.\n\nThis feature is only available with certain AI providers:\n\n- OpenAI (using Whisper model)\n- Google (using Gemini)\n\nIf using an unsupported provider, you'll receive a \"Method not implemented\" error.\n\n## Next Tutorial\n\nNext Tutorial: [Spatial Data Wrangling](./spatial-data-wrangling.md)"
  },
  {
    "path": "docs/spatial-analysis-tutorial/rate-mapping.md",
    "content": "# Maps for Rates or Proportions\n\nOriginal GeoDa lab by Luc Anselin: https://geodacenter.github.io/workbook/3b_ratemaps/lab3b.html\n\n## Introduction\n\nIn this chapter, we will explore some important concepts that are relevant when mapping rates or proportions. Such data are characterized by an intrinsic variance instability, in that the precision of the rate as an estimate for underlying risk is inversely proportional to the population at risk. Specifically, this implies that rates estimated from small populations (e.g., small rural counties) may have a large standard error. Furthermore, such rate estimates may potentially erroneously suggest the presence of outliers.\n\nIn what follows, we will cover two basic methods to map rates. We will also consider the most commonly used rate smoothing technique, based on the Empirical Bayes approach. Spatially explicit smoothing techniques will be treated after we cover distance-based spatial weights.\n\n### Objectives\n\n- Create thematic maps for rates\n- Assess extreme rate values by means of an excess risk map\n- Understand the principle behind shrinkage estimation or smoothing rates\n- Apply the Empirical Bayes smoothing principle to maps for rates\n\n## Getting Started\n\nIn this chapter, we will use a sample data set with lung cancer data for the 88 counties of the state of Ohio. This is a commonly used example in many texts that cover disease mapping and spatial statistics.2 The data set is also included as one of the Center for Spatial Data Science example data sets and can be downloaded from the [Ohio Lung Cancer Mortality page](https://geodacenter.github.io/data-and-lab/ohiolung/).\n\n- [ohlung.geojson](https://geodacenter.github.io/data-and-lab/data/ohlung.geojson)\n\nWe can load the data by prompting:\n\n```\nCan you load the dataset: https://geodacenter.github.io/data-and-lab/data/ohlung.geojson?\n```\n\n<img width=\"1168\" alt=\"Screenshot 2025-06-19 at 4 04 17 PM\" src=\"https://github.com/user-attachments/assets/c215e865-59cb-443c-9d7a-5ff9975a8801\" />\n\n\n## Choropleth Map for Rates\n\n### Spatially extensive and spatially intensive variables\n\nWe start our discussion of rate maps by illustrating something we should not be doing. This pertains to the important difference between a spatially extensive and a spatially intensive variable. In many applications that use public health data, we typically have access to a count of events, such as the number of cancer cases (a spatially extensive variable), as well as to the relevant population at risk, which allows for the calculation of a rate (a spatially intensive variable).\n\nIn our example, we could consider the number (count) of lung cancer cases by county among white females in Ohio (say, in 1968). The corresponding variable in our data set is LFW68. We can create a box map (hinge 1.5) in the by now familar prompt:\n\n```\nCan you create a box map (hinge 1.5) for the LFW68 variable using color theme BuRd?\n```\n\n<img width=\"1167\" alt=\"Screenshot 2025-06-19 at 4 05 08 PM\" src=\"https://github.com/user-attachments/assets/c77a013b-5c79-48ee-b6a0-b13d28e2a4bf\" />\n\nAnyone familiar with the geography of Ohio will recognize the outliers as the counties with the largest populations, i.e., the metropolitan areas of Cincinnati, Columbus, Cleveland, etc. The labels for these cities in the base layer make this clear. This highlights a major problem with spatially extensive variables like total counts, in that they tend to vary with the size (population) of the areal units. So, everything else being the same, we would expect to have more lung cancer cases in counties with larger populations.\n\nInstead, we opt for a spatially intensive variable, such as the ratio of the number of cases over the population. More formally, if $O_i$ is the number of cancer cases in area $i$, and $P_i$ is the corresponding population at risk (in our example, the total number of white females), then the raw or crude rate or proportion follows as:\n\n$$r_i = \\frac{O_i}{P_i}$$\n\n#### Variance instability\n\nThe crude rate is an estimator for the unknown underlying risk. In our example, that would be the risk of a white woman to be exposed to lung cancer. The crude rate is an unbiased estimator for the risk, which is a desirable property. However, its variance has an undesirable property, namely\n\n$$Var[r_i] = \\frac{\\pi_i(1-\\pi_i)}{P_i}$$\n\nwhere $\\pi_i$ is the unknown underlying risk. This implies that the larger the population of an area ($P_i$ in the denominator), the smaller the variance for the estimator, or, in other words, the greater the precision.\n\nThe flip side of this result is that for areas with sparse populations (small $P_i$), the estimate for the risk will be imprecise (large variance). Moreover, since the population typically varies across the areas under consideration, the precision of each rate will vary as well. This variance instability needs to somehow be reflected in the map, or corrected for, to avoid a spurious representation of the spatial distribution of the underlying risk. This is the main motivation for smoothing rates, to which we return below.\n\nThe AI assistant in kepler.gl provides a tool to calculate the different types of rates:\n\n- [Raw Rate]()\n- [Excess Risk]()\n- [Empirical Bayes Rate]()\n- [Spatial Rate]()\n- [Spatial Empirical Bayes Rate]()\n\n### Raw rate map\n\nFirst, we consider the Raw Rate or crude rate (proportion), the simple ratio of the events (number of lung cancer cases) over the population at risk (the county population). For example, we can use the following prompt:\n\n```\nCan you calculate the raw rates using event variable LFW68 and base variable POPFW68, and create a box map using the raw rates?\n```\n\nWe immediately notice that the counties identified as upper outliers in this screenshot are very different from what the map for the counts suggested in the previous box map.\n\nIf we use split map to compare the two maps, we can see that none of the original count outliers remain as extreme values in the rate map. In fact, some counties are in the lower quartiles (blue color) for the rates.\n\n<img width=\"1167\" alt=\"Screenshot 2025-06-19 at 4 18 20 PM\" src=\"https://github.com/user-attachments/assets/5541865c-b5aa-4baf-92d5-f9b60c6e388f\" />\n\n\n#### Raw rate values in table\n\nFrom the response text, we can see that there is a new dataset 'rates_qxxx' has been created and added in Kepler.gl. If we click on the table icon, we can see the raw rates column:\n\n<img width=\"1166\" alt=\"Screenshot 2025-06-19 at 4 20 20 PM\" src=\"https://github.com/user-attachments/assets/b3ee3dc5-3f6c-4df3-bfcd-b7c8078bf110\" />\n\n### Excess risk map\n\n#### Relative risk\n\nA commonly used notion in demography and public health analysis is the concept of a standardized mortality rate (SMR), sometimes also referred to as relative risk or excess risk. The idea is to compare the observed mortality rate to a national (or regional) standard. More specifically, the observed number of events is compared to the number of events that would be expected had a reference risk been applied.\n\nIn most applications, the reference risk is estimated from the aggregate of all the observations under consideration. For example, if we considered all the counties in Ohio, the reference rate would be the sum of all the events over the sum of all the populations at risk. Note that this average is not the average of the county rates. Instead, it is calculated as the ratio of the total sum of all events over the total sum of all populations at risk (e.g., in our example, all the white female deaths in the state over the state white female population). Formally, this is expressed as:\n\n$$\\tilde{\\pi} = \\frac{\\sum_{i=1}^{n} O_i}{\\sum_{i=1}^{n} P_i},$$\nwhich yields the expected number of events for each area $i$ as:\n$$E_i = \\tilde{\\pi} \\times P_i.$$\nThe relative risk then follows as the ratio of the observed number of events (e.g., cancer cases) over the expected number:\n$$SMR_i = \\frac{O_i}{E_i}.$$\n\n#### Excess risk map\n\nWe can map the standardized rates directly using the following prompt:\n\n```\nCan you calculate the excess risk rates using event variable LFW68 and base variable POPFW68, and create a box map using the excess risk rates?\n```\n\nIn the excess risk map, the legend categories are hard coded, with the blue tones representing counties where the risk is less than the state average (excess risk ratio < 1), and the brown tones corresponding to those counties where the risk is higher than the state average (excess risk ratio > 1).\n\n<img width=\"1168\" alt=\"Screenshot 2025-06-19 at 4 24 09 PM\" src=\"https://github.com/user-attachments/assets/37d4eb07-27be-4576-926c-33588120dff0\" />\n\nIn the map above, we have six counties with an SMR greater than 2 (the brown colored counties), suggesting elevated rates relative to the state average.\n\n## Empirical Bayes (EB) Smoothed Rate Map\n\n### Borrowing strength\n\nAs mentioned in the introduction, rates have an intrinsic variance instability, which may lead to the identification of spurious outliers. In order to correct for this, we can use smoothing approaches (also called shrinkage estimators), which improve on the precision of the crude rate by borrowing strength from the other observations. This idea goes back to the fundamental contributions of James and Stein (the so-called James-Stein paradox), who showed that in some instances biased estimators may have better precision in a mean squared error sense.\n\nThe AI assistant in kepler.gl includes three methods to smooth the rates: an Empirical Bayes approach, a spatial averaging approach, and a combination between the two. We will consider the spatial approaches after we discuss distance-based spatial weights. Here, we focus on the Empirical Bayes (EB) method. First, we provide some formal background on the principles behind smoothing and shrinkage estimators.\n\n#### Bayes Law\n\nThe formal logic behind the idea of smoothing is situated in a Bayesian framework, in which the distribution of a random variable is updated after observing data. The principle behind this is the so-called Bayes Law, which follows from the decomposition of a joint probability (or density) into two conditional probabilities:\n\n$$P[AB] = P[A|B] \\times P[B] = P[B|A] \\times P[A],$$\n\nwhere $A$ and $B$ are random events, and |\n stands for the conditional probability of one event, given a value for the other. The second equality yields the formal expression of Bayes law as:\n\n$$P[A|B] = \\frac{P[B|A] \\times P[A]}{P[B]}.$$\n\nIn most instances in practice, the denominator in this expression can be ignored, and the equality sign is replaced by a proportionality sign:\n\n$$P[A|B] \\propto P[B|A] \\times P[A].$$\n\nIn the context of estimation and inference, the $A$ typically stands for a parameter (or a set of parameters) and $B$ stands for the data. The general strategy is to update what we know about the parameter $A$ a priori (reflected in the prior distribution $P[A]$), after observing the data $B$, to yield a posterior distribution, $P[A|B]$. The link between the prior and posterior distribution is established through the likelihood, $P[B|A]$. Using a more conventional notation with say $\\pi$ as the parameter and $y$ as the observations, this gives:\n\n$$P[\\pi|y] \\propto P[y|\\pi] \\times P[\\pi].$$\n\n#### The Poisson-Gamma model\n\nFor each particular estimation problem, we need to specify distributions for the prior and the likelihood in such a way that a proper posterior distribution results. In the context of rate estimation, the standard approach is to specify a Poisson distribution for the observed count of events (conditional upon the risk parameter), and a Gamma distribution for the prior of the risk parameter $\\pi$. This is referred to as the Poisson-Gamma model.\n\nIn this model, the prior distribution for the (unknown) risk parameter $\\pi$ is $\\text{Gamma}(\\alpha, \\beta)$, where $\\alpha$ and $\\beta$ are the shape and scale parameters of the Gamma distribution. In terms of the more familiar notions of mean and variance, this implies:\n\n$$E[\\pi] = \\frac{\\alpha}{\\beta},$$\n\nand\n\n$$\\text{Var}[\\pi] = \\frac{\\alpha}{\\beta^2}.$$\n\nUsing standard Bayesian principles, the combination of a Gamma prior for the risk parameter with a Poisson distribution for the count of events ($O$) yields the posterior distribution as $\\text{Gamma}(O + \\alpha, P + \\beta)$. The new shape and scale parameters yield the mean and variance of the posterior distribution for the risk parameter as:\n\n$$E[\\pi|O, P] = \\frac{O + \\alpha}{P + \\beta},$$\n\nand\n\n$$\\text{Var}[\\pi|O, P] = \\frac{O + \\alpha}{(P + \\beta)^2}.$$\n\nDifferent values for the $\\alpha$ and $\\beta$ parameters (reflecting more or less precise prior information) will yield smoothed rate estimates from the posterior distribution. In other words, the new risk estimate adjusts the crude rate with parameters from the prior Gamma distribution.\n\n#### The Empirical Bayes approach\n\nIn the Empirical Bayes approach, values for $\\alpha$ and $\\beta$ of the prior Gamma distribution are estimated from the actual data. The smoothed rate is then expressed as a weighted average of the crude rate, say $r_i$, and the prior estimate, say $\\theta$. The latter is estimated as a reference rate, typically the overall statewide average or some other standard.\n\nIn essence, the EB technique consists of computing a weighted average between the raw rate for each county and the state average, with weights proportional to the underlying population at risk. Simply put, small counties (i.e., with a small population at risk) will tend to have their rates adjusted considerably, whereas for larger counties the rates will barely change.\n\nMore formally, the EB estimate for the risk in location $i$ is:\n\n$$\\pi_{EBi} = w_i r_i + (1 - w_i) \\theta.$$\n\nIn this expression, the weights are:\n\n$$w_i = \\frac{\\sigma^2}{\\sigma^2 + \\mu/P_i},$$\n\nwith $P_i$ as the population at risk in area $i$, and $\\mu$ and $\\sigma^2$ as the mean and variance of the prior distribution.\n\nIn the empirical Bayes approach, the mean $\\mu$ and variance $\\sigma^2$ of the prior (which determine the scale and shape parameters of the Gamma distribution) are estimated from the data.\n\nFor $\\mu$ this estimate is simply the reference rate (the same reference used in the computation of the SMR),\n\n$\\sum_{i=1}^{n} O_i / \\sum_{i=1}^{n} P_i$.\n\nThe estimate of the variance is a bit more complex:\n\n$$\\sigma^2 = \\frac{\\sum_{i=1}^{n} P_i (r_i - \\mu)^2}{\\sum_{i=1}^{n} P_i} - \\frac{\\mu \\sum_{i=1}^{n} P_i}{n}.$$\n\nWhile easy to calculate, the estimate for the variance can yield negative values. In such instances, the conventional approach is to set $\\sigma^2$ to zero. As a result, the weight $w_i$ becomes zero, which in essence equates the smoothed rate estimate to the reference rate.\n\n### EB rate map\n\nYou can use the following prompt to create an EB smoothed rate map by using the dataset with raw rates, which we will compare to the EB rates in the next step:\n\n```\nCan you calculate the empirical bayes smoothed rates using event variable LFW68 and base variable POPFW68 from dataset with raw rates, and create a box map using the empirical bayes smoothed rates?\n```\n\n<img width=\"1324\" alt=\"Screenshot 2025-06-23 at 7 24 53 PM\" src=\"https://github.com/user-attachments/assets/471c6e4a-aac5-4df0-b9d5-50cf481de7b9\" />\n\nIn comparison to the box map for the crude rates and the excess rate map, none of the original outliers remain identified as such in the smoothed map. Instead, a new outlier is shown in the very southwestern corner of the state (Hamilton county).\n\nSince many of the original outlier counties have small populations at risk (check in the data table), their EB smoothed rates are quite different (lower) from the original. In contrast, Hamilton county is one of the most populous counties (it contains the city of Cincinnati), so that its raw rate is barely adjusted. Because of that, it percolates to the top of the distribution and becomes an outlier.\n\nTo illustrate this phenomenon, we can systematically select observations in the box plot for the raw rates and compare their position in the box plot for the smoothed rates. This will reveal which observations are affected most.\n\nWe create the box plots in the usual way using the raw rates and the empirical bayes smoothed rates by prompting:\n\n```\nCan you create a box plot for the raw rates and the empirical bayes smoothed rates?\n```\n\n<img width=\"1222\" alt=\"Screenshot 2025-06-23 at 8 11 51 PM\" src=\"https://github.com/user-attachments/assets/7a8f0a1c-9d5e-418d-a089-88af81ad7351\" />\n\nNow, the AI assistant will create two box plots for the raw rates and the empirical bayes smoothed rates. We select the three outliers in the raw rates box plot. The corresponding observations are within the upper quartile for the EB smoothed rates, but well within the fence, and thus no longer outliers after smoothing. We can of course also locate these observations on the map, or any other open views.\n\n<img width=\"1123\" alt=\"Screenshot 2025-06-23 at 8 15 36 PM\" src=\"https://github.com/user-attachments/assets/ff24346c-c804-42e3-9430-b2d2b40097a5\" />\n\nNext, we can carry out the reverse and select the outlier in the box plot for the EB smoothed rate. Its position is around the 75 percentile in the box plot for the crude rate. Also note how the range of the rates has shrunk. Many of the higher crude rates are well below 0.00012 for the EB rate, whereas the value for the EB outlier has barely changed.\n\n<img width=\"1129\" alt=\"Screenshot 2025-06-23 at 8 15 19 PM\" src=\"https://github.com/user-attachments/assets/c38531b0-bc50-4a46-aed8-0681f2535606\" />\n\n\nHere is a screen captured video of the above steps:\n\n![rates_box_plots-1](https://github.com/user-attachments/assets/62a416e8-d54b-4a74-a81a-777cf0cea103)\n"
  },
  {
    "path": "docs/spatial-analysis-tutorial/spatial-data-gis.md",
    "content": "# Spatial Data Wrangling (2) – GIS Operations\n\nOriginal GeoDa lab by Luc Anselin: https://geodacenter.github.io/workbook/01_datawrangling_2/lab1b.html\n\n## Introduction\n\nEven though Kepler.gl is not GIS by design, a range of spatial data manipulation functions are available that perform some specialized GIS operations using the AI assistant. This includes ~~an efficient treatment of projections~~, conversion between points and polygons, the computation of a minimum spanning tree, and spatial aggregation and spatial join through multi-layer support.\n\n## Objectives\n\n- Understand how projections are expressed in a coordinate reference system (CRS)\n- ~~Be able to efficiently change from one projection to another~~\n- Create shape centers (mean center and centroid) for polygons\n- Construct Thiessen polygons for a point layer\n- Compute a minimum spanning tree based on the max-min distance between points\n- Operate on more than one layer\n- Dissolve areal units and compute aggregate values\n- Compute aggregate values based on a common indicator in a table\n- Implement a spatial join to carry out point in polygon operations\n- ~~Visualize the common link between two layers~~\n\n## Preliminaries\n\nWe will continue to illustrate the various operations by means of a series of example data sets, all contained in the [GeoDa Center data set collection](https://geodacenter.github.io/data-and-lab/).\n\nSome of these are the same files used in the previous chapter. For the sake of completeness, we list all the sample data sets used:\n\n- Chicago commpop: population data for 77 Chicago Community Areas in 2000 and 2010 https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson\n\n- Groceries: the location of 148 supermarkets in Chicago in 2015 https://geodacenter.github.io/data-and-lab//data/chicago_sup.geojson\n\n- Natregimes: homicide and socio-economic data for 3085 U.S. counties in 1960-1990 https://geodacenter.github.io/data-and-lab/data/natregimes.geojson\n\n- Home Sales: home sales in King County, WA during 2014-2015 (point data) https://geodacenter.github.io/data-and-lab//data/KingCountyHouseSales2015.geojson\n\n## Projections\n\nSpatial observations need to be georeferenced, i.e., associated with specific geometric objects for which the location is represented in a two-dimensional Cartesian coordinate system. Since all observations originate on the surface of the three-dimensional near-spherical earth, this requires a transformation. The transformation involves two steps that are often confused by non-geographers. The topic is complex and forms the basis for the discipline of geodesy. A detailed treatment is beyond our scope, but a good understanding of the fundamental concepts is important. The classic reference is Snyder (1993), and a recent overview of a range of technical issues is offered in Kessler and Battersby (2019).\n\nThe basic building blocks are degrees latitude and longitude that situate each location with respect to the equator and the Greenwich Meridian (near London, England). Longitude is the horizontal dimension (x), and is measured in degrees East (positive) and West (negative) of Greenwich, ranging from 0 to 180 degrees. Since the U.S. is west of Greenwich, the longitude for U.S. locations is negative. Latitude is the vertical dimension (y), and is measured in degrees North (positive) and South (negative) of the equator, ranging from 0 to 90 degrees. Since the U.S. is in the northern hemisphere, its latitude values will be positive.\n\n### Selecting a projection\n\nIn Kepler.gl, the WGS84 geographic coordinate system is used by default. All data is visualized using latitude and longitude in this system to represent geographic locations accurately. Note: the GeoJSON format uses the WGS84 geographic coordinate system by default. If you have datasets not in the WGS84 geographic coordinate system, you can convert them to WGS84 using other software like GDAL or GeoDa (see [Reprojection](https://geodacenter.github.io/workbook/01_datawrangling_2/lab1b.html)).\n\nHowever, when computing geometric measurements—such as distance, buffer, area, length, and perimeter—the AI assistant automatically uses GeoDa library to convert geographic coordinates (latitude and longitude) into projected coordinates (easting and northing) using the UTM projection with the WGS84 datum. This ensures accurate calculations in meters.\n\nFor Universal Transverse Mercator or UTM, the global map is divided into parallel zones, as shown in Figure below. With each zone corresponds a specific projection that can be used to convert geographic coordinates (latitude and longitude) into projected coordinates (easting and northing).\n\n<img width=\"1042\" alt=\"Screenshot 2025-06-08 at 5 01 00 PM\" src=\"https://geodacenter.github.io/workbook/01_datawrangling_2/pics1b/00_utm_zones.png\" />\nUTM zones for North America (source: GISGeography)\n\n## Converting Between Points and Polygons\n\nSo far, we have represented the geography of the community areas by their actual boundaries. However, this is not the only possible way. We can equally well choose a representative point for each area, such as a mean center or a centroid. In addition, we can create new polygons to represent the community areas as tessellations around those central points, such as Thiessen polygons. The key factor is that all three representations are connected to the same cross-sectional data set. As we will see in later chapters, for some types of analyses it is advantageous to treat the areal units as points, whereas in other situations Thiessen polygons form a useful alternative to dealing with an actual point layer.\n\nThe center point and Thiessen polygon functionality is brought up through the options menu associated with any map view (right click on the map to bring up the menu). We illustrate these features with the projected community area map we just created.\n\n### Mean centers and centroids\n\nThe **mean centers** is obtained as the simple average of the the X and Y coordinates that define the vertices of the polygon. The **centroid** is more complex, and is the actual center of mass of the polygon (image a cardboard cutout of the polygon, the centroid is the central point where a pin would hold up the cutout in a stable equilibrium).\n\nBoth methods can yield strange results when the polygons are highly irregular or in a so-called multi-polygon situation (different polygons associated with the same ID, such as a mainland area and an island belonging to the same county). In those instances the centers can end up being located outside the polygon. Nevertheless, the shape centers are a handy way to convert a polygon layer to a corresponding point layer with the same underlying geography. For example, as we will see in a later chapter, they are used under the hood to calculate distance-based spatial weights for polygons.\n\nTo add centroids of the map layer, you can use the following prompt to create a map layer first:\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson?\n```\n\nAfter the map layer is created, you can add centroids by just prompting:\n\n```\nCan you get the centroids from the chicago commpop dataset?\n```\n\n<img width=\"991\" alt=\"Screenshot 2025-06-08 at 9 33 26 PM\" src=\"https://github.com/user-attachments/assets/00d01966-ceb4-4c63-ba4f-b107efad73e5\" />\n\n### Thiessen polygons\n\nPoint layers can be turned into a so-called tessellation or regular tiling of polygons centered around the points. Thiessen polygons are those polygons that define an area that is closer to the central point than to any other point. In other words, the polygon contains all possible locations that are nearest neighbors to the central point, rather than to any other point in the data set. The polygons are constructed by considering a line perpendicular at the midpoint to a line connecting the central point to its neighbors.\n\nFor example, we can prompt the AI assistant to create Thiessen polygons for the point layer:\n\n```\nCan you create Thiessen polygons for the point layer?\n```\n\n<img width=\"1056\" alt=\"Screenshot 2025-06-09 at 1 57 00 PM\" src=\"https://github.com/user-attachments/assets/dc18c468-4a75-41ea-b724-ffac0d4f82ac\" />\n\n### Minimum spanning tree\n\nA graph is a data structure that consists of nodes and vertices connecting these nodes. In our later discussions, we will often encounter a connectivity graph, which shows the observations that are connected for a given distance band. The connected points are separated by a distance that is smaller than the specified distance band.\n\nAn important graph is the so-called max-min connectivity graph, which shows the connectedness structure among points for the smallest distance band that guarantees that each point has at least one other point it is connected to.\n\nA minimum spanning tree (MST) associated with a graph is a path through the graph that ensures that each node is visited once and which achieves a minimum cost objective. A typical application is to minimize the total distance traveled through the path. The MST is employed in a range of methods, particularly clustering algorithms.\n\nThe Ai assistant uses GeoDa library to construct an MST based on the max-min connectivity graph for any point layer. The construction of the MST employs Prim’s algorithm, which is illustrated in more detail in the [Appendix](https://geodacenter.github.io/workbook/01_datawrangling_2/lab1b.html#appendix).\n\nTo create a minimum spanning tree for the point layer, you can use the following prompt:\n\n```\nCan you create a minimum spanning tree for the point layer?\n```\n\n<img width=\"1040\" alt=\"Screenshot 2025-06-09 at 8 39 46 PM\" src=\"https://github.com/user-attachments/assets/acdde378-05d2-4fce-9eba-c9e6eb3db662\" />\n\n## Aggregation\n\nSpatial data sets often contain identifiers of larger encompassing units, such as states for counties, or census tracts for individual household data. Spatial disolve is a function that allows us to aggregate the smaller units into the larger units.\n\nWe illustrate this functionality with the natregimes dataset using **spatial dissolve** tool. First, load the dataset:\n\n```\nLoad the dataset https://geodacenter.github.io/data-and-lab/data/natregimes.geojson\n```\n\nWhile the observations are for counties, each county also includes a numeric code for the encompassing state in the variable STFIPS. We can use this variable to aggregate the counties into states.\n\n```\nCan you aggregate the counties into states based on the STFIPS variable?\n```\n\nIf you want to aggregate the properties values of the counties into the states, you can use the following prompt:\n\n```\nCan you aggregate the counties into the states based on the STFIPS and aggregate the numeric variables by SUM?\n```\n\n<img width=\"1128\" alt=\"Screenshot 2025-06-11 at 4 06 25 PM\" src=\"https://github.com/user-attachments/assets/194a3888-8932-4177-9c36-ffe59ee1a745\" />\n\n## Multi-Layer Support\n\nKepler.gl supports multi-layer visualization by default. When you load more datasets, the map layers will be automatically added to current map. You can drag-n-drop the layers to reorder them. This multi-layer infrastructure allows for the  calculation of variables for one layer, based on the observations in a different layer.\n\n```\nLoad the dataset https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson\n```\n\n```\nLoad the dataset https://geodacenter.github.io/data-and-lab/data/chicago_sup.geojson\n```\n\n<img width=\"1242\" alt=\"Screenshot 2025-06-11 at 10 39 16 PM\" src=\"https://github.com/user-attachments/assets/19c2d82c-4551-48bb-944f-e23093a0c2eb\" />\n\n\n## Spatial Join\n\nThis is an example of a point in polygon GIS operation. There are two applications of this process. In one, the ID variable (or any other variable) of a spatial area is assigned to each point that is within the area’s boundary. We refer to this as Spatial Assign. The reverse of this process is to count (or otherwise aggregate) the number of points that are within a given area. We refer to this as Spatial Count.\n\nEven though the default application is simply assigning an ID or counting the number of points, more complex assignments and aggregations are possible as well. For example, rather than just counting the point, an aggregate over the points can be computed for a given variable, such as the mean, median, standard deviation, or sum. This process is called **Spatial Join**.\n\n### Spatial assign\n\nWe start the process illustrating the Spatial Assign operation by first loading the Chicago supermarkets point layer, chicago_sup.geojson, followed by the projected community area layer, chicago_commpop.geojson.\n\nThe purpose of the Spatial Assign operation is to assign the community area ID to each supermarket. This is done by using the **Spatial Join** tool.\n\n```\nCan you assign the community name to each supermarket in chicago_sup.geojson?\n```\n\n<img width=\"1240\" alt=\"Screenshot 2025-06-11 at 10 59 37 PM\" src=\"https://github.com/user-attachments/assets/1fd33641-50cf-4eaf-a6bb-366062a7a53c\" />\n\n### Spatial count\n\nThe Spatial Count process works in the reverse order by counting the number of supermarkets in each community area.\n\n```\nCan you spatial join the points to the community areas and count the number of points in each community area?\n```\n\n<img width=\"1244\" alt=\"Screenshot 2025-06-11 at 11 09 37 PM\" src=\"https://github.com/user-attachments/assets/95f2a279-0530-4b0a-a463-8bf32f0095c0\" />\n\n:::tip\nYou can click on the spatialJoin tool to see the details of how spatial join tool has been applied on the datasets.\n:::\n\n\n### Spatial join with aggregation\n\nWe can also carry out an aggregation over the points in each area. For example, when joining the points to the community areas, we can aggregate the points by the mean of the variable `NEAR_DIST` to get e.g. the nearest distance of the supermarkets in each community area.\n\n```\nCan you spatial join the points to the community areas and aggregate the mean value of the variable 'NEAR_DIST' of points in each community area?\n```\n\n<img width=\"1240\" alt=\"Screenshot 2025-06-11 at 11 11 08 PM\" src=\"https://github.com/user-attachments/assets/c28b8627-993c-4408-9592-fd2a3c989893\" />\n\nThe aggregation options that are supported now include: COUNT, SUM, MEAN, MIN, MAX, UNIQUE.\n\n\n## ~~Linked Multi-Layers~~\n"
  },
  {
    "path": "docs/spatial-analysis-tutorial/spatial-data-wrangling.md",
    "content": "# Spatial Data Wrangling\n\nOriginal GeoDa lab by Luc Anselin: https://geodacenter.github.io/workbook/01_datawrangling_1/lab1a.html\n\n## Introduction\n\nIn this chapter, we tackle the topic of data wrangling, i.e., the process of getting data from its raw input into a form that is amenable for analysis. This is often considered to be the most time consuming part of a data science project, taking as much as 80% of the effort ([Dasu and Johnson 2003](https://onlinelibrary.wiley.com/doi/book/10.1002/0471448354)). However, with Kepler.gl AI Assistant, we can now achieve the same data manipulation goals with significantly less effort compared to traditional approaches using software like GeoDa.\n\nWhile data wrangling has increasingly evolved into a field of its own, with a growing number of operations turning into automatic procedures embedded into software ([Rattenbury et al. 2017](https://www.oreilly.com/library/view/principles-of-data/9781491938911/)), the integration of AI assistance represents the next evolution in making these processes even more intuitive and efficient. A detailed discussion of the underlying technical implementations is beyond our scope, but we'll provide practical examples of how to harness this technology for your spatial analysis needs.\n\n### Objectives\n\n- Load a spatial layer from a range of formats\n- Create a point layer from coordinates in a table\n- Create a grid layer\n- Create new variables\n- Variable standardization\n- Merging tables\n\n## Preliminaries\n\nWe will illustrate the basic data input operations by means of a series of example data sets, all contained in the GeoDa Center data set collection.\n\nSpecifically, we will use files from the following data sets:\n\n- Chicago commpop: population data for 77 Chicago Community Areas in 2000 and 2010\n- Groceries: the location of 148 supermarkets in Chicago in 2015\n- Natregimes: homicide and socio-economic data for 3085 U.S. counties in 1960-1990\n- SanFran Crime: San Francisco crime incidents in 2012 (point data)\n\nYou can download these data sets from the [GeoDa Center data and lab](https://geodacenter.github.io/data-and-lab). In this tutorial, we will use the following urls:\n\n- Chicago commonpop: https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson\n- Groceries: https://geodacenter.github.io/data-and-lab//data/chicago_sup.geojson\n- Natregimes: https://geodacenter.github.io/data-and-lab/data/natregimes.geojson\n- SanFran Crime: https://geodacenter.github.io/data-and-lab/data/SFcartheft_july12.geojson\n\nA GeoJSON file is simple text and can easily be read by humans. As shown in Figure below, we see how the locational information is combined with the attributes. After some header information follows a list of features. Each of these contains properties, of which the first set consists of the different variable names with the associated values, just as would be the case in any standard data table. However, the final item refers to the geometry. It includes the type, here a MultiPolygon, followed by a list of x-y coordinates. In this fashion, the spatial information is integrated with the attribute information.\n\n<img width=\"1087\" alt=\"Screenshot 2025-05-30 at 4 06 36 PM\" src=\"https://geodacenter.github.io/workbook/01_datawrangling_1/pics1a/00_geojson.png\" />\n\n### Spatial Data and GIS files\n\nIf you are not familiar with spatial data and GIS files, you can refer to the [Spatial Data and GIS files](https://geodacenter.github.io/workbook/01_datawrangling_1/lab1a.html) chapter.\n\n### Polygon Layer\n\nMost of the analyses covered in these chapters will pertain to areal units, or polygon layers. In Kepler.gl, you can drag and drop the e.g. chicago_commonpop.geojson file to load the data and create a polygon layer automatically (see [Add Data to your Map](https://docs.kepler.gl/docs/user-guides/j-get-started)).\n\nHere we will use the AI Assistant to load the data and create a polygon layer.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson\n?\n```\n<img width=\"1087\" alt=\"Screenshot 2025-05-30 at 4 06 36 PM\" src=\"https://github.com/user-attachments/assets/f0ac871e-a321-47e8-9e7c-e635d16cc250\" />\n\n\n### Point Layer\n\nPoint layer GIS files are loaded in the same fashion. For example, we can load the chicago_sup.geojson file to create a point layer.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/chicago_sup.geojson?\n```\n\n<img width=\"1079\" alt=\"Screenshot 2025-05-30 at 4 35 31 PM\" src=\"https://github.com/user-attachments/assets/0b85a3c6-45b7-4660-aa63-b0cc8147c6f1\" />\n\n### Tabular files\n\nTabular files e.g. the comma separated value (csv) files are also supported. For example, we can load the commpopulation.csv file, which doesn't contain any geometric data.\n\n```\nCan you load a dataset from https://geodacenter.github.io/data-and-lab/data/commpopulation.csv?\n```\n\n<img width=\"1205\" alt=\"Screenshot 2025-05-30 at 6 22 22 PM\" src=\"https://github.com/user-attachments/assets/04593e4e-ff28-4551-8eb9-82442bc5ece6\" />\n\nIf the csv file contains coordinates, the AI Assistant will automatically create a point layer.\n\nHere we convert the chicago_sup.dbf, which is used in [GeoDa workbook](https://geodacenter.github.io/workbook/01_datawrangling_1/lab1a.html), to chicago_sup.csv file:\n\n```\nCan you create a map layerfrom https://geodacenter.github.io/data-and-lab/data/chicago_sup.csv?\n```\n\n<img width=\"1092\" alt=\"Screenshot 2025-05-31 at 10 45 07 PM\" src=\"https://github.com/user-attachments/assets/0a3059db-bc76-427d-8866-885f21bded71\" />\n\nAs you can see in the screenshot above, the AI Assistant confirms with you what type of map layer you want to create. Then, it performs fetching the data from the URL. However, it is not sure about which columns should be used for latitude and longitude coordinates since the columns in the csv file are \"XCoord\" and \"YCoord\" not named as \"latitude\" and \"longitude\".\n\nIn the GeoDa workbook, you will need to specify the latitude/longitude columns in the configuration UI. However, with AI assistant, you can simply ask it to guess the columns based on the data. Once you confirm that you want to guess the columns, the AI Assistant successfully identifies the appropriate columns and creates the point layer, showing the grocery store locations as points on the map.\n\n\n## Grid\n\nGrid layers are useful to aggregate counts of point locations in order to calculate point pattern statistics, such as quadrat counts. In Kepler.gl, users can create a grid layer from a dataset easily. See [Kepler.gl Grid Layer](https://docs.kepler.gl/docs/user-guides/c-types-of-layers/d-grid) for more details.\n\nIn this tutorial, we are using the Ai assistant to create a grid layer from either the current map view (the map bounds defind by northwest and southweast points), or use the bounding box of an available data source.\n\n```\nCan you create a 20x20 grid layer over the chicago_commpop areas?\n```\n\n<img width=\"1330\" alt=\"Screenshot 2025-06-04 at 3 55 13 PM\" src=\"https://github.com/user-attachments/assets/3ac2b9b7-8aa9-44c4-9f91-b85089c63782\" />\n\nSince there is a spatial join tool in the AI Assistant, we can also use it to filter the grids that intersect with chicago commpop polygons. The result will be more useful for a spatial data analysis.\n\n```\nCan you filter the grids that intersect with chicago commpop polygons?\n```\n\n<img width=\"1333\" alt=\"Screenshot 2025-06-04 at 4 38 07 PM\" src=\"https://github.com/user-attachments/assets/94a2da06-13c6-4e39-8691-f4a3e78fafcb\" />\n\n## Table Manipulations\n\nA range of variable transformations are supported through the query tool in the AI Assistant. To illustrate these, we will use the Natregimes sample data set. After loading the natregimes.shp file, we obtain the base map of 3085 U.S. counties, shown in the screenshot below.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/natregimes.geojson?\n```\n\n<img width=\"1147\" alt=\"Screenshot 2025-06-06 at 3 49 01 PM\" src=\"https://github.com/user-attachments/assets/892a8219-b400-4b76-ac02-adb82cc2e14b\" />\n\nThe table of the dataset can be opened by using the 'Show Data Table' button when mouse over the dataset name in Kepler.gl layer panel.\n\n<img width=\"1145\" alt=\"Screenshot 2025-06-06 at 3 49 55 PM\" src=\"https://github.com/user-attachments/assets/fa80088b-59d7-4821-ae12-1b7c063fac90\" />\n\nThis is a view only table. You can not e.g. edit the data, or add/remove a column in this table. The Foursqure Studio, which is developed based on Kepler.gl, has a more powerful table editor (see docs [here](https://docs.foursquare.com/analytics-products/docs/data)). We hope these features will be upstream back to Kepler.gl in the future.\n\nHowever, using different AI Assistant tools, like query, standardizeVariable, etc., you can achieve the same goal of manipulating the data in the table. The limitation is that you can not directly edit the data in the table, but you can create a new dataset with the manipulated data.\n\n```\nCan you create a new variable called \"HR60_Z\" and assign the z-value of the variable HR60 to it?\n```\n\n### Variable properties\n\n### Change variable properties\n\nOne of the most used initial transformations pertains to getting the data into the right format. Often, observation identifiers, such as the FIPS code used by the U.S. census, are recorded as character or string variables, not in a numeric format. In order to use these variables effectively, we need to convert them to a different format. For example:\n\n```\nCan you change the column NOSOUTH to integer type and save it in a new dataset?\n```\n\n<img width=\"1146\" alt=\"Screenshot 2025-06-06 at 4 13 37 PM\" src=\"https://github.com/user-attachments/assets/df1ccc5b-e4f7-43ec-a120-95b135046c5f\" />\n\n:::info\nThe AI Assistant calls the `tableTool` to achieve the goal. You can click to expand the `tableTool` to see the details of the tool. This tool uses a SQL query  and a in-memory duckdb database to manipulate the data in the table e.g. `SELECT _geojson, ..., CAST(NOSOUTH AS VARCHAR) AS NOSOUTH, ... FROM natregimes_geojson_123456`. The result arrow object is a duckdb table, which can be used to create a new dataset in Kepler.gl.\n:::\n\n### Other variable operations\n\nThe other variable operations, like add a variable, delete a variable or rename a variable, should also be supported via the `tableTool`.\n\n## Calculator\n\nThe calculator functionality is to create a new variable with:\n- special functions\n- univariate and bivariate operations\n- spatial lag\n- rates\n- data/time operations\n\nSpatial Lag and Rates are advanced functions that are discussed separately in later chapters.\n\nTo illustrate the calculator functionality, we return to the Chicago community area sample data and load the commpopulation.csv file. This file only contains the population totals.\n\n```\nCan you load a dataset from https://geodacenter.github.io/data-and-lab/data/commpopulation.csv?\n```\n\n<img width=\"1205\" alt=\"Screenshot 2025-06-06 at 4 20 05 PM\" src=\"https://github.com/user-attachments/assets/04593e4e-ff28-4551-8eb9-82442bc5ece6\" />\n\n##### Special\n\nThe three Special functions are:\n- **NORMAL**: generate random numbers with normal distribution\n- **UNIFORM RANDOM**: generate random numbers with uniform distribution\n- **ENUMERATE**: generate a sequence of numbers, which is especially useful to retain the order of observations after sorting on a given variable\n\nFor example:\n\n```\nCan you create a new variable called \"RANDOM\" and assign random numbers with normal distribution to it?\n```\n\n<img width=\"1147\" alt=\"Screenshot 2025-06-06 at 4 35 10 PM\" src=\"https://github.com/user-attachments/assets/da92eb4d-3a40-4c19-b62a-64db6645c702\" />\n\n> As you can see from the screenshot, the AI Assistant tries to use `tableTool` to add a new column \"RANDOM\" using the Box-Muller transform with SQL query: `SELECT community, NID, POP2010, POP2000, (sqrt(-2 * log(random())) * cos(2 * pi() * random())) AS RANDOM FROM commpopulation_246810)`.\n\n:::note\nAI Assistant can make mistakes by trying to generate complex SQL query to add variable. One way to improve the result is to add SQL examples with your prompt, e.g. `can you create a variable using [random()](https://duckdb.org/docs/stable/sql/functions/numeric.html#random) function?`\n:::\n\n##### Univariate\n\nIn Kepler.gl AI Assistant, there are six straightforward univariate transformations:\n- **NEGATIVE**: changing the sign\n- **INVERSE**: taking the inverse\n- **SQUARE ROOT**: taking the square root\n- **LOG (base 10/e)**: carrying out a log transformation\n- **ASSIGN**: assign a new variable to be set equal to any other variable, or to a constant (the typical use)\n- **SHUFFLE**: shuffle the values of a variable, which is an efficient way to implement spatial randomness, i.e., an allocation of values to locations, but where the location itself does not matter (any location is equally likely to receive a given observation value).\n\nFor example:\n\n```\nCan you create a new variable called \"POP2010_INV\" and assign the inverse of the variable POP2010 to it?\n```\n\n<img width=\"1146\" alt=\"Screenshot 2025-06-06 at 7 29 18 PM\" src=\"https://github.com/user-attachments/assets/171c03f4-17df-43fc-a279-8fafa8598d71\" />\n\nSince the `tableTool` uses DuckDB to manipulate the data, it can support more univariate transformations. For example, you can use functions like pow(), sqrt(), abs() etc., to transform the variable. For more details, you can refer to the [DuckDB documentation](https://duckdb.org/docs/stable/sql/functions/numeric).\n\n##### Variable Standardization\n\nThe univariate operations also include five types of variable standardization:\n- **STANDARDIZED (Z)**\n- **DEVIATION FROM MEAN**\n- **STANDARDIZED (MAD)**\n- **RANGE ADJUST**\n- **RANGE STANDARDIZED**\n\nThe most commonly used is undoubtedly **STANDARDIZED (Z)**. This converts the specified variable such that its mean is zero and variance one, i.e., it creates a z-value as\n\n$$\nz = \\frac{x - \\mu}{\\sigma}\n$$\n\nwhere $x$ is the original variable, $\\mu$ is the mean, and $\\sigma$ is the standard deviation.\n\n```\nCan you apply standardization (Z-score) to the variable POP2010?\n```\n\nA subset of this standardization is **DEVIATION FROM MEAN**, which only computes the numerator of the z-transformation.\n\nAn alternative standardization is **STANDARDIZED (MAD)**, which uses the mean absolute deviation (MAD) as the denominator in the standardization. This is preferred in some of the clustering literature, since it diminishes the effect of outliers on the standard deviation (see, for example, the illustration in Kaufman and Rousseeuw 2005, 8–9). The mean absolute deviation for a variable $x$ is computed as:\n\n$$\nmad = \\frac{1}{n} \\sum_{i} |x_i - \\bar{x}|,\n$$\n\ni.e., the average of the absolute deviations between an observation and the mean for that variable. The estimate for mad takes the place of $\\sigma(x)$ in the denominator of the standardization expression.\n\n```\nCan you apply STANDARDIZED (MAD) to the variable POP2010?\n```\n\nTwo additional transformations that are based on the range of the observations, i.e., the difference between the maximum and minimum. These are the **RANGE ADJUST** and the **RANGE STANDARDIZE**.\n\nRANGE ADJUST divides each value by the range of observations:\n\n$$\nr_a = \\frac{x_i - x_{max}}{x_{max} - x_{min}}\n$$\n\nWhile RANGE ADJUST simply rescales observations in function of their range, RANGE STANDARDIZE turns them into a value between zero (for the minimum) and one (for the maximum):\n\n$$\nr_s = \\frac{x_i - x_{min}}{x_{max} - x_{min}}\n$$\n\nIn original GeoDa lab, the standardization is limited to one variable at a time, which is not very efficient. However, with the AI Assistant, you can apply the standardization to multiple variables at once in Kepler.gl.\n\n```\nCan you apply standardization (Z-score) to the variables POP2010 and POP2000?\n```\n\n<img width=\"1146\" alt=\"Screenshot 2025-06-07 at 11 49 29 AM\" src=\"https://github.com/user-attachments/assets/b1ea7556-cf4c-498c-aa18-dab35203cd4a\" />\n\n##### Bivariate or Multivariate\n\nThe bivariate functionality includes all classic algebraic operations. For example, to compute the population change for the Chicago community areas between 2010 and 2000, you can create a new variable `POPCH = POP2010 - POP2000`:\n\n```\nCan you create a new variable POPCH = POP2010 - POP2000?\n```\n\n<img width=\"1146\" alt=\"Screenshot 2025-06-07 at 11 45 44 AM\" src=\"https://github.com/user-attachments/assets/ceb5ccfa-471c-43ed-b33d-30533e957c51\" />\n\n:::tip\nSince the AI Assistant can use SQL query to manipulate the data, you can also use it to create a new variable with multiple variables and operations. One example is the **NORMAL** function in the previous example: `SELECT community, NID, POP2010, POP2000, (sqrt(-2 * log(random())) * cos(2 * pi() * random())) AS RANDOM FROM commpopulation_246810)`.\n:::\n\n##### Date and time\n\nTo illustrate these operations, we will use the SanFran Crime data set from the sample collection, which is one of the few sample data sets that contains a date stamp.\n\nWe load the sf_cartheft dataset from the Crime Events subdirectory of the sample data set. This data set contains the locations of 3384 car thefts in San Francisco between July and December 2012.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/SFcartheft_july12.geojson?\n```\n\n<img width=\"1146\" alt=\"Screenshot 2025-06-07 at 11 52 11 AM\" src=\"https://github.com/user-attachments/assets/80325909-5676-49ab-a335-a768e4eee805\" />\n\nWe bring up the data table and note the variable Date in the seventh column. We can ask the AI Assistant to create a new variable called \"YEAR\" and assign the year of the Date to it.\n\n```\nCan you create a new variable called \"YEAR\" and assign the year of the Date to it?\n```\n<img width=\"1030\" alt=\"Screenshot 2025-06-07 at 10 05 26 PM\" src=\"https://github.com/user-attachments/assets/e6a5a6aa-b67c-4c68-ab15-d5b1da5dac73\" />\n\n:::note\nYou will see the AI assistant tried to call tableTools several times to complete the task. If the tool returns error, the AI assistant will try to fix it and recall the tool. The best practice is providing some extra information or example with your prompt e.g. please use date_part() function.\n:::\n\n## Merging tables\n\nAn important operation on tables is the ability to Merge new variables into an existing data set. We illustrate this with the Chicago community area population data.\n\nFirst load the chicago_commpop.geojson file to create a polygon layer.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/chicago_commpop.geojson?\n```\n\nA number of important parameters need to be selected. First is the datasource from which the data will be merged. In our example, we have chosen commpopulation.csv. Even though this contains the same information as we already have, we select it to illustrate the principles behind the merging operation.\n\n```\nCan you load a dataset from https://geodacenter.github.io/data-and-lab/data/commpopulation.csv?\n```\n\nThere are two ways to merge two datasets: horizontal merge and vertical merge. The default method is Merge horizontally, but Stack (vertically) is supported as well. The latter operation is used to add observations to an existing data set.\n\nBest practice to carry out a merging operation is to select a key, i.e., a variable that contains (numeric) values that match the observations in both data sets.\n\n```\nCan you merge the table commpopulation.csv to chicago_commpop.geojson using NID as key?\n```\n\n<img width=\"1042\" alt=\"Screenshot 2025-06-08 at 4 37 00 PM\" src=\"https://github.com/user-attachments/assets/a54a7101-958c-4485-8d2d-45a805172448\" />\n\nSince we are using SQL to merge the two datasets, the key column is required to merge horizontally.\n\nIf you want to specify which columns you want to merge, you can mention them in the prompt:\n\n```\nCan you merge the table commpopulation.csv to chicago_commpop.geojson using NID as key and merge the column 'community'?\n\n## Queries\n\nWith the AI Assistant, you can easily query the dataset by just prompting.\n\nLoad the sf_cartheft.geojson file to create a point layer.\n\n```\nCan you create a map layer from https://geodacenter.github.io/data-and-lab/data/sf_cartheft.geojson?\n```\n\nThen, you can query the dataset by just prompting.\n\n```\nCan you query the sf_cartheft dataset in October?\n```\n\n<img width=\"1038\" alt=\"Screenshot 2025-06-08 at 4 52 41 PM\" src=\"https://github.com/user-attachments/assets/f015581c-47aa-4d9c-9599-03041af56a5a\" />\n"
  },
  {
    "path": "docs/table-of-contents.json",
    "content": "{\n  \"id\": \"table-of-contents\",\n  \"chapters\": [\n    {\n      \"title\": \"User Guides\",\n      \"chapters\": [\n        {\n          \"title\": \"Overview\",\n          \"entries\": [\n            {\"entry\": \"docs\"},\n            {\"entry\": \"docs/user-guides/a-introduction\"},\n            {\"entry\": \"docs/user-guides/b-kepler-gl-workflow/a-add-data-to-the-map\"},\n\n            {\"entry\": \"docs/user-guides/c-types-of-layers\"},\n            {\"entry\": \"docs/user-guides/d-layer-attributes\"},\n            {\"entry\": \"docs/user-guides/e-filters\"},\n            {\"entry\": \"docs/user-guides/f-map-styles\"},\n            {\"entry\": \"docs/user-guides/g-interactions\"},\n            {\"entry\": \"docs/user-guides/h-playback\"},\n            {\"entry\": \"docs/user-guides/i-FAQ\"},\n            {\"entry\": \"docs/user-guides/j-get-started\"},\n            {\"entry\": \"docs/user-guides/k-save-and-export\"},\n            {\"entry\": \"docs/user-guides/l-color-attributes\"},\n            {\"entry\": \"docs/user-guides/m-map-settings\"}\n          ]\n        },\n        {\n          \"title\": \"Workflow\",\n          \"entries\": [\n            {\"entry\": \"docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/a-adding-data-layers\"},\n            {\"entry\": \"docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/b-create-a-layer\"},\n            {\"entry\": \"docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/c-hide-edit-and-delete-layers\"},\n            {\"entry\": \"docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/d-blend-and-rearrange-layers\"}\n          ]\n        },\n        {\n          \"title\": \"Layer Types\",\n          \"entries\": [\n            {\"entry\": \"docs/user-guides/c-types-of-layers/a-point\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/b-arc\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/c-line\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/d-grid\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/e-polygon\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/f-cluster\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/g-icon\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/h-hexbin\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/i-heatmap\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/j-h3\"},\n            {\"entry\": \"docs/user-guides/c-types-of-layers/k.trip\"}\n          ]\n        }\n      ]\n    },\n\n\n\n    {\n      \"title\": \"API Reference\",\n      \"chapters\": [\n        {\n          \"title\": \"Overview\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/overview\"}\n          ]\n        },\n        {\n          \"title\": \"Actions\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/actions/overview\"},\n            {\"entry\": \"docs/api-reference/actions/actions\"}\n          ]\n        },\n        {\n          \"title\": \"Components\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/components/overview\"}\n          ]\n        },\n        {\n          \"title\": \"Processors\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/processors/overview\"},\n            {\"entry\": \"docs/api-reference/processors/processors\"}\n          ]\n        },\n        {\n          \"title\": \"Reducers\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/reducers/overview\"},\n            {\"entry\": \"docs/api-reference/reducers/combine\"},\n            {\"entry\": \"docs/api-reference/reducers/map-state\"},\n            {\"entry\": \"docs/api-reference/reducers/map-style\"},\n            {\"entry\": \"docs/api-reference/reducers/reducers\"},\n            {\"entry\": \"docs/api-reference/reducers/ui-state\"},\n            {\"entry\": \"docs/api-reference/reducers/vis-state\"}\n          ]\n        },\n        {\n          \"title\": \"Schemas\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/schemas/overview\"}\n          ]\n        },\n        {\n          \"title\": \"Advanced Usage\",\n          \"entries\": [\n            {\"entry\": \"docs/api-reference/advanced-usages/custom-initial-state\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/custom-mapbox-host\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/forward-actions\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/reducer-plugin\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/replace-ui-component\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/saving-loading-w-schema\"},\n            {\"entry\": \"docs/api-reference/advanced-usages/using-updaters\"}\n          ]\n        }\n      ]\n    },\n    {\n      \"title\": \"kepler.gl-jupyter\",\n      \"entries\": [\n        {\"entry\": \"docs/keplergl-jupyter/user-guide\"}\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/user-guides/README.md",
    "content": "# User guides\n\nKepler.gl is designed for geospatial data analysis. It allows technical and non-technical audiences to visualize trends in a city or region. With Kepler.gl, you can…\n\nVisualize a large amount of location data in your browser.\nPlayback geo-temporal trends over time.\nExplore, filter, and deeply engage with location data to gain insight\n\nSee the sample maps in the demo app for more examples.\n\n![Kepler.gl sample map](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image11.png \"Kepler.gl sample map\")\n\nThis guide will teach you how to perform data analysis in Kepler.gl by adding data to a map, creating layers, adding filters, and more.\n\n## Table of contents:\n\n#### [Get Started](./j-get-started.md)\n\n#### [The kepler.gl workflow](./b-kepler-gl-workflow/README.md)\n* [Add data to the map](./b-kepler-gl-workflow/a-add-data-to-the-map.md)\n* [Adding data layers](./b-kepler-gl-workflow/b-add-data-layers/a-adding-data-layers.md)\n* [Create a layer](./b-kepler-gl-workflow/b-add-data-layers/b-create-a-layer.md)\n* [Hide, edit and delete layers](./b-kepler-gl-workflow/b-add-data-layers/c-hide-edit-and-delete-layers.md)\n* [Blend and rearrange layers](./b-kepler-gl-workflow/b-add-data-layers/d-blend-and-rearrange-layers.md)\n\n#### [Layers](./c-types-of-layers/README.md)\n\n* [Point](./c-types-of-layers/a-point.md)\n* [Arc](./c-types-of-layers/b-arc.md)\n* [Line](./c-types-of-layers/c-line.md)\n* [Grid](./c-types-of-layers/d-grid.md)\n* [Polygon](./c-types-of-layers/e-polygon.md)\n* [Cluster](./c-types-of-layers/f-cluster.md)\n* [Icon](./c-types-of-layers/g-icon.md)\n* [Hexbin](./c-types-of-layers/h-hexbin.md)\n* [Heatmap](./c-types-of-layers/i-heatmap.md)\n* [H3](./c-types-of-layers/j-h3.md)\n* [Trip](./c-types-of-layers/k-trip.md)\n* [S2](./c-types-of-layers/l-s2.md)\n* [Vector Tile Layer](./c-types-of-layers/m-vector-tile-layer.md)\n* [Raster Tile Layer](./c-types-of-layers/n-raster-tile-layer.md)\n* [WMS Layer](./c-types-of-layers/o-wms-layer.md)\n\n#### [Layer attributes](./d-layer-attributes.md)\n\n#### [Color Palettes](./l-color-attributes.md)\n\n#### [Filters](./e-filters.md)\n\n#### [Map styles](./f-map-styles.md#map-styles.md)\n* [Base map styles](./f-map-styles.md#base-map-styles.md)\n* [Map layers](./f-map-styles.md#map-layers.md)\n* [Custom styles](./f-map-styles.md#custom-styles.md)\n\n#### [Interactions](./g-interactions.md)\n* [Tooltips](./g-interactions.md#tooltips)\n* [Brushing](./g-interactions.md#brushing)\n* [Display Coordinates](./g-interactions.md#display-coordinates)\n\n#### [Map Settings](./m-map-settings.md)\n* [View maps in 3d](./m-map-settings.md#view-maps-in-3d)\n* [Display legend](./m-map-settings.md#display-legend)\n* [Split maps](./m-map-settings.md#split-maps)\n\n#### [SQL Data Explorer](./sql-data-explorer.md)\n\n#### [AI Assistant](./ai-assistant.md)\n\n#### [Time playback](./h-playback.md)\n\n#### [Save and export](./k-save-and-export.md)\n\n#### [FAQ](./i-FAQ.md)\n"
  },
  {
    "path": "docs/user-guides/ai-assistant.md",
    "content": "# AI Assistant\n\n> Note: This feature is currently undergoing development. Stay tuned for updates!\n\nThe AI assistant in Kepler.gl is not only a LLM based chatbot, it is engineered to help users with creating maps and analyzing their spatial data. The AI assistant provides a new way that allows users to interact with the data and the map in a more natural and creative way.\n\n![AI Assistant](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant.png 'AI Assistant')\n\nYour conversations can be advanced: `Create a geojson layer using population with quantile color scale and update its colors inspired by Van Gogh's Starry Night`. Or: `Check the correlation between temperature and precipitation in the dataset`.\n\n## Supported Providers\n\nThe following providers and models are currently supported.\n\n> Note: we are working on feature to allow users specify their own providers, models and base URL.\n\n| **Provider**       | **Models**                                                                                                                                          |\n| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **OpenAI**         | `gpt-4o`, `gpt-4o-mini`, `gpt-3.5-turbo`, `gpt-3.5-turbo-0125`, `o1-mini`, `o1-preview`                                                             |\n| **Google**         | `gemini-2.0-flash-exp`, `gemini-1.5-flash`, `gemini-1.5-pro`, `gemini-1.0-pro`                                                                      |\n| **Ollama** (local) | `deepseek-r1`, `phi4`, `phi3.5`, `qwen2.5-coder`, `qwen2`, `qawa`, `llava`, `mistral`, `gemma2`, `llama3.3`, `llama3.2`, `llama3.1`, `llama3.1:70b` |\n| **DeepSeek**       | `deepseek-chat`, `deepseek-reasoner`                                                                                                                |\n\n## Parameters\n\nBefore initating the AI assistant, the following parameters are required. A temperature and Top P are selected for you; however, you will need to provide an API key for a remote model, and a base URL for local models.\n\n| **Parameter**   | **Description**                                                                                                                        |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |\n| **Temperature** | Controls the randomness of the model's output. Range: `0-2`, Default: `1`. Lower values make responses more focused and deterministic. |\n| **Top P**       | Controls the diversity of the output by limiting the cumulative probability of token selection. Range: `0-1`, Default: `0.8`.          |\n| **API Key**     | Required for OpenAI (`gpt` models) and Google (`gemini` models).                                                                       |\n| **Base URL**    | Required for Ollama (`localhost:11434` or another specified base URL).                                                                 |\n\n## Built-in Features\n\n- Take screenshot to ask [[Demo]](https://geoda.ai/img/highlight-screenshot.mp4)\n- Talk to ask [[Demo]](https://geoda.ai/img/highlight-ai-talk.mp4)\n\n### Take a Screenshot to Ask\n\nThis feature enables users to capture a screenshot anywhere within kepler.gl application and ask questions about the screenshot.\n\nFor example, users can take a screenshot of the map (or partial of the map) and ask questions about the map e.g. _`how many counties are in this screenshot`_, or take a screenshot of the layer configuration panel and ask questions about how to use it, e.g. _`How can I adjust the opacity`_. Users can even take a screenshot of the plots in the chat panel and ask questions about the plots e.g. _`Can you give me a summary of the plot?`_.\n\n![Screenshot to ask](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant-screenshot.png 'Screenshot to ask')\n\n#### How to use this feature?\n\n1. Click the \"Screenshot to Ask\" button in the chat interface\n2. A semi-transparent overlay will appear\n3. Click and drag to select the area you want to capture\n4. Release to complete the capture\n5. The screenshot will be displayed in the chat interface\n6. You can click the x button on the top right corner of the screenshot to delete the screenshot\n\n### Talk to Ask\n\nThis feature enables users to \"talk\" to the AI assistant. After clicking the \"Talk to Ask\" button, users can start talking using microphone. When clicking the same button again, the AI assistant will stop listening and send the transcript to the input box.\n\nWhen using the voice-to-text feature for the first time, users will be prompted to grant microphone access. The browser will display a permission dialog that looks like this:\n\n![Talk to ask](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant-talk-to-ask.png 'Talk to ask')\n\nAfter granting access, users can start talking to the AI assistant.\n\n> Note: for OpenAI, the whisper model is used to transcribe the audio. Google's gemini models are multimodal models, so the audio-to-text feature is supported by default.\n\n## Map and Data Analysis Assistant\n\nUsing LLM function tools, kepler.gl's AI assistant can help transform users's prompt into actions that executed inside kepler.gl. This allows users to interact with kepler.gl in a more natural and creative way.\n\n### Why use LLM function tools?\n\nFunction calling enables the AI Assistant to perform specialized tasks that LLMs cannot handle directly, such as complex calculations, data analysis, visualization generation, and integration with external services. This allows the assistant to execute specific operations within kepler.gl while maintaining natural language interaction with users.\n\n### Is my data secure?\n\nYes, the data you used in kepler.gl stays within the browser, and will **never** be sent to the LLM. Using function tools, we can engineer the AI assistant to use only the meta data for function calling, e.g. the name of the dataset, the name of the layer, the name of the variables, etc. Here is a process diagram to show how the AI assistant works:\n\n![AI Assistant Diagram](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant-diagram.png 'AI Assistant Diagram')\n\n### Actions available to use\n\nThe current supported actions are:\n\n- System:\n  - Show dataset/layer/variable info.\n  - Load data from url.\n  - Change the basemap style.\n- Mapping and Data Analysis:\n  - Create map layer from variable.\n  - Query the data using SQL (coming soon)\n  - Classify the data of a variable.\n    - Quantile / Natural (Jenks) Breaks / Percentile / Box / Standard Deviation\n- Plots\n  - Create histogram.\n  - Create scatter plot with regression line.\n- Spatial Analysis:\n  - Spatial join two datasets (e.g. count points and polygons).\n\n> Note: to see our plan to add more actions to the AI assistant, please check out this [Kepler.gl RFC](<[https://github.com/kepler-gl/kepler.gl/issues/4689](https://github.com/keplergl/kepler.gl/discussions/2843)>) and the [integration of GeoDa with Kepler.gl](<[text](https://github.com/GeoDaCenter/openassistant/wiki/Integration-Kepler.gl---GeoDaLib)>)\n\nUsers can simply describe what they want to accomplish in plain text, and the AI Assistant will invoke the appropriate function with the correct parameters that your application can execute. The LLM will identify if the question can be answered by using one or multiple function tools, and the LLM will ask the user to confirm the parameters of each function call.\n\n> _Can you classify the data of the variable \"population\" using natural breaks and create a geojson layer using the breaks with colors inspired by Van Gogh's Starry Night._\n\n#### Plots\n\nThe plots created by the AI assistant are interactive, and can be used to explore the data.\n\nIn each plot, there is a small toolbar on the top right corner, which contains three buttons:\n\n- **Box Select**: Select the data in the plot.\n- **Keep Selection**: Keep the selected data in the plot.\n- **Clear Selection**: Clear the selected data in the plot.\n\nOne can click the **Box Select** button first. Then, start selecting the data in the plot by left clicking the mouse and dragging the mouse to select the data. The selected data will be highlighted in the plot and also be highlighted in the map.\n\n##### Scatter Plot\n\nFor scatter plot, the AI assistant will create a scatter plot with a regression line by default. If users select points in the plot, there will be 3 different regression lines created: one for all points, one for the selected points, and one for the unselected points.\n\nIf users click the 'expand' button on the top right corner of the plot, the plot will be expanded to a floating modal dialog with more details of the regressions shown in a tablt.\n\nThe regression details include:\n\n- R-squared\n- Slope\n- Intercept\n- P-value\n- Standard Error\n- Chow test for selected and unselected regression lines\n\nThis scatter plot can help users to explore the relationship between two variables, and explore the heterogeneity of the data by selecting different points.\n\n![Scatterplot](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-ai-assistant-scatterplot.png 'Scatterplot')\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/README.md",
    "content": "# Kepler.gl workflow\n\n## Table of contents\n* [Add data to the map](./a-add-data-to-the-map.md)\n* [Adding data layers](./b-add-data-layers/a-adding-data-layers.md)\n* [Create a layer](./b-add-data-layers/b-create-a-layer.md)\n* [Hide, edit and delete layers](./b-add-data-layers/c-hide-edit-and-delete-layers.md)\n* [Blend and rearrange layers](./b-add-data-layers/d-blend-and-rearrange-layers.md)\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/a-add-data-to-the-map.md",
    "content": "# Add Data to the Map\n\n## Ways to Add Data\n- Open kepler.gl/demo. You should see the following prompt:\n\n![Add data to the map pop up](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image42.png \"Add data to the map pop up\")\n\n**kepler.gl is a pure client side app. Data lives only in your machine/browser.  No information or maps is sent back up to our server.**\n\n- Choose one of three ways to add data to your map\n\n|   |   |\n|---|---|\n| **Local files**  | <span style=\"font-weight:normal\">Upload CSV / GeoJSON files. Because data is only stored in your browser, there is a **250mb** limit on how much data Chrome allows you to upload into a browser. For datasets larger than **250mb** you should directly load them from a remote URL. See below.<span>  |\n| **From URL**  | Directly load data or map json by pasting a remote URL. You can link it to CSV | JSON | Kepler.gl config json. Make sure the url contains the file extension. CORS policy must be defined on your custom url domain. |\n| **Sample data**  | Load one of kepler.gl’s sample datasets. The sample map data and config are directly loaded from  [kepler.gl-data github][kepler.gl-data-github] repo  |\n\n\n## Supported Projection Coordinate System\nkepler.gl only supports **[Web Mercator]([https://en.wikipedia.org/wiki/Web_Mercator_projection) EPSG:3857 -- WGS84**.\n\nGeometry coordinates should be presented with a geographic coordinate reference system, using the WGS84 datum, and with longitude and latitude units of decimal degrees.\n\n## Supported File Formats\n - [CSV](#csv)\n - [GeoJSON](#geojson)\n - [GeoArrow](#geoarrow)\n - [kepler.gl Json](#keplergl-json)\n\n\n### CSV\n\nCSV file should contain header row and multiple columns. Each row should be 1 feature. Each column should contain only 1 data type, based on which kepler.gl will use to create layers and filters.\n\n| id | point_latitude | point_longitude | value | start_time\n|---|---|---|---|---\n| a | 31.2384 | -127.30948 | 5 | 2019-08-01 12:00\n| b | 31.2311 | -127.30231 | 11 | 2019-08-01 12:05\n| c | 31.2334 | -127.30238 | 9 | 2019-08-01 11:55\n\n\n#### 1. Data type detection\n\nBecause CSV file content is uploaded as strings, kepler.gl will attempt to detect column data type by parsing a sample of data in each column. kepler.gl can detect\n\n| type | data\n|---|---\n|**_`boolean`_** | `True`, `False`|\n|**_`date`_** | `2019-01-01`|\n|**_`geojson`_** | **WKT string:** `POLYGON ((-74.158 40.835, -74.148 40.830, -74.151 40.832, -74.158 40.835))`, **or GeoJson String** `{\"type\":\"Polygon\",\"coordinates\":[[[-74.158,40.835],[-74.157,40.839],[-74.148,40.830],[-74.150,40.833],[-74.151,40.832],[-74.158,40.835]]]}` |\n|**_`integer`_** | `1`, `2`, `3`|\n|**_`real`_** | `-74.158`, `40.832`|\n|**_`string`_** | `hello`, `world` |\n|**_`timestamp`_** | `2018-09-01 00:00`, `1570306147`, `1570306147000`|\n\n**Note:** Make sure to clean up values such as `N/A`, `Null`, `\\N`. If your column contains mixed type, kepler.gl will treat it as **_`string`_** to be safe.\n\n#### 2. Layer detection based on column names\n\nkepler.gl will auto detect layer, if the column names follows certain naming convention. kepler.gl creates a point layer if  your CSV has columns that are named `<name>_lat` and `<name>_lng` or `<name>_latitude` and `<name>_longitude`, or `<name>_lat` and `<name>_lon`.\n\n| layer | auto create layer from column names\n|---|---\n|**Point** | Point layer names have to be in pairs, and **ends with** `<foo>lat, <foo>lng`; `<foo>latitude, <foo>longitude`; `<foo>lat, <foo>lon`|\n|**Arc**| If two points layers are detected, one arc layer will be created |\n|**Icon**| A column named `icon` is present|\n|**H3**| A column named `h3_id` or `hexagon_id` is present |\n|**Polygon**| A column content contains `geojson` data types. Acceptable formats include [Well-Known Text](http://www.postgis.net/docs/ST_AsText.html) e.g. `POLYGON ((-74.158 40.835, -74.148 40.830, -74.151 40.832, -74.158 40.835))` and [GeoJSON Geometry](https://tools.ietf.org/html/rfc7946#appendix-A). e.g. `{\"type\":\"LineString\",\"coordinates\":[[100.0, 0.0],[101.0, 1.0]]}`\n\n#### 3. Embed Geometries in CSV\nGeometries (Polygons, Points, LindStrings etc) can be embedded into CSV as a `GeoJSON` or `WKT` formatted string.\n\n##### `GeoJSON` String\nUse the geometry of a Feature, which includes type and coordinates. It should be a JSON formatted string, with the `\"` corrected escaped. More info on [String escape in csv](https://gpdb.docs.pivotal.io/43250/admin_guide/load/topics/g-escaping-in-csv-formatted-files.html)\n\nExample data.csv with GeoJSON\n```txt\nid,geometry\n1,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.158491,40.835947],[-74.157914,40.83902]]]}\"\n```\n\n##### `WKT`String\n[The Well-Known Text (WKT)](https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format) representation of geometry values is designed for exchanging geometry data in ASCII form.\n\nExample data.csv with WKT\n```txt\nid,geometry\n1,\"POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))\"\n```\n\n### GeoJSON\n\n#### 1. Feature types\n\n- kepler.gl accepts GeoJSON formatted JSON that contains a single [Feature](https://tools.ietf.org/html/rfc7946#section-3.2) object or a [FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) object. kepler.gl creates one **`Polygon`** layer per GeoJSON file.\n\n  - A single GeoJSON Feature:\n\n  ```json\n    {\n      \"type\": \"Feature\",\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          [\n            [-10.0, -10.0],\n            [10.0, -10.0],\n            [10.0, 10.0],\n            [-10.0, -10.0]\n          ]\n        ]\n      },\n      \"properties\": {\n        \"name\": \"foo\"\n      }\n    }\n  ```\n\n  - GeoJSON Feature Collection.\n  ```json\n  {\n    \"type\": \"FeatureCollection\",\n    \"features\": [{\n        \"type\": \"Feature\",\n        \"geometry\": {\n            \"type\": \"Point\",\n            \"coordinates\": [102.0, 0.5]\n        },\n        \"properties\": {\n            \"prop0\": \"value0\"\n        }\n    }, {\n        \"type\": \"Feature\",\n        \"geometry\": {\n            \"type\": \"LineString\",\n            \"coordinates\": [\n                [102.0, 0.0],\n                [103.0, 1.0],\n                [104.0, 0.0],\n                [105.0, 1.0]\n            ]\n        },\n        \"properties\": {\n          \"prop0\": \"value0\"\n        }\n    }]\n  }\n  ```\n\n  kepler.gl will render all features in one `Polygon` layer even though they have different geometry types. Acceptable geometry types are\n\n  - [Point](https://tools.ietf.org/html/rfc7946#section-3.1.2)\n  - [MultiPoint](https://tools.ietf.org/html/rfc7946#section-3.1.3)\n  - [LineString](https://tools.ietf.org/html/rfc7946#section-3.1.4)\n  - [MultiLineString](https://tools.ietf.org/html/rfc7946#section-3.1.5)\n  - [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6)\n  - [MultiPolygon](https://tools.ietf.org/html/rfc7946#section-3.1.7).\n\n  Feature properties will be parsed as columns. You can apply color, filters based on them.\n\n#### 2. Auto styling\nkepler.gl will read styles from GeoJSON files. If you are a GeoJSON expert, you can add style declarations to feature properties. kepler.gl will use the declarations to automatically style your feature. The acceptable style properties are:\n  ```json\n  \"properties\": {\n    \"lineColor\": [130, 154, 227],\n    \"lineWidth\": 0.5,\n    \"fillColor\": [255, 0, 0],\n    \"radius\": 1 // Point\n  }\n  ```\n\n- See an example below:\n```json\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [{\n      \"type\": \"Feature\",\n      \"geometry\": {\n        \"type\": \"LineString\",\n        \"coordinates\": [\n          [-105.1547889, 39.9862516],\n          [-105.1547167, 39.9862691]\n        ]\n      },\n      \"properties\": {\n        \"id\": \"a1398a11-d1ce-421c-bf66-a456ff525de9\",\n        \"lineColor\": [130, 154, 227],\n        \"lineWidth\": 0.1\n      }\n  }]\n}\n```\n\n### GeoArrow\n\n[GeoArrow](https://geoarrow.org/) file, a binary data format which can be visualized with the [PolygonLayer](https://docs.kepler.gl/docs/user-guides/c-types-of-layers/e-polygon).\n\n### kepler.gl JSON\n\nJSON file exported from kepler.gl. See \"[Export Map as JSON](https://docs.kepler.gl/docs/user-guides/k-save-and-export#export-map-as-json)\".\n\n### Load Map Using URL\n\nYou load data or map through custom URL. It currently supports URLs with file extension of `csv`, `json` and `kepler.gl.json`\n\nIn addition, this also by-passes 250mb file upload size limit which allows you to upload larger file to Kepler.\n\n![Load Map Using URL](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/a-load-map-using-url.gif \"Load Map Using URL\")\n\n### Use Kepler.gl’s Sample Maps\n\nThe sample maps are a great option for new users to explore Kepler.gl and get a feel for how it works.\n\n1. At the initial load prompt select “Try sample data” in the top right corner.\n\n![Try sample data pop up](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image2.png \"Try sample data pop up\")\n\n2. Choose from the options to load the sample map and explore the configurations applied.\n\n![Choose sample data pop up](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image5.png \"Choose sample data pop up\")\n\n### Add multiple datasets\n\nTo add additional datasets to your map:\n\n1. Click __Add More Data__ in the top right corner.\n\n![Add more data](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image22.png \"Add more data\")\n\n2. Choose one of the options above: upload a JSON/CSV file, or use Kepler.gl’s sample data.\n\n3. Repeat as needed. There is no limit on the number of datasets you can add. However, adding too many might cause its performance to suffer.\n\n[Back to table of contents](../README.md)\n\n\n[kepler.gl-data-github]: https://github.com/keplergl/kepler.gl-data\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/a-adding-data-layers.md",
    "content": "# Adding Data Layers\n\nThe term \"Layer\" refers to a layer of data visualization. For example, you might add a point layer to visualize all the instances where taxi trips began, as in the map of New York City below. \n\n![Sample NYC Map](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image43.png \"Sample NYC Map\")\n\nEach blue dot represents the point (latitude and longitude). Layers work like paint—you can build up multiple layers to change the appearance of the canvas. You might create a second point layer to show trip drop-off locations. The map then looks like this:\n\n![Sample NYC Map with colors](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image6.png \"Sample NYC Map with colors\")\n\nLearn more about the [types of layers](../../c-types-of-layers/README.md) available in Kepler.gl.\n\n[Back to table of contents](../../README.md)\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/b-create-a-layer.md",
    "content": "# Create a Layer\n\n1. Click the Data Layers icon in the left navigation bar.\n![Add data layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image39.png \"Add data layer\")\n\n2. The Layers panel displays a list of existing layers and the name of the dataset the layers belong to (Sample Trip Data, in the example below). To create a new layer, click __Add Layer__ at the bottom of the menu.\n![Add layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image16.png \"Add layer\")\n\n3. If your map contains multiple datasets, you’ll be asked to select the data source for your new layer.\n![Select data source for layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image28.png \"Select data source for layer\")\n\n4. Select a layer type. Read about the different [types of layers].\n![Select layer type](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image41.png \"Select layer type\")\n\n5. Fill in the required columns and adjust the optional settings if desired. \n6. Collapse the layer settings menu when finished.\n\n[Back to table of contents](../../README.md)\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/c-hide-edit-and-delete-layers.md",
    "content": "# Hide, Edit and Delete Layers\n\nEach layer has its own tab in the __Data Layers__ menu:\n![Hide, edit and delete layers](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image8.png \"Hide, edit and delete layers\")\n\nClick the __down arrow__ to open up the settings menu for that layer. Click the __trashcan__ to delete a layer. Click the __eye__ to toggle show/hide.\n\n__Note__: The colored line on the left of each layer tab represents what dataset that layer belongs to.\n\n[Back to table of contents](../../README.md)\n"
  },
  {
    "path": "docs/user-guides/b-kepler-gl-workflow/b-add-data-layers/d-blend-and-rearrange-layers.md",
    "content": "# Blend and Rearrange Layers\n<p align=\"center\">\n  <b>Rearrange layers by dragging and dropping them in the Layers panel. The layers at the top of the list will be displayed in the foreground of the map. \n</b>\n  <br><br>\n  <img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image44.png\" alt=\"Rearrange layers\"/>\n  \n  <br><br>\n</p>\n\n<p align=\"center\">\n  <b>Blend layers by selecting an option from the dropdown at the bottom of the Layers panel.</b>\n  <br><br>\n  <img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image10.png\" alt=\"Blend layers\"/>\n  <br><br>\n  <b>There are three different ways to blend layers: Normal, Additive, and Subtractive.</b>\n  <br><br>\n</p>\n\n\n## Normal Blending\nNormal layer blending does not alter the color values of overlapping data points. \n![Normal blending](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image19.png \"Normal blending\")\n\n## Additive Blending\nAdditive blending adds the color values for overlapping data points. It makes layers, and particularly areas of high density, easier to visualize on a dark-colored map.\n![Additive blending](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image34.png \"Additive blending\")\n\n## Subtractive Blending\nSubtractive layer blending does not alter the color values of overlapping data points. \n![Subtractive blending](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image26.png \"Subtractive blending\")\n\n[Back to table of contents](../../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/README.md",
    "content": "# Types of Layers\n\n## Single Feature Layers\n\nSingle feature layers render 1 feature\n\n## Point\n\n![Point layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image34.png 'Point layer')\n\nPoint layers draw points for a given event or object based on its location - latitude and longitude.\n\n## Arc\n\n![Arc layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-arc-layer.png 'Arc layer')\n\nArc layers draw an arc between two points. They’re useful for visualizing the distance between two points as well as comparing distances in 3D. Note that arc layers don’t show routes between points, but simply the distance between the two points. The tallest arc represents the greatest distance.\n\nTo draw arcs, your dataset must contain the latitude and longitude of two different points for each arc.\n\nLayer Attributes: Color/ Color Based On, Opacity, Stroke Width/ Stroke Based On, High Precision Rendering\n\n## Line\n\n![Line layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-line-layer.png 'Line layer')\n\nLine layers are the 2D version of arc layers. Both draw a line between two points to represent distance, but in a line layer, the drawing lies flat on the map.\n\nLayer Attributes: Color, Stroke, High Precision Rendering\n\n## Hexbin\n\n![Hexbin layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-hexbin-layer.png 'Hexbin layer')\n\nHexbin aggregates points into hexagons. The counts can be represented through color and/or height.\n\nLayer Attributes: Color/ Color Based On, Filter by Count Percentile, Opacity, Hexagon Radius (km), Coverage (Radius), Enable Height, Elevation Scale/ Height Based On, High Precision Rendering\n\n## Heatmap\n\n![Heatmap layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-heat-map.png 'Heatmap layer')\n\nHeatmap is a graphical representation of data in which data values are represented as colors.\n\nLayer Attributes: Color, Opacity, Radius, Weight\n\n## Cluster\n\n![Cluster layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-cluster-layer.png 'Cluster layer')\n\nCluster layers visualize aggregated data based on a geospatial radius.\n\nLayer Attributes: Color, Cluster Size\n\n## Icon\n\n![Icon layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image33.png 'Icon layer')\n\nIcon layers are a type of point layer. They allow you to differentiate between points by assigning icons to points based on a field. For example, you might use icons to differentiate between types of venues and points of interest.\n\nLayer Attributes: Color, Radius, Label, High Precision Rendering\n\nTo see the icon menu, create a new icon layer and click how to draw an icon layer:\n\n![How to Draw Icon Layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image38.png 'How to Draw Icon Layer')\n\n## Grid\n\n![Grid layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image21.png 'Grid layer')\n![3D Grid layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-grid-layer.png '3D Grid layer')\n\nGrids layers are similar to heatmaps. They show the density of points. They provide visual discrepancy in a map where multiple heatmap-style layers are present.\n\nLayer Attributes: Color, Radius, Height, High Precision Rendering\n\n## GeoJSON\n\n![GeoJSON layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image20.png 'GeoJSON layer')\n![Polygon geoJSON layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image7.png 'Polygon geoJSON layer')\n\nGeoJSON layers can display either paths, polygons or points. For example, a path GeoJSON layer can display data like trip routes. A polygon GeoJSON layer is essentially a [choropleth](https://en.wikipedia.org/wiki/Choropleth_map) layer and works best for rendering geofences. To add a GeoJSON layer, your dataset must contain geometry data.\n\n## H3\n\n![H3 layer - contour](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/c-h3-layer.png 'H3 layer')\n\nH3 layers visualize spatial data using [H3 Hexagonal Hierarchical Spatial Index](https://eng.uber.com/h3/).\n\nTo use H3 layer, you need a `hex_id` in your dataset, which can be generated using [h3-js](https://github.com/uber/h3-js) from latitude, longitude and resolution.\n\n## S2 Layer\n\n![S2 Layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-s2.png 'S2 layer')\n\nTo use S2 layer, you need to assign a column containing S2 tokens.\n\n## Vector Tile Layer\n\n![Vector Tile layer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/documentation/layer-types/vector-tile.png 'Vector Tile Layer')\n\nVector Tile Layer makes it possible to visualize very large datasets through MVTs (Mapbox Vector Tiles). To optimize performance, the layer only loads and renders tiles containing features that are visible within the current viewport.\n\nSupported URL templates:\n\n- MVT (https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.mvt?access_token=your-mapbox-acceess-token)\n- pmtiles (https://your-cdn/filename.pmtiles)\n\nFor step-by-step instructions, see [Vector Tile Layer — How to add](./m-vector-tile-layer.md).\n\n## Raster Tile Layer (experimental)\n\n![Raster Tile layer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/documentation/layer-types/raster-tile.png 'Raster Tile Layer')\n\nRaster layers are used to show satellite and aerial imagery. They allow you to work interactively directly with massive, image collections stored in .pmtiles (in raster format) or Cloud Optimized GeoTIFF format.\n\nSupported URL templates:\n\n- Users can reference remote **.pmtiles files in raster format** for raster layers by supplying a direct link to the file.\n\n- **Cloud-Optimized GeoTIFFs (COG)** can also be used in raster layers by providing standardized Spatio-Temporal Asset Catalog (STAC) metadata.\n\n  - The metadata file must be a valid _STAC Item_ or _STAC Collection_, version 1.0.0 or higher.\n  - Raster data referenced in STAC assets should be Cloud-Optimized GeoTIFFs and need to be publicly accessible via HTTPS.\n  - STAC item and collections _must have Electro-Optical and Raster extensions_, and at least one asset must have both eo:bands and raster:bands information. common_name must be provided in eo:bands and data_type must be provided in raster:bands.\n  - To use COGs with STAC metadata, you must run your own raster tile server (e.g., TiTiler). Example implementation: [kepler-raster-server](https://github.com/igorDykhta/kepler-raster-server).\n\n  Examples of raster .pmtiles:\n\n  - Mount Whitney - https://pmtiles.io/usgs-mt-whitney-8-15-webp-512.pmtiles\n  - Swiss historical - https://public-bucket-for-tests.s3.us-east-1.amazonaws.com/historic-swis-18xx.pmtiles\n\n  Examples of supported STAC Items:\n\n  - Bangladesh rivers — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/planet-skysat-opendata.json\n  - Antarctica ice — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/sentinel-2-l2a.json\n  - Kiribati island — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/stac-example.json\n\n  Examples of supported STAC Collections:\n\n  - sentinel-2-l1c — https://earth-search.aws.element84.com/v1/collections/sentinel-2-l1c\n  - modis-09A1-061 — https://planetarycomputer.microsoft.com/api/stac/v1/collections/modis-09A1-061\n  - landsat-c2-l1 — https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-c2-l1\n\nFor step-by-step instructions, see [Raster Tile Layer — How to add](./n-raster-tile-layer.md).\n\n## WMS Layer (experimental)\n\n![WMS layer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/documentation/layer-types/wms.png 'WMS Layer')\n\n- Web Map Service (WMS) layers render raster tiles from OGC WMS servers.\n- This feature is experimental and disabled by default. To try it, enable `enableWMSLayer: true` in the application configuration.\n- When enabled, add a WMS service via the Tilesets modal by providing the service URL and selecting a named layer. Feature info on click is supported for queryable layers.\n\nExamples of supported WMS Tiles:\n\n- https://ows.terrestris.de/osm/service\n- https://opengeo.ncep.noaa.gov/geoserver/conus/conus_cref_qcd/ows\n- https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi\n- https://geo.stadt-muenster.de/mapserv/starkregen_serv\n\nFor step-by-step instructions, see [WMS Layer — How to add](./o-wms-layer.md).\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/a-point.md",
    "content": "# Point\n\n![Point layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image34.png \"Point layer\")\n\nPoint layers draw points for a given event or object.\n\n__Layer Attributes__\n- Basic\n  - Columns:\n    - Latitude\n    - Longitude\n    - Altitude (optional)\n- Fill\n  - Enable fill - enabled by default\n  - Single color / color based on\n  - Color scale\n  - Opacity\n- Outline\n  - Enable outline\n  - Single color / color based on\n  - Color scale\n  - Stroke width\n- Radius\n  - Single radius / radius based on\n  - Fixed radius to meter\n- Text,\n - Font Size\n - Font Color\n - Text Anchor\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/b-arc.md",
    "content": "# Arc\n\n![Arc layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image37.png \"Arc layer\")\n\nArc layers draw an arc between two points. They’re useful for visualizing the distance between two points as well as comparing distances in 3D. Note that arc layers don’t show routes between points, but simply the distance between the two points. The tallest arc represents the greatest distance.\n\nTo draw arcs, your dataset must contain the latitude and longitude of two different points for each arc.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/c-line.md",
    "content": "# Line\n\n![Line layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image3.png \"Line layer\")\n\nLine layers are the 2D version of arc layers. Both draw a line between two points to represent distance, but in a line layer, the drawing lies flat on the map.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/d-grid.md",
    "content": "# Grid\n\n![Grid layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image21.png \"Grid layer\")\n![3D Grid layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image17.png \"3D Grid layer\")\n\nGrids layers are similar to heatmaps. They show the density of points. They provide visual discrepancy in a map where multiple heatmap-style layers are present.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/e-polygon.md",
    "content": "# Polygon\n\nPolygon layer can display all geometry types defined by [RFC 7946 (GeoJSON)](https://tools.ietf.org/html/rfc7946): `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`. \n\nYou can load a GeoJSON file (with a single [`Feature`](https://tools.ietf.org/html/rfc7946#section-3.2) or a [`FeatureCollection`](https://tools.ietf.org/html/rfc7946#section-3.3)) or a GeoArrow file.\n\n\n![GeoJSON layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image20.png \"GeoJSON layer\")\n\n![Polygon layer - contour](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/layers-polygon-contour.png \"Polygon layer\")\n\n![Polygon geoJSON layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image7.png \"Polygon geoJSON layer\")\n\n![Polygon layer - buildings](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/layers-polygon-buildings.png \"Grid layer\")\n\nA path GeoJSON layer can display data like trip routes or contours. Stroke color can be set with a numerical field.\n\nA polygon GeoJSON layer is essentially a [choropleth](https://en.wikipedia.org/wiki/Choropleth_map) layer and works best for rendering geofences. Fill color or height can be set with a numerical field. For example, it can display population by census tracts.\n\nTo add a polygon layer, your dataset must contain geometry data.\n\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/f-cluster.md",
    "content": "# Cluster\n\n![Cluster layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image46.png \"Cluster layer\")\n\nCluster layers visualize aggregated data based on a geospatial radius.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/g-icon.md",
    "content": "# Icon\n\n![Icon layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image33.png \"Icon layer\")\n\nIcon layers are a type of point layer. They allow you to differentiate between points by assigning icons to points based on a field. For example, you might use icons to differentiate between types of venues and points of interest.\n\nTo see the icon menu, create a new icon layer and click how to draw an icon layer:\n\n![How to Draw Icon Layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image38.png \"How to Draw Icon Layer\")\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/h-hexbin.md",
    "content": "# Hexbin\n\n![Hexbin layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/layers-hexbin.png \"Hexbin layer\")\n\nHexbin layers are similar to grid layers. They display distributions of aggregate metrics such as point count within each hexbin, average/max/min/median/sum of a numerical field, or mode/unique count of a string field. Both the color and height dimensions can encode data. Users can adjust the hexagon radius and the space between hexbins.\n\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/i-heatmap.md",
    "content": "# Heatmap\n\n![Heatmap layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/layers-heat-map.png \"Heatmap layer\")\n\nHeatmap layers describe the intensity of data at geographical points through a colored overlap. The intensity can be weighted by a numerical field.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/j-h3.md",
    "content": "# H3\n\n![H3 layer - contour](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/layers-h3.png \"H3 layer\")\n\nH3 layers visualize spatial data using [H3 Hexagonal Hierarchical Spatial Index](https://eng.uber.com/h3/).\n\nTo use H3 layer, you need a `hex_id` or `hexagon_id` in your dataset, which can be generated using [h3-js](https://github.com/uber/h3-js) from latitude, longitude and resolution.\n\n### Naming Convention\nkepler.gl __auto generates__ H3 layer from column: `hex_id`, `hexagon_id`\n\n### Sample dataset:\nhex_id | value |\n|----------|:------:|\n89283082c2fffff | 64 |\n8928308288fffff | 73 |\n89283082c07ffff | 65 |\n89283082817ffff | 74 |\n89283082c3bffff | 66 |\n89283082883ffff | 76 |\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/k-trip.md",
    "content": "# Trip layer\n\nTrip layer can display animated path.\n\n![Trip layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-trip.gif 'Trip layer')\n\n### How to use trip layer to animate path\n\n**Data format**\nCurrently trip layer support a special `geoJSON` format where the coordinate `linestring` has a 4th element denoting timestamp.\n\nIn order to animate the path, the `geoJSON` data needs to contain `LineString` in its features' geometry, and the coordinates in the `LineString` need to have 4 elements in the format of `[longitude, latitude, altitude, timestamp]`, with the last element being a timestamp. Valid timestamp formats include unix in seconds such as `1564184363` or in milliseconds such as `1564184363000`.\n\n**Sample data**\n\n```\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"properties\": {\n        \"vendor\":  \"A\"\n      },\n      \"geometry\": {\n        \"type\": \"LineString\",\n        \"coordinates\": [\n          [-74.20986, 40.81773, 0, 1564184363],\n          [-74.20987, 40.81765, 0, 1564184396],\n          [-74.20998, 40.81746, 0, 1564184409]\n        ]\n      }\n    }\n  ]\n}\n```\n\n**Note** Support for more data formats such as csv will be added in future releases.\n\n**Layer attributes**\n\n- Color\n\n  The path can be colored by an attribute from the properties.\n\n  <img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-trip-attribute-colors.png\" width=\"256\" title=\"Color Attribute\">\n\n- Stroke Width\n\n  Stroke width can be set by an attribute from the properties.\n\n- Trail Length\n\n  Trail length determines how long it takes for a path to completely fade out in seconds. This can be adjusted using the slider. Short trail length retains few historical locations while long trail length retain more and show a longer tail.\n\n- Animation speed\n\n  Animation speed can be adjusted using the animation control at the bottom.\n\n**When there are multiple layers**\n\n- Multiple trip layers\n  When you add multiple trip layers, the time range from all the layers will be combined and the animation control will span the entire time range of those layers.\n\n- Multiple layers containing trip layer and other layers\n  Other static layers can be added besides the trip layers. Upon hiding the trip layer, its animation control will also hide, giving place to the filter control.\n\n**Export**\n\nTo export an animated map, you can use a screen recording or gif capture tool. You can also export the map as an interactive HTML to open in the browser.\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/l-s2.md",
    "content": "# S2 Layer\n\n![S2 Layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-s2.png 'S2 layer')\n\nTo use S2 layer, you need to assign a column containing S2 tokens.\n\n### Naming Convention\n\nKepler.gl **auto generates** S2 layer from column named `s2` or `s2_token`\n\n### Simple Dataset\n\n| token    |        value        |\n| -------- | :-----------------: |\n| 80858004 | 0.5979242952642347  |\n| 8085800c | 0.5446256069712141  |\n| 80858014 | 0.1187171597109975  |\n| 8085801c | 0.2859146314037557  |\n| 80858024 | 0.19549012367504126 |\n| 80858034 | 0.3373452974230604  |\n| 8085803c | 0.9218176408795662  |\n| 80858044 | 0.23470692356446143 |\n| 8085804c | 0.1580509670379684  |\n| 80858054 | 0.15992745628743954 |\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/m-vector-tile-layer.md",
    "content": "# Vector Tile Layer — How to add\n\nFollow these steps to add a Vector Tile layer (MVT or pmtiles):\n\n1. Open Add Data → Tilesets.\n2. Select Vector Tile tileset type.\n3. Enter a tileset URL:\n   - Mapbox Vector Tiles (MVT) template, e.g.\n     `https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.mvt?access_token=YOUR_TOKEN`\n   - pmtiles URL, e.g. `https://your-cdn/filename.pmtiles`\n4. Click Add. A new Vector Tile layer will appear in the Layers panel.\n5. Style the layer (color, stroke, height, dynamic color) in the Layers panel.\n\nNotes:\n\n- Use Vector Tile layer for vector data in MVT/pmtiles formats. For raster pmtiles or COG/STAC imagery, use Raster Tile layer.\n\nExample Vector Tile sources:\n\n- US population — https://4sq-studio-public.s3.us-west-2.amazonaws.com/vector-tile/cb_v2/{z}/{x}/{y}.pbf\n- New Zealand buildings — https://r2-public.protomaps.com/protomaps-sample-datasets/nz-buildings-v3.pmtiles\n- US Zip Codes - https://r2-public.protomaps.com/protomaps-sample-datasets/cb_2018_us_zcta510_500k.pmtiles\n- Railways — https://4sq-studio-public.s3.us-west-2.amazonaws.com/pmtiles-test/161727fe-7952-4e57-aa05-850b3086b0b2.pmtiles\n\n[Back to layer overview](./README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/n-raster-tile-layer.md",
    "content": "# Raster Tile Layer — How to add (experimental)\n\nUse Raster Tile layer to visualize satellite/aerial imagery from raster pmtiles or COGs via STAC metadata.\n\n1. Open Add Data → Tilesets.\n2. Select Raster Tile tileset type.\n3. Paste URL to the tileset:\n   - pmtiles (raster format): provide a direct HTTPS URL to a .pmtiles file containing raster imagery. Raster pmtiles don't require dedicated raster tile servers, unless you want to use elevation meshes.\n   - STAC Item/Collection (COGs): provide a HTTPS URL to a STAC Item or Collection (v1.0.0+ with EO + Raster extensions). For this option you need to provide a [compatible raster tile server](https://github.com/igorDykhta/kepler-raster-server).\n4. Click Add.\n5. Style band selection and opacity as needed in Layers panel.\n\nImportant notes for COGs via STAC:\n\n- The STAC Item/Collection must include EO and Raster extensions with `eo:bands` and `raster:bands` .\n- COG assets must be publicly accessible over HTTPS.\n- You must run your own raster tile server (e.g., TiTiler). Example implementation that supports collections and elevations: [kepler-raster-server](https://github.com/igorDykhta/kepler-raster-server).\n\n# Elevation\n\nTo enable elevation rendering, you must provide one or more compatible raster tile servers when adding the tileset. Enter them in the \"Raster tile servers\" field of the Add Tileset form.\n\n- For STAC Items/Collections: compatible raster tile servers are required.\n- For raster .pmtiles: raster tile servers are optional for imagery, but required if you plan to use elevation.\n- The server must expose COGs as XYZ tiles and support elevation/DEM tiles. Example implementation: [kepler-raster-server](https://github.com/igorDykhta/kepler-raster-server) (TiTiler-based).\n\nExample Raster .pmtiles:\n\n- Mount Whitney - https://pmtiles.io/usgs-mt-whitney-8-15-webp-512.pmtiles\n- Swiss historical - https://public-bucket-for-tests.s3.us-east-1.amazonaws.com/historic-swis-18xx.pmtiles\n\nExample STAC Items:\n\n- Bangladesh rivers — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/planet-skysat-opendata.json\n- Antarctica ice — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/sentinel-2-l2a.json\n- Kiribati island — https://4sq-studio-public.s3.us-west-2.amazonaws.com/sdk/examples/sample-data/raster/stac-example.json\n\nExample STAC Collections:\n\n- sentinel-2-l1c — https://earth-search.aws.element84.com/v1/collections/sentinel-2-l1c\n- modis-09A1-061 — https://planetarycomputer.microsoft.com/api/stac/v1/collections/modis-09A1-061\n- landsat-c2-l1 — https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-c2-l1\n\n[Back to layer overview](./README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/o-wms-layer.md",
    "content": "# WMS Layer — How to add (experimental)\n\nUse WMS Layer to render imagery from OGC Web Map Service (WMS) endpoints.\n\n1. Open Add Data → Tilesets.\n2. Select WMS tileset type.\n3. Enter the WMS service URL (GetCapabilities endpoint or base service URL).\n4. Click Add. A new WMS layer will appear in the Layers panel.\n5. In the Layers panel you can select a named layer from the service.\n\nNotes:\n\n- Feature info on click is supported for queryable layers (`queryable=true`).\n\nExample WMS services:\n\n- https://ows.terrestris.de/osm/service\n- https://opengeo.ncep.noaa.gov/geoserver/conus/conus_cref_qcd/ows\n- https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi\n- https://geo.stadt-muenster.de/mapserv/starkregen_serv\n\n[Back to layer overview](./README.md)\n"
  },
  {
    "path": "docs/user-guides/c-types-of-layers/vector.md",
    "content": "# Vector Layer\n\nVector layers use the three basic GIS features – lines, points, and polygons – to represent real-world features in digital format.\n\n![Vector layer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-vector.gif 'Vector layer')\n\n# Settings\n\nThe following tables describe every setting in the Vector Tile layer.\n\n## Basic\n\nCore settings for the Vector layer.\n\n| Setting    | Description                                  |\n| ---------- | -------------------------------------------- |\n| Layer Type | Must be Vector Tile to use the Vector layer. |\n\n## Fill Color\n\nSettings related to the fill color of the vector data.\n\n| Setting        | Description                                                                                                                                    |\n| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| Color Based On | The field to base the fill color on.                                                                                                           |\n| Color Scale    | The color scale and palette for the fill color.                                                                                                |\n| Dynamic Color  | Enable to estimate color range and scale based on tiles selected for rendering. When panning the map, the color scale will update dynamically. |\n| Opacity        | The opacity of the fill color. 100 = fully opaque, 0 = fully transparent.                                                                      |\n\n## Stroke Color\n\nSettings related to the stroke/outline color of the Vector layer.\n\n| Setting               | Description                                                                        |\n| --------------------- | ---------------------------------------------------------------------------------- |\n| Stroke Color          | Toggle to enable stroke.                                                           |\n| Stroke Color Based On | A column to base the stroke color on. When enabled, a color scale can be selected. |\n| Color                 | The color or color scale to use for the stroke.                                    |\n| Opacity               | The opacity of the stroke.                                                         |\n\n## Stroke Width\n\nSettings related to the stroke/outline width of the Vector layer.\n\n| Setting             | Description                                                                                                                            |\n| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |\n| Stroke Width        | Toggle to enable stroke.                                                                                                               |\n| Stroke Width Slider | Slide or enter a number to select the width of the stroke/outline in pixels. When \"Stroke Based On\" is selected, this becomes a range. |\n| Stroke Based On     | A column to base the stroke width on.                                                                                                  |\n| Stroke Scale        | The method by which to scale the stroke/outline size. Choose between Linear, Sqrt, and Log.                                            |\n\n## Height\n\n> Note: Height only affects polygons on your map, not points.\n\nHeight settings for the Vector layer. Height is best viewed with the 3D viewing mode.\n\n| Setting         | Description                                        |\n| --------------- | -------------------------------------------------- |\n| Height Slider   | Increase to raise the height of the layer objects. |\n| Height Based On | A column to base the height on.                    |\n| Height Scale    | Choose from linear, sqrt, and log.                 |\n| Fixed Height    | Applies height without additional modifications.   |\n\n[Back to table of contents](../README.md)\n"
  },
  {
    "path": "docs/user-guides/d-layer-attributes.md",
    "content": "# Layer Attributes\n\n| Layer Attribute                    | Description                                                                                                                                            | Available in                                   |\n| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |\n| Color/Color based on               | Choose the color of your layer or assign color based on a field from your dataset(s).                                                                  | All layers                                     |\n| High-precision rendering           | Activate high-precision rendering when zooming in closely on a layer. High-precision rendering sometimes results in a performance cost.                | Point, Arc, Line, Icon, GeoJSON, Hexagon, Grid |\n| Radius/Radius based on             | Change the radius of points or assign radius values based on a field from your dataset(s).                                                             | Point, Icon, GeoJSON                           |\n| Opacity                            | Change the transparency of a layer. 1 = opaque, 0 = invisible.                                                                                         | All layers                                     |\n| Cluster size                       | Change the granularity of clusters. The lower the numerical value, the smaller the geospatial radius that will be used to aggregate clusters.          | Cluster                                        |\n| Radius range                       | Set a lower and upper threshold for projected radius size.                                                                                             | Point, Icon, Geojson, Cluster                  |\n| Stroke width/stroke width based on | Change the thickness of lines and arcs, or assign a width based on a field from your dataset(s).                                                       | Arc, line, Geojson                             |\n| Stroke width range                 | Set a lower and upper threshold for projected stroke width.                                                                                            | Arc, Line, Geojson                             |\n| Grid size                          | Change the number of square kilometers covered by each grid square.                                                                                    | Grid                                           |\n| Color Palette                      | Choose from multiple, predefined or customized color palettes to apply to your layer. Predefined palettes are either Uber or ColorBrewer colors.       | All layers                                     |\n| Color Scale                        | Choose either a quantile or quantized color scale. A quantile color scale is determined by rank, while a quantized color scale is determined by value. | All layers                                     |\n| Height based on                    | Assign grid square height based on a field from your dataset.                                                                                          | Grid, Hexagon, S2                              |\n| Filter by count percentile         | Increase or decrease the number of grid squares by choosing a range of percentiles to display.                                                         | Grid, Hexagon                                  |\n| Coverage                           | Change what portion of each grid cell is covered by a color square.                                                                                    | Grid, Hexagon                                  |\n| Height Scale                       | Change the height of the grid squares, hexagons or S2 when in 3D mode.                                                                                 | Grid, Hexagon, S2                              |\n| Stroked                            | When activated, draws outlines around geoshapes.                                                                                                       | GeoJSON, Point                                 |\n| Filled                             | When activated, geo shapes are filled in with colors.                                                                                                  | GeoJSON                                        |\n| Extruded                           | In 3D mode, assign polygon height values based on some value from your dataset.                                                                        | GeoJSON                                        |\n| Wireframe                          | Create outlines around extruded polygons.                                                                                                              | GeoJSON                                        |\n| Stroke or radius based on          | Control the radius/thickness of GeoJSON line and point features.                                                                                       | GeoJSON                                        |\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/e-filters.md",
    "content": "# Filters\n\nAdd filters to your map to limit the data that is displayed. Filters must be based on the columns in your dataset. \n\nTo add a filter:\n\n1. Select Filters from the right navigation bar.\n![select filters](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image1.png \"select filters\")\n\n2. The Filters panel displays the list of existing filters, color-coded by dataset. To create a new filter, Click __Add Filter__.\n\n3. Choose a dataset, and then a field on which to filter your data. Filter values are defined by field data type (number, string, timestamp, etc.). \n![choose a dataset](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image29.png \"choose a dataset\")\n\n4. Your filter is applied to your map as soon as you specify the field and value.\n5. Delete a filter anytime by clicking the __trashcan__ to the right of the filter you wish to delete.\n\n__Note__: filters apply to all layers in the same dataset on your map.\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/f-map-styles.md",
    "content": "# Map Styles\n\n<!-- TOC -->\n\n  - [Base Map Styles](#base-map-styles)\n  - [Toggle Map Layers](#toggle-map-layers)\n      - [Map Layers](#map-layers)\n      - [Layer Order](#layer-order)\n  - [Custom Map Styles](#custom-map-styles)\n\n<!-- /TOC -->\n\n![map styles](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-0.png \"Map panel\")\n\nkepler.gl provide a set of [Mapbox](https://www.mapbox.com) basemap styles as background map, including 3D buildings! You can also add your own custom map sytle using the Mapbox style link.\n\n## Base Map Styles\n\nOpen the __Base Map panel__ to select from a list of default map styles.\n\n![base map panel](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-1.png \"base map panel\")\n\nOpen the __base map style__ drop down menu to change map color scheme and imagery. Options include:\n- __Dark__: dark base map with light-colored text.\n- __Light__: light base map with dark-colored text.\n\n## Toggle Map Layers\n\n![map layers](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-2.png \"map layers\")\n\n#### Map Layers\nHide and show water, buildings, roads, and more. Options include:\n- __Labels__: shows labels for cities, neighborhoods, and so on.\n- __Roads__: displays a translucent layer of road lines.\n- __Borders__: shows state and continent borders.\n- __Buildings__: shows building footprints.\n- __Water__: displays bodies of water.\n- __Land__: Shows parks, mountains, and other landscape features.\n- __3d Building__: Shows 3D buildings on the map. 3D buildings are only visible in the 3D map view. Resolution automatically updates based on current map zoom level. Use the input below to edit 3D bulding color.\n\n![3d building](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-3.png \"3d buldings\")\n\n\n#### Layer Order\nTo control the order in which map imagery layers are displayed, toggle the move to top icon:\n\n![move to top icon](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-4.png \"move to top icon\").\n\n__TIP__: Move labels to the top on maps with colored layers to keep the labels from being concealed.\n\n![Examales of ordered layers](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-5.png \"examples of ordered layers\")\n\n\n## Custom Map Styles\n![Add Custom Mapbox Styles button](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image45.png \"Add Custom Mapbox Styles button\")\n\nTo add a custom base map style, click the add map style button to open the custom map style modal, paste in the mapbox [style Url](https://www.mapbox.com/help/studio-manual-publish/#style-url). Note that you need to paste in your [mapbox access token](https://www.mapbox.com/account/) if your style is not [published](https://www.mapbox.com/help/studio-manual-publish/#style-url).\n\n\n![Add Custom Mapbox Styles popup](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image13.png \"Add Custom Mapbox Styles popup\")\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/g-interactions.md",
    "content": "# Interactions\n\n<!-- TOC -->\n  - [Tooltips](#tooltips)\n  - [Brushing](#brushing)\n  - [Display Coordinates](#display-coordinates)\n<!-- /TOC -->\n\nYou can toggle customization options on your map, including tooltips, brush highlighting, map imagery (water, parks, etc.), and more.\n\n![Interaction menu](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/g-interactions-0.png \"Interaction menu\")\n\nTo toggle customization options on your map:\n\n1. Open the Interactions menu by clicking the Interactions icon:\n\n\n2. Click the switch next to the options you wish to activate/deactivate.\n\nThere are three types of interactions to choose from: __Tooltip__, __Brushing__ and __Coordinate__. Note that only one of tooltip and brushing can be on at a time.\n\n## Tooltips\ntooltip displays metrics when hovering over a data point. You can choose which fields are displayed from the tooltip config menu.\n\n![tooltips](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image25.png \"tooltips\")\n\n  - __Image__ Image can be added to tooltip. If field name contains `<img>` and the field content contains `http` url\n\n  |id| `<img>-tooltip` |\n  |---|---|\n  |1|`http://my-image.com/my-image-0.png`|\n  |2|`http://my-image.com/my-image-1.png`|\n\n\n  - __Web link__\nTooltip can be a clicable weblink. To add a web link as tooltip, add  url starts with `http://` to the field content.\n\n![tooltips](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/g-interactions-1.png \"tooltips\")\n\n\nTip: click a point to pin the tooltip info to the map. To unpin the tooltip, press the blue pin icon.\n\n![pin/unpin tooltip](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image15.png \"pin/unpin tooltip\")\n\n\n## Brushing\n- __Brush__: Brush allows you to highlight areas with the cursor. When brush is turned on, all layers darken. Only the portion you hover over with the cursor is illuminated. Brush works well with arc layers in particular.\n\n![brush](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image12.png \"brush\")\n\n[Back to table of contents](README.md)\n\n\n## Display Coordinates\n\n![coordinate](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/g-interactions-2.png \"coordinate\")\n\nWhen then on coordinate, a  panel contains latitude and longitude will follow your mouse\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/h-playback.md",
    "content": "# Playback\n\nFollow these steps to create a playback video of an event:\n1. Add a filter based on a time-related field, like timestamp. For GeoJson, property field should contain a timestamp entry.\n\n2. The playback window will appear on the bottom of the map. The bars are distribution graphs of all data points by time. Select the desired rolling time window:\n\n![select filters](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/h-playback-1.png \"select filters\")\n\n3. Press play to start the video. Click on the speed value and select/input your desired value _1x_, _2x_, _4x_ on the top right to change the playback speed.\n\n![change speed](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/h-playback-2.gif \"select filters\")\n\n4. Choose custom y axis. You can click __Select Y Axis__ to change the default distribution graph to a timeseries of the selected column. An example use of this function is to show a distance vs. time graph of a given trip.\n\n![custom y axis](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/h-playback-3.png \"select filters\")\n\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/i-FAQ.md",
    "content": "# FAQ\n\n## Can I export my map to a video file?\nKepler.gl does not have built-in functionality for exporting video. It only allows you to export maps as an image. However, you can use screen capture softwares such as Quicktime Player, LICEcap, Giffy, and others to export your map.\n\n## How can I get data from my back-end service into Kepler.gl?\nYou can create a react redux app with kepler.gl as your map display component, requesting data with any kind of server-side calls. The kepler.gl demo app uses simple AJAX calls to load data from a URL.\n\n## Is there a limit on the number of datasets that can be added to a map?\nThere is no limit, but the more datasets you add, the more likely it is that performance will suffer. \n\n## What is the maximum file upload size?\nKepler.gl accepts files no larger than 250mb in chrome. You can load bigger files in safari, but performance will be limited.\n\n## How many layers can I add to a map?\nThere is no limit on the amount of layers you can add. However, note that the more layers you have on your map, the more likely it is that performance will suffer. \n\n## Why does my layer color change during filtering?\nWhen layer color is based on a numerical value (e.g, trip fare, distance, ETA), Kepler.gl recalculates the color scale based on filtered data. This is not the case for categorical values such as vehicle_name, cuisine_type and app version.\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/j-get-started.md",
    "content": "# Get Started\n\nKepler.gl is a tool designed for geospatial data analysis. This guide will help you get started creating visualizations in kepler.gl.\n\n\n## 1) Add Data to your Map\n\n![Add data to the map pop up](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image42.png \"Add data to the map pop up\")\n\nKepler.gl will prompt you to add data to your map as soon as you open the web page. Upload your own CSV or GEOJSON file, or add kepler.gl sample data.\nSample data is a great way to explore and get familiar with kepler.gl’s features.\n\nRead more about adding [Add data to the map](./b-kepler-gl-workflow/a-add-data-to-the-map.md).\n\n\n## 2) Add Layers\n\n![Add layer](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/j-get-started-layers.png \"Add layer\")\n\nOpen the Data Layers menu to start building your visualization. Layers are simply data visualizations that can be built on top of one another. The map pictured above contains a GeoJSON path layer showing trip routes.\n\nIf you’re new to kepler.gl, play around with the different settings for each type of layer. Layers of the same type can differ greatly in appearance depending on how they’re configured, opening up new possibilities for data analysis.\n\nLearn more about [adding data layers](./b-kepler-gl-workflow/b-add-data-layers/a-adding-data-layers.md)\n.\n\n## 3) Add Filters\n\n![choose a dataset](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/add-filter.png \"choose a dataset\")\n\nAdd filters to your map to limit the data that is displayed. Filters must be based on the columns in your dataset. To create a new filter, open the Filter menu and click Add Filter. Note that filters apply to all layers and cannot be toggled on and off.\n\nLearn more about [filters](./e-filters.md).\n\n## 4) Customize Map Settings\n\n![Customize Map Settings](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/interactions.png \"Customize Map Settings\")\n\nChange the settings on your map in the Interactions and Base Map menus. Customization options include tooltips, brush highlighting, base map style, map imagery toggles (water, parks, satellite image, etc.), and many more.\n\nRead about [base map styles](./f-map-styles.md), [interactions](./g-interactions.md) and [map settings](./m-map-settings.md).\n\n## 5) Save and Export\n![Save and Export](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/export-save.png \"Save and Export\")\nSave your map as an image, export current map data, export current map as a json file to be load back into kepler.gl.\n\nRead about [Save and export](./k-save-and-export.md).\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/k-save-and-export.md",
    "content": "# Save and Export\n\n![Save and Export](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-1.png \"activate interactions\")\n\nkepler.gl is a client-side only application. In the demo app, the data you uploaded stays in your browser. kepler.gl does not send or store any user data to any backends. This rule poses an limitation on how you can save and share your maps.\n\nHowever, in the demo app, you can:\n\n- [Export map as an image](#export-image).\n- [Export filtered or unfiltered data as a csv](#export-data).\n- [Export Map](#export-map)\n- [Share Public URL (Dropbox)](#export-dropbox)\n\n## <a href=\"#export-image\">Export Image</a>\n\n![Export Image](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-2.png \"activate interactions\")\n\nYou can export the current map as an image. The export window will use the current map viewport, and the preview will show the entire exported map area. To adjust the viewport, you will have to close the export dialog. You can choose different export ratios or resolutions, and also to add a map legend.\n\n## <a href=\"#export-data\">Export Data</a>\n\n![Export Data](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-3.png \"activate interactions\")\n\nYou can export map data as a csv file, with the option to export ONLY the filtered data or the entire dataset.\n\n## <a href=\"#export-map\">Export Map</a>\nYou can export the current map using two different formats. The __Export Map__ window provides two options:\n- HTML: create a single html file loads and renders your current map.\n- JSON: create a json file with your current map config and data.\n\n### <a href=\"#export-html-map\">Export Map as HTML</a>\n\n![Export Map as HTML](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-4.png \"activate interactions\")\n\nTo save and export your current map as HTML file, click on __Export Map__ and subsequently on __Export__.\nWhen prompted provide your own mapbox token to be used in the newly generated file. If you don't provide a Mapbox Token,\nKepler.gl will use a default one which can expire at anytime without any communication and therefore break your your existing map.\n\n#### How to update an exported map token\nIn order to edit the mapbox token in your html file you simply need to perform the following steps:\n1. [Create a new mapbox token](https://docs.mapbox.com/help/how-mapbox-works/access-tokens/) or use your existing one.\n2. Open the kepler.gl.map file with your favorite text editor.\n3. Locate the following line in the exported file __kepler.gl.html__:\n```javascript\n  /**\n   * Provide your MapBox Token\n   **/\n  const MAPBOX_TOKEN = 'CURRENT_TOKEN';\n```\n4. Replace the current value a new valid token. The code should now look like the following:\n```javascript\n  /**\n   * Provide your MapBox Token\n   **/\n  const MAPBOX_TOKEN = 'pk.eyJ1IjoidWJlcmRh...';\n```\n\n### <a href=\"#export-json-map\">Export Map as JSON</a>\n![Export Map as JSON](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-5.png \"activate interactions\")\n\nYou can export the current map as a `json` file. This is useful when you are running your own kepler.gl application and want to load your map programmatticaly.\nThe JSON file includes:\n- dataset: processed data to create used to render your map\n- config: layer, filter, map style and interaction settings.\nThe map config includes the current layer, filter, map style and interaction settings.\n\n**Note:** kepler.gl map config is coupled with loaded datasets. The __`dataId`__ key is used to bind layers, filters and tooltip settings to a specific dataset. If you try to upload a configuration with a dataset in your own kepler.gl app, you also need to make sure your dataset __`id`__ matches the __`dataId`__ in the config.\n\n\n## <a href=\"#export-dropbox\">Share Public URL (Dropbox) </a>\n![Export Map to Dropbox](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/k-save-and-export-5.png \"activate interactions\")\n\nTo export the current map into your Dropbox account, click on __Share Public Url__ and select Dropbox as your cloud storage.\nPerform the authentication against Dropbox using your credentials. Once the authentication process is completed,\nclick on __Upload__ and Kepler.gl will push your current map onto your account.\n\nAt the end of the process Kepler.gl will automatically generate a permalink for your work you can share with other users.\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/l-color-attributes.md",
    "content": "# Color Palettes\n\nColor palettes pride both predefined and customized options and can be applied to either fill or stroke. Predefined palettes comes in diverging, sequential and qualitative types.\n\nTo choose a palette:\n\n1. Expand layer pane and click on the color bar from either filled color or stroke color section. Click on the three dots to select the field to color by.\n\n<img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-color-attributes-0.png\" alt=\"expand\" width=\"500\"/>\n\n2. To choose a custom palette, toggle on custom button. Click on each color to pick new color either by clicking on the color picker or inputting HEX/RGB values. Colors steps can be added, removed or shuffled.\n\n<img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-color-attributes-1.png\" alt=\"toggle\" width=\"300\"/>\n<img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-color-attributes-2.png\" alt=\"toggle\" width=\"300\"/>\n\n\n3) Your color is applied to your map as soon as you select the predefined palette or confirm the choices of customized colors.\n\n<img src=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/l-color-attributes-3.png\" alt=\"toggle\" width=\"500\"/>\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/m-map-settings.md",
    "content": "# Map Settings\n\n<!-- TOC -->\n  - [Split Maps](#split-maps)\n  - [View Maps in 3D](#view-maps-in-3d)\n  - [Display Legend](#display-legend)\n<!-- /TOC -->\n\n\n![Map Settings](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/m-map-settings-0.png \"Split Maps\")\n\n## Split Maps\n\nYou can display a side-by-side comparison of the same map area with different layers with the Split Map functionality.\n\n![Split Maps](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image36.png \"Split Maps\")\n\n1. Enable this by clicking the Split Map icon in the top right corner of your map:\n\n![Split Maps Icon](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/m-map-settings-split.png \"Split Maps Icon\")\n\n2. Toggle the layers visible in each map with the layer icon in the top right corner of each map.\n\n![Split Maps Icon](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/m-map-settings-layer.png \"Split Maps Icon\")\n\n![Toggle Layers](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image35.png \"Toggle Layers\")\n\n3. Zoom in and out on each map and the other will automatically mimic.\n\n\n## View Maps in 3D\nView your map in 3D by clicking the 3D icon in the top right corner of your map\n\n![View Maps in 3D](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/m-map-settings-3d.png \"View Maps in 3D\")\n\n- __drag__:  pan\n- __cmd + drag__ (mac) or __ctrl + drag__ (win): rotate\n\n![Map in 3D](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/f-map-styles-7.png \"Map in 3D\")\n\n\n## Display Legend\nView your map in 3D by clicking the 3D icon in the top right corner of your map:\n\n![Display Legend](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/m-map-settings-legend.png \"Display Legend\")\n\n![Sample Legend](https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/image14.png \"Sample Legend\")\n\n[Back to table of contents](README.md)\n"
  },
  {
    "path": "docs/user-guides/sql-data-explorer.md",
    "content": "# SQL/DuckDB Data Explorer\n\n**This feature is currently undergoing development. Stay tuned for updates!**\n\nThe new SQL data explorer provides a DucKDB instance where you can use SQL to transform and add data to the map.\n\n![SQL Data Explorer](https://4sq-studio-public.s3.us-west-2.amazonaws.com/statics/keplergl/images/kepler-duck-db.png 'SQL Data Explorer')\n\nYou will need local data (i.e. data upload for your machine) or data accessible via a remote URL.\n\nAny dataset already added to kepler can be accessed via SQL editor by selecting it via its name in kepler. For instance, if your dataset is named `world-cities.csv`, you can select the entire dataset by writing `SELECT * FROM 'world-cities.csv'`.\n\nThe data does not need to be pre-loaded to kepler -- you may also select data remotely via SQL. The following example loads earthquake data from our sample data repository:\n\n```\nSELECT * FROM 'https://raw.githubusercontent.com/keplergl/kepler.gl-data/refs/heads/master/earthquakes/data.csv'\n```\n\nOnce you have a data selection you are satisfied with, click **Add to Map**. Your new dataset will be added to kepler. If you have a column that contains recognizable geography data, (i.e. lat/lng columns or polygon geometries), layers will automatically be created.\n"
  },
  {
    "path": "esbuild/umd-esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {umdWrapper} from 'esbuild-plugin-umd-wrapper';\n\nimport process from 'node:process';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\nimport KeplerPackage from '../package.json' assert {type: 'json'};\n\nconst LIB_DIR = './';\nconst NODE_MODULES_DIR = join(LIB_DIR, 'node_modules');\nconst SRC_DIR = join(LIB_DIR, 'src');\n\nconst config = {\n  entryPoints: ['./src/index.js'],\n  bundle: true,\n  platform: 'browser',\n  outfile: './umd/keplergl.min.js',\n  format: 'umd',\n  logLevel: 'error',\n  minify: true,\n  sourcemap: false,\n  treeShaking: true,\n\n  external: ['react', 'react-dom', 'redux', 'react-redux', 'styled-components'],\n\n  plugins: [\n    // automatically injected kepler.gl package version into the bundle\n    replace({\n      __PACKAGE_VERSION__: KeplerPackage.version,\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    umdWrapper({\n      libraryName: \"KeplerGl\",\n      globals: {\n        \"react\": \"React\",\n        \"react-dom\": \"ReactDOM\",\n        'redux': 'Redux', \n        'react-redux': 'ReactRedux', \n        'styled-components': 'styled'\n      }\n    })\n  ]\n};\n\n(async () => {\n  await esbuild\n    .build({\n      ...config\n    })\n    .catch(e => {\n      console.error(e);\n      process.exit(1);\n    });\n})();\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Examples\n\nA list of examples to demonstrate adding `kepler.gl` to your app. Each of the examples is a complete project that can be ran locally.\n\nTo start each example, cd into the folder then run:\n\n```\nyarn && yarn start\n```\n\n- ### [Demo App][demo-app]\n\n  kepler.gl as a single page app, loading sample maps from remote url, saving map data to dropbox. This is also the source code of kepler.gl/#/demo.\n\n- ### [Open Modal][open-modal]\n  Open kepler.gl in a modal.\n\n- ### [Custom Reducer][custom-reducer]\n  Customize kepler.gl reducer initial state, adding more actions using plugin.\n\n- ### [umd client][umd-client]\n  A single html file loading kepler.gl\n\n- ### [Replace UI Component][replace-component]\n  Example showing how to replace kepler.gl default ui components using `injectComponents` method.\n\n- ### [Custom theme][custom-theme]\n  Customize kepler.gl theme by override default style properties.\n\n- ### [Node App][node-app]\n  Embed Kepler.gl in a node/express/webpack application. \n\n- ### [Custom map style][custom-map-style]\n\n  Demo how to use kepler.gl with other basemap services other than Mapbox.\n[custom-reducer]: custom-reducer/README.md\n[demo-app]: demo-app/README.md\n[node-app]: node-app/README.md\n\n[open-modal]: open-modal/README.md\n[umd-client]: umd-client/README.md\n[replace-component]: replace-component/README.md\n[custom-theme]: custom-theme/README.md\n[custom-map-style]: custom-map-style/README.md\n"
  },
  {
    "path": "examples/custom-map-style/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    \"@babel/plugin-transform-export-namespace-from\"\n  ]\n}\n"
  },
  {
    "path": "examples/custom-map-style/README.md",
    "content": "# Custom Map Style\n\n![map layers](https://studio-public-data.foursquare.com/statics/keplergl/documentation/f-map-styles-8.jpg 'custom map style')\n\nDemo how to use kepler.gl with other basemap services other than Mapbox.\n\nRead more about [Custom Map Style][custom-map-styles]\n\n### Run Example\n\n#### 1. Install\n\n```sh\nyarn\n```\n\n#### 2. Start the app\n\n```sh\nyarn start\n```\n\n[custom-map-styles]: ./docs/api-reference/advanced-usages/custom-map-styles.md\n"
  },
  {
    "path": "examples/custom-map-style/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\n\nconst args = process.argv;\n\nconst port = 8080;\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// Ensure a single instance of React and friends to avoid invalid hook calls\nconst ROOT_NODE_MODULES = join('..', '..', 'node_modules');\nconst thirdPartyAliases = {\n  react: join(ROOT_NODE_MODULES, 'react'),\n  'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),\n  'react-redux': join(ROOT_NODE_MODULES, 'react-redux', 'lib'),\n  'styled-components': join(ROOT_NODE_MODULES, 'styled-components'),\n  'apache-arrow': join(ROOT_NODE_MODULES, 'apache-arrow')\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.ts': 'ts',\n    '.tsx': 'tsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.tsx'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || '')\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    replace({\n      __PACKAGE_VERSION__: '3.1.10',\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    const result = await esbuild\n      .build({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: true,\n        sourcemap: false,\n        metafile: true,\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n    fs.writeFileSync('dist/esbuild-metadata.json', JSON.stringify(result.metafile));\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`kepler.gl custom-map-style example running at ${`http://localhost:${port}`}`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/custom-map-style/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <title>kepler.gl demo</title>\n    <link rel=\"stylesheet\" href=\"//d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n    <style type=\"text/css\">\n       body {margin: 0; padding: 0; overflow: hidden;}\n    </style>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src='bundle.js'></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/custom-map-style/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start-local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/components\": \"^3.1.10\",\n    \"@kepler.gl/reducers\": \"^3.1.10\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/custom-map-style/src/app.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport KeplerGl from '@kepler.gl/components';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\n\nconst localeMessages = {\n  en: {\n    mapLayers: {\n      terrain: 'Terrain'\n    }\n  }\n};\n\nconst App = () => (\n  <div style={{position: 'absolute', width: '100%', height: '100%'}}>\n    <AutoSizer>\n      {({height, width}) => (\n        <KeplerGl\n          mapboxApiAccessToken=\"\"\n          id=\"map\"\n          width={width}\n          height={height}\n          localeMessages={localeMessages}\n        />\n      )}\n    </AutoSizer>\n  </div>\n);\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/custom-map-style/src/main.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport {Provider} from 'react-redux';\nimport store from './store.ts';\nimport App from './app.tsx';\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/custom-map-style/src/store.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer, {enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\nconst mapStyles = {\n  voyager: {\n    id: 'voyager',\n    label: 'Voyager',\n    url: 'https://api.maptiler.com/maps/voyager/style.json?key=ySQ0fIYn7eSl3ppOeEJd',\n    icon: 'https://api.maptiler.com/maps/voyager/256/0/0/0.png?key=ySQ0fIYn7eSl3ppOeEJd'\n  },\n  terrain: {\n    id: 'terrain',\n    label: 'Outdoor',\n    url: 'https://api.maptiler.com/maps/outdoor/style.json?key=ySQ0fIYn7eSl3ppOeEJd',\n    icon: 'https://openmaptiles.org/img/styles/terrain.jpg',\n    layerGroups: [\n      {\n        slug: 'label',\n        filter: ({id}) => id.match(/(?=(label|place-|poi-))/),\n        defaultVisibility: true\n      },\n      {\n        slug: 'road',\n        filter: ({id}) => id.match(/(?=(road|railway|tunnel|street|bridge))(?!.*label)/),\n        defaultVisibility: true\n      },\n      {\n        slug: 'terrain',\n        filter: ({id}) => id.match(/(?=(hillshade))/),\n        defaultVisibility: true\n      },\n      {\n        slug: 'building',\n        filter: ({id}) => id.match(/building/),\n        defaultVisibility: true\n      }\n    ]\n  }\n};\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  mapStyle: {\n    mapStyles,\n    styleType: 'voyager'\n  }\n});\n\nconst reducers = combineReducers({\n  keplerGl: customizedKeplerGlReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nexport default createStore(reducers, {}, compose(...enhancers));\n"
  },
  {
    "path": "examples/custom-reducer/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    \"@babel/plugin-transform-export-namespace-from\"\n  ]\n}\n"
  },
  {
    "path": "examples/custom-reducer/README.md",
    "content": "# Customize kepler.gl Reducer\n\nThis example demos how to customize kepler.gl reducer\n\n1. Customize reducer initialState by `keplerGlReducer.initialState`\n2. Adding custom actions by `keplerGlReducer.plugins`\n\n### Local dev\n\n```\nyarn\n```\n\nadd mapbox access token to node env\n\n```\nexport MapboxAccessToken=<your_mapbox_token>\n```\n\nthen\n\n```\nyarn start\n```\n"
  },
  {
    "path": "examples/custom-reducer/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\n\nconst args = process.argv;\n\nconst port = 8080;\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// Ensure a single instance of React and friends to avoid invalid hook calls\nconst ROOT_NODE_MODULES = join('..', '..', 'node_modules');\nconst thirdPartyAliases = {\n  react: join(ROOT_NODE_MODULES, 'react'),\n  'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),\n  'react-redux': join(ROOT_NODE_MODULES, 'react-redux', 'lib'),\n  'styled-components': join(ROOT_NODE_MODULES, 'styled-components'),\n  'apache-arrow': join(ROOT_NODE_MODULES, 'apache-arrow')\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || '')\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    replace({\n      __PACKAGE_VERSION__: '3.1.10',\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    const result = await esbuild\n      .build({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: true,\n        sourcemap: false,\n        metafile: true,\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n    fs.writeFileSync('dist/esbuild-metadata.json', JSON.stringify(result.metafile));\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`kepler.gl custom-reducer example running at ${`http://localhost:${port}`}`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/custom-reducer/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <title>kepler.gl demo</title>\n    <link href=\"//d1a3f4spazzrp4.cloudfront.net/uber-fonts/4.0.0/superfine.css\" rel=\"stylesheet\">\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n    <style type=\"text/css\">\n       body {margin: 0; padding: 0; overflow: hidden;}\n    </style>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src='bundle.js'></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/custom-reducer/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start-local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/actions\": \"^3.1.10\",\n    \"@kepler.gl/components\": \"^3.1.10\",\n    \"@kepler.gl/reducers\": \"^3.1.10\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/custom-reducer/src/app-reducer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction, handleActions} from 'redux-actions';\n\n// CONSTANTS\nexport const INIT = 'INIT';\n\n// ACTIONS\nexport const appInit = createAction(INIT);\n\n// INITIAL_STATE\nconst initialState = {\n  appName: 'example',\n  loaded: false\n};\n\n// REDUCER\nconst appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    })\n  },\n  initialState\n);\n\nexport default appReducer;\n"
  },
  {
    "path": "examples/custom-reducer/src/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {connect} from 'react-redux';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport KeplerGl from '@kepler.gl/components';\nimport {createAction} from 'redux-actions';\n\nimport {addDataToMap, wrapTo} from '@kepler.gl/actions';\nimport sampleData from './data/sample-data';\nimport config from './configurations/config';\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line\n\n// extra actions plugged into kepler.gl reducer (store.js)\nconst hideAndShowSidePanel = createAction('HIDE_AND_SHOW_SIDE_PANEL');\n\nclass App extends Component {\n  componentDidMount() {\n    this.props.dispatch(\n      wrapTo(\n        'map1',\n        addDataToMap({\n          datasets: sampleData,\n          config\n        })\n      )\n    );\n  }\n\n  _toggleSidePanelVisibility = () => {\n    this.props.dispatch(wrapTo('map1', hideAndShowSidePanel()));\n  };\n\n  render() {\n    return (\n      <div style={{position: 'absolute', width: '100%', height: '100%'}}>\n        <button onClick={this._toggleSidePanelVisibility}> Hide / Show Side Panel</button>\n        <AutoSizer>\n          {({height, width}) => (\n            <KeplerGl mapboxApiAccessToken={MAPBOX_TOKEN} id=\"map1\" width={width} height={height} />\n          )}\n        </AutoSizer>\n      </div>\n    );\n  }\n}\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/custom-reducer/src/configurations/config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: '1s8r5md',\n          type: 'point',\n          config: {\n            dataId: 'tree_data',\n            label: 'location',\n            color: [18, 147, 154],\n            columns: {\n              lat: 'Location_latitude',\n              lng: 'Location_longitude',\n              altitude: null\n            },\n            isVisible: true,\n            visConfig: {\n              radius: 10,\n              fixedRadius: false,\n              opacity: 0.8,\n              outline: false,\n              thickness: 2,\n              colorRange: {\n                name: 'Ice And Fire',\n                type: 'diverging',\n                category: 'Uber',\n                colors: ['#D50255', '#FEAD54', '#FEEDB1', '#E8FEB5', '#49E3CE', '#0198BD'],\n                reversed: true\n              },\n              radiusRange: [33.6, 96.2],\n              'hi-precision': false\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: 'Species',\n              type: 'string'\n            },\n            colorScale: 'ordinal',\n            sizeField: {\n              name: 'Age',\n              type: 'integer'\n            },\n            sizeScale: 'sqrt'\n          }\n        },\n        {\n          id: '7otjdz',\n          type: 'hexagon',\n          config: {\n            dataId: 'tree_data',\n            label: 'Density',\n            color: [23, 184, 190],\n            columns: {\n              lat: 'Location_latitude',\n              lng: 'Location_longitude'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.2,\n              worldUnitSize: 0.8,\n              resolution: 8,\n              colorRange: {\n                name: 'ColorBrewer GnBu-6',\n                type: 'sequential',\n                category: 'ColorBrewer',\n                colors: ['#f0f9e8', '#ccebc5', '#a8ddb5', '#7bccc4', '#43a2ca', '#0868ac'],\n                reversed: false\n              },\n              coverage: 1,\n              sizeRange: [0, 500],\n              percentile: [0, 100],\n              elevationPercentile: [0, 100],\n              elevationScale: 5,\n              'hi-precision': false,\n              colorAggregation: 'average',\n              sizeAggregation: 'average',\n              enable3d: false\n            }\n          },\n          visualChannels: {\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            tree_data: ['TreeID', 'Species', 'Address', 'Has_Species', 'SiteInfo']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal',\n      splitMaps: []\n    },\n    mapState: {\n      bearing: 0,\n      dragRotate: false,\n      latitude: 37.76544453921235,\n      longitude: -122.46289885132524,\n      pitch: 0,\n      zoom: 12.032736770460689,\n      isSplit: false\n    },\n    mapStyle: {\n      styleType: 'light',\n      topLayerGroups: {\n        label: true\n      },\n      visibleLayerGroups: {\n        label: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "examples/custom-reducer/src/data/sample-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  info: {\n    label: 'San Francisco Trees',\n    id: 'tree_data'\n  },\n  data: {\n    fields: [\n      {\n        name: 'TreeID'\n      },\n      {\n        name: 'Species'\n      },\n      {\n        name: 'Address'\n      },\n      {\n        name: 'Has_Species'\n      },\n      {\n        name: 'SiteInfo'\n      },\n      {\n        name: 'PlantType'\n      },\n      {\n        name: 'PlantDate'\n      },\n      {\n        name: 'Plan'\n      },\n      {\n        name: 'Age'\n      },\n      {\n        name: 'DBH'\n      },\n      {\n        name: 'Location_latitude'\n      },\n      {\n        name: 'Location_longitude'\n      }\n    ],\n    rows: [\n      [\n        141565,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/21/88 0:00',\n        1988,\n        29,\n        21,\n        37.77596769,\n        -122.4413967\n      ],\n      [\n        232565,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '940 Elizabeth St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.75171022,\n        -122.441498\n      ],\n      [\n        119263,\n        'Pinus radiata :: Monterey Pine',\n        '495X Lakeshore Dr',\n        false,\n        'Median : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        207368,\n        'Ligustrum japonicum :: Japanese Privet',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76021031,\n        -122.4707394\n      ],\n      [\n        188702,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1501 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        17,\n        37.74220867,\n        -122.3872932\n      ],\n      [\n        141566,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.77570453,\n        -122.4414629\n      ],\n      [\n        188697,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1301 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.73973386,\n        -122.3829339\n      ],\n      [\n        122650,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        203507,\n        'Agonis flexuosa :: Peppermint Willow',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        23,\n        37.76018051,\n        -122.4708661\n      ],\n      [\n        122670,\n        'Lyonothamnus floribundus subsp. asplenifolius :: Santa Cruz Ironwood',\n        '301x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122658,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122660,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122648,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        196590,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.77303718,\n        -122.4172458\n      ],\n      [\n        196591,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77305251,\n        -122.4172809\n      ],\n      [\n        122649,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        145278,\n        'Fraxinus uhdei :: Shamel Ash: Evergreen Ash',\n        '730 11th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        0,\n        37.77448101,\n        -122.4691091\n      ],\n      [\n        96369,\n        'Lophostemon confertus :: Brisbane Box',\n        '168 Cervantes Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/9/11 0:00',\n        2011,\n        6,\n        24,\n        37.80468521,\n        -122.4402315\n      ],\n      [\n        182260,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '1471 28th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.75995923,\n        -122.4866885\n      ],\n      [\n        251179,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        251178,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        203506,\n        'Agonis flexuosa :: Peppermint Willow',\n        '910 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.76026555,\n        -122.4705695\n      ],\n      [\n        52566,\n        'Hakea suaveolens :: Sweet Hakea Tree',\n        '1555 40th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '1/12/16 0:00',\n        2016,\n        1,\n        2,\n        37.75778273,\n        -122.4993677\n      ],\n      [\n        22346,\n        'Lophostemon confertus :: Brisbane Box',\n        '5280 03rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/27/14 0:00',\n        2014,\n        3,\n        3,\n        37.72954822,\n        -122.3926894\n      ],\n      [\n        23567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2975 Van Ness Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/27/92 0:00',\n        1992,\n        25,\n        3,\n        37.80304673,\n        -122.4250636\n      ],\n      [\n        237469,\n        'Prunus cerasifera :: Cherry Plum',\n        '4200 23rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/00 0:00',\n        2000,\n        17,\n        0,\n        37.7528239,\n        -122.4366208\n      ],\n      [\n        244214,\n        'Tree(s) ::',\n        '342 Scott St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.77300994,\n        -122.4355982\n      ],\n      [\n        21823,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2741 Taylor St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/19/06 0:00',\n        2006,\n        11,\n        3,\n        37.80797069,\n        -122.4158502\n      ],\n      [\n        188686,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1300 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73901484,\n        -122.381104\n      ],\n      [\n        196592,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.77310137,\n        -122.4173585\n      ],\n      [\n        22614,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1001 Turk St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        37.78136501,\n        -122.4248636\n      ],\n      [\n        10933,\n        'Lophostemon confertus :: Brisbane Box',\n        '1359 Hudson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73743182,\n        -122.3840631\n      ],\n      [\n        2889,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1545 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.79059396,\n        -122.4198251\n      ],\n      [\n        240126,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '20 Lily St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.77473647,\n        -122.4213262\n      ],\n      [\n        183515,\n        'Myoporum laetum :: Myoporum',\n        '2562 26th Ave',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.73970633,\n        -122.4828998\n      ],\n      [\n        13434,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2X Market St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.79448363,\n        -122.3950546\n      ],\n      [\n        226924,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.74625595,\n        -122.4338758\n      ],\n      [\n        192589,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        3558,\n        'Lophostemon confertus :: Brisbane Box',\n        '101 Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 17:52',\n        2017,\n        0,\n        6,\n        37.74050235,\n        -122.3776366\n      ],\n      [\n        222540,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74632546,\n        -122.4338277\n      ],\n      [\n        192590,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        28375,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '3014 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/9/03 0:00',\n        2003,\n        14,\n        3,\n        37.78875791,\n        -122.4429785\n      ],\n      [\n        222539,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74630146,\n        -122.4338571\n      ],\n      [\n        226925,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.74628109,\n        -122.4338516\n      ],\n      [\n        27522,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '3201 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.78709954,\n        -122.4473685\n      ],\n      [\n        18228,\n        'Maytenus boaria :: Mayten',\n        '1737-1795 Post St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/04 0:00',\n        2004,\n        13,\n        12,\n        37.78534132,\n        -122.431005\n      ],\n      [\n        17325,\n        'Fraxinus americana :: American Ash',\n        '150 Otis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/31/16 0:00',\n        2016,\n        1,\n        3,\n        37.77095437,\n        -122.4203558\n      ],\n      [\n        3528,\n        'Lophostemon confertus :: Brisbane Box',\n        '201X Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 16:23',\n        2017,\n        0,\n        3,\n        37.74144532,\n        -122.3793046\n      ],\n      [\n        251186,\n        'Juniperus chinensis :: Juniper',\n        '3042 Steiner St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.79753285,\n        -122.4371106\n      ],\n      [\n        138938,\n        'Tree(s) ::',\n        '632 HAYES ST',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.77646121,\n        -122.4269126\n      ],\n      [\n        60867,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/83 0:00',\n        1983,\n        34,\n        null,\n        null,\n        null\n      ],\n      [\n        233897,\n        'Araucaria heterophylla :: Norfolk Island Pine',\n        '1021 Noe St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        9,\n        37.75266993,\n        -122.4318862\n      ],\n      [\n        251187,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        121137,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        2909,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1700 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        30,\n        37.79038763,\n        -122.4229834\n      ],\n      [\n        27602,\n        'Pittosporum undulatum :: Victorian Box',\n        '988 Fulton St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        20,\n        37.77766599,\n        -122.4324106\n      ],\n      [\n        44642,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/29/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        4749,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1255 Columbus Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/3/15 0:00',\n        2015,\n        2,\n        8,\n        37.80577303,\n        -122.4182612\n      ],\n      [\n        44994,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        121139,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        226596,\n        'Leptospermum scoparium :: New Zealand Tea Tree',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.74632473,\n        -122.4337918\n      ],\n      [\n        239043,\n        'Pyrus calleryana :: Ornamental Pear',\n        '10 12th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77381038,\n        -122.4194082\n      ],\n      [\n        222541,\n        'Callistemon citrinus :: Lemon Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.7463451,\n        -122.433801\n      ],\n      [\n        138748,\n        'Lophostemon confertus :: Brisbane Box',\n        '1614 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        0,\n        37.7853077,\n        -122.4415226\n      ],\n      [\n        226926,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.74630146,\n        -122.4338212\n      ],\n      [\n        222538,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74627164,\n        -122.4338847\n      ],\n      [\n        166623,\n        'Potential Site :: Potential Site',\n        '520 Jersey St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.75036438,\n        -122.4371358\n      ],\n      [\n        251188,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        19206,\n        'Laurus nobilis :: Sweet Bay: Grecian Laurel',\n        '630 Sansome St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79625001,\n        -122.4016986\n      ],\n      [\n        121138,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        251206,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '270 Trumbull St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73083203,\n        -122.4245292\n      ],\n      [\n        25725,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '729 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.77705272,\n        -122.4286972\n      ],\n      [\n        251249,\n        'Private shrub :: Private Shrub',\n        '161 Lundys Ln',\n        false,\n        'Front Yard : Yard',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.7429691,\n        -122.4194092\n      ],\n      [\n        251293,\n        '::',\n        '3789 Market St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.75373446,\n        -122.442203\n      ],\n      [\n        221749,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        24,\n        37.76366261,\n        -122.4520649\n      ],\n      [\n        221750,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        11,\n        37.76366331,\n        -122.4521462\n      ],\n      [\n        245474,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        19,\n        37.76363745,\n        -122.4522558\n      ],\n      [\n        251419,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        251418,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        16698,\n        'Pittosporum undulatum :: Victorian Box',\n        '950X Noriega St',\n        false,\n        'Median : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        12,\n        37.75434951,\n        -122.4740143\n      ],\n      [\n        251532,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251531,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251642,\n        'Shrub :: Shrub',\n        '2253 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.79122797,\n        -122.4428704\n      ],\n      [\n        251700,\n        '::',\n        '1560 Sanchez St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '6/5/17 0:00',\n        2017,\n        0,\n        3,\n        37.74408823,\n        -122.4290963\n      ],\n      [\n        253008,\n        '::',\n        '1201 Tennessee St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '7/10/17 0:00',\n        2017,\n        0,\n        3,\n        37.7563302,\n        -122.3890342\n      ],\n      [\n        208471,\n        'Cordyline australis :: Dracena Palm',\n        '1175 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/23/17 0:00',\n        2017,\n        0,\n        3,\n        37.75989512,\n        -122.4735278\n      ],\n      [\n        253411,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253409,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253410,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253412,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        225282,\n        'Pittosporum crassifolium :: Karo Tree',\n        '101 Rivoli St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        6,\n        37.76244046,\n        -122.4496446\n      ],\n      [\n        173852,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173849,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173850,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173851,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173848,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173846,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173847,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173853,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        107717,\n        'Tree(s) ::',\n        '2400X 25th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '10/16/14 0:00',\n        2014,\n        3,\n        3,\n        37.74238326,\n        -122.4917845\n      ],\n      [\n        110194,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '2123 Pierce St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78980712,\n        -122.4374813\n      ],\n      [\n        59839,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1201 Palou Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/10/81 0:00',\n        1981,\n        36,\n        3,\n        37.72976741,\n        -122.3836854\n      ],\n      [\n        183689,\n        'Rhamnus alaternus :: Italian Buckthorn',\n        '1341 42nd Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76162779,\n        -122.5017998\n      ],\n      [\n        18916,\n        'Olea europaea :: Olive Tree',\n        '2621 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.78950262,\n        -122.4361188\n      ],\n      [\n        20281,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '20X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        null,\n        null\n      ],\n      [\n        119758,\n        'Maytenus boaria :: Mayten',\n        '151X Octavia St Frontage West',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        228560,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76151469,\n        -122.4216727\n      ],\n      [\n        18052,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2840 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78687837,\n        -122.4426898\n      ],\n      [\n        212622,\n        'Prunus cerasifera :: Cherry Plum',\n        '115 Beverly St',\n        false,\n        'Sidewalk: Property side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71819234,\n        -122.4717606\n      ],\n      [\n        20398,\n        'Cupressus macrocarpa :: Monterey Cypress',\n        '262X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        null,\n        null\n      ],\n      [\n        66855,\n        'Maytenus boaria :: Mayten',\n        '2041 Hayes St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/15/91 0:00',\n        1991,\n        26,\n        8,\n        37.77336241,\n        -122.4501107\n      ],\n      [\n        237033,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.77334931,\n        -122.4236445\n      ],\n      [\n        116641,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        218972,\n        'Fraxinus americana :: American Ash',\n        '742 Myra Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.74058076,\n        -122.4513096\n      ],\n      [\n        47510,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '54X Cayuga Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.73158339,\n        -122.4295272\n      ],\n      [\n        237030,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.77325086,\n        -122.4236179\n      ],\n      [\n        213016,\n        'Tree(s) ::',\n        '120 Ortega St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.75295695,\n        -122.4649514\n      ],\n      [\n        228561,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76156532,\n        -122.4216758\n      ],\n      [\n        72565,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        '11/16/96 0:00',\n        1996,\n        21,\n        3,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        116640,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        6337,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1916 Ellis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78175831,\n        -122.4377689\n      ],\n      [\n        832,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1396 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:18',\n        2017,\n        0,\n        3,\n        37.76054428,\n        -122.5069327\n      ],\n      [\n        251241,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2305 Golden Gate Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77731326,\n        -122.448748\n      ],\n      [\n        18836,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2055 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79068779,\n        -122.4267446\n      ],\n      [\n        11598,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4134 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:51',\n        2017,\n        0,\n        3,\n        37.76048345,\n        -122.5065435\n      ],\n      [\n        111561,\n        'Eriobotrya deflexa :: Bronze Loquat',\n        '1498x Kansas St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.75089751,\n        -122.402317\n      ],\n      [\n        97319,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1038 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:52',\n        2017,\n        0,\n        3,\n        37.74306751,\n        -122.4774242\n      ],\n      [\n        58021,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4026 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:07',\n        2017,\n        0,\n        3,\n        37.76257173,\n        -122.5012197\n      ],\n      [\n        175046,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71164354,\n        -122.4709833\n      ],\n      [\n        26071,\n        'Melaleuca quinquenervia :: Cajeput',\n        '247 Orizaba Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:41',\n        2017,\n        0,\n        3,\n        37.71342972,\n        -122.4626502\n      ],\n      [\n        11606,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4200x Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:54',\n        2017,\n        0,\n        3,\n        37.76045095,\n        -122.5071358\n      ],\n      [\n        36617,\n        'Robinia x ambigua :: Locust',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/20/98 0:00',\n        1998,\n        19,\n        null,\n        null,\n        null\n      ],\n      [\n        24532,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1395 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:21',\n        2017,\n        0,\n        3,\n        37.76072337,\n        -122.5070915\n      ],\n      [\n        5972,\n        'Pittosporum undulatum :: Victorian Box',\n        '1550 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '2/2/90 0:00',\n        1990,\n        27,\n        8,\n        37.78138444,\n        -122.4331105\n      ],\n      [\n        830,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '1384 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:15',\n        2017,\n        0,\n        3,\n        37.76071635,\n        -122.5069447\n      ],\n      [\n        251225,\n        'Jacaranda mimosifolia :: Jacaranda',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75927435,\n        -122.4422441\n      ],\n      [\n        54712,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4508 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:58',\n        2017,\n        0,\n        3,\n        37.76234527,\n        -122.5063635\n      ],\n      [\n        3193,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '3698 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78640011,\n        -122.454267\n      ],\n      [\n        21682,\n        'Prunus serrulata :: Ornamental Cherry',\n        '1830 Sutter St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.78651577,\n        -122.4305403\n      ],\n      [\n        97322,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1055 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:42',\n        2017,\n        0,\n        3,\n        37.74287614,\n        -122.4775411\n      ],\n      [\n        175377,\n        'Ulmus pumila :: Siberian Elm',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        2,\n        37.71516198,\n        -122.4717512\n      ],\n      [\n        142720,\n        '::',\n        '1170 COLUMBUS AVE',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.80483123,\n        -122.416565\n      ],\n      [\n        251231,\n        'Pinus radiata :: Monterey Pine',\n        '164 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.72047169,\n        -122.4780251\n      ],\n      [\n        29943,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '242 Broad St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:23',\n        2017,\n        0,\n        3,\n        37.71323398,\n        -122.4605594\n      ],\n      [\n        97323,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1011 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:25',\n        2017,\n        0,\n        3,\n        37.74289372,\n        -122.4772138\n      ],\n      [\n        11582,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4000 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:49',\n        2017,\n        0,\n        3,\n        37.76055019,\n        -122.5050036\n      ],\n      [\n        251230,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        251232,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        179480,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '100 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.72024935,\n        -122.4780157\n      ],\n      [\n        251221,\n        'Tree(s) ::',\n        '2401X Washington St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        48827,\n        'Lophostemon confertus :: Brisbane Box',\n        '435 Pacific Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/7/02 0:00',\n        2002,\n        15,\n        5,\n        37.79736024,\n        -122.4027685\n      ],\n      [\n        251242,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2449 Golden Gate Ave',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7770353,\n        -122.4514366\n      ],\n      [\n        48332,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/02 0:00',\n        2002,\n        15,\n        null,\n        null,\n        null\n      ],\n      [\n        175044,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80x Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.71158043,\n        -122.4709734\n      ],\n      [\n        251226,\n        'Koelreuteria paniculata :: Golden Rain Tree',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75950087,\n        -122.4421669\n      ],\n      [\n        17881,\n        'Olea europaea :: Olive Tree',\n        '2205 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78806671,\n        -122.4322373\n      ],\n      [\n        24515,\n        'Lophostemon confertus :: Brisbane Box',\n        '2030 Fell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/7/16 7:40',\n        2016,\n        1,\n        3,\n        37.77238102,\n        -122.4513341\n      ],\n      [\n        97317,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1000 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:59',\n        2017,\n        0,\n        3,\n        37.74308434,\n        -122.4769891\n      ],\n      [\n        2985,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2253 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.78912498,\n        -122.4314293\n      ],\n      [\n        182083,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1101 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:33',\n        2017,\n        0,\n        3,\n        37.74285808,\n        -122.4780077\n      ],\n      [\n        18700,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '100X Richardson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.79975916,\n        -122.4460044\n      ],\n      [\n        9914,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '1900 Gough St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        126,\n        37.79116413,\n        -122.4257789\n      ],\n      [\n        93463,\n        \"Prunus x 'Amanogawa' :: Flowering Cherry\",\n        '1739 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:05',\n        2017,\n        0,\n        3,\n        37.74255391,\n        -122.4849289\n      ],\n      [\n        15429,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2125 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/15 0:00',\n        2015,\n        2,\n        15,\n        37.76296062,\n        -122.419386\n      ],\n      [\n        138801,\n        'Maytenus boaria :: Mayten',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/9/97 0:00',\n        1997,\n        20,\n        2,\n        37.78770158,\n        -122.4350685\n      ],\n      [\n        251243,\n        \"Ceanothus 'Ray Hartman' :: California Lilac 'Ray Hartman'\",\n        '299x Chapman St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7417247,\n        -122.411639\n      ],\n      [\n        138359,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78607841,\n        -122.4338643\n      ],\n      [\n        251248,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '829 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73477716,\n        -122.4360662\n      ],\n      [\n        138368,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78600955,\n        -122.4344937\n      ],\n      [\n        138807,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2355 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78777373,\n        -122.4346006\n      ],\n      [\n        57209,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '2898 Clay St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '12/15/14 0:00',\n        2014,\n        3,\n        3,\n        37.78998742,\n        -122.4404101\n      ],\n      [\n        210495,\n        'Myoporum laetum :: Myoporum',\n        '365 San Leandro Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        21,\n        37.73051136,\n        -122.4689316\n      ],\n      [\n        138369,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78599791,\n        -122.4345397\n      ],\n      [\n        6069,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1930 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78057618,\n        -122.439557\n      ],\n      [\n        138802,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78771109,\n        -122.4350205\n      ],\n      [\n        138365,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.78603526,\n        -122.4342542\n      ],\n      [\n        251246,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '722X Mendell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73887284,\n        -122.3866046\n      ],\n      [\n        251245,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '701 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/4/92 0:00',\n        1992,\n        25,\n        null,\n        37.73445362,\n        -122.4338962\n      ],\n      [\n        103786,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '1221 Polk St',\n        false,\n        'Sidewalk: Curb side : Pot',\n        'Tree',\n        '11/1/09 0:00',\n        2009,\n        8,\n        3,\n        37.78810145,\n        -122.4202677\n      ],\n      [\n        138362,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78605488,\n        -122.4340541\n      ],\n      [\n        134567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '99 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/6/11 0:00',\n        2011,\n        6,\n        4,\n        37.77846618,\n        -122.4173997\n      ],\n      [\n        138367,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78601298,\n        -122.4344509\n      ]\n    ]\n  }\n};\n"
  },
  {
    "path": "examples/custom-reducer/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport Modal from 'react-modal';\nimport {Provider} from 'react-redux';\nimport store from './store';\nimport App from './app';\n\nModal.setAppElement('#root');\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/custom-reducer/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer, {uiStateUpdaters, enhanceReduxMiddleware} from '@kepler.gl/reducers';\nimport appReducer from './app-reducer';\nimport Window from 'global/window';\n\nconst customizedKeplerGlReducer = keplerGlReducer\n  .initialState({\n    uiState: {\n      // hide side panel to disallower user customize the map\n      readOnly: true,\n\n      // customize which map control button to show\n      mapControls: {\n        ...uiStateUpdaters.DEFAULT_MAP_CONTROLS,\n        visibleLayers: {\n          show: false\n        },\n        mapLegend: {\n          show: true,\n          active: true\n        },\n        toggle3d: {\n          show: false\n        },\n        splitMap: {\n          show: false\n        }\n      }\n    }\n  })\n  // handle additional actions\n  .plugin({\n    HIDE_AND_SHOW_SIDE_PANEL: state => ({\n      ...state,\n      uiState: {\n        ...state.uiState,\n        readOnly: !state.uiState.readOnly\n      }\n    })\n  });\n\nconst reducers = combineReducers({\n  keplerGl: customizedKeplerGlReducer,\n  app: appReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nconst initialState = {};\n\n// add redux devtools\nconst composeEnhancers = Window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\nexport default createStore(reducers, initialState, composeEnhancers(...enhancers));\n"
  },
  {
    "path": "examples/custom-theme/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ]\n}\n"
  },
  {
    "path": "examples/custom-theme/README.md",
    "content": "# Customize kepler.gl Theme\n\nThis example show how to customize Kepler.gl theme\n  1. Define an object (theme) to override Kepler.gl style\n  2. Pass the newly created object as prop to KeplerGl react component\n\n#### 1. Install\n\n```sh\nyarn\n```\n\n\n#### 2. Mapbox Token\nadd mapbox access token to node env\n\n```sh\nexport MapboxAccessToken=<your_mapbox_token>\n```\n\n#### 3. Start the app\n\n```sh\nyarn start\n```\n"
  },
  {
    "path": "examples/custom-theme/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\n\nconst args = process.argv;\n\nconst port = 8080;\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// Ensure a single instance of React and friends to avoid invalid hook calls\nconst ROOT_NODE_MODULES = join('..', '..', 'node_modules');\nconst thirdPartyAliases = {\n  react: join(ROOT_NODE_MODULES, 'react'),\n  'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),\n  'react-redux': join(ROOT_NODE_MODULES, 'react-redux', 'lib'),\n  'styled-components': join(ROOT_NODE_MODULES, 'styled-components'),\n  'apache-arrow': join(ROOT_NODE_MODULES, 'apache-arrow')\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || '')\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    replace({\n      __PACKAGE_VERSION__: '3.1.10',\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    const result = await esbuild\n      .build({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: true,\n        sourcemap: false,\n        metafile: true,\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n    fs.writeFileSync('dist/esbuild-metadata.json', JSON.stringify(result.metafile));\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`kepler.gl custom-theme example running at ${`http://localhost:${port}`}`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/custom-theme/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <title>kepler.gl demo</title>\n    <link rel=\"stylesheet\" href=\"//d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n    <style type=\"text/css\">\n       body {margin: 0; padding: 0; overflow: hidden;}\n    </style>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src=\"/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/custom-theme/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start-local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/components\": \"^3.1.10\",\n    \"@kepler.gl/reducers\": \"^3.1.10\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/custom-theme/src/actions.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// CONSTANTS\nexport const INIT = 'INIT';\n\n// ACTIONS\nexport function initApp() {\n  return {\n    type: INIT\n  };\n}\n"
  },
  {
    "path": "examples/custom-theme/src/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useState} from 'react';\nimport styled from 'styled-components';\nimport Window from 'global/window';\nimport {connect} from 'react-redux';\nimport KeplerGl from '@kepler.gl/components';\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line\n\nconst theme = {\n  sidePanelBg: '#ffffff',\n  titleTextColor: '#000000',\n  sidePanelHeaderBg: '#f7f7F7',\n  subtextColorActive: '#2473bd',\n  tooltipBg: '#1869b5',\n  tooltipColor: '#ffffff',\n  dropdownListBgd: '#ffffff',\n  textColorHl: '#2473bd',\n  inputBgd: '#f7f7f7',\n  inputBgdHover: '#ffffff',\n  inputBgdActive: '#ffffff',\n  dropdownListHighlightBg: '#f0f0f0',\n  panelBackground: '#f7f7F7',\n  panelBackgroundHover: '#f7f7F7',\n  secondaryInputBgd: '#f7f7F7',\n  secondaryInputBgdActive: '#f7f7F7',\n  secondaryInputBgdHover: '#ffffff',\n  panelActiveBg: '#f7f7F7'\n};\n\nconst emptyTheme = {};\n\nconst StyleSwitch = styled.div`\n  position: absolute;\n  bottom: 24px;\n  right: 24px;\n  background-color: whitesmoke;\n  padding: 4px;\n  z-index: 1000;\n  border-radius: 3px;\n  border: 1px solid mediumseagreen;\n`;\n\nfunction App() {\n  const [customTheme, setTheme] = useState(false);\n  const [windowDimension, setDimension] = useState({\n    width: Window.innerWidth,\n    height: Window.innerHeight\n  });\n\n  useEffect(() => {\n    const handleResize = () => {\n      setDimension({width: Window.innerWidth, height: Window.innerHeight});\n    };\n    Window.addEventListener('resize', handleResize);\n    return () => Window.removeEventListener('resize', handleResize);\n  }, []);\n\n  return (\n    <div>\n      <StyleSwitch>\n        <label htmlFor=\"custom-theme\">Custom theme</label>\n        <input\n          type=\"checkbox\"\n          checked={customTheme}\n          id=\"custom-theme\"\n          onChange={e => setTheme(e.target.checked)}\n        />\n      </StyleSwitch>\n      <KeplerGl\n        mapboxApiAccessToken={MAPBOX_TOKEN}\n        id=\"map\"\n        /*\n         * Specify path to keplerGl state, because it is not mount at the root\n         */\n        getState={state => state.demo.keplerGl}\n        width={windowDimension.width}\n        height={windowDimension.height}\n        theme={customTheme ? theme : emptyTheme}\n      />\n    </div>\n  );\n  // }\n}\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/custom-theme/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport Modal from 'react-modal';\nimport {Provider} from 'react-redux';\nimport store from './store';\nimport App from './app';\n\nModal.setAppElement('#root');\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/custom-theme/src/reducers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {combineReducers} from 'redux';\nimport {handleActions} from 'redux-actions';\n\nimport keplerGlReducer from '@kepler.gl/reducers';\n\nimport {INIT} from '../actions';\n\n// INITIAL_APP_STATE\nconst initialAppState = {\n  appName: 'example'\n};\n\n// App reducer\nexport const appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    })\n  },\n  initialAppState\n);\n\n// export demoReducer to be combined in website app\nexport default combineReducers({\n  // mount keplerGl reducer\n  keplerGl: keplerGlReducer,\n  app: appReducer\n});\n"
  },
  {
    "path": "examples/custom-theme/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Window from 'global/window';\nimport {combineReducers, createStore, applyMiddleware, compose} from 'redux';\nimport {enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\nimport demoReducer from './reducers/index';\n\nconst reducers = combineReducers({\n  demo: demoReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\n\nexport const enhancers = [applyMiddleware(...middlewares)];\n\nconst initialState = {};\n\n// add redux devtools\nconst composeEnhancers = Window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\nexport default createStore(reducers, initialState, composeEnhancers(...enhancers));\n"
  },
  {
    "path": "examples/demo-app/.yarnrc.yml",
    "content": "# https://yarnpkg.com/configuration/yarnrc\nnodeLinker: node-modules\n# Define the registry to use when fetching packages.\nnpmRegistryServer: 'https://registry.yarnpkg.com'\n"
  },
  {
    "path": "examples/demo-app/README.md",
    "content": "# Demo App\n\nThis is the src code of kepler.gl demo app. You can copy this folder out and run it locally.\n\n#### Pre requirement\n- [Node.js ^18.x](http://nodejs.org): We use Node to generate the documentation, run a\n  development web server, run tests, and generate distributable files. Depending on your system,\n  you can install Node either from source or as a pre-packaged bundle.\n- [Yarn 4.4.0](https://yarnpkg.com): We use Yarn to install our Node.js module dependencies\n  (rather than using npm). See the detailed [installation instructions][yarn-install].\n\n#### 1. Install Dependencies\n\nGo to the root directory and install the dependencies using yarn:\n\n```sh\nyarn bootstrap\n```\n\nThen, go to the `examples/demo-app` directory and install the dependencies using yarn:\n\n```sh\nyarn install\n```\n\n#### 2. Environment Variables\nCreate a `.env` file at the root directory by copying from `.env.template`:\n\n```sh\ncp .env.template .env\n```\n\nThen update the following environment variables in your `.env` file:\n\n```sh\nMAPBOX_ACCESS_TOKEN=<your_mapbox_token>\nDROPBOX_CLIENT_ID=<your_dropbox_client_id>\nMAPBOX_EXPORT_TOKEN=<your_mapbox_export_token>\nCARTO_CLIENT_ID=<your_carto_client_id>\nFOURSQUARE_CLIENT_ID=<your_foursquare_client_id>\nFOURSQUARE_DOMAIN=<your_foursquare_domain>\nFOURSQUARE_USER_MAPS_URL=<your_foursquare_user_map_url>\n```\n\n#### 3. Start the app\n\n```sh\nyarn start:local\n```\n\n[yarn-install]: https://yarnpkg.com/getting-started/install\n"
  },
  {
    "path": "examples/demo-app/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\nimport KeplerPackage from '../../package.json' assert {type: 'json'};\n\nconst args = process.argv;\n\nconst BASE_NODE_MODULES_DIR = './node_modules';\n\nconst LIB_DIR = '../../';\nconst NODE_MODULES_DIR = join(LIB_DIR, 'node_modules');\nconst SRC_DIR = join(LIB_DIR, 'src');\n\n// For debugging deck.gl, load deck.gl from external deck.gl directory\nconst EXTERNAL_DECK_SRC = join(LIB_DIR, 'deck.gl');\n\n// For debugging loaders.gl, load loaders.gl from external loaders.gl directory\nconst EXTERNAL_LOADERS_SRC = join(LIB_DIR, 'loaders.gl');\n\n// For debugging hubble.gl, load hubble.gl from external hubble.gl directory\nconst EXTERNAL_HUBBLE_SRC = join(LIB_DIR, '../../hubble.gl');\n\nconst port = 8080;\n\nconst getThirdPartyLibraryAliases = useKeplerNodeModules => {\n  const nodeModulesDir = useKeplerNodeModules ? NODE_MODULES_DIR : BASE_NODE_MODULES_DIR;\n\n  const localSources = useKeplerNodeModules\n    ? {\n        // Suppress useless warnings from react-date-picker's dep\n        'tiny-warning': `${SRC_DIR}/utils/src/noop.ts`\n      }\n    : {};\n\n  return {\n    ...localSources,\n    react: `${nodeModulesDir}/react`,\n    'react-dom': `${nodeModulesDir}/react-dom`,\n    'react-redux': `${nodeModulesDir}/react-redux/lib`,\n    'styled-components': `${nodeModulesDir}/styled-components`,\n    'react-intl': `${nodeModulesDir}/react-intl`,\n    'react-palm': `${nodeModulesDir}/react-palm`,\n    // kepler.gl and loaders.gl need to use same apache-arrow\n    'apache-arrow': `${nodeModulesDir}/apache-arrow`\n  };\n};\n\n// Env variables required for demo app\nconst requiredEnvVariables = [\n  'MapboxAccessToken',\n  'DropboxClientId',\n  'MapboxExportToken',\n  'CartoClientId',\n  'FoursquareClientId',\n  'FoursquareDomain',\n  'FoursquareAPIURL',\n  'FoursquareUserMapsURL'\n];\n\n/**\n * Check for all required env variables to be present\n */\nconst checkEnvVariables = () => {\n  const missingVars = requiredEnvVariables.filter(key => !process.env[key]);\n\n  if (missingVars.length > 0) {\n    console.warn(`⚠️  Warning: Missing environment variables: ${missingVars.join(', ')}`);\n  } else {\n    console.log('✅ All required environment variables are set.');\n  }\n};\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    // Define process.env variables for browser environment\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),\n    'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),\n    'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),\n    'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),\n    'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),\n    'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),\n    'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),\n    'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || ''),\n    'process.env.NODE_ENV': NODE_ENV\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    // automatically injected kepler.gl package version into the bundle\n    replace({\n      __PACKAGE_VERSION__: KeplerPackage.version,\n      include: /constants\\/src\\/default-settings\\.ts/\n    })\n  ]\n};\n\nfunction addAliases(externals, args) {\n  const resolveAlias = getThirdPartyLibraryAliases(true);\n\n  // Combine flags\n  const useLocalDeck = args.includes('--env.deck') || args.includes('--env.hubble_src');\n  const useRepoDeck = args.includes('--env.deck_src');\n  const useLocalAiAssistant = args.includes('--env.ai');\n\n  // resolve ai-assistant from local dir\n  if (useLocalAiAssistant) {\n    resolveAlias['@openassistant/core'] = join(LIB_DIR, '../openassistant/packages/core/src');\n    resolveAlias['@openassistant/ui'] = join(LIB_DIR, '../openassistant/packages/ui/src');\n    resolveAlias['@openassistant/echarts'] = join(\n      LIB_DIR,\n      '../openassistant/packages/components/echarts/src'\n    );\n    resolveAlias['@openassistant/tables'] = join(\n      LIB_DIR,\n      '../openassistant/packages/components/tables/src'\n    );\n    resolveAlias['@openassistant/geoda'] = join(\n      LIB_DIR,\n      '../openassistant/packages/tools/geoda/src'\n    );\n    resolveAlias['@openassistant/duckdb'] = join(\n      LIB_DIR,\n      '../openassistant/packages/tools/duckdb/src'\n    );\n    resolveAlias['@openassistant/plots'] = join(\n      LIB_DIR,\n      '../openassistant/packages/tools/plots/src'\n    );\n    resolveAlias['@openassistant/osm'] = join(LIB_DIR, '../openassistant/packages/tools/osm/src');\n    resolveAlias['@openassistant/utils'] = join(LIB_DIR, '../openassistant/packages/utils/src');\n    resolveAlias['@kepler.gl/ai-assistant'] = join(SRC_DIR, 'ai-assistant/src');\n  }\n\n  // resolve deck.gl from local dir\n  if (useLocalDeck || useRepoDeck) {\n    // Load deck.gl from root node_modules\n    // if env.deck_src Load deck.gl from deck.gl/modules/main/src folder parallel to kepler.gl\n    resolveAlias['deck.gl'] = useLocalDeck\n      ? `${NODE_MODULES_DIR}/deck.gl/src`\n      : `${EXTERNAL_DECK_SRC}/modules/main/src`;\n\n    // if env.deck Load @deck.gl modules from root node_modules/@deck.gl\n    // if env.deck_src Load @deck.gl modules from  deck.gl/modules folder parallel to kepler.gl\n    externals['deck.gl'].forEach(mdl => {\n      resolveAlias[`@deck.gl/${mdl}`] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/@deck.gl/${mdl}/src`\n        : `${EXTERNAL_DECK_SRC}/modules/${mdl}/src`;\n      // types are stored in different directory\n      resolveAlias[`@deck.gl/${mdl}/typed`] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/@deck.gl/${mdl}/typed`\n        : `${EXTERNAL_DECK_SRC}/modules/${mdl}/src/types`;\n    });\n\n    ['luma.gl', 'probe.gl', 'loaders.gl'].forEach(name => {\n      // if env.deck Load ${name} from root node_modules\n      // if env.deck_src Load ${name} from deck.gl/node_modules folder parallel to kepler.gl\n      resolveAlias[name] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/${name}/src`\n        : name === 'probe.gl'\n        ? `${EXTERNAL_DECK_SRC}/node_modules/${name}/src`\n        : `${EXTERNAL_DECK_SRC}/node_modules/@${name}/core/src`;\n\n      // if env.deck Load @${name} modules from root node_modules/@${name}\n      // if env.deck_src Load @${name} modules from deck.gl/node_modules/@${name} folder parallel to kepler.gl`\n      externals[name].forEach(mdl => {\n        resolveAlias[`@${name}/${mdl}`] = useLocalDeck\n          ? `${NODE_MODULES_DIR}/@${name}/${mdl}/src`\n          : `${EXTERNAL_DECK_SRC}/node_modules/@${name}/${mdl}/src`;\n      });\n    });\n  }\n\n  if (args.includes('--env.loaders_src')) {\n    externals['loaders.gl'].forEach(mdl => {\n      resolveAlias[`@loaders.gl/${mdl}`] = `${EXTERNAL_LOADERS_SRC}/modules/${mdl}/src`;\n    });\n  }\n\n  if (args.includes('--env.hubble_src')) {\n    externals['hubble.gl'].forEach(mdl => {\n      resolveAlias[`@hubble.gl/${mdl}`] = `${EXTERNAL_HUBBLE_SRC}/modules/${mdl}/src`;\n    });\n  }\n\n  return resolveAlias;\n}\n\nfunction openURL(url) {\n  // Could potentially be replaced by https://www.npmjs.com/package/open, it was throwing an error when tried last\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  // local dev\n\n  const modules = ['@deck.gl', '@loaders.gl', '@luma.gl', '@probe.gl', '@hubble.gl'];\n  const loadAllDirs = modules.map(\n    dir =>\n      new Promise(success => {\n        fs.readdir(join(NODE_MODULES_DIR, dir), (err, items) => {\n          if (err) {\n            const colorRed = '\\x1b[31m';\n            const colorReset = '\\x1b[0m';\n            console.log(\n              `${colorRed}%s${colorReset}`,\n              `Cannot find ${dir} in node_modules, make sure it is installed. ${err}`\n            );\n\n            success(null);\n          }\n          success(items);\n        });\n      })\n  );\n\n  const externals = await Promise.all(loadAllDirs).then(results => ({\n    'deck.gl': results[0],\n    'loaders.gl': results[1],\n    'luma.gl': results[2],\n    'probe.gl': results[3],\n    'hubble.gl': results[4]\n  }));\n\n  const localAliases = addAliases(externals, args);\n\n  if (args.includes('--build')) {\n    await esbuild\n      .build({\n        ...config,\n        minify: true,\n        sourcemap: false,\n        // Add alias resolution for build\n        alias: {\n          ...getThirdPartyLibraryAliases(true)\n        },\n        // Add these production optimizations\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true,\n        metafile: true,\n        // Optionally generate a bundle analysis\n        plugins: [\n          ...config.plugins,\n          {\n            name: 'bundle-analyzer',\n            setup(build) {\n              build.onEnd(result => {\n                if (result.metafile) {\n                  // Write bundle analysis to disk\n                  fs.writeFileSync('meta.json', JSON.stringify(result.metafile));\n                }\n              });\n            }\n          }\n        ]\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      })\n      .then(() => {\n        checkEnvVariables();\n      });\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        minify: false,\n        sourcemap: true,\n        // add alias to resolve libraries so there is only one copy of them\n        ...(process.env.NODE_ENV === 'local'\n          ? {alias: localAliases}\n          : {alias: getThirdPartyLibraryAliases(false)}),\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        checkEnvVariables();\n\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(\n          `kepler.gl demo app running at ${`http://localhost:${port}`}, press Ctrl+C to stop`\n        );\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/demo-app/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"deploy\": \"yarn build && netlify deploy -d dist\",\n    \"deploy:prod\": \"yarn build && netlify deploy -d dist --prod\",\n    \"start:local\": \"NODE_ENV=local node esbuild.config.mjs --start\",\n    \"start:local-ai\": \"NODE_ENV=local node esbuild.config.mjs --start --env.ai\",\n    \"start:local-deck\": \"NODE_ENV=local node esbuild.config.mjs --start --env.deck\",\n    \"start:local-deck-src\": \"NODE_ENV=local node esbuild.config.mjs --start --env.deck_src\",\n    \"start:local-loaders-src\": \"NODE_ENV=local node esbuild.config.mjs --start --env.loaders_src\",\n    \"start:local-hubble-src\": \"NODE_ENV=local node esbuild.config.mjs --start --env.hubble_src\"\n  },\n  \"dependencies\": {\n    \"@auth0/auth0-spa-js\": \"^2.1.2\",\n    \"@carto/toolkit\": \"0.0.1-rc.18\",\n    \"@emotion/is-prop-valid\": \"^1.2.1\",\n    \"@kepler.gl/actions\": \"^3.2.0\",\n    \"@kepler.gl/ai-assistant\": \"^3.2.0\",\n    \"@kepler.gl/cloud-providers\": \"^3.2.0\",\n    \"@kepler.gl/components\": \"^3.2.0\",\n    \"@kepler.gl/constants\": \"^3.2.0\",\n    \"@kepler.gl/duckdb\": \"^3.2.0\",\n    \"@kepler.gl/processors\": \"^3.2.0\",\n    \"@kepler.gl/reducers\": \"^3.2.0\",\n    \"@kepler.gl/schemas\": \"^3.2.0\",\n    \"@kepler.gl/styles\": \"^3.2.0\",\n    \"@kepler.gl/utils\": \"^3.2.0\",\n    \"@loaders.gl/arrow\": \"^4.3.2\",\n    \"@loaders.gl/core\": \"^4.3.2\",\n    \"@loaders.gl/csv\": \"^4.3.2\",\n    \"@loaders.gl/json\": \"^4.3.2\",\n    \"@loaders.gl/parquet\": \"^4.3.2\",\n    \"@openassistant/core\": \"^0.5.17\",\n    \"@openassistant/ui\": \"^0.5.17\",\n    \"@types/classnames\": \"^2.3.1\",\n    \"@types/keymirror\": \"^0.1.1\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"classnames\": \"^2.2.1\",\n    \"d3-format\": \"^2.0.0\",\n    \"dropbox\": \"^4.0.12\",\n    \"esbuild\": \"^0.25.0\",\n    \"global\": \"^4.3.0\",\n    \"keymirror\": \"^0.1.1\",\n    \"markdown-to-jsx\": \"^7.7.6\",\n    \"prop-types\": \"^15.6.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-intl\": \"^6.3.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-resizable-panels\": \"^2.1.7\",\n    \"react-router\": \"3.2.5\",\n    \"react-router-redux\": \"^4.0.8\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux\": \"^4.2.1\",\n    \"redux-actions\": \"^2.2.1\",\n    \"redux-logger\": \"^3.0.6\",\n    \"redux-thunk\": \"^1.0.0\",\n    \"styled-components\": \"6.1.8\",\n    \"usehooks-ts\": \"^3.1.0\"\n  },\n  \"resolutions\": {\n    \"@luma.gl/core\": \"8.5.21\",\n    \"@luma.gl/webgl\": \"8.5.21\",\n    \"@deck.gl/core\": \"8.9.27\",\n    \"@deck.gl/extensions\": \"8.9.27\",\n    \"react-vis\": \"1.11.7\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\",\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/actions.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {push} from 'react-router-redux';\nimport {fetch} from 'global';\n\nimport {loadFiles, toggleModal} from '@kepler.gl/actions';\nimport {parseUri} from '@kepler.gl/common-utils';\nimport {load} from '@loaders.gl/core';\nimport {CSVLoader} from '@loaders.gl/csv';\nimport {GeoArrowLoader} from '@loaders.gl/arrow';\nimport {_GeoJSONLoader as GeoJSONLoader} from '@loaders.gl/json';\nimport {ParquetWasmLoader} from '@loaders.gl/parquet';\n\nimport {\n  LOADING_SAMPLE_ERROR_MESSAGE,\n  LOADING_SAMPLE_LIST_ERROR_MESSAGE,\n  MAP_CONFIG_URL\n} from './constants/default-settings';\n\n// CONSTANTS\nexport const INIT = 'INIT';\nexport const LOAD_REMOTE_RESOURCE_SUCCESS = 'LOAD_REMOTE_RESOURCE_SUCCESS';\nexport const LOAD_REMOTE_DATASET_PROCESSED_SUCCESS = 'LOAD_REMOTE_DATASET_PROCESSED_SUCCESS';\nexport const LOAD_REMOTE_RESOURCE_ERROR = 'LOAD_REMOTE_RESOURCE_ERROR';\nexport const LOAD_MAP_SAMPLE_FILE = 'LOAD_MAP_SAMPLE_FILE';\nexport const SET_SAMPLE_LOADING_STATUS = 'SET_SAMPLE_LOADING_STATUS';\n\n// Sharing\nexport const PUSHING_FILE = 'PUSHING_FILE';\nexport const CLOUD_LOGIN_SUCCESS = 'CLOUD_LOGIN_SUCCESS';\n\n// ACTIONS\nexport function initApp() {\n  return {\n    type: INIT\n  };\n}\n\nexport function loadRemoteResourceSuccess(response, config, options, remoteDatasetConfig) {\n  return {\n    type: LOAD_REMOTE_RESOURCE_SUCCESS,\n    response,\n    config,\n    options,\n    remoteDatasetConfig\n  };\n}\n\nexport function loadRemoteDatasetProcessedSuccessAction(result) {\n  return {\n    type: LOAD_REMOTE_DATASET_PROCESSED_SUCCESS,\n    payload: result\n  };\n}\n\nexport function loadRemoteResourceError(error, url) {\n  return {\n    type: LOAD_REMOTE_RESOURCE_ERROR,\n    error,\n    url\n  };\n}\n\nexport function loadMapSampleFile(samples) {\n  return {\n    type: LOAD_MAP_SAMPLE_FILE,\n    samples\n  };\n}\n\nexport function setLoadingMapStatus(isMapLoading) {\n  return {\n    type: SET_SAMPLE_LOADING_STATUS,\n    isMapLoading\n  };\n}\n\n/**\n * Actions passed to kepler.gl, called\n *\n * Note: exportFile is called on both saving and sharing\n *\n * @param {*} param0\n */\nexport function onExportFileSuccess({provider, options}) {\n  return dispatch => {\n    // if isPublic is true, use share Url\n    if (options.isPublic && provider.getShareUrl) {\n      dispatch(push(provider.getShareUrl(false)));\n    } else if (!options.isPublic && provider.getMapUrl) {\n      // if save private map to storage, use map url\n      dispatch(push(provider.getMapUrl(false)));\n    }\n  };\n}\n\nexport function onLoadCloudMapSuccess({provider, loadParams}) {\n  return dispatch => {\n    const mapUrl = provider?.getMapUrl(loadParams);\n    if (mapUrl) {\n      const url = `/demo/map/${provider.name}?path=${mapUrl}`;\n      dispatch(push(url));\n    }\n  };\n}\n\n// This can be moved into Kepler.gl to provide ability to load data from remote URLs\n/**\n * The method is able to load both data and kepler.gl files.\n * It uses loadFile action to dispatch and add new datasets/configs\n * to the kepler.gl instance\n * @param options\n * @param {string} options.dataUrl the URL to fetch data from. Current supported file type json,csv, kepler.json\n * @returns {Function}\n */\nexport function loadRemoteMap(options) {\n  return dispatch => {\n    dispatch(setLoadingMapStatus(true));\n    // breakdown url into url+query params\n    loadRemoteRawData(options.dataUrl).then(\n      // In this part we turn the response into a FileBlob\n      // so we can use it to call loadFiles\n      ([file, url]) => {\n        const {file: filename} = parseUri(url);\n        dispatch(loadFiles([new File([file], filename)])).then(() =>\n          dispatch(setLoadingMapStatus(false))\n        );\n      },\n      error => {\n        const {target = {}} = error;\n        const {status, responseText} = target;\n        dispatch(loadRemoteResourceError({status, message: responseText}, options.dataUrl));\n      }\n    );\n  };\n}\n\n/**\n * Load a file from a remote URL\n * @param url\n * @returns {Promise<any>}\n */\nfunction loadRemoteRawData(url) {\n  if (!url) {\n    // TODO: we should return reject with an appropriate error\n    return Promise.resolve(null);\n  }\n  return fetch(url)\n    .then(resp => {\n      if (!resp.ok) {\n        return resp.text().then(text => {\n          throw new Error(text);\n        });\n      }\n      return resp.blob();\n    })\n    .then(data => {\n      return [data, url];\n    });\n}\n\n// The following methods are only used to load SAMPLES\n/**\n *\n * @param {Object} options\n * @param {string} [options.dataUrl] the URL to fetch data from, e.g. https://raw.githubusercontent.com/keplergl/kepler.gl-data/master/earthquakes/data.csv\n * @param {string} [options.configUrl] the URL string to fetch kepler config from, e.g. https://raw.githubusercontent.com/keplergl/kepler.gl-data/master/earthquakes/config.json\n * @param {string} [options.id] the id used as dataset unique identifier, e.g. earthquakes\n * @param {string} [options.label] the label used to describe the new dataset, e.g. California Earthquakes\n * @param {string} [options.queryType] the type of query to execute to load data/config, e.g. sample\n * @param {string} [options.imageUrl] the URL to fetch the dataset image to use in sample gallery\n * @param {string} [options.description] the description used in sample galley to define the current example\n * @param {string} [options.size] the number of entries displayed in the current sample\n * @param {string} [keplergl] url to fetch the full data/config generated by kepler\n * @returns {Function}\n */\nexport function loadSample(options, pushRoute = true) {\n  return (dispatch, getState) => {\n    const {routing} = getState();\n    if (options.id && pushRoute) {\n      dispatch(push(`/demo/${options.id}${routing.locationBeforeTransitions?.search ?? ''}`));\n    }\n    // if the sample has a kepler.gl config file url we load it\n    if (options.keplergl) {\n      dispatch(loadRemoteMap({dataUrl: options.keplergl}));\n    } else {\n      dispatch(loadRemoteSampleMap(options));\n    }\n\n    dispatch(setLoadingMapStatus(true));\n  };\n}\n\n/**\n * Load remote map with config and data\n * @param options {configUrl, dataUrl}\n * @returns {Function}\n */\nfunction loadRemoteSampleMap(options) {\n  return dispatch => {\n    // Load configuration first\n    const {configUrl, dataUrl, remoteDatasetConfigUrl} = options;\n    const toLoad = [loadRemoteConfig(configUrl)];\n    toLoad.push(dataUrl ? loadRemoteData(dataUrl) : null);\n    // Load remote dataset config for tiled layers\n    toLoad.push(remoteDatasetConfigUrl ? loadRemoteConfig(remoteDatasetConfigUrl) : null);\n\n    Promise.all(toLoad).then(\n      ([config, data, remoteDatasetConfig]) => {\n        // TODO: these two actions can be merged\n        dispatch(loadRemoteResourceSuccess(data, config, options, remoteDatasetConfig));\n        // TODO: toggleModal when async dataset task is done, show the spinner until then\n        dispatch(toggleModal(null));\n      },\n      error => {\n        if (error) {\n          const {target = {}} = error;\n          const {status, responseText} = target;\n          dispatch(\n            loadRemoteResourceError(\n              {\n                status,\n                message: `${responseText} - ${LOADING_SAMPLE_ERROR_MESSAGE} ${options.id} (${configUrl})`\n              },\n              configUrl\n            )\n          );\n        }\n      }\n    );\n  };\n}\n\n/**\n *\n * @param url\n * @returns {Promise<any>}\n */\nfunction loadRemoteConfig(url) {\n  if (!url) {\n    // TODO: we should return reject with an appropriate error\n    return Promise.resolve(null);\n  }\n\n  return fetch(url).then(resp => {\n    if (!resp.ok) {\n      return resp.text().then(text => {\n        throw new Error(text);\n      });\n    }\n    return resp.json();\n  });\n}\n\n/**\n *\n * @param url to fetch data from (csv, json, geojson)\n * @returns {Promise<any>}\n */\nfunction loadRemoteData(url) {\n  if (!url) {\n    // TODO: we should return reject with an appropriate error\n    return Promise.resolve(null);\n  }\n\n  // Load data\n  return new Promise(resolve => {\n    const loaders = [CSVLoader, GeoArrowLoader, ParquetWasmLoader, GeoJSONLoader];\n    const loadOptions = {\n      csv: {\n        shape: 'object-row-table'\n      },\n      arrow: {\n        shape: 'arrow-table'\n      },\n      parquet: {\n        shape: 'arrow-table'\n      },\n      metadata: true\n    };\n    const data = load(url, loaders, loadOptions);\n    resolve(data);\n  });\n}\n\n/**\n *\n * @param sampleMapId optional if we pass the sampleMapId, after fetching\n * map sample configurations we are going to load the actual map data if it exists\n * @returns {function(*)}\n */\nexport function loadSampleConfigurations(sampleMapId = null) {\n  return dispatch => {\n    fetch(MAP_CONFIG_URL)\n      .then(response => {\n        if (!response.ok) {\n          return response.text().then(text => {\n            throw new Error(text);\n          });\n        } else {\n          return response.json();\n        }\n      })\n      .then(samples => {\n        dispatch(loadMapSampleFile(samples));\n        // Load the specified map\n        const map = sampleMapId && samples.find(s => s.id === sampleMapId);\n        if (map) {\n          dispatch(loadSample(map, false));\n        }\n      })\n      .catch(error => {\n        dispatch(\n          loadRemoteResourceError(\n            {message: `${error} - ${LOADING_SAMPLE_LIST_ERROR_MESSAGE}`},\n            MAP_CONFIG_URL\n          )\n        );\n      });\n  };\n}\n"
  },
  {
    "path": "examples/demo-app/src/app.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useRef, useState} from 'react';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport styled, {ThemeProvider, StyleSheetManager} from 'styled-components';\nimport Window from 'global/window';\nimport {connect, useDispatch} from 'react-redux';\nimport cloneDeep from 'lodash/cloneDeep';\nimport isEqual from 'lodash/isEqual';\nimport {useSelector} from 'react-redux';\nimport isPropValid from '@emotion/is-prop-valid';\nimport {WebMercatorViewport} from '@deck.gl/core';\nimport {ScreenshotWrapper} from '@openassistant/ui';\nimport {\n  setStartScreenCapture,\n  setScreenCaptured,\n  AiAssistantPanel,\n  setMapBoundary\n} from '@kepler.gl/ai-assistant';\nimport {panelBorderColor, theme} from '@kepler.gl/styles';\nimport {ParsedConfig} from '@kepler.gl/types';\nimport {getApplicationConfig} from '@kepler.gl/utils';\nimport {SqlPanel} from '@kepler.gl/duckdb/components';\nimport Banner from './components/banner';\nimport Announcement, {FormLink} from './components/announcement';\nimport {replaceLoadDataModal} from './factories/load-data-modal';\nimport {replaceMapControl} from './factories/map-control';\nimport {replacePanelHeader} from './factories/panel-header';\nimport {CLOUD_PROVIDERS_CONFIGURATION, DEFAULT_FEATURE_FLAGS} from './constants/default-settings';\nimport {messages} from './constants/localization';\n\nimport {\n  loadRemoteMap,\n  loadSampleConfigurations,\n  onExportFileSuccess,\n  onLoadCloudMapSuccess\n} from './actions';\n\nimport {\n  loadCloudMap,\n  addDataToMap,\n  replaceDataInMap,\n  toggleMapControl,\n  toggleModal\n} from '@kepler.gl/actions';\nimport {CLOUD_PROVIDERS} from './cloud-providers';\nimport {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels';\n\nconst KeplerGl = require('@kepler.gl/components').injectComponents([\n  replaceLoadDataModal(),\n  replaceMapControl(),\n  replacePanelHeader()\n]);\n\n// Sample data\n/* eslint-disable no-unused-vars */\nimport sampleTripData, {testCsvData, sampleTripDataConfig} from './data/sample-trip-data';\n// import sampleGeojson from './data/sample-small-geojson';\n// import sampleGeojsonPoints from './data/sample-geojson-points';\nimport sampleGeojsonConfig from './data/sample-geojson-config';\nimport sampleH3Data, {config as h3MapConfig} from './data/sample-hex-id-csv';\nimport sampleS2Data, {config as s2MapConfig, dataId as s2DataId} from './data/sample-s2-data';\nimport sampleAnimateTrip, {\n  pointData,\n  pointDataId,\n  animateTripDataId,\n  replacePointData,\n  config as syncedTripConfig\n} from './data/sample-animate-trip-data';\nimport sampleIconCsv from './data/sample-icon-csv';\nimport sampleGpsData from './data/sample-gps-data';\nimport sampleRowData, {config as rowDataConfig} from './data/sample-row-data';\nimport {processCsvData, processGeojson, processRowObject} from '@kepler.gl/processors';\n\n/* eslint-enable no-unused-vars */\n\n// This implements the default behavior from styled-components v5\nfunction shouldForwardProp(propName, target) {\n  if (typeof target === 'string') {\n    // For HTML elements, forward the prop if it is a valid HTML attribute\n    return isPropValid(propName);\n  }\n  // For other elements, forward all props\n  return true;\n}\n\nconst BannerHeight = 48;\nconst BannerKey = `banner-${FormLink}`;\nconst keplerGlGetState = state => state.demo.keplerGl;\n\nconst GlobalStyle = styled.div`\n  font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;\n  font-weight: 400;\n  font-size: 0.875em;\n  line-height: 1.71429;\n\n  *,\n  *:before,\n  *:after {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n\n  ul {\n    margin: 0;\n    padding: 0;\n  }\n\n  li {\n    margin: 0;\n  }\n\n  a {\n    text-decoration: none;\n    color: ${props => props.theme.labelColor};\n  }\n`;\n\nconst CONTAINER_STYLE = {\n  transition: 'margin 1s, height 1s',\n  position: 'absolute',\n  width: '100%',\n  height: '100%',\n  left: 0,\n  top: 0,\n  display: 'flex',\n  flexDirection: 'column',\n  backgroundColor: '#333'\n};\n\nconst StyledResizeHandle = styled(PanelResizeHandle)`\n  background-color: ${panelBorderColor};\n  &:hover {\n    background-color: #555;\n  }\n  width: 100%;\n  height: 5px;\n  cursor: row-resize;\n`;\n\nconst StyledVerticalResizeHandle = styled(PanelResizeHandle)`\n  background-color: ${panelBorderColor};\n  width: 4px;\n  height: 100%;\n  cursor: row-resize;\n\n  &:hover {\n    background-color: #555;\n  }\n`;\n\nconst App = props => {\n  const [showBanner, toggleShowBanner] = useState(false);\n  const {params: {id, provider} = {}, location: {query = {}} = {}} = props;\n  const dispatch = useDispatch();\n\n  // TODO find another way to check for existence of duckDb plugin\n  const duckDbPluginEnabled = (getApplicationConfig().plugins || []).some(p => p.name === 'duckdb');\n\n  const isSqlPanelOpen = useSelector(\n    state => duckDbPluginEnabled && state?.demo?.keplerGl?.map?.uiState.mapControls.sqlPanel?.active\n  );\n\n  const isAiAssistantPanelOpen = useSelector(\n    state => state?.demo?.keplerGl?.map?.uiState.mapControls.aiAssistant?.active\n  );\n\n  const prevQueryRef = useRef<number>(null);\n\n  useEffect(() => {\n    // if we pass an id as part of the url\n    // we try to fetch along map configurations\n    const cloudProvider = CLOUD_PROVIDERS.find(c => c.name === provider);\n    if (cloudProvider) {\n      // Prevent constant reloading after change of the location\n      if (isEqual(prevQueryRef.current, {provider, id, query})) {\n        return;\n      }\n\n      dispatch(\n        loadCloudMap({\n          loadParams: query,\n          provider: cloudProvider,\n          onSuccess: onLoadCloudMapSuccess\n        })\n      );\n      prevQueryRef.current = {provider, id, query};\n      return;\n    }\n\n    // Load sample using its id\n    if (id) {\n      dispatch(loadSampleConfigurations(id));\n    }\n\n    // Load map using a custom\n    if (query.mapUrl) {\n      // TODO?: validate map url\n      dispatch(loadRemoteMap({dataUrl: query.mapUrl}));\n    }\n\n    if (duckDbPluginEnabled && query.sql) {\n      dispatch(toggleMapControl('sqlPanel', 0));\n      dispatch(toggleModal(null));\n    }\n\n    // delay zs to show the banner\n    // if (!window.localStorage.getItem(BannerKey)) {\n    //   window.setTimeout(_showBanner, 3000);\n    // }\n    // load sample data\n    _loadSampleData();\n\n    // Notifications\n\n    // no dependencies, as this was part of componentDidMount\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  /**\n   * Update map boundary when view state changes, used by ai-assistant to\n   * get data from vector tiles when map boundary changes\n   */\n  const onViewStateChange = useCallback(\n    viewState => {\n      const viewport = new WebMercatorViewport(viewState);\n      const nw = viewport.unproject([0, 0]);\n      const se = viewport.unproject([viewport.width, viewport.height]);\n      dispatch(setMapBoundary(nw, se));\n    },\n    [dispatch]\n  );\n\n  const _setStartScreenCapture = useCallback(\n    flag => {\n      dispatch(setStartScreenCapture(flag));\n    },\n    [dispatch]\n  );\n\n  const _setScreenCaptured = useCallback(\n    screenshot => {\n      dispatch(setScreenCaptured(screenshot));\n    },\n    [dispatch]\n  );\n\n  /*\n  const _showBanner = useCallback(() => {\n    toggleShowBanner(true);\n  }, [toggleShowBanner]);\n  */\n\n  const hideBanner = useCallback(() => {\n    toggleShowBanner(false);\n  }, [toggleShowBanner]);\n\n  const _disableBanner = useCallback(() => {\n    hideBanner();\n    Window.localStorage.setItem(BannerKey, 'true');\n  }, [hideBanner]);\n\n  const _loadRowData = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'Sample Visit Data',\n              id: 'sample_visit_data'\n            },\n            data: processRowObject(sampleRowData)\n          }\n        ],\n        config: rowDataConfig\n      })\n    );\n  }, [dispatch]);\n\n  const _loadVectorTileData = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'Railroads',\n              id: 'railroads.pmtiles',\n              color: [255, 0, 0],\n              type: 'vector-tile'\n            },\n            data: {\n              rows: [],\n              fields: [\n                {\n                  name: 'continent',\n                  type: 'string',\n                  format: '',\n                  analyzerType: 'STRING'\n                }\n              ]\n            },\n            metadata: {\n              name: 'output.pmtiles',\n              description: 'output.pmtiles',\n              type: 'remote',\n              remoteTileFormat: 'pmtiles',\n              tilesetDataUrl:\n                'https://4sq-studio-public.s3.us-west-2.amazonaws.com/pmtiles-test/161727fe-7952-4e57-aa05-850b3086b0b2.pmtiles',\n              tilesetMetadataUrl:\n                'https://4sq-studio-public.s3.us-west-2.amazonaws.com/pmtiles-test/161727fe-7952-4e57-aa05-850b3086b0b2.pmtiles',\n              id: 'sz6uy1xtj',\n              format: 'rows',\n              label: 'output.pmtiles',\n              metaJson: null,\n              bounds: [-150.1122219, -51.8952777, 179.3577783, 69.6043747],\n              center: [14.0625, 50.7026397, 6],\n              maxZoom: 6,\n              minZoom: 0,\n              fields: [\n                {\n                  name: 'continent',\n                  id: 'continent',\n                  format: '',\n                  filterProps: {\n                    domain: [\n                      'Africa',\n                      'Asia',\n                      'Europe',\n                      'North America',\n                      'Oceania',\n                      'South America'\n                    ],\n                    value: [],\n                    type: 'multiSelect',\n                    gpu: false\n                  },\n                  type: 'string',\n                  analyzerType: 'STRING'\n                }\n              ]\n            }\n          }\n        ],\n        options: {\n          autoCreateLayers: true\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _loadPointData = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'Sample Taxi Trips 1',\n              id: 'test_trip_data',\n              color: [255, 0, 0]\n            },\n            data: {\n              rows: sampleTripData.rows.slice(0, 20),\n              fields: cloneDeep(sampleTripData.fields)\n            }\n          },\n          {\n            info: {\n              label: 'Sample Taxi Trips 2',\n              id: 'test_trip_data_2',\n              color: [0, 255, 0]\n            },\n            data: {\n              rows: sampleTripData.rows.slice(5, sampleTripData.rows.length),\n              fields: cloneDeep(sampleTripData.fields)\n            }\n          }\n        ],\n        options: {\n          // centerMap: true,\n          keepExistingConfig: true\n        },\n        config: sampleTripDataConfig\n      })\n    );\n  }, [dispatch]);\n\n  const _loadScenegraphLayer = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: {\n          info: {\n            label: 'Sample Scenegraph Ducks',\n            id: 'test_trip_data'\n          },\n          data: processCsvData(testCsvData)\n        },\n        config: {\n          version: 'v1',\n          config: {\n            visState: {\n              layers: [\n                {\n                  type: '3D',\n                  config: {\n                    dataId: 'test_trip_data',\n                    columns: {\n                      lat: 'gps_data.lat',\n                      lng: 'gps_data.lng'\n                    },\n                    isVisible: true\n                  }\n                }\n              ]\n            }\n          }\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _loadIconData = useCallback(() => {\n    // load icon data and config and process csv file\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'Icon Data',\n              id: 'test_icon_data'\n            },\n            data: processCsvData(sampleIconCsv)\n          }\n        ]\n      })\n    );\n  }, [dispatch]);\n\n  const _loadTripGeoJson = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {label: 'Trip animation', id: animateTripDataId},\n            data: processGeojson(sampleAnimateTrip)\n          }\n        ]\n      })\n    );\n  }, [dispatch]);\n\n  const _loadGeojsonData = useCallback(() => {\n    // load geojson\n    const geojsonPoints = processGeojson(sampleGeojsonPoints);\n    const geojsonZip = null; // processGeojson(sampleGeojson);\n    dispatch(\n      addDataToMap({\n        datasets: [\n          geojsonPoints\n            ? {\n                info: {label: 'Bart Stops Geo', id: 'bart-stops-geo'},\n                data: geojsonPoints\n              }\n            : null,\n          geojsonZip\n            ? {\n                info: {label: 'SF Zip Geo', id: 'sf-zip-geo'},\n                data: geojsonZip\n              }\n            : null\n        ].filter(d => d !== null),\n        options: {\n          keepExistingConfig: true\n        },\n        config: sampleGeojsonConfig as ParsedConfig\n      })\n    );\n  }, [dispatch]);\n\n  const _loadSyncedFilterWTripLayer = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {label: 'Trip animation', id: animateTripDataId},\n            data: processGeojson(sampleAnimateTrip)\n          },\n          {\n            info: {\n              label: 'Sample Taxi Trips',\n              id: pointDataId,\n              color: [255, 0, 0]\n            },\n            data: pointData\n          }\n        ],\n        config: syncedTripConfig,\n        options: {\n          centerMap: true\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _replaceSyncedFilterWTripLayer = useCallback(() => {\n    window.setTimeout(() => {\n      dispatch(\n        replaceDataInMap({\n          datasetToReplaceId: pointDataId,\n          datasetToUse: {\n            info: {label: 'Sample Taxi Trips Replaced', id: `${pointDataId}-2`},\n            data: replacePointData\n          }\n        })\n      );\n    }, 1000);\n  }, [dispatch]);\n\n  const _replaceData = useCallback(() => {\n    // add geojson data\n    const sliceData = processGeojson({\n      type: 'FeatureCollection',\n      features: sampleGeojsonPoints.features.slice(0, 5)\n    });\n    _loadGeojsonData();\n    Window.setTimeout(() => {\n      dispatch(\n        replaceDataInMap({\n          datasetToReplaceId: 'bart-stops-geo',\n          datasetToUse: {\n            info: {label: 'Bart Stops Geo Replaced', id: 'bart-stops-geo-2'},\n            data: sliceData\n          }\n        })\n      );\n    }, 1000);\n  }, [dispatch, _loadGeojsonData]);\n\n  const _loadH3HexagonData = useCallback(() => {\n    // load h3 hexagon\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'H3 Hexagons V2',\n              id: 'h3-hex-id'\n            },\n            data: processCsvData(sampleH3Data)\n          }\n        ],\n        config: h3MapConfig,\n        options: {\n          keepExistingConfig: true\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _loadS2Data = useCallback(() => {\n    // load s2\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'S2 Data',\n              id: s2DataId\n            },\n            data: processCsvData(sampleS2Data)\n          }\n        ],\n        config: s2MapConfig as ParsedConfig,\n        options: {\n          keepExistingConfig: true\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _loadGpsData = useCallback(() => {\n    dispatch(\n      addDataToMap({\n        datasets: [\n          {\n            info: {\n              label: 'Gps Data',\n              id: 'gps-data'\n            },\n            data: processCsvData(sampleGpsData)\n          }\n        ],\n        options: {\n          keepExistingConfig: true\n        }\n      })\n    );\n  }, [dispatch]);\n\n  const _loadSampleData = useCallback(() => {\n    // _loadPointData();\n    // _loadGeojsonData();\n    // _loadTripGeoJson();\n    // _loadIconData();\n    // _loadH3HexagonData();\n    // _loadS2Data();\n    // _loadScenegraphLayer();\n    // _loadGpsData();\n    // _loadRowData();\n    // _loadVectorTileData();\n    // _loadSyncedFilterWTripLayer();\n    // _replaceSyncedFilterWTripLayer();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    _loadPointData,\n    _loadGeojsonData,\n    _loadTripGeoJson,\n    _loadIconData,\n    _loadH3HexagonData,\n    _loadS2Data,\n    _loadScenegraphLayer,\n    _loadGpsData,\n    _loadRowData,\n    _replaceData,\n    _loadVectorTileData,\n    _loadSyncedFilterWTripLayer,\n    _replaceSyncedFilterWTripLayer\n  ]);\n\n  return (\n    <StyleSheetManager shouldForwardProp={shouldForwardProp}>\n      <ThemeProvider theme={theme}>\n        <GlobalStyle\n        // this is to apply the same modal style as kepler.gl core\n        // because styled-components doesn't always return a node\n        // https://github.com/styled-components/styled-components/issues/617\n        // ref={node => {\n        //   node ? (this.root = node) : null;\n        // }}\n        >\n          <ScreenshotWrapper\n            startScreenCapture={props.demo.aiAssistant.screenshotToAsk.startScreenCapture}\n            setScreenCaptured={_setScreenCaptured}\n            setStartScreenCapture={_setStartScreenCapture}\n            className=\"h-screen\"\n          >\n            <Banner show={showBanner} height={BannerHeight} bgColor=\"#2E7CF6\" onClose={hideBanner}>\n              <Announcement onDisable={_disableBanner} />\n            </Banner>\n            <div style={CONTAINER_STYLE}>\n              <PanelGroup direction=\"horizontal\">\n                <Panel defaultSize={isAiAssistantPanelOpen ? 70 : 100}>\n                  <PanelGroup direction=\"vertical\">\n                    <Panel defaultSize={isSqlPanelOpen ? 60 : 100}>\n                      <AutoSizer>\n                        {({height, width}) => (\n                          <KeplerGl\n                            mapboxApiAccessToken={CLOUD_PROVIDERS_CONFIGURATION.MAPBOX_TOKEN}\n                            id=\"map\"\n                            getState={keplerGlGetState}\n                            width={width}\n                            height={height}\n                            cloudProviders={CLOUD_PROVIDERS}\n                            localeMessages={messages}\n                            onExportToCloudSuccess={onExportFileSuccess}\n                            onLoadCloudMapSuccess={onLoadCloudMapSuccess}\n                            featureFlags={DEFAULT_FEATURE_FLAGS}\n                            onViewStateChange={onViewStateChange}\n                          />\n                        )}\n                      </AutoSizer>\n                    </Panel>\n\n                    {isSqlPanelOpen && (\n                      <>\n                        <StyledResizeHandle />\n                        <Panel defaultSize={40} minSize={20}>\n                          <SqlPanel initialSql={query.sql || ''} />\n                        </Panel>\n                      </>\n                    )}\n                  </PanelGroup>\n                </Panel>\n                {isAiAssistantPanelOpen && (\n                  <>\n                    <StyledVerticalResizeHandle />\n                    <Panel defaultSize={30} minSize={20}>\n                      <AiAssistantPanel />\n                    </Panel>\n                  </>\n                )}\n              </PanelGroup>\n            </div>\n          </ScreenshotWrapper>\n        </GlobalStyle>\n      </ThemeProvider>\n    </StyleSheetManager>\n  );\n};\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/carto/carto-icon.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Icons} from '@kepler.gl/components';\nimport PropTypes from 'prop-types';\n\nclass CartoIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '100%',\n    viewBox: '0 0 92 36',\n    predefinedClassName: 'data-ex-icons-carto'\n  };\n\n  render() {\n    return (\n      <Icons.IconWrapper {...this.props}>\n        <g id=\"Page-1\" stroke=\"none\" strokeWidth=\"1\" fill=\"none\" fillRule=\"evenodd\">\n          <g\n            id=\"Logo-Scale-Negative\"\n            transform=\"translate(-162.000000, -282.000000)\"\n            fill=\"#2e3c43\"\n          >\n            <g id=\"logo_carto_n_36\" transform=\"translate(162.000000, 282.000000)\">\n              <path\n                d=\"M74,36 C83.9411255,36 92,27.9411255 92,18 C92,8.0588745 83.9411255,0 74,0 C64.0588745,0 56,8.0588745 56,18 C56,27.9411255 64.0588745,36 74,36 Z\"\n                id=\"halo\"\n                fillOpacity=\"0.200000018\"\n              />\n              <path\n                d=\"M6.25280899,23.981602 C8.76747566,23.981602 10.220757,22.882802 11.2984713,21.390402 L8.9144367,19.684802 C8.22861851,20.521202 7.52647133,21.078802 6.33445401,21.078802 C4.73421159,21.078802 3.60751029,19.734002 3.60751029,18.012002 L3.60751029,17.979202 C3.60751029,16.306402 4.73421159,14.928802 6.33445401,14.928802 C7.4284973,14.928802 8.1796315,15.470002 8.83279168,16.273602 L11.2168263,14.420402 C10.204428,13.026402 8.70215964,12.042402 6.36711202,12.042402 C2.9053631,12.042402 0.358038428,14.666402 0.358038428,18.012002 L0.358038428,18.044802 C0.358038428,21.472402 2.98700813,23.981602 6.25280899,23.981602 L6.25280899,23.981602 Z M16.732047,23.752002 L20.0468349,23.752002 L20.8632851,21.685602 L25.2884453,21.685602 L26.1048955,23.752002 L29.5013284,23.752002 L24.6352851,12.190002 L21.5817613,12.190002 L16.732047,23.752002 Z M21.7940384,19.209202 L23.0840297,15.962002 L24.357692,19.209202 L21.7940384,19.209202 Z M35.6697093,23.752002 L38.8375361,23.752002 L38.8375361,20.275202 L40.2418305,20.275202 L42.5442201,23.752002 L46.1855881,23.752002 L43.4586443,19.750402 C44.8792677,19.143602 45.810021,17.979202 45.810021,16.208002 L45.810021,16.175202 C45.810021,15.043602 45.4671119,14.174402 44.7976227,13.502002 C44.0301595,12.731202 42.8218132,12.272002 41.0746097,12.272002 L35.6697093,12.272002 L35.6697093,23.752002 Z M38.8375361,17.782402 L38.8375361,15.010802 L40.9276487,15.010802 C41.9727049,15.010802 42.6421941,15.470002 42.6421941,16.388402 L42.6421941,16.421202 C42.6421941,17.257602 42.005363,17.782402 40.9439777,17.782402 L38.8375361,17.782402 Z M55.2605317,23.752002 L58.4283585,23.752002 L58.4283585,15.060002 L61.8574495,15.060002 L61.8574495,12.272002 L51.8477698,12.272002 L51.8477698,15.060002 L55.2605317,15.060002 L55.2605317,23.752002 Z M74,24 C77.3137085,24 80,21.3137085 80,18 C80,14.6862915 77.3137085,12 74,12 C70.6862915,12 68,14.6862915 68,18 C68,21.3137085 70.6862915,24 74,24 Z\"\n                id=\"logotype\"\n              />\n            </g>\n          </g>\n        </g>\n      </Icons.IconWrapper>\n    );\n  }\n}\n\nexport default CartoIcon;\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/carto/carto-provider.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {OAuthApp} from '@carto/toolkit';\nimport Console from 'global/console';\nimport CartoIcon from './carto-icon';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {createDataContainer} from '@kepler.gl/utils';\nimport {formatCsv} from '@kepler.gl/reducers';\n\nconst NAME = 'carto';\nconst DISPLAY_NAME = 'CARTO';\nconst NAMESPACE = 'keplergl';\nconst DOMAIN = 'carto.com';\nconst PRIVATE_STORAGE_ENABLED = true;\nconst SHARING_ENABLED = true;\n\nexport default class CartoProvider extends Provider {\n  constructor(clientId) {\n    super({name: NAME, displayName: DISPLAY_NAME, icon: CartoIcon});\n\n    this.clientId = clientId;\n    this.thumbnail = {width: 300, height: 200};\n    this.currentMap = null;\n\n    this._folderLink = `https://{user}.${DOMAIN}/dashboard/maps/external`;\n\n    // Initialize CARTO API\n    this._carto = new OAuthApp(\n      {\n        authorization: `https://${DOMAIN}/oauth2`,\n        scopes: 'schemas:c datasets:rw:*'\n      },\n      {\n        serverUrlTemplate: `https://{user}.${DOMAIN}/`,\n        namespace: NAMESPACE\n      }\n    );\n\n    this._carto.setClientID(clientId);\n  }\n\n  /**\n   * The CARTO toolkit library takes care of the login process.\n   */\n  login(onCloudLoginSuccess) {\n    try {\n      this._carto.login().then(() => {\n        onCloudLoginSuccess && onCloudLoginSuccess(this.name);\n      });\n    } catch (error) {\n      this._manageErrors(error);\n    }\n  }\n\n  logout(onCloudLogoutSuccess) {\n    try {\n      this._carto.oauth.clear();\n      this._carto.oauth._carto.sync();\n      onCloudLogoutSuccess();\n    } catch (error) {\n      this._manageErrors(error);\n    }\n  }\n\n  isEnabled() {\n    return this.clientId !== null;\n  }\n\n  hasPrivateStorage() {\n    return PRIVATE_STORAGE_ENABLED;\n  }\n\n  hasSharingUrl() {\n    return SHARING_ENABLED;\n  }\n\n  async uploadMap({mapData = {}, options = {}}) {\n    try {\n      const {isPublic = true, overwrite = true} = options;\n      const {map: {config, datasets, info} = {}, thumbnail} = mapData;\n\n      const cartoDatasets = datasets.map(this._convertDataset);\n\n      const cs = await this._carto.getCustomStorage();\n\n      const {title, description} = info;\n      const name = title;\n\n      const thumbnailBase64 =\n        mapData && thumbnail ? await this._blobToBase64(mapData.thumbnail) : null;\n\n      let result;\n      if (overwrite) {\n        result = await cs.updateVisualization(\n          {\n            id: this.currentMap.id,\n            name,\n            description,\n            thumbnail: thumbnailBase64,\n            config: JSON.stringify(config),\n            isprivate: this.currentMap.isprivate\n          },\n          cartoDatasets\n        );\n      } else {\n        // TODO: Ask for changing current shared map generation because of being too oriented to file based clouds\n        // Check public name generation and replace\n        const regex = /(?:^keplergl_)([a-z0-9]+)(?:.json$)/;\n        const capturedName = name.match(regex);\n        const visName = capturedName ? `sharedmap_${capturedName[1]}` : name;\n\n        result = await cs.createVisualization(\n          {\n            name: visName,\n            description,\n            thumbnail: thumbnailBase64,\n            config: JSON.stringify(config),\n            isprivate: !isPublic\n          },\n          cartoDatasets,\n          true\n        );\n      }\n\n      if (result) {\n        this.currentMap = result;\n      }\n\n      return {\n        shareUrl: this._getMapPermalinkFromParams(\n          {\n            mapId: result.id,\n            owner: this._carto.username,\n            privateMap: !isPublic\n          },\n          true\n        ),\n        folderLink: this._folderLink.replace('{user}', this._carto.username)\n      };\n    } catch (error) {\n      this._manageErrors(error);\n    }\n  }\n\n  /**\n   * Returns the access token. If it has expired returns null. The toolkit library loads it\n   * from localStorage automatically\n   */\n  async getAccessToken() {\n    let accessToken = null;\n    try {\n      accessToken = this._carto.oauth.expired ? null : this._carto.oauth.token;\n    } catch (error) {\n      this._manageErrors(error, false);\n    }\n\n    return accessToken;\n  }\n\n  getUserName() {\n    let username = null;\n    try {\n      username = this._carto.oauth.expired ? null : this._carto.username;\n    } catch (error) {\n      this._manageErrors(error, false);\n    }\n\n    return username;\n  }\n\n  /**\n   * The CARTO cloud provider polls the created window internally to parse the URL\n   * @param {*} location\n   */\n  getAccessTokenFromLocation() {\n    return;\n  }\n\n  async getUser() {\n    return {\n      name: this.getUserName(),\n      abbreviated: '',\n      email: ''\n    };\n  }\n\n  async downloadMap(queryParams) {\n    try {\n      const {owner: username, mapId, privateMap} = queryParams;\n\n      if (!username || !mapId) {\n        return;\n      }\n\n      let visualization;\n\n      if (privateMap.trim().toLowerCase() === 'true') {\n        await this._carto.login();\n        const currentUsername = this.getUserName();\n        if (currentUsername && currentUsername === username) {\n          const cs = await this._carto.getCustomStorage();\n          visualization = await cs.getVisualization(mapId);\n        }\n      } else {\n        visualization = await this._carto.PublicStorageReader.getVisualization(username, mapId);\n      }\n\n      if (!visualization) {\n        throw new Error(`Can't find map with ID: ${mapId}`);\n      }\n\n      // These are the options required for the action. For now, all datasets that come from CARTO are CSV\n      const datasets = visualization.datasets.map(dataset => {\n        const datasetId = dataset.name;\n\n        return {\n          info: {\n            id: datasetId,\n            label: datasetId,\n            description: dataset.description,\n            dataUrl: '',\n            configUrl: '',\n            panelDisabled: true\n          },\n          data: dataset.file\n        };\n      });\n\n      // const datasets = visualization.datasets.map(dataset => dataset.file);\n\n      this.currentMap = visualization.vis;\n\n      return {\n        map: {\n          datasets,\n          config: visualization.vis.config,\n          info: {title: visualization.vis.name, description: visualization.vis.description}\n        },\n        format: 'csv'\n      };\n    } catch (error) {\n      this._manageErrors(error);\n    }\n  }\n\n  async listMaps() {\n    // TODO: Implement pagination using {type='all', pageOffset=0, pageSize=-1}\n    try {\n      await this._carto.login();\n      const username = this.getUserName();\n      const cs = await this._carto.getCustomStorage();\n\n      const visualizations = await cs.getVisualizations();\n      let formattedVis = [];\n\n      // Format visualization object\n      for (const vis of visualizations) {\n        formattedVis.push({\n          id: vis.id,\n          title: vis.name,\n          description: vis.description,\n          privateMap: vis.isprivate,\n          thumbnail: vis.thumbnail === 'undefined' ? null : vis.thumbnail,\n          lastModification: new Date(Date.parse(vis.lastmodified)),\n          loadParams: {\n            owner: username,\n            mapId: vis.id,\n            privateMap: vis.isprivate.toString()\n          }\n        });\n      }\n\n      formattedVis = formattedVis.sort((a, b) => b.lastModification - a.lastModification);\n\n      return formattedVis;\n    } catch (error) {\n      this._manageErrors(error);\n    }\n  }\n\n  getShareUrl(fullUrl = false) {\n    return this.getMapUrl(fullUrl);\n  }\n\n  getMapUrl(fullUrl = true, mapParams = null) {\n    if (mapParams) {\n      return this._getMapPermalinkFromParams(mapParams, fullUrl);\n    } else if (this.currentMap) {\n      return this._getMapPermalinkFromParams(\n        {\n          mapId: this.currentMap.id,\n          owner: this.getUserName(),\n          privateMap: this.currentMap.isPrivate\n        },\n        fullUrl\n      );\n    }\n  }\n\n  getManagementUrl() {\n    return this._folderLink.replace('{user}', this._carto.username);\n  }\n\n  getCurrentVisualization() {\n    return this.currentMap\n      ? {title: this.currentMap.name, description: this.currentMap.description}\n      : null;\n  }\n\n  // PRIVATE\n\n  _getMapPermalinkFromParams({mapId, owner, privateMap}, fullURL = true) {\n    const mapLink = this._composeURL({mapId, owner, privateMap});\n    return fullURL\n      ? `${window.location.protocol}//${window.location.host}/${mapLink}`\n      : `/${mapLink}`;\n  }\n\n  _convertDataset({data: dataset}) {\n    const {allData, fields, id} = dataset;\n    const columns = fields.map(field => ({\n      name: field.name,\n      type: field.type\n    }));\n\n    const dataContainer = createDataContainer([...allData]);\n\n    const file = formatCsv(dataContainer, fields);\n\n    return {\n      name: id,\n      columns,\n      file\n    };\n  }\n\n  // eslint-disable-next-line complexity\n  _manageErrors(error, throwException = true) {\n    let message;\n    if (error && error.message) {\n      message = error.message;\n\n      switch (error.message) {\n        case 'No client ID has been specified':\n          Console.error('No ClientID set for CARTO provider');\n          break;\n        case 'Cannot set the client ID more than once':\n          Console.error('CARTO provider already initialized');\n          break;\n        case (error.message.match(/relation \"[a-zA-Z0-9_]+\" does not exist/) || {}).input:\n          Console.error('CARTO custom storage is not properly initialized');\n          message = 'Custom storage is not properly initialized';\n          break;\n        case (\n          error.message.match(/Failed to copy to keplergl_[a-zA-Z0-9_]+: Too many retries/) || {}\n        ).input:\n          Console.error('CARTO Rate limit exceeded');\n          message =\n            \"Failed to upload. You've exceeded the number of datasets allowed with your plan. Consider upgrading your plan.\";\n          break;\n        case (error.message.match(/[a-zA-Z0-9_\\s:]+: DB Quota exceeded/) || {}).input:\n          Console.error('CARTO DB Quota exceeded');\n          message =\n            \"Failed to upload. You've exceeded your account's disk storage limit. Consider upgrading your plan.\";\n          break;\n        default:\n          Console.error(`CARTO provider: ${message}`);\n      }\n    } else {\n      message = 'General error in CARTO provider';\n      Console.error(message);\n    }\n\n    // Use 'CARTO' as error code in order to show provider in notifications\n    if (throwException) {\n      throw new Error(message);\n    }\n  }\n\n  _composeURL({mapId, owner, privateMap}) {\n    return `demo/map/carto?mapId=${mapId}&owner=${owner}&privateMap=${privateMap}`;\n  }\n\n  _blobToBase64(blob) {\n    return new Promise((resolve, reject) => {\n      const reader = new FileReader();\n      reader.onloadend = () => {\n        if (!reader.error) {\n          resolve(reader.result);\n        } else {\n          reject(reader.error);\n        }\n      };\n      reader.readAsDataURL(blob);\n    });\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/dropbox/dropbox-error-modal.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef} from 'react';\nimport ReactDOM from 'react-dom';\n\nconst WIDTH = 400;\nconst HEIGHT = 800;\nconst style = {border: 0};\n\nexport default class Frame extends Component {\n  componentDidMount() {\n    this.renderFrameContents();\n  }\n  componentDidUpdate() {\n    this.renderFrameContents();\n  }\n\n  componentWillUnmount() {\n    this.root.current.unmount();\n  }\n\n  root = createRef();\n  innerHtml = createRef();\n\n  renderFrameContents = () => {\n    const doc = this.root.current.contentDocument;\n    if (doc.readyState === 'complete') {\n      ReactDOM.render(\n        <html\n          ref={this.innerHtml}\n          // eslint-disable-next-line react/no-danger\n          dangerouslySetInnerHTML={{\n            __html: this.props.children\n          }}\n        />,\n        doc\n      );\n    } else {\n      setTimeout(this.renderFrameContents.bind(this), 0);\n    }\n  };\n\n  render() {\n    return <iframe width={`${WIDTH}px`} height={`${HEIGHT}px`} style={style} ref={this.root} />;\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/dropbox/dropbox-icon.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Icons} from '@kepler.gl/components';\nimport PropTypes from 'prop-types';\n\nclass DropboxIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-dropbox',\n    totalColor: 1\n  };\n\n  render() {\n    return (\n      <Icons.IconWrapper {...this.props} viewBox={'0 0 416 416'} colors={['#0060ff']}>\n        <path\n          className=\"cr1\"\n          d=\"M348.911,169.42L220.258,91.062l81.133-67.164l128.732,79.217L348.911,169.42z M300.532,317.532\n\t\tl123.547-72.331l-74.31-62.858l-120.961,73.191L300.532,317.532z M201.328,255.534L80.355,182.343L6.06,245.201l123.559,72.331\n\t\tL201.328,255.534z M86.406,307.199v23.251l123.472,75.774V262.414l-81.989,67.168L86.406,307.199z M343.712,307.199l-41.453,22.388\n\t\tl-82.001-67.173v143.811l123.454-75.774V307.199z M209.878,91.062l-81.135-67.164L0,103.115l81.226,66.305L209.878,91.062z\"\n        />\n      </Icons.IconWrapper>\n    );\n  }\n}\n\nexport default DropboxIcon;\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/dropbox/dropbox-provider.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// DROPBOX\nimport {Dropbox} from 'dropbox';\nimport Window from 'global/window';\nimport DropboxIcon from './dropbox-icon';\nimport {MAP_URI} from '../../constants/default-settings';\nimport {KEPLER_FORMAT, Provider} from '@kepler.gl/cloud-providers';\n\nconst NAME = 'dropbox';\nconst DISPLAY_NAME = 'Dropbox';\nconst DOMAIN = 'www.dropbox.com';\nconst KEPLER_DROPBOX_FOLDER_LINK = `//${DOMAIN}/home/Apps`;\nconst CORS_FREE_DOMAIN = 'dl.dropboxusercontent.com';\nconst PRIVATE_STORAGE_ENABLED = true;\nconst SHARING_ENABLED = true;\nconst MAX_THUMBNAIL_BATCH = 25;\nconst IMAGE_URL_PREFIX = 'data:image/gif;base64,';\n\nfunction parseQueryString(query) {\n  const searchParams = new URLSearchParams(query);\n  const params = {};\n  for (const p of searchParams) {\n    if (p && p.length === 2 && p[0]) params[p[0]] = p[1];\n  }\n\n  return params;\n}\n\nfunction isConfigFile(err) {\n  const summary = err.error && err.error.error_summary;\n  return typeof summary === 'string' && Boolean(summary.match(/path\\/conflict\\/file\\//g));\n}\n\nexport default class DropboxProvider extends Provider {\n  constructor(clientId, appName) {\n    super({name: NAME, displayName: DISPLAY_NAME, icon: DropboxIcon});\n    // All cloud-providers providers must implement the following properties\n\n    this.clientId = clientId;\n    this.appName = appName;\n\n    this._folderLink = `${KEPLER_DROPBOX_FOLDER_LINK}/${appName}`;\n    this._path = '';\n\n    // Initialize Dropbox API\n    this._initializeDropbox();\n  }\n\n  /**\n   * This method will handle the oauth flow by performing the following steps:\n   * - Opening a new window\n   * - Subscribe to message channel\n   * - Receive the token when ready\n   * - Close the opened tab\n   */\n  async login() {\n    return new Promise((resolve, reject) => {\n      const link = this._authLink();\n\n      const authWindow = Window.open(link, '_blank', 'width=1024,height=716');\n\n      const handleToken = async event => {\n        // if user has dev tools this will skip all the react-devtools events\n        if (!event.data.token) {\n          return;\n        }\n\n        if (authWindow) {\n          authWindow.close();\n          Window.removeEventListener('message', handleToken);\n        }\n\n        const {token} = event.data;\n\n        if (!token) {\n          reject('Failed to login to Dropbox');\n          return;\n        }\n\n        this._dropbox.setAccessToken(token);\n        // save user name\n        const user = await this.getUser();\n\n        if (Window.localStorage) {\n          Window.localStorage.setItem(\n            'dropbox',\n            JSON.stringify({\n              // dropbox token doesn't expire unless revoked by the user\n              token,\n              user,\n              timestamp: new Date()\n            })\n          );\n        }\n\n        resolve(user);\n      };\n\n      Window.addEventListener('message', handleToken);\n    });\n  }\n\n  /**\n   * returns a list of maps\n   */\n  async listMaps() {\n    // list files\n    try {\n      // https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#filesListFolder__anchor\n      const response = await this._dropbox.filesListFolder({\n        path: `${this._path}`\n      });\n      const {pngs, visualizations} = this._parseEntries(response);\n      // https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#filesGetThumbnailBatch__anchor\n      // up to 25 per request\n      // TODO: implement pagination, so we don't need to get all the thumbs all at once\n      const thumbnails = await Promise.all(this._getThumbnailRequests(pngs)).then(results =>\n        results.reduce((accu, r) => [...accu, ...(r.entries || [])], [])\n      );\n\n      // append to visualizations\n      (thumbnails || []).forEach(thb => {\n        if (thb['.tag'] === 'success' && thb.thumbnail) {\n          const matchViz = visualizations[pngs[thb.metadata.id] && pngs[thb.metadata.id].name];\n          if (matchViz) {\n            matchViz.thumbnail = `${IMAGE_URL_PREFIX}${thb.thumbnail}`;\n          }\n        }\n      });\n\n      // dropbox returns\n      return Object.values(visualizations).reverse();\n    } catch (error) {\n      // made the error message human readable for provider updater\n      throw this._handleDropboxError(error);\n    }\n  }\n\n  /**\n   *\n   * @param mapData map data and config in one json object {map: {datasets: Array<Object>, config: Object, info: Object}\n   * @param blob json file blob to upload\n   */\n  async uploadMap({mapData, options = {}}) {\n    const {isPublic} = options;\n    const {map, thumbnail} = mapData;\n\n    // generate file name if is not provided\n    const name = map.info && map.info.title;\n    const fileName = `${name}.json`;\n    const fileContent = map;\n    // FileWriteMode: Selects what to do if the file already exists.\n    // Always overwrite if sharing\n    const mode = options.overwrite || isPublic ? 'overwrite' : 'add';\n    const path = `${this._path}/${fileName}`;\n    let metadata;\n    try {\n      metadata = await this._dropbox.filesUpload({\n        path,\n        contents: JSON.stringify(fileContent),\n        mode\n      });\n    } catch (err) {\n      if (isConfigFile(err)) {\n        throw this.getFileConflictError();\n      }\n    }\n\n    // save a thumbnail image\n    if (thumbnail) {\n      await this._dropbox.filesUpload({\n        path: path.replace(/\\.json$/, '.png'),\n        contents: thumbnail,\n        mode\n      });\n    }\n\n    // keep on create shareUrl\n    if (isPublic) {\n      return await this._shareFile(metadata);\n    }\n\n    return {id: metadata.id, path: metadata.path_lower};\n  }\n\n  /**\n   * download the map content\n   * @param loadParams\n   */\n  async downloadMap(loadParams) {\n    const {path} = loadParams;\n    const result = await this._dropbox.filesDownload({path});\n    const json = await this._readFile(result.fileBlob);\n\n    const response = {\n      map: json,\n      format: KEPLER_FORMAT\n    };\n\n    return Promise.resolve(response);\n  }\n\n  getUserName() {\n    // load user from\n    if (Window.localStorage) {\n      const jsonString = Window.localStorage.getItem('dropbox');\n      return jsonString && JSON.parse(jsonString).user;\n    }\n    return null;\n  }\n\n  async logout() {\n    await this._dropbox.authTokenRevoke();\n    if (Window.localStorage) {\n      Window.localStorage.removeItem('dropbox');\n    }\n    // re instantiate dropbox\n    this._initializeDropbox();\n  }\n\n  isEnabled() {\n    return this.clientId !== null;\n  }\n\n  hasPrivateStorage() {\n    return PRIVATE_STORAGE_ENABLED;\n  }\n\n  hasSharingUrl() {\n    return SHARING_ENABLED;\n  }\n\n  /**\n   * Get the share url of current map, this url can be accessed by anyone\n   * @param {boolean} fullUrl\n   */\n  getShareUrl(fullUrl = true) {\n    return fullUrl\n      ? `${Window.location.protocol}//${Window.location.host}/${MAP_URI}${this._shareUrl}`\n      : `/${MAP_URI}${this._shareUrl}`;\n  }\n\n  /**\n   * Get the map url of current map, this url can only be accessed by current logged in user\n   */\n  getMapUrl(loadParams) {\n    const {path} = loadParams;\n    return path;\n  }\n\n  getManagementUrl() {\n    return this._folderLink;\n  }\n\n  /**\n   * Provides the current dropbox auth token. If stored in localStorage is set onto dropbox handler and returned\n   * @returns {any}\n   */\n  getAccessToken() {\n    let token = this._dropbox.getAccessToken();\n    if (!token && Window.localStorage) {\n      const jsonString = Window.localStorage.getItem('dropbox');\n      token = jsonString && JSON.parse(jsonString).token;\n      if (token) {\n        this._dropbox.setAccessToken(token);\n      }\n    }\n    return (token || '') !== '' ? token : null;\n  }\n\n  /**\n   * This method will extract the auth token from the third party service callback url.\n   * @param {object} location the window location provided by react router\n   * @returns {?string} the token extracted from the oauth 2 callback URL\n   */\n  getAccessTokenFromLocation(location) {\n    if (!(location && location.hash.length)) {\n      return null;\n    }\n    // dropbox token usually start with # therefore we want to remove the '#'\n    const query = Window.location.hash.substring(1);\n    return parseQueryString(query).access_token;\n  }\n\n  // PRIVATE\n  _initializeDropbox() {\n    this._dropbox = new Dropbox({fetch: Window.fetch});\n    this._dropbox.setClientId(this.clientId);\n  }\n\n  async getUser() {\n    const response = await this._dropbox.usersGetCurrentAccount();\n    return this._getUserFromAccount(response);\n  }\n\n  _handleDropboxError(error) {\n    // dropbox list_folder error\n    if (error && error.error && error.error.error_summary) {\n      return new Error(`Dropbox Error: ${error.error.error_summary}`);\n    }\n\n    return error;\n  }\n\n  _readFile(fileBlob) {\n    return new Promise((resolve, reject) => {\n      const fileReader = new FileReader(fileBlob);\n      fileReader.onload = ({target: {result}}) => {\n        try {\n          const json = JSON.parse(result);\n          resolve(json);\n        } catch (err) {\n          reject(err);\n        }\n      };\n\n      fileReader.readAsText(fileBlob, 'utf-8');\n    });\n  }\n\n  // append url after map sharing\n  _getMapPermalink(mapLink, fullUrl = true) {\n    return fullUrl\n      ? `${Window.location.protocol}//${Window.location.host}/${MAP_URI}${mapLink}`\n      : `/${MAP_URI}${mapLink}`;\n  }\n\n  // append map url after load map from storage, this url is not meant\n  // to be directly shared with others\n  _getMapPermalinkFromParams({path}, fullURL = true) {\n    const mapLink = `demo/map/dropbox?path=${path}`;\n    return fullURL\n      ? `${Window.location.protocol}//${Window.location.host}/${mapLink}`\n      : `/${mapLink}`;\n  }\n  /**\n   * It will set access to file to public\n   * @param {Object} metadata metadata response from uploading the file\n   * @returns {Promise<DropboxTypes.sharing.FileLinkMetadataReference | DropboxTypes.sharing.FolderLinkMetadataReference | DropboxTypes.sharing.SharedLinkMetadataReference>}\n   */\n  _shareFile(metadata) {\n    const shareArgs = {\n      path: metadata.path_display || metadata.path_lower\n    };\n\n    return this._dropbox\n      .sharingListSharedLinks(shareArgs)\n      .then(({links} = {}) => {\n        if (links && links.length) {\n          return links[0];\n        }\n        return this._dropbox.sharingCreateSharedLinkWithSettings(shareArgs);\n      })\n      .then(result => {\n        // Update URL to avoid CORS issue\n        // Unfortunately this is not the ideal scenario but it will make sure people\n        // can share dropbox urls with users without the dropbox account (publish on twitter, facebook)\n        this._shareUrl = this._overrideUrl(result.url);\n\n        return {\n          // the full url to be displayed\n          shareUrl: this.getShareUrl(true),\n          folderLink: this._folderLink\n        };\n      });\n  }\n\n  /**\n   * Generate auth link url to open to be used to handle OAuth2\n   * @param {string} path\n   */\n  _authLink(path = 'auth') {\n    return this._dropbox.getAuthenticationUrl(\n      `${Window.location.origin}/${path}`,\n      btoa(JSON.stringify({handler: 'dropbox', origin: Window.location.origin}))\n    );\n  }\n\n  /**\n   * Override dropbox cloud-providers url\n   * https://www.dropbox.com/s/bxwwdb81z0jg7pb/keplergl_2018-11-01T23%3A22%3A43.940Z.json?rlkey=xxx&dl=0\n   * ->\n   * https://dl.dropboxusercontent.com/s/bxwwdb81z0jg7pb/keplergl_2018-11-01T23%3A22%3A43.940Z.json?rlkey=xxx&dl=0\n   * @param metadata\n   * @returns {DropboxTypes.sharing.FileLinkMetadataReference}\n   */\n  _overrideUrl(url) {\n    return url ? url.replace(DOMAIN, CORS_FREE_DOMAIN) : null;\n  }\n\n  _getUserFromAccount(response) {\n    const {name} = response;\n    return {\n      name: name.display_name,\n      email: response.email,\n      abbreviated: name.abbreviated_name\n    };\n  }\n\n  _getThumbnailRequests(pngs) {\n    const batches = Object.values(pngs).reduce((accu, c) => {\n      const lastBatch = accu.length && accu[accu.length - 1];\n      if (!lastBatch || lastBatch.length >= MAX_THUMBNAIL_BATCH) {\n        // add new batch\n        accu.push([c]);\n      } else {\n        lastBatch.push(c);\n      }\n      return accu;\n    }, []);\n\n    return batches.map(batch =>\n      this._dropbox.filesGetThumbnailBatch({\n        entries: batch.map(img => ({\n          path: img.path_lower,\n          format: 'png',\n          size: 'w128h128'\n        }))\n      })\n    );\n  }\n\n  /**\n   * Parse fileListFolder result as visualizations to be shown in load storage map modal\n   * @param {*} response\n   */\n  _parseEntries(response) {\n    const {entries, cursor, has_more} = response;\n\n    if (has_more) {\n      this._cursor = cursor;\n    }\n    const pngs = {};\n    const visualizations = {};\n\n    entries.forEach(entry => {\n      const {name, path_lower, id, client_modified} = entry;\n      if (name && name.endsWith('.json')) {\n        // find json\n        const title = name.replace(/\\.json$/, '');\n        const viz = {\n          name,\n          title,\n          id,\n          updatedAt: new Date(client_modified).getTime(),\n          loadParams: {\n            id,\n            path: path_lower\n          }\n        };\n\n        visualizations[title] = viz;\n      } else if (name && name.endsWith('.png')) {\n        const title = name.replace(/\\.png$/, '');\n\n        pngs[id] = {\n          name: title,\n          path_lower,\n          id\n        };\n      }\n    });\n\n    return {\n      visualizations,\n      pngs\n    };\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/foursquare/foursquare-icon.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Icons} from '@kepler.gl/components';\nimport PropTypes from 'prop-types';\n\nconst style = {\n  background: 'black'\n};\nexport default class FoursquareIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    width: '64px',\n    fill: 'black',\n    predefinedClassName: 'cloud-provider-studio-icon',\n    totalColor: 1,\n    style\n  };\n\n  render() {\n    return (\n      <Icons.IconWrapper {...this.props} viewBox=\"0 -10 84 120\">\n        <path\n          d=\"M2.30368 0H33.1681V5.341H8.33118V18.5748H30.1695V23.7674H8.33118V43.5885H2.30368V0Z\"\n          fill=\"white\"\n        />\n        <path\n          d=\"M5.66576 84.6965C5.80711 87.1692 6.40279 89.177 7.4528 90.72C9.45187 93.6081 12.9755 95.0521 18.0236 95.0521C20.2852 95.0521 22.3449 94.7356 24.2026 94.1026C27.7969 92.8762 29.594 90.6804 29.594 87.5154C29.594 85.1416 28.8368 83.4503 27.3223 82.4414C25.7877 81.4524 23.3848 80.5919 20.1136 79.86L14.0861 78.5247C10.1485 77.6543 7.36194 76.6949 5.72633 75.6465C2.89937 73.8266 1.48588 71.1067 1.48588 67.4867C1.48588 63.5699 2.86908 60.3554 5.63547 57.8432C8.40186 55.3309 12.3192 54.0748 17.3876 54.0748C22.0521 54.0748 26.0098 55.1826 29.2608 57.3981C32.532 59.5939 34.1676 63.115 34.1676 67.9614H28.5036C28.2007 65.6272 27.5546 63.837 26.5651 62.5907C24.7276 60.3159 21.6078 59.1784 17.2058 59.1784C13.6519 59.1784 11.0976 59.9104 9.54274 61.3742C7.98791 62.838 7.21049 64.5392 7.21049 66.4778C7.21049 68.6142 8.11916 70.1769 9.9365 71.166C11.1279 71.799 13.8236 72.5903 18.0236 73.5398L24.2632 74.9344C27.2719 75.607 29.594 76.5268 31.2296 77.6939C34.0566 79.7314 35.4701 82.6887 35.4701 86.5659C35.4701 91.3926 33.6729 94.8444 30.0786 96.9215C26.5045 98.9985 22.3449 100.037 17.5996 100.037C12.0668 100.037 7.7355 98.6524 4.60564 95.883C1.47579 93.1333 -0.0588526 89.4045 0.00172532 84.6965H5.66576Z\"\n          fill=\"white\"\n        />\n        <path\n          d=\"M83.8112 98.6425L80.7823 102.233L73.9067 97.0995C72.2509 97.9897 70.4538 98.7018 68.5153 99.2359C66.597 99.77 64.497 100.037 62.2152 100.037C55.3093 100.037 49.8977 97.8215 45.9803 93.3905C42.5274 89.0781 40.8009 83.6778 40.8009 77.1895C40.8009 71.2946 42.2952 66.2503 45.2837 62.0566C49.1203 56.6761 54.7944 53.9858 62.3061 53.9858C70.161 53.9858 75.9765 56.4585 79.7525 61.4039C82.7006 65.2612 84.1747 70.1967 84.1747 76.2103C84.1747 79.0193 83.8213 81.7194 83.1146 84.3108C82.0444 88.2671 80.2371 91.4915 77.6929 93.9839L83.8112 98.6425ZM62.9421 94.7554C64.1941 94.7554 65.3653 94.6763 66.4557 94.518C67.5461 94.34 68.4951 94.0037 69.3028 93.5092L64.4263 89.7705L67.4552 86.1208L73.2707 90.542C75.1082 88.4847 76.35 86.1801 76.9962 83.6283C77.6626 81.0765 77.9957 78.6335 77.9957 76.2993C77.9957 71.1759 76.6226 67.0515 73.8764 63.926C71.1504 60.8005 67.4148 59.2378 62.6695 59.2378C57.8637 59.2378 54.0574 60.7412 51.2506 63.748C48.4438 66.735 47.0404 71.344 47.0404 77.5752C47.0404 82.8173 48.3833 86.9912 51.0689 90.0969C53.7747 93.2026 57.7324 94.7554 62.9421 94.7554Z\"\n          fill=\"white\"\n        />\n      </Icons.IconWrapper>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/foursquare/foursquare-provider.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport FSQIcon from './foursquare-icon';\nimport {Provider, KEPLER_FORMAT} from '@kepler.gl/cloud-providers';\nimport {Auth0Client} from '@auth0/auth0-spa-js';\n\nconst NAME = 'foursquare';\nconst DISPLAY_NAME = 'Foursquare';\nconst STORAGE_MESSAGE = 'modal.loadStorageMap.foursquareStorageMessage';\n\nconst APP_NAME = 'Kepler.gl';\n\nconst FOURSQUARE_PRIVATE_STORAGE_ENABLED = true;\nconst FOURSQUARE_SHARING_ENABLED = false;\nconst FOURSQUARE_AUTH_AUDIENCE = 'https://foursquare.com/api/';\nconst FOURSQUARE_AUTH_SCOPE = 'openid profile email';\n\n// Foursquare stores kepler maps using kepler.gl-raw as ImportSource\nconst FOURSQUARE_KEPLER_GL_IMPORT_SOURCE = 'kepler.gl-raw';\n\n/**\n * Converts a FSQ map model to cloud provider map item\n * @param model Foursquare Map\n * @return {MapItem} Map\n */\nfunction convertFSQModelToMapItem(model, baseApi) {\n  return {\n    id: model.id,\n    title: model.name,\n    thumbnail: model.previewReadPath,\n    updatedAt: model.updatedAt,\n    description: model.description,\n    loadParams: {\n      id: model.id,\n      path: `${baseApi}/${model.id}`\n    }\n  };\n}\n\n/**\n * Custom Auth0 popup window to change window height to fit FSQ auth window.\n */\nexport const openPopup = url => {\n  const width = 400;\n  const height = 765;\n  const left = window.screenX + (window.innerWidth - width) / 2;\n  const top = window.screenY + (window.innerHeight - height) / 2;\n\n  return window.open(\n    url,\n    'auth0:authorize:popup',\n    `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1`\n  );\n};\n\nfunction extractMapFromFSQResponse(response) {\n  const {\n    latestState: {data}\n  } = response;\n  return data;\n}\n\nexport default class FoursquareProvider extends Provider {\n  constructor({clientId, authDomain, apiURL, userMapsURL}) {\n    super({name: NAME, displayName: DISPLAY_NAME, storageMessage: STORAGE_MESSAGE, icon: FSQIcon});\n    this.icon = FSQIcon;\n    this.appName = APP_NAME;\n    this.apiURL = apiURL;\n\n    this._auth0 = new Auth0Client({\n      domain: authDomain,\n      clientId,\n      scope: FOURSQUARE_AUTH_SCOPE,\n      authorizationParams: {\n        prompt: 'login',\n        redirect_uri: window.location.origin,\n        audience: FOURSQUARE_AUTH_AUDIENCE\n      },\n      cacheLocation: 'localstorage'\n    });\n\n    // the domain needs to be passed as input param\n    this._folderLink = userMapsURL;\n    this.isNew = true;\n  }\n\n  hasPrivateStorage() {\n    return FOURSQUARE_PRIVATE_STORAGE_ENABLED;\n  }\n\n  hasSharingUrl() {\n    return FOURSQUARE_SHARING_ENABLED;\n  }\n\n  async getUser() {\n    return this._auth0.getUser();\n  }\n\n  async login() {\n    return this._auth0.loginWithPopup(undefined, {popup: openPopup()});\n  }\n\n  async logout() {\n    return this._auth0.logout({\n      // this make sure after logging out the sdk will not redirect the user\n      openUrl: false\n    });\n  }\n\n  async uploadMap({mapData, options = {}}) {\n    const method = options.overwrite ? 'PUT' : 'POST';\n    const {map, thumbnail} = mapData;\n\n    const {title = '', description = '', loadParams} = map.info;\n\n    const mapIdToOverwrite = options.mapIdToOverwrite || loadParams?.id;\n    if (options.overwrite && !mapIdToOverwrite) {\n      throw new Error('Foursquare storage provider: no map id to overwrite');\n    }\n\n    const headers = await this.getHeaders();\n    const payload = {\n      name: title,\n      description,\n      importSource: FOURSQUARE_KEPLER_GL_IMPORT_SOURCE,\n      latestState: {\n        data: map\n      }\n    };\n\n    // To overwrite map.latestState we have to fetch the map first\n    if (options.overwrite) {\n      const response = await fetch(`${this.apiURL}/v1/maps/${mapIdToOverwrite}`, {\n        method: 'GET',\n        headers\n      });\n\n      const data = await response.json();\n      payload.latestState = data.latestState;\n      payload.latestState.data = map;\n    }\n\n    const mapResponse = await fetch(\n      `${this.apiURL}/v1/maps${options.overwrite ? `/${mapIdToOverwrite}` : ''}`,\n      {\n        method,\n        headers,\n        body: JSON.stringify(payload)\n      }\n    );\n\n    const createdMap = await mapResponse.json();\n\n    if (!options.overwrite) {\n      await fetch(`${this.apiURL}/v1/maps/${createdMap.id}/thumbnail`, {\n        method: 'PUT',\n        headers: {\n          ...headers,\n          'Content-Type': 'image/png'\n        },\n        body: thumbnail\n      });\n    }\n\n    // pass through fsq map id\n    const newMapData = extractMapFromFSQResponse(createdMap);\n    return {...newMapData, info: {...newMapData.info, id: createdMap.id}};\n  }\n\n  async listMaps() {\n    const headers = await this.getHeaders();\n    const response = await fetch(\n      `${this.apiURL}/v1/maps?importSource=${FOURSQUARE_KEPLER_GL_IMPORT_SOURCE}`,\n      {\n        method: 'GET',\n        mode: 'cors',\n        headers\n      }\n    );\n    const data = await response.json();\n    return (data?.items || []).map(map => convertFSQModelToMapItem(map, `${this.apiURL}/v1/maps`));\n  }\n\n  async downloadMap(loadParams) {\n    let {id, path} = loadParams;\n    if (!id) {\n      // try to get map id from foursquare map path\n      if (typeof path === 'string') {\n        const pathId = /((\\w{4,12}-?)){5}/.exec(path)[0];\n        if (pathId) {\n          id = pathId;\n        }\n      }\n    }\n\n    if (!id) {\n      return Promise.reject('No Map id was provider as part of loadParams');\n    }\n    const headers = await this.getHeaders();\n\n    const response = await fetch(`${this.apiURL}/v1/maps/${id}`, {\n      method: 'GET',\n      headers\n    });\n\n    const map = await response.json();\n\n    return Promise.resolve({\n      map: extractMapFromFSQResponse(map),\n      format: KEPLER_FORMAT\n    });\n  }\n\n  getMapUrl(loadParams) {\n    const {id} = loadParams;\n    return id ? `${this.apiURL}/v1/maps/${id}` : null;\n  }\n\n  getManagementUrl() {\n    return this._folderLink;\n  }\n\n  async getAccessToken() {\n    return this._auth0.getTokenSilently();\n  }\n\n  async getHeaders() {\n    const accessToken = await this.getAccessToken();\n    return {\n      Authorization: `Bearer ${accessToken}`,\n      Accept: 'application/json',\n      'Content-Type': 'application/json'\n    };\n  }\n}\n"
  },
  {
    "path": "examples/demo-app/src/cloud-providers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CLOUD_PROVIDERS_CONFIGURATION} from '../constants/default-settings';\n\nimport DropboxProvider from './dropbox/dropbox-provider';\nimport CartoProvider from './carto/carto-provider';\nimport FoursquareProvider from './foursquare/foursquare-provider';\n\nconst {\n  DROPBOX_CLIENT_ID,\n  CARTO_CLIENT_ID,\n  FOURSQUARE_CLIENT_ID,\n  FOURSQUARE_DOMAIN,\n  FOURSQUARE_API_URL,\n  FOURSQUARE_USER_MAPS_URL\n} = CLOUD_PROVIDERS_CONFIGURATION;\n\nconst DROPBOX_CLIENT_NAME = 'Kepler.gl Demo App';\n\nexport const DEFAULT_CLOUD_PROVIDER = 'dropbox';\n\nexport const CLOUD_PROVIDERS = [\n  new FoursquareProvider({\n    clientId: FOURSQUARE_CLIENT_ID,\n    authDomain: FOURSQUARE_DOMAIN,\n    apiURL: FOURSQUARE_API_URL,\n    userMapsURL: FOURSQUARE_USER_MAPS_URL\n  }),\n  new DropboxProvider(DROPBOX_CLIENT_ID, DROPBOX_CLIENT_NAME),\n  new CartoProvider(CARTO_CLIENT_ID)\n];\n\nexport function getCloudProvider(providerName) {\n  const cloudProvider = CLOUD_PROVIDERS.find(provider => provider.name === providerName);\n  if (!cloudProvider) {\n    throw new Error(`Unknown cloud provider ${providerName}`);\n  }\n  return cloudProvider;\n}\n"
  },
  {
    "path": "examples/demo-app/src/components/announcement.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nconst StyledText = styled.div`\n  font-size: 12px;\n`;\n\nconst StyledLink = styled.a`\n  text-decoration: underline !important;\n  color: white !important;\n  font-weight: 500;\n  margin-left: 8px;\n\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nconst DisableBanner = styled.div`\n  display: inline-block;\n  margin-left: 20px;\n`;\n\n// We are using the link to make sure users who have seen\n// previous banners can see this one because we check localstorage key\nexport const FormLink = 'https://shan990829.typeform.com/to/RbCAXt';\n\nconst Announcement = ({onDisable}) => (\n  <StyledText>\n    <span>\n      Kepler.gl turns two years old! Help our open source community by taking this 5-minute-survey\n      and get a chance to win a <b>$100 Amazon gift card</b>. Make your answers count!\n    </span>\n    <StyledLink target=\"_blank\" href={FormLink}>\n      Take the survey\n    </StyledLink>\n    {onDisable ? (\n      <DisableBanner>\n        <StyledLink onClick={onDisable}>Already provided my feedback!</StyledLink>\n      </DisableBanner>\n    ) : null}\n  </StyledText>\n);\n\nexport default Announcement;\n"
  },
  {
    "path": "examples/demo-app/src/components/banner.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {Icons} from '@kepler.gl/components';\n\nconst StyledBanner = styled.div`\n  align-items: center;\n  background-color: ${props => props.bgColor};\n  color: ${props => props.fontColor};\n  display: flex;\n  height: ${props => props.height}px;\n  justify-content: space-between;\n  padding: 0 40px;\n  position: absolute;\n  transition: top 1s;\n  width: 100%;\n  z-index: 9999;\n\n  svg:hover {\n    cursor: pointer;\n  }\n\n  top: ${props => (props.visible ? 0 : -100)}px;\n`;\n\nconst Banner = ({\n  bgColor = '#1F7CF4',\n  fontColor = '#FFFFFF',\n  height = 30,\n  children,\n  onClose,\n  show\n}) => (\n  <StyledBanner\n    className=\"top-banner\"\n    bgColor={bgColor}\n    fontColor={fontColor}\n    height={height}\n    visible={show}\n  >\n    <div>{children}</div>\n    <Icons.Delete height=\"14px\" onClick={onClose} />\n  </StyledBanner>\n);\n\nexport default Banner;\n"
  },
  {
    "path": "examples/demo-app/src/components/load-data-modal/load-remote-map.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// TODO: this will move onto kepler.gl core\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\n\nimport {validateUrl} from '@kepler.gl/common-utils';\nimport {Button} from '@kepler.gl/components';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {CORS_LINK} from '../../constants/default-settings';\n\nconst propTypes = {\n  onLoadRemoteMap: PropTypes.func.isRequired\n};\n\nconst StyledDescription = styled.div`\n  font-size: 14px;\n  color: ${props => props.theme.labelColorLT};\n  line-height: 18px;\n  margin-bottom: 12px;\n`;\n\nconst InputForm = styled.div`\n  flex-grow: 1;\n  padding: 32px;\n  background-color: ${props => props.theme.panelBackgroundLT};\n`;\n\nconst StyledInput = styled.input`\n  width: 100%;\n  padding: ${props => props.theme.inputPadding};\n  color: ${props => (props.error ? 'red' : props.theme.titleColorLT)};\n  height: ${props => props.theme.inputBoxHeight};\n  border: 0;\n  outline: 0;\n  font-size: ${props => props.theme.inputFontSize};\n\n  &:active,\n  &:focus,\n  &.focus,\n  &.active {\n    outline: 0;\n  }\n`;\n\nconst StyledFromGroup = styled.div`\n  margin-top: 30px;\n  display: flex;\n  flex-direction: row;\n`;\n\nexport const StyledInputLabel = styled.div`\n  font-size: 11px;\n  color: ${props => props.theme.textColorLT};\n  letter-spacing: 0.2px;\n  ul {\n    padding-left: 12px;\n  }\n`;\n\nexport const StyledError = styled.div`\n  color: red;\n`;\n\nexport const StyledErrorDescription = styled.div`\n  font-size: 14px;\n`;\n\nconst Error = ({error, url}) => (\n  <StyledError>\n    <StyledErrorDescription>{url}</StyledErrorDescription>\n    <StyledErrorDescription>{error.message}</StyledErrorDescription>\n  </StyledError>\n);\n\nconst CORS_LINK_MESSAGE = {corsLink: CORS_LINK};\n\nclass LoadRemoteMap extends Component {\n  state = {\n    dataUrl: '',\n    error: null,\n    submitted: false\n  };\n\n  onMapUrlChange = e => {\n    this.setState({\n      dataUrl: e.target.value,\n      error: !validateUrl(e.target.value) ? {message: 'Incorrect URL'} : null\n    });\n  };\n\n  onLoadRemoteMap = () => {\n    const {dataUrl, error} = this.state;\n\n    this.setState({submitted: true});\n\n    if (!dataUrl || error) {\n      return;\n    }\n\n    this.props.onLoadRemoteMap({dataUrl});\n  };\n\n  render() {\n    const displayedError = this.props.error || this.state.submitted ? this.state.error : null;\n\n    return (\n      <div>\n        <InputForm>\n          <StyledDescription>\n            <FormattedMessage id={'loadRemoteMap.description'} />\n          </StyledDescription>\n          <StyledInputLabel>\n            <FormattedMessage id={'loadRemoteMap.message'} />\n          </StyledInputLabel>\n          <StyledInputLabel>\n            <FormattedMessage id={'loadRemoteMap.examples'} />\n            <ul>\n              <li>https://your.map.url/map.json</li>\n              <li>http://your.map.url/data.csv</li>\n            </ul>\n          </StyledInputLabel>\n          <StyledInputLabel>\n            <FormattedMessage id={'loadRemoteMap.cors'} />\n            <a rel=\"noopener noreferrer\" target=\"_blank\" href={CORS_LINK_MESSAGE.corsLink}>\n              <FormattedMessage id={'loadRemoteMap.clickHere'} />\n            </a>\n          </StyledInputLabel>\n          <StyledFromGroup>\n            <StyledInput\n              onChange={this.onMapUrlChange}\n              type=\"text\"\n              placeholder=\"Url\"\n              value={this.state.dataUrl}\n              error={displayedError}\n            />\n            <Button type=\"submit\" cta size=\"small\" onClick={this.onLoadRemoteMap}>\n              <FormattedMessage id={'loadRemoteMap.fetch'} />\n            </Button>\n          </StyledFromGroup>\n          {displayedError && <Error error={displayedError} url={this.props.option?.dataUrl} />}\n        </InputForm>\n      </div>\n    );\n  }\n}\n\nLoadRemoteMap.propTypes = propTypes;\n\nexport default LoadRemoteMap;\n"
  },
  {
    "path": "examples/demo-app/src/components/load-data-modal/sample-data-viewer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {format} from 'd3-format';\nimport {LoadingDialog} from '@kepler.gl/components';\nimport {FormattedMessage} from 'react-intl';\n\nconst numFormat = format(',');\n\nconst StyledSampleGallery = styled.div`\n  display: flex;\n  justify-content: space-between;\n  flex-wrap: wrap;\n`;\n\nconst StyledSampleMap = styled.div`\n  font-size: 14px;\n  font-weight: 500;\n  color: ${props => props.theme.textColorLT};\n  line-height: 22px;\n  width: 30%;\n  max-width: 480px;\n  margin-bottom: 50px;\n\n  .sample-map__image {\n    border-radius: 4px;\n    overflow: hidden;\n    margin-bottom: 12px;\n    opacity: 0.9;\n    transition: opacity 0.4s ease;\n    position: relative;\n    line-height: 0;\n\n    img {\n      max-width: 100%;\n    }\n    &:hover {\n      cursor: pointer;\n      opacity: 1;\n    }\n  }\n\n  .sample-map__size {\n    font-size: 12px;\n    font-weight: 400;\n    line-height: 24px;\n  }\n\n  &:hover {\n    .sample-map__image__caption {\n      opacity: 0.8;\n      transition: opacity 0.4s ease;\n    }\n  }\n`;\n\nconst StyledImageCaption = styled.div`\n  color: ${props => props.theme.labelColorLT};\n  font-size: 11px;\n  font-weight: 400;\n  line-height: 16px;\n  margin-top: 10px;\n  opacity: 0;\n`;\n\nconst StyledError = styled.div`\n  color: red;\n  font-size: 14px;\n  margin-bottom: 16px;\n`;\n\nconst SampleMap = ({id, sample, onClick}) => (\n  <StyledSampleMap id={id} className=\"sample-map-gallery__item\">\n    <div className=\"sample-map\">\n      <div className=\"sample-map__image\" onClick={onClick}>\n        {sample.imageUrl && <img src={sample.imageUrl} />}\n      </div>\n      <div className=\"sample-map__title\">{sample.label}</div>\n      <div className=\"sample-map__size\">\n        <FormattedMessage\n          id={'sampleDataViewer.rowCount'}\n          values={{rowCount: numFormat(sample.size)}}\n        />\n      </div>\n      <StyledImageCaption className=\"sample-map__image__caption\">\n        {sample.description}\n      </StyledImageCaption>\n    </div>\n  </StyledSampleMap>\n);\n\nconst SampleMapGallery = ({\n  sampleMaps,\n  onLoadSample,\n  error,\n  isMapLoading,\n  locale,\n  loadSampleConfigurations\n}) => {\n  useEffect(() => {\n    if (!sampleMaps.length) {\n      loadSampleConfigurations();\n    }\n  }, [sampleMaps, loadSampleConfigurations]);\n\n  return (\n    <div className=\"sample-data-modal\">\n      {error ? (\n        <StyledError>{error.message}</StyledError>\n      ) : isMapLoading ? (\n        <LoadingDialog size={64} />\n      ) : (\n        <StyledSampleGallery className=\"sample-map-gallery\">\n          {sampleMaps\n            .filter(sp => sp.visible)\n            .map(sp => (\n              <SampleMap\n                id={sp.id}\n                sample={sp}\n                key={sp.id}\n                onClick={() => onLoadSample(sp)}\n                locale={locale}\n              />\n            ))}\n        </StyledSampleGallery>\n      )}\n    </div>\n  );\n};\n\nSampleMapGallery.propTypes = {\n  sampleMaps: PropTypes.arrayOf(PropTypes.object),\n  onLoadSample: PropTypes.func.isRequired,\n  loadSampleConfigurations: PropTypes.func.isRequired,\n  error: PropTypes.object\n};\n\nexport default SampleMapGallery;\n"
  },
  {
    "path": "examples/demo-app/src/components/load-data-modal/sample-maps-tab.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {Icons} from '@kepler.gl/components';\nimport {media} from '@kepler.gl/styles';\nimport {FormattedMessage} from 'react-intl';\n\nimport {ASSETS_URL} from '../../constants/default-settings';\n\nconst StyledMapIcon = styled.div`\n  background-image: url('${ASSETS_URL}icon-demo-map.jpg');\n  background-repeat: no-repeat;\n  background-size: 64px 48px;\n  width: 64px;\n  height: 48px;\n  border-radius: 2px;\n\n  ${media.portable`\n    width: 48px;\n    height: 32px;\n  `};\n`;\n\nconst StyledTrySampleData = styled.div`\n  display: flex;\n  margin-bottom: 12px;\n  flex-grow: 1;\n  justify-content: flex-end;\n  color: ${props => props.theme.subtextColorLT};\n\n  .demo-map-title {\n    margin-left: 16px;\n    display: flex;\n    flex-direction: column;\n    justify-content: flex-end;\n  }\n\n  .demo-map-label {\n    font-size: 11px;\n\n    ${media.portable`\n      font-size: 10px;\n    `};\n  }\n\n  .demo-map-action {\n    display: flex;\n    font-size: 14px;\n    align-items: center;\n    color: ${props => props.theme.subtextColorLT};\n    cursor: pointer;\n\n    ${media.portable`\n      font-size: 12px;\n    `};\n\n    &:hover {\n      color: ${props => props.theme.textColorLT};\n    }\n\n    span {\n      white-space: nowrap;\n    }\n\n    svg {\n      margin-left: 10px;\n    }\n  }\n`;\n\nconst SampleMapsTab = ({onClick}) => {\n  return (\n    <StyledTrySampleData className=\"try-sample-data\">\n      <StyledMapIcon className=\"demo-map-icon\" />\n      <div className=\"demo-map-title\">\n        <div className=\"demo-map-label\">\n          <FormattedMessage id={'sampleMapsTab.noData'} defaultMessage=\"No Data\" />\n        </div>\n        <div className=\"demo-map-action\" onClick={onClick}>\n          <FormattedMessage id={'sampleMapsTab.trySampleData'} defaultMessage=\"Sample Maps\" />\n          <Icons.ArrowRight height=\"16px\" />\n        </div>\n      </div>\n    </StyledTrySampleData>\n  );\n};\n\nexport default SampleMapsTab;\n"
  },
  {
    "path": "examples/demo-app/src/components/map-control/map-control.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport React, {useState} from 'react';\nimport Markdown from 'markdown-to-jsx';\nimport styled from 'styled-components';\nimport {useLocalStorage} from 'usehooks-ts';\n\nimport {Icons, IconRoundSmall, LinkRenderer, MapControlButton} from '@kepler.gl/components';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nconst StyledFloatingPanel = styled.div`\n  margin-right: 12px;\n  margin-top: 12px;\n`;\n\nconst StyledProjectPanel = styled.div`\n  background: ${props => props.theme.panelBackground};\n  padding: 16px 16px 16px 20px;\n  width: 280px;\n  box-shadow: ${props => props.theme.panelBoxShadow};\n\n  .project-title {\n    color: ${props => props.theme.titleTextColor};\n    font-size: 13px;\n    font-weight: 500;\n    display: flex;\n    justify-content: space-between;\n  }\n\n  .project-description {\n    color: ${props => props.theme.textColor};\n    font-size: 11px;\n    margin-top: 12px;\n\n    a {\n      font-weight: 500;\n      color: ${props => props.theme.titleTextColor};\n    }\n  }\n\n  .project-links {\n    margin-top: 16px;\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n  }\n`;\nconst StyledPanelAction = styled.div`\n  border-radius: 2px;\n  margin-left: 4px;\n  padding: 5px;\n  font-weight: 500;\n\n  a {\n    align-items: center;\n    justify-content: flex-start;\n    display: flex;\n    height: 16px;\n    padding-right: 10px;\n    color: ${props => (props.active ? props.theme.textColorHl : props.theme.subtextColor)};\n\n    svg {\n      margin-right: 8px;\n    }\n  }\n\n  &:hover {\n    cursor: pointer;\n    a {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nexport const LinkButton = props => (\n  <StyledPanelAction className=\"project-link__action\">\n    <a target=\"_blank\" rel=\"noopener noreferrer\" href={props.href}>\n      <props.iconComponent height={props.height || '16px'} />\n      <p>{props.label}</p>\n    </a>\n  </StyledPanelAction>\n);\n\nconst CloseButton = ({onClick}) => (\n  <IconRoundSmall>\n    <Icons.Close height=\"16px\" onClick={onClick} />\n  </IconRoundSmall>\n);\n\n// convert https://raw.githubusercontent.com/keplergl/kepler.gl-data/master/nyctrips/config.json\n// to https://github.com/keplergl/kepler.gl-data/blob/master/movement_pittsburgh/config.json\nfunction getURL(url) {\n  return url\n    ? url\n        .replace('https://raw.githubusercontent.com', 'https://github.com')\n        .replace('master', 'blob/master')\n    : url;\n}\n\nexport function SampleMapPanel(props) {\n  const [isActive, setActive] = useState(true);\n\n  return (\n    <StyledFloatingPanel>\n      {isActive ? (\n        <StyledProjectPanel>\n          <div className=\"project-title\">\n            <div>{props.currentSample.label}</div>\n            <CloseButton onClick={() => setActive(false)} />\n          </div>\n          <div className=\"project-description\">\n            <Markdown\n              options={{\n                overrides: {\n                  a: {\n                    component: LinkRenderer\n                  }\n                }\n              }}\n            >\n              {props.currentSample.detail || props.currentSample.description}\n            </Markdown>\n          </div>\n          <div className=\"project-links\">\n            <LinkButton\n              label=\"Data\"\n              href={getURL(\n                props.currentSample.dataUrl || props.currentSample.remoteDatasetConfigUrl\n              )}\n              iconComponent={Icons.Files}\n              height=\"15px\"\n            />\n            <LinkButton\n              label=\"Config\"\n              href={getURL(props.currentSample.configUrl)}\n              iconComponent={Icons.CodeAlt}\n              height=\"17px\"\n            />\n          </div>\n        </StyledProjectPanel>\n      ) : (\n        <MapControlButton\n          className={classnames('map-control-button', 'info-panel', {isActive})}\n          onClick={e => {\n            e.preventDefault();\n            setActive(true);\n          }}\n        >\n          <Icons.Docs height=\"18px\" />\n        </MapControlButton>\n      )}\n    </StyledFloatingPanel>\n  );\n}\n\nexport function BannerMapPanel() {\n  const [isActive, setActive] = useState(true);\n  // Once the banner is closed, the user won't see the banner during next sessions.\n  const [showBanner, setShowBanner] = useLocalStorage(\n    'show-duckdb-preview-banner',\n    getApplicationConfig().showReleaseBanner\n  );\n  const [wasVisible] = useState(showBanner);\n\n  if (!showBanner && !wasVisible) {\n    return null;\n  }\n\n  return (\n    <StyledFloatingPanel>\n      {isActive ? (\n        <StyledProjectPanel>\n          <div className=\"project-title\">\n            <div>{'Kepler.gl 3.1 + DuckDB is here!'}</div>\n\n            <CloseButton\n              onClick={() => {\n                setShowBanner(false);\n                setActive(false);\n              }}\n            />\n          </div>\n          <div className=\"project-description\">\n            <Markdown\n              options={{\n                overrides: {\n                  a: {\n                    component: LinkRenderer\n                  }\n                }\n              }}\n            >\n              {\n                '[Click here](https://kepler-preview.foursquare.com) to check out the preview of Kepler.gl 3.1 with DuckDB enabled!'\n              }\n            </Markdown>\n          </div>\n        </StyledProjectPanel>\n      ) : (\n        <MapControlButton\n          className={classnames('map-control-button', 'info-panel', {isActive})}\n          onClick={e => {\n            e.preventDefault();\n            setActive(true);\n          }}\n        >\n          <Icons.Docs height=\"18px\" />\n        </MapControlButton>\n      )}\n    </StyledFloatingPanel>\n  );\n}\n"
  },
  {
    "path": "examples/demo-app/src/components/map-control/sql-panel-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, ComponentType} from 'react';\n\nimport {MapControlButton, MapControlTooltipFactory} from '@kepler.gl/components';\nimport {MapControls} from '@kepler.gl/types';\n\ninterface SQLControlIcons {\n  sqlPanelIcon: ComponentType<any>;\n}\n\nexport type SqlPanelControlProps = {\n  mapControls: MapControls;\n  onToggleMapControl: (control: string) => void;\n  actionIcons: SQLControlIcons;\n};\n\nSqlPanelControlFactory.deps = [MapControlTooltipFactory];\n\nexport default function SqlPanelControlFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>\n): React.FC<SqlPanelControlProps> {\n  const SqlPanelControl = ({mapControls, onToggleMapControl}) => {\n    const onClick = useCallback(\n      event => {\n        event.preventDefault();\n        onToggleMapControl('sqlPanel');\n      },\n      [onToggleMapControl]\n    );\n\n    const showControl = mapControls?.sqlPanel?.show;\n    if (!showControl) {\n      return null;\n    }\n\n    const active = mapControls?.sqlPanel?.active;\n    return (\n      <MapControlTooltip\n        id=\"show-sql-panel\"\n        message={active ? 'tooltip.hideSQLPanel' : 'tooltip.showSQLPanel'}\n      >\n        <MapControlButton\n          className=\"map-control-button toggle-sql-panel\"\n          onClick={onClick}\n          active={active}\n        >\n          SQL\n        </MapControlButton>\n      </MapControlTooltip>\n    );\n  };\n\n  return SqlPanelControl;\n}\n"
  },
  {
    "path": "examples/demo-app/src/constants/default-settings.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* different option share same query type e.g. events,\nand segments both use queryRunner */\nimport keyMirror from 'keymirror';\n\nexport const ASSETS_URL = 'https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/';\nexport const DATA_URL = 'https://raw.githubusercontent.com/keplergl/kepler.gl-data/master/';\nexport const MAP_URI = 'demo/map?mapUrl=';\n/*\n * If you want to add more samples, feel free to edit the json file on github kepler.gl data repo\n */\nexport const MAP_CONFIG_URL = `${DATA_URL}samples.json?nocache=${new Date().getTime()}`;\n\n/**\n * I know this is already defined in Kepler core but it should be defined here\n * because it belongs to the demo app\n * @type {string}\n */\nexport const KEPLER_GL_WEBSITE = 'http://kepler.gl/';\n\nexport const QUERY_TYPES = keyMirror({\n  file: null,\n  sample: null\n});\n\nexport const QUERY_OPTIONS = keyMirror({\n  csv: null,\n  geojson: null\n});\n\nexport const LOADING_METHODS = keyMirror({\n  remote: null,\n  sample: null\n});\n\nexport const LOADING_SAMPLE_LIST_ERROR_MESSAGE = 'Not able to load sample gallery';\nexport const LOADING_SAMPLE_ERROR_MESSAGE = 'Not able to load sample';\nexport const CORS_LINK = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS';\n\nexport const DEFAULT_FEATURE_FLAGS = {\n  cloudStorage: true\n};\n\nexport const CLOUD_PROVIDERS_CONFIGURATION = {\n  MAPBOX_TOKEN: process.env.MapboxAccessToken, // eslint-disable-line\n  DROPBOX_CLIENT_ID: process.env.DropboxClientId, // eslint-disable-line\n  EXPORT_MAPBOX_TOKEN: process.env.MapboxExportToken, // eslint-disable-line\n  CARTO_CLIENT_ID: process.env.CartoClientId, // eslint-disable-line\n  FOURSQUARE_CLIENT_ID: process.env.FoursquareClientId, // eslint-disable-line\n  FOURSQUARE_DOMAIN: process.env.FoursquareDomain, // eslint-disable-line\n  FOURSQUARE_API_URL: process.env.FoursquareAPIURL, // eslint-disable-line\n  FOURSQUARE_USER_MAPS_URL: process.env.FoursquareUserMapsURL // eslint-disable-line\n};\n"
  },
  {
    "path": "examples/demo-app/src/constants/localization.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Add english messages here, other languages will use these\n// if translations not available for every message\nconst en = {\n  'modal.loadData.remote': 'Load Map using URL',\n  'sampleMapsTab.noData': 'No data ?',\n  'sampleMapsTab.trySampleData': 'Try sample data',\n  'sampleDataViewer.rowCount': ' {rowCount} rows',\n  'loadRemoteMap.description': 'Load your map using your custom URL',\n  'loadRemoteMap.message':\n    'You can use the following formats: CSV | JSON | Kepler.gl config json. Make sure the url contains the file extension.',\n  'loadRemoteMap.examples': 'Examples:',\n  'loadRemoteMap.cors':\n    '* CORS policy must be defined on your custom url domain in order to be accessible. For more info ',\n  'loadRemoteMap.clickHere': 'click here',\n  'loadRemoteMap.fetch': 'Fetch',\n  'tooltip.hideSQLPanel': 'Hide SQL Panel',\n  'tooltip.showSQLPanel': 'Show SQL Panel'\n};\n\nexport const messages = {\n  en,\n  fi: {\n    'modal.loadData.remote': 'Lataa kartta URL-osoitteen avulla',\n    'sampleMapsTab.noData': 'Ei aineistoja?',\n    'sampleMapsTab.trySampleData': 'Kokeile testiaineistoja',\n    'sampleDataViewer.rowCount': ' {rowCount} riviä',\n    'loadRemoteMap.description': 'Lataa karttasi käyttämällä omaa urlia',\n    'loadRemoteMap.message':\n      'Voit käyttää formaatteja: CSV | JSON | Kepler.gl asetus-json. Varmista, että url sisältää tiedostopäätteen nimen.',\n    'loadRemoteMap.examples': 'Esimerkkejä:',\n    'loadRemoteMap.cors':\n      '* CORS-käytäntö pitää olla määriteltynä urlin domainissa, jotta aineiston voi ladata.',\n    'loadRemoteMap.clickHere': 'Lisätietoja',\n    'loadRemoteMap.fetch': 'Nouda'\n  },\n  ca: {\n    'modal.loadData.remote': 'Carrega mapa mitjançant URL',\n    'sampleMapsTab.noData': 'Cap dada?',\n    'sampleMapsTab.trySampleData': 'Prova dades de mostra',\n    'sampleDataViewer.rowCount': ' {rowCount} files',\n    'loadRemoteMap.description': 'Carrega el teu mapa amb la teva URL personalitzada',\n    'loadRemoteMap.message':\n      \"Pots emprar els següents formats: CSV | JSON | Kepler.gl config json. Assegura't que la URL contingui l'extensió de l'arxiu.\",\n    'loadRemoteMap.examples': 'Exemples:',\n    'loadRemoteMap.cors':\n      '* La política CORS s’ha de definir al teu domini per tal que sigui accessible. Per a més informació ',\n    'loadRemoteMap.clickHere': 'fes clic aquí',\n    'loadRemoteMap.fetch': 'Cerca'\n  },\n  es: {\n    'modal.loadData.remote': 'Cargar mapa usando URL',\n    'sampleMapsTab.noData': 'Ningún dato?',\n    'sampleMapsTab.trySampleData': 'Prueba datos de muestra',\n    'sampleDataViewer.rowCount': ' {rowCount} files',\n    'loadRemoteMap.description': 'Carga tu mapa con tu enlace personalizado',\n    'loadRemoteMap.message':\n      'Puedes usar los siguientes formatos: CSV | JSON | Kepler.gl config json. Asegurate que el enlace contenga la extensión del archivo.',\n    'loadRemoteMap.examples': 'Ejemplos:',\n    'loadRemoteMap.cors':\n      '* La política CORS debe ser definida en tu dominio para que sea accessible. Para más información ',\n    'loadRemoteMap.clickHere': 'haz clic aquí',\n    'loadRemoteMap.fetch': 'Busca'\n  },\n  cn: {\n    'modal.loadData.remote': '使用 URL 加载地图',\n    'sampleMapsTab.noData': '没有数据？',\n    'sampleMapsTab.trySampleData': '尝试样本数据',\n    'sampleDataViewer.rowCount': ' {rowCount} 行',\n    'loadRemoteMap.description': '使用自定义 URL 加载地图',\n    'loadRemoteMap.message':\n      '您可以使用以下格式：CSV | JSON | Kepler.gl 配置 json。 确保 url 包含文件扩展名。',\n    'loadRemoteMap.examples': '示例：',\n    'loadRemoteMap.cors': '* 必须在您的自定义 url 域上定义 CORS 策略才能访问。欲了解更多信息',\n    'loadRemoteMap.clickHere': '点击此处',\n    'loadRemoteMap.fetch': '获取'\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-animate-trip-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  type: 'FeatureCollection',\n  features: [\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'A',\n        value: 10\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.78966, 40.6429, 0, 1565578338],\n          [-73.7895, 40.64267, 0, 1565578346],\n          [-73.78923, 40.6424, 0, 1565578356],\n          [-73.78905, 40.64222, 0, 1565578364],\n          [-73.7889, 40.64209, 0, 1565578365],\n          [-73.78859, 40.64191, 0, 1565578367],\n          [-73.78836, 40.64181, 0, 1565578368],\n          [-73.78793, 40.6417, 0, 1565578371],\n          [-73.78756, 40.64163, 0, 1565578373],\n          [-73.78707, 40.64159, 0, 1565578375],\n          [-73.78674, 40.64161, 0, 1565578377],\n          [-73.78659, 40.64166, 0, 1565578378],\n          [-73.78646, 40.64172, 0, 1565578379],\n          [-73.78633, 40.64182, 0, 1565578380],\n          [-73.78612, 40.6422, 0, 1565578382],\n          [-73.78607, 40.64244, 0, 1565578384],\n          [-73.7861, 40.64263, 0, 1565578385],\n          [-73.78621, 40.64305, 0, 1565578388],\n          [-73.78621, 40.64323, 0, 1565578390],\n          [-73.78619, 40.64337, 0, 1565578391],\n          [-73.78612, 40.6435, 0, 1565578392],\n          [-73.78586, 40.64382, 0, 1565578394],\n          [-73.78583, 40.64388, 0, 1565578395],\n          [-73.78582, 40.64397, 0, 1565578395],\n          [-73.78585, 40.64404, 0, 1565578396],\n          [-73.78655, 40.64488, 0, 1565578403],\n          [-73.78666, 40.64505, 0, 1565578404],\n          [-73.78685, 40.64546, 0, 1565578407],\n          [-73.7869, 40.64571, 0, 1565578409],\n          [-73.7869, 40.64592, 0, 1565578410],\n          [-73.78687, 40.64619, 0, 1565578412],\n          [-73.78677, 40.64634, 0, 1565578413],\n          [-73.78668, 40.64643, 0, 1565578414],\n          [-73.78658, 40.64649, 0, 1565578415],\n          [-73.78645, 40.64655, 0, 1565578416],\n          [-73.78629, 40.64657, 0, 1565578416],\n          [-73.78613, 40.64656, 0, 1565578417],\n          [-73.786, 40.64653, 0, 1565578418],\n          [-73.78588, 40.64647, 0, 1565578419],\n          [-73.78578, 40.6464, 0, 1565578419],\n          [-73.7857, 40.6463, 0, 1565578420],\n          [-73.78566, 40.64622, 0, 1565578421],\n          [-73.78565, 40.64607, 0, 1565578422],\n          [-73.78574, 40.64584, 0, 1565578423],\n          [-73.78588, 40.64565, 0, 1565578425],\n          [-73.78611, 40.64547, 0, 1565578427],\n          [-73.78638, 40.64534, 0, 1565578428],\n          [-73.78677, 40.6452, 0, 1565578431],\n          [-73.78702, 40.64514, 0, 1565578432],\n          [-73.78744, 40.64505, 0, 1565578434],\n          [-73.7878, 40.645, 0, 1565578436],\n          [-73.78831, 40.64496, 0, 1565578439],\n          [-73.79014, 40.64495, 0, 1565578449]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'B',\n        value: 4\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.33223, 40.64375, 0, 1565578213],\n          [-74.33242, 40.64353, 0, 1565578217],\n          [-74.33001, 40.64222, 0, 1565578243],\n          [-74.32882, 40.64154, 0, 1565578256],\n          [-74.32682, 40.64039, 0, 1565578278],\n          [-74.32589, 40.63985, 0, 1565578288],\n          [-74.31725, 40.63485, 0, 1565578382],\n          [-74.31404, 40.63302, 0, 1565578417],\n          [-74.30616, 40.6283, 0, 1565578504],\n          [-74.30592, 40.62814, 0, 1565578506],\n          [-74.3056, 40.62783, 0, 1565578511],\n          [-74.30549, 40.62763, 0, 1565578513],\n          [-74.30536, 40.62722, 0, 1565578518],\n          [-74.30524, 40.62704, 0, 1565578521],\n          [-74.30511, 40.62689, 0, 1565578523],\n          [-74.30447, 40.62623, 0, 1565578532],\n          [-74.30409, 40.62652, 0, 1565578536],\n          [-74.30374, 40.62689, 0, 1565578541],\n          [-74.30328, 40.62753, 0, 1565578549],\n          [-74.30297, 40.6279, 0, 1565578553],\n          [-74.30174, 40.6292, 0, 1565578570],\n          [-74.29912, 40.63154, 0, 1565578585],\n          [-74.29855, 40.63202, 0, 1565578588],\n          [-74.29796, 40.63249, 0, 1565578591],\n          [-74.29508, 40.63461, 0, 1565578606],\n          [-74.29448, 40.63508, 0, 1565578610],\n          [-74.29409, 40.6354, 0, 1565578612],\n          [-74.29373, 40.63575, 0, 1565578614],\n          [-74.29322, 40.63626, 0, 1565578617],\n          [-74.29209, 40.63753, 0, 1565578624],\n          [-74.29086, 40.63902, 0, 1565578633],\n          [-74.28998, 40.64015, 0, 1565578639],\n          [-74.28944, 40.64091, 0, 1565578644],\n          [-74.28908, 40.64151, 0, 1565578647],\n          [-74.2888, 40.64201, 0, 1565578649],\n          [-74.2883, 40.64303, 0, 1565578655],\n          [-74.28789, 40.64408, 0, 1565578660],\n          [-74.28762, 40.64494, 0, 1565578664],\n          [-74.28745, 40.6457, 0, 1565578668],\n          [-74.28733, 40.64636, 0, 1565578671],\n          [-74.28723, 40.64724, 0, 1565578676],\n          [-74.28717, 40.64812, 0, 1565578680],\n          [-74.28712, 40.65661, 0, 1565578721],\n          [-74.28709, 40.66022, 0, 1565578739],\n          [-74.28705, 40.6611, 0, 1565578743],\n          [-74.28698, 40.66175, 0, 1565578746],\n          [-74.28687, 40.6624, 0, 1565578750],\n          [-74.28667, 40.66327, 0, 1565578754],\n          [-74.28642, 40.6641, 0, 1565578758],\n          [-74.28619, 40.66472, 0, 1565578761],\n          [-74.28573, 40.66576, 0, 1565578766],\n          [-74.2853, 40.66657, 0, 1565578771],\n          [-74.28506, 40.66697, 0, 1565578773],\n          [-74.28434, 40.66804, 0, 1565578779],\n          [-74.28077, 40.67287, 0, 1565578806]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'A',\n        value: 7\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.87893, 40.64672, 0, 1565578095],\n          [-73.87969, 40.64624, 0, 1565578123],\n          [-73.87976, 40.64619, 0, 1565578125],\n          [-73.88064, 40.64697, 0, 1565578156],\n          [-73.88138, 40.64765, 0, 1565578181],\n          [-73.88234, 40.64849, 0, 1565578214],\n          [-73.883, 40.64911, 0, 1565578237],\n          [-73.88338, 40.64943, 0, 1565578250],\n          [-73.88349, 40.64941, 0, 1565578252],\n          [-73.88411, 40.64995, 0, 1565578273],\n          [-73.8848, 40.65076, 0, 1565578302],\n          [-73.88624, 40.6527, 0, 1565578367],\n          [-73.88632, 40.65281, 0, 1565578371],\n          [-73.88747, 40.65232, 0, 1565578386],\n          [-73.88798, 40.6521, 0, 1565578393],\n          [-73.88896, 40.65341, 0, 1565578430],\n          [-73.88978, 40.65306, 0, 1565578442],\n          [-73.89059, 40.65271, 0, 1565578454],\n          [-73.89413, 40.65747, 0, 1565578588],\n          [-73.89424, 40.65762, 0, 1565578593],\n          [-73.89595, 40.65786, 0, 1565578627],\n          [-73.89613, 40.65785, 0, 1565578630],\n          [-73.89616, 40.65796, 0, 1565578633],\n          [-73.898, 40.65768, 0, 1565578661],\n          [-73.8988, 40.65753, 0, 1565578673],\n          [-73.89955, 40.65738, 0, 1565578684],\n          [-73.90021, 40.65728, 0, 1565578695],\n          [-73.90039, 40.65724, 0, 1565578698],\n          [-73.90051, 40.65719, 0, 1565578700],\n          [-73.90063, 40.65712, 0, 1565578703],\n          [-73.90076, 40.65702, 0, 1565578706],\n          [-73.90118, 40.6566, 0, 1565578717],\n          [-73.90135, 40.65646, 0, 1565578721],\n          [-73.90282, 40.65553, 0, 1565578751],\n          [-73.90299, 40.65544, 0, 1565578755],\n          [-73.90319, 40.65536, 0, 1565578758],\n          [-73.90321, 40.65546, 0, 1565578761],\n          [-73.90337, 40.65542, 0, 1565578765],\n          [-73.90616, 40.65501, 0, 1565578836]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'A',\n        value: 11\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.18572, 40.68929, 0, 1565577803],\n          [-74.1858, 40.68924, 0, 1565577808],\n          [-74.18564, 40.68909, 0, 1565577821],\n          [-74.18529, 40.68929, 0, 1565577830],\n          [-74.18486, 40.68954, 0, 1565577841],\n          [-74.18482, 40.68982, 0, 1565577849],\n          [-74.18481, 40.691, 0, 1565577882],\n          [-74.18484, 40.69121, 0, 1565577888],\n          [-74.18487, 40.6913, 0, 1565577891],\n          [-74.18494, 40.69143, 0, 1565577895],\n          [-74.18506, 40.69157, 0, 1565577900],\n          [-74.18531, 40.69177, 0, 1565577907],\n          [-74.18612, 40.69232, 0, 1565577931],\n          [-74.18633, 40.69247, 0, 1565577937],\n          [-74.18641, 40.69259, 0, 1565577941],\n          [-74.18643, 40.69267, 0, 1565577943],\n          [-74.18644, 40.69272, 0, 1565577944],\n          [-74.18641, 40.69288, 0, 1565577946],\n          [-74.18636, 40.69297, 0, 1565577947],\n          [-74.18582, 40.69346, 0, 1565577954],\n          [-74.18532, 40.69402, 0, 1565577961],\n          [-74.18499, 40.6945, 0, 1565577967]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'B',\n        value: 6\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.00823, 40.71351, 0, 1565577838],\n          [-74.00884, 40.7138, 0, 1565577867],\n          [-74.00668, 40.71653, 0, 1565577947],\n          [-74.00625, 40.71708, 0, 1565577962],\n          [-74.00753, 40.71766, 0, 1565577996],\n          [-74.00888, 40.71827, 0, 1565578032],\n          [-74.00869, 40.71929, 0, 1565578060],\n          [-74.00858, 40.71996, 0, 1565578077],\n          [-74.01191, 40.72029, 0, 1565578205],\n          [-74.01204, 40.72031, 0, 1565578210],\n          [-74.01097, 40.72554, 0, 1565578295],\n          [-74.01078, 40.72677, 0, 1565578315],\n          [-74.01008, 40.73394, 0, 1565578431],\n          [-74.00983, 40.73694, 0, 1565578480],\n          [-74.00979, 40.73746, 0, 1565578488],\n          [-74.00989, 40.73851, 0, 1565578505],\n          [-74.0099, 40.73899, 0, 1565578513],\n          [-74.00985, 40.73929, 0, 1565578518],\n          [-74.0098, 40.73942, 0, 1565578520],\n          [-74.00967, 40.73971, 0, 1565578525],\n          [-74.00928, 40.74033, 0, 1565578536],\n          [-74.00913, 40.74065, 0, 1565578541],\n          [-74.00906, 40.74081, 0, 1565578544],\n          [-74.00887, 40.74154, 0, 1565578556],\n          [-74.00871, 40.74219, 0, 1565578567],\n          [-74.00868, 40.74235, 0, 1565578569],\n          [-74.00837, 40.74395, 0, 1565578595],\n          [-74.00803, 40.74588, 0, 1565578625],\n          [-74.00773, 40.7475, 0, 1565578651],\n          [-74.00769, 40.74775, 0, 1565578654],\n          [-74.00762, 40.74845, 0, 1565578668],\n          [-74.00768, 40.7487, 0, 1565578672],\n          [-74.00777, 40.74892, 0, 1565578677],\n          [-74.00811, 40.74959, 0, 1565578690],\n          [-74.0082, 40.7498, 0, 1565578694],\n          [-74.00825, 40.75003, 0, 1565578699],\n          [-74.00827, 40.75029, 0, 1565578703],\n          [-74.00825, 40.75055, 0, 1565578708],\n          [-74.00699, 40.75388, 0, 1565578773],\n          [-74.00664, 40.75442, 0, 1565578784],\n          [-74.00336, 40.75891, 0, 1565578881]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'A',\n        value: 1\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.97301, 40.67601, 0, 1565578666],\n          [-73.97161, 40.67546, 0, 1565578694],\n          [-73.97142, 40.67575, 0, 1565578712],\n          [-73.9719, 40.67641, 0, 1565578732],\n          [-73.97244, 40.67716, 0, 1565578756],\n          [-73.97234, 40.67744, 0, 1565578778],\n          [-73.96917, 40.67678, 0, 1565578836]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'A',\n        value: 7\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.03806, 40.74578, 0, 1565578252],\n          [-74.03893, 40.74289, 0, 1565578322],\n          [-74.03934, 40.74158, 0, 1565578354],\n          [-74.0384, 40.74142, 0, 1565578376],\n          [-74.03746, 40.74126, 0, 1565578399],\n          [-74.03824, 40.73872, 0, 1565578465],\n          [-74.03878, 40.73693, 0, 1565578511],\n          [-74.03893, 40.73693, 0, 1565578513],\n          [-74.03974, 40.73704, 0, 1565578524],\n          [-74.03979, 40.7369, 0, 1565578528],\n          [-74.03964, 40.73677, 0, 1565578531],\n          [-74.03916, 40.73685, 0, 1565578538],\n          [-74.03881, 40.73686, 0, 1565578546],\n          [-74.03854, 40.73684, 0, 1565578553],\n          [-74.03864, 40.73655, 0, 1565578562],\n          [-74.03879, 40.73571, 0, 1565578589],\n          [-74.03943, 40.73155, 0, 1565578722],\n          [-74.03954, 40.7308, 0, 1565578746],\n          [-74.03791, 40.73066, 0, 1565578753],\n          [-74.03782, 40.73065, 0, 1565578754],\n          [-74.03789, 40.73024, 0, 1565578763],\n          [-74.03795, 40.72999, 0, 1565578769],\n          [-74.03797, 40.72988, 0, 1565578770],\n          [-74.03798, 40.7298, 0, 1565578771],\n          [-74.0368, 40.7298, 0, 1565578784],\n          [-74.03646, 40.72977, 0, 1565578788],\n          [-74.03613, 40.72971, 0, 1565578791],\n          [-74.03604, 40.72969, 0, 1565578792],\n          [-74.03222, 40.72883, 0, 1565578822]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        vendor: 'B',\n        value: 15\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.79007, 40.64681, 0, 1565577697],\n          [-73.79092, 40.64643, 0, 1565577720],\n          [-73.79108, 40.64631, 0, 1565577726],\n          [-73.7912, 40.64613, 0, 1565577732],\n          [-73.79121, 40.64597, 0, 1565577737],\n          [-73.79116, 40.64581, 0, 1565577743],\n          [-73.79106, 40.6457, 0, 1565577747],\n          [-73.79094, 40.64557, 0, 1565577749],\n          [-73.79072, 40.64542, 0, 1565577751],\n          [-73.79057, 40.64534, 0, 1565577753],\n          [-73.7904, 40.64527, 0, 1565577754],\n          [-73.79022, 40.64522, 0, 1565577755],\n          [-73.78974, 40.64522, 0, 1565577758],\n          [-73.78916, 40.64528, 0, 1565577760],\n          [-73.78889, 40.64534, 0, 1565577762],\n          [-73.78855, 40.64545, 0, 1565577764],\n          [-73.78827, 40.64558, 0, 1565577765],\n          [-73.78818, 40.6456, 0, 1565577766],\n          [-73.788, 40.64562, 0, 1565577767],\n          [-73.78793, 40.64561, 0, 1565577767],\n          [-73.7878, 40.64556, 0, 1565577768],\n          [-73.78772, 40.64545, 0, 1565577769],\n          [-73.78771, 40.64534, 0, 1565577769],\n          [-73.78775, 40.64524, 0, 1565577770],\n          [-73.78784, 40.64518, 0, 1565577771],\n          [-73.78799, 40.64513, 0, 1565577771],\n          [-73.78984, 40.64512, 0, 1565577781],\n          [-73.79041, 40.64509, 0, 1565577784],\n          [-73.79124, 40.64508, 0, 1565577788],\n          [-73.79167, 40.64506, 0, 1565577790],\n          [-73.79193, 40.64508, 0, 1565577791],\n          [-73.79313, 40.64494, 0, 1565577797],\n          [-73.79412, 40.64485, 0, 1565577802],\n          [-73.79437, 40.64481, 0, 1565577804],\n          [-73.79472, 40.64474, 0, 1565577805],\n          [-73.79569, 40.64469, 0, 1565577808],\n          [-73.79623, 40.64469, 0, 1565577810],\n          [-73.79671, 40.64472, 0, 1565577811],\n          [-73.79722, 40.64478, 0, 1565577813],\n          [-73.79768, 40.64486, 0, 1565577814],\n          [-73.79813, 40.64496, 0, 1565577816],\n          [-73.79865, 40.64511, 0, 1565577817],\n          [-73.79896, 40.64522, 0, 1565577818],\n          [-73.79953, 40.64545, 0, 1565577820],\n          [-73.80022, 40.64582, 0, 1565577823],\n          [-73.80062, 40.64606, 0, 1565577824],\n          [-73.80102, 40.64636, 0, 1565577826],\n          [-73.80178, 40.64703, 0, 1565577829],\n          [-73.80245, 40.64766, 0, 1565577832],\n          [-73.80314, 40.64837, 0, 1565577836],\n          [-73.80379, 40.64909, 0, 1565577839],\n          [-73.80479, 40.65031, 0, 1565577845],\n          [-73.80528, 40.65096, 0, 1565577848],\n          [-73.80578, 40.65166, 0, 1565577851],\n          [-73.80623, 40.65236, 0, 1565577854],\n          [-73.80658, 40.65294, 0, 1565577856],\n          [-73.80681, 40.65342, 0, 1565577858],\n          [-73.80702, 40.65396, 0, 1565577860],\n          [-73.80718, 40.65449, 0, 1565577862],\n          [-73.8073, 40.65502, 0, 1565577864],\n          [-73.80738, 40.6557, 0, 1565577867],\n          [-73.80731, 40.65585, 0, 1565577868],\n          [-73.80719, 40.65642, 0, 1565577873],\n          [-73.8071, 40.6567, 0, 1565577876],\n          [-73.807, 40.65692, 0, 1565577878],\n          [-73.80687, 40.6571, 0, 1565577880],\n          [-73.80672, 40.65725, 0, 1565577881],\n          [-73.80641, 40.65752, 0, 1565577884],\n          [-73.80622, 40.65766, 0, 1565577886],\n          [-73.80606, 40.65781, 0, 1565577887],\n          [-73.80593, 40.65799, 0, 1565577888]\n        ]\n      }\n    }\n  ]\n};\n\nexport const animateTripDataId = 'animate-trip-data';\nexport const pointDataId = 'sample_taxi_trip';\n\n// animationConfig,domain 1565577697000, 1565578881000\n// filer.domain: 1565576422000, 1565577120000\nexport const pointData = {\n  fields: [\n    {\n      name: 'tpep_pickup_datetime',\n      format: 'YYYY-M-D H:m:s',\n      type: 'timestamp'\n    },\n    {name: 'pickup_longitude', format: '', type: 'real'},\n    {name: 'pickup_latitude', format: '', type: 'real'}\n  ],\n  rows: [\n    ['2019-08-12 2:23:42', -73.99389648, 40.75011063],\n    ['2019-08-12 2:32:00', -73.97642517, 40.73981094],\n    ['2019-08-12 2:21:00', -73.96870422, 40.75424576],\n    ['2019-08-12 2:28:18', -73.86306, 40.76958084],\n    ['2019-08-12 2:20:36', -73.94554138, 40.77942276],\n    ['2019-08-12 2:20:22', -73.87445831, 40.7740097],\n    ['2019-08-12 2:31:00', -73.97660065, 40.7518959]\n  ]\n};\n\nexport const replacePointData = {\n  fields: pointData.fields,\n  rows: [\n    ['2019-08-12 2:23:18', -73.97812653, 40.75257492],\n    ['2019-08-12 2:07:26', -74.00622559, 40.73387146],\n    ['2019-08-12 2:08:55', -73.97122955, 40.75518417],\n    ['2019-08-12 2:10:30', -73.97627258, 40.75893402]\n  ]\n};\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [\n        {\n          dataId: [pointDataId],\n          id: '5tbxclf5g',\n          name: ['tpep_pickup_datetime'],\n          type: 'timeRange',\n          value: [1565576828000, 1565577141000],\n          animationWindow: 'free',\n          view: 'enlarged',\n          syncedWithLayerTimeline: true,\n          syncTimelineMode: 1,\n          enabled: true\n        }\n      ],\n      layers: [\n        {\n          id: 'point-layer',\n          type: 'point',\n          config: {\n            dataId: pointDataId,\n            label: 'pickup',\n            color: [221, 178, 124],\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            isVisible: true\n          }\n        },\n        {\n          id: 'trip-layer',\n          type: 'trip',\n          config: {\n            dataId: animateTripDataId,\n            columnMode: 'geojson',\n            label: 'Trip animation',\n            color: [18, 147, 154],\n            columns: {\n              geojson: '_geojson'\n            }\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            [animateTripDataId]: [\n              {\n                name: 'value',\n                format: null\n              }\n            ],\n            [pointDataId]: [\n              {\n                name: 'tpep_pickup_datetime',\n                format: 'L LT'\n              }\n            ]\n          },\n          compareMode: true,\n          compareType: 'relative',\n          enabled: true\n        }\n      },\n      layerBlending: 'normal',\n      overlayBlending: 'normal',\n      splitMaps: [],\n      animationConfig: {\n        currentTime: 1565577141000,\n        speed: 1\n      },\n      editor: {\n        features: [],\n        visible: true\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-geojson-config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [\n        {\n          dataId: ['bart-stops-geo-2'],\n          id: '2ua7g6t8',\n          name: ['exits'],\n          type: 'range',\n          value: [6121, 13547],\n          plotType: {\n            type: 'histogram'\n          },\n          animationWindow: 'free',\n          yAxis: null,\n          view: 'side',\n          speed: 1,\n          enabled: true\n        },\n        {\n          dataId: ['sf-zip-geo'],\n          id: 'kt1fkkbrb',\n          name: ['ZIP_CODE'],\n          type: 'range',\n          value: [94103, 94133],\n          plotType: {\n            type: 'histogram'\n          },\n          animationWindow: 'free',\n          yAxis: null,\n          view: 'side',\n          speed: 1,\n          enabled: true\n        }\n      ],\n      layers: [\n        {\n          id: 'ze2p6id',\n          type: 'geojson',\n          config: {\n            dataId: 'bart-stops-geo',\n            label: 'Bart Stops Geo',\n            color: [151, 14, 45],\n            columns: {\n              geojson: '_geojson'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              thickness: 0.5,\n              strokeColor: [77, 193, 156],\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              strokeColorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              radius: 22.5,\n              sizeRange: [0, 10],\n              radiusRange: [0, 50],\n              heightRange: [0, 500],\n              elevationScale: 5,\n              stroked: true,\n              filled: true,\n              enable3d: false,\n              wireframe: false\n            },\n            textLabel: [\n              {\n                field: null,\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            strokeColorField: null,\n            strokeColorScale: 'quantile',\n            heightField: null,\n            heightScale: 'linear',\n            radiusField: null,\n            radiusScale: 'linear'\n          }\n        },\n        {\n          id: 'ho3fgt9',\n          type: 'geojson',\n          config: {\n            dataId: 'sf-zip-geo',\n            label: 'SF Zip Geo',\n            color: [136, 87, 44],\n            columns: {\n              geojson: '_geojson'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              thickness: 0.5,\n              strokeColor: [255, 254, 213],\n              colorRange: {\n                name: 'UberPool 8',\n                type: 'diverging',\n                category: 'Uber',\n                colors: [\n                  '#213E9A',\n                  '#3C1FA7',\n                  '#811CB5',\n                  '#C318B0',\n                  '#D01367',\n                  '#DE0F0E',\n                  '#EC7007',\n                  '#F9E200'\n                ],\n                reversed: false\n              },\n              strokeColorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              radius: 10,\n              sizeRange: [0, 10],\n              radiusRange: [0, 50],\n              heightRange: [0, 500],\n              elevationScale: 5,\n              stroked: true,\n              filled: true,\n              enable3d: false,\n              wireframe: false\n            },\n            textLabel: [\n              {\n                field: null,\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: {\n              name: 'ID',\n              type: 'integer'\n            },\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            strokeColorField: null,\n            strokeColorScale: 'quantile',\n            heightField: null,\n            heightScale: 'linear',\n            radiusField: null,\n            radiusScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            'bart-stops-geo': ['name'],\n            'sf-zip-geo': ['OBJECTID', 'ZIP_CODE', 'ID', 'name', 'STREETNAME']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal'\n    },\n\n    mapStyle: {\n      styleType: 'b9tnac',\n      mapStyles: {\n        b9tnac: {\n          accessToken: null,\n          custom: true,\n          icon: 'https://api.mapbox.com/styles/v1/heshan0131/cjg0ks54x300a2squ8fr9vhvq/static/-122.3391,37.7922,9,0,0/400x300?access_token=pk.eyJ1IjoidWJlcmRhdGEiLCJhIjoiY2pmc3hhd21uMzE3azJxczJhOWc4czBpYyJ9.HiDptGv2C0Bkcv_TGr_kJw&logo=false&attribution=false',\n          id: 'b9tnac',\n          label: 'label maker',\n          url: 'mapbox://styles/heshan0131/cjg0ks54x300a2squ8fr9vhvq'\n        }\n      }\n    }\n  }\n};\n\nexport default config;\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-geojson-points.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  type: 'FeatureCollection',\n  features: [\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Lafayette (LAFY)',\n        code: 'LF',\n        address: '3601 Deer Hill Road, Lafayette CA 94549',\n        entries: '3481',\n        exits: '3616',\n        latitude: 37.893394,\n        longitude: -122.123801\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.123801, 37.893394]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: '12th St. Oakland City Center (12TH)',\n        code: '12',\n        address: '1245 Broadway, Oakland CA 94612',\n        entries: '13418',\n        exits: '13547',\n        latitude: 37.803664,\n        longitude: -122.271604\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.271604, 37.803664]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: '16th St. Mission (16TH)',\n        code: '16',\n        address: '2000 Mission Street, San Francisco CA 94110',\n        entries: '12409',\n        exits: '12351',\n        latitude: 37.765062,\n        longitude: -122.419694\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.419694, 37.765062]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: '19th St. Oakland (19TH)',\n        code: '19',\n        address: '1900 Broadway, Oakland CA 94612',\n        entries: '13108',\n        exits: '13090',\n        latitude: 37.80787,\n        longitude: -122.269029\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.269029, 37.80787]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: '24th St. Mission (24TH)',\n        code: '24',\n        address: '2800 Mission Street, San Francisco CA 94110',\n        entries: '12817',\n        exits: '12529',\n        latitude: 37.752254,\n        longitude: -122.418466\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.418466, 37.752254]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Ashby (ASHB)',\n        code: 'AS',\n        address: '3100 Adeline Street, Berkeley CA 94703',\n        entries: '5452',\n        exits: '5341',\n        latitude: 37.853024,\n        longitude: -122.26978\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.26978, 37.853024]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Balboa Park (BALB)',\n        code: 'BP',\n        address: '401 Geneva Avenue, San Francisco CA 94112',\n        entries: '11170',\n        exits: '9817',\n        latitude: 37.721981,\n        longitude: -122.447414\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.447414, 37.721981]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Bay Fair (BAYF)',\n        code: 'BF',\n        address: '15242 Hesperian Blvd., San Leandro CA 94578',\n        entries: '5564',\n        exits: '5516',\n        latitude: 37.697185,\n        longitude: -122.126871\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.126871, 37.697185]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Castro Valley (CAST)',\n        code: 'CV',\n        address: '3301 Norbridge Dr., Castro Valley CA 94546',\n        entries: '2781',\n        exits: '2735',\n        latitude: 37.690754,\n        longitude: -122.075567\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.075567, 37.690754]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Civic Center/UN Plaza (CIVC)',\n        code: 'CC',\n        address: '1150 Market Street, San Francisco CA 94102',\n        entries: '24798',\n        exits: '22626',\n        latitude: 37.779528,\n        longitude: -122.413756\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.413756, 37.779528]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Colma (COLM)',\n        code: 'CM',\n        address: '365 D Street, Colma CA 94014',\n        entries: '4397',\n        exits: '4214',\n        latitude: 37.684638,\n        longitude: -122.466233\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.466233, 37.684638]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Coliseum/Oakland Airport (COLS)',\n        code: 'CL',\n        address: '7200 San Leandro St., Oakland CA 94621',\n        entries: '5837',\n        exits: '5902',\n        latitude: 37.754006,\n        longitude: -122.197273\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.197273, 37.754006]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Concord (CONC)',\n        code: 'CN',\n        address: '1451 Oakland Avenue, Concord CA 94520',\n        entries: '6035',\n        exits: '6008',\n        latitude: 37.973737,\n        longitude: -122.029095\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.029095, 37.973737]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Daly City (DALY)',\n        code: 'DC',\n        address: '500 John Daly Blvd., Daly City CA 94014',\n        entries: '8681',\n        exits: '8502',\n        latitude: 37.706121,\n        longitude: -122.469081\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.469081, 37.706121]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Downtown Berkeley (DBRK)',\n        code: 'BK',\n        address: '2160 Shattuck Avenue, Berkeley CA 94704',\n        entries: '11043',\n        exits: '11762',\n        latitude: 37.869867,\n        longitude: -122.268045\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.268045, 37.869867]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'El Cerrito del Norte (DELN)',\n        code: 'EN',\n        address: '6400 Cutting Blvd., El Cerrito CA 94530',\n        entries: '8176',\n        exits: '8668',\n        latitude: 37.925655,\n        longitude: -122.317269\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.317269, 37.925655]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Dublin/Pleasanton (DUBL)',\n        code: 'ED',\n        address: '5801 Owens Dr., Pleasanton CA 94588',\n        entries: '7702',\n        exits: '7554',\n        latitude: 37.701695,\n        longitude: -121.900367\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-121.900367, 37.701695]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Embarcadero (EMBR)',\n        code: 'EM',\n        address: '298 Market Street, San Francisco CA 94111',\n        entries: '40376',\n        exits: '46951',\n        latitude: 37.792976,\n        longitude: -122.396742\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.396742, 37.792976]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Fremont (FRMT)',\n        code: 'FM',\n        address: '2000 BART Way, Fremont CA 94536',\n        entries: '8748',\n        exits: '8673',\n        latitude: 37.557355,\n        longitude: -121.9764\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-121.9764, 37.557355]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Fruitvale (FTVL)',\n        code: 'FV',\n        address: '3401 East 12th Street, Oakland CA 94601',\n        entries: '7701',\n        exits: '8012',\n        latitude: 37.774963,\n        longitude: -122.224274\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.224274, 37.774963]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Glen Park (GLEN)',\n        code: 'GP',\n        address: '2901 Diamond Street, San Francisco CA 94131',\n        entries: '7732',\n        exits: '7072',\n        latitude: 37.732921,\n        longitude: -122.434092\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.434092, 37.732921]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Hayward (HAYW)',\n        code: 'HY',\n        address: \"699 'B' Street, Hayward CA 94541\",\n        entries: '4958',\n        exits: '5003',\n        latitude: 37.670399,\n        longitude: -122.087967\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.087967, 37.670399]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Lake Merritt (LAKE)',\n        code: 'LM',\n        address: '800 Madison Street, Oakland CA 94607',\n        entries: '6539',\n        exits: '6604',\n        latitude: 37.797484,\n        longitude: -122.265609\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.265609, 37.797484]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'MacArthur (MCAR)',\n        code: 'MA',\n        address: '555 40th Street, Oakland CA 94609',\n        entries: '9000',\n        exits: '9228',\n        latitude: 37.828415,\n        longitude: -122.267227\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.267227, 37.828415]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Millbrae (MLBR)',\n        code: 'MB',\n        address: '200 North Rollins Road, Millbrae CA 94030',\n        entries: '6570',\n        exits: '6149',\n        latitude: 37.599787,\n        longitude: -122.38666\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.38666, 37.599787]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Montgomery St. (MONT)',\n        code: 'MT',\n        address: '598 Market Street, San Francisco CA 94104',\n        entries: '43430',\n        exits: '45128',\n        latitude: 37.789256,\n        longitude: -122.401407\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.401407, 37.789256]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'North Berkeley (NBRK)',\n        code: 'NB',\n        address: '1750 Sacramento Street, Berkeley CA 94702',\n        entries: '4363',\n        exits: '4563',\n        latitude: 37.87404,\n        longitude: -122.283451\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.283451, 37.87404]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'North Concord/Martinez (NCON)',\n        code: 'NC',\n        address: '3700 Port Chicago Highway, Concord CA 94520',\n        entries: '2800',\n        exits: '2652',\n        latitude: 38.003275,\n        longitude: -122.024597\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.024597, 38.003275]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Orinda (ORIN)',\n        code: 'OR',\n        address: '11 Camino Pablo, Orinda CA 94563',\n        entries: '2896',\n        exits: '2970',\n        latitude: 37.878361,\n        longitude: -122.183791\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.183791, 37.878361]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Pleasant Hill/Contra Costa Centre (PHIL)',\n        code: 'PH',\n        address: '1365 Treat Blvd., Walnut Creek CA 94597',\n        entries: '7574',\n        exits: '7442',\n        latitude: 37.928403,\n        longitude: -122.056013\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.056013, 37.928403]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Pittsburg/Bay Point (PITT)',\n        code: 'WP',\n        address: '1700 West Leland Road, Pittsburg CA 94565',\n        entries: '6262',\n        exits: '6343',\n        latitude: 38.018914,\n        longitude: -121.945154\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-121.945154, 38.018914]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'El Cerrito Plaza (PLZA)',\n        code: 'EP',\n        address: '6699 Fairmount Avenue, El Cerrito CA 94530',\n        entries: '4763',\n        exits: '4952',\n        latitude: 37.903059,\n        longitude: -122.299272\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.299272, 37.903059]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Powell St. (POWL)',\n        code: 'PL',\n        address: '899 Market Street, San Francisco CA 94102',\n        entries: '29460',\n        exits: '25621',\n        latitude: 37.784991,\n        longitude: -122.406857\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.406857, 37.784991]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Richmond (RICH)',\n        code: 'RM',\n        address: '1700 Nevin Avenue, Richmond CA 94801',\n        entries: '4184',\n        exits: '4029',\n        latitude: 37.936887,\n        longitude: -122.353165\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.353165, 37.936887]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Rockridge (ROCK)',\n        code: 'RR',\n        address: '5660 College Avenue, Oakland CA 94618',\n        entries: '5299',\n        exits: '5775',\n        latitude: 37.844601,\n        longitude: -122.251793\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.251793, 37.844601]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'San Leandro (SANL)',\n        code: 'SL',\n        address: '1401 San Leandro Blvd., San Leandro CA 94577',\n        entries: '5836',\n        exits: '5921',\n        latitude: 37.722619,\n        longitude: -122.161311\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.161311, 37.722619]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'San Bruno (SBRN)',\n        code: 'SB',\n        address: '1151 Huntington Avenue, San Bruno CA 94066',\n        entries: '3628',\n        exits: '3634',\n        latitude: 37.637753,\n        longitude: -122.416038\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.416038, 37.637753]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: \"San Francisco Int'l Airport (SFIA)\",\n        code: 'SO',\n        address: \"International Terminal, Level 3, San Francisco Int'l Airport CA 94128\",\n        entries: '5833',\n        exits: '4904',\n        latitude: 37.616035,\n        longitude: -122.392612\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.392612, 37.616035]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'South Hayward (SHAY)',\n        code: 'SH',\n        address: '28601 Dixon Street, Hayward CA 94544',\n        entries: '3007',\n        exits: '2829',\n        latitude: 37.6348,\n        longitude: -122.057551\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.057551, 37.6348]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'South San Francisco (SSAN)',\n        code: 'SS',\n        address: '1333 Mission Road, South San Francisco CA 94080',\n        entries: '3542',\n        exits: '3441',\n        latitude: 37.664174,\n        longitude: -122.444116\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.444116, 37.664174]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Union City (UCTY)',\n        code: 'UC',\n        address: '10 Union Square, Union City CA 94587',\n        entries: '4772',\n        exits: '4770',\n        latitude: 37.591208,\n        longitude: -122.017867\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.017867, 37.591208]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'Walnut Creek (WCRK)',\n        code: 'WC',\n        address: '200 Ygnacio Valley Road, Walnut Creek CA 94596',\n        entries: '6719',\n        exits: '6917',\n        latitude: 37.905628,\n        longitude: -122.067423\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.067423, 37.905628]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'West Dublin/Pleasanton (WDUB)',\n        code: 'WD',\n        address: '6501 Golden Gate Drive, Dublin CA 94568',\n        entries: '3303',\n        exits: '3447',\n        latitude: 37.699759,\n        longitude: -121.928099\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-121.928099, 37.699759]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        name: 'West Oakland (WOAK)',\n        code: 'OW',\n        address: '1451 7th Street, Oakland CA 94607',\n        entries: '7312',\n        exits: '6838',\n        latitude: 37.804675,\n        longitude: -122.294582\n      },\n      geometry: {\n        type: 'Point',\n        coordinates: [-122.294582, 37.804675]\n      }\n    }\n  ]\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-gps-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst gps = `timestamp,location-lng,location-lat,ground-speed,heading,id,location-alt\n2014-08-01 00:00:23.000,90.2266981,27.6162803,0.22,0.0,Thang Kaar Thuub (3552),3217.0\n2014-08-01 00:10:07.000,90.2266713,27.6162513,0.27,140.9,Thang Kaar Thuub (3552),3212.3\n2014-08-01 00:20:07.000,90.2267115,27.6162102,0.27,110.1,Thang Kaar Thuub (3552),3209.1\n2014-08-01 00:30:07.000,90.2267409,27.6162514,0.32,0.0,Thang Kaar Thuub (3552),3206.2\n2014-08-01 00:40:07.000,90.2265545,27.6163481,0.46,0.0,Thang Kaar Thuub (3552),3231.3\n2014-08-01 00:50:07.000,90.2269491,27.6161884,0.51,0.0,Thang Kaar Thuub (3552),3178.8\n2014-08-01 01:00:07.000,90.2265336,27.6163122,0.3,99.61,Thang Kaar Thuub (3552),3257.2\n2014-08-01 01:10:07.000,90.2267568,27.6162647,0.33,0.0,Thang Kaar Thuub (3552),3192.5\n2014-08-01 01:20:07.000,90.2267296,27.6163003,0.43,0.0,Thang Kaar Thuub (3552),3199.2\n2014-08-01 01:30:16.000,90.2267121,27.6163019,0.09,172.36,Thang Kaar Thuub (3552),3222.9\n2014-08-01 01:40:21.000,90.2267198,27.6163409,0.07,0.0,Thang Kaar Thuub (3552),3216.7\n2014-08-01 01:50:06.000,90.2266872,27.6162974,0.26,105.51,Thang Kaar Thuub (3552),3215.5\n2014-08-01 02:00:06.000,90.2266652,27.6162898,0.5,157.94,Thang Kaar Thuub (3552),3219.6\n2014-08-01 02:10:07.000,90.2266452,27.6163351,0.18,160.56,Thang Kaar Thuub (3552),3220.4\n2014-08-01 02:20:07.000,90.2266829,27.6162774,0.22,169.74,Thang Kaar Thuub (3552),3216.9\n2014-08-01 02:30:07.000,90.2266533,27.6162986,0.25,145.49,Thang Kaar Thuub (3552),3224.0\n2014-08-01 02:40:07.000,90.2267379,27.6162983,0.21,195.95,Thang Kaar Thuub (3552),3225.6\n2014-08-01 02:50:07.000,90.226692,27.616304,0.19,0.0,Thang Kaar Thuub (3552),3211.8\n2014-08-01 03:00:07.000,90.2266683,27.6163021,0.28,0.0,Thang Kaar Thuub (3552),3227.1\n2014-08-01 03:10:06.000,90.2266718,27.6162954,0.24,138.94,Thang Kaar Thuub (3552),3225.1\n2014-08-01 03:20:07.000,90.2267475,27.6162578,0.06,0.0,Thang Kaar Thuub (3552),3218.9\n2014-08-01 03:30:07.000,90.2266495,27.6163363,0.05,0.0,Thang Kaar Thuub (3552),3209.9\n2014-08-01 03:40:07.000,90.2266573,27.6162552,0.33,278.53,Thang Kaar Thuub (3552),3216.8\n2014-08-01 03:50:06.000,90.200501,27.5963135,11.97,248.38,Thang Kaar Thuub (3552),3150.4\n2014-08-01 04:00:23.000,90.2029316,27.592159,11.09,102.89,Thang Kaar Thuub (3552),3173.1\n2014-08-01 04:10:06.000,90.1979269,27.5900334,14.89,226.1,Thang Kaar Thuub (3552),3078.8\n2014-08-01 04:20:06.000,90.1599981,27.5889937,9.36,214.3,Thang Kaar Thuub (3552),3219.6\n2014-08-01 04:30:06.000,90.1304977,27.5797384,17.17,256.9,Thang Kaar Thuub (3552),3066.5\n2014-08-01 04:40:06.000,90.0803941,27.5599255,10.13,278.53,Thang Kaar Thuub (3552),3100.0\n2014-08-01 04:50:06.000,90.0107878,27.5658968,11.39,235.93,Thang Kaar Thuub (3552),2666.8\n2014-08-01 05:00:06.000,89.9490298,27.5345735,9.28,214.3,Thang Kaar Thuub (3552),2309.5\n2014-08-01 06:42:41.000,82.1138274,27.4378974,15.95,64.23,Thang Kaar Ngang Ka (4002),966.3\n2014-08-01 06:42:42.000,82.1139649,27.4379582,14.9,62.91,Thang Kaar Ngang Ka (4002),969.9\n2014-08-01 06:42:43.000,82.1140881,27.4380211,13.67,58.33,Thang Kaar Ngang Ka (4002),972.8\n2014-08-01 06:42:44.000,82.1141907,27.4380927,12.54,48.5,Thang Kaar Ngang Ka (4002),974.6\n2014-08-01 06:42:45.000,82.114263,27.4381765,11.44,32.11,Thang Kaar Ngang Ka (4002),975.2\n2014-08-01 06:42:46.000,82.1143013,27.438272,11.35,13.76,Thang Kaar Ngang Ka (4002),975.8\n2014-08-01 06:42:47.000,82.11431,27.4383692,10.8,0.66,Thang Kaar Ngang Ka (4002),977.7\n2014-08-01 06:42:48.000,82.1142962,27.4384518,8.76,346.69,Thang Kaar Ngang Ka (4002),980.6\n2014-08-01 06:42:49.000,82.1142637,27.4385091,6.63,325.71,Thang Kaar Ngang Ka (4002),983.6\n2014-08-01 06:42:50.000,82.1142176,27.4385368,5.34,292.95,Thang Kaar Ngang Ka (4002),985.9\n2014-08-01 06:42:51.000,82.1141648,27.4385357,5.47,258.87,Thang Kaar Ngang Ka (4002),987.5\n2014-08-01 06:42:52.000,82.1141123,27.4385072,6.58,232.0,Thang Kaar Ngang Ka (4002),988.8\n2014-08-01 06:42:53.000,82.1140662,27.4384524,8.17,211.68,Thang Kaar Ngang Ka (4002),990.3\n2014-08-01 06:42:54.000,82.1140376,27.4383744,9.62,192.68,Thang Kaar Ngang Ka (4002),992.1\n2014-08-01 06:42:55.000,82.114036,27.4382797,10.99,176.29,Thang Kaar Ngang Ka (4002),994.3\n2014-08-01 06:42:56.000,82.1140666,27.438175,12.55,161.22,Thang Kaar Ngang Ka (4002),996.5\n2014-08-01 06:42:57.000,82.1141258,27.4380679,13.64,150.73,Thang Kaar Ngang Ka (4002),998.2\n2014-08-01 06:42:58.000,82.1142042,27.4379617,14.34,144.83,Thang Kaar Ngang Ka (4002),999.3\n2014-08-01 06:42:59.000,82.1142915,27.4378567,14.58,142.21,Thang Kaar Ngang Ka (4002),1000.9\n2014-08-01 06:43:00.000,82.1143788,27.4377539,14.18,142.21,Thang Kaar Ngang Ka (4002),1002.5\n2014-08-01 06:43:01.000,82.1144592,27.4376522,13.65,145.49,Thang Kaar Ngang Ka (4002),1004.0\n2014-08-01 06:43:02.000,82.1145274,27.4375495,13.09,150.73,Thang Kaar Ngang Ka (4002),1004.7\n2014-08-01 06:43:03.000,82.1145875,27.4374392,13.84,154.66,Thang Kaar Ngang Ka (4002),1005.1\n2014-08-01 06:43:04.000,82.1146421,27.4373208,14.33,157.94,Thang Kaar Ngang Ka (4002),1006.1\n2014-08-01 06:43:05.000,82.1146882,27.4372001,14.03,161.87,Thang Kaar Ngang Ka (4002),1007.5\n2014-08-01 06:43:06.000,82.1147188,27.4370782,13.75,169.08,Thang Kaar Ngang Ka (4002),1009.8\n2014-08-01 06:43:07.000,82.1147296,27.4369611,12.75,177.6,Thang Kaar Ngang Ka (4002),1012.1\n2014-08-01 06:43:08.000,82.1147213,27.4368536,11.67,186.12,Thang Kaar Ngang Ka (4002),1014.2\n2014-08-01 06:43:09.000,82.1146955,27.4367568,10.77,195.95,Thang Kaar Ngang Ka (4002),1017.1\n2014-08-01 06:43:10.000,82.1146547,27.436676,9.5,207.75,Thang Kaar Ngang Ka (4002),1020.1\n2014-08-01 06:43:11.000,82.1145999,27.4366146,8.5,222.82,Thang Kaar Ngang Ka (4002),1023.3\n2014-08-01 06:43:12.000,82.1145381,27.4365653,8.12,230.03,Thang Kaar Ngang Ka (4002),1026.2\n2014-08-01 06:43:13.000,82.1144759,27.4365206,7.87,230.69,Thang Kaar Ngang Ka (4002),1028.2\n2014-08-01 06:43:14.000,82.1144131,27.4364746,8.14,230.03,Thang Kaar Ngang Ka (4002),1029.8\n2014-08-01 06:43:15.000,82.1143506,27.43643,7.86,231.34,Thang Kaar Ngang Ka (4002),1030.8\n2014-08-01 06:43:16.000,82.1142864,27.4363918,7.6,237.9,Thang Kaar Ngang Ka (4002),1031.0\n2014-08-01 06:43:17.000,82.1142171,27.4363629,7.58,247.73,Thang Kaar Ngang Ka (4002),1031.9\n2014-08-01 06:43:18.000,82.114145,27.4363437,7.43,255.59,Thang Kaar Ngang Ka (4002),1033.1\n2014-08-01 06:43:19.000,82.1140733,27.4363307,7.17,259.52,Thang Kaar Ngang Ka (4002),1034.5\n2014-08-01 06:43:20.000,82.114004,27.4363217,6.85,262.14,Thang Kaar Ngang Ka (4002),1035.6\n2014-08-01 06:43:21.000,82.1139337,27.4363205,7.05,271.32,Thang Kaar Ngang Ka (4002),1036.6\n2014-08-01 06:43:22.000,82.1138619,27.4363266,7.2,276.56,Thang Kaar Ngang Ka (4002),1037.3\n2014-08-01 06:43:23.000,82.1137874,27.4363359,7.56,277.87,Thang Kaar Ngang Ka (4002),1038.6\n2014-08-01 06:43:24.000,82.1137119,27.4363441,7.51,275.91,Thang Kaar Ngang Ka (4002),1040.2\n2014-08-01 06:43:25.000,82.1136379,27.4363491,7.29,273.29,Thang Kaar Ngang Ka (4002),1041.4\n2014-08-01 06:43:26.000,82.1135644,27.4363482,7.29,267.39,Thang Kaar Ngang Ka (4002),1043.2`;\n\nexport default gps;\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-hex-id-csv.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `hex_id,value\n89283082c2fffff,64\n8928308288fffff,73\n89283082c07ffff,65\n89283082817ffff,74\n89283082c3bffff,66\n89283082883ffff,76\n89283082c03ffff,60\n89283082807ffff,68\n8928308289bffff,49\n89283082c0fffff,41\n89283082c87ffff,50\n89283082d4fffff,45\n89283082c77ffff,41\n89283082c2bffff,53\n89283082803ffff,41\n89283082813ffff,43\n89283082d5bffff,45\n89283082897ffff,40\n89283082c67ffff,42\n89283082d47ffff,51\n89283082dc3ffff,52\n89283082c33ffff,43\n89283082c23ffff,40\n89283082887ffff,36\n89283082d4bffff,36\n892830828bbffff,48\n892830828b7ffff,28\n89283082c17ffff,34\n89283082c6fffff,21\n8928308288bffff,25\n892830828abffff,26\n89283082c27ffff,27\n89283082c8fffff,33\n89283082cafffff,29\n89283082c13ffff,27\n89283082cabffff,22\n89283082c63ffff,26\n89283082d43ffff,30\n89283082d53ffff,19\n892830828a3ffff,28\n89283082d1bffff,20\n89283095367ffff,17\n8928309536bffff,26\n89283082c37ffff,16\n89283082c73ffff,17\n89283082c8bffff,15\n89283082ca7ffff,27\n89283082cb3ffff,32\n89283082c0bffff,26\n89283082ca3ffff,19\n89283082dcfffff,18\n89283082c1bffff,20\n89283082ddbffff,18\n8928309534fffff,16\n89283082d03ffff,15\n89283082cbbffff,21\n89283082cd7ffff,9\n8928309534bffff,9\n892830828c7ffff,13\n89283082cc7ffff,12\n89283082d0bffff,19\n89283082dcbffff,19\n89283082dd3ffff,15\n89283082dd7ffff,15\n892830828d7ffff,13\n89283082d17ffff,5\n8928309536fffff,8\n89283095373ffff,6\n89283082cb7ffff,15\n89283082d83ffff,9\n89283082d07ffff,4\n89283082d0fffff,3\n89283082d13ffff,6\n89283082d9bffff,5\n89283082c83ffff,11\n89283082d8bffff,4\n89283082dc7ffff,5\n89283095377ffff,5\n89283082c97ffff,4\n89283082d7bffff,2\n89283082d8fffff,1\n89283095347ffff,3\n89283095363ffff,2\n8928309537bffff,4\n89283082d93ffff,6\n89283082d73ffff,1\n8928309530bffff,1\n8928309532bffff,1`;\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: 'avlgol',\n          type: 'hexagonId',\n          config: {\n            dataId: 'h3-hex-id',\n            label: 'H3 Hexagon',\n            color: [241, 92, 23],\n            columns: {\n              hex_id: 'hex_id'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              coverage: 1,\n              sizeRange: [0, 500],\n              coverageRange: [0, 1],\n              elevationScale: 5\n            },\n            textLabel: [\n              {\n                field: null,\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: {\n              name: 'value',\n              type: 'integer'\n            },\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            coverageField: null,\n            coverageScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            'h3-hex-id': ['hex_id', 'value']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal',\n      splitMaps: [\n        {\n          layers: {\n            avlgol: {\n              isAvailable: true,\n              isVisible: true\n            }\n          }\n        },\n        {\n          layers: {\n            avlgol: {\n              isAvailable: true,\n              isVisible: true\n            }\n          }\n        }\n      ]\n    },\n    mapStyle: {\n      styleType: 'dark',\n      topLayerGroups: {},\n      visibleLayerGroups: {\n        label: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true,\n        '3d building': false\n      },\n      mapStyles: {}\n    },\n    mapState: {\n      isSplit: true\n    }\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-icon-csv.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `time,event_lat,event_lng,icon,icon-bk,annotation-severity,annotation-html\n2016-06-28 20:02:06,37.778564,-122.39096,accel,accel,3,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:09:18,37.78824,-122.40894,add-person,add-person,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:16,38.281445,-122.29453,alert,alert,6,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:05:55,37.79354,-122.40121,android,android,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:03:39,37.456535,-122.136795,apple,apple,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:51,37.40066,-122.10239,attach,attach,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:08:20,37.77118,-122.42459,bold,bold,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:05:16,37.879066,-122.26108,,bookmark,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:03:01,37.775578,-122.39363,,brake,3,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:03:22,37.7892,-122.408966,,briefcase,2,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:04:42,37.779964,-122.40398,bug,bug,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:03:55,37.792385,-122.406494,,calculator,2,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:08:31,37.791046,-122.401855,calendar,calendar,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:00:18,37.769897,-122.41168,,camera,2,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:02:13,37.798237,-122.41889,,cancel,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:23,37.76018,-122.41097,,car-black,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:05:16,37.37006,-121.96353,car-suv,car-suv,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:06:28,37.418655,-122.149734,car-taxi,car-taxi,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:41,37.787052,-122.41089,,car-uberx,,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:20,37.780136,-122.40495,,car,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:57,37.753033,-122.42929,,cart,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:01:29,37.749577,-122.41829,,certified,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:10:00,37.786648,-122.401634,,chart-area,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:02:57,38.122818,-122.25759,,chart,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:07:48,37.788277,-122.40152,check-alt,check-alt,1,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:01:43,37.876453,-122.270096,,check,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:28,37.61695,-122.38396,circle-check,circle-check,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:07,37.80718,-122.40902,,circle-ellipsis,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:05:26,37.751713,-122.42713,circle,circle,3,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:08:57,37.75977,-122.41924,,clipboard,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:08:14,37.80641,-122.404816,,clock,1,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:44,37.751354,-122.43317,,cloud,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:02:46,37.40591,-121.943054,,code-alt,1,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:07:07,37.74202,-122.49926,,code,1,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:39,37.80315,-122.402794,,control-off,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:01,37.44642,-122.16117,,control-on,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:25,37.796837,-122.40023,,crosshairs-alt,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:08:14,38.2984,-122.28682,,crosshairs,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:08:49,37.73762,-122.48069,,crown,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:21,37.444183,-122.170074,,dangerous,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:49,37.77029,-122.44518,,dashboard,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:03:43,37.781548,-122.41132,delete-alt,delete-alt,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:47,37.770683,-122.403145,delete-thin,delete-thin,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:15,37.780903,-122.40269,,delete,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:05:04,37.368206,-121.92748,,details,2,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:02:47,37.890514,-122.057884,,directions,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:07,37.765217,-122.40161,,down-arrow-alt,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:07:58,37.759308,-122.40985,,down-arrow-thin,3,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:07:23,37.96648,-121.7171,,down-arrow,2,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:07:41,37.763405,-122.45817,,download,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:01:55,37.777637,-122.42391,,draw-shape,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:19,37.789856,-122.39953,,duplicate,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:08:29,37.557186,-121.9803,,edit,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:06:47,37.78949,-122.45104,,employees,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:11,37.795612,-122.394196,,enclosed-up-arrow,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:03:58,38.30026,-122.30107,,eta,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:02:03,37.78702,-122.433464,,events,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:06:50,37.800163,-122.46132,,eye-closed,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:22,37.800583,-122.44561,,eye,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:02,37.29343,-121.873825,,facebook,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:19,37.792248,-122.400986,,fare-split,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:02:50,37.762466,-122.46835,,files,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:41,37.913967,-122.04143,,film,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:02:38,37.70338,-122.474976,,filter,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:50,37.698505,-121.930046,,free-rides,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:42,37.785946,-122.39721,,fullscreen-exit,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:09,37.80069,-122.437805,,fullscreen,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:49,37.79061,-122.420425,,geofence,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:34,37.788998,-122.40528,,github,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:07:35,37.745995,-122.17901,,globe,1,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:01:57,37.368294,-121.927605,,google+,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:02:35,37.39618,-121.87303,,graph,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:05:54,37.370895,-121.99693,,heart-empty,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:00:23,37.868176,-122.291626,,heart,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:25,37.78467,-122.46707,,help,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:47,37.39412,-122.080154,,hipchat,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:09:58,37.42982,-122.146904,,home,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:32,37.720642,-122.476135,,icons,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:00:05,37.77196,-122.288506,,id-card,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:07,37.801647,-122.41509,,info,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:53,37.557343,-121.97734,,instagram,2,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:09:37,37.79315,-122.394844,,italic,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:50,37.763123,-122.408714,,key,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:16,37.80429,-122.41334,,layers,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:53,37.737015,-122.21498,,layout,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:01:35,37.688007,-122.13062,,left-arrow-alt,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:06:56,36.60167,-121.89041,,left-arrow-thin,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:33,37.77245,-122.5089,,left-arrow,3,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:32,37.787792,-122.39695,,link,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:08:15,37.789505,-122.39494,,linkedin,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:34,37.789154,-122.43993,,location,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:32,37.38603,-121.972755,,lock,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:08:30,37.776512,-122.42585,,lost,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:31,37.764816,-122.413124,,mail-open,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:24,37.80028,-122.40916,,mail-sent,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:01:29,37.927547,-122.05777,,mail,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:58,37.37899,-122.11877,,menu,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:02:13,37.78388,-122.40356,,message-phone,1,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:08,37.41093,-121.94651,,messages,,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:38,37.776924,-122.422935,,minus-alt,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:08:29,37.80069,-122.40894,,minus,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:06:47,37.79061,-122.29453,,moon,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:11,37.788998,-122.40121,,note,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:03:58,37.745995,-122.136795,,notification,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:02:03,37.368294,-122.10239,,notifications-mute,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:06:50,37.39618,-122.42459,,pause,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:22,37.370895,-122.26108,,payment,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:02,37.868176,-122.39363,,phone-alt,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:19,37.78467,-122.408966,,phone,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:02:50,37.39412,-122.40398,,picture,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:41,37.42982,-122.406494,,pin,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:02:38,37.720642,-122.401855,,place,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:50,37.77196,-122.41168,,play,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:42,37.801647,-122.41889,,plus-alt,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:09:09,37.557343,-122.41097,,plus,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:49,37.79315,-121.96353,,power,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:34,37.763123,-122.149734,,previous,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:07:35,37.80429,-122.41089,,printer,1,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:01:57,37.737015,-122.40495,,profile,5,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:02:35,37.688007,-122.42929,,promo-alt,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:05:54,36.60167,-122.41829,,promo,4,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:00:23,37.77245,-122.401634,,receipt,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:25,37.787792,-122.25759,,reset,3,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:08:47,37.789505,-122.40152,,right-arrow-alt,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:09:58,37.789154,-122.270096,,right-arrow-thin,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:32,37.38603,-122.38396,,right-arrow,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:00:05,37.776512,-122.40902,,route-dot,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:07,37.764816,-122.42713,,search,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:53,37.80028,-122.41924,,send,2,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:09:37,37.927547,-122.404816,,settings-alt,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:50,37.37899,-122.43317,,settings,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:16,37.78388,-121.943054,,share-alt,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:53,37.41093,-122.49926,,share,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:01:35,37.776924,-122.402794,,sketch,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:06:56,37.80069,-122.16117,,skip,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:33,37.79061,-122.40023,,slanted-down-arrow,3,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:32,37.788998,-122.28682,,slanted-up-arrow,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:08:15,37.745995,-122.48069,,sound-off,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:34,37.368294,-122.170074,,sound-on,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:00:32,37.39618,-122.44518,,star,5,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:08:30,37.370895,-122.41132,,steering-wheel,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:05:31,37.868176,-122.403145,,stopwatch,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:03:24,37.78467,-122.40269,,sun,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:01:29,37.39412,-121.92748,,support,,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:04:58,37.42982,-122.057884,,surge,,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:02:13,37.720642,-122.40161,,swerve,1,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:07:08,37.77196,-122.40985,,thumbs-up,,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:04:38,37.801647,-121.7171,,trash,1,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:06:53,37.557343,-122.45817,,trip-history,5,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:01:35,37.79315,-122.42391,,trips,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:06:56,37.763123,-122.39953,,trophy,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:00:33,37.80429,-121.9803,,twitter,2,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:01:32,37.737015,-122.45104,,uber-u,3,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:08:15,37.688007,-122.394196,,uber,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:07:34,36.60167,-122.30107,,underline,4,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:00:32,37.77245,-122.433464,,unlock,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:08:30,37.787792,-122.46132,,unstar,,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:05:31,37.789505,-122.44561,,up-arrow-alt,2,\"Aborted lane change, 10:34pm\"\n2016-06-28 20:03:24,37.789154,-121.873825,,up-arrow-thin,3,Period of dangerous driving 10:32pm-10:34pm\n2016-06-28 20:01:29,37.38603,-122.400986,,up-arrow,5,\"Braking at 10:32pm, -0.3g\"\n2016-06-28 20:04:58,37.776512,-122.46835,,upload,4,\"10:33pm,37.3490528616667,-121.972682895794,accel,3,\"\"Acceleration at 10:33pm, +0.2g\"\"\"\n2016-06-28 20:02:13,37.764816,-122.04143,,wrench,2,\"Aborted lane change, 10:34pm\"`;\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: 'xbbp4of',\n          type: 'hexagon',\n          config: {\n            dataId: 'test_icon_data',\n            label: 'new layer',\n            color: [221, 178, 124],\n            columns: {\n              lat: 'event_lat',\n              lng: 'event_lng'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              worldUnitSize: 0.5,\n              resolution: 8,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              coverage: 1,\n              sizeRange: [0, 500],\n              percentile: [0, 100],\n              elevationPercentile: [0, 100],\n              elevationScale: 5,\n              'hi-precision': false,\n              colorAggregation: 'average',\n              sizeAggregation: 'average',\n              enable3d: true\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: 'annotation-severity',\n              type: 'integer'\n            },\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            test_icon_data: ['time', 'icon', 'icon-bk', 'annotation-severity', 'annotation-html'],\n            '6b69fg6ca': ['OBJECTID', 'ZIP_CODE', 'ID']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal',\n      splitMaps: []\n    },\n    mapState: {\n      bearing: 24,\n      dragRotate: true,\n      latitude: 37.77189215118738,\n      longitude: -122.42436896812978,\n      pitch: 50,\n      zoom: 12.132280694715416,\n      isSplit: false\n    },\n    mapStyle: {\n      styleType: 'dark',\n      topLayerGroups: {\n        label: true\n      },\n      visibleLayerGroups: {\n        label: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-row-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst data = [\n  {\n    venue_id: '7c69e9',\n    count: 10,\n    latitude: 41.852015,\n    longitude: -87.616047,\n    neighbors: [1, 2],\n    'source hex_id': '8a2664c1b74ffff',\n    'target latitude': 41.946969,\n    'target longitude': -87.693607,\n    'target hex_id': '8a2664ca51b7fff'\n  },\n  {\n    venue_id: 'fe26e3',\n    count: 22,\n    latitude: 41.946969,\n    longitude: -87.693607,\n    neighbors: [2, 3],\n    'source hex_id': '8a2664ca51b7fff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: '5f3ec9',\n    count: 5,\n    latitude: 41.9228189446294,\n    longitude: -87.7459144592286,\n    neighbors: [3, 4],\n    'source hex_id': '8a2664ca22dffff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: 'fe625f',\n    count: 11,\n    latitude: 41.9458364,\n    longitude: -87.7474061,\n    neighbors: [4, 5, 6],\n    'source hex_id': '8a2664ca66c7fff',\n    'target latitude': 41.990071,\n    'target longitude': -87.710537,\n    'target hex_id': '8a2664d8ad97fff'\n  },\n  {\n    venue_id: '7b1fe3',\n    count: 14,\n    latitude: 41.879519,\n    longitude: -87.6335512,\n    neighbors: [5, 6],\n    'source hex_id': '8a2664c1a89ffff',\n    'target latitude': 41.8801791917474,\n    'target longitude': -87.7507109194994,\n    'target hex_id': '8a2664c81567fff'\n  },\n  {\n    venue_id: 'd16c1b',\n    count: 8,\n    latitude: 41.7552452681605,\n    longitude: -87.6322426600563,\n    neighbors: [6, 7],\n    'source hex_id': '8a2664cc3757fff',\n    'target latitude': 41.9760432,\n    'target longitude': -87.7082406,\n    'target hex_id': '8a2664d8a71ffff'\n  },\n  {\n    venue_id: '5f3be3',\n    count: 6,\n    latitude: 41.990071,\n    longitude: -87.710537,\n    neighbors: [7, 8],\n    'source hex_id': '8a2664d8ad97fff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: 'eeea20',\n    count: 5,\n    latitude: 41.8801791917474,\n    longitude: -87.7507109194994,\n    neighbors: [8, 9],\n    'source hex_id': '8a2664c81567fff',\n    'target latitude': 41.7753234651629,\n    'target longitude': -87.6832077690278,\n    'target hex_id': '8a2664cd1197fff'\n  }\n];\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: 'iulge5',\n          type: 'point',\n          config: {\n            dataId: 'sample_visit_data',\n            label: 'point',\n            columns: {\n              lat: 'latitude',\n              lng: 'longitude',\n              neighbors: 'neighbors'\n            },\n            isVisible: true,\n            visConfig: {\n              radius: 20,\n              opacity: 1,\n              allowHover: true,\n              showNeighborOnHover: true,\n              showHighlightColor: false\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: 'count',\n              type: 'integer'\n            },\n            colorScale: 'quantile'\n          }\n        }\n      ]\n    }\n  }\n};\nexport default data;\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-s2-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `s2, value\n80858004,0.5979242952642347\n8085800c,0.5446256069712141\n80858014,0.1187171597109975\n8085801c,0.2859146314037557\n80858024,0.19549012367504126\n80858034,0.3373452974230604\n8085803c,0.9218176408795662\n80858044,0.23470692356446143\n8085804c,0.1580509670379684\n80858054,0.15992745628743954\n`;\n\nexport const dataId = 's2-data';\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          type: 's2',\n          config: {\n            dataId,\n            label: 'S2 Layer',\n            color: [241, 92, 23],\n            columns: {\n              token: 's2'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              strokeColor: [253, 230, 230],\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              }\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: ' value',\n              type: 'real'\n            },\n            colorScale: 'quantile'\n          }\n        }\n      ]\n    }\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-small-geojson.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  type: 'FeatureCollection',\n  features: [\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 1, ZIP_CODE: 94107, ID: 94107},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.401159718585049, 37.782024266952142],\n            [-122.400374366843309, 37.782644515545172],\n            [-122.400019020063766, 37.782925153640136],\n            [-122.399891477967842, 37.783025880124256],\n            [-122.398930331092998, 37.783784933304034],\n            [-122.397811613142864, 37.784666586003652],\n            [-122.396705177550587, 37.785542130425938],\n            [-122.395895701657864, 37.784896929203114],\n            [-122.395160622349934, 37.78431101230386],\n            [-122.394398389309941, 37.783701667981575],\n            [-122.39343711789931, 37.784459123881106],\n            [-122.39286959393705, 37.78478280203197],\n            [-122.39285700396303, 37.784773072758867],\n            [-122.392216553530702, 37.784278123341721],\n            [-122.392215026316663, 37.784279352365694],\n            [-122.392118766536498, 37.784205306785267],\n            [-122.392032919786118, 37.784168354830349],\n            [-122.391600764141245, 37.783823989838545],\n            [-122.391599105853345, 37.783822668268179],\n            [-122.390856721023212, 37.784410569188324],\n            [-122.389846392025618, 37.783609176617361],\n            [-122.389843136047602, 37.783611740842574],\n            [-122.388306939892615, 37.782385221520776],\n            [-122.388304301898813, 37.782356717503951],\n            [-122.388231340039766, 37.782146697467269],\n            [-122.388106738902522, 37.781931906493995],\n            [-122.388094863959623, 37.781887241816527],\n            [-122.387765464889313, 37.78194244138043],\n            [-122.387754811744713, 37.781795652877442],\n            [-122.385560654233757, 37.781935833609538],\n            [-122.385544999019174, 37.781932650370386],\n            [-122.38553439339303, 37.781923892586839],\n            [-122.385511671392734, 37.781778670688965],\n            [-122.384922372713746, 37.781810068903333],\n            [-122.384901998602373, 37.781825502544642],\n            [-122.384858074907243, 37.781799422577222],\n            [-122.384893494062709, 37.781763146995921],\n            [-122.38491634743859, 37.781777202531266],\n            [-122.38550727073536, 37.781741658296774],\n            [-122.385491676395333, 37.781604563077408],\n            [-122.385493075388183, 37.781591492690396],\n            [-122.38550321006835, 37.781581716485675],\n            [-122.385518655734231, 37.781576662678262],\n            [-122.38762837564704, 37.781450881969413],\n            [-122.387583140105036, 37.780964719298666],\n            [-122.387467577483136, 37.780979617487112],\n            [-122.387479366190874, 37.781103038700273],\n            [-122.387462086323822, 37.781104002067416],\n            [-122.387450297298699, 37.780980580858106],\n            [-122.387337984465276, 37.780987185935416],\n            [-122.387348026094102, 37.781109948402708],\n            [-122.387330746223768, 37.781110911750616],\n            [-122.387320704623349, 37.780988149281868],\n            [-122.387206644553231, 37.780994095205941],\n            [-122.387216703416328, 37.781117544403045],\n            [-122.387199423542512, 37.781118507731705],\n            [-122.387189364714985, 37.780995058807726],\n            [-122.387068420460224, 37.78100248851576],\n            [-122.387080191758074, 37.781125223313062],\n            [-122.387062911534741, 37.78112618662724],\n            [-122.387051140611234, 37.78100345182272],\n            [-122.386937097943829, 37.781010084204837],\n            [-122.386948869025886, 37.781132819015049],\n            [-122.386931588799087, 37.78113378231],\n            [-122.386919818091386, 37.781011047492527],\n            [-122.386812677010425, 37.7810168822962],\n            [-122.386824465334769, 37.781140303837503],\n            [-122.386807185104601, 37.781141267114201],\n            [-122.386795397161777, 37.781017845840225],\n            [-122.386676165495402, 37.781024560738594],\n            [-122.386687953248156, 37.781147982298883],\n            [-122.386670672668515, 37.781148945561135],\n            [-122.386658885297223, 37.781025524268173],\n            [-122.386543112789369, 37.781032183675542],\n            [-122.386554900668202, 37.781155605243434],\n            [-122.386537620431014, 37.781156568480647],\n            [-122.386525832933643, 37.781033147180089],\n            [-122.386411790166775, 37.781039778779515],\n            [-122.386423577828609, 37.781163200360375],\n            [-122.386406297587911, 37.781164163578339],\n            [-122.386394510307582, 37.7810407422648],\n            [-122.386282162758775, 37.781045973167615],\n            [-122.386293984730017, 37.781170767655468],\n            [-122.386276704139945, 37.78117173085996],\n            [-122.386264917419695, 37.781048309528096],\n            [-122.386147415421732, 37.781054996216575],\n            [-122.386159201954754, 37.781178417834589],\n            [-122.386141922053085, 37.781179381008279],\n            [-122.386130135209612, 37.78105595966862],\n            [-122.386019552249252, 37.781062535536719],\n            [-122.386031321484509, 37.781185270717479],\n            [-122.386014040887659, 37.781186233883489],\n            [-122.386002272033735, 37.781063498970049],\n            [-122.385879580345062, 37.78107026841095],\n            [-122.385891367127385, 37.781193690044354],\n            [-122.385874086872718, 37.781194653184308],\n            [-122.385862300471871, 37.781071231818238],\n            [-122.385755176650932, 37.781077752105325],\n            [-122.385766945797698, 37.781200487306606],\n            [-122.385749665539848, 37.78120145042832],\n            [-122.385737896774387, 37.781078715494381],\n            [-122.385622107018349, 37.781084687547477],\n            [-122.385633892683231, 37.781208109217374],\n            [-122.385616612767805, 37.781209072314041],\n            [-122.385604844221021, 37.781086337366979],\n            [-122.385487342103474, 37.781093023397624],\n            [-122.385499110464124, 37.781215758630843],\n            [-122.38548182985329, 37.781216721718849],\n            [-122.385470061873946, 37.781093986752957],\n            [-122.385352559732169, 37.781100672649266],\n            [-122.38536432752538, 37.781223407901315],\n            [-122.385347047602892, 37.781224370958491],\n            [-122.385335279499117, 37.781101635984861],\n            [-122.385219507099919, 37.781108294087041],\n            [-122.385231275020445, 37.781231029346657],\n            [-122.385213994748526, 37.781231992389863],\n            [-122.385202226517421, 37.781109257408659],\n            [-122.385086454440369, 37.781115915374649],\n            [-122.385098222142247, 37.781238650647374],\n            [-122.385080941520883, 37.781239613676604],\n            [-122.385069174200339, 37.781116878671227],\n            [-122.38495511409829, 37.781122822411653],\n            [-122.384966899006869, 37.781246244141819],\n            [-122.384949618382038, 37.781247207151821],\n            [-122.384937851277286, 37.781124472133506],\n            [-122.384823791153082, 37.781130415746894],\n            [-122.384835575844662, 37.781253837490027],\n            [-122.384818295216348, 37.781254800480774],\n            [-122.384792264836477, 37.780979154238715],\n            [-122.384809545055035, 37.780978191257269],\n            [-122.384831743135436, 37.781102819695278],\n            [-122.384925045670656, 37.781097207947639],\n            [-122.384923587517477, 37.780971560938383],\n            [-122.384940867732567, 37.780970597937682],\n            [-122.384963066031062, 37.781095226351141],\n            [-122.385056368546813, 37.781089614499592],\n            [-122.385054910171618, 37.780963967491779],\n            [-122.385072190383269, 37.780963004471836],\n            [-122.385094388899816, 37.781087632860746],\n            [-122.385189421508272, 37.781081993240349],\n            [-122.385187962562227, 37.780956346239556],\n            [-122.385205242424405, 37.780955383205644],\n            [-122.38522744150788, 37.781080011564157],\n            [-122.385322474096498, 37.781074371836517],\n            [-122.385321014925523, 37.780948724837195],\n            [-122.385338295130154, 37.780947761778229],\n            [-122.385360493742681, 37.78107239012293],\n            [-122.385457256077231, 37.781066722624757],\n            [-122.385455797024363, 37.780941075621371],\n            [-122.38547307722547, 37.78094011254268],\n            [-122.385495276407823, 37.781064740856642],\n            [-122.385592038722095, 37.781059073247853],\n            [-122.385590579095435, 37.780933426251508],\n            [-122.385607859293003, 37.780932463153057],\n            [-122.385630058353271, 37.781057091447337],\n            [-122.38572509122794, 37.781051451389651],\n            [-122.385723631030388, 37.780925804400319],\n            [-122.385740911570423, 37.780924841276843],\n            [-122.385763111197619, 37.781049469540704],\n            [-122.385849512311808, 37.781044654171389],\n            [-122.385848051896943, 37.78091900690886],\n            [-122.385865332440758, 37.780918044041726],\n            [-122.385887514843574, 37.781041985837916],\n            [-122.385989466046908, 37.781036234899503],\n            [-122.385988006094351, 37.780910587902042],\n            [-122.386005286281474, 37.780909624745334],\n            [-122.386027486347686, 37.781034252959756],\n            [-122.38611734695634, 37.781029382050761],\n            [-122.386115886434652, 37.78090373478566],\n            [-122.386133166625513, 37.780902771884811],\n            [-122.386155349124707, 37.781026713636507],\n            [-122.386250399355106, 37.78102175959976],\n            [-122.386248938262554, 37.780896112341686],\n            [-122.386266218795839, 37.780895149415798],\n            [-122.386288401860739, 37.781019091137082],\n            [-122.386381721962337, 37.781014164675518],\n            [-122.386380260647812, 37.780888517418902],\n            [-122.386397541177601, 37.780887554473743],\n            [-122.386419741895395, 37.781012182614816],\n            [-122.386513044542724, 37.781006569605005],\n            [-122.386511583006197, 37.780880922349844],\n            [-122.386528863532533, 37.780879959385452],\n            [-122.386551064468406, 37.781004587501954],\n            [-122.386646096514042, 37.780998946712984],\n            [-122.386644635444497, 37.780873299448245],\n            [-122.386661915621403, 37.780872336469891],\n            [-122.386684116778227, 37.780996964561524],\n            [-122.386782608677009, 37.780991268294322],\n            [-122.386781146684797, 37.780865621042153],\n            [-122.386798427204084, 37.780864658038261],\n            [-122.386820628587657, 37.780989286104372],\n            [-122.386907012122748, 37.780983783515829],\n            [-122.386905549920243, 37.780858136265046],\n            [-122.386922830436163, 37.780857173242921],\n            [-122.386945032026247, 37.780981801285769],\n            [-122.387038334595715, 37.780976187860247],\n            [-122.387036872171194, 37.780850540610921],\n            [-122.387054152683675, 37.780849577569541],\n            [-122.387076354491839, 37.780974205587825],\n            [-122.387174846330339, 37.780968508992949],\n            [-122.387173384021025, 37.7808428617396],\n            [-122.387190664183962, 37.780841898683754],\n            [-122.387212865872911, 37.78096652668205],\n            [-122.387306168748907, 37.78096091303906],\n            [-122.387304706217577, 37.780835265787175],\n            [-122.387321986377088, 37.780834302712059],\n            [-122.387344188284104, 37.780958930685784],\n            [-122.387435761378228, 37.780953344631229],\n            [-122.387434316073197, 37.780828383824961],\n            [-122.387451578437933, 37.780826734292219],\n            [-122.387473781252041, 37.780951362230645],\n            [-122.387582651602727, 37.780945499137275],\n            [-122.38754001837809, 37.780493630652451],\n            [-122.38744131821305, 37.780491090765793],\n            [-122.387419058221624, 37.780500374265543],\n            [-122.387330736209734, 37.780497667864211],\n            [-122.387308476209526, 37.780506951617653],\n            [-122.387208063026961, 37.780505125406954],\n            [-122.387184073602839, 37.780514436820333],\n            [-122.387081931006932, 37.780512638185321],\n            [-122.387059670971126, 37.780521921891747],\n            [-122.386964429936867, 37.780519325963184],\n            [-122.386942169884378, 37.780528609647433],\n            [-122.386852118103619, 37.780525930572708],\n            [-122.386829875474817, 37.780535900680007],\n            [-122.386736346744712, 37.780532590439464],\n            [-122.386712374345294, 37.780542588206671],\n            [-122.386618863049748, 37.780539964317235],\n            [-122.386594873201545, 37.780549275890777],\n            [-122.386499632141522, 37.780546679312224],\n            [-122.386475659366695, 37.780556677311395],\n            [-122.386383878154234, 37.780554025277233],\n            [-122.386359888272054, 37.780563336802956],\n            [-122.386266376955888, 37.780560712359396],\n            [-122.386242404491071, 37.780570710305611],\n            [-122.386143703562112, 37.780568168794694],\n            [-122.386119713984229, 37.780577479991436],\n            [-122.386022743164474, 37.78057491124985],\n            [-122.386000500402702, 37.780584880926071],\n            [-122.385901799477139, 37.780582339761835],\n            [-122.385877827288269, 37.780592337079185],\n            [-122.385784298190572, 37.780589026363579],\n            [-122.38576205573851, 37.780598995989244],\n            [-122.385668544412525, 37.780596371620753],\n            [-122.385646283822041, 37.780605654791167],\n            [-122.385554502585677, 37.780603002386016],\n            [-122.385532242331863, 37.780612285803919],\n            [-122.385433541724893, 37.780609743967645],\n            [-122.385411298534606, 37.78061971381274],\n            [-122.385310868512505, 37.780617199530866],\n            [-122.385288625649494, 37.780627169347277],\n            [-122.385191654786624, 37.780624599647666],\n            [-122.38516766472523, 37.780633910656128],\n            [-122.385079342290652, 37.780631202574007],\n            [-122.385057082314987, 37.780640485896626],\n            [-122.384960111442751, 37.78063791600659],\n            [-122.384936138770243, 37.780647913412501],\n            [-122.384835917442729, 37.780653636341071],\n            [-122.384842007573795, 37.78075723388941],\n            [-122.384817825799914, 37.78075899421075],\n            [-122.38480325384981, 37.78052574104445],\n            [-122.384827435209161, 37.780523981006105],\n            [-122.384843799331136, 37.780623294238858],\n            [-122.384923281326039, 37.780618589884874],\n            [-122.384925926962097, 37.780518286009212],\n            [-122.384957841591557, 37.780616663880899],\n            [-122.385044241908545, 37.780611849106826],\n            [-122.385045157967028, 37.780511572605498],\n            [-122.385078784737303, 37.780609236348262],\n            [-122.385154824277294, 37.780605273878329],\n            [-122.385157469939188, 37.780504969717121],\n            [-122.38518938452367, 37.780603347531915],\n            [-122.385275768076681, 37.780597846132551],\n            [-122.385276683130513, 37.780497569640254],\n            [-122.385310328316436, 37.780595919750695],\n            [-122.385398458680143, 37.78059107677575],\n            [-122.385399356494702, 37.780490114106904],\n            [-122.385433018927529, 37.78058915090709],\n            [-122.385519402086118, 37.780583648785594],\n            [-122.385520317170744, 37.78048337283478],\n            [-122.385553962326881, 37.780581722881479],\n            [-122.385633443879414, 37.780577017779677],\n            [-122.385634358810009, 37.780476741827854],\n            [-122.385666274013943, 37.780575119514069],\n            [-122.385749215060514, 37.780570359001402],\n            [-122.385750112750941, 37.780469396598548],\n            [-122.385783775282164, 37.780568432755352],\n            [-122.385864986912736, 37.780563700098369],\n            [-122.385865884092837, 37.780462737425502],\n            [-122.38589954712829, 37.780561773818384],\n            [-122.385987659981154, 37.780556243958578],\n            [-122.385988556648442, 37.780455281290152],\n            [-122.386022220190043, 37.78055431764264],\n            [-122.386106890951012, 37.780549529481313],\n            [-122.386107787802118, 37.780448566806321],\n            [-122.386141451153719, 37.780547603130415],\n            [-122.386229563971753, 37.780542073089819],\n            [-122.386232190406915, 37.780441082739081],\n            [-122.386264124167781, 37.780540146702961],\n            [-122.386347065143937, 37.780535386044313],\n            [-122.386349691419113, 37.78043439569084],\n            [-122.386381625333826, 37.780533459623001],\n            [-122.386462836549086, 37.780528726834362],\n            [-122.386465444877402, 37.780427049764754],\n            [-122.386497396725886, 37.780526800104532],\n            [-122.386582067431817, 37.780522011876563],\n            [-122.386584675942643, 37.78042033479867],\n            [-122.386616627602407, 37.780520085111789],\n            [-122.386699551102055, 37.78051463803542],\n            [-122.386702159451801, 37.78041296095477],\n            [-122.386734111266165, 37.780512711236199],\n            [-122.386817052181556, 37.780507950246935],\n            [-122.386817930281353, 37.780406301126988],\n            [-122.386851612353468, 37.780506023962438],\n            [-122.386929363990731, 37.7805013456708],\n            [-122.386930242282716, 37.780399696544364],\n            [-122.386963924156646, 37.780499419353383],\n            [-122.387046865035501, 37.780494657927875],\n            [-122.387047743173568, 37.78039300907502],\n            [-122.387081425188285, 37.780492731301436],\n            [-122.387171267622861, 37.780487172869918],\n            [-122.387173875339855, 37.780385496327504],\n            [-122.387205827768923, 37.780485246207022],\n            [-122.387295687629134, 37.780480374124949],\n            [-122.387296547982558, 37.780378038825731],\n            [-122.387330247768816, 37.780478447425573],\n            [-122.387406269602806, 37.780473796784797],\n            [-122.38740712980379, 37.780371461484656],\n            [-122.38744082973659, 37.78047187005302],\n            [-122.387527230063114, 37.780467053179258],\n            [-122.38749949992625, 37.780056836734332],\n            [-122.387400782538194, 37.780053609825671],\n            [-122.387378540131436, 37.780063580311925],\n            [-122.387290201196578, 37.780060187435744],\n            [-122.387267941315415, 37.780069470907662],\n            [-122.387167511635454, 37.780066958487033],\n            [-122.387143539437361, 37.780076956068172],\n            [-122.387041380009435, 37.780074471228168],\n            [-122.387019120092901, 37.780083754653084],\n            [-122.386922167327057, 37.780081873095298],\n            [-122.386901619695664, 37.780090442368873],\n            [-122.386811551008179, 37.78008707681861],\n            [-122.386787578762636, 37.780097074601919],\n            [-122.386695780329632, 37.780093736646045],\n            [-122.386671790289242, 37.780103048241536],\n            [-122.386578279887487, 37.780100424039603],\n            [-122.386554289830215, 37.78010973561117],\n            [-122.386459049681108, 37.780107138994076],\n            [-122.386435059945711, 37.780116450261254],\n            [-122.386343278946669, 37.780113798749966],\n            [-122.386319288841676, 37.780123109724549],\n            [-122.386225778441045, 37.780120485792217],\n            [-122.386201788319156, 37.780129796742877],\n            [-122.38610135893272, 37.780127283134078],\n            [-122.386079098889809, 37.780136566656026],\n            [-122.385982128640109, 37.780133997606264],\n            [-122.38595813883623, 37.780143308776367],\n            [-122.385859438837869, 37.78014076729638],\n            [-122.385837196191162, 37.780150737217127],\n            [-122.385743667990269, 37.780147426188911],\n            [-122.385719678159148, 37.780156737585067],\n            [-122.385627896776001, 37.780154084973319],\n            [-122.385603907274145, 37.780163396340377],\n            [-122.385513855977493, 37.78016071596884],\n            [-122.385489865760334, 37.780170027049152],\n            [-122.385392895494292, 37.780167457789418],\n            [-122.385368905944745, 37.780176768559457],\n            [-122.385270205929046, 37.780174226860836],\n            [-122.385246216015972, 37.78018353761145],\n            [-122.385150975133058, 37.78018094022346],\n            [-122.385126985555786, 37.780190251218855],\n            [-122.385038663996198, 37.78018754310051],\n            [-122.385014674056748, 37.780196854078561],\n            [-122.384919433510831, 37.780194256498035],\n            [-122.38489544320835, 37.780203567457356],\n            [-122.384795222823172, 37.780209290346271],\n            [-122.384801312515108, 37.780312887910483],\n            [-122.384777113810173, 37.780313961773459],\n            [-122.384762559495741, 37.780081395028191],\n            [-122.384786741057312, 37.780079634992532],\n            [-122.384803105027004, 37.780178948238863],\n            [-122.384882586203048, 37.780174243917422],\n            [-122.384885231532309, 37.780073940040232],\n            [-122.384917146262893, 37.780172317925249],\n            [-122.385001816666616, 37.780167530831704],\n            [-122.385004462173583, 37.780067226671626],\n            [-122.385036376713245, 37.780165604530019],\n            [-122.38511414553922, 37.780161614154224],\n            [-122.385116790908242, 37.780061310540667],\n            [-122.385148705594133, 37.780159688368748],\n            [-122.385233375608649, 37.780154900565314],\n            [-122.385236021162356, 37.780054596943422],\n            [-122.385267935657353, 37.780152974744887],\n            [-122.385356065837257, 37.780148131521123],\n            [-122.385358710872126, 37.780047827627314],\n            [-122.385390625872631, 37.780146205390146],\n            [-122.385477025953321, 37.780141390018422],\n            [-122.385479653051561, 37.780040399682747],\n            [-122.385511585982442, 37.780139463852002],\n            [-122.385591067071402, 37.78013475905297],\n            [-122.385593711788431, 37.78003445515364],\n            [-122.38562562709447, 37.78013283285312],\n            [-122.385706855349099, 37.780128787029341],\n            [-122.385709482473402, 37.780027796408206],\n            [-122.385741415359448, 37.780126860520987],\n            [-122.385824355914565, 37.780122100504215],\n            [-122.385825252791648, 37.780021137554606],\n            [-122.38585891591876, 37.780120173961421],\n            [-122.385945315592835, 37.780115358252047],\n            [-122.385947942745361, 37.780014367894388],\n            [-122.38597987560469, 37.780113432222926],\n            [-122.386066275948011, 37.78010861613928],\n            [-122.38606717251001, 37.780007653736739],\n            [-122.38610083595357, 37.78010669007471],\n            [-122.386188965678286, 37.780101846233919],\n            [-122.386191574380462, 37.780000169711762],\n            [-122.386223525670573, 37.780099919858799],\n            [-122.386306466157762, 37.780095159228338],\n            [-122.386309092126623, 37.77999416887333],\n            [-122.386341026143853, 37.780093232818778],\n            [-122.386422253963318, 37.780089186233084],\n            [-122.386424863030612, 37.77998750941989],\n            [-122.386456813943639, 37.780087259789596],\n            [-122.386541484497073, 37.780082471584841],\n            [-122.386544092702067, 37.77998079450537],\n            [-122.386576044464221, 37.780080544831833],\n            [-122.386658984914163, 37.780075784227932],\n            [-122.386661592958049, 37.779974107145748],\n            [-122.386693544875143, 37.780073857440478],\n            [-122.386776485303088, 37.780069096479352],\n            [-122.386777363799169, 37.779967447346195],\n            [-122.386809315183925, 37.780067197895306],\n            [-122.386888813892412, 37.780063178385696],\n            [-122.386889674448653, 37.77996084281277],\n            [-122.38692337384856, 37.780061251805442],\n            [-122.387006314247685, 37.780056490682639],\n            [-122.387007174648929, 37.779954155383322],\n            [-122.38704087419768, 37.780054564067953],\n            [-122.387130733547679, 37.78004969211122],\n            [-122.387133323862429, 37.779947329117419],\n            [-122.38716529349125, 37.780047765460061],\n            [-122.387255152824864, 37.780042893408485],\n            [-122.387256012876136, 37.779940557832525],\n            [-122.387289712762055, 37.78004096672084],\n            [-122.387365734157797, 37.780036316380411],\n            [-122.387366594049666, 37.779933980528931],\n            [-122.387400294082084, 37.78003438938579],\n            [-122.387486693563403, 37.780029572821469],\n            [-122.387461810378028, 37.779663260524117],\n            [-122.387378661112308, 37.779659784635669],\n            [-122.387356418813553, 37.779669754843859],\n            [-122.387276728664503, 37.779666223506673],\n            [-122.387252739526915, 37.779675535209975],\n            [-122.387173049376599, 37.779672003802716],\n            [-122.387150789943092, 37.779681287247612],\n            [-122.387067640671745, 37.779677811139862],\n            [-122.387043668592767, 37.779687808975972],\n            [-122.386969150543408, 37.779683507921064],\n            [-122.386945178103403, 37.779693505742671],\n            [-122.386868930320873, 37.779689232309579],\n            [-122.386844958564382, 37.779699230374298],\n            [-122.386765268401049, 37.779695698416859],\n            [-122.386741279182857, 37.779705009741512],\n            [-122.386659859644624, 37.77970150593859],\n            [-122.386635887496141, 37.77971150341719],\n            [-122.386556180245535, 37.77970728514304],\n            [-122.386533937822136, 37.779717255195997],\n            [-122.386452518273302, 37.779713750975354],\n            [-122.386428528664126, 37.77972306224185],\n            [-122.38635402805015, 37.779719447242712],\n            [-122.386331768168034, 37.779728731087609],\n            [-122.386245159413448, 37.779725309752138],\n            [-122.386222916935935, 37.779735279471808],\n            [-122.386151858355305, 37.779730922551266],\n            [-122.386127868702886, 37.77974023375657],\n            [-122.386048178882604, 37.779736701583559],\n            [-122.386025936036276, 37.779746671546171],\n            [-122.385946246559868, 37.779743139298724],\n            [-122.385922256538748, 37.779752450742315],\n            [-122.385842567054112, 37.779748918150233],\n            [-122.385818577011165, 37.779758229298103],\n            [-122.385742346663463, 37.779754641855682],\n            [-122.385718357291026, 37.779763952697529],\n            [-122.385635207994184, 37.779760475579685],\n            [-122.385611235349785, 37.779770473129936],\n            [-122.385528086050684, 37.779766995936541],\n            [-122.385504096654358, 37.779776307009364],\n            [-122.38542267708938, 37.779772802077623],\n            [-122.385400434501221, 37.779782772191204],\n            [-122.385319014927063, 37.779779266913316],\n            [-122.385296754890703, 37.779788550288139],\n            [-122.385225696313427, 37.779784192809487],\n            [-122.385201723952207, 37.779794190270913],\n            [-122.385120304035951, 37.779790685135936],\n            [-122.38509804431726, 37.779799968467685],\n            [-122.385020084218212, 37.779796407941809],\n            [-122.38499609406378, 37.779805719196837],\n            [-122.384916404566042, 37.779802185978852],\n            [-122.384894161901101, 37.779812155996879],\n            [-122.38480257260197, 37.779817053634027],\n            [-122.384809835241697, 37.779898657585051],\n            [-122.384782194261888, 37.779900472932383],\n            [-122.384761383067243, 37.779694102231538],\n            [-122.384789023963819, 37.779692286614427],\n            [-122.384808725038937, 37.779786739726958],\n            [-122.384879557453601, 37.779782173395887],\n            [-122.384880595342565, 37.779686702548538],\n            [-122.384914117331078, 37.779780247404453],\n            [-122.384983237080689, 37.779776395391224],\n            [-122.384984274829336, 37.77968092426827],\n            [-122.385017796952795, 37.779774469369393],\n            [-122.385085186616962, 37.779770645233363],\n            [-122.38508622457347, 37.779675173829325],\n            [-122.385119746476775, 37.779768718907071],\n            [-122.385188848787607, 37.77976418060323],\n            [-122.385189886610789, 37.779668709198148],\n            [-122.385223408641721, 37.779762254246563],\n            [-122.385283897452879, 37.779759226795328],\n            [-122.385284934821826, 37.779663755944007],\n            [-122.385318457309211, 37.779757300685375],\n            [-122.385385847281285, 37.779753476097383],\n            [-122.385386866740049, 37.779657318531406],\n            [-122.3854203897058, 37.77975086351293],\n            [-122.385491239136826, 37.779746983539091],\n            [-122.385492276232114, 37.779651512411178],\n            [-122.385525798982087, 37.779745057368359],\n            [-122.385598378143868, 37.779741149940357],\n            [-122.385599415094418, 37.779645678536831],\n            [-122.385632937976666, 37.779739223463643],\n            [-122.385705499691042, 37.779734629250548],\n            [-122.385706536510781, 37.779639158120546],\n            [-122.385740059531955, 37.779732703291586],\n            [-122.385805719718547, 37.779728905856508],\n            [-122.385806756416358, 37.779633435000086],\n            [-122.385840279547224, 37.779726979593605],\n            [-122.385909399199221, 37.7797231270374],\n            [-122.385910435756728, 37.779627655905408],\n            [-122.385943959022555, 37.779721200744085],\n            [-122.386013061231836, 37.779716661682606],\n            [-122.386014097656016, 37.779621190549562],\n            [-122.386047621049443, 37.779714735358894],\n            [-122.38611501060636, 37.779710910634236],\n            [-122.386116047238417, 37.779615439220123],\n            [-122.386149570411703, 37.779708984006099],\n            [-122.386208311974158, 37.779705297309562],\n            [-122.386211077885747, 37.779609798774842],\n            [-122.386242871788397, 37.779703371203219],\n            [-122.386317180591391, 37.779699435108718],\n            [-122.38631821662446, 37.77960396397274],\n            [-122.386351740393124, 37.779697508695882],\n            [-122.386415670790853, 37.779693738872112],\n            [-122.386416706351284, 37.779598267740703],\n            [-122.386450230587357, 37.779691812430414],\n            [-122.386519350181956, 37.779687959791211],\n            [-122.386520385602097, 37.779592488384218],\n            [-122.386553892529193, 37.77968534660009],\n            [-122.386623012105417, 37.779681493625532],\n            [-122.386624047399195, 37.779586022492111],\n            [-122.386657571897828, 37.779679567397658],\n            [-122.386728421189233, 37.779675686405767],\n            [-122.386729456700351, 37.779580215540342],\n            [-122.386762980969223, 37.779673759872402],\n            [-122.386832100523861, 37.779669906775261],\n            [-122.386833135894662, 37.779574435634252],\n            [-122.386866660298466, 37.779667980211499],\n            [-122.386932302936756, 37.779663495979172],\n            [-122.386933338178679, 37.779568024837168],\n            [-122.386966862705819, 37.779661569386036],\n            [-122.387030793048368, 37.779657799503305],\n            [-122.38704566565184, 37.779562106599087],\n            [-122.387054991513921, 37.779656725452249],\n            [-122.387136202059708, 37.779651991644378],\n            [-122.387139193519673, 37.77956541658935],\n            [-122.387170761825075, 37.779650065266061],\n            [-122.387239881329535, 37.779646211655297],\n            [-122.387241125495137, 37.779558977842669],\n            [-122.387274441082525, 37.779644284971994],\n            [-122.387341830851355, 37.779640459266005],\n            [-122.387344804633926, 37.779553198035785],\n            [-122.387376373154481, 37.7796378461085],\n            [-122.387448951768221, 37.779633937546841],\n            [-122.387428243590108, 37.779295714050406],\n            [-122.387339905566151, 37.779292321211003],\n            [-122.38731766337223, 37.779302291412563],\n            [-122.387236244241279, 37.77929878773282],\n            [-122.387212254525394, 37.779308099439604],\n            [-122.387132565463105, 37.779304567993648],\n            [-122.387108575718415, 37.779313879130164],\n            [-122.387028886654846, 37.779310347614164],\n            [-122.387004914343294, 37.779320345448582],\n            [-122.386932126735232, 37.779316016678415],\n            [-122.386908154408829, 37.779326014493151],\n            [-122.386828447902374, 37.779321796397241],\n            [-122.386804475567118, 37.779331794465456],\n            [-122.386726516216612, 37.779328234787421],\n            [-122.3867025267663, 37.779337546110341],\n            [-122.386624566736728, 37.779333986925117],\n            [-122.386600577610722, 37.779343297947158],\n            [-122.386520887847865, 37.7793397660989],\n            [-122.386496916150335, 37.779349763818921],\n            [-122.386417208949837, 37.779345545456096],\n            [-122.386393237236504, 37.77935554315502],\n            [-122.386318719558147, 37.779351241688801],\n            [-122.386296477216476, 37.779361211972173],\n            [-122.386211598617734, 37.779357762936172],\n            [-122.386187609086349, 37.779367073879669],\n            [-122.386113108495195, 37.779363458733613],\n            [-122.386089119302554, 37.779372769926113],\n            [-122.386011159604379, 37.779369210055243],\n            [-122.385987187482783, 37.779379207677103],\n            [-122.385909210351812, 37.779374961294259],\n            [-122.385885237875669, 37.779384959175481],\n            [-122.385802088988314, 37.779381481900515],\n            [-122.385778099750866, 37.779390793029705],\n            [-122.385698409983121, 37.779387260900187],\n            [-122.385676150450635, 37.779396544067176],\n            [-122.385598190747288, 37.779392983923266],\n            [-122.385574218216505, 37.779402981466639],\n            [-122.3854945117032, 37.779398762469064],\n            [-122.3854705391566, 37.779408759991362],\n            [-122.38538912034204, 37.779405255030689],\n            [-122.385366860087544, 37.779414538699463],\n            [-122.385285441271535, 37.779411033667223],\n            [-122.385261469031803, 37.779421030866885],\n            [-122.385190410808178, 37.779416673366669],\n            [-122.38516642079081, 37.779425984382435],\n            [-122.385088461426918, 37.77942242389598],\n            [-122.38506448916398, 37.779432421330149],\n            [-122.384984782302112, 37.779428201993547],\n            [-122.384960810030293, 37.779438199681202],\n            [-122.384881120582591, 37.779434666444651],\n            [-122.38485713086655, 37.779443977391921],\n            [-122.384765559446308, 37.779449561719943],\n            [-122.384771214222269, 37.779535998445006],\n            [-122.384747015071127, 37.779537072038458],\n            [-122.38473308847739, 37.779329217537743],\n            [-122.384757287215081, 37.779328143952647],\n            [-122.384773494195869, 37.779421279213679],\n            [-122.384846073080865, 37.779417372248531],\n            [-122.384848858140316, 37.779322559636441],\n            [-122.384880632782526, 37.779415445992221],\n            [-122.384949752194402, 37.77941159399834],\n            [-122.384952537135234, 37.779316781932884],\n            [-122.384984294475274, 37.779408981541515],\n            [-122.385051684148522, 37.779405157144254],\n            [-122.385056216100068, 37.779311003588973],\n            [-122.385086243853095, 37.779403231376833],\n            [-122.385155363235981, 37.779399378987847],\n            [-122.385158147906921, 37.779304566642743],\n            [-122.38518992292822, 37.779397452915447],\n            [-122.385248663600038, 37.779393766707848],\n            [-122.385253195301217, 37.779299613144822],\n            [-122.385283223287274, 37.779391840608099],\n            [-122.385354072735922, 37.779387960985474],\n            [-122.385358586870339, 37.779293120699045],\n            [-122.385388632410709, 37.779386034580241],\n            [-122.385459481495417, 37.779382154625232],\n            [-122.385462265784483, 37.779287342547207],\n            [-122.385494023751633, 37.779379542293618],\n            [-122.385561413364954, 37.77937571733046],\n            [-122.385565944668585, 37.779281563755077],\n            [-122.385595973035649, 37.779373791139029],\n            [-122.385663362299539, 37.779369966397688],\n            [-122.385667876044934, 37.779275126373676],\n            [-122.385697921964919, 37.779368040176379],\n            [-122.385765311917211, 37.779364215640285],\n            [-122.385769825525102, 37.779269375337698],\n            [-122.385799871570299, 37.779362289114509],\n            [-122.385874162638899, 37.779357667403531],\n            [-122.38587696382136, 37.779263541485342],\n            [-122.385908722286104, 37.779355740845865],\n            [-122.385976111863471, 37.779351915920223],\n            [-122.385978912570593, 37.779257790005062],\n            [-122.386010671519301, 37.779349989881837],\n            [-122.386078060733283, 37.77934616462889],\n            [-122.386080844570401, 37.779251352255542],\n            [-122.386112620376821, 37.779344238286029],\n            [-122.386174820757347, 37.779340496265526],\n            [-122.386179333847906, 37.779245656221427],\n            [-122.386209380395755, 37.779338569894314],\n            [-122.386283671761092, 37.779333947919731],\n            [-122.386288202139696, 37.779239794041324],\n            [-122.386318231386639, 37.779332021242034],\n            [-122.386380431746588, 37.779328279113052],\n            [-122.386384962016137, 37.779234125779993],\n            [-122.386414991380946, 37.77932635295614],\n            [-122.38648584000218, 37.779322472115325],\n            [-122.386488640754166, 37.779228346176467],\n            [-122.386520399624118, 37.779320545652922],\n            [-122.386589518862621, 37.779316692697755],\n            [-122.386592302045827, 37.779221880311852],\n            [-122.386624078479215, 37.779314766204962],\n            [-122.386691468335471, 37.779310941139499],\n            [-122.386694251035379, 37.779216128482069],\n            [-122.386726027939744, 37.77930901434226],\n            [-122.386793400000514, 37.779304502779596],\n            [-122.38679620001615, 37.779210376838677],\n            [-122.386827959599159, 37.779302575952464],\n            [-122.386897078804992, 37.779298722816996],\n            [-122.386901608410881, 37.779204569189005],\n            [-122.386931638412193, 37.779296796508632],\n            [-122.386992108980877, 37.779293081467337],\n            [-122.386996638465945, 37.779198927835616],\n            [-122.387026668576013, 37.779291154856551],\n            [-122.387095787760913, 37.779287301604562],\n            [-122.38710029967234, 37.779192461524332],\n            [-122.387130347350677, 37.779285374963401],\n            [-122.387199466531811, 37.779281521925199],\n            [-122.387204309717603, 37.779199724010219],\n            [-122.387234026109212, 37.779279594979052],\n            [-122.387304857552181, 37.779275027470717],\n            [-122.387309700628478, 37.779193229825893],\n            [-122.387339417137795, 37.779273101042833],\n            [-122.387415455175415, 37.779269136564935],\n            [-122.387394991136247, 37.778940523542531],\n            [-122.387315302113649, 37.778936992225411],\n            [-122.387291312863496, 37.778946303668732],\n            [-122.387218525259328, 37.778941975080627],\n            [-122.38719628281504, 37.778951945540015],\n            [-122.38712349555685, 37.77894761688772],\n            [-122.387099505926273, 37.778956928022971],\n            [-122.387025006394012, 37.778953313441747],\n            [-122.387002746472433, 37.778962597145551],\n            [-122.386929976655324, 37.778958954818265],\n            [-122.386905987003843, 37.778968266188713],\n            [-122.386836658833175, 37.778963881995679],\n            [-122.386812687306417, 37.778973880055133],\n            [-122.386736440270226, 37.778969606535817],\n            [-122.386712468022296, 37.778979604036806],\n            [-122.386637951048542, 37.77897530276654],\n            [-122.386613979138446, 37.778985300516517],\n            [-122.386549822682738, 37.778980146669305],\n            [-122.386527580483204, 37.778990116996944],\n            [-122.386449603383866, 37.778985870976705],\n            [-122.386425631783965, 37.778995868408273],\n            [-122.386356303615884, 37.778991483932835],\n            [-122.386332313881979, 37.779000795186512],\n            [-122.386256084279836, 37.778997207801154],\n            [-122.386233824940902, 37.779006491348653],\n            [-122.386164496774228, 37.779002106760423],\n            [-122.386140524798748, 37.779012104688697],\n            [-122.386067737197138, 37.779007775390234],\n            [-122.386043765192852, 37.779017772749661],\n            [-122.385962346433857, 37.779014268190103],\n            [-122.385938356643209, 37.779023579363574],\n            [-122.385865569388116, 37.779019249934791],\n            [-122.385841597013936, 37.779029247533202],\n            [-122.385768809758915, 37.779024918044691],\n            [-122.385744837376848, 37.779034915898023],\n            [-122.385675509212518, 37.779030531022293],\n            [-122.385651520065593, 37.779039841851741],\n            [-122.385578749547165, 37.77903619870164],\n            [-122.385554760393234, 37.779045509785973],\n            [-122.385481989874023, 37.779041866576151],\n            [-122.385458000713072, 37.779051177915363],\n            [-122.385385212767062, 37.779046848201126],\n            [-122.385361223578457, 37.779056158971507],\n            [-122.385286723685297, 37.779052543297539],\n            [-122.385262734143524, 37.779061854327978],\n            [-122.385195135701593, 37.77905744150933],\n            [-122.385171163224882, 37.779067438971381],\n            [-122.385094916187072, 37.779063164390635],\n            [-122.385070944393846, 37.779073162095791],\n            [-122.385001616233609, 37.779068776823863],\n            [-122.38497935636353, 37.77907805986537],\n            [-122.384906586186588, 37.779074416294911],\n            [-122.384884326309916, 37.779083699593059],\n            [-122.384796197708141, 37.779088542459625],\n            [-122.384801451464668, 37.77915919068996],\n            [-122.384780712232896, 37.779160209251955],\n            [-122.384765734942761, 37.778979153747279],\n            [-122.384786456703722, 37.778977448743071],\n            [-122.384804149136215, 37.779060946405302],\n            [-122.384871538841949, 37.779057121830682],\n            [-122.384874585188271, 37.778972606432866],\n            [-122.384906098382288, 37.779055195841352],\n            [-122.384966568862538, 37.779051482113282],\n            [-122.384969632515677, 37.778967652883104],\n            [-122.385001128397789, 37.779049556096091],\n            [-122.385058156860921, 37.779046584353601],\n            [-122.385061202980566, 37.778962068401654],\n            [-122.385092716384577, 37.779044658035005],\n            [-122.385158376003019, 37.779040861234485],\n            [-122.38516315173905, 37.77895631817001],\n            [-122.385192918111116, 37.779038248990936],\n            [-122.385249946543951, 37.779035276606521],\n            [-122.385253009876607, 37.778951447368748],\n            [-122.385284506064338, 37.779033350506261],\n            [-122.385350165317007, 37.779029553604488],\n            [-122.385353228882295, 37.778945724358508],\n            [-122.385382995459778, 37.779027655131593],\n            [-122.385445195618914, 37.779023913770359],\n            [-122.385448258723784, 37.778940084252753],\n            [-122.385479755121978, 37.779021987338304],\n            [-122.385541972691954, 37.779018932096108],\n            [-122.385545018273902, 37.778934416680386],\n            [-122.385576532197149, 37.779017005910269],\n            [-122.385638732330676, 37.779013264172356],\n            [-122.385641777795314, 37.77892874847948],\n            [-122.385673291830699, 37.779011337958153],\n            [-122.38573203252615, 37.779007651778535],\n            [-122.385735094960694, 37.77892382225879],\n            [-122.385766592014193, 37.779005725262394],\n            [-122.385828792128677, 37.779001983424223],\n            [-122.385831854467739, 37.778918154451013],\n            [-122.385863351625531, 37.779000057428874],\n            [-122.385925569154423, 37.778997001709705],\n            [-122.385930344007448, 37.778912458338894],\n            [-122.385960128639468, 37.778995075411409],\n            [-122.386030959889439, 37.778990508668088],\n            [-122.386034022338819, 37.778906679409381],\n            [-122.386065519368728, 37.778988582338918],\n            [-122.386127736893627, 37.778985527062204],\n            [-122.386132511508848, 37.778900983408484],\n            [-122.386162278927955, 37.778982913985502],\n            [-122.386221036657432, 37.7789799137378],\n            [-122.386224081811065, 37.778895398298431],\n            [-122.386255596133822, 37.778977987627471],\n            [-122.386319526256145, 37.778974217575232],\n            [-122.386322588033238, 37.778890388314295],\n            [-122.38635408572037, 37.778972291161459],\n            [-122.386412826344156, 37.778968604368089],\n            [-122.386417618076294, 37.778884747421706],\n            [-122.386447385803351, 37.778966677926974],\n            [-122.386514775006972, 37.77896285296778],\n            [-122.386517836902243, 37.778879023421496],\n            [-122.386549334453889, 37.778960926222204],\n            [-122.386602903347566, 37.778958008816609],\n            [-122.386605964804218, 37.778874179548069],\n            [-122.386637462796912, 37.778956082319787],\n            [-122.386699662825961, 37.778952340022151],\n            [-122.386702724173148, 37.778868510751018],\n            [-122.386734222270135, 37.778950413496958],\n            [-122.386799881728336, 37.778946616056871],\n            [-122.386804673015121, 37.778862758819848],\n            [-122.386834441160303, 37.778944689227728],\n            [-122.386894928894066, 37.778941660955091],\n            [-122.38689797293344, 37.778857145503274],\n            [-122.386929488335198, 37.77893973464726],\n            [-122.386989958613299, 37.77893601960681],\n            [-122.386993002191389, 37.778851503883409],\n            [-122.387024518042367, 37.778934092996543],\n            [-122.387086718033501, 37.778930350494704],\n            [-122.387091509002346, 37.778846493520362],\n            [-122.387121277457396, 37.778928423856051],\n            [-122.387183477445888, 37.778924681577777],\n            [-122.387186764992791, 37.77884977579707],\n            [-122.387218036857675, 37.778922754636191],\n            [-122.387280254273051, 37.778919698476713],\n            [-122.387283524285621, 37.778844106523373],\n            [-122.387314813687013, 37.77891777178133],\n            [-122.387382202448663, 37.778913946057301],\n            [-122.387364166474967, 37.778612763090031],\n            [-122.387286207520319, 37.778609204336291],\n            [-122.387262218365407, 37.778618515499687],\n            [-122.387185989110648, 37.778614928715285],\n            [-122.387161999948319, 37.778624240132871],\n            [-122.387092672083611, 37.778619856090089],\n            [-122.387070429718904, 37.778629826526299],\n            [-122.386994183021031, 37.778625553173391],\n            [-122.386971941319302, 37.778635523030793],\n            [-122.386900883746804, 37.778631166560793],\n            [-122.386876911637231, 37.778641164370391],\n            [-122.38679893557611, 37.778636918569845],\n            [-122.386774963796881, 37.778646916353182],\n            [-122.386707365642749, 37.778642504401184],\n            [-122.386683376072511, 37.77865181600145],\n            [-122.386614065647606, 37.778648118121843],\n            [-122.386590076741811, 37.778657429142889],\n            [-122.386510387715276, 37.778653897287107],\n            [-122.386486398801594, 37.778663208561632],\n            [-122.386417070940411, 37.778658824121642],\n            [-122.386393098756784, 37.778668821832838],\n            [-122.38631686949239, 37.77866523448656],\n            [-122.386292880557747, 37.778674545996267],\n            [-122.38622009293212, 37.778670216797138],\n            [-122.386197851113138, 37.778680186508431],\n            [-122.386125063833461, 37.778675857245105],\n            [-122.38610282131512, 37.778685827224102],\n            [-122.386028304671399, 37.778681525568288],\n            [-122.38600433277405, 37.778691523194873],\n            [-122.385931545494543, 37.77868719381209],\n            [-122.385909285516078, 37.778696477309893],\n            [-122.385834786309914, 37.778692862251056],\n            [-122.385810796946046, 37.778702173118987],\n            [-122.385741486517588, 37.778698474726497],\n            [-122.385717497147184, 37.778707785850045],\n            [-122.385642980157812, 37.778703483956278],\n            [-122.385619007862559, 37.778713481784578],\n            [-122.385549680005639, 37.778709096834618],\n            [-122.385525708373962, 37.778719094083733],\n            [-122.385451191384348, 37.778714792068747],\n            [-122.385428949109837, 37.778724761915029],\n            [-122.385354432120124, 37.778720459838901],\n            [-122.385330442694098, 37.778729770883643],\n            [-122.385257672841163, 37.778726127529652],\n            [-122.385235413119702, 37.778735411169237],\n            [-122.385147302363819, 37.778740940194126],\n            [-122.385154355852308, 37.778814307082541],\n            [-122.385130156934849, 37.778815380480168],\n            [-122.385113449499144, 37.778634352661491],\n            [-122.385137630588289, 37.778632592828004],\n            [-122.385155236200447, 37.778712657669637],\n            [-122.385222625586081, 37.778708833443275],\n            [-122.385225758626859, 37.778627749705855],\n            [-122.38525718494833, 37.778706907076334],\n            [-122.38531938483824, 37.778703165506691],\n            [-122.385322517780125, 37.778622082041188],\n            [-122.385352214498553, 37.778701267322347],\n            [-122.385416144082598, 37.778697497765265],\n            [-122.385419276918626, 37.778616414297126],\n            [-122.385450703441506, 37.77869557161619],\n            [-122.38551290331219, 37.778691829944421],\n            [-122.385517765757882, 37.778610719084014],\n            [-122.38554746266594, 37.778689903767003],\n            [-122.385606220534413, 37.778686903820002],\n            [-122.385611082870923, 37.77860579268107],\n            [-122.385640779890451, 37.778684977889775],\n            [-122.385704709445832, 37.778681208176224],\n            [-122.385707841966195, 37.778600124700183],\n            [-122.385739268789678, 37.778679281942566],\n            [-122.385798008866331, 37.778675595460832],\n            [-122.385801141637501, 37.77859451225126],\n            [-122.385832568205146, 37.778673669199819],\n            [-122.385896498100962, 37.77866989992588],\n            [-122.385899630058546, 37.778588816175542],\n            [-122.385931057427598, 37.778667973361443],\n            [-122.385991527195401, 37.778664259112972],\n            [-122.385994659747908, 37.778583175623524],\n            [-122.386026086530904, 37.778662333069811],\n            [-122.386090016393183, 37.778658563141803],\n            [-122.386093148146045, 37.778577479660726],\n            [-122.386124575716508, 37.778656636795191],\n            [-122.386185062898718, 37.778653608892128],\n            [-122.386202015472293, 37.778571617834082],\n            [-122.386209244307707, 37.778651848558759],\n            [-122.386280092656236, 37.778647968384718],\n            [-122.386284954247373, 37.778566856942867],\n            [-122.386314651962735, 37.778646041707809],\n            [-122.386380311166334, 37.778642244226567],\n            [-122.386385172654656, 37.778561133055092],\n            [-122.386414870481516, 37.778640318069463],\n            [-122.386473610832795, 37.778636630970986],\n            [-122.386476742511945, 37.778555547473879],\n            [-122.386508170135954, 37.778634704511973],\n            [-122.386577288736902, 37.778630851563484],\n            [-122.386580420309613, 37.778549768338095],\n            [-122.386611848034704, 37.778628925074045],\n            [-122.386670588381151, 37.778625238426613],\n            [-122.386673719491867, 37.778544154655052],\n            [-122.386705147666916, 37.778623311635243],\n            [-122.386762158289017, 37.778619652349228],\n            [-122.386765307091096, 37.778539255288756],\n            [-122.386796717583763, 37.778617726080171],\n            [-122.386864106438878, 37.778613900371958],\n            [-122.386867237344845, 37.778532816869664],\n            [-122.386898665721333, 37.778611973798455],\n            [-122.386959152842579, 37.77860894577281],\n            [-122.386962284329485, 37.778527861982276],\n            [-122.38699369467254, 37.778606332452348],\n            [-122.387057641885832, 37.778603248718746],\n            [-122.387060772580114, 37.778522165211164],\n            [-122.387092183716675, 37.778600635643997],\n            [-122.38715094145212, 37.778597634927166],\n            [-122.387154334023649, 37.778526848079068],\n            [-122.387183771011991, 37.778595735958248],\n            [-122.38724942978078, 37.778591937998534],\n            [-122.387254551963991, 37.778521123457843],\n            [-122.387283989043027, 37.778590011312069],\n            [-122.387351378207768, 37.778586185868939],\n            [-122.387340432224974, 37.778359741993853],\n            [-122.385336224880277, 37.778480397809368],\n            [-122.385292215885698, 37.77845088576418],\n            [-122.385332752982109, 37.778411780987071],\n            [-122.385371486072913, 37.77843794377425],\n            [-122.387505436887736, 37.778315896876499],\n            [-122.387507690304943, 37.778336462786434],\n            [-122.387691125877438, 37.77833695908997],\n            [-122.387623881872372, 37.778210305157842],\n            [-122.390410335866818, 37.777142437885963],\n            [-122.390374260520773, 37.777113759158638],\n            [-122.390348522259259, 37.777093298197975],\n            [-122.389857561580598, 37.776499598895249],\n            [-122.389810930692391, 37.776503093591124],\n            [-122.389821814335647, 37.776590819837281],\n            [-122.38767762612953, 37.776716506820485],\n            [-122.387616143310936, 37.776748393866363],\n            [-122.387584974466847, 37.776747519757691],\n            [-122.387567625478937, 37.776745737093393],\n            [-122.387552006163176, 37.776743927007061],\n            [-122.387536369402838, 37.776741430474004],\n            [-122.387505026105131, 37.776733691622944],\n            [-122.387489337013818, 37.776729135749626],\n            [-122.387475360143554, 37.776723865736592],\n            [-122.387461365830632, 37.776717909277224],\n            [-122.387447354082425, 37.776711266646082],\n            [-122.387419295684069, 37.77669660766567],\n            [-122.387406978711525, 37.776688564173618],\n            [-122.387394643958672, 37.776679834515676],\n            [-122.387373399569938, 37.776660946088832],\n            [-122.387362760285328, 37.776650815560998],\n            [-122.38735385031967, 37.776640657346583],\n            [-122.387344922566982, 37.776629812692306],\n            [-122.387335978065309, 37.776618281581548],\n            [-122.387328762881694, 37.776606722784834],\n            [-122.387323277015341, 37.77659513630249],\n            [-122.387316044392662, 37.776582891060237],\n            [-122.387312271446064, 37.776570590705511],\n            [-122.387306768480713, 37.776558317497482],\n            [-122.387304707400645, 37.776545302738185],\n            [-122.387302663771393, 37.776532974698164],\n            [-122.387298541607819, 37.776506944904682],\n            [-122.387299939508821, 37.776493875044565],\n            [-122.387299608083069, 37.776480832320061],\n            [-122.387311076202096, 37.776319268586136],\n            [-122.387103587414643, 37.776325337046984],\n            [-122.387082386717438, 37.776103864569507],\n            [-122.385027425864848, 37.776335891081246],\n            [-122.385011788936296, 37.776333394493314],\n            [-122.384999507467228, 37.776326723359297],\n            [-122.384988868215274, 37.776316592896777],\n            [-122.385025875302759, 37.776274797468226],\n            [-122.384827030762366, 37.775394163410851],\n            [-122.384880527469605, 37.775388501004997],\n            [-122.384877317104468, 37.775330181090084],\n            [-122.386995863440688, 37.775082030997162],\n            [-122.386923061819843, 37.774327799469994],\n            [-122.381813822024597, 37.774615486663201],\n            [-122.381456516831207, 37.771917559332238],\n            [-122.384671101595274, 37.773340605158566],\n            [-122.384690336061794, 37.773348538332996],\n            [-122.384706059111224, 37.773354467747083],\n            [-122.38472349432422, 37.773359682786399],\n            [-122.384740912113955, 37.773364211103086],\n            [-122.384758312493929, 37.773368053246259],\n            [-122.384775695456668, 37.773371208941398],\n            [-122.384793061001673, 37.77337367818847],\n            [-122.384812120957065, 37.773374747168276],\n            [-122.384829452003999, 37.773375843238945],\n            [-122.384848494879705, 37.773376225487283],\n            [-122.38486577332894, 37.77337526249628],\n            [-122.386759568868584, 37.773272861257844],\n            [-122.386575835242937, 37.772851405934894],\n            [-122.38657206225551, 37.772839105279289],\n            [-122.386575190127743, 37.772826007459408],\n            [-122.386581812023351, 37.772814227444925],\n            [-122.386593692364443, 37.772805109622055],\n            [-122.386717419519911, 37.772771540111776],\n            [-122.386708370613761, 37.772755890279406],\n            [-122.386704597609267, 37.772743589627844],\n            [-122.386699094688865, 37.772731316663254],\n            [-122.386691548686684, 37.772706715359256],\n            [-122.38668746217553, 37.772682058686371],\n            [-122.386685401318488, 37.772669044181889],\n            [-122.386683357538772, 37.772656715579039],\n            [-122.386682694959006, 37.772630630932561],\n            [-122.386684111009814, 37.772618246955815],\n            [-122.386683779716421, 37.772605204495207],\n            [-122.386685195434609, 37.772592821073083],\n            [-122.386688340360166, 37.772580409425913],\n            [-122.386661929793348, 37.772494305123679],\n            [-122.386659886368264, 37.772481976514491],\n            [-122.386661302081109, 37.772469592817821],\n            [-122.38666965348915, 37.772457784842032],\n            [-122.386681551212874, 37.772449354004202],\n            [-122.386714186256512, 37.772439903990914],\n            [-122.386679336788333, 37.772362175448528],\n            [-122.386644902453284, 37.772368906802633],\n            [-122.386631976023693, 37.772336837636807],\n            [-122.386620901217455, 37.772309545906367],\n            [-122.386592865526183, 37.772227587668034],\n            [-122.386585249868389, 37.772200240573625],\n            [-122.386577616780784, 37.772172207033208],\n            [-122.386570001134302, 37.772144859937534],\n            [-122.386558193761417, 37.772088737774723],\n            [-122.386541497954923, 37.771976381892166],\n            [-122.386539053911875, 37.771948265304715],\n            [-122.386438565499603, 37.771943006477983],\n            [-122.386459543589496, 37.771678968587629],\n            [-122.386414488196351, 37.771676255706971],\n            [-122.386416883827636, 37.771634327179655],\n            [-122.386370064064607, 37.77163026961771],\n            [-122.386370799901215, 37.771591114275026],\n            [-122.386346586274058, 37.771591501747267],\n            [-122.386349295737674, 37.771561929239198],\n            [-122.386373509355195, 37.77156154176641],\n            [-122.386383018231356, 37.77145906765827],\n            [-122.386277185313745, 37.771447713434846],\n            [-122.386286481744634, 37.771404987761201],\n            [-122.386248187810011, 37.771395986654248],\n            [-122.386213474980906, 37.771391735029944],\n            [-122.386175916991888, 37.77134357828146],\n            [-122.386158586399219, 37.771342482408336],\n            [-122.386142916299207, 37.771338612516935],\n            [-122.386128940538697, 37.771333342339254],\n            [-122.386116624249169, 37.771325298710295],\n            [-122.38610598590013, 37.771315168059481],\n            [-122.386097059315517, 37.771304323294956],\n            [-122.386091556948031, 37.771292050293354],\n            [-122.386142815223778, 37.771266508342599],\n            [-122.38608534348306, 37.771183647247369],\n            [-122.386039082134474, 37.771201555805888],\n            [-122.386023428808656, 37.771198372355109],\n            [-122.386011095814837, 37.771189642258307],\n            [-122.386041843996964, 37.771174042639558],\n            [-122.38589549059256, 37.770997835524099],\n            [-122.385920430782591, 37.770821635056869],\n            [-122.385847920885212, 37.770691630725352],\n            [-122.385837299399952, 37.770682186505887],\n            [-122.385823306356514, 37.770676229845748],\n            [-122.385795755972097, 37.770681477661917],\n            [-122.3857856231634, 37.770691253905198],\n            [-122.385782477468226, 37.770703665268918],\n            [-122.385798079922523, 37.771250048587049],\n            [-122.385622255652152, 37.772162083932052],\n            [-122.385554576344688, 37.772154239361129],\n            [-122.385681891861481, 37.771444190077446],\n            [-122.385515420224039, 37.771429684819722],\n            [-122.38546780756441, 37.771530707856108],\n            [-122.385335917119292, 37.771651620630855],\n            [-122.385287036807085, 37.771634547559174],\n            [-122.385642517132254, 37.770847369439849],\n            [-122.385683664105002, 37.770764304300435],\n            [-122.385720236601344, 37.770637361956886],\n            [-122.385724274638122, 37.77059197382043],\n            [-122.385559320924514, 37.770637189122013],\n            [-122.383154992189901, 37.769708715839762],\n            [-122.383375582677616, 37.76933298733065],\n            [-122.385570199525418, 37.770179656618097],\n            [-122.385492733315886, 37.769990673321502],\n            [-122.38547497668678, 37.769836444052352],\n            [-122.38501929687861, 37.768856908704215],\n            [-122.384893636964136, 37.768882266758567],\n            [-122.384787561607197, 37.76865665734794],\n            [-122.38500549801104, 37.768585873156887],\n            [-122.385007528170945, 37.768529529274218],\n            [-122.385037840293677, 37.768496768478933],\n            [-122.385006697245259, 37.768428594043755],\n            [-122.38508052413836, 37.768406124957501],\n            [-122.385042550658142, 37.768341493386622],\n            [-122.38510771303342, 37.768318476117585],\n            [-122.385167686616882, 37.76829554179551],\n            [-122.385149073018809, 37.768175662662372],\n            [-122.385147029970554, 37.768163334292169],\n            [-122.385144812710706, 37.768144141461285],\n            [-122.38470084491486, 37.768171155877575],\n            [-122.384624005099198, 37.768074869544797],\n            [-122.383881223535184, 37.768121766230543],\n            [-122.383842264982363, 37.767745376829602],\n            [-122.382456893000523, 37.767832748125578],\n            [-122.382439473722869, 37.767691561099248],\n            [-122.384901003015941, 37.7675361686969],\n            [-122.384848518174209, 37.767018283287165],\n            [-122.384847918040649, 37.767012360130963],\n            [-122.382854528282635, 37.767138299649837],\n            [-122.382800567665527, 37.766784124750316],\n            [-122.385859942984411, 37.766599245791156],\n            [-122.385894343938588, 37.766523155929399],\n            [-122.385924379700455, 37.76641142564803],\n            [-122.385933483994975, 37.766361149056699],\n            [-122.385914237836445, 37.766284543788501],\n            [-122.385992644580284, 37.766169979928648],\n            [-122.386291877422522, 37.765689977677845],\n            [-122.386372587962043, 37.765598038596792],\n            [-122.386520103480208, 37.765411635088689],\n            [-122.386541125110512, 37.765285627921941],\n            [-122.386469993174757, 37.765277839225583],\n            [-122.386436744413672, 37.76505861928149],\n            [-122.386447302231559, 37.764929345689509],\n            [-122.386483096429387, 37.76490817106005],\n            [-122.386501478211272, 37.764814482226171],\n            [-122.386491269379732, 37.764616868735338],\n            [-122.38644793023208, 37.764613442288407],\n            [-122.386451155646682, 37.764399819097633],\n            [-122.386482284438713, 37.7643993208899],\n            [-122.386481482527714, 37.764367744084808],\n            [-122.386603169586195, 37.764322532741204],\n            [-122.386601548243974, 37.76425869323451],\n            [-122.386585931922852, 37.764256883010674],\n            [-122.386571940029569, 37.764250926432666],\n            [-122.38656131971895, 37.764241482262236],\n            [-122.38655581779787, 37.764229209267128],\n            [-122.38655550400469, 37.764216853507875],\n            [-122.386720358990686, 37.764100218114386],\n            [-122.386739211018067, 37.764025063281011],\n            [-122.386733691302794, 37.764012103852934],\n            [-122.38672476584992, 37.764001259116526],\n            [-122.386714128120815, 37.763991128513133],\n            [-122.38670010140504, 37.763983799331974],\n            [-122.386686126988522, 37.763978529213567],\n            [-122.386670492591179, 37.763976032291019],\n            [-122.386649740182264, 37.763976364463595],\n            [-122.386634036749314, 37.763971122014347],\n            [-122.386621721632579, 37.763963078702353],\n            [-122.386611066835059, 37.763952261637705],\n            [-122.386603852994298, 37.763940702770881],\n            [-122.386600062668251, 37.763927715381541],\n            [-122.386601461140231, 37.763914645215138],\n            [-122.386604606072254, 37.763902234094914],\n            [-122.386738907102767, 37.763808749639459],\n            [-122.386828889531799, 37.763741383598706],\n            [-122.386840716091143, 37.763730206671987],\n            [-122.386855983933643, 37.763718287929905],\n            [-122.386869539846316, 37.763707083316298],\n            [-122.386884824769794, 37.763695851022327],\n            [-122.386900145253236, 37.763685991607943],\n            [-122.38691721218359, 37.763676790958648],\n            [-122.386934296546428, 37.763668276753165],\n            [-122.386951398349183, 37.763660449266119],\n            [-122.386970246931185, 37.763653279988162],\n            [-122.386989112953756, 37.763646797428088],\n            [-122.387007996064526, 37.763641001316863],\n            [-122.387026897301666, 37.763635891637833],\n            [-122.387047562432002, 37.763632127441824],\n            [-122.387066515625051, 37.76362907682639],\n            [-122.387271786163467, 37.763605188503739],\n            [-122.387241692331358, 37.763510215504958],\n            [-122.386905339639924, 37.763549936808673],\n            [-122.386894896912324, 37.763479371256736],\n            [-122.386775626938885, 37.763415354890846],\n            [-122.386725141086345, 37.763471101073094],\n            [-122.386629276523749, 37.763443106355105],\n            [-122.386635775530237, 37.763426520918784],\n            [-122.386503316054672, 37.763388124513462],\n            [-122.386596764100844, 37.763184731253212],\n            [-122.386314395824243, 37.763102036396099],\n            [-122.386214308919619, 37.763316522997265],\n            [-122.385510514730285, 37.763329843761532],\n            [-122.385502904472929, 37.763234510683318],\n            [-122.385278141497096, 37.763240166132988],\n            [-122.385288368966343, 37.763370480347639],\n            [-122.38518118352205, 37.763373568175126],\n            [-122.385104258477085, 37.762319301701545],\n            [-122.384682038971576, 37.762111108737756],\n            [-122.384291646501694, 37.762134518628663],\n            [-122.384461265879253, 37.764047083641032],\n            [-122.384304084745608, 37.764057150214732],\n            [-122.384132119672074, 37.762256559028991],\n            [-122.383427389000644, 37.762300785113965],\n            [-122.383450606038608, 37.762465914870035],\n            [-122.383630300057192, 37.762456862777178],\n            [-122.3836690703355, 37.762553071423532],\n            [-122.383744969614881, 37.762544304446941],\n            [-122.383904907669475, 37.764143878058199],\n            [-122.383832343655854, 37.764147784454543],\n            [-122.383810360659879, 37.764236036587789],\n            [-122.383580701961776, 37.764253441592125],\n            [-122.383538646212543, 37.764164152408867],\n            [-122.383462623420698, 37.764168114122675],\n            [-122.383459954192517, 37.764131073630139],\n            [-122.383299279201026, 37.764139821513076],\n            [-122.383109557752064, 37.762320284660944],\n            [-122.382290828789408, 37.762371818989514],\n            [-122.38251353675102, 37.764606298459128],\n            [-122.382344144468703, 37.764612437347246],\n            [-122.382121545934865, 37.762382076243206],\n            [-122.38174844490716, 37.762405201559091],\n            [-122.381695087947634, 37.764738170521944],\n            [-122.381631135065035, 37.764740565182315],\n            [-122.381630936164214, 37.764801000118787],\n            [-122.381492672008349, 37.764806640579245],\n            [-122.381450941763589, 37.76493503772344],\n            [-122.381172352882004, 37.76493330344676],\n            [-122.381123981911756, 37.764799477425129],\n            [-122.380983831900423, 37.764798967139676],\n            [-122.381029346103958, 37.762223710202747],\n            [-122.38330764917707, 37.762081573209855],\n            [-122.382185556454559, 37.760879873475361],\n            [-122.382168402620636, 37.760885641207267],\n            [-122.382152908506868, 37.760888635257665],\n            [-122.382137362223574, 37.760889570238476],\n            [-122.382121780810863, 37.760889132328003],\n            [-122.38210441862546, 37.760886662951151],\n            [-122.382090445117498, 37.760881392285128],\n            [-122.382076454213362, 37.760875434895482],\n            [-122.382064140442054, 37.760867390822959],\n            [-122.382053521188098, 37.760857946240883],\n            [-122.382044596105573, 37.76084710115525],\n            [-122.382023253267107, 37.760824093302155],\n            [-122.382014294110562, 37.760811875582888],\n            [-122.382005317206719, 37.760798970871889],\n            [-122.381998069607818, 37.76078603882209],\n            [-122.381986997879679, 37.76075874660679],\n            [-122.381983174441103, 37.760744386430567],\n            [-122.381981079958692, 37.760729998921875],\n            [-122.381980366958516, 37.760701854577775],\n            [-122.381981731049137, 37.76068741129496],\n            [-122.381984824092612, 37.760672940679711],\n            [-122.38198793521687, 37.760659156500736],\n            [-122.381992775285752, 37.760645344714604],\n            [-122.381695267558513, 37.760647347915544],\n            [-122.381838070964363, 37.762118779804872],\n            [-122.381335375976406, 37.762148093200558],\n            [-122.381148296266858, 37.760157516712624],\n            [-122.380611046370689, 37.760188065470565],\n            [-122.380912187127535, 37.763336013717719],\n            [-122.380701275367471, 37.763342126118935],\n            [-122.380400282533174, 37.760199669192616],\n            [-122.379507164112297, 37.760250310896886],\n            [-122.379808005517475, 37.763456637815253],\n            [-122.379779318273378, 37.763485250755494],\n            [-122.379565016753276, 37.763494162073435],\n            [-122.379533176547326, 37.763466514378344],\n            [-122.379201704975202, 37.759937914686475],\n            [-122.381256986630419, 37.759667520373661],\n            [-122.381256577752055, 37.759583059710486],\n            [-122.381469121143184, 37.759573486743299],\n            [-122.381446760180623, 37.75930533388199],\n            [-122.381389798546991, 37.759310363785765],\n            [-122.381303248927566, 37.759308311423226],\n            [-122.381202252005025, 37.758325846274602],\n            [-122.381200105587652, 37.758309399127988],\n            [-122.381197941449869, 37.758292265813587],\n            [-122.381197089710227, 37.758258629870305],\n            [-122.381199679398975, 37.758224251997007],\n            [-122.381200991455458, 37.758207749648136],\n            [-122.38120401536365, 37.75819053325035],\n            [-122.381207056652954, 37.758174003300283],\n            [-122.381221367866601, 37.758124330651064],\n            [-122.381234402126012, 37.75809253323947],\n            [-122.381242648836448, 37.758076607200529],\n            [-122.381262669528837, 37.75804744489573],\n            [-122.381289433303067, 37.758011307974613],\n            [-122.381302797797105, 37.757992553063836],\n            [-122.381314432708805, 37.757973825759706],\n            [-122.381327797196747, 37.757955071120548],\n            [-122.381337703552219, 37.757936371131194],\n            [-122.381360938912749, 37.75789754361324],\n            [-122.381380716140868, 37.757858771292213],\n            [-122.381390587715856, 37.757838698951588],\n            [-122.381398729699725, 37.757818653670306],\n            [-122.381408600911726, 37.757798581058942],\n            [-122.381416742893265, 37.757778536050878],\n            [-122.381423155645805, 37.757758518646433],\n            [-122.381431297626008, 37.7577384739118],\n            [-122.381444088336707, 37.757697065930351],\n            [-122.381450501074838, 37.757677048524144],\n            [-122.381464499104396, 37.757615019779642],\n            [-122.381469148185403, 37.757593657075738],\n            [-122.381475021977749, 37.757552359783979],\n            [-122.381477941486636, 37.757531024690024],\n            [-122.381480843608969, 37.757509003148037],\n            [-122.381427480867771, 37.75751946944429],\n            [-122.381405721286413, 37.757343328372727],\n            [-122.381468641764485, 37.757300433411309],\n            [-122.381446638421224, 37.75711468207534],\n            [-122.381323864533158, 37.757116641937891],\n            [-122.381323169199888, 37.757089184020202],\n            [-122.381364426846005, 37.75707891127486],\n            [-122.381321992818584, 37.756564544822638],\n            [-122.381321662531036, 37.756551502035926],\n            [-122.38132307883005, 37.756539118369027],\n            [-122.381326206602509, 37.756526020931318],\n            [-122.381331099013835, 37.756514268226432],\n            [-122.381339449129754, 37.756502460600949],\n            [-122.381347834701543, 37.756492025859821],\n            [-122.381357966510507, 37.756482250242897],\n            [-122.381369844881263, 37.756473132920476],\n            [-122.381383504601786, 37.756466047611781],\n            [-122.381397181710526, 37.756459649024094],\n            [-122.381399266420004, 37.75640536415446],\n            [-122.381383669266881, 37.756404239961626],\n            [-122.381368019603769, 37.756401055879024],\n            [-122.381354081758502, 37.756397158017677],\n            [-122.381338379955181, 37.756391914861666],\n            [-122.381324389963666, 37.756385957652711],\n            [-122.38129976407464, 37.756369869336808],\n            [-122.381289145560629, 37.756360424678547],\n            [-122.381280238512659, 37.756350265974667],\n            [-122.381265813968113, 37.756327147557755],\n            [-122.381260331236177, 37.756315560741236],\n            [-122.381258271437829, 37.756302546110341],\n            [-122.381256229700497, 37.756290217367201],\n            [-122.381255916815178, 37.756277861302344],\n            [-122.381240855709805, 37.755888041607285],\n            [-122.381237067430561, 37.75587505429101],\n            [-122.381229855216972, 37.755863495353786],\n            [-122.381219219049328, 37.755853363971241],\n            [-122.381205211793798, 37.755846720574091],\n            [-122.381189545219783, 37.75584285001387],\n            [-122.381023178934683, 37.755831084166076],\n            [-122.381005052890941, 37.755798410637965],\n            [-122.380939344081298, 37.755799459348452],\n            [-122.380938179657051, 37.755753467322073],\n            [-122.381031311951133, 37.755742366765169],\n            [-122.381010248974547, 37.755730341878809],\n            [-122.38098743910173, 37.755717658143908],\n            [-122.380924145958886, 37.755677464760758],\n            [-122.380881915817497, 37.755649296531168],\n            [-122.380862530269653, 37.755635184397235],\n            [-122.380841397836633, 37.755620413687737],\n            [-122.380821994593731, 37.755605615653565],\n            [-122.380802573973469, 37.755590130893374],\n            [-122.38078488252836, 37.755574618260482],\n            [-122.380758354080839, 37.755551693216646],\n            [-122.380751124581494, 37.755539447252119],\n            [-122.380745589509232, 37.755525801070959],\n            [-122.38074007147074, 37.755512841343261],\n            [-122.380736283298162, 37.75549985401004],\n            [-122.38073074823852, 37.755486208102681],\n            [-122.380728671510397, 37.755472506457778],\n            [-122.380724865279745, 37.755458832961516],\n            [-122.380718635462969, 37.755417728569704],\n            [-122.380718270538722, 37.755403313156876],\n            [-122.380719652161702, 37.755389556598288],\n            [-122.380719304614729, 37.755375827633614],\n            [-122.380722067512963, 37.755348314521783],\n            [-122.380728289091294, 37.755320746216135],\n            [-122.380731417602021, 37.755307648505891],\n            [-122.380736257562816, 37.755293837033165],\n            [-122.38074597221329, 37.755267586159846],\n            [-122.380759145196464, 37.755241280371166],\n            [-122.380767425749198, 37.755226726990642],\n            [-122.380777557807846, 37.755216951415953],\n            [-122.380789436047536, 37.755207834150042],\n            [-122.3808030608416, 37.755199376285184],\n            [-122.380814973485883, 37.755191631918663],\n            [-122.380828633023739, 37.75518454667273],\n            [-122.380844056826504, 37.755178806720394],\n            [-122.380857751116352, 37.755173094367549],\n            [-122.380873209326097, 37.755168727313375],\n            [-122.380888684919853, 37.755165046980004],\n            [-122.380905907041537, 37.755162024947012],\n            [-122.380921434762783, 37.75516040367944],\n            [-122.380952524623297, 37.755158534314724],\n            [-122.380976698502963, 37.755156774772679],\n            [-122.380999125498533, 37.755154356655588],\n            [-122.381021534766717, 37.755151252091551],\n            [-122.381043927344422, 37.755147461063977],\n            [-122.381066302193489, 37.755142983589487],\n            [-122.38108865965917, 37.755137819662529],\n            [-122.38111099939502, 37.755131969288627],\n            [-122.38113332243789, 37.755125432451251],\n            [-122.381153915967687, 37.755118923215505],\n            [-122.381176203900949, 37.755111013753655],\n            [-122.38119676265265, 37.755103131339425],\n            [-122.381225828164943, 37.755089619604064],\n            [-122.381246943141264, 37.755103703797808],\n            [-122.381242963358787, 37.755083165829973],\n            [-122.38126015068255, 37.755078770848776],\n            [-122.381277355393678, 37.755075062587856],\n            [-122.381292865706087, 37.755072754823154],\n            [-122.38131010484075, 37.755070419733926],\n            [-122.381327362035009, 37.75506877053013],\n            [-122.381361945276268, 37.755068218467898],\n            [-122.381396598051168, 37.755070412188203],\n            [-122.381413958860932, 37.755072881946376],\n            [-122.381448715950555, 37.755079194614936],\n            [-122.381480084590436, 37.755088307721437],\n            [-122.381497532329476, 37.755094209708098],\n            [-122.381511539483228, 37.755100853620512],\n            [-122.381527276134307, 37.755107469370813],\n            [-122.381548321633034, 37.755118807717949],\n            [-122.381562363566104, 37.755126824520609],\n            [-122.381579829044853, 37.755133412663042],\n            [-122.381595565024199, 37.755140028689837],\n            [-122.381612996090695, 37.755145244200165],\n            [-122.381630391696547, 37.755149086822691],\n            [-122.381647770609987, 37.755152242983492],\n            [-122.381665131447221, 37.755154712704652],\n            [-122.381684187368847, 37.755155781907895],\n            [-122.38170149639484, 37.755156192273823],\n            [-122.382833235564902, 37.755035795269471],\n            [-122.383668861276263, 37.754971624407645],\n            [-122.383685978819074, 37.754964483555838],\n            [-122.38372028351472, 37.754952947636127],\n            [-122.383737470668777, 37.75494855256818],\n            [-122.383756404038564, 37.75494481631263],\n            [-122.383773626345388, 37.754941794129564],\n            [-122.383792594868737, 37.754939430758334],\n            [-122.383811580457817, 37.754937754111957],\n            [-122.38384268824322, 37.754936570147059],\n            [-122.383861726388119, 37.754936952556008],\n            [-122.383880781586896, 37.754938021140639],\n            [-122.383896396575224, 37.754939831714687],\n            [-122.384073136269635, 37.754951427650568],\n            [-122.384099862744918, 37.754913917176538],\n            [-122.384076095308828, 37.754658835300546],\n            [-122.383998950777894, 37.754618177983438],\n            [-122.383899356302948, 37.754647239093948],\n            [-122.383797997440993, 37.754674954869053],\n            [-122.383696604385719, 37.754701297650087],\n            [-122.383569360630759, 37.75473148712782],\n            [-122.383548680403266, 37.754734564546567],\n            [-122.3835279827709, 37.754736955513799],\n            [-122.383505538587656, 37.754738687938882],\n            [-122.38348480614728, 37.754739706002738],\n            [-122.383441577306456, 37.754740396857535],\n            [-122.383400008003704, 37.754738313997002],\n            [-122.38337920595491, 37.754736586250608],\n            [-122.383356657006146, 37.754734199690795],\n            [-122.383335820163737, 37.754731099315492],\n            [-122.383314965907985, 37.754727311939398],\n            [-122.383295841160447, 37.754723497196949],\n            [-122.383254063070865, 37.754713177184357],\n            [-122.383234885782983, 37.75470730309371],\n            [-122.383213961945074, 37.754700770182538],\n            [-122.383194767270453, 37.754694209911989],\n            [-122.383175537439726, 37.754686276198775],\n            [-122.383158037470992, 37.754678315390812],\n            [-122.383138807994129, 37.754670381666052],\n            [-122.383103737780345, 37.754651713981168],\n            [-122.383086185277548, 37.75464169354953],\n            [-122.383068615380594, 37.754630986667287],\n            [-122.383052774639708, 37.754620252154247],\n            [-122.383036916504807, 37.754608831191064],\n            [-122.383015731729387, 37.754592001250259],\n            [-122.38299259130558, 37.754566275663684],\n            [-122.382971180378888, 37.754540521890775],\n            [-122.382949752076243, 37.754514081940449],\n            [-122.382930053282664, 37.754487614354218],\n            [-122.382908625009904, 37.754461174396191],\n            [-122.382888908501968, 37.754434020360335],\n            [-122.382870920808742, 37.754406838701115],\n            [-122.382842850708954, 37.754363390808408],\n            [-122.382816907001839, 37.754323234339928],\n            [-122.382797016608123, 37.754289216082142],\n            [-122.382791480900465, 37.754275569725095],\n            [-122.382785928504248, 37.754261237457797],\n            [-122.38278210489284, 37.754246877022375],\n            [-122.382781722177867, 37.754231775164968],\n            [-122.382784449823419, 37.754202889097733],\n            [-122.382789271581657, 37.754188390821014],\n            [-122.382795840220822, 37.754174551362205],\n            [-122.382804155740061, 37.754161370721022],\n            [-122.382814217447034, 37.754148848908173],\n            [-122.382826043083483, 37.754137672365715],\n            [-122.382839615252593, 37.75412715464509],\n            [-122.382853204821586, 37.754117323645531],\n            [-122.382868575702929, 37.754109523814002],\n            [-122.382885693124038, 37.754102383077345],\n            [-122.382904591526582, 37.754097274061955],\n            [-122.382896892098685, 37.754066494402807],\n            [-122.382890922160428, 37.754035687111681],\n            [-122.382884969278336, 37.754005566273698],\n            [-122.382876487005475, 37.753943896449059],\n            [-122.382873975008934, 37.75391303391072],\n            [-122.382869733880582, 37.753882198997694],\n            [-122.382868951022118, 37.753851308833418],\n            [-122.382866421635015, 37.753819759846344],\n            [-122.382865638778583, 37.753788869681728],\n            [-122.38286753098933, 37.753727034381221],\n            [-122.382869667430043, 37.75367480879347],\n            [-122.382867485984363, 37.753656988767865],\n            [-122.382865286797127, 37.753638482299571],\n            [-122.382861376571569, 37.753620689893637],\n            [-122.382850096841622, 37.753585160622798],\n            [-122.382842745420064, 37.753568109920103],\n            [-122.382833665214591, 37.753551086561764],\n            [-122.382822855548582, 37.753534091107603],\n            [-122.382812063283737, 37.753517782100509],\n            [-122.382801288419827, 37.753502159540567],\n            [-122.382788801838672, 37.753487251326646],\n            [-122.382774586124242, 37.75347237018628],\n            [-122.382742765715449, 37.753445409495008],\n            [-122.382726873440859, 37.753432615860532],\n            [-122.382709269084074, 37.753420535751609],\n            [-122.382691682135089, 37.753409142362763],\n            [-122.382672383117907, 37.753398463047603],\n            [-122.382653119587403, 37.753389156614546],\n            [-122.382611168393765, 37.753371971889877],\n            [-122.382590227592559, 37.7533647524183],\n            [-122.382569321590282, 37.753358906113988],\n            [-122.382546703510585, 37.753353773605845],\n            [-122.382524103512239, 37.753349327255975],\n            [-122.382501520571083, 37.75334556763007],\n            [-122.382478972425403, 37.753343181170841],\n            [-122.382456441314361, 37.753341480611873],\n            [-122.382433945688362, 37.753341153208574],\n            [-122.382402821501856, 37.753341650348268],\n            [-122.38237864858533, 37.753343409903252],\n            [-122.382356222188207, 37.753345828558139],\n            [-122.382297501266095, 37.753349513077993],\n            [-122.38226810654271, 37.753349982559506],\n            [-122.382240423203967, 37.753349737974666],\n            [-122.382210993352999, 37.753348834550927],\n            [-122.382181546112378, 37.753347244671602],\n            [-122.382152081137065, 37.753344968342162],\n            [-122.382124328241176, 37.753341977937026],\n            [-122.382094811792641, 37.753337642237426],\n            [-122.38206700673382, 37.753332592473967],\n            [-122.382039184289724, 37.753326856255676],\n            [-122.38201134446112, 37.753320433582523],\n            [-122.38200001736864, 37.75331754288009],\n            [-122.381983487255724, 37.753313324729064],\n            [-122.381957341428489, 37.753305501265011],\n            [-122.3819294494578, 37.75329701950217],\n            [-122.38187705455077, 37.753277254112369],\n            [-122.381850839550978, 37.753266685100435],\n            [-122.3818017982653, 37.753242746046119],\n            [-122.381777242861062, 37.753229403614625],\n            [-122.381754416238635, 37.753216033574361],\n            [-122.381731572936843, 37.753201977344837],\n            [-122.381708677132991, 37.753185861496782],\n            [-122.381985040410456, 37.753169774197133],\n            [-122.382560765120331, 37.753136419940226],\n            [-122.382560989570067, 37.75313671714089],\n            [-122.382951309683904, 37.753114103639163],\n            [-122.383438933023925, 37.753085850281579],\n            [-122.383914427464433, 37.753058600498669],\n            [-122.384879974685347, 37.753002645165687],\n            [-122.385845520438053, 37.752946681373402],\n            [-122.385845731512404, 37.752946669205699],\n            [-122.386811064397406, 37.752890709951139],\n            [-122.386811676199414, 37.752890674609837],\n            [-122.387755875750031, 37.752835749697695],\n            [-122.387756291326852, 37.752835725460692],\n            [-122.387756280477618, 37.752835611637821],\n            [-122.387633974640565, 37.751559603455405],\n            [-122.387585270262662, 37.750263378063039],\n            [-122.388507729357968, 37.750205975353602],\n            [-122.389478017653872, 37.750145107673561],\n            [-122.390433038183829, 37.750118341633915],\n            [-122.391418367221192, 37.750140606282606],\n            [-122.391693805117924, 37.750111668488358],\n            [-122.391705979739527, 37.750110389401705],\n            [-122.391714686151033, 37.750109474739723],\n            [-122.391929934247997, 37.750086859865874],\n            [-122.392350740047519, 37.750026024134087],\n            [-122.392354048972692, 37.750025545503377],\n            [-122.392381992909165, 37.750021506018193],\n            [-122.393314555211816, 37.749918012051069],\n            [-122.393322792145199, 37.749917097944952],\n            [-122.393340265733656, 37.749915158351328],\n            [-122.395231400136637, 37.749808114846253],\n            [-122.395307862010839, 37.749803786070743],\n            [-122.396273544250747, 37.74974560058007],\n            [-122.397285415820363, 37.749684622841229],\n            [-122.40206163398004, 37.749396678541473],\n            [-122.402063015501824, 37.749396333205695],\n            [-122.402215286387033, 37.749358244852793],\n            [-122.402746245055127, 37.749375020002113],\n            [-122.402866303074774, 37.749478453789486],\n            [-122.402866309087059, 37.749478458911533],\n            [-122.402968925612569, 37.749550036933734],\n            [-122.403054692816028, 37.74954427306843],\n            [-122.403203674195439, 37.749534260068266],\n            [-122.403260968692152, 37.749530409553117],\n            [-122.403568849778438, 37.749475672986911],\n            [-122.403592456970202, 37.749471475909672],\n            [-122.403667613571599, 37.749458113882532],\n            [-122.403783562755194, 37.749432891426196],\n            [-122.403735650894049, 37.749583872707518],\n            [-122.403514246938869, 37.750281563827826],\n            [-122.403378511001179, 37.750679445903444],\n            [-122.403214572776321, 37.751164169122518],\n            [-122.403175027775376, 37.751281093014263],\n            [-122.403072643662085, 37.751794247946641],\n            [-122.403060331379024, 37.751915861053995],\n            [-122.403006670294943, 37.752445905732571],\n            [-122.402978169178084, 37.75275045704997],\n            [-122.40301031565609, 37.753201877425177],\n            [-122.403011715723153, 37.753221541143297],\n            [-122.403127143734139, 37.75447773377249],\n            [-122.403244936870763, 37.755759623462495],\n            [-122.403245024221292, 37.755760575231484],\n            [-122.403305530792395, 37.756165877853867],\n            [-122.403395700993769, 37.756471262485057],\n            [-122.403543859417937, 37.75680412126377],\n            [-122.403689260967766, 37.757015002579273],\n            [-122.403774084903432, 37.757138026202746],\n            [-122.403774405770974, 37.757138490742172],\n            [-122.404168659910013, 37.75752977302043],\n            [-122.404441730762329, 37.757748221369937],\n            [-122.40472107938929, 37.757950649275713],\n            [-122.404835145723425, 37.7580339263735],\n            [-122.405048169961901, 37.758189448369819],\n            [-122.405545221503274, 37.758536929330724],\n            [-122.405903135660481, 37.758934413706449],\n            [-122.406103779268008, 37.759196479511928],\n            [-122.406247868764623, 37.759429354319998],\n            [-122.406261953936678, 37.75945211819198],\n            [-122.406389357641601, 37.759804405761585],\n            [-122.406492391998796, 37.760255279712112],\n            [-122.406492697683589, 37.760686469836571],\n            [-122.406479479731502, 37.760730932047331],\n            [-122.4062826701159, 37.761392945250627],\n            [-122.406147061407296, 37.7616371808829],\n            [-122.406021608630979, 37.761863123291867],\n            [-122.405925510731336, 37.762011914887523],\n            [-122.405653838302513, 37.762432552128487],\n            [-122.405384490436404, 37.76297005284507],\n            [-122.40532469627189, 37.76313750682079],\n            [-122.405258413683796, 37.763323129887908],\n            [-122.405203088479681, 37.763478066930332],\n            [-122.405104592203571, 37.763852244172895],\n            [-122.405059622300016, 37.764310557738277],\n            [-122.405089602982216, 37.764628538373131],\n            [-122.404841911264157, 37.764643492872132],\n            [-122.404496977488066, 37.764664317561468],\n            [-122.403527034519726, 37.764722870371436],\n            [-122.402563396844656, 37.764781034314389],\n            [-122.401598999326723, 37.764839236400867],\n            [-122.401604204317508, 37.764894634517518],\n            [-122.401720515816834, 37.766132520250999],\n            [-122.40075504647362, 37.766190486570189],\n            [-122.399760960198876, 37.766250252443854],\n            [-122.39980174760619, 37.766587745781216],\n            [-122.399677637243116, 37.766695444602661],\n            [-122.4004285874814, 37.76730030349627],\n            [-122.400680751363893, 37.767503407870436],\n            [-122.400877239583821, 37.76748837440357],\n            [-122.401842503677628, 37.76743077711658],\n            [-122.401962350744, 37.768706209224241],\n            [-122.402926809989424, 37.76864812762463],\n            [-122.403037758616776, 37.769828657390953],\n            [-122.403406518421917, 37.769807099957397],\n            [-122.403615112128733, 37.769893190404822],\n            [-122.403877699904385, 37.770062866265505],\n            [-122.403531054507056, 37.770336670466769],\n            [-122.401656665936628, 37.77181711392474],\n            [-122.399647849152032, 37.773403860176856],\n            [-122.399516440682405, 37.773507653276255],\n            [-122.399433130909728, 37.773573455617395],\n            [-122.400212340411557, 37.77419510487973],\n            [-122.400592319049849, 37.774498244536282],\n            [-122.400981567656032, 37.774808775969753],\n            [-122.402524096584528, 37.776039321403807],\n            [-122.40337073826143, 37.776714700215486],\n            [-122.403505778866929, 37.776822422431877],\n            [-122.403673439982725, 37.776956165214415],\n            [-122.404070256642925, 37.777272702482797],\n            [-122.404605505853894, 37.777699659794813],\n            [-122.405068508898097, 37.778068981419388],\n            [-122.405615266425855, 37.778505104539292],\n            [-122.404431250807633, 37.779440334605447],\n            [-122.403387209275081, 37.78026497235011],\n            [-122.401159718585049, 37.782024266952142]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 2, ZIP_CODE: 94105, ID: 94105},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.39249932896719, 37.793768814133983],\n            [-122.391890260341384, 37.794278544568918],\n            [-122.391788865572423, 37.794170982455135],\n            [-122.39173429034625, 37.79420276052317],\n            [-122.391666728649753, 37.794132425256194],\n            [-122.391723034266192, 37.79410061945832],\n            [-122.391673228351905, 37.794047854124599],\n            [-122.391982015107928, 37.793871906128679],\n            [-122.391589022782, 37.793527982873798],\n            [-122.391723231555744, 37.793429001692992],\n            [-122.390805927608938, 37.792657675413892],\n            [-122.388808599369057, 37.790978116182679],\n            [-122.388930714411984, 37.790880018842998],\n            [-122.388611692601799, 37.790313089306778],\n            [-122.388048971491713, 37.790507516256234],\n            [-122.388012417064502, 37.790431188672287],\n            [-122.388369268465311, 37.790308044637399],\n            [-122.388285919009036, 37.790161047422416],\n            [-122.388320274159014, 37.790150883121463],\n            [-122.388243932142601, 37.790007207265845],\n            [-122.388219415789919, 37.789995925591761],\n            [-122.388076857804336, 37.790038725053051],\n            [-122.38779803320179, 37.790149885537438],\n            [-122.385731305877229, 37.790973806479329],\n            [-122.385715648397209, 37.790970622998962],\n            [-122.385703294724394, 37.790961206457723],\n            [-122.385509561738004, 37.790556391724344],\n            [-122.385507518014222, 37.790544063409705],\n            [-122.385514141658973, 37.790532283492631],\n            [-122.385686917933171, 37.79045260764569],\n            [-122.387606646474296, 37.789787359176458],\n            [-122.387348795424046, 37.789311467853956],\n            [-122.38734757309517, 37.78931176210844],\n            [-122.385363735345749, 37.789788908162748],\n            [-122.385346470166525, 37.789790557672781],\n            [-122.385330865935444, 37.789789433734306],\n            [-122.3853151742195, 37.789784877582385],\n            [-122.385301142770246, 37.789777548268411],\n            [-122.385290501190326, 37.789767417313975],\n            [-122.385283285054584, 37.789755858694043],\n            [-122.385108047186435, 37.789329458915937],\n            [-122.385106003217075, 37.789317130597112],\n            [-122.385109132387825, 37.789304032849302],\n            [-122.385115773404124, 37.789292939120834],\n            [-122.385127656568173, 37.78928382174719],\n            [-122.385141339390941, 37.789277422487608],\n            [-122.385148542800621, 37.789220309385662],\n            [-122.387188020139192, 37.788773590697055],\n            [-122.387049561519532, 37.788228488916552],\n            [-122.385265755459955, 37.788520039162925],\n            [-122.385250134063114, 37.78851822849537],\n            [-122.385234425193858, 37.78851298588809],\n            [-122.385222089078539, 37.788504256015905],\n            [-122.385214890495845, 37.788493383558084],\n            [-122.385126670096767, 37.788154179856562],\n            [-122.385126356426341, 37.788141823596902],\n            [-122.385129502623784, 37.788129412294914],\n            [-122.385137873116122, 37.788118290909864],\n            [-122.385149773868434, 37.788109860244717],\n            [-122.385165203482913, 37.788104119771369],\n            [-122.38712311120571, 37.787785749180493],\n            [-122.387152415559953, 37.787781160094404],\n            [-122.38716964498991, 37.787778137163613],\n            [-122.38718858690207, 37.78777440037284],\n            [-122.387205798545097, 37.787770691273494],\n            [-122.387224740799539, 37.787766954471365],\n            [-122.387241935339802, 37.787762558917869],\n            [-122.387260859452994, 37.787758135677656],\n            [-122.387278054335084, 37.787753740113331],\n            [-122.387312408509445, 37.787743576101576],\n            [-122.387329567801103, 37.787737807654125],\n            [-122.387346745228243, 37.787732725636424],\n            [-122.387363887068403, 37.787726270740649],\n            [-122.38738104704403, 37.787720502274603],\n            [-122.38740694457185, 37.787241442038656],\n            [-122.387019444956891, 37.787247644385083],\n            [-122.384558944804354, 37.787400993480382],\n            [-122.384543323667074, 37.787399182993411],\n            [-122.384527632511421, 37.787394627009306],\n            [-122.384515296650974, 37.787385896512234],\n            [-122.384504655873897, 37.787375765751548],\n            [-122.384499135159118, 37.787362806267062],\n            [-122.384342210870813, 37.785748083262355],\n            [-122.384341897348037, 37.785735727269817],\n            [-122.384346773449252, 37.785723288336335],\n            [-122.38435689108006, 37.785712825798925],\n            [-122.384370521056837, 37.785704367292539],\n            [-122.384385968349036, 37.785699313626168],\n            [-122.387217967726542, 37.785525599931667],\n            [-122.387242186237785, 37.785525212275893],\n            [-122.387269846695702, 37.785524082797316],\n            [-122.387295742393547, 37.78552160811531],\n            [-122.387323367958444, 37.785519105736952],\n            [-122.387349245861117, 37.785515944605116],\n            [-122.387375089561743, 37.785511410569285],\n            [-122.387402662781668, 37.785506848841521],\n            [-122.387454279688384, 37.785495034977437],\n            [-122.387478323373855, 37.785487782842686],\n            [-122.387504079475306, 37.785479816566138],\n            [-122.387528105357717, 37.785471877983142],\n            [-122.387553826899222, 37.785462538802953],\n            [-122.387580894103834, 37.785438070169313],\n            [-122.387630346985745, 37.78486455148672],\n            [-122.385458308223278, 37.78499338709468],\n            [-122.385413881876744, 37.784606785946664],\n            [-122.387221366105805, 37.784501641660896],\n            [-122.387418079949683, 37.784479264504995],\n            [-122.38743876829875, 37.784476186423468],\n            [-122.387483534893533, 37.784467229088612],\n            [-122.387528231689217, 37.784455525961853],\n            [-122.387550562288524, 37.78444898795393],\n            [-122.387571145938807, 37.78444179118685],\n            [-122.387593442333781, 37.784433880546885],\n            [-122.387613991073025, 37.784425310610295],\n            [-122.38765505365474, 37.784406798388098],\n            [-122.387673819852623, 37.78439619708557],\n            [-122.387677823231684, 37.784349435820396],\n            [-122.387709046749492, 37.783875783688671],\n            [-122.386286930602978, 37.783958976243902],\n            [-122.386236533710786, 37.783405597391891],\n            [-122.387525601081578, 37.78333071590594],\n            [-122.387695596747974, 37.783278550154108],\n            [-122.387751878035047, 37.783246059476667],\n            [-122.387776877347846, 37.78286796141245],\n            [-122.3875502225155, 37.782801544578085],\n            [-122.387525883466154, 37.782797127453357],\n            [-122.387484228568439, 37.78279230054234],\n            [-122.387463418560699, 37.782790573250665],\n            [-122.387440913647538, 37.782790246810194],\n            [-122.387420138539767, 37.78278989267335],\n            [-122.387388984601131, 37.782789704683182],\n            [-122.387368261829351, 37.782791409869013],\n            [-122.387347521611616, 37.782792428607252],\n            [-122.384758972173714, 37.782940280456494],\n            [-122.384743316428853, 37.782937096842417],\n            [-122.384732711445736, 37.782928338977264],\n            [-122.384679972094801, 37.782554918115103],\n            [-122.384681388329753, 37.782542534464426],\n            [-122.384691505480149, 37.782532071892298],\n            [-122.384705187096728, 37.782525672675533],\n            [-122.387509249137921, 37.78234690330077],\n            [-122.387528224534123, 37.782344539065186],\n            [-122.387554136561661, 37.78234275076899],\n            [-122.38762294594062, 37.782322839482177],\n            [-122.387787765874378, 37.782275144853472],\n            [-122.387765464889313, 37.78194244138043],\n            [-122.388106738902522, 37.781931906493995],\n            [-122.388231340039766, 37.782146697467269],\n            [-122.388304301898813, 37.782356717503951],\n            [-122.388306939892615, 37.782385221520776],\n            [-122.389843136047602, 37.783611740842574],\n            [-122.389846392025618, 37.783609176617361],\n            [-122.390856721023212, 37.784410569188324],\n            [-122.391599105853345, 37.783822668268179],\n            [-122.391600764141245, 37.783823989838545],\n            [-122.392032919786118, 37.784168354830349],\n            [-122.392118766536498, 37.784205306785267],\n            [-122.392215026316663, 37.784279352365694],\n            [-122.392216553530702, 37.784278123341721],\n            [-122.39285700396303, 37.784773072758867],\n            [-122.39286959393705, 37.78478280203197],\n            [-122.39343711789931, 37.784459123881106],\n            [-122.394398389309941, 37.783701667981575],\n            [-122.395160622349934, 37.78431101230386],\n            [-122.395895701657864, 37.784896929203114],\n            [-122.396705177550587, 37.785542130425938],\n            [-122.397811613142864, 37.784666586003652],\n            [-122.399369533643153, 37.785899247741973],\n            [-122.400468483886854, 37.785030285141588],\n            [-122.400994549885382, 37.785441790992316],\n            [-122.400998469557948, 37.785444857061968],\n            [-122.401480059865179, 37.785821567351917],\n            [-122.401489835489045, 37.785829214187501],\n            [-122.402025419766176, 37.786248152290575],\n            [-122.402575227394536, 37.786678208153539],\n            [-122.403028193190352, 37.787024418822838],\n            [-122.403028734856406, 37.78702485040828],\n            [-122.403342641535644, 37.787274627660381],\n            [-122.403430800369534, 37.787641848645734],\n            [-122.403239953883428, 37.787802352067843],\n            [-122.40290309900054, 37.788085647297414],\n            [-122.402903089488873, 37.788085654318181],\n            [-122.402404665507532, 37.788463925757384],\n            [-122.402065730098272, 37.788721152097366],\n            [-122.401375490979433, 37.789264321921429],\n            [-122.400067228055164, 37.790303858609981],\n            [-122.399919790020746, 37.790414442896498],\n            [-122.399480001759599, 37.790744297277485],\n            [-122.399222136170906, 37.790956214990921],\n            [-122.39914859901539, 37.791016648609592],\n            [-122.399148598683425, 37.79101664916432],\n            [-122.398264448861653, 37.791716058450703],\n            [-122.39826444614279, 37.791716060417308],\n            [-122.397840810091907, 37.792047595331439],\n            [-122.39738344591872, 37.792405521295422],\n            [-122.396504486670068, 37.793097082520163],\n            [-122.396376076969489, 37.793198086984006],\n            [-122.396302004629632, 37.793256350149633],\n            [-122.396302004290646, 37.793256350429786],\n            [-122.395983670970253, 37.793501461560965],\n            [-122.395622922555773, 37.793779228585429],\n            [-122.39533276386878, 37.794011429145591],\n            [-122.395317307728533, 37.794023797689903],\n            [-122.394971722038662, 37.794300351169447],\n            [-122.394747579547172, 37.794479718347191],\n            [-122.394745733667804, 37.794478246556643],\n            [-122.394745701490038, 37.794478274267938],\n            [-122.394150899798674, 37.794987788593353],\n            [-122.39270797067924, 37.793614415765255],\n            [-122.392702063215523, 37.793608792659974],\n            [-122.39249932896719, 37.793768814133983]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 3, ZIP_CODE: 94129, ID: 94129},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.470990721149789, 37.787534455433345],\n            [-122.472290534181823, 37.787395917162272],\n            [-122.472401902490546, 37.787384046501884],\n            [-122.47254342327507, 37.787368961705944],\n            [-122.472782224474642, 37.787331534391747],\n            [-122.473316434470121, 37.787247805418339],\n            [-122.474432824368861, 37.787072820298278],\n            [-122.474584006514732, 37.787049123020516],\n            [-122.47616220885665, 37.786978730627588],\n            [-122.478707989065171, 37.78691405835],\n            [-122.481375583614479, 37.787198893328011],\n            [-122.483910676418589, 37.787705653817092],\n            [-122.483911841345289, 37.78771598691722],\n            [-122.484019551359538, 37.788671434543716],\n            [-122.484071292407094, 37.789571562974182],\n            [-122.484214987310182, 37.789767611906584],\n            [-122.484758163249651, 37.789969442365155],\n            [-122.485095392905748, 37.790094746949087],\n            [-122.486221786002261, 37.790513270391187],\n            [-122.486196663014411, 37.790544596769486],\n            [-122.486148110204482, 37.790605847513199],\n            [-122.486108757859654, 37.790687545376898],\n            [-122.486034586054117, 37.790761588869977],\n            [-122.48595999185838, 37.790819844471812],\n            [-122.485896199163861, 37.790893713055688],\n            [-122.485740384038749, 37.791021323419344],\n            [-122.48558351329909, 37.791238913899868],\n            [-122.485411383576832, 37.791468435611826],\n            [-122.485284654257228, 37.791712987869268],\n            [-122.484994993901111, 37.791948608146861],\n            [-122.484752903676167, 37.79221570331633],\n            [-122.484541285320944, 37.792522116372695],\n            [-122.484363647892437, 37.79274005429609],\n            [-122.484183607122475, 37.793062416612024],\n            [-122.484072129345151, 37.793295035940602],\n            [-122.483858237133077, 37.793646123471206],\n            [-122.483593432919847, 37.794035150786577],\n            [-122.48329368676049, 37.794671303481294],\n            [-122.483518531231283, 37.794730014502065],\n            [-122.48308859662535, 37.79561146099627],\n            [-122.482999467368202, 37.795903449272551],\n            [-122.48285101972256, 37.796177893057802],\n            [-122.482765752291186, 37.796484924353678],\n            [-122.482667211606753, 37.796813467545597],\n            [-122.482504820847737, 37.797149264997877],\n            [-122.482349642968302, 37.797495928150028],\n            [-122.482180347467406, 37.797832527598885],\n            [-122.482169137762298, 37.797996159399602],\n            [-122.482136359830861, 37.798129936813986],\n            [-122.482085493238017, 37.798234488898757],\n            [-122.482121580114068, 37.798289507917929],\n            [-122.482129545147487, 37.798328517967903],\n            [-122.482111649834962, 37.798371396374968],\n            [-122.482102148276397, 37.798404519393337],\n            [-122.482087585279373, 37.798442534909526],\n            [-122.482079722552115, 37.798472196426395],\n            [-122.482040900643639, 37.798509246213612],\n            [-122.481958513784505, 37.798535353148957],\n            [-122.481870239865685, 37.798535463289312],\n            [-122.481831454802517, 37.798573885582073],\n            [-122.48191348856912, 37.79872908258961],\n            [-122.481933747767386, 37.798774753652147],\n            [-122.481894064444091, 37.798779540742402],\n            [-122.481782518807989, 37.79868595916038],\n            [-122.481759479485277, 37.798795537302446],\n            [-122.481735532663109, 37.798806240772073],\n            [-122.481729674725472, 37.79884616987701],\n            [-122.481697076819628, 37.79885701872184],\n            [-122.481553967429107, 37.798748172710248],\n            [-122.481544740366218, 37.798791592096414],\n            [-122.481675391067768, 37.798887599511232],\n            [-122.481685177680191, 37.798930012650303],\n            [-122.481641997341171, 37.798933485499177],\n            [-122.481619981772425, 37.798951710306021],\n            [-122.481601609693158, 37.798976741512377],\n            [-122.481583384160913, 37.799007264136819],\n            [-122.481561643359143, 37.799035785621555],\n            [-122.481552599461594, 37.799086069282282],\n            [-122.48153672724159, 37.799139901359283],\n            [-122.481507471077649, 37.799211126714575],\n            [-122.481470873600131, 37.799266680501994],\n            [-122.481417267273812, 37.799333507835762],\n            [-122.481380082795965, 37.799367095921824],\n            [-122.481394839952742, 37.799401184738606],\n            [-122.48138749011072, 37.799450066445814],\n            [-122.481313103352264, 37.799516556146521],\n            [-122.481199920275785, 37.799556228466017],\n            [-122.481106226731853, 37.799612741364335],\n            [-122.481068868952889, 37.799704704484391],\n            [-122.481063386681015, 37.799823601791012],\n            [-122.481042139949025, 37.799870656721012],\n            [-122.48098682060791, 37.799938199625082],\n            [-122.480980184705871, 37.800013851703859],\n            [-122.480977156432658, 37.800224730569639],\n            [-122.480974804661557, 37.800331213761126],\n            [-122.480860274222692, 37.800385329797535],\n            [-122.480757965088657, 37.800443360304584],\n            [-122.48067857879505, 37.800582040732152],\n            [-122.48065611449222, 37.800648344612128],\n            [-122.480592381330325, 37.800724956178229],\n            [-122.480537665874181, 37.800815150409605],\n            [-122.480468568377589, 37.800950224199951],\n            [-122.480375485908439, 37.801094628286094],\n            [-122.480295978813317, 37.801293743123836],\n            [-122.480202328037436, 37.801416867810858],\n            [-122.480181840275407, 37.801557305763211],\n            [-122.480071801739967, 37.801844835145566],\n            [-122.480027318791514, 37.802059156846411],\n            [-122.480044134726015, 37.802235365083838],\n            [-122.480004943470632, 37.80238847850972],\n            [-122.479965705600534, 37.802604771748101],\n            [-122.479928024203545, 37.802749618690633],\n            [-122.479899085376999, 37.802897752511299],\n            [-122.479813341330441, 37.803057827736559],\n            [-122.479734279947053, 37.803208863153962],\n            [-122.479678141843124, 37.803310755542739],\n            [-122.479612025097651, 37.803427923840943],\n            [-122.479496444899141, 37.803572704736546],\n            [-122.479357004588351, 37.803861413494445],\n            [-122.479173928312875, 37.80433146553451],\n            [-122.479146809127258, 37.804483001950985],\n            [-122.479060667102701, 37.804693214811543],\n            [-122.478953175659996, 37.804881810346664],\n            [-122.478815220557294, 37.805291358623961],\n            [-122.47872883882674, 37.805492647931004],\n            [-122.478723432948883, 37.805679530333919],\n            [-122.47869664200617, 37.805843422292654],\n            [-122.478685946257201, 37.805961720018743],\n            [-122.478628322791295, 37.806008011253461],\n            [-122.478654742843744, 37.806089976088074],\n            [-122.478631304779441, 37.806119899160173],\n            [-122.478660343597923, 37.806170230012903],\n            [-122.478638955177018, 37.806277032642242],\n            [-122.478627125019798, 37.806352771870088],\n            [-122.478625044107972, 37.806469551574978],\n            [-122.47857586751384, 37.806637939992576],\n            [-122.478562516078711, 37.80678649853526],\n            [-122.478544650108333, 37.806960541692234],\n            [-122.478526703332861, 37.807066600081164],\n            [-122.478526389161814, 37.807184723319764],\n            [-122.478521297177736, 37.807253482414033],\n            [-122.478488208711909, 37.807311036148292],\n            [-122.478483958170003, 37.807411370608691],\n            [-122.478496304822514, 37.807549883734964],\n            [-122.478476156109295, 37.807833156107016],\n            [-122.478405186887699, 37.808093245296376],\n            [-122.478343748883177, 37.808126216988313],\n            [-122.478060427750734, 37.808278265895638],\n            [-122.477897062640963, 37.80844925522419],\n            [-122.477885395973601, 37.808531172189731],\n            [-122.477923697644769, 37.808669250052077],\n            [-122.477956528906134, 37.809251736581921],\n            [-122.477922196862778, 37.80978247039468],\n            [-122.477956931601639, 37.810111519960003],\n            [-122.477924513902849, 37.810324264541741],\n            [-122.477975357212003, 37.810543166576302],\n            [-122.477691959274622, 37.810627579828761],\n            [-122.477738440983671, 37.81094269802653],\n            [-122.477669662799912, 37.810960332923507],\n            [-122.477465352217308, 37.810997758254302],\n            [-122.4773147668257, 37.811025342092201],\n            [-122.476951827952391, 37.810983355163785],\n            [-122.476621797907725, 37.810876948869407],\n            [-122.47656163509636, 37.810893065263848],\n            [-122.476466638674822, 37.81083628492037],\n            [-122.47639876264958, 37.810757761311969],\n            [-122.476244453139699, 37.810423848047549],\n            [-122.476192907539087, 37.810243414175943],\n            [-122.47615497008627, 37.809858792499412],\n            [-122.47604874198008, 37.809640130705603],\n            [-122.475967371125094, 37.809574880888988],\n            [-122.475790153494444, 37.809485827869665],\n            [-122.475539966877108, 37.809387008587656],\n            [-122.474949730564461, 37.809260233883315],\n            [-122.474802168498073, 37.809179610065499],\n            [-122.474477490769161, 37.809143842083898],\n            [-122.474059299537672, 37.809106891534391],\n            [-122.473248913495638, 37.809099164155811],\n            [-122.472667246163553, 37.80903404071298],\n            [-122.472248345244111, 37.80897031306057],\n            [-122.471807567969009, 37.808865058985567],\n            [-122.471692590128313, 37.808861156356251],\n            [-122.471577117792876, 37.808857237159089],\n            [-122.471241422813662, 37.80879760884919],\n            [-122.470932885927255, 37.808717610499883],\n            [-122.47066006806898, 37.808613665617798],\n            [-122.470600816238615, 37.80859886080146],\n            [-122.470032562336371, 37.809430372877515],\n            [-122.469989867152222, 37.809452374737276],\n            [-122.469923620349192, 37.809434939483481],\n            [-122.469403337873828, 37.809203958897747],\n            [-122.469386781047135, 37.809167151824653],\n            [-122.469446470968407, 37.809067952374448],\n            [-122.469513627885206, 37.809054469737255],\n            [-122.470046113471852, 37.809288679655658],\n            [-122.470536336868321, 37.808582769832967],\n            [-122.47042036446561, 37.808452854807648],\n            [-122.470227327454069, 37.808354443608728],\n            [-122.470098624189163, 37.80826663136105],\n            [-122.470013817898405, 37.808202121652926],\n            [-122.469815463486142, 37.808098991213633],\n            [-122.469586546186093, 37.807953106986339],\n            [-122.469305300471973, 37.807727061403774],\n            [-122.469233592741006, 37.807568936815628],\n            [-122.469112342826094, 37.807370435141976],\n            [-122.46886886007195, 37.807066844008062],\n            [-122.468789265438318, 37.807002933173059],\n            [-122.468641769161479, 37.806924361175163],\n            [-122.468442727148087, 37.806860383444018],\n            [-122.468303117397753, 37.806818075943092],\n            [-122.46814816670377, 37.806980670547468],\n            [-122.46804250856195, 37.806913074050627],\n            [-122.468204198687829, 37.806743499513175],\n            [-122.468154584727131, 37.80670037662253],\n            [-122.467927641872478, 37.806563383834956],\n            [-122.4677672527434, 37.806455496134681],\n            [-122.467355591928154, 37.806141659736177],\n            [-122.467203291926239, 37.806012347645776],\n            [-122.466996296046148, 37.805909356729394],\n            [-122.466671493417238, 37.805802836197401],\n            [-122.466463320033583, 37.805982110961864],\n            [-122.466652133036774, 37.805790797867139],\n            [-122.466274152924925, 37.805653990321176],\n            [-122.466075349709101, 37.80558203392561],\n            [-122.465895016150796, 37.805570618742252],\n            [-122.465648753037854, 37.805488195293123],\n            [-122.46546102999136, 37.805393808021854],\n            [-122.464551731906468, 37.805113664168701],\n            [-122.464230324554308, 37.805004333527904],\n            [-122.463987997870177, 37.804939695859083],\n            [-122.463579617827847, 37.804879883064366],\n            [-122.463271699005631, 37.804887756693624],\n            [-122.462952230341088, 37.804917109899002],\n            [-122.4624302743036, 37.805013698161765],\n            [-122.462214023782167, 37.805018669869817],\n            [-122.462027122615794, 37.804955166469341],\n            [-122.461896643188638, 37.804930554615567],\n            [-122.461601325019004, 37.804956755736356],\n            [-122.461272453201914, 37.805023344559039],\n            [-122.461007547032963, 37.805086808651389],\n            [-122.459772513833471, 37.805253612450713],\n            [-122.45957795006224, 37.805293241948746],\n            [-122.459540191073017, 37.805305543773983],\n            [-122.45950068434351, 37.805317187894794],\n            [-122.459462907558404, 37.805328803259258],\n            [-122.459423363879012, 37.805339074505127],\n            [-122.45938555118417, 37.805349316978521],\n            [-122.459306427886148, 37.805368486538775],\n            [-122.459227232780066, 37.805384910313265],\n            [-122.459185886621981, 37.805392464497949],\n            [-122.459106618316682, 37.805406142481793],\n            [-122.459065217794489, 37.805411637333286],\n            [-122.459025547917307, 37.805417103422087],\n            [-122.458984129270405, 37.80542191181479],\n            [-122.458944422811243, 37.805426005021417],\n            [-122.458902967930442, 37.80542944052501],\n            [-122.458861494934339, 37.805432189583698],\n            [-122.458788912310027, 37.805436828799479],\n            [-122.458735345621889, 37.80544046541047],\n            [-122.458683526998115, 37.80544475969581],\n            [-122.458631726477662, 37.805449740388731],\n            [-122.458578213760234, 37.805455436499734],\n            [-122.458526449095928, 37.805461790012899],\n            [-122.458474703215558, 37.805468829647623],\n            [-122.458371283182728, 37.805485655405761],\n            [-122.458319609004732, 37.805495440705506],\n            [-122.458269665819927, 37.805505197514321],\n            [-122.45821802818655, 37.805516356173719],\n            [-122.458168120492331, 37.805527485811837],\n            [-122.458116519376176, 37.805540017007239],\n            [-122.458066648197928, 37.805552519182882],\n            [-122.457989327072951, 37.805574405126777],\n            [-122.45793619470966, 37.805594515430641],\n            [-122.457883043876322, 37.805613939560359],\n            [-122.457829874920449, 37.805632677510189],\n            [-122.457774957510779, 37.805650757180459],\n            [-122.457721751946011, 37.805668121951648],\n            [-122.457666780196845, 37.805684142829186],\n            [-122.457610078443324, 37.805700191847833],\n            [-122.457500062415335, 37.80572948720183],\n            [-122.457443287865871, 37.805742790973085],\n            [-122.457386495190917, 37.805755408011883],\n            [-122.457329684053221, 37.805767338598564],\n            [-122.457272855492562, 37.805778582715845],\n            [-122.457186701695477, 37.805793746855791],\n            [-122.457077909022615, 37.805803793319789],\n            [-122.456970882463025, 37.805815183288864],\n            [-122.456862180533747, 37.805828661426311],\n            [-122.456755226634087, 37.80584279746536],\n            [-122.456648326952561, 37.805858992150092],\n            [-122.456541463411881, 37.805876559873688],\n            [-122.456434635998889, 37.805895500361686],\n            [-122.45632959276756, 37.805916471347693],\n            [-122.456224568256204, 37.805938128659257],\n            [-122.456119597596228, 37.80596184517497],\n            [-122.456014644961741, 37.805986248302439],\n            [-122.45591147681688, 37.806012681382633],\n            [-122.455806614105612, 37.806040516209428],\n            [-122.455705248815832, 37.806069666405428],\n            [-122.455564429228914, 37.806112518618505],\n            [-122.45530695988451, 37.806195761893825],\n            [-122.454600415883462, 37.806430130022392],\n            [-122.454483234784888, 37.806468999744823],\n            [-122.454353658700001, 37.806478701096999],\n            [-122.454308145719423, 37.806459540393945],\n            [-122.454293829263207, 37.806375996256122],\n            [-122.454085665791084, 37.806359530611644],\n            [-122.454069929877903, 37.806353610767474],\n            [-122.453815604080873, 37.806293271483817],\n            [-122.453295386032664, 37.806259311412973],\n            [-122.452781551105744, 37.806270567825713],\n            [-122.452438711570082, 37.806267316310901],\n            [-122.451653324871984, 37.806353109244483],\n            [-122.450906745259303, 37.80653233722969],\n            [-122.450459653590798, 37.806646175036533],\n            [-122.44991286747026, 37.806721142700994],\n            [-122.449819483247254, 37.806724746830461],\n            [-122.449720673642986, 37.806719513093618],\n            [-122.449631038513971, 37.806734042766898],\n            [-122.449554038474531, 37.806768278664137],\n            [-122.44947869660497, 37.806799740182214],\n            [-122.449399659671798, 37.806822335243758],\n            [-122.449304851007483, 37.806837636909691],\n            [-122.449171524916196, 37.806836407131136],\n            [-122.449109070409847, 37.806831258752986],\n            [-122.449048183710957, 37.806819903592029],\n            [-122.449010891791161, 37.806784123494651],\n            [-122.448891660899136, 37.806726348099637],\n            [-122.448806459828916, 37.806711961234221],\n            [-122.448728288255808, 37.806701578532227],\n            [-122.448565239272398, 37.806755090545202],\n            [-122.448529100304583, 37.806763241657009],\n            [-122.448455091339923, 37.806133486140737],\n            [-122.448455258275729, 37.806133277363898],\n            [-122.448323332142422, 37.805007750605469],\n            [-122.448321161555853, 37.804993824977359],\n            [-122.448279145759457, 37.80472422506687],\n            [-122.44824552349904, 37.804508484024247],\n            [-122.448240728857812, 37.804477717258706],\n            [-122.448240723025904, 37.804477679722268],\n            [-122.448245764546613, 37.8044754914729],\n            [-122.448316879890498, 37.804444627382082],\n            [-122.448814547024654, 37.804228638340902],\n            [-122.448954929708933, 37.80412855834809],\n            [-122.449124213448584, 37.804007874392056],\n            [-122.449253815658196, 37.803861977230383],\n            [-122.449388062152011, 37.803710849923448],\n            [-122.449495662264184, 37.803470319431604],\n            [-122.449583722697156, 37.803153095621241],\n            [-122.449583713807527, 37.803152915295549],\n            [-122.449565462756809, 37.802791419060213],\n            [-122.449536106109235, 37.802697377072583],\n            [-122.449460220896157, 37.80245428555321],\n            [-122.449178591741017, 37.802073857413127],\n            [-122.449043488821943, 37.801965383441527],\n            [-122.448802640091088, 37.801772003872607],\n            [-122.448359651469772, 37.801586782043685],\n            [-122.447921599691895, 37.801532283988202],\n            [-122.447857007904148, 37.801596177616553],\n            [-122.447819731578562, 37.801633051558142],\n            [-122.447819572978844, 37.801633208279924],\n            [-122.447802865090509, 37.801525678628579],\n            [-122.447614227653389, 37.80032479990944],\n            [-122.447613279369918, 37.80032370169468],\n            [-122.447466030932901, 37.799389886853575],\n            [-122.447400275900733, 37.798948048802856],\n            [-122.447341437979304, 37.798552685345861],\n            [-122.447327769221047, 37.798519415660031],\n            [-122.447303043858923, 37.798459232650295],\n            [-122.447209582420982, 37.797849561838987],\n            [-122.44715829626513, 37.797515005478481],\n            [-122.44715829625072, 37.79751500492933],\n            [-122.447014738232738, 37.796578510197754],\n            [-122.447014738218328, 37.796578509648619],\n            [-122.446928896449293, 37.796018515191513],\n            [-122.44687098056005, 37.795640691997477],\n            [-122.446751148304088, 37.794858932799087],\n            [-122.446727777275527, 37.794706465308671],\n            [-122.446727777261131, 37.794706464759514],\n            [-122.446587862952981, 37.793765120337646],\n            [-122.446446456540414, 37.792813716046261],\n            [-122.446446033268856, 37.792810866507708],\n            [-122.446299573716246, 37.791878694429421],\n            [-122.446299547924625, 37.791878529215651],\n            [-122.44720637942703, 37.791763091830468],\n            [-122.447679208068394, 37.79170289872647],\n            [-122.449367313308201, 37.791487980191171],\n            [-122.449385188501296, 37.791484927305568],\n            [-122.450995459060991, 37.791209906278311],\n            [-122.452035629818212, 37.790998907609811],\n            [-122.454248357567437, 37.79059310209788],\n            [-122.455878319348855, 37.790294143976332],\n            [-122.457498733292852, 37.789996912625632],\n            [-122.459011873160264, 37.789719335764374],\n            [-122.459028627866218, 37.789715983297555],\n            [-122.459250455476877, 37.789713291862398],\n            [-122.45947565027717, 37.789710559160014],\n            [-122.459781586189493, 37.789596250165474],\n            [-122.459925547070256, 37.789542753604124],\n            [-122.468653534340191, 37.787783523544832],\n            [-122.470990721149789, 37.787534455433345]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 4, ZIP_CODE: 94121, ID: 94121},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.504456509027335, 37.788067554312491],\n            [-122.504150949920984, 37.788096097763379],\n            [-122.504066815467581, 37.788120877294119],\n            [-122.504049145771674, 37.78810744254686],\n            [-122.503997231067004, 37.788107638193928],\n            [-122.503846231021797, 37.788091662877633],\n            [-122.503713944876083, 37.788063694207068],\n            [-122.503612889329972, 37.788038628655784],\n            [-122.503526395190363, 37.78804009845387],\n            [-122.503485137277139, 37.78805041389267],\n            [-122.503463545305024, 37.78801987747579],\n            [-122.503373498826377, 37.788017973822534],\n            [-122.503335534005146, 37.788022052592311],\n            [-122.503263415949803, 37.788043193399247],\n            [-122.503234378285072, 37.788057421563131],\n            [-122.503243398050046, 37.788071003120102],\n            [-122.503209152201507, 37.78808463302547],\n            [-122.503179577522019, 37.788078954832578],\n            [-122.503144887269229, 37.788076110778647],\n            [-122.503077958752755, 37.78809716302807],\n            [-122.503049013609541, 37.788114823274398],\n            [-122.503006581160705, 37.78814576070333],\n            [-122.502956321560973, 37.788143180813591],\n            [-122.502925535751373, 37.788156752131144],\n            [-122.502902751007483, 37.788146151342978],\n            [-122.502865053026014, 37.788095972678335],\n            [-122.502836115946863, 37.78804976589835],\n            [-122.502789020665148, 37.78803614434397],\n            [-122.502704311593945, 37.788039643712104],\n            [-122.502589076168022, 37.788066323379901],\n            [-122.502515190951499, 37.788086120259166],\n            [-122.502425496104195, 37.788097257971025],\n            [-122.502376106845503, 37.788126939838207],\n            [-122.502316846531585, 37.7881114644726],\n            [-122.502255070395776, 37.788131055545016],\n            [-122.502169528699341, 37.788103665363288],\n            [-122.502105689513968, 37.788110929794158],\n            [-122.50207398762231, 37.788154733004774],\n            [-122.502071249254328, 37.788181562090124],\n            [-122.501989944924048, 37.788182942633284],\n            [-122.501951813392949, 37.78818084312524],\n            [-122.501912664088508, 37.788140990459205],\n            [-122.501912016401619, 37.788116965280736],\n            [-122.501877326179624, 37.788114120853955],\n            [-122.501877973840479, 37.788138145483494],\n            [-122.501858399507697, 37.788182429188559],\n            [-122.501842192404723, 37.788223222019695],\n            [-122.501801304245816, 37.788247265660054],\n            [-122.501766151357245, 37.788227260005755],\n            [-122.501756965310037, 37.788207500503631],\n            [-122.50172600293584, 37.788150340007959],\n            [-122.501700517223497, 37.788167941185236],\n            [-122.50167957356264, 37.788161429621908],\n            [-122.501619212555269, 37.788169321478662],\n            [-122.501569767420492, 37.788196943730178],\n            [-122.501591358665465, 37.788227480492822],\n            [-122.501533078675976, 37.788248385310276],\n            [-122.501514596042369, 37.788204747707923],\n            [-122.50145942465268, 37.788212551659868],\n            [-122.501437657642668, 37.788239704012419],\n            [-122.501335150234794, 37.788224962263008],\n            [-122.501268313574613, 37.788249445888859],\n            [-122.50115878458611, 37.788295256277053],\n            [-122.501091780980232, 37.788313561975109],\n            [-122.501021937979715, 37.788290711431223],\n            [-122.501006184350814, 37.78828411139213],\n            [-122.50097195648992, 37.788298427073251],\n            [-122.500993621505359, 37.788331709648567],\n            [-122.500947376526028, 37.788349663239131],\n            [-122.500879097304491, 37.788320605139283],\n            [-122.500861336013799, 37.788303738053742],\n            [-122.500826830696198, 37.788307757577726],\n            [-122.500806349431429, 37.788318405959657],\n            [-122.500754452719278, 37.788319286610971],\n            [-122.500738328529678, 37.78829895802815],\n            [-122.500712935348687, 37.788319991115557],\n            [-122.500640723790397, 37.788337698187455],\n            [-122.500593831459696, 37.788331626454848],\n            [-122.500550140713713, 37.78831588602435],\n            [-122.500538567416584, 37.788335997845735],\n            [-122.50048939950284, 37.78837391630227],\n            [-122.500458095017024, 37.788368266503305],\n            [-122.500446429574566, 37.788384946179491],\n            [-122.50034045177307, 37.788434129242091],\n            [-122.500272801380206, 37.788428409551436],\n            [-122.500256870701349, 37.788479498553457],\n            [-122.500250772830086, 37.788574372083048],\n            [-122.500192399744307, 37.788591844106463],\n            [-122.500174472488098, 37.788568799078057],\n            [-122.500178293975864, 37.788517915512088],\n            [-122.500130319126384, 37.788535897847389],\n            [-122.500091521884229, 37.788509086399898],\n            [-122.500099172636652, 37.788471872673249],\n            [-122.500066212558522, 37.788468998362944],\n            [-122.50006658231419, 37.788482726609828],\n            [-122.500011040812936, 37.788476801362307],\n            [-122.499966794796208, 37.788440467945314],\n            [-122.499878115891249, 37.788489357153694],\n            [-122.499810631863213, 37.788489815307436],\n            [-122.499785570748472, 37.78852320387022],\n            [-122.499760324750355, 37.788549727891812],\n            [-122.499797151337432, 37.788567645255235],\n            [-122.499803450517931, 37.788608742793357],\n            [-122.49976162645244, 37.788662331112889],\n            [-122.499733393517658, 37.788642207768127],\n            [-122.499727335024716, 37.788610033760072],\n            [-122.49968724080243, 37.78866359271359],\n            [-122.499634371443463, 37.788692645687142],\n            [-122.499577562358439, 37.788703910220832],\n            [-122.499554630056736, 37.788687817377856],\n            [-122.499511356576448, 37.788623310977023],\n            [-122.499481164550005, 37.788530426400044],\n            [-122.499432590648397, 37.788461889419303],\n            [-122.499336411156918, 37.788425062960037],\n            [-122.499266771879846, 37.788409762065221],\n            [-122.499202877060384, 37.788414965914313],\n            [-122.499128103233858, 37.788401812217707],\n            [-122.499099500985864, 37.788367960195586],\n            [-122.499041638784547, 37.788340098176221],\n            [-122.498955421371278, 37.788351861015244],\n            [-122.498920128036829, 37.788390916780045],\n            [-122.498889600061332, 37.788414096736403],\n            [-122.498846352726645, 37.788414829911119],\n            [-122.498829249780883, 37.788358120749585],\n            [-122.498858133988094, 37.78827384858316],\n            [-122.49881028141921, 37.788232082242921],\n            [-122.498779365121052, 37.788240846950437],\n            [-122.498721638945739, 37.788282343161484],\n            [-122.498684745303493, 37.788326233159403],\n            [-122.498610415395817, 37.78832955335983],\n            [-122.498598336042235, 37.788266578082698],\n            [-122.498619439354101, 37.78821471490064],\n            [-122.498602392043921, 37.788160064708592],\n            [-122.498537684539031, 37.788135065438439],\n            [-122.498508608985759, 37.788147919585676],\n            [-122.498480069151427, 37.788180680090115],\n            [-122.498393075984339, 37.788163612611328],\n            [-122.498359007071954, 37.788119551978269],\n            [-122.498367465612489, 37.788047987710556],\n            [-122.498258002596998, 37.788031987711385],\n            [-122.498205083784299, 37.787930560386634],\n            [-122.498141496631646, 37.787882879514228],\n            [-122.498020127555392, 37.787874635204304],\n            [-122.497965657850202, 37.7879085217278],\n            [-122.497937733854386, 37.787835513791961],\n            [-122.497862794972676, 37.787816181447418],\n            [-122.497833152644503, 37.787872309616162],\n            [-122.497858996475657, 37.787932304773527],\n            [-122.497838976582202, 37.787960113577462],\n            [-122.497779058543074, 37.787984477979037],\n            [-122.497727525397508, 37.787934532426668],\n            [-122.4976806523715, 37.787929145955836],\n            [-122.497620549305267, 37.78794664601778],\n            [-122.497594065916346, 37.787927179270163],\n            [-122.49754290189, 37.78789096244769],\n            [-122.49750382929264, 37.787853853467084],\n            [-122.497544349339876, 37.787816083058736],\n            [-122.497526053110533, 37.787779309105126],\n            [-122.497500930399411, 37.787746084497506],\n            [-122.497517139436695, 37.787705292265386],\n            [-122.497507659501551, 37.787674549599402],\n            [-122.497485331532715, 37.787616555022737],\n            [-122.497458570647495, 37.787586791856477],\n            [-122.497406323956653, 37.787574629197884],\n            [-122.49735786900257, 37.787574763042187],\n            [-122.497315176016556, 37.787596088453697],\n            [-122.497275259657627, 37.787591957457558],\n            [-122.497216443565691, 37.787592953772126],\n            [-122.497168648080745, 37.787553245760044],\n            [-122.497138187607533, 37.787514617578381],\n            [-122.497106127216909, 37.787480823939255],\n            [-122.497044990964866, 37.787459883810421],\n            [-122.496999885071403, 37.787455840347057],\n            [-122.496923235404381, 37.787437223130333],\n            [-122.496894579157143, 37.787401311287425],\n            [-122.49685734707576, 37.787368291637833],\n            [-122.496811927818939, 37.787352579117055],\n            [-122.496751769988833, 37.787368019451996],\n            [-122.496728617227618, 37.787343688934776],\n            [-122.496685721170834, 37.787357463425209],\n            [-122.496639544906756, 37.787313607347343],\n            [-122.496583690806744, 37.787296011229486],\n            [-122.496547105206147, 37.787287016686854],\n            [-122.496516354783793, 37.787301958649365],\n            [-122.49646797383916, 37.787304838384266],\n            [-122.496443903661216, 37.787310739875238],\n            [-122.496383616604291, 37.787321374760424],\n            [-122.496337075877918, 37.787328343443079],\n            [-122.496276530804948, 37.787329368567015],\n            [-122.496247565545247, 37.787346340725328],\n            [-122.496197934785357, 37.787367096454552],\n            [-122.496141439524379, 37.787390028597343],\n            [-122.496086747911505, 37.787415677408667],\n            [-122.496068420456311, 37.787442083500181],\n            [-122.496016690354622, 37.787449139908034],\n            [-122.495972174940263, 37.787467062259353],\n            [-122.495951842506244, 37.787478615416269],\n            [-122.495939786703687, 37.787485465489738],\n            [-122.495905650481646, 37.787503211836551],\n            [-122.495879794980965, 37.787507083489736],\n            [-122.495820979283494, 37.78750807910243],\n            [-122.49579201419597, 37.787525051142431],\n            [-122.495771716907484, 37.787542562920031],\n            [-122.495716453167731, 37.787546932051697],\n            [-122.495690597288615, 37.78755080339463],\n            [-122.495618108578682, 37.787558210990248],\n            [-122.495602871400223, 37.787570830192877],\n            [-122.495572047584162, 37.787583026463722],\n            [-122.495539604212951, 37.787599370571847],\n            [-122.495514135330879, 37.787617657121146],\n            [-122.495483182354334, 37.787625048105859],\n            [-122.495437195053938, 37.787652609233902],\n            [-122.495395954684781, 37.787663608544698],\n            [-122.495370190617706, 37.787670911683094],\n            [-122.495333314088882, 37.787715487058371],\n            [-122.495282044474621, 37.787739703822119],\n            [-122.495193913200154, 37.787744628911668],\n            [-122.495147390885393, 37.787752283545302],\n            [-122.495107603658113, 37.787752956804084],\n            [-122.495081894872186, 37.787762319709344],\n            [-122.495049506978233, 37.787780722682484],\n            [-122.494942531016747, 37.787792833838672],\n            [-122.494936422880045, 37.787823153702263],\n            [-122.49487624549289, 37.787837906657984],\n            [-122.494850021018962, 37.787828049271276],\n            [-122.494803406419749, 37.787832271634258],\n            [-122.494754880927445, 37.787894212652994],\n            [-122.494747761584406, 37.787951332156055],\n            [-122.494723178126165, 37.788002566741419],\n            [-122.494687204176572, 37.788080776838861],\n            [-122.494700862218465, 37.788138231843071],\n            [-122.494641953469724, 37.788135794738686],\n            [-122.494554921507401, 37.788052798168373],\n            [-122.494515868416002, 37.788016374906285],\n            [-122.4945350783522, 37.787958364153376],\n            [-122.494513047598829, 37.787911351574522],\n            [-122.494517883781597, 37.787833668269109],\n            [-122.494492854234224, 37.787803875161934],\n            [-122.494463538439888, 37.787807804761236],\n            [-122.494432676774579, 37.787818627891369],\n            [-122.494408993418205, 37.787838943934148],\n            [-122.494413448366132, 37.787875952476604],\n            [-122.494396887310245, 37.787903702167263],\n            [-122.49434981175834, 37.787890763675087],\n            [-122.494353194757167, 37.787823406042577],\n            [-122.494335895597104, 37.78782369865479],\n            [-122.49430957902598, 37.787810409012593],\n            [-122.494266331651687, 37.787811140513561],\n            [-122.49416034840992, 37.787795764612234],\n            [-122.494129594818148, 37.787746152815927],\n            [-122.494106737724252, 37.787732804619111],\n            [-122.494080878906914, 37.78767212220432],\n            [-122.494005791021607, 37.787582742860096],\n            [-122.49397989817011, 37.78758524069908],\n            [-122.493937148914341, 37.787604505606133],\n            [-122.49390314117592, 37.787627056362858],\n            [-122.493853140270886, 37.787634082573049],\n            [-122.493776804515477, 37.787627132573334],\n            [-122.493743568607016, 37.787613959813378],\n            [-122.493738061048077, 37.787613306047696],\n            [-122.493682801772195, 37.787606746485388],\n            [-122.493586205100101, 37.787618680905503],\n            [-122.493496509486093, 37.787629812101237],\n            [-122.493461856653141, 37.787628337503875],\n            [-122.493419162517711, 37.787649661504638],\n            [-122.493384362282285, 37.787642696015901],\n            [-122.493336275791464, 37.787656556752133],\n            [-122.493293636533849, 37.787679939994845],\n            [-122.493241832649318, 37.78768424947036],\n            [-122.493193617141287, 37.7876933054322],\n            [-122.493182042295089, 37.787713416527986],\n            [-122.493127220447846, 37.787734258699906],\n            [-122.493090653466112, 37.787725949238315],\n            [-122.493056424336842, 37.787740262623288],\n            [-122.493004582859115, 37.787743199151691],\n            [-122.492969929990394, 37.787741724684366],\n            [-122.492955061461288, 37.787768072078173],\n            [-122.492913931184304, 37.787783188809769],\n            [-122.492855170492433, 37.787786242224989],\n            [-122.492860417128782, 37.787852767193691],\n            [-122.492830071355925, 37.787882810136729],\n            [-122.492771255320051, 37.787883804228279],\n            [-122.492769800087729, 37.787829576197716],\n            [-122.492735920840914, 37.787856931882516],\n            [-122.49268687433684, 37.78777054473629],\n            [-122.492679439061774, 37.787751441716487],\n            [-122.492627892968244, 37.787765361179233],\n            [-122.492565672509713, 37.787768472646512],\n            [-122.492506929887313, 37.787772212321414],\n            [-122.492446513322577, 37.787778040470201],\n            [-122.492381054094224, 37.787789447708391],\n            [-122.492305602297094, 37.78781544526975],\n            [-122.492216274554934, 37.787840303764064],\n            [-122.492178548608294, 37.787853302497936],\n            [-122.492118555140365, 37.787874918314273],\n            [-122.492079412316457, 37.787899615509929],\n            [-122.492057605141056, 37.787925393277668],\n            [-122.491876207129678, 37.788001938945712],\n            [-122.491812827679937, 37.788026359033061],\n            [-122.491772083659185, 37.788055890066879],\n            [-122.491719745560601, 37.788104846164785],\n            [-122.491654323091851, 37.788117625573307],\n            [-122.491646041850075, 37.7881315002275],\n            [-122.491614039214184, 37.788164317512276],\n            [-122.491501375628616, 37.788222533071625],\n            [-122.491447583651606, 37.788217260993214],\n            [-122.491404851437281, 37.788237211413325],\n            [-122.491281422016286, 37.788281187084038],\n            [-122.491218428658357, 37.788320021552913],\n            [-122.491190014384841, 37.788357585284182],\n            [-122.491137087049111, 37.788384575161345],\n            [-122.491061983912687, 37.788423614320799],\n            [-122.490946338544006, 37.788499734606098],\n            [-122.490859530409139, 37.788554079246722],\n            [-122.490843207112334, 37.788590751992317],\n            [-122.490818381167585, 37.788633062206074],\n            [-122.490696422877051, 37.78873195173508],\n            [-122.490605105739689, 37.78881178134435],\n            [-122.490527498874997, 37.788886572527467],\n            [-122.490413287423564, 37.788951680519325],\n            [-122.490407103746506, 37.78897925443183],\n            [-122.490354139232181, 37.78900487109366],\n            [-122.490306437661019, 37.789033145843504],\n            [-122.490251889996458, 37.789064283086574],\n            [-122.490252239550742, 37.789077325209703],\n            [-122.490220015123754, 37.789101904980171],\n            [-122.490046709329732, 37.789222262378196],\n            [-122.489918657120555, 37.789352156846768],\n            [-122.489828681483118, 37.789482095324693],\n            [-122.489700630133797, 37.789482882790033],\n            [-122.489592566089883, 37.789454489856745],\n            [-122.489300670464104, 37.789412030065776],\n            [-122.489191447548507, 37.789340391834585],\n            [-122.488990889258972, 37.789283342302205],\n            [-122.48848285911852, 37.789442306771541],\n            [-122.488100013149761, 37.789428847345718],\n            [-122.488028309941384, 37.789530320047362],\n            [-122.487863707315668, 37.789588034282978],\n            [-122.487736999824023, 37.789574375465854],\n            [-122.487584402186499, 37.78949866010057],\n            [-122.487564674856429, 37.789472896340307],\n            [-122.487563664349921, 37.78943514280558],\n            [-122.487464302267156, 37.789473215069947],\n            [-122.48744759425297, 37.789430918694578],\n            [-122.487369692668253, 37.789430171560369],\n            [-122.487296628705792, 37.789480848156245],\n            [-122.487120114954962, 37.789610869819036],\n            [-122.487051777894393, 37.789708851205745],\n            [-122.4869376141597, 37.789840568479555],\n            [-122.486809390152729, 37.789964281705743],\n            [-122.486730121786451, 37.790041845235905],\n            [-122.486553036429143, 37.790150586495734],\n            [-122.486429009493079, 37.790237144752382],\n            [-122.486335272657072, 37.790356155991162],\n            [-122.486245109599949, 37.790414674064778],\n            [-122.486236878145661, 37.790495160989501],\n            [-122.486221786002261, 37.790513270391187],\n            [-122.485095392905748, 37.790094746949087],\n            [-122.484316748868366, 37.789805424389257],\n            [-122.484214987310182, 37.789767611906584],\n            [-122.484071292407094, 37.789571562974182],\n            [-122.484019551359538, 37.788671434543716],\n            [-122.483911841345289, 37.78771598691722],\n            [-122.483910676418589, 37.787705653817092],\n            [-122.481375583614479, 37.787198893328011],\n            [-122.479861181106074, 37.787037199203986],\n            [-122.478707989065171, 37.78691405835],\n            [-122.477707716230128, 37.786939475526701],\n            [-122.476637454822679, 37.786966661899442],\n            [-122.476572244768761, 37.786128658440397],\n            [-122.476575166016133, 37.786128513873635],\n            [-122.476575148769683, 37.786128282045887],\n            [-122.476436803741322, 37.784271125057622],\n            [-122.476306551695615, 37.782405954593735],\n            [-122.476169188584777, 37.78047585000003],\n            [-122.476025278884237, 37.778553841183147],\n            [-122.475888270951586, 37.776692974324831],\n            [-122.475750968701433, 37.774828017958995],\n            [-122.475613644972711, 37.772962670675476],\n            [-122.475613625589148, 37.772962410314676],\n            [-122.476694205661246, 37.772912858094969],\n            [-122.477069824745385, 37.772895630732648],\n            [-122.477766947236773, 37.772863655180586],\n            [-122.478836951668541, 37.772814568639937],\n            [-122.47883762078969, 37.772814536530767],\n            [-122.478848715172788, 37.772814028898644],\n            [-122.479904580478745, 37.772765580938412],\n            [-122.480978851643016, 37.772716079930653],\n            [-122.481947659007389, 37.77267180789157],\n            [-122.482048961146063, 37.772667157434498],\n            [-122.483119121668807, 37.772618024364625],\n            [-122.483119139145145, 37.772618264154566],\n            [-122.484194219251819, 37.772569315707138],\n            [-122.484194201052048, 37.772569061919981],\n            [-122.485152053625171, 37.772525429693054],\n            [-122.485261332297384, 37.772520422109807],\n            [-122.485262022888591, 37.772520390701253],\n            [-122.486334053115812, 37.772471462244795],\n            [-122.486678527930636, 37.772455879683093],\n            [-122.487398288926627, 37.772423078182946],\n            [-122.487398306387334, 37.772423316873983],\n            [-122.487399123482518, 37.77242327974907],\n            [-122.488470602075608, 37.772374307055074],\n            [-122.488471323394165, 37.772374169626119],\n            [-122.489539486385709, 37.772325343661755],\n            [-122.49061689309373, 37.772276279654356],\n            [-122.490617013718605, 37.772276274046327],\n            [-122.491688031137627, 37.772227293012065],\n            [-122.492761363210761, 37.772178246338406],\n            [-122.493316307377469, 37.772153213706439],\n            [-122.493832479293559, 37.77212966191987],\n            [-122.493832498174271, 37.772129926957135],\n            [-122.493833180817205, 37.772129896182108],\n            [-122.494902921073546, 37.772080894286702],\n            [-122.494903299427449, 37.772080795311069],\n            [-122.495971990075716, 37.772031913255347],\n            [-122.497046812008847, 37.771982808835908],\n            [-122.497412740335307, 37.771966238858134],\n            [-122.498113882732468, 37.771934487449556],\n            [-122.499183042122894, 37.771885392463027],\n            [-122.500200291234236, 37.771838923903807],\n            [-122.500257545202601, 37.771836308607973],\n            [-122.501327700223527, 37.771787274197017],\n            [-122.502399595949868, 37.771738155153713],\n            [-122.502399732131124, 37.771738148994636],\n            [-122.503472688521128, 37.771689380410749],\n            [-122.503472690942445, 37.771689380369601],\n            [-122.504544396347384, 37.771640577944709],\n            [-122.505611140464779, 37.771591516134151],\n            [-122.50668530503512, 37.771542352938617],\n            [-122.506685577396198, 37.771542340610623],\n            [-122.506991972993774, 37.771528822999358],\n            [-122.507750664399296, 37.771495034126787],\n            [-122.507750665782908, 37.771495034103232],\n            [-122.508780633594597, 37.771447480819759],\n            [-122.508823169175031, 37.771445503888728],\n            [-122.508823330922013, 37.771445496187766],\n            [-122.508867990729883, 37.771443447055226],\n            [-122.509894749698645, 37.771396032095495],\n            [-122.509894781266311, 37.771396329054475],\n            [-122.511053819156686, 37.771347546731022],\n            [-122.511055400217217, 37.771347479926334],\n            [-122.512571364940968, 37.771283657974607],\n            [-122.51313192303401, 37.771260053409627],\n            [-122.51314077587368, 37.771331323581165],\n            [-122.513103639776702, 37.771429475690731],\n            [-122.513116974838326, 37.771538439951165],\n            [-122.5131111559662, 37.771706791559524],\n            [-122.513101727800077, 37.771741976505247],\n            [-122.513108516285712, 37.771800920539754],\n            [-122.513097415135022, 37.771902061732895],\n            [-122.513158379126494, 37.77223615155534],\n            [-122.513150048202618, 37.772247968474822],\n            [-122.513145175599931, 37.772259726057939],\n            [-122.513142070416649, 37.772272827219133],\n            [-122.513140694405962, 37.772285899124284],\n            [-122.513142777404809, 37.77229891140734],\n            [-122.51314831943732, 37.772311864891819],\n            [-122.513155553774638, 37.772323415987366],\n            [-122.513162806726825, 37.772335653782996],\n            [-122.513171807785298, 37.772348547918142],\n            [-122.513179470052052, 37.772375886809904],\n            [-122.513181590285754, 37.772390272493183],\n            [-122.513180232870297, 37.772404030275354],\n            [-122.513177164205956, 37.772418504300951],\n            [-122.513164035088622, 37.772444825057953],\n            [-122.513153956021, 37.772455985088065],\n            [-122.513145624725894, 37.77246780201267],\n            [-122.513139041542303, 37.772480275551658],\n            [-122.513134206485844, 37.772493406254469],\n            [-122.513132830469672, 37.772506478159016],\n            [-122.513133202560653, 37.772520206404018],\n            [-122.513137015097556, 37.772533189699409],\n            [-122.513142557139105, 37.772546142909071],\n            [-122.513149791498321, 37.772557694004483],\n            [-122.513128650263695, 37.772862969521874],\n            [-122.513143974877181, 37.772917647305441],\n            [-122.513134546565453, 37.772952831971672],\n            [-122.513140759176039, 37.773054363934854],\n            [-122.513127629925961, 37.773080684410282],\n            [-122.513121083909837, 37.773094531347873],\n            [-122.513117996938973, 37.773108318663766],\n            [-122.513113180104355, 37.773122136070647],\n            [-122.513110502783931, 37.773151024750284],\n            [-122.513109145348821, 37.773164782804365],\n            [-122.513109926760848, 37.773193612966814],\n            [-122.513112046989562, 37.773207998100119],\n            [-122.513115877809639, 37.773221667551859],\n            [-122.513119727927929, 37.77323602341751],\n            [-122.513123558750877, 37.773249692868944],\n            [-122.513130848990954, 37.773263303241755],\n            [-122.513136372841529, 37.773275570567371],\n            [-122.513136837962776, 37.773292730939929],\n            [-122.513139032281771, 37.773309862056486],\n            [-122.513139962541018, 37.773344183350439],\n            [-122.513137434071865, 37.77337856371112],\n            [-122.513137899208886, 37.773395724632501],\n            [-122.51313663480029, 37.773412914815644],\n            [-122.513133622247153, 37.773429448109219],\n            [-122.513132357829548, 37.773446638017631],\n            [-122.513129363533338, 37.773463857742755],\n            [-122.513124956674076, 37.773492776231812],\n            [-122.513123636099678, 37.773507907142864],\n            [-122.513125756700802, 37.773522292818349],\n            [-122.513127895547086, 37.773537364376381],\n            [-122.513130108829742, 37.773555182186904],\n            [-122.51313457258631, 37.77359218983559],\n            [-122.513137102897502, 37.773685543750098],\n            [-122.51313464849045, 37.773722669817865],\n            [-122.513133402680765, 37.77374054642582],\n            [-122.513130445590789, 37.773759139276294],\n            [-122.513129218723165, 37.773777702029449],\n            [-122.513126261969134, 37.773796294599215],\n            [-122.513121575665878, 37.773814916705028],\n            [-122.513118599957409, 37.773832822854594],\n            [-122.513113913995852, 37.773851444954062],\n            [-122.513110938637425, 37.773869351371971],\n            [-122.513106251972374, 37.773887973208282],\n            [-122.513098199929146, 37.77391008650784],\n            [-122.513115558504083, 37.774550522030069],\n            [-122.513135161617555, 37.77469921031755],\n            [-122.513164585532962, 37.774890995995399],\n            [-122.513170483591551, 37.775108592933201],\n            [-122.513148799533084, 37.77520236034195],\n            [-122.513166877896921, 37.775294761967629],\n            [-122.513138108336719, 37.775382469681844],\n            [-122.513154511511672, 37.775413093012311],\n            [-122.513118656446565, 37.775494741304421],\n            [-122.513116560550898, 37.775991978980805],\n            [-122.513220716743945, 37.77664260637399],\n            [-122.513262136135666, 37.776766199546913],\n            [-122.513273147342474, 37.776853227885702],\n            [-122.513301250635152, 37.776996277263876],\n            [-122.513336088063554, 37.777196211308791],\n            [-122.513350113009579, 37.777330573444466],\n            [-122.513418927986152, 37.77744339753194],\n            [-122.513485622738756, 37.777541836186963],\n            [-122.513511493535475, 37.777602514461641],\n            [-122.513605787519069, 37.777633867571893],\n            [-122.513789576977686, 37.777711077145469],\n            [-122.513970390041706, 37.777742325491253],\n            [-122.514130469394843, 37.777902348781886],\n            [-122.514271487815051, 37.77812519117407],\n            [-122.514413862528215, 37.778270408245596],\n            [-122.514604595643718, 37.77847591891544],\n            [-122.51462964838835, 37.778506394234036],\n            [-122.514661065627067, 37.778643892998048],\n            [-122.514636184118331, 37.778747329407956],\n            [-122.514564452996041, 37.778846072735902],\n            [-122.514578405995934, 37.778977688986053],\n            [-122.51449838155861, 37.779153489214309],\n            [-122.5144938833634, 37.779242842682805],\n            [-122.514567855611702, 37.77941807161681],\n            [-122.514670725915678, 37.779446530659044],\n            [-122.514854558211539, 37.779525111397355],\n            [-122.514948079566921, 37.779719234525949],\n            [-122.514897941861832, 37.779848512679017],\n            [-122.514749765647835, 37.779872333834206],\n            [-122.514641072918508, 37.779820625636908],\n            [-122.514565266573783, 37.77989677577483],\n            [-122.514533951466817, 37.780082044762075],\n            [-122.514511340895595, 37.780269225221183],\n            [-122.514474054726847, 37.780425753002312],\n            [-122.51446673230646, 37.780602371059075],\n            [-122.514648527113849, 37.780860913612862],\n            [-122.51472341591213, 37.781133644263413],\n            [-122.514586047991969, 37.781300809991649],\n            [-122.514468426928062, 37.781302819849991],\n            [-122.514292998946104, 37.781279034300063],\n            [-122.514303621052633, 37.781351647553429],\n            [-122.514284968188448, 37.781429568185636],\n            [-122.514242655950966, 37.781464628517519],\n            [-122.513867881152649, 37.781428452669296],\n            [-122.513760174171992, 37.78147699096975],\n            [-122.513883322187127, 37.781614982927096],\n            [-122.513812238767187, 37.78173775061245],\n            [-122.513697000270085, 37.781891489394212],\n            [-122.513670014693673, 37.78198122658408],\n            [-122.513522169917209, 37.782081269425682],\n            [-122.513466336763443, 37.782192101792845],\n            [-122.513546762127802, 37.782286185176545],\n            [-122.513589561336119, 37.782396706497991],\n            [-122.513504004159799, 37.782432504856651],\n            [-122.51345966482603, 37.782520478406092],\n            [-122.513490615272289, 37.782576949453883],\n            [-122.51349907897729, 37.782633804536587],\n            [-122.513499154475824, 37.782700417272132],\n            [-122.513444212427984, 37.782780331251374],\n            [-122.513311970586869, 37.782817613200059],\n            [-122.513176844047379, 37.782748499450129],\n            [-122.513099674895386, 37.782774539876293],\n            [-122.513082211268937, 37.782896391754747],\n            [-122.512985625061674, 37.783036075867919],\n            [-122.513019551763222, 37.783138507955194],\n            [-122.513026396706579, 37.783199511429288],\n            [-122.512878567398928, 37.783236372275823],\n            [-122.512854035372925, 37.783352850568228],\n            [-122.512805434943459, 37.783475233699562],\n            [-122.512705462306386, 37.783553855572684],\n            [-122.512518627902452, 37.783620225547125],\n            [-122.512466567525493, 37.783742667306889],\n            [-122.512644903191074, 37.783873537806549],\n            [-122.512713964283492, 37.783931418677646],\n            [-122.512652381073622, 37.784021746473734],\n            [-122.512681006779928, 37.784120148680003],\n            [-122.512565948104239, 37.784153016218092],\n            [-122.512295468592697, 37.783942683042248],\n            [-122.512111442218099, 37.7837933668308],\n            [-122.511916645881882, 37.783949148187006],\n            [-122.511936379260646, 37.784102641671865],\n            [-122.512002036912364, 37.7841626409828],\n            [-122.51201098319315, 37.784237343198853],\n            [-122.511976089219957, 37.784290817818842],\n            [-122.511911287292179, 37.784326260764388],\n            [-122.511796674312308, 37.784311734732256],\n            [-122.511643280924901, 37.784334954352396],\n            [-122.511486279442096, 37.784288874386938],\n            [-122.511300968574147, 37.78428379521467],\n            [-122.511334223422679, 37.784425383212891],\n            [-122.511306545033449, 37.784553589399493],\n            [-122.511229614678896, 37.784588552121491],\n            [-122.51102434727656, 37.784485608568794],\n            [-122.510971002039966, 37.784496819600761],\n            [-122.510838401528204, 37.784584923981406],\n            [-122.510747296660981, 37.784671633775218],\n            [-122.510491862112929, 37.784697278857706],\n            [-122.510370776300974, 37.784699343616424],\n            [-122.510236724687886, 37.784733906185757],\n            [-122.510168389422006, 37.784702794445394],\n            [-122.510020323714343, 37.784723420804994],\n            [-122.509994254968106, 37.784727052267002],\n            [-122.509808028924709, 37.784816069670107],\n            [-122.509716922917022, 37.784902778658349],\n            [-122.509665899527562, 37.784999792217178],\n            [-122.509574233553352, 37.78512977535155],\n            [-122.509483405510977, 37.78522678080374],\n            [-122.50935080157798, 37.785314883231258],\n            [-122.509270075574662, 37.785337547981477],\n            [-122.509233168789549, 37.785444621876991],\n            [-122.509117710773396, 37.785590805131108],\n            [-122.508796514633275, 37.785744614237508],\n            [-122.508661532747013, 37.785744853745925],\n            [-122.508565980602214, 37.785795240243836],\n            [-122.508486275275231, 37.785855657828165],\n            [-122.50845217664569, 37.785938647661681],\n            [-122.508258558155333, 37.786074486851589],\n            [-122.50824179847983, 37.786094687819457],\n            [-122.508218230136194, 37.78611912520401],\n            [-122.508132741023516, 37.786157665329803],\n            [-122.508102962554631, 37.786144437693665],\n            [-122.508035220497703, 37.78613529033418],\n            [-122.507963402235234, 37.786167416830274],\n            [-122.507918608664951, 37.786238914034136],\n            [-122.507884921314982, 37.786273138055307],\n            [-122.507810998339579, 37.786355432403496],\n            [-122.507791149628716, 37.786389420730835],\n            [-122.507786702460081, 37.786416966109108],\n            [-122.507753742643061, 37.786414093677742],\n            [-122.507723143657557, 37.786434530234523],\n            [-122.507686905785704, 37.786502447942446],\n            [-122.50771513615085, 37.786586436657288],\n            [-122.507634122264022, 37.786726537606768],\n            [-122.507529970492527, 37.786842996798164],\n            [-122.507416802174745, 37.786882007667536],\n            [-122.507337150418977, 37.786944484013162],\n            [-122.507251469686537, 37.787040025961154],\n            [-122.50719904571541, 37.787149423517022],\n            [-122.507107186903028, 37.787272540712486],\n            [-122.507053654233644, 37.787276885429115],\n            [-122.506971891727119, 37.787453396158277],\n            [-122.506896872053431, 37.78749519083452],\n            [-122.50673936089315, 37.787494438084011],\n            [-122.506717248362293, 37.787444682355584],\n            [-122.506653688773341, 37.787462245895419],\n            [-122.506526049480186, 37.787478152978885],\n            [-122.506492546406875, 37.787519241120947],\n            [-122.506427084531083, 37.787594522862221],\n            [-122.506343785965981, 37.787650192878154],\n            [-122.506284872420196, 37.787711628562818],\n            [-122.506271997783998, 37.787747558158287],\n            [-122.506258863502353, 37.787773877799495],\n            [-122.50624035413243, 37.787793421492793],\n            [-122.506219873800518, 37.787804071365088],\n            [-122.506177628287418, 37.787841873914068],\n            [-122.506131292484596, 37.787856397132735],\n            [-122.506163876746712, 37.787909408764172],\n            [-122.506122372558949, 37.787974668305665],\n            [-122.506121008614969, 37.788052293211784],\n            [-122.506089600492331, 37.788170947004069],\n            [-122.506052098981115, 37.788192187283286],\n            [-122.505852340805589, 37.788229236102978],\n            [-122.505747263058424, 37.788247505451196],\n            [-122.505657587040744, 37.788259332046266],\n            [-122.505605226952582, 37.788243054476361],\n            [-122.505520554840288, 37.788247928168715],\n            [-122.505423328958457, 37.788236533825454],\n            [-122.505330267621474, 37.78825116471306],\n            [-122.505202440987361, 37.788260206114757],\n            [-122.505107748523827, 37.788214431400775],\n            [-122.505021260444238, 37.788152035322256],\n            [-122.50498134335804, 37.788147906935059],\n            [-122.504941407743146, 37.788143092108974],\n            [-122.504896245414301, 37.788136992665024],\n            [-122.504877161086682, 37.788135256957062],\n            [-122.504859843370596, 37.788134864962309],\n            [-122.504835625333357, 37.788135276762382],\n            [-122.504801138826153, 37.7881399833256],\n            [-122.504785514269201, 37.788138188774688],\n            [-122.504771508408268, 37.788132246255898],\n            [-122.504759158308161, 37.788123528620481],\n            [-122.5047502309164, 37.788113379306509],\n            [-122.504739555111996, 37.788102572979582],\n            [-122.504727204674595, 37.788093855346673],\n            [-122.504711432227211, 37.788086569376937],\n            [-122.504695715029484, 37.788081342687157],\n            [-122.504680053426, 37.788078175271465],\n            [-122.50466269900177, 37.788076410116517],\n            [-122.50464541869988, 37.788077390660611],\n            [-122.504626408521617, 37.788078400888217],\n            [-122.504609090805303, 37.788078008307494],\n            [-122.504591755258659, 37.788076929561612],\n            [-122.504576093667978, 37.788073762406697],\n            [-122.504558683651112, 37.788069937685613],\n            [-122.504543003526479, 37.788066083826294],\n            [-122.504525593174549, 37.788062259380773],\n            [-122.504508220574564, 37.788059807771766],\n            [-122.50449088434911, 37.788058729297397],\n            [-122.504468396217547, 37.788059111612547],\n            [-122.504456509027335, 37.788067554312491]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 5, ZIP_CODE: 94118, ID: 94118},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.448885763577039, 37.778388598994987],\n            [-122.449767079183658, 37.778277008331997],\n            [-122.450654812483819, 37.778164598609756],\n            [-122.450826415883952, 37.778142868140662],\n            [-122.451543139139602, 37.778052106408275],\n            [-122.452431111623397, 37.777939652339285],\n            [-122.453384996187339, 37.777826863341367],\n            [-122.45318832797058, 37.776853425180988],\n            [-122.452963825070867, 37.775750129254348],\n            [-122.452810237317621, 37.774995318605924],\n            [-122.453003330637316, 37.774970754669006],\n            [-122.45468264773983, 37.77475548474694],\n            [-122.454683295611602, 37.774755406979118],\n            [-122.455033749574639, 37.77471323536281],\n            [-122.45525254468275, 37.774686906456331],\n            [-122.455898178089939, 37.774604132119862],\n            [-122.456283639851293, 37.774554711848936],\n            [-122.456283671603558, 37.774554866249353],\n            [-122.456283917173579, 37.774554834704738],\n            [-122.457134751103212, 37.774454490912483],\n            [-122.458371099123696, 37.774308895694055],\n            [-122.458795325509186, 37.774254885425997],\n            [-122.459486118442314, 37.77416693399686],\n            [-122.459486145343419, 37.77416729779403],\n            [-122.459486938645284, 37.774167197254911],\n            [-122.460552700773292, 37.774031358657659],\n            [-122.460552661960989, 37.774031133779268],\n            [-122.461619755154771, 37.7738952534419],\n            [-122.461619775372085, 37.773895534117642],\n            [-122.461620188227315, 37.77389548164836],\n            [-122.462535340858068, 37.773778657528119],\n            [-122.462683796526321, 37.773759751788596],\n            [-122.463749210177951, 37.773624065149079],\n            [-122.464811670328331, 37.773488744378348],\n            [-122.465331676538511, 37.773422509669793],\n            [-122.465883163277454, 37.773398204721545],\n            [-122.466373346760548, 37.773376599548058],\n            [-122.466951728424362, 37.773351356945881],\n            [-122.466952500298319, 37.773351323464517],\n            [-122.468023916207073, 37.773303833556405],\n            [-122.468348575135167, 37.773289518371953],\n            [-122.469092496140973, 37.773256712542192],\n            [-122.469517084069324, 37.773237987003441],\n            [-122.470162692059887, 37.773209510586071],\n            [-122.470162712316579, 37.773209791534974],\n            [-122.471240089434815, 37.773162280380667],\n            [-122.47124006836178, 37.773161981865066],\n            [-122.471762122569857, 37.773138947752848],\n            [-122.472219951938214, 37.773117969260745],\n            [-122.472297341321692, 37.773114423074823],\n            [-122.473427836194134, 37.773062613687692],\n            [-122.473737577092777, 37.773048416620476],\n            [-122.474550910293516, 37.773011133644289],\n            [-122.474550931211951, 37.773011425844139],\n            [-122.47455168367884, 37.773011391539193],\n            [-122.475612802305008, 37.772962709520556],\n            [-122.475613644972711, 37.772962670675476],\n            [-122.475750968701433, 37.774828017958995],\n            [-122.475888270951586, 37.776692974324831],\n            [-122.476025278884237, 37.778553841183147],\n            [-122.476169188584777, 37.78047585000003],\n            [-122.476306551695615, 37.782405954593735],\n            [-122.476436803741322, 37.784271125057622],\n            [-122.476575148769683, 37.786128282045887],\n            [-122.476575166016133, 37.786128513873635],\n            [-122.476572244768761, 37.786128658440397],\n            [-122.476637454822679, 37.786966661899442],\n            [-122.47616220885665, 37.786978730627588],\n            [-122.475567725323828, 37.787005248954515],\n            [-122.475136959985235, 37.787024462123846],\n            [-122.474584006514732, 37.787049123020516],\n            [-122.474432824368861, 37.787072820298278],\n            [-122.473333621918172, 37.787245111436654],\n            [-122.473316434470121, 37.787247805418339],\n            [-122.472782224474642, 37.787331534391747],\n            [-122.47254342327507, 37.787368961705944],\n            [-122.472401902490546, 37.787384046501884],\n            [-122.472290534181823, 37.787395917162272],\n            [-122.468653534340191, 37.787783523544832],\n            [-122.466942641213976, 37.788133153852705],\n            [-122.465882841138949, 37.788349715761562],\n            [-122.4648365613821, 37.788563504283978],\n            [-122.463771243725489, 37.788781172337565],\n            [-122.459781586189493, 37.789596250165474],\n            [-122.45947565027717, 37.789710559160014],\n            [-122.459250455476877, 37.789713291862398],\n            [-122.459028627866218, 37.789715983297555],\n            [-122.459011873160264, 37.789719335764374],\n            [-122.457498733292852, 37.789996912625632],\n            [-122.455878319348855, 37.790294143976332],\n            [-122.454248357567437, 37.79059310209788],\n            [-122.452035629818212, 37.790998907609811],\n            [-122.450995459060991, 37.791209906278311],\n            [-122.449385188501296, 37.791484927305568],\n            [-122.449367313308201, 37.791487980191171],\n            [-122.449189583391288, 37.790608108274284],\n            [-122.449011564525605, 37.789726786022129],\n            [-122.448835781299294, 37.788856511426609],\n            [-122.448656220796209, 37.787977164570449],\n            [-122.4469741664482, 37.788186460801001],\n            [-122.446792779960703, 37.787261853518181],\n            [-122.446610465639836, 37.786302298311156],\n            [-122.446738337622591, 37.786243163870765],\n            [-122.446849274413296, 37.786186897081585],\n            [-122.447533801639068, 37.785411415165207],\n            [-122.447605133356163, 37.785275579171099],\n            [-122.447621599012791, 37.785194684567394],\n            [-122.447620107068687, 37.785083635258417],\n            [-122.447578549915917, 37.784998355250835],\n            [-122.447549635601661, 37.784906798974511],\n            [-122.447149436147441, 37.783030677550911],\n            [-122.447173033991248, 37.782710075581413],\n            [-122.447225622636566, 37.782575775503396],\n            [-122.447283893261542, 37.782434393634787],\n            [-122.447301513844337, 37.78239164221629],\n            [-122.447450898997985, 37.782195217632704],\n            [-122.447637300982407, 37.781719177617617],\n            [-122.447655108274333, 37.781537309189886],\n            [-122.447576361567485, 37.781175778551805],\n            [-122.447517982733274, 37.781069510753063],\n            [-122.447351927163439, 37.780152061280916],\n            [-122.447039806751391, 37.778622307248391],\n            [-122.447997721370214, 37.778501033647089],\n            [-122.448879206948348, 37.778389428728616],\n            [-122.448885763577039, 37.778388598994987]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 6, ZIP_CODE: 94123, ID: 94123},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.438402484723895, 37.794806973138577],\n            [-122.440045216960385, 37.794597761146619],\n            [-122.440868123190498, 37.794493055920753],\n            [-122.440870170501768, 37.794492795601883],\n            [-122.441712771485612, 37.794385578392699],\n            [-122.443384198876302, 37.794172877898092],\n            [-122.445035345747925, 37.79396273368863],\n            [-122.446587034424141, 37.793765298555968],\n            [-122.446446456540414, 37.792813716046261],\n            [-122.446587862952981, 37.793765120337646],\n            [-122.446727777261131, 37.794706464759514],\n            [-122.446727777275527, 37.794706465308671],\n            [-122.446751148304088, 37.794858932799087],\n            [-122.44687098056005, 37.795640691997477],\n            [-122.446928896449293, 37.796018515191513],\n            [-122.447014738218328, 37.796578509648619],\n            [-122.447014738232738, 37.796578510197754],\n            [-122.44715829625072, 37.79751500492933],\n            [-122.44715829626513, 37.797515005478481],\n            [-122.447209582420982, 37.797849561838987],\n            [-122.447303043858923, 37.798459232650295],\n            [-122.447327769221047, 37.798519415660031],\n            [-122.447341437979304, 37.798552685345861],\n            [-122.447400275900733, 37.798948048802856],\n            [-122.447466030932901, 37.799389886853575],\n            [-122.447613279369918, 37.80032370169468],\n            [-122.447614227653389, 37.80032479990944],\n            [-122.447802865090509, 37.801525678628579],\n            [-122.447819572978844, 37.801633208279924],\n            [-122.447819731578562, 37.801633051558142],\n            [-122.447857007904148, 37.801596177616553],\n            [-122.447921599691895, 37.801532283988202],\n            [-122.448359651469772, 37.801586782043685],\n            [-122.448802640091088, 37.801772003872607],\n            [-122.449043488821943, 37.801965383441527],\n            [-122.449178591741017, 37.802073857413127],\n            [-122.449460220896157, 37.80245428555321],\n            [-122.449536106109235, 37.802697377072583],\n            [-122.449565462756809, 37.802791419060213],\n            [-122.449583713807527, 37.803152915295549],\n            [-122.449583722697156, 37.803153095621241],\n            [-122.449495662264184, 37.803470319431604],\n            [-122.449388062152011, 37.803710849923448],\n            [-122.449253815658196, 37.803861977230383],\n            [-122.449124213448584, 37.804007874392056],\n            [-122.448954929708933, 37.80412855834809],\n            [-122.448814547024654, 37.804228638340902],\n            [-122.448316879890498, 37.804444627382082],\n            [-122.448245764546613, 37.8044754914729],\n            [-122.448240723025904, 37.804477679722268],\n            [-122.448240728857812, 37.804477717258706],\n            [-122.44824552349904, 37.804508484024247],\n            [-122.448279145759457, 37.80472422506687],\n            [-122.448321161555853, 37.804993824977359],\n            [-122.448323332142422, 37.805007750605469],\n            [-122.448455258275729, 37.806133277363898],\n            [-122.448456716038393, 37.806145711376217],\n            [-122.448529100304583, 37.806763241657009],\n            [-122.448398315024306, 37.806792871575105],\n            [-122.448372486009177, 37.806798105392311],\n            [-122.448348405000459, 37.806803997055574],\n            [-122.44832261235274, 37.806810603719661],\n            [-122.448274523063262, 37.806825132736471],\n            [-122.448250514438882, 37.806833770101044],\n            [-122.448204281209826, 37.806853075532878],\n            [-122.448182039290586, 37.806863057432189],\n            [-122.448159833037835, 37.806874411921982],\n            [-122.448139357121818, 37.80688573782485],\n            [-122.448117186873532, 37.806898464895561],\n            [-122.448103560271292, 37.806906930735266],\n            [-122.447833857922845, 37.807052164926837],\n            [-122.447528276286675, 37.807250818574119],\n            [-122.447180260711605, 37.80747705551984],\n            [-122.446637954280746, 37.807525151443585],\n            [-122.446476742583258, 37.807516824572637],\n            [-122.445255735074412, 37.807626931568663],\n            [-122.444832951299077, 37.807611928844082],\n            [-122.444812115030089, 37.807609525524043],\n            [-122.444789548054757, 37.807607150739592],\n            [-122.443882698723485, 37.807700913966812],\n            [-122.443762319418525, 37.807713359922936],\n            [-122.443684632386478, 37.807721507559208],\n            [-122.442739197031372, 37.807965235732532],\n            [-122.442711389346584, 37.807972404225552],\n            [-122.442625660786931, 37.808004032657479],\n            [-122.442524054141913, 37.808024247618199],\n            [-122.442354908341343, 37.808043514915532],\n            [-122.441767928096084, 37.808104685164672],\n            [-122.44153654297466, 37.808127036445633],\n            [-122.441200035212404, 37.808167598495942],\n            [-122.44095148917647, 37.808195724629321],\n            [-122.44065270005224, 37.808288542686931],\n            [-122.440627067703531, 37.808301325835579],\n            [-122.440575873699515, 37.808329637315019],\n            [-122.440528212527582, 37.808360637280757],\n            [-122.440504418161254, 37.808377510253422],\n            [-122.440482353473598, 37.808394354762264],\n            [-122.440462037446096, 37.808411857223902],\n            [-122.440440008975642, 37.808430074859068],\n            [-122.440419728804599, 37.808448949904253],\n            [-122.440401196955307, 37.808468483183979],\n            [-122.440382683383717, 37.808488702887828],\n            [-122.440369270902693, 37.808505405024498],\n            [-122.440221248842576, 37.808670595014306],\n            [-122.440123020723803, 37.808753931536479],\n            [-122.440031607015868, 37.808833035512706],\n            [-122.439928060968157, 37.808779113775664],\n            [-122.440270312303127, 37.808229595265985],\n            [-122.440282155842453, 37.808219099502388],\n            [-122.440294017657806, 37.80820928989008],\n            [-122.440307609508267, 37.808199452089056],\n            [-122.440319488926107, 37.808190329461823],\n            [-122.440334864961784, 37.808182522213436],\n            [-122.440348510624844, 37.808174743706033],\n            [-122.440379298231178, 37.808160502620083],\n            [-122.440394728431826, 37.80815475493111],\n            [-122.440410176225527, 37.808149693678615],\n            [-122.440427371995767, 37.808145290667582],\n            [-122.440444586383592, 37.808141573526278],\n            [-122.440470613795014, 37.808143892481645],\n            [-122.440632371579539, 37.808106894262245],\n            [-122.440777543613365, 37.808097638147814],\n            [-122.441785658321223, 37.807988336182923],\n            [-122.441808099289275, 37.807985906519384],\n            [-122.441799274649981, 37.807913258896662],\n            [-122.441490202960892, 37.807943755875684],\n            [-122.441489843938655, 37.807930027497129],\n            [-122.441472521937172, 37.807929625920565],\n            [-122.441480832705722, 37.807916441000579],\n            [-122.441399010156047, 37.807832633902123],\n            [-122.441414331805831, 37.807822767220159],\n            [-122.441512177188741, 37.807923479293905],\n            [-122.44162439956051, 37.807912017357232],\n            [-122.441545678197272, 37.807814424464247],\n            [-122.441557557740268, 37.807805301431408],\n            [-122.441658935146762, 37.807908701878773],\n            [-122.441779792066384, 37.807896411186228],\n            [-122.441788084829597, 37.807882540086432],\n            [-122.441704477043643, 37.807796702127824],\n            [-122.441718086585212, 37.807787550596132],\n            [-122.44181950044613, 37.807892323765437],\n            [-122.441935183814792, 37.807880804821842],\n            [-122.441943476554428, 37.807866933985672],\n            [-122.441859851343693, 37.807780409692931],\n            [-122.441873460855945, 37.807771257868716],\n            [-122.441976640855827, 37.807877375557908],\n            [-122.441977107703579, 37.807895222530654],\n            [-122.441816542778795, 37.807911600852826],\n            [-122.441823637077619, 37.807984277513498],\n            [-122.442320834639688, 37.807932139640414],\n            [-122.442377595612399, 37.807918156721222],\n            [-122.442423345751081, 37.807880319904761],\n            [-122.442454552098894, 37.807815940203007],\n            [-122.442446930178193, 37.807789283331502],\n            [-122.442372794146465, 37.807800805256427],\n            [-122.442345413582302, 37.807812930576347],\n            [-122.442346006240967, 37.807835582850217],\n            [-122.442033707111619, 37.807875062158132],\n            [-122.442009243703154, 37.807733999234173],\n            [-122.442027954502791, 37.807721329750571],\n            [-122.442048400903616, 37.807841170516582],\n            [-122.442059070073554, 37.807851982482966],\n            [-122.442207469058346, 37.807833743991878],\n            [-122.442217473805144, 37.807819158213746],\n            [-122.442189585584913, 37.807679524627673],\n            [-122.44220852946296, 37.807675779293724],\n            [-122.442236490235743, 37.807818158319698],\n            [-122.442248872177956, 37.807828255059988],\n            [-122.442314446214397, 37.807820307862364],\n            [-122.442321008509211, 37.807806465496753],\n            [-122.442293084181387, 37.807665459069689],\n            [-122.442311884371406, 37.807656221984004],\n            [-122.442337612108403, 37.807779409372287],\n            [-122.442355041151117, 37.807783929703042],\n            [-122.442395115206907, 37.807727644711157],\n            [-122.442384613878218, 37.807657084644539],\n            [-122.44241012033585, 37.80763949635341],\n            [-122.442685954576561, 37.807595809313327],\n            [-122.442711892175168, 37.807793159839584],\n            [-122.442714242657956, 37.807793886686525],\n            [-122.442762503926971, 37.807808807618663],\n            [-122.442779771325021, 37.807807149719757],\n            [-122.442902339949939, 37.807794142945916],\n            [-122.442998755417392, 37.807774012900218],\n            [-122.44305537242154, 37.807754538469467],\n            [-122.44312779660163, 37.807743731320251],\n            [-122.443201717191158, 37.807723971451296],\n            [-122.443260064122327, 37.807704468417526],\n            [-122.443291085252142, 37.807699150180341],\n            [-122.443284905918816, 37.807661481932094],\n            [-122.443264195467847, 37.807663883640146],\n            [-122.443274020298077, 37.807642433173122],\n            [-122.443270056810988, 37.807623269807593],\n            [-122.44314383017317, 37.807628783225987],\n            [-122.443254016387542, 37.807605679164965],\n            [-122.44326198542305, 37.807579452177009],\n            [-122.443135758859114, 37.807584965586791],\n            [-122.443242483942598, 37.807561918560637],\n            [-122.443256021380762, 37.807550021121365],\n            [-122.443252111820613, 37.807532917601364],\n            [-122.443237999416624, 37.807522848927505],\n            [-122.443246112189684, 37.807502113399792],\n            [-122.443244040453081, 37.807489099968812],\n            [-122.443229963993403, 37.807480404158795],\n            [-122.443238040829058, 37.80745829576658],\n            [-122.443234130578546, 37.807441191982505],\n            [-122.443218162072228, 37.807426347064215],\n            [-122.443264702406339, 37.807418712940589],\n            [-122.443256517776859, 37.807436702740098],\n            [-122.443274647533997, 37.807467993526231],\n            [-122.44326455321054, 37.80747914750733],\n            [-122.443284610988869, 37.807517960537304],\n            [-122.443274498692247, 37.807528428086727],\n            [-122.443278390296598, 37.807544845448142],\n            [-122.443294466994871, 37.807563808946817],\n            [-122.443284389944367, 37.807575649647809],\n            [-122.443286551537625, 37.807592095514579],\n            [-122.443302628241014, 37.807611058737429],\n            [-122.443292551178217, 37.80762289916445],\n            [-122.443296353650368, 37.807635884626045],\n            [-122.443302173270908, 37.807659823956676],\n            [-122.443308298708217, 37.807695432906783],\n            [-122.443434058250361, 37.807672072344801],\n            [-122.443672727711146, 37.807597406163168],\n            [-122.443654441112585, 37.80749401124762],\n            [-122.443659326683914, 37.80748225662078],\n            [-122.443872884696731, 37.807440280106206],\n            [-122.443843293810986, 37.807367974319583],\n            [-122.44370731452436, 37.807397684591926],\n            [-122.443671444290359, 37.807416130974829],\n            [-122.443653888762242, 37.807406805822907],\n            [-122.443615857187666, 37.807408806048031],\n            [-122.443601726794256, 37.807398051259632],\n            [-122.443646374280576, 37.807384267621238],\n            [-122.443657912040536, 37.807362102160489],\n            [-122.443677395248955, 37.807378949282132],\n            [-122.443708397548889, 37.807372944513865],\n            [-122.443719917316642, 37.807350092614648],\n            [-122.443739400535989, 37.807366939726066],\n            [-122.443768690788104, 37.807361649886793],\n            [-122.443780246134452, 37.8073401708521],\n            [-122.443799711046125, 37.807356331526904],\n            [-122.443829001635834, 37.807351041666962],\n            [-122.443838790315425, 37.807328218286891],\n            [-122.443858291875273, 37.807345751805421],\n            [-122.443944431867095, 37.807329910704055],\n            [-122.443955915261199, 37.807305685922557],\n            [-122.44397716482635, 37.807323877338213],\n            [-122.444023687043469, 37.8073155564826],\n            [-122.444033511615373, 37.807294106225484],\n            [-122.444052976916183, 37.807310266852966],\n            [-122.444104672197867, 37.807301173690028],\n            [-122.444114316981555, 37.807272858828142],\n            [-122.444139117510247, 37.80729442532332],\n            [-122.444173580792466, 37.807288363378881],\n            [-122.444194201234893, 37.80728252962394],\n            [-122.444071355206816, 37.80715270277112],\n            [-122.444216516082363, 37.807275294750141],\n            [-122.444262912050363, 37.807262168504906],\n            [-122.444272682607831, 37.807238658656139],\n            [-122.444290381984246, 37.807253474900222],\n            [-122.444329892128806, 37.807241835878372],\n            [-122.444341267517572, 37.807213492465038],\n            [-122.444362535128633, 37.807232370243902],\n            [-122.44440375797123, 37.807220016231902],\n            [-122.444415151652876, 37.807192359237938],\n            [-122.444436401305339, 37.807210550845831],\n            [-122.444546334776021, 37.807177835248929],\n            [-122.444557764011307, 37.807151551111282],\n            [-122.444577265002437, 37.807169084522954],\n            [-122.444645975607855, 37.807148723180354],\n            [-122.444656807573693, 37.807165712792234],\n            [-122.444442095756031, 37.807229685090327],\n            [-122.444483781319875, 37.807301104324431],\n            [-122.444798026960797, 37.807203901149613],\n            [-122.445256739502199, 37.807070664930905],\n            [-122.445225848727588, 37.807014862399406],\n            [-122.445091259282876, 37.807031503517287],\n            [-122.445058382323012, 37.807032045740513],\n            [-122.44505631062799, 37.807019032334502],\n            [-122.445158131368714, 37.807007052095038],\n            [-122.44517833798561, 37.806985429952121],\n            [-122.445116858617496, 37.806951420767781],\n            [-122.445030629048986, 37.806963830570979],\n            [-122.445058875964975, 37.806918727340673],\n            [-122.445006173970214, 37.806889380441703],\n            [-122.444888888156058, 37.806905735960221],\n            [-122.444874654156408, 37.80682493696483],\n            [-122.444919463246819, 37.806817330453363],\n            [-122.445007311205075, 37.806866699909307],\n            [-122.444974402625263, 37.806799942930326],\n            [-122.444993292526533, 37.806794137571423],\n            [-122.445038942963123, 37.806884719639513],\n            [-122.445121510119179, 37.806930742460146],\n            [-122.445046609432765, 37.806780897144854],\n            [-122.445065498977499, 37.806775091780075],\n            [-122.445153141586133, 37.806948762165227],\n            [-122.44523217658535, 37.806992095976476],\n            [-122.44535586222473, 37.80695571941591],\n            [-122.445247620593648, 37.806722643522114],\n            [-122.445266510453877, 37.806716838119371],\n            [-122.445374751789728, 37.806949914001784],\n            [-122.445469219634987, 37.806921573283674],\n            [-122.445352433919155, 37.8066927587953],\n            [-122.445373036116507, 37.806686238401404],\n            [-122.44548810952854, 37.806915767845673],\n            [-122.445561992695104, 37.80689463390474],\n            [-122.445445188748025, 37.806665133074851],\n            [-122.445464060244248, 37.8066586412139],\n            [-122.445580882228484, 37.806888828457545],\n            [-122.44570454953481, 37.806851765098479],\n            [-122.445651671462073, 37.806749628119896],\n            [-122.44565282578796, 37.806727633749716],\n            [-122.445895754405569, 37.806815647477016],\n            [-122.445736019894767, 37.806863607021306],\n            [-122.445754222526631, 37.806897643432812],\n            [-122.446384760166481, 37.806715556411739],\n            [-122.446258463322081, 37.806718327435846],\n            [-122.446193213892712, 37.806738632616792],\n            [-122.446170395914976, 37.806726648309962],\n            [-122.445981443875837, 37.806782644077451],\n            [-122.445970540045622, 37.80676290912637],\n            [-122.445598235532685, 37.806626211997774],\n            [-122.445606509444289, 37.806611653919497],\n            [-122.446001524256076, 37.806756217316881],\n            [-122.446149253941641, 37.806712575605459],\n            [-122.445708764584751, 37.806550221539489],\n            [-122.445717038134546, 37.806535664008109],\n            [-122.446176741453044, 37.806704568267236],\n            [-122.446283248720263, 37.80667328102048],\n            [-122.446005194935296, 37.806500006546784],\n            [-122.446020461726192, 37.80648808024214],\n            [-122.446305581169554, 37.806666731908173],\n            [-122.446420668040176, 37.806632556303882],\n            [-122.446151409366294, 37.806464630848126],\n            [-122.446166694812902, 37.806453390945222],\n            [-122.446443000465095, 37.806626007165498],\n            [-122.446554662537906, 37.806593261684583],\n            [-122.446391023698155, 37.806492266360543],\n            [-122.446406290771549, 37.806480340000242],\n            [-122.446524184932926, 37.806553247425605],\n            [-122.446620237835958, 37.806519385773647],\n            [-122.446642693746213, 37.806583567659246],\n            [-122.44670978090322, 37.806567352265269],\n            [-122.446939773843297, 37.806492135834091],\n            [-122.446948929215651, 37.806511213110113],\n            [-122.446720738906919, 37.806589146719396],\n            [-122.446733553252031, 37.806615717634571],\n            [-122.446996260681743, 37.80653378052115],\n            [-122.44699045975274, 37.806444601492437],\n            [-122.446974562378131, 37.806432503088267],\n            [-122.446987792972394, 37.806408935599343],\n            [-122.446983774594216, 37.806387713332704],\n            [-122.44696966191627, 37.806377645652319],\n            [-122.446979503500458, 37.806356881028954],\n            [-122.446973448766144, 37.806324017983009],\n            [-122.446942374816217, 37.806327277888172],\n            [-122.44695713738291, 37.806296131639499],\n            [-122.446941332573715, 37.806221538794638],\n            [-122.446922047288439, 37.806212242955326],\n            [-122.446935223870369, 37.806186616724325],\n            [-122.446919473110199, 37.806114083171188],\n            [-122.446903683817155, 37.806106103074534],\n            [-122.446913508081977, 37.806084652286692],\n            [-122.446909453048207, 37.80606205716353],\n            [-122.446700318081568, 37.806074437190453],\n            [-122.446888239127603, 37.80604523913599],\n            [-122.446903488381423, 37.806032626547122],\n            [-122.446899451700631, 37.806010717849745],\n            [-122.446881914446337, 37.806002079602202],\n            [-122.446893486335398, 37.805981286694831],\n            [-122.446879430339621, 37.805907352248376],\n            [-122.446670313096035, 37.805920418116813],\n            [-122.446859964791457, 37.805891192083081],\n            [-122.446873519346127, 37.805879980657039],\n            [-122.446859427027746, 37.80580467279912],\n            [-122.446845404479788, 37.805798037263322],\n            [-122.446855228367411, 37.805776586210264],\n            [-122.446849425403357, 37.805753333484326],\n            [-122.446649086514796, 37.805771061515294],\n            [-122.446833420536592, 37.805737116186556],\n            [-122.446845280397781, 37.805727305922368],\n            [-122.446841244096817, 37.805705397216052],\n            [-122.446823724582785, 37.805697445671754],\n            [-122.446835297139998, 37.805676653032066],\n            [-122.446831332492664, 37.805657489784885],\n            [-122.446817327967949, 37.805651540403126],\n            [-122.446825403536536, 37.805629431757723],\n            [-122.446805830072663, 37.805609152989824],\n            [-122.446817348267402, 37.805586300786253],\n            [-122.446809113007006, 37.805536305492353],\n            [-122.446789863569947, 37.805528382500277],\n            [-122.446803112068949, 37.805505501734793],\n            [-122.446787107265692, 37.805489284429996],\n            [-122.446700807332093, 37.805498949734627],\n            [-122.446698645389645, 37.80548250391827],\n            [-122.446795308825045, 37.805471980811141],\n            [-122.446771068810406, 37.805405768207393],\n            [-122.446462225224153, 37.805444515694951],\n            [-122.445989505497494, 37.805505194954378],\n            [-122.446003707846955, 37.805518695189633],\n            [-122.4460087518259, 37.805579044127001],\n            [-122.446547025898894, 37.805509728799308],\n            [-122.446550972467278, 37.805528205624398],\n            [-122.446518204506802, 37.805532866567553],\n            [-122.446489185128556, 37.80554845357441],\n            [-122.446462996481614, 37.80553995860523],\n            [-122.446437419711515, 37.805554801776253],\n            [-122.446402597499727, 37.805547136014376],\n            [-122.446377002372941, 37.805561292745764],\n            [-122.446359483641928, 37.805553341395452],\n            [-122.446335312891549, 37.805555800213412],\n            [-122.446311412770896, 37.805568555772986],\n            [-122.446295642000578, 37.805561262020625],\n            [-122.44627666253902, 37.805563635428271],\n            [-122.446249319434344, 37.805577134798497],\n            [-122.446231800006771, 37.805569182891531],\n            [-122.446148996869667, 37.805580163541499],\n            [-122.446123402382483, 37.805594320480907],\n            [-122.446104135009989, 37.805585710944754],\n            [-122.44608171290129, 37.805588827858948],\n            [-122.44605783038071, 37.805602270079603],\n            [-122.446038580653479, 37.805594346696139],\n            [-122.446017870868147, 37.805596748888597],\n            [-122.445994006657656, 37.805610876973709],\n            [-122.445976469264281, 37.80560223887116],\n            [-122.445950586862267, 37.805605412856671],\n            [-122.445928452266116, 37.805619512663846],\n            [-122.445909185255772, 37.805610903090184],\n            [-122.445885032816534, 37.805614048516972],\n            [-122.445861132260902, 37.80562680426543],\n            [-122.445845361169205, 37.805619510184115],\n            [-122.445814305801534, 37.805623456219109],\n            [-122.44579215352951, 37.805636869562669],\n            [-122.445772886191605, 37.805628260247033],\n            [-122.445750464048956, 37.80563137682369],\n            [-122.445724833141924, 37.805644160817579],\n            [-122.445709062413187, 37.805636866986909],\n            [-122.445683179641605, 37.805640040919442],\n            [-122.445655872372569, 37.805654912741964],\n            [-122.445638335008098, 37.805646274589208],\n            [-122.445605549302542, 37.805650249115246],\n            [-122.445581648680047, 37.80566300453242],\n            [-122.445565859972788, 37.805655024250306],\n            [-122.445531361592955, 37.805659714013281],\n            [-122.445505730631226, 37.805672497684945],\n            [-122.445489959918007, 37.805665203825008],\n            [-122.445462346810999, 37.80566840625292],\n            [-122.445436715841595, 37.805681190184124],\n            [-122.445420945133293, 37.805673896314971],\n            [-122.445391619690682, 37.8056778137015],\n            [-122.445362527733508, 37.805690654981625],\n            [-122.445346757369364, 37.8056833608223],\n            [-122.44531225895534, 37.805688050246587],\n            [-122.445290052615164, 37.805699404198599],\n            [-122.445276030220143, 37.805692768201546],\n            [-122.445243244128591, 37.805696742632556],\n            [-122.445217576794818, 37.805708153657022],\n            [-122.445203554404628, 37.805701517651372],\n            [-122.445170786636353, 37.80570617848889],\n            [-122.445148507984058, 37.805714786690388],\n            [-122.445105232410981, 37.805714813715646],\n            [-122.445083008046993, 37.805725481196227],\n            [-122.44507071598305, 37.80571881663689],\n            [-122.445037947513853, 37.80572347772349],\n            [-122.445017471439073, 37.805734803086857],\n            [-122.445001701099429, 37.805727508881461],\n            [-122.444908551660831, 37.805740032744836],\n            [-122.44488456097811, 37.805749355858453],\n            [-122.444846440036883, 37.805747924599146],\n            [-122.444825945937126, 37.805758563222234],\n            [-122.444811923572402, 37.805751927170057],\n            [-122.44478259772076, 37.805755844410804],\n            [-122.44476037365142, 37.805766512099389],\n            [-122.444713600749751, 37.805765222919185],\n            [-122.444691394641026, 37.805776576752621],\n            [-122.444677372284929, 37.805769940684463],\n            [-122.444653201761611, 37.805772399426502],\n            [-122.444630958989393, 37.805782380395051],\n            [-122.444585917098436, 37.805781062896116],\n            [-122.444563710619391, 37.805792416711171],\n            [-122.444549688278883, 37.80578578090244],\n            [-122.444518632419573, 37.805789726327305],\n            [-122.444494659650545, 37.805799735794025],\n            [-122.444451329400607, 37.805797703293351],\n            [-122.444429141227388, 37.805809743784408],\n            [-122.444413370583575, 37.805802449506146],\n            [-122.444390948345657, 37.805805566373394],\n            [-122.44436699318581, 37.805816262252314],\n            [-122.444352970844008, 37.805809625871085],\n            [-122.444327105931421, 37.805813485938437],\n            [-122.44430666567736, 37.805826184043205],\n            [-122.444289129128734, 37.805817545953296],\n            [-122.444266706866628, 37.805820662247768],\n            [-122.444242769660434, 37.805832044533943],\n            [-122.444228747333952, 37.805825408412545],\n            [-122.444206307444119, 37.80582783853189],\n            [-122.444182406178044, 37.805840593670816],\n            [-122.44416661758342, 37.805832613201659],\n            [-122.44414592563939, 37.805835701223295],\n            [-122.444121953151352, 37.805845710608445],\n            [-122.444025145055349, 37.805850740063669],\n            [-122.444001189835404, 37.805861436142784],\n            [-122.443959572249256, 37.805858688226643],\n            [-122.443935671622995, 37.805871443304085],\n            [-122.443919900675667, 37.805864149240215],\n            [-122.443902651728223, 37.805866493739174],\n            [-122.443880499045548, 37.805879906997674],\n            [-122.443864710121758, 37.805871926219289],\n            [-122.44378192426916, 37.805883591639486],\n            [-122.443756257338379, 37.805895002334175],\n            [-122.443714639749658, 37.805892254606199],\n            [-122.443692432758084, 37.80590360826271],\n            [-122.4436766621739, 37.805896314160641],\n            [-122.443654240222742, 37.805899430882093],\n            [-122.443630320868039, 37.805911499476395],\n            [-122.443614550281069, 37.80590420509148],\n            [-122.44359211035453, 37.805906635368508],\n            [-122.443568208950381, 37.805919390108215],\n            [-122.443552420405013, 37.80591140955697],\n            [-122.443535171434974, 37.805913754002177],\n            [-122.443511270025937, 37.805926509004905],\n            [-122.443495481838426, 37.805918528714912],\n            [-122.443473041543214, 37.805920958425858],\n            [-122.443450852475209, 37.80593299846943],\n            [-122.443435081915695, 37.805925704609677],\n            [-122.443407468616002, 37.805928906281792],\n            [-122.443388776471963, 37.805942262421105],\n            [-122.443369509665189, 37.805933652158522],\n            [-122.443347069022408, 37.805936082125754],\n            [-122.443328395172557, 37.80595012440785],\n            [-122.443309128031871, 37.805941514415672],\n            [-122.443291878362203, 37.805943859111288],\n            [-122.443267959626397, 37.805955927620595],\n            [-122.443252189065603, 37.805948633187285],\n            [-122.443233209414942, 37.805951006379615],\n            [-122.443207595577817, 37.805964475989526],\n            [-122.443174557723637, 37.805958839788218],\n            [-122.443148908639841, 37.80597093678324],\n            [-122.443134868425886, 37.805963614373887],\n            [-122.443058967715231, 37.805973792109128],\n            [-122.443036832460166, 37.805987891372702],\n            [-122.443017547724423, 37.805978594894604],\n            [-122.442981318355606, 37.805983312150161],\n            [-122.442948532359026, 37.805987285937427],\n            [-122.442926379114695, 37.806000698747546],\n            [-122.44290884267825, 37.805992060178127],\n            [-122.442839827294776, 37.806000751431881],\n            [-122.442815943694384, 37.806014192451634],\n            [-122.442798425230706, 37.806006240298515],\n            [-122.442734582834518, 37.806014159544731],\n            [-122.442707256867862, 37.806028343976273],\n            [-122.44268971976544, 37.806019705660304],\n            [-122.442636241331058, 37.806026767128834],\n            [-122.442610646012994, 37.806040923030253],\n            [-122.442591378939156, 37.80603231319558],\n            [-122.442551688858117, 37.806037087047187],\n            [-122.442547725650329, 37.806017923926198],\n            [-122.443029120691975, 37.805957802366279],\n            [-122.443387985849299, 37.805912059110995],\n            [-122.443753825765924, 37.805868259987832],\n            [-122.443769345089194, 37.805865944017846],\n            [-122.443957339015583, 37.805839496905513],\n            [-122.444492227815289, 37.805772993462419],\n            [-122.444668235229059, 37.805751549942215],\n            [-122.445284142224111, 37.805672032526566],\n            [-122.445279062855903, 37.805610310694497],\n            [-122.44527006920417, 37.805597411188494],\n            [-122.444483346869873, 37.805698286438428],\n            [-122.443744945559246, 37.805793553184138],\n            [-122.443018600699929, 37.805886555905765],\n            [-122.442516549650193, 37.805951138305055],\n            [-122.442427410705633, 37.806050808392641],\n            [-122.442522464016989, 37.806507977467341],\n            [-122.442586642075881, 37.806579026869471],\n            [-122.443131937777821, 37.806513045572309],\n            [-122.443499493172467, 37.806468532301039],\n            [-122.443667282141945, 37.806463706682081],\n            [-122.443846679856065, 37.806439461357343],\n            [-122.443959061743129, 37.806434175357609],\n            [-122.443938050605254, 37.806358981524156],\n            [-122.443116244562106, 37.806442570813587],\n            [-122.442613848569025, 37.80649411140103],\n            [-122.442611615303633, 37.806474919513256],\n            [-122.442654765933469, 37.806470088368528],\n            [-122.442661399886077, 37.806458991436592],\n            [-122.442687570467214, 37.806466801090252],\n            [-122.442708298579689, 37.80646508646474],\n            [-122.442716537115061, 37.806449155723413],\n            [-122.442746276492144, 37.806461026947929],\n            [-122.442770051771902, 37.806443467084335],\n            [-122.44279979080919, 37.80645533830107],\n            [-122.442874033560784, 37.806447934694987],\n            [-122.442882218173921, 37.806429944918698],\n            [-122.442912011462568, 37.806443875399388],\n            [-122.442927548550728, 37.806442245978758],\n            [-122.442937481103471, 37.806424914130673],\n            [-122.442965543726487, 37.806438873384522],\n            [-122.442990995725921, 37.806419225391323],\n            [-122.443019058349662, 37.806433184357886],\n            [-122.443039786445084, 37.806431469674095],\n            [-122.443047971009094, 37.806413479611678],\n            [-122.443077764322211, 37.806427410050546],\n            [-122.443098474448703, 37.80642500892381],\n            [-122.443108388985991, 37.806406990354105],\n            [-122.443136451627183, 37.806420949292765],\n            [-122.443157179716479, 37.806419234588326],\n            [-122.443165418150457, 37.806403303815522],\n            [-122.443193426900422, 37.806415203442874],\n            [-122.443215885660081, 37.806413459937652],\n            [-122.443222483596756, 37.806400990383416],\n            [-122.443248689797912, 37.806410172509004],\n            [-122.443329817859791, 37.806401281706691],\n            [-122.443338074230084, 37.806386037628911],\n            [-122.443366083001223, 37.806397937215287],\n            [-122.443447229004846, 37.806389732764778],\n            [-122.443455467380517, 37.806373802246185],\n            [-122.443480033821288, 37.806386445534933],\n            [-122.443505916940026, 37.806383271811711],\n            [-122.443512496848314, 37.806370115534087],\n            [-122.443538721058857, 37.806379984576957],\n            [-122.443562892155768, 37.806377525783311],\n            [-122.443569490023762, 37.806365055935018],\n            [-122.44359742694175, 37.80637420973634],\n            [-122.443676842221919, 37.806366033924192],\n            [-122.443672164594389, 37.806253487231395],\n            [-122.443695462542081, 37.806349932284753],\n            [-122.443707916670277, 37.806362774599044],\n            [-122.44379596596373, 37.806353769679554],\n            [-122.443802527838344, 37.806339926678575],\n            [-122.443830500721077, 37.806350453289745],\n            [-122.443861574478419, 37.806347194484687],\n            [-122.443868190266627, 37.806335411052103],\n            [-122.443896109229783, 37.80634387807568],\n            [-122.443922010642723, 37.806341390962501],\n            [-122.443913871646629, 37.806228901600704],\n            [-122.443940541034891, 37.806321857121844],\n            [-122.443956527422003, 37.806337388377891],\n            [-122.44398415881686, 37.80633487273537],\n            [-122.444004419522869, 37.806315310362876],\n            [-122.444018675589732, 37.806330870132562],\n            [-122.444044576647997, 37.806328382998174],\n            [-122.444051174437902, 37.806315913397398],\n            [-122.444077344761538, 37.806323722474062],\n            [-122.444106688857119, 37.806320492116114],\n            [-122.444113268646205, 37.806307335530214],\n            [-122.444137744599871, 37.806316546256639],\n            [-122.444163610043603, 37.806312686225482],\n            [-122.444171919814423, 37.806299501392182],\n            [-122.444198126803698, 37.806308683844726],\n            [-122.44422572221977, 37.806304795006142],\n            [-122.444232302000771, 37.806291638962733],\n            [-122.444258508634277, 37.806300820858432],\n            [-122.444287834382735, 37.806296903754081],\n            [-122.44429446807581, 37.806285806729939],\n            [-122.444320620790435, 37.806292929589091],\n            [-122.444410328338193, 37.806281149679826],\n            [-122.444398674018615, 37.806166658108886],\n            [-122.444430732743257, 37.806267078693303],\n            [-122.444443114393508, 37.806277175761053],\n            [-122.44453626493052, 37.806264652260793],\n            [-122.444546322932027, 37.806252125028941],\n            [-122.444572511964111, 37.80626062069139],\n            [-122.44466566211247, 37.806248097094617],\n            [-122.444654277108668, 37.8061439020343],\n            [-122.444684317803762, 37.806233368169231],\n            [-122.444698448132328, 37.806244122830272],\n            [-122.444729504511017, 37.806240177078124],\n            [-122.444737832507585, 37.806227678906012],\n            [-122.444765733548039, 37.806235459291536],\n            [-122.444782982907213, 37.806233114935374],\n            [-122.44479131087958, 37.806220616210318],\n            [-122.444819211930195, 37.806228396857783],\n            [-122.444886496978683, 37.806219733530369],\n            [-122.44488089641429, 37.806138105148783],\n            [-122.444906901269448, 37.806205662186763],\n            [-122.444919282967007, 37.806215758930051],\n            [-122.444986568338436, 37.806207095539854],\n            [-122.444994931901647, 37.806195969945342],\n            [-122.445021067006977, 37.806202405936702],\n            [-122.445093525002505, 37.8061929701732],\n            [-122.445087942204026, 37.80611202850794],\n            [-122.445113929253864, 37.806178899068257],\n            [-122.445128041656801, 37.806188967520207],\n            [-122.445202229600525, 37.806179502882479],\n            [-122.445210557849578, 37.806167004396592],\n            [-122.445236728249824, 37.806174813490792],\n            [-122.44525743822885, 37.806172411710314],\n            [-122.445265766467799, 37.80615991322049],\n            [-122.445267302625481, 37.806086407881679],\n            [-122.445285033545986, 37.806168522898403],\n            [-122.445310916864869, 37.806165349047845],\n            [-122.445305315838027, 37.806083720685585],\n            [-122.445331267107818, 37.80614921860834],\n            [-122.445347145482373, 37.806160631088311],\n            [-122.445366125472304, 37.806158257824791],\n            [-122.445374453006437, 37.806145759613244],\n            [-122.44540062410141, 37.806153568385149],\n            [-122.445424776743849, 37.806150423332426],\n            [-122.445433086611402, 37.80613723812958],\n            [-122.445434730189021, 37.806067851662334],\n            [-122.445459293346119, 37.806146420033315],\n            [-122.445479985322095, 37.806143331781477],\n            [-122.445489989537165, 37.806128745440887],\n            [-122.445516214271365, 37.806138614038851],\n            [-122.445543827543716, 37.806135411324078],\n            [-122.445560788683153, 37.80612208366891],\n            [-122.445567131287589, 37.806165929693421],\n            [-122.445546061556797, 37.806154603157502],\n            [-122.445523639245678, 37.806157719690731],\n            [-122.445501522738013, 37.806172505842895],\n            [-122.445489122634442, 37.806161722734075],\n            [-122.44546670032598, 37.806164839531021],\n            [-122.445442817508763, 37.806178281625698],\n            [-122.445432201695837, 37.806169528989997],\n            [-122.445352822460961, 37.806179079403755],\n            [-122.445327226916191, 37.806193235905667],\n            [-122.445316593481564, 37.806183797096139],\n            [-122.445244135177305, 37.806193233002396],\n            [-122.44522027063509, 37.806207361203953],\n            [-122.445207906544567, 37.80619795120483],\n            [-122.445188926532239, 37.806200324168309],\n            [-122.445161708883106, 37.806218628036419],\n            [-122.445135448551525, 37.806207386764015],\n            [-122.445111565653477, 37.806220828516935],\n            [-122.44509921920563, 37.806212104669896],\n            [-122.445030203866864, 37.80622079693034],\n            [-122.445008123555382, 37.806236955848796],\n            [-122.444978438202398, 37.806227144485362],\n            [-122.444954644838973, 37.806244018373903],\n            [-122.444924959836314, 37.806234206991327],\n            [-122.444902861169254, 37.806249679463384],\n            [-122.444888730810021, 37.806238924826829],\n            [-122.444823175735436, 37.806247559901692],\n            [-122.444801113352611, 37.806264405213646],\n            [-122.444785216712205, 37.806252306233745],\n            [-122.444723104639422, 37.806260197747797],\n            [-122.444699383130299, 37.80627981731422],\n            [-122.444681702577071, 37.806265687549761],\n            [-122.444619590489268, 37.806273579283847],\n            [-122.444597491739671, 37.806289051423988],\n            [-122.444583361417187, 37.806278296750953],\n            [-122.444519536610485, 37.806286903400981],\n            [-122.444493959540637, 37.806301746142793],\n            [-122.44447984720945, 37.80629167788976],\n            [-122.444386678651156, 37.806303514912976],\n            [-122.444361209083098, 37.806322476775328],\n            [-122.444343546554336, 37.806309033118175],\n            [-122.444324566500356, 37.806311406491666],\n            [-122.444297366567298, 37.806330396317819],\n            [-122.444279722039454, 37.806317639632702],\n            [-122.444255568948051, 37.806320784456545],\n            [-122.444233506414719, 37.806337629662124],\n            [-122.444217609851108, 37.806325530605875],\n            [-122.444126171547992, 37.806337339172615],\n            [-122.444102396615747, 37.806354899035341],\n            [-122.444084751416256, 37.806342142057929],\n            [-122.444034733554687, 37.806349147388161],\n            [-122.443955318332002, 37.806357323441311],\n            [-122.443976328801966, 37.806432517557667],\n            [-122.444009133253417, 37.806429229637253],\n            [-122.44401497169018, 37.80645385590833],\n            [-122.443983915176943, 37.806457801197638],\n            [-122.443991501564284, 37.806483085111559],\n            [-122.444006566508079, 37.806529534342857],\n            [-122.443998364182022, 37.80654683776438],\n            [-122.444012889909018, 37.806572694020851],\n            [-122.444137456251781, 37.806569953925113],\n            [-122.444004705900142, 37.806590683869416],\n            [-122.444008597616772, 37.806607101209622],\n            [-122.444028224510816, 37.8066294400098],\n            [-122.444014687364202, 37.806641337539759],\n            [-122.444016813135761, 37.806656410256579],\n            [-122.444032907804939, 37.806676060090197],\n            [-122.4440210291, 37.806685183643154],\n            [-122.444025100564104, 37.806708465032258],\n            [-122.444042961537718, 37.806729459206764],\n            [-122.444031118428768, 37.806739955631187],\n            [-122.444035226540592, 37.806764609873134],\n            [-122.444052799240296, 37.806774621138679],\n            [-122.444173599989711, 37.806760268693552],\n            [-122.444175761756156, 37.806776714546551],\n            [-122.443903104192728, 37.806809364848277],\n            [-122.443899212488802, 37.80679294750523],\n            [-122.443999302417467, 37.806780996791801],\n            [-122.444012857566491, 37.806769785696332],\n            [-122.443998457621589, 37.806748734467796],\n            [-122.444004821781832, 37.806727340974426],\n            [-122.443985267126379, 37.806707748443934],\n            [-122.443996768032036, 37.80668420981921],\n            [-122.443979015619519, 37.806667334220748],\n            [-122.443988858092823, 37.806646570397213],\n            [-122.443972709865449, 37.806624861254427],\n            [-122.443980822328029, 37.806604125398131],\n            [-122.443978768461875, 37.806591798410317],\n            [-122.443966422424324, 37.806583074165033],\n            [-122.443972768605505, 37.806560994240272],\n            [-122.443949501347888, 37.806531848697659],\n            [-122.443975330928197, 37.806526615843026],\n            [-122.443974234501226, 37.806484743188264],\n            [-122.443966666100792, 37.806460145980267],\n            [-122.443854499850644, 37.806473669177059],\n            [-122.443860373479154, 37.806499667782823],\n            [-122.443717402901086, 37.806526746415607],\n            [-122.443667420469765, 37.806535124185942],\n            [-122.443645087827377, 37.806541672798915],\n            [-122.443622719585392, 37.806546848536961],\n            [-122.443600333023568, 37.806551337843935],\n            [-122.443577910179286, 37.806554454561891],\n            [-122.443581820462299, 37.80657155833741],\n            [-122.443586556699174, 37.806620238019541],\n            [-122.443637961503612, 37.806864552713115],\n            [-122.443633273319165, 37.806883858652405],\n            [-122.443607138425932, 37.806877421799527],\n            [-122.443529794028962, 37.806898611546323],\n            [-122.443526675114754, 37.806911710787858],\n            [-122.443502306167488, 37.806906618541966],\n            [-122.443424944087113, 37.80692712178169],\n            [-122.443421824461069, 37.806940221031809],\n            [-122.443397438234925, 37.806934442320348],\n            [-122.443318380993759, 37.806956346879609],\n            [-122.443315262039832, 37.806969446115438],\n            [-122.443289162740911, 37.806964382337512],\n            [-122.443211818508658, 37.806985571869724],\n            [-122.443208734784008, 37.807000044253662],\n            [-122.443180869538779, 37.806993635816845],\n            [-122.443103524899072, 37.807014825283645],\n            [-122.443102154219687, 37.807028582437368],\n            [-122.443076036948625, 37.80702283217979],\n            [-122.442998692594642, 37.807044021572032],\n            [-122.442995572888506, 37.807057120810583],\n            [-122.442969456307765, 37.807051370518039],\n            [-122.442895554290288, 37.807071816676462],\n            [-122.442876807520932, 37.807083113165248],\n            [-122.442860372447001, 37.807050420945309],\n            [-122.44288643546534, 37.807054111684081],\n            [-122.442950027240173, 37.807036582576075],\n            [-122.442956535452808, 37.807020680875418],\n            [-122.44298955579778, 37.807025630704103],\n            [-122.443044549671924, 37.807010303103709],\n            [-122.443051093786181, 37.806995773988241],\n            [-122.443080653430101, 37.80700078080757],\n            [-122.443137377283094, 37.806985425210136],\n            [-122.443143903403268, 37.806970209382371],\n            [-122.443176924084369, 37.806975159152948],\n            [-122.443230187542511, 37.806959860250316],\n            [-122.443236731623188, 37.806945331399028],\n            [-122.443266291259548, 37.806950338171859],\n            [-122.443323015021249, 37.806934981935846],\n            [-122.443327829074263, 37.806920481311195],\n            [-122.443311249747254, 37.806882297698685],\n            [-122.443357388360823, 37.806925488066938],\n            [-122.443415825168074, 37.806909417378883],\n            [-122.443402365072131, 37.806858134260111],\n            [-122.443446755408701, 37.806900666674323],\n            [-122.443506922515155, 37.806884567152593],\n            [-122.443513466527847, 37.806870038011155],\n            [-122.443541295807862, 37.80687507323001],\n            [-122.443611772372606, 37.806856057118701],\n            [-122.443568098366953, 37.806642517527813],\n            [-122.443490717910933, 37.80666233411602],\n            [-122.443464637369544, 37.806657956797729],\n            [-122.443447387531151, 37.806660301241159],\n            [-122.443394142301429, 37.806676286675255],\n            [-122.443369792101194, 37.80667188082289],\n            [-122.443307715462936, 37.806681144456071],\n            [-122.443302883453555, 37.806694958647995],\n            [-122.443274946458374, 37.806685805052041],\n            [-122.443204469968975, 37.806704820962679],\n            [-122.443178370770624, 37.806699757160004],\n            [-122.443107894250431, 37.806718773287464],\n            [-122.44308352539521, 37.806713680954857],\n            [-122.443013048491437, 37.806732697031315],\n            [-122.442985237265603, 37.806728348120288],\n            [-122.442950738216922, 37.806733036852982],\n            [-122.44291647270785, 37.806746649473865],\n            [-122.442888625547027, 37.806740927400313],\n            [-122.442821555362244, 37.806757827053026],\n            [-122.442793726174969, 37.806752791664266],\n            [-122.442731613479054, 37.806760682403457],\n            [-122.442721537132741, 37.806772522772178],\n            [-122.442695384049358, 37.806765399290242],\n            [-122.442678134861055, 37.806767743884308],\n            [-122.442671608648851, 37.806782959137742],\n            [-122.442640174735473, 37.806772489262421],\n            [-122.44262290758212, 37.806774147415787],\n            [-122.442611226258151, 37.806790821310969],\n            [-122.442619566601024, 37.806844935481941],\n            [-122.4425572741916, 37.806845961528367],\n            [-122.442543113877818, 37.806767907443565],\n            [-122.442812274930034, 37.80673394443496],\n            [-122.442895043918895, 37.806721593264598],\n            [-122.44288439854499, 37.806645541768312],\n            [-122.44286390392358, 37.80665618032328],\n            [-122.442620734223425, 37.806691089066426],\n            [-122.442586432743397, 37.80670332845169],\n            [-122.442569254711358, 37.806708418497294],\n            [-122.442553843632766, 37.806714852893514],\n            [-122.44253845015912, 37.806721973451488],\n            [-122.442524957636692, 37.80672882453954],\n            [-122.442523074304333, 37.806729780720396],\n            [-122.44251132146853, 37.806743708869504],\n            [-122.442504741349737, 37.806756865090371],\n            [-122.442501639536246, 37.806770651023797],\n            [-122.442501980779383, 37.80678369296961],\n            [-122.442511153361352, 37.806803457290563],\n            [-122.442513800037688, 37.806838436574978],\n            [-122.442498190669355, 37.806837320216118],\n            [-122.442500262610309, 37.806850333930662],\n            [-122.442500855308538, 37.806872986207821],\n            [-122.442513308640088, 37.806885829207545],\n            [-122.442543448720272, 37.807045340228143],\n            [-122.442550657494735, 37.807056209147177],\n            [-122.442552729104918, 37.807069222865962],\n            [-122.442549574089071, 37.807080949216051],\n            [-122.442542975993348, 37.807093419280186],\n            [-122.442532880918762, 37.80710457321085],\n            [-122.442521020253452, 37.807114382489758],\n            [-122.442507375020014, 37.80712216152461],\n            [-122.442493693848277, 37.807128567143486],\n            [-122.442476479790699, 37.807132284309965],\n            [-122.44245921255002, 37.807133942439243],\n            [-122.442443603113219, 37.807132825798654],\n            [-122.442426210144987, 37.807129678620242],\n            [-122.442401877121071, 37.807125959012424],\n            [-122.442394668361601, 37.807115090084459],\n            [-122.442382322776425, 37.807106365943092],\n            [-122.442371670941014, 37.807096240723055],\n            [-122.442357217513063, 37.80707312972298],\n            [-122.442353415933297, 37.807060144492915],\n            [-122.442352751441661, 37.807034746485243],\n            [-122.442355888527402, 37.807022333707671],\n            [-122.442327317913012, 37.80685661596673],\n            [-122.442325228043273, 37.806842915816503],\n            [-122.442319678177554, 37.80682927264953],\n            [-122.442314163892661, 37.806817002627895],\n            [-122.442303476176264, 37.806805503986638],\n            [-122.442292807121106, 37.806794692040256],\n            [-122.442278730911681, 37.806785996390175],\n            [-122.44225259583861, 37.806779559791913],\n            [-122.442263912071212, 37.806815083032639],\n            [-122.442334351220893, 37.807191623601256],\n            [-122.442574008114292, 37.807154713430769],\n            [-122.442579917970377, 37.807182085225314],\n            [-122.442316125837024, 37.807222826535309],\n            [-122.442296128149565, 37.807120146892402],\n            [-122.442249660075092, 37.807130526084627],\n            [-122.442243205590515, 37.807148487593025],\n            [-122.442218675723581, 37.80713721717698],\n            [-122.442177362768064, 37.807146137987679],\n            [-122.442169249735414, 37.807166873443258],\n            [-122.44214120495144, 37.807153600709071],\n            [-122.442110219881215, 37.807160291509724],\n            [-122.442102143088249, 37.807182399820256],\n            [-122.442074080353734, 37.807168440637511],\n            [-122.441951871381676, 37.807195175211561],\n            [-122.441943758626778, 37.807215910645752],\n            [-122.441915713871779, 37.807202638132658],\n            [-122.441829644720443, 37.807221223495034],\n            [-122.441823243975179, 37.807241243729116],\n            [-122.441796947879098, 37.807228628850183],\n            [-122.441714321769382, 37.807246470710545],\n            [-122.441706226203735, 37.807267892572305],\n            [-122.441678163511327, 37.807253933295577],\n            [-122.441590382530393, 37.807273233392451],\n            [-122.441583945818934, 37.807291881022437],\n            [-122.441559397367783, 37.80727992432282],\n            [-122.441528412884566, 37.807286614959068],\n            [-122.441522065914484, 37.807308694749302],\n            [-122.4414922725324, 37.807294763645217],\n            [-122.441459575632209, 37.807302169181312],\n            [-122.441451444085047, 37.807322218159875],\n            [-122.441425148030646, 37.807309603472838],\n            [-122.441347676557456, 37.807325986488941],\n            [-122.441341185930639, 37.807342574807244],\n            [-122.441318422046436, 37.807332648592578],\n            [-122.441240951224984, 37.807349031801699],\n            [-122.441234550317162, 37.807369052277899],\n            [-122.441208253583667, 37.807356437279445],\n            [-122.44112907030356, 37.807373535328814],\n            [-122.44112095660725, 37.807394270717083],\n            [-122.44109464263579, 37.807380969523756],\n            [-122.440999993407189, 37.807402442127575],\n            [-122.440987126791967, 37.807373811300785],\n            [-122.441006340540554, 37.807380362365826],\n            [-122.441088966969374, 37.807362521001856],\n            [-122.441071293907683, 37.807282464951484],\n            [-122.441105839449861, 37.807345761826411],\n            [-122.441121664279194, 37.807355115562864],\n            [-122.441199135479366, 37.807338732426437],\n            [-122.441181426400448, 37.807257303252058],\n            [-122.441215936141603, 37.807319227778606],\n            [-122.441235275185775, 37.807330583566994],\n            [-122.44130240044376, 37.80731574411066],\n            [-122.44131244119076, 37.80730253099884],\n            [-122.441289899930467, 37.807234916217887],\n            [-122.441336846017577, 37.807308996562313],\n            [-122.441403970896872, 37.807294157328393],\n            [-122.441389758204977, 37.807214043807704],\n            [-122.441424286396526, 37.807276654700864],\n            [-122.441440110897986, 37.807286008125857],\n            [-122.441505522979043, 37.807271883482372],\n            [-122.441489561884524, 37.807191112300579],\n            [-122.441524179900497, 37.807257155053932],\n            [-122.441538238159126, 37.807265164360558],\n            [-122.441610518456187, 37.807248866405487],\n            [-122.441598394609557, 37.807182453357271],\n            [-122.441630905705154, 37.807234109473121],\n            [-122.441643233270554, 37.80724214726019],\n            [-122.441715531475523, 37.807226535673394],\n            [-122.441722129729229, 37.807214065930424],\n            [-122.441749959016704, 37.807219101571583],\n            [-122.44181537098703, 37.807204977030693],\n            [-122.441821969214871, 37.8071925070075],\n            [-122.441804977662258, 37.807138535507519],\n            [-122.441848068147848, 37.807197571115559],\n            [-122.441911750066581, 37.807183474731303],\n            [-122.441901356658576, 37.80711703321635],\n            [-122.441942735164389, 37.807176783982825],\n            [-122.442002974305893, 37.807163431240092],\n            [-122.44201133876993, 37.807152305853435],\n            [-122.442037401781846, 37.80715599677972],\n            [-122.442094216092357, 37.807144073567486],\n            [-122.44210250836673, 37.807130202449336],\n            [-122.442130373562975, 37.807136610866159],\n            [-122.442190612977058, 37.807123257747243],\n            [-122.4421768301127, 37.807059618983033],\n            [-122.442207521070316, 37.807107871278774],\n            [-122.442221597332434, 37.80711656693682],\n            [-122.442292164942742, 37.807100983492141],\n            [-122.442243704644795, 37.806836704402386],\n            [-122.442167767011739, 37.806845508971712],\n            [-122.442147308147, 37.806857520267137],\n            [-122.442072831477645, 37.806855999790052],\n            [-122.442050696118415, 37.806870098860529],\n            [-122.442038314362932, 37.806860002097636],\n            [-122.44197618353418, 37.806867205731571],\n            [-122.44195577849186, 37.806881276017322],\n            [-122.441943396394166, 37.806871178975598],\n            [-122.441881247592036, 37.806877696126804],\n            [-122.441859148445275, 37.806893168021055],\n            [-122.44184673044974, 37.806881698103723],\n            [-122.441786329930238, 37.806888873147223],\n            [-122.441762464513715, 37.80690300092192],\n            [-122.441750082438688, 37.806892903859939],\n            [-122.441684509153674, 37.806900850470612],\n            [-122.441658930973958, 37.80691569315082],\n            [-122.441646530958224, 37.806904909645212],\n            [-122.441572305572137, 37.806912998636243],\n            [-122.441548386244634, 37.806925066794669],\n            [-122.441465311637984, 37.806925747662177],\n            [-122.441443158191802, 37.806939160185685],\n            [-122.44142908206166, 37.806930464709019],\n            [-122.44136529221457, 37.806940441967662],\n            [-122.441344958843104, 37.806957258153332],\n            [-122.441327368515545, 37.806946560051927],\n            [-122.441311849243505, 37.806948875968999],\n            [-122.441293246205632, 37.806965663663163],\n            [-122.441275637234966, 37.806954278858043],\n            [-122.44126011795251, 37.806956594493755],\n            [-122.441239730706059, 37.80697135136279],\n            [-122.441223906642946, 37.806961997904651],\n            [-122.441151465000161, 37.806972117453661],\n            [-122.441129311490656, 37.806985530192655],\n            [-122.441115235374099, 37.806976834129472],\n            [-122.441037621302598, 37.806987725743447],\n            [-122.44101719810962, 37.807001109708246],\n            [-122.441003139964337, 37.806993100613703],\n            [-122.440922064462001, 37.807004048844455],\n            [-122.440901659189493, 37.807018119222057],\n            [-122.440895696481135, 37.806988688312863],\n            [-122.441218230630383, 37.806943549393232],\n            [-122.441263058469275, 37.806936630972665],\n            [-122.441435464921327, 37.80690975751326],\n            [-122.441433016200833, 37.806882328691337],\n            [-122.441372490336036, 37.806884698487615],\n            [-122.441304935046745, 37.806883063866231],\n            [-122.441274237201611, 37.806900737372722],\n            [-122.441242947719005, 37.806895758576474],\n            [-122.44119330612007, 37.806917177241274],\n            [-122.441110393048419, 37.806924035754299],\n            [-122.441042945411667, 37.806926519579946],\n            [-122.440998117551175, 37.806933437642307],\n            [-122.440918593700744, 37.806937493327403],\n            [-122.440899792827196, 37.80694673020367],\n            [-122.440782200958282, 37.806951412279737],\n            [-122.440680218094201, 37.806957210845738],\n            [-122.44054721454367, 37.806968326842338],\n            [-122.440468121234176, 37.806988856613863],\n            [-122.440371365223044, 37.806995942367976],\n            [-122.440134737571853, 37.807016316747912],\n            [-122.43989673886324, 37.807050447763743],\n            [-122.43967267172718, 37.807087782808438],\n            [-122.439538386124013, 37.807116087220599],\n            [-122.439408914197855, 37.80712989048353],\n            [-122.439239537222377, 37.807140229675355],\n            [-122.439104802429114, 37.807151372502958],\n            [-122.438766800452754, 37.807200879974303],\n            [-122.438299128879564, 37.807256638299236],\n            [-122.437960892936687, 37.807297219553782],\n            [-122.437688625577763, 37.807344957007658],\n            [-122.437514504314919, 37.807372539848515],\n            [-122.437376577168436, 37.807394034049501],\n            [-122.437259217227975, 37.807407636246566],\n            [-122.437146815479664, 37.80741222942855],\n            [-122.437031382840118, 37.807433353744614],\n            [-122.436885215537345, 37.80747077743743],\n            [-122.436714393247598, 37.807492124635075],\n            [-122.436614801865048, 37.807523289038706],\n            [-122.436563088290782, 37.807531692199284],\n            [-122.436507376472989, 37.80751955916309],\n            [-122.436298557575597, 37.807544276277241],\n            [-122.436240083275962, 37.807558970839189],\n            [-122.436155205939471, 37.807556930640864],\n            [-122.435982097955772, 37.80755702547102],\n            [-122.435828364056036, 37.807569849863164],\n            [-122.435859235962624, 37.807691580598309],\n            [-122.433925269093152, 37.807945122059223],\n            [-122.433817271048738, 37.80791942385212],\n            [-122.433787568621852, 37.807908923241605],\n            [-122.433755903391244, 37.807889527355464],\n            [-122.43376416154652, 37.807874283957823],\n            [-122.433933527911989, 37.807929878638532],\n            [-122.435838185171932, 37.807680938490563],\n            [-122.435834169572829, 37.807659716129535],\n            [-122.435737591220459, 37.807673662142683],\n            [-122.433917274710794, 37.807904049527295],\n            [-122.433913385157453, 37.807887631858222],\n            [-122.434041074707324, 37.807871803440726],\n            [-122.434044035083929, 37.807852526536877],\n            [-122.434070401961165, 37.807867888872153],\n            [-122.434160130921143, 37.807856803195897],\n            [-122.434164803061421, 37.807836811488876],\n            [-122.434189457816743, 37.807852888603414],\n            [-122.434284359969311, 37.807841031264509],\n            [-122.434289032075128, 37.807821039552501],\n            [-122.434313686852093, 37.80783711664111],\n            [-122.434406858247897, 37.807825287589175],\n            [-122.434411548897913, 37.80780598229466],\n            [-122.434437915834692, 37.807821344547961],\n            [-122.434520741089443, 37.807811058557668],\n            [-122.434525413482959, 37.8077910671051],\n            [-122.434551797979211, 37.80780711577237],\n            [-122.434636336023075, 37.807796114603541],\n            [-122.434641025919433, 37.807776809311157],\n            [-122.434665680757917, 37.807792886326304],\n            [-122.434750218436946, 37.807781885630661],\n            [-122.434754908641708, 37.807762580053435],\n            [-122.434779544918584, 37.807777970622496],\n            [-122.434871004379232, 37.807766856012883],\n            [-122.434875676329611, 37.807746864551945],\n            [-122.434900331208965, 37.807762941518149],\n            [-122.434976252743482, 37.807753454920828],\n            [-122.434979194289198, 37.807733491570779],\n            [-122.435005579555238, 37.807749540125371],\n            [-122.435084943914418, 37.807739310518855],\n            [-122.43508961579829, 37.807719318774772],\n            [-122.435116018975634, 37.807736053738346],\n            [-122.435191922576223, 37.807725881117662],\n            [-122.435196576876137, 37.807705202655406],\n            [-122.43522297972649, 37.807721937600746],\n            [-122.435302361927597, 37.807712394556091],\n            [-122.435306998308789, 37.807691029930353],\n            [-122.435333419066353, 37.8077084510101],\n            [-122.435402436929863, 37.807699764700374],\n            [-122.435407055040258, 37.807677713642711],\n            [-122.435433494058017, 37.807695821128007],\n            [-122.435507685095942, 37.807686362859421],\n            [-122.435514015990663, 37.807663596960829],\n            [-122.43554045469773, 37.807681704977007],\n            [-122.435609472497859, 37.807673017996969],\n            [-122.435614072648008, 37.807650280497519],\n            [-122.435642259638655, 37.807669046525525],\n            [-122.435711277415109, 37.807660359485872],\n            [-122.43572966745738, 37.807635335465775],\n            [-122.435735467093281, 37.807658589004994],\n            [-122.435830350851532, 37.807646043988242],\n            [-122.435804210559851, 37.807572993499143],\n            [-122.435788619382777, 37.807572562680427],\n            [-122.435772938706066, 37.807568699417203],\n            [-122.435758952605468, 37.807563435158237],\n            [-122.435746625277645, 37.807555396762375],\n            [-122.435735992180923, 37.807545957377542],\n            [-122.435727054339125, 37.807535116438025],\n            [-122.435721558628927, 37.807523531999962],\n            [-122.435719488549438, 37.807510518156661],\n            [-122.435678807510797, 37.807410923656249],\n            [-122.435671271850225, 37.807321086052454],\n            [-122.435659314380757, 37.807260850297496],\n            [-122.435635232905554, 37.807266739353338],\n            [-122.435635716129568, 37.807285273061382],\n            [-122.435609349135348, 37.807269911074314],\n            [-122.435543792417761, 37.807278540984541],\n            [-122.43552006850399, 37.80729815869541],\n            [-122.435504101570785, 37.807283312732451],\n            [-122.435441987649398, 37.807291199357643],\n            [-122.435419994063011, 37.807310788651876],\n            [-122.43540402680101, 37.807295942681101],\n            [-122.435341912858448, 37.807303829253563],\n            [-122.435318189229335, 37.807323446918055],\n            [-122.435302222331032, 37.807308600927854],\n            [-122.435234935185079, 37.80731725906076],\n            [-122.435212941536335, 37.807336848316268],\n            [-122.435196974310131, 37.807322002317541],\n            [-122.435127956788023, 37.807330689056798],\n            [-122.435105963445992, 37.807350278012088],\n            [-122.435089996584352, 37.807335431993209],\n            [-122.435019248678017, 37.807344147060114],\n            [-122.4349972725025, 37.807364422440152],\n            [-122.434981288114457, 37.807348889967201],\n            [-122.434910540175395, 37.807357604694324],\n            [-122.434888564319408, 37.807377880322939],\n            [-122.434872579266241, 37.807362348121238],\n            [-122.434798388810123, 37.807371805710694],\n            [-122.434776412580376, 37.807392081598607],\n            [-122.434758697865377, 37.807376577207712],\n            [-122.434691410594411, 37.807385235030431],\n            [-122.434667704309632, 37.80740553900322],\n            [-122.434651719289548, 37.807390006497108],\n            [-122.434575798078043, 37.807399492611601],\n            [-122.434552092103161, 37.807419796555429],\n            [-122.434536125337331, 37.807404950461788],\n            [-122.434461916577604, 37.807413721960565],\n            [-122.434439940575373, 37.80743399723081],\n            [-122.434423973489999, 37.807419151402279],\n            [-122.434349765034597, 37.807427922275664],\n            [-122.43432605864993, 37.807448226454213],\n            [-122.434310074038208, 37.807432693896239],\n            [-122.434230709546782, 37.807442922713086],\n            [-122.434208733825983, 37.807463198209049],\n            [-122.434192749235578, 37.807447665635237],\n            [-122.434111654710136, 37.807457923293533],\n            [-122.434087948236439, 37.807478226874963],\n            [-122.434071963321998, 37.807462694290493],\n            [-122.433992599465014, 37.807472923210291],\n            [-122.433970605449531, 37.807492512508169],\n            [-122.433954638431118, 37.807477666067122],\n            [-122.433870083452888, 37.807487980028228],\n            [-122.433846376910722, 37.807508283835588],\n            [-122.433830409920873, 37.80749343765234],\n            [-122.43374412454402, 37.807503779895939],\n            [-122.433722148333416, 37.807524055581261],\n            [-122.433706163485638, 37.807508522947344],\n            [-122.433606088798641, 37.807521151358706],\n            [-122.433584112530909, 37.807541426469101],\n            [-122.433574191011218, 37.807492831512612],\n            [-122.433602216513336, 37.807505419848866],\n            [-122.433678120078412, 37.807495247927321],\n            [-122.433686343139499, 37.80747863165486],\n            [-122.433714368662436, 37.807491220239072],\n            [-122.433795462933048, 37.807480962859643],\n            [-122.433803685966325, 37.807464346578953],\n            [-122.433831693616042, 37.80747624842688],\n            [-122.433911075733292, 37.807466706318117],\n            [-122.433919280165583, 37.807449403606796],\n            [-122.433947305702674, 37.807461991586599],\n            [-122.434024939884253, 37.807451791061574],\n            [-122.434034892878572, 37.807435146394688],\n            [-122.434062900561514, 37.80744704846235],\n            [-122.434135379039915, 37.807438305591944],\n            [-122.434143601646298, 37.807421689293108],\n            [-122.434171609334427, 37.807433591060409],\n            [-122.434245800271043, 37.807424133585066],\n            [-122.434254022851178, 37.80740751727847],\n            [-122.434282048436359, 37.807420105453431],\n            [-122.434352778623364, 37.807410704669572],\n            [-122.434361000831984, 37.807394088361185],\n            [-122.434389026430239, 37.807406676510716],\n            [-122.43446148730483, 37.807397247549531],\n            [-122.434471439847925, 37.807380602851318],\n            [-122.43449944756594, 37.807392504266289],\n            [-122.434571926300066, 37.807383761671048],\n            [-122.434581860921966, 37.807366430255094],\n            [-122.434609886547221, 37.807379018352258],\n            [-122.434682347359626, 37.807369588980769],\n            [-122.434690569836022, 37.807352972643585],\n            [-122.434718577593046, 37.807364874555745],\n            [-122.434784152364955, 37.807356931263477],\n            [-122.43479237481715, 37.807340314919166],\n            [-122.434820382585727, 37.807352216807175],\n            [-122.434884226976308, 37.807344301846562],\n            [-122.434894179763575, 37.807327657106839],\n            [-122.434922187543705, 37.807339558970696],\n            [-122.434987762271462, 37.807331615565204],\n            [-122.434997697142578, 37.807314284382969],\n            [-122.435025722825799, 37.807326872656006],\n            [-122.43509127963911, 37.807318242759216],\n            [-122.435101232376525, 37.807301598001935],\n            [-122.435129240172827, 37.807313499542126],\n            [-122.43519308450432, 37.807305584688869],\n            [-122.435201306859327, 37.807288968315881],\n            [-122.435231044678986, 37.807300841444686],\n            [-122.435296619692778, 37.807292898136311],\n            [-122.4353048420232, 37.807276281756089],\n            [-122.435332849842581, 37.807288183247969],\n            [-122.435398406593208, 37.807279553455011],\n            [-122.435406628899386, 37.80726293706762],\n            [-122.435436384982893, 37.8072754965727],\n            [-122.435451904167508, 37.80727318170905],\n            [-122.435460126807058, 37.807256565312237],\n            [-122.435488134643933, 37.807268466767304],\n            [-122.435508433635007, 37.807250278724965],\n            [-122.435536459373509, 37.807262866602237],\n            [-122.435602016078107, 37.80725423669606],\n            [-122.435615572581426, 37.80724302656666],\n            [-122.435634803373304, 37.807250264946127],\n            [-122.435655496065394, 37.807247178418407],\n            [-122.435588180541416, 37.806922774713847],\n            [-122.435582667693723, 37.80691050382292],\n            [-122.435534182131477, 37.806909926104069],\n            [-122.435571873987683, 37.806894886513319],\n            [-122.435559492755871, 37.80688478880149],\n            [-122.435545453103273, 37.806877464940861],\n            [-122.435529719250937, 37.806871542612598],\n            [-122.435514074542013, 37.806869052731017],\n            [-122.435493292470525, 37.806868707065597],\n            [-122.435344768504805, 37.806882132076758],\n            [-122.435121911744176, 37.806899523218981],\n            [-122.434871780421872, 37.806933156410132],\n            [-122.434433666315243, 37.806993908011997],\n            [-122.434416381034183, 37.806994878255509],\n            [-122.434278149661878, 37.807004699734037],\n            [-122.434260827915324, 37.807004297098374],\n            [-122.434243471085438, 37.807002521306472],\n            [-122.434227808507927, 37.806999344820447],\n            [-122.434210379805293, 37.806994823568417],\n            [-122.434194645688478, 37.806988901067868],\n            [-122.434180606150846, 37.806981577044944],\n            [-122.434166548746745, 37.806973567135586],\n            [-122.434154185915276, 37.806964155430045],\n            [-122.434143535561248, 37.806954029186485],\n            [-122.434134579447857, 37.806942501702544],\n            [-122.434125606153387, 37.806930288047226],\n            [-122.434123142595496, 37.806902172634167],\n            [-122.434101162359013, 37.806855835376808],\n            [-122.434090404384904, 37.806841590530418],\n            [-122.434081359579054, 37.806826631135792],\n            [-122.43406669419322, 37.806795281911626],\n            [-122.434061091508866, 37.806779579065555],\n            [-122.434055471636611, 37.80676318977391],\n            [-122.434051599648825, 37.806747458545502],\n            [-122.434049475890433, 37.80673238537485],\n            [-122.434047316368137, 37.806715939336222],\n            [-122.434046440132462, 37.806682304072865],\n            [-122.43401902773472, 37.806493903623902],\n            [-122.434011784506907, 37.806481661588982],\n            [-122.434004523385283, 37.806468732570444],\n            [-122.433998992958308, 37.806455775445059],\n            [-122.433995192532976, 37.806442790224423],\n            [-122.433964135838224, 37.806446732869858],\n            [-122.433932703641176, 37.806436260667759],\n            [-122.433886127202001, 37.806442518242726],\n            [-122.433858853036313, 37.806458760199753],\n            [-122.433839551447093, 37.806448775787949],\n            [-122.433791244999313, 37.806455061692688],\n            [-122.4337639708083, 37.80647130362771],\n            [-122.433744668882483, 37.806461319206093],\n            [-122.433694632079209, 37.806467633442772],\n            [-122.433667357862433, 37.806483875355433],\n            [-122.43364805594635, 37.806473890918021],\n            [-122.433596288440668, 37.806480233488799],\n            [-122.433569014197744, 37.806496475378673],\n            [-122.433549712637699, 37.806486490919518],\n            [-122.433503136480809, 37.806492748337497],\n            [-122.433477574676203, 37.806508275404134],\n            [-122.433458272433256, 37.806498290941384],\n            [-122.433404793122563, 37.806505348218124],\n            [-122.43337751882855, 37.806521590063653],\n            [-122.433358216941642, 37.806511605578869],\n            [-122.4333047372658, 37.806518662815925],\n            [-122.433279192939949, 37.806534876279215],\n            [-122.433259891408895, 37.80652489177271],\n            [-122.433206393839583, 37.806531262531053],\n            [-122.433179119492635, 37.806547504330624],\n            [-122.433159817625679, 37.806537519813411],\n            [-122.43309943408164, 37.806545377123399],\n            [-122.43307215969898, 37.806561618623626],\n            [-122.433052858195936, 37.806551634357852],\n            [-122.432990744619175, 37.806559519418741],\n            [-122.432963470221836, 37.806575761442943],\n            [-122.43294416836936, 37.80656577661594],\n            [-122.432885515114464, 37.806573605181946],\n            [-122.432858276424469, 37.806591219775477],\n            [-122.432851977723161, 37.806548745928424],\n            [-122.432876452705358, 37.806557958985486],\n            [-122.432950642939176, 37.806548502597778],\n            [-122.432958937194059, 37.806534631840726],\n            [-122.432985142526235, 37.806543816515223],\n            [-122.433055872042729, 37.806534416507489],\n            [-122.433064219557167, 37.806522605325611],\n            [-122.433088659156226, 37.806530445464773],\n            [-122.43316111864155, 37.80652101703766],\n            [-122.433169413207281, 37.806507146534614],\n            [-122.433195618216885, 37.806516331442637],\n            [-122.433259444532922, 37.80650773092048],\n            [-122.433269469079946, 37.806493832326176],\n            [-122.433293944088703, 37.806503045022133],\n            [-122.433357787918965, 37.806495131161043],\n            [-122.433366082106133, 37.806481260924372],\n            [-122.433392287462695, 37.806490445233919],\n            [-122.433454400931879, 37.806482559686899],\n            [-122.433462695099607, 37.806468689443392],\n            [-122.433488900463672, 37.806477873731517],\n            [-122.433552726373762, 37.806469273330841],\n            [-122.433561038053369, 37.806456089520083],\n            [-122.433587243771214, 37.806465273780709],\n            [-122.433649339320041, 37.806456701696959],\n            [-122.43365765098109, 37.806443517879387],\n            [-122.433683838827505, 37.806452015684521],\n            [-122.433745952233608, 37.806444129983937],\n            [-122.433754245996482, 37.806430259725488],\n            [-122.43378045172912, 37.806439443943233],\n            [-122.433842565114432, 37.806431558191733],\n            [-122.433850858857866, 37.806417687926462],\n            [-122.433877064598036, 37.806426872122742],\n            [-122.433932274491454, 37.806419786251801],\n            [-122.433954393037553, 37.806405002549944],\n            [-122.433958408377592, 37.806426224974288],\n            [-122.43399121327684, 37.806422940114579],\n            [-122.433989124957407, 37.806409239815579],\n            [-122.433988785204093, 37.806396197569725],\n            [-122.433991530954188, 37.80636868345389],\n            [-122.433981985185667, 37.806334503629429],\n            [-122.433942822534163, 37.806226642930824],\n            [-122.433950330141514, 37.806182569274938],\n            [-122.433940283395017, 37.806129169298679],\n            [-122.433873424076822, 37.805821925113698],\n            [-122.433853509955952, 37.80572198947479],\n            [-122.433844483207295, 37.805707716218421],\n            [-122.433823004146618, 37.805680599328511],\n            [-122.433808821860836, 37.805667784061214],\n            [-122.433796405313814, 37.805656313293774],\n            [-122.433780528829729, 37.805644899532474],\n            [-122.433766418068998, 37.805634829721896],\n            [-122.43374886455932, 37.805625503361618],\n            [-122.433731328933206, 37.805616863432853],\n            [-122.433713811190202, 37.805608909935621],\n            [-122.433675422356743, 37.805597178553839],\n            [-122.433656263344702, 37.805592685320363],\n            [-122.433637140790879, 37.805589565215314],\n            [-122.433616323330028, 37.805587846350818],\n            [-122.43359552375523, 37.805586814191379],\n            [-122.433576472363242, 37.805586439544221],\n            [-122.433535016220674, 37.805589866131839],\n            [-122.433516072105618, 37.805593610629053],\n            [-122.433498858293476, 37.805597326206154],\n            [-122.432719648836795, 37.805721348282255],\n            [-122.432704236424613, 37.805727781678655],\n            [-122.432688842557212, 37.805734900946824],\n            [-122.432673484086777, 37.805743393361759],\n            [-122.432659891674504, 37.805753230287664],\n            [-122.432648029581884, 37.805763038856455],\n            [-122.432637933547895, 37.805774191937303],\n            [-122.432627855379891, 37.805786031451547],\n            [-122.432619525401748, 37.805798529044132],\n            [-122.432612943621251, 37.805811684990026],\n            [-122.432606379692928, 37.805825526820627],\n            [-122.432603276080599, 37.805839312496055],\n            [-122.432600137407348, 37.805851724742368],\n            [-122.432500317433352, 37.805940575058216],\n            [-122.432391913920043, 37.805965699763121],\n            [-122.432397426855275, 37.805977970519415],\n            [-122.432516390332239, 37.805959539768239],\n            [-122.432628376560629, 37.805872550799698],\n            [-122.432642540805432, 37.805884679499613],\n            [-122.432527165744716, 37.805974471194816],\n            [-122.432403189584164, 37.805999851361079],\n            [-122.43238951051228, 37.806072868560939],\n            [-122.432404443045669, 37.806114514237365],\n            [-122.432490452847844, 37.806359639916195],\n            [-122.43244910342851, 37.806367184726966],\n            [-122.432520431119073, 37.806580274784345],\n            [-122.432498061398036, 37.806585448423995],\n            [-122.432424985907915, 37.806371700265615],\n            [-122.432400868391511, 37.806376216073893],\n            [-122.432301460176419, 37.806081865240721],\n            [-122.43236869294482, 37.806071149476409],\n            [-122.432380802509087, 37.806004338539104],\n            [-122.43235326017701, 37.806010283635004],\n            [-122.432347175872593, 37.805976047248159],\n            [-122.432319651407369, 37.805982678495994],\n            [-122.432302633632929, 37.805993944955773],\n            [-122.432282173360747, 37.806005686719168],\n            [-122.432280461289963, 37.806006669320666],\n            [-122.432265120589491, 37.806015848390899],\n            [-122.432249654811855, 37.806020221869829],\n            [-122.432227231644546, 37.806023336155739],\n            [-122.432194784491401, 37.806040349205666],\n            [-122.432179693529363, 37.806059138076137],\n            [-122.432171560304809, 37.806079186408191],\n            [-122.432165157068255, 37.806099206671476],\n            [-122.432169117939694, 37.806118369860684],\n            [-122.432192469776069, 37.806150950175152],\n            [-122.432209844223067, 37.806153412703416],\n            [-122.432227007583123, 37.806214250207013],\n            [-122.432213468614208, 37.806226146108848],\n            [-122.432203354593938, 37.806236612991817],\n            [-122.432202178080502, 37.806257920803567],\n            [-122.43220649593593, 37.806290812956746],\n            [-122.432221571326565, 37.806337949854225],\n            [-122.432306430556167, 37.806804907302421],\n            [-122.432383452345448, 37.807103744994606],\n            [-122.432564682589728, 37.808479722911237],\n            [-122.432615131844727, 37.808489197136744],\n            [-122.432610261989339, 37.808501638018015],\n            [-122.432598381927761, 37.808510760137189],\n            [-122.432584628175405, 37.80851441914389],\n            [-122.432342320320956, 37.808516329227118],\n            [-122.432078432185492, 37.807223425689031],\n            [-122.431999351719028, 37.807178023752513],\n            [-122.431443837686672, 37.8072509882197],\n            [-122.431386630568454, 37.807314417209206],\n            [-122.431727121174518, 37.8090235974717],\n            [-122.431341938476805, 37.809056687748011],\n            [-122.430994999022246, 37.807365467556572],\n            [-122.430915919059899, 37.807320064620313],\n            [-122.430360402907354, 37.807393024257635],\n            [-122.430303194747026, 37.8074564524454],\n            [-122.43062118366457, 37.809166690185819],\n            [-122.430108306409053, 37.8092156017048],\n            [-122.429835000067499, 37.807825332620489],\n            [-122.429264030219372, 37.807903346686288],\n            [-122.429270969868725, 37.807970532619663],\n            [-122.428236680346103, 37.808405663457563],\n            [-122.427001622006514, 37.808109268493716],\n            [-122.426995537909036, 37.808141644021028],\n            [-122.427545144293163, 37.808640840008444],\n            [-122.428014153033857, 37.808569308327186],\n            [-122.428043639051282, 37.808704798333089],\n            [-122.427533242566099, 37.808782500069931],\n            [-122.427523467767188, 37.808739396087624],\n            [-122.427301030953672, 37.808773247159571],\n            [-122.427287243070779, 37.808708920235645],\n            [-122.427490716316399, 37.808678126013014],\n            [-122.426909232318224, 37.808151294840016],\n            [-122.426839001555635, 37.808113298884024],\n            [-122.426613230594512, 37.808152010516238],\n            [-122.426560904719622, 37.807943556174827],\n            [-122.426488519796493, 37.807651861239542],\n            [-122.426357658303132, 37.807388412718332],\n            [-122.426257507799036, 37.807185417079722],\n            [-122.426066893674061, 37.80699787612533],\n            [-122.4257796482039, 37.806715259689653],\n            [-122.425627533230582, 37.806456787332273],\n            [-122.425531817297653, 37.806150610629167],\n            [-122.425487611207828, 37.806009959663712],\n            [-122.425486832357592, 37.806006120121552],\n            [-122.425298563577385, 37.80507790289635],\n            [-122.425204919000421, 37.804616199985951],\n            [-122.425109521046082, 37.804145845932808],\n            [-122.424918804152185, 37.803205508171892],\n            [-122.424808684502352, 37.802662664019884],\n            [-122.424731259133807, 37.802280983275736],\n            [-122.424731635083759, 37.802281009278651],\n            [-122.424538324517613, 37.801303719934985],\n            [-122.424517597070277, 37.801201189595353],\n            [-122.424358667137113, 37.800415027771606],\n            [-122.424242907462499, 37.799842398528341],\n            [-122.42417056511286, 37.799484537126212],\n            [-122.42417056430692, 37.799484532744323],\n            [-122.423982145098208, 37.798552453684835],\n            [-122.423982396975646, 37.798552421556877],\n            [-122.423793785438747, 37.7976164776058],\n            [-122.423792964234153, 37.797616581376253],\n            [-122.423601296960271, 37.796691058659476],\n            [-122.42360129621845, 37.796691056748735],\n            [-122.423601876988513, 37.796690945913937],\n            [-122.423601877370103, 37.796690947281171],\n            [-122.425249874473522, 37.79648130315109],\n            [-122.426892728667355, 37.796272251947869],\n            [-122.428537813517494, 37.796062892623418],\n            [-122.430182832162615, 37.795853517593606],\n            [-122.431827832563371, 37.79564412085616],\n            [-122.433469062960725, 37.795435179810816],\n            [-122.435113289920963, 37.795225833115033],\n            [-122.436760975027326, 37.795016021968365],\n            [-122.438402484723895, 37.794806973138577]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 7, ZIP_CODE: 94133, ID: 94133},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.418530631419571, 37.804999043218864],\n            [-122.418718265666911, 37.805931520041717],\n            [-122.418781807492508, 37.806241018338142],\n            [-122.418781854642702, 37.806241248585302],\n            [-122.418856183917853, 37.806607498073362],\n            [-122.418908043871269, 37.806863030947284],\n            [-122.419086067910683, 37.807803117635643],\n            [-122.419223966222532, 37.808453096037454],\n            [-122.419103158585543, 37.808467422639801],\n            [-122.419122467317635, 37.808544708340989],\n            [-122.418980965776527, 37.808562118993599],\n            [-122.418965099874526, 37.808484090255142],\n            [-122.417886530686005, 37.80861494396968],\n            [-122.417923033396889, 37.808821741173873],\n            [-122.417843612318336, 37.80882989992265],\n            [-122.417811191268285, 37.808647071958433],\n            [-122.417726330574766, 37.808645704666795],\n            [-122.417731886802656, 37.808592736810425],\n            [-122.417624531730937, 37.808591735208473],\n            [-122.417601699719071, 37.808579058949235],\n            [-122.41748929463067, 37.808583633129054],\n            [-122.417485092698755, 37.808420948020817],\n            [-122.417586902421718, 37.808408305143338],\n            [-122.417575917233663, 37.808385134844094],\n            [-122.417669145522396, 37.808375378317514],\n            [-122.41765313920358, 37.808291858459306],\n            [-122.41737529132557, 37.808325217992042],\n            [-122.417344584298249, 37.808141675039998],\n            [-122.416347061956941, 37.808260209523006],\n            [-122.416354024690548, 37.808328768820694],\n            [-122.416305697821443, 37.808334361125667],\n            [-122.416338782629509, 37.80847597548388],\n            [-122.41718410046191, 37.808496573566089],\n            [-122.417183326975007, 37.808533669258267],\n            [-122.416327431803367, 37.808505689336052],\n            [-122.416281962820022, 37.808487886292838],\n            [-122.416150789583881, 37.80850306555427],\n            [-122.416144807019506, 37.808539558863203],\n            [-122.416207242955323, 37.808544038257011],\n            [-122.416187547333237, 37.808854071166344],\n            [-122.41742892570683, 37.808927290365972],\n            [-122.417412600540189, 37.80916653558441],\n            [-122.418193962887401, 37.809680962781762],\n            [-122.41929170565983, 37.810403664792204],\n            [-122.41931534577914, 37.810380618458765],\n            [-122.419422885954745, 37.810454266609824],\n            [-122.419833484960733, 37.810735462513406],\n            [-122.419807989843818, 37.810753732347422],\n            [-122.420595101766708, 37.811266948659416],\n            [-122.420353825913267, 37.811509857466909],\n            [-122.420001661760068, 37.81128073132367],\n            [-122.419845148459302, 37.811320361929653],\n            [-122.419951835473171, 37.811563099184333],\n            [-122.419915762044113, 37.811573987538154],\n            [-122.419724362099529, 37.811134852199615],\n            [-122.41972295750638, 37.81113418613959],\n            [-122.419216088646508, 37.810893843097361],\n            [-122.419185739151644, 37.810925239548666],\n            [-122.419097184597589, 37.810915006165231],\n            [-122.418987802638568, 37.810835752698928],\n            [-122.418935347953351, 37.810882616404598],\n            [-122.419125945102621, 37.811023040645175],\n            [-122.419048154317267, 37.811094352280243],\n            [-122.419465867632354, 37.811381473037407],\n            [-122.419350858543552, 37.811486353332427],\n            [-122.418846744437559, 37.811473267417028],\n            [-122.416831664498375, 37.810133272229294],\n            [-122.415352404138332, 37.809160867759147],\n            [-122.415205531523597, 37.809171493557926],\n            [-122.415205867990196, 37.809184535850846],\n            [-122.415200978155667, 37.80919628956385],\n            [-122.415187329670815, 37.809204065160294],\n            [-122.415151663626474, 37.809230739831797],\n            [-122.415171052667063, 37.809244159741915],\n            [-122.415194871121983, 37.809227978313764],\n            [-122.415413019588954, 37.809363153673537],\n            [-122.415370856947149, 37.809406415569271],\n            [-122.415142166917732, 37.809265230241984],\n            [-122.415155744585249, 37.809254708904987],\n            [-122.415139781310558, 37.809239860465802],\n            [-122.415039713249854, 37.809185860695372],\n            [-122.41488880052961, 37.80924118848273],\n            [-122.414968316086458, 37.80930376277346],\n            [-122.414992913705149, 37.809317784591791],\n            [-122.415006420575594, 37.809304517526144],\n            [-122.415222785388849, 37.809437662297633],\n            [-122.415180622939999, 37.809480924119129],\n            [-122.41496425806919, 37.809347778996823],\n            [-122.414981136896941, 37.8093310235652],\n            [-122.41495316698277, 37.809320490118694],\n            [-122.414845930117963, 37.809256992445782],\n            [-122.414780776445994, 37.809281398852669],\n            [-122.414774461081151, 37.809372149219335],\n            [-122.415340036749569, 37.80975508488244],\n            [-122.415288518799699, 37.809838328383556],\n            [-122.41456302792659, 37.80956443044294],\n            [-122.414023978762671, 37.809471544827012],\n            [-122.413420668373689, 37.809236173514122],\n            [-122.41337430449579, 37.809317959368158],\n            [-122.413395600212141, 37.809338215320878],\n            [-122.413411510435665, 37.809351004954635],\n            [-122.413425672211702, 37.809363136228626],\n            [-122.413441564407123, 37.809375239427176],\n            [-122.413455726199771, 37.809387370972054],\n            [-122.413471618398191, 37.809399473891929],\n            [-122.413487492569899, 37.80941089065314],\n            [-122.413505097854454, 37.809422279326348],\n            [-122.413520972382244, 37.809433696077356],\n            [-122.413538559637843, 37.80944439831444],\n            [-122.413554416481929, 37.809455128624236],\n            [-122.413572003401356, 37.809465830861896],\n            [-122.413607142559428, 37.809485862444731],\n            [-122.413624694804525, 37.809495192064489],\n            [-122.414282035042135, 37.809946003161961],\n            [-122.414200903764723, 37.81002217256772],\n            [-122.414027337823839, 37.810138985552911],\n            [-122.413780738047123, 37.809907441000853],\n            [-122.413857049972947, 37.809845771015681],\n            [-122.412616832608151, 37.809078904365926],\n            [-122.412498775807535, 37.80906571088461],\n            [-122.412398060995869, 37.809120908451149],\n            [-122.412400109349747, 37.809133236262518],\n            [-122.412396967167027, 37.809145648237823],\n            [-122.412390346141294, 37.809157430172931],\n            [-122.412380210884578, 37.809167208369729],\n            [-122.412426492762847, 37.809216588736774],\n            [-122.412450629760414, 37.809212763732177],\n            [-122.41262839277087, 37.809393236325491],\n            [-122.412573868329858, 37.809427083257212],\n            [-122.412396105370902, 37.809246610582868],\n            [-122.412412861666652, 37.809225050449037],\n            [-122.412356250722411, 37.809177897728922],\n            [-122.41234073008107, 37.809180209833173],\n            [-122.412323390603305, 37.809179117259049],\n            [-122.412307675451856, 37.809173878278465],\n            [-122.412099340782589, 37.809285071655154],\n            [-122.412099676704088, 37.809298113956672],\n            [-122.412096516784118, 37.809309839486673],\n            [-122.412089895696084, 37.809321621404543],\n            [-122.412078012629308, 37.8093307411864],\n            [-122.412124294062423, 37.809380121676121],\n            [-122.412148413801376, 37.809375610565525],\n            [-122.412315935400727, 37.809561743172587],\n            [-122.412252793608843, 37.809597103119742],\n            [-122.412093889154491, 37.809409457275514],\n            [-122.41211066290181, 37.809388583353197],\n            [-122.412057512853792, 37.809341374382768],\n            [-122.412040244067796, 37.809343027789311],\n            [-122.412024634631933, 37.809341907398313],\n            [-122.412008919813204, 37.809336668372488],\n            [-122.411894272206808, 37.809388657747697],\n            [-122.412861765933144, 37.810522545447967],\n            [-122.412718336607824, 37.810599724430624],\n            [-122.412705816724809, 37.810584132828346],\n            [-122.411509089676429, 37.81123738012537],\n            [-122.41148957739901, 37.811219154555417],\n            [-122.412686215557258, 37.810562475550952],\n            [-122.411452462026304, 37.809105334882879],\n            [-122.411423380532113, 37.809118854220145],\n            [-122.411248622782082, 37.808920476073887],\n            [-122.411085557936261, 37.808907323817536],\n            [-122.411283571142818, 37.809134854169606],\n            [-122.411256255700124, 37.809149718017444],\n            [-122.411031438641331, 37.808889659210472],\n            [-122.410955336187001, 37.808892265747417],\n            [-122.410765573350616, 37.808850703243849],\n            [-122.410736033164511, 37.808846374759867],\n            [-122.410731512966407, 37.808872543480319],\n            [-122.410649866328171, 37.808861505447226],\n            [-122.410652655797975, 37.808835364493682],\n            [-122.410546641899074, 37.808819227353638],\n            [-122.410441958004114, 37.809325664485378],\n            [-122.410406413493931, 37.809289843904054],\n            [-122.410401787512413, 37.809311893981061],\n            [-122.410630331289113, 37.809716792194763],\n            [-122.410709259423612, 37.809689418065965],\n            [-122.410633083668301, 37.809554681184579],\n            [-122.41064851611452, 37.809548937394226],\n            [-122.410729547478269, 37.809670547859675],\n            [-122.410757004560026, 37.809661175631867],\n            [-122.410712947997226, 37.809563688219221],\n            [-122.410728380093076, 37.809557944424022],\n            [-122.410791312651469, 37.80964894551029],\n            [-122.410822177556227, 37.809637457886339],\n            [-122.410778085579025, 37.80953859734884],\n            [-122.410793518010678, 37.809532853539501],\n            [-122.410858216047515, 37.809625199709956],\n            [-122.410889098254941, 37.809614398511123],\n            [-122.410843240792332, 37.809514193154044],\n            [-122.410858672867818, 37.809508449341749],\n            [-122.410923406302985, 37.809602168351013],\n            [-122.410950845665482, 37.809592109640889],\n            [-122.410904952795121, 37.809490531433248],\n            [-122.410920384860916, 37.809484787612853],\n            [-122.410985153693048, 37.809579879462774],\n            [-122.411024617975485, 37.809566192280315],\n            [-122.410978689334627, 37.809463241232329],\n            [-122.410994104065225, 37.809456810959603],\n            [-122.411058925978935, 37.809553962080649],\n            [-122.411089808473605, 37.809543160823544],\n            [-122.411042113998747, 37.809438864964896],\n            [-122.411057546043196, 37.809433121126496],\n            [-122.411195348876859, 37.80967536169041],\n            [-122.41117818636998, 37.809681133587063],\n            [-122.411116453591873, 37.809569511282646],\n            [-122.41104955067469, 37.80959325722408],\n            [-122.41111476155065, 37.809705510201134],\n            [-122.411097598684464, 37.809711282091641],\n            [-122.411034100233891, 37.80959831463543],\n            [-122.410977509113948, 37.809619146556997],\n            [-122.411041007143098, 37.809732113774253],\n            [-122.411025592359422, 37.809738544052792],\n            [-122.410960346257056, 37.809624918427609],\n            [-122.410907162593759, 37.809643635209717],\n            [-122.410972426300958, 37.809757947300923],\n            [-122.410956993836351, 37.80976369113344],\n            [-122.410891730147881, 37.809649379033807],\n            [-122.410835138249837, 37.809670210623501],\n            [-122.410900419538606, 37.809785209191027],\n            [-122.410884987408849, 37.809790953008481],\n            [-122.410819706138938, 37.809675954432528],\n            [-122.410764809648242, 37.809695385622547],\n            [-122.410830108511632, 37.809811070390921],\n            [-122.41081467602497, 37.809816814204737],\n            [-122.410747646757216, 37.80970115746215],\n            [-122.410639354408318, 37.809731067210933],\n            [-122.410896947885561, 37.810188372007936],\n            [-122.410975841429376, 37.810159624823598],\n            [-122.410901536485113, 37.810030351311333],\n            [-122.410916969009435, 37.810024607486383],\n            [-122.410997965998874, 37.810144845157168],\n            [-122.41103912533255, 37.810129757339681],\n            [-122.410977003992286, 37.810003033077805],\n            [-122.410992436505126, 37.809997289242951],\n            [-122.411075146003029, 37.810116812392209],\n            [-122.411112880121649, 37.810103153220581],\n            [-122.411050758345823, 37.809976429551675],\n            [-122.411066208857108, 37.809971371863952],\n            [-122.411147188358683, 37.810090922996501],\n            [-122.411190078057032, 37.810075806811575],\n            [-122.411127956511336, 37.809949082902484],\n            [-122.411143389001253, 37.809943339047805],\n            [-122.411224368596535, 37.81006289012803],\n            [-122.411256964037719, 37.810051374350031],\n            [-122.411194842059288, 37.809924650755768],\n            [-122.41121027488505, 37.809918906886708],\n            [-122.4112929849924, 37.810038429879377],\n            [-122.411332431435511, 37.81002405588923],\n            [-122.411270309344076, 37.809897332059471],\n            [-122.411285742158299, 37.809891588180513],\n            [-122.411366721926342, 37.810011139164168],\n            [-122.411401047752832, 37.809999595302976],\n            [-122.411337195490745, 37.809872899820583],\n            [-122.411354358375661, 37.809867127895387],\n            [-122.411435338227506, 37.809986678832487],\n            [-122.411471359105562, 37.809973733758071],\n            [-122.411409237165202, 37.809847009994684],\n            [-122.411424669612117, 37.809841266103092],\n            [-122.41150566722375, 37.80996150342942],\n            [-122.411546826352634, 37.809946415434261],\n            [-122.411484704299241, 37.809819691435401],\n            [-122.411500136734659, 37.809813947533918],\n            [-122.411582846845619, 37.809933470331323],\n            [-122.4116188680253, 37.809920525480692],\n            [-122.411556745877689, 37.809793801793724],\n            [-122.411572177956074, 37.809788057888397],\n            [-122.411653176094603, 37.809908295109032],\n            [-122.4116909096619, 37.809894636032141],\n            [-122.411628787405917, 37.809767912107915],\n            [-122.411644219819436, 37.809762168187518],\n            [-122.411725217707357, 37.809882405639449],\n            [-122.411759525734439, 37.809870174962185],\n            [-122.411697403388885, 37.809743451348098],\n            [-122.411712835792031, 37.809737707418712],\n            [-122.411881278427131, 37.810026834486933],\n            [-122.41186411554628, 37.81003260648864],\n            [-122.411782586611622, 37.809891776308959],\n            [-122.411719126796399, 37.80991478009306],\n            [-122.411802385721074, 37.810055582271964],\n            [-122.411785222827419, 37.810061354262146],\n            [-122.411703694356945, 37.809920524023092],\n            [-122.411645389734517, 37.809942070760108],\n            [-122.411726900787329, 37.810082214596392],\n            [-122.411711467967621, 37.810087958532876],\n            [-122.411628227200978, 37.809947842721932],\n            [-122.41156305384375, 37.809971560917056],\n            [-122.411644564773539, 37.810111704809465],\n            [-122.411629131941311, 37.810117448735141],\n            [-122.411547621726527, 37.809977304820983],\n            [-122.411485874174801, 37.809999594002001],\n            [-122.411567384297499, 37.810139737958238],\n            [-122.411551952145643, 37.810145481862563],\n            [-122.411470441699677, 37.81000533790143],\n            [-122.411408676427612, 37.810026940604999],\n            [-122.411490204455376, 37.81016777104513],\n            [-122.41147477194562, 37.81017351494495],\n            [-122.411393243940836, 37.810032684494281],\n            [-122.411328070780158, 37.81005640255357],\n            [-122.41140959834128, 37.810197233054254],\n            [-122.411394148491496, 37.81020229050096],\n            [-122.41131090784944, 37.810062174474744],\n            [-122.411249177828495, 37.810085149968437],\n            [-122.411330687944414, 37.810225294080347],\n            [-122.4113152554104, 37.810231037959227],\n            [-122.411232014885002, 37.810090921878086],\n            [-122.411175405434363, 37.810111067735896],\n            [-122.411256933104966, 37.81025189806045],\n            [-122.411241500559697, 37.810257641929645],\n            [-122.411159972912074, 37.810116811594561],\n            [-122.411103380733024, 37.810137643311123],\n            [-122.411184890977466, 37.810277787516789],\n            [-122.411169458075165, 37.810283531382133],\n            [-122.411087948545713, 37.810143387154753],\n            [-122.411017619235537, 37.810168562307894],\n            [-122.411099128998927, 37.810308706303054],\n            [-122.411083696429642, 37.810314450151544],\n            [-122.411004005467134, 37.81017771001823],\n            [-122.410905971459442, 37.810202646996828],\n            [-122.411216101543474, 37.810750434096292],\n            [-122.411307020324173, 37.810718058221234],\n            [-122.411214614556201, 37.8105581756828],\n            [-122.411231794935318, 37.81055309021869],\n            [-122.411325542368957, 37.810697843090367],\n            [-122.411366719609191, 37.810683441322411],\n            [-122.411291830600106, 37.810531515646439],\n            [-122.411307262852333, 37.810525771776568],\n            [-122.411402864908169, 37.810675301597549],\n            [-122.411522935608915, 37.810632152260467],\n            [-122.411446209498621, 37.810476136111049],\n            [-122.411461659747943, 37.810471078652185],\n            [-122.411555407533228, 37.810615831617966],\n            [-122.41160859172949, 37.810597114547022],\n            [-122.411624041992908, 37.810592057067005],\n            [-122.411677226161913, 37.810573340239586],\n            [-122.411600605842565, 37.810421442809904],\n            [-122.411617768833821, 37.810415670846979],\n            [-122.411711534523249, 37.810561109852458],\n            [-122.411738974133357, 37.810551050960818],\n            [-122.411686385419046, 37.810391210096554],\n            [-122.411701817957677, 37.810385466169308],\n            [-122.411764700631707, 37.810541706828857],\n            [-122.411829892102887, 37.810518674961713],\n            [-122.41176529620391, 37.810363148827776],\n            [-122.411780746061879, 37.81035809133261],\n            [-122.411847054752684, 37.810512902971148],\n            [-122.411915671345156, 37.810488441813206],\n            [-122.411840781416544, 37.810336516754752],\n            [-122.411856213585779, 37.81033077281284],\n            [-122.411935941632692, 37.810468884965481],\n            [-122.411921351800245, 37.810305681309181],\n            [-122.411938638145514, 37.810304714362765],\n            [-122.411953405498025, 37.81047478237366],\n            [-122.412071780474818, 37.810433033401743],\n            [-122.412010591984938, 37.810275392256564],\n            [-122.412115642263785, 37.810455670909619],\n            [-122.412100192408317, 37.810460728448561],\n            [-122.412084406280471, 37.810452743692409],\n            [-122.41176878056207, 37.810565675738737],\n            [-122.41175334799118, 37.810571419674538],\n            [-122.411684748938015, 37.810596566899896],\n            [-122.411669298331702, 37.810601624393506],\n            [-122.411598969130708, 37.810626799885483],\n            [-122.411583536533968, 37.810632543799024],\n            [-122.411518344886403, 37.810655575529537],\n            [-122.411502912623547, 37.810661319426885],\n            [-122.411432582949345, 37.810686494275835],\n            [-122.411417150327338, 37.81069223816754],\n            [-122.411223323763636, 37.810761991358582],\n            [-122.41132311146211, 37.810939609065692],\n            [-122.411244573940635, 37.811116682909216],\n            [-122.410949479920689, 37.811220352571489],\n            [-122.410930216342933, 37.811279036146971],\n            [-122.410681455051716, 37.81136684679597],\n            [-122.410613578346386, 37.811352838185535],\n            [-122.410419696661208, 37.811420531005773],\n            [-122.410148367227194, 37.811505958616635],\n            [-122.408511879957302, 37.811283856992631],\n            [-122.407577358643394, 37.808803424612513],\n            [-122.407596251976869, 37.808797625245276],\n            [-122.408535735474004, 37.811269050069917],\n            [-122.410153064417187, 37.811486654300801],\n            [-122.41040703579597, 37.811399447392162],\n            [-122.410397994853795, 37.811384485925792],\n            [-122.410199185064457, 37.81132796106332],\n            [-122.410033026138677, 37.811396577420801],\n            [-122.410028523199003, 37.811423432538689],\n            [-122.408569038736545, 37.811217693403208],\n            [-122.408604939400348, 37.811065346992223],\n            [-122.408622314546491, 37.811067812437848],\n            [-122.408601547208733, 37.811202746444437],\n            [-122.408639776140404, 37.811208308039326],\n            [-122.408681379486623, 37.811075784005041],\n            [-122.408700502750108, 37.811078908147074],\n            [-122.408681466003685, 37.811213813888727],\n            [-122.408723173522517, 37.811220006160561],\n            [-122.408763046269982, 37.811087510102226],\n            [-122.408782169539933, 37.811090634230986],\n            [-122.40876313259281, 37.811225539991135],\n            [-122.408803092361268, 37.811231073796982],\n            [-122.408842964625947, 37.811098577717516],\n            [-122.408862088248199, 37.811101701827724],\n            [-122.408843051796623, 37.811236607869454],\n            [-122.408881280424794, 37.811242169666208],\n            [-122.408921153248102, 37.811109673549396],\n            [-122.408942006980325, 37.811112769370261],\n            [-122.408921240203938, 37.811247703157434],\n            [-122.408964677875915, 37.811253867342053],\n            [-122.409004568208587, 37.811122057634492],\n            [-122.409025404309773, 37.811124467552588],\n            [-122.409006385440833, 37.811260059513721],\n            [-122.409042866672721, 37.811264962818356],\n            [-122.409082738873835, 37.811132466653085],\n            [-122.409101862168811, 37.811135590729982],\n            [-122.409082825791046, 37.81127049654058],\n            [-122.409126264189027, 37.81127666092906],\n            [-122.409166136247578, 37.811144164735886],\n            [-122.409185259542042, 37.811147288524687],\n            [-122.409166223312823, 37.811282194348451],\n            [-122.409211409832309, 37.811289016857089],\n            [-122.409251281745554, 37.811156520635443],\n            [-122.40927213551177, 37.811159616672512],\n            [-122.40925136932222, 37.811294550516585],\n            [-122.409279162626277, 37.81129822094033],\n            [-122.40932078253644, 37.811166383119904],\n            [-122.409339888202226, 37.811168820995697],\n            [-122.409320870242325, 37.811304413000762],\n            [-122.409364308333835, 37.811310577307111],\n            [-122.409405910098016, 37.811178053025465],\n            [-122.409425033757373, 37.811181176769772],\n            [-122.409405998308287, 37.811316082900326],\n            [-122.40944074878638, 37.811321014101964],\n            [-122.40948063795679, 37.811189203966087],\n            [-122.409501474085218, 37.811191613525516],\n            [-122.409480725959895, 37.811327233846249],\n            [-122.409527624648632, 37.811333341792235],\n            [-122.409567496367472, 37.811200845459226],\n            [-122.409588350159311, 37.811203941440354],\n            [-122.40956758453207, 37.81133887533899],\n            [-122.409607544075371, 37.811344408877801],\n            [-122.409647432976385, 37.811212599235269],\n            [-122.409666538650669, 37.811215036508962],\n            [-122.4096475212896, 37.811350629114692],\n            [-122.409687480492039, 37.811356162357413],\n            [-122.409729082397121, 37.811223637951706],\n            [-122.409748205742758, 37.811226761923784],\n            [-122.40972917051603, 37.811361667836366],\n            [-122.40976739996718, 37.811367229334621],\n            [-122.409807270930216, 37.811234732926998],\n            [-122.409826394289098, 37.811237857160975],\n            [-122.409807359540622, 37.811372762805725],\n            [-122.409849067242604, 37.811378954679277],\n            [-122.409888938066231, 37.811246458244362],\n            [-122.409909791884289, 37.81124955416864],\n            [-122.409890757298484, 37.811384460375677],\n            [-122.409934195469972, 37.811390623922954],\n            [-122.409972335687982, 37.811258155482349],\n            [-122.409991388408557, 37.811258533667043],\n            [-122.410051001542016, 37.811355083215751],\n            [-122.410080225111116, 37.811347055723829],\n            [-122.410039452733983, 37.811242647397464],\n            [-122.410060076955276, 37.811236819614464],\n            [-122.410111355764599, 37.81134586484395],\n            [-122.410186754248173, 37.811315801374953],\n            [-122.410002034752523, 37.810999466686859],\n            [-122.410015948336749, 37.810934689402742],\n            [-122.410036236914948, 37.810915819594705],\n            [-122.410045418985376, 37.810868973454134],\n            [-122.40995683043738, 37.810857360334786],\n            [-122.409929090780366, 37.810990347084882],\n            [-122.409911715601922, 37.810987881557118],\n            [-122.409936471227283, 37.810873484930354],\n            [-122.409873927744698, 37.810864883322409],\n            [-122.409849172039969, 37.810979280485419],\n            [-122.409831796859692, 37.810976814671307],\n            [-122.409859730943055, 37.810851378751366],\n            [-122.409793708942289, 37.810842147255194],\n            [-122.409767522509881, 37.810968241308018],\n            [-122.409751878141279, 37.810965747731309],\n            [-122.409778063900148, 37.81083965341854],\n            [-122.409713790367476, 37.810831080014538],\n            [-122.409685856045158, 37.810956515900401],\n            [-122.409668480889763, 37.810954050611365],\n            [-122.409696415233427, 37.810828614454991],\n            [-122.40963387182353, 37.810820012994292],\n            [-122.409609415933446, 37.810946079263672],\n            [-122.409592040775962, 37.810943613688799],\n            [-122.409616496701801, 37.810817547697539],\n            [-122.409552222853762, 37.810808973936524],\n            [-122.409527766835737, 37.810935040463818],\n            [-122.4095103913304, 37.810932574607932],\n            [-122.409534847384165, 37.81080650835878],\n            [-122.409470556252117, 37.810797248385086],\n            [-122.4094443696399, 37.810923342636215],\n            [-122.409426994147125, 37.810920877042648],\n            [-122.409453181134111, 37.810794782789721],\n            [-122.40938717688249, 37.810786237228463],\n            [-122.409360972477401, 37.810911645024227],\n            [-122.409343596990126, 37.810909179418339],\n            [-122.409371532226004, 37.810783743881395],\n            [-122.409307258435504, 37.810775169986833],\n            [-122.409279323454484, 37.810900606052307],\n            [-122.409261948311553, 37.810898140154237],\n            [-122.409289883328043, 37.810772704367388],\n            [-122.409222130673314, 37.810763500569543],\n            [-122.409194195884822, 37.810888936060401],\n            [-122.409178551206097, 37.810886442413199],\n            [-122.409204755910437, 37.810761034657382],\n            [-122.409142194961319, 37.810751746498703],\n            [-122.409114277009948, 37.810877868693844],\n            [-122.409096902238019, 37.810875403314995],\n            [-122.409124837171674, 37.81074996729771],\n            [-122.409062293894237, 37.810741365533815],\n            [-122.409034358851102, 37.810866801261916],\n            [-122.409016983385186, 37.810864335607896],\n            [-122.409044919155974, 37.810738900147257],\n            [-122.408982375890048, 37.810730298066382],\n            [-122.408954440030968, 37.81085573406159],\n            [-122.408937065255401, 37.810853268110044],\n            [-122.408965000457812, 37.810727832404666],\n            [-122.408898979017181, 37.810718600127345],\n            [-122.408871043014514, 37.810844035828495],\n            [-122.408853668258587, 37.8108415704138],\n            [-122.408881603590444, 37.810716134453337],\n            [-122.408815582170575, 37.810706902129297],\n            [-122.408787646031413, 37.810832337810972],\n            [-122.408770288923606, 37.810830558546641],\n            [-122.408798206756401, 37.810704436717558],\n            [-122.408730454910014, 37.810695232351428],\n            [-122.408702518970472, 37.810820667733054],\n            [-122.408686891971527, 37.810818860457857],\n            [-122.408713079833362, 37.810692766372398],\n            [-122.408650536651805, 37.810684164390111],\n            [-122.408622600588473, 37.810809600027646],\n            [-122.408605243143839, 37.810807820744628],\n            [-122.408633161587474, 37.810681698673903],\n            [-122.408556510134844, 37.810663024107164],\n            [-122.408588984612706, 37.810512106294745],\n            [-122.408606359289806, 37.810514572023159],\n            [-122.408587340511048, 37.810650164195629],\n            [-122.408629047705375, 37.810656356501411],\n            [-122.408668902323384, 37.810523174029143],\n            [-122.408686277704945, 37.81052564000916],\n            [-122.40866552792356, 37.81066125993452],\n            [-122.408708965930458, 37.810667424477344],\n            [-122.408748820750048, 37.810534241698186],\n            [-122.40876619543765, 37.81053670740306],\n            [-122.40874717695219, 37.810672299875314],\n            [-122.408794093155748, 37.810679094270796],\n            [-122.408833947835987, 37.810545911737734],\n            [-122.408849592085303, 37.810548405436371],\n            [-122.40883057374208, 37.810683997647232],\n            [-122.408875741869977, 37.810690133853562],\n            [-122.408915614060632, 37.81055763773044],\n            [-122.408932971115192, 37.810559417248072],\n            [-122.408913970565308, 37.81069569563472],\n            [-122.408960869161646, 37.810701804075741],\n            [-122.409000741206668, 37.810569307924148],\n            [-122.409018098597954, 37.810571086874475],\n            [-122.40899736742189, 37.810707393837795],\n            [-122.409040787124908, 37.810712871283229],\n            [-122.409080641733894, 37.810579688936649],\n            [-122.409096286337629, 37.810582182322406],\n            [-122.409077268090201, 37.810717774577761],\n            [-122.40911897536337, 37.810723966710214],\n            [-122.409158829484909, 37.81059078406853],\n            [-122.409176204199355, 37.810593249712966],\n            [-122.409157186441121, 37.810728841975312],\n            [-122.409200623829136, 37.810735006073635],\n            [-122.409240478156264, 37.81060182339904],\n            [-122.409257853229136, 37.810604289300421],\n            [-122.409238852918037, 37.810740567743849],\n            [-122.409285751569428, 37.810746676055665],\n            [-122.409325605404021, 37.810613493358183],\n            [-122.409341250023104, 37.81061598671144],\n            [-122.40932224986912, 37.810752265442567],\n            [-122.4093656699755, 37.81075774303735],\n            [-122.40940554098205, 37.810625246755905],\n            [-122.409422898749284, 37.810627025915643],\n            [-122.409403898742326, 37.810763304659638],\n            [-122.409447336517104, 37.810769468661483],\n            [-122.40948544197218, 37.810635627488516],\n            [-122.409502816715019, 37.810638093359387],\n            [-122.409485547293727, 37.810774343825763],\n            [-122.409528985434875, 37.8107805080665],\n            [-122.409565359949454, 37.810646694615322],\n            [-122.409582735382628, 37.810649160188639],\n            [-122.409567196568673, 37.810785383198734],\n            [-122.409612364812048, 37.81079151884834],\n            [-122.409648756853613, 37.810658392357979],\n            [-122.409666132285238, 37.810660857644436],\n            [-122.409648863165444, 37.8107971084087],\n            [-122.409690552872675, 37.810802613901892],\n            [-122.409730423669671, 37.810670117506156],\n            [-122.409747781109076, 37.81067189662366],\n            [-122.409728781340874, 37.810808175424455],\n            [-122.409772201856015, 37.810813652863935],\n            [-122.409810324407417, 37.810680498024219],\n            [-122.409827699517621, 37.810682963841622],\n            [-122.409810430682086, 37.810819214354417],\n            [-122.4098555989803, 37.810825350459979],\n            [-122.409895451834942, 37.810692167571958],\n            [-122.409912826590485, 37.810694632833282],\n            [-122.409895222374288, 37.810817841328777],\n            [-122.409936876790525, 37.81082197386079],\n            [-122.409973639826163, 37.810703262163521],\n            [-122.409991014940061, 37.810705727682269],\n            [-122.409959938125212, 37.81084357554878],\n            [-122.410048526318235, 37.810855188946014],\n            [-122.41005622518567, 37.810817980946432],\n            [-122.410043511556253, 37.810794838529432],\n            [-122.410054194116455, 37.810739040961913],\n            [-122.410078049482479, 37.810724233171044],\n            [-122.410094488114893, 37.810623018930904],\n            [-122.410081703845862, 37.810597130219826],\n            [-122.410092509988729, 37.810546137980772],\n            [-122.410117989755761, 37.810527183534326],\n            [-122.410136070412563, 37.810422508799753],\n            [-122.410119930917134, 37.810400795044814],\n            [-122.41012762970874, 37.810363587311961],\n            [-122.410156217048225, 37.810330848613937],\n            [-122.41017620465729, 37.81023300994017],\n            [-122.410158299784257, 37.810209951610261],\n            [-122.410179752853338, 37.810101788351538],\n            [-122.410076402542387, 37.809920107344034],\n            [-122.410041882266327, 37.809924099736733],\n            [-122.410019086454341, 37.809912794612728],\n            [-122.409979357246016, 37.809916185169463],\n            [-122.409958274206915, 37.809904165299031],\n            [-122.409922023510461, 37.809908185953802],\n            [-122.409899227375504, 37.80989688081219],\n            [-122.409856019993072, 37.80989964065347],\n            [-122.409833224216882, 37.809888335493483],\n            [-122.409796955869524, 37.80989166994722],\n            [-122.409774159751635, 37.809880364506853],\n            [-122.40973616131653, 37.809883726681605],\n            [-122.409715095991231, 37.809872393479395],\n            [-122.409673618697951, 37.809875125240453],\n            [-122.409650822957047, 37.809863820045173],\n            [-122.409611093758144, 37.809867210477904],\n            [-122.409590028449543, 37.809855876978752],\n            [-122.409552029326491, 37.809859239105428],\n            [-122.40952923395507, 37.809847933880995],\n            [-122.409489504405954, 37.809851324003851],\n            [-122.409466709046484, 37.809840018767318],\n            [-122.409426961850301, 37.809842722706414],\n            [-122.409405896576772, 37.809831389174342],\n            [-122.409362689214092, 37.809834148834945],\n            [-122.409339893540192, 37.80982284385405],\n            [-122.40930537326976, 37.809826836031277],\n            [-122.409282577599768, 37.809815530764716],\n            [-122.409239369556644, 37.809818290665945],\n            [-122.409216574238357, 37.809806985106434],\n            [-122.409183784056154, 37.809810949516532],\n            [-122.409160988755644, 37.809799644220845],\n            [-122.409117781056253, 37.80980240379737],\n            [-122.409096715848435, 37.809791070484557],\n            [-122.409062177941692, 37.809794376702477],\n            [-122.40903938231871, 37.80978307138885],\n            [-122.409001401198537, 37.80978711949534],\n            [-122.408980336012803, 37.809775786161715],\n            [-122.408940588826241, 37.809778489662385],\n            [-122.408917793572726, 37.809767184319618],\n            [-122.408872855464537, 37.809769972088361],\n            [-122.408850059531787, 37.809758666743676],\n            [-122.408812061110055, 37.80976202834627],\n            [-122.408789265535077, 37.809750722984248],\n            [-122.40874778827569, 37.809753454419798],\n            [-122.408724992367041, 37.80974214905094],\n            [-122.408688724387432, 37.809745483158423],\n            [-122.408665928836285, 37.809734177772519],\n            [-122.408627929726592, 37.809737539327024],\n            [-122.408605134533261, 37.809726233923747],\n            [-122.40856886620287, 37.809729567725441],\n            [-122.408546070674888, 37.809718262316345],\n            [-122.408502862660981, 37.80972102194783],\n            [-122.408480067491766, 37.809709716520373],\n            [-122.408442068732256, 37.809713078009395],\n            [-122.408419272882526, 37.809701772581377],\n            [-122.40838475263304, 37.809705765038423],\n            [-122.408363687557454, 37.809694431319926],\n            [-122.408318732173385, 37.809696532434543],\n            [-122.408295936693619, 37.809685226977031],\n            [-122.408254459445629, 37.809687957964584],\n            [-122.408238053815026, 37.809790545390122],\n            [-122.40821721811372, 37.809788135332319],\n            [-122.408256439046454, 37.809562942360742],\n            [-122.408275562248491, 37.809566066566916],\n            [-122.408269450984477, 37.809665053549438],\n            [-122.408286825452777, 37.809667519325309],\n            [-122.40832766898086, 37.809572777430049],\n            [-122.408321557439706, 37.809671764420713],\n            [-122.408342410750109, 37.809674860622998],\n            [-122.408383254218379, 37.809580118983206],\n            [-122.408377142750709, 37.809679105976613],\n            [-122.408394517910963, 37.809681571450852],\n            [-122.408435360963296, 37.809586829524143],\n            [-122.408429249910625, 37.809685816514552],\n            [-122.408451833660507, 37.80968888497187],\n            [-122.408492676297627, 37.809594143031056],\n            [-122.408484852889558, 37.809693844457087],\n            [-122.408503958134872, 37.809696281919784],\n            [-122.408544801055655, 37.809601539955445],\n            [-122.408536959377372, 37.809700554958617],\n            [-122.408557795064482, 37.809702964959982],\n            [-122.408598638259278, 37.809608222696959],\n            [-122.408592544731249, 37.809707896412988],\n            [-122.408615128826568, 37.809710964284278],\n            [-122.40865597161357, 37.809616222281704],\n            [-122.408639231230907, 37.809705767179551],\n            [-122.408663757193779, 37.809717044567101],\n            [-122.408704599922103, 37.80962230254778],\n            [-122.408696758454738, 37.809721317561177],\n            [-122.408722821063833, 37.809725016115578],\n            [-122.408763663375069, 37.80963027408157],\n            [-122.408757553102589, 37.809729261082566],\n            [-122.408776676029532, 37.8097323854876],\n            [-122.408817518615137, 37.809637643154893],\n            [-122.408811408067763, 37.809736630164153],\n            [-122.408839165466787, 37.809738927817939],\n            [-122.408880043284086, 37.809645558612985],\n            [-122.408873932819418, 37.809744545625371],\n            [-122.40889130733342, 37.809747011586715],\n            [-122.408932150126873, 37.809652269208989],\n            [-122.408926057042962, 37.809751942941425],\n            [-122.408946892742989, 37.809754352324887],\n            [-122.408987735476614, 37.809659610202615],\n            [-122.408979894728986, 37.80975862522854],\n            [-122.409002478177683, 37.809761693586005],\n            [-122.409043320837327, 37.809666951170023],\n            [-122.40903548016307, 37.809765966199507],\n            [-122.409052855026459, 37.809768431856902],\n            [-122.409093697286551, 37.809673689703786],\n            [-122.409085856332652, 37.809772704742095],\n            [-122.409106709703295, 37.809775800809213],\n            [-122.409147552244775, 37.809681058631973],\n            [-122.409141441787753, 37.809780045663132],\n            [-122.409162295162758, 37.809783141720409],\n            [-122.409203137637348, 37.809688399524063],\n            [-122.409195297173937, 37.809787414563765],\n            [-122.409214402473708, 37.809789852185943],\n            [-122.409255244539409, 37.809695109977277],\n            [-122.409249134570899, 37.809794097008144],\n            [-122.409268257528154, 37.809797221058851],\n            [-122.409309099536273, 37.809702479106257],\n            [-122.40930298963896, 37.809801466139774],\n            [-122.409325573445983, 37.809804533880602],\n            [-122.409366415377917, 37.809709791633715],\n            [-122.409360305556405, 37.809808778670053],\n            [-122.409379410874138, 37.809811216540062],\n            [-122.409420252741057, 37.809716474274637],\n            [-122.409412429873498, 37.809816175771125],\n            [-122.409431535526792, 37.809818613077915],\n            [-122.409472376985178, 37.809723870800191],\n            [-122.409466267649847, 37.80982285783616],\n            [-122.409488851138192, 37.809825926100473],\n            [-122.409529693219639, 37.809731183791826],\n            [-122.409521853187186, 37.809830198852438],\n            [-122.409544436665897, 37.809833266556943],\n            [-122.409585278680453, 37.809738524229203],\n            [-122.409577438721357, 37.809837539293362],\n            [-122.409600022218754, 37.809840607536366],\n            [-122.409640864166391, 37.809745865189505],\n            [-122.409624124935263, 37.809835409950374],\n            [-122.409648668402042, 37.809847373575664],\n            [-122.409694718878129, 37.809753233598798],\n            [-122.409683400550946, 37.809851618269946],\n            [-122.40970946326199, 37.809855316880828],\n            [-122.409750304385923, 37.809760574507528],\n            [-122.409744195418114, 37.809859561557211],\n            [-122.409763300747386, 37.809861998815862],\n            [-122.409800681301817, 37.809767312459051],\n            [-122.409798050568668, 37.809866929913561],\n            [-122.409813677394268, 37.809868737041889],\n            [-122.409859727674416, 37.809774597001002],\n            [-122.409848409562613, 37.809872981687576],\n            [-122.409872741508423, 37.809876708014343],\n            [-122.409896207540612, 37.809779500054304],\n            [-122.409902917913584, 37.809838449581335],\n            [-122.409927956256155, 37.809869633383848],\n            [-122.410058214833967, 37.809886065459693],\n            [-122.40978805552237, 37.809411107578008],\n            [-122.409692176324654, 37.809452490025258],\n            [-122.409753394315103, 37.809476907533615],\n            [-122.409795083288799, 37.80948241299123],\n            [-122.409790439454952, 37.809503776606206],\n            [-122.409767643810284, 37.809492471432705],\n            [-122.409733123723498, 37.809496464009918],\n            [-122.40971032808983, 37.809485158825325],\n            [-122.40967926884106, 37.809489095348837],\n            [-122.409656473217836, 37.809477790153814],\n            [-122.409630622889722, 37.80948232932171],\n            [-122.409609557687361, 37.809470995825244],\n            [-122.409573289526762, 37.809474329935902],\n            [-122.409550493924201, 37.809463024720365],\n            [-122.40952291317636, 37.809467591881273],\n            [-122.409500117230408, 37.809456286387039],\n            [-122.409465597146067, 37.809460278886],\n            [-122.409442784254765, 37.809448287206727],\n            [-122.409413473087668, 37.809452882357093],\n            [-122.409390677508952, 37.809441576836036],\n            [-122.409354409006824, 37.809444910885041],\n            [-122.409331613792688, 37.809433605621535],\n            [-122.409304033042332, 37.809438172731298],\n            [-122.409281237484791, 37.809426867189075],\n            [-122.409251926309096, 37.809431462024762],\n            [-122.40922913042256, 37.809420156752616],\n            [-122.409192844962547, 37.809422804303409],\n            [-122.409170049433754, 37.809411499014253],\n            [-122.409133798935159, 37.809415519427169],\n            [-122.409110985426068, 37.809403527969394],\n            [-122.409076465338302, 37.809407520079958],\n            [-122.409053670177968, 37.809396214762678],\n            [-122.409027819488273, 37.809400753529559],\n            [-122.409005024337446, 37.809389448202872],\n            [-122.408973965091334, 37.809393384540734],\n            [-122.408951169258785, 37.809382079214799],\n            [-122.40891837994225, 37.80938604354025],\n            [-122.408895584120444, 37.809374738203552],\n            [-122.408862794804193, 37.809378702513534],\n            [-122.408839998993102, 37.809367397166127],\n            [-122.408808940093522, 37.809371333454934],\n            [-122.408786144638981, 37.809360028091476],\n            [-122.408756815463747, 37.809363936372364],\n            [-122.4087340203655, 37.80935263099321],\n            [-122.408701230358204, 37.809356595269499],\n            [-122.408678435270645, 37.809345289879595],\n            [-122.408645645263661, 37.809349254140457],\n            [-122.408622850186802, 37.809337948739781],\n            [-122.408591790941983, 37.809341884977052],\n            [-122.408568995183444, 37.809330579577171],\n            [-122.408536205869268, 37.809334543796403],\n            [-122.408513410121444, 37.809323238385751],\n            [-122.408482351223043, 37.809327174588631],\n            [-122.408459555831755, 37.809315869161935],\n            [-122.408426766172141, 37.809319833356362],\n            [-122.408403970791582, 37.809308527918944],\n            [-122.408371181132239, 37.809312492097938],\n            [-122.408350116177374, 37.809301158650754],\n            [-122.408315578458229, 37.809304464375806],\n            [-122.408292783099327, 37.809293158916844],\n            [-122.408263471569185, 37.80929775351261],\n            [-122.408240676566351, 37.80928644803798],\n            [-122.408207886561954, 37.809290412177198],\n            [-122.408185073925949, 37.809278420254287],\n            [-122.408155762741032, 37.809283014817716],\n            [-122.408132967413039, 37.809271709327838],\n            [-122.408100177755486, 37.809275673431507],\n            [-122.408077382438208, 37.809264367930865],\n            [-122.408039154542493, 37.809258805877114],\n            [-122.408058756181504, 37.809145866471148],\n            [-122.408066594168815, 37.809248747837302],\n            [-122.408087429706811, 37.809251157647353],\n            [-122.408126507222988, 37.809155071214583],\n            [-122.408120448716886, 37.809256117235002],\n            [-122.408137823084502, 37.809258583032992],\n            [-122.408178631290198, 37.80916246830698],\n            [-122.408172554871641, 37.809262828172606],\n            [-122.408196869235468, 37.809265868402413],\n            [-122.408235946617765, 37.809169781933626],\n            [-122.408229888259399, 37.809270827959395],\n            [-122.408254202289157, 37.809273868457552],\n            [-122.408293279587355, 37.809177781420765],\n            [-122.408287221320464, 37.809278827998476],\n            [-122.408308056867014, 37.809281237494979],\n            [-122.408347134113498, 37.809185150989656],\n            [-122.408332158807269, 37.809276040458201],\n            [-122.408356702573101, 37.809288004342378],\n            [-122.408397510165557, 37.809191889546774],\n            [-122.408391452032305, 37.809292935854977],\n            [-122.408412287593251, 37.809295345607659],\n            [-122.408453095124827, 37.809199231067545],\n            [-122.408447037059574, 37.809300277103894],\n            [-122.408471350761801, 37.809303317562879],\n            [-122.408512158553279, 37.809207202447709],\n            [-122.408506100235769, 37.809308249041685],\n            [-122.408521727251397, 37.8093100560612],\n            [-122.408560804236558, 37.809213969485583],\n            [-122.408554745970605, 37.809315015532796],\n            [-122.408577329958945, 37.809318083960704],\n            [-122.408618119627377, 37.809221282377308],\n            [-122.40861206179197, 37.80932232869629],\n            [-122.408636375847209, 37.809325368841058],\n            [-122.408677182759092, 37.809229254229557],\n            [-122.408671125342451, 37.80933030027127],\n            [-122.40868850009889, 37.809332766257086],\n            [-122.408727576866028, 37.809236679077507],\n            [-122.408721501183379, 37.80933703897],\n            [-122.408740606697776, 37.80933947666351],\n            [-122.408781413482515, 37.80924336201619],\n            [-122.408775356206561, 37.809344408062977],\n            [-122.40879620945104, 37.809347504459829],\n            [-122.408837016154081, 37.809251389244281],\n            [-122.408830941310313, 37.809351749130926],\n            [-122.408848316070006, 37.809354214818633],\n            [-122.4088891230695, 37.809258100128716],\n            [-122.40888306559286, 37.80935914618636],\n            [-122.408902170426074, 37.809361583864899],\n            [-122.408942977698928, 37.809265468876312],\n            [-122.408936919955934, 37.809366515216752],\n            [-122.408959503968703, 37.809369583296906],\n            [-122.408998562767209, 37.809272810139959],\n            [-122.408992505092215, 37.809373856208545],\n            [-122.409013358354017, 37.809376952566957],\n            [-122.409054147832379, 37.809280150828236],\n            [-122.409048090239509, 37.809381197174098],\n            [-122.409070674261315, 37.809384265232971],\n            [-122.409109750575112, 37.809288178476827],\n            [-122.40910369305017, 37.809389224550813],\n            [-122.409122798597593, 37.809391662456981],\n            [-122.409161857180791, 37.809294888697217],\n            [-122.409155800079347, 37.809395935042737],\n            [-122.409180113834879, 37.809398975080981],\n            [-122.409220920775525, 37.809302860271487],\n            [-122.409214863400692, 37.809403906350916],\n            [-122.409233968955562, 37.809406344239079],\n            [-122.409273045055528, 37.809310256880117],\n            [-122.409266988111227, 37.80941130350562],\n            [-122.40929130186926, 37.809414343246381],\n            [-122.409332108667385, 37.80931822812412],\n            [-122.409326033795196, 37.809418588046228],\n            [-122.409343408940103, 37.809421053929945],\n            [-122.40938421532131, 37.809324938520824],\n            [-122.409378158527119, 37.809425985151776],\n            [-122.40939901180549, 37.809429080892826],\n            [-122.409438070062492, 37.809332307591447],\n            [-122.409432013326835, 37.809433353675857],\n            [-122.409477180743622, 37.809439489653357],\n            [-122.409529287502835, 37.809446200260403],\n            [-122.409588351243272, 37.809454171101315],\n            [-122.40962830977378, 37.809459704635067],\n            [-122.409632794735771, 37.809432163090825],\n            [-122.409672911837845, 37.809443874825128],\n            [-122.409782563727632, 37.809399522210484],\n            [-122.409759009148317, 37.809358700227392],\n            [-122.409646479104083, 37.809089266648215],\n            [-122.409583548723262, 37.809065563811615],\n            [-122.409522825494292, 37.80906036618412],\n            [-122.409518181262428, 37.809081729795679],\n            [-122.409495386134864, 37.809070424563046],\n            [-122.409466075113144, 37.809075019452258],\n            [-122.409443262001091, 37.809063028052371],\n            [-122.409410472498593, 37.80906699224586],\n            [-122.409387677045885, 37.809055686998015],\n            [-122.409356617959673, 37.80905962343688],\n            [-122.409333822524488, 37.809048318453144],\n            [-122.409299302613846, 37.809052310629191],\n            [-122.40927823759111, 37.809040977347202],\n            [-122.409247178159163, 37.809044913762847],\n            [-122.409226113153551, 37.80903358074611],\n            [-122.409193341651701, 37.809038231311064],\n            [-122.409172276649144, 37.80902689801011],\n            [-122.409141217563558, 37.809030834392281],\n            [-122.409118422162948, 37.809019529092303],\n            [-122.409083884609018, 37.809022835042484],\n            [-122.409061089219591, 37.809011529731414],\n            [-122.409026569318456, 37.809015522102243],\n            [-122.409005504354852, 37.809004189046036],\n            [-122.408972714854954, 37.80900815311788],\n            [-122.408949919486972, 37.808996847785302],\n            [-122.408918860402167, 37.809000784108946],\n            [-122.408896065051678, 37.808989479040513],\n            [-122.408865005613961, 37.808993415081005],\n            [-122.408842210612988, 37.80898210972196],\n            [-122.408807673071223, 37.808985415865877],\n            [-122.408784877735343, 37.808974110501353],\n            [-122.408753818298166, 37.808978046512571],\n            [-122.408732753379752, 37.808966713133003],\n            [-122.40870344269787, 37.808971307827179],\n            [-122.408680647389247, 37.80896000271705],\n            [-122.408653066760635, 37.80896456912587],\n            [-122.408630271454712, 37.808953263731418],\n            [-122.408595751557471, 37.808957255976196],\n            [-122.408574686669112, 37.808945922568363],\n            [-122.40853841837243, 37.808949256366759],\n            [-122.4085173534951, 37.808937922948687],\n            [-122.408484564351539, 37.808941887153871],\n            [-122.408461768739215, 37.80893058200698],\n            [-122.408432440401654, 37.808934489921761],\n            [-122.408409645138534, 37.808923184484605],\n            [-122.408375125243424, 37.808927176664845],\n            [-122.408354060404321, 37.808915843492116],\n            [-122.408319522857553, 37.808919148943978],\n            [-122.408296727616332, 37.808907843484988],\n            [-122.408265686178666, 37.808912466074247],\n            [-122.408242890954767, 37.808901160879408],\n            [-122.408208353409876, 37.808904466298728],\n            [-122.408185558190112, 37.808893160818222],\n            [-122.408154499115199, 37.808897097215251],\n            [-122.40813170390588, 37.808885791724293],\n            [-122.40810064447102, 37.808889727563603],\n            [-122.408077849618252, 37.808878422056637],\n            [-122.408048538584595, 37.808883016593732],\n            [-122.408027473807891, 37.80887168336259],\n            [-122.407989475110696, 37.808875044711961],\n            [-122.407968410338185, 37.808863711195677],\n            [-122.40793908201519, 37.808867619537004],\n            [-122.407916286847737, 37.808856314004366],\n            [-122.407867659098386, 37.808850233137854],\n            [-122.407892470076433, 37.808737895932389],\n            [-122.407895080955839, 37.808839488699526],\n            [-122.407919412118858, 37.808843215435566],\n            [-122.407965428881198, 37.808747703240833],\n            [-122.407954143699186, 37.808847460639832],\n            [-122.407971518664866, 37.808849926725884],\n            [-122.408015804954957, 37.808754442230494],\n            [-122.408004537488765, 37.808854886346353],\n            [-122.408030581417677, 37.80885789835331],\n            [-122.408074867989683, 37.808762414104869],\n            [-122.408063600595128, 37.808862857951631],\n            [-122.408080957579173, 37.808864637589593],\n            [-122.408126991783163, 37.808769811495232],\n            [-122.408115706821903, 37.808869569183827],\n            [-122.408134811496907, 37.808872006712299],\n            [-122.408175637137717, 37.80877657841792],\n            [-122.408167830687617, 37.808876966281417],\n            [-122.408188666131636, 37.808879376348344],\n            [-122.408234682562991, 37.808783863774664],\n            [-122.40822341538842, 37.808884307910851],\n            [-122.4082459985258, 37.808887375864508],\n            [-122.408292014895409, 37.808791863543163],\n            [-122.408280730145464, 37.80889162097256],\n            [-122.408299853182712, 37.808894745449784],\n            [-122.408345869480527, 37.808799232832989],\n            [-122.408334584808884, 37.808898990541991],\n            [-122.40835716795533, 37.808902058474338],\n            [-122.408401454133696, 37.808806574104075],\n            [-122.408390187174248, 37.808907017981177],\n            [-122.408412770332475, 37.808910086177463],\n            [-122.408457056451198, 37.808814602061034],\n            [-122.408447501979367, 37.808914331510991],\n            [-122.408464876962967, 37.808916796975197],\n            [-122.408510893069277, 37.808821284568992],\n            [-122.408499626255633, 37.808921728456291],\n            [-122.408520461722972, 37.808924138464597],\n            [-122.408564747351377, 37.808828653764436],\n            [-122.408553480616533, 37.808929097931319],\n            [-122.40857606412726, 37.808932165816167],\n            [-122.408622080100045, 37.808836653366868],\n            [-122.408610795786757, 37.808936410826988],\n            [-122.408633379309109, 37.808939478975418],\n            [-122.408677664815599, 37.808843994782293],\n            [-122.408666398217051, 37.808944438410528],\n            [-122.408683754881451, 37.808946217690789],\n            [-122.408728058314978, 37.808851419636184],\n            [-122.408718504202398, 37.808951149107379],\n            [-122.40873414881105, 37.808953643085459],\n            [-122.40878016458673, 37.808858130300308],\n            [-122.408768880489617, 37.80895788804974],\n            [-122.408788002858174, 37.808961011909815],\n            [-122.408832288517658, 37.808865527378856],\n            [-122.408822734543293, 37.808965256858244],\n            [-122.40884531843632, 37.808968325235043],\n            [-122.408891334078092, 37.80887281240679],\n            [-122.408880067778369, 37.808973256604034],\n            [-122.40889917250513, 37.808975694008623],\n            [-122.408943458030421, 37.808880209436197],\n            [-122.408933921854199, 37.808980625361755],\n            [-122.408953027290934, 37.808983063295585],\n            [-122.408997312398199, 37.808887578434081],\n            [-122.408987758995764, 37.808987308195377],\n            [-122.409008612134684, 37.808990404005712],\n            [-122.409052897181908, 37.808894919398057],\n            [-122.409041631090304, 37.808995363335725],\n            [-122.40906421464841, 37.808998431401648],\n            [-122.409110230041378, 37.808902919037678],\n            [-122.409098946373874, 37.809002676543273],\n            [-122.409121529583416, 37.809005744329234],\n            [-122.409165814840676, 37.808910259673837],\n            [-122.409154548899522, 37.809010703622],\n            [-122.409175384406026, 37.80901311324002],\n            [-122.4092196692594, 37.808917628844746],\n            [-122.409208385736662, 37.80901738636053],\n            [-122.409229238893218, 37.809020482131849],\n            [-122.409273523674656, 37.808924997441885],\n            [-122.4092622402232, 37.809024754962692],\n            [-122.40927961492774, 37.809027220861601],\n            [-122.409325630047334, 37.808931707864964],\n            [-122.409314364326178, 37.80903215210256],\n            [-122.409336947553612, 37.809035219847274],\n            [-122.409381232551411, 37.808939735111537],\n            [-122.409369949242404, 37.809039492642384],\n            [-122.409390802418955, 37.809042588659686],\n            [-122.409422921195898, 37.808945240701227],\n            [-122.409422903348997, 37.809011853140163],\n            [-122.40946679914822, 37.809035864508125],\n            [-122.409564019832658, 37.809046651478369],\n            [-122.409549629214183, 37.809025596054688],\n            [-122.409548887643922, 37.80899676568643],\n            [-122.409387695818651, 37.808719879048233],\n            [-122.409342422574227, 37.808709624416799],\n            [-122.409302270626966, 37.808696540242821],\n            [-122.409262612300481, 37.808769974638814],\n            [-122.409243366033195, 37.808762045524638],\n            [-122.409286290635848, 37.808681004303558],\n            [-122.409251523749987, 37.808675386616557],\n            [-122.409211865384634, 37.808748820995767],\n            [-122.409192619474325, 37.8087408918677],\n            [-122.409232277847408, 37.808667457220274],\n            [-122.409218046276052, 37.808652579691525],\n            [-122.409199082493714, 37.808655633568947],\n            [-122.409183155837965, 37.808642156922303],\n            [-122.409146746495978, 37.80863999940842],\n            [-122.409132550252011, 37.808626494744182],\n            [-122.409097906968512, 37.808625682074002],\n            [-122.409081980690672, 37.808612205682593],\n            [-122.409045571705349, 37.808610047857293],\n            [-122.409029645094293, 37.808596571464371],\n            [-122.40899498416762, 37.808595072051922],\n            [-122.408979057222851, 37.808581595657749],\n            [-122.408939170155094, 37.808578807372939],\n            [-122.408923243223967, 37.808565330971156],\n            [-122.40887636397639, 37.80855990894463],\n            [-122.408862167796897, 37.808546404247657],\n            [-122.40882402846016, 37.808544274635025],\n            [-122.408771338418944, 37.808649509314336],\n            [-122.408752144836185, 37.80864363943796],\n            [-122.408804834900437, 37.808538404767106],\n            [-122.408755995512777, 37.808524087565431],\n            [-122.40870503613472, 37.808629294204955],\n            [-122.408684130157809, 37.808624138753608],\n            [-122.408736802648747, 37.808518217400646],\n            [-122.408686214896832, 37.808503241742777],\n            [-122.408633542337299, 37.808609162798888],\n            [-122.40861434946801, 37.808603292888833],\n            [-122.408665291301503, 37.808497399575863],\n            [-122.408618182709631, 37.808483054307779],\n            [-122.408565527707552, 37.808589661496853],\n            [-122.408546334502589, 37.808583791581334],\n            [-122.408598989528059, 37.808477184400701],\n            [-122.408550150206281, 37.808462866564483],\n            [-122.408494192805264, 37.808575707944065],\n            [-122.408475017602044, 37.808570524448861],\n            [-122.408530957042373, 37.808456996920889],\n            [-122.408475143235307, 37.808440732021886],\n            [-122.408419203721877, 37.80855425952376],\n            [-122.408400010537662, 37.808548389584381],\n            [-122.40845594972285, 37.808434861822526],\n            [-122.408401884334964, 37.808419255320196],\n            [-122.408345944732787, 37.808532783062383],\n            [-122.408326768858856, 37.808527599554232],\n            [-122.408382691187029, 37.808413385377861],\n            [-122.408326877445205, 37.808397120408593],\n            [-122.40827095504342, 37.808511334558837],\n            [-122.408251779872415, 37.808506151027288],\n            [-122.408325818753553, 37.80835593442692],\n            [-122.408307261690325, 37.808307477072752],\n            [-122.408233773949888, 37.808277076846032],\n            [-122.408190831757594, 37.808290132648246],\n            [-122.408103813680896, 37.80847420912815],\n            [-122.408070707145512, 37.808465817066804],\n            [-122.408167524728952, 37.808258920751598],\n            [-122.408046540290897, 37.80819907276836],\n            [-122.408024204007958, 37.808205614634559],\n            [-122.407919104249203, 37.808427066268948],\n            [-122.407885997416685, 37.80841867443587],\n            [-122.407996041444051, 37.808187528721767],\n            [-122.407913795821727, 37.808153149917878],\n            [-122.407807565173499, 37.808397968053718],\n            [-122.407786659324017, 37.808392812443081],\n            [-122.407868398033912, 37.808205388267609],\n            [-122.407660185387385, 37.808254079991599],\n            [-122.407706584592447, 37.808375566178441],\n            [-122.407613672832028, 37.80839767070411],\n            [-122.407507720833991, 37.808114394328115],\n            [-122.407549056121539, 37.808106171825735],\n            [-122.407536061536263, 37.808072046122568],\n            [-122.407462591282297, 37.808042331588446],\n            [-122.40744611868692, 37.808007575403529],\n            [-122.407487420240358, 37.807930680888951],\n            [-122.407431324879042, 37.807903432764398],\n            [-122.407355431147764, 37.80804887173035],\n            [-122.406644940711814, 37.807817946705647],\n            [-122.406816836689401, 37.809994826134172],\n            [-122.406786252768356, 37.810017295716854],\n            [-122.40617819806161, 37.810068327706666],\n            [-122.406151660268563, 37.810046094760864],\n            [-122.406135997898389, 37.810042914277616],\n            [-122.406123656094778, 37.810034186621294],\n            [-122.405904441804637, 37.807159674056429],\n            [-122.405322208784199, 37.806866923600786],\n            [-122.404400300125417, 37.808853394598287],\n            [-122.404390181819835, 37.80886385881621],\n            [-122.404376497046869, 37.808870260285914],\n            [-122.404359263409262, 37.808873285169796],\n            [-122.404316325011152, 37.808819040682266],\n            [-122.403937477028578, 37.80876197892308],\n            [-122.403923457745378, 37.80875533773979],\n            [-122.403912828869224, 37.808745895483341],\n            [-122.403907320429198, 37.80873362312807],\n            [-122.403906985949291, 37.808720580801918],\n            [-122.404717282916977, 37.806971462592401],\n            [-122.403955936424254, 37.806579276399596],\n            [-122.402761932327437, 37.808014567794245],\n            [-122.402746534430648, 37.808021683436984],\n            [-122.402731014070881, 37.808023993997161],\n            [-122.402715370212889, 37.808021499491424],\n            [-122.40235585639897, 37.807840510524542],\n            [-122.402346922703828, 37.807829667068134],\n            [-122.402341415289797, 37.807817394901065],\n            [-122.402339367950134, 37.807805066923208],\n            [-122.402340764477955, 37.807791996673913],\n            [-122.402345656255733, 37.807780243491045],\n            [-122.403421726415289, 37.806471156550586],\n            [-122.402804368682325, 37.806023074735968],\n            [-122.400943288973451, 37.807638041928655],\n            [-122.40092780379058, 37.807641725129315],\n            [-122.400912142497489, 37.807638543945465],\n            [-122.400398744529411, 37.807261566606726],\n            [-122.400572958659495, 37.806120993590262],\n            [-122.401732349384048, 37.805192215433102],\n            [-122.403194419486127, 37.805118563841738],\n            [-122.403587277644846, 37.805412029499024],\n            [-122.404128809815958, 37.80582849738515],\n            [-122.404696069838451, 37.806264743038192],\n            [-122.405495723806581, 37.806640585378162],\n            [-122.405545441190654, 37.806634177056651],\n            [-122.405362337774832, 37.805695301990511],\n            [-122.405357241074242, 37.805695943874476],\n            [-122.405356257037084, 37.805690948342658],\n            [-122.405173536628851, 37.804763212222298],\n            [-122.404986245080067, 37.803832670552438],\n            [-122.404798316633176, 37.802900762043983],\n            [-122.40352117840483, 37.80305477917063],\n            [-122.40315960682554, 37.803098380641011],\n            [-122.402973676735002, 37.802166749844929],\n            [-122.402973611142329, 37.802166419353441],\n            [-122.402883459886269, 37.801713453423041],\n            [-122.402789664410264, 37.801242172237629],\n            [-122.402604124590809, 37.800307129616797],\n            [-122.402603736644934, 37.800305175692849],\n            [-122.402420666593613, 37.799382141273902],\n            [-122.402228639238132, 37.798429701815799],\n            [-122.402040983242472, 37.797504944420098],\n            [-122.401906946643678, 37.796875708196644],\n            [-122.401853390100442, 37.796626165074152],\n            [-122.402402444702886, 37.796554566975963],\n            [-122.40291796673857, 37.796490379340391],\n            [-122.403496709817034, 37.796417485859841],\n            [-122.404412160626791, 37.796302098196776],\n            [-122.405142977212691, 37.796210954635839],\n            [-122.405862652252878, 37.796122174218517],\n            [-122.406224736410849, 37.796077505131322],\n            [-122.406350669109713, 37.796061968858353],\n            [-122.406652352369846, 37.796024750165536],\n            [-122.407083680819795, 37.795970583772274],\n            [-122.407319877605971, 37.795940921047873],\n            [-122.407432089697608, 37.795926828732469],\n            [-122.407563768850565, 37.795910291726024],\n            [-122.40779730388941, 37.795880962818103],\n            [-122.408253202051654, 37.795823706218044],\n            [-122.408704654154022, 37.795766841471867],\n            [-122.408743234497422, 37.795761981540487],\n            [-122.409075520379659, 37.795720125786943],\n            [-122.409077868767497, 37.795719830108119],\n            [-122.409343544204319, 37.795686363949223],\n            [-122.40954722057397, 37.795660707190791],\n            [-122.40989875147848, 37.795616424537307],\n            [-122.411081667103218, 37.795467382264249],\n            [-122.411541226025591, 37.795408552082151],\n            [-122.411541229133036, 37.795408565216896],\n            [-122.411541531721369, 37.795408526524874],\n            [-122.411893657029495, 37.795363458292385],\n            [-122.412335878555311, 37.795306857217497],\n            [-122.413187771447625, 37.795197816725128],\n            [-122.414825631965456, 37.794988155997579],\n            [-122.415005455310151, 37.795877318912275],\n            [-122.415094279286492, 37.796316514471208],\n            [-122.415172033219093, 37.796700969266084],\n            [-122.41519297245145, 37.796804503853558],\n            [-122.415290513107706, 37.797286788953464],\n            [-122.415384105347158, 37.79774954140936],\n            [-122.415384693800732, 37.797752451808172],\n            [-122.415571551794869, 37.798676329254079],\n            [-122.415572319635913, 37.798680127283284],\n            [-122.415667503417794, 37.799150732537029],\n            [-122.41576151132729, 37.799616465113374],\n            [-122.416491256558686, 37.799525268171301],\n            [-122.417385454528372, 37.799413513042644],\n            [-122.417492539304973, 37.799906258247788],\n            [-122.417586974866069, 37.800340791478632],\n            [-122.417798642975782, 37.801267347943607],\n            [-122.41787748762404, 37.801706751150704],\n            [-122.417966229723234, 37.802201308768375],\n            [-122.418151092101994, 37.803135399210959],\n            [-122.418255886912107, 37.803650215613203],\n            [-122.418215611082957, 37.803687299847837],\n            [-122.418280954876565, 37.804023161939504],\n            [-122.418341342522965, 37.804070022432718],\n            [-122.418530631419571, 37.804999043218864]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 8, ZIP_CODE: 94109, ID: 94109},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.413915879652706, 37.790463115032836],\n            [-122.412266187164306, 37.790672783986288],\n            [-122.412154318600955, 37.79012444884593],\n            [-122.412075867445836, 37.789739906579548],\n            [-122.411885657146456, 37.788807543703371],\n            [-122.413541442066688, 37.788598204045492],\n            [-122.415185269301574, 37.788387930678724],\n            [-122.414995423274036, 37.787453843784689],\n            [-122.414806647966373, 37.786519819325378],\n            [-122.414617207572036, 37.785582474657787],\n            [-122.414430198422409, 37.784657140813145],\n            [-122.414241572919337, 37.783723783021379],\n            [-122.415882538254209, 37.783515640872785],\n            [-122.417528773439201, 37.78331088958533],\n            [-122.419181703937824, 37.783101400150407],\n            [-122.420816620257895, 37.78289417046458],\n            [-122.420817002825672, 37.782894121654586],\n            [-122.422463744273344, 37.782685368786026],\n            [-122.422597534917514, 37.782659914421728],\n            [-122.424108200948226, 37.782477051425644],\n            [-122.427396397680198, 37.782056939506091],\n            [-122.427490665181679, 37.782522998381459],\n            [-122.427584325166535, 37.78298604740867],\n            [-122.427778871361184, 37.783924735360486],\n            [-122.427988927379005, 37.784953879052303],\n            [-122.428148186422831, 37.785784488173462],\n            [-122.428241496400901, 37.78624657727314],\n            [-122.428241829261111, 37.786248226022003],\n            [-122.428335613076769, 37.786712655890966],\n            [-122.428525011003728, 37.78765056176524],\n            [-122.428712111452299, 37.788577067507021],\n            [-122.428905308896674, 37.789533740830485],\n            [-122.429092268257477, 37.790459499306614],\n            [-122.429269817037863, 37.791338636935983],\n            [-122.429447192175289, 37.79221689778025],\n            [-122.429625242377327, 37.793098475947545],\n            [-122.429803981887915, 37.793977244761322],\n            [-122.429989276973558, 37.794894937362344],\n            [-122.430182832162615, 37.795853517593606],\n            [-122.428537813517494, 37.796062892623418],\n            [-122.426892728667355, 37.796272251947869],\n            [-122.425249874473522, 37.79648130315109],\n            [-122.423601877370103, 37.796690947281171],\n            [-122.423601876988513, 37.796690945913937],\n            [-122.42360129621845, 37.796691056748735],\n            [-122.423601296960271, 37.796691058659476],\n            [-122.423792964234153, 37.797616581376253],\n            [-122.423793785438747, 37.7976164776058],\n            [-122.423982396975646, 37.798552421556877],\n            [-122.423982145098208, 37.798552453684835],\n            [-122.42417056430692, 37.799484532744323],\n            [-122.42417056511286, 37.799484537126212],\n            [-122.424242907462499, 37.799842398528341],\n            [-122.424358667137113, 37.800415027771606],\n            [-122.424517597070277, 37.801201189595353],\n            [-122.424538324517613, 37.801303719934985],\n            [-122.424731635083759, 37.802281009278651],\n            [-122.424731259133807, 37.802280983275736],\n            [-122.424808684502352, 37.802662664019884],\n            [-122.424918804152185, 37.803205508171892],\n            [-122.425109521046082, 37.804145845932808],\n            [-122.425204919000421, 37.804616199985951],\n            [-122.425298563577385, 37.80507790289635],\n            [-122.425486832357592, 37.806006120121552],\n            [-122.425487611207828, 37.806009959663712],\n            [-122.425531817297653, 37.806150610629167],\n            [-122.425627533230582, 37.806456787332273],\n            [-122.4257796482039, 37.806715259689653],\n            [-122.426066893674061, 37.80699787612533],\n            [-122.426256728575282, 37.807185221316665],\n            [-122.426357658303132, 37.807388412718332],\n            [-122.426488519796493, 37.807651861239542],\n            [-122.426560904719622, 37.807943556174827],\n            [-122.426613230594512, 37.808152010516238],\n            [-122.426749407153778, 37.80832970781308],\n            [-122.426815901093192, 37.808623913624309],\n            [-122.42681099272437, 37.808701593834883],\n            [-122.426830737473963, 37.808795352699491],\n            [-122.426826276753118, 37.808823581609779],\n            [-122.426828951398363, 37.808859934107588],\n            [-122.426829913470996, 37.808897001584555],\n            [-122.426829127336148, 37.808933411171161],\n            [-122.426830071586494, 37.80896979193826],\n            [-122.426848658020631, 37.809018932542891],\n            [-122.426823913850725, 37.809066034426849],\n            [-122.426821397293551, 37.809102471733105],\n            [-122.426817149990782, 37.809138937589218],\n            [-122.426811172632014, 37.809175431983689],\n            [-122.42680517743706, 37.809211239393754],\n            [-122.426799200066583, 37.80924773378711],\n            [-122.426791474798563, 37.809283569459971],\n            [-122.426783766999961, 37.80932009184702],\n            [-122.426764854928408, 37.809391820287793],\n            [-122.426753650652387, 37.809427026340984],\n            [-122.426742463842274, 37.809462919107801],\n            [-122.42673125988388, 37.809498124878161],\n            [-122.426718325162497, 37.809533359195932],\n            [-122.426703641860442, 37.809567935625843],\n            [-122.426690707106644, 37.80960316966555],\n            [-122.426674294047132, 37.809637774073536],\n            [-122.426659593228109, 37.809671663782375],\n            [-122.426641449382515, 37.809706296733168],\n            [-122.426624999956104, 37.809739528275571],\n            [-122.426606838595305, 37.809773474231129],\n            [-122.426586928644923, 37.809806762295736],\n            [-122.426568749097328, 37.809840022090548],\n            [-122.426547090883986, 37.809872652254199],\n            [-122.426527163066623, 37.80990525387346],\n            [-122.426505504455619, 37.809937883485567],\n            [-122.426482098287991, 37.809969855187106],\n            [-122.426435249574482, 37.810032425717694],\n            [-122.426410094762232, 37.810063739239673],\n            [-122.426384921777455, 37.810094366601653],\n            [-122.426359731637717, 37.810124306962969],\n            [-122.426305889769012, 37.810184245028879],\n            [-122.426277202393095, 37.810212869038814],\n            [-122.426248533505472, 37.810242179739554],\n            [-122.426219828627396, 37.810270117569488],\n            [-122.426165541280753, 37.810312894703067],\n            [-122.426148806543424, 37.810335143220563],\n            [-122.426077517869416, 37.810389871747489],\n            [-122.426029956191201, 37.810424984999884],\n            [-122.426004427087875, 37.810441883304833],\n            [-122.425978879818175, 37.810458095175186],\n            [-122.425953351037933, 37.810474993463338],\n            [-122.425902221511379, 37.810506044020848],\n            [-122.425874926298846, 37.810521597684627],\n            [-122.425847613267436, 37.810536464907365],\n            [-122.425792951557781, 37.810564826464294],\n            [-122.425738254193391, 37.810591815126337],\n            [-122.425709157251291, 37.810604651266786],\n            [-122.425621812948378, 37.810641100340696],\n            [-122.425563512153047, 37.810662654222938],\n            [-122.425532631291532, 37.810673458992433],\n            [-122.425501732623886, 37.810683577593657],\n            [-122.425472546238581, 37.810692981507543],\n            [-122.425441630096913, 37.810702413652621],\n            [-122.425410695451745, 37.810711159366136],\n            [-122.425348791232992, 37.810727278162595],\n            [-122.425317821646317, 37.810734650696368],\n            [-122.42528512127241, 37.810742051749649],\n            [-122.425254133532235, 37.81074873811216],\n            [-122.425221397542259, 37.810754766278279],\n            [-122.425190391643511, 37.810760766194903],\n            [-122.425157620386827, 37.810765421468112],\n            [-122.425124866580816, 37.810770763172656],\n            [-122.425093807273001, 37.810774704034145],\n            [-122.425028228777975, 37.810782641379291],\n            [-122.424962578393604, 37.810787833509224],\n            [-122.424931484149809, 37.810790400897922],\n            [-122.424898623523902, 37.810791623930832],\n            [-122.424876305744775, 37.810798855419677],\n            [-122.424848476260408, 37.810793815800935],\n            [-122.424819040922628, 37.810793609459935],\n            [-122.42478787514149, 37.810793431351755],\n            [-122.424758404216234, 37.810791852400563],\n            [-122.42472891548077, 37.810789586458021],\n            [-122.424699408618324, 37.810786634628094],\n            [-122.424640324731897, 37.810777984365998],\n            [-122.424610747039267, 37.810772286768845],\n            [-122.424582881984065, 37.810765873943147],\n            [-122.42455326835632, 37.810758803192932],\n            [-122.424525367734788, 37.810751018033073],\n            [-122.424497431514737, 37.810741859447681],\n            [-122.42447122609704, 37.810732672889145],\n            [-122.424443254308628, 37.810722141695749],\n            [-122.424406843510837, 37.810719989167595],\n            [-122.424404739729965, 37.810705601989262],\n            [-122.424392430564922, 37.810698249126709],\n            [-122.424380086492334, 37.810689522832604],\n            [-122.424367724289866, 37.810680110382542],\n            [-122.424357092185232, 37.810670669703008],\n            [-122.424346425188247, 37.810659856141378],\n            [-122.424337488288586, 37.810649014350894],\n            [-122.424328533252009, 37.810637486130467],\n            [-122.424321309004071, 37.810625929670387],\n            [-122.424315797750651, 37.810613658536091],\n            [-122.424310250566421, 37.810600014537307],\n            [-122.42430472117745, 37.810587056973155],\n            [-122.4243009222361, 37.810574071450297],\n            [-122.424298835580885, 37.810560370715905],\n            [-122.424298497510776, 37.810547328452351],\n            [-122.424299872431348, 37.810533571515215],\n            [-122.424301264798501, 37.810520501018615],\n            [-122.424304387263248, 37.810507402569137],\n            [-122.424309258634793, 37.810494961760924],\n            [-122.424315860102183, 37.810482492999512],\n            [-122.424324191657433, 37.810469996010013],\n            [-122.424332559488974, 37.810458871878474],\n            [-122.42434440529064, 37.810448377725301],\n            [-122.424354539139387, 37.810438598228359],\n            [-122.424368151310617, 37.810429448977779],\n            [-122.424380050823189, 37.810421013845534],\n            [-122.424395446797789, 37.810413895387818],\n            [-122.424410860570717, 37.810407463637567],\n            [-122.424426309569299, 37.810402404211651],\n            [-122.424441794853934, 37.810398717916826],\n            [-122.42445902802703, 37.810395690099803],\n            [-122.424474548198873, 37.810393376407241],\n            [-122.424495313410532, 37.810393037578869],\n            [-122.424512653696851, 37.810394128357849],\n            [-122.424528280984248, 37.810395933262043],\n            [-122.424545674311858, 37.810399083346539],\n            [-122.424561354987802, 37.810402947551076],\n            [-122.424577071263883, 37.810408184897881],\n            [-122.424591092706251, 37.810414823624235],\n            [-122.424605131932822, 37.810422148234672],\n            [-122.424619206768625, 37.810430846262342],\n            [-122.424631551158143, 37.810439571977035],\n            [-122.424642200368709, 37.810449699078426],\n            [-122.424652868070766, 37.810460512602432],\n            [-122.424661822790284, 37.810472040804292],\n            [-122.424669047076236, 37.810483597243831],\n            [-122.424676306964855, 37.810496526827265],\n            [-122.424683904979332, 37.810522498123575],\n            [-122.424685973208497, 37.810535512153116],\n            [-122.424686685078399, 37.810562969547746],\n            [-122.424688754001352, 37.810575983565833],\n            [-122.424694265313605, 37.810588254682798],\n            [-122.424703220408176, 37.810599783150167],\n            [-122.424713852193449, 37.810609223529688],\n            [-122.424727909273159, 37.81061723483468],\n            [-122.424743643057568, 37.81062315859959],\n            [-122.424759306354005, 37.810626336886287],\n            [-122.424774915898695, 37.810627455322866],\n            [-122.424844204676688, 37.810629071443891],\n            [-122.424883986990039, 37.810627735460123],\n            [-122.424942750746894, 37.810624029768022],\n            [-122.424970402172974, 37.810622204741975],\n            [-122.424999748098813, 37.810618978876803],\n            [-122.425029112521329, 37.810616439702436],\n            [-122.42505844098109, 37.810612527107622],\n            [-122.425086039005805, 37.810608643024629],\n            [-122.425115367473737, 37.810604730964819],\n            [-122.425142947684478, 37.810600160159147],\n            [-122.425172240190349, 37.810594874946659],\n            [-122.42519980259955, 37.810589617967473],\n            [-122.425229077987609, 37.810583646294816],\n            [-122.425311658363697, 37.810563756689383],\n            [-122.425339167350202, 37.810556440647844],\n            [-122.425394149679065, 37.810540434852271],\n            [-122.425449096383744, 37.810523056436082],\n            [-122.425474821139559, 37.810513709039498],\n            [-122.42550225886724, 37.810503646946287],\n            [-122.425553673774345, 37.810483579237776],\n            [-122.425579363239436, 37.810472858943186],\n            [-122.425656378173159, 37.810438638721514],\n            [-122.425680301215294, 37.81042657379389],\n            [-122.425705937577163, 37.810413794436336],\n            [-122.425729843135883, 37.81040104278366],\n            [-122.425777618636445, 37.81037416741794],\n            [-122.425799775930017, 37.810360757570926],\n            [-122.425823645502135, 37.810346633309891],\n            [-122.425845767517558, 37.810331850853636],\n            [-122.425869619617202, 37.810317040417416],\n            [-122.425891740915375, 37.81030225768918],\n            [-122.425912114318606, 37.810286817047619],\n            [-122.425934218490383, 37.810271348139892],\n            [-122.42595457407505, 37.810255221330763],\n            [-122.425976660073871, 37.81023906598594],\n            [-122.426033069704985, 37.810211362243855],\n            [-122.426044381296308, 37.810180274872565],\n            [-122.426071409213066, 37.810154424922551],\n            [-122.426098419287058, 37.810127887982347],\n            [-122.426123698921984, 37.810101379570156],\n            [-122.42617422251692, 37.810046989859728],\n            [-122.426197736051108, 37.810019136822319],\n            [-122.426222979645814, 37.809991255524551],\n            [-122.426244745255502, 37.809962744298282],\n            [-122.426268240925296, 37.809934204811682],\n            [-122.426289987997293, 37.809905007153375],\n            [-122.426310004983833, 37.8098758380223],\n            [-122.426331734895541, 37.809845953635381],\n            [-122.426350003264446, 37.809816126056347],\n            [-122.426370002384175, 37.809786270205322],\n            [-122.42640650446053, 37.80972524214534],\n            [-122.426423024539801, 37.809694756383479],\n            [-122.426439527484902, 37.809663584173109],\n            [-122.426456011918219, 37.809631725811407],\n            [-122.426470784413283, 37.809600581586317],\n            [-122.426485538743833, 37.809568751204715],\n            [-122.426498562648177, 37.809536949086315],\n            [-122.426511568735094, 37.809504460806153],\n            [-122.426524592609866, 37.809472658409931],\n            [-122.426535868255684, 37.809440198117713],\n            [-122.426547125731901, 37.809407051395155],\n            [-122.426556671294222, 37.80937461936086],\n            [-122.42657572607385, 37.809308382431155],\n            [-122.426583523043476, 37.809275292231341],\n            [-122.426589589599459, 37.809242230297407],\n            [-122.426597386209863, 37.809209140101942],\n            [-122.426601704881108, 37.809175419993217],\n            [-122.42660775360666, 37.809141671622967],\n            [-122.426612089737361, 37.809108637953877],\n            [-122.42661985311382, 37.809007562446936],\n            [-122.426622425964752, 37.808906571727555],\n            [-122.426632418849564, 37.808824688117525],\n            [-122.426616150524296, 37.80879817156422],\n            [-122.426607002185577, 37.808712480555535],\n            [-122.426596123115345, 37.808626817267942],\n            [-122.42658353220007, 37.808541869217166],\n            [-122.426569192751245, 37.808456262451642],\n            [-122.426553140769713, 37.808371370658513],\n            [-122.426535358440475, 37.808286507127633],\n            [-122.426521448780022, 37.808150762619441],\n            [-122.42648716178519, 37.808163683754294],\n            [-122.426478316482672, 37.808089661844356],\n            [-122.426472145992449, 37.808051992713068],\n            [-122.426465992974997, 37.808015010021762],\n            [-122.42645810959354, 37.807978055869093],\n            [-122.426450244018099, 37.807941787601465],\n            [-122.426431016184523, 37.807867935277343],\n            [-122.426421420613309, 37.807831695540209],\n            [-122.426410094332368, 37.807795484071661],\n            [-122.426383981060937, 37.807723117657737],\n            [-122.426370941909852, 37.807687620888885],\n            [-122.426356154936116, 37.8076514659409],\n            [-122.42634140359948, 37.807616683860772],\n            [-122.426324904443447, 37.807581243600652],\n            [-122.426289370917274, 37.807546114229226],\n            [-122.426116990256872, 37.807640950874436],\n            [-122.426004575599805, 37.807511622087709],\n            [-122.425948221432193, 37.807541385135394],\n            [-122.426072979790121, 37.807679439549148],\n            [-122.426043902201883, 37.807692962208542],\n            [-122.425788908967789, 37.807405954455511],\n            [-122.425811101339534, 37.807393917738004],\n            [-122.425841407866244, 37.807427759425885],\n            [-122.425860156968014, 37.807416465651144],\n            [-122.425910013941177, 37.807469902573231],\n            [-122.425894653990497, 37.807478394108394],\n            [-122.425932202930852, 37.807524478297722],\n            [-122.425969647726248, 37.80749983141159],\n            [-122.425933954139211, 37.807458524016887],\n            [-122.426097611558333, 37.807361083192824],\n            [-122.42613200511353, 37.807352281054179],\n            [-122.426091315966914, 37.80731860900228],\n            [-122.4260559073373, 37.80728828460429],\n            [-122.425981701022749, 37.807230438026032],\n            [-122.425942884837923, 37.807202229419872],\n            [-122.425865288869659, 37.807147185302298],\n            [-122.425824778720127, 37.807120377771732],\n            [-122.425784286059837, 37.807094256667916],\n            [-122.425743811226624, 37.807068821710658],\n            [-122.425659436564132, 37.807019381954113],\n            [-122.425575133216185, 37.806972687328404],\n            [-122.425531269042693, 37.806950055090923],\n            [-122.425487422692115, 37.806928108997688],\n            [-122.425396304969212, 37.806885646130702],\n            [-122.425305258549756, 37.806845928659463],\n            [-122.425259770989555, 37.806827443179728],\n            [-122.425212552741186, 37.806808985385324],\n            [-122.425165353022919, 37.806791214545072],\n            [-122.425118188573762, 37.806774816287508],\n            [-122.425069293788937, 37.80675844598079],\n            [-122.4250204168317, 37.806742762363598],\n            [-122.424973288049415, 37.806727737193157],\n            [-122.42492444672159, 37.806713426132198],\n            [-122.424873892869243, 37.806699830002309],\n            [-122.424802432493848, 37.806681081528858],\n            [-122.424772802670631, 37.806673324118421],\n            [-122.424744921357572, 37.806666225439869],\n            [-122.424689194317793, 37.806653400109965],\n            [-122.424659600807814, 37.806647015805055],\n            [-122.424631755098972, 37.806641289695882],\n            [-122.42460219684277, 37.806636278252959],\n            [-122.42457436893713, 37.806631238565636],\n            [-122.424544828483292, 37.806626913543731],\n            [-122.424517018379291, 37.806622560278207],\n            [-122.424487495380134, 37.806618921682983],\n            [-122.424428486015572, 37.806613017324239],\n            [-122.424398998964037, 37.806610751846591],\n            [-122.42437124224621, 37.806608457578399],\n            [-122.424341772644766, 37.806606878527504],\n            [-122.424282870046667, 37.806605092433877],\n            [-122.424224002349433, 37.806604679467583],\n            [-122.424165170926941, 37.806605639331515],\n            [-122.424135772832813, 37.80660680569077],\n            [-122.424077012229233, 37.806610511532789],\n            [-122.424018287184794, 37.806615589667317],\n            [-122.42398895989578, 37.806619501737501],\n            [-122.423959615506732, 37.806622727353634],\n            [-122.423932018904594, 37.806626611175993],\n            [-122.423873400581527, 37.806635808400436],\n            [-122.423845839538984, 37.806641064799031],\n            [-122.423816565594137, 37.806647036132823],\n            [-122.423789022338056, 37.806652979228197],\n            [-122.423759766870432, 37.806659637246682],\n            [-122.423706445903605, 37.806672867782268],\n            [-122.423653321266002, 37.806693649347707],\n            [-122.423589920527235, 37.806718718849552],\n            [-122.423526538222887, 37.806744474741464],\n            [-122.423399843925978, 37.80679873217619],\n            [-122.423338262626473, 37.806827205494514],\n            [-122.423215171014846, 37.806886897776501],\n            [-122.423155391052063, 37.806918088524029],\n            [-122.423095628820221, 37.806949965676615],\n            [-122.423037614673063, 37.806982501019867],\n            [-122.422979636035322, 37.807016409205524],\n            [-122.422923387701616, 37.807050289149693],\n            [-122.422867174874767, 37.807085541937973],\n            [-122.422807430162962, 37.807118105379153],\n            [-122.422669617719095, 37.80721100017012],\n            [-122.422538512716017, 37.807295544473774],\n            [-122.422410922154015, 37.807382091240839],\n            [-122.422406568873129, 37.807385156047935],\n            [-122.422285114644055, 37.807470669252126],\n            [-122.422159360151895, 37.8075613064347],\n            [-122.422037101638551, 37.807653259953362],\n            [-122.421916608736879, 37.807746558017001],\n            [-122.421799629592272, 37.807841858870269],\n            [-122.42168270310637, 37.807939218918449],\n            [-122.421532057745097, 37.808071464473507],\n            [-122.421789175548739, 37.808374241220797],\n            [-122.421758561641454, 37.808395342089796],\n            [-122.421493253559859, 37.808110553294661],\n            [-122.421324496714121, 37.808278803348081],\n            [-122.421367127445279, 37.808320685807196],\n            [-122.421400626951964, 37.808344175435167],\n            [-122.421417984134223, 37.808345953110788],\n            [-122.421538848573221, 37.808467594219884],\n            [-122.421521846706554, 37.808479545548913],\n            [-122.421585810928775, 37.808543055557919],\n            [-122.421558551068415, 37.808559981099798],\n            [-122.421494604621685, 37.808497157511823],\n            [-122.421482722503114, 37.808506278522707],\n            [-122.42136546133365, 37.808390072182092],\n            [-122.421343808401488, 37.808356088677073],\n            [-122.421300875659668, 37.808302536803509],\n            [-122.421061625998746, 37.808556401980894],\n            [-122.421102646347336, 37.808803641534986],\n            [-122.421495500883367, 37.809133737012722],\n            [-122.421447795704026, 37.809163356667042],\n            [-122.422886026597482, 37.810219448665336],\n            [-122.422843690020329, 37.8102558485987],\n            [-122.42136637775458, 37.809161249445332],\n            [-122.421209585435776, 37.809189899457976],\n            [-122.421035643651535, 37.808957186122555],\n            [-122.420944163231766, 37.808967603286121],\n            [-122.420910323787112, 37.808797159929568],\n            [-122.420858358549751, 37.808795946109164],\n            [-122.420717381312784, 37.80890056409217],\n            [-122.420760227934167, 37.809084595303112],\n            [-122.420694330112809, 37.809080174685604],\n            [-122.420677247269424, 37.809022081268409],\n            [-122.420415813879998, 37.809020844655912],\n            [-122.420359979289643, 37.809003898946663],\n            [-122.420384124514499, 37.808933459866985],\n            [-122.420668674045118, 37.808958355470025],\n            [-122.420635748727079, 37.808756307812423],\n            [-122.420540860637956, 37.808768840609403],\n            [-122.420461300526242, 37.808303163202581],\n            [-122.419223966222532, 37.808453096037454],\n            [-122.419086067910683, 37.807803117635643],\n            [-122.418908043871269, 37.806863030947284],\n            [-122.418856183917853, 37.806607498073362],\n            [-122.418781854642702, 37.806241248585302],\n            [-122.418781807492508, 37.806241018338142],\n            [-122.418718265666911, 37.805931520041717],\n            [-122.418530631419571, 37.804999043218864],\n            [-122.418341342522965, 37.804070022432718],\n            [-122.418280954876565, 37.804023161939504],\n            [-122.418215611082957, 37.803687299847837],\n            [-122.418255886912107, 37.803650215613203],\n            [-122.418151092101994, 37.803135399210959],\n            [-122.417966229723234, 37.802201308768375],\n            [-122.41787748762404, 37.801706751150704],\n            [-122.417798642975782, 37.801267347943607],\n            [-122.417586974866069, 37.800340791478632],\n            [-122.417492539304973, 37.799906258247788],\n            [-122.417385454528372, 37.799413513042644],\n            [-122.416491256558686, 37.799525268171301],\n            [-122.41576151132729, 37.799616465113374],\n            [-122.415667503417794, 37.799150732537029],\n            [-122.415572319635913, 37.798680127283284],\n            [-122.415571551794869, 37.798676329254079],\n            [-122.415384693800732, 37.797752451808172],\n            [-122.415384105347158, 37.79774954140936],\n            [-122.415290513107706, 37.797286788953464],\n            [-122.41519297245145, 37.796804503853558],\n            [-122.415172033219093, 37.796700969266084],\n            [-122.415094279286492, 37.796316514471208],\n            [-122.415005455310151, 37.795877318912275],\n            [-122.414825631965456, 37.794988155997579],\n            [-122.414647632176155, 37.794109807074861],\n            [-122.414470074462415, 37.79322314646798],\n            [-122.414381908270244, 37.792784070108439],\n            [-122.414293264567746, 37.792342610236801],\n            [-122.414107303557273, 37.79141647884336],\n            [-122.413915879652706, 37.790463115032836]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 9, ZIP_CODE: 94111, ID: 94111},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.400058259594957, 37.793693587424592],\n            [-122.400148778887171, 37.794134234665414],\n            [-122.400149325778898, 37.794134165695652],\n            [-122.401314504615144, 37.793987063215781],\n            [-122.402133431503088, 37.793883778773662],\n            [-122.40215951272063, 37.793880489466119],\n            [-122.402957360909014, 37.793779857671261],\n            [-122.40308689535884, 37.793763518993806],\n            [-122.404016114128936, 37.793646309359175],\n            [-122.404370134182003, 37.793598168205968],\n            [-122.404613421373455, 37.793565084338127],\n            [-122.404710582177955, 37.794021380197684],\n            [-122.404796472837063, 37.794453004909116],\n            [-122.404875610619072, 37.794859533373135],\n            [-122.404957208224928, 37.795326691921687],\n            [-122.405142977212691, 37.796210954635839],\n            [-122.404412160626791, 37.796302098196776],\n            [-122.403496709817034, 37.796417485859841],\n            [-122.40291796673857, 37.796490379340391],\n            [-122.402402444702886, 37.796554566975963],\n            [-122.401853390100442, 37.796626165074152],\n            [-122.401906946643678, 37.796875708196644],\n            [-122.402040983242472, 37.797504944420098],\n            [-122.402228639238132, 37.798429701815799],\n            [-122.402420666593613, 37.799382141273902],\n            [-122.402603736644934, 37.800305175692849],\n            [-122.402604124590809, 37.800307129616797],\n            [-122.402789664410264, 37.801242172237629],\n            [-122.402883459886269, 37.801713453423041],\n            [-122.402973611142329, 37.802166419353441],\n            [-122.402973676735002, 37.802166749844929],\n            [-122.40315960682554, 37.803098380641011],\n            [-122.40352117840483, 37.80305477917063],\n            [-122.404798316633176, 37.802900762043983],\n            [-122.404986245080067, 37.803832670552438],\n            [-122.405173536628851, 37.804763212222298],\n            [-122.405356257037084, 37.805690948342658],\n            [-122.405357241074242, 37.805695943874476],\n            [-122.405362337774832, 37.805695301990511],\n            [-122.405545441190654, 37.806634177056651],\n            [-122.405495723806581, 37.806640585378162],\n            [-122.404696069838451, 37.806264743038192],\n            [-122.404128809815958, 37.80582849738515],\n            [-122.403587277644846, 37.805412029499024],\n            [-122.403194419486127, 37.805118563841738],\n            [-122.401732349384048, 37.805192215433102],\n            [-122.400572958659495, 37.806120993590262],\n            [-122.400963593501643, 37.803563391134098],\n            [-122.400842454396695, 37.803429372376385],\n            [-122.400479886428016, 37.803466118528789],\n            [-122.398551197769294, 37.804602806142064],\n            [-122.398535712754665, 37.804606489036345],\n            [-122.398520052204233, 37.804603307533085],\n            [-122.398559631153262, 37.804526444219782],\n            [-122.398227927335071, 37.804281130126874],\n            [-122.398222420518948, 37.804268857764917],\n            [-122.398225564864617, 37.80425644615601],\n            [-122.399971727587683, 37.803225025895152],\n            [-122.399602741790233, 37.802807948916175],\n            [-122.397824683987196, 37.803810348092696],\n            [-122.397809199101829, 37.803814030892141],\n            [-122.397793538749667, 37.803810849291956],\n            [-122.397517150597622, 37.803494597437428],\n            [-122.397511661501127, 37.80348301148031],\n            [-122.3975148062854, 37.803470600157546],\n            [-122.399615259663278, 37.802282404799911],\n            [-122.39972332623249, 37.802176282179168],\n            [-122.399409670069147, 37.801824238674413],\n            [-122.399371692841143, 37.801828283680663],\n            [-122.399347556878283, 37.801832106025373],\n            [-122.399325151849467, 37.801835900487347],\n            [-122.399302764032285, 37.801840381390434],\n            [-122.39928039377223, 37.801845548729034],\n            [-122.399258040722614, 37.801851402508781],\n            [-122.399235705920944, 37.801857942712921],\n            [-122.399215101014406, 37.801864455053376],\n            [-122.399192800984025, 37.8018723684091],\n            [-122.399172231180287, 37.801880253347079],\n            [-122.399151696503523, 37.801889511710215],\n            [-122.397115576147101, 37.803021039584841],\n            [-122.397100091038084, 37.803024722022734],\n            [-122.397084431235825, 37.80302154059693],\n            [-122.396146888856933, 37.801962584949095],\n            [-122.396143112804936, 37.801950284664713],\n            [-122.396146275276891, 37.801938559540567],\n            [-122.396156183429241, 37.801919858733392],\n            [-122.398383194135846, 37.80067265166084],\n            [-122.398279805658603, 37.800555512940733],\n            [-122.397989357773142, 37.800230561089471],\n            [-122.395767421385884, 37.801472872307706],\n            [-122.395751936546873, 37.801476555118818],\n            [-122.395736277114864, 37.801473372963947],\n            [-122.395444597217249, 37.801167663046662],\n            [-122.395439108270097, 37.801156076993678],\n            [-122.395442253641278, 37.801143665440662],\n            [-122.397525741108808, 37.799970200398498],\n            [-122.397542727338319, 37.799957565794791],\n            [-122.397573204312565, 37.79993098020509],\n            [-122.397588407347527, 37.799916314120374],\n            [-122.397601862985724, 37.799900989703779],\n            [-122.397613588764841, 37.799885693121588],\n            [-122.397625296656045, 37.799869710378033],\n            [-122.397635256446222, 37.799853068765884],\n            [-122.397583045956239, 37.799774248814408],\n            [-122.397381692278955, 37.799548810060863],\n            [-122.397362923291766, 37.799559412884541],\n            [-122.397350478314067, 37.799546565387622],\n            [-122.39725607725606, 37.79944232919879],\n            [-122.396973022275191, 37.799609635784066],\n            [-122.396895724416169, 37.799632854304804],\n            [-122.396944532454214, 37.799578505110418],\n            [-122.39725156444139, 37.799401198172731],\n            [-122.397069823583166, 37.79919810547964],\n            [-122.39674205306757, 37.798834607866056],\n            [-122.396702784737215, 37.798855840868825],\n            [-122.396669034992286, 37.798822047566397],\n            [-122.396624664146429, 37.798846796498019],\n            [-122.396609180236553, 37.798850479143326],\n            [-122.396595251158345, 37.798847269554258],\n            [-122.396579311359616, 37.798833104740751],\n            [-122.395709347701867, 37.79933397933474],\n            [-122.395681634538477, 37.799401037179429],\n            [-122.395581007954107, 37.799459652414193],\n            [-122.395491073098484, 37.799462471471649],\n            [-122.39520444855259, 37.799625711063314],\n            [-122.394470973718072, 37.800049529845779],\n            [-122.394485183177821, 37.80006372301127],\n            [-122.394463863656298, 37.80011007561265],\n            [-122.394495883468409, 37.800143897322769],\n            [-122.394436254505337, 37.800181251519263],\n            [-122.394338714865782, 37.800157409688907],\n            [-122.39425176311174, 37.800073652886503],\n            [-122.394244541007424, 37.799994109133792],\n            [-122.39430588261277, 37.79995604107372],\n            [-122.39433954456824, 37.799986402561352],\n            [-122.394405256913004, 37.79998397351315],\n            [-122.394419466347642, 37.799998166686734],\n            [-122.395383240540369, 37.799442229567333],\n            [-122.3954446687179, 37.79940759283992],\n            [-122.395460481077649, 37.799348967284097],\n            [-122.395564480002861, 37.799286864055659],\n            [-122.395650954616428, 37.799284100562168],\n            [-122.396137291028239, 37.799011206820815],\n            [-122.39652596834469, 37.798777651667436],\n            [-122.396513506102295, 37.798764117365188],\n            [-122.396508017858096, 37.798752531345663],\n            [-122.396511162609784, 37.798740120038758],\n            [-122.396521298172757, 37.798730342643047],\n            [-122.396562296308161, 37.798709081605075],\n            [-122.396530276519627, 37.798675260441449],\n            [-122.396574647301449, 37.798650511819105],\n            [-122.396522999163949, 37.798593657491452],\n            [-122.396499841034597, 37.79856793447253],\n            [-122.396467423337157, 37.798586310883579],\n            [-122.396357190239343, 37.798472027219738],\n            [-122.396393032843889, 37.798452222590186],\n            [-122.395860656588496, 37.797870888391557],\n            [-122.395839947322898, 37.79787328122778],\n            [-122.395814065475506, 37.797876444215788],\n            [-122.395788200806479, 37.797880293644518],\n            [-122.395762353661098, 37.797884829508355],\n            [-122.395738254199486, 37.7978900242679],\n            [-122.395712442099452, 37.797895933002444],\n            [-122.395664348290325, 37.797910440317423],\n            [-122.395640318568425, 37.797918380825799],\n            [-122.395616307044421, 37.797927007209815],\n            [-122.39557006603053, 37.797946292034375],\n            [-122.395547836878862, 37.797956950195463],\n            [-122.395525625244971, 37.797968294793179],\n            [-122.395505143760289, 37.797979611576075],\n            [-122.393842000273821, 37.798938215357374],\n            [-122.393826533722603, 37.798942584352829],\n            [-122.39381087421539, 37.798939401951962],\n            [-122.393557994085199, 37.798661220282753],\n            [-122.393552505963697, 37.798649634131031],\n            [-122.393557381363038, 37.798637194571512],\n            [-122.395454327884067, 37.797548480176857],\n            [-122.395493853667872, 37.797469558194479],\n            [-122.395422650268614, 37.797392416327632],\n            [-122.395305000587612, 37.797394307309993],\n            [-122.395131013605095, 37.797494618186263],\n            [-122.394995545751499, 37.797340909474201],\n            [-122.395176347980751, 37.797236368651362],\n            [-122.395203657816268, 37.797153523030403],\n            [-122.395175134630833, 37.797121018758673],\n            [-122.395015962104765, 37.797123576784635],\n            [-122.394842063300572, 37.797227319439131],\n            [-122.394877471902745, 37.797258339703205],\n            [-122.393211663425149, 37.798247880172283],\n            [-122.393197909634068, 37.798251534593128],\n            [-122.393182250998592, 37.798248352371843],\n            [-122.392924377254829, 37.797977803222231],\n            [-122.392918871782612, 37.79796553059834],\n            [-122.392922034593937, 37.797953805551998],\n            [-122.392933760337485, 37.797938509440698],\n            [-122.393022573378389, 37.797891760094423],\n            [-122.393418332763261, 37.797664281733844],\n            [-122.39349443929828, 37.797594387482981],\n            [-122.393614723501102, 37.797492194551531],\n            [-122.39379620770903, 37.797346441866999],\n            [-122.393858891962495, 37.797293244534764],\n            [-122.39453577454303, 37.796890939592352],\n            [-122.394518070397197, 37.796875429409333],\n            [-122.394797677744066, 37.796708870398604],\n            [-122.394681926486655, 37.796581626428896],\n            [-122.394272580881463, 37.796818941592662],\n            [-122.39351702175675, 37.797258217667746],\n            [-122.39355080472113, 37.797293385033349],\n            [-122.39316857384685, 37.797508285701419],\n            [-122.393092042374221, 37.797425733952373],\n            [-122.393468994757654, 37.797207484670842],\n            [-122.393490421626595, 37.797233236341413],\n            [-122.39412658804784, 37.796863176781166],\n            [-122.393932453058198, 37.796648603921177],\n            [-122.393850369991199, 37.796687691964301],\n            [-122.393741908884792, 37.796574751186853],\n            [-122.393880385256736, 37.796507288896663],\n            [-122.393597295890373, 37.796198002709581],\n            [-122.393523863725633, 37.796236951615114],\n            [-122.393397437145097, 37.796098203833878],\n            [-122.393481197043371, 37.796057028727645],\n            [-122.393264277786542, 37.795831146645604],\n            [-122.39319930347132, 37.795862405337601],\n            [-122.393014017140644, 37.7956552428926],\n            [-122.39305487457618, 37.795628491529783],\n            [-122.392997761288825, 37.795560736430133],\n            [-122.392816418294032, 37.795643993697439],\n            [-122.391589731865537, 37.796205501614423],\n            [-122.391196361068637, 37.795779176166889],\n            [-122.39241116638668, 37.794819563891082],\n            [-122.39226709396803, 37.794667363398794],\n            [-122.391941265746169, 37.794853886332461],\n            [-122.391649609982679, 37.794547480215279],\n            [-122.391828732520565, 37.794445031825084],\n            [-122.391802064349278, 37.794417304057546],\n            [-122.391945361729881, 37.794335345459139],\n            [-122.391890260341384, 37.794278544568918],\n            [-122.392507683750154, 37.79377708635252],\n            [-122.39249932896719, 37.793768814133983],\n            [-122.392702063215523, 37.793608792659974],\n            [-122.39270797067924, 37.793614415765255],\n            [-122.394150899798674, 37.794987788593353],\n            [-122.394745701490038, 37.794478274267938],\n            [-122.394745733667804, 37.794478246556643],\n            [-122.394747579547172, 37.794479718347191],\n            [-122.394971722038662, 37.794300351169447],\n            [-122.395317307728533, 37.794023797689903],\n            [-122.39533276386878, 37.794011429145591],\n            [-122.395622922555773, 37.793779228585429],\n            [-122.395983670970253, 37.793501461560965],\n            [-122.396302004290646, 37.793256350429786],\n            [-122.396302004629632, 37.793256350149633],\n            [-122.396376076969489, 37.793198086984006],\n            [-122.396504486670068, 37.793097082520163],\n            [-122.39738344591872, 37.792405521295422],\n            [-122.397840810091907, 37.792047595331439],\n            [-122.39826444614279, 37.791716060417308],\n            [-122.398264448861653, 37.791716058450703],\n            [-122.399148598683425, 37.79101664916432],\n            [-122.39914859901539, 37.791016648609592],\n            [-122.399148705293825, 37.791016743863253],\n            [-122.399148704954868, 37.791016744143391],\n            [-122.399494165511513, 37.791324916192721],\n            [-122.399572029409299, 37.791326533708947],\n            [-122.399763353084495, 37.792257948007119],\n            [-122.399958735737897, 37.793209101900771],\n            [-122.400058259594957, 37.793693587424592]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 10, ZIP_CODE: 94104, ID: 94104},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.400067228055164, 37.790303858609981],\n            [-122.401375490979433, 37.789264321921429],\n            [-122.4018939793784, 37.788856309111516],\n            [-122.402065730098272, 37.788721152097366],\n            [-122.401960433993054, 37.788922171387867],\n            [-122.401997470431979, 37.789102650051042],\n            [-122.402189645264357, 37.790039096806382],\n            [-122.402532679537515, 37.789995540560724],\n            [-122.40273920526738, 37.789969317269424],\n            [-122.403841563913829, 37.789829338872408],\n            [-122.403934377264903, 37.790286026286218],\n            [-122.404030724679231, 37.790760095776051],\n            [-122.404219835191427, 37.791690583910508],\n            [-122.40441842761642, 37.79263865633952],\n            [-122.404613389070732, 37.793564931583006],\n            [-122.404613421373455, 37.793565084338127],\n            [-122.404370134182003, 37.793598168205968],\n            [-122.404016114128936, 37.793646309359175],\n            [-122.40308689535884, 37.793763518993806],\n            [-122.402957360909014, 37.793779857671261],\n            [-122.40215951272063, 37.793880489466119],\n            [-122.402133431503088, 37.793883778773662],\n            [-122.401314504615144, 37.793987063215781],\n            [-122.400149325778898, 37.794134165695652],\n            [-122.400148778887171, 37.794134234665414],\n            [-122.400058259594957, 37.793693587424592],\n            [-122.399958735737897, 37.793209101900771],\n            [-122.399763353084495, 37.792257948007119],\n            [-122.399572029409299, 37.791326533708947],\n            [-122.399494165511513, 37.791324916192721],\n            [-122.399148704954868, 37.791016744143391],\n            [-122.399148705293825, 37.791016743863253],\n            [-122.39914859901539, 37.791016648609592],\n            [-122.399222136170906, 37.790956214990921],\n            [-122.399480001759599, 37.790744297277485],\n            [-122.399919790020746, 37.790414442896498],\n            [-122.400024135710936, 37.790336179680637],\n            [-122.400067228055164, 37.790303858609981]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 11, ZIP_CODE: 94108, ID: 94108},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.404219835191427, 37.791690583910508],\n            [-122.404030724679231, 37.790760095776051],\n            [-122.403934377264903, 37.790286026286218],\n            [-122.403841563913829, 37.789829338872408],\n            [-122.40273920526738, 37.789969317269424],\n            [-122.402532679537515, 37.789995540560724],\n            [-122.402189645264357, 37.790039096806382],\n            [-122.401997470431979, 37.789102650051042],\n            [-122.401960433993054, 37.788922171387867],\n            [-122.402065730098272, 37.788721152097366],\n            [-122.402404665507532, 37.788463925757384],\n            [-122.402903089488873, 37.788085654318181],\n            [-122.40290309900054, 37.788085647297414],\n            [-122.403239953883428, 37.787802352067843],\n            [-122.403430800369534, 37.787641848645734],\n            [-122.403925775648347, 37.787250481113581],\n            [-122.404583316787637, 37.786730565897571],\n            [-122.404583317133643, 37.786730565891986],\n            [-122.404583317818549, 37.786730565606241],\n            [-122.4045836593057, 37.786730668317922],\n            [-122.404583659644615, 37.786730668037741],\n            [-122.404849901045083, 37.78680995410096],\n            [-122.405346079234604, 37.786747618574395],\n            [-122.40639887788349, 37.786615347203238],\n            [-122.408036236552462, 37.786409613336666],\n            [-122.408226725993813, 37.78735926231473],\n            [-122.408401550373739, 37.788293193957799],\n            [-122.408595045382015, 37.789225614946417],\n            [-122.408595692092703, 37.789225606675593],\n            [-122.410242438640608, 37.789016337723744],\n            [-122.411885657146456, 37.788807543703371],\n            [-122.412075867445836, 37.789739906579548],\n            [-122.412154318600955, 37.79012444884593],\n            [-122.412266187164306, 37.790672783986288],\n            [-122.413915879652706, 37.790463115032836],\n            [-122.414107303557273, 37.79141647884336],\n            [-122.414293264567746, 37.792342610236801],\n            [-122.414381908270244, 37.792784070108439],\n            [-122.414470074462415, 37.79322314646798],\n            [-122.414647632176155, 37.794109807074861],\n            [-122.414825631965456, 37.794988155997579],\n            [-122.413187771447625, 37.795197816725128],\n            [-122.412335878555311, 37.795306857217497],\n            [-122.411893657029495, 37.795363458292385],\n            [-122.411541531721369, 37.795408526524874],\n            [-122.411081667103218, 37.795467382264249],\n            [-122.40989875147848, 37.795616424537307],\n            [-122.40954722057397, 37.795660707190791],\n            [-122.409343544204319, 37.795686363949223],\n            [-122.409077868767497, 37.795719830108119],\n            [-122.409075520379659, 37.795720125786943],\n            [-122.408743234497422, 37.795761981540487],\n            [-122.408704654154022, 37.795766841471867],\n            [-122.408253202051654, 37.795823706218044],\n            [-122.40779730388941, 37.795880962818103],\n            [-122.407563768850565, 37.795910291726024],\n            [-122.407432089697608, 37.795926828732469],\n            [-122.407319877605971, 37.795940921047873],\n            [-122.407083680819795, 37.795970583772274],\n            [-122.406652352369846, 37.796024750165536],\n            [-122.406350669109713, 37.796061968858353],\n            [-122.406224736410849, 37.796077505131322],\n            [-122.405862652252878, 37.796122174218517],\n            [-122.405142977212691, 37.796210954635839],\n            [-122.404957208224928, 37.795326691921687],\n            [-122.404875610619072, 37.794859533373135],\n            [-122.404796472837063, 37.794453004909116],\n            [-122.404710582177955, 37.794021380197684],\n            [-122.404613421373455, 37.793565084338127],\n            [-122.404613389070732, 37.793564931583006],\n            [-122.40441842761642, 37.79263865633952],\n            [-122.404219835191427, 37.791690583910508]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 12, ZIP_CODE: 94103, ID: 94103},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.403877699904385, 37.770062866265505],\n            [-122.403615112128733, 37.769893190404822],\n            [-122.403406518421917, 37.769807099957397],\n            [-122.403037758616776, 37.769828657390953],\n            [-122.402926809989424, 37.76864812762463],\n            [-122.401962350744, 37.768706209224241],\n            [-122.401842503677628, 37.76743077711658],\n            [-122.400877239583821, 37.76748837440357],\n            [-122.400680751363893, 37.767503407870436],\n            [-122.4004285874814, 37.76730030349627],\n            [-122.399677637243116, 37.766695444602661],\n            [-122.39980174760619, 37.766587745781216],\n            [-122.399760960198876, 37.766250252443854],\n            [-122.40075504647362, 37.766190486570189],\n            [-122.401720515816834, 37.766132520250999],\n            [-122.401604204317508, 37.764894634517518],\n            [-122.401598999326723, 37.764839236400867],\n            [-122.402563396844656, 37.764781034314389],\n            [-122.403527034519726, 37.764722870371436],\n            [-122.404496977488066, 37.764664317561468],\n            [-122.404841911264157, 37.764643492872132],\n            [-122.405089602982216, 37.764628538373131],\n            [-122.405089603673972, 37.764628538361961],\n            [-122.405463420216606, 37.764605967981389],\n            [-122.406005237363416, 37.764573252226405],\n            [-122.406429152848446, 37.764547653527842],\n            [-122.406429154577822, 37.764547653499882],\n            [-122.406859230067056, 37.764521681123377],\n            [-122.407419737953433, 37.764487829924626],\n            [-122.407534229395537, 37.765783299286987],\n            [-122.408544242939584, 37.765722599679115],\n            [-122.410486689274492, 37.765605838622655],\n            [-122.411455262151748, 37.765547605227276],\n            [-122.412416426589019, 37.765489809283601],\n            [-122.413105243007919, 37.765447608576679],\n            [-122.415308388038014, 37.765314639624691],\n            [-122.416387522888556, 37.765249494040667],\n            [-122.41748659577631, 37.765183134636899],\n            [-122.418579138459435, 37.765117159398407],\n            [-122.418698425401715, 37.765109955145007],\n            [-122.419668972681833, 37.765051337054757],\n            [-122.420482492120144, 37.765002197009743],\n            [-122.420580607922048, 37.764996270018614],\n            [-122.420901270224292, 37.764976898858535],\n            [-122.421239593012103, 37.764956459387825],\n            [-122.421381475659516, 37.764947887797014],\n            [-122.421886445840627, 37.764917378530825],\n            [-122.42285341443899, 37.764858950163855],\n            [-122.423112421134832, 37.764843298770074],\n            [-122.424102683503691, 37.764783452170008],\n            [-122.424574375550648, 37.764754942537614],\n            [-122.426381062356072, 37.764645726997848],\n            [-122.426381062758907, 37.764645729188807],\n            [-122.426381708477521, 37.764645690891633],\n            [-122.426490697526233, 37.765773605772118],\n            [-122.426518991136163, 37.766066400051031],\n            [-122.426538465047457, 37.766267922745747],\n            [-122.426572006355102, 37.766615035704866],\n            [-122.426615944414863, 37.767069730096857],\n            [-122.426693183296933, 37.76786901846797],\n            [-122.426693183304053, 37.767869018742559],\n            [-122.426902347012131, 37.769049368265463],\n            [-122.426309438735913, 37.769602597220121],\n            [-122.424852598682307, 37.770747745074537],\n            [-122.423971653706317, 37.771401668385572],\n            [-122.423707555870649, 37.771638138620823],\n            [-122.42361952356508, 37.771716961429995],\n            [-122.423479344850293, 37.771827141530693],\n            [-122.423258913662679, 37.772000399157754],\n            [-122.422619690282815, 37.772502817311128],\n            [-122.422169936976104, 37.77285593639396],\n            [-122.421941247178026, 37.773036050819961],\n            [-122.421284370393934, 37.77356039048216],\n            [-122.421008229272687, 37.773780811646674],\n            [-122.420910608715644, 37.773852419132908],\n            [-122.420699239041511, 37.774007568561714],\n            [-122.420698854806645, 37.774007872313192],\n            [-122.42069877383058, 37.774007805509001],\n            [-122.420254278900828, 37.774358674888319],\n            [-122.420254205644639, 37.774358732668077],\n            [-122.419544527888206, 37.774918988504218],\n            [-122.419256088091217, 37.775146694295351],\n            [-122.419256086565611, 37.775146688826375],\n            [-122.419255604623004, 37.775147068879761],\n            [-122.419255606501622, 37.775147074617692],\n            [-122.419255600381447, 37.775147078837719],\n            [-122.418683590110874, 37.775586568849455],\n            [-122.417757669457401, 37.776334062520824],\n            [-122.417501462863584, 37.776540893581355],\n            [-122.416757677810395, 37.777126789723972],\n            [-122.416291701725271, 37.777493842582118],\n            [-122.416291701393504, 37.777493843136881],\n            [-122.416024573316548, 37.777713893977598],\n            [-122.415925978170975, 37.777795112873314],\n            [-122.414741221622947, 37.778719429321896],\n            [-122.412512255967954, 37.78047851222005],\n            [-122.412243715889375, 37.780689421480552],\n            [-122.411972359306191, 37.780902540886757],\n            [-122.411972358960256, 37.78090254089237],\n            [-122.411625130786987, 37.781189627190479],\n            [-122.410716598656492, 37.781899054565059],\n            [-122.410292026384454, 37.782230575136197],\n            [-122.40895215871069, 37.783287852576514],\n            [-122.408597310055299, 37.783569788681966],\n            [-122.408066548174432, 37.783991486066526],\n            [-122.408066547510714, 37.783991487176031],\n            [-122.407625723544982, 37.784337071806426],\n            [-122.406821367207598, 37.78496763543707],\n            [-122.405831011063668, 37.78574398921409],\n            [-122.405371564397683, 37.786107287119592],\n            [-122.404583317818549, 37.786730565606241],\n            [-122.404583317133643, 37.786730565891986],\n            [-122.404583316787637, 37.786730565897571],\n            [-122.403925775648347, 37.787250481113581],\n            [-122.403430800369534, 37.787641848645734],\n            [-122.403342641535644, 37.787274627660381],\n            [-122.403028734856406, 37.78702485040828],\n            [-122.403028193190352, 37.787024418822838],\n            [-122.402575227394536, 37.786678208153539],\n            [-122.402025419766176, 37.786248152290575],\n            [-122.401489835489045, 37.785829214187501],\n            [-122.401480059865179, 37.785821567351917],\n            [-122.400998469557948, 37.785444857061968],\n            [-122.400994549885382, 37.785441790992316],\n            [-122.400468483886854, 37.785030285141588],\n            [-122.399369533643153, 37.785899247741973],\n            [-122.397811613142864, 37.784666586003652],\n            [-122.398930331092998, 37.783784933304034],\n            [-122.399891477967842, 37.783025880124256],\n            [-122.400019020063766, 37.782925153640136],\n            [-122.400374366843309, 37.782644515545172],\n            [-122.401159718585049, 37.782024266952142],\n            [-122.403387209275081, 37.78026497235011],\n            [-122.404431250807633, 37.779440334605447],\n            [-122.405615266425855, 37.778505104539292],\n            [-122.405068508898097, 37.778068981419388],\n            [-122.404605505853894, 37.777699659794813],\n            [-122.404070256642925, 37.777272702482797],\n            [-122.403673439982725, 37.776956165214415],\n            [-122.403505778866929, 37.776822422431877],\n            [-122.40337073826143, 37.776714700215486],\n            [-122.402524096584528, 37.776039321403807],\n            [-122.400981567656032, 37.774808775969753],\n            [-122.400592319049849, 37.774498244536282],\n            [-122.400212340771517, 37.774195105423317],\n            [-122.400212340411557, 37.77419510487973],\n            [-122.399433130909728, 37.773573455617395],\n            [-122.401656680421155, 37.7718171255028],\n            [-122.403877699904385, 37.770062866265505]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 13, ZIP_CODE: 94115, ID: 94115},\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-122.447679208068394, 37.79170289872647],\n            [-122.44720637942703, 37.791763091830468],\n            [-122.446299547924625, 37.791878529215651],\n            [-122.446299573716246, 37.791878694429421],\n            [-122.446446033268856, 37.792810866507708],\n            [-122.446446456540414, 37.792813716046261],\n            [-122.446587034424141, 37.793765298555968],\n            [-122.445035345747925, 37.79396273368863],\n            [-122.443384198876302, 37.794172877898092],\n            [-122.441712771485612, 37.794385578392699],\n            [-122.440870170501768, 37.794492795601883],\n            [-122.440868123190498, 37.794493055920753],\n            [-122.440045216960385, 37.794597761146619],\n            [-122.438402484723895, 37.794806973138577],\n            [-122.436760975027326, 37.795016021968365],\n            [-122.435113289920963, 37.795225833115033],\n            [-122.433469062960725, 37.795435179810816],\n            [-122.431827832563371, 37.79564412085616],\n            [-122.430182832162615, 37.795853517593606],\n            [-122.429989276973558, 37.794894937362344],\n            [-122.429803981887915, 37.793977244761322],\n            [-122.429625242377327, 37.793098475947545],\n            [-122.429447192175289, 37.79221689778025],\n            [-122.429269817037863, 37.791338636935983],\n            [-122.429092268257477, 37.790459499306614],\n            [-122.428905308896674, 37.789533740830485],\n            [-122.428712111452299, 37.788577067507021],\n            [-122.428525011003728, 37.78765056176524],\n            [-122.428335613076769, 37.786712655890966],\n            [-122.428241829261111, 37.786248226022003],\n            [-122.428241496400901, 37.78624657727314],\n            [-122.428148186422831, 37.785784488173462],\n            [-122.427988927379005, 37.784953879052303],\n            [-122.427778871361184, 37.783924735360486],\n            [-122.427584325166535, 37.78298604740867],\n            [-122.427490665181679, 37.782522998381459],\n            [-122.427396397680198, 37.782056939506091],\n            [-122.42720275579525, 37.781117127328834],\n            [-122.427016756808811, 37.780193030481158],\n            [-122.42868554474839, 37.779980201690215],\n            [-122.430233236584385, 37.779782794230314],\n            [-122.431951802315041, 37.779564148987781],\n            [-122.433595938040227, 37.779354585436536],\n            [-122.435241012398947, 37.779144878458894],\n            [-122.436884629197735, 37.778937753600381],\n            [-122.437707163233824, 37.778832380147421],\n            [-122.43855227772093, 37.778724107938281],\n            [-122.440213334218711, 37.778511463388014],\n            [-122.441865640580161, 37.778299553615973],\n            [-122.441865640558632, 37.778299552792262],\n            [-122.441865967212379, 37.778299511152262],\n            [-122.441865967226732, 37.778299511701405],\n            [-122.443506919728407, 37.778089213415292],\n            [-122.445155752336433, 37.777886506805444],\n            [-122.446846469920573, 37.777669108909684],\n            [-122.447039806751391, 37.778622307248391],\n            [-122.447351927163439, 37.780152061280916],\n            [-122.447517982733274, 37.781069510753063],\n            [-122.447576361567485, 37.781175778551805],\n            [-122.447655108274333, 37.781537309189886],\n            [-122.447637300982407, 37.781719177617617],\n            [-122.447450898997985, 37.782195217632704],\n            [-122.447301513844337, 37.78239164221629],\n            [-122.447283893261542, 37.782434393634787],\n            [-122.447225622636566, 37.782575775503396],\n            [-122.447173033991248, 37.782710075581413],\n            [-122.447149436147441, 37.783030677550911],\n            [-122.447549635601661, 37.784906798974511],\n            [-122.447578549915917, 37.784998355250835],\n            [-122.447620107068687, 37.785083635258417],\n            [-122.447621599012791, 37.785194684567394],\n            [-122.447605133356163, 37.785275579171099],\n            [-122.447533801639068, 37.785411415165207],\n            [-122.446849274413296, 37.786186897081585],\n            [-122.446738337622591, 37.786243163870765],\n            [-122.446610465639836, 37.786302298311156],\n            [-122.446792779960703, 37.787261853518181],\n            [-122.4469741664482, 37.788186460801001],\n            [-122.448656220796209, 37.787977164570449],\n            [-122.448835781299294, 37.788856511426609],\n            [-122.449011564525605, 37.789726786022129],\n            [-122.449189583391288, 37.790608108274284],\n            [-122.449367313308201, 37.791487980191171],\n            [-122.447679208068394, 37.79170289872647]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 14, name: '16th St. Mission (16TH)'},\n      geometry: {type: 'Point', coordinates: [-122.419694, 37.765062]}\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 15, name: '19th St. Oakland (19TH)'},\n      geometry: {type: 'Point', coordinates: [-122.269029, 37.80787]}\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 16, name: '24th St. Mission (24TH)'},\n      geometry: {type: 'Point', coordinates: [-122.418466, 37.752254]}\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 17, STREETNAME: null, CNN: null, CNNTEXT: null, LAYER: null},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-122.404496510855935, 37.747208431159258, 0.0],\n          [-122.404724947212969, 37.746505061621882, 0.0],\n          [-122.404910121756458, 37.745826557926733, 0.0],\n          [-122.40505115991364, 37.745369647146362, 0.0],\n          [-122.405179629589213, 37.744981134594937, 0.0],\n          [-122.405475696208015, 37.744427002056398, 0.0],\n          [-122.405747110770719, 37.74402859882732, 0.0],\n          [-122.406047832261379, 37.743656241938524, 0.0],\n          [-122.406881612819447, 37.742665294624537, 0.0],\n          [-122.407209492100122, 37.742235665881928, 0.0],\n          [-122.407448074495264, 37.741860522092949, 0.0],\n          [-122.407625696930154, 37.741528038899268, 0.0],\n          [-122.407757063324084, 37.741253133458535, 0.0],\n          [-122.407846654066077, 37.741024367212859, 0.0],\n          [-122.407964646258392, 37.74066255255704, 0.0],\n          [-122.408049229599797, 37.74033911017515, 0.0],\n          [-122.40811407182305, 37.739818087474802, 0.0],\n          [-122.408155426277474, 37.739489700638032, 0.0],\n          [-122.4081815622297, 37.739061925772134, 0.0]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 18, STREETNAME: null, CNN: null, CNNTEXT: null, LAYER: null},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-122.423396761198774, 37.771885644205057, 0.0],\n          [-122.423341877997842, 37.7716970422617, 0.0],\n          [-122.42310057516444, 37.771322004989457, 0.0],\n          [-122.423023389959624, 37.771214151829795, 0.0],\n          [-122.422909336817526, 37.771075536726087, 0.0],\n          [-122.422813562263059, 37.770970841731483, 0.0],\n          [-122.422687528244538, 37.770851679988603, 0.0],\n          [-122.422548319806467, 37.770735086967235, 0.0],\n          [-122.422398678994213, 37.770624666937692, 0.0],\n          [-122.422251635554701, 37.770526810613305, 0.0],\n          [-122.422118196802614, 37.770448774473365, 0.0],\n          [-122.421934187850198, 37.770355122684251, 0.0],\n          [-122.421813265154583, 37.770301864818435, 0.0],\n          [-122.421699352349904, 37.770256296204039, 0.0],\n          [-122.421444606183769, 37.77017071999213, 0.0],\n          [-122.42115970557785, 37.770096121272886, 0.0],\n          [-122.420898240339739, 37.770049954636129, 0.0],\n          [-122.420722192307608, 37.770030010784517, 0.0],\n          [-122.420585860012409, 37.770019508727906, 0.0],\n          [-122.419997377433887, 37.769997815936115, 0.0],\n          [-122.419496824292551, 37.769981089772898, 0.0],\n          [-122.419358230344216, 37.7699709673944, 0.0],\n          [-122.419180619296924, 37.769953778292248, 0.0],\n          [-122.418926623491956, 37.769918930266449, 0.0],\n          [-122.416139450807378, 37.769545473811675, 0.0]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 19, STREETNAME: null, CNN: null, CNNTEXT: null, LAYER: null},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-122.416338879874303, 37.769687518927974, 0.0],\n          [-122.418400932566996, 37.769948381534682, 0.0],\n          [-122.419176763922451, 37.770054187185572, 0.0],\n          [-122.419354003754208, 37.770073213616591, 0.0],\n          [-122.419542138973512, 37.770086427945351, 0.0],\n          [-122.419723964918447, 37.770092686385524, 0.0],\n          [-122.420100214695708, 37.770105478471756, 0.0],\n          [-122.420476463491056, 37.770118270276519, 0.0],\n          [-122.420616194034608, 37.770125614199095, 0.0],\n          [-122.420816021735661, 37.770145216687936, 0.0],\n          [-122.421007563773117, 37.770174292292772, 0.0],\n          [-122.421149554735379, 37.770202567397305, 0.0],\n          [-122.421323625825579, 37.770245387827444, 0.0],\n          [-122.42150000102616, 37.770298447039025, 0.0],\n          [-122.421757333137421, 37.770394874007152, 0.0],\n          [-122.421886515500674, 37.770452654763503, 0.0],\n          [-122.422041292168728, 37.770530023310897, 0.0],\n          [-122.422168383585031, 37.770601574230888, 0.0],\n          [-122.42227536100421, 37.770668224151905, 0.0],\n          [-122.422351617622013, 37.770720521989226, 0.0],\n          [-122.422517355438828, 37.770847486375189, 0.0],\n          [-122.422654966009191, 37.770969406462996, 0.0],\n          [-122.422814001010366, 37.771134535526315, 0.0],\n          [-122.422907046260278, 37.77124703002864, 0.0],\n          [-122.422985096055257, 37.771353508317482, 0.0],\n          [-122.423251598467544, 37.771769674548985, 0.0],\n          [-122.423396761198774, 37.771885644205057, 0.0]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 20, name: 'FRMT-DALY (ROUTE 5/6)', color: '#4db848'},\n      geometry: {\n        type: 'MultiLineString',\n        coordinates: [\n          [\n            [-122.117691, 37.690853],\n            [-122.115804, 37.689526],\n            [-122.114714, 37.688759],\n            [-122.113653, 37.688024],\n            [-122.111942, 37.686838],\n            [-122.108067, 37.684181],\n            [-122.105053, 37.682106],\n            [-122.102954, 37.68066],\n            [-122.102038, 37.680022],\n            [-122.101587, 37.679705],\n            [-122.101264, 37.679484],\n            [-122.100991, 37.679307],\n            [-122.099792, 37.678482],\n            [-122.099169, 37.678052],\n            [-122.098553, 37.677627],\n            [-122.098007, 37.677245],\n            [-122.095037, 37.675203],\n            [-122.094074, 37.674541],\n            [-122.090903, 37.672359],\n            [-122.089886, 37.671685],\n            [-122.089195, 37.671243],\n            [-122.088304, 37.670637],\n            [-122.087763, 37.670258],\n            [-122.08744, 37.670036],\n            [-122.086993, 37.669721],\n            [-122.086371, 37.669288],\n            [-122.085998, 37.669034],\n            [-122.084869, 37.668261],\n            [-122.084598, 37.668069],\n            [-122.08442, 37.667945],\n            [-122.084211, 37.667804],\n            [-122.084084, 37.667711],\n            [-122.083897, 37.667578],\n            [-122.08372, 37.667449],\n            [-122.083589, 37.66735],\n            [-122.083479, 37.667265],\n            [-122.083322, 37.66714],\n            [-122.083076, 37.666953],\n            [-122.082935, 37.66683],\n            [-122.082632, 37.666538],\n            [-122.082446, 37.666354],\n            [-122.082369, 37.666272],\n            [-122.082333, 37.666234],\n            [-122.082284, 37.666184],\n            [-122.082226, 37.666121],\n            [-122.082156, 37.666044],\n            [-122.082064, 37.665942],\n            [-122.081967, 37.665832],\n            [-122.081859, 37.665707],\n            [-122.081619, 37.665418],\n            [-122.081437, 37.66521],\n            [-122.08108, 37.664741],\n            [-122.080772, 37.66435],\n            [-122.080436, 37.663925],\n            [-122.080142, 37.663548],\n            [-122.079515, 37.66275],\n            [-122.079077, 37.662191],\n            [-122.078544, 37.661513],\n            [-122.078221, 37.661104],\n            [-122.077928, 37.660729],\n            [-122.076837, 37.659338],\n            [-122.076609, 37.659057],\n            [-122.076483, 37.658895],\n            [-122.076067, 37.658358],\n            [-122.075285, 37.657361],\n            [-122.074609, 37.656503],\n            [-122.074027, 37.655761],\n            [-122.07354, 37.65514],\n            [-122.07297, 37.654414],\n            [-122.071948, 37.653108],\n            [-122.071259, 37.652248],\n            [-122.070921, 37.651817],\n            [-122.070654, 37.651473],\n            [-122.070295, 37.651016],\n            [-122.069886, 37.650495],\n            [-122.069584, 37.650128],\n            [-122.069135, 37.649539],\n            [-122.068707, 37.648992],\n            [-122.067593, 37.647577],\n            [-122.066986, 37.646801],\n            [-122.066533, 37.646228],\n            [-122.065976, 37.645512],\n            [-122.065554, 37.644979],\n            [-122.065403, 37.644785],\n            [-122.064328, 37.643416],\n            [-122.064026, 37.643029],\n            [-122.06333, 37.642146],\n            [-122.062654, 37.641281],\n            [-122.062072, 37.640542],\n            [-122.061487, 37.639797],\n            [-122.061192, 37.639424],\n            [-122.06063, 37.638707],\n            [-122.060059, 37.637982],\n            [-122.05983, 37.637697],\n            [-122.059604, 37.637414],\n            [-122.059404, 37.637168],\n            [-122.059164, 37.636868],\n            [-122.058808, 37.636417],\n            [-122.058253, 37.635703],\n            [-122.057911, 37.635273],\n            [-122.05747, 37.63471],\n            [-122.05725, 37.634427],\n            [-122.057054, 37.634176],\n            [-122.056852, 37.633926],\n            [-122.056006, 37.632842],\n            [-122.055549, 37.63226],\n            [-122.054814, 37.631328],\n            [-122.054167, 37.630498],\n            [-122.053868, 37.63011],\n            [-122.053568, 37.629681],\n            [-122.053468, 37.629507],\n            [-122.053338, 37.629302],\n            [-122.053222, 37.629103],\n            [-122.053084, 37.628859],\n            [-122.05294, 37.628593],\n            [-122.052839, 37.628415],\n            [-122.0527, 37.62811],\n            [-122.052452, 37.627612],\n            [-122.051964, 37.626617],\n            [-122.051772, 37.626229],\n            [-122.051609, 37.625908],\n            [-122.050977, 37.624599],\n            [-122.050737, 37.624113],\n            [-122.050523, 37.623678],\n            [-122.050316, 37.623254],\n            [-122.049984, 37.622579],\n            [-122.04972, 37.622073],\n            [-122.049565, 37.621804],\n            [-122.049416, 37.621558],\n            [-122.049302, 37.621378],\n            [-122.04918, 37.621195],\n            [-122.049031, 37.620985],\n            [-122.048878, 37.620777],\n            [-122.048723, 37.620579],\n            [-122.048556, 37.620373],\n            [-122.04842, 37.620214],\n            [-122.048146, 37.619908],\n            [-122.047999, 37.619757],\n            [-122.04792, 37.619676],\n            [-122.047835, 37.619592],\n            [-122.047736, 37.619493],\n            [-122.047497, 37.619263],\n            [-122.047325, 37.619105],\n            [-122.047063, 37.618871],\n            [-122.046236, 37.618155],\n            [-122.045398, 37.617408],\n            [-122.044619, 37.616726],\n            [-122.043788, 37.615997],\n            [-122.037595, 37.610563],\n            [-122.037278, 37.610274],\n            [-122.036229, 37.60925],\n            [-122.036117, 37.609133],\n            [-122.034705, 37.607718],\n            [-122.033443, 37.606397],\n            [-122.03295, 37.605879],\n            [-122.030835, 37.603662],\n            [-122.029644, 37.602411],\n            [-122.029135, 37.601873],\n            [-122.028693, 37.601412],\n            [-122.02835, 37.601054],\n            [-122.028222, 37.600917],\n            [-122.028091, 37.600781],\n            [-122.027998, 37.600684],\n            [-122.02774, 37.600412],\n            [-122.027534, 37.600196],\n            [-122.02729, 37.599942],\n            [-122.027101, 37.599745],\n            [-122.026868, 37.599512],\n            [-122.026728, 37.599375],\n            [-122.026611, 37.599264],\n            [-122.026452, 37.599123],\n            [-122.026324, 37.599004],\n            [-122.026214, 37.598906],\n            [-122.025593, 37.598354],\n            [-122.025281, 37.598081],\n            [-122.025, 37.597834],\n            [-122.024688, 37.597564],\n            [-122.024296, 37.59722],\n            [-122.023757, 37.596736],\n            [-122.023642, 37.596631],\n            [-122.023542, 37.596538],\n            [-122.023369, 37.596378],\n            [-122.023206, 37.596218],\n            [-122.023154, 37.596168],\n            [-122.023115, 37.59613],\n            [-122.023079, 37.596097],\n            [-122.023029, 37.596045],\n            [-122.022965, 37.595978],\n            [-122.02275, 37.595756],\n            [-122.022531, 37.595514],\n            [-122.022291, 37.595242],\n            [-122.022234, 37.595174],\n            [-122.022161, 37.595086],\n            [-122.022103, 37.595019],\n            [-122.022034, 37.594939],\n            [-122.021966, 37.594859],\n            [-122.021915, 37.594795],\n            [-122.021817, 37.594675],\n            [-122.021658, 37.594495],\n            [-122.021426, 37.594212],\n            [-122.021219, 37.593972],\n            [-122.021065, 37.593801],\n            [-122.021007, 37.593736],\n            [-122.020967, 37.593694],\n            [-122.020929, 37.593655],\n            [-122.020872, 37.593597],\n            [-122.020802, 37.593526],\n            [-122.020647, 37.593377],\n            [-122.020549, 37.593284],\n            [-122.020461, 37.593199],\n            [-122.020412, 37.593156],\n            [-122.020354, 37.593104],\n            [-122.020183, 37.592954],\n            [-122.020048, 37.592838],\n            [-122.019885, 37.592705],\n            [-122.019793, 37.592635],\n            [-122.01972, 37.592576],\n            [-122.01965, 37.592525],\n            [-122.019557, 37.592457],\n            [-122.019385, 37.592326],\n            [-122.019085, 37.592102],\n            [-122.018765, 37.591865],\n            [-122.018204, 37.591448],\n            [-122.017895, 37.591219],\n            [-122.0171, 37.590622],\n            [-122.016692, 37.59032],\n            [-122.016371, 37.590089],\n            [-122.015817, 37.589669],\n            [-122.015497, 37.589431],\n            [-122.01515, 37.589169],\n            [-122.014159, 37.588415],\n            [-122.013847, 37.588182],\n            [-122.011853, 37.586658],\n            [-122.011481, 37.586373],\n            [-122.011229, 37.586182],\n            [-122.01049, 37.585619],\n            [-122.010333, 37.585498],\n            [-122.010143, 37.585354],\n            [-122.009574, 37.584919],\n            [-122.00888, 37.584388],\n            [-122.008313, 37.583958],\n            [-122.007269, 37.583164],\n            [-122.006446, 37.58254],\n            [-122.005567, 37.58186],\n            [-122.004923, 37.581365],\n            [-122.004537, 37.581069],\n            [-122.003136, 37.580005],\n            [-122.002242, 37.579319],\n            [-122.001715, 37.578913],\n            [-122.001253, 37.578556],\n            [-122.000564, 37.578025],\n            [-121.999847, 37.577472],\n            [-121.997932, 37.576006],\n            [-121.995922, 37.574459],\n            [-121.995619, 37.574225],\n            [-121.994714, 37.573527],\n            [-121.99397, 37.572962],\n            [-121.993694, 37.572743],\n            [-121.993273, 37.57242],\n            [-121.991502, 37.571059],\n            [-121.990996, 37.57067],\n            [-121.990659, 37.570401],\n            [-121.990182, 37.569997],\n            [-121.989955, 37.569795],\n            [-121.989463, 37.569339],\n            [-121.98896, 37.568829],\n            [-121.988807, 37.568679],\n            [-121.988626, 37.568481],\n            [-121.988448, 37.568282],\n            [-121.988089, 37.567844],\n            [-121.987733, 37.567417],\n            [-121.986703, 37.566157],\n            [-121.986537, 37.56595],\n            [-121.986376, 37.565751],\n            [-121.986181, 37.565514],\n            [-121.985957, 37.56525],\n            [-121.985776, 37.565041],\n            [-121.985623, 37.564863],\n            [-121.985499, 37.56472],\n            [-121.985318, 37.56452],\n            [-121.985027, 37.564218],\n            [-121.984899, 37.564089],\n            [-121.984774, 37.563963],\n            [-121.984676, 37.563865],\n            [-121.984603, 37.563796],\n            [-121.984412, 37.563618],\n            [-121.984246, 37.563464],\n            [-121.984127, 37.563356],\n            [-121.983855, 37.563112],\n            [-121.983641, 37.562932],\n            [-121.983256, 37.562582],\n            [-121.982572, 37.561982],\n            [-121.982033, 37.561511],\n            [-121.981559, 37.561092],\n            [-121.981106, 37.560701],\n            [-121.980652, 37.560324],\n            [-121.980387, 37.560105],\n            [-121.980102, 37.559882],\n            [-121.979814, 37.559666],\n            [-121.979434, 37.559423],\n            [-121.978995, 37.559116],\n            [-121.978639, 37.558864],\n            [-121.978169, 37.558551],\n            [-121.977623, 37.55817],\n            [-121.977417, 37.558024],\n            [-121.977121, 37.557831],\n            [-121.976865, 37.557655],\n            [-121.976568, 37.557436],\n            [-121.976395, 37.557353]\n          ],\n          [\n            [-122.270713, 37.799861],\n            [-122.270179, 37.799654],\n            [-122.269529, 37.799396],\n            [-122.268525, 37.798992],\n            [-122.267377, 37.798517],\n            [-122.267183, 37.798395],\n            [-122.26691, 37.79824],\n            [-122.266683, 37.798083],\n            [-122.266323, 37.797875],\n            [-122.265589, 37.797472],\n            [-122.265182, 37.797248],\n            [-122.264574, 37.796939],\n            [-122.264202, 37.79674],\n            [-122.26366, 37.796465],\n            [-122.262696, 37.795886],\n            [-122.261985, 37.79539],\n            [-122.261091, 37.794706],\n            [-122.25951, 37.793439],\n            [-122.25895, 37.793028],\n            [-122.258557, 37.792739],\n            [-122.258382, 37.79261],\n            [-122.258303, 37.792553],\n            [-122.258165, 37.792459],\n            [-122.258031, 37.79237],\n            [-122.257893, 37.792285],\n            [-122.257676, 37.792154],\n            [-122.257415, 37.791995],\n            [-122.257306, 37.791933],\n            [-122.257196, 37.791872],\n            [-122.256969, 37.791744],\n            [-122.256716, 37.791614],\n            [-122.256498, 37.791511],\n            [-122.256259, 37.7914],\n            [-122.25612, 37.791336],\n            [-122.256014, 37.791289],\n            [-122.255908, 37.791246],\n            [-122.255851, 37.791221],\n            [-122.255652, 37.791139],\n            [-122.255356, 37.791024],\n            [-122.255004, 37.790899],\n            [-122.254959, 37.790879],\n            [-122.254782, 37.79082],\n            [-122.254661, 37.790779],\n            [-122.254394, 37.790691],\n            [-122.254234, 37.790639],\n            [-122.25379, 37.790503],\n            [-122.253541, 37.790425],\n            [-122.25316, 37.79031],\n            [-122.252814, 37.790202],\n            [-122.252551, 37.790121],\n            [-122.252439, 37.790085],\n            [-122.252299, 37.790038],\n            [-122.252153, 37.789989],\n            [-122.251745, 37.789843],\n            [-122.251551, 37.789773],\n            [-122.250857, 37.789496],\n            [-122.24971, 37.788996],\n            [-122.248543, 37.788495],\n            [-122.246809, 37.787751],\n            [-122.245552, 37.787202],\n            [-122.244862, 37.786885],\n            [-122.244353, 37.786636],\n            [-122.243912, 37.786423],\n            [-122.243421, 37.786186],\n            [-122.243086, 37.786023],\n            [-122.24279, 37.785882],\n            [-122.242285, 37.785636],\n            [-122.241987, 37.785491],\n            [-122.241666, 37.785326],\n            [-122.241364, 37.785177],\n            [-122.241185, 37.785089],\n            [-122.241043, 37.785018],\n            [-122.240807, 37.7849],\n            [-122.240536, 37.78476],\n            [-122.240049, 37.784497],\n            [-122.239549, 37.784216],\n            [-122.23922, 37.784016],\n            [-122.238971, 37.783868],\n            [-122.238817, 37.783777],\n            [-122.238699, 37.783707],\n            [-122.238569, 37.783631],\n            [-122.238397, 37.783525],\n            [-122.238274, 37.783455],\n            [-122.238181, 37.7834],\n            [-122.238025, 37.783314],\n            [-122.237927, 37.783256],\n            [-122.237796, 37.783181],\n            [-122.237656, 37.783095],\n            [-122.237312, 37.782888],\n            [-122.237159, 37.782797],\n            [-122.236743, 37.782548],\n            [-122.236563, 37.78244],\n            [-122.236442, 37.782368],\n            [-122.236194, 37.782221],\n            [-122.236047, 37.782134],\n            [-122.235734, 37.781944],\n            [-122.235587, 37.781855],\n            [-122.235446, 37.781769],\n            [-122.235044, 37.781514],\n            [-122.234858, 37.781391],\n            [-122.234693, 37.781277],\n            [-122.234605, 37.781216],\n            [-122.234475, 37.781124],\n            [-122.234323, 37.781014],\n            [-122.234195, 37.780916],\n            [-122.233983, 37.78075],\n            [-122.2332, 37.780127],\n            [-122.232935, 37.779904],\n            [-122.232743, 37.77975],\n            [-122.232408, 37.779492],\n            [-122.232055, 37.779233],\n            [-122.231803, 37.779057],\n            [-122.231595, 37.778921],\n            [-122.231321, 37.778746],\n            [-122.231123, 37.778624],\n            [-122.230862, 37.778473],\n            [-122.230571, 37.778311],\n            [-122.230383, 37.778209],\n            [-122.23023, 37.778127],\n            [-122.230001, 37.778007],\n            [-122.229772, 37.777891],\n            [-122.229105, 37.777549],\n            [-122.228728, 37.777359],\n            [-122.228307, 37.77714],\n            [-122.22813, 37.777051],\n            [-122.227894, 37.776929],\n            [-122.227597, 37.77678],\n            [-122.22656, 37.776269],\n            [-122.226374, 37.776174],\n            [-122.226171, 37.776069],\n            [-122.226094, 37.776027],\n            [-122.225862, 37.775897],\n            [-122.225558, 37.775724],\n            [-122.225047, 37.775422],\n            [-122.224572, 37.775141],\n            [-122.22382, 37.774706],\n            [-122.223652, 37.77461],\n            [-122.223298, 37.774402],\n            [-122.222984, 37.774223],\n            [-122.222818, 37.774124],\n            [-122.222585, 37.773986],\n            [-122.222329, 37.773839],\n            [-122.222026, 37.773662],\n            [-122.22163, 37.773441],\n            [-122.221151, 37.773194],\n            [-122.220924, 37.773077],\n            [-122.220649, 37.772945],\n            [-122.220236, 37.772752],\n            [-122.219737, 37.772515],\n            [-122.219285, 37.7723],\n            [-122.218757, 37.772057],\n            [-122.218323, 37.771832],\n            [-122.218171, 37.771751],\n            [-122.218089, 37.771708],\n            [-122.217913, 37.771608],\n            [-122.217764, 37.771525],\n            [-122.217523, 37.771389],\n            [-122.217314, 37.77126],\n            [-122.217092, 37.771119],\n            [-122.216791, 37.770914],\n            [-122.216585, 37.770767],\n            [-122.216384, 37.770616],\n            [-122.216272, 37.770528],\n            [-122.216187, 37.77046],\n            [-122.2161, 37.770392],\n            [-122.215956, 37.770275],\n            [-122.215834, 37.770169],\n            [-122.215617, 37.769975],\n            [-122.215391, 37.769768],\n            [-122.215127, 37.76951],\n            [-122.214725, 37.769102],\n            [-122.213975, 37.768328],\n            [-122.213715, 37.768059],\n            [-122.213622, 37.767966],\n            [-122.213373, 37.767709],\n            [-122.213171, 37.767508],\n            [-122.213032, 37.767372],\n            [-122.212766, 37.767121],\n            [-122.21262, 37.766983],\n            [-122.212323, 37.766721],\n            [-122.211957, 37.766401],\n            [-122.211802, 37.766276],\n            [-122.211492, 37.766011],\n            [-122.211363, 37.765908],\n            [-122.211146, 37.765721],\n            [-122.210737, 37.765378],\n            [-122.210451, 37.765136],\n            [-122.210161, 37.764895],\n            [-122.209748, 37.76455],\n            [-122.209532, 37.764369],\n            [-122.209231, 37.764115],\n            [-122.209064, 37.76398],\n            [-122.208445, 37.763457],\n            [-122.206766, 37.762054],\n            [-122.205483, 37.760979],\n            [-122.205025, 37.760595],\n            [-122.203892, 37.759651],\n            [-122.20063, 37.756912],\n            [-122.199879, 37.756274],\n            [-122.198766, 37.755271],\n            [-122.198327, 37.754895],\n            [-122.196884, 37.753678],\n            [-122.196409, 37.753284],\n            [-122.195922, 37.752873],\n            [-122.19573, 37.752712],\n            [-122.195487, 37.75251],\n            [-122.19528, 37.752335],\n            [-122.194969, 37.752074],\n            [-122.194684, 37.751843],\n            [-122.194409, 37.751629],\n            [-122.193529, 37.750938],\n            [-122.192961, 37.750466],\n            [-122.191093, 37.748907],\n            [-122.190632, 37.748524],\n            [-122.188553, 37.746778],\n            [-122.186353, 37.744931],\n            [-122.185911, 37.744555],\n            [-122.184359, 37.743261],\n            [-122.180251, 37.73981],\n            [-122.179614, 37.739272],\n            [-122.17726, 37.737349],\n            [-122.174961, 37.735423],\n            [-122.174445, 37.734989],\n            [-122.167116, 37.728838],\n            [-122.165421, 37.727412],\n            [-122.164801, 37.726886],\n            [-122.164287, 37.72643],\n            [-122.164047, 37.726194],\n            [-122.16369, 37.725831],\n            [-122.163281, 37.725371],\n            [-122.16291, 37.724904],\n            [-122.162513, 37.724379],\n            [-122.162009, 37.723643],\n            [-122.161361, 37.722695],\n            [-122.159669, 37.720244],\n            [-122.15952, 37.72003],\n            [-122.159398, 37.719865],\n            [-122.159334, 37.71978],\n            [-122.159273, 37.719699],\n            [-122.15918, 37.719588],\n            [-122.159048, 37.719437],\n            [-122.15897, 37.719351],\n            [-122.158889, 37.719271],\n            [-122.158804, 37.719185],\n            [-122.158779, 37.71916],\n            [-122.158714, 37.719101],\n            [-122.158639, 37.719034],\n            [-122.158541, 37.718949],\n            [-122.158298, 37.718757],\n            [-122.158132, 37.718635],\n            [-122.157878, 37.718452],\n            [-122.157566, 37.718237],\n            [-122.156866, 37.717754],\n            [-122.155854, 37.717057],\n            [-122.154863, 37.716379],\n            [-122.154301, 37.715991],\n            [-122.154006, 37.715787],\n            [-122.15375, 37.715612],\n            [-122.153029, 37.715119],\n            [-122.148444, 37.711966],\n            [-122.147591, 37.711379],\n            [-122.146414, 37.710571],\n            [-122.144545, 37.709282],\n            [-122.142296, 37.707738],\n            [-122.138726, 37.70528],\n            [-122.137211, 37.704236],\n            [-122.133704, 37.701825],\n            [-122.130722, 37.699774],\n            [-122.130305, 37.699488],\n            [-122.130015, 37.699288],\n            [-122.12982, 37.699153],\n            [-122.128081, 37.697997],\n            [-122.124181, 37.695383],\n            [-122.124181, 37.695383],\n            [-122.121564, 37.693512],\n            [-122.119058, 37.691789],\n            [-122.117691, 37.690853]\n          ],\n          [\n            [-122.270713, 37.799861],\n            [-122.270928, 37.799942],\n            [-122.271097, 37.799987],\n            [-122.271322, 37.800023],\n            [-122.272291, 37.799938],\n            [-122.27243, 37.799896],\n            [-122.272781, 37.799796],\n            [-122.2732, 37.79959],\n            [-122.273728, 37.799315],\n            [-122.274691, 37.79884],\n            [-122.274868, 37.798753],\n            [-122.276054, 37.798516],\n            [-122.276236, 37.798476],\n            [-122.276856, 37.798527]\n          ],\n          [\n            [-122.469106, 37.706105],\n            [-122.468903, 37.706333],\n            [-122.468809, 37.706437],\n            [-122.468678, 37.706588],\n            [-122.468434, 37.706903],\n            [-122.468029, 37.707393],\n            [-122.467923, 37.707522],\n            [-122.467832, 37.707627],\n            [-122.466801, 37.708779],\n            [-122.466732, 37.708861],\n            [-122.46667, 37.708939],\n            [-122.4666, 37.709019],\n            [-122.466526, 37.709095],\n            [-122.466449, 37.709166],\n            [-122.466376, 37.709237],\n            [-122.466284, 37.709309],\n            [-122.466215, 37.709364],\n            [-122.466126, 37.709428],\n            [-122.466028, 37.709493],\n            [-122.465941, 37.709544],\n            [-122.465846, 37.709608],\n            [-122.465752, 37.709656],\n            [-122.465664, 37.709709],\n            [-122.465569, 37.709755],\n            [-122.465466, 37.709801],\n            [-122.465351, 37.709849],\n            [-122.465245, 37.70989],\n            [-122.465148, 37.709922],\n            [-122.465035, 37.709958],\n            [-122.464925, 37.709987],\n            [-122.46481, 37.710013],\n            [-122.464685, 37.710036],\n            [-122.464576, 37.71006],\n            [-122.464464, 37.710078],\n            [-122.464349, 37.710097],\n            [-122.464248, 37.710106],\n            [-122.464125, 37.710117],\n            [-122.464, 37.710122],\n            [-122.463871, 37.710128],\n            [-122.463753, 37.710132],\n            [-122.463654, 37.710127],\n            [-122.462673, 37.710117],\n            [-122.461932, 37.710109],\n            [-122.461456, 37.710106],\n            [-122.461205, 37.710107],\n            [-122.460612, 37.710103],\n            [-122.460513, 37.710103],\n            [-122.460089, 37.7101],\n            [-122.459798, 37.710097],\n            [-122.459371, 37.710095],\n            [-122.458818, 37.710096],\n            [-122.458429, 37.710097],\n            [-122.458239, 37.710103],\n            [-122.458046, 37.710114],\n            [-122.457854, 37.710123],\n            [-122.457689, 37.710132],\n            [-122.457494, 37.710148],\n            [-122.457301, 37.710168],\n            [-122.45712, 37.710193],\n            [-122.456943, 37.71022],\n            [-122.456714, 37.710261],\n            [-122.45647, 37.710314],\n            [-122.456253, 37.710367],\n            [-122.456029, 37.710421],\n            [-122.455812, 37.710483],\n            [-122.455588, 37.710559],\n            [-122.455342, 37.710654],\n            [-122.455158, 37.710734],\n            [-122.454985, 37.71082],\n            [-122.454815, 37.710906],\n            [-122.454757, 37.710939],\n            [-122.454638, 37.711002],\n            [-122.454466, 37.711098],\n            [-122.454306, 37.711201],\n            [-122.454132, 37.711315],\n            [-122.453978, 37.711421],\n            [-122.453808, 37.711542],\n            [-122.453514, 37.711768],\n            [-122.453207, 37.712002],\n            [-122.452952, 37.712213],\n            [-122.452635, 37.712477],\n            [-122.45215, 37.712868],\n            [-122.451705, 37.713224],\n            [-122.451441, 37.713439],\n            [-122.451107, 37.713707],\n            [-122.450884, 37.713896],\n            [-122.450715, 37.714043],\n            [-122.450556, 37.714184],\n            [-122.450426, 37.714317],\n            [-122.450264, 37.714469],\n            [-122.450109, 37.714616],\n            [-122.449961, 37.714764],\n            [-122.449842, 37.714875],\n            [-122.449687, 37.715039],\n            [-122.449586, 37.715155],\n            [-122.449493, 37.715256],\n            [-122.449409, 37.715351],\n            [-122.449317, 37.71545],\n            [-122.449226, 37.715551],\n            [-122.449141, 37.715654],\n            [-122.449058, 37.71576],\n            [-122.448989, 37.715852],\n            [-122.448913, 37.715962],\n            [-122.448844, 37.716072],\n            [-122.448783, 37.716173],\n            [-122.448722, 37.716283],\n            [-122.448661, 37.716398],\n            [-122.448609, 37.716502],\n            [-122.448567, 37.716602],\n            [-122.448514, 37.716725],\n            [-122.448431, 37.716914],\n            [-122.448349, 37.717127],\n            [-122.448303, 37.717282],\n            [-122.448233, 37.71749],\n            [-122.448165, 37.717655],\n            [-122.448122, 37.717956],\n            [-122.448092, 37.718172],\n            [-122.448092, 37.718314],\n            [-122.448061, 37.718552],\n            [-122.448055, 37.718621],\n            [-122.448051, 37.718656],\n            [-122.448034, 37.718801],\n            [-122.448005, 37.719277],\n            [-122.447846, 37.720179],\n            [-122.447631, 37.721123],\n            [-122.447624, 37.721147],\n            [-122.447593, 37.721264],\n            [-122.447372, 37.722221],\n            [-122.447196, 37.723],\n            [-122.446967, 37.723855],\n            [-122.446671, 37.724539],\n            [-122.446363, 37.725104],\n            [-122.446308, 37.725188],\n            [-122.446245, 37.72529],\n            [-122.446186, 37.725395],\n            [-122.44613, 37.725479],\n            [-122.446059, 37.725571],\n            [-122.445995, 37.725663],\n            [-122.44593, 37.725755],\n            [-122.445865, 37.725839],\n            [-122.445526, 37.726234],\n            [-122.445164, 37.726612],\n            [-122.445034, 37.726729],\n            [-122.444943, 37.726806],\n            [-122.444845, 37.726909],\n            [-122.444723, 37.727013],\n            [-122.44461, 37.727109],\n            [-122.444507, 37.727194],\n            [-122.444392, 37.727285],\n            [-122.444267, 37.727381],\n            [-122.443613, 37.727828],\n            [-122.443394, 37.727959],\n            [-122.44319, 37.728081],\n            [-122.442962, 37.728209],\n            [-122.442869, 37.728256],\n            [-122.442575, 37.728409],\n            [-122.442264, 37.72856],\n            [-122.441807, 37.728764],\n            [-122.441408, 37.728925],\n            [-122.440789, 37.729147],\n            [-122.43985, 37.729516],\n            [-122.439205, 37.72978],\n            [-122.43892, 37.72988],\n            [-122.438031, 37.730328],\n            [-122.437495, 37.730633],\n            [-122.437173, 37.730829],\n            [-122.436901, 37.731003],\n            [-122.43666, 37.731183],\n            [-122.436385, 37.731366],\n            [-122.436179, 37.731508],\n            [-122.43605, 37.731595],\n            [-122.435966, 37.731656],\n            [-122.435706, 37.731839],\n            [-122.435447, 37.732004],\n            [-122.435241, 37.732136],\n            [-122.435117, 37.732222],\n            [-122.435026, 37.732295],\n            [-122.434642, 37.732542],\n            [-122.434329, 37.732752],\n            [-122.433801, 37.733121],\n            [-122.433405, 37.733368],\n            [-122.432847, 37.733721],\n            [-122.432582, 37.733918],\n            [-122.431811, 37.734415],\n            [-122.431274, 37.734758],\n            [-122.430172, 37.735479],\n            [-122.428873, 37.736323],\n            [-122.427615, 37.737145],\n            [-122.426334, 37.737984],\n            [-122.425826, 37.738339],\n            [-122.425538, 37.738547],\n            [-122.425326, 37.738713],\n            [-122.424999, 37.73895],\n            [-122.424828, 37.739072],\n            [-122.424649, 37.739215],\n            [-122.424537, 37.739296],\n            [-122.424351, 37.739428],\n            [-122.42411, 37.739622],\n            [-122.424011, 37.739738],\n            [-122.423827, 37.739869],\n            [-122.423179, 37.740563],\n            [-122.422476, 37.741588],\n            [-122.422061, 37.742264],\n            [-122.420958, 37.743961],\n            [-122.419834, 37.745675],\n            [-122.419392, 37.746361],\n            [-122.418916, 37.747089],\n            [-122.418687, 37.747518],\n            [-122.41859, 37.747722],\n            [-122.418544, 37.747813],\n            [-122.41849, 37.747912],\n            [-122.418397, 37.748166],\n            [-122.41836, 37.748248],\n            [-122.418323, 37.748339],\n            [-122.41829, 37.748442],\n            [-122.418259, 37.748552],\n            [-122.418218, 37.748731],\n            [-122.418186, 37.748868],\n            [-122.41816, 37.749093],\n            [-122.418301, 37.75055],\n            [-122.41844, 37.752086],\n            [-122.418615, 37.753786],\n            [-122.418742, 37.755411],\n            [-122.4189, 37.756821],\n            [-122.418962, 37.757461],\n            [-122.419053, 37.758554],\n            [-122.419231, 37.76021],\n            [-122.419372, 37.761849],\n            [-122.419543, 37.763429],\n            [-122.41967, 37.764723],\n            [-122.419692, 37.765024],\n            [-122.419854, 37.766637],\n            [-122.419892, 37.767184],\n            [-122.419996, 37.768204],\n            [-122.420043, 37.768923],\n            [-122.420062, 37.769113],\n            [-122.420119, 37.769442],\n            [-122.420128, 37.769528],\n            [-122.42014, 37.769621],\n            [-122.420147, 37.76977],\n            [-122.420152, 37.769886],\n            [-122.420176, 37.770065],\n            [-122.420215, 37.770362],\n            [-122.420327, 37.771558],\n            [-122.420316, 37.771965],\n            [-122.420322, 37.772277],\n            [-122.420317, 37.772531],\n            [-122.420281, 37.772812],\n            [-122.420153, 37.773345],\n            [-122.420104, 37.773593],\n            [-122.420038, 37.773762],\n            [-122.419906, 37.774022],\n            [-122.419471, 37.774777],\n            [-122.419429, 37.774913],\n            [-122.419406, 37.774983],\n            [-122.419377, 37.775045],\n            [-122.419321, 37.775113],\n            [-122.419239, 37.775191],\n            [-122.418701, 37.775596],\n            [-122.417492, 37.776574],\n            [-122.41652, 37.777348],\n            [-122.416228, 37.77756],\n            [-122.414798, 37.7787],\n            [-122.412528, 37.780489],\n            [-122.41145, 37.781351],\n            [-122.410319, 37.782234],\n            [-122.408084, 37.784002],\n            [-122.405876, 37.785769],\n            [-122.404718, 37.786653],\n            [-122.404003, 37.787215],\n            [-122.403499, 37.787631],\n            [-122.402875, 37.788109],\n            [-122.402102, 37.788723],\n            [-122.401386, 37.789267],\n            [-122.399138, 37.791042],\n            [-122.398258, 37.791728],\n            [-122.397415, 37.792407],\n            [-122.396506, 37.793169],\n            [-122.395321, 37.794057],\n            [-122.394828, 37.794362],\n            [-122.394659, 37.794469],\n            [-122.394508, 37.79455],\n            [-122.394366, 37.794631],\n            [-122.394072, 37.794782],\n            [-122.393814, 37.794904],\n            [-122.393498, 37.79506],\n            [-122.393151, 37.795219],\n            [-122.392537, 37.795517],\n            [-122.391751, 37.795841],\n            [-122.391448, 37.795976],\n            [-122.391302, 37.796041],\n            [-122.390733, 37.796294],\n            [-122.38985, 37.796687],\n            [-122.389027, 37.797053],\n            [-122.388439, 37.797315],\n            [-122.387975, 37.797522],\n            [-122.387476, 37.797743],\n            [-122.386946, 37.797979],\n            [-122.386305, 37.798265],\n            [-122.385701, 37.798534],\n            [-122.385193, 37.79876],\n            [-122.384824, 37.798924],\n            [-122.38453, 37.799055],\n            [-122.384152, 37.799223],\n            [-122.383849, 37.799358],\n            [-122.383682, 37.799432],\n            [-122.383442, 37.799539],\n            [-122.383164, 37.799663],\n            [-122.382839, 37.799808],\n            [-122.382558, 37.799933],\n            [-122.382295, 37.80005],\n            [-122.381886, 37.800232],\n            [-122.381553, 37.80038],\n            [-122.381386, 37.800454],\n            [-122.381017, 37.800618],\n            [-122.380655, 37.800779],\n            [-122.380293, 37.800941],\n            [-122.379997, 37.801072],\n            [-122.379566, 37.801264],\n            [-122.379333, 37.801367],\n            [-122.379061, 37.801484],\n            [-122.378788, 37.801599],\n            [-122.378485, 37.801723],\n            [-122.378206, 37.801835],\n            [-122.377961, 37.801929],\n            [-122.377783, 37.801997],\n            [-122.377556, 37.802081],\n            [-122.377278, 37.802182],\n            [-122.377102, 37.802244],\n            [-122.376862, 37.802327],\n            [-122.376663, 37.802394],\n            [-122.37642, 37.802474],\n            [-122.376049, 37.802592],\n            [-122.375907, 37.802636],\n            [-122.375677, 37.802705],\n            [-122.375582, 37.802734],\n            [-122.374975, 37.802906],\n            [-122.374499, 37.803034],\n            [-122.374181, 37.803113],\n            [-122.373835, 37.803197],\n            [-122.373325, 37.803313],\n            [-122.37284, 37.803416],\n            [-122.372496, 37.803485],\n            [-122.37218, 37.803544],\n            [-122.372018, 37.803573],\n            [-122.371873, 37.803599],\n            [-122.371594, 37.803647],\n            [-122.37109, 37.803733],\n            [-122.370628, 37.803811],\n            [-122.370395, 37.80385],\n            [-122.370216, 37.803881],\n            [-122.370018, 37.803914],\n            [-122.369691, 37.803969],\n            [-122.369393, 37.80402],\n            [-122.369234, 37.804047],\n            [-122.368983, 37.804089],\n            [-122.36873, 37.804132],\n            [-122.368345, 37.804197],\n            [-122.367904, 37.804272],\n            [-122.367638, 37.804317],\n            [-122.367375, 37.804361],\n            [-122.367188, 37.804393],\n            [-122.366998, 37.804425],\n            [-122.366678, 37.804479],\n            [-122.366367, 37.804532],\n            [-122.366027, 37.804589],\n            [-122.365735, 37.804639],\n            [-122.365328, 37.804708],\n            [-122.365107, 37.804745],\n            [-122.364819, 37.804794],\n            [-122.364621, 37.804827],\n            [-122.364322, 37.804878],\n            [-122.364022, 37.804929],\n            [-122.363705, 37.804982],\n            [-122.363326, 37.805046],\n            [-122.363004, 37.805101],\n            [-122.362666, 37.805158],\n            [-122.362384, 37.805206],\n            [-122.362149, 37.805246],\n            [-122.361906, 37.805287],\n            [-122.361806, 37.805303],\n            [-122.361615, 37.805336],\n            [-122.361334, 37.805383],\n            [-122.361157, 37.805413],\n            [-122.360916, 37.805454],\n            [-122.360766, 37.805479],\n            [-122.360571, 37.805512],\n            [-122.360333, 37.805553],\n            [-122.360139, 37.805586],\n            [-122.359966, 37.805615],\n            [-122.359805, 37.805642],\n            [-122.359603, 37.805676],\n            [-122.359438, 37.805704],\n            [-122.359239, 37.805738],\n            [-122.359127, 37.805757],\n            [-122.358849, 37.805804],\n            [-122.358639, 37.805839],\n            [-122.358458, 37.80587],\n            [-122.35819, 37.805915],\n            [-122.358054, 37.805938],\n            [-122.357891, 37.805966],\n            [-122.357755, 37.805989],\n            [-122.357578, 37.806019],\n            [-122.357468, 37.806037],\n            [-122.357171, 37.806087],\n            [-122.356963, 37.806122],\n            [-122.356845, 37.806143],\n            [-122.35676, 37.806157],\n            [-122.356589, 37.806186],\n            [-122.356403, 37.806217],\n            [-122.356282, 37.806238],\n            [-122.356198, 37.806252],\n            [-122.356066, 37.806274],\n            [-122.355882, 37.806305],\n            [-122.355686, 37.806339],\n            [-122.355403, 37.806386],\n            [-122.355272, 37.806408],\n            [-122.355008, 37.806453],\n            [-122.354917, 37.806468],\n            [-122.35485, 37.80648],\n            [-122.35476, 37.806495],\n            [-122.354695, 37.806506],\n            [-122.354525, 37.806535],\n            [-122.354409, 37.806554],\n            [-122.354325, 37.806569],\n            [-122.354227, 37.806585],\n            [-122.354086, 37.806609],\n            [-122.354017, 37.806621],\n            [-122.353947, 37.806633],\n            [-122.353904, 37.80664],\n            [-122.353765, 37.806663],\n            [-122.353575, 37.806695],\n            [-122.353343, 37.806735],\n            [-122.353173, 37.806763],\n            [-122.35303, 37.806788],\n            [-122.352832, 37.806821],\n            [-122.352675, 37.806848],\n            [-122.352471, 37.806882],\n            [-122.35208, 37.806948],\n            [-122.351678, 37.807016],\n            [-122.351343, 37.807073],\n            [-122.351125, 37.80711],\n            [-122.350989, 37.807133],\n            [-122.350903, 37.807147],\n            [-122.350826, 37.80716],\n            [-122.350549, 37.807207],\n            [-122.350215, 37.807264],\n            [-122.349855, 37.807325],\n            [-122.349491, 37.807386],\n            [-122.349227, 37.807431],\n            [-122.348673, 37.807524],\n            [-122.34762, 37.807702],\n            [-122.346954, 37.807817],\n            [-122.346269, 37.807931],\n            [-122.345982, 37.807981],\n            [-122.345735, 37.808021],\n            [-122.34529, 37.808096],\n            [-122.344754, 37.808186],\n            [-122.342008, 37.808651],\n            [-122.341598, 37.80872],\n            [-122.341341, 37.808763],\n            [-122.340242, 37.808948],\n            [-122.339687, 37.809041],\n            [-122.339143, 37.809133],\n            [-122.338773, 37.809194],\n            [-122.338187, 37.809285],\n            [-122.337648, 37.809363],\n            [-122.337162, 37.809429],\n            [-122.336553, 37.809505],\n            [-122.336131, 37.809552],\n            [-122.3358, 37.809588],\n            [-122.335224, 37.809645],\n            [-122.334668, 37.809694],\n            [-122.334142, 37.809735],\n            [-122.333583, 37.809772],\n            [-122.33315, 37.809797],\n            [-122.332755, 37.809816],\n            [-122.332116, 37.809842],\n            [-122.33108, 37.809866],\n            [-122.330735, 37.80987],\n            [-122.330014, 37.80987],\n            [-122.329757, 37.809867],\n            [-122.329449, 37.809862],\n            [-122.32917, 37.809857],\n            [-122.328638, 37.809842],\n            [-122.328189, 37.809825],\n            [-122.328029, 37.809818],\n            [-122.327321, 37.809778],\n            [-122.326497, 37.809718],\n            [-122.326137, 37.809687],\n            [-122.325817, 37.809649],\n            [-122.323554, 37.809392],\n            [-122.322814, 37.809368],\n            [-122.322059, 37.809327],\n            [-122.320701, 37.80925],\n            [-122.320136, 37.80922],\n            [-122.31951, 37.809162],\n            [-122.318885, 37.809106],\n            [-122.316062, 37.808815],\n            [-122.315696, 37.808781],\n            [-122.315482, 37.808758],\n            [-122.315185, 37.808731],\n            [-122.314429, 37.808664],\n            [-122.312583, 37.808473],\n            [-122.312057, 37.808422],\n            [-122.308982, 37.808129],\n            [-122.308486, 37.808084],\n            [-122.308295, 37.808064],\n            [-122.306411, 37.807806],\n            [-122.305541, 37.807685],\n            [-122.305182, 37.807598],\n            [-122.30506, 37.807577],\n            [-122.304526, 37.807461],\n            [-122.30445, 37.807442],\n            [-122.304175, 37.807385],\n            [-122.303977, 37.807343],\n            [-122.303794, 37.807301],\n            [-122.302436, 37.806996],\n            [-122.302398, 37.806989],\n            [-122.298135, 37.806031],\n            [-122.297836, 37.805959],\n            [-122.297584, 37.805879],\n            [-122.297324, 37.805794],\n            [-122.297073, 37.805706],\n            [-122.296836, 37.805615],\n            [-122.296599, 37.805523],\n            [-122.296356, 37.805421],\n            [-122.296134, 37.805318],\n            [-122.296096, 37.805302],\n            [-122.295089, 37.804897],\n            [-122.293883, 37.80441],\n            [-122.29341, 37.804204],\n            [-122.292579, 37.803875],\n            [-122.292563, 37.803872],\n            [-122.29206, 37.803669],\n            [-122.291923, 37.803616],\n            [-122.291747, 37.803548],\n            [-122.291663, 37.803513],\n            [-122.291564, 37.803468],\n            [-122.291335, 37.803381],\n            [-122.29132, 37.803376],\n            [-122.291312, 37.803371],\n            [-122.291297, 37.803365],\n            [-122.291137, 37.803291],\n            [-122.290946, 37.803223],\n            [-122.284796, 37.801721],\n            [-122.284606, 37.801668],\n            [-122.284431, 37.801611],\n            [-122.284095, 37.801496],\n            [-122.283744, 37.801381],\n            [-122.283645, 37.801347],\n            [-122.283523, 37.801296],\n            [-122.283386, 37.801237],\n            [-122.283157, 37.801141],\n            [-122.282943, 37.801045],\n            [-122.282546, 37.800853],\n            [-122.282294, 37.800744],\n            [-122.282272, 37.800732],\n            [-122.28218, 37.800693],\n            [-122.281188, 37.800214],\n            [-122.280051, 37.799664],\n            [-122.280044, 37.79966],\n            [-122.279952, 37.799607],\n            [-122.279907, 37.799584],\n            [-122.279716, 37.799493],\n            [-122.279471, 37.799368],\n            [-122.279183, 37.799217],\n            [-122.278686, 37.798956],\n            [-122.278478, 37.798859],\n            [-122.278369, 37.798811],\n            [-122.278197, 37.798746],\n            [-122.278051, 37.798694],\n            [-122.277819, 37.798637],\n            [-122.277697, 37.798614],\n            [-122.277507, 37.798583],\n            [-122.277358, 37.798568],\n            [-122.276984, 37.798532],\n            [-122.276923, 37.798528],\n            [-122.276856, 37.798527]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {OBJECTID: 21, name: 'PITT-SFIA (ROUTE 1/2)', color: '#ffe800'},\n      geometry: {\n        type: 'MultiLineString',\n        coordinates: [\n          [\n            [-121.942073, 38.018937],\n            [-121.942449, 38.01892],\n            [-121.942633, 38.018917],\n            [-121.94282, 38.018919],\n            [-121.943121, 38.018915],\n            [-121.943447, 38.018913],\n            [-121.943743, 38.018913],\n            [-121.944108, 38.018909],\n            [-121.94449, 38.01891],\n            [-121.944892, 38.018911],\n            [-121.945267, 38.018911],\n            [-121.9456, 38.018911],\n            [-121.945852, 38.018909],\n            [-121.946445, 38.018912],\n            [-121.946981, 38.018924],\n            [-121.947611, 38.018963],\n            [-121.948062, 38.019005],\n            [-121.948319, 38.019033],\n            [-121.948534, 38.019066],\n            [-121.948917, 38.019132],\n            [-121.949248, 38.0192],\n            [-121.949597, 38.019284],\n            [-121.94979, 38.019335],\n            [-121.950002, 38.019394],\n            [-121.950206, 38.019454],\n            [-121.950605, 38.019575],\n            [-121.950798, 38.019635],\n            [-121.951102, 38.019731],\n            [-121.951338, 38.019803],\n            [-121.95161, 38.019887],\n            [-121.951874, 38.019969],\n            [-121.952, 38.020008],\n            [-121.952239, 38.020082],\n            [-121.952554, 38.020179],\n            [-121.952843, 38.020269],\n            [-121.953136, 38.020357],\n            [-121.953308, 38.020406],\n            [-121.953643, 38.020503],\n            [-121.953936, 38.020584],\n            [-121.954182, 38.02065],\n            [-121.954427, 38.020712],\n            [-121.954624, 38.020762],\n            [-121.954938, 38.020838],\n            [-121.955164, 38.020892],\n            [-121.9554, 38.020944],\n            [-121.955637, 38.020996],\n            [-121.955805, 38.021031],\n            [-121.955926, 38.021057],\n            [-121.956017, 38.021075],\n            [-121.956428, 38.021157],\n            [-121.956689, 38.021207],\n            [-121.957004, 38.021266],\n            [-121.95732, 38.021325],\n            [-121.957584, 38.021376],\n            [-121.957925, 38.021441],\n            [-121.958182, 38.021489],\n            [-121.958478, 38.021547],\n            [-121.958713, 38.02159],\n            [-121.958968, 38.021638],\n            [-121.959195, 38.02168],\n            [-121.959414, 38.021721],\n            [-121.959704, 38.021777],\n            [-121.959955, 38.021825],\n            [-121.960349, 38.0219],\n            [-121.960645, 38.021956],\n            [-121.961001, 38.022024],\n            [-121.96136, 38.022091],\n            [-121.961901, 38.022194],\n            [-121.962517, 38.022311],\n            [-121.962991, 38.022401],\n            [-121.963184, 38.022438],\n            [-121.96357, 38.022509],\n            [-121.963898, 38.022572],\n            [-121.964273, 38.022644],\n            [-121.964605, 38.022706],\n            [-121.965156, 38.02281],\n            [-121.965598, 38.022894],\n            [-121.966146, 38.022998],\n            [-121.966646, 38.023093],\n            [-121.967215, 38.0232],\n            [-121.967743, 38.0233],\n            [-121.96817, 38.023381],\n            [-121.968312, 38.023408],\n            [-121.96833, 38.023412],\n            [-121.968348, 38.023416],\n            [-121.96836, 38.023418],\n            [-121.968371, 38.02342],\n            [-121.968384, 38.023422],\n            [-121.968431, 38.023431],\n            [-121.968606, 38.023462],\n            [-121.968714, 38.023483],\n            [-121.968898, 38.023518],\n            [-121.969114, 38.023556],\n            [-121.969264, 38.023582],\n            [-121.969396, 38.023604],\n            [-121.969728, 38.023657],\n            [-121.970104, 38.023708],\n            [-121.970512, 38.023755],\n            [-121.970877, 38.02379],\n            [-121.970942, 38.023793],\n            [-121.971315, 38.023818],\n            [-121.971738, 38.023836],\n            [-121.972116, 38.023844],\n            [-121.972593, 38.023843],\n            [-121.972882, 38.023833],\n            [-121.973374, 38.023809],\n            [-121.973674, 38.023789],\n            [-121.974088, 38.02375],\n            [-121.974252, 38.023734],\n            [-121.974485, 38.023705],\n            [-121.974717, 38.023673],\n            [-121.975026, 38.023627],\n            [-121.975275, 38.023585],\n            [-121.975607, 38.023523],\n            [-121.976055, 38.023429],\n            [-121.976189, 38.023397],\n            [-121.976447, 38.023336],\n            [-121.976794, 38.023244],\n            [-121.977226, 38.02312],\n            [-121.977574, 38.02301],\n            [-121.978322, 38.022747],\n            [-121.978744, 38.022577],\n            [-121.979167, 38.022398],\n            [-121.979561, 38.022228],\n            [-121.979989, 38.022042],\n            [-121.980224, 38.021939],\n            [-121.980495, 38.021823],\n            [-121.980797, 38.021691],\n            [-121.981043, 38.021584],\n            [-121.981329, 38.02146],\n            [-121.981452, 38.021406],\n            [-121.981598, 38.021343],\n            [-121.98182, 38.021247],\n            [-121.982059, 38.021142],\n            [-121.982358, 38.021012],\n            [-121.982668, 38.020878],\n            [-121.982765, 38.020837],\n            [-121.982911, 38.020771],\n            [-121.983045, 38.020712],\n            [-121.983301, 38.020603],\n            [-121.983564, 38.02049],\n            [-121.98378, 38.020399],\n            [-121.983998, 38.020308],\n            [-121.984221, 38.020212],\n            [-121.984539, 38.020075],\n            [-121.984759, 38.019977],\n            [-121.985049, 38.019844],\n            [-121.985239, 38.019761],\n            [-121.985631, 38.019591],\n            [-121.98587, 38.019486],\n            [-121.986149, 38.019364],\n            [-121.986548, 38.019193],\n            [-121.986814, 38.019074],\n            [-121.986944, 38.019018],\n            [-121.987103, 38.018949],\n            [-121.987383, 38.018825],\n            [-121.987605, 38.018731],\n            [-121.987787, 38.018649],\n            [-121.988059, 38.018527],\n            [-121.98833, 38.018402],\n            [-121.988546, 38.018296],\n            [-121.988731, 38.018205],\n            [-121.988833, 38.018152],\n            [-121.988994, 38.01807],\n            [-121.989134, 38.017995],\n            [-121.989373, 38.017865],\n            [-121.989611, 38.017729],\n            [-121.989789, 38.017623],\n            [-121.990001, 38.017498],\n            [-121.990232, 38.017355],\n            [-121.9904, 38.017249],\n            [-121.990604, 38.017119],\n            [-121.990891, 38.016935],\n            [-121.991101, 38.0168],\n            [-121.991406, 38.016606],\n            [-121.991625, 38.016471],\n            [-121.991832, 38.016339],\n            [-121.992061, 38.016198],\n            [-121.99226, 38.016074],\n            [-121.99242, 38.01597],\n            [-121.992677, 38.01581],\n            [-121.992931, 38.015657],\n            [-121.99325, 38.015474],\n            [-121.993406, 38.015387],\n            [-121.993527, 38.015321],\n            [-121.993695, 38.015231],\n            [-121.993836, 38.015157],\n            [-121.994145, 38.014999],\n            [-121.994326, 38.01491],\n            [-121.994522, 38.014818],\n            [-121.994656, 38.014758],\n            [-121.995046, 38.014586],\n            [-121.995326, 38.014469],\n            [-121.995546, 38.01438],\n            [-121.99591, 38.014241],\n            [-121.996157, 38.01415],\n            [-121.996489, 38.014036],\n            [-121.996811, 38.01393],\n            [-121.99707, 38.013846],\n            [-121.997424, 38.013739],\n            [-121.997933, 38.013589],\n            [-121.998366, 38.013486],\n            [-121.998717, 38.013412],\n            [-121.999094, 38.013332],\n            [-121.99933, 38.013286],\n            [-121.999656, 38.013227],\n            [-121.999918, 38.013184],\n            [-122.000255, 38.013132],\n            [-122.000681, 38.013064],\n            [-122.001179, 38.012989],\n            [-122.001466, 38.012943],\n            [-122.001936, 38.012871],\n            [-122.002351, 38.012808],\n            [-122.002858, 38.01273],\n            [-122.003721, 38.012599],\n            [-122.004356, 38.012501],\n            [-122.004993, 38.012403],\n            [-122.005781, 38.012282],\n            [-122.006144, 38.012227],\n            [-122.006602, 38.012159],\n            [-122.007324, 38.012048],\n            [-122.008053, 38.011937],\n            [-122.008585, 38.011856],\n            [-122.009173, 38.011766],\n            [-122.009755, 38.011677],\n            [-122.010711, 38.011532],\n            [-122.01142, 38.011425],\n            [-122.012111, 38.011321],\n            [-122.012819, 38.011211],\n            [-122.013204, 38.011152],\n            [-122.013766, 38.011067],\n            [-122.014316, 38.010984],\n            [-122.014971, 38.010884],\n            [-122.015295, 38.010835],\n            [-122.015508, 38.010804],\n            [-122.015974, 38.010733],\n            [-122.016267, 38.010687],\n            [-122.016695, 38.01062],\n            [-122.016966, 38.010569],\n            [-122.01727, 38.010498],\n            [-122.017495, 38.010426],\n            [-122.017728, 38.010344],\n            [-122.018126, 38.010192],\n            [-122.018407, 38.010064],\n            [-122.018543, 38.009996],\n            [-122.018649, 38.009941],\n            [-122.018963, 38.009755],\n            [-122.019068, 38.009691],\n            [-122.019212, 38.009595],\n            [-122.019342, 38.0095],\n            [-122.01949, 38.009386],\n            [-122.019647, 38.009251],\n            [-122.019919, 38.008995],\n            [-122.02003, 38.008878],\n            [-122.020287, 38.008581],\n            [-122.020469, 38.008361],\n            [-122.020641, 38.008152],\n            [-122.020727, 38.008044],\n            [-122.020874, 38.007862],\n            [-122.021006, 38.007698],\n            [-122.021128, 38.007545],\n            [-122.021212, 38.007436],\n            [-122.021328, 38.007292],\n            [-122.021457, 38.007135],\n            [-122.021712, 38.006806],\n            [-122.021812, 38.006683],\n            [-122.022092, 38.006338],\n            [-122.022378, 38.005996],\n            [-122.022525, 38.005806],\n            [-122.022894, 38.005373],\n            [-122.023019, 38.005212],\n            [-122.0231, 38.005107],\n            [-122.02322, 38.004959],\n            [-122.023369, 38.004781],\n            [-122.023504, 38.004629],\n            [-122.02377, 38.004309],\n            [-122.023902, 38.004159],\n            [-122.024144, 38.003848],\n            [-122.024449, 38.003457],\n            [-122.024759, 38.003083],\n            [-122.025006, 38.002785],\n            [-122.025391, 38.002335],\n            [-122.025883, 38.001722],\n            [-122.026355, 38.001156],\n            [-122.026752, 38.000666],\n            [-122.02711, 38.000188],\n            [-122.027332, 37.999882],\n            [-122.027812, 37.999224],\n            [-122.028239, 37.998637],\n            [-122.028478, 37.99831],\n            [-122.028674, 37.998043],\n            [-122.028856, 37.997791],\n            [-122.029139, 37.997397],\n            [-122.02948, 37.996925],\n            [-122.029684, 37.996642],\n            [-122.029926, 37.996302],\n            [-122.030118, 37.996029],\n            [-122.030299, 37.995784],\n            [-122.030406, 37.995633],\n            [-122.030454, 37.995568],\n            [-122.030556, 37.995433],\n            [-122.030663, 37.995291],\n            [-122.030729, 37.995194],\n            [-122.030811, 37.995078],\n            [-122.031045, 37.994757],\n            [-122.031277, 37.994439],\n            [-122.031429, 37.994227],\n            [-122.031513, 37.994114],\n            [-122.031621, 37.993966],\n            [-122.031827, 37.993685],\n            [-122.031997, 37.99345],\n            [-122.032141, 37.99325],\n            [-122.032285, 37.993048],\n            [-122.032442, 37.992816],\n            [-122.032583, 37.992598],\n            [-122.032652, 37.992485],\n            [-122.032726, 37.992358],\n            [-122.032846, 37.992139],\n            [-122.032926, 37.991986],\n            [-122.033011, 37.991807],\n            [-122.033088, 37.991637],\n            [-122.033156, 37.99147],\n            [-122.033223, 37.991295],\n            [-122.033284, 37.991123],\n            [-122.033344, 37.990938],\n            [-122.033396, 37.990756],\n            [-122.033422, 37.990656],\n            [-122.033454, 37.990521],\n            [-122.033503, 37.990288],\n            [-122.033543, 37.990063],\n            [-122.033572, 37.989836],\n            [-122.033576, 37.989801],\n            [-122.033594, 37.989496],\n            [-122.033606, 37.9893],\n            [-122.033608, 37.989183],\n            [-122.03361, 37.989022],\n            [-122.033606, 37.988848],\n            [-122.033596, 37.988684],\n            [-122.033583, 37.988529],\n            [-122.033567, 37.988374],\n            [-122.033544, 37.988195],\n            [-122.033524, 37.988076],\n            [-122.033494, 37.987894],\n            [-122.033465, 37.987749],\n            [-122.033406, 37.987515],\n            [-122.033336, 37.987262],\n            [-122.033277, 37.987087],\n            [-122.033176, 37.986795],\n            [-122.033066, 37.986501],\n            [-122.032884, 37.986057],\n            [-122.032782, 37.985814],\n            [-122.032673, 37.985558],\n            [-122.032566, 37.985305],\n            [-122.032466, 37.985074],\n            [-122.032339, 37.984774],\n            [-122.032246, 37.984552],\n            [-122.032128, 37.984277],\n            [-122.032074, 37.984151],\n            [-122.031941, 37.983837],\n            [-122.031813, 37.983541],\n            [-122.0317, 37.983267],\n            [-122.031642, 37.983134],\n            [-122.03159, 37.983009],\n            [-122.031521, 37.982852],\n            [-122.031437, 37.982651],\n            [-122.031284, 37.982289],\n            [-122.031154, 37.981975],\n            [-122.03108, 37.981803],\n            [-122.031004, 37.981624],\n            [-122.03087, 37.981305],\n            [-122.030754, 37.981029],\n            [-122.03063, 37.980742],\n            [-122.030506, 37.980451],\n            [-122.030448, 37.980322],\n            [-122.030372, 37.980158],\n            [-122.030297, 37.979999],\n            [-122.030255, 37.979918],\n            [-122.030232, 37.97987],\n            [-122.030155, 37.97973],\n            [-122.030078, 37.979592],\n            [-122.029968, 37.979416],\n            [-122.029916, 37.979332],\n            [-122.029858, 37.979243],\n            [-122.029822, 37.979191],\n            [-122.029779, 37.979128],\n            [-122.029734, 37.979063],\n            [-122.029696, 37.979011],\n            [-122.029613, 37.978894],\n            [-122.029577, 37.978846],\n            [-122.029555, 37.978813],\n            [-122.029516, 37.978762],\n            [-122.029494, 37.978732],\n            [-122.029475, 37.978706],\n            [-122.02945, 37.978672],\n            [-122.02943, 37.978644],\n            [-122.029413, 37.978621],\n            [-122.029398, 37.978603],\n            [-122.029381, 37.978579],\n            [-122.029296, 37.978464],\n            [-122.029225, 37.97837],\n            [-122.029165, 37.978289],\n            [-122.029068, 37.978157],\n            [-122.029045, 37.978128],\n            [-122.029019, 37.97809],\n            [-122.029009, 37.978077],\n            [-122.028998, 37.978065],\n            [-122.028982, 37.978042],\n            [-122.028967, 37.978024],\n            [-122.028956, 37.978007],\n            [-122.028936, 37.977978],\n            [-122.028902, 37.97793],\n            [-122.028862, 37.977876],\n            [-122.028819, 37.977812],\n            [-122.02878, 37.977756],\n            [-122.028722, 37.977667],\n            [-122.028649, 37.977548],\n            [-122.02862, 37.977497],\n            [-122.028565, 37.977386],\n            [-122.028527, 37.977311],\n            [-122.028499, 37.977249],\n            [-122.028458, 37.977152],\n            [-122.028427, 37.977059],\n            [-122.028385, 37.976924],\n            [-122.028361, 37.976832],\n            [-122.028332, 37.976699],\n            [-122.028315, 37.976598],\n            [-122.028302, 37.976504],\n            [-122.028289, 37.976372],\n            [-122.028281, 37.976257],\n            [-122.028273, 37.976102],\n            [-122.028277, 37.97599],\n            [-122.028282, 37.975917],\n            [-122.028297, 37.975822],\n            [-122.028317, 37.975692],\n            [-122.028363, 37.975495],\n            [-122.028398, 37.97538],\n            [-122.028425, 37.975301],\n            [-122.028437, 37.975263],\n            [-122.028455, 37.975212],\n            [-122.028476, 37.975155],\n            [-122.028485, 37.97513],\n            [-122.028501, 37.975094],\n            [-122.028507, 37.975079],\n            [-122.028514, 37.975066],\n            [-122.028526, 37.975037],\n            [-122.028535, 37.975017],\n            [-122.028543, 37.974995],\n            [-122.02855, 37.974971],\n            [-122.028561, 37.974941],\n            [-122.02859, 37.974878],\n            [-122.028669, 37.974696],\n            [-122.028749, 37.974538],\n            [-122.028823, 37.974372],\n            [-122.028913, 37.974168],\n            [-122.028976, 37.974023],\n            [-122.02905, 37.973841],\n            [-122.029177, 37.973532],\n            [-122.029447, 37.972906],\n            [-122.029506, 37.972792],\n            [-122.029548, 37.972709],\n            [-122.029641, 37.972496],\n            [-122.029753, 37.97223],\n            [-122.029834, 37.97204],\n            [-122.029901, 37.97189],\n            [-122.030006, 37.971655],\n            [-122.030049, 37.971567],\n            [-122.030071, 37.971525],\n            [-122.030117, 37.971418],\n            [-122.030184, 37.971265],\n            [-122.030245, 37.971121],\n            [-122.030273, 37.97105],\n            [-122.030301, 37.970982],\n            [-122.03034, 37.970893],\n            [-122.030397, 37.970764],\n            [-122.030466, 37.970607],\n            [-122.0305, 37.97052],\n            [-122.030517, 37.970481],\n            [-122.030544, 37.970415],\n            [-122.030564, 37.970373],\n            [-122.030596, 37.970301],\n            [-122.030645, 37.970173],\n            [-122.030681, 37.970087],\n            [-122.030705, 37.970022],\n            [-122.030757, 37.969912],\n            [-122.030799, 37.96981],\n            [-122.030852, 37.96967],\n            [-122.030892, 37.969562],\n            [-122.030944, 37.969422],\n            [-122.030996, 37.969283],\n            [-122.031036, 37.969181],\n            [-122.031057, 37.969118],\n            [-122.031085, 37.969033],\n            [-122.031093, 37.969004],\n            [-122.031105, 37.968969],\n            [-122.031114, 37.968943],\n            [-122.031119, 37.968926],\n            [-122.031126, 37.968905],\n            [-122.031132, 37.968882],\n            [-122.031142, 37.968849],\n            [-122.031156, 37.968801],\n            [-122.03117, 37.968755],\n            [-122.031174, 37.968743],\n            [-122.031183, 37.968707],\n            [-122.031191, 37.968675],\n            [-122.031212, 37.968598],\n            [-122.03122, 37.968569],\n            [-122.031227, 37.96854],\n            [-122.031233, 37.968514],\n            [-122.03124, 37.96849],\n            [-122.031245, 37.968472],\n            [-122.031251, 37.968445],\n            [-122.031255, 37.968426],\n            [-122.03126, 37.968402],\n            [-122.031266, 37.968378],\n            [-122.031271, 37.968353],\n            [-122.031276, 37.968327],\n            [-122.031282, 37.968292],\n            [-122.031289, 37.968263],\n            [-122.031293, 37.968242],\n            [-122.031297, 37.96822],\n            [-122.031301, 37.968197],\n            [-122.031305, 37.968176],\n            [-122.03131, 37.968152],\n            [-122.031314, 37.968129],\n            [-122.031317, 37.96811],\n            [-122.031321, 37.968089],\n            [-122.031324, 37.968076],\n            [-122.031327, 37.968062],\n            [-122.031329, 37.96805],\n            [-122.031332, 37.968032],\n            [-122.031335, 37.96801],\n            [-122.031338, 37.967993],\n            [-122.031343, 37.967961],\n            [-122.031347, 37.96793],\n            [-122.031349, 37.967915],\n            [-122.031352, 37.967891],\n            [-122.031354, 37.967873],\n            [-122.031357, 37.967853],\n            [-122.031359, 37.967838],\n            [-122.03136, 37.967826],\n            [-122.031363, 37.967805],\n            [-122.031366, 37.967786],\n            [-122.031368, 37.967767],\n            [-122.031369, 37.967751],\n            [-122.031373, 37.967724],\n            [-122.031375, 37.967707],\n            [-122.031377, 37.96769],\n            [-122.031379, 37.967666],\n            [-122.031382, 37.967642],\n            [-122.031384, 37.967621],\n            [-122.031386, 37.967602],\n            [-122.031388, 37.967588],\n            [-122.03139, 37.967566],\n            [-122.031391, 37.967545],\n            [-122.031393, 37.967525],\n            [-122.031395, 37.967502],\n            [-122.031397, 37.96748],\n            [-122.031399, 37.967463],\n            [-122.031401, 37.967436],\n            [-122.031404, 37.967403],\n            [-122.031406, 37.967362],\n            [-122.031408, 37.967328],\n            [-122.03141, 37.967292],\n            [-122.031412, 37.967258],\n            [-122.031414, 37.967214],\n            [-122.031415, 37.967187],\n            [-122.031416, 37.967168],\n            [-122.031417, 37.967141],\n            [-122.031418, 37.967096],\n            [-122.031421, 37.967005],\n            [-122.031421, 37.966932],\n            [-122.031422, 37.96688],\n            [-122.031422, 37.966844],\n            [-122.031422, 37.966818],\n            [-122.031421, 37.966793],\n            [-122.03142, 37.966776],\n            [-122.031419, 37.966742],\n            [-122.031418, 37.966715],\n            [-122.031418, 37.966673],\n            [-122.031416, 37.966641],\n            [-122.031414, 37.966595],\n            [-122.031411, 37.966552],\n            [-122.031407, 37.966459],\n            [-122.031404, 37.966417],\n            [-122.031401, 37.966357],\n            [-122.031396, 37.966288],\n            [-122.03139, 37.966219],\n            [-122.031383, 37.966143],\n            [-122.031373, 37.966073],\n            [-122.031366, 37.96601],\n            [-122.031357, 37.965943],\n            [-122.031351, 37.965904],\n            [-122.031346, 37.965868],\n            [-122.03134, 37.965837],\n            [-122.031336, 37.965808],\n            [-122.031326, 37.965747],\n            [-122.031304, 37.965629],\n            [-122.031285, 37.965546],\n            [-122.03128, 37.965518],\n            [-122.03127, 37.965474],\n            [-122.03125, 37.965388],\n            [-122.031209, 37.965223],\n            [-122.031178, 37.965118],\n            [-122.031139, 37.964994],\n            [-122.0311, 37.964873],\n            [-122.031052, 37.964731],\n            [-122.030978, 37.964522],\n            [-122.030912, 37.964355],\n            [-122.030833, 37.964146],\n            [-122.030788, 37.964035],\n            [-122.030745, 37.963927],\n            [-122.030729, 37.963886],\n            [-122.030703, 37.96382],\n            [-122.030657, 37.96371],\n            [-122.030575, 37.963524],\n            [-122.030531, 37.96341],\n            [-122.030474, 37.963254],\n            [-122.030417, 37.963108],\n            [-122.030371, 37.96299],\n            [-122.030313, 37.962844],\n            [-122.030234, 37.962649],\n            [-122.030175, 37.962502],\n            [-122.030122, 37.962367],\n            [-122.030044, 37.962167],\n            [-122.029973, 37.961995],\n            [-122.029899, 37.961811],\n            [-122.029821, 37.961613],\n            [-122.029687, 37.961274],\n            [-122.029537, 37.960899],\n            [-122.029496, 37.960799],\n            [-122.029292, 37.96029],\n            [-122.029097, 37.959802],\n            [-122.028931, 37.959381],\n            [-122.028776, 37.959001],\n            [-122.028643, 37.958666],\n            [-122.028194, 37.957541],\n            [-122.027773, 37.956486],\n            [-122.027168, 37.95498],\n            [-122.026893, 37.954328],\n            [-122.026552, 37.953546],\n            [-122.026283, 37.952933],\n            [-122.026082, 37.952465],\n            [-122.025991, 37.952245],\n            [-122.025824, 37.951824],\n            [-122.025655, 37.951438],\n            [-122.025597, 37.951264],\n            [-122.025457, 37.950904],\n            [-122.02532, 37.950553],\n            [-122.025247, 37.950361],\n            [-122.025161, 37.950118],\n            [-122.025134, 37.950042],\n            [-122.025078, 37.949857],\n            [-122.025008, 37.94961],\n            [-122.024986, 37.949495],\n            [-122.024956, 37.949362],\n            [-122.024921, 37.94917],\n            [-122.024889, 37.948918],\n            [-122.024875, 37.948681],\n            [-122.024868, 37.948419],\n            [-122.024876, 37.948247],\n            [-122.024882, 37.948137],\n            [-122.024891, 37.948034],\n            [-122.024915, 37.947829],\n            [-122.024956, 37.947611],\n            [-122.024996, 37.947429],\n            [-122.025018, 37.947326],\n            [-122.025064, 37.947173],\n            [-122.025117, 37.947005],\n            [-122.025205, 37.946765],\n            [-122.02538, 37.946375],\n            [-122.025441, 37.946257],\n            [-122.025523, 37.94611],\n            [-122.025604, 37.945977],\n            [-122.025727, 37.945789],\n            [-122.025847, 37.945617],\n            [-122.026003, 37.945413],\n            [-122.026147, 37.945243],\n            [-122.026314, 37.945063],\n            [-122.026501, 37.944873],\n            [-122.026633, 37.944749],\n            [-122.02667, 37.944717],\n            [-122.026777, 37.944622],\n            [-122.027002, 37.94444],\n            [-122.027186, 37.9443],\n            [-122.027358, 37.944178],\n            [-122.027635, 37.943997],\n            [-122.027835, 37.943876],\n            [-122.028005, 37.943777],\n            [-122.028411, 37.943553],\n            [-122.028725, 37.943386],\n            [-122.029026, 37.94323],\n            [-122.02917, 37.943155],\n            [-122.029623, 37.942923],\n            [-122.029932, 37.942764],\n            [-122.030779, 37.942327],\n            [-122.031436, 37.941989],\n            [-122.032056, 37.941668],\n            [-122.0329, 37.941234],\n            [-122.033731, 37.940806],\n            [-122.034252, 37.940538],\n            [-122.034962, 37.940173],\n            [-122.035604, 37.939842],\n            [-122.03631, 37.939478],\n            [-122.037001, 37.939123],\n            [-122.037792, 37.938716],\n            [-122.038461, 37.93837],\n            [-122.039014, 37.938086],\n            [-122.039425, 37.937874],\n            [-122.04021, 37.93747],\n            [-122.040958, 37.937085],\n            [-122.041682, 37.936713],\n            [-122.042314, 37.936386],\n            [-122.043008, 37.936028],\n            [-122.043736, 37.935653],\n            [-122.04448, 37.935271],\n            [-122.045119, 37.934941],\n            [-122.045894, 37.934541],\n            [-122.046614, 37.93417],\n            [-122.046874, 37.934035],\n            [-122.04711, 37.933915],\n            [-122.047342, 37.933796],\n            [-122.047998, 37.933456],\n            [-122.048771, 37.933055],\n            [-122.049464, 37.932703],\n            [-122.050202, 37.93232],\n            [-122.050837, 37.931991],\n            [-122.05118, 37.931818],\n            [-122.051502, 37.931646],\n            [-122.051814, 37.931467],\n            [-122.052129, 37.931285],\n            [-122.05244, 37.931096],\n            [-122.052729, 37.930911],\n            [-122.053008, 37.930724],\n            [-122.053296, 37.93052],\n            [-122.05357, 37.93032],\n            [-122.053868, 37.930092],\n            [-122.054121, 37.929895],\n            [-122.054361, 37.929705],\n            [-122.054593, 37.929521],\n            [-122.055089, 37.929129],\n            [-122.055619, 37.928707],\n            [-122.056244, 37.928208],\n            [-122.05647, 37.928033],\n            [-122.056722, 37.927837],\n            [-122.057279, 37.927393],\n            [-122.057696, 37.927061],\n            [-122.057855, 37.926933],\n            [-122.05804, 37.92678],\n            [-122.058216, 37.926635],\n            [-122.058403, 37.926469],\n            [-122.058554, 37.926333],\n            [-122.058817, 37.926078],\n            [-122.059009, 37.925877],\n            [-122.059179, 37.925686],\n            [-122.059314, 37.925523],\n            [-122.059395, 37.925421],\n            [-122.059477, 37.925312],\n            [-122.059556, 37.925203],\n            [-122.05962, 37.925112],\n            [-122.059743, 37.924921],\n            [-122.059868, 37.924717],\n            [-122.059981, 37.92451],\n            [-122.060046, 37.924384],\n            [-122.060111, 37.924247],\n            [-122.060178, 37.924106],\n            [-122.060236, 37.923973],\n            [-122.060295, 37.923831],\n            [-122.060358, 37.923674],\n            [-122.060454, 37.923416],\n            [-122.060536, 37.92319],\n            [-122.060644, 37.922884],\n            [-122.06078, 37.922499],\n            [-122.060897, 37.92216],\n            [-122.060987, 37.921901],\n            [-122.061017, 37.921815],\n            [-122.061041, 37.921745],\n            [-122.061071, 37.921666],\n            [-122.061195, 37.921303],\n            [-122.061333, 37.920914],\n            [-122.061504, 37.920448],\n            [-122.061695, 37.919982],\n            [-122.061817, 37.919706],\n            [-122.061928, 37.919469],\n            [-122.062064, 37.919196],\n            [-122.062192, 37.918943],\n            [-122.062299, 37.918736],\n            [-122.062327, 37.918687],\n            [-122.062471, 37.918407],\n            [-122.062555, 37.918243],\n            [-122.062633, 37.918086],\n            [-122.062708, 37.917938],\n            [-122.062784, 37.91778],\n            [-122.062855, 37.917628],\n            [-122.062926, 37.917467],\n            [-122.062988, 37.917323],\n            [-122.063052, 37.917164],\n            [-122.063109, 37.917009],\n            [-122.063168, 37.916848],\n            [-122.063221, 37.916687],\n            [-122.063271, 37.916535],\n            [-122.06335, 37.916259],\n            [-122.06344, 37.915883],\n            [-122.063484, 37.915677],\n            [-122.063517, 37.915499],\n            [-122.063579, 37.91509],\n            [-122.063642, 37.914584],\n            [-122.063711, 37.913988],\n            [-122.063759, 37.913583],\n            [-122.06382, 37.91308],\n            [-122.063873, 37.91274],\n            [-122.063904, 37.912556],\n            [-122.063959, 37.91229],\n            [-122.064019, 37.912056],\n            [-122.064078, 37.911834],\n            [-122.064133, 37.911658],\n            [-122.064186, 37.911493],\n            [-122.064231, 37.911363],\n            [-122.064281, 37.911226],\n            [-122.064362, 37.911021],\n            [-122.064456, 37.910802],\n            [-122.064548, 37.910601],\n            [-122.064633, 37.910421],\n            [-122.064704, 37.910279],\n            [-122.064781, 37.910128],\n            [-122.064867, 37.90997],\n            [-122.064951, 37.909823],\n            [-122.065061, 37.909621],\n            [-122.065168, 37.909435],\n            [-122.065297, 37.909212],\n            [-122.065406, 37.909031],\n            [-122.065497, 37.908876],\n            [-122.065705, 37.908513],\n            [-122.065934, 37.908128],\n            [-122.066039, 37.907949],\n            [-122.066252, 37.907593],\n            [-122.066313, 37.90749],\n            [-122.066378, 37.90738],\n            [-122.066518, 37.907146],\n            [-122.066837, 37.906625],\n            [-122.067124, 37.906142],\n            [-122.067453, 37.905586],\n            [-122.06768, 37.905209],\n            [-122.06785, 37.904925],\n            [-122.067963, 37.904732],\n            [-122.068106, 37.90449],\n            [-122.068172, 37.904377],\n            [-122.06837, 37.90404],\n            [-122.068521, 37.903785],\n            [-122.068696, 37.903488],\n            [-122.068846, 37.903233],\n            [-122.069023, 37.902935],\n            [-122.069233, 37.902581],\n            [-122.069374, 37.902342],\n            [-122.069546, 37.90206],\n            [-122.069745, 37.901742],\n            [-122.069955, 37.901435],\n            [-122.070226, 37.901058],\n            [-122.070387, 37.900856],\n            [-122.070531, 37.900686],\n            [-122.070698, 37.900507],\n            [-122.070853, 37.900347],\n            [-122.07097, 37.90023],\n            [-122.071092, 37.900117],\n            [-122.071225, 37.899993],\n            [-122.07137, 37.899868],\n            [-122.071467, 37.899781],\n            [-122.071596, 37.899679],\n            [-122.071826, 37.899498],\n            [-122.072064, 37.899326],\n            [-122.072297, 37.899169],\n            [-122.072422, 37.899091],\n            [-122.072551, 37.899008],\n            [-122.072811, 37.898856],\n            [-122.072935, 37.898786],\n            [-122.07307, 37.898712],\n            [-122.073338, 37.898578],\n            [-122.073473, 37.898513],\n            [-122.073625, 37.898447],\n            [-122.073896, 37.898319],\n            [-122.074181, 37.898201],\n            [-122.074426, 37.898107],\n            [-122.07464, 37.898032],\n            [-122.074892, 37.897949],\n            [-122.075117, 37.89788],\n            [-122.075421, 37.897795],\n            [-122.075666, 37.897735],\n            [-122.075937, 37.897674],\n            [-122.076224, 37.897615],\n            [-122.076442, 37.897576],\n            [-122.076568, 37.897555],\n            [-122.076814, 37.897519],\n            [-122.077104, 37.897482],\n            [-122.077399, 37.897452],\n            [-122.077705, 37.897429],\n            [-122.077981, 37.897416],\n            [-122.078272, 37.897409],\n            [-122.078572, 37.897406],\n            [-122.078865, 37.897412],\n            [-122.079159, 37.89743],\n            [-122.079336, 37.897433],\n            [-122.07943, 37.89744],\n            [-122.079926, 37.897488],\n            [-122.080699, 37.897578],\n            [-122.081421, 37.897673],\n            [-122.083043, 37.897865],\n            [-122.083481, 37.897906],\n            [-122.083709, 37.897931],\n            [-122.083917, 37.897949],\n            [-122.08412, 37.897966],\n            [-122.084325, 37.897983],\n            [-122.084931, 37.898011],\n            [-122.085042, 37.898017],\n            [-122.085254, 37.898012],\n            [-122.08556, 37.898012],\n            [-122.08585, 37.89801],\n            [-122.086564, 37.897997],\n            [-122.087033, 37.897984],\n            [-122.08768, 37.897964],\n            [-122.08832, 37.897946],\n            [-122.088961, 37.897927],\n            [-122.089679, 37.897907],\n            [-122.090239, 37.89789],\n            [-122.09084, 37.897871],\n            [-122.091431, 37.897855],\n            [-122.091908, 37.897842],\n            [-122.092559, 37.897824],\n            [-122.093164, 37.897805],\n            [-122.093314, 37.8978],\n            [-122.093488, 37.897795],\n            [-122.093626, 37.897791],\n            [-122.094135, 37.897777],\n            [-122.094551, 37.897764],\n            [-122.095063, 37.89775],\n            [-122.095476, 37.897737],\n            [-122.095941, 37.897724],\n            [-122.09632, 37.897713],\n            [-122.096785, 37.897698],\n            [-122.097061, 37.897686],\n            [-122.097323, 37.897674],\n            [-122.09755, 37.897661],\n            [-122.097964, 37.897631],\n            [-122.098179, 37.897614],\n            [-122.098377, 37.897595],\n            [-122.098681, 37.897566],\n            [-122.099021, 37.897527],\n            [-122.099365, 37.897483],\n            [-122.099709, 37.897435],\n            [-122.10006, 37.897382],\n            [-122.10031, 37.897343],\n            [-122.100744, 37.897273],\n            [-122.101295, 37.897183],\n            [-122.101782, 37.897103],\n            [-122.102573, 37.896973],\n            [-122.103007, 37.896902],\n            [-122.103468, 37.896827],\n            [-122.104145, 37.896717],\n            [-122.104747, 37.896618],\n            [-122.105289, 37.89653],\n            [-122.105754, 37.896454],\n            [-122.106463, 37.896337],\n            [-122.106742, 37.896291],\n            [-122.107287, 37.896201],\n            [-122.107736, 37.896128],\n            [-122.108122, 37.896064],\n            [-122.108822, 37.89595],\n            [-122.109287, 37.895875],\n            [-122.110045, 37.89575],\n            [-122.110363, 37.895698],\n            [-122.110735, 37.895635],\n            [-122.111323, 37.895541],\n            [-122.111839, 37.895457],\n            [-122.112545, 37.895345],\n            [-122.112906, 37.89529],\n            [-122.113536, 37.895201],\n            [-122.113836, 37.895153],\n            [-122.115183, 37.894937],\n            [-122.116589, 37.894701],\n            [-122.117119, 37.894617],\n            [-122.117675, 37.894523],\n            [-122.118452, 37.894397],\n            [-122.119625, 37.894206],\n            [-122.120367, 37.89409],\n            [-122.120872, 37.894005],\n            [-122.121193, 37.893954],\n            [-122.121403, 37.893915],\n            [-122.121903, 37.89383],\n            [-122.122213, 37.893769],\n            [-122.122628, 37.893677],\n            [-122.122909, 37.893606],\n            [-122.123403, 37.893484],\n            [-122.124118, 37.893311],\n            [-122.12495, 37.893104],\n            [-122.125285, 37.893019],\n            [-122.125808, 37.892903],\n            [-122.126042, 37.892839],\n            [-122.126427, 37.892744],\n            [-122.127333, 37.892528],\n            [-122.127831, 37.892398],\n            [-122.128357, 37.892274],\n            [-122.128562, 37.892222],\n            [-122.128766, 37.892165],\n            [-122.129335, 37.892021],\n            [-122.129545, 37.891966],\n            [-122.129806, 37.891897],\n            [-122.130207, 37.891793],\n            [-122.130382, 37.89175],\n            [-122.131627, 37.891443],\n            [-122.132914, 37.891131],\n            [-122.133472, 37.890998],\n            [-122.133882, 37.890906],\n            [-122.134253, 37.890829],\n            [-122.134444, 37.890794],\n            [-122.13467, 37.890756],\n            [-122.134915, 37.890719],\n            [-122.135158, 37.890689],\n            [-122.135349, 37.89067],\n            [-122.135582, 37.89065],\n            [-122.135962, 37.890629],\n            [-122.136245, 37.890623],\n            [-122.136644, 37.890627],\n            [-122.137026, 37.890646],\n            [-122.137212, 37.890659],\n            [-122.137401, 37.890675],\n            [-122.13773, 37.890712],\n            [-122.137928, 37.890737],\n            [-122.138236, 37.890783],\n            [-122.138664, 37.890853],\n            [-122.139894, 37.891076],\n            [-122.140433, 37.891173],\n            [-122.141124, 37.891287],\n            [-122.141428, 37.89133],\n            [-122.141695, 37.891364],\n            [-122.142026, 37.8914],\n            [-122.142256, 37.89142],\n            [-122.142363, 37.891428],\n            [-122.142603, 37.891441],\n            [-122.142884, 37.891453],\n            [-122.143173, 37.891458],\n            [-122.143432, 37.891456],\n            [-122.143614, 37.891455],\n            [-122.143783, 37.891449],\n            [-122.144097, 37.891434],\n            [-122.14452, 37.891406],\n            [-122.145264, 37.891339],\n            [-122.145699, 37.8913],\n            [-122.146021, 37.89127],\n            [-122.146321, 37.891245],\n            [-122.146627, 37.891222],\n            [-122.146881, 37.891205],\n            [-122.147078, 37.891194],\n            [-122.147274, 37.891186],\n            [-122.147564, 37.891178],\n            [-122.147984, 37.891177],\n            [-122.148174, 37.89118],\n            [-122.148425, 37.89119],\n            [-122.148708, 37.891205],\n            [-122.149008, 37.891228],\n            [-122.149326, 37.89126],\n            [-122.149509, 37.891281],\n            [-122.149724, 37.891307],\n            [-122.149939, 37.891335],\n            [-122.150395, 37.8914],\n            [-122.150773, 37.891456],\n            [-122.152034, 37.891647],\n            [-122.153092, 37.891808],\n            [-122.153961, 37.89194],\n            [-122.15428, 37.891987],\n            [-122.154442, 37.892011],\n            [-122.154684, 37.892048],\n            [-122.156106, 37.892264],\n            [-122.156886, 37.892382],\n            [-122.157639, 37.892495],\n            [-122.158295, 37.892597],\n            [-122.158883, 37.892685],\n            [-122.159525, 37.892771],\n            [-122.160152, 37.892842],\n            [-122.160362, 37.892863],\n            [-122.160584, 37.892883],\n            [-122.160983, 37.892914],\n            [-122.161209, 37.892928],\n            [-122.161427, 37.892941],\n            [-122.161788, 37.892956],\n            [-122.16202, 37.892966],\n            [-122.162237, 37.892971],\n            [-122.162843, 37.892977],\n            [-122.163443, 37.892969],\n            [-122.163803, 37.892959],\n            [-122.163997, 37.89295],\n            [-122.164168, 37.892941],\n            [-122.164748, 37.892906],\n            [-122.164954, 37.892889],\n            [-122.165192, 37.892869],\n            [-122.165395, 37.892848],\n            [-122.165963, 37.892787],\n            [-122.166357, 37.892737],\n            [-122.16679, 37.892673],\n            [-122.167103, 37.892625],\n            [-122.167703, 37.892517],\n            [-122.168181, 37.892423],\n            [-122.168619, 37.892326],\n            [-122.169012, 37.892231],\n            [-122.169292, 37.892162],\n            [-122.169577, 37.892085],\n            [-122.170122, 37.891931],\n            [-122.170348, 37.891863],\n            [-122.170679, 37.89176],\n            [-122.170928, 37.891676],\n            [-122.171169, 37.891598],\n            [-122.171346, 37.891532],\n            [-122.171523, 37.891469],\n            [-122.17171, 37.8914],\n            [-122.17207, 37.891259],\n            [-122.17235, 37.891142],\n            [-122.172659, 37.891005],\n            [-122.172857, 37.890914],\n            [-122.172967, 37.890863],\n            [-122.173243, 37.890727],\n            [-122.173438, 37.890625],\n            [-122.173613, 37.890532],\n            [-122.173756, 37.890453],\n            [-122.173961, 37.890337],\n            [-122.174309, 37.890128],\n            [-122.17455, 37.889975],\n            [-122.174793, 37.889815],\n            [-122.175003, 37.889669],\n            [-122.17512, 37.889586],\n            [-122.175233, 37.889504],\n            [-122.175375, 37.889397],\n            [-122.175491, 37.889308],\n            [-122.175728, 37.889122],\n            [-122.175947, 37.888936],\n            [-122.176169, 37.88874],\n            [-122.176403, 37.888529],\n            [-122.176594, 37.888345],\n            [-122.176726, 37.888213],\n            [-122.177005, 37.88792],\n            [-122.177185, 37.887721],\n            [-122.177404, 37.887464],\n            [-122.17751, 37.887334],\n            [-122.177607, 37.887212],\n            [-122.177831, 37.886916],\n            [-122.177958, 37.886737],\n            [-122.178057, 37.886592],\n            [-122.178133, 37.886478],\n            [-122.178166, 37.886428],\n            [-122.178221, 37.886328],\n            [-122.178249, 37.886286],\n            [-122.178282, 37.886231],\n            [-122.178491, 37.885903],\n            [-122.178658, 37.885629],\n            [-122.178805, 37.885391],\n            [-122.178936, 37.885179],\n            [-122.179086, 37.884933],\n            [-122.179286, 37.884618],\n            [-122.179609, 37.884089],\n            [-122.179896, 37.88362],\n            [-122.180111, 37.883276],\n            [-122.180331, 37.882937],\n            [-122.18064, 37.882486],\n            [-122.180931, 37.882091],\n            [-122.18106, 37.881908],\n            [-122.181226, 37.881703],\n            [-122.181584, 37.881226],\n            [-122.181925, 37.880777],\n            [-122.182287, 37.880304],\n            [-122.18262, 37.879865],\n            [-122.183026, 37.879327],\n            [-122.18341, 37.878845],\n            [-122.183629, 37.878562],\n            [-122.183821, 37.878329],\n            [-122.184077, 37.878018],\n            [-122.184302, 37.877691],\n            [-122.184466, 37.877463],\n            [-122.184618, 37.877259],\n            [-122.184736, 37.877106],\n            [-122.184881, 37.876927],\n            [-122.185022, 37.876763],\n            [-122.185167, 37.876611],\n            [-122.185308, 37.876457],\n            [-122.18547, 37.876306],\n            [-122.185633, 37.876164],\n            [-122.185768, 37.87604],\n            [-122.185975, 37.875879],\n            [-122.186184, 37.875725],\n            [-122.186388, 37.875574],\n            [-122.186626, 37.875423],\n            [-122.187309, 37.874958],\n            [-122.187429, 37.874876],\n            [-122.187586, 37.874776],\n            [-122.187872, 37.874583],\n            [-122.18847, 37.874243],\n            [-122.189997, 37.873426],\n            [-122.193628, 37.871592],\n            [-122.194625, 37.871135],\n            [-122.196606, 37.870307],\n            [-122.200967, 37.868313],\n            [-122.202143, 37.867772],\n            [-122.202946, 37.867403],\n            [-122.203586, 37.867107],\n            [-122.204374, 37.866714],\n            [-122.205596, 37.866208],\n            [-122.206474, 37.865838],\n            [-122.207471, 37.86537],\n            [-122.208153, 37.86506],\n            [-122.208629, 37.864843],\n            [-122.209415, 37.864488],\n            [-122.209843, 37.864253],\n            [-122.21151, 37.863523],\n            [-122.213367, 37.862676],\n            [-122.214723, 37.861989],\n            [-122.216104, 37.861378],\n            [-122.217319, 37.860864],\n            [-122.217962, 37.860569],\n            [-122.218938, 37.860135],\n            [-122.219915, 37.859701],\n            [-122.221355, 37.859134],\n            [-122.238479, 37.85134],\n            [-122.239586, 37.850655],\n            [-122.240056, 37.850393],\n            [-122.24046, 37.850169],\n            [-122.240901, 37.849925],\n            [-122.241351, 37.849656],\n            [-122.241571, 37.849537],\n            [-122.241747, 37.849444],\n            [-122.241906, 37.849356],\n            [-122.242039, 37.84928],\n            [-122.242168, 37.849198],\n            [-122.24231, 37.849119],\n            [-122.242393, 37.849075],\n            [-122.24309, 37.848686],\n            [-122.244161, 37.848091],\n            [-122.244611, 37.847838],\n            [-122.245911, 37.847108],\n            [-122.246404, 37.846842],\n            [-122.24683, 37.846623],\n            [-122.247307, 37.846394],\n            [-122.247714, 37.846214],\n            [-122.248267, 37.845983],\n            [-122.248551, 37.845869],\n            [-122.250023, 37.845295],\n            [-122.251032, 37.844906],\n            [-122.25165, 37.844656],\n            [-122.251927, 37.84455],\n            [-122.252371, 37.84438],\n            [-122.252694, 37.844257],\n            [-122.253644, 37.843886],\n            [-122.254002, 37.843748],\n            [-122.254171, 37.843683],\n            [-122.254359, 37.843613],\n            [-122.254953, 37.843397],\n            [-122.255366, 37.843258],\n            [-122.25581, 37.843113],\n            [-122.256061, 37.843031],\n            [-122.256377, 37.842936],\n            [-122.256568, 37.842878],\n            [-122.256817, 37.842799],\n            [-122.257179, 37.842685],\n            [-122.257785, 37.842494],\n            [-122.258124, 37.842388],\n            [-122.258415, 37.8423],\n            [-122.261228, 37.841409],\n            [-122.261533, 37.841308],\n            [-122.26165, 37.841266],\n            [-122.261805, 37.841212],\n            [-122.262011, 37.841133],\n            [-122.262221, 37.841049],\n            [-122.262335, 37.841001],\n            [-122.262437, 37.840953],\n            [-122.262577, 37.84089],\n            [-122.262721, 37.840818],\n            [-122.262872, 37.840737],\n            [-122.263007, 37.840665],\n            [-122.263158, 37.840567],\n            [-122.263312, 37.840471],\n            [-122.263383, 37.840424],\n            [-122.263466, 37.840369],\n            [-122.26358, 37.840286],\n            [-122.263696, 37.8402],\n            [-122.263846, 37.840079],\n            [-122.263985, 37.839962],\n            [-122.264121, 37.83984],\n            [-122.264219, 37.839736],\n            [-122.264333, 37.839628],\n            [-122.264435, 37.839517],\n            [-122.264553, 37.839378],\n            [-122.264616, 37.839303],\n            [-122.2647, 37.839194],\n            [-122.264797, 37.839055],\n            [-122.264888, 37.838934],\n            [-122.264962, 37.838812],\n            [-122.265035, 37.838697],\n            [-122.265115, 37.838542],\n            [-122.265151, 37.838467],\n            [-122.265228, 37.83831],\n            [-122.265286, 37.838169],\n            [-122.265343, 37.838018],\n            [-122.265376, 37.837926],\n            [-122.265434, 37.837748],\n            [-122.265506, 37.837498],\n            [-122.265559, 37.837282],\n            [-122.265609, 37.837041],\n            [-122.265703, 37.836541],\n            [-122.265822, 37.835868],\n            [-122.265861, 37.835581],\n            [-122.265932, 37.835121],\n            [-122.265967, 37.834891],\n            [-122.26608, 37.834117],\n            [-122.266182, 37.833521],\n            [-122.266209, 37.833347],\n            [-122.266223, 37.833272]\n          ],\n          [\n            [-122.266223, 37.833272],\n            [-122.266242, 37.833187],\n            [-122.266289, 37.832956],\n            [-122.266551, 37.831691],\n            [-122.266637, 37.831245],\n            [-122.26672, 37.830865],\n            [-122.266969, 37.829645],\n            [-122.266993, 37.829541],\n            [-122.26701, 37.829461],\n            [-122.2674, 37.827569],\n            [-122.267504, 37.827066],\n            [-122.267547, 37.826888],\n            [-122.267602, 37.826666],\n            [-122.26769, 37.826358],\n            [-122.267849, 37.825838],\n            [-122.268149, 37.824951],\n            [-122.268228, 37.824702],\n            [-122.268284, 37.824541],\n            [-122.268331, 37.824388],\n            [-122.26836, 37.824292],\n            [-122.268384, 37.824222],\n            [-122.26841, 37.824145],\n            [-122.26843, 37.824084],\n            [-122.268459, 37.823999],\n            [-122.268482, 37.823931],\n            [-122.2685, 37.823877],\n            [-122.268547, 37.823729],\n            [-122.26858, 37.823624],\n            [-122.268612, 37.82349],\n            [-122.268674, 37.823233],\n            [-122.268719, 37.823044],\n            [-122.268774, 37.822819],\n            [-122.268855, 37.822479],\n            [-122.268918, 37.822252],\n            [-122.269016, 37.821886],\n            [-122.269583, 37.819828],\n            [-122.269692, 37.819431],\n            [-122.269854, 37.818831],\n            [-122.27016, 37.817787],\n            [-122.270474, 37.816811],\n            [-122.270625, 37.816364],\n            [-122.270726, 37.816043],\n            [-122.270784, 37.815843],\n            [-122.270811, 37.815732],\n            [-122.270865, 37.815487],\n            [-122.270876, 37.815432],\n            [-122.270896, 37.815301],\n            [-122.27093, 37.81504],\n            [-122.270936, 37.814932],\n            [-122.270938, 37.814854],\n            [-122.270938, 37.814854],\n            [-122.270858, 37.814403],\n            [-122.270767, 37.814035],\n            [-122.270547, 37.813539],\n            [-122.270027, 37.812683],\n            [-122.268951, 37.811691],\n            [-122.267838, 37.810589],\n            [-122.267688, 37.810365],\n            [-122.267591, 37.810093],\n            [-122.268034, 37.809454],\n            [-122.268034, 37.809454],\n            [-122.268106, 37.809342],\n            [-122.268211, 37.809176],\n            [-122.268251, 37.809113],\n            [-122.26828, 37.809066],\n            [-122.268426, 37.808835],\n            [-122.269092, 37.80776],\n            [-122.269489, 37.807113],\n            [-122.269779, 37.80664],\n            [-122.270603, 37.805288],\n            [-122.271205, 37.8043],\n            [-122.271335, 37.804087],\n            [-122.271633, 37.803634],\n            [-122.27206, 37.802936],\n            [-122.272386, 37.802362]\n          ],\n          [\n            [-122.276856, 37.798527],\n            [-122.276827, 37.798521],\n            [-122.276602, 37.798505],\n            [-122.276496, 37.798504],\n            [-122.276426, 37.798505],\n            [-122.27637, 37.798508],\n            [-122.276299, 37.798511],\n            [-122.276219, 37.798509],\n            [-122.276139, 37.798509],\n            [-122.27606, 37.798511],\n            [-122.27598, 37.798516],\n            [-122.2759, 37.798523],\n            [-122.275821, 37.798533],\n            [-122.275742, 37.798546],\n            [-122.275664, 37.798561],\n            [-122.275586, 37.798578],\n            [-122.275509, 37.798598],\n            [-122.275432, 37.79862],\n            [-122.275356, 37.798644],\n            [-122.275281, 37.798671],\n            [-122.275207, 37.798701],\n            [-122.275133, 37.798732],\n            [-122.275061, 37.798766],\n            [-122.27499, 37.798803],\n            [-122.27492, 37.798841],\n            [-122.274851, 37.798882],\n            [-122.274783, 37.798924],\n            [-122.274717, 37.798969],\n            [-122.274653, 37.799016],\n            [-122.27459, 37.799065],\n            [-122.274528, 37.799116],\n            [-122.274468, 37.799169],\n            [-122.27441, 37.799223],\n            [-122.274354, 37.79928],\n            [-122.274299, 37.799338],\n            [-122.274246, 37.799398],\n            [-122.274239, 37.799405],\n            [-122.274232, 37.799413],\n            [-122.274225, 37.799421],\n            [-122.274218, 37.799429],\n            [-122.274211, 37.799437],\n            [-122.274204, 37.799445],\n            [-122.274198, 37.799452],\n            [-122.274191, 37.79946],\n            [-122.274184, 37.799468],\n            [-122.274177, 37.799476],\n            [-122.274171, 37.799484],\n            [-122.274164, 37.799492],\n            [-122.274157, 37.7995],\n            [-122.274151, 37.799508],\n            [-122.274144, 37.799516],\n            [-122.274138, 37.799524],\n            [-122.274131, 37.799532],\n            [-122.274125, 37.799541],\n            [-122.274118, 37.799549],\n            [-122.274112, 37.799557],\n            [-122.274105, 37.799565],\n            [-122.274099, 37.799573],\n            [-122.274093, 37.799582],\n            [-122.274086, 37.79959],\n            [-122.27408, 37.799598],\n            [-122.274074, 37.799606],\n            [-122.274068, 37.799615],\n            [-122.274062, 37.799623],\n            [-122.274055, 37.799632],\n            [-122.273734, 37.800156],\n            [-122.273306, 37.800852],\n            [-122.27275, 37.80177],\n            [-122.27246, 37.802239],\n            [-122.272386, 37.802362]\n          ],\n          [\n            [-122.469106, 37.706105],\n            [-122.468903, 37.706333],\n            [-122.468809, 37.706437],\n            [-122.468678, 37.706588],\n            [-122.468434, 37.706903],\n            [-122.468029, 37.707393],\n            [-122.467923, 37.707522],\n            [-122.467832, 37.707627],\n            [-122.466801, 37.708779],\n            [-122.466732, 37.708861],\n            [-122.46667, 37.708939],\n            [-122.4666, 37.709019],\n            [-122.466526, 37.709095],\n            [-122.466449, 37.709166],\n            [-122.466376, 37.709237],\n            [-122.466284, 37.709309],\n            [-122.466215, 37.709364],\n            [-122.466126, 37.709428],\n            [-122.466028, 37.709493],\n            [-122.465941, 37.709544],\n            [-122.465846, 37.709608],\n            [-122.465752, 37.709656],\n            [-122.465664, 37.709709],\n            [-122.465569, 37.709755],\n            [-122.465466, 37.709801],\n            [-122.465351, 37.709849],\n            [-122.465245, 37.70989],\n            [-122.465148, 37.709922],\n            [-122.465035, 37.709958],\n            [-122.464925, 37.709987],\n            [-122.46481, 37.710013],\n            [-122.464685, 37.710036],\n            [-122.464576, 37.71006],\n            [-122.464464, 37.710078],\n            [-122.464349, 37.710097],\n            [-122.464248, 37.710106],\n            [-122.464125, 37.710117],\n            [-122.464, 37.710122],\n            [-122.463871, 37.710128],\n            [-122.463753, 37.710132],\n            [-122.463654, 37.710127],\n            [-122.462673, 37.710117],\n            [-122.461932, 37.710109],\n            [-122.461456, 37.710106],\n            [-122.461205, 37.710107],\n            [-122.460612, 37.710103],\n            [-122.460513, 37.710103],\n            [-122.460089, 37.7101],\n            [-122.459798, 37.710097],\n            [-122.459371, 37.710095],\n            [-122.458818, 37.710096],\n            [-122.458429, 37.710097],\n            [-122.458239, 37.710103],\n            [-122.458046, 37.710114],\n            [-122.457854, 37.710123],\n            [-122.457689, 37.710132],\n            [-122.457494, 37.710148],\n            [-122.457301, 37.710168],\n            [-122.45712, 37.710193],\n            [-122.456943, 37.71022],\n            [-122.456714, 37.710261],\n            [-122.45647, 37.710314],\n            [-122.456253, 37.710367],\n            [-122.456029, 37.710421],\n            [-122.455812, 37.710483],\n            [-122.455588, 37.710559],\n            [-122.455342, 37.710654],\n            [-122.455158, 37.710734],\n            [-122.454985, 37.71082],\n            [-122.454815, 37.710906],\n            [-122.454757, 37.710939],\n            [-122.454638, 37.711002],\n            [-122.454466, 37.711098],\n            [-122.454306, 37.711201],\n            [-122.454132, 37.711315],\n            [-122.453978, 37.711421],\n            [-122.453808, 37.711542],\n            [-122.453514, 37.711768],\n            [-122.453207, 37.712002],\n            [-122.452952, 37.712213],\n            [-122.452635, 37.712477],\n            [-122.45215, 37.712868],\n            [-122.451705, 37.713224],\n            [-122.451441, 37.713439],\n            [-122.451107, 37.713707],\n            [-122.450884, 37.713896],\n            [-122.450715, 37.714043],\n            [-122.450556, 37.714184],\n            [-122.450426, 37.714317],\n            [-122.450264, 37.714469],\n            [-122.450109, 37.714616],\n            [-122.449961, 37.714764],\n            [-122.449842, 37.714875],\n            [-122.449687, 37.715039],\n            [-122.449586, 37.715155],\n            [-122.449493, 37.715256],\n            [-122.449409, 37.715351],\n            [-122.449317, 37.71545],\n            [-122.449226, 37.715551],\n            [-122.449141, 37.715654],\n            [-122.449058, 37.71576],\n            [-122.448989, 37.715852],\n            [-122.448913, 37.715962],\n            [-122.448844, 37.716072],\n            [-122.448783, 37.716173],\n            [-122.448722, 37.716283],\n            [-122.448661, 37.716398],\n            [-122.448609, 37.716502],\n            [-122.448567, 37.716602],\n            [-122.448514, 37.716725],\n            [-122.448431, 37.716914],\n            [-122.448349, 37.717127],\n            [-122.448303, 37.717282],\n            [-122.448233, 37.71749],\n            [-122.448165, 37.717655],\n            [-122.448122, 37.717956],\n            [-122.448092, 37.718172],\n            [-122.448092, 37.718314],\n            [-122.448061, 37.718552],\n            [-122.448055, 37.718621],\n            [-122.448051, 37.718656],\n            [-122.448034, 37.718801],\n            [-122.448005, 37.719277],\n            [-122.447846, 37.720179],\n            [-122.447631, 37.721123],\n            [-122.447624, 37.721147],\n            [-122.447593, 37.721264],\n            [-122.447372, 37.722221],\n            [-122.447196, 37.723],\n            [-122.446967, 37.723855],\n            [-122.446671, 37.724539],\n            [-122.446363, 37.725104],\n            [-122.446308, 37.725188],\n            [-122.446245, 37.72529],\n            [-122.446186, 37.725395],\n            [-122.44613, 37.725479],\n            [-122.446059, 37.725571],\n            [-122.445995, 37.725663],\n            [-122.44593, 37.725755],\n            [-122.445865, 37.725839],\n            [-122.445526, 37.726234],\n            [-122.445164, 37.726612],\n            [-122.445034, 37.726729],\n            [-122.444943, 37.726806],\n            [-122.444845, 37.726909],\n            [-122.444723, 37.727013],\n            [-122.44461, 37.727109],\n            [-122.444507, 37.727194],\n            [-122.444392, 37.727285],\n            [-122.444267, 37.727381],\n            [-122.443613, 37.727828],\n            [-122.443394, 37.727959],\n            [-122.44319, 37.728081],\n            [-122.442962, 37.728209],\n            [-122.442869, 37.728256],\n            [-122.442575, 37.728409],\n            [-122.442264, 37.72856],\n            [-122.441807, 37.728764],\n            [-122.441408, 37.728925],\n            [-122.440789, 37.729147],\n            [-122.43985, 37.729516],\n            [-122.439205, 37.72978],\n            [-122.43892, 37.72988],\n            [-122.438031, 37.730328],\n            [-122.437495, 37.730633],\n            [-122.437173, 37.730829],\n            [-122.436901, 37.731003],\n            [-122.43666, 37.731183],\n            [-122.436385, 37.731366],\n            [-122.436179, 37.731508],\n            [-122.43605, 37.731595],\n            [-122.435966, 37.731656],\n            [-122.435706, 37.731839],\n            [-122.435447, 37.732004],\n            [-122.435241, 37.732136],\n            [-122.435117, 37.732222],\n            [-122.435026, 37.732295],\n            [-122.434642, 37.732542],\n            [-122.434329, 37.732752],\n            [-122.433801, 37.733121],\n            [-122.433405, 37.733368],\n            [-122.432847, 37.733721],\n            [-122.432582, 37.733918],\n            [-122.431811, 37.734415],\n            [-122.431274, 37.734758],\n            [-122.430172, 37.735479],\n            [-122.428873, 37.736323],\n            [-122.427615, 37.737145],\n            [-122.426334, 37.737984],\n            [-122.425826, 37.738339],\n            [-122.425538, 37.738547],\n            [-122.425326, 37.738713],\n            [-122.424999, 37.73895],\n            [-122.424828, 37.739072],\n            [-122.424649, 37.739215],\n            [-122.424537, 37.739296],\n            [-122.424351, 37.739428],\n            [-122.42411, 37.739622],\n            [-122.424011, 37.739738],\n            [-122.423827, 37.739869],\n            [-122.423179, 37.740563],\n            [-122.422476, 37.741588],\n            [-122.422061, 37.742264],\n            [-122.420958, 37.743961],\n            [-122.419834, 37.745675],\n            [-122.419392, 37.746361],\n            [-122.418916, 37.747089],\n            [-122.418687, 37.747518],\n            [-122.41859, 37.747722],\n            [-122.418544, 37.747813],\n            [-122.41849, 37.747912],\n            [-122.418397, 37.748166],\n            [-122.41836, 37.748248],\n            [-122.418323, 37.748339],\n            [-122.41829, 37.748442],\n            [-122.418259, 37.748552],\n            [-122.418218, 37.748731],\n            [-122.418186, 37.748868],\n            [-122.41816, 37.749093],\n            [-122.418301, 37.75055],\n            [-122.41844, 37.752086],\n            [-122.418615, 37.753786],\n            [-122.418742, 37.755411],\n            [-122.4189, 37.756821],\n            [-122.418962, 37.757461],\n            [-122.419053, 37.758554],\n            [-122.419231, 37.76021],\n            [-122.419372, 37.761849],\n            [-122.419543, 37.763429],\n            [-122.41967, 37.764723],\n            [-122.419692, 37.765024],\n            [-122.419854, 37.766637],\n            [-122.419892, 37.767184],\n            [-122.419996, 37.768204],\n            [-122.420043, 37.768923],\n            [-122.420062, 37.769113],\n            [-122.420119, 37.769442],\n            [-122.420128, 37.769528],\n            [-122.42014, 37.769621],\n            [-122.420147, 37.76977],\n            [-122.420152, 37.769886],\n            [-122.420176, 37.770065],\n            [-122.420215, 37.770362],\n            [-122.420327, 37.771558],\n            [-122.420316, 37.771965],\n            [-122.420322, 37.772277],\n            [-122.420317, 37.772531],\n            [-122.420281, 37.772812],\n            [-122.420153, 37.773345],\n            [-122.420104, 37.773593],\n            [-122.420038, 37.773762],\n            [-122.419906, 37.774022],\n            [-122.419471, 37.774777],\n            [-122.419429, 37.774913],\n            [-122.419406, 37.774983],\n            [-122.419377, 37.775045],\n            [-122.419321, 37.775113],\n            [-122.419239, 37.775191],\n            [-122.418701, 37.775596],\n            [-122.417492, 37.776574],\n            [-122.41652, 37.777348],\n            [-122.416228, 37.77756],\n            [-122.414798, 37.7787],\n            [-122.412528, 37.780489],\n            [-122.41145, 37.781351],\n            [-122.410319, 37.782234],\n            [-122.408084, 37.784002],\n            [-122.405876, 37.785769],\n            [-122.404718, 37.786653],\n            [-122.404003, 37.787215],\n            [-122.403499, 37.787631],\n            [-122.402875, 37.788109],\n            [-122.402102, 37.788723],\n            [-122.401386, 37.789267],\n            [-122.399138, 37.791042],\n            [-122.398258, 37.791728],\n            [-122.397415, 37.792407],\n            [-122.396506, 37.793169],\n            [-122.395321, 37.794057],\n            [-122.394828, 37.794362],\n            [-122.394659, 37.794469],\n            [-122.394508, 37.79455],\n            [-122.394366, 37.794631],\n            [-122.394072, 37.794782],\n            [-122.393814, 37.794904],\n            [-122.393498, 37.79506],\n            [-122.393151, 37.795219],\n            [-122.392537, 37.795517],\n            [-122.391751, 37.795841],\n            [-122.391448, 37.795976],\n            [-122.391302, 37.796041],\n            [-122.390733, 37.796294],\n            [-122.38985, 37.796687],\n            [-122.389027, 37.797053],\n            [-122.388439, 37.797315],\n            [-122.387975, 37.797522],\n            [-122.387476, 37.797743],\n            [-122.386946, 37.797979],\n            [-122.386305, 37.798265],\n            [-122.385701, 37.798534],\n            [-122.385193, 37.79876],\n            [-122.384824, 37.798924],\n            [-122.38453, 37.799055],\n            [-122.384152, 37.799223],\n            [-122.383849, 37.799358],\n            [-122.383682, 37.799432],\n            [-122.383442, 37.799539],\n            [-122.383164, 37.799663],\n            [-122.382839, 37.799808],\n            [-122.382558, 37.799933],\n            [-122.382295, 37.80005],\n            [-122.381886, 37.800232],\n            [-122.381553, 37.80038],\n            [-122.381386, 37.800454],\n            [-122.381017, 37.800618],\n            [-122.380655, 37.800779],\n            [-122.380293, 37.800941],\n            [-122.379997, 37.801072],\n            [-122.379566, 37.801264],\n            [-122.379333, 37.801367],\n            [-122.379061, 37.801484],\n            [-122.378788, 37.801599],\n            [-122.378485, 37.801723],\n            [-122.378206, 37.801835],\n            [-122.377961, 37.801929],\n            [-122.377783, 37.801997],\n            [-122.377556, 37.802081],\n            [-122.377278, 37.802182],\n            [-122.377102, 37.802244],\n            [-122.376862, 37.802327],\n            [-122.376663, 37.802394],\n            [-122.37642, 37.802474],\n            [-122.376049, 37.802592],\n            [-122.375907, 37.802636],\n            [-122.375677, 37.802705],\n            [-122.375582, 37.802734],\n            [-122.374975, 37.802906],\n            [-122.374499, 37.803034],\n            [-122.374181, 37.803113],\n            [-122.373835, 37.803197],\n            [-122.373325, 37.803313],\n            [-122.37284, 37.803416],\n            [-122.372496, 37.803485],\n            [-122.37218, 37.803544],\n            [-122.372018, 37.803573],\n            [-122.371873, 37.803599],\n            [-122.371594, 37.803647],\n            [-122.37109, 37.803733],\n            [-122.370628, 37.803811],\n            [-122.370395, 37.80385],\n            [-122.370216, 37.803881],\n            [-122.370018, 37.803914],\n            [-122.369691, 37.803969],\n            [-122.369393, 37.80402],\n            [-122.369234, 37.804047],\n            [-122.368983, 37.804089],\n            [-122.36873, 37.804132],\n            [-122.368345, 37.804197],\n            [-122.367904, 37.804272],\n            [-122.367638, 37.804317],\n            [-122.367375, 37.804361],\n            [-122.367188, 37.804393],\n            [-122.366998, 37.804425],\n            [-122.366678, 37.804479],\n            [-122.366367, 37.804532],\n            [-122.366027, 37.804589],\n            [-122.365735, 37.804639],\n            [-122.365328, 37.804708],\n            [-122.365107, 37.804745],\n            [-122.364819, 37.804794],\n            [-122.364621, 37.804827],\n            [-122.364322, 37.804878],\n            [-122.364022, 37.804929],\n            [-122.363705, 37.804982],\n            [-122.363326, 37.805046],\n            [-122.363004, 37.805101],\n            [-122.362666, 37.805158],\n            [-122.362384, 37.805206],\n            [-122.362149, 37.805246],\n            [-122.361906, 37.805287],\n            [-122.361806, 37.805303],\n            [-122.361615, 37.805336],\n            [-122.361334, 37.805383],\n            [-122.361157, 37.805413],\n            [-122.360916, 37.805454],\n            [-122.360766, 37.805479],\n            [-122.360571, 37.805512],\n            [-122.360333, 37.805553],\n            [-122.360139, 37.805586],\n            [-122.359966, 37.805615],\n            [-122.359805, 37.805642],\n            [-122.359603, 37.805676],\n            [-122.359438, 37.805704],\n            [-122.359239, 37.805738],\n            [-122.359127, 37.805757],\n            [-122.358849, 37.805804],\n            [-122.358639, 37.805839],\n            [-122.358458, 37.80587],\n            [-122.35819, 37.805915],\n            [-122.358054, 37.805938],\n            [-122.357891, 37.805966],\n            [-122.357755, 37.805989],\n            [-122.357578, 37.806019],\n            [-122.357468, 37.806037],\n            [-122.357171, 37.806087],\n            [-122.356963, 37.806122],\n            [-122.356845, 37.806143],\n            [-122.35676, 37.806157],\n            [-122.356589, 37.806186],\n            [-122.356403, 37.806217],\n            [-122.356282, 37.806238],\n            [-122.356198, 37.806252],\n            [-122.356066, 37.806274],\n            [-122.355882, 37.806305],\n            [-122.355686, 37.806339],\n            [-122.355403, 37.806386],\n            [-122.355272, 37.806408],\n            [-122.355008, 37.806453],\n            [-122.354917, 37.806468],\n            [-122.35485, 37.80648],\n            [-122.35476, 37.806495],\n            [-122.354695, 37.806506],\n            [-122.354525, 37.806535],\n            [-122.354409, 37.806554],\n            [-122.354325, 37.806569],\n            [-122.354227, 37.806585],\n            [-122.354086, 37.806609],\n            [-122.354017, 37.806621],\n            [-122.353947, 37.806633],\n            [-122.353904, 37.80664],\n            [-122.353765, 37.806663],\n            [-122.353575, 37.806695],\n            [-122.353343, 37.806735],\n            [-122.353173, 37.806763],\n            [-122.35303, 37.806788],\n            [-122.352832, 37.806821],\n            [-122.352675, 37.806848],\n            [-122.352471, 37.806882],\n            [-122.35208, 37.806948],\n            [-122.351678, 37.807016],\n            [-122.351343, 37.807073],\n            [-122.351125, 37.80711],\n            [-122.350989, 37.807133],\n            [-122.350903, 37.807147],\n            [-122.350826, 37.80716],\n            [-122.350549, 37.807207],\n            [-122.350215, 37.807264],\n            [-122.349855, 37.807325],\n            [-122.349491, 37.807386],\n            [-122.349227, 37.807431],\n            [-122.348673, 37.807524],\n            [-122.34762, 37.807702],\n            [-122.346954, 37.807817],\n            [-122.346269, 37.807931],\n            [-122.345982, 37.807981],\n            [-122.345735, 37.808021],\n            [-122.34529, 37.808096],\n            [-122.344754, 37.808186],\n            [-122.342008, 37.808651],\n            [-122.341598, 37.80872],\n            [-122.341341, 37.808763],\n            [-122.340242, 37.808948],\n            [-122.339687, 37.809041],\n            [-122.339143, 37.809133],\n            [-122.338773, 37.809194],\n            [-122.338187, 37.809285],\n            [-122.337648, 37.809363],\n            [-122.337162, 37.809429],\n            [-122.336553, 37.809505],\n            [-122.336131, 37.809552],\n            [-122.3358, 37.809588],\n            [-122.335224, 37.809645],\n            [-122.334668, 37.809694],\n            [-122.334142, 37.809735],\n            [-122.333583, 37.809772],\n            [-122.33315, 37.809797],\n            [-122.332755, 37.809816],\n            [-122.332116, 37.809842],\n            [-122.33108, 37.809866],\n            [-122.330735, 37.80987],\n            [-122.330014, 37.80987],\n            [-122.329757, 37.809867],\n            [-122.329449, 37.809862],\n            [-122.32917, 37.809857],\n            [-122.328638, 37.809842],\n            [-122.328189, 37.809825],\n            [-122.328029, 37.809818],\n            [-122.327321, 37.809778],\n            [-122.326497, 37.809718],\n            [-122.326137, 37.809687],\n            [-122.325817, 37.809649],\n            [-122.323554, 37.809392],\n            [-122.322814, 37.809368],\n            [-122.322059, 37.809327],\n            [-122.320701, 37.80925],\n            [-122.320136, 37.80922],\n            [-122.31951, 37.809162],\n            [-122.318885, 37.809106],\n            [-122.316062, 37.808815],\n            [-122.315696, 37.808781],\n            [-122.315482, 37.808758],\n            [-122.315185, 37.808731],\n            [-122.314429, 37.808664],\n            [-122.312583, 37.808473],\n            [-122.312057, 37.808422],\n            [-122.308982, 37.808129],\n            [-122.308486, 37.808084],\n            [-122.308295, 37.808064],\n            [-122.306411, 37.807806],\n            [-122.305541, 37.807685],\n            [-122.305182, 37.807598],\n            [-122.30506, 37.807577],\n            [-122.304526, 37.807461],\n            [-122.30445, 37.807442],\n            [-122.304175, 37.807385],\n            [-122.303977, 37.807343],\n            [-122.303794, 37.807301],\n            [-122.302436, 37.806996],\n            [-122.302398, 37.806989],\n            [-122.298135, 37.806031],\n            [-122.297836, 37.805959],\n            [-122.297584, 37.805879],\n            [-122.297324, 37.805794],\n            [-122.297073, 37.805706],\n            [-122.296836, 37.805615],\n            [-122.296599, 37.805523],\n            [-122.296356, 37.805421],\n            [-122.296134, 37.805318],\n            [-122.296096, 37.805302],\n            [-122.295089, 37.804897],\n            [-122.293883, 37.80441],\n            [-122.29341, 37.804204],\n            [-122.292579, 37.803875],\n            [-122.292563, 37.803872],\n            [-122.29206, 37.803669],\n            [-122.291923, 37.803616],\n            [-122.291747, 37.803548],\n            [-122.291663, 37.803513],\n            [-122.291564, 37.803468],\n            [-122.291335, 37.803381],\n            [-122.29132, 37.803376],\n            [-122.291312, 37.803371],\n            [-122.291297, 37.803365],\n            [-122.291137, 37.803291],\n            [-122.290946, 37.803223],\n            [-122.284796, 37.801721],\n            [-122.284606, 37.801668],\n            [-122.284431, 37.801611],\n            [-122.284095, 37.801496],\n            [-122.283744, 37.801381],\n            [-122.283645, 37.801347],\n            [-122.283523, 37.801296],\n            [-122.283386, 37.801237],\n            [-122.283157, 37.801141],\n            [-122.282943, 37.801045],\n            [-122.282546, 37.800853],\n            [-122.282294, 37.800744],\n            [-122.282272, 37.800732],\n            [-122.28218, 37.800693],\n            [-122.281188, 37.800214],\n            [-122.280051, 37.799664],\n            [-122.280044, 37.79966],\n            [-122.279952, 37.799607],\n            [-122.279907, 37.799584],\n            [-122.279716, 37.799493],\n            [-122.279471, 37.799368],\n            [-122.279183, 37.799217],\n            [-122.278686, 37.798956],\n            [-122.278478, 37.798859],\n            [-122.278369, 37.798811],\n            [-122.278197, 37.798746],\n            [-122.278051, 37.798694],\n            [-122.277819, 37.798637],\n            [-122.277697, 37.798614],\n            [-122.277507, 37.798583],\n            [-122.277358, 37.798568],\n            [-122.276984, 37.798532],\n            [-122.276923, 37.798528],\n            [-122.276856, 37.798527]\n          ],\n          [\n            [-122.466727, 37.684953],\n            [-122.467276, 37.685356],\n            [-122.467791, 37.685769],\n            [-122.468086, 37.686119],\n            [-122.468184, 37.686287],\n            [-122.468285, 37.686457],\n            [-122.468473, 37.68675],\n            [-122.468667, 37.687103],\n            [-122.468809, 37.687399],\n            [-122.468959, 37.687724],\n            [-122.469114, 37.688025],\n            [-122.469228, 37.688263],\n            [-122.469306, 37.688412],\n            [-122.469413, 37.688604],\n            [-122.469451, 37.68868],\n            [-122.469482, 37.688755],\n            [-122.46955, 37.688926],\n            [-122.469611, 37.689098],\n            [-122.469665, 37.689285],\n            [-122.46971, 37.689477],\n            [-122.469749, 37.68968],\n            [-122.469779, 37.689901],\n            [-122.469795, 37.690102],\n            [-122.46981, 37.690338],\n            [-122.469808, 37.690623],\n            [-122.469825, 37.690832],\n            [-122.469818, 37.691068],\n            [-122.46982, 37.691295],\n            [-122.469832, 37.69178],\n            [-122.469833, 37.691812],\n            [-122.46984, 37.691968],\n            [-122.469855, 37.692544],\n            [-122.469851, 37.693124],\n            [-122.469835, 37.693391],\n            [-122.469838, 37.69367],\n            [-122.46984, 37.693948],\n            [-122.469848, 37.694165],\n            [-122.469856, 37.694261],\n            [-122.469871, 37.694482],\n            [-122.469894, 37.694687],\n            [-122.469944, 37.694924],\n            [-122.469987, 37.695075],\n            [-122.470029, 37.695191],\n            [-122.470073, 37.695321],\n            [-122.470334, 37.696002],\n            [-122.470462, 37.696332],\n            [-122.470493, 37.696415],\n            [-122.470511, 37.696473],\n            [-122.470563, 37.696611],\n            [-122.470682, 37.696913],\n            [-122.470741, 37.697087],\n            [-122.470871, 37.697466],\n            [-122.470884, 37.697511],\n            [-122.470916, 37.697629],\n            [-122.470942, 37.697747],\n            [-122.470957, 37.697838],\n            [-122.470971, 37.697921],\n            [-122.47098, 37.698007],\n            [-122.470996, 37.698132],\n            [-122.471008, 37.698336],\n            [-122.47101, 37.698393],\n            [-122.471006, 37.69852],\n            [-122.471002, 37.698636],\n            [-122.470998, 37.699043],\n            [-122.471013, 37.699549],\n            [-122.471023, 37.700169],\n            [-122.471041, 37.700673],\n            [-122.471052, 37.7012],\n            [-122.47106, 37.701706],\n            [-122.471064, 37.701856],\n            [-122.471063, 37.701909],\n            [-122.471067, 37.702173],\n            [-122.471074, 37.702402],\n            [-122.47108, 37.702639],\n            [-122.471085, 37.70278],\n            [-122.471081, 37.703002],\n            [-122.471075, 37.703124],\n            [-122.471063, 37.703206],\n            [-122.471046, 37.703305],\n            [-122.471016, 37.703421],\n            [-122.470989, 37.703498],\n            [-122.470959, 37.703591],\n            [-122.470914, 37.703706],\n            [-122.470846, 37.703849],\n            [-122.470808, 37.703916],\n            [-122.470779, 37.703972],\n            [-122.470713, 37.704079],\n            [-122.470583, 37.704263],\n            [-122.470461, 37.704413],\n            [-122.4703, 37.704608],\n            [-122.470082, 37.704878],\n            [-122.469934, 37.705059],\n            [-122.469864, 37.705149],\n            [-122.46976, 37.705276],\n            [-122.469524, 37.705566],\n            [-122.469423, 37.705687],\n            [-122.469351, 37.705767],\n            [-122.469106, 37.706105]\n          ],\n          [\n            [-122.386655, 37.599782],\n            [-122.386721, 37.599826],\n            [-122.386987, 37.600082],\n            [-122.387483, 37.600584],\n            [-122.387696, 37.600796],\n            [-122.388117, 37.601234],\n            [-122.388787, 37.601899],\n            [-122.389715, 37.602821],\n            [-122.390234, 37.603307],\n            [-122.390656, 37.603728],\n            [-122.391122, 37.604193],\n            [-122.391435, 37.6045],\n            [-122.392204, 37.605233],\n            [-122.392459, 37.605484],\n            [-122.392981, 37.606019],\n            [-122.393405, 37.606423],\n            [-122.393745, 37.606752],\n            [-122.394104, 37.607121],\n            [-122.394396, 37.607411],\n            [-122.395019, 37.607982],\n            [-122.395445, 37.608416],\n            [-122.395726, 37.608689],\n            [-122.396003, 37.608957],\n            [-122.396505, 37.609462],\n            [-122.396853, 37.609811],\n            [-122.397283, 37.610218],\n            [-122.397707, 37.610639],\n            [-122.397815, 37.610748],\n            [-122.398332, 37.611257],\n            [-122.398737, 37.611677],\n            [-122.398866, 37.611829],\n            [-122.399232, 37.612239],\n            [-122.399469, 37.612547],\n            [-122.399673, 37.612791],\n            [-122.399816, 37.612964],\n            [-122.400005, 37.613219],\n            [-122.40019, 37.613484],\n            [-122.400655, 37.614099],\n            [-122.400809, 37.614317],\n            [-122.401288, 37.614932],\n            [-122.401645, 37.61541],\n            [-122.402026, 37.615909],\n            [-122.402329, 37.61632],\n            [-122.402734, 37.616842],\n            [-122.403059, 37.617274],\n            [-122.403159, 37.617412],\n            [-122.403482, 37.617826],\n            [-122.404185, 37.618738],\n            [-122.404687, 37.619429],\n            [-122.404838, 37.619635],\n            [-122.404945, 37.619781],\n            [-122.405191, 37.620098],\n            [-122.405634, 37.620702],\n            [-122.405913, 37.621072],\n            [-122.406123, 37.621365],\n            [-122.406399, 37.621748],\n            [-122.40655, 37.621948],\n            [-122.406647, 37.622075],\n            [-122.406818, 37.622328],\n            [-122.407048, 37.622664],\n            [-122.407201, 37.622907],\n            [-122.407346, 37.623149],\n            [-122.40764, 37.623538],\n            [-122.407808, 37.623749],\n            [-122.408078, 37.624106],\n            [-122.408262, 37.624418],\n            [-122.40848, 37.624752],\n            [-122.40868, 37.625073],\n            [-122.408929, 37.625483],\n            [-122.409273, 37.626104],\n            [-122.40947, 37.626419],\n            [-122.409804, 37.627],\n            [-122.410307, 37.627847],\n            [-122.410538, 37.628272],\n            [-122.411056, 37.629165],\n            [-122.41148, 37.62992],\n            [-122.411672, 37.630225],\n            [-122.411909, 37.630664],\n            [-122.412351, 37.631448],\n            [-122.412497, 37.631688],\n            [-122.41268, 37.631993],\n            [-122.412775, 37.632167],\n            [-122.412868, 37.632301],\n            [-122.413103, 37.632709],\n            [-122.41348, 37.633364],\n            [-122.41365, 37.633687],\n            [-122.413836, 37.633927],\n            [-122.414039, 37.634292],\n            [-122.414355, 37.634856],\n            [-122.414708, 37.635469],\n            [-122.415165, 37.636273],\n            [-122.41549, 37.636822],\n            [-122.415885, 37.637521],\n            [-122.416411, 37.638367],\n            [-122.416686, 37.638913],\n            [-122.416983, 37.639438],\n            [-122.417237, 37.639874],\n            [-122.417473, 37.64031],\n            [-122.417764, 37.640799],\n            [-122.418178, 37.641489],\n            [-122.418474, 37.641967],\n            [-122.418611, 37.642212],\n            [-122.419044, 37.642799],\n            [-122.419639, 37.643561],\n            [-122.420638, 37.644768],\n            [-122.421338, 37.645655],\n            [-122.421632, 37.645985],\n            [-122.421809, 37.646193],\n            [-122.422173, 37.646622],\n            [-122.42266, 37.64716],\n            [-122.422992, 37.647552],\n            [-122.423255, 37.647852],\n            [-122.424112, 37.648831],\n            [-122.424499, 37.649253],\n            [-122.424816, 37.649632],\n            [-122.425071, 37.649901],\n            [-122.425651, 37.650544],\n            [-122.426269, 37.651204],\n            [-122.426586, 37.651474],\n            [-122.42674, 37.651596],\n            [-122.427119, 37.651804],\n            [-122.427977, 37.652324],\n            [-122.42868, 37.652758],\n            [-122.42953, 37.653266],\n            [-122.430249, 37.653645],\n            [-122.431587, 37.654355],\n            [-122.43297, 37.655126],\n            [-122.433503, 37.655414],\n            [-122.434075, 37.655707],\n            [-122.434354, 37.65586],\n            [-122.435243, 37.656491],\n            [-122.436224, 37.657237],\n            [-122.436487, 37.657488],\n            [-122.436819, 37.657818],\n            [-122.43712, 37.658112],\n            [-122.437662, 37.658669],\n            [-122.438148, 37.659299],\n            [-122.438396, 37.659623],\n            [-122.438844, 37.660186],\n            [-122.439284, 37.660718],\n            [-122.440065, 37.661422],\n            [-122.440281, 37.661599],\n            [-122.440451, 37.661752],\n            [-122.440707, 37.661942],\n            [-122.441224, 37.662279],\n            [-122.441766, 37.662676],\n            [-122.442384, 37.663116],\n            [-122.442964, 37.663502],\n            [-122.443829, 37.66401],\n            [-122.444363, 37.664328],\n            [-122.444698, 37.66455],\n            [-122.44533, 37.664973],\n            [-122.446086, 37.665483],\n            [-122.446569, 37.66579],\n            [-122.446723, 37.665896],\n            [-122.447202, 37.666243],\n            [-122.447444, 37.666429],\n            [-122.447734, 37.666663],\n            [-122.448252, 37.667151],\n            [-122.448492, 37.667392],\n            [-122.448971, 37.667918],\n            [-122.449348, 37.668414],\n            [-122.449595, 37.668741],\n            [-122.450034, 37.669376],\n            [-122.450358, 37.669848],\n            [-122.450805, 37.670495],\n            [-122.451185, 37.67117],\n            [-122.45139, 37.6715],\n            [-122.451614, 37.671864],\n            [-122.45178, 37.672032],\n            [-122.452161, 37.672407],\n            [-122.452625, 37.67286],\n            [-122.453015, 37.673251],\n            [-122.453539, 37.673757],\n            [-122.453718, 37.673939],\n            [-122.454356, 37.674569],\n            [-122.454764, 37.674973],\n            [-122.455485, 37.675425],\n            [-122.456053, 37.675782],\n            [-122.456609, 37.676257],\n            [-122.456889, 37.676488],\n            [-122.457171, 37.676727],\n            [-122.457542, 37.677108],\n            [-122.457737, 37.677313],\n            [-122.457903, 37.677484],\n            [-122.458269, 37.67786],\n            [-122.458418, 37.678013],\n            [-122.458949, 37.678518],\n            [-122.459194, 37.678758],\n            [-122.459724, 37.679272],\n            [-122.460147, 37.679686],\n            [-122.460462, 37.679992],\n            [-122.460925, 37.680441],\n            [-122.461428, 37.680942],\n            [-122.462044, 37.681564],\n            [-122.462282, 37.681772],\n            [-122.462617, 37.682144],\n            [-122.463067, 37.682468],\n            [-122.463327, 37.68265],\n            [-122.463441, 37.682734],\n            [-122.463563, 37.682817],\n            [-122.463861, 37.683022],\n            [-122.464151, 37.683224],\n            [-122.465059, 37.683883],\n            [-122.465089, 37.683902],\n            [-122.465768, 37.684349],\n            [-122.466727, 37.684953]\n          ],\n          [\n            [-122.392604, 37.616029],\n            [-122.39316, 37.615899],\n            [-122.394211, 37.615653],\n            [-122.394879, 37.615632],\n            [-122.395393, 37.615625],\n            [-122.395757, 37.615619],\n            [-122.396337, 37.615623],\n            [-122.396751, 37.615627],\n            [-122.397311, 37.615639],\n            [-122.397686, 37.615645],\n            [-122.398004, 37.61564],\n            [-122.398469, 37.615641],\n            [-122.398924, 37.61566],\n            [-122.399647, 37.615699],\n            [-122.40019, 37.615773],\n            [-122.400629, 37.615881],\n            [-122.400993, 37.616]\n          ],\n          [\n            [-122.394208, 37.615656],\n            [-122.394307, 37.615647],\n            [-122.39487, 37.615607],\n            [-122.3957, 37.615596],\n            [-122.396902, 37.615596],\n            [-122.397654, 37.615572],\n            [-122.39827, 37.615514],\n            [-122.398487, 37.615453],\n            [-122.398597, 37.615423],\n            [-122.398814, 37.615336],\n            [-122.398959, 37.615264],\n            [-122.39913, 37.615157],\n            [-122.399239, 37.615086],\n            [-122.399404, 37.61495],\n            [-122.399567, 37.614761],\n            [-122.399671, 37.614614],\n            [-122.399722, 37.614507],\n            [-122.399838, 37.614246],\n            [-122.39987, 37.614087],\n            [-122.399876, 37.613952],\n            [-122.399879, 37.613814],\n            [-122.399851, 37.613635],\n            [-122.399811, 37.613464],\n            [-122.399774, 37.613352],\n            [-122.399698, 37.613176],\n            [-122.399612, 37.613027],\n            [-122.399534, 37.612906],\n            [-122.399232, 37.612239]\n          ]\n        ]\n      }\n    }\n  ]\n};\n"
  },
  {
    "path": "examples/demo-app/src/data/sample-trip-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Sample Taxi Trip Data\nexport const testCsvData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date,<img>_tooltip,website\n2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:14:00,29.9923041,31.2154899,driver_analytics,1472688000000,False,5,2016-09-23T00:00:00.000Z,2016-10-01 09:47:37+00:00,2016-10-01 16:47:37+00:00,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:15:01,29.9968249,31.2149361,driver_analytics,1472688000000,False,12124,2016-09-23T05:00:00.000Z,,,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:16:03,30.0037217,31.2164035,driver_analytics,1472688000000,False,222,2016-09-23T05:00:00.000Z,,,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:17:05,30.0116207,31.2179346,driver_analytics,1472688000000,False,345,2016-09-23T00:00:00.000Z,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:18:09,30.0208925,31.2179556,driver_analytics,1472708000000,False,,2016-09-23T00:00:00.000Z,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:19:12,30.0218999,31.2178842,driver_analytics,1472708000000,False,,2016-09-23T06:00:00.000Z,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:19:27,30.0229344,31.2179138,driver_analytics,1472708000000,False,,2016-09-23T05:00:00.000Z,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:20:14,30.0264237,31.2179415,driver_analytics,1472708000000,False,,,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:21:17,30.0292134,31.2181809,driver_analytics,1472754400000,False,,,,,2016-09-24,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:22:20,30.034391,31.2193991,driver_analytics,1472754400000,,,2016-09-23T06:00:00.000Z,,,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:23:22,30.0352752,31.2181803,driver_analytics,1472754400000,,,2016-09-23T06:00:00.000Z,2016-10-01 10:01:54+00:00,2016-10-01 17:01:54+00:00,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:24:24,30.0395918,31.2195902,driver_analytics,1472754400000,,1,2016-09-23T00:00:00.000Z,2016-10-01 09:53:04+00:00,2016-10-01 16:53:04+00:00,,https://user-images.githubusercontent.com/3605556/66859873-f2aed400-ef40-11e9-8a41-d07de7560376.png,http://kepler.gl\n2016-09-17 00:25:28,30.0497387,31.2174421,driver_analytics,1472774400000,,,2016-09-23T07:00:00.000Z,2016-10-01 09:55:23+00:00,2016-10-01 16:55:23+00:00,,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:26:29,30.0538936,31.2165983,driver_analytics,1472774400000,,43,2016-09-23T07:00:00.000Z,2016-10-01 09:59:53+00:00,2016-10-01 16:59:53+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:27:31,30.060911,31.2148748,driver_analytics,1472774400000,,4,2016-09-23T07:00:00.000Z,2016-10-01 09:57:11+00:00,2016-10-01 16:57:11+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:28:35,30.060334,31.2212278,driver_analytics,1472774400000,,5,2016-09-23T07:00:00.000Z,2016-10-01 09:59:27+00:00,2016-10-01 16:59:27+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:29:40,30.0554663,31.2288985,driver_analytics,1472774400000,True,,2016-09-23T07:00:00.000Z,2016-10-01 09:46:36+00:00,2016-10-01 16:46:36+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:30:03,30.0614122,31.2187021,driver_gps,1472774400000,True,6,2016-09-23T08:00:00.000Z,2016-10-01 09:54:31+00:00,2016-10-01 16:54:31+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:30:03,30.0612697,31.2191059,driver_gps,1472774400000,True,7,2016-09-23T08:00:00.000Z,2016-10-01 09:53:35+00:00,2016-10-01 16:53:35+00:00,2016-10-10,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n2016-09-17 00:30:08,30.0610977,31.2194728,driver_gps,1472774400000,True,,2016-09-23T08:00:00.000Z,,,,https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/icon-demo-map.jpg,http://kepler.gl\n`;\n\nexport default {\n  fields: [\n    {\n      name: 'tpep_pickup_datetime',\n      format: 'YYYY-M-D H:m:s',\n      type: 'timestamp'\n    },\n    {\n      name: 'tpep_dropoff_datetime',\n      format: 'YYYY-M-D H:m:s',\n      type: 'timestamp'\n    },\n    {\n      name: 'passenger_count',\n      format: '',\n      type: 'integer'\n    },\n    {name: 'trip_distance', format: '', type: 'real'},\n    {name: 'pickup_longitude', format: '', type: 'real'},\n    {name: 'pickup_latitude', format: '', type: 'real'},\n    {name: 'dropoff_longitude', format: '', type: 'real'},\n    {name: 'dropoff_latitude', format: '', type: 'real'},\n    {name: 'fare_amount', format: '', type: 'real'},\n    {name: 'is_completed', format: '', type: 'boolean'},\n    {name: 'fare_type', format: '', type: 'string'}\n  ],\n  rows: [\n    [\n      null,\n      '2015-01-15 19:23:42 +00:00',\n      null,\n      1.59,\n      -73.99389648,\n      40.75011063,\n      -73.97478485,\n      40.75061798,\n      12,\n      true,\n      'orange peel 0'\n    ],\n    [\n      null,\n      '2015-01-15 19:32:00 +00:00',\n      -3,\n      2.38,\n      -73.97642517,\n      40.73981094,\n      -73.98397827,\n      40.75788879,\n      16.5,\n      false,\n      'orange peel 0'\n    ],\n    [\n      '2015-01-15 19:05:40 +00:00',\n      '2015-01-15 19:21:00 +00:00',\n      5,\n      2.83,\n      -73.96870422,\n      40.75424576,\n      -73.9551239,\n      40.7868576,\n      12.5,\n      false,\n      'apple tree'\n    ],\n    [\n      null,\n      '2015-01-15 19:28:18 +00:00',\n      5,\n      8.33,\n      -73.86306,\n      40.76958084,\n      -73.95271301,\n      40.78578186,\n      26,\n      true,\n      'orange peel 0'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:20:36 +00:00',\n      1,\n      2.37,\n      -73.94554138,\n      40.77942276,\n      -73.98085022,\n      40.78608322,\n      11.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:20:22 +00:00',\n      2,\n      7.13,\n      -73.87445831,\n      40.7740097,\n      -73.95237732,\n      40.71858978,\n      21.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:31:00 +00:00',\n      0,\n      3.6,\n      -73.97660065,\n      40.7518959,\n      -73.99892426,\n      40.71459579,\n      17.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:10:22 +00:00',\n      1,\n      0.89,\n      -73.99495697,\n      40.74507904,\n      -73.99993896,\n      40.73464966,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:10:55 +00:00',\n      1,\n      0.96,\n      -74.00093842,\n      40.74706268,\n      -74.00356293,\n      40.73551178,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:12:36 +00:00',\n      2,\n      1.25,\n      -74.0027771,\n      40.71789169,\n      -74.00791931,\n      40.70421982,\n      6.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:22:11 +00:00',\n      5,\n      2.11,\n      -73.99745941,\n      40.73636246,\n      -73.9781723,\n      40.76185608,\n      11.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:41 +00:00',\n      '2015-01-15 19:14:05 +00:00',\n      5,\n      1.15,\n      -73.95227814,\n      40.82399368,\n      -73.95333862,\n      40.81108856,\n      7.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 19:05:42 +00:00',\n      '2015-01-15 19:16:18 +00:00',\n      0,\n      1.53,\n      -73.99112701,\n      40.75008011,\n      -73.98860931,\n      40.73488998,\n      9,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:42 +00:00',\n      '2015-01-15 19:49:07 +00:00',\n      1,\n      18.06,\n      -73.78657532,\n      40.64412689,\n      -73.98560333,\n      40.74353027,\n      52,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 19:05:42 +00:00',\n      '2015-01-15 19:18:33 +00:00',\n      1,\n      1.76,\n      -73.9936676,\n      40.74144745,\n      -73.99451447,\n      40.75772095,\n      10,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 19:05:42 +00:00',\n      '2015-01-15 19:21:40 +00:00',\n      6,\n      5.19,\n      -73.98529053,\n      40.7440834,\n      -74.00907898,\n      40.70468903,\n      17.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 19:05:42 +00:00',\n      '2015-01-15 19:14:23 +00:00',\n      1,\n      1.38,\n      -73.96977234,\n      40.7856102,\n      -73.95070648,\n      40.77468109,\n      7.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:43 +00:00',\n      '2015-01-15 19:17:43 +00:00',\n      -3,\n      2.23,\n      -73.98509216,\n      40.75698853,\n      -73.98487854,\n      40.78015137,\n      10,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 19:05:43 +00:00',\n      '2015-01-15 19:15:17 +00:00',\n      1,\n      1.56,\n      -73.96616364,\n      40.76216125,\n      -73.95005035,\n      40.77625275,\n      8,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 19:05:43 +00:00',\n      '2015-01-15 19:15:09 +00:00',\n      5,\n      1.57,\n      -73.97842407,\n      40.74620819,\n      -73.98021698,\n      40.72854233,\n      8,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 19:05:44 +00:00',\n      '2015-01-15 19:17:44 +00:00',\n      1,\n      2.92,\n      -73.99835205,\n      40.72003174,\n      -73.95582581,\n      40.71559906,\n      12.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:00:41 +00:00',\n      '2015-01-15 14:11:18 +00:00',\n      1,\n      1.64,\n      -73.9837265,\n      40.74634171,\n      -73.96679688,\n      40.76140594,\n      8.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:41 +00:00',\n      '2015-01-15 14:19:26 +00:00',\n      1,\n      1.53,\n      -73.99578094,\n      40.73294067,\n      -73.99107361,\n      40.75038147,\n      12,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:00:41 +00:00',\n      '2015-01-15 14:31:42 +00:00',\n      -5,\n      7.48,\n      -73.87081909,\n      40.77370071,\n      -73.96869659,\n      40.76268005,\n      27,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:41 +00:00',\n      '2015-01-15 14:13:48 +00:00',\n      1,\n      3,\n      -73.96427155,\n      40.7730217,\n      -73.96577454,\n      40.80466843,\n      12,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:42 +00:00',\n      '2015-01-15 14:05:01 +00:00',\n      1,\n      0.67,\n      -73.97093201,\n      40.79592133,\n      -73.97016907,\n      40.78912354,\n      5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:00:42 +00:00',\n      '2015-01-15 14:20:40 +00:00',\n      1,\n      1.51,\n      -73.97212219,\n      40.75902939,\n      -73.99093628,\n      40.7557373,\n      13,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:42 +00:00',\n      '2015-01-15 14:05:16 +00:00',\n      2,\n      0.85,\n      -73.98056793,\n      40.7656517,\n      -73.97304535,\n      40.75827026,\n      5.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:43 +00:00',\n      '2015-01-15 14:58:40 +00:00',\n      -1,\n      15.2,\n      -73.8012619,\n      40.66772842,\n      -73.9933548,\n      40.75600052,\n      52,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:43 +00:00',\n      '2015-01-15 14:41:50 +00:00',\n      3,\n      9.96,\n      -73.99095917,\n      40.74534225,\n      -73.97360229,\n      40.63248062,\n      35.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:00:44 +00:00',\n      '2015-01-15 14:04:55 +00:00',\n      1,\n      0.76,\n      -73.96351624,\n      40.77156067,\n      -73.95298767,\n      40.77246094,\n      5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:44 +00:00',\n      '2015-01-15 14:04:06 +00:00',\n      1,\n      0.38,\n      -73.99138641,\n      40.7701683,\n      -73.98978424,\n      40.77473831,\n      4,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:45 +00:00',\n      '2015-01-15 14:05:40 +00:00',\n      2,\n      0.63,\n      -73.95426941,\n      40.77827072,\n      -73.95627594,\n      40.78404617,\n      5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:45 +00:00',\n      '2015-01-15 14:39:09 +00:00',\n      1,\n      12.63,\n      -73.78852844,\n      40.64778519,\n      -73.97788239,\n      40.68964005,\n      40,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:45 +00:00',\n      '2015-01-15 14:10:24 +00:00',\n      null,\n      1.2,\n      -73.9940033,\n      40.7513504,\n      -73.98135376,\n      40.76112366,\n      8.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:00:45 +00:00',\n      '2015-01-15 14:16:31 +00:00',\n      2,\n      1.18,\n      -74.00641632,\n      40.73965454,\n      -73.98991394,\n      40.73839569,\n      10.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:00:45 +00:00',\n      '2015-01-15 14:47:12 +00:00',\n      1,\n      10.89,\n      -74.00640869,\n      40.73976517,\n      -73.87119293,\n      40.77421188,\n      40.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:46 +00:00',\n      '2015-01-15 14:41:54 +00:00',\n      6,\n      10.39,\n      -73.9930954,\n      40.74725723,\n      -73.86483002,\n      40.77032089,\n      38,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:46 +00:00',\n      '2015-01-15 14:33:02 +00:00',\n      5,\n      2.96,\n      -73.96273041,\n      40.77416992,\n      -73.99333191,\n      40.7498703,\n      20.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:46 +00:00',\n      '2015-01-15 14:10:09 +00:00',\n      1,\n      1.18,\n      -73.9825592,\n      40.77320099,\n      -73.96412659,\n      40.76470947,\n      8,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:00:46 +00:00',\n      '2015-01-15 14:14:51 +00:00',\n      1,\n      1.62,\n      -73.97618103,\n      40.75120544,\n      -73.9863739,\n      40.7665863,\n      10.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:00:47 +00:00',\n      '2015-01-15 14:16:01 +00:00',\n      null,\n      1.56,\n      -73.95927429,\n      40.77437592,\n      -73.97639465,\n      40.76293945,\n      10.5,\n      true,\n      'banana peel 2'\n    ],\n    [\n      '2015-01-15 14:00:47 +00:00',\n      '2015-01-15 14:23:36 +00:00',\n      5,\n      4.96,\n      -73.97286224,\n      40.74352264,\n      -73.91412354,\n      40.76496887,\n      19,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:13 +00:00',\n      '2015-01-15 10:30:49 +00:00',\n      1,\n      0.8,\n      -73.97223663,\n      40.74559402,\n      -73.96466827,\n      40.75601959,\n      5.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:14 +00:00',\n      '2015-01-15 10:42:16 +00:00',\n      -1,\n      1.9,\n      -73.94948578,\n      40.77207184,\n      -73.97072601,\n      40.75839233,\n      12.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:26:14 +00:00',\n      '2015-01-15 10:51:03 +00:00',\n      1,\n      2.6,\n      -73.96176147,\n      40.76427078,\n      -73.98921967,\n      40.74206543,\n      16,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:26:14 +00:00',\n      '2015-01-15 10:38:13 +00:00',\n      1,\n      0.8,\n      -73.99343872,\n      40.74200058,\n      -73.98744965,\n      40.75202179,\n      8.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:26:14 +00:00',\n      '2015-01-15 10:49:00 +00:00',\n      null,\n      6.8,\n      -73.97297668,\n      40.7611351,\n      -74.00971222,\n      40.70956421,\n      23,\n      true,\n      'banana peel 2'\n    ],\n    [\n      '2015-01-15 10:26:15 +00:00',\n      '2015-01-15 10:34:40 +00:00',\n      1,\n      1.3,\n      -73.94763947,\n      40.77514267,\n      -73.96069336,\n      40.76080704,\n      7.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:26:15 +00:00',\n      '2015-01-15 10:35:33 +00:00',\n      1,\n      0.7,\n      -73.98516846,\n      40.75121307,\n      -73.97951508,\n      40.7618866,\n      7.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:26:15 +00:00',\n      '2015-01-15 10:33:37 +00:00',\n      1,\n      0.3,\n      -73.98387146,\n      40.74245453,\n      -73.98730469,\n      40.74406052,\n      6,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:26:15 +00:00',\n      '2015-01-15 10:36:03 +00:00',\n      1,\n      1.4,\n      -73.96815491,\n      40.76235962,\n      -73.98164368,\n      40.74707031,\n      8,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:26:16 +00:00',\n      '2015-01-15 10:32:04 +00:00',\n      1,\n      1.2,\n      -74.00554657,\n      40.72677612,\n      -74.01348114,\n      40.71365738,\n      6.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:16 +00:00',\n      '2015-01-15 11:08:10 +00:00',\n      2,\n      12.6,\n      -73.86273956,\n      40.7688446,\n      -73.98956299,\n      40.75883865,\n      40,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:26:16 +00:00',\n      '2015-01-15 10:26:42 +00:00',\n      2,\n      0,\n      -73.98765564,\n      40.74364471,\n      -73.98748779,\n      40.7434845,\n      60,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:17 +00:00',\n      '2015-01-15 10:27:28 +00:00',\n      1,\n      0,\n      -73.94652557,\n      40.7449913,\n      -73.94652557,\n      40.7449913,\n      3,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 09:46:59 +00:00',\n      '2015-01-15 09:57:44 +00:00',\n      1,\n      3.1,\n      -74.01138306,\n      40.71348953,\n      -73.99554443,\n      40.74922943,\n      11.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:46:59 +00:00',\n      '2015-01-15 09:56:44 +00:00',\n      1,\n      0.8,\n      -73.99701691,\n      40.74232101,\n      -73.98770142,\n      40.73540497,\n      7.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:46:59 +00:00',\n      '2015-01-15 10:27:34 +00:00',\n      1,\n      9.5,\n      -73.87297058,\n      40.77403259,\n      -74.00010681,\n      40.71933746,\n      34,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 09:47:00 +00:00',\n      '2015-01-15 09:50:43 +00:00',\n      1,\n      0.4,\n      -74.00221252,\n      40.72949219,\n      -73.9969101,\n      40.72514343,\n      4.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:02 +00:00',\n      '2015-01-15 10:01:48 +00:00',\n      1,\n      1.1,\n      -73.96573639,\n      40.7954216,\n      -73.95262909,\n      40.78934479,\n      10,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 09:47:02 +00:00',\n      '2015-01-15 10:14:12 +00:00',\n      2,\n      2.2,\n      -74.00695801,\n      40.73000336,\n      -73.98561859,\n      40.7528801,\n      16.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:47:03 +00:00',\n      '2015-01-15 09:57:10 +00:00',\n      1,\n      0.7,\n      -73.97551727,\n      40.75428009,\n      -73.97853088,\n      40.76155853,\n      7.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:03 +00:00',\n      '2015-01-15 09:53:04 +00:00',\n      3,\n      0.8,\n      -73.94737244,\n      40.77603912,\n      -73.95754242,\n      40.77267075,\n      6,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 09:47:03 +00:00',\n      '2015-01-15 09:51:15 +00:00',\n      1,\n      1.1,\n      -73.96231079,\n      40.75884628,\n      -73.95182037,\n      40.77346039,\n      5.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:04 +00:00',\n      '2015-01-15 10:02:36 +00:00',\n      1,\n      1.4,\n      -73.98874664,\n      40.75502777,\n      -73.99000549,\n      40.73872375,\n      10.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:47:04 +00:00',\n      '2015-01-15 10:00:56 +00:00',\n      2,\n      1.6,\n      -73.98256683,\n      40.73551559,\n      -73.9910965,\n      40.74888992,\n      10,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:47:05 +00:00',\n      '2015-01-15 09:52:25 +00:00',\n      1,\n      0.6,\n      -73.99432373,\n      40.75033188,\n      -74.00239563,\n      40.74866867,\n      5.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 09:47:05 +00:00',\n      '2015-01-15 09:53:18 +00:00',\n      1,\n      0.7,\n      -73.96313477,\n      40.77178955,\n      -73.95404053,\n      40.76702499,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:05 +00:00',\n      '2015-01-15 09:48:54 +00:00',\n      1,\n      0.5,\n      -73.96200562,\n      40.75959015,\n      -73.95683289,\n      40.76490021,\n      3.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:05 +00:00',\n      '2015-01-15 09:51:23 +00:00',\n      1,\n      0.9,\n      -73.97065735,\n      40.78341293,\n      -73.9588623,\n      40.78094482,\n      5.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:06 +00:00',\n      '2015-01-15 09:55:23 +00:00',\n      1,\n      1.6,\n      -73.96646881,\n      40.79238129,\n      -73.95868683,\n      40.77577209,\n      8,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:06 +00:00',\n      '2015-01-15 10:01:35 +00:00',\n      2,\n      6.9,\n      -73.98991394,\n      40.75692368,\n      -73.94661713,\n      40.83629227,\n      21,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 09:47:07 +00:00',\n      '2015-01-15 09:55:25 +00:00',\n      1,\n      0.7,\n      -73.9852066,\n      40.74163055,\n      -73.97882843,\n      40.75057602,\n      7,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 09:47:07 +00:00',\n      '2015-01-15 10:08:50 +00:00',\n      1,\n      2.9,\n      -73.97415161,\n      40.78395081,\n      -73.97834778,\n      40.75157547,\n      15,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 09:47:07 +00:00',\n      '2015-01-15 09:56:05 +00:00',\n      1,\n      1.6,\n      -73.97924042,\n      40.77156448,\n      -73.96215057,\n      40.77807236,\n      8.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:26:10 +00:00',\n      '2015-01-15 10:42:12 +00:00',\n      1,\n      1.5,\n      -73.99133301,\n      40.74978256,\n      -73.97821045,\n      40.73733521,\n      11,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:26:10 +00:00',\n      '2015-01-15 10:53:36 +00:00',\n      1,\n      3.7,\n      -73.99958801,\n      40.72176361,\n      -73.96715546,\n      40.75602341,\n      18.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:26:12 +00:00',\n      '2015-01-15 10:46:58 +00:00',\n      1,\n      1.8,\n      -73.97556305,\n      40.75548553,\n      -73.99723816,\n      40.75146484,\n      13.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:12 +00:00',\n      '2015-01-15 10:50:20 +00:00',\n      1,\n      1.4,\n      -73.97470093,\n      40.74204636,\n      -73.97841644,\n      40.75669479,\n      15.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:26:12 +00:00',\n      '2015-01-15 10:39:05 +00:00',\n      1,\n      1.4,\n      -73.9725647,\n      40.75384521,\n      -73.98822021,\n      40.74080658,\n      9.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:12 +00:00',\n      '2015-01-15 10:43:52 +00:00',\n      2,\n      2.6,\n      -73.9773941,\n      40.78989792,\n      -73.9559021,\n      40.7661171,\n      13,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:26:12 +00:00',\n      '2015-01-15 10:32:31 +00:00',\n      1,\n      0.8,\n      -73.95783997,\n      40.7794075,\n      -73.95165253,\n      40.77099991,\n      6,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:26:13 +00:00',\n      '2015-01-15 10:35:01 +00:00',\n      1,\n      0.5,\n      -73.98423767,\n      40.7456665,\n      -73.97926331,\n      40.74409866,\n      7,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:56:46 +00:00',\n      '2015-01-15 11:06:13 +00:00',\n      1,\n      1.6,\n      -73.98516846,\n      40.76850128,\n      -73.98157501,\n      40.75225067,\n      7.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:46 +00:00',\n      '2015-01-15 11:09:23 +00:00',\n      1,\n      2,\n      -73.98225403,\n      40.76245117,\n      -73.99003601,\n      40.73810959,\n      10,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:56:47 +00:00',\n      '2015-01-15 11:23:18 +00:00',\n      1,\n      5.4,\n      -73.97812653,\n      40.75257492,\n      -74.00234222,\n      40.7152977,\n      22,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:48 +00:00',\n      '2015-01-15 11:07:26 +00:00',\n      1,\n      1.5,\n      -74.00622559,\n      40.73387146,\n      -73.99993134,\n      40.75349426,\n      9,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:56:48 +00:00',\n      '2015-01-15 11:08:55 +00:00',\n      1,\n      1.5,\n      -73.97122955,\n      40.75518417,\n      -73.95401001,\n      40.76623917,\n      9.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:56:48 +00:00',\n      '2015-01-15 11:10:30 +00:00',\n      1,\n      2.2,\n      -73.97627258,\n      40.75893402,\n      -73.98604584,\n      40.73451614,\n      11,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:48 +00:00',\n      '2015-01-15 11:06:10 +00:00',\n      1,\n      1.4,\n      -73.97628021,\n      40.77590942,\n      -73.96232605,\n      40.76773834,\n      8.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:49 +00:00',\n      '2015-01-15 11:03:29 +00:00',\n      1,\n      0.7,\n      -73.98015594,\n      40.76077652,\n      -73.97042084,\n      40.76482773,\n      6,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:56:49 +00:00',\n      '2015-01-15 11:05:22 +00:00',\n      1,\n      0.5,\n      -73.98032379,\n      40.74335861,\n      -73.97641754,\n      40.73955154,\n      6.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:49 +00:00',\n      '2015-01-15 11:04:05 +00:00',\n      1,\n      0.5,\n      -73.97009277,\n      40.7521019,\n      -73.97557068,\n      40.7491951,\n      6,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:49 +00:00',\n      '2015-01-15 11:12:20 +00:00',\n      1,\n      1.5,\n      -73.98545837,\n      40.75137329,\n      -74.00403595,\n      40.75012589,\n      11,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:49 +00:00',\n      '2015-01-15 11:25:21 +00:00',\n      1,\n      8.7,\n      -73.86603546,\n      40.77074432,\n      -73.98206329,\n      40.74584198,\n      29,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:56:50 +00:00',\n      '2015-01-15 11:31:54 +00:00',\n      1,\n      12.5,\n      -73.99441528,\n      40.72490692,\n      -73.94686127,\n      40.61621094,\n      38,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 10:56:50 +00:00',\n      '2015-01-15 11:16:04 +00:00',\n      1,\n      0,\n      -73.99421692,\n      40.73490906,\n      -73.97660828,\n      40.75004578,\n      12,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:50 +00:00',\n      '2015-01-15 11:03:51 +00:00',\n      1,\n      1.3,\n      -74.01493835,\n      40.71023178,\n      -74.0075531,\n      40.72661591,\n      7,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:50 +00:00',\n      '2015-01-15 11:21:30 +00:00',\n      1,\n      6,\n      -73.99806976,\n      40.73566437,\n      -73.96785736,\n      40.80531311,\n      22.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 10:56:50 +00:00',\n      '2015-01-15 11:03:30 +00:00',\n      1,\n      1.3,\n      -73.98352051,\n      40.76331329,\n      -73.99245453,\n      40.74995804,\n      6,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:56:52 +00:00',\n      '2015-01-15 11:05:46 +00:00',\n      1,\n      1.2,\n      -74.00131989,\n      40.7518959,\n      -73.98478699,\n      40.75503922,\n      7.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:52 +00:00',\n      '2015-01-15 11:11:58 +00:00',\n      1,\n      3.6,\n      -74.01648712,\n      40.71627426,\n      -73.99113464,\n      40.75481415,\n      13.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 10:56:52 +00:00',\n      '2015-01-15 11:12:56 +00:00',\n      1,\n      1.6,\n      -73.9691925,\n      40.76200104,\n      -73.98760986,\n      40.74968338,\n      11,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 10:56:53 +00:00',\n      '2015-01-15 10:59:35 +00:00',\n      1,\n      0.4,\n      -73.9547348,\n      40.76564026,\n      -73.95345306,\n      40.77137756,\n      4,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:35:15 +00:00',\n      '2015-01-15 11:53:23 +00:00',\n      1,\n      2.2,\n      -73.9984436,\n      40.71989441,\n      -74.00462341,\n      40.74206543,\n      12.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:15 +00:00',\n      '2015-01-15 11:43:13 +00:00',\n      1,\n      1.2,\n      -73.9718399,\n      40.79421616,\n      -73.95295715,\n      40.78664398,\n      7.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:16 +00:00',\n      '2015-01-15 12:01:53 +00:00',\n      1,\n      6.8,\n      -73.95982361,\n      40.77128983,\n      -74.00769043,\n      40.71765518,\n      24.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:17 +00:00',\n      '2015-01-15 11:49:54 +00:00',\n      1,\n      1.9,\n      -73.9616394,\n      40.77980804,\n      -73.98139954,\n      40.75747299,\n      10.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:18 +00:00',\n      '2015-01-15 11:51:31 +00:00',\n      2,\n      0.8,\n      -73.96839905,\n      40.76218033,\n      -73.98125458,\n      40.76370239,\n      10.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:35:19 +00:00',\n      '2015-01-15 11:46:14 +00:00',\n      1,\n      1.4,\n      -73.96898651,\n      40.79833221,\n      -73.97193146,\n      40.78203964,\n      9,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:20 +00:00',\n      '2015-01-15 11:43:38 +00:00',\n      1,\n      1.7,\n      -73.98987579,\n      40.76201248,\n      -73.98152924,\n      40.78125763,\n      8,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:20 +00:00',\n      '2015-01-15 11:47:17 +00:00',\n      1,\n      1.4,\n      -73.98216248,\n      40.77657318,\n      -73.97359467,\n      40.76300049,\n      9.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:21 +00:00',\n      '2015-01-15 11:35:28 +00:00',\n      1,\n      0,\n      -73.9733963,\n      40.76347733,\n      -73.9733963,\n      40.76347733,\n      2.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:22 +00:00',\n      '2015-01-15 11:46:32 +00:00',\n      1,\n      1.1,\n      -73.98245239,\n      40.7314949,\n      -73.97905731,\n      40.74420929,\n      8.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:23 +00:00',\n      '2015-01-15 11:44:32 +00:00',\n      1,\n      1.2,\n      -73.94425964,\n      40.7760582,\n      -73.96222687,\n      40.77615738,\n      8,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:35:24 +00:00',\n      '2015-01-15 11:58:40 +00:00',\n      1,\n      2.1,\n      -74.00631714,\n      40.73365784,\n      -74.00494385,\n      40.71043015,\n      15,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:25 +00:00',\n      '2015-01-15 11:51:24 +00:00',\n      1,\n      1.9,\n      -73.98616791,\n      40.72227859,\n      -73.99510193,\n      40.73960114,\n      12,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:25 +00:00',\n      '2015-01-15 11:51:06 +00:00',\n      1,\n      2,\n      -73.96168518,\n      40.75761414,\n      -73.98378754,\n      40.74235153,\n      11,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:25 +00:00',\n      '2015-01-15 12:01:33 +00:00',\n      1,\n      3.5,\n      -73.9727478,\n      40.74853134,\n      -74.00801849,\n      40.72272873,\n      18,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:25 +00:00',\n      '2015-01-15 11:40:23 +00:00',\n      1,\n      0.6,\n      -73.96208954,\n      40.76380157,\n      -73.96765137,\n      40.75511551,\n      5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:25 +00:00',\n      '2015-01-15 11:56:42 +00:00',\n      1,\n      1.9,\n      -73.9851532,\n      40.7371788,\n      -73.96670532,\n      40.76086426,\n      14,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:26 +00:00',\n      '2015-01-15 11:51:09 +00:00',\n      2,\n      2.3,\n      -73.97872925,\n      40.75224304,\n      -73.96221161,\n      40.77930069,\n      12,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:35:26 +00:00',\n      '2015-01-15 11:44:50 +00:00',\n      1,\n      1.3,\n      -73.95684814,\n      40.78347778,\n      -73.95326996,\n      40.79862976,\n      7.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:35:26 +00:00',\n      '2015-01-15 11:48:16 +00:00',\n      1,\n      1.8,\n      -73.95348358,\n      40.76652908,\n      -73.97425842,\n      40.75358963,\n      10,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:35:27 +00:00',\n      '2015-01-15 11:43:45 +00:00',\n      2,\n      0.9,\n      -73.98709869,\n      40.7389946,\n      -73.97855377,\n      40.75112534,\n      7,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:53 +00:00',\n      '2015-01-15 12:02:35 +00:00',\n      1,\n      1.3,\n      -73.98623657,\n      40.74385834,\n      -73.97293854,\n      40.75786209,\n      9,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:53 +00:00',\n      '2015-01-15 12:06:10 +00:00',\n      1,\n      6.3,\n      -73.96685791,\n      40.76138687,\n      -73.87236786,\n      40.77438354,\n      20,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:53 +00:00',\n      '2015-01-15 11:56:36 +00:00',\n      1,\n      0.4,\n      -73.96569824,\n      40.75917053,\n      -73.97373962,\n      40.7628479,\n      6,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:54 +00:00',\n      '2015-01-15 11:59:59 +00:00',\n      1,\n      0.9,\n      -74.00165558,\n      40.74570465,\n      -73.98720551,\n      40.73950577,\n      8,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:54 +00:00',\n      '2015-01-15 11:50:33 +00:00',\n      1,\n      0,\n      -73.94851685,\n      40.74454117,\n      -73.94844818,\n      40.74446869,\n      20,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:54 +00:00',\n      '2015-01-15 12:20:30 +00:00',\n      1,\n      9.9,\n      -73.95471954,\n      40.6891098,\n      -73.87099457,\n      40.77423859,\n      31,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:54 +00:00',\n      '2015-01-15 11:51:35 +00:00',\n      1,\n      0.3,\n      -73.97939301,\n      40.78484344,\n      -73.98139191,\n      40.78102112,\n      3.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 12:15:37 +00:00',\n      1,\n      2.1,\n      -73.96233368,\n      40.7553978,\n      -73.9940567,\n      40.75914383,\n      16,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 12:23:44 +00:00',\n      2,\n      2.2,\n      -73.9991684,\n      40.72670746,\n      -73.98468781,\n      40.75434113,\n      20,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 12:01:31 +00:00',\n      1,\n      1.9,\n      -73.99319458,\n      40.74750137,\n      -73.99531555,\n      40.72745132,\n      9,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 11:57:33 +00:00',\n      1,\n      1.3,\n      -73.98823547,\n      40.76163101,\n      -73.97983551,\n      40.77527237,\n      7,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 12:02:53 +00:00',\n      1,\n      1.5,\n      -73.98677063,\n      40.74547958,\n      -73.97886658,\n      40.76271439,\n      9.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:49:55 +00:00',\n      '2015-01-15 11:54:40 +00:00',\n      1,\n      0.8,\n      -73.99420166,\n      40.75626373,\n      -73.99887848,\n      40.74616241,\n      5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:49:56 +00:00',\n      '2015-01-15 12:38:59 +00:00',\n      3,\n      17.4,\n      -73.9848175,\n      40.75334549,\n      -73.78993988,\n      40.6469841,\n      52,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:49:56 +00:00',\n      '2015-01-15 11:56:08 +00:00',\n      1,\n      0.9,\n      -73.98517609,\n      40.75651169,\n      -73.98495483,\n      40.74806595,\n      6,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:56 +00:00',\n      '2015-01-15 12:02:54 +00:00',\n      1,\n      1.4,\n      -73.98865509,\n      40.7688942,\n      -73.97301483,\n      40.78256226,\n      10,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:56 +00:00',\n      '2015-01-15 12:01:01 +00:00',\n      1,\n      1.8,\n      -73.96639252,\n      40.78882599,\n      -73.98210907,\n      40.76841354,\n      9.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:49:57 +00:00',\n      '2015-01-15 12:03:47 +00:00',\n      4,\n      1.9,\n      -73.9731369,\n      40.75410843,\n      -73.98535156,\n      40.73196411,\n      10.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:57 +00:00',\n      '2015-01-15 12:03:11 +00:00',\n      1,\n      0.9,\n      -73.97318268,\n      40.75216675,\n      -73.98213196,\n      40.75175858,\n      9.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:49:57 +00:00',\n      '2015-01-15 11:58:02 +00:00',\n      1,\n      0.9,\n      -73.99911499,\n      40.72656631,\n      -73.99121094,\n      40.71820831,\n      7,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:49:57 +00:00',\n      '2015-01-15 12:14:42 +00:00',\n      1,\n      2.8,\n      -73.99176788,\n      40.74915695,\n      -73.95938873,\n      40.76327515,\n      16.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:15 +00:00',\n      '2015-01-15 12:24:25 +00:00',\n      1,\n      1.5,\n      -73.98931885,\n      40.73603058,\n      -73.98506165,\n      40.75305939,\n      10.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:15 +00:00',\n      '2015-01-15 12:46:14 +00:00',\n      1,\n      17,\n      -73.98258209,\n      40.73946762,\n      -73.77700043,\n      40.6446991,\n      52,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:22:54 +00:00',\n      1,\n      3.5,\n      -74.0118866,\n      40.71377945,\n      -74.00259399,\n      40.76077271,\n      13.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:26:07 +00:00',\n      1,\n      0.9,\n      -73.96848297,\n      40.76449585,\n      -73.97989655,\n      40.75763321,\n      11,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:21:53 +00:00',\n      2,\n      2.1,\n      -73.97838593,\n      40.75712967,\n      -73.98999023,\n      40.73477173,\n      10.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:19:48 +00:00',\n      1,\n      1.4,\n      -73.97607422,\n      40.75993729,\n      -73.96143341,\n      40.77157211,\n      8.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:14:50 +00:00',\n      1,\n      0.7,\n      -73.99591827,\n      40.75410461,\n      -73.98538208,\n      40.74870682,\n      5.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:22:32 +00:00',\n      1,\n      2.6,\n      -73.98551941,\n      40.76311111,\n      -73.95552063,\n      40.7767334,\n      11.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:16 +00:00',\n      '2015-01-15 12:26:30 +00:00',\n      1,\n      1.5,\n      -74.00605774,\n      40.74533844,\n      -73.9868927,\n      40.75108337,\n      12,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 12:09:17 +00:00',\n      '2015-01-15 12:21:25 +00:00',\n      1,\n      1.5,\n      -73.97014618,\n      40.78909683,\n      -73.95428467,\n      40.78998566,\n      9.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:18 +00:00',\n      '2015-01-15 12:27:32 +00:00',\n      1,\n      2.9,\n      -73.9768219,\n      40.75079346,\n      -74.00571442,\n      40.7264595,\n      13.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:18 +00:00',\n      '2015-01-15 12:34:06 +00:00',\n      1,\n      4.5,\n      -73.94947815,\n      40.78125381,\n      -73.98471832,\n      40.73619461,\n      19,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:19 +00:00',\n      '2015-01-15 12:16:46 +00:00',\n      1,\n      1.2,\n      -73.99941254,\n      40.72211075,\n      -73.99222565,\n      40.73061752,\n      7,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 12:09:19 +00:00',\n      '2015-01-15 12:18:59 +00:00',\n      1,\n      1.5,\n      -74.00551605,\n      40.73760223,\n      -74.00695038,\n      40.71791077,\n      8,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:19 +00:00',\n      '2015-01-15 12:09:49 +00:00',\n      1,\n      0,\n      -73.97665405,\n      40.74756622,\n      -73.97657776,\n      40.74789429,\n      52,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 12:09:19 +00:00',\n      '2015-01-15 12:19:40 +00:00',\n      1,\n      1,\n      -73.96508026,\n      40.77230072,\n      -73.96895599,\n      40.76291275,\n      8,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 12:09:19 +00:00',\n      '2015-01-15 12:21:35 +00:00',\n      1,\n      0.5,\n      -73.97304535,\n      40.75561142,\n      -73.97867584,\n      40.75485992,\n      8.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:20 +00:00',\n      '2015-01-15 12:16:18 +00:00',\n      1,\n      0.8,\n      -73.96009827,\n      40.76205063,\n      -73.95944214,\n      40.77100372,\n      6.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:20 +00:00',\n      '2015-01-15 12:11:19 +00:00',\n      1,\n      0.3,\n      -73.98959351,\n      40.76280975,\n      -73.99365997,\n      40.75977707,\n      3.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:21 +00:00',\n      '2015-01-15 12:26:54 +00:00',\n      1,\n      1.4,\n      -73.98150635,\n      40.74670792,\n      -73.97824097,\n      40.76279449,\n      12,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 12:09:21 +00:00',\n      '2015-01-15 12:35:03 +00:00',\n      1,\n      5.5,\n      -74.01249695,\n      40.70261383,\n      -73.98406219,\n      40.76308823,\n      21.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:08:41 +00:00',\n      '2015-01-15 13:26:26 +00:00',\n      1,\n      1.8,\n      -73.99486542,\n      40.72507477,\n      -73.99129486,\n      40.74486542,\n      12.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:08:42 +00:00',\n      '2015-01-15 13:19:14 +00:00',\n      1,\n      1.3,\n      -73.97502136,\n      40.75313187,\n      -73.9878006,\n      40.73916626,\n      8.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:43 +00:00',\n      '2015-01-15 13:17:41 +00:00',\n      1,\n      0.6,\n      -73.98260498,\n      40.73439026,\n      -73.97531128,\n      40.74148941,\n      7,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:43 +00:00',\n      '2015-01-15 13:19:05 +00:00',\n      2,\n      1.3,\n      -73.98284912,\n      40.7354126,\n      -73.99924469,\n      40.7266922,\n      8.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:08:43 +00:00',\n      '2015-01-15 13:34:05 +00:00',\n      1,\n      3.6,\n      -73.95719147,\n      40.78299713,\n      -73.99330902,\n      40.74977875,\n      17.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:44 +00:00',\n      '2015-01-15 13:14:13 +00:00',\n      1,\n      0.5,\n      -73.98175812,\n      40.75990677,\n      -73.97349548,\n      40.75848007,\n      5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:08:44 +00:00',\n      '2015-01-15 13:17:26 +00:00',\n      1,\n      1.1,\n      -73.95582581,\n      40.78220749,\n      -73.95806885,\n      40.76962662,\n      7.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:08:45 +00:00',\n      '2015-01-15 13:19:40 +00:00',\n      1,\n      1.9,\n      -73.96497345,\n      40.75543213,\n      -73.96150208,\n      40.77624893,\n      9,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:08:45 +00:00',\n      '2015-01-15 13:23:14 +00:00',\n      1,\n      2.4,\n      -73.95355988,\n      40.77692032,\n      -73.98401642,\n      40.77029419,\n      11.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:08:45 +00:00',\n      '2015-01-15 13:17:58 +00:00',\n      1,\n      0.3,\n      -73.97407532,\n      40.76476288,\n      -73.96971893,\n      40.76197052,\n      7,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:08:46 +00:00',\n      '2015-01-15 13:26:23 +00:00',\n      1,\n      1.4,\n      -73.99007416,\n      40.73517609,\n      -73.99027252,\n      40.74991226,\n      11.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:08:46 +00:00',\n      '2015-01-15 13:13:56 +00:00',\n      1,\n      0.9,\n      -73.96412659,\n      40.79253387,\n      -73.95281982,\n      40.78904343,\n      5.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:46 +00:00',\n      '2015-01-15 13:15:29 +00:00',\n      3,\n      1.2,\n      -73.97790527,\n      40.75739288,\n      -73.98935699,\n      40.74221802,\n      7,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:08:47 +00:00',\n      '2015-01-15 13:26:21 +00:00',\n      1,\n      1.7,\n      -73.98762512,\n      40.74157333,\n      -73.9746933,\n      40.76345444,\n      12,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:47 +00:00',\n      '2015-01-15 13:21:41 +00:00',\n      1,\n      0.6,\n      -73.98404694,\n      40.7493248,\n      -73.98500824,\n      40.75600052,\n      9,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:48 +00:00',\n      '2015-01-15 13:23:10 +00:00',\n      1,\n      1.8,\n      -73.97254944,\n      40.76478958,\n      -73.99060059,\n      40.74874496,\n      10.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:08:49 +00:00',\n      '2015-01-15 13:13:00 +00:00',\n      1,\n      0.9,\n      -73.96455383,\n      40.77020264,\n      -73.95604706,\n      40.78163147,\n      5.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:08:49 +00:00',\n      '2015-01-15 13:23:34 +00:00',\n      1,\n      0.9,\n      -73.98060608,\n      40.73400497,\n      -73.98609924,\n      40.7435112,\n      10,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:49 +00:00',\n      '2015-01-15 13:30:58 +00:00',\n      4,\n      2.5,\n      -73.97557068,\n      40.76527786,\n      -73.94625854,\n      40.7804451,\n      15.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:08:49 +00:00',\n      '2015-01-15 13:13:42 +00:00',\n      1,\n      1.2,\n      -73.96572876,\n      40.79036331,\n      -73.97844696,\n      40.77741241,\n      6.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:08:49 +00:00',\n      '2015-01-15 13:29:01 +00:00',\n      1,\n      1.6,\n      -73.96913147,\n      40.76098633,\n      -73.98926544,\n      40.75290298,\n      13,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:08:50 +00:00',\n      '2015-01-15 13:32:26 +00:00',\n      2,\n      6.1,\n      -74.01412964,\n      40.71213531,\n      -73.97323608,\n      40.78503036,\n      22,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:31 +00:00',\n      '2015-01-15 13:58:40 +00:00',\n      1,\n      3.1,\n      -73.99403381,\n      40.75093079,\n      -73.99698639,\n      40.71781158,\n      19.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:32 +00:00',\n      '2015-01-15 13:44:36 +00:00',\n      1,\n      3.2,\n      -73.94548798,\n      40.77409363,\n      -73.98342133,\n      40.74536514,\n      14.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:28:32 +00:00',\n      '2015-01-15 13:32:56 +00:00',\n      1,\n      0.6,\n      -73.96230316,\n      40.76759338,\n      -73.96643829,\n      40.76444244,\n      5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:33 +00:00',\n      '2015-01-15 13:32:51 +00:00',\n      1,\n      0.6,\n      -73.96742249,\n      40.7690239,\n      -73.96128082,\n      40.77759552,\n      5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:28:33 +00:00',\n      '2015-01-15 13:49:49 +00:00',\n      1,\n      1.1,\n      -73.97927094,\n      40.76162338,\n      -73.97733307,\n      40.75179291,\n      13,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:33 +00:00',\n      '2015-01-15 13:44:36 +00:00',\n      1,\n      1.9,\n      -73.98165131,\n      40.77998734,\n      -73.99479675,\n      40.75537109,\n      11.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:34 +00:00',\n      '2015-01-15 13:30:29 +00:00',\n      1,\n      0.4,\n      -73.9751358,\n      40.77751541,\n      -73.98141479,\n      40.77855301,\n      3.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:34 +00:00',\n      '2015-01-15 13:46:03 +00:00',\n      1,\n      0.6,\n      -73.9865036,\n      40.75201797,\n      -73.98056793,\n      40.76026917,\n      11,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:34 +00:00',\n      '2015-01-15 13:53:30 +00:00',\n      1,\n      9.1,\n      -73.96624756,\n      40.76560974,\n      -73.87232208,\n      40.77435303,\n      29,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:34 +00:00',\n      '2015-01-15 13:43:58 +00:00',\n      1,\n      1.7,\n      -73.97924042,\n      40.76350784,\n      -73.98809814,\n      40.74477005,\n      11,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:28:35 +00:00',\n      '2015-01-15 13:55:10 +00:00',\n      1,\n      2.6,\n      -74.00570679,\n      40.74041748,\n      -73.97388458,\n      40.75088882,\n      17,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:28:35 +00:00',\n      '2015-01-15 13:46:07 +00:00',\n      1,\n      4.9,\n      -74.00881195,\n      40.70446777,\n      -73.98458862,\n      40.74863434,\n      18.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:35 +00:00',\n      '2015-01-15 13:42:22 +00:00',\n      1,\n      1.3,\n      -73.99788666,\n      40.74094391,\n      -73.98456573,\n      40.74730682,\n      10,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:36 +00:00',\n      '2015-01-15 13:35:23 +00:00',\n      3,\n      1.1,\n      -73.99003601,\n      40.73796463,\n      -73.99784851,\n      40.72401047,\n      6.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:02 +00:00',\n      '2015-01-15 15:02:15 +00:00',\n      1,\n      1.7,\n      -73.96941376,\n      40.76636124,\n      -73.95368958,\n      40.78794479,\n      10.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:02 +00:00',\n      '2015-01-15 15:35:10 +00:00',\n      1,\n      18.7,\n      -73.78253937,\n      40.64458466,\n      -73.99058533,\n      40.72745132,\n      52,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:02 +00:00',\n      '2015-01-15 14:58:13 +00:00',\n      1,\n      1.7,\n      -73.97823334,\n      40.7544632,\n      -73.98209381,\n      40.77376556,\n      9,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:02 +00:00',\n      '2015-01-15 16:02:16 +00:00',\n      1,\n      17.9,\n      -73.9863205,\n      40.72613144,\n      -73.78303528,\n      40.64387894,\n      52,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:02 +00:00',\n      '2015-01-15 14:52:03 +00:00',\n      4,\n      0.7,\n      -73.97944641,\n      40.77158737,\n      -73.98216248,\n      40.76358795,\n      4.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:03 +00:00',\n      '2015-01-15 14:54:40 +00:00',\n      1,\n      0.7,\n      -73.98408508,\n      40.73751831,\n      -73.9903183,\n      40.73723984,\n      6,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:03 +00:00',\n      '2015-01-15 15:07:23 +00:00',\n      1,\n      2.1,\n      -74.00341034,\n      40.72686768,\n      -73.98469543,\n      40.75408173,\n      13,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:48:03 +00:00',\n      '2015-01-15 15:28:40 +00:00',\n      2,\n      11.6,\n      -73.77835846,\n      40.64676285,\n      -73.95575714,\n      40.65594101,\n      39,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:03 +00:00',\n      '2015-01-15 14:50:48 +00:00',\n      1,\n      0.7,\n      -73.96367645,\n      40.77734756,\n      -73.97085571,\n      40.78310394,\n      4.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:48:04 +00:00',\n      '2015-01-15 15:12:15 +00:00',\n      2,\n      2.7,\n      -73.96225739,\n      40.77906799,\n      -73.99147034,\n      40.75802994,\n      16,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:04 +00:00',\n      '2015-01-15 14:52:41 +00:00',\n      1,\n      0.6,\n      -73.97543335,\n      40.75215912,\n      -73.96953583,\n      40.75200653,\n      5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:48:04 +00:00',\n      '2015-01-15 14:54:52 +00:00',\n      1,\n      0.8,\n      -73.97546387,\n      40.75792694,\n      -73.97644043,\n      40.75066376,\n      6,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:48:04 +00:00',\n      '2015-01-15 14:54:11 +00:00',\n      1,\n      0.7,\n      -73.98100281,\n      40.75943375,\n      -73.99040222,\n      40.75676727,\n      5.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:48:05 +00:00',\n      '2015-01-15 14:52:33 +00:00',\n      1,\n      0.8,\n      -73.96006012,\n      40.76252747,\n      -73.95191193,\n      40.77336502,\n      5.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:48:05 +00:00',\n      '2015-01-15 15:02:12 +00:00',\n      1,\n      2.6,\n      -73.95397949,\n      40.76646423,\n      -73.98020935,\n      40.77532959,\n      11.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:05 +00:00',\n      '2015-01-15 15:17:12 +00:00',\n      1,\n      10.2,\n      -73.87229156,\n      40.77449799,\n      -73.9723587,\n      40.76523972,\n      32.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:48:06 +00:00',\n      '2015-01-15 14:58:55 +00:00',\n      1,\n      1.3,\n      -73.99183655,\n      40.7434082,\n      -73.97814178,\n      40.75121689,\n      8.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:06 +00:00',\n      '2015-01-15 15:03:19 +00:00',\n      1,\n      2,\n      -73.99678802,\n      40.76287842,\n      -73.98536682,\n      40.7436142,\n      11.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:48:06 +00:00',\n      '2015-01-15 14:56:09 +00:00',\n      1,\n      0.8,\n      -73.97661591,\n      40.78069687,\n      -73.98725128,\n      40.77615738,\n      6.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:37 +00:00',\n      '2015-01-15 13:39:20 +00:00',\n      1,\n      1.3,\n      -73.96536255,\n      40.76590729,\n      -73.95040131,\n      40.77541351,\n      8.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 13:28:37 +00:00',\n      '2015-01-15 13:37:49 +00:00',\n      1,\n      2.9,\n      -73.95761871,\n      40.76847076,\n      -73.97514343,\n      40.73571014,\n      11.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 13:28:37 +00:00',\n      '2015-01-15 13:52:56 +00:00',\n      2,\n      2.4,\n      -73.99796295,\n      40.75651932,\n      -73.9693985,\n      40.76195526,\n      16,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 13:28:37 +00:00',\n      '2015-01-15 13:57:15 +00:00',\n      1,\n      3.3,\n      -74.01082611,\n      40.70900726,\n      -73.98595428,\n      40.75083542,\n      19,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:28:37 +00:00',\n      '2015-01-15 13:33:57 +00:00',\n      1,\n      0.5,\n      -73.97025299,\n      40.75899887,\n      -73.97548676,\n      40.75217438,\n      5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 13:28:38 +00:00',\n      '2015-01-15 14:13:39 +00:00',\n      1,\n      10.4,\n      -73.98912048,\n      40.75639343,\n      -73.87306213,\n      40.77438354,\n      38.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:27 +00:00',\n      '2015-01-15 11:13:04 +00:00',\n      1,\n      0.57,\n      -73.99191284,\n      40.73366165,\n      -73.99928284,\n      40.73590088,\n      5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:08:27 +00:00',\n      '2015-01-15 11:17:22 +00:00',\n      5,\n      1.36,\n      -73.97545624,\n      40.78210831,\n      -73.98251343,\n      40.76395035,\n      8,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:38 +00:00',\n      '2015-01-15 16:13:56 +00:00',\n      2,\n      3.4,\n      -73.98490143,\n      40.76909256,\n      -73.95078278,\n      40.78242874,\n      18.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:38 +00:00',\n      '2015-01-15 16:11:28 +00:00',\n      1,\n      4.9,\n      -73.98446655,\n      40.75934601,\n      -73.94443512,\n      40.8130722,\n      19.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:38 +00:00',\n      '2015-01-15 15:56:08 +00:00',\n      1,\n      1.1,\n      -73.95365906,\n      40.76678848,\n      -73.9686203,\n      40.76480484,\n      7.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:39 +00:00',\n      '2015-01-15 16:01:53 +00:00',\n      4,\n      1.6,\n      -74.00598907,\n      40.74010849,\n      -74.00376129,\n      40.7591362,\n      10.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:39 +00:00',\n      '2015-01-15 16:06:25 +00:00',\n      1,\n      2.5,\n      -73.98991394,\n      40.7408638,\n      -73.96156311,\n      40.75641632,\n      13.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:40 +00:00',\n      '2015-01-15 15:54:36 +00:00',\n      1,\n      0.6,\n      -73.9804306,\n      40.76034927,\n      -73.97067261,\n      40.75859451,\n      6,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:40 +00:00',\n      '2015-01-15 15:57:34 +00:00',\n      1,\n      1.2,\n      -73.98097992,\n      40.73818588,\n      -73.98110962,\n      40.75061417,\n      8,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:40 +00:00',\n      '2015-01-15 15:56:19 +00:00',\n      1,\n      1.4,\n      -73.98873138,\n      40.75407028,\n      -74.00460052,\n      40.73969269,\n      7.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 15:47:41 +00:00',\n      '2015-01-15 16:00:53 +00:00',\n      1,\n      1.4,\n      -73.9916153,\n      40.75009918,\n      -73.99889374,\n      40.7610817,\n      9.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:41 +00:00',\n      '2015-01-15 15:57:32 +00:00',\n      1,\n      1.3,\n      -73.9928894,\n      40.75384521,\n      -73.97768402,\n      40.76447296,\n      8,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 15:47:41 +00:00',\n      '2015-01-15 16:02:12 +00:00',\n      2,\n      2.1,\n      -73.987854,\n      40.75493622,\n      -73.99325562,\n      40.73668671,\n      11,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:41 +00:00',\n      '2015-01-15 17:08:39 +00:00',\n      1,\n      8.8,\n      -73.98908997,\n      40.75322723,\n      -73.8707962,\n      40.77392578,\n      50,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:41 +00:00',\n      '2015-01-15 15:54:06 +00:00',\n      1,\n      1.5,\n      -73.98497009,\n      40.76370239,\n      -73.97200775,\n      40.78221512,\n      7,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:42 +00:00',\n      '2015-01-15 16:17:48 +00:00',\n      1,\n      9.2,\n      -73.8709259,\n      40.77367401,\n      -73.95968628,\n      40.7660408,\n      31.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 15:47:42 +00:00',\n      '2015-01-15 15:53:42 +00:00',\n      2,\n      0.7,\n      -73.95475006,\n      40.77365875,\n      -73.96230316,\n      40.77204895,\n      5.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 15:47:42 +00:00',\n      '2015-01-15 15:52:40 +00:00',\n      1,\n      0.6,\n      -73.96900177,\n      40.76102829,\n      -73.97166443,\n      40.75520325,\n      5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:43 +00:00',\n      '2015-01-15 16:02:21 +00:00',\n      1,\n      2,\n      -73.98990631,\n      40.77584076,\n      -73.97659302,\n      40.7581749,\n      11,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 15:47:43 +00:00',\n      '2015-01-15 17:12:21 +00:00',\n      1,\n      17.2,\n      -73.97954559,\n      40.74938965,\n      -73.77823639,\n      40.64494324,\n      52,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:43 +00:00',\n      '2015-01-15 15:52:40 +00:00',\n      1,\n      0.7,\n      -73.96670532,\n      40.80432129,\n      -73.97161102,\n      40.79508209,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 15:47:43 +00:00',\n      '2015-01-15 16:01:34 +00:00',\n      1,\n      1.6,\n      -73.97691345,\n      40.75243378,\n      -73.9618454,\n      40.76618576,\n      10.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 15:47:43 +00:00',\n      '2015-01-15 16:05:15 +00:00',\n      1,\n      5.6,\n      -73.97419739,\n      40.75210953,\n      -74.01583099,\n      40.70493317,\n      19,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 15:47:44 +00:00',\n      '2015-01-15 15:51:25 +00:00',\n      3,\n      0.6,\n      -73.98048401,\n      40.73053741,\n      -73.99012756,\n      40.73469543,\n      4.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:28 +00:00',\n      '2015-01-15 11:22:08 +00:00',\n      5,\n      1.02,\n      -73.96199036,\n      40.76301956,\n      -73.97537231,\n      40.76025009,\n      9.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:28 +00:00',\n      '2015-01-15 11:12:03 +00:00',\n      2,\n      0.71,\n      -73.96024323,\n      40.81326675,\n      -73.96228027,\n      40.80551529,\n      5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:08:28 +00:00',\n      '2015-01-15 11:45:14 +00:00',\n      4,\n      3.29,\n      -73.99386597,\n      40.72647858,\n      -73.97525024,\n      40.76517868,\n      22.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:28 +00:00',\n      '2015-01-15 11:16:27 +00:00',\n      6,\n      1.69,\n      -73.97033691,\n      40.76505661,\n      -73.95837402,\n      40.78314972,\n      7.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:29 +00:00',\n      '2015-01-15 11:31:28 +00:00',\n      1,\n      3.45,\n      -73.97387695,\n      40.76417923,\n      -73.99996185,\n      40.72422028,\n      16.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:29 +00:00',\n      '2015-01-15 11:19:43 +00:00',\n      1,\n      1.91,\n      -73.98752594,\n      40.7325058,\n      -74.00524902,\n      40.71485138,\n      9.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:08:30 +00:00',\n      '2015-01-15 11:20:15 +00:00',\n      1,\n      1.71,\n      -73.99472046,\n      40.75059509,\n      -73.97688293,\n      40.76406479,\n      9.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:30 +00:00',\n      '2015-01-15 11:23:16 +00:00',\n      1,\n      1.92,\n      -73.97637177,\n      40.76343155,\n      -73.99304962,\n      40.74539948,\n      11,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:30 +00:00',\n      '2015-01-15 11:10:32 +00:00',\n      5,\n      0.27,\n      -73.95477295,\n      40.76545715,\n      -73.95536804,\n      40.76841354,\n      3.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:30 +00:00',\n      '2015-01-15 11:22:21 +00:00',\n      1,\n      1.44,\n      -73.95503235,\n      40.77841568,\n      -73.97670746,\n      40.78928375,\n      10,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:08:31 +00:00',\n      '2015-01-15 11:17:10 +00:00',\n      1,\n      2.17,\n      -73.95035553,\n      40.77127838,\n      -73.96971893,\n      40.75237656,\n      9,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:31 +00:00',\n      '2015-01-15 11:22:19 +00:00',\n      1,\n      2.13,\n      -73.95650482,\n      40.77070236,\n      -73.9798584,\n      40.75445557,\n      10.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:31 +00:00',\n      '2015-01-15 12:07:41 +00:00',\n      1,\n      18.23,\n      -73.78193665,\n      40.64471054,\n      -73.99067688,\n      40.72071075,\n      52,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:31 +00:00',\n      '2015-01-15 11:24:35 +00:00',\n      1,\n      1.01,\n      -73.97892761,\n      40.75037766,\n      -73.99127197,\n      40.75544739,\n      10.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:31 +00:00',\n      '2015-01-15 11:14:02 +00:00',\n      5,\n      0.88,\n      -73.9934845,\n      40.73516464,\n      -73.99437714,\n      40.74361038,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:33 +00:00',\n      '2015-01-15 11:16:14 +00:00',\n      5,\n      1.51,\n      -73.97502899,\n      40.76099014,\n      -73.98868561,\n      40.74270248,\n      7.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:33 +00:00',\n      '2015-01-15 11:52:22 +00:00',\n      1,\n      8.14,\n      -73.95440674,\n      40.76395798,\n      -73.8562851,\n      40.75259018,\n      33.5,\n      false,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:08:33 +00:00',\n      '2015-01-15 11:48:04 +00:00',\n      5,\n      15.13,\n      -73.95636749,\n      40.78319931,\n      -73.98403931,\n      40.63248825,\n      46,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:08:34 +00:00',\n      '2015-01-15 11:30:55 +00:00',\n      1,\n      2.47,\n      -74.00182343,\n      40.75087738,\n      -73.9638443,\n      40.77070618,\n      15.5,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:08:34 +00:00',\n      '2015-01-15 11:15:45 +00:00',\n      1,\n      1.27,\n      -73.97843933,\n      40.74962616,\n      -73.99088287,\n      40.73462677,\n      7,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:19:58 +00:00',\n      '2015-01-15 14:30:43 +00:00',\n      3,\n      1.9,\n      -73.9719696,\n      40.78208542,\n      -73.9462738,\n      40.78117752,\n      9.5,\n      true,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:19:59 +00:00',\n      '2015-01-15 14:26:18 +00:00',\n      1,\n      1.4,\n      -73.96221161,\n      40.75906372,\n      -73.95623016,\n      40.77560425,\n      6.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:20:00 +00:00',\n      '2015-01-15 14:29:24 +00:00',\n      1,\n      1,\n      -73.98879242,\n      40.74028015,\n      -73.99024963,\n      40.72889328,\n      7.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:20:00 +00:00',\n      '2015-01-15 14:24:37 +00:00',\n      1,\n      0.7,\n      -73.96638489,\n      40.80471039,\n      -73.97330475,\n      40.79518509,\n      5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:20:00 +00:00',\n      '2015-01-15 14:25:07 +00:00',\n      1,\n      0.6,\n      -73.98246765,\n      40.77352142,\n      -73.9892807,\n      40.76781464,\n      5.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:20:00 +00:00',\n      '2015-01-15 15:08:06 +00:00',\n      1,\n      20.8,\n      -73.97194672,\n      40.75946808,\n      -73.78960419,\n      40.64704132,\n      52,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:20:01 +00:00',\n      '2015-01-15 14:30:10 +00:00',\n      1,\n      1.5,\n      -73.97073364,\n      40.76262283,\n      -73.95559692,\n      40.7793045,\n      8.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:20:02 +00:00',\n      '2015-01-15 14:44:57 +00:00',\n      1,\n      4.9,\n      -73.95665741,\n      40.76708221,\n      -73.96069336,\n      40.81632614,\n      20.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 14:20:02 +00:00',\n      '2015-01-15 14:25:28 +00:00',\n      1,\n      1.5,\n      -73.97408295,\n      40.77892685,\n      -73.95641327,\n      40.7840538,\n      6.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:20:02 +00:00',\n      '2015-01-15 15:05:17 +00:00',\n      1,\n      16.2,\n      -73.97773743,\n      40.76435471,\n      -73.79027557,\n      40.64681244,\n      52,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:20:04 +00:00',\n      '2015-01-15 14:30:20 +00:00',\n      1,\n      0.9,\n      -73.96961975,\n      40.75938034,\n      -73.9821701,\n      40.76047134,\n      7.5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 14:20:06 +00:00',\n      '2015-01-15 14:23:11 +00:00',\n      1,\n      0.2,\n      -73.97293091,\n      40.79558563,\n      -73.97042847,\n      40.79651642,\n      4,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:20:06 +00:00',\n      '2015-01-15 14:25:16 +00:00',\n      1,\n      0.7,\n      -73.74542999,\n      40.58639145,\n      -73.74523926,\n      40.58619308,\n      5.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:20:06 +00:00',\n      '2015-01-15 14:25:32 +00:00',\n      1,\n      0.7,\n      -73.98985291,\n      40.73440552,\n      -73.99806976,\n      40.73797989,\n      5.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:20:07 +00:00',\n      '2015-01-15 14:31:34 +00:00',\n      1,\n      1.2,\n      -73.96231842,\n      40.77913666,\n      -73.9730835,\n      40.76346207,\n      9,\n      true,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 14:20:08 +00:00',\n      '2015-01-15 14:43:34 +00:00',\n      1,\n      4.3,\n      -73.97229004,\n      40.74654388,\n      -74.00812531,\n      40.71973419,\n      19,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 14:20:08 +00:00',\n      '2015-01-15 14:23:44 +00:00',\n      1,\n      0.9,\n      -74.00809479,\n      40.73937225,\n      -74.00430298,\n      40.72974777,\n      5,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:09:27 +00:00',\n      '2015-01-15 11:18:50 +00:00',\n      2,\n      0.82,\n      -73.98420715,\n      40.74626923,\n      -73.97663116,\n      40.75596619,\n      7,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:09:28 +00:00',\n      '2015-01-15 11:21:16 +00:00',\n      5,\n      1.48,\n      -73.9804306,\n      40.74560547,\n      -73.98104858,\n      40.72947693,\n      9,\n      true,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:09:28 +00:00',\n      '2015-01-15 11:17:09 +00:00',\n      1,\n      0.85,\n      -73.98188782,\n      40.74059677,\n      -73.98110199,\n      40.74951172,\n      6.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:09:28 +00:00',\n      '2015-01-15 11:12:01 +00:00',\n      1,\n      0.39,\n      -73.96213531,\n      40.76808167,\n      -73.96128845,\n      40.76436234,\n      3.5,\n      false,\n      'banana peel'\n    ],\n    [\n      '2015-01-15 11:09:28 +00:00',\n      '2015-01-15 11:16:34 +00:00',\n      4,\n      1.08,\n      -73.98171997,\n      40.75831985,\n      -73.96775055,\n      40.76005936,\n      6.5,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:09:32 +00:00',\n      '2015-01-15 11:45:33 +00:00',\n      1,\n      4.35,\n      -73.92948914,\n      40.75658035,\n      -73.99256134,\n      40.75829315,\n      23,\n      false,\n      'orange peel'\n    ],\n    [\n      '2015-01-15 11:09:32 +00:00',\n      '2015-01-15 11:27:44 +00:00',\n      1,\n      0.87,\n      -73.97641754,\n      40.74801636,\n      -73.97517395,\n      40.74153519,\n      11.5,\n      true,\n      'apple tree'\n    ],\n    [\n      '2015-01-15 11:09:32 +00:00',\n      '2015-01-15 11:16:36 +00:00',\n      1,\n      0.96,\n      -73.96311951,\n      40.76504517,\n      -73.95355225,\n      40.77010727,\n      6.5,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:09:34 +00:00',\n      '2015-01-15 11:28:22 +00:00',\n      1,\n      1.2,\n      -73.99105835,\n      40.74952698,\n      -73.97509003,\n      40.74176025,\n      12,\n      false,\n      'mango mint pineapple juice'\n    ],\n    [\n      '2015-01-15 11:09:34 +00:00',\n      '2015-01-15 11:39:55 +00:00',\n      1,\n      7.47,\n      -74.01488495,\n      40.71375275,\n      -73.97367859,\n      40.76446533,\n      28,\n      true,\n      'orange peel'\n    ]\n  ]\n};\n\nexport const sampleTripDataConfig = {\n  version: 'v1',\n  config: {\n    visState: {\n      layers: [\n        {\n          type: 'heatmap',\n          config: {\n            dataId: 'test_trip_data',\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            isVisible: true\n          }\n        },\n        {\n          type: 'point',\n          config: {\n            dataId: 'test_trip_data',\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            color: [255, 0, 0],\n            label: 'pickup',\n            isVisible: true,\n            visConfig: {\n              colorRange: {\n                colors: ['#FF000', '#00FF00', '#0000FF', '#555555', '#111111', '#222222'],\n                colorMap: [\n                  ['apple tree', '#FF000'],\n                  ['banana peel', '#00FF00'],\n                  ['banana peel 2', '#0000FF'],\n                  ['mango mint pineapple juice', '#555555'],\n                  ['orange peel', '#111111'],\n                  ['orange peel 0', '#222222']\n                ]\n              }\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: 'fare_type',\n              type: 'string'\n            },\n            colorScale: 'custom'\n          }\n        },\n        {\n          type: 'point',\n          config: {\n            dataId: 'test_trip_data',\n            columns: {\n              lat: 'dropoff_latitude',\n              lng: 'dropoff_longitude'\n            },\n            color: [0, 0, 255],\n            label: 'dropoff',\n            isVisible: true\n          }\n        },\n        {\n          type: 'cluster',\n          config: {\n            dataId: 'test_trip_data',\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            isVisible: true\n          }\n        },\n        {\n          type: 'arc',\n          config: {\n            dataId: 'test_trip_data',\n            label: 'pickup -> dropoff',\n            columns: {\n              lat0: 'pickup_latitude',\n              lng0: 'pickup_longitude',\n              lat1: 'dropoff_latitude',\n              lng1: 'dropoff_longitude'\n            },\n            color: [255, 0, 0],\n            isVisible: true,\n            visConfig: {\n              targetColor: [0, 0, 255]\n            }\n          }\n        }\n      ],\n      filters: [\n        {\n          dataId: 'test_trip_data',\n          name: 'tpep_pickup_datetime',\n          view: 'enlarged'\n        },\n        {\n          dataId: 'test_trip_data',\n          name: 'passenger_count'\n        },\n        {\n          dataId: 'test_trip_data',\n          name: 'fare_type',\n          value: ['orange peel', 'apple tree']\n        },\n        {\n          dataId: 'test_trip_data',\n          name: 'is_completed',\n          value: true\n        }\n      ]\n    },\n    mapStyle: {\n      styleType: '41fv96u',\n      visibleLayerGroups: {\n        label: false,\n        road: false,\n        border: false,\n        building: true,\n        water: true,\n        land: true,\n        '3d building': false\n      },\n      mapStyles: {\n        '41fv96u': {\n          accessToken: null,\n          custom: true,\n          icon: 'https://api.mapbox.com/styles/v1/MAPBOX_USER/cjg0ks54x300a2squ8fr9vhvq/static/-122.3391,37.7922,9,0,0/400x300?access_token=ACCESS_TOKEN&logo=false&attribution=false',\n          id: '41fv96u',\n          label: 'Outdoors',\n          url: 'mapbox://styles/MAPBOX_USER/cjhnxdcfy4ug62sn6qdfjutob'\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "examples/demo-app/src/factories/load-data-modal.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LoadDataModalFactory, withState} from '@kepler.gl/components';\nimport {LOADING_METHODS} from '../constants/default-settings';\n\nimport SampleMapGallery from '../components/load-data-modal/sample-data-viewer';\nimport LoadRemoteMap from '../components/load-data-modal/load-remote-map';\nimport SampleMapsTab from '../components/load-data-modal/sample-maps-tab';\nimport {loadRemoteMap, loadSample, loadSampleConfigurations} from '../actions';\n\nconst CustomLoadDataModalFactory = (...deps) => {\n  const LoadDataModal = LoadDataModalFactory(...deps);\n  const defaultLoadingMethods = LoadDataModal.defaultLoadingMethods;\n  const additionalMethods = {\n    remote: {\n      id: LOADING_METHODS.remote,\n      label: 'modal.loadData.remote',\n      elementType: LoadRemoteMap\n    },\n    sample: {\n      id: LOADING_METHODS.sample,\n      label: 'modal.loadData.sample',\n      elementType: SampleMapGallery,\n      tabElementType: SampleMapsTab\n    }\n  };\n\n  // add more loading methods\n  const loadingMethods = [\n    defaultLoadingMethods.find(lm => lm.id === 'upload'),\n    defaultLoadingMethods.find(lm => lm.id === 'tileset'),\n    additionalMethods.remote,\n    defaultLoadingMethods.find(lm => lm.id === 'storage'),\n    additionalMethods.sample\n  ];\n\n  return withState(\n    [],\n    state => ({...state.demo.app, ...state.demo.keplerGl.map.uiState, loadingMethods}),\n    {\n      onLoadSample: loadSample,\n      onLoadRemoteMap: loadRemoteMap,\n      loadSampleConfigurations\n    }\n  )(LoadDataModal);\n};\n\nCustomLoadDataModalFactory.deps = LoadDataModalFactory.deps;\n\nexport function replaceLoadDataModal() {\n  return [LoadDataModalFactory, CustomLoadDataModalFactory];\n}\n"
  },
  {
    "path": "examples/demo-app/src/factories/map-control.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {\n  withState,\n  MapControlFactory,\n  EffectControlFactory,\n  EffectManagerFactory\n} from '@kepler.gl/components';\nimport {AiAssistantControlFactory} from '@kepler.gl/ai-assistant';\n\nimport {BannerMapPanel, SampleMapPanel} from '../components/map-control/map-control';\nimport SqlPanelControlFactory from '../components/map-control/sql-panel-control';\n\nconst StyledMapControlPanel = styled.div`\n  position: relative;\n`;\n\nconst StyledMapControlContextPanel = styled.div`\n  max-height: 100%;\n  overflow: hidden;\n\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  pointer-events: none !important; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\nconst StyledMapControlOverlay = styled.div`\n  position: absolute;\n  display: flex;\n  top: ${props => props.top}px;\n  right: 0;\n  z-index: 1;\n  pointer-events: none !important; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n\n  margin-top: ${props => (props.rightPanelVisible ? props.theme.rightPanelMarginTop : 0)}px;\n  margin-right: ${props => (props.rightPanelVisible ? props.theme.rightPanelMarginRight : 0)}px;\n  ${props => (props.fullHeight ? 'height' : 'max-height')}: calc(100% - ${props =>\n    props.theme.rightPanelMarginTop + props.theme.bottomWidgetPaddingBottom}px);\n\n  .map-control {\n    ${props => (props.rightPanelVisible ? 'padding-top: 0px;' : '')}\n  }\n`;\n\nCustomMapControlFactory.deps = [\n  EffectControlFactory,\n  EffectManagerFactory,\n  SqlPanelControlFactory,\n  AiAssistantControlFactory,\n  ...MapControlFactory.deps\n];\nfunction CustomMapControlFactory(\n  EffectControl,\n  EffectManager,\n  SqlPanelControl,\n  AiAssistantControl,\n  ...deps\n) {\n  const MapControl = MapControlFactory(...deps);\n  const actionComponents = [\n    ...(MapControl.defaultActionComponents ?? []),\n    EffectControl,\n    SqlPanelControl,\n    AiAssistantControl\n  ];\n\n  const CustomMapControl = props => {\n    const showEffects = Boolean(props.mapControls?.effect?.active);\n    return (\n      <StyledMapControlOverlay top={props.top} rightPanelVisible={showEffects}>\n        <StyledMapControlPanel>\n          {<BannerMapPanel {...props} />}\n          {!props.isExport && props.currentSample ? <SampleMapPanel {...props} /> : null}\n          <MapControl {...props} top={0} actionComponents={actionComponents} />\n        </StyledMapControlPanel>\n        <StyledMapControlContextPanel>\n          {showEffects ? <EffectManager /> : null}\n        </StyledMapControlContextPanel>\n      </StyledMapControlOverlay>\n    );\n  };\n\n  return withState([], state => ({...state.demo.app}))(CustomMapControl);\n}\n\nexport function replaceMapControl() {\n  return [MapControlFactory, CustomMapControlFactory];\n}\n"
  },
  {
    "path": "examples/demo-app/src/factories/panel-header.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {PanelHeaderFactory, Icons} from '@kepler.gl/components';\nimport {BUG_REPORT_LINK, USER_GUIDE_DOC} from '@kepler.gl/constants';\n\nexport function CustomPanelHeaderFactory(...deps) {\n  const PanelHeader = PanelHeaderFactory(...deps);\n  const defaultActionItems = PanelHeader.defaultProps.actionItems;\n  PanelHeader.defaultProps = {\n    ...PanelHeader.defaultProps,\n    actionItems: [\n      {\n        id: 'bug',\n        iconComponent: Icons.Bug,\n        href: BUG_REPORT_LINK,\n        blank: true,\n        tooltip: 'Bug Report',\n        onClick: () => {}\n      },\n      {\n        id: 'docs',\n        iconComponent: Icons.Docs2,\n        href: USER_GUIDE_DOC,\n        blank: true,\n        tooltip: 'User Guide',\n        onClick: () => {}\n      },\n      defaultActionItems.find(item => item.id === 'storage'),\n      {\n        ...defaultActionItems.find(item => item.id === 'save'),\n        label: null,\n        tooltip: 'Share'\n      }\n    ]\n  };\n  return PanelHeader;\n}\n\nCustomPanelHeaderFactory.deps = PanelHeaderFactory.deps;\n\nexport function replacePanelHeader() {\n  return [PanelHeaderFactory, CustomPanelHeaderFactory];\n}\n"
  },
  {
    "path": "examples/demo-app/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport {Provider} from 'react-redux';\nimport {browserHistory, Router, Route} from 'react-router';\nimport {syncHistoryWithStore} from 'react-router-redux';\nimport store from './store';\nimport App from './app';\nimport {buildAppRoutes} from './utils/routes';\n\nconst history = syncHistoryWithStore(browserHistory, store);\n\nconst appRoute = buildAppRoutes(App);\n\nconst Root = () => (\n  <Provider store={store}>\n    <Router history={history}>\n      <Route path=\"/\" component={App}>\n        {appRoute}\n      </Route>\n    </Router>\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/demo-app/src/reducers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {combineReducers} from 'redux';\nimport {handleActions} from 'redux-actions';\nimport Task, {withTask} from 'react-palm/tasks';\n\nimport {aiAssistantReducer} from '@kepler.gl/ai-assistant';\nimport {EXPORT_MAP_FORMATS} from '@kepler.gl/constants';\nimport {processGeojson, processRowObject, processArrowTable} from '@kepler.gl/processors';\nimport keplerGlReducer, {combinedUpdaters, uiStateUpdaters} from '@kepler.gl/reducers';\nimport KeplerGlSchema from '@kepler.gl/schemas';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\n// import {getApplicationConfig, initApplicationConfig} from '@kepler.gl/utils';\n// import keplerGlDuckdbPlugin, {KeplerGlDuckDbTable, DuckDBWasmAdapter} from '@kepler.gl/duckdb';\n\nimport {\n  INIT,\n  LOAD_MAP_SAMPLE_FILE,\n  LOAD_REMOTE_RESOURCE_SUCCESS,\n  LOAD_REMOTE_DATASET_PROCESSED_SUCCESS,\n  LOAD_REMOTE_RESOURCE_ERROR,\n  SET_SAMPLE_LOADING_STATUS,\n  loadRemoteDatasetProcessedSuccessAction\n} from '../actions';\n\nimport {CLOUD_PROVIDERS_CONFIGURATION} from '../constants/default-settings';\nimport {generateHashId} from '../utils/strings';\n\n// initialize kepler demo-app with DuckDB plugin\n/*\ninitApplicationConfig({\n  // Custom UI for DuckDB\n  plugins: [keplerGlDuckdbPlugin],\n  // async data ingestion to DuckDb\n  table: KeplerGlDuckDbTable,\n  // setup database for DuckDB plugin\n  database: new DuckDBWasmAdapter({\n    config: {\n      query: {\n        castBigIntToDouble: true\n      }\n    }\n  }),\n  // progressive loading is sync, doesn't wait properly for a dataset to be created in DuckDB\n  useArrowProgressiveLoading: false\n});\n*/\n\nconst {DEFAULT_MAP_CONTROLS} = uiStateUpdaters;\n\n// INITIAL_APP_STATE\nconst initialAppState = {\n  appName: 'example',\n  loaded: false,\n  sampleMaps: [], // this is used to store sample maps fetch from a remote json file\n  isMapLoading: false, // determine whether we are loading a sample map,\n  error: null // contains error when loading/retrieving data/configuration\n  // {\n  //   status: null,\n  //   message: null\n  // }\n};\n\n// App reducer\nexport const appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    }),\n    [LOAD_MAP_SAMPLE_FILE]: (state, action) => ({\n      ...state,\n      sampleMaps: action.samples\n    }),\n    [SET_SAMPLE_LOADING_STATUS]: (state, action) => ({\n      ...state,\n      isMapLoading: action.isMapLoading\n    })\n  },\n  initialAppState\n);\n\nconst {DEFAULT_EXPORT_MAP} = uiStateUpdaters;\n\n// combine app reducer and keplerGl reducer\n// to mimic the reducer state of kepler.gl website\nconst demoReducer = combineReducers({\n  // mount keplerGl reducer\n  keplerGl: keplerGlReducer.initialState({\n    // In order to provide single file export functionality\n    // we are going to set the mapbox access token to be used\n    // in the exported file\n    uiState: {\n      exportMap: {\n        ...DEFAULT_EXPORT_MAP,\n        [EXPORT_MAP_FORMATS.HTML]: {\n          ...DEFAULT_EXPORT_MAP[[EXPORT_MAP_FORMATS.HTML]],\n          exportMapboxAccessToken: CLOUD_PROVIDERS_CONFIGURATION.EXPORT_MAPBOX_TOKEN\n        }\n      },\n      mapControls: {\n        ...DEFAULT_MAP_CONTROLS,\n        // TODO find a better way not to add extra controls optionally - from plugin?\n        ...((getApplicationConfig().plugins || []).some(p => p.name === 'duckdb')\n          ? {\n              sqlPanel: {\n                active: false,\n                activeMapIndex: 0,\n                disableClose: false,\n                show: true\n              }\n            }\n          : {})\n      }\n    },\n    visState: {\n      loaders: [], // Add additional loaders.gl loaders here\n      loadOptions: {} // Add additional loaders.gl loader options here\n    }\n  }),\n  app: appReducer,\n  aiAssistant: aiAssistantReducer\n});\n\nasync function loadRemoteResourceSuccessTask({\n  dataUrl,\n  datasetId,\n  processorMethod,\n  remoteDatasetConfig,\n  unprocessedData\n}) {\n  if (dataUrl) {\n    const data = await processorMethod(unprocessedData);\n    return {\n      info: {\n        id: datasetId\n      },\n      data\n    };\n  }\n\n  // remote datasets like vector tile datasets\n  return remoteDatasetConfig;\n}\n\nconst LOAD_REMOTE_RESOURCE_SUCCESS_TASK = Task.fromPromise(\n  loadRemoteResourceSuccessTask,\n  'LOAD_REMOTE_RESOURCE_SUCCESS_TASK'\n);\n\n// this can be moved into a action and call kepler.gl action\n/**\n * Used to load Kepler.gl demo examples\n * @param state\n * @param action {map: resultset, config, map}\n * @returns {{app: {isMapLoading: boolean}, keplerGl: {map: (state|*)}}}\n */\nexport const loadRemoteResourceSuccess = (state, action) => {\n  // TODO: replace generate with a different function\n  const datasetId = action.options.id || generateHashId(6);\n  const {dataUrl} = action.options;\n\n  const {shape} = dataUrl ? action.response : {};\n  let processorMethod = processRowObject;\n  let unprocessedData = action.response;\n  unprocessedData = shape === 'object-row-table' ? action.response.data : unprocessedData;\n\n  if (dataUrl) {\n    const table = getApplicationConfig().table ?? KeplerTable;\n    if (typeof table.getFileProcessor === 'function') {\n      if (shape === 'arrow-table') {\n        // arrow processor from table plugin expects batches\n        unprocessedData = action.response.data.batches;\n      }\n      // use custom processors from table class\n      const processorResult = table.getFileProcessor(unprocessedData);\n      // TODO save processorResult.format here with the dataset\n      processorMethod = processorResult.processor;\n    } else {\n      if (shape === 'arrow-table') {\n        processorMethod = processArrowTable;\n      } else if (shape === 'object-row-table') {\n        processorMethod = processRowObject;\n      } else if (dataUrl.includes('.json') || dataUrl.includes('.geojson')) {\n        processorMethod = processGeojson;\n      } else {\n        throw new Error('Failed to select data processor');\n      }\n    }\n  }\n\n  // processorMethod can be async so create a task\n  const task = LOAD_REMOTE_RESOURCE_SUCCESS_TASK({\n    dataUrl,\n    datasetId,\n    processorMethod,\n    remoteDatasetConfig: action.remoteDatasetConfig,\n    unprocessedData\n  }).bimap(\n    datasets => loadRemoteDatasetProcessedSuccessAction({...action, datasets}),\n    () => {\n      throw new Error('loadRemoteResource data processor failed');\n    }\n  );\n\n  return withTask(state, task);\n};\n\nconst loadRemoteDatasetProcessedSuccess = (state, action) => {\n  const {config, datasets, options} = action.payload;\n\n  const parsedConfig = config ? KeplerGlSchema.parseSavedConfig(config) : null;\n\n  // a hack to use minZoom and maxZoom from examples\n  if (parsedConfig?.mapState) {\n    if (typeof config?.config?.mapState?.maxZoom === 'number') {\n      parsedConfig.mapState.maxZoom = config.config.mapState.maxZoom;\n    }\n    if (typeof config?.config?.mapState?.minZoom === 'number') {\n      parsedConfig.mapState.minZoom = config.config.mapState.minZoom;\n    }\n  }\n\n  const keplerGlInstance = combinedUpdaters.addDataToMapUpdater(\n    state.keplerGl.map, // \"map\" is the id of your kepler.gl instance\n    {\n      payload: {\n        datasets,\n        config: parsedConfig,\n        options: {\n          centerMap: Boolean(!config)\n        }\n      }\n    }\n  );\n\n  return {\n    ...state,\n    app: {\n      ...state.app,\n      currentSample: options,\n      isMapLoading: false // we turn off the spinner\n    },\n    keplerGl: {\n      ...state.keplerGl, // in case you keep multiple instances\n      map: keplerGlInstance\n    }\n  };\n};\n\nexport const loadRemoteResourceError = (state, action) => {\n  const {error, url} = action;\n\n  const errorNote = {\n    type: 'error',\n    message: error.message || `Error loading ${url}`\n  };\n\n  return {\n    ...state,\n    app: {\n      ...state.app,\n      isMapLoading: false // we turn of the spinner\n    },\n    keplerGl: {\n      ...state.keplerGl, // in case you keep multiple instances\n      map: {\n        ...state.keplerGl.map,\n        uiState: uiStateUpdaters.addNotificationUpdater(state.keplerGl.map.uiState, {\n          payload: errorNote\n        })\n      }\n    }\n  };\n};\n\nconst composedUpdaters = {\n  [LOAD_REMOTE_RESOURCE_SUCCESS]: loadRemoteResourceSuccess,\n  [LOAD_REMOTE_DATASET_PROCESSED_SUCCESS]: loadRemoteDatasetProcessedSuccess,\n  [LOAD_REMOTE_RESOURCE_ERROR]: loadRemoteResourceError\n};\n\nconst composedReducer = (state, action) => {\n  if (composedUpdaters[action.type]) {\n    return composedUpdaters[action.type](state, action);\n  }\n  return demoReducer(state, action);\n};\n\n// export demoReducer to be combined in website app\nexport default composedReducer;\n"
  },
  {
    "path": "examples/demo-app/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {combineReducers, createStore, applyMiddleware, compose} from 'redux';\nimport {routerReducer, routerMiddleware} from 'react-router-redux';\nimport {browserHistory} from 'react-router';\nimport {createLogger} from 'redux-logger';\nimport thunk from 'redux-thunk';\n\nimport {enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\n// eslint-disable-next-line no-unused-vars\nimport Window from 'global/window';\n\nimport demoReducer from './reducers/index';\n\nconst reducers = combineReducers({\n  demo: demoReducer,\n  routing: routerReducer\n});\n\nexport const middlewares = enhanceReduxMiddleware([thunk, routerMiddleware(browserHistory)]);\n\nif (NODE_ENV === 'local') {\n  // Redux logger\n  const logger = createLogger({\n    collapsed: () => true // Collapse all actions for more compact log\n  });\n  middlewares.push(logger);\n}\n\nexport const enhancers = [applyMiddleware(...middlewares)];\n\nconst initialState = {};\n\n// eslint-disable-next-line prefer-const\nlet composeEnhancers = compose;\n\n/**\n * comment out code below to enable Redux Devtools\n */\n\nif (Window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {\n  composeEnhancers = Window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({\n    actionsBlacklist: [\n      '@@kepler.gl/MOUSE_MOVE',\n      '@@kepler.gl/UPDATE_MAP',\n      '@@kepler.gl/LAYER_HOVER'\n    ]\n  });\n}\n\nexport default createStore(reducers, initialState, composeEnhancers(...enhancers));\n"
  },
  {
    "path": "examples/demo-app/src/utils/routes.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {IndexRoute, Route} from 'react-router';\nimport React from 'react';\nimport Demo from '../app';\nimport {getCloudProvider, DEFAULT_CLOUD_PROVIDER} from '../cloud-providers';\n\nexport function onAuthEnterCallback(nextState, replace, callback) {\n  // TODO: detect auth provider\n  const authProvider = getCloudProvider(DEFAULT_CLOUD_PROVIDER);\n\n  // Check if the current tab was opened by our previous tab\n  if (window.opener) {\n    const {location} = nextState;\n    const token = authProvider.getAccessTokenFromLocation(location);\n    window.opener.postMessage({token}, location.origin);\n  }\n\n  callback();\n}\n\nexport function buildAppRoutes(Component) {\n  return [\n    <Route key=\"auth\" path=\"auth\" component={Demo} onEnter={onAuthEnterCallback} />,\n    <Route key=\"demo\" path=\"demo\">\n      <IndexRoute component={Component} />\n      <Route path=\"map\" component={Component} />\n      <Route path=\"(:id)\" component={Component} />\n      <Route path=\"map/:provider\" component={Component} />\n    </Route>\n  ];\n}\n"
  },
  {
    "path": "examples/demo-app/src/utils/strings.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Generate a hash string based on number of character\n * @param {number} count\n * @returns {string} hash string\n */\nexport function generateHashId(count) {\n  return Math.random().toString(36).substr(count);\n}\n"
  },
  {
    "path": "examples/demo-app/yarn.lock",
    "content": "# This file is generated by running \"yarn install\" inside your project.\n# Manual changes might be lost - proceed with caution!\n\n__metadata:\n  version: 8\n  cacheKey: 10c0\n\n\"@ai-sdk/anthropic@npm:^1.2.11\":\n  version: 1.2.12\n  resolution: \"@ai-sdk/anthropic@npm:1.2.12\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/da13e1ed3c03efe207dbb0fd5fe9f399e4119e6687ec1096418a33a7eeea3c5f912a51c74b185bba3c203b15ee0c1b9cdf649711815ff8e769e31af266ac00fb\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/deepseek@npm:^0.2.14\":\n  version: 0.2.16\n  resolution: \"@ai-sdk/deepseek@npm:0.2.16\"\n  dependencies:\n    \"@ai-sdk/openai-compatible\": \"npm:0.2.16\"\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/02bffba5e75f68e4eb13d74b302f61fdf4c976745e27b8816ae718da84b61d935c4dbe5bc6af91d348ce68107ba3ce4ec379b2689e13a050c4669d3d39122830\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/google@npm:^1.2.18\":\n  version: 1.2.22\n  resolution: \"@ai-sdk/google@npm:1.2.22\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/662005cb4d972e4048d496e9713cd0ff2828f8363c9f1449e5a8580523c34f9ab57126ad3129e4d535561aa1bac0ae0d68bb471c379be80f5a23d455eea49987\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/openai-compatible@npm:0.2.16\":\n  version: 0.2.16\n  resolution: \"@ai-sdk/openai-compatible@npm:0.2.16\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/03054b7d6960be64cd9788cc7ecde324a6c70b8d51df4c3252deba8f6c5b641fce1058ab89c4793bbb4f2237e4fe09d1f4966205015b75b04bb49931d49a4253\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/openai@npm:^1.3.21\":\n  version: 1.3.21\n  resolution: \"@ai-sdk/openai@npm:1.3.21\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.7\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/adba1dcfe26eaea09e3ebdf55d03f5008765e1b153f7fbf376b703d40cc16b574fc44f73507d2a0e8aef81fbe70d9f2ca0a4b482d8ba73d1ea78a7489d418a9c\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/provider-utils@npm:2.1.6\":\n  version: 2.1.6\n  resolution: \"@ai-sdk/provider-utils@npm:2.1.6\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.0.7\"\n    eventsource-parser: \"npm:^3.0.0\"\n    nanoid: \"npm:^3.3.8\"\n    secure-json-parse: \"npm:^2.7.0\"\n  peerDependencies:\n    zod: ^3.0.0\n  peerDependenciesMeta:\n    zod:\n      optional: true\n  checksum: 10c0/dacf9640c0ab2da69157d08c7db47f9ac82aa140780ad18d03b802a88ec9e81ce6f0626cb6f7139086013c5850f829f74e81783a6fe1e6a7e08d090f08479179\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/provider-utils@npm:2.2.7\":\n  version: 2.2.7\n  resolution: \"@ai-sdk/provider-utils@npm:2.2.7\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    nanoid: \"npm:^3.3.8\"\n    secure-json-parse: \"npm:^2.7.0\"\n  peerDependencies:\n    zod: ^3.23.8\n  checksum: 10c0/e89a4e03be59df56bfb15e25e761955ffabd39b350527dd3e27da89c35332d1db6eeffc596d2aa3e18a2f5535d79e8ddc4ad7066d6f05f490f7d10082f427f00\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/provider-utils@npm:2.2.8\":\n  version: 2.2.8\n  resolution: \"@ai-sdk/provider-utils@npm:2.2.8\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    nanoid: \"npm:^3.3.8\"\n    secure-json-parse: \"npm:^2.7.0\"\n  peerDependencies:\n    zod: ^3.23.8\n  checksum: 10c0/34c72bf5f23f2d3e7aef496da7099422ba3b3ff243c35511853e16c3f1528717500262eea32b19e3e09bc4452152a5f31e650512f53f08a5f5645d907bff429e\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/provider@npm:1.0.7\":\n  version: 1.0.7\n  resolution: \"@ai-sdk/provider@npm:1.0.7\"\n  dependencies:\n    json-schema: \"npm:^0.4.0\"\n  checksum: 10c0/92d0a3cd4d577ca8028d7b8fb3c2b4b837be10fced5d66dbe67fe415b678d2ff039d32721b71b88389568729e707b35df81172908e2584f2e24ff20fcdcdeb65\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/provider@npm:1.1.3\":\n  version: 1.1.3\n  resolution: \"@ai-sdk/provider@npm:1.1.3\"\n  dependencies:\n    json-schema: \"npm:^0.4.0\"\n  checksum: 10c0/40e080e223328e7c89829865e9c48f4ce8442a6a59f7ed5dfbdb4f63e8d859a76641e2d31e91970dd389bddb910f32ec7c3dbb0ce583c119e5a1e614ea7b8bc4\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/react@npm:1.2.11, @ai-sdk/react@npm:^1.2.11\":\n  version: 1.2.11\n  resolution: \"@ai-sdk/react@npm:1.2.11\"\n  dependencies:\n    \"@ai-sdk/provider-utils\": \"npm:2.2.7\"\n    \"@ai-sdk/ui-utils\": \"npm:1.2.10\"\n    swr: \"npm:^2.2.5\"\n    throttleit: \"npm:2.1.0\"\n  peerDependencies:\n    react: ^18 || ^19 || ^19.0.0-rc\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    zod:\n      optional: true\n  checksum: 10c0/a679c217961358c261cf374d888904777a12c45c661e8fa5faba7a93f4382e06cb46b0a176a300fa7ec5b2c00f13b164d82d3d5f1457f68d0d9567659fdb527e\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/react@npm:1.2.12\":\n  version: 1.2.12\n  resolution: \"@ai-sdk/react@npm:1.2.12\"\n  dependencies:\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n    \"@ai-sdk/ui-utils\": \"npm:1.2.11\"\n    swr: \"npm:^2.2.5\"\n    throttleit: \"npm:2.1.0\"\n  peerDependencies:\n    react: ^18 || ^19 || ^19.0.0-rc\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    zod:\n      optional: true\n  checksum: 10c0/5422feb4ffeebd3287441cf658733e9ad7f9081fc279e85f57700d7fe9f4ed8a0504789c1be695790df44b28730e525cf12acf0f52bfa5adecc561ffd00cb2a5\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/ui-utils@npm:1.2.10, @ai-sdk/ui-utils@npm:^1.2.10\":\n  version: 1.2.10\n  resolution: \"@ai-sdk/ui-utils@npm:1.2.10\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.7\"\n    zod-to-json-schema: \"npm:^3.24.1\"\n  peerDependencies:\n    zod: ^3.23.8\n  checksum: 10c0/dcafcfd047130c9a1c6b26857f2bb82bcd67e7d487925a0e1d4cf60f0e01ef597c61997d102755599fa997ce13f8474bbcf1620a91f7310aceb3923a2b3916a5\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/ui-utils@npm:1.2.11, @ai-sdk/ui-utils@npm:^1.2.11\":\n  version: 1.2.11\n  resolution: \"@ai-sdk/ui-utils@npm:1.2.11\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n    zod-to-json-schema: \"npm:^3.24.1\"\n  peerDependencies:\n    zod: ^3.23.8\n  checksum: 10c0/de0a10f9e16010126a21a1690aaf56d545b9c0f8d8b2cc33ffd22c2bb2e914949acb9b3f86e0e39a0e4b0d4f24db12e2b094045e34b311de0c8f84bfab48cc92\n  languageName: node\n  linkType: hard\n\n\"@ai-sdk/xai@npm:^1.2.16\":\n  version: 1.2.18\n  resolution: \"@ai-sdk/xai@npm:1.2.18\"\n  dependencies:\n    \"@ai-sdk/openai-compatible\": \"npm:0.2.16\"\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/caf92d750e4e0aecd598d3ebe2fdf4da56e2113d81d11bc4a965412e78186552ed7175f68f3f5584c27ba2b8f74e5d9f31c553d47a6989cbfacbdb2ceef1a332\n  languageName: node\n  linkType: hard\n\n\"@alloc/quick-lru@npm:^5.2.0\":\n  version: 5.2.0\n  resolution: \"@alloc/quick-lru@npm:5.2.0\"\n  checksum: 10c0/7b878c48b9d25277d0e1a9b8b2f2312a314af806b4129dc902f2bc29ab09b58236e53964689feec187b28c80d2203aff03829754773a707a8a5987f1b7682d92\n  languageName: node\n  linkType: hard\n\n\"@auth0/auth0-spa-js@npm:^2.1.2\":\n  version: 2.1.3\n  resolution: \"@auth0/auth0-spa-js@npm:2.1.3\"\n  checksum: 10c0/781d474564731f96182ff78d85e1d13841e924eba5d2e9a42593db28c66e2a6c59db1755c8c906698f0cd6d33d6e61501f0e0ab57ec981700966246944cccf74\n  languageName: node\n  linkType: hard\n\n\"@babel/code-frame@npm:^7.0.0\":\n  version: 7.24.7\n  resolution: \"@babel/code-frame@npm:7.24.7\"\n  dependencies:\n    \"@babel/highlight\": \"npm:^7.24.7\"\n    picocolors: \"npm:^1.0.0\"\n  checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6\n  languageName: node\n  linkType: hard\n\n\"@babel/helper-validator-identifier@npm:^7.24.7\":\n  version: 7.24.7\n  resolution: \"@babel/helper-validator-identifier@npm:7.24.7\"\n  checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651\n  languageName: node\n  linkType: hard\n\n\"@babel/highlight@npm:^7.24.7\":\n  version: 7.24.7\n  resolution: \"@babel/highlight@npm:7.24.7\"\n  dependencies:\n    \"@babel/helper-validator-identifier\": \"npm:^7.24.7\"\n    chalk: \"npm:^2.4.2\"\n    js-tokens: \"npm:^4.0.0\"\n    picocolors: \"npm:^1.0.0\"\n  checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a\n  languageName: node\n  linkType: hard\n\n\"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.9.2\":\n  version: 7.25.0\n  resolution: \"@babel/runtime@npm:7.25.0\"\n  dependencies:\n    regenerator-runtime: \"npm:^0.14.0\"\n  checksum: 10c0/bd3faf246170826cef2071a94d7b47b49d532351360ecd17722d03f6713fd93a3eb3dbd9518faa778d5e8ccad7392a7a604e56bd37aaad3f3aa68d619ccd983d\n  languageName: node\n  linkType: hard\n\n\"@babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.7\":\n  version: 7.26.0\n  resolution: \"@babel/runtime@npm:7.26.0\"\n  dependencies:\n    regenerator-runtime: \"npm:^0.14.0\"\n  checksum: 10c0/12c01357e0345f89f4f7e8c0e81921f2a3e3e101f06e8eaa18a382b517376520cd2fa8c237726eb094dab25532855df28a7baaf1c26342b52782f6936b07c287\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit-auth@npm:^0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit-auth@npm:0.0.1-rc.18\"\n  dependencies:\n    \"@carto/toolkit-core\": \"npm:^0.0.1-rc.18\"\n    \"@salte-auth/popup\": \"npm:1.0.0-rc.2\"\n    \"@salte-auth/salte-auth\": \"npm:3.0.0-rc.8\"\n    mitt: \"npm:^1.1.3\"\n  checksum: 10c0/6fc8db6168c59e8dc47a04f49cccd9b1cc2bc27856b1f8340631715bfd3e477ca265c6f5fdaf5b968b875aad8674bb9077685b93ee25392f4d740cb3a880b289\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit-core@npm:^0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit-core@npm:0.0.1-rc.18\"\n  checksum: 10c0/f4b4d4e37265acc881ee295207d56376daa86d9ed6c3faf4cfb8587f04ba65c6413bcf29769a6ac97806f04c233960175199c0bccdec5e3f7b29ffd03f2cf72d\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit-custom-storage@npm:^0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit-custom-storage@npm:0.0.1-rc.18\"\n  dependencies:\n    \"@carto/toolkit-core\": \"npm:^0.0.1-rc.18\"\n    \"@carto/toolkit-sql\": \"npm:^0.0.1-rc.18\"\n  checksum: 10c0/1547778358271c2f73e62e3b4e5435a451ea8005f8150f11f36d566ad4c7b9d1b90daad491f6e3410b3cb6ce7e78abf6c041a425a90af8847ed391f2722477b8\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit-maps@npm:^0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit-maps@npm:0.0.1-rc.18\"\n  dependencies:\n    \"@carto/toolkit-core\": \"npm:^0.0.1-rc.18\"\n  checksum: 10c0/489320cc3d89443ce350d1c77a8b3e0ce51ee46c62f5c6caebb2796dfb1dd25239d12d58e2524780499118183826350c5c40829dc287f23f9939e17ee9370b69\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit-sql@npm:^0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit-sql@npm:0.0.1-rc.18\"\n  dependencies:\n    \"@carto/toolkit-core\": \"npm:^0.0.1-rc.18\"\n  checksum: 10c0/365402775aadb4f96911e3d0c4a4f30a976c5b66defe9e5357357a86975e273f6813a0b7012cc22ef73dfeb31aba945e59f51d1a6f01dcc60bd151e4c06f348e\n  languageName: node\n  linkType: hard\n\n\"@carto/toolkit@npm:0.0.1-rc.18\":\n  version: 0.0.1-rc.18\n  resolution: \"@carto/toolkit@npm:0.0.1-rc.18\"\n  dependencies:\n    \"@carto/toolkit-auth\": \"npm:^0.0.1-rc.18\"\n    \"@carto/toolkit-core\": \"npm:^0.0.1-rc.18\"\n    \"@carto/toolkit-custom-storage\": \"npm:^0.0.1-rc.18\"\n    \"@carto/toolkit-maps\": \"npm:^0.0.1-rc.18\"\n    \"@carto/toolkit-sql\": \"npm:^0.0.1-rc.18\"\n  checksum: 10c0/381794a8b93b6371243ef99692c4a3ec573ab9fe05aeb7ce5b6a382da75048f472394787193d787ae7a68023129575f8470e5fd97a0a0b36c82cd8f493de5c8d\n  languageName: node\n  linkType: hard\n\n\"@cfworker/json-schema@npm:^4.0.2\":\n  version: 4.1.0\n  resolution: \"@cfworker/json-schema@npm:4.1.0\"\n  checksum: 10c0/a7917d197ad9d5deb48a4d800ab41b3de5fe856745c164c5a4ed914263a0a11b08cf1b11040eb325e756e8f9418b07511109df856281dd10bf288f743d4fb3e6\n  languageName: node\n  linkType: hard\n\n\"@danmarshall/deckgl-typings@npm:4.9.12\":\n  version: 4.9.12\n  resolution: \"@danmarshall/deckgl-typings@npm:4.9.12\"\n  dependencies:\n    \"@types/hammerjs\": \"npm:^2.0.36\"\n    \"@types/react\": \"npm:*\"\n    indefinitely-typed: \"npm:^1.1.0\"\n  checksum: 10c0/7e2d500f79d0c900771b70c7781cc382deb25d18300163cf8df9eac70f866f89de9b7ac458ec2894252bc947aaf0e8eab4e7a3b0db3e6cb5300221fa10c3e7c1\n  languageName: node\n  linkType: hard\n\n\"@danmarshall/deckgl-typings@npm:4.9.22\":\n  version: 4.9.22\n  resolution: \"@danmarshall/deckgl-typings@npm:4.9.22\"\n  dependencies:\n    \"@types/hammerjs\": \"npm:^2.0.36\"\n    \"@types/react\": \"npm:*\"\n    indefinitely-typed: \"npm:^1.1.0\"\n  checksum: 10c0/50cdbaaf91505f531e4e14526473b7c911f339ab7e7f91ffe9fdff705f221a0bd3ba3418213509cabd4d32e832a4b0ba63749c449483e93d3e6a7e47a5a08c68\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/aggregation-layers@npm:^8.9.27\":\n  version: 8.9.36\n  resolution: \"@deck.gl/aggregation-layers@npm:8.9.36\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/constants\": \"npm:^8.5.21\"\n    \"@luma.gl/shadertools\": \"npm:^8.5.21\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    d3-hexbin: \"npm:^0.2.1\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@deck.gl/layers\": ^8.0.0\n    \"@luma.gl/core\": ^8.0.0\n  checksum: 10c0/a3ec6a2b6dc664a82302370de503379c9f9fb8f7fa741a5d0d99e5b971bcb4213d0411939a8894b74daa742e9053fc396f0181d1f5c06168d0cb71a0af4e7dfd\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/core@npm:8.9.27\":\n  version: 8.9.27\n  resolution: \"@deck.gl/core@npm:8.9.27\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@loaders.gl/core\": \"npm:^3.4.13\"\n    \"@loaders.gl/images\": \"npm:^3.4.13\"\n    \"@luma.gl/constants\": \"npm:^8.5.20\"\n    \"@luma.gl/core\": \"npm:^8.5.20\"\n    \"@luma.gl/webgl\": \"npm:^8.5.20\"\n    \"@math.gl/core\": \"npm:^3.6.2\"\n    \"@math.gl/sun\": \"npm:^3.6.2\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    \"@probe.gl/env\": \"npm:^3.5.0\"\n    \"@probe.gl/log\": \"npm:^3.5.0\"\n    \"@probe.gl/stats\": \"npm:^3.5.0\"\n    gl-matrix: \"npm:^3.0.0\"\n    math.gl: \"npm:^3.6.2\"\n    mjolnir.js: \"npm:^2.7.0\"\n  checksum: 10c0/6263b1f3cf50736d1adf5ea3d95a88d11cd82bcb318f1399892ac78a0528deb7f56461daaef916e8aaffc7ff502e6fe1fb9631e828e0717126e2005a51162cef\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/extensions@npm:8.9.27\":\n  version: 8.9.27\n  resolution: \"@deck.gl/extensions@npm:8.9.27\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/shadertools\": \"npm:^8.5.20\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@luma.gl/constants\": ^8.0.0\n    \"@luma.gl/core\": ^8.0.0\n    \"@math.gl/core\": ^3.6.2\n    \"@math.gl/web-mercator\": ^3.6.2\n    gl-matrix: ^3.0.0\n  checksum: 10c0/597b6b8c6e6fd9ba41de1d7b850858e27fda5eaa71dc2eb47000403cddc99d52cdff3fa623b8ac61a30b6a3dfc25b7d0fa6f143fd62591cccb813a73e8435b47\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/geo-layers@npm:^8.9.27\":\n  version: 8.9.36\n  resolution: \"@deck.gl/geo-layers@npm:8.9.36\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@loaders.gl/3d-tiles\": \"npm:^3.4.13\"\n    \"@loaders.gl/gis\": \"npm:^3.4.13\"\n    \"@loaders.gl/loader-utils\": \"npm:^3.4.13\"\n    \"@loaders.gl/mvt\": \"npm:^3.4.13\"\n    \"@loaders.gl/schema\": \"npm:^3.4.13\"\n    \"@loaders.gl/terrain\": \"npm:^3.4.13\"\n    \"@loaders.gl/tiles\": \"npm:^3.4.13\"\n    \"@loaders.gl/wms\": \"npm:^3.4.13\"\n    \"@luma.gl/constants\": \"npm:^8.5.21\"\n    \"@luma.gl/experimental\": \"npm:^8.5.21\"\n    \"@math.gl/core\": \"npm:^3.6.2\"\n    \"@math.gl/culling\": \"npm:^3.6.2\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    \"@types/geojson\": \"npm:^7946.0.8\"\n    h3-js: \"npm:^3.7.0\"\n    long: \"npm:^3.2.0\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@deck.gl/extensions\": ^8.0.0\n    \"@deck.gl/layers\": ^8.0.0\n    \"@deck.gl/mesh-layers\": ^8.0.0\n    \"@loaders.gl/core\": ^3.4.13\n    \"@luma.gl/core\": ^8.0.0\n  checksum: 10c0/c5326b3fb22b1eb63bb2dd8abcf0db0e829700a8a20f19900c8e8471ad4dd8d28cc4a18bfe7931515659e83b9e878241743a9b4a47131b4723bb902e7bcf3841\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/layers@npm:^8.9.27\":\n  version: 8.9.36\n  resolution: \"@deck.gl/layers@npm:8.9.36\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@loaders.gl/images\": \"npm:^3.4.13\"\n    \"@loaders.gl/schema\": \"npm:^3.4.13\"\n    \"@luma.gl/constants\": \"npm:^8.5.21\"\n    \"@mapbox/tiny-sdf\": \"npm:^2.0.5\"\n    \"@math.gl/core\": \"npm:^3.6.2\"\n    \"@math.gl/polygon\": \"npm:^3.6.2\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    earcut: \"npm:^2.2.4\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@loaders.gl/core\": ^3.4.13\n    \"@luma.gl/core\": ^8.0.0\n  checksum: 10c0/e3a7d1f57ccbc9c2fe7606bdc0a604d3e3598ef7eaa8d4b1b44ef295318a91a0a241ae3698b414830c8b6589281aa96f239856b071f52e283c552d58830a75da\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/mesh-layers@npm:^8.9.27\":\n  version: 8.9.36\n  resolution: \"@deck.gl/mesh-layers@npm:8.9.36\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@loaders.gl/gltf\": \"npm:^3.4.13\"\n    \"@luma.gl/constants\": \"npm:^8.5.21\"\n    \"@luma.gl/experimental\": \"npm:^8.5.21\"\n    \"@luma.gl/shadertools\": \"npm:^8.5.21\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@luma.gl/core\": ^8.0.0\n  checksum: 10c0/8c1fcf6aeaaf5c710c3d4d0d107255d554799e61d16a12c59015513a3d2abffda354a0ff3d9370979e30189d9a5a68a083cc07cc83e385d7c519f50859d6164f\n  languageName: node\n  linkType: hard\n\n\"@deck.gl/react@npm:^8.9.27\":\n  version: 8.9.36\n  resolution: \"@deck.gl/react@npm:8.9.36\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.0.0\n    \"@types/react\": \">= 16.3\"\n    react: \">=16.3\"\n    react-dom: \">=16.3\"\n  checksum: 10c0/cf5af570c982041d73de0def96d75609db1bb2cecaa8d34f6f891ccf7c3c19a9e177dbbd5d97b537e6fc48daf845d5fa123e183a241cff518d606dee3f794c56\n  languageName: node\n  linkType: hard\n\n\"@dnd-kit/accessibility@npm:^3.1.1\":\n  version: 3.1.1\n  resolution: \"@dnd-kit/accessibility@npm:3.1.1\"\n  dependencies:\n    tslib: \"npm:^2.0.0\"\n  peerDependencies:\n    react: \">=16.8.0\"\n  checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8\n  languageName: node\n  linkType: hard\n\n\"@dnd-kit/core@npm:^6.1.0\":\n  version: 6.3.1\n  resolution: \"@dnd-kit/core@npm:6.3.1\"\n  dependencies:\n    \"@dnd-kit/accessibility\": \"npm:^3.1.1\"\n    \"@dnd-kit/utilities\": \"npm:^3.2.2\"\n    tslib: \"npm:^2.0.0\"\n  peerDependencies:\n    react: \">=16.8.0\"\n    react-dom: \">=16.8.0\"\n  checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52\n  languageName: node\n  linkType: hard\n\n\"@dnd-kit/modifiers@npm:^7.0.0\":\n  version: 7.0.0\n  resolution: \"@dnd-kit/modifiers@npm:7.0.0\"\n  dependencies:\n    \"@dnd-kit/utilities\": \"npm:^3.2.2\"\n    tslib: \"npm:^2.0.0\"\n  peerDependencies:\n    \"@dnd-kit/core\": ^6.1.0\n    react: \">=16.8.0\"\n  checksum: 10c0/542e1d2b6102a5c826118c36158aab23c5437d24008cab4848b0866d3d850b4410c4f465690767dd1f31fde33a1fa9d238675be70f174c179485ce376f0c8aa6\n  languageName: node\n  linkType: hard\n\n\"@dnd-kit/sortable@npm:^8.0.0\":\n  version: 8.0.0\n  resolution: \"@dnd-kit/sortable@npm:8.0.0\"\n  dependencies:\n    \"@dnd-kit/utilities\": \"npm:^3.2.2\"\n    tslib: \"npm:^2.0.0\"\n  peerDependencies:\n    \"@dnd-kit/core\": ^6.1.0\n    react: \">=16.8.0\"\n  checksum: 10c0/a6066c652b892c6a11320c7d8f5c18fdf723e721e8eea37f4ab657dee1ac5e7ca710ac32ce0712a57fe968bc07c13bcea5d5599d90dfdd95619e162befd4d2fb\n  languageName: node\n  linkType: hard\n\n\"@dnd-kit/utilities@npm:^3.2.2\":\n  version: 3.2.2\n  resolution: \"@dnd-kit/utilities@npm:3.2.2\"\n  dependencies:\n    tslib: \"npm:^2.0.0\"\n  peerDependencies:\n    react: \">=16.8.0\"\n  checksum: 10c0/9aa90526f3e3fd567b5acc1b625a63177b9e8d00e7e50b2bd0e08fa2bf4dba7e19529777e001fdb8f89a7ce69f30b190c8364d390212634e0afdfa8c395e85a0\n  languageName: node\n  linkType: hard\n\n\"@dotenv-run/core@npm:~1.3.6\":\n  version: 1.3.6\n  resolution: \"@dotenv-run/core@npm:1.3.6\"\n  dependencies:\n    chalk: \"npm:^4.1.2\"\n    dotenv: \"npm:^16.4.5\"\n    dotenv-expand: \"npm:^10.0.0\"\n    find-up: \"npm:^5.0.0\"\n  checksum: 10c0/26a5e8698017ad3f56c236f199d945c4df00f19ac28381dc00ef688dba23e436449ae7d27606ca63d026b92143fa8e4840aca4ad89031b5ec9dbc6ee00c50fc2\n  languageName: node\n  linkType: hard\n\n\"@dotenv-run/esbuild@npm:^1.5.0\":\n  version: 1.5.0\n  resolution: \"@dotenv-run/esbuild@npm:1.5.0\"\n  dependencies:\n    \"@dotenv-run/core\": \"npm:~1.3.6\"\n  peerDependencies:\n    esbuild: ^0.21.0\n  checksum: 10c0/ff359e0aea49534be60486065b2f20ce4a83aacadf7706ff3bef5a793473d8527efe3b9c9b5c00333b04874d69f08676f27da5c8ec1027ed5a7d5df0ec4b6066\n  languageName: node\n  linkType: hard\n\n\"@duckdb/duckdb-wasm@npm:1.29.0, @duckdb/duckdb-wasm@npm:^1.28.0, @duckdb/duckdb-wasm@npm:^1.29.0\":\n  version: 1.29.0\n  resolution: \"@duckdb/duckdb-wasm@npm:1.29.0\"\n  dependencies:\n    apache-arrow: \"npm:^17.0.0\"\n  checksum: 10c0/caa550b09433adb709f997241ef00fd7ee93dc4cb4a03838d9d978536fdc640c80ef0f2c294248c688e4a38231be42287e4aba655e13d94bcd9aff4767a404a0\n  languageName: node\n  linkType: hard\n\n\"@emotion/is-prop-valid@npm:1.2.1\":\n  version: 1.2.1\n  resolution: \"@emotion/is-prop-valid@npm:1.2.1\"\n  dependencies:\n    \"@emotion/memoize\": \"npm:^0.8.1\"\n  checksum: 10c0/7c2aabdf0ca9986ca25abc9dae711348308cf18d418d64ffa4c8ffd2114806c47f2e06ba8ee769f38ec67d65bd59ec73f34d94023e81baa1c43510ac86ccd5e6\n  languageName: node\n  linkType: hard\n\n\"@emotion/is-prop-valid@npm:^1.2.1\":\n  version: 1.3.1\n  resolution: \"@emotion/is-prop-valid@npm:1.3.1\"\n  dependencies:\n    \"@emotion/memoize\": \"npm:^0.9.0\"\n  checksum: 10c0/123215540c816ff510737ec68dcc499c53ea4deb0bb6c2c27c03ed21046e2e69f6ad07a7a174d271c6cfcbcc9ea44e1763e0cf3875c92192f7689216174803cd\n  languageName: node\n  linkType: hard\n\n\"@emotion/memoize@npm:^0.8.1\":\n  version: 0.8.1\n  resolution: \"@emotion/memoize@npm:0.8.1\"\n  checksum: 10c0/dffed372fc3b9fa2ba411e76af22b6bb686fb0cb07694fdfaa6dd2baeb0d5e4968c1a7caa472bfcf06a5997d5e7c7d16b90e993f9a6ffae79a2c3dbdc76dfe78\n  languageName: node\n  linkType: hard\n\n\"@emotion/memoize@npm:^0.9.0\":\n  version: 0.9.0\n  resolution: \"@emotion/memoize@npm:0.9.0\"\n  checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15\n  languageName: node\n  linkType: hard\n\n\"@emotion/unitless@npm:0.8.0\":\n  version: 0.8.0\n  resolution: \"@emotion/unitless@npm:0.8.0\"\n  checksum: 10c0/1f2cfb7c0ccb83c20b1c6d8d92a74a93da4b2a440f9a0d49ded08647faf299065a2ffde17e1335920fa10397b85f8635bbfe14f3cd29222a59ea81d978478072\n  languageName: node\n  linkType: hard\n\n\"@esbuild/aix-ppc64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/aix-ppc64@npm:0.25.0\"\n  conditions: os=aix & cpu=ppc64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/android-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/android-arm64@npm:0.25.0\"\n  conditions: os=android & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/android-arm@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/android-arm@npm:0.25.0\"\n  conditions: os=android & cpu=arm\n  languageName: node\n  linkType: hard\n\n\"@esbuild/android-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/android-x64@npm:0.25.0\"\n  conditions: os=android & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/darwin-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/darwin-arm64@npm:0.25.0\"\n  conditions: os=darwin & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/darwin-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/darwin-x64@npm:0.25.0\"\n  conditions: os=darwin & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/freebsd-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/freebsd-arm64@npm:0.25.0\"\n  conditions: os=freebsd & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/freebsd-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/freebsd-x64@npm:0.25.0\"\n  conditions: os=freebsd & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-arm64@npm:0.25.0\"\n  conditions: os=linux & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-arm@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-arm@npm:0.25.0\"\n  conditions: os=linux & cpu=arm\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-ia32@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-ia32@npm:0.25.0\"\n  conditions: os=linux & cpu=ia32\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-loong64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-loong64@npm:0.25.0\"\n  conditions: os=linux & cpu=loong64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-mips64el@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-mips64el@npm:0.25.0\"\n  conditions: os=linux & cpu=mips64el\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-ppc64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-ppc64@npm:0.25.0\"\n  conditions: os=linux & cpu=ppc64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-riscv64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-riscv64@npm:0.25.0\"\n  conditions: os=linux & cpu=riscv64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-s390x@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-s390x@npm:0.25.0\"\n  conditions: os=linux & cpu=s390x\n  languageName: node\n  linkType: hard\n\n\"@esbuild/linux-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/linux-x64@npm:0.25.0\"\n  conditions: os=linux & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/netbsd-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/netbsd-arm64@npm:0.25.0\"\n  conditions: os=netbsd & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/netbsd-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/netbsd-x64@npm:0.25.0\"\n  conditions: os=netbsd & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/openbsd-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/openbsd-arm64@npm:0.25.0\"\n  conditions: os=openbsd & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/openbsd-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/openbsd-x64@npm:0.25.0\"\n  conditions: os=openbsd & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/sunos-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/sunos-x64@npm:0.25.0\"\n  conditions: os=sunos & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/win32-arm64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/win32-arm64@npm:0.25.0\"\n  conditions: os=win32 & cpu=arm64\n  languageName: node\n  linkType: hard\n\n\"@esbuild/win32-ia32@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/win32-ia32@npm:0.25.0\"\n  conditions: os=win32 & cpu=ia32\n  languageName: node\n  linkType: hard\n\n\"@esbuild/win32-x64@npm:0.25.0\":\n  version: 0.25.0\n  resolution: \"@esbuild/win32-x64@npm:0.25.0\"\n  conditions: os=win32 & cpu=x64\n  languageName: node\n  linkType: hard\n\n\"@ffmpeg/ffmpeg@npm:^0.11.6\":\n  version: 0.11.6\n  resolution: \"@ffmpeg/ffmpeg@npm:0.11.6\"\n  dependencies:\n    is-url: \"npm:^1.2.4\"\n    node-fetch: \"npm:^2.6.1\"\n    regenerator-runtime: \"npm:^0.13.7\"\n    resolve-url: \"npm:^0.2.1\"\n  checksum: 10c0/7692b4b14369f81ff2833e93767a202872991ead75eee0490f55668d3b6d1911495bd08879cbb23b4781e8c492a75316eb3c2fde7066cc093235bef4649096d4\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/core@npm:^1.6.0\":\n  version: 1.6.7\n  resolution: \"@floating-ui/core@npm:1.6.7\"\n  dependencies:\n    \"@floating-ui/utils\": \"npm:^0.2.7\"\n  checksum: 10c0/5c9ae274854f87ed09a61de758377d444c2b13ade7fd1067d74287b3e66de5340ae1281e48604b631c540855a2595cfc717adf9a2331eaadc4fa6d28e8571f64\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/dom@npm:^1.0.0\":\n  version: 1.6.10\n  resolution: \"@floating-ui/dom@npm:1.6.10\"\n  dependencies:\n    \"@floating-ui/core\": \"npm:^1.6.0\"\n    \"@floating-ui/utils\": \"npm:^0.2.7\"\n  checksum: 10c0/ed7d7b400e00b2f31f1b8f11863af2cb95d0d3cd84635186ca31b41d8d9fe7fe12c85e4985617d7df7ed365abad48b327d0bae35934842007b4e1052d9780576\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/react-dom@npm:^2.0.1\":\n  version: 2.1.1\n  resolution: \"@floating-ui/react-dom@npm:2.1.1\"\n  dependencies:\n    \"@floating-ui/dom\": \"npm:^1.0.0\"\n  peerDependencies:\n    react: \">=16.8.0\"\n    react-dom: \">=16.8.0\"\n  checksum: 10c0/732ab64600c511ceb0563b87bc557aa61789fec4f416a3f092bab89e508fa1d3ee5ade0f42051cc56eb5e4db867b87ab7fd48ce82db9fd4c01d94ffa08f60115\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/react@npm:0.25.1\":\n  version: 0.25.1\n  resolution: \"@floating-ui/react@npm:0.25.1\"\n  dependencies:\n    \"@floating-ui/react-dom\": \"npm:^2.0.1\"\n    \"@floating-ui/utils\": \"npm:^0.1.1\"\n    tabbable: \"npm:^6.0.1\"\n  peerDependencies:\n    react: \">=16.8.0\"\n    react-dom: \">=16.8.0\"\n  checksum: 10c0/4adf6ea303554e72bff3e92ebe73c4f7aa99ca9b221d110f51208986c8e98495d77bdbd4652420478a8aeabf8dc8ad76d8e204c956d992801e825d1910142947\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/utils@npm:^0.1.1\":\n  version: 0.1.6\n  resolution: \"@floating-ui/utils@npm:0.1.6\"\n  checksum: 10c0/0a089db0e0526b89e83cb0a773a903517db5c9067cd473febfd8fa91a3a2ccbc3a835234796c1bb528def21dbb67be50e28d9c473cb58a6d90679d7e549b9c0c\n  languageName: node\n  linkType: hard\n\n\"@floating-ui/utils@npm:^0.2.7\":\n  version: 0.2.7\n  resolution: \"@floating-ui/utils@npm:0.2.7\"\n  checksum: 10c0/0559ea5df2dc82219bad26e3509e9d2b70f6987e552dc8ddf7d7f5923cfeb7c44bf884567125b1f9cdb122a4c7e6e7ddbc666740bc30b0e4091ccbca63c6fb1c\n  languageName: node\n  linkType: hard\n\n\"@formatjs/ecma402-abstract@npm:2.0.0\":\n  version: 2.0.0\n  resolution: \"@formatjs/ecma402-abstract@npm:2.0.0\"\n  dependencies:\n    \"@formatjs/intl-localematcher\": \"npm:0.5.4\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/94cba291aeadffa3ca416087c2c2352c8a741bb4dcb7f47f15c247b1f043ffcef1af5b20a1b7578fbba9e704fc5f1c079923f3537a273d50162be62f8037625c\n  languageName: node\n  linkType: hard\n\n\"@formatjs/ecma402-abstract@npm:2.3.2\":\n  version: 2.3.2\n  resolution: \"@formatjs/ecma402-abstract@npm:2.3.2\"\n  dependencies:\n    \"@formatjs/fast-memoize\": \"npm:2.2.6\"\n    \"@formatjs/intl-localematcher\": \"npm:0.5.10\"\n    decimal.js: \"npm:10\"\n    tslib: \"npm:2\"\n  checksum: 10c0/364e9e7de974fed976e0e8142a0f888ee0af4a11a61899115e5761ed933e7c1f16379b7b54a01524fd3c5d58bf08b71308237ea969cd54889eaf7bb2d30ec776\n  languageName: node\n  linkType: hard\n\n\"@formatjs/fast-memoize@npm:2.2.0\":\n  version: 2.2.0\n  resolution: \"@formatjs/fast-memoize@npm:2.2.0\"\n  dependencies:\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/ae88c5a93b96235aba4bd9b947d0310d2ec013687a99133413361b24122b5cdea8c9bf2e04a4a2a8b61f1f4ee5419ef6416ca4796554226b5050e05a9ce6ef49\n  languageName: node\n  linkType: hard\n\n\"@formatjs/fast-memoize@npm:2.2.6\":\n  version: 2.2.6\n  resolution: \"@formatjs/fast-memoize@npm:2.2.6\"\n  dependencies:\n    tslib: \"npm:2\"\n  checksum: 10c0/dccdc21105af673e58ec7b04eb17cd6fde1fb1a7e7a446273ca43f7ab97c26d5c0fcc2b9e80d5b54bf9b80354f9e1e681273c0ed26633ec72f0adc2d116dfd7f\n  languageName: node\n  linkType: hard\n\n\"@formatjs/icu-messageformat-parser@npm:2.11.0\":\n  version: 2.11.0\n  resolution: \"@formatjs/icu-messageformat-parser@npm:2.11.0\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.3.2\"\n    \"@formatjs/icu-skeleton-parser\": \"npm:1.8.12\"\n    tslib: \"npm:2\"\n  checksum: 10c0/9ad43847cb4a5c13895af606c634dcf2ec034d484cbbce6566746b60920643f33cbc5e2e3fd1efe21bcfdb555e1ee527e4518768001c3b36bf2e76c171e4049f\n  languageName: node\n  linkType: hard\n\n\"@formatjs/icu-messageformat-parser@npm:2.7.8\":\n  version: 2.7.8\n  resolution: \"@formatjs/icu-messageformat-parser@npm:2.7.8\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/icu-skeleton-parser\": \"npm:1.8.2\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/a3b759a825fb22ffd7b906f6a07b1a079bbc34f72c745de2c2514e439c4bb75bc1a9442eba1bac7ff3ea3010e12076374cd755ad12116b1d066cc90da5fbcbc9\n  languageName: node\n  linkType: hard\n\n\"@formatjs/icu-skeleton-parser@npm:1.8.12\":\n  version: 1.8.12\n  resolution: \"@formatjs/icu-skeleton-parser@npm:1.8.12\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.3.2\"\n    tslib: \"npm:2\"\n  checksum: 10c0/03e743aa09acb2137e37d03b98578fcbbc949d056b8c151763778e885d04d621e69c82f7656547f0532351d2a987bffac0a8c4c3d81186f47a28047ba64385e2\n  languageName: node\n  linkType: hard\n\n\"@formatjs/icu-skeleton-parser@npm:1.8.2\":\n  version: 1.8.2\n  resolution: \"@formatjs/icu-skeleton-parser@npm:1.8.2\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/9b15013acc47b8d560b52942e3dab2abaaa9c5a4410bbd1d490a4b22bf5ca36fdd88b71f241d05479bddf856d0d1d57b7ecc9e79738497ac518616aa6d4d0015\n  languageName: node\n  linkType: hard\n\n\"@formatjs/intl-displaynames@npm:6.6.8\":\n  version: 6.6.8\n  resolution: \"@formatjs/intl-displaynames@npm:6.6.8\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/intl-localematcher\": \"npm:0.5.4\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/1a03e7644022741c1bcf10fcd07da88c434416a13603ace693a038114010463307b4130d3a3f53ad5665bd27fca9a6b19ac8e5bf58e17598b1ea84db173fdfbb\n  languageName: node\n  linkType: hard\n\n\"@formatjs/intl-listformat@npm:7.5.7\":\n  version: 7.5.7\n  resolution: \"@formatjs/intl-listformat@npm:7.5.7\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/intl-localematcher\": \"npm:0.5.4\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/5d0478752d669d87c82aa80880df464d64a1c8974fcb6136bc854567f570a1696e5468005ffa266cfcb623adb7c7299b839c06ea33897f55d35dab6a7575cc84\n  languageName: node\n  linkType: hard\n\n\"@formatjs/intl-localematcher@npm:0.5.10\":\n  version: 0.5.10\n  resolution: \"@formatjs/intl-localematcher@npm:0.5.10\"\n  dependencies:\n    tslib: \"npm:2\"\n  checksum: 10c0/362ec83aca9382165be575f1cefa477478339e6fead8ca8866185ce6e58427ea1487a811b12c73d1bcfa99fd4db0c24543b35c823451839f585576bfccb8c9cc\n  languageName: node\n  linkType: hard\n\n\"@formatjs/intl-localematcher@npm:0.5.4\":\n  version: 0.5.4\n  resolution: \"@formatjs/intl-localematcher@npm:0.5.4\"\n  dependencies:\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/c9ff5d34ca8b6fe59f8f303a3cc31a92d343e095a6987e273e5cc23f0fe99feb557a392a05da95931c7d24106acb6988e588d00ddd05b0934005aafd7fdbafe6\n  languageName: node\n  linkType: hard\n\n\"@formatjs/intl@npm:2.10.4\":\n  version: 2.10.4\n  resolution: \"@formatjs/intl@npm:2.10.4\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/fast-memoize\": \"npm:2.2.0\"\n    \"@formatjs/icu-messageformat-parser\": \"npm:2.7.8\"\n    \"@formatjs/intl-displaynames\": \"npm:6.6.8\"\n    \"@formatjs/intl-listformat\": \"npm:7.5.7\"\n    intl-messageformat: \"npm:10.5.14\"\n    tslib: \"npm:^2.4.0\"\n  peerDependencies:\n    typescript: ^4.7 || 5\n  peerDependenciesMeta:\n    typescript:\n      optional: true\n  checksum: 10c0/ca7877e962f73f1fe0e358f12d73bdc3ec4006c14ee801e06d9f7aef06bcdcc12355a8f53f32b0e890f829949ded35e825c914ca5f4709eb1e08c2a18c1368c2\n  languageName: node\n  linkType: hard\n\n\"@geoarrow/geoarrow-js@npm:^0.3.0\":\n  version: 0.3.1\n  resolution: \"@geoarrow/geoarrow-js@npm:0.3.1\"\n  dependencies:\n    \"@math.gl/polygon\": \"npm:^4.0.0\"\n    proj4: \"npm:^2.9.2\"\n    threads: \"npm:^1.7.0\"\n  peerDependencies:\n    apache-arrow: \">=15\"\n  checksum: 10c0/0f7cb53ab43651cdca5ab7e0055ba1a7a3b1ce69f86c6ddabc2d5375b7426895a12ea3b35e0c2aba208b0660b916c7c5e7f7635c0830e7a5d1fc4a03146b0907\n  languageName: node\n  linkType: hard\n\n\"@geoda/core@npm:^0.0.22\":\n  version: 0.0.22\n  resolution: \"@geoda/core@npm:0.0.22\"\n  dependencies:\n    \"@loaders.gl/schema\": \"npm:^4.1.0-alpha.4\"\n    geojson: \"npm:^0.5.0\"\n  checksum: 10c0/228d4278a0e18f9cd4877cedb5624c537dc487aa8d6eec0ffba74cc0ee7a7321dfdafa962ac95de016d0e8dd6f79edc46ebb1c37f365d82a65415bfc61a77f82\n  languageName: node\n  linkType: hard\n\n\"@geoda/lisa@npm:^0.0.22\":\n  version: 0.0.22\n  resolution: \"@geoda/lisa@npm:0.0.22\"\n  dependencies:\n    \"@loaders.gl/schema\": \"npm:^4.1.0-alpha.4\"\n    geojson: \"npm:^0.5.0\"\n  checksum: 10c0/60a8f37f7cb7eadb256eaf10161a9896ebf40c7ab066814d30cd81cd9e249e61fd7466f22735f86aaa993c0bde3a5d4ddb50836e594f4207a6e293df4890404b\n  languageName: node\n  linkType: hard\n\n\"@geoda/regression@npm:^0.0.22\":\n  version: 0.0.22\n  resolution: \"@geoda/regression@npm:0.0.22\"\n  checksum: 10c0/f705bf24c82be77268a6703a21252a92216ea40672015ff84dd4215408d976b810fb45934fe46644bbf00b786ebc059c59d0308839f244279cb74c0cc11f4d73\n  languageName: node\n  linkType: hard\n\n\"@heroui/accordion@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/accordion@npm:2.2.16\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/divider\": \"npm:2.2.13\"\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-accordion\": \"npm:2.2.11\"\n    \"@react-aria/button\": \"npm:3.13.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/tree\": \"npm:3.8.9\"\n    \"@react-types/accordion\": \"npm:3.0.0-alpha.26\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/9b696113f3c09403de5b55c4ef00d671ad595a85ca59711c24175c111bdb96b3f10aaa8b73c488519f27b93dcd00fb4dacb1770496e6e19473a8865c27c133d2\n  languageName: node\n  linkType: hard\n\n\"@heroui/alert@npm:2.2.19\":\n  version: 2.2.19\n  resolution: \"@heroui/alert@npm:2.2.19\"\n  dependencies:\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/15765a21481253cd3ead2ed6177223633168e61bf6b28b5ca9f5ac962d28421a4f776ec44f80ce273b14407d08d57eec4dd78afe291002c74a419e95ea12b461\n  languageName: node\n  linkType: hard\n\n\"@heroui/aria-utils@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/aria-utils@npm:2.2.16\"\n  dependencies:\n    \"@heroui/react-rsc-utils\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system\": \"npm:2.4.15\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/collections\": \"npm:3.12.3\"\n    \"@react-stately/overlays\": \"npm:3.6.15\"\n    \"@react-types/overlays\": \"npm:3.8.14\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/d70329fa02b3fc40db6d6f6cb7b04f1cd9efba56d79a24f1a1687322d0b98b63d5d7bc9eb5230f33ec4577864685fed6ca8bc0bf233095dcdc338237f9d0efbf\n  languageName: node\n  linkType: hard\n\n\"@heroui/aria-utils@npm:2.2.21\":\n  version: 2.2.21\n  resolution: \"@heroui/aria-utils@npm:2.2.21\"\n  dependencies:\n    \"@heroui/system\": \"npm:2.4.20\"\n    \"@react-aria/utils\": \"npm:3.30.0\"\n    \"@react-stately/collections\": \"npm:3.12.6\"\n    \"@react-types/overlays\": \"npm:3.9.0\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/6998fab4f3b664a79943220cdcfaf928783296a80dd163c88d97723935a8f48c0cd107710007e2537b86afb3224696d3c85bc4b77f63cb52605325c465951fef\n  languageName: node\n  linkType: hard\n\n\"@heroui/autocomplete@npm:2.3.20\":\n  version: 2.3.20\n  resolution: \"@heroui/autocomplete@npm:2.3.20\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/input\": \"npm:2.4.19\"\n    \"@heroui/listbox\": \"npm:2.3.18\"\n    \"@heroui/popover\": \"npm:2.3.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/scroll-shadow\": \"npm:2.3.13\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/spinner\": \"npm:2.2.16\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/combobox\": \"npm:3.12.2\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/combobox\": \"npm:3.10.4\"\n    \"@react-types/combobox\": \"npm:3.13.4\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/15e9aaa2b7c3678c1e7b307308042fe95e0723aa0758846d4c2d040b3e7a8d0e23fab8fe8248545f23fefe2116bc45aaee1738353c9e3a83c58836daa8dc49c4\n  languageName: node\n  linkType: hard\n\n\"@heroui/avatar@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/avatar@npm:2.2.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-image\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/f48f5cc364a30164a85e8c2f7f2eafeff1ba6ac0cf35d13147e29772bad7fbfc90cd51a21e1873d711fc170d2e85fe3ea1d6cfef5d5bc00acc8348daaadb16ea\n  languageName: node\n  linkType: hard\n\n\"@heroui/badge@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/badge@npm:2.2.12\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/8dbb207a20bf8d9ac9ab7220ef7191f087390fb615234cab2e7fe29aaf4ea07229a6714baff2cbd261e850466250f357b25801be9923a9557c0d5bab308e8bed\n  languageName: node\n  linkType: hard\n\n\"@heroui/breadcrumbs@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/breadcrumbs@npm:2.2.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/breadcrumbs\": \"npm:3.5.23\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/breadcrumbs\": \"npm:3.7.12\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/f76b18d8c6949b2df0ea31b5d8ff9222ff9b25d48d938753692f85e6fdf28e28febbcfe65377335acfca46d1cceaf0117257b0415b171cedaa7c3ea66955992a\n  languageName: node\n  linkType: hard\n\n\"@heroui/button@npm:2.2.19\":\n  version: 2.2.19\n  resolution: \"@heroui/button@npm:2.2.19\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/ripple\": \"npm:2.2.14\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/spinner\": \"npm:2.2.16\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@react-aria/button\": \"npm:3.13.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/c9d590b9758cc6d5373d42c550ee5f22856bf5f5a1182e2071ef8913fae7ad34d4c54614fb0df61ac6d55738d1180b01d78dfbe6b1f10e6121a877716652aa93\n  languageName: node\n  linkType: hard\n\n\"@heroui/button@npm:2.2.24, @heroui/button@npm:^2.2.19\":\n  version: 2.2.24\n  resolution: \"@heroui/button@npm:2.2.24\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/ripple\": \"npm:2.2.18\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/spinner\": \"npm:2.2.21\"\n    \"@heroui/use-aria-button\": \"npm:2.2.18\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/e783f0d24fd3f8146d2e3f3dd1c7765107ac78ed94975154be296b877353289c7c057d0aeb83b3a105b3e047635dd3d64d51131aff228e9862b47d1923df256d\n  languageName: node\n  linkType: hard\n\n\"@heroui/calendar@npm:2.2.19\":\n  version: 2.2.19\n  resolution: \"@heroui/calendar@npm:2.2.19\"\n  dependencies:\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@internationalized/date\": \"npm:3.8.0\"\n    \"@react-aria/calendar\": \"npm:3.8.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/calendar\": \"npm:3.8.0\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/calendar\": \"npm:3.7.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n    \"@types/lodash.debounce\": \"npm:^4.0.7\"\n    scroll-into-view-if-needed: \"npm:3.0.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/9305e63570afbd397e9478aba0e2f3bf4363c796d4ae645bfcd4d35a1da96341a7e7f29837e7c9baeaedc80c527f490f09c3d2ae95800dc40bae77b57f03d5dd\n  languageName: node\n  linkType: hard\n\n\"@heroui/card@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/card@npm:2.2.18\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/ripple\": \"npm:2.2.14\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@react-aria/button\": \"npm:3.13.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a4371beae378eb1d953d9fdeb86b74ff4f0bdda07172f125302a882cc559fc6e29fe3175ce990d90b268e1ea3a7ff2a8bb9603833c94dec91ecd2b4bfa96bfeb\n  languageName: node\n  linkType: hard\n\n\"@heroui/checkbox@npm:2.3.18\":\n  version: 2.3.18\n  resolution: \"@heroui/checkbox@npm:2.3.18\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-callback-ref\": \"npm:2.1.7\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/checkbox\": \"npm:3.15.4\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/checkbox\": \"npm:3.6.13\"\n    \"@react-stately/toggle\": \"npm:3.8.3\"\n    \"@react-types/checkbox\": \"npm:3.9.3\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.3\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/3d75aace30d39fe7bc2f6d0bf7b5baa797ebeb4d1dd532d3a3e278f9116a180de00140f80d73d943cd3eac8ba455ff67dc5effcbb19cab8136e6af0188615769\n  languageName: node\n  linkType: hard\n\n\"@heroui/checkbox@npm:2.3.24, @heroui/checkbox@npm:^2.3.18\":\n  version: 2.3.24\n  resolution: \"@heroui/checkbox@npm:2.3.24\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.24\"\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/use-callback-ref\": \"npm:2.1.8\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.8\"\n    \"@react-aria/checkbox\": \"npm:3.16.0\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-stately/checkbox\": \"npm:3.7.0\"\n    \"@react-stately/toggle\": \"npm:3.9.0\"\n    \"@react-types/checkbox\": \"npm:3.10.0\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/6eeaa17d7e8ec076dcc666ad1fc658a308d55000525b36ad97a6f0b9e540f025fb4e3db7955e0b195699d1c21c61127adde3068d86472b9ebf225ec48813a2e9\n  languageName: node\n  linkType: hard\n\n\"@heroui/chip@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/chip@npm:2.2.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/checkbox\": \"npm:3.9.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0b2cd9eadfefea77ee2e48e8f98a74935ddd059446fd793f667bee536a4b81a2b476255586e79b8502c5dc14b1607a104b8acb72b9cd7d3ef9e9183c8dc65d27\n  languageName: node\n  linkType: hard\n\n\"@heroui/code@npm:2.2.14\":\n  version: 2.2.14\n  resolution: \"@heroui/code@npm:2.2.14\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/d57bd9d2fd3326baeb54da5d0b07b3cdca4cc340c5cd81f83a128b6f76d18cc769545878898c2ff95efcbe6a9025d729c888f7363311b162783561f163a5942a\n  languageName: node\n  linkType: hard\n\n\"@heroui/date-input@npm:2.3.18\":\n  version: 2.3.18\n  resolution: \"@heroui/date-input@npm:2.3.18\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@internationalized/date\": \"npm:3.8.0\"\n    \"@react-aria/datepicker\": \"npm:3.14.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/datepicker\": \"npm:3.14.0\"\n    \"@react-types/datepicker\": \"npm:3.12.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.9\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/b42039dbc056ea90c0a289444418e2a69bbf21daeb1f245d47e2fb67cca888df69e720a61c899691a94baae99248ebae5938739e0e643ca81ea67384202c3f6c\n  languageName: node\n  linkType: hard\n\n\"@heroui/date-picker@npm:2.3.19\":\n  version: 2.3.19\n  resolution: \"@heroui/date-picker@npm:2.3.19\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/calendar\": \"npm:2.2.19\"\n    \"@heroui/date-input\": \"npm:2.3.18\"\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/popover\": \"npm:2.3.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@internationalized/date\": \"npm:3.8.0\"\n    \"@react-aria/datepicker\": \"npm:3.14.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/datepicker\": \"npm:3.14.0\"\n    \"@react-stately/overlays\": \"npm:3.6.15\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/datepicker\": \"npm:3.12.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.9\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/375d35d03ba6968c0cbd74d406f309fc55a99890564654e636534d29f6088edb86bccfcf390268264eee9503d15ec8579ca103e3cb7f664c3ef6bb32b3b9b338\n  languageName: node\n  linkType: hard\n\n\"@heroui/divider@npm:2.2.13\":\n  version: 2.2.13\n  resolution: \"@heroui/divider@npm:2.2.13\"\n  dependencies:\n    \"@heroui/react-rsc-utils\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/7681905ec64cc37b93490a4f7d17cfdf594c990d5c85abdd36d3874722f874489b7d7e234e0bd4668d88112e5c73231964b04f2be490954b5bf099449fe5ce5a\n  languageName: node\n  linkType: hard\n\n\"@heroui/divider@npm:2.2.17\":\n  version: 2.2.17\n  resolution: \"@heroui/divider@npm:2.2.17\"\n  dependencies:\n    \"@heroui/react-rsc-utils\": \"npm:2.1.9\"\n    \"@heroui/system-rsc\": \"npm:2.3.17\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/1ce07b792fd9c14c20535a46d31d56a09c5d5955c0e2cd0c4a451cef0cd996faeeb4c218ccb8e6f25e976674c0b098d9489fb963b349ed3c58f0edaabb4f034e\n  languageName: node\n  linkType: hard\n\n\"@heroui/dom-animation@npm:2.1.10\":\n  version: 2.1.10\n  resolution: \"@heroui/dom-animation@npm:2.1.10\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n  checksum: 10c0/dc111d80b4364109f45610cd9f3ff8ed75b92ef97b86f74685d626ac27d9a58d59ab1c8505d53830aad4ebd2fd22587213dcfde9d2d126a4c0e61071b6291300\n  languageName: node\n  linkType: hard\n\n\"@heroui/dom-animation@npm:2.1.8\":\n  version: 2.1.8\n  resolution: \"@heroui/dom-animation@npm:2.1.8\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n  checksum: 10c0/731d0baef0fe2dfec9700734f0e0ac5c9ca51e3e6fb0335517e99057049efe831a192d286f0311bd98a484ccf672954b0eabb1cad57cc945f46586a9dde6bd9d\n  languageName: node\n  linkType: hard\n\n\"@heroui/drawer@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/drawer@npm:2.2.16\"\n  dependencies:\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/modal\": \"npm:2.2.16\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/baf8964787b93effebc5b736e38175417955dbc14935db8cb41afb0f585e298e9b0f9ac103a109f833f0dfdfc4ce1759315d62e78ae557b4d6a25901938c887e\n  languageName: node\n  linkType: hard\n\n\"@heroui/dropdown@npm:2.3.19\":\n  version: 2.3.19\n  resolution: \"@heroui/dropdown@npm:2.3.19\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/menu\": \"npm:2.2.18\"\n    \"@heroui/popover\": \"npm:2.3.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/menu\": \"npm:3.18.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/menu\": \"npm:3.9.3\"\n    \"@react-types/menu\": \"npm:3.10.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/59c0b9b086be578c39478adf645d43341a998d63d0edf09220f783a3128acb59d105939db25d29505b1a9ed8b7eb2cb94e000fc0a136aeb17b416eb0d3ea3c66\n  languageName: node\n  linkType: hard\n\n\"@heroui/form@npm:2.1.18\":\n  version: 2.1.18\n  resolution: \"@heroui/form@npm:2.1.18\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system\": \"npm:2.4.15\"\n    \"@heroui/theme\": \"npm:2.4.15\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/form\": \"npm:3.1.3\"\n    \"@react-types/form\": \"npm:3.7.11\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18\"\n    react-dom: \">=18\"\n  checksum: 10c0/c25d273c8a27b211663a90ac4c563ceb5e9d082254ea065b8b4e6fa576fd79a4e05937a9f2075746c4c18c2c976f1dd7b91402819c69be7c9e03082533a46403\n  languageName: node\n  linkType: hard\n\n\"@heroui/form@npm:2.1.24\":\n  version: 2.1.24\n  resolution: \"@heroui/form@npm:2.1.24\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/system\": \"npm:2.4.20\"\n    \"@heroui/theme\": \"npm:2.4.20\"\n    \"@react-stately/form\": \"npm:3.2.0\"\n    \"@react-types/form\": \"npm:3.7.14\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18\"\n    react-dom: \">=18\"\n  checksum: 10c0/86697ad51502ba862caaaa763b5eab3de4a42c13da49799fd7f09ebc276246f33edce468885f2f2453fdb4b2cb390949ec40ef45b3210776bda15e4cc19ed01b\n  languageName: node\n  linkType: hard\n\n\"@heroui/framer-utils@npm:2.1.15\":\n  version: 2.1.15\n  resolution: \"@heroui/framer-utils@npm:2.1.15\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system\": \"npm:2.4.15\"\n    \"@heroui/use-measure\": \"npm:2.1.7\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a69a2e809e2f7c719eaf54d58440563c825849ac7c4aecaed77f242b8dbf60b563acffdc6e2e07ad61b7c4f0df1f1e752a2dc3f29d394d7f03e42496a59bb177\n  languageName: node\n  linkType: hard\n\n\"@heroui/framer-utils@npm:2.1.20\":\n  version: 2.1.20\n  resolution: \"@heroui/framer-utils@npm:2.1.20\"\n  dependencies:\n    \"@heroui/system\": \"npm:2.4.20\"\n    \"@heroui/use-measure\": \"npm:2.1.8\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/7266ec678720c32204e5db0b6a90d666c7a5700a7c981e12d8f828f01940e5566972ac3d805234c9ea74d177a84f4280d4c80e47831ac54a9a02b9a511acd4c8\n  languageName: node\n  linkType: hard\n\n\"@heroui/image@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/image@npm:2.2.12\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-image\": \"npm:2.1.9\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/922275600c716ab4664310e00c42444598c287347d1f69a05aa00b788f01fd37df29a26f10d0974034c5f95b8376a95f50f6eec98032fa67548095210fa01cdc\n  languageName: node\n  linkType: hard\n\n\"@heroui/input-otp@npm:2.1.18\":\n  version: 2.1.18\n  resolution: \"@heroui/input-otp@npm:2.1.18\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/form\": \"npm:3.0.15\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/form\": \"npm:3.1.3\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/textfield\": \"npm:3.12.1\"\n    input-otp: \"npm:1.4.1\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.13\"\n    react: \">=18\"\n    react-dom: \">=18\"\n  checksum: 10c0/4c31e675b85cc1f495eecf8ae52831ac2d4bf43b976cdcad1fc229872a5bde40f1b72968a31c860aec829c6d2661f9b77267e8348a2706cd448e621d1dd46f06\n  languageName: node\n  linkType: hard\n\n\"@heroui/input@npm:2.4.19\":\n  version: 2.4.19\n  resolution: \"@heroui/input@npm:2.4.19\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/textfield\": \"npm:3.17.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n    \"@react-types/textfield\": \"npm:3.12.1\"\n    react-textarea-autosize: \"npm:^8.5.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.12\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/17b943518ea5a0bf65719981e0b45f2514ccbdd6de864772e6f9bcca0c2010e43f39fb42972510272518c3a59c46294167932a8e91d4275207c3805ba6aed0ba\n  languageName: node\n  linkType: hard\n\n\"@heroui/kbd@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/kbd@npm:2.2.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/8c6e28d8503d8d2b2547fae2113000679df1e6d64bc518478f931fb12660dbb1e70ddf47a35ab16784f422ff3801de5c8ce57f5cd92e23c6d521e589c16409db\n  languageName: node\n  linkType: hard\n\n\"@heroui/link@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/link@npm:2.2.16\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-link\": \"npm:2.2.14\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/link\": \"npm:3.8.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/link\": \"npm:3.6.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0c4f523215d593d18b64f4234c9e28b237cc6339e56104c858cb163f06d860d084ecaed4d7343157dbf3a8dfc64ffb725246f16e7f879d65f2208a89f247f2fa\n  languageName: node\n  linkType: hard\n\n\"@heroui/listbox@npm:2.3.18\":\n  version: 2.3.18\n  resolution: \"@heroui/listbox@npm:2.3.18\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/divider\": \"npm:2.2.13\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-is-mobile\": \"npm:2.2.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/listbox\": \"npm:3.14.3\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/list\": \"npm:3.12.1\"\n    \"@react-types/menu\": \"npm:3.10.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n    \"@tanstack/react-virtual\": \"npm:3.11.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/bff30b43e4916feed85c1089694c499d6972426999ffaf94f644e73ca08e12d8f1f98e7a2c4a4bd937f1981fd353e3473730bca9ce34e9d218535b19a787ee3b\n  languageName: node\n  linkType: hard\n\n\"@heroui/listbox@npm:2.3.23\":\n  version: 2.3.23\n  resolution: \"@heroui/listbox@npm:2.3.23\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.21\"\n    \"@heroui/divider\": \"npm:2.2.17\"\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/use-is-mobile\": \"npm:2.2.12\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/listbox\": \"npm:3.14.7\"\n    \"@react-stately/list\": \"npm:3.12.4\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n    \"@tanstack/react-virtual\": \"npm:3.11.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/395ae59a33eeda2180c02fbacb4c496d3b103d5e07b5d109eafc04f601eddef589bf75a1f4d2e855ba27a7890aff4f9ef856cc4cb594b227a1a350b02bf43e18\n  languageName: node\n  linkType: hard\n\n\"@heroui/menu@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/menu@npm:2.2.18\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/divider\": \"npm:2.2.13\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-is-mobile\": \"npm:2.2.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/menu\": \"npm:3.18.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/menu\": \"npm:3.9.3\"\n    \"@react-stately/tree\": \"npm:3.8.9\"\n    \"@react-types/menu\": \"npm:3.10.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/c589b05d9af83297bf1397ece52fcf305a698a53ef66af58f2cd7dc9f8a609e893bcf85e1fb8bfb743df4b3dcc0a177b7b38c180b2a212747ab5e46ddbc13f1c\n  languageName: node\n  linkType: hard\n\n\"@heroui/modal@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/modal@npm:2.2.16\"\n  dependencies:\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@heroui/use-aria-modal-overlay\": \"npm:2.2.12\"\n    \"@heroui/use-disclosure\": \"npm:2.2.11\"\n    \"@heroui/use-draggable\": \"npm:2.1.11\"\n    \"@react-aria/dialog\": \"npm:3.5.24\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/overlays\": \"npm:3.6.15\"\n    \"@react-types/overlays\": \"npm:3.8.14\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/c1f96831d8854757faa244407855f2dec9941f40ba3d1fb28cd0de7438b59885548994f6eee0e2a68c0d18b853ac2016306cd33eee0c612a97c84ba48b6d53f3\n  languageName: node\n  linkType: hard\n\n\"@heroui/navbar@npm:2.2.17\":\n  version: 2.2.17\n  resolution: \"@heroui/navbar@npm:2.2.17\"\n  dependencies:\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-scroll-position\": \"npm:2.1.7\"\n    \"@react-aria/button\": \"npm:3.13.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/toggle\": \"npm:3.8.3\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/72b223d9c696b27e957b1ae401679429715a737dd938821ec34bcdc885003673add9d47199776b60bb8d96918d7d2b067f18dbc948de90f6f8c4115e910f9d8a\n  languageName: node\n  linkType: hard\n\n\"@heroui/number-input@npm:2.0.9\":\n  version: 2.0.9\n  resolution: \"@heroui/number-input@npm:2.0.9\"\n  dependencies:\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/numberfield\": \"npm:3.11.13\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/numberfield\": \"npm:3.9.11\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/numberfield\": \"npm:3.8.10\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.9\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/2b66932ed402d25f4f06ba9b00cf9115b7ae603885e8672669423676404872bc4f1eb8f2de00876221a903ae947ebf058e68fc09ed8043883185458fab25d650\n  languageName: node\n  linkType: hard\n\n\"@heroui/pagination@npm:2.2.17\":\n  version: 2.2.17\n  resolution: \"@heroui/pagination@npm:2.2.17\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-intersection-observer\": \"npm:2.2.11\"\n    \"@heroui/use-pagination\": \"npm:2.2.12\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    scroll-into-view-if-needed: \"npm:3.0.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/303307b34007d5ab2320a5ddeafdc3a42702349d50b0317d39fd069650e20e738cb7e1ae5c6ed2b06094196035e65ccc9861e9af1942d1a3e9e0a2cb8570f0dd\n  languageName: node\n  linkType: hard\n\n\"@heroui/pagination@npm:^2.2.17\":\n  version: 2.2.22\n  resolution: \"@heroui/pagination@npm:2.2.22\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-icons\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/use-intersection-observer\": \"npm:2.2.14\"\n    \"@heroui/use-pagination\": \"npm:2.2.16\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/i18n\": \"npm:3.12.11\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/utils\": \"npm:3.30.0\"\n    scroll-into-view-if-needed: \"npm:3.0.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/48d660967b6e0fdca281ee569c95e28834d67be6ec784edc57c9c64fcfc1e1f7035452b93e1d54794e2fa525b7d2b4d3e54cfec03d61678583bcb08b6a109528\n  languageName: node\n  linkType: hard\n\n\"@heroui/popover@npm:2.3.19\":\n  version: 2.3.19\n  resolution: \"@heroui/popover@npm:2.3.19\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/dialog\": \"npm:3.5.24\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/overlays\": \"npm:3.6.15\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/overlays\": \"npm:3.8.14\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/64a250e05e8838475f6fd24d54a9cdebb6ead008f1bf0c88581956532010643b9746c79e5557c933e7ff97956ad528d659ecb833aaea2f7a49dcf5e1a118e052\n  languageName: node\n  linkType: hard\n\n\"@heroui/popover@npm:2.3.24\":\n  version: 2.3.24\n  resolution: \"@heroui/popover@npm:2.3.24\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.21\"\n    \"@heroui/button\": \"npm:2.2.24\"\n    \"@heroui/dom-animation\": \"npm:2.1.10\"\n    \"@heroui/framer-utils\": \"npm:2.1.20\"\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/use-aria-button\": \"npm:2.2.18\"\n    \"@heroui/use-aria-overlay\": \"npm:2.0.2\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.8\"\n    \"@react-aria/dialog\": \"npm:3.5.28\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/overlays\": \"npm:3.28.0\"\n    \"@react-stately/overlays\": \"npm:3.6.18\"\n    \"@react-types/overlays\": \"npm:3.9.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a3b8cda2e95c25c120c4fc4955816c3f0ea14294c57f6ab3db5e630c4f1fb9c54b11f4d5ad8fbef3f01a9ff8093c9c61e9840187f038169c9f8831fabb12f811\n  languageName: node\n  linkType: hard\n\n\"@heroui/progress@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/progress@npm:2.2.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-is-mounted\": \"npm:2.1.7\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/progress\": \"npm:3.4.22\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/progress\": \"npm:3.5.11\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/dfbbf675db9da8358687b5a5e79692a853b4f9d2494f2bbc9a4a8d446764faf4e95903de2651698d1888b3a5650a2a2f904a30f3d247e2b141884d5f46b31a1a\n  languageName: node\n  linkType: hard\n\n\"@heroui/radio@npm:2.3.18\":\n  version: 2.3.18\n  resolution: \"@heroui/radio@npm:2.3.18\"\n  dependencies:\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/radio\": \"npm:3.11.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/radio\": \"npm:3.10.12\"\n    \"@react-types/radio\": \"npm:3.8.8\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.3\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/79c784535bb0f5873ffb63a9df6e7461d29557828963cf03d809b2915ad739a5048143912163601d3d3bf7ffaa13210db02b65c2ccdbb1550ef2217e967cb66c\n  languageName: node\n  linkType: hard\n\n\"@heroui/react-rsc-utils@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/react-rsc-utils@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/3828b02a790bba27f289a2262134c4d7db8f91731aa66ba7607a9b4e2d71b460ce3cfadf3b97a552175acd9516b2c4f86852cdb21fa2d3aec409b03beb588003\n  languageName: node\n  linkType: hard\n\n\"@heroui/react-rsc-utils@npm:2.1.9\":\n  version: 2.1.9\n  resolution: \"@heroui/react-rsc-utils@npm:2.1.9\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/b3b400c4ae701a89d9bd7301d41f49861eb2cf891dd1b6ce5f9b31260197beb9ac947fa434ec26d91d4214f88e1c049c84a0d50846530081de0dd81a01cfdcb2\n  languageName: node\n  linkType: hard\n\n\"@heroui/react-utils@npm:2.1.10\":\n  version: 2.1.10\n  resolution: \"@heroui/react-utils@npm:2.1.10\"\n  dependencies:\n    \"@heroui/react-rsc-utils\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/d2fda2d81fff7a2e6dce86902a3700cbb292676bd57fa3972567d6c62c14d2d30b28df7a2a30fe684f1bd1520199bf2fdbf1012faba4b7bc584725f683fd5a9e\n  languageName: node\n  linkType: hard\n\n\"@heroui/react-utils@npm:2.1.12\":\n  version: 2.1.12\n  resolution: \"@heroui/react-utils@npm:2.1.12\"\n  dependencies:\n    \"@heroui/react-rsc-utils\": \"npm:2.1.9\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/4f07dda2eaef3f37d48c11d9ffe3c3415a50ce311c57a41bc2aa49a484d2e2c5314c557553b146976a9f01ca6a32c7a4d4d24fdafd9fd357c8adeb8f04f609d9\n  languageName: node\n  linkType: hard\n\n\"@heroui/react@npm:^2.7.8\":\n  version: 2.7.8\n  resolution: \"@heroui/react@npm:2.7.8\"\n  dependencies:\n    \"@heroui/accordion\": \"npm:2.2.16\"\n    \"@heroui/alert\": \"npm:2.2.19\"\n    \"@heroui/autocomplete\": \"npm:2.3.20\"\n    \"@heroui/avatar\": \"npm:2.2.15\"\n    \"@heroui/badge\": \"npm:2.2.12\"\n    \"@heroui/breadcrumbs\": \"npm:2.2.15\"\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/calendar\": \"npm:2.2.19\"\n    \"@heroui/card\": \"npm:2.2.18\"\n    \"@heroui/checkbox\": \"npm:2.3.18\"\n    \"@heroui/chip\": \"npm:2.2.15\"\n    \"@heroui/code\": \"npm:2.2.14\"\n    \"@heroui/date-input\": \"npm:2.3.18\"\n    \"@heroui/date-picker\": \"npm:2.3.19\"\n    \"@heroui/divider\": \"npm:2.2.13\"\n    \"@heroui/drawer\": \"npm:2.2.16\"\n    \"@heroui/dropdown\": \"npm:2.3.19\"\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/image\": \"npm:2.2.12\"\n    \"@heroui/input\": \"npm:2.4.19\"\n    \"@heroui/input-otp\": \"npm:2.1.18\"\n    \"@heroui/kbd\": \"npm:2.2.15\"\n    \"@heroui/link\": \"npm:2.2.16\"\n    \"@heroui/listbox\": \"npm:2.3.18\"\n    \"@heroui/menu\": \"npm:2.2.18\"\n    \"@heroui/modal\": \"npm:2.2.16\"\n    \"@heroui/navbar\": \"npm:2.2.17\"\n    \"@heroui/number-input\": \"npm:2.0.9\"\n    \"@heroui/pagination\": \"npm:2.2.17\"\n    \"@heroui/popover\": \"npm:2.3.19\"\n    \"@heroui/progress\": \"npm:2.2.15\"\n    \"@heroui/radio\": \"npm:2.3.18\"\n    \"@heroui/ripple\": \"npm:2.2.14\"\n    \"@heroui/scroll-shadow\": \"npm:2.3.13\"\n    \"@heroui/select\": \"npm:2.4.19\"\n    \"@heroui/skeleton\": \"npm:2.2.12\"\n    \"@heroui/slider\": \"npm:2.4.16\"\n    \"@heroui/snippet\": \"npm:2.2.20\"\n    \"@heroui/spacer\": \"npm:2.2.14\"\n    \"@heroui/spinner\": \"npm:2.2.16\"\n    \"@heroui/switch\": \"npm:2.2.17\"\n    \"@heroui/system\": \"npm:2.4.15\"\n    \"@heroui/table\": \"npm:2.2.18\"\n    \"@heroui/tabs\": \"npm:2.2.16\"\n    \"@heroui/theme\": \"npm:2.4.15\"\n    \"@heroui/toast\": \"npm:2.0.9\"\n    \"@heroui/tooltip\": \"npm:2.2.16\"\n    \"@heroui/user\": \"npm:2.2.15\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/574790aff3dfe2a55136a6012ea76106b3504a7d0d6d952d57964203f46c8878ce6f0509eb170b7eaed922dda233316c3d670915cc038f472385f10207d63842\n  languageName: node\n  linkType: hard\n\n\"@heroui/ripple@npm:2.2.14\":\n  version: 2.2.14\n  resolution: \"@heroui/ripple@npm:2.2.14\"\n  dependencies:\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0cc48c88cd4a6f5806237444cefb13458a24f71cffd344e1aaf288c3096844a04a38b0a93361c5c38ed8806e9f0a04a479b2e053d46e9c66e4fde6e353f0dd95\n  languageName: node\n  linkType: hard\n\n\"@heroui/ripple@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/ripple@npm:2.2.18\"\n  dependencies:\n    \"@heroui/dom-animation\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/7f2e601988132404fb354c14349d071219d24a8d5d41b33a6510a14a0e82f0c9e4331d6194c63dc9d3c90b4f4d9acee0c8c41a7e786ef943c1f6457759f7a891\n  languageName: node\n  linkType: hard\n\n\"@heroui/scroll-shadow@npm:2.3.13\":\n  version: 2.3.13\n  resolution: \"@heroui/scroll-shadow@npm:2.3.13\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-data-scroll-overflow\": \"npm:2.2.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/13e01555e3a89c5bb5dcced9de5407a7c688e77cb777c9ddfc33c50b83403406f721e8d688ce2cd0b4be314209f6c3f3f4b38c5272b3a56b9b7695bfeb663cd1\n  languageName: node\n  linkType: hard\n\n\"@heroui/scroll-shadow@npm:2.3.16\":\n  version: 2.3.16\n  resolution: \"@heroui/scroll-shadow@npm:2.3.16\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/use-data-scroll-overflow\": \"npm:2.2.11\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/ba2e48d9c04be58ca75a2f40602313c74de4569daa885b222aeb20520fcba46b7304e118e9a367501eb0cf0a4aa64cd852ae77291ccbd0631e8c3a0e22b5e7a6\n  languageName: node\n  linkType: hard\n\n\"@heroui/select@npm:2.4.19\":\n  version: 2.4.19\n  resolution: \"@heroui/select@npm:2.4.19\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/form\": \"npm:2.1.18\"\n    \"@heroui/listbox\": \"npm:2.3.18\"\n    \"@heroui/popover\": \"npm:2.3.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/scroll-shadow\": \"npm:2.3.13\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/spinner\": \"npm:2.2.16\"\n    \"@heroui/use-aria-button\": \"npm:2.2.13\"\n    \"@heroui/use-aria-multiselect\": \"npm:2.4.12\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/form\": \"npm:3.0.15\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n    \"@tanstack/react-virtual\": \"npm:3.11.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.12\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/95e13635d8aa72d47896db804fe9cbcfe240b2b9dfa32225aef2864274046e5e212cd31227c536b0dae0a067d576441b979c7f78a59995e5ed9ef28efb5565b4\n  languageName: node\n  linkType: hard\n\n\"@heroui/select@npm:^2.4.19\":\n  version: 2.4.25\n  resolution: \"@heroui/select@npm:2.4.25\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.21\"\n    \"@heroui/form\": \"npm:2.1.24\"\n    \"@heroui/listbox\": \"npm:2.3.23\"\n    \"@heroui/popover\": \"npm:2.3.24\"\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/scroll-shadow\": \"npm:2.3.16\"\n    \"@heroui/shared-icons\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/spinner\": \"npm:2.2.21\"\n    \"@heroui/use-aria-button\": \"npm:2.2.18\"\n    \"@heroui/use-aria-multiselect\": \"npm:2.4.17\"\n    \"@heroui/use-form-reset\": \"npm:2.0.1\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.8\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/form\": \"npm:3.1.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/overlays\": \"npm:3.28.0\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.26\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/8dd27c690f96196e33e7070e7b624398e43ecd90afdde2a3048533f997fe15cc36d0129d992cac82399a46b0d69a155adce20ef121145830ec647d12a03b5834\n  languageName: node\n  linkType: hard\n\n\"@heroui/shared-icons@npm:2.1.10\":\n  version: 2.1.10\n  resolution: \"@heroui/shared-icons@npm:2.1.10\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/69525f7e85364bc202981ca2d3e8c321435bf153b14b158577bc55e61c41be6e1962b951c61a1aba6b6adf7954ddd712f2330e5846612179520387cd045c9878\n  languageName: node\n  linkType: hard\n\n\"@heroui/shared-icons@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/shared-icons@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/22ce7fca8a565e032f6140817a3c3e668aa5445892ae9f52e42b639aa8175307bc5b3eb878107de8d3bdfddd15f9f151e2f6effe96271eb6cdb7f54ff823a267\n  languageName: node\n  linkType: hard\n\n\"@heroui/shared-utils@npm:2.1.10\":\n  version: 2.1.10\n  resolution: \"@heroui/shared-utils@npm:2.1.10\"\n  checksum: 10c0/d8a1d27ea9fb18c7ae828f3b52b0fef2b19ebd2b2f3506f356a2359270d7420bd9b5d04daadf502759d7d8050f8c6a7e3294470ba59ea89af5f7a53d3e6a5c9a\n  languageName: node\n  linkType: hard\n\n\"@heroui/shared-utils@npm:2.1.9\":\n  version: 2.1.9\n  resolution: \"@heroui/shared-utils@npm:2.1.9\"\n  checksum: 10c0/336f641c48fdab0b1633f0bab4ce559de95a4cc994c0287741dbfce6050d47cf8ec13130ba94671cd7aecba8771a2e14d368d3e5f299aedbeecdae1dddf873f1\n  languageName: node\n  linkType: hard\n\n\"@heroui/skeleton@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/skeleton@npm:2.2.12\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a5fef406ba03755f1c7ada1f5e0d65399338a1276a53e60b0711b945c18df3037f03e90bb0b9c69f330ece1a1233bad37e34d28392bb80f7f7423ec538bb58ad\n  languageName: node\n  linkType: hard\n\n\"@heroui/slider@npm:2.4.16\":\n  version: 2.4.16\n  resolution: \"@heroui/slider@npm:2.4.16\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/tooltip\": \"npm:2.2.16\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/slider\": \"npm:3.7.18\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/slider\": \"npm:3.6.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/4139f3d629a2a0e719a65495720a9abf1547b1a9200de190b1cfd73027681db1d6d7eca41919a228d96174b14586eb5beac27ccd1bcf645413f445cc7eb6536f\n  languageName: node\n  linkType: hard\n\n\"@heroui/snippet@npm:2.2.20\":\n  version: 2.2.20\n  resolution: \"@heroui/snippet@npm:2.2.20\"\n  dependencies:\n    \"@heroui/button\": \"npm:2.2.19\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/tooltip\": \"npm:2.2.16\"\n    \"@heroui/use-clipboard\": \"npm:2.1.8\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/fa0abe5f1d58929e9e899b7d35bacaa7c6692f8a9f4146e96bc609c25e9da37f05c64a31760b9d3afe6a4e4d8037989041b5d19fab5a6377c89226c1264e47e1\n  languageName: node\n  linkType: hard\n\n\"@heroui/spacer@npm:2.2.14\":\n  version: 2.2.14\n  resolution: \"@heroui/spacer@npm:2.2.14\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/541b77d1f991d3fee9ad020c5ab69ed03d01e703b2a907b5e06cf573010b189f8ac9e3c2802c0c2777342ba84018fe106a742d3440121cdc1e0364bbaba67166\n  languageName: node\n  linkType: hard\n\n\"@heroui/spacer@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/spacer@npm:2.2.18\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/system-rsc\": \"npm:2.3.17\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a3aa66035d8c7609891bba06c86be9fde383d3455bbc5059fa98b09bbbd1e4b5779e988c45273df966fee273c5ace2e18faf78cf0d10d9f9086bc202fdd09612\n  languageName: node\n  linkType: hard\n\n\"@heroui/spinner@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/spinner@npm:2.2.16\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/system\": \"npm:2.4.15\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a912aa476b73e795d69facfbdc18264f47322ff80cbdcd5f7275ca1f7480a18773272558342d161231d10f31597f20663516db741e46458b0363f62efd80d557\n  languageName: node\n  linkType: hard\n\n\"@heroui/spinner@npm:2.2.21\":\n  version: 2.2.21\n  resolution: \"@heroui/spinner@npm:2.2.21\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/system\": \"npm:2.4.20\"\n    \"@heroui/system-rsc\": \"npm:2.3.17\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/8ae63caffa2ea0b767550acb1d9c055da9758fa5dc510cb148c2152ba311b94b13e4979bbdd2e04d095204323f860d1a93e32cdd2b5ba1037bf774afbc5fa6d6\n  languageName: node\n  linkType: hard\n\n\"@heroui/switch@npm:2.2.17\":\n  version: 2.2.17\n  resolution: \"@heroui/switch@npm:2.2.17\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/switch\": \"npm:3.7.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/toggle\": \"npm:3.8.3\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.3\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0fbd89e0b61731af2fa98caa47911e1fccf2275f731832a15cce4d5986ee7f0a291f1f0a9301386b8320726347454ddf0cf62208a236016c4455cf77fe27ee3c\n  languageName: node\n  linkType: hard\n\n\"@heroui/system-rsc@npm:2.3.13\":\n  version: 2.3.13\n  resolution: \"@heroui/system-rsc@npm:2.3.13\"\n  dependencies:\n    \"@react-types/shared\": \"npm:3.29.0\"\n    clsx: \"npm:^1.2.1\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/987cc02de926e5bea64bf6a931bd936101fcef63c5626f90f4af76baaf7aeeffea983e39b0a54c9502db0070df36e1458acdc229b604461e891abe387e13334a\n  languageName: node\n  linkType: hard\n\n\"@heroui/system-rsc@npm:2.3.17\":\n  version: 2.3.17\n  resolution: \"@heroui/system-rsc@npm:2.3.17\"\n  dependencies:\n    \"@react-types/shared\": \"npm:3.31.0\"\n    clsx: \"npm:^1.2.1\"\n  peerDependencies:\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/b3ec31d33e03d8af80e3c7bd1b7657df3d71677c4cd9036e0dcf84be5a4c1b1ac82960769ea17c57c19d037118a22db5ec9c056ea3264091e5f90f5784d61d64\n  languageName: node\n  linkType: hard\n\n\"@heroui/system@npm:2.4.15\":\n  version: 2.4.15\n  resolution: \"@heroui/system@npm:2.4.15\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/system-rsc\": \"npm:2.3.13\"\n    \"@internationalized/date\": \"npm:3.8.0\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n    \"@react-types/datepicker\": \"npm:3.12.0\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/03c7891651d5785769ab1e3f1b19761eef813d51a0d488434e5d8bd7c1191d98867177b5a9f1aa552faba516175f201e04e4f165bb99ae3c45fd281882e06e51\n  languageName: node\n  linkType: hard\n\n\"@heroui/system@npm:2.4.20, @heroui/system@npm:^2.4.15\":\n  version: 2.4.20\n  resolution: \"@heroui/system@npm:2.4.20\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/system-rsc\": \"npm:2.3.17\"\n    \"@react-aria/i18n\": \"npm:3.12.11\"\n    \"@react-aria/overlays\": \"npm:3.28.0\"\n    \"@react-aria/utils\": \"npm:3.30.0\"\n  peerDependencies:\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/82e6d94b1161c6683af00203b4e1ff5a871d6bc2cc30f025b4e67dcdacd6b87c70494cd596ab40a5fc77523eeb9b3d859c337da145f19391af393c12a8ec88b1\n  languageName: node\n  linkType: hard\n\n\"@heroui/table@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/table@npm:2.2.18\"\n  dependencies:\n    \"@heroui/checkbox\": \"npm:2.3.18\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/spacer\": \"npm:2.2.14\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/table\": \"npm:3.17.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.22\"\n    \"@react-stately/table\": \"npm:3.14.1\"\n    \"@react-stately/virtualizer\": \"npm:4.3.2\"\n    \"@react-types/grid\": \"npm:3.3.1\"\n    \"@react-types/table\": \"npm:3.12.0\"\n    \"@tanstack/react-virtual\": \"npm:3.11.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0c93a9e1dcd6c83ab7d7a90e489aa73b56d3d24624e15eaa16edd62c98d30959f8bf82c8da8224b7fb5e914c2b8d3f388f682e1b76b7692187ee7a5351794795\n  languageName: node\n  linkType: hard\n\n\"@heroui/table@npm:^2.2.18\":\n  version: 2.2.24\n  resolution: \"@heroui/table@npm:2.2.24\"\n  dependencies:\n    \"@heroui/checkbox\": \"npm:2.3.24\"\n    \"@heroui/react-utils\": \"npm:2.1.12\"\n    \"@heroui/shared-icons\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@heroui/spacer\": \"npm:2.2.18\"\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/table\": \"npm:3.17.6\"\n    \"@react-aria/visually-hidden\": \"npm:3.8.26\"\n    \"@react-stately/table\": \"npm:3.14.4\"\n    \"@react-stately/virtualizer\": \"npm:4.4.2\"\n    \"@react-types/grid\": \"npm:3.3.4\"\n    \"@react-types/table\": \"npm:3.13.2\"\n    \"@tanstack/react-virtual\": \"npm:3.11.3\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.18\"\n    \"@heroui/theme\": \">=2.4.17\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/02c16d47b5b373b11ad32a6af78c9ef7104e1d77515eb751d490fa7966f5fe04f189e123692d4003c96375e0f8a9b99f6d6cd22891b49c56ef84272f40c25c9c\n  languageName: node\n  linkType: hard\n\n\"@heroui/tabs@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/tabs@npm:2.2.16\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-is-mounted\": \"npm:2.1.7\"\n    \"@heroui/use-update-effect\": \"npm:2.1.7\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/tabs\": \"npm:3.10.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/tabs\": \"npm:3.8.1\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n    \"@react-types/tabs\": \"npm:3.3.14\"\n    scroll-into-view-if-needed: \"npm:3.0.10\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/dc5272be69a9c2c784b34c61586148904d9884d8c1073fcf0f3bfd302ce421f28328ebd344c671e1612cb97be4ffa079eed66e5740356f4a2416ef34c02dc96a\n  languageName: node\n  linkType: hard\n\n\"@heroui/theme@npm:2.4.15\":\n  version: 2.4.15\n  resolution: \"@heroui/theme@npm:2.4.15\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    clsx: \"npm:^1.2.1\"\n    color: \"npm:^4.2.3\"\n    color2k: \"npm:^2.0.3\"\n    deepmerge: \"npm:4.3.1\"\n    flat: \"npm:^5.0.2\"\n    tailwind-merge: \"npm:2.5.4\"\n    tailwind-variants: \"npm:0.3.0\"\n  peerDependencies:\n    tailwindcss: \">=3.4.0\"\n  checksum: 10c0/c139f84c3027bb8835a3e5eb8ad21414c3b009bf63b938a78c8c03a07dd72baec8b6360e3257cefaa5c474369648c43eac3f886b686c0ba5a755ac6fc9d6e564\n  languageName: node\n  linkType: hard\n\n\"@heroui/theme@npm:2.4.20, @heroui/theme@npm:^2.4.15\":\n  version: 2.4.20\n  resolution: \"@heroui/theme@npm:2.4.20\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    clsx: \"npm:^1.2.1\"\n    color: \"npm:^4.2.3\"\n    color2k: \"npm:^2.0.3\"\n    deepmerge: \"npm:4.3.1\"\n    flat: \"npm:^5.0.2\"\n    tailwind-merge: \"npm:3.3.1\"\n    tailwind-variants: \"npm:2.0.1\"\n  peerDependencies:\n    tailwindcss: \">=4.0.0\"\n  checksum: 10c0/a1ea366123892d42ce99d78a1f2da52b62e872d0b37c105032b2b6c77e9af48faf6263585160e00a895fa628838ad24684bfb99d8f364673513affe7bde5761c\n  languageName: node\n  linkType: hard\n\n\"@heroui/toast@npm:2.0.9\":\n  version: 2.0.9\n  resolution: \"@heroui/toast@npm:2.0.9\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-icons\": \"npm:2.1.7\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/spinner\": \"npm:2.2.16\"\n    \"@heroui/use-is-mobile\": \"npm:2.2.9\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/toast\": \"npm:3.0.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/toast\": \"npm:3.1.0\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.10\"\n    \"@heroui/theme\": \">=2.4.12\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/e1aae6d2a37eac8c24eda8ba6ea91650ed0fed4bc0cf57388f803de5fbc32f985a23a9f7565355292bd3b483762f093693e8f359bfa555859b240a85a32d2cab\n  languageName: node\n  linkType: hard\n\n\"@heroui/tooltip@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/tooltip@npm:2.2.16\"\n  dependencies:\n    \"@heroui/aria-utils\": \"npm:2.2.16\"\n    \"@heroui/dom-animation\": \"npm:2.1.8\"\n    \"@heroui/framer-utils\": \"npm:2.1.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/tooltip\": \"npm:3.8.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/tooltip\": \"npm:3.5.3\"\n    \"@react-types/overlays\": \"npm:3.8.14\"\n    \"@react-types/tooltip\": \"npm:3.4.16\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    framer-motion: \">=11.5.6 || >=12.0.0-alpha.1\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/e532156bec180bc409f2eae97ac2a7e1a152071ed0118a0c1be7b93b544ca17d6a52af59c72ff12326cc5e66d9e76dd9e5741abc4cee2c6521c0204f6bb78e93\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-accordion@npm:2.2.11\":\n  version: 2.2.11\n  resolution: \"@heroui/use-aria-accordion@npm:2.2.11\"\n  dependencies:\n    \"@react-aria/button\": \"npm:3.13.0\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/selection\": \"npm:3.24.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/tree\": \"npm:3.8.9\"\n    \"@react-types/accordion\": \"npm:3.0.0-alpha.26\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/763f615920b326ac2c83ac1ef6897eff84044ef5394b81c699233f60828c4d39bf0b6292f906070cc641c0cb6d9cc0d64d18439050bbac8b145fab34487b57ed\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-button@npm:2.2.13\":\n  version: 2.2.13\n  resolution: \"@heroui/use-aria-button@npm:2.2.13\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0ffd637d848da7c213043e41a85f654a271363e524bd9441bbdd0232b2fded44ac7d9e218ca504bd52d7f958e3c691d2bcd2665a89a45cb7beb15cbcba9a9fa4\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-button@npm:2.2.18\":\n  version: 2.2.18\n  resolution: \"@heroui/use-aria-button@npm:2.2.18\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/utils\": \"npm:3.30.0\"\n    \"@react-types/button\": \"npm:3.13.0\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/66d83ca40df7423f203a145fdf7fc90bb19a2949fc6da9a0bd64158c52751648f14ea1a549d32b3df650ccc139352d970c3853f80d7b29a57ae1420829d73a7f\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-link@npm:2.2.14\":\n  version: 2.2.14\n  resolution: \"@heroui/use-aria-link@npm:2.2.14\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/link\": \"npm:3.6.0\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/1aea64c46e5ac96b2f223633306925a60c8879bf077ec837f0bb36c77d57c10880971af9ca5efeb9c2f64eae04a5bad871de5b59fdd384c1b0222bde14a43e4f\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-modal-overlay@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/use-aria-modal-overlay@npm:2.2.12\"\n  dependencies:\n    \"@react-aria/overlays\": \"npm:3.27.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/overlays\": \"npm:3.6.15\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/4c2538fae6c8899e476312630100d818e1242f61324c571177f6d2937c0da98b4bada70b8223f0d7fe455f7102971f44c027f183f8b5a8b6ee928f7bc8c10990\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-multiselect@npm:2.4.12\":\n  version: 2.4.12\n  resolution: \"@heroui/use-aria-multiselect@npm:2.4.12\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/label\": \"npm:3.7.17\"\n    \"@react-aria/listbox\": \"npm:3.14.3\"\n    \"@react-aria/menu\": \"npm:3.18.2\"\n    \"@react-aria/selection\": \"npm:3.24.0\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/form\": \"npm:3.1.3\"\n    \"@react-stately/list\": \"npm:3.12.1\"\n    \"@react-stately/menu\": \"npm:3.9.3\"\n    \"@react-types/button\": \"npm:3.12.0\"\n    \"@react-types/overlays\": \"npm:3.8.14\"\n    \"@react-types/select\": \"npm:3.9.11\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/3c7a1a8058cbc9930f5f93dfff459e29e611d634f8d33ec71489bb6f052ad50430adcdf7a5739af2e07f10260236d83c99eac4c27ea1a1f57583a8f1a9c65287\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-multiselect@npm:2.4.17\":\n  version: 2.4.17\n  resolution: \"@heroui/use-aria-multiselect@npm:2.4.17\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:3.12.11\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/label\": \"npm:3.7.20\"\n    \"@react-aria/listbox\": \"npm:3.14.7\"\n    \"@react-aria/menu\": \"npm:3.19.0\"\n    \"@react-aria/selection\": \"npm:3.25.0\"\n    \"@react-aria/utils\": \"npm:3.30.0\"\n    \"@react-stately/form\": \"npm:3.2.0\"\n    \"@react-stately/list\": \"npm:3.12.4\"\n    \"@react-stately/menu\": \"npm:3.9.6\"\n    \"@react-types/button\": \"npm:3.13.0\"\n    \"@react-types/overlays\": \"npm:3.9.0\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/aebe94a9f15efa2a30f7bfffa78c8186183be7e471ca1341cdf3991cb9a7f5211f56e817b2b32d74baba7161497090b46105cf124e819c65f3c30aa920728e20\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-aria-overlay@npm:2.0.2\":\n  version: 2.0.2\n  resolution: \"@heroui/use-aria-overlay@npm:2.0.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:3.21.0\"\n    \"@react-aria/interactions\": \"npm:3.25.4\"\n    \"@react-aria/overlays\": \"npm:3.28.0\"\n    \"@react-types/shared\": \"npm:3.31.0\"\n  peerDependencies:\n    react: \">=18\"\n    react-dom: \">=18\"\n  checksum: 10c0/ca58f0f153a406f7877a32ae986891458db1a90ac6ffbd44962f872decc9e1fb5283a0ab0af5725bdc88cc6c9c6f43869633de8ccf7007ab0e8799fdf24aec60\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-callback-ref@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-callback-ref@npm:2.1.7\"\n  dependencies:\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/8a22fda2c4defd01f328b8c82e0dbe4e91efa027884a36604fa495a9c82a7c39208dfac4d80a3e31360a599868a4ccf3f759ba592660f5fadc369fb8e98fbae2\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-callback-ref@npm:2.1.8\":\n  version: 2.1.8\n  resolution: \"@heroui/use-callback-ref@npm:2.1.8\"\n  dependencies:\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/a1ab39bed0b9f58e890b9f3c6f9a58049d13510c618f8685ebceb60655a29969babe09c44a4480c086b69c5014058688801319f8a534197a71f000da6e05f440\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-clipboard@npm:2.1.8, @heroui/use-clipboard@npm:^2.1.8\":\n  version: 2.1.8\n  resolution: \"@heroui/use-clipboard@npm:2.1.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/43ef0ff48b6dc0eceba1ed825d51d1b2e0de789d8e6d21723c49958a6b7cbb84294758bcf140fd4d7b74ce0ce12dc1631fef99804084276370dd37374ebd0f8b\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-data-scroll-overflow@npm:2.2.10\":\n  version: 2.2.10\n  resolution: \"@heroui/use-data-scroll-overflow@npm:2.2.10\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/feddf1308a443e3e39a3fc6302a7b6b90725b05f989d17cece569f01677ecacde3b07f43059427e7bbe195fea18f5f72cbf07ba5c7f0a598be6c395596e90710\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-data-scroll-overflow@npm:2.2.11\":\n  version: 2.2.11\n  resolution: \"@heroui/use-data-scroll-overflow@npm:2.2.11\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/ef060aef99fda153f24c44958c20ec94884ed27bcba78b3136bd4c163a105f3290f3aaa2b0758881230f1aeb7c72f39eda87d67331da943af47042c691c44a75\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-disclosure@npm:2.2.11\":\n  version: 2.2.11\n  resolution: \"@heroui/use-disclosure@npm:2.2.11\"\n  dependencies:\n    \"@heroui/use-callback-ref\": \"npm:2.1.7\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-stately/utils\": \"npm:3.10.6\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/246e9e8f5cad81d98e78da90206ab9ef5419b62683d42f837ce6664f37eb82523f03a29c95ba073eeddfcd98ac92a6fdf84011443707ab245222cefb05a034a7\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-draggable@npm:2.1.11\":\n  version: 2.1.11\n  resolution: \"@heroui/use-draggable@npm:2.1.11\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/da0d1732fbc4e964ea1483cb73f4e9383d71a70801fd67f4c72911159a6565b35df52d91ba31dc61c97d4d716ccead5a24e4d81fccb90c6d53677501f5500e97\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-form-reset@npm:2.0.1\":\n  version: 2.0.1\n  resolution: \"@heroui/use-form-reset@npm:2.0.1\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/6c8ae0171b86cdf64cc6450005bdda9b0a944ba705aef0a2e42c9ca9f605353caa20a157ad445fef5f8402d25d140e31acd8a4e8fd147a7e802740a83577f996\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-image@npm:2.1.9\":\n  version: 2.1.9\n  resolution: \"@heroui/use-image@npm:2.1.9\"\n  dependencies:\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/use-safe-layout-effect\": \"npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/538d83457503dadd6ba1ff664623288aa9880013382c9567be28389a8ecb49ad36628c27cdf8b1051804a542d94ced8cde668a79c66e332097562303128a349d\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-intersection-observer@npm:2.2.11\":\n  version: 2.2.11\n  resolution: \"@heroui/use-intersection-observer@npm:2.2.11\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:3.25.0\"\n    \"@react-aria/ssr\": \"npm:3.9.8\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n    \"@react-types/shared\": \"npm:3.29.0\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/63092962929cd57b9779718c3bbf34a3abf644bd45af0a26a9b33356cfd293881e7925705af823bba1cb6e731ff866356aa9b800b1cba13e34a7308445498183\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-intersection-observer@npm:2.2.14\":\n  version: 2.2.14\n  resolution: \"@heroui/use-intersection-observer@npm:2.2.14\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/dd291622142b93406d7faa1f881af469de2700905fb23e6d1b0f53c327c95cba6a67169f3beffb4c718a00ba0ef512e2977e2b5580c66d23ce1dd52b450bd51a\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-is-mobile@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/use-is-mobile@npm:2.2.12\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:3.9.10\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/aa8229eee8da564804d03edbaef35d6bf65425535237ba5ff12ec44890e367795b8f74a2ebd720fd1a77161758e34817d9281bfcbfb37e7fa54e699f12ce6715\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-is-mobile@npm:2.2.9\":\n  version: 2.2.9\n  resolution: \"@heroui/use-is-mobile@npm:2.2.9\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:3.9.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/0ed3f8ea8f132d5e11a89e9e82c003ab8c808613e5f954ea9c703289d5d5f906a8ab950ca85979caac581aff9dc866ad9f98f8b277d9b2b030976710d35b7b7b\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-is-mounted@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-is-mounted@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/33ed9df764c93f0d257a2e25019e8d188878e60026a760b29d48ec66dbf713fb1094cd7d48e3355c4f1ad337682f335004167fb11a1fc6c592dcf7ad91ed6554\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-measure@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-measure@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/fcd6f28fb2e7a67d7c175af44b077e78555fa5a7e768acc559ca63fc626adecd7491e18febd71732c13d6c305a5470c20c5a1a31e233966d39440d3bd2bfb45c\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-measure@npm:2.1.8\":\n  version: 2.1.8\n  resolution: \"@heroui/use-measure@npm:2.1.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/4e0f86c51718c5cfe8eccd8160e4a0fbeb3315b29643c1364eec09d6a78fb8d2dd865b192c3433dd25696b5b38d085c927c2d2c8f744b02beb854ec70195e8f5\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-pagination@npm:2.2.12\":\n  version: 2.2.12\n  resolution: \"@heroui/use-pagination@npm:2.2.12\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/i18n\": \"npm:3.12.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/cc9bbd9a1d6299ab2f0abcd74a4cad257378040b75ea1ee92a9557fc27367038b58714217e973efa0675cd90f12424537af88f1c91cf0cfb366c20972731a00d\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-pagination@npm:2.2.16\":\n  version: 2.2.16\n  resolution: \"@heroui/use-pagination@npm:2.2.16\"\n  dependencies:\n    \"@heroui/shared-utils\": \"npm:2.1.10\"\n    \"@react-aria/i18n\": \"npm:3.12.11\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/ca756f8c3a069ee1f72f0a45992c990cc73442d2888caec0468cb73e7a01c547652a3781218d0449dd625a9dbd56dbb78ddccdb6e90126f3c6f6d1257bf24161\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-safe-layout-effect@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-safe-layout-effect@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/6950b89adae4c17a55f9365cab8b3a317b644eefdb935a1785de78c2700d459e920752fd78520a92ffd6371d7634ca9abcb32c51f3b41744f74b027bdd9eb7f6\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-safe-layout-effect@npm:2.1.8\":\n  version: 2.1.8\n  resolution: \"@heroui/use-safe-layout-effect@npm:2.1.8\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/f29195426b0941f9f5c8a91840121e753a9927fa8ae3675249751c7911ac8aa1caf1426cbf2ae06a3afea18509ee87669c2d0030ec9c59e34e4bde60af097fb1\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-scroll-position@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-scroll-position@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/313640216f8c99d75a645dde822a3481dc7aa8be632ccf84340e24c6c4fa9a5bc6e8df7eb1cb8d3ae79b5382c0cd9756c2dc32ca6e6b449afe20357693dfeac2\n  languageName: node\n  linkType: hard\n\n\"@heroui/use-update-effect@npm:2.1.7\":\n  version: 2.1.7\n  resolution: \"@heroui/use-update-effect@npm:2.1.7\"\n  peerDependencies:\n    react: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/ebed1e6a364ab9637148ce72ec2bc967220702b6191a96935ca542a4b153f77b76e4f1972bc358780b8e918def0e59069fd6c68d79c32320b8d2a44af5b8cdce\n  languageName: node\n  linkType: hard\n\n\"@heroui/user@npm:2.2.15\":\n  version: 2.2.15\n  resolution: \"@heroui/user@npm:2.2.15\"\n  dependencies:\n    \"@heroui/avatar\": \"npm:2.2.15\"\n    \"@heroui/react-utils\": \"npm:2.1.10\"\n    \"@heroui/shared-utils\": \"npm:2.1.9\"\n    \"@react-aria/focus\": \"npm:3.20.2\"\n    \"@react-aria/utils\": \"npm:3.28.2\"\n  peerDependencies:\n    \"@heroui/system\": \">=2.4.7\"\n    \"@heroui/theme\": \">=2.4.6\"\n    react: \">=18 || >=19.0.0-rc.0\"\n    react-dom: \">=18 || >=19.0.0-rc.0\"\n  checksum: 10c0/e277447cc645f634f151a39dd940ff0808ef6c0ba1b2161955660af2ad3357f082bef6baa229409b213cd1b606e97ad2b6d4f9949ec1508facbbaf1221e396e1\n  languageName: node\n  linkType: hard\n\n\"@iconify/react@npm:^5.1.0\":\n  version: 5.2.0\n  resolution: \"@iconify/react@npm:5.2.0\"\n  dependencies:\n    \"@iconify/types\": \"npm:^2.0.0\"\n  peerDependencies:\n    react: \">=16\"\n  checksum: 10c0/e63102e6408506058fc8a6a37681a8037bc9796c35ea9d334a8d741d5c0d89e72ce75f5bb6181c42c507d0252018ba5b8a118de87651efe1cc890899dc9f48bd\n  languageName: node\n  linkType: hard\n\n\"@iconify/types@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"@iconify/types@npm:2.0.0\"\n  checksum: 10c0/65a3be43500c7ccacf360e136d00e1717f050b7b91da644e94370256ac66f582d59212bdb30d00788aab4fc078262e91c95b805d1808d654b72f6d2072a7e4b2\n  languageName: node\n  linkType: hard\n\n\"@icons/material@npm:^0.2.4\":\n  version: 0.2.4\n  resolution: \"@icons/material@npm:0.2.4\"\n  peerDependencies:\n    react: \"*\"\n  checksum: 10c0/133518adf91010704b716e7671fc28bcc3c461dc4f4a56ad3a73a955b9993dfaa22494579e9377247fd3318baebe8e4ae7962c01bffeaca0044722c09baa9d73\n  languageName: node\n  linkType: hard\n\n\"@internationalized/date@npm:3.8.0\":\n  version: 3.8.0\n  resolution: \"@internationalized/date@npm:3.8.0\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/7ac0cae2f1832fe2f2950e22208812ed8bf2845dd903ec93bd3aa024ca020124e137638b11bb5817b92abde1daa3f881cc81d62db0b20f5db2d9e07ab0cd9e01\n  languageName: node\n  linkType: hard\n\n\"@internationalized/date@npm:^3.8.0, @internationalized/date@npm:^3.8.1\":\n  version: 3.8.1\n  resolution: \"@internationalized/date@npm:3.8.1\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/22374a28e803c1d8399954f9e7b5f4b0de384871f4b09f865d10e0fe0a16d1df9bea663e3fe903b9c63e419ff528eef4f3eb23557f8b86d10fc7fa4fe0823de2\n  languageName: node\n  linkType: hard\n\n\"@internationalized/date@npm:^3.8.2, @internationalized/date@npm:^3.9.0\":\n  version: 3.9.0\n  resolution: \"@internationalized/date@npm:3.9.0\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/8f2bf54c407aa95ab9922759c27f19bd9185bc6c4bde936fb5cc7a99bf764de8483102a61d53afa0598eefa11711617d3c05a65e8a5cb8bfac10c2c0800e488a\n  languageName: node\n  linkType: hard\n\n\"@internationalized/message@npm:^3.1.7\":\n  version: 3.1.7\n  resolution: \"@internationalized/message@npm:3.1.7\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    intl-messageformat: \"npm:^10.1.0\"\n  checksum: 10c0/0e3d46c97e790e34074f2589cbbe220bac8da453bf6d5d5da5d545b8a3989d37dc02d5209296f3cb900cea5a1220658821c7fe04fd00b2a27c446fcc6f062b1a\n  languageName: node\n  linkType: hard\n\n\"@internationalized/message@npm:^3.1.8\":\n  version: 3.1.8\n  resolution: \"@internationalized/message@npm:3.1.8\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    intl-messageformat: \"npm:^10.1.0\"\n  checksum: 10c0/91019d66d62ab6733fa46ed495fac6878bcc98f082e51be9fd0e4b5836a4df0f488c8dcd218f2e566c713e59cc68ef3aa5fc45e5b9bca8cca458d0990765b77a\n  languageName: node\n  linkType: hard\n\n\"@internationalized/number@npm:^3.6.1, @internationalized/number@npm:^3.6.2\":\n  version: 3.6.2\n  resolution: \"@internationalized/number@npm:3.6.2\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/40d1ce69582ddd46437144ff7e95e45c90e18838b2e41771deac2058d0f98169aa0e10a285ff9f1a59f575ce06bd3f495355a5591259d92a535956a4755374b8\n  languageName: node\n  linkType: hard\n\n\"@internationalized/number@npm:^3.6.4, @internationalized/number@npm:^3.6.5\":\n  version: 3.6.5\n  resolution: \"@internationalized/number@npm:3.6.5\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/f87d710863a8dbf057aac311193c82f3c42e862abdd99e5b71034f1022926036552620eab5dd00c23e975f28b9e41e830cb342ba0264436749d9cdc5ae031d44\n  languageName: node\n  linkType: hard\n\n\"@internationalized/string@npm:^3.2.6\":\n  version: 3.2.6\n  resolution: \"@internationalized/string@npm:3.2.6\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/8ed556697fee4aa2a115ea9d44075e8be8a7f80c76ebfcc6a4f14681175c4e59438f7ba049748d9c9cd0b46c7927b731d7c1f7fa53aaaf58b4c46dbd9f471b61\n  languageName: node\n  linkType: hard\n\n\"@internationalized/string@npm:^3.2.7\":\n  version: 3.2.7\n  resolution: \"@internationalized/string@npm:3.2.7\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/8f7bea379ce047026ef20d535aa1bd7612a5e5a5108d1e514965696a46bce34e38111411943b688d00dae2c81eae7779ae18343961310696d32ebb463a19b94a\n  languageName: node\n  linkType: hard\n\n\"@isaacs/cliui@npm:^8.0.2\":\n  version: 8.0.2\n  resolution: \"@isaacs/cliui@npm:8.0.2\"\n  dependencies:\n    string-width: \"npm:^5.1.2\"\n    string-width-cjs: \"npm:string-width@^4.2.0\"\n    strip-ansi: \"npm:^7.0.1\"\n    strip-ansi-cjs: \"npm:strip-ansi@^6.0.1\"\n    wrap-ansi: \"npm:^8.1.0\"\n    wrap-ansi-cjs: \"npm:wrap-ansi@^7.0.0\"\n  checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e\n  languageName: node\n  linkType: hard\n\n\"@isaacs/fs-minipass@npm:^4.0.0\":\n  version: 4.0.1\n  resolution: \"@isaacs/fs-minipass@npm:4.0.1\"\n  dependencies:\n    minipass: \"npm:^7.0.4\"\n  checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2\n  languageName: node\n  linkType: hard\n\n\"@jridgewell/gen-mapping@npm:^0.3.2\":\n  version: 0.3.8\n  resolution: \"@jridgewell/gen-mapping@npm:0.3.8\"\n  dependencies:\n    \"@jridgewell/set-array\": \"npm:^1.2.1\"\n    \"@jridgewell/sourcemap-codec\": \"npm:^1.4.10\"\n    \"@jridgewell/trace-mapping\": \"npm:^0.3.24\"\n  checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a\n  languageName: node\n  linkType: hard\n\n\"@jridgewell/resolve-uri@npm:^3.1.0\":\n  version: 3.1.2\n  resolution: \"@jridgewell/resolve-uri@npm:3.1.2\"\n  checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e\n  languageName: node\n  linkType: hard\n\n\"@jridgewell/set-array@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"@jridgewell/set-array@npm:1.2.1\"\n  checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4\n  languageName: node\n  linkType: hard\n\n\"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14\":\n  version: 1.5.0\n  resolution: \"@jridgewell/sourcemap-codec@npm:1.5.0\"\n  checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18\n  languageName: node\n  linkType: hard\n\n\"@jridgewell/trace-mapping@npm:^0.3.24\":\n  version: 0.3.25\n  resolution: \"@jridgewell/trace-mapping@npm:0.3.25\"\n  dependencies:\n    \"@jridgewell/resolve-uri\": \"npm:^3.1.0\"\n    \"@jridgewell/sourcemap-codec\": \"npm:^1.4.14\"\n  checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/actions@npm:3.2.0, @kepler.gl/actions@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/actions@npm:3.2.0\"\n  dependencies:\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@kepler.gl/cloud-providers\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/layers\": \"npm:3.2.0\"\n    \"@kepler.gl/processors\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@reduxjs/toolkit\": \"npm:^1.7.2\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    \"@types/react-redux\": \"npm:^7.1.23\"\n    \"@types/redux-actions\": \"npm:^2.6.2\"\n    lodash: \"npm:4.17.21\"\n    react-palm: \"npm:^3.3.8\"\n    react-redux: \"npm:^8.0.5\"\n    redux: \"npm:^4.2.1\"\n    redux-actions: \"npm:^2.2.1\"\n  checksum: 10c0/1c0445a3041f3a4bd0eaa6da29db1b60ac1d34d2d3345cabe50218a6f04aeb27216462bceeb0e57051dede7f6b4c8b195c2c4fad178a2fe4f2e50fcb02bfd27e\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/ai-assistant@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/ai-assistant@npm:3.2.0\"\n  dependencies:\n    \"@ai-sdk/anthropic\": \"npm:^1.2.11\"\n    \"@ai-sdk/deepseek\": \"npm:^0.2.14\"\n    \"@ai-sdk/google\": \"npm:^1.2.18\"\n    \"@ai-sdk/xai\": \"npm:^1.2.16\"\n    \"@kepler.gl/components\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/layers\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@openassistant/core\": \"npm:^0.5.13\"\n    \"@openassistant/duckdb\": \"npm:^0.5.13\"\n    \"@openassistant/echarts\": \"npm:^0.5.13\"\n    \"@openassistant/geoda\": \"npm:^0.5.13\"\n    \"@openassistant/osm\": \"npm:^0.5.13\"\n    \"@openassistant/plots\": \"npm:^0.5.13\"\n    \"@openassistant/tables\": \"npm:^0.5.13\"\n    \"@openassistant/ui\": \"npm:^0.5.13\"\n    \"@openassistant/utils\": \"npm:^0.5.13\"\n    color-interpolate: \"npm:^1.0.5\"\n    global: \"npm:^4.3.0\"\n    ollama-ai-provider-v2: \"npm:^0.0.5\"\n    react-intl: \"npm:^6.3.0\"\n    usehooks-ts: \"npm:^3.1.0\"\n  checksum: 10c0/cd7a59c236fdda1b2cf18c348441d4213660ad79350b130d80aa97b13899b61ae75aa3e09b1afd781c183eceb3bb0222f6be11ba6d4f49d299972c0c0191d228\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/cloud-providers@npm:3.2.0, @kepler.gl/cloud-providers@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/cloud-providers@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    react: \"npm:^18.2.0\"\n  checksum: 10c0/ada8e1ba12e4416a9a3466cca559b7bb116a5a1fa029ad018e86b73d14fdd67e071d0ac841badea49d919a58d66ed0b3cede9954d7551057e0834412e6c7183d\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/common-utils@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/common-utils@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    d3-array: \"npm:^2.8.0\"\n    global: \"npm:^4.3.0\"\n    h3-js: \"npm:^3.1.0\"\n    type-analyzer: \"npm:0.4.0\"\n  checksum: 10c0/96e63931a4886aa5cc6ad11fd210f41ce2e64ba4e7479cb7f4558646f8c60e69d64e574bb5b28e637544f31967ff85df204f8154ee8411a0d4cde2b19a799cea\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/components@npm:3.2.0, @kepler.gl/components@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/components@npm:3.2.0\"\n  dependencies:\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@deck.gl/react\": \"npm:^8.9.27\"\n    \"@dnd-kit/core\": \"npm:^6.1.0\"\n    \"@dnd-kit/modifiers\": \"npm:^7.0.0\"\n    \"@dnd-kit/sortable\": \"npm:^8.0.0\"\n    \"@dnd-kit/utilities\": \"npm:^3.2.2\"\n    \"@emotion/is-prop-valid\": \"npm:^1.2.1\"\n    \"@floating-ui/react\": \"npm:0.25.1\"\n    \"@kepler.gl/actions\": \"npm:3.2.0\"\n    \"@kepler.gl/cloud-providers\": \"npm:3.2.0\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/effects\": \"npm:3.2.0\"\n    \"@kepler.gl/layers\": \"npm:3.2.0\"\n    \"@kepler.gl/localization\": \"npm:3.2.0\"\n    \"@kepler.gl/processors\": \"npm:3.2.0\"\n    \"@kepler.gl/reducers\": \"npm:3.2.0\"\n    \"@kepler.gl/schemas\": \"npm:3.2.0\"\n    \"@kepler.gl/styles\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/mvt\": \"npm:^4.3.2\"\n    \"@loaders.gl/pmtiles\": \"npm:^4.3.2\"\n    \"@loaders.gl/wms\": \"npm:4.3.2\"\n    \"@mapbox/mapbox-sdk\": \"npm:^0.15.3\"\n    \"@nebula.gl/edit-modes\": \"npm:1.0.2-alpha.1\"\n    \"@tippyjs/react\": \"npm:^4.2.0\"\n    \"@types/classnames\": \"npm:^2.3.1\"\n    \"@types/d3-array\": \"npm:^2.8.0\"\n    \"@types/d3-brush\": \"npm:^3.0.1\"\n    \"@types/d3-scale\": \"npm:^3.2.2\"\n    \"@types/d3-selection\": \"npm:^3.0.2\"\n    \"@types/exenv\": \"npm:^1.2.0\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    \"@types/react\": \"npm:^18.0.28\"\n    \"@types/react-copy-to-clipboard\": \"npm:^5.0.2\"\n    \"@types/react-dom\": \"npm:^18.0.11\"\n    \"@types/react-lifecycles-compat\": \"npm:^3.0.1\"\n    \"@types/react-map-gl\": \"npm:^6.1.3\"\n    \"@types/react-modal\": \"npm:^3.16.3\"\n    \"@types/react-redux\": \"npm:^7.1.23\"\n    \"@types/react-virtualized\": \"npm:^9.21.30\"\n    \"@types/react-vis\": \"npm:1.11.7\"\n    \"@types/styled-components\": \"npm:^5.1.32\"\n    classnames: \"npm:^2.2.1\"\n    copy-to-clipboard: \"npm:^3.3.1\"\n    d3-array: \"npm:^2.8.0\"\n    d3-axis: \"npm:^2.0.0\"\n    d3-brush: \"npm:^2.1.0\"\n    d3-color: \"npm:^2.0.0\"\n    d3-format: \"npm:^2.0.0\"\n    d3-scale: \"npm:^3.2.3\"\n    d3-selection: \"npm:^2.0.0\"\n    exenv: \"npm:^1.2.2\"\n    fuzzy: \"npm:^0.1.3\"\n    global: \"npm:^4.3.0\"\n    lodash: \"npm:4.17.21\"\n    mapbox-gl: \"npm:1.13.1\"\n    maplibre-gl: \"npm:^3.6.2\"\n    markdown-to-jsx: \"npm:^7.7.6\"\n    mjolnir.js: \"npm:^2.7.0\"\n    moment: \"npm:^2.10.6\"\n    moment-timezone: \"npm:^0.5.35\"\n    prop-types: \"npm:^15.6.0\"\n    react: \"npm:^18.2.0\"\n    react-color: \"npm:^2.19.3\"\n    react-copy-to-clipboard: \"npm:^5.0.2\"\n    react-date-picker: \"npm:^10.2.0\"\n    react-dom: \"npm:^18.2.0\"\n    react-intl: \"npm:^6.3.0\"\n    react-json-pretty: \"npm:^2.2.0\"\n    react-lifecycles-compat: \"npm:^3.0.4\"\n    react-map-gl: \"npm:^7.1.6\"\n    react-modal: \"npm:^3.12.1\"\n    react-redux: \"npm:^8.0.5\"\n    react-sortable-hoc: \"npm:^1.8.3\"\n    react-time-picker: \"npm:^6.2.0\"\n    react-tooltip: \"npm:^4.2.17\"\n    react-virtualized: \"npm:^9.22.5\"\n    react-vis: \"npm:1.11.7\"\n    redux: \"npm:^4.2.1\"\n    reselect: \"npm:^4.1.0\"\n    styled-components: \"npm:6.1.8\"\n    suncalc: \"npm:^1.9.0\"\n    viewport-mercator-project: \"npm:^6.0.0\"\n  checksum: 10c0/4c3da0e6e2045a05d569fdac044da95be244303e5a4bb195bf6b031476b3db66ab66f34d8b665359c773c444a7447cd0fda956164c67659660bb917de07e57ca\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/constants@npm:3.2.0, @kepler.gl/constants@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/constants@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@types/d3-scale\": \"npm:^3.2.2\"\n    \"@types/keymirror\": \"npm:^0.1.1\"\n    chroma-js: \"npm:2.1.2\"\n    colorbrewer: \"npm:^1.5.0\"\n    d3-array: \"npm:^2.8.0\"\n    d3-color: \"npm:^2.0.0\"\n    d3-scale: \"npm:^3.2.3\"\n    d3-scale-chromatic: \"npm:2.0.0\"\n    d3-time: \"npm:^2.0.0\"\n    global: \"npm:^4.3.0\"\n    keymirror: \"npm:^0.1.1\"\n  checksum: 10c0/c90a238f346baadbd64f65f8e2521cbbcb5ef7501a1b28ee09c549a2ed5e6596192171cdd1b818153a8a93edc5d876aab65f1b7c0d42b234f421a2831b4cfe36\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/deckgl-arrow-layers@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/deckgl-arrow-layers@npm:3.2.0\"\n  dependencies:\n    \"@geoarrow/geoarrow-js\": \"npm:^0.3.0\"\n    \"@kepler.gl/constants\": \"npm:^3.2.0\"\n    \"@math.gl/core\": \"npm:^4.0.0\"\n    \"@math.gl/polygon\": \"npm:^4.0.0\"\n    \"@math.gl/types\": \"npm:^4.0.0\"\n    apache-arrow: \"npm:>=15\"\n    threads: \"npm:^1.7.0\"\n  peerDependencies:\n    \"@deck.gl/aggregation-layers\": ^8.9.27\n    \"@deck.gl/core\": ^8.9.27\n    \"@deck.gl/geo-layers\": ^8.9.27\n    \"@deck.gl/layers\": ^8.9.27\n  checksum: 10c0/1b92da41c5fea81105d012d5175ebde238d4e1dbb6ca52ad639974338a8439a0c4895883a21075edc03a7e0129430a4bea969459dc67b56e24eaeec1253822f0\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/deckgl-layers@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/deckgl-layers@npm:3.2.0\"\n  dependencies:\n    \"@danmarshall/deckgl-typings\": \"npm:4.9.22\"\n    \"@deck.gl/aggregation-layers\": \"npm:^8.9.27\"\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@deck.gl/geo-layers\": \"npm:^8.9.27\"\n    \"@deck.gl/layers\": \"npm:^8.9.27\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/wms\": \"npm:4.3.2\"\n    \"@luma.gl/constants\": \"npm:^8.5.20\"\n    \"@luma.gl/core\": \"npm:^8.5.20\"\n    \"@mapbox/geo-viewport\": \"npm:^0.4.1\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    \"@types/d3-array\": \"npm:^2.8.0\"\n    \"@types/geojson\": \"npm:^7946.0.8\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    \"@types/supercluster\": \"npm:^7.1.0\"\n    d3-array: \"npm:^2.8.0\"\n    global: \"npm:^4.3.0\"\n    lodash: \"npm:4.17.21\"\n    pbf: \"npm:^3.1.0\"\n    supercluster: \"npm:^7.1.0\"\n    viewport-mercator-project: \"npm:^6.0.0\"\n  checksum: 10c0/23fdd68d8be7fc05b6e5e2e1baf6ff3f111382f7c097323179a84df72105658a6bc6882c86199d26b4dcb8654fe53abb8fce973d0cc75cb7bef1defeade7c47c\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/duckdb@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/duckdb@npm:3.2.0\"\n  dependencies:\n    \"@duckdb/duckdb-wasm\": \"npm:^1.28.0\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/processors\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@monaco-editor/react\": \"npm:^4.6.0\"\n    \"@radix-ui/react-collapsible\": \"npm:^1.1.0\"\n    apache-arrow: \"npm:>=15.0.0\"\n    monaco-editor: \"npm:^0.52.0\"\n    react-resizable-panels: \"npm:^2.1.7\"\n  checksum: 10c0/3cf7a69b9249ad721f7dc1aaafd17893579786a3ce6de499760d92ca0e9c4ea6b78919e898a1acbcbc79b78a82eb0df72593462364267660964a198fbd56b945\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/effects@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/effects@npm:3.2.0\"\n  dependencies:\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@luma.gl/core\": \"npm:^8.5.20\"\n    \"@luma.gl/shadertools\": \"npm:^8.5.20\"\n    moment-timezone: \"npm:^0.5.35\"\n    suncalc: \"npm:^1.9.0\"\n  checksum: 10c0/771dbae6c5d728f4f4e8c32ff65e110ce27bc6e331db22a896156e318de3848b02503342fb87c9f417f5c4240d7b0a286eca41b984bf08f6308f0685c014d575\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/layers@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/layers@npm:3.2.0\"\n  dependencies:\n    \"@danmarshall/deckgl-typings\": \"npm:4.9.22\"\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@deck.gl/extensions\": \"npm:^8.9.27\"\n    \"@deck.gl/geo-layers\": \"npm:^8.9.27\"\n    \"@deck.gl/layers\": \"npm:^8.9.27\"\n    \"@deck.gl/mesh-layers\": \"npm:^8.9.27\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/deckgl-arrow-layers\": \"npm:3.2.0\"\n    \"@kepler.gl/deckgl-layers\": \"npm:3.2.0\"\n    \"@kepler.gl/localization\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/arrow\": \"npm:^4.3.2\"\n    \"@loaders.gl/core\": \"npm:^4.3.2\"\n    \"@loaders.gl/gis\": \"npm:^4.3.2\"\n    \"@loaders.gl/gltf\": \"npm:^4.3.2\"\n    \"@loaders.gl/mvt\": \"npm:^4.3.2\"\n    \"@loaders.gl/parquet\": \"npm:^4.3.2\"\n    \"@loaders.gl/pmtiles\": \"npm:^4.3.2\"\n    \"@loaders.gl/schema\": \"npm:^4.3.2\"\n    \"@loaders.gl/wkt\": \"npm:^4.3.2\"\n    \"@luma.gl/constants\": \"npm:^8.5.20\"\n    \"@mapbox/geojson-normalize\": \"npm:0.0.1\"\n    \"@nebula.gl/edit-modes\": \"npm:1.0.2-alpha.1\"\n    \"@nebula.gl/layers\": \"npm:1.0.2-alpha.1\"\n    \"@turf/bbox\": \"npm:^6.0.1\"\n    \"@turf/boolean-within\": \"npm:^6.0.1\"\n    \"@turf/center\": \"npm:^6.0.1\"\n    \"@turf/helpers\": \"npm:^6.1.4\"\n    \"@types/geojson\": \"npm:^7946.0.8\"\n    \"@types/keymirror\": \"npm:^0.1.1\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    \"@types/styled-components\": \"npm:^5.1.32\"\n    apache-arrow: \"npm:>=15.0.0\"\n    buffer: \"npm:6.0.3\"\n    d3-array: \"npm:^2.8.0\"\n    d3-shape: \"npm:^1.2.0\"\n    global: \"npm:^4.3.0\"\n    keymirror: \"npm:^0.1.1\"\n    lodash: \"npm:4.17.21\"\n    long: \"npm:^4.0.0\"\n    markdown-to-jsx: \"npm:^7.7.6\"\n    prop-types: \"npm:^15.6.0\"\n    react: \"npm:^18.2.0\"\n    react-intl: \"npm:^6.3.0\"\n    reselect: \"npm:^4.1.0\"\n    s2-geometry: \"npm:^1.2.10\"\n    styled-components: \"npm:6.1.8\"\n    type-analyzer: \"npm:0.4.0\"\n    viewport-mercator-project: \"npm:^6.0.0\"\n  checksum: 10c0/715eca5ed3aabaf70c532cd9b4e4952a1b6823957fbe3a52ecba7b58bebbd5715f6eefe1c697165cda4484b32c5b6efa0684fb7f5c6a5406116b900fd7d22f42\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/localization@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/localization@npm:3.2.0\"\n  dependencies:\n    react: \"npm:^18.2.0\"\n    react-intl: \"npm:^6.3.0\"\n    redux: \"npm:^4.2.1\"\n  checksum: 10c0/a602f7e75b7861a575828c9edd7377796118600b0a0a138ada4a9c3beaec57427f78d70095f748ba9448c26030689cf748a0fd72c7803295cc6b4d80bc6061c8\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/processors@npm:3.2.0, @kepler.gl/processors@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/processors@npm:3.2.0\"\n  dependencies:\n    \"@danmarshall/deckgl-typings\": \"npm:4.9.22\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/schemas\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/arrow\": \"npm:^4.3.2\"\n    \"@loaders.gl/core\": \"npm:^4.3.2\"\n    \"@loaders.gl/csv\": \"npm:^4.3.2\"\n    \"@loaders.gl/gis\": \"npm:^4.3.2\"\n    \"@loaders.gl/json\": \"npm:^4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:^4.3.2\"\n    \"@loaders.gl/parquet\": \"npm:^4.3.2\"\n    \"@loaders.gl/schema\": \"npm:^4.3.2\"\n    \"@loaders.gl/wkt\": \"npm:^4.3.2\"\n    \"@mapbox/geojson-normalize\": \"npm:0.0.1\"\n    \"@nebula.gl/edit-modes\": \"npm:1.0.2-alpha.1\"\n    \"@turf/helpers\": \"npm:^6.1.4\"\n    apache-arrow: \"npm:>=15.0.0\"\n    d3-dsv: \"npm:^2.0.0\"\n    type-analyzer: \"npm:0.4.0\"\n  checksum: 10c0/e34a7cd1ffa2eec9667e67e437b6f1c3ff6b990a4c605e476359cca39b36a04e6645c322d499753d11bd0dacfe9fdcd45a8607c8f3b0ed69e27055b43c2fbf24\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/reducers@npm:3.2.0, @kepler.gl/reducers@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/reducers@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/actions\": \"npm:3.2.0\"\n    \"@kepler.gl/cloud-providers\": \"npm:3.2.0\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/deckgl-arrow-layers\": \"npm:3.2.0\"\n    \"@kepler.gl/deckgl-layers\": \"npm:3.2.0\"\n    \"@kepler.gl/effects\": \"npm:3.2.0\"\n    \"@kepler.gl/layers\": \"npm:3.2.0\"\n    \"@kepler.gl/localization\": \"npm:3.2.0\"\n    \"@kepler.gl/processors\": \"npm:3.2.0\"\n    \"@kepler.gl/schemas\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/tasks\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/loader-utils\": \"npm:^4.3.2\"\n    \"@mapbox/geo-viewport\": \"npm:^0.4.1\"\n    \"@math.gl/web-mercator\": \"npm:^3.6.2\"\n    \"@turf/bbox\": \"npm:^6.0.1\"\n    \"@turf/bbox-polygon\": \"npm:^6.0.1\"\n    \"@turf/boolean-within\": \"npm:^6.0.1\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    \"@types/redux-actions\": \"npm:^2.6.2\"\n    copy-to-clipboard: \"npm:^3.3.1\"\n    d3-color: \"npm:^2.0.0\"\n    d3-dsv: \"npm:^2.0.0\"\n    deepmerge: \"npm:^4.2.2\"\n    global: \"npm:^4.3.0\"\n    lodash: \"npm:4.17.21\"\n    react-palm: \"npm:^3.3.8\"\n    redux: \"npm:^4.2.1\"\n    redux-actions: \"npm:^2.2.1\"\n    reselect: \"npm:^4.1.0\"\n  checksum: 10c0/7ff864731cf64a6422b11ab7ae83bbf50e9aa86f74be63d69121167eff1f33e20622045721ea411e66f903d304a38e74159b549a157bfe77694aed848552aeef\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/schemas@npm:3.2.0, @kepler.gl/schemas@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/schemas@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/layers\": \"npm:3.2.0\"\n    \"@kepler.gl/table\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/loader-utils\": \"npm:^4.3.2\"\n    \"@types/keymirror\": \"npm:^0.1.1\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    apache-arrow: \"npm:>=15.0.0\"\n    global: \"npm:^4.3.0\"\n    keymirror: \"npm:^0.1.1\"\n    lodash: \"npm:4.17.21\"\n  checksum: 10c0/7809bb356d2faa0591f5f79928aaa563effc9867dff7863ec7755dd85436fac83bb93fc910095121e4052c459cf882eb26df2f1884bd2f5d6a7e3d252920bbd1\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/styles@npm:3.2.0, @kepler.gl/styles@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/styles@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@types/styled-components\": \"npm:^5.1.32\"\n    styled-components: \"npm:6.1.8\"\n  checksum: 10c0/5bf5f69323b8cef6bf258b2bbf1bd705df5dfe4a3b32c2948e4884845a84dc3469a214b2e9d2aafafc56f77d606f0edc8c19af62ec848a0409d2b645874b1111\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/table@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/table@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@kepler.gl/utils\": \"npm:3.2.0\"\n    \"@loaders.gl/mvt\": \"npm:^4.3.2\"\n    \"@loaders.gl/pmtiles\": \"npm:^4.3.2\"\n    \"@types/d3-array\": \"npm:^2.8.0\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    d3-array: \"npm:^2.8.0\"\n    global: \"npm:^4.3.0\"\n    lodash: \"npm:4.17.21\"\n    moment: \"npm:^2.10.6\"\n    react-palm: \"npm:^3.3.8\"\n    type-analyzer: \"npm:0.4.0\"\n  checksum: 10c0/4207b74600234d4dd413838ed1111b6e1104a68bfe2a37c286125e19d80b2b152eeecd907806d3d0cb75f9b3f37cd03e95c0122acc13679105ca23b8873fe1b4\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/tasks@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/tasks@npm:3.2.0\"\n  dependencies:\n    \"@kepler.gl/processors\": \"npm:3.2.0\"\n    react-palm: \"npm:^3.3.8\"\n  checksum: 10c0/d2fd9d43c5d3993c73959c24a490a8518b56959659eb6cebc8856aed9d86605fdb10335f6153bb0515e8559818fe59e8cf323acac803c52147d7d325480746d3\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/types@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/types@npm:3.2.0\"\n  checksum: 10c0/7001e640fd548047e356259c2e88905776c01246cc664af28d4fe68ed98aa07b6e5337ce61d7ce113ec48626b93e3dba1d4f5a69105c7bc73352d4bfc308b451\n  languageName: node\n  linkType: hard\n\n\"@kepler.gl/utils@npm:3.2.0, @kepler.gl/utils@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"@kepler.gl/utils@npm:3.2.0\"\n  dependencies:\n    \"@deck.gl/core\": \"npm:^8.9.27\"\n    \"@kepler.gl/common-utils\": \"npm:3.2.0\"\n    \"@kepler.gl/constants\": \"npm:3.2.0\"\n    \"@kepler.gl/types\": \"npm:3.2.0\"\n    \"@loaders.gl/arrow\": \"npm:^4.3.2\"\n    \"@luma.gl/constants\": \"npm:^8.5.20\"\n    \"@luma.gl/core\": \"npm:^8.5.20\"\n    \"@mapbox/geo-viewport\": \"npm:^0.4.1\"\n    \"@turf/boolean-within\": \"npm:^6.0.1\"\n    \"@turf/helpers\": \"npm:^6.1.4\"\n    \"@types/d3-array\": \"npm:^2.8.0\"\n    \"@types/keymirror\": \"npm:^0.1.1\"\n    \"@types/lodash\": \"npm:4.17.5\"\n    apache-arrow: \"npm:>=15.0.0\"\n    d3-array: \"npm:^2.8.0\"\n    d3-color: \"npm:^2.0.0\"\n    d3-format: \"npm:^2.0.0\"\n    d3-interpolate: \"npm:^2.0.1\"\n    decimal.js: \"npm:^10.2.0\"\n    global: \"npm:^4.3.0\"\n    h3-js: \"npm:^3.1.0\"\n    keymirror: \"npm:^0.1.1\"\n    lodash: \"npm:4.17.21\"\n    mapbox-gl: \"npm:^1.13.1\"\n    maplibre-gl: \"npm:^3.6.2\"\n    maplibregl-mapbox-request-transformer: \"npm:^0.0.2\"\n    mini-svg-data-uri: \"npm:^1.0.3\"\n    moment: \"npm:^2.10.6\"\n    moment-timezone: \"npm:^0.5.35\"\n    react: \"npm:^18.2.0\"\n    react-map-gl: \"npm:^7.1.6\"\n    resize-observer-polyfill: \"npm:^1.5.1\"\n    suncalc: \"npm:^1.9.0\"\n    type-analyzer: \"npm:0.4.0\"\n    viewport-mercator-project: \"npm:^6.0.0\"\n  checksum: 10c0/f7631a3944f826bea655f1dc6b755063cc9df718d745b2b56acae1c6f861c8cb71c6433ffcc0fc9fafac622954026c4856f6723ab8e263ebd2950e0f51a65a8c\n  languageName: node\n  linkType: hard\n\n\"@langchain/core@npm:^0.3.38\":\n  version: 0.3.53\n  resolution: \"@langchain/core@npm:0.3.53\"\n  dependencies:\n    \"@cfworker/json-schema\": \"npm:^4.0.2\"\n    ansi-styles: \"npm:^5.0.0\"\n    camelcase: \"npm:6\"\n    decamelize: \"npm:1.2.0\"\n    js-tiktoken: \"npm:^1.0.12\"\n    langsmith: \"npm:^0.3.16\"\n    mustache: \"npm:^4.2.0\"\n    p-queue: \"npm:^6.6.2\"\n    p-retry: \"npm:4\"\n    uuid: \"npm:^10.0.0\"\n    zod: \"npm:^3.22.4\"\n    zod-to-json-schema: \"npm:^3.22.3\"\n  checksum: 10c0/006e2ef11354d85dc9cd3529f8ec747bbe8e7e8bdf57bff8a0949155e04186563848f0047469f406702d3c69d1d9acb776a95303ee94183227dbebcd75383fbd\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/3d-tiles@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/3d-tiles@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/draco\": \"npm:3.4.15\"\n    \"@loaders.gl/gltf\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/math\": \"npm:3.4.15\"\n    \"@loaders.gl/tiles\": \"npm:3.4.15\"\n    \"@math.gl/core\": \"npm:^3.5.1\"\n    \"@math.gl/geospatial\": \"npm:^3.5.1\"\n    long: \"npm:^5.2.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^3.4.0\n  checksum: 10c0/5826642c7dda7277d536705de59b6cb562e6c6f70852fd8b59feaab37c298768d95a9a982756536142c990b711fa4e37c4f6b160bdb6bec30ae9ea4f0f8a0770\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/arrow@npm:4.3.2, @loaders.gl/arrow@npm:^4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/arrow@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@loaders.gl/wkt\": \"npm:4.3.2\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.2\"\n    \"@math.gl/polygon\": \"npm:^4.1.0\"\n    apache-arrow: \"npm:>= 15.0.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/009a3cb32fbdcda3806211a4ceab8ac4037c37b93a67e8da361c23005885ae24190132cac4ca67580dfcf513a0a24642b30f23acd1df9d136505eec3475aba76\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/bson@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/bson@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@types/bson\": \"npm:4.2.0\"\n    bson: \"npm:4.2.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/0b3a9bde4d53db3ba8918e432ff65e35e8c1f98a0b6b84d2c8da55b98055562dc09769933aeb988bdca8349c15baaba484a55790259fcc72ed54c0b076d1acd9\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/compression@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/compression@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.2\"\n    \"@types/brotli\": \"npm:^1.3.0\"\n    \"@types/pako\": \"npm:^1.0.1\"\n    brotli: \"npm:^1.3.2\"\n    fflate: \"npm:0.7.4\"\n    lz4js: \"npm:^0.2.0\"\n    lzo-wasm: \"npm:^0.0.4\"\n    pako: \"npm:1.0.11\"\n    snappyjs: \"npm:^0.6.1\"\n    zstd-codec: \"npm:^0.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  dependenciesMeta:\n    brotli:\n      optional: true\n    lz4js:\n      optional: true\n    zstd-codec:\n      optional: true\n  checksum: 10c0/0fa9ee1d9a57707e069b66966396999e54a23188d94f5dcd98b7409c0654c0cff396971f835d59e368b5e6e55c8517af8cc58c59bd3f7d5e22065779f0aee97a\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/core@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/core@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/worker-utils\": \"npm:3.4.15\"\n    \"@probe.gl/log\": \"npm:^3.5.0\"\n  checksum: 10c0/7ab7fc7faf818d0ae77515ebcecf5c6ac5cc632c808fbd877fb80094ebdf5c32d472696a6eba8532e7041c6d80b19bf370405f772bd8f97a7a97fe92b2621ddd\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/core@npm:^4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/core@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.2\"\n    \"@probe.gl/log\": \"npm:^4.0.2\"\n  checksum: 10c0/3dbc564707996f376221e49f6ceaa53c8ce1b8026af591cbdce5c6eb87ffaeddc5e4174c0f4c4f2b589c5c586a4fa0045b6891d196ea3a6929d9a8ce1a6ae9e5\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/core@npm:^4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/core@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.3\"\n    \"@probe.gl/log\": \"npm:^4.0.2\"\n  checksum: 10c0/edaf68a70fb5d2b10cf57f932d5274e66d91927edc29413499ba1852fa19fc137c97b281c447b1a0558f8212c5be276d6574d0fef4caf96b3b8a1373519fb266\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/csv@npm:^4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/csv@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    d3-dsv: \"npm:^1.2.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/69fb75b0957b4765b0d70dfa2519aa41fcb0da428574d3ab92192db783c140acc4fa417555856456e46258bd087edbac0f9072a1e2f98f9cae1f9c2aaa616185\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/draco@npm:3.4.15\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/draco@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@loaders.gl/worker-utils\": \"npm:3.4.15\"\n    draco3d: \"npm:1.5.5\"\n  checksum: 10c0/11105aaa5f98329b99c97cbb2dde07250318f9086c9e0592a582d620ea4a4f7d8a37e9d234dd0db849f0ae87423f18223692075c420ecfe7d0ff3667b2bcab59\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/draco@npm:4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/draco@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.3\"\n    draco3d: \"npm:1.5.7\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/ae55555cfffbd0087713bcf5770cafb42029c1f3757d44bffb5dd751c7eaf9f63d6103a68f052bac35ca5391eb7763128aa945332ecf92e437d4bfd0686bc5a1\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gis@npm:3.4.15, @loaders.gl/gis@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/gis@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@math.gl/polygon\": \"npm:^3.5.1\"\n    pbf: \"npm:^3.2.1\"\n  checksum: 10c0/ac011ce792a5f1e2ead127b85532db5710db0ea123157fc65457e56de5dd45ab16baed2c8356ff7ee84db1873e02a7477ff41b2f9cb56b3089a64b20ca39c806\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gis@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/gis@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@math.gl/polygon\": \"npm:^4.1.0\"\n    pbf: \"npm:^3.2.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/b8c643bc9bddeaf3a34b6ea162a36bd7f1943898a326e441a52e6340f248e7fe1be3a031945b0fb46f004a3a0578e25cf7435d54fab5cdb92597aefc1083a43d\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gis@npm:4.3.3, @loaders.gl/gis@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/gis@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@math.gl/polygon\": \"npm:^4.1.0\"\n    pbf: \"npm:^3.2.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/a8577630c38690ccc83e125010d5cb3c8f9e4f05ead1772b3c48e1803a087c1d4e50613bab025dd38f01780c1db80c2f391c61c14912eda67de8b2359be9fc87\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gis@npm:^4.3.3\":\n  version: 4.3.4\n  resolution: \"@loaders.gl/gis@npm:4.3.4\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.4\"\n    \"@loaders.gl/schema\": \"npm:4.3.4\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@math.gl/polygon\": \"npm:^4.1.0\"\n    pbf: \"npm:^3.2.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/45a8a957d29502d50db40b5f17cb7df58eccbea56c4fad31d92b2cce08b7e06b7abc5cd1f9ac1d498091386fd61d588167530099d010424c21dc7f1ca76850f9\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gltf@npm:3.4.15, @loaders.gl/gltf@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/gltf@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/draco\": \"npm:3.4.15\"\n    \"@loaders.gl/images\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/textures\": \"npm:3.4.15\"\n    \"@math.gl/core\": \"npm:^3.5.1\"\n  checksum: 10c0/ac06fd23872b91af49b1916cc23bb4ff75112c5be6d6cc36d3d8464fe386b8774f1573752ca421fa5f96d4d4378214109da86a2cdd834641d460a0944160fe1d\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/gltf@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/gltf@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/draco\": \"npm:4.3.3\"\n    \"@loaders.gl/images\": \"npm:4.3.3\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@loaders.gl/textures\": \"npm:4.3.3\"\n    \"@math.gl/core\": \"npm:^4.1.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/4e2dfc2f6c5ba08d2287cc4f8d508d0225767c6bc0535ed55413cc5e1c7a6aca02798909fcb22cbc8e36e26f4618979d9fd914b078d0459eaab80d937203d943\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/images@npm:3.4.15, @loaders.gl/images@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/images@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n  checksum: 10c0/861db0e3a0dd4da84a5c211459662900a546df9abcdddae47aa21f133247b5d623035654e23d0cf84fd5251b4ea265d93c7f4c44f9636019b2b9d3819fe3e1ca\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/images@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/images@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/2fc2f32880436dbf28d14b642fe2007b41372951b65248e5c5c4dd1bc631236af387b0561b4f5fb2ad85fe72852be79b26c91fa206980aa8d57465d21ba332b3\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/images@npm:4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/images@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/9b6dbcce46b687532bd8d6b73bd24b3e315ead3ed5601e0b46e9a0232300405dc14a67e9e7e5aa7abcd25e104f475ec7ddf27a136b8eccc3e57043be9b07b4d8\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/json@npm:^4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/json@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/10101cb63e0fc210e7a61234aff331c4f4a992eee58a88e6379a663bfd881a6e8cead9600b2fecf7500143d45063d9a082d59592246e1aab72cf15b4fa1555a9\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/loader-utils@npm:3.4.15, @loaders.gl/loader-utils@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/loader-utils@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/worker-utils\": \"npm:3.4.15\"\n    \"@probe.gl/stats\": \"npm:^3.5.0\"\n  checksum: 10c0/ccf5b03f84dc5b6ee310edb6dacfb4d35087a79d411cbafee1f50c6cd7b58caec115dbece3536e226c7a7057252c416380cf9bd85ea0a58c54527bcda6c958e5\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/loader-utils@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/loader-utils@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.2\"\n    \"@probe.gl/log\": \"npm:^4.0.2\"\n    \"@probe.gl/stats\": \"npm:^4.0.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/bbaeb362c63b760569323c50ad3a695737fdc93f5ef68c594aa35668ebb35a8332f53cd0cb74c49cc5cf026b9fad4f889c2efde0dc294a269c10c6b304ec5f0c\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/loader-utils@npm:4.3.3, @loaders.gl/loader-utils@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/loader-utils@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.3\"\n    \"@probe.gl/log\": \"npm:^4.0.2\"\n    \"@probe.gl/stats\": \"npm:^4.0.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/5da26b94b9c9b260bf4da211c3daee801f3f810c56ca81aec75979134c9d4e46ffeb4c74a896605529f2a7858797ad9dd383f3058b7a2dd864e3b0d602209059\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/loader-utils@npm:4.3.4\":\n  version: 4.3.4\n  resolution: \"@loaders.gl/loader-utils@npm:4.3.4\"\n  dependencies:\n    \"@loaders.gl/schema\": \"npm:4.3.4\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.4\"\n    \"@probe.gl/log\": \"npm:^4.0.2\"\n    \"@probe.gl/stats\": \"npm:^4.0.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/f303500f8f0c5313cf7c8bd2e63bba3a885cc9db95ec66ba195f52453800f7da45accf458a53d50ef380983dc2ebdd1067c6bcc2118dc5f79963fa7a09c542ec\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/math@npm:3.4.15\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/math@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/images\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@math.gl/core\": \"npm:^3.5.1\"\n  checksum: 10c0/5f101b1e9a041b9613215cde9a90a64dfe7cca34f86c93c57f6a6930385fa8b4f7536a21761cd7fde019fd8dc98eee568bef5a6d16c964d1aac695e5ce1fd8c9\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/mvt@npm:4.3.3, @loaders.gl/mvt@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/mvt@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.3\"\n    \"@loaders.gl/images\": \"npm:4.3.3\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@math.gl/polygon\": \"npm:^4.1.0\"\n    \"@probe.gl/stats\": \"npm:^4.0.0\"\n    pbf: \"npm:^3.2.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/438061e08d1ca414d798a9414ba71637bfd0a8c1f1175bb4fb19f67553820d275aef664a47d94de6e5b11210be3d6f92cb6c0e2d84caa6a674d7b6c35a94f475\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/mvt@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/mvt@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@math.gl/polygon\": \"npm:^3.5.1\"\n    pbf: \"npm:^3.2.1\"\n  checksum: 10c0/d4d2c07eb5fdcfe1d3e1c4faac0b744757982171c63e810fabba5af6bbc022ebedeee13e8883d25c5a8d4c8f5ddd91e39a66af0528aeb4263684047178d08ee3\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/parquet@npm:^4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/parquet@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/arrow\": \"npm:4.3.2\"\n    \"@loaders.gl/bson\": \"npm:4.3.2\"\n    \"@loaders.gl/compression\": \"npm:4.3.2\"\n    \"@loaders.gl/gis\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@loaders.gl/wkt\": \"npm:4.3.2\"\n    \"@probe.gl/log\": \"npm:^4.0.9\"\n    async-mutex: \"npm:^0.2.2\"\n    base64-js: \"npm:^1.3.1\"\n    brotli: \"npm:^1.3.2\"\n    ieee754: \"npm:^1.2.1\"\n    int53: \"npm:^0.2.4\"\n    lz4js: \"npm:^0.2.0\"\n    node-int64: \"npm:^0.4.0\"\n    object-stream: \"npm:0.0.1\"\n    parquet-wasm: \"npm:^0.6.1\"\n    snappyjs: \"npm:^0.6.0\"\n    thrift: \"npm:^0.19.0\"\n    util: \"npm:^0.12.5\"\n    varint: \"npm:^6.0.0\"\n    zstd-codec: \"npm:^0.1\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n    apache-arrow: \">= 15.0.0\"\n  checksum: 10c0/5a0996df63a24a1e32b8275fa20f9cfa6018eaae55757b9a43c80f9f8e72e8728617d67c7df54c203df0a558de50ca7f3143f7f6aea4c064ab61cb1b2297335e\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/pmtiles@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/pmtiles@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/images\": \"npm:4.3.3\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/mvt\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    pmtiles: \"npm:^3.0.4\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/2b77243fcb3f3b61d795ce5344774feeb604b2d419384ca366a52797739d9614ced381df4d3cde30b0e2c4e5914fd9803b634d1fef785ee15a2d22950ac0148c\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/schema@npm:3.4.15, @loaders.gl/schema@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/schema@npm:3.4.15\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.7\"\n  checksum: 10c0/651b62681c2122d926c3faa5d1bf51550734474c41ab4d118b48fd56959126cf90c4d47522f6bd1ecbd418fc44ef859bd6cecf341eb1470fb3f07acab23e495e\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/schema@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/schema@npm:4.3.2\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.7\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/6ba7f1b60b1c746f71a62e0dc627a13e1f7ebedb338630ce8b00972ef8c67dae614bc698be35618f4bc02c7d2536b2e686329af6076ed22ddddaf2269fc5efd1\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/schema@npm:4.3.3, @loaders.gl/schema@npm:^4.1.0-alpha.4, @loaders.gl/schema@npm:^4.3.2, @loaders.gl/schema@npm:^4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/schema@npm:4.3.3\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.7\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/abc83524fd03f0964a8964e4338667ef9a5de367301cbc73587564b764b7e33e6777f6b61ce8939300fac973f63eca3ccca5353da6384a53504428aedd37d918\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/schema@npm:4.3.4\":\n  version: 4.3.4\n  resolution: \"@loaders.gl/schema@npm:4.3.4\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.7\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/8a1a8d7c3a1c553d02086519252ed37d9311643a1eecae0a037a7c6498fee6c07829054bbdcfd24f040f062857f5a9daa0da4f9d83b773e28723b6281e71ad9c\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/terrain@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/terrain@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/images\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@mapbox/martini\": \"npm:^0.2.0\"\n  checksum: 10c0/50ab91120631e71b8c387476a6b458ed41547db157117716e2d7cc6be855f0f7b54f5c2a3907a02444656f6e109619ff074b70153d5a7a60c94fbebf092ae432\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/textures@npm:3.4.15\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/textures@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/images\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@loaders.gl/worker-utils\": \"npm:3.4.15\"\n    ktx-parse: \"npm:^0.0.4\"\n    texture-compressor: \"npm:^1.0.2\"\n  checksum: 10c0/60362028f6500ac5e01a7a745726e14f80577c837a07f882adbe9b7aa30a39aea21c334f4c228b037ce51ce5a1bf68ef2f305853d779b654651a10a8c907ea68\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/textures@npm:4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/textures@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/images\": \"npm:4.3.3\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n    \"@loaders.gl/worker-utils\": \"npm:4.3.3\"\n    \"@math.gl/types\": \"npm:^4.1.0\"\n    ktx-parse: \"npm:^0.7.0\"\n    texture-compressor: \"npm:^1.0.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/fe8fc9788d3314e9958d87a3da1808c35b657dbf58b5fc7b47ba3fb1de7b41faadc15c01e664e4936884000892aaebed9daa3529d663864467108d9ea6aab032\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/tiles@npm:3.4.15, @loaders.gl/tiles@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/tiles@npm:3.4.15\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/math\": \"npm:3.4.15\"\n    \"@math.gl/core\": \"npm:^3.5.1\"\n    \"@math.gl/culling\": \"npm:^3.5.1\"\n    \"@math.gl/geospatial\": \"npm:^3.5.1\"\n    \"@math.gl/web-mercator\": \"npm:^3.5.1\"\n    \"@probe.gl/stats\": \"npm:^3.5.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^3.4.0\n  checksum: 10c0/fe5437895ef80379112fb336bd8c875993350be83d194ec7c83098dfc6d985c37026342eb75e7c5e5ae89e36fcfbb8c55a32ba79fe168ae2e5b2e291fffbb3c1\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/wkt@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/wkt@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/dc9809948be2dc1fa2b4da322a9c5ad63501b260c2c76ea0a694944aaddf09e6c8586e0999e4073fa039f6e801b9a6e8b3654ffa56d7e5b32a9ccebb938d9b9e\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/wkt@npm:^4.3.2\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/wkt@npm:4.3.3\"\n  dependencies:\n    \"@loaders.gl/gis\": \"npm:4.3.3\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.3\"\n    \"@loaders.gl/schema\": \"npm:4.3.3\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/5b50ca01f6934730107b5ea02f2bda0cadc7f05878c999294573b946bc1ec6d4e10ec73ad7f5f8d09354498b60fb312b82166332b79bcee416e69184124da6a4\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/wms@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/wms@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/images\": \"npm:4.3.2\"\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    \"@loaders.gl/xml\": \"npm:4.3.2\"\n    \"@turf/rewind\": \"npm:^5.1.5\"\n    deep-strict-equal: \"npm:^0.2.0\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/6ab4e3afb91aa65a53eaee88e0538fa91e422b7c23ee38a94f86f76940cbd193d1ca6effebfbe082ecde63c2f8cc1076a92074be6dbd7ff20ff6cd3dbe8aec94\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/wms@npm:^3.4.13\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/wms@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/images\": \"npm:3.4.15\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    \"@loaders.gl/xml\": \"npm:3.4.15\"\n    \"@turf/rewind\": \"npm:^5.1.5\"\n    deep-strict-equal: \"npm:^0.2.0\"\n    lerc: \"npm:^4.0.1\"\n  checksum: 10c0/c3266c8c8d43ae1ba1dd02a536687aa38ab8cc9c50b87cd1473aef4d69fc1eb893f752f5466a638559d54fbd63a4b7fa0fc157eab34a5d37101cf874b9f532ad\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/worker-utils@npm:3.4.15\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/worker-utils@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n  checksum: 10c0/158d5aac65e62e002085cf556e896ef177b6bd0760470c08e556d32e67eadef24b72ca826b55396929007702c664127558c8fe8e5d90569ac9485081ac2772b5\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/worker-utils@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/worker-utils@npm:4.3.2\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/53a2b595f6e85786fb7233e28239dc49b52fb08278d93c85a3f01d9315dcd9404aa0d3458ec586768a43894d7ba1a32e14f10e11a8308dc1c6baed63a06bd8da\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/worker-utils@npm:4.3.3\":\n  version: 4.3.3\n  resolution: \"@loaders.gl/worker-utils@npm:4.3.3\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/d20a666966ce7f0116cf0ec51318152c78342cedb3cc1c0594784bcad65bb94f50929f5b722d8d2842e90eef08e6278bb58c0e028151b9e6076007040ba56fca\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/worker-utils@npm:4.3.4\":\n  version: 4.3.4\n  resolution: \"@loaders.gl/worker-utils@npm:4.3.4\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/2ed330bcdfc4d18dea3c5d86ec92d76cd4b4c8f93277e2553c744982fec27ec6041d8f0b33076043d85242d608087e11640eb0a2a4d40b1e02480e1fc938ab12\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/xml@npm:3.4.15\":\n  version: 3.4.15\n  resolution: \"@loaders.gl/xml@npm:3.4.15\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.3.1\"\n    \"@loaders.gl/loader-utils\": \"npm:3.4.15\"\n    \"@loaders.gl/schema\": \"npm:3.4.15\"\n    fast-xml-parser: \"npm:^4.2.5\"\n  checksum: 10c0/70034a653b3917afa54ba96ce5b7c438e42b41c2130bb91e53966e1ac919739fad055a1a9a84fb0b1ff1765a9bf910c0f55e291595dbb1357731abad522a9558\n  languageName: node\n  linkType: hard\n\n\"@loaders.gl/xml@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@loaders.gl/xml@npm:4.3.2\"\n  dependencies:\n    \"@loaders.gl/loader-utils\": \"npm:4.3.2\"\n    \"@loaders.gl/schema\": \"npm:4.3.2\"\n    fast-xml-parser: \"npm:^4.2.5\"\n  peerDependencies:\n    \"@loaders.gl/core\": ^4.3.0\n  checksum: 10c0/9b0a702fd6d98c4c4f9f26c94cd926fbd6f154b7020d73af93aea2dd93c65bf3cea161053e97269734a2fc80fe85b5288b52b28fea46b74041a0f296798ea39e\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/constants@npm:8.5.21, @luma.gl/constants@npm:^8.5.20, @luma.gl/constants@npm:^8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/constants@npm:8.5.21\"\n  checksum: 10c0/c444920f69eeaf2bc6c569d90871b1fbcd6e85b24c57d6ea11005ee69ca4e1afdf995bf1a38dd5af060c56490247bd42c802ce0c98c665a87a66ccac17c3ec10\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/core@npm:8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/core@npm:8.5.21\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/constants\": \"npm:8.5.21\"\n    \"@luma.gl/engine\": \"npm:8.5.21\"\n    \"@luma.gl/gltools\": \"npm:8.5.21\"\n    \"@luma.gl/shadertools\": \"npm:8.5.21\"\n    \"@luma.gl/webgl\": \"npm:8.5.21\"\n  checksum: 10c0/4ea497838abc7eddf9aa4aa4ff1b1a087e95eedf55258ac30913e8b61f65f439cb7eb548ee74ee90aca202e0577098341f847e2e732e5da312dc376446bb7b65\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/engine@npm:8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/engine@npm:8.5.21\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/constants\": \"npm:8.5.21\"\n    \"@luma.gl/gltools\": \"npm:8.5.21\"\n    \"@luma.gl/shadertools\": \"npm:8.5.21\"\n    \"@luma.gl/webgl\": \"npm:8.5.21\"\n    \"@math.gl/core\": \"npm:^3.5.0\"\n    \"@probe.gl/env\": \"npm:^3.5.0\"\n    \"@probe.gl/stats\": \"npm:^3.5.0\"\n    \"@types/offscreencanvas\": \"npm:^2019.7.0\"\n  checksum: 10c0/e2089447bdcd8425173eb81ad8fdd4b5ce2292d40c13fdd3ba45df79458be84312927d9392ee3f1984c4fa6df11c9ae9a12a2c17faaed205275b5b4303ebe9b9\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/experimental@npm:^8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/experimental@npm:8.5.21\"\n  dependencies:\n    \"@luma.gl/constants\": \"npm:8.5.21\"\n    \"@math.gl/core\": \"npm:^3.5.0\"\n    earcut: \"npm:^2.0.6\"\n  peerDependencies:\n    \"@loaders.gl/gltf\": ^3.0.0\n    \"@loaders.gl/images\": ^3.0.0\n    \"@luma.gl/engine\": ^8.4.0\n    \"@luma.gl/gltools\": ^8.4.0\n    \"@luma.gl/shadertools\": ^8.4.0\n    \"@luma.gl/webgl\": ^8.4.0\n  checksum: 10c0/ed65e12b71fbabca9dccaf237b5d185eb7303491a92a9b8c438f9f8d1d551fe8ac615503bb86fd46c8d2244e9a7fc0b57e4c6254622f3d537d33c4ae329fe6a3\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/gltools@npm:8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/gltools@npm:8.5.21\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/constants\": \"npm:8.5.21\"\n    \"@probe.gl/env\": \"npm:^3.5.0\"\n    \"@probe.gl/log\": \"npm:^3.5.0\"\n    \"@types/offscreencanvas\": \"npm:^2019.7.0\"\n  checksum: 10c0/fa9f57d3c7d364c7754b3ac3f6349c3096ca424802b38bfeb57eb16858e09408515cb59aef681591fab366f4801680e83fe5ba6f7f0f5f73d86ab5f1e1e2c683\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/shadertools@npm:8.5.21, @luma.gl/shadertools@npm:^8.5.20, @luma.gl/shadertools@npm:^8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/shadertools@npm:8.5.21\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@math.gl/core\": \"npm:^3.5.0\"\n  checksum: 10c0/e5b72b4ef292f33c664c2961d5476fd3c8d971c23e3d6b95d08f6988e39c53421f25cf549206271ebe75d282d0dffa2e198f40e2932f47f16e8f1a6727e5c916\n  languageName: node\n  linkType: hard\n\n\"@luma.gl/webgl@npm:8.5.21\":\n  version: 8.5.21\n  resolution: \"@luma.gl/webgl@npm:8.5.21\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@luma.gl/constants\": \"npm:8.5.21\"\n    \"@luma.gl/gltools\": \"npm:8.5.21\"\n    \"@probe.gl/env\": \"npm:^3.5.0\"\n    \"@probe.gl/stats\": \"npm:^3.5.0\"\n  checksum: 10c0/f4e75f2b95ec0019cf3d3fbb8312bfe6efaa0b787689fb8b35745e2000ccfc1bfdb77239ac13c134e430bec65dba4fb11bb3dd12afc0e9bf451f13cefa971e73\n  languageName: node\n  linkType: hard\n\n\"@mapbox/fusspot@npm:^0.4.0\":\n  version: 0.4.0\n  resolution: \"@mapbox/fusspot@npm:0.4.0\"\n  dependencies:\n    is-plain-obj: \"npm:^1.1.0\"\n    xtend: \"npm:^4.0.1\"\n  checksum: 10c0/521af958fca8fb5c7f958713318c4035dd0da27888d3082f5e6993699b4b738a0f31e4a656bf551243a778e3726e0b5a3d37898a4d288eee68ad9bac69661050\n  languageName: node\n  linkType: hard\n\n\"@mapbox/geo-viewport@npm:^0.4.1\":\n  version: 0.4.1\n  resolution: \"@mapbox/geo-viewport@npm:0.4.1\"\n  dependencies:\n    \"@mapbox/sphericalmercator\": \"npm:~1.1.0\"\n  checksum: 10c0/a2fc2cac2c35b4abcb1bf3d6fd4c11234d8c896267b1829d17856ccad246ed174be2230c6d4f8036b72e32438b181e749ee758f072ba2844ceac54c0e19c0148\n  languageName: node\n  linkType: hard\n\n\"@mapbox/geojson-normalize@npm:0.0.1\":\n  version: 0.0.1\n  resolution: \"@mapbox/geojson-normalize@npm:0.0.1\"\n  bin:\n    geojson-normalize: geojson-normalize\n  checksum: 10c0/c51e7fa1462a9152493e2b639ce0b50d4c59ed8615f79c6be509312e4db2f29741cae50ecba1b8e290ce50c50f00c892039df3a5306d15712e51a6a28fcd4ecf\n  languageName: node\n  linkType: hard\n\n\"@mapbox/geojson-rewind@npm:^0.5.0, @mapbox/geojson-rewind@npm:^0.5.2\":\n  version: 0.5.2\n  resolution: \"@mapbox/geojson-rewind@npm:0.5.2\"\n  dependencies:\n    get-stream: \"npm:^6.0.1\"\n    minimist: \"npm:^1.2.6\"\n  bin:\n    geojson-rewind: geojson-rewind\n  checksum: 10c0/631f89ba5b656cb1e02197c242b231f98da0afb96815fa26481497176d6bd5f2aac77af4950da91c954094694acbc26382bd3d38146705737e8ff06442d95a12\n  languageName: node\n  linkType: hard\n\n\"@mapbox/geojson-types@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"@mapbox/geojson-types@npm:1.0.2\"\n  checksum: 10c0/aa0a2cb95a358d8756ab5aa70356bcbd6f554a4571703a88a09e7db6580061d6ef4054db5fe3ecb2817c383b8b5433746a8f46712dc606b32063f73b154f99fc\n  languageName: node\n  linkType: hard\n\n\"@mapbox/jsonlint-lines-primitives@npm:^2.0.2, @mapbox/jsonlint-lines-primitives@npm:~2.0.2\":\n  version: 2.0.2\n  resolution: \"@mapbox/jsonlint-lines-primitives@npm:2.0.2\"\n  checksum: 10c0/5814e42fc453700132f93ea742aabcef9a3c98d9bf17d4c1106f82d1dcd91bbc93052e66e29014323b9b2a41b020c743d897e4a96cc4ed2f734482d587d8c2b2\n  languageName: node\n  linkType: hard\n\n\"@mapbox/mapbox-gl-supported@npm:^1.5.0\":\n  version: 1.5.0\n  resolution: \"@mapbox/mapbox-gl-supported@npm:1.5.0\"\n  peerDependencies:\n    mapbox-gl: \">=0.32.1 <2.0.0\"\n  checksum: 10c0/5b7712e8b546e598dc5152632504cad53081211b64ddc4447825840ddca703275bc36599167b9550ab906ca8a9554936bcdae562073fdef24b8d38d78ee262fb\n  languageName: node\n  linkType: hard\n\n\"@mapbox/mapbox-sdk@npm:^0.15.3\":\n  version: 0.15.6\n  resolution: \"@mapbox/mapbox-sdk@npm:0.15.6\"\n  dependencies:\n    \"@mapbox/fusspot\": \"npm:^0.4.0\"\n    \"@mapbox/parse-mapbox-token\": \"npm:^0.2.0\"\n    \"@mapbox/polyline\": \"npm:^1.0.0\"\n    eventemitter3: \"npm:^3.1.0\"\n    form-data: \"npm:^3.0.0\"\n    got: \"npm:^11.8.5\"\n    is-plain-obj: \"npm:^1.1.0\"\n    xtend: \"npm:^4.0.1\"\n  checksum: 10c0/ea94cdc9b5ba8ce8b603685b92aa63c0a8a848f4a59103d323ae228149784b87d808165ff6929045a7cfdf31c853a4d99f91cf228ea19359c21d348ff169b106\n  languageName: node\n  linkType: hard\n\n\"@mapbox/martini@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"@mapbox/martini@npm:0.2.0\"\n  checksum: 10c0/6d74edcdb9bc2d5243a0f3a42b6e92dce5a6f9c92481d3478246ca1aabe61adf8bfbd965b32271d81493e2ce49bab9671e19c9fd9e23d8f6f98ffd437f212b6c\n  languageName: node\n  linkType: hard\n\n\"@mapbox/parse-mapbox-token@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"@mapbox/parse-mapbox-token@npm:0.2.0\"\n  dependencies:\n    base-64: \"npm:^0.1.0\"\n  checksum: 10c0/86c6c397cdbaee0eb298e70efce4c09ecfc62a502dcecd88b7a1edfa57136b03fbf7bd485bc0e959af5dea6324e394530819ca7105a77d1e2a12355a9a9ab17f\n  languageName: node\n  linkType: hard\n\n\"@mapbox/point-geometry@npm:0.1.0, @mapbox/point-geometry@npm:^0.1.0, @mapbox/point-geometry@npm:~0.1.0\":\n  version: 0.1.0\n  resolution: \"@mapbox/point-geometry@npm:0.1.0\"\n  checksum: 10c0/e4d861908574cb3165f5ad37b000416ebc90a2d6b3e0073191e6b6dc5074a6159d84ac5114d78557399bb429134f0d05bfb529e7902d1cb2b36d722b72ab662c\n  languageName: node\n  linkType: hard\n\n\"@mapbox/polyline@npm:^1.0.0\":\n  version: 1.2.1\n  resolution: \"@mapbox/polyline@npm:1.2.1\"\n  dependencies:\n    meow: \"npm:^9.0.0\"\n  bin:\n    polyline: bin/polyline.bin.js\n  checksum: 10c0/b0defde6b2436db4d48c167310c96a83e17bf8d3300e4202ae5169371adad779897f27430d1be6b6def9da695dfb9e4bca505f21754f43105a4cf9fd98871a28\n  languageName: node\n  linkType: hard\n\n\"@mapbox/sphericalmercator@npm:~1.1.0\":\n  version: 1.1.0\n  resolution: \"@mapbox/sphericalmercator@npm:1.1.0\"\n  bin:\n    bbox: bin/bbox.js\n    to4326: bin/to4326.js\n    to900913: bin/to900913.js\n    xyz: bin/xyz.js\n  checksum: 10c0/06b0a78efe81c33cc28254cc1c26a8a106bce6a997882a52749bf768a66e00ea8b263dea80c77daff22751ebcc7f190e486f23da94ca84a327f0e16c79d83dba\n  languageName: node\n  linkType: hard\n\n\"@mapbox/tiny-sdf@npm:^1.1.1\":\n  version: 1.2.5\n  resolution: \"@mapbox/tiny-sdf@npm:1.2.5\"\n  checksum: 10c0/de0252388a628ddb491c986c715f0b63ca6a74f5dac16d3e51eb75a21935a31e34fba5db47c81cc59a462d782021fc68b2e3cc119b2d6cabe15d31e311674d6c\n  languageName: node\n  linkType: hard\n\n\"@mapbox/tiny-sdf@npm:^2.0.5, @mapbox/tiny-sdf@npm:^2.0.6\":\n  version: 2.0.6\n  resolution: \"@mapbox/tiny-sdf@npm:2.0.6\"\n  checksum: 10c0/cb272578a30c88d6694937af9b084106aa251e92c71089e7d57b0df8152fd0ce0598d5816182a4cd478dc40b188ea680cb6d53f4385107719424beabe7ed4e13\n  languageName: node\n  linkType: hard\n\n\"@mapbox/unitbezier@npm:^0.0.0\":\n  version: 0.0.0\n  resolution: \"@mapbox/unitbezier@npm:0.0.0\"\n  checksum: 10c0/af1943ebeb7532317a5cedfc38d0e580b7bd76cc5c43988df65541d377f3e3fa7d68c201dda20f5239213a4bc81ec5d13146354107196ffc4f14d6f39b8343b5\n  languageName: node\n  linkType: hard\n\n\"@mapbox/unitbezier@npm:^0.0.1\":\n  version: 0.0.1\n  resolution: \"@mapbox/unitbezier@npm:0.0.1\"\n  checksum: 10c0/97f39d4fbdf9579d0a1a8be0d536eb113a805d36459e774014f488a7ca6cc9dcfc77ab7a2ebe5af395ad50da6efb4dbf2566de0db3f62b6b8675cddbace8f86a\n  languageName: node\n  linkType: hard\n\n\"@mapbox/vector-tile@npm:^1.3.1\":\n  version: 1.3.1\n  resolution: \"@mapbox/vector-tile@npm:1.3.1\"\n  dependencies:\n    \"@mapbox/point-geometry\": \"npm:~0.1.0\"\n  checksum: 10c0/ffb271b95c383923768295e72bdf95e428efb906434b864ea04d3853a8373cf0de19f039bd6615f7cf018fbfb4dbf4599f27ebaa86c2b7b09f7d69187f8d7da1\n  languageName: node\n  linkType: hard\n\n\"@mapbox/whoots-js@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"@mapbox/whoots-js@npm:3.1.0\"\n  checksum: 10c0/fe9e959a9049bcbc2c05d9d1156e050191ad697a1bd95e41cdfa069051ff1d6f2930ced234a8d68d5a0bf78091feab30d76497418ec800d90f0aac8691fe4fd4\n  languageName: node\n  linkType: hard\n\n\"@maplibre/maplibre-gl-style-spec@npm:^19.2.1, @maplibre/maplibre-gl-style-spec@npm:^19.3.3\":\n  version: 19.3.3\n  resolution: \"@maplibre/maplibre-gl-style-spec@npm:19.3.3\"\n  dependencies:\n    \"@mapbox/jsonlint-lines-primitives\": \"npm:~2.0.2\"\n    \"@mapbox/unitbezier\": \"npm:^0.0.1\"\n    json-stringify-pretty-compact: \"npm:^3.0.0\"\n    minimist: \"npm:^1.2.8\"\n    rw: \"npm:^1.3.3\"\n    sort-object: \"npm:^3.0.3\"\n  bin:\n    gl-style-format: dist/gl-style-format.mjs\n    gl-style-migrate: dist/gl-style-migrate.mjs\n    gl-style-validate: dist/gl-style-validate.mjs\n  checksum: 10c0/ef315bf9c4e5ebce0d76a7722e53c3e4192b92ea405c95392f61655f551a112bbc17fd00c7c62a16eeb57cdb79a2145e385c4eaf2ca7f50222d2540c9e7e0a7a\n  languageName: node\n  linkType: hard\n\n\"@math.gl/core@npm:3.6.3, @math.gl/core@npm:^3.5.0, @math.gl/core@npm:^3.5.1, @math.gl/core@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"@math.gl/core@npm:3.6.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.0\"\n    \"@math.gl/types\": \"npm:3.6.3\"\n    gl-matrix: \"npm:^3.4.0\"\n  checksum: 10c0/f26b1f5b6f65061190ef087d0c418c020ba6b8391ebe4ac5ba830b900d4a531d84e2099b05583e44893b45dcfc7faae3e3966c6d1629801d013643cddc42120e\n  languageName: node\n  linkType: hard\n\n\"@math.gl/core@npm:4.0.1, @math.gl/core@npm:^4.0.0\":\n  version: 4.0.1\n  resolution: \"@math.gl/core@npm:4.0.1\"\n  dependencies:\n    \"@math.gl/types\": \"npm:4.0.1\"\n  checksum: 10c0/f3c8bfec6c66f0036949a037d565788b99e42edec15d6f3033159901ba1ad3166c9d7208bb33b51abfcbf7a74475e79b941834b47b2577aa2411b8372d07afd6\n  languageName: node\n  linkType: hard\n\n\"@math.gl/core@npm:4.1.0, @math.gl/core@npm:^4.1.0\":\n  version: 4.1.0\n  resolution: \"@math.gl/core@npm:4.1.0\"\n  dependencies:\n    \"@math.gl/types\": \"npm:4.1.0\"\n  checksum: 10c0/495934dc2be0b60cd6ff2cc16a0215608c9254919db741a0074b6b41cef9a0543c7f790eda7d529afa102d2937490608ef75fcc64c789ef2876ae750fd0ed3d6\n  languageName: node\n  linkType: hard\n\n\"@math.gl/culling@npm:^3.5.1, @math.gl/culling@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"@math.gl/culling@npm:3.6.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.0\"\n    \"@math.gl/core\": \"npm:3.6.3\"\n    gl-matrix: \"npm:^3.4.0\"\n  checksum: 10c0/c589e5c1a0b51d2661e4d311e9fb6b2ff18f8a496f186b08c31a2e655f3b9e4d1641b6e285fe795deb9e4e5a07632d2df9b31a3984b57d559b214804c4607e52\n  languageName: node\n  linkType: hard\n\n\"@math.gl/geospatial@npm:^3.5.1\":\n  version: 3.6.3\n  resolution: \"@math.gl/geospatial@npm:3.6.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.0\"\n    \"@math.gl/core\": \"npm:3.6.3\"\n    gl-matrix: \"npm:^3.4.0\"\n  checksum: 10c0/67238ec94ea453d3c2c3bcf64508967402d822546d6fdd7eb43afece420e46a6c2a30e073bcf1d672df021bb08ab230d3733f7d07382a325629e65adcca04852\n  languageName: node\n  linkType: hard\n\n\"@math.gl/polygon@npm:^3.5.1, @math.gl/polygon@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"@math.gl/polygon@npm:3.6.3\"\n  dependencies:\n    \"@math.gl/core\": \"npm:3.6.3\"\n  checksum: 10c0/a4d7a81ed6a2f8dafabaeefb1c9216064ab48b50a5774128b47fa197b58aa4afcf7f4f8279a2fe5a1e5e45b97299b604ae0b1c47ceb029702c3a5ecd6e70452f\n  languageName: node\n  linkType: hard\n\n\"@math.gl/polygon@npm:^4.0.0\":\n  version: 4.0.1\n  resolution: \"@math.gl/polygon@npm:4.0.1\"\n  dependencies:\n    \"@math.gl/core\": \"npm:4.0.1\"\n  checksum: 10c0/ebbd5e698d643078b711e09ebe8750c6a46e7f541b98effffd335bb055f7579792f39a2afd731bf574ddd35025dc1bf2792b1266037a6310cbeae5be3a847d71\n  languageName: node\n  linkType: hard\n\n\"@math.gl/polygon@npm:^4.1.0\":\n  version: 4.1.0\n  resolution: \"@math.gl/polygon@npm:4.1.0\"\n  dependencies:\n    \"@math.gl/core\": \"npm:4.1.0\"\n  checksum: 10c0/0fcfb489c5613ddff6dd0cbea65084e10fa9a3523fb87a36a4fdf10057d12d2a99f1ebd93da6e72b45db0783fb8cd9cff704765473372a8db580faaf50c85ab5\n  languageName: node\n  linkType: hard\n\n\"@math.gl/sun@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"@math.gl/sun@npm:3.6.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.0\"\n  checksum: 10c0/d6453e73c378a1bfaf5a619e752a584b6873f0d733892ae942e2756aef2471ce126420e17f3caaf32571b4f0d45d7b98d62f81652dddb88a893c8452ec8159e4\n  languageName: node\n  linkType: hard\n\n\"@math.gl/types@npm:3.6.3\":\n  version: 3.6.3\n  resolution: \"@math.gl/types@npm:3.6.3\"\n  checksum: 10c0/93beb933ba9a1671c03f728e829ba3653be5ad362d8648aeba7422043bfd323839f08374d85dd7cd03758e0487a84ce0c9f80379eeb19c02d92e0b669a14d294\n  languageName: node\n  linkType: hard\n\n\"@math.gl/types@npm:4.0.1\":\n  version: 4.0.1\n  resolution: \"@math.gl/types@npm:4.0.1\"\n  checksum: 10c0/1488cd0ac119ae21de42ca86e4fee6703962ba620c84257fcdeda63962afd1749868a68e490c41338edc2470c2795b90f1cf9c2006bb0fa079a27fde1508c0f8\n  languageName: node\n  linkType: hard\n\n\"@math.gl/types@npm:4.1.0, @math.gl/types@npm:^4.0.0, @math.gl/types@npm:^4.1.0\":\n  version: 4.1.0\n  resolution: \"@math.gl/types@npm:4.1.0\"\n  checksum: 10c0/3c4dfa5ac5c9e2cef24d31f56b89c1dde785a5d70fd1a7030386346cb7dd4fa2cce5ba983b89842c1971492e30870dd22a078d64893f9c66887e38367bf992fa\n  languageName: node\n  linkType: hard\n\n\"@math.gl/web-mercator@npm:^3.5.1, @math.gl/web-mercator@npm:^3.5.5, @math.gl/web-mercator@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"@math.gl/web-mercator@npm:3.6.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.0\"\n    gl-matrix: \"npm:^3.4.0\"\n  checksum: 10c0/95a1267261e03fa0194ec5ac5f3f8c3a13e1182cf3865d4d3bbd669706d42bd067335d1812df2e844c63c1f0f21c50e08af2b90168f2db1d3fa3ba2087f59703\n  languageName: node\n  linkType: hard\n\n\"@monaco-editor/loader@npm:^1.4.0\":\n  version: 1.4.0\n  resolution: \"@monaco-editor/loader@npm:1.4.0\"\n  dependencies:\n    state-local: \"npm:^1.0.6\"\n  peerDependencies:\n    monaco-editor: \">= 0.21.0 < 1\"\n  checksum: 10c0/68938350adf2f42363a801d87f5d00c87d397d4cba7041141af10a9216bd35c85209b4723a26d56cb32e68eef61471deda2a450f8892891118fbdce7fa1d987d\n  languageName: node\n  linkType: hard\n\n\"@monaco-editor/react@npm:^4.6.0\":\n  version: 4.6.0\n  resolution: \"@monaco-editor/react@npm:4.6.0\"\n  dependencies:\n    \"@monaco-editor/loader\": \"npm:^1.4.0\"\n  peerDependencies:\n    monaco-editor: \">= 0.25.0 < 1\"\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  checksum: 10c0/231e9a9b66a530db326f6732de0ebffcce6b79dcfaf4948923d78b9a3d5e2a04b7a06e1f85bbbca45a5ae15c107a124e4c5c46cabadc20a498fb5f2d05f7f379\n  languageName: node\n  linkType: hard\n\n\"@nebula.gl/edit-modes@npm:1.0.2-alpha.1\":\n  version: 1.0.2-alpha.1\n  resolution: \"@nebula.gl/edit-modes@npm:1.0.2-alpha.1\"\n  dependencies:\n    \"@turf/along\": \"npm:>=6.3.0\"\n    \"@turf/area\": \"npm:>=4.0.0\"\n    \"@turf/bbox\": \"npm:>=4.0.0\"\n    \"@turf/bbox-polygon\": \"npm:>=4.0.0\"\n    \"@turf/bearing\": \"npm:>=4.0.0\"\n    \"@turf/boolean-point-in-polygon\": \"npm:>=4.0.0\"\n    \"@turf/buffer\": \"npm:>=4.0.0\"\n    \"@turf/center\": \"npm:>=4.0.0\"\n    \"@turf/centroid\": \"npm:>=4.0.0\"\n    \"@turf/circle\": \"npm:>=4.0.0\"\n    \"@turf/destination\": \"npm:>=4.0.0\"\n    \"@turf/difference\": \"npm:>=4.0.0\"\n    \"@turf/distance\": \"npm:>=4.0.0\"\n    \"@turf/ellipse\": \"npm:>=4.0.0\"\n    \"@turf/helpers\": \"npm:>=4.0.0\"\n    \"@turf/intersect\": \"npm:>=4.0.0\"\n    \"@turf/line-intersect\": \"npm:>=4.0.0\"\n    \"@turf/nearest-point-on-line\": \"npm:>=4.0.0\"\n    \"@turf/point-to-line-distance\": \"npm:>=4.0.0\"\n    \"@turf/polygon-to-line\": \"npm:>=4.0.0\"\n    \"@turf/rewind\": \"npm:>=4.0.0\"\n    \"@turf/transform-rotate\": \"npm:>=4.0.0\"\n    \"@turf/transform-scale\": \"npm:>=4.0.0\"\n    \"@turf/transform-translate\": \"npm:>=4.0.0\"\n    \"@turf/union\": \"npm:>=4.0.0\"\n    geojson: \"npm:0.5.0\"\n    lodash.throttle: \"npm:^4.1.1\"\n    viewport-mercator-project: \"npm:>=6.0.0\"\n  checksum: 10c0/4a96eb463c3c2cb43f1d8caa123f1571d95f4d1d79e25c45bf038b40f2c7b1839756dc4610fbf5afa23d9c6989e32233e6b11f5cadec2babb12c419d9629e79c\n  languageName: node\n  linkType: hard\n\n\"@nebula.gl/layers@npm:1.0.2-alpha.1\":\n  version: 1.0.2-alpha.1\n  resolution: \"@nebula.gl/layers@npm:1.0.2-alpha.1\"\n  dependencies:\n    \"@danmarshall/deckgl-typings\": \"npm:4.9.12\"\n    \"@nebula.gl/edit-modes\": \"npm:1.0.2-alpha.1\"\n    \"@turf/bbox\": \"npm:>=4.0.0\"\n    \"@turf/bbox-polygon\": \"npm:>=4.0.0\"\n    \"@turf/bearing\": \"npm:>=4.0.0\"\n    \"@turf/boolean-point-in-polygon\": \"npm:>=4.0.0\"\n    \"@turf/buffer\": \"npm:>=4.0.0\"\n    \"@turf/center\": \"npm:>=4.0.0\"\n    \"@turf/centroid\": \"npm:>=4.0.0\"\n    \"@turf/circle\": \"npm:>=4.0.0\"\n    \"@turf/destination\": \"npm:>=4.0.0\"\n    \"@turf/difference\": \"npm:>=4.0.0\"\n    \"@turf/distance\": \"npm:>=4.0.0\"\n    \"@turf/ellipse\": \"npm:>=4.0.0\"\n    \"@turf/helpers\": \"npm:>=4.0.0\"\n    \"@turf/intersect\": \"npm:>=4.0.0\"\n    \"@turf/line-intersect\": \"npm:>=4.0.0\"\n    \"@turf/nearest-point-on-line\": \"npm:>=4.0.0\"\n    \"@turf/point-to-line-distance\": \"npm:>=4.0.0\"\n    \"@turf/polygon-to-line\": \"npm:>=4.0.0\"\n    \"@turf/transform-rotate\": \"npm:>=4.0.0\"\n    \"@turf/transform-scale\": \"npm:>=4.0.0\"\n    \"@turf/transform-translate\": \"npm:>=4.0.0\"\n    \"@turf/union\": \"npm:>=4.0.0\"\n    cubic-hermite-spline: \"npm:^1.0.1\"\n    geojson-types: \"npm:^2.0.1\"\n    global: \"npm:>=4.3.0\"\n    h3-js: \"npm:^3.6.4\"\n    viewport-mercator-project: \"npm:>=6.0.0\"\n  peerDependencies:\n    \"@deck.gl/core\": ^8.6.0\n    \"@deck.gl/extensions\": ^8.6.0\n    \"@deck.gl/geo-layers\": ^8.6.0\n    \"@deck.gl/layers\": ^8.6.0\n    \"@deck.gl/mesh-layers\": ^8.6.0\n    \"@luma.gl/constants\": ^8.5.10\n    \"@luma.gl/core\": ^8.5.10\n  checksum: 10c0/52041841f9ac09e61006200e41f14658faf306c65f671e9ad702da87bcbe36ebc9f279291b536f887d8c60c9ee41f091eec6f0a8948b473b76cecad6325e2c30\n  languageName: node\n  linkType: hard\n\n\"@nodelib/fs.scandir@npm:2.1.5\":\n  version: 2.1.5\n  resolution: \"@nodelib/fs.scandir@npm:2.1.5\"\n  dependencies:\n    \"@nodelib/fs.stat\": \"npm:2.0.5\"\n    run-parallel: \"npm:^1.1.9\"\n  checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb\n  languageName: node\n  linkType: hard\n\n\"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2\":\n  version: 2.0.5\n  resolution: \"@nodelib/fs.stat@npm:2.0.5\"\n  checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d\n  languageName: node\n  linkType: hard\n\n\"@nodelib/fs.walk@npm:^1.2.3\":\n  version: 1.2.8\n  resolution: \"@nodelib/fs.walk@npm:1.2.8\"\n  dependencies:\n    \"@nodelib/fs.scandir\": \"npm:2.1.5\"\n    fastq: \"npm:^1.6.0\"\n  checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1\n  languageName: node\n  linkType: hard\n\n\"@npmcli/agent@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"@npmcli/agent@npm:3.0.0\"\n  dependencies:\n    agent-base: \"npm:^7.1.0\"\n    http-proxy-agent: \"npm:^7.0.0\"\n    https-proxy-agent: \"npm:^7.0.1\"\n    lru-cache: \"npm:^10.0.1\"\n    socks-proxy-agent: \"npm:^8.0.3\"\n  checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271\n  languageName: node\n  linkType: hard\n\n\"@npmcli/fs@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"@npmcli/fs@npm:4.0.0\"\n  dependencies:\n    semver: \"npm:^7.3.5\"\n  checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5\n  languageName: node\n  linkType: hard\n\n\"@openassistant/core@npm:0.5.13, @openassistant/core@npm:^0.5.13\":\n  version: 0.5.13\n  resolution: \"@openassistant/core@npm:0.5.13\"\n  dependencies:\n    \"@ai-sdk/openai\": \"npm:^1.3.21\"\n    \"@ai-sdk/react\": \"npm:^1.2.11\"\n    \"@ai-sdk/ui-utils\": \"npm:^1.2.10\"\n    \"@langchain/core\": \"npm:^0.3.38\"\n    ai: \"npm:^4.3.13\"\n    openai: \"npm:^4.93.0\"\n    openai-zod-functions: \"npm:^0.1.2\"\n    zod: \"npm:^3.24.4\"\n    zod-to-json-schema: \"npm:^3.24.1\"\n  peerDependencies:\n    \"@ai-sdk/anthropic\": ^1.1.14\n    \"@ai-sdk/deepseek\": ^0.1.8\n    \"@ai-sdk/google\": ^1.1.8\n    \"@ai-sdk/xai\": ^1.1.8\n    ollama-ai-provider-v2: ^0.0.5\n    react: \">=18.2\"\n  peerDependenciesMeta:\n    \"@ai-sdk/anthropic\":\n      optional: true\n    \"@ai-sdk/deepseek\":\n      optional: true\n    \"@ai-sdk/google\":\n      optional: true\n    \"@ai-sdk/xai\":\n      optional: true\n    ollama-ai-provider-v2:\n      optional: true\n  checksum: 10c0/46cf0831dd170b125b5ac1754b7ebbcff84f11158574dc51602f1b6ddf02953411d9a500440775f07f2e7fca8707d3295eb5d69676ac561d6d893c35d5b665f0\n  languageName: node\n  linkType: hard\n\n\"@openassistant/core@npm:0.5.18, @openassistant/core@npm:^0.5.17\":\n  version: 0.5.18\n  resolution: \"@openassistant/core@npm:0.5.18\"\n  dependencies:\n    \"@ai-sdk/openai\": \"npm:^1.3.21\"\n    \"@ai-sdk/react\": \"npm:^1.2.11\"\n    \"@ai-sdk/ui-utils\": \"npm:^1.2.10\"\n    \"@langchain/core\": \"npm:^0.3.38\"\n    ai: \"npm:^4.3.19\"\n    openai: \"npm:^4.93.0\"\n    openai-zod-functions: \"npm:^0.1.2\"\n    zod: \"npm:^3.24.4\"\n    zod-to-json-schema: \"npm:^3.24.1\"\n  peerDependencies:\n    \"@ai-sdk/amazon-bedrock\": ^1.1.6\n    \"@ai-sdk/anthropic\": ^1.1.14\n    \"@ai-sdk/deepseek\": ^0.1.8\n    \"@ai-sdk/google\": ^1.1.8\n    \"@ai-sdk/xai\": ^1.1.8\n    ollama-ai-provider-v2: ^0.0.6\n    react: \">=18.2\"\n  peerDependenciesMeta:\n    \"@ai-sdk/amazon-bedrock\":\n      optional: true\n    \"@ai-sdk/anthropic\":\n      optional: true\n    \"@ai-sdk/deepseek\":\n      optional: true\n    \"@ai-sdk/google\":\n      optional: true\n    \"@ai-sdk/xai\":\n      optional: true\n    ollama-ai-provider-v2:\n      optional: true\n  checksum: 10c0/3e370d3f574ae3eaa0cdbf0658812f2d5b9bacb92ee8645fc18cd29c9d0b6e0dd5d66c2377eb2d0b26382d8b9fe42ed320ad43f8b2610c068fdf1a3907098aa6\n  languageName: node\n  linkType: hard\n\n\"@openassistant/duckdb@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/duckdb@npm:0.5.18\"\n  dependencies:\n    \"@duckdb/duckdb-wasm\": \"npm:^1.29.0\"\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    apache-arrow: \"npm:^17.0.0\"\n    zod: \"npm:^3.24.4\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/e9735bd9aff0a2ae2f145d783b38e06ba17af35c5978e266b51b845489b85b9e4f5a0f09f5a3d5700299c9d265fb216e9f6e4640d5be621a9fbcfd986086d487\n  languageName: node\n  linkType: hard\n\n\"@openassistant/echarts@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/echarts@npm:0.5.18\"\n  dependencies:\n    \"@heroui/react\": \"npm:^2.7.8\"\n    \"@iconify/react\": \"npm:^5.1.0\"\n    \"@openassistant/hooks\": \"npm:0.5.18\"\n    \"@openassistant/plots\": \"npm:0.5.18\"\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    echarts: \"npm:^5.5.1\"\n    echarts-for-react: \"npm:^3.0.2\"\n    framer-motion: \"npm:^12.12.1\"\n    tailwindcss: \"npm:^3.4.17\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/80bf54c0329c8b670156c22de099967002811e2865b21413d12fc3395931f6d8a3bdebe8d64ce581c939f8f556ac9d64f1cb29ed292318cf36e37addc4fc2d69\n  languageName: node\n  linkType: hard\n\n\"@openassistant/geoda@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/geoda@npm:0.5.18\"\n  dependencies:\n    \"@geoda/core\": \"npm:^0.0.22\"\n    \"@geoda/lisa\": \"npm:^0.0.22\"\n    \"@geoda/regression\": \"npm:^0.0.22\"\n    \"@loaders.gl/core\": \"npm:^4.3.3\"\n    \"@loaders.gl/gis\": \"npm:^4.3.3\"\n    \"@loaders.gl/schema\": \"npm:^4.3.3\"\n    \"@openassistant/plots\": \"npm:0.5.18\"\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    \"@turf/bbox\": \"npm:^7.2.0\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/79b909e1a2fae13b813ac1e8499699c4d0ead00586b6ba76eda7c34d75bc2375db592df6587ed4963b732152794a00b7b682e0273c5d7213953d437d8fee2766\n  languageName: node\n  linkType: hard\n\n\"@openassistant/hooks@npm:0.5.18\":\n  version: 0.5.18\n  resolution: \"@openassistant/hooks@npm:0.5.18\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/2b34551f2c538135f4f6fec3f6744dda0d9b46f63848c418554c802bdd3466183be9a87618fdd1bd4fde81e9155c33f6f2f5c444471e058405cbf10e5ce3a213\n  languageName: node\n  linkType: hard\n\n\"@openassistant/osm@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/osm@npm:0.5.18\"\n  dependencies:\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    zip3: \"npm:^1.0.4\"\n    zod: \"npm:^3.24.4\"\n  checksum: 10c0/58a902102e1eb61d4b2dc53bc98c0827b07cffd672a2bf19d342c3fa9cf234adfd3e0f5803bd06be95dd4a2c89c80bd97f74a79dd7db68471286fdbfc552f36b\n  languageName: node\n  linkType: hard\n\n\"@openassistant/plots@npm:0.5.18, @openassistant/plots@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/plots@npm:0.5.18\"\n  dependencies:\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    d3-array: \"npm:^3.2.4\"\n    jstat: \"npm:^1.9.6\"\n    simple-statistics: \"npm:^7.8.7\"\n    zod: \"npm:^3.24.4\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/319a6bff1df4551814d880220b2d45596e865142d61a6ccefa9133e005b300fdec7149aeb3d7efced7857037db5eb88461b42cb10a83693acd5dd25597d35a9c\n  languageName: node\n  linkType: hard\n\n\"@openassistant/tables@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/tables@npm:0.5.18\"\n  dependencies:\n    \"@duckdb/duckdb-wasm\": \"npm:1.29.0\"\n    \"@heroui/button\": \"npm:^2.2.19\"\n    \"@heroui/checkbox\": \"npm:^2.3.18\"\n    \"@heroui/pagination\": \"npm:^2.2.17\"\n    \"@heroui/select\": \"npm:^2.4.19\"\n    \"@heroui/system\": \"npm:^2.4.15\"\n    \"@heroui/table\": \"npm:^2.2.18\"\n    \"@heroui/theme\": \"npm:^2.4.15\"\n    \"@iconify/react\": \"npm:^5.1.0\"\n    apache-arrow: \"npm:^17.0.0\"\n    framer-motion: \"npm:^12.12.1\"\n    tailwindcss: \"npm:^3.4.17\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/d3874133ac8ff9ab0268dfc145e74cdbb87cea25d2def177e79b859301d5d9908d4806e9a4ab527def3f632fb3461f06d15996ddebf1b8a582fc4104f7487e46\n  languageName: node\n  linkType: hard\n\n\"@openassistant/ui@npm:^0.5.13\":\n  version: 0.5.13\n  resolution: \"@openassistant/ui@npm:0.5.13\"\n  dependencies:\n    \"@ai-sdk/ui-utils\": \"npm:^1.2.11\"\n    \"@heroui/react\": \"npm:^2.7.8\"\n    \"@heroui/use-clipboard\": \"npm:^2.1.8\"\n    \"@iconify/react\": \"npm:^5.1.0\"\n    \"@openassistant/core\": \"npm:0.5.13\"\n    \"@openassistant/utils\": \"npm:0.5.13\"\n    ai: \"npm:^4.3.16\"\n    framer-motion: \"npm:^11.15.0\"\n    html2canvas: \"npm:^1.4.1\"\n    next-themes: \"npm:^0.4.4\"\n    react-audio-voice-recorder: \"npm:^2.2.0\"\n    react-markdown: \"npm:^10.0.0\"\n    remark-gfm: \"npm:^4.0.1\"\n    tailwindcss: \"npm:^3.4.17\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/06aea41b8e1e026728f9b51518c67fea7bd5fcacbe800b8df69bd7449ef66fd929dc234cba884f9f9249acd509a213375caad1a0c94a132fa0f6473f6c42e04d\n  languageName: node\n  linkType: hard\n\n\"@openassistant/ui@npm:^0.5.17\":\n  version: 0.5.18\n  resolution: \"@openassistant/ui@npm:0.5.18\"\n  dependencies:\n    \"@ai-sdk/ui-utils\": \"npm:^1.2.11\"\n    \"@heroui/react\": \"npm:^2.7.8\"\n    \"@heroui/use-clipboard\": \"npm:^2.1.8\"\n    \"@iconify/react\": \"npm:^5.1.0\"\n    \"@openassistant/core\": \"npm:0.5.18\"\n    \"@openassistant/utils\": \"npm:0.5.18\"\n    ai: \"npm:^4.3.19\"\n    framer-motion: \"npm:^11.15.0\"\n    html2canvas: \"npm:^1.4.1\"\n    next-themes: \"npm:^0.4.4\"\n    react-audio-voice-recorder: \"npm:^2.2.0\"\n    react-markdown: \"npm:^10.0.0\"\n    remark-gfm: \"npm:^4.0.1\"\n    tailwindcss: \"npm:^3.4.17\"\n  peerDependencies:\n    react: \">=18.2\"\n    react-dom: \">=18.2\"\n  checksum: 10c0/d0ddada031f21e24396d0f060b629495506207b6fc875e72fd389be5a2fb6a61fc7424a924eb0709453e35e830ae5b063bb420a4edab721d303a6e0b01794b7e\n  languageName: node\n  linkType: hard\n\n\"@openassistant/utils@npm:0.5.13\":\n  version: 0.5.13\n  resolution: \"@openassistant/utils@npm:0.5.13\"\n  dependencies:\n    zod: \"npm:^3.24.4\"\n  checksum: 10c0/3f28b04cc9d75ada1721cb4292ce4fb97acd500710db4c54b84ab74367182b31674489fa6f22c22d6d0e97649d28a73076b9c163485c80e1c29f711fea18319c\n  languageName: node\n  linkType: hard\n\n\"@openassistant/utils@npm:0.5.18, @openassistant/utils@npm:^0.5.13\":\n  version: 0.5.18\n  resolution: \"@openassistant/utils@npm:0.5.18\"\n  dependencies:\n    zod: \"npm:^3.24.4\"\n  checksum: 10c0/51ca502e44829131c8739d2c0f66bc7b4e8ee07effa356ea9a964561b5aeeb74cb9c81641eb134fca22fa063d7b6ffa096d24e50aefdb351fa08a5cbe5915490\n  languageName: node\n  linkType: hard\n\n\"@opentelemetry/api@npm:1.9.0\":\n  version: 1.9.0\n  resolution: \"@opentelemetry/api@npm:1.9.0\"\n  checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add\n  languageName: node\n  linkType: hard\n\n\"@pkgjs/parseargs@npm:^0.11.0\":\n  version: 0.11.0\n  resolution: \"@pkgjs/parseargs@npm:0.11.0\"\n  checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd\n  languageName: node\n  linkType: hard\n\n\"@popperjs/core@npm:^2.9.0\":\n  version: 2.11.8\n  resolution: \"@popperjs/core@npm:2.11.8\"\n  checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/env@npm:3.6.0, @probe.gl/env@npm:^3.5.0\":\n  version: 3.6.0\n  resolution: \"@probe.gl/env@npm:3.6.0\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n  checksum: 10c0/407593697a94ee871d38abcbe541357140dfcce7075b120eed4f66473eb0496df87890a3b7a77032b006a488a60d7f4bd11a476e672cf33393e0b69daa98bf68\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/env@npm:4.0.9\":\n  version: 4.0.9\n  resolution: \"@probe.gl/env@npm:4.0.9\"\n  checksum: 10c0/c6fcd1742aea014d15fe36a6cf0724d7faf3eeda27856978d87c1658b26ceaefc86254b011de65de35c1dfc0e3074fdbeaef5fd4362a05541a5e46b880e4024f\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/log@npm:^3.5.0\":\n  version: 3.6.0\n  resolution: \"@probe.gl/log@npm:3.6.0\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    \"@probe.gl/env\": \"npm:3.6.0\"\n  checksum: 10c0/03bbddffd89a482a4d539225132154cd1e63eae77a286b22919e5e46e99498f5f1a6a55a61348672e339ed64c7936a85aae980a6120bdd841eb66ec87ff1d2fb\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/log@npm:^4.0.2, @probe.gl/log@npm:^4.0.9\":\n  version: 4.0.9\n  resolution: \"@probe.gl/log@npm:4.0.9\"\n  dependencies:\n    \"@probe.gl/env\": \"npm:4.0.9\"\n  checksum: 10c0/23521b46fdda80470d8b38d70c62d77f3b50257a63b3e7660655936593cf5f54ec7f1e2b0a36e8ecb7635c7fd45280bb66bd379a3e58afbde99f23d46f43c112\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/stats@npm:^3.5.0\":\n  version: 3.6.0\n  resolution: \"@probe.gl/stats@npm:3.6.0\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n  checksum: 10c0/81077d458314a5d08ed5f2393cdd7572f40ea68589bc16ff0cc3e690f54db1443df6499e77c44af0a0ca3faff8613d6895df4e0203f785bf9d83801ff402cc66\n  languageName: node\n  linkType: hard\n\n\"@probe.gl/stats@npm:^4.0.0, @probe.gl/stats@npm:^4.0.2\":\n  version: 4.0.9\n  resolution: \"@probe.gl/stats@npm:4.0.9\"\n  checksum: 10c0/23c205232a45941b13d1e87efe060d81f3a09339b0294991fc9fdf6dd9d090a9e6c79cccf23ce8f09e5c14aa44ef128836639490df22729a13214b0ff3c7cd80\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/primitive@npm:1.1.1\":\n  version: 1.1.1\n  resolution: \"@radix-ui/primitive@npm:1.1.1\"\n  checksum: 10c0/6457bd8d1aa4ecb948e5d2a2484fc570698b2ab472db6d915a8f1eec04823f80423efa60b5ba840f0693bec2ca380333cc5f3b52586b40f407d9f572f9261f8d\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-collapsible@npm:^1.1.0\":\n  version: 1.1.2\n  resolution: \"@radix-ui/react-collapsible@npm:1.1.2\"\n  dependencies:\n    \"@radix-ui/primitive\": \"npm:1.1.1\"\n    \"@radix-ui/react-compose-refs\": \"npm:1.1.1\"\n    \"@radix-ui/react-context\": \"npm:1.1.1\"\n    \"@radix-ui/react-id\": \"npm:1.1.0\"\n    \"@radix-ui/react-presence\": \"npm:1.1.2\"\n    \"@radix-ui/react-primitive\": \"npm:2.0.1\"\n    \"@radix-ui/react-use-controllable-state\": \"npm:1.1.0\"\n    \"@radix-ui/react-use-layout-effect\": \"npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    \"@types/react-dom\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n    react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n    \"@types/react-dom\":\n      optional: true\n  checksum: 10c0/8a725539c0c259ea53a0e35d4ddd3acca42cab5113fd537758450ad1e76f0b757423f18aca29364f963bef4f0624d57feb32bf9d12a3ea6b2c084b523ba65205\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-compose-refs@npm:1.1.1\":\n  version: 1.1.1\n  resolution: \"@radix-ui/react-compose-refs@npm:1.1.1\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/3e84580024e66e3cc5b9ae79355e787815c1d2a3c7d46e7f47900a29c33751ca24cf4ac8903314957ab1f7788aebe1687e2258641c188cf94653f7ddf8f70627\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-context@npm:1.1.1\":\n  version: 1.1.1\n  resolution: \"@radix-ui/react-context@npm:1.1.1\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/fc4ace9d79d7954c715ade765e06c95d7e1b12a63a536bcbe842fb904f03f88fc5bd6e38d44bd23243d37a270b4c44380fedddaeeae2d274f0b898a20665aba2\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-id@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"@radix-ui/react-id@npm:1.1.0\"\n  dependencies:\n    \"@radix-ui/react-use-layout-effect\": \"npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/acf13e29e51ee96336837fc0cfecc306328b20b0e0070f6f0f7aa7a621ded4a1ee5537cfad58456f64bae76caa7f8769231e88dc7dc106197347ee433c275a79\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-presence@npm:1.1.2\":\n  version: 1.1.2\n  resolution: \"@radix-ui/react-presence@npm:1.1.2\"\n  dependencies:\n    \"@radix-ui/react-compose-refs\": \"npm:1.1.1\"\n    \"@radix-ui/react-use-layout-effect\": \"npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    \"@types/react-dom\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n    react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n    \"@types/react-dom\":\n      optional: true\n  checksum: 10c0/0c6fa281368636308044df3be4c1f02733094b5e35ba04f26e610dd1c4315a245ffc758e0e176c444742a7a46f4328af1a9d8181e860175ec39338d06525a78d\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-primitive@npm:2.0.1\":\n  version: 2.0.1\n  resolution: \"@radix-ui/react-primitive@npm:2.0.1\"\n  dependencies:\n    \"@radix-ui/react-slot\": \"npm:1.1.1\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    \"@types/react-dom\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n    react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n    \"@types/react-dom\":\n      optional: true\n  checksum: 10c0/6a562bec14f8e9fbfe0012d6c2932b0e54518fed898fa0622300c463611e77a4ca28a969f0cd484efd6570c01c5665dd6151f736262317d01715bc4da1a7dea6\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-slot@npm:1.1.1\":\n  version: 1.1.1\n  resolution: \"@radix-ui/react-slot@npm:1.1.1\"\n  dependencies:\n    \"@radix-ui/react-compose-refs\": \"npm:1.1.1\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/f3cc71c16529c67a8407a89e0ac13a868cafa0cd05ca185b464db609aa5996a3f00588695518e420bd47ffdb4cc2f76c14cc12ea5a38fc2ca3578a30d2ca58b9\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-use-callback-ref@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"@radix-ui/react-use-callback-ref@npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/e954863f3baa151faf89ac052a5468b42650efca924417470efd1bd254b411a94c69c30de2fdbb90187b38cb984795978e12e30423dc41e4309d93d53b66d819\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-use-controllable-state@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"@radix-ui/react-use-controllable-state@npm:1.1.0\"\n  dependencies:\n    \"@radix-ui/react-use-callback-ref\": \"npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/2af883b5b25822ac226e60a6bfde647c0123a76345052a90219026059b3f7225844b2c13a9a16fba859c1cda5fb3d057f2a04503f71780e607516492db4eb3a1\n  languageName: node\n  linkType: hard\n\n\"@radix-ui/react-use-layout-effect@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"@radix-ui/react-use-layout-effect@npm:1.1.0\"\n  peerDependencies:\n    \"@types/react\": \"*\"\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/9bf87ece1845c038ed95863cfccf9d75f557c2400d606343bab0ab3192b9806b9840e6aa0a0333fdf3e83cf9982632852192f3e68d7d8367bc8c788dfdf8e62b\n  languageName: node\n  linkType: hard\n\n\"@react-aria/breadcrumbs@npm:3.5.23\":\n  version: 3.5.23\n  resolution: \"@react-aria/breadcrumbs@npm:3.5.23\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/link\": \"npm:^3.8.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/breadcrumbs\": \"npm:^3.7.12\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3f0ef474612baeae6ece803897f8e28ee1124d0ce235745aeb85cde6ee89a16c1b157a50b678ae5e466248bce21045ff47a9e20f810484b68a0ffa0ca21ae389\n  languageName: node\n  linkType: hard\n\n\"@react-aria/button@npm:3.13.0\":\n  version: 3.13.0\n  resolution: \"@react-aria/button@npm:3.13.0\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/toolbar\": \"npm:3.0.0-beta.15\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/toggle\": \"npm:^3.8.3\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/43f9cb740889d52e1091316b2997a7d5cbff41ee685610d195cc0987aff8bcec75aa462eb0bfcfdb02bb860b1f0e2fc84038dffbff378a8db3c7f34880acefed\n  languageName: node\n  linkType: hard\n\n\"@react-aria/calendar@npm:3.8.0\":\n  version: 3.8.0\n  resolution: \"@react-aria/calendar@npm:3.8.0\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/calendar\": \"npm:^3.8.0\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/calendar\": \"npm:^3.7.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1354c49f2f6a8fb57d90fc7925dc380cfe7c5f685179f2754f991b101c9941673d0142f7d18cb9094bc31cd7288704fdf83ecf54bea177492993edfe1f2d8168\n  languageName: node\n  linkType: hard\n\n\"@react-aria/checkbox@npm:3.15.4\":\n  version: 3.15.4\n  resolution: \"@react-aria/checkbox@npm:3.15.4\"\n  dependencies:\n    \"@react-aria/form\": \"npm:^3.0.15\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/toggle\": \"npm:^3.11.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/checkbox\": \"npm:^3.6.13\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/toggle\": \"npm:^3.8.3\"\n    \"@react-types/checkbox\": \"npm:^3.9.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f82677187a4f99fb50b6169b392ed5bf301dd72a0d5dfdc622b2134bc607e3ef52e765178ae4add72ea19b6b4ce420a89c05c25f6428102344095841fc4102d7\n  languageName: node\n  linkType: hard\n\n\"@react-aria/checkbox@npm:3.16.0\":\n  version: 3.16.0\n  resolution: \"@react-aria/checkbox@npm:3.16.0\"\n  dependencies:\n    \"@react-aria/form\": \"npm:^3.1.0\"\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/label\": \"npm:^3.7.20\"\n    \"@react-aria/toggle\": \"npm:^3.12.0\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/checkbox\": \"npm:^3.7.0\"\n    \"@react-stately/form\": \"npm:^3.2.0\"\n    \"@react-stately/toggle\": \"npm:^3.9.0\"\n    \"@react-types/checkbox\": \"npm:^3.10.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/6bfc60da1ba1c421bae1931e4db9c6add7dcda5d88f00ca05af8892bbb7aade592899ffc4b62c3d40d8430952d7389a3943ffd6cebd7a8f69bec48c05e0bd3f2\n  languageName: node\n  linkType: hard\n\n\"@react-aria/combobox@npm:3.12.2\":\n  version: 3.12.2\n  resolution: \"@react-aria/combobox@npm:3.12.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/listbox\": \"npm:^3.14.3\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.2\"\n    \"@react-aria/menu\": \"npm:^3.18.2\"\n    \"@react-aria/overlays\": \"npm:^3.27.0\"\n    \"@react-aria/selection\": \"npm:^3.24.0\"\n    \"@react-aria/textfield\": \"npm:^3.17.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/combobox\": \"npm:^3.10.4\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/combobox\": \"npm:^3.13.4\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/6832fc6d88e28d5688b8330853c8ee0109383b869cbce159f31d65fdd5a6d10b5fe637922cc75ea0840ab823207b5e1832dae87e84362b56ef5438f9e6b624f1\n  languageName: node\n  linkType: hard\n\n\"@react-aria/datepicker@npm:3.14.2\":\n  version: 3.14.2\n  resolution: \"@react-aria/datepicker@npm:3.14.2\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@internationalized/number\": \"npm:^3.6.1\"\n    \"@internationalized/string\": \"npm:^3.2.6\"\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/form\": \"npm:^3.0.15\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/spinbutton\": \"npm:^3.6.14\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/datepicker\": \"npm:^3.14.0\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/calendar\": \"npm:^3.7.0\"\n    \"@react-types/datepicker\": \"npm:^3.12.0\"\n    \"@react-types/dialog\": \"npm:^3.5.17\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/adaf07fe07cdd27c473292256be65baabdc5d2a6fd7b377cefd05e14469d719a2bc490c220f9dc4bb7c66dac19b8c30fabea706af1fa59ea3af17893a44f9d9b\n  languageName: node\n  linkType: hard\n\n\"@react-aria/dialog@npm:3.5.24\":\n  version: 3.5.24\n  resolution: \"@react-aria/dialog@npm:3.5.24\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/overlays\": \"npm:^3.27.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/dialog\": \"npm:^3.5.17\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/23d14b9558ecc01f6eb5845de3844d4fd5e48e164e6f62b7701cd2b3ec8a9ea3e7c36d079d03c6929230d877847e69563f9b9756f89c6240aa6f7d472dd30a35\n  languageName: node\n  linkType: hard\n\n\"@react-aria/dialog@npm:3.5.28\":\n  version: 3.5.28\n  resolution: \"@react-aria/dialog@npm:3.5.28\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/overlays\": \"npm:^3.28.0\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/dialog\": \"npm:^3.5.20\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2138c4a2c5845064061639045af12cdbbbdc9fcc8fe2255513aa4e288ef127030d022e67dc727db7155220616521899c5cf627845cf44e54f4ef2375b66271b2\n  languageName: node\n  linkType: hard\n\n\"@react-aria/focus@npm:3.20.2\":\n  version: 3.20.2\n  resolution: \"@react-aria/focus@npm:3.20.2\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/83c7ce227affed990833664b75c99601390ea9c879a44032541447268da22508712c512f5a943f702aef07bfe1e0ea51f554f49db132f17d80b2da9cb71ec687\n  languageName: node\n  linkType: hard\n\n\"@react-aria/focus@npm:3.21.0\":\n  version: 3.21.0\n  resolution: \"@react-aria/focus@npm:3.21.0\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/036b6811b137bc9e305c2340bd9ae0539567f0acc63b6bad6b768a640ade8d1728748ee0dd4da822a1c874e93c40ac042854c7bcd0bc8ee5541a9d4742f41c61\n  languageName: node\n  linkType: hard\n\n\"@react-aria/focus@npm:^3.20.2, @react-aria/focus@npm:^3.20.3\":\n  version: 3.20.3\n  resolution: \"@react-aria/focus@npm:3.20.3\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/c8f220b517fdf52b214d6b74e1ad599e42b5177afdecf6380cf11ee0eac0ad57cf91b8e29fac185881e253f8edf1a88ab242df422aed902004115ea212ade26f\n  languageName: node\n  linkType: hard\n\n\"@react-aria/focus@npm:^3.21.0, @react-aria/focus@npm:^3.21.1\":\n  version: 3.21.1\n  resolution: \"@react-aria/focus@npm:3.21.1\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/9271132d9b215f916a19fa72a8a15eb68dc15a73ed8f9fc41096166c703a27336a1d908e3d55cd95de7eac234037abe3ff1fe2a33f15fc48934e9dd8cb97ff48\n  languageName: node\n  linkType: hard\n\n\"@react-aria/form@npm:3.0.15\":\n  version: 3.0.15\n  resolution: \"@react-aria/form@npm:3.0.15\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b95733c22e0de879443513adbc8c9b26d9903f8ea6a21d63499088df76c895d5b03e73817f135fbd260000a2954b5ea1f4aff7f12003c85288254edca8de82d5\n  languageName: node\n  linkType: hard\n\n\"@react-aria/form@npm:3.1.0\":\n  version: 3.1.0\n  resolution: \"@react-aria/form@npm:3.1.0\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/form\": \"npm:^3.2.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/97212f9904f7cfda797e43a77e2e5371ecccb63193658930832cf1e0955f64e80bdb888fca2c6184dae3c3ba8a3c6e14ac219ae63c6e633558063aadb20bae55\n  languageName: node\n  linkType: hard\n\n\"@react-aria/form@npm:^3.0.15, @react-aria/form@npm:^3.0.16\":\n  version: 3.0.16\n  resolution: \"@react-aria/form@npm:3.0.16\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/5ee975ee59c4d450c44e5365465c5b91d470700a9e0e6d8a8cce93a7727c372d3228259d5d33cad72239751c195ed5734ac440af1095d7f1694c9898c8d41fc5\n  languageName: node\n  linkType: hard\n\n\"@react-aria/form@npm:^3.1.0\":\n  version: 3.1.1\n  resolution: \"@react-aria/form@npm:3.1.1\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-stately/form\": \"npm:^3.2.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/34872e8b30e2e407311e94bc6104af360f8795eaf7c66600c0de2a7c842d5aacc65628493fde92be0b578206761d720088150b12c2b9243032795c5a0b50d3fe\n  languageName: node\n  linkType: hard\n\n\"@react-aria/grid@npm:^3.13.0\":\n  version: 3.14.0\n  resolution: \"@react-aria/grid@npm:3.14.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.3\"\n    \"@react-aria/i18n\": \"npm:^3.12.9\"\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.2\"\n    \"@react-aria/selection\": \"npm:^3.24.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/grid\": \"npm:^3.11.2\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-types/checkbox\": \"npm:^3.9.4\"\n    \"@react-types/grid\": \"npm:^3.3.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ae7f5bb177110a56e0d9f6c8ed7eb070b36684dacfcaa5238198c4e05f9e3220b0f081d6bd6dcaeb4a17e0fe668b7818cdc5a9f1d18aabc475be219b87124f46\n  languageName: node\n  linkType: hard\n\n\"@react-aria/grid@npm:^3.14.3\":\n  version: 3.14.4\n  resolution: \"@react-aria/grid@npm:3.14.4\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.1\"\n    \"@react-aria/i18n\": \"npm:^3.12.12\"\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.4\"\n    \"@react-aria/selection\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/grid\": \"npm:^3.11.5\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-types/checkbox\": \"npm:^3.10.1\"\n    \"@react-types/grid\": \"npm:^3.3.5\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f7cf1586b2c1da0b3e1e3ea66bc1b94d54329815ffba38d069c129fb3c1e724d39d3a1b37f6f7fa3dc58e6203ad3692ac35524d7b9dad926111446fc00fa4985\n  languageName: node\n  linkType: hard\n\n\"@react-aria/i18n@npm:3.12.11\":\n  version: 3.12.11\n  resolution: \"@react-aria/i18n@npm:3.12.11\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.2\"\n    \"@internationalized/message\": \"npm:^3.1.8\"\n    \"@internationalized/number\": \"npm:^3.6.4\"\n    \"@internationalized/string\": \"npm:^3.2.7\"\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/79e8b464e42b17e5d5f6b8e43e9fe641e901048e6c2279412f4ecdb2c3d4caeba26e0dc7ff2cd6fc8a7f1d5f0cfe7aaa7b33212c659ab5f9ab81ef50818b6b7e\n  languageName: node\n  linkType: hard\n\n\"@react-aria/i18n@npm:3.12.8\":\n  version: 3.12.8\n  resolution: \"@react-aria/i18n@npm:3.12.8\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@internationalized/message\": \"npm:^3.1.7\"\n    \"@internationalized/number\": \"npm:^3.6.1\"\n    \"@internationalized/string\": \"npm:^3.2.6\"\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/fc6ccd3a44b084a9ad4001f8adfd086598ed4be229b51c3702b3d289003a67cf0ffd1d93174c16e51bb1745930571ba277d3132a25e6f8d4cc610ef273da7bdb\n  languageName: node\n  linkType: hard\n\n\"@react-aria/i18n@npm:^3.12.11, @react-aria/i18n@npm:^3.12.12\":\n  version: 3.12.12\n  resolution: \"@react-aria/i18n@npm:3.12.12\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.9.0\"\n    \"@internationalized/message\": \"npm:^3.1.8\"\n    \"@internationalized/number\": \"npm:^3.6.5\"\n    \"@internationalized/string\": \"npm:^3.2.7\"\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/83e1c4d0f246951ca9da6adf2e2825d50668b31f2de62a23ac04a0d9dd3e874a17e4616c72321a3fca6a99e22460f79fb15dee86637b6c7bea5c00835a076f8a\n  languageName: node\n  linkType: hard\n\n\"@react-aria/i18n@npm:^3.12.8, @react-aria/i18n@npm:^3.12.9\":\n  version: 3.12.9\n  resolution: \"@react-aria/i18n@npm:3.12.9\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.1\"\n    \"@internationalized/message\": \"npm:^3.1.7\"\n    \"@internationalized/number\": \"npm:^3.6.2\"\n    \"@internationalized/string\": \"npm:^3.2.6\"\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/478955ee021ffb7bd4b34b248060b655a9ab05e3d60c3b4e0c1f01da45936572202b55a0f41a5464a78d850ba5f23135dc0a44823be43728db16df69a0e146d3\n  languageName: node\n  linkType: hard\n\n\"@react-aria/interactions@npm:3.25.0\":\n  version: 3.25.0\n  resolution: \"@react-aria/interactions@npm:3.25.0\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/091c7b7b2f94b2fefed440b97a986eff0320d5ba5e1a43c195a912e035aa8ce4d4be15f1852b2dffdbcc6a70190a74f0809e4d4cf3d7646b70c9f9b0fca829f5\n  languageName: node\n  linkType: hard\n\n\"@react-aria/interactions@npm:3.25.4\":\n  version: 3.25.4\n  resolution: \"@react-aria/interactions@npm:3.25.4\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/38375299a2cb9e91de8a2e599dab768e9ecc66b8c49311a2d128b6af96f954270136c9f469540ff99cf9291ea45cf531afdd3e63d8c88eb11d06976cef07f0c8\n  languageName: node\n  linkType: hard\n\n\"@react-aria/interactions@npm:^3.25.0, @react-aria/interactions@npm:^3.25.1\":\n  version: 3.25.1\n  resolution: \"@react-aria/interactions@npm:3.25.1\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/9f9d63c0eae8809810883d3469044beab2e8df074a992ba935b6aeb74831c13cb463b5854b61deb3ebc1ccf7c05d70d2fae85d40375d7b8b56503100010c7c6d\n  languageName: node\n  linkType: hard\n\n\"@react-aria/interactions@npm:^3.25.4, @react-aria/interactions@npm:^3.25.5\":\n  version: 3.25.5\n  resolution: \"@react-aria/interactions@npm:3.25.5\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/056875ecc08b085134cc8298d5824ed55ff11433cd240320e14f8514e517d64f02f6a95e414a5304f46488c83090e3d1c138b0cf9cbe5d6fdab4e5a4bad5d727\n  languageName: node\n  linkType: hard\n\n\"@react-aria/label@npm:3.7.17\":\n  version: 3.7.17\n  resolution: \"@react-aria/label@npm:3.7.17\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3a120b1cec9ac10f9cdb970a03651a64c5d80cb9b5f968d644164d5e736d2834968a1ef9252aefa633af577378a2884a564b4a99daead920f00ea2c861da8267\n  languageName: node\n  linkType: hard\n\n\"@react-aria/label@npm:3.7.20\":\n  version: 3.7.20\n  resolution: \"@react-aria/label@npm:3.7.20\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b845674d02b05403d853beee835129970570d29284241858728e3b650b07ae10c46f5e9af8022f40f57437295d6ae652914ee8342ce996d1bc1a395f96c85f5d\n  languageName: node\n  linkType: hard\n\n\"@react-aria/label@npm:^3.7.17, @react-aria/label@npm:^3.7.18\":\n  version: 3.7.18\n  resolution: \"@react-aria/label@npm:3.7.18\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4d2bc1120dd76c894ef23209e09a47a6ea09e38d9d6f4f9f4af0b9ef158a1956178da9cbf4b49644672c558056a865fa6a98ec9a91c45c7275af230fb08c8fc4\n  languageName: node\n  linkType: hard\n\n\"@react-aria/label@npm:^3.7.20\":\n  version: 3.7.21\n  resolution: \"@react-aria/label@npm:3.7.21\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/34d55f423cd0ca6061453b2feee0dacc6ad70f7ddea7922615287a11283c8fc053e89e7425b2f2ca3d7e1a077b1bcedf5a2b4c6e95e8c7a203756b6703ffbd78\n  languageName: node\n  linkType: hard\n\n\"@react-aria/landmark@npm:^3.0.2\":\n  version: 3.0.3\n  resolution: \"@react-aria/landmark@npm:3.0.3\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    use-sync-external-store: \"npm:^1.4.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/107ee577c79511ba39dd8433d3ed69861e520d300cc34633f800e3f7bf29e801d76f2695a5ed0c358e8ee2b0fa0419edb5b0fd9ec6b01a87ace0e0334d0fe70e\n  languageName: node\n  linkType: hard\n\n\"@react-aria/link@npm:3.8.0\":\n  version: 3.8.0\n  resolution: \"@react-aria/link@npm:3.8.0\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/link\": \"npm:^3.6.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ecd7c477cf671ec6daed2faf100779ee98e230c3feb513a38ca7588034592db378f5294c6932fd670bee1fbdb4721ac6c8d8e817fbbfed61997b364f98f39b02\n  languageName: node\n  linkType: hard\n\n\"@react-aria/link@npm:^3.8.0\":\n  version: 3.8.1\n  resolution: \"@react-aria/link@npm:3.8.1\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/link\": \"npm:^3.6.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3976c045a109a8b2123b2f363b23a79abc2c6764e3d2e12f7cbdca46b3328e9c0a5e9a4d1bb383684b5c427938c4afac6b9f40179ec7b2483ee4b9e2d028947b\n  languageName: node\n  linkType: hard\n\n\"@react-aria/listbox@npm:3.14.3\":\n  version: 3.14.3\n  resolution: \"@react-aria/listbox@npm:3.14.3\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/selection\": \"npm:^3.24.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/list\": \"npm:^3.12.1\"\n    \"@react-types/listbox\": \"npm:^3.6.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ff386c6dcbcea500699c06029abb22a0c04a6421e8adb354fa55d9dfe80e3e0b3848175bc7c1e1634cf7ad7abdf7ab8daf2930bf039409df424bf88b10a6f0d8\n  languageName: node\n  linkType: hard\n\n\"@react-aria/listbox@npm:3.14.7\":\n  version: 3.14.7\n  resolution: \"@react-aria/listbox@npm:3.14.7\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/label\": \"npm:^3.7.20\"\n    \"@react-aria/selection\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/collections\": \"npm:^3.12.6\"\n    \"@react-stately/list\": \"npm:^3.12.4\"\n    \"@react-types/listbox\": \"npm:^3.7.2\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3a15f9a32dff26b84d53d3c89c15820bbc112a189ab81c4e4c217b1feb2d810870dc4368a83eca56e3f4a9925ac210a9ce7e5d990890856fd544366f68e7f02a\n  languageName: node\n  linkType: hard\n\n\"@react-aria/listbox@npm:^3.14.3\":\n  version: 3.14.4\n  resolution: \"@react-aria/listbox@npm:3.14.4\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/label\": \"npm:^3.7.18\"\n    \"@react-aria/selection\": \"npm:^3.24.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/list\": \"npm:^3.12.2\"\n    \"@react-types/listbox\": \"npm:^3.7.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1fe30a23021d04164671bade1519426246caa4da3cf853daf2211d25405dcc2aa91800638603fbab878b439f32367b8528da283db6c2f7f59ff354e9de39148c\n  languageName: node\n  linkType: hard\n\n\"@react-aria/live-announcer@npm:^3.4.2\":\n  version: 3.4.2\n  resolution: \"@react-aria/live-announcer@npm:3.4.2\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/db9c08540316f309a50d7f2af893904999500db3ede82a95209c2614613f2be2605a3396795dc3cfcde399c1cd7733383e1f5d4783e92723edce0791108e13d7\n  languageName: node\n  linkType: hard\n\n\"@react-aria/live-announcer@npm:^3.4.4\":\n  version: 3.4.4\n  resolution: \"@react-aria/live-announcer@npm:3.4.4\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/1598372e773ee8dbb2f1d2a946652384f5140ab54106416e2a182c72eaabc1b3739e624bac7aea3d95429ba16487074c782ff90db093be36dd1d4cf84f9f9a17\n  languageName: node\n  linkType: hard\n\n\"@react-aria/menu@npm:3.18.2\":\n  version: 3.18.2\n  resolution: \"@react-aria/menu@npm:3.18.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/overlays\": \"npm:^3.27.0\"\n    \"@react-aria/selection\": \"npm:^3.24.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/menu\": \"npm:^3.9.3\"\n    \"@react-stately/selection\": \"npm:^3.20.1\"\n    \"@react-stately/tree\": \"npm:^3.8.9\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/menu\": \"npm:^3.10.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ed1d6795398e8756f1206d7fdb9a7d23b3ef4b9c95b20ca3b3c6e1cab8e36c24d2e78531dab6e65e7d4a4ca831246fbd3c61db4bd8d1773a4957f2a86fe450e6\n  languageName: node\n  linkType: hard\n\n\"@react-aria/menu@npm:3.19.0\":\n  version: 3.19.0\n  resolution: \"@react-aria/menu@npm:3.19.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.0\"\n    \"@react-aria/i18n\": \"npm:^3.12.11\"\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/overlays\": \"npm:^3.28.0\"\n    \"@react-aria/selection\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/collections\": \"npm:^3.12.6\"\n    \"@react-stately/menu\": \"npm:^3.9.6\"\n    \"@react-stately/selection\": \"npm:^3.20.4\"\n    \"@react-stately/tree\": \"npm:^3.9.1\"\n    \"@react-types/button\": \"npm:^3.13.0\"\n    \"@react-types/menu\": \"npm:^3.10.3\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8f149eb798430a48e66416200d14eea95a6d750198be7a3477a9fb5a00b8227a176b6f192b9e5b22d54166dcf05192a3f59db90a8b3fbab1bac466a67c3a51f7\n  languageName: node\n  linkType: hard\n\n\"@react-aria/menu@npm:^3.18.2\":\n  version: 3.18.3\n  resolution: \"@react-aria/menu@npm:3.18.3\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.3\"\n    \"@react-aria/i18n\": \"npm:^3.12.9\"\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/overlays\": \"npm:^3.27.1\"\n    \"@react-aria/selection\": \"npm:^3.24.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/menu\": \"npm:^3.9.4\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-stately/tree\": \"npm:^3.8.10\"\n    \"@react-types/button\": \"npm:^3.12.1\"\n    \"@react-types/menu\": \"npm:^3.10.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/107a84dc82fe199d1834dcbaa43125a43c4057a5160f094fa4a71cd579dc3e8c9edcbd72e770b192f669a8a60da1d9fec3984e3799f20e81c1505fbeb7e24fb9\n  languageName: node\n  linkType: hard\n\n\"@react-aria/numberfield@npm:3.11.13\":\n  version: 3.11.13\n  resolution: \"@react-aria/numberfield@npm:3.11.13\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/spinbutton\": \"npm:^3.6.14\"\n    \"@react-aria/textfield\": \"npm:^3.17.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/numberfield\": \"npm:^3.9.11\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/numberfield\": \"npm:^3.8.10\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2d2a138d77006c74af2ec5eeb50ef7a7d7ecc4d195bcf7b2b932ca0ff063596f4f0aa8f3bbc99db79a8ddddf3e7fa9e1092395a7599de41c2de76c652af8c11f\n  languageName: node\n  linkType: hard\n\n\"@react-aria/overlays@npm:3.27.0\":\n  version: 3.27.0\n  resolution: \"@react-aria/overlays@npm:3.27.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.22\"\n    \"@react-stately/overlays\": \"npm:^3.6.15\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/overlays\": \"npm:^3.8.14\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2835dba32a055160341b0c04c8922c4cfb905d2c630508f9b6fc94844131e5359fd27e72f44b7d313339519285465cc6b08c720f2deb8511185d1bdd6c7b10f3\n  languageName: node\n  linkType: hard\n\n\"@react-aria/overlays@npm:3.28.0\":\n  version: 3.28.0\n  resolution: \"@react-aria/overlays@npm:3.28.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.0\"\n    \"@react-aria/i18n\": \"npm:^3.12.11\"\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.26\"\n    \"@react-stately/overlays\": \"npm:^3.6.18\"\n    \"@react-types/button\": \"npm:^3.13.0\"\n    \"@react-types/overlays\": \"npm:^3.9.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ecd3ce4ad847d667f7b57e8fedc300b41f2a2f1694cdd3510bfac6635f2b23bcb114ec13cdb6255160eefbcbfc3aa80d6e19d77764e76e2558c8b761218a304e\n  languageName: node\n  linkType: hard\n\n\"@react-aria/overlays@npm:^3.27.0, @react-aria/overlays@npm:^3.27.1\":\n  version: 3.27.1\n  resolution: \"@react-aria/overlays@npm:3.27.1\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.3\"\n    \"@react-aria/i18n\": \"npm:^3.12.9\"\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.23\"\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-types/button\": \"npm:^3.12.1\"\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/994034f759a0f0501ff8804f10988af60233cadb68222ee76ce956d685fb7639fbf82491c59f51e499083a2d25931ff2f55e673c4b276c870dabc4736d79c035\n  languageName: node\n  linkType: hard\n\n\"@react-aria/overlays@npm:^3.28.0\":\n  version: 3.29.1\n  resolution: \"@react-aria/overlays@npm:3.29.1\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.1\"\n    \"@react-aria/i18n\": \"npm:^3.12.12\"\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.27\"\n    \"@react-stately/overlays\": \"npm:^3.6.19\"\n    \"@react-types/button\": \"npm:^3.14.0\"\n    \"@react-types/overlays\": \"npm:^3.9.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e69f2178cbbd30bd43373ca4dcb68edf275dae57926912c2845bd109b0ddf5820e28e8882df049ce188a42a1690ae7a31795d0be8895318b80478c61baf8af4c\n  languageName: node\n  linkType: hard\n\n\"@react-aria/progress@npm:3.4.22\":\n  version: 3.4.22\n  resolution: \"@react-aria/progress@npm:3.4.22\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/progress\": \"npm:^3.5.11\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/452c2f69891f33996777133c0a584eb850d2e4761539afe9942ab8e3312042d4d63d03df482edb67f1d7abcd7de1805bdc4c0515e408364e0975af7f644e01c8\n  languageName: node\n  linkType: hard\n\n\"@react-aria/radio@npm:3.11.2\":\n  version: 3.11.2\n  resolution: \"@react-aria/radio@npm:3.11.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/form\": \"npm:^3.0.15\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/radio\": \"npm:^3.10.12\"\n    \"@react-types/radio\": \"npm:^3.8.8\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/672c88dba8d1f6bdd81d80fce339b463f1158c37342d4cb531947b2ebf00bce865e0dce3524beca978dbcafe2d6d26617256af24d2eb7c3d992d1772d907dec6\n  languageName: node\n  linkType: hard\n\n\"@react-aria/selection@npm:3.24.0\":\n  version: 3.24.0\n  resolution: \"@react-aria/selection@npm:3.24.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/selection\": \"npm:^3.20.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/793c707617cc8225307af19d9f5a515bd257196a1f9b006070af309ef90977769a1c8f0fa5cebacac3ed56bcfadbc511bb5f282c8ca186906ac089ee46122918\n  languageName: node\n  linkType: hard\n\n\"@react-aria/selection@npm:3.25.0\":\n  version: 3.25.0\n  resolution: \"@react-aria/selection@npm:3.25.0\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.0\"\n    \"@react-aria/i18n\": \"npm:^3.12.11\"\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-stately/selection\": \"npm:^3.20.4\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a69c1492095dad5a60304039b7d9ed0f7fa0134b21061218867c2327cc396ec27d0aa3be461e71afc3defce146f04e2290208c1062bc8425e218f8c011259358\n  languageName: node\n  linkType: hard\n\n\"@react-aria/selection@npm:^3.24.0, @react-aria/selection@npm:^3.24.1\":\n  version: 3.24.1\n  resolution: \"@react-aria/selection@npm:3.24.1\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.3\"\n    \"@react-aria/i18n\": \"npm:^3.12.9\"\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b99ae6254f20660dff73b8f62c6ab8920df5059491aecf010e0029ea4deea28b7aed1f131dca71aa2a4e78a917db25f8b4994d836142ec6a0923b10765f262a9\n  languageName: node\n  linkType: hard\n\n\"@react-aria/selection@npm:^3.25.0, @react-aria/selection@npm:^3.25.1\":\n  version: 3.25.1\n  resolution: \"@react-aria/selection@npm:3.25.1\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.1\"\n    \"@react-aria/i18n\": \"npm:^3.12.12\"\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/7212dfc3280167c5f87256bbc580c3f05e1a8388d93ce5d66090778b67b7a3bcb49c522172a1a062c0c237204e1d85e6a9cb8ae6095725ed7f1e194ba277ed0e\n  languageName: node\n  linkType: hard\n\n\"@react-aria/slider@npm:3.7.18\":\n  version: 3.7.18\n  resolution: \"@react-aria/slider@npm:3.7.18\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/slider\": \"npm:^3.6.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/slider\": \"npm:^3.7.10\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/caecc4782fa2a904d995a39346e88225b7a89c40d3ba54d798f85632b7081e384081a15e9ff74bae2218b7d334180af3040b86269cf49b4e596c784a362c497a\n  languageName: node\n  linkType: hard\n\n\"@react-aria/spinbutton@npm:^3.6.14\":\n  version: 3.6.15\n  resolution: \"@react-aria/spinbutton@npm:3.6.15\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.9\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.2\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/button\": \"npm:^3.12.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8c433910413c4cf6f85a7b0a0c2c66b198f6a031918cf66739e5c2e209f2180a950be4f9701060d9170e761def7e92152ae1012834c4679b3121c8cd790ab2fe\n  languageName: node\n  linkType: hard\n\n\"@react-aria/ssr@npm:3.9.10, @react-aria/ssr@npm:^3.9.10\":\n  version: 3.9.10\n  resolution: \"@react-aria/ssr@npm:3.9.10\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/44acb4c441d9c5d65aab94aa81fd8368413cf2958ab458582296dd78f6ba4783583f2311fa986120060e5c26b54b1f01e8910ffd17e4f41ccc5fc8c357d84089\n  languageName: node\n  linkType: hard\n\n\"@react-aria/ssr@npm:3.9.8, @react-aria/ssr@npm:^3.9.8\":\n  version: 3.9.8\n  resolution: \"@react-aria/ssr@npm:3.9.8\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/848cac34f8584477ab6c91686ab447c7f7eee997e0b1771cc71298d15a4dd0400ce7b899ad8c1603a72d59a72f24a390964133693a3ba602828801d4dacc3f45\n  languageName: node\n  linkType: hard\n\n\"@react-aria/switch@npm:3.7.2\":\n  version: 3.7.2\n  resolution: \"@react-aria/switch@npm:3.7.2\"\n  dependencies:\n    \"@react-aria/toggle\": \"npm:^3.11.2\"\n    \"@react-stately/toggle\": \"npm:^3.8.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/switch\": \"npm:^3.5.10\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/86ef2597e387480e49c118f42778407562a2cbbc8a2c1ad06750496ac9890b58157997e77e2f74def22e30a19eeaf0a127e08a28e6644d387b48bbf72bdde132\n  languageName: node\n  linkType: hard\n\n\"@react-aria/table@npm:3.17.2\":\n  version: 3.17.2\n  resolution: \"@react-aria/table@npm:3.17.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/grid\": \"npm:^3.13.0\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.22\"\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-stately/table\": \"npm:^3.14.1\"\n    \"@react-types/checkbox\": \"npm:^3.9.3\"\n    \"@react-types/grid\": \"npm:^3.3.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/table\": \"npm:^3.12.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/858399030bf4e373d020c250d770645b96c1c7e5721c96f7f02c619ef19d7833b6fe8c00f608239cf6079da856ba6d87a7e15e88c01be077c9b5b03b0133c024\n  languageName: node\n  linkType: hard\n\n\"@react-aria/table@npm:3.17.6\":\n  version: 3.17.6\n  resolution: \"@react-aria/table@npm:3.17.6\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.21.0\"\n    \"@react-aria/grid\": \"npm:^3.14.3\"\n    \"@react-aria/i18n\": \"npm:^3.12.11\"\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/live-announcer\": \"npm:^3.4.4\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-aria/visually-hidden\": \"npm:^3.8.26\"\n    \"@react-stately/collections\": \"npm:^3.12.6\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-stately/table\": \"npm:^3.14.4\"\n    \"@react-types/checkbox\": \"npm:^3.10.0\"\n    \"@react-types/grid\": \"npm:^3.3.4\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@react-types/table\": \"npm:^3.13.2\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a6ab8cbead9954ab9df52a0c135702c257b149f6e71d6d82ff608f8515baa0a0c5f4df123586a00c943a9d4a815ed2b0cdf6af6e6fba0f80bed622d148572dc7\n  languageName: node\n  linkType: hard\n\n\"@react-aria/tabs@npm:3.10.2\":\n  version: 3.10.2\n  resolution: \"@react-aria/tabs@npm:3.10.2\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/selection\": \"npm:^3.24.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/tabs\": \"npm:^3.8.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/tabs\": \"npm:^3.3.14\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/594d6afcb1c9232823159ef3f655bea194c5cabf3c5868e8c55a29eac103508c2cefde61a28a71046fe43fb8ca5fc24deea4fe2cc1b7a6c5be30e7c4cef843f7\n  languageName: node\n  linkType: hard\n\n\"@react-aria/textfield@npm:3.17.2\":\n  version: 3.17.2\n  resolution: \"@react-aria/textfield@npm:3.17.2\"\n  dependencies:\n    \"@react-aria/form\": \"npm:^3.0.15\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/label\": \"npm:^3.7.17\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/textfield\": \"npm:^3.12.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/c7357e67f4a63d2526792a5741cbf222cfb8d56f816e0c3a524d0ee3e0f05c9adbe294f2ff65f89a648d77351f6b5c80c374116a5d95c3b1a2c358f6ad2f91da\n  languageName: node\n  linkType: hard\n\n\"@react-aria/textfield@npm:^3.17.2\":\n  version: 3.17.3\n  resolution: \"@react-aria/textfield@npm:3.17.3\"\n  dependencies:\n    \"@react-aria/form\": \"npm:^3.0.16\"\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/label\": \"npm:^3.7.18\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@react-types/textfield\": \"npm:^3.12.2\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2459dd50fa0b0dbe5235a53fbe38fdf8f96bee21df713dcf3e8561ad350b337f37887bbcfa26a0ecaaec50616f6da28ceb829853cbdb0ecd8ba135a5b5ed182d\n  languageName: node\n  linkType: hard\n\n\"@react-aria/toast@npm:3.0.2\":\n  version: 3.0.2\n  resolution: \"@react-aria/toast@npm:3.0.2\"\n  dependencies:\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/landmark\": \"npm:^3.0.2\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/toast\": \"npm:^3.1.0\"\n    \"@react-types/button\": \"npm:^3.12.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/65c377028a39e15fe54c74e24cb2ad382f71a8425aa9b7dd6a717dbc7d3720a7b8c9f148b794ffcc0d2735e8486f098b1531e52affd0202279be2e1391626e01\n  languageName: node\n  linkType: hard\n\n\"@react-aria/toggle@npm:^3.11.2\":\n  version: 3.11.3\n  resolution: \"@react-aria/toggle@npm:3.11.3\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-stately/toggle\": \"npm:^3.8.4\"\n    \"@react-types/checkbox\": \"npm:^3.9.4\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/23bd7694b3d95cd192b732f665524f1a3205cc564243a006c0f53136dbe89cb086e017c49ef7f9f4cf30e24c214fdbde20519431de58817705291344a583b42e\n  languageName: node\n  linkType: hard\n\n\"@react-aria/toggle@npm:^3.12.0\":\n  version: 3.12.1\n  resolution: \"@react-aria/toggle@npm:3.12.1\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-stately/toggle\": \"npm:^3.9.1\"\n    \"@react-types/checkbox\": \"npm:^3.10.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3ace07768327c7d86f57d7bcf22a60d64c84f7d9adef66e80db2194d237f82016a635903417289eaa7408caca74fee094b8e9dc7ac7d923823b63eabcc094b38\n  languageName: node\n  linkType: hard\n\n\"@react-aria/toolbar@npm:3.0.0-beta.15\":\n  version: 3.0.0-beta.15\n  resolution: \"@react-aria/toolbar@npm:3.0.0-beta.15\"\n  dependencies:\n    \"@react-aria/focus\": \"npm:^3.20.2\"\n    \"@react-aria/i18n\": \"npm:^3.12.8\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/cdd5f5be3bc3cdd95fe8e25187380969dff133b2119c3e498286ab0d9fceac25325d2684e39f27c39c06a24eacf828cba27270ce191e7ad81086885b25f16691\n  languageName: node\n  linkType: hard\n\n\"@react-aria/tooltip@npm:3.8.2\":\n  version: 3.8.2\n  resolution: \"@react-aria/tooltip@npm:3.8.2\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-stately/tooltip\": \"npm:^3.5.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/tooltip\": \"npm:^3.4.16\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b24c1912b8e8e26088f0b726163b822005415bc4fb37112068a941384a2bcc3b088333ef0551c52e0317009e9f82ec0a4f6e911ef7bc086402df61a4c2827e89\n  languageName: node\n  linkType: hard\n\n\"@react-aria/utils@npm:3.28.2\":\n  version: 3.28.2\n  resolution: \"@react-aria/utils@npm:3.28.2\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/69fc00d5dbd0fae9349a06cc3bcf327aa1edcb9049a491d0949b4de30c1b7669edc7150cc6885aa362af74a21b68c400b2965c3e117871264c47c379f4a98695\n  languageName: node\n  linkType: hard\n\n\"@react-aria/utils@npm:3.30.0\":\n  version: 3.30.0\n  resolution: \"@react-aria/utils@npm:3.30.0\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d845c76eee4ce09248244b698df11c4eb6bf374a9735d74af8acf9e2d4f01ab0653dd707a48f8b1c5cc54b23ce0eb2b2897f770c82eb35045a284f2fbd572f34\n  languageName: node\n  linkType: hard\n\n\"@react-aria/utils@npm:^3.28.2, @react-aria/utils@npm:^3.29.0\":\n  version: 3.29.0\n  resolution: \"@react-aria/utils@npm:3.29.0\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.8\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/99ceabb63b98c0d7526ebb6ab6dbbf1cf06ea0ed5e4f2794337aaa3952c338b24a21023e762286f0054522b4a48925f1d96361433acf81245c329d6c657aecfe\n  languageName: node\n  linkType: hard\n\n\"@react-aria/utils@npm:^3.30.0, @react-aria/utils@npm:^3.30.1\":\n  version: 3.30.1\n  resolution: \"@react-aria/utils@npm:3.30.1\"\n  dependencies:\n    \"@react-aria/ssr\": \"npm:^3.9.10\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    clsx: \"npm:^2.0.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3417a3ea7250c4ad23e6943117eb304a3708859fe8c738e0bee39edaefe7a7b82cedecc564f1a7f7fdf715ad13f57804a0b7c015a75fefdecbe3ecd7162f3e2f\n  languageName: node\n  linkType: hard\n\n\"@react-aria/visually-hidden@npm:3.8.22\":\n  version: 3.8.22\n  resolution: \"@react-aria/visually-hidden@npm:3.8.22\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.0\"\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/fbf527b526250865731737d3d421d796ab0a2a2bb0dbd2532a506693fe8902fa2a53b0afbb7ee4d295028621bdcbe8e50583d3d6d0f87d5ae014eb97953e0a2a\n  languageName: node\n  linkType: hard\n\n\"@react-aria/visually-hidden@npm:3.8.26\":\n  version: 3.8.26\n  resolution: \"@react-aria/visually-hidden@npm:3.8.26\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.4\"\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/22f16eb10fba76c0cae30040e9eee9cd7529ca0f68c5adf07d69afcd4db630e3f5c89f64002d7b62ecccd0ad2f78b368a295224c210aa9fae295e897bede745b\n  languageName: node\n  linkType: hard\n\n\"@react-aria/visually-hidden@npm:^3.8.22, @react-aria/visually-hidden@npm:^3.8.23\":\n  version: 3.8.23\n  resolution: \"@react-aria/visually-hidden@npm:3.8.23\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.1\"\n    \"@react-aria/utils\": \"npm:^3.29.0\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2a62131005e98fe24ec13bc356c82c3097f1f15e249869d7c8cea8364bfd543988d61bd65b6d5e7f8b1ec8ddec6d62f3f81af1f2e184fe0cd269655a141da0c5\n  languageName: node\n  linkType: hard\n\n\"@react-aria/visually-hidden@npm:^3.8.26, @react-aria/visually-hidden@npm:^3.8.27\":\n  version: 3.8.27\n  resolution: \"@react-aria/visually-hidden@npm:3.8.27\"\n  dependencies:\n    \"@react-aria/interactions\": \"npm:^3.25.5\"\n    \"@react-aria/utils\": \"npm:^3.30.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b9c1e64c9560ec6ff5e186502cc4c89f366d17d8ccd0487c698b22358b0583385f404c567861497cb4c0b035b3906993f700fc219040519b0ce9be1f69d74b24\n  languageName: node\n  linkType: hard\n\n\"@react-stately/calendar@npm:3.8.0\":\n  version: 3.8.0\n  resolution: \"@react-stately/calendar@npm:3.8.0\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/calendar\": \"npm:^3.7.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/5b7dc76aa4d1d97f2f551d8db97caa8feb8ac8f4731c33b30f06e2aa3792708a7786c511dd6475eb79345c110f9c013ca037407a46f4dddec35411ec16edb189\n  languageName: node\n  linkType: hard\n\n\"@react-stately/calendar@npm:^3.8.0\":\n  version: 3.8.1\n  resolution: \"@react-stately/calendar@npm:3.8.1\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/calendar\": \"npm:^3.7.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/dc567ee0ed6b7d7a3baaf26afeff01836c8be5cf1390900aea5e52b33c80eefbe85099883c89f834aaf110832649bf164afd175e19fc7e02a4b40410bd7f8865\n  languageName: node\n  linkType: hard\n\n\"@react-stately/checkbox@npm:3.6.13\":\n  version: 3.6.13\n  resolution: \"@react-stately/checkbox@npm:3.6.13\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/checkbox\": \"npm:^3.9.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bbeca294e9f30fbbeedb01ed7a7c758d778a71173c4e594b3312a1ef55ca4dd8d67c1397f426f3fc5393a063d5245b813a777f1a32cd3a9eaac1c9c41f2299de\n  languageName: node\n  linkType: hard\n\n\"@react-stately/checkbox@npm:3.7.0\":\n  version: 3.7.0\n  resolution: \"@react-stately/checkbox@npm:3.7.0\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.2.0\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/checkbox\": \"npm:^3.10.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1d8646ecdd1bd527bec60075f46bf12ed8abb259eca653d1d95dd72f967659e06d5d536aedcf87554630d912e1b819ad66a61761b9ef8e284f63dd397a8e90ff\n  languageName: node\n  linkType: hard\n\n\"@react-stately/checkbox@npm:^3.6.13\":\n  version: 3.6.14\n  resolution: \"@react-stately/checkbox@npm:3.6.14\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/checkbox\": \"npm:^3.9.4\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/cceeb49bfbeca7b9f9a379665c847018b7ae9aaed7a9966ff54448a9393a27164e56d1b5fad960cb7e7cea2e60bf86bf4613cda1750c3682b68952cf27db6376\n  languageName: node\n  linkType: hard\n\n\"@react-stately/checkbox@npm:^3.7.0\":\n  version: 3.7.1\n  resolution: \"@react-stately/checkbox@npm:3.7.1\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.2.1\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/checkbox\": \"npm:^3.10.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/283d7e5aa63761c956fc48c42d12e5dee779c362013afabd36e086f530b4b8137966e6769421951c28cbffa4e793c0ce857de5aea85403a42688f0898f2503fa\n  languageName: node\n  linkType: hard\n\n\"@react-stately/collections@npm:3.12.3\":\n  version: 3.12.3\n  resolution: \"@react-stately/collections@npm:3.12.3\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bdcf755aeb114b936057e9bb75de8a6765b5b35a8d330f5fce7151b3b2c052662cd2f630cbfce282e477435e47c64b31c361f0188f2dbe378b011b356e8e2a8c\n  languageName: node\n  linkType: hard\n\n\"@react-stately/collections@npm:3.12.6\":\n  version: 3.12.6\n  resolution: \"@react-stately/collections@npm:3.12.6\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4d200565c88ef179c15653eaaa2079b5e96fd8ed3ab34b863e7a4a7c421cf3f90f4348db37cfd5842bbdb17df165992c07a6013809aa550459819e057ca208c4\n  languageName: node\n  linkType: hard\n\n\"@react-stately/collections@npm:^3.12.3, @react-stately/collections@npm:^3.12.4\":\n  version: 3.12.4\n  resolution: \"@react-stately/collections@npm:3.12.4\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/c9c6067cd83531322068263dcf56e9c0aac4254d20c4e647a5a3e853c67521216cdd53c1405e3f15bae297ffff193a40dba4b5844ad195acb000bd148b7e2804\n  languageName: node\n  linkType: hard\n\n\"@react-stately/collections@npm:^3.12.6, @react-stately/collections@npm:^3.12.7\":\n  version: 3.12.7\n  resolution: \"@react-stately/collections@npm:3.12.7\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/9f8e2f34a7e8a9630699ca91d8d5f215468b2a669df4e06bfd337d365d52df9b2e42b983d18f2023b746e30f0b06ee76e5838e1067299935ce78fab1c2c959c1\n  languageName: node\n  linkType: hard\n\n\"@react-stately/combobox@npm:3.10.4\":\n  version: 3.10.4\n  resolution: \"@react-stately/combobox@npm:3.10.4\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/list\": \"npm:^3.12.1\"\n    \"@react-stately/overlays\": \"npm:^3.6.15\"\n    \"@react-stately/select\": \"npm:^3.6.12\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/combobox\": \"npm:^3.13.4\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/373de12e2d0ea48fc358ca713156990aa5b7dde147f4d5db90f47befbc985475f2c8ac666aaef2843bb0346768f08e0795d8239c1b396de929f16e96dede898a\n  languageName: node\n  linkType: hard\n\n\"@react-stately/combobox@npm:^3.10.4\":\n  version: 3.10.5\n  resolution: \"@react-stately/combobox@npm:3.10.5\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/list\": \"npm:^3.12.2\"\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-stately/select\": \"npm:^3.6.13\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/combobox\": \"npm:^3.13.5\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/dc7d4ccef8a7e5d12a571793a7e7358c40e071a40dd4f647bde50bfe26acf9b04bf7eba9919a993d3f4a6a255e8d7a5e7c7617206613dae56fb34b1e505d140a\n  languageName: node\n  linkType: hard\n\n\"@react-stately/datepicker@npm:3.14.0\":\n  version: 3.14.0\n  resolution: \"@react-stately/datepicker@npm:3.14.0\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@internationalized/string\": \"npm:^3.2.6\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/overlays\": \"npm:^3.6.15\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/datepicker\": \"npm:^3.12.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2ed4689a48ab535e468bbfcd9bdde9f12791e80899df993751fcbc08e5e6e3b44ea04481047f7a7e2a694f4ea40207404cae91ac723ddfb425076e87cbb2468f\n  languageName: node\n  linkType: hard\n\n\"@react-stately/datepicker@npm:^3.14.0\":\n  version: 3.14.1\n  resolution: \"@react-stately/datepicker@npm:3.14.1\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.1\"\n    \"@internationalized/string\": \"npm:^3.2.6\"\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/datepicker\": \"npm:^3.12.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8c711546e05ddb03678f60eac64ed7192f67a477ff04a0a32e85e0f81ea765e3767c110a00f94cb79526c2f15738cd94bfb89459715cc0e7c206d4fbab4e0348\n  languageName: node\n  linkType: hard\n\n\"@react-stately/flags@npm:^3.1.1\":\n  version: 3.1.1\n  resolution: \"@react-stately/flags@npm:3.1.1\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/3f64deb7b5daa787072f77e358624b65ad0627ee148d968423f3a5968b655d99671673927e21e4ba2ad0c9828d6ea75dae8ca421af90f9b41986a28341a4101f\n  languageName: node\n  linkType: hard\n\n\"@react-stately/flags@npm:^3.1.2\":\n  version: 3.1.2\n  resolution: \"@react-stately/flags@npm:3.1.2\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  checksum: 10c0/d86890ce662f04c7d8984e9560527f46c9779b97757abded9e1bf7e230a6900a0ea7a3e7c22534de8d2ff278abae194e4e4ad962d710f3b04c52a4e1011c2e5b\n  languageName: node\n  linkType: hard\n\n\"@react-stately/form@npm:3.1.3\":\n  version: 3.1.3\n  resolution: \"@react-stately/form@npm:3.1.3\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4b27d5c824abefd832152abe0355af3fcb74e61855ad204e20e82503f14b6ca0c0353b0f6372634fc53dd27c94a16448d8b69d9320ceb64b0fb406513dca0c00\n  languageName: node\n  linkType: hard\n\n\"@react-stately/form@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"@react-stately/form@npm:3.2.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/da9c2988540e6d97f203d554e7cd6d306a91a76b12ec9a3d18bb8f28afe90d3dafb5b6bab8bef719b4a6993dd9cbf1054c15c6cf4eb45576fc2694cb558aa7ce\n  languageName: node\n  linkType: hard\n\n\"@react-stately/form@npm:^3.1.3, @react-stately/form@npm:^3.1.4\":\n  version: 3.1.4\n  resolution: \"@react-stately/form@npm:3.1.4\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b49513626112b44e4824108e5b0ed5b9953395cf2801378cd4c04624feeb69c25a50f51617639fe0ba574178f30a893386bbda7f7efba3469e890f193781022e\n  languageName: node\n  linkType: hard\n\n\"@react-stately/form@npm:^3.2.0, @react-stately/form@npm:^3.2.1\":\n  version: 3.2.1\n  resolution: \"@react-stately/form@npm:3.2.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/9aa4c38001ea7811fc65677f04ffdaecf03be75bd9da911754d2510ef30be1b83fc45ef023660727bfdaf2f24dcebaa5587ca1ca4f5e1bc7aeb2319b3768c2c2\n  languageName: node\n  linkType: hard\n\n\"@react-stately/grid@npm:^3.11.1, @react-stately/grid@npm:^3.11.2\":\n  version: 3.11.2\n  resolution: \"@react-stately/grid@npm:3.11.2\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-types/grid\": \"npm:^3.3.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/362f346b69fdfc9d52924b45a641f3d9a2691dd072040a4dee42f97e89639fddc46a8bb7c386235e8626b69379a80bb9ce415c3e1069c03809e89b2cc2a0c977\n  languageName: node\n  linkType: hard\n\n\"@react-stately/grid@npm:^3.11.4, @react-stately/grid@npm:^3.11.5\":\n  version: 3.11.5\n  resolution: \"@react-stately/grid@npm:3.11.5\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-types/grid\": \"npm:^3.3.5\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bacfde659d10815a435cf0c8333a15da9ff1629483fa32c2263ebb1975ee1b8de21e1768f136c0dc6db8e7e60fac6d7ae72f610915d1b147716d47022a1f35c9\n  languageName: node\n  linkType: hard\n\n\"@react-stately/list@npm:3.12.1\":\n  version: 3.12.1\n  resolution: \"@react-stately/list@npm:3.12.1\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/selection\": \"npm:^3.20.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e5de895d5a722e4f6d6d0be439a70bf60bef7b320bd66c08b6e60d8f9512521dfa68de7bd08be510409496111b3c23789ec5aafa1c06a1c261b6e24b4e5d4b0f\n  languageName: node\n  linkType: hard\n\n\"@react-stately/list@npm:3.12.4\":\n  version: 3.12.4\n  resolution: \"@react-stately/list@npm:3.12.4\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.6\"\n    \"@react-stately/selection\": \"npm:^3.20.4\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/46e09c1c75188502b9a47571584ec5000f31bfc4a82dca63e8bd97d909f080dd41bd39dc2aada181c17acf9d6b110af41c5a36db76a6a7ceb078f46343d4a21a\n  languageName: node\n  linkType: hard\n\n\"@react-stately/list@npm:^3.12.1, @react-stately/list@npm:^3.12.2\":\n  version: 3.12.2\n  resolution: \"@react-stately/list@npm:3.12.2\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f5d1e7b15727f71072d3ee53eb9fc1fea1b3f3f3cca94207868247e058c199b02099665a710a6b5909b7e6a0c488b0cd03f10f3405ea16c707ca78598ced44a4\n  languageName: node\n  linkType: hard\n\n\"@react-stately/list@npm:^3.12.4\":\n  version: 3.13.0\n  resolution: \"@react-stately/list@npm:3.13.0\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d408513e6b984ce912bb744b4da04222c0fa1a57e11fe53976c42df6d7126d3945fc65caaf8d67587ccaf2dce147658de432ddaa80e5b2b0b49012f7b572f810\n  languageName: node\n  linkType: hard\n\n\"@react-stately/menu@npm:3.9.3\":\n  version: 3.9.3\n  resolution: \"@react-stately/menu@npm:3.9.3\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.15\"\n    \"@react-types/menu\": \"npm:^3.10.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8b8977cb82b6e7505b10961a4e2eafa6dc6773e0ae58d86d43c386010877f08770433e1345f8a92a75e488db5b9e0f90039cf10246a1a0e62a82b34eb02ce773\n  languageName: node\n  linkType: hard\n\n\"@react-stately/menu@npm:3.9.6\":\n  version: 3.9.6\n  resolution: \"@react-stately/menu@npm:3.9.6\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.18\"\n    \"@react-types/menu\": \"npm:^3.10.3\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/5e78cfb9ff33f3dbfff69bc3f8991fd68493a8480c6b42c3f1b9988f78d49fd379cfcfc3d3e2549e7fb08d4fbf7c8bc6d5dcefe08a64d5062ffdb2de233bb86e\n  languageName: node\n  linkType: hard\n\n\"@react-stately/menu@npm:^3.9.3, @react-stately/menu@npm:^3.9.4\":\n  version: 3.9.4\n  resolution: \"@react-stately/menu@npm:3.9.4\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-types/menu\": \"npm:^3.10.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/93487af61be461bcfb590cf0e8e9fc8096336ac0f8d58af858a379db685908f71b052b238470193be4bce94b1febdbbdb8009f80744f168a1672d7b8fb433dd5\n  languageName: node\n  linkType: hard\n\n\"@react-stately/menu@npm:^3.9.6\":\n  version: 3.9.7\n  resolution: \"@react-stately/menu@npm:3.9.7\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.19\"\n    \"@react-types/menu\": \"npm:^3.10.4\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4ad5b7da2f6c09efcb459f77bab624be65d37ba6b72cf76c704e28361f9ee6f598365728f351aa15dc27bdb2dca8e1c634e0cf131f036fc5aafd308a2d0c111f\n  languageName: node\n  linkType: hard\n\n\"@react-stately/numberfield@npm:3.9.11\":\n  version: 3.9.11\n  resolution: \"@react-stately/numberfield@npm:3.9.11\"\n  dependencies:\n    \"@internationalized/number\": \"npm:^3.6.1\"\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/numberfield\": \"npm:^3.8.10\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2cf8d39b3a4b87b17c7f98555c2d5ee770876db670edd89f727e85a02e12175c80bac97442ff124427737d7dd42b9d6f38d26b17d0be64ff806fb2a1509b7968\n  languageName: node\n  linkType: hard\n\n\"@react-stately/numberfield@npm:^3.9.11\":\n  version: 3.9.12\n  resolution: \"@react-stately/numberfield@npm:3.9.12\"\n  dependencies:\n    \"@internationalized/number\": \"npm:^3.6.2\"\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/numberfield\": \"npm:^3.8.11\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d4ed68ce1c8bbb182a5ac804ec8de1e516d6f977b93f362ef49993acee80ebd7fa54f1efb355a1f403367f14395e8dba3c776871ac769bd4724948e8576c37ec\n  languageName: node\n  linkType: hard\n\n\"@react-stately/overlays@npm:3.6.15\":\n  version: 3.6.15\n  resolution: \"@react-stately/overlays@npm:3.6.15\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/overlays\": \"npm:^3.8.14\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4e75602378869027bb752500c771a732e5c9d7963f8101eb03941b350e6b6a74c0da20ab75de9daa28e3fa10f7230952636957caf16953c8b70fa8eb836a4657\n  languageName: node\n  linkType: hard\n\n\"@react-stately/overlays@npm:3.6.18\":\n  version: 3.6.18\n  resolution: \"@react-stately/overlays@npm:3.6.18\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/overlays\": \"npm:^3.9.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3dff0037459f6e4d5dd9b2bd754c02985a9aa9c9b0ebd16451ea607f9c0b1a194e51fbdbac4d3b35c3350a425515d39e5b84cbe650102fee82afb00516b27ad0\n  languageName: node\n  linkType: hard\n\n\"@react-stately/overlays@npm:^3.6.15, @react-stately/overlays@npm:^3.6.16\":\n  version: 3.6.16\n  resolution: \"@react-stately/overlays@npm:3.6.16\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/50201f009045ad61d6138b20ff7fa60f5ec513810038035b55d62d17ae4db20a9a49a25aa5ae5c31fb5c7e669fbcaed4e6f7939f0422c6a464a0442552bddbbb\n  languageName: node\n  linkType: hard\n\n\"@react-stately/overlays@npm:^3.6.18, @react-stately/overlays@npm:^3.6.19\":\n  version: 3.6.19\n  resolution: \"@react-stately/overlays@npm:3.6.19\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/overlays\": \"npm:^3.9.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bc6749850313185a927f3d2f72e8e155d8452a4ec9f19ff3df7c167c6a60a29d91dd97e0b5d5f78ed8fa1a0b275cbfc4f5b135dbd37412246e0cc647499d4cde\n  languageName: node\n  linkType: hard\n\n\"@react-stately/radio@npm:3.10.12\":\n  version: 3.10.12\n  resolution: \"@react-stately/radio@npm:3.10.12\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.1.3\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/radio\": \"npm:^3.8.8\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/7f5fd316b88eac3149c2f46c6c4d48022dabe5e871dd15042084fa94cfbe85873d7d0be7562447e9797c838b3a72ce1fe7842e983c682b80d58596e40c78f136\n  languageName: node\n  linkType: hard\n\n\"@react-stately/radio@npm:^3.10.12\":\n  version: 3.10.13\n  resolution: \"@react-stately/radio@npm:3.10.13\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/radio\": \"npm:^3.8.9\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1fa710c5ab053b5e9c42268b04c8f0d5723c17ad2bce59cb44c54f27c775df27bfd5b8f1a04fb71739f252ea74607ccbeb8de14b86d9d9ffe2815dc1ffbec24a\n  languageName: node\n  linkType: hard\n\n\"@react-stately/select@npm:^3.6.12, @react-stately/select@npm:^3.6.13\":\n  version: 3.6.13\n  resolution: \"@react-stately/select@npm:3.6.13\"\n  dependencies:\n    \"@react-stately/form\": \"npm:^3.1.4\"\n    \"@react-stately/list\": \"npm:^3.12.2\"\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-types/select\": \"npm:^3.9.12\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/164eb2e576e5563de9b5b23a5965e0c983e497b8fce734958c0d5371d930dd20c913a7417e3f3dfce9f4e7c5ad5f3e2219708a0514cd69dd08c702b843702285\n  languageName: node\n  linkType: hard\n\n\"@react-stately/selection@npm:^3.20.1, @react-stately/selection@npm:^3.20.2\":\n  version: 3.20.2\n  resolution: \"@react-stately/selection@npm:3.20.2\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a9b520f8637e55ec29f0b2388d94f7d72787b05322d155f3964266a8ec66e06e158d0ecfdde6668ede27de8d113f1e67b91cd196fbe0dee0ab0652801e9ccfe4\n  languageName: node\n  linkType: hard\n\n\"@react-stately/selection@npm:^3.20.4, @react-stately/selection@npm:^3.20.5\":\n  version: 3.20.5\n  resolution: \"@react-stately/selection@npm:3.20.5\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/fa3e9440c10d836e48e019ce8811eab2bc38c15e807fec0d1f857ec30f180fa87005f882385259c48fa73d9793c292f3322c35b94df06535fe19eb7b0e715c76\n  languageName: node\n  linkType: hard\n\n\"@react-stately/slider@npm:3.6.3\":\n  version: 3.6.3\n  resolution: \"@react-stately/slider@npm:3.6.3\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/slider\": \"npm:^3.7.10\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1c61ac4bb660f137d858e2f6f55ebb360643fd2e5a9c61c4dd6964e7cecf7eafc010bc2269297f6d949df212b3283494878b75ce42769e9d2a633f0d4aa9cbc5\n  languageName: node\n  linkType: hard\n\n\"@react-stately/slider@npm:^3.6.3\":\n  version: 3.6.4\n  resolution: \"@react-stately/slider@npm:3.6.4\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@react-types/slider\": \"npm:^3.7.11\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/878490d9e964876e8675025c2d95c7b65ec6b9a61c1756aab03efd5895fbc2d114edc8c122c727fcbf9f6f5c47f8a14e91d0d3b9832fb9add37546ebc7bc4e59\n  languageName: node\n  linkType: hard\n\n\"@react-stately/table@npm:3.14.1\":\n  version: 3.14.1\n  resolution: \"@react-stately/table@npm:3.14.1\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-stately/grid\": \"npm:^3.11.1\"\n    \"@react-stately/selection\": \"npm:^3.20.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/grid\": \"npm:^3.3.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/table\": \"npm:^3.12.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/65b4c847ebc6d67d5c3a7e7d449fb550319fde6276454c9ead00dfee4924d6bd6b3662c927a7ef2dce6a9a2465036c2d5ddb20664a5b2bdc77178fd25cf2c5cc\n  languageName: node\n  linkType: hard\n\n\"@react-stately/table@npm:3.14.4\":\n  version: 3.14.4\n  resolution: \"@react-stately/table@npm:3.14.4\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.6\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-stately/grid\": \"npm:^3.11.4\"\n    \"@react-stately/selection\": \"npm:^3.20.4\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/grid\": \"npm:^3.3.4\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@react-types/table\": \"npm:^3.13.2\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/85c4a8745f6de9dc13540ed7d61ad8ab812a386ebe5f6684f014ef8b78ab2e446dd4fe8154f224b9621e291a9844ed76bf79d13cc6cff2712ab2458c7dfea9d8\n  languageName: node\n  linkType: hard\n\n\"@react-stately/table@npm:^3.14.1\":\n  version: 3.14.2\n  resolution: \"@react-stately/table@npm:3.14.2\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/flags\": \"npm:^3.1.1\"\n    \"@react-stately/grid\": \"npm:^3.11.2\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/grid\": \"npm:^3.3.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@react-types/table\": \"npm:^3.13.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d9548b85786e59d2260ee5f7eedcaabe725876664bf79d214758f3329c1b46b7929ca82f322003d169503c517be7a94dcbce7dfcb14205610ff2cfe7d79215db\n  languageName: node\n  linkType: hard\n\n\"@react-stately/table@npm:^3.14.4\":\n  version: 3.15.0\n  resolution: \"@react-stately/table@npm:3.15.0\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/flags\": \"npm:^3.1.2\"\n    \"@react-stately/grid\": \"npm:^3.11.5\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/grid\": \"npm:^3.3.5\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@react-types/table\": \"npm:^3.13.3\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/93813ef88a756755fdbb0a92f65d43b7cf83d2029290c34a2e0b337f1e2f25e9ebb7d54b122c4f280dc797ea82550bd0cc105072b7cdec836d5d48d175ea220e\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tabs@npm:3.8.1\":\n  version: 3.8.1\n  resolution: \"@react-stately/tabs@npm:3.8.1\"\n  dependencies:\n    \"@react-stately/list\": \"npm:^3.12.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@react-types/tabs\": \"npm:^3.3.14\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/33af583704e7e63f9b7fac7f67beb42feb6f8ccd545b577eb409a7d3b438bae91573f1bfcccccc78544a0eb63e34c5673ac604c8c27cf66fd659ae49bb0f9f48\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tabs@npm:^3.8.1\":\n  version: 3.8.2\n  resolution: \"@react-stately/tabs@npm:3.8.2\"\n  dependencies:\n    \"@react-stately/list\": \"npm:^3.12.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@react-types/tabs\": \"npm:^3.3.15\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/12feabc89a8a3e95822ca6f2bc9ab2d411afa192147fc27d9e725f2456d3ab49d04e5d396f240d89d96148084ad133f9d1863b0250688d6a473fcbdef3d7284c\n  languageName: node\n  linkType: hard\n\n\"@react-stately/toast@npm:3.1.0, @react-stately/toast@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"@react-stately/toast@npm:3.1.0\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n    use-sync-external-store: \"npm:^1.4.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1ccbabc31aefb18f6ef47f050c7074f187562aef48559f700244be33f5ad78a4edd9a2bb46db7bb727a87edab199e30f6a42d0fd3fe63e96bdca1040a969bd2a\n  languageName: node\n  linkType: hard\n\n\"@react-stately/toggle@npm:3.8.3\":\n  version: 3.8.3\n  resolution: \"@react-stately/toggle@npm:3.8.3\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/checkbox\": \"npm:^3.9.3\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ade9e87e20ca91980f639e782013ab420c8a6715330858db36132e443f9235f777d3f12e28c5d4da6337f0bcc21e5635a6855be959bfe4a10d04028e20a1b461\n  languageName: node\n  linkType: hard\n\n\"@react-stately/toggle@npm:3.9.0\":\n  version: 3.9.0\n  resolution: \"@react-stately/toggle@npm:3.9.0\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/checkbox\": \"npm:^3.10.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/eab0003f9709d8140aa8fa7a0283b186de7fa45511739d163fc23398517d4b094679545a136f940f7ba16344481f84f5829a03939075b7400fc428c7534b9473\n  languageName: node\n  linkType: hard\n\n\"@react-stately/toggle@npm:^3.8.3, @react-stately/toggle@npm:^3.8.4\":\n  version: 3.8.4\n  resolution: \"@react-stately/toggle@npm:3.8.4\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/checkbox\": \"npm:^3.9.4\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4e71b46a5fae68f55a7344703f910d3dd548c8fcc3207b3b6bfe1bde8ad8bd732e02bf63acbeecafc7dcf8a22a5d0ad20d3a1ee7b6b22893b17e871620f467e7\n  languageName: node\n  linkType: hard\n\n\"@react-stately/toggle@npm:^3.9.0, @react-stately/toggle@npm:^3.9.1\":\n  version: 3.9.1\n  resolution: \"@react-stately/toggle@npm:3.9.1\"\n  dependencies:\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/checkbox\": \"npm:^3.10.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d7a87f9b00f324cfd2cab13733ceebaf66df9514024bfa85f7f8bef27ac0037b0568f763e96a4a9b46798fbd90048d8afffc0a6ad38803e121a3251d13bf7113\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tooltip@npm:3.5.3\":\n  version: 3.5.3\n  resolution: \"@react-stately/tooltip@npm:3.5.3\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.15\"\n    \"@react-types/tooltip\": \"npm:^3.4.16\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/42c7d49320ec21a8d880302f6a012d29646df726ab4e3c1df208c16d36ad85c04d4c716eee4833923f1dddcfc43de70126931fe9df0215741bce1487750449ae\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tooltip@npm:^3.5.3\":\n  version: 3.5.4\n  resolution: \"@react-stately/tooltip@npm:3.5.4\"\n  dependencies:\n    \"@react-stately/overlays\": \"npm:^3.6.16\"\n    \"@react-types/tooltip\": \"npm:^3.4.17\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b24cb3e96dfb73edf9ce5f808c2c0669b5196ddba23d118c59f4b083c8d042e9eaa550b69f293efb2e472ac9b4d6da02f68c05e4e308ade7d4665c60674c0dad\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tree@npm:3.8.9\":\n  version: 3.8.9\n  resolution: \"@react-stately/tree@npm:3.8.9\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.3\"\n    \"@react-stately/selection\": \"npm:^3.20.1\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a886415525822978ebb1176cf2ff0871df3102f93a3038aa7ccb6d2363a584341bbddc6de245bf7c798db8f3b14c034e5e3d8080abfc6056eef75f1924de1509\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tree@npm:^3.8.10, @react-stately/tree@npm:^3.8.9\":\n  version: 3.8.10\n  resolution: \"@react-stately/tree@npm:3.8.10\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.4\"\n    \"@react-stately/selection\": \"npm:^3.20.2\"\n    \"@react-stately/utils\": \"npm:^3.10.6\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3871f7f51d533cfe64645417eb2ce07fab2ff9736c1b7c75be844401a79fbe998f9490dfcc9ec3d357ade3b6dc7f323433e6b9b9337c4a8dd9d95084555126f1\n  languageName: node\n  linkType: hard\n\n\"@react-stately/tree@npm:^3.9.1\":\n  version: 3.9.2\n  resolution: \"@react-stately/tree@npm:3.9.2\"\n  dependencies:\n    \"@react-stately/collections\": \"npm:^3.12.7\"\n    \"@react-stately/selection\": \"npm:^3.20.5\"\n    \"@react-stately/utils\": \"npm:^3.10.8\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e2c3eb2eec5c0fdfc18e7cf09c3a866f0ebc261bf3398df7b54fa41c8b233e68ba4366c043896a101ddb72d2786adc5bad00f85eb61d0ff60afec34665de096f\n  languageName: node\n  linkType: hard\n\n\"@react-stately/utils@npm:3.10.6, @react-stately/utils@npm:^3.10.6\":\n  version: 3.10.6\n  resolution: \"@react-stately/utils@npm:3.10.6\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/09403746285a3866765c04bed5f2505e0bdbb971bdeb2eedee31ebf5bad3d0c0c0cef9d0dd5852fc6c58f6b552cbc90364eb32403245dfc04dc22c5e2fbfbe32\n  languageName: node\n  linkType: hard\n\n\"@react-stately/utils@npm:^3.10.8\":\n  version: 3.10.8\n  resolution: \"@react-stately/utils@npm:3.10.8\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a97cc292986e3eeb2ceb1626671ce60e8342a3ff35ab92bcfcb94bd6b28729836cc592e3fe4df2fba603e5fdd26291be77b7f60441920298c282bb93f424feba\n  languageName: node\n  linkType: hard\n\n\"@react-stately/virtualizer@npm:4.3.2\":\n  version: 4.3.2\n  resolution: \"@react-stately/virtualizer@npm:4.3.2\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.28.2\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d6f8465dac5d6a816cadcb9f3d9a9d2b0ce18519ac0a1fd3fdb3983eaadbf98ac5c6f343df6bde1d340e4f8290993aeaf2c956d47fa9402130b41b84888d76cd\n  languageName: node\n  linkType: hard\n\n\"@react-stately/virtualizer@npm:4.4.2\":\n  version: 4.4.2\n  resolution: \"@react-stately/virtualizer@npm:4.4.2\"\n  dependencies:\n    \"@react-aria/utils\": \"npm:^3.30.0\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n    \"@swc/helpers\": \"npm:^0.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n    react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/898f309c0738699b2e547d0c6c12e9bbb0452b577e6a0ab80c6086ad9fcb05dc8521a92b6bcc05ab8bea74af5710cf7813e9116f0891d38b9090cedd0e2652a0\n  languageName: node\n  linkType: hard\n\n\"@react-types/accordion@npm:3.0.0-alpha.26\":\n  version: 3.0.0-alpha.26\n  resolution: \"@react-types/accordion@npm:3.0.0-alpha.26\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.27.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/fe3dda6c148a815ea9e82d1ba0796f03954467290816e5b815bb26c7a503c1c46c0db8d43836b8da221bf1bc92e068f230509a93c2b30b912c2a2510f1798732\n  languageName: node\n  linkType: hard\n\n\"@react-types/breadcrumbs@npm:3.7.12\":\n  version: 3.7.12\n  resolution: \"@react-types/breadcrumbs@npm:3.7.12\"\n  dependencies:\n    \"@react-types/link\": \"npm:^3.6.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/0517e4cf06dcbd0b132d4b245a81ff87182db74e5dd021f3838a9d1940381820d5508c03e878fe16fa2ab1c502eb2b302b37d79b9dae57afc43b20e6b9d179cf\n  languageName: node\n  linkType: hard\n\n\"@react-types/breadcrumbs@npm:^3.7.12\":\n  version: 3.7.13\n  resolution: \"@react-types/breadcrumbs@npm:3.7.13\"\n  dependencies:\n    \"@react-types/link\": \"npm:^3.6.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/215dc4f2ffe2177869c5d91affae90374952ff55411765d1f7419ce8f97eb2a00cff0be6db0628950f195315cdc1680be13a521082223d672cc43d62752a0c60\n  languageName: node\n  linkType: hard\n\n\"@react-types/button@npm:3.12.0\":\n  version: 3.12.0\n  resolution: \"@react-types/button@npm:3.12.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/6412c06f1a590581283d8cfe7661bbc9f1916b827ecb332fc7d65c918f9e52496fd11ce7859742532ab0d75b8746098e9fe04561efc700969d41e59179f15c30\n  languageName: node\n  linkType: hard\n\n\"@react-types/button@npm:3.13.0\":\n  version: 3.13.0\n  resolution: \"@react-types/button@npm:3.13.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e3f0110fb845783ee7b1280258d03b749575492d9ee12b0c18903e0d7919d42c76c6d843b3f78f36dcf13bca67f7b0110f52f8d7808c97cb8489453551092e30\n  languageName: node\n  linkType: hard\n\n\"@react-types/button@npm:^3.12.0, @react-types/button@npm:^3.12.1\":\n  version: 3.12.1\n  resolution: \"@react-types/button@npm:3.12.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/60db0784703cc24bfcffcc73784c98d74e5cd7de31cbd6e853edf7b2c261c1da44fc204f203c93f348585b9ebae7aaf38855ca45a856994a0540f9a29b8370ec\n  languageName: node\n  linkType: hard\n\n\"@react-types/button@npm:^3.13.0, @react-types/button@npm:^3.14.0\":\n  version: 3.14.0\n  resolution: \"@react-types/button@npm:3.14.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/33891e850e0cccb5326cbd866c9bc7312611e7476ca82a83fee601a516a07a04da1eef1e9dbbf34f56a2f7cfcd546306ae91c088d99cdc548b49b80267e3f623\n  languageName: node\n  linkType: hard\n\n\"@react-types/calendar@npm:3.7.0\":\n  version: 3.7.0\n  resolution: \"@react-types/calendar@npm:3.7.0\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3154bcc223bfcbd34b674cf3db059fb459434bc63c00edae98b433ef8348b30ff8d7f84ffe9f630e68c4ec69931e5a7608a8d318ccea895b65996cd855efb029\n  languageName: node\n  linkType: hard\n\n\"@react-types/calendar@npm:^3.7.0, @react-types/calendar@npm:^3.7.1\":\n  version: 3.7.1\n  resolution: \"@react-types/calendar@npm:3.7.1\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.1\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/17cbb9c731ade4d07d54d9a563050747dd62752bab32fe67459dfc20639b94caa042fb76fd84301c4a1751d3938c7aada8600aa2facf94f6d84ad0a130227edf\n  languageName: node\n  linkType: hard\n\n\"@react-types/checkbox@npm:3.10.0\":\n  version: 3.10.0\n  resolution: \"@react-types/checkbox@npm:3.10.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/7e7fa09b955a618f8b327c5859f9c7481552c40229bd0eed9596cc4825125eda45e4f59891c12c48a6615b53d6e7504a8c112ee2a3820b6511c8afa013387600\n  languageName: node\n  linkType: hard\n\n\"@react-types/checkbox@npm:3.9.3\":\n  version: 3.9.3\n  resolution: \"@react-types/checkbox@npm:3.9.3\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/5bda856c04146e4438be578d3fdea3d8ec151d21f1afc7c19926f8d667cd167fafdf64585e02a0bf065439a6318dc95a25ec5f70e0850f6f0c5eaf60ba84bf55\n  languageName: node\n  linkType: hard\n\n\"@react-types/checkbox@npm:^3.10.0, @react-types/checkbox@npm:^3.10.1\":\n  version: 3.10.1\n  resolution: \"@react-types/checkbox@npm:3.10.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/cb2e3d0f4a47c2f664cce06a0956825d80e30d8c30e4d80fd6d657822dbbdee0d46f49d93c1f31d4919bbe2d69b09556d8185b1e0d4ebd4f658fe431e6a6aa65\n  languageName: node\n  linkType: hard\n\n\"@react-types/checkbox@npm:^3.9.3, @react-types/checkbox@npm:^3.9.4\":\n  version: 3.9.4\n  resolution: \"@react-types/checkbox@npm:3.9.4\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/98a69de79694059d99c7ca2850ee62fa7a7add9c84abf29ad6c8b3101c156b39205436841701513c70679b557acddf39e05fd1a67df3dc5916052bcfbb1944f5\n  languageName: node\n  linkType: hard\n\n\"@react-types/combobox@npm:3.13.4\":\n  version: 3.13.4\n  resolution: \"@react-types/combobox@npm:3.13.4\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e51b63761ac3607d05708cb280a9a737c13cac03aac440ab220ea2fd2c05889a9c2d2bf2f8728f7df774febc8507c8986d709492685fac3fa0e582740c292b60\n  languageName: node\n  linkType: hard\n\n\"@react-types/combobox@npm:^3.13.4, @react-types/combobox@npm:^3.13.5\":\n  version: 3.13.5\n  resolution: \"@react-types/combobox@npm:3.13.5\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/00573ad1778522dfe68b729ead0cfc9cc4a8ead083a566dd7b5953c06a757194c4a1e42cc1a5d4b3094c87d3be651e3a5665bdad00c468cf9a90e02a064ec32f\n  languageName: node\n  linkType: hard\n\n\"@react-types/datepicker@npm:3.12.0\":\n  version: 3.12.0\n  resolution: \"@react-types/datepicker@npm:3.12.0\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.0\"\n    \"@react-types/calendar\": \"npm:^3.7.0\"\n    \"@react-types/overlays\": \"npm:^3.8.14\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d0f7beb51bb408fb6a01ddcfac18bc4da00df96b29dcb1dac1e10f5d209003b18c5a4a4992c4ff698e5158d34849638f2a3355ac9fddcab8003e7005aec9a228\n  languageName: node\n  linkType: hard\n\n\"@react-types/datepicker@npm:^3.12.0, @react-types/datepicker@npm:^3.12.1\":\n  version: 3.12.1\n  resolution: \"@react-types/datepicker@npm:3.12.1\"\n  dependencies:\n    \"@internationalized/date\": \"npm:^3.8.1\"\n    \"@react-types/calendar\": \"npm:^3.7.1\"\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f0553bbdf48cd6d0ad044e99b4e9170cf8d6a5e1b66191a432bbad8eb57c6caf8fc6eddd2d9a88c09289a85455f8ba86de1c4198f728d58c16935348cd30be35\n  languageName: node\n  linkType: hard\n\n\"@react-types/dialog@npm:^3.5.17\":\n  version: 3.5.18\n  resolution: \"@react-types/dialog@npm:3.5.18\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/d204beee78a8e333deef53f1ca31540fbe516a7eeaf8b7fc1d84214f89d54d075206392bae3b75730ce1b77ddc1a9cf6f659e2ea87633f78113dd643107daca3\n  languageName: node\n  linkType: hard\n\n\"@react-types/dialog@npm:^3.5.20\":\n  version: 3.5.21\n  resolution: \"@react-types/dialog@npm:3.5.21\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.9.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/129bbdca319ab5353361f861c973837b73f7ed21cc7a887acb1e528b781ccbf390292bf5c8ca48a425e8b5a14d59d45be708e40a5b5f3aca4404c816a14ad135\n  languageName: node\n  linkType: hard\n\n\"@react-types/form@npm:3.7.11\":\n  version: 3.7.11\n  resolution: \"@react-types/form@npm:3.7.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4837042c78fdb80c172f4a71d8fbf7e182e6d3b8d6ffde9025568eb8430761893f24a060ed287fc0adfb8d264be3092511f00d78a4e3342393914ec6ee9fc007\n  languageName: node\n  linkType: hard\n\n\"@react-types/form@npm:3.7.14\":\n  version: 3.7.14\n  resolution: \"@react-types/form@npm:3.7.14\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b2bf0802a21d7fc9e0b22b7925905a90d2394de0a7e94189855329138d423dbaeaba2f887b93db731982e1c00a8e021e56524f5827a17cfd7cd338803e7ec6a6\n  languageName: node\n  linkType: hard\n\n\"@react-types/grid@npm:3.3.1\":\n  version: 3.3.1\n  resolution: \"@react-types/grid@npm:3.3.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/15e1c6c6228ced1c3bd48c5b56d2e9426e34aa932564377d9ce957fa3f8d421570eb4782971473c8cb0a18745bb34ae17a3164976aa8bc710dfa578a3374234a\n  languageName: node\n  linkType: hard\n\n\"@react-types/grid@npm:3.3.4\":\n  version: 3.3.4\n  resolution: \"@react-types/grid@npm:3.3.4\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/c3203832fda6503eef159a5334b8029860bede0c298acf23689bc13a3373c1ffefce82a4a0f7e8f723a8b18ae664bf341ae51821f82bfa3e61fbc97306b52838\n  languageName: node\n  linkType: hard\n\n\"@react-types/grid@npm:^3.3.1, @react-types/grid@npm:^3.3.2\":\n  version: 3.3.2\n  resolution: \"@react-types/grid@npm:3.3.2\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/61127b1442d6a218b91780dafe7507766db566597eb109154d3ad3a1d6107e9580896fc81ec771abf5b724daa1caeba20fad61365ba825e678b3acea8286da52\n  languageName: node\n  linkType: hard\n\n\"@react-types/grid@npm:^3.3.4, @react-types/grid@npm:^3.3.5\":\n  version: 3.3.5\n  resolution: \"@react-types/grid@npm:3.3.5\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/4b49af54683ce73ed2ee9be2b6f7a03870ee461bf41f1943f5d88fc4a4cedf62091e9a7937245db10acc0a1e4feedffe579be7e8746a7d71f6483553eed08e55\n  languageName: node\n  linkType: hard\n\n\"@react-types/link@npm:3.6.0\":\n  version: 3.6.0\n  resolution: \"@react-types/link@npm:3.6.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/88b8472b95c91bff78c8134f629b3299f8f0cca5e0ad3226dd601a843df877d131ae5fd4e2b4e5c95cd5cae005a0a6e8b1bbb56e53e2285048aee32c5cbe0e08\n  languageName: node\n  linkType: hard\n\n\"@react-types/link@npm:^3.6.0, @react-types/link@npm:^3.6.1\":\n  version: 3.6.1\n  resolution: \"@react-types/link@npm:3.6.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1f83e0d1f44b5560935577e5588e8a3d51fde9d9b63daeca168b3ab9ef44e52b6e147f8a17b2c65b446f29dd9db5d8fc31a397201e9971a3fc6ab75c3e2db710\n  languageName: node\n  linkType: hard\n\n\"@react-types/listbox@npm:^3.6.0, @react-types/listbox@npm:^3.7.0\":\n  version: 3.7.0\n  resolution: \"@react-types/listbox@npm:3.7.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/06fad7c64aa1655d820327d54822c9fe48af712309648748a9bbe53ffa3cd7f17dcfbb1248c05f0236bd6339bd227455e3b394f94d5fc12f645e823d1c387088\n  languageName: node\n  linkType: hard\n\n\"@react-types/listbox@npm:^3.7.2\":\n  version: 3.7.3\n  resolution: \"@react-types/listbox@npm:3.7.3\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/94fce2d390bfb9beafcc5a241ffe32524512240c7980fafee3195c859973ba84e1df2afc8a55e679d797c74f5d33fa18162fbaeeda983187423f7ce9bfd6d74a\n  languageName: node\n  linkType: hard\n\n\"@react-types/menu@npm:3.10.0\":\n  version: 3.10.0\n  resolution: \"@react-types/menu@npm:3.10.0\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.8.14\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2fe48d50240463a1f10e08e96fd72bfe86e38accfd98e3379f26c91f0832a3b533a071cdc330232cd892aff830b0690d26002cbf577b48081f63d49e8916b543\n  languageName: node\n  linkType: hard\n\n\"@react-types/menu@npm:^3.10.0, @react-types/menu@npm:^3.10.1\":\n  version: 3.10.1\n  resolution: \"@react-types/menu@npm:3.10.1\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e20915b049b5f1a3586b52a927d7b7d5e075f75e69f67b85180533367194a40885aaa4c3d664be05799a02b971b282eee1ac23fa101277d0db43cc36552e4c02\n  languageName: node\n  linkType: hard\n\n\"@react-types/menu@npm:^3.10.3, @react-types/menu@npm:^3.10.4\":\n  version: 3.10.4\n  resolution: \"@react-types/menu@npm:3.10.4\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.9.1\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/699da0cac2e31fdc362e8f5e227c2221187e4d883509ae242b1efd58ab28c55c2ee695c227ea04c3a4510354dc3348b409fa13a38b88a91544597cad63eb202b\n  languageName: node\n  linkType: hard\n\n\"@react-types/numberfield@npm:3.8.10\":\n  version: 3.8.10\n  resolution: \"@react-types/numberfield@npm:3.8.10\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/190360d5e8731d93436c0fa3661d531cfe354e52591c66a4a9cab2fc4007c82bc8eca33ae5e7bba3366977e3fdd081e57c516228f59aaf214ac3516801b69b70\n  languageName: node\n  linkType: hard\n\n\"@react-types/numberfield@npm:^3.8.10, @react-types/numberfield@npm:^3.8.11\":\n  version: 3.8.11\n  resolution: \"@react-types/numberfield@npm:3.8.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/3341952e1b5b9136409a2314015412bc4f7b4e6bcae284074a0ae0b015e59a0aed623520202b95e93be707c3ab1958ef21557cfaea88299744868baf31cb68a7\n  languageName: node\n  linkType: hard\n\n\"@react-types/overlays@npm:3.8.14\":\n  version: 3.8.14\n  resolution: \"@react-types/overlays@npm:3.8.14\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a30a90997d40139a99d85b5a2f5e07ead48163e909f25e0ca1ff4664ebf8bd3bb59cebd3124b0eeba2e45226d36a54265eae5946dc587ec168acae47ed8f8090\n  languageName: node\n  linkType: hard\n\n\"@react-types/overlays@npm:3.9.0\":\n  version: 3.9.0\n  resolution: \"@react-types/overlays@npm:3.9.0\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/daf8e878a96181a48b22f1ed3f48d769c72290c858becfbca6adf8da255531b4f79124d184c4fbd3534cfaa6a42edeb221f9b54cfa75132e131e781c3108a2c1\n  languageName: node\n  linkType: hard\n\n\"@react-types/overlays@npm:^3.8.14, @react-types/overlays@npm:^3.8.15\":\n  version: 3.8.15\n  resolution: \"@react-types/overlays@npm:3.8.15\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/80853c4795bf4879de3d86465f0c2606fcb1b56774a7af689862e7a4f0d0fde7b0375cbd4aa62a452adc699f3a2948c775de43d835be407db6140497d005ad8c\n  languageName: node\n  linkType: hard\n\n\"@react-types/overlays@npm:^3.9.0, @react-types/overlays@npm:^3.9.1\":\n  version: 3.9.1\n  resolution: \"@react-types/overlays@npm:3.9.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bf0e1c11251e2c6c79e12762d30e886ba5587cd7d38761d4174c3f512ace205cf7b3d7da44ca7fe3797af27ad32b844a6c4ecb3cf0a5c6b9784557cfaf035346\n  languageName: node\n  linkType: hard\n\n\"@react-types/progress@npm:3.5.11\":\n  version: 3.5.11\n  resolution: \"@react-types/progress@npm:3.5.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/1be57922b8a820b391e58057e6d8377c6b17a9beb8d52b638d1d57fdf54efceab614f26ad3cfb2d818069cfbaf1e6bb38bf1c27d94a002b53a870c9ca740bb25\n  languageName: node\n  linkType: hard\n\n\"@react-types/progress@npm:^3.5.11\":\n  version: 3.5.12\n  resolution: \"@react-types/progress@npm:3.5.12\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e913268969a95ac152f00de3477c429bf7a5a16f27fa8ee75e5e57bc512ac881c45e01ba6776f32093be1c15fdc71a46af315bdbfb4a8b1a73a20f1258f641ec\n  languageName: node\n  linkType: hard\n\n\"@react-types/radio@npm:3.8.8\":\n  version: 3.8.8\n  resolution: \"@react-types/radio@npm:3.8.8\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/04c10aebf1f1862065a93f97e3cfbfed1339f9e91be9b80a916deb885702b105ffb904ad36fb41a4a2ee618b1bc13c5463d93ef89cdaf9f624bfbf1136860dab\n  languageName: node\n  linkType: hard\n\n\"@react-types/radio@npm:^3.8.8, @react-types/radio@npm:^3.8.9\":\n  version: 3.8.9\n  resolution: \"@react-types/radio@npm:3.8.9\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/2a9ba6fc84f3e1441ed41c93b283206ae791e4d370403b2a3b7d7d1fc4867b1078122d7016ef163630f79760139c82c3d0dfca66ddf060a585df615c4822df2e\n  languageName: node\n  linkType: hard\n\n\"@react-types/select@npm:3.9.11\":\n  version: 3.9.11\n  resolution: \"@react-types/select@npm:3.9.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8d5eb28256e3530ee4b5d16687e4f9efb9d33ceded6448b8b7f3ea551151608b1e3668080fd97c4a822d779ff82f9f6f5463c3210c1a35d346098ddc9b3f3757\n  languageName: node\n  linkType: hard\n\n\"@react-types/select@npm:^3.9.12\":\n  version: 3.9.12\n  resolution: \"@react-types/select@npm:3.9.12\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/b4cf6b5e7f2e42e3dab4eb7b809cf05f0046cb4b838ffee6074cd7ef3a593c55caf78e926c9f97a80e73d8d0e7dcc96dcf8bb008ad8b0ec1b9350a97be1cde0d\n  languageName: node\n  linkType: hard\n\n\"@react-types/shared@npm:3.29.0\":\n  version: 3.29.0\n  resolution: \"@react-types/shared@npm:3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a629e4fe9ce9062de603a1e01ae90a999b07f1367143f3b66921c8a0c8e59d90a528263be74d930162ed4a78a725a253c48b6f3b00a85767549e86cac4cc8218\n  languageName: node\n  linkType: hard\n\n\"@react-types/shared@npm:3.31.0\":\n  version: 3.31.0\n  resolution: \"@react-types/shared@npm:3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/6944eba44a5bc390a0c4136f9bdcc8caee8408bba2d1b90160ae7397b9455efb3f28864a796c15e26132b522a60c389a7f0cf67674d64aec2947601962d3e4d6\n  languageName: node\n  linkType: hard\n\n\"@react-types/shared@npm:^3.27.0\":\n  version: 3.27.0\n  resolution: \"@react-types/shared@npm:3.27.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/7d710d776dcb6a832d3dc5ec281a81cd00a5b5aec1ecfcf7799c73bdb62c8739e574e29dafe04f7710a0ea568e4e11621091658a9c73d3191e1b0f5f3ff21f95\n  languageName: node\n  linkType: hard\n\n\"@react-types/shared@npm:^3.29.0, @react-types/shared@npm:^3.29.1\":\n  version: 3.29.1\n  resolution: \"@react-types/shared@npm:3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/7b124568475d8e067d3d71de2dc31d82479a92b9a9a7cd493802e2780b973787adfb65eb718d475950078c57087369edf156aed68e5029111a39c3b34bf4250b\n  languageName: node\n  linkType: hard\n\n\"@react-types/shared@npm:^3.31.0, @react-types/shared@npm:^3.32.0\":\n  version: 3.32.0\n  resolution: \"@react-types/shared@npm:3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/8484f310a8911ab01daa87f9bfdea0a9a76e152d13d8421c28560dc84d64a7df23cda956db59f7010d2e8eaea27d7644118bfbe60b603499903b5f7e6cdfe4fa\n  languageName: node\n  linkType: hard\n\n\"@react-types/slider@npm:^3.7.10, @react-types/slider@npm:^3.7.11\":\n  version: 3.7.11\n  resolution: \"@react-types/slider@npm:3.7.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/bb71e5707f9be3b650445e8998298795626b39e043a9757e2c7349b344e5ed5e3d78ec80c0f5d1092c51d9f5fde16244bcf2cd5ddd80d1c232a605e487aab8cc\n  languageName: node\n  linkType: hard\n\n\"@react-types/switch@npm:^3.5.10\":\n  version: 3.5.11\n  resolution: \"@react-types/switch@npm:3.5.11\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/57312d1044bd3888fe274f2a120ac036eb496c86c904dcf63a3c2dc424a256331cd64bf66b9240f7ec9a7ade7a2a6d258eb73ac900a7286668fab9eb73d613bf\n  languageName: node\n  linkType: hard\n\n\"@react-types/table@npm:3.12.0\":\n  version: 3.12.0\n  resolution: \"@react-types/table@npm:3.12.0\"\n  dependencies:\n    \"@react-types/grid\": \"npm:^3.3.1\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/e6c88d9f357dbc1b5cd8a420ec5bebeebfbdcd6cc02652bd62741efe03f46ee5ee562fc1c338c7451513ce86953a6f6e230c480a178cf1ca1a95d59f0f598cad\n  languageName: node\n  linkType: hard\n\n\"@react-types/table@npm:3.13.2\":\n  version: 3.13.2\n  resolution: \"@react-types/table@npm:3.13.2\"\n  dependencies:\n    \"@react-types/grid\": \"npm:^3.3.4\"\n    \"@react-types/shared\": \"npm:^3.31.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/42e37782d1d914472ee049dd697041f97bec5b6f9481c8c91775e2d7bae24ce1ca10d97e0413c133834241ddb7fdc0c9ae453fd06aa2f42c4dea76d14a27090f\n  languageName: node\n  linkType: hard\n\n\"@react-types/table@npm:^3.12.0, @react-types/table@npm:^3.13.0\":\n  version: 3.13.0\n  resolution: \"@react-types/table@npm:3.13.0\"\n  dependencies:\n    \"@react-types/grid\": \"npm:^3.3.2\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/104e71a62827134854be37dde26652d66c39de0786e03d08b769a18cedddb3f6e758ec3d63aa3731f3226cad562f28fe46c9982d3fe621bf4d82b5e707b4ea88\n  languageName: node\n  linkType: hard\n\n\"@react-types/table@npm:^3.13.2, @react-types/table@npm:^3.13.3\":\n  version: 3.13.3\n  resolution: \"@react-types/table@npm:3.13.3\"\n  dependencies:\n    \"@react-types/grid\": \"npm:^3.3.5\"\n    \"@react-types/shared\": \"npm:^3.32.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f1d40064f28441ae0387467f29ff01c641a8eb134b0e2d0dcb3b97331bdf56ac8d619e000bbb5a6229a31ddc288884913fcefb1e255f0c2f1c37f30575170b72\n  languageName: node\n  linkType: hard\n\n\"@react-types/tabs@npm:3.3.14\":\n  version: 3.3.14\n  resolution: \"@react-types/tabs@npm:3.3.14\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/f3871cfd02273bbe8729b3544fa31716b21395f9bf5096b177971c59a7642a31cee1e2756cf0eb046a86ef2d93b20c7e618bbcde53b0f169d7643e81e744d4a2\n  languageName: node\n  linkType: hard\n\n\"@react-types/tabs@npm:^3.3.14, @react-types/tabs@npm:^3.3.15\":\n  version: 3.3.15\n  resolution: \"@react-types/tabs@npm:3.3.15\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/16ba8b19e5389cf815cbcccf14bf7649e268c5b511cea5d5e2483eb49bb00f0db1c316de6af4be00b6c06957c3704c6817e8a79d04b5464b537319a862e3113a\n  languageName: node\n  linkType: hard\n\n\"@react-types/textfield@npm:3.12.1\":\n  version: 3.12.1\n  resolution: \"@react-types/textfield@npm:3.12.1\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/a639622457207c5909a140e93373e77bd1c934f940b54bb24aa2ba67dddcee7930491c57d0ed4847690fc55abf9e24aa0d04c2e7c7fc72a65f987a0d73e610b9\n  languageName: node\n  linkType: hard\n\n\"@react-types/textfield@npm:^3.12.1, @react-types/textfield@npm:^3.12.2\":\n  version: 3.12.2\n  resolution: \"@react-types/textfield@npm:3.12.2\"\n  dependencies:\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/edd5c17d84cb576ab0627d7a047a76a72b9ea09fe51e27685c5d12b2c38ccdc46cf044b12bc1948c18b067c8262db4a038bb6a84c7d4d41d69a6368dafb8f771\n  languageName: node\n  linkType: hard\n\n\"@react-types/tooltip@npm:3.4.16\":\n  version: 3.4.16\n  resolution: \"@react-types/tooltip@npm:3.4.16\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.8.14\"\n    \"@react-types/shared\": \"npm:^3.29.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/ef6cb4ec5a2cf50345c5460afa6c03164de37cd94b93033224a8cdfcca42f86c8f86e8d3ba684b783f61a1dd30fdd7103933d8597808e6ece36c6fb8fd27b062\n  languageName: node\n  linkType: hard\n\n\"@react-types/tooltip@npm:^3.4.16, @react-types/tooltip@npm:^3.4.17\":\n  version: 3.4.17\n  resolution: \"@react-types/tooltip@npm:3.4.17\"\n  dependencies:\n    \"@react-types/overlays\": \"npm:^3.8.15\"\n    \"@react-types/shared\": \"npm:^3.29.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1\n  checksum: 10c0/5e367e65ce212b6db9b60b652f6d35ce9e0730e696d941bf4a4a9276591a51033a6e5b11066f3364019ac0ee8245fc51c90162d3c262b20e0630a8c8b21c1540\n  languageName: node\n  linkType: hard\n\n\"@reduxjs/toolkit@npm:^1.7.2\":\n  version: 1.9.7\n  resolution: \"@reduxjs/toolkit@npm:1.9.7\"\n  dependencies:\n    immer: \"npm:^9.0.21\"\n    redux: \"npm:^4.2.1\"\n    redux-thunk: \"npm:^2.4.2\"\n    reselect: \"npm:^4.1.8\"\n  peerDependencies:\n    react: ^16.9.0 || ^17.0.0 || ^18\n    react-redux: ^7.2.1 || ^8.0.2\n  peerDependenciesMeta:\n    react:\n      optional: true\n    react-redux:\n      optional: true\n  checksum: 10c0/fa0aa4b7c6973ac87ce0ac7e45faa02c73b66c4ee0bc950d178494539a42a1bb908d109297102458b7ea14d5e7dae356e7a7ce9a1b9849b0e8451e6dd70fca9c\n  languageName: node\n  linkType: hard\n\n\"@salte-auth/popup@npm:1.0.0-rc.2\":\n  version: 1.0.0-rc.2\n  resolution: \"@salte-auth/popup@npm:1.0.0-rc.2\"\n  peerDependencies:\n    \"@salte-auth/salte-auth\": ^3.0.0-rc.5\n  checksum: 10c0/46e7e91527d5be92d3cdd5496109b9002f44f653c6077c3e32d5835e848dd5649d1ac8dc575ccea4e7c6a4545c8525aae1b5e0ee77e336bd0dc4d06111b0b292\n  languageName: node\n  linkType: hard\n\n\"@salte-auth/salte-auth@npm:3.0.0-rc.8\":\n  version: 3.0.0-rc.8\n  resolution: \"@salte-auth/salte-auth@npm:3.0.0-rc.8\"\n  checksum: 10c0/00fae36711be2a71e4ed8075b60b947ae536e942d029219773a9e00a875ec311d1179d9d3f0e86de56ed29c644d2b01b027b788c08bdca21f32e12f903024ba9\n  languageName: node\n  linkType: hard\n\n\"@sindresorhus/is@npm:^4.0.0\":\n  version: 4.6.0\n  resolution: \"@sindresorhus/is@npm:4.6.0\"\n  checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e\n  languageName: node\n  linkType: hard\n\n\"@swc/helpers@npm:^0.5.0\":\n  version: 0.5.15\n  resolution: \"@swc/helpers@npm:0.5.15\"\n  dependencies:\n    tslib: \"npm:^2.8.0\"\n  checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4\n  languageName: node\n  linkType: hard\n\n\"@swc/helpers@npm:^0.5.11\":\n  version: 0.5.12\n  resolution: \"@swc/helpers@npm:0.5.12\"\n  dependencies:\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/44693c0f34d772d63f3a6fb461964ec583055549a96df9790afec125b2ba06929a63cf9a165a9aaf22317779f460f8caafa94458b70d5cb2bc057b6ba9b5d02c\n  languageName: node\n  linkType: hard\n\n\"@szmarczak/http-timer@npm:^4.0.5\":\n  version: 4.0.6\n  resolution: \"@szmarczak/http-timer@npm:4.0.6\"\n  dependencies:\n    defer-to-connect: \"npm:^2.0.0\"\n  checksum: 10c0/73946918c025339db68b09abd91fa3001e87fc749c619d2e9c2003a663039d4c3cb89836c98a96598b3d47dec2481284ba85355392644911f5ecd2336536697f\n  languageName: node\n  linkType: hard\n\n\"@tanstack/react-virtual@npm:3.11.3\":\n  version: 3.11.3\n  resolution: \"@tanstack/react-virtual@npm:3.11.3\"\n  dependencies:\n    \"@tanstack/virtual-core\": \"npm:3.11.3\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  checksum: 10c0/9718379045ecda92d2c59f3c25699c703d98509ea569d7bfb0dbf78f1e0f46f0023d7a5d3a373fb7cdd4507286c1cf47648b5c0f4b1bdb85b2d2a6c26814b884\n  languageName: node\n  linkType: hard\n\n\"@tanstack/virtual-core@npm:3.11.3\":\n  version: 3.11.3\n  resolution: \"@tanstack/virtual-core@npm:3.11.3\"\n  checksum: 10c0/94701b8d2da9167c8b4ba36bdaff22019b8ebb19224c357c1af16cbc375b39076ecc021a8c9581001607afc921d0a843019cb27168999065dda511c445a1a335\n  languageName: node\n  linkType: hard\n\n\"@tippyjs/react@npm:^4.2.0\":\n  version: 4.2.6\n  resolution: \"@tippyjs/react@npm:4.2.6\"\n  dependencies:\n    tippy.js: \"npm:^6.3.1\"\n  peerDependencies:\n    react: \">=16.8\"\n    react-dom: \">=16.8\"\n  checksum: 10c0/b174f2fbd27c16c5a8554ee8b26f3cc61bc37507669a1cef3e3333bfb3db85c84a57a93003c972ede8007786cf0e813d489781aa9caf46fb3bf1b851e3f4daba\n  languageName: node\n  linkType: hard\n\n\"@turf/along@npm:>=6.3.0\":\n  version: 7.1.0\n  resolution: \"@turf/along@npm:7.1.0\"\n  dependencies:\n    \"@turf/bearing\": \"npm:^7.1.0\"\n    \"@turf/destination\": \"npm:^7.1.0\"\n    \"@turf/distance\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/c9dad54fe71359b77c7cb8f1717032df26178d99645cd76f762c4381bbf6b556970a96ef8b9d0ae7fd0b2d4a5f9afdff7bc54069f2096d827217eae4d8667b5b\n  languageName: node\n  linkType: hard\n\n\"@turf/area@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/area@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/f7444e75e04be1de6045b22fc723fb254367748ed1a0aa4b2357baed2ef3098a63ef8976b394a9cc03c228ebdc7013982da71f5bd3be4a2a65ef5898c772a135\n  languageName: node\n  linkType: hard\n\n\"@turf/bbox-polygon@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/bbox-polygon@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/8615161b4e922e92d2e3b8d7aaba291bc376f8f648417b83df7021d123b3d27dfa3fcb7dbe572bf581a8a856715051e160dd11f786ccce734d1e74941def51ca\n  languageName: node\n  linkType: hard\n\n\"@turf/bbox-polygon@npm:^6.0.1\":\n  version: 6.5.0\n  resolution: \"@turf/bbox-polygon@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n  checksum: 10c0/1953acb262e459b27f5d6ce71b4b6849c8b4ff04d4146570ee56a4ece43f397eb639b16fddf996f65ccf5bab6b0c197bc475a2e84f3585a4e447a8f3167a32e1\n  languageName: node\n  linkType: hard\n\n\"@turf/bbox@npm:>=4.0.0, @turf/bbox@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/bbox@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/901ed437ad1241b1c7cf76ee3f1dd998b32a59647074216d076a62080281693cc3f1d66d1dedd02fd5617ea57434ec059843bcc275d20f667019f3e1f378b05d\n  languageName: node\n  linkType: hard\n\n\"@turf/bbox@npm:^6.0.1, @turf/bbox@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/bbox@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n    \"@turf/meta\": \"npm:^6.5.0\"\n  checksum: 10c0/32c705ff0462f9f72fd4c78f013ebf3cbb30127c998770841d41540b246d3f3a73365a714ef335e45a70b9340317f402af76c36dbd64e9d9c2dfc65de71a9f84\n  languageName: node\n  linkType: hard\n\n\"@turf/bbox@npm:^7.2.0\":\n  version: 7.2.0\n  resolution: \"@turf/bbox@npm:7.2.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.2.0\"\n    \"@turf/meta\": \"npm:^7.2.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.8.1\"\n  checksum: 10c0/766d59d5f75c272481e971cd4004e139962607e8f34391b2abfb15bb34f9544a0479ceb14772565e005e4a12fdd82adf0d440ab1c9e0decbde6de50a5706db43\n  languageName: node\n  linkType: hard\n\n\"@turf/bearing@npm:>=4.0.0, @turf/bearing@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/bearing@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/2487615497fea6ba59260f98fb6631826f61cbbd6a0a6358204820fe3bbfde2832e38baad11077cd211e82319c074b86dd6d9e7ee08574fd9bc806f5acd26609\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-clockwise@npm:^5.1.5\":\n  version: 5.1.5\n  resolution: \"@turf/boolean-clockwise@npm:5.1.5\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^5.1.5\"\n    \"@turf/invariant\": \"npm:^5.1.5\"\n  checksum: 10c0/3aa66df49319e7b7fbbf02826d05c3c221b4d416d5e0fa21adc8d3e033d72055d3b1a7f4d319600b6f1d43c04c7e10f387fb490117884de8137c375b7fd2e543\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-clockwise@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/boolean-clockwise@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/92c2dd7025d35553f6b0759a321367029d74049d5375a4623c9fe372cd56886d6d1e43ba42d2f82121ed90de747f0253edf811198ee572c5c2379a2f4896d243\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-point-in-polygon@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/boolean-point-in-polygon@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    point-in-polygon-hao: \"npm:^1.1.0\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/ad6f66bfe52e15b011ddd074731df4ed2bdbcc14d66a2624f64d8ac0981882e7c39cb10f8c975e4d8bd3e83acae3284ad0abf28db15500fb3865f28d6fe8a8bf\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-point-in-polygon@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/boolean-point-in-polygon@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n    \"@turf/invariant\": \"npm:^6.5.0\"\n  checksum: 10c0/438d53d4056afba0e43c5159554d541fd90d5fcfb0d9a19d6dd4ca5121f75a8f72b1e2c3da8974c752d0729247f37f8b5147c1ffe89ba6b77249154fa108d00e\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-point-on-line@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/boolean-point-on-line@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n    \"@turf/invariant\": \"npm:^6.5.0\"\n  checksum: 10c0/80a9dfd47983e44fb57704e9bcb49cf66d698e93721550c09703345b14d7f1258f34ac2b07b3c3721e3388880ec6367f2a950df4de5b9e006b951d78ad145d79\n  languageName: node\n  linkType: hard\n\n\"@turf/boolean-within@npm:^6.0.1\":\n  version: 6.5.0\n  resolution: \"@turf/boolean-within@npm:6.5.0\"\n  dependencies:\n    \"@turf/bbox\": \"npm:^6.5.0\"\n    \"@turf/boolean-point-in-polygon\": \"npm:^6.5.0\"\n    \"@turf/boolean-point-on-line\": \"npm:^6.5.0\"\n    \"@turf/helpers\": \"npm:^6.5.0\"\n    \"@turf/invariant\": \"npm:^6.5.0\"\n  checksum: 10c0/0d9f9bfe6f492735215bbc00050b5d328b0ead797c7d7a300e503194a6a7f05feaf729fa35c0bef921e47efa6a63fdccefdd10665eb04e2edc863a03b7bb270b\n  languageName: node\n  linkType: hard\n\n\"@turf/buffer@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/buffer@npm:7.1.0\"\n  dependencies:\n    \"@turf/bbox\": \"npm:^7.1.0\"\n    \"@turf/center\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/jsts\": \"npm:^2.7.1\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@turf/projection\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    d3-geo: \"npm:1.7.1\"\n  checksum: 10c0/2b1a9f4501c1e2c29e56b20df22f63acc567ccd45230acd89a24989bd68178cd482ec06c1561f7f9ae6a749e8d2b3647dd6c97a499c1b406d0e06e1264608bf0\n  languageName: node\n  linkType: hard\n\n\"@turf/center@npm:>=4.0.0, @turf/center@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/center@npm:7.1.0\"\n  dependencies:\n    \"@turf/bbox\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/8b797f9a09fe8e21a6debf59f5d57b09494ac0d4d6c02bfa16425c84afbc8c222880ebe9d760d05e09b26733da2d9271214c165d71be4133331092d36bfc0bf0\n  languageName: node\n  linkType: hard\n\n\"@turf/center@npm:^6.0.1\":\n  version: 6.5.0\n  resolution: \"@turf/center@npm:6.5.0\"\n  dependencies:\n    \"@turf/bbox\": \"npm:^6.5.0\"\n    \"@turf/helpers\": \"npm:^6.5.0\"\n  checksum: 10c0/5997f022057f526792617491cdf2af11350cd4d03964bb582d7bf775b66ae2b5e5e668a8b9004f831b7c76a7b8004dcd70d66b58572d43a6de0a12468b6af5a2\n  languageName: node\n  linkType: hard\n\n\"@turf/centroid@npm:>=4.0.0, @turf/centroid@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/centroid@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/23b29a1d0d668220e975ab2fb8f0594ebd26c89a6399eef539d6d84b091611e8a09380f070957969a51a54f400b2303c23bf6a4a965909316a60f936d97fa0df\n  languageName: node\n  linkType: hard\n\n\"@turf/circle@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/circle@npm:7.1.0\"\n  dependencies:\n    \"@turf/destination\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/7cb9e3cf0a6c717a4fbcc9ded4b0c693fd7fe96026c6bdde55c69c7cd4ccc46f8df1417772c3ef9915438d63528e1c93335aad072591f8b7ffdf8754672ce4e5\n  languageName: node\n  linkType: hard\n\n\"@turf/clone@npm:^5.1.5\":\n  version: 5.1.5\n  resolution: \"@turf/clone@npm:5.1.5\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^5.1.5\"\n  checksum: 10c0/8bb5c9266a458e42fc6f8b13977fea5a1c46f7de20ac5e65090134d8f2d130bd313ef04137d6bef62070876593bcee94119369119ab38eeddddacf18e270d900\n  languageName: node\n  linkType: hard\n\n\"@turf/clone@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/clone@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/18cc6fd7f285652e5ad38d432ebdf66fc7ed4b960ff732d35833a12662f1cda442a3ab27a9ac789e9fad8a82aafc917f89ef44a680d52385770af226c2ccdda3\n  languageName: node\n  linkType: hard\n\n\"@turf/destination@npm:>=4.0.0, @turf/destination@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/destination@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/88c11061bf5e12b5bb8fe3ea5bdf7d241ade0dfc8604d8715571ab7da89552940d4fd3a95852d2cce2942ed8a20e745be071330d8ad7c502b5632255a2a4a119\n  languageName: node\n  linkType: hard\n\n\"@turf/difference@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/difference@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    polygon-clipping: \"npm:^0.15.3\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/e4927996b2e45d111ad727b883993a10712590b792d51a01688de3b4e476bf0ec628886fbb2d0476a490f7b2464e6ef9c316158884dd4f99484440bc5028539a\n  languageName: node\n  linkType: hard\n\n\"@turf/distance@npm:>=4.0.0, @turf/distance@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/distance@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/3eb2ad0eb8a3a754e732fc6cb85aaef7df35d13edc063248bb9313ae267c7d53bc0eef3a5ce536cf7283bf76c49523289889ae1651824e1030ea532dd49310ca\n  languageName: node\n  linkType: hard\n\n\"@turf/ellipse@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/ellipse@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/rhumb-destination\": \"npm:^7.1.0\"\n    \"@turf/transform-rotate\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/8230425aea1ed1b9f1b55bb12356498c8c58bf4fe294dcecb4a74658fefab5bcf3a7829864810d1b11d6e58599f174ac14bc5ca28ae43099f72c16994bf0dbc9\n  languageName: node\n  linkType: hard\n\n\"@turf/helpers@npm:>=4.0.0, @turf/helpers@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/helpers@npm:7.1.0\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/0b07c01584d8bee977edec8752109b4f79ab5b149e55a7dbe051e412e150c0a96f2464c9647676a092b7ab4429271eee4a31400ea45e9b55095ae53ad22f43d6\n  languageName: node\n  linkType: hard\n\n\"@turf/helpers@npm:^5.1.5\":\n  version: 5.1.5\n  resolution: \"@turf/helpers@npm:5.1.5\"\n  checksum: 10c0/f5ed19cddef37fb5098e2509e8472df3afe099dcd6db62b7e541cf37c02c6ea1b13f69c29ff493ded7a1374c1a9b185a87fefee368211934364977dffd48b2e9\n  languageName: node\n  linkType: hard\n\n\"@turf/helpers@npm:^6.1.4, @turf/helpers@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/helpers@npm:6.5.0\"\n  checksum: 10c0/786cbe0c0027f85db286fb3a0b7be04bb29bd63ec07760a49735ef32e9c5b4a7c059a8f691fafa31c7e0e9be34c281e014dc24077438bae01a09b492a680fb6f\n  languageName: node\n  linkType: hard\n\n\"@turf/helpers@npm:^7.2.0\":\n  version: 7.2.0\n  resolution: \"@turf/helpers@npm:7.2.0\"\n  dependencies:\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.8.1\"\n  checksum: 10c0/4d6f57164cca00ec7a18e2d3c0200d0274e4ab2b6b3201c6a867b867d899f3f618a82ae242617d467efb04f32740cec150ae06a0e07ee39318397ebc34914687\n  languageName: node\n  linkType: hard\n\n\"@turf/intersect@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/intersect@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    polygon-clipping: \"npm:^0.15.3\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/de283d539e1ecf285a6b656019d4779915f25104ba00fa5013092cf7f572dec9b431d28577baa5290d52c92904194291c17c653f7572e9dc0c2cdae0e837a670\n  languageName: node\n  linkType: hard\n\n\"@turf/invariant@npm:^5.1.5\":\n  version: 5.2.0\n  resolution: \"@turf/invariant@npm:5.2.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^5.1.5\"\n  checksum: 10c0/c7d6c81f85d85ce7da5bdbc457a61609a11a54f209f0bb922bcd12c329e9e7855d2b14b2df596c78521193b44c2a92cecf2f50db228546fa1a92beb413a22fbb\n  languageName: node\n  linkType: hard\n\n\"@turf/invariant@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/invariant@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n  checksum: 10c0/5ff9f2043d629cc5f6d3df78452632b2213f17931caa34698d9e8c651640508814b3522d4ab747f36a4b9dfaf6ff64eedc3a04ba62c39ddeb6706f052b621dc6\n  languageName: node\n  linkType: hard\n\n\"@turf/invariant@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/invariant@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/63a163ee7babf539af64bd204808979ce45e0d0bf772b3f28cda9fa99ab9c54150ea90d3203ae25cdda1a78eb206faf89db5847dc58ebc0eae8df0dab55822b8\n  languageName: node\n  linkType: hard\n\n\"@turf/jsts@npm:^2.7.1\":\n  version: 2.7.1\n  resolution: \"@turf/jsts@npm:2.7.1\"\n  dependencies:\n    jsts: \"npm:2.7.1\"\n  checksum: 10c0/d358d7336ea74779d1f4cab025ac0c8a7ed14d9575c0b430b1691570fec05eddef4051f157fe3bf57aebb9e87447dacf780f9a4bb09c10ef44cef457fc74aed0\n  languageName: node\n  linkType: hard\n\n\"@turf/line-intersect@npm:>=4.0.0, @turf/line-intersect@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/line-intersect@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    sweepline-intersections: \"npm:^1.5.0\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/540871a2b4ba3ecf444d2150053d91f328f851d1eca12a46c51681136ee090020b18ba5f355e4d8cfc81b1b53bd14f2dbfa808203882331a9bfda9ce7504e7c0\n  languageName: node\n  linkType: hard\n\n\"@turf/meta@npm:^5.1.5\":\n  version: 5.2.0\n  resolution: \"@turf/meta@npm:5.2.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^5.1.5\"\n  checksum: 10c0/fd41fbad84d840bebf75fdf13a4e3dd15b8c600251533073d5f6129a31a42e4f88790ce396492cec69f42ca4365e96d6f7940aeb302daaedcb795dc9414e7adc\n  languageName: node\n  linkType: hard\n\n\"@turf/meta@npm:^6.5.0\":\n  version: 6.5.0\n  resolution: \"@turf/meta@npm:6.5.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^6.5.0\"\n  checksum: 10c0/9df6cb5af7af98a477ddcd744fb44e4e890fe8a67afa50bb24cad7d9c15af67362d24f1f8890d706a9c369b18285b0ba42430509add769ad868ac452703bb59b\n  languageName: node\n  linkType: hard\n\n\"@turf/meta@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/meta@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n  checksum: 10c0/c7aa77ddb28ef5068b031c1b422d2d5dc1df51975f727be42e2d8d500a026a2e667242d6aa06453f757cbd5ead2db0ba6b9a5d2fcf5ab496574cd4c0ae4fe325\n  languageName: node\n  linkType: hard\n\n\"@turf/meta@npm:^7.2.0\":\n  version: 7.2.0\n  resolution: \"@turf/meta@npm:7.2.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.2.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n  checksum: 10c0/707ed63ba64fe48769806bf2419f5c0cd2ebf821a6467aeffb784ba7ebd6a63ec98d4192b97915948529c00304ed46ddc83842a80714fb1f2018fd4e3c455498\n  languageName: node\n  linkType: hard\n\n\"@turf/nearest-point-on-line@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/nearest-point-on-line@npm:7.1.0\"\n  dependencies:\n    \"@turf/bearing\": \"npm:^7.1.0\"\n    \"@turf/destination\": \"npm:^7.1.0\"\n    \"@turf/distance\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/line-intersect\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/6a9bb7fde7cc4d770ad6555eb3235cb70283b33698fca97c0b5683f3f612c71e4fa355aeeb14283f9cb0c88a73640d90691e3d8c2af0619ce97f801b6eb7208b\n  languageName: node\n  linkType: hard\n\n\"@turf/point-to-line-distance@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/point-to-line-distance@npm:7.1.0\"\n  dependencies:\n    \"@turf/bearing\": \"npm:^7.1.0\"\n    \"@turf/distance\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@turf/projection\": \"npm:^7.1.0\"\n    \"@turf/rhumb-bearing\": \"npm:^7.1.0\"\n    \"@turf/rhumb-distance\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/074042dd11caedddb354db95e7fe98f1995c76b087d1930d5dd93e627f428dcc482db484834eda49eecdffe2bf9cb4ac7860d9d13fed2d9a887e589114f129c4\n  languageName: node\n  linkType: hard\n\n\"@turf/polygon-to-line@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/polygon-to-line@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/84bc472c58722c98e887b52f2a5071c80405dded721e0be4ff43fed104e6c09d2d3f6e48296ae92b0c60ed433c34360e588f066ea4d16de866bdce50000228ef\n  languageName: node\n  linkType: hard\n\n\"@turf/projection@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/projection@npm:7.1.0\"\n  dependencies:\n    \"@turf/clone\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/269f64e4b61cefe3f5f95911d02fd50956315b478d2f891c705e09b7e5af76104a092794114f41b9b9b2543b2dcdab366360950625854a2613dab2caa88e8673\n  languageName: node\n  linkType: hard\n\n\"@turf/rewind@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/rewind@npm:7.1.0\"\n  dependencies:\n    \"@turf/boolean-clockwise\": \"npm:^7.1.0\"\n    \"@turf/clone\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/945063a52ea59b709fba4ad325dd0bd08c595ef4738afcd18238df12317f36c8ebcecf79c4f40dcb7bac10e844f44239fcba8d5111d653efaa81b49822fe34b8\n  languageName: node\n  linkType: hard\n\n\"@turf/rewind@npm:^5.1.5\":\n  version: 5.1.5\n  resolution: \"@turf/rewind@npm:5.1.5\"\n  dependencies:\n    \"@turf/boolean-clockwise\": \"npm:^5.1.5\"\n    \"@turf/clone\": \"npm:^5.1.5\"\n    \"@turf/helpers\": \"npm:^5.1.5\"\n    \"@turf/invariant\": \"npm:^5.1.5\"\n    \"@turf/meta\": \"npm:^5.1.5\"\n  checksum: 10c0/503c624ba2b5898daac6937ecf5eaf9f8b1ccd8109233b977adc8aeefbb0a086ff09f0813677b3fdf3d3c1072a9f3f22dfc4c6dc10dfbddf7f063bb4a543ec90\n  languageName: node\n  linkType: hard\n\n\"@turf/rhumb-bearing@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/rhumb-bearing@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/8dddfdbad6972e6381aafc2d2a991dcf0bf6745528b6cb10d9f3b0c5048b6406ff3542ab31b65e8e630bb272fb6d42f70b1b16ee031428f27ac2cd6d3b21b665\n  languageName: node\n  linkType: hard\n\n\"@turf/rhumb-destination@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/rhumb-destination@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/acbdf4b0372ba4fc9987cb4dedb2ddda4f9beaca255da6e6338eba94e9b818dcc9a04e6964ee10ff8777235a9510e8f1a6f231873dcd93bb45c862638d4db789\n  languageName: node\n  linkType: hard\n\n\"@turf/rhumb-distance@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/rhumb-distance@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/fb99cca3e96a04e069c17f314127a6dadbb0070082e046677027181f1238e335717d4273773c6def6a6d88627e21cd16c5b60ef67a4c8f395232cde91ddd2fe1\n  languageName: node\n  linkType: hard\n\n\"@turf/transform-rotate@npm:>=4.0.0, @turf/transform-rotate@npm:^7.1.0\":\n  version: 7.1.0\n  resolution: \"@turf/transform-rotate@npm:7.1.0\"\n  dependencies:\n    \"@turf/centroid\": \"npm:^7.1.0\"\n    \"@turf/clone\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@turf/rhumb-bearing\": \"npm:^7.1.0\"\n    \"@turf/rhumb-destination\": \"npm:^7.1.0\"\n    \"@turf/rhumb-distance\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/5aaada26a9a63284e60a20cbed5b1f19667104f6690d4ecb8e618c96186f743bb9954f0bb1447b355e07f869342bb5709cc4922b9f857ce32c85a33cf63fe572\n  languageName: node\n  linkType: hard\n\n\"@turf/transform-scale@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/transform-scale@npm:7.1.0\"\n  dependencies:\n    \"@turf/bbox\": \"npm:^7.1.0\"\n    \"@turf/center\": \"npm:^7.1.0\"\n    \"@turf/centroid\": \"npm:^7.1.0\"\n    \"@turf/clone\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@turf/rhumb-bearing\": \"npm:^7.1.0\"\n    \"@turf/rhumb-destination\": \"npm:^7.1.0\"\n    \"@turf/rhumb-distance\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/d7b34e40f9e32f50e4b8fdabcc626837b763a1c374fe4dd709f8c8d3f689cf9b634c6bbdf0b31a00d88f902e4c2a814a2cbaef2e0e93e8747fa8c24b5f78fe34\n  languageName: node\n  linkType: hard\n\n\"@turf/transform-translate@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/transform-translate@npm:7.1.0\"\n  dependencies:\n    \"@turf/clone\": \"npm:^7.1.0\"\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/invariant\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@turf/rhumb-destination\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/3776f9ddfcab819799c81a8ea3163c0f4d7a245df4cd0622b259ca8d270397c90d427b76b15087f25aa3a5af59aea8d0db7f1d99a1ab9052ac91d61b386cdf7c\n  languageName: node\n  linkType: hard\n\n\"@turf/union@npm:>=4.0.0\":\n  version: 7.1.0\n  resolution: \"@turf/union@npm:7.1.0\"\n  dependencies:\n    \"@turf/helpers\": \"npm:^7.1.0\"\n    \"@turf/meta\": \"npm:^7.1.0\"\n    \"@types/geojson\": \"npm:^7946.0.10\"\n    polygon-clipping: \"npm:^0.15.3\"\n    tslib: \"npm:^2.6.2\"\n  checksum: 10c0/1c60c87a83ab0cb45b952b74e761eedd52f2f148bda4e96ccee7c5c63038ac6d99142d5a5a772e1a49fe66ce8a1344537f4e192991e1fd3214cb406cd7cdd398\n  languageName: node\n  linkType: hard\n\n\"@types/brotli@npm:^1.3.0\":\n  version: 1.3.4\n  resolution: \"@types/brotli@npm:1.3.4\"\n  dependencies:\n    \"@types/node\": \"npm:*\"\n  checksum: 10c0/b13829301b3ee79250d3fa8b1973097837bdd040fb90c90f63f406385e62b8e06e72a41e77939c6f8118090a7c72215b0d7b4d83b1e69457616810a65a576339\n  languageName: node\n  linkType: hard\n\n\"@types/bson@npm:4.2.0\":\n  version: 4.2.0\n  resolution: \"@types/bson@npm:4.2.0\"\n  dependencies:\n    bson: \"npm:*\"\n  checksum: 10c0/06f226fa7d033badf3b0a90cd31fc720e82637b513282bf843732bdf3e8127a81dd0050cf95acd324e8812be6d16726c859c3f9dbf1d0e5e4d2e092a2cdff37d\n  languageName: node\n  linkType: hard\n\n\"@types/cacheable-request@npm:^6.0.1\":\n  version: 6.0.3\n  resolution: \"@types/cacheable-request@npm:6.0.3\"\n  dependencies:\n    \"@types/http-cache-semantics\": \"npm:*\"\n    \"@types/keyv\": \"npm:^3.1.4\"\n    \"@types/node\": \"npm:*\"\n    \"@types/responselike\": \"npm:^1.0.0\"\n  checksum: 10c0/10816a88e4e5b144d43c1d15a81003f86d649776c7f410c9b5e6579d0ad9d4ca71c541962fb403077388b446e41af7ae38d313e46692144985f006ac5e11fa03\n  languageName: node\n  linkType: hard\n\n\"@types/classnames@npm:^2.3.1\":\n  version: 2.3.1\n  resolution: \"@types/classnames@npm:2.3.1\"\n  dependencies:\n    classnames: \"npm:*\"\n  checksum: 10c0/6b71e5220aa3f04dbe1eba910f7755b880a2c6e3ba0ebf71fe73db99d58628022de06340a029c97db093e31d7981bc695b6c2ce65b2a58492d245e2cbe44a47d\n  languageName: node\n  linkType: hard\n\n\"@types/command-line-args@npm:^5.2.3\":\n  version: 5.2.3\n  resolution: \"@types/command-line-args@npm:5.2.3\"\n  checksum: 10c0/3a9bc58fd26e546391f6369dd28c03d59349dc4ac39eada1a5c39cc3578e02e4aac222615170e0db79b198ffba2af84fdbdda46e08c6edc4da42bc17ea85200f\n  languageName: node\n  linkType: hard\n\n\"@types/command-line-usage@npm:^5.0.4\":\n  version: 5.0.4\n  resolution: \"@types/command-line-usage@npm:5.0.4\"\n  checksum: 10c0/67840ebf4bcfee200c07d978669ad596fe2adc350fd5c19d44ec2248623575d96ec917f513d1d59453f8f57e879133861a4cc41c20045c07f6c959f1fcaac7ad\n  languageName: node\n  linkType: hard\n\n\"@types/d3-array@npm:^2.8.0\":\n  version: 2.12.7\n  resolution: \"@types/d3-array@npm:2.12.7\"\n  checksum: 10c0/31f9fc63e14de866630c526ac479b0854ad936b699fdd6a43df52628861ee50faddd6973e27cd3531193fc061c072810b0a07443921cb89e213988262c185312\n  languageName: node\n  linkType: hard\n\n\"@types/d3-brush@npm:^3.0.1\":\n  version: 3.0.6\n  resolution: \"@types/d3-brush@npm:3.0.6\"\n  dependencies:\n    \"@types/d3-selection\": \"npm:*\"\n  checksum: 10c0/fd6e2ac7657a354f269f6b9c58451ffae9d01b89ccb1eb6367fd36d635d2f1990967215ab498e0c0679ff269429c57fad6a2958b68f4d45bc9f81d81672edc01\n  languageName: node\n  linkType: hard\n\n\"@types/d3-scale@npm:^3.2.2\":\n  version: 3.3.5\n  resolution: \"@types/d3-scale@npm:3.3.5\"\n  dependencies:\n    \"@types/d3-time\": \"npm:^2\"\n  checksum: 10c0/2689ab13092e3fded22cdd1b888afd91aa60190be40c8eddc12b2d42de59b00917778340f90317c68c5ffc3a1bee68f5ca155434cd466bc7804f400f3f9e7529\n  languageName: node\n  linkType: hard\n\n\"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.2\":\n  version: 3.0.10\n  resolution: \"@types/d3-selection@npm:3.0.10\"\n  checksum: 10c0/de1f99ab186a08999bf394a645fd76911add1b02316270d4c07616c8383903a2b068d7e02b73b6a99a1f26bb49a2e99ef4b55a5d2ddfa165f6f3c53144897920\n  languageName: node\n  linkType: hard\n\n\"@types/d3-time@npm:^2\":\n  version: 2.1.4\n  resolution: \"@types/d3-time@npm:2.1.4\"\n  checksum: 10c0/b597bfa51a163d4231e953d6903b06fd6341d0f11a28222a79fafaddb46155d7f458a67c814de53df84926a47dd535897228a475679d228576b0cda87351e534\n  languageName: node\n  linkType: hard\n\n\"@types/debug@npm:^4.0.0\":\n  version: 4.1.12\n  resolution: \"@types/debug@npm:4.1.12\"\n  dependencies:\n    \"@types/ms\": \"npm:*\"\n  checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f\n  languageName: node\n  linkType: hard\n\n\"@types/diff-match-patch@npm:^1.0.36\":\n  version: 1.0.36\n  resolution: \"@types/diff-match-patch@npm:1.0.36\"\n  checksum: 10c0/0bad011ab138baa8bde94e7815064bb881f010452463272644ddbbb0590659cb93f7aa2776ff442c6721d70f202839e1053f8aa62d801cc4166f7a3ea9130055\n  languageName: node\n  linkType: hard\n\n\"@types/estree-jsx@npm:^1.0.0\":\n  version: 1.0.5\n  resolution: \"@types/estree-jsx@npm:1.0.5\"\n  dependencies:\n    \"@types/estree\": \"npm:*\"\n  checksum: 10c0/07b354331516428b27a3ab99ee397547d47eb223c34053b48f84872fafb841770834b90cc1a0068398e7c7ccb15ec51ab00ec64b31dc5e3dbefd624638a35c6d\n  languageName: node\n  linkType: hard\n\n\"@types/estree@npm:*, @types/estree@npm:^1.0.0\":\n  version: 1.0.7\n  resolution: \"@types/estree@npm:1.0.7\"\n  checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c\n  languageName: node\n  linkType: hard\n\n\"@types/exenv@npm:^1.2.0\":\n  version: 1.2.2\n  resolution: \"@types/exenv@npm:1.2.2\"\n  checksum: 10c0/1d90ba53911ef8b3ddc4d2a4933879e2c9741391de02189f7df9b99f5dea3636ed79bfd07d549fa2b82dfd771d812cd814331107bbfff627eef3d5526277707f\n  languageName: node\n  linkType: hard\n\n\"@types/geojson@npm:*, @types/geojson@npm:^7946.0.10, @types/geojson@npm:^7946.0.13, @types/geojson@npm:^7946.0.7, @types/geojson@npm:^7946.0.8\":\n  version: 7946.0.14\n  resolution: \"@types/geojson@npm:7946.0.14\"\n  checksum: 10c0/54f3997708fa2970c03eeb31f7e4540a0eb6387b15e9f8a60513a1409c23cafec8d618525404573468b59c6fecbfd053724b3327f7fca416729c26271d799f55\n  languageName: node\n  linkType: hard\n\n\"@types/hammerjs@npm:^2.0.36, @types/hammerjs@npm:^2.0.41\":\n  version: 2.0.45\n  resolution: \"@types/hammerjs@npm:2.0.45\"\n  checksum: 10c0/1f01e3d0260e3cb824fd0ae32c9a8e1b3727e53ef31682612a0a282c4a84bb758dd30b04749b2ae91e621443c80bfe541b38e91e33308f9dea5d9ac92bd0e854\n  languageName: node\n  linkType: hard\n\n\"@types/hast@npm:^3.0.0\":\n  version: 3.0.4\n  resolution: \"@types/hast@npm:3.0.4\"\n  dependencies:\n    \"@types/unist\": \"npm:*\"\n  checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7\n  languageName: node\n  linkType: hard\n\n\"@types/hoist-non-react-statics@npm:*, @types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1\":\n  version: 3.3.5\n  resolution: \"@types/hoist-non-react-statics@npm:3.3.5\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n    hoist-non-react-statics: \"npm:^3.3.0\"\n  checksum: 10c0/2a3b64bf3d9817d7830afa60ee314493c475fb09570a64e7737084cd482d2177ebdddf888ce837350bac51741278b077683facc9541f052d4bbe8487b4e3e618\n  languageName: node\n  linkType: hard\n\n\"@types/http-cache-semantics@npm:*\":\n  version: 4.0.4\n  resolution: \"@types/http-cache-semantics@npm:4.0.4\"\n  checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6\n  languageName: node\n  linkType: hard\n\n\"@types/keymirror@npm:^0.1.1\":\n  version: 0.1.4\n  resolution: \"@types/keymirror@npm:0.1.4\"\n  checksum: 10c0/f3c66ae8a2d786728cb91004e9a4d016c6df31478976b2e3d73d70b7fb6d8ca55216045973768be9a57f4c29a6ce70a18e1ff2205230831c8e509ed1b977f338\n  languageName: node\n  linkType: hard\n\n\"@types/keyv@npm:^3.1.4\":\n  version: 3.1.4\n  resolution: \"@types/keyv@npm:3.1.4\"\n  dependencies:\n    \"@types/node\": \"npm:*\"\n  checksum: 10c0/ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c\n  languageName: node\n  linkType: hard\n\n\"@types/leaflet@npm:^1.9.8\":\n  version: 1.9.16\n  resolution: \"@types/leaflet@npm:1.9.16\"\n  dependencies:\n    \"@types/geojson\": \"npm:*\"\n  checksum: 10c0/80403093b918ed19911997dc8ebe1aac44f49ada065f5b60ea451f6b8c92ffc942ef49706f7faa1a4e7c62824390a44d407bd5b1a9cfbbaea82c1501129c919a\n  languageName: node\n  linkType: hard\n\n\"@types/lodash.debounce@npm:^4.0.7\":\n  version: 4.0.9\n  resolution: \"@types/lodash.debounce@npm:4.0.9\"\n  dependencies:\n    \"@types/lodash\": \"npm:*\"\n  checksum: 10c0/9fbb24e5e52616faf60ba5c82d8c6517f4b86fc6e9ab353b4c56c0760f63d9bf53af3f2d8f6c37efa48090359fb96dba1087d497758511f6c40677002191d042\n  languageName: node\n  linkType: hard\n\n\"@types/lodash@npm:*\":\n  version: 4.17.7\n  resolution: \"@types/lodash@npm:4.17.7\"\n  checksum: 10c0/40c965b5ffdcf7ff5c9105307ee08b782da228c01b5c0529122c554c64f6b7168fc8f11dc79aa7bae4e67e17efafaba685dc3a47e294dbf52a65ed2b67100561\n  languageName: node\n  linkType: hard\n\n\"@types/lodash@npm:4.17.5\":\n  version: 4.17.5\n  resolution: \"@types/lodash@npm:4.17.5\"\n  checksum: 10c0/55924803ed853e72261512bd3eaf2c5b16558c3817feb0a3125ef757afe46e54b86f33d1960e40b7606c0ddab91a96f47966bf5e6006b7abfd8994c13b04b19b\n  languageName: node\n  linkType: hard\n\n\"@types/mapbox-gl@npm:*, @types/mapbox-gl@npm:>=1.0.0\":\n  version: 3.4.0\n  resolution: \"@types/mapbox-gl@npm:3.4.0\"\n  dependencies:\n    \"@types/geojson\": \"npm:*\"\n  checksum: 10c0/62f293bc29cb31596cf1749a2d0ff79fc52a48921ea450bf7922ce52faca3343b757ec7a75b9c6210310899df9e8956a146a044bc4c299a84fd69331444ee8b7\n  languageName: node\n  linkType: hard\n\n\"@types/mapbox__point-geometry@npm:*, @types/mapbox__point-geometry@npm:^0.1.4\":\n  version: 0.1.4\n  resolution: \"@types/mapbox__point-geometry@npm:0.1.4\"\n  checksum: 10c0/670191664ea0a6ccb4563500fe815a9aba029ba2f0528d42f9eb560ccb44f6542ba8674e2a3f6d41bd10ad8855b4df4782b5340c980ca182ef9fe6752f2737b8\n  languageName: node\n  linkType: hard\n\n\"@types/mapbox__vector-tile@npm:^1.3.4\":\n  version: 1.3.4\n  resolution: \"@types/mapbox__vector-tile@npm:1.3.4\"\n  dependencies:\n    \"@types/geojson\": \"npm:*\"\n    \"@types/mapbox__point-geometry\": \"npm:*\"\n    \"@types/pbf\": \"npm:*\"\n  checksum: 10c0/082907ed9cf96b82327dabf3b4c3a14746a825e4a81f0abf46b50e2557f25cbda652725d8af002e5edcc344a83c85e1a4b71a2d39ef4d829c243344a85ac13a6\n  languageName: node\n  linkType: hard\n\n\"@types/mdast@npm:^4.0.0\":\n  version: 4.0.4\n  resolution: \"@types/mdast@npm:4.0.4\"\n  dependencies:\n    \"@types/unist\": \"npm:*\"\n  checksum: 10c0/84f403dbe582ee508fd9c7643ac781ad8597fcbfc9ccb8d4715a2c92e4545e5772cbd0dbdf18eda65789386d81b009967fdef01b24faf6640f817287f54d9c82\n  languageName: node\n  linkType: hard\n\n\"@types/minimist@npm:^1.2.0\":\n  version: 1.2.5\n  resolution: \"@types/minimist@npm:1.2.5\"\n  checksum: 10c0/3f791258d8e99a1d7d0ca2bda1ca6ea5a94e5e7b8fc6cde84dd79b0552da6fb68ade750f0e17718f6587783c24254bbca0357648dd59dc3812c150305cabdc46\n  languageName: node\n  linkType: hard\n\n\"@types/ms@npm:*\":\n  version: 2.1.0\n  resolution: \"@types/ms@npm:2.1.0\"\n  checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225\n  languageName: node\n  linkType: hard\n\n\"@types/node-fetch@npm:^2.6.4\":\n  version: 2.6.12\n  resolution: \"@types/node-fetch@npm:2.6.12\"\n  dependencies:\n    \"@types/node\": \"npm:*\"\n    form-data: \"npm:^4.0.0\"\n  checksum: 10c0/7693acad5499b7df2d1727d46cff092a63896dc04645f36b973dd6dd754a59a7faba76fcb777bdaa35d80625c6a9dd7257cca9c401a4bab03b04480cda7fd1af\n  languageName: node\n  linkType: hard\n\n\"@types/node@npm:*\":\n  version: 22.4.1\n  resolution: \"@types/node@npm:22.4.1\"\n  dependencies:\n    undici-types: \"npm:~6.19.2\"\n  checksum: 10c0/e42607438fcbd3a6aebd09084868fa0b22a4b0daf9eda79ed615df7ff8ae95e35ea56e090e1f3140ebae76b640abe42d4a6d5b60c0819eadf499adca737305b6\n  languageName: node\n  linkType: hard\n\n\"@types/node@npm:^18.11.18\":\n  version: 18.19.71\n  resolution: \"@types/node@npm:18.19.71\"\n  dependencies:\n    undici-types: \"npm:~5.26.4\"\n  checksum: 10c0/9f9b4a1c4e2db2994ef36f165322b3bb807466e3f92751ed52a40af0212917bc6ecd12dc6775eb829176b71b26570bea9c6a0a2d9e3ae6b496721c71934244db\n  languageName: node\n  linkType: hard\n\n\"@types/node@npm:^20.13.0\":\n  version: 20.16.1\n  resolution: \"@types/node@npm:20.16.1\"\n  dependencies:\n    undici-types: \"npm:~6.19.2\"\n  checksum: 10c0/cac13c0f42467df3254805a671ca9e74a6eb7c41568de972e26b10dcc448a45743aaf00e9e5fce4a9214da5bc8444fe902918e105dac5a224e24e83fd9989a97\n  languageName: node\n  linkType: hard\n\n\"@types/normalize-package-data@npm:^2.4.0\":\n  version: 2.4.4\n  resolution: \"@types/normalize-package-data@npm:2.4.4\"\n  checksum: 10c0/aef7bb9b015883d6f4119c423dd28c4bdc17b0e8a0ccf112c78b4fe0e91fbc4af7c6204b04bba0e199a57d2f3fbbd5b4a14bf8739bf9d2a39b2a0aad545e0f86\n  languageName: node\n  linkType: hard\n\n\"@types/offscreencanvas@npm:^2019.7.0\":\n  version: 2019.7.3\n  resolution: \"@types/offscreencanvas@npm:2019.7.3\"\n  checksum: 10c0/6d1dfae721d321cd2b5435f347a0e53b09f33b2f9e9333396480f592823bc323847b8169f7d251d2285cb93dbc1ba2e30741ac5cf4b1c003d660fd4c24526963\n  languageName: node\n  linkType: hard\n\n\"@types/pako@npm:^1.0.1\":\n  version: 1.0.7\n  resolution: \"@types/pako@npm:1.0.7\"\n  checksum: 10c0/1ba133db0b30a974c3d651c85651fd30135f629727b4b4d7ef2649c8f8b01014d5ef41f75399d939e320a50bfa87c32beccbb513badfeaf85d74ea6d5370fdcc\n  languageName: node\n  linkType: hard\n\n\"@types/pbf@npm:*, @types/pbf@npm:^3.0.5\":\n  version: 3.0.5\n  resolution: \"@types/pbf@npm:3.0.5\"\n  checksum: 10c0/c32348c6c81e6c31fe4a1f59983e3a9904727b809fb1e5ddec4fad49abaf93070ec26ee0c04c6516536c181c945b3c7d9e226549eaac3b2e12cb7b57f549a49c\n  languageName: node\n  linkType: hard\n\n\"@types/prop-types@npm:*\":\n  version: 15.7.12\n  resolution: \"@types/prop-types@npm:15.7.12\"\n  checksum: 10c0/1babcc7db6a1177779f8fde0ccc78d64d459906e6ef69a4ed4dd6339c920c2e05b074ee5a92120fe4e9d9f1a01c952f843ebd550bee2332fc2ef81d1706878f8\n  languageName: node\n  linkType: hard\n\n\"@types/react-copy-to-clipboard@npm:^5.0.2\":\n  version: 5.0.7\n  resolution: \"@types/react-copy-to-clipboard@npm:5.0.7\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/33bea4549fa263b597d0dedb3807f99286d8ccf59adb370e3d82d1c9075195925a343982abd73c63cc47854a7240ddae79873a5cb3590c9b33c1b65bf9d07689\n  languageName: node\n  linkType: hard\n\n\"@types/react-dom@npm:^18.0.11\":\n  version: 18.3.0\n  resolution: \"@types/react-dom@npm:18.3.0\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b\n  languageName: node\n  linkType: hard\n\n\"@types/react-lifecycles-compat@npm:^3.0.1\":\n  version: 3.0.4\n  resolution: \"@types/react-lifecycles-compat@npm:3.0.4\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/3c33fcd7d52d44031b21cf8a6ae9c0f208fe3b972ee4f03fcbe4509d2c50da474bfdd3330f5a09046b7fd63a1f7f23b194bc8d774823c1981cc13929744b90d2\n  languageName: node\n  linkType: hard\n\n\"@types/react-map-gl@npm:^6.1.3\":\n  version: 6.1.6\n  resolution: \"@types/react-map-gl@npm:6.1.6\"\n  dependencies:\n    \"@types/geojson\": \"npm:*\"\n    \"@types/mapbox-gl\": \"npm:*\"\n    \"@types/react\": \"npm:*\"\n    \"@types/viewport-mercator-project\": \"npm:*\"\n  checksum: 10c0/caf1dc92a90d6a2a8d31945de3e7b7df190dbd1b56da12740f74f4cf0ae328adc0046230fc05fcba4abfc2dd904818a16b3e211cb6edf4180be49f32fc27286c\n  languageName: node\n  linkType: hard\n\n\"@types/react-modal@npm:^3.16.3\":\n  version: 3.16.3\n  resolution: \"@types/react-modal@npm:3.16.3\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/dfcf52fa4b5d5c203aa47ffaafade369836cb9f891e9b60e7056bf5fdfc508ebec7971bb3d4c4018f422953c1c79948755ef19da3e816c28d7c5fdacf9466af8\n  languageName: node\n  linkType: hard\n\n\"@types/react-redux@npm:^7.1.23\":\n  version: 7.1.33\n  resolution: \"@types/react-redux@npm:7.1.33\"\n  dependencies:\n    \"@types/hoist-non-react-statics\": \"npm:^3.3.0\"\n    \"@types/react\": \"npm:*\"\n    hoist-non-react-statics: \"npm:^3.3.0\"\n    redux: \"npm:^4.0.0\"\n  checksum: 10c0/e17a2fea00c6ab5f22868e927b4da7b7cf8dc7c85102638fa0f87e12ae0ec13335d9b3bf75098b3316dd8d2a18c99fe08bed22daa989a13f3710c4530f7b979e\n  languageName: node\n  linkType: hard\n\n\"@types/react-virtualized@npm:^9.21.30\":\n  version: 9.22.0\n  resolution: \"@types/react-virtualized@npm:9.22.0\"\n  dependencies:\n    \"@types/prop-types\": \"npm:*\"\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/dede58499c90fbc2042e58a3c7192a92b8bc61fed0589a95911d2a0726f202fa0bd87c9fc68e861ce0a6a034a7460a195b291ff49465a3b620e9f630bfdc9246\n  languageName: node\n  linkType: hard\n\n\"@types/react-vis@npm:1.11.7\":\n  version: 1.11.7\n  resolution: \"@types/react-vis@npm:1.11.7\"\n  dependencies:\n    \"@types/react\": \"npm:*\"\n  checksum: 10c0/beddf4062c64aef8e5fcfe07debad3ae128e8a8bbc76fbd7d89b0604d4f242c9ef78d29357806b6d464a610b415f948d0981a1e65b4de9b72b9c3d3d23497daa\n  languageName: node\n  linkType: hard\n\n\"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:^18.0.28\":\n  version: 18.3.4\n  resolution: \"@types/react@npm:18.3.4\"\n  dependencies:\n    \"@types/prop-types\": \"npm:*\"\n    csstype: \"npm:^3.0.2\"\n  checksum: 10c0/5c52e1e6f540cff21e3c2a5212066d02e005f6fb21e4a536a29097fae878db9f407cd7a4b43778f51359349c5f692e08bc77ddb5f5cecbfca9ca4d4e3c91a48e\n  languageName: node\n  linkType: hard\n\n\"@types/redux-actions@npm:^2.6.2\":\n  version: 2.6.5\n  resolution: \"@types/redux-actions@npm:2.6.5\"\n  checksum: 10c0/0b1a4da8448f90b777c227a26a04864dda6fae5d31b627e01a51f36d0ab6e9aa17b6ac5fb12f9ad3bdf122303c9ecb6b7ea11952cff0aaa3456dd040e8401041\n  languageName: node\n  linkType: hard\n\n\"@types/responselike@npm:^1.0.0\":\n  version: 1.0.3\n  resolution: \"@types/responselike@npm:1.0.3\"\n  dependencies:\n    \"@types/node\": \"npm:*\"\n  checksum: 10c0/a58ba341cb9e7d74f71810a88862da7b2a6fa42e2a1fc0ce40498f6ea1d44382f0640117057da779f74c47039f7166bf48fad02dc876f94e005c7afa50f5e129\n  languageName: node\n  linkType: hard\n\n\"@types/retry@npm:0.12.0\":\n  version: 0.12.0\n  resolution: \"@types/retry@npm:0.12.0\"\n  checksum: 10c0/7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328\n  languageName: node\n  linkType: hard\n\n\"@types/styled-components@npm:^5.1.32\":\n  version: 5.1.34\n  resolution: \"@types/styled-components@npm:5.1.34\"\n  dependencies:\n    \"@types/hoist-non-react-statics\": \"npm:*\"\n    \"@types/react\": \"npm:*\"\n    csstype: \"npm:^3.0.2\"\n  checksum: 10c0/5bce93ea2c6161fc45daaf863eefdc20672e839ae486597c40b95e7978e249c160c1bc9706f56cb5152a7ef63cf485d15a9502889169ef945281f511e4b2d5a0\n  languageName: node\n  linkType: hard\n\n\"@types/stylis@npm:4.2.0\":\n  version: 4.2.0\n  resolution: \"@types/stylis@npm:4.2.0\"\n  checksum: 10c0/c76c13e76ca485f598a13984cfb5e07bb88a851da5bee213587424a5f101f182c74f4f92d633cebf9abcec40ccebb645d600d184b7e4b42793e3eeca8729b110\n  languageName: node\n  linkType: hard\n\n\"@types/supercluster@npm:^7.1.0, @types/supercluster@npm:^7.1.3\":\n  version: 7.1.3\n  resolution: \"@types/supercluster@npm:7.1.3\"\n  dependencies:\n    \"@types/geojson\": \"npm:*\"\n  checksum: 10c0/0d55dad98df0990fc38a7bb64dc23dda46014187c0d7638e6f2b6717ba8931b13e5b1d394789833a2ac822014c977ef64623dffd81a0bbf39e52c53c8183741f\n  languageName: node\n  linkType: hard\n\n\"@types/unist@npm:*, @types/unist@npm:^3.0.0\":\n  version: 3.0.3\n  resolution: \"@types/unist@npm:3.0.3\"\n  checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60\n  languageName: node\n  linkType: hard\n\n\"@types/unist@npm:^2.0.0\":\n  version: 2.0.11\n  resolution: \"@types/unist@npm:2.0.11\"\n  checksum: 10c0/24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d\n  languageName: node\n  linkType: hard\n\n\"@types/use-sync-external-store@npm:^0.0.3\":\n  version: 0.0.3\n  resolution: \"@types/use-sync-external-store@npm:0.0.3\"\n  checksum: 10c0/82824c1051ba40a00e3d47964cdf4546a224e95f172e15a9c62aa3f118acee1c7518b627a34f3aa87298a2039f982e8509f92bfcc18bea7c255c189c293ba547\n  languageName: node\n  linkType: hard\n\n\"@types/uuid@npm:^10.0.0\":\n  version: 10.0.0\n  resolution: \"@types/uuid@npm:10.0.0\"\n  checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60\n  languageName: node\n  linkType: hard\n\n\"@types/viewport-mercator-project@npm:*\":\n  version: 6.1.6\n  resolution: \"@types/viewport-mercator-project@npm:6.1.6\"\n  dependencies:\n    gl-matrix: \"npm:^3.2.0\"\n  checksum: 10c0/8671280edbe719b6649a682310113c9a86a98f7e990cebe38d928d8eb1d64216e20ba0b3117735f22df621c3fd6d2bf78de992f3416c365030ed07baaefaa0a6\n  languageName: node\n  linkType: hard\n\n\"@ungap/structured-clone@npm:^1.0.0\":\n  version: 1.3.0\n  resolution: \"@ungap/structured-clone@npm:1.3.0\"\n  checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a\n  languageName: node\n  linkType: hard\n\n\"@wojtekmaj/date-utils@npm:^1.1.3, @wojtekmaj/date-utils@npm:^1.5.0\":\n  version: 1.5.1\n  resolution: \"@wojtekmaj/date-utils@npm:1.5.1\"\n  checksum: 10c0/7c213cca5ab6b84ef61b9aea2b9fb8a04bf4c9764b28a97ffc4ee46a3e81560532a74d106a6f8aeef4792e1aaa6ea3dfd3c4a639dddbea560eb3f33cd62b8d7d\n  languageName: node\n  linkType: hard\n\n\"abbrev@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"abbrev@npm:2.0.0\"\n  checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372\n  languageName: node\n  linkType: hard\n\n\"abort-controller@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"abort-controller@npm:3.0.0\"\n  dependencies:\n    event-target-shim: \"npm:^5.0.0\"\n  checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5\n  languageName: node\n  linkType: hard\n\n\"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2\":\n  version: 7.1.3\n  resolution: \"agent-base@npm:7.1.3\"\n  checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11\n  languageName: node\n  linkType: hard\n\n\"agentkeepalive@npm:^4.2.1\":\n  version: 4.6.0\n  resolution: \"agentkeepalive@npm:4.6.0\"\n  dependencies:\n    humanize-ms: \"npm:^1.2.1\"\n  checksum: 10c0/235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187\n  languageName: node\n  linkType: hard\n\n\"ai@npm:^4.3.13\":\n  version: 4.3.13\n  resolution: \"ai@npm:4.3.13\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.7\"\n    \"@ai-sdk/react\": \"npm:1.2.11\"\n    \"@ai-sdk/ui-utils\": \"npm:1.2.10\"\n    \"@opentelemetry/api\": \"npm:1.9.0\"\n    jsondiffpatch: \"npm:0.6.0\"\n  peerDependencies:\n    react: ^18 || ^19 || ^19.0.0-rc\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    react:\n      optional: true\n  checksum: 10c0/b142a4794fc6c387967b9e1d283290bda14bac69ffc9d35128c81c801dffc24a6e7e2eda1276282ce7bbc03fc6e5dd08723160ab0dd36e667eb2ccbbfa169b7a\n  languageName: node\n  linkType: hard\n\n\"ai@npm:^4.3.16\":\n  version: 4.3.16\n  resolution: \"ai@npm:4.3.16\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n    \"@ai-sdk/react\": \"npm:1.2.12\"\n    \"@ai-sdk/ui-utils\": \"npm:1.2.11\"\n    \"@opentelemetry/api\": \"npm:1.9.0\"\n    jsondiffpatch: \"npm:0.6.0\"\n  peerDependencies:\n    react: ^18 || ^19 || ^19.0.0-rc\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    react:\n      optional: true\n  checksum: 10c0/befe761c9386cda6de33370a2590900352b444d81959255c624e2bfd40765f126d29269f0ef3e00bde07daf237004aa0b66d0b253664aa478c148e923ce78c41\n  languageName: node\n  linkType: hard\n\n\"ai@npm:^4.3.19\":\n  version: 4.3.19\n  resolution: \"ai@npm:4.3.19\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.1.3\"\n    \"@ai-sdk/provider-utils\": \"npm:2.2.8\"\n    \"@ai-sdk/react\": \"npm:1.2.12\"\n    \"@ai-sdk/ui-utils\": \"npm:1.2.11\"\n    \"@opentelemetry/api\": \"npm:1.9.0\"\n    jsondiffpatch: \"npm:0.6.0\"\n  peerDependencies:\n    react: ^18 || ^19 || ^19.0.0-rc\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    react:\n      optional: true\n  checksum: 10c0/738ac453b3e61b2f2282941fe8af946c42696fbdcffa5ac213823377bcddf475f26923cf2ca5656d5655e5c351e355e1af62dcb04a6df6139b67bac650b01af2\n  languageName: node\n  linkType: hard\n\n\"almost-equal@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"almost-equal@npm:1.1.0\"\n  checksum: 10c0/169f50ec565cafcb99043532789c982da1f5b82abb49cf6376d3c2b989e27b6455004f854b26b25086fc5579e4186994d8de64dae29d171f9f7593f43f8c6aa2\n  languageName: node\n  linkType: hard\n\n\"ansi-regex@npm:^5.0.1\":\n  version: 5.0.1\n  resolution: \"ansi-regex@npm:5.0.1\"\n  checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737\n  languageName: node\n  linkType: hard\n\n\"ansi-regex@npm:^6.0.1\":\n  version: 6.1.0\n  resolution: \"ansi-regex@npm:6.1.0\"\n  checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc\n  languageName: node\n  linkType: hard\n\n\"ansi-styles@npm:^3.2.1\":\n  version: 3.2.1\n  resolution: \"ansi-styles@npm:3.2.1\"\n  dependencies:\n    color-convert: \"npm:^1.9.0\"\n  checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b\n  languageName: node\n  linkType: hard\n\n\"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0\":\n  version: 4.3.0\n  resolution: \"ansi-styles@npm:4.3.0\"\n  dependencies:\n    color-convert: \"npm:^2.0.1\"\n  checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041\n  languageName: node\n  linkType: hard\n\n\"ansi-styles@npm:^5.0.0\":\n  version: 5.2.0\n  resolution: \"ansi-styles@npm:5.2.0\"\n  checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df\n  languageName: node\n  linkType: hard\n\n\"ansi-styles@npm:^6.1.0\":\n  version: 6.2.1\n  resolution: \"ansi-styles@npm:6.2.1\"\n  checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c\n  languageName: node\n  linkType: hard\n\n\"any-promise@npm:^1.0.0\":\n  version: 1.3.0\n  resolution: \"any-promise@npm:1.3.0\"\n  checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889\n  languageName: node\n  linkType: hard\n\n\"anymatch@npm:~3.1.2\":\n  version: 3.1.3\n  resolution: \"anymatch@npm:3.1.3\"\n  dependencies:\n    normalize-path: \"npm:^3.0.0\"\n    picomatch: \"npm:^2.0.4\"\n  checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac\n  languageName: node\n  linkType: hard\n\n\"apache-arrow@npm:>= 15.0.0, apache-arrow@npm:^17.0.0\":\n  version: 17.0.0\n  resolution: \"apache-arrow@npm:17.0.0\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.11\"\n    \"@types/command-line-args\": \"npm:^5.2.3\"\n    \"@types/command-line-usage\": \"npm:^5.0.4\"\n    \"@types/node\": \"npm:^20.13.0\"\n    command-line-args: \"npm:^5.2.1\"\n    command-line-usage: \"npm:^7.0.1\"\n    flatbuffers: \"npm:^24.3.25\"\n    json-bignum: \"npm:^0.0.3\"\n    tslib: \"npm:^2.6.2\"\n  bin:\n    arrow2csv: bin/arrow2csv.cjs\n  checksum: 10c0/fa705b88a5d76c05122414bf7ba8a46c93f3509584de332eb045c736384cdfe53fc140135abbdeaa6036dfe422d145598b461b89f0b710cdeebbb732e9fb7f58\n  languageName: node\n  linkType: hard\n\n\"apache-arrow@npm:>=15, apache-arrow@npm:>=15.0.0\":\n  version: 19.0.0\n  resolution: \"apache-arrow@npm:19.0.0\"\n  dependencies:\n    \"@swc/helpers\": \"npm:^0.5.11\"\n    \"@types/command-line-args\": \"npm:^5.2.3\"\n    \"@types/command-line-usage\": \"npm:^5.0.4\"\n    \"@types/node\": \"npm:^20.13.0\"\n    command-line-args: \"npm:^6.0.1\"\n    command-line-usage: \"npm:^7.0.1\"\n    flatbuffers: \"npm:^24.3.25\"\n    json-bignum: \"npm:^0.0.3\"\n    tslib: \"npm:^2.6.2\"\n  bin:\n    arrow2csv: bin/arrow2csv.js\n  checksum: 10c0/1424e9214ee5a5914fcbab43b2e28efb3f4f30be27226c3bbea53fe1a99e8a6a7982f42978231e247714d5da1bcc0121d051c24bbecd0b9b246608e885437e9a\n  languageName: node\n  linkType: hard\n\n\"arg@npm:^5.0.2\":\n  version: 5.0.2\n  resolution: \"arg@npm:5.0.2\"\n  checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e\n  languageName: node\n  linkType: hard\n\n\"argparse@npm:^1.0.10\":\n  version: 1.0.10\n  resolution: \"argparse@npm:1.0.10\"\n  dependencies:\n    sprintf-js: \"npm:~1.0.2\"\n  checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de\n  languageName: node\n  linkType: hard\n\n\"arr-union@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"arr-union@npm:3.1.0\"\n  checksum: 10c0/7d5aa05894e54aa93c77c5726c1dd5d8e8d3afe4f77983c0aa8a14a8a5cbe8b18f0cf4ecaa4ac8c908ef5f744d2cbbdaa83fd6e96724d15fea56cfa7f5efdd51\n  languageName: node\n  linkType: hard\n\n\"array-back@npm:^3.0.1, array-back@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"array-back@npm:3.1.0\"\n  checksum: 10c0/bb1fe86aa8b39c21e73c68c7abf8b05ed939b8951a3b17527217f6a2a84e00e4cfa4fdec823081689c5e216709bf1f214a4f5feeee6726eaff83897fa1a7b8ee\n  languageName: node\n  linkType: hard\n\n\"array-back@npm:^6.2.2\":\n  version: 6.2.2\n  resolution: \"array-back@npm:6.2.2\"\n  checksum: 10c0/c98a6e43b669400f58e2fba478336d5d02aac970566ffae3af0cb9b5585ec3811a1e010c76e34fb809a9762e6822a43a9c9a1b99f2a35f43b11a9e198e782818\n  languageName: node\n  linkType: hard\n\n\"array-buffer-byte-length@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"array-buffer-byte-length@npm:1.0.1\"\n  dependencies:\n    call-bind: \"npm:^1.0.5\"\n    is-array-buffer: \"npm:^3.0.4\"\n  checksum: 10c0/f5cdf54527cd18a3d2852ddf73df79efec03829e7373a8322ef5df2b4ef546fb365c19c71d6b42d641cb6bfe0f1a2f19bc0ece5b533295f86d7c3d522f228917\n  languageName: node\n  linkType: hard\n\n\"arraybuffer.prototype.slice@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"arraybuffer.prototype.slice@npm:1.0.3\"\n  dependencies:\n    array-buffer-byte-length: \"npm:^1.0.1\"\n    call-bind: \"npm:^1.0.5\"\n    define-properties: \"npm:^1.2.1\"\n    es-abstract: \"npm:^1.22.3\"\n    es-errors: \"npm:^1.2.1\"\n    get-intrinsic: \"npm:^1.2.3\"\n    is-array-buffer: \"npm:^3.0.4\"\n    is-shared-array-buffer: \"npm:^1.0.2\"\n  checksum: 10c0/d32754045bcb2294ade881d45140a5e52bda2321b9e98fa514797b7f0d252c4c5ab0d1edb34112652c62fa6a9398def568da63a4d7544672229afea283358c36\n  languageName: node\n  linkType: hard\n\n\"arrify@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"arrify@npm:1.0.1\"\n  checksum: 10c0/c35c8d1a81bcd5474c0c57fe3f4bad1a4d46a5fa353cedcff7a54da315df60db71829e69104b859dff96c5d68af46bd2be259fe5e50dc6aa9df3b36bea0383ab\n  languageName: node\n  linkType: hard\n\n\"asap@npm:~2.0.3\":\n  version: 2.0.6\n  resolution: \"asap@npm:2.0.6\"\n  checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d\n  languageName: node\n  linkType: hard\n\n\"assign-symbols@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"assign-symbols@npm:1.0.0\"\n  checksum: 10c0/29a654b8a6da6889a190d0d0efef4b1bfb5948fa06cbc245054aef05139f889f2f7c75b989917e3fde853fc4093b88048e4de8578a73a76f113d41bfd66e5775\n  languageName: node\n  linkType: hard\n\n\"async-limiter@npm:~1.0.0\":\n  version: 1.0.1\n  resolution: \"async-limiter@npm:1.0.1\"\n  checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969\n  languageName: node\n  linkType: hard\n\n\"async-mutex@npm:^0.2.2\":\n  version: 0.2.6\n  resolution: \"async-mutex@npm:0.2.6\"\n  dependencies:\n    tslib: \"npm:^2.0.0\"\n  checksum: 10c0/440f1388fdbf2021261ba05952765182124a333681692fdef6af13935c20bfc2017e24e902362f12b29094a77b359ce3131e8dd45b1db42f1d570927ace9e7d9\n  languageName: node\n  linkType: hard\n\n\"asynckit@npm:^0.4.0\":\n  version: 0.4.0\n  resolution: \"asynckit@npm:0.4.0\"\n  checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d\n  languageName: node\n  linkType: hard\n\n\"available-typed-arrays@npm:^1.0.7\":\n  version: 1.0.7\n  resolution: \"available-typed-arrays@npm:1.0.7\"\n  dependencies:\n    possible-typed-array-names: \"npm:^1.0.0\"\n  checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2\n  languageName: node\n  linkType: hard\n\n\"bail@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"bail@npm:2.0.2\"\n  checksum: 10c0/25cbea309ef6a1f56214187004e8f34014eb015713ea01fa5b9b7e9e776ca88d0fdffd64143ac42dc91966c915a4b7b683411b56e14929fad16153fc026ffb8b\n  languageName: node\n  linkType: hard\n\n\"balanced-match@npm:^1.0.0\":\n  version: 1.0.2\n  resolution: \"balanced-match@npm:1.0.2\"\n  checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee\n  languageName: node\n  linkType: hard\n\n\"base-64@npm:^0.1.0\":\n  version: 0.1.0\n  resolution: \"base-64@npm:0.1.0\"\n  checksum: 10c0/fe0dcf076e823f04db7ee9b02495be08a91c445fbc6db03cb9913be9680e2fcc0af8b74459041fe08ad16800b1f65a549501d8f08696a8a6d32880789b7de69d\n  languageName: node\n  linkType: hard\n\n\"base64-arraybuffer@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"base64-arraybuffer@npm:1.0.2\"\n  checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f\n  languageName: node\n  linkType: hard\n\n\"base64-js@npm:^1.1.2, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1\":\n  version: 1.5.1\n  resolution: \"base64-js@npm:1.5.1\"\n  checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf\n  languageName: node\n  linkType: hard\n\n\"binary-extensions@npm:^2.0.0\":\n  version: 2.3.0\n  resolution: \"binary-extensions@npm:2.3.0\"\n  checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5\n  languageName: node\n  linkType: hard\n\n\"brace-expansion@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"brace-expansion@npm:2.0.1\"\n  dependencies:\n    balanced-match: \"npm:^1.0.0\"\n  checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f\n  languageName: node\n  linkType: hard\n\n\"braces@npm:^3.0.3, braces@npm:~3.0.2\":\n  version: 3.0.3\n  resolution: \"braces@npm:3.0.3\"\n  dependencies:\n    fill-range: \"npm:^7.1.1\"\n  checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04\n  languageName: node\n  linkType: hard\n\n\"brotli@npm:^1.3.2\":\n  version: 1.3.3\n  resolution: \"brotli@npm:1.3.3\"\n  dependencies:\n    base64-js: \"npm:^1.1.2\"\n  checksum: 10c0/9d24e24f8b7eabf44af034ed5f7d5530008b835f09a107a84ac060723e86dd43c6aa68958691fe5df524f59473b35f5ce2e0854aa1152c0a254d1010f51bcf22\n  languageName: node\n  linkType: hard\n\n\"browser-or-node@npm:^1.2.1\":\n  version: 1.3.0\n  resolution: \"browser-or-node@npm:1.3.0\"\n  checksum: 10c0/a914790dd6a857149b1bb128a39872a95172f6f1db7824dd014b88cc2f88ce6433013ec83388fff83512fb815e7dc2ea60ddbfe8ff150d4f233092ff0d60ec60\n  languageName: node\n  linkType: hard\n\n\"bson@npm:*\":\n  version: 6.8.0\n  resolution: \"bson@npm:6.8.0\"\n  checksum: 10c0/0503bb2a4ce2e183bd06151bdb983a623cfde05c76fbb5a34e941c594f3a681b52333d61b37c7933b8733b0ba14607b3599d94b46635d90bb96b1e7cec51aa8f\n  languageName: node\n  linkType: hard\n\n\"bson@npm:4.2.0\":\n  version: 4.2.0\n  resolution: \"bson@npm:4.2.0\"\n  dependencies:\n    buffer: \"npm:^5.6.0\"\n  checksum: 10c0/50e852996fd630a1d8d40840f1f775b5e0c821616d0a3710abdd45fcfdaec796b215202ed33d4955f517f3ac2119648ea1c0d96654309bbf1fee1d736cf5e0d8\n  languageName: node\n  linkType: hard\n\n\"buf-compare@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"buf-compare@npm:1.0.1\"\n  checksum: 10c0/ccf1a89efc24bb36213813802b50c20752308859de42a7dca1b035231c72407aa0dbd49383409c818bb93e748117ea7961a8b5366e2597c7db76eb2b028cd64f\n  languageName: node\n  linkType: hard\n\n\"buffer@npm:6.0.3\":\n  version: 6.0.3\n  resolution: \"buffer@npm:6.0.3\"\n  dependencies:\n    base64-js: \"npm:^1.3.1\"\n    ieee754: \"npm:^1.2.1\"\n  checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0\n  languageName: node\n  linkType: hard\n\n\"buffer@npm:^5.0.8, buffer@npm:^5.6.0\":\n  version: 5.7.1\n  resolution: \"buffer@npm:5.7.1\"\n  dependencies:\n    base64-js: \"npm:^1.3.1\"\n    ieee754: \"npm:^1.1.13\"\n  checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e\n  languageName: node\n  linkType: hard\n\n\"bytewise-core@npm:^1.2.2\":\n  version: 1.2.3\n  resolution: \"bytewise-core@npm:1.2.3\"\n  dependencies:\n    typewise-core: \"npm:^1.2\"\n  checksum: 10c0/210239f3048de9463b4ab02968bd0ef7b3c9b330c0329f9df1851fee0819e19fbb0eca8cc235947112dcce942ed58541283ddaefe29515c93a2b7e0820be3f2d\n  languageName: node\n  linkType: hard\n\n\"bytewise@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"bytewise@npm:1.1.0\"\n  dependencies:\n    bytewise-core: \"npm:^1.2.2\"\n    typewise: \"npm:^1.0.3\"\n  checksum: 10c0/bcf994a8b635390dce43b22e97201cc0e3df0089ada4e77cc0bb48ce241efd0c27ca24a9400828cdd288a69a961da0b60c05bf7381b6cb529f048ab22092cc6d\n  languageName: node\n  linkType: hard\n\n\"cacache@npm:^19.0.1\":\n  version: 19.0.1\n  resolution: \"cacache@npm:19.0.1\"\n  dependencies:\n    \"@npmcli/fs\": \"npm:^4.0.0\"\n    fs-minipass: \"npm:^3.0.0\"\n    glob: \"npm:^10.2.2\"\n    lru-cache: \"npm:^10.0.1\"\n    minipass: \"npm:^7.0.3\"\n    minipass-collect: \"npm:^2.0.1\"\n    minipass-flush: \"npm:^1.0.5\"\n    minipass-pipeline: \"npm:^1.2.4\"\n    p-map: \"npm:^7.0.2\"\n    ssri: \"npm:^12.0.0\"\n    tar: \"npm:^7.4.3\"\n    unique-filename: \"npm:^4.0.0\"\n  checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c\n  languageName: node\n  linkType: hard\n\n\"cacheable-lookup@npm:^5.0.3\":\n  version: 5.0.4\n  resolution: \"cacheable-lookup@npm:5.0.4\"\n  checksum: 10c0/a6547fb4954b318aa831cbdd2f7b376824bc784fb1fa67610e4147099e3074726072d9af89f12efb69121415a0e1f2918a8ddd4aafcbcf4e91fbeef4a59cd42c\n  languageName: node\n  linkType: hard\n\n\"cacheable-request@npm:^7.0.2\":\n  version: 7.0.4\n  resolution: \"cacheable-request@npm:7.0.4\"\n  dependencies:\n    clone-response: \"npm:^1.0.2\"\n    get-stream: \"npm:^5.1.0\"\n    http-cache-semantics: \"npm:^4.0.0\"\n    keyv: \"npm:^4.0.0\"\n    lowercase-keys: \"npm:^2.0.0\"\n    normalize-url: \"npm:^6.0.1\"\n    responselike: \"npm:^2.0.0\"\n  checksum: 10c0/0834a7d17ae71a177bc34eab06de112a43f9b5ad05ebe929bec983d890a7d9f2bc5f1aa8bb67ea2b65e07a3bc74bea35fa62dd36dbac52876afe36fdcf83da41\n  languageName: node\n  linkType: hard\n\n\"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7\":\n  version: 1.0.7\n  resolution: \"call-bind@npm:1.0.7\"\n  dependencies:\n    es-define-property: \"npm:^1.0.0\"\n    es-errors: \"npm:^1.3.0\"\n    function-bind: \"npm:^1.1.2\"\n    get-intrinsic: \"npm:^1.2.4\"\n    set-function-length: \"npm:^1.2.1\"\n  checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d\n  languageName: node\n  linkType: hard\n\n\"callsites@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"callsites@npm:3.1.0\"\n  checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301\n  languageName: node\n  linkType: hard\n\n\"camelcase-css@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"camelcase-css@npm:2.0.1\"\n  checksum: 10c0/1a1a3137e8a781e6cbeaeab75634c60ffd8e27850de410c162cce222ea331cd1ba5364e8fb21c95e5ca76f52ac34b81a090925ca00a87221355746d049c6e273\n  languageName: node\n  linkType: hard\n\n\"camelcase-keys@npm:^6.2.2\":\n  version: 6.2.2\n  resolution: \"camelcase-keys@npm:6.2.2\"\n  dependencies:\n    camelcase: \"npm:^5.3.1\"\n    map-obj: \"npm:^4.0.0\"\n    quick-lru: \"npm:^4.0.1\"\n  checksum: 10c0/bf1a28348c0f285c6c6f68fb98a9d088d3c0269fed0cdff3ea680d5a42df8a067b4de374e7a33e619eb9d5266a448fe66c2dd1f8e0c9209ebc348632882a3526\n  languageName: node\n  linkType: hard\n\n\"camelcase@npm:6\":\n  version: 6.3.0\n  resolution: \"camelcase@npm:6.3.0\"\n  checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710\n  languageName: node\n  linkType: hard\n\n\"camelcase@npm:^5.3.1\":\n  version: 5.3.1\n  resolution: \"camelcase@npm:5.3.1\"\n  checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23\n  languageName: node\n  linkType: hard\n\n\"camelize@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"camelize@npm:1.0.1\"\n  checksum: 10c0/4c9ac55efd356d37ac483bad3093758236ab686192751d1c9daa43188cc5a07b09bd431eb7458a4efd9ca22424bba23253e7b353feb35d7c749ba040de2385fb\n  languageName: node\n  linkType: hard\n\n\"ccount@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"ccount@npm:2.0.1\"\n  checksum: 10c0/3939b1664390174484322bc3f45b798462e6c07ee6384cb3d645e0aa2f318502d174845198c1561930e1d431087f74cf1fe291ae9a4722821a9f4ba67e574350\n  languageName: node\n  linkType: hard\n\n\"chalk-template@npm:^0.4.0\":\n  version: 0.4.0\n  resolution: \"chalk-template@npm:0.4.0\"\n  dependencies:\n    chalk: \"npm:^4.1.2\"\n  checksum: 10c0/6a4cb4252966475f0bd3ee1cd8780146e1ba69f445e59c565cab891ac18708c8143515d23e2b0fb7e192574fb7608d429ea5b28f3b7b9507770ad6fccd3467e3\n  languageName: node\n  linkType: hard\n\n\"chalk@npm:^2.4.2\":\n  version: 2.4.2\n  resolution: \"chalk@npm:2.4.2\"\n  dependencies:\n    ansi-styles: \"npm:^3.2.1\"\n    escape-string-regexp: \"npm:^1.0.5\"\n    supports-color: \"npm:^5.3.0\"\n  checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073\n  languageName: node\n  linkType: hard\n\n\"chalk@npm:^4.1.2\":\n  version: 4.1.2\n  resolution: \"chalk@npm:4.1.2\"\n  dependencies:\n    ansi-styles: \"npm:^4.1.0\"\n    supports-color: \"npm:^7.1.0\"\n  checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880\n  languageName: node\n  linkType: hard\n\n\"chalk@npm:^5.3.0\":\n  version: 5.4.1\n  resolution: \"chalk@npm:5.4.1\"\n  checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef\n  languageName: node\n  linkType: hard\n\n\"character-entities-html4@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"character-entities-html4@npm:2.1.0\"\n  checksum: 10c0/fe61b553f083400c20c0b0fd65095df30a0b445d960f3bbf271536ae6c3ba676f39cb7af0b4bf2755812f08ab9b88f2feed68f9aebb73bb153f7a115fe5c6e40\n  languageName: node\n  linkType: hard\n\n\"character-entities-legacy@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"character-entities-legacy@npm:3.0.0\"\n  checksum: 10c0/ec4b430af873661aa754a896a2b55af089b4e938d3d010fad5219299a6b6d32ab175142699ee250640678cd64bdecd6db3c9af0b8759ab7b155d970d84c4c7d1\n  languageName: node\n  linkType: hard\n\n\"character-entities@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"character-entities@npm:2.0.2\"\n  checksum: 10c0/b0c645a45bcc90ff24f0e0140f4875a8436b8ef13b6bcd31ec02cfb2ca502b680362aa95386f7815bdc04b6464d48cf191210b3840d7c04241a149ede591a308\n  languageName: node\n  linkType: hard\n\n\"character-reference-invalid@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"character-reference-invalid@npm:2.0.1\"\n  checksum: 10c0/2ae0dec770cd8659d7e8b0ce24392d83b4c2f0eb4a3395c955dce5528edd4cc030a794cfa06600fcdd700b3f2de2f9b8e40e309c0011c4180e3be64a0b42e6a1\n  languageName: node\n  linkType: hard\n\n\"chokidar@npm:^3.6.0\":\n  version: 3.6.0\n  resolution: \"chokidar@npm:3.6.0\"\n  dependencies:\n    anymatch: \"npm:~3.1.2\"\n    braces: \"npm:~3.0.2\"\n    fsevents: \"npm:~2.3.2\"\n    glob-parent: \"npm:~5.1.2\"\n    is-binary-path: \"npm:~2.1.0\"\n    is-glob: \"npm:~4.0.1\"\n    normalize-path: \"npm:~3.0.0\"\n    readdirp: \"npm:~3.6.0\"\n  dependenciesMeta:\n    fsevents:\n      optional: true\n  checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462\n  languageName: node\n  linkType: hard\n\n\"chownr@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"chownr@npm:3.0.0\"\n  checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10\n  languageName: node\n  linkType: hard\n\n\"chroma-js@npm:2.1.2\":\n  version: 2.1.2\n  resolution: \"chroma-js@npm:2.1.2\"\n  dependencies:\n    cross-env: \"npm:^6.0.3\"\n  checksum: 10c0/f3760059b76240bab7387f335c798bbf55a4edf937534be7bc5c16ecad9b358dcfd891ca4fffa2c34742f45d5c3e96c8927c6a9906a13905da2bfa4c9ad30418\n  languageName: node\n  linkType: hard\n\n\"clamp@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"clamp@npm:1.0.1\"\n  checksum: 10c0/8f95ccbc5d646a98c1d690bce820f3d060a5267242083e8994f70dc504ce441dbc7b8ef13b93819129fc166e7a5b6abd320f109bdba0f49b8dcc794710987c97\n  languageName: node\n  linkType: hard\n\n\"classnames@npm:*, classnames@npm:^2.2.1\":\n  version: 2.5.1\n  resolution: \"classnames@npm:2.5.1\"\n  checksum: 10c0/afff4f77e62cea2d79c39962980bf316bacb0d7c49e13a21adaadb9221e1c6b9d3cdb829d8bb1b23c406f4e740507f37e1dcf506f7e3b7113d17c5bab787aa69\n  languageName: node\n  linkType: hard\n\n\"clone-response@npm:^1.0.2\":\n  version: 1.0.3\n  resolution: \"clone-response@npm:1.0.3\"\n  dependencies:\n    mimic-response: \"npm:^1.0.0\"\n  checksum: 10c0/06a2b611824efb128810708baee3bd169ec9a1bf5976a5258cd7eb3f7db25f00166c6eee5961f075c7e38e194f373d4fdf86b8166ad5b9c7e82bbd2e333a6087\n  languageName: node\n  linkType: hard\n\n\"clsx@npm:^1.0.4, clsx@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"clsx@npm:1.2.1\"\n  checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27\n  languageName: node\n  linkType: hard\n\n\"clsx@npm:^2.0.0\":\n  version: 2.1.1\n  resolution: \"clsx@npm:2.1.1\"\n  checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839\n  languageName: node\n  linkType: hard\n\n\"color-convert@npm:^1.9.0\":\n  version: 1.9.3\n  resolution: \"color-convert@npm:1.9.3\"\n  dependencies:\n    color-name: \"npm:1.1.3\"\n  checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c\n  languageName: node\n  linkType: hard\n\n\"color-convert@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"color-convert@npm:2.0.1\"\n  dependencies:\n    color-name: \"npm:~1.1.4\"\n  checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7\n  languageName: node\n  linkType: hard\n\n\"color-interpolate@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"color-interpolate@npm:1.0.5\"\n  dependencies:\n    clamp: \"npm:^1.0.1\"\n    color-parse: \"npm:^1.2.0\"\n    color-space: \"npm:^1.14.3\"\n    lerp: \"npm:^1.0.3\"\n  checksum: 10c0/d32de079fa2722b8c0eeb79a45a2400639f2dfafd20a141743b3b068b656fbc844685b589a7ab40ebeec2da72c1b3391c2c2bbaf9e07b1720ee7d3300a99dbde\n  languageName: node\n  linkType: hard\n\n\"color-name@npm:1.1.3\":\n  version: 1.1.3\n  resolution: \"color-name@npm:1.1.3\"\n  checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6\n  languageName: node\n  linkType: hard\n\n\"color-name@npm:^1.0.0, color-name@npm:~1.1.4\":\n  version: 1.1.4\n  resolution: \"color-name@npm:1.1.4\"\n  checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95\n  languageName: node\n  linkType: hard\n\n\"color-parse@npm:^1.2.0\":\n  version: 1.4.3\n  resolution: \"color-parse@npm:1.4.3\"\n  dependencies:\n    color-name: \"npm:^1.0.0\"\n  checksum: 10c0/3473e7fb53d643dffc508c4f3161890f405ece9d78cc84277576212346b17ad037d1a0c6beb49e9b55bc11435a8c85b7ff9f94a6b898eb82647d4a46615d23da\n  languageName: node\n  linkType: hard\n\n\"color-space@npm:^1.14.3\":\n  version: 1.16.0\n  resolution: \"color-space@npm:1.16.0\"\n  dependencies:\n    hsluv: \"npm:^0.0.3\"\n    mumath: \"npm:^3.3.4\"\n  checksum: 10c0/93c977671d9b90392477e863c6bf49598256ae63b5196bcdc9f74184a7cd4e125ebf6695e3f34082c1dba8f9eb303eeafd91d8be237064ab7d6a2f6a256014ea\n  languageName: node\n  linkType: hard\n\n\"color-string@npm:^1.9.0\":\n  version: 1.9.1\n  resolution: \"color-string@npm:1.9.1\"\n  dependencies:\n    color-name: \"npm:^1.0.0\"\n    simple-swizzle: \"npm:^0.2.2\"\n  checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404\n  languageName: node\n  linkType: hard\n\n\"color2k@npm:^2.0.3\":\n  version: 2.0.3\n  resolution: \"color2k@npm:2.0.3\"\n  checksum: 10c0/e7c13d212c9d1abb1690e378bbc0a6fb1751e4b02e9a73ba3b2ade9d54da673834597d342791d577d1ce400ec486c7f92c5098f9fa85cd113bcfde57420a2bb9\n  languageName: node\n  linkType: hard\n\n\"color@npm:^4.2.3\":\n  version: 4.2.3\n  resolution: \"color@npm:4.2.3\"\n  dependencies:\n    color-convert: \"npm:^2.0.1\"\n    color-string: \"npm:^1.9.0\"\n  checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118\n  languageName: node\n  linkType: hard\n\n\"colorbrewer@npm:^1.5.0\":\n  version: 1.5.6\n  resolution: \"colorbrewer@npm:1.5.6\"\n  checksum: 10c0/da8045058698e2df21df1b676184d9db6651b5c4c2d4775d24bfb60c761150c4e102b54db4a85f4b3019726b5a16053b040f026ee0214cc0ee6ba8612d8a54cd\n  languageName: node\n  linkType: hard\n\n\"combined-stream@npm:^1.0.8\":\n  version: 1.0.8\n  resolution: \"combined-stream@npm:1.0.8\"\n  dependencies:\n    delayed-stream: \"npm:~1.0.0\"\n  checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5\n  languageName: node\n  linkType: hard\n\n\"comma-separated-tokens@npm:^2.0.0\":\n  version: 2.0.3\n  resolution: \"comma-separated-tokens@npm:2.0.3\"\n  checksum: 10c0/91f90f1aae320f1755d6957ef0b864fe4f54737f3313bd95e0802686ee2ca38bff1dd381964d00ae5db42912dd1f4ae5c2709644e82706ffc6f6842a813cdd67\n  languageName: node\n  linkType: hard\n\n\"command-line-args@npm:^5.2.1\":\n  version: 5.2.1\n  resolution: \"command-line-args@npm:5.2.1\"\n  dependencies:\n    array-back: \"npm:^3.1.0\"\n    find-replace: \"npm:^3.0.0\"\n    lodash.camelcase: \"npm:^4.3.0\"\n    typical: \"npm:^4.0.0\"\n  checksum: 10c0/a4f6a23a1e420441bd1e44dee24efd12d2e49af7efe6e21eb32fca4e843ca3d5501ddebad86a4e9d99aa626dd6dcb64c04a43695388be54e3a803dbc326cc89f\n  languageName: node\n  linkType: hard\n\n\"command-line-args@npm:^6.0.1\":\n  version: 6.0.1\n  resolution: \"command-line-args@npm:6.0.1\"\n  dependencies:\n    array-back: \"npm:^6.2.2\"\n    find-replace: \"npm:^5.0.2\"\n    lodash.camelcase: \"npm:^4.3.0\"\n    typical: \"npm:^7.2.0\"\n  peerDependencies:\n    \"@75lb/nature\": \"*\"\n  peerDependenciesMeta:\n    \"@75lb/nature\":\n      optional: true\n  checksum: 10c0/45556284f60db8d258fa262a0532ce7eadeb87da3475a5c39191b5c208c02a726f84a117c71d91fe3600f764f77bd5269e0c77444fb3eed4525be610802dcef4\n  languageName: node\n  linkType: hard\n\n\"command-line-usage@npm:^7.0.1\":\n  version: 7.0.3\n  resolution: \"command-line-usage@npm:7.0.3\"\n  dependencies:\n    array-back: \"npm:^6.2.2\"\n    chalk-template: \"npm:^0.4.0\"\n    table-layout: \"npm:^4.1.0\"\n    typical: \"npm:^7.1.1\"\n  checksum: 10c0/444a3e3c6fcbdcb5802de0fd2864ea5aef83eeeb3a825fd24846b996503d4b4140e75aeb2939b3430a06407f3acc02b76b3e08dafb3a3092d22fdcced0ecb0b0\n  languageName: node\n  linkType: hard\n\n\"commander@npm:2\":\n  version: 2.20.3\n  resolution: \"commander@npm:2.20.3\"\n  checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288\n  languageName: node\n  linkType: hard\n\n\"commander@npm:^4.0.0\":\n  version: 4.1.1\n  resolution: \"commander@npm:4.1.1\"\n  checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab\n  languageName: node\n  linkType: hard\n\n\"compute-scroll-into-view@npm:^3.0.2\":\n  version: 3.1.1\n  resolution: \"compute-scroll-into-view@npm:3.1.1\"\n  checksum: 10c0/59761ed62304a9599b52ad75d0d6fbf0669ee2ab7dd472fdb0ad9da36628414c014dea7b5810046560180ad30ffec52a953d19297f66a1d4f3aa0999b9d2521d\n  languageName: node\n  linkType: hard\n\n\"console-table-printer@npm:^2.12.1\":\n  version: 2.12.1\n  resolution: \"console-table-printer@npm:2.12.1\"\n  dependencies:\n    simple-wcswidth: \"npm:^1.0.1\"\n  checksum: 10c0/8f28e9c0ae5df77f5d60da3da002ecd95ebe1812b0b9e0a6d2795c81b5121b39774f32506bccf68830a838ca4d8fbb2ab8824e729dba2c5e30cdeb9df4dd5f2b\n  languageName: node\n  linkType: hard\n\n\"copy-to-clipboard@npm:^3.3.1\":\n  version: 3.3.3\n  resolution: \"copy-to-clipboard@npm:3.3.3\"\n  dependencies:\n    toggle-selection: \"npm:^1.0.6\"\n  checksum: 10c0/3ebf5e8ee00601f8c440b83ec08d838e8eabb068c1fae94a9cda6b42f288f7e1b552f3463635f419af44bf7675afc8d0390d30876cf5c2d5d35f86d9c56a3e5f\n  languageName: node\n  linkType: hard\n\n\"core-assert@npm:^0.2.0\":\n  version: 0.2.1\n  resolution: \"core-assert@npm:0.2.1\"\n  dependencies:\n    buf-compare: \"npm:^1.0.0\"\n    is-error: \"npm:^2.2.0\"\n  checksum: 10c0/d4eb0482861a77ac476e01b6eb48f924adeb94003c01c541e020b7cfacddca28fc30be183cdda42231a2e90e66afeeaac46028c9885bfa98faee8d15bdb6e3c6\n  languageName: node\n  linkType: hard\n\n\"core-js@npm:^1.0.0\":\n  version: 1.2.7\n  resolution: \"core-js@npm:1.2.7\"\n  checksum: 10c0/2b2966e40833f522129da4cc979688760654e9b38e32b06517a94c0edf2882d8990c4b1b0087cb2abfbe6219fdb21dc64343fa264a6b6280ec88c3682b8eee66\n  languageName: node\n  linkType: hard\n\n\"create-react-class@npm:^15.5.1\":\n  version: 15.7.0\n  resolution: \"create-react-class@npm:15.7.0\"\n  dependencies:\n    loose-envify: \"npm:^1.3.1\"\n    object-assign: \"npm:^4.1.1\"\n  checksum: 10c0/bce4b46e6d85b424cb50ca8089266c7664fcecfd81abaafb829680fae2e2e60dc6999cac88f5a16a38473ed284859f2328935a42fc5cd1b7cc48888fdd8363c9\n  languageName: node\n  linkType: hard\n\n\"cross-env@npm:^6.0.3\":\n  version: 6.0.3\n  resolution: \"cross-env@npm:6.0.3\"\n  dependencies:\n    cross-spawn: \"npm:^7.0.0\"\n  bin:\n    cross-env: src/bin/cross-env.js\n    cross-env-shell: src/bin/cross-env-shell.js\n  checksum: 10c0/0d176b91c730abb08589431970a59771148f8fbf338959f5e3aa71b866d38ba390fc67f5330306d0a37d7cb74675224d0f23086f291661b944abbf5a00bd7080\n  languageName: node\n  linkType: hard\n\n\"cross-spawn@npm:^7.0.0\":\n  version: 7.0.6\n  resolution: \"cross-spawn@npm:7.0.6\"\n  dependencies:\n    path-key: \"npm:^3.1.0\"\n    shebang-command: \"npm:^2.0.0\"\n    which: \"npm:^2.0.1\"\n  checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1\n  languageName: node\n  linkType: hard\n\n\"css-color-keywords@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"css-color-keywords@npm:1.0.0\"\n  checksum: 10c0/af205a86c68e0051846ed91eb3e30b4517e1904aac040013ff1d742019b3f9369ba5658ba40901dbbc121186fc4bf0e75a814321cc3e3182fbb2feb81c6d9cb7\n  languageName: node\n  linkType: hard\n\n\"css-line-break@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"css-line-break@npm:2.1.0\"\n  dependencies:\n    utrie: \"npm:^1.0.2\"\n  checksum: 10c0/b2222d99d5daf7861ecddc050244fdce296fad74b000dcff6bdfb1eb16dc2ef0b9ffe2c1c965e3239bd05ebe9eadb6d5438a91592fa8648d27a338e827cf9048\n  languageName: node\n  linkType: hard\n\n\"css-to-react-native@npm:3.2.0\":\n  version: 3.2.0\n  resolution: \"css-to-react-native@npm:3.2.0\"\n  dependencies:\n    camelize: \"npm:^1.0.0\"\n    css-color-keywords: \"npm:^1.0.0\"\n    postcss-value-parser: \"npm:^4.0.2\"\n  checksum: 10c0/fde850a511d5d3d7c55a1e9b8ed26b69a8ad4868b3487e36ebfbfc0b96fc34bc977d9cd1d61a289d0c74d3f9a662d8cee297da53d4433bf2e27d6acdff8e1003\n  languageName: node\n  linkType: hard\n\n\"csscolorparser@npm:~1.0.3\":\n  version: 1.0.3\n  resolution: \"csscolorparser@npm:1.0.3\"\n  checksum: 10c0/57b30e1dd3e639fb74d63d3ee5a078ae6d0aaba26bc731fdec1a0f50fd77c2531a1fca2bbe07c18e0569255f92064cda0747e0115955fdb8c037332798ca634f\n  languageName: node\n  linkType: hard\n\n\"cssesc@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"cssesc@npm:3.0.0\"\n  bin:\n    cssesc: bin/cssesc\n  checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7\n  languageName: node\n  linkType: hard\n\n\"csstype@npm:3.1.2\":\n  version: 3.1.2\n  resolution: \"csstype@npm:3.1.2\"\n  checksum: 10c0/32c038af259897c807ac738d9eab16b3d86747c72b09d5c740978e06f067f9b7b1737e1b75e407c7ab1fe1543dc95f20e202b4786aeb1b8d3bdf5d5ce655e6c6\n  languageName: node\n  linkType: hard\n\n\"csstype@npm:^3.0.2\":\n  version: 3.1.3\n  resolution: \"csstype@npm:3.1.3\"\n  checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248\n  languageName: node\n  linkType: hard\n\n\"cubic-hermite-spline@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"cubic-hermite-spline@npm:1.0.1\"\n  checksum: 10c0/34d84de4deb69a30ff23f726cbc1666129c3a3ab8ac98b66bd779fdc060a695e114b4505637f554998c764485fd311cba4c5df3120aa1fb18bc9ccbeafdfced2\n  languageName: node\n  linkType: hard\n\n\"d3-array@npm:1, d3-array@npm:^1.1.1, d3-array@npm:^1.2.0\":\n  version: 1.2.4\n  resolution: \"d3-array@npm:1.2.4\"\n  checksum: 10c0/7ac0ae096838e75d06350381442d84b327e3215d470f26c297851675bd25c47a633d35b04bfaa0397c529f42428d19f3f80bead24e1e866832e064cc6af24f3a\n  languageName: node\n  linkType: hard\n\n\"d3-array@npm:2, d3-array@npm:^2.3.0, d3-array@npm:^2.8.0\":\n  version: 2.12.1\n  resolution: \"d3-array@npm:2.12.1\"\n  dependencies:\n    internmap: \"npm:^1.0.0\"\n  checksum: 10c0/7eca10427a9f113a4ca6a0f7301127cab26043fd5e362631ef5a0edd1c4b2dd70c56ed317566700c31e4a6d88b55f3951aaba192291817f243b730cb2352882e\n  languageName: node\n  linkType: hard\n\n\"d3-array@npm:^3.2.4\":\n  version: 3.2.4\n  resolution: \"d3-array@npm:3.2.4\"\n  dependencies:\n    internmap: \"npm:1 - 2\"\n  checksum: 10c0/08b95e91130f98c1375db0e0af718f4371ccacef7d5d257727fe74f79a24383e79aba280b9ffae655483ffbbad4fd1dec4ade0119d88c4749f388641c8bf8c50\n  languageName: node\n  linkType: hard\n\n\"d3-axis@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"d3-axis@npm:2.1.0\"\n  checksum: 10c0/812558c0a016f8541e4704837b4428060695e2397e00a374caab5ae060a8ea6254bb097a4cfb7e01deff61da8a91c582eda614659ada5fd590da902a397428cc\n  languageName: node\n  linkType: hard\n\n\"d3-brush@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"d3-brush@npm:2.1.0\"\n  dependencies:\n    d3-dispatch: \"npm:1 - 2\"\n    d3-drag: \"npm:2\"\n    d3-interpolate: \"npm:1 - 2\"\n    d3-selection: \"npm:2\"\n    d3-transition: \"npm:2\"\n  checksum: 10c0/f4221f815f8b0fb5af42a2c5baf7cb884067fdddfe5e3d9ae8b603912a47c205e138b8cdcb19a693feac46aa790c0dc9204c893e4b09cfdb944d31bde0b81126\n  languageName: node\n  linkType: hard\n\n\"d3-collection@npm:1, d3-collection@npm:^1.0.3\":\n  version: 1.0.7\n  resolution: \"d3-collection@npm:1.0.7\"\n  checksum: 10c0/7a3c7f733ce4a1a02f46a96c7dd02f8e46a2fa83fc4195682fd33624d6a56fbda6388c86ff5d30799cc768cb63bffcdb216b45c51a8d6e0b23117db9c18bedb3\n  languageName: node\n  linkType: hard\n\n\"d3-color@npm:1 - 2, d3-color@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"d3-color@npm:2.0.0\"\n  checksum: 10c0/5aa58dfb78e3db764373a904eabb643dc024ff6071128a41e86faafa100e0e17a796e06ac3f2662e9937242bb75b8286788629773d76936f11c17bd5fe5e15cd\n  languageName: node\n  linkType: hard\n\n\"d3-color@npm:1, d3-color@npm:^1.0.3\":\n  version: 1.4.1\n  resolution: \"d3-color@npm:1.4.1\"\n  checksum: 10c0/668721dedc1d7e84aa9b3c51ccbaa1facd92ae9303d86ea0d5d0cf589dab892bc4cc7e155696537ed43e8f053d3ad0dde86363e1a789bad800a786f2ede527d7\n  languageName: node\n  linkType: hard\n\n\"d3-contour@npm:^1.1.0\":\n  version: 1.3.2\n  resolution: \"d3-contour@npm:1.3.2\"\n  dependencies:\n    d3-array: \"npm:^1.1.1\"\n  checksum: 10c0/a9539c6ccb656ae469f341373662d473b9f8991d0d70a28dab053610d41bfff1a07934071f199cc384d914b850765850c1e9e1fa497b36bac78a984633ae077b\n  languageName: node\n  linkType: hard\n\n\"d3-dispatch@npm:1 - 2\":\n  version: 2.0.0\n  resolution: \"d3-dispatch@npm:2.0.0\"\n  checksum: 10c0/379f7ce1510f529da00a34016630e92e41c0f6bbffef7b849f4e46733c188c67418df266a9a541cda17572b5286e32fbaf66308fe04dcfe52aa551830825bc93\n  languageName: node\n  linkType: hard\n\n\"d3-drag@npm:2\":\n  version: 2.0.0\n  resolution: \"d3-drag@npm:2.0.0\"\n  dependencies:\n    d3-dispatch: \"npm:1 - 2\"\n    d3-selection: \"npm:2\"\n  checksum: 10c0/a6f2cfea47aa888b56476454554b37befb0d332f326704913142c6a89b295edfc0947a9fb5afdd0c6d0028878b832a975c80df55d009ab7ccf7b59faa99e926c\n  languageName: node\n  linkType: hard\n\n\"d3-dsv@npm:^1.2.0\":\n  version: 1.2.0\n  resolution: \"d3-dsv@npm:1.2.0\"\n  dependencies:\n    commander: \"npm:2\"\n    iconv-lite: \"npm:0.4\"\n    rw: \"npm:1\"\n  bin:\n    csv2json: bin/dsv2json\n    csv2tsv: bin/dsv2dsv\n    dsv2dsv: bin/dsv2dsv\n    dsv2json: bin/dsv2json\n    json2csv: bin/json2dsv\n    json2dsv: bin/json2dsv\n    json2tsv: bin/json2dsv\n    tsv2csv: bin/dsv2dsv\n    tsv2json: bin/dsv2json\n  checksum: 10c0/e1698408b3c583d236f2c5f333d793cff2ac65e5ee11c4b6606becf86bf1d089e6de2be730aa9103c3d175fe3d63942de9a5a808e439ff9de7219149aa58aa37\n  languageName: node\n  linkType: hard\n\n\"d3-dsv@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"d3-dsv@npm:2.0.0\"\n  dependencies:\n    commander: \"npm:2\"\n    iconv-lite: \"npm:0.4\"\n    rw: \"npm:1\"\n  bin:\n    csv2json: bin/dsv2json\n    csv2tsv: bin/dsv2dsv\n    dsv2dsv: bin/dsv2dsv\n    dsv2json: bin/dsv2json\n    json2csv: bin/json2dsv\n    json2dsv: bin/json2dsv\n    json2tsv: bin/json2dsv\n    tsv2csv: bin/dsv2dsv\n    tsv2json: bin/dsv2json\n  checksum: 10c0/b5e4a0945664a941ad37d3c5ee8c8cbb3c99712ff7f36b2420446ebfc3b3909eb3d9ef2682b4a77afa6a6c5f5af1d659e17d6678863d95b244aed479b6c7c5c3\n  languageName: node\n  linkType: hard\n\n\"d3-ease@npm:1 - 2\":\n  version: 2.0.0\n  resolution: \"d3-ease@npm:2.0.0\"\n  checksum: 10c0/4c2e74417739b73f5d185675be0a72b19df1f729b87c457d4b13976f7eef5c1f54739b4ccd71d9730521c959cfac28a56f5f4040dd2baf5729ac7e4adce344f1\n  languageName: node\n  linkType: hard\n\n\"d3-format@npm:1 - 2, d3-format@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"d3-format@npm:2.0.0\"\n  checksum: 10c0/c869af459e20767dc3d9cbb2946ba79cc266ae4fb35d11c50c63fc89ea4ed168c702c7e3db94d503b3618de9609bf3bf2d855ef53e21109ddd7eb9c8f3fcf8a1\n  languageName: node\n  linkType: hard\n\n\"d3-format@npm:1, d3-format@npm:^1.2.0\":\n  version: 1.4.5\n  resolution: \"d3-format@npm:1.4.5\"\n  checksum: 10c0/40800a2fb2182d2d711cea3acc2b8b2b3afdb6f644c51de77feb9b08a6150b14c753933d2fd4ad2f6f45130757b738673372c45b4b820466c560f3b1ec0b3ce8\n  languageName: node\n  linkType: hard\n\n\"d3-geo@npm:1.7.1\":\n  version: 1.7.1\n  resolution: \"d3-geo@npm:1.7.1\"\n  dependencies:\n    d3-array: \"npm:1\"\n  checksum: 10c0/004f858aafb6d23459efc53596dc8a796d21e294e76074da8abaaf95c8796ad2f6b4825b0e9d9afa79eea87e89f851dbb4ba88a6e8f8646c4d278abd28460419\n  languageName: node\n  linkType: hard\n\n\"d3-geo@npm:^1.6.4\":\n  version: 1.12.1\n  resolution: \"d3-geo@npm:1.12.1\"\n  dependencies:\n    d3-array: \"npm:1\"\n  checksum: 10c0/ec46ff8fdd824df2fbcb9c6e7a6e8c778ecaa196ba1a3265fa6b58d32e526d258dda7d6033698f9625189d364d70f5a9b9a7f6f54fdefe8ef677c28e9765b602\n  languageName: node\n  linkType: hard\n\n\"d3-hexbin@npm:^0.2.1, d3-hexbin@npm:^0.2.2\":\n  version: 0.2.2\n  resolution: \"d3-hexbin@npm:0.2.2\"\n  checksum: 10c0/775ed2d2b278836c84de2ce97770e515e74d44cda28bd548fcee90b0fd4026da5abf0cb089d67b7af44e3ad092df5f3da04953172ddf66a6328e2b15d37f70ea\n  languageName: node\n  linkType: hard\n\n\"d3-hierarchy@npm:^1.1.4\":\n  version: 1.1.9\n  resolution: \"d3-hierarchy@npm:1.1.9\"\n  checksum: 10c0/63b0ae0953bda076866b8705f8ea6fa1f67ded7ee99d98b20ef4364ce21868c292c9b45e887fde0f0dba1d0202466b2a87e7d5a6cc6388e759aadc5f055142e0\n  languageName: node\n  linkType: hard\n\n\"d3-interpolate@npm:1 - 2, d3-interpolate@npm:1.2.0 - 2, d3-interpolate@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"d3-interpolate@npm:2.0.1\"\n  dependencies:\n    d3-color: \"npm:1 - 2\"\n  checksum: 10c0/2a5725b0c9c7fef3e8878cf75ad67be851b1472de3dda1f694c441786a1a32e198ddfaa6880d6b280401c1af5b844b61ccdd63d85d1607c1e6bb3a3f0bf532ea\n  languageName: node\n  linkType: hard\n\n\"d3-interpolate@npm:1, d3-interpolate@npm:^1.1.4\":\n  version: 1.4.0\n  resolution: \"d3-interpolate@npm:1.4.0\"\n  dependencies:\n    d3-color: \"npm:1\"\n  checksum: 10c0/33274a06d5969f7cf8477f490dd9bea6cf2f5e89da800e0db17e1b6663dfcc288ebc34ae85f891e83f59f87086e9468db0614281b9fe8ba50162d8a45149ed0f\n  languageName: node\n  linkType: hard\n\n\"d3-path@npm:1\":\n  version: 1.0.9\n  resolution: \"d3-path@npm:1.0.9\"\n  checksum: 10c0/e35e84df5abc18091f585725b8235e1fa97efc287571585427d3a3597301e6c506dea56b11dfb3c06ca5858b3eb7f02c1bf4f6a716aa9eade01c41b92d497eb5\n  languageName: node\n  linkType: hard\n\n\"d3-sankey@npm:^0.7.1\":\n  version: 0.7.1\n  resolution: \"d3-sankey@npm:0.7.1\"\n  dependencies:\n    d3-array: \"npm:1\"\n    d3-collection: \"npm:1\"\n    d3-shape: \"npm:^1.2.0\"\n  checksum: 10c0/eba13f931d3bde6eae99c74b966e63400d5a10ecd3627b22f771e11a08eb3239a5e77c81c378533e574444be62b34b5758d757e13c5e55b4ffb63c69c4e7fc88\n  languageName: node\n  linkType: hard\n\n\"d3-scale-chromatic@npm:2.0.0\":\n  version: 2.0.0\n  resolution: \"d3-scale-chromatic@npm:2.0.0\"\n  dependencies:\n    d3-color: \"npm:1 - 2\"\n    d3-interpolate: \"npm:1 - 2\"\n  checksum: 10c0/93cafe497b00046b1d4e237a8bb8981fbb35ba03070f420bd913872f6e9d2c9628ed8bb8c84c6a6ffe16029359fa74b646c5c5129732ef4186ab059a77da3021\n  languageName: node\n  linkType: hard\n\n\"d3-scale@npm:^1.0.5\":\n  version: 1.0.7\n  resolution: \"d3-scale@npm:1.0.7\"\n  dependencies:\n    d3-array: \"npm:^1.2.0\"\n    d3-collection: \"npm:1\"\n    d3-color: \"npm:1\"\n    d3-format: \"npm:1\"\n    d3-interpolate: \"npm:1\"\n    d3-time: \"npm:1\"\n    d3-time-format: \"npm:2\"\n  checksum: 10c0/95d614fb10fc23cffd42f46cd8bd610a27543dea28f94dec42923a5b85303f8b1d5e8530f7a8f980a47df2d48e1a784dd5cfbd1281f9c646f147f9c8571b4f14\n  languageName: node\n  linkType: hard\n\n\"d3-scale@npm:^3.2.3\":\n  version: 3.3.0\n  resolution: \"d3-scale@npm:3.3.0\"\n  dependencies:\n    d3-array: \"npm:^2.3.0\"\n    d3-format: \"npm:1 - 2\"\n    d3-interpolate: \"npm:1.2.0 - 2\"\n    d3-time: \"npm:^2.1.1\"\n    d3-time-format: \"npm:2 - 3\"\n  checksum: 10c0/cb63c271ec9c5b632c245c63e0d0716b32adcc468247972c552f5be62fb34a17f71e4ac29fd8976704369f4b958bc6789c61a49427efe2160ae979d7843569dc\n  languageName: node\n  linkType: hard\n\n\"d3-selection@npm:2, d3-selection@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"d3-selection@npm:2.0.0\"\n  checksum: 10c0/cd38f5e0baf2011f421e909ff5748235b18510175b7433ddddb04df325a4a03d7db07da09993afb464d5f050174c13cb5d843da902e71e3e8eb5f06c28fc8689\n  languageName: node\n  linkType: hard\n\n\"d3-shape@npm:^1.1.0, d3-shape@npm:^1.2.0\":\n  version: 1.3.7\n  resolution: \"d3-shape@npm:1.3.7\"\n  dependencies:\n    d3-path: \"npm:1\"\n  checksum: 10c0/548057ce59959815decb449f15632b08e2a1bdce208f9a37b5f98ec7629dda986c2356bc7582308405ce68aedae7d47b324df41507404df42afaf352907577ae\n  languageName: node\n  linkType: hard\n\n\"d3-time-format@npm:2\":\n  version: 2.3.0\n  resolution: \"d3-time-format@npm:2.3.0\"\n  dependencies:\n    d3-time: \"npm:1\"\n  checksum: 10c0/4ca7b5b4ac8fcf4b7930a5dc4ee5f34b188ebd0a36029df2acee0f68b5028451c045c63a12e3970da8e9840548588bde3d366ef8ee7fcb107459d4a6ad11d8c5\n  languageName: node\n  linkType: hard\n\n\"d3-time-format@npm:2 - 3\":\n  version: 3.0.0\n  resolution: \"d3-time-format@npm:3.0.0\"\n  dependencies:\n    d3-time: \"npm:1 - 2\"\n  checksum: 10c0/0abe3379f07d1c12ce8930cdddad1223c99cd3e4eac05cf409b5a7953e9ebed56a95a64b0977f63958cfb6101fa4a2a85533a5eae40df84f22c0117dbf5e8982\n  languageName: node\n  linkType: hard\n\n\"d3-time@npm:1\":\n  version: 1.1.0\n  resolution: \"d3-time@npm:1.1.0\"\n  checksum: 10c0/69ab137adff5b22d0fa148ea514a207bd9cd7d2c042ccf34a268f2ef73720b404f0be6e7b56c95650c53caf52080b5254e2a27f0a676f41d1dd22ef8872c8335\n  languageName: node\n  linkType: hard\n\n\"d3-time@npm:1 - 2, d3-time@npm:^2.0.0, d3-time@npm:^2.1.1\":\n  version: 2.1.1\n  resolution: \"d3-time@npm:2.1.1\"\n  dependencies:\n    d3-array: \"npm:2\"\n  checksum: 10c0/4a01770a857bc37d2bafb8f00250e0e6a1fcc8051aea93e5eed168d8ee93e92da508a75ab5e42fc5472aa37e2a83aac68afaf3f12d9167c184ce781faadf5682\n  languageName: node\n  linkType: hard\n\n\"d3-timer@npm:1 - 2\":\n  version: 2.0.0\n  resolution: \"d3-timer@npm:2.0.0\"\n  checksum: 10c0/95f92ed8edbd0844c023de543ebca4d6aba7f9f8b2ecdbc3d61e01e4df5e74ffbce81238a3c4fd63d118bb1d05ca6331522df565fab146a2790e5c6a847f6275\n  languageName: node\n  linkType: hard\n\n\"d3-transition@npm:2\":\n  version: 2.0.0\n  resolution: \"d3-transition@npm:2.0.0\"\n  dependencies:\n    d3-color: \"npm:1 - 2\"\n    d3-dispatch: \"npm:1 - 2\"\n    d3-ease: \"npm:1 - 2\"\n    d3-interpolate: \"npm:1 - 2\"\n    d3-timer: \"npm:1 - 2\"\n  peerDependencies:\n    d3-selection: 2\n  checksum: 10c0/a775365c90acfcf5745fb18146ce0a5cb71bfdf69433f4d12cc286da0f88b3b1fbdd6782ccfe8e60de95133107cd8a47a4736fa19e94ffb08f1000e6cd9ebdb6\n  languageName: node\n  linkType: hard\n\n\"d3-voronoi@npm:^1.1.2\":\n  version: 1.1.4\n  resolution: \"d3-voronoi@npm:1.1.4\"\n  checksum: 10c0/9fd4689323a8eed547dde44e9cae0b3e6a7333447cf37b069e278b8e899fc16e0251d099fe22d751c900a3a0b7a610a16e747224219538758afaf4561749047a\n  languageName: node\n  linkType: hard\n\n\"data-view-buffer@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"data-view-buffer@npm:1.0.1\"\n  dependencies:\n    call-bind: \"npm:^1.0.6\"\n    es-errors: \"npm:^1.3.0\"\n    is-data-view: \"npm:^1.0.1\"\n  checksum: 10c0/8984119e59dbed906a11fcfb417d7d861936f16697a0e7216fe2c6c810f6b5e8f4a5281e73f2c28e8e9259027190ac4a33e2a65fdd7fa86ac06b76e838918583\n  languageName: node\n  linkType: hard\n\n\"data-view-byte-length@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"data-view-byte-length@npm:1.0.1\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    es-errors: \"npm:^1.3.0\"\n    is-data-view: \"npm:^1.0.1\"\n  checksum: 10c0/b7d9e48a0cf5aefed9ab7d123559917b2d7e0d65531f43b2fd95b9d3a6b46042dd3fca597c42bba384e66b70d7ad66ff23932f8367b241f53d93af42cfe04ec2\n  languageName: node\n  linkType: hard\n\n\"data-view-byte-offset@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"data-view-byte-offset@npm:1.0.0\"\n  dependencies:\n    call-bind: \"npm:^1.0.6\"\n    es-errors: \"npm:^1.3.0\"\n    is-data-view: \"npm:^1.0.1\"\n  checksum: 10c0/21b0d2e53fd6e20cc4257c873bf6d36d77bd6185624b84076c0a1ddaa757b49aaf076254006341d35568e89f52eecd1ccb1a502cfb620f2beca04f48a6a62a8f\n  languageName: node\n  linkType: hard\n\n\"debug@npm:4, debug@npm:^4.2.0, debug@npm:^4.3.4\":\n  version: 4.4.0\n  resolution: \"debug@npm:4.4.0\"\n  dependencies:\n    ms: \"npm:^2.1.3\"\n  peerDependenciesMeta:\n    supports-color:\n      optional: true\n  checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de\n  languageName: node\n  linkType: hard\n\n\"debug@npm:^4.0.0\":\n  version: 4.3.6\n  resolution: \"debug@npm:4.3.6\"\n  dependencies:\n    ms: \"npm:2.1.2\"\n  peerDependenciesMeta:\n    supports-color:\n      optional: true\n  checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285\n  languageName: node\n  linkType: hard\n\n\"decamelize-keys@npm:^1.1.0\":\n  version: 1.1.1\n  resolution: \"decamelize-keys@npm:1.1.1\"\n  dependencies:\n    decamelize: \"npm:^1.1.0\"\n    map-obj: \"npm:^1.0.0\"\n  checksum: 10c0/4ca385933127437658338c65fb9aead5f21b28d3dd3ccd7956eb29aab0953b5d3c047fbc207111672220c71ecf7a4d34f36c92851b7bbde6fca1a02c541bdd7d\n  languageName: node\n  linkType: hard\n\n\"decamelize@npm:1.2.0, decamelize@npm:^1.1.0, decamelize@npm:^1.2.0\":\n  version: 1.2.0\n  resolution: \"decamelize@npm:1.2.0\"\n  checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2\n  languageName: node\n  linkType: hard\n\n\"decimal.js@npm:10, decimal.js@npm:^10.2.0\":\n  version: 10.4.3\n  resolution: \"decimal.js@npm:10.4.3\"\n  checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee\n  languageName: node\n  linkType: hard\n\n\"decode-named-character-reference@npm:^1.0.0\":\n  version: 1.1.0\n  resolution: \"decode-named-character-reference@npm:1.1.0\"\n  dependencies:\n    character-entities: \"npm:^2.0.0\"\n  checksum: 10c0/359c76305b47e67660ec096c5cd3f65972ed75b8a53a40435a7a967cfab3e9516e64b443cbe0c7edcf5ab77f65a6924f12fb1872b1e09e2f044f28f4fd10996a\n  languageName: node\n  linkType: hard\n\n\"decompress-response@npm:^6.0.0\":\n  version: 6.0.0\n  resolution: \"decompress-response@npm:6.0.0\"\n  dependencies:\n    mimic-response: \"npm:^3.1.0\"\n  checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e\n  languageName: node\n  linkType: hard\n\n\"deep-diff@npm:^0.3.5\":\n  version: 0.3.8\n  resolution: \"deep-diff@npm:0.3.8\"\n  checksum: 10c0/dbb10937ccff21b1ad1ee5e64ad926643a7000cb9837bc2165bacfddf569788c78b8bfd74401c32121af3fb1cb1092b7447319d9f01900676473a1b840c08ade\n  languageName: node\n  linkType: hard\n\n\"deep-equal@npm:^1.0.1\":\n  version: 1.1.2\n  resolution: \"deep-equal@npm:1.1.2\"\n  dependencies:\n    is-arguments: \"npm:^1.1.1\"\n    is-date-object: \"npm:^1.0.5\"\n    is-regex: \"npm:^1.1.4\"\n    object-is: \"npm:^1.1.5\"\n    object-keys: \"npm:^1.1.1\"\n    regexp.prototype.flags: \"npm:^1.5.1\"\n  checksum: 10c0/cd85d822d18e9b3e1532d0f6ba412d229aa9d22881d70da161674428ae96e47925191296f7cda29306bac252889007da40ed8449363bd1c96c708acb82068a00\n  languageName: node\n  linkType: hard\n\n\"deep-strict-equal@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"deep-strict-equal@npm:0.2.0\"\n  dependencies:\n    core-assert: \"npm:^0.2.0\"\n  checksum: 10c0/774bffaa22c625e54452de4796183099ad95f03f6a4ead2dac7e61d884fddd7e56d56e07d89d31df5914a7172458377fedc33e84579a47301130c41da6fe06b1\n  languageName: node\n  linkType: hard\n\n\"deepmerge@npm:4.3.1, deepmerge@npm:^4.2.2\":\n  version: 4.3.1\n  resolution: \"deepmerge@npm:4.3.1\"\n  checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044\n  languageName: node\n  linkType: hard\n\n\"defer-to-connect@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"defer-to-connect@npm:2.0.1\"\n  checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782\n  languageName: node\n  linkType: hard\n\n\"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4\":\n  version: 1.1.4\n  resolution: \"define-data-property@npm:1.1.4\"\n  dependencies:\n    es-define-property: \"npm:^1.0.0\"\n    es-errors: \"npm:^1.3.0\"\n    gopd: \"npm:^1.0.1\"\n  checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37\n  languageName: node\n  linkType: hard\n\n\"define-properties@npm:^1.2.0, define-properties@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"define-properties@npm:1.2.1\"\n  dependencies:\n    define-data-property: \"npm:^1.0.1\"\n    has-property-descriptors: \"npm:^1.0.0\"\n    object-keys: \"npm:^1.1.1\"\n  checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3\n  languageName: node\n  linkType: hard\n\n\"delayed-stream@npm:~1.0.0\":\n  version: 1.0.0\n  resolution: \"delayed-stream@npm:1.0.0\"\n  checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19\n  languageName: node\n  linkType: hard\n\n\"dequal@npm:^2.0.0, dequal@npm:^2.0.3\":\n  version: 2.0.3\n  resolution: \"dequal@npm:2.0.3\"\n  checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888\n  languageName: node\n  linkType: hard\n\n\"detect-element-overflow@npm:^1.4.0\":\n  version: 1.4.2\n  resolution: \"detect-element-overflow@npm:1.4.2\"\n  checksum: 10c0/dcc5f6d89c31f6035202cce64b96093338e777752b62c401c672e84908097ef01f833af5864e1c86c6440e4f843658f006d1084fb9090983ea5c438c8ad4a180\n  languageName: node\n  linkType: hard\n\n\"devlop@npm:^1.0.0, devlop@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"devlop@npm:1.1.0\"\n  dependencies:\n    dequal: \"npm:^2.0.0\"\n  checksum: 10c0/e0928ab8f94c59417a2b8389c45c55ce0a02d9ac7fd74ef62d01ba48060129e1d594501b77de01f3eeafc7cb00773819b0df74d96251cf20b31c5b3071f45c0e\n  languageName: node\n  linkType: hard\n\n\"didyoumean@npm:^1.2.2\":\n  version: 1.2.2\n  resolution: \"didyoumean@npm:1.2.2\"\n  checksum: 10c0/95d0b53d23b851aacff56dfadb7ecfedce49da4232233baecfeecb7710248c4aa03f0aa8995062f0acafaf925adf8536bd7044a2e68316fd7d411477599bc27b\n  languageName: node\n  linkType: hard\n\n\"diff-match-patch@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"diff-match-patch@npm:1.0.5\"\n  checksum: 10c0/142b6fad627b9ef309d11bd935e82b84c814165a02500f046e2773f4ea894d10ed3017ac20454900d79d4a0322079f5b713cf0986aaf15fce0ec4a2479980c86\n  languageName: node\n  linkType: hard\n\n\"dlv@npm:^1.1.3\":\n  version: 1.1.3\n  resolution: \"dlv@npm:1.1.3\"\n  checksum: 10c0/03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42\n  languageName: node\n  linkType: hard\n\n\"dom-helpers@npm:^5.1.3\":\n  version: 5.2.1\n  resolution: \"dom-helpers@npm:5.2.1\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.8.7\"\n    csstype: \"npm:^3.0.2\"\n  checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c\n  languageName: node\n  linkType: hard\n\n\"dom-walk@npm:^0.1.0\":\n  version: 0.1.2\n  resolution: \"dom-walk@npm:0.1.2\"\n  checksum: 10c0/4d2ad9062a9423d890f8577aa202b597a6b85f9489bdde656b9443901b8b322b289655c3affefc58ec2e41931e0828dfee0a1d2db6829a607d76def5901fc5a9\n  languageName: node\n  linkType: hard\n\n\"dotenv-expand@npm:^10.0.0\":\n  version: 10.0.0\n  resolution: \"dotenv-expand@npm:10.0.0\"\n  checksum: 10c0/298f5018e29cfdcb0b5f463ba8e8627749103fbcf6cf81c561119115754ed582deee37b49dfc7253028aaba875ab7aea5fa90e5dac88e511d009ab0e6677924e\n  languageName: node\n  linkType: hard\n\n\"dotenv@npm:^16.4.5\":\n  version: 16.4.7\n  resolution: \"dotenv@npm:16.4.7\"\n  checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462\n  languageName: node\n  linkType: hard\n\n\"draco3d@npm:1.5.5\":\n  version: 1.5.5\n  resolution: \"draco3d@npm:1.5.5\"\n  checksum: 10c0/bbf5d91ee7fb24aba80d75030590141ac14cf2865ad9d0a5127bdf73e89ce81cce15f28bb262940fd997a4dbae925ea52895a65db2e1941d52be293853fd3df0\n  languageName: node\n  linkType: hard\n\n\"draco3d@npm:1.5.7\":\n  version: 1.5.7\n  resolution: \"draco3d@npm:1.5.7\"\n  checksum: 10c0/4419509bb93c31560a22a1a54e1c394a5b5017cab39941120c75151d941c11dec05925abf31f597ac2694c570b78c04f82aa3d69e5311f8f8e71fc8b9d92d12f\n  languageName: node\n  linkType: hard\n\n\"dropbox@npm:^4.0.12\":\n  version: 4.0.30\n  resolution: \"dropbox@npm:4.0.30\"\n  dependencies:\n    buffer: \"npm:^5.0.8\"\n    moment: \"npm:^2.19.3\"\n  checksum: 10c0/590c859b4861490158dcc594e62e72e0907106a2eefaa845a8cd9a65595ed876c93eb7c40b672d99d1f5afa66cff2632110a3508f3656a57ea882d04a3648020\n  languageName: node\n  linkType: hard\n\n\"earcut@npm:^2.0.6, earcut@npm:^2.2.2, earcut@npm:^2.2.4\":\n  version: 2.2.4\n  resolution: \"earcut@npm:2.2.4\"\n  checksum: 10c0/01ca51830edd2787819f904ae580087d37351f6048b4565e7add4b3da8a86b7bc19262ab2aa7fdc64129ab03af2d9cec8cccee4d230c82275f97ef285c79aafb\n  languageName: node\n  linkType: hard\n\n\"eastasianwidth@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"eastasianwidth@npm:0.2.0\"\n  checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39\n  languageName: node\n  linkType: hard\n\n\"echarts-for-react@npm:^3.0.2\":\n  version: 3.0.2\n  resolution: \"echarts-for-react@npm:3.0.2\"\n  dependencies:\n    fast-deep-equal: \"npm:^3.1.3\"\n    size-sensor: \"npm:^1.0.1\"\n  peerDependencies:\n    echarts: ^3.0.0 || ^4.0.0 || ^5.0.0\n    react: ^15.0.0 || >=16.0.0\n  checksum: 10c0/a67d76d2cea6fdb8efa9e305fa998f4933053a647df13ea58b4ea0c7892a72e9afb7523a7382d98b541983e5e073adafc2c4103d7dcb88d86757c4582d45e400\n  languageName: node\n  linkType: hard\n\n\"echarts@npm:^5.5.1\":\n  version: 5.6.0\n  resolution: \"echarts@npm:5.6.0\"\n  dependencies:\n    tslib: \"npm:2.3.0\"\n    zrender: \"npm:5.6.1\"\n  checksum: 10c0/6d6a2ee88534d1ff0433e935c542237b9896de1c94959f47ebc7e0e9da26f59bf11c91ed6fc135b62ad2786c779ee12bc536fa481e60532dad5b6a2f5167e9ea\n  languageName: node\n  linkType: hard\n\n\"emoji-regex@npm:^8.0.0\":\n  version: 8.0.0\n  resolution: \"emoji-regex@npm:8.0.0\"\n  checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010\n  languageName: node\n  linkType: hard\n\n\"emoji-regex@npm:^9.2.2\":\n  version: 9.2.2\n  resolution: \"emoji-regex@npm:9.2.2\"\n  checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639\n  languageName: node\n  linkType: hard\n\n\"encoding@npm:^0.1.11, encoding@npm:^0.1.13\":\n  version: 0.1.13\n  resolution: \"encoding@npm:0.1.13\"\n  dependencies:\n    iconv-lite: \"npm:^0.6.2\"\n  checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039\n  languageName: node\n  linkType: hard\n\n\"end-of-stream@npm:^1.1.0\":\n  version: 1.4.4\n  resolution: \"end-of-stream@npm:1.4.4\"\n  dependencies:\n    once: \"npm:^1.4.0\"\n  checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975\n  languageName: node\n  linkType: hard\n\n\"env-paths@npm:^2.2.0\":\n  version: 2.2.1\n  resolution: \"env-paths@npm:2.2.1\"\n  checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4\n  languageName: node\n  linkType: hard\n\n\"err-code@npm:^2.0.2\":\n  version: 2.0.3\n  resolution: \"err-code@npm:2.0.3\"\n  checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66\n  languageName: node\n  linkType: hard\n\n\"error-ex@npm:^1.3.1\":\n  version: 1.3.2\n  resolution: \"error-ex@npm:1.3.2\"\n  dependencies:\n    is-arrayish: \"npm:^0.2.1\"\n  checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce\n  languageName: node\n  linkType: hard\n\n\"es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0\":\n  version: 1.23.3\n  resolution: \"es-abstract@npm:1.23.3\"\n  dependencies:\n    array-buffer-byte-length: \"npm:^1.0.1\"\n    arraybuffer.prototype.slice: \"npm:^1.0.3\"\n    available-typed-arrays: \"npm:^1.0.7\"\n    call-bind: \"npm:^1.0.7\"\n    data-view-buffer: \"npm:^1.0.1\"\n    data-view-byte-length: \"npm:^1.0.1\"\n    data-view-byte-offset: \"npm:^1.0.0\"\n    es-define-property: \"npm:^1.0.0\"\n    es-errors: \"npm:^1.3.0\"\n    es-object-atoms: \"npm:^1.0.0\"\n    es-set-tostringtag: \"npm:^2.0.3\"\n    es-to-primitive: \"npm:^1.2.1\"\n    function.prototype.name: \"npm:^1.1.6\"\n    get-intrinsic: \"npm:^1.2.4\"\n    get-symbol-description: \"npm:^1.0.2\"\n    globalthis: \"npm:^1.0.3\"\n    gopd: \"npm:^1.0.1\"\n    has-property-descriptors: \"npm:^1.0.2\"\n    has-proto: \"npm:^1.0.3\"\n    has-symbols: \"npm:^1.0.3\"\n    hasown: \"npm:^2.0.2\"\n    internal-slot: \"npm:^1.0.7\"\n    is-array-buffer: \"npm:^3.0.4\"\n    is-callable: \"npm:^1.2.7\"\n    is-data-view: \"npm:^1.0.1\"\n    is-negative-zero: \"npm:^2.0.3\"\n    is-regex: \"npm:^1.1.4\"\n    is-shared-array-buffer: \"npm:^1.0.3\"\n    is-string: \"npm:^1.0.7\"\n    is-typed-array: \"npm:^1.1.13\"\n    is-weakref: \"npm:^1.0.2\"\n    object-inspect: \"npm:^1.13.1\"\n    object-keys: \"npm:^1.1.1\"\n    object.assign: \"npm:^4.1.5\"\n    regexp.prototype.flags: \"npm:^1.5.2\"\n    safe-array-concat: \"npm:^1.1.2\"\n    safe-regex-test: \"npm:^1.0.3\"\n    string.prototype.trim: \"npm:^1.2.9\"\n    string.prototype.trimend: \"npm:^1.0.8\"\n    string.prototype.trimstart: \"npm:^1.0.8\"\n    typed-array-buffer: \"npm:^1.0.2\"\n    typed-array-byte-length: \"npm:^1.0.1\"\n    typed-array-byte-offset: \"npm:^1.0.2\"\n    typed-array-length: \"npm:^1.0.6\"\n    unbox-primitive: \"npm:^1.0.2\"\n    which-typed-array: \"npm:^1.1.15\"\n  checksum: 10c0/d27e9afafb225c6924bee9971a7f25f20c314f2d6cb93a63cada4ac11dcf42040896a6c22e5fb8f2a10767055ed4ddf400be3b1eb12297d281726de470b75666\n  languageName: node\n  linkType: hard\n\n\"es-define-property@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"es-define-property@npm:1.0.0\"\n  dependencies:\n    get-intrinsic: \"npm:^1.2.4\"\n  checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4\n  languageName: node\n  linkType: hard\n\n\"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0\":\n  version: 1.3.0\n  resolution: \"es-errors@npm:1.3.0\"\n  checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85\n  languageName: node\n  linkType: hard\n\n\"es-object-atoms@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"es-object-atoms@npm:1.0.0\"\n  dependencies:\n    es-errors: \"npm:^1.3.0\"\n  checksum: 10c0/1fed3d102eb27ab8d983337bb7c8b159dd2a1e63ff833ec54eea1311c96d5b08223b433060ba240541ca8adba9eee6b0a60cdbf2f80634b784febc9cc8b687b4\n  languageName: node\n  linkType: hard\n\n\"es-set-tostringtag@npm:^2.0.3\":\n  version: 2.0.3\n  resolution: \"es-set-tostringtag@npm:2.0.3\"\n  dependencies:\n    get-intrinsic: \"npm:^1.2.4\"\n    has-tostringtag: \"npm:^1.0.2\"\n    hasown: \"npm:^2.0.1\"\n  checksum: 10c0/f22aff1585eb33569c326323f0b0d175844a1f11618b86e193b386f8be0ea9474cfbe46df39c45d959f7aa8f6c06985dc51dd6bce5401645ec5a74c4ceaa836a\n  languageName: node\n  linkType: hard\n\n\"es-to-primitive@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"es-to-primitive@npm:1.2.1\"\n  dependencies:\n    is-callable: \"npm:^1.1.4\"\n    is-date-object: \"npm:^1.0.1\"\n    is-symbol: \"npm:^1.0.2\"\n  checksum: 10c0/0886572b8dc075cb10e50c0af62a03d03a68e1e69c388bd4f10c0649ee41b1fbb24840a1b7e590b393011b5cdbe0144b776da316762653685432df37d6de60f1\n  languageName: node\n  linkType: hard\n\n\"esbuild-plugin-replace@npm:^1.4.0\":\n  version: 1.4.0\n  resolution: \"esbuild-plugin-replace@npm:1.4.0\"\n  dependencies:\n    magic-string: \"npm:^0.25.7\"\n  checksum: 10c0/9574557ca6a63bb31818df3383618d8c12c32f142145cbdd1a80db5d7b88ed032e989708e302651a74bc9fa40bcf441c1aa6cbbce0fce5c511a5843577906f6a\n  languageName: node\n  linkType: hard\n\n\"esbuild@npm:^0.25.0\":\n  version: 0.25.0\n  resolution: \"esbuild@npm:0.25.0\"\n  dependencies:\n    \"@esbuild/aix-ppc64\": \"npm:0.25.0\"\n    \"@esbuild/android-arm\": \"npm:0.25.0\"\n    \"@esbuild/android-arm64\": \"npm:0.25.0\"\n    \"@esbuild/android-x64\": \"npm:0.25.0\"\n    \"@esbuild/darwin-arm64\": \"npm:0.25.0\"\n    \"@esbuild/darwin-x64\": \"npm:0.25.0\"\n    \"@esbuild/freebsd-arm64\": \"npm:0.25.0\"\n    \"@esbuild/freebsd-x64\": \"npm:0.25.0\"\n    \"@esbuild/linux-arm\": \"npm:0.25.0\"\n    \"@esbuild/linux-arm64\": \"npm:0.25.0\"\n    \"@esbuild/linux-ia32\": \"npm:0.25.0\"\n    \"@esbuild/linux-loong64\": \"npm:0.25.0\"\n    \"@esbuild/linux-mips64el\": \"npm:0.25.0\"\n    \"@esbuild/linux-ppc64\": \"npm:0.25.0\"\n    \"@esbuild/linux-riscv64\": \"npm:0.25.0\"\n    \"@esbuild/linux-s390x\": \"npm:0.25.0\"\n    \"@esbuild/linux-x64\": \"npm:0.25.0\"\n    \"@esbuild/netbsd-arm64\": \"npm:0.25.0\"\n    \"@esbuild/netbsd-x64\": \"npm:0.25.0\"\n    \"@esbuild/openbsd-arm64\": \"npm:0.25.0\"\n    \"@esbuild/openbsd-x64\": \"npm:0.25.0\"\n    \"@esbuild/sunos-x64\": \"npm:0.25.0\"\n    \"@esbuild/win32-arm64\": \"npm:0.25.0\"\n    \"@esbuild/win32-ia32\": \"npm:0.25.0\"\n    \"@esbuild/win32-x64\": \"npm:0.25.0\"\n  dependenciesMeta:\n    \"@esbuild/aix-ppc64\":\n      optional: true\n    \"@esbuild/android-arm\":\n      optional: true\n    \"@esbuild/android-arm64\":\n      optional: true\n    \"@esbuild/android-x64\":\n      optional: true\n    \"@esbuild/darwin-arm64\":\n      optional: true\n    \"@esbuild/darwin-x64\":\n      optional: true\n    \"@esbuild/freebsd-arm64\":\n      optional: true\n    \"@esbuild/freebsd-x64\":\n      optional: true\n    \"@esbuild/linux-arm\":\n      optional: true\n    \"@esbuild/linux-arm64\":\n      optional: true\n    \"@esbuild/linux-ia32\":\n      optional: true\n    \"@esbuild/linux-loong64\":\n      optional: true\n    \"@esbuild/linux-mips64el\":\n      optional: true\n    \"@esbuild/linux-ppc64\":\n      optional: true\n    \"@esbuild/linux-riscv64\":\n      optional: true\n    \"@esbuild/linux-s390x\":\n      optional: true\n    \"@esbuild/linux-x64\":\n      optional: true\n    \"@esbuild/netbsd-arm64\":\n      optional: true\n    \"@esbuild/netbsd-x64\":\n      optional: true\n    \"@esbuild/openbsd-arm64\":\n      optional: true\n    \"@esbuild/openbsd-x64\":\n      optional: true\n    \"@esbuild/sunos-x64\":\n      optional: true\n    \"@esbuild/win32-arm64\":\n      optional: true\n    \"@esbuild/win32-ia32\":\n      optional: true\n    \"@esbuild/win32-x64\":\n      optional: true\n  bin:\n    esbuild: bin/esbuild\n  checksum: 10c0/5767b72da46da3cfec51661647ec850ddbf8a8d0662771139f10ef0692a8831396a0004b2be7966cecdb08264fb16bdc16290dcecd92396fac5f12d722fa013d\n  languageName: node\n  linkType: hard\n\n\"escape-string-regexp@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"escape-string-regexp@npm:1.0.5\"\n  checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371\n  languageName: node\n  linkType: hard\n\n\"escape-string-regexp@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"escape-string-regexp@npm:5.0.0\"\n  checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95\n  languageName: node\n  linkType: hard\n\n\"esm@npm:^3.2.25\":\n  version: 3.2.25\n  resolution: \"esm@npm:3.2.25\"\n  checksum: 10c0/8e60e8075506a7ce28681c30c8f54623fe18a251c364cd481d86719fc77f58aa055b293d80632d9686d5408aaf865ffa434897dc9fd9153c8b3f469fad23f094\n  languageName: node\n  linkType: hard\n\n\"estree-util-is-identifier-name@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"estree-util-is-identifier-name@npm:3.0.0\"\n  checksum: 10c0/d1881c6ed14bd588ebd508fc90bf2a541811dbb9ca04dec2f39d27dcaa635f85b5ed9bbbe7fc6fb1ddfca68744a5f7c70456b4b7108b6c4c52780631cc787c5b\n  languageName: node\n  linkType: hard\n\n\"event-target-shim@npm:^5.0.0\":\n  version: 5.0.1\n  resolution: \"event-target-shim@npm:5.0.1\"\n  checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b\n  languageName: node\n  linkType: hard\n\n\"eventemitter3@npm:^3.1.0\":\n  version: 3.1.2\n  resolution: \"eventemitter3@npm:3.1.2\"\n  checksum: 10c0/c67262eccbf85848b7cc6d4abb6c6e34155e15686db2a01c57669fd0d44441a574a19d44d25948b442929e065774cbe5003d8e77eed47674fbf876ac77887793\n  languageName: node\n  linkType: hard\n\n\"eventemitter3@npm:^4.0.4\":\n  version: 4.0.7\n  resolution: \"eventemitter3@npm:4.0.7\"\n  checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b\n  languageName: node\n  linkType: hard\n\n\"eventsource-parser@npm:^3.0.0\":\n  version: 3.0.6\n  resolution: \"eventsource-parser@npm:3.0.6\"\n  checksum: 10c0/70b8ccec7dac767ef2eca43f355e0979e70415701691382a042a2df8d6a68da6c2fca35363669821f3da876d29c02abe9b232964637c1b6635c940df05ada78a\n  languageName: node\n  linkType: hard\n\n\"exenv@npm:^1.2.0, exenv@npm:^1.2.2\":\n  version: 1.2.2\n  resolution: \"exenv@npm:1.2.2\"\n  checksum: 10c0/4e96b355a6b9b9547237288ca779dd673b2e698458b409e88b50df09feb7c85ef94c07354b6b87bc3ed0193a94009a6f7a3c71956da12f45911c0d0f5aa3caa0\n  languageName: node\n  linkType: hard\n\n\"exponential-backoff@npm:^3.1.1\":\n  version: 3.1.1\n  resolution: \"exponential-backoff@npm:3.1.1\"\n  checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579\n  languageName: node\n  linkType: hard\n\n\"extend-shallow@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"extend-shallow@npm:2.0.1\"\n  dependencies:\n    is-extendable: \"npm:^0.1.0\"\n  checksum: 10c0/ee1cb0a18c9faddb42d791b2d64867bd6cfd0f3affb711782eb6e894dd193e2934a7f529426aac7c8ddb31ac5d38000a00aa2caf08aa3dfc3e1c8ff6ba340bd9\n  languageName: node\n  linkType: hard\n\n\"extend-shallow@npm:^3.0.0\":\n  version: 3.0.2\n  resolution: \"extend-shallow@npm:3.0.2\"\n  dependencies:\n    assign-symbols: \"npm:^1.0.0\"\n    is-extendable: \"npm:^1.0.1\"\n  checksum: 10c0/f39581b8f98e3ad94995e33214fff725b0297cf09f2725b6f624551cfb71e0764accfd0af80becc0182af5014d2a57b31b85ec999f9eb8a6c45af81752feac9a\n  languageName: node\n  linkType: hard\n\n\"extend@npm:^3.0.0\":\n  version: 3.0.2\n  resolution: \"extend@npm:3.0.2\"\n  checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9\n  languageName: node\n  linkType: hard\n\n\"fast-deep-equal@npm:^3.1.3\":\n  version: 3.1.3\n  resolution: \"fast-deep-equal@npm:3.1.3\"\n  checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0\n  languageName: node\n  linkType: hard\n\n\"fast-glob@npm:^3.3.2\":\n  version: 3.3.3\n  resolution: \"fast-glob@npm:3.3.3\"\n  dependencies:\n    \"@nodelib/fs.stat\": \"npm:^2.0.2\"\n    \"@nodelib/fs.walk\": \"npm:^1.2.3\"\n    glob-parent: \"npm:^5.1.2\"\n    merge2: \"npm:^1.3.0\"\n    micromatch: \"npm:^4.0.8\"\n  checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe\n  languageName: node\n  linkType: hard\n\n\"fast-xml-parser@npm:^4.2.5\":\n  version: 4.5.3\n  resolution: \"fast-xml-parser@npm:4.5.3\"\n  dependencies:\n    strnum: \"npm:^1.1.1\"\n  bin:\n    fxparser: src/cli/cli.js\n  checksum: 10c0/bf9ccadacfadc95f6e3f0e7882a380a7f219cf0a6f96575149f02cb62bf44c3b7f0daee75b8ff3847bcfd7fbcb201e402c71045936c265cf6d94b141ec4e9327\n  languageName: node\n  linkType: hard\n\n\"fastq@npm:^1.6.0\":\n  version: 1.18.0\n  resolution: \"fastq@npm:1.18.0\"\n  dependencies:\n    reusify: \"npm:^1.0.4\"\n  checksum: 10c0/7be87ecc41762adbddf558d24182f50a4b1a3ef3ee807d33b7623da7aee5faecdcc94fce5aa13fe91df93e269f383232bbcdb2dc5338cd1826503d6063221f36\n  languageName: node\n  linkType: hard\n\n\"fbjs@npm:^0.8.16\":\n  version: 0.8.18\n  resolution: \"fbjs@npm:0.8.18\"\n  dependencies:\n    core-js: \"npm:^1.0.0\"\n    isomorphic-fetch: \"npm:^2.1.1\"\n    loose-envify: \"npm:^1.0.0\"\n    object-assign: \"npm:^4.1.0\"\n    promise: \"npm:^7.1.1\"\n    setimmediate: \"npm:^1.0.5\"\n    ua-parser-js: \"npm:^0.7.30\"\n  checksum: 10c0/a7e1c64c349cde000e5d94ce0289c59b725a95fbdacc22529155c4dacea1dde37a4dae7e16f0f6602dca566f15978b42acd7ee973b620eaac612b5228687ffe0\n  languageName: node\n  linkType: hard\n\n\"fflate@npm:0.7.4\":\n  version: 0.7.4\n  resolution: \"fflate@npm:0.7.4\"\n  checksum: 10c0/5e749eb3a6ed61a0f6c55756abf9f4258f06f60505db689e22d18503dd252ca5af656d32668e4b7b20714adf8b313febf695d23863a8352f23e325baee0f672d\n  languageName: node\n  linkType: hard\n\n\"fflate@npm:^0.8.0\":\n  version: 0.8.2\n  resolution: \"fflate@npm:0.8.2\"\n  checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293\n  languageName: node\n  linkType: hard\n\n\"fill-range@npm:^7.1.1\":\n  version: 7.1.1\n  resolution: \"fill-range@npm:7.1.1\"\n  dependencies:\n    to-regex-range: \"npm:^5.0.1\"\n  checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018\n  languageName: node\n  linkType: hard\n\n\"find-replace@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"find-replace@npm:3.0.0\"\n  dependencies:\n    array-back: \"npm:^3.0.1\"\n  checksum: 10c0/fcd1bf7960388c8193c2861bcdc760c18ac14edb4bde062a961915d9a25727b2e8aabf0229e90cc09c753fd557e5a3e5ae61e49cadbe727be89a9e8e49ce7668\n  languageName: node\n  linkType: hard\n\n\"find-replace@npm:^5.0.2\":\n  version: 5.0.2\n  resolution: \"find-replace@npm:5.0.2\"\n  peerDependencies:\n    \"@75lb/nature\": \"*\"\n  peerDependenciesMeta:\n    \"@75lb/nature\":\n      optional: true\n  checksum: 10c0/25db7167e8767b0683251a985af82e29b0ac26003fe2cd6ddd86e4f2c83fc10d97a81c6f6686034d8c2b9ee88bc53547bf86cc103479a7323342da5ace9f6504\n  languageName: node\n  linkType: hard\n\n\"find-up@npm:^4.1.0\":\n  version: 4.1.0\n  resolution: \"find-up@npm:4.1.0\"\n  dependencies:\n    locate-path: \"npm:^5.0.0\"\n    path-exists: \"npm:^4.0.0\"\n  checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1\n  languageName: node\n  linkType: hard\n\n\"find-up@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"find-up@npm:5.0.0\"\n  dependencies:\n    locate-path: \"npm:^6.0.0\"\n    path-exists: \"npm:^4.0.0\"\n  checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a\n  languageName: node\n  linkType: hard\n\n\"flat@npm:^5.0.2\":\n  version: 5.0.2\n  resolution: \"flat@npm:5.0.2\"\n  bin:\n    flat: cli.js\n  checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe\n  languageName: node\n  linkType: hard\n\n\"flatbuffers@npm:^24.3.25\":\n  version: 24.3.25\n  resolution: \"flatbuffers@npm:24.3.25\"\n  checksum: 10c0/a40a1d46f6d474f1299091970700f36dc5bd86616b71cd99a1b6f521ca33b05f5d7623bd0ffadaa1e10fc63d6663aa3f9595f4916cd379e80c15787257e17a61\n  languageName: node\n  linkType: hard\n\n\"for-each@npm:^0.3.3\":\n  version: 0.3.3\n  resolution: \"for-each@npm:0.3.3\"\n  dependencies:\n    is-callable: \"npm:^1.1.3\"\n  checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa\n  languageName: node\n  linkType: hard\n\n\"foreground-child@npm:^3.1.0\":\n  version: 3.3.0\n  resolution: \"foreground-child@npm:3.3.0\"\n  dependencies:\n    cross-spawn: \"npm:^7.0.0\"\n    signal-exit: \"npm:^4.0.1\"\n  checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2\n  languageName: node\n  linkType: hard\n\n\"form-data-encoder@npm:1.7.2\":\n  version: 1.7.2\n  resolution: \"form-data-encoder@npm:1.7.2\"\n  checksum: 10c0/56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464\n  languageName: node\n  linkType: hard\n\n\"form-data@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"form-data@npm:3.0.1\"\n  dependencies:\n    asynckit: \"npm:^0.4.0\"\n    combined-stream: \"npm:^1.0.8\"\n    mime-types: \"npm:^2.1.12\"\n  checksum: 10c0/1ccc3ae064a080a799923f754d49fcebdd90515a8924f0f54de557540b50e7f1fe48ba5f2bd0435a5664aa2d49729107e6aaf2155a9abf52339474c5638b4485\n  languageName: node\n  linkType: hard\n\n\"form-data@npm:^4.0.0\":\n  version: 4.0.1\n  resolution: \"form-data@npm:4.0.1\"\n  dependencies:\n    asynckit: \"npm:^0.4.0\"\n    combined-stream: \"npm:^1.0.8\"\n    mime-types: \"npm:^2.1.12\"\n  checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8\n  languageName: node\n  linkType: hard\n\n\"formdata-node@npm:^4.3.2\":\n  version: 4.4.1\n  resolution: \"formdata-node@npm:4.4.1\"\n  dependencies:\n    node-domexception: \"npm:1.0.0\"\n    web-streams-polyfill: \"npm:4.0.0-beta.3\"\n  checksum: 10c0/74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a\n  languageName: node\n  linkType: hard\n\n\"framer-motion@npm:^11.15.0\":\n  version: 11.18.2\n  resolution: \"framer-motion@npm:11.18.2\"\n  dependencies:\n    motion-dom: \"npm:^11.18.1\"\n    motion-utils: \"npm:^11.18.1\"\n    tslib: \"npm:^2.4.0\"\n  peerDependencies:\n    \"@emotion/is-prop-valid\": \"*\"\n    react: ^18.0.0 || ^19.0.0\n    react-dom: ^18.0.0 || ^19.0.0\n  peerDependenciesMeta:\n    \"@emotion/is-prop-valid\":\n      optional: true\n    react:\n      optional: true\n    react-dom:\n      optional: true\n  checksum: 10c0/41b1ef1b4e54ea13adaf01d61812a8783d2352f74641c91b50519775704bc6274db6b6863ff494a1f705fa6c6ed8f4df3497292327c906d53ea0129cef3ec361\n  languageName: node\n  linkType: hard\n\n\"framer-motion@npm:^12.12.1\":\n  version: 12.23.12\n  resolution: \"framer-motion@npm:12.23.12\"\n  dependencies:\n    motion-dom: \"npm:^12.23.12\"\n    motion-utils: \"npm:^12.23.6\"\n    tslib: \"npm:^2.4.0\"\n  peerDependencies:\n    \"@emotion/is-prop-valid\": \"*\"\n    react: ^18.0.0 || ^19.0.0\n    react-dom: ^18.0.0 || ^19.0.0\n  peerDependenciesMeta:\n    \"@emotion/is-prop-valid\":\n      optional: true\n    react:\n      optional: true\n    react-dom:\n      optional: true\n  checksum: 10c0/40dfb57bf714075c4f6dd0bbe5b84dd11310114474ebf603846ef9b888ed475fa653271c1fd98ec57a6a1d0b781cdf8b3ebcd5e2c6a3620e934b46304ae0fd39\n  languageName: node\n  linkType: hard\n\n\"fs-extra@npm:^7.0.0\":\n  version: 7.0.1\n  resolution: \"fs-extra@npm:7.0.1\"\n  dependencies:\n    graceful-fs: \"npm:^4.1.2\"\n    jsonfile: \"npm:^4.0.0\"\n    universalify: \"npm:^0.1.0\"\n  checksum: 10c0/1943bb2150007e3739921b8d13d4109abdc3cc481e53b97b7ea7f77eda1c3c642e27ae49eac3af074e3496ea02fde30f411ef410c760c70a38b92e656e5da784\n  languageName: node\n  linkType: hard\n\n\"fs-minipass@npm:^3.0.0\":\n  version: 3.0.3\n  resolution: \"fs-minipass@npm:3.0.3\"\n  dependencies:\n    minipass: \"npm:^7.0.3\"\n  checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94\n  languageName: node\n  linkType: hard\n\n\"fsevents@npm:~2.3.2\":\n  version: 2.3.3\n  resolution: \"fsevents@npm:2.3.3\"\n  dependencies:\n    node-gyp: \"npm:latest\"\n  checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60\n  conditions: os=darwin\n  languageName: node\n  linkType: hard\n\n\"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>\":\n  version: 2.3.3\n  resolution: \"fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1\"\n  dependencies:\n    node-gyp: \"npm:latest\"\n  conditions: os=darwin\n  languageName: node\n  linkType: hard\n\n\"function-bind@npm:^1.1.2\":\n  version: 1.1.2\n  resolution: \"function-bind@npm:1.1.2\"\n  checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5\n  languageName: node\n  linkType: hard\n\n\"function.prototype.name@npm:^1.1.0, function.prototype.name@npm:^1.1.6\":\n  version: 1.1.6\n  resolution: \"function.prototype.name@npm:1.1.6\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    define-properties: \"npm:^1.2.0\"\n    es-abstract: \"npm:^1.22.1\"\n    functions-have-names: \"npm:^1.2.3\"\n  checksum: 10c0/9eae11294905b62cb16874adb4fc687927cda3162285e0ad9612e6a1d04934005d46907362ea9cdb7428edce05a2f2c3dabc3b2d21e9fd343e9bb278230ad94b\n  languageName: node\n  linkType: hard\n\n\"functions-have-names@npm:^1.2.3\":\n  version: 1.2.3\n  resolution: \"functions-have-names@npm:1.2.3\"\n  checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca\n  languageName: node\n  linkType: hard\n\n\"fuzzy@npm:^0.1.3\":\n  version: 0.1.3\n  resolution: \"fuzzy@npm:0.1.3\"\n  checksum: 10c0/584fcd57a03431707a6d0c1c4a41f17368cdb23d37dcb176d6cbbeeaecaac51be15dec229b3547acfb7db052cb066fcd86db907d40112ac4a3d3a368f88e7105\n  languageName: node\n  linkType: hard\n\n\"geojson-types@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"geojson-types@npm:2.0.1\"\n  checksum: 10c0/e7828f1c8106778f11d2437e10380be512bca5212e87302a5d856f8d35ef7994908c6dfb700e1e9625c58203b7a489b087b2354b1c8379f7ecc6da6360e84af6\n  languageName: node\n  linkType: hard\n\n\"geojson-vt@npm:^3.2.1\":\n  version: 3.2.1\n  resolution: \"geojson-vt@npm:3.2.1\"\n  checksum: 10c0/db2fc1a452067ee8436fa86e5a138f6ebd3d64893e0af097bc1cc960ec63d67c0ce77444711e9583036192d6bf9ce754bf9b56a76789684fc0fea4d52321fffc\n  languageName: node\n  linkType: hard\n\n\"geojson@npm:0.5.0, geojson@npm:^0.5.0\":\n  version: 0.5.0\n  resolution: \"geojson@npm:0.5.0\"\n  checksum: 10c0/d7fd376c201933dea1307b7c08032f249f73b9399d6287211f6a523d0efa9260caf2a38319f55075682d53dc4cb4c52b9f9c845f2fd03bef9832903f16177ab0\n  languageName: node\n  linkType: hard\n\n\"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4\":\n  version: 1.2.4\n  resolution: \"get-intrinsic@npm:1.2.4\"\n  dependencies:\n    es-errors: \"npm:^1.3.0\"\n    function-bind: \"npm:^1.1.2\"\n    has-proto: \"npm:^1.0.1\"\n    has-symbols: \"npm:^1.0.3\"\n    hasown: \"npm:^2.0.0\"\n  checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7\n  languageName: node\n  linkType: hard\n\n\"get-stream@npm:^5.1.0\":\n  version: 5.2.0\n  resolution: \"get-stream@npm:5.2.0\"\n  dependencies:\n    pump: \"npm:^3.0.0\"\n  checksum: 10c0/43797ffd815fbb26685bf188c8cfebecb8af87b3925091dd7b9a9c915993293d78e3c9e1bce125928ff92f2d0796f3889b92b5ec6d58d1041b574682132e0a80\n  languageName: node\n  linkType: hard\n\n\"get-stream@npm:^6.0.1\":\n  version: 6.0.1\n  resolution: \"get-stream@npm:6.0.1\"\n  checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341\n  languageName: node\n  linkType: hard\n\n\"get-symbol-description@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"get-symbol-description@npm:1.0.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.5\"\n    es-errors: \"npm:^1.3.0\"\n    get-intrinsic: \"npm:^1.2.4\"\n  checksum: 10c0/867be6d63f5e0eb026cb3b0ef695ec9ecf9310febb041072d2e142f260bd91ced9eeb426b3af98791d1064e324e653424afa6fd1af17dee373bea48ae03162bc\n  languageName: node\n  linkType: hard\n\n\"get-user-locale@npm:^2.2.1\":\n  version: 2.3.2\n  resolution: \"get-user-locale@npm:2.3.2\"\n  dependencies:\n    mem: \"npm:^8.0.0\"\n  checksum: 10c0/2796b3fc3782b1f4826f31e899642cf72eeb23e296e1cf55280aab5caf7a25f4b906491ee1508a001519d6a410902ccf8fa8edaa895b7aee5dfd422ffe5523b9\n  languageName: node\n  linkType: hard\n\n\"get-value@npm:^2.0.2, get-value@npm:^2.0.6\":\n  version: 2.0.6\n  resolution: \"get-value@npm:2.0.6\"\n  checksum: 10c0/f069c132791b357c8fc4adfe9e2929b0a2c6e95f98ca7bc6fcbc27f8a302e552f86b4ae61ec56d9e9ac2544b93b6a39743d479866a37b43fcc104088ba74f0d9\n  languageName: node\n  linkType: hard\n\n\"gl-matrix@npm:^3.0.0, gl-matrix@npm:^3.2.0, gl-matrix@npm:^3.2.1, gl-matrix@npm:^3.4.0, gl-matrix@npm:^3.4.3\":\n  version: 3.4.3\n  resolution: \"gl-matrix@npm:3.4.3\"\n  checksum: 10c0/c8ee6e2ce2d089b4ba4ae13ec9d4cb99bf2abe5f68f0cb08d94bbd8bafbec13aacc7230b86539ce5ca01b79226ea8c3194f971f5ca0c81838bc5e4e619dc398e\n  languageName: node\n  linkType: hard\n\n\"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2\":\n  version: 5.1.2\n  resolution: \"glob-parent@npm:5.1.2\"\n  dependencies:\n    is-glob: \"npm:^4.0.1\"\n  checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee\n  languageName: node\n  linkType: hard\n\n\"glob-parent@npm:^6.0.2\":\n  version: 6.0.2\n  resolution: \"glob-parent@npm:6.0.2\"\n  dependencies:\n    is-glob: \"npm:^4.0.3\"\n  checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8\n  languageName: node\n  linkType: hard\n\n\"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7\":\n  version: 10.4.5\n  resolution: \"glob@npm:10.4.5\"\n  dependencies:\n    foreground-child: \"npm:^3.1.0\"\n    jackspeak: \"npm:^3.1.2\"\n    minimatch: \"npm:^9.0.4\"\n    minipass: \"npm:^7.1.2\"\n    package-json-from-dist: \"npm:^1.0.0\"\n    path-scurry: \"npm:^1.11.1\"\n  bin:\n    glob: dist/esm/bin.mjs\n  checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e\n  languageName: node\n  linkType: hard\n\n\"global-prefix@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"global-prefix@npm:3.0.0\"\n  dependencies:\n    ini: \"npm:^1.3.5\"\n    kind-of: \"npm:^6.0.2\"\n    which: \"npm:^1.3.1\"\n  checksum: 10c0/510f489fb68d1cc7060f276541709a0ee6d41356ef852de48f7906c648ac223082a1cc8fce86725ca6c0e032bcdc1189ae77b4744a624b29c34a9d0ece498269\n  languageName: node\n  linkType: hard\n\n\"global@npm:>=4.3.0, global@npm:^4.3.0, global@npm:^4.3.1\":\n  version: 4.4.0\n  resolution: \"global@npm:4.4.0\"\n  dependencies:\n    min-document: \"npm:^2.19.0\"\n    process: \"npm:^0.11.10\"\n  checksum: 10c0/4a467aec6602c00a7c5685f310574ab04e289ad7f894f0f01c9c5763562b82f4b92d1e381ce6c5bbb12173e2a9f759c1b63dda6370cfb199970267e14d90aa91\n  languageName: node\n  linkType: hard\n\n\"globalthis@npm:^1.0.3\":\n  version: 1.0.4\n  resolution: \"globalthis@npm:1.0.4\"\n  dependencies:\n    define-properties: \"npm:^1.2.1\"\n    gopd: \"npm:^1.0.1\"\n  checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846\n  languageName: node\n  linkType: hard\n\n\"gopd@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"gopd@npm:1.0.1\"\n  dependencies:\n    get-intrinsic: \"npm:^1.1.3\"\n  checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63\n  languageName: node\n  linkType: hard\n\n\"got@npm:^11.8.5\":\n  version: 11.8.6\n  resolution: \"got@npm:11.8.6\"\n  dependencies:\n    \"@sindresorhus/is\": \"npm:^4.0.0\"\n    \"@szmarczak/http-timer\": \"npm:^4.0.5\"\n    \"@types/cacheable-request\": \"npm:^6.0.1\"\n    \"@types/responselike\": \"npm:^1.0.0\"\n    cacheable-lookup: \"npm:^5.0.3\"\n    cacheable-request: \"npm:^7.0.2\"\n    decompress-response: \"npm:^6.0.0\"\n    http2-wrapper: \"npm:^1.0.0-beta.5.2\"\n    lowercase-keys: \"npm:^2.0.0\"\n    p-cancelable: \"npm:^2.0.0\"\n    responselike: \"npm:^2.0.0\"\n  checksum: 10c0/754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1\n  languageName: node\n  linkType: hard\n\n\"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.6\":\n  version: 4.2.11\n  resolution: \"graceful-fs@npm:4.2.11\"\n  checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2\n  languageName: node\n  linkType: hard\n\n\"grid-index@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"grid-index@npm:1.1.0\"\n  checksum: 10c0/0ba2a622a52badc86642a002abee79b48c207092347e869528253e634573b9b55494db649fbd1779122d797aaa3b59e284023a96a7a5c666c7375f0e320f8f57\n  languageName: node\n  linkType: hard\n\n\"h3-js@npm:^3.1.0, h3-js@npm:^3.6.4, h3-js@npm:^3.7.0\":\n  version: 3.7.2\n  resolution: \"h3-js@npm:3.7.2\"\n  checksum: 10c0/c5a85743619809b81874630d6583fa6153ffa12e8f0aa4edb44bc9f78b2581a3aa84da47a5b1d0a69a20f127f8d943bf0a9c30c8344f1c7de41025486c0177ae\n  languageName: node\n  linkType: hard\n\n\"hammerjs@npm:^2.0.8\":\n  version: 2.0.8\n  resolution: \"hammerjs@npm:2.0.8\"\n  checksum: 10c0/5c95e5774b5ea49492cb3fa8f1949aea67048a0b84af33acb555e7139abfcf3c83aca2b83e0c5008755bc230166df7b5e469d1e3eb6746c48f215f3672609fed\n  languageName: node\n  linkType: hard\n\n\"hard-rejection@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"hard-rejection@npm:2.1.0\"\n  checksum: 10c0/febc3343a1ad575aedcc112580835b44a89a89e01f400b4eda6e8110869edfdab0b00cd1bd4c3bfec9475a57e79e0b355aecd5be46454b6a62b9a359af60e564\n  languageName: node\n  linkType: hard\n\n\"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"has-bigints@npm:1.0.2\"\n  checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b\n  languageName: node\n  linkType: hard\n\n\"has-flag@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"has-flag@npm:3.0.0\"\n  checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473\n  languageName: node\n  linkType: hard\n\n\"has-flag@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"has-flag@npm:4.0.0\"\n  checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1\n  languageName: node\n  linkType: hard\n\n\"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"has-property-descriptors@npm:1.0.2\"\n  dependencies:\n    es-define-property: \"npm:^1.0.0\"\n  checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236\n  languageName: node\n  linkType: hard\n\n\"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"has-proto@npm:1.0.3\"\n  checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205\n  languageName: node\n  linkType: hard\n\n\"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"has-symbols@npm:1.0.3\"\n  checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3\n  languageName: node\n  linkType: hard\n\n\"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"has-tostringtag@npm:1.0.2\"\n  dependencies:\n    has-symbols: \"npm:^1.0.3\"\n  checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c\n  languageName: node\n  linkType: hard\n\n\"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2\":\n  version: 2.0.2\n  resolution: \"hasown@npm:2.0.2\"\n  dependencies:\n    function-bind: \"npm:^1.1.2\"\n  checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9\n  languageName: node\n  linkType: hard\n\n\"hast-util-to-jsx-runtime@npm:^2.0.0\":\n  version: 2.3.6\n  resolution: \"hast-util-to-jsx-runtime@npm:2.3.6\"\n  dependencies:\n    \"@types/estree\": \"npm:^1.0.0\"\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/unist\": \"npm:^3.0.0\"\n    comma-separated-tokens: \"npm:^2.0.0\"\n    devlop: \"npm:^1.0.0\"\n    estree-util-is-identifier-name: \"npm:^3.0.0\"\n    hast-util-whitespace: \"npm:^3.0.0\"\n    mdast-util-mdx-expression: \"npm:^2.0.0\"\n    mdast-util-mdx-jsx: \"npm:^3.0.0\"\n    mdast-util-mdxjs-esm: \"npm:^2.0.0\"\n    property-information: \"npm:^7.0.0\"\n    space-separated-tokens: \"npm:^2.0.0\"\n    style-to-js: \"npm:^1.0.0\"\n    unist-util-position: \"npm:^5.0.0\"\n    vfile-message: \"npm:^4.0.0\"\n  checksum: 10c0/27297e02848fe37ef219be04a26ce708d17278a175a807689e94a821dcffc88aa506d62c3a85beed1f9a8544f7211bdcbcde0528b7b456a57c2e342c3fd11056\n  languageName: node\n  linkType: hard\n\n\"hast-util-whitespace@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"hast-util-whitespace@npm:3.0.0\"\n  dependencies:\n    \"@types/hast\": \"npm:^3.0.0\"\n  checksum: 10c0/b898bc9fe27884b272580d15260b6bbdabe239973a147e97fa98c45fa0ffec967a481aaa42291ec34fb56530dc2d484d473d7e2bae79f39c83f3762307edfea8\n  languageName: node\n  linkType: hard\n\n\"history@npm:^3.0.0\":\n  version: 3.3.0\n  resolution: \"history@npm:3.3.0\"\n  dependencies:\n    invariant: \"npm:^2.2.1\"\n    loose-envify: \"npm:^1.2.0\"\n    query-string: \"npm:^4.2.2\"\n    warning: \"npm:^3.0.0\"\n  checksum: 10c0/30de5218421dd698718b93e6aeade58cacbfc9403ea336060ef3861f5b3478269785df9df7e0642e10832eedf33d770a08ccfab4407440535f2030e3e31b33be\n  languageName: node\n  linkType: hard\n\n\"hoek@npm:4.2.1\":\n  version: 4.2.1\n  resolution: \"hoek@npm:4.2.1\"\n  checksum: 10c0/f799216a59cd059ffdb60a0ab6aae87f190606503951ba9cea53185dceae005a5ba3d935a4a356949cd4bb131b93aedcbf330d84d9aec45a5c6a9d03fe727130\n  languageName: node\n  linkType: hard\n\n\"hoist-non-react-statics@npm:^2.3.1\":\n  version: 2.5.5\n  resolution: \"hoist-non-react-statics@npm:2.5.5\"\n  checksum: 10c0/79c204446a61ad490cc9342b2deeedea5a17f03a5fc091f367b6c08f29387c8d5578634ebfb8733043422a5e1182b372893d63f8737ac4b75da3a2ec4a316162\n  languageName: node\n  linkType: hard\n\n\"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2\":\n  version: 3.3.2\n  resolution: \"hoist-non-react-statics@npm:3.3.2\"\n  dependencies:\n    react-is: \"npm:^16.7.0\"\n  checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74\n  languageName: node\n  linkType: hard\n\n\"hosted-git-info@npm:^2.1.4\":\n  version: 2.8.9\n  resolution: \"hosted-git-info@npm:2.8.9\"\n  checksum: 10c0/317cbc6b1bbbe23c2a40ae23f3dafe9fa349ce42a89a36f930e3f9c0530c179a3882d2ef1e4141a4c3674d6faaea862138ec55b43ad6f75e387fda2483a13c70\n  languageName: node\n  linkType: hard\n\n\"hosted-git-info@npm:^4.0.1\":\n  version: 4.1.0\n  resolution: \"hosted-git-info@npm:4.1.0\"\n  dependencies:\n    lru-cache: \"npm:^6.0.0\"\n  checksum: 10c0/150fbcb001600336d17fdbae803264abed013548eea7946c2264c49ebe2ebd8c4441ba71dd23dd8e18c65de79d637f98b22d4760ba5fb2e0b15d62543d0fff07\n  languageName: node\n  linkType: hard\n\n\"hsluv@npm:^0.0.3\":\n  version: 0.0.3\n  resolution: \"hsluv@npm:0.0.3\"\n  checksum: 10c0/5855c9dec7d82f5da11769adcf035bdccc025ade9e8f58601bdd7541fc33ef6a2a14dcaa89db097775d028db1bf80b5b4f6167140b61fac3957906401823bb4d\n  languageName: node\n  linkType: hard\n\n\"html-url-attributes@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"html-url-attributes@npm:3.0.1\"\n  checksum: 10c0/496e4908aa8b77665f348b4b03521901794f648b8ac34a581022cd6f2c97934d5c910cd91bc6593bbf2994687549037bc2520fcdc769b31484f29ffdd402acd0\n  languageName: node\n  linkType: hard\n\n\"html2canvas@npm:^1.4.1\":\n  version: 1.4.1\n  resolution: \"html2canvas@npm:1.4.1\"\n  dependencies:\n    css-line-break: \"npm:^2.1.0\"\n    text-segmentation: \"npm:^1.0.3\"\n  checksum: 10c0/6de86f75762b00948edf2ea559f16da0a1ec3facc4a8a7d3f35fcec59bb0c5970463478988ae3d9082152e0173690d46ebf4082e7ac803dd4817bae1d355c0db\n  languageName: node\n  linkType: hard\n\n\"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1\":\n  version: 4.1.1\n  resolution: \"http-cache-semantics@npm:4.1.1\"\n  checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc\n  languageName: node\n  linkType: hard\n\n\"http-proxy-agent@npm:^7.0.0\":\n  version: 7.0.2\n  resolution: \"http-proxy-agent@npm:7.0.2\"\n  dependencies:\n    agent-base: \"npm:^7.1.0\"\n    debug: \"npm:^4.3.4\"\n  checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921\n  languageName: node\n  linkType: hard\n\n\"http2-wrapper@npm:^1.0.0-beta.5.2\":\n  version: 1.0.3\n  resolution: \"http2-wrapper@npm:1.0.3\"\n  dependencies:\n    quick-lru: \"npm:^5.1.1\"\n    resolve-alpn: \"npm:^1.0.0\"\n  checksum: 10c0/6a9b72a033e9812e1476b9d776ce2f387bc94bc46c88aea0d5dab6bd47d0a539b8178830e77054dd26d1142c866d515a28a4dc7c3ff4232c88ff2ebe4f5d12d1\n  languageName: node\n  linkType: hard\n\n\"https-proxy-agent@npm:^7.0.1\":\n  version: 7.0.6\n  resolution: \"https-proxy-agent@npm:7.0.6\"\n  dependencies:\n    agent-base: \"npm:^7.1.2\"\n    debug: \"npm:4\"\n  checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac\n  languageName: node\n  linkType: hard\n\n\"humanize-ms@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"humanize-ms@npm:1.2.1\"\n  dependencies:\n    ms: \"npm:^2.0.0\"\n  checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a\n  languageName: node\n  linkType: hard\n\n\"iconv-lite@npm:0.4\":\n  version: 0.4.24\n  resolution: \"iconv-lite@npm:0.4.24\"\n  dependencies:\n    safer-buffer: \"npm:>= 2.1.2 < 3\"\n  checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4\n  languageName: node\n  linkType: hard\n\n\"iconv-lite@npm:^0.6.2\":\n  version: 0.6.3\n  resolution: \"iconv-lite@npm:0.6.3\"\n  dependencies:\n    safer-buffer: \"npm:>= 2.1.2 < 3.0.0\"\n  checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1\n  languageName: node\n  linkType: hard\n\n\"ieee754@npm:^1.1.12, ieee754@npm:^1.1.13, ieee754@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"ieee754@npm:1.2.1\"\n  checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb\n  languageName: node\n  linkType: hard\n\n\"image-size@npm:^0.7.4\":\n  version: 0.7.5\n  resolution: \"image-size@npm:0.7.5\"\n  bin:\n    image-size: bin/image-size.js\n  checksum: 10c0/74e978c525e7d777df145bc66cfc4a4a7aa56de8e5daead3a69b0872082dbe385deb4a3b80799810df9e34b2031467412ae28810f2f65ec2e5fe08b895aa3491\n  languageName: node\n  linkType: hard\n\n\"immer@npm:^9.0.21\":\n  version: 9.0.21\n  resolution: \"immer@npm:9.0.21\"\n  checksum: 10c0/03ea3ed5d4d72e8bd428df4a38ad7e483ea8308e9a113d3b42e0ea2cc0cc38340eb0a6aca69592abbbf047c685dbda04e3d34bf2ff438ab57339ed0a34cc0a05\n  languageName: node\n  linkType: hard\n\n\"imurmurhash@npm:^0.1.4\":\n  version: 0.1.4\n  resolution: \"imurmurhash@npm:0.1.4\"\n  checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6\n  languageName: node\n  linkType: hard\n\n\"indefinitely-typed@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"indefinitely-typed@npm:1.1.0\"\n  dependencies:\n    fs-extra: \"npm:^7.0.0\"\n    minimist: \"npm:^1.2.5\"\n  bin:\n    indefinitely-typed: bin/cli2.js\n  checksum: 10c0/935a62479ee7faf3705411f38f72ae65c5a8d5b889211e1ff63a66ca48dc5a902be54dc2e9d18612a90772ce7a89f6de28694ad66196c6aab332d559d27870ff\n  languageName: node\n  linkType: hard\n\n\"indent-string@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"indent-string@npm:4.0.0\"\n  checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f\n  languageName: node\n  linkType: hard\n\n\"inherits@npm:^2.0.3\":\n  version: 2.0.4\n  resolution: \"inherits@npm:2.0.4\"\n  checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2\n  languageName: node\n  linkType: hard\n\n\"ini@npm:^1.3.5\":\n  version: 1.3.8\n  resolution: \"ini@npm:1.3.8\"\n  checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a\n  languageName: node\n  linkType: hard\n\n\"inline-style-parser@npm:0.2.4\":\n  version: 0.2.4\n  resolution: \"inline-style-parser@npm:0.2.4\"\n  checksum: 10c0/ddc0b210eaa03e0f98d677b9836242c583c7c6051e84ce0e704ae4626e7871c5b78f8e30853480218b446355745775df318d4f82d33087ff7e393245efa9a881\n  languageName: node\n  linkType: hard\n\n\"input-otp@npm:1.4.1\":\n  version: 1.4.1\n  resolution: \"input-otp@npm:1.4.1\"\n  peerDependencies:\n    react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc\n    react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc\n  checksum: 10c0/7630c5b2be54a0569d52579c8cc4a66dab5e17afff75adf889f878c2da35cebf391be5fd4f374884e2ca5514b563dfac46eb70f4047282ba8a3c91859ae8500b\n  languageName: node\n  linkType: hard\n\n\"int53@npm:^0.2.4\":\n  version: 0.2.4\n  resolution: \"int53@npm:0.2.4\"\n  checksum: 10c0/7942bc3cee8aa6ed143b5169bcc2518c7d8d7e67601e3a982e853efb6a67420e863b90ff4d2edf0f4cfe6bd259b16d91690067986f8b2d4f8026c70d369b401a\n  languageName: node\n  linkType: hard\n\n\"internal-slot@npm:^1.0.7\":\n  version: 1.0.7\n  resolution: \"internal-slot@npm:1.0.7\"\n  dependencies:\n    es-errors: \"npm:^1.3.0\"\n    hasown: \"npm:^2.0.0\"\n    side-channel: \"npm:^1.0.4\"\n  checksum: 10c0/f8b294a4e6ea3855fc59551bbf35f2b832cf01fd5e6e2a97f5c201a071cc09b49048f856e484b67a6c721da5e55736c5b6ddafaf19e2dbeb4a3ff1821680de6c\n  languageName: node\n  linkType: hard\n\n\"internmap@npm:1 - 2\":\n  version: 2.0.3\n  resolution: \"internmap@npm:2.0.3\"\n  checksum: 10c0/8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed\n  languageName: node\n  linkType: hard\n\n\"internmap@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"internmap@npm:1.0.1\"\n  checksum: 10c0/60942be815ca19da643b6d4f23bd0bf4e8c97abbd080fb963fe67583b60bdfb3530448ad4486bae40810e92317bded9995cc31411218acc750d72cd4e8646eee\n  languageName: node\n  linkType: hard\n\n\"intl-messageformat@npm:10.5.14\":\n  version: 10.5.14\n  resolution: \"intl-messageformat@npm:10.5.14\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/fast-memoize\": \"npm:2.2.0\"\n    \"@formatjs/icu-messageformat-parser\": \"npm:2.7.8\"\n    tslib: \"npm:^2.4.0\"\n  checksum: 10c0/8ec0a60539f67039356e211bcc8d81cf1bd9d62190a72ab0e94504da92f0242fe2f94ffb512b97cc6f63782b7891874d4038536ce04631e59d762c3441c60b4b\n  languageName: node\n  linkType: hard\n\n\"intl-messageformat@npm:^10.1.0\":\n  version: 10.7.14\n  resolution: \"intl-messageformat@npm:10.7.14\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.3.2\"\n    \"@formatjs/fast-memoize\": \"npm:2.2.6\"\n    \"@formatjs/icu-messageformat-parser\": \"npm:2.11.0\"\n    tslib: \"npm:2\"\n  checksum: 10c0/914c11118c47bb7d0163a7d54afebf52ae4fe9b250bba609b5db4c802a34f8bf758edbbc8c58094203205faf76948c7112399addb32e9b36e40483c4cf7c1cb7\n  languageName: node\n  linkType: hard\n\n\"invariant@npm:^2.2.1, invariant@npm:^2.2.4\":\n  version: 2.2.4\n  resolution: \"invariant@npm:2.2.4\"\n  dependencies:\n    loose-envify: \"npm:^1.0.0\"\n  checksum: 10c0/5af133a917c0bcf65e84e7f23e779e7abc1cd49cb7fdc62d00d1de74b0d8c1b5ee74ac7766099fb3be1b05b26dfc67bab76a17030d2fe7ea2eef867434362dfc\n  languageName: node\n  linkType: hard\n\n\"ip-address@npm:^9.0.5\":\n  version: 9.0.5\n  resolution: \"ip-address@npm:9.0.5\"\n  dependencies:\n    jsbn: \"npm:1.1.0\"\n    sprintf-js: \"npm:^1.1.3\"\n  checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc\n  languageName: node\n  linkType: hard\n\n\"is-alphabetical@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"is-alphabetical@npm:2.0.1\"\n  checksum: 10c0/932367456f17237533fd1fc9fe179df77957271020b83ea31da50e5cc472d35ef6b5fb8147453274ffd251134472ce24eb6f8d8398d96dee98237cdb81a6c9a7\n  languageName: node\n  linkType: hard\n\n\"is-alphanumerical@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"is-alphanumerical@npm:2.0.1\"\n  dependencies:\n    is-alphabetical: \"npm:^2.0.0\"\n    is-decimal: \"npm:^2.0.0\"\n  checksum: 10c0/4b35c42b18e40d41378293f82a3ecd9de77049b476f748db5697c297f686e1e05b072a6aaae2d16f54d2a57f85b00cbbe755c75f6d583d1c77d6657bd0feb5a2\n  languageName: node\n  linkType: hard\n\n\"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1\":\n  version: 1.1.1\n  resolution: \"is-arguments@npm:1.1.1\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f\n  languageName: node\n  linkType: hard\n\n\"is-array-buffer@npm:^3.0.4\":\n  version: 3.0.4\n  resolution: \"is-array-buffer@npm:3.0.4\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    get-intrinsic: \"npm:^1.2.1\"\n  checksum: 10c0/42a49d006cc6130bc5424eae113e948c146f31f9d24460fc0958f855d9d810e6fd2e4519bf19aab75179af9c298ea6092459d8cafdec523cd19e529b26eab860\n  languageName: node\n  linkType: hard\n\n\"is-arrayish@npm:^0.2.1\":\n  version: 0.2.1\n  resolution: \"is-arrayish@npm:0.2.1\"\n  checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729\n  languageName: node\n  linkType: hard\n\n\"is-arrayish@npm:^0.3.1\":\n  version: 0.3.2\n  resolution: \"is-arrayish@npm:0.3.2\"\n  checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54\n  languageName: node\n  linkType: hard\n\n\"is-bigint@npm:^1.0.1\":\n  version: 1.0.4\n  resolution: \"is-bigint@npm:1.0.4\"\n  dependencies:\n    has-bigints: \"npm:^1.0.1\"\n  checksum: 10c0/eb9c88e418a0d195ca545aff2b715c9903d9b0a5033bc5922fec600eb0c3d7b1ee7f882dbf2e0d5a6e694e42391be3683e4368737bd3c4a77f8ac293e7773696\n  languageName: node\n  linkType: hard\n\n\"is-binary-path@npm:~2.1.0\":\n  version: 2.1.0\n  resolution: \"is-binary-path@npm:2.1.0\"\n  dependencies:\n    binary-extensions: \"npm:^2.0.0\"\n  checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38\n  languageName: node\n  linkType: hard\n\n\"is-boolean-object@npm:^1.1.0\":\n  version: 1.1.2\n  resolution: \"is-boolean-object@npm:1.1.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/6090587f8a8a8534c0f816da868bc94f32810f08807aa72fa7e79f7e11c466d281486ffe7a788178809c2aa71fe3e700b167fe80dd96dad68026bfff8ebf39f7\n  languageName: node\n  linkType: hard\n\n\"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7\":\n  version: 1.2.7\n  resolution: \"is-callable@npm:1.2.7\"\n  checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f\n  languageName: node\n  linkType: hard\n\n\"is-core-module@npm:^2.13.0, is-core-module@npm:^2.5.0\":\n  version: 2.15.0\n  resolution: \"is-core-module@npm:2.15.0\"\n  dependencies:\n    hasown: \"npm:^2.0.2\"\n  checksum: 10c0/da161f3d9906f459486da65609b2f1a2dfdc60887c689c234d04e88a062cb7920fa5be5fb7ab08dc43b732929653c4135ef05bf77888ae2a9040ce76815eb7b1\n  languageName: node\n  linkType: hard\n\n\"is-core-module@npm:^2.16.0\":\n  version: 2.16.1\n  resolution: \"is-core-module@npm:2.16.1\"\n  dependencies:\n    hasown: \"npm:^2.0.2\"\n  checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd\n  languageName: node\n  linkType: hard\n\n\"is-data-view@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"is-data-view@npm:1.0.1\"\n  dependencies:\n    is-typed-array: \"npm:^1.1.13\"\n  checksum: 10c0/a3e6ec84efe303da859107aed9b970e018e2bee7ffcb48e2f8096921a493608134240e672a2072577e5f23a729846241d9634806e8a0e51d9129c56d5f65442d\n  languageName: node\n  linkType: hard\n\n\"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"is-date-object@npm:1.0.5\"\n  dependencies:\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/eed21e5dcc619c48ccef804dfc83a739dbb2abee6ca202838ee1bd5f760fe8d8a93444f0d49012ad19bb7c006186e2884a1b92f6e1c056da7fd23d0a9ad5992e\n  languageName: node\n  linkType: hard\n\n\"is-decimal@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"is-decimal@npm:2.0.1\"\n  checksum: 10c0/8085dd66f7d82f9de818fba48b9e9c0429cb4291824e6c5f2622e96b9680b54a07a624cfc663b24148b8e853c62a1c987cfe8b0b5a13f5156991afaf6736e334\n  languageName: node\n  linkType: hard\n\n\"is-error@npm:^2.2.0\":\n  version: 2.2.2\n  resolution: \"is-error@npm:2.2.2\"\n  checksum: 10c0/475d3463968bf16e94485555d7cb7a879ed68685e08d365a3370972e626054f1846ebbb3934403091e06682445568601fe919e41646096e5007952d0c1f4fd9b\n  languageName: node\n  linkType: hard\n\n\"is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1\":\n  version: 0.1.1\n  resolution: \"is-extendable@npm:0.1.1\"\n  checksum: 10c0/dd5ca3994a28e1740d1e25192e66eed128e0b2ff161a7ea348e87ae4f616554b486854de423877a2a2c171d5f7cd6e8093b91f54533bc88a59ee1c9838c43879\n  languageName: node\n  linkType: hard\n\n\"is-extendable@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"is-extendable@npm:1.0.1\"\n  dependencies:\n    is-plain-object: \"npm:^2.0.4\"\n  checksum: 10c0/1d6678a5be1563db6ecb121331c819c38059703f0179f52aa80c242c223ee9c6b66470286636c0e63d7163e4d905c0a7d82a096e0b5eaeabb51b9f8d0af0d73f\n  languageName: node\n  linkType: hard\n\n\"is-extglob@npm:^2.1.1\":\n  version: 2.1.1\n  resolution: \"is-extglob@npm:2.1.1\"\n  checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912\n  languageName: node\n  linkType: hard\n\n\"is-fullwidth-code-point@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"is-fullwidth-code-point@npm:3.0.0\"\n  checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc\n  languageName: node\n  linkType: hard\n\n\"is-generator-function@npm:^1.0.7\":\n  version: 1.0.10\n  resolution: \"is-generator-function@npm:1.0.10\"\n  dependencies:\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b\n  languageName: node\n  linkType: hard\n\n\"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1\":\n  version: 4.0.3\n  resolution: \"is-glob@npm:4.0.3\"\n  dependencies:\n    is-extglob: \"npm:^2.1.1\"\n  checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a\n  languageName: node\n  linkType: hard\n\n\"is-hexadecimal@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"is-hexadecimal@npm:2.0.1\"\n  checksum: 10c0/3eb60fe2f1e2bbc760b927dcad4d51eaa0c60138cf7fc671803f66353ad90c301605b502c7ea4c6bb0548e1c7e79dfd37b73b632652e3b76030bba603a7e9626\n  languageName: node\n  linkType: hard\n\n\"is-negative-zero@npm:^2.0.3\":\n  version: 2.0.3\n  resolution: \"is-negative-zero@npm:2.0.3\"\n  checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e\n  languageName: node\n  linkType: hard\n\n\"is-number-object@npm:^1.0.4\":\n  version: 1.0.7\n  resolution: \"is-number-object@npm:1.0.7\"\n  dependencies:\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/aad266da1e530f1804a2b7bd2e874b4869f71c98590b3964f9d06cc9869b18f8d1f4778f838ecd2a11011bce20aeecb53cb269ba916209b79c24580416b74b1b\n  languageName: node\n  linkType: hard\n\n\"is-number@npm:^7.0.0\":\n  version: 7.0.0\n  resolution: \"is-number@npm:7.0.0\"\n  checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811\n  languageName: node\n  linkType: hard\n\n\"is-observable@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"is-observable@npm:2.1.0\"\n  checksum: 10c0/f6ae9e136f66ad59c4faa4661112c389b398461cdeb0ef5bc3c505989469b77b2ba4602e2abc54a635d65f616eec9b5a40cd7d2c1f96b2cc4748b56635eba1c6\n  languageName: node\n  linkType: hard\n\n\"is-plain-obj@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"is-plain-obj@npm:1.1.0\"\n  checksum: 10c0/daaee1805add26f781b413fdf192fc91d52409583be30ace35c82607d440da63cc4cac0ac55136716688d6c0a2c6ef3edb2254fecbd1fe06056d6bd15975ee8c\n  languageName: node\n  linkType: hard\n\n\"is-plain-obj@npm:^4.0.0\":\n  version: 4.1.0\n  resolution: \"is-plain-obj@npm:4.1.0\"\n  checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e\n  languageName: node\n  linkType: hard\n\n\"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4\":\n  version: 2.0.4\n  resolution: \"is-plain-object@npm:2.0.4\"\n  dependencies:\n    isobject: \"npm:^3.0.1\"\n  checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4\n  languageName: node\n  linkType: hard\n\n\"is-regex@npm:^1.1.4\":\n  version: 1.1.4\n  resolution: \"is-regex@npm:1.1.4\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1\n  languageName: node\n  linkType: hard\n\n\"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"is-shared-array-buffer@npm:1.0.3\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n  checksum: 10c0/adc11ab0acbc934a7b9e5e9d6c588d4ec6682f6fea8cda5180721704fa32927582ede5b123349e32517fdadd07958973d24716c80e7ab198970c47acc09e59c7\n  languageName: node\n  linkType: hard\n\n\"is-stream@npm:^1.0.1\":\n  version: 1.1.0\n  resolution: \"is-stream@npm:1.1.0\"\n  checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002\n  languageName: node\n  linkType: hard\n\n\"is-string@npm:^1.0.5, is-string@npm:^1.0.7\":\n  version: 1.0.7\n  resolution: \"is-string@npm:1.0.7\"\n  dependencies:\n    has-tostringtag: \"npm:^1.0.0\"\n  checksum: 10c0/905f805cbc6eedfa678aaa103ab7f626aac9ebbdc8737abb5243acaa61d9820f8edc5819106b8fcd1839e33db21de9f0116ae20de380c8382d16dc2a601921f6\n  languageName: node\n  linkType: hard\n\n\"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3\":\n  version: 1.0.4\n  resolution: \"is-symbol@npm:1.0.4\"\n  dependencies:\n    has-symbols: \"npm:^1.0.2\"\n  checksum: 10c0/9381dd015f7c8906154dbcbf93fad769de16b4b961edc94f88d26eb8c555935caa23af88bda0c93a18e65560f6d7cca0fd5a3f8a8e1df6f1abbb9bead4502ef7\n  languageName: node\n  linkType: hard\n\n\"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.3\":\n  version: 1.1.13\n  resolution: \"is-typed-array@npm:1.1.13\"\n  dependencies:\n    which-typed-array: \"npm:^1.1.14\"\n  checksum: 10c0/fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca\n  languageName: node\n  linkType: hard\n\n\"is-url@npm:^1.2.4\":\n  version: 1.2.4\n  resolution: \"is-url@npm:1.2.4\"\n  checksum: 10c0/0157a79874f8f95fdd63540e3f38c8583c2ef572661cd0693cda80ae3e42dfe8e9a4a972ec1b827f861d9a9acf75b37f7d58a37f94a8a053259642912c252bc3\n  languageName: node\n  linkType: hard\n\n\"is-weakref@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"is-weakref@npm:1.0.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n  checksum: 10c0/1545c5d172cb690c392f2136c23eec07d8d78a7f57d0e41f10078aa4f5daf5d7f57b6513a67514ab4f073275ad00c9822fc8935e00229d0a2089e1c02685d4b1\n  languageName: node\n  linkType: hard\n\n\"isarray@npm:^2.0.5\":\n  version: 2.0.5\n  resolution: \"isarray@npm:2.0.5\"\n  checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd\n  languageName: node\n  linkType: hard\n\n\"isexe@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"isexe@npm:2.0.0\"\n  checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d\n  languageName: node\n  linkType: hard\n\n\"isexe@npm:^3.1.1\":\n  version: 3.1.1\n  resolution: \"isexe@npm:3.1.1\"\n  checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7\n  languageName: node\n  linkType: hard\n\n\"isobject@npm:^3.0.1\":\n  version: 3.0.1\n  resolution: \"isobject@npm:3.0.1\"\n  checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db\n  languageName: node\n  linkType: hard\n\n\"isomorphic-fetch@npm:^2.1.1\":\n  version: 2.2.1\n  resolution: \"isomorphic-fetch@npm:2.2.1\"\n  dependencies:\n    node-fetch: \"npm:^1.0.1\"\n    whatwg-fetch: \"npm:>=0.10.0\"\n  checksum: 10c0/ea9fd37d31ec7b35b82180e1946d4a2f512506d0559fa567ec6ee6701ff1c6d924be90e75499c50982274b707e03ecd9eaa21d618872dd0deff530e4c3bdb074\n  languageName: node\n  linkType: hard\n\n\"isomorphic-ws@npm:^4.0.1\":\n  version: 4.0.1\n  resolution: \"isomorphic-ws@npm:4.0.1\"\n  peerDependencies:\n    ws: \"*\"\n  checksum: 10c0/7cb90dc2f0eb409825558982fb15d7c1d757a88595efbab879592f9d2b63820d6bbfb5571ab8abe36c715946e165a413a99f6aafd9f40ab1f514d73487bc9996\n  languageName: node\n  linkType: hard\n\n\"jackspeak@npm:^3.1.2\":\n  version: 3.4.3\n  resolution: \"jackspeak@npm:3.4.3\"\n  dependencies:\n    \"@isaacs/cliui\": \"npm:^8.0.2\"\n    \"@pkgjs/parseargs\": \"npm:^0.11.0\"\n  dependenciesMeta:\n    \"@pkgjs/parseargs\":\n      optional: true\n  checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9\n  languageName: node\n  linkType: hard\n\n\"jiti@npm:^1.21.6\":\n  version: 1.21.7\n  resolution: \"jiti@npm:1.21.7\"\n  bin:\n    jiti: bin/jiti.js\n  checksum: 10c0/77b61989c758ff32407cdae8ddc77f85e18e1a13fc4977110dbd2e05fc761842f5f71bce684d9a01316e1c4263971315a111385759951080bbfe17cbb5de8f7a\n  languageName: node\n  linkType: hard\n\n\"js-tiktoken@npm:^1.0.12\":\n  version: 1.0.16\n  resolution: \"js-tiktoken@npm:1.0.16\"\n  dependencies:\n    base64-js: \"npm:^1.5.1\"\n  checksum: 10c0/9c3b7ff9b675334eb939f97fb83da31bb499b2a34cc7da42ee7c1a72f4286b40d2c78c7dca375eece5cc20c35a00f2b6b343387fa14f2472e615cf09b755cfdd\n  languageName: node\n  linkType: hard\n\n\"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"js-tokens@npm:4.0.0\"\n  checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed\n  languageName: node\n  linkType: hard\n\n\"jsbn@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"jsbn@npm:1.1.0\"\n  checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96\n  languageName: node\n  linkType: hard\n\n\"json-bignum@npm:^0.0.3\":\n  version: 0.0.3\n  resolution: \"json-bignum@npm:0.0.3\"\n  checksum: 10c0/f9f9312d57a68f72676802fa087da4ed60241d73b6cc0e3fb9f587ca0de7364efb62612a14414ccfbedc0b77ce3c320adca21834a5673c99eb3375aef9f561db\n  languageName: node\n  linkType: hard\n\n\"json-buffer@npm:3.0.1\":\n  version: 3.0.1\n  resolution: \"json-buffer@npm:3.0.1\"\n  checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7\n  languageName: node\n  linkType: hard\n\n\"json-parse-even-better-errors@npm:^2.3.0\":\n  version: 2.3.1\n  resolution: \"json-parse-even-better-errors@npm:2.3.1\"\n  checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3\n  languageName: node\n  linkType: hard\n\n\"json-schema@npm:^0.4.0\":\n  version: 0.4.0\n  resolution: \"json-schema@npm:0.4.0\"\n  checksum: 10c0/d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3\n  languageName: node\n  linkType: hard\n\n\"json-stringify-pretty-compact@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"json-stringify-pretty-compact@npm:3.0.0\"\n  checksum: 10c0/fc522c25047bd96d72ded77af4002e7f12e9ba9f4b7e7e12a9316aee166f1b8f9c7b0d0d989a8494e3fdd804a23819f411479f68f2ef10b2f7a144581b2c68f4\n  languageName: node\n  linkType: hard\n\n\"jsondiffpatch@npm:0.6.0\":\n  version: 0.6.0\n  resolution: \"jsondiffpatch@npm:0.6.0\"\n  dependencies:\n    \"@types/diff-match-patch\": \"npm:^1.0.36\"\n    chalk: \"npm:^5.3.0\"\n    diff-match-patch: \"npm:^1.0.5\"\n  bin:\n    jsondiffpatch: bin/jsondiffpatch.js\n  checksum: 10c0/f7822e48a8ef8b9f7c6024cc59b7d3707a9fe6d84fd776d169de5a1803ad551ffe7cfdc7587f3900f224bc70897355884ed43eb1c8ccd02e7f7b43a7ebcfed4f\n  languageName: node\n  linkType: hard\n\n\"jsonfile@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"jsonfile@npm:4.0.0\"\n  dependencies:\n    graceful-fs: \"npm:^4.1.6\"\n  dependenciesMeta:\n    graceful-fs:\n      optional: true\n  checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480\n  languageName: node\n  linkType: hard\n\n\"jstat@npm:^1.9.6\":\n  version: 1.9.6\n  resolution: \"jstat@npm:1.9.6\"\n  checksum: 10c0/4243295ff7a6f23951ad7247882abe06f08dc5a6cb4086109880642636adaac7f6322d48a0663004b4bd01ed94a64a78cc3a949ce473f23f32a87989a33fda99\n  languageName: node\n  linkType: hard\n\n\"jsts@npm:2.7.1\":\n  version: 2.7.1\n  resolution: \"jsts@npm:2.7.1\"\n  checksum: 10c0/57752f181eafef7af3f7e0b374ec0a820288bf0c9dc2d3a854643092331431d6f091a26c7a3947634a92cd64990004f4ddd750b0e95d7f8f6711e1d7bdc8237c\n  languageName: node\n  linkType: hard\n\n\"just-curry-it@npm:^3.1.0\":\n  version: 3.2.1\n  resolution: \"just-curry-it@npm:3.2.1\"\n  checksum: 10c0/8d3e4a0129632fe652b978f7e477e25270ae137c808077881ff5affb91013550ced01fd7508fd786d5e2a00a16ff3f88339d637893a0e77fb82c5ac492b6f410\n  languageName: node\n  linkType: hard\n\n\"kdbush@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"kdbush@npm:3.0.0\"\n  checksum: 10c0/3fc8795870bd04f60627e7345b26fd0644beb91bc4164912c9d9378b39c674ba01c31db68ecaf6266d51c9ad81bf5b770b7effa51eeee37553d38293a094a686\n  languageName: node\n  linkType: hard\n\n\"kdbush@npm:^4.0.2\":\n  version: 4.0.2\n  resolution: \"kdbush@npm:4.0.2\"\n  checksum: 10c0/d50183b299c57e2573114e902ab47aed7494be3ca41b66d456779ecc3b2f153f491de341f9609965414784f728894f9a9001152eb9f3a40cd3755521c06a45a3\n  languageName: node\n  linkType: hard\n\n\"keymirror@npm:^0.1.1\":\n  version: 0.1.1\n  resolution: \"keymirror@npm:0.1.1\"\n  checksum: 10c0/5a5196cc7cff6ec844b4f24d73bee65c3023e107a8c423ccc87af61925f953df1bfe3b467deb95644a28240ed8adf5fdac6c5dc45ad0b5ca4feab8c170be9e22\n  languageName: node\n  linkType: hard\n\n\"keyv@npm:^4.0.0\":\n  version: 4.5.4\n  resolution: \"keyv@npm:4.5.4\"\n  dependencies:\n    json-buffer: \"npm:3.0.1\"\n  checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e\n  languageName: node\n  linkType: hard\n\n\"kind-of@npm:^6.0.2, kind-of@npm:^6.0.3\":\n  version: 6.0.3\n  resolution: \"kind-of@npm:6.0.3\"\n  checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4\n  languageName: node\n  linkType: hard\n\n\"ktx-parse@npm:^0.0.4\":\n  version: 0.0.4\n  resolution: \"ktx-parse@npm:0.0.4\"\n  checksum: 10c0/806d5005696c25147dcc9aa6a2033587041fb391c58faaecd88b94ad17b3edfc145d3d69746a218f569600e8e9c7116886e70251c5fd1c813086a093271643b2\n  languageName: node\n  linkType: hard\n\n\"ktx-parse@npm:^0.7.0\":\n  version: 0.7.1\n  resolution: \"ktx-parse@npm:0.7.1\"\n  checksum: 10c0/07c0870a0d0fe0bd412b430ac58afd721136a1ca76db83a066b6d20810878822822cbe3b8c8507c15ae8680be0988675b4f5c3d1243262284f161b52c59d09e9\n  languageName: node\n  linkType: hard\n\n\"langsmith@npm:^0.3.16\":\n  version: 0.3.25\n  resolution: \"langsmith@npm:0.3.25\"\n  dependencies:\n    \"@types/uuid\": \"npm:^10.0.0\"\n    chalk: \"npm:^4.1.2\"\n    console-table-printer: \"npm:^2.12.1\"\n    p-queue: \"npm:^6.6.2\"\n    p-retry: \"npm:4\"\n    semver: \"npm:^7.6.3\"\n    uuid: \"npm:^10.0.0\"\n  peerDependencies:\n    openai: \"*\"\n  peerDependenciesMeta:\n    openai:\n      optional: true\n  checksum: 10c0/09f912c9bae7f55376dbf96b971a667355ed2449383295bf7c18516202b238bbfeda84bfccdd6216f8626bcea28a8e3c4fb51956fc66fae576be7825f338de90\n  languageName: node\n  linkType: hard\n\n\"lerc@npm:^4.0.1\":\n  version: 4.0.4\n  resolution: \"lerc@npm:4.0.4\"\n  checksum: 10c0/71a153605ece2bac0c0da71bfae8ac70f0393e8ff7c7e27da20148a7c07f4a1cb421a263bf967dbf4b9a5bcadf8c7f819ab10ee6b8aa6ab59d233cf85cceafa4\n  languageName: node\n  linkType: hard\n\n\"lerp@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"lerp@npm:1.0.3\"\n  checksum: 10c0/cd451cff0b0dc9742bd89f8410468834bd184603f19c915bdf89e5a7f8ce5c67a2a9f5d9be53bb18cd3085db02ecbc1543ab8b7e81293b9c8811cad582f8a9d9\n  languageName: node\n  linkType: hard\n\n\"lilconfig@npm:^3.0.0, lilconfig@npm:^3.1.3\":\n  version: 3.1.3\n  resolution: \"lilconfig@npm:3.1.3\"\n  checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc\n  languageName: node\n  linkType: hard\n\n\"lines-and-columns@npm:^1.1.6\":\n  version: 1.2.4\n  resolution: \"lines-and-columns@npm:1.2.4\"\n  checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d\n  languageName: node\n  linkType: hard\n\n\"locate-path@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"locate-path@npm:5.0.0\"\n  dependencies:\n    p-locate: \"npm:^4.1.0\"\n  checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59\n  languageName: node\n  linkType: hard\n\n\"locate-path@npm:^6.0.0\":\n  version: 6.0.0\n  resolution: \"locate-path@npm:6.0.0\"\n  dependencies:\n    p-locate: \"npm:^5.0.0\"\n  checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3\n  languageName: node\n  linkType: hard\n\n\"lodash-es@npm:^4.17.15\":\n  version: 4.17.21\n  resolution: \"lodash-es@npm:4.17.21\"\n  checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2\n  languageName: node\n  linkType: hard\n\n\"lodash.camelcase@npm:^4.3.0\":\n  version: 4.3.0\n  resolution: \"lodash.camelcase@npm:4.3.0\"\n  checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432\n  languageName: node\n  linkType: hard\n\n\"lodash.debounce@npm:^4.0.8\":\n  version: 4.0.8\n  resolution: \"lodash.debounce@npm:4.0.8\"\n  checksum: 10c0/762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987\n  languageName: node\n  linkType: hard\n\n\"lodash.throttle@npm:^4.1.1\":\n  version: 4.1.1\n  resolution: \"lodash.throttle@npm:4.1.1\"\n  checksum: 10c0/14628013e9e7f65ac904fc82fd8ecb0e55a9c4c2416434b1dd9cf64ae70a8937f0b15376a39a68248530adc64887ed0fe2b75204b2c9ec3eea1cb2d66ddd125d\n  languageName: node\n  linkType: hard\n\n\"lodash@npm:4.17.21, lodash@npm:^4.0.1, lodash@npm:^4.17.15\":\n  version: 4.17.21\n  resolution: \"lodash@npm:4.17.21\"\n  checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c\n  languageName: node\n  linkType: hard\n\n\"long@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"long@npm:3.2.0\"\n  checksum: 10c0/03884ad097403bda356228899c8397d7e4e2cd26489983034faa8e52ab9f18df4539de548571ad2f574ecf78454fc377bbcd2ba8ba76d567bf973345aefcbdb2\n  languageName: node\n  linkType: hard\n\n\"long@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"long@npm:4.0.0\"\n  checksum: 10c0/50a6417d15b06104dbe4e3d4a667c39b137f130a9108ea8752b352a4cfae047531a3ac351c181792f3f8768fe17cca6b0f406674a541a86fb638aaac560d83ed\n  languageName: node\n  linkType: hard\n\n\"long@npm:^5.2.1\":\n  version: 5.2.3\n  resolution: \"long@npm:5.2.3\"\n  checksum: 10c0/6a0da658f5ef683b90330b1af76f06790c623e148222da9d75b60e266bbf88f803232dd21464575681638894a84091616e7f89557aa087fd14116c0f4e0e43d9\n  languageName: node\n  linkType: hard\n\n\"longest-streak@npm:^3.0.0\":\n  version: 3.1.0\n  resolution: \"longest-streak@npm:3.1.0\"\n  checksum: 10c0/7c2f02d0454b52834d1bcedef79c557bd295ee71fdabb02d041ff3aa9da48a90b5df7c0409156dedbc4df9b65da18742652aaea4759d6ece01f08971af6a7eaa\n  languageName: node\n  linkType: hard\n\n\"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0\":\n  version: 1.4.0\n  resolution: \"loose-envify@npm:1.4.0\"\n  dependencies:\n    js-tokens: \"npm:^3.0.0 || ^4.0.0\"\n  bin:\n    loose-envify: cli.js\n  checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e\n  languageName: node\n  linkType: hard\n\n\"lowercase-keys@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"lowercase-keys@npm:2.0.0\"\n  checksum: 10c0/f82a2b3568910509da4b7906362efa40f5b54ea14c2584778ddb313226f9cbf21020a5db35f9b9a0e95847a9b781d548601f31793d736b22a2b8ae8eb9ab1082\n  languageName: node\n  linkType: hard\n\n\"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0\":\n  version: 10.4.3\n  resolution: \"lru-cache@npm:10.4.3\"\n  checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb\n  languageName: node\n  linkType: hard\n\n\"lru-cache@npm:^6.0.0\":\n  version: 6.0.0\n  resolution: \"lru-cache@npm:6.0.0\"\n  dependencies:\n    yallist: \"npm:^4.0.0\"\n  checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9\n  languageName: node\n  linkType: hard\n\n\"lz4js@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"lz4js@npm:0.2.0\"\n  checksum: 10c0/73c6dd5117a6fa465e6be1bd5ae34ef4f02e4eb164a47d77f8c2483ccdde43cbdf8509a5e5a4480ef812e5c7a2cb946e026b9c784533775ee5c91861edaefb39\n  languageName: node\n  linkType: hard\n\n\"lzo-wasm@npm:^0.0.4\":\n  version: 0.0.4\n  resolution: \"lzo-wasm@npm:0.0.4\"\n  checksum: 10c0/33c0d43c9617f23e03196f475fd35b6b4a725e7252cf328002a606a4377ea281d380f12160df80bedfbe5516f7d9ad70592f469a6bef1e91db274e86d411452e\n  languageName: node\n  linkType: hard\n\n\"magic-string@npm:^0.25.7\":\n  version: 0.25.9\n  resolution: \"magic-string@npm:0.25.9\"\n  dependencies:\n    sourcemap-codec: \"npm:^1.4.8\"\n  checksum: 10c0/37f5e01a7e8b19a072091f0b45ff127cda676232d373ce2c551a162dd4053c575ec048b9cbb4587a1f03adb6c5d0fd0dd49e8ab070cd2c83a4992b2182d9cb56\n  languageName: node\n  linkType: hard\n\n\"make-event-props@npm:^1.6.0\":\n  version: 1.6.2\n  resolution: \"make-event-props@npm:1.6.2\"\n  checksum: 10c0/ecf0b742e43a392c07e2267baca2397e750d38cc14ef3cb72ef8bfe4a8c8b0fd99a03a2eeab84a26c2b204f7c231da6af31fa26321fbfd413ded43ba1825e867\n  languageName: node\n  linkType: hard\n\n\"make-fetch-happen@npm:^14.0.3\":\n  version: 14.0.3\n  resolution: \"make-fetch-happen@npm:14.0.3\"\n  dependencies:\n    \"@npmcli/agent\": \"npm:^3.0.0\"\n    cacache: \"npm:^19.0.1\"\n    http-cache-semantics: \"npm:^4.1.1\"\n    minipass: \"npm:^7.0.2\"\n    minipass-fetch: \"npm:^4.0.0\"\n    minipass-flush: \"npm:^1.0.5\"\n    minipass-pipeline: \"npm:^1.2.4\"\n    negotiator: \"npm:^1.0.0\"\n    proc-log: \"npm:^5.0.0\"\n    promise-retry: \"npm:^2.0.1\"\n    ssri: \"npm:^12.0.0\"\n  checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0\n  languageName: node\n  linkType: hard\n\n\"map-age-cleaner@npm:^0.1.3\":\n  version: 0.1.3\n  resolution: \"map-age-cleaner@npm:0.1.3\"\n  dependencies:\n    p-defer: \"npm:^1.0.0\"\n  checksum: 10c0/7495236c7b0950956c144fd8b4bc6399d4e78072a8840a4232fe1c4faccbb5eb5d842e5c0a56a60afc36d723f315c1c672325ca03c1b328650f7fcc478f385fd\n  languageName: node\n  linkType: hard\n\n\"map-obj@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"map-obj@npm:1.0.1\"\n  checksum: 10c0/ccca88395e7d38671ed9f5652ecf471ecd546924be2fb900836b9da35e068a96687d96a5f93dcdfa94d9a27d649d2f10a84595590f89a347fb4dda47629dcc52\n  languageName: node\n  linkType: hard\n\n\"map-obj@npm:^4.0.0\":\n  version: 4.3.0\n  resolution: \"map-obj@npm:4.3.0\"\n  checksum: 10c0/1c19e1c88513c8abdab25c316367154c6a0a6a0f77e3e8c391bb7c0e093aefed293f539d026dc013d86219e5e4c25f23b0003ea588be2101ccd757bacc12d43b\n  languageName: node\n  linkType: hard\n\n\"mapbox-gl@npm:1.13.1\":\n  version: 1.13.1\n  resolution: \"mapbox-gl@npm:1.13.1\"\n  dependencies:\n    \"@mapbox/geojson-rewind\": \"npm:^0.5.0\"\n    \"@mapbox/geojson-types\": \"npm:^1.0.2\"\n    \"@mapbox/jsonlint-lines-primitives\": \"npm:^2.0.2\"\n    \"@mapbox/mapbox-gl-supported\": \"npm:^1.5.0\"\n    \"@mapbox/point-geometry\": \"npm:^0.1.0\"\n    \"@mapbox/tiny-sdf\": \"npm:^1.1.1\"\n    \"@mapbox/unitbezier\": \"npm:^0.0.0\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@mapbox/whoots-js\": \"npm:^3.1.0\"\n    csscolorparser: \"npm:~1.0.3\"\n    earcut: \"npm:^2.2.2\"\n    geojson-vt: \"npm:^3.2.1\"\n    gl-matrix: \"npm:^3.2.1\"\n    grid-index: \"npm:^1.1.0\"\n    minimist: \"npm:^1.2.5\"\n    murmurhash-js: \"npm:^1.0.0\"\n    pbf: \"npm:^3.2.1\"\n    potpack: \"npm:^1.0.1\"\n    quickselect: \"npm:^2.0.0\"\n    rw: \"npm:^1.3.3\"\n    supercluster: \"npm:^7.1.0\"\n    tinyqueue: \"npm:^2.0.3\"\n    vt-pbf: \"npm:^3.1.1\"\n  checksum: 10c0/d507b1ce27fe77ed68250096a60a929ea4149fcab3f7037f60a7b73709391a6f78413aa4e98fd2f5e37388cb1a040b5cc15cbf8b164fdb65d98362ba726523d6\n  languageName: node\n  linkType: hard\n\n\"mapbox-gl@npm:^1.13.1\":\n  version: 1.13.3\n  resolution: \"mapbox-gl@npm:1.13.3\"\n  dependencies:\n    \"@mapbox/geojson-rewind\": \"npm:^0.5.2\"\n    \"@mapbox/geojson-types\": \"npm:^1.0.2\"\n    \"@mapbox/jsonlint-lines-primitives\": \"npm:^2.0.2\"\n    \"@mapbox/mapbox-gl-supported\": \"npm:^1.5.0\"\n    \"@mapbox/point-geometry\": \"npm:^0.1.0\"\n    \"@mapbox/tiny-sdf\": \"npm:^1.1.1\"\n    \"@mapbox/unitbezier\": \"npm:^0.0.0\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@mapbox/whoots-js\": \"npm:^3.1.0\"\n    csscolorparser: \"npm:~1.0.3\"\n    earcut: \"npm:^2.2.2\"\n    geojson-vt: \"npm:^3.2.1\"\n    gl-matrix: \"npm:^3.2.1\"\n    grid-index: \"npm:^1.1.0\"\n    murmurhash-js: \"npm:^1.0.0\"\n    pbf: \"npm:^3.2.1\"\n    potpack: \"npm:^1.0.1\"\n    quickselect: \"npm:^2.0.0\"\n    rw: \"npm:^1.3.3\"\n    supercluster: \"npm:^7.1.0\"\n    tinyqueue: \"npm:^2.0.3\"\n    vt-pbf: \"npm:^3.1.1\"\n  checksum: 10c0/82bf86daa69e068d4138c4e5a1dbfc2a28c6d0e2cc142ef96d3ce671770e8173d3ffd62e6614b3606f932f99b40eb4dfc52d5f44c48dabd393ba27b61d9e50c4\n  languageName: node\n  linkType: hard\n\n\"maplibre-gl@npm:^3.6.2\":\n  version: 3.6.2\n  resolution: \"maplibre-gl@npm:3.6.2\"\n  dependencies:\n    \"@mapbox/geojson-rewind\": \"npm:^0.5.2\"\n    \"@mapbox/jsonlint-lines-primitives\": \"npm:^2.0.2\"\n    \"@mapbox/point-geometry\": \"npm:^0.1.0\"\n    \"@mapbox/tiny-sdf\": \"npm:^2.0.6\"\n    \"@mapbox/unitbezier\": \"npm:^0.0.1\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    \"@mapbox/whoots-js\": \"npm:^3.1.0\"\n    \"@maplibre/maplibre-gl-style-spec\": \"npm:^19.3.3\"\n    \"@types/geojson\": \"npm:^7946.0.13\"\n    \"@types/mapbox__point-geometry\": \"npm:^0.1.4\"\n    \"@types/mapbox__vector-tile\": \"npm:^1.3.4\"\n    \"@types/pbf\": \"npm:^3.0.5\"\n    \"@types/supercluster\": \"npm:^7.1.3\"\n    earcut: \"npm:^2.2.4\"\n    geojson-vt: \"npm:^3.2.1\"\n    gl-matrix: \"npm:^3.4.3\"\n    global-prefix: \"npm:^3.0.0\"\n    kdbush: \"npm:^4.0.2\"\n    murmurhash-js: \"npm:^1.0.0\"\n    pbf: \"npm:^3.2.1\"\n    potpack: \"npm:^2.0.0\"\n    quickselect: \"npm:^2.0.0\"\n    supercluster: \"npm:^8.0.1\"\n    tinyqueue: \"npm:^2.0.3\"\n    vt-pbf: \"npm:^3.1.3\"\n  checksum: 10c0/51245b634087dd5f2a971b1dfa4170b98e10472e0d00859774d2ab1bd646de9a78342616862f4a66e932d0a75a6a5695258ce044f128a26e77d2f6b9efe7dafe\n  languageName: node\n  linkType: hard\n\n\"maplibregl-mapbox-request-transformer@npm:^0.0.2\":\n  version: 0.0.2\n  resolution: \"maplibregl-mapbox-request-transformer@npm:0.0.2\"\n  checksum: 10c0/fb73e476a94039a884982e831060feae70977edba7963249bb8b924cdc72fdee29e1968710e026a45dcd4079b5c11477689f54d528ea0bd481aba47cc0ac924d\n  languageName: node\n  linkType: hard\n\n\"markdown-table@npm:^3.0.0\":\n  version: 3.0.4\n  resolution: \"markdown-table@npm:3.0.4\"\n  checksum: 10c0/1257b31827629a54c24a5030a3dac952256c559174c95ce3ef89bebd6bff0cb1444b1fd667b1a1bb53307f83278111505b3e26f0c4e7b731e0060d435d2d930b\n  languageName: node\n  linkType: hard\n\n\"markdown-to-jsx@npm:^7.7.6\":\n  version: 7.7.6\n  resolution: \"markdown-to-jsx@npm:7.7.6\"\n  peerDependencies:\n    react: \">= 0.14.0\"\n  checksum: 10c0/be645e0e07cc54e52f2f934c879b10459e33a83f2592d8d7c03eb9ef2c91cdbd777f667040b3d15a5f6fb3b3d77fb7b3428d49e90573581eb85a21011533ee67\n  languageName: node\n  linkType: hard\n\n\"material-colors@npm:^1.2.1\":\n  version: 1.2.6\n  resolution: \"material-colors@npm:1.2.6\"\n  checksum: 10c0/6502ab25b23b783cca4112946bc28a64fdba8bf8efcfbb1e76028d21ecc4b88a2b1338dacf1b64e4b6af46ab98984080586002cdaa9428dc91f6fdcc6909b12a\n  languageName: node\n  linkType: hard\n\n\"math.gl@npm:^3.6.2\":\n  version: 3.6.3\n  resolution: \"math.gl@npm:3.6.3\"\n  dependencies:\n    \"@math.gl/core\": \"npm:3.6.3\"\n  checksum: 10c0/7516182bc7f47fd224e991f97c173d9f628f4aafac72749c403d38d4befdaaaac7dea309826b7710e264ae9e142376f237a0f2b6297ff01654b9771eddc701d0\n  languageName: node\n  linkType: hard\n\n\"mdast-util-find-and-replace@npm:^3.0.0\":\n  version: 3.0.2\n  resolution: \"mdast-util-find-and-replace@npm:3.0.2\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    escape-string-regexp: \"npm:^5.0.0\"\n    unist-util-is: \"npm:^6.0.0\"\n    unist-util-visit-parents: \"npm:^6.0.0\"\n  checksum: 10c0/c8417a35605d567772ff5c1aa08363ff3010b0d60c8ea68c53cba09bf25492e3dd261560425c1756535f3b7107f62e7ff3857cdd8fb1e62d1b2cc2ea6e074ca2\n  languageName: node\n  linkType: hard\n\n\"mdast-util-from-markdown@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"mdast-util-from-markdown@npm:2.0.2\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    \"@types/unist\": \"npm:^3.0.0\"\n    decode-named-character-reference: \"npm:^1.0.0\"\n    devlop: \"npm:^1.0.0\"\n    mdast-util-to-string: \"npm:^4.0.0\"\n    micromark: \"npm:^4.0.0\"\n    micromark-util-decode-numeric-character-reference: \"npm:^2.0.0\"\n    micromark-util-decode-string: \"npm:^2.0.0\"\n    micromark-util-normalize-identifier: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n    unist-util-stringify-position: \"npm:^4.0.0\"\n  checksum: 10c0/76eb2bd2c6f7a0318087c73376b8af6d7561c1e16654e7667e640f391341096c56142618fd0ff62f6d39e5ab4895898b9789c84cd7cec2874359a437a0e1ff15\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm-autolink-literal@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"mdast-util-gfm-autolink-literal@npm:2.0.1\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    ccount: \"npm:^2.0.0\"\n    devlop: \"npm:^1.0.0\"\n    mdast-util-find-and-replace: \"npm:^3.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n  checksum: 10c0/963cd22bd42aebdec7bdd0a527c9494d024d1ad0739c43dc040fee35bdfb5e29c22564330a7418a72b5eab51d47a6eff32bc0255ef3ccb5cebfe8970e91b81b6\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm-footnote@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"mdast-util-gfm-footnote@npm:2.1.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.1.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n    micromark-util-normalize-identifier: \"npm:^2.0.0\"\n  checksum: 10c0/8ab965ee6be3670d76ec0e95b2ba3101fc7444eec47564943ab483d96ac17d29da2a4e6146a2a288be30c21b48c4f3938a1e54b9a46fbdd321d49a5bc0077ed0\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm-strikethrough@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"mdast-util-gfm-strikethrough@npm:2.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/b053e93d62c7545019bd914271ea9e5667ad3b3b57d16dbf68e56fea39a7e19b4a345e781312714eb3d43fdd069ff7ee22a3ca7f6149dfa774554f19ce3ac056\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm-table@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"mdast-util-gfm-table@npm:2.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.0.0\"\n    markdown-table: \"npm:^3.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/128af47c503a53bd1c79f20642561e54a510ad5e2db1e418d28fefaf1294ab839e6c838e341aef5d7e404f9170b9ca3d1d89605f234efafde93ee51174a6e31e\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm-task-list-item@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"mdast-util-gfm-task-list-item@npm:2.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/258d725288482b636c0a376c296431390c14b4f29588675297cb6580a8598ed311fc73ebc312acfca12cc8546f07a3a285a53a3b082712e2cbf5c190d677d834\n  languageName: node\n  linkType: hard\n\n\"mdast-util-gfm@npm:^3.0.0\":\n  version: 3.1.0\n  resolution: \"mdast-util-gfm@npm:3.1.0\"\n  dependencies:\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-gfm-autolink-literal: \"npm:^2.0.0\"\n    mdast-util-gfm-footnote: \"npm:^2.0.0\"\n    mdast-util-gfm-strikethrough: \"npm:^2.0.0\"\n    mdast-util-gfm-table: \"npm:^2.0.0\"\n    mdast-util-gfm-task-list-item: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/4bedcfb6a20e39901c8772f0d2bb2d7a64ae87a54c13cbd92eec062cf470fbb68c2ad754e149af5b30794e2de61c978ab1de1ace03c0c40f443ca9b9b8044f81\n  languageName: node\n  linkType: hard\n\n\"mdast-util-mdx-expression@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"mdast-util-mdx-expression@npm:2.0.1\"\n  dependencies:\n    \"@types/estree-jsx\": \"npm:^1.0.0\"\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/9a1e57940f66431f10312fa239096efa7627f375e7933b5d3162c0b5c1712a72ac87447aff2b6838d2bbd5c1311b188718cc90b33b67dc67a88550e0a6ef6183\n  languageName: node\n  linkType: hard\n\n\"mdast-util-mdx-jsx@npm:^3.0.0\":\n  version: 3.2.0\n  resolution: \"mdast-util-mdx-jsx@npm:3.2.0\"\n  dependencies:\n    \"@types/estree-jsx\": \"npm:^1.0.0\"\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    \"@types/unist\": \"npm:^3.0.0\"\n    ccount: \"npm:^2.0.0\"\n    devlop: \"npm:^1.1.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n    parse-entities: \"npm:^4.0.0\"\n    stringify-entities: \"npm:^4.0.0\"\n    unist-util-stringify-position: \"npm:^4.0.0\"\n    vfile-message: \"npm:^4.0.0\"\n  checksum: 10c0/3acadaf3b962254f7ad2990fed4729961dc0217ca31fde9917986e880843f3ecf3392b1f22d569235cacd180d50894ad266db7af598aedca69d330d33c7ac613\n  languageName: node\n  linkType: hard\n\n\"mdast-util-mdxjs-esm@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"mdast-util-mdxjs-esm@npm:2.0.1\"\n  dependencies:\n    \"@types/estree-jsx\": \"npm:^1.0.0\"\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n  checksum: 10c0/5bda92fc154141705af2b804a534d891f28dac6273186edf1a4c5e3f045d5b01dbcac7400d27aaf91b7e76e8dce007c7b2fdf136c11ea78206ad00bdf9db46bc\n  languageName: node\n  linkType: hard\n\n\"mdast-util-phrasing@npm:^4.0.0\":\n  version: 4.1.0\n  resolution: \"mdast-util-phrasing@npm:4.1.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    unist-util-is: \"npm:^6.0.0\"\n  checksum: 10c0/bf6c31d51349aa3d74603d5e5a312f59f3f65662ed16c58017169a5fb0f84ca98578f626c5ee9e4aa3e0a81c996db8717096705521bddb4a0185f98c12c9b42f\n  languageName: node\n  linkType: hard\n\n\"mdast-util-to-hast@npm:^13.0.0\":\n  version: 13.2.0\n  resolution: \"mdast-util-to-hast@npm:13.2.0\"\n  dependencies:\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    \"@ungap/structured-clone\": \"npm:^1.0.0\"\n    devlop: \"npm:^1.0.0\"\n    micromark-util-sanitize-uri: \"npm:^2.0.0\"\n    trim-lines: \"npm:^3.0.0\"\n    unist-util-position: \"npm:^5.0.0\"\n    unist-util-visit: \"npm:^5.0.0\"\n    vfile: \"npm:^6.0.0\"\n  checksum: 10c0/9ee58def9287df8350cbb6f83ced90f9c088d72d4153780ad37854f87144cadc6f27b20347073b285173b1649b0723ddf0b9c78158608a804dcacb6bda6e1816\n  languageName: node\n  linkType: hard\n\n\"mdast-util-to-markdown@npm:^2.0.0\":\n  version: 2.1.2\n  resolution: \"mdast-util-to-markdown@npm:2.1.2\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    \"@types/unist\": \"npm:^3.0.0\"\n    longest-streak: \"npm:^3.0.0\"\n    mdast-util-phrasing: \"npm:^4.0.0\"\n    mdast-util-to-string: \"npm:^4.0.0\"\n    micromark-util-classify-character: \"npm:^2.0.0\"\n    micromark-util-decode-string: \"npm:^2.0.0\"\n    unist-util-visit: \"npm:^5.0.0\"\n    zwitch: \"npm:^2.0.0\"\n  checksum: 10c0/4649722a6099f12e797bd8d6469b2b43b44e526b5182862d9c7766a3431caad2c0112929c538a972f214e63c015395e5d3f54bd81d9ac1b16e6d8baaf582f749\n  languageName: node\n  linkType: hard\n\n\"mdast-util-to-string@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"mdast-util-to-string@npm:4.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n  checksum: 10c0/2d3c1af29bf3fe9c20f552ee9685af308002488f3b04b12fa66652c9718f66f41a32f8362aa2d770c3ff464c034860b41715902ada2306bb0a055146cef064d7\n  languageName: node\n  linkType: hard\n\n\"mem@npm:^8.0.0\":\n  version: 8.1.1\n  resolution: \"mem@npm:8.1.1\"\n  dependencies:\n    map-age-cleaner: \"npm:^0.1.3\"\n    mimic-fn: \"npm:^3.1.0\"\n  checksum: 10c0/5829c404d024c1accaf76ebacbc7eae9b59e5ce5722d184aa24e8387a8097a499f6aa7e181021003c51eb87b2dcdc9a2270050c58753cce761de206643cba91c\n  languageName: node\n  linkType: hard\n\n\"meow@npm:^9.0.0\":\n  version: 9.0.0\n  resolution: \"meow@npm:9.0.0\"\n  dependencies:\n    \"@types/minimist\": \"npm:^1.2.0\"\n    camelcase-keys: \"npm:^6.2.2\"\n    decamelize: \"npm:^1.2.0\"\n    decamelize-keys: \"npm:^1.1.0\"\n    hard-rejection: \"npm:^2.1.0\"\n    minimist-options: \"npm:4.1.0\"\n    normalize-package-data: \"npm:^3.0.0\"\n    read-pkg-up: \"npm:^7.0.1\"\n    redent: \"npm:^3.0.0\"\n    trim-newlines: \"npm:^3.0.0\"\n    type-fest: \"npm:^0.18.0\"\n    yargs-parser: \"npm:^20.2.3\"\n  checksum: 10c0/998955ecff999dc3f3867ef3b51999218212497f27d75b9cbe10bdb73aac4ee308d484f7801fd1b3cfa4172819065f65f076ca018c1412fab19d0ea486648722\n  languageName: node\n  linkType: hard\n\n\"merge2@npm:^1.3.0\":\n  version: 1.4.1\n  resolution: \"merge2@npm:1.4.1\"\n  checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb\n  languageName: node\n  linkType: hard\n\n\"mgrs@npm:1.0.0\":\n  version: 1.0.0\n  resolution: \"mgrs@npm:1.0.0\"\n  checksum: 10c0/a360853be5a3b4f4734dbf0a193851c08039ccb6077362ab0f890cccd170ef88b59585129757da9fea4d7a313d7dc3ee88f37f594fc1ae3e4e8efc5353144d77\n  languageName: node\n  linkType: hard\n\n\"micromark-core-commonmark@npm:^2.0.0\":\n  version: 2.0.3\n  resolution: \"micromark-core-commonmark@npm:2.0.3\"\n  dependencies:\n    decode-named-character-reference: \"npm:^1.0.0\"\n    devlop: \"npm:^1.0.0\"\n    micromark-factory-destination: \"npm:^2.0.0\"\n    micromark-factory-label: \"npm:^2.0.0\"\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-factory-title: \"npm:^2.0.0\"\n    micromark-factory-whitespace: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-chunked: \"npm:^2.0.0\"\n    micromark-util-classify-character: \"npm:^2.0.0\"\n    micromark-util-html-tag-name: \"npm:^2.0.0\"\n    micromark-util-normalize-identifier: \"npm:^2.0.0\"\n    micromark-util-resolve-all: \"npm:^2.0.0\"\n    micromark-util-subtokenize: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/bd4a794fdc9e88dbdf59eaf1c507ddf26e5f7ddf4e52566c72239c0f1b66adbcd219ba2cd42350debbe24471434d5f5e50099d2b3f4e5762ca222ba8e5b549ee\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-autolink-literal@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"micromark-extension-gfm-autolink-literal@npm:2.1.0\"\n  dependencies:\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-sanitize-uri: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-footnote@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"micromark-extension-gfm-footnote@npm:2.1.0\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-core-commonmark: \"npm:^2.0.0\"\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-normalize-identifier: \"npm:^2.0.0\"\n    micromark-util-sanitize-uri: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-strikethrough@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"micromark-extension-gfm-strikethrough@npm:2.1.0\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-util-chunked: \"npm:^2.0.0\"\n    micromark-util-classify-character: \"npm:^2.0.0\"\n    micromark-util-resolve-all: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/ef4f248b865bdda71303b494671b7487808a340b25552b11ca6814dff3fcfaab9be8d294643060bbdb50f79313e4a686ab18b99cbe4d3ee8a4170fcd134234fb\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-table@npm:^2.0.0\":\n  version: 2.1.1\n  resolution: \"micromark-extension-gfm-table@npm:2.1.1\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-tagfilter@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"micromark-extension-gfm-tagfilter@npm:2.0.0\"\n  dependencies:\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/995558843fff137ae4e46aecb878d8a4691cdf23527dcf1e2f0157d66786be9f7bea0109c52a8ef70e68e3f930af811828ba912239438e31a9cfb9981f44d34d\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm-task-list-item@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"micromark-extension-gfm-task-list-item@npm:2.1.0\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/78aa537d929e9309f076ba41e5edc99f78d6decd754b6734519ccbbfca8abd52e1c62df68d41a6ae64d2a3fc1646cea955893c79680b0b4385ced4c52296181f\n  languageName: node\n  linkType: hard\n\n\"micromark-extension-gfm@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"micromark-extension-gfm@npm:3.0.0\"\n  dependencies:\n    micromark-extension-gfm-autolink-literal: \"npm:^2.0.0\"\n    micromark-extension-gfm-footnote: \"npm:^2.0.0\"\n    micromark-extension-gfm-strikethrough: \"npm:^2.0.0\"\n    micromark-extension-gfm-table: \"npm:^2.0.0\"\n    micromark-extension-gfm-tagfilter: \"npm:^2.0.0\"\n    micromark-extension-gfm-task-list-item: \"npm:^2.0.0\"\n    micromark-util-combine-extensions: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/970e28df6ebdd7c7249f52a0dda56e0566fbfa9ae56c8eeeb2445d77b6b89d44096880cd57a1c01e7821b1f4e31009109fbaca4e89731bff7b83b8519690e5d9\n  languageName: node\n  linkType: hard\n\n\"micromark-factory-destination@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-factory-destination@npm:2.0.1\"\n  dependencies:\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/bbafcf869cee5bf511161354cb87d61c142592fbecea051000ff116068dc85216e6d48519d147890b9ea5d7e2864a6341c0c09d9948c203bff624a80a476023c\n  languageName: node\n  linkType: hard\n\n\"micromark-factory-label@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-factory-label@npm:2.0.1\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/0137716b4ecb428114165505e94a2f18855c8bbea21b07a8b5ce514b32a595ed789d2b967125718fc44c4197ceaa48f6609d58807a68e778138d2e6b91b824e8\n  languageName: node\n  linkType: hard\n\n\"micromark-factory-space@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-factory-space@npm:2.0.1\"\n  dependencies:\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/f9ed43f1c0652d8d898de0ac2be3f77f776fffe7dd96bdbba1e02d7ce33d3853c6ff5daa52568fc4fa32cdf3a62d86b85ead9b9189f7211e1d69ff2163c450fb\n  languageName: node\n  linkType: hard\n\n\"micromark-factory-title@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-factory-title@npm:2.0.1\"\n  dependencies:\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/e72fad8d6e88823514916890099a5af20b6a9178ccf78e7e5e05f4de99bb8797acb756257d7a3a57a53854cb0086bf8aab15b1a9e9db8982500dd2c9ff5948b6\n  languageName: node\n  linkType: hard\n\n\"micromark-factory-whitespace@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-factory-whitespace@npm:2.0.1\"\n  dependencies:\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/20a1ec58698f24b766510a309b23a10175034fcf1551eaa9da3adcbed3e00cd53d1ebe5f030cf873f76a1cec3c34eb8c50cc227be3344caa9ed25d56cf611224\n  languageName: node\n  linkType: hard\n\n\"micromark-util-character@npm:^2.0.0\":\n  version: 2.1.1\n  resolution: \"micromark-util-character@npm:2.1.1\"\n  dependencies:\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/d3fe7a5e2c4060fc2a076f9ce699c82a2e87190a3946e1e5eea77f563869b504961f5668d9c9c014724db28ac32fa909070ea8b30c3a39bd0483cc6c04cc76a1\n  languageName: node\n  linkType: hard\n\n\"micromark-util-chunked@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-chunked@npm:2.0.1\"\n  dependencies:\n    micromark-util-symbol: \"npm:^2.0.0\"\n  checksum: 10c0/b68c0c16fe8106949537bdcfe1be9cf36c0ccd3bc54c4007003cb0984c3750b6cdd0fd77d03f269a3382b85b0de58bde4f6eedbe7ecdf7244759112289b1ab56\n  languageName: node\n  linkType: hard\n\n\"micromark-util-classify-character@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-classify-character@npm:2.0.1\"\n  dependencies:\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/8a02e59304005c475c332f581697e92e8c585bcd45d5d225a66c1c1b14ab5a8062705188c2ccec33cc998d33502514121478b2091feddbc751887fc9c290ed08\n  languageName: node\n  linkType: hard\n\n\"micromark-util-combine-extensions@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-combine-extensions@npm:2.0.1\"\n  dependencies:\n    micromark-util-chunked: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/f15e282af24c8372cbb10b9b0b3e2c0aa681fea0ca323a44d6bc537dc1d9382c819c3689f14eaa000118f5a163245358ce6276b2cda9a84439cdb221f5d86ae7\n  languageName: node\n  linkType: hard\n\n\"micromark-util-decode-numeric-character-reference@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"micromark-util-decode-numeric-character-reference@npm:2.0.2\"\n  dependencies:\n    micromark-util-symbol: \"npm:^2.0.0\"\n  checksum: 10c0/9c8a9f2c790e5593ffe513901c3a110e9ec8882a08f466da014112a25e5059b51551ca0aeb7ff494657d86eceb2f02ee556c6558b8d66aadc61eae4a240da0df\n  languageName: node\n  linkType: hard\n\n\"micromark-util-decode-string@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-decode-string@npm:2.0.1\"\n  dependencies:\n    decode-named-character-reference: \"npm:^1.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-decode-numeric-character-reference: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n  checksum: 10c0/f24d75b2e5310be6e7b6dee532e0d17d3bf46996841d6295f2a9c87a2046fff4ab603c52ab9d7a7a6430a8b787b1574ae895849c603d262d1b22eef71736b5cb\n  languageName: node\n  linkType: hard\n\n\"micromark-util-encode@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-encode@npm:2.0.1\"\n  checksum: 10c0/b2b29f901093845da8a1bf997ea8b7f5e061ffdba85070dfe14b0197c48fda64ffcf82bfe53c90cf9dc185e69eef8c5d41cae3ba918b96bc279326921b59008a\n  languageName: node\n  linkType: hard\n\n\"micromark-util-html-tag-name@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-html-tag-name@npm:2.0.1\"\n  checksum: 10c0/ae80444db786fde908e9295f19a27a4aa304171852c77414516418650097b8afb401961c9edb09d677b06e97e8370cfa65638dde8438ebd41d60c0a8678b85b9\n  languageName: node\n  linkType: hard\n\n\"micromark-util-normalize-identifier@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-normalize-identifier@npm:2.0.1\"\n  dependencies:\n    micromark-util-symbol: \"npm:^2.0.0\"\n  checksum: 10c0/5299265fa360769fc499a89f40142f10a9d4a5c3dd8e6eac8a8ef3c2e4a6570e4c009cf75ea46dce5ee31c01f25587bde2f4a5cc0a935584ae86dd857f2babbd\n  languageName: node\n  linkType: hard\n\n\"micromark-util-resolve-all@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-resolve-all@npm:2.0.1\"\n  dependencies:\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/bb6ca28764696bb479dc44a2d5b5fe003e7177aeae1d6b0d43f24cc223bab90234092d9c3ce4a4d2b8df095ccfd820537b10eb96bb7044d635f385d65a4c984a\n  languageName: node\n  linkType: hard\n\n\"micromark-util-sanitize-uri@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-sanitize-uri@npm:2.0.1\"\n  dependencies:\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-encode: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n  checksum: 10c0/60e92166e1870fd4f1961468c2651013ff760617342918e0e0c3c4e872433aa2e60c1e5a672bfe5d89dc98f742d6b33897585cf86ae002cda23e905a3c02527c\n  languageName: node\n  linkType: hard\n\n\"micromark-util-subtokenize@npm:^2.0.0\":\n  version: 2.1.0\n  resolution: \"micromark-util-subtokenize@npm:2.1.0\"\n  dependencies:\n    devlop: \"npm:^1.0.0\"\n    micromark-util-chunked: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/bee69eece4393308e657c293ba80d92ebcb637e5f55e21dcf9c3fa732b91a8eda8ac248d76ff375e675175bfadeae4712e5158ef97eef1111789da1ce7ab5067\n  languageName: node\n  linkType: hard\n\n\"micromark-util-symbol@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"micromark-util-symbol@npm:2.0.1\"\n  checksum: 10c0/f2d1b207771e573232436618e78c5e46cd4b5c560dd4a6d63863d58018abbf49cb96ec69f7007471e51434c60de3c9268ef2bf46852f26ff4aacd10f9da16fe9\n  languageName: node\n  linkType: hard\n\n\"micromark-util-types@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"micromark-util-types@npm:2.0.2\"\n  checksum: 10c0/c8c15b96c858db781c4393f55feec10004bf7df95487636c9a9f7209e51002a5cca6a047c5d2a5dc669ff92da20e57aaa881e81a268d9ccadb647f9dce305298\n  languageName: node\n  linkType: hard\n\n\"micromark@npm:^4.0.0\":\n  version: 4.0.2\n  resolution: \"micromark@npm:4.0.2\"\n  dependencies:\n    \"@types/debug\": \"npm:^4.0.0\"\n    debug: \"npm:^4.0.0\"\n    decode-named-character-reference: \"npm:^1.0.0\"\n    devlop: \"npm:^1.0.0\"\n    micromark-core-commonmark: \"npm:^2.0.0\"\n    micromark-factory-space: \"npm:^2.0.0\"\n    micromark-util-character: \"npm:^2.0.0\"\n    micromark-util-chunked: \"npm:^2.0.0\"\n    micromark-util-combine-extensions: \"npm:^2.0.0\"\n    micromark-util-decode-numeric-character-reference: \"npm:^2.0.0\"\n    micromark-util-encode: \"npm:^2.0.0\"\n    micromark-util-normalize-identifier: \"npm:^2.0.0\"\n    micromark-util-resolve-all: \"npm:^2.0.0\"\n    micromark-util-sanitize-uri: \"npm:^2.0.0\"\n    micromark-util-subtokenize: \"npm:^2.0.0\"\n    micromark-util-symbol: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n  checksum: 10c0/07462287254219d6eda6eac8a3cebaff2994e0575499e7088027b825105e096e4f51e466b14b2a81b71933a3b6c48ee069049d87bc2c2127eee50d9cc69e8af6\n  languageName: node\n  linkType: hard\n\n\"micromatch@npm:^4.0.8\":\n  version: 4.0.8\n  resolution: \"micromatch@npm:4.0.8\"\n  dependencies:\n    braces: \"npm:^3.0.3\"\n    picomatch: \"npm:^2.3.1\"\n  checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8\n  languageName: node\n  linkType: hard\n\n\"mime-db@npm:1.52.0\":\n  version: 1.52.0\n  resolution: \"mime-db@npm:1.52.0\"\n  checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa\n  languageName: node\n  linkType: hard\n\n\"mime-types@npm:^2.1.12\":\n  version: 2.1.35\n  resolution: \"mime-types@npm:2.1.35\"\n  dependencies:\n    mime-db: \"npm:1.52.0\"\n  checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2\n  languageName: node\n  linkType: hard\n\n\"mimic-fn@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"mimic-fn@npm:3.1.0\"\n  checksum: 10c0/a07cdd8ed6490c2dff5b11f889b245d9556b80f5a653a552a651d17cff5a2d156e632d235106c2369f00cccef4071704589574cf3601bc1b1400a1f620dff067\n  languageName: node\n  linkType: hard\n\n\"mimic-response@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"mimic-response@npm:1.0.1\"\n  checksum: 10c0/c5381a5eae997f1c3b5e90ca7f209ed58c3615caeee850e85329c598f0c000ae7bec40196580eef1781c60c709f47258131dab237cad8786f8f56750594f27fa\n  languageName: node\n  linkType: hard\n\n\"mimic-response@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"mimic-response@npm:3.1.0\"\n  checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362\n  languageName: node\n  linkType: hard\n\n\"min-document@npm:^2.19.0\":\n  version: 2.19.0\n  resolution: \"min-document@npm:2.19.0\"\n  dependencies:\n    dom-walk: \"npm:^0.1.0\"\n  checksum: 10c0/783724da716fc73a51c171865d7b29bf2b855518573f82ef61c40d214f6898d7b91b5c5419e4d22693cdb78d4615873ebc3b37d7639d3dd00ca283e5a07c7af9\n  languageName: node\n  linkType: hard\n\n\"min-indent@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"min-indent@npm:1.0.1\"\n  checksum: 10c0/7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c\n  languageName: node\n  linkType: hard\n\n\"mini-svg-data-uri@npm:^1.0.3\":\n  version: 1.4.4\n  resolution: \"mini-svg-data-uri@npm:1.4.4\"\n  bin:\n    mini-svg-data-uri: cli.js\n  checksum: 10c0/24545fa30b5a45449241bf19c25b8bc37594b63ec06401b3d563bd1c2e8a6abb7c18741f8b354e0064baa63c291be214154bf3a66f201ae71dfab3cc1a5e3191\n  languageName: node\n  linkType: hard\n\n\"minimatch@npm:^9.0.4\":\n  version: 9.0.5\n  resolution: \"minimatch@npm:9.0.5\"\n  dependencies:\n    brace-expansion: \"npm:^2.0.1\"\n  checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed\n  languageName: node\n  linkType: hard\n\n\"minimist-options@npm:4.1.0\":\n  version: 4.1.0\n  resolution: \"minimist-options@npm:4.1.0\"\n  dependencies:\n    arrify: \"npm:^1.0.1\"\n    is-plain-obj: \"npm:^1.1.0\"\n    kind-of: \"npm:^6.0.3\"\n  checksum: 10c0/7871f9cdd15d1e7374e5b013e2ceda3d327a06a8c7b38ae16d9ef941e07d985e952c589e57213f7aa90a8744c60aed9524c0d85e501f5478382d9181f2763f54\n  languageName: node\n  linkType: hard\n\n\"minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8\":\n  version: 1.2.8\n  resolution: \"minimist@npm:1.2.8\"\n  checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6\n  languageName: node\n  linkType: hard\n\n\"minipass-collect@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"minipass-collect@npm:2.0.1\"\n  dependencies:\n    minipass: \"npm:^7.0.3\"\n  checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e\n  languageName: node\n  linkType: hard\n\n\"minipass-fetch@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"minipass-fetch@npm:4.0.0\"\n  dependencies:\n    encoding: \"npm:^0.1.13\"\n    minipass: \"npm:^7.0.3\"\n    minipass-sized: \"npm:^1.0.3\"\n    minizlib: \"npm:^3.0.1\"\n  dependenciesMeta:\n    encoding:\n      optional: true\n  checksum: 10c0/7fa30ce7c373fb6f94c086b374fff1589fd7e78451855d2d06c2e2d9df936d131e73e952163063016592ed3081444bd8d1ea608533313b0149156ce23311da4b\n  languageName: node\n  linkType: hard\n\n\"minipass-flush@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"minipass-flush@npm:1.0.5\"\n  dependencies:\n    minipass: \"npm:^3.0.0\"\n  checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd\n  languageName: node\n  linkType: hard\n\n\"minipass-pipeline@npm:^1.2.4\":\n  version: 1.2.4\n  resolution: \"minipass-pipeline@npm:1.2.4\"\n  dependencies:\n    minipass: \"npm:^3.0.0\"\n  checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2\n  languageName: node\n  linkType: hard\n\n\"minipass-sized@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"minipass-sized@npm:1.0.3\"\n  dependencies:\n    minipass: \"npm:^3.0.0\"\n  checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb\n  languageName: node\n  linkType: hard\n\n\"minipass@npm:^3.0.0\":\n  version: 3.3.6\n  resolution: \"minipass@npm:3.3.6\"\n  dependencies:\n    yallist: \"npm:^4.0.0\"\n  checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c\n  languageName: node\n  linkType: hard\n\n\"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2\":\n  version: 7.1.2\n  resolution: \"minipass@npm:7.1.2\"\n  checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557\n  languageName: node\n  linkType: hard\n\n\"minizlib@npm:^3.0.1\":\n  version: 3.0.1\n  resolution: \"minizlib@npm:3.0.1\"\n  dependencies:\n    minipass: \"npm:^7.0.4\"\n    rimraf: \"npm:^5.0.5\"\n  checksum: 10c0/82f8bf70da8af656909a8ee299d7ed3b3372636749d29e105f97f20e88971be31f5ed7642f2e898f00283b68b701cc01307401cdc209b0efc5dd3818220e5093\n  languageName: node\n  linkType: hard\n\n\"mitt@npm:^1.1.3\":\n  version: 1.2.0\n  resolution: \"mitt@npm:1.2.0\"\n  checksum: 10c0/81a0f22b7ac1a0ab5f17489e811641ad589b5e9d2a46a25adac19c8c0ba9c3b50bef7b287582357de25eaa0a47e5d4ced63ea8b8641ba6ca1d1f39d3ec47bb11\n  languageName: node\n  linkType: hard\n\n\"mjolnir.js@npm:^2.7.0\":\n  version: 2.7.3\n  resolution: \"mjolnir.js@npm:2.7.3\"\n  dependencies:\n    \"@types/hammerjs\": \"npm:^2.0.41\"\n    hammerjs: \"npm:^2.0.8\"\n  checksum: 10c0/82f86dedfd410a640e5d0ef6f04e1d72ab292e61d42434bc56f103670ada45b2aacf130527f434d7d4bd0b8d2d5bc82bd36850e7586f1b70ebb72da2fe2c72b2\n  languageName: node\n  linkType: hard\n\n\"mkdirp@npm:^3.0.1\":\n  version: 3.0.1\n  resolution: \"mkdirp@npm:3.0.1\"\n  bin:\n    mkdirp: dist/cjs/src/bin.js\n  checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d\n  languageName: node\n  linkType: hard\n\n\"moment-timezone@npm:^0.5.35\":\n  version: 0.5.45\n  resolution: \"moment-timezone@npm:0.5.45\"\n  dependencies:\n    moment: \"npm:^2.29.4\"\n  checksum: 10c0/7497f23c4b8c875dbf07c03f9a1253f79edaeedc29d5732e36bfd3c5577e25aed1924fbd84cbb713ce1920dbe822be0e21bd487851a7d13907226f289a5e568b\n  languageName: node\n  linkType: hard\n\n\"moment@npm:^2.10.6, moment@npm:^2.19.3, moment@npm:^2.29.4\":\n  version: 2.30.1\n  resolution: \"moment@npm:2.30.1\"\n  checksum: 10c0/865e4279418c6de666fca7786607705fd0189d8a7b7624e2e56be99290ac846f90878a6f602e34b4e0455c549b85385b1baf9966845962b313699e7cb847543a\n  languageName: node\n  linkType: hard\n\n\"monaco-editor@npm:^0.52.0\":\n  version: 0.52.2\n  resolution: \"monaco-editor@npm:0.52.2\"\n  checksum: 10c0/5a92da64f1e2ab375c0ce99364137f794d057c97bed10ecc65a08d6e6846804b8ecbd377eacf01e498f7dfbe1b21e8be64f728256681448f0484df90e767b435\n  languageName: node\n  linkType: hard\n\n\"motion-dom@npm:^11.18.1\":\n  version: 11.18.1\n  resolution: \"motion-dom@npm:11.18.1\"\n  dependencies:\n    motion-utils: \"npm:^11.18.1\"\n  checksum: 10c0/98378bdf9d77870829cdf3624c5eff02e48cfa820dfc74450364d7421884700048d60e277bfbf477df33270fbae4c1980e5914586f5b6dff28d4921fdca8ac47\n  languageName: node\n  linkType: hard\n\n\"motion-dom@npm:^12.23.12\":\n  version: 12.23.12\n  resolution: \"motion-dom@npm:12.23.12\"\n  dependencies:\n    motion-utils: \"npm:^12.23.6\"\n  checksum: 10c0/1b6a4b86c1aed5b5da7b8a5d1f8310ad169125235bdc1953b8c41cf9f4e2c460ee90bb48ffdae54daecb8db1d7006566ceb5f5c9ccdc82606d548c527cb2631e\n  languageName: node\n  linkType: hard\n\n\"motion-utils@npm:^11.18.1\":\n  version: 11.18.1\n  resolution: \"motion-utils@npm:11.18.1\"\n  checksum: 10c0/dac083bdeb6e433a277ac4362211b0fdce59ff09d6f7897f0f49d1e3561209c6481f676876daf99a33485054bc7e4b1d1b8d1de16f7b1e5c6f117fe76358ca00\n  languageName: node\n  linkType: hard\n\n\"motion-utils@npm:^12.23.6\":\n  version: 12.23.6\n  resolution: \"motion-utils@npm:12.23.6\"\n  checksum: 10c0/c058e8ba6423b3baa63e985bcc669877ee7d9579d938f5348b4e60c5ea1b4b33dd7f4877434436a4a5807f3cf00370d3fd4079a6fdd6309c5c87aa62b311a897\n  languageName: node\n  linkType: hard\n\n\"ms@npm:2.1.2\":\n  version: 2.1.2\n  resolution: \"ms@npm:2.1.2\"\n  checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc\n  languageName: node\n  linkType: hard\n\n\"ms@npm:^2.0.0, ms@npm:^2.1.3\":\n  version: 2.1.3\n  resolution: \"ms@npm:2.1.3\"\n  checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48\n  languageName: node\n  linkType: hard\n\n\"mumath@npm:^3.3.4\":\n  version: 3.3.4\n  resolution: \"mumath@npm:3.3.4\"\n  dependencies:\n    almost-equal: \"npm:^1.1.0\"\n  checksum: 10c0/92b07302674e903b9c2ee0117e04b6783b1619d6bf6c5149d34346e51ce1fb91ab389f30b7f49baa1a9337201afe8f4522120ef8fb7a45bf221f5c1c49da849b\n  languageName: node\n  linkType: hard\n\n\"murmurhash-js@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"murmurhash-js@npm:1.0.0\"\n  checksum: 10c0/f8569e16db0ba6f953bf88286e97cf737f1efe97b224e537c9308566ab963a067c7eca5b636fb473d6413c4cc3b79690b78ff7ab0f290e75db91c6fde0df92b4\n  languageName: node\n  linkType: hard\n\n\"mustache@npm:^4.2.0\":\n  version: 4.2.0\n  resolution: \"mustache@npm:4.2.0\"\n  bin:\n    mustache: bin/mustache\n  checksum: 10c0/1f8197e8a19e63645a786581d58c41df7853da26702dbc005193e2437c98ca49b255345c173d50c08fe4b4dbb363e53cb655ecc570791f8deb09887248dd34a2\n  languageName: node\n  linkType: hard\n\n\"mz@npm:^2.7.0\":\n  version: 2.7.0\n  resolution: \"mz@npm:2.7.0\"\n  dependencies:\n    any-promise: \"npm:^1.0.0\"\n    object-assign: \"npm:^4.0.1\"\n    thenify-all: \"npm:^1.0.0\"\n  checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39\n  languageName: node\n  linkType: hard\n\n\"nanoid@npm:^3.3.6, nanoid@npm:^3.3.8\":\n  version: 3.3.8\n  resolution: \"nanoid@npm:3.3.8\"\n  bin:\n    nanoid: bin/nanoid.cjs\n  checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120\n  languageName: node\n  linkType: hard\n\n\"negotiator@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"negotiator@npm:1.0.0\"\n  checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b\n  languageName: node\n  linkType: hard\n\n\"next-themes@npm:^0.4.4\":\n  version: 0.4.4\n  resolution: \"next-themes@npm:0.4.4\"\n  peerDependencies:\n    react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc\n    react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc\n  checksum: 10c0/45d82f968ccfc90f6e6e3cdb95962132f6d1e19eaf614c4e824fc5d079ec522dd50cc196c58da2f000d755b81dbb0caf378c4f512a9e6b6c0a0daa3f99f1a2c7\n  languageName: node\n  linkType: hard\n\n\"node-domexception@npm:1.0.0\":\n  version: 1.0.0\n  resolution: \"node-domexception@npm:1.0.0\"\n  checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b\n  languageName: node\n  linkType: hard\n\n\"node-fetch@npm:^1.0.1\":\n  version: 1.7.3\n  resolution: \"node-fetch@npm:1.7.3\"\n  dependencies:\n    encoding: \"npm:^0.1.11\"\n    is-stream: \"npm:^1.0.1\"\n  checksum: 10c0/5a6b56b3edf909ccd20414355867d24f15f1885da3b26be90840241c46e63754ebf4697050f897daab676e3952d969611ffe1d4bc4506cf50f70837e20ad5328\n  languageName: node\n  linkType: hard\n\n\"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7\":\n  version: 2.7.0\n  resolution: \"node-fetch@npm:2.7.0\"\n  dependencies:\n    whatwg-url: \"npm:^5.0.0\"\n  peerDependencies:\n    encoding: ^0.1.0\n  peerDependenciesMeta:\n    encoding:\n      optional: true\n  checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8\n  languageName: node\n  linkType: hard\n\n\"node-gyp@npm:latest\":\n  version: 11.0.0\n  resolution: \"node-gyp@npm:11.0.0\"\n  dependencies:\n    env-paths: \"npm:^2.2.0\"\n    exponential-backoff: \"npm:^3.1.1\"\n    glob: \"npm:^10.3.10\"\n    graceful-fs: \"npm:^4.2.6\"\n    make-fetch-happen: \"npm:^14.0.3\"\n    nopt: \"npm:^8.0.0\"\n    proc-log: \"npm:^5.0.0\"\n    semver: \"npm:^7.3.5\"\n    tar: \"npm:^7.4.3\"\n    which: \"npm:^5.0.0\"\n  bin:\n    node-gyp: bin/node-gyp.js\n  checksum: 10c0/a3b885bbee2d271f1def32ba2e30ffcf4562a3db33af06b8b365e053153e2dd2051b9945783c3c8e852d26a0f20f65b251c7e83361623383a99635c0280ee573\n  languageName: node\n  linkType: hard\n\n\"node-int64@npm:^0.4.0\":\n  version: 0.4.0\n  resolution: \"node-int64@npm:0.4.0\"\n  checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a\n  languageName: node\n  linkType: hard\n\n\"nopt@npm:^8.0.0\":\n  version: 8.0.0\n  resolution: \"nopt@npm:8.0.0\"\n  dependencies:\n    abbrev: \"npm:^2.0.0\"\n  bin:\n    nopt: bin/nopt.js\n  checksum: 10c0/19cb986f79abaca2d0f0b560021da7b32ee6fcc3de48f3eaeb0c324d36755c17754f886a754c091f01f740c17caf7d6aea8237b7fbaf39f476ae5e30a249f18f\n  languageName: node\n  linkType: hard\n\n\"normalize-package-data@npm:^2.5.0\":\n  version: 2.5.0\n  resolution: \"normalize-package-data@npm:2.5.0\"\n  dependencies:\n    hosted-git-info: \"npm:^2.1.4\"\n    resolve: \"npm:^1.10.0\"\n    semver: \"npm:2 || 3 || 4 || 5\"\n    validate-npm-package-license: \"npm:^3.0.1\"\n  checksum: 10c0/357cb1646deb42f8eb4c7d42c4edf0eec312f3628c2ef98501963cc4bbe7277021b2b1d977f982b2edce78f5a1014613ce9cf38085c3df2d76730481357ca504\n  languageName: node\n  linkType: hard\n\n\"normalize-package-data@npm:^3.0.0\":\n  version: 3.0.3\n  resolution: \"normalize-package-data@npm:3.0.3\"\n  dependencies:\n    hosted-git-info: \"npm:^4.0.1\"\n    is-core-module: \"npm:^2.5.0\"\n    semver: \"npm:^7.3.4\"\n    validate-npm-package-license: \"npm:^3.0.1\"\n  checksum: 10c0/e5d0f739ba2c465d41f77c9d950e291ea4af78f8816ddb91c5da62257c40b76d8c83278b0d08ffbcd0f187636ebddad20e181e924873916d03e6e5ea2ef026be\n  languageName: node\n  linkType: hard\n\n\"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0\":\n  version: 3.0.0\n  resolution: \"normalize-path@npm:3.0.0\"\n  checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046\n  languageName: node\n  linkType: hard\n\n\"normalize-url@npm:^6.0.1\":\n  version: 6.1.0\n  resolution: \"normalize-url@npm:6.1.0\"\n  checksum: 10c0/95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23\n  languageName: node\n  linkType: hard\n\n\"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1\":\n  version: 4.1.1\n  resolution: \"object-assign@npm:4.1.1\"\n  checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414\n  languageName: node\n  linkType: hard\n\n\"object-hash@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"object-hash@npm:3.0.0\"\n  checksum: 10c0/a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47\n  languageName: node\n  linkType: hard\n\n\"object-inspect@npm:^1.13.1\":\n  version: 1.13.2\n  resolution: \"object-inspect@npm:1.13.2\"\n  checksum: 10c0/b97835b4c91ec37b5fd71add84f21c3f1047d1d155d00c0fcd6699516c256d4fcc6ff17a1aced873197fe447f91a3964178fd2a67a1ee2120cdaf60e81a050b4\n  languageName: node\n  linkType: hard\n\n\"object-is@npm:^1.1.5\":\n  version: 1.1.6\n  resolution: \"object-is@npm:1.1.6\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    define-properties: \"npm:^1.2.1\"\n  checksum: 10c0/506af444c4dce7f8e31f34fc549e2fb8152d6b9c4a30c6e62852badd7f520b579c679af433e7a072f9d78eb7808d230dc12e1cf58da9154dfbf8813099ea0fe0\n  languageName: node\n  linkType: hard\n\n\"object-keys@npm:^1.1.1\":\n  version: 1.1.1\n  resolution: \"object-keys@npm:1.1.1\"\n  checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d\n  languageName: node\n  linkType: hard\n\n\"object-stream@npm:0.0.1\":\n  version: 0.0.1\n  resolution: \"object-stream@npm:0.0.1\"\n  checksum: 10c0/f8ecf919b3e10917daf34fb2bb94521108b85411b8a94d5a996e6d40eb4c6e2246e179067dc528b83e86c51b01b1fd16c617da0e2193dd00f0bf74a1efb8f048\n  languageName: node\n  linkType: hard\n\n\"object.assign@npm:^4.1.5\":\n  version: 4.1.5\n  resolution: \"object.assign@npm:4.1.5\"\n  dependencies:\n    call-bind: \"npm:^1.0.5\"\n    define-properties: \"npm:^1.2.1\"\n    has-symbols: \"npm:^1.0.3\"\n    object-keys: \"npm:^1.1.1\"\n  checksum: 10c0/60108e1fa2706f22554a4648299b0955236c62b3685c52abf4988d14fffb0e7731e00aa8c6448397e3eb63d087dcc124a9f21e1980f36d0b2667f3c18bacd469\n  languageName: node\n  linkType: hard\n\n\"observable-fns@npm:^0.6.1\":\n  version: 0.6.1\n  resolution: \"observable-fns@npm:0.6.1\"\n  checksum: 10c0/bf25d5b3e4040233e886800c48b347361d9c7a1179f345590e671c2dd5ea9b4447bd5037f8ed40b2bb6fd7e205f0c5450eff15f48efdf91b3dec027007cf2834\n  languageName: node\n  linkType: hard\n\n\"ollama-ai-provider-v2@npm:^0.0.5\":\n  version: 0.0.5\n  resolution: \"ollama-ai-provider-v2@npm:0.0.5\"\n  dependencies:\n    \"@ai-sdk/provider\": \"npm:1.0.7\"\n    \"@ai-sdk/provider-utils\": \"npm:2.1.6\"\n  peerDependencies:\n    zod: ^3.0.0\n  checksum: 10c0/d5e251a2f4d00dc9829538a3aa284446c77365ea36cc371898231b91e764727a0de4af88e76bdba7545fe78bdc79f6f851aebeaff1d8f20cb89ab14487e02840\n  languageName: node\n  linkType: hard\n\n\"once@npm:^1.3.1, once@npm:^1.4.0\":\n  version: 1.4.0\n  resolution: \"once@npm:1.4.0\"\n  dependencies:\n    wrappy: \"npm:1\"\n  checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0\n  languageName: node\n  linkType: hard\n\n\"openai-zod-functions@npm:^0.1.2\":\n  version: 0.1.2\n  resolution: \"openai-zod-functions@npm:0.1.2\"\n  dependencies:\n    zod: \"npm:^3.22.4\"\n    zod-to-json-schema: \"npm:^3.22.3\"\n    zod-validation-error: \"npm:^2.1.0\"\n  peerDependencies:\n    openai: \">= 4.20.0\"\n  checksum: 10c0/439a3af9afbe2e9c672a0539026e26046969d8d4db8887330f4c2a7f0b4a653ba15bd735d0bd9c392b7d59e05649fa8ab9e52ae8de7f622caec953048e1c5a92\n  languageName: node\n  linkType: hard\n\n\"openai@npm:^4.93.0\":\n  version: 4.97.0\n  resolution: \"openai@npm:4.97.0\"\n  dependencies:\n    \"@types/node\": \"npm:^18.11.18\"\n    \"@types/node-fetch\": \"npm:^2.6.4\"\n    abort-controller: \"npm:^3.0.0\"\n    agentkeepalive: \"npm:^4.2.1\"\n    form-data-encoder: \"npm:1.7.2\"\n    formdata-node: \"npm:^4.3.2\"\n    node-fetch: \"npm:^2.6.7\"\n  peerDependencies:\n    ws: ^8.18.0\n    zod: ^3.23.8\n  peerDependenciesMeta:\n    ws:\n      optional: true\n    zod:\n      optional: true\n  bin:\n    openai: bin/cli\n  checksum: 10c0/9938eb85102dd0465197e79c0912df9f3afd52be2d9524fd5100f5befbfa70e6bb7803120515ffa0925b7afc14762052b087c7c2f3d0b85852efa22aa1d745d6\n  languageName: node\n  linkType: hard\n\n\"p-cancelable@npm:^2.0.0\":\n  version: 2.1.1\n  resolution: \"p-cancelable@npm:2.1.1\"\n  checksum: 10c0/8c6dc1f8dd4154fd8b96a10e55a3a832684c4365fb9108056d89e79fbf21a2465027c04a59d0d797b5ffe10b54a61a32043af287d5c4860f1e996cbdbc847f01\n  languageName: node\n  linkType: hard\n\n\"p-defer@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"p-defer@npm:1.0.0\"\n  checksum: 10c0/ed603c3790e74b061ac2cb07eb6e65802cf58dce0fbee646c113a7b71edb711101329ad38f99e462bd2e343a74f6e9366b496a35f1d766c187084d3109900487\n  languageName: node\n  linkType: hard\n\n\"p-finally@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"p-finally@npm:1.0.0\"\n  checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7\n  languageName: node\n  linkType: hard\n\n\"p-limit@npm:^2.2.0\":\n  version: 2.3.0\n  resolution: \"p-limit@npm:2.3.0\"\n  dependencies:\n    p-try: \"npm:^2.0.0\"\n  checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12\n  languageName: node\n  linkType: hard\n\n\"p-limit@npm:^3.0.2\":\n  version: 3.1.0\n  resolution: \"p-limit@npm:3.1.0\"\n  dependencies:\n    yocto-queue: \"npm:^0.1.0\"\n  checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a\n  languageName: node\n  linkType: hard\n\n\"p-locate@npm:^4.1.0\":\n  version: 4.1.0\n  resolution: \"p-locate@npm:4.1.0\"\n  dependencies:\n    p-limit: \"npm:^2.2.0\"\n  checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9\n  languageName: node\n  linkType: hard\n\n\"p-locate@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"p-locate@npm:5.0.0\"\n  dependencies:\n    p-limit: \"npm:^3.0.2\"\n  checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a\n  languageName: node\n  linkType: hard\n\n\"p-map@npm:^7.0.2\":\n  version: 7.0.3\n  resolution: \"p-map@npm:7.0.3\"\n  checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c\n  languageName: node\n  linkType: hard\n\n\"p-queue@npm:^6.6.2\":\n  version: 6.6.2\n  resolution: \"p-queue@npm:6.6.2\"\n  dependencies:\n    eventemitter3: \"npm:^4.0.4\"\n    p-timeout: \"npm:^3.2.0\"\n  checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e\n  languageName: node\n  linkType: hard\n\n\"p-retry@npm:4\":\n  version: 4.6.2\n  resolution: \"p-retry@npm:4.6.2\"\n  dependencies:\n    \"@types/retry\": \"npm:0.12.0\"\n    retry: \"npm:^0.13.1\"\n  checksum: 10c0/d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0\n  languageName: node\n  linkType: hard\n\n\"p-timeout@npm:^3.2.0\":\n  version: 3.2.0\n  resolution: \"p-timeout@npm:3.2.0\"\n  dependencies:\n    p-finally: \"npm:^1.0.0\"\n  checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61\n  languageName: node\n  linkType: hard\n\n\"p-try@npm:^2.0.0\":\n  version: 2.2.0\n  resolution: \"p-try@npm:2.2.0\"\n  checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f\n  languageName: node\n  linkType: hard\n\n\"package-json-from-dist@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"package-json-from-dist@npm:1.0.1\"\n  checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b\n  languageName: node\n  linkType: hard\n\n\"pako@npm:1.0.11\":\n  version: 1.0.11\n  resolution: \"pako@npm:1.0.11\"\n  checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe\n  languageName: node\n  linkType: hard\n\n\"parquet-wasm@npm:^0.6.1\":\n  version: 0.6.1\n  resolution: \"parquet-wasm@npm:0.6.1\"\n  checksum: 10c0/dcade4b006ff86edca711dacf3c71997b4a86985384fc183119f73d5dae1d0c94d3e7fa5b48845845df5db97d3eb445d6c137f3459a5d9a66a3b9b3bc0474497\n  languageName: node\n  linkType: hard\n\n\"parse-entities@npm:^4.0.0\":\n  version: 4.0.2\n  resolution: \"parse-entities@npm:4.0.2\"\n  dependencies:\n    \"@types/unist\": \"npm:^2.0.0\"\n    character-entities-legacy: \"npm:^3.0.0\"\n    character-reference-invalid: \"npm:^2.0.0\"\n    decode-named-character-reference: \"npm:^1.0.0\"\n    is-alphanumerical: \"npm:^2.0.0\"\n    is-decimal: \"npm:^2.0.0\"\n    is-hexadecimal: \"npm:^2.0.0\"\n  checksum: 10c0/a13906b1151750b78ed83d386294066daf5fb559e08c5af9591b2d98cc209123103016a01df776f65f8219ad26652d6d6b210d0974d452049cddfc53a8916c34\n  languageName: node\n  linkType: hard\n\n\"parse-json@npm:^5.0.0\":\n  version: 5.2.0\n  resolution: \"parse-json@npm:5.2.0\"\n  dependencies:\n    \"@babel/code-frame\": \"npm:^7.0.0\"\n    error-ex: \"npm:^1.3.1\"\n    json-parse-even-better-errors: \"npm:^2.3.0\"\n    lines-and-columns: \"npm:^1.1.6\"\n  checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585\n  languageName: node\n  linkType: hard\n\n\"path-exists@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"path-exists@npm:4.0.0\"\n  checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b\n  languageName: node\n  linkType: hard\n\n\"path-key@npm:^3.1.0\":\n  version: 3.1.1\n  resolution: \"path-key@npm:3.1.1\"\n  checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c\n  languageName: node\n  linkType: hard\n\n\"path-parse@npm:^1.0.7\":\n  version: 1.0.7\n  resolution: \"path-parse@npm:1.0.7\"\n  checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1\n  languageName: node\n  linkType: hard\n\n\"path-scurry@npm:^1.11.1\":\n  version: 1.11.1\n  resolution: \"path-scurry@npm:1.11.1\"\n  dependencies:\n    lru-cache: \"npm:^10.2.0\"\n    minipass: \"npm:^5.0.0 || ^6.0.2 || ^7.0.0\"\n  checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d\n  languageName: node\n  linkType: hard\n\n\"pbf@npm:^3.1.0, pbf@npm:^3.2.1\":\n  version: 3.3.0\n  resolution: \"pbf@npm:3.3.0\"\n  dependencies:\n    ieee754: \"npm:^1.1.12\"\n    resolve-protobuf-schema: \"npm:^2.1.0\"\n  bin:\n    pbf: bin/pbf\n  checksum: 10c0/79e5dc59a9391789de84b0a6d713fad0dd1e5ce6eb721536af8c9ec49feae04fdebab9f077b760bba858e615a95ac714a7d248ecb43736e904bb8396885e16d6\n  languageName: node\n  linkType: hard\n\n\"performance-now@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"performance-now@npm:0.2.0\"\n  checksum: 10c0/d7f3824e443491208f7124b45d3280dbff889f8f048c3aee507109c24644d51a226eb07fd7ac51dd0eef144639590c57410c2d167bd4fdf0c5caa0101a449c3d\n  languageName: node\n  linkType: hard\n\n\"performance-now@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"performance-now@npm:2.1.0\"\n  checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9\n  languageName: node\n  linkType: hard\n\n\"picocolors@npm:^1.0.0\":\n  version: 1.0.1\n  resolution: \"picocolors@npm:1.0.1\"\n  checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400\n  languageName: node\n  linkType: hard\n\n\"picocolors@npm:^1.1.1\":\n  version: 1.1.1\n  resolution: \"picocolors@npm:1.1.1\"\n  checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58\n  languageName: node\n  linkType: hard\n\n\"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1\":\n  version: 2.3.1\n  resolution: \"picomatch@npm:2.3.1\"\n  checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be\n  languageName: node\n  linkType: hard\n\n\"pify@npm:^2.3.0\":\n  version: 2.3.0\n  resolution: \"pify@npm:2.3.0\"\n  checksum: 10c0/551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc\n  languageName: node\n  linkType: hard\n\n\"pirates@npm:^4.0.1\":\n  version: 4.0.6\n  resolution: \"pirates@npm:4.0.6\"\n  checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36\n  languageName: node\n  linkType: hard\n\n\"pmtiles@npm:^3.0.4\":\n  version: 3.2.1\n  resolution: \"pmtiles@npm:3.2.1\"\n  dependencies:\n    \"@types/leaflet\": \"npm:^1.9.8\"\n    fflate: \"npm:^0.8.0\"\n  checksum: 10c0/645612bb23837808345581dde9cee89c2987b0556c26ac0b661dffeef42c53ceb888bb5be8817ef3fd40623b96dc6036808773529ee5f6694ddbb38af4bc5bcb\n  languageName: node\n  linkType: hard\n\n\"point-in-polygon-hao@npm:^1.1.0\":\n  version: 1.1.0\n  resolution: \"point-in-polygon-hao@npm:1.1.0\"\n  checksum: 10c0/2f992143b7b7ba095e43e98d6df6c5ad2d381f03fe6ac54e0f84aa0032fccf3996906ba06b8541014c6d59ee4ecef934c9356b9e34b3dc450e6ee0f13fe1f39e\n  languageName: node\n  linkType: hard\n\n\"polygon-clipping@npm:^0.15.3\":\n  version: 0.15.7\n  resolution: \"polygon-clipping@npm:0.15.7\"\n  dependencies:\n    robust-predicates: \"npm:^3.0.2\"\n    splaytree: \"npm:^3.1.0\"\n  checksum: 10c0/9515283509f1793f22fd87e68662838a6ebbd33cca4fc4bccd9f1b4f366383a4590e9a1cd48287e6897ad018803aeb97ed9ebe38bada5cffa7b4551539a9ab10\n  languageName: node\n  linkType: hard\n\n\"possible-typed-array-names@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"possible-typed-array-names@npm:1.0.0\"\n  checksum: 10c0/d9aa22d31f4f7680e20269db76791b41c3a32c01a373e25f8a4813b4d45f7456bfc2b6d68f752dc4aab0e0bb0721cb3d76fb678c9101cb7a16316664bc2c73fd\n  languageName: node\n  linkType: hard\n\n\"postcss-import@npm:^15.1.0\":\n  version: 15.1.0\n  resolution: \"postcss-import@npm:15.1.0\"\n  dependencies:\n    postcss-value-parser: \"npm:^4.0.0\"\n    read-cache: \"npm:^1.0.0\"\n    resolve: \"npm:^1.1.7\"\n  peerDependencies:\n    postcss: ^8.0.0\n  checksum: 10c0/518aee5c83ea6940e890b0be675a2588db68b2582319f48c3b4e06535a50ea6ee45f7e63e4309f8754473245c47a0372632378d1d73d901310f295a92f26f17b\n  languageName: node\n  linkType: hard\n\n\"postcss-js@npm:^4.0.1\":\n  version: 4.0.1\n  resolution: \"postcss-js@npm:4.0.1\"\n  dependencies:\n    camelcase-css: \"npm:^2.0.1\"\n  peerDependencies:\n    postcss: ^8.4.21\n  checksum: 10c0/af35d55cb873b0797d3b42529514f5318f447b134541844285c9ac31a17497297eb72296902967911bb737a75163441695737300ce2794e3bd8c70c13a3b106e\n  languageName: node\n  linkType: hard\n\n\"postcss-load-config@npm:^4.0.2\":\n  version: 4.0.2\n  resolution: \"postcss-load-config@npm:4.0.2\"\n  dependencies:\n    lilconfig: \"npm:^3.0.0\"\n    yaml: \"npm:^2.3.4\"\n  peerDependencies:\n    postcss: \">=8.0.9\"\n    ts-node: \">=9.0.0\"\n  peerDependenciesMeta:\n    postcss:\n      optional: true\n    ts-node:\n      optional: true\n  checksum: 10c0/3d7939acb3570b0e4b4740e483d6e555a3e2de815219cb8a3c8fc03f575a6bde667443aa93369c0be390af845cb84471bf623e24af833260de3a105b78d42519\n  languageName: node\n  linkType: hard\n\n\"postcss-nested@npm:^6.2.0\":\n  version: 6.2.0\n  resolution: \"postcss-nested@npm:6.2.0\"\n  dependencies:\n    postcss-selector-parser: \"npm:^6.1.1\"\n  peerDependencies:\n    postcss: ^8.2.14\n  checksum: 10c0/7f9c3f2d764191a39364cbdcec350f26a312431a569c9ef17408021424726b0d67995ff5288405e3724bb7152a4c92f73c027e580ec91e798800ed3c52e2bc6e\n  languageName: node\n  linkType: hard\n\n\"postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2\":\n  version: 6.1.2\n  resolution: \"postcss-selector-parser@npm:6.1.2\"\n  dependencies:\n    cssesc: \"npm:^3.0.0\"\n    util-deprecate: \"npm:^1.0.2\"\n  checksum: 10c0/523196a6bd8cf660bdf537ad95abd79e546d54180f9afb165a4ab3e651ac705d0f8b8ce6b3164fb9e3279ce482c5f751a69eb2d3a1e8eb0fd5e82294fb3ef13e\n  languageName: node\n  linkType: hard\n\n\"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.0.2\":\n  version: 4.2.0\n  resolution: \"postcss-value-parser@npm:4.2.0\"\n  checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161\n  languageName: node\n  linkType: hard\n\n\"postcss@npm:8.4.31\":\n  version: 8.4.31\n  resolution: \"postcss@npm:8.4.31\"\n  dependencies:\n    nanoid: \"npm:^3.3.6\"\n    picocolors: \"npm:^1.0.0\"\n    source-map-js: \"npm:^1.0.2\"\n  checksum: 10c0/748b82e6e5fc34034dcf2ae88ea3d11fd09f69b6c50ecdd3b4a875cfc7cdca435c958b211e2cb52355422ab6fccb7d8f2f2923161d7a1b281029e4a913d59acf\n  languageName: node\n  linkType: hard\n\n\"postcss@npm:^8.4.47\":\n  version: 8.5.1\n  resolution: \"postcss@npm:8.5.1\"\n  dependencies:\n    nanoid: \"npm:^3.3.8\"\n    picocolors: \"npm:^1.1.1\"\n    source-map-js: \"npm:^1.2.1\"\n  checksum: 10c0/c4d90c59c98e8a0c102b77d3f4cac190f883b42d63dc60e2f3ed840f16197c0c8e25a4327d2e9a847b45a985612317dc0534178feeebd0a1cf3eb0eecf75cae4\n  languageName: node\n  linkType: hard\n\n\"potpack@npm:^1.0.1\":\n  version: 1.0.2\n  resolution: \"potpack@npm:1.0.2\"\n  checksum: 10c0/670c23898a4257130858b960c2e654d3327c0f6a7e7091ff5846f213e65af8f9476320b995b8ad561a47a4d1c359c7ef347de57d22e7b02597051abb52bc85c4\n  languageName: node\n  linkType: hard\n\n\"potpack@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"potpack@npm:2.0.0\"\n  checksum: 10c0/8df693484486535b95586a624c5abf3fd7bebad161b1c10c10d86562c870fe1c90703b862a19020b1d1c2f3fbd6cea1e9d699c6eca6ded4b5792873a992c81e1\n  languageName: node\n  linkType: hard\n\n\"proc-log@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"proc-log@npm:5.0.0\"\n  checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3\n  languageName: node\n  linkType: hard\n\n\"process@npm:^0.11.10\":\n  version: 0.11.10\n  resolution: \"process@npm:0.11.10\"\n  checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3\n  languageName: node\n  linkType: hard\n\n\"proj4@npm:^2.9.2\":\n  version: 2.15.0\n  resolution: \"proj4@npm:2.15.0\"\n  dependencies:\n    mgrs: \"npm:1.0.0\"\n    wkt-parser: \"npm:^1.4.0\"\n  checksum: 10c0/01c0ef831247e08f4e0fce3193033913ca07be0301522bdefddabe1bb783b947bf731425f0aa6b14a01ee546f90cd6301a3eefed859afad3345884d1d01ec8f4\n  languageName: node\n  linkType: hard\n\n\"promise-retry@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"promise-retry@npm:2.0.1\"\n  dependencies:\n    err-code: \"npm:^2.0.2\"\n    retry: \"npm:^0.12.0\"\n  checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96\n  languageName: node\n  linkType: hard\n\n\"promise@npm:^7.1.1\":\n  version: 7.3.1\n  resolution: \"promise@npm:7.3.1\"\n  dependencies:\n    asap: \"npm:~2.0.3\"\n  checksum: 10c0/742e5c0cc646af1f0746963b8776299701ad561ce2c70b49365d62c8db8ea3681b0a1bf0d4e2fe07910bf72f02d39e51e8e73dc8d7503c3501206ac908be107f\n  languageName: node\n  linkType: hard\n\n\"prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1\":\n  version: 15.8.1\n  resolution: \"prop-types@npm:15.8.1\"\n  dependencies:\n    loose-envify: \"npm:^1.4.0\"\n    object-assign: \"npm:^4.1.1\"\n    react-is: \"npm:^16.13.1\"\n  checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077\n  languageName: node\n  linkType: hard\n\n\"property-information@npm:^7.0.0\":\n  version: 7.0.0\n  resolution: \"property-information@npm:7.0.0\"\n  checksum: 10c0/bf443e3bbdfc154da8f4ff4c85ed97c3d21f5e5f77cce84d2fd653c6dfb974a75ad61eafbccb2b8d2285942be35d763eaa99d51e29dccc28b40917d3f018107e\n  languageName: node\n  linkType: hard\n\n\"protocol-buffers-schema@npm:^3.3.1\":\n  version: 3.6.0\n  resolution: \"protocol-buffers-schema@npm:3.6.0\"\n  checksum: 10c0/23a08612e5cc903f917ae3b680216ccaf2d889c61daa68d224237f455182fa96fff16872ac94b1954b5dd26fc7e8ce7e9360c54d54ea26218d107b2f059fca37\n  languageName: node\n  linkType: hard\n\n\"pump@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"pump@npm:3.0.0\"\n  dependencies:\n    end-of-stream: \"npm:^1.1.0\"\n    once: \"npm:^1.3.1\"\n  checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478\n  languageName: node\n  linkType: hard\n\n\"q@npm:^1.5.0\":\n  version: 1.5.1\n  resolution: \"q@npm:1.5.1\"\n  checksum: 10c0/7855fbdba126cb7e92ef3a16b47ba998c0786ec7fface236e3eb0135b65df36429d91a86b1fff3ab0927b4ac4ee88a2c44527c7c3b8e2a37efbec9fe34803df4\n  languageName: node\n  linkType: hard\n\n\"query-string@npm:^4.2.2\":\n  version: 4.3.4\n  resolution: \"query-string@npm:4.3.4\"\n  dependencies:\n    object-assign: \"npm:^4.1.0\"\n    strict-uri-encode: \"npm:^1.0.0\"\n  checksum: 10c0/6181c343074c2049fbbcde63f87c1da5d3a49c6e34c8d94a61d692e886e0b8cd1ae4a4be00b598112bb9c4cb819e423ed503a5d246e4d24ecb0990d8bb21570b\n  languageName: node\n  linkType: hard\n\n\"queue-microtask@npm:^1.2.2\":\n  version: 1.2.3\n  resolution: \"queue-microtask@npm:1.2.3\"\n  checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102\n  languageName: node\n  linkType: hard\n\n\"quick-lru@npm:^4.0.1\":\n  version: 4.0.1\n  resolution: \"quick-lru@npm:4.0.1\"\n  checksum: 10c0/f9b1596fa7595a35c2f9d913ac312fede13d37dc8a747a51557ab36e11ce113bbe88ef4c0154968845559a7709cb6a7e7cbe75f7972182451cd45e7f057a334d\n  languageName: node\n  linkType: hard\n\n\"quick-lru@npm:^5.1.1\":\n  version: 5.1.1\n  resolution: \"quick-lru@npm:5.1.1\"\n  checksum: 10c0/a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da\n  languageName: node\n  linkType: hard\n\n\"quickselect@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"quickselect@npm:2.0.0\"\n  checksum: 10c0/6c8d591bc73beae4c1996b7b7138233a7dbbbdde29b7b6d822a02d08cd220fd27613f47d6e9635989b12e250d42ef9da3448de1ed12ad962974e207ab3c3562c\n  languageName: node\n  linkType: hard\n\n\"raf@npm:^3.1.0\":\n  version: 3.4.1\n  resolution: \"raf@npm:3.4.1\"\n  dependencies:\n    performance-now: \"npm:^2.1.0\"\n  checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e\n  languageName: node\n  linkType: hard\n\n\"react-audio-visualize@npm:^1.1.3\":\n  version: 1.2.0\n  resolution: \"react-audio-visualize@npm:1.2.0\"\n  peerDependencies:\n    react: \">=16.2.0\"\n    react-dom: \">=16.2.0\"\n  checksum: 10c0/d6899be0a34b853a3354b53d067299d03f08288ec427d076efdb360582be2f32db1b2883ac9f9669430306177930aea4777e08e869925cc752279e558f41bc78\n  languageName: node\n  linkType: hard\n\n\"react-audio-voice-recorder@npm:^2.2.0\":\n  version: 2.2.0\n  resolution: \"react-audio-voice-recorder@npm:2.2.0\"\n  dependencies:\n    \"@ffmpeg/ffmpeg\": \"npm:^0.11.6\"\n    react-audio-visualize: \"npm:^1.1.3\"\n  peerDependencies:\n    react: \">=16.2.0\"\n    react-dom: \">=16.2.0\"\n  checksum: 10c0/07a3637a7806940718fcffca9ecfa41d2b85d6cc5f25bcbddc8a8a924e4be05e2497d5b070876ba459ac6738d6e427f85489e847ea82008f32382ba06218849c\n  languageName: node\n  linkType: hard\n\n\"react-calendar@npm:^4.6.0\":\n  version: 4.8.0\n  resolution: \"react-calendar@npm:4.8.0\"\n  dependencies:\n    \"@wojtekmaj/date-utils\": \"npm:^1.1.3\"\n    clsx: \"npm:^2.0.0\"\n    get-user-locale: \"npm:^2.2.1\"\n    prop-types: \"npm:^15.6.0\"\n    warning: \"npm:^4.0.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/ae2d978c0a37f71688f2a1f3e2ee99c5713207257a066dd54b8bdb020f422d8370bd9b4bbeba0c802ad089352e84343087e085d29f1e1593d020e92d1328deaf\n  languageName: node\n  linkType: hard\n\n\"react-clock@npm:^4.5.0\":\n  version: 4.6.0\n  resolution: \"react-clock@npm:4.6.0\"\n  dependencies:\n    \"@wojtekmaj/date-utils\": \"npm:^1.5.0\"\n    clsx: \"npm:^2.0.0\"\n    get-user-locale: \"npm:^2.2.1\"\n    prop-types: \"npm:^15.6.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/0b567a6fcec8fefe8ccbbe656307bdd10bea0b75daf446ed36c5fc9972ba4b12f6acce0e9f884f09cc97995bec21d29b6720affe31e4786ded8b966e9c6b1ed5\n  languageName: node\n  linkType: hard\n\n\"react-color@npm:^2.19.3\":\n  version: 2.19.3\n  resolution: \"react-color@npm:2.19.3\"\n  dependencies:\n    \"@icons/material\": \"npm:^0.2.4\"\n    lodash: \"npm:^4.17.15\"\n    lodash-es: \"npm:^4.17.15\"\n    material-colors: \"npm:^1.2.1\"\n    prop-types: \"npm:^15.5.10\"\n    reactcss: \"npm:^1.2.0\"\n    tinycolor2: \"npm:^1.4.1\"\n  peerDependencies:\n    react: \"*\"\n  checksum: 10c0/6f9fce9ff3014926d960abccd5d79ecb391a41f835a4b85663fd48ff933554391222f40ed8cfc5417305d47b1e480023981ec22e13c3b16b0d8d718270fa815e\n  languageName: node\n  linkType: hard\n\n\"react-copy-to-clipboard@npm:^5.0.2\":\n  version: 5.1.0\n  resolution: \"react-copy-to-clipboard@npm:5.1.0\"\n  dependencies:\n    copy-to-clipboard: \"npm:^3.3.1\"\n    prop-types: \"npm:^15.8.1\"\n  peerDependencies:\n    react: ^15.3.0 || 16 || 17 || 18\n  checksum: 10c0/de70d9f9c2d17cee207888ed791d4a042c300e5ca732503434d49e6745cff56c0d5ebcc82ab86237e9c2248e636d1d031b9f9cf9913ecec61d82a0e5ebc93881\n  languageName: node\n  linkType: hard\n\n\"react-date-picker@npm:^10.2.0\":\n  version: 10.6.0\n  resolution: \"react-date-picker@npm:10.6.0\"\n  dependencies:\n    \"@wojtekmaj/date-utils\": \"npm:^1.1.3\"\n    clsx: \"npm:^2.0.0\"\n    get-user-locale: \"npm:^2.2.1\"\n    make-event-props: \"npm:^1.6.0\"\n    prop-types: \"npm:^15.6.0\"\n    react-calendar: \"npm:^4.6.0\"\n    react-fit: \"npm:^1.7.0\"\n    update-input-width: \"npm:^1.4.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/8b94dfbbf5f6c52e943f5402d721397cd307baf9b2deb4beee33acecbcdfce90a8144ffa6db2c68cd140e53482bf130627839f0cd51c2d2ddbbe6a4677597eb6\n  languageName: node\n  linkType: hard\n\n\"react-dom@npm:^16.4.2\":\n  version: 16.14.0\n  resolution: \"react-dom@npm:16.14.0\"\n  dependencies:\n    loose-envify: \"npm:^1.1.0\"\n    object-assign: \"npm:^4.1.1\"\n    prop-types: \"npm:^15.6.2\"\n    scheduler: \"npm:^0.19.1\"\n  peerDependencies:\n    react: ^16.14.0\n  checksum: 10c0/ca146e780631672a2d57c8d77775d38f394a6cd67db30c6af7964d0b3574ef7edccb1de8d592e990b98f4f5f8d1c8460b0691f04e7a45799962a51dcbaaa1371\n  languageName: node\n  linkType: hard\n\n\"react-dom@npm:^18.2.0\":\n  version: 18.3.1\n  resolution: \"react-dom@npm:18.3.1\"\n  dependencies:\n    loose-envify: \"npm:^1.1.0\"\n    scheduler: \"npm:^0.23.2\"\n  peerDependencies:\n    react: ^18.3.1\n  checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85\n  languageName: node\n  linkType: hard\n\n\"react-fit@npm:^1.7.0\":\n  version: 1.7.1\n  resolution: \"react-fit@npm:1.7.1\"\n  dependencies:\n    detect-element-overflow: \"npm:^1.4.0\"\n    prop-types: \"npm:^15.6.0\"\n    tiny-warning: \"npm:^1.0.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    \"@types/react-dom\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n    \"@types/react-dom\":\n      optional: true\n  checksum: 10c0/263b36d3d9cf34f0e6e7942cb94686d4e971dec056f37747d7f1683c5510db55affc2d2687812592b53f9077b437cf5b97d9483a6d07b7a2d1b12d93e795fde6\n  languageName: node\n  linkType: hard\n\n\"react-intl@npm:^6.3.0\":\n  version: 6.6.8\n  resolution: \"react-intl@npm:6.6.8\"\n  dependencies:\n    \"@formatjs/ecma402-abstract\": \"npm:2.0.0\"\n    \"@formatjs/icu-messageformat-parser\": \"npm:2.7.8\"\n    \"@formatjs/intl\": \"npm:2.10.4\"\n    \"@formatjs/intl-displaynames\": \"npm:6.6.8\"\n    \"@formatjs/intl-listformat\": \"npm:7.5.7\"\n    \"@types/hoist-non-react-statics\": \"npm:^3.3.1\"\n    \"@types/react\": \"npm:16 || 17 || 18\"\n    hoist-non-react-statics: \"npm:^3.3.2\"\n    intl-messageformat: \"npm:10.5.14\"\n    tslib: \"npm:^2.4.0\"\n  peerDependencies:\n    react: ^16.6.0 || 17 || 18\n    typescript: ^4.7 || 5\n  peerDependenciesMeta:\n    typescript:\n      optional: true\n  checksum: 10c0/7673507eb73ad4edd1593da7173cec68f316cf77037e0959900babd32d5984a39ba7fa10aaa0a23bcddb7b98daf7dd007cb73ddfc39127ede87c18ec780a519c\n  languageName: node\n  linkType: hard\n\n\"react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.6\":\n  version: 16.13.1\n  resolution: \"react-is@npm:16.13.1\"\n  checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1\n  languageName: node\n  linkType: hard\n\n\"react-is@npm:^18.0.0\":\n  version: 18.3.1\n  resolution: \"react-is@npm:18.3.1\"\n  checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072\n  languageName: node\n  linkType: hard\n\n\"react-json-pretty@npm:^2.2.0\":\n  version: 2.2.0\n  resolution: \"react-json-pretty@npm:2.2.0\"\n  dependencies:\n    prop-types: \"npm:^15.6.2\"\n  peerDependencies:\n    react: \">=15.0\"\n    react-dom: \">=15.0\"\n  checksum: 10c0/d0073a1406ab7bec0369ae31279d74f963dfb47d2890c4a51e69f23506c07cb4a761f5925042db3585634b91ee66f5d737b61153c5578c8750d4e4137b1e0395\n  languageName: node\n  linkType: hard\n\n\"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4\":\n  version: 3.0.4\n  resolution: \"react-lifecycles-compat@npm:3.0.4\"\n  checksum: 10c0/1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27\n  languageName: node\n  linkType: hard\n\n\"react-map-gl@npm:^7.1.6\":\n  version: 7.1.7\n  resolution: \"react-map-gl@npm:7.1.7\"\n  dependencies:\n    \"@maplibre/maplibre-gl-style-spec\": \"npm:^19.2.1\"\n    \"@types/mapbox-gl\": \"npm:>=1.0.0\"\n  peerDependencies:\n    mapbox-gl: \">=1.13.0\"\n    maplibre-gl: \">=1.13.0\"\n    react: \">=16.3.0\"\n    react-dom: \">=16.3.0\"\n  peerDependenciesMeta:\n    mapbox-gl:\n      optional: true\n    maplibre-gl:\n      optional: true\n  checksum: 10c0/bd289f2f466a905d1ef23459c49deb9b71d51aa569f59e098e64fe984c5b8c04cce589ebf59642e09142a61077bd995ae4ad1641318a15640a63ffaf28c148b8\n  languageName: node\n  linkType: hard\n\n\"react-markdown@npm:^10.0.0\":\n  version: 10.1.0\n  resolution: \"react-markdown@npm:10.1.0\"\n  dependencies:\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    devlop: \"npm:^1.0.0\"\n    hast-util-to-jsx-runtime: \"npm:^2.0.0\"\n    html-url-attributes: \"npm:^3.0.0\"\n    mdast-util-to-hast: \"npm:^13.0.0\"\n    remark-parse: \"npm:^11.0.0\"\n    remark-rehype: \"npm:^11.0.0\"\n    unified: \"npm:^11.0.0\"\n    unist-util-visit: \"npm:^5.0.0\"\n    vfile: \"npm:^6.0.0\"\n  peerDependencies:\n    \"@types/react\": \">=18\"\n    react: \">=18\"\n  checksum: 10c0/4a5dc7d15ca6d05e9ee95318c1904f83b111a76f7588c44f50f1d54d4c97193b84e4f64c4b592057c989228238a2590306cedd0c4d398e75da49262b2b5ae1bf\n  languageName: node\n  linkType: hard\n\n\"react-modal@npm:^3.12.1\":\n  version: 3.16.1\n  resolution: \"react-modal@npm:3.16.1\"\n  dependencies:\n    exenv: \"npm:^1.2.0\"\n    prop-types: \"npm:^15.7.2\"\n    react-lifecycles-compat: \"npm:^3.0.0\"\n    warning: \"npm:^4.0.3\"\n  peerDependencies:\n    react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18\n    react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18\n  checksum: 10c0/7b56e2c505b2b924736c471a34754a4211df40ac2d6fb0949cf095aea5e65d3326bd9f111fa7898acf40afa54f526809ad8aa47e02b8328663d11422568dc7b1\n  languageName: node\n  linkType: hard\n\n\"react-motion@npm:^0.5.2\":\n  version: 0.5.2\n  resolution: \"react-motion@npm:0.5.2\"\n  dependencies:\n    performance-now: \"npm:^0.2.0\"\n    prop-types: \"npm:^15.5.8\"\n    raf: \"npm:^3.1.0\"\n  peerDependencies:\n    react: ^0.14.9 || ^15.3.0 || ^16.0.0\n  checksum: 10c0/4ea6f1cc7079f0161fd786cc755133a822d87d9c0510369b8fb348d9ad602111efa2e3496dbcc390c967229e39e3eb5f6dd5dd6d3d124289443de31d6035a6c8\n  languageName: node\n  linkType: hard\n\n\"react-palm@npm:^3.3.8\":\n  version: 3.3.8\n  resolution: \"react-palm@npm:3.3.8\"\n  dependencies:\n    function.prototype.name: \"npm:^1.1.0\"\n    react-dom: \"npm:^16.4.2\"\n    react-reconciler: \"npm:^0.12.0\"\n    react-test-renderer: \"npm:^16.4.2\"\n  peerDependencies:\n    enzyme: ^3.6.0\n    enzyme-adapter-utils: ^1.13.0\n    react: ^16.4.1\n    react-test-renderer: ^16.4.1\n  checksum: 10c0/dd052acbeff68e55f184f2c181d5ceb9aa129598ff3116fa9e5921b4d720957e0314efe7346620e6f1971cbfc574f39fd3d79b1659b8bf560feb5f54fe03a7c5\n  languageName: node\n  linkType: hard\n\n\"react-reconciler@npm:^0.12.0\":\n  version: 0.12.0\n  resolution: \"react-reconciler@npm:0.12.0\"\n  dependencies:\n    fbjs: \"npm:^0.8.16\"\n    loose-envify: \"npm:^1.1.0\"\n    object-assign: \"npm:^4.1.1\"\n    prop-types: \"npm:^15.6.0\"\n  peerDependencies:\n    react: ^16.0.0\n  checksum: 10c0/2e3262b276751465fb833ef6c989cee0716742c2d90a1bafe574f19d8cacefdc3b94dda0f25c3e664711ca8b25d35a033a3bafa21d733d7334c2611d81a54694\n  languageName: node\n  linkType: hard\n\n\"react-redux@npm:^8.0.5\":\n  version: 8.1.3\n  resolution: \"react-redux@npm:8.1.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.12.1\"\n    \"@types/hoist-non-react-statics\": \"npm:^3.3.1\"\n    \"@types/use-sync-external-store\": \"npm:^0.0.3\"\n    hoist-non-react-statics: \"npm:^3.3.2\"\n    react-is: \"npm:^18.0.0\"\n    use-sync-external-store: \"npm:^1.0.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8 || ^17.0 || ^18.0\n    \"@types/react-dom\": ^16.8 || ^17.0 || ^18.0\n    react: ^16.8 || ^17.0 || ^18.0\n    react-dom: ^16.8 || ^17.0 || ^18.0\n    react-native: \">=0.59\"\n    redux: ^4 || ^5.0.0-beta.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n    \"@types/react-dom\":\n      optional: true\n    react-dom:\n      optional: true\n    react-native:\n      optional: true\n    redux:\n      optional: true\n  checksum: 10c0/64c8be2765568dc66a3c442a41dd0ed74fe048d5ceb7a4fe72e5bac3d3687996a7115f57b5156af7406521087065a0e60f9194318c8ca99c55e9ce48558980ce\n  languageName: node\n  linkType: hard\n\n\"react-resizable-panels@npm:^2.1.7\":\n  version: 2.1.7\n  resolution: \"react-resizable-panels@npm:2.1.7\"\n  peerDependencies:\n    react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc\n    react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc\n  checksum: 10c0/644a57960507b809f571bf8c95f07a04e3aeb9b20405a1d9d8cb6deb72d2462192e6e5e9751237347e7818179a619a6f65de4b355e276334d10b8cfe737ca4e1\n  languageName: node\n  linkType: hard\n\n\"react-router-redux@npm:^4.0.8\":\n  version: 4.0.8\n  resolution: \"react-router-redux@npm:4.0.8\"\n  checksum: 10c0/01b1b2f6b350cd428424f3bdccf0e7ff4a37f8426074a941690e7fce2ef391cae95401d1903eb297e7ba58c2c8fc12e2e71c87b494e5983e16297d99211647fb\n  languageName: node\n  linkType: hard\n\n\"react-router@npm:3.2.5\":\n  version: 3.2.5\n  resolution: \"react-router@npm:3.2.5\"\n  dependencies:\n    create-react-class: \"npm:^15.5.1\"\n    history: \"npm:^3.0.0\"\n    hoist-non-react-statics: \"npm:^2.3.1\"\n    invariant: \"npm:^2.2.1\"\n    loose-envify: \"npm:^1.2.0\"\n    prop-types: \"npm:^15.7.2\"\n    react-is: \"npm:^16.8.6\"\n    warning: \"npm:^3.0.0\"\n  peerDependencies:\n    react: ^0.14.0 || ^15.0.0 || ^16.0.0\n  checksum: 10c0/87687ac4ad414a93811ddfe1ac0c31207a79a297a8146def4de77c465f21162af9884c9a3aa874318fb4a68fc2f80f81fd0e85083da5f11f15497721528d6693\n  languageName: node\n  linkType: hard\n\n\"react-sortable-hoc@npm:^1.8.3\":\n  version: 1.11.0\n  resolution: \"react-sortable-hoc@npm:1.11.0\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.2.0\"\n    invariant: \"npm:^2.2.4\"\n    prop-types: \"npm:^15.5.7\"\n  peerDependencies:\n    prop-types: ^15.5.7\n    react: ^0.14.0 || ^15.0.0 || ^16.0.0\n    react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0\n  checksum: 10c0/d226693ba7b24e86be81c639fd489ccb461e7f3b625faa991f67b68e72bb9349bae57f2135c158691afe08c7459b5183a65a8fff709eb32f2b18c68d00caf522\n  languageName: node\n  linkType: hard\n\n\"react-test-renderer@npm:^16.4.2\":\n  version: 16.14.0\n  resolution: \"react-test-renderer@npm:16.14.0\"\n  dependencies:\n    object-assign: \"npm:^4.1.1\"\n    prop-types: \"npm:^15.6.2\"\n    react-is: \"npm:^16.8.6\"\n    scheduler: \"npm:^0.19.1\"\n  peerDependencies:\n    react: ^16.14.0\n  checksum: 10c0/119e3ce5509c3443393ca750e39dd4ac9ee9ddfaafca58c9067b477447edc2badb75660b9fea7e9ddef012e37bbba427681cf6f8d3fde61b8054655a133bfbf5\n  languageName: node\n  linkType: hard\n\n\"react-textarea-autosize@npm:^8.5.3\":\n  version: 8.5.7\n  resolution: \"react-textarea-autosize@npm:8.5.7\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.20.13\"\n    use-composed-ref: \"npm:^1.3.0\"\n    use-latest: \"npm:^1.2.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  checksum: 10c0/ff004797ea28faca442460c42b30042d4c34a140f324eeeddee74508688dbc0f98966d21282c945630655006ad28a87edbcb59e6da7f9e762f4f3042c72f9f24\n  languageName: node\n  linkType: hard\n\n\"react-time-picker@npm:^6.2.0\":\n  version: 6.6.0\n  resolution: \"react-time-picker@npm:6.6.0\"\n  dependencies:\n    \"@wojtekmaj/date-utils\": \"npm:^1.1.3\"\n    clsx: \"npm:^2.0.0\"\n    get-user-locale: \"npm:^2.2.1\"\n    make-event-props: \"npm:^1.6.0\"\n    prop-types: \"npm:^15.6.0\"\n    react-clock: \"npm:^4.5.0\"\n    react-fit: \"npm:^1.7.0\"\n    update-input-width: \"npm:^1.4.0\"\n  peerDependencies:\n    \"@types/react\": ^16.8.0 || ^17.0.0 || ^18.0.0\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/748935912991864cba126c5436e3d04680dfebafd8f6f2d7588c6904460a9568176177dfbef0e6025a5ef7b89f37e3e726c0037196b7a42866dc272ab45b28bf\n  languageName: node\n  linkType: hard\n\n\"react-tooltip@npm:^4.2.17\":\n  version: 4.5.1\n  resolution: \"react-tooltip@npm:4.5.1\"\n  dependencies:\n    prop-types: \"npm:^15.8.1\"\n    uuid: \"npm:^7.0.3\"\n  peerDependencies:\n    react: \">=16.0.0\"\n    react-dom: \">=16.0.0\"\n  checksum: 10c0/fc6d9046c5cc3fa05e63c4015a8dfc0170097ca3a5e8fa2147242405892d05eea3630ceee0c72b6ae2420f7893f4bff0f5fa0a2828937a988109a8258c3cfae6\n  languageName: node\n  linkType: hard\n\n\"react-virtualized@npm:^9.21.0\":\n  version: 9.22.5\n  resolution: \"react-virtualized@npm:9.22.5\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.7.2\"\n    clsx: \"npm:^1.0.4\"\n    dom-helpers: \"npm:^5.1.3\"\n    loose-envify: \"npm:^1.4.0\"\n    prop-types: \"npm:^15.7.2\"\n    react-lifecycles-compat: \"npm:^3.0.4\"\n  peerDependencies:\n    react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0\n    react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0\n  checksum: 10c0/b0444b472f317dce61119c07426c5e9ebfe5125d049996678da922717715a1aa83df755aa36877f4b1718aa2e181d22f15ebb807ee356418c56f922f865628c1\n  languageName: node\n  linkType: hard\n\n\"react-virtualized@npm:^9.22.5\":\n  version: 9.22.6\n  resolution: \"react-virtualized@npm:9.22.6\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.7.2\"\n    clsx: \"npm:^1.0.4\"\n    dom-helpers: \"npm:^5.1.3\"\n    loose-envify: \"npm:^1.4.0\"\n    prop-types: \"npm:^15.7.2\"\n    react-lifecycles-compat: \"npm:^3.0.4\"\n  peerDependencies:\n    react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n    react-dom: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  checksum: 10c0/0c4fbe86e0c121adcdb7a3f322601eee4661afe65e31ef767c6d876016b1e7043fdad7998b4fa0252eaf73ffb6c14effcf0f729d154cd15304a8b15ad42b7b06\n  languageName: node\n  linkType: hard\n\n\"react-vis@npm:1.11.7\":\n  version: 1.11.7\n  resolution: \"react-vis@npm:1.11.7\"\n  dependencies:\n    d3-array: \"npm:^1.2.0\"\n    d3-collection: \"npm:^1.0.3\"\n    d3-color: \"npm:^1.0.3\"\n    d3-contour: \"npm:^1.1.0\"\n    d3-format: \"npm:^1.2.0\"\n    d3-geo: \"npm:^1.6.4\"\n    d3-hexbin: \"npm:^0.2.2\"\n    d3-hierarchy: \"npm:^1.1.4\"\n    d3-interpolate: \"npm:^1.1.4\"\n    d3-sankey: \"npm:^0.7.1\"\n    d3-scale: \"npm:^1.0.5\"\n    d3-shape: \"npm:^1.1.0\"\n    d3-voronoi: \"npm:^1.1.2\"\n    deep-equal: \"npm:^1.0.1\"\n    global: \"npm:^4.3.1\"\n    hoek: \"npm:4.2.1\"\n    prop-types: \"npm:^15.5.8\"\n    react-motion: \"npm:^0.5.2\"\n  peerDependencies:\n    react: 15.3.0 - 16.x\n  checksum: 10c0/1cfbe734806bf1c5cd21b49e42c4a1cb7b239c2943dbbc23fdbee468acfc72c22e0fafd46a346e8686040ed7d454abab4d9372d8756a78972743417cf1a5ac0f\n  languageName: node\n  linkType: hard\n\n\"react@npm:^18.2.0\":\n  version: 18.3.1\n  resolution: \"react@npm:18.3.1\"\n  dependencies:\n    loose-envify: \"npm:^1.1.0\"\n  checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3\n  languageName: node\n  linkType: hard\n\n\"reactcss@npm:^1.2.0\":\n  version: 1.2.3\n  resolution: \"reactcss@npm:1.2.3\"\n  dependencies:\n    lodash: \"npm:^4.0.1\"\n  checksum: 10c0/a3aceb0fbfd58312f0c7fadbe92920e6536ec24d17ebee44fd4a14dd831d413fff5c2df0e85579b440667935e57a06876325cbd1368d3131824a8c2ec43b7978\n  languageName: node\n  linkType: hard\n\n\"read-cache@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"read-cache@npm:1.0.0\"\n  dependencies:\n    pify: \"npm:^2.3.0\"\n  checksum: 10c0/90cb2750213c7dd7c80cb420654344a311fdec12944e81eb912cd82f1bc92aea21885fa6ce442e3336d9fccd663b8a7a19c46d9698e6ca55620848ab932da814\n  languageName: node\n  linkType: hard\n\n\"read-pkg-up@npm:^7.0.1\":\n  version: 7.0.1\n  resolution: \"read-pkg-up@npm:7.0.1\"\n  dependencies:\n    find-up: \"npm:^4.1.0\"\n    read-pkg: \"npm:^5.2.0\"\n    type-fest: \"npm:^0.8.1\"\n  checksum: 10c0/82b3ac9fd7c6ca1bdc1d7253eb1091a98ff3d195ee0a45386582ce3e69f90266163c34121e6a0a02f1630073a6c0585f7880b3865efcae9c452fa667f02ca385\n  languageName: node\n  linkType: hard\n\n\"read-pkg@npm:^5.2.0\":\n  version: 5.2.0\n  resolution: \"read-pkg@npm:5.2.0\"\n  dependencies:\n    \"@types/normalize-package-data\": \"npm:^2.4.0\"\n    normalize-package-data: \"npm:^2.5.0\"\n    parse-json: \"npm:^5.0.0\"\n    type-fest: \"npm:^0.6.0\"\n  checksum: 10c0/b51a17d4b51418e777029e3a7694c9bd6c578a5ab99db544764a0b0f2c7c0f58f8a6bc101f86a6fceb8ba6d237d67c89acf6170f6b98695d0420ddc86cf109fb\n  languageName: node\n  linkType: hard\n\n\"readdirp@npm:~3.6.0\":\n  version: 3.6.0\n  resolution: \"readdirp@npm:3.6.0\"\n  dependencies:\n    picomatch: \"npm:^2.2.1\"\n  checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b\n  languageName: node\n  linkType: hard\n\n\"redent@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"redent@npm:3.0.0\"\n  dependencies:\n    indent-string: \"npm:^4.0.0\"\n    strip-indent: \"npm:^3.0.0\"\n  checksum: 10c0/d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae\n  languageName: node\n  linkType: hard\n\n\"reduce-reducers@npm:^0.4.3\":\n  version: 0.4.3\n  resolution: \"reduce-reducers@npm:0.4.3\"\n  checksum: 10c0/6e0f8b5d92d9ca66b36b5462c9f31c15b24aca2b761effb7e9740264edde86e069949d4ee8e408b2c0b3c54c939f8e0b36c9791b102fc50ed62e9276d8061e17\n  languageName: node\n  linkType: hard\n\n\"redux-actions@npm:^2.2.1\":\n  version: 2.6.5\n  resolution: \"redux-actions@npm:2.6.5\"\n  dependencies:\n    invariant: \"npm:^2.2.4\"\n    just-curry-it: \"npm:^3.1.0\"\n    loose-envify: \"npm:^1.4.0\"\n    reduce-reducers: \"npm:^0.4.3\"\n    to-camel-case: \"npm:^1.0.0\"\n  checksum: 10c0/10a05b22847e2fe8c3bda9257322a10a0adcc803970e8b15db1b2f823143f9d3ca1aa2e55ae27fc945ac90c775f25302a2f84c44f6f44fb63984066f3f7ca478\n  languageName: node\n  linkType: hard\n\n\"redux-logger@npm:^3.0.6\":\n  version: 3.0.6\n  resolution: \"redux-logger@npm:3.0.6\"\n  dependencies:\n    deep-diff: \"npm:^0.3.5\"\n  checksum: 10c0/65eb71a1c72d9636368672a684bde62c746e64c19c8b92e3f00bdf5cf240ea1695eccb95a0fa3d5044f6a86cbf11fd35dc5150fd3351fa53c72721c336e9098f\n  languageName: node\n  linkType: hard\n\n\"redux-thunk@npm:^1.0.0\":\n  version: 1.0.3\n  resolution: \"redux-thunk@npm:1.0.3\"\n  checksum: 10c0/b708c4df202f906edbe79a1f07fe78a69fe184f6059a0abab2c42f016a5b2b9af96b821d859a10f949ae1aee16f745ab6d446753b75d999867fe75e87eae3e8f\n  languageName: node\n  linkType: hard\n\n\"redux-thunk@npm:^2.4.2\":\n  version: 2.4.2\n  resolution: \"redux-thunk@npm:2.4.2\"\n  peerDependencies:\n    redux: ^4\n  checksum: 10c0/e202d6ef7dfa7df08ed24cb221aa89d6c84dbaa7d65fe90dbd8e826d0c10d801f48388f9a7598a4fd970ecbc93d335014570a61ca7bc8bf569eab5de77b31a3c\n  languageName: node\n  linkType: hard\n\n\"redux@npm:^4.0.0, redux@npm:^4.2.1\":\n  version: 4.2.1\n  resolution: \"redux@npm:4.2.1\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.9.2\"\n  checksum: 10c0/136d98b3d5dbed1cd6279c8c18a6a74c416db98b8a432a46836bdd668475de6279a2d4fd9d1363f63904e00f0678a8a3e7fa532c897163340baf1e71bb42c742\n  languageName: node\n  linkType: hard\n\n\"regenerator-runtime@npm:^0.13.7\":\n  version: 0.13.11\n  resolution: \"regenerator-runtime@npm:0.13.11\"\n  checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24\n  languageName: node\n  linkType: hard\n\n\"regenerator-runtime@npm:^0.14.0\":\n  version: 0.14.1\n  resolution: \"regenerator-runtime@npm:0.14.1\"\n  checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4\n  languageName: node\n  linkType: hard\n\n\"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2\":\n  version: 1.5.2\n  resolution: \"regexp.prototype.flags@npm:1.5.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.6\"\n    define-properties: \"npm:^1.2.1\"\n    es-errors: \"npm:^1.3.0\"\n    set-function-name: \"npm:^2.0.1\"\n  checksum: 10c0/0f3fc4f580d9c349f8b560b012725eb9c002f36daa0041b3fbf6f4238cb05932191a4d7d5db3b5e2caa336d5150ad0402ed2be81f711f9308fe7e1a9bf9bd552\n  languageName: node\n  linkType: hard\n\n\"remark-gfm@npm:^4.0.1\":\n  version: 4.0.1\n  resolution: \"remark-gfm@npm:4.0.1\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    mdast-util-gfm: \"npm:^3.0.0\"\n    micromark-extension-gfm: \"npm:^3.0.0\"\n    remark-parse: \"npm:^11.0.0\"\n    remark-stringify: \"npm:^11.0.0\"\n    unified: \"npm:^11.0.0\"\n  checksum: 10c0/427ecc6af3e76222662061a5f670a3e4e33ec5fffe2cabf04034da6a3f9a1bda1fc023e838a636385ba314e66e2bebbf017ca61ebea357eb0f5200fe0625a4b7\n  languageName: node\n  linkType: hard\n\n\"remark-parse@npm:^11.0.0\":\n  version: 11.0.0\n  resolution: \"remark-parse@npm:11.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    mdast-util-from-markdown: \"npm:^2.0.0\"\n    micromark-util-types: \"npm:^2.0.0\"\n    unified: \"npm:^11.0.0\"\n  checksum: 10c0/6eed15ddb8680eca93e04fcb2d1b8db65a743dcc0023f5007265dda558b09db595a087f622062ccad2630953cd5cddc1055ce491d25a81f3317c858348a8dd38\n  languageName: node\n  linkType: hard\n\n\"remark-rehype@npm:^11.0.0\":\n  version: 11.1.2\n  resolution: \"remark-rehype@npm:11.1.2\"\n  dependencies:\n    \"@types/hast\": \"npm:^3.0.0\"\n    \"@types/mdast\": \"npm:^4.0.0\"\n    mdast-util-to-hast: \"npm:^13.0.0\"\n    unified: \"npm:^11.0.0\"\n    vfile: \"npm:^6.0.0\"\n  checksum: 10c0/f9eccacfb596d9605581dc05bfad28635d6ded5dd0a18e88af5fd4df0d3fcf9612e1501d4513bc2164d833cfe9636dab20400080b09e53f155c6e1442a1231fb\n  languageName: node\n  linkType: hard\n\n\"remark-stringify@npm:^11.0.0\":\n  version: 11.0.0\n  resolution: \"remark-stringify@npm:11.0.0\"\n  dependencies:\n    \"@types/mdast\": \"npm:^4.0.0\"\n    mdast-util-to-markdown: \"npm:^2.0.0\"\n    unified: \"npm:^11.0.0\"\n  checksum: 10c0/0cdb37ce1217578f6f847c7ec9f50cbab35df5b9e3903d543e74b405404e67c07defcb23cd260a567b41b769400f6de03c2c3d9cd6ae7a6707d5c8d89ead489f\n  languageName: node\n  linkType: hard\n\n\"reselect@npm:^4.1.0, reselect@npm:^4.1.8\":\n  version: 4.1.8\n  resolution: \"reselect@npm:4.1.8\"\n  checksum: 10c0/06a305a504affcbb67dd0561ddc8306b35796199c7e15b38934c80606938a021eadcf68cfd58e7bb5e17786601c37602a3362a4665c7bf0a96c1041ceee9d0b7\n  languageName: node\n  linkType: hard\n\n\"resize-observer-polyfill@npm:^1.5.1\":\n  version: 1.5.1\n  resolution: \"resize-observer-polyfill@npm:1.5.1\"\n  checksum: 10c0/5e882475067f0b97dc07e0f37c3e335ac5bc3520d463f777cec7e894bb273eddbfecb857ae668e6fb6881fd6f6bb7148246967172139302da50fa12ea3a15d95\n  languageName: node\n  linkType: hard\n\n\"resolve-alpn@npm:^1.0.0\":\n  version: 1.2.1\n  resolution: \"resolve-alpn@npm:1.2.1\"\n  checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4\n  languageName: node\n  linkType: hard\n\n\"resolve-protobuf-schema@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"resolve-protobuf-schema@npm:2.1.0\"\n  dependencies:\n    protocol-buffers-schema: \"npm:^3.3.1\"\n  checksum: 10c0/8e656b9072b1c001952f851251413bc79d8c771c3015f607b75e1ca3b8bd7c4396068dd19cdbb3019affa03f6457d2c0fd38d981ffd714215cd2e7c2b67221a7\n  languageName: node\n  linkType: hard\n\n\"resolve-url@npm:^0.2.1\":\n  version: 0.2.1\n  resolution: \"resolve-url@npm:0.2.1\"\n  checksum: 10c0/c285182cfcddea13a12af92129ce0569be27fb0074ffaefbd3ba3da2eac2acecdfc996d435c4982a9fa2b4708640e52837c9153a5ab9255886a00b0b9e8d2a54\n  languageName: node\n  linkType: hard\n\n\"resolve@npm:^1.1.7, resolve@npm:^1.22.8\":\n  version: 1.22.10\n  resolution: \"resolve@npm:1.22.10\"\n  dependencies:\n    is-core-module: \"npm:^2.16.0\"\n    path-parse: \"npm:^1.0.7\"\n    supports-preserve-symlinks-flag: \"npm:^1.0.0\"\n  bin:\n    resolve: bin/resolve\n  checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203\n  languageName: node\n  linkType: hard\n\n\"resolve@npm:^1.10.0\":\n  version: 1.22.8\n  resolution: \"resolve@npm:1.22.8\"\n  dependencies:\n    is-core-module: \"npm:^2.13.0\"\n    path-parse: \"npm:^1.0.7\"\n    supports-preserve-symlinks-flag: \"npm:^1.0.0\"\n  bin:\n    resolve: bin/resolve\n  checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a\n  languageName: node\n  linkType: hard\n\n\"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>\":\n  version: 1.22.10\n  resolution: \"resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d\"\n  dependencies:\n    is-core-module: \"npm:^2.16.0\"\n    path-parse: \"npm:^1.0.7\"\n    supports-preserve-symlinks-flag: \"npm:^1.0.0\"\n  bin:\n    resolve: bin/resolve\n  checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939\n  languageName: node\n  linkType: hard\n\n\"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>\":\n  version: 1.22.8\n  resolution: \"resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d\"\n  dependencies:\n    is-core-module: \"npm:^2.13.0\"\n    path-parse: \"npm:^1.0.7\"\n    supports-preserve-symlinks-flag: \"npm:^1.0.0\"\n  bin:\n    resolve: bin/resolve\n  checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729\n  languageName: node\n  linkType: hard\n\n\"responselike@npm:^2.0.0\":\n  version: 2.0.1\n  resolution: \"responselike@npm:2.0.1\"\n  dependencies:\n    lowercase-keys: \"npm:^2.0.0\"\n  checksum: 10c0/360b6deb5f101a9f8a4174f7837c523c3ec78b7ca8a7c1d45a1062b303659308a23757e318b1e91ed8684ad1205721142dd664d94771cd63499353fd4ee732b5\n  languageName: node\n  linkType: hard\n\n\"retry@npm:^0.12.0\":\n  version: 0.12.0\n  resolution: \"retry@npm:0.12.0\"\n  checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe\n  languageName: node\n  linkType: hard\n\n\"retry@npm:^0.13.1\":\n  version: 0.13.1\n  resolution: \"retry@npm:0.13.1\"\n  checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772\n  languageName: node\n  linkType: hard\n\n\"reusify@npm:^1.0.4\":\n  version: 1.0.4\n  resolution: \"reusify@npm:1.0.4\"\n  checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107\n  languageName: node\n  linkType: hard\n\n\"rimraf@npm:^5.0.5\":\n  version: 5.0.10\n  resolution: \"rimraf@npm:5.0.10\"\n  dependencies:\n    glob: \"npm:^10.3.7\"\n  bin:\n    rimraf: dist/esm/bin.mjs\n  checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc\n  languageName: node\n  linkType: hard\n\n\"robust-predicates@npm:^3.0.2\":\n  version: 3.0.2\n  resolution: \"robust-predicates@npm:3.0.2\"\n  checksum: 10c0/4ecd53649f1c2d49529c85518f2fa69ffb2f7a4453f7fd19c042421c7b4d76c3efb48bc1c740c8f7049346d7cb58cf08ee0c9adaae595cc23564d360adb1fde4\n  languageName: node\n  linkType: hard\n\n\"root-workspace-0b6124@workspace:.\":\n  version: 0.0.0-use.local\n  resolution: \"root-workspace-0b6124@workspace:.\"\n  dependencies:\n    \"@auth0/auth0-spa-js\": \"npm:^2.1.2\"\n    \"@carto/toolkit\": \"npm:0.0.1-rc.18\"\n    \"@dotenv-run/esbuild\": \"npm:^1.5.0\"\n    \"@emotion/is-prop-valid\": \"npm:^1.2.1\"\n    \"@kepler.gl/actions\": \"npm:^3.2.0\"\n    \"@kepler.gl/ai-assistant\": \"npm:^3.2.0\"\n    \"@kepler.gl/cloud-providers\": \"npm:^3.2.0\"\n    \"@kepler.gl/components\": \"npm:^3.2.0\"\n    \"@kepler.gl/constants\": \"npm:^3.2.0\"\n    \"@kepler.gl/duckdb\": \"npm:^3.2.0\"\n    \"@kepler.gl/processors\": \"npm:^3.2.0\"\n    \"@kepler.gl/reducers\": \"npm:^3.2.0\"\n    \"@kepler.gl/schemas\": \"npm:^3.2.0\"\n    \"@kepler.gl/styles\": \"npm:^3.2.0\"\n    \"@kepler.gl/utils\": \"npm:^3.2.0\"\n    \"@loaders.gl/arrow\": \"npm:^4.3.2\"\n    \"@loaders.gl/core\": \"npm:^4.3.2\"\n    \"@loaders.gl/csv\": \"npm:^4.3.2\"\n    \"@loaders.gl/json\": \"npm:^4.3.2\"\n    \"@loaders.gl/parquet\": \"npm:^4.3.2\"\n    \"@openassistant/core\": \"npm:^0.5.17\"\n    \"@openassistant/ui\": \"npm:^0.5.17\"\n    \"@types/classnames\": \"npm:^2.3.1\"\n    \"@types/keymirror\": \"npm:^0.1.1\"\n    apache-arrow: \"npm:>=15.0.0\"\n    classnames: \"npm:^2.2.1\"\n    d3-format: \"npm:^2.0.0\"\n    dropbox: \"npm:^4.0.12\"\n    esbuild: \"npm:^0.25.0\"\n    esbuild-plugin-replace: \"npm:^1.4.0\"\n    global: \"npm:^4.3.0\"\n    keymirror: \"npm:^0.1.1\"\n    markdown-to-jsx: \"npm:^7.7.6\"\n    prop-types: \"npm:^15.6.0\"\n    react: \"npm:^18.2.0\"\n    react-dom: \"npm:^18.2.0\"\n    react-intl: \"npm:^6.3.0\"\n    react-redux: \"npm:^8.0.5\"\n    react-resizable-panels: \"npm:^2.1.7\"\n    react-router: \"npm:3.2.5\"\n    react-router-redux: \"npm:^4.0.8\"\n    react-virtualized: \"npm:^9.21.0\"\n    redux: \"npm:^4.2.1\"\n    redux-actions: \"npm:^2.2.1\"\n    redux-logger: \"npm:^3.0.6\"\n    redux-thunk: \"npm:^1.0.0\"\n    styled-components: \"npm:6.1.8\"\n    usehooks-ts: \"npm:^3.1.0\"\n  languageName: unknown\n  linkType: soft\n\n\"run-parallel@npm:^1.1.9\":\n  version: 1.2.0\n  resolution: \"run-parallel@npm:1.2.0\"\n  dependencies:\n    queue-microtask: \"npm:^1.2.2\"\n  checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39\n  languageName: node\n  linkType: hard\n\n\"rw@npm:1, rw@npm:^1.3.3\":\n  version: 1.3.3\n  resolution: \"rw@npm:1.3.3\"\n  checksum: 10c0/b1e1ef37d1e79d9dc7050787866e30b6ddcb2625149276045c262c6b4d53075ddc35f387a856a8e76f0d0df59f4cd58fe24707e40797ebee66e542b840ed6a53\n  languageName: node\n  linkType: hard\n\n\"s2-geometry@npm:^1.2.10\":\n  version: 1.2.10\n  resolution: \"s2-geometry@npm:1.2.10\"\n  dependencies:\n    long: \"npm:^3.2.0\"\n  checksum: 10c0/4cc50d9ee56a15731d8952755ef30f22c5139158c77b4ee0d015fc3676049ffc632d7e6fd489aeeabb04b8ded59d1577b8a270b0317f675a31b70adfecd9e9df\n  languageName: node\n  linkType: hard\n\n\"safe-array-concat@npm:^1.1.2\":\n  version: 1.1.2\n  resolution: \"safe-array-concat@npm:1.1.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    get-intrinsic: \"npm:^1.2.4\"\n    has-symbols: \"npm:^1.0.3\"\n    isarray: \"npm:^2.0.5\"\n  checksum: 10c0/12f9fdb01c8585e199a347eacc3bae7b5164ae805cdc8c6707199dbad5b9e30001a50a43c4ee24dc9ea32dbb7279397850e9208a7e217f4d8b1cf5d90129dec9\n  languageName: node\n  linkType: hard\n\n\"safe-regex-test@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"safe-regex-test@npm:1.0.3\"\n  dependencies:\n    call-bind: \"npm:^1.0.6\"\n    es-errors: \"npm:^1.3.0\"\n    is-regex: \"npm:^1.1.4\"\n  checksum: 10c0/900bf7c98dc58f08d8523b7012b468e4eb757afa624f198902c0643d7008ba777b0bdc35810ba0b758671ce887617295fb742b3f3968991b178ceca54cb07603\n  languageName: node\n  linkType: hard\n\n\"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0\":\n  version: 2.1.2\n  resolution: \"safer-buffer@npm:2.1.2\"\n  checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4\n  languageName: node\n  linkType: hard\n\n\"scheduler@npm:^0.19.1\":\n  version: 0.19.1\n  resolution: \"scheduler@npm:0.19.1\"\n  dependencies:\n    loose-envify: \"npm:^1.1.0\"\n    object-assign: \"npm:^4.1.1\"\n  checksum: 10c0/9658932a73148a93d791c064b331d9690ddfecc4de25bcd6c9b89f5f1166e3d23d9c31c1595d66565e5ffbb34d47035cb14841aba6444bc266bfcd215cefe9c0\n  languageName: node\n  linkType: hard\n\n\"scheduler@npm:^0.23.2\":\n  version: 0.23.2\n  resolution: \"scheduler@npm:0.23.2\"\n  dependencies:\n    loose-envify: \"npm:^1.1.0\"\n  checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78\n  languageName: node\n  linkType: hard\n\n\"scroll-into-view-if-needed@npm:3.0.10\":\n  version: 3.0.10\n  resolution: \"scroll-into-view-if-needed@npm:3.0.10\"\n  dependencies:\n    compute-scroll-into-view: \"npm:^3.0.2\"\n  checksum: 10c0/8bce433c0139cfd74d5b784113251f1c1783bcd4152c2f3a7e4ca6ff73d644eafd891747bdfb02456d7437835991b142ddd2cfa8c6ef78dd0d7fd6eb4c7c70b4\n  languageName: node\n  linkType: hard\n\n\"secure-json-parse@npm:^2.7.0\":\n  version: 2.7.0\n  resolution: \"secure-json-parse@npm:2.7.0\"\n  checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4\n  languageName: node\n  linkType: hard\n\n\"semver@npm:2 || 3 || 4 || 5\":\n  version: 5.7.2\n  resolution: \"semver@npm:5.7.2\"\n  bin:\n    semver: bin/semver\n  checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25\n  languageName: node\n  linkType: hard\n\n\"semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.6.3\":\n  version: 7.6.3\n  resolution: \"semver@npm:7.6.3\"\n  bin:\n    semver: bin/semver.js\n  checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf\n  languageName: node\n  linkType: hard\n\n\"set-function-length@npm:^1.2.1\":\n  version: 1.2.2\n  resolution: \"set-function-length@npm:1.2.2\"\n  dependencies:\n    define-data-property: \"npm:^1.1.4\"\n    es-errors: \"npm:^1.3.0\"\n    function-bind: \"npm:^1.1.2\"\n    get-intrinsic: \"npm:^1.2.4\"\n    gopd: \"npm:^1.0.1\"\n    has-property-descriptors: \"npm:^1.0.2\"\n  checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c\n  languageName: node\n  linkType: hard\n\n\"set-function-name@npm:^2.0.1\":\n  version: 2.0.2\n  resolution: \"set-function-name@npm:2.0.2\"\n  dependencies:\n    define-data-property: \"npm:^1.1.4\"\n    es-errors: \"npm:^1.3.0\"\n    functions-have-names: \"npm:^1.2.3\"\n    has-property-descriptors: \"npm:^1.0.2\"\n  checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316\n  languageName: node\n  linkType: hard\n\n\"set-value@npm:^2.0.1\":\n  version: 2.0.1\n  resolution: \"set-value@npm:2.0.1\"\n  dependencies:\n    extend-shallow: \"npm:^2.0.1\"\n    is-extendable: \"npm:^0.1.1\"\n    is-plain-object: \"npm:^2.0.3\"\n    split-string: \"npm:^3.0.1\"\n  checksum: 10c0/4c40573c4f6540456e4b38b95f570272c4cfbe1d12890ad4057886da8535047cd772dfadf5b58e2e87aa244dfb4c57e3586f6716b976fc47c5144b6b09e1811b\n  languageName: node\n  linkType: hard\n\n\"setimmediate@npm:^1.0.5\":\n  version: 1.0.5\n  resolution: \"setimmediate@npm:1.0.5\"\n  checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49\n  languageName: node\n  linkType: hard\n\n\"shallowequal@npm:1.1.0\":\n  version: 1.1.0\n  resolution: \"shallowequal@npm:1.1.0\"\n  checksum: 10c0/b926efb51cd0f47aa9bc061add788a4a650550bbe50647962113a4579b60af2abe7b62f9b02314acc6f97151d4cf87033a2b15fc20852fae306d1a095215396c\n  languageName: node\n  linkType: hard\n\n\"shebang-command@npm:^2.0.0\":\n  version: 2.0.0\n  resolution: \"shebang-command@npm:2.0.0\"\n  dependencies:\n    shebang-regex: \"npm:^3.0.0\"\n  checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e\n  languageName: node\n  linkType: hard\n\n\"shebang-regex@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"shebang-regex@npm:3.0.0\"\n  checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690\n  languageName: node\n  linkType: hard\n\n\"side-channel@npm:^1.0.4\":\n  version: 1.0.6\n  resolution: \"side-channel@npm:1.0.6\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    es-errors: \"npm:^1.3.0\"\n    get-intrinsic: \"npm:^1.2.4\"\n    object-inspect: \"npm:^1.13.1\"\n  checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f\n  languageName: node\n  linkType: hard\n\n\"signal-exit@npm:^4.0.1\":\n  version: 4.1.0\n  resolution: \"signal-exit@npm:4.1.0\"\n  checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83\n  languageName: node\n  linkType: hard\n\n\"simple-statistics@npm:^7.8.7\":\n  version: 7.8.8\n  resolution: \"simple-statistics@npm:7.8.8\"\n  checksum: 10c0/728083b6d6f478959436223164ff94c4a1fb627e601669150afff280620aea61d575826583e1ed3904f7bb86e3d9572dac3769f213707df740889f84529de0a6\n  languageName: node\n  linkType: hard\n\n\"simple-swizzle@npm:^0.2.2\":\n  version: 0.2.2\n  resolution: \"simple-swizzle@npm:0.2.2\"\n  dependencies:\n    is-arrayish: \"npm:^0.3.1\"\n  checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308\n  languageName: node\n  linkType: hard\n\n\"simple-wcswidth@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"simple-wcswidth@npm:1.0.1\"\n  checksum: 10c0/2befead4c97134424aa3fba593a81daa9934fd61b9e4c65374b57ac5eecc2f2be1984b017bbdbc919923e19b77f2fcbdb94434789b9643fa8c3fde3a2a6a4b6f\n  languageName: node\n  linkType: hard\n\n\"size-sensor@npm:^1.0.1\":\n  version: 1.0.2\n  resolution: \"size-sensor@npm:1.0.2\"\n  checksum: 10c0/2cb020cd54ae7bea76a0600ba82c1a09daf51e8bec6f96355e17b4806eb24003626e3349d6cb091f821ed5fbba193f59ca59f0ea7f6dd8b75d7c20b96c465c26\n  languageName: node\n  linkType: hard\n\n\"smart-buffer@npm:^4.2.0\":\n  version: 4.2.0\n  resolution: \"smart-buffer@npm:4.2.0\"\n  checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539\n  languageName: node\n  linkType: hard\n\n\"snappyjs@npm:^0.6.0, snappyjs@npm:^0.6.1\":\n  version: 0.6.1\n  resolution: \"snappyjs@npm:0.6.1\"\n  checksum: 10c0/6fa62bb62adfc2bad577db78c58b1c52aba5482f749018ff3000f13bf660f13ec5f16ab80673ce4e8792902a16c30e5fb40a3b67dbca9af525658c9d343c5b1c\n  languageName: node\n  linkType: hard\n\n\"socks-proxy-agent@npm:^8.0.3\":\n  version: 8.0.5\n  resolution: \"socks-proxy-agent@npm:8.0.5\"\n  dependencies:\n    agent-base: \"npm:^7.1.2\"\n    debug: \"npm:^4.3.4\"\n    socks: \"npm:^2.8.3\"\n  checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6\n  languageName: node\n  linkType: hard\n\n\"socks@npm:^2.8.3\":\n  version: 2.8.3\n  resolution: \"socks@npm:2.8.3\"\n  dependencies:\n    ip-address: \"npm:^9.0.5\"\n    smart-buffer: \"npm:^4.2.0\"\n  checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7\n  languageName: node\n  linkType: hard\n\n\"sort-asc@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"sort-asc@npm:0.2.0\"\n  checksum: 10c0/637d08a3f42d9b20ad489fa50970cc46ff606764ecfc95f00e607151d38a4b9086b74366ef99177783ff2da75ad821dff5d76557caa7206e5b3082e1e743e7cf\n  languageName: node\n  linkType: hard\n\n\"sort-desc@npm:^0.2.0\":\n  version: 0.2.0\n  resolution: \"sort-desc@npm:0.2.0\"\n  checksum: 10c0/d9b49e49c8aa1d443ace95a86845f7ad9c9aeb9bb231f43adf7af22109bbd05b11983497f7c449d88ee6c499f43cf0591cde59975f5321c7883c6a395dce8482\n  languageName: node\n  linkType: hard\n\n\"sort-object@npm:^3.0.3\":\n  version: 3.0.3\n  resolution: \"sort-object@npm:3.0.3\"\n  dependencies:\n    bytewise: \"npm:^1.1.0\"\n    get-value: \"npm:^2.0.2\"\n    is-extendable: \"npm:^0.1.1\"\n    sort-asc: \"npm:^0.2.0\"\n    sort-desc: \"npm:^0.2.0\"\n    union-value: \"npm:^1.0.1\"\n  checksum: 10c0/b19518e3659875d0997bfb6e4a2bc681a71b3cc30a39b7ab673fcb5c32e65b2c9d4b66eba6c97c29c78b5fc437306ec0e0a85a381565d54eff8716388535db5d\n  languageName: node\n  linkType: hard\n\n\"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1\":\n  version: 1.2.1\n  resolution: \"source-map-js@npm:1.2.1\"\n  checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf\n  languageName: node\n  linkType: hard\n\n\"sourcemap-codec@npm:^1.4.8\":\n  version: 1.4.8\n  resolution: \"sourcemap-codec@npm:1.4.8\"\n  checksum: 10c0/f099279fdaae070ff156df7414bbe39aad69cdd615454947ed3e19136bfdfcb4544952685ee73f56e17038f4578091e12b17b283ed8ac013882916594d95b9e6\n  languageName: node\n  linkType: hard\n\n\"space-separated-tokens@npm:^2.0.0\":\n  version: 2.0.2\n  resolution: \"space-separated-tokens@npm:2.0.2\"\n  checksum: 10c0/6173e1d903dca41dcab6a2deed8b4caf61bd13b6d7af8374713500570aa929ff9414ae09a0519f4f8772df993300305a395d4871f35bc4ca72b6db57e1f30af8\n  languageName: node\n  linkType: hard\n\n\"spdx-correct@npm:^3.0.0\":\n  version: 3.2.0\n  resolution: \"spdx-correct@npm:3.2.0\"\n  dependencies:\n    spdx-expression-parse: \"npm:^3.0.0\"\n    spdx-license-ids: \"npm:^3.0.0\"\n  checksum: 10c0/49208f008618b9119208b0dadc9208a3a55053f4fd6a0ae8116861bd22696fc50f4142a35ebfdb389e05ccf2de8ad142573fefc9e26f670522d899f7b2fe7386\n  languageName: node\n  linkType: hard\n\n\"spdx-exceptions@npm:^2.1.0\":\n  version: 2.5.0\n  resolution: \"spdx-exceptions@npm:2.5.0\"\n  checksum: 10c0/37217b7762ee0ea0d8b7d0c29fd48b7e4dfb94096b109d6255b589c561f57da93bf4e328c0290046115961b9209a8051ad9f525e48d433082fc79f496a4ea940\n  languageName: node\n  linkType: hard\n\n\"spdx-expression-parse@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"spdx-expression-parse@npm:3.0.1\"\n  dependencies:\n    spdx-exceptions: \"npm:^2.1.0\"\n    spdx-license-ids: \"npm:^3.0.0\"\n  checksum: 10c0/6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171\n  languageName: node\n  linkType: hard\n\n\"spdx-license-ids@npm:^3.0.0\":\n  version: 3.0.20\n  resolution: \"spdx-license-ids@npm:3.0.20\"\n  checksum: 10c0/bdff7534fad6ef59be49becda1edc3fb7f5b3d6f296a715516ab9d972b8ad59af2c34b2003e01db8970d4c673d185ff696ba74c6b61d3bf327e2b3eac22c297c\n  languageName: node\n  linkType: hard\n\n\"splaytree@npm:^3.1.0\":\n  version: 3.1.2\n  resolution: \"splaytree@npm:3.1.2\"\n  checksum: 10c0/ba82da4e4185d692eb2b1c9e000a9dde6cd713ec447f5c90ec97264ce9de19ba1f5f90fbef8a9ffa37bbbe2e9f4b031c6ee45d4119acbf1cddb93112ec5ecf86\n  languageName: node\n  linkType: hard\n\n\"split-string@npm:^3.0.1\":\n  version: 3.1.0\n  resolution: \"split-string@npm:3.1.0\"\n  dependencies:\n    extend-shallow: \"npm:^3.0.0\"\n  checksum: 10c0/72d7cd625445c7af215130e1e2bc183013bb9dd48a074eda1d35741e2b0dcb355e6df5b5558a62543a24dcec37dd1d6eb7a6228ff510d3c9de0f3dc1d1da8a70\n  languageName: node\n  linkType: hard\n\n\"sprintf-js@npm:^1.1.3\":\n  version: 1.1.3\n  resolution: \"sprintf-js@npm:1.1.3\"\n  checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec\n  languageName: node\n  linkType: hard\n\n\"sprintf-js@npm:~1.0.2\":\n  version: 1.0.3\n  resolution: \"sprintf-js@npm:1.0.3\"\n  checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb\n  languageName: node\n  linkType: hard\n\n\"ssri@npm:^12.0.0\":\n  version: 12.0.0\n  resolution: \"ssri@npm:12.0.0\"\n  dependencies:\n    minipass: \"npm:^7.0.3\"\n  checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d\n  languageName: node\n  linkType: hard\n\n\"state-local@npm:^1.0.6\":\n  version: 1.0.7\n  resolution: \"state-local@npm:1.0.7\"\n  checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9\n  languageName: node\n  linkType: hard\n\n\"strict-uri-encode@npm:^1.0.0\":\n  version: 1.1.0\n  resolution: \"strict-uri-encode@npm:1.1.0\"\n  checksum: 10c0/eb8a4109ba2588239787389313ba58ec49e043d4c64a1d44716defe5821a68ae49abe0cdefed9946ca9fc2a4af7ecf321da92422b0a67258ec0a3638b053ae62\n  languageName: node\n  linkType: hard\n\n\"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0\":\n  version: 4.2.3\n  resolution: \"string-width@npm:4.2.3\"\n  dependencies:\n    emoji-regex: \"npm:^8.0.0\"\n    is-fullwidth-code-point: \"npm:^3.0.0\"\n    strip-ansi: \"npm:^6.0.1\"\n  checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b\n  languageName: node\n  linkType: hard\n\n\"string-width@npm:^5.0.1, string-width@npm:^5.1.2\":\n  version: 5.1.2\n  resolution: \"string-width@npm:5.1.2\"\n  dependencies:\n    eastasianwidth: \"npm:^0.2.0\"\n    emoji-regex: \"npm:^9.2.2\"\n    strip-ansi: \"npm:^7.0.1\"\n  checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca\n  languageName: node\n  linkType: hard\n\n\"string.prototype.trim@npm:^1.2.9\":\n  version: 1.2.9\n  resolution: \"string.prototype.trim@npm:1.2.9\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    define-properties: \"npm:^1.2.1\"\n    es-abstract: \"npm:^1.23.0\"\n    es-object-atoms: \"npm:^1.0.0\"\n  checksum: 10c0/dcef1a0fb61d255778155006b372dff8cc6c4394bc39869117e4241f41a2c52899c0d263ffc7738a1f9e61488c490b05c0427faa15151efad721e1a9fb2663c2\n  languageName: node\n  linkType: hard\n\n\"string.prototype.trimend@npm:^1.0.8\":\n  version: 1.0.8\n  resolution: \"string.prototype.trimend@npm:1.0.8\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    define-properties: \"npm:^1.2.1\"\n    es-object-atoms: \"npm:^1.0.0\"\n  checksum: 10c0/0a0b54c17c070551b38e756ae271865ac6cc5f60dabf2e7e343cceae7d9b02e1a1120a824e090e79da1b041a74464e8477e2da43e2775c85392be30a6f60963c\n  languageName: node\n  linkType: hard\n\n\"string.prototype.trimstart@npm:^1.0.8\":\n  version: 1.0.8\n  resolution: \"string.prototype.trimstart@npm:1.0.8\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    define-properties: \"npm:^1.2.1\"\n    es-object-atoms: \"npm:^1.0.0\"\n  checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366\n  languageName: node\n  linkType: hard\n\n\"stringify-entities@npm:^4.0.0\":\n  version: 4.0.4\n  resolution: \"stringify-entities@npm:4.0.4\"\n  dependencies:\n    character-entities-html4: \"npm:^2.0.0\"\n    character-entities-legacy: \"npm:^3.0.0\"\n  checksum: 10c0/537c7e656354192406bdd08157d759cd615724e9d0873602d2c9b2f6a5c0a8d0b1d73a0a08677848105c5eebac6db037b57c0b3a4ec86331117fa7319ed50448\n  languageName: node\n  linkType: hard\n\n\"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1\":\n  version: 6.0.1\n  resolution: \"strip-ansi@npm:6.0.1\"\n  dependencies:\n    ansi-regex: \"npm:^5.0.1\"\n  checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952\n  languageName: node\n  linkType: hard\n\n\"strip-ansi@npm:^7.0.1\":\n  version: 7.1.0\n  resolution: \"strip-ansi@npm:7.1.0\"\n  dependencies:\n    ansi-regex: \"npm:^6.0.1\"\n  checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4\n  languageName: node\n  linkType: hard\n\n\"strip-indent@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"strip-indent@npm:3.0.0\"\n  dependencies:\n    min-indent: \"npm:^1.0.0\"\n  checksum: 10c0/ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679\n  languageName: node\n  linkType: hard\n\n\"strnum@npm:^1.1.1\":\n  version: 1.1.1\n  resolution: \"strnum@npm:1.1.1\"\n  checksum: 10c0/c016034f9896ea99c4a22a8a8142d1ec72dba8d514ddec399f96998d5d2ab9f9e5b6c75c761d9730c3244b794022b1a63ec293f0da41ab0a994e3584020ba1ad\n  languageName: node\n  linkType: hard\n\n\"style-to-js@npm:^1.0.0\":\n  version: 1.1.16\n  resolution: \"style-to-js@npm:1.1.16\"\n  dependencies:\n    style-to-object: \"npm:1.0.8\"\n  checksum: 10c0/578a4dff804539ec7e64d3cc8d327540befb9ad30e3cd0b6b0392f93f793f3a028f90084a9aaff088bffb87818fa2c6c153f0df576f61f9ab0b0938b582bcac7\n  languageName: node\n  linkType: hard\n\n\"style-to-object@npm:1.0.8\":\n  version: 1.0.8\n  resolution: \"style-to-object@npm:1.0.8\"\n  dependencies:\n    inline-style-parser: \"npm:0.2.4\"\n  checksum: 10c0/daa6646b1ff18258c0ca33ed281fbe73485c8391192db1b56ce89d40c93ea64507a41e8701d0dadfe771bc2f540c46c9b295135f71584c8e5cb23d6a19be9430\n  languageName: node\n  linkType: hard\n\n\"styled-components@npm:6.1.8\":\n  version: 6.1.8\n  resolution: \"styled-components@npm:6.1.8\"\n  dependencies:\n    \"@emotion/is-prop-valid\": \"npm:1.2.1\"\n    \"@emotion/unitless\": \"npm:0.8.0\"\n    \"@types/stylis\": \"npm:4.2.0\"\n    css-to-react-native: \"npm:3.2.0\"\n    csstype: \"npm:3.1.2\"\n    postcss: \"npm:8.4.31\"\n    shallowequal: \"npm:1.1.0\"\n    stylis: \"npm:4.3.1\"\n    tslib: \"npm:2.5.0\"\n  peerDependencies:\n    react: \">= 16.8.0\"\n    react-dom: \">= 16.8.0\"\n  checksum: 10c0/fafe4b9198d5d7980c2358821d1a89f86200d55c8eec03670cf12cf43b8a05a77eafaf0872cd85821f68238308e0f5c9d9aa43a62e6987c65b70baa2c3277ab8\n  languageName: node\n  linkType: hard\n\n\"stylis@npm:4.3.1\":\n  version: 4.3.1\n  resolution: \"stylis@npm:4.3.1\"\n  checksum: 10c0/33e8ebd2bfa5f0bd0215f718dc2d3be896e1d00c5bcaeb9a4ae03cf239db6867af9eee230f57229bf1c29499357073ba3e6b547484ba1db2f5de1e8be7d4eee9\n  languageName: node\n  linkType: hard\n\n\"sucrase@npm:^3.35.0\":\n  version: 3.35.0\n  resolution: \"sucrase@npm:3.35.0\"\n  dependencies:\n    \"@jridgewell/gen-mapping\": \"npm:^0.3.2\"\n    commander: \"npm:^4.0.0\"\n    glob: \"npm:^10.3.10\"\n    lines-and-columns: \"npm:^1.1.6\"\n    mz: \"npm:^2.7.0\"\n    pirates: \"npm:^4.0.1\"\n    ts-interface-checker: \"npm:^0.1.9\"\n  bin:\n    sucrase: bin/sucrase\n    sucrase-node: bin/sucrase-node\n  checksum: 10c0/ac85f3359d2c2ecbf5febca6a24ae9bf96c931f05fde533c22a94f59c6a74895e5d5f0e871878dfd59c2697a75ebb04e4b2224ef0bfc24ca1210735c2ec191ef\n  languageName: node\n  linkType: hard\n\n\"suncalc@npm:^1.9.0\":\n  version: 1.9.0\n  resolution: \"suncalc@npm:1.9.0\"\n  checksum: 10c0/3c76c600ec8e60b7a6efab72f48cce3fc9b4ce95f8fa404fcd13d22f618fee2b83d7ad486544aee76bc20129c9f4eae4a76b09640c54167edd1de6099ca6cd3d\n  languageName: node\n  linkType: hard\n\n\"supercluster@npm:^7.1.0\":\n  version: 7.1.5\n  resolution: \"supercluster@npm:7.1.5\"\n  dependencies:\n    kdbush: \"npm:^3.0.0\"\n  checksum: 10c0/bbebf45927d0019831731c94b78d1c9a1f3e2da0be9875d7ea75c6f261487e0f15d3f1822a9a49256e3c1672bdfb9138f9a5e44e2de99edb06cd1e758083e12d\n  languageName: node\n  linkType: hard\n\n\"supercluster@npm:^8.0.1\":\n  version: 8.0.1\n  resolution: \"supercluster@npm:8.0.1\"\n  dependencies:\n    kdbush: \"npm:^4.0.2\"\n  checksum: 10c0/79121e6dbff67b3036ea6651f3baddb942478830ba3ecbc27455715ab5bd269de8381dc04618c0c15963346ea2ed0f81cd5f767c2978a63e2841807c73445d57\n  languageName: node\n  linkType: hard\n\n\"supports-color@npm:^5.3.0\":\n  version: 5.5.0\n  resolution: \"supports-color@npm:5.5.0\"\n  dependencies:\n    has-flag: \"npm:^3.0.0\"\n  checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05\n  languageName: node\n  linkType: hard\n\n\"supports-color@npm:^7.1.0\":\n  version: 7.2.0\n  resolution: \"supports-color@npm:7.2.0\"\n  dependencies:\n    has-flag: \"npm:^4.0.0\"\n  checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124\n  languageName: node\n  linkType: hard\n\n\"supports-preserve-symlinks-flag@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"supports-preserve-symlinks-flag@npm:1.0.0\"\n  checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39\n  languageName: node\n  linkType: hard\n\n\"sweepline-intersections@npm:^1.5.0\":\n  version: 1.5.0\n  resolution: \"sweepline-intersections@npm:1.5.0\"\n  dependencies:\n    tinyqueue: \"npm:^2.0.0\"\n  checksum: 10c0/587a597c75b787e61054ef88b98463af47f60855265b7829fa8acc5ebe68fb4bc3d148a80e9f72c69c16a0241bfed38d3fbbe93a735ea5a2276c00116adc5283\n  languageName: node\n  linkType: hard\n\n\"swr@npm:^2.2.5\":\n  version: 2.3.3\n  resolution: \"swr@npm:2.3.3\"\n  dependencies:\n    dequal: \"npm:^2.0.3\"\n    use-sync-external-store: \"npm:^1.4.0\"\n  peerDependencies:\n    react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  checksum: 10c0/882fc8291912860e0c50eae3470ebf0cd58b0144cb12adcc4b14c5cef913ea06479043830508d8b0b3d4061d99ad8dd52485c9c879fbd4e9b893484e6d8da9e3\n  languageName: node\n  linkType: hard\n\n\"tabbable@npm:^6.0.1\":\n  version: 6.2.0\n  resolution: \"tabbable@npm:6.2.0\"\n  checksum: 10c0/ced8b38f05f2de62cd46836d77c2646c42b8c9713f5bd265daf0e78ff5ac73d3ba48a7ca45f348bafeef29b23da7187c72250742d37627883ef89cbd7fa76898\n  languageName: node\n  linkType: hard\n\n\"table-layout@npm:^4.1.0\":\n  version: 4.1.1\n  resolution: \"table-layout@npm:4.1.1\"\n  dependencies:\n    array-back: \"npm:^6.2.2\"\n    wordwrapjs: \"npm:^5.1.0\"\n  checksum: 10c0/26d8e54a55ddb4de447c8f02a8d7fcbb66a9580375e406a3bc7717ab223a413f6dfbded6710f288b3dfd277991813a0bd5a17419a0dc6db54d9a36d883d868dc\n  languageName: node\n  linkType: hard\n\n\"tailwind-merge@npm:2.5.4\":\n  version: 2.5.4\n  resolution: \"tailwind-merge@npm:2.5.4\"\n  checksum: 10c0/6c3d2a1d44344f373859f005e6366f0dbd7f66131d330a51dbe823dab08f71c388b2efcbb2b6a2170ca469581d27079c25cd40c234ca1356c4893ae99c2febb3\n  languageName: node\n  linkType: hard\n\n\"tailwind-merge@npm:3.3.1\":\n  version: 3.3.1\n  resolution: \"tailwind-merge@npm:3.3.1\"\n  checksum: 10c0/b84c6a78d4669fa12bf5ab8f0cdc4400a3ce0a7c006511af4af4be70bb664a27466dbe13ee9e3b31f50ddf6c51d380e8192ce0ec9effce23ca729d71a9f63818\n  languageName: node\n  linkType: hard\n\n\"tailwind-merge@npm:^2.5.4\":\n  version: 2.6.0\n  resolution: \"tailwind-merge@npm:2.6.0\"\n  checksum: 10c0/fc8a5535524de9f4dacf1c16ab298581c7bb757d68a95faaf28942b1c555a619bba9d4c6726fe83986e44973b315410c1a5226e5354c30ba82353bd6d2288fa5\n  languageName: node\n  linkType: hard\n\n\"tailwind-variants@npm:0.3.0\":\n  version: 0.3.0\n  resolution: \"tailwind-variants@npm:0.3.0\"\n  dependencies:\n    tailwind-merge: \"npm:^2.5.4\"\n  peerDependencies:\n    tailwindcss: \"*\"\n  checksum: 10c0/115b0efb5ca5b76d4dfe6148e3f2f314f5af8490d0d2be8bdb6f062c0963ead87227ffd7340c6fb38cd95c0c751e2f65b04f3c0d833bf058d8cfe25210777bc7\n  languageName: node\n  linkType: hard\n\n\"tailwind-variants@npm:2.0.1\":\n  version: 2.0.1\n  resolution: \"tailwind-variants@npm:2.0.1\"\n  peerDependencies:\n    tailwind-merge: \">=3.0.0\"\n    tailwindcss: \"*\"\n  peerDependenciesMeta:\n    tailwind-merge:\n      optional: true\n  checksum: 10c0/c36884e9d4aa4345dc0fd98cd5d402fbe8c6ac62a19ad2a40f7e3eaffa5cfc15fea8ffefff5eb40c405ee3489343f027e83c47cad4a2e044510e52a14efadd81\n  languageName: node\n  linkType: hard\n\n\"tailwindcss@npm:^3.4.17\":\n  version: 3.4.17\n  resolution: \"tailwindcss@npm:3.4.17\"\n  dependencies:\n    \"@alloc/quick-lru\": \"npm:^5.2.0\"\n    arg: \"npm:^5.0.2\"\n    chokidar: \"npm:^3.6.0\"\n    didyoumean: \"npm:^1.2.2\"\n    dlv: \"npm:^1.1.3\"\n    fast-glob: \"npm:^3.3.2\"\n    glob-parent: \"npm:^6.0.2\"\n    is-glob: \"npm:^4.0.3\"\n    jiti: \"npm:^1.21.6\"\n    lilconfig: \"npm:^3.1.3\"\n    micromatch: \"npm:^4.0.8\"\n    normalize-path: \"npm:^3.0.0\"\n    object-hash: \"npm:^3.0.0\"\n    picocolors: \"npm:^1.1.1\"\n    postcss: \"npm:^8.4.47\"\n    postcss-import: \"npm:^15.1.0\"\n    postcss-js: \"npm:^4.0.1\"\n    postcss-load-config: \"npm:^4.0.2\"\n    postcss-nested: \"npm:^6.2.0\"\n    postcss-selector-parser: \"npm:^6.1.2\"\n    resolve: \"npm:^1.22.8\"\n    sucrase: \"npm:^3.35.0\"\n  bin:\n    tailwind: lib/cli.js\n    tailwindcss: lib/cli.js\n  checksum: 10c0/cc42c6e7fdf88a5507a0d7fea37f1b4122bec158977f8c017b2ae6828741f9e6f8cb90282c6bf2bd5951fd1220a53e0a50ca58f5c1c00eb7f5d9f8b80dc4523c\n  languageName: node\n  linkType: hard\n\n\"tar@npm:^7.4.3\":\n  version: 7.4.3\n  resolution: \"tar@npm:7.4.3\"\n  dependencies:\n    \"@isaacs/fs-minipass\": \"npm:^4.0.0\"\n    chownr: \"npm:^3.0.0\"\n    minipass: \"npm:^7.1.2\"\n    minizlib: \"npm:^3.0.1\"\n    mkdirp: \"npm:^3.0.1\"\n    yallist: \"npm:^5.0.0\"\n  checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d\n  languageName: node\n  linkType: hard\n\n\"text-segmentation@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"text-segmentation@npm:1.0.3\"\n  dependencies:\n    utrie: \"npm:^1.0.2\"\n  checksum: 10c0/8b9ae8524e3a332371060d0ca62f10ad49a13e954719ea689a6c3a8b8c15c8a56365ede2bb91c322fb0d44b6533785f0da603e066b7554d052999967fb72d600\n  languageName: node\n  linkType: hard\n\n\"texture-compressor@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"texture-compressor@npm:1.0.2\"\n  dependencies:\n    argparse: \"npm:^1.0.10\"\n    image-size: \"npm:^0.7.4\"\n  bin:\n    texture-compressor: ./bin/texture-compressor.js\n  checksum: 10c0/ea0bce1cbda3a64f826867e97ee2a1f17a7219d21d97d7625a6370792afddc697e6832a9b3b39bf2eef0978af7bc0d1dcefaf7fd91d719213658b0a010a83c44\n  languageName: node\n  linkType: hard\n\n\"thenify-all@npm:^1.0.0\":\n  version: 1.6.0\n  resolution: \"thenify-all@npm:1.6.0\"\n  dependencies:\n    thenify: \"npm:>= 3.1.0 < 4\"\n  checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b\n  languageName: node\n  linkType: hard\n\n\"thenify@npm:>= 3.1.0 < 4\":\n  version: 3.3.1\n  resolution: \"thenify@npm:3.3.1\"\n  dependencies:\n    any-promise: \"npm:^1.0.0\"\n  checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767\n  languageName: node\n  linkType: hard\n\n\"threads@npm:^1.7.0\":\n  version: 1.7.0\n  resolution: \"threads@npm:1.7.0\"\n  dependencies:\n    callsites: \"npm:^3.1.0\"\n    debug: \"npm:^4.2.0\"\n    is-observable: \"npm:^2.1.0\"\n    observable-fns: \"npm:^0.6.1\"\n    tiny-worker: \"npm:>= 2\"\n  dependenciesMeta:\n    tiny-worker:\n      optional: true\n  checksum: 10c0/34114075c5eb253e937c01e0e51babb71eb1571c2fd4a8170645aa20c10186422324537d017d64bfd3de655cbb6647bf70d1d602260273b1e9750719d67f359a\n  languageName: node\n  linkType: hard\n\n\"thrift@npm:^0.19.0\":\n  version: 0.19.0\n  resolution: \"thrift@npm:0.19.0\"\n  dependencies:\n    browser-or-node: \"npm:^1.2.1\"\n    isomorphic-ws: \"npm:^4.0.1\"\n    node-int64: \"npm:^0.4.0\"\n    q: \"npm:^1.5.0\"\n    ws: \"npm:^5.2.3\"\n  checksum: 10c0/935c2d9fcd3f23528fb462e8186aacf6e9c185593f59ac6ede1e3f5862de85f7d093a99546edce77443c990e6fde850a5ef5603e087cc1e9df96003f01860435\n  languageName: node\n  linkType: hard\n\n\"throttleit@npm:2.1.0\":\n  version: 2.1.0\n  resolution: \"throttleit@npm:2.1.0\"\n  checksum: 10c0/1696ae849522cea6ba4f4f3beac1f6655d335e51b42d99215e196a718adced0069e48deaaf77f7e89f526ab31de5b5c91016027da182438e6f9280be2f3d5265\n  languageName: node\n  linkType: hard\n\n\"tiny-warning@npm:^1.0.0\":\n  version: 1.0.3\n  resolution: \"tiny-warning@npm:1.0.3\"\n  checksum: 10c0/ef8531f581b30342f29670cb41ca248001c6fd7975ce22122bd59b8d62b4fc84ad4207ee7faa95cde982fa3357cd8f4be650142abc22805538c3b1392d7084fa\n  languageName: node\n  linkType: hard\n\n\"tiny-worker@npm:>= 2\":\n  version: 2.3.0\n  resolution: \"tiny-worker@npm:2.3.0\"\n  dependencies:\n    esm: \"npm:^3.2.25\"\n  checksum: 10c0/3106cace86e673216426a517e96fb72ce642ba79002554e4c6bceb585ba77cf5e5e68b452c752cada6136ae94fdbf11c56943a70de6c6bc6a2a3a9ae439746c9\n  languageName: node\n  linkType: hard\n\n\"tinycolor2@npm:^1.4.1\":\n  version: 1.6.0\n  resolution: \"tinycolor2@npm:1.6.0\"\n  checksum: 10c0/9aa79a36ba2c2a87cb221453465cabacd04b9e35f9694373e846fdc78b1c768110f81e581ea41440106c0f24d9a023891d0887e8075885e790ac40eb0e74a5c1\n  languageName: node\n  linkType: hard\n\n\"tinyqueue@npm:^2.0.0, tinyqueue@npm:^2.0.3\":\n  version: 2.0.3\n  resolution: \"tinyqueue@npm:2.0.3\"\n  checksum: 10c0/d7b590088f015a94a17132fa209c2f2a80c45158259af5474901fdf5932e95ea13ff6f034bcc725a6d5f66d3e5b888b048c310229beb25ad5bebb4f0a635abf2\n  languageName: node\n  linkType: hard\n\n\"tippy.js@npm:^6.3.1\":\n  version: 6.3.7\n  resolution: \"tippy.js@npm:6.3.7\"\n  dependencies:\n    \"@popperjs/core\": \"npm:^2.9.0\"\n  checksum: 10c0/ec3677beb8caec791ee1f715663f28f42d60e0f7250074a047d13d5e6db95fdb6d26d8a3ac16cecb4ebcaf33ae919dbc889cf97948d115e8d3c81518c911b379\n  languageName: node\n  linkType: hard\n\n\"to-camel-case@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"to-camel-case@npm:1.0.0\"\n  dependencies:\n    to-space-case: \"npm:^1.0.0\"\n  checksum: 10c0/357921548908053d774d4b836f42437139c6fc9d73aaf40a1aa59d7317d760541a19667eb2884d9db83902065a90d80b0fe74c59bc13943e8489df9ef4335069\n  languageName: node\n  linkType: hard\n\n\"to-no-case@npm:^1.0.0\":\n  version: 1.0.2\n  resolution: \"to-no-case@npm:1.0.2\"\n  checksum: 10c0/c035b04e1042ed67ceb23dc5c7c20ccde11a83ab1d2b3947c17918472b5d26dd4ffdb4cf9464752e7707ab9f3af4a106f9b61244c724bc6810422acd5984da3d\n  languageName: node\n  linkType: hard\n\n\"to-regex-range@npm:^5.0.1\":\n  version: 5.0.1\n  resolution: \"to-regex-range@npm:5.0.1\"\n  dependencies:\n    is-number: \"npm:^7.0.0\"\n  checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892\n  languageName: node\n  linkType: hard\n\n\"to-space-case@npm:^1.0.0\":\n  version: 1.0.0\n  resolution: \"to-space-case@npm:1.0.0\"\n  dependencies:\n    to-no-case: \"npm:^1.0.0\"\n  checksum: 10c0/b99e1b5d0f3c90a8d47fa3b155d515027bd83a370740e82ee7cb064f86e3655f030f068bddcb8d18239e7408761b4376d89ab91e5ccdb17dc859d8fd4f570ac5\n  languageName: node\n  linkType: hard\n\n\"toggle-selection@npm:^1.0.6\":\n  version: 1.0.6\n  resolution: \"toggle-selection@npm:1.0.6\"\n  checksum: 10c0/f2cf1f2c70f374fd87b0cdc8007453ba9e981c4305a8bf4eac10a30e62ecdfd28bca7d18f8f15b15a506bf8a7bfb20dbe3539f0fcf2a2c8396c1a78d53e1f179\n  languageName: node\n  linkType: hard\n\n\"tr46@npm:~0.0.3\":\n  version: 0.0.3\n  resolution: \"tr46@npm:0.0.3\"\n  checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11\n  languageName: node\n  linkType: hard\n\n\"trim-lines@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"trim-lines@npm:3.0.1\"\n  checksum: 10c0/3a1611fa9e52aa56a94c69951a9ea15b8aaad760eaa26c56a65330dc8adf99cb282fc07cc9d94968b7d4d88003beba220a7278bbe2063328eb23fb56f9509e94\n  languageName: node\n  linkType: hard\n\n\"trim-newlines@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"trim-newlines@npm:3.0.1\"\n  checksum: 10c0/03cfefde6c59ff57138412b8c6be922ecc5aec30694d784f2a65ef8dcbd47faef580b7de0c949345abdc56ec4b4abf64dd1e5aea619b200316e471a3dd5bf1f6\n  languageName: node\n  linkType: hard\n\n\"trough@npm:^2.0.0\":\n  version: 2.2.0\n  resolution: \"trough@npm:2.2.0\"\n  checksum: 10c0/58b671fc970e7867a48514168894396dd94e6d9d6456aca427cc299c004fe67f35ed7172a36449086b2edde10e78a71a284ec0076809add6834fb8f857ccb9b0\n  languageName: node\n  linkType: hard\n\n\"ts-interface-checker@npm:^0.1.9\":\n  version: 0.1.13\n  resolution: \"ts-interface-checker@npm:0.1.13\"\n  checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7\n  languageName: node\n  linkType: hard\n\n\"tslib@npm:2, tslib@npm:^2.8.0, tslib@npm:^2.8.1\":\n  version: 2.8.1\n  resolution: \"tslib@npm:2.8.1\"\n  checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62\n  languageName: node\n  linkType: hard\n\n\"tslib@npm:2.3.0\":\n  version: 2.3.0\n  resolution: \"tslib@npm:2.3.0\"\n  checksum: 10c0/a845aed84e7e7dbb4c774582da60d7030ea39d67307250442d35c4c5dd77e4b44007098c37dd079e100029c76055f2a362734b8442ba828f8cc934f15ed9be61\n  languageName: node\n  linkType: hard\n\n\"tslib@npm:2.5.0\":\n  version: 2.5.0\n  resolution: \"tslib@npm:2.5.0\"\n  checksum: 10c0/e32fc99cc730dd514e53c44e668d76016e738f0bcc726aad5dbd2d335cf19b87a95a9b1e4f0a9993e370f1d702b5e471cdd4acabcac428a3099d496b9af2021e\n  languageName: node\n  linkType: hard\n\n\"tslib@npm:^2.0.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2\":\n  version: 2.6.3\n  resolution: \"tslib@npm:2.6.3\"\n  checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a\n  languageName: node\n  linkType: hard\n\n\"type-analyzer@npm:0.4.0\":\n  version: 0.4.0\n  resolution: \"type-analyzer@npm:0.4.0\"\n  checksum: 10c0/5fac26cdde6e328419fce596826ad761f7aa96e613b210aa05b3710711893e61fcd8458936539c2634940912a063519e962bfa6f221e6d829b8a2b6193b06f20\n  languageName: node\n  linkType: hard\n\n\"type-fest@npm:^0.18.0\":\n  version: 0.18.1\n  resolution: \"type-fest@npm:0.18.1\"\n  checksum: 10c0/303f5ecf40d03e1d5b635ce7660de3b33c18ed8ebc65d64920c02974d9e684c72483c23f9084587e9dd6466a2ece1da42ddc95b412a461794dd30baca95e2bac\n  languageName: node\n  linkType: hard\n\n\"type-fest@npm:^0.6.0\":\n  version: 0.6.0\n  resolution: \"type-fest@npm:0.6.0\"\n  checksum: 10c0/0c585c26416fce9ecb5691873a1301b5aff54673c7999b6f925691ed01f5b9232db408cdbb0bd003d19f5ae284322523f44092d1f81ca0a48f11f7cf0be8cd38\n  languageName: node\n  linkType: hard\n\n\"type-fest@npm:^0.8.1\":\n  version: 0.8.1\n  resolution: \"type-fest@npm:0.8.1\"\n  checksum: 10c0/dffbb99329da2aa840f506d376c863bd55f5636f4741ad6e65e82f5ce47e6914108f44f340a0b74009b0cb5d09d6752ae83203e53e98b1192cf80ecee5651636\n  languageName: node\n  linkType: hard\n\n\"typed-array-buffer@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"typed-array-buffer@npm:1.0.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    es-errors: \"npm:^1.3.0\"\n    is-typed-array: \"npm:^1.1.13\"\n  checksum: 10c0/9e043eb38e1b4df4ddf9dde1aa64919ae8bb909571c1cc4490ba777d55d23a0c74c7d73afcdd29ec98616d91bb3ae0f705fad4421ea147e1daf9528200b562da\n  languageName: node\n  linkType: hard\n\n\"typed-array-byte-length@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"typed-array-byte-length@npm:1.0.1\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    for-each: \"npm:^0.3.3\"\n    gopd: \"npm:^1.0.1\"\n    has-proto: \"npm:^1.0.3\"\n    is-typed-array: \"npm:^1.1.13\"\n  checksum: 10c0/fcebeffb2436c9f355e91bd19e2368273b88c11d1acc0948a2a306792f1ab672bce4cfe524ab9f51a0505c9d7cd1c98eff4235c4f6bfef6a198f6cfc4ff3d4f3\n  languageName: node\n  linkType: hard\n\n\"typed-array-byte-offset@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"typed-array-byte-offset@npm:1.0.2\"\n  dependencies:\n    available-typed-arrays: \"npm:^1.0.7\"\n    call-bind: \"npm:^1.0.7\"\n    for-each: \"npm:^0.3.3\"\n    gopd: \"npm:^1.0.1\"\n    has-proto: \"npm:^1.0.3\"\n    is-typed-array: \"npm:^1.1.13\"\n  checksum: 10c0/d2628bc739732072e39269389a758025f75339de2ed40c4f91357023c5512d237f255b633e3106c461ced41907c1bf9a533c7e8578066b0163690ca8bc61b22f\n  languageName: node\n  linkType: hard\n\n\"typed-array-length@npm:^1.0.6\":\n  version: 1.0.6\n  resolution: \"typed-array-length@npm:1.0.6\"\n  dependencies:\n    call-bind: \"npm:^1.0.7\"\n    for-each: \"npm:^0.3.3\"\n    gopd: \"npm:^1.0.1\"\n    has-proto: \"npm:^1.0.3\"\n    is-typed-array: \"npm:^1.1.13\"\n    possible-typed-array-names: \"npm:^1.0.0\"\n  checksum: 10c0/74253d7dc488eb28b6b2711cf31f5a9dcefc9c41b0681fd1c178ed0a1681b4468581a3626d39cd4df7aee3d3927ab62be06aa9ca74e5baf81827f61641445b77\n  languageName: node\n  linkType: hard\n\n\"typewise-core@npm:^1.2, typewise-core@npm:^1.2.0\":\n  version: 1.2.0\n  resolution: \"typewise-core@npm:1.2.0\"\n  checksum: 10c0/0c574b036e430ef29a3c71dca1f88c041597734448db50e697ec4b7d03d71af4f8afeec556a2553f7db1cf98f9313b983071f0731d784108b2daf4f2e0c37d9e\n  languageName: node\n  linkType: hard\n\n\"typewise@npm:^1.0.3\":\n  version: 1.0.3\n  resolution: \"typewise@npm:1.0.3\"\n  dependencies:\n    typewise-core: \"npm:^1.2.0\"\n  checksum: 10c0/0e300a963cd344f9f4216343eb1c9714e1aee12c5b928ae3ff4a19b4b1edcd82356b8bd763905bd72528718a3c863612f8259cb047934b59bdd849f305e12e80\n  languageName: node\n  linkType: hard\n\n\"typical@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"typical@npm:4.0.0\"\n  checksum: 10c0/f300b198fb9fe743859b75ec761d53c382723dc178bbce4957d9cb754f2878a44ce17dc0b6a5156c52be1065449271f63754ba594dac225b80ce3aa39f9241ed\n  languageName: node\n  linkType: hard\n\n\"typical@npm:^7.1.1\":\n  version: 7.1.1\n  resolution: \"typical@npm:7.1.1\"\n  checksum: 10c0/bbb28ccd09909446759db1b50bb466826077b4fdf99c3be963539ed9ee84090479976df9c51279302b9b4d24573657911b5a2b9d5e9996ed2daab9b02e0970ba\n  languageName: node\n  linkType: hard\n\n\"typical@npm:^7.2.0\":\n  version: 7.3.0\n  resolution: \"typical@npm:7.3.0\"\n  checksum: 10c0/bee697a88e1dd0447bc1cf7f6e875eaa2b0fb2cccb338b7b261e315f7df84a66402864bfc326d6b3117c50475afd1d49eda03d846a6299ad25f211035bfab3b1\n  languageName: node\n  linkType: hard\n\n\"ua-parser-js@npm:^0.7.30\":\n  version: 0.7.38\n  resolution: \"ua-parser-js@npm:0.7.38\"\n  checksum: 10c0/da963eae1618f0c60d0812851a4d478fb8bb127ee6e5c566b8dac27eeb25757d818d9ade2c312d73018f2bb3c3e629d26c066fcda3cb9d55a31289c9566198df\n  languageName: node\n  linkType: hard\n\n\"unbox-primitive@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"unbox-primitive@npm:1.0.2\"\n  dependencies:\n    call-bind: \"npm:^1.0.2\"\n    has-bigints: \"npm:^1.0.2\"\n    has-symbols: \"npm:^1.0.3\"\n    which-boxed-primitive: \"npm:^1.0.2\"\n  checksum: 10c0/81ca2e81134167cc8f75fa79fbcc8a94379d6c61de67090986a2273850989dd3bae8440c163121b77434b68263e34787a675cbdcb34bb2f764c6b9c843a11b66\n  languageName: node\n  linkType: hard\n\n\"undici-types@npm:~5.26.4\":\n  version: 5.26.5\n  resolution: \"undici-types@npm:5.26.5\"\n  checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501\n  languageName: node\n  linkType: hard\n\n\"undici-types@npm:~6.19.2\":\n  version: 6.19.8\n  resolution: \"undici-types@npm:6.19.8\"\n  checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344\n  languageName: node\n  linkType: hard\n\n\"unified@npm:^11.0.0\":\n  version: 11.0.5\n  resolution: \"unified@npm:11.0.5\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n    bail: \"npm:^2.0.0\"\n    devlop: \"npm:^1.0.0\"\n    extend: \"npm:^3.0.0\"\n    is-plain-obj: \"npm:^4.0.0\"\n    trough: \"npm:^2.0.0\"\n    vfile: \"npm:^6.0.0\"\n  checksum: 10c0/53c8e685f56d11d9d458a43e0e74328a4d6386af51c8ac37a3dcabec74ce5026da21250590d4aff6733ccd7dc203116aae2b0769abc18cdf9639a54ae528dfc9\n  languageName: node\n  linkType: hard\n\n\"union-value@npm:^1.0.1\":\n  version: 1.0.1\n  resolution: \"union-value@npm:1.0.1\"\n  dependencies:\n    arr-union: \"npm:^3.1.0\"\n    get-value: \"npm:^2.0.6\"\n    is-extendable: \"npm:^0.1.1\"\n    set-value: \"npm:^2.0.1\"\n  checksum: 10c0/8758d880cb9545f62ce9cfb9b791b2b7a206e0ff5cc4b9d7cd6581da2c6839837fbb45e639cf1fd8eef3cae08c0201b614b7c06dd9f5f70d9dbe7c5fe2fbf592\n  languageName: node\n  linkType: hard\n\n\"unique-filename@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"unique-filename@npm:4.0.0\"\n  dependencies:\n    unique-slug: \"npm:^5.0.0\"\n  checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc\n  languageName: node\n  linkType: hard\n\n\"unique-slug@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"unique-slug@npm:5.0.0\"\n  dependencies:\n    imurmurhash: \"npm:^0.1.4\"\n  checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293\n  languageName: node\n  linkType: hard\n\n\"unist-util-is@npm:^6.0.0\":\n  version: 6.0.0\n  resolution: \"unist-util-is@npm:6.0.0\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n  checksum: 10c0/9419352181eaa1da35eca9490634a6df70d2217815bb5938a04af3a662c12c5607a2f1014197ec9c426fbef18834f6371bfdb6f033040fa8aa3e965300d70e7e\n  languageName: node\n  linkType: hard\n\n\"unist-util-position@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"unist-util-position@npm:5.0.0\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n  checksum: 10c0/dde3b31e314c98f12b4dc6402f9722b2bf35e96a4f2d463233dd90d7cde2d4928074a7a11eff0a5eb1f4e200f27fc1557e0a64a7e8e4da6558542f251b1b7400\n  languageName: node\n  linkType: hard\n\n\"unist-util-stringify-position@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"unist-util-stringify-position@npm:4.0.0\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n  checksum: 10c0/dfe1dbe79ba31f589108cb35e523f14029b6675d741a79dea7e5f3d098785045d556d5650ec6a8338af11e9e78d2a30df12b1ee86529cded1098da3f17ee999e\n  languageName: node\n  linkType: hard\n\n\"unist-util-visit-parents@npm:^6.0.0\":\n  version: 6.0.1\n  resolution: \"unist-util-visit-parents@npm:6.0.1\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n    unist-util-is: \"npm:^6.0.0\"\n  checksum: 10c0/51b1a5b0aa23c97d3e03e7288f0cdf136974df2217d0999d3de573c05001ef04cccd246f51d2ebdfb9e8b0ed2704451ad90ba85ae3f3177cf9772cef67f56206\n  languageName: node\n  linkType: hard\n\n\"unist-util-visit@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"unist-util-visit@npm:5.0.0\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n    unist-util-is: \"npm:^6.0.0\"\n    unist-util-visit-parents: \"npm:^6.0.0\"\n  checksum: 10c0/51434a1d80252c1540cce6271a90fd1a106dbe624997c09ed8879279667fb0b2d3a685e02e92bf66598dcbe6cdffa7a5f5fb363af8fdf90dda6c855449ae39a5\n  languageName: node\n  linkType: hard\n\n\"universalify@npm:^0.1.0\":\n  version: 0.1.2\n  resolution: \"universalify@npm:0.1.2\"\n  checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045\n  languageName: node\n  linkType: hard\n\n\"update-input-width@npm:^1.4.0\":\n  version: 1.4.2\n  resolution: \"update-input-width@npm:1.4.2\"\n  checksum: 10c0/d3344f91c1c08a26f81d172dd774ca8834ddfaec1eb78e05280d303800a3236c4e122df14ea34fe7f0e1bdada733dec5d3676d38ce0777bafe603de0a6199473\n  languageName: node\n  linkType: hard\n\n\"use-composed-ref@npm:^1.3.0\":\n  version: 1.4.0\n  resolution: \"use-composed-ref@npm:1.4.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/c77e0cba9579b7746d52feaf3ce77d8c345f266c9c1ef46584ae68f54646537c87b2ad97f5219a4b1db52f97ec2905e88e5b146add1f28f7e457bd52ca1b93cf\n  languageName: node\n  linkType: hard\n\n\"use-isomorphic-layout-effect@npm:^1.1.1\":\n  version: 1.2.0\n  resolution: \"use-isomorphic-layout-effect@npm:1.2.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/2e4bdee68d65893b37e716ebdcc111550775189c80e662eda87d6f5b54dc431d3383a18914ea01a893ee5478902a878012713eaebcacbb6611ab88c463accb83\n  languageName: node\n  linkType: hard\n\n\"use-latest@npm:^1.2.1\":\n  version: 1.3.0\n  resolution: \"use-latest@npm:1.3.0\"\n  dependencies:\n    use-isomorphic-layout-effect: \"npm:^1.1.1\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  peerDependenciesMeta:\n    \"@types/react\":\n      optional: true\n  checksum: 10c0/067c648814ad0c1f1e89d2d0e496254b05c4bed6a34e23045b4413824222aab08fd803c59a42852acc16830c17567d03f8c90af0a62be2f4e4b931454d079798\n  languageName: node\n  linkType: hard\n\n\"use-sync-external-store@npm:^1.0.0\":\n  version: 1.2.2\n  resolution: \"use-sync-external-store@npm:1.2.2\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0\n  checksum: 10c0/23b1597c10adf15b26ade9e8c318d8cc0abc9ec0ab5fc7ca7338da92e89c2536abd150a5891bf076836c352fdfa104fc7231fb48f806fd9960e0cbe03601abaf\n  languageName: node\n  linkType: hard\n\n\"use-sync-external-store@npm:^1.4.0\":\n  version: 1.5.0\n  resolution: \"use-sync-external-store@npm:1.5.0\"\n  peerDependencies:\n    react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\n  checksum: 10c0/1b8663515c0be34fa653feb724fdcce3984037c78dd4a18f68b2c8be55cc1a1084c578d5b75f158d41b5ddffc2bf5600766d1af3c19c8e329bb20af2ec6f52f4\n  languageName: node\n  linkType: hard\n\n\"usehooks-ts@npm:^3.1.0\":\n  version: 3.1.0\n  resolution: \"usehooks-ts@npm:3.1.0\"\n  dependencies:\n    lodash.debounce: \"npm:^4.0.8\"\n  peerDependencies:\n    react: ^16.8.0  || ^17 || ^18\n  checksum: 10c0/2204d8c95109302bdaaa51a66bf216f3dba750f1d2795c20ecba75ba1c44a070a253935d537ef536514ab6e363bcc02ccc78b5ad63576ff8d880d577cf3fc48f\n  languageName: node\n  linkType: hard\n\n\"util-deprecate@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"util-deprecate@npm:1.0.2\"\n  checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942\n  languageName: node\n  linkType: hard\n\n\"util@npm:^0.12.5\":\n  version: 0.12.5\n  resolution: \"util@npm:0.12.5\"\n  dependencies:\n    inherits: \"npm:^2.0.3\"\n    is-arguments: \"npm:^1.0.4\"\n    is-generator-function: \"npm:^1.0.7\"\n    is-typed-array: \"npm:^1.1.3\"\n    which-typed-array: \"npm:^1.1.2\"\n  checksum: 10c0/c27054de2cea2229a66c09522d0fa1415fb12d861d08523a8846bf2e4cbf0079d4c3f725f09dcb87493549bcbf05f5798dce1688b53c6c17201a45759e7253f3\n  languageName: node\n  linkType: hard\n\n\"utrie@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"utrie@npm:1.0.2\"\n  dependencies:\n    base64-arraybuffer: \"npm:^1.0.2\"\n  checksum: 10c0/eaffe645bd81a39e4bc3abb23df5895e9961dbdd49748ef3b173529e8b06ce9dd1163e9705d5309a1c61ee41ffcb825e2043bc0fd1659845ffbdf4b1515dfdb4\n  languageName: node\n  linkType: hard\n\n\"uuid@npm:^10.0.0\":\n  version: 10.0.0\n  resolution: \"uuid@npm:10.0.0\"\n  bin:\n    uuid: dist/bin/uuid\n  checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe\n  languageName: node\n  linkType: hard\n\n\"uuid@npm:^7.0.3\":\n  version: 7.0.3\n  resolution: \"uuid@npm:7.0.3\"\n  bin:\n    uuid: dist/bin/uuid\n  checksum: 10c0/2eee5723b0fcce8256f5bfd3112af6c453b5471db00af9c3533e3d5a6e57de83513f9a145a570890457bd7abf2c2aa05797291d950ac666e5a074895dc63168b\n  languageName: node\n  linkType: hard\n\n\"validate-npm-package-license@npm:^3.0.1\":\n  version: 3.0.4\n  resolution: \"validate-npm-package-license@npm:3.0.4\"\n  dependencies:\n    spdx-correct: \"npm:^3.0.0\"\n    spdx-expression-parse: \"npm:^3.0.0\"\n  checksum: 10c0/7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f\n  languageName: node\n  linkType: hard\n\n\"varint@npm:^6.0.0\":\n  version: 6.0.0\n  resolution: \"varint@npm:6.0.0\"\n  checksum: 10c0/737fc37088a62ed3bd21466e318d21ca7ac4991d0f25546f518f017703be4ed0f9df1c5559f1dd533dddba4435a1b758fd9230e4772c1a930ef72b42f5c750fd\n  languageName: node\n  linkType: hard\n\n\"vfile-message@npm:^4.0.0\":\n  version: 4.0.2\n  resolution: \"vfile-message@npm:4.0.2\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n    unist-util-stringify-position: \"npm:^4.0.0\"\n  checksum: 10c0/07671d239a075f888b78f318bc1d54de02799db4e9dce322474e67c35d75ac4a5ac0aaf37b18801d91c9f8152974ea39678aa72d7198758b07f3ba04fb7d7514\n  languageName: node\n  linkType: hard\n\n\"vfile@npm:^6.0.0\":\n  version: 6.0.3\n  resolution: \"vfile@npm:6.0.3\"\n  dependencies:\n    \"@types/unist\": \"npm:^3.0.0\"\n    vfile-message: \"npm:^4.0.0\"\n  checksum: 10c0/e5d9eb4810623f23758cfc2205323e33552fb5972e5c2e6587babe08fe4d24859866277404fb9e2a20afb71013860d96ec806cb257536ae463c87d70022ab9ef\n  languageName: node\n  linkType: hard\n\n\"viewport-mercator-project@npm:>=6.0.0\":\n  version: 7.0.4\n  resolution: \"viewport-mercator-project@npm:7.0.4\"\n  dependencies:\n    \"@math.gl/web-mercator\": \"npm:^3.5.5\"\n  checksum: 10c0/9a8a12f4bd68355a8162a23d4b161133b45aee71b2453fd8d57f54c55466007428cf502d0c02f58620d4c92ba934c29608613bc3176dd8c1d348f8a9deb34e32\n  languageName: node\n  linkType: hard\n\n\"viewport-mercator-project@npm:^6.0.0\":\n  version: 6.2.3\n  resolution: \"viewport-mercator-project@npm:6.2.3\"\n  dependencies:\n    \"@babel/runtime\": \"npm:^7.0.0\"\n    gl-matrix: \"npm:^3.0.0\"\n  checksum: 10c0/787b02f89d60c4e71fd1faa9391853502339cc65159b7b3a70c4ee79f232187daad24e0e3694dded836cee9a69a19138981735361c50ac4172c15e4100c1891a\n  languageName: node\n  linkType: hard\n\n\"vt-pbf@npm:^3.1.1, vt-pbf@npm:^3.1.3\":\n  version: 3.1.3\n  resolution: \"vt-pbf@npm:3.1.3\"\n  dependencies:\n    \"@mapbox/point-geometry\": \"npm:0.1.0\"\n    \"@mapbox/vector-tile\": \"npm:^1.3.1\"\n    pbf: \"npm:^3.2.1\"\n  checksum: 10c0/a568801ae25f0ffe65ef697bf520c996c8a4067f73f355c0d5815238de90322c8ca207c61220206141cfe6f5b525de875b7eb26e22979a1b768b96d03b93dca7\n  languageName: node\n  linkType: hard\n\n\"warning@npm:^3.0.0\":\n  version: 3.0.0\n  resolution: \"warning@npm:3.0.0\"\n  dependencies:\n    loose-envify: \"npm:^1.0.0\"\n  checksum: 10c0/6a2a56ab3139d3927193d926a027e74e1449fa47cc692feea95f8a81a4bb5b7f10c312def94cce03f3b58cb26ba3247858e75d17d596451d2c483a62e8204705\n  languageName: node\n  linkType: hard\n\n\"warning@npm:^4.0.0, warning@npm:^4.0.3\":\n  version: 4.0.3\n  resolution: \"warning@npm:4.0.3\"\n  dependencies:\n    loose-envify: \"npm:^1.0.0\"\n  checksum: 10c0/aebab445129f3e104c271f1637fa38e55eb25f968593e3825bd2f7a12bd58dc3738bb70dc8ec85826621d80b4acfed5a29ebc9da17397c6125864d72301b937e\n  languageName: node\n  linkType: hard\n\n\"web-streams-polyfill@npm:4.0.0-beta.3\":\n  version: 4.0.0-beta.3\n  resolution: \"web-streams-polyfill@npm:4.0.0-beta.3\"\n  checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e\n  languageName: node\n  linkType: hard\n\n\"webidl-conversions@npm:^3.0.0\":\n  version: 3.0.1\n  resolution: \"webidl-conversions@npm:3.0.1\"\n  checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db\n  languageName: node\n  linkType: hard\n\n\"whatwg-fetch@npm:>=0.10.0\":\n  version: 3.6.20\n  resolution: \"whatwg-fetch@npm:3.6.20\"\n  checksum: 10c0/fa972dd14091321d38f36a4d062298df58c2248393ef9e8b154493c347c62e2756e25be29c16277396046d6eaa4b11bd174f34e6403fff6aaca9fb30fa1ff46d\n  languageName: node\n  linkType: hard\n\n\"whatwg-url@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"whatwg-url@npm:5.0.0\"\n  dependencies:\n    tr46: \"npm:~0.0.3\"\n    webidl-conversions: \"npm:^3.0.0\"\n  checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5\n  languageName: node\n  linkType: hard\n\n\"which-boxed-primitive@npm:^1.0.2\":\n  version: 1.0.2\n  resolution: \"which-boxed-primitive@npm:1.0.2\"\n  dependencies:\n    is-bigint: \"npm:^1.0.1\"\n    is-boolean-object: \"npm:^1.1.0\"\n    is-number-object: \"npm:^1.0.4\"\n    is-string: \"npm:^1.0.5\"\n    is-symbol: \"npm:^1.0.3\"\n  checksum: 10c0/0a62a03c00c91dd4fb1035b2f0733c341d805753b027eebd3a304b9cb70e8ce33e25317add2fe9b5fea6f53a175c0633ae701ff812e604410ddd049777cd435e\n  languageName: node\n  linkType: hard\n\n\"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.2\":\n  version: 1.1.15\n  resolution: \"which-typed-array@npm:1.1.15\"\n  dependencies:\n    available-typed-arrays: \"npm:^1.0.7\"\n    call-bind: \"npm:^1.0.7\"\n    for-each: \"npm:^0.3.3\"\n    gopd: \"npm:^1.0.1\"\n    has-tostringtag: \"npm:^1.0.2\"\n  checksum: 10c0/4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983\n  languageName: node\n  linkType: hard\n\n\"which@npm:^1.3.1\":\n  version: 1.3.1\n  resolution: \"which@npm:1.3.1\"\n  dependencies:\n    isexe: \"npm:^2.0.0\"\n  bin:\n    which: ./bin/which\n  checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59\n  languageName: node\n  linkType: hard\n\n\"which@npm:^2.0.1\":\n  version: 2.0.2\n  resolution: \"which@npm:2.0.2\"\n  dependencies:\n    isexe: \"npm:^2.0.0\"\n  bin:\n    node-which: ./bin/node-which\n  checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f\n  languageName: node\n  linkType: hard\n\n\"which@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"which@npm:5.0.0\"\n  dependencies:\n    isexe: \"npm:^3.1.1\"\n  bin:\n    node-which: bin/which.js\n  checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b\n  languageName: node\n  linkType: hard\n\n\"wkt-parser@npm:^1.4.0\":\n  version: 1.4.0\n  resolution: \"wkt-parser@npm:1.4.0\"\n  checksum: 10c0/4046fd355ddc626e6ea71f5a05e670bd3d6dc65dc0f8b10f2e9381ea6bd61cb90379389b0fb52337de9330788405dd0590b57e01b2c85f6f471acc794713243b\n  languageName: node\n  linkType: hard\n\n\"wordwrapjs@npm:^5.1.0\":\n  version: 5.1.0\n  resolution: \"wordwrapjs@npm:5.1.0\"\n  checksum: 10c0/e147162f139eb8c05257729fde586f5422a2d242aa8f027b5fa5adead1b571b455d0690a15c73aeaa31c93ba96864caa06d84ebdb2c32a0890602ab86a7568d1\n  languageName: node\n  linkType: hard\n\n\"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0\":\n  version: 7.0.0\n  resolution: \"wrap-ansi@npm:7.0.0\"\n  dependencies:\n    ansi-styles: \"npm:^4.0.0\"\n    string-width: \"npm:^4.1.0\"\n    strip-ansi: \"npm:^6.0.0\"\n  checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da\n  languageName: node\n  linkType: hard\n\n\"wrap-ansi@npm:^8.1.0\":\n  version: 8.1.0\n  resolution: \"wrap-ansi@npm:8.1.0\"\n  dependencies:\n    ansi-styles: \"npm:^6.1.0\"\n    string-width: \"npm:^5.0.1\"\n    strip-ansi: \"npm:^7.0.1\"\n  checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60\n  languageName: node\n  linkType: hard\n\n\"wrappy@npm:1\":\n  version: 1.0.2\n  resolution: \"wrappy@npm:1.0.2\"\n  checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0\n  languageName: node\n  linkType: hard\n\n\"ws@npm:^5.2.3\":\n  version: 5.2.4\n  resolution: \"ws@npm:5.2.4\"\n  dependencies:\n    async-limiter: \"npm:~1.0.0\"\n  checksum: 10c0/14e84e4209f86ab68b01b9ebf42c88f81f26a64ff4886ef65d27c734c9b90b8d7e84712da3c3f7a922a4ab58bf0b96544d32ae28ebbd620fcda3027dd5bc7604\n  languageName: node\n  linkType: hard\n\n\"xtend@npm:^4.0.1\":\n  version: 4.0.2\n  resolution: \"xtend@npm:4.0.2\"\n  checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e\n  languageName: node\n  linkType: hard\n\n\"yallist@npm:^4.0.0\":\n  version: 4.0.0\n  resolution: \"yallist@npm:4.0.0\"\n  checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a\n  languageName: node\n  linkType: hard\n\n\"yallist@npm:^5.0.0\":\n  version: 5.0.0\n  resolution: \"yallist@npm:5.0.0\"\n  checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416\n  languageName: node\n  linkType: hard\n\n\"yaml@npm:^2.3.4\":\n  version: 2.7.0\n  resolution: \"yaml@npm:2.7.0\"\n  bin:\n    yaml: bin.mjs\n  checksum: 10c0/886a7d2abbd70704b79f1d2d05fe9fb0aa63aefb86e1cb9991837dced65193d300f5554747a872b4b10ae9a12bc5d5327e4d04205f70336e863e35e89d8f4ea9\n  languageName: node\n  linkType: hard\n\n\"yargs-parser@npm:^20.2.3\":\n  version: 20.2.9\n  resolution: \"yargs-parser@npm:20.2.9\"\n  checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72\n  languageName: node\n  linkType: hard\n\n\"yocto-queue@npm:^0.1.0\":\n  version: 0.1.0\n  resolution: \"yocto-queue@npm:0.1.0\"\n  checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f\n  languageName: node\n  linkType: hard\n\n\"zip3@npm:^1.0.4\":\n  version: 1.0.4\n  resolution: \"zip3@npm:1.0.4\"\n  checksum: 10c0/28d9cb2efac02d93483ee037dad7f6b1afdc66bc3b5ebae3467f68bf4364f76d8339b1e314b21063bd89f0a5c0bba357faf43ff5bec8ea2683bc27c95c4be2b7\n  languageName: node\n  linkType: hard\n\n\"zod-to-json-schema@npm:^3.22.3\":\n  version: 3.24.1\n  resolution: \"zod-to-json-schema@npm:3.24.1\"\n  peerDependencies:\n    zod: ^3.24.1\n  checksum: 10c0/dd4e72085003e41a3f532bd00061f27041418a4eb176aa6ce33042db08d141bd37707017ee9117d97738ae3f22fc3e1404ea44e6354634ac5da79d7d3173b4ee\n  languageName: node\n  linkType: hard\n\n\"zod-to-json-schema@npm:^3.24.1\":\n  version: 3.24.5\n  resolution: \"zod-to-json-schema@npm:3.24.5\"\n  peerDependencies:\n    zod: ^3.24.1\n  checksum: 10c0/0745b94ba53e652d39f262641cdeb2f75d24339fb6076a38ce55bcf53d82dfaea63adf524ebc5f658681005401687f8e9551c4feca7c4c882e123e66091dfb90\n  languageName: node\n  linkType: hard\n\n\"zod-validation-error@npm:^2.1.0\":\n  version: 2.1.0\n  resolution: \"zod-validation-error@npm:2.1.0\"\n  peerDependencies:\n    zod: ^3.18.0\n  checksum: 10c0/e8e8a0af64092dfb3388d759bf10fb7cf5358bc1bdb365771b8ac1944b1fb014ccbc8e60fbd69627961ea5873c5694e5c3fe730341c9842312fbb91661a1f451\n  languageName: node\n  linkType: hard\n\n\"zod@npm:^3.22.4\":\n  version: 3.24.1\n  resolution: \"zod@npm:3.24.1\"\n  checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b\n  languageName: node\n  linkType: hard\n\n\"zod@npm:^3.24.4\":\n  version: 3.24.4\n  resolution: \"zod@npm:3.24.4\"\n  checksum: 10c0/ab3112f017562180a41a0f83d870b333677f7d6b77f106696c56894567051b91154714a088149d8387a4f50806a2520efcb666f108cd384a35c236a191186d91\n  languageName: node\n  linkType: hard\n\n\"zrender@npm:5.6.1\":\n  version: 5.6.1\n  resolution: \"zrender@npm:5.6.1\"\n  dependencies:\n    tslib: \"npm:2.3.0\"\n  checksum: 10c0/dc1cc570054640cbd8fbb7b92e6252f225319522bfe3e8dc8bf02cc02d414e00a4c8d0a6f89bfc9d96e5e9511fdca94dd3d06bf53690df2b2f12b0fc560ac307\n  languageName: node\n  linkType: hard\n\n\"zstd-codec@npm:^0.1\":\n  version: 0.1.5\n  resolution: \"zstd-codec@npm:0.1.5\"\n  checksum: 10c0/8b7e6d9ce86f00fc4ea16c949aab5538505a1f3f1a9c7c095b2a7308b4ed894deec7bdb2c614e1486a337abdce09a6e56282dc0e39fe9f880953b094f8c7810b\n  languageName: node\n  linkType: hard\n\n\"zwitch@npm:^2.0.0\":\n  version: 2.0.4\n  resolution: \"zwitch@npm:2.0.4\"\n  checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e\n  languageName: node\n  linkType: hard\n"
  },
  {
    "path": "examples/get-started/.yarnrc.yml",
    "content": "# https://yarnpkg.com/configuration/yarnrc\nnodeLinker: node-modules\n# Define the registry to use when fetching packages.\nnpmRegistryServer: 'https://registry.yarnpkg.com'\n"
  },
  {
    "path": "examples/get-started/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\n\nconst args = process.argv;\n\nconst port = 8080;\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {'.js': 'jsx', '.css': 'css'},\n  entryPoints: ['src/app.tsx'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'production'),\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),\n    'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),\n    'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),\n    'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),\n    'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),\n    'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),\n    'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),\n    'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || ''),\n    'process.env.OpenAIToken': JSON.stringify(process.env.OpenAIToken || ''),\n    'process.env.NODE_DEBUG': JSON.stringify(false)\n  },\n  plugins: [\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['src/index.html'],\n        to: ['dist']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    const result = await esbuild\n      .build({\n        ...config,\n\n        minify: true,\n        sourcemap: false,\n        metafile: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n    fs.writeFileSync('dist/esbuild-metadata.json', JSON.stringify(result.metafile));\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(\n          `kepler.gl demo app running at ${`http://localhost:${port}`}, press Ctrl+C to stop`\n        );\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/get-started/package.json",
    "content": "{\n  \"name\": \"kepler-app-esbuild\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"bootstrap\": \"yarn install && yarn\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start\": \"node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/components\": \"^3.1.10\",\n    \"@kepler.gl/reducers\": \"^3.1.10\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-scripts\": \"^5.0.1\"\n  },\n  \"devDependencies\": {\n    \"@types/mapbox-gl\": \"^2.7.10\",\n    \"@types/node\": \"^18.15.3\",\n    \"@types/react\": \"^18.0.28\",\n    \"@types/react-dom\": \"^18.0.11\",\n    \"assert\": \"^2.1.0\",\n    \"axios\": \"^1.3.4\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"html-loader\": \"^4.2.0\",\n    \"ts-loader\": \"^9.4.2\",\n    \"ts-node\": \"^10.9.1\",\n    \"typescript\": \"^4.9.5\"\n  }\n}\n"
  },
  {
    "path": "examples/get-started/src/app.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport {connect, Provider} from 'react-redux';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport {applyMiddleware, combineReducers, compose, createStore} from 'redux';\nimport document from 'global/document';\n\nimport keplerGlReducer, {enhanceReduxMiddleware} from '@kepler.gl/reducers';\nimport KeplerGl from '@kepler.gl/components';\n\nconst reducers = combineReducers({\n  keplerGl: keplerGlReducer.initialState({\n    uiState: {\n      readOnly: false,\n      currentModal: null\n    }\n  })\n});\n\nconst middleWares = enhanceReduxMiddleware([\n  // Add other middlewares here\n]);\n\nconst enhancers = applyMiddleware(...middleWares);\n\nconst initialState = {};\nconst store = createStore(reducers, initialState, compose(enhancers));\n\nconst App = () => (\n  <div\n    style={{\n      position: 'absolute',\n      top: '0px',\n      left: '0px',\n      width: '100%',\n      height: '100%'\n    }}\n  >\n    <AutoSizer>\n      {({height, width}) => (\n        <KeplerGl\n          mapboxApiAccessToken=\"pk.xxx.yyy\" // Replace with your mapbox token\n          id=\"map\"\n          width={width}\n          height={height}\n        />\n      )}\n    </AutoSizer>\n  </div>\n);\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\nconst ConnectedApp = connect(mapStateToProps, dispatchToProps)(App);\nconst Root = () => (\n  <Provider store={store}>\n    <ConnectedApp />\n  </Provider>\n);\n\nconst container = document.getElementById('root');\nconst root = ReactDOM.createRoot(container);\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/get-started/src/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Get Started</title>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src=\"./bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/get-started/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"sourceMap\": true,\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"jsx\": \"react\",\n    \"esModuleInterop\": true\n  },\n  \"include\": [\"src\"],\n  \"compileOnSave\": false\n}\n"
  },
  {
    "path": "examples/get-started-vite/README.md",
    "content": "# Kepler.gl with Vite\n\nThis example uses [Kepler.gl](https://kepler.gl/) with Vite as the build tool.\n\n## Development\n\n### Installation\n\n```bash\n# Install dependencies\nyarn install\n```\n\n### Development Server\n\nTo start the development server with hot module replacement:\n\n```bash\nyarn dev\n```\n\n### Production Build\n\nTo create a production build and preview it:\n\n```bash\n# Create production build\nyarn build\n\n# Preview production build\nyarn preview\n```\n"
  },
  {
    "path": "examples/get-started-vite/eslint.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport js from '@eslint/js';\nimport globals from 'globals';\nimport reactHooks from 'eslint-plugin-react-hooks';\nimport reactRefresh from 'eslint-plugin-react-refresh';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n  {ignores: ['dist']},\n  {\n    extends: [js.configs.recommended, ...tseslint.configs.recommended],\n    files: ['**/*.{ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 2020,\n      globals: globals.browser\n    },\n    plugins: {\n      'react-hooks': reactHooks,\n      'react-refresh': reactRefresh\n    },\n    rules: {\n      ...reactHooks.configs.recommended.rules,\n      'react-refresh/only-export-components': ['warn', {allowConstantExport: true}]\n    }\n  }\n);\n"
  },
  {
    "path": "examples/get-started-vite/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Kepler + Vite</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/get-started-vite/package.json",
    "content": "{\n  \"name\": \"kepler-app-vite\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.12.1\",\n    \"@deck.gl/aggregation-layers\": \"^8.9.27\",\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/extensions\": \"^8.9.27\",\n    \"@deck.gl/geo-layers\": \"^8.9.27\",\n    \"@deck.gl/layers\": \"^8.9.27\",\n    \"@deck.gl/mesh-layers\": \"^8.9.27\",\n    \"@dnd-kit/core\": \"^6.1.0\",\n    \"@dnd-kit/modifiers\": \"^7.0.0\",\n    \"@dnd-kit/sortable\": \"^8.0.0\",\n    \"@kepler.gl/components\": \"3.1.7\",\n    \"@kepler.gl/reducers\": \"3.1.7\",\n    \"@loaders.gl/arrow\": \"4.3.2\",\n    \"@loaders.gl/core\": \"4.3.2\",\n    \"@loaders.gl/gltf\": \"4.3.2\",\n    \"@loaders.gl/images\": \"4.3.2\",\n    \"@loaders.gl/mvt\": \"4.3.2\",\n    \"@loaders.gl/parquet\": \"4.3.2\",\n    \"@loaders.gl/pmtiles\": \"4.3.2\",\n    \"@luma.gl/core\": \"8.5.21\",\n    \"@luma.gl/engine\": \"8.5.21\",\n    \"@luma.gl/gltools\": \"8.5.21\",\n    \"@luma.gl/shadertools\": \"8.5.21\",\n    \"@luma.gl/webgl\": \"8.5.21\",\n    \"@math.gl/core\": \"^4.0.0\",\n    \"@math.gl/web-mercator\": \"^3.6.2\",\n    \"apache-arrow\": \"17.0.0\",\n    \"gl-matrix\": \"^3.4.3\",\n    \"lodash\": \"4.17.21\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-scripts\": \"^5.0.1\",\n    \"react-virtualized\": \"^9.22.5\",\n    \"redux\": \"^4.2.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.25.0\",\n    \"@types/lodash.uniq\": \"^4\",\n    \"@types/node\": \"^22.15.21\",\n    \"@types/react\": \"^19.1.2\",\n    \"@types/react-dom\": \"^19.1.2\",\n    \"@vitejs/plugin-react\": \"^4.4.1\",\n    \"eslint\": \"^9.25.0\",\n    \"eslint-plugin-react-hooks\": \"^5.2.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.19\",\n    \"globals\": \"^16.0.0\",\n    \"typescript\": \"~5.8.3\",\n    \"typescript-eslint\": \"^8.30.1\",\n    \"vite\": \"^6.3.5\",\n    \"vite-plugin-wasm\": \"^3.4.1\"\n  },\n  \"resolutions\": {\n    \"apache-arrow\": \"17.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/get-started-vite/src/main.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Dispatch} from 'react';\nimport ReactDOM from 'react-dom/client';\nimport {connect, Provider} from 'react-redux';\n\nimport {applyMiddleware, combineReducers, compose, createStore} from 'redux';\n\nimport KeplerGl from '@kepler.gl/components';\nimport keplerGlReducer, {enhanceReduxMiddleware, KeplerGlState} from '@kepler.gl/reducers';\n\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\n\n// create reducers\nconst reducers = combineReducers({\n  // mount keplerGl reducer\n  keplerGl: keplerGlReducer.initialState({\n    uiState: {\n      readOnly: false,\n      currentModal: null\n    }\n  })\n});\n\n// create middlewares\nconst middleWares = enhanceReduxMiddleware([\n  // Add other middlewares here\n]);\n\n// craeteEnhancers\nconst enhancers = applyMiddleware(...middleWares);\n\n// create store\nconst initialState = {};\nconst store = createStore(reducers, initialState, compose(enhancers));\n\nconst App = () => (\n  <div\n    style={{\n      position: 'absolute',\n      top: '0px',\n      left: '0px',\n      width: '100%',\n      height: '100%'\n    }}\n  >\n    <AutoSizer>\n      {({height, width}) => (\n        <KeplerGl\n          mapboxApiAccessToken=\"pk.eyJ1IjoiaWdvcjEzMTMxMyIsImEiOiJjbTRtMWxoMnIwN3VhMmlxOGRnZ3AxcGhhIn0.d9YD6z5nsBNzPXrXWzIaAA\"\n          id=\"map\"\n          width={width}\n          height={height}\n        />\n      )}\n    </AutoSizer>\n  </div>\n);\n\nconst mapStateToProps = (state: KeplerGlState) => state;\nconst dispatchToProps = (dispatch: Dispatch<any>) => ({dispatch});\nconst ConnectedApp = connect(mapStateToProps, dispatchToProps)(App);\nconst Root = () => (\n  <Provider store={store}>\n    <ConnectedApp />\n  </Provider>\n);\n\nconst container = document.getElementById('root');\nif (container) {\n  const root = ReactDOM.createRoot(container);\n  root.render(<Root />);\n}\n"
  },
  {
    "path": "examples/get-started-vite/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/get-started-vite/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/get-started-vite/vite.config.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {defineConfig} from 'vite';\nimport react from '@vitejs/plugin-react';\nimport wasm from 'vite-plugin-wasm';\nimport {resolve} from 'path';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [wasm(), react()],\n  server: {\n    port: 8081,\n    open: true\n  },\n  build: {\n    outDir: 'dist',\n    sourcemap: false,\n    minify: true,\n    rollupOptions: {\n      input: {\n        main: resolve(__dirname, 'index.html')\n      }\n    },\n    target: 'esnext',\n    commonjsOptions: {\n      include: [/node_modules/],\n      transformMixedEsModules: true\n    }\n  },\n  define: {\n    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),\n    'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),\n    'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),\n    'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),\n    'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),\n    'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),\n    'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),\n    'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || ''),\n    'process.env.OpenAIToken': JSON.stringify(process.env.OpenAIToken || ''),\n    'process.env.NODE_DEBUG': JSON.stringify(false)\n  },\n  resolve: {\n    dedupe: ['styled-components'],\n    alias: {\n      '@': resolve(__dirname, './src')\n    }\n  },\n  optimizeDeps: {\n    exclude: ['parquet-wasm', '@loaders.gl/parquet', 'apache-arrow'],\n    include: [\n      'buffer',\n      'react',\n      'react-dom',\n      'react-redux',\n      'redux',\n      'styled-components',\n      '@kepler.gl/components',\n      '@kepler.gl/reducers',\n      '@kepler.gl/actions',\n      '@kepler.gl/constants',\n      '@kepler.gl/utils',\n      '@kepler.gl/schemas',\n      '@kepler.gl/table',\n      '@kepler.gl/layers',\n      '@kepler.gl/deckgl-layers',\n      '@kepler.gl/effects',\n      '@kepler.gl/styles',\n      '@kepler.gl/tasks',\n      '@deck.gl/core',\n      '@deck.gl/layers',\n      '@deck.gl/aggregation-layers',\n      '@deck.gl/geo-layers',\n      '@deck.gl/mesh-layers',\n      '@deck.gl/extensions',\n      '@luma.gl/core',\n      '@luma.gl/engine',\n      '@luma.gl/gltools',\n      '@luma.gl/shadertools',\n      '@luma.gl/webgl',\n      '@loaders.gl/core',\n      '@loaders.gl/gltf',\n      '@loaders.gl/images',\n      '@loaders.gl/parquet',\n      '@math.gl/core',\n      '@math.gl/web-mercator',\n      'gl-matrix',\n      'lodash.uniq'\n    ],\n    esbuildOptions: {\n      target: 'es2020'\n    }\n  }\n});\n"
  },
  {
    "path": "examples/node-app/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    \"@babel/plugin-transform-export-namespace-from\"\n  ]\n}\n"
  },
  {
    "path": "examples/node-app/README.md",
    "content": "# Node/Express\n\nThis example shows how to embed Kepler.gl in a node/express/webpack application.\n\n#### 1. Install\n\n```sh\nyarn\n```\n\n#### 2. Mapbox Token\n\nadd mapbox access token to node env\n\n```sh\nexport MapboxAccessToken=<your_mapbox_token>\n```\n\n#### 3. Start the app\n\n```sh\nyarn start\n```\n"
  },
  {
    "path": "examples/node-app/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\n\nconst args = process.argv;\n\nconst port = 3000;\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// Ensure a single instance of React and friends to avoid invalid hook calls\nconst ROOT_NODE_MODULES = join('..', '..', 'node_modules');\nconst thirdPartyAliases = {\n  react: join(ROOT_NODE_MODULES, 'react'),\n  'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),\n  'react-redux': join(ROOT_NODE_MODULES, 'react-redux', 'lib'),\n  'styled-components': join(ROOT_NODE_MODULES, 'styled-components'),\n  'apache-arrow': join(ROOT_NODE_MODULES, 'apache-arrow')\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || '')\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    replace({\n      __PACKAGE_VERSION__: '3.1.10',\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    const result = await esbuild\n      .build({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: true,\n        sourcemap: false,\n        metafile: true,\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n    fs.writeFileSync('dist/esbuild-metadata.json', JSON.stringify(result.metafile));\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`kepler.gl node-app example running at ${`http://localhost:${port}`}`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/node-app/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <title>kepler.gl demo</title>\n    <link rel=\"stylesheet\" href=\"//d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n    <style type=\"text/css\">\n       body {margin: 0; padding: 0; overflow: hidden;}\n    </style>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src='/bundle.js'></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/node-app/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start-local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/components\": \"^3.1.10\",\n    \"@kepler.gl/reducers\": \"^3.1.10\",\n    \"express\": \"^4.17.1\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/node-app/server.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst express = require('express');\nconst path = require('path');\n\nconst ADDRESS = '0.0.0.0';\nconst PORT = process.env.PORT || 3000;\n\nconst app = express();\n\n// Serve static files from dist\napp.use(express.static(path.join(__dirname, 'dist')));\n\n// Always return index.html for any route (SPA)\napp.get('*', function response(req, res) {\n  res.sendFile(path.join(__dirname, 'dist', 'index.html'));\n});\n\n/* eslint-disable no-console */\napp.listen(PORT, ADDRESS, function onStart(err) {\n  if (err) {\n    console.error(err);\n    return;\n  }\n  console.info(`==> 🌎 Listening on port ${PORT}. Open up http://${ADDRESS}:${PORT}/`);\n});\n/* eslint-enable no-console */\n"
  },
  {
    "path": "examples/node-app/src/app-reducer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction, handleActions} from 'redux-actions';\n\n// CONSTANTS\nexport const INIT = 'INIT';\n\n// ACTIONS\nexport const appInit = createAction(INIT);\n\n// INITIAL_STATE\nconst initialState = {\n  appName: 'example',\n  loaded: false\n};\n\n// REDUCER\nconst appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    })\n  },\n  initialState\n);\n\nexport default appReducer;\n"
  },
  {
    "path": "examples/node-app/src/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport styled from 'styled-components';\nimport KeplerGl from '@kepler.gl/components';\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line\n\nconst StyledWrapper = styled.div`\n  position: absolute;\n  width: 100vw;\n  height: 100vh;\n`;\n\nconst App = () => (\n  <StyledWrapper>\n    <AutoSizer>\n      {({height, width}) => (\n        <KeplerGl mapboxApiAccessToken={MAPBOX_TOKEN} id=\"map1\" width={width} height={height} />\n      )}\n    </AutoSizer>\n  </StyledWrapper>\n);\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/node-app/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport Modal from 'react-modal';\nimport {Provider} from 'react-redux';\nimport store from './store';\nimport App from './app';\n\nModal.setAppElement('#root');\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/node-app/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer, {enhanceReduxMiddleware} from '@kepler.gl/reducers';\nimport appReducer from './app-reducer';\n\nconst reducers = combineReducers({\n  keplerGl: keplerGlReducer,\n  app: appReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nexport default createStore(reducers, {}, compose(...enhancers));\n"
  },
  {
    "path": "examples/open-modal/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    \"@babel/plugin-transform-export-namespace-from\"\n  ]\n}\n"
  },
  {
    "path": "examples/open-modal/README.md",
    "content": "# Open modal\n\nExample showing how to open kepler.gl in a modal.\n\n#### 1. Install\n\n```sh\nyarn\n```\n\n#### 2. Mapbox Token\n\nadd mapbox access token to node env\n\n```sh\nexport MapboxAccessToken=<your_mapbox_token>\n```\n\n#### 3. Start the app\n\n```sh\nyarn start\n```\n"
  },
  {
    "path": "examples/open-modal/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\n\nconst args = process.argv;\n\nconst port = 8080;\n\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// Ensure a single instance of React and friends to avoid invalid hook calls\nconst ROOT_NODE_MODULES = join('..', '..', 'node_modules');\nconst thirdPartyAliases = {\n  react: join(ROOT_NODE_MODULES, 'react'),\n  'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),\n  'react-redux': join(ROOT_NODE_MODULES, 'react-redux', 'lib'),\n  'styled-components': join(ROOT_NODE_MODULES, 'styled-components'),\n  'apache-arrow': join(ROOT_NODE_MODULES, 'apache-arrow')\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || '')\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    replace({\n      __PACKAGE_VERSION__: '3.1.10',\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        alias: thirdPartyAliases,\n        minify: false,\n        sourcemap: true,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`kepler.gl open-modal example running at ${`http://localhost:${port}`}`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/open-modal/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <title>kepler.gl demo</title>\n    <link rel=\"stylesheet\" href=\"//d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n    <style type=\"text/css\">\n       body {margin: 0; padding: 0; overflow: hidden;}\n    </style>\n  </head>\n  <body>\n    <div id=\"root\" />\n    <script src='bundle.js'></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/open-modal/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"start-local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/actions\": \"^3.1.9\",\n    \"@kepler.gl/components\": \"^3.1.9\",\n    \"@kepler.gl/reducers\": \"^3.1.9\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-modal\": \"^3.1.10\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  }\n}\n"
  },
  {
    "path": "examples/open-modal/src/app-reducer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction, handleActions} from 'redux-actions';\n\n// CONSTANTS\nexport const INIT = 'INIT';\nexport const SHOW_MODAL = 'SHOW_MODAL';\n\n// ACTIONS\nexport const appInit = createAction(INIT);\nexport const showModal = createAction(SHOW_MODAL);\n\n// INITIAL_STATE\nconst initialState = {\n  appName: 'example',\n  loaded: false,\n  modal: null\n};\n\n// REDUCER\nconst appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    }),\n\n    [SHOW_MODAL]: (state, action) => ({\n      ...state,\n      modal: action.payload\n    })\n  },\n  initialState\n);\n\nexport default appReducer;\n"
  },
  {
    "path": "examples/open-modal/src/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {connect} from 'react-redux';\nimport {addDataToMap, wrapTo} from '@kepler.gl/actions';\n\nimport Modal from 'react-modal';\nimport {showModal} from './app-reducer';\nimport sampleData from './data/sample-data';\n\nimport FreshMap from './components/fresh-map';\nimport SavedMap from './components/saved-map';\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line\n\nclass App extends Component {\n  componentDidUpdate(prevProps) {\n    if (!prevProps.keplerGl.bar && this.props.keplerGl.bar) {\n      this.props.dispatch(\n        wrapTo(\n          'bar',\n          addDataToMap({\n            datasets: sampleData,\n            options: {\n              centerMap: true\n            },\n            config: {\n              mapStyle: {\n                styleType: 'light'\n              }\n            }\n          })\n        )\n      );\n    }\n  }\n  _closeModal = () => {\n    this.props.dispatch(showModal(null));\n  };\n\n  _openModal = id => {\n    this.props.dispatch(showModal(id));\n  };\n\n  render() {\n    const {\n      app: {modal}\n    } = this.props;\n\n    return (\n      <div style={{position: 'absolute', width: '100%', height: '100%'}}>\n        <button onClick={() => this._openModal('foo')}>Show Kepler.gl id: foo</button>\n        <button onClick={() => this._openModal('bar')}>Show Kepler.gl id: bar</button>\n\n        <Modal isOpen={modal === 'foo'}>\n          <div>\n            This Kepler.gl component will always load a fresh state when re mounted, state inside\n            this component will be destroyed once its unmounted.\n          </div>\n          <button onClick={this._closeModal}>Close</button>\n          <FreshMap dispatch={this.props.dispatch} mapboxApiAccessToken={MAPBOX_TOKEN} id=\"foo\" />\n        </Modal>\n\n        <Modal isOpen={modal === 'bar'}>\n          By passing in mint: false, This Kepler.gl instance will keep the state of \"bar\" even when\n          it is unmounted.\n          <button onClick={this._closeModal}>Close</button>\n          <SavedMap dispatch={this.props.dispatch} mapboxApiAccessToken={MAPBOX_TOKEN} id=\"bar\" />\n        </Modal>\n      </div>\n    );\n  }\n}\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/open-modal/src/components/fresh-map.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport {addDataToMap, wrapTo} from '@kepler.gl/actions';\nimport KeplerGl from '@kepler.gl/components';\n\nimport sampleData from '../data/sample-data';\nimport config from '../configurations/config';\n\nexport default class FreshMap extends Component {\n  componentDidMount() {\n    this.props.dispatch(\n      wrapTo(\n        this.props.id,\n        addDataToMap({\n          datasets: sampleData,\n          options: {\n            centerMap: true\n          },\n          config\n        })\n      )\n    );\n  }\n\n  render() {\n    const {mapboxApiAccessToken, id} = this.props;\n\n    return (\n      <AutoSizer>\n        {({height, width}) => (\n          <KeplerGl\n            mapboxApiAccessToken={mapboxApiAccessToken}\n            id={id}\n            width={width}\n            height={height}\n          />\n        )}\n      </AutoSizer>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/open-modal/src/components/saved-map.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport KeplerGl from '@kepler.gl/components';\n\nexport default class SavedMap extends Component {\n  render() {\n    const {mapboxApiAccessToken, id} = this.props;\n\n    return (\n      <AutoSizer>\n        {({height, width}) => (\n          <KeplerGl\n            mapboxApiAccessToken={mapboxApiAccessToken}\n            id={id}\n            width={width}\n            height={height}\n            mint={false}\n          />\n        )}\n      </AutoSizer>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/open-modal/src/configurations/config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: '1s8r5md',\n          type: 'point',\n          config: {\n            dataId: 'tree_data',\n            label: 'location',\n            color: [18, 147, 154],\n            columns: {\n              lat: 'Location_latitude',\n              lng: 'Location_longitude',\n              altitude: null\n            },\n            isVisible: true,\n            visConfig: {\n              radius: 10,\n              fixedRadius: false,\n              opacity: 0.8,\n              outline: false,\n              thickness: 2,\n              colorRange: {\n                name: 'Ice And Fire',\n                type: 'diverging',\n                category: 'Uber',\n                colors: ['#D50255', '#FEAD54', '#FEEDB1', '#E8FEB5', '#49E3CE', '#0198BD'],\n                reversed: true\n              },\n              radiusRange: [33.6, 96.2],\n              'hi-precision': false\n            }\n          },\n          visualChannels: {\n            colorField: {\n              name: 'Species',\n              type: 'string'\n            },\n            colorScale: 'ordinal',\n            sizeField: {\n              name: 'Age',\n              type: 'integer'\n            },\n            sizeScale: 'sqrt'\n          }\n        },\n        {\n          id: '7otjdz',\n          type: 'hexagon',\n          config: {\n            dataId: 'tree_data',\n            label: 'Density',\n            color: [23, 184, 190],\n            columns: {\n              lat: 'Location_latitude',\n              lng: 'Location_longitude'\n            },\n            isVisible: true,\n            visConfig: {\n              opacity: 0.2,\n              worldUnitSize: 0.8,\n              resolution: 8,\n              colorRange: {\n                name: 'ColorBrewer GnBu-6',\n                type: 'sequential',\n                category: 'ColorBrewer',\n                colors: ['#f0f9e8', '#ccebc5', '#a8ddb5', '#7bccc4', '#43a2ca', '#0868ac'],\n                reversed: false\n              },\n              coverage: 1,\n              sizeRange: [0, 500],\n              percentile: [0, 100],\n              elevationPercentile: [0, 100],\n              elevationScale: 5,\n              'hi-precision': false,\n              colorAggregation: 'average',\n              sizeAggregation: 'average',\n              enable3d: false\n            }\n          },\n          visualChannels: {\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            tree_data: ['TreeID', 'Species', 'Address', 'Has_Species', 'SiteInfo']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal',\n      splitMaps: []\n    },\n    mapState: {\n      bearing: 0,\n      dragRotate: false,\n      latitude: 37.76544453921235,\n      longitude: -122.46289885132524,\n      pitch: 0,\n      zoom: 12.032736770460689,\n      isSplit: false\n    },\n    mapStyle: {\n      styleType: 'light',\n      topLayerGroups: {\n        label: true\n      },\n      visibleLayerGroups: {\n        label: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "examples/open-modal/src/data/sample-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default {\n  info: {\n    label: 'San Francisco Trees',\n    id: 'tree_data'\n  },\n  data: {\n    fields: [\n      {\n        name: 'TreeID'\n      },\n      {\n        name: 'Species'\n      },\n      {\n        name: 'Address'\n      },\n      {\n        name: 'Has_Species'\n      },\n      {\n        name: 'SiteInfo'\n      },\n      {\n        name: 'PlantType'\n      },\n      {\n        name: 'PlantDate'\n      },\n      {\n        name: 'Plan'\n      },\n      {\n        name: 'Age'\n      },\n      {\n        name: 'DBH'\n      },\n      {\n        name: 'Location_latitude'\n      },\n      {\n        name: 'Location_longitude'\n      }\n    ],\n    rows: [\n      [\n        141565,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/21/88 0:00',\n        1988,\n        29,\n        21,\n        37.77596769,\n        -122.4413967\n      ],\n      [\n        232565,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '940 Elizabeth St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.75171022,\n        -122.441498\n      ],\n      [\n        119263,\n        'Pinus radiata :: Monterey Pine',\n        '495X Lakeshore Dr',\n        false,\n        'Median : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        207368,\n        'Ligustrum japonicum :: Japanese Privet',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76021031,\n        -122.4707394\n      ],\n      [\n        188702,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1501 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        17,\n        37.74220867,\n        -122.3872932\n      ],\n      [\n        141566,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.77570453,\n        -122.4414629\n      ],\n      [\n        188697,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1301 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.73973386,\n        -122.3829339\n      ],\n      [\n        122650,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        203507,\n        'Agonis flexuosa :: Peppermint Willow',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        23,\n        37.76018051,\n        -122.4708661\n      ],\n      [\n        122670,\n        'Lyonothamnus floribundus subsp. asplenifolius :: Santa Cruz Ironwood',\n        '301x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122658,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122660,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122648,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        196590,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.77303718,\n        -122.4172458\n      ],\n      [\n        196591,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77305251,\n        -122.4172809\n      ],\n      [\n        122649,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        145278,\n        'Fraxinus uhdei :: Shamel Ash: Evergreen Ash',\n        '730 11th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        0,\n        37.77448101,\n        -122.4691091\n      ],\n      [\n        96369,\n        'Lophostemon confertus :: Brisbane Box',\n        '168 Cervantes Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/9/11 0:00',\n        2011,\n        6,\n        24,\n        37.80468521,\n        -122.4402315\n      ],\n      [\n        182260,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '1471 28th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.75995923,\n        -122.4866885\n      ],\n      [\n        251179,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        251178,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        203506,\n        'Agonis flexuosa :: Peppermint Willow',\n        '910 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.76026555,\n        -122.4705695\n      ],\n      [\n        52566,\n        'Hakea suaveolens :: Sweet Hakea Tree',\n        '1555 40th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '1/12/16 0:00',\n        2016,\n        1,\n        2,\n        37.75778273,\n        -122.4993677\n      ],\n      [\n        22346,\n        'Lophostemon confertus :: Brisbane Box',\n        '5280 03rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/27/14 0:00',\n        2014,\n        3,\n        3,\n        37.72954822,\n        -122.3926894\n      ],\n      [\n        23567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2975 Van Ness Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/27/92 0:00',\n        1992,\n        25,\n        3,\n        37.80304673,\n        -122.4250636\n      ],\n      [\n        237469,\n        'Prunus cerasifera :: Cherry Plum',\n        '4200 23rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/00 0:00',\n        2000,\n        17,\n        0,\n        37.7528239,\n        -122.4366208\n      ],\n      [\n        244214,\n        'Tree(s) ::',\n        '342 Scott St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.77300994,\n        -122.4355982\n      ],\n      [\n        21823,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2741 Taylor St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/19/06 0:00',\n        2006,\n        11,\n        3,\n        37.80797069,\n        -122.4158502\n      ],\n      [\n        188686,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1300 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73901484,\n        -122.381104\n      ],\n      [\n        196592,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.77310137,\n        -122.4173585\n      ],\n      [\n        22614,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1001 Turk St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        37.78136501,\n        -122.4248636\n      ],\n      [\n        10933,\n        'Lophostemon confertus :: Brisbane Box',\n        '1359 Hudson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73743182,\n        -122.3840631\n      ],\n      [\n        2889,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1545 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.79059396,\n        -122.4198251\n      ],\n      [\n        240126,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '20 Lily St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.77473647,\n        -122.4213262\n      ],\n      [\n        183515,\n        'Myoporum laetum :: Myoporum',\n        '2562 26th Ave',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.73970633,\n        -122.4828998\n      ],\n      [\n        13434,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2X Market St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.79448363,\n        -122.3950546\n      ],\n      [\n        226924,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.74625595,\n        -122.4338758\n      ],\n      [\n        192589,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        3558,\n        'Lophostemon confertus :: Brisbane Box',\n        '101 Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 17:52',\n        2017,\n        0,\n        6,\n        37.74050235,\n        -122.3776366\n      ],\n      [\n        222540,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74632546,\n        -122.4338277\n      ],\n      [\n        192590,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        28375,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '3014 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/9/03 0:00',\n        2003,\n        14,\n        3,\n        37.78875791,\n        -122.4429785\n      ],\n      [\n        222539,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74630146,\n        -122.4338571\n      ],\n      [\n        226925,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.74628109,\n        -122.4338516\n      ],\n      [\n        27522,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '3201 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.78709954,\n        -122.4473685\n      ],\n      [\n        18228,\n        'Maytenus boaria :: Mayten',\n        '1737-1795 Post St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/04 0:00',\n        2004,\n        13,\n        12,\n        37.78534132,\n        -122.431005\n      ],\n      [\n        17325,\n        'Fraxinus americana :: American Ash',\n        '150 Otis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/31/16 0:00',\n        2016,\n        1,\n        3,\n        37.77095437,\n        -122.4203558\n      ],\n      [\n        3528,\n        'Lophostemon confertus :: Brisbane Box',\n        '201X Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 16:23',\n        2017,\n        0,\n        3,\n        37.74144532,\n        -122.3793046\n      ],\n      [\n        251186,\n        'Juniperus chinensis :: Juniper',\n        '3042 Steiner St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.79753285,\n        -122.4371106\n      ],\n      [\n        138938,\n        'Tree(s) ::',\n        '632 HAYES ST',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.77646121,\n        -122.4269126\n      ],\n      [\n        60867,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/83 0:00',\n        1983,\n        34,\n        null,\n        null,\n        null\n      ],\n      [\n        233897,\n        'Araucaria heterophylla :: Norfolk Island Pine',\n        '1021 Noe St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        9,\n        37.75266993,\n        -122.4318862\n      ],\n      [\n        251187,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        121137,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        2909,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1700 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        30,\n        37.79038763,\n        -122.4229834\n      ],\n      [\n        27602,\n        'Pittosporum undulatum :: Victorian Box',\n        '988 Fulton St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        20,\n        37.77766599,\n        -122.4324106\n      ],\n      [\n        44642,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/29/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        4749,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1255 Columbus Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/3/15 0:00',\n        2015,\n        2,\n        8,\n        37.80577303,\n        -122.4182612\n      ],\n      [\n        44994,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        121139,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        226596,\n        'Leptospermum scoparium :: New Zealand Tea Tree',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.74632473,\n        -122.4337918\n      ],\n      [\n        239043,\n        'Pyrus calleryana :: Ornamental Pear',\n        '10 12th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77381038,\n        -122.4194082\n      ],\n      [\n        222541,\n        'Callistemon citrinus :: Lemon Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.7463451,\n        -122.433801\n      ],\n      [\n        138748,\n        'Lophostemon confertus :: Brisbane Box',\n        '1614 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        0,\n        37.7853077,\n        -122.4415226\n      ],\n      [\n        226926,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.74630146,\n        -122.4338212\n      ],\n      [\n        222538,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74627164,\n        -122.4338847\n      ],\n      [\n        166623,\n        'Potential Site :: Potential Site',\n        '520 Jersey St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.75036438,\n        -122.4371358\n      ],\n      [\n        251188,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        19206,\n        'Laurus nobilis :: Sweet Bay: Grecian Laurel',\n        '630 Sansome St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79625001,\n        -122.4016986\n      ],\n      [\n        121138,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        251206,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '270 Trumbull St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73083203,\n        -122.4245292\n      ],\n      [\n        25725,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '729 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.77705272,\n        -122.4286972\n      ],\n      [\n        251249,\n        'Private shrub :: Private Shrub',\n        '161 Lundys Ln',\n        false,\n        'Front Yard : Yard',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.7429691,\n        -122.4194092\n      ],\n      [\n        251293,\n        '::',\n        '3789 Market St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.75373446,\n        -122.442203\n      ],\n      [\n        221749,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        24,\n        37.76366261,\n        -122.4520649\n      ],\n      [\n        221750,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        11,\n        37.76366331,\n        -122.4521462\n      ],\n      [\n        245474,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        19,\n        37.76363745,\n        -122.4522558\n      ],\n      [\n        251419,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        251418,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        16698,\n        'Pittosporum undulatum :: Victorian Box',\n        '950X Noriega St',\n        false,\n        'Median : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        12,\n        37.75434951,\n        -122.4740143\n      ],\n      [\n        251532,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251531,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251642,\n        'Shrub :: Shrub',\n        '2253 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.79122797,\n        -122.4428704\n      ],\n      [\n        251700,\n        '::',\n        '1560 Sanchez St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '6/5/17 0:00',\n        2017,\n        0,\n        3,\n        37.74408823,\n        -122.4290963\n      ],\n      [\n        253008,\n        '::',\n        '1201 Tennessee St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '7/10/17 0:00',\n        2017,\n        0,\n        3,\n        37.7563302,\n        -122.3890342\n      ],\n      [\n        208471,\n        'Cordyline australis :: Dracena Palm',\n        '1175 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/23/17 0:00',\n        2017,\n        0,\n        3,\n        37.75989512,\n        -122.4735278\n      ],\n      [\n        253411,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253409,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253410,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253412,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        225282,\n        'Pittosporum crassifolium :: Karo Tree',\n        '101 Rivoli St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        6,\n        37.76244046,\n        -122.4496446\n      ],\n      [\n        173852,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173849,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173850,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173851,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173848,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173846,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173847,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173853,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        107717,\n        'Tree(s) ::',\n        '2400X 25th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '10/16/14 0:00',\n        2014,\n        3,\n        3,\n        37.74238326,\n        -122.4917845\n      ],\n      [\n        110194,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '2123 Pierce St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78980712,\n        -122.4374813\n      ],\n      [\n        59839,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1201 Palou Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/10/81 0:00',\n        1981,\n        36,\n        3,\n        37.72976741,\n        -122.3836854\n      ],\n      [\n        183689,\n        'Rhamnus alaternus :: Italian Buckthorn',\n        '1341 42nd Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76162779,\n        -122.5017998\n      ],\n      [\n        18916,\n        'Olea europaea :: Olive Tree',\n        '2621 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.78950262,\n        -122.4361188\n      ],\n      [\n        20281,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '20X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        null,\n        null\n      ],\n      [\n        119758,\n        'Maytenus boaria :: Mayten',\n        '151X Octavia St Frontage West',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        228560,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76151469,\n        -122.4216727\n      ],\n      [\n        18052,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2840 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78687837,\n        -122.4426898\n      ],\n      [\n        212622,\n        'Prunus cerasifera :: Cherry Plum',\n        '115 Beverly St',\n        false,\n        'Sidewalk: Property side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71819234,\n        -122.4717606\n      ],\n      [\n        20398,\n        'Cupressus macrocarpa :: Monterey Cypress',\n        '262X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        null,\n        null\n      ],\n      [\n        66855,\n        'Maytenus boaria :: Mayten',\n        '2041 Hayes St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/15/91 0:00',\n        1991,\n        26,\n        8,\n        37.77336241,\n        -122.4501107\n      ],\n      [\n        237033,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.77334931,\n        -122.4236445\n      ],\n      [\n        116641,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        218972,\n        'Fraxinus americana :: American Ash',\n        '742 Myra Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.74058076,\n        -122.4513096\n      ],\n      [\n        47510,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '54X Cayuga Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.73158339,\n        -122.4295272\n      ],\n      [\n        237030,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.77325086,\n        -122.4236179\n      ],\n      [\n        213016,\n        'Tree(s) ::',\n        '120 Ortega St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.75295695,\n        -122.4649514\n      ],\n      [\n        228561,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76156532,\n        -122.4216758\n      ],\n      [\n        72565,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        '11/16/96 0:00',\n        1996,\n        21,\n        3,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        116640,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        6337,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1916 Ellis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78175831,\n        -122.4377689\n      ],\n      [\n        832,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1396 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:18',\n        2017,\n        0,\n        3,\n        37.76054428,\n        -122.5069327\n      ],\n      [\n        251241,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2305 Golden Gate Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77731326,\n        -122.448748\n      ],\n      [\n        18836,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2055 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79068779,\n        -122.4267446\n      ],\n      [\n        11598,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4134 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:51',\n        2017,\n        0,\n        3,\n        37.76048345,\n        -122.5065435\n      ],\n      [\n        111561,\n        'Eriobotrya deflexa :: Bronze Loquat',\n        '1498x Kansas St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.75089751,\n        -122.402317\n      ],\n      [\n        97319,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1038 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:52',\n        2017,\n        0,\n        3,\n        37.74306751,\n        -122.4774242\n      ],\n      [\n        58021,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4026 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:07',\n        2017,\n        0,\n        3,\n        37.76257173,\n        -122.5012197\n      ],\n      [\n        175046,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71164354,\n        -122.4709833\n      ],\n      [\n        26071,\n        'Melaleuca quinquenervia :: Cajeput',\n        '247 Orizaba Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:41',\n        2017,\n        0,\n        3,\n        37.71342972,\n        -122.4626502\n      ],\n      [\n        11606,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4200x Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:54',\n        2017,\n        0,\n        3,\n        37.76045095,\n        -122.5071358\n      ],\n      [\n        36617,\n        'Robinia x ambigua :: Locust',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/20/98 0:00',\n        1998,\n        19,\n        null,\n        null,\n        null\n      ],\n      [\n        24532,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1395 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:21',\n        2017,\n        0,\n        3,\n        37.76072337,\n        -122.5070915\n      ],\n      [\n        5972,\n        'Pittosporum undulatum :: Victorian Box',\n        '1550 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '2/2/90 0:00',\n        1990,\n        27,\n        8,\n        37.78138444,\n        -122.4331105\n      ],\n      [\n        830,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '1384 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:15',\n        2017,\n        0,\n        3,\n        37.76071635,\n        -122.5069447\n      ],\n      [\n        251225,\n        'Jacaranda mimosifolia :: Jacaranda',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75927435,\n        -122.4422441\n      ],\n      [\n        54712,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4508 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:58',\n        2017,\n        0,\n        3,\n        37.76234527,\n        -122.5063635\n      ],\n      [\n        3193,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '3698 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78640011,\n        -122.454267\n      ],\n      [\n        21682,\n        'Prunus serrulata :: Ornamental Cherry',\n        '1830 Sutter St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.78651577,\n        -122.4305403\n      ],\n      [\n        97322,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1055 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:42',\n        2017,\n        0,\n        3,\n        37.74287614,\n        -122.4775411\n      ],\n      [\n        175377,\n        'Ulmus pumila :: Siberian Elm',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        2,\n        37.71516198,\n        -122.4717512\n      ],\n      [\n        142720,\n        '::',\n        '1170 COLUMBUS AVE',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.80483123,\n        -122.416565\n      ],\n      [\n        251231,\n        'Pinus radiata :: Monterey Pine',\n        '164 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.72047169,\n        -122.4780251\n      ],\n      [\n        29943,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '242 Broad St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:23',\n        2017,\n        0,\n        3,\n        37.71323398,\n        -122.4605594\n      ],\n      [\n        97323,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1011 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:25',\n        2017,\n        0,\n        3,\n        37.74289372,\n        -122.4772138\n      ],\n      [\n        11582,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4000 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:49',\n        2017,\n        0,\n        3,\n        37.76055019,\n        -122.5050036\n      ],\n      [\n        251230,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        251232,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        179480,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '100 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.72024935,\n        -122.4780157\n      ],\n      [\n        251221,\n        'Tree(s) ::',\n        '2401X Washington St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        48827,\n        'Lophostemon confertus :: Brisbane Box',\n        '435 Pacific Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/7/02 0:00',\n        2002,\n        15,\n        5,\n        37.79736024,\n        -122.4027685\n      ],\n      [\n        251242,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2449 Golden Gate Ave',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7770353,\n        -122.4514366\n      ],\n      [\n        48332,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/02 0:00',\n        2002,\n        15,\n        null,\n        null,\n        null\n      ],\n      [\n        175044,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80x Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.71158043,\n        -122.4709734\n      ],\n      [\n        251226,\n        'Koelreuteria paniculata :: Golden Rain Tree',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75950087,\n        -122.4421669\n      ],\n      [\n        17881,\n        'Olea europaea :: Olive Tree',\n        '2205 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78806671,\n        -122.4322373\n      ],\n      [\n        24515,\n        'Lophostemon confertus :: Brisbane Box',\n        '2030 Fell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/7/16 7:40',\n        2016,\n        1,\n        3,\n        37.77238102,\n        -122.4513341\n      ],\n      [\n        97317,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1000 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:59',\n        2017,\n        0,\n        3,\n        37.74308434,\n        -122.4769891\n      ],\n      [\n        2985,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2253 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.78912498,\n        -122.4314293\n      ],\n      [\n        182083,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1101 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:33',\n        2017,\n        0,\n        3,\n        37.74285808,\n        -122.4780077\n      ],\n      [\n        18700,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '100X Richardson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.79975916,\n        -122.4460044\n      ],\n      [\n        9914,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '1900 Gough St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        126,\n        37.79116413,\n        -122.4257789\n      ],\n      [\n        93463,\n        \"Prunus x 'Amanogawa' :: Flowering Cherry\",\n        '1739 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:05',\n        2017,\n        0,\n        3,\n        37.74255391,\n        -122.4849289\n      ],\n      [\n        15429,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2125 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/15 0:00',\n        2015,\n        2,\n        15,\n        37.76296062,\n        -122.419386\n      ],\n      [\n        138801,\n        'Maytenus boaria :: Mayten',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/9/97 0:00',\n        1997,\n        20,\n        2,\n        37.78770158,\n        -122.4350685\n      ],\n      [\n        251243,\n        \"Ceanothus 'Ray Hartman' :: California Lilac 'Ray Hartman'\",\n        '299x Chapman St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7417247,\n        -122.411639\n      ],\n      [\n        138359,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78607841,\n        -122.4338643\n      ],\n      [\n        251248,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '829 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73477716,\n        -122.4360662\n      ],\n      [\n        138368,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78600955,\n        -122.4344937\n      ],\n      [\n        138807,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2355 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78777373,\n        -122.4346006\n      ],\n      [\n        57209,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '2898 Clay St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '12/15/14 0:00',\n        2014,\n        3,\n        3,\n        37.78998742,\n        -122.4404101\n      ],\n      [\n        210495,\n        'Myoporum laetum :: Myoporum',\n        '365 San Leandro Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        21,\n        37.73051136,\n        -122.4689316\n      ],\n      [\n        138369,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78599791,\n        -122.4345397\n      ],\n      [\n        6069,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1930 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78057618,\n        -122.439557\n      ],\n      [\n        138802,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78771109,\n        -122.4350205\n      ],\n      [\n        138365,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.78603526,\n        -122.4342542\n      ],\n      [\n        251246,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '722X Mendell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73887284,\n        -122.3866046\n      ],\n      [\n        251245,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '701 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/4/92 0:00',\n        1992,\n        25,\n        null,\n        37.73445362,\n        -122.4338962\n      ],\n      [\n        103786,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '1221 Polk St',\n        false,\n        'Sidewalk: Curb side : Pot',\n        'Tree',\n        '11/1/09 0:00',\n        2009,\n        8,\n        3,\n        37.78810145,\n        -122.4202677\n      ],\n      [\n        138362,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78605488,\n        -122.4340541\n      ],\n      [\n        134567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '99 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/6/11 0:00',\n        2011,\n        6,\n        4,\n        37.77846618,\n        -122.4173997\n      ],\n      [\n        138367,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78601298,\n        -122.4344509\n      ]\n    ]\n  }\n};\n"
  },
  {
    "path": "examples/open-modal/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport Modal from 'react-modal';\nimport {Provider} from 'react-redux';\nimport store from './store';\nimport App from './app';\n\n// Ensure screen readers don't see main content when modal is open\nModal.setAppElement('#root');\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/open-modal/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer, {enhanceReduxMiddleware} from '@kepler.gl/reducers';\n\nimport appReducer from './app-reducer';\nimport Window from 'global/window';\n\nconst reducers = combineReducers({\n  keplerGl: keplerGlReducer,\n  app: appReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nconst initialState = {};\n\n// add redux devtools\nconst composeEnhancers = Window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\nexport default createStore(reducers, initialState, composeEnhancers(...enhancers));\n"
  },
  {
    "path": "examples/replace-component/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    \"@babel/plugin-transform-export-namespace-from\"\n  ]\n}\n"
  },
  {
    "path": "examples/replace-component/README.md",
    "content": "# Replacing components\n\nExample showing how to replace kepler.gl default components using `injectComponents` method.\n\n#### 1. Install\n\n```sh\nyarn install\n```\n\n#### 2. Mapbox Token\n\nadd mapbox access token to node env\n\n```sh\nexport MapboxAccessToken=<your_mapbox_token>\n```\n\n#### 3. Start the app\n\n```sh\nyarn start\n```\n"
  },
  {
    "path": "examples/replace-component/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport {replace} from 'esbuild-plugin-replace';\nimport {dotenvRun} from '@dotenv-run/esbuild';\nimport copyPlugin from 'esbuild-plugin-copy';\n\nimport process from 'node:process';\nimport fs from 'node:fs';\nimport {spawn} from 'node:child_process';\nimport {join} from 'node:path';\nimport KeplerPackage from '../../package.json' assert {type: 'json'};\n\nconst args = process.argv;\nconst LIB_DIR = '../../';\nconst NODE_MODULES_DIR = join(LIB_DIR, 'node_modules');\nconst SRC_DIR = join(LIB_DIR, 'src');\n\n// For debugging deck.gl, load deck.gl from external deck.gl directory\nconst EXTERNAL_DECK_SRC = join(LIB_DIR, 'deck.gl');\n\n// For debugging loaders.gl, load loaders.gl from external loaders.gl directory\nconst EXTERNAL_LOADERS_SRC = join(LIB_DIR, 'loaders.gl');\n\n// For debugging hubble.gl, load hubble.gl from external hubble.gl directory\nconst EXTERNAL_HUBBLE_SRC = join(LIB_DIR, '../../hubble.gl');\n\nconst port = 8080;\nconst NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'production');\n\n// add alias to serve from kepler src, resolve libraries so there is only one copy of them\nconst RESOLVE_LOCAL_ALIASES = {\n  react: `${NODE_MODULES_DIR}/react`,\n  'react-dom': `${NODE_MODULES_DIR}/react-dom`,\n  'react-redux': `${NODE_MODULES_DIR}/react-redux/lib`,\n  'styled-components': `${NODE_MODULES_DIR}/styled-components`,\n  'react-intl': `${NODE_MODULES_DIR}/react-intl`,\n  'react-palm': `${NODE_MODULES_DIR}/react-palm`,\n  // Suppress useless warnings from react-date-picker's dep\n  'tiny-warning': `${SRC_DIR}/utils/src/noop.ts`,\n  // kepler.gl and loaders.gl need to use same apache-arrow\n  'apache-arrow': `${NODE_MODULES_DIR}/apache-arrow`\n};\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: ['src/main.js'],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV,\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),\n    'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),\n    'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),\n    'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),\n    'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),\n    'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),\n    'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),\n    'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || ''),\n    'process.env.NODE_DEBUG': JSON.stringify(false)\n  },\n  plugins: [\n    dotenvRun({\n      verbose: true,\n      environment: NODE_ENV,\n      root: '../../.env'\n    }),\n    // automatically injected kepler.gl package version into the bundle\n    replace({\n      __PACKAGE_VERSION__: KeplerPackage.version,\n      include: /constants\\/src\\/default-settings\\.ts/\n    }),\n    copyPlugin({\n      resolveFrom: 'cwd',\n      assets: {\n        from: ['index.html'],\n        to: ['dist/index.html']\n      }\n    })\n  ]\n};\n\nfunction addAliases(externals, args) {\n  const resolveAlias = RESOLVE_LOCAL_ALIASES;\n\n  // Combine flags\n  const useLocalDeck = args.includes('--env.deck') || args.includes('--env.hubble_src');\n  const useRepoDeck = args.includes('--env.deck_src');\n\n  // resolve deck.gl from local dir\n  if (useLocalDeck || useRepoDeck) {\n    // Load deck.gl from root node_modules\n    // if env.deck_src Load deck.gl from deck.gl/modules/main/src folder parallel to kepler.gl\n    resolveAlias['deck.gl'] = useLocalDeck\n      ? `${NODE_MODULES_DIR}/deck.gl/src`\n      : `${EXTERNAL_DECK_SRC}/modules/main/src`;\n\n    // if env.deck Load @deck.gl modules from root node_modules/@deck.gl\n    // if env.deck_src Load @deck.gl modules from  deck.gl/modules folder parallel to kepler.gl\n    externals['deck.gl'].forEach(mdl => {\n      resolveAlias[`@deck.gl/${mdl}`] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/@deck.gl/${mdl}/src`\n        : `${EXTERNAL_DECK_SRC}/modules/${mdl}/src`;\n      // types are stored in different directory\n      resolveAlias[`@deck.gl/${mdl}/typed`] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/@deck.gl/${mdl}/typed`\n        : `${EXTERNAL_DECK_SRC}/modules/${mdl}/src/types`;\n    });\n\n    ['luma.gl', 'probe.gl', 'loaders.gl'].forEach(name => {\n      // if env.deck Load ${name} from root node_modules\n      // if env.deck_src Load ${name} from deck.gl/node_modules folder parallel to kepler.gl\n      resolveAlias[name] = useLocalDeck\n        ? `${NODE_MODULES_DIR}/${name}/src`\n        : name === 'probe.gl'\n        ? `${EXTERNAL_DECK_SRC}/node_modules/${name}/src`\n        : `${EXTERNAL_DECK_SRC}/node_modules/@${name}/core/src`;\n\n      // if env.deck Load @${name} modules from root node_modules/@${name}\n      // if env.deck_src Load @${name} modules from deck.gl/node_modules/@${name} folder parallel to kepler.gl`\n      externals[name].forEach(mdl => {\n        resolveAlias[`@${name}/${mdl}`] = useLocalDeck\n          ? `${NODE_MODULES_DIR}/@${name}/${mdl}/src`\n          : `${EXTERNAL_DECK_SRC}/node_modules/@${name}/${mdl}/src`;\n      });\n    });\n  }\n\n  if (args.includes('--env.loaders_src')) {\n    externals['loaders.gl'].forEach(mdl => {\n      resolveAlias[`@loaders.gl/${mdl}`] = `${EXTERNAL_LOADERS_SRC}/modules/${mdl}/src`;\n    });\n  }\n\n  if (args.includes('--env.hubble_src')) {\n    externals['hubble.gl'].forEach(mdl => {\n      resolveAlias[`@hubble.gl/${mdl}`] = `${EXTERNAL_HUBBLE_SRC}/modules/${mdl}/src`;\n    });\n  }\n\n  return resolveAlias;\n}\n\nfunction openURL(url) {\n  // Could potentially be replaced by https://www.npmjs.com/package/open, it was throwing an error when tried last\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\n(async () => {\n  // local dev\n\n  const modules = ['@deck.gl', '@loaders.gl', '@luma.gl', '@probe.gl', '@hubble.gl'];\n  const loadAllDirs = modules.map(\n    dir =>\n      new Promise(success => {\n        fs.readdir(join(NODE_MODULES_DIR, dir), (err, items) => {\n          if (err) {\n            const colorRed = '\\x1b[31m';\n            const colorReset = '\\x1b[0m';\n            console.log(\n              `${colorRed}%s${colorReset}`,\n              `Cannot find ${dir} in node_modules, make sure it is installed. ${err}`\n            );\n\n            success(null);\n          }\n          success(items);\n        });\n      })\n  );\n\n  const externals = await Promise.all(loadAllDirs).then(results => ({\n    'deck.gl': results[0],\n    'loaders.gl': results[1],\n    'luma.gl': results[2],\n    'probe.gl': results[3],\n    'hubble.gl': results[4]\n  }));\n\n  const localAliases = addAliases(externals, args);\n\n  if (args.includes('--build')) {\n    await esbuild\n      .build({\n        ...config,\n        minify: true,\n        sourcemap: false,\n        define: {\n          ...config.define,\n          'process.env.NODE_ENV': '\"production\"'\n        },\n        drop: ['console', 'debugger'],\n        treeShaking: true\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        minify: false,\n        sourcemap: true,\n        // add alias to resolve libraries so there is only one copy of them\n        // always alias to avoid duplicate Reacts\n        alias: localAliases,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({remoteAddress, method, path, status, timeInMS}) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(\n          `kepler.gl demo app running at ${`http://localhost:${port}`}, press Ctrl+C to stop`\n        );\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "examples/replace-component/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>kepler.gl demo</title>\n    <link\n      rel=\"stylesheet\"\n      href=\"//d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\"\n    />\n    <link href=\"//api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\" />\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\" />\n    <style type=\"text/css\">\n      body {\n        margin: 0;\n        padding: 0;\n        overflow: hidden;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script src=\"bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/replace-component/package.json",
    "content": "{\n  \"scripts\": {\n    \"start\": \"node esbuild.config.mjs --start\",\n    \"build\": \"node esbuild.config.mjs --build\",\n    \"start:local\": \"NODE_ENV=local node esbuild.config.mjs --start\"\n  },\n  \"dependencies\": {\n    \"@kepler.gl/actions\": \"^3.1.9\",\n    \"@kepler.gl/components\": \"^3.1.9\",\n    \"@kepler.gl/reducers\": \"^3.1.9\",\n    \"@kepler.gl/schemas\": \"^3.1.9\",\n    \"@kepler.gl/styles\": \"^3.1.9\",\n    \"esbuild\": \"^0.25.0\",\n    \"global\": \"^4.3.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.3.6\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-virtualized\": \"^9.21.0\",\n    \"redux-actions\": \"^2.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@dotenv-run/esbuild\": \"^1.5.0\",\n    \"esbuild-plugin-copy\": \"^2.1.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "examples/replace-component/src/actions.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from 'redux-actions';\n\nexport const setMapConfig = createAction('SET_MAP_CONFIG', payload => payload);\n"
  },
  {
    "path": "examples/replace-component/src/app-reducer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction, handleActions} from 'redux-actions';\nimport KeplerGlSchema from '@kepler.gl/schemas';\n\n// CONSTANTS\nexport const INIT = 'INIT';\nexport const SET_MAP_CONFIG = 'SET_MAP_CONFIG';\n\n// ACTIONS\nexport const appInit = createAction(INIT);\nexport const setMapConfig = createAction(SET_MAP_CONFIG);\n\n// INITIAL_STATE\nconst initialState = {\n  appName: 'example',\n  loaded: false\n};\n\n// REDUCER\nconst appReducer = handleActions(\n  {\n    [INIT]: state => ({\n      ...state,\n      loaded: true\n    }),\n    [SET_MAP_CONFIG]: (state, action) => ({\n      ...state,\n      mapConfig: KeplerGlSchema.getConfigToSave(action.payload)\n    })\n  },\n  initialState\n);\n\nexport default appReducer;\n"
  },
  {
    "path": "examples/replace-component/src/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {connect} from 'react-redux';\nimport {addDataToMap, wrapTo} from '@kepler.gl/actions';\nimport AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';\nimport styled from 'styled-components';\nimport {theme} from '@kepler.gl/styles';\n\nimport sampleData, {config} from './data/sample-data';\n\nconst MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line\n\nimport {\n  SidebarFactory,\n  PanelHeaderFactory,\n  PanelToggleFactory,\n  CustomPanelsFactory,\n  MapPopoverFactory,\n  injectComponents\n} from '@kepler.gl/components';\n\nimport CustomPanelHeaderFactory from './components/panel-header';\nimport CustomSidebarFactory from './components/side-bar';\nimport CustomPanelToggleFactory from './components/panel-toggle';\nimport CustomSidePanelFactory from './components/custom-panel';\nimport CustomMapPopoverFactory from './components/custom-map-popover';\n\nconst StyledMapConfigDisplay = styled.div`\n  position: absolute;\n  z-index: 100;\n  bottom: 10px;\n  right: 10px;\n  background-color: ${theme.sidePanelBg};\n  font-size: 11px;\n  width: 300px;\n  color: ${theme.textColor};\n  word-wrap: break-word;\n  min-height: 60px;\n  padding: 10px;\n`;\n\n// Inject custom components\nconst KeplerGl = injectComponents([\n  [SidebarFactory, CustomSidebarFactory],\n  [PanelHeaderFactory, CustomPanelHeaderFactory],\n  [PanelToggleFactory, CustomPanelToggleFactory],\n  [CustomPanelsFactory, CustomSidePanelFactory],\n  [MapPopoverFactory, CustomMapPopoverFactory]\n]);\n\nclass App extends Component {\n  componentDidMount() {\n    this.props.dispatch(wrapTo('map1', addDataToMap({datasets: sampleData, config})));\n  }\n\n  render() {\n    return (\n      <div style={{position: 'absolute', width: '100%', height: '100%'}}>\n        <AutoSizer>\n          {({height, width}) => (\n            <KeplerGl mapboxApiAccessToken={MAPBOX_TOKEN} id=\"map1\" width={width} height={height} />\n          )}\n        </AutoSizer>\n        <StyledMapConfigDisplay>\n          {this.props.app.mapConfig\n            ? JSON.stringify(this.props.app.mapConfig)\n            : 'Click Save Config to Display Config Here'}\n        </StyledMapConfigDisplay>\n      </div>\n    );\n  }\n}\n\nconst mapStateToProps = state => state;\nconst dispatchToProps = dispatch => ({dispatch});\n\nexport default connect(mapStateToProps, dispatchToProps)(App);\n"
  },
  {
    "path": "examples/replace-component/src/components/custom-map-popover.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {MapPopoverFactory} from '@kepler.gl/components';\n\nconst CustomMapPopoverFactory = (...deps) => {\n  const MapPopover = MapPopoverFactory(...deps);\n  const MapPopoverWrapper = props => {\n    // Disable tooltip for point layer\n    if (props.layerHoverProp?.layer?.id === 'point_layer') {\n      return null;\n    }\n\n    return <MapPopover {...props} />;\n  };\n\n  return MapPopoverWrapper;\n};\nCustomMapPopoverFactory.deps = MapPopoverFactory.deps;\nexport default CustomMapPopoverFactory;\n"
  },
  {
    "path": "examples/replace-component/src/components/custom-panel.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Icons} from '@kepler.gl/components';\n\nfunction CustomSidePanelsFactory() {\n  const defaultPanels = [\n    {\n      id: 'rocket',\n      label: 'Rocket',\n      iconComponent: Icons.Globe\n    },\n    {\n      id: 'chart',\n      label: 'Chart',\n      iconComponent: Icons.LineChart\n    }\n  ];\n  const defaultGetProps = props => ({\n    layers: props.layers\n  });\n  const CustomPanels = props => {\n    if (props.activeSidePanel === 'rocket') {\n      return <div className=\"rocket-panel\">Rocket</div>;\n    } else if (props.activeSidePanel === 'chart') {\n      return <div className=\"rocket-panel\">Charts?</div>;\n    }\n\n    return null;\n  };\n\n  CustomPanels.panels = defaultPanels;\n  CustomPanels.getProps = defaultGetProps;\n\n  return CustomPanels;\n}\n\nexport default CustomSidePanelsFactory;\n"
  },
  {
    "path": "examples/replace-component/src/components/panel-header.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {PanelHeaderFactory} from '@kepler.gl/components';\n\n// Custom Panel Header renders default panel header, changing its default props\n// to avoid rendering any action items on the top right\nexport function CustomPanelHeaderFactory() {\n  const PanelHeader = PanelHeaderFactory();\n\n  PanelHeader.defaultProps = {\n    ...PanelHeader.defaultProps,\n    actionItems: []\n  };\n  return PanelHeader;\n}\n\nexport default CustomPanelHeaderFactory;\n"
  },
  {
    "path": "examples/replace-component/src/components/panel-toggle.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {PanelToggleFactory, Button, Icons, withState} from '@kepler.gl/components';\nimport {visStateLens} from '@kepler.gl/reducers';\n\nimport {setMapConfig} from '../app-reducer';\n\nconst StyledPanelToggleWrapper = styled.div`\n  display: flex;\n  justify-content: space-between;\n  padding-right: 16px;\n  background-color: ${props => props.theme.sidePanelHeaderBg};\n`;\n\nconst ButtonWrapper = styled.div`\n  margin-bottom: 4px;\n`;\n\nconst CustomPanelToggleFactory = (...deps) => {\n  const PanelToggle = PanelToggleFactory(...deps);\n  const PanelToggleWrapper = props => (\n    <StyledPanelToggleWrapper>\n      <PanelToggle {...props} />\n      <ButtonWrapper>\n        <Button onClick={() => props.onClickSaveConfig(props.mapState)} width=\"120px\">\n          <Icons.Files height=\"12px\" />\n          Save Config\n        </Button>\n      </ButtonWrapper>\n    </StyledPanelToggleWrapper>\n  );\n\n  return withState(\n    // lenses\n    [visStateLens],\n    // mapStateToProps\n    state => ({mapState: state.keplerGl.map1}),\n    {\n      onClickSaveConfig: setMapConfig\n    }\n  )(PanelToggleWrapper);\n};\nCustomPanelToggleFactory.deps = PanelToggleFactory.deps;\nexport default CustomPanelToggleFactory;\n"
  },
  {
    "path": "examples/replace-component/src/components/side-bar.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {SidebarFactory, Icons} from '@kepler.gl/components';\nimport styled from 'styled-components';\n\nconst StyledSideBarContainer = styled.div`\n  .side-panel--container {\n    transform: scale(0.85);\n    transform-origin: top left;\n    height: 117.64%;\n    padding-top: 0;\n    padding-right: 0;\n    padding-bottom: 0;\n    padding-left: 0;\n  }\n`;\n\nconst StyledCloseButton = styled.div`\n  align-items: center;\n  justify-content: center;\n  background-color: ${props => props.theme.primaryBtnBgd};\n  color: ${props => props.theme.primaryBtnColor};\n  display: flex;\n  height: 46px;\n  position: absolute;\n  top: 0;\n  width: 80px;\n  right: 0;\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.primaryBtnBgdHover};\n  }\n`;\n\nconst CloseButtonFactory = () => {\n  const CloseButton = ({onClick, isOpen}) => (\n    <StyledCloseButton className=\"side-bar__close\" onClick={onClick}>\n      <Icons.ArrowRight\n        height=\"18px\"\n        style={{transform: `rotate(${isOpen ? 180 : 0}deg)`, marginLeft: isOpen ? 0 : '30px'}}\n      />\n    </StyledCloseButton>\n  );\n  return CloseButton;\n};\n\n// Custom sidebar will render kepler.gl default side bar\n// adding a wrapper component to edit its style\nfunction CustomSidebarFactory(CloseButton) {\n  const SideBar = SidebarFactory(CloseButton);\n  const CustomSidebar = props => (\n    <StyledSideBarContainer>\n      <SideBar {...props} />\n    </StyledSideBarContainer>\n  );\n  return CustomSidebar;\n}\n\n// You can add custom dependencies to your custom factory\nCustomSidebarFactory.deps = [CloseButtonFactory];\n\nexport default CustomSidebarFactory;\n"
  },
  {
    "path": "examples/replace-component/src/data/sample-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      layers: [\n        {\n          type: 'point',\n          id: 'point_layer',\n          config: {\n            dataId: 'tree_data',\n            label: 'Trees',\n            color: [69, 138, 70],\n            columns: {lat: 'Location_latitude', lng: 'Location_longitude', altitude: null},\n            isVisible: true,\n            highlightColor: [255, 0, 0, 255]\n          },\n          visualChannels: {\n            sizeField: {name: 'Plan', type: 'integer'}\n          }\n        },\n        {\n          id: 'heatmap_layer',\n          type: 'heatmap',\n          config: {\n            dataId: 'tree_data',\n            label: 'Heatmap',\n            columns: {lat: 'Location_latitude', lng: 'Location_longitude'},\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              radius: 46.4\n            }\n          },\n          visualChannels: {weightField: null, weightScale: 'linear'}\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {tree_data: []},\n          compareMode: false,\n          compareType: 'absolute',\n          enabled: true\n        }\n      }\n    },\n    mapState: {\n      bearing: 0,\n      dragRotate: false,\n      latitude: 37.759775559999994,\n      longitude: -122.4423862,\n      pitch: 0,\n      zoom: 12,\n      isSplit: false\n    }\n  }\n};\n\nexport default {\n  info: {\n    label: 'San Francisco Trees',\n    id: 'tree_data'\n  },\n  data: {\n    fields: [\n      {\n        name: 'TreeID'\n      },\n      {\n        name: 'Species'\n      },\n      {\n        name: 'Address'\n      },\n      {\n        name: 'Has_Species'\n      },\n      {\n        name: 'SiteInfo'\n      },\n      {\n        name: 'PlantType'\n      },\n      {\n        name: 'PlantDate'\n      },\n      {\n        name: 'Plan'\n      },\n      {\n        name: 'Age'\n      },\n      {\n        name: 'DBH'\n      },\n      {\n        name: 'Location_latitude'\n      },\n      {\n        name: 'Location_longitude'\n      }\n    ],\n    rows: [\n      [\n        141565,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/21/88 0:00',\n        1988,\n        29,\n        21,\n        37.77596769,\n        -122.4413967\n      ],\n      [\n        232565,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '940 Elizabeth St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.75171022,\n        -122.441498\n      ],\n      [\n        119263,\n        'Pinus radiata :: Monterey Pine',\n        '495X Lakeshore Dr',\n        false,\n        'Median : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        207368,\n        'Ligustrum japonicum :: Japanese Privet',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76021031,\n        -122.4707394\n      ],\n      [\n        188702,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1501 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        17,\n        37.74220867,\n        -122.3872932\n      ],\n      [\n        141566,\n        'Myoporum laetum :: Myoporum',\n        '501X Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.77570453,\n        -122.4414629\n      ],\n      [\n        188697,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1301 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.73973386,\n        -122.3829339\n      ],\n      [\n        122650,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        203507,\n        'Agonis flexuosa :: Peppermint Willow',\n        '920 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        23,\n        37.76018051,\n        -122.4708661\n      ],\n      [\n        122670,\n        'Lyonothamnus floribundus subsp. asplenifolius :: Santa Cruz Ironwood',\n        '301x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122658,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122660,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        122648,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        196590,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.77303718,\n        -122.4172458\n      ],\n      [\n        196591,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77305251,\n        -122.4172809\n      ],\n      [\n        122649,\n        'Acer rubrum :: Red Maple',\n        '300x Hickory St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/10/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        145278,\n        'Fraxinus uhdei :: Shamel Ash: Evergreen Ash',\n        '730 11th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        0,\n        37.77448101,\n        -122.4691091\n      ],\n      [\n        96369,\n        'Lophostemon confertus :: Brisbane Box',\n        '168 Cervantes Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/9/11 0:00',\n        2011,\n        6,\n        24,\n        37.80468521,\n        -122.4402315\n      ],\n      [\n        182260,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '1471 28th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.75995923,\n        -122.4866885\n      ],\n      [\n        251179,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        251178,\n        'Podocarpus gracilor :: Fern Pine',\n        '1043 Minna St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77353367,\n        -122.4169045\n      ],\n      [\n        203506,\n        'Agonis flexuosa :: Peppermint Willow',\n        '910 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.76026555,\n        -122.4705695\n      ],\n      [\n        52566,\n        'Hakea suaveolens :: Sweet Hakea Tree',\n        '1555 40th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '1/12/16 0:00',\n        2016,\n        1,\n        2,\n        37.75778273,\n        -122.4993677\n      ],\n      [\n        22346,\n        'Lophostemon confertus :: Brisbane Box',\n        '5280 03rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/27/14 0:00',\n        2014,\n        3,\n        3,\n        37.72954822,\n        -122.3926894\n      ],\n      [\n        23567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2975 Van Ness Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/27/92 0:00',\n        1992,\n        25,\n        3,\n        37.80304673,\n        -122.4250636\n      ],\n      [\n        237469,\n        'Prunus cerasifera :: Cherry Plum',\n        '4200 23rd St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/00 0:00',\n        2000,\n        17,\n        0,\n        37.7528239,\n        -122.4366208\n      ],\n      [\n        244214,\n        'Tree(s) ::',\n        '342 Scott St',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/20/17 0:00',\n        2017,\n        0,\n        3,\n        37.77300994,\n        -122.4355982\n      ],\n      [\n        21823,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2741 Taylor St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/19/06 0:00',\n        2006,\n        11,\n        3,\n        37.80797069,\n        -122.4158502\n      ],\n      [\n        188686,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1300 Evans Ave',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73901484,\n        -122.381104\n      ],\n      [\n        196592,\n        'Pyrus calleryana :: Ornamental Pear',\n        '43 Lafayette St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.77310137,\n        -122.4173585\n      ],\n      [\n        22614,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1001 Turk St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        37.78136501,\n        -122.4248636\n      ],\n      [\n        10933,\n        'Lophostemon confertus :: Brisbane Box',\n        '1359 Hudson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.73743182,\n        -122.3840631\n      ],\n      [\n        2889,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1545 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.79059396,\n        -122.4198251\n      ],\n      [\n        240126,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '20 Lily St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.77473647,\n        -122.4213262\n      ],\n      [\n        183515,\n        'Myoporum laetum :: Myoporum',\n        '2562 26th Ave',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.73970633,\n        -122.4828998\n      ],\n      [\n        13434,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2X Market St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.79448363,\n        -122.3950546\n      ],\n      [\n        226924,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.74625595,\n        -122.4338758\n      ],\n      [\n        192589,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        3558,\n        'Lophostemon confertus :: Brisbane Box',\n        '101 Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 17:52',\n        2017,\n        0,\n        6,\n        37.74050235,\n        -122.3776366\n      ],\n      [\n        222540,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74632546,\n        -122.4338277\n      ],\n      [\n        192590,\n        'Lophostemon confertus :: Brisbane Box',\n        '1390 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.7755405,\n        -122.4157935\n      ],\n      [\n        28375,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '3014 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/9/03 0:00',\n        2003,\n        14,\n        3,\n        37.78875791,\n        -122.4429785\n      ],\n      [\n        222539,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74630146,\n        -122.4338571\n      ],\n      [\n        226925,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.74628109,\n        -122.4338516\n      ],\n      [\n        27522,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '3201 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        8,\n        37.78709954,\n        -122.4473685\n      ],\n      [\n        18228,\n        'Maytenus boaria :: Mayten',\n        '1737-1795 Post St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/04 0:00',\n        2004,\n        13,\n        12,\n        37.78534132,\n        -122.431005\n      ],\n      [\n        17325,\n        'Fraxinus americana :: American Ash',\n        '150 Otis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/31/16 0:00',\n        2016,\n        1,\n        3,\n        37.77095437,\n        -122.4203558\n      ],\n      [\n        3528,\n        'Lophostemon confertus :: Brisbane Box',\n        '201X Cargo Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 16:23',\n        2017,\n        0,\n        3,\n        37.74144532,\n        -122.3793046\n      ],\n      [\n        251186,\n        'Juniperus chinensis :: Juniper',\n        '3042 Steiner St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.79753285,\n        -122.4371106\n      ],\n      [\n        138938,\n        'Tree(s) ::',\n        '632 HAYES ST',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.77646121,\n        -122.4269126\n      ],\n      [\n        60867,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/83 0:00',\n        1983,\n        34,\n        null,\n        null,\n        null\n      ],\n      [\n        233897,\n        'Araucaria heterophylla :: Norfolk Island Pine',\n        '1021 Noe St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        9,\n        37.75266993,\n        -122.4318862\n      ],\n      [\n        251187,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        121137,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        2909,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '1700 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        30,\n        37.79038763,\n        -122.4229834\n      ],\n      [\n        27602,\n        'Pittosporum undulatum :: Victorian Box',\n        '988 Fulton St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        20,\n        37.77766599,\n        -122.4324106\n      ],\n      [\n        44642,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/29/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        4749,\n        'Acacia melanoxylon :: Blackwood Acacia',\n        '1255 Columbus Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '8/3/15 0:00',\n        2015,\n        2,\n        8,\n        37.80577303,\n        -122.4182612\n      ],\n      [\n        44994,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/25/01 0:00',\n        2001,\n        16,\n        null,\n        null,\n        null\n      ],\n      [\n        121139,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        226596,\n        'Leptospermum scoparium :: New Zealand Tea Tree',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.74632473,\n        -122.4337918\n      ],\n      [\n        239043,\n        'Pyrus calleryana :: Ornamental Pear',\n        '10 12th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.77381038,\n        -122.4194082\n      ],\n      [\n        222541,\n        'Callistemon citrinus :: Lemon Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.7463451,\n        -122.433801\n      ],\n      [\n        138748,\n        'Lophostemon confertus :: Brisbane Box',\n        '1614 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        0,\n        37.7853077,\n        -122.4415226\n      ],\n      [\n        226926,\n        'Juniperus chinensis :: Juniper',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Property side : Pot',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.74630146,\n        -122.4338212\n      ],\n      [\n        222538,\n        'Callistemon viminalis :: Weeping Bottlebrush',\n        '1 Newburg St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.74627164,\n        -122.4338847\n      ],\n      [\n        166623,\n        'Potential Site :: Potential Site',\n        '520 Jersey St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/21/17 0:00',\n        2017,\n        0,\n        3,\n        37.75036438,\n        -122.4371358\n      ],\n      [\n        251188,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '1 Baker St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77112958,\n        -122.4406092\n      ],\n      [\n        19206,\n        'Laurus nobilis :: Sweet Bay: Grecian Laurel',\n        '630 Sansome St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79625001,\n        -122.4016986\n      ],\n      [\n        121138,\n        'Pittosporum undulatum :: Victorian Box',\n        '1388x Minna St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '9/8/16 0:00',\n        2016,\n        1,\n        3,\n        null,\n        null\n      ],\n      [\n        251206,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '270 Trumbull St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73083203,\n        -122.4245292\n      ],\n      [\n        25725,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '729 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.77705272,\n        -122.4286972\n      ],\n      [\n        251249,\n        'Private shrub :: Private Shrub',\n        '161 Lundys Ln',\n        false,\n        'Front Yard : Yard',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.7429691,\n        -122.4194092\n      ],\n      [\n        251293,\n        '::',\n        '3789 Market St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.75373446,\n        -122.442203\n      ],\n      [\n        221749,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        24,\n        37.76366261,\n        -122.4520649\n      ],\n      [\n        221750,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        11,\n        37.76366331,\n        -122.4521462\n      ],\n      [\n        245474,\n        'Pittosporum undulatum :: Victorian Box',\n        '275 Grattan St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        19,\n        37.76363745,\n        -122.4522558\n      ],\n      [\n        251419,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        251418,\n        'Melaleuca quinquenervia :: Cajeput',\n        '39 Jones St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '4/14/15 0:00',\n        2015,\n        2,\n        null,\n        37.78164643,\n        -122.4122686\n      ],\n      [\n        16698,\n        'Pittosporum undulatum :: Victorian Box',\n        '950X Noriega St',\n        false,\n        'Median : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        12,\n        37.75434951,\n        -122.4740143\n      ],\n      [\n        251532,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251531,\n        '::',\n        '2100X 20th St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '5/15/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        251642,\n        'Shrub :: Shrub',\n        '2253 Broderick St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        null,\n        37.79122797,\n        -122.4428704\n      ],\n      [\n        251700,\n        '::',\n        '1560 Sanchez St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '6/5/17 0:00',\n        2017,\n        0,\n        3,\n        37.74408823,\n        -122.4290963\n      ],\n      [\n        253008,\n        '::',\n        '1201 Tennessee St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '7/10/17 0:00',\n        2017,\n        0,\n        3,\n        37.7563302,\n        -122.3890342\n      ],\n      [\n        208471,\n        'Cordyline australis :: Dracena Palm',\n        '1175 Kirkham St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/23/17 0:00',\n        2017,\n        0,\n        3,\n        37.75989512,\n        -122.4735278\n      ],\n      [\n        253411,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253409,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253410,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        253412,\n        'Lophostemon confertus :: Brisbane Box',\n        '1321 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '8/30/17 0:00',\n        2017,\n        0,\n        3,\n        37.77599135,\n        -122.4149312\n      ],\n      [\n        225282,\n        'Pittosporum crassifolium :: Karo Tree',\n        '101 Rivoli St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        null,\n        null,\n        null,\n        6,\n        37.76244046,\n        -122.4496446\n      ],\n      [\n        173852,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173849,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173850,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173851,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173848,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173846,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173847,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        173853,\n        '::',\n        '450 Toland St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '1/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.74445161,\n        -122.3985096\n      ],\n      [\n        107717,\n        'Tree(s) ::',\n        '2400X 25th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Landscaping',\n        '10/16/14 0:00',\n        2014,\n        3,\n        3,\n        37.74238326,\n        -122.4917845\n      ],\n      [\n        110194,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '2123 Pierce St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78980712,\n        -122.4374813\n      ],\n      [\n        59839,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1201 Palou Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '10/10/81 0:00',\n        1981,\n        36,\n        3,\n        37.72976741,\n        -122.3836854\n      ],\n      [\n        183689,\n        'Rhamnus alaternus :: Italian Buckthorn',\n        '1341 42nd Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.76162779,\n        -122.5017998\n      ],\n      [\n        18916,\n        'Olea europaea :: Olive Tree',\n        '2621 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        14,\n        37.78950262,\n        -122.4361188\n      ],\n      [\n        20281,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '20X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        null,\n        null\n      ],\n      [\n        119758,\n        'Maytenus boaria :: Mayten',\n        '151X Octavia St Frontage West',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        null,\n        null\n      ],\n      [\n        228560,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76151469,\n        -122.4216727\n      ],\n      [\n        18052,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2840 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78687837,\n        -122.4426898\n      ],\n      [\n        212622,\n        'Prunus cerasifera :: Cherry Plum',\n        '115 Beverly St',\n        false,\n        'Sidewalk: Property side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71819234,\n        -122.4717606\n      ],\n      [\n        20398,\n        'Cupressus macrocarpa :: Monterey Cypress',\n        '262X Sunset Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        24,\n        null,\n        null\n      ],\n      [\n        66855,\n        'Maytenus boaria :: Mayten',\n        '2041 Hayes St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/15/91 0:00',\n        1991,\n        26,\n        8,\n        37.77336241,\n        -122.4501107\n      ],\n      [\n        237033,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        6,\n        37.77334931,\n        -122.4236445\n      ],\n      [\n        116641,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        218972,\n        'Fraxinus americana :: American Ash',\n        '742 Myra Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        10,\n        37.74058076,\n        -122.4513096\n      ],\n      [\n        47510,\n        \"Arbutus 'Marina' :: Hybrid Strawberry Tree\",\n        '54X Cayuga Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.73158339,\n        -122.4295272\n      ],\n      [\n        237030,\n        \"Prunus serrulata 'Kwanzan' :: Kwanzan Flowering Cherry\",\n        '100 Octavia St',\n        false,\n        ':',\n        'Tree',\n        null,\n        null,\n        null,\n        5,\n        37.77325086,\n        -122.4236179\n      ],\n      [\n        213016,\n        'Tree(s) ::',\n        '120 Ortega St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/27/17 0:00',\n        2017,\n        0,\n        3,\n        37.75295695,\n        -122.4649514\n      ],\n      [\n        228561,\n        'Lophostemon confertus :: Brisbane Box',\n        '700 Valencia St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        7,\n        37.76156532,\n        -122.4216758\n      ],\n      [\n        72565,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        '11/16/96 0:00',\n        1996,\n        21,\n        3,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        116640,\n        'Ginkgo biloba :: Maidenhair Tree',\n        '270 Brannan St',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        1,\n        37.78270018,\n        -122.3912181\n      ],\n      [\n        6337,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1916 Ellis St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.78175831,\n        -122.4377689\n      ],\n      [\n        832,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1396 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:18',\n        2017,\n        0,\n        3,\n        37.76054428,\n        -122.5069327\n      ],\n      [\n        251241,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2305 Golden Gate Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.77731326,\n        -122.448748\n      ],\n      [\n        18836,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2055 Sacramento St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.79068779,\n        -122.4267446\n      ],\n      [\n        11598,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4134 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:51',\n        2017,\n        0,\n        3,\n        37.76048345,\n        -122.5065435\n      ],\n      [\n        111561,\n        'Eriobotrya deflexa :: Bronze Loquat',\n        '1498x Kansas St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.75089751,\n        -122.402317\n      ],\n      [\n        97319,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1038 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:52',\n        2017,\n        0,\n        3,\n        37.74306751,\n        -122.4774242\n      ],\n      [\n        58021,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4026 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:07',\n        2017,\n        0,\n        3,\n        37.76257173,\n        -122.5012197\n      ],\n      [\n        175046,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        4,\n        37.71164354,\n        -122.4709833\n      ],\n      [\n        26071,\n        'Melaleuca quinquenervia :: Cajeput',\n        '247 Orizaba Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:41',\n        2017,\n        0,\n        3,\n        37.71342972,\n        -122.4626502\n      ],\n      [\n        11606,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4200x Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:54',\n        2017,\n        0,\n        3,\n        37.76045095,\n        -122.5071358\n      ],\n      [\n        36617,\n        'Robinia x ambigua :: Locust',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/20/98 0:00',\n        1998,\n        19,\n        null,\n        null,\n        null\n      ],\n      [\n        24532,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1395 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:21',\n        2017,\n        0,\n        3,\n        37.76072337,\n        -122.5070915\n      ],\n      [\n        5972,\n        'Pittosporum undulatum :: Victorian Box',\n        '1550 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '2/2/90 0:00',\n        1990,\n        27,\n        8,\n        37.78138444,\n        -122.4331105\n      ],\n      [\n        830,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '1384 47th Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:15',\n        2017,\n        0,\n        3,\n        37.76071635,\n        -122.5069447\n      ],\n      [\n        251225,\n        'Jacaranda mimosifolia :: Jacaranda',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75927435,\n        -122.4422441\n      ],\n      [\n        54712,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4508 Irving St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:58',\n        2017,\n        0,\n        3,\n        37.76234527,\n        -122.5063635\n      ],\n      [\n        3193,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '3698 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78640011,\n        -122.454267\n      ],\n      [\n        21682,\n        'Prunus serrulata :: Ornamental Cherry',\n        '1830 Sutter St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        37.78651577,\n        -122.4305403\n      ],\n      [\n        97322,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1055 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:42',\n        2017,\n        0,\n        3,\n        37.74287614,\n        -122.4775411\n      ],\n      [\n        175377,\n        'Ulmus pumila :: Siberian Elm',\n        '80X Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        2,\n        37.71516198,\n        -122.4717512\n      ],\n      [\n        142720,\n        '::',\n        '1170 COLUMBUS AVE',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.80483123,\n        -122.416565\n      ],\n      [\n        251231,\n        'Pinus radiata :: Monterey Pine',\n        '164 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.72047169,\n        -122.4780251\n      ],\n      [\n        29943,\n        \"Tristaniopsis laurina 'Elegant' :: Small-leaf Tristania 'Elegant'\",\n        '242 Broad St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:23',\n        2017,\n        0,\n        3,\n        37.71323398,\n        -122.4605594\n      ],\n      [\n        97323,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1011 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:25',\n        2017,\n        0,\n        3,\n        37.74289372,\n        -122.4772138\n      ],\n      [\n        11582,\n        'Lagunaria patersonii :: Primrose Tree',\n        '4000 Judah St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 11:49',\n        2017,\n        0,\n        3,\n        37.76055019,\n        -122.5050036\n      ],\n      [\n        251230,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        251232,\n        'Ligustrum lucidum :: Glossy Privet',\n        '99x Ogden Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73605476,\n        -122.4163909\n      ],\n      [\n        179480,\n        'Schinus terebinthifolius :: Brazilian Pepper',\n        '100 Serrano Dr',\n        false,\n        'Sidewalk: Curb side : Yard',\n        'Tree',\n        null,\n        null,\n        null,\n        11,\n        37.72024935,\n        -122.4780157\n      ],\n      [\n        251221,\n        'Tree(s) ::',\n        '2401X Washington St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 0:00',\n        2017,\n        0,\n        3,\n        null,\n        null\n      ],\n      [\n        48827,\n        'Lophostemon confertus :: Brisbane Box',\n        '435 Pacific Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '11/7/02 0:00',\n        2002,\n        15,\n        5,\n        37.79736024,\n        -122.4027685\n      ],\n      [\n        251242,\n        'Cupressocyparis leylandii :: Leyland Cypress',\n        '2449 Golden Gate Ave',\n        false,\n        'Sidewalk: Property side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7770353,\n        -122.4514366\n      ],\n      [\n        48332,\n        'Tree(s) ::',\n        null,\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/17/02 0:00',\n        2002,\n        15,\n        null,\n        null,\n        null\n      ],\n      [\n        175044,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '80x Junipero Serra Blvd',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.71158043,\n        -122.4709734\n      ],\n      [\n        251226,\n        'Koelreuteria paniculata :: Golden Rain Tree',\n        '1 Yukon St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.75950087,\n        -122.4421669\n      ],\n      [\n        17881,\n        'Olea europaea :: Olive Tree',\n        '2205 Pine St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78806671,\n        -122.4322373\n      ],\n      [\n        24515,\n        'Lophostemon confertus :: Brisbane Box',\n        '2030 Fell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/7/16 7:40',\n        2016,\n        1,\n        3,\n        37.77238102,\n        -122.4513341\n      ],\n      [\n        97317,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1000 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:59',\n        2017,\n        0,\n        3,\n        37.74308434,\n        -122.4769891\n      ],\n      [\n        2985,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '2253 California St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        65,\n        37.78912498,\n        -122.4314293\n      ],\n      [\n        182083,\n        'Lagunaria patersonii :: Primrose Tree',\n        '1101 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 12:33',\n        2017,\n        0,\n        3,\n        37.74285808,\n        -122.4780077\n      ],\n      [\n        18700,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '100X Richardson Ave',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        18,\n        37.79975916,\n        -122.4460044\n      ],\n      [\n        9914,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '1900 Gough St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        126,\n        37.79116413,\n        -122.4257789\n      ],\n      [\n        93463,\n        \"Prunus x 'Amanogawa' :: Flowering Cherry\",\n        '1739 Taraval St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '3/28/17 13:05',\n        2017,\n        0,\n        3,\n        37.74255391,\n        -122.4849289\n      ],\n      [\n        15429,\n        \"Ficus microcarpa nitida 'Green Gem' :: Indian Laurel Fig Tree 'Green Gem'\",\n        '2125 Mission St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '4/9/15 0:00',\n        2015,\n        2,\n        15,\n        37.76296062,\n        -122.419386\n      ],\n      [\n        138801,\n        'Maytenus boaria :: Mayten',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/9/97 0:00',\n        1997,\n        20,\n        2,\n        37.78770158,\n        -122.4350685\n      ],\n      [\n        251243,\n        \"Ceanothus 'Ray Hartman' :: California Lilac 'Ray Hartman'\",\n        '299x Chapman St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.7417247,\n        -122.411639\n      ],\n      [\n        138359,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78607841,\n        -122.4338643\n      ],\n      [\n        251248,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '829 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73477716,\n        -122.4360662\n      ],\n      [\n        138368,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78600955,\n        -122.4344937\n      ],\n      [\n        138807,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2355 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        13,\n        37.78777373,\n        -122.4346006\n      ],\n      [\n        57209,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '2898 Clay St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '12/15/14 0:00',\n        2014,\n        3,\n        3,\n        37.78998742,\n        -122.4404101\n      ],\n      [\n        210495,\n        'Myoporum laetum :: Myoporum',\n        '365 San Leandro Way',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        21,\n        37.73051136,\n        -122.4689316\n      ],\n      [\n        138369,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        16,\n        37.78599791,\n        -122.4345397\n      ],\n      [\n        6069,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '1930 Eddy St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '5/26/15 0:00',\n        2015,\n        2,\n        3,\n        37.78057618,\n        -122.439557\n      ],\n      [\n        138802,\n        'Ulmus parvifolia :: Chinese Elm',\n        '2375 PINE ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        12,\n        37.78771109,\n        -122.4350205\n      ],\n      [\n        138365,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        19,\n        37.78603526,\n        -122.4342542\n      ],\n      [\n        251246,\n        'Metrosideros excelsa :: New Zealand Xmas Tree',\n        '722X Mendell St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        null,\n        37.73887284,\n        -122.3866046\n      ],\n      [\n        251245,\n        'Corymbia ficifolia :: Red Flowering Gum',\n        '701 Chenery St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '6/4/92 0:00',\n        1992,\n        25,\n        null,\n        37.73445362,\n        -122.4338962\n      ],\n      [\n        103786,\n        'Magnolia grandiflora :: Southern Magnolia',\n        '1221 Polk St',\n        false,\n        'Sidewalk: Curb side : Pot',\n        'Tree',\n        '11/1/09 0:00',\n        2009,\n        8,\n        3,\n        37.78810145,\n        -122.4202677\n      ],\n      [\n        138362,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78605488,\n        -122.4340541\n      ],\n      [\n        134567,\n        'Platanus x hispanica :: Sycamore: London Plane',\n        '99 Grove St',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        '7/6/11 0:00',\n        2011,\n        6,\n        4,\n        37.77846618,\n        -122.4173997\n      ],\n      [\n        138367,\n        'Ficus retusa nitida :: Banyan Fig',\n        '2060 SUTTER ST',\n        false,\n        'Sidewalk: Curb side : Cutout',\n        'Tree',\n        null,\n        null,\n        null,\n        15,\n        37.78601298,\n        -122.4344509\n      ]\n    ]\n  }\n};\n"
  },
  {
    "path": "examples/replace-component/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport document from 'global/document';\nimport Modal from 'react-modal';\nimport {Provider} from 'react-redux';\nimport store from './store';\nimport App from './app';\n\nModal.setAppElement('#root');\n\nconst Root = () => (\n  <Provider store={store}>\n    <App />\n  </Provider>\n);\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\n\nroot.render(<Root />);\n"
  },
  {
    "path": "examples/replace-component/src/store.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createStore, combineReducers, applyMiddleware, compose} from 'redux';\nimport keplerGlReducer, {enhanceReduxMiddleware} from '@kepler.gl/reducers';\nimport appReducer from './app-reducer';\n\nconst customizedKeplerGlReducer = keplerGlReducer.initialState({\n  uiState: {\n    // hide side panel when mounted\n    activeSidePanel: null,\n    // hide all modals whtn mounted\n    currentModal: null\n  }\n});\n\nconst reducers = combineReducers({\n  keplerGl: customizedKeplerGlReducer,\n  app: appReducer\n});\n\nconst middlewares = enhanceReduxMiddleware([]);\nconst enhancers = [applyMiddleware(...middlewares)];\n\nexport default createStore(reducers, {}, compose(...enhancers));\n"
  },
  {
    "path": "examples/umd-client/README.md",
    "content": "# Umd client\n\nA single html file loading kepler.gl. This html is loading kepler.gl and its dependencies from the script tags in the header. You can embed this html in your Medium or other single page blog page.\n\n\n### Usage\nAdd your own Mapbox access token to line 48:\n```js\n const MAPBOX_TOKEN = 'PROVIDE_MAPBOX_TOKEN';\n```\n\n**Note**: You will need internet to load the map and kepler.gl scripts.\n\n"
  },
  {
    "path": "examples/umd-client/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Kepler.gl embedded map</title>\n\n    <!--Uber Font-->\n    <link\n      rel=\"stylesheet\"\n      href=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\"\n    />\n\n    <!--MapBox css-->\n    <link href=\"https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\" />\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\" />\n\n    <!-- Load React/Redux -->\n    <script src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\" crossorigin></script>\n    <script\n      src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\"\n      crossorigin\n    ></script>\n    <script src=\"https://unpkg.com/redux@4.2.1/dist/redux.js\" crossorigin></script>\n    <script src=\"https://unpkg.com/react-redux@8.1.2/dist/react-redux.min.js\" crossorigin></script>\n    <script\n      src=\"https://unpkg.com/styled-components@6.1.8/dist/styled-components.min.js\"\n      crossorigin\n    ></script>\n\n    <!-- Load Kepler.gl-->\n    <script src=\"https://unpkg.com/kepler.gl@3.1.10/umd/keplergl.min.js\"></script>\n    <!--If you want to load the build from filepath -->\n    <!--<script src=\"../../umd/keplergl.min.js\"></script>-->\n\n    <style type=\"text/css\">\n      body {\n        margin: 0;\n        padding: 0;\n        overflow: hidden;\n      }\n    </style>\n\n    <!--MapBox token-->\n    <script>\n      /**\n       * Provide your MapBox Token (Used in pre 3.x kepler.gl)\n       **/\n      const MAPBOX_TOKEN = 'PROVIDE_MAPBOX_TOKEN';\n      const WARNING_MESSAGE =\n        'Please Provide a Mapbox Token in order to use Kepler.gl. Edit this file and fill out MAPBOX_TOKEN with your access key';\n    </script>\n  </head>\n  <body>\n    <!-- We will put our React component inside this div. -->\n    <div id=\"app\">\n      <!-- Kepler.gl map will be placed here-->\n    </div>\n\n    <!-- Load our React component. -->\n    <script>\n      /* Validate Mapbox Token */\n      if ((MAPBOX_TOKEN || '') === '' || MAPBOX_TOKEN === 'PROVIDE_MAPBOX_TOKEN') {\n        alert(WARNING_MESSAGE);\n      }\n\n      /** STORE **/\n      const reducers = (function createReducers(redux, keplerGl) {\n        return redux.combineReducers({\n          // mount keplerGl reducer\n          keplerGl: keplerGl.keplerGlReducer\n        });\n      })(Redux, KeplerGl);\n\n      const middleWares = (function createMiddlewares(keplerGl) {\n        return keplerGl.enhanceReduxMiddleware([\n          // Add other middlewares here\n        ]);\n      })(KeplerGl);\n\n      const enhancers = (function craeteEnhancers(redux, middles) {\n        return redux.applyMiddleware(...middles);\n      })(Redux, middleWares);\n\n      const store = (function createStore(redux, enhancers) {\n        const initialState = {};\n\n        return redux.createStore(reducers, initialState, redux.compose(enhancers));\n      })(Redux, enhancers);\n      /** END STORE **/\n\n      /** COMPONENTS **/\n      const KeplerElement = (function (react, keplerGl, mapboxToken) {\n        return function (props) {\n          return react.createElement(\n            'div',\n            {style: {position: 'absolute', left: 0, width: '100vw', height: '100vh'}},\n            react.createElement(keplerGl.KeplerGl, {\n              mapboxApiAccessToken: mapboxToken,\n              id: 'map',\n              width: props.width || 1200,\n              height: props.height || 800\n            })\n          );\n        };\n      })(React, KeplerGl, MAPBOX_TOKEN);\n\n      const app = (function createReactReduxProvider(react, reactRedux, KeplerElement) {\n        return react.createElement(\n          reactRedux.Provider,\n          {store},\n          react.createElement(KeplerElement, null)\n        );\n      })(React, ReactRedux, KeplerElement);\n      /** END COMPONENTS **/\n\n      /** Render **/\n      (function render(react, reactDOM, app) {\n        const container = document.getElementById('app');\n        const root = reactDOM.createRoot(container);\n        root.render(app);\n      })(React, ReactDOM, app);\n    </script>\n\n    <!-- The next script will show how to interact directly with Kepler map store -->\n    <script>\n      /**\n       * Customize map.\n       * Interact with map store to customize data and behavior\n       */\n      (function customize(keplerGl, store) {\n        // store.dispatch(keplerGl.toggleSplitMap());\n      })(KeplerGl, store);\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "jest.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/** @type {import('jest').Config} */\nconst config = {\n  collectCoverageFrom: ['<rootDir>/src/**/*.{js|ts|tsx}', '!<rootDir>/src/**/*.spec.js'],\n  coverageDirectory: './jest-coverage',\n  testEnvironment: 'jsdom',\n  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],\n  verbose: true,\n  testPathIgnorePatterns: [\n    // ignore all dist computed directories\n    '<rootDir>/.*(/|\\\\\\\\)dist(/|\\\\\\\\).*'\n  ],\n  testMatch: [\n    '<rootDir>/src/**/*.spec.(ts|tsx)',\n    '<rootDir>/src/**/*.spec.js',\n    '<rootDir>/test/**/*.spec.js'\n  ],\n  // Per https://jestjs.io/docs/configuration#transformignorepatterns-arraystring, transformIgnorePatterns ignores\n  // node_modules and pnp folders by default so that they are not transpiled\n  // Some libraries (even if transitive) are transitioning to ESM and need additional transpilation. Relevant issues:\n  // - tiny-sdf: https://github.com/visgl/deck.gl/issues/7735\n  transformIgnorePatterns: [\n    '/node_modules\\\\/(?!(.*@mapbox\\\\/tiny-sdf\\\\.*|@loaders\\\\.gl))',\n    '\\\\.pnp\\\\.[^\\\\/]+$'\n  ]\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "jest.setup.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport '@testing-library/jest-dom';\n\n// Disable polyfills as // ReferenceError: ReadableStream is not defined in @loaders.gl/polyfills\n// import {installFilePolyfills} from '@loaders.gl/polyfills';\n// installFilePolyfills();\n\n// Remove once @loaders.gl/polyfills are reenabled\nconst {TextDecoder, TextEncoder} = require('node:util');\nObject.defineProperties(globalThis, {\n  TextDecoder: {value: TextDecoder},\n  TextEncoder: {value: TextEncoder}\n});\n\njest.mock('mapbox-gl/dist/mapbox-gl', () => ({\n  Map: () => ({})\n}));\n\njest.mock('@kepler.gl/utils', () => ({\n  ...jest.requireActual('@kepler.gl/utils'),\n  hasPortableWidth: jest.fn(),\n  hasMobileWidth: jest.fn()\n}));\n\n// @loaders.gl/parquet isn't tested in jest atm, and is generating errors\njest.mock('@loaders.gl/parquet', () => ({}));\n\nglobal.URL.createObjectURL = jest.fn();\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\n    \"src/**/*\",\n    \"test/**/*\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"kepler.gl\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl is a webgl based application to visualize large scale location data in the browser\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"./types.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"private\": true,\n  \"workspaces\": [\n    \"./src/types\",\n    \"./src/constants\",\n    \"./src/common-utils\",\n    \"./src/utils\",\n    \"./src/styles\",\n    \"./src/localization\",\n    \"./src/deckgl-layers\",\n    \"./src/deckgl-arrow-layers\",\n    \"./src/table\",\n    \"./src/layers\",\n    \"./src/schemas\",\n    \"./src/cloud-providers\",\n    \"./src/processors\",\n    \"./src/tasks\",\n    \"./src/actions\",\n    \"./src/effects\",\n    \"./src/reducers\",\n    \"./src/components\",\n    \"./src/duckdb\",\n    \"./src/ai-assistant\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"bootstrap\": \"PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true git submodule update --init --recursive && yarn install && yarn fix-dependencies\",\n    \"install:example\": \"cd examples/demo-app && NODE_OPTIONS=--openssl-legacy-provider yarn\",\n    \"install:web\": \"yarn install:example && cd website && yarn\",\n    \"install-and-start\": \"node ./scripts/install-and-start\",\n    \"test-fast\": \"yarn test-node-debug && yarn test-browser-debug\",\n    \"test-node\": \"yarn test-node-debug | tap-spec\",\n    \"test-browser\": \"yarn test-browser-debug | tap-spec\",\n    \"test-headless\": \"NODE_ENV=test node ./test/browser-drive.js\",\n    \"test-browser-drive\": \"NODE_ENV=test node ./test/browser-drive.js debug\",\n    \"test-node-debug\": \"NODE_ENV=test node -r ./babel-register.js ./test/node.js\",\n    \"test-browser-debug\": \"NODE_ENV=test node -r ./babel-register.js ./test/setup-browser-env.js ./test/js-dom.js\",\n    \"test-jest\": \"jest\",\n    \"test-tape\": \"yarn test-node && yarn test-browser\",\n    \"test\": \"yarn test-jest && yarn test-tape\",\n    \"cover\": \"yarn cover-tape && yarn cover-jest && yarn cover-merge && yarn cover-report\",\n    \"cover-tape\": \"nyc --reporter=json --report-dir=tape-coverage --reporter=lcov yarn test-tape\",\n    \"cover-jest\": \"jest --coverage --reporter=lcov --watchAll=false\",\n    \"cover-merge\": \"istanbul-merge --out=coverage/coverage-all.json tape-coverage/coverage-final.json jest-coverage/coverage-final.json\",\n    \"cover-report\": \"nyc report -t coverage --report-dir coverage --reporter=json --reporter=lcov\",\n    \"start\": \"NODE_OPTIONS=--openssl-legacy-provider yarn install-and-start examples/demo-app start:local\",\n    \"start:deck\": \"NODE_OPTIONS=--openssl-legacy-provider yarn install-and-start examples/demo-app start:local-deck\",\n    \"start:deck-src\": \"NODE_OPTIONS=--openssl-legacy-provider yarn install-and-start examples/demo-app start:local-deck-src\",\n    \"start:loaders-src\": \"NODE_OPTIONS=--openssl-legacy-provider yarn install-and-start examples/demo-app start:local-loaders-src\",\n    \"start:open-modal\": \"yarn install-and-start examples/open-modal start-local\",\n    \"start:custom-reducer\": \"yarn install-and-start examples/custom-reducer start-local\",\n    \"start:replace-component\": \"yarn install-and-start examples/replace-component start:local\",\n    \"start:custom-theme\": \"yarn install-and-start examples/custom-theme start-local\",\n    \"start:custom-map-style\": \"yarn install-and-start examples/custom-map-style start-local\",\n    \"start:node-app\": \"yarn install-and-start examples/node-app start-local\",\n    \"start:web\": \"yarn install-and-start website start\",\n    \"start:https\": \"yarn install-and-start examples/demo-app start-local-https\",\n    \"start:e2e\": \"yarn install-and-start examples/demo-app start-local-e2e\",\n    \"build\": \"NODE_OPTIONS=--openssl-legacy-provider rm -fr dist && babel src/{actions,components,reducers,cloud-providers,localization,tasks,ai-assistant} --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"node ./esbuild/umd-esbuild.config.mjs\",\n    \"build:types\": \"tsc --project tsconfig.production.json\",\n    \"analyze\": \"yarn analyze:bundle\",\n    \"analyze:bundle\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/bundle.js --progress --env.prod\",\n    \"check-licence\": \"babel-node ./scripts/license-header/bin --license ./FILE-HEADER --dry\",\n    \"add-licence\": \"babel-node ./scripts/license-header/bin --license ./FILE-HEADER\",\n    \"prepublishOnly\": \"yarn workspaces foreach -At run stab && yarn workspaces foreach -At run prepublishOnly && yarn add-licence && yarn build:umd && yarn build:types\",\n    \"docs\": \"babel-node ./scripts/documentation.js\",\n    \"typedoc\": \"typedoc --theme markdown --out typedoc --inputFiles ./src/reducers --inputFiles ./src/actions --excludeExternals --excludeNotExported --excludePrivate\",\n    \"example-version\": \"babel-node ./scripts/edit-version.js\",\n    \"prettier-all\": \"prettier --write '{examples,src}/**/src/**/*.{js,tsx,ts}' 'test/**/*.{js,ts,tsx}'\",\n    \"lint\": \"yarn typescript && eslint src test webpack examples website --fix --ignore-path .gitignore\",\n    \"lint:css\": \"stylelint './src/**/*.js'\",\n    \"typescript\": \"tsc --noEmit\",\n    \"web\": \"(yarn && yarn install:web && yarn start:web)\",\n    \"deploy\": \"yarn install:web && cd website && yarn build\",\n    \"clean\": \"rm -rf node_modules examples/**/node_modules website/node_modules .nyc_output coverage jest-coverage tape-coverage\",\n    \"release:patch\": \"git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin && git push origin --tags\",\n    \"fix-dependencies\": \"./scripts/fix-dependencies.sh\"\n  },\n  \"files\": [\n    \"dist\",\n    \"src\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@deck.gl/mapbox\": \"^8.9.27\",\n    \"@hubble.gl/core\": \"1.4.0\",\n    \"@hubble.gl/react\": \"1.4.0\",\n    \"@kepler.gl/components\": \"3.2.6\",\n    \"@loaders.gl/polyfills\": \"^4.3.2\",\n    \"@types/mapbox__geo-viewport\": \"^0.4.1\",\n    \"html-webpack-plugin\": \"^4.3.0\",\n    \"monaco-editor\": \"^0.52.0\",\n    \"typedoc\": \"^0.19.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.12.1\",\n    \"@babel/core\": \"^7.12.1\",\n    \"@babel/eslint-parser\": \"^7.17.0\",\n    \"@babel/node\": \"^7.12.1\",\n    \"@babel/parser\": \"^7.12.1\",\n    \"@babel/plugin-transform-class-properties\": \"^7.12.1\",\n    \"@babel/plugin-transform-export-namespace-from\": \"^7.12.1\",\n    \"@babel/plugin-transform-logical-assignment-operators\": \"^7.12.1\",\n    \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\": \"^7.12.1\",\n    \"@babel/plugin-transform-optional-chaining\": \"^7.12.1\",\n    \"@babel/plugin-transform-runtime\": \"^7.12.1\",\n    \"@babel/plugin-transform-typescript\": \"^7.16.8\",\n    \"@babel/polyfill\": \"^7.12.1\",\n    \"@babel/preset-env\": \"^7.12.1\",\n    \"@babel/preset-react\": \"^7.12.1\",\n    \"@babel/preset-typescript\": \"^7.16.7\",\n    \"@babel/register\": \"^7.12.1\",\n    \"@babel/runtime\": \"^7.12.1\",\n    \"@babel/traverse\": \"^7.12.1\",\n    \"@cfaester/enzyme-adapter-react-18\": \"^0.7.0\",\n    \"@deck.gl/test-utils\": \"^8.9.27\",\n    \"@loaders.gl/polyfills\": \"^4.3.2\",\n    \"@luma.gl/test-utils\": \"^8.5.20\",\n    \"@nebula.gl/layers\": \"1.0.2-alpha.1\",\n    \"@open-wc/webpack-import-meta-loader\": \"0.4.7\",\n    \"@probe.gl/env\": \"^3.5.0\",\n    \"@probe.gl/test-utils\": \"^3.5.0\",\n    \"@testing-library/dom\": \"^9.0.1\",\n    \"@testing-library/jest-dom\": \"^5.16.5\",\n    \"@testing-library/react\": \"^14.0.0\",\n    \"@testing-library/user-event\": \"^14.4.3\",\n    \"@types/d3-array\": \"^2.8.0\",\n    \"@types/d3-scale\": \"^3.2.2\",\n    \"@types/geojson\": \"^7946.0.8\",\n    \"@types/jsdom\": \"^21.1.1\",\n    \"@types/redux-actions\": \"^2.6.2\",\n    \"@types/redux-logger\": \"^3\",\n    \"@types/supercluster\": \"^7.1.0\",\n    \"@typescript-eslint/eslint-plugin\": \"5.57.0\",\n    \"@typescript-eslint/parser\": \"^5.57.1\",\n    \"babel-loader\": \"^8.0.0\",\n    \"babel-plugin-istanbul\": \"^6.0.0\",\n    \"babel-plugin-module-resolver\": \"^4.0.0\",\n    \"babel-plugin-search-and-replace\": \"^1.0.0\",\n    \"babel-plugin-styled-components\": \"^2.1.4\",\n    \"babel-plugin-transform-builtin-extend\": \"^1.1.0\",\n    \"babelify\": \"^10.0.0\",\n    \"css-loader\": \"5.2.7\",\n    \"documentation\": \"^9.1.1\",\n    \"dts-bundle-webpack\": \"^1.0.2\",\n    \"enzyme\": \"^3.11.0\",\n    \"esbuild\": \"^0.23.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\",\n    \"esbuild-plugin-umd-wrapper\": \"3.0.0\",\n    \"eslint\": \"~8.53.0\",\n    \"eslint-config-developit\": \"^1.2.0\",\n    \"eslint-config-prettier\": \"8.5.0\",\n    \"eslint-import-resolver-typescript\": \"3.6.1\",\n    \"eslint-plugin-babel\": \"^5.3.0\",\n    \"eslint-plugin-enzyme-deprecation\": \"^0.7.7\",\n    \"eslint-plugin-import\": \"2.29.1\",\n    \"eslint-plugin-jest\": \"^27.2.1\",\n    \"eslint-plugin-prettier\": \"4.2.1\",\n    \"eslint-plugin-react\": \"^7.35.0\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"gl\": \"^6.0.2\",\n    \"global\": \"^4.4.0\",\n    \"istanbul-merge\": \"^2.0.0\",\n    \"jest\": \"^29.5.0\",\n    \"jest-environment-jsdom\": \"^29.5.0\",\n    \"jsdom\": \"^24.1.1\",\n    \"json-loader\": \"^0.5.4\",\n    \"mapbox-gl\": \"1.13.1\",\n    \"maplibre-gl\": \"^3.6.2\",\n    \"minimist\": \"^1.2.6\",\n    \"nyc\": \"^15.1.0\",\n    \"prettier\": \"2.8.8\",\n    \"progress-bar-webpack-plugin\": \"^2.1.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-hot-loader\": \"^4.13.0\",\n    \"readdirp\": \"^2.1.0\",\n    \"redux-logger\": \"^3.0.6\",\n    \"redux-mock-store\": \"^1.2.1\",\n    \"sinon\": \"^2.4.1\",\n    \"sinon-stub-promise\": \"^4.0.0\",\n    \"source-map-loader\": \"^1.1.1\",\n    \"style-loader\": \"2.0.0\",\n    \"styled-components\": \"6.1.8\",\n    \"stylelint\": \"^13.6.1\",\n    \"stylelint-config-recommended\": \"^3.0.0\",\n    \"stylelint-config-styled-components\": \"^0.1.1\",\n    \"stylelint-processor-styled-components\": \"^1.10.0\",\n    \"tap-spec\": \"^5.0.0\",\n    \"tape\": \"^4.9.2\",\n    \"tape-catch\": \"^1.0.6\",\n    \"typedoc-plugin-markdown\": \"^3.0.11\",\n    \"typescript\": \"4.7.2\",\n    \"url-loader\": \"^4.1.1\",\n    \"usehooks-ts\": \"^3.1.0\",\n    \"watchify\": \"^3.6.1\",\n    \"webpack\": \"^4.29.0\",\n    \"webpack-bundle-analyzer\": \"^3.3.2\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-middleware\": \"^3.5.1\",\n    \"webpack-dev-server\": \"^3.1.14\",\n    \"webpack-hot-middleware\": \"^2.24.3\",\n    \"webpack-stats-plugin\": \"^0.2.1\"\n  },\n  \"resolutions\": {\n    \"@loaders.gl/core\": \"4.3.2\",\n    \"@loaders.gl/csv\": \"4.3.2\",\n    \"@loaders.gl/draco\": \"4.3.2\",\n    \"@loaders.gl/gltf\": \"4.3.2\",\n    \"@loaders.gl/json\": \"4.3.2\",\n    \"@loaders.gl/loader-utils\": \"4.3.2\",\n    \"@loaders.gl/polyfills\": \"4.3.2\",\n    \"@loaders.gl/arrow\": \"4.3.2\",\n    \"@loaders.gl/parquet\": \"4.3.2\",\n    \"@loaders.gl/gis\": \"4.3.2\",\n    \"@loaders.gl/schema\": \"4.3.2\",\n    \"@loaders.gl/wkt\": \"4.3.2\",\n    \"@loaders.gl/wms\": \"4.3.2\",\n    \"@luma.gl/constants\": \"8.5.21\",\n    \"@luma.gl/core\": \"8.5.21\",\n    \"@luma.gl/experimental\": \"8.5.21\",\n    \"@luma.gl/shadertools\": \"8.5.21\",\n    \"@luma.gl/test-utils\": \"8.5.21\",\n    \"@luma.gl/webgl\": \"8.5.21\",\n    \"@types/lodash\": \"4.17.5\",\n    \"browserslist\": \"^4.17.0\",\n    \"caniuse-lite\": \"^1.0.30001636\",\n    \"d3-array\": \"^2.8.0\",\n    \"d3-scale\": \"^3.2.3\",\n    \"eslint-plugin-jest\": \"^27.2.1\",\n    \"dot-prop\": \"6.0.0\",\n    \"kind-of\": \"6.0.3\",\n    \"jpeg-js\": \"^0.4.3\",\n    \"lodash\": \"4.17.21\",\n    \"minimist\": \"1.2.6\",\n    \"node-fetch\": \"2.6.1\",\n    \"tough-cookie\": \"4.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \">=18.2\",\n    \"react-dom\": \">=18.2\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false,\n    \"exclude\": [\n      \"test/**/*.js\",\n      \"src/tasks\",\n      \"src/templates\",\n      \"src/styles\"\n    ]\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"nx\": {\n    \"tags\": [\n      \"type:lib\",\n      \"scope:frontend\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "scripts/action-table-maker.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport fs from 'fs';\nimport {resolve, join} from 'path';\nimport traverse from '@babel/traverse';\nimport * as parser from '@babel/parser';\nimport {walkSync} from './ast-helper';\n\nconst SRC_DIR = './src';\nconst entries = [\n  // action to type mapping\n  join(SRC_DIR, 'actions'),\n\n  // type to updater mapping\n  join(SRC_DIR, 'reducers')\n];\n\n/**\n * Build action table\n * @public\n */\nexport function buildActionTable() {\n  const allFiles = walkSync(entries, []);\n\n  let actionTypeMap = {};\n\n  allFiles.forEach(file => {\n    actionTypeMap = traverseTree(file, actionTypeMap);\n  });\n\n  return Object.keys(actionTypeMap).reduce((accu, type) => {\n    accu[actionTypeMap[type].action.name] = {\n      ...actionTypeMap[type].action,\n      updaters: actionTypeMap[type].updaters,\n      actionType: type\n    };\n\n    return accu;\n  }, {});\n}\n\nfunction createActionNode(actionType) {\n  return {action: '', updaters: [], actionType};\n}\n\n/**\n * Parse actionHandler declaration to map updater to action type\n * @param {Object} path - AST node\n * @param {Object} actionMap\n * @param {string} filePath\n */\nfunction addActionHandler(path, actionMap, filePath) {\n\n  const {init} = path.node;\n\n  if (init && Array.isArray(init.properties)) {\n    init.properties.forEach(property => {\n      const {key, value} = property;\n      if (key && value && key.property && value.property) {\n        const actionType = key.property.name;\n        const updater = value.name;\n\n        actionMap[actionType] = actionMap[actionType] || createActionNode(actionType);\n        actionMap[actionType].updaters.push({\n          updater: value.object.name,\n          name: value.property.name,\n          path: filePath\n        });\n      }\n    })\n  }\n}\n\n/**\n * Parse createAction function to add action to action type\n * @param {*} path\n * @param {*} actionMap\n * @param {*} filePath\n */\nfunction addActionCreator(path, actionMap, filePath) {\n\n  const {node, parentPath} = path;\n  if (node.arguments.length && parentPath.node && parentPath.node.id) {\n\n    const action = parentPath.node.id.name;\n    const firstArg = node.arguments[0];\n    const actionType = firstArg.property ? firstArg.property.name : firstArg.name;\n\n    const {loc} = parentPath.node\n    actionMap[actionType] = actionMap[actionType] || createActionNode(actionType);\n    actionMap[actionType].action = {name: action, path: `${filePath}#L${loc.start.line}-L${loc.end.line}`};\n  }\n}\n\nfunction addActionDeclaration(path, actionMap, filePath) {\n  const {node} = path;\n\n  const action = node.id.name;\n  const returnStatement = node.body.body[0];\n\n  const returnValue = returnStatement.argument.properties[0].value;\n  const actionType = returnValue.property ? returnValue.property.name : returnValue.name;\n  const {loc} = path.node;\n\n  actionMap[actionType] = actionMap[actionType] || createActionNode(actionType);\n  actionMap[actionType].action = {name: action, path: `${filePath}#L${loc.start.line}-L${loc.end.line}`};\n}\n\nfunction traverseTree(filePath, actionMap = {}) {\n  const content = fs.readFileSync(filePath, 'utf8');\n\n  const ast = parser.parse(content, {\n    sourceType: 'module',\n    plugins: ['exportNamespaceFrom', 'exportDefaultFrom']\n  });\n\n  traverse(ast, {\n    VariableDeclarator(path) {\n      const {id} = path.node;\n      if (id.name === 'actionHandler') {\n        addActionHandler(path, actionMap, filePath);\n      }\n    },\n    CallExpression(path)  {\n      /**\n       * If action is declared with createAction\n       * export const togglePerspective = createAction(\n       *   ActionTypes.TOGGLE_PERSPECTIVE\n       * );\n       */\n      const {callee} = path.node;\n      if (callee.name === 'createAction') {\n        addActionCreator(path, actionMap, filePath);\n      }\n    },\n    FunctionDeclaration(path) {\n      /**\n       * If action is declared with a function call\n       * export function layerConfigChange(oldLayer, newConfig) {\n       *  return {type: ActionTypes.LAYER_CONFIG_CHANGE};\n       * }\n       */\n      if (filePath.endsWith('actions.js')) {\n        const {leadingComments} = path.parentPath.node;\n        if (\n          Array.isArray(leadingComments) && leadingComments[0] && leadingComments[0].value.includes('@public')\n        ) {\n          addActionDeclaration(path, actionMap, filePath);\n        }\n\n      }\n\n    }\n\n  });\n\n  return actionMap;\n}\n\nexport default buildActionTable;\n"
  },
  {
    "path": "scripts/ast-helper.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport fs from 'fs';\nimport {join} from 'path';\n\nexport function walkSync(dir, fileList = []) {\n  // Ensure dir is an array\n  const directories = Array.isArray(dir) ? dir : [dir];\n\n  directories.forEach(dirPath => {\n    const files = fs.readdirSync(dirPath);\n\n    files.forEach((file) => {\n      const path = join(dirPath, file);\n\n      if (fs.statSync(path).isDirectory()) {\n        fileList = walkSync(path, fileList);\n      } else if (path.endsWith('.js')){\n        fileList.push(path);\n      }\n    });\n  });\n\n  return fileList;\n};\n"
  },
  {
    "path": "scripts/documentation.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport documentation from 'documentation';\nimport fs from 'fs';\nimport {resolve, join} from 'path';\nimport {logSuccess, logProgress, logOk, logStep, logError} from './log';\nimport remark from 'remark';\nimport toc from 'remark-toc';\nimport unified from 'unified';\nimport markdown from 'remark-parse';\nimport actionTableMaker from './action-table-maker';\n\nconst INPUT_CONFIG = {\n  shallow: true,\n  access: ['public'],\n  'document-exported': true,\n  sortOrder: 'alpha'\n  // github: true\n};\n\nconst OUT_CONFIG = {\n  markdownToc: true\n};\n\nconst PATHS = {\n  src: resolve('./src'),\n  api: resolve('./docs/api-reference')\n};\n\nconst BT = '`';\n\nconst TREE = {\n  path: '',\n  children: [\n    {\n      path: 'actions',\n      children: [\n        {\n          input: [\n            '../constants/action-types.js',\n            'actions.js',\n            'action-wrapper.js',\n            'vis-state-actions.js',\n            'ui-state-actions.js',\n            'map-state-actions.js',\n            'map-style-actions.js',\n            'identity-actions.js'\n          ],\n          output: 'actions.md',\n          config: {shallow: true}\n        }\n      ]\n    },\n    {\n      path: 'reducers',\n      children: [\n        {input: ['root.js', 'core.js'], output: 'reducers.md', config: {shallow: true}},\n        {input: 'combined-updaters.js', output: 'combine.md', config: {shallow: true}},\n        {input: 'vis-state-updaters.js', output: 'vis-state.md', config: {shallow: true}},\n        {input: 'map-state-updaters.js', output: 'map-state.md', config: {shallow: true}},\n        {input: 'map-style-updaters.js', output: 'map-style.md', config: {shallow: true}},\n        {input: 'ui-state-updaters.js', output: 'ui-state.md', config: {shallow: true}}\n      ]\n    },\n    {\n      path: 'processors',\n      children: [{input: 'data-processor.js', output: 'processors.md', config: {shallow: true}}]\n    },\n    {\n      path: 'cloud-providers',\n      children: [{input: 'provider.js', output: 'cloud-provider.md', config: {shallow: true}}]\n    }\n  ]\n};\n\nfunction _overrideHeading(nodes) {\n  const contents = ['Examples', 'Parameters'];\n  const mdContents = contents.map(text => {\n    return unified()\n      .use(markdown)\n      .parse(`__${text}__`);\n  });\n\n  return nodes.map(node => {\n    if (\n      node.type === 'heading' &&\n      Array.isArray(node.children) &&\n      contents.includes(node.children[0].value)\n    ) {\n      const value = node.children[0].value;\n      const replacement = mdContents[contents.indexOf(value)].children[0];\n      return replacement;\n    }\n\n    return node;\n  });\n}\n\nfunction _appendActionTypesAndUpdatersToActions(node, actionMap) {\n  // __Updaters__: [`visStateUpdaters.loadFilesUpdater`](../reducers/ui-state.md#uistateupdaterssetexportfilteredupdater)\n\n  if (node.members && node.members.static.length) {\n    node.members.static = node.members.static.map(nd =>\n      _appendActionTypesAndUpdatersToActions(nd, actionMap)\n    );\n  }\n\n  const action = node.name;\n\n  if (!actionMap[action]) {\n    return node;\n  }\n\n  const {actionType, updaters} = actionMap[action];\n  const updaterList = updaters\n    .map(\n      ({updater, name, path}) =>\n        `[${BT}${updater}.${name}${BT}](../reducers/${path.split('/')[2].replace('.js', '')}.md#${(\n          updater + name\n        ).toLowerCase()})`\n    )\n    .join(', ');\n\n  const mdContent = `\n  * __ActionTypes__: [${BT}ActionTypes.${actionType}${BT}](#actiontypes)\n  * __Updaters__: ${updaterList}\n  `;\n\n  return _appendListToDescription(node, mdContent);\n}\n\n/**\n * Add action to linked updaters\n * @param {Object} node\n * @param {Object} actionMap\n */\nfunction _appendActionToUpdaters(node, actionMap) {\n  if (node.members && node.members.static.length) {\n    node.members.static = node.members.static.map(nd => _appendActionToUpdaters(nd, actionMap));\n  }\n\n  const updater = node.name;\n\n  const action = Object.values(actionMap).find(action =>\n    action.updaters.find(up => up.name === updater)\n  );\n\n  if (!action) {\n    return node;\n  }\n  const actionName = action.name;\n\n  const mdContent = `\n  * __Action__: [${BT}${actionName}${BT}](../actions/actions.md#${actionName.toLowerCase()})\n  `;\n\n  return _appendListToDescription(node, mdContent);\n}\n\nfunction _appendListToDescription(node, mdContent) {\n  const tree = unified()\n    .use(markdown)\n    .parse(mdContent);\n\n  if (typeof node.description === 'object') {\n    node.description.children = (node.description.children || []).concat(tree.children);\n  } else {\n    logError(`Missing Description for ${node.name}`);\n    node.description = tree;\n  }\n\n  return node;\n}\n\nfunction _isParagraph(node) {\n  return node.type === 'paragraph' && node.children.length === 1;\n}\n\nfunction _isLink(node) {\n  return node.type === 'link' && node.children.length === 1;\n}\n\nfunction _isLinkReference(node) {\n  return node.type === 'linkReference' && node.children.length === 1;\n}\nfunction _isExampleOrParam(node) {\n  return node.type === 'text' && ['Parameters', 'Examples', 'Properties'].includes(node.value);\n}\n\nfunction _isExampleOrParameterLink(node) {\n  return (\n    _isParagraph(node) &&\n    _isLinkReference(node.children[0]) &&\n    _isExampleOrParam(node.children[0].children[0])\n  );\n}\n\n/**\n * Remove example and parameter link from TOC\n */\nfunction _cleanUpTOCChildren(node) {\n  if (!Array.isArray(node.children)) {\n    return node;\n  }\n\n  if (_isExampleOrParameterLink(node)) {\n    return null;\n  }\n\n  const filteredChildren = node.children\n    .reduce((accu, nd) => {\n      accu.push(_cleanUpTOCChildren(nd));\n      return accu;\n    }, [])\n    .filter(n => n);\n\n  if (!filteredChildren.length) {\n    return null;\n  }\n\n  return {\n    ...node,\n    children: filteredChildren\n  };\n}\n\nfunction buildChildDoc(inputPath, outputPath, actionMap, config) {\n  return documentation\n    .build(inputPath, {...INPUT_CONFIG, ...config})\n    .then(res => {\n      // res is an array of parsed comments with inferred properties\n      // and more: everything you need to build documentation or\n      // any other kind of code data.\n      let processed = res;\n\n      if (outputPath.includes('actions.md')) {\n        // add action type and updater links to action\n        processed = res.map(node => _appendActionTypesAndUpdatersToActions(node, actionMap));\n      } else if (inputPath.some(p => p.includes('reducers'))) {\n        // add action type and updater links to action\n        processed = res.map(node => _appendActionToUpdaters(node, actionMap));\n      }\n\n      return documentation.formats.remark(processed, OUT_CONFIG);\n    })\n    .then(output => {\n      // output is a string of remark json\n      const ast = JSON.parse(output);\n\n      ast.children = _overrideHeading(ast.children);\n      if (ast.children.length < 3) {\n        logError(inputPath, 'has less than 3 children');\n      }\n      const tableOfContent = _cleanUpTOCChildren(ast.children[2]);\n      ast.children[2] = tableOfContent;\n\n      const mdOutput = remark().stringify(ast);\n      fs.writeFileSync(outputPath, mdOutput);\n      logOk(`   ✓ build docs ${inputPath} -> ${outputPath}`);\n    })\n    .catch(err => {\n      logError(err);\n    });\n}\n\nfunction buildMdDocs(nodePath, node, actionMap, allTasks) {\n  const {path, children} = node;\n  const joinPath = nodePath ? `${nodePath}/${path}` : path;\n\n  children.forEach(child => {\n    if (!child.children) {\n      const {input, output, config} = child;\n      const inputPaths = (Array.isArray(input) ? input : [input]).reduce((accu, inp) => {\n        const inputPath = join(PATHS.src, joinPath, inp);\n\n        if (fs.existsSync(inputPath)) {\n          // Do something\n          accu.push(inputPath);\n        } else {\n          logError(`[Error] ${inputPath} doesn't exist!`);\n        }\n\n        return accu;\n      }, []);\n\n      if (!inputPaths.length) {\n        return;\n      }\n      const outputPath = join(PATHS.api, joinPath, output);\n\n      allTasks.push(buildChildDoc(inputPaths, outputPath, actionMap, config));\n    } else {\n      buildMdDocs(joinPath, child, actionMap, allTasks);\n    }\n  });\n\n  return allTasks;\n}\n\nfunction buildDocs() {\n  logProgress('\\n================= Start Building API Documentation =================\\n');\n\n  logStep('   ## 1. Gathering action and updater mapping');\n  const actionMap = actionTableMaker();\n\n  logStep('   ## 2. Build Markdown files from jsDoc');\n  const allTasks = buildMdDocs(null, TREE, actionMap, []);\n\n  Promise.all(allTasks).then(() => {\n    logSuccess('\\n================= Building API Documentation Success! =================\\n');\n  });\n}\n\nbuildDocs();\n"
  },
  {
    "path": "scripts/edit-version.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport fs from 'fs';\nimport {resolve, join} from 'path';\nimport {logSuccess, logProgress, logStep} from './log';\n\nconst KeplerPackage = require('../package');\nconst exampleDir = resolve('./examples');\n// edit package.json in examples\n\nfunction readWritePackage(file, version) {\n  fs.readFile(file, (err, data) => {\n    if (err) throw err;\n    const PackageContent = JSON.parse(data);\n    PackageContent.dependencies['kepler.gl'] = `^${version}`;\n    const out = JSON.stringify(PackageContent, null, 2);\n\n    fs.writeFile(file, out, error => {\n      if (error) throw error;\n      logStep(`   ## Edit ${file} success.`);\n    });\n  });\n}\n\nfunction editExamplePackageJson(version) {\n  logProgress(\n    '\\n============= Start Editing examples package.json =================\\n'\n  );\n\n  fs.readdir(exampleDir, (err, items) => {\n    if (err) throw err;\n\n    for (let i = 0; i < items.length; i++) {\n      // for each example folder\n      const packageJsonPath = join(exampleDir, items[i], 'package.json');\n      if (fs.existsSync(packageJsonPath)) {\n        readWritePackage(packageJsonPath, version);\n      }\n    }\n  });\n}\n\nfunction editUMDPackage(version) {\n  logProgress(\n    '\\n============= Start Editing umd package version =================\\n'\n  );\n  const htmlFile = join(exampleDir, 'umd-client', 'index.html');\n  fs.readFile(htmlFile, 'utf8', (err, html) => {\n    if (err) throw err;\n\n    const out = html.replace(\n      /kepler\\.gl@\\d+.\\d+.\\d+\\/umd/,\n      `kepler.gl@${version}/umd`\n    );\n    fs.writeFile(htmlFile, out, error => {\n      if (error) throw error;\n      logStep(`   ## Edit ${htmlFile} success.`);\n    });\n  });\n}\n\n(function main() {\n  const KeplerGlVersion = KeplerPackage.version;\n\n  editExamplePackageJson(KeplerGlVersion);\n  editUMDPackage(KeplerGlVersion);\n\n  logSuccess(\n    `\\n================= Edit example kepler.gl version to ${KeplerGlVersion} Success! =================\\n`\n  );\n})();\n"
  },
  {
    "path": "scripts/fix-dependencies.sh",
    "content": "#!/bin/bash\n\n# Here we patch up the dependencies that need to be tweaked to work with our build system after installed\n\n# Per https://github.com/visgl/deck.gl/issues/7735, @mapbox/tiny-sdf is a ESM that we need to transpile\n# and consume the cjs version. For some reason, trying to force transpile it through Babel does not work\n# as crash happens before it even gets to that point\n# We use tail to avoid the first line of the the output which is the command itself\nyarn babel node_modules/@mapbox/tiny-sdf/index.js | tail -n +2 > node_modules/@mapbox/tiny-sdf/index.cjs\n\n# Patch for an issue with react-virtualized output having an invalid import\n# https://github.com/bvaughn/react-virtualized/issues/1212\nif [[ -f \"node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js\" ]]; then\n  sed -i -e '/import { bpfrpt_proptype_WindowScroller } from \"..\\/WindowScroller.js\";/d' node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js\nfi\n\n# fix ERR_REQUIRE_ESM in yarn cover\nyarn babel node_modules/maplibregl-mapbox-request-transformer/src/index.js | tail -n +2 > node_modules/maplibregl-mapbox-request-transformer/src/index.cjs"
  },
  {
    "path": "scripts/install-and-start.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst {existsSync} = require('fs');\nconst {execSync} = require('child_process');\n\nconst folder = process.argv[2];\nconst script = process.argv[3];\n\nconst cmd = !existsSync(`${folder}/node_modules`) ? `yarn && yarn ${script}` : `yarn ${script}`;\n\nexecSync(cmd, {\n  cwd: folder,\n  stdio: 'inherit'\n});\n"
  },
  {
    "path": "scripts/license-header/README.md",
    "content": "Utility to add license header to your files.\n\nForked from [uber-licence](https://github.com/uber/uber-licence).\n\nRunning the `license-header` binary adds licencing information to every javascript file in your project.\n\nYou can run `license-header --dry` where it does not mutate any files and instead outputs the number of files it would change.\n\nYou can use `--file` and `--dir` to specify your own file and directory filters to select source files to consider.\n\n## Recommended usage\n\n```\n// package.json\n{\n  \"scripts\": {\n    \"check-license\": \"license-header --dry\",\n    \"add-license\": \"license-header\"\n  },\n  \"devDependencies\": {\n    \"minimist\": \"^1.1.0\",\n    \"readdirp\": \"^2.1.0\",\n    \"pre-commit\": \"0.0.9\"\n  },\n  \"pre-commit\": [\n    \"test\",\n    \"check-license\"\n  ],\n  \"pre-commit.silent\": true\n}\n```\n\nAdd missing headers.\n\n```bash\nyarn run babel-node ./scripts/license-header/bin\n```\n\nMigrate to a new header.\n\n```bash\nyarn run babel-node ./scripts/license-header/bin --license ./scripts/license-header/FILE-HEADER --legacy ./LICENSE\n```\n\n"
  },
  {
    "path": "scripts/license-header/bin.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Forked from uber-licence, MIT\n\nimport readdirp from 'readdirp';\nimport minimist from 'minimist';\nimport {readFileSync} from 'fs';\nimport process from 'global/process.js';\nimport console from 'global/console.js';\n\nimport LicenseFixer from './license-fixer.mjs';\n\nvar argv = minimist(process.argv.slice(2));\nvar cwd = process.cwd();\n\n/*eslint no-process-exit: 0, no-console: 0*/\n// jscs:disable maximumLineLength\nif (argv.help || argv.h) {\n    console.log('license-header');\n    console.log('  ');\n    console.log('  This binary will add a license to the top');\n    console.log('  of all your files');\n    console.log('');\n    console.log('  Options:');\n    console.log('    --dry      does not write to files');\n    console.log('    --file     pattern of files to modify');\n    console.log('    --dir      pattern for directories containing files');\n    console.log('    --license  intended license (file)');\n    console.log('    --legacy   licenses to replace');\n    console.log('    --verbose  log skipped and empty files');\n    console.log('    --silent   do not log fixed files');\n    process.exit(0);\n}\n\nvar fileFilter = ['*.js', '*.ts', '*.tsx', '*.mjs', '*.py'];\nif (typeof argv.file === 'string') {\n    fileFilter = [argv.file];\n} else if (Array.isArray(argv.file)) {\n    fileFilter = argv.file;\n}\n\nvar directoryFilter = ['!.git', '!node_modules', '!coverage', '!env', '!.tox', '!vendor', '!Godeps', '!dist'];\nif (typeof argv.dir === 'string') {\n    directoryFilter = [argv.dir];\n} else if (Array.isArray(argv.dir)) {\n    directoryFilter = argv.dir;\n}\n\nvar licenses = null;\n\nif (typeof argv.license === 'string') {\n    licenses = licenses || [];\n    licenses.push(argv.license);\n} else if (Array.isArray(argv.license)) {\n    licenses = licenses || [];\n    Array.prototype.push.apply(licenses, argv.license);\n}\n\nif (typeof argv.legacy === 'string') {\n    licenses = licenses || [];\n    licenses.push(argv.legacy);\n} else if (Array.isArray(argv.legacy)) {\n    licenses = licenses || [];\n    Array.prototype.push.apply(licenses, argv.legacy);\n}\n\nif (licenses) {\n    for (var i = 0; i < licenses.length; i++) {\n        // Replace file names with content of files\n        licenses[i] = readFileSync(licenses[i], 'utf8');\n    }\n} else {\n    console.error('no license provided');\n    process.exit(1);\n}\n\nvar licenseFixer = new LicenseFixer({\n    dry: argv.dry,\n    silent: argv.silent,\n    verbose: argv.verbose\n});\n\n// Set the intended license text\nlicenseFixer.setLicense(licenses[0]);\n// Add a license to match and replace.\n// There can be multiple recognized licenses, for migration purposes.\nfor (var i = 0; i < licenses.length; i++) {\n    licenseFixer.addLicense(licenses[i]);\n}\n\nreadTree({\n    root: cwd,\n    fileFilter: fileFilter,\n    directoryFilter: directoryFilter\n}, processFiles);\n\nfunction readTree(options, callback) {\n    var stream = readdirp(options);\n    var files = [];\n    stream.on('data', onData);\n    stream.on('end', onEnd);\n    stream.on('error', onEnd);\n    function onData(event) {\n        files.push(event.path);\n    }\n    function onEnd(err) {\n        callback(err, files);\n    }\n}\n\nfunction processFiles(err, files) {\n    if (err) {\n        console.error(err.message);\n        process.exit(1);\n        return;\n    }\n\n    var fixed = 0;\n    for (var filesIndex = 0; filesIndex < files.length; filesIndex++) {\n        var file = files[filesIndex];\n        fixed = fixed + licenseFixer.fixFile(file);\n    }\n\n    if (argv.dry) {\n        process.exit(fixed);\n    } else {\n        process.exit(0);\n    }\n}\n"
  },
  {
    "path": "scripts/license-header/license-fixer.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Forked from uber-licence, MIT\n\nimport {readFileSync, writeFileSync} from 'fs';\nimport console from 'global/console.js';\n\nfunction LicenseFixer(options) {\n    options = options || {};\n    this.slashLicense = null;\n    this.hashLicense = null;\n    this.licenseExpressions = [];\n    this.dry = options.dry || false;\n    this.silent = options.silent || false;\n    this.verbose = options.verbose || false;\n    Object.seal(this);\n}\n\nLicenseFixer.prototype.addLicense = function addLicense(license) {\n    this.licenseExpressions.push(createLicenseExpression(license));\n};\n\nfunction createLicenseExpression(license) {\n    license = license.trim();\n    // Transform the license into a regular expression that matches the exact\n    // license as well as similar licenses, with different dates and line\n    // wraps.\n    var pattern = license.split(/\\s+/).map(relaxLicenseTerm).join('') + '\\\\s*';\n    return new RegExp(pattern, 'gmi');\n}\n\nfunction relaxLicenseTerm(term) {\n    // There has been at least one occasion where someone replaced all single\n    // quotes with double quotes throughout a file and got an extra license.\n    return '\\\\s*((//|#)\\\\s*)*' + // wrap around any comment or spacing\n        regexpEscape(term)\n            .replace(/\\d{4}/g, '\\\\d{4}') // dates to date patterns\n            .replace(/['\"]/g, '[\\'\"]'); // relax quotes\n}\n\nvar regexpEscapePattern = /[|\\\\{}()[\\]^$+*?.]/g;\n\nfunction regexpEscape(string) {\n    return string.replace(regexpEscapePattern, '\\\\$&');\n}\n\nLicenseFixer.prototype.setLicense = function setLicense(license) {\n    this.slashLicense = createSlashLicense(license);\n    this.hashLicense = createHashLicense(license);\n};\n\nfunction createSlashLicense(license) {\n    return license.trim().split('\\n').map(slashPrefix).join('');\n}\n\nfunction createHashLicense(license) {\n    return license.trim().split('\\n').map(hashPrefix).join('');\n}\n\nfunction slashPrefix(line) {\n    return ('// ' + line).trim() + '\\n';\n}\n\nfunction hashPrefix(line) {\n    return ('# ' + line).trim() + '\\n';\n}\n\nLicenseFixer.prototype.getLicenseForFile = function getLicenseForFile(file) {\n    if (file.match(/\\.(js|go|java|ts|tsx|mjs)$/)) {\n        return this.slashLicense;\n    } else if (file.match(/\\.(pyx?|pxd)$/)) {\n        return this.hashLicense;\n    }\n    return null;\n};\n\nLicenseFixer.prototype.fixContent = function fixContent(file, content) {\n    var preamble = '';\n    // Check for shebang\n    var foundShebang = content.match(/^#!|#\\s*(en)?coding=/m);\n    if (foundShebang) {\n        var shebangIndex = content.indexOf('\\n');\n        if (shebangIndex >= 0) {\n            preamble += content.slice(0, shebangIndex + 1);\n            content = content.slice(shebangIndex + 1).trim() + '\\n';\n        }\n    }\n\n    // check for @flow header\n    var foundFlowHeader = content.match(/^\\/\\/ @flow|^\\/\\* @flow \\*\\//m);\n    if (foundFlowHeader) {\n        var flowIndex = content.indexOf('\\n');\n        if (flowIndex >= 0) {\n            preamble += content.slice(0, flowIndex + 1);\n            content = content.slice(flowIndex + 1).trim() + '\\n';\n        }\n    }\n\n    if (foundShebang || foundFlowHeader) {\n        preamble += '\\n';\n    }\n\n    // Remove old licenses\n    for (var i = 0; i < this.licenseExpressions.length; i++) {\n        // string replace hangs in some pathelogical cases of repeated licenses\n        var match = this.licenseExpressions[i].exec(content);\n        while (match) {\n            content = content.slice(0, match.index) + content.slice(match.index + match[0].length);\n            match = this.licenseExpressions[i].exec(content);\n        }\n    }\n\n    var license = this.getLicenseForFile(file);\n    if (license === null) {\n        if (!this.silent) {\n            console.error(`unrecognized file type ${file}`);\n        }\n        return null;\n    }\n\n    // Reintroduce the preamble and license\n    content = preamble + license + '\\n' + content;\n\n    return content;\n};\n\nLicenseFixer.prototype.fixFile = function fixFile(file) {\n    var original = readFileSync(file, 'utf8');\n\n    if (original.length === 0) {\n        // Ignore empty files\n        if (this.verbose) {\n            console.log(`empty ${file}`);\n        }\n        return false;\n    }\n\n    var content = this.fixContent(file, original);\n\n    if (content === null) {\n        // Return true on error so dry run fails\n        return true;\n    }\n\n    if (original === content) {\n        // No change\n        if (this.verbose) {\n            console.log(`skip ${file}`);\n        }\n        return false;\n    }\n\n    if (!this.silent) {\n        console.log(`fix ${file}`);\n    }\n\n    if (this.dry) {\n        return true;\n    }\n\n    writeFileSync(file, content, 'utf8');\n    return true;\n};\n\nexport default LicenseFixer"
  },
  {
    "path": "scripts/log.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst console = require('global/console');\n\nconst Colors = {\n  Reset: '\\x1b[0m',\n  Bright: '\\x1b[1m',\n  Dim: '\\x1b[2m',\n  Underscore: '\\x1b[4m',\n  Blink: '\\x1b[5m',\n  Reverse: '\\x1b[7m',\n  Hidden: '\\x1b[8m',\n\n  FgBlack: '\\x1b[30m',\n  FgRed: '\\x1b[31m',\n  FgGreen: '\\x1b[32m',\n  FgYellow: '\\x1b[33m',\n  FgBlue: '\\x1b[34m',\n  FgMagenta: '\\x1b[35m',\n  FgCyan: '\\x1b[36m',\n  FgWhite: '\\x1b[37m',\n\n  BgBlack: '\\x1b[40m',\n  BgRed: '\\x1b[41m',\n  BgGreen: '\\x1b[42m',\n  BgYellow: '\\x1b[43m',\n  BgBlue: '\\x1b[44m',\n  BgMagenta: '\\x1b[45m',\n  BgCyan: '\\x1b[46m',\n  BgWhite: '\\x1b[47m'\n}\n\nfunction log(color) {\n  return function logWithColor(msg) {\n    console.log(`${color}%s${Colors.Reset}`, msg);\n  };\n}\n\nmodule.exports = {\n  logSuccess: log(Colors.FgGreen),\n  logOk: log(Colors.FgCyan),\n  logError: log(Colors.FgRed),\n  logProgress: log(Colors.FgBlue),\n  logStep: log(Colors.FgMagenta),\n}\n"
  },
  {
    "path": "scripts/ts-smoosh/README.md",
    "content": "## TS Smoosh\n\nCombine type decls with related source files.\n\n\n# Use\n\nGiven a JavaScript file (or list of files) like so:\n\n```\n$ node ts-smoosh/bin ./src/some-file.js\n```\n\nWill produce `.jsx` files using nearby `.d.ts` files.\n\n\n# Running Tests\n\n```\n$ node test\n```\n\nTest cases live in various directories in `./tests`. Tests run by comparing the output against golden master `.tsx` files. To update the masters, run `node ts-smoosh/bin ./test/0X-dir/somefile.js`.\n\n# Notes\n\nHere are articles and docs I that were helpful when writing this script.\n\n- [Using the Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)\n- [Blog post about using TS's parser directly](https://medium.com/allenhwkim/how-to-parse-typescript-from-source-643387971f4e)\n- [TypeScript compiler APIs revisited](https://blog.scottlogic.com/2017/05/02/typescript-compiler-api-revisited.html)\n- [TypeScript API Playground on Glitch](https://typescript-api-playground.glitch.me/#example=Transformation%203)\n"
  },
  {
    "path": "scripts/ts-smoosh/bin.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst { smoosh } = require(\"./smoosh\");\n\n// Given an array of `.js` files, smooshes their .d.ts declarations and\n// produces a .tsx file.\n\nconst without = (ending) => (fileName) =>\n  fileName.endsWith(ending)\n    ? fileName.substr(0, fileName.length - ending.length)\n    : fileName;\n\nconst files = process.argv.slice(2);\n\nfiles.map(without(\".js\")).map(smoosh);\n"
  },
  {
    "path": "scripts/ts-smoosh/smoosh.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst ts = require(\"typescript\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\n/**\n * Writes the result of smooshing to a file\n */\nfunction smoosh(base) {\n  const smooshedSrc = returnSmooshed(base);\n  const outputFile = `./${base}.tsx`;\n  fs.writeFileSync(outputFile, smooshedSrc);\n}\n\nconst declDoesntExist = { typeAliases: [], declarations: [], imports: [] };\n\nfunction returnSmooshed(base) {\n  const dtsFile = `${base}.d.ts`;\n  // TODO(btford): log a warning here?\n  const decls = fs.existsSync(dtsFile) ? parseDts(dtsFile) : declDoesntExist;\n\n  const jsFile = `${base}.js`;\n  const enrichedJsNode = enrichJs(jsFile, decls);\n\n  const outputFile = `${base}.tsx`;\n\n  const resultFile = ts.createSourceFile(\n    outputFile,\n    \"\",\n    ts.ScriptTarget.Latest,\n    false,\n    ts.ScriptKind.TSX\n  );\n  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });\n\n  const smooshedSrc = printer.printNode(\n    ts.EmitHint.Unspecified,\n    enrichedJsNode,\n    resultFile\n  );\n\n  return smooshedSrc;\n}\n\nfunction parseDts(dtsFile) {\n  const parsed = ts.createSourceFile(\n    dtsFile,\n    fs.readFileSync(dtsFile, \"utf8\"),\n    ts.ScriptTarget.Latest\n  );\n\n  // these are going on top\n  const typeAliases = [];\n  const declarations = {};\n  const imports = [];\n\n  const aggregateDecl = (statement) => {\n    // console.log(statement);\n    const kind = ts.SyntaxKind[statement.kind];\n\n    if (kind === \"TypeAliasDeclaration\") {\n      declarations[getIdentifierName(statement)] = statement.type;\n      typeAliases.push(statement);\n      return;\n    }\n    if (kind === \"ImportDeclaration\") {\n      return console.log(\"import...\");\n    }\n\n    if (kind === \"FirstStatement\") {\n      return statement.declarationList.declarations.map(aggregateDecl);\n    }\n\n    if (!kind.endsWith(\"Declaration\")) {\n      const message = `Unexpected statement kind \"${kind}\" in type definition file \"${dtsFile}\"`;\n      return console.warn(message);\n    } else {\n      declarations[getIdentifierName(statement)] = statement;\n    }\n  };\n\n  parsed.statements.forEach(aggregateDecl);\n\n  return { typeAliases, declarations, imports };\n}\n\nfunction enrichJs(jsFile, dts) {\n  const parsed = ts.createSourceFile(\n    jsFile,\n    fs.readFileSync(jsFile, \"utf8\"),\n    ts.ScriptTarget.Latest\n  );\n\n  const findSource = (node) => {\n\n    let typeSource = null;\n\n    // First, search for a jsdoc tag with the type, like:\n    // @type {typeof import('./b').Noop}\n    if (node.jsDoc) {\n      const typeTag = node.jsDoc[0].tags.find(\n        (tag) => tag.tagName.escapedText === \"type\"\n      );\n      if (typeTag) {\n        const fileName =\n          typeTag.typeExpression.type.argument.literal.text;\n        const identifier =\n          typeTag.typeExpression.type.qualifier.escapedText;\n        const dir = path.dirname(jsFile);\n        const fullPath = path.resolve(dir, fileName + \".d.ts\");\n        const importedDts = parseDts(fullPath);\n        const importedType = importedDts.declarations[identifier];\n        if (!importedType) {\n          console.warn(\n            `Could not find ${identifier} in ${fullPath} while trying to smoosh ${jsFile}`\n          );\n          return node;\n        }\n        typeSource = importedType;\n      }\n    }\n\n    // Second, use the d.ts file with the same name as this file.\n    if (!typeSource) {\n      typeSource = dts.declarations[getIdentifierName(node)];\n    }\n\n    return typeSource;\n  }\n\n  const transformer = (context) => (rootNode) => {\n    function visit(node) {\n      const kind = ts.SyntaxKind[node.kind];\n      if (kind.endsWith(\"Declaration\")) {\n        if (kind === \"FunctionDeclaration\") {\n          const typeSource = findSource(node);\n          if (typeSource) {\n            return ts.factory.updateFunctionDeclaration(\n              node,\n              node.decorators,\n              node.modifiers,\n              node.asteriskToken,\n              node.name,\n              typeSource.typeParameters,\n              typeSource.parameters,\n              typeSource.type,\n              node.body\n            );\n          }\n          return node;\n        } else if (kind === 'VariableDeclaration') {\n          const typeSource = findSource(node);\n          if (typeSource) {\n            return ts.factory.updateVariableDeclaration(\n              node,\n              node.name,\n              node.exclamationToken,\n              typeSource.type,\n              node.initializer\n            );\n          }\n          return node;\n        }\n\n        return node;\n      }\n\n      return ts.visitEachChild(node, visit, context);\n    }\n    return ts.visitNode(rootNode, visit);\n  };\n\n  return ts.transform(parsed, [transformer]).transformed[0];\n}\n\nfunction getIdentifierName(node) {\n  return node.name.escapedText;\n}\n\nmodule.exports = {\n  smoosh,\n  returnSmooshed,\n};\n"
  },
  {
    "path": "scripts/ts-smoosh/test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst fs = require(\"fs\");\nconst assert = require(\"assert\").strict;\nconst path = require(\"path\");\n\nconst { returnSmooshed } = require(\"./smoosh\");\n\nconst args = process.argv.slice(2);\n\nconst dirs = args.length > 0 ? args : fs.readdirSync(\"./tests\").map(f => `./tests/${f}`);\n\ndirs.forEach((dir) => {\n  const files = fs.readdirSync(dir);\n\n  files\n    .filter((file) => file.endsWith(\".js\"))\n    .map((file) => file.substr(0, file.length - 3))\n    .forEach((file) => {\n      const fullFile = path.resolve(dir, file);\n      console.log(`Testing ${fullFile}.js`);\n      const smooshed = returnSmooshed(fullFile);\n\n      const target = fs.readFileSync(fullFile + \".tsx\", \"utf8\");\n      assert.equal(smooshed, target);\n    });\n});\n\nconsole.log(\"Done\");\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/01-functions/a.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport function foo(\n  bar: string,\n  baz: number\n): {boo: number};\n\nexport function typeParams<A, B>(a: A, b: B): B;\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/01-functions/a.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * A function with a JSDoc type import that matches its name\n * @type {typeof import('./a').foo}\n */\n export function foo(\n  bar,\n  baz\n)  {\n  return {boo: baz}\n}\n\n// A function with no type import\nexport function typeParams(a, b) {\n  return a + b;\n}\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/01-functions/a.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * A function with a JSDoc type import that matches its name\n * @type {typeof import('./a').foo}\n */\nexport function foo(bar: string, baz: number): {\n    boo: number;\n} {\n    return { boo: baz };\n}\n// A function with no type import\nexport function typeParams<A, B>(a: A, b: B): B {\n    return a + b;\n}\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/02-imports/b.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport type MyFn = (a: number, b: number) => number\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/02-imports/c.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * A function with a JSDoc type import that is different from its name\n * @type {typeof import('./b').MyFn}\n */\nfunction c(a, b) {\n  console.log('haha hi')\n  return a + b;\n}\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/02-imports/c.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * A function with a JSDoc type import that is different from its name\n * @type {typeof import('./b').MyFn}\n */\nfunction c(a: number, b: number): number {\n    console.log(\"haha hi\");\n    return a + b;\n}\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/03-const/e.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// hi\nexport const x: number;\n\nexport const add: (a: number, b: number) => number;\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/03-const/e.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const x = 3;\n\nexport const add = (a, b) => a + b;\n"
  },
  {
    "path": "scripts/ts-smoosh/tests/03-const/e.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const x: number = 3;\nexport const add: (a: number, b: number) => number = (a, b) => a + b;\n"
  },
  {
    "path": "src/actions/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/actions/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/actions\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@kepler.gl/cloud-providers\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/layers\": \"3.2.6\",\n    \"@kepler.gl/processors\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@reduxjs/toolkit\": \"^1.7.2\",\n    \"@types/lodash\": \"4.17.5\",\n    \"@types/react-redux\": \"^7.1.23\",\n    \"@types/redux-actions\": \"^2.6.2\",\n    \"lodash\": \"4.17.21\",\n    \"react-palm\": \"^3.3.8\",\n    \"react-redux\": \"^8.0.5\",\n    \"redux\": \"^4.2.1\",\n    \"redux-actions\": \"^2.2.1\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/actions/src/action-types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const ACTION_PREFIX = '@@kepler.gl/';\n\n/**\n * Kepler.gl action types, can be listened by reducers to perform additional tasks whenever an action is called in kepler.gl\n * @constant\n * @type {Object}\n * @public\n *\n * @example\n * // store.js\n * import {handleActions} from 'redux-actions';\n * import {createStore, combineReducers, applyMiddleware} from 'redux';\n * import {taskMiddleware} from 'react-palm/tasks';\n *\n * import keplerGlReducer from '@kepler.gl/reducers';\n * import {ActionTypes} from '@kepler.gl/actions';\n *\n * const appReducer = handleActions({\n *   // listen on kepler.gl map update action to store a copy of viewport in app state\n *   [ActionTypes.UPDATE_MAP]: (state, action) => ({\n *     ...state,\n *     viewport: action.payload\n *   }),\n * }, {});\n *\n * const reducers = combineReducers({\n *   app: appReducer,\n *   keplerGl: keplerGlReducer\n * });\n *\n * export default createStore(reducers, {}, applyMiddleware(taskMiddleware))\n */\nexport const ActionTypes = {\n  // identity action\n  REGISTER_ENTRY: `${ACTION_PREFIX}REGISTER_ENTRY`,\n  DELETE_ENTRY: `${ACTION_PREFIX}DELETE_ENTRY`,\n  RENAME_ENTRY: `${ACTION_PREFIX}RENAME_ENTRY`,\n\n  // visState\n  ADD_DATA: `${ACTION_PREFIX}ADD_DATA`,\n  ADD_FILTER: `${ACTION_PREFIX}ADD_FILTER`,\n  CREATE_OR_UPDATE_FILTER: `${ACTION_PREFIX}CREATE_OR_UPDATE_FILTER`,\n  CREATE_NEW_DATASET_SUCCESS: `${ACTION_PREFIX}CREATE_NEW_DATASET_SUCCESS`,\n  ADD_LAYER: `${ACTION_PREFIX}ADD_LAYER`,\n  APPLY_FILTER_CONFIG: `${ACTION_PREFIX}APPLY_FILTER_CONFIG`,\n  APPLY_LAYER_CONFIG: `${ACTION_PREFIX}APPLY_LAYER_CONFIG`,\n  DUPLICATE_LAYER: `${ACTION_PREFIX}DUPLICATE_LAYER`,\n  INTERACTION_CONFIG_CHANGE: `${ACTION_PREFIX}INTERACTION_CONFIG_CHANGE`,\n  LAYER_CONFIG_CHANGE: `${ACTION_PREFIX}LAYER_CONFIG_CHANGE`,\n  LAYER_SET_IS_VALID: `${ACTION_PREFIX}LAYER_SET_IS_VALID`,\n  LAYER_VISUAL_CHANNEL_CHANGE: `${ACTION_PREFIX}LAYER_VISUAL_CHANNEL_CHANGE`,\n  LAYER_TYPE_CHANGE: `${ACTION_PREFIX}LAYER_TYPE_CHANGE`,\n  LAYER_VIS_CONFIG_CHANGE: `${ACTION_PREFIX}LAYER_VIS_CONFIG_CHANGE`,\n  LAYER_TOGGLE_VISIBILITY: `${ACTION_PREFIX}LAYER_TOGGLE_VISIBILITY`,\n  LAYER_TEXT_LABEL_CHANGE: `${ACTION_PREFIX}LAYER_TEXT_LABEL_CHANGE`,\n  LAYER_HOVER: `${ACTION_PREFIX}LAYER_HOVER`,\n  LAYER_CLICK: `${ACTION_PREFIX}LAYER_CLICK`,\n  MAP_CLICK: `${ACTION_PREFIX}MAP_CLICK`,\n  MOUSE_MOVE: `${ACTION_PREFIX}MOUSE_MOVE`,\n  REMOVE_FILTER: `${ACTION_PREFIX}REMOVE_FILTER`,\n  REMOVE_LAYER: `${ACTION_PREFIX}REMOVE_LAYER`,\n  REMOVE_DATASET: `${ACTION_PREFIX}REMOVE_DATASET`,\n  REORDER_LAYER: `${ACTION_PREFIX}REORDER_LAYER`,\n  SET_FILTER: `${ACTION_PREFIX}SET_FILTER`,\n  SET_FILTER_ANIMATION_TIME: `${ACTION_PREFIX}SET_FILTER_ANIMATION_TIME`,\n  SET_FILTER_ANIMATION_TIME_CONFIG: `${ACTION_PREFIX}SET_FILTER_ANIMATION_TIME_CONFIG`,\n  SET_FILTER_ANIMATION_WINDOW: `${ACTION_PREFIX}SET_FILTER_ANIMATION_WINDOW`,\n  SHOW_DATASET_TABLE: `${ACTION_PREFIX}SHOW_DATASET_TABLE`,\n  UPDATE_LAYER_BLENDING: `${ACTION_PREFIX}UPDATE_LAYER_BLENDING`,\n  UPDATE_OVERLAY_BLENDING: `${ACTION_PREFIX}UPDATE_OVERLAY_BLENDING`,\n  UPDATE_VIS_DATA: `${ACTION_PREFIX}UPDATE_VIS_DATA`,\n  RENAME_DATASET: `${ACTION_PREFIX}RENAME_DATASET`,\n  UPDATE_DATASET_PROPS: `${ACTION_PREFIX}UPDATE_DATASET_PROPS`,\n  TOGGLE_FILTER_ANIMATION: `${ACTION_PREFIX}TOGGLE_FILTER_ANIMATION`,\n  UPDATE_FILTER_ANIMATION_SPEED: `${ACTION_PREFIX}UPDATE_FILTER_ANIMATION_SPEED`,\n  PLAY_ANIMATION: `${ACTION_PREFIX}PLAY_ANIMATION`,\n  SET_ANIMATION_CONFIG: `${ACTION_PREFIX}SET_ANIMATION_CONFIG`,\n  SET_LAYER_ANIMATION_TIME: `${ACTION_PREFIX}SET_LAYER_ANIMATION_TIME`,\n  SET_LAYER_ANIMATION_TIME_CONFIG: `${ACTION_PREFIX}SET_LAYER_ANIMATION_TIME_CONFIG`,\n  UPDATE_ANIMATION_SPEED: `${ACTION_PREFIX}UPDATE_ANIMATION_SPEED`,\n  UPDATE_LAYER_ANIMATION_SPEED: `${ACTION_PREFIX}UPDATE_LAYER_ANIMATION_SPEED`,\n  TOGGLE_LAYER_ANIMATION: `${ACTION_PREFIX}TOGGLE_LAYER_ANIMATION`,\n  TOGGLE_LAYER_ANIMATION_CONTROL: `${ACTION_PREFIX}TOGGLE_LAYER_ANIMATION_CONTROL`,\n  TOGGLE_LAYER_CONFIG_ACTIVE: `${ACTION_PREFIX}TOGGLE_LAYER_CONFIG_ACTIVE`,\n  SET_FILTER_VIEW: `${ACTION_PREFIX}SET_FILTER_VIEW`,\n  TOGGLE_FILTER_FEATURE: `${ACTION_PREFIX}TOGGLE_FILTER_FEATURE`,\n  TOGGLE_LAYER_FOR_MAP: `${ACTION_PREFIX}TOGGLE_LAYER_FOR_MAP`,\n  SET_FILTER_PLOT: `${ACTION_PREFIX}SET_FILTER_PLOT`,\n  LOAD_FILES: `${ACTION_PREFIX}LOAD_FILES`,\n  LOAD_NEXT_FILE: `${ACTION_PREFIX}LOAD_NEXT_FILE`,\n  LOAD_BATCH_DATA_SUCCESS: `${ACTION_PREFIX}LOAD_BATCH_DATA_SUCCESS`,\n  LOAD_FILE_STEP_SUCCESS: `${ACTION_PREFIX}LOAD_FILE_STEP_SUCCESS`,\n  LOAD_FILES_ERR: `${ACTION_PREFIX}LOAD_FILES_ERR`,\n  LOAD_FILES_SUCCESS: `${ACTION_PREFIX}LOAD_FILES_SUCCESS`,\n  LAYER_COLOR_UI_CHANGE: `${ACTION_PREFIX}LAYER_COLOR_UI_CHANGE`,\n  TOGGLE_FEATURE_LAYER: `${ACTION_PREFIX}TOGGLE_FEATURE_LAYER`,\n  APPLY_CPU_FILTER: `${ACTION_PREFIX}APPLY_CPU_FILTER`,\n  SET_MAP_INFO: `${ACTION_PREFIX}SET_MAP_INFO`,\n  SORT_TABLE_COLUMN: `${ACTION_PREFIX}SORT_TABLE_COLUMN`,\n  PIN_TABLE_COLUMN: `${ACTION_PREFIX}PIN_TABLE_COLUMN`,\n  COPY_TABLE_COLUMN: `${ACTION_PREFIX}COPY_TABLE_COLUMN`,\n  SET_COLUMN_DISPLAY_FORMAT: `${ACTION_PREFIX}SET_COLUMN_DISPLAY_FORMAT`,\n  NEXT_FILE_BATCH: `${ACTION_PREFIX}NEXT_FILE_BATCH`,\n  PROCESS_FILE_CONTENT: `${ACTION_PREFIX}PROCESS_FILE_CONTENT`,\n  UPDATE_TABLE_COLOR: `${ACTION_PREFIX}UPDATE_TABLE_COLOR`,\n  ADD_EFFECT: `${ACTION_PREFIX}ADD_EFFECT`,\n  REORDER_EFFECT: `${ACTION_PREFIX}REORDER_EFFECT`,\n  REMOVE_EFFECT: `${ACTION_PREFIX}REMOVE_EFFECT`,\n  UPDATE_EFFECT: `${ACTION_PREFIX}UPDATE_EFFECT`,\n\n  // mapState\n  UPDATE_MAP: `${ACTION_PREFIX}UPDATE_MAP`,\n  FIT_BOUNDS: `${ACTION_PREFIX}FIT_BOUNDS`,\n  TOGGLE_PERSPECTIVE: `${ACTION_PREFIX}TOGGLE_PERSPECTIVE`,\n  TOGGLE_FULLSCREEN: `${ACTION_PREFIX}TOGGLE_FULLSCREEN`,\n  TOGGLE_SPLIT_MAP: `${ACTION_PREFIX}TOGGLE_SPLIT_MAP`,\n  TOGGLE_SPLIT_MAP_VIEWPORT: `${ACTION_PREFIX}TOGGLE_SPLIT_MAP_VIEWPORT`,\n\n  // mapStyle\n  MAP_CONFIG_CHANGE: `${ACTION_PREFIX}MAP_CONFIG_CHANGE`,\n  SET_DEFAULT_MAP_STYLE: `${ACTION_PREFIX}SET_DEFAULT_MAP_STYLE`,\n  MAP_STYLE_CHANGE: `${ACTION_PREFIX}MAP_STYLE_CHANGE`,\n  LOAD_MAP_STYLES: `${ACTION_PREFIX}LOAD_MAP_STYLES`,\n  LOAD_MAP_STYLE_ERR: `${ACTION_PREFIX}LOAD_MAP_STYLE_ERR`,\n  INPUT_MAP_STYLE: `${ACTION_PREFIX}INPUT_MAP_STYLE`,\n  LOAD_CUSTOM_MAP_STYLE: `${ACTION_PREFIX}LOAD_CUSTOM_MAP_STYLE`,\n  ADD_CUSTOM_MAP_STYLE: `${ACTION_PREFIX}ADD_CUSTOM_MAP_STYLE`,\n  EDIT_CUSTOM_MAP_STYLE: `${ACTION_PREFIX}EDIT_CUSTOM_MAP_STYLE`,\n  REMOVE_CUSTOM_MAP_STYLE: `${ACTION_PREFIX}REMOVE_CUSTOM_MAP_STYLE`,\n  REQUEST_MAP_STYLES: `${ACTION_PREFIX}REQUEST_MAP_STYLES`,\n  SET_3D_BUILDING_COLOR: `${ACTION_PREFIX}SET_3D_BUILDING_COLOR`,\n  SET_BACKGROUND_COLOR: `${ACTION_PREFIX}SET_BACKGROUND_COLOR`,\n\n  // uiState\n  TOGGLE_SIDE_PANEL: `${ACTION_PREFIX}TOGGLE_SIDE_PANEL`,\n  TOGGLE_MODAL: `${ACTION_PREFIX}TOGGLE_MODAL`,\n  SHOW_EXPORT_DROPDOWN: `${ACTION_PREFIX}SHOW_EXPORT_DROPDOWN`,\n  HIDE_EXPORT_DROPDOWN: `${ACTION_PREFIX}HIDE_EXPORT_DROPDOWN`,\n  TOGGLE_SIDE_PANEL_CLOSE_BUTTON: `${ACTION_PREFIX}TOGGLE_SIDE_PANEL_CLOSE_BUTTON`,\n  OPEN_DELETE_MODAL: `${ACTION_PREFIX}OPEN_DELETE_MODAL`,\n  TOGGLE_MAP_CONTROL: `${ACTION_PREFIX}TOGGLE_MAP_CONTROL`,\n  SET_MAP_CONTROL_VISIBILITY: `${ACTION_PREFIX}SET_MAP_CONTROL_VISIBILITY`,\n  SET_MAP_CONTROL_SETTINGS: `${ACTION_PREFIX}SET_MAP_CONTROL_SETTINGS`,\n  ADD_NOTIFICATION: `${ACTION_PREFIX}ADD_NOTIFICATION`,\n  REMOVE_NOTIFICATION: `${ACTION_PREFIX}REMOVE_NOTIFICATION`,\n  SET_LOCALE: `${ACTION_PREFIX}SET_LOCALE`,\n  LAYER_FILTERED_ITEMS_CHANGE: `${ACTION_PREFIX}LAYER_FILTERED_ITEMS_CHANGE`,\n  WMS_FEATURE_INFO: `${ACTION_PREFIX}WMS_FEATURE_INFO`,\n  SYNC_TIME_FILTER_WITH_LAYER_TIMELINE: `${ACTION_PREFIX}SYNC_TIME_FILTER_WITH_LAYER_TIMELINE`,\n  SYNC_TIME_FILTER_TIMELINE_MODE: `${ACTION_PREFIX}SYNC_TIME_FILTER_TIMELINE_MODE`,\n  TOGGLE_PANEL_LIST_VIEW: `${ACTION_PREFIX}TOGGLE_PANEL_LIST_VIEW`,\n  SET_LOADING_INDICATOR: `${ACTION_PREFIX}SET_LOADING_INDICATOR`,\n\n  // uiState > export image\n  SET_EXPORT_IMAGE_SETTING: `${ACTION_PREFIX}SET_EXPORT_IMAGE_SETTING`,\n  START_EXPORTING_IMAGE: `${ACTION_PREFIX}START_EXPORTING_IMAGE`,\n  SET_EXPORT_IMAGE_DATA_URI: `${ACTION_PREFIX}SET_EXPORT_IMAGE_DATA_URI`,\n  SET_EXPORT_IMAGE_ERROR: `${ACTION_PREFIX}SET_EXPORT_IMAGE_ERROR`,\n  CLEANUP_EXPORT_IMAGE: `${ACTION_PREFIX}CLEANUP_EXPORT_IMAGE`,\n\n  // uiState > export video (state only, via hubble.gl)\n  SET_EXPORT_VIDEO_SETTING: `${ACTION_PREFIX}SET_EXPORT_VIDEO_SETTING`,\n\n  // uiState > export data\n  SET_EXPORT_SELECTED_DATASET: `${ACTION_PREFIX}SET_EXPORT_SELECTED_DATASET`,\n  SET_EXPORT_DATA_TYPE: `${ACTION_PREFIX}SET_EXPORT_DATA_TYPE`,\n  SET_EXPORT_FILTERED: `${ACTION_PREFIX}SET_EXPORT_FILTERED`,\n  SET_EXPORT_DATA: `${ACTION_PREFIX}SET_EXPORT_DATA`,\n\n  // uiState > export map\n  SET_EXPORT_MAP_FORMAT: `${ACTION_PREFIX}SET_EXPORT_MAP_FORMAT`,\n  SET_USER_MAPBOX_ACCESS_TOKEN: `${ACTION_PREFIX}SET_USER_MAPBOX_ACCESS_TOKEN`,\n  SET_EXPORT_MAP_HTML_MODE: `${ACTION_PREFIX}SET_EXPORT_MAP_HTML_MODE`,\n\n  // uiState > editor\n  SET_EDITOR_MODE: `${ACTION_PREFIX}SET_EDITOR_MODE`,\n  SET_SELECTED_FEATURE: `${ACTION_PREFIX}SET_SELECTED_FEATURE`,\n\n  // all\n  INIT: `${ACTION_PREFIX}INIT`,\n  ADD_DATA_TO_MAP: `${ACTION_PREFIX}ADD_DATA_TO_MAP`,\n  RECEIVE_MAP_CONFIG: `${ACTION_PREFIX}RECEIVE_MAP_CONFIG`,\n  RESET_MAP_CONFIG: `${ACTION_PREFIX}RESET_MAP_CONFIG`,\n  REPLACE_DATA_IN_MAP: `${ACTION_PREFIX}REPLACE_DATA_IN_MAP`,\n\n  // geo-operations\n  SET_FEATURES: `${ACTION_PREFIX}SET_FEATURES`,\n  SET_POLYGON_FILTER_LAYER: `${ACTION_PREFIX}SET_POLYGON_FILTER_LAYER`,\n  DELETE_FEATURE: `${ACTION_PREFIX}DELETE_FEATURE`,\n  TOGGLE_EDITOR_VISIBILITY: `${ACTION_PREFIX}TOGGLE_EDITOR_VISIBILITY`,\n\n  // storage\n  START_SAVE_STORAGE: `${ACTION_PREFIX}START_SAVE_STORAGE`\n};\n\n// eslint-disable-next-line prettier/prettier\nconst assignType = <T>(obj: T): {[K in keyof T]: `${typeof ACTION_PREFIX}${string & K}`} =>\n  obj as any;\n\nexport default assignType(ActionTypes);\n"
  },
  {
    "path": "src/actions/src/action-wrapper.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const FORWARD = '@redux-forward/FORWARD';\nexport const ADDRESS_PREFIX = '@@KG_';\nimport {Dispatch} from 'redux';\nimport {ActionTypes} from './action-types';\n\nimport curry from 'lodash/curry';\n\ninterface IKeplerGlAction {\n  type: (typeof ActionTypes)[keyof typeof ActionTypes];\n  // TODO: Construct a type depending on the payload\n  payload?: any;\n  meta?: {\n    _forward_?: string;\n    _addr_?: string;\n    _id_?: string;\n    [key: string]: any;\n  };\n}\n\nexport const getActionForwardAddress = (id: string) => `${ADDRESS_PREFIX}${id.toUpperCase()}`;\n\n/**\n * Wrap an action into a forward action that only modify the state of a specific\n * kepler.gl instance. kepler.gl reducer will look for signatures in the action to\n * determine whether it needs to be forwarded to a specific instance reducer.\n *\n * wrapTo can be curried. You can create a curried action wrapper by only supply the `id` argument\n *\n * A forward action looks like this\n * ```js\n *  {\n *    type: \"@@kepler.gl/LAYER_CONFIG_CHANGE\",\n *    payload: {\n *      type: '@@kepler.gl/LAYER_CONFIG_CHANGE',\n *      payload: {},\n *      meta: {\n *       // id of instance\n *        _id_: id\n *       // other meta\n *      }\n *    },\n *    meta: {\n *      _forward_: '@redux-forward/FORWARD',\n *      _addr_: '@@KG_id'\n *    }\n *  };\n * ```\n *\n * @memberof forwardActions\n * @param {string} id - The id to forward to\n * @param {Object} action - the action object {type: string, payload: *}\n * @returns {{type: string, payload: {type: string, payload: *, meta: {_id_: string}, meta: {_forward_: string, _addr_: string}}}}\n * @public\n * @example\n *\n * import {wrapTo, togglePerspective} from '@kepler.gl/actions';\n *\n * // This action will only dispatch to the KeplerGl instance with `id: map_1`\n * this.props.dispatch(wrapTo('map_1', togglePerspective()));\n *\n * // You can also create a curried action for each instance\n * const wrapToMap1 = wrapTo('map_1');\n * this.props.dispatch(wrapToMap1(togglePerspective()));\n */\nexport const wrapTo = curry((id: string, action: IKeplerGlAction) => ({\n  // keep original action.type\n  type: action.type,\n\n  // actual action\n  payload: {\n    ...action,\n    meta: {\n      ...action.meta,\n      _id_: id\n    }\n  },\n\n  // add forward signature to meta\n  meta: {\n    ...(action.meta || {}),\n    _forward_: FORWARD,\n    _addr_: getActionForwardAddress(id)\n  }\n}));\n\n/**\n * Whether an action is a forward action\n * @memberof forwardActions\n * @param {Object} action - the action object\n * @returns {boolean} boolean - whether the action is a forward action\n * @public\n */\nexport const isForwardAction = (action: IKeplerGlAction) => {\n  return Boolean(action && action.meta && action.meta._forward_ === FORWARD);\n};\n\n/**\n * Unwrap an action\n * @memberof forwardActions\n * @param {Object} action - the action object\n * @returns {Object} - unwrapped action\n * @public\n */\nexport const unwrap = (action: IKeplerGlAction) =>\n  isForwardAction(action) ? unwrap(action.payload) : action;\n\n/**\n * Given an id, returns the action for that id.\n * If the action is not a forward action, return the action\n * @memberof forwardActions\n * @param {String} id\n * @param {Object} action\n * @private\n */\nexport const _actionFor = (id: string, action: IKeplerGlAction) =>\n  isForwardAction(action)\n    ? action.meta?._addr_ === getActionForwardAddress(id)\n      ? action.payload\n      : {}\n    : action;\n\n/**\n * Returns an action dispatcher that wraps and forwards the actions to a specific instance\n * @memberof forwardActions\n * @param {string} id - instance id\n * @param {Function} dispatch - action dispatcher\n * @public\n * @example\n *\n * // action and forward dispatcher\n * import {toggleSplitMap, forwardTo} from '@kepler.gl/actions';\n * import {connect} from 'react-redux';\n *\n * const MapContainer = props => (\n *  <div>\n *   <button onClick={() => props.keplerGlDispatch(toggleSplitMap())}/>\n *  </div>\n * )\n *\n * const mapDispatchToProps = (dispatch, props) => ({\n *  dispatch,\n *  keplerGlDispatch: forwardTo(‘foo’, dispatch)\n * });\n *\n * export default connect(\n *  state => state,\n *  mapDispatchToProps\n * )(MapContainer);\n */\nexport const forwardTo =\n  (id: string, dispatch: Dispatch<IKeplerGlAction>) => (action: IKeplerGlAction) =>\n    dispatch(wrapTo(id, action));\n\n/**\n * Update the state of a kepler.gl instance\n * @memberof forwardActions\n * @param {Object} state\n * @param {string} id\n * @param {Object} nextState\n * @private\n */\nexport const _updateProperty = (state, id: string, nextState) =>\n  state[id] === nextState\n    ? state\n    : {\n        ...state,\n        [id]: nextState\n      };\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * A set of helpers to forward dispatch actions to a specific instance reducer\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst forwardActions = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {default as ActionTypes} from './action-types';\nimport {createAction} from '@reduxjs/toolkit';\n\nimport {\n  AddDataToMapOptions,\n  AddDataToMapPayload,\n  Bounds,\n  UiState,\n  ParsedConfig,\n  ProtoDataset\n} from '@kepler.gl/types';\n\ntype Handler = (...args: any) => any;\n\nexport type ActionHandler<A extends Handler> = (...args: Parameters<A>) => void;\n\nexport type ActionHandlers<T extends {[k: string]: Handler}> = {\n  [K in keyof T]: ActionHandler<T[K]>;\n};\n\n/**\n * Add data to kepler.gl reducer, prepare map with preset configuration if config is passed.\n * Kepler.gl provides a handy set of utils to parse data from different formats to the `data` object required in dataset. You rarely need to manually format the data obejct.\n *\n * Use `KeplerGlSchema.getConfigToSave` to generate a json blob of the currents instance config.\n * The config object value will always have higher precedence than the options properties.\n *\n * Kepler.gl uses `dataId` in the config to match with loaded dataset. If you pass a config object, you need\n * to match the `info.id` of your dataset to the `dataId` in each `layer`, `filter` and `interactionConfig.tooltips.fieldsToShow`\n *\n * @memberof main\n * @param {Object} data\n * @param {Array<Object>|Object} data.datasets - ***required** datasets can be a dataset or an array of datasets\n * Each dataset object needs to have `info` and `data` property.\n * @param {Object} data.datasets.info -info of a dataset\n * @param {string} data.datasets.info.id - id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n * @param {string} data.datasets.info.label - A display name of this dataset\n * @param {Object} data.datasets.data - ***required** The data object, in a tabular format with 2 properties `fields` and `rows`\n * @param {Array<Object>} data.datasets.data.fields - ***required** Array of fields,\n * @param {string} data.datasets.data.fields.name - ***required** Name of the field,\n * @param {Array<Array>} data.datasets.data.rows - ***required** Array of rows, in a tabular format with `fields` and `rows`\n *\n * @param {Object} data.options\n * @param {boolean} data.options.centerMap `default: true` if `centerMap` is set to `true` kepler.gl will\n * place the map view within the data points boundaries.  `options.centerMap` will override `config.mapState` if passed in.\n * @param {boolean} data.options.readOnly `default: false` if `readOnly` is set to `true`\n * the left setting panel will be hidden\n * @param {boolean} data.options.keepExistingConfig whether to keep exiting map data and associated layer filter  interaction config `default: false`.\n * @param {Object} data.config this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n * @public\n * @example\n *\n * // app.js\n * import {addDataToMap} from '@kepler.gl/actions';\n *\n * const sampleTripData = {\n *  fields: [\n *    {name: 'tpep_pickup_datetime', format: 'YYYY-M-D H:m:s', type: 'timestamp'},\n *    {name: 'pickup_longitude', format: '', type: 'real'},\n *    {name: 'pickup_latitude', format: '', type: 'real'}\n *  ],\n *  rows: [\n *    ['2015-01-15 19:05:39 +00:00', -73.99389648, 40.75011063],\n *    ['2015-01-15 19:05:39 +00:00', -73.97642517, 40.73981094],\n *    ['2015-01-15 19:05:40 +00:00', -73.96870422, 40.75424576],\n *  ]\n * };\n *\n * const sampleConfig = {\n *   visState: {\n *     filters: [\n *       {\n *         id: 'me',\n *         dataId: 'test_trip_data',\n *         name: 'tpep_pickup_datetime',\n *         type: 'timeRange',\n *         view: 'enlarged'\n *       }\n *     ]\n *   }\n * }\n *\n * this.props.dispatch(\n *   addDataToMap({\n *     datasets: {\n *       info: {\n *         label: 'Sample Taxi Trips in New York City',\n *         id: 'test_trip_data'\n *       },\n *       data: sampleTripData\n *     },\n *     options: {\n *       centerMap: true,\n *       readOnly: false,\n *       keepExistingConfig: false\n *     },\n *     info: {\n *       title: 'Taro and Blue',\n *       description: 'This is my map'\n *     },\n *     config: sampleConfig\n *   })\n * );\n */\nexport const addDataToMap: (data: AddDataToMapPayload) => {\n  type: typeof ActionTypes.ADD_DATA_TO_MAP;\n  payload: AddDataToMapPayload;\n} = createAction(ActionTypes.ADD_DATA_TO_MAP, (data: AddDataToMapPayload) => ({payload: data}));\n\n/**\n * Reset all sub-reducers to its initial state. This can be used to clear out all configuration in the reducer.\n * @memberof main\n * @public\n */\nexport const resetMapConfig: () => {type: typeof ActionTypes.RESET_MAP_CONFIG} = createAction(\n  ActionTypes.RESET_MAP_CONFIG\n);\n\nexport type ReceiveMapConfigPayload = {\n  config: ParsedConfig;\n  options?: AddDataToMapOptions;\n  bounds?: Bounds;\n};\n/**\n * Pass config to kepler.gl instance, prepare the state with preset configs.\n * Calling `KeplerGlSchema.parseSavedConfig` to convert saved config before passing it in is required.\n *\n * You can call `receiveMapConfig` before passing in any data. The reducer will store layer and filter config, waiting for\n * data to come in. When data arrives, you can call `addDataToMap` without passing any config, and the reducer will try to match\n * preloaded configs. This behavior is designed to allow asynchronous data loading.\n *\n * It is also useful when you want to prepare the kepler.gl instance with some preset layer and filter settings.\n * **Note** Sequence is important, `receiveMapConfig` needs to be called __before__ data is loaded. Currently kepler.gl doesn't allow calling `receiveMapConfig` after data is loaded.\n * It will reset current configuration first then apply config to it.\n * @memberof main\n * @param {Object} config - ***required** The Config Object\n * @param {Object} options - ***optional** The Option object\n * @param {boolean} options.centerMap `default: true` if `centerMap` is set to `true` kepler.gl will\n * place the map view within the data points boundaries\n * @param {boolean} options.readOnly `default: false` if `readOnly` is set to `true`\n * the left setting panel will be hidden\n * @param {boolean} options.keepExistingConfig whether to keep exiting layer filter and interaction config `default: false`.\n * @param {boolean} options.autoCreateLayers whether to automatically create layers based on dataset columns `default: true`.\n * @public\n * @example\n * import {receiveMapConfig} from '@kepler.gl/actions';\n * import KeplerGlSchema from '@kepler.gl/schemas';\n *\n * const parsedConfig = KeplerGlSchema.parseSavedConfig(config);\n * this.props.dispatch(receiveMapConfig(parsedConfig));\n */\nexport const receiveMapConfig: (\n  config: ReceiveMapConfigPayload['config'],\n  options: ReceiveMapConfigPayload['options']\n) => {\n  type: typeof ActionTypes.RECEIVE_MAP_CONFIG;\n  payload: ReceiveMapConfigPayload;\n} = createAction(\n  ActionTypes.RECEIVE_MAP_CONFIG,\n  (config: ReceiveMapConfigPayload['config'], options: ReceiveMapConfigPayload['options']) => ({\n    payload: {\n      config,\n      options\n    }\n  })\n);\n\nexport type KeplerGlInitPayload = {\n  mapboxApiAccessToken?: string;\n  mapboxApiUrl?: string;\n  mapStylesReplaceDefault?: boolean;\n  initialUiState?: Partial<UiState>;\n};\n/**\n * Initialize kepler.gl reducer. It is used to pass in `mapboxApiAccessToken` to `mapStyle` reducer.\n * @memberof main\n * @param {object} payload\n * @param payload.mapboxApiAccessToken - mapboxApiAccessToken to be saved to mapStyle reducer\n * @param payload.mapboxApiUrl - mapboxApiUrl to be saved to mapStyle reducer.\n * @param payload.mapStylesReplaceDefault - mapStylesReplaceDefault to be saved to mapStyle reducer\n * @param payload.initialUiState - initial ui state\n * @public\n */\n// @ts-expect-error\nexport const keplerGlInit: (options?: KeplerGlInitPayload) => {\n  type: typeof ActionTypes.INIT;\n  payload: KeplerGlInitPayload;\n} = createAction(ActionTypes.INIT, (payload: KeplerGlInitPayload) => ({payload}));\n\nexport type ReplaceDataToMapOptions = {\n  centerMap?: boolean;\n  keepExistingConfig?: boolean;\n  autoCreateLayers?: boolean;\n};\nexport type ReplaceDataInMapPayload = {\n  datasetToReplaceId: string;\n  datasetToUse: ProtoDataset;\n  options?: ReplaceDataToMapOptions;\n};\n\n/**\n * Initialize kepler.gl reducer. It is used to pass in `mapboxApiAccessToken` to `mapStyle` reducer.\n * @memberof main\n * @param payload\n * @param payload.datasetToReplaceId - mapboxApiAccessToken to be saved to mapStyle reducer\n * @param payload.datasetToUse - mapboxApiUrl to be saved to mapStyle reducer.\n * @public\n */\nexport const replaceDataInMap: (payload: ReplaceDataInMapPayload) => {\n  type: typeof ActionTypes.REPLACE_DATA_IN_MAP;\n  payload: ReplaceDataInMapPayload;\n} = createAction(ActionTypes.REPLACE_DATA_IN_MAP, (payload: ReplaceDataInMapPayload) => ({\n  payload\n}));\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * Main kepler.gl actions, these actions handles loading data and config into kepler.gl reducer. These actions\n * is listened by all subreducers,\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst main = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/identity-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from '@reduxjs/toolkit';\nimport {default as ActionTypes} from './action-types';\nimport {UiState} from '@kepler.gl/types';\n\nexport type RegisterEntryUpdaterAction = {\n  payload: {\n    id: string;\n    mint?: boolean;\n    mapboxApiAccessToken?: string;\n    mapboxApiUrl?: string;\n    mapStylesReplaceDefault?: boolean;\n    initialUiState?: Partial<UiState>;\n  };\n};\n\nexport type RenameEntryUpdaterAction = {\n  payload: {\n    oldId: string;\n    newId: string;\n  };\n};\n/**\n *\n * Add a new kepler.gl instance in `keplerGlReducer`. This action is called under-the-hood when a `KeplerGl` component is **mounted** to the dom.\n * Note that if you dispatch actions such as adding data to a kepler.gl instance before the React component is mounted, the action will not be\n * performed. Instance reducer can only handle actions when it is instantiated.\n * @memberof rootActions\n * @param payload\n * @param payload.id - ***required** The id of the instance\n * @param payload.mint - Whether to use a fresh empty state, when `mint: true` it will *always* load a fresh state when the component is re-mounted.\n * When `mint: false` it will register with existing instance state under the same `id`, when the component is unmounted then mounted again. Default: `true`\n * @param payload.mapboxApiAccessToken - mapboxApiAccessToken to be saved in `map-style` reducer.\n * @param payload.mapboxApiUrl - mapboxApiUrl to be saved in `map-style` reducer.\n * @param payload.mapStylesReplaceDefault - mapStylesReplaceDefault to be saved in `map-style` reducer.\n * @param payload.initialUiState - initial ui state\n * @public\n */\nexport const registerEntry: (entry: RegisterEntryUpdaterAction['payload']) => {\n  type: typeof ActionTypes.REGISTER_ENTRY;\n  payload: RegisterEntryUpdaterAction['payload'];\n} = createAction(ActionTypes.REGISTER_ENTRY, (payload: RegisterEntryUpdaterAction['payload']) => ({\n  payload\n}));\n\n/**\n *\n * Delete an instance from `keplerGlReducer`. This action is called under-the-hood when a `KeplerGl` component is **un-mounted** to the dom.\n * If `mint` is set to be `true` in the component prop, the instance state will be deleted from the root reducer. Otherwise, the root reducer will keep\n * the instance state and later transfer it to a newly mounted component with the same `id`\n * @memberof rootActions\n * @param {string} id - the id of the instance to be deleted\n * @public\n */\nexport const deleteEntry: (id: string) => {\n  type: typeof ActionTypes.DELETE_ENTRY;\n  payload: {id: string};\n} = createAction(ActionTypes.DELETE_ENTRY, (id: string) => ({payload: {id: id}}));\n\n/**\n *\n * Rename an instance in the root reducer, keep its entire state\n *\n * @memberof rootActions\n * @param {string} oldId - ***required** old id\n * @param {string} newId - ***required** new id\n * @public\n */\nexport const renameEntry: (\n  oldId: string,\n  newId: string\n) => {\n  type: typeof ActionTypes.RENAME_ENTRY;\n  payload: RenameEntryUpdaterAction['payload'];\n} = createAction(ActionTypes.RENAME_ENTRY, (oldId: string, newId: string) => ({\n  payload: {\n    oldId,\n    newId\n  }\n}));\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * Root actions managers adding and removing instances in root reducer.\n * Under-the-hood, when a `KeplerGl` component is mounted or unmounted,\n * it will automatically calls these actions to add itself to the root reducer.\n * However, sometimes the data is ready before the component is registered in the reducer,\n * in this case, you can manually call these actions or the corresponding updater to add it to the reducer.\n *\n * @public\n */\n/* eslint-disable  @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst rootActions = null;\n/* eslint-enable  @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as VisStateActions from './vis-state-actions';\nimport * as MapStateActions from './map-state-actions';\nimport * as UIStateActions from './ui-state-actions';\nimport * as MapStyleActions from './map-style-actions';\nimport * as ProviderActions from './provider-actions';\n\n// Actions\nexport * from './actions';\n\n// kepler.gl actions accessible outside component\nexport * from './action-wrapper';\nexport * from './vis-state-actions';\nexport * from './ui-state-actions';\nexport * from './map-state-actions';\nexport * from './map-style-actions';\nexport * from './identity-actions';\nexport * from './provider-actions';\n\nexport {VisStateActions, MapStateActions, UIStateActions, MapStyleActions, ProviderActions};\n\n// Dispatch\nexport {\n  _actionFor,\n  forwardTo,\n  getActionForwardAddress,\n  isForwardAction,\n  unwrap,\n  wrapTo\n} from './action-wrapper';\n\nexport {default as ActionTypes, ACTION_PREFIX} from './action-types';\nexport {ActionTypes as ProviderActionTypes} from './provider-actions';\n"
  },
  {
    "path": "src/actions/src/map-state-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from '@reduxjs/toolkit';\nimport {default as ActionTypes} from './action-types';\nimport {Bounds, Merge, Viewport} from '@kepler.gl/types';\n\nexport type TogglePerspectiveUpdaterAction = void;\n/**\n *\n * Toggle between 3d and 2d map.\n * @memberof mapStateActions\n * @public\n * @example\n * import {togglePerspective} from '@kepler.gl/actions';\n * this.props.dispatch(togglePerspective());\n */\nexport const togglePerspective: () => Merge<\n  TogglePerspectiveUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_PERSPECTIVE}\n> = createAction(ActionTypes.TOGGLE_PERSPECTIVE);\n\nexport type FitBoundsUpdaterAction = {payload: Bounds};\n/**\n * Fit map viewport to bounds\n * @memberof mapStateActions\n * @param {Array<Number>} bounds as `[lngMin, latMin, lngMax, latMax]`\n * @public\n * @example\n * import {fitBounds} from '@kepler.gl/actions';\n * this.props.dispatch(fitBounds([-122.23, 37.127, -122.11, 37.456]));\n */\nexport const fitBounds: (\n  payload: Bounds\n) => Merge<FitBoundsUpdaterAction, {type: typeof ActionTypes.FIT_BOUNDS}> = createAction(\n  ActionTypes.FIT_BOUNDS,\n  (bounds: Bounds) => ({payload: bounds})\n);\n\nexport type UpdateMapUpdaterAction = {payload: {viewport: Viewport; mapIndex?: number}};\n/**\n * Update map viewport\n * @memberof mapStateActions\n * @param {Object} viewport viewport object container one or any of these properties `width`, `height`, `latitude` `longitude`, `zoom`, `pitch`, `bearing`, `dragRotate`\n * @param {Number} [viewport.width] Width of viewport\n * @param {Number} [viewport.height] Height of viewport\n * @param {Number} [viewport.zoom] Zoom of viewport\n * @param {Number} [viewport.pitch] Camera angle in degrees (0 is straight down)\n * @param {Number} [viewport.bearing] Map rotation in degrees (0 means north is up)\n * @param {Number} [viewport.latitude] Latitude center of viewport on map in mercator projection\n * @param {Number} [viewport.longitude] Longitude Center of viewport on map in mercator projection\n * @param {boolean} [viewport.dragRotate] Whether to enable drag and rotate map into perspective viewport\n * @param {number} mapIndex Index of which map to update the viewport of\n * @public\n * @example\n * import {updateMap} from '@kepler.gl/actions';\n * this.props.dispatch(updateMap({latitude: 37.75043, longitude: -122.34679, width: 800, height: 1200}, 0));\n */\n\nexport const updateMap: (\n  viewport: Viewport,\n  mapIndex?: number\n) => Merge<UpdateMapUpdaterAction, {type: typeof ActionTypes.UPDATE_MAP}> = createAction(\n  ActionTypes.UPDATE_MAP,\n  (viewport: Viewport, mapIndex?: number) => ({\n    payload: {\n      viewport,\n      mapIndex\n    }\n  })\n);\n\nexport type ToggleSplitMapUpdaterAction = {\n  payload: number;\n};\n/**\n * Toggle between single map or split maps\n * @memberof mapStateActions\n * @param {Number} [index] index is provided, close split map at index\n * @public\n * @example\n * import {toggleSplitMap} from '@kepler.gl/actions';\n * this.props.dispatch(toggleSplitMap());\n */\nexport const toggleSplitMap: (\n  payload: number\n) => Merge<ToggleSplitMapUpdaterAction, {type: typeof ActionTypes.TOGGLE_SPLIT_MAP}> = createAction(\n  ActionTypes.TOGGLE_SPLIT_MAP,\n  (index: number) => ({payload: index})\n);\n\nexport type ToggleSplitMapViewportUpdaterAction = {\n  payload: {\n    isViewportSynced?: boolean;\n    isZoomLocked?: boolean;\n  };\n};\n\n/**\n * For split maps, toggle between having (un)synced viewports and (un)locked zooms\n * @memberof mapStateActions\n * @param {Object} syncInfo\n * @param {boolean} [syncInfo.isViewportSynced] Are the 2 split maps having synced viewports?\n * @param {boolean} [syncInfo.isZoomLocked] If split, are the zooms locked to each other or independent?\n */\nexport const toggleSplitMapViewport: (payload: {\n  isViewportSynced?: boolean;\n  isZoomLocked?: boolean;\n}) => Merge<\n  ToggleSplitMapViewportUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_SPLIT_MAP_VIEWPORT}\n> = createAction(\n  ActionTypes.TOGGLE_SPLIT_MAP_VIEWPORT,\n  (syncInfo: ToggleSplitMapViewportUpdaterAction['payload']) => ({payload: syncInfo})\n);\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * Actions handled mostly by  `mapState` reducer.\n * They manage map viewport update, toggle between 2d and 3d map,\n * toggle between single and split maps.\n *\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst mapStateActions = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/map-style-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from '@reduxjs/toolkit';\nimport {default as ActionTypes} from './action-types';\nimport {\n  InputStyle,\n  MapStyles,\n  Merge,\n  RGBColor,\n  MapState,\n  VisibleLayerGroups,\n  LayerGroup\n} from '@kepler.gl/types';\n\n/**\n * Add map style from user input to reducer and set it to current style\n * This action is called when user click confirm after putting in a valid style url in the custom map style dialog.\n * It should not be called from outside kepler.gl without a valid `inputStyle` in the `mapStyle` reducer.\n * param {void}\n * @memberof mapStyleActions\n * @public\n */\nexport const addCustomMapStyle: () => {\n  type: typeof ActionTypes.ADD_CUSTOM_MAP_STYLE;\n} = createAction(ActionTypes.ADD_CUSTOM_MAP_STYLE);\n\nexport type RemoveCustomMapStyleUpdaterAction = {\n  payload: {\n    id: string;\n  };\n};\n\n/**\n * Edit map style from user input to reducer.\n * This action is called when user clicks confirm after editing an existing custom style in the custom map style dialog.\n * It should not be called from outside kepler.gl without a valid `inputStyle` in the `mapStyle` reducer.\n * param {void}\n * @memberof mapStyleActions\n * @public\n */\nexport const editCustomMapStyle: () => {\n  type: typeof ActionTypes.EDIT_CUSTOM_MAP_STYLE;\n} = createAction(ActionTypes.EDIT_CUSTOM_MAP_STYLE);\n\n/**\n * Remove a custom map style from `state.mapStyle.mapStyles`.\n * @param id\n * @memberof mapStyleActions\n * @public\n */\nexport const removeCustomMapStyle: ({\n  id\n}: RemoveCustomMapStyleUpdaterAction['payload']) => Merge<\n  RemoveCustomMapStyleUpdaterAction,\n  {type: typeof ActionTypes.REMOVE_CUSTOM_MAP_STYLE}\n> = createAction(ActionTypes.REMOVE_CUSTOM_MAP_STYLE, ({id}) => {\n  return {\n    payload: {\n      id\n    }\n  };\n});\n\n/** INPUT_MAP_STYLE */\nexport type InputMapStyleUpdaterAction = {\n  payload: {\n    inputStyle: Partial<InputStyle>;\n    mapState?: MapState;\n  };\n};\n/**\n * Input a custom map style object\n * @memberof mapStyleActions\n * @param inputStyle\n * @param inputStyle.url - style url e.g. `'mapbox://styles/heshan/xxxxxyyyyzzz'`\n * @param inputStyle.id - style id e.g. `'custom_style_1'`\n * @param inputStyle.style - actual mapbox style json\n * @param inputStyle.label - style name\n * @param inputStyle.accessToken - mapbox access token\n * @param inputStyle.icon - icon image data url\n * @param [mapState] - mapState is optional\n * @public\n */\nexport const inputMapStyle: (\n  inputStyle: InputMapStyleUpdaterAction['payload']['inputStyle'],\n  mapState?: InputMapStyleUpdaterAction['payload']['mapState']\n) => Merge<InputMapStyleUpdaterAction, {type: typeof ActionTypes.INPUT_MAP_STYLE}> = createAction(\n  ActionTypes.INPUT_MAP_STYLE,\n  (\n    inputStyle: InputMapStyleUpdaterAction['payload']['inputStyle'],\n    mapState: InputMapStyleUpdaterAction['payload']['mapState']\n  ) => ({\n    payload: {\n      inputStyle,\n      mapState\n    }\n  })\n);\n\n/** MAP_CONFIG_CHANGE */\nexport type MapConfigChangeUpdaterAction = {\n  payload: {\n    visibleLayerGroups?: VisibleLayerGroups;\n    topLayerGroups?: VisibleLayerGroups;\n  };\n};\n/**\n * Update `visibleLayerGroups`to change layer group visibility\n * @memberof mapStyleActions\n * @param mapStyle new config `{visibleLayerGroups: {label: false, road: true, background: true}}`\n * @public\n */\nexport const mapConfigChange: (\n  mapStyle: MapConfigChangeUpdaterAction['payload']\n) => Merge<MapConfigChangeUpdaterAction, {type: typeof ActionTypes.MAP_CONFIG_CHANGE}> =\n  createAction(\n    ActionTypes.MAP_CONFIG_CHANGE,\n    (mapStyle: MapConfigChangeUpdaterAction['payload']) => ({payload: mapStyle})\n  );\n\ntype OnLoadMapStyleSuccessCallback = (payload: {styleType: string}) => any;\n\n/** REQUEST_MAP_STYLES */\nexport type RequestMapStylesUpdaterAction = {\n  payload: {\n    mapStyles: {\n      [key: string]: {\n        id: string;\n        label?: string;\n        url: string;\n        icon?: string;\n        layerGroups?: LayerGroup[];\n      };\n    };\n    onSuccess?: OnLoadMapStyleSuccessCallback;\n  };\n};\n/**\n * Request map style style object based on style.url.\n * @memberof mapStyleActions\n * @public\n */\nexport const requestMapStyles: (\n  mapStyles: RequestMapStylesUpdaterAction['payload']['mapStyles'],\n  onSuccess?: RequestMapStylesUpdaterAction['payload']['onSuccess']\n) => Merge<RequestMapStylesUpdaterAction, {type: typeof ActionTypes.REQUEST_MAP_STYLES}> =\n  createAction(\n    ActionTypes.REQUEST_MAP_STYLES,\n    (\n      mapStyles: RequestMapStylesUpdaterAction['payload']['mapStyles'],\n      onSuccess?: RequestMapStylesUpdaterAction['payload']['onSuccess']\n    ) => ({payload: {mapStyles, onSuccess}})\n  );\n\n/** LOAD_MAP_STYLES */\nexport type LoadMapStylesUpdaterAction = {\n  payload: {\n    newStyles: MapStyles;\n    onSuccess?: OnLoadMapStyleSuccessCallback;\n  };\n};\n/**\n * Callback when load map style success\n * @memberof mapStyleActions\n * @param newStyles a `{[id]: style}` mapping\n * @public\n */\nexport const loadMapStyles: (\n  newStyles: LoadMapStylesUpdaterAction['payload']['newStyles'],\n  onSuccess?: LoadMapStylesUpdaterAction['payload']['onSuccess']\n) => Merge<LoadMapStylesUpdaterAction, {type: typeof ActionTypes.LOAD_MAP_STYLES}> = createAction(\n  ActionTypes.LOAD_MAP_STYLES,\n  (\n    newStyles: LoadMapStylesUpdaterAction['payload']['newStyles'],\n    onSuccess?: LoadMapStylesUpdaterAction['payload']['onSuccess']\n  ) => ({payload: {newStyles, onSuccess}})\n);\n\n/** LOAD_MAP_STYLE_ERR */\nexport type LoadMapStyleErrUpdaterAction = {\n  payload: {\n    ids: string[];\n    error: Error;\n  };\n};\n/**\n * Callback when load map style error\n * @memberof mapStyleActions\n * @param ids\n * @param error\n * @public\n */\nexport const loadMapStyleErr: (\n  ids: LoadMapStyleErrUpdaterAction['payload']['ids'],\n  error: LoadMapStyleErrUpdaterAction['payload']['error']\n) => Merge<LoadMapStyleErrUpdaterAction, {type: typeof ActionTypes.LOAD_MAP_STYLE_ERR}> =\n  createAction(\n    ActionTypes.LOAD_MAP_STYLE_ERR,\n    (\n      ids: LoadMapStyleErrUpdaterAction['payload']['ids'],\n      error: LoadMapStyleErrUpdaterAction['payload']['error']\n    ) => ({payload: {ids, error}})\n  );\n\n/** MAP_STYLE_CHANGE */\nexport type MapStyleChangeUpdaterAction = {\n  payload: {\n    styleType: string;\n    onSuccess?: OnLoadMapStyleSuccessCallback;\n  };\n};\n/**\n * Change to another map style. The selected style should already been loaded into `mapStyle.mapStyles`\n * @memberof mapStyleActions\n * @param styleType the style to change to\n * @param onSuccess optional success callback function when an asynchronous basemap syle has loaded\n * @public\n */\nexport const mapStyleChange: (\n  styleType: MapStyleChangeUpdaterAction['payload']['styleType'],\n  onSuccess?: MapStyleChangeUpdaterAction['payload']['onSuccess']\n) => Merge<MapStyleChangeUpdaterAction, {type: typeof ActionTypes.MAP_STYLE_CHANGE}> = createAction(\n  ActionTypes.MAP_STYLE_CHANGE,\n  (\n    styleType: MapStyleChangeUpdaterAction['payload']['styleType'],\n    onSuccess?: MapStyleChangeUpdaterAction['payload']['onSuccess']\n  ) => ({payload: {styleType, onSuccess}})\n);\n\n/** LOAD_CUSTOM_MAP_STYLE */\nexport type LoadCustomMapStyleUpdaterAction = {\n  payload: {\n    icon?: string;\n    style?: object;\n    error?: object | boolean;\n  };\n};\n/**\n * Callback when a custom map style object is received\n * @memberof mapStyleActions\n * @param customMapStyle\n * @param customMapStyle.icon\n * @param customMapStyle.style\n * @param customMapStyle.error\n * @public\n */\nexport const loadCustomMapStyle: (\n  customMapStyle: LoadCustomMapStyleUpdaterAction['payload']\n) => Merge<LoadCustomMapStyleUpdaterAction, {type: typeof ActionTypes.LOAD_CUSTOM_MAP_STYLE}> =\n  createAction(\n    ActionTypes.LOAD_CUSTOM_MAP_STYLE,\n    (customMapStyle: LoadCustomMapStyleUpdaterAction['payload']) => ({payload: customMapStyle})\n  );\n\n/** SET_3D_BUILDING_COLOR */\nexport type Set3dBuildingColorUpdaterAction = {\n  payload: RGBColor;\n};\n// SET_3D_BUILDING_COLOR\n/**\n * Set 3d building layer group color\n * @memberof mapStyleActions\n * @param color - [r, g, b]\n * @public\n */\nexport const set3dBuildingColor: (\n  color: Set3dBuildingColorUpdaterAction['payload']\n) => Merge<Set3dBuildingColorUpdaterAction, {type: typeof ActionTypes.SET_3D_BUILDING_COLOR}> =\n  createAction(\n    ActionTypes.SET_3D_BUILDING_COLOR,\n    (color: Set3dBuildingColorUpdaterAction['payload']) => ({payload: color})\n  );\n\n/** SET_BACKGROUND_COLOR */\nexport type SetBackgroundColorUpdaterAction = {\n  payload: RGBColor;\n};\n\n/**\n * Set background color\n * @memberof mapStyleActions\n * @param color - [r, g, b]\n * @public\n */\nexport const setBackgroundColor: (\n  color: SetBackgroundColorUpdaterAction['payload']\n) => Merge<SetBackgroundColorUpdaterAction, {type: typeof ActionTypes.SET_BACKGROUND_COLOR}> =\n  createAction(ActionTypes.SET_BACKGROUND_COLOR, (color: RGBColor) => ({payload: color}));\n\n/**\n * Actions handled mostly by  `mapStyle` reducer.\n * They manage the display of base map, such as loading and receiving base map styles,\n * hiding and showing map layers, user input of custom map style url.\n *\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst mapStyleActions = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/provider-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from '@reduxjs/toolkit';\nimport {ACTION_PREFIX} from './action-types';\nimport {\n  ExportFileOptions,\n  ExportFileToCloudPayload,\n  OnErrorCallBack,\n  OnSuccessCallBack\n} from '@kepler.gl/types';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\n// eslint-disable-next-line prettier/prettier\nconst assignType = <T>(obj: T): {[K in keyof T]: `${typeof ACTION_PREFIX}${string & K}`} =>\n  obj as any;\nexport const ActionTypes = assignType({\n  EXPORT_FILE_TO_CLOUD: `${ACTION_PREFIX}EXPORT_FILE_TO_CLOUD`,\n  EXPORT_FILE_SUCCESS: `${ACTION_PREFIX}EXPORT_FILE_SUCCESS`,\n  EXPORT_FILE_ERROR: `${ACTION_PREFIX}EXPORT_FILE_ERROR`,\n  RESET_PROVIDER_STATUS: `${ACTION_PREFIX}RESET_PROVIDER_STATUS`,\n  POST_SAVE_LOAD_SUCCESS: `${ACTION_PREFIX}POST_SAVE_LOAD_SUCCESS`,\n  LOAD_CLOUD_MAP: `${ACTION_PREFIX}LOAD_CLOUD_MAP`,\n  LOAD_CLOUD_MAP_SUCCESS: `${ACTION_PREFIX}LOAD_CLOUD_MAP_SUCCESS`,\n  LOAD_CLOUD_MAP_SUCCESS_2: `${ACTION_PREFIX}LOAD_CLOUD_MAP_SUCCESS_2`,\n  LOAD_CLOUD_MAP_ERROR: `${ACTION_PREFIX}LOAD_CLOUD_MAP_ERROR`\n});\n\n/**\n * Call provider to upload file to cloud\n * @param mapData\n * @param provider\n * @param options\n * @param onSuccess\n * @param onError\n * @param closeModal\n */\nexport const exportFileToCloud: (p: ExportFileToCloudPayload) => {\n  type: typeof ActionTypes.EXPORT_FILE_TO_CLOUD;\n  payload: ExportFileToCloudPayload;\n} = createAction(ActionTypes.EXPORT_FILE_TO_CLOUD, (payload: ExportFileToCloudPayload) => ({\n  payload\n}));\n\n/** EXPORT_FILE_SUCCESS */\nexport type ExportFileSuccessPayload = {\n  response: any;\n  provider: Provider;\n  options?: ExportFileOptions;\n  onSuccess?: OnSuccessCallBack;\n  closeModal?: boolean;\n};\n\nexport const exportFileSuccess: (p: ExportFileSuccessPayload) => {\n  type: typeof ActionTypes.EXPORT_FILE_SUCCESS;\n  payload: ExportFileSuccessPayload;\n} = createAction(ActionTypes.EXPORT_FILE_SUCCESS, (payload: ExportFileSuccessPayload) => ({\n  payload\n}));\n\n/** EXPORT_FILE_ERROR */\nexport type ExportFileErrorPayload = {\n  error: any;\n  provider: Provider;\n  options?: ExportFileOptions;\n  onError?: OnErrorCallBack;\n};\n\nexport const exportFileError: (p: ExportFileErrorPayload) => {\n  type: typeof ActionTypes.EXPORT_FILE_ERROR;\n  payload: ExportFileErrorPayload;\n} = createAction(ActionTypes.EXPORT_FILE_ERROR, (payload: ExportFileErrorPayload) => ({payload}));\n\n/** POST_SAVE_LOAD_SUCCESS */\nexport type PostSaveLoadSuccessPayload = string;\nexport const postSaveLoadSuccess: (p: PostSaveLoadSuccessPayload) => {\n  type: typeof ActionTypes.POST_SAVE_LOAD_SUCCESS;\n  payload: PostSaveLoadSuccessPayload;\n} = createAction(ActionTypes.POST_SAVE_LOAD_SUCCESS, (message: PostSaveLoadSuccessPayload) => ({\n  payload: message\n}));\n\nexport const resetProviderStatus: () => {\n  type: typeof ActionTypes.RESET_PROVIDER_STATUS;\n} = createAction(ActionTypes.RESET_PROVIDER_STATUS);\n\n/** LOAD_CLOUD_MAP */\nexport type LoadCloudMapPayload = {\n  loadParams: any;\n  provider: string;\n  onSuccess?: any;\n  onError?: OnErrorCallBack;\n};\nexport const loadCloudMap: (p: LoadCloudMapPayload) => {\n  type: typeof ActionTypes.LOAD_CLOUD_MAP;\n  payload: LoadCloudMapPayload;\n} = createAction(ActionTypes.LOAD_CLOUD_MAP, payload => ({payload}));\n\n/** LOAD_CLOUD_MAP_SUCCESS */\ntype LoadCloudMapSuccessCallback = (p: {response: any; loadParams: any; provider: Provider}) => any;\nexport type LoadCloudMapSuccessPayload = {\n  response: any;\n  loadParams: any;\n  provider: Provider;\n  onSuccess?: LoadCloudMapSuccessCallback;\n  onError?: OnErrorCallBack;\n};\nexport const loadCloudMapSuccess: (p: LoadCloudMapSuccessPayload) => {\n  type: typeof ActionTypes.LOAD_CLOUD_MAP_SUCCESS;\n  payload: LoadCloudMapSuccessPayload;\n} = createAction(ActionTypes.LOAD_CLOUD_MAP_SUCCESS, (payload: LoadCloudMapSuccessPayload) => ({\n  payload\n}));\n\n/** LOAD_CLOUD_MAP_SUCCESS_2 */\nexport type LoadCloudMapSuccess2Payload = LoadCloudMapSuccessPayload & {\n  datasetsPayload: any;\n};\nexport const loadCloudMapSuccess2: (p: LoadCloudMapSuccess2Payload) => {\n  type: typeof ActionTypes.LOAD_CLOUD_MAP_SUCCESS_2;\n  payload: LoadCloudMapSuccess2Payload;\n} = createAction(ActionTypes.LOAD_CLOUD_MAP_SUCCESS_2, (payload: LoadCloudMapSuccess2Payload) => ({\n  payload\n}));\n\n/** LOAD_CLOUD_MAP_ERROR */\nexport type LoadCloudMapErrorPayload = {\n  error: any;\n  provider: Provider;\n  onError?: OnErrorCallBack;\n};\nexport const loadCloudMapError: (p: LoadCloudMapErrorPayload) => {\n  type: typeof ActionTypes.LOAD_CLOUD_MAP_ERROR;\n  payload: LoadCloudMapErrorPayload;\n} = createAction(ActionTypes.LOAD_CLOUD_MAP_ERROR, (payload: LoadCloudMapErrorPayload) => ({\n  payload\n}));\n"
  },
  {
    "path": "src/actions/src/ui-state-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createAction} from '@reduxjs/toolkit';\nimport {default as ActionTypes} from './action-types';\nimport {Merge, ExportImage} from '@kepler.gl/types';\n\n/** TOGGLE_SIDE_PANEL */\nexport type ToggleSidePanelUpdaterAction = {\n  payload: string | null;\n};\n/**\n * Toggle active side panel\n * @memberof uiStateActions\n * @param id  id of side panel to be shown, one of `layer`, `filter`, `interaction`, `map`\n * @public\n */\nexport const toggleSidePanel: (\n  id: ToggleSidePanelUpdaterAction['payload']\n) => Merge<ToggleSidePanelUpdaterAction, {type: typeof ActionTypes.TOGGLE_SIDE_PANEL}> =\n  createAction(ActionTypes.TOGGLE_SIDE_PANEL, (id: string | null) => ({payload: id}));\n\n/** TOGGLE_MODAL */\nexport type ToggleModalUpdaterAction = {\n  payload: string | null;\n};\n/**\n * Show and hide modal dialog\n * @memberof uiStateActions\n * @param id - id of modal to be shown, null to hide modals. One of:\n *  - [`DATA_TABLE_ID`](../constants/default-settings.md#data_table_id)\n *  - [`DELETE_DATA_ID`](../constants/default-settings.md#delete_data_id)\n *  - [`ADD_DATA_ID`](../constants/default-settings.md#add_data_id)\n *  - [`EXPORT_IMAGE_ID`](../constants/default-settings.md#export_image_id)\n *  - [`EXPORT_DATA_ID`](../constants/default-settings.md#export_data_id)\n *  - [`ADD_MAP_STYLE_ID`](../constants/default-settings.md#add_map_style_id)\n * @public\n */\nexport const toggleModal: (\n  id: ToggleModalUpdaterAction['payload']\n) => Merge<ToggleModalUpdaterAction, {type: typeof ActionTypes.TOGGLE_MODAL}> = createAction(\n  ActionTypes.TOGGLE_MODAL,\n  (id: ToggleModalUpdaterAction['payload']) => ({\n    payload: id\n  })\n);\n\n/** SHOW_EXPORT_DROPDOWN */\nexport type ShowExportDropdownUpdaterAction = {\n  payload: string;\n};\n/**\n * Hide and show side panel header dropdown, activated by clicking the share link on top of the side panel\n * @memberof uiStateActions\n * @param id - id of the dropdown\n * @public\n */\nexport const showExportDropdown: (\n  id: ShowExportDropdownUpdaterAction['payload']\n) => Merge<ShowExportDropdownUpdaterAction, {type: typeof ActionTypes.SHOW_EXPORT_DROPDOWN}> =\n  createAction(\n    ActionTypes.SHOW_EXPORT_DROPDOWN,\n    (id: ShowExportDropdownUpdaterAction['payload']) => ({payload: id})\n  );\n\n/**\n * Hide side panel header dropdown, activated by clicking the share link on top of the side panel\n * @memberof uiStateActions\n * @public\n */\nexport const hideExportDropdown: () => {\n  type: typeof ActionTypes.HIDE_EXPORT_DROPDOWN;\n} = createAction(ActionTypes.HIDE_EXPORT_DROPDOWN);\n\n/** TOGGLE_SIDE_PANEL_CLOSE_BUTTON*/\nexport type ToggleSidePanelCloseButtonUpdaterAction = {\n  payload: {\n    show: boolean;\n  };\n};\n\n/**\n * Toggle side panel close button\n * @memberof uiStateActions\n * @param show - if side panel button visible\n * @public\n */\nexport const toggleSidePanelCloseButton: (\n  show: boolean\n) => Merge<\n  ToggleSidePanelCloseButtonUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_SIDE_PANEL_CLOSE_BUTTON}\n> = createAction(ActionTypes.TOGGLE_SIDE_PANEL_CLOSE_BUTTON, show => ({payload: {show}}));\n\n/** TOGGLE_MAP_CONTROL */\nexport type ToggleMapControlUpdaterAction = {\n  payload: {\n    panelId: string;\n    index: number;\n  };\n};\n/**\n * Toggle active map control panel\n * @memberof uiStateActions\n * @param panelId - map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @public\n */\nexport const toggleMapControl: (\n  panelId: ToggleMapControlUpdaterAction['payload']['panelId'],\n  index: ToggleMapControlUpdaterAction['payload']['index']\n) => Merge<ToggleMapControlUpdaterAction, {type: typeof ActionTypes.TOGGLE_MAP_CONTROL}> =\n  createAction(\n    ActionTypes.TOGGLE_MAP_CONTROL,\n    (\n      panelId: ToggleMapControlUpdaterAction['payload']['panelId'],\n      index: ToggleMapControlUpdaterAction['payload']['index']\n    ) => ({\n      payload: {\n        panelId,\n        index\n      }\n    })\n  );\n\n/** SET_MAP_CONTROL_VISIBILITY */\nexport type setMapControlVisibilityUpdaterAction = {\n  payload: {\n    panelId: string;\n    show: boolean;\n  };\n};\n/**\n * Toggle active map control panel\n * @memberof uiStateActions\n * @param panelId - map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @public\n */\nexport const setMapControlVisibility: (\n  panelId: setMapControlVisibilityUpdaterAction['payload']['panelId'],\n  show: setMapControlVisibilityUpdaterAction['payload']['show']\n) => Merge<\n  setMapControlVisibilityUpdaterAction,\n  {type: typeof ActionTypes.SET_MAP_CONTROL_VISIBILITY}\n> = createAction(\n  ActionTypes.SET_MAP_CONTROL_VISIBILITY,\n  (\n    panelId: setMapControlVisibilityUpdaterAction['payload']['panelId'],\n    show: setMapControlVisibilityUpdaterAction['payload']['show']\n  ) => ({\n    payload: {\n      panelId,\n      show\n    }\n  })\n);\n\n/** SET_MAP_CONTROL_SETTINGS */\nexport type setMapControlSettingsUpdaterAction = {\n  payload: {\n    panelId: string;\n    settings: Record<string, unknown>;\n  };\n};\n\n/**\n * Set map control settings\n * @memberof uiStateActions\n * @param panelId - map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @public\n */\nexport const setMapControlSettings: (\n  panelId: string,\n  settings: Record<string, unknown>\n) => Merge<\n  setMapControlSettingsUpdaterAction,\n  {type: typeof ActionTypes.SET_MAP_CONTROL_SETTINGS}\n> = createAction(ActionTypes.SET_MAP_CONTROL_SETTINGS, (panelId, settings) => ({\n  payload: {panelId, settings}\n}));\n\n/** OPEN_DELETE_MODAL */\nexport type OpenDeleteModalUpdaterAction = {\n  payload: string;\n};\n/**\n * Toggle active map control panel\n * @memberof uiStateActions\n * @param datasetId - `id` of the dataset to be deleted\n * @public\n */\nexport const openDeleteModal: (\n  datasetId: OpenDeleteModalUpdaterAction['payload']\n) => Merge<OpenDeleteModalUpdaterAction, {type: typeof ActionTypes.OPEN_DELETE_MODAL}> =\n  createAction(\n    ActionTypes.OPEN_DELETE_MODAL,\n    (datasetId: OpenDeleteModalUpdaterAction['payload']) => ({payload: datasetId})\n  );\n\n/** ADD_NOTIFICATION */\nexport type AddNotificationUpdaterAction = {\n  payload: object;\n};\n/**\n * Add a notification to be displayed.\n * Existing notification will be updated in case of matching id.\n * @memberof uiStateActions\n * @param notification - The `notification` object to be added or updated\n * @public\n */\nexport const addNotification: (notification: AddNotificationUpdaterAction['payload']) => Merge<\n  AddNotificationUpdaterAction,\n  {\n    type: typeof ActionTypes.ADD_NOTIFICATION;\n  }\n> = createAction(\n  ActionTypes.ADD_NOTIFICATION,\n  (notification: AddNotificationUpdaterAction['payload']) => ({payload: notification})\n);\n\n/** REMOVE_NOTIFICATION */\nexport type RemoveNotificationUpdaterAction = {\n  payload: string;\n};\n/**\n * Remove a notification\n * @memberof uiStateActions\n * @param id - `id` of the notification to be removed\n * @public\n */\nexport const removeNotification: (\n  id: RemoveNotificationUpdaterAction['payload']\n) => Merge<RemoveNotificationUpdaterAction, {type: typeof ActionTypes.REMOVE_NOTIFICATION}> =\n  createAction(\n    ActionTypes.REMOVE_NOTIFICATION,\n    (id: RemoveNotificationUpdaterAction['payload']) => ({payload: id})\n  );\n\n/** SET_EXPORT_IMAGE_SETTING */\nexport type SetExportImageSettingUpdaterAction = {\n  payload: Partial<ExportImage>;\n};\n/**\n * Set `exportImage` settings: ratio, resolution, legend\n * @memberof uiStateActions\n * @param newSetting - {ratio: '1x'}\n * @public\n */\nexport const setExportImageSetting: (\n  newSetting: SetExportImageSettingUpdaterAction['payload']\n) => Merge<\n  SetExportImageSettingUpdaterAction,\n  {type: typeof ActionTypes.SET_EXPORT_IMAGE_SETTING}\n> = createAction(\n  ActionTypes.SET_EXPORT_IMAGE_SETTING,\n  (newSetting: SetExportImageSettingUpdaterAction['payload']) => ({payload: newSetting})\n);\n\n/**\n * Start exporting image flow\n * @memberof uiStateActions\n * @public\n */\nexport const startExportingImage: (options?: {\n  ratio?: string;\n  resolution?: string;\n  legend?: string;\n  center?: boolean;\n}) => Merge<SetExportImageSettingUpdaterAction, {type: typeof ActionTypes.START_EXPORTING_IMAGE}> =\n  createAction(ActionTypes.START_EXPORTING_IMAGE, (payload: any) => ({payload}));\n\n/** SET_EXPORT_IMAGE_DATA_URI */\nexport type SetExportImageDataUriUpdaterAction = {\n  payload: string;\n};\n/**\n * Set `exportImage.setExportImageDataUri` to a dataUri\n * @memberof uiStateActions\n * @param dataUri - export image data uri\n * @public\n */\nexport const setExportImageDataUri: (\n  dataUri: SetExportImageDataUriUpdaterAction['payload']\n) => Merge<\n  SetExportImageDataUriUpdaterAction,\n  {type: typeof ActionTypes.SET_EXPORT_IMAGE_DATA_URI}\n> = createAction(\n  ActionTypes.SET_EXPORT_IMAGE_DATA_URI,\n  (dataUri: SetExportImageDataUriUpdaterAction['payload']) => ({payload: dataUri})\n);\n\n/** SET_EXPORT_IMAGE_ERROR */\nexport type SetExportImageErrorUpdaterAction = {\n  payload: Error;\n};\n/**\n * Set Export image error\n * @memberof uiStateActions\n * @public\n */\nexport const setExportImageError: (\n  error: SetExportImageErrorUpdaterAction['payload']\n) => Merge<SetExportImageErrorUpdaterAction, {type: typeof ActionTypes.SET_EXPORT_IMAGE_ERROR}> =\n  createAction(\n    ActionTypes.SET_EXPORT_IMAGE_ERROR,\n    (error: SetExportImageErrorUpdaterAction['payload']) => ({payload: error})\n  );\n\n/**\n * Delete cached export image\n * @memberof uiStateActions\n * @public\n */\nexport const cleanupExportImage: () => {\n  type: typeof ActionTypes.CLEANUP_EXPORT_IMAGE;\n} = createAction(ActionTypes.CLEANUP_EXPORT_IMAGE);\n\n/** SET_EXPORT_SELECTED_DATASET */\nexport type SetExportSelectedDatasetUpdaterAction = {\n  payload: string;\n};\n/**\n * Set selected dataset for export\n * @memberof uiStateActions\n * @param datasetId - dataset id\n * @public\n */\nexport const setExportSelectedDataset: (\n  datasetId: SetExportSelectedDatasetUpdaterAction['payload']\n) => Merge<\n  SetExportSelectedDatasetUpdaterAction,\n  {type: typeof ActionTypes.SET_EXPORT_SELECTED_DATASET}\n> = createAction(\n  ActionTypes.SET_EXPORT_SELECTED_DATASET,\n  (datasetId: SetExportSelectedDatasetUpdaterAction['payload']) => ({payload: datasetId})\n);\n\n/** SET_EXPORT_DATA_TYPE */\nexport type SetExportDataTypeUpdaterAction = {\n  payload: string;\n};\n/**\n * Set data format for exporting data\n * @memberof uiStateActions\n * @param dataType - one of `'text/csv'`\n * @public\n */\nexport const setExportDataType: (\n  dataType: SetExportDataTypeUpdaterAction['payload']\n) => Merge<SetExportDataTypeUpdaterAction, {type: typeof ActionTypes.SET_EXPORT_DATA_TYPE}> =\n  createAction(\n    ActionTypes.SET_EXPORT_DATA_TYPE,\n    (dataType: SetExportDataTypeUpdaterAction['payload']) => ({payload: dataType})\n  );\n\n/** SET_EXPORT_FILTERED */\nexport type SetExportFilteredUpdaterAction = {\n  payload: boolean;\n};\n/**\n * Whether to export filtered data, `true` or `false`\n * @memberof uiStateActions\n * @param payload - set `true` to ony export filtered data\n * @public\n */\nexport const setExportFiltered: (\n  exportFiltered: SetExportFilteredUpdaterAction['payload']\n) => Merge<SetExportFilteredUpdaterAction, {type: typeof ActionTypes.SET_EXPORT_FILTERED}> =\n  createAction(\n    ActionTypes.SET_EXPORT_FILTERED,\n    (payload: SetExportFilteredUpdaterAction['payload']) => ({payload})\n  );\n\n/**\n * Whether to including data in map config, toggle between `true` or `false`\n * @memberof uiStateActions\n * @public\n */\nexport const setExportData: () => {type: typeof ActionTypes.SET_EXPORT_DATA} = createAction(\n  ActionTypes.SET_EXPORT_DATA\n);\n\n/** SET_USER_MAPBOX_ACCESS_TOKEN */\nexport type SetUserMapboxAccessTokenUpdaterAction = {\n  payload: string;\n};\n/**\n * Whether we export a mapbox access token used to create a single map html file\n * @memberof uiStateActions\n * @param payload - mapbox access token\n * @public\n */\nexport const setUserMapboxAccessToken: (\n  payload: SetUserMapboxAccessTokenUpdaterAction['payload']\n) => Merge<\n  SetUserMapboxAccessTokenUpdaterAction,\n  {type: typeof ActionTypes.SET_USER_MAPBOX_ACCESS_TOKEN}\n> = createAction(\n  ActionTypes.SET_USER_MAPBOX_ACCESS_TOKEN,\n  (payload: SetUserMapboxAccessTokenUpdaterAction['payload']) => ({payload})\n);\n\n/** SET_EXPORT_MAP_FORMAT */\nexport type SetExportMapFormatUpdaterAction = {\n  payload: string;\n};\n/**\n * Set the export map format (html, json)\n * @memberOf uiStateActions\n * @param payload - map format\n * @public\n */\nexport const setExportMapFormat: (\n  mapFormat: SetExportMapFormatUpdaterAction['payload']\n) => Merge<SetExportMapFormatUpdaterAction, {type: typeof ActionTypes.SET_EXPORT_MAP_FORMAT}> =\n  createAction(\n    ActionTypes.SET_EXPORT_MAP_FORMAT,\n    (payload: SetExportMapFormatUpdaterAction['payload']) => ({payload})\n  );\n\n/** SET_EXPORT_MAP_HTML_MODE */\nexport type SetExportHTMLMapModeUpdaterAction = {\n  payload: string;\n};\n/**\n * Set the HTML mode to use to export HTML mode\n * @memberOf uiStateActions\n * @param payload - map mode\n */\nexport const setExportHTMLMapMode: (\n  mode: SetExportHTMLMapModeUpdaterAction['payload']\n) => Merge<SetExportHTMLMapModeUpdaterAction, {type: typeof ActionTypes.SET_EXPORT_MAP_HTML_MODE}> =\n  createAction(\n    ActionTypes.SET_EXPORT_MAP_HTML_MODE,\n    (payload: SetExportHTMLMapModeUpdaterAction['payload']) => ({payload})\n  );\n\n/** SET_LOCALE */\nexport type SetLocaleUpdaterAction = {\n  payload: {locale: string};\n};\n/**\n * Set `locale` value\n * @memberof uiStateActions\n * @param locale - locale of the UI\n * @public\n */\nexport const setLocale: (\n  locale: SetLocaleUpdaterAction['payload']['locale']\n) => Merge<SetLocaleUpdaterAction, {type: typeof ActionTypes.SET_LOCALE}> = createAction(\n  ActionTypes.SET_LOCALE,\n  (locale: SetLocaleUpdaterAction['payload']['locale']) => ({\n    payload: {\n      locale\n    }\n  })\n);\n\n/** TOGGLE_PANEL_LIST_VIEW */\nexport type TogglePanelListViewAction = {\n  payload: {\n    panelId: string;\n    listView: string;\n  };\n};\n\n/**\n * Toggle layer panel list view\n * @memberof uiStateActions\n * @param payload\n * @param payload.panelId panel id.\n * @param payload.listView layer panel listView value. Can be 'list' or 'sortByDataset'\n * @public\n */\nexport const togglePanelListView: (\n  payload: TogglePanelListViewAction['payload']\n) => Merge<TogglePanelListViewAction, {type: typeof ActionTypes.TOGGLE_PANEL_LIST_VIEW}> =\n  createAction(\n    ActionTypes.TOGGLE_PANEL_LIST_VIEW,\n    (payload: TogglePanelListViewAction['payload']) => ({payload})\n  );\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * Actions handled mostly by  `uiState` reducer.\n * They manage UI changes in tha app, such as open and close side panel,\n * switch between tabs in the side panel, open and close modal dialog for exporting data / images etc.\n * It also manges which settings are selected during image and map export\n *\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst uiStateActions = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/src/vis-state-actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// vis-state-reducer\nimport {PickInfo} from '@deck.gl/core/lib/deck';\nimport {default as ActionTypes} from './action-types';\nimport {FileCacheItem} from '@kepler.gl/processors';\nimport {Layer, LayerBaseConfig} from '@kepler.gl/layers';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {\n  AddDataToMapPayload,\n  ValueOf,\n  Merge,\n  RGBColor,\n  NestedPartial,\n  LayerVisConfig,\n  ColorUI,\n  Feature,\n  FeatureSelectionContext,\n  InteractionConfig,\n  Filter,\n  ParsedConfig,\n  ParsedLayer,\n  EffectPropsPartial,\n  SyncTimelineMode,\n  AnimationConfig,\n  FilterAnimationConfig\n} from '@kepler.gl/types';\nimport {createAction} from '@reduxjs/toolkit';\n\n// TODO - import LoaderObject type from @loaders.gl/core when supported\n// TODO - import LoadOptions type from @loaders.gl/core when supported\n\nexport type ApplyLayerConfigUpdaterAction = {\n  oldLayerId: string;\n  newLayerConfig: ParsedLayer;\n  layerIndex?: number;\n};\n\n/**\n * Update layer base config: dataId, label, column, isVisible\n * @param oldLayerId - layer id to be updated\n * @param newLayerConfig - new layer config\n * @param layerIndex - (Optional) Index of the layer to be updated (can be useful in some cases, because\n *                     the layer id might change during update, e.g. when the type of the layer changes)\n * @returns action\n * @public\n */\nexport function applyLayerConfig(\n  oldLayerId: string,\n  newLayerConfig: ParsedLayer,\n  layerIndex?: number\n): Merge<ApplyLayerConfigUpdaterAction, {type: typeof ActionTypes.APPLY_LAYER_CONFIG}> {\n  return {\n    type: ActionTypes.APPLY_LAYER_CONFIG,\n    oldLayerId,\n    newLayerConfig,\n    layerIndex\n  };\n}\n\nexport type LayerConfigChangeUpdaterAction = {\n  oldLayer: Layer;\n  newConfig: Partial<Layer['config']>;\n};\n/**\n * Update layer base config: dataId, label, column, isVisible\n * @param oldLayer - layer to be updated\n * @param newConfig - new config to be merged with old config\n * @returns action\n * @public\n */\nexport function layerConfigChange(\n  oldLayer: Layer,\n  newConfig: Partial<LayerBaseConfig>\n): Merge<LayerConfigChangeUpdaterAction, {type: typeof ActionTypes.LAYER_CONFIG_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_CONFIG_CHANGE,\n    oldLayer,\n    newConfig\n  };\n}\n\nexport type LayerToggleVisibilityUpdaterAction = {\n  layerId: string;\n  isVisible: boolean;\n  splitMapId?: string;\n};\n\n/**\n * Update layer visibility depends on splitMap single or dual\n * @param layerId - layerId to be updated\n * @param isVisible - whether this layer is visible globally\n * @param splitMapId - id for this splitMap\n * @returns action\n * @public\n */\nexport function layerToggleVisibility(\n  layerId: string,\n  isVisible: boolean,\n  splitMapId?: string\n): Merge<LayerToggleVisibilityUpdaterAction, {type: typeof ActionTypes.LAYER_TOGGLE_VISIBILITY}> {\n  return {\n    type: ActionTypes.LAYER_TOGGLE_VISIBILITY,\n    layerId,\n    isVisible,\n    splitMapId\n  };\n}\n\nexport type LayerTextLabelChangeUpdaterAction = {\n  oldLayer: Layer;\n  idx: number | 'all';\n  prop: string;\n  value: any;\n};\n\n/**\n * Update layer text label\n * @param oldLayer - layer to be updated\n * @param idx -`idx` of text label to be updated\n * @param prop - `prop` of text label, e,g, `anchor`, `alignment`, `color`, `size`, `field`, `outlineWidth`, `outlineColor`\n * @param value - new value\n * @returns action\n * @public\n */\nexport function layerTextLabelChange(\n  oldLayer: Layer,\n  idx: number | 'all',\n  prop: string,\n  value: any\n): Merge<LayerTextLabelChangeUpdaterAction, {type: typeof ActionTypes.LAYER_TEXT_LABEL_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_TEXT_LABEL_CHANGE,\n    oldLayer,\n    idx,\n    prop,\n    value\n  };\n}\n\nexport type LayerSetIsValidUpdaterAction = {\n  oldLayer: Layer;\n  isValid: boolean;\n};\n\n/**\n * Changes value of isValid flag for a layer.\n * The action also updates visibility of the layer based on isValid.\n * @param oldLayer - layer to be updated\n * @param isValid - new value for isValid flag\n * @returns action\n * @public\n */\nexport function layerSetIsValid(\n  oldLayer: Layer,\n  isValid: boolean\n): Merge<LayerSetIsValidUpdaterAction, {type: typeof ActionTypes.LAYER_SET_IS_VALID}> {\n  return {\n    type: ActionTypes.LAYER_SET_IS_VALID,\n    oldLayer,\n    isValid\n  };\n}\n\nexport type LayerTypeChangeUpdaterAction = {\n  oldLayer: Layer;\n  newType: string;\n};\n/**\n * Update layer type. Previews layer config will be copied if applicable.\n * @param oldLayer - layer to be updated\n * @param newType - new type\n * @returns action\n * @public\n */\nexport function layerTypeChange(\n  oldLayer: Layer,\n  newType: string\n): Merge<LayerTypeChangeUpdaterAction, {type: typeof ActionTypes.LAYER_TYPE_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_TYPE_CHANGE,\n    oldLayer,\n    newType\n  };\n}\nexport type LayerVisualChannelConfigChangeUpdaterAction = {\n  oldLayer: Layer;\n  newConfig: Partial<Layer['config']>;\n  channel: string;\n  newVisConfig?: Partial<LayerVisConfig>;\n};\n/**\n * Update layer visual channel\n * @memberof visStateActions\n * @param oldLayer - layer to be updated\n * @param newConfig - new visual channel config\n * @param channel - channel to be updated\n * @returns action\n * @public\n */\nexport function layerVisualChannelConfigChange(\n  oldLayer: Layer,\n  newConfig: Partial<LayerBaseConfig>,\n  channel: string,\n  newVisConfig?: Partial<LayerVisConfig>\n): Merge<\n  LayerVisualChannelConfigChangeUpdaterAction,\n  {type: typeof ActionTypes.LAYER_VISUAL_CHANNEL_CHANGE}\n> {\n  return {\n    type: ActionTypes.LAYER_VISUAL_CHANNEL_CHANGE,\n    oldLayer,\n    newConfig,\n    channel,\n    newVisConfig\n  };\n}\nexport type LayerVisConfigChangeUpdaterAction = {\n  oldLayer: Layer;\n  newVisConfig: Partial<LayerVisConfig>;\n};\n/**\n * Update layer `visConfig`\n * @memberof visStateActions\n * @param oldLayer - layer to be updated\n * @param newVisConfig - new visConfig as a key value map: e.g. `{opacity: 0.8}`\n * @returns action\n * @public\n */\nexport function layerVisConfigChange(\n  oldLayer: Layer,\n  newVisConfig: Partial<LayerVisConfig>\n): Merge<LayerVisConfigChangeUpdaterAction, {type: typeof ActionTypes.LAYER_VIS_CONFIG_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_VIS_CONFIG_CHANGE,\n    oldLayer,\n    newVisConfig\n  };\n}\nexport type LayerColorUIChangeUpdaterAction = {\n  oldLayer: Layer;\n  prop: string;\n  newConfig: NestedPartial<ColorUI>;\n};\n\n/**\n * Set the color palette ui for layer color\n * @memberof visStateActions\n * @param oldLayer - layer to be updated\n * @param prop - which color prop\n * @param newConfig - to be merged\n * @returns action\n * @public\n */\nexport function layerColorUIChange(\n  oldLayer: Layer,\n  prop: string,\n  newConfig: NestedPartial<ColorUI>\n): Merge<LayerColorUIChangeUpdaterAction, {type: typeof ActionTypes.LAYER_COLOR_UI_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_COLOR_UI_CHANGE,\n    oldLayer,\n    prop,\n    newConfig\n  };\n}\n\nexport type UpdateLayerBlendingUpdaterAction = {\n  mode: 'additive' | 'normal' | 'subtractive';\n};\n/**\n * Update layer blending mode\n * @memberof visStateActions\n * @param mode one of `additive`, `normal` and `subtractive`\n * @returns action\n * @public\n */\nexport function updateLayerBlending(\n  mode: 'additive' | 'normal' | 'subtractive'\n): Merge<UpdateLayerBlendingUpdaterAction, {type: typeof ActionTypes.UPDATE_LAYER_BLENDING}> {\n  return {\n    type: ActionTypes.UPDATE_LAYER_BLENDING,\n    mode\n  };\n}\n\nexport type UpdateOverlayBlendingUpdaterAction = {\n  mode: 'screen' | 'normal' | 'darken';\n};\n\n/**\n * Update overlay blending mode\n * @memberof visStateActions\n * @param mode one of `screen`, `normal` and `darken`\n * @returns action\n * @public\n */\nexport function updateOverlayBlending(\n  mode: 'screen' | 'normal' | 'darken'\n): Merge<UpdateOverlayBlendingUpdaterAction, {type: typeof ActionTypes.UPDATE_OVERLAY_BLENDING}> {\n  return {\n    type: ActionTypes.UPDATE_OVERLAY_BLENDING,\n    mode\n  };\n}\n\nexport type InteractionConfigChangeUpdaterAction = {\n  config: ValueOf<InteractionConfig>;\n};\n/**\n * Update `interactionConfig`\n * @memberof visStateActions\n * @param config - new config as key value map: `{tooltip: {enabled: true}}`\n * @returns action\n * @public\n */\nexport function interactionConfigChange(\n  config: ValueOf<InteractionConfig>\n): Merge<\n  InteractionConfigChangeUpdaterAction,\n  {type: typeof ActionTypes.INTERACTION_CONFIG_CHANGE}\n> {\n  return {\n    type: ActionTypes.INTERACTION_CONFIG_CHANGE,\n    config\n  };\n}\n\nexport type ApplyFilterConfigUpdaterAction = {\n  filterId: string;\n  newFilter: Filter;\n};\n\n/**\n * Update filter config\n * @param filterId - id of the filter to be updated\n * @param newFilter - new filter config\n * @returns action\n * @public\n */\nexport function applyFilterConfig(\n  filterId: string,\n  newFilter: Filter\n): Merge<ApplyFilterConfigUpdaterAction, {type: typeof ActionTypes.APPLY_FILTER_CONFIG}> {\n  return {\n    type: ActionTypes.APPLY_FILTER_CONFIG,\n    filterId,\n    newFilter\n  };\n}\n\nexport type SetFilterUpdaterAction = {\n  idx: number;\n  prop: string | string[];\n  value: any;\n  valueIndex?: number;\n};\n/**\n * Update filter property\n * @memberof visStateActions\n * @param idx -`idx` of filter to be updated\n * @param prop - `prop` of filter, e,g, `dataId`, `name`, `value`\n *                or an array e.g. ['idx', 'name']. in that case the value\n *                should also be an array of the corresponding values (by index)\n * @param value - new value\n * @param valueIndex - dataId index\n * @returns action\n * @public\n */\nexport function setFilter(\n  idx: number,\n  prop: string | string[],\n  value: any,\n  valueIndex?: number\n): Merge<SetFilterUpdaterAction, {type: typeof ActionTypes.SET_FILTER}> {\n  return {\n    type: ActionTypes.SET_FILTER,\n    idx,\n    prop,\n    value,\n    valueIndex\n  };\n}\n\nexport type SetFilterAnimationTimeUpdaterAction = {\n  idx: number;\n  prop: string;\n  value: any;\n  valueIndex?: number;\n};\n/**\n * Same as Update filter\n * @memberof visStateActions\n * @param idx -`idx` of filter to be updated\n * @param prop - `prop` of filter, e,g, `dataId`, `name`, `value`\n * @param value - new value\n * @param valueIndex - dataId index\n * @returns action\n * @public\n */\nexport function setFilterAnimationTime(\n  idx: number,\n  prop: string,\n  value: any,\n  valueIndex?: number\n): Merge<\n  SetFilterAnimationTimeUpdaterAction,\n  {type: typeof ActionTypes.SET_FILTER_ANIMATION_TIME}\n> {\n  return {\n    type: ActionTypes.SET_FILTER_ANIMATION_TIME,\n    idx,\n    prop,\n    value,\n    valueIndex\n  };\n}\n\nexport type SetFilterAnimationWindowUpdaterAction = {\n  id: string;\n  animationWindow: string;\n};\n/**\n * Same as Update filter\n * @memberof visStateActions\n * @public\n */\nexport function setFilterAnimationWindow({\n  id,\n  animationWindow\n}: SetFilterAnimationWindowUpdaterAction): Merge<\n  SetFilterAnimationWindowUpdaterAction,\n  {type: typeof ActionTypes.SET_FILTER_ANIMATION_WINDOW}\n> {\n  return {\n    type: ActionTypes.SET_FILTER_ANIMATION_WINDOW,\n    id,\n    animationWindow\n  };\n}\n\nexport type AddFilterUpdaterAction = {\n  dataId?: string | string[] | null;\n  id?: string;\n};\n/**\n * Add a new filter\n * @memberof visStateActions\n * @param dataId - dataset `id` this new filter is associated with\n * @param id - `id` for the new filter\n * @returns action\n * @public\n */\nexport function addFilter(\n  dataId: string | null,\n  id?: string\n): Merge<AddFilterUpdaterAction, {type: typeof ActionTypes.ADD_FILTER}> {\n  return {\n    type: ActionTypes.ADD_FILTER,\n    dataId,\n    id\n  };\n}\n\nexport type CreateOrUpdateFilterUpdaterAction = {\n  id?: string;\n  dataId?: string | string[];\n  field?: string | string[];\n  value?: any;\n};\n\n/**\n * Create or updates a filter\n * @memberof visStateActions\n * @param dataId - dataset `id` this new filter is associated with\n * @returns action\n * @public\n */\nexport function createOrUpdateFilter(\n  id?: string,\n  dataId?: string | string[],\n  field?: string | string[],\n  value?: any\n): Merge<CreateOrUpdateFilterUpdaterAction, {type: typeof ActionTypes.CREATE_OR_UPDATE_FILTER}> {\n  return {\n    type: ActionTypes.CREATE_OR_UPDATE_FILTER,\n    id,\n    dataId,\n    field,\n    value\n  };\n}\n\nexport type AddLayerUpdaterAction = {\n  config?: object;\n  datasetId?: string;\n};\n/**\n * Add a new layer\n * @memberof visStateActions\n * @param config - new layer config\n * @param datasetId - dataset id used for creating an empty layer\n * @returns action\n * @public\n */\nexport function addLayer(\n  config?: object,\n  datasetId?: string\n): Merge<AddLayerUpdaterAction, {type: typeof ActionTypes.ADD_LAYER}> {\n  return {\n    type: ActionTypes.ADD_LAYER,\n    config,\n    datasetId\n  };\n}\n\nexport type ReorderLayerUpdaterAction = {\n  order: string[];\n};\n/**\n * Reorder layer, order is an array of layer indexes, index 0 will be the one at the bottom\n * @memberof visStateActions\n * @param order an array of layer indexes\n * @returns action\n * @public\n * @example\n *\n * bring `layers[1]` below `layers[0]`, the sequence layers will be rendered is `layers[1].id`, `layers[0].id`, `layers[2].id`, `layers[3].id`.\n * `layers[1]` will be at the bottom, `layers[13]` will be at the top.\n * this.props.dispatch(reorderLayer([`layers[1].id`, `layers[0].id`, `layers[2].id`, `layers[3].id`]));\n */\nexport function reorderLayer(\n  order: string[]\n): Merge<ReorderLayerUpdaterAction, {type: typeof ActionTypes.REORDER_LAYER}> {\n  return {\n    type: ActionTypes.REORDER_LAYER,\n    order\n  };\n}\n\nexport type RemoveFilterUpdaterAction = {\n  idx: number;\n};\n/**\n * Remove a filter from `visState.filters`, once a filter is removed, data will be re-filtered and layer will be updated\n * @memberof visStateActions\n * @param idx idx of filter to be removed\n * @returns action\n * @public\n */\nexport function removeFilter(\n  idx: number\n): Merge<RemoveFilterUpdaterAction, {type: typeof ActionTypes.REMOVE_FILTER}> {\n  return {\n    type: ActionTypes.REMOVE_FILTER,\n    idx\n  };\n}\n\nexport type RemoveLayerUpdaterAction = {\n  id: string;\n};\n/**\n * Remove a layer\n * @memberof visStateActions\n * @param id idx of layer to be removed\n * @returns action\n * @public\n */\nexport function removeLayer(\n  id: string\n): Merge<RemoveLayerUpdaterAction, {type: typeof ActionTypes.REMOVE_LAYER}> {\n  return {\n    type: ActionTypes.REMOVE_LAYER,\n    id\n  };\n}\n\nexport type DuplicateLayerUpdaterAction = {\n  id: string;\n};\n/**\n * Duplicate a layer\n * @memberof visStateActions\n * @param id id of layer to be duplicated\n * @returns action\n * @public\n */\nexport function duplicateLayer(\n  id: string\n): Merge<DuplicateLayerUpdaterAction, {type: typeof ActionTypes.DUPLICATE_LAYER}> {\n  return {\n    type: ActionTypes.DUPLICATE_LAYER,\n    id\n  };\n}\n\nexport type AddEffectUpdaterAction = {\n  config: EffectPropsPartial;\n};\n\n/**\n * Add a new effect\n * @memberof visStateActions\n * @param config - new effect config\n * @returns action\n * @public\n */\nexport function addEffect(\n  config: EffectPropsPartial\n): Merge<AddEffectUpdaterAction, {type: typeof ActionTypes.ADD_EFFECT}> {\n  return {\n    type: ActionTypes.ADD_EFFECT,\n    config\n  };\n}\n\nexport type ReorderEffectUpdaterAction = {\n  order: string[];\n};\n\n/**\n * Reorder effects\n * @memberof visStateActions\n * @param order an array of effect ids\n * @returns action\n * @public\n */\nexport function reorderEffect(\n  order: string[]\n): Merge<ReorderEffectUpdaterAction, {type: typeof ActionTypes.REORDER_EFFECT}> {\n  return {\n    type: ActionTypes.REORDER_EFFECT,\n    order\n  };\n}\n\nexport type RemoveEffectUpdaterAction = {\n  id: string;\n};\n\n/**\n * Remove an effect\n * @memberof visStateActions\n * @param id idx of the effect to be removed\n * @returns action\n * @public\n */\nexport function removeEffect(\n  id: string\n): Merge<RemoveEffectUpdaterAction, {type: typeof ActionTypes.REMOVE_EFFECT}> {\n  return {\n    type: ActionTypes.REMOVE_EFFECT,\n    id\n  };\n}\n\nexport type UpdateEffectUpdaterAction = {\n  id: string;\n  props: EffectPropsPartial;\n};\n\n/**\n * Update an effect\n * @memberof visStateActions\n * @param props idx of the effect to be updated with specified props\n * @returns action\n * @public\n */\nexport function updateEffect(\n  id: string,\n  props: EffectPropsPartial\n): Merge<UpdateEffectUpdaterAction, {type: typeof ActionTypes.UPDATE_EFFECT}> {\n  return {\n    type: ActionTypes.UPDATE_EFFECT,\n    id,\n    props\n  };\n}\n\nexport type RemoveDatasetUpdaterAction = {\n  dataId: string;\n};\n/**\n * Remove a dataset and all layers, filters, tooltip configs that based on it\n * @memberof visStateActions\n * @param dataId dataset id\n * @returns action\n * @public\n */\nexport function removeDataset(\n  dataId: string\n): Merge<RemoveDatasetUpdaterAction, {type: typeof ActionTypes.REMOVE_DATASET}> {\n  return {\n    type: ActionTypes.REMOVE_DATASET,\n    dataId\n  };\n}\n\nexport type ShowDatasetTableUpdaterAction = {\n  dataId: string;\n};\n/**\n * Display dataset table in a modal\n * @memberof visStateActions\n * @param dataId dataset id to show in table\n * @returns action\n * @public\n */\nexport function showDatasetTable(\n  dataId: string\n): Merge<ShowDatasetTableUpdaterAction, {type: typeof ActionTypes.SHOW_DATASET_TABLE}> {\n  return {\n    type: ActionTypes.SHOW_DATASET_TABLE,\n    dataId\n  };\n}\n\nexport type UpdateDatasetColorUpdater = {\n  dataId: string;\n  newColor: RGBColor;\n};\n/**\n * Update dataset color to custom by means of color picker\n * @memberof visStateActions\n * @param dataId dataset `id` this custom color is associated with\n * @param newColor custom color in RGBformat\n * @returns action\n * @public\n */\nexport function updateTableColor(\n  dataId: string,\n  newColor: RGBColor\n): Merge<UpdateDatasetColorUpdater, {type: typeof ActionTypes.UPDATE_TABLE_COLOR}> {\n  return {\n    type: ActionTypes.UPDATE_TABLE_COLOR,\n    dataId,\n    newColor\n  };\n}\n\nexport type SortTableColumnUpdaterAction = {\n  dataId: string;\n  column: string;\n  mode?: string;\n};\n/**\n * Sort dataset column, for table display\n * @memberof visStateActions\n * @param dataId\n * @param column\n * @param mode\n * @returns action\n * @public\n */\nexport function sortTableColumn(\n  dataId: string,\n  column: string,\n  mode?: string\n): Merge<SortTableColumnUpdaterAction, {type: typeof ActionTypes.SORT_TABLE_COLUMN}> {\n  return {\n    type: ActionTypes.SORT_TABLE_COLUMN,\n    dataId,\n    column,\n    mode\n  };\n}\n\nexport type PinTableColumnUpdaterAction = {\n  dataId: string;\n  column: string;\n};\n/**\n * Pin dataset column, for table display\n * @param dataId\n * @param column\n * @returns action\n * @public\n */\nexport function pinTableColumn(\n  dataId: string,\n  column: string\n): Merge<PinTableColumnUpdaterAction, {type: typeof ActionTypes.PIN_TABLE_COLUMN}> {\n  return {\n    type: ActionTypes.PIN_TABLE_COLUMN,\n    dataId,\n    column\n  };\n}\n\nexport type CopyTableColumnUpdaterAction = {\n  dataId: string;\n  column: string;\n};\n/**\n * Copy column, for table display\n * @param dataId\n * @param column\n * @returns action\n * @public\n */\nexport function copyTableColumn(\n  dataId: string,\n  column: string\n): Merge<CopyTableColumnUpdaterAction, {type: typeof ActionTypes.COPY_TABLE_COLUMN}> {\n  return {\n    type: ActionTypes.COPY_TABLE_COLUMN,\n    dataId,\n    column\n  };\n}\n\nexport type SetColumnDisplayFormatUpdaterAction = {\n  dataId: string;\n  formats: {\n    [key: string]: string;\n  };\n};\n\n/**\n * Set column display format\n * @param dataId\n * @param formats\n * @returns action\n * @public\n */\nexport function setColumnDisplayFormat(\n  dataId: SetColumnDisplayFormatUpdaterAction['dataId'],\n  formats: SetColumnDisplayFormatUpdaterAction['formats']\n): Merge<\n  SetColumnDisplayFormatUpdaterAction,\n  {type: typeof ActionTypes.SET_COLUMN_DISPLAY_FORMAT}\n> {\n  return {\n    type: ActionTypes.SET_COLUMN_DISPLAY_FORMAT,\n    dataId,\n    formats\n  };\n}\n\nexport type AddDataToMapUpdaterOptions = {\n  centerMap?: boolean;\n  readOnly?: boolean;\n  keepExistingConfig?: boolean;\n};\n\nexport type UpdateVisDataUpdaterAction = {\n  datasets: AddDataToMapPayload['datasets'];\n  options: AddDataToMapPayload['options'];\n  config?: ParsedConfig;\n};\n// * @param dataset.info -info of a dataset\n// * @param dataset.info.id - id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n// * @param dataset.info.label - A display name of this dataset\n// * @param dataset.data - ***required** The data object, in a tabular format with 2 properties `fields` and `rows`\n// * @param dataset.data.fields - ***required** Array of fields,\n// * @param dataset.data.fields.name - ***required** Name of the field,\n// * @param dataset.data.rows - ***required** Array of rows, in a tabular format with `fields` and `rows`\n/**\n * Add new dataset to `visState`, with option to load a map config along with the datasets\n * @memberof visStateActions\n * @param datasets - ***required** datasets can be a dataset or an array of datasets\n * Each dataset object needs to have `info` and `data` property.\n * @param {object} options\n * @param options.centerMap `default: true` if `centerMap` is set to `true` kepler.gl will\n * place the map view within the data points boundaries\n * @param options.readOnly `default: false` if `readOnly` is set to `true`\n * the left setting panel will be hidden\n * @param config this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n * @returns action\n * @public\n */\nexport function updateVisData(\n  datasets: AddDataToMapPayload['datasets'],\n  options: AddDataToMapPayload['options'],\n  config?: ParsedConfig\n): Merge<UpdateVisDataUpdaterAction, {type: typeof ActionTypes.UPDATE_VIS_DATA}> {\n  return {\n    type: ActionTypes.UPDATE_VIS_DATA,\n    datasets,\n    options,\n    config\n  };\n}\n\nexport type RenameDatasetUpdaterAction = {\n  dataId: string;\n  label: string;\n};\n/**\n * Rename an existing dataset in `visState`\n * @memberof visStateActions\n * @param dataId - ***required** Id of the dataset to update\n * @param label - ***required** New name for the dataset\n * @returns action\n * @public\n */\nexport function renameDataset(\n  dataId: string,\n  label: string\n): Merge<RenameDatasetUpdaterAction, {type: typeof ActionTypes.RENAME_DATASET}> {\n  return {\n    type: ActionTypes.RENAME_DATASET,\n    dataId,\n    label\n  };\n}\n\nexport type UpdateDatasetPropsUpdaterAction = {\n  dataId: string;\n  props: {\n    label?: string;\n    color?: RGBColor;\n    metadata?: Record<string, unknown>;\n  };\n};\n/**\n * Update an existing dataset props in `visState`\n * @param dataId - ***required** Id of the dataset to update\n * @param props - ***required** New props to update\n * @returns action\n */\nexport function updateDatasetProps(\n  dataId: string,\n  props: {\n    label?: string;\n    color?: RGBColor;\n    metadata?: Record<string, unknown>;\n  }\n): Merge<UpdateDatasetPropsUpdaterAction, {type: typeof ActionTypes.UPDATE_DATASET_PROPS}> {\n  return {\n    type: ActionTypes.UPDATE_DATASET_PROPS,\n    dataId,\n    props\n  };\n}\n\nexport type ToggleFilterAnimationUpdaterAction = {\n  idx: number;\n};\n/**\n * Start and end filter animation\n * @memberof visStateActions\n * @param {Number} idx of filter\n * @returns action\n * @public\n */\nexport function toggleFilterAnimation(\n  idx: number\n): Merge<ToggleFilterAnimationUpdaterAction, {type: typeof ActionTypes.TOGGLE_FILTER_ANIMATION}> {\n  return {\n    type: ActionTypes.TOGGLE_FILTER_ANIMATION,\n    idx\n  };\n}\n\nexport type UpdateFilterAnimationSpeedUpdaterAction = {\n  idx: number;\n  speed: number;\n};\n/**\n * Change filter animation speed\n * @memberof visStateActions\n * @param idx -  `idx` of filter\n * @param speed - `speed` to change it to. `speed` is a multiplier\n * @returns action\n * @public\n */\nexport function updateFilterAnimationSpeed(\n  idx: number,\n  speed: number\n): Merge<\n  UpdateFilterAnimationSpeedUpdaterAction,\n  {type: typeof ActionTypes.UPDATE_FILTER_ANIMATION_SPEED}\n> {\n  return {\n    type: ActionTypes.UPDATE_FILTER_ANIMATION_SPEED,\n    idx,\n    speed\n  };\n}\n\nexport type SetAnimationConfigUpdaterAction = {\n  config: AnimationConfig | FilterAnimationConfig;\n};\n/**\n * Set animation config: works with both layer animation and filter animation\n * @param config\n * @returns action\n */\nexport function setAnimationConfig(\n  config: AnimationConfig | FilterAnimationConfig\n): Merge<SetAnimationConfigUpdaterAction, {type: typeof ActionTypes.SET_ANIMATION_CONFIG}> {\n  return {\n    type: ActionTypes.SET_ANIMATION_CONFIG,\n    config\n  };\n}\n\nexport type SetLayerAnimationTimeUpdaterAction = {\n  value: number | null;\n};\n/**\n * Reset animation\n * @memberof visStateActions\n * @param value -  Current value of the slider\n * @returns action\n * @public\n */\nexport function setLayerAnimationTime(\n  value: number\n): Merge<SetLayerAnimationTimeUpdaterAction, {type: typeof ActionTypes.SET_LAYER_ANIMATION_TIME}> {\n  return {\n    type: ActionTypes.SET_LAYER_ANIMATION_TIME,\n    value\n  };\n}\n\nexport type UpdateLayerAnimationSpeedUpdaterAction = {\n  speed: number;\n};\n/**\n * update trip layer animation speed\n * @memberof visStateActions\n * @param speed - `speed` to change it to. `speed` is a multiplier\n * @returns action\n * @public\n */\nexport function updateLayerAnimationSpeed(\n  speed: number\n): Merge<\n  UpdateLayerAnimationSpeedUpdaterAction,\n  {type: typeof ActionTypes.UPDATE_LAYER_ANIMATION_SPEED}\n> {\n  return {\n    type: ActionTypes.UPDATE_LAYER_ANIMATION_SPEED,\n    speed\n  };\n}\nexport type ToggleLayerAnimationUpdaterAction = void;\n/**\n * start end end layer animation\n * @memberof visStateActions\n * @returns action\n * @public\n */\nexport function toggleLayerAnimation(): Merge<\n  ToggleLayerAnimationUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_LAYER_ANIMATION}\n> {\n  return {\n    type: ActionTypes.TOGGLE_LAYER_ANIMATION\n  };\n}\n\nexport type ToggleLayerAnimationControlUpdaterAction = void;\n/**\n * hide and show layer animation control\n * @memberof visStateActions\n * @returns action\n * @public\n */\nexport function toggleLayerAnimationControl(): Merge<\n  ToggleLayerAnimationControlUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_LAYER_ANIMATION_CONTROL}\n> {\n  return {\n    type: ActionTypes.TOGGLE_LAYER_ANIMATION_CONTROL\n  };\n}\n\nexport type SetFilterViewUpdaterAction = {\n  idx: number;\n  view: Filter['view'];\n};\n/**\n * Show larger time filter at bottom for time playback (apply to time filter only)\n * @memberof visStateActions\n * @param idx - index of filter to enlarge\n * @param view - type of filter view\n * @returns action\n * @public\n */\nexport function setFilterView(\n  idx: number,\n  view: Filter['view']\n): Merge<SetFilterViewUpdaterAction, {type: typeof ActionTypes.SET_FILTER_VIEW}> {\n  return {\n    type: ActionTypes.SET_FILTER_VIEW,\n    idx,\n    view\n  };\n}\n\nexport type ToggleFilterFeatureUpdaterAction = {\n  idx: number;\n};\n/**\n * Show/hide filter feature on map\n * @memberof visStateActions\n * @param idx - index of filter feature to show/hide\n * @return action\n */\nexport function toggleFilterFeature(\n  idx: number\n): Merge<ToggleFilterFeatureUpdaterAction, {type: typeof ActionTypes.TOGGLE_FILTER_FEATURE}> {\n  return {\n    type: ActionTypes.TOGGLE_FILTER_FEATURE,\n    idx\n  };\n}\n\nexport type OnLayerHoverUpdaterAction = {\n  info: PickInfo<any> | null;\n  mapIndex?: number;\n};\n/**\n * Trigger layer hover event with hovered object\n * @memberof visStateActions\n * @param info - Object hovered, returned by deck.gl.\n * @param mapIndex - Optional property for limiting the display of the `<MapPopover>` to the `<MapContainer>` the user is interacting with.\n * @returns action\n * @public\n */\nexport function onLayerHover(\n  info: PickInfo<any> | null,\n  mapIndex?: number\n): Merge<OnLayerHoverUpdaterAction, {type: typeof ActionTypes.LAYER_HOVER}> {\n  return {\n    type: ActionTypes.LAYER_HOVER,\n    info,\n    mapIndex\n  };\n}\n\nexport type OnLayerClickUpdaterAction = {\n  info: PickInfo<any> | null;\n};\n/**\n * Trigger layer click event with clicked object\n * @memberof visStateActions\n * @param info - Object clicked, returned by deck.gl\n * @returns action\n * @public\n */\nexport function onLayerClick(\n  info: PickInfo<any> | null\n): Merge<OnLayerClickUpdaterAction, {type: typeof ActionTypes.LAYER_CLICK}> {\n  return {\n    type: ActionTypes.LAYER_CLICK,\n    info\n  };\n}\n\nexport type OnMapClickUpdaterAction = void;\n/**\n * Trigger map click event, unselect clicked object\n * @memberof visStateActions\n * @returns action\n * @public\n */\nexport function onMapClick(): Merge<OnMapClickUpdaterAction, {type: typeof ActionTypes.MAP_CLICK}> {\n  return {\n    type: ActionTypes.MAP_CLICK\n  };\n}\n\nexport type OnMouseMoveUpdaterAction = {\n  evt;\n};\n/**\n * Trigger map mouse moveevent, payload would be\n * React-map-gl MapLayerMouseEvent\n * https://visgl.github.io/react-map-gl/docs/api-reference/types#maplayermouseevent\n *\n * @memberof visStateActions\n * @param evt - MapLayerMouseEvent\n * @returns action\n * @public\n */\nexport function onMouseMove(\n  evt\n): Merge<OnMouseMoveUpdaterAction, {type: typeof ActionTypes.MOUSE_MOVE}> {\n  return {\n    type: ActionTypes.MOUSE_MOVE,\n    evt\n  };\n}\n\nexport type ToggleLayerForMapUpdaterAction = {\n  mapIndex: number;\n  layerId: string;\n};\n/**\n * Toggle visibility of a layer in a split map\n * @memberof visStateActions\n * @param mapIndex - index of the split map\n * @param layerId - id of the layer\n * @returns action\n * @public\n */\nexport function toggleLayerForMap(\n  mapIndex: number,\n  layerId: string\n): Merge<ToggleLayerForMapUpdaterAction, {type: typeof ActionTypes.TOGGLE_LAYER_FOR_MAP}> {\n  return {\n    type: ActionTypes.TOGGLE_LAYER_FOR_MAP,\n    mapIndex,\n    layerId\n  };\n}\n\ntype FilterPlotNewProp = {\n  yAxis?: null | Record<string, any>;\n  plotType?: {type: string};\n};\nexport type SetFilterPlotUpdaterAction = {\n  idx: number;\n  newProp: FilterPlotNewProp;\n  valueIndex?: number;\n};\n/**\n * Set the property of a filter plot\n * @memberof visStateActions\n * @param idx\n * @param newProp key value mapping of new prop `{yAxis: 'histogram'}`\n * @param valueIndex dataId index\n * @returns action\n * @public\n */\nexport function setFilterPlot(\n  idx: number,\n  newProp: FilterPlotNewProp,\n  valueIndex?: number\n): Merge<SetFilterPlotUpdaterAction, {type: typeof ActionTypes.SET_FILTER_PLOT}> {\n  return {\n    type: ActionTypes.SET_FILTER_PLOT,\n    idx,\n    newProp,\n    valueIndex\n  };\n}\n\nexport type SetMapInfoUpdaterAction = {\n  info: any;\n};\n/**\n * Set the property of a filter plot\n * @memberof visStateActions\n * @param info\n * @returns action\n * @public\n */\nexport function setMapInfo(\n  info: any\n): Merge<SetMapInfoUpdaterAction, {type: typeof ActionTypes.SET_MAP_INFO}> {\n  return {\n    type: ActionTypes.SET_MAP_INFO,\n    info\n  };\n}\n\nexport type LoadFilesUpdaterAction = {\n  files: File[];\n  onFinish?(result: any): any;\n};\n/**\n * Trigger file loading dispatch `addDataToMap` if succeed, or `loadFilesErr` if failed\n * @memberof visStateActions\n * @param files array of fileblob\n * @returns action\n * @public\n */\nexport function loadFiles(\n  files: File[],\n  onFinish?: (result: any) => any\n): Merge<LoadFilesUpdaterAction, {type: typeof ActionTypes.LOAD_FILES}> {\n  return {\n    type: ActionTypes.LOAD_FILES,\n    files,\n    onFinish\n  };\n}\n\n/**\n * Called with next file to load\n * @memberof visStateActions\n * @returns action\n * @public\n */\nexport function loadNextFile(): {type: typeof ActionTypes.LOAD_NEXT_FILE} {\n  return {\n    type: ActionTypes.LOAD_NEXT_FILE\n  };\n}\n\nexport type loadFilesSuccessUpdaterAction = {\n  result: FileCacheItem[];\n};\n/**\n * called when all files are processed and loaded\n * @memberof visStateActions\n * @param result\n * @returns action\n */\nexport function loadFilesSuccess(\n  result: FileCacheItem[]\n): Merge<loadFilesSuccessUpdaterAction, {type: typeof ActionTypes.LOAD_FILES_SUCCESS}> {\n  return {\n    type: ActionTypes.LOAD_FILES_SUCCESS,\n    result\n  };\n}\n\nexport type LoadFileStepSuccessAction = {\n  fileName: string;\n  fileCache: FileCacheItem[];\n};\n/**\n * called when successfully loaded one file, ready to move on to the next one\n * @memberof visStateActions\n * @param result\n * @returns action\n */\nexport function loadFileStepSuccess({\n  fileName,\n  fileCache\n}: {\n  fileName: string;\n  fileCache: FileCacheItem[];\n}): Merge<LoadFileStepSuccessAction, {type: typeof ActionTypes.LOAD_FILE_STEP_SUCCESS}> {\n  return {\n    type: ActionTypes.LOAD_FILE_STEP_SUCCESS,\n    fileName,\n    fileCache\n  };\n}\n\nexport type LoadFilesErrUpdaterAction = {\n  fileName: string;\n  error: any;\n};\n/**\n * Trigger loading file error\n * @memberof visStateActions\n * @param  error\n * @returns action\n * @public\n */\n\nexport function loadFilesErr(\n  fileName: string,\n  error: any\n): Merge<LoadFilesErrUpdaterAction, {type: typeof ActionTypes.LOAD_FILES_ERR}> {\n  return {\n    type: ActionTypes.LOAD_FILES_ERR,\n    fileName,\n    error\n  };\n}\n\nexport type SetFeaturesUpdaterAction = {\n  features: Feature[];\n};\n/**\n * Store features to state\n * @memberof visStateActions\n * @param features\n * @returns action\n */\nexport function setFeatures(\n  features: Feature[]\n): Merge<SetFeaturesUpdaterAction, {type: typeof ActionTypes.SET_FEATURES}> {\n  return {\n    type: ActionTypes.SET_FEATURES,\n    features\n  };\n}\n\nexport type SetPolygonFilterLayerUpdaterAction = {\n  layer: Layer;\n  feature: Feature;\n};\n/**\n * It will apply the provide feature as filter to the given layer.\n * If the given feature is already applied as filter to the layer, it will remove the layer from the filter\n * @memberof visStateActions\n * @param layer\n * @param feature\n * @returns action\n */\nexport function setPolygonFilterLayer(\n  layer: Layer,\n  feature: Feature\n): Merge<SetPolygonFilterLayerUpdaterAction, {type: typeof ActionTypes.SET_POLYGON_FILTER_LAYER}> {\n  return {\n    type: ActionTypes.SET_POLYGON_FILTER_LAYER,\n    layer,\n    feature\n  };\n}\n\nexport type SetSelectedFeatureUpdaterAction = {\n  feature: Feature | null;\n  selectionContext?: FeatureSelectionContext;\n};\n\n/**\n * Set the current feature to be edited/deleted,\n * and the context of how the feature was selected.\n * @memberof visStateActions\n * @param feature\n * @param selectionContext\n * @returns action\n */\nexport function setSelectedFeature(\n  feature: Feature | null,\n  selectionContext?: FeatureSelectionContext\n): Merge<SetSelectedFeatureUpdaterAction, {type: typeof ActionTypes.SET_SELECTED_FEATURE}> {\n  return {\n    type: ActionTypes.SET_SELECTED_FEATURE,\n    feature,\n    selectionContext\n  };\n}\n\nexport type DeleteFeatureUpdaterAction = {\n  feature: Feature;\n};\n/**\n * Delete the given feature\n * @memberof visStateActions\n * @param feature\n * @returns action\n */\nexport function deleteFeature(\n  feature: Feature\n): Merge<DeleteFeatureUpdaterAction, {type: typeof ActionTypes.DELETE_FEATURE}> {\n  return {\n    type: ActionTypes.DELETE_FEATURE,\n    feature\n  };\n}\n\nexport type SetEditorModeUpdaterAction = {\n  mode: string;\n};\n/** Set the map mode\n * @memberof visStateActions\n * @param mode one of EDITOR_MODES\n * @returns action\n * @public\n * @example\n * import {setMapMode} from '@kepler.gl/actions';\n * import {EDITOR_MODES} from '@kepler.gl/constants';\n *\n * this.props.dispatch(setMapMode(EDITOR_MODES.DRAW_POLYGON));\n */\nexport function setEditorMode(\n  mode: string\n): Merge<SetEditorModeUpdaterAction, {type: typeof ActionTypes.SET_EDITOR_MODE}> {\n  return {\n    type: ActionTypes.SET_EDITOR_MODE,\n    mode\n  };\n}\n\nexport type ApplyCPUFilterUpdaterAction = {\n  dataId: string | string[];\n};\n/**\n * Trigger CPU filter of selected dataset\n * @memberof visStateActions\n * @param dataId - single dataId or an array of dataIds\n * @returns action\n * @public\n */\nexport function applyCPUFilter(\n  dataId: string | string[]\n): Merge<ApplyCPUFilterUpdaterAction, {type: typeof ActionTypes.APPLY_CPU_FILTER}> {\n  return {\n    type: ActionTypes.APPLY_CPU_FILTER,\n    dataId\n  };\n}\n\nexport type ToggleEditorVisibilityUpdaterAction = void;\n/**\n * Toggle editor layer visibility\n * @memberof visStateActions\n * @return action\n */\nexport function toggleEditorVisibility(): Merge<\n  ToggleEditorVisibilityUpdaterAction,\n  {type: typeof ActionTypes.TOGGLE_EDITOR_VISIBILITY}\n> {\n  return {\n    type: ActionTypes.TOGGLE_EDITOR_VISIBILITY\n  };\n}\n\ntype FileContent = {\n  fileName: string;\n  header: string[];\n  data: any;\n};\nexport type NextFileBatchUpdaterAction = {\n  payload: {\n    /* eslint-disable no-undef */\n    gen: AsyncGenerator<FileContent>;\n    fileName: string;\n    progress?: any;\n    accumulated?: any;\n    onFinish: (result: any) => any;\n  };\n};\n/**\n * Process the next file batch\n * @memberof visStateActions\n * @param payload - batch payload\n * @return action\n */\nexport function nextFileBatch(\n  payload: NextFileBatchUpdaterAction['payload']\n): Merge<NextFileBatchUpdaterAction, {type: typeof ActionTypes.NEXT_FILE_BATCH}> {\n  return {\n    type: ActionTypes.NEXT_FILE_BATCH,\n    payload\n  };\n}\n\nexport type ProcessFileContentUpdaterAction = {\n  payload: {\n    content: FileContent;\n    fileCache: FileCacheItem[];\n  };\n};\n/**\n * Process the file content\n * @memberof visStateActions\n * @param payload - the file content\n * @return action\n */\nexport function processFileContent(\n  payload: ProcessFileContentUpdaterAction['payload']\n): Merge<ProcessFileContentUpdaterAction, {type: typeof ActionTypes.PROCESS_FILE_CONTENT}> {\n  return {\n    type: ActionTypes.PROCESS_FILE_CONTENT,\n    payload\n  };\n}\n\nexport type SetLayerAnimationTimeConfigAction = {\n  config: {\n    timezone?: string;\n    timeFormat?: string;\n  };\n};\n/**\n * Set layer animation time format and timezone\n * @memberof visStateActions\n * @param config - {timeFormat: string, timezone: string}\n * @return action\n */\nexport function setLayerAnimationTimeConfig(\n  config: SetLayerAnimationTimeConfigAction['config']\n): Merge<\n  SetLayerAnimationTimeConfigAction,\n  {type: typeof ActionTypes.SET_LAYER_ANIMATION_TIME_CONFIG}\n> {\n  return {\n    type: ActionTypes.SET_LAYER_ANIMATION_TIME_CONFIG,\n    config\n  };\n}\n\nexport type SetFilterAnimationTimeConfigAction = {\n  idx: number;\n  config: {\n    timezone?: string;\n    timeFormat?: string;\n  };\n};\n/**\n * Set Filter animation time format and timezone\n * @memberof visStateActions\n * @param idx\n * @param config\n * @return action\n */\nexport function setFilterAnimationTimeConfig(\n  idx: SetFilterAnimationTimeConfigAction['idx'],\n  config: SetFilterAnimationTimeConfigAction['config']\n): Merge<\n  SetFilterAnimationTimeConfigAction,\n  {type: typeof ActionTypes.SET_FILTER_ANIMATION_TIME_CONFIG}\n> {\n  return {\n    type: ActionTypes.SET_FILTER_ANIMATION_TIME_CONFIG,\n    idx,\n    config\n  };\n}\n\nexport type LayerFilteredItemsChangeAction = {\n  event: {\n    id: string;\n    count: number;\n  };\n  layer: Layer;\n};\n\n/**\n * deck.gl layer gpu filter callback\n * @memberof visStateActions\n * @param layer\n * @param event\n * @return action\n */\nexport function layerFilteredItemsChange(\n  layer: LayerFilteredItemsChangeAction['layer'],\n  event: LayerFilteredItemsChangeAction['event']\n): Merge<LayerFilteredItemsChangeAction, {type: typeof ActionTypes.LAYER_FILTERED_ITEMS_CHANGE}> {\n  return {\n    type: ActionTypes.LAYER_FILTERED_ITEMS_CHANGE,\n    layer,\n    event\n  };\n}\n\nexport type WMSFeatureInfoAction = {\n  layer: Layer;\n  featureInfo: Array<{name: string; value: string}> | string | null;\n  coordinate?: [number, number] | null;\n};\n\n/**\n * WMS layer feature info callback\n * @memberof visStateActions\n * @param layer\n * @param featureInfo\n * @param coordinate\n * @return action\n */\nexport function wmsFeatureInfo(\n  layer: WMSFeatureInfoAction['layer'],\n  featureInfo: WMSFeatureInfoAction['featureInfo'],\n  coordinate?: WMSFeatureInfoAction['coordinate']\n): Merge<WMSFeatureInfoAction, {type: typeof ActionTypes.WMS_FEATURE_INFO}> {\n  return {\n    type: ActionTypes.WMS_FEATURE_INFO,\n    layer,\n    featureInfo,\n    coordinate\n  };\n}\n\nexport type SyncTimeFilterWithLayerTimelineAction = {\n  idx: number;\n  enable: boolean;\n};\n\n/**\n * Sync time filter with layer timeline\n * @memberof visStateActions\n * @param idx\n * @param enable\n * @return action\n */\nexport function syncTimeFilterWithLayerTimeline(\n  idx: SyncTimeFilterWithLayerTimelineAction['idx'],\n  enable: SyncTimeFilterWithLayerTimelineAction['enable']\n): Merge<\n  SyncTimeFilterWithLayerTimelineAction,\n  {type: typeof ActionTypes.SYNC_TIME_FILTER_WITH_LAYER_TIMELINE}\n> {\n  return {\n    type: ActionTypes.SYNC_TIME_FILTER_WITH_LAYER_TIMELINE,\n    idx,\n    enable\n  };\n}\n\nexport type setTimeFilterSyncTimelineModeAction = {\n  id: string;\n  mode: SyncTimelineMode;\n};\n\n/**\n * Set time filter sync timeline mode\n * @memberof visStateActions\n * @param id\n * @param mode\n * @return action\n */\nexport function setTimeFilterSyncTimelineMode({\n  id,\n  mode\n}: setTimeFilterSyncTimelineModeAction): Merge<\n  setTimeFilterSyncTimelineModeAction,\n  {type: typeof ActionTypes.SYNC_TIME_FILTER_TIMELINE_MODE}\n> {\n  return {\n    type: ActionTypes.SYNC_TIME_FILTER_TIMELINE_MODE,\n    id,\n    mode\n  };\n}\n\nexport type CreateNewDatasetSuccessPayload = {\n  results: (PromiseFulfilledResult<KeplerTable> | PromiseRejectedResult)[];\n  addToMapOptions: AddDataToMapPayload['options'];\n};\n\n/**\n * Called when a new dataset is created successfully via async table methods\n * @memberof visStateActions\n * @param payload\n * @param payload.results - results of promises.allSettlted\n * @returns\n */\nexport const createNewDatasetSuccess = createAction<CreateNewDatasetSuccessPayload>(\n  ActionTypes.CREATE_NEW_DATASET_SUCCESS\n);\n\nexport type SetLoadingIndicatorPayload = {\n  change: number;\n};\n\n/**\n * Change of number of active loading items, used to render loading indicator.\n * @memberof visStateActions\n * @param payload\n * @param payload.change Change of number of active loading actions.\n * @public\n */\nexport const setLoadingIndicator = createAction<SetLoadingIndicatorPayload>(\n  ActionTypes.SET_LOADING_INDICATOR\n);\n\n/**\n * This declaration is needed to group actions in docs\n */\n/**\n * Actions handled mostly by `visState` reducer.\n * They manage how data is processed, filtered and displayed on the map by operates on layers,\n * filters and interaction settings.\n *\n * @public\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst visStateActions = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n"
  },
  {
    "path": "src/actions/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": false, //TODO needs to be removed once all isolations are ready\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/ai-assistant/README.md",
    "content": "# Kepler.gl AI Assistant Module\n\nThe AI Assistant is a module that adds an AI chatbot to Kepler.gl. This module aims to integrates Kepler.gl with AI-powered capabilities, enabling it to interact with multiple AI models seamlessly.\n\n## Overview\n\nThe system is designed to enable Kepler.gl, a React-based single-page application, to integrate an AI Assistant Module for performing tasks with large language models (LLMs) like OpenAI GPT models, Google Gemini models, Ollama models, etc.\n\n![image](https://github.com/user-attachments/assets/77a240f1-037f-488e-8261-b7e82c42606d)\n\nBelow is a flow map that shows how a user can update a basemap in Kepler.gl through a simple AI-driven prompt, showcasing the integration of LLMs with application actions and rendering.\n\n![image](https://github.com/user-attachments/assets/17992157-3393-4fcb-8e72-7edf46268c6c)\n\nThe AI Assistant Module also provides a set of tools to support data analysis and visualization. These AI Tools are designed to be used in conjunction with the Kepler.gl application and transform kepler.gl into a powerful spatial data analysis and visualization tool. For more details about the AI Assistant Module, please to https://github.com/geodacenter/openassistant.\n\n## AI Tools\n\nLLMs use these AI Tools to perform spatial data analysis and visualization tasks to help users explore and understand their data.\n\nFor example, a user can ask the AI Assistant to simply change the basemap to a `voyager` basemap, and the AI Assistant will call the `basemap` tool to change the basemap.\n\n<img width=\"741\" alt=\"Screenshot 2025-05-30 at 11 54 21 AM\" src=\"https://github.com/user-attachments/assets/6e7a1e45-17b1-48c9-9ce6-5ead3430d703\" />\n\nFor complex tasks, the AI Assistant can use multiple tools to perform the task. For example, a user can ask the AI Assistant if the points dataset loaded in kepler.gl is clustering in zipcode areas. The AI Assistant could call the following tools to perform the task:\n\n1. `mapBoundary` to get the boundary of current map view\n2. `queryUSZipcode` to get a list of zipcodes using the map boundary\n3. `usZipcode` to fetch the geometries of the zipcodes from Github site\n4. `saveData` to save the zipcode areas as a new GeoJSON dataset in kepler.gl\n5. `spatialJoin` to count the number of points in each zipcode area\n6. `saveData` to save the spatialJoin result as a new dataset in kepler.gl\n7. `weightsCreation` to create a e.g. queen contiguity weights using the spatialJoin result\n8. `local Moran's I` to apply local Moran's I using the counts and the queen contiguity weights\n9. `saveData` to save the local Moran's I result as a new dataset in kepler.gl\n10. `addLayer` to add the local Moran's I result as a new layer in kepler.gl\n\n![kepler-vectortile-ai-3](https://github.com/user-attachments/assets/406afbfe-4671-42a6-8f38-90cdf171c363)\n\nThese fine grained spatial tools are designed to transform the LLMs, which are fundarmentally statistical language models, into powerful spatial data analysis and visualization AI Agent.\n\nAs of May 2025, the AI Assistant Module supports the following AI Tools:\n\n- Kepler.gl Tools\n\n  - [basemap](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/basemap-tool.tsx)\n  - [mapBoundary](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/boundary-tool.tsx)\n  - [addLayer](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/layer-creation-tool.tsx)\n  - [updateLayerColor](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/layer-style-tool.tsx)\n  - [loadData](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/loaddata-tool.tsx)\n  - [saveData](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/kepler-tools/savedata-tool.tsx)\n\n- Plot Tools\n\n  - eCharts Plots\n    - [boxplot](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/echarts/boxplot/tool.ts)\n    - [bubbleChart](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/echarts/bubble-chart/tool.ts)\n    - [histogram](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/echarts/histogram/tool.ts)\n    - [parallel coordinate](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/echarts/pcp/tool.ts)\n    - [scatterplot](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/echarts/scatterplot/tool.ts)\n  - [Vega-Lite Plots](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/plots/src/vegalite/tool.ts) (coming soon)\n\n- Query Tools\n\n  - [genericQuery](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/duckdb/src/tool.ts)\n  - [filterDataset](https://github.com/keplergl/kepler.gl/blob/main/src/ai-assistant/src/tools/query-tool.tsx)\n\n- OSM Tools\n\n  - [geocoding](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/geocoding.ts)\n  - [isochrone](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/isochrone.ts)\n  - [routing](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/routing.ts)\n  - [roads](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/roads.ts)\n  - [US County](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/us/county.ts)\n  - [US State](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/us/state.ts)\n  - [US Zipcode](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/us/zipcode.ts)\n  - [Query US Zipcodes](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/osm/src/us/queryZipcode.ts)\n\n- Spatial Analysis Tools (powered by [Geoda](https://geodacenter.github.io/geoda-lib/))\n  - Geo Tools\n    - [area](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/area.ts)\n    - [buffer](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/buffer.ts)\n    - [centroid](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/centroid.ts)\n    - [dissolve](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/dissolve.ts)\n    - [length](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/length.ts)\n    - [perimeter](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_ops/perimeter.ts)\n  - Data Tools\n    - [data classification](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/data-classify/tool.ts)\n      - quantile\n      - natural Jenks breaks\n      - equal interval\n      - percentile\n      - box\n      - standard deviation\n      - unique values\n    - data by rates (comming soon)\n  - Spatial Join\n    - [spatialJoin](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_join/tool.ts)\n    - [spatialFilter](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_join/spatial-filter.ts)\n  - Spatial Weights\n    - [weightsCreation](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/weights/tool.ts)\n      - queen/rook\n      - k-nearest neighbors\n      - distance band\n  - Spatial Autocorrelation\n    - [global Moran's I](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/spatial_autocorrelation/global-moran.ts)\n    - [local spatial autocorrelation](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/lisa/tool.ts)\n      - local Moran's I\n      - local Geary's C\n      - local Getis-Ord Gi\\*\n      - quantile LISA\n    - [spatial regression](https://github.com/GeoDaCenter/openassistant/blob/main/packages/tools/geoda/src/regression/tool.ts)\n      - OLS with spatial diagnostics\n      - Spatial Lag Model\n      - Spatial Error Model\n\n## Tutorials\n\nNext: [Spatial Data Analysis using Kepler.gl AI Assistant](https://github.com/keplergl/kepler.gl/blob/main/docs/spatial-analysis-tutorial/README.md)\n"
  },
  {
    "path": "src/ai-assistant/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/ai-assistant/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/ai-assistant\",\n  \"author\": \"Xun Li<lixun910@gmail.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl AI assistant\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@ai-sdk/anthropic\": \"^1.2.11\",\n    \"@ai-sdk/deepseek\": \"^0.2.14\",\n    \"@ai-sdk/google\": \"^1.2.18\",\n    \"@ai-sdk/xai\": \"^1.2.16\",\n    \"@kepler.gl/components\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/layers\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@openassistant/core\": \"^0.5.17\",\n    \"@openassistant/duckdb\": \"^0.5.17\",\n    \"@openassistant/echarts\": \"^0.5.17\",\n    \"@openassistant/geoda\": \"^0.5.17\",\n    \"@openassistant/osm\": \"^0.5.17\",\n    \"@openassistant/plots\": \"^0.5.17\",\n    \"@openassistant/tables\": \"^0.5.17\",\n    \"@openassistant/ui\": \"^0.5.17\",\n    \"@openassistant/utils\": \"^0.5.17\",\n    \"color-interpolate\": \"^1.0.5\",\n    \"global\": \"^4.3.0\",\n    \"ollama-ai-provider-v2\": \"^0.0.5\",\n    \"react-intl\": \"^6.3.0\",\n    \"usehooks-ts\": \"^3.1.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Xun Li <lixun910@gmail.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/ai-assistant/src/actions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {MessageModel} from '@openassistant/core';\nimport {AiAssistantConfig} from './reducers';\n\nconst ACTION_PREFIX = '@@openassistant/';\n\nexport const UPDATE_AI_ASSISTANT_CONFIG = `${ACTION_PREFIX}UPDATE_AI_ASSISTANT_CONFIG`;\nexport const UPDATE_AI_ASSISTANT_MESSAGES = `${ACTION_PREFIX}UPDATE_AI_ASSISTANT_MESSAGES`;\nexport const SET_START_SCREEN_CAPTURE = `${ACTION_PREFIX}SET_START_SCREEN_CAPTURE`;\nexport const SET_SCREEN_CAPTURED = `${ACTION_PREFIX}SET_SCREEN_CAPTURED`;\nexport const SET_MAP_BOUNDARY = `${ACTION_PREFIX}SET_MAP_BOUNDARY`;\n// Action creators\nexport function updateAiAssistantConfig(config: AiAssistantConfig) {\n  return {\n    type: UPDATE_AI_ASSISTANT_CONFIG,\n    payload: config\n  };\n}\n\nexport function updateAiAssistantMessages(messages: MessageModel[]) {\n  return {\n    type: UPDATE_AI_ASSISTANT_MESSAGES,\n    payload: messages\n  };\n}\n\nexport function setStartScreenCapture(flag: boolean) {\n  return {\n    type: SET_START_SCREEN_CAPTURE,\n    payload: flag\n  };\n}\n\nexport function setScreenCaptured(screenshot: string) {\n  return {\n    type: SET_SCREEN_CAPTURED,\n    payload: screenshot\n  };\n}\n\nexport function setMapBoundary(nw: [number, number], se: [number, number]) {\n  return {\n    type: SET_MAP_BOUNDARY,\n    payload: {nw, se}\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/components/ai-assistant-component.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useRef, useState} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport styled from 'styled-components';\nimport {textColorLT, theme} from '@kepler.gl/styles';\nimport {MessageModel, useAssistant} from '@openassistant/core';\nimport {AiAssistant} from '@openassistant/ui';\nimport '@openassistant/echarts/dist/index.css';\nimport '@openassistant/ui/dist/index.css';\nimport {setScreenCaptured, setStartScreenCapture, updateAiAssistantMessages} from '../actions';\nimport {\n  ASSISTANT_DESCRIPTION,\n  ASSISTANT_NAME,\n  ASSISTANT_VERSION,\n  INSTRUCTIONS,\n  PROMPT_IDEAS,\n  WELCOME_MESSAGE\n} from '../constants';\nimport {getDatasetContext} from '../tools/utils';\nimport {setupLLMTools} from '../tools/tools';\nimport {State} from './ai-assistant-manager';\n\nconst StyledAiAssistantComponent = styled.div`\n  height: 100%;\n  padding-bottom: 4px;\n\n  * {\n    font-size: 11px;\n  }\n`;\n\nexport function AiAssistantComponent() {\n  const visState = useSelector((state: State) => state.demo.keplerGl.map.visState);\n  const aiAssistant = useSelector((state: State) => state.demo.aiAssistant);\n  const dispatch = useDispatch();\n\n  // define LLM functions\n  const tools = setupLLMTools({visState, aiAssistant, dispatch});\n\n  // enable voice and screen capture\n  const enableVoiceAndScreenCapture =\n    aiAssistant?.config.provider === 'openai' || aiAssistant?.config.provider === 'google' || false;\n\n  // define assistant props\n  const assistantProps = {\n    name: ASSISTANT_NAME,\n    description: ASSISTANT_DESCRIPTION,\n    version: ASSISTANT_VERSION,\n    modelProvider: aiAssistant?.config.provider || '',\n    model: aiAssistant?.config.model || '',\n    apiKey: aiAssistant?.config.apiKey || '',\n    baseUrl: aiAssistant?.config.baseUrl || '',\n    tools\n  };\n\n  const [datasetMetaData, setDatasetMetaData] = useState<string>('');\n\n  const [ideas, setIdeas] = useState<{title: string; description: string}[]>([]);\n\n  const [restartKey, setRestartKey] = useState<number>(0);\n\n  // get dataset meta data and re-initialize assistant when datasets or layers change\n  useEffect(() => {\n    const metaData = getDatasetContext(visState?.datasets, visState?.layers || []);\n    setDatasetMetaData(metaData);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [visState?.datasets, visState?.layers]);\n\n  // use dataset meta data in LLM instructions\n  const instructions = `${INSTRUCTIONS}\\n\\n${datasetMetaData}`;\n\n  // generate ideas from LLM\n  const {temporaryPrompt, restartChat: libraryRestartChat} = useAssistant({...assistantProps, instructions});\n  \n  const restartChatRef = useRef(libraryRestartChat);\n  restartChatRef.current = libraryRestartChat;\n\n  const generateIdeas = async () => {\n    try {\n      const response = await temporaryPrompt({\n        prompt: PROMPT_IDEAS,\n        temperature: 1.0\n      });\n      // find [{},{}...] in the text and parse it as json, handling whitespace\n      const match = response?.match(/\\[\\s*\\{.*\\}\\s*\\]/s);\n      if (match) {\n        const json = JSON.parse(match[0]);\n        setIdeas(json);\n      }\n    } catch (error) {\n      console.error('Error generating ideas', error);\n    }\n  };\n\n  useEffect(() => {\n    // get ideas UI component\n    if (ideas.length === 0 && datasetMetaData.length > 0) {\n      generateIdeas();\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [datasetMetaData]);\n\n  const onRestartAssistant = async () => {\n    dispatch(updateAiAssistantMessages([]));\n    \n    try {\n      await restartChatRef.current();\n    } catch (e) {\n      console.error('Error restarting chat:', e);\n    }\n    \n    setRestartKey(prev => prev + 1);\n  };\n\n  const onMessagesUpdated = (messages: MessageModel[]) => {\n    dispatch(updateAiAssistantMessages(messages));\n  };\n\n  const onScreenshotClick = () => {\n    dispatch(setStartScreenCapture(true));\n  };\n\n  const onRemoveScreenshot = () => {\n    dispatch(setScreenCaptured(''));\n  };\n\n  return (\n    <StyledAiAssistantComponent className=\"ai-assistant-component\">\n      <AiAssistant\n        key={restartKey}\n        {...assistantProps}\n        instructions={instructions}\n        theme={theme.textColor === textColorLT ? 'light' : 'dark'}\n        welcomeMessage={WELCOME_MESSAGE}\n        temperature={aiAssistant?.config.temperature || 0}\n        topP={aiAssistant?.config.topP || 0}\n        initialMessages={aiAssistant?.messages}\n        onMessagesUpdated={onMessagesUpdated}\n        enableVoice={enableVoiceAndScreenCapture}\n        enableScreenCapture={enableVoiceAndScreenCapture}\n        onScreenshotClick={onScreenshotClick}\n        screenCapturedBase64={aiAssistant?.screenshotToAsk.screenCaptured || ''}\n        onRemoveScreenshot={onRemoveScreenshot}\n        onRestartChat={onRestartAssistant}\n        fontSize={'text-tiny'}\n        botMessageClassName={''}\n        githubIssueLink={'https://github.com/keplergl/kepler.gl/issues'}\n        ideas={ideas}\n        onRefreshIdeas={generateIdeas}\n      />\n    </StyledAiAssistantComponent>\n  );\n}\n"
  },
  {
    "path": "src/ai-assistant/src/components/ai-assistant-config.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState} from 'react';\nimport styled from 'styled-components';\nimport {useSelector, useDispatch} from 'react-redux';\nimport {\n  Input,\n  PanelLabelWrapper,\n  ItemSelector,\n  RangeSliderFactory,\n  Button,\n  LoadingSpinner,\n  appInjector,\n  Checkbox\n} from '@kepler.gl/components';\nimport {State} from '../index';\nimport ApiKey from '../icons/api-key';\nimport {PROVIDER_MODELS, PROVIDER_DEFAULT_BASE_URLS} from '../config/models';\nimport {useLocalStorage} from 'usehooks-ts';\nimport {GetAssistantModelByProvider} from '@openassistant/core';\nimport {updateAiAssistantConfig} from '../actions';\nimport {FormattedMessage, useIntl} from 'react-intl';\n\nconst SectionTitle = styled.div`\n  font-size: ${props => props.theme.inputFontSize};\n  color: ${props => props.theme.effectPanelTextSecondary1};\n  text-transform: capitalize;\n`;\n\nconst StyledAiAssistantConfig = styled.div`\n  padding: 12px;\n  font-size: ${props => props.theme.primaryBtnFontSizeDefault};\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n  width: 100%;\n  height: 100%;\n\n  .api-key-input {\n    box-shadow: ${props => props.theme.boxShadow};\n    width: 100%;\n    .api-key-input__icon {\n      position: absolute;\n      height: ${props => props.theme.geocoderInputHeight}px;\n      width: 30px;\n      padding-left: 6px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: ${props => props.theme.subtextColor};\n    }\n\n    input {\n      padding: 4px 36px;\n      height: ${props => props.theme.geocoderInputHeight}px;\n      caret-color: unset;\n    }\n  }\n`;\n\n// Ollama model input wrapper: checkbox + 'Input Model Name:' + input\n// all children element have width based on the content\nconst OllamaModelInputWrapper = styled.div`\n  display: flex;\n  flex-direction: row;\n  gap: 4px;\n  align-items: center;\n`;\n\nconst StyledWrapper = styled.div`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n`;\n\nconst StyledItemSelector = styled(ItemSelector)`\n  .item-selector__dropdown {\n    padding-left: 10px;\n    border-radius: 4px;\n  }\n  .active {\n    border-color: ${props => props.theme.activeColor};\n    border-radius: 4px 4px 0px 0px !important;\n  }\n  width: 100%;\n`;\n\nconst StyleSliderWrapper = styled.div`\n  width: 100%;\n  align-self: flex-start;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  .kg-range-slider__input {\n    height: 32px;\n    text-align: center;\n    padding: 3px 6px;\n  }\n  .kg-slider {\n    padding-left: 6px;\n  }\n  .kg-range-slider {\n    padding: 0px !important;\n  }\n`;\n\nconst StyledButton = styled.div`\n  width: 100%;\n  align-self: flex-start;\n  margin-top: 12px;\n\n  button div {\n    display: flex;\n    align-items: center;\n    gap: 4px;\n    margin-right: 4px;\n  }\n`;\n\nconst StyleErrorMessage = styled.div`\n  font-size: ${props => props.theme.primaryBtnFontSizeDefault};\n  background-color: ${props => props.theme.errorColor};\n  border-radius: 4px;\n  padding: 4px 8px;\n  color: ${props => props.theme.errorTextColor};\n`;\n\nconst RangeSlider = appInjector.get(RangeSliderFactory);\n\nexport function AiAssistantConfig() {\n  const dispatch = useDispatch();\n  const aiAssistantConfig = useSelector((state: State) => state.demo.aiAssistant.config);\n  const intl = useIntl();\n\n  const [provider, setProvider] = useLocalStorage(\n    'ai-assistant-provider',\n    aiAssistantConfig.provider || 'openai'\n  );\n  const [model, setModel] = useLocalStorage(\n    'ai-assistant-model',\n    aiAssistantConfig.model || PROVIDER_MODELS[provider][0]\n  );\n  const [apiKey, setApiKey] = useLocalStorage(\n    'ai-assistant-api-key',\n    aiAssistantConfig.apiKey || ''\n  );\n  const [temperature, setTemperature] = useLocalStorage(\n    'ai-assistant-temperature',\n    aiAssistantConfig.temperature || 0.0\n  );\n  const [topP, setTopP] = useLocalStorage('ai-assistant-top-p', aiAssistantConfig.topP || 1.0);\n  const [baseUrl, setBaseUrl] = useLocalStorage(\n    'ai-assistant-base-url',\n    aiAssistantConfig.baseUrl || PROVIDER_DEFAULT_BASE_URLS[provider]\n  );\n  const [mapboxToken, setMapboxToken] = useLocalStorage(\n    'ai-assistant-mapbox-token',\n    aiAssistantConfig.mapboxToken || ''\n  );\n  const [ollamaModelInputChecked, setOllamaModelInputChecked] = useState(false);\n  const [ollamaModelInputValue, setOllamaModelInputValue] = useState('');\n  const [connectionError, setConnectionError] = useState(false);\n  const [errorMessage, setErrorMessage] = useState('');\n  const [isRunning, setIsRunning] = useState(false);\n\n  const onAiProviderSelect = (value: string | number | boolean | object | null) => {\n    if (typeof value === 'string') {\n      setProvider(value);\n      setModel(PROVIDER_MODELS[value][0]);\n      setBaseUrl(PROVIDER_DEFAULT_BASE_URLS[value]);\n      setConnectionError(false);\n      setErrorMessage('');\n    }\n  };\n\n  const onLLMModelSelect = (value: string | number | boolean | object | null) => {\n    if (typeof value === 'string') {\n      setModel(value);\n    }\n  };\n\n  const onApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setApiKey(e.target.value);\n    // reset previous key error if any\n    setConnectionError(false);\n    setErrorMessage('');\n  };\n\n  const onTemperatureChange = (value: number[]) => {\n    setTemperature(value[1]);\n  };\n\n  const onTopPChange = (value: number[]) => {\n    setTopP(value[1]);\n  };\n\n  const onBaseUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setBaseUrl(e.target.value);\n    setConnectionError(false);\n    setErrorMessage('');\n  };\n\n  const onMapboxTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setMapboxToken(e.target.value);\n  };\n\n  const onOllamaModelInputChecked = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setOllamaModelInputChecked(e.target.checked);\n    if (!e.target.checked) {\n      // use model from selector\n      setModel('');\n    }\n  };\n\n  const onOllamaModelInputValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setOllamaModelInputValue(e.target.value);\n    setModel(e.target.value);\n  };\n\n  const onStartChat = async () => {\n    setIsRunning(true);\n    try {\n      const timeoutPromise = new Promise((_, reject) => {\n        setTimeout(() => reject(new Error('Connection timeout after 15 seconds')), 15000);\n      });\n\n      const AssistantModel = await GetAssistantModelByProvider({\n        provider: provider\n      });\n\n      if (!AssistantModel || !AssistantModel.configure || !AssistantModel.testConnection) {\n        throw new Error('Failed to initialize AI model');\n      }\n\n      // configure model\n      AssistantModel.configure({\n        model: model,\n        baseURL: baseUrl || PROVIDER_DEFAULT_BASE_URLS[provider],\n        apiKey: apiKey,\n        temperature: Number(temperature),\n        topP: Number(topP)\n      });\n\n      const success = (await Promise.race([\n        AssistantModel.testConnection(apiKey, model),\n        timeoutPromise\n      ])) as boolean;\n\n      const errorMessage = !success\n        ? provider === 'ollama'\n          ? 'Connection failed: maybe invalid Base URL'\n          : 'Connection failed: maybe invalid API Key or Base URL'\n        : '';\n      setConnectionError(!success);\n      setErrorMessage(errorMessage);\n      dispatch(\n        updateAiAssistantConfig({\n          provider: provider,\n          model: model,\n          apiKey: apiKey,\n          baseUrl: baseUrl || PROVIDER_DEFAULT_BASE_URLS[provider],\n          isReady: success,\n          temperature: temperature,\n          topP: topP,\n          mapboxToken: mapboxToken\n        })\n      );\n    } catch (error) {\n      setConnectionError(true);\n      setErrorMessage(error instanceof Error ? error.message : 'Connection failed');\n    } finally {\n      setIsRunning(false);\n    }\n  };\n\n  return (\n    <StyledAiAssistantConfig className=\"ai-assistant-config__type\">\n      <PanelLabelWrapper>\n        <SectionTitle>\n          <FormattedMessage id=\"aiAssistantManager.aiProvider\" />\n        </SectionTitle>\n      </PanelLabelWrapper>\n      <StyledWrapper>\n        <StyledItemSelector\n          selectedItems={provider}\n          options={Object.keys(PROVIDER_MODELS)}\n          multiSelect={false}\n          disabled={false}\n          onChange={onAiProviderSelect}\n          filterOption=\"name\"\n          getOptionValue={op => op}\n          displayOption={op => op}\n          searchable={false}\n          showArrow={true}\n        />\n      </StyledWrapper>\n      <PanelLabelWrapper>\n        <SectionTitle>\n          <FormattedMessage id=\"aiAssistantManager.llmModel.title\" />\n        </SectionTitle>\n      </PanelLabelWrapper>\n      {((provider === 'ollama' && !ollamaModelInputChecked) || provider !== 'ollama') && (\n        <StyledWrapper>\n          <StyledItemSelector\n            selectedItems={model}\n            options={PROVIDER_MODELS[provider]}\n            multiSelect={false}\n            disabled={provider === 'ollama' ? ollamaModelInputChecked : false}\n            placeholder=\"Select LLM Model\"\n            onChange={onLLMModelSelect}\n            filterOption=\"name\"\n            getOptionValue={op => op}\n            displayOption={op => op}\n            searchable={false}\n            showArrow={true}\n          />\n        </StyledWrapper>\n      )}\n      {provider === 'ollama' && (\n        <OllamaModelInputWrapper>\n          <div style={{width: '250px'}}>\n            <Checkbox\n              id=\"ollama-model-input\"\n              label=\"Input Model Name\"\n              onChange={onOllamaModelInputChecked}\n              checked={ollamaModelInputChecked}\n            />\n          </div>\n          <Input\n            type=\"text\"\n            onChange={onOllamaModelInputValueChange}\n            placeholder=\"Enter Model Name\"\n            value={ollamaModelInputValue}\n            disabled={!ollamaModelInputChecked}\n          />\n        </OllamaModelInputWrapper>\n      )}\n      {provider !== 'ollama' && (\n        <>\n          <PanelLabelWrapper>\n            <SectionTitle>\n              <FormattedMessage id=\"aiAssistantManager.apiKey.title\" />\n            </SectionTitle>\n          </PanelLabelWrapper>\n          <div className=\"api-key-input\">\n            <div className=\"api-key-input__icon\">\n              <ApiKey height=\"20px\" />\n            </div>\n            <Input\n              type=\"text\"\n              onChange={onApiKeyChange}\n              placeholder={intl.formatMessage({id: 'aiAssistantManager.apiKey.placeholder'})}\n              value={apiKey}\n            />\n          </div>\n        </>\n      )}\n      <PanelLabelWrapper>\n        <SectionTitle>\n          <FormattedMessage id=\"aiAssistantManager.baseUrl.title\" />\n        </SectionTitle>\n      </PanelLabelWrapper>\n      <div className=\"api-key-input\">\n        <div className=\"api-key-input__icon\">\n          <ApiKey height=\"20px\" />\n        </div>\n        <Input\n          type=\"text\"\n          onChange={onBaseUrlChange}\n          placeholder={PROVIDER_DEFAULT_BASE_URLS[provider]}\n          value={baseUrl}\n        />\n      </div>\n      {connectionError && (\n        <StyleErrorMessage className=\"error-message\">{errorMessage}</StyleErrorMessage>\n      )}\n      <PanelLabelWrapper>\n        <SectionTitle>\n          <FormattedMessage id=\"aiAssistantManager.temperature.title\" />\n        </SectionTitle>\n      </PanelLabelWrapper>\n      <StyleSliderWrapper>\n        <RangeSlider\n          showInput={true}\n          isRanged={false}\n          value0={0}\n          value1={temperature}\n          onChange={onTemperatureChange}\n          range={[0, 2]}\n          step={0.1}\n        />\n      </StyleSliderWrapper>\n      <PanelLabelWrapper>\n        <SectionTitle>\n          <FormattedMessage id=\"aiAssistantManager.topP.title\" />\n        </SectionTitle>\n      </PanelLabelWrapper>\n      <StyleSliderWrapper>\n        <RangeSlider\n          showInput={true}\n          isRanged={false}\n          value0={0}\n          value1={topP}\n          onChange={onTopPChange}\n          range={[0, 1]}\n          step={0.1}\n        />\n      </StyleSliderWrapper>\n      <>\n        <PanelLabelWrapper>\n          <SectionTitle>Mapbox Token (optional for route/isochrone)</SectionTitle>\n        </PanelLabelWrapper>\n        <div className=\"api-key-input\">\n          <div className=\"api-key-input__icon\">\n            <ApiKey height=\"20px\" />\n          </div>\n          <Input\n            type=\"text\"\n            onChange={onMapboxTokenChange}\n            placeholder=\"Enter your Mapbox Token\"\n            value={mapboxToken}\n          />\n        </div>\n      </>\n      <StyledButton>\n        <Button onClick={onStartChat} width={'100%'}>\n          {isRunning && <LoadingSpinner size={12} />}\n          <FormattedMessage id=\"aiAssistantManager.startChat\" />\n        </Button>\n      </StyledButton>\n    </StyledAiAssistantConfig>\n  );\n}\n"
  },
  {
    "path": "src/ai-assistant/src/components/ai-assistant-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport {useSelector, useDispatch} from 'react-redux';\nimport {IntlProvider} from 'react-intl';\nimport {flattenMessages} from '@kepler.gl/utils';\nimport {messages as keplerGlMessages} from '@kepler.gl/localization';\n\nimport {MapStyle} from '@kepler.gl/reducers';\nimport {SidePanelTitleFactory, Icons} from '@kepler.gl/components';\nimport {VisState} from '@kepler.gl/schemas';\n\nimport {AiAssistantState} from '../index';\nimport {updateAiAssistantConfig} from '../actions';\nimport {AiAssistantConfig} from './ai-assistant-config';\nimport {AiAssistantComponent} from './ai-assistant-component';\nimport {messages} from '../localization';\n\nconst StyledAiAssistantPanelContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  pointer-events: none !important; /* prevent padding from blocking input */\n  flex-grow: 1;\n  justify-content: space-between;\n  overflow: hidden;\n  height: 100%;\n  width: 100%;\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\nconst StyledAiAssistantPanel = styled.div`\n  top: 0;\n  background-color: ${props => props.theme.sidePanelBg};\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  overflow: hidden;\n`;\n\nconst StyledAiAssistantPanelHeader = styled.div`\n  padding: 16px 16px 4px 16px;\n  border-bottom: 1px solid ${props => props.theme.borderColor};\n  color: ${props => props.theme.subtextColorActive};\n`;\n\nconst StyledAiAssistantPanelContent = styled.div`\n  ${props => props.theme.sidePanelScrollBar};\n  color: ${props => props.theme.subtextColorActive};\n  padding: 10px 0px 10px 0px;\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n`;\n\nconst SidePanelTitle = SidePanelTitleFactory();\n\nexport type State = {\n  demo: {\n    keplerGl: {\n      map: {\n        uiState: {locale: string};\n        visState: VisState;\n        mapStyle: MapStyle;\n      };\n    };\n    aiAssistant: AiAssistantState;\n  };\n};\n\nexport function AiAssistantPanel() {\n  const dispatch = useDispatch();\n  const aiAssistant = useSelector((state: State) => state.demo.aiAssistant);\n  const locale = useSelector((state: State) => state.demo.keplerGl.map.uiState.locale);\n\n  const onConfigButtonClick = useCallback(() => {\n    if (aiAssistant) {\n      // set aiAssistant.config.isReady to false so we can render the config component\n      dispatch(updateAiAssistantConfig({...aiAssistant.config, isReady: false}));\n    }\n  }, [aiAssistant, dispatch]);\n\n  // combine keplerGlMessages and messages\n  const combinedMessages = useMemo(() => {\n    return Object.keys(messages).reduce(\n      (acc, language) => ({\n        ...acc,\n        [language]: {\n          ...(messages[language] || {}),\n          ...(keplerGlMessages[language] || {})\n        }\n      }),\n      {}\n    );\n  }, []);\n\n  return (\n    <IntlProvider locale={locale} messages={flattenMessages(combinedMessages[locale])}>\n      <StyledAiAssistantPanelContainer className=\"ai-assistant-manager\">\n        <StyledAiAssistantPanel>\n          <StyledAiAssistantPanelHeader>\n            <SidePanelTitle className=\"ai-assistant-manager-title\" title=\"AI Assistant\">\n              <Icons.Settings onClick={onConfigButtonClick} />\n            </SidePanelTitle>\n          </StyledAiAssistantPanelHeader>\n\n          <StyledAiAssistantPanelContent>\n            {!aiAssistant?.config.isReady ? <AiAssistantConfig /> : <AiAssistantComponent />}\n          </StyledAiAssistantPanelContent>\n        </StyledAiAssistantPanel>\n      </StyledAiAssistantPanelContainer>\n    </IntlProvider>\n  );\n}\n"
  },
  {
    "path": "src/ai-assistant/src/config/models.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const PROVIDER_DEFAULT_BASE_URLS = {\n  \"openai\": \"https://api.openai.com/v1\",\n  \"anthropic\": \"https://api.anthropic.com/v1\",\n  \"google\": \"https://generativelanguage.googleapis.com/v1beta\",\n  \"deepseek\": \"https://api.deepseek.com/v1\",\n  \"xai\": \"https://api.x.ai/v1\",\n  \"ollama\": \"http://localhost:11434/api\"\n};\n\nexport const PROVIDER_MODELS = {\n  openai: [\n    'gpt-4.1',\n    'gpt-4o',\n    'gpt-4o-mini',\n    'gpt-4',\n    'gpt-3.5-turbo',\n    'o1',\n    'o1-preview',\n    'o1-mini'\n  ],\n  google: [\n    'gemini-2.5-flash',\n    'gemini-2.5-pro',\n    'gemini-2.0-flash',\n    'gemini-1.5-flash',\n    'gemini-1.5-pro'\n  ],\n  deepseek: ['deepseek-chat'],\n  anthropic: [\n    'claude-opus-4-1',\n    'claude-opus-4-0',\n    'claude-sonnet-4-0',\n    'claude-3.7-sonnet',\n    'claude-3.5-sonnet',\n    'claude-3.5-haiku',\n    'claude-3-opus',\n    'claude-3-sonnet',\n    'claude-3-haiku'\n  ],\n  xai: ['grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'],\n  ollama: ['qwen3:32b', 'gpt-oss']\n};\n"
  },
  {
    "path": "src/ai-assistant/src/constants.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const WELCOME_MESSAGE = `Hi, I am Kepler.gl AI Assistant!`;\n\nexport const INSTRUCTIONS = `You are a Kepler.gl AI Assistant. You are a helpful assistant that can help users with their spatial analysis tasks.\nPlease act like an instructor and explain your reasoning in a concise and clear manner:\n- Explain the terms in the user's question in a way that is easy to understand\n- Explain the tools in a way that is easy to understand\n- Explain the steps to achieve the user's goal in a way that is easy to understand\n- Explain the results in a way that is easy to understand\n\nNote:\n- IMPORTANT: make a plan if tools can be used to answer the question before calling tools\n- Add emojis to your responses to make them more engaging\n\n- For tool usage:\n  1. If parameters are missing, ask the user to provide them\n  2. If a tool fails:\n     a. First try to understand and fix the error\n     b. If the error persists, explain the issue to the user\n     c. Suggest alternative approaches if available\n  3. Use the most appropriate tool for each task\n  4. Chain tool calls when necessary to achieve the desired outcome\n  5. Please do not run tools in parallel\n\n- For kepler.gl tools:\n  1. IMPORTANT: use loadData tool to load a dataset from a URL if requested by the user.\n  1. Do not call addLayer tool after loadData tool or saveToolResults tool, as these tools automatically create a map layer.\n  2. For addLayer tool: if dataset can not be found, please prompt the user to save the dataset first\n  3. Do not call saveToolResults after tableTool, as tableTool will automatically save the dataset to kepler.gl\n\n- For any SQL query:\n  1. IMPORTANT: only use statements, query syntax, data types, expressions, functions, constraints and operators that are supported by DuckDB\n  2. Only include columns that already exist in the dataset in variableNames. New columns created via SQL expressions should only be referenced in the SQL query.\n  3. Please use dbTableName in the SQL query to reference the table in the database.\n  4. Please note that for data security, only first 2 rows of the result will be returned to LLM for reference, and the full result will be returned to the user.\n\n- For dataClassify tool: when classify data into bins using break values, please use the following rules:\n    a. The lower bound is inclusive, and the upper bound is exclusive for all bins except the last bin, which is inclusive of both bounds.\n    b. For example, for breaks at [1000, 1100], the classifcation or bins should be:\n      - b1: values < 1000\n      - b2: 1000 ≤ values < 1100\n      - b3: values ≥ 1100\n    c. If user provides custom breaks, there is no need to call dataClassify tool\n\n- Colocation is a map that shows the co-location of two variables V1 and V2 from a dataset A\n  1. create a categorical variable for the first variable by other tools, e.g.\n    a. breaks in quantile/box map/equal interval/natural breaks/percentile/standard deviation/custom breaks\n    b. clusters in lisa\n  2. create a categorical variable for the second varaible using the same tool\n  3. use tableTool to save the two categorical variables in a new dataset B e.g.\n    a. SELECT ..., CASE WHEN V1 < 0 THEN 1 WHEN V1 >= 5 AND V1 < 10 THEN 2 WHEN V1 >= 10 THEN 3 END AS C1, CASE WHEN V2 < 4 THEN 1 WHEN V2 >= 8 AND V2 < 9 THEN 2 WHEN V2 >= 9 THEN 3 END AS C2;\n  4. use tableTool to compare the two categorical values from dataset B and save the result in a new dataset C\n    a. keep the value if they are the same\n    b. assign -1 if they are different\n  5. create a unique values for the comparison result with Paired color scheme and assign gray color to value -1\n\n- When a user requests to standardize (or apply a similar transformation to) multiple variables in a dataset, follow this strategy:\n  1. If the tool only allows standardizing one variable at a time and creates a new dataset for each operation, always use the most recently created dataset (which contains the previously standardized variables) as the input for the next standardization.\n  2. Repeat this process for each variable to be standardized, so that the final dataset contains all the standardized variables together.\n  3. Only use the original dataset for the first standardization; for subsequent variables, use the latest dataset that includes all previous results.\n  4. After all variables are standardized, confirm that the final dataset contains all original columns plus all new standardized columns.\n\n- For data analysis:\n  1. If new dataset has been generated by tools e.g. bufferTool, centroidTool, getUsCountyTool, getUsZipcodeTool, isochrone, mergeTables, etc.:\n     a. Save them as a new dataset in kepler.gl first\n     b. Use the new dataset for spatial analysis\n  2. For clustering analysis:\n     a. Always perform a spatial statistical test (e.g., Local Moran's I)\n     b. Explain the results in context\n     c. STRICT RULE: Never use datasets generated from previous LISA tools (dataset name with \"lisa_\" prefix) as input for a new LISA analysis\n  3. For using road or line dataset in spatial analysis (e.g. local moran, spatial weights, and spatial join):\n     a. buffer the road by 1 meter first\n     b. save the buffered road as a new dataset\n     c. if needed, spatial join by buffered road (left) with points (right)\n     d. if needed, create a spatial weights using the buffered road\n     e. apply spatial analysis using the count in the result of spatial join\n\n- For spatial filtering:\n  1. IMPORTANT: when use spatial filter to filter the points within polygons, please follow the steps:\n    a. The leftDatasetName should be the points dataset\n    b. The rightDatasetName should be the polygon dataset\n    c. Apply the spatial filter tool and save the result (points)\n    d. Use the filterDataset tool to filter the result where Count > 0\n    e. Save the result of filterDataset as the final answer\n\n- For datasetName argument:\n  1. Please use the dataset name or dataset label as the datasetName argument, not the dataset id\n`;\n\nexport const PROMPT_IDEAS = `Return ONLY a JSON array of 5 ideas based on the tools in current context.\nIMPORTANT: please mention tool in a user-friendly title, and use actual field name in the description.\nDo not include any other text or explanation.\nRandomly pick 5 tools.\nFormat:\n[{\n  \"title\": \"Data Insight\",\n  \"description\": \"What is the distribution of HR60?\"\n},\n{\n  \"title\": \"Spatial Analysis\",\n  \"description\": \"Is HR60 spatially clustered?\"\n},\n];\n`;\n\nexport const ASSISTANT_NAME = 'kepler-gl-ai-assistant';\n\nexport const ASSISTANT_DESCRIPTION = 'A Kepler.gl AI Assistant';\n\nexport const ASSISTANT_VERSION = '0.0.2';\n"
  },
  {
    "path": "src/ai-assistant/src/icons/ai-star.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Icons} from '@kepler.gl/components';\n\nexport default class AiStar extends Component {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '2 2 20 20',\n    predefinedClassName: 'data-ex-icons-ai-assistant-star'\n  };\n\n  render() {\n    return (\n      <Icons.Base {...this.props}>\n        <g transform=\"scale(0.7)\" transform-origin=\"center\">\n          <path d=\"m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z\" />\n          <path d=\"M9.107 5.448c.598-1.75 3.016-1.803 3.725-.159l.06.16l.807 2.36a4 4 0 0 0 2.276 2.411l.217.081l2.36.806c1.75.598 1.803 3.016.16 3.725l-.16.06l-2.36.807a4 4 0 0 0-2.412 2.276l-.081.216l-.806 2.361c-.598 1.75-3.016 1.803-3.724.16l-.062-.16l-.806-2.36a4 4 0 0 0-2.276-2.412l-.216-.081l-2.36-.806c-1.751-.598-1.804-3.016-.16-3.724l.16-.062l2.36-.806A4 4 0 0 0 8.22 8.025l.081-.216zM11 6.094l-.806 2.36a6 6 0 0 1-3.49 3.649l-.25.091l-2.36.806l2.36.806a6 6 0 0 1 3.649 3.49l.091.25l.806 2.36l.806-2.36a6 6 0 0 1 3.49-3.649l.25-.09l2.36-.807l-2.36-.806a6 6 0 0 1-3.649-3.49l-.09-.25zM19 2a1 1 0 0 1 .898.56l.048.117l.35 1.026l1.027.35a1 1 0 0 1 .118 1.845l-.118.048l-1.026.35l-.35 1.027a1 1 0 0 1-1.845.117l-.048-.117l-.35-1.026l-1.027-.35a1 1 0 0 1-.118-1.845l.118-.048l1.026-.35l.35-1.027A1 1 0 0 1 19 2\" />\n        </g>\n      </Icons.Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/ai-assistant/src/icons/api-key.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Icons} from '@kepler.gl/components';\n\nexport default class ApiKey extends Component<Partial<Icons.BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-api-key'\n  };\n\n  render() {\n    return (\n      <Icons.Base {...this.props}>\n        <g fill=\"none\" stroke=\"currentColor\">\n          <circle cx={9} cy={14} r={4}></circle>\n          <path strokeLinecap=\"round\" d=\"m12 11l3.5-3.5M17 6l-1.5 1.5m0 0L18 10\"></path>\n        </g>\n      </Icons.Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/ai-assistant/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './components/ai-assistant-manager';\nexport * from './components/ai-assistant-config';\nexport * from './components/ai-assistant-component';\nexport {default as AiAssistantControlFactory} from './map/ai-assistant-control';\n\nexport type {AiAssistantState, AiAssistantConfig} from './reducers';\n\nexport {aiAssistantReducer} from './reducers';\nexport * from './actions';\nexport * from './localization';\n\nimport {keplerGlAiAssistantPlugin} from './plugin';\n\nexport {keplerGlAiAssistantPlugin};\n"
  },
  {
    "path": "src/ai-assistant/src/localization.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Add english messages here, other languages will use these\n// if translations not available for every message\n\nexport const messages = {\n  en: {\n    tooltip: {\n      showAiAssistantPanel: 'Show AI Assistant panel',\n      hideAiAssistantPanel: 'Hide AI Assistant panel'\n    },\n    aiAssistantManager: {\n      title: 'AI Assistant',\n      aiProvider: 'AI Provider',\n      llmModel: {\n        title: 'Select LLM Model'\n      },\n      apiKey: {\n        title: 'API Key',\n        placeholder: 'Enter your API Key'\n      },\n      baseUrl: {\n        title: 'Base URL',\n        placeholder: 'Enter Base URL'\n      },\n      temperature: {\n        title: 'Temperature'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: \"Let's Chat\"\n    }\n  },\n  fi: {\n    tooltip: {\n      showAiAssistantPanel: 'Näytä AI-ohjainpaneeli',\n      hideAiAssistantPanel: 'Piilota AI-ohjainpaneeli'\n    },\n    aiAssistantManager: {\n      title: 'AI-ohjain',\n      aiProvider: 'AI-toimittaja',\n      llmModel: {\n        title: 'Valitse LLM-malli'\n      },\n      apiKey: {\n        title: 'API-avain',\n        placeholder: 'Syötä API-avain'\n      },\n      baseUrl: {\n        title: 'Base URL',\n        placeholder: 'Syötä Base URL'\n      },\n      temperature: {\n        title: 'Lämpötila'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: 'Aloita keskustelu'\n    }\n  },\n  ca: {\n    tooltip: {\n      showAiAssistantPanel: \"Mostrar panell d'IA\",\n      hideAiAssistantPanel: \"Ocultar panell d'IA\"\n    },\n    aiAssistantManager: {\n      title: 'IA',\n      aiProvider: \"Proveïdor d'IA\",\n      llmModel: {\n        title: 'Seleccionar model LLM'\n      },\n      apiKey: {\n        title: 'Clau API',\n        placeholder: 'Introdueix la teva clau API'\n      },\n      baseUrl: {\n        title: 'URL OllamaBase',\n        placeholder: 'Introdueix URL OllamaBase'\n      },\n      temperature: {\n        title: 'Temperatura'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: 'Començar xat'\n    }\n  },\n  es: {\n    tooltip: {\n      showAiAssistantPanel: 'Mostrar panel de IA',\n      hideAiAssistantPanel: 'Ocultar panel de IA'\n    },\n    aiAssistantManager: {\n      title: 'IA',\n      aiProvider: 'Proveedor de IA',\n      llmModel: {\n        title: 'Seleccionar modelo LLM'\n      },\n      apiKey: {\n        title: 'Clave API',\n        placeholder: 'Introduce tu clave API'\n      },\n      baseUrl: {\n        title: 'URL OllamaBase',\n        placeholder: 'Introduce URL OllamaBase'\n      },\n      temperature: {\n        title: 'Temperatura'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: 'Iniciar chat'\n    }\n  },\n  cn: {\n    tooltip: {\n      showAiAssistantPanel: '显示 AI 助手面板',\n      hideAiAssistantPanel: '隐藏 AI 助手面板'\n    },\n    aiAssistantManager: {\n      title: 'AI 助手',\n      aiProvider: 'AI 提供商',\n      llmModel: {\n        title: '选择 LLM 模型'\n      },\n      apiKey: {\n        title: 'API 密钥',\n        placeholder: '输入你的 API 密钥'\n      },\n      baseUrl: {\n        title: 'OllamaBase 网址',\n        placeholder: '输入 OllamaBase 网址'\n      },\n      temperature: {\n        title: '温度'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: '开始聊天'\n    }\n  },\n  jp: {\n    tooltip: {\n      showAiAssistantPanel: 'AI アシスタントパネルを表示',\n      hideAiAssistantPanel: 'AI アシスタントパネルを非表示'\n    },\n    aiAssistantManager: {\n      title: 'AI アシスタント',\n      aiProvider: 'AI プロバイダー',\n      llmModel: {\n        title: 'LLM モデルを選択'\n      },\n      apiKey: {\n        title: 'API キー',\n        placeholder: 'API キーを入力'\n      },\n      baseUrl: {\n        title: 'Base URL',\n        placeholder: 'Base URL を入力'\n      },\n      temperature: {\n        title: '温度'\n      },\n      topP: {\n        title: 'Top P'\n      },\n      startChat: 'チャットを開始'\n    }\n  }\n};\n"
  },
  {
    "path": "src/ai-assistant/src/map/ai-assistant-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, ComponentType} from 'react';\nimport {MapControls} from '@kepler.gl/types';\nimport AiStar from '../icons/ai-star';\nimport {MapControlButton, MapControlTooltipFactory} from '@kepler.gl/components';\n\ntype AiAssistantControlIcons = {\n  aiAssistantIcon: ComponentType<{\n    height?: string;\n    width?: string;\n    className?: string;\n    style?: React.CSSProperties;\n  }>;\n};\n\n/**\n * AiAssistantControlProps\n * @param mapControls MapControls from kepler.gl\n * @param onToggleMapControl (control: string) => void\n * @param actionIcons AiAssistantControlIcons\n * @returns\n */\nexport type AiAssistantControlProps = {\n  mapControls: MapControls;\n  onToggleMapControl: (control: string) => void;\n  actionIcons: AiAssistantControlIcons;\n};\n\nAiAssistantControlFactory.deps = [MapControlTooltipFactory];\n\n/**\n * AiAssistantControlFactory\n * @param MapControlTooltip\n * @returns\n */\nexport default function AiAssistantControlFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>\n): React.FC<AiAssistantControlProps> {\n  const defaultActionIcons = {\n    aiAssistantIcon: AiStar\n  };\n\n  const AiAssistantControl = ({\n    mapControls,\n    onToggleMapControl,\n    actionIcons = defaultActionIcons\n  }: AiAssistantControlProps) => {\n    const onClick = useCallback(\n      event => {\n        event.preventDefault();\n        onToggleMapControl('aiAssistant');\n      },\n      [onToggleMapControl]\n    );\n\n    const showControl = mapControls?.aiAssistant?.show;\n    if (!showControl) {\n      return null;\n    }\n\n    const active = mapControls?.aiAssistant?.active;\n    return (\n      <MapControlTooltip\n        id=\"show-ai-assistant\"\n        message={active ? 'tooltip.hideAiAssistantPanel' : 'tooltip.showAiAssistantPanel'}\n      >\n        <MapControlButton\n          className=\"map-control-button toggle-ai-assistant\"\n          onClick={onClick}\n          active={active}\n        >\n          <actionIcons.aiAssistantIcon height=\"22px\" />\n        </MapControlButton>\n      </MapControlTooltip>\n    );\n  };\n\n  AiAssistantControl.displayName = 'AiAssistantControl';\n  return React.memo(AiAssistantControl);\n}\n"
  },
  {
    "path": "src/ai-assistant/src/plugin.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const keplerGlAiAssistantPlugin = {\n  name: 'ai-assistant',\n  async init() {\n    console.log('kepler.gl AI Assistant Plugin initialized');\n  }\n};\n"
  },
  {
    "path": "src/ai-assistant/src/reducers/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Action, handleActions} from 'redux-actions';\nimport {\n  UPDATE_AI_ASSISTANT_CONFIG,\n  UPDATE_AI_ASSISTANT_MESSAGES,\n  SET_START_SCREEN_CAPTURE,\n  SET_SCREEN_CAPTURED,\n  SET_MAP_BOUNDARY\n} from '../actions';\nimport {MessageModel} from '@openassistant/core';\nimport {PROVIDER_DEFAULT_BASE_URLS} from '../config/models';\n\nexport type AiAssistantConfig = {\n  isReady: boolean;\n  provider: string;\n  model: string;\n  apiKey: string;\n  baseUrl?: string;\n  temperature: number;\n  topP: number;\n  mapboxToken?: string;\n};\n\n// Initial state for the reducer\nconst initialConfig: AiAssistantConfig = {\n  isReady: false,\n  provider: 'openai',\n  model: 'gpt-4o',\n  apiKey: '',\n  baseUrl: PROVIDER_DEFAULT_BASE_URLS['openai'],\n  temperature: 0.0,\n  topP: 1.0\n};\n\nexport type AiAssistantState = {\n  config: AiAssistantConfig;\n  messages: MessageModel[];\n  screenshotToAsk: {\n    startScreenCapture: boolean;\n    screenCaptured: string;\n  };\n  keplerGl?: {\n    mapBoundary?: {\n      nw: [number, number];\n      se: [number, number];\n    };\n  };\n};\n\nconst initialState: AiAssistantState = {\n  config: initialConfig,\n  messages: [],\n  screenshotToAsk: {\n    startScreenCapture: false,\n    screenCaptured: ''\n  }\n};\n\nexport const aiAssistantReducer = handleActions<AiAssistantState, any>(\n  {\n    [UPDATE_AI_ASSISTANT_CONFIG]: updateAiAssistantConfigHandler,\n    [UPDATE_AI_ASSISTANT_MESSAGES]: updateAiAssistantMessagesHandler,\n    [SET_START_SCREEN_CAPTURE]: setStartScreenCaptureHandler,\n    [SET_SCREEN_CAPTURED]: setScreenCapturedHandler,\n    [SET_MAP_BOUNDARY]: setMapBoundaryHandler\n  },\n  initialState\n);\n\nfunction updateAiAssistantConfigHandler(\n  state: AiAssistantState,\n  action: Action<AiAssistantConfig>\n) {\n  return {\n    ...state,\n    config: {...state.config, ...action.payload}\n  };\n}\n\nfunction updateAiAssistantMessagesHandler(state: AiAssistantState, action: Action<MessageModel[]>) {\n  return {\n    ...state,\n    messages: action.payload\n  };\n}\n\nfunction setStartScreenCaptureHandler(state: AiAssistantState, action: Action<boolean>) {\n  return {\n    ...state,\n    screenshotToAsk: {startScreenCapture: action.payload, screenCaptured: ''}\n  };\n}\n\nfunction setScreenCapturedHandler(state: AiAssistantState, action: Action<string>) {\n  return {\n    ...state,\n    screenshotToAsk: {...state.screenshotToAsk, screenCaptured: action.payload}\n  };\n}\n\nfunction setMapBoundaryHandler(\n  state: AiAssistantState,\n  action: Action<{nw: [number, number]; se: [number, number]}>\n) {\n  return {\n    ...state,\n    keplerGl: {\n      ...state.keplerGl,\n      mapBoundary: action.payload\n    }\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/echarts-tools.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  boxplot,\n  BoxplotTool,\n  bubbleChart,\n  BubbleChartTool,\n  histogram,\n  HistogramTool,\n  pcp,\n  PCPTool,\n  scatterplot,\n  ScatterplotTool\n} from '@openassistant/plots';\nimport {\n  BoxplotComponent,\n  BubbleChartComponent,\n  HistogramPlotComponent,\n  ParallelCoordinateComponent,\n  ScatterplotComponent\n} from '@openassistant/echarts';\n\nimport {layerSetIsValid} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\nimport {Layer} from '@kepler.gl/layers';\nimport {Dispatch} from 'redux';\n\nimport {getValuesFromDataset, highlightRows} from './utils';\n\nexport function getEchartsTools(datasets: Datasets, layers: Layer[], dispatch: Dispatch) {\n  // context for tools\n  const getValues = async (datasetName: string, variableName: string) => {\n    const values = getValuesFromDataset(datasets, layers, datasetName, variableName);\n    return values as number[];\n  };\n\n  const onSelected = (datasetName: string, selectedIndices: number[]) => {\n    const triggerLayerReRender = (layer: Layer, isValid: boolean) => {\n      dispatch(layerSetIsValid(layer, isValid));\n    };\n    highlightRows(datasets, layers, datasetName, selectedIndices, triggerLayerReRender);\n  };\n\n  // Create the boxplot tool with the getValues implementation\n  const boxplotTool: BoxplotTool = {\n    ...boxplot,\n    context: {\n      ...boxplot.context,\n      getValues,\n      onSelected\n    },\n    component: BoxplotComponent\n  };\n\n  // Create the bubble chart tool with the getValues implementation\n  const bubbleChartTool: BubbleChartTool = {\n    ...bubbleChart,\n    context: {\n      ...bubbleChart.context,\n      getValues,\n      onSelected\n    },\n    component: BubbleChartComponent\n  };\n\n  const histogramTool: HistogramTool = {\n    ...histogram,\n    context: {\n      ...histogram.context,\n      getValues,\n      onSelected\n    },\n    component: HistogramPlotComponent\n  };\n\n  const pcpTool: PCPTool = {\n    ...pcp,\n    context: {\n      ...pcp.context,\n      getValues,\n      onSelected\n    },\n    component: ParallelCoordinateComponent\n  };\n\n  const scatterplotTool: ScatterplotTool = {\n    ...scatterplot,\n    context: {\n      ...scatterplot.context,\n      getValues,\n      onSelected\n    },\n    component: ScatterplotComponent\n  };\n\n  return {\n    boxplotTool,\n    bubbleChartTool,\n    histogramTool,\n    pcpTool,\n    scatterplotTool\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/geo-tools.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {\n  dataClassify,\n  DataClassifyTool,\n  spatialWeights,\n  SpatialWeightsTool,\n  globalMoran,\n  GlobalMoranTool,\n  spatialRegression,\n  SpatialRegressionTool,\n  lisa,\n  LisaTool,\n  spatialJoin,\n  SpatialJoinTool,\n  spatialFilter,\n  buffer,\n  BufferTool,\n  centroid,\n  CentroidTool,\n  dissolve,\n  DissolveTool,\n  length,\n  area,\n  perimeter,\n  grid,\n  GridTool,\n  standardizeVariable,\n  StandardizeVariableTool,\n  thiessenPolygons,\n  ThiessenPolygonsTool,\n  minimumSpanningTree,\n  MinimumSpanningTreeTool,\n  cartogram,\n  CartogramTool,\n  rate,\n  RateTool\n} from '@openassistant/geoda';\nimport {\n  getUsStateGeojson,\n  getUsCountyGeojson,\n  getUsZipcodeGeojson,\n  queryUSZipcodes,\n  geocoding,\n  routing,\n  isochrone,\n  IsochroneTool,\n  RoutingTool,\n  roads,\n  GetUsStateGeojsonTool,\n  GetUsCountyGeojsonTool,\n  GetUsZipcodeGeojsonTool\n} from '@openassistant/osm';\nimport {\n  SpatialJoinComponent,\n  DataTableComponent,\n  DataTableComponentProps\n} from '@openassistant/tables';\nimport {ToolCache} from '@openassistant/utils';\nimport {Datasets} from '@kepler.gl/table';\nimport {Layer} from '@kepler.gl/layers';\nimport {addDataToMap} from '@kepler.gl/actions';\n\nimport {LisaToolComponent} from './lisa-tool';\nimport {appendColumnsToDataset, getGeometriesFromDataset, getValuesFromDataset} from './utils';\nimport {AiAssistantState} from '../reducers';\nimport {State} from '../components/ai-assistant-manager';\n\nexport function getGeoTools(\n  aiAssistant: AiAssistantState,\n  datasets: Datasets,\n  layers: Layer[],\n  layerData: any[]\n) {\n  // tool cache\n  const toolCache = ToolCache.getInstance();\n\n  // context for geo tools\n  const getValues = async (datasetName: string, variableName: string) => {\n    const values = getValuesFromDataset(datasets, layers, datasetName, variableName);\n    return values as number[];\n  };\n\n  const getGeometries = async (datasetName: string) => {\n    let geoms = getGeometriesFromDataset(datasets, layers, layerData, datasetName);\n\n    if (geoms.length === 0) {\n      // even though the tool dataset should be saved by 'saveDataToMapTool',\n      // we still try to get the dataset from tool cache, e.g. route, isochrone etc.\n      const dataset = toolCache.getDataset(datasetName);\n      // check if dataset is a GeoJSON object\n      if (dataset && dataset.type === 'geojson') {\n        geoms = dataset.content.features;\n      }\n    }\n    return geoms;\n  };\n\n  const getMapboxToken = () => {\n    if (aiAssistant.config.mapboxToken) {\n      return aiAssistant.config.mapboxToken;\n    }\n    throw new Error('Mapbox token is not provided');\n  };\n\n  // onToolCompleted\n  const onToolCompleted = (toolName: string, result: unknown) => {\n    toolCache.addDataset(toolName, result);\n  };\n\n  // geo tools\n  const classifyTool: DataClassifyTool = {\n    ...dataClassify,\n    context: {getValues}\n  };\n\n  const weightsTool: SpatialWeightsTool = {\n    ...spatialWeights,\n    context: {getGeometries}\n  };\n\n  const globalMoranTool: GlobalMoranTool = {\n    ...globalMoran,\n    context: {getValues}\n  };\n\n  const regressionTool: SpatialRegressionTool = {\n    ...spatialRegression,\n    context: {getValues}\n  };\n\n  const lisaTool: LisaTool = {\n    ...lisa,\n    context: {getValues},\n    component: LisaToolComponent\n  };\n\n  const spatialJoinTool: SpatialJoinTool = {\n    ...spatialJoin,\n    context: {getValues, getGeometries},\n    onToolCompleted,\n    component: SpatialJoinComponent\n  };\n\n  const spatialFilterTool = {\n    ...spatialFilter,\n    context: {getValues, getGeometries},\n    onToolCompleted,\n    component: SpatialJoinComponent\n  };\n\n  const routingTool: RoutingTool = {\n    ...routing,\n    context: {getMapboxToken},\n    onToolCompleted\n  };\n\n  const isochroneTool: IsochroneTool = {\n    ...isochrone,\n    context: {getMapboxToken},\n    onToolCompleted\n  };\n\n  const bufferTool: BufferTool = {\n    ...buffer,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const centroidTool: CentroidTool = {\n    ...centroid,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const dissolveTool: DissolveTool = {\n    ...dissolve,\n    context: {\n      getGeometries,\n      getValues\n    },\n    onToolCompleted\n  };\n\n  const roadsTool = {\n    ...roads,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const lengthTool = {\n    ...length,\n    context: {getGeometries}\n  };\n\n  const areaTool = {\n    ...area,\n    context: {getGeometries}\n  };\n\n  const perimeterTool = {\n    ...perimeter,\n    context: {getGeometries}\n  };\n\n  const thiessenPolygonsTool: ThiessenPolygonsTool = {\n    ...thiessenPolygons,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const minimumSpanningTreeTool: MinimumSpanningTreeTool = {\n    ...minimumSpanningTree,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const cartogramTool: CartogramTool = {\n    ...cartogram,\n    context: {getGeometries, getValues},\n    onToolCompleted\n  };\n\n  const gridTool: GridTool = {\n    ...grid,\n    context: {getGeometries},\n    onToolCompleted\n  };\n\n  const getUsStateTool: GetUsStateGeojsonTool = {\n    ...getUsStateGeojson,\n    onToolCompleted\n  };\n\n  const getUsCountyTool: GetUsCountyGeojsonTool = {\n    ...getUsCountyGeojson,\n    onToolCompleted\n  };\n\n  const getUsZipcodeTool: GetUsZipcodeGeojsonTool = {\n    ...getUsZipcodeGeojson,\n    onToolCompleted\n  };\n\n  const standardizeVariableTool: StandardizeVariableTool = {\n    ...standardizeVariable,\n    context: {getValues},\n    component: CustomDataTableComponent\n  };\n\n  const rateTool: RateTool = {\n    ...rate,\n    context: {getValues},\n    component: CustomDataTableComponent\n  };\n\n  return {\n    classifyTool,\n    weightsTool,\n    globalMoranTool,\n    regressionTool,\n    lisaTool,\n    spatialJoinTool,\n    spatialFilterTool,\n    gridTool,\n    bufferTool,\n    centroidTool,\n    dissolveTool,\n    lengthTool,\n    areaTool,\n    perimeterTool,\n    getUsStateTool,\n    getUsCountyTool,\n    getUsZipcodeTool,\n    queryUSZipcodes,\n    geocoding,\n    routing: routingTool,\n    isochrone: isochroneTool,\n    roads: roadsTool,\n    standardizeVariable: standardizeVariableTool,\n    thiessenPolygons: thiessenPolygonsTool,\n    minimumSpanningTree: minimumSpanningTreeTool,\n    cartogram: cartogramTool,\n    rate: rateTool\n  };\n}\n\n/**\n * Customize the DataTableComponent for StandardizeVariableTool and RateTool\n */\nfunction CustomDataTableComponent(props: DataTableComponentProps) {\n  const dispatch = useDispatch();\n  const datasets = useSelector((state: State) => state.demo.keplerGl.map.visState.datasets);\n  const layers = useSelector((state: State) => state.demo.keplerGl.map.visState.layers);\n  const {originalDatasetName, datasetName, saveData} = props;\n\n  // get data by datasetName\n  const dataset = props[datasetName] as {\n    type: string;\n    content: Record<string, number[]>;\n  };\n\n  const data = dataset?.content;\n\n  useEffect(() => {\n    async function saveStandardizedData() {\n      // convert column-wise data to a row-wise Record<string, number>[]\n      const dataRecord: Record<string, number>[] = [];\n      const columnNames = Object.keys(data);\n      const numberOfRows = data[columnNames[0]].length;\n      for (let i = 0; i < numberOfRows; i++) {\n        const row: Record<string, number> = {};\n        for (const key of columnNames) {\n          row[key] = data[key][i];\n        }\n        dataRecord.push(row);\n      }\n\n      const processedData = await appendColumnsToDataset(\n        datasets,\n        layers,\n        originalDatasetName,\n        dataRecord,\n        datasetName\n      );\n      dispatch(\n        addDataToMap({datasets: processedData, options: {autoCreateLayers: true, centerMap: false}})\n      );\n    }\n    if (saveData) {\n      saveStandardizedData();\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return <DataTableComponent {...props} />;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/basemap-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DEFAULT_MAP_STYLES} from '@kepler.gl/constants';\nimport {mapStyleChange} from '@kepler.gl/actions';\nimport {extendedTool} from '@openassistant/utils';\nimport {z} from 'zod';\nimport {useEffect} from 'react';\nimport {useDispatch} from 'react-redux';\n\nexport const basemap = extendedTool({\n  description: 'change basemap',\n  parameters: z.object({\n    styleType: z.enum([\n      'no_map',\n      'dark-matter',\n      'positron',\n      'voyager',\n      'satellite',\n      'dark',\n      'light',\n      'muted',\n      'muted_night'\n    ])\n  }),\n  execute: executeBasemap,\n  component: BasemapToolComponent\n});\n\nexport type BasemapTool = typeof basemap;\n\ntype ExecuteBasemapResult = {\n  llmResult: {\n    success: boolean;\n    styleType: string;\n    details?: string;\n    instruction?: string;\n  };\n  additionalData?: {\n    styleType: string;\n  };\n};\n\nasync function executeBasemap({styleType}): Promise<ExecuteBasemapResult> {\n  try {\n    // check if styleType is valid\n    if (!DEFAULT_MAP_STYLES.find(style => style.id === styleType)) {\n      throw new Error(`Invalid basemap style: ${styleType}.`);\n    }\n\n    return {\n      llmResult: {\n        success: true,\n        styleType,\n        details: `basemap style changed to ${styleType}.`\n      },\n      additionalData: {\n        styleType\n      }\n    };\n  } catch (error) {\n    return {\n      llmResult: {\n        success: false,\n        styleType,\n        details: `Error changing basemap style: ${error}`,\n        instruction:\n          'Try to fix the error. If the error persists, pause the execution and ask the user to try with different prompt and context.'\n      }\n    };\n  }\n}\n\nexport function BasemapToolComponent({styleType}) {\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch(mapStyleChange(styleType));\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/boundary-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {extendedTool} from '@openassistant/utils';\nimport {z} from 'zod';\n\ntype MapBoundaryContext = {\n  getMapBoundary: () => {\n    nw: [number, number];\n    se: [number, number];\n  };\n};\n\nfunction isMapBoundaryContext(context: unknown): context is MapBoundaryContext {\n  return typeof context === 'object' && context !== null && 'getMapBoundary' in context;\n}\n\nexport const mapBoundary = extendedTool({\n  description:\n    'Get the boundary of the map. Northwest and Southeast coordinates in [longitude, latitude] format.',\n  parameters: z.object({}),\n  execute: async (args, options) => {\n    try {\n      if (!options?.context || !isMapBoundaryContext(options.context)) {\n        throw new Error('context getMapBoundary() not implemented.');\n      }\n      const {getMapBoundary} = options.context;\n      const boundary = getMapBoundary();\n      return {\n        llmResult: {\n          success: true,\n          boundary\n        }\n      };\n    } catch (error) {\n      return {\n        llmResult: {\n          success: false,\n          error: error instanceof Error ? error.message : 'Unknown error',\n          instruction:\n            'Please ask the user to make sure the kepler.gl app has been intialized successfully to get the map boundary.'\n        }\n      };\n    }\n  },\n  context: {\n    getMapBoundary: () => {\n      throw new Error('getMapBoundary() not implemented.');\n    }\n  }\n});\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VisState} from '@kepler.gl/schemas';\n\nimport {basemap} from './basemap-tool';\nimport {addLayer, AddLayerTool} from './layer-creation-tool';\nimport {updateLayerColor} from './layer-style-tool';\nimport {loadData, LoadDataTool, LoadDataToolComponent} from './loaddata-tool';\nimport {mapBoundary} from './boundary-tool';\nimport {saveToolResults} from './save-data-tool';\nimport {AiAssistantState} from '../../reducers';\n\nexport function getKeplerTools(visState: VisState, aiAssistant: AiAssistantState) {\n  // context for tools\n  const getDatasets = () => {\n    return visState.datasets;\n  };\n\n  const getLayers = () => {\n    return visState.layers;\n  };\n\n  const getLoaders = () => {\n    return {\n      loaders: visState.loaders,\n      loadOptions: visState.loadOptions\n    };\n  };\n\n  // tool: addLayer\n  const addLayerTool: AddLayerTool = {\n    ...addLayer,\n    context: {\n      getDatasets\n    }\n  };\n\n  // tool: updateLayerColor\n  const updateLayerColorTool = {\n    ...updateLayerColor,\n    context: {\n      getLayers\n    }\n  };\n\n  // tool: loadData\n  const loadDataTool: LoadDataTool = {\n    ...loadData,\n    context: {\n      getLoaders\n    },\n    component: LoadDataToolComponent\n  };\n\n  // tool: mapBoundary\n  const mapBoundaryTool = {\n    ...mapBoundary,\n    context: {\n      getMapBoundary: () => {\n        return aiAssistant.keplerGl?.mapBoundary;\n      }\n    }\n  };\n\n  return {\n    basemap,\n    addLayer: addLayerTool,\n    updateLayerColor: updateLayerColorTool,\n    loadData: loadDataTool,\n    mapBoundary: mapBoundaryTool,\n    saveDataToMap: saveToolResults\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/layer-creation-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useDispatch} from 'react-redux';\nimport {LayerClasses} from '@kepler.gl/layers';\nimport KeplerTable, {Datasets} from '@kepler.gl/table';\nimport {findDefaultLayer} from '@kepler.gl/reducers';\nimport {addLayer as addLayerAction} from '@kepler.gl/actions';\nimport {extendedTool, generateId} from '@openassistant/utils';\nimport {z} from 'zod';\nimport {useEffect} from 'react';\n\nexport const addLayer = extendedTool<\n  // parameters\n  z.ZodObject<{\n    datasetName: z.ZodString;\n    latitudeColumn: z.ZodOptional<z.ZodString>;\n    longitudeColumn: z.ZodOptional<z.ZodString>;\n    layerType: z.ZodEnum<\n      [\n        'point',\n        'arc',\n        'line',\n        'grid',\n        'hexagon',\n        'geojson',\n        'cluster',\n        'heatmap',\n        'h3',\n        'trip',\n        's2'\n      ]\n    >;\n    colorBy: z.ZodOptional<z.ZodString>;\n    colorType: z.ZodOptional<z.ZodEnum<['breaks', 'unique']>>;\n    colorMap: z.ZodOptional<\n      z.ZodArray<\n        z.ZodObject<{\n          value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodNull]>;\n          color: z.ZodString;\n        }>\n      >\n    >;\n  }>,\n  // return type\n  ExecuteAddLayerResult['llmResult'],\n  // additional data\n  ExecuteAddLayerResult['additionalData'],\n  // context\n  AddLayerFunctionContext\n>({\n  description: `Add a kepler.gl map layer from a dataset.\nYou can create basic map layer without color styling, or enhanced map layer with color visualization.\n\nFor basic maps:\n- Simply use datasetName, geometryColumn (if needed), latitudeColumn/longitudeColumn (for point maps), and mapType\n- Omit color-related parameters for simple visualization\n\nFor colored maps:\n- If user requests color visualization, use available columns in the dataset\n- Use dataClassify tool to classify data into bins or unique values when needed\n- If dataClassify tool returns a list of k breaks\n  a. For a list of k break values, you must create k+1 entries in the colorMap, with the last value being null.\n  b. For example: for breaks = [0, 3, 10], the colorMap could be [{value: 0, color: '##fff7bc', label: '< 0'}, {value: 3, color: '#fec44f', label: '[0-3)'}, {value: null, color: '#d95f0e', label: '>= 3'}]\n- If dataClassify tool returns a list of k unique values\n  a. There should be k colors in the colorMap. For example: for uniqueValues = ['a', 'b', 'c'], the colorMap could be [{value: 'a', color: '#1b9e77'}, {value: 'b', color: '#d95f02'}, {value: 'c', color: '#7570b3'}]\n- Generate colorBrewer colors automatically if user doesn't specify colors\n\nFor geojson datasets:\n- Use geometryColumn: '_geojson' and mapType: 'geojson' even for point collections\n`,\n  parameters: z.object({\n    datasetName: z\n      .string()\n      .describe('The name of the dataset. Note: please do NOT use the datasetId.'),\n    latitudeColumn: z.string().optional(),\n    longitudeColumn: z.string().optional(),\n    layerName: z\n      .string()\n      .optional()\n      .describe('If possible, generate a name for the layer based on the context.'),\n    layerType: z.enum([\n      'point',\n      'arc',\n      'line',\n      'grid',\n      'hexagon',\n      'geojson',\n      'cluster',\n      'heatmap',\n      'h3',\n      'trip',\n      's2'\n    ]),\n    colorBy: z.string().optional(),\n    colorType: z.enum(['breaks', 'unique']).optional(),\n    colorMap: z\n      .array(\n        z.object({\n          value: z.union([z.string(), z.number(), z.null()]),\n          color: z.string()\n        })\n      )\n      .optional()\n  }),\n  execute: executeAddLayer,\n  component: AddLayerToolComponent\n});\n\nexport type AddLayerTool = typeof addLayer;\n\ntype AddLayerArgs = {\n  datasetName: string;\n  layerName?: string;\n  layerType: string;\n  latitudeColumn?: string;\n  longitudeColumn?: string;\n  colorBy?: string;\n  colorType?: string;\n  colorMap?: Array<{value: string | number | null; color: string; label?: string}>;\n};\n\nfunction isAddLayerArgs(args: any): args is AddLayerArgs {\n  return typeof args === 'object' && args !== null && 'datasetName' in args && 'layerType' in args;\n}\n\ntype AddLayerFunctionContext = {\n  getDatasets: () => Datasets;\n};\n\nfunction isAddLayerFunctionContext(context: any): context is AddLayerFunctionContext {\n  return context && typeof context.getDatasets === 'function';\n}\n\ntype ExecuteAddLayerResult = {\n  llmResult: {\n    success: boolean;\n    layer?: string;\n    details?: string;\n    error?: string;\n    instruction?: string;\n  };\n  additionalData?: {\n    layer: object;\n    datasetId: string;\n  };\n};\n\nasync function executeAddLayer(args, options): Promise<ExecuteAddLayerResult> {\n  try {\n    if (!isAddLayerArgs(args)) {\n      throw new Error('Invalid addLayer arguments');\n    }\n\n    if (!isAddLayerFunctionContext(options.context)) {\n      throw new Error('Invalid addLayer context');\n    }\n\n    const {\n      datasetName,\n      layerName,\n      latitudeColumn,\n      longitudeColumn,\n      layerType,\n      colorBy,\n      colorType,\n      colorMap\n    } = args;\n\n    const datasets = options.context.getDatasets();\n\n    // check if dataset exists in kepler.gl\n    const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n    if (!datasetId) {\n      throw new Error(`Dataset ${datasetName} not found.`);\n    }\n\n    // check if field exists in the dataset\n    const dataset = datasets[datasetId];\n\n    // check if layerType is valid\n    let layer = guessDefaultLayer(dataset, layerType);\n\n    const layerId = layer?.id || `layer_${generateId()}`;\n\n    if (!layer) {\n      // for point layer, try to creat a point layer manually if LLM sugggests Lat/Lng fields\n      if (layerType === 'point' && latitudeColumn && longitudeColumn) {\n        layer = {\n          id: layerId,\n          type: 'point',\n          config: {\n            dataId: datasetId,\n            label: layerName || `${datasetName}-${layerType}`,\n            columns: {\n              lat: {value: latitudeColumn, fieldIdx: dataset.getColumnFieldIdx(latitudeColumn)},\n              lng: {value: longitudeColumn, fieldIdx: dataset.getColumnFieldIdx(longitudeColumn)}\n            }\n          },\n          visConfig: {\n            colorRange: {\n              name: 'Ice And Fire',\n              type: 'diverging',\n              category: 'Uber',\n              colors: ['#D50255', '#FEAD54', '#FEEDB1', '#E8FEB5', '#49E3CE', '#0198BD']\n            }\n          }\n        } as any;\n      }\n    }\n    if (!layer) {\n      throw new Error(`Invalid layer type: ${layerType}.`);\n    }\n\n    const columns = layer?.config?.columns || {};\n\n    // construct new layer config for addLayer() action\n    const newLayer = {\n      id: layerId,\n      type: layer.type,\n      config: {\n        ...layer.config,\n        dataId: datasetId,\n        label: layerName || `${datasetName}-${layerType}`,\n        columns: Object.keys(columns).reduce((acc, key) => {\n          const column = columns[key];\n          if (column) {\n            acc[key] = column.value;\n          }\n          return acc;\n        }, {})\n      }\n    };\n\n    if (colorBy) {\n      const colorField = dataset.fields.find(f => f.name === colorBy);\n      if (!colorField) {\n        throw new Error(`Field ${colorBy} not found.`);\n      }\n      // create kepler.gl's colorMap from uniqueValues and breaks\n      const colorScale = colorType === 'breaks' ? 'custom' : 'customOrdinal';\n      const colors = colorMap?.map(color => color.color);\n      const keplerColorMap = colorMap?.map(color => [color.value, color.color]);\n      const colorRange = {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors,\n        colorMap: keplerColorMap\n      };\n\n      newLayer.config['colorScale'] = colorScale;\n      newLayer.config['colorField'] = colorField;\n      newLayer.config['strokeColorScale'] = colorScale;\n      newLayer.config['strokeColorField'] = colorField;\n      newLayer.config.visConfig['colorRange'] = colorRange;\n      newLayer.config.visConfig['strokeColorRange'] = colorRange;\n      newLayer.config['visualChannels'] = {\n        colorField: {\n          name: colorBy,\n          type: colorField?.type\n        },\n        colorScale\n      };\n    }\n\n    return {\n      llmResult: {\n        success: true,\n        layer: JSON.stringify(newLayer),\n        details: `map layer ${layerId} will be added to the map.`\n      },\n      additionalData: {\n        layer: newLayer,\n        datasetId\n      }\n    };\n  } catch (error) {\n    return {\n      llmResult: {\n        success: false,\n        error: error instanceof Error ? error.message : 'Unknown error',\n        instruction:\n          'Try to fix the error. If the error persists, pause the execution and ask the user to try with different prompt and context.'\n      }\n    };\n  }\n}\n\nexport function guessDefaultLayer(dataset: KeplerTable, layerType: string) {\n  // special case for hexagon layer, which could be implemented as findDefaultLayerProps() in hexagon-layer.tsx\n  if (layerType === 'hexagon') {\n    if (dataset.fieldPairs && dataset.fieldPairs.length > 0) {\n      const props = dataset.fieldPairs.map(fieldPair => ({\n        isVisible: true,\n        label: 'Hexbin',\n        columns: fieldPair.pair\n      }));\n      const layer = new LayerClasses.hexagon(props[0]);\n      return layer;\n    }\n  }\n  const defaultLayers = findDefaultLayer(dataset, LayerClasses);\n  const layer = defaultLayers.find(l => l.type === layerType);\n  return layer || defaultLayers.length > 0 ? defaultLayers[0] : null;\n}\n\nexport function AddLayerToolComponent({layer, datasetId}) {\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch(addLayerAction(layer, datasetId));\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/layer-style-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// This file is used to call the LAYER_VISUAL_CHANNEL_CHANGE to update the layer style\n\nimport {useEffect} from 'react';\nimport {extendedTool} from '@openassistant/utils';\nimport {layerVisualChannelConfigChange} from '@kepler.gl/actions';\nimport {Layer, LayerBaseConfig} from '@kepler.gl/layers';\nimport {LayerVisConfig} from '@kepler.gl/types';\nimport {z} from 'zod';\nimport {useDispatch} from 'react-redux';\n\nfunction UpdateLayerColorToolComponent({layer, newConfig, channel, newVisConfig}) {\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch(layerVisualChannelConfigChange(layer, newConfig, channel, newVisConfig));\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return null;\n}\n\n/**\n * Update the color of a layer\n * NOTE: this tool should be updated to updateLayerStyle including color, size, opacity, etc.\n */\nexport const updateLayerColor = extendedTool({\n  description: 'Update the color of a layer',\n  parameters: z.object({\n    layerId: z.string(),\n    numberOfColors: z.number(),\n    customColors: z\n      .array(z.string())\n      .describe(\n        'An array of hex color values. Please try to generate colors from user description like: van gogh starry night, water color etc.'\n      )\n  }),\n  execute: executeUpdateLayerColor,\n  context: {\n    getLayers: () => {\n      throw new Error('getLayers() not implemented.');\n    },\n    layerVisualChannelConfigChange: () => {\n      throw new Error('layerVisualChannelConfigChange() not implemented.');\n    }\n  },\n  component: UpdateLayerColorToolComponent\n});\n\ntype UpdateLayerColorArgs = {\n  layerId: string;\n  numberOfColors: number;\n  customColors: string[];\n};\n\nfunction isUpdateLayerColorArgs(args: any): args is UpdateLayerColorArgs {\n  return (\n    typeof args === 'object' &&\n    args !== null &&\n    typeof args.layerId === 'string' &&\n    typeof args.numberOfColors === 'number' &&\n    Array.isArray(args.customColors) &&\n    args.customColors.every(color => typeof color === 'string')\n  );\n}\n\ntype UpdateLayerColorFunctionContext = {\n  getLayers: () => Layer[];\n};\n\nfunction isUpdateLayerColorFunctionContext(\n  context: any\n): context is UpdateLayerColorFunctionContext {\n  return typeof context === 'object' && context !== null && typeof context.getLayers === 'function';\n}\n\ntype ExecuteUpdateLayerColorResult = {\n  llmResult: {\n    success: boolean;\n    details?: string;\n    error?: string;\n    instruction?: string;\n  };\n  additionalData?: {\n    layerId: string;\n    layer: Layer;\n    newConfig: Partial<LayerBaseConfig>;\n    channel: string;\n    newVisConfig: Partial<LayerVisConfig>;\n  };\n};\n\nasync function executeUpdateLayerColor(args, options): Promise<ExecuteUpdateLayerColorResult> {\n  try {\n    if (!isUpdateLayerColorArgs(args)) {\n      throw new Error('Invalid updateLayerColor arguments');\n    }\n    if (!isUpdateLayerColorFunctionContext(options.context)) {\n      throw new Error('Invalid updateLayerColor function context');\n    }\n\n    const {layerId, numberOfColors, customColors} = args;\n    const {getLayers} = options.context;\n\n    // get layer from visState by layerId\n    const layers = getLayers();\n    const layer = layers.find(l => l.id === layerId);\n    if (!layer) {\n      throw new Error(`Layer with id ${layerId} not found`);\n    }\n\n    // verify numberOfColors is equal to customColors.length\n    if (numberOfColors !== customColors.length) {\n      throw new Error(`customColors array must contain exactly ${numberOfColors} colors`);\n    }\n\n    const channel = 'color';\n\n    const newConfig = {\n      // colorScale: 'custom'\n    } as Partial<LayerBaseConfig>;\n\n    const oldColorRange = layer.config.visConfig.colorRange;\n    const newColorRange = {\n      ...oldColorRange,\n      colors: customColors,\n      ...(oldColorRange.colorMap\n        ? {\n            colorMap: [...oldColorRange.colorMap.map((c, i) => [c[0], customColors[i]])]\n          }\n        : {})\n    };\n\n    const newVisConfig = {\n      colorRange: newColorRange,\n      strokeColorRange: newColorRange\n    };\n\n    return {\n      llmResult: {\n        success: true,\n        details: `Color updated successfully to ${customColors.join(', ')} for layer ${layerId}`\n      },\n      additionalData: {\n        layerId,\n        layer,\n        newConfig,\n        channel,\n        newVisConfig\n      }\n    };\n  } catch (error) {\n    return {\n      llmResult: {\n        success: false,\n        error: error instanceof Error ? error.message : 'Unknown error',\n        instruction: `Try to fix the error. If the error persists, pause the execution and ask the user to try with different prompt and context.`\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/loaddata-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useEffect} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {extendedTool} from '@openassistant/utils';\nimport {z} from 'zod';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {Loader} from '@loaders.gl/loader-utils';\nimport {ProtoDataset} from '@kepler.gl/types';\nimport {readFileInBatches, processFileData, ProcessFileDataContent} from '@kepler.gl/processors';\n\nexport const loadData = extendedTool<\n  // parameters of the tool\n  z.ZodObject<{\n    url: z.ZodString;\n  }>,\n  // return type of the tool\n  ExecuteLoadDataResult['llmResult'],\n  // additional data of the tool\n  ExecuteLoadDataResult['additionalData'],\n  // context of the tool\n  LoadDataToolContext\n>({\n  description: `Load dataset from a URL in kepler.gl.`,\n  parameters: z.object({\n    url: z.string().describe('The URL or file path to load data from')\n  }),\n  execute: executeLoadData,\n  context: {\n    getLoaders: () => {\n      throw new Error('getLoaders() not implemented.');\n    }\n  },\n  component: LoadDataToolComponent\n});\n\nexport type LoadDataTool = typeof loadData;\n\ntype ExecuteLoadDataResult = {\n  llmResult: {\n    success: boolean;\n    url: string;\n    details?: string;\n    dataInfo?: object;\n    instruction?: string;\n  };\n  additionalData?: {\n    parsedData: ProtoDataset[];\n  };\n};\n\ntype LoadDataToolContext = {\n  getLoaders: () => {\n    loaders?: Loader[];\n    loadOptions?: object;\n  };\n};\n\nfunction isLoadDataContext(context: any): context is LoadDataToolContext {\n  return context && typeof context.getLoaders === 'function';\n}\n\nasync function executeLoadData({url}, options): Promise<ExecuteLoadDataResult> {\n  try {\n    if (!isLoadDataContext(options.context)) {\n      throw new Error('Invalid load data context. Please provide a valid context.');\n    }\n\n    const {getLoaders} = options.context;\n    const {loaders, loadOptions} = getLoaders();\n\n    // Validate URL\n    try {\n      new URL(url);\n    } catch (e) {\n      throw new Error(`Invalid URL: ${url}`);\n    }\n\n    // Fetch data\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);\n    }\n\n    const blob = await response.blob();\n    const fileName = url.split('/').pop() || 'data';\n\n    // Create file object\n    const file = new File([blob], fileName);\n\n    // Process file data\n    const batches = await readFileInBatches({\n      file,\n      fileCache: [],\n      loaders: loaders ?? [],\n      loadOptions: loadOptions ?? {}\n    });\n\n    let result = await batches.next();\n    let content: ProcessFileDataContent = {data: [], fileName: ''};\n    let parsedData: ProtoDataset[] = [];\n\n    while (!result.done) {\n      content = result.value as ProcessFileDataContent;\n      result = await batches.next();\n      if (result.done) {\n        parsedData = await processFileData({\n          content,\n          fileCache: []\n        });\n        break;\n      }\n    }\n\n    // get metadata for LLM\n    const dataInfo = parsedData[0].info;\n\n    return {\n      llmResult: {\n        success: true,\n        url,\n        details: `Successfully loaded data from ${url}`,\n        dataInfo\n      },\n      additionalData: {\n        parsedData\n      }\n    };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';\n    return {\n      llmResult: {\n        success: false,\n        url,\n        details: `Error loading data: ${errorMessage}`,\n        instruction:\n          'Try to fix the error. If the error persists, pause the execution and ask the user to try with different URL or format.'\n      }\n    };\n  }\n}\n\nexport function LoadDataToolComponent({parsedData}) {\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch(\n      addDataToMap({\n        datasets: parsedData,\n        options: {\n          autoCreateLayers: true,\n          centerMap: true\n        }\n      })\n    );\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/save-data-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useEffect} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {processFileData} from '@kepler.gl/processors';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {ProtoDataset} from '@kepler.gl/types';\nimport {FeatureCollection} from 'geojson';\nimport {extendedTool, ToolCache, generateId} from '@openassistant/utils';\nimport {z} from 'zod';\n\nexport const saveToolResults = extendedTool({\n  description:\n    'Save tool results to kepler.gl. The tool includes: buffer, zipcode, county, state, isochrone, thiessenPolygons, mst, cartogram, etc.',\n  parameters: z.object({\n    datasetNames: z.array(z.string()).describe('The names of the datasets created by tools.')\n  }),\n  execute: async (args: {datasetNames: string[]}) => {\n    try {\n      const {datasetNames} = args;\n      const loadedDatasetNames: string[] = [];\n      const result: unknown[] = [];\n      const toolCache = ToolCache.getInstance();\n\n      let datasetType;\n\n      for (const datasetName of datasetNames) {\n        const dataset = toolCache.getDataset(datasetName);\n        if (dataset && dataset.type === 'geojson') {\n          datasetType = 'geojson';\n        } else if (dataset && dataset.type === 'columnData') {\n          datasetType = 'columnData';\n        } else if (dataset && dataset.type === 'rowObjects') {\n          datasetType = 'rowObjects';\n        } else {\n          throw new Error(\n            `Can not save tool cache dataset ${datasetName}, the dataset type ${datasetType} is not supported`\n          );\n        }\n        result.push(dataset.content);\n        loadedDatasetNames.push(datasetName);\n      }\n\n      if (result.length === 0) {\n        throw new Error(`Can not save dataset, No datasets found from ${datasetNames.join(', ')}`);\n      }\n\n      // merge the result based on the dataset type\n      let mergedResult;\n\n      if (datasetType === 'geojson') {\n        mergedResult = (result as FeatureCollection[]).reduce(\n          (acc, geom) => {\n            return {\n              ...acc,\n              features: [...acc.features, ...geom.features]\n            };\n          },\n          {type: 'FeatureCollection', features: []}\n        );\n      } else if (datasetType === 'columnData') {\n        const mergedColumnData = (result as Record<string, unknown>[]).reduce((acc, row) => {\n          return {\n            ...acc,\n            ...row\n          };\n        }, {}) as Record<string, unknown[]>;\n        // convert the merged result to a rowObjects array\n        const columnNames = Object.keys(mergedColumnData);\n        const numberOfRows = mergedColumnData[columnNames[0]].length;\n        for (let i = 0; i < numberOfRows; i++) {\n          const rowObject: Record<string, unknown> = {};\n          for (const columnName of columnNames) {\n            rowObject[columnName] = mergedColumnData[columnName][i];\n          }\n          mergedResult.push(rowObject);\n        }\n      } else if (datasetType === 'rowObjects') {\n        mergedResult = (result as unknown[][]).reduce((acc, row) => {\n          return [...acc, ...row];\n        }, []);\n      }\n\n      const datasetName =\n        datasetNames.length > 1 ? `${datasetNames.join('_')}_${generateId()}` : datasetNames[0];\n\n      // try to process the merged result using kepler.gl processor\n      const parsedData = await processFileData({\n        content: {\n          data: mergedResult,\n          fileName: `${datasetName}`\n        },\n        fileCache: []\n      });\n\n      return {\n        llmResult: {\n          success: true,\n          savedDatasetName: datasetName,\n          details: `Successfully save dataset: ${datasetName} in kepler.gl`\n        },\n        additionalData: {\n          parsedData\n        }\n      };\n    } catch (error) {\n      return {\n        llmResult: {\n          success: false,\n          details: `Can not save data to kepler.gl, ${error}`\n        }\n      };\n    }\n  },\n  component: SaveDataToMapToolComponent\n});\n\nexport function SaveDataToMapToolComponent({parsedData}: {parsedData: ProtoDataset[]}) {\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch(\n      addDataToMap({\n        datasets: parsedData,\n        options: {autoCreateLayers: true, centerMap: true}\n      })\n    );\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/kepler-tools/table-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useSelector} from 'react-redux';\nimport React, {useEffect, useRef, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {getDuckDB, LocalQueryAdditionalData, convertArrowRowToObject} from '@openassistant/duckdb';\n\nimport {State} from '../../components/ai-assistant-manager';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {processFileData} from '@kepler.gl/processors';\n\n// This component will create a new table using the SQL query which will\n// 1. add a new column\n// 2. delete a column\n// 3. rename a column\n// 4. change the variable type of a column\nexport function TableToolComponent({\n  sql,\n  dbTableName,\n  queryDatasetName: newDatasetName\n}: LocalQueryAdditionalData) {\n  const datasets = useSelector((state: State) => state.demo.keplerGl.map.visState.datasets);\n  const queryInProgress = useRef<Promise<void> | null>(null);\n  const dispatch = useDispatch();\n\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    // check if the newDatasetName is already in the datasets\n    const newDatasetId = Object.keys(datasets).find(\n      dataId => datasets[dataId].label === newDatasetName\n    );\n    // if the newDatasetName is already in the datasets, return\n    if (newDatasetId) return;\n\n    const addTable = async () => {\n      // If a query is already in progress, wait for it to complete\n      if (queryInProgress.current) {\n        await queryInProgress.current;\n      }\n\n      // Create a new promise for this query\n      queryInProgress.current = (async () => {\n        try {\n          const duckDB = await getDuckDB();\n          if (!duckDB) {\n            throw new Error('DuckDB instance is not initialized');\n          }\n          if (dbTableName && sql) {\n            // connect to the database\n            const conn = await duckDB.connect();\n\n            // Execute the provided SQL query\n            const arrowResult = await conn.query(sql);\n\n            // convert arrowResult to a JSON object\n            const jsonResult = arrowResult.toArray().map(row => convertArrowRowToObject(row));\n\n            // use processFileData to process the rowObject\n            const processedData = await processFileData({\n              content: {fileName: newDatasetName, data: jsonResult},\n              fileCache: []\n            });\n\n            // add the new dataset to the map\n            dispatch(\n              addDataToMap({\n                datasets: processedData,\n                options: {autoCreateLayers: true, centerMap: true}\n              })\n            );\n\n            // delete the table from the database\n            await conn.query(`DROP TABLE ${dbTableName}`);\n\n            // close the connection\n            await conn.close();\n          }\n        } catch (error) {\n          console.error(error);\n          setError(error instanceof Error ? error.message : 'Unknown error occurred');\n        } finally {\n          queryInProgress.current = null;\n        }\n      })();\n\n      // Wait for the query to complete\n      await queryInProgress.current;\n    };\n\n    addTable();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  return error ? <div style={{color: 'red', fontSize: '8px'}}>{error}</div> : null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/lisa-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useEffect} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {addDataToMap, addLayer} from '@kepler.gl/actions';\nimport {LisaAdditionalData} from '@openassistant/geoda';\nimport {saveAsDataset} from './utils';\nimport {guessDefaultLayer} from './kepler-tools/layer-creation-tool';\nimport {State} from '../components/ai-assistant-manager';\n\n/**\n * Use LisaToolComponent adding a customized Lisa map layer to kepler.gl\n */\nexport function LisaToolComponent(props: LisaAdditionalData) {\n  const datasets = useSelector((state: State) => state.demo.keplerGl.map.visState.datasets);\n  const layers = useSelector((state: State) => state.demo.keplerGl.map.visState.layers);\n  const dispatch = useDispatch();\n\n  const {\n    originalDatasetName,\n    datasetName,\n    significanceThreshold,\n    clusters,\n    lagValues,\n    pValues,\n    lisaValues,\n    sigCategories,\n    nn,\n    labels,\n    colors\n  } = props;\n\n  // save the result from lisa tool to a new lisa dataset\n  useEffect(() => {\n    if (\n      !clusters ||\n      !lagValues ||\n      !pValues ||\n      !lisaValues ||\n      !sigCategories ||\n      !nn ||\n      !labels ||\n      !colors\n    ) {\n      return;\n    }\n\n    const lisaData: Record<string, unknown[]> = {\n      clusters,\n      lagValues,\n      pValues,\n      lisaValues,\n      sigCategories,\n      nn,\n      fillColor: clusters.map((cluster, index) =>\n        pValues[index] < significanceThreshold ? colors[cluster] : colors[0]\n      ),\n      label: clusters.map((cluster, index) =>\n        pValues[index] < significanceThreshold ? labels[cluster] : labels[0]\n      )\n    };\n    // create a new geojson layer using clusters with ordinal color scale and customColorScale and colors\n    const newDataset = saveAsDataset(datasets, layers, originalDatasetName, datasetName, lisaData);\n    if (newDataset) {\n      // add the new dataset to the map\n      dispatch(\n        addDataToMap({datasets: [newDataset], options: {autoCreateLayers: false, centerMap: false}})\n      );\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // create a LISA layer for the new lisa dataset\n  useEffect(() => {\n    const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n    if (!datasetId || !colors || !labels) return;\n\n    const newDataset = datasets[datasetId];\n    if (newDataset) {\n      const layerId = `${datasetName}_lisa`;\n      // check if the layer already exists?\n      if (layers.find(l => l.id === layerId)) {\n        return;\n      }\n      // add a new layer for the new dataset\n      const layer = guessDefaultLayer(newDataset, 'geojson');\n      if (!layer) return;\n      const colorField = {\n        name: 'clusters',\n        type: 'integer'\n      };\n      const colorDomain = Array.from({length: colors.length}, (_, i) => i);\n      const colorLegends = {};\n      for (let i = 0; i < colors.length; i++) {\n        colorLegends[colors[i]] = labels[i];\n      }\n      const colorMap = colors.map((color, index) => [index, color]);\n      const newLayer = {\n        id: layerId,\n        type: layer.type,\n        config: {\n          ...layer.config,\n          dataId: datasetId,\n          columns: Object.keys(layer.config.columns).reduce((acc, key) => {\n            acc[key] = layer.config.columns[key].value;\n            return acc;\n          }, {}),\n          colorScale: 'customOrdinal',\n          colorField,\n          strokeColorDomain: colorDomain,\n          strokeColorField: colorField,\n          strokeColorScale: 'customOrdinal',\n          visConfig: {\n            ...layer.config.visConfig,\n            colorRange: {\n              ...layer.config.visConfig.colorRange,\n              colorDomain,\n              colors,\n              colorMap,\n              colorLegends\n            },\n            strokeColorRange: {\n              category: 'Custom',\n              name: 'custom',\n              colors,\n              colorMap\n            },\n            strokeOpacity: 1,\n            stroked: true,\n            strokedWidth: 0.5\n          }\n        }\n      };\n      dispatch(addLayer(newLayer, datasetId));\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [datasets]);\n  return null;\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/query-tool.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Datasets} from '@kepler.gl/table';\nimport {Layer} from '@kepler.gl/layers';\nimport {\n  getDuckDB,\n  localQuery,\n  LocalQueryTool,\n  mergeTables,\n  MergeTablesTool\n} from '@openassistant/duckdb';\nimport {QueryDuckDBComponent, QueryDuckDBOutputData} from '@openassistant/tables';\nimport {ToolCache} from '@openassistant/utils';\n\nimport {getValuesFromDataset} from './utils';\nimport {TableToolComponent} from './kepler-tools/table-tool';\n\nexport function getQueryTool(datasets: Datasets, layers: Layer[]) {\n  const toolCache = ToolCache.getInstance();\n\n  // context for query tools\n  const getValues = async (datasetName: string, variableName: string) => {\n    const values = getValuesFromDataset(datasets, layers, datasetName, variableName);\n    return values;\n  };\n\n  // customize some query tools from localQuery tool\n  function QueryToolComponent(props: QueryDuckDBOutputData) {\n    return <QueryDuckDBComponent {...props} getValues={getValues} getDuckDB={getDuckDB} />;\n  }\n\n  // this tool will execute a generic select SQL query against user's dataset\n  const genericQuery: LocalQueryTool = {\n    ...localQuery,\n    description: `execute a generic select SQL query in duckdb to answer user's question. Please note:\n1. This tool is NOT for filtering the user dataset.\n2. This tool does NOT support geometry column and geometric operations.\n3. The variableNames should not be empty. If it is not provided, then pick a variable name from the dataset.\n4. There is no need to add a sub-query to add an auto-increment column 'row_index' to the original dataset.\n`,\n    context: {\n      ...localQuery.context,\n      getValues\n    },\n    component: QueryToolComponent\n  };\n\n  // customize a filterDataset tool from localQuery tool\n  // this tool will use the selected rows (row indexes) to filter the user dataset and save the result as a new dataset.\n  const filterDataset: LocalQueryTool = {\n    ...localQuery,\n    description: `filter the user dataset by using a select SQL query in duckdb and save the result as a new dataset.\nPlease note:\n1. Do not use * to select all columns, instead use all the column names in dataset.\n`,\n    context: {\n      ...localQuery.context,\n      getValues\n    },\n    component: TableToolComponent\n  };\n\n  // customize a table tool from localQuery tool to create a new table/dataset in kepler.gl using SQL query\n  const tableTool: LocalQueryTool = {\n    ...localQuery,\n    description: `Create a new table/dataset in kepler.gl using SQL query which will\n1. add a new column to the original dataset\n2. delete a column from the original dataset\n3. rename a column in the original dataset\n4. change the column type in the original dataset.\nPlease note:\n1. Do not use * to select all columns, instead use all the column names in dataset.\n2. List all column names the new table or dataset will have.\n`,\n    context: {\n      ...localQuery.context,\n      getValues\n    },\n    component: TableToolComponent\n  };\n\n  // customize a mergeTables tool from localQuery tool to merge two datasets into a new dataset\n  const mergeTablesTool: MergeTablesTool = {\n    ...mergeTables,\n    context: {\n      getValues\n    },\n    onToolCompleted: (toolName: string, additionalData: unknown) => {\n      toolCache.addDataset(toolName, additionalData);\n    }\n    // component: MergeTablesToolComponent\n  };\n\n  return {\n    filterDataset,\n    genericQuery,\n    tableTool,\n    mergeTablesTool\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/tools.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VisState} from '@kepler.gl/schemas';\nimport {Dispatch} from 'redux';\n\nimport {AiAssistantState} from '../reducers';\nimport {getEchartsTools} from './echarts-tools';\nimport {getGeoTools} from './geo-tools';\nimport {getKeplerTools} from './kepler-tools';\nimport {getQueryTool} from './query-tool';\n\nexport function setupLLMTools({\n  visState,\n  aiAssistant,\n  dispatch\n}: {\n  visState: VisState;\n  aiAssistant: AiAssistantState;\n  dispatch: Dispatch;\n}) {\n  return {\n    ...getKeplerTools(visState, aiAssistant),\n    ...getEchartsTools(visState.datasets, visState.layers, dispatch),\n    ...getGeoTools(aiAssistant, visState.datasets, visState.layers, visState.layerData),\n    ...getQueryTool(visState.datasets, visState.layers)\n  };\n}\n"
  },
  {
    "path": "src/ai-assistant/src/tools/utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport interpolate from 'color-interpolate';\nimport {Feature} from 'geojson';\nimport {Layer, VectorTileLayer} from '@kepler.gl/layers';\nimport {Datasets, KeplerTable} from '@kepler.gl/table';\nimport {SpatialJoinGeometries} from '@openassistant/geoda';\nimport {ALL_FIELD_TYPES, LAYER_TYPES} from '@kepler.gl/constants';\nimport {Field, ProtoDataset, ProtoDatasetField} from '@kepler.gl/types';\nimport {processFileData} from '@kepler.gl/processors';\n\n/**\n * Interpolate the colors from the original colors with the given number of colors\n * @param originalColors The original colors\n * @param numberOfColors The number of colors\n * @returns The interpolated colors\n */\nexport function interpolateColor(originalColors: string[], numberOfColors: number) {\n  if (originalColors.length === numberOfColors) {\n    return originalColors;\n  }\n  const interp = interpolate(originalColors);\n  const colors = Array.from({length: numberOfColors}, (_, j) => interp(j / (numberOfColors - 1)));\n  // convert colors from 'rgb(255, 255, 255)' to '#ffffff'\n  const hexColors = colors.map(color => {\n    const rgb = color.match(/\\d+/g);\n    return `#${rgb?.map(c => parseInt(c).toString(16).padStart(2, '0')).join('')}`;\n  });\n  return hexColors;\n}\n\n/**\n * Get the values from a dataset for a variable\n * @param datasets\n * @param datasetName\n * @param variableName\n * @returns {number[]}\n */\nexport function getValuesFromDataset(\n  datasets: Datasets,\n  layers: Layer[],\n  datasetName: string,\n  variableName: string\n): unknown[] {\n  // find which dataset has the variableName\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) {\n    throw new Error(`Dataset ${datasetName} not found`);\n  }\n  const dataset = datasets[datasetId];\n  if (dataset) {\n    // check if field exists\n    const field = dataset.fields.find(field => field.name === variableName);\n    if (!field) {\n      throw new Error(`Field ${variableName} not found in dataset ${datasetName}`);\n    }\n    // for vector-tile, getting values from layerData\n    if (dataset.type === 'vector-tile') {\n      // get field from dataset\n      const field = dataset.fields.find(field => field.name === variableName);\n      if (field) {\n        return getValuesFromVectorTileLayer(datasetId, layers, field);\n      }\n    }\n    return Array.from({length: dataset.length}, (_, i) => dataset.getValue(variableName, i));\n  }\n  return [];\n}\n\nfunction isVectorTileLayer(layer: Layer): layer is VectorTileLayer {\n  return layer.type === LAYER_TYPES.vectorTile;\n}\n\nexport function getValuesFromVectorTileLayer(datasetId: string, layers: Layer[], field: Field) {\n  // get the index of the layer\n  const layerIndex = layers.findIndex(layer => layer.config.dataId === datasetId);\n  if (layerIndex === -1) return [];\n  const layer = layers[layerIndex];\n  if (!isVectorTileLayer(layer)) return [];\n  const accessor = layer.accessRowValue(field);\n  const values: unknown[] = [];\n  // @ts-expect-error TODO fix this later in the vector-tile layer\n  for (const row of layer.tileDataset.tileSet) {\n    const value = accessor(field, row);\n    if (value === null) break;\n    values.push(value);\n  }\n  return values;\n}\n\n/**\n * Highlight the rows in a dataset\n * @param datasets The kepler.gl datasets\n * @param layers The kepler.gl layers\n * @param datasetName The name of the dataset\n * @param selectedRowIndices The indices of the rows to highlight\n * @param layerSetIsValid The function to set the layer validity\n */\nexport function highlightRows(\n  datasets: Datasets,\n  layers: Layer[],\n  datasetName: string,\n  selectedRowIndices: number[],\n  layerSetIsValid: (layer: Layer, isValid: boolean) => void\n) {\n  // update the filteredIndex in the dataset\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) return;\n  const dataset = datasets[datasetId];\n  if (dataset) {\n    dataset.filteredIndex =\n      selectedRowIndices.length === 0 ? dataset.allIndexes : selectedRowIndices;\n    // get all layers that use this dataset\n    const selectLayers = layers.filter(layer => layer.config.dataId === dataset.id);\n    selectLayers.forEach(layer => {\n      layer.formatLayerData(datasets);\n      // trigger a re-render using layerSetIsValid() to update the top layer\n      layerSetIsValid(layer, true);\n    });\n  }\n}\n\n/**\n * Get the dataset context, which is used to provide the dataset information to the AI assistant\n * @param datasets The kepler.gl datasets\n * @param layers The kepler.gl layers\n * @returns The dataset context\n */\nexport function getDatasetContext(datasets?: Datasets, layers?: Layer[]) {\n  if (!datasets || !layers) return '';\n  const context =\n    'Please remember the following datasets and layers for answering the user question:';\n  const dataMeta = Object.values(datasets).map((dataset: KeplerTable) => ({\n    datasetName: dataset.label,\n    datasetId: dataset.id,\n    fields: dataset.fields.map(field => ({[field.name]: field.type})),\n    layers: layers\n      .filter(layer => layer.config.dataId === dataset.id)\n      .map(layer => ({\n        id: layer.id,\n        label: layer.config.label,\n        type: layer.type,\n        geometryMode: layer.config.columnMode,\n        // get the valid geometry columns as string\n        geometryColumns: Object.fromEntries(\n          Object.entries(layer.config.columns)\n            .filter(([, value]) => value !== null)\n            .map(([key, value]) => [\n              key,\n              typeof value === 'object' && value !== null\n                ? Object.fromEntries(Object.entries(value).filter(([, v]) => v !== null))\n                : value\n            ])\n        )\n      }))\n  }));\n  return `${context}\\n${JSON.stringify(dataMeta)}`;\n}\n\n/**\n * Get the geometries from a dataset\n * @param datasets The kepler.gl datasets\n * @param layers The kepler.gl layers\n * @param layerData The layer data\n * @param datasetName The name of the dataset\n * @returns The geometries\n */\nexport function getGeometriesFromDataset(\n  datasets: Datasets,\n  layers: Layer[],\n  layerData: any[],\n  datasetName: string\n): SpatialJoinGeometries {\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) {\n    return [];\n  }\n  const dataset = datasets[datasetId];\n\n  // if layer is vector-tile, get the geometries from the layer\n  if (dataset.type === 'vector-tile') {\n    // find the vector-tile layer\n    const selected = layers.filter(layer => layer.config.dataId === dataset.id);\n    const layer = selected.find(layer => layer.type === LAYER_TYPES.vectorTile);\n    if (!layer) return [];\n\n    const geometries: Feature[] = [];\n    // @ts-expect-error TODO fix this later in the vector-tile layer\n    for (const row of layer.tileDataset.tileSet) {\n      geometries.push(row);\n    }\n    return geometries;\n  }\n\n  // for non-vector-tile dataset, get the geometries from the possible layer\n  const selectedLayers = layers.filter(layer => layer.config.dataId === dataset.id);\n  if (selectedLayers.length === 0) return [];\n\n  // find geojson layer, then point layer, then other layers\n  const geojsonLayer = selectedLayers.find(layer => layer.type === LAYER_TYPES.geojson);\n  const pointLayer = selectedLayers.find(layer => layer.type === LAYER_TYPES.point);\n  const otherLayers = selectedLayers.filter(\n    layer => layer.type !== LAYER_TYPES.geojson && layer.type !== LAYER_TYPES.point\n  );\n\n  const validLayer = geojsonLayer || pointLayer || otherLayers[0];\n  if (validLayer) {\n    const layerIndex = layers.findIndex(layer => layer.id === validLayer.id);\n    const geometries = layerData[layerIndex];\n    return geometries.data;\n  }\n\n  return [];\n}\n\n/**\n * Save the data as a new dataset by joining it with the left dataset\n * @param datasets The kepler.gl datasets\n * @param datasetName The name of the left dataset\n * @param data The data to save\n * @param addDataToMap The function to add the data to the map\n */\nexport function saveAsDataset(\n  datasets: Datasets,\n  layers: Layer[],\n  datasetName: string,\n  newDatasetName: string,\n  data: Record<string, unknown[]>\n) {\n  // find datasetId from datasets\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) return;\n\n  // check if newDatasetName already exists\n  if (Object.keys(datasets).includes(newDatasetName)) return;\n\n  // Save the data as a new dataset by joining it with the left dataset\n  const leftDataset = datasets[datasetId];\n  let numRows = leftDataset.length;\n  let geometries: Feature[];\n\n  if (leftDataset.type === 'vector-tile') {\n    // we need to get geometries from the vector-tile layer\n    geometries = getFeaturesFromVectorTile(leftDataset, layers) || [];\n    numRows = geometries.length;\n  }\n\n  const fields: ProtoDatasetField[] = [\n    // New fields from data\n    ...Object.keys(data).map((fieldName, index) => ({\n      name: fieldName,\n      id: `${fieldName}_${index}`,\n      displayName: fieldName,\n      type: determineFieldType(data[fieldName][0])\n    })),\n    // Existing fields from leftDataset\n    ...leftDataset.fields.map((field, index) => ({\n      name: field.name,\n      id: field.id || `${field.name}_${index}`,\n      displayName: field.displayName,\n      type: field.type\n    })),\n    // add geometry column for vector-tile\n    ...(leftDataset.type === 'vector-tile'\n      ? [\n          {\n            name: '_geojson',\n            id: '_geojson',\n            displayName: '_geojson',\n            type: 'geojson'\n          }\n        ]\n      : [])\n  ];\n\n  // Pre-calculate data values array\n  const dataValues = Object.values(data);\n\n  const rows = Array(numRows)\n    .fill(null)\n    .map((_, rowIdx) => [\n      // New data values\n      ...dataValues.map(col => col[rowIdx]),\n      // Existing dataset values\n      ...leftDataset.fields.map(field =>\n        leftDataset.type === 'vector-tile'\n          ? geometries[rowIdx].properties?.[field.name]\n          : leftDataset.getValue(field.name, rowIdx)\n      ),\n      // geometry column for vector-tile\n      ...(leftDataset.type === 'vector-tile' ? [geometries[rowIdx]] : [])\n    ]);\n\n  // create new dataset\n  const newDataset: ProtoDataset = {\n    info: {\n      id: newDatasetName,\n      label: newDatasetName\n    },\n    data: {\n      fields,\n      rows\n    }\n  };\n\n  return newDataset;\n}\n\n/**\n * Helper function to determine field type\n * @param value The value to determine the field type\n * @returns The field type\n */\nfunction determineFieldType(value: unknown): keyof typeof ALL_FIELD_TYPES {\n  return typeof value === 'number'\n    ? Number.isInteger(value)\n      ? ALL_FIELD_TYPES.integer\n      : ALL_FIELD_TYPES.real\n    : ALL_FIELD_TYPES.string;\n}\n\nexport function highlightRowsByColumnValues(\n  datasets: Datasets,\n  layers: Layer[],\n  datasetName: string,\n  columnName: string,\n  selectedValues: unknown[],\n  layerSetIsValid: (layer: Layer, isValid: boolean) => void\n) {\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) return;\n  const dataset = datasets[datasetId];\n  if (dataset) {\n    // get the values of the column\n    const values = Array.from({length: dataset.length}, (_, i) => dataset.getValue(columnName, i));\n    // create a dict using the values\n    const valueDict = values.reduce((acc, value, index) => {\n      acc[value] = index;\n      return acc;\n    }, {});\n    // need to fix the type error of value here\n    const selectedIndices = selectedValues.map(value => valueDict[value as any]);\n    // highlight the rows\n    highlightRows(datasets, layers, datasetName, selectedIndices, layerSetIsValid);\n  }\n}\n\nfunction getFeaturesFromVectorTile(leftDataset: KeplerTable, layers: Layer[]) {\n  const layerIndex = layers.findIndex(layer => layer.config.dataId === leftDataset.id);\n  if (layerIndex === -1) return;\n\n  const layer = layers[layerIndex];\n  if (!isVectorTileLayer(layer)) return;\n\n  const features: Feature[] = [];\n  // @ts-expect-error TODO fix this later in the vector-tile layer\n  for (const row of layer.tileDataset.tileSet) {\n    features.push(row);\n  }\n\n  return features;\n}\n\nexport async function appendColumnsToDataset(\n  datasets: Datasets,\n  layers: Layer[],\n  datasetName: string,\n  result: Record<string, number>[],\n  newDatasetName: string\n) {\n  // find datasetId from datasets\n  const datasetId = Object.keys(datasets).find(dataId => datasets[dataId].label === datasetName);\n  if (!datasetId) {\n    throw new Error(`Dataset ${datasetName} not found`);\n  }\n\n  const originalDataset = datasets[datasetId];\n\n  const fields = originalDataset.fields;\n\n  const numRows = originalDataset.length || result.length;\n\n  // create a rowObjects array to store the original dataset values + query result values\n  const rowObjects: Record<string, unknown>[] = [];\n\n  if (originalDataset.type === 'vector-tile') {\n    const columnData = {};\n    for (const field of fields) {\n      // get the values from the vector tile layer\n      columnData[field.name] = getValuesFromVectorTileLayer(datasetId, layers, field);\n    }\n    // convert columnData to rowObjects\n    for (let i = 0; i < numRows; i++) {\n      const rowObject: Record<string, unknown> = {};\n      for (const field of fields) {\n        rowObject[field.name] = columnData[field.name][i];\n      }\n      rowObjects.push(rowObject);\n    }\n  } else {\n    for (let i = 0; i < numRows; i++) {\n      const rowObject: Record<string, unknown> = {};\n      for (const field of fields) {\n        const value = originalDataset.getValue(field.name, i);\n        rowObject[field.name] = value;\n      }\n      rowObjects.push(rowObject);\n    }\n  }\n\n  // add the query result to the original dataset or update the field values from query result\n  for (let i = 0; i < numRows; i++) {\n    const queryRow = result[i];\n    const rowObject = rowObjects[i];\n    // iterate over the keys of queryRow\n    Object.keys(queryRow).forEach(key => {\n      const value = queryRow[key];\n      rowObject[key] = value;\n    });\n  }\n\n  // use processFileData to process the rowObject\n  const processedData = await processFileData({\n    content: {fileName: newDatasetName, data: rowObjects},\n    fileCache: []\n  });\n\n  return processedData;\n}\n"
  },
  {
    "path": "src/ai-assistant/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/cloud-providers/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/cloud-providers/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/cloud-providers\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"react\": \"^18.2.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/cloud-providers/src/base.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, CSSProperties} from 'react';\n\nconst getStyleClassFromColor = (totalColor: number, colors: string[]) =>\n  new Array(totalColor)\n    .fill(1)\n    .reduce((accu, c, i) => `${accu}.cr${i + 1} {fill:${colors[i % colors.length]};}`, '');\n\nconst nop = () => {\n  return;\n};\n\nexport type BaseProps = {\n  /** Set the height of the icon, ex. '16px' */\n  height?: string;\n  /** Set the width of the icon, ex. '16px' */\n  width?: string;\n  /** Set the viewbox of the svg */\n  viewBox?: string;\n  /** Path element */\n\n  predefinedClassName?: string;\n  className?: string;\n  style?: CSSProperties;\n  colors?: string[];\n  totalColor?: number;\n} & React.SVGAttributes<SVGSVGElement> &\n  React.DOMAttributes<SVGSVGElement>;\n\nexport class Base extends Component<BaseProps> {\n  static displayName = 'Base Icon';\n\n  static defaultProps = {\n    height: null,\n    width: null,\n    viewBox: '0 0 64 64',\n    predefinedClassName: '',\n    className: '',\n    style: {\n      fill: 'currentColor'\n    }\n  };\n\n  render() {\n    const {\n      height,\n      width,\n      viewBox,\n      style,\n      children,\n      predefinedClassName,\n      className,\n      colors,\n      totalColor,\n      ...props\n    } = this.props;\n    const svgHeight = height;\n    const svgWidth = width || svgHeight;\n\n    const fillStyle =\n      Array.isArray(colors) && totalColor && getStyleClassFromColor(totalColor, colors);\n\n    return (\n      <svg\n        viewBox={viewBox}\n        width={svgWidth}\n        height={svgHeight}\n        style={style}\n        className={`${predefinedClassName} ${className}`}\n        onClick={nop}\n        {...props}\n      >\n        {fillStyle ? <style type=\"text/css\">{fillStyle}</style> : null}\n        {children}\n      </svg>\n    );\n  }\n}\n"
  },
  {
    "path": "src/cloud-providers/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as Provider, FILE_CONFLICT_MSG, KEPLER_FORMAT} from './provider';\n// eslint-disable-next-line prettier/prettier\nexport type {MapListItem, Thumbnail, ProviderProps, IconProps, CloudUser} from './provider';\nexport {default as Upload} from './upload';\n"
  },
  {
    "path": "src/cloud-providers/src/provider.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Upload from './upload';\nimport {MapData, ExportFileOptions, Millisecond, SavedMap} from '@kepler.gl/types';\nimport {ComponentType} from 'react';\n\nexport type MapItemLoadParams = {\n  id: string;\n  path: string;\n};\n\nexport type MapListItem = {\n  id: string;\n  title: string;\n  description: string;\n  loadParams: any;\n  imageUrl?: string;\n  updatedAt?: Millisecond;\n  privateMap?: boolean;\n};\n\nexport type CloudUser = {\n  name: string;\n  email: string;\n  thumbnail?: string;\n};\n\nexport type Thumbnail = {\n  width: number;\n  height: number;\n};\n\nexport type ProviderProps = {\n  name?: string;\n  displayName?: string;\n  storageMessage?: string;\n  icon?: ComponentType<IconProps>;\n  thumbnail?: Thumbnail;\n};\n\nexport interface IconProps {\n  height?: string;\n  width?: string;\n}\n\nconst NAME = 'cloud-provider';\nconst DISPLAY_NAME = 'Cloud Provider';\nconst THUMBNAIL = {width: 300, height: 200};\nconst ICON = Upload;\nexport const KEPLER_FORMAT = 'keplergl';\nexport const FILE_CONFLICT_MSG = 'file_conflict';\n\n/**\n * The default provider class\n * @param {object} props\n * @param {string} props.name\n * @param {string} props.displayName\n * @param {React.Component} props.icon - React element\n * @param {object} props.thumbnail - thumbnail size object\n * @param {number} props.thumbnail.width - thumbnail width in pixels\n * @param {number} props.thumbnail.height - thumbnail height in pixels\n * @public\n * @example\n *\n * const myProvider = new Provider({\n *  name: 'foo',\n *  displayName: 'Foo Storage'\n *  icon: Icon,\n *  thumbnail: {width: 300, height: 200}\n * })\n */\nexport default class Provider {\n  name: string;\n  displayName: string;\n  storageMessage?: string;\n  icon: ComponentType<IconProps>;\n  thumbnail: Thumbnail;\n  isNew = false;\n\n  constructor(props: ProviderProps) {\n    this.name = props.name || NAME;\n    this.displayName = props.displayName || DISPLAY_NAME;\n    this.storageMessage = props.storageMessage;\n    this.icon = props.icon || ICON;\n    this.thumbnail = props.thumbnail || THUMBNAIL;\n  }\n\n  /**\n   * Whether this provider support upload map to a private storage. If truthy, user will be displayed with the storage save icon on the top right of the side bar.\n   * @returns\n   * @public\n   */\n  hasPrivateStorage(): boolean {\n    return true;\n  }\n\n  /**\n   * Whether this provider support share map via a public url, if truthy, user will be displayed with a share map via url under the export map option on the top right of the side bar\n   * @returns\n   * @public\n   */\n  hasSharingUrl(): boolean {\n    return false;\n  }\n\n  /**\n   * This method is called after user share a map, to display the share url.\n   * @param fullUrl - Whether to return the full url with domain, or just the location\n   * @returns shareUrl\n   * @public\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  getShareUrl(fullUrl = false): string {\n    return '';\n  }\n\n  /**\n   * This method is called by kepler.gl demo app to pushes a new location to history, becoming the current location.\n   * @returns mapUrl\n   * @public\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  getMapUrl(loadParams: MapItemLoadParams): string {\n    return '';\n  }\n\n  /**\n   * This method is called to determine whether user already logged in to this provider\n   * @public\n   * @returns {Promise<string>} return the access token if a user already logged in\n   */\n  async getAccessToken(): Promise<string | null> {\n    return Promise.reject('You must implement getAccessToken');\n  }\n\n  /**\n   * This method is called to get the user name of the current user. It will be displayed in the cloud provider tile.\n   * @public\n   * @deprecated please use getUser\n   * @returns true if a user already logged in\n   */\n  getUserName(): string {\n    return '';\n  }\n\n  /**\n   * return a Promise with the user object\n   */\n  async getUser(): Promise<CloudUser | null> {\n    return Promise.reject('You must implement getUser');\n  }\n\n  /**\n   * This return a standard error that will trigger the overwrite map modal\n   */\n  getFileConflictError() {\n    return new Error(FILE_CONFLICT_MSG);\n  }\n\n  /**\n   * This method will be called when user click the login button in the cloud provider tile.\n   * Upon login success and return the user Object {name, email, abbreviated}\n   * @public\n   */\n  async login() {\n    return Promise.reject(new Error('you must implement the `login` method'));\n  }\n\n  /**\n   * This method will be called when user click the logout button under the cloud provider tile.\n   * Upon login success\n   * @public\n   */\n  async logout(): Promise<void> {\n    return Promise.reject(new Error('you must implement the `logout` method'));\n  }\n\n  /**\n   * This method will be called to upload map for saving and sharing. Kepler.gl will package map data, config, title, description and thumbnail for upload to storage.\n   * With the option to overwrite already saved map, and upload as private or public map.\n   *\n   * @param {Object} param\n   * @param {Object} param.mapData - the map object\n   * @param {Object} param.mapData.map - {datasets. config, info: {title, description}}\n   * @param {Blob} param.mapData.thumbnail - A thumbnail of current map. thumbnail size can be defined by provider by this.thumbnail\n   * @param {object} [param.options]\n   * @param {boolean} [param.options.overwrite] - whether user choose to overwrite already saved map under the same name\n   * @param {boolean} [param.options.isPublic] - whether user wish to share the map with others. if isPublic is truthy, kepler will call this.getShareUrl() to display an URL they can share with others\n   * @public\n   */\n  async uploadMap({\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    mapData,\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    options = {}\n  }: {\n    mapData: MapData;\n    options: ExportFileOptions;\n  }): Promise<MapListItem> {\n    return Promise.reject('You must implement uploadMap');\n  }\n\n  /**\n   * This method is called to get a list of maps saved by the current logged in user.\n   * @returns visualizations an array of Viz objects\n   * @public\n   * @example\n   *  async listMaps() {\n   *    return [\n   *      {\n   *        id: 'a',\n   *        title: 'My map',\n   *        description: 'My first kepler map',\n   *        imageUrl: 'http://',\n   *        updatedAt: 1582677787000,\n   *        privateMap: false,\n   *        loadParams: {}\n   *      }\n   *    ];\n   *  }\n   */\n  async listMaps(): Promise<MapListItem[]> {\n    return [];\n  }\n\n  /**\n   * This method will be called when user select a map to load from the storage map viewer\n   * @param {*} loadParams - the loadParams property of each visualization object\n   * @returns mapResponse - the map object containing dataset config info and format option\n   * @public\n   * @example\n   * async downloadMap(loadParams) {\n   *  const mockResponse = {\n   *    map: {\n   *      datasets: [],\n   *      config: {},\n   *      info: {\n   *        app: 'kepler.gl',\n   *        created_at: ''\n   *        title: 'test map',\n   *        description: 'Hello this is my test dropbox map'\n   *      }\n   *    },\n   *    // pass csv here if your provider currently only support save / load file as csv\n   *    format: 'keplergl'\n   *  };\n   *\n   *  return downloadMap;\n   * }\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  async downloadMap(loadParams): Promise<{map: SavedMap; format: string}> {\n    return Promise.reject('You must implement downloadMap');\n  }\n\n  /**\n   * @return {string} return the storage location url for the current provider\n   * @public\n   */\n  getManagementUrl(): string {\n    throw new Error('You must implement getManagementUrl');\n  }\n\n  /**\n   * @typedef {Object} Viz\n   * @property {string} id - An unique id\n   * @property {string} title - The title of the map\n   * @property {string} description - The description of the map\n   * @property {string} imageUrl - The imageUrl of the map\n   * @property {number} updatedAt - An epoch timestamp in milliseconds\n   * @property {boolean} privateMap - Optional, whether if this map is private to the user, or can be accessed by others via URL\n   * @property {*} loadParams - A property to be passed to `downloadMap`\n   * @public\n   */\n\n  /**\n   * The returned object of `downloadMap`. The response object should contain: datasets: [], config: {}, and info: {}\n   * each dataset object should be {info: {id, label}, data: {...}}\n   * to inform how kepler should process your data object, pass in `format`\n   * @typedef {Object} MapResponse\n   * @property {Object} map\n   * @property {Array<Object>} map.datasets\n   * @property {Object} map.config\n   * @property {Object} map.info\n   * @property {string} format - one of 'csv': csv file string, 'geojson': geojson object, 'row': row object, 'keplergl': datasets array saved using KeplerGlSchema.save\n   * @public\n   */\n}\n"
  },
  {
    "path": "src/cloud-providers/src/upload.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Base, BaseProps} from './base';\n\nexport default class Upload extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-upload'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M52 9v6a1 1 0 0 1-1 1H13a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h38a1 1 0 0 1 1 1zm-4 31L34.426 21.336a3 3 0 0 0-4.852 0L16 40h8v16h16V40h8z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/cloud-providers/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/cloud-providers/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/common-utils/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/common-utils/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/common-utils\",\n  \"author\": \"Shan He <heshan0131@gmail.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl common utils\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"d3-array\": \"^2.8.0\",\n    \"global\": \"^4.3.0\",\n    \"h3-js\": \"^3.1.0\",\n    \"type-analyzer\": \"0.4.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Igor Dykhta <dikhta.igor@gmail.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/common-utils/src/data-type.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Analyzer, DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer';\nimport {ArrowTableInterface, ApacheVectorInterface, RowData, Field} from '@kepler.gl/types';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {console as globalConsole} from 'global/window';\nimport {range} from 'd3-array';\nimport {isHexWkb, notNullorUndefined} from './data';\nimport {h3IsValid} from './h3-utils';\n\nconst H3_ANALYZER_TYPE = 'H3';\n\nexport const ACCEPTED_ANALYZER_TYPES = [\n  AnalyzerDATA_TYPES.DATE,\n  AnalyzerDATA_TYPES.TIME,\n  AnalyzerDATA_TYPES.DATETIME,\n  AnalyzerDATA_TYPES.NUMBER,\n  AnalyzerDATA_TYPES.INT,\n  AnalyzerDATA_TYPES.FLOAT,\n  AnalyzerDATA_TYPES.BOOLEAN,\n  AnalyzerDATA_TYPES.STRING,\n  AnalyzerDATA_TYPES.GEOMETRY,\n  AnalyzerDATA_TYPES.GEOMETRY_FROM_STRING,\n  AnalyzerDATA_TYPES.PAIR_GEOMETRY_FROM_STRING,\n  AnalyzerDATA_TYPES.ZIPCODE,\n  AnalyzerDATA_TYPES.ARRAY,\n  AnalyzerDATA_TYPES.OBJECT,\n  H3_ANALYZER_TYPE\n];\n\nconst IGNORE_DATA_TYPES = Object.keys(AnalyzerDATA_TYPES).filter(\n  type => !ACCEPTED_ANALYZER_TYPES.includes(type)\n);\n\n/**\n * Getting sample data for analyzing field type.\n */\nexport function getSampleForTypeAnalyze({\n  fields,\n  rows,\n  sampleCount = 50\n}: {\n  fields: string[];\n  rows: unknown[][] | RowData;\n  sampleCount?: number;\n}): RowData {\n  const total = Math.min(sampleCount, rows.length);\n  // const fieldOrder = fields.map(f => f.name);\n  const sample = range(0, total, 1).map(() => ({}));\n\n  if (rows.length < 1) {\n    return [];\n  }\n  const isRowObject = !Array.isArray(rows[0]);\n\n  // collect sample data for each field\n  fields.forEach((field, fieldIdx) => {\n    // row counter\n    let i = 0;\n    // sample counter\n    let j = 0;\n\n    while (j < total) {\n      if (i >= rows.length) {\n        // if depleted data pool\n        sample[j][field] = null;\n        j++;\n      } else if (notNullorUndefined(rows[i][isRowObject ? field : fieldIdx])) {\n        const value = rows[i][isRowObject ? field : fieldIdx];\n        sample[j][field] = typeof value === 'string' ? value.trim() : value;\n        j++;\n        i++;\n      } else {\n        i++;\n      }\n    }\n  });\n\n  return sample;\n}\n\n/**\n * Getting sample data for analyzing field type for Arrow tables.\n * @param table Arrow table or an array of vectors.\n * @param fields Field names.\n * @param sampleCount Number of sample rows to get.\n * @returns Sample rows.\n */\nexport function getSampleForTypeAnalyzeArrow(\n  table: ArrowTableInterface | ApacheVectorInterface[],\n  fields: string[],\n  sampleCount = 50\n): any[] {\n  const isTable = !Array.isArray(table);\n\n  const numRows = isTable ? table.numRows : table[0].length;\n  const getVector = isTable ? index => table.getChildAt(index) : index => table[index];\n\n  const total = Math.min(sampleCount, numRows);\n  const sample = range(0, total, 1).map(() => ({}));\n\n  if (numRows < 1) {\n    return [];\n  }\n\n  // collect sample data for each field\n  fields.forEach((field, fieldIdx) => {\n    let rowIndex = 0;\n    let sampleIndex = 0;\n\n    while (sampleIndex < total) {\n      if (rowIndex >= numRows) {\n        // if depleted data pool\n        sample[sampleIndex][field] = null;\n        sampleIndex++;\n      } else if (notNullorUndefined(getVector(fieldIdx)?.get(rowIndex))) {\n        const value = getVector(fieldIdx)?.get(rowIndex);\n        sample[sampleIndex][field] = typeof value === 'string' ? value.trim() : value;\n        sampleIndex++;\n        rowIndex++;\n      } else {\n        rowIndex++;\n      }\n    }\n  });\n\n  return sample;\n}\n\n/**\n * Convert type-analyzer output to kepler.gl field types\n *\n * @param aType\n * @returns corresponding type in `ALL_FIELD_TYPES`\n */\n/* eslint-disable complexity */\nexport function analyzerTypeToFieldType(aType: string): string {\n  const {\n    DATE,\n    TIME,\n    DATETIME,\n    NUMBER,\n    INT,\n    FLOAT,\n    BOOLEAN,\n    STRING,\n    GEOMETRY,\n    GEOMETRY_FROM_STRING,\n    PAIR_GEOMETRY_FROM_STRING,\n    ZIPCODE,\n    ARRAY,\n    OBJECT\n  } = AnalyzerDATA_TYPES;\n\n  // TODO: un recognized types\n  // CURRENCY PERCENT NONE\n  switch (aType) {\n    case DATE:\n      return ALL_FIELD_TYPES.date;\n    case TIME:\n    case DATETIME:\n      return ALL_FIELD_TYPES.timestamp;\n    case FLOAT:\n      return ALL_FIELD_TYPES.real;\n    case INT:\n      return ALL_FIELD_TYPES.integer;\n    case BOOLEAN:\n      return ALL_FIELD_TYPES.boolean;\n    case GEOMETRY:\n    case GEOMETRY_FROM_STRING:\n    case PAIR_GEOMETRY_FROM_STRING:\n      return ALL_FIELD_TYPES.geojson;\n    case ARRAY:\n      return ALL_FIELD_TYPES.array;\n    case OBJECT:\n      return ALL_FIELD_TYPES.object;\n    case NUMBER:\n    case STRING:\n    case ZIPCODE:\n      return ALL_FIELD_TYPES.string;\n    case H3_ANALYZER_TYPE:\n      return ALL_FIELD_TYPES.h3;\n    default:\n      globalConsole.warn(`Unsupported analyzer type: ${aType}`);\n      return ALL_FIELD_TYPES.string;\n  }\n}\n\n/**\n * Analyze field types from data in `string` format, e.g. uploaded csv.\n * Assign `type`, `fieldIdx` and `format` (timestamp only) to each field\n *\n * @param data array of row object\n * @param fieldOrder array of field names as string\n * @returns formatted fields\n * @public\n * @example\n *\n * import {getFieldsFromData} from '@kepler.gl/common-utils';\n * const data = [{\n *   time: '2016-09-17 00:09:55',\n *   value: '4',\n *   surge: '1.2',\n *   isTrip: 'true',\n *   zeroOnes: '0'\n * }, {\n *   time: '2016-09-17 00:30:08',\n *   value: '3',\n *   surge: null,\n *   isTrip: 'false',\n *   zeroOnes: '1'\n * }, {\n *   time: null,\n *   value: '2',\n *   surge: '1.3',\n *   isTrip: null,\n *   zeroOnes: '1'\n * }];\n *\n * const fieldOrder = ['time', 'value', 'surge', 'isTrip', 'zeroOnes'];\n * const fields = getFieldsFromData(data, fieldOrder);\n * // fields = [\n * // {name: 'time', format: 'YYYY-M-D H:m:s', fieldIdx: 1, type: 'timestamp'},\n * // {name: 'value', format: '', fieldIdx: 4, type: 'integer'},\n * // {name: 'surge', format: '', fieldIdx: 5, type: 'real'},\n * // {name: 'isTrip', format: '', fieldIdx: 6, type: 'boolean'},\n * // {name: 'zeroOnes', format: '', fieldIdx: 7, type: 'integer'}];\n *\n */\nexport function getFieldsFromData(data: RowData, fieldOrder: string[]): Field[] {\n  // add a check for epoch timestamp\n  const metadata = Analyzer.computeColMeta(\n    data,\n    [\n      {regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'},\n      {regex: /.*census/g, dataType: 'STRING'}\n    ],\n    {ignoredDataTypes: IGNORE_DATA_TYPES}\n  );\n\n  const {fieldByIndex} = renameDuplicateFields(fieldOrder);\n\n  const result = fieldOrder.map((field, index) => {\n    const name = fieldByIndex[index];\n\n    const fieldMeta = metadata.find(m => m.key === field);\n\n    // fieldMeta could be undefined if the field has no data and Analyzer.computeColMeta\n    // will ignore the field. In this case, we will simply assign the field type to STRING\n    // since dropping the column in the RowData could be expensive\n    let type = fieldMeta?.type || 'STRING';\n    const format = fieldMeta?.format || '';\n\n    // quick check if first valid string in column is H3\n    if (type === AnalyzerDATA_TYPES.STRING) {\n      for (let i = 0, n = data.length; i < n; ++i) {\n        if (notNullorUndefined(data[i][name])) {\n          type = h3IsValid(data[i][name] || '') ? H3_ANALYZER_TYPE : type;\n          break;\n        }\n      }\n    }\n\n    // quick check if string is hex wkb\n    if (type === AnalyzerDATA_TYPES.STRING) {\n      type = data.some(d => isHexWkb(d[name])) ? AnalyzerDATA_TYPES.GEOMETRY : type;\n    }\n\n    return {\n      name,\n      id: name,\n      displayName: name,\n      format,\n      fieldIdx: index,\n      type: analyzerTypeToFieldType(type),\n      analyzerType: type,\n      valueAccessor: dc => d => {\n        return dc.valueAt(d.index, index);\n      }\n    };\n  });\n\n  return result;\n}\n\n/**\n * pass in an array of field names, rename duplicated one\n * and return a map from old field index to new name\n *\n * @param fieldOrder\n * @returns new field name by index\n */\nexport function renameDuplicateFields(fieldOrder: string[]): {\n  allNames: string[];\n  fieldByIndex: string[];\n} {\n  return fieldOrder.reduce<{allNames: string[]; fieldByIndex: string[]}>(\n    (accu, field, i) => {\n      const {allNames} = accu;\n      let fieldName = field;\n\n      // add a counter to duplicated names\n      if (allNames.includes(field)) {\n        let counter = 0;\n        while (allNames.includes(`${field}-${counter}`)) {\n          counter++;\n        }\n        fieldName = `${field}-${counter}`;\n      }\n\n      accu.fieldByIndex[i] = fieldName;\n      accu.allNames.push(fieldName);\n\n      return accu;\n    },\n    {allNames: [], fieldByIndex: []}\n  );\n}\n"
  },
  {
    "path": "src/common-utils/src/data.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Analyzer, DATA_TYPES} from 'type-analyzer';\n\nimport {Field} from '@kepler.gl/types';\n\nexport function notNullorUndefined<T extends NonNullable<any>>(d: T | null | undefined): d is T {\n  return d !== undefined && d !== null;\n}\n\n/**\n * Check if string is a valid Well-known binary (WKB) in HEX format\n * https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry\n *\n * @param str input string\n * @returns true if string is a valid WKB in HEX format\n */\nexport function isHexWkb(str: string | null): boolean {\n  if (!str) return false;\n  // check if the length of the string is even and is at least 10 characters long\n  if (str.length < 10 || str.length % 2 !== 0) {\n    return false;\n  }\n  // check if first two characters are 00 or 01\n  if (!str.startsWith('00') && !str.startsWith('01')) {\n    return false;\n  }\n  // check if the rest of the string is a valid hex\n  return /^[0-9a-fA-F]+$/.test(str.slice(2));\n}\n\n/**\n * Converts non-arrays to arrays.  Leaves arrays alone.  Converts\n * undefined values to empty arrays ([] instead of [undefined]).\n * Otherwise, just returns [item] for non-array items.\n *\n * @param {*} item\n * @returns {array} boom! much array. very indexed. so useful.\n */\nexport function toArray<T>(item: T | T[]): T[] {\n  if (Array.isArray(item)) {\n    return item;\n  }\n\n  if (typeof item === 'undefined' || item === null) {\n    return [];\n  }\n\n  return [item];\n}\n\n/**\n * Move an array item to a different position. Returns a new array with the item moved to the new position.\n */\nexport function arrayMove<T>(array: T[], from: number, to: number): T[] {\n  const newArray = array.slice();\n  newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);\n\n  return newArray;\n}\n\n/**\n * Check whether geojson linestring's 4th coordinate is 1) not timestamp 2) unix time stamp 3) real date time\n * @param timestamps array to be tested if its elements are timestamp\n * @returns the type of timestamp: unix/datetime/invalid(not timestamp)\n */\nexport function containValidTime(timestamps: string[]): Field | null {\n  const formattedTimeStamps = timestamps.map(ts => ({ts}));\n  const ignoredDataTypes = Object.keys(DATA_TYPES).filter(\n    type => ![DATA_TYPES.TIME, DATA_TYPES.DATETIME, DATA_TYPES.DATE].includes(type)\n  );\n\n  // ignore all types but TIME to improve performance\n  const analyzedType = Analyzer.computeColMeta(formattedTimeStamps, [], {ignoredDataTypes})[0];\n\n  if (!analyzedType || analyzedType.category !== 'TIME') {\n    return null;\n  }\n  return analyzedType;\n}\n"
  },
  {
    "path": "src/common-utils/src/h3-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {h3GetResolution, H3Index, h3IsValid, h3ToGeo, h3ToGeoBoundary} from 'h3-js';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\n\nexport {h3GetResolution, h3IsValid};\n\nexport type Centroid = [number, number];\n\n// get vertices should return [lon, lat]\nexport function getVertices({id}: {id: H3Index}) {\n  // always reverse it\n  return h3ToGeoBoundary(id, true);\n}\n\n// get centroid should return [lon, lat]\nexport function getCentroid({id}: {id: H3Index}): Centroid {\n  // always reverse it to [lng, lat]\n  return h3ToGeo(id).reverse() as Centroid;\n}\n\nexport function idToPolygonGeo(object?: {id: H3Index}, properties?: any) {\n  if (!object?.id) {\n    return null;\n  }\n  const vertices = getVertices(object);\n\n  return {\n    type: 'Feature',\n    geometry: {\n      coordinates: properties?.isClosed ? [vertices] : vertices,\n      type: properties?.isClosed ? 'Polygon' : 'LineString'\n    },\n    properties\n  };\n}\n\nexport const isHexField = (field, _fieldIdx, _dataContainer) => {\n  return field.type === ALL_FIELD_TYPES.h3;\n};\n\nexport const getHexFields = (fields, dataContainer) =>\n  fields.filter((f, i) => isHexField(f, i, dataContainer));\n"
  },
  {
    "path": "src/common-utils/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './data';\nexport * from './data-type';\nexport * from './string';\nexport * from './url';\nexport * from './promise';\n\nexport {getCentroid, getHexFields, h3IsValid, idToPolygonGeo} from './h3-utils';\nexport type {Centroid} from './h3-utils';\n"
  },
  {
    "path": "src/common-utils/src/promise.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Utility function to create a promise that resolves after a specified number of milliseconds\n * @param ms number of milliseconds to wait\n * @returns Promise that resolves after the specified delay\n */\nexport const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));\n"
  },
  {
    "path": "src/common-utils/src/string.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Generate a hash string based on number of character\n * @param {number} count\n * @returns {string} hash string\n */\nexport function generateHashId(count = 6): string {\n  return Math.random().toString(36).substr(count);\n}\n"
  },
  {
    "path": "src/common-utils/src/url.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Allows to break down a url into multiple params\n * from http://blog.stevenlevithan.com/archives/parseuri\n */\nexport function parseUri(str: string): {[key: string]: any} {\n  const o = parseUri.options;\n  const m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str);\n  const uri = {};\n  let i = 14;\n\n  while (i--) uri[o.key[i]] = m?.[i] || '';\n\n  uri[o.q.name] = {};\n  uri[o.key[12]].replace(o.q.parser, ($0, $1, $2) => {\n    if ($1) uri[o.q.name][$1] = $2;\n  });\n\n  return uri;\n}\n\nparseUri.options = {\n  strictMode: false,\n  key: [\n    'source',\n    'protocol',\n    'authority',\n    'userInfo',\n    'user',\n    'password',\n    'host',\n    'port',\n    'relative',\n    'path',\n    'directory',\n    'file',\n    'query',\n    'anchor'\n  ],\n  q: {\n    name: 'queryKey',\n    parser: /(?:^|&)([^&=]*)=?([^&]*)/g\n  },\n  parser: {\n    strict:\n      // eslint-disable-next-line no-useless-escape\n      /^(?:([^:\\/?#]+):)?(?:\\/\\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\\/?#]*)(?::(\\d*))?))?((((?:[^?#\\/]*\\/)*)([^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/,\n    loose:\n      // eslint-disable-next-line no-useless-escape\n      /^(?:(?![^:@]+:[^:@\\/]*@)([^:\\/?#.]+):)?(?:\\/\\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\\/?#]*)(?::(\\d*))?)(((\\/(?:[^?#](?![^?#\\/]*\\.[^?#\\/.]+(?:[?#]|$)))*\\/?)?([^?#\\/]*))(?:\\?([^#]*))?(?:#(.*))?)/\n  }\n};\n\n/**\n * Validates an url\n * @param str\n */\nexport function validateUrl(str) {\n  try {\n    new URL(str);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Checks whether a given URL points to a PMTiles file.\n * @param url The URL to check.\n * @returns True if the URL includes '.pmtiles', otherwise false.\n */\nexport const isPMTilesUrl = (url?: string | null) => url?.includes('.pmtiles');\n"
  },
  {
    "path": "src/common-utils/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/components/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/components/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/components\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && ../../node_modules/.bin/babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/react\": \"^8.9.27\",\n    \"@dnd-kit/core\": \"^6.1.0\",\n    \"@dnd-kit/modifiers\": \"^7.0.0\",\n    \"@dnd-kit/sortable\": \"^8.0.0\",\n    \"@dnd-kit/utilities\": \"^3.2.2\",\n    \"@emotion/is-prop-valid\": \"^1.2.1\",\n    \"@floating-ui/react\": \"0.25.1\",\n    \"@kepler.gl/actions\": \"3.2.6\",\n    \"@kepler.gl/cloud-providers\": \"3.2.6\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/effects\": \"3.2.6\",\n    \"@kepler.gl/layers\": \"3.2.6\",\n    \"@kepler.gl/localization\": \"3.2.6\",\n    \"@kepler.gl/processors\": \"3.2.6\",\n    \"@kepler.gl/reducers\": \"3.2.6\",\n    \"@kepler.gl/schemas\": \"3.2.6\",\n    \"@kepler.gl/styles\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/mvt\": \"^4.3.2\",\n    \"@loaders.gl/pmtiles\": \"^4.3.2\",\n    \"@loaders.gl/wms\": \"4.3.2\",\n    \"@mapbox/mapbox-sdk\": \"^0.15.3\",\n    \"@nebula.gl/edit-modes\": \"1.0.2-alpha.1\",\n    \"@tippyjs/react\": \"^4.2.0\",\n    \"@types/classnames\": \"^2.3.1\",\n    \"@types/d3-array\": \"^2.8.0\",\n    \"@types/d3-brush\": \"^3.0.1\",\n    \"@types/d3-scale\": \"^3.2.2\",\n    \"@types/d3-selection\": \"^3.0.2\",\n    \"@types/exenv\": \"^1.2.0\",\n    \"@types/lodash\": \"4.17.5\",\n    \"@types/react\": \"^18.0.28\",\n    \"@types/react-copy-to-clipboard\": \"^5.0.2\",\n    \"@types/react-dom\": \"^18.0.11\",\n    \"@types/react-lifecycles-compat\": \"^3.0.1\",\n    \"@types/react-map-gl\": \"^6.1.3\",\n    \"@types/react-modal\": \"^3.16.3\",\n    \"@types/react-redux\": \"^7.1.23\",\n    \"@types/react-virtualized\": \"^9.21.30\",\n    \"@types/react-vis\": \"1.11.7\",\n    \"@types/styled-components\": \"^5.1.32\",\n    \"classnames\": \"^2.2.1\",\n    \"copy-to-clipboard\": \"^3.3.1\",\n    \"d3-array\": \"^2.8.0\",\n    \"d3-axis\": \"^2.0.0\",\n    \"d3-brush\": \"^2.1.0\",\n    \"d3-color\": \"^2.0.0\",\n    \"d3-format\": \"^2.0.0\",\n    \"d3-scale\": \"^3.2.3\",\n    \"d3-selection\": \"^2.0.0\",\n    \"exenv\": \"^1.2.2\",\n    \"fuzzy\": \"^0.1.3\",\n    \"global\": \"^4.3.0\",\n    \"lodash\": \"4.17.21\",\n    \"mapbox-gl\": \"1.13.1\",\n    \"maplibre-gl\": \"^3.6.2\",\n    \"markdown-to-jsx\": \"^7.7.6\",\n    \"mjolnir.js\": \"^2.7.0\",\n    \"moment\": \"^2.10.6\",\n    \"moment-timezone\": \"^0.5.35\",\n    \"prop-types\": \"^15.6.0\",\n    \"react\": \"^18.2.0\",\n    \"react-color\": \"^2.19.3\",\n    \"react-copy-to-clipboard\": \"^5.0.2\",\n    \"react-date-picker\": \"^10.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-intl\": \"^6.3.0\",\n    \"react-json-pretty\": \"^2.2.0\",\n    \"react-lifecycles-compat\": \"^3.0.4\",\n    \"react-map-gl\": \"^7.1.6\",\n    \"react-modal\": \"^3.12.1\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-sortable-hoc\": \"^1.8.3\",\n    \"react-time-picker\": \"^6.2.0\",\n    \"react-tooltip\": \"^4.2.17\",\n    \"react-virtualized\": \"9.22.6\",\n    \"react-vis\": \"1.11.7\",\n    \"redux\": \"^4.2.1\",\n    \"reselect\": \"^4.1.0\",\n    \"styled-components\": \"6.1.8\",\n    \"suncalc\": \"^1.9.0\",\n    \"viewport-mercator-project\": \"^6.0.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/components/src/bottom-widget.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {forwardRef, useMemo, useCallback} from 'react';\nimport styled, {withTheme, IStyledComponent} from 'styled-components';\n\nimport {FILTER_VIEW_TYPES} from '@kepler.gl/constants';\nimport {hasPortableWidth, isSideFilter, mergeFilterWithTimeline} from '@kepler.gl/utils';\nimport {media, breakPointValues} from '@kepler.gl/styles';\nimport {TimeRangeFilter} from '@kepler.gl/types';\n\nimport TimeWidgetFactory from './filters/time-widget';\nimport {bottomWidgetSelector} from './kepler-gl';\nimport FilterAnimationControllerFactory from './filter-animation-controller';\nimport LayerAnimationControllerFactory from './layer-animation-controller';\nimport AnimationControlFactory from './common/animation-control/animation-control';\nimport {BaseComponentProps} from './types';\n\nconst maxWidth = 1080;\n\nexport type BottomWidgetContainerProps = BaseComponentProps & {\n  hasPadding?: boolean;\n  width: number;\n  ref: React.ForwardedRef<HTMLDivElement>;\n};\n\nconst BottomWidgetContainer: IStyledComponent<\n  'web',\n  BottomWidgetContainerProps\n> = styled.div<BottomWidgetContainerProps>`\n  display: flex;\n  flex-direction: column;\n  padding-top: ${props => (props.hasPadding ? props.theme.bottomWidgetPaddingTop : 0)}px;\n  padding-right: ${props => (props.hasPadding ? props.theme.bottomWidgetPaddingRight : 0)}px;\n  padding-bottom: ${props => (props.hasPadding ? props.theme.bottomWidgetPaddingBottom : 0)}px;\n  padding-left: ${props => (props.hasPadding ? props.theme.bottomWidgetPaddingLeft : 0)}px;\n  pointer-events: none !important; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n  width: ${props => props.width}px;\n  z-index: 1;\n  ${media.portable`padding: 0;`}\n`;\n\nexport type BottomWidgetProps = {\n  rootRef: React.ForwardedRef<HTMLDivElement>;\n  containerW: number;\n} & ReturnType<typeof bottomWidgetSelector>;\ntype ThemeProp = {\n  theme: any;\n};\ntype BottomWidgetThemedProps = BottomWidgetProps & ThemeProp;\n\nBottomWidgetFactory.deps = [\n  TimeWidgetFactory,\n  AnimationControlFactory,\n  FilterAnimationControllerFactory,\n  LayerAnimationControllerFactory\n];\n\n/* eslint-disable complexity */\nexport default function BottomWidgetFactory(\n  TimeWidget: ReturnType<typeof TimeWidgetFactory>,\n  AnimationControl: ReturnType<typeof AnimationControlFactory>,\n  FilterAnimationController: ReturnType<typeof FilterAnimationControllerFactory>,\n  LayerAnimationController: ReturnType<typeof LayerAnimationControllerFactory>\n): React.FC<BottomWidgetThemedProps> {\n  const LayerAnimationControl = styled(AnimationControl)`\n    background-color: ${props => props.theme.sidePanelBg};\n  `;\n\n  const BottomWidget: React.FC<BottomWidgetThemedProps> = (props: BottomWidgetThemedProps) => {\n    const {\n      datasets,\n      filters,\n      animationConfig,\n      visStateActions,\n      containerW,\n      uiState,\n      sidePanelWidth,\n      layers,\n      rootRef,\n      theme\n    } = props;\n\n    const {activeSidePanel, readOnly} = uiState;\n    const isOpen = Boolean(activeSidePanel);\n\n    const enlargedFilterIdx = useMemo(() => filters.findIndex(f => !isSideFilter(f)), [filters]);\n    const animatedFilterIdx = useMemo(() => filters.findIndex(f => f.isAnimating), [filters]);\n    const animatedFilter = animatedFilterIdx > -1 ? filters[animatedFilterIdx] : null;\n    // we need to hide layer timeline when filter is synced and not enlarged\n    const isTimelineLinkedWithFilter = useMemo(\n      () => (filters as TimeRangeFilter[]).some(f => f.syncedWithLayerTimeline),\n      [filters]\n    );\n\n    const isMobile = useMemo(() => hasPortableWidth(breakPointValues), []);\n    const isLegendPinned =\n      uiState.mapControls?.mapLegend?.show && uiState.mapControls?.mapLegend?.active;\n    const spaceForLegendWidth = isLegendPinned\n      ? theme.mapControl?.width +\n        theme.mapControl?.mapLegend?.pinned?.right * 2 -\n        theme.bottomWidgetPaddingRight\n      : 0;\n\n    const enlargedFilterWidth = useMemo(\n      () => (!isMobile && isOpen ? containerW - sidePanelWidth : containerW) - spaceForLegendWidth,\n      [isMobile, isOpen, containerW, sidePanelWidth, spaceForLegendWidth]\n    );\n\n    // show playback control if layers contain trip layer & at least one trip layer is visible\n    const animatableLayer = useMemo(\n      () =>\n        layers.filter(l => l.config.animation && l.config.animation.enabled && l.config.isVisible),\n      [layers]\n    );\n\n    const readyToAnimation = useMemo(\n      () => Array.isArray(animationConfig.domain) && Number.isFinite(animationConfig.currentTime),\n      [animationConfig.domain, animationConfig.currentTime]\n    );\n\n    // if animation control is showing, hide time display in time slider\n    const showFloatingTimeDisplay = !animatableLayer.length;\n    const showAnimationControl =\n      animatableLayer.length && readyToAnimation && !animationConfig.hideControl;\n    const showTimeWidget = enlargedFilterIdx > -1 && Object.keys(datasets).length > 0;\n\n    // if filter is not animating, pass in enlarged filter here because\n    // animation controller needs to call reset on it\n    const filter = useMemo(\n      () => (animatedFilter as TimeRangeFilter) || filters[enlargedFilterIdx],\n      [animatedFilter, filters, enlargedFilterIdx]\n    );\n\n    // we merge filter and timeline if filter is synced\n    const {filter: enhancedFilter, animationConfig: enhancedAnimationConfig} = useMemo(\n      () =>\n        filter?.syncedWithLayerTimeline\n          ? mergeFilterWithTimeline(filter, animationConfig)\n          : {filter, animationConfig},\n      [filter, animationConfig]\n    );\n\n    const onClose = useCallback(\n      () => visStateActions.setFilterView(enlargedFilterIdx, FILTER_VIEW_TYPES.side),\n      [visStateActions, enlargedFilterIdx]\n    );\n\n    const onToggleMinify = useCallback(\n      () =>\n        visStateActions.setFilterView(\n          enlargedFilterIdx,\n          filter.view === FILTER_VIEW_TYPES.enlarged\n            ? FILTER_VIEW_TYPES.minified\n            : FILTER_VIEW_TYPES.enlarged\n        ),\n      [enlargedFilterIdx, visStateActions, filter]\n    );\n\n    return (\n      <BottomWidgetContainer\n        width={Math.min(maxWidth, enlargedFilterWidth)}\n        style={{marginRight: spaceForLegendWidth}}\n        className=\"bottom-widget--container\"\n        hasPadding={showAnimationControl || showTimeWidget}\n        ref={rootRef}\n      >\n        {!isTimelineLinkedWithFilter ? (\n          <LayerAnimationController\n            animationConfig={enhancedAnimationConfig}\n            setLayerAnimationTime={visStateActions.setLayerAnimationTime}\n          >\n            {(isAnimating, start, pause, resetAnimation, timeline, setTimelineValue) =>\n              showAnimationControl ? (\n                <LayerAnimationControl\n                  updateAnimationSpeed={visStateActions.updateLayerAnimationSpeed}\n                  toggleAnimation={visStateActions.toggleLayerAnimation}\n                  isAnimatable={!animatedFilter}\n                  isAnimating={isAnimating}\n                  resetAnimation={resetAnimation}\n                  setTimelineValue={setTimelineValue}\n                  timeline={timeline}\n                />\n              ) : null\n            }\n          </LayerAnimationController>\n        ) : null}\n        {enhancedFilter ? (\n          <FilterAnimationController\n            filter={enhancedFilter}\n            filterIdx={animatedFilterIdx > -1 ? animatedFilterIdx : enlargedFilterIdx}\n            setFilterAnimationTime={visStateActions.setFilterAnimationTime}\n          >\n            {(isAnimating, start, pause, resetAnimation, timeline, setTimelineValue) =>\n              showTimeWidget && timeline ? (\n                <TimeWidget\n                  // TimeWidget uses React.memo, here we pass width\n                  // even though it doesnt use it, to force rerender\n                  filter={enhancedFilter as TimeRangeFilter}\n                  index={enlargedFilterIdx}\n                  datasets={datasets}\n                  layers={layers}\n                  readOnly={readOnly}\n                  showTimeDisplay={showFloatingTimeDisplay}\n                  setFilterPlot={visStateActions.setFilterPlot}\n                  setFilterAnimationTime={setTimelineValue}\n                  setFilterAnimationWindow={visStateActions.setFilterAnimationWindow}\n                  setFilterSyncTimelineMode={visStateActions.setTimeFilterSyncTimelineMode}\n                  toggleAnimation={visStateActions.toggleFilterAnimation}\n                  updateAnimationSpeed={visStateActions.updateFilterAnimationSpeed}\n                  resetAnimation={resetAnimation}\n                  isAnimatable={!animationConfig || !animationConfig.isAnimating}\n                  animationConfig={animationConfig}\n                  onClose={onClose}\n                  timeline={timeline}\n                  onToggleMinify={onToggleMinify}\n                />\n              ) : null\n            }\n          </FilterAnimationController>\n        ) : null}\n      </BottomWidgetContainer>\n    );\n  };\n\n  return withTheme(\n    forwardRef((props: BottomWidgetThemedProps, ref: React.ForwardedRef<HTMLDivElement>) => (\n      <BottomWidget {...props} rootRef={ref} />\n    ))\n  );\n}\n/* eslint-enable complexity */\n"
  },
  {
    "path": "src/components/src/common/action-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport React, {useCallback, PropsWithChildren, ElementType, CSSProperties, ReactNode} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {ArrowRight} from './icons';\nimport Checkbox from './switch';\nimport {BaseComponentProps} from '../types';\nimport TippyTooltip from './tippy-tooltip';\n\nexport type ActionPanelProps = PropsWithChildren<{\n  color?: string;\n  className?: string;\n  direction?: CSSProperties['direction'];\n}>;\n\nexport type ActionPanelItemProps = PropsWithChildren<{\n  color?: string;\n  className?: string;\n  Icon?: ElementType;\n  label: string;\n  onClick?: () => void;\n  isSelection?: boolean;\n  isActive?: boolean;\n  isDisabled?: boolean;\n  tooltipText?: string | null;\n  style?: CSSProperties;\n}>;\n\nconst StyledItem = styled.div`\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  font-size: 12px;\n  line-height: 14px;\n  padding: 8px 16px 8px 8px;\n  min-height: ${props => props.theme.actionPanelHeight}px;\n  text-transform: capitalize;\n  background-color: ${props => props.theme.dropdownListBgd};\n  max-width: 200px;\n  position: relative;\n\n  ${props => (props.color ? `border-left: 3px solid rgb(${props.color});` : '')} &:hover {\n    color: ${props => props.theme.textColorHl};\n    .nested-group {\n      display: block;\n    }\n  }\n\n  .label {\n    margin-left: 8px;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n\n  .label-icon {\n    margin-left: 4px;\n    margin-bottom: -2px;\n  }\n  .icon {\n    width: 18px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n  .nested-group {\n    max-width: 200px;\n    overflow: hidden;\n    display: none;\n    color: ${props => props.theme.textColor};\n    position: absolute;\n    left: 100%;\n    top: 0px;\n    padding-left: 4px;\n\n    label {\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n  }\n`;\n\nconst StyledCheckedbox = styled(Checkbox)`\n  label {\n    margin-bottom: 0;\n    color: ${props => props.theme.textColor};\n    padding-left: 20px;\n    line-height: 12px;\n\n    &:before {\n      width: 12px;\n      height: 12px;\n      background-color: ${props => props.theme.dropdownListBgd};\n    }\n    &:hover {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst renderChildren = (child: ReactNode, index: number) =>\n  child &&\n  React.isValidElement<any>(child) &&\n  React.cloneElement(child, {\n    onClick: () => {\n      if (child.props.onClick) {\n        child.props.onClick(index);\n      }\n    },\n    className: classnames('action-panel-item', child.props.className)\n  });\n\nexport const ActionPanelItem = React.memo(\n  ({\n    children,\n    color,\n    className,\n    Icon,\n    label,\n    onClick,\n    isSelection,\n    isActive,\n    isDisabled,\n    tooltipText,\n    style\n  }: ActionPanelItemProps) => {\n    const onClickCallback = useCallback(\n      event => {\n        if (isDisabled) {\n          return;\n        }\n        event.preventDefault();\n        event.stopPropagation();\n        onClick?.();\n      },\n      [onClick, isDisabled]\n    );\n\n    const content = (\n      <StyledItem\n        className={className}\n        onClick={onClickCallback}\n        color={color}\n        style={{\n          ...(isDisabled ? {cursor: 'not-allowed', opacity: 0.5} : {cursor: 'pointer'}),\n          ...style\n        }}\n      >\n        {Icon ? (\n          <div className=\"icon\">\n            <Icon height=\"16px\" />\n          </div>\n        ) : null}\n        {isSelection ? (\n          <StyledCheckedbox\n            type=\"checkbox\"\n            checked={Boolean(isActive)}\n            disabled={Boolean(isDisabled)}\n            id={`switch-${label}`}\n            secondary\n            label={label}\n          />\n        ) : (\n          <span className=\"label\">{label}</span>\n        )}\n        {children ? (\n          <div>\n            <div className=\"label-icon\">\n              <ArrowRight height=\"16px\" />\n            </div>\n            {!isDisabled ? (\n              <div className=\"nested-group\">{React.Children.map(children, renderChildren)}</div>\n            ) : null}\n          </div>\n        ) : null}\n      </StyledItem>\n    );\n    return tooltipText ? (\n      <TippyTooltip render={() => <div>{tooltipText}</div>}>{content}</TippyTooltip>\n    ) : (\n      content\n    );\n  }\n);\n\nActionPanelItem.displayName = 'ActionPanelItem';\n\nexport type StyledActionPanelProps = BaseComponentProps & {\n  direction?: string;\n};\n\nconst StyledActionPanel: IStyledComponent<\n  'web',\n  StyledActionPanelProps\n> = styled.div<StyledActionPanelProps>`\n  display: flex;\n  flex-direction: ${props => props.direction};\n  box-shadow: ${props => props.theme.dropdownListShadow};\n  transition: ${props => props.theme.transitionSlow};\n  color: ${props => props.theme.textColor};\n\n  .action-panel-item {\n    ${props =>\n      props.direction === 'column'\n        ? `border-bottom: 1px solid ${props.theme.panelHeaderIcon}`\n        : `border-right: 1px solid ${props.theme.panelHeaderIcon}`} &:last-of-type {\n      border-bottom: 0;\n    }\n  }\n`;\n\n// React compound element https://medium.com/@Dane_s/react-js-compound-components-a6e54b5c9992\n// @ts-expect-error looks like not valid default value for direction prop\nconst ActionPanel = ({children, className, direction = 'column'}: ActionPanelProps) => (\n  <StyledActionPanel className={className} direction={direction}>\n    {React.Children.map(children, renderChildren)}\n  </StyledActionPanel>\n);\n\nActionPanel.displayName = 'ActionPanel';\n\nexport default ActionPanel;\n"
  },
  {
    "path": "src/components/src/common/animation-control/animation-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\n\nimport {media} from '@kepler.gl/styles';\nimport {Timeline, Filter} from '@kepler.gl/types';\n\nimport TimelineSliderFactory from '../timeline-slider';\nimport PlaybackControlsFactory from './playback-controls';\nimport FloatingTimeDisplayFactory from './floating-time-display';\n\nconst SLIDER_MARGIN_PALM = 6;\n\nconst AnimationControlContainer = styled.div`\n  padding: ${props => `${props.theme.bottomInnerPdVert}px ${props.theme.bottomInnerPdSide}px`};\n  position: relative;\n  margin-top: ${props => props.theme.bottomPanelGap}px;\n\n  ${media.portable`\n    border-top: 1px solid ${props => props.theme.panelBorderColor};\n    border-left: 1px solid ${props => props.theme.panelBorderColor};\n    padding: 12px 12px;\n    margin-top: 0;\n  `}\n`;\n\nconst AnimationWidgetInner = styled.div`\n  position: relative;\n  display: flex;\n  align-items: center;\n\n  .animation-control__time-slider {\n    display: flex;\n    align-items: center;\n    height: 32px;\n    width: 100%;\n  }\n  .playback-controls {\n    margin-left: 16px;\n  }\n\n  ${media.palm`\n    flex-direction: column;\n    .playback-controls {\n      margin: 0;\n    }\n    .animation-control__time-slider {\n      width: 100%;\n      position: relative;\n    }\n    .animation-control__time-domain {\n      position: absolute;\n      top: -24px;\n\n      &.domain-start {\n        left: ${SLIDER_MARGIN_PALM}px;\n      }\n      &.domain-end {\n        right: ${SLIDER_MARGIN_PALM}px;\n      }\n    }\n  `};\n`;\n\nconst TIMELINE_PLAYBACK_STYLE = {flex: 1};\n\nexport type AnimationControlProps = {\n  filter?: Filter;\n  timeline?: Timeline;\n  isAnimatable?: boolean;\n  isAnimating?: boolean;\n  updateAnimationSpeed?: (val: number) => void;\n  setAnimationWindow?: (id: string) => void;\n  toggleAnimation: () => void;\n  resetAnimation?: () => void;\n  setTimelineValue: (value: number[]) => void;\n  showTimeDisplay?: boolean;\n  showTimeline?: boolean;\n  showControls?: boolean;\n  className?: string;\n  style?: object;\n};\n\nAnimationControlFactory.deps = [\n  PlaybackControlsFactory,\n  FloatingTimeDisplayFactory,\n  TimelineSliderFactory\n];\n\nfunction AnimationControlFactory(\n  PlaybackControls: ReturnType<typeof PlaybackControlsFactory>,\n  FloatingTimeDisplay: ReturnType<typeof FloatingTimeDisplayFactory>,\n  TimelineSlider: ReturnType<typeof TimelineSliderFactory>\n) {\n  const AnimationControl: React.FC<AnimationControlProps> = ({\n    filter,\n    className,\n    style,\n    isAnimatable,\n    isAnimating,\n    resetAnimation,\n    toggleAnimation = () => {\n      return;\n    },\n    updateAnimationSpeed = () => {\n      return;\n    },\n    setTimelineValue,\n    setAnimationWindow,\n    timeline,\n    showTimeline = true,\n    showControls = true,\n    showTimeDisplay = true\n  }) => {\n    if (!timeline) {\n      return null;\n    }\n\n    const {animationWindow, value, speed, defaultTimeFormat, timeFormat, timezone} = timeline;\n\n    return (\n      <AnimationControlContainer\n        style={style}\n        className={classnames('animation-control-container', className)}\n      >\n        <AnimationWidgetInner className=\"animation-widget--inner\">\n          {showTimeline ? (\n            <TimelineSlider\n              style={TIMELINE_PLAYBACK_STYLE}\n              timeline={timeline}\n              setTimelineValue={setTimelineValue}\n            />\n          ) : null}\n          {showControls ? (\n            <PlaybackControls\n              className=\"animation-control-playpause\"\n              filter={filter}\n              isAnimatable={isAnimatable}\n              startAnimation={toggleAnimation}\n              isAnimating={isAnimating}\n              pauseAnimation={toggleAnimation}\n              resetAnimation={resetAnimation}\n              speed={speed}\n              updateAnimationSpeed={updateAnimationSpeed}\n              setFilterAnimationWindow={setAnimationWindow}\n              animationWindow={animationWindow}\n            />\n          ) : null}\n        </AnimationWidgetInner>\n        {showTimeDisplay ? (\n          <FloatingTimeDisplay\n            currentTime={value}\n            defaultTimeFormat={defaultTimeFormat}\n            timeFormat={timeFormat}\n            timezone={timezone}\n          />\n        ) : null}\n      </AnimationControlContainer>\n    );\n  };\n\n  return AnimationControl;\n}\n\nexport default AnimationControlFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/animation-controller.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {bisectLeft} from 'd3-array';\nimport {requestAnimationFrame, cancelAnimationFrame} from 'global/window';\nimport Console from 'global/console';\nimport {BASE_SPEED, FPS, ANIMATION_WINDOW} from '@kepler.gl/constants';\nimport {Timeline} from '@kepler.gl/types';\n\ninterface AnimationControllerProps<T extends number | number[]> {\n  isAnimating?: boolean;\n  speed?: number;\n  updateAnimation?: (x: T) => void;\n  setTimelineValue: (x: T) => void;\n  timeline?: Timeline;\n  animationWindow?: string;\n  steps?: number[] | null;\n  domain: number[] | null;\n  value: T;\n  baseSpeed?: number;\n  children?: (\n    isAnimating: boolean | undefined,\n    startAnimation: () => void,\n    pauseAnimation: () => void,\n    resetAnimation: () => void,\n    timeline: Timeline | undefined,\n    setTimelineValue: (x: T) => void\n  ) => React.ReactElement | null;\n}\n\nclass AnimationControllerType<T extends number | number[]> extends Component<\n  AnimationControllerProps<T>\n> {}\n\nfunction AnimationControllerFactory(): typeof AnimationControllerType {\n  /**\n   * 4 Animation Window Types\n   * 1. free\n   *  |->  |->\n   * Current time is a fixed range, animate a moving window that calls next animation frames continuously\n   * The increment id based on domain / BASE_SPEED * SPEED\n   *\n   * 2. incremental\n   * |    |->\n   * Same as free, current time is a growing range, only the max value of range increment during animation.\n   * The increment is also based on domain / BASE_SPEED * SPEED\n   *\n   * 3. point\n   * o -> o\n   * Current time is a point, animate a moving point calls next animation frame continuously\n   * The increment is based on domain / BASE_SPEED * SPEED\n   *\n   * 4. interval\n   * o ~> o\n   * Current time is a point. An array of sorted time steps are provided,\n   * animate a moving point jumps to the next step\n   */\n  class AnimationController<T extends number | number[]> extends Component<\n    AnimationControllerProps<T>\n  > {\n    static defaultProps = {\n      baseSpeed: BASE_SPEED,\n      speed: 1,\n      steps: null,\n      animationWindow: ANIMATION_WINDOW.free\n    };\n\n    state = {\n      isAnimating: false\n    };\n\n    componentDidMount() {\n      this._startOrPauseAnimation();\n    }\n\n    componentDidUpdate() {\n      this._startOrPauseAnimation();\n    }\n\n    componentWillUnmount() {\n      if (this._timer) {\n        cancelAnimationFrame(this._timer);\n      }\n    }\n\n    _timer = null;\n    _startTime = 0;\n\n    _startOrPauseAnimation() {\n      const {isAnimating, speed = 1} = this.props;\n      if (!this._timer && isAnimating && speed > 0) {\n        this._startAnimation();\n      } else if (this._timer && !isAnimating) {\n        this._pauseAnimation();\n      }\n    }\n\n    _animate = delay => {\n      this._startTime = new Date().getTime();\n\n      const loop = () => {\n        const current = new Date().getTime();\n        const delta = current - this._startTime;\n\n        if (delta >= delay) {\n          this._nextFrame();\n          this._startTime = new Date().getTime();\n        } else {\n          this._timer = requestAnimationFrame(loop);\n        }\n      };\n\n      this._timer = requestAnimationFrame(loop);\n    };\n\n    _resetAnimationByDomain = () => {\n      const {domain, value, animationWindow, updateAnimation} = this.props;\n      if (!domain) {\n        return;\n      }\n      // interim solution while we fully migrate filter and layer controllers\n      const setTimelineValue = updateAnimation || this.props.setTimelineValue;\n\n      if (Array.isArray(value)) {\n        if (animationWindow === ANIMATION_WINDOW.incremental) {\n          setTimelineValue([value[0], value[0] + 1] as T);\n        } else {\n          setTimelineValue([domain[0], domain[0] + value[1] - value[0]] as T);\n        }\n      } else {\n        setTimelineValue(domain[0] as T);\n      }\n    };\n\n    _resetAnimationByTimeStep = () => {\n      const {steps = null, updateAnimation} = this.props;\n      if (!steps) return;\n      // interim solution while we fully migrate filter and layer controllers\n      const setTimelineValue = updateAnimation || this.props.setTimelineValue;\n\n      // go to the first steps\n      setTimelineValue([steps[0], 0] as T);\n    };\n\n    _resetAnimation = () => {\n      if (this.props.animationWindow === ANIMATION_WINDOW.interval) {\n        this._resetAnimationByTimeStep();\n      } else {\n        this._resetAnimationByDomain();\n      }\n    };\n\n    _startAnimation = () => {\n      const {speed = 1} = this.props;\n      this._clearTimer();\n      if (speed > 0) {\n        if (this.props.animationWindow === ANIMATION_WINDOW.interval) {\n          // animate by interval\n          // 30*600\n          const {steps} = this.props;\n          if (!Array.isArray(steps) || !steps.length) {\n            Console.warn('animation steps should be an array');\n            return;\n          }\n          // when speed = 1, animation should loop through 600 frames at 60 FPS\n          // calculate delay based on # steps\n          const delay = (BASE_SPEED * (1000 / FPS)) / steps.length / (speed || 1);\n          this._animate(delay);\n        } else {\n          this._timer = requestAnimationFrame(this._nextFrame);\n        }\n      }\n      this.setState({isAnimating: true});\n    };\n\n    _clearTimer = () => {\n      if (this._timer) {\n        cancelAnimationFrame(this._timer);\n        this._timer = null;\n      }\n    };\n\n    _pauseAnimation = () => {\n      this._clearTimer();\n      this.setState({isAnimating: false});\n    };\n\n    _nextFrame = () => {\n      this._timer = null;\n      const nextValue =\n        this.props.animationWindow === ANIMATION_WINDOW.interval\n          ? this._nextFrameByTimeStep()\n          : this._nextFrameByDomain();\n\n      // interim solution while we fully migrate filter and layer controllers\n      const setTimelineValue = this.props.updateAnimation || this.props.setTimelineValue;\n      setTimelineValue(nextValue as T);\n    };\n\n    _nextFrameByDomain() {\n      const {domain, value, speed = 1, baseSpeed = 600, animationWindow} = this.props;\n      if (!domain) {\n        return;\n      }\n      const delta = ((domain[1] - domain[0]) / baseSpeed) * speed;\n\n      // loop when reaches the end\n      // current time is a range\n      if (Array.isArray(value)) {\n        let value0: number;\n        let value1: number;\n        if (animationWindow === ANIMATION_WINDOW.incremental) {\n          const lastFrame = value[1] + delta > domain[1];\n          value0 = value[0];\n          value1 = lastFrame ? value[0] + 1 : value[1] + delta;\n        } else {\n          // use value[0] to display the last item  duration as the first item\n          const lastFrame = value[0] + delta > domain[1];\n          value0 = lastFrame ? domain[0] : value[0] + delta;\n          value1 = value0 + value[1] - value[0];\n        }\n        return [value0, value1];\n      }\n\n      // current time is a point\n      return Number(value) + delta > domain[1] ? domain[0] : Number(value) + delta;\n    }\n\n    _nextFrameByTimeStep() {\n      const {steps = null, value} = this.props;\n      if (!steps) return;\n      const val = Array.isArray(value) ? value[0] : Number(value);\n      const index = bisectLeft(steps, val);\n      const nextIdx = index >= steps.length - 1 ? 0 : index + 1;\n\n      // why do we need to pass an array of two objects? are we reading nextIdx at some point?\n      // _nextFrameByDomain only returns one value\n      return [steps[nextIdx], nextIdx];\n    }\n\n    render() {\n      const {isAnimating} = this.state;\n      const {children} = this.props;\n\n      return typeof children === 'function'\n        ? children(\n            isAnimating,\n            this._startAnimation,\n            this._pauseAnimation,\n            this._resetAnimation,\n            this.props.timeline,\n            this.props.setTimelineValue\n          )\n        : null;\n    }\n  }\n\n  return AnimationController;\n}\n\nexport default AnimationControllerFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/animation-speed-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\nimport {useDismiss, useFloating, useInteractions} from '@floating-ui/react';\n\nimport {SPEED_CONTROL_RANGE, SPEED_CONTROL_STEP} from '@kepler.gl/constants';\n\nimport RangeSliderFactory from '../range-slider';\n\nconst SliderWrapper = styled.div`\n  position: relative;\n`;\n\nconst SpeedSliderContainer = styled.div`\n  position: absolute;\n  bottom: 50px;\n  right: calc(0% - 32px);\n  width: 180px;\n  padding: 2px 8px 2px 12px;\n  background-color: ${props => props.theme.bottomWidgetBgd};\n  box-shadow: -2px -2px 0 0 rgba(0, 0, 0, 0.1);\n\n  .kg-range-slider__input {\n    width: 48px;\n    padding: 6px;\n  }\n`;\n\nAnimationSpeedSliderFactory.deps = [RangeSliderFactory];\n\ninterface AnimationSpeedSliderProps {\n  onHide: () => void;\n  speed: number;\n  updateAnimationSpeed: (val: number) => void;\n}\n\nexport default function AnimationSpeedSliderFactory(\n  RangeSlider: ReturnType<typeof RangeSliderFactory>\n): React.FC<AnimationSpeedSliderProps> {\n  const AnimationSpeedSlider: React.FC<AnimationSpeedSliderProps> = ({\n    onHide,\n    speed,\n    updateAnimationSpeed\n  }: AnimationSpeedSliderProps) => {\n    // floating-ui boilerplate to establish close on outside click\n    const {refs, context} = useFloating({\n      open: true,\n      onOpenChange: v => {\n        if (!v) {\n          onHide();\n        }\n      }\n    });\n    const dismiss = useDismiss(context);\n    const {getFloatingProps} = useInteractions([dismiss]);\n\n    const onChange = useCallback(v => updateAnimationSpeed(v[1]), [updateAnimationSpeed]);\n\n    return (\n      <SpeedSliderContainer\n        className=\"animation-control__speed-slider\"\n        ref={refs.setFloating}\n        {...getFloatingProps()}\n      >\n        <SliderWrapper>\n          <RangeSlider\n            range={SPEED_CONTROL_RANGE}\n            step={SPEED_CONTROL_STEP}\n            value0={0}\n            value1={speed}\n            onChange={onChange}\n            isRanged={false}\n            showInput\n            inputTheme=\"secondary\"\n            inputSize=\"tiny\"\n          />\n        </SliderWrapper>\n      </SpeedSliderContainer>\n    );\n  };\n  return AnimationSpeedSlider;\n}\n"
  },
  {
    "path": "src/components/src/common/animation-control/animation-window-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ReactComponentLike} from 'prop-types';\nimport IconButton from '../icon-button';\nimport {Tooltip} from '../styled-components';\n\nexport interface AnimationItem {\n  id: string;\n  icon: ReactComponentLike;\n  tooltip: string;\n}\ninterface AnimationWindowControlProps {\n  animationWindow?: string;\n  setFilterAnimationWindow: (id: string) => void;\n  toggleAnimationWindowControl: () => void;\n  height?: string;\n  animationItems: {[key: string]: AnimationItem};\n  btnStyle;\n  showAnimationWindowControl: boolean;\n}\n\nfunction AnimationWindowControlFactory(): React.FC<AnimationWindowControlProps> {\n  const AnimationWindowControl = ({\n    animationWindow,\n    setFilterAnimationWindow,\n    toggleAnimationWindowControl,\n    height,\n    animationItems,\n    btnStyle = {},\n    showAnimationWindowControl\n  }: AnimationWindowControlProps) => {\n    const onSelectAnimationControl = useCallback(\n      item => {\n        setFilterAnimationWindow(item.id);\n        toggleAnimationWindowControl();\n      },\n      [setFilterAnimationWindow, toggleAnimationWindowControl]\n    );\n\n    return showAnimationWindowControl ? (\n      <div className=\"animation-window-control\">\n        {Object.values(animationItems)\n          .filter(item => item.id !== animationWindow)\n          .map(item => (\n            <IconButton\n              key={item.id}\n              data-tip\n              data-for={`${item.id}-tooltip`}\n              className=\"playback-control-button\"\n              onClick={() => onSelectAnimationControl(item)}\n              {...btnStyle}\n            >\n              <item.icon height={height} />\n              {item.tooltip ? (\n                <Tooltip id={`${item.id}-tooltip`} effect=\"solid\" place=\"top\">\n                  <FormattedMessage id={item.tooltip} />\n                </Tooltip>\n              ) : null}\n            </IconButton>\n          ))}\n      </div>\n    ) : null;\n  };\n\n  return AnimationWindowControl;\n}\n\nexport default AnimationWindowControlFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/floating-time-display.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport {Minus} from '../icons';\nimport {DEFAULT_TIME_FORMAT} from '@kepler.gl/constants';\nimport {CenterFlexbox} from '../../common/styled-components';\nimport {datetimeFormatter} from '@kepler.gl/utils';\n\nconst StyledTimeDisplayWrapper = styled.div.attrs({\n  className: 'floating-time-display'\n})`\n  bottom: ${props => `calc(100% + ${props.theme.bottomPanelGap}px)`};\n  display: flex;\n  position: absolute;\n  width: 100%;\n  margin-left: -${props => props.theme.bottomInnerPdSide}px;\n  justify-content: center;\n  pointer-events: none; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\nconst StyledTimeDisplay = styled.div.attrs(props => ({\n  className: classnames('floating-time-display__inner', props.className)\n}))`\n  background-color: ${props => props.theme.panelBackground};\n  border-radius: ${props => props.theme.timeDisplayBorderRadius}px;\n  color: ${props => props.theme.titleTextColor};\n  display: flex;\n  height: ${props => props.theme.timeDisplayHeight}px;\n  justify-content: center;\n  min-width: ${props => props.theme.timeDisplayMinWidth}px;\n  opacity: ${props => props.theme.timeDisplayOpacity};\n  padding: ${props => props.theme.timeDisplayPadding};\n`;\n\nconst StyledTimeDisplayGroups = styled.div`\n  align-items: center;\n  display: flex;\n  flex-direction: row;\n`;\n\nconst StyledTimeDisplayRows = styled.div`\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n`;\n\nconst StyledTimeDisplayTop = styled.div.attrs({\n  className: 'animation-control__time-display__top'\n})`\n  color: ${props => props.theme.textColor};\n  display: flex;\n  font-size: 12px;\n  font-weight: 500;\n  justify-content: center;\n`;\n\nconst StyledTimeDisplayBottom = styled.div.attrs({\n  className: 'animation-control__time-display__bottom'\n})`\n  color: ${props => props.theme.titleTextColor};\n  display: flex;\n  font-size: 14px;\n  font-weight: 500;\n  justify-content: center;\n`;\n\nconst StyledTimeValueGroup = styled.div.attrs({\n  className: 'animation-control__time-value-group'\n})`\n  display: flex;\n  flex-direction: column;\n`;\n\nconst StyledHorizontalBar = styled.div.attrs({\n  className: 'animation-control__horizontal-bar'\n})`\n  margin: 0 12px;\n`;\n\nconst TimeDivider = () => (\n  <StyledHorizontalBar>\n    <Minus height=\"12px\" />\n  </StyledHorizontalBar>\n);\n\ninterface TimeDisplayRowProps {\n  timeValues?: string[];\n}\n\nconst TimeDisplayRow = ({timeValues = []}: TimeDisplayRowProps) => (\n  <CenterFlexbox>\n    <div className=\"time-value\">{timeValues[0]}</div>\n    {timeValues[1] ? <TimeDivider /> : null}\n    {timeValues[1] ? <div className=\"time-value\">{timeValues[1]}</div> : null}\n  </CenterFlexbox>\n);\n\ninterface FloatingTimeDisplayProps {\n  currentTime: number | number[];\n  defaultTimeFormat?: string | null;\n  timeFormat?: string | null;\n  timezone?: string | null;\n}\n\nexport default function FloatingTimeDisplayFactory() {\n  const FloatingTimeDisplay = ({\n    currentTime,\n    defaultTimeFormat,\n    timeFormat,\n    timezone\n  }: FloatingTimeDisplayProps) => {\n    const {displayDate, displayTime} = useMemo(() => {\n      const groupTime = Array.isArray(currentTime) ? currentTime : [currentTime];\n      const hasUserFormat = typeof timeFormat === 'string';\n      const currentFormat = (hasUserFormat ? timeFormat : defaultTimeFormat) || DEFAULT_TIME_FORMAT;\n      const dateFunc = datetimeFormatter(timezone);\n\n      if (hasUserFormat) {\n        // dont split time if user defined it\n        return {\n          displayDate: groupTime.map(dateFunc(currentFormat)),\n          displayTime: []\n        };\n      }\n      return groupTime.reduce<{displayDate: string[]; displayTime: string[]}>(\n        (accu, curr) => {\n          const [dateFormat, datetimeFormat] = currentFormat.split(' ');\n          const dateString = dateFunc(dateFormat)(curr);\n          const timeString = datetimeFormat ? dateFunc(datetimeFormat)(curr) : null;\n\n          if (!accu.displayDate.includes(dateString)) {\n            accu.displayDate.push(dateString);\n          }\n          if (timeString) {\n            accu.displayTime.push(timeString);\n          }\n\n          return accu;\n        },\n        {displayDate: [], displayTime: []} as {displayDate: string[]; displayTime: string[]}\n      );\n    }, [currentTime, timeFormat, defaultTimeFormat, timezone]);\n\n    const twoGroups = displayDate.length === 2 && displayTime.length === 2;\n    const bottomRow = displayTime.length ? displayTime : displayDate.length ? displayDate : null;\n    const topRow = displayDate.length && displayTime.length ? displayDate : null;\n\n    return (\n      <StyledTimeDisplayWrapper>\n        <StyledTimeDisplay className=\"animation-control__time-display\">\n          {twoGroups ? (\n            <StyledTimeDisplayGroups>\n              <StyledTimeValueGroup>\n                {/* Time Start */}\n                <StyledTimeDisplayTop>{displayDate[0]}</StyledTimeDisplayTop>\n                <StyledTimeDisplayBottom>{displayTime[0]}</StyledTimeDisplayBottom>\n              </StyledTimeValueGroup>\n              <TimeDivider />\n              <StyledTimeValueGroup>\n                {/* Time End */}\n                <StyledTimeDisplayTop>{displayDate[1]}</StyledTimeDisplayTop>\n                <StyledTimeDisplayBottom>{displayTime[1]}</StyledTimeDisplayBottom>\n              </StyledTimeValueGroup>\n            </StyledTimeDisplayGroups>\n          ) : (\n            <StyledTimeDisplayRows>\n              {topRow ? (\n                <StyledTimeDisplayTop>\n                  <TimeDisplayRow timeValues={topRow} />\n                </StyledTimeDisplayTop>\n              ) : null}\n              {bottomRow ? (\n                <StyledTimeDisplayBottom>\n                  <TimeDisplayRow timeValues={bottomRow} />\n                </StyledTimeDisplayBottom>\n              ) : null}\n            </StyledTimeDisplayRows>\n          )}\n        </StyledTimeDisplay>\n      </StyledTimeDisplayWrapper>\n    );\n  };\n\n  return FloatingTimeDisplay;\n}\n"
  },
  {
    "path": "src/components/src/common/animation-control/play-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport classnames from 'classnames';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport IconButton from '../icon-button';\nimport TippyTooltip from '../tippy-tooltip';\n\nfunction PlayControlFactory() {\n  const PlayControl = ({\n    showAnimationWindowControl,\n    isAnimating,\n    pauseAnimation,\n    startAnimation,\n    isSpeedControlVisible,\n    btnStyle,\n    playbackIcons,\n    buttonHeight\n  }) => {\n    return showAnimationWindowControl ? null : (\n      <TippyTooltip\n        placement=\"top\"\n        delay={[500, 0]}\n        render={() => <FormattedMessage id={isAnimating ? 'tooltip.pause' : 'tooltip.play'} />}\n      >\n        <IconButton\n          className={classnames('playback-control-button', {active: isAnimating})}\n          onClick={isAnimating ? pauseAnimation : startAnimation}\n          hide={isSpeedControlVisible}\n          {...btnStyle}\n        >\n          {isAnimating ? (\n            <playbackIcons.pause height={buttonHeight} />\n          ) : (\n            <playbackIcons.play height={buttonHeight} />\n          )}\n        </IconButton>\n      </TippyTooltip>\n    );\n  };\n\n  return PlayControl;\n}\n\nexport default PlayControlFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/playback-controls.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, useCallback} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport classnames from 'classnames';\nimport {Reset, Play, Pause, Save, Speed, AnchorWindow, FreeWindow} from '../icons';\nimport {ANIMATION_WINDOW} from '@kepler.gl/constants';\nimport {Filter, TimeRangeFilter} from '@kepler.gl/types';\nimport AnimationSpeedSliderFactory from './animation-speed-slider';\nimport WindowActionControlFactory from './window-action-control';\nimport AnimationWindowControlFactory, {AnimationItem} from './animation-window-control';\nimport ResetControlFactory from './reset-control';\nimport PlayControlFactory from './play-control';\nimport SpeedControlFactory from './speed-control';\nimport {BaseComponentProps} from '../../types';\n\nconst DEFAULT_BUTTON_HEIGHT = '20px';\n\nexport type StyledAnimationControlsProps = BaseComponentProps & {\n  width?: number;\n};\n\nconst StyledAnimationControls: IStyledComponent<\n  'web',\n  StyledAnimationControlsProps\n> = styled.div<StyledAnimationControlsProps>`\n  display: flex;\n  position: relative;\n  width: ${props => props.width}px;\n  &.disabled {\n    opacity: 0.4;\n    pointer-events: none;\n  }\n`;\n\nconst DEFAULT_ICONS = {\n  /* eslint-disable react/display-name */\n  reset: () => <Reset height=\"16px\" />,\n  play: () => <Play height=\"16px\" />,\n  pause: () => <Pause height=\"16px\" />,\n  export: () => <Save height=\"16px\" />,\n  /* eslint-enable react/display-name */\n  speed: () => <Speed height=\"16px\" />,\n  animationFree: FreeWindow,\n  animationIncremental: AnchorWindow\n};\n\nfunction nop() {\n  return;\n}\n\nconst DEFAULT_ANIMATE_ITEMS = {\n  [ANIMATION_WINDOW.free]: {\n    id: ANIMATION_WINDOW.free,\n    icon: DEFAULT_ICONS.animationFree,\n    tooltip: 'tooltip.animationByWindow'\n  },\n  [ANIMATION_WINDOW.incremental]: {\n    id: ANIMATION_WINDOW.incremental,\n    icon: DEFAULT_ICONS.animationIncremental,\n    tooltip: 'tooltip.animationByIncremental'\n  }\n};\nexport interface PlaybackControlsProps {\n  filter?: Filter;\n  isAnimatable?: boolean;\n  isAnimating?: boolean;\n  width?: number;\n  speed: number;\n  animationWindow?: null | TimeRangeFilter['animationWindow'];\n  setFilterAnimationWindow?: (id: string) => void;\n  updateAnimationSpeed?: (idx: number, speed: number) => void;\n  pauseAnimation?: () => void;\n  resetAnimation?: () => void;\n  startAnimation: () => void;\n  playbackIcons?: Record<string, React.FC<{height: number}>>;\n  animationItems?: {[key: string]: AnimationItem};\n  buttonStyle?: string;\n  buttonHeight?: string;\n  playbackActionItems?: React.FC[];\n  className?: string;\n}\n\nPlaybackControlsFactory.deps = [\n  // keeping this for backwards compatibility but we can decide to drop it later\n  AnimationSpeedSliderFactory,\n  WindowActionControlFactory,\n  AnimationWindowControlFactory,\n  ResetControlFactory,\n  PlayControlFactory\n];\n\nfunction PlaybackControlsFactory(\n  AnimationSpeedSlider: ReturnType<typeof AnimationSpeedSliderFactory>,\n  WindowActionControl,\n  AnimationWindowControl,\n  ResetControl,\n  PlayControl\n) {\n  const PLAYBACK_CONTROLS_DEFAULT_ACTION_COMPONENTS = [\n    PlayControl,\n    SpeedControlFactory(AnimationSpeedSlider),\n    ResetControl,\n    WindowActionControl,\n    AnimationWindowControl\n  ];\n\n  // eslint-disable-next-line complexity\n  const PlaybackControls: React.FC<PlaybackControlsProps> = ({\n    filter,\n    isAnimatable = true,\n    isAnimating,\n    width,\n    speed,\n    animationWindow = ANIMATION_WINDOW.free,\n    setFilterAnimationWindow,\n    updateAnimationSpeed,\n    pauseAnimation = nop,\n    resetAnimation = nop,\n    startAnimation = nop,\n    playbackIcons = DEFAULT_ICONS,\n    animationItems = DEFAULT_ANIMATE_ITEMS,\n    buttonStyle = 'secondary',\n    buttonHeight = DEFAULT_BUTTON_HEIGHT,\n    playbackActionItems = PLAYBACK_CONTROLS_DEFAULT_ACTION_COMPONENTS\n  }) => {\n    const [isSpeedControlVisible, toggleSpeedControl] = useState(false);\n    const [showAnimationWindowControl, setShowAnimationWindowControl] = useState(false);\n\n    const toggleAnimationWindowControl = useCallback(() => {\n      setShowAnimationWindowControl(!showAnimationWindowControl);\n    }, [showAnimationWindowControl, setShowAnimationWindowControl]);\n    const btnStyle = buttonStyle ? {[buttonStyle]: true} : {};\n\n    const hideAndShowSpeedControl = useCallback(() => {\n      if (!isSpeedControlVisible) {\n        toggleSpeedControl(true);\n      } else {\n        // TODO: A HACK to allow input onblur get triggered before the input is unmounted\n        // A better solution should be invested, see https://github.com/facebook/react/issues/12363\n        window.setTimeout(() => toggleSpeedControl(false), 200);\n      }\n    }, [isSpeedControlVisible, toggleSpeedControl]);\n\n    return (\n      <StyledAnimationControls\n        className={classnames('playback-controls', {\n          disabled: !isAnimatable\n        })}\n        width={width}\n      >\n        {/** Window */}\n        {playbackActionItems.map((ActionComponent, index) => (\n          <ActionComponent\n            key={index}\n            toggleAnimationWindowControl={toggleAnimationWindowControl}\n            showAnimationWindowControl={showAnimationWindowControl}\n            btnStyle={btnStyle}\n            hideAndShowSpeedControl={hideAndShowSpeedControl}\n            animationItems={animationItems}\n            animationWindow={animationWindow}\n            buttonHeight={buttonHeight}\n            filter={filter}\n            setFilterAnimationWindow={setFilterAnimationWindow}\n            updateAnimationSpeed={updateAnimationSpeed}\n            isAnimating={isAnimating}\n            pauseAnimation={pauseAnimation}\n            resetAnimation={resetAnimation}\n            startAnimation={startAnimation}\n            playbackIcons={playbackIcons}\n            isSpeedControlVisible={isSpeedControlVisible}\n            speed={speed}\n          />\n        ))}\n      </StyledAnimationControls>\n    );\n  };\n\n  return PlaybackControls;\n}\n\nexport default PlaybackControlsFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/reset-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport IconButton from '../icon-button';\nimport TippyTooltip from '../tippy-tooltip';\n\nfunction ResetControlFactory() {\n  const ResetControl = ({\n    showAnimationWindowControl,\n    resetAnimation,\n    btnStyle,\n    playbackIcons,\n    buttonHeight\n  }) => {\n    return showAnimationWindowControl ? null : (\n      <TippyTooltip\n        placement=\"top\"\n        delay={[500, 0]}\n        render={() => <FormattedMessage id=\"tooltip.reset\" />}\n      >\n        <IconButton className=\"playback-control-button\" onClick={resetAnimation} {...btnStyle}>\n          <playbackIcons.reset height={buttonHeight} />\n        </IconButton>\n      </TippyTooltip>\n    );\n  };\n\n  return ResetControl;\n}\n\nexport default ResetControlFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/speed-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport IconButton from '../icon-button';\nimport {media} from '@kepler.gl/styles';\nimport {preciseRound} from '@kepler.gl/utils';\nimport TippyTooltip from '../tippy-tooltip';\n\nconst StyledSpeedControl = styled.div`\n  display: flex;\n  align-items: center;\n\n  .animation-control__speed-slider {\n    left: 0;\n    ${media.palm`\n      left: 60px;\n    `}\n  }\n`;\n\nconst PRECISE_SPEED_ROUND = 1;\n\nfunction SpeedControlFactory(AnimationSpeedSlider) {\n  const SpeedControl = ({\n    showAnimationWindowControl,\n    updateAnimationSpeed,\n    btnStyle,\n    hideAndShowSpeedControl,\n    buttonHeight,\n    playbackIcons,\n    speed,\n    isSpeedControlVisible\n  }) => {\n    return showAnimationWindowControl || !updateAnimationSpeed ? null : (\n      <StyledSpeedControl>\n        <TippyTooltip\n          placement=\"top\"\n          delay={[500, 0]}\n          render={() => <span>{preciseRound(speed, PRECISE_SPEED_ROUND)}x</span>}\n        >\n          <IconButton\n            className=\"playback-control-button\"\n            {...btnStyle}\n            onClick={hideAndShowSpeedControl}\n          >\n            <playbackIcons.speed height={buttonHeight} />\n          </IconButton>\n        </TippyTooltip>\n        {isSpeedControlVisible ? (\n          <AnimationSpeedSlider\n            onHide={hideAndShowSpeedControl}\n            updateAnimationSpeed={updateAnimationSpeed}\n            speed={speed}\n          />\n        ) : null}\n      </StyledSpeedControl>\n    );\n  };\n\n  return SpeedControl;\n}\n\nexport default SpeedControlFactory;\n"
  },
  {
    "path": "src/components/src/common/animation-control/window-action-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport classnames from 'classnames';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Tooltip} from '../styled-components';\nimport IconButton from '../icon-button';\n\nconst DELAY_SHOW = 500;\nfunction WindowActionControlFactory() {\n  const WindowActionComponent = ({\n    toggleAnimationWindowControl,\n    showAnimationWindowControl,\n    btnStyle,\n    animationItems,\n    animationWindow,\n    buttonHeight,\n    setFilterAnimationWindow\n  }) => {\n    const icon = useMemo(() => {\n      if (animationItems[animationWindow]) {\n        const WindowIcon = animationItems[animationWindow].icon;\n        return <WindowIcon height={buttonHeight} />;\n      }\n      return null;\n    }, [animationItems, animationWindow, buttonHeight]);\n\n    return setFilterAnimationWindow ? (\n      <IconButton\n        data-tip\n        data-for=\"animate-window\"\n        className={classnames('playback-control-button', {active: showAnimationWindowControl})}\n        onClick={toggleAnimationWindowControl}\n        {...btnStyle}\n      >\n        {icon}\n        {animationItems[animationWindow] && animationItems[animationWindow].tooltip ? (\n          <Tooltip id=\"animate-window\" place=\"top\" delayShow={DELAY_SHOW} effect=\"solid\">\n            <FormattedMessage id={animationItems[animationWindow].tooltip} />\n          </Tooltip>\n        ) : null}\n      </IconButton>\n    ) : null;\n  };\n\n  return WindowActionComponent;\n}\n\nexport default WindowActionControlFactory;\n"
  },
  {
    "path": "src/components/src/common/checkbox.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport pick from 'lodash/pick';\nimport React, {ChangeEventHandler, Component, FocusEventHandler, ReactNode} from 'react';\nimport styled from 'styled-components';\nimport {shouldForwardProp} from './styled-components';\n\nfunction noop() {\n  return;\n}\n\ninterface StyledSwitchInputProps {\n  secondary?: boolean;\n}\n\nconst StyledSwitchInput = styled.label.withConfig({shouldForwardProp})<StyledSwitchInputProps>`\n  ${props => (props.secondary ? props.theme.secondarySwitch : props.theme.inputSwitch)};\n`;\n\nconst StyledCheckboxInput = styled.label`\n  ${props => props.theme.inputCheckbox};\n`;\n\nconst StyledRadiuInput = styled.label.withConfig({shouldForwardProp})<StyledSwitchInputProps>`\n  ${props => (props.secondary ? props.theme.secondaryRadio : props.theme.inputRadio)};\n`;\n\nconst HiddenInput = styled.input.withConfig({shouldForwardProp})`\n  position: absolute;\n  opacity: 0;\n`;\n\ninterface StyledCheckboxProps {\n  type?: string;\n  disabled?: boolean;\n}\n\nconst StyledCheckbox = styled.div.withConfig({shouldForwardProp})<StyledCheckboxProps>`\n  display: flex;\n  min-height: ${props => props.theme.switchHeight}px;\n  margin-left: ${props => (props.type === 'radio' ? 0 : props.theme.switchLabelMargin)}px;\n  ${props =>\n    props.disabled\n      ? `\n    cursor: not-allowed;\n    pointer-events: none;\n    opacity: 0.5;\n  `\n      : ''}\n`;\n\ninterface CheckboxProps {\n  id: string;\n  type?: string;\n  label?: ReactNode;\n  name?: string;\n  className?: string;\n  value?: string | 'indeterminate';\n  checked?: boolean;\n  disabled?: boolean;\n\n  error?: string;\n  switch?: boolean;\n  activeColor?: string;\n  secondary?: boolean;\n  onBlur: FocusEventHandler<HTMLInputElement>;\n  onChange?: ChangeEventHandler<HTMLInputElement>;\n  onFocus: FocusEventHandler<HTMLInputElement>;\n}\n\nexport default class Checkbox extends Component<CheckboxProps> {\n  static defaultProps = {\n    disabled: false,\n    checked: false,\n    onBlur: noop,\n    onChange: noop,\n    onFocus: noop,\n    label: ''\n  };\n\n  state = {\n    focused: false\n  };\n\n  handleFocus: FocusEventHandler<HTMLInputElement> = args => {\n    this.setState({focused: true});\n    this.props.onFocus(args);\n  };\n\n  handleBlur: FocusEventHandler<HTMLInputElement> = args => {\n    this.setState({focused: false});\n    this.props.onBlur(args);\n  };\n\n  render() {\n    const inputProps = {\n      ...pick(this.props, ['checked', 'disabled', 'id', 'onChange', 'value', 'secondary']),\n      type: 'checkbox',\n      onFocus: this.handleFocus,\n      onBlur: this.handleBlur\n    };\n\n    const labelProps = {\n      ...pick(this.props, ['checked', 'disabled', 'secondary']),\n      htmlFor: this.props.id\n    };\n\n    const LabelElement =\n      this.props.type === 'checkbox'\n        ? StyledCheckboxInput\n        : this.props.type === 'radio'\n        ? StyledRadiuInput\n        : StyledSwitchInput;\n\n    return (\n      <StyledCheckbox\n        type={this.props.type}\n        className={classnames('kg-checkbox', this.props.className)}\n        disabled={this.props.disabled}\n      >\n        <HiddenInput {...inputProps} />\n        <LabelElement className=\"kg-checkbox__label\" {...labelProps}>\n          {this.props.label}\n        </LabelElement>\n      </StyledCheckbox>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/color-legend.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled, {css} from 'styled-components';\n\nimport {SCALE_TYPES} from '@kepler.gl/constants';\nimport {Layer} from '@kepler.gl/layers';\nimport {ColorRange, HexColor, MapState} from '@kepler.gl/types';\nimport {\n  getLayerColorScale,\n  getLegendOfScale,\n  getVisualChannelScaleByZoom,\n  colorMapToCategoricalColorBreaks,\n  isObject\n} from '@kepler.gl/utils';\n\nimport {Reset} from './icons';\nimport {InlineInput} from './styled-components';\n\nconst ROW_H = 15;\nconst GAP = 2;\nconst RECT_W = 20;\n\nconst stopClickPropagation = e => e.stopPropagation();\n\nconst inputCss = css`\n  input {\n    pointer-events: none;\n  }\n`;\nconst StyledLegend = styled.div<{disableEdit: boolean; isExpanded?: boolean}>`\n  ${props => props.theme.sidePanelScrollBar};\n  ${props => (props.isExpanded ? '' : `max-height: 156px;`)};\n  overflow-y: auto;\n  overflow-x: hidden;\n  margin-bottom: ${GAP}px;\n  display: grid;\n  grid-row-gap: ${GAP}px;\n  padding: 2px 0;\n\n  ${props => (props.disableEdit ? inputCss : '')}\n`;\n\nconst StyledLegendRow = styled.div`\n  display: flex;\n  align-items: center;\n  height: 20px;\n  min-width: 0;\n`;\n\nexport function ResetColorLabelFactory() {\n  return styled(Reset)`\n    color: ${props => props.theme.labelColorLT};\n    cursor: pointer;\n    flex-shrink: 0;\n    margin-left: ${GAP}px;\n\n    &:hover {\n      color: ${props => props.theme.panelHeaderIconHover};\n    }\n  `;\n}\n\nconst StyleInlineInput = styled(InlineInput)`\n  font-size: 9.5px;\n  line-height: ${ROW_H}px;\n  height: ${ROW_H}px;\n  color: ${props => props.theme.textColor};\n  width: unset;\n  padding: 2px;\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  margin: 0 ${GAP}px;\n  :hover {\n    height: ${ROW_H}px;\n  }\n`;\n\nexport type LegendRowEditorProps = {\n  color: string;\n  label: string;\n  customLabel?: string;\n  onEdit: (newValue: string) => void;\n  disabled?: boolean;\n};\nexport function LegendRowEditorFactory() {\n  const LegendRowEditor: React.FC<LegendRowEditorProps> = ({\n    color,\n    label,\n    onEdit,\n    disabled = false\n  }) => {\n    const onChange = useCallback(event => onEdit(event.target.value), [onEdit]);\n    return (\n      <StyleInlineInput\n        type=\"text\"\n        className=\"legend__label__title__editor\"\n        value={label}\n        onClick={stopClickPropagation}\n        onChange={onChange}\n        disabled={disabled}\n        id={`${color}:input-legend-label`}\n      />\n    );\n  };\n\n  LegendRowEditor.displayName = 'LegendRowEditor';\n  return LegendRowEditor;\n}\n\nconst LegendRowStyle = {\n  width: `${RECT_W}px`,\n  height: `${ROW_H}px`\n};\n\nexport function LegendColorDisplayFactory() {\n  const LegendColorDisplay = ({color}) => {\n    const style = useMemo(\n      () => ({...LegendRowStyle, backgroundColor: color, marginRight: `${GAP}px`}),\n      [color]\n    );\n    return <div style={style} className=\"legend-row-color\" />;\n  };\n\n  return LegendColorDisplay;\n}\n\nconst StyledLabel = styled.div`\n  font-size: 10px;\n  color: ${props => props.theme.textColor};\n  padding-left: 2px;\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n`;\n\nexport type LegendRowProps = {\n  label: string;\n  customLabel?: string;\n  displayLabel?: boolean;\n  color: string;\n  onUpdateLabel?: (selectedColor: string, newLabel: string) => void;\n  onResetLabel?: (color: string) => void;\n  disableEdit?: boolean;\n};\n\nLegendRowFactory.deps = [LegendColorDisplayFactory, LegendRowEditorFactory, ResetColorLabelFactory];\nexport function LegendRowFactory(\n  LegendColorDisplay: ReturnType<typeof LegendColorDisplayFactory>,\n  LegendRowEditor: ReturnType<typeof LegendRowEditorFactory>,\n  ResetColorLabel: ReturnType<typeof ResetColorLabelFactory>\n) {\n  const LegendRow: React.FC<LegendRowProps> = ({\n    label = '',\n    displayLabel,\n    color,\n    onUpdateLabel,\n    onResetLabel\n  }) => {\n    const onEdit = useCallback(\n      newLabel => onUpdateLabel && onUpdateLabel(color, newLabel),\n      [color, onUpdateLabel]\n    );\n    const onReset = useCallback(() => onResetLabel && onResetLabel(color), [color, onResetLabel]);\n    const value = displayLabel ? String(label ?? '') : '';\n    return (\n      <StyledLegendRow>\n        <LegendColorDisplay color={color} />\n        {onUpdateLabel ? (\n          <LegendRowEditor disabled={!onUpdateLabel} label={value} color={color} onEdit={onEdit} />\n        ) : (\n          <StyledLabel>{value}</StyledLabel>\n        )}\n        {onResetLabel ? <ResetColorLabel onClick={onReset} height=\"16px\" /> : null}\n      </StyledLegendRow>\n    );\n  };\n  LegendRow.displayName = 'LegendRow';\n  return LegendRow;\n}\n\nconst overrideColorLegends = (colorLegends, overrides) => {\n  const {data, labels} = overrides;\n\n  const newColorLegends = [...colorLegends];\n\n  data.forEach((datum, index) => {\n    const currentIndex = colorLegends.findIndex(d => d.data === datum);\n    if (currentIndex !== -1) {\n      newColorLegends[currentIndex] = {\n        label: labels[index],\n        data: datum,\n        override: true\n      };\n      newColorLegends[currentIndex].label = labels[index];\n    } else {\n      newColorLegends.push({\n        data: datum,\n        label: labels[index],\n        override: true\n      });\n    }\n  });\n\n  return newColorLegends;\n};\n\ntype OverrideByCustomLegendOptions = {\n  /**\n   * Legend parameters to override\n   */\n  colorLegends?: Record<string, any>;\n  /**\n   * Original Legends\n   */\n  currentLegends?: ReturnType<typeof getLegendOfScale>;\n};\n\n/**\n * Overrides legend labels with color legends.\n * @param param0 Legend info and override parameters.\n * @returns Original or overriden lenends.\n */\nfunction overrideByCustomLegend({colorLegends, currentLegends}: OverrideByCustomLegendOptions) {\n  if (colorLegends && isObject(colorLegends)) {\n    // override labels with color legends\n    const data = Object.keys(colorLegends);\n    const labels = Object.values(colorLegends);\n\n    return overrideColorLegends(currentLegends, {data, labels});\n  }\n\n  return currentLegends;\n}\n\nexport function useLayerColorLegends(\n  layer: ColorLegendProps['layer'],\n  scaleType: ColorLegendProps['scaleType'],\n  domain: ColorLegendProps['domain'],\n  range: ColorLegendProps['range'],\n  isFixed: ColorLegendProps['isFixed'],\n  fieldType: ColorLegendProps['fieldType'],\n  labelFormat: ColorLegendProps['labelFormat'],\n  mapState: ColorLegendProps['mapState']\n): Legend[] {\n  const scale = useMemo(\n    () => getLayerColorScale({range, domain, scaleType, isFixed, layer}),\n    [range, domain, scaleType, isFixed, layer]\n  );\n\n  const scaleByZoom = useMemo(\n    () => getVisualChannelScaleByZoom({scale, layer, mapState}),\n    [scale, layer, mapState]\n  );\n\n  const currentLegends = useMemo(() => {\n    if (scaleType === SCALE_TYPES.customOrdinal && range?.colorMap) {\n      const colorBreaks = colorMapToCategoricalColorBreaks(range.colorMap);\n      return colorBreaks?.map(cb => {\n        return {\n          data: cb.data,\n          label: Array.isArray(cb.label)\n            ? cb.label.length > 5\n              ? `${cb.label.length} selected`\n              : cb.label\n            : cb.label || ''\n        };\n      });\n    }\n    return getLegendOfScale({scale: scaleByZoom, scaleType, labelFormat, fieldType});\n  }, [range, scaleByZoom, scaleType, labelFormat, fieldType]);\n\n  const LegendsWithCustomLegends = useMemo(\n    () =>\n      overrideByCustomLegend({\n        colorLegends: range?.colorLegends,\n        currentLegends\n      }),\n    [range?.colorLegends, currentLegends]\n  );\n\n  return LegendsWithCustomLegends || [];\n}\n\nexport type ColorLegendProps = {\n  layer: Layer;\n  scaleType: string;\n  domain: number[] | string[];\n  fieldType?: string | null;\n  range?: ColorRange | null;\n  labelFormat?: (n: any) => string;\n  displayLabel?: boolean;\n  disableEdit?: boolean;\n  mapState?: MapState;\n  isFixed?: boolean;\n  isExpanded?: boolean;\n  onUpdateColorLegend?: (colorLegends: {[key: HexColor]: string}) => void;\n};\n\nexport type Legend = {\n  data: string;\n  label: string;\n  override?: boolean;\n};\n\nColorLegendFactory.deps = [LegendRowFactory];\nfunction ColorLegendFactory(LegendRow: ReturnType<typeof LegendRowFactory>) {\n  const ColorLegend: React.FC<ColorLegendProps> = ({\n    layer,\n    isFixed,\n    isExpanded,\n    domain,\n    range,\n    labelFormat,\n    scaleType,\n    fieldType,\n    mapState,\n    onUpdateColorLegend,\n    displayLabel = true,\n    disableEdit = false\n  }) => {\n    const {colorLegends} = range || {};\n\n    const legends = useLayerColorLegends(\n      layer,\n      scaleType,\n      domain,\n      range,\n      isFixed,\n      fieldType,\n      labelFormat,\n      mapState\n    );\n\n    const onUpdateLabel = useCallback(\n      (color, newValue) => {\n        if (onUpdateColorLegend) {\n          onUpdateColorLegend({\n            ...colorLegends,\n            [color]: newValue\n          });\n        }\n      },\n      [onUpdateColorLegend, colorLegends]\n    );\n\n    const onResetLabel = useCallback(\n      color => {\n        /* eslint-disable no-unused-vars */\n        // @ts-ignore\n        const {[color]: _, ...rest} = colorLegends;\n        if (onUpdateColorLegend && rest) {\n          onUpdateColorLegend(rest);\n        }\n        /* eslint-enable no-unused-vars */\n      },\n      [onUpdateColorLegend, colorLegends]\n    );\n\n    return (\n      <StyledLegend\n        className=\"styled-color-legend\"\n        disableEdit={disableEdit}\n        isExpanded={isExpanded}\n      >\n        {legends.map((legend, i) => (\n          <LegendRow\n            key={`${legend.data}-${i}`}\n            label={legend.label}\n            displayLabel={displayLabel}\n            color={legend.data}\n            onUpdateLabel={!disableEdit ? onUpdateLabel : undefined}\n            onResetLabel={legend.override && !disableEdit ? onResetLabel : undefined}\n          />\n        ))}\n      </StyledLegend>\n    );\n  };\n\n  return React.memo(ColorLegend);\n}\n\nexport default ColorLegendFactory;\n"
  },
  {
    "path": "src/components/src/common/column-stats-chart.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {format as d3Format} from 'd3-format';\nimport {scaleLinear} from 'd3-scale';\nimport React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport styled from 'styled-components';\n\nimport {KeplerTable} from '@kepler.gl/table';\nimport {Bin, Field} from '@kepler.gl/types';\nimport {\n  ColorBreak,\n  ColorBreakOrdinal,\n  isNumber,\n  isNumericColorBreaks,\n  useDimensions\n} from '@kepler.gl/utils';\n\nimport ColorPalette from '../side-panel/layer-panel/color-palette';\nimport HistogramPlotFactory, {HISTOGRAM_MASK_MODE} from './histogram-plot';\nimport LoadingSpinner from './loading-spinner';\n\nexport const HISTOGRAM_WIDTH = 210;\nexport const HISTOGRAM_HEIGHT = 80;\nconst HISTOGRAM_MARGIN = {top: 10, bottom: 8, left: 10, right: 20};\nconst COLOR_CHART_TICK_WRAPPER_HEIGHT = 10;\nconst COLOR_CHART_TICK_HEIGHT = 8;\nconst COLOR_CHART_TICK_WIDTH = 4;\nconst COLOR_CHART_TICK_BORDER_COLOR = '#999999';\n\nconst StyledContainer = styled.div.attrs({\n  className: 'color-chart-loading'\n})`\n  height: ${HISTOGRAM_HEIGHT}px;\n`;\n\n// height 142 = 18 + 110 + 10\nconst ColorChartContainer = styled.div.attrs({\n  className: 'color-chart-container'\n})`\n  margin-top: 8px;\n`;\n\nconst ColorChartHeaderWrapper = styled.div.attrs({\n  className: 'color-chart-header'\n})`\n  display: flex;\n  justify-content: space-around;\n  color: ${props => props.theme.textColor};\n  margin-left: ${HISTOGRAM_MARGIN.left}px;\n  margin-right: ${HISTOGRAM_MARGIN.right}px;\n  font-size: 9px;\n`;\n\nconst ColorChartHeaderItem = styled.div`\n  width: 33%;\n  overflow: hidden;\n  white-space: nowrap;\n`;\n\nconst ColorChartWrapper = styled.div.attrs({\n  className: 'color-chart-wrapper'\n})`\n  position: relative;\n  height: ${HISTOGRAM_HEIGHT + 30}px;\n`;\n\nconst ColorPaletteWrapper = styled.div.attrs({\n  className: 'color-chart-palette'\n})`\n  position: absolute;\n  margin-top: ${HISTOGRAM_MARGIN.top}px;\n  margin-left: ${HISTOGRAM_MARGIN.left}px;\n  margin-right: ${HISTOGRAM_MARGIN.right}px;\n`;\n\nconst HistogramWrapper = styled.div.attrs({\n  className: 'color-chart-histogram'\n})`\n  position: absolute;\n`;\n\nconst ColorChartTickContainer = styled.div.attrs({\n  className: 'color-chart-tick-container'\n})`\n  height: ${COLOR_CHART_TICK_WRAPPER_HEIGHT}px;\n  position: relative;\n  margin-left: ${HISTOGRAM_MARGIN.left}px;\n  margin-right: ${HISTOGRAM_MARGIN.right}px;\n`;\n\nexport const ColorChartHeader = ({minVal, meanVal, maxVal}) => {\n  return (\n    <ColorChartHeaderWrapper>\n      <ColorChartHeaderItem title={minVal}>MIN: {minVal}</ColorChartHeaderItem>\n      <ColorChartHeaderItem title={meanVal}>MEAN: {d3Format('.4~r')(meanVal)}</ColorChartHeaderItem>\n      <ColorChartHeaderItem title={maxVal} style={{textAlign: 'right'}}>\n        MAX: {maxVal}\n      </ColorChartHeaderItem>\n    </ColorChartHeaderWrapper>\n  );\n};\n\nexport type ColorChartTickProps = {\n  colors: string[];\n  positions: number[];\n  onTickMoving: (positions: number[], dragTick: number) => void;\n  onTickChanged: () => void;\n  histogramWidth: number;\n};\n\nexport const ColorChartTick: React.FC<ColorChartTickProps> = ({\n  colors,\n  positions,\n  histogramWidth,\n  onTickMoving,\n  onTickChanged\n}) => {\n  const [statePositions, setPositions] = useState(positions);\n  const [dragTick, setDragTick] = useState(-1);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    setPositions(positions);\n    setDragTick(dragTick);\n  }, [positions, dragTick]);\n\n  const onMouseMove = useCallback(\n    e => {\n      if (dragTick >= 0) {\n        // @ts-ignore\n        const offsetX = containerRef.current.getBoundingClientRect().x;\n        statePositions[dragTick] = e.clientX - (offsetX ?? 0);\n        const leftBound = dragTick === 0 ? 0 : statePositions[dragTick - 1] + 1;\n        const rightBound =\n          dragTick === positions.length - 1 ? histogramWidth : statePositions[dragTick + 1] - 1;\n\n        // restrict user drag-n-move between left and right neighboring ticks\n        if (statePositions[dragTick] < leftBound) {\n          statePositions[dragTick] = leftBound;\n        }\n        if (statePositions[dragTick] > rightBound) {\n          statePositions[dragTick] = rightBound;\n        }\n        setPositions([...statePositions]);\n        onTickMoving(statePositions, dragTick);\n      }\n    },\n    [dragTick, onTickMoving, positions.length, statePositions, histogramWidth]\n  );\n\n  const onMouseUp = useCallback(\n    e => {\n      if (dragTick >= 0) {\n        onTickChanged();\n        setDragTick(-1);\n        e.stopPropagation();\n        e.preventDefault();\n      }\n    },\n    [dragTick, onTickChanged]\n  );\n\n  const onMouseDown = useCallback((e, tickIndex) => {\n    if (isNumber(tickIndex)) {\n      setDragTick(tickIndex);\n      e.stopPropagation();\n      e.preventDefault();\n    }\n  }, []);\n\n  return (\n    <ColorChartTickContainer\n      ref={containerRef}\n      onMouseMove={onMouseMove}\n      onMouseUp={onMouseUp}\n      onMouseLeave={onMouseUp}\n    >\n      {colors.map((color, index) => (\n        <div\n          draggable={true}\n          key={`color-chart-tick-${color}-${index}`}\n          onMouseDown={e => onMouseDown(e, index)}\n          style={{\n            backgroundColor: color,\n            left: `${statePositions[index] - COLOR_CHART_TICK_WIDTH / 2 - 1}px`,\n            borderWidth: `1px`,\n            borderStyle: 'solid',\n            borderColor: COLOR_CHART_TICK_BORDER_COLOR,\n            position: 'absolute',\n            width: `${COLOR_CHART_TICK_WIDTH}px`,\n            height: `${COLOR_CHART_TICK_HEIGHT}px`,\n            cursor: 'pointer'\n          }}\n        />\n      ))}\n    </ColorChartTickContainer>\n  );\n};\n\n// only for numetic field\nColumnStatsChartFactory.deps = [HistogramPlotFactory];\n\nexport type ColumnStatsChartWLoadingProps = {\n  colorField: Field;\n  dataset: KeplerTable;\n  colorBreaks: ColorBreak[] | ColorBreakOrdinal[] | null;\n  allBins: Bin[];\n  filteredBins: Bin[];\n  isFiltered: boolean;\n  histogramDomain: number[];\n  onChangedUpdater: (ticks: ColorBreak[]) => void;\n};\n\nexport type ColumnStatsChartProps = {\n  allBins: Bin[];\n  filteredBins: Bin[];\n  isFiltered: boolean;\n  histogramDomain: number[];\n  colorBreaks: ColorBreak[];\n  onChangedUpdater: (ticks: ColorBreak[]) => void;\n};\nfunction ColumnStatsChartFactory(\n  HistogramPlot: ReturnType<typeof HistogramPlotFactory>\n): React.FC<ColumnStatsChartWLoadingProps> {\n  const ColumnStatsChart: React.FC<ColumnStatsChartProps> = ({\n    allBins,\n    filteredBins,\n    isFiltered,\n    histogramDomain,\n    colorBreaks,\n    onChangedUpdater\n  }) => {\n    const [ticks, setTicks] = useState(colorBreaks);\n    const [ref, size] = useDimensions<HTMLDivElement>();\n    const histogramWidth = size\n      ? size.width - HISTOGRAM_MARGIN.left - HISTOGRAM_MARGIN.right\n      : HISTOGRAM_WIDTH;\n\n    // distinguish between props.colorBreaks and states.ticks\n    const isTickChangingRef = React.useRef(false);\n\n    useEffect(() => {\n      setTicks(ticks);\n      // reset isTickChanging once histogram domain is recomputed\n      isTickChangingRef.current = false;\n    }, [ticks]);\n\n    // histograms used by histogram-plot.js\n    const histogramsByGroup = useMemo(\n      () => ({\n        bins: allBins,\n        filteredBins\n      }),\n      [allBins, filteredBins]\n    );\n\n    // get colors from colorBreaks\n    const domainColors = useMemo(\n      () => (colorBreaks ? colorBreaks.map(c => c.data) : []),\n      [colorBreaks]\n    );\n\n    const tickPositions = useMemo(() => {\n      if (!isTickChangingRef.current) {\n        setTicks(colorBreaks);\n      }\n\n      const [valueMin, valueMax] = histogramDomain;\n      const widthScale = scaleLinear().domain([valueMin, valueMax]).range([0, histogramWidth]);\n      return ticks.slice(0, -1).map(cb => {\n        const pos = widthScale(cb.range[1]);\n        if (pos < 0) return 0;\n        else if (pos > histogramWidth) return histogramWidth;\n        return pos;\n      });\n    }, [histogramDomain, ticks, colorBreaks, histogramWidth]);\n\n    const domainColorWidths = useMemo(() => {\n      const n = tickPositions.length;\n      const widths = [tickPositions[0]];\n      for (let i = 1; i < n; ++i) {\n        widths.push(tickPositions[i] - tickPositions[i - 1]);\n      }\n      widths.push(histogramWidth - tickPositions[n - 1]);\n      return widths;\n    }, [tickPositions, histogramWidth]);\n\n    // handle tick drag-n-move\n    const onTickMovingHandler = useCallback(\n      (newTickPositions, tickIndex) => {\n        const [valueMin, valueMax] = histogramDomain;\n        const valueRange = valueMax - valueMin;\n        const breaks = [valueMin];\n        newTickPositions.forEach(element => {\n          breaks.push(valueMin + (valueRange * element) / histogramWidth);\n        });\n        breaks.push(valueMax);\n\n        for (let i = 0; i < ticks.length; ++i) {\n          const leftValue = i === tickIndex + 1 ? breaks[i] : ticks[i].range[0];\n          const rightValue = i + 1 === tickIndex + 1 ? breaks[i + 1] : ticks[i].range[1];\n\n          ticks[i] = {\n            ...ticks[i],\n            range: [leftValue, rightValue],\n            inputs: [leftValue, rightValue],\n            label: `${d3Format('.2f')(breaks[i])} to ${d3Format('.2f')(breaks[i + 1])}`\n          };\n        }\n        isTickChangingRef.current = true;\n        setTicks([...ticks]);\n      },\n      [histogramDomain, ticks, histogramWidth]\n    );\n\n    // update parent and sibling components when tick dragging ended\n    const onTickChangedHandler = useCallback(() => {\n      onChangedUpdater(ticks);\n    }, [onChangedUpdater, ticks]);\n\n    return (\n      <ColorChartContainer ref={ref}>\n        <ColorChartHeader\n          minVal={histogramDomain[0]}\n          maxVal={histogramDomain[1]}\n          meanVal={histogramDomain[2]}\n        />\n        <ColorChartWrapper>\n          <ColorPaletteWrapper>\n            <ColorPalette\n              colors={domainColors}\n              colorWidths={domainColorWidths}\n              height={HISTOGRAM_HEIGHT + 16}\n            />\n          </ColorPaletteWrapper>\n          <HistogramWrapper>\n            <HistogramPlot\n              histogramsByGroup={histogramsByGroup}\n              colorsByGroup={null}\n              isMasked={isFiltered ? HISTOGRAM_MASK_MODE.MaskWithOverlay : HISTOGRAM_MASK_MODE.Mask}\n              value={histogramDomain}\n              width={histogramWidth}\n              height={HISTOGRAM_HEIGHT}\n              margin={HISTOGRAM_MARGIN}\n              breakLines={tickPositions}\n            />\n          </HistogramWrapper>\n        </ColorChartWrapper>\n        <ColorChartTick\n          colors={domainColors.slice(0, -1)}\n          positions={tickPositions}\n          histogramWidth={histogramWidth}\n          onTickMoving={onTickMovingHandler}\n          onTickChanged={onTickChangedHandler}\n        />\n      </ColorChartContainer>\n    );\n  };\n\n  const ColumnStatsChartWLoading: React.FC<ColumnStatsChartWLoadingProps> = ({\n    colorField,\n    dataset,\n    colorBreaks,\n    allBins,\n    filteredBins,\n    isFiltered,\n    histogramDomain,\n    onChangedUpdater\n  }) => {\n    const fieldName = colorField?.name;\n    const field = useMemo(\n      () => (fieldName ? dataset.getColumnField(fieldName) : null),\n      [dataset, fieldName]\n    );\n\n    if (!isNumericColorBreaks(colorBreaks)) {\n      // TODO: implement display for ordinal breaks\n      return null;\n    }\n\n    if (field?.isLoadingStats) {\n      return (\n        <StyledContainer>\n          <LoadingSpinner />\n        </StyledContainer>\n      );\n    }\n\n    return (\n      <ColumnStatsChart\n        colorBreaks={colorBreaks}\n        allBins={allBins}\n        filteredBins={filteredBins}\n        isFiltered={isFiltered}\n        histogramDomain={histogramDomain}\n        onChangedUpdater={onChangedUpdater}\n      />\n    );\n  };\n\n  return ColumnStatsChartWLoading;\n}\n\nexport default ColumnStatsChartFactory;\n"
  },
  {
    "path": "src/components/src/common/data-table/button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {MouseEventHandler} from 'react';\nimport styled from 'styled-components';\n\nconst StyledButton = styled.button`\n  color: ${props => props.theme.optionButtonColor};\n  background-color: transparent;\n  border: none;\n  cursor: pointer;\n  outline: none;\n  transition: ${props => props.theme.transition};\n  height: 2rem;\n  display: flex;\n  align-items: center;\n  padding: 0;\n\n  &:hover {\n    opacity: 0.8;\n  }\n`;\n\ntype ButtonProps = {\n  onClick?: MouseEventHandler<HTMLButtonElement>;\n  disabled?: boolean;\n  text?: string;\n} & React.ButtonHTMLAttributes<HTMLButtonElement>;\n\nconst noop = () => {\n  return;\n};\nconst Button = ({onClick = noop, disabled = false, text = '', children, ...props}: ButtonProps) => (\n  <StyledButton {...props} onClick={disabled ? undefined : onClick}>\n    {text || children}\n  </StyledButton>\n);\n\nexport default Button;\n"
  },
  {
    "path": "src/components/src/common/data-table/canvas.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\n\n/*\n    ScrollSync works by getting a callback about the dom elements scroll amount\n    and then using that to pass into how much to scroll all child components,\n    it works great!\n\n    Except... Because scrolling is managed by a separate thread, and JavaScript\n    is only periodically notified of the updated position, there's some latency\n    issues with this.\n\n    We can hack around this by using a niche property of canvas that removes the\n    delay in scroll event firing! Easiest way to reproduce: enable \"Trace React updates\"\n    in React DevTools (it works by overlaying a viewport-wide canvas over the document).\n  */\nexport default styled.canvas`\n  height: 100%;\n  left: 0;\n  pointer-events: none;\n  position: absolute;\n  top: 0;\n  width: 100%;\n`;\n"
  },
  {
    "path": "src/components/src/common/data-table/cell-size.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport document from 'global/document';\nimport {DataContainerInterface, parseFieldValue} from '@kepler.gl/utils';\n\nconst MIN_GHOST_CELL_SIZE = 200;\nconst MIN_CELL_SIZE = 45;\n// first column have padding on the left\nconst EDGE_COLUMN_PADDING = 10;\n\n// in case cell content is small, column name is big, we allow max empty space to\n// be added to min cell width in order to show column name\nconst MAX_EMPTY_COLUMN_SPACE = 60;\n\ntype RenderSizeParam = {\n  text: {dataContainer: DataContainerInterface; column: string};\n  type?: string;\n  colIdx: number;\n  numRowsToCalculate?: number;\n  fontSize?: number;\n  font?: string;\n  cellPadding?: number;\n  maxCellSize?: number;\n  maxHeaderSize?: number;\n  minCellSize?: number;\n  optionsButton?: number;\n};\n\nexport type CellSizeCache = {\n  [key: string]: {\n    row: number;\n    header: number;\n  };\n};\n\n/**\n * Measure rows and column content to determine min width for each column\n * @param {RenderSizeParam} param0\n */\nexport function renderedSize({\n  text: {dataContainer, column},\n  type = 'string',\n  colIdx,\n  numRowsToCalculate = 10,\n  fontSize = 12,\n  font = 'Lato',\n  cellPadding = 40,\n  maxCellSize = 500,\n  maxHeaderSize = 500,\n  minCellSize = MIN_CELL_SIZE,\n  optionsButton = 44\n}: RenderSizeParam): {row: number; header: number} {\n  if (!document) {\n    return {\n      row: 0,\n      header: 0\n    };\n  }\n\n  const textCanvas = document.createElement('canvas');\n  document.body.appendChild(textCanvas);\n  const context = textCanvas.getContext('2d');\n  context.font = [fontSize, font].join('px ');\n\n  let rowsToSample = [...Array(numRowsToCalculate)].map(() =>\n    Math.floor(Math.random() * (dataContainer.numRows() - 1))\n  );\n\n  // If we have less than 10 rows, lets measure all of them\n  if (dataContainer.numRows() <= numRowsToCalculate) {\n    rowsToSample = Array.from(Array(dataContainer.numRows()).keys());\n  }\n  const rowWidth = Math.max(\n    ...rowsToSample.map(rowIdx => {\n      const value = parseFieldValue(dataContainer.valueAt(rowIdx, colIdx), type);\n      // measuring large text cause slow performance\n      if (value.length > maxCellSize) {\n        return maxCellSize;\n      }\n      const textWidth = context.measureText(value).width;\n      return Math.ceil(textWidth) + cellPadding;\n    })\n  );\n  // header cell only has left padding\n  const headerWidth =\n    Math.ceil(context.measureText(column).width) + cellPadding / 2 + optionsButton;\n\n  // min row width is measured by cell content\n  const minRowWidth = minCellSize + cellPadding;\n  // min header width is measured by cell\n  const minHeaderWidth = minCellSize + cellPadding / 2 + optionsButton;\n\n  const clampedRowWidth = clamp(minRowWidth, maxCellSize, rowWidth);\n  const clampedHeaderWidth = clamp(minHeaderWidth, maxHeaderSize, headerWidth);\n\n  // cleanup\n  textCanvas.parentElement.removeChild(textCanvas);\n\n  return {\n    row: clampedRowWidth,\n    header: clampedHeaderWidth\n  };\n}\n\nfunction clamp(min, max, value) {\n  return Math.max(Math.min(max, value), min);\n}\n\nfunction getColumnOrder(pinnedColumns: string[] = [], unpinnedColumns: string[] = []) {\n  return [...pinnedColumns, ...unpinnedColumns];\n}\n\n// If total min cell size is bigger than containerWidth adjust column\nfunction getMinCellSize(cellSizeCache: CellSizeCache) {\n  return Object.keys(cellSizeCache).reduce(\n    (accu, col) => ({\n      ...accu,\n      // if row is larger than header, use row\n      [col]:\n        cellSizeCache[col].row > cellSizeCache[col].header\n          ? cellSizeCache[col].row\n          : // if row is smaller than header, use the smaller of MAX_EMPTY_COLUMN_SPACE + row width and header\n            Math.min(cellSizeCache[col].header, cellSizeCache[col].row + MAX_EMPTY_COLUMN_SPACE)\n    }),\n    {}\n  );\n}\n\nfunction getSizeSum(sizeCache, key) {\n  return Object.keys(sizeCache).reduce(\n    (acc, val) => acc + (key ? sizeCache[val][key] : sizeCache[val]),\n    0\n  );\n}\n\n/**\n * Expand cell to fit both row and header, if there is still room left,\n * expand last cell to fit the entire width of the container\n * @param {CellSizeCache} cellSizeCache\n * @param {string[]} columnOrder\n * @param {number} containerWidth\n * @param {number} roomToFill\n */\nfunction expandCellSize(\n  cellSizeCache: CellSizeCache,\n  columnOrder: string[],\n  containerWidth: number,\n  roomToFill: number\n): {\n  cellSizeCache: CellSizeCache;\n  ghost: number | null;\n} {\n  let remaining = roomToFill;\n\n  const expandedCellSize = columnOrder.reduce((accu, col) => {\n    let size = cellSizeCache[col].row;\n    if (cellSizeCache[col].row < cellSizeCache[col].header && remaining > 0) {\n      // if we are cutting off the header, expand to fit it\n      size =\n        cellSizeCache[col].header - cellSizeCache[col].row < remaining\n          ? cellSizeCache[col].header\n          : cellSizeCache[col].row + remaining;\n      remaining -= size - cellSizeCache[col].row;\n    }\n\n    return {\n      ...accu,\n      [col]: size\n    };\n  }, {});\n\n  let ghost: number | null = null;\n  if (remaining > 0 && remaining < MIN_GHOST_CELL_SIZE) {\n    // expand last cell\n    const lastCell = columnOrder[columnOrder.length - 1];\n    expandedCellSize[lastCell] += remaining;\n  } else if (remaining >= MIN_GHOST_CELL_SIZE) {\n    // if too much left add a ghost cell\n    ghost = remaining;\n  }\n\n  return {\n    cellSizeCache: expandedCellSize,\n    ghost\n  };\n}\n\nfunction addPaddingToFirstColumn(\n  cellSizeCache: CellSizeCache,\n  columnOrder: string[] = []\n): CellSizeCache {\n  const firstCol = columnOrder[0];\n\n  if (firstCol && cellSizeCache[firstCol]) {\n    return {\n      ...cellSizeCache,\n      [firstCol]: {\n        header: cellSizeCache[firstCol].header + EDGE_COLUMN_PADDING,\n        row: cellSizeCache[firstCol].row + EDGE_COLUMN_PADDING\n      }\n    };\n  }\n  return cellSizeCache;\n}\n\n/**\n * Adjust cell size based on container width\n * @param {number} containerWidth\n * @param {CellSizeCache} cellSizeCache\n * @param {string[]} pinnedColumns\n * @param {string[]} unpinnedColumns\n */\nexport function adjustCellsToContainer(\n  containerWidth: number,\n  cellSizeCache: CellSizeCache,\n  pinnedColumns: string[],\n  unpinnedColumns: string[]\n): {\n  cellSizeCache: CellSizeCache;\n  ghost?: number | null;\n} {\n  const columnOrder = getColumnOrder(pinnedColumns, unpinnedColumns);\n  const paddedCellSize = addPaddingToFirstColumn(cellSizeCache, columnOrder);\n  const minRowSum = getSizeSum(paddedCellSize, 'row');\n\n  if (minRowSum >= containerWidth) {\n    // we apply the min Width to all cells\n    return {cellSizeCache: getMinCellSize(paddedCellSize)};\n  }\n  // if we have some room to expand\n  return expandCellSize(paddedCellSize, columnOrder, containerWidth, containerWidth - minRowSum);\n}\n"
  },
  {
    "path": "src/components/src/common/data-table/display-format.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled from 'styled-components';\n\nimport {getFieldFormatLabels} from '@kepler.gl/utils';\nimport {ALL_FIELD_TYPES, TooltipFormat} from '@kepler.gl/constants';\nimport {ColMeta, ColMetaProps} from '@kepler.gl/types';\n\nimport {InputLight} from '../../common/styled-components';\nimport {FormatterDropdown} from './option-dropdown';\n\nconst StyledConfigPanel = styled.div`\n  background-color: ${props => props.theme.headerCellBackground};\n  box-shadow: 0 10px 18px 0 rgb(0 0 0 / 36%);\n  flex-grow: 1;\n`;\nconst StyledConfigPanelContent = styled.div`\n  padding: 20px;\n  min-width: 230px;\n  max-height: 400px;\n  overflow: overlay;\n`;\n\nconst StyledTableConfigGroup = styled.div`\n  margin-bottom: 10px;\n  display: flex;\n  align-items: center;\n\n  input {\n    cursor: pointer !important;\n    width: 184px;\n    height: 22px;\n  }\n`;\n\nexport type DataTableConfigProps = {\n  title: string;\n  id: string;\n  defaultFormat: string;\n  options: TooltipFormat[];\n  columns: {name: string}[];\n  colMeta: ColMeta;\n  setColumnDisplayFormat: (formats: {[key: string]: string}) => void;\n  onClose: () => void;\n};\n\nexport const NumberFormatConfig: React.FC<DataTableConfigProps> = ({\n  title,\n  id,\n  defaultFormat,\n  options,\n  columns,\n  setColumnDisplayFormat,\n  onClose\n}: DataTableConfigProps) => {\n  const [showFormatter, setShowFormatter] = useState(false);\n  const [format, setFormat] = useState(defaultFormat);\n\n  const onSetDisplayFormat = useCallback(\n    (option: TooltipFormat) => {\n      setFormat(option.label);\n      const formats: {[key: string]: string} = columns.reduce((prev, col) => {\n        prev[col.name] = option.format;\n        return prev;\n      }, {});\n      setColumnDisplayFormat(formats);\n      onClose();\n    },\n    [columns, setColumnDisplayFormat, onClose]\n  );\n\n  return (\n    <StyledTableConfigGroup>\n      <InputLight\n        id={id}\n        type=\"text\"\n        value={title}\n        data-tip={format}\n        readOnly\n        onClick={() => setShowFormatter(true)}\n      />\n      <FormatterDropdown\n        left={-185}\n        top={10}\n        isOpened={showFormatter}\n        displayFormat={format}\n        setDisplayFormat={onSetDisplayFormat}\n        onClose={() => setShowFormatter(false)}\n        formatLabels={options}\n      />\n    </StyledTableConfigGroup>\n  );\n};\n\nfunction DataTableConfigFactory() {\n  const getColumnsByFieldType = (columns: string[], colMeta: ColMeta, fieldType: string) => {\n    const result: ColMetaProps[] = [];\n    columns.forEach(colName => {\n      if (colMeta[colName]?.type === fieldType) {\n        result.push(colMeta[colName]);\n      }\n    });\n    return result;\n  };\n\n  const DataTableConfig = ({columns, colMeta, setColumnDisplayFormat, onClose}) => {\n    const formatConfigs = [\n      {\n        title: '# Set Integer Number Format',\n        id: 'input-iteger-format',\n        displayType: ALL_FIELD_TYPES.integer\n      },\n      {\n        title: '# Set Float Number Format',\n        id: 'input-float-format',\n        displayType: ALL_FIELD_TYPES.real\n      },\n      {\n        title: '# Set Timestamp Format',\n        id: 'input-datetime-format',\n        displayType: ALL_FIELD_TYPES.timestamp\n      },\n      {\n        title: '# Set Date Format',\n        id: 'input-date-format',\n        displayType: ALL_FIELD_TYPES.date\n      },\n      {\n        title: '# Set Boolean Format',\n        id: 'input-bool-format',\n        displayType: ALL_FIELD_TYPES.boolean\n      }\n    ];\n\n    return (\n      <StyledConfigPanel>\n        <StyledConfigPanelContent>\n          {formatConfigs.map((config, index) => (\n            <NumberFormatConfig\n              title={`${config.title}`}\n              key={index}\n              id={config.id}\n              defaultFormat={'None'}\n              colMeta={colMeta}\n              options={getFieldFormatLabels(`${config.displayType}`)}\n              columns={getColumnsByFieldType(columns, colMeta, `${config.displayType}`)}\n              setColumnDisplayFormat={setColumnDisplayFormat}\n              onClose={onClose}\n            />\n          ))}\n        </StyledConfigPanelContent>\n      </StyledConfigPanel>\n    );\n  };\n  return DataTableConfig;\n}\n\nexport default DataTableConfigFactory;\n"
  },
  {
    "path": "src/components/src/common/data-table/grid.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport {Grid, GridProps} from 'react-virtualized';\nimport isEqual from 'lodash/isEqual';\n\nexport default class GridHack extends PureComponent<GridProps> {\n  grid: Grid | null = null;\n\n  componentDidUpdate(preProps) {\n    /*\n     * This hack exists because in react-virtualized the\n     * _columnWidthGetter is only called in the constructor\n     * even though it is reassigned with new props resulting in\n     * a new width for cells not being calculated so we must\n     * force trigger a resize.\n     *\n     * https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/Grid.js#L322\n     *\n     */\n    if (!isEqual(preProps.cellSizeCache, this.props.cellSizeCache)) {\n      this.grid?.recomputeGridSize();\n    }\n  }\n\n  componentWillUnmount() {\n    // @ts-expect-error _scrollingContainer not typed in Grid\n    this.grid?._scrollingContainer?.removeEventListener('wheel', this._preventScrollBack, {\n      passive: false\n    });\n  }\n\n  _preventScrollBack = e => {\n    const {scrollLeft} = this.props;\n    if (scrollLeft !== undefined && scrollLeft <= 0 && e.deltaX < 0) {\n      // Prevent Scroll On Scrollable Elements, avoid browser backward navigation\n      // https://alvarotrigo.com/blog/prevent-scroll-on-scrollable-element-js/\n      e.preventDefault();\n      e.stopPropagation();\n      return false;\n    }\n    return;\n  };\n\n  _updateRef = x => {\n    if (!this.grid && x) {\n      this.grid = x;\n      /*\n       * This hack exists because we need to add wheel event listener to the div rendered by Grid\n       *\n       */\n      // @ts-expect-error _scrollingContainer not typed in Grid\n      this.grid?._scrollingContainer?.addEventListener('wheel', this._preventScrollBack, {\n        passive: false\n      });\n    }\n  };\n\n  render() {\n    const {setGridRef, ...rest} = this.props;\n    return (\n      <Grid\n        ref={x => {\n          if (setGridRef) setGridRef(x);\n          this._updateRef(x);\n        }}\n        key=\"grid-hack\"\n        {...rest}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/data-table/header-cell.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {CSSProperties, useState, useCallback} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport Button from './button';\nimport {ArrowUp, ArrowDown, VertThreeDots, Hash} from '../../common/icons';\nimport {SORT_ORDER} from '@kepler.gl/constants';\nimport OptionDropdown, {FormatterDropdown} from './option-dropdown';\nimport {getFieldFormatLabels} from '@kepler.gl/utils';\nimport {ColMeta} from '@kepler.gl/types';\nimport FieldTokenFactory, {FieldTokenProps} from '../../common/field-token';\nimport {DataTableProps} from './index';\n\nconst StyledHeaderCell = styled.div`\n  border-bottom: 1px solid ${props => props.theme.headerCellBorderColor};\n  border-top: 1px solid ${props => props.theme.headerCellBorderColor};\n  padding-top: ${props => props.theme.headerPaddingTop}px;\n  padding-right: 0;\n  padding-bottom: ${props => props.theme.headerPaddingBottom}px;\n  padding-left: ${props => props.theme.cellPaddingSide}px;\n  align-items: center;\n  justify-content: space-between;\n  display: flex;\n  flex-direction: row;\n  background-color: ${props => props.theme.headerCellBackground};\n\n  .n-sort-idx {\n    font-size: 9px;\n  }\n  .details {\n    font-weight: 500;\n    display: flex;\n    flex-direction: column;\n    justify-content: flex-start;\n    height: 100%;\n    overflow: hidden;\n    flex-grow: 1;\n\n    .col-name {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n\n      .col-name__left {\n        display: flex;\n        align-items: center;\n        overflow: hidden;\n\n        svg {\n          margin-left: 6px;\n        }\n      }\n      .col-name__name {\n        overflow: hidden;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n      }\n    }\n  }\n\n  .more {\n    margin-left: 5px;\n  }\n\n  .col-name__format svg {\n    width: 10px;\n    height: 10px;\n    stroke-width: 1;\n  }\n`;\n\ntype CellInfo = {\n  columnIndex: number;\n  isScrolling: boolean;\n  isVisible: boolean;\n  key: string;\n  parent: any;\n  rowIndex: number;\n  style: CSSProperties;\n};\n\ntype HeaderCellProps = {\n  // passed down from react virtualized Grid\n  cellInfo: CellInfo;\n  columns: DataTableProps['columns'];\n  colMeta?: ColMeta;\n  isPinned?: boolean;\n  showStats?: boolean;\n  props: DataTableProps;\n  toggleMoreOptions: (moreOptionsColumn: string) => void;\n  moreOptionsColumn: null | string;\n  style: CSSProperties;\n};\n\nconst HeaderCellFactory = (FieldToken: React.FC<FieldTokenProps>) => {\n  const HeaderCell = ({\n    cellInfo,\n    columns,\n    isPinned,\n    props,\n    toggleMoreOptions,\n    moreOptionsColumn\n  }: HeaderCellProps) => {\n    const {columnIndex, key, style} = cellInfo;\n    const {\n      colMeta,\n      sortColumn,\n      sortTableColumn,\n      pinTableColumn,\n      copyTableColumn,\n      setColumnDisplayFormat\n    } = props;\n    const [showFormatter, setShowFormatter] = useState(false);\n    const column = columns[columnIndex];\n\n    const isGhost = column.ghost;\n    const isSorted = sortColumn[column];\n    const firstCell = columnIndex === 0;\n    const isFormatted = Boolean(colMeta[column]?.displayFormat);\n    const formatLabels = isFormatted ? getFieldFormatLabels(colMeta[column].type) : [];\n    const onSortTable = useCallback(() => sortTableColumn?.(column), [sortTableColumn, column]);\n    const onToggleOptionMenu = useCallback(\n      () => toggleMoreOptions(column),\n      [toggleMoreOptions, column]\n    );\n    const onPin = useCallback(() => pinTableColumn(column), [pinTableColumn, column]);\n    const onCopy = useCallback(() => copyTableColumn(column), [copyTableColumn, column]);\n    const onSetDisplayFormat = useCallback(\n      displayFormat => {\n        setColumnDisplayFormat?.({[column]: displayFormat.format});\n      },\n      [column, setColumnDisplayFormat]\n    );\n\n    const onToggleDisplayFormat = useCallback(() => {\n      setShowFormatter(!showFormatter);\n    }, [showFormatter]);\n\n    return (\n      <StyledHeaderCell\n        className={classnames('header-cell', {\n          [`column-${columnIndex}`]: true,\n          'pinned-header-cell': isPinned,\n          'first-cell': firstCell\n        })}\n        key={key}\n        style={style}\n        onClick={e => {\n          e.shiftKey ? sortTableColumn(column) : null;\n        }}\n        onDoubleClick={onSortTable}\n        title={column}\n      >\n        {isGhost ? (\n          <div />\n        ) : (\n          <>\n            <section className=\"details\">\n              <div className=\"col-name\">\n                <div className=\"col-name__left\">\n                  <div className=\"col-name__name\">{colMeta[column].name}</div>\n                  <Button className=\"col-name__sort\" onClick={onSortTable}>\n                    {isSorted ? (\n                      isSorted === SORT_ORDER.ASCENDING ? (\n                        <ArrowUp height=\"14px\" />\n                      ) : (\n                        <ArrowDown height=\"14px\" />\n                      )\n                    ) : null}\n                  </Button>\n                  <Button className=\"col-name__format\" onClick={onToggleDisplayFormat}>\n                    {isFormatted ? <Hash height=\"14px\" /> : null}\n                    <FormatterDropdown\n                      left={0}\n                      top={0}\n                      isOpened={isFormatted && showFormatter}\n                      displayFormat={colMeta[column].displayFormat}\n                      setDisplayFormat={onSetDisplayFormat}\n                      onClose={() => setShowFormatter(false)}\n                      formatLabels={formatLabels}\n                    />\n                  </Button>\n                </div>\n                <Button className=\"more\" onClick={onToggleOptionMenu}>\n                  <VertThreeDots height=\"14px\" />\n                </Button>\n              </div>\n              <FieldToken type={colMeta[column].type} />\n            </section>\n\n            <section className=\"options\">\n              <OptionDropdown\n                isOpened={moreOptionsColumn === column}\n                column={column}\n                colMeta={colMeta}\n                toggleMoreOptions={toggleMoreOptions}\n                sortTableColumn={\n                  sortTableColumn ? mode => sortTableColumn(column, mode) : undefined\n                }\n                pinTableColumn={onPin}\n                copyTableColumn={onCopy}\n                setDisplayFormat={setColumnDisplayFormat ? onSetDisplayFormat : undefined}\n              />\n            </section>\n          </>\n        )}\n      </StyledHeaderCell>\n    );\n  };\n  return HeaderCell;\n};\nHeaderCellFactory.deps = [FieldTokenFactory];\nexport default HeaderCellFactory;\n"
  },
  {
    "path": "src/components/src/common/data-table/index.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, useMemo} from 'react';\nimport {ScrollSync, AutoSizer, OnScrollParams, GridProps, Index} from 'react-virtualized';\nimport styled, {withTheme} from 'styled-components';\nimport classnames from 'classnames';\nimport {createSelector} from 'reselect';\nimport get from 'lodash/get';\nimport debounce from 'lodash/debounce';\nimport {ArrowDown} from '../icons';\n\nimport {CellSizeCache} from './cell-size';\n\nimport Grid from './grid';\nimport HeaderCellFactory from './header-cell';\n\nimport {ColMeta} from '@kepler.gl/types';\nimport {parseFieldValue, getColumnFormatter, DataContainerInterface} from '@kepler.gl/utils';\nimport {adjustCellsToContainer} from './cell-size';\n\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\n\nconst defaultHeaderRowHeight = 55;\nconst defaultHeaderStatsControlHeight = 40;\nconst defaultRowHeight = 32;\nconst overscanColumnCount = 10;\nconst overscanRowCount = 10;\n// The default scrollbar width can range anywhere from 12px to 17px\nconst browserScrollBarWidth = 17;\nconst fieldToAlignRight = {\n  [ALL_FIELD_TYPES.integer]: true,\n  [ALL_FIELD_TYPES.real]: true\n};\n\nconst pinnedClassList = {\n  header: 'pinned-columns--header pinned-grid-container',\n  rows: 'pinned-columns--rows pinned-grid-container'\n};\n\nconst unpinnedClassList = {\n  header: 'unpinned-columns--header unpinned-grid-container',\n  rows: 'unpinned-columns--rows unpinned-grid-container'\n};\n\ntype ContainerProps = {\n  hasCustomScrollBarStyle?: boolean;\n};\n\nexport const Container = styled.div<ContainerProps>`\n  display: flex;\n  font-size: 11px;\n  flex-grow: 1;\n  color: ${props => props.theme.dataTableTextColor};\n  width: 100%;\n  position: relative;\n  .ReactVirtualized__Grid:focus,\n  .ReactVirtualized__Grid:active {\n    outline: 0;\n  }\n  .body-grid {\n    ${props => props.hasCustomScrollBarStyle && props.theme.modalScrollBar}\n  }\n\n  .cell {\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n\n  *:focus {\n    outline: 0;\n  }\n\n  .results-table-wrapper {\n    position: relative;\n    min-height: 100%;\n    max-height: 100%;\n    display: flex;\n    flex-direction: row;\n    flex-grow: 1;\n    overflow: hidden;\n\n    .scroll-in-ui-thread.pinned-columns--header {\n      overflow: hidden;\n      border-bottom: 1px solid ${props => props.theme.cellBorderColor};\n      padding-bottom: ${browserScrollBarWidth}px;\n    }\n    .scroll-in-ui-thread.unpinned-columns--header {\n      width: 100vw;\n      overflow: hidden;\n      border-bottom: 1px solid ${props => props.theme.cellBorderColor};\n      // leave room for scrollbar\n      padding-bottom: ${browserScrollBarWidth}px;\n    }\n\n    .scroll-in-ui-thread::after {\n      content: '';\n      height: 100%;\n      left: 0;\n      position: absolute;\n      pointer-events: none;\n      top: 0;\n      width: 100%;\n    }\n\n    .grid-row {\n      position: relative;\n      display: flex;\n      flex-direction: row;\n    }\n    .grid-column {\n      display: flex;\n      flex-direction: column;\n      flex: 1 1 auto;\n    }\n    .pinned-grid-container {\n      flex: 0 0 75px;\n      z-index: 10;\n      position: absolute;\n      left: 0;\n      top: 0;\n      border-right: 2px solid ${props => props.theme.pinnedGridBorderColor};\n    }\n    .even-row {\n      background-color: ${props => props.theme.evenRowBackground};\n    }\n    .odd-row {\n      background-color: ${props => props.theme.oddRowBackground};\n    }\n    .cell,\n    .header-cell {\n      width: 100%;\n      height: 100%;\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n      align-items: flex-start;\n      text-align: center;\n      overflow: hidden;\n      // header border is rendered by header container\n      border-bottom: 0;\n      .n-sort-idx {\n        font-size: 9px;\n      }\n    }\n    .cell {\n      border-bottom: 1px solid ${props => props.theme.cellBorderColor};\n      border-right: 1px solid ${props => props.theme.cellBorderColor};\n      white-space: nowrap;\n      overflow: auto;\n      padding: 0 ${props => props.theme.cellPaddingSide}px;\n      font-size: ${props => props.theme.cellFontSize}px;\n\n      .result-link {\n        text-decoration: none;\n      }\n    }\n    .cell.end-cell,\n    .header-cell.end-cell {\n      border-right: none;\n      padding-right: ${props => props.theme.cellPaddingSide + props.theme.edgeCellPaddingSide}px;\n    }\n    .cell.first-cell,\n    .header-cell.first-cell {\n      padding-left: ${props => props.theme.cellPaddingSide + props.theme.edgeCellPaddingSide}px;\n    }\n    .cell.bottom-cell {\n      border-bottom: none;\n    }\n    .cell.align-right {\n      align-items: flex-end;\n    }\n  }\n\n  &:focus {\n    outline: none;\n  }\n`;\n\nconst defaultColumnWidth = 200;\n\nexport type SortColumn = {\n  column?: string;\n  mode?: string;\n};\n\nconst columnWidthFunction =\n  (columns, cellSizeCache, ghost?) =>\n  ({index}) => {\n    return (columns[index] || {}).ghost\n      ? ghost\n      : cellSizeCache[columns[index]] || defaultColumnWidth;\n  };\n\ninterface GetRowCellProps {\n  dataContainer: DataContainerInterface | null;\n  columns: (string & {ghost?: boolean})[];\n  column: string;\n  colMeta;\n  rowIndex: number;\n  sortOrder?: number[] | null;\n}\n\n/*\n * This is an accessor method used to generalize getting a cell from a data row\n */\nconst defaultGetRowCell = (\n  {dataContainer, columns, column, colMeta, rowIndex, sortOrder}: GetRowCellProps,\n  formatter\n) => {\n  const rowIdx = sortOrder && sortOrder.length ? get(sortOrder, rowIndex) : rowIndex;\n  const {type} = colMeta[column];\n\n  const value = dataContainer?.valueAt(rowIdx, columns.indexOf(column));\n  return value === null || value === undefined || value === ''\n    ? ''\n    : formatter\n    ? formatter(value)\n    : parseFieldValue(value, type);\n};\n\ntype StatsControlProps = {\n  top: number;\n  showStats?: boolean;\n};\n\nconst StyledStatsControl = styled.div<StatsControlProps>`\n  height: ${props => props.theme.headerStatsControlHeight}px;\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  align-items: stretch;\n  position: absolute;\n  top: ${props => props.top}px;\n  font-family: ${props => props.theme.fontFamilyMedium}px;\n  font-size: 12px;\n  color: ${props => props.theme.activeColor};\n  background-color: ${props => props.theme.headerCellStatsControlBackground};\n  &:hover {\n    cursor: pointer;\n  }\n\n  > div {\n    padding: 0px 24px;\n    display: flex;\n    align-items: center;\n\n    svg {\n      margin-left: 12px;\n      transition: transform 0.5s ease;\n      transform: rotate(${props => (props.showStats ? 180 : 0)}deg);\n    }\n  }\n`;\n\nconst StatsControl = ({\n  top,\n  showStats,\n  toggleShowStats\n}: {\n  top: number;\n  showStats?: boolean;\n  toggleShowStats: () => void;\n}) => (\n  <StyledStatsControl top={top} showStats={showStats}>\n    <div onClick={toggleShowStats}>\n      {showStats ? 'Hide Column Stats' : 'Show Column Stats'}\n      <ArrowDown height=\"18px\" />\n    </div>\n  </StyledStatsControl>\n);\n\ninterface TableSectionProps {\n  classList?: {\n    header: string;\n    rows: string;\n  };\n  isPinned?: boolean;\n  columns: (string & {ghost?: boolean})[];\n  headerGridProps?;\n  fixedWidth?: number | null;\n  fixedHeight?: number | null;\n  onScroll?: (params: OnScrollParams) => void;\n  scrollTop?: number;\n  dataGridProps: {\n    rowHeight: number | ((params: Index) => number);\n    rowCount: number;\n  } & Partial<GridProps>;\n  columnWidth?;\n  setGridRef?: (ref: HTMLDivElement | null) => void;\n  headerCellRender?;\n  dataCellRender?;\n  scrollLeft?: number;\n}\n\nexport const TableSection = ({\n  classList,\n  isPinned,\n  columns,\n  headerGridProps,\n  fixedWidth,\n  fixedHeight = undefined,\n  onScroll,\n  scrollTop,\n  dataGridProps,\n  columnWidth,\n  setGridRef = undefined,\n  headerCellRender,\n  dataCellRender,\n  scrollLeft = 0\n}: TableSectionProps) => {\n  const headerHeight = headerGridProps.height;\n\n  const headerStyle = useMemo(\n    () => ({\n      height: `${headerHeight}px`\n    }),\n    [headerHeight]\n  );\n  const contentStyle = useMemo(\n    () => ({\n      top: `${headerHeight}px`\n    }),\n    [headerHeight]\n  );\n\n  return (\n    <AutoSizer>\n      {({width, height}) => {\n        const gridDimension = {\n          columnCount: columns.length,\n          columnWidth,\n          width: fixedWidth || width\n        };\n        const headerGridWidth = fixedWidth || width;\n        const dataGridHeight = fixedHeight || height;\n\n        return (\n          <>\n            <div\n              className={classnames('scroll-in-ui-thread', classList?.header)}\n              style={headerStyle}\n            >\n              <Grid\n                cellRenderer={headerCellRender}\n                {...headerGridProps}\n                {...gridDimension}\n                height={headerGridProps.height + browserScrollBarWidth}\n                width={headerGridWidth}\n                scrollLeft={scrollLeft}\n                onScroll={args => onScroll?.({...args, scrollTop:scrollTop ?? 0 })}\n              />\n            </div>\n            <div\n              className={classnames('scroll-in-ui-thread', classList?.rows)}\n              style={contentStyle}\n            >\n              <Grid\n                cellRenderer={dataCellRender}\n                {...dataGridProps}\n                {...gridDimension}\n                className={isPinned ? 'pinned-grid' : 'body-grid'}\n                height={dataGridHeight - headerGridProps.height}\n                onScroll={onScroll}\n                scrollLeft={scrollLeft}\n                scrollTop={scrollTop}\n                setGridRef={setGridRef}\n              />\n            </div>\n          </>\n        );\n      }}\n    </AutoSizer>\n  );\n};\n\nexport interface DataTableProps {\n  dataId?: string;\n  hasStats?: boolean;\n  cellSizeCache?: CellSizeCache;\n  pinnedColumns?: string[];\n  columns: (string & {ghost?: boolean})[];\n  fixedWidth?: number | null;\n  theme?: any;\n  dataContainer: DataContainerInterface | null;\n  fixedHeight?: number | null;\n  colMeta: ColMeta;\n  sortColumn: SortColumn;\n  sortTableColumn: (column: string, mode?: string) => void;\n  pinTableColumn: (column: string) => void;\n  setColumnDisplayFormat?: (formats: {[key: string]: string}) => void;\n  copyTableColumn: (column: string) => void;\n  sortOrder?: number[] | null;\n  showStats?: boolean;\n  hasCustomScrollBarStyle?: boolean;\n  getRowCell?: (renderDataCellProps: GetRowCellProps, formatter: any) => string | number;\n}\n\ninterface DataTableState {\n  cellSizeCache?: CellSizeCache;\n  moreOptionsColumn?;\n  ghost?;\n  showStats?: boolean;\n}\n\nconst DUMMY_STYLE = {};\n\nDataTableFactory.deps = [HeaderCellFactory];\nfunction DataTableFactory(\n  HeaderCell: ReturnType<typeof HeaderCellFactory>\n): React.ComponentType<DataTableProps> {\n  class DataTable extends Component<DataTableProps, DataTableState> {\n    static defaultProps = {\n      dataContainer: null,\n      pinnedColumns: [],\n      colMeta: {},\n      cellSizeCache: {},\n      sortColumn: {},\n      fixedWidth: null,\n      fixedHeight: null,\n      theme: {},\n      hasStats: false,\n      hasCustomScrollBarStyle: true\n    };\n\n    pinnedGrid: HTMLDivElement | null = null;\n    unpinnedGrid: HTMLDivElement | null = null;\n    hasMounted = false;\n\n    state: DataTableState = {\n      cellSizeCache: {},\n      moreOptionsColumn: null,\n      showStats: true\n    };\n\n    componentDidMount() {\n      this.hasMounted = true;\n      window.addEventListener('resize', this.scaleCellsToWidth);\n      this.scaleCellsToWidth();\n    }\n\n    componentDidUpdate(prevProps) {\n      if (\n        this.props.cellSizeCache !== prevProps.cellSizeCache ||\n        this.props.pinnedColumns !== prevProps.pinnedColumns\n      ) {\n        this.scaleCellsToWidth();\n      }\n    }\n\n    componentWillUnmount() {\n      this.hasMounted = false;\n      window.removeEventListener('resize', this.scaleCellsToWidth);\n    }\n\n    root = createRef<HTMLDivElement>();\n    columns = (props: DataTableProps) => props.columns;\n    pinnedColumns = (props: DataTableProps) => props.pinnedColumns;\n    unpinnedColumns = createSelector(this.columns, this.pinnedColumns, (columns, pinnedColumns) =>\n      !Array.isArray(pinnedColumns) ? columns : columns.filter(c => !pinnedColumns.includes(c))\n    );\n\n    toggleMoreOptions = moreOptionsColumn => {\n      if (this.hasMounted)\n        this.setState({\n          moreOptionsColumn:\n            this.state.moreOptionsColumn === moreOptionsColumn ? null : moreOptionsColumn\n        });\n    };\n    toggleShowStats = () => {\n      if (this.hasMounted) this.setState({showStats: !this.state.showStats});\n    };\n    getCellSizeCache = () => {\n      const {cellSizeCache: propsCache = {}, fixedWidth, pinnedColumns = []} = this.props;\n      const unpinnedColumns = this.unpinnedColumns(this.props);\n\n      const width = fixedWidth ? fixedWidth : this.root.current ? this.root.current.clientWidth : 0;\n\n      // pin column border is 2 pixel vs 1 pixel\n      const adjustWidth = pinnedColumns.length ? width - 1 : width;\n      const {cellSizeCache, ghost} = adjustCellsToContainer(\n        adjustWidth,\n        propsCache,\n        pinnedColumns,\n        unpinnedColumns\n      ) as {cellSizeCache: CellSizeCache; ghost: number | null | undefined};\n\n      return {\n        cellSizeCache,\n        ghost\n      };\n    };\n\n    doScaleCellsToWidth = () => {\n      if (this.hasMounted) this.setState(this.getCellSizeCache());\n    };\n\n    scaleCellsToWidth = debounce(this.doScaleCellsToWidth, 300);\n\n    renderDataCell = (columns, isPinned, props: DataTableProps) => {\n      const getRowCell = this.props.getRowCell ?? defaultGetRowCell;\n      const DataCellRenderer = cellInfo => {\n        const {columnIndex, key, style, rowIndex} = cellInfo;\n        const {dataContainer, colMeta} = props;\n        const column = columns[columnIndex];\n        const isGhost = column.ghost;\n\n        const formatter = isGhost ? null : getColumnFormatter(colMeta[column]);\n        const rowCell = isGhost ? '' : getRowCell({...props, column, rowIndex}, formatter);\n        const type = isGhost ? null : colMeta[column].type;\n\n        const lastRowIndex = dataContainer ? dataContainer.numRows() - 1 : 0;\n\n        const endCell = columnIndex === columns.length - 1;\n        const firstCell = columnIndex === 0;\n        const bottomCell = rowIndex === lastRowIndex;\n        const alignRight = fieldToAlignRight[Number(type)];\n\n        const cell = (\n          <div\n            className={classnames('cell', {\n              [rowIndex % 2 === 0 ? 'even-row' : 'odd-row']: true,\n              [`row-${rowIndex}`]: true,\n              'pinned-cell': isPinned,\n              'first-cell': firstCell,\n              'end-cell': endCell,\n              'bottom-cell': bottomCell,\n              'align-right': alignRight\n            })}\n            key={key}\n            style={style}\n            title={isGhost ? undefined : rowCell}\n          >\n            {`${rowCell}${endCell ? '\\n' : '\\t'}`}\n          </div>\n        );\n\n        return cell;\n      };\n      return DataCellRenderer;\n    };\n\n    renderHeaderCell(columns, isPinned, props, toggleMoreOptions, moreOptionsColumn) {\n      const HeaderCellRenderer = cellInfo => (\n        <HeaderCell\n          cellInfo={cellInfo}\n          key={cellInfo.columnIndex}\n          columns={columns}\n          isPinned={isPinned}\n          showStats={this.state.showStats}\n          props={props}\n          toggleMoreOptions={toggleMoreOptions}\n          moreOptionsColumn={moreOptionsColumn}\n          // pass dummy style to prevent warnings from react-virtualized Grid\n          style={DUMMY_STYLE}\n        />\n      );\n      return HeaderCellRenderer;\n    }\n    render() {\n      const {\n        dataContainer,\n        pinnedColumns = [],\n        theme = {},\n        fixedWidth,\n        fixedHeight = 0,\n        hasStats,\n        hasCustomScrollBarStyle\n      } = this.props;\n      const unpinnedColumns = this.unpinnedColumns(this.props);\n\n      const {cellSizeCache = {}, moreOptionsColumn, ghost, showStats} = this.state;\n      const unpinnedColumnsGhost = ghost\n        ? [...unpinnedColumns, {ghost: true} as string & {ghost: boolean}]\n        : unpinnedColumns;\n      const pinnedColumnsWidth = pinnedColumns.reduce(\n        (acc, val) => acc + (get(cellSizeCache, val, 0) as number),\n        0\n      );\n\n      const hasPinnedColumns = Boolean(pinnedColumns.length);\n      const {\n        headerRowHeight = defaultHeaderRowHeight,\n        headerStatsControlHeight = defaultHeaderStatsControlHeight,\n        headerRowWStatsHeight = defaultHeaderRowHeight,\n        rowHeight = defaultRowHeight\n      } = theme;\n\n      const headerGridProps = {\n        cellSizeCache,\n        className: 'header-grid',\n        height: !hasStats\n          ? headerRowHeight\n          : showStats\n          ? headerRowWStatsHeight\n          : headerRowHeight + headerStatsControlHeight,\n        rowCount: 1,\n        rowHeight: !hasStats\n          ? headerRowHeight\n          : showStats\n          ? headerRowWStatsHeight\n          : headerRowHeight + headerStatsControlHeight\n      };\n\n      const dataGridProps = {\n        cellSizeCache,\n        overscanColumnCount,\n        overscanRowCount,\n        rowCount: dataContainer ? dataContainer.numRows() : 0,\n        rowHeight\n      };\n\n      return (\n        <Container\n          className=\"data-table-container\"\n          ref={this.root}\n          hasCustomScrollBarStyle={hasCustomScrollBarStyle}\n        >\n          {Object.keys(cellSizeCache).length ? (\n            <>\n              <ScrollSync>\n                {({onScroll, scrollLeft, scrollTop}) => {\n                  return (\n                    <div className=\"results-table-wrapper\">\n                      {hasPinnedColumns && (\n                        <div key=\"pinned-columns\" className=\"pinned-columns grid-row\">\n                          <TableSection\n                            classList={pinnedClassList}\n                            isPinned\n                            columns={pinnedColumns}\n                            headerGridProps={headerGridProps}\n                            fixedWidth={pinnedColumnsWidth}\n                            onScroll={args => onScroll({...args, scrollLeft})}\n                            scrollTop={scrollTop}\n                            scrollLeft={scrollLeft}\n                            dataGridProps={dataGridProps}\n                            setGridRef={pinnedGrid => (this.pinnedGrid = pinnedGrid)}\n                            columnWidth={columnWidthFunction(pinnedColumns, cellSizeCache)}\n                            headerCellRender={this.renderHeaderCell(\n                              pinnedColumns,\n                              true,\n                              this.props,\n                              this.toggleMoreOptions,\n                              moreOptionsColumn\n                            )}\n                            dataCellRender={this.renderDataCell(pinnedColumns, true, this.props)}\n                          />\n                        </div>\n                      )}\n                      <div\n                        key=\"unpinned-columns\"\n                        style={{\n                          marginLeft: `${hasPinnedColumns ? `${pinnedColumnsWidth}px` : '0'}`\n                        }}\n                        className=\"unpinned-columns grid-column\"\n                      >\n                        <TableSection\n                          classList={unpinnedClassList}\n                          isPinned={false}\n                          columns={unpinnedColumnsGhost}\n                          headerGridProps={headerGridProps}\n                          fixedWidth={fixedWidth}\n                          fixedHeight={fixedHeight}\n                          onScroll={onScroll}\n                          scrollTop={scrollTop}\n                          scrollLeft={scrollLeft}\n                          dataGridProps={dataGridProps}\n                          setGridRef={unpinnedGrid => (this.unpinnedGrid = unpinnedGrid)}\n                          columnWidth={columnWidthFunction(\n                            unpinnedColumnsGhost,\n                            cellSizeCache,\n                            ghost\n                          )}\n                          headerCellRender={this.renderHeaderCell(\n                            unpinnedColumnsGhost,\n                            false,\n                            this.props,\n                            this.toggleMoreOptions,\n                            moreOptionsColumn\n                          )}\n                          dataCellRender={this.renderDataCell(\n                            unpinnedColumnsGhost,\n                            false,\n                            this.props\n                          )}\n                        />\n                      </div>\n                    </div>\n                  );\n                }}\n              </ScrollSync>\n              {hasStats ? (\n                <StatsControl\n                  top={headerRowHeight}\n                  showStats={showStats}\n                  toggleShowStats={this.toggleShowStats}\n                />\n              ) : null}\n            </>\n          ) : null}\n        </Container>\n      );\n    }\n  }\n\n  return withTheme(DataTable as React.ComponentType<DataTableProps>);\n}\n\nexport default DataTableFactory;\n"
  },
  {
    "path": "src/components/src/common/data-table/option-dropdown.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled from 'styled-components';\nimport Portaled from '../portaled';\nimport DropdownList from '../item-selector/dropdown-list';\nimport {\n  SORT_ORDER,\n  TABLE_OPTION,\n  TABLE_OPTION_LIST,\n  TooltipFormat,\n  TableOption\n} from '@kepler.gl/constants';\nimport {getFieldFormatLabels} from '@kepler.gl/utils';\nimport {ColMeta} from '@kepler.gl/types';\nimport {ArrowDown, ArrowUp, Clipboard, Pin, Cancel, Hash} from '../icons';\n\nconst ListItem = ({value}) => (\n  <div>\n    <value.icon height=\"13px\" />\n    {value.display}\n  </div>\n);\n\n// make hash icon smaller\nconst StyledOptionsDropdown = styled.div`\n  .list-selector {\n    border-top: 0;\n    width: max-content;\n    padding: 8px 0;\n  }\n\n  .list__item > div {\n    display: flex;\n    align-items: center;\n    flex-direction: row;\n    justify-content: flex-start;\n    line-height: 18px;\n\n    svg {\n      margin-right: 5px;\n    }\n\n    .data-ex-icons-hash {\n      width: 10px;\n      height: 10px;\n      stroke-width: 1px;\n      margin-left: 2px;\n      margin-right: 6px;\n    }\n  }\n`;\n\nconst StyledPopover = styled.div`\n  width: 184px;\n  height: 160px;\n  z-index: 101;\n  .list-selector {\n    max-height: 160px;\n  }\n  .hover:after {\n    content: '\\\\2713';\n    margin-left: 5px;\n  }\n`;\n\nexport type FormatterDropdownProps = {\n  left: number;\n  top: number;\n  isOpened: boolean;\n  displayFormat?: string;\n  setDisplayFormat?: (displayFormat: TooltipFormat) => void;\n  onClose: () => void;\n  formatLabels: TooltipFormat[];\n};\n\nexport const FormatterDropdown: React.FC<FormatterDropdownProps> = (\n  props: FormatterDropdownProps\n) => {\n  const {\n    left,\n    top,\n    isOpened,\n    displayFormat = 'None',\n    setDisplayFormat,\n    onClose,\n    formatLabels\n  } = props;\n  const selectionIndex = formatLabels.findIndex(label => label.format === displayFormat);\n\n  const onSelectDisplayFormat = useCallback(\n    result => {\n      setDisplayFormat?.(result);\n      onClose();\n    },\n    [setDisplayFormat, onClose]\n  );\n\n  return (\n    <Portaled left={left} top={top} isOpened={isOpened} onClose={onClose}>\n      <StyledPopover className=\"formatter-popover\">\n        <DropdownList\n          options={formatLabels}\n          selectionIndex={selectionIndex}\n          displayOption={option => (option as TooltipFormat).label}\n          onOptionSelected={onSelectDisplayFormat}\n          light\n        />\n      </StyledPopover>\n    </Portaled>\n  );\n};\n\nexport interface OptionDropdownProps {\n  isOpened?: boolean;\n  column: string;\n  colMeta: ColMeta;\n  toggleMoreOptions: (column: string) => void;\n  sortTableColumn?: (sort: string) => void;\n  pinTableColumn: () => void;\n  copyTableColumn: () => void;\n  setDisplayFormat?: (displayFormat: any) => void;\n  sortMode?: string;\n  isSorted?: string;\n  isPinned?: boolean;\n}\n\nconst OptionDropdown = (props: OptionDropdownProps) => {\n  const {\n    isOpened,\n    column,\n    colMeta,\n    toggleMoreOptions,\n    sortTableColumn,\n    pinTableColumn,\n    copyTableColumn,\n    setDisplayFormat\n  } = props;\n  const [showFormatter, setShowFormatter] = useState(false);\n  const onOptionSelected: (v: TableOption) => void = useCallback(\n    ({value}) => {\n      switch (value) {\n        case TABLE_OPTION.SORT_ASC:\n          sortTableColumn?.(SORT_ORDER.ASCENDING);\n          break;\n        case TABLE_OPTION.SORT_DES:\n          sortTableColumn?.(SORT_ORDER.DESCENDING);\n          break;\n        case TABLE_OPTION.UNSORT:\n          sortTableColumn?.(SORT_ORDER.UNSORT);\n          break;\n        case TABLE_OPTION.PIN:\n          pinTableColumn();\n          break;\n        case TABLE_OPTION.UNPIN:\n          pinTableColumn();\n          break;\n        case TABLE_OPTION.COPY:\n          copyTableColumn();\n          break;\n        case TABLE_OPTION.FORMAT_COLUMN:\n          setShowFormatter(true);\n          return;\n        default:\n          break;\n      }\n\n      toggleMoreOptions(column);\n    },\n    [column, sortTableColumn, pinTableColumn, copyTableColumn, toggleMoreOptions]\n  );\n\n  const TABLE_OPTION_LIST_ICONS = {\n    Pin,\n    ArrowDown,\n    ArrowUp,\n    Clipboard,\n    Cancel,\n    Hash\n  };\n\n  const formatLabels = getFieldFormatLabels(colMeta[column].type);\n  const options = TABLE_OPTION_LIST.filter(op => {\n    // cant use conditions because it creates a circular dependency\n    // TODO: move condition clause out of default-settings TABLE_OPTION_LIST\n    const validToFormat = op.value !== TABLE_OPTION.FORMAT_COLUMN || formatLabels.length;\n    return (!op.condition || op.condition(props)) && validToFormat;\n  }).map(op => ({\n    ...op,\n    icon: TABLE_OPTION_LIST_ICONS[op.icon]\n  }));\n\n  const onClose = useCallback(() => {\n    setShowFormatter(false);\n    toggleMoreOptions(column);\n  }, [column, toggleMoreOptions]);\n\n  return (\n    <Portaled right={120} top={20} isOpened={isOpened} onClose={onClose}>\n      <StyledOptionsDropdown className=\"more-options\">\n        <DropdownList\n          displayOption={d => (d as TableOption).display}\n          options={options}\n          customListItemComponent={ListItem}\n          onOptionSelected={onOptionSelected}\n          light\n        />\n        <FormatterDropdown\n          left={120}\n          top={-10}\n          isOpened={Boolean(isOpened && showFormatter)}\n          formatLabels={formatLabels}\n          displayFormat={colMeta[column]?.displayFormat}\n          setDisplayFormat={setDisplayFormat}\n          onClose={onClose}\n        />\n      </StyledOptionsDropdown>\n    </Portaled>\n  );\n};\n\nexport default OptionDropdown;\n"
  },
  {
    "path": "src/components/src/common/dataset-label.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {CenterFlexbox, DatasetSquare} from './styled-components';\nimport {RGBColor} from '@kepler.gl/types';\n\nconst DatasetName = styled.div.attrs({\n  className: 'dataset-name'\n})`\n  font-weight: 500;\n  font-size: 12px;\n  color: ${props => props.theme.titleColorLT};\n  white-space: nowrap;\n`;\n\ninterface DatasetLabelType {\n  dataset: {\n    color: RGBColor;\n    label: string;\n  };\n}\n\nconst DatasetLabel = ({dataset}: DatasetLabelType) => (\n  <CenterFlexbox>\n    <DatasetSquare className=\"dataset-color\" backgroundColor={dataset.color} />\n    <DatasetName>{dataset.label}</DatasetName>\n  </CenterFlexbox>\n);\n\nexport default DatasetLabel;\n"
  },
  {
    "path": "src/components/src/common/dnd-layer-items.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Modifiers} from '@dnd-kit/core';\nimport {restrictToVerticalAxis} from '@dnd-kit/modifiers';\n\nexport const DND_MODIFIERS: Modifiers = [restrictToVerticalAxis];\nexport const DND_EMPTY_MODIFIERS: Modifiers = [];\nexport const SORTABLE_SIDE_PANEL_TYPE = 'root';\nexport const DROPPABLE_MAP_CONTAINER_TYPE = 'map';\nexport const SORTABLE_LAYER_TYPE = 'layer';\n\nexport const SORTABLE_EFFECT_PANEL_TYPE = 'root';\nexport const SORTABLE_EFFECT_TYPE = 'effect';\n"
  },
  {
    "path": "src/components/src/common/error-boundary.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ErrorInfo} from 'react';\nimport console from 'global/console';\n\ninterface ErrorBoundaryProps {\n  children?: React.ReactNode;\n}\nexport default class ErrorBoundary extends React.Component<ErrorBoundaryProps> {\n  static getDerivedStateFromError(error) {\n    // Update state so the next render will show the fallback UI.\n    return {hasError: true, error};\n  }\n  state = {hasError: false};\n  componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n    // You can also log the error to an error reporting service\n    console.error(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      // You can render any custom fallback UI\n      return <h1>Something went wrong.</h1>;\n    }\n\n    return this.props.children;\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/field-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport React, {Component, ComponentType} from 'react';\nimport styled from 'styled-components';\nimport {createSelector} from 'reselect';\n\nimport {Field, TooltipField} from '@kepler.gl/types';\nimport {notNullorUndefined, toArray} from '@kepler.gl/common-utils';\n\nimport ItemSelector from './item-selector/item-selector';\nimport {classList} from './item-selector/dropdown-list';\nimport FieldTokenFactory from '../common/field-token';\n\nconst defaultDisplayOption = (d: Field) => d.displayName || d.name;\nconst defaultValueOption = (d: Field) => d.name;\n\nconst StyledToken = styled.div`\n  display: inline-block;\n  margin: 0 ${props => props.theme.fieldTokenRightMargin}px 0 0;\n`;\nconst StyledFieldListItem = styled.div`\n  line-height: 0;\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n`;\nconst StyledFieldSelector = styled.div`\n  .item-selector__dropdown {\n    // smaller padding on the side to accomodate field token\n    padding: 0 6px;\n  }\n`;\nexport type FieldListItemFactoryProps = {\n  value: Field;\n  displayOption: (field: Field) => string;\n};\n\nFieldListItemFactoryFactory.deps = [FieldTokenFactory];\n// custom list Item\nexport function FieldListItemFactoryFactory(FieldToken) {\n  const FieldListItemFactory = (showToken = true) => {\n    const FieldListItem = ({\n      value,\n      displayOption = defaultDisplayOption\n    }: FieldListItemFactoryProps) => (\n      <StyledFieldListItem className=\"field-selector_list-item\">\n        {showToken ? (\n          <StyledToken>\n            <FieldToken type={value.type} />\n          </StyledToken>\n        ) : null}\n        <span className={classList.listItemAnchor}>{displayOption(value)}</span>\n      </StyledFieldListItem>\n    );\n    return FieldListItem;\n  };\n  return FieldListItemFactory;\n}\n\nconst SuggestedFieldHeader = () => <div>Suggested Field</div>;\n\nexport type MinimalField = {name: string; displayName: string; format: string; type?: string};\nexport type FieldType = string | TooltipField | Field;\nexport type FieldValue = string | {name: string} | string[] | {name: string}[];\n\nexport type FieldSelectorProps<Option extends MinimalField> = {\n  id?: string;\n  fields: Option[];\n  onSelect: (\n    items:\n      | ReadonlyArray<string | number | boolean | object>\n      | string\n      | number\n      | boolean\n      | object\n      | null\n  ) => void;\n  value?: FieldValue | null;\n  filterFieldTypes?: string | string[];\n  inputTheme?: string;\n  placement?: string;\n  placeholder?: string;\n  erasable?: boolean;\n  disabled?: boolean;\n  error?: boolean;\n  multiSelect?: boolean;\n  closeOnSelect?: boolean;\n  showToken?: boolean;\n  suggested?: Option[] | null;\n  CustomChickletComponent?: ComponentType<any>;\n  size?: string;\n  reorderItems?: (newOrder: any) => void;\n  className?: string;\n};\n\nfunction noop() {\n  return;\n}\nfunction FieldSelectorFactory(\n  FieldListItemFactory: ReturnType<typeof FieldListItemFactoryFactory>\n): ComponentType<FieldSelectorProps<MinimalField>> {\n  class FieldSelector extends Component<FieldSelectorProps<MinimalField>> {\n    static defaultProps = {\n      erasable: true,\n      disabled: false,\n      error: false,\n      fields: [],\n      onSelect: noop,\n      reorderItems: noop,\n      placement: 'bottom',\n      value: null,\n      multiSelect: false,\n      closeOnSelect: true,\n      showToken: true,\n      placeholder: 'placeholder.selectField',\n      className: ''\n    };\n\n    fieldsSelector = props => props.fields;\n    valueSelector = props => props.value;\n    filteredFieldsSelector = createSelector(\n      this.fieldsSelector,\n      this.valueSelector,\n      (fields, value) => {\n        return fields.filter(\n          field => !toArray(value).find(d => (d.name ? d.name === field.name : d === field.name))\n        );\n      }\n    );\n    filterFieldTypesSelector = props => props.filterFieldTypes;\n    showTokenSelector = props => props.showToken;\n\n    selectedItemsSelector = createSelector(\n      this.fieldsSelector,\n      this.valueSelector,\n      (fields, value) =>\n        toArray(value)\n          .map(d =>\n            fields.find(f =>\n              notNullorUndefined(d) && d.name\n                ? d.name === defaultValueOption(f)\n                : d === defaultValueOption(f)\n            )\n          )\n          .filter(d => d)\n    );\n\n    fieldOptionsSelector = createSelector(\n      this.filteredFieldsSelector,\n      this.filterFieldTypesSelector,\n      (fields, filterFieldTypes) => {\n        if (!filterFieldTypes) {\n          return fields;\n        }\n        const filters = Array.isArray(filterFieldTypes) ? filterFieldTypes : [filterFieldTypes];\n        return fields.filter(f => filters.includes(f.type));\n      }\n    );\n\n    // @ts-ignore Fix later\n    fieldListItemSelector = createSelector(this.showTokenSelector, FieldListItemFactory);\n\n    render() {\n      return (\n        <StyledFieldSelector className={classnames('field-selector', this.props.className)}>\n          <ItemSelector\n            getOptionValue={d => d}\n            disabled={this.props.disabled}\n            closeOnSelect={this.props.closeOnSelect}\n            displayOption={defaultDisplayOption}\n            filterOption=\"displayName\"\n            fixedOptions={this.props.suggested}\n            inputTheme={this.props.inputTheme}\n            size={this.props.size}\n            isError={this.props.error}\n            selectedItems={this.selectedItemsSelector(this.props)}\n            erasable={this.props.erasable}\n            // @ts-ignore - Argument of type 'Readonly<FieldSelectorFactoryProps>' is not assignable to parameter of type 'never'\n            options={this.fieldOptionsSelector(this.props)}\n            multiSelect={this.props.multiSelect}\n            placeholder={this.props.placeholder}\n            placement={this.props.placement}\n            onChange={this.props.onSelect}\n            reorderItems={this.props.reorderItems}\n            DropDownLineItemRenderComponent={this.fieldListItemSelector(this.props)}\n            DropdownHeaderComponent={this.props.suggested ? SuggestedFieldHeader : null}\n            CustomChickletComponent={this.props.CustomChickletComponent}\n          />\n        </StyledFieldSelector>\n      );\n    }\n  }\n\n  // @ts-ignore: Fix me\n  return FieldSelector;\n}\n\nFieldSelectorFactory.deps = [FieldListItemFactoryFactory];\nexport default FieldSelectorFactory;\n"
  },
  {
    "path": "src/components/src/common/field-token.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {FIELD_TYPE_DISPLAY, FIELD_COLORS} from '@kepler.gl/constants';\n\nexport type FieldTokenProps = {\n  type: string;\n};\nconst FieldTag = styled.div`\n  background-color: rgba(${props => props.color}, 0.2);\n  border-radius: 2px;\n  border: 1px solid rgb(${props => props.color});\n  color: rgb(${props => props.color});\n  display: inline-block;\n  font-size: 10px;\n  font-weight: 400;\n  padding: 0 5px;\n  text-align: center;\n  width: ${props => props.theme.fieldTokenWidth}px;\n  line-height: ${props => props.theme.fieldTokenHeight}px;\n`;\n\nfunction FieldTokenFactory(\n  fieldTypeDisplay: ReturnType<typeof getFieldTypes>,\n  fieldColors: ReturnType<typeof getFieldColors>\n): React.FC<FieldTokenProps> {\n  const FieldToken = ({type}: FieldTokenProps) => (\n    <FieldTag color={fieldTypeDisplay[type]?.color || fieldColors.default}>\n      {fieldTypeDisplay[type]?.label}\n    </FieldTag>\n  );\n  return FieldToken;\n}\n\nfunction getFieldTypes() {\n  return FIELD_TYPE_DISPLAY;\n}\n\nfunction getFieldColors() {\n  return FIELD_COLORS;\n}\nFieldTokenFactory.deps = [getFieldTypes, getFieldColors];\nexport default FieldTokenFactory;\n"
  },
  {
    "path": "src/components/src/common/file-uploader/file-drop.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Copied from https://github.com/sarink/react-file-drop\n * For React 16.8 compatibility\n */\nimport React, {ReactNode, useCallback, useEffect, useState, useRef} from 'react';\nimport Window from 'global/window';\n\nconst isIE = () =>\n  Window &&\n  Window.navigator &&\n  ((Window.navigator.userAgent || []).includes('MSIE') ||\n    (Window.navigator.appVersion || []).includes('Trident/'));\n\nconst eventHasFiles = event => {\n  // In most browsers this is an array, but in IE11 it's an Object :(\n\n  let hasFiles = false;\n  if (event.dataTransfer) {\n    const types = event.dataTransfer.types;\n    for (const keyOrIndex in types) {\n      if (types[keyOrIndex] === 'Files') {\n        hasFiles = true;\n        break;\n      }\n    }\n  }\n  return hasFiles;\n};\n\nexport type FileDropProps = {\n  dropEffect?: 'copy' | 'move' | 'link' | 'none';\n  frame?: typeof document | typeof Window | HTMLElement;\n  className?: string;\n  targetClassName?: string;\n  draggingOverFrameClassName?: string;\n  draggingOverTargetClassName?: string;\n  onDragOver?: (event: any) => void;\n  onDragLeave?: (event: any) => void;\n  onDrop?: (fileList: FileList, event: any) => void;\n  onFrameDragEnter?: (event: any) => void;\n  onFrameDragLeave?: (event: any) => void;\n  onFrameDrop?: (event: any) => void;\n  children?: ReactNode;\n};\n\nconst FileDrop = ({\n  dropEffect = 'copy',\n  frame = Window ? Window.document : undefined,\n  className = 'file-drop',\n  targetClassName = 'file-drop-target',\n  draggingOverFrameClassName = 'file-drop-dragging-over-frame',\n  draggingOverTargetClassName = 'file-drop-dragging-over-target',\n  onDragOver,\n  onDragLeave,\n  onDrop,\n  onFrameDragEnter,\n  onFrameDragLeave,\n  onFrameDrop,\n  children\n}: FileDropProps) => {\n  const [draggingOverTarget, setDraggingOverTarget] = useState(false);\n  const [draggingOverFrame, setDraggingOverFrame] = useState(false);\n  const [frameDragCounter, setFrameDragCounter] = useState(0);\n\n  const prevFrame = useRef(frame);\n\n  useEffect(() => {\n    // componentDidMount\n    startFrameListeners(frame);\n    resetDragging();\n    Window.addEventListener('dragover', handleWindowDragOverOrDrop);\n    Window.addEventListener('drop', handleWindowDragOverOrDrop);\n\n    return () => {\n      // componentWillUnmount\n      stopFrameListeners(frame);\n      Window.removeEventListener('dragover', handleWindowDragOverOrDrop);\n      Window.removeEventListener('drop', handleWindowDragOverOrDrop);\n    };\n\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  const resetDragging = useCallback(() => {\n    setFrameDragCounter(0);\n    setDraggingOverTarget(false);\n    setDraggingOverFrame(false);\n  }, []);\n\n  const handleWindowDragOverOrDrop = useCallback(event => {\n    // This prevents the browser from trying to load whatever file the user dropped on the window\n    event.preventDefault();\n  }, []);\n\n  const handleFrameDrag = useCallback(\n    event => {\n      // Only allow dragging of files\n      if (!eventHasFiles(event)) return;\n\n      // We are listening for events on the 'frame', so every time the user drags over any element in the frame's tree,\n      // the event bubbles up to the frame. By keeping count of how many \"dragenters\" we get, we can tell if they are still\n      // \"draggingOverFrame\" (b/c you get one \"dragenter\" initially, and one \"dragenter\"/one \"dragleave\" for every bubble)\n      // This is far better than a \"dragover\" handler, which would be calling `setState` continuously.\n      const newDragCounterValue = frameDragCounter + (event.type === 'dragenter' ? 1 : -1);\n      setFrameDragCounter(newDragCounterValue);\n\n      if (newDragCounterValue === 1) {\n        setDraggingOverFrame(true);\n        if (onFrameDragEnter) onFrameDragEnter(event);\n        return;\n      }\n\n      if (newDragCounterValue === 0) {\n        setDraggingOverFrame(false);\n        if (onFrameDragLeave) onFrameDragLeave(event);\n        return;\n      }\n    },\n    [frameDragCounter, setDraggingOverFrame, onFrameDragEnter, onFrameDragLeave]\n  );\n\n  const handleFrameDrop = useCallback(\n    event => {\n      event.preventDefault();\n      if (!draggingOverTarget) {\n        resetDragging();\n        if (onFrameDrop) onFrameDrop(event);\n      }\n    },\n    [onFrameDrop, draggingOverTarget, resetDragging]\n  );\n\n  const handleDragOver = useCallback(\n    event => {\n      if (eventHasFiles(event)) {\n        setDraggingOverTarget(true);\n        if (!isIE() && dropEffect) event.dataTransfer.dropEffect = dropEffect;\n        if (onDragOver) onDragOver(event);\n      }\n    },\n    [dropEffect, onDragOver]\n  );\n\n  const handleDragLeave = useCallback(\n    event => {\n      setDraggingOverTarget(false);\n\n      if (onDragLeave) onDragLeave(event);\n    },\n    [onDragLeave]\n  );\n\n  const handleDrop = useCallback(\n    event => {\n      if (onDrop && eventHasFiles(event)) {\n        const files = event.dataTransfer ? event.dataTransfer.files : null;\n        onDrop(files, event);\n      }\n      resetDragging();\n    },\n    [onDrop, resetDragging]\n  );\n\n  const stopFrameListeners = useCallback(\n    frame => {\n      if (frame) {\n        frame.removeEventListener('dragenter', handleFrameDrag);\n        frame.removeEventListener('dragleave', handleFrameDrag);\n        frame.removeEventListener('drop', handleFrameDrop);\n      }\n    },\n    [handleFrameDrag, handleFrameDrop]\n  );\n\n  const startFrameListeners = useCallback(\n    frame => {\n      if (frame) {\n        frame.addEventListener('dragenter', handleFrameDrag);\n        frame.addEventListener('dragleave', handleFrameDrag);\n        frame.addEventListener('drop', handleFrameDrop);\n      }\n    },\n    [handleFrameDrag, handleFrameDrop]\n  );\n\n  useEffect(() => {\n    // componentDidUpdate\n    if (prevFrame.current !== frame) {\n      resetDragging();\n      stopFrameListeners(prevFrame.current);\n      startFrameListeners(frame);\n\n      prevFrame.current = frame;\n    }\n  }, [frame, resetDragging, stopFrameListeners, startFrameListeners]);\n\n  // Render\n  let fileDropTargetClassName = targetClassName;\n  if (draggingOverFrame) fileDropTargetClassName += ` ${draggingOverFrameClassName}`;\n  if (draggingOverTarget) fileDropTargetClassName += ` ${draggingOverTargetClassName}`;\n\n  return (\n    <div\n      className={className}\n      onDragOver={handleDragOver}\n      onDragLeave={handleDragLeave}\n      onDrop={handleDrop}\n    >\n      <div className={fileDropTargetClassName}>{children}</div>\n    </div>\n  );\n};\n\nexport default FileDrop;\n"
  },
  {
    "path": "src/components/src/common/file-uploader/file-upload-progress.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport classnames from 'classnames';\nimport ProgressBar from '../progress-bar';\nimport {TruncatedTitleText} from '../styled-components';\nimport {getError} from '@kepler.gl/utils';\nimport {FileLoadingProgress} from '@kepler.gl/types';\n\nconst StyledFileProgress = styled.div.attrs(props => ({\n  className: classnames('file-upload__progress', props.className)\n}))`\n  color: ${props => props.theme.textColorLT};\n  font-size: 12px;\n  margin-top: 12px;\n  border-image: initial;\n  padding: 8px 12px;\n\n  .top-row {\n    display: flex;\n    justify-content: space-between;\n  }\n\n  .file-name {\n    font-weight: 500;\n  }\n  .middle-row {\n    margin-top: 6px;\n  }\n  .bottom-row {\n    margin-top: 6px;\n    text-align: start;\n  }\n`;\n\nconst StyledProgressWrapper = styled.div.attrs({\n  className: 'file-upload__progress__wrapper'\n})`\n  width: 400px;\n`;\n\nconst StyledContainer = styled.div`\n  width: 100%;\n  display: flex;\n  justify-content: center;\n`;\n\nconst formatPercent = percent => `${Math.floor(percent * 100)}%`;\n\ninterface UploadProgressProps {\n  message?: string;\n  fileName?: string;\n  percent: number;\n  error?: any;\n  theme: any;\n}\n\n/**\n * @param {object} params\n * @param {string} params.message\n * @param {string} params.fileName\n * @param {number} params.percent\n * @param {any} params.error\n * @param {object} params.theme\n */\nconst UploadProgress = ({message, fileName, percent, error, theme}: UploadProgressProps) => {\n  const percentStr = formatPercent(percent);\n  const barColor = error ? theme.errorColor : theme.activeColorLT;\n\n  return (\n    <StyledFileProgress className=\"file-upload-progress__message\">\n      <div className=\"top-row\">\n        <TruncatedTitleText className=\"file-name\" title={fileName}>\n          {fileName}\n        </TruncatedTitleText>\n        <div className=\"percent\">{percentStr}</div>\n      </div>\n      <div className=\"middle-row\">\n        <ProgressBar percent={percentStr} barColor={barColor} isLoading theme={theme} />\n      </div>\n      <div className=\"bottom-row\" style={{color: error ? theme.errorColor : theme.textColorLT}}>\n        {error ? getError(error) : message}\n      </div>\n    </StyledFileProgress>\n  );\n};\n\ntype FileUploadProgressProps = {\n  fileLoadingProgress: FileLoadingProgress;\n  theme: any;\n};\n\nconst FileUploadProgress: React.FC<FileUploadProgressProps> = ({\n  fileLoadingProgress,\n  theme\n}: FileUploadProgressProps) => (\n  <StyledContainer>\n    <StyledProgressWrapper>\n      {Object.values(fileLoadingProgress).map(item => (\n        <UploadProgress {...item} key={item.fileName} theme={theme} />\n      ))}\n    </StyledProgressWrapper>\n  </StyledContainer>\n);\n\nexport default withTheme(FileUploadProgress) as React.FC<FileUploadProgressProps>;\n"
  },
  {
    "path": "src/components/src/common/file-uploader/file-upload.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef} from 'react';\nimport styled from 'styled-components';\nimport {injectIntl, WrappedComponentProps} from 'react-intl';\nimport UploadButton from './upload-button';\nimport {DragNDrop, FileType} from '../icons';\nimport FileUploadProgress from './file-upload-progress';\nimport FileDrop from './file-drop';\nimport {FileLoading, FileLoadingProgress} from '@kepler.gl/types';\n\nimport {isChrome} from '@kepler.gl/utils';\nimport {GUIDES_FILE_FORMAT_DOC} from '@kepler.gl/constants';\nimport Markdown from 'markdown-to-jsx';\n// Breakpoints\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {media} from '@kepler.gl/styles';\n\nimport LinkRenderer from '../link-renderer';\n\nconst fileIconColor = '#D3D8E0';\n\nconst StyledUploadMessage = styled.div`\n  color: ${props => props.theme.textColorLT};\n  font-size: 14px;\n  margin-bottom: 12px;\n\n  ${media.portable`\n    font-size: 12px;\n  `};\n`;\n\nexport const WarningMsg = styled.span`\n  margin-top: 10px;\n  color: ${props => props.theme.errorColor};\n  font-weight: 500;\n`;\n\ninterface StyledFileDropProps {\n  dragOver?: boolean;\n}\n\nconst StyledFileDrop = styled.div<StyledFileDropProps>`\n  background-color: white;\n  border-radius: 4px;\n  border-style: ${props => (props.dragOver ? 'solid' : 'dashed')};\n  border-width: 1px;\n  border-color: ${props => (props.dragOver ? props.theme.textColorLT : props.theme.subtextColorLT)};\n  text-align: center;\n  width: 100%;\n  padding: 48px 8px 0;\n  height: 360px;\n\n  .file-upload-or {\n    color: ${props => props.theme.linkBtnColor};\n    padding-right: 4px;\n  }\n\n  .file-type-row {\n    opacity: 0.5;\n  }\n  ${media.portable`\n    padding: 16px 4px 0;\n  `};\n`;\n\nconst MsgWrapper = styled.div`\n  color: ${props => props.theme.modalTitleColor};\n  font-size: 20px;\n  height: 36px;\n`;\n\nconst StyledDragNDropIcon = styled.div`\n  color: ${fileIconColor};\n  margin-bottom: 48px;\n\n  display: flex;\n  justify-content: center;\n\n  ${media.portable`\n    margin-bottom: 16px;\n  `};\n  ${media.palm`\n    margin-bottom: 8px;\n  `};\n`;\n\nconst StyledFileTypeFow = styled.div`\n  margin-bottom: 24px;\n  ${media.portable`\n    margin-bottom: 16px;\n  `};\n  ${media.palm`\n    margin-bottom: 8px;\n  `};\n`;\n\nconst StyledFileUpload = styled.div`\n  .file-drop {\n    position: relative;\n  }\n`;\n\nconst StyledMessage = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  margin-bottom: 32px;\n\n  .loading-action {\n    margin-right: 10px;\n  }\n  .loading-spinner {\n    margin-left: 10px;\n  }\n`;\n\nconst StyledDragFileWrapper = styled.div`\n  margin-bottom: 32px;\n  ${media.portable`\n    margin-bottom: 24px;\n  `};\n  ${media.portable`\n    margin-bottom: 16px;\n  `};\n`;\n\nconst StyledDisclaimer = styled(StyledMessage)`\n  margin: 0 auto;\n`;\n\ntype FileUploadProps = {\n  onFileUpload: (files: File[]) => void;\n  fileLoading: FileLoading | false;\n  fileLoadingProgress: FileLoadingProgress;\n  theme: object;\n  /** A list of names of supported formats suitable to present to user */\n  fileFormatNames?: string[];\n  /** A list of typically 3 letter extensions (without '.') for file matching */\n  fileExtensions?: string[];\n  /** Set to true if app wants to do its own file filtering */\n  disableExtensionFilter?: boolean;\n} & WrappedComponentProps;\n\nfunction FileUploadFactory() {\n  /** @augments {Component<FileUploadProps>} */\n  class FileUpload extends Component<FileUploadProps> {\n    state = {\n      dragOver: false,\n      fileLoading: false,\n      files: [],\n      errorFiles: []\n    };\n\n    static getDerivedStateFromProps(props, state) {\n      if (state.fileLoading && props.fileLoading === false && state.files.length) {\n        return {\n          files: [],\n          fileLoading: props.fileLoading\n        };\n      }\n      return {\n        fileLoading: props.fileLoading\n      };\n    }\n\n    frame = createRef<HTMLDivElement>();\n\n    _isValidFileType = filename => {\n      const {fileExtensions = []} = this.props;\n      const fileExt = fileExtensions.find(ext => filename.endsWith(ext));\n\n      return Boolean(fileExt);\n    };\n\n    /** @param {FileList} fileList */\n    _handleFileInput = (fileList: FileList, event: any) => {\n      if (event) {\n        event.stopPropagation();\n      }\n\n      const files = [...fileList].filter(Boolean);\n\n      const {disableExtensionFilter = false} = this.props;\n\n      // TODO - move this code out of the component\n      const filesToLoad: File[] = [];\n      const errorFiles: string[] = [];\n      for (const file of files) {\n        if (disableExtensionFilter || this._isValidFileType(file.name)) {\n          filesToLoad.push(file);\n        } else {\n          errorFiles.push(file.name);\n        }\n      }\n\n      const nextState = {files: filesToLoad, errorFiles, dragOver: false};\n\n      this.setState(nextState, () =>\n        nextState.files.length ? this.props.onFileUpload(nextState.files) : null\n      );\n    };\n\n    _toggleDragState = newState => {\n      this.setState({dragOver: newState});\n    };\n\n    render() {\n      const {dragOver, files, errorFiles} = this.state;\n      const {fileLoading, fileLoadingProgress, theme, intl} = this.props;\n      const {fileExtensions = [], fileFormatNames = []} = this.props;\n      const fileUploadInfoText = `${intl.formatMessage(\n        {\n          id: 'fileUploader.configUploadMessage'\n        },\n        {\n          fileFormatNames: fileFormatNames.map(format => `**${format}**`).join(', ')\n        }\n      )}(${GUIDES_FILE_FORMAT_DOC}).`;\n      return (\n        <StyledFileUpload className=\"file-uploader\" ref={this.frame}>\n          {FileDrop ? (\n            <FileDrop\n              frame={this.frame.current || document}\n              onDragOver={() => this._toggleDragState(true)}\n              onDragLeave={() => this._toggleDragState(false)}\n              onDrop={this._handleFileInput}\n              className=\"file-uploader__file-drop\"\n            >\n              <StyledUploadMessage className=\"file-upload__message\">\n                <Markdown\n                  options={{\n                    overrides: {\n                      a: {\n                        component: LinkRenderer\n                      }\n                    }\n                  }}\n                >\n                  {fileUploadInfoText}\n                </Markdown>\n              </StyledUploadMessage>\n              <StyledFileDrop dragOver={dragOver}>\n                <StyledFileTypeFow className=\"file-type-row\">\n                  {fileExtensions.map(ext => (\n                    <FileType key={ext} ext={ext} height=\"50px\" fontSize=\"9px\" />\n                  ))}\n                </StyledFileTypeFow>\n                {fileLoading ? (\n                  <FileUploadProgress fileLoadingProgress={fileLoadingProgress} theme={theme} />\n                ) : (\n                  <>\n                    <div\n                      style={{opacity: dragOver ? 0.5 : 1}}\n                      className=\"file-upload-display-message\"\n                    >\n                      <StyledDragNDropIcon>\n                        <DragNDrop height=\"44px\" />\n                      </StyledDragNDropIcon>\n\n                      {errorFiles.length ? (\n                        <WarningMsg>\n                          <FormattedMessage\n                            id={'fileUploader.fileNotSupported'}\n                            values={{errorFiles: errorFiles.join(', ')}}\n                          />\n                        </WarningMsg>\n                      ) : null}\n                    </div>\n                    {!files.length ? (\n                      <StyledDragFileWrapper>\n                        <MsgWrapper>\n                          <FormattedMessage id={'fileUploader.message'} />\n                        </MsgWrapper>\n                        <span className=\"file-upload-or\">\n                          <FormattedMessage id={'fileUploader.or'} />\n                        </span>\n                        <UploadButton onUpload={this._handleFileInput}>\n                          <FormattedMessage id={'fileUploader.browseFiles'} />\n                        </UploadButton>\n                      </StyledDragFileWrapper>\n                    ) : null}\n\n                    <StyledDisclaimer>\n                      <FormattedMessage id={'fileUploader.disclaimer'} />\n                    </StyledDisclaimer>\n                  </>\n                )}\n              </StyledFileDrop>\n            </FileDrop>\n          ) : null}\n\n          <WarningMsg>\n            {isChrome() ? <FormattedMessage id={'fileUploader.chromeMessage'} /> : ''}\n          </WarningMsg>\n        </StyledFileUpload>\n      );\n    }\n  }\n\n  return injectIntl(FileUpload);\n}\n\nexport default FileUploadFactory;\nexport const FileUpload = FileUploadFactory();\n"
  },
  {
    "path": "src/components/src/common/file-uploader/upload-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useRef} from 'react';\nimport styled from 'styled-components';\n\nconst Wrapper = styled.div`\n  display: inline-block;\n  color: ${props => props.theme.textColorLT};\n  font-size: 12px;\n  text-decoration: underline;\n\n  &:hover {\n    cursor: pointer;\n    font-weight: 500;\n  }\n`;\nconst inputStyle = {display: 'none'};\n\ninterface UploadButtonProps {\n  onUpload: (files: FileList, event: any) => void;\n  children?: React.ReactNode;\n}\n\n/*\nInspired by https://github.com/okonet/react-dropzone/blob/master/src/index.js\n*/\n/** @type {typeof import('./upload-button').UploadButton} */\nconst UploadButton: React.FC<UploadButtonProps> = ({onUpload, children}) => {\n  const _fileInput = useRef(null);\n\n  const _onClick = useCallback(() => {\n    if (_fileInput.current) {\n      // @ts-ignore create ref with useRef<HTMLInputElement>\n      _fileInput.current.value = null;\n      // @ts-ignore create ref with useRef<HTMLInputElement>\n      _fileInput.current.click();\n    }\n  }, [_fileInput]);\n\n  const _onChange = useCallback(\n    event => {\n      const {\n        target: {files}\n      } = event;\n\n      if (!files) {\n        return;\n      }\n\n      onUpload(files, event);\n    },\n    [onUpload]\n  );\n\n  return (\n    <Wrapper className=\"upload-button\">\n      <input\n        type=\"file\"\n        ref={_fileInput}\n        style={inputStyle}\n        onChange={_onChange}\n        className=\"upload-button-input\"\n      />\n      <span className=\"file-upload__upload-button-span\" onClick={_onClick}>\n        {children}\n      </span>\n    </Wrapper>\n  );\n};\n\nexport default UploadButton;\n"
  },
  {
    "path": "src/components/src/common/flex-container.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\n\nexport const FlexContainer = styled.div`\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n`;\n\nexport const FlexColContainer = styled(FlexContainer)`\n  flex-direction: column;\n`;\n\nexport const FlexContainerGrow = styled(FlexContainer)`\n  flex-grow: 1;\n`;\n"
  },
  {
    "path": "src/components/src/common/histogram-plot.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ReactElement, useMemo} from 'react';\nimport {scaleLinear} from 'd3-scale';\nimport {hcl} from 'd3-color';\nimport {min, max} from 'd3-array';\nimport styled from 'styled-components';\n\nimport {Bins} from '@kepler.gl/types';\n\n// clipping mask constants\nexport const HISTOGRAM_MASK_MODE = {\n  NoMask: 0,\n  Mask: 1,\n  MaskWithOverlay: 2\n};\nconst HISTOGRAM_MASK_BGCOLOR = '#FFFFFF';\nconst HISTOGRAM_MASK_FGCOLOR = '#000000';\n\nconst histogramStyle = {\n  highlightW: 0.7,\n  unHighlightedW: 0.4\n};\n\nconst HistogramWrapper = styled.svg`\n  overflow: visible;\n`;\n\nconst HistogramMaskRect = styled.rect`\n  fill: ${props => props.theme.panelBackground};\n`;\n\nconst HistogramBreakLine = styled.g`\n  stroke: ${props => props.theme.histogramBreakLineColor};\n  stroke-width: 1px;\n  transform: translate(0, 0);\n`;\n\ntype BarType = {\n  $inRange: boolean;\n  $isOverlay: boolean;\n  $color?: string;\n};\nconst BarUnmemoized = styled.rect<BarType>(\n  ({theme, $inRange, $isOverlay, $color}) => `\n  ${\n    $isOverlay\n      ? `fill: ${$color ?? theme.histogramOverlayColor};`\n      : $inRange\n      ? `fill: ${$color ?? theme.histogramFillInRange};`\n      : `fill: ${$color ? hcl($color).darker() : theme.histogramFillOutRange};`\n  }\n`\n);\nconst Bar = React.memo(BarUnmemoized);\nBar.displayName = 'Bar';\n\nconst isBarInRange = (\n  bar: {x0: number; x1: number},\n  index: number,\n  list: any[],\n  filterDomain: any[],\n  filterValue: any[]\n) => {\n  // first\n  // if x0 <= domain[0] and current value[0] wasn't changed from the original domain\n  const x0Condition =\n    index === 0 ? bar.x0 <= filterDomain[0] && filterDomain[0] === filterValue[0] : false;\n  // Last\n  // if x1 >= domain[1] and current value[1] wasn't changed from the original domain\n  const x1Condition =\n    index === list.length - 1\n      ? bar.x1 >= filterDomain[1] && filterDomain[1] === filterValue[1]\n      : false;\n  return (x0Condition || bar.x0 >= filterValue[0]) && (x1Condition || bar.x1 <= filterValue[1]);\n};\n\nexport type HistogramMaskModeType = {\n  NoMask: number;\n  Mask: number;\n  MaskWithOverlay: number;\n};\n\ninterface HistogramPlotProps {\n  width: number;\n  height: number;\n  margin: {top: number; bottom: number; left: number; right: number};\n  isRanged?: boolean;\n  value: number[];\n  isMasked?: number;\n  brushComponent?: ReactElement;\n  histogramsByGroup: Bins;\n  colorsByGroup?: null | {[dataId: string]: string};\n  countProp?: string;\n  range?: number[];\n  breakLines?: number[];\n}\n\nfunction HistogramPlotFactory() {\n  const HistogramPlot = ({\n    width,\n    height,\n    histogramsByGroup,\n    colorsByGroup,\n    isMasked = HISTOGRAM_MASK_MODE.NoMask,\n    countProp = 'count',\n    margin,\n    isRanged,\n    range,\n    value,\n    brushComponent,\n    breakLines\n  }: HistogramPlotProps) => {\n    const undefinedToZero = (x: number | undefined) => (x ? x : 0);\n    const groupKeys = useMemo(\n      () =>\n        Object.keys(histogramsByGroup)\n          // only keep non-empty groups\n          .filter(key => histogramsByGroup[key]?.length > 0),\n      [histogramsByGroup]\n    );\n\n    const domain = useMemo(\n      () =>\n        range ?? [\n          min(groupKeys, key => histogramsByGroup[key][0].x0) ?? 0,\n          max(groupKeys, key => histogramsByGroup[key][histogramsByGroup[key].length - 1].x1) ?? 0\n        ],\n      [range, histogramsByGroup, groupKeys]\n    );\n\n    const barWidth = useMemo(() => {\n      if (groupKeys.length === 0) return 0;\n      // find histogramsByGroup with max number of bins\n      const maxGroup = groupKeys.reduce((accu, key, _idx) => {\n        if (histogramsByGroup[key].length > accu.length) {\n          return histogramsByGroup[key];\n        }\n        return accu;\n      }, histogramsByGroup[groupKeys[0]]);\n\n      // find the bin for measuring step\n      const stdBinIdx = maxGroup.length > 1 ? 1 : 0;\n      const xStep = maxGroup[stdBinIdx].x1 - maxGroup[stdBinIdx].x0;\n      const maxBins = (domain[1] - domain[0]) / xStep;\n      if (!maxBins) return 0;\n      return width / maxBins / (isMasked ? 1 : groupKeys.length);\n    }, [histogramsByGroup, domain, groupKeys, width, isMasked]);\n\n    const x = useMemo(\n      () => scaleLinear().domain(domain).range([barWidth, width]),\n      [domain, width, barWidth]\n    );\n\n    const y = useMemo(\n      () =>\n        scaleLinear()\n          .domain([\n            0,\n            Math.max(\n              Number(max(groupKeys, key => max(histogramsByGroup[key], d => d[countProp]))),\n              1\n            )\n          ])\n          .range([0, height]),\n      [histogramsByGroup, groupKeys, height, countProp]\n    );\n\n    if (groupKeys.length === 0) {\n      return null;\n    }\n\n    const maskedHistogram = () => {\n      return (\n        <HistogramWrapper\n          width={width}\n          height={height}\n          style={{margin: `${margin.top}px ${margin.right}px ${margin.bottom}px ${margin.left}px`}}\n        >\n          <defs>\n            <mask id=\"histogram-mask\">\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width={width}\n                height={height + margin.bottom}\n                fill={HISTOGRAM_MASK_BGCOLOR}\n              />\n              <g key=\"filtered-bins\" className=\"histogram-bars\">\n                {histogramsByGroup.filteredBins.map((bar, idx, list) => {\n                  const inRange = isBarInRange(bar, idx, list, domain, value);\n                  const wRatio = inRange\n                    ? histogramStyle.highlightW\n                    : histogramStyle.unHighlightedW;\n                  return (\n                    <Bar\n                      $isOverlay={false}\n                      $inRange={inRange}\n                      $color={HISTOGRAM_MASK_FGCOLOR}\n                      key={`mask-${idx}`}\n                      height={y(bar[countProp])}\n                      width={barWidth * wRatio}\n                      x={x(bar.x0) + (barWidth * (1 - wRatio)) / 2}\n                      y={height - y(bar[countProp])}\n                    />\n                  );\n                })}\n              </g>\n            </mask>\n          </defs>\n          <g transform=\"translate(0,0)\">\n            <HistogramMaskRect\n              x=\"0\"\n              y=\"0\"\n              width=\"100%\"\n              height={height + margin.bottom}\n              mask=\"url(#histogram-mask)\"\n            />\n          </g>\n          {isMasked === HISTOGRAM_MASK_MODE.MaskWithOverlay && (\n            <g key=\"bins\" transform=\"translate(0,0)\" className=\"overlay-histogram-bars\">\n              {histogramsByGroup.bins.map((bar, idx, list) => {\n                const filterBar = histogramsByGroup.filteredBins[idx];\n                const maskHeight = filterBar\n                  ? y(bar[countProp]) - y(filterBar[countProp])\n                  : y(bar[countProp]);\n                const inRange = isBarInRange(bar, idx, list, domain, value);\n                const wRatio = inRange ? histogramStyle.highlightW : histogramStyle.unHighlightedW;\n                return (\n                  <Bar\n                    $inRange={inRange}\n                    $isOverlay={true}\n                    key={`bar-${idx}`}\n                    height={maskHeight}\n                    width={barWidth * wRatio}\n                    x={x(bar.x0) + (barWidth * (1 - wRatio)) / 2}\n                    y={height - y(bar[countProp])}\n                  />\n                );\n              })}\n            </g>\n          )}\n          <HistogramBreakLine>\n            {(breakLines ?? []).map((pos, idx) => {\n              return (\n                <path key={`mask-line-${idx}`} strokeDasharray=\"4,4\" d={`M${pos}, 0 l0 100`} />\n              );\n            })}\n          </HistogramBreakLine>\n          <g transform={`translate(${isRanged ? 0 : barWidth / 2}, 0)`}>{brushComponent}</g>\n        </HistogramWrapper>\n      );\n    };\n\n    return isMasked ? (\n      maskedHistogram()\n    ) : (\n      <HistogramWrapper\n        width={width}\n        height={height}\n        style={{margin: `${margin.top}px ${margin.right}px ${margin.bottom}px ${margin.left}px`}}\n      >\n        <g>\n          {groupKeys.map((key, i) => (\n            <g key={key} className=\"histogram-bars\">\n              {histogramsByGroup[key].map((bar, idx, list) => {\n                const inRange = isBarInRange(bar, idx, list, domain, value);\n\n                const wRatio = inRange ? histogramStyle.highlightW : histogramStyle.unHighlightedW;\n                const startX =\n                  x(undefinedToZero(bar.x0)) + barWidth * i + (barWidth * (1 - wRatio)) / 2;\n                if (startX > 0 && startX + barWidth * histogramStyle.unHighlightedW <= width) {\n                  return (\n                    <Bar\n                      $isOverlay={false}\n                      $inRange={inRange}\n                      $color={colorsByGroup?.[key]}\n                      key={`bar-${idx}`}\n                      height={y(bar[countProp])}\n                      width={barWidth * wRatio}\n                      x={startX}\n                      rx={1}\n                      ry={1}\n                      y={height - y(bar[countProp])}\n                    />\n                  );\n                }\n                return null;\n              })}\n            </g>\n          ))}\n        </g>\n        <g transform={`translate(${isRanged ? 0 : barWidth / 2}, 0)`}>{brushComponent}</g>\n      </HistogramWrapper>\n    );\n  };\n\n  const HistogramPlotWithGroups = props => {\n    const groups = useMemo(\n      () => (props.histogramsByGroup ? Object.keys(props.histogramsByGroup) : null),\n      [props.histogramsByGroup]\n    );\n\n    if (!groups?.length) {\n      return null;\n    }\n\n    return <HistogramPlot {...props} />;\n  };\n\n  return HistogramPlotWithGroups;\n}\nexport default HistogramPlotFactory;\n"
  },
  {
    "path": "src/components/src/common/icon-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {MouseEvent, ReactNode} from 'react';\nimport styled from 'styled-components';\nimport {Button, ButtonProps} from './styled-components';\n\ninterface IconButtonProps extends ButtonProps {\n  collapsed?: boolean;\n  theme?: object;\n  className?: string;\n  onClick?: (event: MouseEvent<HTMLButtonElement>) => void;\n  children?: ReactNode;\n}\n\nexport const IconButton = styled(Button)<IconButtonProps>`\n  width: ${props => (props.collapsed ? 0 : 32)}px;\n  height: 32px;\n  color: ${props => props.theme.playbackButtonColor};\n  background-color: ${props => props.theme.playbackButtonBgColor};\n  border-radius: 4px;\n  margin-left: 7px;\n  border: 0;\n  padding: 0;\n\n  .__react_component_tooltip {\n    font-family: ${props => props.theme.fontFamily};\n  }\n  svg {\n    margin: 0;\n  }\n  &.active {\n    background-color: ${props => props.theme.playbackButtonActBgColor};\n  }\n`;\n\nexport default IconButton;\n"
  },
  {
    "path": "src/components/src/common/icons/add.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Add extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-add'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M35.93,28.57V9.89a1,1,0,0,0-1-1h-5.9a1,1,0,0,0-1,1V28.57H9.39a1,1,0,0,0-1,1v5.9a1,1,0,0,0,1,1H28.07V55.11a1,1,0,0,0,1,1h5.9a1,1,0,0,0,1-1V36.43H54.61a1,1,0,0,0,1-1v-5.9a1,1,0,0,0-1-1Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/anchor_window.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nconst AnchorWindow = (props: Partial<BaseProps>) => (\n  <Base {...props}>\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M2.73645 4.67861C2.73645 4.40246 2.96031 4.17861 3.23645 4.17861H11.5177C11.7938 4.17861 12.0177 4.40246 12.0177 4.67861V11.3214C12.0177 11.5975 11.7938 11.8214 11.5177 11.8214H3.23645C2.96031 11.8214 2.73645 11.5975 2.73645 11.3214V4.67861ZM3.73645 5.17861V10.8214H11.0177V5.17861H3.73645Z\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M0.5 3.66954C0.776142 3.66954 1 3.8934 1 4.16954L1 11.8304C1 12.1066 0.776143 12.3304 0.5 12.3304C0.223858 12.3304 3.68794e-07 12.1066 3.56723e-07 11.8304L2.18557e-08 4.16954C9.78513e-09 3.8934 0.223858 3.66954 0.5 3.66954Z\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M13.7039 5.74894C13.9227 5.58041 14.2366 5.62113 14.4051 5.83988L15.8342 7.69486C15.9727 7.8747 15.9727 8.1253 15.8342 8.30514L14.4051 10.1601C14.2366 10.3789 13.9227 10.4196 13.7039 10.2511C13.4852 10.0825 13.4444 9.76858 13.613 9.54983L14.8069 8L13.613 6.45017C13.4444 6.23142 13.4852 5.91746 13.7039 5.74894Z\"\n    />\n  </Base>\n);\n\nAnchorWindow.defaultProps = {\n  height: '16px',\n  viewBox: '0 0 16 16',\n  predefinedClassName: 'data-ex-icons-anchorwindow'\n};\nexport default AnchorWindow;\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-down-alt.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowDownAlt extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrow_down_alt'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M30.9874294,23.8822323 L21.4022168,35.7397323 L21.4022168,3.80585729 C21.4022168,2.11829479 20.0731075,0.750419792 18.4333707,0.750419792 C16.7936338,0.750419792 15.4645245,2.11829479 15.4645245,3.80585729 L15.4645245,35.7397323 L5.87742937,23.8822323 C4.82882614,22.5802323 2.95282411,22.4077948 1.69713585,23.4869823 C0.433917229,24.5661698 0.26260144,26.4920448 1.31308727,27.789201 L16.1516703,46.1479823 C16.7155063,46.846451 17.552318,47.2504198 18.4333707,47.2504198 C19.3144233,47.2504198 20.151235,46.846451 20.7150711,46.1479823 L35.556478,27.789201 C36.6041399,26.4920448 36.4309415,24.5661698 35.1724294,23.4869823 C33.9007391,22.4077948 32.0407391,22.5802323 30.9874294,23.8822323 Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-down-full.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class ArrowDown extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    style: PropTypes.any\n  };\n\n  static defaultProps = {\n    height: '8px',\n    width: '8px',\n    predefinedClassName: 'data-ex-icons-arrowdown-full',\n    viewBox: '0 0 8 4'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M4 4L0 0H8L4 4Z\" fill=\"currentColor\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-down-small.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class ArrowDownSmall extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-arrow-down-small'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M12.4419 5.55807C12.1979 5.314 11.8021 5.314 11.5581 5.55807L8 9.11613L4.44194 5.55807C4.19786 5.314 3.80214 5.314 3.55806 5.55807C3.31398 5.80215 3.31398 6.19788 3.55806 6.44196L7.55806 10.442C7.67527 10.5592 7.83424 10.625 8 10.625C8.16576 10.625 8.32473 10.5592 8.44194 10.442L12.4419 6.44196C12.686 6.19788 12.686 5.80215 12.4419 5.55807Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-down-solid.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowDownSolid extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrowdown-solid'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon points=\"6.4,20.8 56.4,20.8 31.4,45.8 \" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-down.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowDown extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-arrowdown'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        style={{fill: 'none', stroke: 'currentColor'}}\n      >\n        <path d=\"m6 9 6 6 6-6\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-left.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowLeft extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrowleft'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g\n          id=\"arrow-left\"\n          transform=\"translate(32.000000, 31.500000) scale(-1, 1) translate(-32.000000, -31.500000) translate(17.000000, 9.000000)\"\n        >\n          <path d=\"M5.7,44.7 L1.2,40.3 C0.8,39.9 0.8,39.3 1.2,38.9 L17.6,23 L1.2,7 C0.8,6.6 0.8,6 1.2,5.5 L5.7,1.1 C6.1,0.7 6.8,0.7 7.2,1.1 L24.3,17.8 L28.8,22.2 C29.2,22.6 29.2,23.2 28.8,23.6 L24.2,28 L7.2,44.7 C6.8,45.1 6.1,45.1 5.7,44.7\" />\n        </g>{' '}\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-right.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowRight extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrowright'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M26.7,54.7l-4.5-4.4c-0.4-0.4-0.4-1,0-1.4L38.6,33L22.2,17c-0.4-0.4-0.4-1,0-1.5l4.5-4.4c0.4-0.4,1.1-0.4,1.5,0\n\tl17.1,16.7l4.5,4.4c0.4,0.4,0.4,1,0,1.4L45.2,38L28.2,54.7C27.8,55.1,27.1,55.1,26.7,54.7\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-up-alt.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowUpAlt extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrow_up_alt'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M35.1724294,23.4869823 C33.9007391,22.4077948 32.0407391,22.5802323 30.9874294,23.8822323 L21.4022168,35.7397323 L21.4022168,3.80585729 C21.4022168,2.11829479 20.0731075,0.750419792 18.4333707,0.750419792 C16.7936338,0.750419792 15.4645245,2.11829479 15.4645245,3.80585729 L15.4645245,35.7397323 L5.87742937,23.8822323 C4.82882614,22.5802323 2.95282411,22.4077948 1.69713585,23.4869823 C0.433917229,24.5661698 0.26260144,26.4920448 1.31308727,27.789201 L16.1516703,46.1479823 C16.7155063,46.846451 17.552318,47.2504198 18.4333707,47.2504198 C19.3144233,47.2504198 20.151235,46.846451 20.7150711,46.1479823 L35.556478,27.789201 C36.6041399,26.4920448 36.4309415,24.5661698 35.1724294,23.4869823\"\n          transform=\"rotate(-180.000000)\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-up-solid.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowUpSolid extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrowup-solid'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon points=\"32.3,20 57.3,45 7.3,45 \" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/arrow-up.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ArrowUp extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-arrow_up'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M50.9,46.7c-0.4,0.4-1.1,0.4-1.6,0L32,29.3L14.6,46.7c-0.4,0.4-1.1,0.4-1.6,0l-4.7-4.7c-0.4-0.4-0.4-1.1,0-1.6l22.9-22.9\n\tc0.4-0.4,1.1-0.4,1.6,0l22.9,22.9c0.4,0.4,0.4,1.1,0,1.6L50.9,46.7z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/base-map.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nclass BaseMap extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-basemap',\n    viewBox: '0 0 24 24',\n    totalColor: 1\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentColor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <g>\n          <path d=\"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z\" />\n          <path d=\"M15 5.764v15\" />\n          <path d=\"M9 3.236v15\" />\n        </g>\n      </Base>\n    );\n  }\n}\n\nexport default BaseMap;\n"
  },
  {
    "path": "src/components/src/common/icons/base.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, CSSProperties} from 'react';\n\nconst getStyleClassFromColor = (totalColor: number, colors: string[]) =>\n  new Array(totalColor)\n    .fill(1)\n    .reduce((accu, c, i) => `${accu}.cr${i + 1} {fill:${colors[i % colors.length]};}`, '');\n\nconst nop = () => {\n  return;\n};\n\nexport type BaseProps = {\n  /** Set the height of the icon, ex. '16px' */\n  height?: string;\n  /** Set the width of the icon, ex. '16px' */\n  width?: string;\n  /** Set the viewbox of the svg */\n  viewBox?: string;\n  /** Path element */\n\n  predefinedClassName?: string;\n  className?: string;\n  style?: CSSProperties;\n  colors?: string[];\n  totalColor?: number;\n} & React.SVGAttributes<SVGSVGElement> &\n  React.DOMAttributes<SVGSVGElement>;\n\nexport default class Base extends Component<BaseProps> {\n  static displayName = 'Base Icon';\n\n  static defaultProps = {\n    height: null,\n    width: null,\n    viewBox: '0 0 64 64',\n    predefinedClassName: '',\n    className: '',\n    style: {\n      fill: 'currentColor'\n    }\n  };\n\n  render() {\n    const {\n      height,\n      width,\n      viewBox,\n      style,\n      children,\n      predefinedClassName,\n      className,\n      colors,\n      totalColor,\n      ...props\n    } = this.props;\n    const svgHeight = height;\n    const svgWidth = width || svgHeight;\n\n    const fillStyle =\n      Array.isArray(colors) && totalColor && getStyleClassFromColor(totalColor, colors);\n\n    return (\n      <svg\n        viewBox={viewBox}\n        width={svgWidth}\n        height={svgHeight}\n        style={style}\n        className={`${predefinedClassName} ${className}`}\n        onClick={nop}\n        {...props}\n      >\n        {fillStyle ? <style type=\"text/css\">{fillStyle}</style> : null}\n        {children}\n      </svg>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/bug.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Bug extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-bug'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"m8 2 1.88 1.88\" />\n        <path d=\"M14.12 3.88 16 2\" />\n        <path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\" />\n        <path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\" />\n        <path d=\"M12 20v-9\" />\n        <path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\" />\n        <path d=\"M6 13H2\" />\n        <path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\" />\n        <path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\" />\n        <path d=\"M22 13h-4\" />\n        <path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/calendar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Calendar extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-calendar'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M11.2537 3H13.5C14.0523 3 14.5 3.44772 14.5 4V13C14.5 13.5523 14.0523 14 13.5 14H2.5C1.94772 14 1.5 13.5523 1.5 13V4C1.5 3.44772 1.94772 3 2.5 3H4.73697V2.5C4.73697 2.22386 4.96082 2 5.23697 2C5.51311 2 5.73697 2.22386 5.73697 2.5V3H10.2537V2.5C10.2537 2.22386 10.4776 2 10.7537 2C11.0299 2 11.2537 2.22386 11.2537 2.5V3ZM2.5 13H13.5V7H2.5V13ZM13.5 6H2.5V4H4.7V4.45C4.7 4.75376 4.94624 5 5.25 5C5.55376 5 5.8 4.75376 5.8 4.45V4H10.2V4.45C10.2 4.75376 10.4462 5 10.75 5C11.0538 5 11.3 4.75376 11.3 4.45V4H13.5V6Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/cancel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Cancel extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-cancel'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M16.7240288,20.6999691 C14.3839567,23.8581781 13,27.7674688 13,32 C13,42.4934102 21.5065898,51 32,51 C36.2325312,51 40.1418219,49.6160433 43.3000309,47.2759712 L16.7240288,20.6999691 Z M20.2014265,17.106299 L46.893701,43.7985735 C49.4645783,40.5576433 51,36.4581301 51,32 C51,21.5065898 42.4934102,13 32,13 C27.5418699,13 23.4423567,14.5354217 20.2014265,17.106299 Z M32,56 C18.745166,56 8,45.254834 8,32 C8,18.745166 18.745166,8 32,8 C45.254834,8 56,18.745166 56,32 C56,45.254834 45.254834,56 32,56 Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/checkmark.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Checkmark extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-checkmark',\n    stroke: '#FFF'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 42 42\" {...this.props}>\n        <path d=\"M16,0C7.163,0,0,7.163,0,16c0,8.837,7.163,16,16,16c8.836,0,16-7.164,16-16C32,7.163,24.836,0,16,0z M16,30   C8.268,30,2,23.732,2,16C2,8.268,8.268,2,16,2s14,6.268,14,14C30,23.732,23.732,30,16,30z\" />\n        <path d=\"M23.3,10.393L13.012,20.589l-4.281-4.196c-0.394-0.391-1.034-0.391-1.428,0   c-0.395,0.391-0.395,1.024,0,1.414l4.999,4.899c0.41,0.361,1.023,0.401,1.428,0l10.999-10.899c0.394-0.39,0.394-1.024,0-1.414   C24.334,10.003,23.695,10.003,23.3,10.393z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/clipboard.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Clipboard extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-clipboard'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          transform=\"translate(6.000000, 0.000000)\"\n          d=\"M46.8184484,5.81818182 L34.5871288,5.81818182 C33.3581445,2.44363636 30.1393762,7.10542736e-15 26.3353772,7.10542736e-15 C22.5313783,7.10542736e-15 19.31261,2.44363636 18.0836257,5.81818182 L5.85230605,5.81818182 C2.63353772,5.81818182 0,8.43636364 0,11.6363636 L0,58.1818182 C0,61.3818182 2.63353772,64 5.85230605,64 L46.8184484,64 C50.0372167,64 52.6707545,61.3818182 52.6707545,58.1818182 L52.6707545,11.6363636 C52.6707545,8.43636364 50.0372167,5.81818182 46.8184484,5.81818182 L46.8184484,5.81818182 Z M26.3353772,5.81818182 C27.9447614,5.81818182 29.2615303,7.12727273 29.2615303,8.72727273 C29.2615303,10.3272727 27.9447614,11.6363636 26.3353772,11.6363636 C24.7259931,11.6363636 23.4092242,10.3272727 23.4092242,8.72727273 C23.4092242,7.12727273 24.7259931,5.81818182 26.3353772,5.81818182 L26.3353772,5.81818182 Z M46.8184484,58.1818182 L5.85230605,58.1818182 L5.85230605,11.6363636 L11.7046121,11.6363636 L11.7046121,20.3636364 L40.9661424,20.3636364 L40.9661424,11.6363636 L46.8184484,11.6363636 L46.8184484,58.1818182 L46.8184484,58.1818182 Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/clock.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Clock extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-clock'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M41.68,41.16l-11.36-5a3,3,0,0,1-1.74-3L29.86,19h3l1.48,13.29,8.86,5.91Z\" />\n        <path d=\"M32.21,11.7A20.06,20.06,0,1,1,12.15,31.77,20.09,20.09,0,0,1,32.21,11.7m0-3.65A23.71,23.71,0,1,0,55.92,31.77,23.71,23.71,0,0,0,32.21,8.06Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/close.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Close extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-closewindow'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(32,32)\">\n          <path d=\"M-16,-16 L16,16 M16,-16 L-16,16\" stroke=\"currentColor\" strokeWidth=\"5\" strokeLinecap=\"round\" fill=\"none\" />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/cloud.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Layers extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-layers'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/code-alt.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class CodeAlt extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-codealt'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <path d=\"M27.768 13.97v4.803a1.2 1.2 0 0 1-1.201 1.2H22.964v8.407a3.603 3.603 0 0 1-3.602 3.603 3.603 3.603 0 0 1 3.602 3.603v8.406H26.567a1.2 1.2 0 0 1 1.2 1.201v4.804a1.2 1.2 0 0 1-1.2 1.2h-6.005a4.804 4.804 0 0 1-4.803-4.803v-7.206a3.603 3.603 0 0 0-3.603-3.602H9.754a1.196 1.196 0 0 1-1.19-1.176h-.01v-4.829c0-.663.537-1.2 1.2-1.2h2.402a3.603 3.603 0 0 0 3.603-3.604v-7.205a4.804 4.804 0 0 1 4.803-4.804h6.005a1.2 1.2 0 0 1 1.2 1.201v.002zm27.584 21.616h-2.401a3.603 3.603 0 0 0-3.603 3.602v7.206a4.804 4.804 0 0 1-4.804 4.804H38.54a1.2 1.2 0 0 1-1.201-1.201V45.193c0-.663.537-1.2 1.2-1.2H42.143v-8.407a3.603 3.603 0 0 1 3.603-3.603 3.603 3.603 0 0 1-3.603-3.603v-8.406H38.54a1.2 1.2 0 0 1-1.201-1.201v-4.804c0-.663.537-1.2 1.2-1.2h6.005a4.804 4.804 0 0 1 4.804 4.803v7.205a3.603 3.603 0 0 0 3.603 3.603h2.401c.654 0 1.175.526 1.19 1.176h.011v4.829a1.2 1.2 0 0 1-1.2 1.2z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/copy.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Copy extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '14px',\n    viewBox: '0 0 14 14',\n    predefinedClassName: 'data-ex-icons-copy',\n    style: {\n      fill: 'none',\n      stroke: 'currentColor'\n    }\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          d=\"M11.8002 4.98792H6.4002C5.73745 4.98792 5.2002 5.52517 5.2002 6.18792V11.5879C5.2002 12.2507 5.73745 12.7879 6.4002 12.7879H11.8002C12.4629 12.7879 13.0002 12.2507 13.0002 11.5879V6.18792C13.0002 5.52517 12.4629 4.98792 11.8002 4.98792Z\"\n        />\n        <path\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          d=\"M2.8 8.58796H2.2C1.88174 8.58796 1.57652 8.46154 1.35147 8.23649C1.12643 8.01145 1 7.70622 1 7.38796V1.98796C1 1.6697 1.12643 1.36448 1.35147 1.13944C1.57652 0.914392 1.88174 0.787964 2.2 0.787964H7.6C7.91826 0.787964 8.22348 0.914392 8.44853 1.13944C8.67357 1.36448 8.8 1.6697 8.8 1.98796V2.58796\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/crosshairs.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Crosshairs extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-crosshairs'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <path d=\"M60.015 30h-4.12c-.961-11.648-10.237-20.932-21.88-21.908V4h-4v4.087C18.343 9.037 9.038 18.332 8.075 30h-4.06v4h4.06c.963 11.668 10.268 20.964 21.94 21.913V60h4v-4.092c11.643-.976 20.919-10.26 21.88-21.908h4.12v-4zm-8.131 0H39.723a8 8 0 0 0-5.708-5.73V12.103c9.42.954 16.928 8.473 17.869 17.897zm-21.87-17.9v12.155A7.999 7.999 0 0 0 24.248 30H12.086c.942-9.444 8.48-16.972 17.929-17.9zM12.087 34h12.161a7.999 7.999 0 0 0 5.768 5.745V51.9c-9.448-.928-16.987-8.456-17.93-17.9zm21.929 17.897V39.73A8 8 0 0 0 39.723 34h12.16c-.94 9.424-8.448 16.943-17.868 17.897z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/cube-3d.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Cube3D extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-layers'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\" />\n        <path d=\"m3.3 7 8.7 5 8.7-5\" />\n        <path d=\"M12 22V12\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/cursor-click.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class CursorClick extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-cursorclick'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"scale(1.2, 1.2) translate(0, 2)\">\n          <polygon points=\"22.5,11.1 27.6,43.9 35.3,37.3 43,49 48.8,45 41,33.2 49,28.3 \" />\n          <path\n            d=\"M21.2,27.8C14.5,26.6,9.8,20.7,9.8,14c0-7.7,6.3-14,14-14s14,6.3,14,14c0,0.8-0.1,1.5-0.2,2.3l-2.5-0.4\n\tc0.1-0.6,0.2-1.3,0.2-1.8c0-6.4-5.2-11.5-11.5-11.5S12.3,7.7,12.3,14c0,5.5,3.9,10.3,9.4,11.4L21.2,27.8z\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/cursor-point.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base, {BaseProps} from './base';\n\nexport default class CursorPoint extends Component<Partial<BaseProps>> {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-cursorpoint'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"scale(4, 4)\">\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M1.11516 0.69784C0.869317 0.556367 0.562398 0.57724 0.337968 0.750693C0.113538 0.924147 0.0159658 1.21589 0.0908917 1.48946L3.32832 13.31C3.40524 13.5909 3.648 13.7948 3.93792 13.8221C4.22784 13.8493 4.50435 13.6943 4.63228 13.4327L6.3441 9.93235L9.41359 13.9039C9.65 14.2098 10.0896 14.2661 10.3955 14.0297C10.7014 13.7933 10.7577 13.3537 10.5213 13.0478L7.35388 8.94949L11.5277 8.10342C11.8131 8.04556 12.0329 7.81707 12.0796 7.52964C12.1263 7.24222 11.9902 6.95589 11.7378 6.81065L1.11516 0.69784ZM4.18896 11.1525L1.89017 2.75907L9.43295 7.09958L6.15023 7.76501C5.93703 7.80823 5.75604 7.94811 5.66047 8.14353L4.18896 11.1525Z\"\n            fill=\"#54638C\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/data-table.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class DataTable extends Component<Partial<BaseProps>> {\n  static displayName = 'DataTable';\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-datatable'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n        <line x1=\"3\" x2=\"21\" y1=\"9\" y2=\"9\" />\n        <line x1=\"3\" x2=\"21\" y1=\"15\" y2=\"15\" />\n        <line x1=\"9\" x2=\"9\" y1=\"9\" y2=\"21\" />\n        <line x1=\"15\" x2=\"15\" y1=\"9\" y2=\"21\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/db.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Db extends Component<Partial<BaseProps>> {\n  static displayName = 'Db';\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-db'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\" />\n        <path d=\"M3 5V19A9 3 0 0 0 21 19V5\" />\n        <path d=\"M3 12A9 3 0 0 0 21 12\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/delete.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Delete extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-delete'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M18 6 6 18\" />\n        <path d=\"m6 6 12 12\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/docs.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Docs extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-docs'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M23.62,23.41a1,1,0,0,1,.39.08,1,1,0,0,0-.78,0A1,1,0,0,1,23.62,23.41Z\" />\n        <path d=\"M32,57.5A24.83,24.83,0,1,1,56.83,32.67,24.86,24.86,0,0,1,32,57.5Zm0-44.86a20,20,0,1,0,20,20A20,20,0,0,0,32,12.64Z\" />\n        <rect x=\"28.8\" y=\"29.46\" width=\"6.41\" height=\"16.02\" rx=\"1.6\" ry=\"1.6\" />\n        <rect x=\"28.8\" y=\"19.85\" width=\"6.41\" height=\"6.41\" rx=\"1.6\" ry=\"1.6\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/docs2.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Docs2 extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-docs2'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/drag-n-drop.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class DragNDrop extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-dragndrop'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M53.11,56.13H10.89A3.11,3.11,0,0,1,7.79,53V31.92a1.86,1.86,0,0,1,3.72,0V52.4h41V31.92a1.86,1.86,0,0,1,3.72,0V53A3.11,3.11,0,0,1,53.11,56.13Z\" />\n        <path d=\"M33.86,33l8-8a1.86,1.86,0,1,1,2.63,2.63L33.32,38.82a1.86,1.86,0,0,1-2.63,0L19.51,27.64A1.86,1.86,0,0,1,22.14,25l8,8V11.43a1.86,1.86,0,0,1,3.72,0Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/draggable-dots.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class DraggableDots extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-draggabledots'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <circle cx=\"14\" cy=\"5.5\" r=\"1\" transform=\"rotate(90 14 5.5)\" />\n        <circle cx=\"14\" cy=\"10.5\" r=\"1\" transform=\"rotate(90 14 10.5)\" />\n        <circle cx=\"10\" cy=\"5.5\" r=\"1\" transform=\"rotate(90 10 5.5)\" />\n        <circle cx=\"10\" cy=\"10.5\" r=\"1\" transform=\"rotate(90 10 10.5)\" />\n        <circle cx=\"6\" cy=\"5.5\" r=\"1\" transform=\"rotate(90 6 5.5)\" />\n        <circle cx=\"6\" cy=\"10.5\" r=\"1\" transform=\"rotate(90 6 10.5)\" />\n        <circle cx=\"2\" cy=\"5.5\" r=\"1\" transform=\"rotate(90 2 5.5)\" />\n        <circle cx=\"2\" cy=\"10.5\" r=\"1\" transform=\"rotate(90 2 10.5)\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/draw-polygon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class DrawPolygon extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-draw-polygon',\n    viewBox: '0 0 24 25'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M5 6L13 2L22 9L21 23L2 17L5 6Z\"\n          stroke=\"currentColor\"\n          fill=\"transparent\"\n          strokeWidth=\"1.5\"\n        />\n        <path d=\"M10 11C10.5523 11 11 10.5523 11 10C11 9.44772 10.5523 9 10 9C9.44772 9 9 9.44772 9 10C9 10.5523 9.44772 11 10 11Z\" />\n        <path d=\"M11.5 16C12.3284 16 13 15.3284 13 14.5C13 13.6716 12.3284 13 11.5 13C10.6716 13 10 13.6716 10 14.5C10 15.3284 10.6716 16 11.5 16Z\" />\n        <path d=\"M15.5 12C16.3284 12 17 11.3284 17 10.5C17 9.67157 16.3284 9 15.5 9C14.6716 9 14 9.67157 14 10.5C14 11.3284 14.6716 12 15.5 12Z\" />\n        <path d=\"M22 11C23.1046 11 24 10.1046 24 9C24 7.89543 23.1046 7 22 7C20.8954 7 20 7.89543 20 9C20 10.1046 20.8954 11 22 11Z\" />\n        <path d=\"M21 25C22.1046 25 23 24.1046 23 23C23 21.8954 22.1046 21 21 21C19.8954 21 19 21.8954 19 23C19 24.1046 19.8954 25 21 25Z\" />\n        <path d=\"M2 19C3.10457 19 4 18.1046 4 17C4 15.8954 3.10457 15 2 15C0.89543 15 0 15.8954 0 17C0 18.1046 0.89543 19 2 19Z\" />\n        <path d=\"M13 4C14.1046 4 15 3.10457 15 2C15 0.89543 14.1046 0 13 0C11.8954 0 11 0.89543 11 2C11 3.10457 11.8954 4 13 4Z\" />\n        <path d=\"M5 8C6.10457 8 7 7.10457 7 6C7 4.89543 6.10457 4 5 4C3.89543 4 3 4.89543 3 6C3 7.10457 3.89543 8 5 8Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/edit.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport PropTypes from 'prop-types';\nimport React, {Component} from 'react';\nimport Base from './base';\n\nexport default class Edit extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-edit'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M11.8155 2.78902C12.0005 2.60397 12.2515 2.5 12.5132 2.5C12.6428 2.5 12.7711 2.52552 12.8908 2.57512C13.0106 2.62471 13.1194 2.69739 13.211 2.78902C13.3026 2.88066 13.3753 2.98944 13.4249 3.10916C13.4745 3.22889 13.5 3.3572 13.5 3.48679C13.5 3.61638 13.4745 3.7447 13.4249 3.86442C13.3753 3.98414 13.3026 4.09293 13.211 4.18456L4.54791 12.8476L2.68719 13.3128L3.15237 11.4521L11.8155 2.78902ZM12.5132 1.5C11.9863 1.5 11.4809 1.70932 11.1083 2.08192L2.34734 10.8429C2.28326 10.907 2.2378 10.9873 2.21582 11.0752L1.51494 13.8787C1.47234 14.0491 1.52227 14.2294 1.64646 14.3536C1.77065 14.4777 1.95089 14.5277 2.12128 14.4851L4.9248 13.7842C5.01272 13.7622 5.09301 13.7168 5.15709 13.6527L13.9181 4.89167C14.1026 4.70718 14.2489 4.48815 14.3488 4.2471C14.4486 4.00606 14.5 3.7477 14.5 3.48679C14.5 3.22588 14.4486 2.96753 14.3488 2.72648C14.2489 2.48543 14.1026 2.26641 13.9181 2.08192C13.7336 1.89743 13.5146 1.75108 13.2735 1.65124C13.0325 1.55139 12.7741 1.5 12.5132 1.5ZM8 13.5C7.72386 13.5 7.5 13.7239 7.5 14C7.5 14.2761 7.72386 14.5 8 14.5H14C14.2761 14.5 14.5 14.2761 14.5 14C14.5 13.7239 14.2761 13.5 14 13.5H8Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/brightness-contrast.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class BrightnessContrast extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-brightness-contrast'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M8.381 2.28577H7.6191V3.42862H8.381V2.28577Z\" />\n          <path d=\"M13.7143 7.6191H12.5715V8.381H13.7143V7.6191Z\" />\n          <path d=\"M7.6191 12.5715H8.381V13.7143H7.6191V12.5715Z\" />\n          <path d=\"M3.42862 7.6191H2.28577V8.381H3.42862V7.6191Z\" />\n          <path d=\"M3.63645 4.22943L4.22137 3.6445L5.09876 4.52189L4.51383 5.10682L3.63645 4.22943Z\" />\n          <path d=\"M11.7725 3.64043L10.8951 4.51782L11.48 5.10274L12.3574 4.22535L11.7725 3.64043Z\" />\n          <path d=\"M10.8963 11.4812L11.4812 10.8963L12.3586 11.7737L11.7736 12.3586L10.8963 11.4812Z\" />\n          <path d=\"M3.42862 11.9645L4.34377 11.0477L4.95243 11.6589L4.03728 12.5715L3.42862 11.9645Z\" />\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M6.09524 5.1493C6.65907 4.77256 7.32195 4.57148 8.00005 4.57148C8.90937 4.57148 9.78144 4.9327 10.4244 5.57569C11.0674 6.21867 11.4286 7.09074 11.4286 8.00005C11.4286 8.67816 11.2275 9.34104 10.8508 9.90486C10.4741 10.4687 9.9386 10.9081 9.31211 11.1676C8.68562 11.4271 7.99625 11.495 7.33117 11.3627C6.66609 11.2305 6.05518 10.9039 5.57569 10.4244C5.09619 9.94492 4.76965 9.33401 4.63736 8.66893C4.50507 8.00385 4.57297 7.31448 4.83247 6.68799C5.09197 6.06151 5.53142 5.52604 6.09524 5.1493ZM6.18178 9.81833C6.66402 10.3006 7.31807 10.5715 8.00005 10.5715V5.42862C7.31807 5.42862 6.66402 5.69954 6.18178 6.18178C5.69954 6.66401 5.42862 7.31807 5.42862 8.00005C5.42862 8.68204 5.69954 9.33609 6.18178 9.81833Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/color-halftone.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class ColorHalftone extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-color-halftone'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11269)\">\n            <path d=\"M4.03812 5.10475C4.03812 5.69386 3.56056 6.17142 2.97145 6.17142C2.38235 6.17142 1.90479 5.69386 1.90479 5.10475C1.90479 4.51565 2.38235 4.03809 2.97145 4.03809C3.56056 4.03809 4.03812 4.51565 4.03812 5.10475Z\" />\n            <path\n              opacity=\"0.5\"\n              d=\"M2.81902 11.0476C2.81902 11.3843 2.54613 11.6572 2.2095 11.6572C1.87287 11.6572 1.59998 11.3843 1.59998 11.0476C1.59998 10.711 1.87287 10.4381 2.2095 10.4381C2.54613 10.4381 2.81902 10.711 2.81902 11.0476Z\"\n            />\n            <path d=\"M5.86668 1.90484C5.86668 2.5781 5.3209 3.12389 4.64764 3.12389C3.97438 3.12389 3.42859 2.5781 3.42859 1.90484C3.42859 1.23158 3.97438 0.685791 4.64764 0.685791C5.3209 0.685791 5.86668 1.23158 5.86668 1.90484Z\" />\n            <path d=\"M2.51427 1.90484C2.51427 2.5781 1.96848 3.12389 1.29522 3.12389C0.621958 3.12389 0.0761719 2.5781 0.0761719 1.90484C0.0761719 1.23158 0.621958 0.685791 1.29522 0.685791C1.96848 0.685791 2.51427 1.23158 2.51427 1.90484Z\" />\n            <path d=\"M4.95235 8.30479C4.95235 8.80973 4.54301 9.21907 4.03807 9.21907C3.53312 9.21907 3.12378 8.80973 3.12378 8.30479C3.12378 7.79984 3.53312 7.3905 4.03807 7.3905C4.54301 7.3905 4.95235 7.79984 4.95235 8.30479Z\" />\n            <path\n              opacity=\"0.3\"\n              d=\"M4.34287 13.3334C4.34287 13.5858 4.1382 13.7905 3.88573 13.7905C3.63326 13.7905 3.42859 13.5858 3.42859 13.3334C3.42859 13.0809 3.63326 12.8762 3.88573 12.8762C4.1382 12.8762 4.34287 13.0809 4.34287 13.3334Z\"\n            />\n            <path d=\"M7.39054 5.10475C7.39054 5.69386 6.91297 6.17142 6.32387 6.17142C5.73477 6.17142 5.2572 5.69386 5.2572 5.10475C5.2572 4.51565 5.73477 4.03809 6.32387 4.03809C6.91297 4.03809 7.39054 4.51565 7.39054 5.10475Z\" />\n            <path\n              opacity=\"0.5\"\n              d=\"M6.17144 11.0476C6.17144 11.3843 5.89855 11.6572 5.56192 11.6572C5.22529 11.6572 4.95239 11.3843 4.95239 11.0476C4.95239 10.711 5.22529 10.4381 5.56192 10.4381C5.89855 10.4381 6.17144 10.711 6.17144 11.0476Z\"\n            />\n            <path d=\"M9.2191 1.90484C9.2191 2.5781 8.67332 3.12389 8.00005 3.12389C7.32679 3.12389 6.78101 2.5781 6.78101 1.90484C6.78101 1.23158 7.32679 0.685791 8.00005 0.685791C8.67332 0.685791 9.2191 1.23158 9.2191 1.90484Z\" />\n            <path d=\"M8.30477 8.30479C8.30477 8.80973 7.89543 9.21907 7.39048 9.21907C6.88554 9.21907 6.4762 8.80973 6.4762 8.30479C6.4762 7.79984 6.88554 7.3905 7.39048 7.3905C7.89543 7.3905 8.30477 7.79984 8.30477 8.30479Z\" />\n            <path\n              opacity=\"0.3\"\n              d=\"M7.69529 13.3334C7.69529 13.5858 7.49062 13.7905 7.23815 13.7905C6.98568 13.7905 6.78101 13.5858 6.78101 13.3334C6.78101 13.0809 6.98568 12.8762 7.23815 12.8762C7.49062 12.8762 7.69529 13.0809 7.69529 13.3334Z\"\n            />\n            <path d=\"M10.7428 5.10475C10.7428 5.69386 10.2653 6.17142 9.67616 6.17142C9.08706 6.17142 8.6095 5.69386 8.6095 5.10475C8.6095 4.51565 9.08706 4.03809 9.67616 4.03809C10.2653 4.03809 10.7428 4.51565 10.7428 5.10475Z\" />\n            <path\n              opacity=\"0.5\"\n              d=\"M9.52386 11.0476C9.52386 11.3843 9.25096 11.6572 8.91433 11.6572C8.5777 11.6572 8.30481 11.3843 8.30481 11.0476C8.30481 10.711 8.5777 10.4381 8.91433 10.4381C9.25096 10.4381 9.52386 10.711 9.52386 11.0476Z\"\n            />\n            <path d=\"M12.5714 1.90484C12.5714 2.5781 12.0256 3.12389 11.3523 3.12389C10.6791 3.12389 10.1333 2.5781 10.1333 1.90484C10.1333 1.23158 10.6791 0.685791 11.3523 0.685791C12.0256 0.685791 12.5714 1.23158 12.5714 1.90484Z\" />\n            <path d=\"M11.6572 8.30479C11.6572 8.80973 11.2478 9.21907 10.7429 9.21907C10.238 9.21907 9.82861 8.80973 9.82861 8.30479C9.82861 7.79984 10.238 7.3905 10.7429 7.3905C11.2478 7.3905 11.6572 7.79984 11.6572 8.30479Z\" />\n            <path\n              opacity=\"0.3\"\n              d=\"M11.0476 13.3334C11.0476 13.5858 10.8429 13.7905 10.5904 13.7905C10.338 13.7905 10.1333 13.5858 10.1333 13.3334C10.1333 13.0809 10.338 12.8762 10.5904 12.8762C10.8429 12.8762 11.0476 13.0809 11.0476 13.3334Z\"\n            />\n            <path d=\"M14.0952 5.10475C14.0952 5.69386 13.6177 6.17142 13.0286 6.17142C12.4395 6.17142 11.9619 5.69386 11.9619 5.10475C11.9619 4.51565 12.4395 4.03809 13.0286 4.03809C13.6177 4.03809 14.0952 4.51565 14.0952 5.10475Z\" />\n            <path\n              opacity=\"0.5\"\n              d=\"M12.8763 11.0476C12.8763 11.3843 12.6034 11.6572 12.2668 11.6572C11.9301 11.6572 11.6572 11.3843 11.6572 11.0476C11.6572 10.711 11.9301 10.4381 12.2668 10.4381C12.6034 10.4381 12.8763 10.711 12.8763 11.0476Z\"\n            />\n            <path d=\"M15.9238 1.90484C15.9238 2.5781 15.378 3.12389 14.7048 3.12389C14.0315 3.12389 13.4857 2.5781 13.4857 1.90484C13.4857 1.23158 14.0315 0.685791 14.7048 0.685791C15.378 0.685791 15.9238 1.23158 15.9238 1.90484Z\" />\n            <path d=\"M15.0096 8.30479C15.0096 8.80973 14.6003 9.21907 14.0953 9.21907C13.5904 9.21907 13.181 8.80973 13.181 8.30479C13.181 7.79984 13.5904 7.3905 14.0953 7.3905C14.6003 7.3905 15.0096 7.79984 15.0096 8.30479Z\" />\n            <path\n              opacity=\"0.3\"\n              d=\"M14.4 13.3334C14.4 13.5858 14.1953 13.7905 13.9429 13.7905C13.6904 13.7905 13.4857 13.5858 13.4857 13.3334C13.4857 13.0809 13.6904 12.8762 13.9429 12.8762C14.1953 12.8762 14.4 13.0809 14.4 13.3334Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11269\">\n              <rect\n                width=\"12.1905\"\n                height=\"12.1905\"\n                fill=\"white\"\n                transform=\"translate(1.90479 1.90479)\"\n              />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/dot-screen.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class DotScreen extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-dot-screen'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11268)\">\n            <path d=\"M4.34288 4.95245C4.34288 5.62571 3.79709 6.17149 3.12383 6.17149C2.45057 6.17149 1.90479 5.62571 1.90479 4.95245C1.90479 4.27918 2.45057 3.7334 3.12383 3.7334C3.79709 3.7334 4.34288 4.27918 4.34288 4.95245Z\" />\n            <path d=\"M4.34288 11.0477C4.34288 11.7209 3.79709 12.2667 3.12383 12.2667C2.45057 12.2667 1.90479 11.7209 1.90479 11.0477C1.90479 10.3744 2.45057 9.82861 3.12383 9.82861C3.79709 9.82861 4.34288 10.3744 4.34288 11.0477Z\" />\n            <path d=\"M5.86668 1.90484C5.86668 2.5781 5.3209 3.12389 4.64764 3.12389C3.97438 3.12389 3.42859 2.5781 3.42859 1.90484C3.42859 1.23158 3.97438 0.685791 4.64764 0.685791C5.3209 0.685791 5.86668 1.23158 5.86668 1.90484Z\" />\n            <path d=\"M2.51427 1.90484C2.51427 2.5781 1.96848 3.12389 1.29522 3.12389C0.621958 3.12389 0.0761719 2.5781 0.0761719 1.90484C0.0761719 1.23158 0.621958 0.685791 1.29522 0.685791C1.96848 0.685791 2.51427 1.23158 2.51427 1.90484Z\" />\n            <path d=\"M5.86668 8.00005C5.86668 8.67332 5.3209 9.2191 4.64764 9.2191C3.97438 9.2191 3.42859 8.67332 3.42859 8.00005C3.42859 7.32679 3.97438 6.78101 4.64764 6.78101C5.3209 6.78101 5.86668 7.32679 5.86668 8.00005Z\" />\n            <path d=\"M2.51427 8.00005C2.51427 8.67332 1.96848 9.2191 1.29522 9.2191C0.621958 9.2191 0.0761719 8.67332 0.0761719 8.00005C0.0761719 7.32679 0.621958 6.78101 1.29522 6.78101C1.96848 6.78101 2.51427 7.32679 2.51427 8.00005Z\" />\n            <path d=\"M5.86668 14.0953C5.86668 14.7685 5.3209 15.3143 4.64764 15.3143C3.97438 15.3143 3.42859 14.7685 3.42859 14.0953C3.42859 13.422 3.97438 12.8762 4.64764 12.8762C5.3209 12.8762 5.86668 13.422 5.86668 14.0953Z\" />\n            <path d=\"M2.51427 14.0953C2.51427 14.7685 1.96848 15.3143 1.29522 15.3143C0.621958 15.3143 0.0761719 14.7685 0.0761719 14.0953C0.0761719 13.422 0.621958 12.8762 1.29522 12.8762C1.96848 12.8762 2.51427 13.422 2.51427 14.0953Z\" />\n            <path d=\"M7.6953 4.95245C7.6953 5.62571 7.14951 6.17149 6.47625 6.17149C5.80299 6.17149 5.2572 5.62571 5.2572 4.95245C5.2572 4.27918 5.80299 3.7334 6.47625 3.7334C7.14951 3.7334 7.6953 4.27918 7.6953 4.95245Z\" />\n            <path d=\"M7.6953 11.0477C7.6953 11.7209 7.14951 12.2667 6.47625 12.2667C5.80299 12.2667 5.2572 11.7209 5.2572 11.0477C5.2572 10.3744 5.80299 9.82861 6.47625 9.82861C7.14951 9.82861 7.6953 10.3744 7.6953 11.0477Z\" />\n            <path d=\"M9.2191 1.90484C9.2191 2.5781 8.67332 3.12389 8.00005 3.12389C7.32679 3.12389 6.78101 2.5781 6.78101 1.90484C6.78101 1.23158 7.32679 0.685791 8.00005 0.685791C8.67332 0.685791 9.2191 1.23158 9.2191 1.90484Z\" />\n            <path d=\"M9.2191 8.00005C9.2191 8.67332 8.67332 9.2191 8.00005 9.2191C7.32679 9.2191 6.78101 8.67332 6.78101 8.00005C6.78101 7.32679 7.32679 6.78101 8.00005 6.78101C8.67332 6.78101 9.2191 7.32679 9.2191 8.00005Z\" />\n            <path d=\"M9.2191 14.0953C9.2191 14.7685 8.67332 15.3143 8.00005 15.3143C7.32679 15.3143 6.78101 14.7685 6.78101 14.0953C6.78101 13.422 7.32679 12.8762 8.00005 12.8762C8.67332 12.8762 9.2191 13.422 9.2191 14.0953Z\" />\n            <path d=\"M11.0476 4.95245C11.0476 5.62571 10.5018 6.17149 9.82854 6.17149C9.15528 6.17149 8.6095 5.62571 8.6095 4.95245C8.6095 4.27918 9.15528 3.7334 9.82854 3.7334C10.5018 3.7334 11.0476 4.27918 11.0476 4.95245Z\" />\n            <path d=\"M11.0476 11.0477C11.0476 11.7209 10.5018 12.2667 9.82854 12.2667C9.15528 12.2667 8.6095 11.7209 8.6095 11.0477C8.6095 10.3744 9.15528 9.82861 9.82854 9.82861C10.5018 9.82861 11.0476 10.3744 11.0476 11.0477Z\" />\n            <path d=\"M12.5714 1.90484C12.5714 2.5781 12.0256 3.12389 11.3523 3.12389C10.6791 3.12389 10.1333 2.5781 10.1333 1.90484C10.1333 1.23158 10.6791 0.685791 11.3523 0.685791C12.0256 0.685791 12.5714 1.23158 12.5714 1.90484Z\" />\n            <path d=\"M12.5714 8.00005C12.5714 8.67332 12.0256 9.2191 11.3523 9.2191C10.6791 9.2191 10.1333 8.67332 10.1333 8.00005C10.1333 7.32679 10.6791 6.78101 11.3523 6.78101C12.0256 6.78101 12.5714 7.32679 12.5714 8.00005Z\" />\n            <path d=\"M12.5714 14.0953C12.5714 14.7685 12.0256 15.3143 11.3523 15.3143C10.6791 15.3143 10.1333 14.7685 10.1333 14.0953C10.1333 13.422 10.6791 12.8762 11.3523 12.8762C12.0256 12.8762 12.5714 13.422 12.5714 14.0953Z\" />\n            <path d=\"M14.4 4.95245C14.4 5.62571 13.8542 6.17149 13.181 6.17149C12.5077 6.17149 11.9619 5.62571 11.9619 4.95245C11.9619 4.27918 12.5077 3.7334 13.181 3.7334C13.8542 3.7334 14.4 4.27918 14.4 4.95245Z\" />\n            <path d=\"M14.4 11.0477C14.4 11.7209 13.8542 12.2667 13.181 12.2667C12.5077 12.2667 11.9619 11.7209 11.9619 11.0477C11.9619 10.3744 12.5077 9.82861 13.181 9.82861C13.8542 9.82861 14.4 10.3744 14.4 11.0477Z\" />\n            <path d=\"M15.9238 1.90484C15.9238 2.5781 15.378 3.12389 14.7048 3.12389C14.0315 3.12389 13.4857 2.5781 13.4857 1.90484C13.4857 1.23158 14.0315 0.685791 14.7048 0.685791C15.378 0.685791 15.9238 1.23158 15.9238 1.90484Z\" />\n            <path d=\"M15.9238 8.00005C15.9238 8.67332 15.378 9.2191 14.7048 9.2191C14.0315 9.2191 13.4857 8.67332 13.4857 8.00005C13.4857 7.32679 14.0315 6.78101 14.7048 6.78101C15.378 6.78101 15.9238 7.32679 15.9238 8.00005Z\" />\n            <path d=\"M15.9238 14.0953C15.9238 14.7685 15.378 15.3143 14.7048 15.3143C14.0315 15.3143 13.4857 14.7685 13.4857 14.0953C13.4857 13.422 14.0315 12.8762 14.7048 12.8762C15.378 12.8762 15.9238 13.422 15.9238 14.0953Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11268\">\n              <rect width=\"12.1905\" height=\"12.1905\" transform=\"translate(1.90479 1.90479)\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/edge-work.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class EdgeWork extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-edge-work'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11274)\">\n            <path d=\"M4.06642 1.90479L1.90479 6.33314L2.53682 6.64166L4.11683 3.40484L5.98457 6.65718L8.0826 3.33597L10.0205 6.71046L11.7371 3.38925L13.4983 6.45609L14.1082 6.10584L11.7045 1.92037L9.98798 5.24157L8.11129 1.97365L6.01326 5.29486L4.06642 1.90479Z\" />\n            <path d=\"M4.06593 5.36231L6.01361 9.09299L8.11163 5.43967L9.98765 9.03309L11.7042 5.37976L14.1026 9.97369L13.4791 10.2992L11.7374 6.96301L10.0208 10.6163L8.08225 6.9031L5.98423 10.5564L4.11732 6.98045L2.55385 10.5037L1.91099 10.2184L4.06593 5.36231Z\" />\n            <path d=\"M4.06642 9.28959L6.01326 12.6797L8.11129 9.35845L9.98798 12.6264L11.7045 9.30517L14.1082 13.4906L13.4983 13.8409L11.7371 10.7741L10.0205 14.0953L8.0826 10.7208L5.98457 14.042L4.11683 10.7896L2.53682 14.0265L1.90479 13.7179L4.06642 9.28959Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11274\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/hexagonal-pixelate.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class HexagonalPixelate extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-hexagonal-pixelate'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11277)\">\n            <path\n              opacity=\"0.5\"\n              d=\"M4.91606 1.29938L3.36296 0.376709L1.81042 1.29835L1.811 3.14267L3.36411 4.06534L4.91664 3.14369L4.91606 1.29938Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M4.91606 6.73529L3.36296 5.81262L1.81042 6.73427L1.811 8.57858L3.36411 9.50125L4.91664 8.5796L4.91606 6.73529Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M4.91606 12.1711L3.36296 11.2484L1.81042 12.1701L1.811 14.0144L3.36411 14.937L4.91664 14.0154L4.91606 12.1711Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M3.36296 4.01727L1.80986 3.0946L0.257324 4.01625L0.257899 5.86056L1.811 6.78323L3.36354 5.86159L3.36296 4.01727Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M3.36296 9.45319L1.80986 8.53052L0.257324 9.45216L0.257899 11.2965L1.811 12.2191L3.36354 11.2975L3.36296 9.45319Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M3.36296 14.8891L1.80986 13.9664L0.257324 14.8881L0.257899 16.7324L1.811 17.6551L3.36354 16.7334L3.36296 14.8891Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M8.02226 1.29938L6.46916 0.376709L4.91663 1.29835L4.9172 3.14267L6.47031 4.06534L8.02284 3.14369L8.02226 1.29938Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M8.02226 6.73529L6.46916 5.81262L4.91663 6.73427L4.9172 8.57858L6.47031 9.50125L8.02284 8.5796L8.02226 6.73529Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M8.02226 12.1711L6.46916 11.2484L4.91663 12.1701L4.9172 14.0144L6.47031 14.937L8.02284 14.0154L8.02226 12.1711Z\"\n            />\n            <path d=\"M6.46916 4.01727L4.91606 3.0946L3.36353 4.01625L3.3641 5.86056L4.91721 6.78323L6.46974 5.86159L6.46916 4.01727Z\" />\n            <path d=\"M6.46916 9.45319L4.91606 8.53052L3.36353 9.45216L3.3641 11.2965L4.91721 12.2191L6.46974 11.2975L6.46916 9.45319Z\" />\n            <path d=\"M6.46916 14.8891L4.91606 13.9664L3.36353 14.8881L3.3641 16.7324L4.91721 17.6551L6.46974 16.7334L6.46916 14.8891Z\" />\n            <path d=\"M11.1285 1.29938L9.57536 0.376709L8.02283 1.29835L8.0234 3.14267L9.57651 4.06534L11.129 3.14369L11.1285 1.29938Z\" />\n            <path d=\"M11.1285 6.73529L9.57536 5.81262L8.02283 6.73427L8.0234 8.57858L9.57651 9.50125L11.129 8.5796L11.1285 6.73529Z\" />\n            <path d=\"M11.1285 12.1711L9.57536 11.2484L8.02283 12.1701L8.0234 14.0144L9.57651 14.937L11.129 14.0154L11.1285 12.1711Z\" />\n            <path\n              opacity=\"0.5\"\n              d=\"M9.57536 4.01727L8.02226 3.0946L6.46973 4.01625L6.4703 5.86056L8.02341 6.78323L9.57594 5.86159L9.57536 4.01727Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M9.57536 9.45319L8.02226 8.53052L6.46973 9.45216L6.4703 11.2965L8.02341 12.2191L9.57594 11.2975L9.57536 9.45319Z\"\n            />\n            <path\n              opacity=\"0.3\"\n              d=\"M9.57536 14.8891L8.02226 13.9664L6.46973 14.8881L6.4703 16.7324L8.02341 17.6551L9.57594 16.7334L9.57536 14.8891Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M14.2347 1.29938L12.6816 0.376709L11.129 1.29835L11.1296 3.14267L12.6827 4.06534L14.2352 3.14369L14.2347 1.29938Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M14.2347 6.73529L12.6816 5.81262L11.129 6.73427L11.1296 8.57858L12.6827 9.50125L14.2352 8.5796L14.2347 6.73529Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M14.2347 12.1711L12.6816 11.2484L11.129 12.1701L11.1296 14.0144L12.6827 14.937L14.2352 14.0154L14.2347 12.1711Z\"\n            />\n            <path\n              opacity=\"0.7\"\n              d=\"M12.6816 4.01727L11.1285 3.0946L9.57593 4.01625L9.5765 5.86056L11.1296 6.78323L12.6821 5.86159L12.6816 4.01727Z\"\n            />\n            <path d=\"M15.7878 4.01727L14.2347 3.0946L12.6821 4.01625L12.6827 5.86056L14.2358 6.78323L15.7883 5.86159L15.7878 4.01727Z\" />\n            <path\n              opacity=\"0.7\"\n              d=\"M12.6816 9.45319L11.1285 8.53052L9.57593 9.45216L9.5765 11.2965L11.1296 12.2191L12.6821 11.2975L12.6816 9.45319Z\"\n            />\n            <path\n              opacity=\"0.5\"\n              d=\"M12.6816 14.8891L11.1285 13.9664L9.57593 14.8881L9.5765 16.7324L11.1296 17.6551L12.6821 16.7334L12.6816 14.8891Z\"\n            />\n            <path d=\"M15.7878 9.45319L14.2347 8.53052L12.6821 9.45216L12.6827 11.2965L14.2358 12.2191L15.7883 11.2975L15.7878 9.45319Z\" />\n            <path d=\"M15.7878 14.8891L14.2347 13.9664L12.6821 14.8881L12.6827 16.7324L14.2358 17.6551L15.7883 16.7334L15.7878 14.8891Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11277\">\n              <rect width=\"12.1905\" height=\"12.1905\" transform=\"translate(1.90479 1.90479)\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/hue-saturation.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class HueSaturation extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-hue-saturation'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11265)\">\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M3.87106 6.06729C3.87098 6.05614 3.87093 6.04499 3.87093 6.03382C3.87093 3.75342 5.71956 1.90479 7.99996 1.90479C10.2804 1.90479 12.129 3.75342 12.129 6.03382C12.129 6.04496 12.129 6.05609 12.1289 6.06721C13.3087 6.79404 14.0953 8.0978 14.0953 9.58527C14.0953 11.8657 12.2467 13.7143 9.96625 13.7143C9.25441 13.7143 8.58464 13.5342 8.00003 13.217C7.41543 13.5342 6.74566 13.7143 6.03382 13.7143C3.75342 13.7143 1.90479 11.8657 1.90479 9.58527C1.90479 8.09786 2.69127 6.79414 3.87106 6.06729ZM9.36787 9.11148C8.94989 9.29754 8.487 9.40094 7.99996 9.40094C7.51297 9.40094 7.05013 9.29756 6.63219 9.11154C6.76297 8.18272 7.27272 7.37559 8.00003 6.85155C8.72733 7.37558 9.23707 8.18269 9.36787 9.11148ZM9.384 9.92517C8.95134 10.0791 8.48543 10.1628 7.99996 10.1628C7.51455 10.1628 7.04869 10.0791 6.61607 9.92522C6.71482 10.9102 7.23801 11.77 8.00003 12.319C8.76207 11.7699 9.28527 10.9101 9.384 9.92517ZM5.93393 8.69278C6.12934 7.8059 6.60989 7.026 7.27454 6.45411C6.89059 6.30184 6.47198 6.21815 6.03382 6.21815C5.54678 6.21815 5.08389 6.32155 4.66591 6.50761C4.79064 7.39331 5.25996 8.16835 5.93393 8.69278ZM3.96766 6.9264C4.21174 8.03404 4.90058 8.97481 5.83735 9.55189C5.83726 9.563 5.83722 9.57413 5.83722 9.58527C5.83722 10.8373 6.3945 11.9592 7.27454 12.7164C6.89059 12.8687 6.47198 12.9524 6.03382 12.9524C4.1742 12.9524 2.66669 11.4449 2.66669 9.58527C2.66669 8.50441 3.17597 7.5425 3.96766 6.9264ZM4.64978 5.69392C4.82019 3.99393 6.25508 2.66669 7.99996 2.66669C9.74483 2.66669 11.1797 3.9939 11.3501 5.69387C10.9175 5.54 10.4517 5.45624 9.96625 5.45624C9.25441 5.45624 8.58464 5.63638 8.00003 5.95355C7.41543 5.63638 6.74566 5.45624 6.03382 5.45624C5.54835 5.45624 5.08244 5.54002 4.64978 5.69392ZM11.334 6.50755C10.9161 6.32153 10.4532 6.21815 9.96625 6.21815C9.52809 6.21815 9.10948 6.30184 8.72553 6.45411C9.39016 7.02598 9.87069 7.80585 10.0661 8.69269C10.74 8.16825 11.2093 7.39322 11.334 6.50755ZM12.0323 6.92631C11.7882 8.03394 11.0994 8.9747 10.1627 9.5518C10.1628 9.56295 10.1629 9.5741 10.1629 9.58527C10.1629 10.8373 9.60557 11.9592 8.72553 12.7164C9.10948 12.8687 9.52809 12.9524 9.96625 12.9524C11.8259 12.9524 13.3334 11.4449 13.3334 9.58527C13.3334 8.50435 12.824 7.5424 12.0323 6.92631Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11265\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/ink.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Ink extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-ink'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11263)\">\n            <path d=\"M11.3656 10.5766C10.8658 11.4055 10.2342 11.142 9.64103 10.7907C9.04785 10.4394 8.48763 10.0168 8.13062 10.5163C7.74066 10.9444 8.04275 11.6305 8.19653 12.2343C8.35032 12.8435 8.3613 13.3704 7.38915 13.4857C6.62021 13.3814 6.66415 12.7447 6.79048 12.0696C6.9168 11.3945 7.11453 10.6809 6.62021 10.4284C6.22476 10.165 5.95014 10.6206 5.60412 11.0706C5.24712 11.5152 4.84068 11.9488 4.17061 11.6305C3.56644 11.131 3.79712 10.8401 4.11568 10.4669C4.43424 10.0936 4.84617 9.59415 4.61 8.71594C4.51113 8.35917 4.01682 8.4415 3.528 8.43052C3.05016 8.41954 2.56683 8.32074 2.5174 7.6072C2.47895 7.17358 2.803 6.99794 3.13804 6.86072C3.47856 6.7235 3.82459 6.6247 3.83008 6.27891C3.84655 5.94409 3.62137 5.70807 3.47856 5.46108C3.33576 5.21408 3.27535 4.95611 3.61587 4.57189C4.19258 4.09985 4.65394 4.50054 5.1153 4.93415C5.57666 5.36777 6.03253 5.82334 6.59825 5.46657C7.04862 5.17017 6.63669 4.64873 6.31813 4.13828C5.99957 3.62782 5.77439 3.13931 6.59825 2.91427C7.31226 2.71668 7.58688 3.20518 7.83403 3.77053C8.08668 4.33587 8.30638 4.97806 8.91054 5.09333C9.77285 5.2525 10.399 4.24256 10.9977 3.4412C11.5963 2.63983 12.184 2.04155 12.9365 3.02954C13.7603 4.06692 12.9749 4.54994 12.0577 4.97806C11.1405 5.37875 10.0859 5.73003 10.3386 6.50395C10.4869 6.95403 10.9702 6.70704 11.481 6.53688C11.9863 6.36673 12.53 6.27342 12.7937 7.04734C13.0738 7.86517 12.4092 8.05179 11.6622 8.12864C10.9153 8.20548 10.0804 8.17255 10.0255 8.54579C9.96508 8.93549 10.4649 9.09467 10.8988 9.3252C11.3327 9.56121 11.7007 9.87408 11.3656 10.5766ZM12.6618 11.8391C12.1401 11.8391 11.8709 11.4329 11.8709 11.0157C11.8709 10.5986 12.1346 10.1924 12.6618 10.1924C13.2111 10.1924 13.4857 10.5986 13.4857 11.0157C13.4857 11.4329 13.2111 11.8391 12.6618 11.8391Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11263\">\n              <rect width=\"16\" height=\"16\" fill=\"white\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/light-and-shadow.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class LightAndShadow extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-light-and-shadow'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M1.91925 7.57675C2.13661 4.40791 4.776 1.90479 8.00002 1.90479C11.3663 1.90479 14.0953 4.63372 14.0953 8.00002C14.0953 11.3663 11.3663 14.0953 8.00002 14.0953C4.74735 14.0953 2.08974 11.5475 1.91403 8.33866H1.90479V7.57675H1.91925ZM8.00002 12.4021V13.4701C6.784 13.4701 5.66065 13.0733 4.75239 12.4021H8.00002ZM8.00002 11.6402V10.3704H3.0688C3.29618 10.8426 3.58938 11.2771 3.9365 11.662V11.6402H8.00002ZM8.00002 9.60849V8.33866H2.54025C2.56708 8.77796 2.64576 9.20325 2.77025 9.60849H8.00002ZM8.00002 7.57675V6.3069H2.79701C2.66543 6.71153 2.57974 7.13686 2.54607 7.57675H8.00002ZM8.00002 2.52994V3.51325H4.87018C5.75697 2.8935 6.83604 2.52994 8.00002 2.52994ZM3.9941 4.27516H8.00002V5.54499H3.11048C3.34449 5.07982 3.64294 4.65264 3.9941 4.27516Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/magic-wand.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class MagicWand extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '2 2 20 20',\n    predefinedClassName: 'data-ex-icons-effects-magic-wand'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g clipPath=\"url(#clip0_129_11251)\">\n          <path d=\"M18.7071 16L10 7.29298C9.80957 7.11112 9.55636 7.00964 9.29301 7.00964C9.02967 7.00964 8.77645 7.11112 8.58599 7.29298L7.29299 8.58598C7.10576 8.77365 7.00061 9.02792 7.00061 9.29301C7.00061 9.5581 7.10576 9.81237 7.29299 10L15.9995 18.7071C16.1872 18.8943 16.4415 18.9994 16.7066 18.9994C16.9716 18.9994 17.2259 18.8943 17.4136 18.7071L18.7071 17.4138C18.8 17.321 18.8736 17.2108 18.9239 17.0895C18.9742 16.9682 19 16.8381 19 16.7068C19 16.5755 18.9742 16.4455 18.9239 16.3242C18.8736 16.2028 18.8 16.0929 18.7071 16ZM8.00004 9.29298L9.29299 8.00003L11.793 10.5L10.4997 11.7935L7.99969 9.29348L8.00004 9.29298ZM16.7066 18L11.2066 12.5005L12.5 11.2071L18 16.7071L16.7066 18Z\" />\n          <path d=\"M5.99999 11L5 12L5.99999 13L6.99998 12L5.99999 11Z\" />\n          <path d=\"M12 5.00001L11 6L12 6.99999L13 6L12 5.00001Z\" />\n          <path d=\"M5.99999 5.00001L5 6L5.99999 6.99999L6.99998 6L5.99999 5.00001Z\" />\n        </g>\n        <defs>\n          <clipPath id=\"clip0_129_11251\">\n            <rect width=\"16\" height=\"16\" transform=\"translate(4 4)\" />\n          </clipPath>\n        </defs>{' '}\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/magnify.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Magnify extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-magnify'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M1.90479 10.5143H2.66669V12.1905C2.66669 12.8217 3.17836 13.3334 3.80955 13.3334H5.48574V14.0953H3.80955C2.75758 14.0953 1.90479 13.2425 1.90479 12.1905V10.5143Z\" />\n          <path d=\"M1.90479 9.67621H2.66669V6.32383H1.90479V9.67621Z\" />\n          <path d=\"M1.90479 5.48574H2.66669V3.80955C2.66669 3.17836 3.17836 2.66669 3.80955 2.66669H5.48574V1.90479H3.80955C2.75758 1.90479 1.90479 2.75758 1.90479 3.80955V5.48574Z\" />\n          <path d=\"M6.32383 1.90479V2.66669H9.67621V1.90479H6.32383Z\" />\n          <path d=\"M10.5143 1.90479V2.66669H12.1905C12.8217 2.66669 13.3334 3.17836 13.3334 3.80955V5.48574H14.0953V3.80955C14.0953 2.75758 13.2425 1.90479 12.1905 1.90479H10.5143Z\" />\n          <path d=\"M14.0953 6.32383H13.3334V6.85717H14.0953V6.32383Z\" />\n          <path d=\"M6.32383 13.3334H7.23812V14.0953H6.32383V13.3334Z\" />\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M11.4286 6.85718C12.5592 7.98786 12.6772 9.74776 11.7824 11.0097L13.9482 13.1755L13.4094 13.7143L11.2712 11.576C10.0014 12.6889 8.06829 12.6398 6.85714 11.4286C5.59478 10.1662 5.59478 8.11954 6.85714 6.85718C8.1195 5.59482 10.1662 5.59482 11.4286 6.85718ZM10.8898 10.8899C11.8546 9.92503 11.8546 8.36075 10.8898 7.39593C9.92498 6.43111 8.3607 6.43111 7.39588 7.39593C6.43107 8.36075 6.43107 9.92503 7.39588 10.8899C8.3607 11.8547 9.92498 11.8547 10.8898 10.8899Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/noise.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Noise extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-noise'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path d=\"M11.1778 5.42465C11.3485 5.24574 11.3403 4.96381 11.1594 4.79496C10.9786 4.6261 10.6937 4.63425 10.523 4.81316C10.3524 4.99208 10.3606 5.274 10.5414 5.44286C10.7222 5.61172 11.0072 5.60356 11.1778 5.42465Z\" />\n          <path d=\"M10.3358 5.65847C10.5166 5.82733 10.5248 6.10925 10.3542 6.28817C10.1835 6.46708 9.89856 6.47523 9.71775 6.30637C9.53693 6.13752 9.5287 5.85559 9.69936 5.67668C9.87003 5.49777 10.155 5.48961 10.3358 5.65847Z\" />\n          <path d=\"M9.53016 7.15179C9.70082 6.97287 9.69259 6.69095 9.51177 6.52209C9.33096 6.35323 9.04603 6.36139 8.87537 6.5403C8.7047 6.71921 8.71293 7.00114 8.89375 7.16999C9.07456 7.33885 9.35949 7.3307 9.53016 7.15179Z\" />\n          <path d=\"M8.70637 8.01541C8.87703 7.83649 8.8688 7.55457 8.68799 7.38571C8.50717 7.21685 8.22224 7.22501 8.05158 7.40392C7.88092 7.58283 7.88915 7.86476 8.06996 8.03361C8.25078 8.20247 8.53571 8.19432 8.70637 8.01541Z\" />\n          <path d=\"M10.4519 9.64561C10.6225 9.4667 10.6143 9.18477 10.4335 9.01591C10.2527 8.84706 9.96775 8.85521 9.79709 9.03412C9.62643 9.21303 9.63466 9.49496 9.81547 9.66382C9.99629 9.83267 10.2812 9.82452 10.4519 9.64561Z\" />\n          <path d=\"M9.57934 8.83043C9.75 8.65151 9.74177 8.36959 9.56096 8.20073C9.38014 8.03188 9.09521 8.04003 8.92455 8.21894C8.75389 8.39785 8.76212 8.67978 8.94293 8.84863C9.12375 9.01749 9.40868 9.00934 9.57934 8.83043Z\" />\n          <path d=\"M12.1976 11.276C12.3683 11.0971 12.36 10.8152 12.1792 10.6463C11.9984 10.4775 11.7135 10.4856 11.5428 10.6645C11.3722 10.8435 11.3804 11.1254 11.5612 11.2942C11.742 11.4631 12.0269 11.4549 12.1976 11.276Z\" />\n          <path d=\"M11.3251 10.4608C11.4957 10.2819 11.4875 10 11.3067 9.83115C11.1259 9.66229 10.8409 9.67044 10.6703 9.84936C10.4996 10.0283 10.5078 10.3102 10.6887 10.479C10.8695 10.6479 11.1544 10.6398 11.3251 10.4608Z\" />\n          <path d=\"M5.41102 11.4699C5.58168 11.291 5.57345 11.0091 5.39263 10.8402C5.21182 10.6713 4.92689 10.6795 4.75623 10.8584C4.58556 11.0373 4.59379 11.3192 4.77461 11.4881C4.95542 11.657 5.24035 11.6488 5.41102 11.4699Z\" />\n          <path d=\"M6.28388 12.285C6.45454 12.106 6.44631 11.8241 6.26549 11.6553C6.08468 11.4864 5.79975 11.4946 5.62909 11.6735C5.45843 11.8524 5.46666 12.1343 5.64747 12.3032C5.82829 12.472 6.11322 12.4639 6.28388 12.285Z\" />\n          <path d=\"M11.2574 8.15229C11.4382 8.32115 11.4464 8.60308 11.2758 8.78199C11.1051 8.9609 10.8202 8.96905 10.6394 8.8002C10.4586 8.63134 10.4503 8.34941 10.621 8.1705C10.7916 7.99159 11.0766 7.98344 11.2574 8.15229Z\" />\n          <path d=\"M7.98063 12.2367C8.15129 12.0578 8.14306 11.7758 7.96225 11.607C7.78143 11.4381 7.4965 11.4463 7.32584 11.6252C7.15518 11.8041 7.16341 12.086 7.34423 12.2549C7.52504 12.4237 7.80997 12.4156 7.98063 12.2367Z\" />\n          <path d=\"M10.4031 7.96697C10.5738 7.78805 10.5656 7.50613 10.3847 7.33727C10.2039 7.16842 9.919 7.17657 9.74833 7.35548C9.57767 7.53439 9.5859 7.81632 9.76672 7.98517C9.94753 8.15403 10.2325 8.14588 10.4031 7.96697Z\" />\n          <path d=\"M12.1486 9.59717C12.3193 9.41826 12.3111 9.13633 12.1303 8.96748C11.9494 8.79862 11.6645 8.80677 11.4938 8.98568C11.3232 9.1646 11.3314 9.44652 11.5122 9.61538C11.693 9.78423 11.978 9.77608 12.1486 9.59717Z\" />\n          <path d=\"M12.0997 7.91837C12.2703 7.73945 12.2621 7.45753 12.0813 7.28867C11.9005 7.11982 11.6155 7.12797 11.4449 7.30688C11.2742 7.48579 11.2824 7.76772 11.4633 7.93657C11.6441 8.10543 11.929 8.09728 12.0997 7.91837Z\" />\n          <path d=\"M6.21663 9.97657C6.39745 10.1454 6.40568 10.4274 6.23501 10.6063C6.06435 10.7852 5.77942 10.7933 5.59861 10.6245C5.41779 10.4556 5.40956 10.1737 5.58022 9.99478C5.75089 9.81587 6.03582 9.80772 6.21663 9.97657Z\" />\n          <path d=\"M7.08928 10.7916C7.2701 10.9605 7.27833 11.2424 7.10766 11.4213C6.937 11.6002 6.65207 11.6084 6.47126 11.4395C6.29044 11.2706 6.28221 10.9887 6.45287 10.8098C6.62354 10.6309 6.90847 10.6227 7.08928 10.7916Z\" />\n          <path d=\"M8.78625 10.7432C8.96706 10.9121 8.97529 11.194 8.80463 11.3729C8.63397 11.5518 8.34904 11.56 8.16822 11.3911C7.98741 11.2223 7.97918 10.9403 8.14984 10.7614C8.3205 10.5825 8.60543 10.5744 8.78625 10.7432Z\" />\n          <path d=\"M8.835 12.422C9.01582 12.5909 9.02405 12.8728 8.85339 13.0517C8.68272 13.2306 8.3978 13.2388 8.21698 13.0699C8.03617 12.9011 8.02794 12.6191 8.1986 12.4402C8.36926 12.2613 8.65419 12.2532 8.835 12.422Z\" />\n          <path d=\"M12.9052 6.42489C13.086 6.59375 13.0942 6.87568 12.9236 7.05459C12.7529 7.2335 12.468 7.24165 12.2872 7.0728C12.1063 6.90394 12.0981 6.62201 12.2688 6.4431C12.4394 6.26419 12.7244 6.25604 12.9052 6.42489Z\" />\n          <path d=\"M11.2085 6.47349C11.3893 6.64235 11.3976 6.92427 11.2269 7.10319C11.0562 7.2821 10.7713 7.29025 10.5905 7.12139C10.4097 6.95254 10.4015 6.67061 10.5721 6.4917C10.7428 6.31279 11.0277 6.30464 11.2085 6.47349Z\" />\n          <path d=\"M12.9541 8.10401C13.135 8.27287 13.1432 8.5548 12.9725 8.73371C12.8019 8.91262 12.5169 8.92077 12.3361 8.75192C12.1553 8.58306 12.1471 8.30113 12.3177 8.12222C12.4884 7.94331 12.7733 7.93516 12.9541 8.10401Z\" />\n          <path d=\"M9.60982 9.87969C9.79063 10.0486 9.79886 10.3305 9.6282 10.5094C9.45754 10.6883 9.17261 10.6965 8.9918 10.5276C8.81098 10.3587 8.80275 10.0768 8.97341 9.8979C9.14408 9.71899 9.429 9.71084 9.60982 9.87969Z\" />\n          <path d=\"M7.04052 9.11295C7.22134 9.28181 7.22957 9.56373 7.05891 9.74265C6.88824 9.92156 6.60331 9.92971 6.4225 9.76085C6.24168 9.592 6.23345 9.31007 6.40412 9.13116C6.57478 8.95225 6.85971 8.94409 7.04052 9.11295Z\" />\n          <path d=\"M7.93145 10.5577C8.10211 10.3788 8.09388 10.0968 7.91307 9.92797C7.73225 9.75912 7.44732 9.76727 7.27666 9.94618C7.106 10.1251 7.11423 10.407 7.29504 10.5759C7.47586 10.7447 7.76079 10.7366 7.93145 10.5577Z\" />\n          <path d=\"M9.65879 11.5582C9.8396 11.7271 9.84784 12.009 9.67717 12.1879C9.50651 12.3668 9.22158 12.375 9.04077 12.2061C8.85995 12.0373 8.85172 11.7554 9.02238 11.5764C9.19305 11.3975 9.47798 11.3894 9.65879 11.5582Z\" />\n          <path d=\"M12.0504 6.23973C12.221 6.06081 12.2128 5.77889 12.032 5.61003C11.8512 5.44118 11.5662 5.44933 11.3956 5.62824C11.2249 5.80715 11.2332 6.08908 11.414 6.25793C11.5948 6.42679 11.8797 6.41864 12.0504 6.23973Z\" />\n          <path d=\"M7.86399 8.24933C8.0448 8.41819 8.05303 8.70011 7.88237 8.87903C7.71171 9.05794 7.42678 9.06609 7.24597 8.89723C7.06515 8.72838 7.05692 8.44645 7.22758 8.26754C7.39825 8.08863 7.68317 8.08047 7.86399 8.24933Z\" />\n          <path d=\"M8.73706 9.06435C8.91788 9.23321 8.92611 9.51513 8.75545 9.69405C8.58478 9.87296 8.29986 9.88111 8.11904 9.71226C7.93823 9.5434 7.93 9.26147 8.10066 9.08256C8.27132 8.90365 8.55625 8.8955 8.73706 9.06435Z\" />\n          <path d=\"M10.4825 10.6946C10.6633 10.8634 10.6715 11.1453 10.5009 11.3243C10.3302 11.5032 10.0453 11.5113 9.86445 11.3425C9.68363 11.1736 9.6754 10.8917 9.84606 10.7128C10.0167 10.5339 10.3017 10.5257 10.4825 10.6946Z\" />\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M13.9683 9.24363C13.9681 9.24476 13.9678 9.24589 13.9676 9.24701C13.9548 9.30874 13.941 9.37013 13.9263 9.43115C13.8392 9.79316 13.7198 10.1426 13.5713 10.4762C13.5665 10.487 13.5617 10.4977 13.5568 10.5084C13.4519 10.7406 13.3328 10.965 13.2007 11.1805C13.1462 11.2694 13.0896 11.3567 13.0307 11.4425C13.0248 11.4512 13.0188 11.4598 13.0129 11.4685C12.8486 11.7055 12.6679 11.9303 12.4724 12.1413C12.4037 12.2154 12.3332 12.2879 12.2609 12.3586C12.2599 12.3595 12.259 12.3605 12.258 12.3614C12.0451 12.5692 11.8171 12.7616 11.5758 12.9367C11.4884 13.0001 11.3993 13.0613 11.3085 13.12C11.3008 13.125 11.2931 13.13 11.2853 13.135C11.0803 13.2665 10.8668 13.386 10.646 13.4926C10.3589 13.6311 10.0594 13.7478 9.74949 13.8405C9.74659 13.8413 9.7437 13.8422 9.7408 13.8431C9.57194 13.8933 9.4 13.9364 9.22535 13.9721C9.08739 14.0002 8.94773 14.0237 8.80655 14.0424C8.79649 14.0437 8.78641 14.045 8.77633 14.0463C8.61254 14.0671 8.44671 14.0814 8.27913 14.089C8.18661 14.0932 8.09356 14.0953 8.00002 14.0953C7.44452 14.0953 6.90637 14.0209 6.39497 13.8817C6.37373 13.8759 6.35253 13.87 6.33138 13.864C6.06681 13.7889 5.80956 13.6963 5.56097 13.5877C3.40876 12.6469 1.90479 10.4991 1.90479 8.00002C1.90479 4.63372 4.63372 1.90479 8.00002 1.90479C10.4583 1.90479 12.5767 3.36009 13.5407 5.45615C13.5461 5.46795 13.5515 5.47977 13.5568 5.49161C13.6494 5.69632 13.731 5.90705 13.8008 6.12306C13.8384 6.23945 13.8727 6.35738 13.9034 6.47673C13.9523 6.66676 13.9922 6.86038 14.0228 7.05712C14.0705 7.36443 14.0953 7.67933 14.0953 8.00002C14.0953 8.00206 14.0953 8.0041 14.0953 8.00614C14.0952 8.03039 14.0951 8.05462 14.0948 8.0788C14.092 8.29577 14.0779 8.51001 14.0531 8.72095C14.0323 8.8976 14.0039 9.07194 13.9683 9.24363ZM7.38056 13.2978C7.55181 13.1246 7.83242 13.1185 8.01122 13.2855C8.02731 13.3005 8.04203 13.3164 8.05538 13.3331C8.41759 13.3294 8.77115 13.2896 9.11264 13.2171C9.2188 13.1303 9.35644 13.0988 9.48554 13.1237C9.62072 13.0846 9.75369 13.0403 9.88422 12.991C9.73278 12.8202 9.7348 12.5603 9.89556 12.3918C10.0662 12.2129 10.3512 12.2047 10.532 12.3736C10.6052 12.442 10.6501 12.5289 10.6663 12.6201C10.8669 12.5041 11.0592 12.3754 11.2421 12.2351C11.0789 12.3129 10.8773 12.288 10.7377 12.1576C10.5569 11.9888 10.5487 11.7069 10.7193 11.5279C10.89 11.349 11.1749 11.3409 11.3558 11.5097C11.5264 11.6691 11.5433 11.9291 11.4013 12.1082C11.9215 11.6771 12.3584 11.149 12.6844 10.5518C12.5767 10.5496 12.4694 10.5093 12.3849 10.4304C12.2041 10.2615 12.1958 9.97962 12.3665 9.8007C12.5372 9.62179 12.8221 9.61364 13.0029 9.7825C13.0094 9.78853 13.0156 9.7947 13.0216 9.80101C13.0609 9.69139 13.0968 9.58011 13.129 9.4673C13.031 9.30104 13.0504 9.08437 13.1907 8.9373C13.2126 8.91433 13.2364 8.89417 13.2616 8.87684C13.3088 8.59158 13.3334 8.29867 13.3334 8.00002C13.3334 7.99632 13.3334 7.99261 13.3333 7.9889C13.2705 7.96932 13.211 7.93573 13.1599 7.88798C12.9791 7.71912 12.9709 7.4372 13.1415 7.25828C13.1791 7.21894 13.2221 7.18785 13.2684 7.16509C13.1743 6.56694 12.9809 6.0018 12.7054 5.48711C12.5496 5.54486 12.3673 5.51456 12.2382 5.39394C12.0574 5.22508 12.0491 4.94316 12.2198 4.76425C12.2235 4.76033 12.2273 4.7565 12.2312 4.75275C12.1692 4.67217 12.105 4.5934 12.0387 4.51655C12.0274 4.53185 12.0151 4.54662 12.0016 4.56074C11.831 4.73965 11.546 4.7478 11.3652 4.57895C11.1844 4.41009 11.1762 4.12816 11.3468 3.94925C11.3656 3.9296 11.3857 3.91201 11.4069 3.89649C10.4829 3.12851 9.2954 2.66669 8.00002 2.66669C5.0545 2.66669 2.66669 5.0545 2.66669 8.00002C2.66669 9.39656 3.20345 10.6677 4.08192 11.6185C4.24183 11.5506 4.43414 11.5782 4.56875 11.7039C4.70268 11.8289 4.74192 12.016 4.68392 12.1774C4.78852 12.2605 4.89631 12.3398 5.00709 12.415C5.15522 12.3726 5.32155 12.4066 5.44182 12.5189C5.51107 12.5836 5.55501 12.6648 5.57327 12.7505C5.87702 12.906 6.19764 13.0332 6.53166 13.1287C6.52781 13.1253 6.52399 13.1219 6.52023 13.1184C6.33941 12.9495 6.33118 12.6676 6.50184 12.4887C6.67251 12.3097 6.95744 12.3016 7.13825 12.4704C7.31907 12.6393 7.3273 12.9212 7.15663 13.1001C7.09466 13.1651 7.01762 13.2076 6.93596 13.2272C7.08208 13.2568 7.23037 13.2804 7.38056 13.2978Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/sepia.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Sepia extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-sepia'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path opacity=\"0.5\" d=\"M1.90479 1.90479H14.0953V14.0953H1.90479V1.90479Z\" />\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M13.3334 2.66669H2.66669V13.3334H13.3334V2.66669ZM1.90479 1.90479V14.0953H14.0953V1.90479H1.90479Z\"\n          />\n          <path d=\"M6.92068 5.36513C6.92068 6.22424 6.22424 6.92068 5.36513 6.92068C4.50602 6.92068 3.80957 6.22424 3.80957 5.36513C3.80957 4.50602 4.50602 3.80957 5.36513 3.80957C6.22424 3.80957 6.92068 4.50602 6.92068 5.36513Z\" />\n          <path d=\"M5.3726 8.73856L2.28577 13.7143H13.3334V10.449L10.7339 6.09521L7.48465 11.5374L5.3726 8.73856Z\" />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/tilt-shift.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class TiltShift extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-tilt-shift'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11273)\">\n            <path d=\"M1.24084 2.58293L11.8227 1.24084L11.9186 1.99669L1.33671 3.33878L1.24084 2.58293Z\" />\n            <path d=\"M2.3837 11.7258L12.9656 10.3837L13.0615 11.1396L2.47956 12.4817L2.3837 11.7258Z\" />\n            <path d=\"M7.28677 3.68251L7.06069 3.85721L6.83861 3.67744C6.89358 3.60955 6.97657 3.57053 7.06392 3.57152C7.15126 3.5725 7.23336 3.61339 7.28677 3.68251Z\" />\n            <path d=\"M6.93947 8.90484C7.45111 8.90484 7.86588 8.49007 7.86588 7.97843H8.43731C8.43731 8.80567 7.7667 9.47627 6.93947 9.47627V8.90484Z\" />\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M7.06069 3.85721C6.83861 3.67744 6.83861 3.67744 6.83861 3.67744L6.83673 3.67978L6.83191 3.68577L6.81385 3.70841C6.79819 3.72811 6.77545 3.75692 6.74666 3.79393C6.68907 3.86793 6.60717 3.97482 6.50904 4.10743C6.313 4.37237 6.05115 4.7414 5.7888 5.15692C5.52699 5.57156 5.26139 6.03764 5.06038 6.49608C4.86188 6.94883 4.71437 7.42054 4.71437 7.83882C4.71437 8.62009 4.96685 9.24753 5.40223 9.68015C5.83617 10.1113 6.42751 10.3247 7.06069 10.3247C8.36553 10.3247 9.407 9.3024 9.407 7.83882C9.407 7.44811 9.25753 6.9897 9.06043 6.54597C8.85994 6.09458 8.59503 5.62574 8.33383 5.20438C8.07206 4.7821 7.81078 4.40233 7.61518 4.12839C7.51727 3.99128 7.43558 3.88035 7.37819 3.80346C7.34949 3.76501 7.32685 3.73505 7.31128 3.71457L7.29334 3.69107L7.28859 3.68488L7.28677 3.68251C7.28677 3.68251 7.28677 3.68251 7.06069 3.85721ZM7.05609 4.32985C7.02874 4.36615 6.99942 4.40539 6.96839 4.44733C6.77807 4.70454 6.52476 5.06163 6.27197 5.462C6.01863 5.86323 5.76907 6.3028 5.58372 6.72554C5.39586 7.15399 5.2858 7.53902 5.2858 7.83882C5.2858 8.49369 5.49459 8.96636 5.80501 9.27481C6.11685 9.58468 6.55582 9.75331 7.06069 9.75331C8.03192 9.75331 8.83557 9.00512 8.83557 7.83882C8.83557 7.57205 8.72747 7.20403 8.53821 6.77793C8.35234 6.35949 8.1021 5.91512 7.84815 5.50545C7.59476 5.09669 7.34089 4.72761 7.15013 4.46046C7.1167 4.41364 7.08524 4.36999 7.05609 4.32985Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11273\">\n              <rect width=\"12.1905\" height=\"12.1905\" transform=\"translate(0.904785 0.904785)\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/triangle-blur.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class TriangleBlur extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-triangle-blur'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11271)\">\n            <path d=\"M9.81261 2.05281L9.51117 2.28574L9.21508 2.04604C9.28836 1.95552 9.39902 1.90349 9.51548 1.90481C9.63194 1.90613 9.7414 1.96065 9.81261 2.05281Z\" />\n            <path d=\"M9.90479 11.8095C10.7464 11.8095 11.4286 11.1273 11.4286 10.2857H12.1905C12.1905 11.5481 11.1672 12.5715 9.90479 12.5715V11.8095Z\" />\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M5.31629 10.2117C5.41417 11.391 5.85028 12.3392 6.53097 13.0156C7.30635 13.7861 8.36665 14.1714 9.51117 14.1714C11.8515 14.1714 13.727 12.3411 13.727 9.69573C13.727 9.00131 13.4598 8.17203 13.095 7.35087C12.7257 6.51951 12.2366 5.65342 11.7524 4.87233C11.2675 4.09001 10.7832 3.38606 10.4204 2.87804C10.2389 2.62384 10.0875 2.41824 9.98124 2.27589C9.9281 2.2047 9.88623 2.1493 9.85751 2.11152C9.84314 2.09262 9.83206 2.07813 9.8245 2.06827L9.81583 2.05698L9.8129 2.05317C9.81284 2.0531 9.81261 2.05281 9.51117 2.28574C9.21508 2.04604 9.21514 2.04597 9.21508 2.04604L9.21178 2.05013L9.20302 2.06103C9.1954 2.07052 9.18427 2.08445 9.16984 2.1026C9.141 2.1389 9.09901 2.19211 9.04574 2.26056C8.93923 2.39743 8.78754 2.59539 8.60573 2.8411C8.2424 3.33212 7.75734 4.01576 7.2716 4.78506C7.03523 5.15943 6.79766 5.5557 6.57337 5.96129H3.64893V6.72319H6.17508C6.0877 6.90131 6.00448 7.07971 5.92658 7.25738C5.86124 7.4064 5.79914 7.55602 5.7412 7.70552H2.77681V8.46742H5.48518C5.391 8.80484 5.32702 9.13517 5.30439 9.44975H1.90479V10.2117H5.31629ZM6.08112 10.2117C6.17573 11.2042 6.54449 11.955 7.068 12.4751C7.68059 13.0838 8.53773 13.4095 9.51117 13.4095C11.4067 13.4095 12.9651 11.9447 12.9651 9.69573C12.9651 9.16656 12.753 8.4578 12.3987 7.66016C12.049 6.87272 11.5794 6.03926 11.1048 5.27376C10.6311 4.50947 10.1566 3.81977 9.80035 3.32079C9.68897 3.16481 9.58927 3.02765 9.50489 2.91287C9.42245 3.02077 9.32579 3.14889 9.2182 3.29429C8.86249 3.77502 8.38882 4.44273 7.91584 5.19183C7.75831 5.44132 7.60134 5.69907 7.44915 5.96129H10.5061V6.72319H7.02889C6.88272 7.00381 6.74625 7.28533 6.62436 7.56332C6.60348 7.61095 6.58308 7.65836 6.56318 7.70552H9.63395V8.46742H6.28002C6.16879 8.82013 6.09558 9.15066 6.06871 9.44975H8.76193V10.2117H6.08112Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11271\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/vibrance.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Vibrance extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-vibrance'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11266)\">\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M8.19047 14.0953L1.5238 1.90479L14.8571 1.90479L8.19047 14.0953ZM10.6483 8.01305L8.19047 12.5073L5.73266 8.01305C6.32652 8.70473 7.20733 9.14288 8.19047 9.14288C9.17361 9.14288 10.0544 8.70473 10.6483 8.01305ZM11.2935 6.83324L13.5721 2.66669L8.19332 2.66669C9.98036 2.66823 11.4286 4.11738 11.4286 5.90478C11.4286 6.22747 11.3814 6.53913 11.2935 6.83324ZM8.18762 2.66669L2.80887 2.66669L5.08745 6.83325C4.99958 6.53914 4.95238 6.22747 4.95238 5.90478C4.95238 4.11738 6.40058 2.66823 8.18762 2.66669Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11266\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/vignette.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class Vignette extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-vignette'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M14.0953 1.90479H1.90479V14.0953H14.0953V1.90479ZM8.00002 13.7566C11.1793 13.7566 13.7566 11.1793 13.7566 8.00002C13.7566 4.82073 11.1793 2.24341 8.00002 2.24341C4.82073 2.24341 2.24341 4.82073 2.24341 8.00002C2.24341 11.1793 4.82073 13.7566 8.00002 13.7566Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/effects/zoom-blur.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from '../base';\n\nexport default class ZoomBlur extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-zoom-blur'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_129_11272)\">\n            <path d=\"M14.0436 8.79612C14.0776 8.53557 14.0951 8.26984 14.0951 8.00002C14.0951 7.7302 14.0776 7.46448 14.0436 7.20393L13.2881 7.30248C13.3179 7.53044 13.3332 7.76324 13.3332 8.00002C13.3332 8.2368 13.3179 8.46961 13.2881 8.69757L14.0436 8.79612Z\" />\n            <path d=\"M13.6326 5.66684C13.4271 5.17131 13.1582 4.70876 12.8359 4.28934L12.2317 4.75359C12.5139 5.12082 12.7492 5.52552 12.9288 5.95869L13.6326 5.66684Z\" />\n            <path d=\"M11.7106 3.16406C11.2912 2.84175 10.8286 2.57278 10.3331 2.36729L10.0412 3.07109C10.4744 3.25072 10.8791 3.48599 11.2463 3.76819L11.7106 3.16406Z\" />\n            <path d=\"M8.796 1.9563C8.53544 1.92232 8.26972 1.90479 7.9999 1.90479C7.73008 1.90479 7.46436 1.92232 7.20381 1.9563L7.30236 2.71181C7.53032 2.68207 7.76312 2.66669 7.9999 2.66669C8.23668 2.66669 8.46949 2.68207 8.69745 2.71181L8.796 1.9563Z\" />\n            <path d=\"M5.66672 2.3673C5.17119 2.57278 4.70864 2.84175 4.28922 3.16406L4.75347 3.76819C5.1207 3.48599 5.5254 3.25072 5.95857 3.07109L5.66672 2.3673Z\" />\n            <path d=\"M3.16394 4.28934C2.84163 4.70876 2.57266 5.17131 2.36717 5.66684L3.07096 5.95869C3.25059 5.52552 3.48587 5.12083 3.76807 4.75359L3.16394 4.28934Z\" />\n            <path d=\"M1.90466 8.00002C1.90466 7.7302 1.92219 7.46448 1.95618 7.20393L2.71169 7.30248C2.68195 7.53044 2.66657 7.76324 2.66657 8.00002C2.66657 8.2368 2.68195 8.46961 2.71169 8.69757L1.95618 8.79612C1.9222 8.53557 1.90466 8.26984 1.90466 8.00002Z\" />\n            <path d=\"M2.36717 10.3332C2.57266 10.8287 2.84163 11.2913 3.16394 11.7107L3.76807 11.2465C3.48587 10.8792 3.25059 10.4745 3.07096 10.0414L2.36717 10.3332Z\" />\n            <path d=\"M4.28922 12.836C4.70864 13.1583 5.17119 13.4273 5.66672 13.6328L5.95857 12.929C5.5254 12.7493 5.1207 12.5141 4.75347 12.2319L4.28922 12.836Z\" />\n            <path d=\"M7.20381 14.0437C7.46436 14.0777 7.73008 14.0953 7.9999 14.0953C8.26972 14.0953 8.53544 14.0777 8.796 14.0437L8.69745 13.2882C8.46949 13.318 8.23668 13.3334 7.9999 13.3334C7.76312 13.3334 7.53032 13.318 7.30236 13.2882L7.20381 14.0437Z\" />\n            <path d=\"M10.3331 13.6328C10.8286 13.4273 11.2912 13.1583 11.7106 12.836L11.2463 12.2319C10.8791 12.5141 10.4744 12.7493 10.0412 12.929L10.3331 13.6328Z\" />\n            <path d=\"M12.8359 11.7107C13.1582 11.2913 13.4271 10.8287 13.6326 10.3332L12.9288 10.0414C12.7492 10.4745 12.5139 10.8792 12.2317 11.2465L12.8359 11.7107Z\" />\n            <path d=\"M8.28668 4.68247L8.0606 4.85717L7.83853 4.6774C7.89349 4.6095 7.97649 4.57048 8.06383 4.57147C8.15118 4.57246 8.23327 4.61335 8.28668 4.68247Z\" />\n            <path d=\"M7.93939 9.90479C8.45103 9.90479 8.86579 9.49003 8.86579 8.97839H9.43722C9.43722 9.80562 8.76662 10.4762 7.93939 10.4762V9.90479Z\" />\n            <path\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n              d=\"M8.0606 4.85717C7.83853 4.6774 7.83857 4.67735 7.83853 4.6774L7.83796 4.6781L7.83664 4.67973L7.83182 4.68573L7.81376 4.70836C7.7981 4.72807 7.77537 4.75687 7.74657 4.79388C7.68898 4.86788 7.60708 4.97477 7.50895 5.10739C7.31292 5.37232 7.05107 5.74136 6.78871 6.15687C6.5269 6.57152 6.2613 7.0376 6.0603 7.49604C5.86179 7.94878 5.71428 8.42049 5.71428 8.83877C5.71428 9.62005 5.96677 10.2475 6.40214 10.6801C6.83609 11.1113 7.42742 11.3247 8.0606 11.3247C9.36544 11.3247 10.4069 10.3023 10.4069 8.83877C10.4069 8.44807 10.2574 7.98965 10.0603 7.54592C9.85985 7.09454 9.59494 6.62569 9.33374 6.20433C9.07197 5.78205 8.81069 5.40228 8.61509 5.12835C8.51719 4.99124 8.43549 4.8803 8.3781 4.80341C8.3494 4.76496 8.32676 4.735 8.31119 4.71452L8.29326 4.69102L8.2885 4.68483L8.28668 4.68247C8.28664 4.68242 8.28668 4.68247 8.0606 4.85717ZM8.056 5.32981C8.02866 5.36611 7.99933 5.40535 7.9683 5.44728C7.77798 5.7045 7.52468 6.06158 7.27188 6.46195C7.01854 6.86319 6.76899 7.30275 6.58363 7.7255C6.39578 8.15394 6.28571 8.53897 6.28571 8.83877C6.28571 9.49365 6.4945 9.96631 6.80492 10.2748C7.11677 10.5846 7.55573 10.7533 8.0606 10.7533C9.03184 10.7533 9.83549 10.0051 9.83549 8.83877C9.83549 8.572 9.72738 8.20398 9.53812 7.77789C9.35225 7.35945 9.10201 6.91507 8.84806 6.5054C8.59468 6.09665 8.34081 5.72756 8.15005 5.46041C8.11661 5.41359 8.08515 5.36994 8.056 5.32981Z\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_129_11272\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/email.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Email extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-email'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M23.85,23a1,1,0,0,1,.38.08,1,1,0,0,0-.76,0A1,1,0,0,1,23.85,23Z\" />\n        <path d=\"M8.35,24.41V47.18a3.37,3.37,0,0,0,3.37,3.37H52.2a3.37,3.37,0,0,0,3.37-3.37V24.41L32,37.9Z\" />\n        <path d=\"M55.57,16.82a3.37,3.37,0,0,0-3.37-3.37H11.72a3.37,3.37,0,0,0-3.37,3.37L32,30.31Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/expand.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Expand extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-expand'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(6.000000, 6.000000)\">\n          <path d=\"M31.25,6.25 L36.0416667,11.0416667 L30.0208333,17.0208333 L32.9791667,19.9791667 L38.9583333,13.9583333 L43.75,18.75 L43.75,6.25 L31.25,6.25 Z M6.25,18.75 L11.0416667,13.9583333 L17.0208333,19.9791667 L19.9791667,17.0208333 L13.9583333,11.0416667 L18.75,6.25 L6.25,6.25 L6.25,18.75 Z M18.75,43.75 L13.9583333,38.9583333 L19.9791667,32.9791667 L17.0208333,30.0208333 L11.0416667,36.0416667 L6.25,31.25 L6.25,43.75 L18.75,43.75 Z M43.75,31.25 L38.9583333,36.0416667 L32.9791667,30.0208333 L30.0208333,32.9791667 L36.0416667,38.9583333 L31.25,43.75 L43.75,43.75 L43.75,31.25 Z\" />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/eye-seen.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class EyeSeen extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-eyeseen'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          fill=\"currentColor\"\n          d=\"M15.3 7.50005C15.2 7.40005 15.2 7.30005 15.2 7.30005C15.1 7.20005 15 7.00005 14.8 6.70005C14.5 6.20005 14 5.60005 13.4 5.00005C12.2 3.70005 10.4 2.30005 8 2.30005C5.6 2.30005 3.7 3.70005 2.5 5.00005C1.9 5.60005 1.5 6.20005 1.2 6.70005C1 7.00005 0.9 7.20005 0.8 7.30005C0.8 7.40005 0.7 7.40005 0.7 7.50005C0.6 7.70005 0.6 8.00005 0.7 8.20005C0.7 8.20005 0.7 8.30005 0.8 8.40005C0.9 8.50005 1 8.80005 1.2 9.00005C1.5 9.50005 2 10.1 2.6 10.8C3.8 12.1 5.6 13.4 8 13.4C10.4 13.4 12.2 12 13.4 10.8C14 10.1 14.5 9.50005 14.8 9.00005C15 8.80005 15.1 8.50005 15.2 8.40005C15.2 8.30005 15.3 8.30005 15.3 8.20005C15.4 8.00005 15.4 7.70005 15.3 7.50005ZM13.6 8.20005C13.3 8.60005 12.9 9.20005 12.4 9.80005C11.3 11 9.8 12 8 12C6.1 12 4.6 11 3.6 9.80005C3 9.20005 2.6 8.70005 2.3 8.20005C2.2 8.10005 2.2 8.00005 2.1 7.90005C2.2 7.80005 2.2 7.70005 2.3 7.50005C2.6 7.00005 3 6.50005 3.6 5.90005C4.6 4.80005 6.1 3.70005 8 3.70005C9.8 3.70005 11.3 4.80005 12.4 5.90005C12.9 6.50005 13.3 7.00005 13.6 7.50005C13.7 7.60005 13.8 7.80005 13.8 7.90005C13.8 8.00005 13.7 8.10005 13.6 8.20005Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          fill=\"currentColor\"\n          d=\"M7.8999 5.40002C6.4999 5.40002 5.3999 6.50002 5.3999 7.90002C5.3999 9.30002 6.4999 10.4 7.8999 10.4C9.2999 10.4 10.3999 9.30002 10.3999 7.90002C10.3999 6.50002 9.2999 5.40002 7.8999 5.40002ZM7.8999 9.00002C7.2999 9.00002 6.7999 8.50002 6.7999 7.90002C6.7999 7.30002 7.2999 6.80002 7.8999 6.80002C8.4999 6.80002 8.9999 7.30002 8.9999 7.90002C8.9999 8.50002 8.4999 9.00002 7.8999 9.00002Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/eye-unseen.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class EyeUnseen extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-eyeunseen'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M15.4999 14.5L12.2999 11.3L9.7999 8.90002L7.1999 6.20002L4.6999 3.70002L1.4999 0.500024C1.1999 0.200024 0.799902 0.200024 0.499902 0.500024C0.199902 0.800024 0.199902 1.20002 0.499902 1.50002L3.1999 4.20002C1.9999 5.20002 1.0999 6.30002 0.399902 7.70002C0.299902 7.90002 0.299902 8.10003 0.399902 8.30002C0.399902 8.30002 0.499902 8.40002 0.499902 8.50002C0.599902 8.70002 0.699902 8.90003 0.899902 9.10003C1.1999 9.60003 1.6999 10.3 2.2999 10.9C3.4999 12.2 5.4999 13.7 7.9999 13.7C9.2999 13.7 10.5999 13.3 11.6999 12.6L14.4999 15.4C14.7999 15.7 15.1999 15.7 15.4999 15.4C15.7999 15.2 15.7999 14.8 15.4999 14.5ZM6.7999 7.80002L8.1999 9.20002C8.0999 9.20002 7.9999 9.20002 7.8999 9.20002C7.6999 9.20002 7.5999 9.20003 7.3999 9.10003C7.1999 9.00002 7.1999 9.00002 7.0999 8.90002C6.9999 8.80002 6.8999 8.70002 6.7999 8.50002C6.6999 8.30002 6.6999 8.20002 6.6999 8.00002C6.7999 7.90002 6.7999 7.80002 6.7999 7.80002ZM7.9999 12.4C6.0999 12.4 4.4999 11.3 3.3999 10.1C2.7999 9.50002 2.3999 8.90002 2.0999 8.40002C1.9999 8.30002 1.8999 8.10002 1.7999 8.00002C2.3999 6.90002 3.1999 6.00002 4.1999 5.20002L5.6999 6.80002C5.6999 6.80002 5.5999 6.90002 5.5999 7.00002C5.3999 7.30002 5.2999 7.70002 5.2999 8.00002C5.2999 8.30002 5.3999 8.70002 5.4999 9.00002C5.5999 9.30002 5.7999 9.60002 6.0999 9.90002C6.3999 10.2 6.5999 10.4 6.9999 10.5C7.2999 10.6 7.6999 10.7 7.9999 10.7C8.2999 10.7 8.6999 10.6 8.9999 10.5C9.0999 10.5 9.1999 10.4 9.2999 10.4L10.6999 11.8C9.8999 12.1 8.8999 12.4 7.9999 12.4Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M15.0001 7.69995C15.1001 7.89995 15.1001 8.09995 15.0001 8.29995C14.6001 9.09995 14.1001 9.79995 13.5001 10.5C13.3001 10.8 12.8001 10.8 12.5001 10.6C12.2001 10.4 12.2001 9.89995 12.4001 9.59995C12.8001 9.09995 13.2001 8.59995 13.5001 7.99995C13.4001 7.89995 13.3001 7.79995 13.3001 7.59995C13.0001 7.09995 12.6001 6.59995 12.1001 5.99995C11.5001 5.39995 10.9001 4.79995 10.1001 4.39995C9.30009 3.89995 8.50009 3.59995 7.50009 3.59995C7.10009 3.59995 6.70009 3.59995 6.30009 3.69995C6.00009 3.79995 5.60009 3.59995 5.50009 3.19995C5.40009 2.79995 5.60009 2.49995 6.00009 2.39995C6.50009 2.29995 7.00009 2.19995 7.50009 2.19995C10.0001 2.19995 11.9001 3.59995 13.1001 4.99995C13.7001 5.69995 14.2001 6.29995 14.5001 6.79995C14.7001 6.99995 14.9001 7.49995 15.0001 7.69995Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/file-type.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ReactNode} from 'react';\nimport styled from 'styled-components';\n\nimport {File} from './';\n\ninterface FileNameTagProps {\n  fontSize: string;\n}\n\nconst FileNameTag = styled.div<FileNameTagProps>`\n  background-color: currentColor;\n  border-radius: 1px;\n  display: inline-block;\n  padding: 0 4px;\n  position: absolute;\n  top: 45%;\n  left: 10%;\n\n  .text {\n    color: white;\n    font-size: ${props => props.fontSize};\n  }\n`;\n\ninterface FileTypeIconWrapperProps {\n  height: string;\n}\n\nconst FileTypeIconWrapper = styled.div<FileTypeIconWrapperProps>`\n  display: inline-block;\n  position: relative;\n  color: currentColor;\n  height: ${props => props.height};\n`;\n\ninterface FileTypeIconProps {\n  ext?: ReactNode;\n  height: string;\n  fontSize: string;\n}\n\nconst FileTypeIcon = ({ext, height, fontSize}: FileTypeIconProps) => (\n  <FileTypeIconWrapper height={height}>\n    <File height={height} />\n    <FileNameTag fontSize={fontSize}>\n      <div className=\"text\">{ext}</div>\n    </FileNameTag>\n  </FileTypeIconWrapper>\n);\n\nexport default FileTypeIcon;\n"
  },
  {
    "path": "src/components/src/common/icons/file.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class File extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-file'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M49.61,20.51,36.56,7.46a1.36,1.36,0,0,0-.32-.24,1.6,1.6,0,0,0-.39-.14,1.53,1.53,0,0,0-.26,0H15.38A1.39,1.39,0,0,0,14,8.45v47.1a1.39,1.39,0,0,0,1.39,1.39H48.63A1.39,1.39,0,0,0,50,55.55V21.48A1.4,1.4,0,0,0,49.61,20.51Zm-1,35H15.38V8.45h19.4V20.92a1.39,1.39,0,0,0,1.39,1.39H48.63Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/files.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Files extends React.Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-files'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <path d=\"M48.015 58h-32a8 8 0 0 1-8-8V26h48v24a8 8 0 0 1-8 8zm-2-44h-28a6 6 0 0 0-6 6v2h40v-2a6 6 0 0 0-6-6zm-2 26v-6h-4v4h-16v-4h-4v6a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2zm-4-34h-16a4 4 0 0 0-4 4h24a4 4 0 0 0-4-4z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/filter-funnel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class FilterFunnel extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-filterfunnel'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentColor'}}\n        strokeWidth=\"1.75\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/free-window.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nconst FreeWindow = ({\n  height = '16px',\n  viewBox = '0 0 16 16',\n  predefinedClassName = 'data-ex-icons-freewindow',\n  ...restProps\n}: Partial<BaseProps>) => {\n  const props = {\n    height,\n    viewBox,\n    predefinedClassName,\n    ...restProps\n  };\n  return (\n    <Base {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3.75665 4.67861C3.75665 4.40246 3.98051 4.17861 4.25665 4.17861H11.7433C12.0195 4.17861 12.2433 4.40246 12.2433 4.67861V11.3214C12.2433 11.5975 12.0195 11.8214 11.7433 11.8214H4.25665C3.98051 11.8214 3.75665 11.5975 3.75665 11.3214V4.67861ZM4.75665 5.17861V10.8214H11.2433V5.17861H4.75665Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.25611 10.2511C2.03735 10.4196 1.7234 10.3789 1.55487 10.1601L0.125822 8.30514C-0.0127244 8.1253 -0.0127244 7.8747 0.125822 7.69486L1.55487 5.83988C1.7234 5.62113 2.03735 5.58041 2.25611 5.74894C2.47486 5.91746 2.51558 6.23142 2.34705 6.45017L1.15308 8L2.34705 9.54983C2.51558 9.76858 2.47486 10.0825 2.25611 10.2511Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M13.7439 5.76226C13.9626 5.59374 14.2766 5.63446 14.4451 5.85321L15.8742 7.70818C16.0127 7.88802 16.0127 8.13863 15.8742 8.31847L14.4451 10.1734C14.2766 10.3922 13.9626 10.4329 13.7439 10.2644C13.5251 10.0959 13.4844 9.78191 13.6529 9.56316L14.8469 8.01333L13.6529 6.4635C13.4844 6.24474 13.5251 5.93079 13.7439 5.76226Z\"\n      />\n    </Base>\n  );\n};\n\nexport default FreeWindow;\n"
  },
  {
    "path": "src/components/src/common/icons/gear.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Gear extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-gear'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g>\n          <path d=\"M39.1740741,58 L26.2185185,58 L25.9240741,52.012963 C24.9425926,51.7185185 24.0592593,51.3259259 23.1759259,50.9333333 L18.562963,54.7611111 L9.63148148,45.8296296 L13.6555556,41.2166667 C13.262963,40.3333333 12.8703704,39.45 12.5759259,38.5666667 L6.49074074,38.2722222 L6.49074074,25.3166667 L12.6740741,25.0222222 C12.9685185,24.0407407 13.3611111,23.1574074 13.7537037,22.3722222 L9.72962963,17.7592593 L18.6611111,8.82777778 L23.2740741,12.7537037 C24.0592593,12.3611111 24.9425926,11.8703704 26.0222222,11.5759259 L26.3166667,5.58888889 L39.1740741,5.58888889 L39.4685185,11.4777778 C40.45,11.7722222 41.3333333,12.1648148 42.412963,12.7537037 L46.7314815,8.92592593 L55.662963,17.8574074 L51.9333333,22.1759259 C52.4240741,23.2555556 52.8166667,24.237037 53.2092593,25.1203704 L58.9018519,25.4148148 L58.9018519,38.2722222 L53.2092593,38.5666667 C52.9148148,39.5481481 52.4240741,40.6277778 51.9333333,41.6092593 L55.5648148,45.9277778 L46.6333333,54.8592593 L42.3148148,51.0314815 C41.2351852,51.5222222 40.3518519,51.9148148 39.3703704,52.3074074 L39.1740741,58 Z M30.1444444,53.9759259 L35.3462963,53.9759259 L35.6407407,49.0685185 L37.112963,48.6759259 C38.5851852,48.2833333 40.0574074,47.6944444 41.8240741,46.712963 L43.1,46.0259259 L46.7314815,49.2648148 L50.362963,45.6333333 L47.2222222,42.0018519 L48.0074074,40.7259259 C48.7925926,39.3518519 49.4796296,37.6833333 49.8722222,36.112963 L50.2648148,34.6407407 L55.0740741,34.3462963 L55.0740741,29.1444444 L50.1666667,28.85 L49.7740741,27.3777778 C49.3814815,25.9055556 48.7925926,24.4333333 47.8111111,22.6666667 L47.1240741,21.4888889 L50.1666667,17.8574074 L46.5351852,14.2259259 L42.9037037,17.4648148 L41.7259259,16.7777778 C39.9592593,15.7962963 38.487037,15.2074074 37.0148148,14.8148148 L35.5425926,14.4222222 L35.2481481,9.51481481 L30.0462963,9.51481481 L29.7518519,14.6185185 L28.2796296,15.0111111 C26.9055556,15.4037037 25.6296296,15.8944444 23.9611111,16.8759259 L22.6851852,17.6611111 L18.7592593,14.2259259 L15.1277778,17.8574074 L18.562963,21.7833333 L17.7777778,23.0592593 C17.0907407,24.237037 16.5018519,25.7092593 16.0111111,27.4759259 L15.6185185,28.85 L10.4166667,29.1444444 L10.4166667,34.3462963 L15.6185185,34.6407407 L16.0111111,36.112963 C16.4037037,37.7814815 16.9925926,39.1555556 17.7777778,40.4314815 L18.562963,41.7074074 L15.1277778,45.6333333 L18.7592593,49.2648148 L22.6851852,46.0259259 L23.9611111,46.8111111 C25.3351852,47.5962963 26.6111111,48.1851852 28.2796296,48.5777778 L29.7518519,48.9703704 L30.1444444,53.9759259 Z\" />\n          <path d=\"M32.8068966,45.2275862 C25.2758621,45.2275862 19.1931034,39.2413793 19.1931034,31.9034483 C19.1931034,24.3724138 25.2758621,18.2896552 32.8068966,18.2896552 C40.337931,18.2896552 46.1310345,24.2758621 46.1310345,31.9034483 C46.2275862,39.2413793 40.1448276,45.2275862 32.8068966,45.2275862 Z M32.8068966,22.6344828 C27.6896552,22.6344828 23.537931,26.7862069 23.537931,31.9034483 C23.537931,36.9241379 27.6896552,40.9793103 32.8068966,40.9793103 C37.8275862,40.9793103 41.8827586,36.9241379 41.8827586,31.9034483 C41.8827586,26.6896552 37.9241379,22.6344828 32.8068966,22.6344828 Z\" />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/globe.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base, {BaseProps} from './base';\n\nexport default class Globe extends Component<Partial<BaseProps>> {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-globe'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"17\" viewBox=\"0 0 16 17\" xmlns=\"http://www.w3.org/2000/svg\">\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M6.38584 1.68702C6.90402 1.56472 7.44445 1.5 8 1.5C9.23055 1.5 10.3869 1.81752 11.3917 2.37511C12.4112 2.94093 13.2749 3.75396 13.9013 4.73321C14.4377 5.5719 14.6851 6.188 14.7456 6.62353C14.9114 7.22072 15 7.85001 15 8.5C15 10.527 14.1385 12.3527 12.7616 13.631C11.9832 14.3623 10.9811 14.9754 9.75268 15.2788C9.19254 15.4232 8.60524 15.5 8 15.5C4.63461 15.5 1.82391 13.1251 1.15243 9.95976C1.05442 9.54484 1 9.06056 1 8.5C1 8.49999 1 8.50001 1 8.5C1 5.19522 3.29014 2.42529 6.36999 1.69074C6.37528 1.68948 6.38056 1.68824 6.38584 1.68702ZM14 8.5C14 9.08044 13.9176 9.64163 13.7638 10.1725C12.755 9.00544 10.8189 8.33275 9.51221 8.62684C7.80468 9.01112 7.72976 10.9923 8.46642 12.0631C8.83704 12.6018 8.91074 13.1143 8.98743 13.6476C9.02242 13.891 9.05804 14.1387 9.12278 14.3951C8.75907 14.464 8.38374 14.5 8 14.5C6.01503 14.5 4.2552 13.5361 3.16302 12.0508C3.96089 12.1461 4.82827 11.8298 5.48008 11.2844C6.69976 10.2638 6.57435 9.20516 6.44788 8.13751C6.40898 7.80916 6.36999 7.47997 6.36999 7.15077C6.36999 6.39461 6.69305 5.95645 7.06097 5.45747C7.37404 5.03287 7.71959 4.56422 7.92621 3.81811C8.07377 3.28525 8.08049 2.84344 7.9823 2.50003C7.9882 2.50001 7.9941 2.5 8 2.5C8.67572 2.5 9.32534 2.6117 9.93142 2.81764C9.29673 3.3956 9.1115 4.40569 9.43855 5.14271C9.79682 5.95012 10.3908 6.13003 10.9903 6.31161C11.2417 6.38774 11.494 6.46415 11.7303 6.58722C12.5302 7.00383 13.1339 7.29592 13.9013 7.47784L13.9138 7.48073C13.9705 7.81201 14 8.15256 14 8.5ZM6.62925 2.65729C6.77252 2.62674 6.8659 2.63612 6.91413 2.64841C6.95864 2.65974 6.97132 2.67386 6.97807 2.68199C6.98858 2.69464 7.1408 2.9073 6.96248 3.55123C6.80646 4.11462 6.56084 4.44896 6.24765 4.87526L6.17228 4.97803C5.80911 5.47481 5.36999 6.12055 5.36999 7.15077C5.36999 7.54169 5.41575 7.92659 5.45353 8.24443L5.46209 8.31662C5.50393 8.67118 5.53234 8.9484 5.52363 9.2086C5.50906 9.64448 5.39669 10.0503 4.83835 10.5175C4.22693 11.0291 3.48249 11.1744 2.9997 10.9895C2.7365 10.8887 2.35103 10.6037 2.14406 9.81283C2.04975 9.3903 2 8.95096 2 8.5C2 8.49353 2.00001 8.48706 2.00003 8.48059C2.00034 8.38358 2.00295 8.28711 2.00781 8.19124C2.1449 5.48676 4.07275 3.25474 6.62925 2.65729ZM13.3119 11.2912C13.0234 11.834 12.6167 12.3926 12.0926 12.8876C11.5179 13.4239 10.8378 13.8485 10.0867 14.1272C10.0403 13.9356 10.0098 13.7315 9.97582 13.4953L9.97119 13.4631C9.89588 12.9383 9.79404 12.2285 9.29027 11.4963C9.07472 11.183 8.96775 10.6974 9.05201 10.2901C9.12368 9.94368 9.31182 9.69695 9.73177 9.60244C10.2869 9.47751 11.1638 9.61414 11.9811 10.0425C12.6583 10.3974 13.1122 10.8535 13.3119 11.2912ZM11.1022 3.36315C12.235 4.04873 13.1228 5.09845 13.6033 6.35005C13.1774 6.20083 12.7565 5.99418 12.1922 5.70029C11.9064 5.55147 11.6211 5.45906 11.4003 5.39112C11.351 5.37595 11.3054 5.36211 11.2629 5.34919C11.0939 5.29784 10.9724 5.26095 10.8505 5.21158C10.622 5.11901 10.4782 5.02021 10.3526 4.73711C10.2389 4.48099 10.2524 4.0818 10.4376 3.76614C10.5684 3.5432 10.7707 3.37786 11.1022 3.36315Z\"\n          />\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/hash.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Hash extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-hash'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M33.3763 45.978H24.1434L20.3011 64H8.88889L12.7312 45.978H0V37.9341H14.4516L16.9749 26.3297H4.30108V18.2857H18.6953L22.6523 0H34.0072L30.1075 18.2857H39.3405L43.2975 0H54.7097L50.7527 18.2857H64V26.3297H49.0323L46.509 37.9341H59.6416V45.978H44.7885L40.9462 64H29.5341L33.3763 45.978ZM25.8638 37.9341H35.0968L37.6201 26.3297H28.3871L25.8638 37.9341Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/help.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class Help extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-info'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 16 16\" {...this.props}>\n        <path d=\"M8 15.375C3.933 15.375.625 12.067.625 8S3.933.625 8 .625 15.375 3.933 15.375 8 12.067 15.375 8 15.375zM8 1.89C4.608 1.89 1.868 4.63 1.868 8A6.138 6.138 0 008 14.132 6.124 6.124 0 0014.132 8C14.11 4.629 11.372 1.89 8 1.89z\" />\n        <path d=\"M7.81 10.044a.634.634 0 01-.632-.632c0-.97.632-1.812 1.538-2.086.4-.127.675-.485.654-.906 0-.759-.632-1.391-1.391-1.391s-1.39.632-1.39 1.39a.634.634 0 01-.633.633.634.634 0 01-.632-.632C5.366 4.966 6.546 3.786 8 3.786a2.635 2.635 0 012.634 2.634c0 .969-.632 1.812-1.538 2.086-.4.126-.675.484-.653.906a.648.648 0 01-.633.632zM7.81 12.109a.759.759 0 100-1.517.759.759 0 000 1.517z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/histogram.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Histogram extends React.Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-histogram'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(7.500000, 7.500000)\">\n          <path\n            d=\"M5,40.593203 L16.7666161,40.593203 L16.7666161,10 L5,10 L5,40.593203 L5,40.593203 Z M33.2333839,40.593203 L45,40.593203 L45,10 L33.2333839,10 L33.2333839,40.593203 L33.2333839,40.593203 Z M30.883308,40.5892837 L30.883308,26.4693451 L19.116692,26.4693451 L19.116692,40.5892837 L30.883308,40.5892837 Z\"\n            id=\"Shape\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/horizontal-resize-handle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class HorizontalResizeHandle extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-horizontalresizehandle'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect x=\"15\" y=\"6\" width=\"1\" height=\"14\" rx=\"0.5\" transform=\"rotate(90 15 6)\" />\n        <rect x=\"15\" y=\"9\" width=\"1\" height=\"14\" rx=\"0.5\" transform=\"rotate(90 15 9)\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/index.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as Base} from './base';\n\n// eslint-disable-next-line prettier/prettier\nexport type {BaseProps} from './base';\n\nexport {default as Add} from './add';\nexport {default as AnchorWindow} from './anchor_window';\nexport {default as ArrowDown} from './arrow-down';\nexport {default as ArrowDownAlt} from './arrow-down-alt';\nexport {default as ArrowDownSolid} from './arrow-down-alt';\nexport {default as ArrowDownFull} from './arrow-down-full';\nexport {default as ArrowLeft} from './arrow-left';\nexport {default as ArrowRight} from './arrow-right';\nexport {default as ArrowUpSolid} from './arrow-up-solid';\nexport {default as ArrowUpAlt} from './arrow-up-alt';\nexport {default as ArrowUp} from './arrow-up';\nexport {default as BaseMap} from './base-map';\nexport {default as Bug} from './bug';\nexport {default as Cancel} from './cancel';\nexport {default as Checkmark} from './checkmark';\nexport {default as Clipboard} from './clipboard';\nexport {default as Clock} from './clock';\nexport {default as Close} from './close';\nexport {default as Cloud} from './cloud';\nexport {default as Copy} from './copy';\nexport {default as Cube3d} from './cube-3d';\nexport {default as DataTable} from './data-table';\nexport {default as Db} from './db';\nexport {default as Delete} from './delete';\nexport {default as Docs} from './docs';\nexport {default as Docs2} from './docs2';\nexport {default as DragNDrop} from './drag-n-drop';\nexport {default as DraggableDots} from './draggable-dots';\nexport {default as Edit} from './edit';\nexport {default as Email} from './email';\nexport {default as Expand} from './expand';\nexport {default as EyeSeen} from './eye-seen';\nexport {default as EyeUnseen} from './eye-unseen';\nexport {default as File} from './file';\nexport {default as Files} from './files';\nexport {default as FileType} from './file-type';\nexport {default as FilterFunnel} from './filter-funnel';\nexport {default as FreeWindow} from './free-window';\nexport {default as Gear} from './gear';\nexport {default as Hash} from './hash';\nexport {default as Help} from './help';\nexport {default as Histogram} from './histogram';\nexport {default as HorizontalResizeHandle} from './horizontal-resize-handle';\nexport {default as IconWrapper} from './base';\nexport {default as Info} from './info';\nexport {default as Layers} from './layers';\nexport {default as LeftArrow} from './left-arrow';\nexport {default as Legend} from './legend';\nexport {default as LineChart} from './line-chart';\nexport {default as Logout} from './logout';\nexport {default as Login} from './login';\nexport {default as MapIcon} from './map-icon';\nexport {default as MapPin} from './map-pin';\nexport {default as Minus} from './minus';\nexport {default as Pause} from './pause';\nexport {default as Picture} from './picture';\nexport {default as Pin} from './pin';\nexport {default as PointerClick} from './pointer-click';\nexport {default as Play} from './play';\nexport {default as Reduce} from './reduce';\nexport {default as Reset} from './reset';\nexport {default as Save} from './save';\nexport {default as Save2} from './save2';\nexport {default as Share} from './share';\nexport {default as Speed} from './speed';\nexport {default as SquareSelect} from './square-select';\nexport {default as Settings} from './settings';\nexport {default as Search} from './search';\nexport {default as Split} from './split';\nexport {default as Trash} from './trash';\nexport {default as VertDots} from './vert-dots';\nexport {default as VertThreeDots} from './vert-three-dots';\nexport {default as CodeAlt} from './code-alt';\nexport {default as Warning} from './warning';\nexport {default as WarningSign} from './warning-sign';\nexport {default as DrawPolygon} from './draw-polygon';\nexport {default as Polygon} from './polygon';\nexport {default as Rectangle} from './rectangle';\nexport {default as TimelineMarker} from './timeline-marker';\nexport {default as OrderByList} from './order-by-list';\nexport {default as OrderByDataset} from './order-by-dataset';\nexport {default as Messages} from './messages';\nexport {default as Crosshairs} from './crosshairs';\nexport {default as CursorClick} from './cursor-click';\nexport {default as CursorPoint} from './cursor-point';\nexport {default as Calendar} from './calendar';\nexport {default as LocationMarker} from './location-marker';\nexport {default as Globe} from './globe';\nexport {default as Sun} from './sun';\nexport {default as Sunrise} from './sunrise';\nexport {default as Sunset} from './sunset';\nexport {default as Moon} from './moon';\nexport {default as ArrowDownSmall} from './arrow-down-small';\nexport {default as MagicWand} from './effects/magic-wand';\nexport {default as LightAndShadowEffectIcon} from './effects/light-and-shadow';\nexport {default as InkEffectIcon} from './effects/ink';\nexport {default as BrightnessContrastEffectIcon} from './effects/brightness-contrast';\nexport {default as HueSaturationEffectIcon} from './effects/hue-saturation';\nexport {default as VibranceEffectIcon} from './effects/vibrance';\nexport {default as SepiaEffectIcon} from './effects/sepia';\nexport {default as DotScreenEffectIcon} from './effects/dot-screen';\nexport {default as ColorHalftoneEffectIcon} from './effects/color-halftone';\nexport {default as NoiseEffectIcon} from './effects/noise';\nexport {default as TriangleBlurEffectIcon} from './effects/triangle-blur';\nexport {default as ZoomBlurEffectIcon} from './effects/zoom-blur';\nexport {default as TiltShiftEffectIcon} from './effects/tilt-shift';\nexport {default as EdgeWorkEffectIcon} from './effects/edge-work';\nexport {default as VignetteEffectIcon} from './effects/vignette';\nexport {default as MagnifyEffectIcon} from './effects/magnify';\nexport {default as HexagonalPixelateEffectIcon} from './effects/hexagonal-pixelate';\nexport {default as ZoomIn} from './zoom-in';\nexport {default as ZoomOut} from './zoom-out';\n"
  },
  {
    "path": "src/components/src/common/icons/info.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Info extends Component<Partial<BaseProps> & {stroke?: string}> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-info',\n    stroke: '#FFF'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <circle\n          cx=\"25\"\n          cy=\"25\"\n          fill=\"none\"\n          r=\"24\"\n          stroke={this.props.stroke}\n          strokeLinecap=\"round\"\n          strokeMiterlimit=\"10\"\n          strokeWidth=\"2\"\n        />\n        <path d=\"M23.779,16.241c-0.216,0-0.357-0.144-0.357-0.359v-2.618c0-0.215,0.142-0.359,0.357-0.359h2.439  c0.215,0,0.359,0.144,0.359,0.359v2.618c0,0.215-0.145,0.359-0.359,0.359H23.779z M23.852,37.293c-0.215,0-0.358-0.143-0.358-0.358  V20.473c0-0.215,0.144-0.359,0.358-0.359h2.295c0.216,0,0.359,0.144,0.359,0.359v16.462c0,0.216-0.144,0.358-0.359,0.358H23.852z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/layers.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Layers extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-layers'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z\" />\n        <path d=\"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12\" />\n        <path d=\"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17\" />{' '}\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/left-arrow.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class LeftArrow extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-left-arrow'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <path d=\"M39.425 53.21L23.16 36.947l-4.242-4.243a1 1 0 0 1 0-1.414l4.242-4.243 16.264-16.263a1 1 0 0 1 1.414 0l4.242 4.242a1 1 0 0 1 0 1.414L29.525 31.997l15.556 15.556a1 1 0 0 1 0 1.414L40.84 53.21a1 1 0 0 1-1.414 0z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/legend.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Legend extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-legend'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\" />\n        <rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\" />\n        <path d=\"M14 4h7\" />\n        <path d=\"M14 9h7\" />\n        <path d=\"M14 15h7\" />\n        <path d=\"M14 20h7\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/line-chart.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class LineChart extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-linechart'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M53.4647408,17.8549995L35.8387756,35.4809608L25.5911236,25.2333088L6.607347,44.2427025\n\tl3.6122975,3.6122971l15.371479-15.3714752l10.2476521,10.2476501l21.2638779-21.2382584L53.4647408,17.8549995z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/location-marker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class LocationMarker extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-location-marker'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_118_4762)\">\n            <path d=\"M15 7.5H11.965C11.8528 6.62017 11.4517 5.80249 10.8246 5.17529C10.1975 4.54809 9.37982 4.14689 8.5 4.03465V1H7.5V4.03465C6.62018 4.14689 5.80254 4.54809 5.17541 5.17529C4.54827 5.80249 4.14715 6.62017 4.035 7.5H1V8.5H4.035C4.14715 9.37983 4.54827 10.1975 5.17541 10.8247C5.80254 11.4519 6.62018 11.8531 7.5 11.9654V15H8.5V11.9654C9.37986 11.8532 10.1976 11.452 10.8247 10.8248C11.4519 10.1976 11.8531 9.37986 11.9653 8.5H15V7.5ZM8 11C7.40666 11 6.82664 10.8241 6.33329 10.4944C5.83994 10.1648 5.45542 9.69623 5.22836 9.14805C5.0013 8.59987 4.94189 7.99667 5.05764 7.41473C5.1734 6.83279 5.45912 6.29824 5.87868 5.87868C6.29824 5.45912 6.83279 5.1734 7.41473 5.05764C7.99667 4.94189 8.59987 5.0013 9.14805 5.22836C9.69623 5.45542 10.1648 5.83994 10.4944 6.33329C10.8241 6.82664 11 7.40666 11 8C10.9991 8.79538 10.6828 9.55794 10.1204 10.1204C9.55794 10.6828 8.79538 10.9991 8 11Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_118_4762\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/login.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Login extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-login'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon\n          id=\"Path\"\n          points=\"26.9730089 40.981391 30.7141656 44.7225477 44.0754395 31.3612739 30.7141656 18 26.9730089 21.7411567 33.9208713 28.6890191 8 28.6890191 8 34.0335286 33.9208713 34.0335286\"\n        />\n        <path d=\"M50.7560765,8 L13.3445095,8 C10.4050293,8 8,10.4050293 8,13.3445095 L8,24.0335286 L13.3445095,24.0335286 L13.3445095,13.3445095 L50.7560765,13.3445095 L50.7560765,50.7560765 L13.3445095,50.7560765 L13.3445095,40.0670573 L8,40.0670573 L8,50.7560765 C8,53.6955566 10.4050293,56.1005859 13.3445095,56.1005859 L50.7560765,56.1005859 C53.6955566,56.1005859 56.1005859,53.6955566 56.1005859,50.7560765 L56.1005859,13.3445095 C56.1005859,10.4050293 53.6955566,8 50.7560765,8 Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/logout.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Logout extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-logout'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon points=\"27.1024306 41.981391 23.3612739 45.7225477 10 32.3612739 23.3612739 19 27.1024306 22.7411567 20.1545681 29.6890191 46.0754395 29.6890191 46.0754395 35.0335286 20.1545681 35.0335286\" />\n        <path d=\"M50.7560765,8 L13.3445095,8 C10.4050293,8 8,10.4050293 8,13.3445095 L8,24.0335286 L13.3445095,24.0335286 L13.3445095,13.3445095 L50.7560765,13.3445095 L50.7560765,50.7560765 L13.3445095,50.7560765 L13.3445095,40.0670573 L8,40.0670573 L8,50.7560765 C8,53.6955566 10.4050293,56.1005859 13.3445095,56.1005859 L50.7560765,56.1005859 C53.6955566,56.1005859 56.1005859,53.6955566 56.1005859,50.7560765 L56.1005859,13.3445095 C56.1005859,10.4050293 53.6955566,8 50.7560765,8 Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/map-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nclass MapIcon extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-map-icon',\n    totalColor: 1\n  };\n\n  render() {\n    return (\n      <Base {...this.props} viewBox={'0 0 602 602'}>\n        <g>\n          <path\n            d=\"M573.864,323.679l25.6-201.737L409.988,50.046L197.993,151.289L0,67.678l27.935,220.105L2.223,506.009l208.665,39.135\n\t\t\tl200.136-38.125l189.865,43.823L573.864,323.679z M210.855,522.625L26.64,488.076l23.732-199.335L26.803,103.007l171.761,72.543\n\t\t\tL410.987,74.083l164.331,62.361l-23.755,187.142l23.648,198.602l-163.764-37.782L210.855,522.625z\"\n          />\n          <path\n            d=\"M506.021,405.449c-5.509,0-10.604,1.596-15.049,4.167c-17.531-12.832-35.583-27.904-36.47-47.735\n\t\t\tc-1.217-27.195,15.439-53.227-3.623-78.182c-18.625-24.382-66.301-7.944-84.73-26.264c2.873-4.617,4.593-10.01,4.593-15.844\n\t\t\tc0-16.671-13.519-30.192-30.187-30.192c-16.68,0-30.192,13.515-30.192,30.192c0,9.561,4.534,17.98,11.467,23.513\n\t\t\tc-2.907,7.358-4.729,15.167-5.048,23.141c-0.496,12.596,3.121,25.126,0.391,37.634c-2.252,10.391-16.875,9.729-24.757,9.788\n\t\t\tc-19.875,0.177-48.202-3.57-61.023,10.462c-3.783-2.843-8.207-4.812-13.077-5.621c-2.87-25.848,2.098-51.102-20.824-70.985\n\t\t\tc-14.736-12.794-38.846-18.344-52.438-32.719c2.873-4.619,4.61-10.031,4.61-15.871c0-16.674-13.515-30.198-30.189-30.198\n\t\t\tc-16.668,0-30.192,13.515-30.192,30.198c0,16.668,13.515,30.183,30.192,30.183c5.562,0,10.707-1.604,15.179-4.229\n\t\t\tc10.048,10.083,23.915,16.784,36.892,23.56c16.529,8.627,28.844,19.698,31.108,39.283c1.241,10.734,0.762,21.291,1.46,31.854\n\t\t\tc-12.135,3.918-20.978,15.16-20.978,28.602c0,16.681,13.515,30.192,30.195,30.192c16.668,0,30.189-13.512,30.189-30.192\n\t\t\tc0-3.942-0.81-7.701-2.189-11.159c10.024-18.063,56.066-5.745,73.423-11.55c12.644-4.238,17.13-16.083,18.247-28.365\n\t\t\tc1.063-11.473-2.637-22.757-0.91-34.283c0.686-4.69,1.867-9.224,3.439-13.55c1.644,0.271,3.311,0.502,5.024,0.502\n\t\t\tc5.308,0,10.22-1.489,14.559-3.904c18.684,18.713,55.768,6.174,79.328,20.271c28.017,16.754-0.792,64.046,8.051,89.309\n\t\t\tc6.123,17.472,22.13,30.499,37.994,42.232c-2.902,4.64-4.664,10.084-4.664,15.953c0,16.681,13.507,30.192,30.198,30.192\n\t\t\tc16.657,0,30.192-13.512,30.192-30.192C536.213,418.979,522.689,405.449,506.021,405.449z\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n\nexport default MapIcon;\n"
  },
  {
    "path": "src/components/src/common/icons/map-pin.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class MapPin extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-map-pin'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M18 8c0 3.613-3.869 7.429-5.393 8.795a1 1 0 0 1-1.214 0C9.87 15.429 6 11.613 6 8a6 6 0 0 1 12 0\" />\n        <circle cx=\"12\" cy=\"8\" r=\"2\" />\n        <path d=\"M8.714 14h-3.71a1 1 0 0 0-.948.683l-2.004 6A1 1 0 0 0 3 22h18a1 1 0 0 0 .948-1.316l-2-6a1 1 0 0 0-.949-.684h-3.712\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/messages.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Messages extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-messages'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n        <path d=\"M13 8H7\" />\n        <path d=\"M17 12H7\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/minus.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Minus extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-minus'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g>\n          <path d=\"M55 36H9a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h46a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1z\" />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/moon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class Moon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-moon'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_85_57878)\">\n            <path d=\"M6.75138 2.7068C6.52216 3.68541 6.49097 4.69999 6.65965 5.69083C6.82833 6.68168 7.19347 7.62878 7.73358 8.47643C8.2737 9.32407 8.97788 10.0551 9.80471 10.6266C10.6315 11.1981 11.5643 11.5984 12.5481 11.8041C12.0309 12.3392 11.4113 12.765 10.7264 13.0561C10.0414 13.3472 9.30494 13.4977 8.56068 13.4987C8.49143 13.4987 8.42158 13.5013 8.35178 13.4987C7.05611 13.4529 5.81743 12.9547 4.85087 12.0906C3.88431 11.2265 3.25093 10.0512 3.06075 8.76875C2.87057 7.48629 3.13559 6.17773 3.80979 5.07034C4.48399 3.96295 5.52478 3.12669 6.75138 2.7068ZM7.49013 1.5C7.46086 1.50005 7.43165 1.50266 7.40283 1.5078C5.81071 1.79059 4.37954 2.65251 3.38498 3.92753C2.39042 5.20254 1.90282 6.80048 2.0161 8.41355C2.12938 10.0266 2.83552 11.5407 3.99852 12.6642C5.16152 13.7877 6.69911 14.4412 8.31513 14.4986C8.39718 14.5016 8.47923 14.4986 8.56058 14.4986C9.60999 14.4992 10.6441 14.2471 11.5755 13.7636C12.5069 13.2801 13.3082 12.5794 13.9116 11.7208C13.9605 11.6469 13.9891 11.5613 13.9944 11.4728C13.9997 11.3842 13.9816 11.2958 13.9419 11.2165C13.9022 11.1372 13.8423 11.0698 13.7682 11.021C13.6941 10.9722 13.6085 10.9438 13.5199 10.9386C12.521 10.851 11.5556 10.5349 10.6984 10.0146C9.84118 9.49439 9.11506 8.784 8.57617 7.93837C8.03728 7.09274 7.70006 6.13452 7.59057 5.13777C7.48108 4.14102 7.60226 3.13245 7.94473 2.19C7.97387 2.1146 7.98457 2.03332 7.97594 1.95295C7.96731 1.87257 7.93961 1.79542 7.89514 1.72792C7.85066 1.66042 7.79071 1.60452 7.72026 1.56487C7.64981 1.52523 7.57091 1.50299 7.49013 1.5Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_85_57878\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/order-by-dataset.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nconst OrderByDataset = ({\n  height = '20px',\n  fill = 'currentColor',\n  viewBox = '0 0 24 24',\n  predefinedClassName = 'data-ex-icons-order-by-dataset',\n  ...restProps\n}: Partial<BaseProps>) => {\n  const props = {\n    height,\n    fill,\n    viewBox,\n    predefinedClassName,\n    ...restProps\n  };\n  return (\n    <Base {...props}>\n      <path\n        d=\"M14.7225 13.7778C14.7225 14.1294 14.6183 14.4731 14.4229 14.7655C14.2276 15.0578 13.9499 15.2857 13.6251 15.4202C13.3003 15.5548 12.9428 15.59 12.5979 15.5214C12.2531 15.4528 11.9363 15.2835 11.6877 15.0349C11.4391 14.7862 11.2697 14.4695 11.2012 14.1246C11.1326 13.7798 11.1678 13.4223 11.3023 13.0975C11.4369 12.7726 11.6647 12.495 11.9571 12.2996C12.2494 12.1043 12.5932 12 12.9448 12C13.4163 12 13.8685 12.1873 14.2018 12.5207C14.5352 12.8541 14.7225 13.3063 14.7225 13.7778ZM23.5003 13.7778C23.5003 13.542 23.4067 13.3159 23.24 13.1492C23.0733 12.9825 22.8472 12.8889 22.6114 12.8889H18.2781C18.0424 12.8889 17.8163 12.9825 17.6496 13.1492C17.4829 13.3159 17.3892 13.542 17.3892 13.7778C17.3892 14.0135 17.4829 14.2396 17.6496 14.4063C17.8163 14.573 18.0424 14.6667 18.2781 14.6667H22.6114C22.8472 14.6667 23.0733 14.573 23.24 14.4063C23.4067 14.2396 23.5003 14.0135 23.5003 13.7778ZM12.9448 18.2222C12.5932 18.2222 12.2494 18.3265 11.9571 18.5218C11.6647 18.7172 11.4369 18.9948 11.3023 19.3197C11.1678 19.6445 11.1326 20.002 11.2012 20.3468C11.2697 20.6917 11.4391 21.0085 11.6877 21.2571C11.9363 21.5057 12.2531 21.675 12.5979 21.7436C12.9428 21.8122 13.3003 21.777 13.6251 21.6425C13.9499 21.5079 14.2276 21.28 14.4229 20.9877C14.6183 20.6953 14.7225 20.3516 14.7225 20C14.7225 19.5285 14.5352 19.0763 14.2018 18.7429C13.8685 18.4095 13.4163 18.2222 12.9448 18.2222ZM23.5003 20C23.5003 19.7643 23.4067 19.5382 23.24 19.3715C23.0733 19.2048 22.8472 19.1111 22.6114 19.1111H18.2781C18.0424 19.1111 17.8163 19.2048 17.6496 19.3715C17.4829 19.5382 17.3892 19.7643 17.3892 20C17.3892 20.2357 17.4829 20.4618 17.6496 20.6285C17.8163 20.7952 18.0424 20.8889 18.2781 20.8889H22.6114C22.8472 20.8889 23.0733 20.7952 23.24 20.6285C23.4067 20.4618 23.5003 20.2357 23.5003 20Z\"\n        fill={props.fill}\n      />\n      <path\n        d=\"M6.77778 6C6.42617 6 6.08245 6.10426 5.7901 6.29961C5.49774 6.49495 5.26988 6.7726 5.13533 7.09745C5.00077 7.4223 4.96556 7.77975 5.03416 8.1246C5.10276 8.46946 5.27207 8.78623 5.5207 9.03486C5.76933 9.28348 6.0861 9.4528 6.43095 9.5214C6.77581 9.58999 7.13326 9.55479 7.4581 9.42023C7.78295 9.28567 8.0606 9.05781 8.25595 8.76546C8.45129 8.4731 8.55556 8.12939 8.55556 7.77778C8.55556 7.30628 8.36826 6.8541 8.03486 6.5207C7.70146 6.1873 7.24927 6 6.77778 6ZM23.3333 7.77778C23.3333 7.54203 23.2397 7.31594 23.073 7.14924C22.9063 6.98254 22.6802 6.88889 22.4444 6.88889H12.1111C11.8754 6.88889 11.6493 6.98254 11.4826 7.14924C11.3159 7.31594 11.2222 7.54203 11.2222 7.77778C11.2222 8.01353 11.3159 8.23962 11.4826 8.40632C11.6493 8.57302 11.8754 8.66667 12.1111 8.66667H22.4444C22.6802 8.66667 22.9063 8.57302 23.073 8.40632C23.2397 8.23962 23.3333 8.01353 23.3333 7.77778Z\"\n        fill={props.fill}\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.2998 9H6.2998V19.5V20V20.5H11.2998V19.5H7.2998V14.4H11.4004V13.4H7.2998V9Z\"\n        fill={props.fill}\n      />\n    </Base>\n  );\n};\nexport default OrderByDataset;\n"
  },
  {
    "path": "src/components/src/common/icons/order-by-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nconst OrderByList = ({\n  height = '20px',\n  fill = 'currentColor',\n  viewBox = '0 0 24 24',\n  predefinedClassName = 'data-ex-icons-order-by-list',\n  ...restProps\n}: Partial<BaseProps>) => {\n  const props = {\n    height,\n    fill,\n    viewBox,\n    predefinedClassName,\n    ...restProps\n  };\n  return (\n    <Base {...props}>\n      <path\n        d=\"M8.55556 14C8.55556 14.3516 8.45129 14.6953 8.25595 14.9877C8.0606 15.28 7.78295 15.5079 7.4581 15.6425C7.13326 15.777 6.77581 15.8122 6.43095 15.7436C6.0861 15.675 5.76933 15.5057 5.5207 15.2571C5.27207 15.0085 5.10276 14.6917 5.03416 14.3468C4.96556 14.002 5.00077 13.6445 5.13533 13.3197C5.26988 12.9948 5.49774 12.7172 5.7901 12.5218C6.08245 12.3265 6.42617 12.2222 6.77778 12.2222C7.24927 12.2222 7.70146 12.4095 8.03486 12.7429C8.36826 13.0763 8.55556 13.5285 8.55556 14ZM23.3333 14C23.3333 13.7643 23.2397 13.5382 23.073 13.3715C22.9063 13.2048 22.6802 13.1111 22.4444 13.1111H12.1111C11.8754 13.1111 11.6493 13.2048 11.4826 13.3715C11.3159 13.5382 11.2222 13.7643 11.2222 14C11.2222 14.2357 11.3159 14.4618 11.4826 14.6285C11.6493 14.7952 11.8754 14.8889 12.1111 14.8889H22.4444C22.6802 14.8889 22.9063 14.7952 23.073 14.6285C23.2397 14.4618 23.3333 14.2357 23.3333 14ZM6.77778 6C6.42617 6 6.08245 6.10426 5.7901 6.29961C5.49774 6.49495 5.26988 6.7726 5.13533 7.09745C5.00077 7.4223 4.96556 7.77975 5.03416 8.1246C5.10276 8.46946 5.27207 8.78623 5.5207 9.03486C5.76933 9.28348 6.0861 9.4528 6.43095 9.5214C6.77581 9.58999 7.13326 9.55479 7.4581 9.42023C7.78295 9.28567 8.0606 9.05781 8.25595 8.76546C8.45129 8.4731 8.55556 8.12939 8.55556 7.77778C8.55556 7.30628 8.36826 6.8541 8.03486 6.5207C7.70146 6.1873 7.24927 6 6.77778 6ZM23.3333 7.77778C23.3333 7.54203 23.2397 7.31594 23.073 7.14924C22.9063 6.98254 22.6802 6.88889 22.4444 6.88889H12.1111C11.8754 6.88889 11.6493 6.98254 11.4826 7.14924C11.3159 7.31594 11.2222 7.54203 11.2222 7.77778C11.2222 8.01353 11.3159 8.23962 11.4826 8.40632C11.6493 8.57302 11.8754 8.66667 12.1111 8.66667H22.4444C22.6802 8.66667 22.9063 8.57302 23.073 8.40632C23.2397 8.23962 23.3333 8.01353 23.3333 7.77778ZM6.77778 18.4444C6.42617 18.4444 6.08245 18.5487 5.7901 18.7441C5.49774 18.9394 5.26988 19.217 5.13533 19.5419C5.00077 19.8667 4.96556 20.2242 5.03416 20.569C5.10276 20.9139 5.27207 21.2307 5.5207 21.4793C5.76933 21.7279 6.0861 21.8972 6.43095 21.9658C6.77581 22.0344 7.13326 21.9992 7.4581 21.8647C7.78295 21.7301 8.0606 21.5023 8.25595 21.2099C8.45129 20.9175 8.55556 20.5738 8.55556 20.2222C8.55556 19.7507 8.36826 19.2985 8.03486 18.9651C7.70146 18.6317 7.24927 18.4444 6.77778 18.4444ZM23.3333 20.2222C23.3333 19.9865 23.2397 19.7604 23.073 19.5937C22.9063 19.427 22.6802 19.3333 22.4444 19.3333H12.1111C11.8754 19.3333 11.6493 19.427 11.4826 19.5937C11.3159 19.7604 11.2222 19.9865 11.2222 20.2222C11.2222 20.458 11.3159 20.6841 11.4826 20.8508C11.6493 21.0175 11.8754 21.1111 12.1111 21.1111H22.4444C22.6802 21.1111 22.9063 21.0175 23.073 20.8508C23.2397 20.6841 23.3333 20.458 23.3333 20.2222Z\"\n        fill={props.fill}\n      />\n    </Base>\n  );\n};\n\nexport default OrderByList;\n"
  },
  {
    "path": "src/components/src/common/icons/pause.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Pause extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n\n    predefinedClassName: 'data-ex-icons-pause'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\" />\n        <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/picture.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Picture extends React.Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n\n    predefinedClassName: 'data-ex-icons-minus'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n        <circle cx=\"9\" cy=\"9\" r=\"2\" />\n        <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />{' '}\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/pin.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Pin extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-pin'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M12 17v5\" />\n        <path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/play.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Play extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n\n    predefinedClassName: 'data-ex-icons-play'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <polygon points=\"6 3 20 12 6 21 6 3\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/pointer-click.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class PointerClick extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-pointer-click'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M14 4.1 12 6\" />\n        <path d=\"m5.1 8-2.9-.8\" />\n        <path d=\"m6 12-1.9 2\" />\n        <path d=\"M7.2 2.2 8 5.1\" />\n        <path d=\"M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/polygon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class DrawPolygon extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-polygon',\n    viewBox: '0 0 22 18'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M9 2L18 6L20 16L2 13L9 2Z\"\n          stroke=\"currentColor\"\n          fill=\"transparent\"\n          strokeWidth=\"1.5\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M9 4C10.1046 4 11 3.10457 11 2C11 0.89543 10.1046 0 9 0C7.89543 0 7 0.89543 7 2C7 3.10457 7.89543 4 9 4Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M2 15C3.10457 15 4 14.1046 4 13C4 11.8954 3.10457 11 2 11C0.89543 11 0 11.8954 0 13C0 14.1046 0.89543 15 2 15Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M20 18C21.1046 18 22 17.1046 22 16C22 14.8954 21.1046 14 20 14C18.8954 14 18 14.8954 18 16C18 17.1046 18.8954 18 20 18Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M18 8C19.1046 8 20 7.10457 20 6C20 4.89543 19.1046 4 18 4C16.8954 4 16 4.89543 16 6C16 7.10457 16.8954 8 18 8Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/rectangle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Rectangle extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-rectangle',\n    viewBox: '0 0 22 16'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect\n          x=\"2\"\n          y=\"2\"\n          width=\"18\"\n          height=\"12\"\n          stroke=\"currentColor\"\n          fill=\"transparent\"\n          strokeWidth=\"1.5\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M2 4C3.10457 4 4 3.10457 4 2C4 0.89543 3.10457 0 2 0C0.89543 0 0 0.89543 0 2C0 3.10457 0.89543 4 2 4Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.89543 12 0 12.8954 0 14C0 15.1046 0.89543 16 2 16Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M20 16C21.1046 16 22 15.1046 22 14C22 12.8954 21.1046 12 20 12C18.8954 12 18 12.8954 18 14C18 15.1046 18.8954 16 20 16Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M20 4C21.1046 4 22 3.10457 22 2C22 0.89543 21.1046 0 20 0C18.8954 0 18 0.89543 18 2C18 3.10457 18.8954 4 20 4Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/reduce.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Reduce extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-reduce'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(12.000000, 12.000000)\">\n          <path\n            d=\"M36.5208333,13.9791667 L31.7291666,9.1875 L37.75,3.2083334 L34.7916666,0.25 L28.8125,6.2708334 L24.0208333,1.4791667 L24.0208333,13.9791667 L36.5208333,13.9791667 Z M13.9791667,1.4791667 L9.1875,6.2708334 L3.2083334,0.25 L0.25,3.2083334 L6.2708334,9.1875 L1.4791667,13.9791667 L13.9791667,13.9791667 L13.9791667,1.4791667 Z M1.4791667,24.0208333 L6.2708334,28.8125 L0.25,34.7916666 L3.2083334,37.75 L9.1875,31.7291666 L13.9791667,36.5208333 L13.9791667,24.0208333 L1.4791667,24.0208333 Z M24.0208333,36.5208333 L28.8125,31.7291666 L34.7916666,37.75 L37.75,34.7916666 L31.7291666,28.8125 L36.5208333,24.0208333 L24.0208333,24.0208333 L24.0208333,36.5208333 Z\"\n            id=\"Shape\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/reset.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Reset extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-reset'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M10 2h4\" />\n        <path d=\"M12 14v-4\" />\n        <path d=\"M4 13a8 8 0 0 1 8-7 8 8 0 1 1-5.3 14L4 17.6\" />\n        <path d=\"M9 17H4v5\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/save.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Save extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-save'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n        <polyline points=\"7 10 12 15 17 10\" />\n        <line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"3\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/save2.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Save2 extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-save2'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z\" />\n        <path d=\"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7\" />\n        <path d=\"M7 3v4a1 1 0 0 0 1 1h7\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/search.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Search extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-search'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M56.74,53.21l-3.53,3.53a1.67,1.67,0,0,1-2.35,0L40.21,46.09A24.32,24.32,0,0,0,46.1,40.2L56.74,50.85A1.66,1.66,0,0,1,56.74,53.21Z\" />\n        <path d=\"M26.22,6.78A19.46,19.46,0,1,0,42.6,36.7a19.18,19.18,0,0,0,3.08-10.47A19.45,19.45,0,0,0,26.22,6.78ZM11.64,26.22A14.58,14.58,0,1,1,26.22,40.81,14.6,14.6,0,0,1,11.64,26.22Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/settings.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Settings extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '24px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-settings'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M20 7h-9\" />\n        <path d=\"M14 17H5\" />\n        <circle cx=\"17\" cy=\"17\" r=\"3\" />\n        <circle cx=\"7\" cy=\"7\" r=\"3\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/share.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Share extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-share'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <circle cx=\"18\" cy=\"5\" r=\"3\" />\n        <circle cx=\"6\" cy=\"12\" r=\"3\" />\n        <circle cx=\"18\" cy=\"19\" r=\"3\" />\n        <line x1=\"8.59\" x2=\"15.42\" y1=\"13.51\" y2=\"17.49\" />\n        <line x1=\"15.41\" x2=\"8.59\" y1=\"6.51\" y2=\"10.49\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/speed.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Speed extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-speed'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"m12 14 4-4\" />\n        <path d=\"M3.34 19a10 10 0 1 1 17.32 0\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/split.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Split extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-split'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(7.500000, 7.500000)\">\n          <path d=\"M19.5,47.4137931 C19.5,48.8421157 20.6192881,50 22,50 C23.3807119,50 24.5,48.8421157 24.5,47.4137931 L24.5,2.5862069 C24.5,1.15788427 23.3807119,0 22,0 C20.6192881,0 19.5,1.15788427 19.5,2.5862069 L19.5,47.4137931 Z\" />\n          <rect x=\"0\" y=\"4\" width=\"44\" height=\"5\" rx=\"2.5\" />\n          <rect\n            transform=\"translate(2.500000, 24.500000) rotate(90.000000) translate(-2.500000, -24.500000) \"\n            x=\"-18\"\n            y=\"22\"\n            width=\"41\"\n            height=\"5\"\n            rx=\"2.5\"\n          />\n          <rect\n            transform=\"translate(41.500000, 25.000000) rotate(90.000000) translate(-41.500000, -25.000000) \"\n            x=\"20.5\"\n            y=\"22.5\"\n            width=\"42\"\n            height=\"5\"\n            rx=\"2.5\"\n          />\n          <rect x=\"0\" y=\"41\" width=\"44\" height=\"5\" rx=\"2.5\" />\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/square-select.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class SquareSelect extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-select'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M57,15.36a8.38,8.38,0,0,1-8.32,8.32,8.35,8.35,0,0,1-8.32-8.32A8.38,8.38,0,0,1,48.64,7,8.35,8.35,0,0,1,57,15.36Z\" />\n        <path d=\"M57,48.64a8.31,8.31,0,0,1-16.35,2.08H23.39A8.31,8.31,0,1,1,13.27,40.61V23.39a8.3,8.3,0,0,1-6.24-8A8.38,8.38,0,0,1,15.36,7a8.3,8.3,0,0,1,8,6.24H36.16v4.16H23.39a7.88,7.88,0,0,1-2.16,3.79,7.88,7.88,0,0,1-3.79,2.16V40.61a8.29,8.29,0,0,1,6,6H40.61a8.29,8.29,0,0,1,6-6V27.84h4.16V40.61A8.3,8.3,0,0,1,57,48.64Z\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/sun.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class Sun extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-sun'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_85_57860)\">\n            <path d=\"M8 6.00244C8.39556 6.00244 8.78224 6.11974 9.11114 6.3395C9.44004 6.55927 9.69639 6.87162 9.84776 7.23707C9.99914 7.60253 10.0387 8.00466 9.96157 8.39262C9.8844 8.78058 9.69392 9.13695 9.41421 9.41665C9.13451 9.69636 8.77814 9.88684 8.39018 9.96401C8.00222 10.0412 7.60009 10.0016 7.23463 9.8502C6.86918 9.69883 6.55683 9.44248 6.33706 9.11358C6.1173 8.78468 6 8.398 6 8.00244C6.0006 7.47219 6.2115 6.96383 6.58645 6.58889C6.96139 6.21394 7.46975 6.00304 8 6.00244ZM8 5.00244C7.40666 5.00244 6.82664 5.17839 6.33329 5.50803C5.83994 5.83768 5.45543 6.30621 5.22836 6.85439C5.0013 7.40257 4.94189 8.00577 5.05765 8.58771C5.1734 9.16966 5.45912 9.7042 5.87868 10.1238C6.29824 10.5433 6.83279 10.829 7.41473 10.9448C7.99667 11.0606 8.59987 11.0011 9.14805 10.7741C9.69623 10.547 10.1648 10.1625 10.4944 9.66915C10.8241 9.1758 11 8.59579 11 8.00244C11 7.20679 10.6839 6.44373 10.1213 5.88112C9.55871 5.31851 8.79565 5.00244 8 5.00244Z\" />\n            <path d=\"M3.40381 2.69921L2.69678 3.40625L4.4497 5.15917L5.15673 4.45213L3.40381 2.69921Z\" />\n            <path d=\"M3.5 7.50244H1V8.50244H3.5V7.50244Z\" />\n            <path d=\"M4.4497 10.8457L2.69678 12.5986L3.40381 13.3057L5.15673 11.5528L4.4497 10.8457Z\" />\n            <path d=\"M8.5 12.5024H7.5V15.0024H8.5V12.5024Z\" />\n            <path d=\"M11.5503 10.8457L10.8433 11.5527L12.5962 13.3057L13.3032 12.5986L11.5503 10.8457Z\" />\n            <path d=\"M15 7.50244H12.5V8.50244H15V7.50244Z\" />\n            <path d=\"M12.5962 2.69923L10.8433 4.45215L11.5503 5.15918L13.3032 3.40627L12.5962 2.69923Z\" />\n            <path d=\"M8.5 1.00244H7.5V3.50244H8.5V1.00244Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_85_57860\">\n              <rect width=\"16\" height=\"16\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/sunrise.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class Sunrise extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '12px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '12px',\n    viewBox: '0 0 12 12',\n    predefinedClassName: 'data-ex-icons-sunrise'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_85_57889)\">\n            <path d=\"M11.2494 10.125H0.75V10.875H11.2494V10.125Z\" />\n            <path d=\"M6 7.5C6.39769 7.50045 6.77896 7.65863 7.06017 7.93983C7.34137 8.22104 7.49955 8.60231 7.5 9H8.25C8.25 8.40326 8.01295 7.83097 7.59099 7.40901C7.16903 6.98705 6.59674 6.75 6 6.75C5.40326 6.75 4.83097 6.98705 4.40901 7.40901C3.98705 7.83097 3.75 8.40326 3.75 9H4.5C4.50045 8.60231 4.65863 8.22104 4.93983 7.93983C5.22104 7.65863 5.60231 7.50045 6 7.5Z\" />\n            <path d=\"M11.25 8.25H9.375V9H11.25V8.25Z\" />\n            <path d=\"M9.44701 5.02259L8.13232 6.33728L8.6626 6.86756L9.97729 5.55287L9.44701 5.02259Z\" />\n            <path d=\"M6 1.5L4.125 3.375L4.65375 3.90375L5.625 2.93625V3V5.625H6.375V3V2.93625L7.34625 3.90375L7.875 3.375L6 1.5Z\" />\n            <path d=\"M2.55274 5.02258L2.02246 5.55286L3.33715 6.86754L3.86743 6.33727L2.55274 5.02258Z\" />\n            <path d=\"M2.625 8.25H0.75V9H2.625V8.25Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_85_57889\">\n              <rect width=\"12\" height=\"12\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/sunset.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base from './base';\n\nexport default class Sunset extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '12px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '12px',\n    viewBox: '0 0 12 12',\n    predefinedClassName: 'data-ex-icons-sunset'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g clipPath=\"url(#clip0_85_57899)\">\n            <path d=\"M11.2494 10.1268H0.75V10.8768H11.2494V10.1268Z\" />\n            <path d=\"M6 7.50183C6.39769 7.50228 6.77896 7.66046 7.06017 7.94166C7.34137 8.22287 7.49955 8.60414 7.5 9.00183H8.25C8.25 8.40509 8.01295 7.8328 7.59099 7.41084C7.16903 6.98888 6.59674 6.75183 6 6.75183C5.40326 6.75183 4.83097 6.98888 4.40901 7.41084C3.98705 7.8328 3.75 8.40509 3.75 9.00183H4.5C4.50045 8.60414 4.65863 8.22287 4.93983 7.94166C5.22104 7.66046 5.60231 7.50228 6 7.50183Z\" />\n            <path d=\"M11.25 8.25183H9.375V9.00183H11.25V8.25183Z\" />\n            <path d=\"M9.44701 5.02442L8.13232 6.33911L8.6626 6.86939L9.97729 5.5547L9.44701 5.02442Z\" />\n            <path d=\"M7.34625 3.59808L6.375 4.56558V1.50183H5.625V4.56558L4.65375 3.59808L4.125 4.12683L6 6.00183L7.875 4.12683L7.34625 3.59808Z\" />\n            <path d=\"M2.55274 5.02441L2.02246 5.55469L3.33715 6.86938L3.86743 6.3391L2.55274 5.02441Z\" />\n            <path d=\"M2.625 8.25183H0.75V9.00183H2.625V8.25183Z\" />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_85_57899\">\n              <rect width=\"12\" height=\"12\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/timeline-marker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes, {any} from 'prop-types';\nimport Base from './base';\n\nexport default class TimelineMarker extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    // not expected?\n    width: PropTypes.string,\n    style: any\n  };\n\n  static defaultProps = {\n    height: '12px',\n    width: '5px',\n    predefinedClassName: 'data-ex-icons-timeline-marker',\n    viewBox: '0 0 5 12'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect width=\"5\" height=\"9\" fill=\"currentColor\" />\n        <path d=\"M2.5 11.5L0 9H5L2.5 11.5Z\" fill=\"currentColor\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/trash.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Trash extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '14px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-trash'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M3 6h18\" />\n        <path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\" />\n        <path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/vert-dots.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class VertDots extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-vertdot'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect x=\"35.01\" y=\"48.31\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"35.01\" y=\"35.43\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"35.01\" y=\"22.55\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"35.01\" y=\"9.67\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"22.13\" y=\"48.31\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"22.13\" y=\"35.43\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"22.13\" y=\"22.55\" width=\"6.44\" height=\"6.44\" />\n        <rect x=\"22.13\" y=\"9.67\" width=\"6.44\" height=\"6.44\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/vert-three-dots.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class VertThreeDots extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-vert-three-dots'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect x=\"28\" y=\"44\" width=\"8\" height=\"8\" />\n        <rect x=\"28\" y=\"28\" width=\"8\" height=\"8\" />\n        <rect x=\"28\" y=\"12\" width=\"8\" height=\"8\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/warning-sign.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport Base, {BaseProps} from './base';\n\nexport default class WarningSign extends Component<Partial<BaseProps>> {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-warning-sign',\n    stroke: '#FFF'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 16 16\" {...this.props} stroke=\"none\">\n        <path\n          d=\"M15.6753 12.0189L10.017 1.60205C9.58989 0.88311 8.83564 0.453735 7.99939 0.453735C7.16314 0.453735 6.40889 0.88311 5.98183 1.60205C5.97861 1.60755 5.97589 1.61305 5.97267 1.61855L0.33317 12.0024C-0.103049 12.7365 -0.111299 13.6181 0.310701 14.3604C0.73367 15.1032 1.49567 15.5463 2.35026 15.5463H13.6173C14.4719 15.5463 15.2652 15.1032 15.6881 14.3604C16.1101 13.6181 16.1019 12.7365 15.6753 12.0189ZM7.06095 5.22339C7.06095 4.70508 7.48111 4.28495 7.99939 4.28495C8.5177 4.28495 8.93783 4.70511 8.93783 5.22339V8.97717C8.93783 9.49542 8.51767 9.91561 7.99939 9.91561C7.48111 9.91561 7.06095 9.49539 7.06095 8.97717V5.22339ZM7.99939 13.6694C7.22317 13.6694 6.5917 13.038 6.5917 12.2617C6.5917 11.4855 7.22314 10.8541 7.99939 10.8541C8.77561 10.8541 9.40705 11.4855 9.40705 12.2617C9.40708 13.038 8.77564 13.6694 7.99939 13.6694Z\"\n          fill=\"#F5B766\"\n        />\n        <circle cx=\"8\" cy=\"12.2773\" r=\"1.43359\" fill=\"#121621\" />\n        <path\n          d=\"M8 4.28125C8.51992 4.28125 8.94141 4.70273 8.94141 5.22266V8.97266C8.94141 9.49258 8.51992 9.91406 8 9.91406C7.48008 9.91406 7.05859 9.49258 7.05859 8.97266V5.22266C7.05859 4.70273 7.48008 4.28125 8 4.28125Z\"\n          fill=\"#121621\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/warning.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class Warning extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'data-ex-icons-warning',\n    stroke: '#FFF'\n  };\n\n  render() {\n    return (\n      <Base viewBox=\"0 0 64 64\" {...this.props}>\n        <path d=\"M0.349,49h49.302L25,1.842L0.349,49z M3.651,47L25,6.159L46.349,47H3.651z\" />\n        <rect height=\"18\" width=\"2\" x=\"24\" y=\"18\" />\n        <rect height=\"3\" width=\"2\" x=\"24\" y=\"39\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/zoom-in.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ZoomIn extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-zoom-in'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M7.85938 3.84995C7.85938 3.49097 7.56836 3.19995 7.20937 3.19995C6.85039 3.19995 6.55938 3.49097 6.55938 3.84995V6.54449H3.84995C3.49097 6.54449 3.19995 6.83551 3.19995 7.19449C3.19995 7.55348 3.49097 7.84449 3.84995 7.84449H6.55937V10.55C6.55937 10.9089 6.85039 11.2 7.20937 11.2C7.56836 11.2 7.85937 10.9089 7.85937 10.55V7.84449H10.55C10.9089 7.84449 11.2 7.55348 11.2 7.19449C11.2 6.83551 10.9089 6.54449 10.55 6.54449H7.85938V3.84995Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M12.1 7.2C12.1 9.90619 9.90619 12.1 7.2 12.1C4.4938 12.1 2.3 9.90619 2.3 7.2C2.3 4.4938 4.4938 2.3 7.2 2.3C9.90619 2.3 12.1 4.4938 12.1 7.2ZM11.0938 12.0251C10.0295 12.885 8.67487 13.4 7.2 13.4C3.77583 13.4 1 10.6242 1 7.2C1 3.77583 3.77583 1 7.2 1C10.6242 1 13.4 3.77583 13.4 7.2C13.4 8.68104 12.8807 10.0408 12.0143 11.1071L14.7721 13.8649C15.0259 14.1187 15.0259 14.5303 14.7721 14.7841C14.5182 15.038 14.1067 15.038 13.8528 14.7841L11.0938 12.0251Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/icons/zoom-out.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport Base, {BaseProps} from './base';\n\nexport default class ZoomOut extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 16 16',\n    predefinedClassName: 'data-ex-icons-zoom-out'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M3.84995 6.54449C3.49097 6.54449 3.19995 6.83551 3.19995 7.19449C3.19995 7.55348 3.49097 7.84449 3.84995 7.84449H10.55C10.9089 7.84449 11.2 7.55348 11.2 7.19449C11.2 6.83551 10.9089 6.54449 10.55 6.54449H3.84995Z\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M12.1 7.2C12.1 9.90619 9.90619 12.1 7.2 12.1C4.4938 12.1 2.3 9.90619 2.3 7.2C2.3 4.4938 4.4938 2.3 7.2 2.3C9.90619 2.3 12.1 4.4938 12.1 7.2ZM11.0938 12.0251C10.0295 12.885 8.67487 13.4 7.2 13.4C3.77583 13.4 1 10.6242 1 7.2C1 3.77583 3.77583 1 7.2 1C10.6242 1 13.4 3.77583 13.4 7.2C13.4 8.68104 12.8807 10.0408 12.0143 11.1071L14.7721 13.8649C15.0259 14.1187 15.0259 14.5303 14.7721 14.7841C14.5182 15.038 14.1067 15.038 13.8528 14.7841L11.0938 12.0251Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/image-preview.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport LoadingSpinner from './loading-spinner';\nimport {ExportImage} from '@kepler.gl/types';\n\nconst StyledImagePreview = styled.div.attrs({\n  className: 'image-preview'\n})`\n  align-items: center;\n  display: flex;\n  flex-direction: column;\n  flex: 1;\n  justify-content: center;\n  width: 100%;\n  height: 100%;\n\n  .dimension,\n  .instruction {\n    padding: 8px 0px;\n  }\n\n  .preview-image {\n    background: #e2e2e2;\n    border-radius: 4px;\n    box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.18);\n    width: 100%;\n    max-width: 400px;\n    position: relative;\n    overflow: hidden;\n  }\n\n  .preview-image-container {\n    position: relative;\n    width: 100%;\n    height: 0;\n    padding-bottom: var(--aspect-ratio);\n    max-height: 400px;\n  }\n\n  .preview-image-placeholder {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    object-fit: contain;\n  }\n\n  .preview-image-spinner {\n    position: absolute;\n    left: calc(50% - 25px);\n    top: calc(50% - 25px);\n  }\n\n  .preview-image--error {\n    font-size: 12px;\n    padding: 12px;\n    color: ${props => props.theme.errorColor};\n    text-align: center;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n`;\n\ninterface ImagePreviewProps {\n  exportImage?: ExportImage;\n  width?: number;\n  showDimension?: boolean;\n}\n\n/**\n * @param {object} props\n * @param {ExportImage} [props.exportImage]\n * @param {number} [props.width]\n * @param {boolean} [props.showDimension]\n */\nconst ImagePreview = ({exportImage, showDimension = false}: ImagePreviewProps) => {\n  const {\n    error,\n    imageDataUri,\n    processing,\n    imageSize: {imageW = 0, imageH = 0} = {}\n  } = exportImage || {};\n\n  // Calculate aspect ratio percentage for padding-bottom trick\n  const aspectRatio = imageW && imageH ? (imageH / imageW) * 100 : 75; // default to 4:3 if no dimensions\n\n  return (\n    <StyledImagePreview style={{'--aspect-ratio': `${aspectRatio}%`} as React.CSSProperties}>\n      {showDimension ? (\n        <div className=\"dimension\">\n          {imageW} pixel x {imageH} pixel\n        </div>\n      ) : null}\n      <div className=\"preview-image\">\n        <div className=\"preview-image-container\">\n          {processing ? (\n            <div className=\"preview-image-spinner\">\n              <LoadingSpinner />\n            </div>\n          ) : error ? (\n            <div className=\"preview-image--error\">\n              <span>{error.message || 'Generate map image failed!'}</span>\n            </div>\n          ) : (\n            <img className=\"preview-image-placeholder\" src={imageDataUri} alt=\"Map preview\" />\n          )}\n        </div>\n      </div>\n    </StyledImagePreview>\n  );\n};\n\nexport default ImagePreview;\n"
  },
  {
    "path": "src/components/src/common/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable prettier/prettier */\nexport * as Icons from './icons';\nexport {\n  BottomWidgetInner,\n  Button,\n  ButtonGroup,\n  CenterFlexbox,\n  CenterVerticalFlexbox,\n  CheckMark,\n  DatasetSquare,\n  IconRoundSmall,\n  InlineInput,\n  Input,\n  InputLight,\n  MapControlButton,\n  PanelContent,\n  PanelHeaderContent,\n  PanelHeaderTitle,\n  PanelLabel,\n  PanelLabelBold,\n  PanelLabelWrapper,\n  SBFlexboxItem,\n  SBFlexboxNoMargin,\n  SelectText,\n  SelectTextBold,\n  SelectionButton,\n  SidePanelDivider,\n  SidePanelSection,\n  SpaceBetweenFlexbox,\n  StyledAttribution,\n  StyledExportSection,\n  StyledFilterContent,\n  StyledFilteredOption,\n  StyledMapContainer,\n  StyledModalContent,\n  StyledModalInputFootnote,\n  StyledModalSection,\n  StyledModalVerticalPanel,\n  StyledPanelDropdown,\n  StyledPanelHeader,\n  StyledType,\n  shouldForwardProp,\n  TextArea,\n  TextAreaLight,\n  Tooltip,\n  TruncatedTitleText,\n  WidgetContainer\n} from './styled-components';\nexport type {\n  ButtonProps,\n  StyledExportSectionProps,\n  StyledPanelHeaderProps\n} from './styled-components';\n\nexport {Edit} from './icons';\n"
  },
  {
    "path": "src/components/src/common/info-helper.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {useIntl} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Tooltip} from './styled-components';\nimport {Docs} from './icons';\nimport styled from 'styled-components';\nimport {camelize} from '@kepler.gl/utils';\n\ninterface StyledInfoHelperProps {\n  width?: number;\n}\n\nconst StyledInfoHelper = styled.div<StyledInfoHelperProps>`\n  align-items: center;\n  margin-left: 10px;\n  color: ${props => props.theme.labelColor};\n  display: inline-flex;\n  .info-helper__content {\n    width: ${props => (props.width ? `${props.width}px` : 'auto')};\n    max-width: ${props => (props.width ? 'auto' : '100px')};\n  }\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.textColorHl};\n  }\n`;\n\ninterface InfoHelperProps {\n  description: string;\n  containerClass?: string;\n  width?: number;\n  property?: string;\n  id?: string;\n}\n\nfunction InfoHelperFactory() {\n  const InfoHelper = ({description, property, containerClass, width, id}: InfoHelperProps) => {\n    // TODO: move intl out\n    const intl = useIntl();\n\n    return (\n      <StyledInfoHelper\n        className={`info-helper ${containerClass || ''}`}\n        width={width}\n        data-tip\n        data-for={id}\n      >\n        <Docs height=\"16px\" />\n        <Tooltip id={id} effect=\"solid\">\n          <div className=\"info-helper__content\">\n            {description && (\n              <FormattedMessage\n                id={description}\n                defaultValue={description}\n                values={{\n                  property: intl.formatMessage({\n                    id: property ? `property.${camelize(property)}` : 'misc.empty'\n                  })\n                }}\n              />\n            )}\n          </div>\n        </Tooltip>\n      </StyledInfoHelper>\n    );\n  };\n  return InfoHelper;\n}\n\nexport default InfoHelperFactory;\n"
  },
  {
    "path": "src/components/src/common/item-selector/accessor.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst Accessor = {\n  IDENTITY_FN: input => input,\n\n  generateAccessor: (field: string) => (object: object) => object[field],\n\n  generateOptionToStringFor: function generateOptionToStringFor(prop) {\n    if (typeof prop === 'string') {\n      return this.generateAccessor(prop);\n    } else if (typeof prop === 'function') {\n      return prop;\n    }\n    return this.IDENTITY_FN;\n  },\n\n  valueForOption: (option, object) => {\n    if (typeof option === 'string') {\n      return object[option];\n    } else if (typeof option === 'function') {\n      return option(object);\n    }\n    return object;\n  }\n};\n\nexport default Accessor;\n"
  },
  {
    "path": "src/components/src/common/item-selector/chickleted-input.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {MouseEventHandler, ReactNode, useMemo, useCallback} from 'react';\n\nimport styled, {IStyledComponent} from 'styled-components';\nimport {DndContext, DragOverlay, pointerWithin} from '@dnd-kit/core';\nimport {SortableContext, useSortable} from '@dnd-kit/sortable';\nimport {restrictToParentElement} from '@dnd-kit/modifiers';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {arrayMove} from '@kepler.gl/common-utils';\n\nimport Delete from '../icons/delete';\nimport {BaseComponentProps} from '../../types';\nimport {shouldForwardProp} from '../styled-components';\n\nexport type ChickletButtonProps = BaseComponentProps & {\n  inputTheme?: string;\n  ref?: (node: HTMLElement | null) => void;\n};\n\nexport const ChickletButton: IStyledComponent<\n  'web',\n  ChickletButtonProps\n> = styled.div<ChickletButtonProps>`\n  background: ${props =>\n    props.inputTheme === 'light' ? props.theme.chickletBgdLT : props.theme.chickletBgd};\n  border-radius: 1px;\n  color: ${props =>\n    props.inputTheme === 'light' ? props.theme.textColorLT : props.theme.textColor};\n  font-size: 11px;\n  line-height: 20px;\n  margin: 4px 10px 4px 3px;\n  padding: 2px 6px;\n  display: flex;\n  align-items: center;\n  max-width: calc(100% - 8px);\n\n  &:hover {\n    color: ${props =>\n      props.inputTheme === 'light' ? props.theme.textColorHlLT : props.theme.textColorHl};\n  }\n`;\n\nconst DND_MODIFIERS = [restrictToParentElement];\nexport const ChickletTag: IStyledComponent<'web'> = styled.span`\n  margin-right: 10px;\n  text-overflow: ellipsis;\n  width: 100%;\n  overflow: hidden;\n\n  &:hover {\n    overflow: visible;\n  }\n`;\n\ninterface ChickletProps {\n  disabled?: boolean;\n  name: ReactNode;\n  remove?: MouseEventHandler<SVGSVGElement>;\n  inputTheme?: string;\n}\n\nconst Chicklet = ({disabled, name, remove, inputTheme}: ChickletProps) => (\n  <ChickletButton inputTheme={inputTheme}>\n    <ChickletTag>{name}</ChickletTag>\n    <Delete height=\"16px\" onClick={disabled ? undefined : remove} />\n  </ChickletButton>\n);\n\nexport type ChickletedInputContainerProps = BaseComponentProps & {\n  inputTheme?: string;\n  hasPlaceholder?: boolean;\n  focus?: HTMLInputElement['focus'];\n  disabled?: boolean;\n  onClick?: (e: React.MouseEvent) => void;\n};\n\nconst ChickletedInputContainer: IStyledComponent<\n  'web',\n  ChickletedInputContainerProps\n> = styled.div.withConfig({shouldForwardProp})<ChickletedInputContainerProps>`\n  ${props =>\n    props.inputTheme === 'secondary'\n      ? props.theme.secondaryChickletedInput\n      : props.inputTheme === 'light'\n      ? props.theme.chickletedInputLT\n      : props.theme.chickletedInput}\n\n  color: ${props =>\n    props.hasPlaceholder ? props.theme.selectColorPlaceHolder : props.theme.selectColor};\n  overflow: hidden;\n`;\n\nconst ChickletedItem = ({\n  item,\n  removeItem,\n  displayOption,\n  CustomChickletComponent,\n  inputTheme,\n  disabled,\n  itemId\n}) => {\n  const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({\n    id: itemId\n  });\n  const chickletProps = useMemo(\n    () => ({\n      inputTheme,\n      disabled,\n      name: displayOption(item),\n      displayOption,\n      item,\n      attributes,\n      listeners,\n      setNodeRef,\n      transform,\n      transition,\n      isDragging,\n      remove: e => removeItem(item, e)\n    }),\n    [\n      item,\n      removeItem,\n      displayOption,\n      inputTheme,\n      disabled,\n      attributes,\n      listeners,\n      setNodeRef,\n      transform,\n      transition,\n      isDragging\n    ]\n  );\n  return CustomChickletComponent ? (\n    <CustomChickletComponent {...chickletProps} />\n  ) : (\n    <Chicklet {...chickletProps} />\n  );\n};\n\ntype Item = string | number | boolean | object | undefined;\n\nexport type ChickletedInputProps = ChickletedInputContainerProps & {\n  selectedItems?: any[];\n  placeholder?: string;\n  CustomChickletComponent?: React.ComponentType<any> | null;\n  reorderItems?: (newOrder: any) => void;\n  displayOption?: (item: Item) => string;\n  removeItem: (item: Item, e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;\n};\n\nconst ChickletedInput: React.FC<ChickletedInputProps> = ({\n  disabled,\n  onClick,\n  className,\n  selectedItems = [],\n  placeholder = '',\n  removeItem,\n  reorderItems = d => d,\n  displayOption = d => String(d),\n  inputTheme,\n  CustomChickletComponent\n}) => {\n  const selectedItemIds = useMemo(\n    () => selectedItems.map(item => displayOption(item)),\n    [displayOption, selectedItems]\n  );\n  const handleDragEnd = useCallback(\n    ({active, over}) => {\n      if (!over) return;\n      if (active.id !== over.id) {\n        const oldIndex = selectedItemIds.findIndex(itemId => itemId === active.id);\n        const newIndex = selectedItemIds.findIndex(itemId => itemId === over.id);\n        reorderItems(arrayMove(selectedItems, oldIndex, newIndex));\n      }\n    },\n    [selectedItemIds, selectedItems, reorderItems]\n  );\n\n  return (\n    <ChickletedInputContainer\n      className={`${className} chickleted-input`}\n      onClick={onClick}\n      inputTheme={inputTheme}\n      hasPlaceholder={!selectedItems || !selectedItems.length}\n    >\n      <DndContext\n        onDragEnd={handleDragEnd}\n        modifiers={DND_MODIFIERS}\n        collisionDetection={pointerWithin}\n        autoScroll={false}\n      >\n        <SortableContext items={selectedItemIds}>\n          {selectedItems.length > 0 ? (\n            selectedItems.map((item, index) => (\n              <ChickletedItem\n                item={item}\n                itemId={displayOption(item)}\n                removeItem={removeItem}\n                displayOption={displayOption}\n                CustomChickletComponent={CustomChickletComponent}\n                disabled={disabled}\n                inputTheme={inputTheme}\n                key={`${displayOption(item)}_${index}`}\n              />\n            ))\n          ) : (\n            <span className={`${className} chickleted-input__placeholder`}>\n              <FormattedMessage id={placeholder || 'placeholder.enterValue'} />\n            </span>\n          )}\n        </SortableContext>\n        <DragOverlay dropAnimation={null} />\n      </DndContext>\n    </ChickletedInputContainer>\n  );\n};\n\nexport default ChickletedInput;\n"
  },
  {
    "path": "src/components/src/common/item-selector/dropdown-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, ElementType} from 'react';\nimport classNames from 'classnames';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {INIT_FILTER_ITEMS_IN_DROPDOWN} from '@kepler.gl/constants';\nimport {BaseComponentProps} from '../../types';\n\nconst LEFT_BUTTON = 0;\n\nexport const classList = {\n  list: 'list-selector',\n  listHeader: 'list__header',\n  listSection: 'list__section',\n  listItem: 'list__item',\n  listItemAnchor: 'list__item__anchor',\n  listItemFixed: 'list__item__fixed'\n};\n\nexport type ListItemProps<Option> = {\n  value: Option;\n  displayOption: (opt: Option) => string;\n  disabled?: boolean;\n  light?: boolean;\n};\n\nconst defaultDisplay = d => d;\nexport const ListItem = ({value, displayOption = defaultDisplay, disabled}: ListItemProps<any>) => {\n  const displayValue = displayOption(value);\n  return (\n    <span title={displayValue} className={classNames(classList.listItemAnchor, {disabled})}>\n      {displayValue}\n    </span>\n  );\n};\n\nexport type DropdownListWrapperProps = BaseComponentProps & {\n  $light?: boolean;\n};\n\nconst DropdownListWrapper: IStyledComponent<\n  'web',\n  DropdownListWrapperProps\n> = styled.div<DropdownListWrapperProps>`\n  background-color: ${props =>\n    props.$light ? props.theme.dropdownListBgdLT : props.theme.dropdownListBgd};\n  border-top: 1px solid\n    ${props =>\n      props.$light ? props.theme.dropdownListBorderTopLT : props.theme.dropdownListBorderTop};\n  ${props => (props.$light ? props.theme.dropdownListLT : props.theme.dropdownList)};\n`;\n\nconst DropdownFooterWrapper = styled.div`\n  height: '0px';\n`;\n\ntype Option = string | number | boolean | object | any;\n// TODO: make Option a generic type\ninterface DropdownListProps {\n  options?: Option[];\n  allowCustomValues?: number;\n  customClasses?: {listHeader?: string; listItem?: string; results?: string};\n  customValues?: any[];\n  customListItemComponent?: ElementType;\n  customListHeaderComponent?: ElementType;\n  selectionIndex?: number;\n  onOptionSelected?: (option: Option, event: React.MouseEvent) => void;\n  displayOption?: (option: Option) => string;\n  defaultClassNames?: boolean;\n  areResultsTruncated?: boolean;\n  resultsTruncatedMessage?: string;\n  listItemComponent?: ElementType;\n  light?: boolean;\n  fixedOptions?: any[];\n  selectedItems?: any[]; // Passed through by Typeahead\n}\n\ninterface DropdownListState {\n  options: Array<any> | null;\n}\n\nexport default class DropdownList extends Component<DropdownListProps, DropdownListState> {\n  static defaultProps = {\n    customClasses: {},\n    customListItemComponent: ListItem,\n    customListHeaderComponent: null,\n    allowCustomValues: 0,\n    customValues: [],\n    displayOption: defaultDisplay,\n    onOptionSelected: () => {\n      return;\n    },\n    defaultClassNames: true,\n    selectionIndex: null\n  };\n\n  initNumberOfOptions: number;\n  page: number;\n  prevY: number;\n  loadingRef: React.RefObject<HTMLDivElement>;\n  observer: IntersectionObserver | undefined;\n\n  constructor(props) {\n    super(props);\n\n    this.state = {options: []};\n    this.initNumberOfOptions = INIT_FILTER_ITEMS_IN_DROPDOWN;\n    this.page = 0;\n    this.prevY = 0;\n    this.loadingRef = React.createRef();\n  }\n\n  componentDidMount() {\n    const options = this._getOptions(this.page);\n    this.setState({options});\n\n    const divOptions = {\n      root: null,\n      rootMargin: '0%',\n      threshold: 1.0\n    };\n\n    if (this.loadingRef.current) {\n      this.observer = new IntersectionObserver(this.handleObserver, divOptions);\n      this.observer.observe(this.loadingRef.current);\n    }\n  }\n\n  getSnapshotBeforeUpdate(prevProps: DropdownListProps) {\n    if (prevProps.options !== this.props.options) {\n      // check if user searching, reset state.options at the first time\n      const options = this._getOptions(0);\n      this.setState({options});\n    }\n    return null;\n  }\n\n  // prevent console warning: getSnapshotBeforeUpdate() should be used with componentDidUpdate().\n  componentDidUpdate() {\n    return;\n  }\n\n  componentWillUnmount() {\n    if (this.loadingRef.current) {\n      this.observer?.unobserve(this.loadingRef?.current);\n      this.page = 0;\n      this.prevY = 0;\n    }\n  }\n\n  handleObserver = entities => {\n    const y = entities[0].boundingClientRect.y;\n    if (this.prevY > y) {\n      const options = this._getOptions(this.page);\n      if (options) this.setState({options});\n    }\n    this.prevY = y;\n  };\n\n  _getOptions(page) {\n    if (!this.props.options) {\n      return [];\n    }\n\n    const n = this.props.options.length;\n    if (n === 0) {\n      return [];\n    }\n    const start = page * this.initNumberOfOptions;\n    const end = start + this.initNumberOfOptions > n ? n : start + this.initNumberOfOptions;\n\n    if (start < end && end <= n) {\n      this.page = page + 1;\n      // in case of user searching, props.options will be updated\n      // so \"page\" value will be set to 0 and previous state.options will be discarded\n      return [\n        ...(page > 0 ? this.state.options || [] : []),\n        ...this.props.options.slice(start, end)\n      ];\n    }\n\n    return null;\n  }\n\n  _onClick(result, event) {\n    event.preventDefault();\n    // only work when left is clicked\n    if ((event.type === 'mousedown' && event.button === LEFT_BUTTON) || event.type === 'click') {\n      this.props.onOptionSelected?.(result, event);\n    }\n  }\n\n  render() {\n    const {\n      fixedOptions,\n      light,\n      allowCustomValues = 0,\n      customListItemComponent: CustomListItemComponent = ListItem\n    } = this.props;\n    const {displayOption: display = defaultDisplay} = this.props;\n\n    // Don't render if there are no options to display\n    if (!this.props.options?.length && allowCustomValues <= 0) {\n      return <div />;\n    }\n\n    const valueOffset = Array.isArray(fixedOptions) ? fixedOptions.length : 0;\n\n    // For some reason onClick is not fired when clicked on an option\n    // onMouseDown is used here as a workaround of #205 and other\n    return (\n      <DropdownListWrapper\n        className={classNames(classList.list, this.props.customClasses?.results)}\n        $light={light}\n      >\n        {this.props.customListHeaderComponent ? (\n          <div className={classNames(classList.listHeader, this.props.customClasses?.listHeader)}>\n            <this.props.customListHeaderComponent />\n          </div>\n        ) : null}\n\n        {valueOffset > 0 ? (\n          <div className={classList.listSection}>\n            {fixedOptions?.map((value, i) => (\n              <div\n                className={classNames(\n                  classList.listItem,\n                  {\n                    hover: this.props.selectionIndex === i,\n                    [classList.listItemFixed]: true\n                  },\n                  this.props.customClasses?.listItem\n                )}\n                key={`${display(value)}_${i}`}\n                onMouseDown={e => this._onClick(value, e)}\n                onClick={e => this._onClick(value, e)}\n              >\n                <CustomListItemComponent value={value} displayOption={display} light={light} />\n              </div>\n            ))}\n          </div>\n        ) : null}\n\n        {this.state.options?.map((value, i) => (\n          <div\n            className={classNames(\n              classList.listItem,\n              {\n                hover: this.props.selectionIndex === i + valueOffset,\n                selected: (this.props.selectedItems || []).find(\n                  item => display(item) === display(value)\n                )\n              },\n              this.props.customClasses?.listItem\n            )}\n            key={`${display(value)}_${i}`}\n            onMouseDown={e => this._onClick(value, e)}\n            onClick={e => this._onClick(value, e)}\n          >\n            <CustomListItemComponent value={value} displayOption={display} />\n          </div>\n        ))}\n\n        <DropdownFooterWrapper ref={this.loadingRef} />\n      </DropdownListWrapper>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/item-selector/dropdown-select.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType} from 'react';\nimport styled from 'styled-components';\n\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {ArrowDown, Delete} from '../icons';\nimport {ListItem} from './dropdown-list';\nimport {shouldForwardProp} from '../styled-components';\n\nexport type ListItemProps<Option> = {\n  value: Option;\n  displayOption: (opt: Option) => string;\n  light: boolean;\n  disabled: boolean;\n};\n\nexport type DropdownSelectProps<Option> = {\n  className?: string;\n  displayOption?: string | ((opt: Option) => string);\n  disabled?: boolean;\n  onClick: (e: React.MouseEvent) => void;\n  inputTheme?: string;\n  error?: boolean;\n  size?: string;\n  value: Option;\n  placeholder?: string;\n  erasable?: boolean;\n  showArrow?: boolean;\n  onErase?: (e: React.MouseEvent) => void;\n  showDropdown: (e: React.MouseEvent) => void;\n  DropDownLineItemRenderComponent?: ComponentType<ListItemProps<Option>>;\n};\n\nexport const StyledDropdownSelect = styled.div\n  .withConfig({shouldForwardProp})\n  .attrs<{className?: string}>({\n    className: 'item-selector__dropdown'\n  })<{\n  size: DropdownSelectProps<any>['size'];\n  inputTheme: DropdownSelectProps<any>['inputTheme'];\n}>`\n  ${props =>\n    props.inputTheme === 'secondary'\n      ? props.theme.secondaryInput\n      : props.inputTheme === 'light'\n      ? props.theme.inputLT\n      : props.theme.input};\n\n  height: ${props =>\n    props.size === 'small' ? props.theme.inputBoxHeightSmall : props.theme.inputBoxHeight};\n\n  .list__item__anchor {\n    ${props => props.theme.dropdownListAnchor};\n  }\n`;\n\nexport const DropdownSelectValue = styled.span.withConfig({shouldForwardProp})<{\n  inputTheme: DropdownSelectProps<any>['inputTheme'];\n  hasPlaceholder: boolean;\n}>`\n  color: ${props =>\n    props.hasPlaceholder && props.inputTheme === 'light'\n      ? props.theme.selectColorPlaceHolderLT\n      : props.hasPlaceholder\n      ? props.theme.selectColorPlaceHolder\n      : props.inputTheme === 'light'\n      ? props.theme.selectColorLT\n      : props.theme.selectColor};\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n\n  .list__item {\n    ${props =>\n      props.inputTheme === 'light' ? props.theme.dropdownListItemLT : props.theme.dropdownListItem};\n  }\n\n  .list__item__anchor {\n    ${props =>\n      props.inputTheme === 'light'\n        ? props.theme.dropdownListAnchorLT\n        : props.theme.dropdownListAnchor};\n  }\n`;\n\nconst DropdownSelectActionRight = styled.div`\n  margin-right: 2px;\n  display: flex;\n  color: ${props => props.theme.subtextColor};\n\n  &:hover {\n    color: ${props => props.theme.textColor};\n  }\n`;\n\nfunction DropdownSelect<Option>({\n  // dropdownSelectProps,\n  className,\n  displayOption,\n  disabled,\n  onClick,\n  inputTheme,\n  size,\n  value,\n  placeholder,\n  erasable,\n  showArrow,\n  onErase,\n  showDropdown,\n  DropDownLineItemRenderComponent = ListItem\n}: DropdownSelectProps<Option>): JSX.Element {\n  const hasValue = notNullorUndefined(value);\n\n  return (\n    <StyledDropdownSelect\n      className={className}\n      onClick={onClick}\n      inputTheme={inputTheme}\n      size={size}\n    >\n      <DropdownSelectValue\n        hasPlaceholder={!hasValue}\n        inputTheme={inputTheme}\n        className=\"item-selector__dropdown__value\"\n      >\n        {hasValue ? (\n          <DropDownLineItemRenderComponent\n            displayOption={displayOption as ListItemProps<any>['displayOption']}\n            value={value}\n            disabled={Boolean(disabled)}\n            light={inputTheme === 'light'}\n          />\n        ) : (\n          <FormattedMessage id={placeholder || 'placeholder.selectValue'} />\n        )}\n      </DropdownSelectValue>\n      {erasable && hasValue ? (\n        <DropdownSelectActionRight>\n          <Delete height=\"16px\" onClick={onErase} />\n        </DropdownSelectActionRight>\n      ) : showArrow ? (\n        <DropdownSelectActionRight>\n          <ArrowDown height=\"14px\" onClick={showDropdown} />\n        </DropdownSelectActionRight>\n      ) : null}\n    </StyledDropdownSelect>\n  );\n}\n\nexport default DropdownSelect;\n"
  },
  {
    "path": "src/components/src/common/item-selector/item-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, ComponentType, MouseEventHandler, RefObject} from 'react';\nimport classnames from 'classnames';\nimport uniqBy from 'lodash/uniqBy';\nimport styled, {IStyledComponent} from 'styled-components';\n\nimport Accessor from './accessor';\nimport ChickletedInput from './chickleted-input';\nimport Typeahead from './typeahead';\nimport DropdownList, {ListItem} from './dropdown-list';\nimport Portaled from '../../common/portaled';\nimport {observeDimensions, unobserveDimensions} from '@kepler.gl/utils';\nimport {toArray} from '@kepler.gl/common-utils';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {ListItemProps} from './dropdown-select';\nimport DropdownSelect from './dropdown-select';\nimport {shouldForwardProp} from '../styled-components';\n\nexport type DropdownWrapperProps = {\n  placement?: string;\n  width: number;\n};\n\nconst DropdownWrapper: IStyledComponent<'web', DropdownWrapperProps> = styled.div.withConfig({\n  shouldForwardProp\n})<DropdownWrapperProps>`\n  border: 0;\n  width: 100%;\n  left: 0;\n  z-index: ${props => props.theme.dropdownWrapperZ};\n  width: ${props => props.width}px;\n`;\n\nexport type ItemSelectorProps<Option> = {\n  selectedItems?: ReadonlyArray<Option> | string | number | boolean | object | null;\n  options: ReadonlyArray<Option>;\n  onChange: (items: ReadonlyArray<Option> | string | number | boolean | object | null) => void;\n  fixedOptions?: ReadonlyArray<Option> | null;\n  erasable?: boolean;\n  showArrow?: boolean;\n  searchOptions?: (value: any, opt: Option) => any;\n  searchable?: boolean;\n  displayOption?: string | ((opt: Option) => string);\n  getOptionValue?: string | ((opt: Option) => any);\n  filterOption?: string | ((opt: Option) => boolean);\n  placement?: string;\n  disabled?: boolean;\n  isError?: boolean;\n  multiSelect?: boolean;\n  inputTheme?: string;\n  onOpen?: () => void;\n  size?: string;\n  onBlur?: () => void;\n  placeholder?: string;\n  closeOnSelect?: boolean;\n  typeaheadPlaceholder?: string;\n  DropDownWrapperComponent?: ComponentType<any> | null;\n  DropdownHeaderComponent?: ComponentType<any> | null;\n  DropDownRenderComponent?: ComponentType<any>;\n  DropDownLineItemRenderComponent?: ComponentType<ListItemProps<Option>>;\n  CustomChickletComponent?: ComponentType<any>;\n  intl: IntlShape;\n  className?: string;\n  reorderItems?: (newOrder: any) => void;\n  showDropdownOnMount?: boolean;\n};\n\nclass ItemSelectorUnmemoized extends Component<ItemSelectorProps<any>> {\n  static defaultProps = {\n    multiSelect: true,\n    placeholder: 'placeholder.enterValue',\n    closeOnSelect: true,\n    searchable: true,\n    DropDownRenderComponent: DropdownList,\n    DropDownLineItemRenderComponent: ListItem,\n    DropDownWrapperComponent: DropdownWrapper,\n    reorderItems: undefined,\n    className: ''\n  };\n\n  state = {\n    showTypeahead: false,\n    dimensions: {\n      width: 200\n    }\n  };\n\n  componentDidMount() {\n    if (this.props.showDropdownOnMount) {\n      this.setState({showTypeahead: true});\n    }\n\n    if (this.root.current instanceof HTMLElement) {\n      observeDimensions(this.root.current, this._handleResize);\n    }\n  }\n\n  componentWillUnmount() {\n    if (this.root.current instanceof HTMLElement) {\n      unobserveDimensions(this.root.current);\n    }\n  }\n\n  root: RefObject<HTMLDivElement> = createRef();\n\n  handleClickOutside = () => {\n    this._hideTypeahead();\n  };\n\n  _handleResize = dimensions => {\n    this.setState({dimensions});\n  };\n\n  _hideTypeahead = () => {\n    this.setState({showTypeahead: false});\n    this._onBlur();\n  };\n\n  _onBlur = () => {\n    // note: chickleted input is not a real form element so we call onBlur()\n    // when we feel the events are appropriate\n    if (this.props.onBlur) {\n      this.props.onBlur();\n    }\n  };\n\n  _removeItem = (item, e) => {\n    // only used when multiSelect = true\n    e.preventDefault();\n    e.stopPropagation();\n    const multiSelectedItems = toArray(this.props.selectedItems);\n    const index = multiSelectedItems.findIndex(t => t === item);\n\n    if (index < 0) {\n      return;\n    }\n\n    const items = [\n      ...multiSelectedItems.slice(0, index),\n      ...multiSelectedItems.slice(index + 1, multiSelectedItems.length)\n    ];\n\n    this.props.onChange(items);\n\n    if (this.props.closeOnSelect) {\n      this.setState({showTypeahead: false});\n      this._onBlur();\n    }\n  };\n\n  _selectItem = item => {\n    const getValue = Accessor.generateOptionToStringFor(\n      this.props.getOptionValue || this.props.displayOption\n    );\n\n    const previousSelected = toArray(this.props.selectedItems);\n\n    if (this.props.multiSelect) {\n      const items = uniqBy(previousSelected.concat(toArray(item)), getValue);\n      this.props.onChange(items);\n    } else {\n      this.props.onChange(getValue(item));\n    }\n\n    if (this.props.closeOnSelect) {\n      this.setState({showTypeahead: false});\n      this._onBlur();\n    }\n  };\n\n  _onErase: MouseEventHandler = e => {\n    e.stopPropagation();\n    this.props.onChange(null);\n  };\n\n  _showTypeahead: MouseEventHandler = e => {\n    e.stopPropagation();\n    if (!this.props.disabled) {\n      if (this.props.onOpen) {\n        this.props.onOpen();\n      }\n      this.setState({\n        showTypeahead: true\n      });\n    }\n  };\n\n  _renderDropdown(intl: IntlShape) {\n    const {placement = 'bottom'} = this.props;\n    const {dimensions} = this.state;\n\n    const DropDownWrapperComponent = this.props\n      .DropDownWrapperComponent as React.ComponentType<any>;\n\n    return (\n      <Portaled left={0} top={0} isOpened={this.state.showTypeahead} onClose={this._hideTypeahead}>\n        <DropDownWrapperComponent placement={placement} width={dimensions?.width}>\n          <Typeahead\n            customClasses={{\n              results: 'list-selector',\n              input: 'typeahead__input',\n              listItem: 'list__item',\n              listAnchor: 'list__item__anchor'\n            }}\n            options={this.props.options}\n            filterOption={this.props.filterOption}\n            fixedOptions={this.props.fixedOptions}\n            placeholder={\n              this.props.typeaheadPlaceholder || intl\n                ? intl.formatMessage({id: 'placeholder.search'})\n                : 'Search'\n            }\n            onOptionSelected={this._selectItem}\n            customListComponent={this.props.DropDownRenderComponent}\n            customListHeaderComponent={this.props.DropdownHeaderComponent}\n            customListItemComponent={this.props.DropDownLineItemRenderComponent}\n            displayOption={Accessor.generateOptionToStringFor(this.props.displayOption)}\n            searchable={this.props.searchable}\n            searchOptions={this.props.searchOptions}\n            showOptionsWhenEmpty\n            selectedItems={toArray(this.props.selectedItems)}\n            light={this.props.inputTheme === 'light'}\n          />\n        </DropDownWrapperComponent>\n      </Portaled>\n    );\n  }\n\n  render() {\n    const selected = toArray(this.props.selectedItems);\n    const displayOption = Accessor.generateOptionToStringFor(this.props.displayOption);\n    const {disabled, inputTheme = 'primary'} = this.props;\n\n    const dropdownSelectProps = {\n      className: classnames({\n        active: this.state.showTypeahead\n      }),\n      displayOption,\n      disabled,\n      onClick: this._showTypeahead,\n      error: this.props.isError,\n      inputTheme,\n      size: this.props.size\n    };\n    const intl = this.props.intl;\n\n    return (\n      <div className={classnames('item-selector', this.props.className)} ref={this.root}>\n        <div style={{position: 'relative'}}>\n          {/* this part is used to display the label */}\n          {this.props.multiSelect ? (\n            <ChickletedInput\n              {...dropdownSelectProps}\n              selectedItems={toArray(this.props.selectedItems)}\n              placeholder={this.props.placeholder}\n              removeItem={this._removeItem}\n              reorderItems={this.props.reorderItems}\n              CustomChickletComponent={this.props.CustomChickletComponent}\n              inputTheme={inputTheme}\n            />\n          ) : (\n            <DropdownSelect\n              {...dropdownSelectProps}\n              value={selected[0]}\n              placeholder={this.props.placeholder}\n              erasable={this.props.erasable}\n              showArrow={this.props.showArrow}\n              onErase={this._onErase}\n              showDropdown={this._showTypeahead}\n              DropDownLineItemRenderComponent={this.props.DropDownLineItemRenderComponent}\n            />\n          )}\n          {/* this part is used to built the list */}\n          {this._renderDropdown(intl)}\n        </div>\n      </div>\n    );\n  }\n}\n\nconst ItemSelector = React.memo(ItemSelectorUnmemoized);\nItemSelector.displayName = 'ItemSelector';\n\nexport default injectIntl(ItemSelector);\n"
  },
  {
    "path": "src/components/src/common/item-selector/typeahead.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, ElementType, KeyboardEventHandler} from 'react';\nimport {polyfill} from 'react-lifecycles-compat';\nimport fuzzy from 'fuzzy';\nimport classNames from 'classnames';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {console as Console} from 'global/window';\n\nimport Accessor from './accessor';\nimport DropdownList, {ListItem} from './dropdown-list';\nimport {Search} from '../icons';\nimport {KeyEvent} from '@kepler.gl/constants';\nimport {BaseComponentProps} from '../../types';\nimport {shouldForwardProp} from '../styled-components';\nconst DEFAULT_CLASS = 'typeahead';\n/**\n * Copied mostly from 'react-typeahead', an auto-completing text input\n *\n * Renders an text input that shows options nearby that you can use the\n * keyboard or mouse to select.\n */\n\nexport type TypeaheadWrapperProps = BaseComponentProps & {\n  light?: boolean;\n  ref: React.RefObject<HTMLDivElement>;\n};\n\nconst TypeaheadWrapper: IStyledComponent<'web', TypeaheadWrapperProps> = styled.div.withConfig({\n  shouldForwardProp\n})<TypeaheadWrapperProps>`\n  display: flex;\n  flex-direction: column;\n  background-color: ${props =>\n    props.light ? props.theme.dropdownListBgdLT : props.theme.dropdownListBgd};\n  box-shadow: ${props => props.theme.dropdownListShadow};\n\n  &:focus {\n    outline: 0;\n  }\n`;\n\nconst InputBox = styled.div.attrs({\n  className: 'typeahead__input_box'\n})`\n  padding: 8px;\n`;\n\nexport type TypeaheadInputProps = BaseComponentProps & {\n  light?: boolean;\n  value?: string;\n  type: string;\n  disabled?: boolean;\n  ref: React.RefObject<HTMLDivElement>;\n  placeholder?: string | undefined;\n};\n\nconst TypeaheadInput: IStyledComponent<'web', TypeaheadInputProps> = styled.input.withConfig({\n  shouldForwardProp\n})<TypeaheadInputProps>`\n  ${props => (props.light ? props.theme.inputLT : props.theme.secondaryInput)}\n  &:hover {\n    cursor: pointer;\n    background-color: ${props =>\n      props.light ? props.theme.selectBackgroundLT : props.theme.secondaryInputBgd};\n  }\n`;\n\nconst InputIcon = styled.div.attrs({\n  className: 'typeahead__input_icon'\n})`\n  position: absolute;\n  right: 15px;\n  top: 14px;\n  color: ${props => props.theme.inputPlaceholderColor};\n`;\n\nfunction generateSearchFunction(props: TypeaheadProps) {\n  const {searchOptions, filterOption} = props;\n  if (typeof searchOptions === 'function') {\n    if (filterOption !== null) {\n      Console.warn('searchOptions prop is being used, filterOption prop will be ignored');\n    }\n    return searchOptions;\n  } else if (typeof filterOption === 'function') {\n    // use custom filter option\n    return (value, options) => options.filter(o => filterOption(value, o));\n  }\n\n  const mapper =\n    typeof filterOption === 'string'\n      ? Accessor.generateAccessor(filterOption)\n      : Accessor.IDENTITY_FN;\n\n  return (value, options) => {\n    return fuzzy.filter(value, options, {extract: mapper}).map(res => options[res.index]);\n  };\n}\n\nfunction searchOptionsOnInput(inputValue, props) {\n  const searchOptions = generateSearchFunction(props);\n  return searchOptions(inputValue, props.options);\n}\n\nfunction getOptionsForValue(value, props, state) {\n  const {options, showOptionsWhenEmpty} = props;\n\n  if (!props.searchable) {\n    // directly pass through options if can not be searched\n    return options;\n  }\n  if (shouldSkipSearch(value, state, showOptionsWhenEmpty)) {\n    return options;\n  }\n\n  const searchOptions = generateSearchFunction(props);\n  return searchOptions(value, options);\n}\n\nfunction shouldSkipSearch(input, state, showOptionsWhenEmpty) {\n  const emptyValue = !input || input.trim().length === 0;\n\n  // this.state must be checked because it may not be defined yet if this function\n  // is called from within getInitialState\n  const isFocused = state && state.isFocused;\n  return !(showOptionsWhenEmpty && isFocused) && emptyValue;\n}\n\ntype Option = string | number | boolean | object | undefined;\ninterface TypeaheadProps {\n  name?: string;\n  customClasses?: any;\n  resultsTruncatedMessage?: string;\n  options?: ReadonlyArray<Option>;\n  fixedOptions?: ReadonlyArray<Option> | null;\n  allowCustomValues?: number;\n  initialValue?: string;\n  value?: string;\n  placeholder?: string;\n  disabled?: boolean;\n  textarea?: boolean;\n  inputProps?: object;\n  onOptionSelected?: (option: any, event: any) => any;\n  onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n  onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n  onKeyPress?: KeyboardEventHandler<HTMLDivElement>;\n  onKeyUp?: KeyboardEventHandler<HTMLDivElement>;\n  onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;\n  onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;\n  filterOption?: string | ((o: Option, s: string) => boolean);\n  searchOptions?: (o: Option, s: string) => boolean;\n  displayOption?: string | ((o: Option) => string);\n  inputDisplayOption?: string | ((o: Option) => string);\n  formInputOption?: string | ((o: Option) => string);\n  defaultClassNames?: boolean;\n  customListComponent?: ElementType;\n  customListItemComponent?: ElementType;\n  customListHeaderComponent?: ElementType | null;\n  showOptionsWhenEmpty?: boolean;\n  searchable?: boolean;\n  light?: boolean;\n  inputIcon: ElementType;\n  className?: string;\n  selectedItems?: any[] | null;\n  autoFocus: boolean;\n  // deprecated\n  maxVisible?: number;\n}\n\ninterface TypeaheadState {\n  searchResults: ReadonlyArray<string | number | boolean | object | undefined>;\n\n  // This should be called something else, 'entryValue'\n  entryValue?: string;\n\n  // A valid typeahead value\n  selection?: string;\n\n  // Index of the selection\n  selectionIndex: null;\n\n  // Keep track of the focus state of the input element, to determine\n  // whether to show options when empty (if showOptionsWhenEmpty is true)\n  isFocused: boolean;\n}\n\nfunction noop() {\n  return;\n}\nclass Typeahead extends Component<TypeaheadProps, TypeaheadState> {\n  static defaultProps = {\n    options: [],\n    customClasses: {\n      results: 'list-selector',\n      input: 'typeahead__input',\n      listItem: 'list__item',\n      listAnchor: 'list__item__anchor'\n    },\n    allowCustomValues: 0,\n    initialValue: '',\n    value: '',\n    placeholder: '',\n    disabled: false,\n    textarea: false,\n    inputProps: {},\n    onOptionSelected: noop,\n    onChange: noop,\n    onKeyDown: noop,\n    onKeyPress: noop,\n    onKeyUp: noop,\n    onFocus: noop,\n    onBlur: noop,\n    filterOption: null,\n    searchOptions: null,\n    inputDisplayOption: null,\n    defaultClassNames: true,\n    customListComponent: DropdownList,\n    customListItemComponent: ListItem,\n    inputIcon: Search,\n    customListHeaderComponent: null,\n    showOptionsWhenEmpty: true,\n    searchable: true,\n    resultsTruncatedMessage: null,\n    autoFocus: true\n  };\n\n  static getDerivedStateFromProps(props, state) {\n    if (props.options === state.prevOptions) {\n      return {};\n    }\n\n    //  invoked after a component is instantiated as well as before it is re-rendered\n    const searchResults = getOptionsForValue(state.entryValue, props, state);\n\n    return {\n      searchResults,\n      prevOptions: props.options\n    };\n  }\n\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      // initiate searchResults with options\n      searchResults: this.props.options || [],\n\n      // This should be called something else, 'entryValue'\n      entryValue: this.props.value || this.props.initialValue,\n\n      // A valid typeahead value\n      selection: this.props.value,\n\n      // Index of the selection\n      selectionIndex: null,\n\n      // Keep track of the focus state of the input element, to determine\n      // whether to show options when empty (if showOptionsWhenEmpty is true)\n      isFocused: false\n    };\n  }\n\n  componentDidMount() {\n    // call focus on entry or div to trigger key events listener\n    if (this.props.autoFocus) {\n      if (this.entry.current) {\n        this.entry.current.focus();\n      } else {\n        this.root.current?.focus();\n      }\n    }\n  }\n\n  root = createRef<HTMLDivElement>();\n  entry = createRef<HTMLInputElement>();\n\n  focus = () => {\n    if (this.entry.current) {\n      this.entry.current.focus();\n    }\n  };\n\n  _hasCustomValue = () => {\n    return (\n      Number(this.props.allowCustomValues) > 0 &&\n      Number(this.state.entryValue?.length) >= Number(this.props.allowCustomValues) &&\n      this.state.searchResults.indexOf(this.state.entryValue) < 0\n    );\n  };\n\n  _getCustomValue = () => {\n    return this._hasCustomValue() ? this.state.entryValue : null;\n  };\n\n  _renderIncrementalSearchResults() {\n    const {customListComponent: CustomListComponent = DropdownList} = this.props;\n    return (\n      <CustomListComponent\n        fixedOptions={this.props.fixedOptions}\n        options={this.state.searchResults}\n        areResultsTruncated={false}\n        resultsTruncatedMessage={this.props.resultsTruncatedMessage}\n        onOptionSelected={this._onOptionSelected}\n        allowCustomValues={this.props.allowCustomValues}\n        customValue={this._getCustomValue()}\n        customClasses={this.props.customClasses}\n        customListItemComponent={this.props.customListItemComponent}\n        customListHeaderComponent={this.props.customListHeaderComponent}\n        selectionIndex={this.state.selectionIndex}\n        defaultClassNames={this.props.defaultClassNames}\n        displayOption={this.props.displayOption}\n        selectedItems={this.props.selectedItems}\n        light={this.props.light}\n      />\n    );\n  }\n\n  getSelection() {\n    let index: number | null = this.state.selectionIndex;\n    if (index === null) {\n      return null;\n    }\n    index = Number(index);\n\n    if (this._hasCustomValue()) {\n      if (index === 0) {\n        return this.state.entryValue;\n      }\n      index--;\n    }\n    if (this._hasFixedOptions()) {\n      return index < Number(this.props.fixedOptions?.length)\n        ? this.props.fixedOptions?.[index]\n        : this.state.searchResults[index - Number(this.props.fixedOptions?.length)];\n    }\n    return this.state.searchResults[index];\n  }\n\n  _onOptionSelected = (option, event) => {\n    if (this.props.searchable) {\n      // reset entry input\n      this.setState({\n        // reset search options when selection has been made\n        searchResults: this.props.options || [],\n        selection: '',\n        entryValue: ''\n      });\n    }\n\n    this.props.onOptionSelected?.(option, event);\n  };\n\n  // use () => {} to avoid binding 'this'\n  _onTextEntryUpdated = () => {\n    if (this.props.searchable) {\n      const value = this.entry.current?.value;\n\n      this.setState({\n        searchResults: searchOptionsOnInput(value, this.props),\n        selection: '',\n        entryValue: value\n      });\n    }\n  };\n\n  _onEnter = event => {\n    const selection = this.getSelection();\n    if (!selection) {\n      this.props.onKeyDown?.(event);\n    }\n    this._onOptionSelected(selection, event);\n  };\n\n  _onEscape = () => {\n    this.setState({\n      selectionIndex: null\n    });\n  };\n\n  _onTab = event => {\n    const selection = this.getSelection();\n    let option = selection\n      ? selection\n      : this.state.searchResults.length > 0\n      ? this.state.searchResults[0]\n      : null;\n\n    if (option === null && this._hasCustomValue()) {\n      option = this._getCustomValue();\n    }\n\n    if (option !== null) {\n      return this._onOptionSelected(option, event);\n    }\n  };\n\n  eventMap = () => {\n    const events = {};\n\n    events[KeyEvent.DOM_VK_UP] = this.navUp;\n    events[KeyEvent.DOM_VK_DOWN] = this.navDown;\n    events[KeyEvent.DOM_VK_RETURN] = events[KeyEvent.DOM_VK_ENTER] = this._onEnter;\n    events[KeyEvent.DOM_VK_ESCAPE] = this._onEscape;\n    events[KeyEvent.DOM_VK_TAB] = this._onTab;\n\n    return events;\n  };\n\n  _nav = delta => {\n    if (!this._hasHint()) {\n      return;\n    }\n    let newIndex =\n      this.state.selectionIndex === null\n        ? delta === 1\n          ? 0\n          : delta\n        : this.state.selectionIndex + delta;\n    let length = this.props.maxVisible\n      ? this.state.searchResults.slice(0, this.props.maxVisible).length\n      : this.state.searchResults.length;\n    if (this._hasCustomValue()) {\n      length += 1;\n    }\n\n    if (newIndex < 0) {\n      newIndex += length;\n    } else if (newIndex >= length) {\n      newIndex -= length;\n    }\n\n    this.setState({selectionIndex: newIndex});\n  };\n\n  navDown = () => {\n    this._nav(1);\n  };\n\n  navUp = () => {\n    this._nav(-1);\n  };\n\n  _onChange: React.ChangeEventHandler<HTMLInputElement> = event => {\n    if (this.props.onChange) {\n      this.props.onChange(event);\n    }\n\n    this._onTextEntryUpdated();\n  };\n\n  _onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {\n    // If there are no visible elements, don't perform selector navigation.\n    // Just pass this up to the upstream onKeydown handler.\n    // Also skip if the user is pressing the shift key, since none of our handlers are looking for shift\n    if (!this._hasHint() || event.shiftKey) {\n      return this.props.onKeyDown?.(event);\n    }\n\n    const handler = this.eventMap()[event.keyCode];\n\n    if (handler) {\n      handler(event);\n    } else {\n      return this.props.onKeyDown?.(event);\n    }\n    // Don't propagate the keystroke back to the DOM/browser\n    event.preventDefault();\n  };\n\n  _onFocus: React.FocusEventHandler<HTMLDivElement> = event => {\n    this.setState({isFocused: true});\n    if (this.props.onFocus) {\n      return this.props.onFocus(event);\n    }\n  };\n\n  _onBlur: React.FocusEventHandler<HTMLInputElement> = event => {\n    this.setState({isFocused: false});\n    if (this.props.onBlur) {\n      return this.props.onBlur(event);\n    }\n  };\n\n  _renderHiddenInput() {\n    if (!this.props.name) {\n      return null;\n    }\n\n    return <input type=\"hidden\" name={this.props.name} value={this.state.selection} />;\n  }\n\n  _hasHint() {\n    return this.state.searchResults.length > 0 || this._hasCustomValue();\n  }\n\n  _hasFixedOptions() {\n    return Array.isArray(this.props.fixedOptions) && this.props.fixedOptions.length;\n  }\n\n  render() {\n    const inputClasses = {};\n    inputClasses[this.props.customClasses?.input] = Boolean(this.props.customClasses?.input);\n    const inputClassList = classNames(inputClasses);\n\n    const classes = {\n      [DEFAULT_CLASS]: this.props.defaultClassNames\n    };\n    classes[this.props.className ? this.props.className : ''] = Boolean(this.props.className);\n    const classList = classNames(classes);\n\n    return (\n      <TypeaheadWrapper\n        className={classList}\n        ref={this.root}\n        tabIndex={0}\n        onKeyDown={this._onKeyDown}\n        onKeyPress={this.props.onKeyPress}\n        onKeyUp={this.props.onKeyUp}\n        onFocus={this._onFocus}\n        light={this.props.light}\n      >\n        {this._renderHiddenInput()}\n        {this.props.searchable ? (\n          <InputBox>\n            <TypeaheadInput\n              ref={this.entry}\n              type=\"text\"\n              disabled={this.props.disabled}\n              {...this.props.inputProps}\n              placeholder={this.props.placeholder}\n              className={inputClassList}\n              value={this.state.entryValue}\n              onChange={this._onChange}\n              onBlur={this._onBlur}\n              light={this.props.light}\n            />\n            <InputIcon>\n              <this.props.inputIcon height=\"18px\" />\n            </InputIcon>\n          </InputBox>\n        ) : null}\n        {this._renderIncrementalSearchResults()}\n      </TypeaheadWrapper>\n    );\n  }\n}\n\npolyfill(Typeahead);\n\nexport default Typeahead;\n"
  },
  {
    "path": "src/components/src/common/line-chart.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport {\n  HorizontalGridLines,\n  LineSeries,\n  XYPlot,\n  CustomSVGSeries,\n  Hint,\n  YAxis,\n  MarkSeries,\n  LineSeriesPoint,\n  RVNearestXData\n} from 'react-vis';\nimport {LineChart} from '@kepler.gl/types';\nimport styled from 'styled-components';\nimport {datetimeFormatter} from '@kepler.gl/utils';\n\nconst LineChartWrapper = styled.div`\n  .rv-xy-plot {\n    /* important for rendering hint */\n    position: relative;\n  }\n  .rv-xy-plot__inner {\n    /* important to show axis */\n    overflow: visible;\n  }\n\n  .rv-xy-plot__grid-lines__line {\n    stroke: ${props => props.theme.histogramFillOutRange};\n    stroke-dasharray: 1px 4px;\n  }\n\n  .rv-xy-plot__axis__tick__text {\n    font-size: 9px;\n    fill: ${props => props.theme.textColor};\n  }\n`;\n\nconst StyledHint = styled.div`\n  background-color: #d3d8e0;\n  border-radius: 2px;\n  color: ${props => props.theme.textColorLT};\n  font-size: 9px;\n  margin: 4px;\n  padding: 3px 6px;\n  pointer-events: none;\n  user-select: none;\n`;\n\ninterface HintContentProps {\n  x: number;\n  y: number;\n  format: (ts: number) => string;\n}\n\nconst HintContent = ({x, y, format}: HintContentProps) => (\n  <StyledHint>\n    <div className=\"hint--x\">{format(x)}</div>\n    <div className=\"row\">{y}</div>\n  </StyledHint>\n);\n\nexport interface HoverDP {\n  x: number;\n  y: number;\n  color?: string | number;\n  opacity?: string | number;\n  stroke?: string | number;\n  fill?: string | number;\n  size?: string | number;\n}\n\ninterface LineChartProps {\n  brushComponent?: any;\n  brushing?: boolean;\n  color?: string;\n  enableChartHover?: boolean;\n  height: number;\n  hoveredDP?: HoverDP | null;\n  isEnlarged?: boolean;\n  lineChart?: LineChart;\n  margin: {top?: number; bottom?: number; left?: number; right?: number};\n  onMouseMove: (datapoint: LineSeriesPoint | null, data?: RVNearestXData<LineSeriesPoint>) => void;\n  width: number;\n  timezone?: string | null;\n  timeFormat?: string;\n}\n\nconst MARGIN = {top: 0, bottom: 0, left: 0, right: 0};\nfunction LineChartFactory() {\n  const LineChartComponent = ({\n    brushComponent,\n    brushing,\n    color,\n    enableChartHover,\n    height,\n    hoveredDP,\n    isEnlarged,\n    lineChart,\n    margin,\n    onMouseMove,\n    width,\n    timezone,\n    timeFormat\n  }: LineChartProps) => {\n    const {yDomain, xDomain} = lineChart || {};\n    // @ts-expect-error seems lineChart.series has ambiguous types. Requires refactoring.\n    const series: {lines: any[]; markers: any[]} = lineChart?.series;\n\n    const paddedYDomain = useMemo(\n      () =>\n        yDomain && yDomain[0] && yDomain[1]\n          ? [yDomain[0], yDomain[1] + (yDomain[1] - yDomain[0]) * 0.2]\n          : [],\n      [yDomain]\n    );\n    const brushData = useMemo(() => {\n      return xDomain && paddedYDomain\n        ? [\n            {\n              x: xDomain[0],\n              y: paddedYDomain[1],\n              customComponent: () => brushComponent\n            }\n          ]\n        : [];\n    }, [xDomain, paddedYDomain, brushComponent]);\n\n    const hintFormatter = useMemo(\n      () => datetimeFormatter(timezone)(timeFormat),\n      [timezone, timeFormat]\n    );\n\n    return (\n      <LineChartWrapper style={{marginTop: `${margin.top}px`}}>\n        <XYPlot\n          xType=\"time\"\n          width={width}\n          height={height}\n          margin={MARGIN}\n          onMouseLeave={() => {\n            onMouseMove(null);\n          }}\n          yDomain={paddedYDomain}\n          xDomain={xDomain}\n        >\n          <HorizontalGridLines tickTotal={3} />\n          {series.lines.map((d, i) => (\n            <LineSeries\n              key={i}\n              style={{fill: 'none'}}\n              color={color}\n              data={d}\n              onNearestX={series.markers.length || !enableChartHover ? undefined : onMouseMove}\n            />\n          ))}\n          <MarkSeries data={hoveredDP ? [hoveredDP] : []} color={color} />\n          <CustomSVGSeries data={brushData} />\n          {isEnlarged && <YAxis tickTotal={3} />}\n          {hoveredDP && enableChartHover && !brushing ? (\n            <Hint value={hoveredDP}>\n              <HintContent {...hoveredDP} format={hintFormatter} />\n            </Hint>\n          ) : null}\n        </XYPlot>\n      </LineChartWrapper>\n    );\n  };\n  return LineChartComponent;\n}\n\nexport default LineChartFactory;\n"
  },
  {
    "path": "src/components/src/common/link-renderer.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {FC} from 'react';\n\ninterface LinkRendererProps {\n  href: string;\n  children: React.ReactNode;\n}\n\nconst LinkRenderer: FC<LinkRendererProps> = props => {\n  return (\n    <a href={props.href} target=\"_blank\" rel=\"noopener noreferrer\">\n      {props.children}\n    </a>\n  );\n};\n\nexport default LinkRenderer;\n"
  },
  {
    "path": "src/components/src/common/loading-spinner.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {CSSProperties} from 'react';\nimport styled, {keyframes, IStyledComponent} from 'styled-components';\n\nimport {BaseComponentProps} from '../types';\n\nconst animationName = keyframes`\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n`;\n\nconst Loader = styled.span`\n    border-left-color: ${props => props.color || props.theme.primaryBtnBgd};\n    border-radius: 50%;\n    border-top-color: transparent;\n    border-bottom-color: transparent;\n    border-right-color: transparent;\n    cursor: wait;\n    border-style: solid;\n    display: block;\n    animation: ${animationName} 500ms linear infinite;\n}`;\n\nexport type LoadingWrapperProps = BaseComponentProps & {\n  borderColor?: CSSProperties['borderColor'];\n};\n\nconst LoadingWrapper: IStyledComponent<\n  'web',\n  LoadingWrapperProps\n> = styled.div<LoadingWrapperProps>`\n  border-radius: 50%;\n  border: 3px solid ${props => props.borderColor || props.theme.borderColorLT};\n  padding: 2px;\n`;\n\nexport type LoadingSpinnerProps = {\n  size?: number;\n  color?: string;\n  borderColor?: CSSProperties['borderColor'];\n  strokeWidth?: number;\n  gap?: number;\n};\n\nconst LoadingSpinner: React.FC<LoadingSpinnerProps> = ({\n  size = 32,\n  color = '',\n  borderColor = '',\n  strokeWidth = 3,\n  gap = 2\n}) => (\n  <LoadingWrapper\n    style={{width: `${size}px`, height: `${size}px`, padding: `${gap}px`, borderColor}}\n  >\n    <Loader\n      color={color}\n      style={{\n        width: `${size - strokeWidth * 2 - gap * 2}px`,\n        height: `${size - strokeWidth * 2 - gap * 2}px`,\n        borderWidth: strokeWidth\n      }}\n    />\n  </LoadingWrapper>\n);\n\nexport default LoadingSpinner;\n"
  },
  {
    "path": "src/components/src/common/logo.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {KEPLER_GL_NAME, KEPLER_GL_VERSION, KEPLER_GL_WEBSITE} from '@kepler.gl/constants';\n\nconst LogoTitle = styled.div`\n  display: inline-block;\n  margin-left: 6px;\n`;\n\nconst LogoName = styled.div`\n  .logo__link {\n    color: ${props => props.theme.logoColor};\n    font-size: 14px;\n    font-weight: 600;\n    letter-spacing: 1.17px;\n  }\n`;\nconst LogoVersion = styled.div`\n  font-size: 10px;\n  color: ${props => props.theme.subtextColor};\n  letter-spacing: 0.83px;\n  line-height: 14px;\n`;\n\nconst LogoWrapper = styled.div`\n  display: flex;\n  align-items: flex-start;\n`;\n\nconst LogoSvgWrapper = styled.div`\n  margin-top: 3px;\n`;\n\nconst LogoSvg = () => (\n  <svg className=\"side-panel-logo__logo\" width=\"22px\" height=\"15px\" viewBox=\"0 0 22 15\">\n    <g transform=\"translate(11, -3) rotate(45.000000)\">\n      <rect fill=\"#535C6C\" x=\"0\" y=\"5\" width=\"10\" height=\"10\" />\n      <rect fill=\"#1FBAD6\" x=\"5\" y=\"0\" width=\"10\" height=\"10\" />\n    </g>\n  </svg>\n);\ninterface KeplerGlLogoProps {\n  appName?: string;\n  version?: string | boolean;\n  appWebsite?: string;\n}\n\nconst KeplerGlLogo = ({\n  appName = KEPLER_GL_NAME,\n  appWebsite = KEPLER_GL_WEBSITE,\n  version = KEPLER_GL_VERSION\n}: KeplerGlLogoProps) => (\n  <LogoWrapper className=\"side-panel-logo\">\n    <LogoSvgWrapper>\n      <LogoSvg />\n    </LogoSvgWrapper>\n    <LogoTitle className=\"logo__title\">\n      <LogoName className=\"logo__name\">\n        <a className=\"logo__link\" target=\"_blank\" rel=\"noopener noreferrer\" href={appWebsite}>\n          {appName}\n        </a>\n      </LogoName>\n      {version ? <LogoVersion className=\"logo__version\">{version}</LogoVersion> : null}\n    </LogoTitle>\n  </LogoWrapper>\n);\n\nexport default KeplerGlLogo;\n"
  },
  {
    "path": "src/components/src/common/map-layer-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport Checkbox from './checkbox';\nimport {generateHashId} from '@kepler.gl/common-utils';\n\nconst MapLayerSelect = styled.div`\n  padding: 12px;\n\n  .map-layer-selector__item {\n    margin: 12px 0;\n  }\n`;\ninterface Layer {\n  id: string;\n  name: string;\n  isVisible: boolean;\n}\n\ninterface MapLayerSelectorProps {\n  layers: Layer[];\n  onMapToggleLayer: (layerId: string) => void;\n}\n\n/** @type {typeof import('./map-layer-selector').default} */\nconst MapLayerSelector = ({layers, onMapToggleLayer}: MapLayerSelectorProps) => (\n  <MapLayerSelect className=\"map-layer-selector\">\n    {layers.map(layer => (\n      <div key={layer.id} className=\"map-layer-selector__item\">\n        <Checkbox\n          type=\"radio\"\n          checked={layer.isVisible}\n          id={`${layer.id}-toggle-${generateHashId(4)}`}\n          label={layer.name}\n          onChange={() => {\n            onMapToggleLayer(layer.id);\n          }}\n        />\n      </div>\n    ))}\n  </MapLayerSelect>\n);\n\nexport default MapLayerSelector;\n"
  },
  {
    "path": "src/components/src/common/modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, ReactNode, PropsWithChildren} from 'react';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport styled, {css} from 'styled-components';\nimport Modal from 'react-modal';\nimport {Delete} from './icons';\nimport {Button} from './styled-components';\nimport {media} from '@kepler.gl/styles';\n\ntype CssStyleType = ReturnType<typeof css>;\n\ninterface ModalContentWrapperProps {\n  cssStyle?: CssStyleType | string;\n}\n\nconst ModalContentWrapper = styled.div<ModalContentWrapperProps>`\n  overflow-y: auto;\n  max-width: 70vw;\n  max-height: 85vh;\n  padding: 24px 72px 40px;\n  position: relative;\n  top: 92px;\n  left: 0;\n  right: 0;\n  margin: 0 auto;\n  background-color: #ffffff;\n  border-radius: 4px;\n  transition: ${props => props.theme.transition};\n  box-sizing: border-box;\n  font-size: 12px;\n  color: ${props => props.theme.labelColorLT};\n\n  ${media.portable`\n    padding: 12px 36px 24px;\n    max-width: 80vw;\n  `}\n\n  ${media.palm`\n    max-width: 100vw;\n  `}\n\n  ${props => props.cssStyle || ''};\n`;\n\nconst ModalContent = styled.div`\n  position: relative;\n  z-index: ${props => props.theme.modalContentZ};\n`;\n\ntype ModalTitleProps = PropsWithChildren<{\n  style?: React.CSSProperties;\n  className?: string;\n}>;\n\nexport const ModalTitle = styled.div<ModalTitleProps>`\n  font-size: ${props => props.theme.modalTitleFontSize};\n  color: ${props => props.theme.modalTitleColor};\n  margin-bottom: 10px;\n  position: relative;\n  z-index: ${props => props.theme.modalTitleZ};\n`;\n\nconst StyledModalFooter = styled.div`\n  width: 100%;\n  left: 0;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-end;\n  padding-top: 24px;\n  ${media.portable`\n    padding-top: 24px;\n  `};\n\n  ${media.palm`\n    padding-top: 16px;\n  `};\n  z-index: ${props => props.theme.modalFooterZ};\n`;\n\nconst CloseButton = styled.div`\n  color: ${props => props.theme.titleColorLT};\n  display: flex;\n  justify-content: flex-end;\n  z-index: ${props => props.theme.modalButtonZ};\n  position: absolute;\n  top: 24px;\n  right: 24px;\n\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nconst FooterActionWrapper = styled.div`\n  display: flex;\n  justify-content: flex-end;\n`;\n\nconst defaultCancelButton = {\n  link: true,\n  large: true,\n  children: 'modal.button.defaultCancel'\n};\n\nconst defaultConfirmButton = {\n  cta: true,\n  large: true,\n  width: '160px',\n  children: 'modal.button.defaultConfirm'\n};\n\ntype ModalButtonProps = {\n  style?: React.CSSProperties;\n  large?: boolean;\n  disabled?: boolean;\n  negative?: boolean;\n  children?: string;\n};\n\ntype ModalFooterProps = {\n  cancel: () => void;\n  confirm: (data?: any) => void;\n  cancelButton?: ModalButtonProps;\n  confirmButton?: ModalButtonProps;\n};\n\n/**\n * this method removes the `disabled` property from button props when disabled is set to false\n * to avoid issue with the disabled tag\n *\n * @param props\n */\nconst processDisabledProperty = (props: ModalButtonProps): ModalButtonProps => {\n  if (!props.disabled) {\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const {disabled, ...newProps} = props;\n    return newProps;\n  }\n  return props;\n};\n\nexport const ModalFooter: React.FC<ModalFooterProps> = ({\n  cancel,\n  confirm,\n  cancelButton,\n  confirmButton\n}) => {\n  const cancelButtonProps = processDisabledProperty({\n    ...defaultCancelButton,\n    ...cancelButton\n  });\n  const confirmButtonProps = processDisabledProperty({...defaultConfirmButton, ...confirmButton});\n  return (\n    <StyledModalFooter className=\"modal--footer\">\n      <FooterActionWrapper>\n        <Button className=\"modal--footer--cancel-button\" {...cancelButtonProps} onClick={cancel}>\n          <FormattedMessage id={cancelButtonProps.children ?? ''} />\n        </Button>\n        <Button className=\"modal--footer--confirm-button\" {...confirmButtonProps} onClick={confirm}>\n          <FormattedMessage id={confirmButtonProps.children ?? ''} />\n        </Button>\n      </FooterActionWrapper>\n    </StyledModalFooter>\n  );\n};\n\nexport interface ModalDialogOwnProps {\n  footer: boolean;\n  close: boolean;\n  isOpen: boolean;\n  title?: string;\n  className?: string;\n  onConfirm: (...args: any) => void;\n  onCancel: (...args: any) => void;\n  confirmButton?: ModalButtonProps;\n  confirmButtonLabel?: string;\n  cancelButton?: ModalButtonProps;\n  cancelButtonLabel?: string;\n  cssStyle?: CssStyleType | string;\n  style?: React.CSSProperties;\n  theme: any;\n  children?: ReactNode;\n}\n\nexport type ModalDialogProps = ModalDialogOwnProps &\n  Omit<ReactModal.Props, 'style' | 'ariaHideApp' | 'className'>;\n\nexport class ModalDialog extends Component<ModalDialogProps> {\n  static defaultProps = {\n    footer: false,\n    close: true,\n    onConfirm: () => {\n      return;\n    },\n    onCancel: () => {\n      return;\n    },\n    cancelButton: defaultCancelButton,\n    confirmButton: defaultConfirmButton,\n    cssStyle: []\n  };\n\n  render() {\n    const {props} = this;\n    return (\n      <Modal\n        className={props.className}\n        {...props}\n        testId={props['data-testid']}\n        ariaHideApp={false}\n        style={{\n          overlay: {\n            backgroundColor: (props.theme && props.theme.modalOverlayBgd) || 'rgba(0, 0, 0, 0.5)',\n            zIndex: (props.theme && props.theme.modalOverLayZ) || 1000,\n            // in case we want to override the modal dialog style\n            ...props.style\n          }\n        }}\n      >\n        <ModalContentWrapper className=\"modal--wrapper\" cssStyle={props.cssStyle}>\n          {props.close && (\n            <CloseButton className=\"modal--close\" onClick={props.onCancel}>\n              <Delete height=\"18px\" />\n            </CloseButton>\n          )}\n          <div>\n            {props.title && (\n              <ModalTitle className=\"modal--title\">\n                <FormattedMessage id={props.title} />\n              </ModalTitle>\n            )}\n            <ModalContent className=\"modal--body\">{props.children}</ModalContent>\n            {props.footer && (\n              <ModalFooter\n                cancel={props.onCancel}\n                confirm={props.onConfirm}\n                cancelButton={props.cancelButton}\n                confirmButton={props.confirmButton}\n              />\n            )}\n          </div>\n        </ModalContentWrapper>\n      </Modal>\n    );\n  }\n}\n\nconst StyledModal = styled(ModalDialog)`\n  top: 0;\n  left: 0;\n  transition: ${props => props.theme.transition};\n  padding-left: 40px;\n  padding-right: 40px;\n  outline: none;\n\n  ${media.portable`\n    padding-left: 24px;\n    padding-right: 24px;\n  `};\n\n  ${media.palm`\n    padding-left: 0;\n    padding-right: 0;\n  `};\n\n  &:focus {\n    outline: 0;\n  }\n`;\n\nexport default StyledModal;\n"
  },
  {
    "path": "src/components/src/common/portaled.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, PropsWithChildren} from 'react';\nimport debounce from 'lodash/debounce';\nimport isEqual from 'lodash/isEqual';\n\nimport {canUseDOM} from 'exenv';\nimport {withTheme} from 'styled-components';\nimport {RootContext} from '../context';\nimport Modal from 'react-modal';\nimport Window from 'global/window';\nimport {theme} from '@kepler.gl/styles';\n\nconst listeners = {};\n\nconst startListening = () => Object.keys(listeners).forEach(key => listeners[key]());\n\nconst getPageOffset = () => ({\n  x:\n    Window.pageXOffset !== undefined\n      ? Window.pageXOffset\n      : (document.documentElement || document.body.parentNode || document.body).scrollLeft,\n  y:\n    Window.pageYOffset !== undefined\n      ? Window.pageYOffset\n      : (document.documentElement || document.body.parentNode || document.body).scrollTop\n});\n\nconst addEventListeners = () => {\n  if (document && document.body)\n    document.body.addEventListener('mousewheel', debounce(startListening, 100, {leading: true}));\n  Window.addEventListener('resize', debounce(startListening, 50, {leading: true}));\n};\n\ninterface GetChildPosProps {\n  offsets: Partial<{\n    topOffset: number;\n    leftOffset: number;\n    rightOffset: number;\n  }>;\n  rect: DOMRect;\n  childRect: DOMRect;\n  pageOffset: {\n    x: number;\n    y: number;\n  };\n  padding: number;\n}\n\nexport const getChildPos = ({offsets, rect, childRect, pageOffset, padding}: GetChildPosProps) => {\n  const {topOffset, leftOffset, rightOffset} = offsets;\n\n  const anchorLeft = leftOffset !== undefined;\n  const pos = {\n    top: pageOffset.y + rect.top + (topOffset || 0),\n    ...(anchorLeft\n      ? {left: pageOffset.x + rect.left + (leftOffset || 0)}\n      : {right: Window.innerWidth - rect.right - pageOffset.x + (rightOffset || 0)})\n  };\n\n  const leftOrRight = anchorLeft ? 'left' : 'right';\n\n  if (pos[leftOrRight] && pos[leftOrRight] < 0) {\n    pos[leftOrRight] = padding;\n  } else if (pos[leftOrRight] && pos[leftOrRight] + childRect.width > Window.innerWidth) {\n    pos[leftOrRight] = Window.innerWidth - childRect.width - padding;\n  }\n\n  if (pos.top < 0) {\n    pos.top = padding;\n  } else if (pos.top + childRect.height > Window.innerHeight) {\n    pos.top = Window.innerHeight - childRect.height - padding;\n  }\n\n  return pos;\n};\n\nif (canUseDOM) {\n  if (document.body) {\n    addEventListeners();\n  } else {\n    document.addEventListener('DOMContentLoaded', addEventListeners);\n  }\n}\n\nlet listenerIdCounter = 0;\nfunction subscribe(fn) {\n  listenerIdCounter += 1;\n  const id = listenerIdCounter;\n  listeners[id] = fn;\n  return () => delete listeners[id];\n}\n\nconst defaultModalStyle = {\n  content: {\n    top: 0,\n    left: 0,\n    border: 0,\n    right: 'auto',\n    bottom: 'auto',\n    padding: '0px 0px 0px 0px'\n  },\n  overlay: {\n    right: 'auto',\n    bottom: 'auto',\n    width: '100vw',\n    height: '100vh',\n    backgroundColor: 'rgba(0, 0, 0, 0)'\n  }\n};\n\nconst WINDOW_PAD = 40;\n\nconst noop = () => {\n  return;\n};\n\ntype PortaledProps = PropsWithChildren<{\n  right?: number;\n  left?: number;\n  top?: number;\n  component?: React.ElementType<any>;\n  onClose?: (\n    event: React.MouseEvent<Element, globalThis.MouseEvent> | React.KeyboardEvent<Element>\n  ) => void;\n  topOffset?: number;\n  leftOffset?: number;\n  rightOffset?: number;\n  overlayZIndex?: number;\n  isOpened?: boolean;\n  modalProps?: Partial<ReactModal.Props>;\n  modalStyle?: Partial<typeof defaultModalStyle>;\n  theme?: any;\n}>;\n\ninterface PortaledState {\n  pos:\n    | {\n        left: number;\n        top: number;\n      }\n    | {\n        right: number;\n        top: number;\n      }\n    | null;\n  isVisible: boolean;\n}\n\nconst DefaultComponent = 'div';\n\nclass Portaled extends Component<PortaledProps, PortaledState> {\n  // Make Portaled a component with Error Boundary, so React can recreate\n  // this component if the child 'ColorPicker' throws cross-origin error.\n  // see function componentDidCatch()\n  static getDerivedStateFromError(): {hasError: boolean} {\n    return {hasError: true};\n  }\n\n  static defaultProps: PortaledProps = {\n    component: DefaultComponent,\n    onClose: noop,\n    theme\n  };\n\n  state = {\n    pos: null,\n    isVisible: false\n  };\n\n  unsubscribe: (() => boolean) | undefined = undefined;\n  _unmounted = false;\n\n  componentDidMount() {\n    this._unmounted = false;\n    // relative\n    this.unsubscribe = subscribe(this.handleScroll);\n    this.handleScroll();\n  }\n\n  componentDidUpdate(prevProps: PortaledProps) {\n    const didOpen = this.props.isOpened && !prevProps.isOpened;\n    const didClose = !this.props.isOpened && prevProps.isOpened;\n    if (didOpen || didClose) {\n      Window.requestAnimationFrame(() => {\n        if (this._unmounted) return;\n        this.setState({isVisible: Boolean(didOpen)});\n      });\n    }\n\n    this.handleScroll();\n  }\n\n  // ColorPicker will throw a cross-origin error when it is closed\n  // when the app is within an iframe.\n  // This is a known issue of react-color component:\n  // see: https://github.com/casesandberg/react-color/issues/806\n  componentDidCatch() {\n    // Do nothing here, since React will try to recreate this component\n    // tree from scratch using the error boundary, which is this component\n    // itself. This is a temporal fix for a crash.\n  }\n\n  componentWillUnmount() {\n    this._unmounted = true;\n    // @ts-ignore\n    this.unsubscribe();\n  }\n\n  element = createRef<HTMLDivElement>();\n  child = createRef<HTMLDivElement>();\n\n  // eslint-disable-next-line complexity\n  handleScroll = () => {\n    if (this.child.current && this.element.current) {\n      const rect = this.element.current.getBoundingClientRect();\n      const childRect = this.child.current.getBoundingClientRect();\n      const pageOffset = getPageOffset();\n      const {top: topOffset, left: leftOffset = 0, right: rightOffset} = this.props;\n\n      const pos = getChildPos({\n        offsets: {topOffset, leftOffset, rightOffset},\n        rect,\n        childRect,\n        pageOffset,\n        padding: WINDOW_PAD\n      });\n\n      if (!isEqual(pos, this.state.pos)) {\n        this.setState({pos});\n      }\n    }\n  };\n\n  render(): JSX.Element {\n    const {\n      // relative\n      component = DefaultComponent,\n      overlayZIndex,\n      isOpened,\n      onClose,\n\n      // Modal\n      children,\n      modalProps,\n      modalStyle = {}\n    } = this.props;\n\n    const {isVisible, pos} = this.state;\n\n    const newModalStyle = {\n      ...defaultModalStyle,\n      content: {\n        ...(modalStyle.content || {})\n      },\n      overlay: {\n        ...defaultModalStyle.overlay,\n        ...(modalStyle.overlay || {}),\n        // needs to be on top of existing modal\n        zIndex: overlayZIndex || 9999\n      }\n    };\n\n    const Comp = component;\n\n    return (\n      <RootContext.Consumer>\n        {context => (\n          <Comp ref={this.element}>\n            {isOpened ? (\n              <Modal\n                className=\"modal-portal\"\n                {...modalProps}\n                ariaHideApp={false}\n                isOpen\n                style={newModalStyle}\n                parentSelector={() => {\n                  // React modal issue: https://github.com/reactjs/react-modal/issues/769\n                  // failed to execute removeChild on parent node when it is already unmounted\n                  return (context && context.current) || document.body;\n                }}\n                onRequestClose={onClose}\n              >\n                <div\n                  className=\"portaled-content\"\n                  key=\"item\"\n                  style={{\n                    position: 'fixed',\n                    opacity: isVisible ? 1 : 0,\n                    transition: this.props.theme.transitionFast,\n                    marginTop: isVisible ? '0px' : '14px',\n                    // @ts-ignore\n                    ...pos\n                  }}\n                >\n                  <div\n                    ref={this.child}\n                    style={{\n                      position: 'absolute',\n                      zIndex: overlayZIndex ? overlayZIndex + 1 : 10000\n                    }}\n                  >\n                    {children}\n                  </div>\n                </div>\n              </Modal>\n            ) : null}\n          </Comp>\n        )}\n      </RootContext.Consumer>\n    );\n  }\n}\n\nexport default withTheme(Portaled);\n"
  },
  {
    "path": "src/components/src/common/progress-bar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\n/** @typedef {import('./progress-bar').ProgressBarProps} ProgressBarProps */\n\ninterface StyledBarProps {\n  barColor?: string;\n}\n\nconst StyledBar = styled.span.attrs({\n  className: 'progress-bar__bar'\n})<StyledBarProps>`\n  background-color: ${props => props.barColor || props.theme.progressBarColor};\n  /* transition: width 200ms; */\n  display: block;\n`;\n\ninterface StyledTrackProps {\n  trackColor?: string;\n}\n\nconst StyledTrack = styled.div.attrs({\n  className: 'progress-bar'\n})<StyledTrackProps>`\n  background-color: ${props => props.trackColor || props.theme.progressBarTrackColor};\n`;\n\nexport type ProgressBarProps = {\n  percent: string;\n  height?: number;\n  isLoading: boolean;\n  barColor: string;\n  trackColor?: string;\n  theme: any;\n};\n\n/** @type {React.FunctionComponent<ProgressBarProps>} */\nconst ProgressBar = ({\n  percent,\n  height = 4,\n  isLoading,\n  barColor,\n  trackColor,\n  theme\n}: ProgressBarProps) => (\n  <StyledTrack trackColor={trackColor} theme={theme}>\n    <StyledBar\n      barColor={barColor}\n      style={{width: percent, height: `${height}px`, opacity: isLoading ? 1 : 0}}\n    />\n  </StyledTrack>\n);\n\nexport default ProgressBar;\n"
  },
  {
    "path": "src/components/src/common/radius-legend.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, FC} from 'react';\nimport styled from 'styled-components';\nimport {scaleSqrt} from 'd3-scale';\nimport {SCALE_TYPES} from '@kepler.gl/constants';\nimport {formatNumber} from '@kepler.gl/utils';\nimport {max} from 'd3-array';\nimport {console as Console} from 'global/window';\n\nconst StyledLegend = styled.div<{width: number; height: number}>`\n  width: ${props => props.width}px;\n  height: ${props => props.height}px;\n  position: relative;\n  svg {\n    circle {\n      stroke: ${props => props.theme.borderColorLT};\n      fill: none;\n    }\n    line {\n      stroke: ${props => props.theme.borderColor};\n      stroke-dasharray: 2, 1;\n    }\n  }\n`;\n\nconst LabelsOuter = styled.div<{width: number; height: number}>`\n  text-align: right;\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: ${props => props.width}px;\n  height: ${props => props.height}px;\n`;\n\nconst tickHeight = 9;\n\nconst ValueLabel = styled.div`\n  position: absolute;\n  font-size: ${tickHeight}px;\n  height: ${tickHeight}px;\n  line-height: ${tickHeight}px;\n  margin-top: -${tickHeight / 2}px;\n  color: ${props => props.theme.textColor};\n  background-color: ${props => props.theme.mapPanelBackgroundColor};\n  padding: 0 2px;\n  border-radius: 2px;\n`;\n\nconst margin = {left: 1, top: 5, right: 2, bottom: 5};\n\ntype Props = {\n  width: number;\n  scaleType: string;\n  domain: [number, number];\n  fieldType: string;\n  range: [number, number];\n};\n\nconst RadiusLegend: FC<Props> = ({scaleType, width, domain, range, fieldType}) => {\n  const radiusScale = useMemo(() => {\n    if (scaleType !== SCALE_TYPES.sqrt) {\n      Console.warn(`Unsupported radius scale type: ${scaleType}`);\n      return undefined;\n    }\n    if (!Array.isArray(domain) || !domain.every(Number.isFinite)) {\n      return undefined;\n    }\n    return scaleSqrt().domain(domain).range(range);\n  }, [domain, range, scaleType]);\n\n  const radiusTicks = useMemo(() => {\n    if (radiusScale === undefined) return [];\n    const numTicksToFit = Math.min(10, ((range[1] - range[0]) * 2) / tickHeight);\n    const ticks = radiusScale.ticks(numTicksToFit);\n    // Add min and max values\n    if (ticks[0] > domain[0]) {\n      ticks.unshift(domain[0]);\n    }\n    if (ticks[ticks.length - 1] < domain[1]) {\n      ticks.push(domain[1]);\n    }\n    // Make sure there is no overlap\n    return ticks.reduceRight((acc, v) => {\n      if (acc.length === 0 || Math.abs(radiusScale(acc[0]) - radiusScale(v)) * 2 > tickHeight) {\n        // @ts-ignore\n        acc.unshift(v);\n      }\n      return acc;\n    }, new Array<number>());\n  }, [radiusScale, domain, range]);\n\n  if (!radiusScale || !radiusTicks.length) {\n    return null;\n  }\n  const maxR = Math.ceil(radiusScale(max(radiusTicks) || 0));\n  const w = width - margin.left - margin.right;\n  const h = maxR * 2;\n  const height = h + margin.top + margin.bottom;\n\n  return (\n    <StyledLegend width={width} height={height}>\n      <svg width={width} height={height}>\n        <g transform={`translate(${margin.left},${margin.top})`}>\n          <g>\n            {radiusTicks.map((v, i) => (\n              <g key={i}>\n                <g transform={`translate(${w},${h - radiusScale(v) * 2})`}>\n                  <line x1={0} x2={maxR - w} />\n                </g>\n              </g>\n            ))}\n          </g>\n          <g>\n            {radiusTicks.map((v, i) => {\n              const r = radiusScale(v);\n              return (\n                <g key={i}>\n                  <g transform={`translate(0,${h - r * 2})`}>\n                    <circle\n                      cx={maxR}\n                      cy={r}\n                      r={Math.max(0, r - 1)} /* stroke is drawn outside, hence r-1 */\n                    />\n                  </g>\n                </g>\n              );\n            })}\n          </g>\n        </g>\n      </svg>\n      <LabelsOuter width={width} height={height}>\n        {radiusTicks.map((v, i) => (\n          <ValueLabel\n            key={i}\n            style={{\n              right: margin.right,\n              top: margin.top + h - radiusScale(v) * 2\n            }}\n          >\n            {formatNumber(v, fieldType)}\n          </ValueLabel>\n        ))}\n      </LabelsOuter>\n    </StyledLegend>\n  );\n};\n\nexport default RadiusLegend;\n"
  },
  {
    "path": "src/components/src/common/range-brush.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef} from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport {select, Selection} from 'd3-selection';\nimport {BrushBehavior, brushX} from 'd3-brush';\nimport {normalizeSliderValue} from '@kepler.gl/utils';\n\ninterface StyledGProps {\n  $isRanged?: boolean;\n}\n\nconst StyledG = styled.g<StyledGProps>`\n  .selection {\n    stroke: none;\n    fill: ${props => (props.$isRanged ? props.theme.rangeBrushBgd : props.theme.BLUE2)};\n    fill-opacity: ${props => (props.$isRanged ? 0.3 : 1)};\n  }\n  .handle {\n    fill: ${props => props.theme.BLUE2};\n    fill-opacity: 0.3;\n  }\n`;\n\nfunction moveRight(startSel, selection) {\n  const [startSel0] = startSel;\n  const [sel0] = selection;\n\n  return Boolean(startSel0 === sel0);\n}\n// style brush resize handle\n// https://github.com/crossfilter/crossfilter/blob/gh-pages/index.html#L466\nconst getHandlePath = (props: RangeBrushProps) => {\n  return function brushResizePath(d) {\n    const e = Number(d.type === 'e');\n    const x = e ? 1 : -1;\n    const h = 39;\n    const w = 4.5;\n    const y = (props.height - h) / 2;\n    return `M${0.5 * x},${y}c${2.5 * x},0,${w * x},2,${w * x},${w}v${h - w * 2}c0,2.5,${\n      -2 * x\n    },${w},${-w * x},${w}V${y}z`;\n  };\n};\n\nexport type OnBrush = (val0: number, val1: number) => void;\n\nexport interface RangeBrushProps {\n  isRanged?: boolean;\n  theme?: any;\n  range: number[];\n  value: number[];\n  onBrushStart: () => void;\n  onBrushEnd: () => void;\n  width: number;\n  height: number;\n  onBrush: OnBrush;\n  step?: number;\n  marks?: number[];\n  onMouseoverHandle: () => void;\n  onMouseoutHandle: () => void;\n}\n\nfunction RangeBrushFactory(): React.ComponentType<RangeBrushProps> {\n  class RangeBrush extends Component<RangeBrushProps> {\n    static defaultProps = {\n      isRanged: true\n    };\n\n    rootContainer = createRef<SVGGElement>();\n\n    brushing = false;\n    moving = false;\n\n    root = this.rootContainer.current ? select(this.rootContainer.current) : undefined;\n    brush: BrushBehavior<any> | undefined;\n    _startSel: number[] | undefined;\n    _lastSel: number[] | undefined;\n\n    handle: Selection<SVGPathElement, {type: string}, SVGGElement | null, unknown> | undefined;\n\n    componentDidMount() {\n      // We want the React app to respond to brush state and vice-versa\n      // but d3-brush fires the same events for both user-initiated brushing\n      // and programmatic brushing (brush.move). We need these flags to\n      // distinguish between the uses.\n      //\n      // We don't use state because that would trigger another `componentDidUpdate`\n      const {theme, isRanged, onMouseoverHandle, onMouseoutHandle} = this.props;\n\n      this.root = this.rootContainer.current ? select(this.rootContainer.current) : undefined;\n      this.brush = brushX()\n        .handleSize(3)\n        .on('start', event => {\n          if (typeof this.props.onBrushStart === 'function') this.props.onBrushStart();\n          this._startSel = event.selection;\n        })\n        .on('brush', event => {\n          if (this.moving) {\n            return;\n          }\n          if (event.selection) {\n            this._lastSel = event.selection;\n            this.brushing = true;\n            this._brushed(event);\n          }\n        })\n        .on('end', event => {\n          if (!event.selection) {\n            if (this.brushing) {\n              // handle null selection\n              this._click(this._lastSel);\n            } else if (this._startSel) {\n              // handle click\n              this._click(this._startSel);\n            }\n          }\n\n          if (typeof this.props.onBrushEnd === 'function') this.props.onBrushEnd();\n\n          this.brushing = false;\n          this.moving = false;\n        });\n\n      this.root?.call(this.brush);\n      const brushResizePath = getHandlePath(this.props);\n      this.handle = this.root\n        ?.selectAll('.handle--custom')\n        .data([{type: 'w'}, {type: 'e'}])\n        .enter()\n        .append('path')\n        .attr('class', 'handle--custom')\n        .attr('display', isRanged ? null : 'none')\n        .attr('fill', theme ? theme.sliderHandleColor : '#D3D8E0')\n        .attr('cursor', 'ew-resize')\n        .attr('d', brushResizePath)\n        .on('mouseover', () => {\n          if (onMouseoverHandle) onMouseoverHandle();\n        })\n        .on('mouseout', () => {\n          if (onMouseoutHandle) onMouseoutHandle();\n        });\n\n      const {\n        value: [val0, val1]\n      } = this.props;\n      this.moving = true;\n      this._move(val0, val1);\n    }\n\n    componentDidUpdate(prevProps) {\n      const {\n        value: [val0, val1],\n        width\n      } = this.props;\n      const [prevVal0, prevVal1] = prevProps.value;\n\n      if (\n        prevProps.width !== width ||\n        prevProps.range[0] !== this.props.range[0] ||\n        prevProps.range[1] !== this.props.range[1]\n      ) {\n        // dimension change should not trigger this._brushed\n        this.moving = true;\n        if (this.brush) this.root?.call(this.brush);\n        this._move(val0, val1);\n      }\n\n      if (!this.brushing && !this.moving) {\n        if (prevVal0 !== val0 || prevVal1 !== val1) {\n          this.moving = true;\n          this._move(val0, val1);\n        }\n      }\n\n      if (!this.props.isRanged && this.handle) {\n        this.handle.attr('display', 'none');\n      }\n    }\n\n    _click(selection) {\n      // fake brush\n      this.brushing = true;\n      this._brushed({sourceEvent: {}, selection});\n    }\n\n    _move(val0 = 0, val1 = 0) {\n      const {\n        range: [min, max],\n        width,\n        isRanged\n      } = this.props;\n\n      if (width && max - min && this.brush && this.handle) {\n        const scale = (x: number) => ((x - min) * width) / (max - min);\n        if (!isRanged) {\n          // only draw a 1 pixel line\n          if (this.root) this.brush.move(this.root, [scale(val0), scale(val0) + 1]);\n        } else {\n          if (this.root) this.brush.move(this.root, [scale(val0), scale(val1)]);\n\n          this.handle\n            .attr('display', null)\n            .attr('transform', (d, i) => `translate(${[i === 0 ? scale(val0) : scale(val1), 0]})`);\n        }\n      }\n    }\n\n    _brushed = (evt: {sourceEvent: any; selection: number[]}) => {\n      // Ignore brush events which don't have an underlying sourceEvent\n      if (!evt.sourceEvent) return;\n      const [sel0, sel1] = evt.selection;\n      const right = moveRight(this._startSel, evt.selection);\n\n      const {\n        range: [min, max],\n        step = 0,\n        width,\n        marks,\n        isRanged\n      } = this.props;\n      const invert = (x: number) => (x * (max - min)) / width + min;\n      let d0 = invert(sel0);\n      let d1 = invert(sel1);\n      // this makes sure if points are right at the beginning of the domains are displayed correctly\n      // the problem here is bisectLeftx\n      d0 = d0 === min ? d0 : normalizeSliderValue(d0, min, step, marks);\n      d1 = normalizeSliderValue(d1, min, step, marks);\n\n      if (isRanged) this._move(d0, d1);\n      else this._move(...(right ? [d1, d1] : [d0, d0]));\n\n      if (isRanged) this._onBrush(d0, d1);\n      else this._onBrush(right ? d1 : d0);\n    };\n\n    _onBrush(val0 = 0, val1 = 0) {\n      const {\n        isRanged,\n        value: [currentVal0, currentVal1]\n      } = this.props;\n\n      if (currentVal0 === val0 && currentVal1 === val1) {\n        return;\n      }\n\n      if (isRanged) {\n        this.props.onBrush(val0, val1);\n      } else {\n        this.props.onBrush(val0, val0);\n      }\n    }\n\n    render() {\n      const {isRanged} = this.props;\n      return (\n        <StyledG className=\"kg-range-slider__brush\" $isRanged={isRanged} ref={this.rootContainer} />\n      );\n    }\n  }\n  return withTheme(RangeBrush);\n}\n\nexport default RangeBrushFactory;\n"
  },
  {
    "path": "src/components/src/common/range-plot.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useMemo, useRef, useState, CSSProperties} from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport RangeBrushFactory, {OnBrush, RangeBrushProps} from './range-brush';\nimport HistogramPlotFactory from './histogram-plot';\nimport LineChartFactory, {HoverDP} from './line-chart';\nimport {hasMobileWidth, isTest} from '@kepler.gl/utils';\nimport {PLOT_TYPES} from '@kepler.gl/constants';\nimport LoadingSpinner from './loading-spinner';\nimport {breakPointValues} from '@kepler.gl/styles';\nimport {LineChart as LineChartType, Filter, Bins} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\n\nconst StyledRangePlot = styled.div`\n  margin-bottom: ${props => props.theme.sliderBarHeight}px;\n  display: flex;\n  position: relative;\n`;\n\ninterface RangePlotProps {\n  onBrush: OnBrush;\n  range: number[];\n  value: number[];\n  width: number;\n  plotType: {\n    [key: string]: any;\n  };\n  lineChart?: LineChartType;\n  bins?: Bins;\n\n  isEnlarged?: boolean;\n  isRanged?: boolean;\n  theme: any;\n  timeFormat?: string;\n  timezone?: string | null;\n  playbackControlWidth?: number;\n\n  animationWindow?: string;\n  filter?: Filter;\n  datasets?: Datasets;\n\n  invertTrendColor?: boolean;\n\n  style: CSSProperties;\n}\n\ntype WithPlotLoadingProps = RangePlotProps &\n  Partial<RangeBrushProps> & {\n    setFilterPlot: any;\n  };\n\nRangePlotFactory.deps = [RangeBrushFactory, HistogramPlotFactory, LineChartFactory];\n\nconst isHistogramPlot = plotType => plotType?.type === PLOT_TYPES.histogram;\nconst isLineChart = plotType => plotType?.type === PLOT_TYPES.lineChart;\nconst hasHistogram = (plotType, bins) => isHistogramPlot(plotType) && bins;\nconst hasLineChart = (plotType, lineChart) => isLineChart(plotType) && lineChart;\n\nconst LOADING_SPINNER_CONTAINER_STYLE = {\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  width: '100%'\n};\n\nexport default function RangePlotFactory(\n  RangeBrush: ReturnType<typeof RangeBrushFactory>,\n  HistogramPlot: ReturnType<typeof HistogramPlotFactory>,\n  LineChartPlot: ReturnType<typeof LineChartFactory>\n) {\n  const RangePlot = ({\n    bins,\n    onBrush,\n    range,\n    value,\n    width,\n    plotType,\n    lineChart,\n    isEnlarged,\n    isRanged,\n    theme,\n    ...chartProps\n  }: RangePlotProps & Partial<RangeBrushProps>) => {\n    const groupColors = useMemo(() => {\n      const dataIds = bins ? Object.keys(bins) : [];\n      return plotType.colorsByDataId\n        ? dataIds.reduce((acc, dataId) => {\n            acc[dataId] = plotType.colorsByDataId[dataId];\n            return acc;\n          }, {})\n        : null;\n    }, [bins, plotType.colorsByDataId]);\n\n    const [brushing, setBrushing] = useState(false);\n    const [hoveredDP, onMouseMove] = useState<HoverDP | null>(null);\n    const [enableChartHover, setEnableChartHover] = useState(false);\n    const height = isEnlarged\n      ? hasMobileWidth(breakPointValues)\n        ? theme.rangePlotHLargePalm\n        : theme.rangePlotHLarge\n      : theme.rangePlotH;\n\n    const onBrushStart = useCallback(() => {\n      setBrushing(true);\n      onMouseMove(null);\n      setEnableChartHover(false);\n    }, [setBrushing, onMouseMove, setEnableChartHover]);\n\n    const onBrushEnd = useCallback(() => {\n      setBrushing(false);\n      setEnableChartHover(true);\n    }, [setBrushing, setEnableChartHover]);\n\n    const onMouseoverHandle = useCallback(() => {\n      onMouseMove(null);\n      setEnableChartHover(false);\n    }, [onMouseMove, setEnableChartHover]);\n\n    const onMouseoutHandle = useCallback(() => {\n      setEnableChartHover(true);\n    }, [setEnableChartHover]);\n\n    // JsDom have limited support for SVG, d3 will fail\n    const brushComponent = isTest() ? null : (\n      <RangeBrush\n        onBrush={onBrush}\n        onBrushStart={onBrushStart}\n        onBrushEnd={onBrushEnd}\n        range={range}\n        value={value}\n        width={width}\n        height={height}\n        isRanged={isRanged}\n        onMouseoverHandle={onMouseoverHandle}\n        onMouseoutHandle={onMouseoutHandle}\n        {...chartProps}\n      />\n    );\n\n    const commonProps = {\n      width,\n      value,\n      height,\n      margin: isEnlarged ? theme.rangePlotMarginLarge : theme.rangePlotMargin,\n      brushComponent,\n      brushing,\n      isEnlarged,\n      enableChartHover,\n      onMouseMove,\n      hoveredDP,\n      isRanged,\n      onBrush,\n      ...chartProps\n    };\n\n    return isLineChart(plotType) && lineChart ? (\n      <LineChartPlot lineChart={lineChart} {...commonProps} />\n    ) : (\n      <HistogramPlot\n        histogramsByGroup={bins}\n        colorsByGroup={groupColors}\n        range={range}\n        {...commonProps}\n      />\n    );\n  };\n\n  const RangePlotWithTheme = withTheme(RangePlot) as React.FC<\n    RangePlotProps & Partial<RangeBrushProps>\n  >;\n\n  // a container to render spinner or message when the data is too big\n  // to generate a plot\n  const WithPlotLoading = ({\n    lineChart,\n    plotType,\n    bins,\n    setFilterPlot,\n    isEnlarged,\n    theme,\n    ...otherProps\n  }: WithPlotLoadingProps) => {\n    const [isLoading, setIsLoading] = useState(false);\n    const isChangingRef = useRef(false);\n\n    useEffect(() => {\n      if (isChangingRef.current) {\n        if (hasHistogram(plotType, bins)) {\n          // Bins are loaded\n          isChangingRef.current = false;\n        }\n      } else {\n        if (!plotType || (isHistogramPlot(plotType) && !bins)) {\n          // load histogram\n          setIsLoading(true);\n          setFilterPlot({plotType: {type: PLOT_TYPES.histogram}});\n          isChangingRef.current = true;\n        }\n      }\n    }, [bins, plotType, setFilterPlot]);\n\n    useEffect(() => {\n      if (isChangingRef.current) {\n        if (hasLineChart(plotType, lineChart)) {\n          // Line chart is loaded\n          isChangingRef.current = false;\n        }\n      } else {\n        if (isLineChart(plotType) && !lineChart) {\n          // load line chart\n          setIsLoading(true);\n          setFilterPlot({plotType: {type: PLOT_TYPES.lineChart}});\n          isChangingRef.current = true;\n        }\n      }\n    }, [lineChart, plotType, setFilterPlot]);\n\n    const rangePlotStyle = useMemo(\n      () => ({\n        height: `${\n          isEnlarged\n            ? hasMobileWidth(breakPointValues)\n              ? theme.rangePlotContainerHLargePalm\n              : theme.rangePlotContainerHLarge\n            : theme.rangePlotContainerH\n        }px`\n      }),\n      [isEnlarged, theme]\n    );\n\n    return (\n      <StyledRangePlot style={rangePlotStyle} className=\"kg-range-slider__plot\">\n        {isLoading ? (\n          <div style={LOADING_SPINNER_CONTAINER_STYLE}>\n            <LoadingSpinner borderColor=\"transparent\" size={40} />\n          </div>\n        ) : (\n          <RangePlotWithTheme\n            lineChart={lineChart}\n            bins={bins}\n            plotType={plotType}\n            isEnlarged={isEnlarged}\n            theme={theme}\n            {...otherProps}\n          />\n        )}\n      </StyledRangePlot>\n    );\n  };\n\n  return withTheme(WithPlotLoading) as React.FC<Omit<WithPlotLoadingProps, 'theme'>>;\n}\n"
  },
  {
    "path": "src/components/src/common/range-slider-timeline-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport RangeSliderTimelineFactory from './range-slider-timeline';\n\nfunction RangeSliderSubAnimationPanelFactory(RangeSliderTimeline) {\n  const RangeSliderSubAnimationPanel = ({subAnimations, scaledValue, style}) => {\n    const containerStyle = useMemo(\n      () => ({\n        display: 'flex',\n        justifyContent: 'spaceBetween',\n        flexWrap: 'wrap',\n        ...style\n      }),\n      [style]\n    );\n\n    return (\n      <div style={containerStyle}>\n        {subAnimations.map((subAnimation, index) => (\n          <RangeSliderTimeline key={index} subAnimation={subAnimation} scaledValue={scaledValue} />\n        ))}\n      </div>\n    );\n  };\n\n  return RangeSliderSubAnimationPanel;\n}\n\nRangeSliderSubAnimationPanelFactory.deps = [RangeSliderTimelineFactory];\n\nexport default RangeSliderSubAnimationPanelFactory;\n"
  },
  {
    "path": "src/components/src/common/range-slider-timeline.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport {CSSProperties} from 'react';\nimport {ArrowDownFull, TimelineMarker} from '../common/icons';\nimport {Tooltip} from '../common/styled-components';\n\nconst BACKGROUND_LINE_STYLE: CSSProperties = {\n  height: '4px',\n  backgroundColor: '#1C2233',\n  position: 'relative',\n  width: '100%',\n  marginTop: '20px'\n};\n\nconst TIMELINE_MARKER_STYLE: CSSProperties = {\n  position: 'absolute',\n  top: '-8px',\n  fill: '#3D4866',\n  color: '#3D4866'\n};\n\nconst containerStyle: CSSProperties = {\n  display: 'flex',\n  width: '100%',\n  height: '24px'\n};\n\nconst iconWrapperStyle: CSSProperties = {\n  marginRight: '8px',\n  cursor: 'pointer'\n};\n\nconst TIMELINE_INDICATOR_STYLE: CSSProperties = {\n  position: 'absolute',\n  top: '-14px',\n  fill: '#C4C4C4',\n  color: '#C4C4C4'\n};\n\nfunction RangeSliderTimelineFactory() {\n  const RangeSliderTimeline = ({subAnimation, scaledValue, style}) => {\n    const {startTime, endTime, syncMode, Icon, label} = subAnimation;\n\n    const progressStyle: CSSProperties = {\n      left: `${startTime}%`,\n      top: '0',\n      width: `${endTime - startTime}%`,\n      height: '100%',\n      position: 'absolute',\n      backgroundColor: '#5558DB'\n    };\n\n    const progressBarContainer = useMemo(\n      () => ({\n        ...BACKGROUND_LINE_STYLE,\n        flex: 1,\n        ...style\n      }),\n      [style]\n    );\n\n    const value = scaledValue[syncMode];\n\n    const leftMarketStyle = useMemo(\n      () => ({\n        left: `calc(${startTime}% - 4px)`,\n        ...TIMELINE_MARKER_STYLE\n      }),\n      [startTime]\n    );\n\n    const rightMarketStyle = useMemo(\n      () => ({\n        left: `calc(${endTime}% - 4px)`,\n        ...TIMELINE_MARKER_STYLE\n      }),\n      [endTime]\n    );\n\n    const indicatorStyle = useMemo(\n      () => ({\n        ...TIMELINE_INDICATOR_STYLE,\n        left: `calc(${value}% - 3px)`\n      }),\n      [value]\n    );\n\n    return (\n      <div style={containerStyle}>\n        <div data-tip data-for={label} style={iconWrapperStyle}>\n          <Icon height=\"24px\" color=\"#F7F8FA\" />\n          <Tooltip id={label} place=\"right\" effect=\"solid\">\n            <span>{label}</span>\n          </Tooltip>\n        </div>\n        <div style={progressBarContainer}>\n          <div style={progressStyle} />\n          <ArrowDownFull style={leftMarketStyle} />\n          <ArrowDownFull style={rightMarketStyle} />\n          <TimelineMarker style={indicatorStyle} />\n        </div>\n      </div>\n    );\n  };\n\n  return RangeSliderTimeline;\n}\n\nexport default RangeSliderTimelineFactory;\n"
  },
  {
    "path": "src/components/src/common/range-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, ComponentType, createRef, ElementType} from 'react';\nimport {polyfill} from 'react-lifecycles-compat';\nimport {createSelector} from 'reselect';\nimport styled from 'styled-components';\nimport RangePlotFactory from './range-plot';\nimport Slider from './slider/slider';\nimport {Input} from './styled-components';\nimport RangeSliderSubAnimationPanelFactory from '../common/range-slider-timeline-panel';\nimport {\n  observeDimensions,\n  unobserveDimensions,\n  roundValToStep,\n  clamp,\n  scaleSourceDomainToDestination\n} from '@kepler.gl/utils';\nimport {LineChart, Filter, Bins} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\nimport {ActionHandler, setFilterPlot} from '@kepler.gl/actions';\n\ninterface SliderInputProps {\n  $flush?: boolean;\n  $inputSize?: string;\n}\nconst noop = () => {\n  return;\n};\nconst SliderInput = styled(Input)<SliderInputProps>`\n  width: ${props => props.theme.sliderInputWidth}px;\n  margin-left: ${props => (props.$flush ? 0 : props.$inputSize === 'tiny' ? 12 : 18)}px;\n  font-size: ${props => props.theme.sliderInputFontSize}; // 10px // 12px;\n  padding: ${props => props.theme.sliderInputPadding}; // 4px 6px; // 6px 12px;\n`;\n\ninterface SliderWrapperProps {\n  $isRanged?: boolean;\n  $showInput?: boolean;\n}\n\nconst SliderWrapper = styled.div<SliderWrapperProps>`\n  display: flex;\n  position: relative;\n  align-items: ${props => (!props.$isRanged && props.$showInput ? 'center' : 'flex-start')};\n`;\n\nconst RangeInputWrapper = styled.div`\n  margin-top: 12px;\n  display: flex;\n  justify-content: space-between;\n`;\n\ninterface RangeSliderProps {\n  range?: number[];\n  value0: number;\n  value1: number;\n  onChange?: (val: number[], e?: Event | null) => void; // TODO\n  setFilterPlot?: ActionHandler<typeof setFilterPlot>;\n  bins?: Bins;\n  isRanged?: boolean;\n  isEnlarged?: boolean;\n  showInput?: boolean;\n  inputTheme?: string;\n  inputSize?: string;\n  step?: number;\n  sliderHandleWidth?: number;\n  xAxis?: ElementType;\n  subAnimations?: any[];\n  timelineLabel?: string;\n\n  timezone?: string | null;\n  timeFormat?: string;\n  playbackControlWidth?: number;\n  lineChart?: LineChart;\n  marks?: number[];\n  plotType?: {\n    [key: string]: any;\n  };\n  plotValue?: number[];\n\n  animationWindow?: string;\n  filter?: Filter;\n  datasets?: Datasets;\n\n  invertTrendColor?: boolean;\n}\n\nconst RANGE_SLIDER_TIMELINE_PANEL_STYLE = {marginLeft: '-32px'};\n\nRangeSliderFactory.deps = [RangePlotFactory, RangeSliderSubAnimationPanelFactory];\n\nexport default function RangeSliderFactory(\n  RangePlot: ReturnType<typeof RangePlotFactory>,\n  RangeSliderSubAnimationPanel: ReturnType<typeof RangeSliderSubAnimationPanelFactory>\n): ComponentType<RangeSliderProps> {\n  class RangeSlider extends Component<RangeSliderProps> {\n    static defaultProps = {\n      isEnlarged: false,\n      isRanged: true,\n      showInput: true,\n      sliderHandleWidth: 12,\n      inputTheme: '',\n      inputSize: 'small',\n      onChange: noop\n    };\n\n    static getDerivedStateFromProps(props, state) {\n      let update: {value1?: any; prevValue1?: any; value0?: any; prevValue0?: any} | null = null;\n      const {value0, value1} = props;\n      if (props.value0 !== state.prevValue0 && !isNaN(value0)) {\n        update = {...(update || {}), value0, prevValue0: value0};\n      }\n      if (props.value1 !== state.prevValue1 && !isNaN(value1)) {\n        update = {...(update || {}), value1, prevValue1: value1};\n      }\n      return update;\n    }\n\n    state = {\n      value0: 0,\n      value1: 1,\n      prevValue0: 0,\n      prevValue1: 1,\n      width: 288\n    };\n\n    sliderContainer: HTMLDivElement | null = null;\n\n    componentDidMount() {\n      if (this.sliderContainer instanceof Element) {\n        observeDimensions(this.sliderContainer, this._resize, 100);\n      }\n    }\n\n    componentWillUnmount() {\n      if (this.sliderContainer instanceof Element) {\n        unobserveDimensions(this.sliderContainer);\n      }\n    }\n\n    setSliderContainer: React.LegacyRef<HTMLDivElement> = element => {\n      this.sliderContainer = element;\n      this._resize();\n    };\n    inputValue0 = createRef<HTMLInputElement>();\n    inputValue1 = createRef<HTMLInputElement>();\n    value0Selector = props => props.value0;\n    value1Selector = props => props.value1;\n    filterValueSelector = createSelector(\n      this.value0Selector,\n      this.value1Selector,\n      (value0, value1) => [value0, value1]\n    );\n\n    _roundValToStep = val => {\n      const {range, step} = this.props;\n      if (!range || !step) return;\n      return roundValToStep(range[0], step, val);\n    };\n\n    _setRangeVal1 = val => {\n      const {value0, range, onChange = noop} = this.props;\n      if (!range) return;\n      const val1 = Number(val);\n      onChange([value0, clamp([value0, range[1]], this._roundValToStep(val1))]);\n      return true;\n    };\n\n    _setRangeVal0 = val => {\n      const {value1, range, onChange = noop} = this.props;\n      if (!range) return;\n      const val0 = Number(val);\n      onChange([clamp([range[0], value1], this._roundValToStep(val0)), value1]);\n      return true;\n    };\n\n    _resize = () => {\n      if (this.sliderContainer) {\n        const width = this.sliderContainer.offsetWidth;\n        if (width !== this.state.width) {\n          this.setState({width});\n        }\n      }\n    };\n\n    _onChangeInput = (key, e) => {\n      this.setState({[key]: e.target.value});\n    };\n\n    _renderInput(key) {\n      const setRange = key === 'value0' ? this._setRangeVal0 : this._setRangeVal1;\n      const ref = key === 'value0' ? this.inputValue0 : this.inputValue1;\n      const update = e => {\n        if (!setRange(e.target.value)) {\n          this.setState({[key]: this.state[key]});\n        }\n      };\n\n      const onChange = this._onChangeInput.bind(this, key);\n\n      return (\n        <SliderInput\n          className=\"kg-range-slider__input\"\n          type=\"number\"\n          ref={ref}\n          id={`slider-input-${key}`}\n          key={key}\n          value={this.state[key]}\n          onChange={onChange}\n          onKeyPress={e => {\n            if (e.key === 'Enter') {\n              update(e);\n              (ref.current as any).blur();\n            }\n          }}\n          onBlur={update}\n          $flush={key === 'value0'}\n          $inputSize={this.props.inputSize}\n          secondary={this.props.inputTheme === 'secondary'}\n        />\n      );\n    }\n\n    // eslint-disable-next-line complexity\n    render() {\n      const {\n        isRanged,\n        showInput,\n        bins,\n        lineChart,\n        plotType,\n        invertTrendColor,\n        range,\n        onChange = noop,\n        sliderHandleWidth,\n        step,\n        timezone,\n        timeFormat,\n        playbackControlWidth,\n        setFilterPlot,\n        animationWindow,\n        subAnimations: subAnimations,\n        filter,\n        datasets\n      } = this.props;\n\n      const {width} = this.state;\n      const plotWidth = Math.max(width - Number(sliderHandleWidth), 0);\n      const hasPlot = plotType?.type;\n\n      const value = this.props.plotValue || this.filterValueSelector(this.props);\n      const scaledValue =\n        subAnimations?.length && range\n          ? scaleSourceDomainToDestination(value as [number, number], range as [number, number])\n          : [0, 0];\n      const commonPadding = `${Number(sliderHandleWidth) / 2}px`;\n      return (\n        <div\n          className=\"kg-range-slider\"\n          style={{width: '100%', padding: `0 ${commonPadding}`}}\n          ref={this.setSliderContainer}\n        >\n          {Array.isArray(range) && range.every(Number.isFinite) && (\n            <>\n              {hasPlot ? (\n                <RangePlot\n                  bins={bins}\n                  lineChart={lineChart}\n                  plotType={plotType}\n                  invertTrendColor={invertTrendColor}\n                  isEnlarged={this.props.isEnlarged}\n                  onBrush={(val0, val1) => onChange([val0, val1])}\n                  marks={this.props.marks}\n                  animationWindow={animationWindow}\n                  filter={filter}\n                  datasets={datasets}\n                  range={range}\n                  value={value}\n                  width={plotWidth}\n                  isRanged={isRanged}\n                  step={step}\n                  timezone={timezone}\n                  timeFormat={timeFormat}\n                  playbackControlWidth={playbackControlWidth}\n                  setFilterPlot={setFilterPlot}\n                  style={{paddingLeft: commonPadding}}\n                />\n              ) : null}\n              {subAnimations?.length ? (\n                <RangeSliderSubAnimationPanel\n                  subAnimations={subAnimations}\n                  scaledValue={scaledValue}\n                  style={RANGE_SLIDER_TIMELINE_PANEL_STYLE}\n                />\n              ) : null}\n              <SliderWrapper\n                className=\"kg-range-slider__slider\"\n                $isRanged={isRanged}\n                $showInput={showInput}\n              >\n                {this.props.xAxis ? (\n                  <div style={{height: '30px'}}>\n                    <this.props.xAxis\n                      width={plotWidth}\n                      timezone={timezone}\n                      domain={range}\n                      isEnlarged={this.props.isEnlarged}\n                    />\n                  </div>\n                ) : null}\n                <Slider\n                  marks={this.props.marks}\n                  isRanged={isRanged}\n                  minValue={range[0]}\n                  maxValue={range[1]}\n                  value0={this.props.value0}\n                  value1={this.props.value1}\n                  step={step}\n                  sliderHandleWidth={sliderHandleWidth}\n                  onSlider0Change={this._setRangeVal0}\n                  onSlider1Change={this._setRangeVal1}\n                  onSliderBarChange={(val0, val1) => {\n                    onChange([val0, val1]);\n                  }}\n                  enableBarDrag\n                />\n                {!isRanged && showInput ? this._renderInput('value1') : null}\n              </SliderWrapper>\n              {isRanged && showInput ? (\n                <RangeInputWrapper className=\"range-slider__input-group\">\n                  {this._renderInput('value0')}\n                  {this._renderInput('value1')}\n                </RangeInputWrapper>\n              ) : null}\n            </>\n          )}\n        </div>\n      );\n    }\n  }\n\n  polyfill(RangeSlider);\n\n  return RangeSlider;\n}\n"
  },
  {
    "path": "src/components/src/common/slider/mouse-event.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport document from 'global/document';\nimport {\n  RefObject,\n  TouchEvent,\n  TouchEventHandler,\n  MouseEventHandler as ReactMouseEventHandler,\n  MouseEvent\n} from 'react';\nimport {StyleRangeSliderType} from './slider';\n\nfunction nope() {\n  return;\n}\n\ntype MouseEventHandlerProps = {\n  vertical: boolean;\n  valueListener: (distance: number) => void;\n  toggleMouseOver: () => void;\n  track: RefObject<StyleRangeSliderType>;\n  setAnchor?: null | ((distance: number) => void);\n};\n\nexport default class MouseEventHandler {\n  private vertical: boolean;\n  private valueListener: (distance: number) => void;\n  private toggleMouseOver: () => void;\n  private track: RefObject<StyleRangeSliderType>; // Set correct type\n  private setAnchor: null | ((distance: number) => void); // Set correct type\n\n  constructor({\n    vertical = false,\n    valueListener = nope,\n    toggleMouseOver = nope,\n    track,\n    setAnchor = null\n  }: MouseEventHandlerProps) {\n    this.vertical = vertical;\n    this.valueListener = valueListener;\n    this.toggleMouseOver = toggleMouseOver;\n    this.track = track;\n    this.setAnchor = setAnchor;\n  }\n\n  handleMouseDown: ReactMouseEventHandler = (e: MouseEvent) => {\n    document.addEventListener('mouseup', this.mouseup);\n    document.addEventListener('mousemove', this.mousemove);\n    if (this.setAnchor) {\n      const pos = this.getMousePos(e);\n      this.setAnchor(this.getDistanceToTrack(pos));\n    }\n    this.toggleMouseOver();\n  };\n\n  private getMousePos(e: MouseEvent) {\n    return this.vertical ? e.clientY : e.clientX;\n  }\n\n  private getTouchPosition(e: TouchEvent) {\n    return this.vertical ? e.touches[0].clientY : e.touches[0].clientX;\n  }\n\n  private mouseup = () => {\n    document.removeEventListener('mouseup', this.mouseup);\n    document.removeEventListener('mousemove', this.mousemove);\n    this.toggleMouseOver();\n  };\n\n  private getDistanceToTrack(pos: number) {\n    if (!this.track.current) {\n      return 0;\n    }\n    const trackRect = this.track.current.getBoundingClientRect();\n    return pos - (this.vertical ? trackRect.bottom : trackRect.left);\n  }\n\n  private mousemove = (e: MouseEvent) => {\n    e.preventDefault();\n    const pos = this.getMousePos(e);\n    this.valueListener(this.getDistanceToTrack(pos));\n  };\n\n  handleTouchStart: TouchEventHandler = (e: TouchEvent) => {\n    // TODO: fix touch event\n    document.addEventListener('touchend', this.touchend);\n    document.addEventListener('touchmove', this.touchmove);\n    if (this.setAnchor) {\n      const pos = this.getTouchPosition(e);\n      this.setAnchor(this.getDistanceToTrack(pos));\n    }\n    this.toggleMouseOver();\n  };\n\n  private touchmove = (e: TouchEvent) => {\n    // TODO: touch not tested\n    const pos = this.getTouchPosition(e);\n    this.valueListener(this.getDistanceToTrack(pos));\n  };\n\n  private touchend = () => {\n    document.removeEventListener('touchend', this.touchend);\n    document.removeEventListener('touchmove', this.touchmove);\n    this.toggleMouseOver();\n  };\n}\n"
  },
  {
    "path": "src/components/src/common/slider/slider-bar-handle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, RefObject} from 'react';\nimport classnames from 'classnames';\nimport styled, {IStyledComponent} from 'styled-components';\nimport MouseEventHandler from './mouse-event';\nimport {StyleRangeSliderType} from './slider';\nimport {BaseComponentProps} from '../../types';\n\nexport type StyledSliderProps = BaseComponentProps & {\n  $active?: boolean;\n  $vertical?: boolean;\n};\n\nconst StyledSlider: IStyledComponent<'web', StyledSliderProps> = styled.div<StyledSliderProps>`\n  position: relative;\n  background-color: ${props =>\n    props.$active ? props.theme.sliderBarHoverColor : props.theme.sliderBarColor};\n  ${props => `${props.$vertical ? 'width' : 'height'}: ${props.theme.sliderBarHeight}px`};\n  border-radius: ${props => props.theme.sliderBarRadius};\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nfunction nope() {\n  return;\n}\n\ntype SliderBarHandleProps = {\n  width: number;\n  v0Left: number;\n  sliderBarListener: (distance: number) => void;\n  enableBarDrag: boolean;\n  vertical: boolean;\n  track: RefObject<StyleRangeSliderType>;\n  setAnchor: (distance: number) => void;\n};\n\nexport default class SliderBarHandle extends Component {\n  static defaultProps = {\n    sliderBarListener: nope,\n    enableBarDrag: false,\n    vertical: false\n  };\n\n  public mouseEvent: MouseEventHandler;\n\n  constructor(public props: SliderBarHandleProps) {\n    super(props);\n    this.mouseEvent = new MouseEventHandler({\n      vertical: props.vertical,\n      valueListener: props.sliderBarListener,\n      toggleMouseOver: this.toggleMouseOver,\n      track: props.track,\n      setAnchor: props.setAnchor\n    });\n  }\n\n  state = {mouseOver: false};\n\n  toggleMouseOver = () => {\n    this.setState({mouseOver: !this.state.mouseOver});\n  };\n\n  render() {\n    const {width, v0Left} = this.props;\n\n    const style = this.props.vertical\n      ? {\n          height: `${width}%`,\n          bottom: `${-100 + width + v0Left}%`\n        }\n      : {\n          width: `${width}%`,\n          left: `${v0Left}%`\n        };\n\n    return (\n      <StyledSlider\n        $active={this.state.mouseOver}\n        className={classnames('kg-range-slider__bar', {\n          'kg-range-slider__bar--active': this.state.mouseOver\n        })}\n        style={style}\n        onMouseDown={this.props.enableBarDrag ? this.mouseEvent.handleMouseDown : nope}\n        onTouchStart={this.props.enableBarDrag ? this.mouseEvent.handleTouchStart : nope}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/slider/slider-handle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, CSSProperties, RefObject} from 'react';\nimport classnames from 'classnames';\nimport styled, {IStyledComponent} from 'styled-components';\nimport MouseEventHandler from './mouse-event';\nimport {StyleRangeSliderType} from './slider';\nimport {BaseComponentProps} from '../../types';\n\nexport type StyledSliderHandleProps = StyledSliderTooltipProps & {\n  $vertical?: boolean;\n  $active?: boolean;\n  ref: RefObject<Element>;\n};\n\nconst StyledSliderHandle: IStyledComponent<'web', StyledSliderHandleProps> = styled.span.attrs(\n  props => ({\n    className: classnames('kg-range-slider__handle', props.className)\n  })\n)<StyledSliderHandleProps>`\n  position: absolute;\n  z-index: 10;\n  ${props => (props.$vertical ? 'margin-left' : 'margin-top')}: -${props =>\n    (props.$sliderHandleWidth - props.theme.sliderBarHeight) / 2}px;\n\n  height: ${props =>\n    Number.isFinite(props.$sliderHandleWidth)\n      ? props.$sliderHandleWidth\n      : props.theme.sliderHandleHeight}px;\n  width: ${props =>\n    Number.isFinite(props.$sliderHandleWidth)\n      ? props.$sliderHandleWidth\n      : props.theme.sliderHandleHeight}px;\n  box-shadow: ${props => props.theme.sliderHandleShadow};\n  background-color: ${props => props.theme.sliderHandleColor};\n  color: ${props => props.theme.sliderHandleTextColor};\n\n  border-width: 1px;\n  border-radius: ${props => props.theme.sliderBorderRadius};\n  border-style: solid;\n  border-color: ${props =>\n    props.$active ? props.theme.selectBorderColor : props.theme.sliderInactiveBorderColor};\n\n  &:hover {\n    background-color: ${props => props.theme.sliderHandleHoverColor};\n    cursor: pointer;\n  }\n\n  line-height: 10px;\n  font-size: 6px;\n  padding: 0 3px;\n  letter-spacing: 1px;\n  &:after {\n    content: '${props => props.theme.sliderHandleAfterContent}';\n  }\n`;\n\nexport type StyledSliderTooltipProps = BaseComponentProps & {\n  $sliderHandleWidth: number;\n};\n\nconst StyledSliderTooltip: IStyledComponent<\n  'web',\n  StyledSliderTooltipProps\n> = styled.div<StyledSliderTooltipProps>`\n  position: absolute;\n  border-radius: 3px;\n  display: inline-block;\n  pointer-events: none;\n  transition: opacity 0.3s ease-out;\n  z-index: 999;\n  margin-left: ${props => props.$sliderHandleWidth + 12}px;\n  font-size: 9.5px;\n  font-weight: 500;\n  padding: 7px 10px;\n  background-color: ${props => props.theme.tooltipBg};\n  color: ${props => props.theme.tooltipColor};\n  margin-bottom: -6px;\n  width: 50px;\n\n  &:before,\n  &:after {\n    content: '';\n    width: 0;\n    height: 0;\n    position: absolute;\n  }\n\n  &:before {\n    border-top: 6px solid transparent;\n    border-bottom: 6px solid transparent;\n    left: -8px;\n    top: 50%;\n  }\n\n  &:after {\n    border-top: 5px solid transparent;\n    border-bottom: 5px solid transparent;\n    left: -6px;\n    top: 50%;\n    margin-top: -4px;\n    border-right-color: ${props => props.theme.tooltipBg};\n    border-right-style: solid;\n    border-right-width: 6px;\n  }\n`;\n\ntype SliderTooltipProps = {\n  value?: number | null;\n  format?: (value: number | null | undefined) => number | null | undefined;\n  style: CSSProperties;\n  sliderHandleWidth: number;\n};\n\nconst SliderTooltip = ({\n  value,\n  format = val => val,\n  style,\n  sliderHandleWidth\n}: SliderTooltipProps) => {\n  return (\n    <StyledSliderTooltip $sliderHandleWidth={sliderHandleWidth} style={style}>\n      {format(value)}\n    </StyledSliderTooltip>\n  );\n};\n\ntype SliderHandleProps = {\n  sliderHandleWidth: number;\n  left: string;\n  display: boolean;\n  valueListener: (distance: number) => void;\n  vertical: boolean;\n  track: RefObject<StyleRangeSliderType>;\n  showTooltip: boolean;\n  value?: number;\n};\n\nexport default class SliderHandle extends Component {\n  static defaultProps = {\n    sliderHandleWidth: 12,\n    left: '50%',\n    display: true,\n    vertical: false,\n    valueListener: function valueListenerFn() {\n      return;\n    },\n    showTooltip: false\n  };\n\n  public mouseEvent: MouseEventHandler;\n\n  constructor(public props: SliderHandleProps) {\n    super(props);\n\n    this.mouseEvent = new MouseEventHandler({\n      vertical: props.vertical,\n      valueListener: props.valueListener,\n      toggleMouseOver: this.toggleMouseOver,\n      track: props.track\n    });\n  }\n\n  state = {mouseOver: false};\n  ref = createRef<HTMLSpanElement>();\n\n  toggleMouseOver = () => {\n    this.setState({mouseOver: !this.state.mouseOver});\n  };\n\n  render() {\n    const style = {[this.props.vertical ? 'bottom' : 'left']: this.props.left};\n\n    return (\n      <div style={{display: this.props.display ? 'block' : 'none'}}>\n        {this.props.showTooltip && this.state.mouseOver ? (\n          <SliderTooltip\n            style={style}\n            sliderHandleWidth={this.props.sliderHandleWidth}\n            value={Number.isFinite(this.props.value) ? this.props.value : null}\n          />\n        ) : null}\n        <StyledSliderHandle\n          className={classnames({\n            'kg-range-slider__handle--active': this.state.mouseOver\n          })}\n          ref={this.ref}\n          $sliderHandleWidth={this.props.sliderHandleWidth}\n          $active={this.state.mouseOver}\n          $vertical={this.props.vertical}\n          style={style}\n          onMouseDown={this.mouseEvent.handleMouseDown}\n          onTouchStart={this.mouseEvent.handleTouchStart}\n        />\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/slider/slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, RefObject} from 'react';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\n\nimport SliderHandle from './slider-handle';\nimport SliderBarHandle from './slider-bar-handle';\nimport {normalizeSliderValue, clamp} from '@kepler.gl/utils';\n\nfunction noop() {\n  return;\n}\n\ninterface StyledRangeSliderProps {\n  $vertical?: boolean;\n}\n\nconst StyledRangeSlider = styled.div<StyledRangeSliderProps>`\n  position: relative;\n  background-color: ${props => props.theme.sliderBarBgd};\n  ${props => `${props.$vertical ? 'width' : 'height'}: ${props.theme.sliderBarHeight}px`};\n  ${props => `${props.$vertical ? 'height' : 'width'}: 100%`};\n`;\n\nexport type StyleRangeSliderType = typeof StyledRangeSlider & HTMLDivElement;\n\ninterface SliderWrapperProps {\n  $isRanged?: boolean;\n  $vertical?: boolean;\n}\n\nconst SliderWrapper = styled.div<SliderWrapperProps>`\n  flex-grow: 1;\n`;\n\ntype SliderProps = {\n  title: string;\n  isRanged: boolean;\n  value0: number;\n  value1: number;\n  minValue: number;\n  maxValue: number;\n  sliderHandleWidth: number;\n  onSlider0Change: (val: number) => any;\n  onSlider1Change: (val: number) => any;\n  onSliderBarChange: (val0: number, val1: number) => void;\n  step: number;\n  enableBarDrag: boolean;\n  showTooltip: boolean;\n  vertical: boolean;\n  marks?: number[] | null;\n  classSet?: {[key: string]: boolean};\n  disabled: boolean;\n  className?: string;\n  style?: object;\n};\n\nexport default class Slider extends Component<SliderProps> {\n  static defaultProps = {\n    title: '',\n    isRanged: true,\n    value0: 0,\n    value1: 100,\n    minValue: 0,\n    maxValue: 100,\n    step: 1,\n    sliderHandleWidth: 12,\n    enableBarDrag: false,\n    onSlider0Change: noop,\n    onSlider1Change: noop,\n    onSliderBarChange: noop,\n    disabled: false,\n    vertical: false,\n    showTooltip: false\n  };\n\n  private anchor = 0;\n\n  public ref: RefObject<typeof SliderWrapper & HTMLDivElement> = createRef<\n    typeof SliderWrapper & HTMLDivElement\n  >();\n  public track: RefObject<StyleRangeSliderType> = createRef<StyleRangeSliderType>();\n\n  constructor(public props: SliderProps) {\n    super(props);\n  }\n\n  private setAnchor = (x: number) => {\n    // used to calculate delta\n    this.anchor = x;\n  };\n\n  private getBaseDistance() {\n    if (!this.ref.current) {\n      return 0;\n    }\n    return this.props.vertical ? this.ref.current.offsetHeight : this.ref.current.offsetWidth;\n  }\n\n  private getDeltaVal(x: number) {\n    const percent = x / this.getBaseDistance();\n    const maxDelta = this.props.maxValue - this.props.minValue;\n    return percent * maxDelta;\n  }\n  private getDeltaX(v: number) {\n    const percent = v / (this.props.maxValue - this.props.minValue);\n    const maxDelta = this.getBaseDistance();\n    return percent * maxDelta;\n  }\n\n  private getValue(baseV: number, offset: number) {\n    // offset is the distance between slider handle and track left\n    const rawValue = baseV + this.getDeltaVal(offset);\n\n    return this.normalizeValue(rawValue);\n  }\n\n  private normalizeValue(val: number) {\n    const {minValue, step, marks} = this.props;\n    return normalizeSliderValue(val, minValue, step, marks);\n  }\n\n  slide0Listener = (x: number) => {\n    const {value1, minValue} = this.props;\n    const val = this.getValue(minValue, x);\n    this.props.onSlider0Change(clamp([minValue, value1], val));\n  };\n\n  slide1Listener = (x: number) => {\n    const {minValue, maxValue, value0} = this.props;\n    const val = this.getValue(minValue, x);\n    this.props.onSlider1Change(clamp([value0, maxValue], val));\n  };\n\n  sliderBarListener = (x: number) => {\n    const {value0, value1, minValue, maxValue} = this.props;\n    // for slider bar, we use distance delta\n    const anchor = this.anchor;\n    const length = value1 - value0; // the length of the selected range shouldn't change when clamping\n    const val0 = clamp([minValue, maxValue - length], this.getValue(value0, x - anchor));\n    const val1 = clamp([val0 + length, maxValue], this.getValue(value1, x - anchor));\n\n    const deltaX = this.getDeltaX(val0 - this.props.value0);\n    this.props.onSliderBarChange(val0, val1);\n    // update anchor\n    this.anchor = this.anchor + deltaX;\n  };\n\n  calcHandleLeft0 = (w: number, l: number) => {\n    return w === 0\n      ? `calc(${l}% - ${this.props.sliderHandleWidth / 2}px)`\n      : `calc(${l}% - ${this.props.sliderHandleWidth / 2}px)`;\n  };\n\n  calcHandleLeft1 = (w: number, l: number) => {\n    return this.props.isRanged && w === 0\n      ? `${l}%`\n      : `calc(${l + w}% - ${this.props.sliderHandleWidth / 2}px)`;\n  };\n\n  render() {\n    const {\n      className,\n      classSet,\n      disabled,\n      isRanged,\n      maxValue,\n      minValue,\n      value1,\n      vertical,\n      sliderHandleWidth,\n      showTooltip,\n      style\n    } = this.props;\n    const value0 = !isRanged && minValue > 0 ? minValue : this.props.value0;\n    const currValDelta = value1 - value0;\n    const maxDelta = maxValue - minValue;\n    const width = (currValDelta / maxDelta) * 100;\n\n    const v0Left = ((value0 - minValue) / maxDelta) * 100;\n\n    return (\n      <SliderWrapper\n        className={classnames('kg-slider', {...classSet, disabled}, className)}\n        ref={this.ref}\n        $isRanged={isRanged}\n        $vertical={vertical}\n        style={style}\n      >\n        <StyledRangeSlider className=\"kg-range-slider\" $vertical={vertical} ref={this.track}>\n          <SliderHandle\n            left={this.calcHandleLeft0(width, v0Left)}\n            valueListener={this.slide0Listener}\n            sliderHandleWidth={sliderHandleWidth}\n            display={isRanged}\n            vertical={vertical}\n            showTooltip={showTooltip}\n            track={this.track}\n          />\n          <SliderHandle\n            left={this.calcHandleLeft1(width, v0Left)}\n            valueListener={this.slide1Listener}\n            sliderHandleWidth={sliderHandleWidth}\n            vertical={vertical}\n            value={value1}\n            showTooltip={showTooltip}\n            track={this.track}\n          />\n          <SliderBarHandle\n            width={width}\n            v0Left={v0Left}\n            enableBarDrag={this.props.enableBarDrag}\n            sliderBarListener={this.sliderBarListener}\n            vertical={vertical}\n            track={this.track}\n            setAnchor={this.setAnchor}\n          />\n        </StyledRangeSlider>\n      </SliderWrapper>\n    );\n  }\n}\n"
  },
  {
    "path": "src/components/src/common/styled-components.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {media} from '@kepler.gl/styles';\nimport {RGBColor} from '@kepler.gl/types';\nimport classnames from 'classnames';\nimport DatePicker from 'react-date-picker';\nimport TimePicker from 'react-time-picker';\nimport ReactTooltip, {TooltipProps} from 'react-tooltip';\nimport styled, {IStyledComponent} from 'styled-components';\nimport isPropValid from '@emotion/is-prop-valid';\n\nimport {BaseComponentProps} from '../types';\n\n// This implements the default behavior from styled-components v5\nexport function shouldForwardProp(propName, target) {\n  if (typeof target === 'string') {\n    // For HTML elements, forward the prop if it is a valid HTML attribute\n    return isPropValid(propName);\n  }\n  // For other elements, forward all props\n  return true;\n}\n\nexport const SelectText = styled.span`\n  color: ${props => props.theme.labelColor};\n  font-size: ${props => props.theme.selectFontSize};\n  font-weight: 400;\n\n  i {\n    font-size: 13px;\n    margin-right: 6px;\n  }\n`;\n\nexport const SelectTextBold = styled(SelectText)`\n  color: ${props => props.theme.textColor};\n  font-weight: 500;\n`;\n\nexport const IconRoundSmall = styled.div`\n  display: flex;\n  width: 18px;\n  height: 18px;\n  border-radius: 9px;\n  background-color: ${props => props.theme.secondaryBtnBgdHover};\n  color: ${props => props.theme.secondaryBtnColor};\n  align-items: center;\n  justify-content: center;\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.secondaryBtnBgdHover};\n  }\n`;\n\nexport const CenterFlexbox = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nexport const CenterVerticalFlexbox = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n`;\n\nexport const EndHorizontalFlexbox = styled.div`\n  display: flex;\n  flex-direction: row;\n  align-items: end;\n`;\n\nexport const SpaceBetweenFlexbox = styled.div`\n  display: flex;\n  justify-content: space-between;\n  margin-left: -16px;\n`;\n\nexport const SBFlexboxItem = styled.div`\n  flex-grow: 1;\n  margin-left: 16px;\n`;\n\nexport const SBFlexboxNoMargin = styled.div`\n  display: flex;\n  justify-content: space-between;\n`;\n\nexport const PanelLabel = styled.label.attrs({\n  className: 'side-panel-panel__label'\n})`\n  color: ${props => props.theme.labelColor};\n  display: inline-block;\n  font-size: 11px;\n  font-weight: 400;\n  margin-bottom: 4px;\n  text-transform: capitalize;\n`;\n\nexport const PanelLabelWrapper = styled.div.attrs({\n  className: 'side-panel-panel__label-wrapper'\n})`\n  display: flex;\n  align-items: self-start;\n`;\n\nexport const PanelLabelBold = styled(PanelLabel)`\n  font-weight: 500;\n`;\n\nexport const PanelHeaderTitle = styled.span.attrs(props => ({\n  className: classnames('side-panel-panel__header__title', props.className)\n}))`\n  color: ${props => props.theme.textColor};\n  font-size: 13px;\n  letter-spacing: 0.43px;\n  text-transform: capitalize;\n`;\n\nexport const PanelHeaderContent = styled.div`\n  display: flex;\n  align-items: center;\n  color: ${props => props.theme.textColor};\n  padding-left: 12px;\n\n  .icon {\n    color: ${props => props.theme.labelColor};\n    display: flex;\n    align-items: center;\n    margin-right: 12px;\n  }\n`;\n\nexport const PanelContent = styled.div.attrs(props => ({\n  className: classnames('side-panel-panel__content', props.className)\n}))`\n  background-color: ${props => props.theme.panelContentBackground};\n  padding: 12px;\n`;\n\ninterface SidePanelSectionProps {\n  disabled?: boolean;\n}\n\nexport const SidePanelSection = styled.div.attrs(props => ({\n  className: classnames('side-panel-section', props.className)\n}))<SidePanelSectionProps>`\n  margin-bottom: 12px;\n\n  opacity: ${props => (props.disabled ? 0.4 : 1)};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n`;\n\nexport const SidePanelDivider = styled.div.attrs({\n  className: 'side-panel-divider'\n})`\n  border-bottom: ${props => props.theme.sidepanelDividerBorder} solid\n    ${props => props.theme.panelBorderColor};\n  margin-bottom: ${props => props.theme.sidepanelDividerMargin}px;\n  height: ${props => props.theme.sidepanelDividerHeight}px;\n`;\n\ntype TooltipAttrsProps = {interactive?: boolean} & TooltipProps & BaseComponentProps;\n\nexport const Tooltip: IStyledComponent<'web', TooltipAttrsProps> = styled(\n  ReactTooltip\n)<TooltipProps>`\n  &.__react_component_tooltip {\n    font-size: ${props => props.theme.tooltipFontSize};\n    font-weight: 400;\n    padding: 10px 18px;\n    box-shadow: ${props => props.theme.tooltipBoxShadow};\n\n    &.type-dark {\n      background-color: ${props => props.theme.tooltipBg};\n      color: ${props => props.theme.tooltipColor};\n      &.place-bottom {\n        &:after {\n          border-bottom-color: ${props => props.theme.tooltipBg};\n        }\n      }\n\n      &.place-top {\n        &:after {\n          border-top-color: ${props => props.theme.tooltipBg};\n        }\n      }\n\n      &.place-right {\n        &:after {\n          border-right-color: ${props => props.theme.tooltipBg};\n        }\n      }\n\n      &.place-left {\n        &:after {\n          border-left-color: ${props => props.theme.tooltipBg};\n        }\n      }\n    }\n  }\n`;\n\nexport type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n  className?: string;\n  ref?: React.ForwardedRef<HTMLElement>;\n  children?: React.ReactNode;\n  negative?: boolean;\n  secondary?: boolean;\n  link?: boolean;\n  floating?: boolean;\n  cta?: boolean;\n  large?: boolean;\n  small?: boolean;\n  disabled?: boolean;\n  width?: string;\n  inactive?: boolean;\n  size?: string;\n};\n\n// this needs to be an actual button to be able to set disabled attribute correctly\nexport const Button = styled.button.withConfig({shouldForwardProp}).attrs(props => ({\n  className: classnames('button', props.className)\n}))<ButtonProps>`\n  align-items: center;\n  background-color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnBgd\n      : props.secondary\n      ? props.theme.secondaryBtnBgd\n      : props.link\n      ? props.theme.linkBtnBgd\n      : props.floating\n      ? props.theme.floatingBtnBgd\n      : props.cta\n      ? props.theme.ctaBtnBgd\n      : props.theme.primaryBtnBgd};\n  border-radius: ${props => props.theme.primaryBtnRadius};\n  color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnColor\n      : props.secondary\n      ? props.theme.secondaryBtnColor\n      : props.link\n      ? props.theme.linkBtnColor\n      : props.floating\n      ? props.theme.floatingBtnColor\n      : props.cta\n      ? props.theme.ctaBtnColor\n      : props.theme.primaryBtnColor};\n  cursor: pointer;\n  display: inline-flex;\n  font-size: ${props =>\n    props.large\n      ? props.theme.primaryBtnFontSizeLarge\n      : props.small\n      ? props.theme.primaryBtnFontSizeSmall\n      : props.theme.primaryBtnFontSizeDefault};\n  font-weight: 500;\n  font-family: ${props => props.theme.btnFontFamily};\n  justify-content: center;\n  letter-spacing: 0.3px;\n  line-height: 14px;\n  outline: 0;\n  padding: ${props => (props.large ? '14px 32px' : props.small ? '6px 9px' : '9px 12px')};\n  text-align: center;\n  transition: ${props => props.theme.transition};\n  vertical-align: middle;\n  width: ${props => props.width || 'auto'};\n  opacity: ${props => (props.disabled ? 0.4 : 1)};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n  border: ${props =>\n    props.negative\n      ? props.theme.negativeBtnBorder\n      : props.secondary\n      ? props.theme.secondaryBtnBorder\n      : props.floating\n      ? props.theme.floatingBtnBorder\n      : props.link\n      ? props.theme.linkBtnBorder\n      : props.theme.primaryBtnBorder};\n  &:hover,\n  &:focus,\n  &:active,\n  &.active {\n    background-color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnBgdHover\n        : props.secondary\n        ? props.theme.secondaryBtnBgdHover\n        : props.link\n        ? props.theme.linkBtnActBgdHover\n        : props.floating\n        ? props.theme.floatingBtnBgdHover\n        : props.cta\n        ? props.theme.ctaBtnBgdHover\n        : props.theme.primaryBtnBgdHover};\n    color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnActColor\n        : props.secondary\n        ? props.theme.secondaryBtnActColor\n        : props.link\n        ? props.theme.linkBtnActColor\n        : props.floating\n        ? props.theme.floatingBtnActColor\n        : props.cta\n        ? props.theme.ctaBtnActColor\n        : props.theme.primaryBtnActColor};\n  }\n\n  svg {\n    margin-right: ${props => (props.large ? '10px' : props.small ? '6px' : '8px')};\n  }\n`;\n\ninterface InputProps {\n  secondary?: boolean;\n}\n\nexport const Input = styled.input.withConfig({shouldForwardProp})<InputProps>`\n  ${props => (props.secondary ? props.theme.secondaryInput : props.theme.input)};\n`;\n\nexport const InputLight = styled.input.withConfig({shouldForwardProp})`\n  ${props => props.theme.inputLT};\n`;\n\nexport const TextArea = styled.textarea.withConfig({shouldForwardProp})<InputProps>`\n  ${props => (props.secondary ? props.theme.secondaryInput : props.theme.input)};\n`;\nexport const TextAreaLight = styled.textarea.withConfig({shouldForwardProp})`\n  ${props => props.theme.inputLT} height: auto;\n  white-space: pre-wrap;\n`;\n\nexport const InlineInput = styled(Input)`\n  ${props => props.theme.inlineInput};\n`;\n\nexport interface StyledPanelHeaderProps {\n  active?: boolean;\n  labelRCGColorValues?: RGBColor | null;\n  warning?: boolean;\n  isValid?: boolean;\n}\n\nexport const StyledPanelHeader = styled.div.withConfig({shouldForwardProp})<StyledPanelHeaderProps>`\n  background-color: ${props =>\n    props.active ? props.theme.panelBackgroundHover : props.theme.panelBackground};\n  border-left: 3px solid\n    rgb(\n      ${props => (props.labelRCGColorValues ? props.labelRCGColorValues.join(',') : 'transparent')}\n    );\n  padding: 0 10px 0 0;\n  height: ${props => props.theme.panelHeaderHeight}px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  border-radius: ${props => props.theme.panelHeaderBorderRadius};\n  transition: ${props => props.theme.transition};\n`;\n\ninterface StyledPanelDropdownProps {\n  type?: string;\n}\n\nexport const StyledPanelDropdown = styled.div<StyledPanelDropdownProps>`\n  ${props => props.theme.panelDropdownScrollBar}\n  background-color: ${props =>\n    props.type === 'light' ? props.theme.modalDropdownBackground : props.theme.panelBackground};\n  overflow-y: auto;\n  box-shadow: ${props => props.theme.panelBoxShadow};\n  border-radius: ${props => props.theme.panelBorderRadius};\n  max-height: 500px;\n  position: relative;\n  z-index: 999;\n`;\n\nexport const ButtonGroup = styled.div`\n  display: flex;\n  .button {\n    border-radius: 0;\n    margin-left: 2px;\n  }\n  .button:first-child {\n    border-bottom-left-radius: ${props => props.theme.primaryBtnRadius};\n    border-top-left-radius: ${props => props.theme.primaryBtnRadius};\n    margin-left: 0;\n  }\n  .button:last-child {\n    border-bottom-right-radius: ${props => props.theme.primaryBtnRadius};\n    border-top-right-radius: ${props => props.theme.primaryBtnRadius};\n  }\n`;\n\ninterface DatasetSquareProps {\n  backgroundColor: RGBColor;\n}\n\nexport const DatasetSquare = styled.div.withConfig({shouldForwardProp})<DatasetSquareProps>`\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  background-color: rgb(${props => props.backgroundColor.join(',')});\n  margin-right: 12px;\n`;\n\ninterface SelectionButtonProps {\n  selected?: boolean;\n}\n\nexport const SelectionButton = styled.div.withConfig({shouldForwardProp})<SelectionButtonProps>`\n  position: relative;\n  border-radius: 2px;\n  border: 1px solid\n    ${props =>\n      props.selected\n        ? props.theme.selectionBtnBorderActColor\n        : props.theme.selectionBtnBorderColor};\n  color: ${props =>\n    props.selected ? props.theme.selectionBtnActColor : props.theme.selectionBtnColor};\n  background-color: ${props =>\n    props.selected ? props.theme.selectionBtnActBgd : props.theme.selectionBtnBgd};\n\n  cursor: pointer;\n  font-weight: 500;\n  margin-right: 6px;\n  padding: 6px 16px;\n\n  &:hover {\n    color: ${props => props.theme.selectionBtnActColor};\n    border: 1px solid ${props => props.theme.selectionBtnBorderActColor};\n  }\n`;\n\nexport const StyledTable = styled.table`\n  width: 100%;\n  border-spacing: 0;\n\n  thead {\n    tr th {\n      background: ${props => props.theme.panelBackgroundLT};\n      color: ${props => props.theme.titleColorLT};\n      padding: 18px 12px;\n      text-align: start;\n    }\n  }\n\n  tbody {\n    tr td {\n      border-bottom: ${props => props.theme.panelBorderLT};\n      padding: 12px;\n    }\n  }\n`;\n\nexport const StyledModalContent = styled.div`\n  background: ${props => props.theme.panelBackgroundLT};\n  color: ${props => props.theme.textColorLT};\n  display: flex;\n  flex-direction: row;\n  font-size: 10px;\n  padding: 24px ${props => props.theme.modalLateralPadding};\n  margin: 0 -${props => props.theme.modalLateralPadding};\n  justify-content: space-between;\n  ${media.portable`\n    flex-direction: column;\n    padding: 16px ${props => props.theme.modalPortableLateralPadding};\n    margin: 0 -${props => props.theme.modalPortableLateralPadding};\n  `};\n`;\n\nexport const StyledModalVerticalPanel = styled.div.attrs({\n  className: 'modal-vertical-panel'\n})`\n  display: flex;\n  flex-direction: column;\n  justify-content: space-around;\n  font-size: 12px;\n\n  .modal-section:first-child {\n    margin-top: 24px;\n    ${media.palm`\n      margin-top: 0;\n    `};\n  }\n\n  input {\n    margin-right: 8px;\n  }\n`;\n\nexport const StyledModalSection = styled.div.attrs(({className}) => ({\n  className: classnames('modal-section', className)\n}))`\n  margin-bottom: 32px;\n\n  .modal-section-title {\n    font-weight: 500;\n  }\n  .modal-section-subtitle {\n    color: ${props => props.theme.subtextColorLT};\n  }\n\n  input {\n    margin-top: 8px;\n  }\n\n  ${media.portable`\n    margin-bottom: 24px;\n  `};\n  ${media.palm`\n    margin-bottom: 16px;\n  `};\n`;\n\ninterface StyledModalInputFootnoteProps {\n  error?: boolean;\n}\n\nexport const StyledModalInputFootnote = styled.div.attrs({\n  className: 'modal-input__footnote'\n})<StyledModalInputFootnoteProps>`\n  display: flex;\n  justify-content: flex-end;\n  color: ${props => (props.error ? props.theme.errorColor : props.theme.subtextColorLT)};\n  font-size: 10px;\n`;\n/**\n * Newer versions of mapbox.gl display an error message banner on top of the map by default\n * which will cause the map to display points in the wrong locations\n * This workaround will hide the error banner.\n */\nexport const StyledMapContainer = styled.div`\n  width: 100%;\n  height: 100%;\n  .maplibregl-map {\n    .maplibregl-missing-css {\n      display: none;\n    }\n    .maplibregl-ctrl-attrib {\n      display: none;\n    }\n  }\n  .mapboxgl-map {\n    .mapboxgl-missing-css {\n      display: none;\n    }\n    .mapboxgl-ctrl-attrib {\n      display: none;\n    }\n  }\n`;\n\nexport type StyledAttributionProps = {\n  mapLibCssClass: string;\n  mapLibAttributionCssClass: string;\n};\n\nexport const StyledAttribution = styled.div\n  .withConfig({\n    shouldForwardProp: prop => !['mapLibCssClass', 'mapLibAttributionCssClass'].includes(prop)\n  })\n  .attrs<StyledAttributionProps>(props => ({\n    className: props.mapLibAttributionCssClass\n  }))<StyledAttributionProps>`\n  bottom: 0;\n  right: 0;\n  position: absolute;\n  display: block;\n  margin: 0 10px 6px;\n  z-index: 1;\n  .attrition-link {\n    display: flex;\n    align-items: center;\n    margin-left: 10px;\n\n    a,\n    .pipe-separator {\n      margin-right: 2px;\n      color: ${props => props.theme.labelColor};\n    }\n\n    .pipe-separator {\n      text-decoration: none;\n    }\n  }\n\n  .attrition-logo {\n    display: flex;\n    font-size: 10px;\n    justify-content: flex-end;\n    align-items: center;\n    color: ${props => props.theme.labelColor};\n\n    a.${props => props.mapLibCssClass}-ctrl-logo {\n      width: 72px;\n      margin-left: 4px;\n      background-size: contain;\n    }\n  }\n  a,\n  .pipe-separator {\n    font-size: 10px;\n  }\n\n  ${media.palm`\n    .attrition-logo a {\n      width: 60px;\n    }\n\n    .attrition-link {\n      line-height: 1em;\n    }\n  `};\n`;\n\nexport interface StyledExportSectionProps {\n  disabled?: boolean;\n}\n\nexport const StyledExportSection = styled.div<StyledExportSectionProps>`\n  display: flex;\n  flex-direction: row;\n  margin: 35px 0;\n  width: 100%;\n  color: ${props => props.theme.textColorLT};\n  font-size: 12px;\n  opacity: ${props => (props.disabled ? 0.3 : 1)};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n\n  .description {\n    width: 185px;\n    .title {\n      font-weight: 500;\n      font-family: ${props => props.theme.fontFamilyMedium ?? props.theme.fontFamily};\n    }\n    .subtitle {\n      color: ${props => props.theme.subtextColorLT};\n      font-size: 11px;\n    }\n  }\n  .warning {\n    color: ${props => props.theme.errorColor};\n    font-weight: 500;\n  }\n  .description.full {\n    width: 100%;\n  }\n  .selection {\n    display: flex;\n    flex-wrap: wrap;\n    flex: 1;\n    padding-left: 50px;\n\n    select {\n      background-color: white;\n      border-radius: 1px;\n      display: inline-block;\n      font: inherit;\n      line-height: 1.5em;\n      padding: 0.5em 3.5em 0.5em 1em;\n      margin: 0;\n      box-sizing: border-box;\n      appearance: none;\n      width: 250px;\n      height: 36px;\n\n      background-image: linear-gradient(45deg, transparent 50%, gray 50%),\n        linear-gradient(135deg, gray 50%, transparent 50%), linear-gradient(to right, #ccc, #ccc);\n      background-position: calc(100% - 20px) calc(1em + 2px), calc(100% - 15px) calc(1em + 2px),\n        calc(100% - 2.5em) 4.5em;\n      background-size: 5px 5px, 5px 5px, 1px 1.5em;\n      background-repeat: no-repeat;\n    }\n\n    select:focus {\n      background-image: linear-gradient(45deg, green 50%, transparent 50%),\n        linear-gradient(135deg, transparent 50%, green 50%), linear-gradient(to right, #ccc, #ccc);\n      background-position: calc(100% - 15px) 1em, calc(100% - 20px) 1em, calc(100% - 2.5em) 4.5em;\n      background-size: 5px 5px, 5px 5px, 1px 1.5em;\n      background-repeat: no-repeat;\n      border-color: green;\n      outline: 0;\n    }\n  }\n`;\n\nexport const StyledFilteredOption = styled(SelectionButton)`\n  align-items: center;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  margin: 4px;\n  padding: 8px 12px;\n  width: 140px;\n\n  .filter-option-title {\n    color: ${props => props.theme.textColorLT};\n    font-size: 12px;\n    font-weight: 500;\n  }\n  .filter-option-subtitle {\n    color: ${props => props.theme.subtextColorLT};\n    font-size: 11px;\n  }\n`;\n\nexport const StyledType = styled(SelectionButton)`\n  height: 100px;\n  margin: 4px;\n  padding: 6px 10px;\n  width: 100px;\n`;\n\nexport const WidgetContainer = styled.div`\n  z-index: 1;\n`;\n\nexport const BottomWidgetInner = styled.div`\n  background-color: ${props => props.theme.bottomWidgetBgd};\n  padding: ${props => `${props.theme.bottomInnerPdVert}px ${props.theme.bottomInnerPdSide}px`};\n  position: relative;\n  margin-top: ${props => props.theme.bottomPanelGap}px;\n\n  ${media.portable`\n    border-top: 1px solid ${props => props.theme.panelBorderColor};\n    border-left: 1px solid ${props => props.theme.panelBorderColor};\n    padding: 12px 12px;\n    margin-top: 0;\n  `}\n`;\n\ninterface MapControlButtonProps {\n  active?: boolean;\n}\n\nexport const MapControlButton = styled(Button)\n  .withConfig({\n    shouldForwardProp: prop => !['active'].includes(prop)\n  })\n  .attrs(props => ({\n    className: classnames('map-control-button', props.className)\n  }))<MapControlButtonProps>`\n  box-shadow: 0 6px 12px 0 rgba(0, 0, 0, 0.16);\n  height: 32px;\n  width: 32px;\n  padding: 0;\n  border-radius: ${props => props.theme.floatingBtnRadius};\n  background-color: ${props =>\n    props.active ? props.theme.floatingBtnBgdHover : props.theme.floatingBtnBgd};\n  color: ${props =>\n    props.active ? props.theme.floatingBtnActColor : props.theme.floatingBtnColor};\n  border: ${props =>\n    props.active ? props.theme.floatingBtnBorderHover : props.theme.floatingBtnBorder};\n\n  &:hover,\n  &:focus,\n  &:active,\n  &.active {\n    background-color: ${props => props.theme.floatingBtnBgdHover};\n    color: ${props => props.theme.floatingBtnActColor};\n    border: ${props => props.theme.floatingBtnBorderHover};\n  }\n  svg {\n    margin-right: 0;\n  }\n`;\n\nexport const StyledFilterContent = styled.div`\n  background-color: ${props => props.theme.panelContentBackground};\n  padding: 12px;\n`;\n\nexport const TruncatedTitleText = styled.div`\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  overflow: hidden;\n`;\n\nexport const CheckMark = styled.span.attrs({\n  className: 'checkbox-inner'\n})`\n  background-color: ${props => props.theme.selectionBtnBorderActColor};\n  position: absolute;\n  top: 0;\n  right: 0;\n  display: block;\n  width: 10px;\n  height: 10px;\n  border-top-left-radius: 2px;\n\n  &:after {\n    position: absolute;\n    display: table;\n    border: 1px solid #fff;\n    border-top: 0;\n    border-left: 0;\n    transform: rotate(45deg) scale(1) translate(-50%, -50%);\n    opacity: 1;\n    content: ' ';\n    top: 40%;\n    left: 30%;\n    width: 3.2px;\n    height: 6.22px;\n  }\n`;\n\nexport const StyledTimePicker = styled(TimePicker)`\n  .react-time-picker {\n    display: inline-flex;\n    position: relative;\n    font-family: ${props => props.theme.fontFamily};\n    font-size: ${props => props.theme.inputFontSize};\n    background-color: ${props => props.theme.inputBgd};\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-time-picker,\n  .react-time-picker *,\n  .react-time-picker *:before,\n  .react-time-picker *:after {\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n  .react-time-picker__wrapper {\n    display: flex;\n    flex-grow: 1;\n    flex-shrink: 0;\n    background-color: ${props => props.theme.inputBgd};\n    border-radius: 4px;\n    width: 110px;\n    white-space: nowrap;\n  }\n  .react-time-picker__wrapper:hover {\n    background-color: ${props => props.theme.inputBgdHover};\n  }\n  .react-time-picker__inputGroup {\n    min-width: calc((4px * 3) + 0.54em * 6 + 0.217em * 2);\n    flex-grow: 1;\n    padding: 4px 2px;\n    box-sizing: content-box;\n    display: flex;\n    justify-content: end;\n    align-items: center;\n    height: 24px;\n  }\n  .react-time-picker__inputGroup__divider {\n    padding: 1px 0;\n    white-space: pre;\n  }\n  .react-time-picker__inputGroup__divider,\n  .react-time-picker__inputGroup__leadingZero {\n    display: inline-block;\n    color: ${props => props.theme.effectPanelTextMain};\n    font-family: ${props => props.theme.fontFamily};\n    font-size: ${props => props.theme.inputFontSize};\n    font-weight: 400;\n  }\n  .react-time-picker__inputGroup__input {\n    min-width: 0.54em;\n    height: 100%;\n    position: relative;\n    padding: 0 1px;\n    border: 0;\n    background: transparent;\n    color: ${props => props.theme.effectPanelTextMain};\n    font-family: ${props => props.theme.fontFamily};\n    font-size: ${props => props.theme.inputFontSize};\n    box-sizing: content-box;\n    -webkit-appearance: textfield;\n    -moz-appearance: textfield;\n    appearance: textfield;\n  }\n  .react-time-picker__inputGroup__input:focus {\n    outline: none;\n  }\n  .react-time-picker__inputGroup__input::-webkit-outer-spin-button,\n  .react-time-picker__inputGroup__input::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    margin: 0;\n  }\n  .react-time-picker__inputGroup__input--hasLeadingZero {\n    margin-left: -0.54em;\n    padding-left: calc(1px + 0.54em);\n  }\n  .react-time-picker__inputGroup__amPm {\n    max-width: 37px;\n    font: inherit;\n    font-weight: 400;\n    -webkit-appearance: menulist;\n    -moz-appearance: menulist;\n    appearance: menulist;\n    font-size: ${props => props.theme.inputFontSize};\n  }\n  .react-time-picker__button {\n    border: 0;\n    background-color: ${props => props.theme.inputBgd};\n    padding: 4px 6px;\n  }\n  .react-time-picker__button:enabled {\n    cursor: pointer;\n  }\n  .react-time-picker__button svg {\n    display: inherit;\n  }\n  .react-time-picker__clock--closed,\n  .react-time-picker__clear-button {\n    display: none;\n  }\n`;\n\nexport const StyledDatePicker = styled(DatePicker)`\n  .react-date-picker {\n    display: inline-flex;\n    position: relative;\n    font-family: ${props => props.theme.fontFamily};\n    font-size: ${props => props.theme.inputFontSize};\n  }\n  .react-date-picker,\n  .react-date-picker *,\n  .react-date-picker *:before,\n  .react-date-picker *:after {\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n  .react-date-picker__wrapper {\n    display: flex;\n    flex-grow: 1;\n    flex-shrink: 0;\n    width: 108px;\n    white-space: nowrap;\n    border-radius: 4px;\n  }\n  .react-date-picker__inputGroup {\n    min-width: calc((4px * 3) + 0.54em * 8 + 0.217em * 2);\n    flex-grow: 1;\n    padding: 4px 9px;\n    box-sizing: content-box;\n    background-color: ${props => props.theme.inputBgd};\n    display: flex;\n    justify-content: end;\n    align-items: center;\n    height: 22px;\n    border: 1px solid ${props => props.theme.inputBgd};\n    border-radius: 4px;\n  }\n\n  .react-date-picker__inputGroup:hover {\n    background-color: ${props => props.theme.inputBgdHover};\n  }\n  .react-date-picker__inputGroup__divider {\n    padding: 1px 0;\n    white-space: pre;\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-date-picker__inputGroup__divider,\n  .react-date-picker__inputGroup__leadingZero {\n    display: inline-block;\n  }\n  .react-date-picker__inputGroup__input {\n    min-width: 0.54em;\n    height: 100%;\n    position: relative;\n    padding: 0 1px;\n    border: 0;\n    background: none;\n    color: ${props => props.theme.effectPanelTextMain};\n    font: inherit;\n    font-size: ${props => props.theme.inputFontSize};\n    box-sizing: content-box;\n    -webkit-appearance: textfield;\n    -moz-appearance: textfield;\n    appearance: textfield;\n  }\n  .react-date-picker__inputGroup__input:focus {\n    outline: none;\n  }\n  .react-date-picker__inputGroup__input::-webkit-outer-spin-button,\n  .react-date-picker__inputGroup__input::-webkit-inner-spin-button {\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    margin: 0;\n  }\n  .react-date-picker__inputGroup__input:invalid {\n    background: ${props => props.theme.inputBgd};\n  }\n  .react-date-picker__inputGroup__input--hasLeadingZero {\n    margin-left: -0.54em;\n    padding-left: calc(1px + 0.54em);\n  }\n  .react-date-picker__calendar {\n    width: 257px;\n    max-width: 100vw;\n    z-index: 11;\n    color: ${props => props.theme.effectPanelTextSecondary1};\n    inset: auto !important;\n  }\n  .react-date-picker__calendar--closed {\n    display: none;\n  }\n  .react-date-picker__calendar .react-calendar {\n    border-width: thin;\n  }\n  .react-date-picker__button {\n    display: none;\n  }\n  .react-calendar {\n    width: 256px;\n    max-width: 100%;\n    color: ${props => props.theme.effectPanelTextSecondary1};\n    background: ${props => props.theme.inputBgdHover};\n    border-radius: 0px 4px 4px 4px;\n    font-family: ${props => props.theme.fontFamily};\n    line-height: 1.125em;\n    padding: 16px;\n  }\n  .react-calendar,\n  .react-calendar *,\n  .react-calendar *:before,\n  .react-calendar *:after {\n    -moz-box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n  .react-calendar button {\n    margin: 0;\n    border: 0;\n    outline: none;\n  }\n  .react-calendar buttom:enabled {\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-calendar button:enabled:hover {\n    cursor: pointer;\n  }\n  .react-calendar__navigation {\n    display: flex;\n    height: 25px;\n    margin-bottom: 1em;\n  }\n  .react-calendar__navigation button {\n    min-width: 25px;\n    background: none;\n  }\n  .react-calendar__navigation button:enabled:hover,\n  .react-calendar__navigation button:enabled:focus {\n    background-color: ${props => props.theme.inputBgdActive};\n  }\n  .react-calendar__month-view__weekdays {\n    text-align: center;\n    text-transform: uppercase;\n    font-weight: bold;\n  }\n  .react-calendar__month-view__weekdays__weekday {\n    color: ${props => props.theme.effectPanelTextSecondary2};\n    padding: 0.5em;\n    font-size: ${props => props.theme.inputFontSize};\n    abbr {\n      text-decoration: none;\n    }\n  }\n  .react-calendar__month-view__weekNumbers .react-calendar__tile {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    font-weight: bold;\n  }\n  .react-calendar__month-view__days__day--weekend {\n    color: ${props => props.theme.effectPanelTextSecondary1};\n  }\n  .react-calendar__month-view__days__day--neighboringMonth {\n    color: ${props => props.theme.effectPanelTextSecondary3};\n    opacity: 0.4;\n  }\n  .react-calendar__navigation__label__labelText,\n  .react-calendar__navigation__arrow {\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-calendar__year-view .react-calendar__tile,\n  .react-calendar__decade-view .react-calendar__tile,\n  .react-calendar__century-view .react-calendar__tile {\n    padding: 2em 0.5em;\n  }\n  .react-calendar__tile {\n    color: ${props => props.theme.effectPanelTextSecondary1};\n    max-width: 100%;\n    padding: 6px 4px;\n    background: none;\n    text-align: center;\n    font-size: ${props => props.theme.inputFontSize};\n    height: 30px;\n  }\n  .react-calendar__tile:enabled:hover,\n  .react-calendar__tile:enabled:focus {\n    background-color: ${props => props.theme.primaryBtnBgd};\n    color: ${props => props.theme.primaryBtnActColor};\n  }\n  .react-calendar__tile--now:enabled:hover,\n  .react-calendar__tile--now:enabled:focus {\n    background: ${props => props.theme.primaryBtnBgd};\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-calendar__tile--hasActive {\n    background: ${props => props.theme.primaryBtnActBgd};\n  }\n  .react-calendar__tile--hasActive:enabled:hover,\n  .react-calendar__tile--hasActive:enabled:focus {\n    background: ${props => props.theme.primaryBtnActBgd};\n    color: ${props => props.theme.effectPanelTextMain};\n  }\n  .react-calendar__tile--active {\n    background: ${props => props.theme.primaryBtnActBgd};\n    color: ${props => props.theme.effectPanelTextMain};\n    border-radius: 4px;\n  }\n  .react-calendar__tile--active:enabled:hover,\n  .react-calendar__tile--active:enabled:focus {\n    background: ${props => props.theme.primaryBtnActBgd};\n  }\n  .calendar__navigation__label__labelText {\n    сolor: ${props => props.theme.effectPanelTextMain};\n  }\n`;\n"
  },
  {
    "path": "src/components/src/common/switch.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ChangeEventHandler, FocusEventHandler, ReactNode} from 'react';\nimport Checkbox from './checkbox';\n\ninterface SwitchProps {\n  checked?: boolean;\n  type?: string;\n  id: string;\n  label?: ReactNode;\n  error?: string;\n  onBlur?: FocusEventHandler<HTMLInputElement>;\n  onChange?: ChangeEventHandler<HTMLInputElement>;\n  onFocus?: FocusEventHandler<HTMLInputElement>;\n  value?: string;\n  secondary?: boolean;\n  disabled?: boolean;\n}\n\nconst Switch = (props: SwitchProps) => {\n  const switchProps = {\n    ...props,\n    switch: props.type !== 'checkbox'\n  };\n\n  return <Checkbox {...switchProps} />;\n};\n\nexport default Switch;\n"
  },
  {
    "path": "src/components/src/common/sync-timeline-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Tooltip} from '../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {SYNC_TIMELINE_MODES} from '@kepler.gl/constants';\nimport IconButton from './icon-button';\n\nexport type SyncTimelineAnimationItem = {\n  id: string;\n  content: React.ElementType;\n  tooltip: string;\n};\n\nexport type SyncTimelineControlProps = {\n  syncTimelineMode: string;\n  setFilterSyncTimelineMode: (id: string) => void;\n  syncTimelineAnimationItems: SyncTimelineAnimationItem[];\n  btnStyle: Record<string, any>;\n};\n\nexport const SYNC_TIMELINE_ANIMATION_ITEMS: Record<\n  string,\n  {\n    id: number;\n    content: React.ElementType;\n    tooltip: string;\n  }\n> = {\n  [SYNC_TIMELINE_MODES.start]: {\n    id: SYNC_TIMELINE_MODES.start,\n    content: () => <span>Start</span>,\n    tooltip: 'tooltip.syncTimelineStart'\n  },\n  [SYNC_TIMELINE_MODES.end]: {\n    id: SYNC_TIMELINE_MODES.end,\n    content: () => <span>End</span>,\n    tooltip: 'tooltip.syncTimelineEnd'\n  }\n};\n\nfunction SyncTimelineControlFactory() {\n  const SyncTimelineControl = ({\n    syncTimelineAnimationItems = SYNC_TIMELINE_ANIMATION_ITEMS,\n    syncTimelineMode,\n    btnStyle,\n    setFilterSyncTimelineMode\n  }) => {\n    return (\n      <div>\n        {Object.values(syncTimelineAnimationItems)\n          .filter((item, _index) => item.id !== syncTimelineMode)\n          .map(item => (\n            <IconButton\n              key={item.id}\n              data-tip\n              data-for={`${item.id}-tooltip`}\n              className=\"playback-control-button\"\n              onClick={() => setFilterSyncTimelineMode(item.id)}\n              {...btnStyle}\n            >\n              <item.content />\n              {item.tooltip ? (\n                <Tooltip id={`${item.id}-tooltip`} effect=\"solid\" place=\"top\">\n                  <FormattedMessage id={item.tooltip} />\n                </Tooltip>\n              ) : null}\n            </IconButton>\n          ))}\n      </div>\n    );\n  };\n\n  return SyncTimelineControl;\n}\n\nexport default SyncTimelineControlFactory;\n"
  },
  {
    "path": "src/components/src/common/time-range-slider-time-title.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {Minus} from './icons';\nimport {datetimeFormatter} from '@kepler.gl/utils';\nimport {BaseComponentProps} from '../types';\n\nexport type TimeValueWrapperProps = BaseComponentProps & {\n  isEnlarged?: boolean;\n};\n\nconst TimeValueWrapper: IStyledComponent<\n  'web',\n  TimeValueWrapperProps\n> = styled.div<TimeValueWrapperProps>`\n  display: flex;\n  align-items: center;\n  font-size: ${props => props.theme.timeTitleFontSize};\n  justify-content: ${props => (props.isEnlarged ? 'center' : 'space-between')};\n\n  .horizontal-bar {\n    padding: 0 12px;\n    color: ${props => props.theme.textColor};\n  }\n\n  .time-value {\n    display: flex;\n    flex-direction: ${props => (props.isEnlarged ? 'row' : 'column')};\n    align-items: flex-start;\n    max-width: ${props => (!props.isEnlarged ? '40%' : 'auto')};\n    span {\n      color: ${props => props.theme.textColor};\n    }\n  }\n\n  .time-value:last-child {\n    align-items: flex-end;\n    text-align: right;\n  }\n`;\n\nconst TimeValue = ({value}) => (\n  // render two lines if not enlarged\n  <div className=\"time-value\">\n    <span>{value}</span>\n  </div>\n);\n\ninterface TimeTitleProps {\n  value: number[];\n  isEnlarged?: boolean;\n  timezone?: string | null;\n  timeFormat: string;\n}\n\nfunction TimeRangeSliderTimeTitleFactory() {\n  const TimeTitle = ({value, isEnlarged, timezone, timeFormat}: TimeTitleProps) => (\n    <TimeValueWrapper isEnlarged={isEnlarged} className=\"time-range-slider__time-title\">\n      <TimeValue key={0} value={datetimeFormatter(timezone)(timeFormat)(value[0])} />\n      {isEnlarged ? (\n        <div className=\"horizontal-bar\">\n          <Minus height=\"12px\" />\n        </div>\n      ) : null}\n      <TimeValue key={1} value={datetimeFormatter(timezone)(timeFormat)(value[1])} />\n    </TimeValueWrapper>\n  );\n\n  return TimeTitle;\n}\n\nexport default TimeRangeSliderTimeTitleFactory;\n"
  },
  {
    "path": "src/components/src/common/time-range-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport throttle from 'lodash/throttle';\nimport styled, {IStyledComponent} from 'styled-components';\n\nimport RangeSliderFactory from './range-slider';\nimport TimeSliderMarkerFactory from './time-slider-marker';\nimport PlaybackControlsFactory from './animation-control/playback-controls';\nimport TimeRangeSliderTimeTitleFactory from './time-range-slider-time-title';\nimport {LineChart, Timeline, AnimationConfig, TimeBins} from '@kepler.gl/types';\nimport {ActionHandler, setFilterPlot} from '@kepler.gl/actions';\nimport AnimationControlFactory from './animation-control/animation-control';\nimport {BaseComponentProps} from '../types';\n\nconst animationControlWidth = 176;\n\ntype TimeRangeSliderProps = {\n  domain?: [number, number];\n  value: [number, number];\n  isEnlarged?: boolean;\n  isMinified?: boolean;\n  hideTimeTitle?: boolean;\n  isAnimating: boolean;\n  timeFormat: string;\n  timezone?: string | null;\n  timeBins?: TimeBins;\n  plotType?: {\n    [key: string]: any;\n  };\n  lineChart?: LineChart;\n  step: number;\n  isAnimatable?: boolean;\n  speed: number;\n  animationWindow: string;\n  resetAnimation?: () => void;\n  toggleAnimation: () => void;\n  updateAnimationSpeed?: (val: number) => void;\n  setFilterAnimationWindow?: (id: string) => void;\n  setFilterPlot?: ActionHandler<typeof setFilterPlot>;\n  onChange: (v: number[]) => void;\n  timeline: Timeline;\n  invertTrendColor?: boolean;\n  animationConfig?: AnimationConfig;\n};\n\nexport type StyledSliderContainerProps = BaseComponentProps & {\n  isEnlarged?: boolean;\n};\n\nconst StyledSliderContainer: IStyledComponent<\n  'web',\n  StyledSliderContainerProps\n> = styled.div<StyledSliderContainerProps>`\n  align-items: flex-end;\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  padding-left: ${props => (props.isEnlarged ? 24 : 0)}px;\n\n  .timeline-container .kg-slider {\n    display: none;\n  }\n\n  .playback-controls {\n    margin-left: 22px;\n  }\n`;\n\nconst ANIMATION_CONTROL_STYLE = {flex: 1};\n\nTimeRangeSliderFactory.deps = [\n  PlaybackControlsFactory,\n  RangeSliderFactory,\n  TimeSliderMarkerFactory,\n  TimeRangeSliderTimeTitleFactory,\n  AnimationControlFactory\n];\n\nexport function getTimeBinsForInterval(timeBins: TimeBins | undefined, interval: number) {\n  if (!timeBins) return {};\n  return Object.keys(timeBins).reduce((acc, dataId) => {\n    acc[dataId] = timeBins[dataId][interval];\n    return acc;\n  }, {});\n}\n\nexport default function TimeRangeSliderFactory(\n  PlaybackControls: ReturnType<typeof PlaybackControlsFactory>,\n  RangeSlider: ReturnType<typeof RangeSliderFactory>,\n  TimeSliderMarker: ReturnType<typeof TimeSliderMarkerFactory>,\n  TimeRangeSliderTimeTitle: ReturnType<typeof TimeRangeSliderTimeTitleFactory>,\n  AnimationControl: ReturnType<typeof AnimationControlFactory>\n) {\n  const TimeRangeSlider: React.FC<TimeRangeSliderProps> = props => {\n    const {\n      domain,\n      value,\n      isEnlarged,\n      isMinified,\n      hideTimeTitle,\n      isAnimating,\n      resetAnimation,\n      timeFormat,\n      timezone,\n      timeBins,\n      plotType,\n      lineChart,\n      invertTrendColor,\n      step,\n      isAnimatable,\n      speed,\n      animationWindow,\n      updateAnimationSpeed,\n      setFilterAnimationWindow,\n      toggleAnimation,\n      onChange,\n      setFilterPlot,\n      timeline\n    } = props;\n\n    const throttledOnchange = useMemo(() => throttle(onChange, 20), [onChange]);\n    const binsForInterval = useMemo(\n      () => getTimeBinsForInterval(timeBins, plotType?.interval),\n      [timeBins, plotType?.interval]\n    );\n\n    const style = useMemo(\n      () => ({\n        width: isEnlarged ? `calc(100% - ${animationControlWidth}px)` : '100%'\n      }),\n      [isEnlarged]\n    );\n\n    return (\n      <div className=\"time-range-slider\">\n        {!hideTimeTitle && isEnlarged ? (\n          <div className=\"time-range-slider__title\" style={style}>\n            <TimeRangeSliderTimeTitle\n              timeFormat={timeFormat}\n              timezone={timezone}\n              value={value}\n              isEnlarged={isEnlarged}\n            />\n          </div>\n        ) : null}\n        <StyledSliderContainer className=\"time-range-slider__container\" isEnlarged={isEnlarged}>\n          {!isMinified ? (\n            <div className=\"timeline-container\" style={style}>\n              <RangeSlider\n                range={domain}\n                value0={value[0]}\n                value1={value[1]}\n                bins={binsForInterval}\n                lineChart={lineChart}\n                invertTrendColor={invertTrendColor}\n                plotType={plotType}\n                isEnlarged={isEnlarged}\n                showInput={false}\n                step={step}\n                onChange={throttledOnchange}\n                xAxis={TimeSliderMarker}\n                timezone={timezone}\n                timeFormat={timeFormat}\n                setFilterPlot={setFilterPlot}\n              />\n            </div>\n          ) : (\n            <AnimationControl\n              style={ANIMATION_CONTROL_STYLE}\n              isAnimatable={isAnimatable}\n              isAnimating={isAnimating}\n              resetAnimation={resetAnimation}\n              toggleAnimation={toggleAnimation}\n              updateAnimationSpeed={updateAnimationSpeed}\n              setTimelineValue={throttledOnchange}\n              setAnimationWindow={setFilterAnimationWindow}\n              showTimeDisplay={false}\n              timeline={timeline}\n            />\n          )}\n          {isEnlarged && !isMinified ? (\n            <PlaybackControls\n              isAnimatable={isAnimatable}\n              width={animationControlWidth}\n              speed={speed}\n              animationWindow={animationWindow}\n              updateAnimationSpeed={updateAnimationSpeed}\n              setFilterAnimationWindow={setFilterAnimationWindow}\n              pauseAnimation={toggleAnimation}\n              resetAnimation={resetAnimation}\n              isAnimating={isAnimating}\n              startAnimation={toggleAnimation}\n            />\n          ) : null}\n        </StyledSliderContainer>\n      </div>\n    );\n  };\n\n  return React.memo(TimeRangeSlider);\n}\n"
  },
  {
    "path": "src/components/src/common/time-slider-marker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useRef, useEffect, useMemo} from 'react';\nimport moment from 'moment-timezone';\nimport {NumberValue, scaleUtc} from 'd3-scale';\nimport {select} from 'd3-selection';\nimport {axisBottom} from 'd3-axis';\nimport styled from 'styled-components';\nimport {datetimeFormatter} from '@kepler.gl/utils';\n\nconst MIN_TICK_WIDTH_LARGE = 80;\nconst MIN_TICK_WIDTH_SMALL = 50;\nconst HEIGHT = 30;\n\nconst TimeSliderContainer = styled.svg`\n  pointer-events: none;\n  position: absolute;\n  top: 0;\n  overflow: visible;\n  margin-top: 6px;\n\n  .axis text {\n    font-size: ${props => props.theme.axisFontSize};\n    fill: ${props => props.theme.axisFontColor};\n  }\n\n  .axis line {\n    stroke: ${props => props.theme.axisFontColor};\n    shape-rendering: crispEdges;\n    stroke-width: 1;\n  }\n\n  .axis path {\n    fill: none;\n    stroke: ${props => props.theme.sliderBarBgd};\n    shape-rendering: crispEdges;\n    stroke-width: 2;\n  }\n\n  .axis .domain {\n    display: none;\n  }\n\n  .value {\n    fill: ${props => props.theme.axisFontColor};\n    font-size: ${props => props.theme.axisFontSize};\n\n    &.start {\n      text-anchor: start;\n    }\n\n    &.end {\n      text-anchor: end;\n    }\n  }\n`;\n\nconst TICK_FORMATS = {\n  millisecond: '.SSS',\n  second: ':ss',\n  minute: 'HH:mm',\n  hour: 'HH A',\n  day: 'ddd DD',\n  week: 'MMM DD',\n  month: 'MMM',\n  year: 'YYYY'\n};\n\n// timezone sensitive tick formatter based on moment\n// adapted based on d3 time scale tick format https://github.com/d3/d3-scale/blob/master/src/time.js#L59\nexport function getTickFormat(timezone: string) {\n  // date is js date object\n  const toMoment = timezone ? date => moment(date).tz(timezone) : moment;\n  const formatter = datetimeFormatter(timezone);\n\n  return date =>\n    (toMoment(date).startOf('second') < date\n      ? formatter(TICK_FORMATS.millisecond)\n      : toMoment(date).startOf('minute') < date\n      ? formatter(TICK_FORMATS.second)\n      : toMoment(date).startOf('hour') < date\n      ? formatter(TICK_FORMATS.minute)\n      : toMoment(date).startOf('day') < date\n      ? formatter(TICK_FORMATS.hour)\n      : toMoment(date).startOf('month') < date\n      ? toMoment(date).startOf('isoWeek') < date\n        ? formatter(TICK_FORMATS.day)\n        : formatter(TICK_FORMATS.week)\n      : toMoment(date).startOf('year') < date\n      ? formatter(TICK_FORMATS.month)\n      : formatter(TICK_FORMATS.year))(date);\n}\n\n// create a helper function so we can test it\nexport function getXAxis(\n  domain: Date[] | NumberValue[],\n  width: number,\n  isEnlarged: boolean,\n  timezone: string\n) {\n  if (!Array.isArray(domain) || !domain.every(Number.isFinite)) {\n    return null;\n  }\n  const scale = scaleUtc().domain(domain).range([0, width]);\n  if (!scale) {\n    return null;\n  }\n\n  const ticks = Math.floor(width / (isEnlarged ? MIN_TICK_WIDTH_LARGE : MIN_TICK_WIDTH_SMALL));\n  const tickFormat = timezone ? getTickFormat(timezone) : null;\n  const xAxis = axisBottom(scale).ticks(ticks).tickSize(4).tickPadding(4);\n  if (tickFormat) {\n    xAxis.tickFormat(tickFormat);\n  }\n\n  return xAxis;\n}\n\nexport function updateAxis(xAxisRef, xAxis) {\n  if (!xAxis) {\n    return;\n  }\n\n  select(xAxisRef.current).call(xAxis);\n}\n\ninterface TimeSliderMarkerProps {\n  width: number;\n  domain: Date[] | NumberValue[];\n  isEnlarged?: boolean;\n  height?: number;\n  timezone: string;\n}\n\nfunction TimeSliderMarkerFactory() {\n  const TimeSliderMarker = ({\n    width,\n    domain,\n    isEnlarged = true,\n    height = HEIGHT,\n    timezone\n  }: TimeSliderMarkerProps) => {\n    const xAxisRef = useRef(null);\n    const xAxis = useMemo(\n      () => getXAxis(domain, width, isEnlarged, timezone),\n      [domain, width, isEnlarged, timezone]\n    );\n    useEffect(() => {\n      updateAxis(xAxisRef, xAxis);\n    }, [xAxisRef, xAxis]);\n    return (\n      <TimeSliderContainer className=\"time-slider-marker\" width={width} height={height}>\n        <g className=\"x axis\" ref={xAxisRef} transform=\"translate(0, 0)\" />\n      </TimeSliderContainer>\n    );\n  };\n\n  return React.memo(TimeSliderMarker);\n}\n\nexport default TimeSliderMarkerFactory;\n"
  },
  {
    "path": "src/components/src/common/timeline-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport throttle from 'lodash/throttle';\nimport classnames from 'classnames';\nimport {clamp, datetimeFormatter} from '@kepler.gl/utils';\nimport {media} from '@kepler.gl/styles';\nimport {DEFAULT_TIME_FORMAT, ANIMATION_WINDOW} from '@kepler.gl/constants';\nimport {Timeline} from '@kepler.gl/types';\nimport Slider from './slider/slider';\nimport {BaseComponentProps} from '../types';\n\nfunction noop() {\n  return;\n}\n\nconst SLIDER_MARGIN_PALM = 6;\n\nexport type AnimationControlSliderProps = BaseComponentProps;\n\nconst AnimationControlSlider: IStyledComponent<'web', AnimationControlSliderProps> = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nconst SliderWrapper = styled.div`\n  display: flex;\n  position: relative;\n  flex-grow: 1;\n  margin: 0 24px;\n\n  ${media.palm`\n    margin: 0 ${SLIDER_MARGIN_PALM}px;\n  `}\n`;\n\nconst StyledSlider = styled(Slider)`\n  .kg-range-slider__bar {\n    // change colors\n  }\n`;\n\nconst StyledDomain = styled.div.attrs(props => ({\n  className: classnames('animation-control__time-domain', props.className)\n}))`\n  color: ${props => props.theme.titleTextColor};\n  font-weight: 400;\n  font-size: 10px;\n`;\n\nconst PROGRESS_BAR_HEIGHT = 8;\n\ninterface TimelineSliderProps {\n  timeline: Timeline;\n  setTimelineValue: (value: [number] | [number, number]) => void;\n  enableBarDrag?: boolean;\n  showDomainTimes?: boolean;\n  height?: number;\n  className?: string | null;\n  style?: object;\n}\n\nfunction TimelineSliderFactory() {\n  const TimelineSlider: React.FC<TimelineSliderProps> = ({\n    timeline, // timeline can be a union of filter and animationConfig\n    // we can pass timeline to a hook and get back values and controllers\n    setTimelineValue,\n    enableBarDrag = true,\n    showDomainTimes = true,\n    height = PROGRESS_BAR_HEIGHT,\n    className = null,\n    style\n  }) => {\n    const onThrottleUpdate = useMemo(() => throttle(setTimelineValue, 20), [setTimelineValue]);\n\n    const {step, domain, value, timeFormat, defaultTimeFormat, timezone, animationWindow, marks} =\n      timeline;\n\n    const isRanged = useMemo(\n      () =>\n        Array.isArray(value) && value.length === 2 && animationWindow !== ANIMATION_WINDOW.interval,\n      [animationWindow, value]\n    );\n\n    const [value0, value1]: [number, number] = useMemo(\n      () => [isRanged ? value[0] : null, isRanged ? value[1] : value[0]],\n      [isRanged, value]\n    );\n\n    const [onSlider0Change, onSlider1Change] = useMemo(() => {\n      if (!domain) return [noop, noop];\n      return [\n        isRanged ? (newValue: number) => onThrottleUpdate([clamp(domain, newValue), value1]) : noop,\n        isRanged\n          ? (newValue: number) => onThrottleUpdate([value0, clamp(domain, newValue)])\n          : (newValue: number) =>\n              onThrottleUpdate(\n                animationWindow === ANIMATION_WINDOW.interval\n                  ? // filter requires an array with 2 values\n                    [clamp(domain, newValue), clamp(domain, newValue)]\n                  : // animationConfig only requires one value\n                    [clamp(domain, newValue)]\n              )\n      ];\n    }, [animationWindow, domain, isRanged, value0, value1, onThrottleUpdate]);\n\n    const timelineSliderStyle = useMemo(() => ({height: `${height}px`}), [height]);\n\n    const [timeStart, timeEnd] = useMemo(() => {\n      if (!showDomainTimes) {\n        return [null, null];\n      }\n\n      const hasUserFormat = typeof timeFormat === 'string';\n      const currentFormat = (hasUserFormat ? timeFormat : defaultTimeFormat) || DEFAULT_TIME_FORMAT;\n      const dateFunc = datetimeFormatter(timezone)(currentFormat);\n\n      return [domain ? dateFunc(domain[0]) : '', domain ? dateFunc(domain[1]) : ''];\n    }, [domain, timezone, timeFormat, defaultTimeFormat, showDomainTimes]);\n\n    const requiresRangeSlider = isRanged && animationWindow !== ANIMATION_WINDOW.interval;\n\n    return (\n      <AnimationControlSlider\n        style={style}\n        className={classnames('animation-control__time-slider', className)}\n      >\n        {timeStart ? (\n          <StyledDomain className=\"domain-start\">\n            <span>{timeStart}</span>\n          </StyledDomain>\n        ) : null}\n        <SliderWrapper className=\"animation-control__slider\">\n          <StyledSlider\n            isRanged={requiresRangeSlider}\n            step={step || undefined}\n            minValue={domain ? domain[0] : 0}\n            maxValue={domain ? domain[1] : 1}\n            enableBarDrag={enableBarDrag}\n            style={timelineSliderStyle}\n            onSlider0Change={onSlider0Change}\n            onSlider1Change={onSlider1Change}\n            value0={value0}\n            value1={value1}\n            marks={marks}\n          />\n        </SliderWrapper>\n        {timeEnd ? (\n          <StyledDomain className=\"domain-end\">\n            <span>{timeEnd}</span>\n          </StyledDomain>\n        ) : null}\n      </AnimationControlSlider>\n    );\n  };\n\n  return TimelineSlider;\n}\n\nexport default TimelineSliderFactory;\n"
  },
  {
    "path": "src/components/src/common/tippy-tooltip.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Tippy, {TippyProps} from '@tippyjs/react';\nimport React, {useCallback, useRef, useState} from 'react';\nimport styled from 'styled-components';\n\nimport {RootContext} from '../context';\n\nconst TippyArrow = styled.div`\n  position: absolute;\n  width: 15px;\n  height: 15px;\n  fill: ${props => props.theme.tooltipBg};\n  text-align: initial;\n\n  > svg {\n    position: absolute;\n  }\n`;\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst TippyTooltipContent = styled(({children, arrow, isLightTheme, ...props}) => (\n  <div {...props}>\n    {children}\n    {arrow ? (\n      <TippyArrow className=\"svg-arrow\" data-popper-arrow=\"\">\n        <svg width={15} height={15}>\n          <path d=\"M2,7.5 7.5,2 13,7.5Z\" />\n        </svg>\n      </TippyArrow>\n    ) : null}\n  </div>\n))`\n  font-family: ${props => props.theme.fontFamily};\n  font-size: ${props => props.theme.tooltipFontSize};\n  font-weight: 400;\n  padding: 7px 18px;\n  box-shadow: ${props =>\n    props.isLightTheme ? props.theme.panelBoxShadow : props.theme.tooltipBoxShadow};\n  background-color: ${props =>\n    props.isLightTheme ? props.theme.tooltipBgLT : props.theme.tooltipBg};\n  color: ${props => (props.isLightTheme ? props.theme.tooltipColorLT : props.theme.tooltipColor)};\n  border-radius: ${props => props.theme.primaryBtnRadius};\n  ${props =>\n    props.arrow\n      ? `\n    &[data-placement^='top'] > .svg-arrow {\n      bottom: 0;\n      &::after,\n      > svg {\n        top: 7px;\n        transform: rotate(180deg);\n      }\n    }\n\n    &[data-placement^='bottom'] > .svg-arrow {\n      top: 0;\n      > svg {\n        bottom: 7px;\n      }\n    }\n\n    &[data-placement^='left'] > .svg-arrow {\n      right: 0;\n      &::after,\n      > svg {\n        transform: rotate(90deg);\n        left: 7px;\n      }\n    }\n\n    &[data-placement^='right'] > .svg-arrow {\n      left: 0;\n      &::after,\n      > svg {\n        transform: rotate(-90deg);\n        right: 7px;\n      }\n    }\n  `\n      : ''}\n`;\n\nconst TippyTooltip = ({\n  children,\n  render,\n  duration = 200,\n  arrow = true,\n  isLightTheme = false,\n  className,\n  ...rest\n}: TippyProps & {isLightTheme?: boolean}) => {\n  const [opacity, setOpacity] = useState(0);\n  const [timer, setTimer] = useState(null);\n  function onMount() {\n    setOpacity(1);\n    if (timer) {\n      // @ts-ignore\n      clearTimeout(timer);\n    }\n  }\n\n  function onHide(instance) {\n    const {unmount} = instance;\n    const timeout = setTimeout(() => {\n      if (!instance.state?.isDestroyed) {\n        unmount();\n      }\n    }, duration[0] || duration);\n    // @ts-ignore\n    setTimer(timeout);\n    setOpacity(0);\n  }\n\n  const skipNextShow = useRef(false);\n  const onTrigger = useCallback((instance, event) => {\n    if (event instanceof MouseEvent && event.buttons > 0) {\n      // if the user is holding down the mouse button, e.g. while dragging, we won't show the tooltip\n      skipNextShow.current = true;\n    } else {\n      skipNextShow.current = false;\n    }\n  }, []);\n  const onShow = useCallback(() => {\n    if (skipNextShow.current) {\n      return false;\n    }\n    return;\n  }, []);\n\n  return (\n    <RootContext.Consumer>\n      {context => (\n        <Tippy\n          {...rest}\n          // Using document.body would result in the CSS styles not being applied\n          // to the tooltip content when embedding the map widget as a Shadow DOM element.\n          appendTo={context?.current || 'parent'}\n          animation={true}\n          render={attrs => (\n            <TippyTooltipContent\n              {...attrs}\n              className={className}\n              style={{opacity, transition: `opacity ${duration}ms`}}\n              arrow={arrow}\n              isLightTheme={isLightTheme}\n            >\n              {render?.(attrs)}\n            </TippyTooltipContent>\n          )}\n          onMount={onMount}\n          onHide={onHide}\n          onTrigger={onTrigger}\n          onShow={onShow}\n        >\n          {children}\n        </Tippy>\n      )}\n    </RootContext.Consumer>\n  );\n};\n\nexport default TippyTooltip;\n"
  },
  {
    "path": "src/components/src/common/toolbar-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ComponentType, MouseEvent} from 'react';\n\ninterface StyledDivProps {\n  active?: boolean;\n}\n\nconst StyledDiv = styled.div.attrs(props => ({\n  className: classnames('toolbar-item', props.className)\n}))<StyledDivProps>`\n  color: ${props =>\n    props.active ? props.theme.toolbarItemIconHover : props.theme.panelHeaderIcon};\n  padding: 12px 20px;\n  align-items: center;\n  display: flex;\n  flex-direction: column;\n  width: 110px;\n  justify-content: space-between;\n  border: 1px solid ${props => (props.active ? props.theme.toolbarItemBorderHover : 'transparent')};\n  border-radius: ${props => props.theme.toolbarItemBorderRaddius};\n  background-color: ${props =>\n    props.active ? props.theme.toolbarItemBgdHover : props.theme.dropdownListBgd};\n\n  .toolbar-item__svg-container {\n    margin-bottom: 4px;\n  }\n  .toolbar-item__title {\n    white-space: nowrap;\n    color: ${props => props.theme.textColorHl};\n  }\n\n  &:hover {\n    background-color: ${props => props.theme.toolbarItemBgdHover};\n    border-color: ${props => props.theme.toolbarItemBorderHover};\n    svg {\n      color: ${props => props.theme.toolbarItemIconHover};\n    }\n    cursor: pointer;\n  }\n`;\n\nexport type ToolbarItemProps = {\n  id?: string;\n  key?: string;\n  label: string;\n  className?: string;\n  active?: boolean;\n  onClose?: () => void;\n  onClick: ((event: MouseEvent<HTMLDivElement>) => void) | null;\n  icon?: ComponentType<any>;\n};\n\nconst ToolbarItem = React.memo((props: ToolbarItemProps) => (\n  <StyledDiv\n    id={props.id}\n    className={props.className}\n    active={props.active}\n    onClick={e => {\n      e.stopPropagation();\n      e.preventDefault();\n      if (typeof props.onClose === 'function') {\n        props.onClose();\n      }\n      props.onClick?.(e);\n    }}\n  >\n    {props.icon && (\n      <div className=\"toolbar-item__svg-container\">\n        <props.icon />\n      </div>\n    )}\n    <div className=\"toolbar-item__title\">\n      <FormattedMessage id={props.label} />\n    </div>\n  </StyledDiv>\n));\n\nToolbarItem.displayName = 'ToolbarItem';\n\nexport default ToolbarItem;\n"
  },
  {
    "path": "src/components/src/common/toolbar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled, {IStyledComponent} from 'styled-components';\nimport {BaseComponentProps} from '../types';\n\nexport type ToolbarProps = {\n  show?: boolean;\n} & BaseComponentProps;\n\nconst Toolbar: IStyledComponent<'web', ToolbarProps> = styled.div<ToolbarProps>`\n  display: flex;\n  flex-direction: row;\n  background-color: ${props => props.theme.dropdownListBgd};\n  box-shadow: ${props => props.theme.dropdownListShadow};\n  font-size: 12px;\n  transition: ${props => props.theme.transitionSlow};\n  margin-top: ${props => (props.show ? '6px' : '20px')};\n  opacity: ${props => (props.show ? 1 : 0)};\n  transform: translateX(calc(-50% + 20px));\n  pointer-events: ${props => (props.show ? 'all' : 'none')};\n  z-index: 1000;\n\n  .panel-header-dropdown__inner {\n    box-shadow: none;\n    background-color: transparent;\n    display: flex;\n  }\n`;\n\nToolbar.displayName = 'Toolbar';\n\nexport default Toolbar;\n"
  },
  {
    "path": "src/components/src/common/vertical-toolbar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\nimport Toolbar, {ToolbarProps} from './toolbar';\n\nconst VerticalToolbar = styled(Toolbar)<ToolbarProps>`\n  flex-direction: column;\n\n  .toolbar-item {\n    width: 78px;\n    padding: 13px 16px;\n  }\n`;\n\nVerticalToolbar.displayName = 'VerticalToolbar';\n\nexport default VerticalToolbar;\n"
  },
  {
    "path": "src/components/src/connect/keplergl-connect.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {JSXElementConstructor} from 'react';\nimport {connect as reduxConnect, GetProps, Matching} from 'react-redux';\nimport withLocalSelector from './with-local-selector';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst defaultMapStateToProps = (state, _, __) => state;\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst defaultMapDispatchToProps = () => (dispatch, _, __) => ({dispatch});\n\nexport const connect =\n  <T extends JSXElementConstructor<Matching<any, GetProps<T>>>>(\n    mapStateToProps = defaultMapStateToProps,\n    makeMapDispatchToProps = defaultMapDispatchToProps,\n    reduxMergeProps?,\n    options?\n  ) =>\n  (BaseComponent: T) => {\n    const mapDispatchToProps = makeMapDispatchToProps();\n    const reduxMapState = (state, props) => mapStateToProps(props.selector(state), props, state);\n\n    const reduxMapDispatch = (dispatch, props) =>\n      mapDispatchToProps(props.dispatch, props, dispatch);\n\n    const ReduxComponent = reduxConnect(\n      reduxMapState,\n      reduxMapDispatch,\n      reduxMergeProps,\n      options\n    )(BaseComponent);\n\n    // save selector to context so it can be accessed by its children\n    return withLocalSelector(ReduxComponent);\n  };\n"
  },
  {
    "path": "src/components/src/connect/with-local-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {createSelector} from 'reselect';\nimport KeplerGlContext from '../context';\nimport {KeplerGlState} from '@kepler.gl/reducers';\n\nconst identity = state => state;\n\nconst mergeSelectors = (parentSelector, childSelector) => state =>\n  childSelector(parentSelector(state));\n\n// store the parent selector in the parent context\n// and return the parent component\n// when a selector is passed to a container component,\n// it will be stored in the context and passed down to child components,\n// as well as prop to the given component\n\nconst withLocalSelector = <P extends object>(\n  ParentComponent: React.ComponentType<P>\n): React.ComponentType<P & {selector: (...args: any[]) => KeplerGlState}> => {\n  class WithConnectSelector extends Component<P & {selector: (...args: any[]) => KeplerGlState}> {\n    static contextType = KeplerGlContext;\n\n    selectorFromContext = (_, ctx) => (ctx.selector ? ctx.selector : identity);\n    selectorFromProps = props => (props.selector ? props.selector : identity);\n    idFromProps = props => props.id;\n    computedSelector = createSelector(\n      this.selectorFromContext,\n      this.selectorFromProps,\n      (ctx, props) => mergeSelectors(ctx, props)\n    );\n\n    contextSelector = createSelector(this.computedSelector, this.idFromProps, (selector, id) => ({\n      selector,\n      id\n    }));\n\n    render() {\n      // @ts-ignore Argument of type 'Readonly<P & { selector: (...args: any[]) => KeplerGlState; }>' is not assignable to parameter of type 'never'\n      const computedContext = this.contextSelector(this.props, this.context);\n      return (\n        <KeplerGlContext.Provider value={computedContext}>\n          <ParentComponent {...this.props} selector={computedContext.selector} />\n        </KeplerGlContext.Provider>\n      );\n    }\n  }\n\n  return WithConnectSelector;\n};\n\nexport default withLocalSelector;\n"
  },
  {
    "path": "src/components/src/container.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useRef, ComponentType, Dispatch, useEffect, useMemo} from 'react';\nimport {connect, ConnectedProps, useDispatch} from 'react-redux';\n\nimport {console as Console} from 'global/window';\nimport {injector, provideRecipesToInjector, flattenDeps} from './injector';\nimport KeplerGlFactory from './kepler-gl';\nimport {registerEntry, deleteEntry, renameEntry, forwardTo} from '@kepler.gl/actions';\nimport {KeplerGlState} from '@kepler.gl/reducers';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nexport const ERROR_MSG = {\n  noState:\n    `kepler.gl state does not exist. ` +\n    `You might forget to mount keplerGlReducer in your root reducer.` +\n    `If it is not mounted as state.keplerGl by default, you need to provide getState as a prop`\n};\n\nconst mapStateToProps = (state: any, props: ContainerProps) => ({state, ...props});\nconst dispatchToProps = (dispatch: Dispatch<any>) => ({dispatch});\nconst connector = connect(mapStateToProps, dispatchToProps);\n\ntype ContainerProps = {\n  id: string;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl?: string;\n  mapStylesReplaceDefault?: boolean;\n  initialUiState?: object;\n  width: number;\n  mint?: boolean;\n  getState: (state: any) => KeplerGlState;\n};\n\ntype PropsFromRedux = ConnectedProps<typeof connector> & ContainerProps;\n\nContainerFactory.deps = [KeplerGlFactory];\n\nexport function ContainerFactory(\n  KeplerGl: ReturnType<typeof KeplerGlFactory>\n): ComponentType<PropsFromRedux> {\n  /** @lends KeplerGl */\n  /**\n    * Main Kepler.gl Component\n    * @param {Object} props\n    *\n    * @param {string} props.id - _required_\n    *\n    * - Default: `map`\n    * The id of this KeplerGl instance. `id` is required if you have multiple\n    * KeplerGl instances in your app. It defines the prop name of the KeplerGl state that is\n    * stored in the KeplerGl reducer. For example, the state of the KeplerGl component with id `foo` is\n    * stored in `state.keplerGl.foo`.\n    *\n    * In case you create multiple kepler.gl instances using the same id, the kepler.gl state defined by the entry will be\n    * overridden by the latest instance and reset to a blank state.\n    * @param {string} props.mapboxApiAccessToken - _required_\n    * @param {string} props.mapboxApiUrl - _optional_\n    * @param {Boolean} props.mapStylesReplaceDefault - _optional_\n    * @param {object} props.initialUiState - _optional_\n\n    * You can create a free account at [www.mapbox.com](www.mapbox.com) and create a token at\n    * [www.mapbox.com/account/access-tokens](www.mapbox.com/account/access-tokens)\n    *\n    *\n    * @param {Number} props.width - _required_ Width of the KeplerGl UI.\n    * @public\n   */\n\n  function usePreviousId(value) {\n    const ref = useRef();\n    useEffect(() => {\n      ref.current = value;\n    });\n    return ref.current;\n  }\n\n  const Container: React.FC<PropsFromRedux> = props => {\n    const {\n      // default id and address if not provided\n      id = 'map',\n      getState = state => state.keplerGl,\n      mint = true,\n      mapboxApiAccessToken,\n      mapboxApiUrl,\n      mapStylesReplaceDefault,\n      initialUiState,\n      state\n    } = props;\n    const prevId = usePreviousId(id);\n    const dispatch = useDispatch();\n\n    useEffect(() => {\n      // add a new entry to reducer\n      dispatch(\n        registerEntry({\n          id,\n          mint,\n          mapboxApiAccessToken,\n          mapboxApiUrl,\n          mapStylesReplaceDefault,\n          initialUiState\n        })\n      );\n\n      // initialize plugins\n      if (getApplicationConfig().plugins.length) {\n        getApplicationConfig().plugins.forEach(async plugin => {\n          await plugin.init();\n        });\n      }\n\n      // cleanup\n      return () => {\n        if (mint !== false) {\n          // delete entry in reducer\n          dispatch(deleteEntry(id));\n        }\n      };\n    }, [\n      id,\n      dispatch,\n      initialUiState,\n      mapStylesReplaceDefault,\n      mapboxApiAccessToken,\n      mapboxApiUrl,\n      mint\n    ]);\n\n    useEffect(() => {\n      // check if id has changed, if true, copy state over\n      if (prevId && id && prevId !== id) {\n        dispatch(renameEntry(prevId, id));\n      }\n    }, [dispatch, prevId, id]);\n\n    const stateSelector = useMemo(\n      () => keplerState => {\n        if (!getState(keplerState)) {\n          // log error\n          Console.error(ERROR_MSG.noState);\n\n          return null;\n        }\n        return getState(keplerState)[id];\n      },\n      [id, getState]\n    );\n    const forwardDispatch = useMemo(() => forwardTo(id, dispatch), [id, dispatch]);\n\n    // const selector = getSelector(id, getState);\n\n    if (!stateSelector || !stateSelector(state)) {\n      // instance state hasn't been mounted yet\n      return <div />;\n    }\n\n    return <KeplerGl {...props} id={id} selector={stateSelector} dispatch={forwardDispatch} />;\n  };\n\n  return connector(Container);\n}\n\nconst allDependencies = flattenDeps([], ContainerFactory);\n\n// provide all dependencies to appInjector\nexport const appInjector = allDependencies.reduce(\n  (inj, factory) => inj.provide(factory, factory),\n  injector()\n);\n\n// Helper to inject custom components and return kepler.gl container\nexport function injectComponents(recipes = []) {\n  return provideRecipesToInjector(recipes, appInjector).get(ContainerFactory);\n}\n\nconst InjectedContainer = appInjector.get(ContainerFactory);\n\nexport default InjectedContainer;\n"
  },
  {
    "path": "src/components/src/context.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {createContext, RefObject, ReactNode, ReactElement} from 'react';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\nconst identity = state => state;\n// New Context API only supported after 16.3\nconst KeplerGlContext = createContext({\n  selector: identity,\n  id: 'map'\n});\n\n// TODO: breakdown this file into multiple files\nexport const FeatureFlagsContext = createContext<object | null>({});\n\nexport type FeatureFlags = {[key: string]: string | boolean};\n\nexport type FeatureFlagsContextProviderProps = {\n  children: ReactNode;\n  featureFlags?: FeatureFlags;\n};\n\nexport type CloudProviderContextType = {\n  provider: Provider | null;\n  setProvider: (provider: Provider | null) => void;\n  cloudProviders: Provider[];\n};\n\nexport const FeatureFlagsContextProvider = (\n  props: FeatureFlagsContextProviderProps\n): ReactElement => (\n  <FeatureFlagsContext.Provider value={props.featureFlags || null}>\n    {props.children}\n  </FeatureFlagsContext.Provider>\n);\n\n/**\n * This provides keeps track of the ist cloud providers\n * and the current selected one\n */\nexport const CloudProviderContext = createContext<CloudProviderContextType>({\n  provider: null,\n  setProvider: () => {\n    return;\n  },\n  cloudProviders: []\n});\n\nexport const RootContext = createContext<RefObject<HTMLDivElement> | null>(null);\n\nexport default KeplerGlContext;\n"
  },
  {
    "path": "src/components/src/dnd-context.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, PropsWithChildren} from 'react';\nimport styled from 'styled-components';\nimport {DndContext as DndKitContext, DragOverlay} from '@dnd-kit/core';\n\nimport Console from 'global/console';\nimport {VisState} from '@kepler.gl/schemas';\n\nimport LayerPanelHeaderFactory from './side-panel/layer-panel/layer-panel-header';\nimport useDndLayers from './hooks/use-dnd-layers';\nimport useDndEffects from './hooks/use-dnd-effects';\n\nimport {\n  DND_MODIFIERS,\n  DND_EMPTY_MODIFIERS,\n  SORTABLE_LAYER_TYPE,\n  SORTABLE_EFFECT_TYPE\n} from './common/dnd-layer-items';\n\nexport type DndContextProps = PropsWithChildren<{\n  visState: VisState;\n}>;\n\nexport type DndContextComponent = React.FC<DndContextProps>;\n\nexport const DragItem = styled.div`\n  color: ${props => props.theme.textColorHl};\n  border-radius: ${props => props.theme.radioButtonRadius}px;\n  padding: 5px 10px;\n  display: inline;\n`;\n\nconst nop = () => undefined;\n\nDndContextFactory.deps = [LayerPanelHeaderFactory];\n\nfunction DndContextFactory(\n  LayerPanelHeader: ReturnType<typeof LayerPanelHeaderFactory>\n): React.FC<DndContextProps> {\n  const LayerPanelOverlay = ({layer, datasets}) => {\n    const color =\n      layer.config.dataId && datasets[layer.config.dataId]\n        ? datasets[layer.config.dataId].color\n        : null;\n    return (\n      <LayerPanelHeader\n        isConfigActive={false}\n        layerId={layer.id}\n        isVisible={true}\n        isValid={true}\n        label={layer.config.label}\n        labelRCGColorValues={color}\n        onToggleVisibility={nop}\n        onResetIsValid={nop}\n        onUpdateLayerLabel={nop}\n        onToggleEnableConfig={nop}\n        onDuplicateLayer={nop}\n        onRemoveLayer={nop}\n        onZoomToLayer={nop}\n        layerType={layer.type}\n        allowDuplicate={false}\n        isDragNDropEnabled={false}\n      />\n    );\n  };\n\n  const DndContext = ({children, visState}: DndContextProps) => {\n    const {datasets, layerOrder, layers, effects, effectOrder, splitMaps} = visState;\n\n    const {\n      activeLayer,\n      onDragStart: onLayerDragStart,\n      onDragEnd: onLayerDragEnd\n    } = useDndLayers(layers, layerOrder);\n    const {onDragStart: onEffectDragStart, onDragEnd: onEffectDragEnd} = useDndEffects(\n      effects,\n      effectOrder\n    );\n\n    const isSplit = useMemo(() => splitMaps?.length > 1, [splitMaps]);\n    const dndModifiers = useMemo(() => (isSplit ? DND_EMPTY_MODIFIERS : DND_MODIFIERS), [isSplit]);\n\n    const onDragStart = useCallback(\n      event => {\n        const activeType = event.active.data?.current?.type;\n        switch (activeType) {\n          case SORTABLE_LAYER_TYPE:\n            onLayerDragStart(event);\n            break;\n          case SORTABLE_EFFECT_TYPE:\n            onEffectDragStart(event);\n            break;\n          default:\n            Console.log(`activeType ${activeType} unknown`);\n        }\n      },\n      [onLayerDragStart, onEffectDragStart]\n    );\n\n    const onDragEnd = useCallback(\n      event => {\n        const activeType = event.active.data?.current?.type;\n        switch (activeType) {\n          case SORTABLE_LAYER_TYPE:\n            onLayerDragEnd(event);\n            break;\n          case SORTABLE_EFFECT_TYPE:\n            onEffectDragEnd(event);\n            break;\n          default:\n            Console.log(`activeType ${activeType} unknown`);\n        }\n      },\n      [onLayerDragEnd, onEffectDragEnd]\n    );\n\n    return (\n      <DndKitContext onDragStart={onDragStart} onDragEnd={onDragEnd} modifiers={dndModifiers}>\n        {children}\n        {activeLayer ? (\n          <DragOverlay modifiers={dndModifiers} dropAnimation={null}>\n            <DragItem>\n              <LayerPanelOverlay layer={activeLayer} datasets={datasets} />\n            </DragItem>\n          </DragOverlay>\n        ) : null}\n      </DndKitContext>\n    );\n  };\n\n  return DndContext;\n}\n\nexport default DndContextFactory;\n"
  },
  {
    "path": "src/components/src/editor/editor.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, CSSProperties, KeyboardEvent} from 'react';\nimport {createPortal} from 'react-dom';\nimport styled from 'styled-components';\nimport Window from 'global/window';\nimport classnames from 'classnames';\nimport get from 'lodash/get';\nimport {createSelector} from 'reselect';\nimport FeatureActionPanelFactory, {FeatureActionPanelProps} from './feature-action-panel';\nimport {\n  EDITOR_AVAILABLE_LAYERS,\n  FILTER_TYPES,\n  EDITOR_MODES,\n  GEOCODER_LAYER_ID,\n  KeyEvent\n} from '@kepler.gl/constants';\nimport {Layer, EditorLayerUtils} from '@kepler.gl/layers';\nimport {Filter, FeatureSelectionContext, Feature} from '@kepler.gl/types';\nimport {FeatureOf, Polygon} from '@nebula.gl/edit-modes';\nimport {Datasets} from '@kepler.gl/table';\n\nimport {RootContext} from '../context';\n\nconst DECKGL_RENDER_LAYER = 'default-deckgl-overlay-wrapper';\n\nconst StyledWrapper = styled.div`\n  position: relative;\n`;\n\nconst editorLayerFilter = (layer: Layer) => EDITOR_AVAILABLE_LAYERS.includes(layer.type || '');\n\nEditorFactory.deps = [FeatureActionPanelFactory];\n\ninterface EditorProps {\n  filters: Filter[];\n  layers: Layer[];\n  datasets: Datasets;\n  editor: {selectedFeature: Feature; mode: string; selectionContext?: FeatureSelectionContext};\n  index: number;\n  className?: string;\n  style: CSSProperties;\n  onSelect: (f: Feature | null) => any;\n  onSetEditorMode: (m: any) => void;\n  onDeleteFeature: (f: Feature) => any;\n  onTogglePolygonFilter: (l: Layer, f: Feature) => any;\n}\n\nexport type PortalEditorProps = FeatureActionPanelProps & {\n  visiblePanel: boolean;\n  style?: React.CSSProperties;\n};\n\nexport default function EditorFactory(\n  FeatureActionPanel: React.FC<FeatureActionPanelProps>\n): React.ComponentClass<EditorProps> {\n  const PortalEditor: React.FC<PortalEditorProps> = ({\n    visiblePanel,\n    className,\n    style,\n    selectedFeature,\n    datasets,\n    layers,\n    currentFilter,\n    onClose,\n    onDeleteFeature,\n    onToggleLayer,\n    position\n  }) => {\n    return (\n      <RootContext.Consumer>\n        {context => (\n          <>\n            {createPortal(\n              <StyledWrapper className={classnames('editor', className)} style={style}>\n                {visiblePanel ? (\n                  <FeatureActionPanel\n                    selectedFeature={selectedFeature as FeatureOf<Polygon>}\n                    datasets={datasets}\n                    layers={layers}\n                    currentFilter={currentFilter}\n                    onClose={onClose}\n                    onDeleteFeature={onDeleteFeature}\n                    onToggleLayer={onToggleLayer}\n                    position={position || null}\n                  />\n                ) : null}\n              </StyledWrapper>,\n              context?.current ?? document.body\n            )}\n          </>\n        )}\n      </RootContext.Consumer>\n    );\n  };\n  class EditorUnmemoized extends Component<EditorProps> {\n    static defaultProps = {};\n\n    static displayName = 'Editor';\n\n    state = {};\n\n    componentDidMount() {\n      Window.addEventListener('keydown', this._onKeyPressed);\n    }\n\n    componentWillUnmount() {\n      Window.removeEventListener('keydown', this._onKeyPressed);\n    }\n\n    layerSelector = (props: EditorProps) => props.layers;\n    filterSelector = (props: EditorProps) => props.filters;\n    selectedFeatureIdSelector = (props: EditorProps) =>\n      get(props, ['editor', 'selectedFeature', 'id']);\n    editorFeatureSelector = (props: EditorProps) => get(props, ['editor', 'features']);\n\n    currentFilterSelector = createSelector(\n      this.filterSelector,\n      this.selectedFeatureIdSelector,\n      (filters, selectedFeatureId) => filters.find(f => f.value && f.value.id === selectedFeatureId)\n    );\n\n    availableLayersSelector = createSelector(this.layerSelector, layers =>\n      layers\n        .filter(editorLayerFilter)\n        .filter(layer => layer.config?.isVisible && layer.id !== GEOCODER_LAYER_ID)\n    );\n\n    allFeaturesSelector = createSelector(\n      this.filterSelector,\n      this.editorFeatureSelector,\n      (filters, editorFeatures) =>\n        filters\n          .filter(f => f.type === FILTER_TYPES.polygon)\n          .map(f => f.value)\n          .concat(editorFeatures)\n    );\n\n    isInFocus = () => document.activeElement?.id === DECKGL_RENDER_LAYER;\n\n    _onKeyPressed = (event: KeyboardEvent) => {\n      if (this.isInFocus()) {\n        switch (event.keyCode) {\n          case KeyEvent.DOM_VK_DELETE:\n          case KeyEvent.DOM_VK_BACK_SPACE:\n            this._onDeleteSelectedFeature();\n            break;\n          case KeyEvent.DOM_VK_ESCAPE:\n            // reset active drawing\n            if (EditorLayerUtils.isDrawingActive(true, this.props.editor.mode)) {\n              this.props.onSetEditorMode(EDITOR_MODES.EDIT);\n            }\n\n            this.props.onSelect(null);\n            break;\n          default:\n            break;\n        }\n      }\n    };\n\n    _onDeleteSelectedFeature = () => {\n      const {editor} = this.props;\n      const {selectedFeature} = editor;\n      if (selectedFeature) {\n        this.props.onDeleteFeature(selectedFeature);\n      }\n    };\n\n    _closeFeatureAction = () => {\n      // reset selection context\n      const {selectedFeature} = this.props.editor;\n      this.props.onSelect(selectedFeature);\n    };\n\n    _togglePolygonFilter = (layer: Layer) => {\n      const {selectedFeature} = this.props.editor;\n      if (selectedFeature) {\n        this.props.onTogglePolygonFilter(layer, selectedFeature);\n      }\n    };\n\n    render() {\n      const {className, datasets, editor, style, index} = this.props;\n      const {selectedFeature, selectionContext} = editor;\n      const currentFilter = this.currentFilterSelector(this.props);\n      const availableLayers = this.availableLayersSelector(this.props);\n\n      const {rightClick, position, mapIndex} = selectionContext || {};\n\n      return (\n        <PortalEditor\n          selectedFeature={selectedFeature as FeatureOf<Polygon>}\n          visiblePanel={Boolean(rightClick) && selectedFeature && index === mapIndex}\n          datasets={datasets}\n          layers={availableLayers}\n          currentFilter={currentFilter}\n          onClose={this._closeFeatureAction}\n          onDeleteFeature={this._onDeleteSelectedFeature}\n          onToggleLayer={this._togglePolygonFilter}\n          position={position || null}\n          className={className}\n          style={style}\n        />\n      );\n    }\n  }\n\n  const Editor = React.memo(EditorUnmemoized) as unknown as typeof EditorUnmemoized;\n  Editor.displayName = 'Editor';\n  return Editor;\n}\n"
  },
  {
    "path": "src/components/src/editor/feature-action-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState, ComponentType} from 'react';\nimport {useIntl} from 'react-intl';\nimport copy from 'copy-to-clipboard';\nimport {useDismiss, useFloating, useInteractions} from '@floating-ui/react';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\n\nimport {Layer} from '@kepler.gl/layers';\nimport {Filter} from '@kepler.gl/types';\nimport {Feature} from '@nebula.gl/edit-modes';\nimport {Datasets} from '@kepler.gl/table';\nimport {canApplyFeatureFilter} from '@kepler.gl/utils';\n\nimport ActionPanel, {ActionPanelItem} from '../common/action-panel';\nimport {Trash, Layers, Copy, Checkmark} from '../common/icons';\n\nconst LAYOVER_OFFSET = 4;\n\nconst StyledActionsLayer = styled.div`\n  position: absolute;\n  .layer-panel-item-disabled {\n    color: ${props => props.theme.textColor};\n  }\n`;\nconst defaultActionIcons = {\n  remove: Trash,\n  layer: Layers,\n  copy: Copy,\n  copied: Checkmark\n};\nPureFeatureActionPanelFactory.deps = [];\n\nexport interface FeatureActionPanelProps {\n  className?: string;\n  datasets: Datasets;\n  selectedFeature: Feature | null;\n  position: {\n    x: number;\n    y: number;\n  } | null;\n  layers: Layer[];\n  currentFilter?: Filter;\n  onToggleLayer: (layer: Layer) => void;\n  onDeleteFeature: () => void;\n  onClose?: () => void;\n  children?: React.ReactNode;\n  actionIcons?: {\n    [id: string]: React.ElementType;\n  };\n}\n\nexport function PureFeatureActionPanelFactory(): React.FC<FeatureActionPanelProps> {\n  const FeatureActionPanel = ({\n    className,\n    datasets,\n    selectedFeature,\n    position = null,\n    layers,\n    currentFilter,\n    onToggleLayer,\n    onDeleteFeature,\n    actionIcons = defaultActionIcons,\n    children,\n    onClose\n  }: FeatureActionPanelProps) => {\n    const [copied, setCopied] = useState(false);\n    const {layerId = []} = currentFilter || {};\n    const intl = useIntl();\n\n    const {refs, context} = useFloating({\n      open: true,\n      onOpenChange: v => {\n        if (!v && onClose) {\n          onClose();\n        }\n      }\n    });\n    const dismiss = useDismiss(context);\n\n    const {getFloatingProps} = useInteractions([dismiss]);\n\n    const copyGeometry = useCallback(() => {\n      if (selectedFeature?.geometry) copy(JSON.stringify(selectedFeature.geometry));\n      setCopied(true);\n    }, [selectedFeature?.geometry]);\n\n    if (!position) {\n      return null;\n    }\n\n    const isFilterLayerDisabled = !canApplyFeatureFilter(selectedFeature as any);\n    return (\n      <StyledActionsLayer\n        ref={refs.setFloating}\n        {...getFloatingProps()}\n        className={classnames('feature-action-panel', className)}\n        style={{\n          top: `${position.y + LAYOVER_OFFSET}px`,\n          left: `${position.x + LAYOVER_OFFSET}px`\n        }}\n      >\n        <ActionPanel>\n          <ActionPanelItem\n            className=\"editor-layers-list\"\n            label={intl.formatMessage({id: 'editor.filterLayer', defaultMessage: 'Filter layers'})}\n            Icon={actionIcons.layer}\n            isDisabled={isFilterLayerDisabled}\n            tooltipText={\n              isFilterLayerDisabled ? intl.formatMessage({id: 'editor.filterLayerDisabled'}) : null\n            }\n          >\n            {layers.length ? (\n              layers.map((layer, index) => (\n                <ActionPanelItem\n                  key={index}\n                  label={layer.config.label}\n                  // @ts-ignore\n                  color={datasets[layer.config.dataId].color}\n                  isSelection={true}\n                  isActive={layerId.includes(layer.id)}\n                  onClick={() => onToggleLayer(layer)}\n                  className=\"layer-panel-item\"\n                />\n              ))\n            ) : (\n              <ActionPanelItem\n                key={'no-layers'}\n                label={intl.formatMessage({\n                  id: 'editor.noLayersToFilter',\n                  defaultMessage: 'No layers to filter'\n                })}\n                isSelection={false}\n                isActive={false}\n                className=\"layer-panel-item-disabled\"\n              />\n            )}\n          </ActionPanelItem>\n          <ActionPanelItem\n            label={intl.formatMessage({id: 'editor.copyGeometry', defaultMessage: 'Copy Geometry'})}\n            className=\"delete-panel-item\"\n            Icon={copied ? actionIcons.copied : actionIcons.copy}\n            onClick={copyGeometry}\n          />\n          {children}\n          <ActionPanelItem\n            label={intl.formatMessage({id: 'tooltip.delete', defaultMessage: 'Delete'})}\n            className=\"delete-panel-item\"\n            Icon={actionIcons.remove}\n            onClick={onDeleteFeature}\n          />\n        </ActionPanel>\n      </StyledActionsLayer>\n    );\n  };\n\n  FeatureActionPanel.displayName = 'FeatureActionPanel';\n\n  return FeatureActionPanel;\n}\n\nFeatureActionPanelFactory.deps = PureFeatureActionPanelFactory.deps;\n\nexport default function FeatureActionPanelFactory(): ComponentType<FeatureActionPanelProps> {\n  return PureFeatureActionPanelFactory();\n}\n"
  },
  {
    "path": "src/components/src/effects/compact-color-picker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport {rgbToHex} from '@kepler.gl/utils';\n\nimport {Button} from '../common/styled-components';\nimport Portaled from '../common/portaled';\nimport SingleColorPalette from '../side-panel/layer-panel/single-color-palette';\n\nexport type SingleColorPickerProps = {\n  color: [number, number, number];\n  onSetColor: (value: [number, number, number]) => void;\n  label: string;\n  Icon: React.ElementType;\n};\n\nexport const StyledPanelDropdown = styled.div`\n  ${props => props.theme.panelDropdownScrollBar}\n  background-color: ${props => props.theme.panelBackground};\n  box-shadow: ${props => props.theme.panelBoxShadow};\n  border-radius: ${props => props.theme.panelBorderRadius};\n  overflow-y: auto;\n  max-height: 500px;\n  position: relative;\n  z-index: 999;\n  width: 220px;\n`;\n\nconst StyledConfigSection = styled.div`\n  display: flex;\n  flex-direction: column;\n`;\n\nconst SectionTitle = styled.div`\n  font-size: ${props => props.theme.inputFontSize};\n  color: ${props => props.theme.effectPanelTextSecondary2};\n  margin-bottom: 8px;\n`;\n\nconst StyledDropdownButtonWrapper = styled.div`\n  align-self: flex-start;\n  .button {\n    color: ${props => props.theme.effectPanelTextSecondary2};\n    display: flex;\n    gap: 5px;\n    border: none;\n    transition: background 0.2s;\n    background-color: ${props => props.theme.inputBgd};\n    padding: 8px 5px 8px 10px;\n    &:active {\n      color: ${props => props.theme.effectPanelTextMain};\n      background-color: ${props => props.theme.inputBgdHover};\n    }\n    &:hover {\n      color: ${props => props.theme.effectPanelTextMain};\n      background-color: ${props => props.theme.inputBgdHover};\n    }\n    & > svg {\n      margin-right: 0;\n    }\n  }\n`;\n\nconst DEFAULT_OFFSET = {\n  top: 0,\n  left: 0\n};\n\nconst SingleColorPickerDropdown = ({\n  isOpened,\n  onClose,\n  selectedColor,\n  onSelectColor,\n  offset = DEFAULT_OFFSET\n}) => {\n  const onSelectColorCb = useCallback(\n    v => {\n      onSelectColor(v);\n    },\n    [onSelectColor]\n  );\n  return (\n    <Portaled top={offset.top} left={offset.left} isOpened={isOpened} onClose={onClose}>\n      <StyledPanelDropdown>\n        <SingleColorPalette selectedColor={selectedColor} onSelectColor={onSelectColorCb} />\n      </StyledPanelDropdown>\n    </Portaled>\n  );\n};\n\nconst CompactColorPicker: React.FC<SingleColorPickerProps> = ({\n  color,\n  onSetColor,\n  Icon,\n  label\n}: SingleColorPickerProps) => {\n  const [isColorPickerOpened, setIsColorPickerOpened] = React.useState(false);\n\n  const hexColor = useMemo(() => {\n    return rgbToHex(color);\n  }, [color]);\n\n  const colorBlockStyle = useMemo(\n    () => ({\n      width: 16,\n      height: 16,\n      backgroundColor: hexColor,\n      borderRadius: 2\n    }),\n    [hexColor]\n  );\n\n  const toggleDropdown = useCallback(() => {\n    setIsColorPickerOpened(!isColorPickerOpened);\n  }, [isColorPickerOpened, setIsColorPickerOpened]);\n\n  const closeDropdown = useCallback(() => {\n    setIsColorPickerOpened(false);\n  }, [setIsColorPickerOpened]);\n\n  return (\n    <StyledConfigSection>\n      <SectionTitle>{label}</SectionTitle>\n      <StyledDropdownButtonWrapper>\n        <Button onClick={toggleDropdown}>\n          <div style={colorBlockStyle} />\n          <Icon />\n        </Button>\n      </StyledDropdownButtonWrapper>\n      <SingleColorPickerDropdown\n        isOpened={isColorPickerOpened}\n        onClose={closeDropdown}\n        selectedColor={hexColor}\n        onSelectColor={onSetColor}\n      />\n    </StyledConfigSection>\n  );\n};\n\nexport default CompactColorPicker;\n"
  },
  {
    "path": "src/components/src/effects/effect-configurator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport {injectIntl, IntlShape} from 'react-intl';\n\nimport {LIGHT_AND_SHADOW_EFFECT} from '@kepler.gl/constants';\nimport {isNumber} from '@kepler.gl/utils';\nimport {Effect, EffectUpdateProps} from '@kepler.gl/types';\n\nimport RangeSliderFactory from '../common/range-slider';\nimport {ArrowDownSmall} from '../common/icons';\nimport EffectTimeConfiguratorFactory from './effect-time-configurator';\nimport CompactColorPicker from './compact-color-picker';\n\nexport type EffectConfiguratorProps = {\n  effect: Effect;\n  updateEffectConfig: (\n    e: Event | null | undefined,\n    id: string,\n    config: Partial<EffectUpdateProps>\n  ) => void;\n};\n\nconst StyledEffectConfigurator = styled.div.attrs({\n  className: 'effect-panel__config'\n})`\n  position: relative;\n  margin: ${props => props.theme.effectConfiguratorMargin};\n  padding: ${props => props.theme.effectConfiguratorPadding};\n`;\n\nexport const PanelLabelWrapper = styled.div.attrs({\n  className: 'side-panel-panel__label-wrapper'\n})`\n  display: flex;\n  align-items: self-start;\n  margin-bottom: 11px;\n\n  .side-panel-panel__label {\n    margin-top: 2px;\n    margin-bottom: 0px;\n  }\n`;\n\nexport const StyledColorSelectorWrapper = styled.div`\n  margin-right: 5px;\n  margin-left: 5px;\n  margin-bottom: 6px;\n  margin-top: 2px;\n`;\n\nconst StyledVerticalSeparator = styled.div`\n  height: 1px;\n  background-color: ${props => props.theme.inputBgd};\n  margin-top: 20px;\n  margin-bottom: 20px;\n  margin-left: -20px;\n`;\n\ntype StyledWrapperProps = {\n  marginBottom?: number;\n};\nconst StyledWrapper = styled.div<StyledWrapperProps>`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: ${props => props.marginBottom ?? 9}px;\n`;\n\nconst StyledConfigSection = styled.div`\n  display: flex;\n  flex-direction: column;\n`;\n\nconst SectionTitle = styled.div`\n  font-size: ${props => props.theme.inputFontSize};\n  color: ${props => props.theme.effectPanelTextSecondary1};\n  margin-bottom: 5px;\n  text-transform: capitalize;\n`;\n\nconst SectionSubTitle = styled.div`\n  font-size: ${props => props.theme.inputFontSize};\n  color: ${props => props.theme.effectPanelTextSecondary2};\n  margin-bottom: 8px;\n  margin-left: 6px;\n`;\n\nconst StyleSliderWrapper = styled.div`\n  align-self: flex-start;\n  width: 199px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  .kg-range-slider__input {\n    height: 32px;\n    text-align: center;\n    padding: 3px 6px;\n  }\n  .kg-slider {\n    padding-left: 6px;\n  }\n  .kg-range-slider {\n    padding: 0px !important;\n  }\n`;\n\nconst RegularOuterWrapper = styled.div.attrs({\n  className: 'effect-configurator__pp-section'\n})`\n  margin-bottom: 8px;\n`;\n\nconst RegularSectionTitleWrapper = styled.div.attrs({\n  className: 'effect-configurator__pp-section-title'\n})`\n  font-size: ${props => props.theme.inputFontSize};\n  color: ${props => props.theme.effectPanelTextSecondary1};\n  text-transform: capitalize;\n  margin-bottom: -3px;\n`;\n\nconst RegularSliderWrapper = styled.div.attrs({\n  className: 'effect-configurator__pp-section-control'\n})`\n  height: 32px;\n  .kg-range-slider__input {\n    height: 32px;\n    text-align: center;\n    padding: 3px 6px;\n  }\n  .kg-slider {\n    padding-left: 6px;\n  }\n  .kg-range-slider {\n    padding: 0px !important;\n  }\n`;\n\nconst COMMON_SLIDER_PROPS = {\n  showInput: true,\n  isRanged: false,\n  step: 0.001,\n  label: 'value'\n};\n\ntype EffectParameterDescriptionFlattened = {\n  name: string;\n  label?: string | false | (string | false)[];\n  min: number;\n  max: number;\n  index?: number;\n};\n\nEffectConfiguratorFactory.deps = [RangeSliderFactory, EffectTimeConfiguratorFactory];\n\nexport default function EffectConfiguratorFactory(\n  RangeSlider: ReturnType<typeof RangeSliderFactory>,\n  EffectTimeConfigurator: ReturnType<typeof EffectTimeConfiguratorFactory>\n): React.FC<EffectConfiguratorProps> {\n  const ShadowEffectConfigurator: React.FC<EffectConfiguratorProps> = ({\n    effect,\n    updateEffectConfig\n  }) => {\n    const {parameters, id} = effect;\n\n    const sliderProps = useMemo(() => {\n      const propNames = ['shadowIntensity', 'ambientLightIntensity', 'sunLightIntensity'];\n      return propNames.map(propName => {\n        return {\n          value1: parameters[propName],\n          range: [0, 1],\n          value0: 0,\n          onChange: (value: number[], event?: Event | null) => {\n            updateEffectConfig(event, id, {parameters: {[propName]: value[1]}});\n          }\n        };\n      });\n    }, [id, parameters, updateEffectConfig]);\n\n    const onTimeParametersChanged = useCallback(\n      parameters => {\n        updateEffectConfig(null, id, {\n          parameters: {\n            ...(parameters.timestamp ? {timestamp: parameters.timestamp} : null),\n            ...(parameters.timezone ? {timezone: parameters.timezone} : null),\n            ...(parameters.timeMode ? {timeMode: parameters.timeMode} : null)\n          }\n        });\n      },\n      [id, updateEffectConfig]\n    );\n\n    const colorPickerProps = useMemo(() => {\n      const propNames = ['ambientLightColor', 'sunLightColor', 'shadowColor'];\n      return propNames.map(propName => {\n        return {\n          colorSets: [\n            {\n              selectedColor: parameters[propName],\n              setColor: v => updateEffectConfig(null, id, {parameters: {[propName]: v}})\n            }\n          ]\n        };\n      });\n    }, [id, parameters, updateEffectConfig]);\n\n    return (\n      <StyledEffectConfigurator key={effect.id}>\n        <PanelLabelWrapper>\n          <SectionTitle>{'Date & Time'}</SectionTitle>\n        </PanelLabelWrapper>\n        <EffectTimeConfigurator\n          timestamp={parameters.timestamp}\n          timezone={parameters.timezone}\n          timeMode={parameters.timeMode}\n          onChange={onTimeParametersChanged}\n        />\n\n        <StyledVerticalSeparator />\n\n        <StyledWrapper marginBottom={0}>\n          <SectionTitle>{'Shadow'}</SectionTitle>\n        </StyledWrapper>\n        <StyledWrapper marginBottom={16}>\n          <CompactColorPicker\n            label={'Color'}\n            color={colorPickerProps[2].colorSets[0].selectedColor}\n            onSetColor={colorPickerProps[2].colorSets[0].setColor}\n            Icon={ArrowDownSmall}\n          />\n          <StyledConfigSection>\n            <SectionSubTitle>Intensity</SectionSubTitle>\n            <StyleSliderWrapper>\n              <RangeSlider {...COMMON_SLIDER_PROPS} {...sliderProps[0]} />\n            </StyleSliderWrapper>\n          </StyledConfigSection>\n        </StyledWrapper>\n\n        <StyledWrapper marginBottom={0}>\n          <SectionTitle>{'Ambient light'}</SectionTitle>\n        </StyledWrapper>\n        <StyledWrapper marginBottom={16}>\n          <CompactColorPicker\n            label={'Color'}\n            color={colorPickerProps[0].colorSets[0].selectedColor}\n            onSetColor={colorPickerProps[0].colorSets[0].setColor}\n            Icon={ArrowDownSmall}\n          />\n          <StyledConfigSection>\n            <SectionSubTitle>Intensity</SectionSubTitle>\n            <StyleSliderWrapper>\n              <RangeSlider {...COMMON_SLIDER_PROPS} {...sliderProps[1]} />\n            </StyleSliderWrapper>\n          </StyledConfigSection>\n        </StyledWrapper>\n\n        <StyledWrapper marginBottom={0}>\n          <SectionTitle>{'Sun light'}</SectionTitle>\n        </StyledWrapper>\n        <StyledWrapper marginBottom={0}>\n          <CompactColorPicker\n            label={'Color'}\n            color={colorPickerProps[1].colorSets[0].selectedColor}\n            onSetColor={colorPickerProps[1].colorSets[0].setColor}\n            Icon={ArrowDownSmall}\n          />\n          <StyledConfigSection>\n            <SectionSubTitle>Intensity</SectionSubTitle>\n            <StyleSliderWrapper>\n              <RangeSlider {...COMMON_SLIDER_PROPS} {...sliderProps[2]} />\n            </StyleSliderWrapper>\n          </StyledConfigSection>\n        </StyledWrapper>\n      </StyledEffectConfigurator>\n    );\n  };\n  const defaultUniforms = {};\n\n  const PostProcessingEffectConfigurator: React.FC<EffectConfiguratorProps> = ({\n    effect,\n    updateEffectConfig\n  }) => {\n    const uniforms = effect.deckEffect?.module.uniforms || defaultUniforms;\n    const parameterDescriptions = effect.getParameterDescriptions();\n    const {parameters, id} = effect;\n    const flatParameterDescriptions = useMemo(() => {\n      return parameterDescriptions.reduce((acc, description) => {\n        if (description.type === 'array') {\n          // split arrays of controls into a separate controls for each component\n          if (Array.isArray(description.defaultValue)) {\n            description.defaultValue.forEach((_, index) => {\n              acc.push({\n                ...description,\n                index,\n                label: description.label?.[index]\n              });\n            });\n          }\n        } else {\n          acc.push(description);\n        }\n\n        return acc;\n      }, [] as EffectParameterDescriptionFlattened[]);\n    }, [parameterDescriptions]);\n\n    const controls = useMemo(() => {\n      return flatParameterDescriptions.map(desc => {\n        const paramName = desc.name;\n\n        const uniform = uniforms[desc.name];\n        if ((!uniform && uniform !== 0) || uniform.private) {\n          return null;\n        }\n\n        const prevValue = parameters[paramName];\n\n        const label = desc.label === false ? false : desc.label || desc.name;\n\n        // the uniform is [number, number] array\n        if (uniform.length === 2) {\n          return {\n            label,\n            value1: prevValue[desc.index || 0] || 0,\n            range: [0, 1],\n            value0: 0,\n            onChange: (newValue: number[], event) => {\n              updateEffectConfig(event, id, {\n                parameters: {\n                  [paramName]:\n                    desc.index === 0 ? [newValue[1], prevValue[1]] : [prevValue[0], newValue[1]]\n                }\n              });\n            }\n          };\n        }\n        // the uniform is a plain number without any description\n        else if (isNumber(uniform)) {\n          return {\n            label,\n            value1: prevValue ?? 0,\n            range: [desc.min ?? 0, desc.max ?? 500],\n            value0: desc.min ?? 0,\n            onChange: (newValue: number[], event) => {\n              updateEffectConfig(event, id, {parameters: {[paramName]: newValue[1]}});\n            }\n          };\n        }\n        // the uniform description is {value: 0, min: 0, max: 1, ...}\n        else if (isNumber(uniform.value)) {\n          return {\n            label,\n            value1: prevValue || 0,\n            range: [\n              desc.min ?? uniform.min ?? uniform.softMin ?? 0,\n              desc.max ?? uniform.max ?? uniform.softMax ?? 1\n            ],\n            value0: desc.min ?? uniform.min ?? uniform.softMin ?? 0,\n            onChange: (newValue: number[], event) => {\n              updateEffectConfig(event, id, {parameters: {[paramName]: newValue[1]}});\n            }\n          };\n        }\n\n        // ignore everything else for now\n        return null;\n      });\n    }, [flatParameterDescriptions, id, parameters, updateEffectConfig, uniforms]);\n\n    return (\n      <StyledEffectConfigurator key={effect.id}>\n        {flatParameterDescriptions.map((desc, parameterIndex) => {\n          const control = controls[parameterIndex];\n          if (!control) {\n            return null;\n          }\n\n          return (\n            <RegularOuterWrapper key={`${effect.id}-${parameterIndex}`}>\n              {control.label ? (\n                <RegularSectionTitleWrapper>{control.label}</RegularSectionTitleWrapper>\n              ) : null}\n              <RegularSliderWrapper>\n                <RangeSlider key={parameterIndex} {...COMMON_SLIDER_PROPS} {...control} />\n              </RegularSliderWrapper>\n            </RegularOuterWrapper>\n          );\n        })}\n      </StyledEffectConfigurator>\n    );\n  };\n\n  const EffectConfigurator = ({\n    effect,\n    updateEffectConfig\n  }: EffectConfiguratorProps & {intl: IntlShape}) => {\n    return effect.type === LIGHT_AND_SHADOW_EFFECT.type ? (\n      <ShadowEffectConfigurator effect={effect} updateEffectConfig={updateEffectConfig} />\n    ) : (\n      <PostProcessingEffectConfigurator effect={effect} updateEffectConfig={updateEffectConfig} />\n    );\n  };\n\n  return injectIntl(EffectConfigurator);\n}\n"
  },
  {
    "path": "src/components/src/effects/effect-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport {CSS} from '@dnd-kit/utilities';\nimport {useSortable, SortableContext, verticalListSortingStrategy} from '@dnd-kit/sortable';\n\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {findById} from '@kepler.gl/utils';\nimport {Effect} from '@kepler.gl/types';\nimport {\n  addEffect,\n  updateEffect,\n  removeEffect,\n  reorderEffect,\n  ActionHandler\n} from '@kepler.gl/actions';\n\nimport {SORTABLE_EFFECT_TYPE, SORTABLE_EFFECT_PANEL_TYPE} from '../common/dnd-layer-items';\nimport EffectPanelFactory from './effect-panel';\n\nexport type EffectListProps = {\n  effects: Effect[];\n  effectOrder: string[];\n  visStateActions: {\n    addEffect: ActionHandler<typeof addEffect>;\n    updateEffect: ActionHandler<typeof updateEffect>;\n    removeEffect: ActionHandler<typeof removeEffect>;\n    reorderEffect: ActionHandler<typeof reorderEffect>;\n  };\n  isSortable: boolean;\n};\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n`;\n\ntype SortableStyledItemProps = {transition?: string; transform?: string};\nconst SortableStyledItem = styled.div<SortableStyledItemProps>`\n  z-index: ${props => props.theme.dropdownWrapperZ + 1};\n  transition: ${props => props.transition};\n  transform: ${props => props.transform};\n  outline: none;\n  &.sorting {\n    opacity: 0.3;\n    pointer-events: none;\n  }\n  &.sorting-effects .effect-panel__header {\n    background-color: ${props => props.theme.panelBackgroundHover};\n    font-family: ${props => props.theme.fontFamily};\n    font-weight: ${props => props.theme.fontWeight};\n    font-size: ${props => props.theme.fontSize};\n    line-height: ${props => props.theme.lineHeight};\n    *,\n    *:before,\n    *:after {\n      box-sizing: border-box;\n    }\n    .effect__drag-handle {\n      opacity: 1;\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nEffectListFactory.deps = [EffectPanelFactory];\n\nfunction EffectListFactory(\n  EffectPanel: ReturnType<typeof EffectPanelFactory>\n): React.FC<EffectListProps> {\n  const SortableItem = ({effect, idx, panelProps, disabled}) => {\n    const {attributes, listeners, setNodeRef, isDragging, transform, transition} = useSortable({\n      id: effect.id,\n      data: {\n        type: SORTABLE_EFFECT_TYPE,\n        parent: SORTABLE_EFFECT_PANEL_TYPE\n      },\n      disabled\n    });\n\n    return (\n      <SortableStyledItem\n        ref={setNodeRef}\n        className={classnames(\n          {[dataTestIds.sortableEffectItem]: !disabled},\n          {[dataTestIds.staticEffectItem]: disabled},\n          {sorting: isDragging}\n        )}\n        data-testid={disabled ? dataTestIds.staticEffectItem : dataTestIds.sortableEffectItem}\n        transform={CSS.Transform.toString(transform)}\n        transition={transition || ''}\n        {...attributes}\n      >\n        <EffectPanel\n          {...panelProps}\n          key={effect.id}\n          idx={idx}\n          effect={effect}\n          listeners={listeners}\n          isDraggable={!disabled}\n        />\n      </SortableStyledItem>\n    );\n  };\n\n  const EffectList = (props: EffectListProps) => {\n    const {effects, effectOrder, visStateActions} = props;\n\n    const effectsToShow = useMemo(() => {\n      return effectOrder.reduce((acc, effectId) => {\n        const effect = findById(effectId)(effects.filter(Boolean));\n        if (!effect) {\n          return acc;\n        }\n        return [...acc, effect];\n      }, [] as Effect[]);\n    }, [effects, effectOrder]);\n\n    const sidePanelDndItems = useMemo(() => {\n      return effectsToShow.map(({id}) => id);\n    }, [effectsToShow]);\n\n    const panelProps = useMemo(\n      () => ({\n        effects,\n        effectOrder,\n        removeEffect: visStateActions.removeEffect,\n        updateEffect: visStateActions.updateEffect\n      }),\n      [effects, effectOrder, visStateActions]\n    );\n\n    return (\n      <Container>\n        <SortableContext\n          id={SORTABLE_EFFECT_PANEL_TYPE}\n          items={sidePanelDndItems}\n          strategy={verticalListSortingStrategy}\n          disabled={false}\n        >\n          {effectsToShow.map(effect => (\n            <SortableItem\n              key={effect.id}\n              effect={effect}\n              idx={effects.findIndex(l => l?.id === effect.id)}\n              panelProps={panelProps}\n              disabled={false}\n            />\n          ))}\n        </SortableContext>\n      </Container>\n    );\n  };\n  return EffectList;\n}\n\nexport default EffectListFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, useState, useCallback} from 'react';\nimport styled from 'styled-components';\nimport {injectIntl, IntlShape} from 'react-intl';\n\nimport {\n  addEffect,\n  updateEffect,\n  removeEffect,\n  reorderEffect,\n  ActionHandler\n} from '@kepler.gl/actions';\nimport {LIGHT_AND_SHADOW_EFFECT, EFFECT_DESCRIPTIONS} from '@kepler.gl/constants';\nimport {visStateLens} from '@kepler.gl/reducers';\nimport {Effect} from '@kepler.gl/types';\nimport {VisState} from '@kepler.gl/schemas';\n\nimport {withState} from '../injector';\nimport SidePanelTitleFactory from './side-panel-title';\nimport EffectListFactory from './effect-list';\nimport EffectTypeSelectorFactory, {EffectTypeSelectorProps} from './effect-type-selector';\n\nexport type EffectManagerState = {\n  visState: VisState;\n  visStateActions: {\n    addEffect: ActionHandler<typeof addEffect>;\n    updateEffect: ActionHandler<typeof updateEffect>;\n    removeEffect: ActionHandler<typeof removeEffect>;\n    reorderEffect: ActionHandler<typeof reorderEffect>;\n  };\n  effects: Effect[];\n  effectOrder: string[];\n  children: React.ReactNode;\n};\nexport type EffectManagerProps = EffectManagerWithIntlProp & EffectManagerState;\n\nexport type EffectManagerWithIntlProp = {intl: IntlShape};\n\nconst StyledEffectPanelContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  pointer-events: none !important; /* prevent padding from blocking input */\n  flex-grow: 1;\n  justify-content: space-between;\n  overflow: hidden;\n\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\n// top right position absolute\nconst StyledEffectPanel = styled.div`\n  top: 0;\n  background-color: ${props => props.theme.sidePanelBg};\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  overflow: hidden;\n`;\n\nconst StyledEffectPanelHeader = styled.div`\n  padding: ${({theme}) =>\n    `${theme.effectPanelPaddingTop}px ${theme.effectPanelPaddingSide}px 4px ${theme.effectPanelPaddingSide}px`};\n  border-bottom: 1px solid ${props => props.theme.borderColor};\n  min-width: ${({theme}) => theme.effectPanelWidth}px;\n`;\n\ntype StyledEffectPanelContentProps = {\n  extended?: boolean;\n};\nconst StyledEffectPanelContent = styled.div<StyledEffectPanelContentProps>`\n  ${props => props.theme.sidePanelScrollBar};\n  padding: ${props => (props.extended ? '32px' : '10px 0px 10px 0px')};\n  overflow: overlay;\n  display: flex;\n  flex-direction: column;\n`;\n\nEffectManagerFactory.deps = [EffectListFactory, SidePanelTitleFactory, EffectTypeSelectorFactory];\n\nfunction EffectManagerFactory(\n  EffectList: ReturnType<typeof EffectListFactory>,\n  SidePanelTitle: ReturnType<typeof SidePanelTitleFactory>,\n  EffectTypeSelector: ReturnType<typeof EffectTypeSelectorFactory>\n): React.FC<EffectManagerProps> {\n  const EffectManager = (props: EffectManagerWithIntlProp & EffectManagerState) => {\n    const {intl, visStateActions, visState, children} = props;\n    const {effects, effectOrder} = visState;\n    const {addEffect: visStateAddEffect} = visStateActions;\n    const [typeSelectorOpened, setTypeSelectorOpened] = useState(false);\n\n    // Prevent shadow effect from being added multiple times\n    const effectOptions: EffectTypeSelectorProps['options'] = useMemo(() => {\n      const hasShadow = effects.some(effect => {\n        return effect.type === LIGHT_AND_SHADOW_EFFECT.type;\n      });\n\n      return EFFECT_DESCRIPTIONS.map(desc => {\n        return {\n          ...desc,\n          disabled: Boolean(hasShadow && desc.type === LIGHT_AND_SHADOW_EFFECT.type)\n        };\n      });\n    }, [effects]);\n\n    const onAddEffect = useCallback(\n      type => {\n        visStateAddEffect({type});\n      },\n      [visStateAddEffect]\n    );\n\n    const onTypeSelectOpen = useCallback(() => {\n      setTypeSelectorOpened(true);\n    }, []);\n\n    const onTypeSelectClose = useCallback(() => {\n      setTypeSelectorOpened(false);\n    }, []);\n\n    return (\n      <StyledEffectPanelContainer className=\"effect-manager\">\n        <StyledEffectPanel>\n          <StyledEffectPanelHeader className=\"effect-panel-header\">\n            <SidePanelTitle\n              className=\"effect-manager-title\"\n              title={intl.formatMessage({id: 'effectManager.effects'})}\n            >\n              <EffectTypeSelector\n                options={effectOptions}\n                onSelect={onAddEffect}\n                onOpen={onTypeSelectOpen}\n                onBlur={onTypeSelectClose}\n              />\n            </SidePanelTitle>\n          </StyledEffectPanelHeader>\n\n          <StyledEffectPanelContent extended={typeSelectorOpened && effects.length === 0}>\n            <EffectList\n              effects={effects}\n              effectOrder={effectOrder}\n              visStateActions={visStateActions}\n              isSortable={true}\n            />\n          </StyledEffectPanelContent>\n        </StyledEffectPanel>\n        {children}\n      </StyledEffectPanelContainer>\n    );\n  };\n\n  return withState([visStateLens], state => state, {\n    visStateActions: {addEffect, updateEffect, removeEffect, reorderEffect}\n  })(injectIntl(EffectManager)) as React.FC<EffectManagerProps>;\n}\n\nexport default EffectManagerFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-panel-header.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\n\nimport {\n  EFFECT_DESCRIPTIONS,\n  LIGHT_AND_SHADOW_EFFECT,\n  POSTPROCESSING_EFFECTS\n} from '@kepler.gl/constants';\n\nimport PanelHeaderActionFactory, {PanelHeaderActionIcon} from '../side-panel/panel-header-action';\nimport {\n  ArrowDown,\n  ArrowUp,\n  EyeSeen,\n  EyeUnseen,\n  Trash,\n  VertDots,\n  LightAndShadowEffectIcon,\n  InkEffectIcon,\n  BrightnessContrastEffectIcon,\n  HueSaturationEffectIcon,\n  VibranceEffectIcon,\n  SepiaEffectIcon,\n  DotScreenEffectIcon,\n  ColorHalftoneEffectIcon,\n  NoiseEffectIcon,\n  TriangleBlurEffectIcon,\n  ZoomBlurEffectIcon,\n  TiltShiftEffectIcon,\n  EdgeWorkEffectIcon,\n  VignetteEffectIcon,\n  MagnifyEffectIcon,\n  HexagonalPixelateEffectIcon,\n  BaseProps\n} from '../common/icons';\nimport {StyledPanelHeader} from '../common/styled-components';\n\nexport type ActionItem = {\n  key: string;\n  isHidden?: boolean;\n  tooltip: string;\n  classNames?: Record<string, boolean>;\n  icon: PanelHeaderActionIcon;\n  tooltipType?: 'error';\n  onClick: () => void;\n};\n\nexport type EffectPanelHeaderProps = {\n  type: string;\n  listeners: any;\n  effectId: string;\n  isEnabled: boolean;\n  isConfigActive: boolean;\n  isJsonEditorActive: boolean;\n  showSortHandle?: boolean;\n  isDragNDropEnabled: boolean;\n  onToggleEnabled: () => void;\n  onRemoveEffect: () => void;\n  onToggleEnableConfig: () => void;\n  actionIcons?: {\n    remove: React.ComponentType<Partial<BaseProps>>;\n    visible: React.ComponentType<Partial<BaseProps>>;\n    hidden: React.ComponentType<Partial<BaseProps>>;\n    enableConfig: React.ComponentType<Partial<BaseProps>>;\n    disableConfig: React.ComponentType<Partial<BaseProps>>;\n  };\n  actionItems?: ActionItem[];\n};\n\nexport const defaultProps = {\n  isDragNDropEnabled: true\n};\n\nconst defaultActionIcons = {\n  remove: Trash,\n  visible: EyeSeen,\n  hidden: EyeUnseen,\n  enableConfig: ArrowDown,\n  disableConfig: ArrowUp\n};\n\nconst defaultEffectIcons = {\n  [LIGHT_AND_SHADOW_EFFECT.type]: LightAndShadowEffectIcon,\n  [POSTPROCESSING_EFFECTS.ink.type]: InkEffectIcon,\n  [POSTPROCESSING_EFFECTS.brightnessContrast.type]: BrightnessContrastEffectIcon,\n  [POSTPROCESSING_EFFECTS.hueSaturation.type]: HueSaturationEffectIcon,\n  [POSTPROCESSING_EFFECTS.vibrance.type]: VibranceEffectIcon,\n  [POSTPROCESSING_EFFECTS.sepia.type]: SepiaEffectIcon,\n  [POSTPROCESSING_EFFECTS.dotScreen.type]: DotScreenEffectIcon,\n  [POSTPROCESSING_EFFECTS.colorHalftone.type]: ColorHalftoneEffectIcon,\n  [POSTPROCESSING_EFFECTS.noise.type]: NoiseEffectIcon,\n  [POSTPROCESSING_EFFECTS.triangleBlur.type]: TriangleBlurEffectIcon,\n  [POSTPROCESSING_EFFECTS.zoomBlur.type]: ZoomBlurEffectIcon,\n  [POSTPROCESSING_EFFECTS.tiltShift.type]: TiltShiftEffectIcon,\n  [POSTPROCESSING_EFFECTS.edgeWork.type]: EdgeWorkEffectIcon,\n  [POSTPROCESSING_EFFECTS.vignette.type]: VignetteEffectIcon,\n  [POSTPROCESSING_EFFECTS.magnify.type]: MagnifyEffectIcon,\n  [POSTPROCESSING_EFFECTS.hexagonalPixelate.type]: HexagonalPixelateEffectIcon\n};\n\nconst StyledEffectPanelHeader = styled(StyledPanelHeader)`\n  height: ${props => props.theme.effectPanelHeaderHeight}px;\n  position: relative;\n  align-items: stretch;\n\n  .effect__drag-handle {\n    margin-left: -5px;\n    color: ${props => props.theme.textColor};\n  }\n\n  .effect__drag-handle__placeholder {\n    height: 20px;\n    padding: 0px;\n    margin: 10px 10px 10px 5px;\n  }\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.panelBackgroundHover};\n    .effect__drag-handle {\n      opacity: 1;\n    }\n  }\n\n  border-left: 3px solid ${props => props.theme.panelBackgroundHover};\n`;\n\nconst HeaderActionSection = styled.div`\n  display: flex;\n  position: absolute;\n  height: 100%;\n  align-items: stretch;\n  right: 10px;\n  &:hover {\n    .effect-panel__header__actions__hidden {\n      opacity: 1;\n      background-color: ${props => props.theme.panelBackgroundHover};\n    }\n  }\n`;\n\n// Hiden actions only show up on hover\ntype StyledPanelHeaderHiddenActionsProps = {isConfigActive: boolean};\nconst StyledPanelHeaderHiddenActions = styled.div.attrs({\n  className: 'effect-panel__header__actions__hidden'\n})<StyledPanelHeaderHiddenActionsProps>`\n  opacity: 0;\n  display: flex;\n  align-items: center;\n  transition: opacity 0.4s ease, background-color 0.4s ease;\n  background-color: ${props =>\n    props.isConfigActive ? props.theme.panelBackgroundHover : props.theme.panelBackground};\n\n  &:hover {\n    opacity: 1;\n  }\n`;\n\nconst StyledDragHandle = styled.div`\n  display: flex;\n  align-items: center;\n  opacity: 0;\n  z-index: 1000;\n  &:hover {\n    cursor: move;\n    opacity: 1;\n    color: ${props => props.theme.textColorHl};\n  }\n`;\n\nconst DragHandle = ({className, listeners, children}) => (\n  <StyledDragHandle className={className} {...listeners}>\n    {children}\n  </StyledDragHandle>\n);\n\nEffectPanelHeaderActionSectionFactory.deps = [PanelHeaderActionFactory];\n\nexport function EffectPanelHeaderActionSectionFactory(\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n): React.FC<EffectPanelHeaderProps> {\n  const EffectPanelHeaderActionSection = (props: EffectPanelHeaderProps) => {\n    const {\n      effectId,\n      isEnabled,\n      isConfigActive,\n      onToggleEnabled,\n      onRemoveEffect,\n      onToggleEnableConfig,\n      actionItems,\n      actionIcons = defaultActionIcons\n    } = props;\n\n    const effectActionItems: ActionItem[] = useMemo(\n      () =>\n        actionItems ?? [\n          {\n            key: 'remove-effect',\n            isHidden: true,\n            tooltip: 'tooltip.removeEffect',\n            tooltipType: 'error',\n            onClick: onRemoveEffect,\n            icon: actionIcons.remove\n          },\n          {\n            key: 'visibility-toggle',\n            tooltip: isEnabled ? 'tooltip.disableEffect' : 'tooltip.enabledEffect',\n            onClick: onToggleEnabled,\n            icon: isEnabled ? actionIcons.visible : actionIcons.hidden\n          },\n          {\n            key: 'enable-config',\n            classNames: {'is-open': isConfigActive},\n            tooltip: 'tooltip.effectSettings',\n            onClick: onToggleEnableConfig,\n            icon: actionIcons.enableConfig\n          }\n        ],\n      [\n        actionItems,\n        actionIcons,\n        isEnabled,\n        isConfigActive,\n        onRemoveEffect,\n        onToggleEnabled,\n        onToggleEnableConfig\n      ]\n    );\n\n    return (\n      <HeaderActionSection className=\"effect-panel__header__actions\">\n        <StyledPanelHeaderHiddenActions isConfigActive={isConfigActive}>\n          {effectActionItems\n            .filter((item: ActionItem) => Boolean(item.isHidden))\n            .map((item: ActionItem) => (\n              <PanelHeaderAction\n                key={item.key}\n                className={`effect__${item.key}`}\n                testId={`${item.key}-action`}\n                id={effectId}\n                tooltip={item.tooltip}\n                onClick={item.onClick}\n                tooltipType={item.tooltipType}\n                IconComponent={item.icon}\n              />\n            ))}\n        </StyledPanelHeaderHiddenActions>\n        {effectActionItems\n          .filter((item: ActionItem) => !item.isHidden)\n          .map((item: ActionItem) => (\n            <PanelHeaderAction\n              key={item.key}\n              className={classnames(`effect__${item.key}`, item.classNames)}\n              testId={`${item.key}-action`}\n              id={effectId}\n              tooltip={item.tooltip}\n              onClick={item.onClick}\n              tooltipType={item.tooltipType}\n              IconComponent={item.icon}\n            />\n          ))}\n      </HeaderActionSection>\n    );\n  };\n\n  return EffectPanelHeaderActionSection;\n}\n\nconst StyledEffectTitleSection = styled.div`\n  margin-left: 8px;\n  flex-grow: 1;\n  align-items: center;\n  display: flex;\n  color: ${props => props.theme.textColor};\n`;\n\nconst IconPlaceholder = styled.div`\n  width: 20px;\n  height: 20px;\n`;\n\nconst EffectIconWrapper = styled.div`\n  height: 18px;\n  margin: auto;\n  color: ${props => props.theme.textColor};\n`;\n\nEffectPanelHeaderFactory.deps = [EffectPanelHeaderActionSectionFactory];\n\nfunction EffectPanelHeaderFactory(\n  EffectPanelHeaderActionSection: ReturnType<typeof EffectPanelHeaderActionSectionFactory>\n): React.FC<EffectPanelHeaderProps> {\n  const EffectPanelHeader = (props: EffectPanelHeaderProps) => {\n    const {\n      isConfigActive,\n      isDragNDropEnabled = true,\n      type,\n      onToggleEnableConfig,\n      listeners,\n      showSortHandle = true\n    } = props;\n\n    const label = useMemo(() => {\n      const description = EFFECT_DESCRIPTIONS.find(_description => _description.type === type);\n      return description?.name || 'Effect';\n    }, [type]);\n\n    const EffectIcon = defaultEffectIcons[type];\n\n    return (\n      <StyledEffectPanelHeader\n        className={classnames('effect-panel__header', {\n          'sort--handle': !isConfigActive\n        })}\n        active={isConfigActive}\n        onClick={onToggleEnableConfig}\n      >\n        {isDragNDropEnabled ? (\n          <DragHandle className=\"effect__drag-handle\" listeners={listeners}>\n            {showSortHandle ? <VertDots height=\"20px\" /> : <IconPlaceholder />}\n          </DragHandle>\n        ) : (\n          <div className=\"effect__drag-handle__placeholder\" />\n        )}\n\n        <EffectIconWrapper>{EffectIcon ? <EffectIcon height=\"18px\" /> : null}</EffectIconWrapper>\n\n        <StyledEffectTitleSection>{label}</StyledEffectTitleSection>\n\n        <EffectPanelHeaderActionSection {...props} />\n      </StyledEffectPanelHeader>\n    );\n  };\n\n  return EffectPanelHeader;\n}\n\nexport default EffectPanelHeaderFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, ComponentType} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport PropTypes from 'prop-types';\n\nimport {dataTestIds, LIGHT_AND_SHADOW_EFFECT} from '@kepler.gl/constants';\nimport {removeEffect, updateEffect} from '@kepler.gl/actions';\nimport {Effect} from '@kepler.gl/types';\n\nimport EffectPanelHeaderFactory from './effect-panel-header';\nimport EffectConfiguratorFactory from './effect-configurator';\n\nexport type EffectPanelProps = {\n  className: string;\n  effect: Effect;\n  isDraggable: boolean;\n  listeners: any;\n  removeEffect: typeof removeEffect;\n  updateEffect: typeof updateEffect;\n\n  style?: React.CSSProperties;\n  onMouseDown: React.MouseEventHandler<HTMLDivElement>;\n  onTouchStart: React.TouchEventHandler<HTMLDivElement>;\n};\n\nexport type PanelWrapperProps = {\n  active: boolean;\n};\n\nconst PanelWrapper = styled.div<PanelWrapperProps>`\n  font-size: 12px;\n  border-radius: 1px;\n  z-index: 1000;\n  &.dragging {\n    cursor: move;\n  }\n  margin: 3px auto 3px 25px;\n  max-width: 295px;\n`;\n\nEffectPanelFactory.deps = [EffectPanelHeaderFactory, EffectConfiguratorFactory];\n\nfunction EffectPanelFactory(\n  EffectPanelHeader: ReturnType<typeof EffectPanelHeaderFactory>,\n  EffectConfigurator: ReturnType<typeof EffectConfiguratorFactory>\n): ComponentType<EffectPanelProps> {\n  class EffectPanel extends Component<EffectPanelProps> {\n    static propTypes = {\n      effect: PropTypes.object.isRequired,\n      removeEffect: PropTypes.func.isRequired,\n      updateEffect: PropTypes.func.isRequired,\n      isDraggable: PropTypes.bool\n    };\n\n    _toggleEnabled = (event?: Event) => {\n      event?.stopPropagation();\n      this.props.updateEffect(this.props.effect.id, {\n        isEnabled: !this.props.effect.isEnabled\n      });\n    };\n\n    _toggleConfigActive = (event?: Event) => {\n      event?.stopPropagation();\n      this.props.updateEffect(this.props.effect.id, {\n        isConfigActive: !this.props.effect.isConfigActive\n      });\n    };\n\n    _removeEffect = (event?: Event) => {\n      event?.stopPropagation();\n      this.props.removeEffect(this.props.effect.id);\n    };\n\n    _updateEffectConfig = (event, id, props) => {\n      event?.stopPropagation();\n      this.props.updateEffect(id, props);\n    };\n\n    render() {\n      const {effect, isDraggable, listeners} = this.props;\n      const {id, type, isConfigActive, isJsonEditorActive, isEnabled} = effect;\n\n      const sortingAllowed = type !== LIGHT_AND_SHADOW_EFFECT.type;\n\n      return (\n        <PanelWrapper\n          active={false}\n          className={classnames('effect-panel', this.props.className)}\n          data-testid={dataTestIds.effectPanel}\n          style={this.props.style}\n          onMouseDown={this.props.onMouseDown}\n          onTouchStart={this.props.onTouchStart}\n        >\n          <EffectPanelHeader\n            isConfigActive={isConfigActive}\n            effectId={id}\n            type={type}\n            isEnabled={isEnabled}\n            isJsonEditorActive={isJsonEditorActive}\n            onToggleEnabled={this._toggleEnabled}\n            onRemoveEffect={this._removeEffect}\n            onToggleEnableConfig={this._toggleConfigActive}\n            isDragNDropEnabled={isDraggable && sortingAllowed}\n            listeners={listeners}\n            showSortHandle={type !== LIGHT_AND_SHADOW_EFFECT.type}\n          />\n          {isConfigActive && (\n            <EffectConfigurator\n              key={`effect-configurator-${id}`}\n              effect={effect}\n              updateEffectConfig={this._updateEffectConfig}\n            />\n          )}\n        </PanelWrapper>\n      );\n    }\n  }\n\n  // @ts-expect-error fix The types of 'propTypes.isDraggable[nominalTypeHack]' are incompatible between these types.\n  return EffectPanel;\n}\n\nexport default EffectPanelFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-time-configurator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport styled from 'styled-components';\nimport moment from 'moment-timezone';\nimport SunCalc from 'suncalc';\n\nimport {MapState} from '@kepler.gl/types';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {clamp} from '@kepler.gl/utils';\nimport {\n  LIGHT_AND_SHADOW_EFFECT_TIME_MODES,\n  LightAndShadowEffectTimeMode,\n  DEFAULT_TIMEZONE\n} from '@kepler.gl/constants';\nimport {mapStateLens} from '@kepler.gl/reducers';\n\nimport {withState} from '../injector';\nimport {StyledDatePicker as DatePicker, Tooltip} from '../common/styled-components';\nimport Checkbox from '../common/checkbox';\nimport Button from '../common/data-table/button';\nimport {LocationMarker, Calendar, Clock, Globe} from '../common/icons';\nimport TimezoneSelectorFactory from './timezone-selector';\nimport EffectTimeSliderFactory from './effect-time-slider';\nimport EffectTimeSelectorFactory from './effect-time-selector';\n\nconst DAY_MILISECONDS = 1000 * 60 * 60 * 24;\n\nexport type EffectTimeConfiguratorProps = {\n  timestamp: number;\n  timezone: string;\n  timeMode: LightAndShadowEffectTimeMode;\n  onChange: (parameters: {\n    timestamp?: number | null;\n    timezone?: string;\n    timeMode?: LightAndShadowEffectTimeMode;\n  }) => void;\n};\n\ntype StyledWrapperProps = {disabled?: boolean; marginBottom?: number};\nconst StyledWrapper = styled.div<StyledWrapperProps>`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: ${props => props.marginBottom ?? 9}px;\n  ${props => (props.hidden ? 'display: none;' : '')}\n`;\n\ntype SliderWrapperProps = {disabled?: boolean};\nconst SliderWrapper = styled.div<SliderWrapperProps>`\n  margin-top: 13px;\n  margin-bottom: 12px;\n  ${props => (props.hidden ? 'display: none;' : '')}\n`;\n\nconst StyledButton = styled(Button)`\n  color: ${props => props.theme.effectPanelTextSecondary2};\n  background-color: ${props => props.theme.inputBgd};\n  height: 32px;\n  width: 32px;\n  padding: 5px;\n  border-radius: 4px;\n  justify-content: center;\n  &:hover {\n    color: ${props => props.theme.effectPanelTextMain};\n    background-color: ${props => props.theme.inputBgdHover};\n  }\n`;\n\nconst StyledRadio = styled(Checkbox)`\n  .kg-checkbox__label {\n    font-family: ${props => props.theme.fontFamily};\n    font-size: ${props => props.theme.inputFontSize};\n  }\n  .kg-checkbox__label:before {\n    background: transparent;\n    border-color: ${props => props.theme.effectPanelTextSecondary2};\n  }\n  input:checked + .kg-checkbox__label:before {\n    border-color: ${props => props.theme.activeColor};\n  }\n  .kg-checkbox__label:after {\n    background-color: ${props => props.theme.activeColor};\n  }\n`;\n\nconst StyledEffectTimeConfigurator = styled.div`\n  margin-bottom: 8px;\n  margin-top: 3px;\n`;\n\nconst StyledDatePicker = styled.div`\n  .react-date-picker--open .react-date-picker__wrapper .react-date-picker__inputGroup {\n    border: 1px solid ${props => props.theme.activeColor};\n    border-radius: 4px 4px 0px 0px !important;\n  }\n  .react-calendar__navigation__prev2-button,\n  .react-calendar__navigation__next2-button {\n    display: none;\n  }\n  .react-calendar__navigation__label {\n    position: absolute;\n    top: 20px;\n  }\n  .react-calendar__navigation__arrow {\n    position: absolute;\n    top: 18px;\n    font-size: 16px;\n  }\n  .react-calendar__navigation__prev-button {\n    right: 36px;\n  }\n  .react-calendar__navigation__next-button {\n    right: 12px;\n  }\n`;\n\ntype WithIconWrapperProps = {width?: string};\nconst WithIconWrapper = styled.div<WithIconWrapperProps>`\n  position: relative;\n  ${props => (props.width ? `width: ${props.width}` : '')}\n`;\n\nconst StyledExtraIcon = styled.div`\n  position: absolute;\n  top: 0px;\n  left: 8px;\n  width: 0px;\n  height: 32px;\n  color: ${props => props.theme.effectPanelTextSecondary2};\n  pointer-events: none;\n`;\n\ntype TextBlockProps = {\n  width?: string;\n};\nconst TextBlock = styled.div<TextBlockProps>`\n  color: ${props => props.theme.effectPanelTextSecondary2};\n  width: ${props => props.width};\n  font-size: ${props => props.theme.inputFontSize};\n`;\n\n/**\n * Converts date, time and timezone into a UTC timestamp.\n * @param dateStr Date string in YYYY-MM-DD format.\n * @param timeStr Time string in HH:MM format.\n * @param timezone Timezone name.\n * @returns Timestamp or null if case of bad inputs.\n */\nconst getTimestamp = (dateStr: string, timeStr: string, timezone: string): number | null => {\n  let timestamp: number | null = null;\n  const curr = moment.tz(`${dateStr}T${timeStr}:00`, timezone);\n  if (curr.isValid()) {\n    timestamp = curr.utc().valueOf();\n  }\n  return timestamp;\n};\n\n/**\n * Converts time of the day into [0, 1] range\n * @param date\n * @returns\n */\nconst getDayRatio = (date: moment.Moment) => {\n  return ((date.hours() * 60 + date.minutes()) * 60 * 1000) / DAY_MILISECONDS;\n};\n\nEffectTimeConfiguratorFactory.deps = [\n  TimezoneSelectorFactory,\n  EffectTimeSliderFactory,\n  EffectTimeSelectorFactory\n];\n\nexport default function EffectTimeConfiguratorFactory(\n  TimezoneSelector: ReturnType<typeof TimezoneSelectorFactory>,\n  EffectTimeSlider: ReturnType<typeof EffectTimeSliderFactory>,\n  EffectTimeSelector: ReturnType<typeof EffectTimeSelectorFactory>\n): React.FC<EffectTimeConfiguratorProps> {\n  const EffectTimeConfigurator = ({\n    timestamp,\n    timezone: _timezone,\n    timeMode,\n    onChange: onTimeParametersChanged,\n    mapState,\n    intl\n  }: EffectTimeConfiguratorProps & {intl: IntlShape; mapState: MapState}) => {\n    const timezone = useMemo(() => {\n      return moment.tz.names().includes(_timezone) ? _timezone : DEFAULT_TIMEZONE;\n    }, [_timezone]);\n\n    const [datePickerDate, fullDate, formattedTime, formattedDate, dayTimeProgress] =\n      useMemo(() => {\n        const currentMoment = moment.tz(timestamp, timezone);\n\n        // Slider value from 0 to 1\n        const dayProgress = getDayRatio(currentMoment);\n\n        // Date picker always renders Date in local timezone\n        const date = new Date();\n        date.setFullYear(currentMoment.year(), currentMoment.month(), currentMoment.date());\n        date.setHours(0, 0, 0, 0);\n\n        return [\n          date,\n          currentMoment.toDate(),\n          currentMoment.format('HH:mm'),\n          currentMoment.format('YYYY-MM-DD'),\n          dayProgress\n        ];\n      }, [timestamp, timezone]);\n\n    const timeSliderConfig = useMemo(() => {\n      const times = SunCalc.getTimes(fullDate, mapState.latitude, mapState.longitude);\n      const {dawn, sunrise, sunset, dusk} = times;\n\n      return {\n        dawn: getDayRatio(moment.tz(dawn.valueOf(), timezone)),\n        sunrise: getDayRatio(moment.tz(sunrise.valueOf(), timezone)),\n        sunset: getDayRatio(moment.tz(sunset.valueOf(), timezone)),\n        dusk: getDayRatio(moment.tz(dusk.valueOf(), timezone)),\n        sunriseTime: moment.tz(sunrise.valueOf(), timezone).format('hh:mm A'),\n        sunsetTime: moment.tz(sunset.valueOf(), timezone).format('hh:mm A')\n      };\n    }, [fullDate, timezone, mapState.latitude, mapState.longitude]);\n\n    const onTimeSliderChange = useCallback(\n      value => {\n        const hours = clamp([0, 23], Math.floor(value[1] * 24));\n        const minutes = clamp([0, 59], Math.floor((value[1] * 24 - hours) * 60));\n\n        const newFormattedTime = `${hours < 10 ? `0${hours}` : hours}:${\n          minutes < 10 ? `0${minutes}` : minutes\n        }`;\n        const newTimestamp = getTimestamp(formattedDate, newFormattedTime, timezone);\n        onTimeParametersChanged({timestamp: newTimestamp});\n      },\n      [formattedDate, timezone, onTimeParametersChanged]\n    );\n\n    const setDate = useCallback(\n      newDate => {\n        if (!newDate) return;\n\n        const newFormattedDate = moment(newDate).format('YYYY-MM-DD');\n        const newTimestamp = getTimestamp(newFormattedDate, formattedTime, timezone);\n        onTimeParametersChanged({timestamp: newTimestamp});\n      },\n      [formattedTime, timezone, onTimeParametersChanged]\n    );\n\n    const setTime = useCallback(\n      newTime => {\n        if (!newTime) return;\n\n        const newTimestamp = getTimestamp(formattedDate, newTime, timezone);\n        onTimeParametersChanged({timestamp: newTimestamp});\n      },\n      [formattedDate, timezone, onTimeParametersChanged]\n    );\n\n    const setTimezone = useCallback(\n      newTimezone => {\n        if (!newTimezone) return;\n\n        const newTimestamp = getTimestamp(formattedDate, formattedTime, newTimezone);\n        // date and time are adjusted to have the same value but in the new timezone\n        onTimeParametersChanged({timestamp: newTimestamp, timezone: newTimezone});\n      },\n      [formattedDate, formattedTime, onTimeParametersChanged]\n    );\n\n    const setCurrentDateTime = useCallback(() => {\n      onTimeParametersChanged({timestamp: new Date().valueOf()});\n    }, [onTimeParametersChanged]);\n\n    const formatShortWeekday = useCallback((locale, date) => {\n      return ['S', 'M', 'T', 'W', 'T', 'F', 'S'][date.getDay()];\n    }, []);\n\n    const disableDateTimePick = timeMode !== LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick;\n\n    return (\n      <StyledEffectTimeConfigurator>\n        <StyledWrapper marginBottom={16}>\n          <StyledRadio\n            type=\"radio\"\n            checked={timeMode === LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick}\n            id={`effect-time-toggle-use-pick-time`}\n            label={intl.formatMessage({\n              id: 'effectManager.pickDateTime'\n            })}\n            onChange={() => {\n              onTimeParametersChanged({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick});\n            }}\n          />\n        </StyledWrapper>\n\n        <SliderWrapper hidden={disableDateTimePick}>\n          <EffectTimeSlider\n            value={dayTimeProgress}\n            onChange={onTimeSliderChange}\n            config={timeSliderConfig}\n          />\n        </SliderWrapper>\n\n        <StyledWrapper hidden={disableDateTimePick} marginBottom={2}>\n          <TextBlock width=\"32px\" />\n          <TextBlock width=\"110px\">\n            <FormattedMessage id={'effectManager.date'} />\n          </TextBlock>\n          <TextBlock width=\"110px\">\n            <FormattedMessage id={'effectManager.time'} />\n          </TextBlock>\n        </StyledWrapper>\n\n        <StyledWrapper hidden={disableDateTimePick} marginBottom={16}>\n          <StyledButton onClick={setCurrentDateTime} data-for=\"pick-time-button\" data-tip>\n            <LocationMarker height=\"16px\" />\n            <Tooltip id=\"pick-time-button\" effect=\"solid\" place=\"top\" delayShow={500}>\n              <FormattedMessage id={'effectManager.pickCurrrentTime'} />\n            </Tooltip>\n          </StyledButton>\n          <WithIconWrapper>\n            <StyledDatePicker>\n              <DatePicker\n                value={datePickerDate}\n                onChange={setDate}\n                minDetail={'month'}\n                formatShortWeekday={formatShortWeekday}\n              />\n            </StyledDatePicker>\n            <StyledExtraIcon>\n              <Calendar width=\"16px\" height=\"32px\" />\n            </StyledExtraIcon>\n          </WithIconWrapper>\n          <WithIconWrapper>\n            <EffectTimeSelector value={formattedTime} onChange={setTime} />\n            <StyledExtraIcon>\n              <Clock width=\"16px\" height=\"32px\" />\n            </StyledExtraIcon>\n          </WithIconWrapper>\n        </StyledWrapper>\n\n        <StyledWrapper hidden={disableDateTimePick} marginBottom={2}>\n          <TextBlock>\n            <FormattedMessage id={'effectManager.timezone'} />\n          </TextBlock>\n        </StyledWrapper>\n\n        <StyledWrapper hidden={disableDateTimePick} marginBottom={24}>\n          <WithIconWrapper width={'100%'}>\n            <TimezoneSelector selected={timezone} onSelect={setTimezone} />\n            <StyledExtraIcon>\n              <Globe width=\"16px\" height=\"32px\" />\n            </StyledExtraIcon>\n          </WithIconWrapper>\n        </StyledWrapper>\n\n        <StyledWrapper marginBottom={16}>\n          <StyledRadio\n            type=\"radio\"\n            checked={timeMode === LIGHT_AND_SHADOW_EFFECT_TIME_MODES.current}\n            id={`effect-time-toggle-use-current-time`}\n            label={intl.formatMessage({\n              id: 'effectManager.currentTime'\n            })}\n            onChange={() => {\n              onTimeParametersChanged({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.current});\n            }}\n          />\n        </StyledWrapper>\n\n        <StyledWrapper marginBottom={16}>\n          <StyledRadio\n            type=\"radio\"\n            checked={timeMode === LIGHT_AND_SHADOW_EFFECT_TIME_MODES.animation}\n            id={`effect-time-toggle-use-animation-time`}\n            label={'Animation time'}\n            onChange={() => {\n              onTimeParametersChanged({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.animation});\n            }}\n          />\n        </StyledWrapper>\n      </StyledEffectTimeConfigurator>\n    );\n  };\n\n  // @ts-expect-error how to properly type?\n  return withState([mapStateLens])(injectIntl(EffectTimeConfigurator));\n}\n"
  },
  {
    "path": "src/components/src/effects/effect-time-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, useCallback} from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport classNames from 'classnames';\nimport moment from 'moment-timezone';\n\nimport ItemSelector from '../common/item-selector/item-selector';\nimport {StyledTimePicker} from '../common/styled-components';\nimport {classList} from '../common/item-selector/dropdown-list';\n\nconst HEADER_ITEM_ID = 'HEADER_ITEM_ID';\n\nexport type EffectTimeSelectorProps = {\n  value: string;\n  onChange: (string) => void;\n  theme?: any;\n};\n\nconst StyledEffectTimeSelector = styled.div`\n  .item-selector {\n    background-color: transparent;\n  }\n  .item-selector .item-selector__dropdown {\n    padding: 0px;\n    width: 110px;\n    height: 30px;\n  }\n  border: 1px solid ${props => props.theme.inputBgd};\n  border-radius: 4px;\n  &:hover {\n    border: 1px solid ${props => props.theme.inputBgdHover};\n  }\n  .active {\n    border: 1px solid ${props => props.theme.activeColor};\n    border-radius: 4px 4px 0px 0px;\n  }\n`;\n\nconst StyledLabel = styled.div`\n  color: ${props => props.theme.effectPanelTextMain};\n  font-size: ${props => props.theme.inputFontSize};\n  height: 13px;\n  display: flex;\n  align-items: center;\n`;\n\nconst DropdownListWrapper = styled.div`\n  ${props => props.theme.dropdownList};\n  background-color: ${props => props.theme.inputBgdHover};\n  display: flex;\n  flex-wrap: wrap;\n  padding: 0px;\n  max-height: 160px;\n  border-radius: 0px 0px 4px 4px;\n`;\n\nconst StyledDropdownListItem = styled.div`\n  padding: 10px 5px 9px 10px;\n  width: 100%;\n  &:hover {\n    background-color: ${props => props.theme.effectPanelElementColorSelected};\n    cursor: pointer;\n  }\n`;\n\n// Generate time options for the dropdown with 30 min intervals.\nconst timeOptions = (() => {\n  const m = moment().utcOffset(0);\n  m.set({hour: 12, minute: 0, second: 0, millisecond: 0});\n\n  const out: {name: string; id: string}[] = [];\n  for (let i = 0; i < 48; i++) {\n    out.push({name: m.format('hh:mm A'), id: m.format('HH:mm')});\n    m.add(30, 'minutes');\n  }\n  return out;\n})();\n\nconst getDisplayOption = op => op.name;\nconst getOptionValue = op => op.id;\n\n/**\n * A component to render TimePicker as the header for the dropdown component.\n */\nconst EffectTimeListItem = ({value}) => {\n  if (value?.id === HEADER_ITEM_ID) {\n    return (\n      <StyledTimePicker\n        value={value.value}\n        onChange={value.onChange}\n        onClick={value.onClick}\n        disableClock={true}\n        format={'hh:mm a'}\n      />\n    );\n  }\n\n  return <StyledLabel>{value.name}</StyledLabel>;\n};\n\nconst EffectTimeDropdownList = ({\n  onOptionSelected,\n  options,\n  selectionIndex,\n  customListItemComponent\n}) => {\n  const onSelectOption = useCallback(\n    (e, value) => {\n      e.preventDefault();\n      onOptionSelected(value);\n    },\n    [onOptionSelected]\n  );\n\n  const ListItemComponent = customListItemComponent;\n\n  return (\n    <DropdownListWrapper className={classList.list}>\n      {options.map((value, i) => (\n        <StyledDropdownListItem\n          className={classNames('effect-type-selector__item', {\n            hover: selectionIndex === i\n          })}\n          key={`${value.id}_${i}`}\n          onMouseDown={e => onSelectOption(e, value)}\n          onClick={e => onSelectOption(e, value)}\n        >\n          <ListItemComponent value={value} />\n        </StyledDropdownListItem>\n      ))}\n    </DropdownListWrapper>\n  );\n};\n\nEffectTimeSelectorFactory.deps = [];\n\nfunction EffectTimeSelectorFactory() {\n  const EffectTimeSelector: React.FC<EffectTimeSelectorProps> = ({\n    value,\n    onChange\n  }: EffectTimeSelectorProps) => {\n    // Selected item is rendered as TimePicker in EffectTimeListItem\n    const selectedItems = useMemo(() => {\n      return [\n        {\n          id: HEADER_ITEM_ID,\n          value,\n          onChange,\n          onClick: e => {\n            // DatePicker is used as custom header.\n            // Don't open the dropdown when the user is editing time values directly.\n            const name = e?.target?.name;\n            if (name === 'hour12' || name === 'minute' || name === 'amPm') {\n              e.stopPropagation();\n            }\n          }\n        }\n      ];\n    }, [value, onChange]);\n\n    return (\n      <StyledEffectTimeSelector>\n        <ItemSelector\n          selectedItems={selectedItems}\n          options={timeOptions}\n          multiSelect={false}\n          searchable={false}\n          placeholder=\"\"\n          filterOption=\"name\"\n          onChange={onChange}\n          getOptionValue={getOptionValue}\n          displayOption={getDisplayOption}\n          DropDownLineItemRenderComponent={EffectTimeListItem}\n          DropDownRenderComponent={EffectTimeDropdownList}\n        />\n      </StyledEffectTimeSelector>\n    );\n  };\n\n  return withTheme(EffectTimeSelector) as React.FC<Omit<EffectTimeSelectorProps, 'theme'>>;\n}\n\nexport default EffectTimeSelectorFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-time-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport RangeSliderFactory from '../common/range-slider';\nimport {Sun, Moon, Sunset, Sunrise} from '../common/icons';\n\nexport type EffectTimeSliderConfig = {\n  dawn: number;\n  sunrise: number;\n  sunset: number;\n  dusk: number;\n  sunriseTime: string;\n  sunsetTime: string;\n};\n\nexport type UIBlock = {\n  type: string;\n  width: number;\n  center: number;\n  start: number;\n  end: number;\n  text?: string;\n  TopRowIcon?: React.ElementType<any>;\n  BottomRowIcon?: React.ElementType<any>;\n};\n\nexport type EffectTimeSliderProps = {\n  value: number;\n  onChange: (number) => void;\n  config: EffectTimeSliderConfig;\n};\n\nconst TimeSliderWrapper = styled.div`\n  width: 100%;\n  height: 48px;\n`;\n\nconst TopRowWrapper = styled.div`\n  width: 100%;\n  height: 32px;\n  display: flex;\n  position: relative;\n  align-items: center;\n\n  .day {\n    background-color: ${props => props.theme.effectPanelElementColorHovered};\n  }\n  .night {\n    background-color: ${props => props.theme.effectPanelElementColor};\n  }\n  .sunrise,\n  .sunset {\n    background-color: ${props => props.theme.effectPanelElementColorActive};\n  }\n  .inline_icon__day {\n    color: ${props => props.theme.effectPanelElementColorSun};\n  }\n  .inline_icon__night {\n    color: ${props => props.theme.effectPanelTextSecondary2};\n  }\n`;\n\nconst BottomRowWrapper = styled.div`\n  position: relative;\n  height: 16px;\n  .bottom_icon__sunrise,\n  .bottom_icon__sunset {\n    color: ${props => props.theme.effectPanelTextSecondary2};\n  }\n`;\n\ntype BackgroundBlockProps = {width: string};\nconst BackgroundBlock = styled.div<BackgroundBlockProps>`\n  margin: 0px;\n  padding: 0px;\n  width: ${props => props.width};\n  height: 24px;\n  pointer-events: none;\n`;\n\ntype StyledIconProps = {left: number};\nconst StyledIcon = styled.div<StyledIconProps>`\n  position: absolute;\n  top: 0px;\n  left: calc(${props => props.left}% - 8px);\n  height: 32px;\n  pointer-events: none;\n`;\n\ntype StyledBottomIconProps = {left: number};\nconst StyledBottomIcon = styled.div<StyledBottomIconProps>`\n  position: absolute;\n  top: 0px;\n  left: calc(${props => props.left}% - 27px);\n  height: 12px;\n  pointer-events: none;\n  margin-top: 1px;\n  display: flex;\n  align-items: flex-end;\n`;\n\nconst RangeSliderWrapper = styled.div`\n  position: absolute;\n  width: 100%;\n  .kg-slider {\n    padding-left: 6px;\n  }\n  .kg-range-slider {\n    padding: 0px !important;\n    background-color: transparent;\n  }\n  .kg-range-slider__bar {\n    background-color: transparent;\n  }\n  .kg-range-slider__handle {\n    height: 32px;\n    width: 8px;\n    margin-top: -14px;\n    align-items: center;\n    display: flex;\n    justify-content: center;\n  }\n  .kg-range-slider__handle::after {\n    margin-left: 1px;\n  }\n  .kg-range-slider__input {\n    height: 32px;\n    text-align: center;\n    padding: 3px 6px;\n  }\n`;\n\nconst Text = styled.div`\n  margin-left: 3px;\n  font-size: 10px;\n  line-height: 11px;\n  white-space: nowrap;\n`;\n\nconst MODES = {\n  sunrise: 'sunrise',\n  day: 'day',\n  sunset: 'sunset',\n  night: 'night'\n};\n\n/**\n * Generate rendering blocks for each part of the day.\n */\nexport function getUIBlocks(config: EffectTimeSliderConfig): UIBlock[] {\n  const {dawn} = config;\n  let {sunrise, sunset, dusk} = config;\n  if (dawn > sunrise) sunrise += 1;\n  if (dawn > sunset) sunset += 1;\n  if (dawn > dusk) dusk += 1;\n\n  const blocks = [\n    {\n      start: dawn,\n      end: sunrise,\n      type: MODES.sunrise,\n      width: 0,\n      center: 0\n    },\n    {\n      start: sunrise,\n      end: sunset,\n      type: MODES.day,\n      width: 0,\n      center: 0\n    },\n    {\n      start: sunset,\n      end: dusk,\n      type: MODES.sunset,\n      width: 0,\n      center: 0\n    },\n    {\n      start: dusk,\n      end: dawn,\n      type: MODES.night,\n      width: 0,\n      center: 0\n    }\n  ];\n  const updatedBlocks: UIBlock[] = [];\n\n  // sort and split ui blocks\n  blocks.forEach(block => {\n    if (block.start > block.end) {\n      block.end += 1;\n    }\n    if (block.start > 1 && block.end > 1) {\n      updatedBlocks.push({...block, start: block.start - 1, end: block.end - 1});\n    } else if (block.end > 1) {\n      updatedBlocks.push({\n        ...block,\n        end: 1\n      });\n\n      updatedBlocks.push({...block, start: 0, end: block.end - 1});\n    } else {\n      updatedBlocks.push(block);\n    }\n  });\n  updatedBlocks.sort((a, b) => a.start - b.start);\n\n  const existingBottomBlocks = {};\n  // eslint-disable-next-line complexity\n  updatedBlocks.forEach(block => {\n    block.width = (block.end - block.start) * 100;\n    block.center = block.start * 100 + block.width / 2;\n\n    // Don't display inline icons when day or night is too short.\n    if ((block.type === MODES.day || block.type === MODES.night) && block.width > 5) {\n      block.TopRowIcon = block.type === MODES.day ? Sun : Moon;\n    }\n\n    // bottom row icons below the slider\n    if ((block.type === MODES.sunrise || block.type === MODES.sunset) && block.width > 0.1) {\n      // prevent duplicates for bottom row\n      const existingBlock = existingBottomBlocks[block.type];\n      if (existingBlock) {\n        if (existingBlock.width < block.width) existingBlock.BottomRowIcon = null;\n        else if (existingBlock.width > block.width) return;\n      }\n      existingBottomBlocks[block.type] = block;\n\n      // prevent bottom icon and labels\n      block.BottomRowIcon = block.type === MODES.sunrise ? Sunrise : Sunset;\n      if (block.center > 90) {\n        block.center = 90;\n      } else if (block.center < 10) {\n        block.center = 10;\n      }\n      block.text = block.type === MODES.sunrise ? config.sunriseTime : config.sunsetTime;\n    }\n  });\n  return updatedBlocks;\n}\n\nEffectTimeSliderFactory.deps = [RangeSliderFactory];\n\nexport default function EffectTimeSliderFactory(\n  RangeSlider: ReturnType<typeof RangeSliderFactory>\n) {\n  const EffectTimeSlider: React.FC<EffectTimeSliderProps> = ({\n    value: rangeSliderValue,\n    onChange,\n    config\n  }: EffectTimeSliderProps) => {\n    const uiBlocks = useMemo(() => {\n      return getUIBlocks(config);\n    }, [config]);\n\n    const rangeSliderProps = useMemo(() => {\n      return {\n        label: 'value',\n        value1: rangeSliderValue,\n        step: 0.001,\n        range: [0, 1],\n        value0: 0,\n        onChange,\n        showInput: false,\n        isRanged: false\n      };\n    }, [rangeSliderValue, onChange]);\n\n    return (\n      <TimeSliderWrapper>\n        <TopRowWrapper>\n          {uiBlocks.map((block, index) => {\n            return <BackgroundBlock key={index} width={`${block.width}%`} className={block.type} />;\n          })}\n\n          {uiBlocks.map((block, index) => {\n            return block.TopRowIcon ? (\n              <StyledIcon key={index} left={block.center} className={`inline_icon__${block.type}`}>\n                <block.TopRowIcon width=\"16px\" height=\"32px\" />\n              </StyledIcon>\n            ) : null;\n          })}\n\n          <RangeSliderWrapper>\n            <RangeSlider {...rangeSliderProps} />\n          </RangeSliderWrapper>\n        </TopRowWrapper>\n        <BottomRowWrapper>\n          {uiBlocks.map((block, index) => {\n            return block.BottomRowIcon ? (\n              <StyledBottomIcon\n                key={index}\n                left={block.center}\n                className={`bottom_icon__${block.type}`}\n              >\n                <block.BottomRowIcon width=\"12px\" height=\"12px\" />\n                <Text>{block.text}</Text>\n              </StyledBottomIcon>\n            ) : null;\n          })}\n        </BottomRowWrapper>\n      </TimeSliderWrapper>\n    );\n  };\n\n  return EffectTimeSlider;\n}\n"
  },
  {
    "path": "src/components/src/effects/effect-type-dropdown-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\nimport classNames from 'classnames';\n\nimport {classList} from '../common/item-selector/dropdown-list';\n\nexport type EffectTypeDropdownListItem = {\n  type: string;\n  name: string;\n  disabled: boolean;\n};\n\nexport type EffectTypeDropdownListProps = {\n  onOptionSelected: (string) => void;\n  options: EffectTypeDropdownListItem[];\n  selectedItems: EffectTypeDropdownListItem[];\n  selectionIndex: number;\n  customListItemComponent: React.FC<{value: EffectTypeDropdownListItem; isTile?: boolean}>;\n};\n\nconst DropdownListWrapper = styled.div`\n  ${props => props.theme.dropdownList};\n  background-color: ${props => props.theme.dropdownListBgd};\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  padding: 24px 0 12px 0;\n  max-height: 430px;\n  justify-content: center;\n`;\n\nconst StyledDropdownListItem = styled.div`\n  margin: 0px 4px 8px 4px;\n\n  &.disabled {\n    pointer-events: none;\n    opacity: 0.3;\n  }\n\n  &:hover {\n    cursor: pointer;\n    .effect-type-selector__item__label {\n      color: ${props => props.theme.effectPanelTextMain};\n    }\n  }\n`;\n\nexport function EffectTypeDropdownListFactory() {\n  const EffectTypeDropdownList: React.FC<EffectTypeDropdownListProps> = ({\n    onOptionSelected,\n    options,\n    selectedItems,\n    selectionIndex,\n    customListItemComponent\n  }: EffectTypeDropdownListProps) => {\n    const onSelectOption = useCallback(\n      (e, value) => {\n        e.preventDefault();\n        onOptionSelected(value);\n      },\n      [onOptionSelected]\n    );\n\n    const ListItemComponent = customListItemComponent;\n\n    return (\n      <DropdownListWrapper className={classList.list}>\n        {options.map((value, i) => (\n          <StyledDropdownListItem\n            className={classNames('effect-type-selector__item', {\n              selected: selectedItems.find(it => it.type === value.type),\n              hover: selectionIndex === i,\n              disabled: value.disabled\n            })}\n            key={`${value.type}_${i}`}\n            onMouseDown={e => onSelectOption(e, value)}\n            onClick={e => onSelectOption(e, value)}\n          >\n            <ListItemComponent value={value} isTile />\n          </StyledDropdownListItem>\n        ))}\n      </DropdownListWrapper>\n    );\n  };\n\n  return EffectTypeDropdownList;\n}\n\nexport default EffectTypeDropdownListFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-type-list-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport classNames from 'classnames';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {Add} from '../common/icons';\n\nexport const DUMMY_ITEM_ID = 'dummy';\n\nexport type EffectTypeListItemProps = {\n  value: {type: string; name: string};\n  className?: string;\n  isTile?: boolean;\n  theme: any;\n};\n\nconst StyledListItem = styled.div`\n  border-radius: 2px;\n  height: 89px;\n  transition: background-color 0.4s;\n\n  &:hover {\n    background-color: ${props => props.theme.effectTypeIconBgHoverColor};\n  }\n\n  .effect-type-selector__item__icon {\n    display: flex;\n    height: ${props => props.theme.effectTypeIconSizeL}px;\n    width: ${props => props.theme.effectTypeIconSizeL}px;\n    border-radius: 2px;\n\n    .effect-preview {\n      height: ${props => props.theme.effectTypeIconSizeL}px;\n      width: ${props => props.theme.effectTypeIconSizeL}px;\n    }\n  }\n\n  .effect-type-selector__item__label {\n    text-transform: capitalize;\n    font-size: 10px;\n    text-align: center;\n    color: ${props => props.theme.effectPanelTextMain};\n    max-width: ${props => props.theme.effectTypeIconSizeL}px;\n    line-height: 14px;\n    padding-top: 2px;\n  }\n`;\n\nconst StyledAddButton = styled(Add)`\n  margin-right: 8px;\n  height: 16px;\n`;\n\nconst StyledPlaceholderButton = styled.div`\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-left: 3px;\n  margin-right: 3px;\n  letter-spacing: 0.3px;\n  font-family: ${props => props.theme.effectPanelAddEffectFontFamily};\n  font-weight: 500;\n`;\n\n/**\n * Transforms an effect type from camel case into a name of the image in kebab case.\n * @param {string} type\n * @returns {string}\n */\nconst getImageUrl = type => {\n  const kebab = type.replace(\n    /[A-Z]+(?![a-z])|[A-Z]/g,\n    ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()\n  );\n  return `${getApplicationConfig().cdnUrl}/images/effects/${kebab}.png`;\n};\n\nexport function EffectTypeListItemFactory() {\n  const EffectTypeListItem: React.FC<EffectTypeListItemProps> = ({value, isTile, className}) => {\n    if (value?.type === DUMMY_ITEM_ID) {\n      return (\n        <StyledPlaceholderButton>\n          <StyledAddButton />\n          <FormattedMessage id={'effectManager.addEffect'} />\n        </StyledPlaceholderButton>\n      );\n    }\n\n    return (\n      <StyledListItem\n        className={classNames('effect-type-selector__item__inner', className, {\n          list: !isTile\n        })}\n      >\n        <div className=\"effect-type-selector__item__icon\">\n          <img className=\"effect-preview\" src={getImageUrl(value.type)} />\n        </div>\n        <div className=\"effect-type-selector__item__label\">\n          <FormattedMessage id={`effect.type.${value.type}`} defaultMessage={value.name} />\n        </div>\n      </StyledListItem>\n    );\n  };\n\n  return withTheme(EffectTypeListItem);\n}\n\nexport default EffectTypeListItemFactory;\n"
  },
  {
    "path": "src/components/src/effects/effect-type-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled, {withTheme} from 'styled-components';\n\nimport ItemSelector from '../common/item-selector/item-selector';\nimport EffectTypeDropdownListFactory from './effect-type-dropdown-list';\nimport EffectTypeListItemFactory, {DUMMY_ITEM_ID} from './effect-type-list-item';\n\nexport type EffectTypeSelectorProps = {\n  options: {type: string; name: string; disabled: boolean}[];\n  onSelect: (type: any) => void;\n  theme: any;\n  onBlur?: () => void;\n  onOpen?: () => void;\n};\n\nconst DropdownWrapper = styled.div`\n  border: 0;\n  width: 100%;\n  left: 0;\n  z-index: ${props => props.theme.dropdownWrapperZ};\n  width: 297px;\n  margin-left: -194px;\n  margin-top: 26px;\n\n  .typeahead__input {\n    border-color: ${props => props.theme.activeColor};\n    border-radius: 4px 4px 0px 0px !important;\n  }\n  .typeahead__input_box {\n    padding: 0px;\n  }\n  .typeahead__input_icon {\n    top: 34px;\n    right: 9px;\n  }\n`;\n\nconst StyledEffectTypeSelector = styled.div`\n  .item-selector .item-selector__dropdown {\n    padding: 4px 10px 4px 10px;\n    background-color: ${props => props.theme.secondaryBtnBgd};\n    border-radius: ${props => props.theme.primaryBtnRadius};\n    font-size: ${props => props.theme.primaryBtnFontSizeDefault};\n    border: none;\n\n    &:hover {\n      background-color: ${props => props.theme.secondaryBtnBgdHover};\n    }\n\n    .item-selector__dropdown__value {\n      color: ${props => props.theme.secondaryBtnActColor};\n    }\n  }\n`;\n\nconst getDisplayOption = op => op.name;\nconst getOptionValue = op => op.type;\n\nEffectTypeSelectorFactory.deps = [EffectTypeListItemFactory, EffectTypeDropdownListFactory];\n\nfunction EffectTypeSelectorFactory(EffectTypeListItem, EffectTypeDropdownList) {\n  const EffectTypeSelector: React.FC<EffectTypeSelectorProps> = ({\n    options,\n    onSelect,\n    onBlur,\n    onOpen\n  }: EffectTypeSelectorProps) => {\n    // Make sure effect type selector has dummy as selection\n    const selectedItems = useMemo(() => {\n      return [\n        {\n          type: DUMMY_ITEM_ID,\n          name: DUMMY_ITEM_ID\n        }\n      ];\n    }, []);\n\n    return (\n      <StyledEffectTypeSelector className=\"effect-config__type\">\n        <ItemSelector\n          selectedItems={selectedItems}\n          options={options}\n          multiSelect={false}\n          disabled={false}\n          placeholder=\"effectManager.addEffect\"\n          onChange={onSelect}\n          onBlur={onBlur}\n          onOpen={onOpen}\n          getOptionValue={getOptionValue}\n          filterOption=\"name\"\n          displayOption={getDisplayOption}\n          DropDownLineItemRenderComponent={EffectTypeListItem}\n          DropDownRenderComponent={EffectTypeDropdownList}\n          DropDownWrapperComponent={DropdownWrapper}\n        />\n      </StyledEffectTypeSelector>\n    );\n  };\n\n  return withTheme(EffectTypeSelector) as React.FC<Omit<EffectTypeSelectorProps, 'theme'>>;\n}\n\nexport default EffectTypeSelectorFactory;\n"
  },
  {
    "path": "src/components/src/effects/side-panel-title.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PropsWithChildren} from 'react';\nimport styled from 'styled-components';\n\nexport type PanelTitleProps = PropsWithChildren<{\n  title: string;\n  className?: string;\n}>;\n\ntype StyledSidePanelTitleProps = {className: string};\nconst StyledSidePanelTitle = styled.div.attrs<StyledSidePanelTitleProps>({\n  className: 'panel-title'\n})`\n  color: ${props => props.theme.titleTextColor};\n  font-size: ${props => props.theme.sidePanelTitleFontsize};\n  line-height: ${props => props.theme.sidePanelTitleLineHeight};\n  font-weight: 400;\n  letter-spacing: 1.25px;\n`;\n\nconst PanelHeaderRow = styled.div.attrs({\n  className: 'side-panel-header-row'\n})`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-top: 0px;\n  margin-bottom: 8px;\n`;\n\nconst SidePanelTitleFactory = (): React.FC<PanelTitleProps> => {\n  const SidePanelTitle = ({title, className, children}: PanelTitleProps) => (\n    <PanelHeaderRow>\n      <StyledSidePanelTitle className={className || 'side-panel-title'}>\n        {title}\n      </StyledSidePanelTitle>\n      {children}\n    </PanelHeaderRow>\n  );\n\n  return SidePanelTitle;\n};\n\nexport default SidePanelTitleFactory;\n"
  },
  {
    "path": "src/components/src/effects/timezone-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport moment from 'moment-timezone';\nimport styled, {withTheme} from 'styled-components';\n\nimport {DEFAULT_TIMEZONE} from '@kepler.gl/constants';\n\nimport ItemSelector from '../common/item-selector/item-selector';\n\nconst getValue = op => op?.name || DEFAULT_TIMEZONE;\n\nexport type TimezoneSelectorProps = {\n  selected?: string;\n  options?: {name: string}[];\n  onSelect: (string) => void;\n  theme?: any;\n};\n\nconst StyledItemSelector = styled(ItemSelector)`\n  .item-selector__dropdown {\n    padding-left: 30px;\n    border-radius: 4px;\n  }\n  .active {\n    border-color: ${props => props.theme.activeColor};\n    border-radius: 4px 4px 0px 0px !important;\n  }\n`;\n\nconst defaultTimezones = moment.tz.names().map(name => {\n  return {\n    name\n  };\n});\n\nfunction TimezoneSelectorFactory() {\n  const TimezoneTypeSelector: React.FC<TimezoneSelectorProps> = ({\n    selected,\n    options = defaultTimezones,\n    onSelect\n  }: TimezoneSelectorProps) => {\n    const selectedItems = useMemo(() => {\n      return selected ? [{name: selected}] : [];\n    }, [selected]);\n\n    return (\n      <StyledItemSelector\n        selectedItems={selectedItems}\n        options={options}\n        multiSelect={false}\n        disabled={false}\n        placeholder=\"effectManager.timezone\"\n        onChange={onSelect}\n        filterOption=\"name\"\n        getOptionValue={getValue}\n        displayOption={getValue}\n        searchable={true}\n        showArrow={true}\n      />\n    );\n  };\n\n  return withTheme(TimezoneTypeSelector) as React.FC<Omit<TimezoneSelectorProps, 'theme'>>;\n}\n\nexport default TimezoneSelectorFactory;\n"
  },
  {
    "path": "src/components/src/filter-animation-controller.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport {getBinThresholds, getTimelineFromFilter} from '@kepler.gl/utils';\nimport {TimeRangeFilter} from '@kepler.gl/types';\nimport {ANIMATION_WINDOW} from '@kepler.gl/constants';\nimport AnimationControllerFactory from './common/animation-control/animation-controller';\nimport {Timeline} from '@kepler.gl/types';\n\ninterface FilterAnimationControllerProps {\n  filter: TimeRangeFilter & {animationWindow?: string};\n  filterIdx: number;\n  setFilterAnimationTime: (idx: number, value: string, a: any[]) => void;\n  children?: (\n    isAnimating: boolean | undefined,\n    startAnimation: () => void,\n    pauseAnimation: () => void,\n    resetAnimation: () => void,\n    timeline: Timeline | undefined,\n    setTimelineValue: (x: any) => void\n  ) => React.ReactElement | null;\n}\n\nFilterAnimationControllerFactory.deps = [AnimationControllerFactory];\nfunction FilterAnimationControllerFactory(\n  AnimationController: ReturnType<typeof AnimationControllerFactory>\n) {\n  const FilterAnimationController: React.FC<FilterAnimationControllerProps> = ({\n    filter,\n    filterIdx,\n    setFilterAnimationTime,\n    children\n  }) => {\n    const binThresholds = useMemo(() => {\n      return getBinThresholds(filter.plotType?.interval, filter.domain);\n    }, [filter.plotType?.interval, filter.domain]);\n\n    const steps = useMemo(() => {\n      if (binThresholds) {\n        const thresholds = [...binThresholds];\n        // pop last threshold\n        thresholds.pop();\n        return thresholds;\n      }\n\n      return null;\n    }, [binThresholds]);\n\n    const updateAnimation = useCallback(\n      value => {\n        switch (filter.animationWindow) {\n          case ANIMATION_WINDOW.interval: {\n            const idx = value[1];\n            if (idx < binThresholds.length - 1) {\n              setFilterAnimationTime(filterIdx, 'value', [\n                binThresholds[idx],\n                binThresholds[idx + 1] - 1\n              ]);\n            }\n            break;\n          }\n          default:\n            setFilterAnimationTime(filterIdx, 'value', value);\n            break;\n        }\n      },\n      [filterIdx, binThresholds, filter.animationWindow, setFilterAnimationTime]\n    );\n\n    // if filter is synced merge the filter and animation config\n    const timeline = getTimelineFromFilter(filter);\n\n    return (\n      <AnimationController\n        key=\"filter-control\"\n        value={filter.value}\n        domain={filter.domain}\n        speed={filter.speed}\n        isAnimating={filter.isAnimating}\n        animationWindow={filter.animationWindow}\n        steps={steps}\n        updateAnimation={updateAnimation}\n        // @ts-expect-error different function type, goes to TimeWidget setFilterAnimationTime()\n        setTimelineValue={setFilterAnimationTime}\n        timeline={timeline}\n        children={children}\n      />\n    );\n  };\n  return FilterAnimationController;\n}\n\nexport default FilterAnimationControllerFactory;\n"
  },
  {
    "path": "src/components/src/filters/components.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\nimport {PanelLabel} from '../common/styled-components';\n\nexport const StyledFilterPanel = styled(PanelLabel)`\n  font-weight: 500;\n  color: white;\n  flex: 1;\n`;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/filter-panel-with-field-select.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport {StyledFilterContent} from '../../common/styled-components';\nimport FilterPanelHeaderFactory from '../../side-panel/filter-panel/filter-panel-header';\nimport PanelHeaderActionFactory from '../../side-panel/panel-header-action';\nimport SourceDataSelectorFactory from '../../side-panel/common/source-data-selector';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport {getSupportedFilterFields} from './new-filter-panel';\nimport {FilterPanelWithFieldSelectComponent} from './types';\nimport {KeplerTable} from '@kepler.gl/table';\n\nFieldPanelWithFieldSelectFactory.deps = [\n  FilterPanelHeaderFactory,\n  SourceDataSelectorFactory,\n  FieldSelectorFactory,\n  PanelHeaderActionFactory\n];\n\nfunction FieldPanelWithFieldSelectFactory(\n  FilterPanelHeader: ReturnType<typeof FilterPanelHeaderFactory>,\n  SourceDataSelector: ReturnType<typeof SourceDataSelectorFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>,\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n) {\n  const FilterPanelWithFieldSelect: FilterPanelWithFieldSelectComponent = React.memo(\n    ({\n      children,\n      allAvailableFields,\n      datasets,\n      filter,\n      idx,\n      removeFilter,\n      setFilter,\n      panelActions = []\n    }) => {\n      const onFieldSelector = useCallback(\n        field => setFilter(idx, 'name', field.name),\n        [idx, setFilter]\n      );\n\n      const onSourceDataSelector = useCallback(\n        value => setFilter(idx, 'dataId', value, 0),\n        [idx, setFilter]\n      );\n\n      const fieldValue = useMemo(\n        () => (Array.isArray(filter.name) ? filter.name[0] : filter.name),\n        [filter.name]\n      );\n\n      const dataset: KeplerTable = datasets[filter.dataId[0]];\n      const supportedFields = useMemo(\n        () => getSupportedFilterFields(dataset.supportedFilterTypes, allAvailableFields),\n        [dataset.supportedFilterTypes, allAvailableFields]\n      );\n      return (\n        <>\n          <FilterPanelHeader datasets={[dataset]} filter={filter} removeFilter={removeFilter}>\n            <FieldSelector\n              inputTheme=\"secondary\"\n              fields={supportedFields}\n              value={fieldValue}\n              erasable={false}\n              onSelect={onFieldSelector}\n            />\n            {panelActions.map(panelAction => (\n              <PanelHeaderAction\n                id={panelAction.id}\n                key={panelAction.id}\n                onClick={panelAction.onClick}\n                tooltip={panelAction.tooltip}\n                IconComponent={panelAction.iconComponent}\n                active={panelAction.active}\n              />\n            ))}\n          </FilterPanelHeader>\n          <StyledFilterContent className=\"filter-panel__content\">\n            {Object.keys(datasets).length > 1 && (\n              <SourceDataSelector\n                inputTheme=\"secondary\"\n                datasets={datasets}\n                dataId={filter.dataId}\n                onSelect={onSourceDataSelector}\n              />\n            )}\n            {children}\n          </StyledFilterContent>\n        </>\n      );\n    }\n  );\n\n  FilterPanelWithFieldSelect.displayName = 'FilterPanelWithFieldSelect';\n\n  return FilterPanelWithFieldSelect;\n}\n\nexport default FieldPanelWithFieldSelectFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/filter-synced-dataset-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled, {withTheme} from 'styled-components';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {getAnimatableVisibleLayers} from '@kepler.gl/utils';\n\nimport {Button} from '../../common/styled-components';\nimport {Add, Trash} from '../../common/icons';\nimport TippyTooltip from '../../common/tippy-tooltip';\nimport FilterPanelHeaderFactory from '../../side-panel/filter-panel/filter-panel-header';\nimport SourceSelectorFactory from '../../side-panel/common/source-selector';\nimport SourceDataSelectorFactory from '../../side-panel/common/source-data-selector';\nimport LayerTypeListItemFactory from '../../side-panel/layer-panel/layer-type-list-item';\n\nconst TrashIcon = styled(Trash)`\n  cursor: pointer;\n  color: ${props => props.theme.fontWhiteColor};\n  margin-left: 8px;\n`;\n\nconst SyncedDatasetsArea = styled.div`\n  .side-panel-section {\n    margin-bottom: 0;\n  }\n`;\n\nconst StyledContentTitle = styled.div`\n  color: ${props => props.theme.subtextColor};\n  margin-bottom: 8px;\n`;\n\nconst StyledSeparator = styled.div`\n  border-left: 1px dashed ${props => props.theme.subtextColor};\n  height: 16px;\n  margin: 4px 0 4px 8px;\n`;\n\nconst StyledButton = styled(Button)`\n  margin-top: 2px;\n  padding: 2px;\n`;\n\nfunction getDatasetsWithTimeField(datasets) {\n  const rv = {};\n  for (const id of Object.keys(datasets)) {\n    // TODO: change to\n    if (datasets[id].fields.some(f => f.type === ALL_FIELD_TYPES.timestamp)) {\n      rv[id] = datasets[id];\n    }\n  }\n  return rv;\n}\n\nfunction getTimeFields(dataset) {\n  return dataset.fields.filter(f => f.type === ALL_FIELD_TYPES.timestamp);\n}\n\nfunction DatasetItemFactory(SourceSelector, FilterPanelHeader) {\n  const StyledFilterPanelHeader = styled(FilterPanelHeader)`\n    display: flex;\n    border: none;\n    height: unset;\n    padding: 2px 0;\n    background: none;\n    align-items: baseline;\n  `;\n\n  const StyledSourceSelector = styled(SourceSelector)`\n    flex: 1;\n    -webkit-border-radius: 8px;\n    -moz-border-radius: 8px;\n    border-radius: 8px;\n    background-color: transparent;\n  `;\n\n  // Check if this component already exists\n  const DatasetItem = ({\n    dataId,\n    datasets,\n    supportedFields,\n    idx,\n    filter,\n    index,\n    onRemoveSyncedFilter,\n    filterDatasetsNum,\n    datasetsWithTimeNum,\n    onSelectSyncedDataset,\n    onFieldSelector\n  }) => (\n    <div>\n      <StyledSeparator />\n      <StyledFilterPanelHeader\n        datasets={[datasets[dataId]]}\n        allAvailableFields={supportedFields}\n        idx={idx}\n        filter={filter}\n        removeFilter={() => onRemoveSyncedFilter(index)}\n      >\n        <StyledSourceSelector\n          datasets={datasets}\n          disabled={filterDatasetsNum >= datasetsWithTimeNum}\n          dataId={dataId}\n          onSelectDataset={datasetId => onSelectSyncedDataset(datasetId, index)}\n          fields={getTimeFields(datasets[dataId])}\n          fieldValue={filter.name[index]}\n          onFieldSelector={field => onFieldSelector(field, index)}\n        />\n      </StyledFilterPanelHeader>\n    </div>\n  );\n\n  return DatasetItem;\n}\n\nDatasetItemFactory.deps = [SourceSelectorFactory, FilterPanelHeaderFactory];\n\nconst StyledLayerTimeline = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nconst StyledLayerList = styled.div`\n  flex: 1;\n`;\n\nfunction LayerTimelineFactory(LayerTypeListItem) {\n  const StyledLayerTypeListItem = styled(LayerTypeListItem)`\n    background-color: ${props => props.theme.dropdownListHighlightBg};\n    padding: 4px;\n  `;\n\n  const LayerTimeline = ({layers, theme, onDelete}) => (\n    <StyledLayerTimeline>\n      <StyledLayerList>\n        {layers.map(layer => (\n          <StyledLayerTypeListItem\n            key={layer.id}\n            value={{icon: layer.layerIcon, label: layer.name}}\n            theme={{...theme, layerTypeIconSizeSM: 24}}\n          />\n        ))}\n      </StyledLayerList>\n      <TrashIcon height=\"12px\" width=\"12px\" onClick={onDelete} />\n    </StyledLayerTimeline>\n  );\n\n  return withTheme(LayerTimeline);\n}\n\nLayerTimelineFactory.deps = [LayerTypeListItemFactory];\n\nfunction SyncedDatasetButtonFactory() {\n  const SyncedDatasetButton = ({onAddSyncedFilter}) => (\n    <div>\n      <StyledSeparator />\n      <TippyTooltip\n        delay={[500, 0]}\n        placement=\"top\"\n        render={() => (\n          <div>\n            <FormattedMessage id={'tooltip.timeFilterSync'} />\n          </div>\n        )}\n      >\n        <StyledButton className=\"add-sync-dataset\" secondary={true} onClick={onAddSyncedFilter}>\n          <Add height=\"12px\" />\n          <FormattedMessage id={'filterManager.timeFilterSync'} />\n        </StyledButton>\n      </TippyTooltip>\n    </div>\n  );\n\n  return SyncedDatasetButton;\n}\n\nfunction SyncLayerTimelineButtonFactory() {\n  const SyncLayerTimelineButton = ({onSyncLayerTimeline}) => (\n    <div>\n      <StyledSeparator />\n      <TippyTooltip\n        delay={[500, 0]}\n        placement=\"top\"\n        render={() => (\n          <div>\n            <FormattedMessage id={'tooltip.timeLayerSync'} />\n          </div>\n        )}\n      >\n        <StyledButton className=\"add-sync-dataset\" secondary={true} onClick={onSyncLayerTimeline}>\n          <Add height=\"12px\" />\n          <FormattedMessage id={'filterManager.timeLayerSync'} />\n        </StyledButton>\n      </TippyTooltip>\n    </div>\n  );\n\n  return SyncLayerTimelineButton;\n}\n\nfunction FilterSyncedDatasetPanelFactory(\n  DatasetItem,\n  LayerTimeline,\n  SourceDataSelector,\n  SyncedDatasetButton,\n  SyncLayerTimelineButton\n) {\n  const FilterSyncedDatasetPanel = ({\n    datasets,\n    layers,\n    filter,\n    setFilter,\n    idx,\n    supportedFields,\n    onFieldSelector,\n    onSourceDataSelector,\n    syncTimeFilterWithLayerTimeline\n  }) => {\n    const datasetsWithTime = useMemo(() => getDatasetsWithTimeField(datasets), [datasets]);\n    const filterDatasetsNum = useMemo(() => filter.dataId.length, [filter.dataId]);\n    const datasetsWithTimeNum = useMemo(\n      () => Object.keys(datasetsWithTime).length,\n      [datasetsWithTime]\n    );\n\n    const onRemoveSyncedFilter = useCallback(\n      valueIndex => {\n        setFilter(idx, 'dataId', null, valueIndex);\n      },\n      [idx, setFilter]\n    );\n\n    const onSelectSyncedDataset = useCallback(\n      (datasetId, valueIndex) => {\n        setFilter(idx, 'dataId', datasetId, valueIndex);\n      },\n      [setFilter, idx]\n    );\n\n    const onAddSyncedFilter = useCallback(() => {\n      const nextId = Object.keys(datasetsWithTime).find(id => !filter.dataId.includes(id));\n      if (!nextId) return;\n      const timeFieldNames = getTimeFields(datasets[nextId])?.map(f => f.name);\n      if (!timeFieldNames || timeFieldNames.length < 1) return;\n      const nextName = timeFieldNames.includes(filter.name[0]) ? filter.name[0] : timeFieldNames[0];\n      setFilter(idx, ['dataId', 'name'], [nextId, nextName], filter.dataId.length);\n    }, [setFilter, idx, datasetsWithTime, datasets, filter.dataId, filter.name]);\n\n    const onSyncLayerTimeline = useCallback(\n      () => syncTimeFilterWithLayerTimeline(idx, true),\n      [syncTimeFilterWithLayerTimeline, idx]\n    );\n\n    const onRemoveSyncWithLayerTimeline = useCallback(\n      () => syncTimeFilterWithLayerTimeline(idx, false),\n      [syncTimeFilterWithLayerTimeline, idx]\n    );\n\n    const animatableLayers = useMemo(() => getAnimatableVisibleLayers(layers), [layers]);\n\n    const isLinkedWithLayerTimeline = useMemo(() => filter.syncedWithLayerTimeline, [filter]);\n\n    return (\n      <SyncedDatasetsArea>\n        {filter.dataId.length > 1 ? (\n          <>\n            <StyledContentTitle>Datasets</StyledContentTitle>\n            {filter.dataId.map((dataId, index) => {\n              return (\n                <DatasetItem\n                  key={dataId}\n                  dataId={dataId}\n                  index={index}\n                  datasets={datasets}\n                  supportedFields={supportedFields}\n                  idx={idx}\n                  filter={filter}\n                  onRemoveSyncedFilter={onRemoveSyncedFilter}\n                  filterDatasetsNum={filterDatasetsNum}\n                  datasetsWithTimeNum={datasetsWithTimeNum}\n                  onSelectSyncedDataset={onSelectSyncedDataset}\n                  onFieldSelector={onFieldSelector}\n                />\n              );\n            })}\n          </>\n        ) : (\n          <>\n            <SourceDataSelector\n              inputTheme=\"secondary\"\n              datasets={datasets}\n              dataId={Array.isArray(filter.dataId) ? filter.dataId[0] : filter.dataId}\n              onSelect={onSourceDataSelector}\n            />\n          </>\n        )}\n        {/** sync with animatable layers */}\n        {isLinkedWithLayerTimeline ? (\n          <>\n            <StyledSeparator />\n            <LayerTimeline layers={animatableLayers} onDelete={onRemoveSyncWithLayerTimeline} />\n          </>\n        ) : (\n          <>\n            {animatableLayers.length ? (\n              <SyncLayerTimelineButton onSyncLayerTimeline={onSyncLayerTimeline} />\n            ) : null}\n          </>\n        )}\n        {filterDatasetsNum < datasetsWithTimeNum ? (\n          <SyncedDatasetButton onAddSyncedFilter={onAddSyncedFilter} />\n        ) : null}\n      </SyncedDatasetsArea>\n    );\n  };\n\n  FilterSyncedDatasetPanel.displayName = 'FilterSyncedDatasetPanel';\n\n  return FilterSyncedDatasetPanel;\n}\n\nFilterSyncedDatasetPanelFactory.deps = [\n  DatasetItemFactory,\n  LayerTimelineFactory,\n  SourceDataSelectorFactory,\n  SyncedDatasetButtonFactory,\n  SyncLayerTimelineButtonFactory\n];\n\nexport default FilterSyncedDatasetPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/multi-select-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport MultiSelectFilterFactory from '../multi-select-filter';\nimport {MultiSelectFilter} from '@kepler.gl/types';\nimport FieldPanelWithFieldSelectFactory from './filter-panel-with-field-select';\nimport {FilterPanelComponent} from './types';\n\nMultiSelectFilterPanelFactory.deps = [FieldPanelWithFieldSelectFactory, MultiSelectFilterFactory];\n\nfunction MultiSelectFilterPanelFactory(\n  FieldPanelWithFieldSelect: ReturnType<typeof FieldPanelWithFieldSelectFactory>,\n  MultiSelectFilterComponent: ReturnType<typeof MultiSelectFilterFactory>\n) {\n  const MultiSelectFilterPanel: FilterPanelComponent<MultiSelectFilter> = React.memo(\n    ({idx, datasets, allAvailableFields, filter, setFilter, removeFilter}) => {\n      const onSetFilter = useCallback(value => setFilter(idx, 'value', value), [idx, setFilter]);\n\n      return (\n        <div className=\"multi-select-filter-panel\">\n          <FieldPanelWithFieldSelect\n            allAvailableFields={allAvailableFields}\n            datasets={datasets}\n            filter={filter}\n            idx={idx}\n            removeFilter={removeFilter}\n            setFilter={setFilter}\n          >\n            {filter.type && (\n              <div className=\"filter-panel__filter\">\n                <MultiSelectFilterComponent filter={filter} setFilter={onSetFilter} />\n              </div>\n            )}\n          </FieldPanelWithFieldSelect>\n        </div>\n      );\n    }\n  );\n\n  MultiSelectFilterPanel.displayName = 'MultiSelectFilterPanel';\n\n  return MultiSelectFilterPanel;\n}\n\nexport default MultiSelectFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/new-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport {StyledFilterContent} from '../../common/styled-components';\nimport FilterPanelHeaderFactory from '../../side-panel/filter-panel/filter-panel-header';\nimport SourceDataSelectorFactory from '../../side-panel/common/source-data-selector';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport {FilterPanelComponent} from './types';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {Field, FilterBase, LineChart} from '@kepler.gl/types';\n\nNewFilterPanelFactory.deps = [\n  FilterPanelHeaderFactory,\n  SourceDataSelectorFactory,\n  FieldSelectorFactory\n];\n\nexport function getSupportedFilterFields(\n  supportedFilterTypes: KeplerTable['supportedFilterTypes'],\n  fields: Field[]\n) {\n  return supportedFilterTypes\n    ? fields.filter(field => supportedFilterTypes.includes(field.type))\n    : fields;\n}\n\nfunction NewFilterPanelFactory(\n  FilterPanelHeader: ReturnType<typeof FilterPanelHeaderFactory>,\n  SourceDataSelector: ReturnType<typeof SourceDataSelectorFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>\n) {\n  const NewFilterPanel: FilterPanelComponent<FilterBase<LineChart>> = React.memo(\n    ({idx, filter, datasets, allAvailableFields, setFilter, removeFilter}) => {\n      const onFieldSelector = useCallback(\n        field => setFilter(idx, 'name', field.name),\n        [idx, setFilter]\n      );\n\n      const onSourceDataSelector = useCallback(\n        value => setFilter(idx, 'dataId', value, 0),\n        [idx, setFilter]\n      );\n\n      const dataset: KeplerTable = datasets[filter.dataId[0]];\n      const supportedFields = useMemo(\n        () => getSupportedFilterFields(dataset.supportedFilterTypes, allAvailableFields),\n        [dataset.supportedFilterTypes, allAvailableFields]\n      );\n\n      return (\n        <>\n          <FilterPanelHeader datasets={[dataset]} filter={filter} removeFilter={removeFilter}>\n            <FieldSelector\n              inputTheme=\"secondary\"\n              fields={supportedFields}\n              value={Array.isArray(filter.name) ? filter.name[0] : filter.name}\n              erasable={false}\n              onSelect={onFieldSelector}\n            />\n          </FilterPanelHeader>\n          <StyledFilterContent className=\"filter-panel__content\">\n            {Object.keys(datasets).length > 1 && (\n              <SourceDataSelector\n                inputTheme=\"secondary\"\n                datasets={datasets}\n                dataId={filter.dataId}\n                onSelect={onSourceDataSelector}\n              />\n            )}\n          </StyledFilterContent>\n        </>\n      );\n    }\n  );\n\n  NewFilterPanel.displayName = 'NewFilterPanel';\n\n  return NewFilterPanel;\n}\n\nexport default NewFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/polygon-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, useCallback} from 'react';\nimport {StyledFilterContent} from '../../common/styled-components';\nimport PolygonFilterFactory from '../polygon-filter';\nimport PanelHeaderActionFactory from '../../side-panel/panel-header-action';\nimport {EyeSeen, EyeUnseen} from '../../common/icons';\n\nimport FilterPanelHeaderFactory from '../../side-panel/filter-panel/filter-panel-header';\nimport {StyledFilterPanel} from '../components';\n\nimport get from 'lodash/get';\nimport {PolygonFilterPanelComponent} from './types';\nimport {KeplerTable} from '@kepler.gl/table';\n\nPolygonFilterPanelFactory.deps = [\n  FilterPanelHeaderFactory,\n  PolygonFilterFactory,\n  PanelHeaderActionFactory\n];\n\nfunction PolygonFilterPanelFactory(\n  FilterPanelHeader: ReturnType<typeof FilterPanelHeaderFactory>,\n  PolygonFilter: ReturnType<typeof PolygonFilterFactory>,\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n) {\n  const PolygonFilterPanel: PolygonFilterPanelComponent = React.memo(\n    ({idx, datasets, layers, filter, removeFilter, setFilter, toggleFilterFeature}) => {\n      const filterDatasets: KeplerTable[] = useMemo(\n        () => filter.dataId.map(d => datasets[d]),\n        [filter, datasets]\n      );\n\n      const onSetLayers = useCallback(value => setFilter(idx, 'layerId', value), [setFilter, idx]);\n\n      const isVisible = get(filter, ['value', 'properties', 'isVisible'], true);\n      const featureType = get(filter, ['value', 'geometry', 'type'], 'Polygon');\n\n      return (\n        <div className=\"polygon-filter-panel\">\n          <FilterPanelHeader datasets={filterDatasets} filter={filter} removeFilter={removeFilter}>\n            <StyledFilterPanel>Geo - {featureType}</StyledFilterPanel>\n            <PanelHeaderAction\n              id={filter.id}\n              onClick={toggleFilterFeature}\n              tooltip={isVisible ? 'tooltip.hideFeature' : 'tooltip.showFeature'}\n              IconComponent={isVisible ? EyeSeen : EyeUnseen}\n              active={isVisible}\n            />\n          </FilterPanelHeader>\n          <StyledFilterContent className=\"filter-panel__content\">\n            <div className=\"filter-panel__filter\">\n              <PolygonFilter filter={filter} layers={layers} setLayers={onSetLayers} />\n            </div>\n          </StyledFilterContent>\n        </div>\n      );\n    }\n  );\n\n  PolygonFilterPanel.displayName = 'PolygonFilterPanel';\n\n  return PolygonFilterPanel;\n}\n\nexport default PolygonFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/range-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport RangeFilterFactory from '../range-filter';\nimport {RangeFilter} from '@kepler.gl/types';\nimport FieldPanelWithFieldSelectFactory from './filter-panel-with-field-select';\nimport {FilterPanelComponent} from './types';\n\nRangeFilterPanelFactory.deps = [FieldPanelWithFieldSelectFactory, RangeFilterFactory];\n\nfunction RangeFilterPanelFactory(\n  FieldPanelWithFieldSelect: ReturnType<typeof FieldPanelWithFieldSelectFactory>,\n  RangeFilterComponent: ReturnType<typeof RangeFilterFactory>\n) {\n  const RangeFilterPanel: FilterPanelComponent<RangeFilter> = React.memo(\n    ({idx, datasets, allAvailableFields, filter, removeFilter, setFilter, setFilterPlot}) => {\n      const onSetFilter = useCallback(value => setFilter(idx, 'value', value), [idx, setFilter]);\n      const onSetFilterPlot = useCallback(\n        (newProp, valueIndex) => setFilterPlot(idx, newProp, valueIndex),\n        [idx, setFilterPlot]\n      );\n\n      return (\n        <div className=\"range-filter-panel\">\n          <FieldPanelWithFieldSelect\n            allAvailableFields={allAvailableFields}\n            datasets={datasets}\n            filter={filter}\n            idx={idx}\n            removeFilter={removeFilter}\n            setFilter={setFilter}\n          >\n            {filter.type && (\n              <div className=\"filter-panel__filter\">\n                <RangeFilterComponent\n                  filter={filter}\n                  setFilter={onSetFilter}\n                  setFilterPlot={onSetFilterPlot}\n                />\n              </div>\n            )}\n          </FieldPanelWithFieldSelect>\n        </div>\n      );\n    }\n  );\n\n  RangeFilterPanel.displayName = 'RangeFilterPanel';\n\n  return RangeFilterPanel;\n}\n\nexport default RangeFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/single-select-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport SingleSelectFilterFactory from '../single-select-filter';\nimport {SelectFilter} from '@kepler.gl/types';\nimport FieldPanelWithFieldSelectFactory from './filter-panel-with-field-select';\nimport {FilterPanelComponent} from './types';\n\nSingleSelectFilterPanelFactory.deps = [FieldPanelWithFieldSelectFactory, SingleSelectFilterFactory];\n\nfunction SingleSelectFilterPanelFactory(\n  FieldPanelWithFieldSelect: ReturnType<typeof FieldPanelWithFieldSelectFactory>,\n  SingleSelectFilter: ReturnType<typeof SingleSelectFilterFactory>\n) {\n  const SingleSelectFilterPanel: FilterPanelComponent<SelectFilter> = React.memo(\n    ({idx, datasets, allAvailableFields, filter, setFilter, removeFilter}) => {\n      const onSetFilter = useCallback(value => setFilter(idx, 'value', value), [idx, setFilter]);\n\n      return (\n        <div className=\"single-select-filter-panel\">\n          <FieldPanelWithFieldSelect\n            allAvailableFields={allAvailableFields}\n            datasets={datasets}\n            filter={filter}\n            idx={idx}\n            removeFilter={removeFilter}\n            setFilter={setFilter}\n          >\n            {filter.type && (\n              <div className=\"filter-panel__filter\">\n                <SingleSelectFilter filter={filter} setFilter={onSetFilter} />\n              </div>\n            )}\n          </FieldPanelWithFieldSelect>\n        </div>\n      );\n    }\n  );\n\n  SingleSelectFilterPanel.displayName = 'SingleSelectFilterPanel';\n\n  return SingleSelectFilterPanel;\n}\n\nexport default SingleSelectFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/time-range-filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport TimeRangeFilterFactory from '../time-range-filter';\nimport {Clock} from '../../common/icons';\nimport {TimeRangeFilterPanelComponent} from './types';\nimport {isSideFilter, getTimelineFromFilter} from '@kepler.gl/utils';\nimport FilterPanelHeaderFactory from '../../side-panel/filter-panel/filter-panel-header';\nimport PanelHeaderActionFactory from '../../side-panel/panel-header-action';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport {StyledFilterContent} from '../../common/styled-components';\nimport {getSupportedFilterFields} from './new-filter-panel';\nimport {FILTER_TYPES} from '@kepler.gl/constants';\nimport TimeSyncedFieldSelectorFactory from './time-synced-field-selector';\nimport FilterSyncedDatasetPanelFactory from './filter-synced-dataset-panel';\n\nconst SYNC_FILTER_ID_LENGTH = 2;\n\nTimeRangeFilterPanelFactory.deps = [\n  TimeRangeFilterFactory,\n  FilterPanelHeaderFactory,\n  FieldSelectorFactory,\n  PanelHeaderActionFactory,\n  TimeSyncedFieldSelectorFactory,\n  FilterSyncedDatasetPanelFactory\n];\n\nfunction TimeRangeFilterPanelFactory(\n  TimeRangeFilter: ReturnType<typeof TimeRangeFilterFactory>,\n  FilterPanelHeader: ReturnType<typeof FilterPanelHeaderFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>,\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>,\n  TimeSyncedFieldSelector: ReturnType<typeof TimeSyncedFieldSelectorFactory>,\n  FilterSyncedDatasetPanel: ReturnType<typeof FilterSyncedDatasetPanelFactory>\n) {\n  const TimeRangeFilterPanel: TimeRangeFilterPanelComponent = React.memo(\n    ({\n      idx,\n      datasets,\n      layers,\n      allAvailableFields,\n      filter,\n      enlargeFilter,\n      setFilter,\n      setFilterPlot,\n      removeFilter,\n      toggleAnimation,\n      syncTimeFilterWithLayerTimeline\n    }) => {\n      const onSetFilterValue = useCallback(\n        value => setFilter(idx, 'value', value),\n        [idx, setFilter]\n      );\n\n      const onSetFilterPlot = useCallback(\n        (newProp, valueIndex) => setFilterPlot(idx, newProp, valueIndex),\n        [idx, setFilterPlot]\n      );\n\n      const isEnlarged = useMemo(() => !isSideFilter(filter), [filter]);\n\n      const panelActions = useMemo(\n        () => [\n          {\n            id: filter.id,\n            onClick: enlargeFilter,\n            tooltip: 'tooltip.timePlayback',\n            iconComponent: Clock,\n            active: isEnlarged\n          }\n        ],\n        [filter.id, isEnlarged, enlargeFilter]\n      );\n\n      const onFieldSelector = useCallback(\n        (field, valueIndex) => setFilter(idx, 'name', field.name, valueIndex),\n        [setFilter, idx]\n      );\n\n      const onSourceDataSelector = useCallback(\n        value => setFilter(idx, 'dataId', value, 0),\n        [idx, setFilter]\n      );\n\n      const dataset = datasets[filter.dataId[0]];\n      const supportedFields = useMemo(\n        () => getSupportedFilterFields(dataset.supportedFilterTypes, allAvailableFields),\n        [dataset.supportedFilterTypes, allAvailableFields]\n      );\n\n      const isSynced = useMemo(() => {\n        return (\n          filter.dataId.length >= SYNC_FILTER_ID_LENGTH && filter.type === FILTER_TYPES.timeRange\n        );\n      }, [filter.dataId, filter.type]);\n\n      const isHistogramVisible = useMemo(\n        () => filter.type && !isEnlarged,\n        [filter.type, isEnlarged]\n      );\n\n      const timeline = getTimelineFromFilter(filter);\n\n      return (\n        <>\n          <FilterPanelHeader\n            datasets={[dataset]}\n            allAvailableFields={supportedFields}\n            idx={idx}\n            filter={filter}\n            removeFilter={removeFilter}\n          >\n            {isSynced ? (\n              <TimeSyncedFieldSelector />\n            ) : (\n              <FieldSelector\n                inputTheme=\"secondary\"\n                fields={supportedFields}\n                value={Array.isArray(filter.name) ? filter.name[0] : filter.name}\n                erasable={false}\n                onSelect={field => onFieldSelector(field, 0)}\n              />\n            )}\n            {panelActions.map(panelAction => (\n              <PanelHeaderAction\n                id={panelAction.id}\n                key={panelAction.id}\n                onClick={panelAction.onClick}\n                tooltip={panelAction.tooltip}\n                IconComponent={panelAction.iconComponent}\n                active={panelAction.active}\n              />\n            ))}\n          </FilterPanelHeader>\n          <StyledFilterContent className=\"filter-panel__content\">\n            <FilterSyncedDatasetPanel\n              datasets={datasets}\n              layers={layers}\n              filter={filter}\n              idx={idx}\n              onFieldSelector={onFieldSelector}\n              onSourceDataSelector={onSourceDataSelector}\n              setFilter={setFilter}\n              supportedFields={supportedFields}\n              syncTimeFilterWithLayerTimeline={syncTimeFilterWithLayerTimeline}\n            />\n            {isHistogramVisible && (\n              <div className=\"filter-panel__filter\">\n                <TimeRangeFilter\n                  filter={filter}\n                  datasets={datasets}\n                  layers={layers}\n                  idx={idx}\n                  toggleAnimation={toggleAnimation}\n                  setFilter={onSetFilterValue}\n                  setFilterPlot={onSetFilterPlot}\n                  isAnimatable\n                  hideTimeTitle\n                  timeline={timeline}\n                />\n              </div>\n            )}\n          </StyledFilterContent>\n        </>\n      );\n    }\n  );\n\n  TimeRangeFilterPanel.displayName = 'TimeRangeFilterPanel';\n\n  return TimeRangeFilterPanel;\n}\n\nexport default TimeRangeFilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/time-synced-field-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport FieldTokenFactory from '../../common/field-token';\n\nconst StyledSyncTimeHeader = styled.div`\n  color: ${props => props.theme.subtextColor};\n  flex: 1;\n  cursor: auto;\n  display: grid;\n  align-items: center;\n  grid-column-gap: 8px;\n  grid-auto-columns: min-content;\n  grid-auto-flow: column;\n`;\n\nTimeSyncedFieldSelectorFactory.deps = [FieldTokenFactory];\n\nfunction TimeSyncedFieldSelectorFactory(FieldToken) {\n  const TimeSyncedFieldSelector = () => (\n    <StyledSyncTimeHeader>\n      <FieldToken type={ALL_FIELD_TYPES.timestamp} />\n      <span>Synced</span>\n    </StyledSyncTimeHeader>\n  );\n\n  return TimeSyncedFieldSelector;\n}\n\nexport default TimeSyncedFieldSelectorFactory;\n"
  },
  {
    "path": "src/components/src/filters/filter-panels/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {FunctionComponent, ComponentType, ReactNode} from 'react';\nimport {Filter, PolygonFilter, TimeRangeFilter, Field} from '@kepler.gl/types';\nimport {Layer} from '@kepler.gl/layers';\nimport {Datasets} from '@kepler.gl/table';\nimport {setFilter, setFilterPlot, syncTimeFilterWithLayerTimeline} from '@kepler.gl/actions';\n\ninterface PanelAction {\n  id: string;\n  tooltip: string;\n  onClick: () => void;\n  iconComponent: ComponentType;\n  active: boolean;\n}\n\nexport interface FilterPanelProps<F = Filter> {\n  idx: number;\n  datasets: Datasets;\n  allAvailableFields: Field[];\n  filter: F;\n  removeFilter: () => void;\n  setFilter: (...args: Parameters<typeof setFilter>) => void;\n  setFilterPlot: (...args: Parameters<typeof setFilterPlot>) => void;\n  syncTimeFilterWithLayerTimeline?: (\n    ...args: Parameters<typeof syncTimeFilterWithLayerTimeline>\n  ) => void;\n  children?: ReactNode;\n}\nexport interface PolygonFilterPanelProps extends FilterPanelProps<PolygonFilter> {\n  layers: ReadonlyArray<Layer>;\n  toggleFilterFeature: () => void;\n}\nexport interface TimeRangeFilterPanelProps extends FilterPanelProps<TimeRangeFilter> {\n  layers: ReadonlyArray<Layer>;\n  enlargeFilter: () => void;\n  toggleAnimation: () => void;\n}\nexport interface FilterPanelWithFieldSelectProps extends FilterPanelProps {\n  panelActions?: ReadonlyArray<PanelAction>;\n}\n\nexport type FilterPanelComponent<F> = FunctionComponent<FilterPanelProps<F>>;\nexport type PolygonFilterPanelComponent = FunctionComponent<PolygonFilterPanelProps>;\nexport type TimeRangeFilterPanelComponent = FunctionComponent<TimeRangeFilterPanelProps>;\nexport type FilterPanelWithFieldSelectComponent = FunctionComponent<\n  Omit<FilterPanelWithFieldSelectProps, 'setFilterPlot'>\n>;\n"
  },
  {
    "path": "src/components/src/filters/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as SingleSelectFilter} from './single-select-filter';\nexport {default as MultiSelectFilter} from './multi-select-filter';\nexport {default as TimeRangeFilter} from './time-range-filter';\nexport {default as RangeFilter} from './range-filter';\nexport {default as PolygonFilter} from './polygon-filter';\nexport {default as TimeWidget} from './time-widget';\nexport {default as TimeWidgetTop} from './time-widget-top';\n"
  },
  {
    "path": "src/components/src/filters/multi-select-filter.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ItemSelector from '../common/item-selector/item-selector';\nimport {PanelLabel} from '../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {MultiSelectFilterProps} from './types';\n\nexport default function MultiSelectFilterFactory() {\n  const MultiSelectFilter: React.FC<MultiSelectFilterProps> = ({filter, setFilter}) => (\n    <div>\n      <PanelLabel htmlFor={`filter-${filter.id}`}>\n        <FormattedMessage id={'misc.valuesIn'} />\n      </PanelLabel>\n      <ItemSelector options={filter.domain} selectedItems={filter.value} onChange={setFilter} />\n    </div>\n  );\n  return MultiSelectFilter;\n}\n"
  },
  {
    "path": "src/components/src/filters/polygon-filter.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, useCallback} from 'react';\nimport ItemSelector from '../common/item-selector/item-selector';\nimport {Layer} from '@kepler.gl/layers';\nimport {LAYER_TYPES} from '@kepler.gl/constants';\nimport {PolygonFilterProps} from './types';\nimport {StyledFilterPanel} from './components';\n\nconst layerFilter = (layer: Layer) => layer.type === LAYER_TYPES.point;\nconst isAlreadySelected = (selectedLayers: Layer[], layerId: string) =>\n  selectedLayers.findIndex(l => l.id === layerId) === -1;\n\nfunction PolygonFilterFactory() {\n  const PolygonFilter: React.FC<PolygonFilterProps> = React.memo(({filter, layers, setLayers}) => {\n    const setNewLayers = useCallback(\n      newLayers => {\n        return setLayers(newLayers.map((l: Layer) => l.id));\n      },\n      [setLayers]\n    );\n\n    const selectedLayers = useMemo(\n      () => layers.filter(l => filter.layerId?.includes(l.id)),\n      [filter, layers]\n    );\n\n    const availableLayers = useMemo(() => {\n      // remove already added layers and filter out non point layers\n      return layers.filter(\n        layer => layerFilter(layer) && isAlreadySelected(selectedLayers, layer.id)\n      );\n    }, [layers, selectedLayers]);\n\n    const searchOptions = useCallback((value, options) => {\n      const searchStr = value?.toLowerCase();\n      return options.filter(l => l.config?.label?.toLowerCase().indexOf(searchStr) >= 0);\n    }, []);\n\n    return (\n      <div>\n        <StyledFilterPanel htmlFor={`filter-${filter.id}`}>Layers:</StyledFilterPanel>\n        <ItemSelector\n          options={availableLayers}\n          selectedItems={selectedLayers}\n          onChange={setNewLayers}\n          searchable={true}\n          searchOptions={searchOptions}\n          multiSelect={true}\n          getOptionValue={(l: Layer) => l.id}\n          displayOption={(l: Layer) => l.config.label}\n          placeholder={'placeholder.selectLayer'}\n        />\n      </div>\n    );\n  });\n\n  PolygonFilter.displayName = 'PolygonFilter';\n\n  return PolygonFilter;\n}\n\nexport default PolygonFilterFactory;\n"
  },
  {
    "path": "src/components/src/filters/range-filter.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport RangeSliderFactory from '../common/range-slider';\nimport {FILTER_VIEW_TYPES} from '@kepler.gl/constants';\nimport {RangeFilterProps} from './types';\n\nRangeFilterFactory.deps = [RangeSliderFactory];\n\nexport default function RangeFilterFactory(RangeSlider: ReturnType<typeof RangeSliderFactory>) {\n  const RangeFilter: React.FC<RangeFilterProps> = ({filter, setFilter, setFilterPlot}) => {\n    return (\n      <div>\n        <RangeSlider\n          range={filter.domain}\n          value0={filter.value[0]}\n          value1={filter.value[1]}\n          step={filter.step}\n          bins={filter.bins}\n          isEnlarged={filter.view === FILTER_VIEW_TYPES.enlarged}\n          onChange={setFilter}\n          setFilterPlot={setFilterPlot}\n          inputTheme=\"secondary\"\n          plotType={filter.plotType}\n        />\n      </div>\n    );\n  };\n\n  return RangeFilter;\n}\n"
  },
  {
    "path": "src/components/src/filters/single-select-filter.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport ItemSelector from '../common/item-selector/item-selector';\nimport {PanelLabel, SidePanelSection} from '../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {SingleSelectFilterProps} from './types';\n\nexport default function SingleSelectFilterFactory() {\n  const SingleSelectFilter: React.FC<SingleSelectFilterProps> = ({filter, setFilter}) => (\n    <SidePanelSection>\n      <PanelLabel>\n        <FormattedMessage id={'misc.valueEquals'} />\n      </PanelLabel>\n      <ItemSelector\n        selectedItems={filter.value}\n        placeholder=\"placeholder.selectValue\"\n        options={filter.domain}\n        multiSelect={false}\n        searchable={false}\n        displayOption={d => String(d)}\n        getOptionValue={d => d}\n        onChange={setFilter}\n        inputTheme=\"secondary\"\n      />\n    </SidePanelSection>\n  );\n\n  SingleSelectFilter.displayName = 'SingleSelectFilter';\n\n  return SingleSelectFilter;\n}\n"
  },
  {
    "path": "src/components/src/filters/time-range-filter.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport TimeRangeSliderFactory from '../common/time-range-slider';\nimport {DEFAULT_TIME_FORMAT, FILTER_VIEW_TYPES} from '@kepler.gl/constants';\nimport {TimeRangeFilter} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\nimport {Layer} from '@kepler.gl/layers';\nimport {TimeRangeFilterProps} from './types';\n/*\n * TimeRangeFilter -> TimeRangeSlider -> RangeSlider\n */\nexport function timeRangeSliderFieldsSelector(\n  filter: TimeRangeFilter,\n  datasets: Datasets,\n  layers: readonly Layer[]\n) {\n  const hasUserFormat = typeof filter.timeFormat === 'string';\n  const timeFormat =\n    (hasUserFormat ? filter.timeFormat : filter.defaultTimeFormat) || DEFAULT_TIME_FORMAT;\n\n  return {\n    id: filter.id,\n    domain: filter.domain,\n    timeBins: filter.timeBins,\n    value: filter.value,\n    plotType: filter.plotType,\n    lineChart: filter.lineChart,\n    yAxis: filter.yAxis,\n    step: filter.step,\n    speed: filter.speed,\n    animationWindow: filter.animationWindow,\n    isAnimating: filter.isAnimating,\n    timezone: filter.timezone,\n    timeFormat,\n    filter,\n    datasets,\n    layers,\n    isMinified: filter.view === FILTER_VIEW_TYPES.minified,\n    isEnlarged: filter.view === FILTER_VIEW_TYPES.enlarged\n  };\n}\n\nTimeRangeFilterFactory.deps = [TimeRangeSliderFactory];\n\nfunction TimeRangeFilterFactory(TimeRangeSlider: ReturnType<typeof TimeRangeSliderFactory>) {\n  const TimeRangeFilterComponent: React.FC<TimeRangeFilterProps> = ({\n    filter,\n    datasets,\n    layers,\n    setFilter,\n    setFilterPlot,\n    isAnimatable,\n    toggleAnimation,\n    hideTimeTitle,\n    timeline\n  }) => (\n    <TimeRangeSlider\n      {...timeRangeSliderFieldsSelector(filter, datasets, layers)}\n      onChange={setFilter}\n      setFilterPlot={setFilterPlot}\n      toggleAnimation={toggleAnimation}\n      isAnimatable={isAnimatable}\n      hideTimeTitle={hideTimeTitle}\n      timeline={timeline}\n    />\n  );\n\n  return TimeRangeFilterComponent;\n}\n\nexport default TimeRangeFilterFactory;\n"
  },
  {
    "path": "src/components/src/filters/time-widget-top.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {Clock, Close, LineChart, ArrowDown, ArrowUp} from '../common/icons';\nimport FieldSelectorFactory from '../common/field-selector';\nimport {SelectTextBold, IconRoundSmall, CenterFlexbox} from '../common/styled-components';\nimport {TimeWidgetTopProps, TopSectionWrapperProps} from './types';\nimport {Field} from '@kepler.gl/types';\n\nconst TOP_SECTION_HEIGHT = '36px';\n\nconst TopSectionWrapper: IStyledComponent<'web', TopSectionWrapperProps> = styled.div.attrs({\n  className: 'time-widget--top'\n})<TopSectionWrapperProps>`\n  display: flex;\n  justify-content: space-between;\n  width: 100%;\n  color: ${props => props.theme.labelColor};\n  height: ${TOP_SECTION_HEIGHT};\n\n  .bottom-widget__y-axis {\n    flex-grow: 1;\n    margin-left: 20px;\n  }\n\n  .bottom-widget__field-select {\n    width: 160px;\n    display: inline-block;\n\n    .item-selector__dropdown {\n      background: transparent;\n      padding: 4px 10px 4px 4px;\n      border-color: transparent;\n\n      &:active,\n      &:focus,\n      &.focus,\n      &.active {\n        background: transparent;\n        border-color: transparent;\n      }\n    }\n\n    .item-selector__dropdown:hover {\n      background: transparent;\n      border-color: transparent;\n\n      .item-selector__dropdown__value {\n        color: ${props =>\n          props.hoverColor ? props.theme[props.hoverColor] : props.theme.textColorHl};\n      }\n    }\n  }\n\n  .animation-control__speed-control {\n    margin-right: -12px;\n\n    .animation-control__speed-slider {\n      right: calc(0% - 48px);\n    }\n  }\n`;\n\nconst StyledTitle = styled(CenterFlexbox)`\n  flex-grow: 0;\n  color: ${props => props.theme.textColor};\n  margin-right: 10px;\n\n  .bottom-widget__icon {\n    margin-right: 6px;\n  }\n  .bottom-widget__icon.speed {\n    margin-right: 0;\n  }\n`;\n\nconst StyledCenterBox = styled(CenterFlexbox)`\n  > div {\n    margin-left: 4px;\n  }\n`;\n\nTimeWidgetTopFactory.deps = [FieldSelectorFactory];\nfunction TimeWidgetTopFactory(FieldSelector: ReturnType<typeof FieldSelectorFactory>) {\n  const TimeWidgetTop: React.FC<TimeWidgetTopProps> = ({\n    filter,\n    readOnly,\n    datasets,\n    setFilterPlot,\n    onClose,\n    isMinified,\n    onToggleMinify\n  }) => {\n    const yAxisFields = useMemo(\n      () =>\n        ((datasets[filter.dataId[0]] || {}).fields || []).filter(\n          (f: Field) => f.type === 'integer' || f.type === 'real'\n        ),\n      [datasets, filter.dataId]\n    );\n    const _setFilterPlotYAxis = useCallback(\n      value => setFilterPlot({yAxis: value}),\n      [setFilterPlot]\n    );\n\n    return (\n      <TopSectionWrapper>\n        <StyledTitle className=\"bottom-widget__field\">\n          <CenterFlexbox className=\"bottom-widget__icon\">\n            <Clock height=\"15px\" />\n          </CenterFlexbox>\n          <SelectTextBold>{filter.name}</SelectTextBold>\n        </StyledTitle>\n        {!isMinified ? (\n          <StyledTitle className=\"bottom-widget__y-axis\">\n            <CenterFlexbox className=\"bottom-widget__icon\">\n              <LineChart height=\"15px\" />\n            </CenterFlexbox>\n            <div className=\"bottom-widget__field-select\">\n              <FieldSelector\n                fields={yAxisFields}\n                placement=\"top\"\n                id=\"selected-time-widget-field\"\n                value={filter.yAxis ? filter.yAxis.name : null}\n                onSelect={_setFilterPlotYAxis}\n                placeholder=\"placeholder.yAxis\"\n                erasable\n                showToken={false}\n              />\n            </div>\n          </StyledTitle>\n        ) : null}\n        <StyledCenterBox>\n          <IconRoundSmall>\n            {isMinified ? (\n              <ArrowUp height=\"12px\" onClick={onToggleMinify} />\n            ) : (\n              <ArrowDown height=\"12px\" onClick={onToggleMinify} />\n            )}\n          </IconRoundSmall>\n          {!readOnly ? (\n            <IconRoundSmall>\n              <Close height=\"12px\" onClick={onClose} />\n            </IconRoundSmall>\n          ) : null}\n        </StyledCenterBox>\n      </TopSectionWrapper>\n    );\n  };\n  return TimeWidgetTop;\n}\n\nexport default TimeWidgetTopFactory;\n"
  },
  {
    "path": "src/components/src/filters/time-widget.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport {FILTER_VIEW_TYPES} from '@kepler.gl/constants';\nimport {BottomWidgetInner} from '../common/styled-components';\nimport TimeRangeSliderFactory from '../common/time-range-slider';\nimport FloatingTimeDisplayFactory from '../common/animation-control/floating-time-display';\nimport {timeRangeSliderFieldsSelector} from './time-range-filter';\nimport {TimeWidgetProps} from './types';\nimport TimeWidgetTopFactory from './time-widget-top';\n\nconst TimeBottomWidgetInner = styled(BottomWidgetInner)`\n  padding: 6px 32px 24px 32px;\n`;\n\nTimeWidgetFactory.deps = [TimeRangeSliderFactory, FloatingTimeDisplayFactory, TimeWidgetTopFactory];\n\nfunction TimeWidgetFactory(\n  TimeRangeSlider: ReturnType<typeof TimeRangeSliderFactory>,\n  FloatingTimeDisplay: ReturnType<typeof FloatingTimeDisplayFactory>,\n  TimeWidgetTop: ReturnType<typeof TimeWidgetTopFactory>\n) {\n  const TimeWidget: React.FC<TimeWidgetProps> = ({\n    datasets,\n    filter,\n    layers,\n    index,\n    readOnly,\n    showTimeDisplay,\n    setFilterAnimationTime,\n    onClose,\n    onToggleMinify,\n    resetAnimation,\n    isAnimatable,\n    updateAnimationSpeed,\n    toggleAnimation,\n    setFilterPlot,\n    setFilterAnimationWindow,\n    animationConfig,\n    timeline\n  }: TimeWidgetProps) => {\n    const _updateAnimationSpeed = useCallback(\n      speed => updateAnimationSpeed(index, speed),\n      [updateAnimationSpeed, index]\n    );\n\n    const _toggleAnimation = useCallback(() => toggleAnimation(index), [toggleAnimation, index]);\n\n    const isMinified = useMemo(() => filter.view === FILTER_VIEW_TYPES.minified, [filter]);\n\n    const _setFilterAnimationWindow = useCallback(\n      animationWindow => setFilterAnimationWindow({id: filter.id, animationWindow}),\n      [setFilterAnimationWindow, filter.id]\n    );\n\n    const timeSliderOnChange = useCallback(\n      value => setFilterAnimationTime(index, 'value', value),\n      [setFilterAnimationTime, index]\n    );\n\n    const _setFilterPlot = useCallback(\n      (newProp, valueIndex) => setFilterPlot(index, newProp, valueIndex),\n      [index, setFilterPlot]\n    );\n\n    const timeRangeSlideProps = useMemo(\n      () => timeRangeSliderFieldsSelector(filter, datasets, layers),\n      [filter, datasets, layers]\n    );\n\n    return (\n      <TimeBottomWidgetInner className=\"bottom-widget--inner\">\n        <TimeWidgetTop\n          filter={filter}\n          readOnly={readOnly}\n          datasets={datasets}\n          setFilterPlot={_setFilterPlot}\n          index={index}\n          onClose={onClose}\n          onToggleMinify={onToggleMinify}\n          isMinified={isMinified}\n        />\n        {/* Once AnimationControl is able to display large timeline*/}\n        {/* we can replace TimeRangeSlider with AnimationControl*/}\n        <TimeRangeSlider\n          {...timeRangeSlideProps}\n          onChange={timeSliderOnChange}\n          toggleAnimation={_toggleAnimation}\n          updateAnimationSpeed={_updateAnimationSpeed}\n          setFilterAnimationWindow={_setFilterAnimationWindow}\n          hideTimeTitle={showTimeDisplay}\n          resetAnimation={resetAnimation}\n          isAnimatable={isAnimatable}\n          setFilterPlot={_setFilterPlot}\n          animationConfig={animationConfig}\n          isMinified={isMinified}\n          timeline={timeline}\n        />\n        {showTimeDisplay ? (\n          <FloatingTimeDisplay\n            currentTime={filter.value}\n            defaultTimeFormat={filter.defaultTimeFormat}\n            timeFormat={filter.timeFormat}\n            timezone={filter.timezone}\n          />\n        ) : null}\n      </TimeBottomWidgetInner>\n    );\n  };\n\n  return React.memo(TimeWidget);\n}\n\nexport default TimeWidgetFactory;\n"
  },
  {
    "path": "src/components/src/filters/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CSSProperties} from 'react';\nimport {\n  Filter,\n  MultiSelectFilter,\n  RangeFilter,\n  SelectFilter,\n  TimeRangeFilter,\n  Timeline,\n  AnimationConfig\n} from '@kepler.gl/types';\nimport {Layer} from '@kepler.gl/layers';\nimport {\n  ActionHandler,\n  setFilterAnimationTime,\n  setFilterAnimationWindow,\n  setTimeFilterSyncTimelineMode,\n  setFilterPlot,\n  toggleFilterAnimation,\n  updateFilterAnimationSpeed\n} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\nimport {BaseComponentProps} from '../types';\n\nexport type PolygonFilterProps = {\n  filter: Filter;\n  layers: ReadonlyArray<Layer>;\n  setLayers: (ids: ReadonlyArray<string>) => void;\n};\n\nexport type TopSectionWrapperProps = BaseComponentProps & {\n  hoverColor?: CSSProperties['color'];\n};\n\nexport type RangeFilterProps = {\n  filter: RangeFilter;\n  setFilter: (v: number[]) => void;\n  setFilterPlot?: ActionHandler<typeof setFilterPlot>;\n};\n\nexport type TimeRangeFilterProps = {\n  idx: number;\n  filter: TimeRangeFilter;\n  isAnimatable: boolean;\n  hideTimeTitle: boolean;\n  setFilter: (v: number[]) => void;\n  setFilterPlot: ActionHandler<typeof setFilterPlot>;\n  toggleAnimation: () => void;\n  timeline: Timeline;\n  datasets: Datasets;\n  layers: readonly Layer[];\n};\n\nexport type SingleSelectFilterProps = {\n  filter: SelectFilter;\n  setFilter: (v: string | number | boolean | object | null) => void;\n};\n\nexport type MultiSelectFilterProps = {\n  filter: MultiSelectFilter;\n  setFilter: (\n    v: ReadonlyArray<string | number | boolean | object> | string | number | boolean | object | null\n  ) => void;\n};\n\nexport type TimeWidgetTopProps = {\n  filter: Filter;\n  readOnly: boolean;\n  datasets: Datasets;\n  setFilterPlot: (newProp, valueIndex?: number) => void;\n  index: number;\n  onClose: () => void;\n  onToggleMinify: () => void;\n  isMinified: boolean;\n};\n\nexport type TimeWidgetProps = {\n  layers: Layer[];\n  datasets: Datasets;\n  filter: TimeRangeFilter;\n  index: number;\n  readOnly: boolean;\n  showTimeDisplay: boolean;\n  isAnimatable: boolean;\n  resetAnimation: () => void;\n  onClose: () => void;\n  onToggleMinify: () => void;\n  setFilterAnimationTime: ActionHandler<typeof setFilterAnimationTime>;\n  updateAnimationSpeed: ActionHandler<typeof updateFilterAnimationSpeed>;\n  toggleAnimation: ActionHandler<typeof toggleFilterAnimation>;\n  setFilterPlot: ActionHandler<typeof setFilterPlot>;\n  setFilterAnimationWindow: ActionHandler<typeof setFilterAnimationWindow>;\n  setFilterSyncTimelineMode: ActionHandler<typeof setTimeFilterSyncTimelineMode>;\n  timeline: Timeline;\n  animationConfig: AnimationConfig;\n};\n"
  },
  {
    "path": "src/components/src/geocoder/geocoder.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, useState} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport geocoderService from '@mapbox/mapbox-sdk/services/geocoding';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {WebMercatorViewport} from 'viewport-mercator-project';\nimport {KeyEvent} from '@kepler.gl/constants';\nimport {Input} from '../common/styled-components';\nimport {Search, Delete} from '../common/icons';\nimport {Viewport} from '@kepler.gl/types';\nimport {isTest} from '@kepler.gl/utils';\n\ntype StyledContainerProps = {\n  width?: number;\n};\n\n// matches only valid coordinates\nconst COORDINATE_REGEX_STRING =\n  '(^[-+]?(?:[1-8]?\\\\d(?:\\\\.\\\\d+)?|90(?:\\\\.0+)?)),\\\\s*([-+]?(?:180(?:\\\\.0+)?|(?:(?:1[0-7]\\\\d)|(?:[1-9]?\\\\d))(?:\\\\.\\\\d+)?))$';\n\nconst COORDINATE_REGEX = RegExp(COORDINATE_REGEX_STRING);\n\nconst PLACEHOLDER = 'Enter an address or coordinates, ex 37.79,-122.40';\n\nlet debounceTimeout: NodeJS.Timeout | null = null;\n\n/**\n * Tests if a given query string contains valid coordinates.\n * @param query The input string to test for coordinates.\n * @returns A tuple where:\n *   - If valid, returns `[true, longitude, latitude]`.\n *   - If invalid, returns `[false, query]`.\n */\nexport const testForCoordinates = (query: string): [true, number, number] | [false, string] => {\n  const isValid = COORDINATE_REGEX.test(query.trim());\n\n  if (!isValid) {\n    return [isValid, query];\n  }\n\n  const tokens = query.trim().split(',');\n  const latitude = Number(tokens[0]);\n  const longitude = Number(tokens[1]);\n\n  return [isValid, longitude, latitude];\n};\n\nconst StyledContainer = styled.div<StyledContainerProps>`\n  position: relative;\n  color: ${props => props.theme.textColor};\n\n  .geocoder-input {\n    box-shadow: ${props => props.theme.boxShadow};\n\n    .geocoder-input__search {\n      position: absolute;\n      height: ${props => props.theme.geocoderInputHeight}px;\n      width: 30px;\n      padding-left: 6px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: ${props => props.theme.subtextColor};\n    }\n\n    input {\n      padding: 4px 36px;\n      height: ${props => props.theme.geocoderInputHeight}px;\n      caret-color: unset;\n    }\n  }\n\n  .geocoder-results {\n    box-shadow: ${props => props.theme.boxShadow};\n    background-color: ${props => props.theme.panelBackground};\n    position: absolute;\n    width: ${props => (Number.isFinite(props.width) ? props.width : props.theme.geocoderWidth)}px;\n    margin-top: ${props => props.theme.dropdownWapperMargin}px;\n  }\n\n  .geocoder-item {\n    ${props => props.theme.dropdownListItem};\n    ${props => props.theme.textTruncate};\n\n    &.active {\n      background-color: ${props => props.theme.dropdownListHighlightBg};\n    }\n  }\n\n  .remove-result {\n    position: absolute;\n    right: 16px;\n    top: 0px;\n    height: ${props => props.theme.geocoderInputHeight}px;\n    display: flex;\n    align-items: center;\n\n    &:hover {\n      cursor: pointer;\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nexport interface Result {\n  center: [number, number];\n  place_name: string;\n  bbox?: [number, number, number, number];\n  text?: string;\n}\n\nexport type Results = ReadonlyArray<Result>;\n\ntype GeocoderProps = {\n  mapboxApiAccessToken: string;\n  className?: string;\n  limit?: number;\n  timeout?: number;\n  formatItem?: (item: Result) => string;\n  viewport?: Viewport;\n  onSelected: (viewport: Viewport | null, item: Result) => void;\n  onDeleteMarker?: () => void;\n  transitionDuration?: number;\n  pointZoom?: number;\n  width?: number;\n};\n\ntype IntlProps = {\n  intl: IntlShape;\n};\n\nconst GeoCoder: React.FC<GeocoderProps & IntlProps> = ({\n  mapboxApiAccessToken,\n  className = '',\n  limit = 5,\n  timeout = 300,\n  formatItem = item => item.place_name,\n  viewport,\n  onSelected,\n  onDeleteMarker,\n  transitionDuration,\n  pointZoom,\n  width,\n  intl\n}) => {\n  const [inputValue, setInputValue] = useState('');\n  const [showResults, setShowResults] = useState(false);\n  const [showDelete, setShowDelete] = useState(false);\n  const initialResults: Result[] = [];\n  const [results, setResults] = useState(initialResults);\n  const [selectedIndex, setSelectedIndex] = useState(0);\n\n  const client = useMemo(\n    () => (isTest() ? null : geocoderService({accessToken: mapboxApiAccessToken})),\n    [mapboxApiAccessToken]\n  );\n\n  const onChange = useCallback(\n    event => {\n      const queryString = event.target.value;\n      setInputValue(queryString);\n      const resultCoordinates = testForCoordinates(queryString);\n      if (resultCoordinates[0]) {\n        const [_isValid, longitude, latitude] = resultCoordinates;\n        setResults([{center: [longitude, latitude], place_name: queryString}]);\n      } else {\n        if (debounceTimeout) {\n          clearTimeout(debounceTimeout);\n        }\n        debounceTimeout = setTimeout(async () => {\n          if (limit > 0 && Boolean(queryString)) {\n            try {\n              const response = await client\n                .forwardGeocode({\n                  query: queryString,\n                  limit\n                })\n                .send();\n              if (response.body.features) {\n                setShowResults(true);\n                setResults(response.body.features);\n              }\n            } catch (e) {\n              // TODO: show geocode error\n              // eslint-disable-next-line no-console\n              console.log(e);\n            }\n          }\n        }, timeout);\n      }\n    },\n    [client, limit, timeout, setResults, setShowResults]\n  );\n\n  const onBlur = useCallback(() => {\n    setTimeout(() => {\n      setShowResults(false);\n    }, timeout);\n  }, [setShowResults, timeout]);\n\n  const onFocus = useCallback(() => setShowResults(true), [setShowResults]);\n\n  const onItemSelected = useCallback(\n    item => {\n      const newViewport = new WebMercatorViewport(viewport);\n      const {bbox, center} = item;\n\n      const gotoViewport = bbox\n        ? newViewport.fitBounds([\n            [bbox[0], bbox[1]],\n            [bbox[2], bbox[3]]\n          ])\n        : {\n            longitude: center[0],\n            latitude: center[1],\n            zoom: pointZoom\n          };\n\n      const {longitude, latitude, zoom} = gotoViewport;\n\n      onSelected({...viewport, ...{longitude, latitude, zoom, transitionDuration}}, item);\n\n      setShowResults(false);\n      setInputValue(formatItem(item));\n      setShowDelete(true);\n    },\n    [viewport, onSelected, transitionDuration, pointZoom, formatItem]\n  );\n\n  const onMarkDeleted = useCallback(() => {\n    setShowDelete(false);\n    setInputValue('');\n    onDeleteMarker?.();\n  }, [onDeleteMarker]);\n\n  const onKeyDown = useCallback(\n    e => {\n      if (!results || results.length === 0) {\n        return;\n      }\n      switch (e.keyCode) {\n        case KeyEvent.DOM_VK_UP:\n          setSelectedIndex(selectedIndex > 0 ? selectedIndex - 1 : selectedIndex);\n          break;\n        case KeyEvent.DOM_VK_DOWN:\n          setSelectedIndex(selectedIndex < results.length - 1 ? selectedIndex + 1 : selectedIndex);\n          break;\n        case KeyEvent.DOM_VK_ENTER:\n        case KeyEvent.DOM_VK_RETURN:\n          if (results[selectedIndex]) {\n            onItemSelected(results[selectedIndex]);\n          }\n          break;\n        default:\n          break;\n      }\n    },\n    [results, selectedIndex, setSelectedIndex, onItemSelected]\n  );\n\n  return (\n    <StyledContainer className={className} width={width}>\n      <div className=\"geocoder-input\">\n        <div className=\"geocoder-input__search\">\n          <Search height=\"20px\" />\n        </div>\n        <Input\n          type=\"text\"\n          onChange={onChange}\n          onBlur={onBlur}\n          onFocus={onFocus}\n          onKeyDown={onKeyDown}\n          value={inputValue}\n          placeholder={\n            intl\n              ? intl.formatMessage({id: 'geocoder.title', defaultMessage: PLACEHOLDER})\n              : PLACEHOLDER\n          }\n        />\n        {showDelete ? (\n          <div className=\"remove-result\">\n            <Delete height=\"16px\" onClick={onMarkDeleted} />\n          </div>\n        ) : null}\n      </div>\n\n      {showResults ? (\n        <div className=\"geocoder-results\">\n          {results.map((item, index) => (\n            <div\n              key={index}\n              className={classnames('geocoder-item', {active: selectedIndex === index})}\n              onClick={() => onItemSelected(item)}\n            >\n              {formatItem(item)}\n            </div>\n          ))}\n        </div>\n      ) : null}\n    </StyledContainer>\n  );\n};\n\nexport default injectIntl(GeoCoder);\n"
  },
  {
    "path": "src/components/src/geocoder-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport classnames from 'classnames';\nimport {processRowObject} from '@kepler.gl/processors';\nimport {FlyToInterpolator} from '@deck.gl/core/typed';\nimport {getCenterAndZoomFromBounds} from '@kepler.gl/utils';\nimport {\n  GEOCODER_DATASET_NAME,\n  GEOCODER_LAYER_ID,\n  GEOCODER_GEO_OFFSET,\n  GEOCODER_ICON_COLOR,\n  GEOCODER_ICON_SIZE\n} from '@kepler.gl/constants';\nimport {AddDataToMapOptions, MapState, ProtoDataset, UiState, Viewport} from '@kepler.gl/types';\nimport {ActionHandler, removeDataset, updateMap, updateVisData} from '@kepler.gl/actions';\n\nimport Geocoder, {Result} from './geocoder/geocoder';\nimport {MapViewState} from '@deck.gl/core/typed';\n\nimport {BaseComponentProps} from './types';\n\nconst ICON_LAYER = {\n  id: GEOCODER_LAYER_ID,\n  type: 'icon',\n  config: {\n    label: 'Geocoder Layer',\n    color: GEOCODER_ICON_COLOR,\n    dataId: GEOCODER_DATASET_NAME,\n    columns: {\n      lat: 'lt',\n      lng: 'ln',\n      icon: 'icon',\n      label: 'text'\n    },\n    isVisible: true,\n    hidden: true,\n    visConfig: {\n      radius: GEOCODER_ICON_SIZE\n    }\n  }\n};\n\nfunction generateConfig(layerOrder) {\n  return {\n    visState: {\n      layers: [ICON_LAYER],\n      layerOrder: [ICON_LAYER.id, ...layerOrder]\n    }\n  };\n}\n\nexport type StyledGeocoderPanelProps = BaseComponentProps & {\n  unsyncedViewports?: boolean;\n  index?: number;\n  width?: number;\n};\n\nconst StyledGeocoderPanel: IStyledComponent<\n  'web',\n  StyledGeocoderPanelProps\n> = styled.div<StyledGeocoderPanelProps>`\n  position: absolute;\n  top: ${props => props.theme.geocoderTop}px;\n  right: ${props =>\n    props.unsyncedViewports\n      ? // 2 geocoders: split mode and unsynced viewports\n        Number.isFinite(props.index) && props.index === 0\n        ? `calc(50% + ${props.theme.geocoderRight}px)` // unsynced left geocoder (index 0)\n        : `${props.theme.geocoderRight}px` // unsynced right geocoder (index 1)\n      : // 1 geocoder: single mode OR split mode and synced viewports\n        `${props.theme.geocoderRight}px`};\n  width: ${props => (Number.isFinite(props.width) ? props.width : props.theme.geocoderWidth)}px;\n  box-shadow: ${props => props.theme.boxShadow};\n  z-index: 100;\n`;\n\nfunction generateGeocoderDataset(lat: number, lon: number, text?: string): ProtoDataset | null {\n  const data = processRowObject([\n    {\n      lt: lat,\n      ln: lon,\n      icon: 'place',\n      text\n    }\n  ]);\n  if (!data) {\n    return null;\n  }\n\n  return {\n    data,\n    info: {\n      hidden: true,\n      id: GEOCODER_DATASET_NAME,\n      label: GEOCODER_DATASET_NAME\n    }\n  };\n}\n\nfunction isValid(key) {\n  return /pk\\..*\\..*/.test(key);\n}\n\nexport function getUpdateVisDataPayload(\n  lat: number,\n  lon: number,\n  text?: string\n): [ProtoDataset[], AddDataToMapOptions] | null {\n  const dataset = generateGeocoderDataset(lat, lon, text);\n  if (!dataset) {\n    return null;\n  }\n  return [\n    [dataset],\n    {\n      keepExistingConfig: true\n    }\n  ];\n}\n\ninterface GeocoderPanelProps {\n  isGeocoderEnabled: boolean;\n  mapState: MapState;\n  uiState: UiState;\n  mapboxApiAccessToken: string;\n  updateVisData: ActionHandler<typeof updateVisData>;\n  removeDataset: ActionHandler<typeof removeDataset>;\n  updateMap: ActionHandler<typeof updateMap>;\n  layerOrder: string[];\n\n  transitionDuration?: MapViewState['transitionDuration'];\n  width?: number;\n  className?: string;\n  index: number;\n  unsyncedViewports: boolean;\n}\n\nexport default function GeocoderPanelFactory(): React.FC<GeocoderPanelProps> {\n  const GeocoderPanel = ({\n    isGeocoderEnabled,\n    mapState,\n    mapboxApiAccessToken,\n    updateVisData,\n    removeDataset,\n    updateMap,\n    layerOrder,\n    transitionDuration = 3000,\n    width,\n    className,\n    index,\n    unsyncedViewports\n  }: GeocoderPanelProps) => {\n    const removeGeocoderDataset = useCallback(() => {\n      removeDataset(GEOCODER_DATASET_NAME);\n    }, [removeDataset]);\n\n    const onSelected = useCallback(\n      (_viewport: Viewport | null = null, geoItem: Result) => {\n        const {\n          center: [lon, lat],\n          text,\n          bbox\n        } = geoItem;\n\n        const updateVisDataPayload = getUpdateVisDataPayload(lat, lon, text);\n        if (updateVisDataPayload) {\n          removeGeocoderDataset();\n          updateVisData(...updateVisDataPayload, generateConfig(layerOrder));\n        }\n\n        const bounds = bbox || [\n          lon - GEOCODER_GEO_OFFSET,\n          lat - GEOCODER_GEO_OFFSET,\n          lon + GEOCODER_GEO_OFFSET,\n          lat + GEOCODER_GEO_OFFSET\n        ];\n        const centerAndZoom = getCenterAndZoomFromBounds(bounds, {\n          width: mapState.width,\n          height: mapState.height\n        });\n\n        if (!centerAndZoom) {\n          // failed to fit bounds\n          return;\n        }\n\n        updateMap(\n          {\n            latitude: centerAndZoom.center[1],\n            longitude: centerAndZoom.center[0],\n            // For marginal or invalid bounds, zoom may be NaN. Make sure to provide a valid value in order\n            // to avoid corrupt state and potential crashes as zoom is expected to be a number\n            ...(Number.isFinite(centerAndZoom.zoom) ? {zoom: centerAndZoom.zoom} : {}),\n            pitch: 0,\n            bearing: 0,\n            transitionDuration,\n            transitionInterpolator: new FlyToInterpolator()\n          },\n          index\n        );\n      },\n      [\n        index,\n        layerOrder,\n        mapState,\n        removeGeocoderDataset,\n        transitionDuration,\n        updateMap,\n        updateVisData\n      ]\n    );\n\n    return (\n      <StyledGeocoderPanel\n        className={classnames('geocoder-panel', className)}\n        width={width}\n        index={index}\n        unsyncedViewports={unsyncedViewports}\n        style={{display: isGeocoderEnabled ? 'block' : 'none'}}\n      >\n        {isValid(mapboxApiAccessToken) && (\n          <Geocoder\n            mapboxApiAccessToken={mapboxApiAccessToken}\n            onSelected={onSelected}\n            onDeleteMarker={removeGeocoderDataset}\n            width={width}\n          />\n        )}\n      </StyledGeocoderPanel>\n    );\n  };\n\n  return GeocoderPanel;\n}\n"
  },
  {
    "path": "src/components/src/hooks/use-cloud-list-provider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PropsWithChildren, useCallback, useContext, useMemo, useRef, useState} from 'react';\nimport {CloudProviderContext} from '../context';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\ntype CloudListProviderProps = PropsWithChildren<{\n  providers: Provider[];\n}>;\n\nexport const CloudListProvider: React.FC<CloudListProviderProps> = ({children, providers = []}) => {\n  const [currentCloudProvider, setCurrentCloudProvider] = useState<Provider | null>(null);\n  const cloudProviders = useRef(providers);\n\n  const setProvider = useCallback(\n    provider => {\n      setCurrentCloudProvider(currentCloudProvider === provider ? null : provider);\n    },\n    [currentCloudProvider]\n  );\n\n  const value = useMemo(\n    () => ({\n      provider: currentCloudProvider,\n      setProvider,\n      cloudProviders: cloudProviders.current\n    }),\n    [currentCloudProvider, setProvider]\n  );\n\n  return <CloudProviderContext.Provider value={value}>{children}</CloudProviderContext.Provider>;\n};\n\n/**\n * this hook provides access the CloudList provider context\n */\nexport const useCloudListProvider = () => useContext(CloudProviderContext);\n"
  },
  {
    "path": "src/components/src/hooks/use-dnd-effects.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useCallback, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {DragEndEvent, DragStartEvent} from '@dnd-kit/core';\n\nimport {reorderEffect, updateEffect} from '@kepler.gl/actions';\nimport {reorderEffectOrder} from '@kepler.gl/utils';\nimport {Effect} from '@kepler.gl/types';\nimport {SORTABLE_EFFECT_PANEL_TYPE, SORTABLE_EFFECT_TYPE} from '../common/dnd-layer-items';\n\ntype DndEffectsHook = {\n  activeEffect: Effect | undefined;\n  onDragStart: (event: DragStartEvent) => void;\n  onDragEnd: (event: DragEndEvent) => void;\n};\n\nconst useDndEffects: (effects: Effect[], effectOrder: string[]) => DndEffectsHook = (\n  effects,\n  effectOrder\n) => {\n  const dispatch = useDispatch();\n  const [activeEffect, setActiveEffect]: [\n    Effect | undefined,\n    (effect: Effect | undefined) => void\n  ] = useState();\n  const onEffectDragStart = useCallback(\n    event => {\n      const {active} = event;\n      const newActiveEffect = effects.find(effect => effect.id === active.id);\n      if (newActiveEffect) {\n        setActiveEffect(newActiveEffect);\n        if (newActiveEffect.isConfigActive) {\n          dispatch(updateEffect(newActiveEffect.id, {isConfigActive: false}));\n        }\n      }\n    },\n    [dispatch, effects]\n  );\n\n  const onEffectDragEnd = useCallback(\n    event => {\n      const {active, over} = event;\n\n      const {id: activeEffectId} = active;\n      const overType = over?.data?.current?.type;\n\n      if (!overType) {\n        setActiveEffect(undefined);\n        return;\n      }\n\n      switch (overType) {\n        // swaping effects\n        case SORTABLE_EFFECT_TYPE:\n          dispatch(reorderEffect(reorderEffectOrder(effectOrder, activeEffectId, over.id)));\n          break;\n        //  moving effects within side panel\n        case SORTABLE_EFFECT_PANEL_TYPE:\n          // move effect to the end of the list\n          dispatch(\n            reorderEffect(\n              reorderEffectOrder(effectOrder, activeEffectId, effectOrder[effectOrder.length - 1])\n            )\n          );\n          break;\n        default:\n          break;\n      }\n\n      setActiveEffect(undefined);\n    },\n    [dispatch, effectOrder]\n  );\n\n  return {activeEffect, onDragStart: onEffectDragStart, onDragEnd: onEffectDragEnd};\n};\n\nexport default useDndEffects;\n"
  },
  {
    "path": "src/components/src/hooks/use-dnd-layers.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useCallback, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {DragEndEvent, DragStartEvent} from '@dnd-kit/core';\nimport {layerConfigChange, reorderLayer, toggleLayerForMap} from '@kepler.gl/actions';\nimport {\n  DROPPABLE_MAP_CONTAINER_TYPE,\n  SORTABLE_LAYER_TYPE,\n  SORTABLE_SIDE_PANEL_TYPE\n} from '../common/dnd-layer-items';\nimport {reorderLayerOrder} from '@kepler.gl/reducers';\nimport {Layer} from '@kepler.gl/layers';\n\ntype DndEffectsHook = {\n  activeLayer: Layer | undefined;\n  onDragStart: (event: DragStartEvent) => void;\n  onDragEnd: (event: DragEndEvent) => void;\n};\n\nconst useDndLayers: (layers: Layer[], layerOrder: string[]) => DndEffectsHook = (\n  layers,\n  layerOrder\n) => {\n  const dispatch = useDispatch();\n\n  const [activeLayer, setActiveLayer]: [\n    activeEffect: Layer | undefined,\n    setActiveEffect: (effect: Layer | undefined) => void\n  ] = useState();\n\n  const onDragStart = useCallback(\n    event => {\n      const {active} = event;\n      const newActiveLayer = layers.find(layer => layer.id === active.id);\n      if (newActiveLayer) {\n        setActiveLayer(newActiveLayer);\n        if (newActiveLayer?.config.isConfigActive) {\n          dispatch(layerConfigChange(newActiveLayer, {isConfigActive: false}));\n        }\n      }\n    },\n    [dispatch, layers]\n  );\n\n  const onDragEnd = useCallback(\n    event => {\n      const {active, over} = event;\n\n      const {id: activeLayerId} = active;\n      const overType = over?.data?.current?.type;\n\n      if (!overType) {\n        setActiveLayer(undefined);\n        return;\n      }\n\n      switch (overType) {\n        // moving layers into maps\n        case DROPPABLE_MAP_CONTAINER_TYPE: {\n          const mapIndex = over.data.current?.index ?? 0;\n          dispatch(toggleLayerForMap(mapIndex, activeLayerId));\n          break;\n        }\n        // swaping layers\n        case SORTABLE_LAYER_TYPE: {\n          const newLayerOrder = reorderLayerOrder(layerOrder, activeLayerId, over.id);\n          dispatch(reorderLayer(newLayerOrder));\n          break;\n        }\n        //  moving layers within side panel\n        case SORTABLE_SIDE_PANEL_TYPE:\n          // move layer to the end of the list\n          dispatch(\n            reorderLayer(\n              reorderLayerOrder(layerOrder, activeLayerId, layerOrder[layerOrder.length - 1])\n            )\n          );\n          break;\n        default:\n          break;\n      }\n\n      setActiveLayer(undefined);\n    },\n    [dispatch, layerOrder]\n  );\n\n  return {activeLayer, onDragStart, onDragEnd};\n};\n\nexport default useDndLayers;\n"
  },
  {
    "path": "src/components/src/hooks/use-feature-flags.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useContext} from 'react';\nimport {FeatureFlagsContext} from '../context';\n\ntype FeatureFlags = {\n  // Define your feature flags here\n  [flagName: string]: boolean;\n};\n\n// @ts-expect-error FeatureFlagsContext is typed as object\nconst useFeatureFlags = () => useContext<FeatureFlags>(FeatureFlagsContext);\n\nexport default useFeatureFlags;\n"
  },
  {
    "path": "src/components/src/hooks/use-fetch-raster-tile-metadata.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {fetch} from 'global';\nimport {useEffect, useState} from 'react';\n\nimport {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';\n\nimport {VectorTileMetadata} from '@kepler.gl/table';\nimport {JsonObjectOrArray, StacTypes} from '@kepler.gl/types';\nimport {RasterTileType} from '@kepler.gl/constants';\n\ntype RasterMetadataResponse = VectorTileMetadata | StacTypes.CompleteSTACObject | null;\n\nexport type FetchJsonProps = {\n  url: string | null;\n  rasterTileType: RasterTileType;\n  options?: JsonObjectOrArray;\n  process: (\n    json: PMTilesMetadata | JsonObjectOrArray,\n    options: {metadataUrl: string; rasterTileType: RasterTileType}\n  ) => RasterMetadataResponse | Error;\n};\n\nexport type UseFetchJsonReturn = {\n  data: RasterMetadataResponse;\n  loading: boolean;\n  error: Error | null;\n};\n\nexport default function useFetchJson({\n  url,\n  rasterTileType,\n  options,\n  process\n}: FetchJsonProps): UseFetchJsonReturn {\n  const [error, setError] = useState<Error | null>(null);\n  const [data, setData] = useState<RasterMetadataResponse>(null);\n  const [loading, setLoading] = useState<boolean>(false);\n\n  useEffect(() => {\n    const getAndProcessMetadata = async () => {\n      setError(null);\n      setData(null);\n      if (url) {\n        setLoading(true);\n\n        try {\n          let rawMetadata: PMTilesMetadata | JsonObjectOrArray | null = null;\n          if (rasterTileType === RasterTileType.PMTILES) {\n            const tileSource = PMTilesSource.createDataSource(url, {});\n            rawMetadata = await tileSource.metadata;\n          } else {\n            const response = await fetch(url, options);\n            if (!response.ok) {\n              throw new Error(`Failed Fetch ${url}`);\n            }\n            rawMetadata = await response.json();\n          }\n\n          if (!rawMetadata) {\n            throw new Error('Failed to fetch metadata');\n          }\n\n          const processedMetadata = process(rawMetadata, {rasterTileType, metadataUrl: url});\n          if (processedMetadata instanceof Error) {\n            setError(processedMetadata);\n          } else {\n            setError(null);\n            setData(processedMetadata);\n          }\n        } catch (metadataError) {\n          setError(metadataError as Error);\n        }\n        setLoading(false);\n      }\n    };\n\n    getAndProcessMetadata();\n  }, [url, rasterTileType, options, process]);\n\n  return {data, loading, error};\n}\n"
  },
  {
    "path": "src/components/src/hooks/use-fetch-vector-tile-metadata.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useEffect, useState} from 'react';\n\nimport {TileJSON} from '@loaders.gl/mvt';\nimport {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';\n\nimport {RemoteTileFormat} from '@kepler.gl/constants';\nimport {getMVTMetadata, VectorTileMetadata, getFieldsFromTile} from '@kepler.gl/table';\n\ntype FetchVectorTileMetadataProps = {\n  metadataUrl: string | null;\n  tilesetUrl: string | null;\n  remoteTileFormat: RemoteTileFormat;\n  process?: (json: PMTilesMetadata | TileJSON) => VectorTileMetadata | Error | null;\n};\n\nconst DEFAULT_PROCESS_FUNCTION = (json: PMTilesMetadata | TileJSON): VectorTileMetadata => {\n  return {\n    metaJson: null,\n    bounds: null,\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    fields: [],\n    ...json\n  };\n};\n\ntype FetchVectorTileMetadataReturn = {\n  data: VectorTileMetadata | null;\n  loading: boolean;\n  error: Error | null;\n};\n\n/** Hook to fetch and return mvt or pmtiles metadata. */\nexport default function useFetchVectorTileMetadata({\n  remoteTileFormat,\n  tilesetUrl,\n  metadataUrl,\n  process = DEFAULT_PROCESS_FUNCTION\n}: FetchVectorTileMetadataProps): FetchVectorTileMetadataReturn {\n  const [error, setError] = useState<Error | null>(null);\n  const [data, setData] = useState<VectorTileMetadata | null>(null);\n  const [loading, setLoading] = useState<boolean>(false);\n\n  useEffect(() => {\n    const getAndProcessMetadata = async () => {\n      setError(null);\n      setData(null);\n      if (metadataUrl) {\n        setLoading(true);\n\n        try {\n          let metadata: PMTilesMetadata | TileJSON | null = null;\n          if (remoteTileFormat === RemoteTileFormat.MVT) {\n            metadata = await getMVTMetadata(metadataUrl);\n\n            // MVTSource returns messy partial metadata\n            // MVTSource.createDataSource('', {\n            //   mvt: {\n            //     metadataUrl: decodeURIComponent(url)\n            //   }\n            // })\n          } else {\n            const tileSource = PMTilesSource.createDataSource(metadataUrl, {});\n            metadata = await tileSource.metadata;\n          }\n\n          // Since we switched to Source.createDataSource detailed response errors aren't available here...\n          if (!metadata) {\n            throw new Error('Failed to fetch metadata');\n          }\n\n          const processedMetadata = process(metadata);\n          if (processedMetadata instanceof Error) {\n            setError(processedMetadata);\n          } else {\n            setError(null);\n\n            await getFieldsFromTile({\n              remoteTileFormat,\n              tilesetUrl,\n              metadataUrl,\n              metadata: processedMetadata\n            });\n\n            setData(processedMetadata);\n          }\n        } catch (metadataError) {\n          setError(metadataError as any);\n        }\n        setLoading(false);\n      }\n    };\n\n    getAndProcessMetadata();\n  }, [metadataUrl, tilesetUrl, remoteTileFormat, setError, setData, process]);\n\n  return {data, loading, error};\n}\n"
  },
  {
    "path": "src/components/src/hooks/use-legend-position.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useMemo, useRef, useCallback, useEffect} from 'react';\n\nimport {MapLegendControlSettings} from '@kepler.gl/types';\n\ntype Params = {\n  legendContentRef: React.MutableRefObject<HTMLElement | null>;\n  isSidePanelShown: boolean;\n  settings?: MapLegendControlSettings;\n  onChangeSettings: (settings: Partial<MapLegendControlSettings>) => void;\n  theme: Record<string, any>;\n  mapHeight?: number;\n  mapWidth?: number;\n};\n\ntype ReturnType = {\n  positionStyles: Record<string, unknown>;\n  updatePosition: () => void;\n  contentHeight: number;\n  maxContentHeight?: number;\n  startResize: () => void;\n  resize: (deltaY: number) => void;\n};\n\nconst MARGIN = {\n  left: 10,\n  top: 10,\n  right: 10,\n  bottom: 30\n};\nconst DEFAULT_POSITION: MapLegendControlSettings['position'] = {\n  x: MARGIN.right,\n  y: MARGIN.bottom,\n  anchorX: 'right',\n  anchorY: 'bottom'\n};\nconst MIN_CONTENT_HEIGHT = 100;\nconst MAP_CONTROL_HEADER_FULL_HEIGHT = 34;\n\nexport type UseCalcLegendPositionProps = {\n  legendContentRef: React.MutableRefObject<HTMLElement | null>;\n  isSidePanelShown: boolean;\n  sidePanelWidth: number;\n};\n\nexport function useCalcLegendPosition({\n  legendContentRef,\n  isSidePanelShown,\n  sidePanelWidth\n}: UseCalcLegendPositionProps) {\n  return useCallback((): MapLegendControlSettings['position'] => {\n    const root = legendContentRef.current?.closest('.kepler-gl');\n    const legendContent = legendContentRef.current;\n    if (!legendContent || !(root instanceof HTMLElement)) {\n      return DEFAULT_POSITION;\n    }\n    const legendRect = legendContent.getBoundingClientRect();\n    const mapRootBounds = root.getBoundingClientRect();\n    const leftSidebarOffset = isSidePanelShown ? sidePanelWidth : 0;\n\n    const leftOffset = Math.max(\n      MARGIN.left,\n      legendRect.left - mapRootBounds.left - leftSidebarOffset\n    );\n    const rightOffset = Math.max(MARGIN.right, mapRootBounds.right - legendRect.right);\n\n    const topOffset = Math.max(MARGIN.top, legendRect.top - mapRootBounds.top);\n    const bottomOffset = Math.max(MARGIN.bottom, mapRootBounds.bottom - legendRect.bottom);\n\n    return {\n      ...(leftOffset < rightOffset\n        ? {x: leftOffset + leftSidebarOffset, anchorX: 'left'}\n        : {x: rightOffset, anchorX: 'right'}),\n      ...(topOffset < bottomOffset\n        ? {y: topOffset, anchorY: 'top'}\n        : {y: bottomOffset, anchorY: 'bottom'})\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [isSidePanelShown, sidePanelWidth]);\n}\n\n/**\n * Returns a function that calculates the anchored position of the map legend\n * that is being dragged.\n */\nexport default function useLegendPosition({\n  legendContentRef,\n  isSidePanelShown,\n  settings,\n  onChangeSettings,\n  theme,\n  mapHeight,\n  mapWidth\n}: Params): ReturnType {\n  const pos = settings?.position ?? DEFAULT_POSITION;\n  const contentHeight = settings?.contentHeight ?? -1;\n  const positionStyles = useMemo(() => ({[pos.anchorX]: pos.x, [pos.anchorY]: pos.y}), [pos]);\n  const startHeightRef = useRef(0);\n  const sidePanelWidth = theme.sidePanel?.width || 0;\n\n  // Calculate dynamic max content height based on map root dimensions\n  const maxContentHeight = useMemo(() => {\n    if (!mapHeight) return undefined;\n    // Available height minus margins and header\n    return mapHeight - MARGIN.top - MARGIN.bottom - MAP_CONTROL_HEADER_FULL_HEIGHT;\n  }, [mapHeight]);\n\n  const calcPosition = useCalcLegendPosition({\n    legendContentRef,\n    isSidePanelShown,\n    sidePanelWidth\n  });\n  const updatePosition = useCallback(\n    () => onChangeSettings({position: calcPosition()}),\n    [calcPosition, onChangeSettings]\n  );\n\n  const startResize = useCallback(() => {\n    const content = legendContentRef.current?.querySelector('.map-control__panel-content');\n    if (content instanceof HTMLElement) {\n      startHeightRef.current = content.offsetHeight;\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n  const resize = useCallback(\n    deltaY => {\n      const root = legendContentRef.current?.closest('.kepler-gl');\n      const legendContent = legendContentRef.current;\n      if (root instanceof HTMLElement && legendContent) {\n        const mapRootBounds = root.getBoundingClientRect();\n        const legendRect = legendContent.getBoundingClientRect();\n        const remainingHeight =\n          mapRootBounds.bottom - (legendRect.top + MAP_CONTROL_HEADER_FULL_HEIGHT + MARGIN.bottom);\n        // Use maxContentHeight if available, otherwise fall back to viewport-based calculation\n        const maxHeight = maxContentHeight\n          ? Math.min(maxContentHeight, remainingHeight)\n          : remainingHeight;\n        const nextHeight = Math.min(\n          maxHeight,\n          Math.max(MIN_CONTENT_HEIGHT, startHeightRef.current + deltaY)\n        );\n        onChangeSettings({contentHeight: nextHeight});\n        if (contentHeight > 0 && pos.anchorY === 'bottom') {\n          onChangeSettings({position: {...pos, y: pos.y - (nextHeight - contentHeight)}});\n        }\n      }\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [contentHeight, pos, onChangeSettings, maxContentHeight]\n  );\n\n  // Shift when side panel is shown/hidden\n  const posRef = useRef(pos);\n  posRef.current = pos;\n  useEffect(() => {\n    const currentPos = posRef.current;\n    if (currentPos.anchorX === 'left') {\n      if (isSidePanelShown) {\n        if (currentPos.x <= sidePanelWidth + MARGIN.left) {\n          onChangeSettings({position: {...currentPos, x: sidePanelWidth + MARGIN.left}});\n        }\n      } else {\n        onChangeSettings({\n          position: {...currentPos, x: Math.max(MARGIN.left, currentPos.x - sidePanelWidth)}\n        });\n      }\n    }\n  }, [isSidePanelShown, onChangeSettings, sidePanelWidth]);\n\n  // Clamp contentHeight when map resizes to ensure legend stays within available space\n  useEffect(() => {\n    if (!mapWidth || !mapHeight || !legendContentRef.current) return;\n    if (maxContentHeight && contentHeight > 0 && contentHeight > maxContentHeight) {\n      onChangeSettings({contentHeight: maxContentHeight});\n    }\n  }, [mapWidth, mapHeight, contentHeight, onChangeSettings, maxContentHeight, legendContentRef]);\n\n  return {positionStyles, updatePosition, contentHeight, maxContentHeight, startResize, resize};\n}\n"
  },
  {
    "path": "src/components/src/hooks/use-on-click-outside.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport document from 'global/document';\nimport {useCallback, useEffect, useRef, MutableRefObject} from 'react';\nexport default function useOnClickOutside<T extends HTMLElement>(\n  onClose: (e) => void,\n  disabled = false\n): MutableRefObject<T | null> {\n  const containerRef = useRef<T>(null);\n  const handleClickOutside = useCallback(\n    e => {\n      if (containerRef.current && !containerRef.current.contains(e.target)) {\n        onClose(e);\n      }\n    },\n    [onClose]\n  );\n\n  useEffect(() => {\n    if (disabled) {\n      return;\n    }\n\n    document.addEventListener('click', handleClickOutside, {capture: true});\n\n    return () => {\n      document.removeEventListener('click', handleClickOutside, {capture: true});\n    };\n  }, [handleClickOutside, disabled]);\n\n  return containerRef;\n}\n"
  },
  {
    "path": "src/components/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Context\nexport {default as KeplerGlContext, RootContext} from './context';\n\nimport FieldSelectorFactory, {FieldListItemFactoryFactory} from './common/field-selector';\nimport FieldTokenFactory from './common/field-token';\nimport InfoHelperFactory from './common/info-helper';\nimport RangeSliderFactory from './common/range-slider';\nimport TimeRangeSliderFactory from './common/time-range-slider';\nimport {appInjector} from './container';\nimport ChannelByValueSelectorFactory from './side-panel/layer-panel/channel-by-value-selector';\nimport ColorSelectorFactory from './side-panel/layer-panel/color-selector';\nimport {\n  ArcLayerColorSelectorFactory,\n  LayerColorRangeSelectorFactory,\n  LayerColorSelectorFactory\n} from './side-panel/layer-panel/layer-color-selector';\nimport LayerColumnConfigFactory from './side-panel/layer-panel/layer-column-config';\nimport LayerConfigGroupFactory from './side-panel/layer-panel/layer-config-group';\nimport LayerTypeListItemFactory from './side-panel/layer-panel/layer-type-list-item';\nimport VisConfigSliderFactory from './side-panel/layer-panel/vis-config-slider';\nimport VisConfigSwitchFactory from './side-panel/layer-panel/vis-config-switch';\nimport PanelHeaderActionFactory from './side-panel/panel-header-action';\nimport {\n  default as LayerColumnModeConfigFactory,\n  ColumnModeConfigFactory\n} from './side-panel/layer-panel/layer-column-mode-config';\nimport DataTableFactory from './common/data-table';\n\n// Components\nexport {\n  ContainerFactory,\n  ERROR_MSG,\n  default as KeplerGl,\n  default,\n  injectComponents\n} from './container';\n\n// factories\nexport {default as BottomWidgetFactory} from './bottom-widget';\nexport {default as FilterAnimationControllerFactory} from './filter-animation-controller';\nexport {default as GeocoderPanelFactory, getUpdateVisDataPayload} from './geocoder-panel';\nexport {testForCoordinates} from './geocoder/geocoder';\nexport {\n  DEFAULT_KEPLER_GL_PROPS,\n  default as KeplerGlFactory,\n  getVisibleDatasets,\n  makeGetActionCreators,\n  mapFieldsSelector,\n  bottomWidgetSelector,\n  plotContainerSelector,\n  modalContainerSelector,\n  geoCoderPanelSelector\n} from './kepler-gl';\nexport {default as LayerAnimationControllerFactory} from './layer-animation-controller';\nexport {Attribution, default as MapContainerFactory} from './map-container';\nexport {default as MapsLayoutFactory} from './maps-layout';\nexport {default as ModalContainerFactory} from './modal-container';\nexport {default as PlotContainerFactory} from './plot-container';\nexport {default as SidePanelFactory} from './side-panel';\nexport {default as PanelTitleFactory} from './side-panel/panel-title';\n\n// // side panel factories\nexport {\n  CloudStorageDropdownFactory,\n  PanelAction,\n  PanelHeaderDropdownFactory,\n  default as PanelHeaderFactory,\n  SaveExportDropdownFactory\n} from './side-panel/panel-header';\nexport {default as PanelHeaderActionFactory} from './side-panel/panel-header-action';\nexport {default as PanelTabFactory} from './side-panel/panel-tab';\nexport {default as PanelToggleFactory} from './side-panel/panel-toggle';\nexport {CollapseButtonFactory, default as SidebarFactory} from './side-panel/side-bar';\nexport {default as ColorScaleSelectorFactory} from './side-panel/layer-panel/color-scale-selector';\n\nexport {LayerBlendingSelector, default as LayerManagerFactory} from './side-panel/layer-manager';\nexport {default as ColorPalette} from './side-panel/layer-panel/color-palette';\nexport {\n  ALL_TYPES,\n  default as ColorRangeSelector,\n  PaletteConfig\n} from './side-panel/layer-panel/color-range-selector';\nexport {\n  ColorBlock,\n  ColorSelectorInput,\n  default as CustomSelector\n} from './side-panel/layer-panel/color-selector';\nexport {\n  default as LayerConfiguratorFactory,\n  getLayerConfiguratorProps,\n  getLayerDataset,\n  getLayerFields,\n  getVisConfiguratorProps\n} from './side-panel/layer-panel/layer-configurator';\nexport {default as LayerPanelFactory} from './side-panel/layer-panel/layer-panel';\nexport {default as SingleColorPalette} from './side-panel/layer-panel/single-color-palette';\nexport {default as TextLabelPanelFactory} from './side-panel/layer-panel/text-label-panel';\n\nexport {default as AddLayerButtonFactory} from './side-panel/layer-panel/add-layer-button';\nexport * from './side-panel/layer-panel/channel-by-value-selector';\nexport * from './side-panel/layer-panel/color-breaks-panel';\nexport * from './side-panel/layer-panel/color-range-selector';\nexport * from './side-panel/layer-panel/color-scale-selector';\nexport {\n  AddColorStop,\n  default as CustomPalette,\n  DeleteColorStop,\n  ColorPaletteItem,\n  ColorSwatch,\n  EditableColorRange\n} from './side-panel/layer-panel/custom-palette';\nexport {default as CustomPicker} from './side-panel/layer-panel/custom-picker';\nexport {default as DatasetLayerGroupFactory} from './side-panel/layer-panel/dataset-layer-group';\nexport {default as DatasetLayerSectionFactory} from './side-panel/layer-panel/dataset-layer-section';\nexport {\n  AddDataButtonFactory,\n  default as DatasetSectionFactory\n} from './side-panel/layer-panel/dataset-section';\nexport * from './side-panel/layer-panel/dimension-scale-selector';\n\nexport {default as LayerListFactory} from './side-panel/layer-panel/layer-list';\nexport * from './side-panel/layer-panel/vis-config-by-field-selector';\nexport {default as PanelViewListToggleFactory} from './side-panel/panel-view-list-toggle';\nexport {default as DatasetInfoFactory} from './side-panel/common/dataset-info';\nexport {default as DatasetTagFactory} from './side-panel/common/dataset-tag';\nexport {default as DatasetTitleFactory} from './side-panel/common/dataset-title';\nexport {default as SourceDataCatalogFactory} from './side-panel/common/source-data-catalog';\nexport {default as SourceDataSelectorFactory} from './side-panel/common/source-data-selector';\n\nexport {default as FilterManagerFactory} from './side-panel/filter-manager';\nexport {default as AddFilterButtonFactory} from './side-panel/filter-panel/add-filter-button';\nexport {default as FilterPanelFactory} from './side-panel/filter-panel/filter-panel';\n\nexport {default as InteractionManagerFactory} from './side-panel/interaction-manager';\nexport {default as BrushConfigFactory} from './side-panel/interaction-panel/brush-config';\nexport {default as TooltipConfigFactory} from './side-panel/interaction-panel/tooltip-config';\n\nexport {default as DndContextFactory} from './dnd-context';\nexport {default as CustomPanelsFactory} from './side-panel/custom-panel';\nexport {default as MapManagerFactory} from './side-panel/map-manager';\nexport {default as LayerGroupColorPickerFactory} from './side-panel/map-style-panel/map-layer-group-color-picker';\nexport {default as LayerGroupSelectorFactory} from './side-panel/map-style-panel/map-layer-selector';\nexport {default as MapStyleSelectorFactory} from './side-panel/map-style-panel/map-style-selector';\n\n// map factories\nexport {default as CoordinateInfoFactory} from './map/coordinate-info';\nexport {default as LayerHoverInfoFactory} from './map/layer-hover-info';\nexport {default as LayerSelectorPanelFactory} from './map/layer-selector-panel';\nexport {default as LazyTippy} from './map/lazy-tippy';\nexport {default as LocalePanelFactory} from './map/locale-panel';\nexport {default as MapControlFactory} from './map/map-control';\nexport {default as MapControlPanelFactory} from './map/map-control-panel';\nexport {default as MapControlToolbarFactory} from './map/map-control-toolbar';\nexport {default as MapControlTooltipFactory} from './map/map-control-tooltip';\nexport {default as MapDrawPanelFactory} from './map/map-draw-panel';\nexport {\n  LayerColorLegendFactory,\n  LayerDefaultLegend,\n  LayerLegendContentFactory,\n  LayerLegendHeaderFactory,\n  default as MapLegendFactory,\n  SingleColorLegendFactory,\n  StyledMapControlLegend,\n  VisualChannelMetric\n} from './map/map-legend';\n\nexport {default as MapLegendPanelFactory} from './map/map-legend-panel';\nexport {default as MapPopoverFactory, getSelectedFeature} from './map/map-popover';\nexport {default as MapPopoverContentFactory} from './map/map-popover-content';\nexport {default as SplitMapButtonFactory} from './map/split-map-button';\nexport {default as Toggle3dButtonFactory} from './map/toggle-3d-button';\n\n// modal factories\nexport {default as AddMapStyleModalFactory} from './modals/add-map-style-modal';\nexport {\n  default as DataTableModalFactory,\n  DatasetModalTab,\n  DatasetTabs\n} from './modals/data-table-modal';\nexport {default as DeleteDatasetModalFactory} from './modals/delete-data-modal';\nexport {default as ExportDataModalFactory} from './modals/export-data-modal';\nexport {default as ExportImageModalFactory} from './modals/export-image-modal';\nexport {default as ExportHtmlMapFactory} from './modals/export-map-modal/export-html-map';\nexport {default as ExportJsonMapFactory} from './modals/export-map-modal/export-json-map';\nexport {default as ExportMapModalFactory} from './modals/export-map-modal/export-map-modal';\nexport {default as LoadDataModalFactory} from './modals/load-data-modal';\nexport {default as LoadTileSetFactory} from './modals/tilesets-modals/load-tileset';\nexport {default as LoadStorageMapFactory} from './modals/load-storage-map';\nexport {default as ModalDialogFactory} from './modals/modal-dialog';\nexport {ModalTabItem, default as ModalTabsFactory} from './modals/modal-tabs';\nexport {default as SaveMapModalFactory} from './modals/save-map-modal';\nexport {default as StatusPanel} from './modals/status-panel';\n\n// // notification panel\nexport {default as NotificationPanelFactory} from './notification-panel';\nexport {default as NotificationItemFactory} from './notification-panel/notification-item';\n\n// // common factory\nexport {default as AnimationControlFactory} from './common/animation-control/animation-control';\nexport {default as AnimationControllerFactory} from './common/animation-control/animation-controller';\nexport {default as AnimationSpeedSliderFactory} from './common/animation-control/animation-speed-slider';\nexport {default as AnimationWindowControlFactory} from './common/animation-control/animation-window-control';\nexport {default as FloatingTimeDisplayFactory} from './common/animation-control/floating-time-display';\nexport {default as PlayControlFactory} from './common/animation-control/play-control';\nexport {default as PlaybackControlsFactory} from './common/animation-control/playback-controls';\nexport {default as ResetControlFactory} from './common/animation-control/reset-control';\nexport {default as SpeedControlFactory} from './common/animation-control/speed-control';\nexport {default as WindowActionControlFactory} from './common/animation-control/window-action-control';\n\nexport {default as HistogramPlotFactory, HISTOGRAM_MASK_MODE} from './common/histogram-plot';\nexport {default as IconButton} from './common/icon-button';\nexport {default as LinkRenderer} from './common/link-renderer';\nexport {\n  default as ColumnStatsChartFactory,\n  HISTOGRAM_WIDTH,\n  HISTOGRAM_HEIGHT,\n  ColorChartHeader,\n  ColorChartTick\n} from './common/column-stats-chart';\nexport {default as ImagePreview} from './common/image-preview';\nexport {default as LineChartFactory} from './common/line-chart';\nexport {default as RangeBrushFactory} from './common/range-brush';\nexport {default as RangePlotFactory} from './common/range-plot';\nexport {default as TimeRangeSliderTimeTitleFactory} from './common/time-range-slider-time-title';\nexport {\n  default as TimeSliderMarkerFactory,\n  getTickFormat,\n  getXAxis,\n  updateAxis\n} from './common/time-slider-marker';\nexport {\n  SYNC_TIMELINE_ANIMATION_ITEMS,\n  default as SyncTimelineControlFactory\n} from './common/sync-timeline-control';\n// Filters factory\nexport {default as NewFilterPanelFactory} from './filters/filter-panels/new-filter-panel';\nexport {default as MultiSelectFilterFactory} from './filters/multi-select-filter';\nexport {default as RangeFilterFactory} from './filters/range-filter';\nexport {default as SingleSelectFilterFactory} from './filters/single-select-filter';\nexport {default as TimeRangeFilterPanelFactory} from './filters/filter-panels/time-range-filter-panel';\nexport {default as FilterSyncedDatasetPanelFactory} from './filters/filter-panels/filter-synced-dataset-panel';\nexport {\n  default as TimeRangeFilterFactory,\n  timeRangeSliderFieldsSelector\n} from './filters/time-range-filter';\nexport {default as TimeWidgetFactory} from './filters/time-widget';\nexport {default as TimeWidgetTopFactory} from './filters/time-widget-top';\n\n// // Editor Factory\nexport {default as EditorFactory} from './editor/editor';\nexport {\n  default as FeatureActionPanelFactory,\n  PureFeatureActionPanelFactory\n} from './editor/feature-action-panel';\n\n// Injector\nexport * from './injector';\n\n// Common Components\nexport {default as ActionPanel, ActionPanelItem} from './common/action-panel';\nexport {default as Checkbox} from './common/checkbox';\nexport {\n  default as ColorLegendFactory,\n  LegendColorDisplayFactory,\n  LegendRowEditorFactory,\n  LegendRowFactory,\n  ResetColorLabelFactory\n} from './common/color-legend';\nexport {default as CanvasHack} from './common/data-table/canvas';\nexport {renderedSize} from './common/data-table/cell-size';\nexport {\n  default as DataTableConfigFactory,\n  NumberFormatConfig\n} from './common/data-table/display-format';\nexport {default as HeaderCellFactory} from './common/data-table/header-cell';\nexport {FormatterDropdown, default as OptionDropdown} from './common/data-table/option-dropdown';\nexport {default as DatasetLabel} from './common/dataset-label';\nexport {default as FieldSelectorFactory} from './common/field-selector';\nexport * from './common/field-token';\nexport {default as FieldTokenFactory} from './common/field-token';\nexport {default as FileDrop} from './common/file-uploader/file-drop';\nexport {\n  FileUpload,\n  default as FileUploadFactory,\n  WarningMsg\n} from './common/file-uploader/file-upload';\nexport {default as FileUploadProgress} from './common/file-uploader/file-upload-progress';\nexport {default as UploadButton} from './common/file-uploader/upload-button';\nexport {default as Accessor} from './common/item-selector/accessor';\nexport {\n  default as ChickletedInput,\n  ChickletButton,\n  ChickletTag\n} from './common/item-selector/chickleted-input';\nexport {\n  default as DropdownList,\n  ListItem,\n  classList as dropdownListClassList\n} from './common/item-selector/dropdown-list';\nexport * from './common/item-selector/dropdown-select';\nexport {default as ItemSelector} from './common/item-selector/item-selector';\nexport {default as Typeahead} from './common/item-selector/typeahead';\nexport {default as DropdownSelect} from './common/item-selector/dropdown-select';\nexport {default as LoadingSpinner} from './common/loading-spinner';\nexport {default as AppLogo} from './common/logo';\nexport {default as MapLayerSelector} from './common/map-layer-selector';\nexport {default as Modal, ModalFooter, ModalTitle} from './common/modal';\nexport {default as Portaled} from './common/portaled';\nexport {default as ProgressBar} from './common/progress-bar';\nexport {default as Slider} from './common/slider/slider';\nexport {default as SliderBarHandle} from './common/slider/slider-bar-handle';\nexport {default as SliderHandle} from './common/slider/slider-handle';\nexport {default as Switch} from './common/switch';\nexport {default as TippyTooltip} from './common/tippy-tooltip';\nexport {default as ToolbarItem} from './common/toolbar-item';\nexport {default as VerticalToolbar} from './common/vertical-toolbar';\nexport {MapViewStateContext, MapViewStateContextProvider} from './map-view-state-context';\nexport {default as EffectControlFactory} from './map/effects/effect-control';\nexport {default as CloudTile} from './modals/cloud-tile';\nexport {default as LoadingDialog} from './modals/loading-dialog';\nexport {default as ShareMapUrlModalFactory, SharingUrl} from './modals/share-map-modal';\nexport {default as ColorRangeSelectorFactory} from './side-panel/layer-panel/color-range-selector';\nexport {default as CustomPaletteFactory} from './side-panel/layer-panel/custom-palette';\n\n// side pane components\nexport {default as StyledDropdownSelect} from './common/item-selector/item-selector';\nexport {default as FilterPanelHeaderFactory} from './side-panel/filter-panel/filter-panel-header';\nexport {default as ColumnSelectorFactory} from './side-panel/layer-panel/column-selector';\nexport {\n  ConfigGroupCollapsibleContent,\n  LayerConfigGroupLabelFactory,\n  StyledConfigGroupHeader\n} from './side-panel/layer-panel/layer-config-group';\nexport {\n  DragHandle,\n  LayerLabelEditor,\n  LayerPanelHeaderActionSectionFactory,\n  default as LayerPanelHeaderFactory,\n  LayerTitleSectionFactory\n} from './side-panel/layer-panel/layer-panel-header';\nexport {default as LayerTypeDropdownListFactory} from './side-panel/layer-panel/layer-type-dropdown-list';\nexport {default as LayerTypeSelectorFactory} from './side-panel/layer-panel/layer-type-selector';\n\nexport {default as EffectConfiguratorFactory} from './effects/effect-configurator';\nexport {default as EffectListFactory} from './effects/effect-list';\nexport {default as EffectManagerFactory} from './effects/effect-manager';\nexport {default as EffectTimeConfiguratorFactory} from './effects/effect-time-configurator';\nexport {default as EffectTypeSelectorFactory} from './effects/effect-type-selector';\nexport {default as SidePanelTitleFactory} from './effects/side-panel-title';\nexport {default as ColorBreaksPanelFactory} from './side-panel/layer-panel/color-breaks-panel';\nexport {default as DimensionScaleSelectorFactory} from './side-panel/layer-panel/dimension-scale-selector';\nexport {default as HowToButton} from './side-panel/layer-panel/how-to-button';\n// eslint-disable-next-line prettier/prettier\nexport type {ButtonProps, StyledExportSectionProps, StyledPanelHeaderProps} from './common';\nexport type {\n  FormatterDropdownProps,\n  OptionDropdownProps\n} from './common/data-table/option-dropdown';\nexport type {DndContextComponent, DndContextProps} from './dnd-context';\nexport type {FeatureActionPanelProps} from './editor/feature-action-panel';\nexport type {PlaybackControlsProps} from './common/animation-control/playback-controls';\nexport type {MapContainerProps} from './map-container';\nexport type {MapControlProps} from './map/map-control';\nexport type {MapDrawPanelProps} from './map/map-draw-panel';\nexport type {MapLegendPanelFactoryDeps, MapLegendPanelProps} from './map/map-legend-panel';\nexport type {DatasetInfoProps} from './side-panel/common/dataset-info';\nexport type {DatasetTagProps} from './side-panel/common/dataset-tag';\nexport type {DatasetTitleProps} from './side-panel/common/dataset-title';\nexport type {SourceDataCatalogProps} from './side-panel/common/source-data-catalog';\nexport type {PanelMeta, SourceDataSelectorProps} from './side-panel/common/types';\nexport type {CustomPanelsProps} from './side-panel/custom-panel';\nexport type {FilterManagerProps} from './side-panel/filter-manager';\nexport type {FilterPanelHeaderProps} from './side-panel/filter-panel/filter-panel-header';\nexport type {\n  LayerConfigGroupLabelProps,\n  LayerConfigGroupProps\n} from './side-panel/layer-panel/layer-config-group';\nexport type {LayerListFactoryDeps, LayerListProps} from './side-panel/layer-panel/layer-list';\nexport type {\n  LayerLabelEditorProps,\n  LayerPanelHeaderActionSectionProps,\n  LayerPanelHeaderProps,\n  LayerTitleSectionProps\n} from './side-panel/layer-panel/layer-panel-header';\nexport type {LayerTypeOption} from './side-panel/layer-panel/layer-type-dropdown-list';\nexport type {\n  LayerTypeListItemProps,\n  LayerTypeListItemType\n} from './side-panel/layer-panel/layer-type-list-item';\nexport type {SingleColorPaletteProps} from './side-panel/layer-panel/single-color-palette';\nexport type {SupportedColumnModeConfig} from './side-panel/layer-panel/layer-column-mode-config';\nexport type {MapManagerProps} from './side-panel/map-manager';\nexport type {LayerGroupColorPickerProps} from './side-panel/map-style-panel/map-layer-group-color-picker';\nexport type {LayerGroupSelectorProps} from './side-panel/map-style-panel/map-layer-selector';\nexport type {MapStyleSelectorProps} from './side-panel/map-style-panel/map-style-selector';\nexport type {PanelHeaderProps} from './side-panel/panel-header';\nexport type {PanelTabProps} from './side-panel/panel-tab';\nexport type {CollapseButtonProps, SideBarProps} from './side-panel/side-bar';\n\nexport {\n  BottomWidgetInner,\n  Button,\n  ButtonGroup,\n  CenterFlexbox,\n  CenterVerticalFlexbox,\n  CheckMark,\n  DatasetSquare,\n  Edit,\n  IconRoundSmall,\n  Icons,\n  InlineInput,\n  Input,\n  InputLight,\n  MapControlButton,\n  PanelContent,\n  PanelHeaderContent,\n  PanelHeaderTitle,\n  PanelLabel,\n  PanelLabelBold,\n  PanelLabelWrapper,\n  SBFlexboxItem,\n  SBFlexboxNoMargin,\n  SelectText,\n  SelectTextBold,\n  SelectionButton,\n  SidePanelDivider,\n  SidePanelSection,\n  SpaceBetweenFlexbox,\n  StyledAttribution,\n  StyledExportSection,\n  StyledFilterContent,\n  StyledFilteredOption,\n  StyledMapContainer,\n  StyledModalContent,\n  StyledModalInputFootnote,\n  StyledModalSection,\n  StyledModalVerticalPanel,\n  StyledPanelDropdown,\n  StyledPanelHeader,\n  StyledType,\n  shouldForwardProp,\n  TextArea,\n  TextAreaLight,\n  Tooltip,\n  TruncatedTitleText,\n  WidgetContainer\n} from './common';\nexport {\n  ArcLayerColorSelectorFactory,\n  ChannelByValueSelectorFactory,\n  ColorSelectorFactory,\n  ColumnModeConfigFactory,\n  DataTableFactory,\n  FieldListItemFactoryFactory,\n  InfoHelperFactory,\n  LayerColorRangeSelectorFactory,\n  LayerColorSelectorFactory,\n  LayerColumnConfigFactory,\n  LayerColumnModeConfigFactory,\n  LayerConfigGroupFactory,\n  LayerTypeListItemFactory,\n  RangeSliderFactory,\n  TimeRangeSliderFactory,\n  VisConfigSliderFactory,\n  VisConfigSwitchFactory,\n  appInjector\n};\nexport {\n  DROPPABLE_MAP_CONTAINER_TYPE,\n  SORTABLE_LAYER_TYPE,\n  SORTABLE_EFFECT_TYPE,\n  SORTABLE_SIDE_PANEL_TYPE,\n  DND_MODIFIERS,\n  DND_EMPTY_MODIFIERS\n} from './common/dnd-layer-items';\n\n// Individual Component from Dependency Tree\nexport const TimeRangeSlider = appInjector.get(TimeRangeSliderFactory);\nexport const RangeSlider = appInjector.get(RangeSliderFactory);\nexport const VisConfigSlider = appInjector.get(VisConfigSliderFactory);\nexport const VisConfigSwitch = appInjector.get(VisConfigSwitchFactory);\nexport const LayerConfigGroup = appInjector.get(LayerConfigGroupFactory);\nexport const LayerColumnModeConfig = appInjector.get(LayerColumnModeConfigFactory);\nexport const LayerColumnConfig = appInjector.get(LayerColumnConfigFactory);\nexport const ChannelByValueSelector = appInjector.get(ChannelByValueSelectorFactory);\nexport const FieldSelector = appInjector.get(FieldSelectorFactory);\nexport const FieldToken = appInjector.get(FieldTokenFactory);\nexport const PanelHeaderAction = appInjector.get(PanelHeaderActionFactory);\nexport const FieldListItemFactory = appInjector.get(FieldListItemFactoryFactory);\nexport const LayerTypeListItem = appInjector.get(LayerTypeListItemFactory);\nexport const InfoHelper = appInjector.get(InfoHelperFactory);\nexport const ColorSelector = appInjector.get(ColorSelectorFactory);\nexport const LayerColorSelector = appInjector.get(LayerColorSelectorFactory);\nexport const LayerColorRangeSelector = appInjector.get(LayerColorRangeSelectorFactory);\nexport const ArcLayerColorSelector = appInjector.get(ArcLayerColorSelectorFactory);\nexport const DataTable = appInjector.get(DataTableFactory);\n\n// hooks\nexport {CloudListProvider, useCloudListProvider} from './hooks/use-cloud-list-provider';\nexport {default as useDndEffects} from './hooks/use-dnd-effects';\nexport {default as useDndLayers} from './hooks/use-dnd-layers';\nexport {default as useFeatureFlags} from './hooks/use-feature-flags';\nexport {default as useLegendPosition} from './hooks/use-legend-position';\n"
  },
  {
    "path": "src/components/src/injector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\nimport {\n  MapStateToPropsParam,\n  MapDispatchToPropsParam,\n  InferableComponentEnhancerWithProps\n} from 'react-redux';\nimport {console as Console} from 'global/window';\nimport KeplerGlContext from './context';\n\nexport type FactoryElement = (...args) => React.ComponentType;\nexport type Factory = FactoryElement & {\n  deps: FactoryElement[];\n};\n\nexport type InjectorType = {\n  provide: (factory: any, replacement: any) => InjectorType;\n  get: (fac: any, parent?: any) => any;\n};\n\nconst MissingComp = () => <div />;\n\nexport const ERROR_MSG = {\n  wrongRecipeType:\n    `injectComponents takes an array of factories replacement pairs as input, ` +\n    `each pair be a array as [originalFactory, replacement].`,\n\n  noDep: (fac, parent) =>\n    `${fac.name} is required as a dependency of ${parent.name}, ` +\n    `but is not provided to injectComponents. It will not be rendered.`,\n\n  notFunc: 'factory and its replacement should be a function'\n};\n\nexport function injector(map = new Map()): InjectorType {\n  const cache = new Map(); // map<factory, factory -> ?>\n  const get = (fac, parent) => {\n    const factory = map.get(fac);\n    // factory is not injected\n    if (!factory) {\n      Console.error(ERROR_MSG.noDep(fac, parent));\n      return MissingComp;\n    }\n\n    // check if custom factory deps is declared\n    const instances =\n      cache.get(factory) ||\n      factory(...(factory.deps ? factory.deps.map(dep => get(dep, factory)) : []));\n\n    cache.set(fac, instances);\n    return instances;\n  };\n\n  // if you have two functions that happen to have the exactly same text\n  // it will be override: 2018-02-05\n  return {\n    provide: (factory, replacement) => {\n      if (!typeCheckRecipe([factory, replacement])) {\n        return injector(map);\n      }\n      return injector(new Map(map).set(factory, replacement));\n    },\n    get\n  };\n}\n\n// entryPoint\nexport function flattenDeps(allDeps: Factory[], factory: any): Factory[] {\n  const addToDeps = allDeps.includes(factory) ? allDeps : allDeps.concat([factory]);\n  return Array.isArray(factory.deps) && factory.deps.length\n    ? factory.deps.reduce((accu, dep) => flattenDeps(accu, dep), addToDeps)\n    : addToDeps;\n}\n\nexport function provideRecipesToInjector(recipes: [Factory, Factory][], appInjector: InjectorType) {\n  const provided = new Map();\n\n  const injector = recipes.reduce((inj, recipe) => {\n    if (!typeCheckRecipe(recipe)) {\n      return inj;\n    }\n\n    // collect dependencies of custom factories, if there is any.\n    // Add them to the appInjector\n    const customDependencies = flattenDeps([], recipe[1]);\n    inj = customDependencies.reduce((ij, factory) => {\n      if (provided.get(factory)) {\n        Console.warn(\n          `${factory.name} already injected from ${provided.get(factory).name}, injecting ${\n            recipe[0].name\n          } after ${provided.get(factory).name} will override it`\n        );\n      }\n      return ij.provide(factory, factory);\n    }, inj);\n\n    provided.set(recipe[0], recipe[1]);\n    return inj.provide(...recipe);\n  }, appInjector);\n\n  // make sure all component instance are cached\n  provided.forEach(v => {\n    injector.get(v);\n  });\n\n  return injector;\n}\n\nexport function typeCheckRecipe(recipe) {\n  if (!Array.isArray(recipe) || recipe.length < 2) {\n    Console.error('Error injecting [factory, replacement]', recipe);\n    Console.error(ERROR_MSG.wrongRecipeType);\n    return false;\n  }\n\n  const [factory, replacement] = recipe;\n  if (typeof factory !== 'function') {\n    Console.error('Error injecting factory: ', factory);\n    Console.error(ERROR_MSG.notFunc);\n    return false;\n  } else if (typeof replacement !== 'function') {\n    Console.error('Error injecting replacement for: ', factory);\n    Console.error(ERROR_MSG.notFunc);\n    return false;\n  }\n\n  return true;\n}\n\nexport interface WithState<RootState> {\n  <TStateProps = object, TDispatchProps = object, TOwnProps = object, State = RootState>(\n    lenses: any[],\n    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,\n    mapDispatchToProps?: MapDispatchToPropsParam<TDispatchProps, TOwnProps>\n  ): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;\n}\n\nconst identity = state => state;\n// Helper to add reducer state to custom component\nexport function withState(lenses: any[] = [], mapStateToProps = identity, actions = {}) {\n  return Component => {\n    const WrappedComponent = ({state, ...props}) => (\n      <KeplerGlContext.Consumer>\n        {context => (\n          <Component\n            {...lenses.reduce(\n              (totalState, lens) => ({\n                ...totalState,\n                ...lens(context.selector(state))\n              }),\n              props\n            )}\n          />\n        )}\n      </KeplerGlContext.Consumer>\n    );\n\n    return connect(\n      state => ({...mapStateToProps(state), state}),\n      dispatch =>\n        Object.keys(actions).reduce(\n          (accu, key) => ({\n            ...accu,\n            [key]: bindActionCreators(actions[key], dispatch)\n          }),\n          {}\n        )\n    )(WrappedComponent);\n  };\n}\n"
  },
  {
    "path": "src/components/src/kepler-gl.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, createRef, Dispatch} from 'react';\nimport Console from 'global/console';\nimport {bindActionCreators} from 'redux';\nimport styled, {withTheme, StyleSheetManager, ThemeProvider} from 'styled-components';\nimport {createSelector} from 'reselect';\nimport {connect as keplerGlConnect} from './connect/keplergl-connect';\nimport {IntlProvider} from 'react-intl';\nimport {messages} from '@kepler.gl/localization';\nimport {RootContext, FeatureFlagsContextProvider, FeatureFlags} from './context';\nimport {\n  AttributionWithStyle,\n  DatasetAttribution,\n  OnErrorCallBack,\n  OnSuccessCallBack,\n  Viewport,\n  MapState\n} from '@kepler.gl/types';\n\nimport {\n  MapStateActions,\n  MapStyleActions,\n  ProviderActions,\n  UIStateActions,\n  VisStateActions\n} from '@kepler.gl/actions';\n\nimport {generateHashId} from '@kepler.gl/common-utils';\n\nimport {shouldForwardProp} from './common/styled-components';\n\ntype KeplerGlActions = {\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n  mapStyleActions: typeof MapStyleActions;\n  uiStateActions: typeof UIStateActions;\n  providerActions: typeof ProviderActions;\n};\n\nimport {\n  DIMENSIONS,\n  KEPLER_GL_NAME,\n  KEPLER_GL_VERSION,\n  THEME,\n  DEFAULT_MAPBOX_API_URL,\n  GEOCODER_DATASET_NAME,\n  MISSING_MAPBOX_TOKEN\n} from '@kepler.gl/constants';\n\nimport SidePanelFactory from './side-panel';\nimport MapContainerFactory from './map-container';\nimport MapsLayoutFactory from './maps-layout';\nimport BottomWidgetFactory from './bottom-widget';\nimport ModalContainerFactory from './modal-container';\nimport PlotContainerFactory from './plot-container';\nimport NotificationPanelFactory from './notification-panel';\nimport GeoCoderPanelFactory from './geocoder-panel';\nimport EffectManagerFactory from './effects/effect-manager';\nimport DndContextFactory from './dnd-context';\nimport {CloudListProvider} from './hooks/use-cloud-list-provider';\n\nimport {\n  filterObjectByPredicate,\n  validateToken,\n  mergeMessages,\n  observeDimensions,\n  unobserveDimensions,\n  hasPortableWidth,\n  getApplicationConfig\n} from '@kepler.gl/utils';\n\nimport {theme as basicTheme, themeLT, themeBS, breakPointValues} from '@kepler.gl/styles';\nimport {KeplerGlState} from '@kepler.gl/reducers';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\n// Maybe we should think about exporting this or creating a variable\n// as part of the base.js theme\nconst GlobalStyle = styled.div`\n  font-family: ${props => props.theme.fontFamily};\n  font-weight: ${props => props.theme.fontWeight};\n  font-size: ${props => props.theme.fontSize};\n  line-height: ${props => props.theme.lineHeight};\n\n  *,\n  *:before,\n  *:after {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n\n  ul {\n    margin: 0;\n    padding: 0;\n  }\n\n  li {\n    margin: 0;\n  }\n\n  a {\n    text-decoration: none;\n    color: ${props => props.theme.labelColor};\n  }\n\n  :focus {\n    outline: none;\n  }\n\n  .maplibregl-ctrl .maplibregl-ctrl-logo {\n    display: none;\n  }\n\n  .mapboxgl-ctrl .mapboxgl-ctrl-logo {\n    display: none;\n  }\n`;\n\ntype BottomWidgetOuterProps = {\n  absolute?: boolean;\n};\n\nconst BottomWidgetOuter = styled.div<BottomWidgetOuterProps>(\n  ({absolute}) => `\n  ${absolute ? 'position: absolute; bottom: 0; right: 0;' : ''}\n  pointer-events: none; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }`\n);\n\nexport const isViewportDisjointed = props => {\n  return (\n    props.mapState.isSplit &&\n    !props.mapState.isViewportSynced &&\n    props.mapState.splitMapViewports.length > 1\n  );\n};\n\ntype MapStateProps = {\n  mapState: MapState;\n};\n\nexport const mapStateSelector = createSelector(\n  [(props: MapStateProps) => props.mapState, (_: MapStateProps, index?: number) => index],\n  (mapState: MapState, index?: number): MapState => {\n    if (!Number.isFinite(index)) {\n      // either no index arg or an invalid index was provided\n      // it is expected to be either 0 or 1 when in split mode\n      // only use the mapState\n      return mapState;\n    }\n\n    return isViewportDisjointed({mapState})\n      ? // mix together the viewport properties intended for this disjointed <MapContainer> with the other necessary mapState properties\n        {...mapState, ...mapState.splitMapViewports[index || 0]}\n      : // otherwise only use the mapState\n        mapState;\n  }\n);\n\nexport const mapFieldsSelector = (props: KeplerGLProps, index = 0) => ({\n  getMapboxRef: props.getMapboxRef,\n  mapboxApiAccessToken: props.mapboxApiAccessToken,\n  mapboxApiUrl: props.mapboxApiUrl ? props.mapboxApiUrl : DEFAULT_KEPLER_GL_PROPS.mapboxApiUrl,\n  mapState: mapStateSelector(props, index),\n  datasetAttributions: attributionSelector(props).sources,\n  mapStyle: props.mapStyle,\n  onDeckInitialized: props.onDeckInitialized,\n  onViewStateChange: props.onViewStateChange,\n  onMouseMove: props.onMouseMove,\n  deckGlProps: props.deckGlProps,\n  uiStateActions: props.uiStateActions,\n  visStateActions: props.visStateActions,\n  mapStateActions: props.mapStateActions,\n\n  // visState\n  visState: props.visState,\n\n  // uiState\n  activeSidePanel: props.uiState.activeSidePanel,\n  mapControls: props.uiState.mapControls,\n  readOnly: props.uiState.readOnly,\n  locale: props.uiState.locale,\n  isLoadingIndicatorVisible: Number(props.visState.loadingIndicatorValue) > 0,\n  sidePanelWidth: props.sidePanelWidth ? props.sidePanelWidth : DEFAULT_KEPLER_GL_PROPS.width,\n\n  // mapStyle\n  topMapContainerProps: props.topMapContainerProps,\n  bottomMapContainerProps: props.bottomMapContainerProps,\n\n  // transformRequest for Mapbox basemaps\n  transformRequest: props.transformRequest\n});\n\nexport function getVisibleDatasets(datasets) {\n  // We don't want Geocoder dataset to be present in SidePanel dataset list\n  return filterObjectByPredicate(datasets, key => key !== GEOCODER_DATASET_NAME);\n}\n\nexport const sidePanelSelector = (props: KeplerGLProps, availableProviders, filteredDatasets) => ({\n  appName: props.appName ? props.appName : DEFAULT_KEPLER_GL_PROPS.appName,\n  version: props.version ? props.version : DEFAULT_KEPLER_GL_PROPS.version,\n  appWebsite: props.appWebsite,\n  mapStyle: props.mapStyle,\n  onSaveMap: props.onSaveMap,\n  uiState: props.uiState,\n  mapStyleActions: props.mapStyleActions,\n  visStateActions: props.visStateActions,\n  uiStateActions: props.uiStateActions,\n  mapStateActions: props.mapStateActions,\n\n  datasets: filteredDatasets,\n  filters: props.visState.filters,\n  layers: props.visState.layers,\n  layerOrder: props.visState.layerOrder,\n  layerClasses: props.visState.layerClasses,\n  interactionConfig: props.visState.interactionConfig,\n  mapInfo: props.visState.mapInfo,\n  layerBlending: props.visState.layerBlending,\n  overlayBlending: props.visState.overlayBlending,\n\n  width: props.sidePanelWidth ? props.sidePanelWidth : DEFAULT_KEPLER_GL_PROPS.width,\n  availableProviders,\n  mapSaved: props.providerState.mapSaved\n});\n\nexport const plotContainerSelector = (props: KeplerGLProps) => ({\n  width: props.width,\n  height: props.height,\n  ratio: props.uiState.exportImage.ratio,\n  resolution: props.uiState.exportImage.resolution,\n  legend: props.uiState.exportImage.legend,\n  center: props.uiState.exportImage.center,\n  imageSize: props.uiState.exportImage.imageSize,\n  escapeXhtmlForWebpack: props.uiState.exportImage.escapeXhtmlForWebpack,\n\n  mapFields: mapFieldsSelector(props),\n  addNotification: props.uiStateActions.addNotification,\n  setExportImageSetting: props.uiStateActions.setExportImageSetting,\n  setExportImageDataUri: props.uiStateActions.setExportImageDataUri,\n  setExportImageError: props.uiStateActions.setExportImageError,\n  splitMaps: props.visState.splitMaps\n});\n\nexport const isSplitSelector = (props: KeplerGLProps) =>\n  props.visState.splitMaps && props.visState.splitMaps.length > 1;\n\nexport const bottomWidgetSelector = (props: KeplerGLProps, theme) => ({\n  filters: props.visState.filters,\n  datasets: props.visState.datasets,\n  uiState: props.uiState,\n  layers: props.visState.layers,\n  animationConfig: props.visState.animationConfig,\n  visStateActions: props.visStateActions,\n  toggleModal: props.uiStateActions.toggleModal,\n  sidePanelWidth: props.uiState.readOnly ? 0 : props.sidePanelWidth + theme.sidePanel.margin.left\n});\n\nexport const modalContainerSelector = (props: KeplerGLProps, rootNode) => ({\n  appName: props.appName ? props.appName : DEFAULT_KEPLER_GL_PROPS.appName,\n  mapStyle: props.mapStyle,\n  visState: props.visState,\n  mapState: props.mapState,\n  uiState: props.uiState,\n  providerState: props.providerState,\n\n  mapboxApiAccessToken: props.mapboxApiAccessToken,\n  mapboxApiUrl: props.mapboxApiUrl,\n  visStateActions: props.visStateActions,\n  uiStateActions: props.uiStateActions,\n  mapStyleActions: props.mapStyleActions,\n  providerActions: props.providerActions,\n\n  rootNode,\n  // User defined cloud provider props\n  cloudProviders: props.cloudProviders\n    ? props.cloudProviders\n    : DEFAULT_KEPLER_GL_PROPS.cloudProviders,\n  onExportToCloudSuccess: props.onExportToCloudSuccess,\n  onLoadCloudMapSuccess: props.onLoadCloudMapSuccess,\n  onLoadCloudMapError: props.onLoadCloudMapError,\n  onExportToCloudError: props.onExportToCloudError\n});\n\nexport const geoCoderPanelSelector = (\n  props: KeplerGLProps,\n  dimensions: {width: number; height: number}\n) => ({\n  isGeocoderEnabled: props.visState.interactionConfig.geocoder.enabled,\n  mapboxApiAccessToken: props.mapboxApiAccessToken,\n  mapState: props.mapState,\n  uiState: props.uiState,\n  layerOrder: props.visState.layerOrder,\n  updateVisData: props.visStateActions.updateVisData,\n  removeDataset: props.visStateActions.removeDataset,\n  updateMap: props.mapStateActions.updateMap,\n  appWidth: dimensions.width\n});\n\n/**\n * Returns array of unique dataset attributions, each with title and url properties.\n */\nexport const datasetAttributionSelector = createSelector(\n  [state => state.visState.datasets],\n  datasets => {\n    const uniqueAttributions: DatasetAttribution[] = [];\n    Object.keys(datasets).forEach(key => {\n      const ds = datasets[key];\n      const attributions = ds?.metadata?.attributions;\n      if (Array.isArray(attributions)) {\n        attributions.forEach(attribution => {\n          if (typeof attribution === 'string') {\n            // attribution can be a raw string or a string with link tags\n            const links = attribution.match(/<a[^]+?a>/g);\n            if (links) {\n              try {\n                links?.forEach(link => {\n                  const href = link.match(/href=\"([^\"]*)/)?.[1];\n                  const title = link.match(/title=\"([^\"]*)/)?.[1];\n                  if (href && title) {\n                    uniqueAttributions.push({\n                      title: `${link.indexOf('&copy;') >= 0 ? '© ' : ''}${title}`,\n                      url: href\n                    });\n                  }\n                });\n              } catch (error) {\n                // just ignore for now\n              }\n            } else {\n              uniqueAttributions.push({title: attribution, url: null});\n            }\n          }\n        });\n      }\n    });\n    return uniqueAttributions;\n  }\n);\n\n/**\n * Deduplicated dataset and layer text attributions and logos.\n * Returns text attributions and logos to display.\n */\nexport const attributionSelector = createSelector(\n  [datasetAttributionSelector],\n  datasetAttributions => {\n    // TODO collect attributions from layers, and merge with dataset attributions here\n    const uniqueTextAttributions = datasetAttributions;\n    const logos: AttributionWithStyle[] = [];\n\n    return {sources: uniqueTextAttributions, logos};\n  }\n);\n\nexport const notificationPanelSelector = (props: KeplerGLProps) => ({\n  removeNotification: props.uiStateActions.removeNotification,\n  notifications: props.uiState.notifications\n});\n\nexport const DEFAULT_KEPLER_GL_PROPS = {\n  mapStyles: [],\n  mapStylesReplaceDefault: false,\n  mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n  width: 800,\n  height: 800,\n  appName: KEPLER_GL_NAME,\n  version: KEPLER_GL_VERSION,\n  sidePanelWidth: DIMENSIONS.sidePanel.width,\n  theme: {},\n  cloudProviders: [],\n  readOnly: false,\n  featureFlags: {}\n};\n\ntype KeplerGLBasicProps = {\n  mapboxApiAccessToken: string;\n  mapboxApiUrl?: string;\n  id: string;\n  width?: number;\n  height?: number;\n\n  appWebsite?: any;\n  onSaveMap?: () => void;\n  onViewStateChange?: (viewState: Viewport) => void;\n  onDeckInitialized?: () => void;\n  onKeplerGlInitialized?: () => void;\n  getMapboxRef?: () => React.RefObject<any>;\n  mapStyles?: {id: string; style?: object}[];\n  mapStylesReplaceDefault?: boolean;\n  appName?: string;\n  version?: string;\n  sidePanelWidth?: number;\n  theme?: object;\n  cloudProviders?: Provider[];\n  deckGlProps?: object;\n  onLoadCloudMapSuccess?: OnSuccessCallBack;\n  onLoadCloudMapError?: OnErrorCallBack;\n  onMouseMove?: (event: React.MouseEvent & {lngLat?: [number, number]}) => void;\n  onExportToCloudSuccess?: OnSuccessCallBack;\n  onExportToCloudError?: OnErrorCallBack;\n  readOnly?: boolean;\n  featureFlags?: FeatureFlags;\n\n  localeMessages?: {[key: string]: {[key: string]: string}};\n  dispatch: Dispatch<any>;\n\n  topMapContainerProps?: object;\n  bottomMapContainerProps?: object;\n\n  transformRequest?: (url: string) => {url: string};\n};\n\ntype KeplerGLProps = KeplerGlState & KeplerGlActions & KeplerGLBasicProps;\ntype KeplerGLCompState = {\n  dimensions: {width: number; height: number} | null;\n};\n\nKeplerGlFactory.deps = [\n  BottomWidgetFactory,\n  GeoCoderPanelFactory,\n  MapContainerFactory,\n  MapsLayoutFactory,\n  ModalContainerFactory,\n  SidePanelFactory,\n  PlotContainerFactory,\n  NotificationPanelFactory,\n  DndContextFactory,\n  EffectManagerFactory\n];\n\nfunction KeplerGlFactory(\n  BottomWidget: ReturnType<typeof BottomWidgetFactory>,\n  GeoCoderPanel: ReturnType<typeof GeoCoderPanelFactory>,\n  MapContainer: ReturnType<typeof MapContainerFactory>,\n  MapsLayout: ReturnType<typeof MapsLayoutFactory>,\n  ModalContainer: ReturnType<typeof ModalContainerFactory>,\n  SidePanel: ReturnType<typeof SidePanelFactory>,\n  PlotContainer: ReturnType<typeof PlotContainerFactory>,\n  NotificationPanel: ReturnType<typeof NotificationPanelFactory>,\n  DndContext: ReturnType<typeof DndContextFactory>\n): React.ComponentType<KeplerGLBasicProps & {selector: (...args: any[]) => KeplerGlState}> {\n  /** @typedef {import('./kepler-gl').UnconnectedKeplerGlProps} KeplerGlProps */\n  /** @augments React.Component<KeplerGlProps> */\n  class KeplerGL extends Component<\n    KeplerGLProps & {selector: (...args: any[]) => KeplerGlState},\n    KeplerGLCompState\n  > {\n    static defaultProps = DEFAULT_KEPLER_GL_PROPS;\n\n    state: KeplerGLCompState = {\n      dimensions: null\n    };\n\n    componentDidMount() {\n      if (getApplicationConfig().baseMapLibraryConfig?.['mapbox']?.mapLibName === 'Mapbox') {\n        this._validateMapboxToken();\n      }\n      this._loadMapStyle();\n      if (typeof this.props.onKeplerGlInitialized === 'function') {\n        this.props.onKeplerGlInitialized();\n      }\n      if (this.root.current instanceof HTMLElement) {\n        observeDimensions(this.root.current, this._handleResize);\n      }\n    }\n\n    componentWillUnmount() {\n      if (this.root.current instanceof HTMLElement) {\n        unobserveDimensions(this.root.current);\n      }\n    }\n\n    _handleResize = dimensions => {\n      this.setState({dimensions});\n    };\n\n    static contextType = RootContext;\n\n    root = createRef<HTMLDivElement>();\n    bottomWidgetRef = createRef<HTMLDivElement>();\n\n    /* selectors */\n    themeSelector = props => props.theme;\n    availableThemeSelector = createSelector(this.themeSelector, theme =>\n      typeof theme === 'object'\n        ? {\n            ...basicTheme,\n            ...theme\n          }\n        : theme === THEME.light\n        ? themeLT\n        : theme === THEME.base\n        ? themeBS\n        : theme\n    );\n\n    datasetsSelector = props => props.visState.datasets;\n    filteredDatasetsSelector = createSelector(this.datasetsSelector, getVisibleDatasets);\n\n    availableProviders = createSelector(\n      (props: KeplerGLProps) => props.cloudProviders,\n      providers =>\n        Array.isArray(providers) && providers.length\n          ? {\n              hasStorage: providers.some(p => p.hasPrivateStorage()),\n              hasShare: providers.some(p => p.hasSharingUrl())\n            }\n          : {}\n    );\n\n    localeMessagesSelector = createSelector(\n      (props: KeplerGLProps) => props.localeMessages,\n      customMessages => (customMessages ? mergeMessages(messages, customMessages) : messages)\n    );\n\n    /* private methods */\n    _validateMapboxToken() {\n      const {mapboxApiAccessToken} = this.props;\n      if (!validateToken(mapboxApiAccessToken)) {\n        Console.warn(MISSING_MAPBOX_TOKEN);\n      }\n    }\n\n    _loadMapStyle = () => {\n      const defaultStyles = Object.values(this.props.mapStyle.mapStyles);\n      // add id to custom map styles if not given\n      const customStyles = (this.props.mapStyles || []).map(ms => ({\n        ...ms,\n        id: ms.id || generateHashId()\n      }));\n\n      const allStyles = [...customStyles, ...defaultStyles].reduce((accu, style) => {\n        accu[style.id] = style;\n        return accu;\n      }, {});\n\n      this.props.mapStyleActions.loadMapStyles(allStyles);\n    };\n\n    _deleteMapLabels = (containerId, layerId) => {\n      this.props.visStateActions.toggleLayerForMap(containerId, layerId);\n    };\n\n    // eslint-disable-next-line complexity\n    render() {\n      const {\n        id = 'map',\n        width = DEFAULT_KEPLER_GL_PROPS.width,\n        height = DEFAULT_KEPLER_GL_PROPS.height,\n        uiState,\n        visState,\n        // readOnly override\n        readOnly,\n\n        // features\n        featureFlags,\n\n        // cloud providers\n        cloudProviders = []\n      } = this.props;\n\n      const dimensions = this.state.dimensions || {width, height};\n      const {\n        splitMaps, // this will store support for split map view is necessary\n        interactionConfig\n      } = visState;\n\n      const isSplit = isSplitSelector(this.props);\n      const theme = this.availableThemeSelector(this.props);\n      const localeMessages = this.localeMessagesSelector(this.props);\n      const isExportingImage = uiState.exportImage.exporting;\n      const availableProviders = this.availableProviders(this.props);\n\n      const filteredDatasets = this.filteredDatasetsSelector(this.props);\n      const sideFields = sidePanelSelector(this.props, availableProviders, filteredDatasets);\n      const plotContainerFields = plotContainerSelector(this.props);\n      const bottomWidgetFields = bottomWidgetSelector(this.props, theme);\n      const modalContainerFields = modalContainerSelector(this.props, this.root.current);\n      const geoCoderPanelFields = geoCoderPanelSelector(this.props, dimensions);\n      const notificationPanelFields = notificationPanelSelector(this.props);\n\n      const mapContainers = !isSplit\n        ? [\n            <MapContainer\n              primary={true}\n              key={0}\n              index={0}\n              {...mapFieldsSelector(this.props)}\n              containerId={0}\n              deleteMapLabels={this._deleteMapLabels}\n            />\n          ]\n        : splitMaps.map((settings, index) => (\n            <MapContainer\n              key={index}\n              index={index}\n              primary={index === 1}\n              {...mapFieldsSelector(this.props, index)}\n              containerId={index}\n              deleteMapLabels={this._deleteMapLabels}\n            />\n          ));\n\n      return (\n        <RootContext.Provider value={this.root}>\n          <FeatureFlagsContextProvider featureFlags={featureFlags}>\n            <IntlProvider locale={uiState.locale} messages={localeMessages[uiState.locale]}>\n              <StyleSheetManager shouldForwardProp={shouldForwardProp}>\n                <ThemeProvider theme={theme}>\n                  <CloudListProvider providers={cloudProviders}>\n                    <GlobalStyle\n                      className=\"kepler-gl\"\n                      id={`kepler-gl__${id}`}\n                      style={{\n                        display: 'flex',\n                        flexDirection: 'column',\n                        position: 'relative',\n                        width: `${width}px`,\n                        height: `${height}px`\n                      }}\n                      ref={this.root}\n                    >\n                      <NotificationPanel {...notificationPanelFields} />\n                      <DndContext visState={visState}>\n                        {!uiState.readOnly && !readOnly && <SidePanel {...sideFields} />}\n                        <MapsLayout className=\"maps\" mapState={this.props.mapState}>\n                          {mapContainers}\n                        </MapsLayout>\n                      </DndContext>\n                      {isExportingImage && <PlotContainer {...plotContainerFields} />}\n                      {/* 1 geocoder: single mode OR split mode and synced viewports */}\n                      {!isViewportDisjointed(this.props) && interactionConfig.geocoder.enabled && (\n                        <GeoCoderPanel\n                          {...geoCoderPanelFields}\n                          index={0}\n                          unsyncedViewports={false}\n                        />\n                      )}\n                      {/* 2 geocoders: split mode and unsynced viewports */}\n                      {isViewportDisjointed(this.props) &&\n                        interactionConfig.geocoder.enabled &&\n                        mapContainers.map((_mapContainer, index) => (\n                          <GeoCoderPanel\n                            key={index}\n                            {...geoCoderPanelFields}\n                            index={index}\n                            unsyncedViewports={true}\n                          />\n                        ))}\n                      <BottomWidgetOuter absolute={!hasPortableWidth(breakPointValues)}>\n                        <BottomWidget\n                          rootRef={this.bottomWidgetRef}\n                          {...bottomWidgetFields}\n                          containerW={dimensions.width}\n                          theme={theme}\n                        />\n                      </BottomWidgetOuter>\n                      <ModalContainer\n                        {...modalContainerFields}\n                        containerW={dimensions.width}\n                        containerH={dimensions.height}\n                      />\n                    </GlobalStyle>\n                  </CloudListProvider>\n                </ThemeProvider>\n              </StyleSheetManager>\n            </IntlProvider>\n          </FeatureFlagsContextProvider>\n        </RootContext.Provider>\n      );\n    }\n  }\n\n  return keplerGlConnect(\n    mapStateToProps,\n    makeMapDispatchToProps\n  )(withTheme(KeplerGL)) as ReturnType<typeof KeplerGlFactory>;\n}\n\nexport function mapStateToProps(state: KeplerGlState, props: KeplerGLProps) {\n  return {\n    ...props,\n    visState: state.visState,\n    mapStyle: state.mapStyle,\n    mapState: state.mapState,\n    uiState: state.uiState,\n    providerState: state.providerState\n  };\n}\n\nconst defaultUserActions = {};\n\nconst getDispatch = dispatch => dispatch;\nconst getUserActions = (dispatch, props) => props.actions || defaultUserActions;\n\n/** @type {() => import('reselect').OutputParametricSelector<any, any, any, any>} */\nexport function makeGetActionCreators() {\n  return createSelector([getDispatch, getUserActions], (dispatch, userActions) => {\n    const [visStateActions, mapStateActions, mapStyleActions, uiStateActions, providerActions] = [\n      VisStateActions,\n      MapStateActions,\n      MapStyleActions,\n      UIStateActions,\n      ProviderActions\n    ].map(actions => bindActionCreators(mergeActions(actions, userActions), dispatch));\n\n    return {\n      visStateActions,\n      mapStateActions,\n      mapStyleActions,\n      uiStateActions,\n      providerActions,\n      dispatch\n    };\n  });\n}\n\nfunction makeMapDispatchToProps() {\n  const getActionCreators = makeGetActionCreators();\n  const mapDispatchToProps = (dispatch, ownProps) => {\n    const groupedActionCreators = getActionCreators(dispatch, ownProps);\n\n    return {\n      ...groupedActionCreators,\n      dispatch\n    };\n  };\n\n  return mapDispatchToProps;\n}\n\n/**\n * Override default kepler.gl actions with user defined actions using the same key\n */\nfunction mergeActions(actions, userActions) {\n  const overrides = {};\n  for (const key in userActions) {\n    if (\n      Object.prototype.hasOwnProperty.call(userActions, key) &&\n      Object.prototype.hasOwnProperty.call(actions, key)\n    ) {\n      overrides[key] = userActions[key];\n    }\n  }\n\n  return {...actions, ...overrides};\n}\n\nexport default KeplerGlFactory;\n"
  },
  {
    "path": "src/components/src/layer-animation-controller.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport {ANIMATION_WINDOW} from '@kepler.gl/constants';\nimport {AnimationConfig, Timeline} from '@kepler.gl/types';\nimport {snapToMarks, getTimelineFromAnimationConfig} from '@kepler.gl/utils';\nimport {toArray} from '@kepler.gl/common-utils';\nimport AnimationControllerFactory from './common/animation-control/animation-controller';\n\ninterface LayerAnimationControllerProps {\n  animationConfig: AnimationConfig;\n  setLayerAnimationTime: (x: number) => void;\n  children?: (\n    isAnimating: boolean | undefined,\n    startAnimation: () => void,\n    pauseAnimation: () => void,\n    resetAnimation: () => void,\n    timeline: Timeline | undefined,\n    setTimelineValue: (x: number | number[]) => void\n  ) => React.ReactElement | null;\n}\n\nLayerAnimationControllerFactory.deps = [AnimationControllerFactory];\n\nfunction LayerAnimationControllerFactory(\n  AnimationController: ReturnType<typeof AnimationControllerFactory>\n) {\n  const LayerAnimationController: React.FC<LayerAnimationControllerProps> = ({\n    animationConfig,\n    setLayerAnimationTime,\n    children\n  }) => {\n    const {timeSteps, domain} = animationConfig;\n\n    const setTimelineValue = useCallback(\n      (value: number | number[]) => {\n        const timelineValue = toArray(value)[0];\n        if (Array.isArray(timeSteps)) {\n          setLayerAnimationTime(snapToMarks(timelineValue, timeSteps));\n\n          // TODO: merge slider in to avoid this step\n        } else if (domain && timelineValue >= domain[0] && timelineValue <= domain[1]) {\n          setLayerAnimationTime(timelineValue);\n        }\n      },\n      [domain, setLayerAnimationTime, timeSteps]\n    );\n\n    const timeline = getTimelineFromAnimationConfig(animationConfig);\n\n    return (\n      <AnimationController\n        key=\"layer-control\"\n        value={Number(animationConfig.currentTime)}\n        domain={animationConfig.domain}\n        speed={animationConfig.speed}\n        isAnimating={animationConfig.isAnimating}\n        steps={animationConfig.timeSteps}\n        animationWindow={\n          animationConfig.timeSteps ? ANIMATION_WINDOW.interval : ANIMATION_WINDOW.point\n        }\n        setTimelineValue={setTimelineValue}\n        timeline={timeline}\n        children={children}\n      />\n    );\n  };\n  return LayerAnimationController;\n}\n\nexport default LayerAnimationControllerFactory;\n"
  },
  {
    "path": "src/components/src/loading-indicator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PropsWithChildren, useRef, useEffect} from 'react';\nimport styled, {withTheme, keyframes} from 'styled-components';\n\nimport {getNumRasterTilesBeingLoaded, getNumVectorTilesBeingLoaded} from '@kepler.gl/layers';\n\ntype StyledContainerProps = {\n  $isVisible?: boolean;\n  $left: number;\n};\n\nconst spin = keyframes`\n  0% { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n`;\n\nconst Spinner = styled.div`\n  display: inline-block;\n  width: 14px;\n  height: 14px;\n  margin-right: 8px;\n  border: 2px solid ${props => props.theme.textColorHl};\n  border-top-color: transparent;\n  border-radius: 50%;\n  animation: ${spin} 0.8s linear infinite;\n  vertical-align: middle;\n`;\n\nexport const StyledContainer = styled.div<StyledContainerProps>`\n  position: absolute;\n  left: ${props => props.$left}px;\n  bottom: ${props => props.theme.sidePanel.margin.left}px;\n  z-index: 1;\n  color: ${props => props.theme.textColor};\n  opacity: ${props => (props.$isVisible ? 1 : 0)};\n  transition: opacity 0.5s ease-in-out;\n  background-color: ${props => props.theme.sidePanelBg};\n  padding: 8px 12px;\n  font-size: 13px;\n  font-weight: 500;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n  backdrop-filter: blur(4px);\n  display: flex;\n  align-items: center;\n  pointer-events: none;\n`;\n\ntype LoadingIndicatorProps = {\n  isVisible?: boolean;\n  activeSidePanel?: boolean;\n  sidePanelWidth?: number;\n};\n\n/** Extra adjustment for the loading indicator when side panel is visible */\nconst LEFT_POSITION_ADJUSTMENT = 3;\n\nconst LoadingIndicator: React.FC<LoadingIndicatorProps & {theme: any}> = ({\n  isVisible,\n  activeSidePanel,\n  sidePanelWidth,\n  theme\n}) => {\n  const left =\n    (activeSidePanel ? (sidePanelWidth || 0) + LEFT_POSITION_ADJUSTMENT : 0) +\n    theme.sidePanel.margin.left;\n\n  // Helper message to track number of tiles that are being loaded\n  const numRasterTilesInProgress = getNumRasterTilesBeingLoaded();\n  const numVectorTilesInProgress = getNumVectorTilesBeingLoaded();\n\n  let extraMessage = '';\n  if (numRasterTilesInProgress > 0 && numVectorTilesInProgress > 0) {\n    // Both types loading: show combined count\n    const totalTiles = numRasterTilesInProgress + numVectorTilesInProgress;\n    extraMessage = `${totalTiles} tile${totalTiles === 1 ? ' is' : 's are'} being loaded`;\n  } else if (numRasterTilesInProgress > 0) {\n    // Only raster tiles loading\n    extraMessage = `${numRasterTilesInProgress} raster tile${\n      numRasterTilesInProgress === 1 ? ' is' : 's are'\n    } being loaded`;\n  } else if (numVectorTilesInProgress > 0) {\n    // Only vector tiles loading\n    extraMessage = `${numVectorTilesInProgress} vector tile${\n      numVectorTilesInProgress === 1 ? ' is' : 's are'\n    } being loaded`;\n  }\n\n  // Preserve the last message during fade-out\n  const lastMessageRef = useRef(extraMessage);\n  useEffect(() => {\n    if (isVisible && extraMessage) {\n      lastMessageRef.current = extraMessage;\n    }\n  }, [isVisible, extraMessage]);\n\n  const displayMessage = isVisible ? extraMessage : lastMessageRef.current;\n\n  return (\n    <StyledContainer $isVisible={isVisible} $left={left}>\n      <Spinner />\n      <span>{`Loading... ${displayMessage}`}</span>\n    </StyledContainer>\n  );\n};\n\nexport default withTheme(LoadingIndicator) as React.FC<PropsWithChildren<LoadingIndicatorProps>>;\n"
  },
  {
    "path": "src/components/src/map/coordinate-info.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {preciseRound} from '@kepler.gl/utils';\nimport {CursorClick} from '../common/icons';\nimport {StyledLayerName} from './layer-hover-info';\n\n// 6th decimal is worth up to 0.11 m\n// https://gis.stackexchange.com/questions/8650/measuring-accuracy-of-latitude-and-longitude\nconst DECIMAL = 6;\nconst DECIMAL_Z = 1;\n\nexport interface CoordinateInfoProps {\n  coordinate: number[];\n  zoom: number;\n}\n\nconst CoordinateInfoFactory = () => {\n  const CoordinateInfo: React.FC<CoordinateInfoProps> = ({coordinate, zoom}) => (\n    <div className=\"coordingate-hover-info\">\n      <StyledLayerName className=\"map-popover__layer-name\">\n        <CursorClick height=\"12px\" />\n        Coordinate\n      </StyledLayerName>\n      <table>\n        <tbody>\n          <tr className=\"row\">\n            <td className=\"row__value\">{preciseRound(coordinate[1], DECIMAL)},</td>\n            <td className=\"row__value\">{preciseRound(coordinate[0], DECIMAL)},</td>\n            <td className=\"row__value\">{preciseRound(zoom, DECIMAL_Z)}z</td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  );\n\n  return CoordinateInfo;\n};\n\nexport default CoordinateInfoFactory;\n"
  },
  {
    "path": "src/components/src/map/effects/effect-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, ComponentType} from 'react';\n\nimport {MapControls} from '@kepler.gl/types';\n\nimport {MagicWand} from '../../common/icons';\nimport {MapControlButton} from '../../common/styled-components';\nimport MapControlTooltipFactory from '../map-control-tooltip';\n\ninterface EffectControlIcons {\n  effectsIcon: ComponentType<any>;\n}\n\nexport type EffectControlProps = {\n  mapControls: MapControls;\n  onToggleMapControl: (control: string) => void;\n  actionIcons: EffectControlIcons;\n};\n\nEffectControlFactory.deps = [MapControlTooltipFactory];\n\nexport default function EffectControlFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>\n): React.FC<EffectControlProps> {\n  const defaultActionIcons = {\n    effectsIcon: MagicWand\n  };\n\n  const EffectControl = ({\n    mapControls,\n    onToggleMapControl,\n    actionIcons = defaultActionIcons\n  }: EffectControlProps) => {\n    const onClick = useCallback(\n      event => {\n        event.preventDefault();\n        onToggleMapControl('effect');\n      },\n      [onToggleMapControl]\n    );\n\n    const showControl = mapControls?.effect?.show;\n    if (!showControl) {\n      return null;\n    }\n\n    const active = mapControls?.effect?.active;\n    return (\n      <MapControlTooltip\n        id=\"show-effect\"\n        message={active ? 'tooltip.hideEffectPanel' : 'tooltip.showEffectPanel'}\n      >\n        <MapControlButton\n          className=\"map-control-button toggle-effect\"\n          onClick={onClick}\n          active={active}\n        >\n          <actionIcons.effectsIcon height=\"22px\" />\n        </MapControlButton>\n      </MapControlTooltip>\n    );\n  };\n\n  EffectControl.displayName = 'EffectControl';\n  return React.memo(EffectControl);\n}\n"
  },
  {
    "path": "src/components/src/map/layer-hover-info.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport truncate from 'lodash/truncate';\nimport {CompareType, Field, Merge, TooltipField} from '@kepler.gl/types';\nimport {CenterFlexbox} from '../common/styled-components';\nimport {Layers} from '../common/icons';\nimport PropTypes from 'prop-types';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {DataRow} from '@kepler.gl/utils';\nimport {Layer} from '@kepler.gl/layers';\nimport {\n  AggregationLayerHoverData,\n  LayerHoverProp,\n  getTooltipDisplayDeltaValue,\n  getTooltipDisplayValue\n} from '@kepler.gl/reducers';\nimport {useIntl} from 'react-intl';\nimport {VisState} from '@kepler.gl/schemas';\nimport {capitalizeFirstLetter} from '@kepler.gl/utils';\n\nexport const StyledLayerName = styled(CenterFlexbox)`\n  color: ${props => props.theme.textColorHl};\n  font-size: 12px;\n  letter-spacing: 0.43px;\n  text-transform: capitalize;\n\n  svg {\n    margin-right: 4px;\n  }\n`;\n\nconst StyledTable = styled.table`\n  & .row__delta-value {\n    text-align: right;\n    margin-left: 6px;\n\n    &.positive {\n      color: ${props => props.theme.notificationColors.success};\n    }\n\n    &.negative {\n      color: ${props => props.theme.negativeBtnActBgd};\n    }\n  }\n  & .row__value,\n  & .row__name {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: no-wrap;\n  }\n`;\n\nconst StyledDivider = styled.div`\n  // offset divider to reach popover edge\n  margin-left: -14px;\n  margin-right: -14px;\n  border-bottom: 1px solid ${props => props.theme.panelBorderColor};\n`;\n\ninterface RowProps {\n  name: string;\n  value: string;\n  deltaValue?: string | null;\n  url?: string;\n}\n\nconst TOOLTIP_VALUE_MAX_LENGTH = 256;\n\nconst Row: React.FC<RowProps> = ({name, value, deltaValue, url}) => {\n  // Set 'url' to 'value' if it looks like a url\n  if (!url && value && typeof value === 'string' && value.match(/^http/)) {\n    url = value;\n  }\n\n  const displayValue =\n    typeof value === 'string' && value.length > TOOLTIP_VALUE_MAX_LENGTH\n      ? truncate(value, {length: TOOLTIP_VALUE_MAX_LENGTH})\n      : value;\n\n  const asImg = /<img>/.test(name);\n  return (\n    <tr className=\"layer-hover-info__row\" key={name}>\n      <td className=\"row__name\">{asImg ? name.replace('<img>', '') : name}</td>\n      <td className=\"row__value\">\n        {asImg ? (\n          <img src={value} />\n        ) : url ? (\n          <a target=\"_blank\" rel=\"noopener noreferrer\" href={url}>\n            {displayValue}\n          </a>\n        ) : (\n          <>\n            <span>{displayValue}</span>\n            {notNullorUndefined(deltaValue) ? (\n              <span\n                className={`row__delta-value ${\n                  deltaValue?.toString().charAt(0) === '+' ? 'positive' : 'negative'\n                }`}\n              >\n                {deltaValue}\n              </span>\n            ) : null}\n          </>\n        )}\n      </td>\n    </tr>\n  );\n};\n\nexport type EntryInfoProps = Merge<LayerHoverProp, {fieldsToShow: TooltipField[]}>;\n\nconst EntryInfo: React.FC<EntryInfoProps> = ({fieldsToShow, ...props}) => (\n  <tbody>\n    {fieldsToShow.map(item => (\n      <EntryInfoRow key={item.name} item={item} {...props} />\n    ))}\n  </tbody>\n);\n\nexport type EntryInfoRowProps = {\n  data: LayerHoverProp['data'];\n  fields: Field[];\n  layer: Layer;\n  primaryData?: LayerHoverProp['primaryData'];\n  compareType?: CompareType;\n  currentTime?: VisState['animationConfig']['currentTime'];\n  item: TooltipField;\n};\n\nconst EntryInfoRow: React.FC<EntryInfoRowProps> = ({\n  layer,\n  item,\n  fields,\n  data,\n  primaryData,\n  compareType,\n  currentTime\n}) => {\n  const fieldIdx = fields.findIndex(f => f.name === item.name);\n  if (fieldIdx < 0) {\n    return null;\n  }\n  const field = fields[fieldIdx];\n  const fieldValueAccessor = layer.accessVSFieldValue(field, currentTime);\n  const value = fieldValueAccessor(field, data instanceof DataRow ? {index: data._rowIndex} : data);\n\n  // Handle WMS layer data in comparison mode - WMS layers don't have comparable field data\n  let primaryValue = null;\n  let displayDeltaValue: string | null = null;\n\n  if (primaryData) {\n    try {\n      // Only calculate primary value if primaryData has a compatible structure\n      if (\n        primaryData instanceof DataRow ||\n        (primaryData && typeof primaryData === 'object' && 'index' in primaryData)\n      ) {\n        primaryValue = fieldValueAccessor(\n          field,\n          primaryData instanceof DataRow ? {index: primaryData._rowIndex} : primaryData\n        );\n\n        displayDeltaValue = getTooltipDisplayDeltaValue({\n          field,\n          value,\n          primaryValue,\n          compareType\n        });\n      }\n    } catch (error) {\n      // If there's an error accessing primaryData (e.g., WMS layer data), skip comparison\n      primaryValue = null;\n    }\n  }\n\n  const displayValue = getTooltipDisplayValue({item, field, value});\n\n  return (\n    <Row\n      name={field.displayName || field.name}\n      value={displayValue}\n      deltaValue={displayDeltaValue}\n    />\n  );\n};\n\n// TODO: supporting comparative value for aggregated cells as well\nconst CellInfo = ({\n  fieldsToShow,\n  data,\n  layer\n}: {\n  data: AggregationLayerHoverData;\n  fieldsToShow: TooltipField[];\n  layer: Layer;\n}) => {\n  const {colorField, sizeField} = layer.config as any;\n\n  const colorValue = useMemo(() => {\n    if (colorField && layer.visualChannels.color) {\n      const item = fieldsToShow.find(field => field.name === colorField.name);\n      return getTooltipDisplayValue({item, field: colorField, value: data.colorValue});\n    }\n    return null;\n  }, [fieldsToShow, colorField, layer, data.colorValue]);\n\n  const elevationValue = useMemo(() => {\n    if (sizeField && layer.visualChannels.size) {\n      const item = fieldsToShow.find(field => field.name === sizeField.name);\n      return getTooltipDisplayValue({item, field: sizeField, value: data.elevationValue});\n    }\n    return null;\n  }, [fieldsToShow, sizeField, layer, data.elevationValue]);\n\n  const aggregatedData = useMemo(() => {\n    if (data.aggregatedData && fieldsToShow) {\n      return fieldsToShow.reduce((acc, field) => {\n        const dataForField = data.aggregatedData?.[field.name];\n        if (dataForField?.measure && field.name !== colorField?.name) {\n          acc.push({\n            name: `${capitalizeFirstLetter(dataForField.measure)} of ${field.name}`,\n            value: dataForField.value\n          });\n        }\n        return acc;\n      }, [] as {name: string; value?: string}[]);\n    }\n    return [];\n  }, [data.aggregatedData, fieldsToShow, colorField?.name]);\n\n  const colorMeasure = layer.getVisualChannelDescription('color').measure;\n  const sizeMeasure = layer.getVisualChannelDescription('size').measure;\n  return (\n    <tbody>\n      <Row name={'total points'} key=\"count\" value={String(data.points && data.points.length)} />\n      {colorField && layer.visualChannels.color && colorMeasure ? (\n        <Row name={colorMeasure} key=\"color\" value={colorValue || 'N/A'} />\n      ) : null}\n      {sizeField && layer.visualChannels.size && sizeMeasure ? (\n        <Row name={sizeMeasure} key=\"size\" value={elevationValue || 'N/A'} />\n      ) : null}\n      {aggregatedData.map((dataForField, idx) => (\n        <Row name={dataForField.name} key={`data_${idx}`} value={dataForField.value || 'N/A'} />\n      ))}\n    </tbody>\n  );\n};\n\nconst LayerHoverInfoFactory = () => {\n  const LayerHoverInfo = props => {\n    const {data, layer} = props;\n    const intl = useIntl();\n    if (!data || !layer) {\n      return null;\n    }\n\n    const hasFieldsToShow =\n      (data.fieldValues && Object.keys(data.fieldValues).length > 0) ||\n      (data.wmsFeatureData && data.wmsFeatureData.length > 0) ||\n      (props.fieldsToShow && props.fieldsToShow.length > 0);\n\n    return (\n      <div className=\"map-popover__layer-info\">\n        <StyledLayerName className=\"map-popover__layer-name\">\n          <Layers height=\"12px\" />\n          {props.layer.config.label}\n        </StyledLayerName>\n        {hasFieldsToShow && <StyledDivider />}\n        <StyledTable>\n          {data.wmsFeatureData ? (\n            <tbody>\n              {data.wmsFeatureData.map(({name, value}, i) => (\n                <Row key={i} name={name} value={value} />\n              ))}\n            </tbody>\n          ) : data.fieldValues ? (\n            <tbody>\n              {data.fieldValues.map(({labelMessage, value}, i) => (\n                <Row key={i} name={intl.formatMessage({id: labelMessage})} value={value} />\n              ))}\n            </tbody>\n          ) : props.layer.isAggregated ? (\n            <CellInfo {...props} />\n          ) : (\n            <EntryInfo {...props} />\n          )}\n        </StyledTable>\n        {hasFieldsToShow && <StyledDivider />}\n      </div>\n    );\n  };\n\n  LayerHoverInfo.propTypes = {\n    fields: PropTypes.arrayOf(PropTypes.any),\n    fieldsToShow: PropTypes.arrayOf(PropTypes.any),\n    layer: PropTypes.object,\n    data: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.any), PropTypes.object])\n  };\n  return LayerHoverInfo;\n};\n\nexport default LayerHoverInfoFactory;\n"
  },
  {
    "path": "src/components/src/map/layer-selector-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport classnames from 'classnames';\n\nimport {MapControlButton} from '../common/styled-components';\nimport {Layers} from '../common/icons';\nimport MapLayerSelector from '../common/map-layer-selector';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport MapControlPanelFactory from './map-control-panel';\nimport {Layer} from '@kepler.gl/layers';\nimport {MapControlItem, MapControls} from '@kepler.gl/types';\n\nLayerSelectorPanelFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory];\n\nexport type LayerSelectorPanelProps = {\n  onMapToggleLayer: (layerId: string) => void;\n  onToggleMapControl: (control: string) => void;\n  layers: ReadonlyArray<Layer>;\n  layersToRender: {[key: string]: boolean};\n  isSplit: boolean;\n  mapControls: MapControls;\n  readOnly: boolean;\n};\n\nfunction LayerSelectorPanelFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>,\n  MapControlPanel: ReturnType<typeof MapControlPanelFactory>\n) {\n  /** @type {import('./layer-selector-panel').LayerSelectorPanelComponent} */\n  const LayerSelectorPanel: React.FC<LayerSelectorPanelProps> = ({\n    onMapToggleLayer,\n    onToggleMapControl,\n    layers,\n    layersToRender,\n    isSplit,\n    mapControls,\n    readOnly\n  }) => {\n    const visibleLayers = mapControls?.visibleLayers || ({} as MapControlItem);\n    const {active: isActive, show, disableClose} = visibleLayers || {};\n\n    const legendLayers = useMemo(\n      () =>\n        layers\n          .filter(({config}) => config.isVisible)\n          .map(({id, config}) => ({\n            id,\n            name: config.label,\n            // layer\n            isVisible: layersToRender[id]\n          })),\n      [layers, layersToRender]\n    );\n\n    const isVisible = useMemo(\n      () => isSplit && show && readOnly !== true,\n      [isSplit, show, readOnly]\n    );\n\n    const onToggleMenuPanel = useCallback(\n      event => {\n        event.preventDefault();\n        onToggleMapControl('visibleLayers');\n      },\n      [onToggleMapControl]\n    );\n\n    return isVisible ? (\n      !isActive ? (\n        <MapControlButton\n          key={1}\n          onClick={onToggleMenuPanel}\n          className={classnames('map-control-button', 'toggle-layer', {isActive})}\n          data-tip\n          data-for=\"toggle-layer\"\n        >\n          <Layers height=\"16px\" />\n          <MapControlTooltip\n            id=\"toggle-layer\"\n            message={isActive ? 'tooltip.hideLayerPanel' : 'tooltip.showLayerPanel'}\n          />\n        </MapControlButton>\n      ) : (\n        <MapControlPanel\n          header=\"header.visibleLayers\"\n          onClick={onToggleMenuPanel}\n          disableClose={disableClose}\n        >\n          <MapLayerSelector layers={legendLayers} onMapToggleLayer={onMapToggleLayer} />\n        </MapControlPanel>\n      )\n    ) : null;\n  };\n\n  LayerSelectorPanel.displayName = 'LayerSelectorPanel';\n\n  return React.memo(LayerSelectorPanel);\n}\n\nexport default LayerSelectorPanelFactory;\n"
  },
  {
    "path": "src/components/src/map/lazy-tippy.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport React, {useState, forwardRef} from 'react';\nimport Tippy from '@tippyjs/react/headless';\nimport type {TippyProps} from '@tippyjs/react';\nimport {isTest} from '@kepler.gl/utils';\n\nconst isTestEnv = isTest();\n\n/**\n * Lazy mounting tippy content\n * https://gist.github.com/atomiks/520f4b0c7b537202a23a3059d4eec908\n */\n// eslint-disable-next-line react/display-name\nconst LazyTippy = forwardRef((props: TippyProps, ref) => {\n  // Mount in test env for easier testing\n  const [mounted, setMounted] = useState(isTestEnv);\n\n  const lazyPlugin = {\n    fn: () => ({\n      onMount: () => setMounted(true),\n      onHidden: () => setMounted(false)\n    })\n  };\n\n  const computedProps = {...props};\n\n  computedProps.plugins = [lazyPlugin, ...(props.plugins || [])];\n\n  if (props.render) {\n    computedProps.render = (...args) => (mounted ? props.render(...args) : '');\n  } else {\n    computedProps.content = mounted ? props.content : '';\n  }\n\n  return <Tippy {...computedProps} ref={ref} />;\n});\n\nexport default LazyTippy;\n"
  },
  {
    "path": "src/components/src/map/locale-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport classnames from 'classnames';\n\nimport ToolbarItem from '../common/toolbar-item';\nimport {MapControlButton} from '../common/styled-components';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport MapControlToolbarFactory from './map-control-toolbar';\nimport {MapControls} from '@kepler.gl/types';\n\nLocalePanelFactory.deps = [MapControlTooltipFactory, MapControlToolbarFactory];\n\nexport type LocalePanelProps = {\n  availableLocales: ReadonlyArray<string>;\n  onSetLocale: (locale: string) => void;\n  locale: string;\n  onToggleMapControl: (control: string) => void;\n  mapControls: MapControls;\n};\n\nfunction LocalePanelFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>,\n  MapControlToolbar: ReturnType<typeof MapControlToolbarFactory>\n) {\n  const LocalePanel: React.FC<LocalePanelProps> = React.memo(\n    ({availableLocales, onToggleMapControl, onSetLocale, locale: currentLocal, mapControls}) => {\n      const {active: isActive, show} = mapControls.mapLocale || {};\n\n      const onClickItem = useCallback(\n        locale => {\n          onSetLocale(locale);\n        },\n        [onSetLocale]\n      );\n\n      const onClickButton = useCallback(\n        e => {\n          e.preventDefault();\n          onToggleMapControl('mapLocale');\n        },\n        [onToggleMapControl]\n      );\n      const getLabel = useCallback(locale => `toolbar.${locale}`, []);\n\n      if (!show) {\n        return null;\n      }\n      return (\n        <div className=\"locale-panel-controls\" style={{position: 'relative'}}>\n          {isActive ? (\n            <MapControlToolbar show={isActive}>\n              {availableLocales.map(locale => (\n                <ToolbarItem\n                  key={locale}\n                  onClick={() => onClickItem(locale)}\n                  label={getLabel(locale)}\n                  active={currentLocal === locale}\n                />\n              ))}\n            </MapControlToolbar>\n          ) : null}\n          <MapControlTooltip id=\"locale\" message=\"tooltip.selectLocale\">\n            <MapControlButton\n              className={classnames('map-control-button', 'locale-panel', {isActive})}\n              onClick={onClickButton}\n              active={isActive}\n            >\n              <span className=\"map-control-button__locale\">{currentLocal.toUpperCase()}</span>\n            </MapControlButton>\n          </MapControlTooltip>\n        </div>\n      );\n    }\n  );\n\n  LocalePanel.displayName = 'LocalePanel';\n\n  return LocalePanel;\n}\n\nexport default LocalePanelFactory;\n"
  },
  {
    "path": "src/components/src/map/map-control-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {CenterFlexbox, IconRoundSmall} from '../common/styled-components';\nimport {Close, Pin} from '../common/icons';\nimport Switch from '../common/switch';\nimport {MapState} from '@kepler.gl/types';\nimport {ActionHandler, toggleSplitMapViewport} from '@kepler.gl/actions';\nimport classNames from 'classnames';\n\nconst StyledMapControlPanel = styled.div`\n  background-color: ${props => props.theme.mapPanelBackgroundColor};\n  flex-grow: 1;\n  z-index: 1;\n  p {\n    margin-bottom: 0;\n  }\n`;\n\ntype StyledMapControlPanelContentProps = {\n  isExport?: boolean;\n};\n\nconst StyledMapControlPanelContent = styled.div.attrs({\n  className: 'map-control__panel-content'\n})<StyledMapControlPanelContentProps>`\n  ${props => props.theme.sidePanelScrollBar};\n  max-height: 500px;\n  min-height: 100px;\n  min-width: ${props => props.theme.mapControl.width}px;\n  overflow: ${props => (props.isExport ? 'hidden' : 'overlay')};\n`;\n\ntype MapControlPanelHeaderProps = {\n  children?: React.ReactNode[];\n};\n\nconst StyledMapControlPanelHeader = styled.div.attrs({\n  className: 'map-control__panel-header'\n})<MapControlPanelHeaderProps>`\n  display: flex;\n  justify-content: space-between;\n  background-color: ${props => props.theme.mapPanelHeaderBackgroundColor};\n  height: 32px;\n  padding: 6px 12px;\n  font-family: ${props => props.theme.fontFamily};\n  font-size: 11px;\n  color: ${props => props.theme.titleTextColor};\n  position: relative;\n  box-sizing: border-box;\n  align-items: center;\n\n  button {\n    width: 18px;\n    height: 18px;\n  }\n`;\n\nconst StyledMapControlPanelHeaderSplitViewportsTools = styled(StyledMapControlPanelHeader).attrs({\n  className: 'map-control__panel-split-viewport-tools'\n})`\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  align-items: self-start;\n  height: unset;\n`;\n\nconst StyledSBCenterFlexbox = styled(CenterFlexbox)`\n  width: 100%;\n  justify-content: space-between;\n  flex-direction: row;\n`;\n\ninterface StyledDisableableTextProps {\n  disabled?: boolean;\n}\n\nconst StyledDisableableText = styled.span<StyledDisableableTextProps>`\n  opacity: ${props => (props.disabled ? 0.4 : 1)};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n`;\n\nconst StyledDisableableSwitch = styled(Switch)`\n  opacity: ${props => (props.disabled ? 0.4 : 1)};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n`;\n\nconst StyledIcon = styled(IconRoundSmall)`\n  color: ${props => props.theme.activeColor};\n  background-color: transparent;\n\n  &:hover {\n    cursor: pointer;\n    background-color: transparent;\n    color: ${props => props.theme.floatingBtnActColor};\n  }\n`;\n\nexport type MapControlPanelProps = {\n  header?: string;\n  scale?: number;\n  onClick?: React.MouseEventHandler<HTMLDivElement>;\n  onPinClick?: React.MouseEventHandler<HTMLDivElement>;\n  pinnable?: boolean;\n  disableClose?: boolean;\n  isExport?: boolean;\n  logoComponent?: Element;\n  mapState?: MapState;\n  onToggleSplitMapViewport?: ActionHandler<typeof toggleSplitMapViewport>;\n  isViewportUnsyncAllowed?: boolean;\n  children?: React.ReactNode;\n  className?: string;\n};\n\nfunction MapControlPanelFactory() {\n  const MapControlPanel: React.FC<MapControlPanelProps> = React.memo(\n    ({\n      children,\n      header,\n      pinnable,\n      disableClose,\n      onPinClick,\n      onClick,\n      scale = 1,\n      isExport,\n      logoComponent,\n      mapState,\n      onToggleSplitMapViewport,\n      isViewportUnsyncAllowed,\n      className = 'map-control-panel'\n    }) => {\n      const {isViewportSynced, isZoomLocked} = mapState || {};\n      const onUnlockViewportChange = useCallback(() => {\n        onToggleSplitMapViewport?.({isViewportSynced: !isViewportSynced});\n      }, [isViewportSynced, onToggleSplitMapViewport]);\n\n      const onSyncZoomChange = useCallback(() => {\n        onToggleSplitMapViewport?.({isZoomLocked: !isZoomLocked});\n      }, [isZoomLocked, onToggleSplitMapViewport]);\n\n      return (\n        <StyledMapControlPanel\n          style={{\n            transform: `scale(${scale})`,\n            marginBottom: '8px !important'\n          }}\n          className={classNames('map-control-panel', className)}\n        >\n          {mapState?.isSplit && isViewportUnsyncAllowed ? (\n            <StyledMapControlPanelHeaderSplitViewportsTools>\n              <StyledSBCenterFlexbox style={{paddingBottom: '6px'}}>\n                <FormattedMessage id=\"Unlock Viewport\" />\n                <StyledDisableableSwitch\n                  checked={!mapState?.isViewportSynced}\n                  id=\"unlock-viewport-toggle\"\n                  onChange={onUnlockViewportChange}\n                />\n              </StyledSBCenterFlexbox>\n              <StyledSBCenterFlexbox>\n                <StyledDisableableText disabled={mapState?.isViewportSynced}>\n                  <FormattedMessage id=\"Sync Zoom\" />\n                </StyledDisableableText>\n                <StyledDisableableSwitch\n                  checked={mapState?.isZoomLocked}\n                  id=\"sync-zoom-toggle\"\n                  onChange={onSyncZoomChange}\n                  disabled={mapState?.isViewportSynced}\n                />\n              </StyledSBCenterFlexbox>\n            </StyledMapControlPanelHeaderSplitViewportsTools>\n          ) : null}\n\n          <StyledMapControlPanelHeader>\n            {\n              (isExport && logoComponent ? (\n                logoComponent\n              ) : header ? (\n                <span style={{verticalAlign: 'middle'}}>\n                  <FormattedMessage id={header} />\n                </span>\n              ) : null) as React.ReactNode\n            }\n            {isExport ? null : (\n              <>\n                {pinnable && (\n                  <StyledIcon className=\"pin-map-control-item\" onClick={onPinClick}>\n                    <Pin height=\"16px\" />\n                  </StyledIcon>\n                )}\n                {disableClose ? null : (\n                  <StyledIcon className=\"close-map-control-item\" onClick={onClick}>\n                    <Close height=\"16px\" />\n                  </StyledIcon>\n                )}\n              </>\n            )}\n          </StyledMapControlPanelHeader>\n          <StyledMapControlPanelContent isExport={isExport}>\n            {children}\n          </StyledMapControlPanelContent>\n        </StyledMapControlPanel>\n      );\n    }\n  );\n\n  MapControlPanel.displayName = 'MapControlPanel';\n\n  return MapControlPanel;\n}\n\nexport default MapControlPanelFactory;\n"
  },
  {
    "path": "src/components/src/map/map-control-toolbar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\nimport VerticalToolbar from '../common/vertical-toolbar';\nimport {BaseComponentProps} from '../types';\n\nfunction MapControlToolbar() {\n  const StyledToolbar = styled(VerticalToolbar)<BaseComponentProps>`\n    position: absolute;\n    right: 32px;\n    transform: translateX(calc(-50% + 45px));\n\n    .toolbar-item {\n      width: 120px;\n      padding: 13px 16px;\n      flex-direction: row;\n      justify-content: flex-start;\n\n      .toolbar-item__svg-container {\n        width: 16px;\n        height: 16px;\n        margin-right: 10px;\n      }\n\n      .toolbar-item__title {\n        margin-left: auto;\n        margin-right: auto;\n      }\n    }\n  `;\n\n  return StyledToolbar;\n}\n\nexport default MapControlToolbar;\n"
  },
  {
    "path": "src/components/src/map/map-control-tooltip.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ReactElement, JSXElementConstructor} from 'react';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport TippyTooltip from '../common/tippy-tooltip';\n\nexport type MapControlTooltipProps = {\n  id: string;\n  message: string;\n  children?: ReactElement<any, string | JSXElementConstructor<any>>;\n};\n\nfunction MapControlTooltipFactory() {\n  const MapControlTooltip: React.FC<MapControlTooltipProps> = React.memo(\n    ({id, message, children}) => (\n      <TippyTooltip\n        placement=\"left\"\n        render={() => (\n          <div id={id}>\n            <FormattedMessage id={message} />\n          </div>\n        )}\n      >\n        {children}\n      </TippyTooltip>\n    )\n  );\n\n  MapControlTooltip.displayName = 'MapControlTooltip';\n\n  return MapControlTooltip;\n}\n\nexport default MapControlTooltipFactory;\n"
  },
  {
    "path": "src/components/src/map/map-control.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport KeplerGlLogo from '../common/logo';\n\n// factories\nimport SplitMapButtonFactory from './split-map-button';\nimport Toggle3dButtonFactory from './toggle-3d-button';\nimport LayerSelectorPanelFactory from './layer-selector-panel';\nimport MapLegendPanelFactory from './map-legend-panel';\nimport MapDrawPanelFactory from './map-draw-panel';\nimport LocalePanelFactory from './locale-panel';\nimport {Layer} from '@kepler.gl/layers';\nimport {Editor, LayerVisConfig, MapControls, MapState} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\nimport {MapStateActions, UIStateActions} from '@kepler.gl/actions';\n\ninterface StyledMapControlProps {\n  $top?: number;\n}\n\nconst StyledMapControl = styled.div<StyledMapControlProps>`\n  right: 0;\n  padding: ${props => props.theme.mapControl.padding}px;\n  z-index: 10;\n  margin-top: ${props => props.$top || 0}px;\n  position: absolute;\n  display: grid;\n  row-gap: 8px;\n  justify-items: end;\n  pointer-events: none; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\nconst LegendLogo = <KeplerGlLogo version={false} appName=\"kepler.gl\" />;\n\nexport type MapControlProps = {\n  datasets: Datasets;\n  dragRotate: boolean;\n  isSplit: boolean;\n  primary: boolean;\n  layers: Layer[];\n  layersToRender: {[key: string]: boolean};\n  mapIndex: number;\n  mapControls: MapControls;\n  onTogglePerspective: () => void;\n  onToggleSplitMap: typeof MapStateActions.toggleSplitMap;\n  onToggleSplitMapViewport: ({\n    isViewportSynced,\n    isZoomLocked\n  }: {\n    isViewportSynced: boolean;\n    isZoomLocked: boolean;\n  }) => void;\n  onMapToggleLayer: (layerId: string) => void;\n  onToggleMapControl: (control: string) => void;\n  onSetEditorMode: (mode: string) => void;\n  onToggleEditorVisibility: () => void;\n  onLayerVisConfigChange: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;\n  top: number;\n  onSetLocale: typeof UIStateActions.setLocale;\n  availableLocales: string[];\n  locale: string;\n  logoComponent?: React.FC | React.ReactNode;\n  isExport?: boolean;\n\n  setMapControlSettings: typeof UIStateActions.setMapControlSettings;\n  activeSidePanel: string | null;\n\n  // optional\n  mapState?: MapState;\n  readOnly?: boolean;\n  scale?: number;\n  mapLayers?: {[key: string]: boolean};\n  editor: Editor;\n  actionComponents?: React.ComponentType<any>[];\n  mapHeight?: number;\n};\n\nMapControlFactory.deps = [\n  SplitMapButtonFactory,\n  Toggle3dButtonFactory,\n  LayerSelectorPanelFactory,\n  MapLegendPanelFactory,\n  MapDrawPanelFactory,\n  LocalePanelFactory\n];\n\nfunction MapControlFactory(\n  SplitMapButton: ReturnType<typeof SplitMapButtonFactory>,\n  Toggle3dButton: ReturnType<typeof Toggle3dButtonFactory>,\n  LayerSelectorPanel: ReturnType<typeof LayerSelectorPanelFactory>,\n  MapLegendPanel: ReturnType<typeof MapLegendPanelFactory>,\n  MapDrawPanel: ReturnType<typeof MapDrawPanelFactory>,\n  LocalePanel: ReturnType<typeof LocalePanelFactory>\n) {\n  const DEFAULT_ACTIONS = [\n    SplitMapButton,\n    LayerSelectorPanel,\n    Toggle3dButton,\n    MapDrawPanel,\n    LocalePanel,\n    MapLegendPanel\n  ];\n\n  const MapControl: React.FC<MapControlProps> & {\n    defaultActionComponents: MapControlProps['actionComponents'];\n  } = ({\n    actionComponents = DEFAULT_ACTIONS,\n    isSplit = false,\n    top = 0,\n    mapIndex = 0,\n    logoComponent = LegendLogo,\n    ...restProps\n  }) => {\n    const actionComponentProps = {\n      isSplit,\n      mapIndex,\n      logoComponent,\n      ...restProps\n    };\n    return (\n      <StyledMapControl className=\"map-control\" $top={top}>\n        {actionComponents.map((ActionComponent, index) => (\n          <ActionComponent key={index} className=\"map-control-action\" {...actionComponentProps} />\n        ))}\n      </StyledMapControl>\n    );\n  };\n\n  MapControl.defaultActionComponents = DEFAULT_ACTIONS;\n\n  MapControl.displayName = 'MapControl';\n\n  return MapControl;\n}\n\nexport default MapControlFactory;\n"
  },
  {
    "path": "src/components/src/map/map-draw-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport classnames from 'classnames';\n\nimport {EDITOR_MODES} from '@kepler.gl/constants';\nimport {CursorClick, DrawPolygon, EyeSeen, EyeUnseen, Polygon, Rectangle} from '../common/icons';\nimport {MapControlButton} from '../common/styled-components';\nimport ToolbarItem from '../common/toolbar-item';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport MapControlToolbarFactory from './map-control-toolbar';\nimport {Editor, MapControls} from '@kepler.gl/types';\nimport {BaseProps} from '../common/icons';\n\nMapDrawPanelFactory.deps = [MapControlTooltipFactory, MapControlToolbarFactory];\n\nexport type MapDrawPanelProps = {\n  editor: Editor;\n  mapControls: MapControls;\n  onToggleMapControl: (control: string) => void;\n  onSetEditorMode: (mode: string) => void;\n  onToggleEditorVisibility: () => void;\n  actionIcons: {[id: string]: React.ComponentType<Partial<BaseProps>>};\n};\n\nfunction MapDrawPanelFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>,\n  MapControlToolbar: ReturnType<typeof MapControlToolbarFactory>\n) {\n  const defaultActionIcons = {\n    visible: EyeSeen,\n    hidden: EyeUnseen,\n    polygon: DrawPolygon,\n    cursor: CursorClick,\n    innerPolygon: Polygon,\n    rectangle: Rectangle\n  };\n\n  const MapDrawPanel: React.FC<MapDrawPanelProps> = React.memo(\n    ({\n      editor,\n      mapControls,\n      onToggleMapControl,\n      onSetEditorMode,\n      actionIcons = defaultActionIcons\n    }) => {\n      const isActive = mapControls?.mapDraw?.active;\n      const onToggleMenuPanel = useCallback(\n        () => onToggleMapControl('mapDraw'),\n        [onToggleMapControl]\n      );\n      if (!mapControls?.mapDraw?.show) {\n        return null;\n      }\n      return (\n        <div className=\"map-draw-controls\" style={{position: 'relative'}}>\n          {isActive ? (\n            <MapControlToolbar show={isActive}>\n              <ToolbarItem\n                className=\"edit-feature\"\n                onClick={() => onSetEditorMode(EDITOR_MODES.EDIT)}\n                label=\"toolbar.select\"\n                icon={actionIcons.cursor}\n                active={editor.mode === EDITOR_MODES.EDIT}\n              />\n              <ToolbarItem\n                className=\"draw-feature\"\n                onClick={() => onSetEditorMode(EDITOR_MODES.DRAW_POLYGON)}\n                label=\"toolbar.polygon\"\n                icon={actionIcons.innerPolygon}\n                active={editor.mode === EDITOR_MODES.DRAW_POLYGON}\n              />\n              <ToolbarItem\n                className=\"draw-rectangle\"\n                onClick={() => onSetEditorMode(EDITOR_MODES.DRAW_RECTANGLE)}\n                label=\"toolbar.rectangle\"\n                icon={actionIcons.rectangle}\n                active={editor.mode === EDITOR_MODES.DRAW_RECTANGLE}\n              />\n            </MapControlToolbar>\n          ) : null}\n          <MapControlTooltip id=\"map-draw\" message=\"tooltip.DrawOnMap\">\n            <MapControlButton\n              className={classnames('map-control-button', 'map-draw', {isActive})}\n              onClick={e => {\n                e.preventDefault();\n                onToggleMenuPanel();\n              }}\n              active={isActive}\n            >\n              <actionIcons.polygon height=\"18px\" />\n            </MapControlButton>\n          </MapControlTooltip>\n        </div>\n      );\n    }\n  );\n\n  MapDrawPanel.displayName = 'MapDrawPanel';\n  return MapDrawPanel;\n}\n\nexport default MapDrawPanelFactory;\n"
  },
  {
    "path": "src/components/src/map/map-legend-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport {createPortal} from 'react-dom';\nimport React, {\n  ComponentType,\n  FC,\n  forwardRef,\n  useCallback,\n  useContext,\n  useRef,\n  PropsWithChildren\n} from 'react';\nimport styled, {withTheme} from 'styled-components';\n\nimport {DndContext, useDraggable} from '@dnd-kit/core';\nimport {CSS} from '@dnd-kit/utilities';\nimport {useMergeRefs} from '@floating-ui/react';\n\nimport {ActionHandler, setMapControlSettings, toggleSplitMapViewport} from '@kepler.gl/actions';\nimport {Layer} from '@kepler.gl/layers';\nimport {breakPointValues} from '@kepler.gl/styles';\nimport {LayerVisConfig, MapControlMapLegend, MapControls, MapState} from '@kepler.gl/types';\nimport {hasPortableWidth} from '@kepler.gl/utils';\nimport {MapLegendControlSettings} from '@kepler.gl/types';\n\nimport {Legend, DraggableDots, HorizontalResizeHandle} from '../common/icons';\nimport {MapControlButton} from '../common/styled-components';\nimport {RootContext} from '../context';\nimport useLegendPosition from '../hooks/use-legend-position';\nimport MapControlPanelFactory from './map-control-panel';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport MapLegendFactory from './map-legend';\nimport {restrictToWindowEdges} from '@dnd-kit/modifiers';\n\nconst DRAG_RESIZE_ID = 'map-legend-resize';\nconst DRAG_MOVE_ID = 'map-legend-move';\n\nconst StyledDraggableLegendContent = styled.div<{\n  contentHeight?: number;\n  maxContentHeight?: number;\n}>`\n  position: absolute;\n  outline: none;\n  transition: border-color 0.2s ease-in-out;\n  border: 1px solid transparent;\n  .legend-input-block {\n    display: none;\n  }\n  &.is-dragging .legend-input-block {\n    display: block;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n  }\n  &:hover,\n  &.is-dragging {\n    .legend-move-handle {\n      opacity: 1;\n      pointer-events: auto;\n    }\n    .legend-resize-handle {\n      opacity: 1;\n      pointer-events: auto;\n    }\n    border-color: ${props => props.theme.activeColor};\n  }\n  .map-control__panel-content {\n    max-height: ${props =>\n      props.maxContentHeight ? `${props.maxContentHeight}px` : 'calc(100vh - 100px)'};\n    ${props => (props.contentHeight ? `height: ${props.contentHeight}px;` : '')};\n  }\n  border-radius: 4px;\n  z-index: 2;\n  .map-control-panel {\n    margin-bottom: 0 !important;\n  }\n`;\n\nconst StyledMoveHandle = styled.div`\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity 0.2s ease-in-out;\n  position: absolute;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  color: white;\n  z-index: 2;\n  top: 0;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 48px;\n  height: 16px;\n  border-radius: 4px;\n  cursor: move;\n  background-color: ${props => props.theme.activeColor};\n`;\n\nconst StyledResizeHandle = styled.div`\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity 0.2s ease-in-out;\n  position: absolute;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  color: #f7f8fa;\n  z-index: 2;\n  bottom: 0;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 48px;\n  height: 16px;\n  border-radius: 4px;\n  cursor: ns-resize;\n`;\n\nconst StyledFixedLegendContent = styled.div<{contentHeight?: number}>`\n  .map-control__panel-content {\n    max-height: calc(100vh - 100px);\n    ${props => (props.contentHeight ? `height: ${props.contentHeight}px;` : '')};\n  }\n\n  /* Hide scrollbars in export to avoid OS default styling differences */\n  .styled-color-legend {\n    -ms-overflow-style: none; /* IE and old Edge */\n    scrollbar-width: none; /* Firefox */\n  }\n  .styled-color-legend::-webkit-scrollbar {\n    width: 0 !important; /* Chrome, Safari, new Edge */\n    height: 0 !important;\n  }\n`;\n\nexport type MapLegendPanelFactoryDeps = [\n  typeof MapControlTooltipFactory,\n  typeof MapControlPanelFactory,\n  typeof MapLegendFactory\n];\n\ntype DraggableLegendContentProps = {\n  contentHeight?: number;\n  maxContentHeight?: number;\n  positionStyles: Record<string, unknown>;\n  children: React.ReactNode;\n};\n\nconst DraggableLegendContent = forwardRef((props: DraggableLegendContentProps, ref) => {\n  const {positionStyles, children, contentHeight, maxContentHeight} = props;\n  const draggableMove = useDraggable({id: DRAG_MOVE_ID});\n  const draggableResize = useDraggable({id: DRAG_RESIZE_ID});\n  const refs = useMergeRefs([draggableMove.setNodeRef, ref]);\n  const isDragging = draggableMove.isDragging || draggableResize.isDragging;\n  return (\n    <StyledDraggableLegendContent\n      ref={refs}\n      className={classnames('draggable-legend', {'is-dragging': isDragging})}\n      style={{...positionStyles, transform: CSS.Translate.toString(draggableMove.transform)}}\n      contentHeight={contentHeight}\n      maxContentHeight={maxContentHeight}\n      {...draggableMove.attributes}\n    >\n      {children}\n      {isDragging ? <div className=\"legend-input-block\" /> : null}\n      <StyledMoveHandle className=\"legend-move-handle\" {...draggableMove.listeners}>\n        <DraggableDots height=\"16px\" />\n      </StyledMoveHandle>\n      <StyledResizeHandle\n        className=\"legend-resize-handle\"\n        ref={draggableResize.setNodeRef}\n        {...draggableResize.listeners}\n      >\n        <HorizontalResizeHandle height=\"16px\" />\n      </StyledResizeHandle>\n    </StyledDraggableLegendContent>\n  );\n});\n\ntype DraggableLegendProps = PropsWithChildren<{\n  isSidePanelShown: boolean;\n  mapControls: MapControls;\n  setMapControlSettings: typeof setMapControlSettings;\n  mapState?: MapState;\n}>;\n\nconst DraggableLegend = withTheme(\n  ({\n    isSidePanelShown,\n    children,\n    mapControls,\n    setMapControlSettings,\n    mapState,\n    theme\n  }: DraggableLegendProps & {theme: any}) => {\n    const settings = mapControls?.mapLegend?.settings;\n\n    const legendContentRef = useRef<HTMLElement>(null);\n    const onChangeSettings = useCallback(\n      newSettings => setMapControlSettings('mapLegend', newSettings),\n      [setMapControlSettings]\n    );\n    const {positionStyles, updatePosition, startResize, resize, contentHeight, maxContentHeight} =\n      useLegendPosition({\n        legendContentRef,\n        isSidePanelShown,\n        theme,\n        settings,\n        onChangeSettings,\n        mapHeight: mapState?.height,\n        mapWidth: mapState?.width\n      });\n\n    const handleDragStart = useCallback(\n      event => {\n        switch (event.active.id) {\n          case DRAG_RESIZE_ID:\n            startResize();\n            break;\n          default:\n            updatePosition();\n        }\n      },\n      [updatePosition, startResize]\n    );\n    const handleDragEnd = useCallback(updatePosition, [updatePosition]);\n    const handleDragMove = useCallback(\n      event => {\n        switch (event.active.id) {\n          case DRAG_RESIZE_ID:\n            resize(event.delta.y);\n            break;\n        }\n      },\n      [resize]\n    );\n\n    return (\n      <DndContext\n        onDragStart={handleDragStart}\n        onDragMove={handleDragMove}\n        onDragEnd={handleDragEnd}\n        modifiers={[restrictToWindowEdges]}\n      >\n        <DraggableLegendContent\n          ref={legendContentRef}\n          positionStyles={positionStyles}\n          contentHeight={contentHeight}\n          maxContentHeight={maxContentHeight}\n        >\n          {children}\n        </DraggableLegendContent>\n      </DndContext>\n    );\n  }\n) as FC<DraggableLegendProps>;\n\ntype ImageExportLegendProps = {\n  settings?: MapLegendControlSettings;\n  isSidePanelShown: boolean;\n  children: React.ReactNode;\n};\n\nconst ImageExportLegend = withTheme(({settings, isSidePanelShown, theme, children}) => {\n  const containerRef: React.MutableRefObject<HTMLDivElement | null> = useRef(null);\n  const legendContentRef: React.MutableRefObject<HTMLDivElement | null> = useRef(null);\n\n  const {positionStyles, contentHeight} = useLegendPosition({\n    legendContentRef,\n    isSidePanelShown,\n    theme,\n    settings,\n    onChangeSettings: () => {\n      // do nothing by default\n    }\n  });\n\n  const portalRoot = containerRef.current\n    ?.closest('.export-map-instance')\n    ?.querySelector('#default-deckgl-overlay-wrapper');\n\n  return (\n    <div ref={containerRef}>\n      {portalRoot\n        ? createPortal(\n            <div\n              className=\"fixed-legend\"\n              ref={legendContentRef}\n              style={{...positionStyles, position: 'absolute'}}\n            >\n              <StyledFixedLegendContent contentHeight={contentHeight}>\n                {children}\n              </StyledFixedLegendContent>\n            </div>,\n            portalRoot\n          )\n        : null}\n    </div>\n  );\n}) as React.FC<ImageExportLegendProps>;\n\nMapLegendPanelFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory, MapLegendFactory];\n\ninterface MapLegendPanelIcons {\n  legend: ComponentType<any>;\n}\n\nexport type MapLegendPanelProps = {\n  theme: any;\n  layers: ReadonlyArray<Layer>;\n  scale: number;\n  onToggleMapControl: (control: string) => void;\n  isExport: boolean;\n  logoComponent: Element;\n  actionIcons: MapLegendPanelIcons;\n  mapControls: MapControls;\n  mapState?: MapState;\n  onLayerVisConfigChange?: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;\n  onToggleSplitMapViewport?: ActionHandler<typeof toggleSplitMapViewport>;\n  isViewportUnsyncAllowed?: boolean;\n  onClickControlBtn?: (e?: MouseEvent) => void;\n  className: string;\n  settings: MapLegendControlSettings;\n  isSidePanelShown: boolean;\n  activeSidePanel: string | null;\n  setMapControlSettings: any;\n};\n\ntype MapLegendPanelComponents = {\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>;\n  MapControlPanel: ReturnType<typeof MapControlPanelFactory>;\n  MapLegend: ReturnType<typeof MapLegendFactory>;\n};\n\ntype MapLegendPanelComponentType = React.FC<MapLegendPanelProps>;\n\nconst defaultActionIcons = {\n  legend: props => <Legend {...props} height=\"18px\" />\n};\n\nconst MapLegendPanelComponent = ({\n  layers,\n  mapControls,\n  scale,\n  onToggleMapControl,\n  isExport,\n  logoComponent,\n  actionIcons = defaultActionIcons,\n  mapState,\n  onLayerVisConfigChange,\n  onToggleSplitMapViewport,\n  onClickControlBtn,\n  activeSidePanel,\n  setMapControlSettings,\n  isViewportUnsyncAllowed = true,\n  className,\n  MapControlTooltip,\n  MapControlPanel,\n  MapLegend\n}: MapLegendPanelProps & MapLegendPanelComponents) => {\n  const isSidePanelShown = Boolean(activeSidePanel);\n  const settings = mapControls?.mapLegend?.settings;\n\n  const mapLegend = mapControls?.mapLegend || ({} as MapControlMapLegend);\n  const {active, disableEdit} = mapLegend || {};\n  const rootContext = useContext(RootContext);\n\n  const onClick = useCallback(() => {\n    onClickControlBtn?.();\n    if (mapControls?.mapDraw?.active) {\n      onToggleMapControl('mapDraw');\n    }\n    onToggleMapControl('mapLegend');\n  }, [onClickControlBtn, onToggleMapControl, mapControls]);\n  const onCloseClick = useCallback(\n    e => {\n      e.preventDefault();\n      onToggleMapControl('mapLegend');\n    },\n    [onToggleMapControl]\n  );\n\n  if (!mapLegend.show) {\n    return null;\n  }\n\n  const legendPanel = active ? (\n    <MapControlPanel\n      scale={scale}\n      header=\"header.layerLegend\"\n      {...{onClick: onCloseClick, pinnable: false, disableClose: false}}\n      isExport={isExport}\n      logoComponent={logoComponent}\n      mapState={mapState}\n      onToggleSplitMapViewport={onToggleSplitMapViewport}\n      isViewportUnsyncAllowed={isViewportUnsyncAllowed}\n      className={className}\n    >\n      <MapLegend\n        layers={layers}\n        mapState={mapState}\n        disableEdit={disableEdit}\n        isExport={isExport}\n        onLayerVisConfigChange={onLayerVisConfigChange}\n      />\n    </MapControlPanel>\n  ) : null;\n\n  return (\n    <>\n      {active ? (\n        hasPortableWidth(breakPointValues) ? (\n          legendPanel\n        ) : isExport ? (\n          <ImageExportLegend isSidePanelShown={isSidePanelShown} settings={settings}>\n            {legendPanel}\n          </ImageExportLegend>\n        ) : (\n          createPortal(\n            <DraggableLegend\n              isSidePanelShown={isSidePanelShown}\n              mapControls={mapControls}\n              setMapControlSettings={setMapControlSettings}\n              mapState={mapState}\n            >\n              {legendPanel}\n            </DraggableLegend>,\n            rootContext?.current ?? document.body\n          )\n        )\n      ) : null}\n      {!isExport ? (\n        <MapControlTooltip id=\"show-legend\" message=\"tooltip.showLegend\">\n          <MapControlButton className=\"map-control-button show-legend\" onClick={onClick}>\n            <actionIcons.legend height=\"22px\" />\n          </MapControlButton>\n        </MapControlTooltip>\n      ) : null}\n    </>\n  );\n};\n\nfunction MapLegendPanelFactory(\n  MapControlTooltip: ReturnType<typeof MapControlTooltipFactory>,\n  MapControlPanel: ReturnType<typeof MapControlPanelFactory>,\n  MapLegend: ReturnType<typeof MapLegendFactory>\n): MapLegendPanelComponentType {\n  return (props: MapLegendPanelProps) => (\n    <MapLegendPanelComponent\n      {...props}\n      MapControlTooltip={MapControlTooltip}\n      MapControlPanel={MapControlPanel}\n      MapLegend={MapLegend}\n    />\n  );\n}\n\nexport default MapLegendPanelFactory;\n"
  },
  {
    "path": "src/components/src/map/map-legend.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {FC, useCallback, useState, ComponentType} from 'react';\nimport styled from 'styled-components';\nimport {rgb} from 'd3-color';\nimport {format as d3Format} from 'd3-format';\nimport {useIntl} from 'react-intl';\nimport ColorLegendFactory, {LegendRowFactory} from '../common/color-legend';\nimport RadiusLegend from '../common/radius-legend';\nimport {CHANNEL_SCALES, DIMENSIONS} from '@kepler.gl/constants';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Layer, LayerBaseConfig, VisualChannel, VisualChannelDescription} from '@kepler.gl/layers';\nimport {LayerVisConfig, MapState, RGBColor} from '@kepler.gl/types';\nimport {getDistanceScales} from 'viewport-mercator-project';\nimport {ArrowDown, ArrowRight} from '../common/icons';\nimport PanelHeaderActionFactory from '../side-panel/panel-header-action';\n\ninterface StyledMapControlLegendProps {\n  width?: number;\n  last?: boolean;\n}\n\nexport const StyledMapControlLegend = styled.div<StyledMapControlLegendProps>`\n  padding: 10px ${props => props.theme.mapControl.padding}px 10px\n    ${props => props.theme.mapControl.padding}px;\n  font-size: 11px;\n  font-family: ${props => props.theme.fontFamily};\n  border-bottom-color: ${props => props.theme.panelBorderColor};\n  border-bottom-style: solid;\n  border-bottom-width: ${props => (props.last ? 0 : '1px')};\n  width: ${props => props.width}px;\n  box-sizing: border-box;\n\n  .legend--layer_name {\n    font-size: 12px;\n    padding-right: ${props => props.theme.mapControl.padding}px;\n    color: ${props => props.theme.textColor};\n    font-weight: 500;\n  }\n  .legend--layer_type {\n    color: ${props => props.theme.subtextColor};\n    font-weight: 500;\n    font-size: 11px;\n    padding-right: ${props => props.theme.mapControl.padding}px;\n  }\n\n  .legend--layer_size-title-row {\n    display: flex;\n    margin-top: 4px;\n    padding-right: ${props => props.theme.mapControl.padding}px;\n    align-items: center;\n  }\n\n  .legend--layer__title {\n  }\n\n  .legend--layer__item {\n    padding-bottom: 4px;\n  }\n  .legend--layer_by {\n    color: ${props => props.theme.subtextColor};\n    margin-top: 4px;\n  }\n\n  .legend--layer_color_field {\n    color: ${props => props.theme.textColorHl};\n    font-weight: 500;\n  }\n\n  .legend--layer_color-legend {\n    margin-top: 6px;\n  }\n`;\n\nexport const VisualChannelMetric = ({name}) => {\n  return (\n    <div className=\"legend--layer__title\">\n      <span className=\"legend--layer_color_field\">\n        <FormattedMessage id={name} />\n      </span>\n    </div>\n  );\n};\n\nexport type LayerSizeLegendProps = {\n  label: string;\n  name: string | undefined;\n};\n\nexport const LayerDefaultLegend: React.FC<LayerSizeLegendProps> = ({label, name}) =>\n  label ? (\n    <div className=\"legend--layer_size-schema\">\n      <p>\n        <span className=\"legend--layer_by\">{label ? <FormattedMessage id={label} /> : null}</span>\n        <span className=\"legend--layer_by\"> by </span>\n      </p>\n      {name && <VisualChannelMetric name={name} />}\n    </div>\n  ) : null;\n\nexport type SingleColorLegendProps = {\n  color: RGBColor;\n  label?: string;\n};\n\nSingleColorLegendFactory.deps = [LegendRowFactory];\n\nexport function SingleColorLegendFactory(LegendRow: ReturnType<typeof LegendRowFactory>) {\n  const SingleColorLegend: React.FC<SingleColorLegendProps> = ({color, label}) => (\n    <LegendRow\n      label={label ?? ''}\n      displayLabel={Boolean(label)}\n      color={Array.isArray(color) ? rgb(...color).toString() : color}\n    />\n  );\n\n  SingleColorLegend.displayName = 'SingleColorLegend';\n\n  return React.memo(SingleColorLegend);\n}\n\nexport type LayerColorLegendProps = {\n  description: VisualChannelDescription;\n  config: LayerBaseConfig;\n  colorChannel: VisualChannel;\n  onLayerVisConfigChange?: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;\n  layer: Layer;\n  disableEdit?: boolean;\n  isExport?: boolean;\n  mapState?: MapState;\n  actionIcons: MapLegendIcons;\n};\n\nLayerColorLegendFactory.deps = [\n  ColorLegendFactory,\n  SingleColorLegendFactory,\n  PanelHeaderActionFactory\n];\nexport function LayerColorLegendFactory(\n  ColorLegend: ReturnType<typeof ColorLegendFactory>,\n  SingleColorLegend: ReturnType<typeof SingleColorLegendFactory>,\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n) {\n  const LayerColorLegend: React.FC<LayerColorLegendProps> = ({\n    description,\n    config,\n    layer,\n    colorChannel,\n    disableEdit,\n    onLayerVisConfigChange,\n    isExport,\n    mapState,\n    actionIcons\n  }) => {\n    const intl = useIntl();\n    const enableColorBy = description.measure;\n    const {scale, field, domain, range, property, fixed} = colorChannel;\n    const [colorScale, colorField, colorDomain] = [scale, field, domain].map(k => config[k]);\n    const isFixed = fixed && config.visConfig[fixed];\n\n    const colorRange = config.visConfig[range];\n    const onUpdateColorLegend = useCallback(\n      colorLegends => {\n        if (onLayerVisConfigChange) {\n          onLayerVisConfigChange(layer, {\n            [range]: {\n              ...colorRange,\n              colorLegends\n            }\n          });\n        }\n      },\n      [layer, onLayerVisConfigChange, colorRange, range]\n    );\n    const [isExpanded, setIsExpanded] = useState(isExport);\n    const handleToggleExpanded = () => setIsExpanded(!isExpanded);\n    return (\n      <div className=\"legend--layer__item\">\n        <div className=\"legend--layer_color-schema\">\n          <div>\n            {enableColorBy ? (\n              <div className=\"legend--layer_size-title-row\">\n                <VisualChannelMetric name={enableColorBy} />\n                {!isExport ? (\n                  <PanelHeaderAction\n                    id=\"legend-collapse-button\"\n                    onClick={handleToggleExpanded}\n                    IconComponent={isExpanded ? actionIcons.expanded : actionIcons.collapsed}\n                  />\n                ) : null}\n              </div>\n            ) : null}\n            <div className=\"legend--layer_color-legend\">\n              {enableColorBy ? (\n                <ColorLegend\n                  layer={layer}\n                  isExpanded={isExpanded}\n                  scaleType={colorScale}\n                  displayLabel\n                  domain={colorDomain}\n                  fieldType={(colorField && colorField.type) || 'real'}\n                  range={colorRange}\n                  onUpdateColorLegend={onUpdateColorLegend}\n                  disableEdit={disableEdit || Boolean(isExport)}\n                  isFixed={isFixed}\n                  mapState={mapState}\n                  labelFormat={\n                    colorField?.displayFormat ? d3Format(colorField?.displayFormat) : null\n                  }\n                />\n              ) : (\n                <SingleColorLegend\n                  color={config.visConfig[property] || config[property] || config.color}\n                  label={intl.formatMessage({\n                    id: `mapLegend.layers.${layer.type}.singleColor.${colorChannel.key}`,\n                    defaultMessage: intl.formatMessage({\n                      id: `mapLegend.layers.default.singleColor.${colorChannel.key}`,\n                      defaultMessage: ' ' // mustn't be empty string or id will be used\n                    })\n                  })}\n                />\n              )}\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  };\n\n  LayerColorLegend.displayName = 'LayerColorLegend';\n  return React.memo(LayerColorLegend);\n}\n\nfunction getLayerRadiusScaleMetersToPixelsMultiplier(layer, mapState) {\n  // @ts-ignore this actually exist\n  const {metersPerPixel} = getDistanceScales(mapState);\n  // if no field size is defined we need to pass fixed radius = false\n  const fixedRadius = layer.config.visConfig.fixedRadius && Boolean(layer.config.sizeField);\n  return layer.getRadiusScaleByZoom(mapState, fixedRadius) / metersPerPixel[0];\n}\n\nexport type MapLegendIcons = {\n  expanded: ComponentType<any>;\n  collapsed: ComponentType<any>;\n};\n\nexport type LayerRadiusLegendProps = {\n  layer: Layer;\n  mapState?: MapState;\n  width: number;\n  isExport?: boolean;\n  visualChannel: VisualChannel;\n};\n\nexport const LayerRadiusLegend: FC<LayerRadiusLegendProps> = React.memo(\n  ({layer, width, visualChannel, mapState}) => {\n    const description = layer.getVisualChannelDescription(visualChannel.key);\n    const config = layer.config;\n\n    const enableSizeBy = description.measure;\n    const {scale, field, domain, range} = visualChannel;\n    const [sizeScale, sizeField, sizeDomain] = [scale, field, domain].map(k => config[k]);\n    let sizeRange = config.visConfig[range];\n\n    if (mapState) {\n      const radiusMultiplier = getLayerRadiusScaleMetersToPixelsMultiplier(layer, mapState);\n      sizeRange = sizeRange.map(v => v * radiusMultiplier);\n    }\n\n    return (\n      <div>\n        <div className=\"legend--layer_size-schema\">\n          <div>\n            {enableSizeBy ? <VisualChannelMetric name={enableSizeBy} /> : null}\n            <div className=\"legend--layer_size-legend\">\n              {enableSizeBy ? (\n                <RadiusLegend\n                  scaleType={sizeScale}\n                  domain={sizeDomain}\n                  fieldType={(sizeField && sizeField.type) || 'real'}\n                  range={sizeRange}\n                  width={width}\n                />\n              ) : null}\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n);\n\nconst isColorChannel = visualChannel =>\n  [CHANNEL_SCALES.color, CHANNEL_SCALES.colorAggr].includes(visualChannel.channelScaleType);\n\nexport type LayerLegendHeaderProps = {\n  layer: Layer;\n  options?: {\n    showLayerName?: boolean;\n  };\n  isExport?: boolean;\n};\n\nconst isRadiusChannel = visualChannel =>\n  [CHANNEL_SCALES.radius].includes(visualChannel.channelScaleType);\n\nexport function LayerLegendHeaderFactory() {\n  const LayerLegendHeader: React.FC<LayerLegendHeaderProps> = ({options, layer}) => {\n    return options?.showLayerName !== false ? (\n      <div className=\"legend--layer_name\">{layer.config.label}</div>\n    ) : null;\n  };\n  return LayerLegendHeader;\n}\n\nconst defaultActionIcons = {\n  expanded: ArrowDown,\n  collapsed: ArrowRight\n};\n\nexport type LayerLegendContentProps = {\n  layer: Layer;\n  containerW: number;\n  mapState?: MapState;\n  disableEdit?: boolean;\n  isExport?: boolean;\n  onLayerVisConfigChange?: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;\n  actionIcons: MapLegendIcons;\n};\n\nLayerLegendContentFactory.deps = [LayerColorLegendFactory];\n\nexport function LayerLegendContentFactory(\n  LayerColorLegend: ReturnType<typeof LayerColorLegendFactory>\n) {\n  const LayerLegendContent: React.FC<LayerLegendContentProps> = ({\n    layer,\n    containerW,\n    mapState,\n    disableEdit,\n    isExport,\n    onLayerVisConfigChange,\n    actionIcons\n  }) => {\n    const visualChannels = layer.getLegendVisualChannels();\n    const channelKeys = Object.values(visualChannels);\n    const colorChannels = channelKeys.filter(isColorChannel) as VisualChannel[];\n    const nonColorChannels = channelKeys.filter(vc => !isColorChannel(vc));\n    const width = containerW - 2 * DIMENSIONS.mapControl.padding;\n\n    // render color by chanel only\n    let colorChannelToRender = colorChannels.filter(\n      cc =>\n        (!cc.condition || cc.condition(layer.config)) &&\n        layer.getVisualChannelDescription(cc.key)?.measure\n    );\n    // if no color by chanel, render rest\n    if (!colorChannelToRender.length) {\n      colorChannelToRender = colorChannels.filter(\n        cc => !cc.condition || cc.condition(layer.config)\n      );\n    }\n    return (\n      <>\n        {colorChannelToRender.map(colorChannel => (\n          <LayerColorLegend\n            key={colorChannel.key}\n            colorChannel={colorChannel}\n            config={layer.config}\n            description={layer.getVisualChannelDescription(colorChannel.key)}\n            layer={layer}\n            isExport={isExport}\n            disableEdit={disableEdit}\n            mapState={mapState}\n            onLayerVisConfigChange={onLayerVisConfigChange}\n            actionIcons={actionIcons}\n          />\n        ))}\n        {nonColorChannels.map((visualChannel: VisualChannel) => {\n          const matchCondition = !visualChannel.condition || visualChannel.condition(layer.config);\n          const enabled = layer.config[visualChannel.field] || visualChannel.defaultMeasure;\n\n          if (matchCondition && enabled) {\n            const description = layer.getVisualChannelDescription(visualChannel.key);\n            if (isRadiusChannel(visualChannel)) {\n              return (\n                <LayerRadiusLegend\n                  key={visualChannel.key}\n                  layer={layer}\n                  mapState={mapState}\n                  width={width}\n                  visualChannel={visualChannel}\n                />\n              );\n            }\n            return (\n              <LayerDefaultLegend\n                key={visualChannel.key}\n                label={description.label}\n                name={description.measure}\n              />\n            );\n          }\n          return null;\n        })}\n      </>\n    );\n  };\n\n  return LayerLegendContent;\n}\n\nexport type MapLegendProps = {\n  layers?: ReadonlyArray<Layer>;\n  width?: number;\n  mapState?: MapState;\n  options?: {\n    showLayerName?: boolean;\n  };\n  disableEdit?: boolean;\n  isExport?: boolean;\n  onLayerVisConfigChange?: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;\n  actionIcons?: MapLegendIcons;\n};\n\nMapLegendFactory.deps = [LayerLegendHeaderFactory, LayerLegendContentFactory];\n\nfunction MapLegendFactory(\n  LayerLegendHeader: ReturnType<typeof LayerLegendHeaderFactory>,\n  LayerLegendContent: ReturnType<typeof LayerLegendContentFactory>\n) {\n  const MapLegend: React.FC<MapLegendProps> = ({\n    layers = [],\n    width,\n    mapState,\n    options,\n    disableEdit,\n    isExport,\n    onLayerVisConfigChange,\n    actionIcons = defaultActionIcons\n  }) => (\n    <div className=\"map-legend\">\n      {layers.map((layer, index) => {\n        if (!layer.isValidToSave() || layer.config.hidden) {\n          return null;\n        }\n        const containerW = width || DIMENSIONS.mapControl.width;\n\n        return (\n          <StyledMapControlLegend\n            className=\"legend--layer\"\n            last={index === layers.length - 1}\n            key={index}\n            width={containerW}\n          >\n            <LayerLegendHeader isExport={isExport} options={options} layer={layer} />\n            <LayerLegendContent\n              containerW={containerW}\n              layer={layer}\n              mapState={mapState}\n              disableEdit={disableEdit}\n              isExport={isExport}\n              onLayerVisConfigChange={onLayerVisConfigChange}\n              actionIcons={actionIcons}\n            />\n          </StyledMapControlLegend>\n        );\n      })}\n    </div>\n  );\n\n  MapLegend.displayName = 'MapLegend';\n\n  return MapLegend;\n}\n\nexport default MapLegendFactory;\n"
  },
  {
    "path": "src/components/src/map/map-popover-content.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {LayerHoverProp} from '@kepler.gl/reducers';\nimport LayerHoverInfoFactory from './layer-hover-info';\nimport CoordinateInfoFactory from './coordinate-info';\n\nMapPopoverContentFactory.deps = [LayerHoverInfoFactory, CoordinateInfoFactory];\n\ntype MapPopoverContentProps = {\n  coordinate: [number, number] | boolean;\n  layerHoverProp: LayerHoverProp | null;\n  zoom: number;\n};\n\ntype IntlProps = {\n  intl: IntlShape;\n};\n\nexport default function MapPopoverContentFactory(\n  LayerHoverInfo: ReturnType<typeof LayerHoverInfoFactory>,\n  CoordinateInfo: ReturnType<typeof CoordinateInfoFactory>\n) {\n  const MapPopoverContent: React.FC<MapPopoverContentProps & IntlProps> = ({\n    coordinate,\n    layerHoverProp,\n    zoom\n  }) => {\n    return (\n      <>\n        {Array.isArray(coordinate) && <CoordinateInfo coordinate={coordinate} zoom={zoom} />}\n        {layerHoverProp && <LayerHoverInfo {...layerHoverProp} />}\n      </>\n    );\n  };\n  return injectIntl(MapPopoverContent);\n}\n"
  },
  {
    "path": "src/components/src/map/map-popover.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, useCallback, useContext} from 'react';\nimport styled from 'styled-components';\nimport MapPopoverContentFactory from './map-popover-content';\nimport {Pin, ArrowLeft, ArrowRight, CursorPoint} from '../common/icons';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {RootContext} from '../context';\nimport {parseGeoJsonRawFeature} from '@kepler.gl/layers';\nimport {generateHashId, idToPolygonGeo} from '@kepler.gl/common-utils';\nimport {LAYER_TYPES} from '@kepler.gl/constants';\nimport {LayerHoverProp, getLayerHoverPropValue} from '@kepler.gl/reducers';\nimport {Feature, FeatureSelectionContext} from '@kepler.gl/types';\nimport {\n  FloatingPortal,\n  flip,\n  offset,\n  useClientPoint,\n  useFloating,\n  useInteractions\n} from '@floating-ui/react';\n\nconst SELECTABLE_LAYERS: string[] = [LAYER_TYPES.hexagonId, LAYER_TYPES.geojson];\nconst MAX_WIDTH = 500;\nconst MAX_HEIGHT = 600;\n\nconst StyledMapPopover = styled.div`\n  display: flex;\n  flex-direction: column;\n\n  max-width: ${MAX_WIDTH}px;\n  max-height: ${MAX_HEIGHT}px;\n  padding: 14px;\n  & > * + * {\n    margin-top: 6px;\n  }\n  ${props => props.theme.scrollBar};\n  font-family: ${props => props.theme.fontFamily};\n  font-size: 11px;\n  font-weight: 500;\n  background-color: ${props => props.theme.panelBackground};\n  color: ${props => props.theme.textColor};\n  z-index: 98; /* should be below 99 which is side pane */\n  overflow-x: auto;\n  box-shadow: ${props => props.theme.panelBoxShadow};\n\n  &:hover {\n    background-color: ${props => `${props.theme.panelBackground}`};\n  }\n\n  .primary-label {\n    color: ${props => props.theme.notificationColors.success};\n    font-size: 10px;\n  }\n\n  .map-popover__layer-info,\n  .coordingate-hover-info {\n    & > * + * {\n      margin-top: 7px;\n    }\n  }\n\n  table {\n    width: auto;\n    display: grid;\n    border-collapse: collapse;\n    row-gap: 5px;\n    column-gap: 5px;\n  }\n\n  .coordingate-hover-info > table {\n    grid-template-columns: auto auto auto;\n  }\n  .map-popover__layer-info > table {\n    grid-template-columns: auto auto;\n  }\n\n  tbody,\n  tr {\n    display: contents;\n  }\n\n  td {\n    border-color: transparent;\n    color: ${props => props.theme.textColor};\n  }\n\n  td.row__value {\n    text-align: right;\n    font-weight: 500;\n    color: ${props => props.theme.textColorHl};\n  }\n`;\n\nconst PinnedButtons = styled.div`\n  display: flex;\n  align-self: center;\n  align-items: center;\n  justify-items: center;\n  & > * + * {\n    margin-left: 10px;\n  }\n`;\n\nconst PopoverContent = styled.div`\n  display: flex;\n  flex-direction: column;\n  & > * + * {\n    margin-top: 12px;\n  }\n`;\n\nconst StyledIcon = styled.div`\n  color: ${props => props.theme.activeColor};\n\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.linkBtnColor};\n  }\n`;\n\nconst StyledSelectGeometry = styled.div`\n  display: flex;\n  align-items: center;\n  color: ${props => props.theme.textColorHl};\n  svg {\n    margin-right: 6px;\n  }\n\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.linkBtnColor};\n  }\n`;\n\nMapPopoverFactory.deps = [MapPopoverContentFactory];\n\nexport function getSelectedFeature(layerHoverProp: LayerHoverProp | null): Feature | null {\n  const layer = layerHoverProp?.layer;\n  let fieldIdx;\n  let selectedFeature;\n  switch (layer?.type) {\n    case LAYER_TYPES.hexagonId:\n      fieldIdx = layer.config?.columns?.hex_id?.fieldIdx;\n      selectedFeature = idToPolygonGeo(\n        {id: getLayerHoverPropValue(layerHoverProp?.data, fieldIdx)},\n        {isClosed: true}\n      );\n      break;\n    case LAYER_TYPES.geojson:\n      fieldIdx = layer.config?.columns?.geojson?.fieldIdx;\n      selectedFeature = parseGeoJsonRawFeature(\n        getLayerHoverPropValue(layerHoverProp?.data, fieldIdx)\n      );\n      break;\n    default:\n      break;\n  }\n\n  if (selectedFeature) {\n    return {\n      ...selectedFeature,\n      // unique id should be assigned to features in the editor\n      id: generateHashId(8)\n    };\n  } else {\n    return null;\n  }\n}\n\nexport type MapPopoverProps = {\n  x: number;\n  y: number;\n  frozen?: boolean;\n  coordinate: [number, number] | boolean;\n  layerHoverProp: LayerHoverProp | null;\n  isBase?: boolean;\n  zoom: number;\n  container?: HTMLElement | null;\n  onClose: () => void;\n  onSetFeatures: (features: Feature[]) => any;\n  setSelectedFeature: (feature: Feature | null, clickContext?: FeatureSelectionContext) => any;\n  featureCollection?: {\n    type: string;\n    features: Feature[];\n  };\n};\n\ntype IntlProps = {\n  intl: IntlShape;\n};\n\nexport default function MapPopoverFactory(\n  MapPopoverContent: ReturnType<typeof MapPopoverContentFactory>\n) {\n  const MapPopover: React.FC<MapPopoverProps & IntlProps> = ({\n    x,\n    y,\n    frozen,\n    coordinate,\n    layerHoverProp,\n    isBase,\n    zoom,\n    container,\n    onClose,\n    onSetFeatures,\n    setSelectedFeature,\n    featureCollection\n  }) => {\n    const [horizontalPlacement, setHorizontalPlacement] = useState('start');\n    const moveLeft = () => setHorizontalPlacement('end');\n    const moveRight = () => setHorizontalPlacement('start');\n    const rootContext = useContext(RootContext);\n    const {refs, context, floatingStyles} = useFloating({\n      placement: `${horizontalPlacement == 'end' ? 'left' : 'right'}-start`,\n      middleware: [offset({mainAxis: 20, alignmentAxis: 20}), flip()]\n    });\n    const onSetSelectedFeature = useCallback(() => {\n      const clickContext = {\n        mapIndex: 0,\n        rightClick: true,\n        position: {x, y}\n      };\n      const selectedFeature = getSelectedFeature(layerHoverProp);\n      if (selectedFeature) {\n        setSelectedFeature(selectedFeature, clickContext);\n        const updatedFeatures = featureCollection\n          ? [...featureCollection.features, selectedFeature]\n          : [selectedFeature];\n        onSetFeatures(updatedFeatures);\n      }\n      onClose();\n    }, [onClose, onSetFeatures, x, y, setSelectedFeature, layerHoverProp, featureCollection]);\n\n    const containerBounds = container?.getBoundingClientRect();\n    const clientPoint = useClientPoint(context, {\n      x: (containerBounds?.left || 0) + x,\n      y: (containerBounds?.top || 0) + y\n    });\n    const {getFloatingProps} = useInteractions([clientPoint]);\n\n    return (\n      <FloatingPortal root={rootContext?.current}>\n        <StyledMapPopover\n          className=\"map-popover\"\n          ref={refs.setFloating}\n          style={floatingStyles}\n          {...getFloatingProps()}\n        >\n          {frozen ? (\n            <PinnedButtons>\n              {horizontalPlacement === 'start' && (\n                <StyledIcon className=\"popover-arrow-left\" onClick={moveLeft}>\n                  <ArrowLeft />\n                </StyledIcon>\n              )}\n              <StyledIcon className=\"popover-pin\" onClick={onClose}>\n                <Pin height=\"16px\" />\n              </StyledIcon>\n              {horizontalPlacement === 'end' && (\n                <StyledIcon className=\"popover-arrow-right\" onClick={moveRight}>\n                  <ArrowRight />\n                </StyledIcon>\n              )}\n              {isBase && (\n                <div className=\"primary-label\">\n                  <FormattedMessage id=\"mapPopover.primary\" />\n                </div>\n              )}\n            </PinnedButtons>\n          ) : null}\n          <PopoverContent>\n            <MapPopoverContent\n              coordinate={coordinate}\n              zoom={zoom}\n              layerHoverProp={layerHoverProp}\n            />\n          </PopoverContent>\n          {layerHoverProp?.layer?.type &&\n          SELECTABLE_LAYERS.includes(layerHoverProp?.layer?.type) &&\n          frozen ? (\n            <StyledSelectGeometry className=\"select-geometry\" onClick={onSetSelectedFeature}>\n              <CursorPoint />\n              Select Geometry\n            </StyledSelectGeometry>\n          ) : null}\n        </StyledMapPopover>\n      </FloatingPortal>\n    );\n  };\n  return injectIntl(MapPopover);\n}\n"
  },
  {
    "path": "src/components/src/map/split-map-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType, useCallback, useMemo} from 'react';\nimport classnames from 'classnames';\nimport {MapControlButton} from '../common/styled-components';\nimport {Delete, Split} from '../common/icons';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport {MapControlItem, MapControls} from '@kepler.gl/types';\n\nSplitMapButtonFactory.deps = [MapControlTooltipFactory];\n\ninterface SplitMapButtonIcons {\n  delete: ComponentType<any>;\n  split: ComponentType<any>;\n}\n\nexport type SplitMapButtonProps = {\n  isSplit: boolean;\n  mapIndex: number;\n  onToggleSplitMap: (index?: number) => void;\n  actionIcons: SplitMapButtonIcons;\n  readOnly: boolean;\n  mapControls: MapControls;\n};\n\nfunction SplitMapButtonFactory(MapControlTooltip) {\n  const defaultActionIcons = {\n    delete: Delete,\n    split: Split\n  };\n\n  /** @type {import('./split-map-button').SplitMapButtonComponent} */\n  const SplitMapButton: React.FC<SplitMapButtonProps> = ({\n    isSplit,\n    mapIndex,\n    onToggleSplitMap,\n    actionIcons = defaultActionIcons,\n    mapControls,\n    readOnly\n  }) => {\n    const splitMap = mapControls?.splitMap || ({} as MapControlItem);\n    const onClick = useCallback(\n      event => {\n        event.preventDefault();\n        onToggleSplitMap(isSplit ? mapIndex : undefined);\n      },\n      [isSplit, mapIndex, onToggleSplitMap]\n    );\n\n    const isVisible = useMemo(() => splitMap.show && readOnly !== true, [splitMap.show, readOnly]);\n\n    if (!splitMap.show) {\n      return null;\n    }\n    return isVisible ? (\n      <MapControlTooltip\n        id=\"action-toggle\"\n        message={isSplit ? 'tooltip.closePanel' : 'tooltip.switchToDualView'}\n      >\n        <MapControlButton\n          active={isSplit}\n          onClick={onClick}\n          className={classnames('map-control-button', 'split-map', {'close-map': isSplit})}\n        >\n          {isSplit ? <actionIcons.delete height=\"18px\" /> : <actionIcons.split height=\"18px\" />}\n        </MapControlButton>\n      </MapControlTooltip>\n    ) : null;\n  };\n\n  SplitMapButton.displayName = 'SplitMapButton';\n  return React.memo(SplitMapButton);\n}\n\nexport default SplitMapButtonFactory;\n"
  },
  {
    "path": "src/components/src/map/toggle-3d-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType, useCallback, useMemo} from 'react';\nimport classnames from 'classnames';\nimport {Cube3d} from '../common/icons';\nimport {MapControlButton} from '../common/styled-components';\nimport MapControlTooltipFactory from './map-control-tooltip';\nimport {MapControls} from '@kepler.gl/types';\n\nToggle3dButtonFactory.deps = [MapControlTooltipFactory];\n\ninterface Toggle3dButtonIcons {\n  cube: ComponentType<any>;\n}\n\nexport type Toggle3dButtonProps = {\n  dragRotate: boolean;\n  onTogglePerspective: () => void;\n  actionIcons: Toggle3dButtonIcons;\n  mapControls: MapControls;\n};\n\nfunction Toggle3dButtonFactory(MapControlTooltip) {\n  const defaultActionIcons = {\n    cube: props => <Cube3d {...props} height=\"18px\" />\n  };\n  /** @type {import('./toggle-3d-button').Toggle3dButtonComponent} */\n  const Toggle3dButton: React.FC<Toggle3dButtonProps> = ({\n    dragRotate,\n    onTogglePerspective,\n    actionIcons = defaultActionIcons,\n    mapControls\n  }) => {\n    const onClick = useCallback(\n      event => {\n        event.preventDefault();\n        onTogglePerspective();\n      },\n      [onTogglePerspective]\n    );\n\n    const isVisible = useMemo(() => {\n      return (mapControls?.toggle3d || {}).show;\n    }, [mapControls]);\n\n    return isVisible ? (\n      <MapControlTooltip\n        id=\"action-3d\"\n        message={dragRotate ? 'tooltip.disable3DMap' : 'tooltip.3DMap'}\n      >\n        <MapControlButton\n          onClick={onClick}\n          active={dragRotate}\n          className={classnames('map-control-button', 'toggle-3d', {map3d: dragRotate})}\n        >\n          <actionIcons.cube height=\"22px\" />\n        </MapControlButton>\n      </MapControlTooltip>\n    ) : null;\n  };\n\n  Toggle3dButton.displayName = 'Toggle3dButton';\n  return React.memo(Toggle3dButton);\n}\n\nexport default Toggle3dButtonFactory;\n"
  },
  {
    "path": "src/components/src/map-container.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// libraries\nimport React, {Component, createRef, useMemo} from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport {Map, MapRef} from 'react-map-gl';\nimport {PickInfo} from '@deck.gl/core/lib/deck';\nimport DeckGL from '@deck.gl/react';\nimport {createSelector, Selector} from 'reselect';\nimport {useDroppable} from '@dnd-kit/core';\nimport debounce from 'lodash/debounce';\n\nimport {VisStateActions, MapStateActions, UIStateActions} from '@kepler.gl/actions';\n\n// components\nimport MapPopoverFactory from './map/map-popover';\nimport MapControlFactory from './map/map-control';\nimport {\n  StyledMapContainer,\n  StyledAttribution,\n  EndHorizontalFlexbox\n} from './common/styled-components';\n\nimport EditorFactory from './editor/editor';\n\n// utils\nimport {\n  generateMapboxLayers,\n  updateMapboxLayers,\n  LayerBaseConfig,\n  VisualChannelDomain,\n  EditorLayerUtils,\n  AggregatedBin\n} from '@kepler.gl/layers';\nimport {\n  DatasetAttribution,\n  MapState,\n  MapControls,\n  Viewport,\n  SplitMap,\n  SplitMapLayers\n} from '@kepler.gl/types';\nimport {\n  errorNotification,\n  setLayerBlending,\n  isStyleUsingMapboxTiles,\n  isStyleUsingOpenStreetMapTiles,\n  getBaseMapLibrary,\n  BaseMapLibraryConfig,\n  transformRequest,\n  observeDimensions,\n  unobserveDimensions,\n  hasMobileWidth,\n  getMapLayersFromSplitMaps,\n  onViewPortChange,\n  getViewportFromMapState,\n  normalizeEvent,\n  rgbToHex,\n  computeDeckEffects,\n  getApplicationConfig,\n  GetMapRef\n} from '@kepler.gl/utils';\nimport {breakPointValues} from '@kepler.gl/styles';\n\n// default-settings\nimport {\n  FILTER_TYPES,\n  GEOCODER_LAYER_ID,\n  THROTTLE_NOTIFICATION_TIME,\n  DEFAULT_PICKING_RADIUS,\n  NO_MAP_ID,\n  EMPTY_MAPBOX_STYLE\n} from '@kepler.gl/constants';\n\nimport {DROPPABLE_MAP_CONTAINER_TYPE} from './common/dnd-layer-items';\n// Contexts\nimport {MapViewStateContext} from './map-view-state-context';\n\nimport ErrorBoundary from './common/error-boundary';\nimport {LOCALE_CODES} from '@kepler.gl/localization';\nimport {MapView} from '@deck.gl/core';\nimport {\n  MapStyle,\n  areAnyDeckLayersLoading,\n  computeDeckLayers,\n  getLayerHoverProp,\n  LayerHoverProp,\n  prepareLayersForDeck,\n  prepareLayersToRender,\n  LayersToRender\n} from '@kepler.gl/reducers';\nimport {VisState} from '@kepler.gl/schemas';\n\nimport LoadingIndicator from './loading-indicator';\n\n// Debounce the propagation of viewport change and mouse moves to redux store.\n// This is to avoid too many renders of other components when the map is\n// being panned/zoomed (leading to laggy basemap/deck syncing).\nconst DEBOUNCE_VIEWPORT_PROPAGATE = 10;\nconst DEBOUNCE_MOUSE_MOVE_PROPAGATE = 10;\n\n// How long should we wait between layer loading state changes before triggering a UI update\nconst DEBOUNCE_LOADING_STATE_PROPAGATE = 100;\n\nconst MAP_STYLE: {[key: string]: React.CSSProperties} = {\n  container: {\n    display: 'inline-block',\n    position: 'relative',\n    width: '100%',\n    height: '100%'\n  },\n  top: {\n    position: 'absolute',\n    top: 0,\n    width: '100%',\n    height: '100%',\n    pointerEvents: 'none'\n  }\n};\n\nconst LOCALE_CODES_ARRAY = Object.keys(LOCALE_CODES);\n\ninterface StyledMapContainerProps {\n  $mixBlendMode?: string;\n  $mapLibCssClass: string;\n}\n\nconst StyledMap = styled(StyledMapContainer)<StyledMapContainerProps>(\n  ({$mixBlendMode = 'normal', $mapLibCssClass}) => `\n  #default-deckgl-overlay {\n    mix-blend-mode: ${$mixBlendMode};\n  };\n  *[${$mapLibCssClass}-children] {\n    position: absolute;\n  }\n`\n);\n\nconst MAPBOXGL_STYLE_UPDATE = 'style.load';\nconst MAPBOXGL_RENDER = 'render';\nconst nop = () => {\n  return;\n};\n\ntype MapLibLogoProps = {\n  baseMapLibraryConfig: BaseMapLibraryConfig;\n};\n\nconst MapLibLogo = ({baseMapLibraryConfig}: MapLibLogoProps) => (\n  <div className=\"attrition-logo\">\n    Basemap by:\n    <a\n      style={{marginLeft: '5px'}}\n      className={`${baseMapLibraryConfig.mapLibCssClass}-ctrl-logo`}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      href={baseMapLibraryConfig.mapLibUrl}\n      aria-label={`${baseMapLibraryConfig.mapLibName} logo`}\n    />\n  </div>\n);\n\ninterface StyledDroppableProps {\n  isOver: boolean;\n}\n\nconst StyledDroppable = styled.div<StyledDroppableProps>`\n  background-color: ${props => (props.isOver ? props.theme.dndOverBackgroundColor : 'none')};\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  pointer-events: none;\n  z-index: 1;\n`;\n\nexport const isSplitSelector = props =>\n  props.visState.splitMaps && props.visState.splitMaps.length > 1;\n\nexport const Droppable = ({containerId}) => {\n  const {isOver, setNodeRef} = useDroppable({\n    id: containerId,\n    data: {type: DROPPABLE_MAP_CONTAINER_TYPE, index: containerId},\n    disabled: !containerId\n  });\n\n  return <StyledDroppable ref={setNodeRef} isOver={isOver} />;\n};\n\ninterface StyledDatasetAttributionsContainerProps {\n  isPalm: boolean;\n}\n\nconst StyledDatasetAttributionsContainer = styled.div<StyledDatasetAttributionsContainerProps>`\n  max-width: ${props => (props.isPalm ? '200px' : '300px')};\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  overflow: hidden;\n  color: ${props => props.theme.labelColor};\n  margin-right: 2px;\n  margin-bottom: 1px;\n  line-height: ${props => (props.isPalm ? '1em' : '1.4em')};\n\n  &:hover {\n    white-space: inherit;\n  }\n`;\n\nconst DatasetAttributions = ({\n  datasetAttributions,\n  isPalm\n}: {\n  datasetAttributions: DatasetAttribution[];\n  isPalm: boolean;\n}) => (\n  <>\n    {datasetAttributions?.length ? (\n      <StyledDatasetAttributionsContainer isPalm={isPalm}>\n        {datasetAttributions.map((ds, idx) => (\n          <a\n            {...(ds.url ? {href: ds.url} : null)}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            key={`${ds.title}_${idx}`}\n          >\n            {ds.title}\n            {idx !== datasetAttributions.length - 1 ? ', ' : null}\n          </a>\n        ))}\n      </StyledDatasetAttributionsContainer>\n    ) : null}\n  </>\n);\n\ntype AttributionProps = {\n  showBaseMapLibLogo: boolean;\n  showOsmBasemapAttribution: boolean;\n  datasetAttributions: DatasetAttribution[];\n  baseMapLibraryConfig: BaseMapLibraryConfig;\n};\n\nexport const Attribution: React.FC<AttributionProps> = ({\n  showBaseMapLibLogo = true,\n  showOsmBasemapAttribution = false,\n  datasetAttributions,\n  baseMapLibraryConfig\n}: AttributionProps) => {\n  const isPalm = hasMobileWidth(breakPointValues);\n\n  const memoizedComponents = useMemo(() => {\n    if (!showBaseMapLibLogo) {\n      return (\n        <StyledAttribution\n          mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}\n          mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}\n        >\n          <EndHorizontalFlexbox>\n            <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />\n            {showOsmBasemapAttribution ? (\n              <div className=\"attrition-link\">\n                {datasetAttributions?.length ? <span className=\"pipe-separator\">|</span> : null}\n                <a\n                  href=\"http://www.openstreetmap.org/copyright\"\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                >\n                  © OpenStreetMap\n                </a>\n              </div>\n            ) : null}\n          </EndHorizontalFlexbox>\n        </StyledAttribution>\n      );\n    }\n\n    return (\n      <StyledAttribution\n        mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}\n        mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}\n      >\n        <EndHorizontalFlexbox>\n          <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />\n          <div className=\"attrition-link\">\n            {datasetAttributions?.length ? <span className=\"pipe-separator\">|</span> : null}\n            {isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}\n            <a href=\"https://kepler.gl/policy/\" target=\"_blank\" rel=\"noopener noreferrer\">\n              © kepler.gl |{' '}\n            </a>\n            {!isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}\n          </div>\n        </EndHorizontalFlexbox>\n      </StyledAttribution>\n    );\n  }, [\n    showBaseMapLibLogo,\n    showOsmBasemapAttribution,\n    datasetAttributions,\n    isPalm,\n    baseMapLibraryConfig\n  ]);\n\n  return memoizedComponents;\n};\n\nMapContainerFactory.deps = [MapPopoverFactory, MapControlFactory, EditorFactory];\n\ntype MapboxStyle = string | object | undefined;\ntype PropSelector<R> = Selector<MapContainerProps, R>;\n\nexport interface MapContainerProps {\n  visState: VisState;\n  mapState: MapState;\n  mapControls: MapControls;\n  mapStyle: {bottomMapStyle?: MapboxStyle; topMapStyle?: MapboxStyle} & MapStyle;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl: string;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n  uiStateActions: typeof UIStateActions;\n\n  // optional\n  primary?: boolean; // primary one will be reporting its size to appState\n  readOnly?: boolean;\n  isExport?: boolean;\n  // onMapStyleLoaded?: (map: maplibregl.Map | ReturnType<MapRef['getMap']> | null) => void;\n  onMapStyleLoaded?: (map: GetMapRef | null) => void;\n  onMapRender?: (map: GetMapRef | null) => void;\n  getMapboxRef?: (mapbox?: MapRef | null, index?: number) => void;\n  index?: number;\n  deleteMapLabels?: (containerId: string, layerId: string) => void;\n  containerId?: number;\n\n  isLoadingIndicatorVisible?: boolean;\n  activeSidePanel: string | null;\n  sidePanelWidth?: number;\n\n  locale?: any;\n  theme?: any;\n  editor?: any;\n  MapComponent?: typeof Map;\n  deckGlProps?: any;\n  onDeckInitialized?: (a: any, b: any) => void;\n  onViewStateChange?: (viewport: Viewport) => void;\n\n  topMapContainerProps: any;\n  bottomMapContainerProps: any;\n  transformRequest?: (url: string, resourceType?: string) => {url: string};\n\n  datasetAttributions?: DatasetAttribution[];\n\n  generateMapboxLayers?: typeof generateMapboxLayers;\n  generateDeckGLLayers?: typeof computeDeckLayers;\n\n  onMouseMove?: (event: React.MouseEvent & {lngLat?: [number, number]}) => void;\n\n  children?: React.ReactNode;\n  deckRenderCallbacks?: {\n    onDeckLoad?: () => void;\n    onDeckRender?: (deckProps: Record<string, unknown>) => Record<string, unknown> | null;\n    onDeckAfterRender?: (deckProps: Record<string, unknown>) => any;\n  };\n\n  // Optional: override legend header logo in map controls (used by image export)\n  logoComponent?: React.FC | React.ReactNode;\n}\n\nexport default function MapContainerFactory(\n  MapPopover: ReturnType<typeof MapPopoverFactory>,\n  MapControl: ReturnType<typeof MapControlFactory>,\n  Editor: ReturnType<typeof EditorFactory>\n): React.ComponentType<MapContainerProps> {\n  class MapContainer extends Component<MapContainerProps> {\n    displayName = 'MapContainer';\n\n    private anyActiveLayerLoading = false;\n\n    static contextType = MapViewStateContext;\n\n    declare context: React.ContextType<typeof MapViewStateContext>;\n\n    static defaultProps = {\n      MapComponent: Map,\n      deckGlProps: {},\n      index: 0,\n      primary: true\n    };\n\n    constructor(props) {\n      super(props);\n    }\n\n    state = {\n      // Determines whether attribution should be visible based the result of loading the map style\n      showBaseMapAttribution: true\n    };\n\n    componentDidMount() {\n      if (!this._ref.current) {\n        return;\n      }\n      observeDimensions(this._ref.current, this._handleResize);\n    }\n\n    componentWillUnmount() {\n      // unbind mapboxgl event listener\n      if (this._map) {\n        this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);\n        this._map?.off(MAPBOXGL_RENDER, nop);\n      }\n      if (!this._ref.current) {\n        return;\n      }\n      unobserveDimensions(this._ref.current);\n    }\n\n    _deck: any = null;\n    _map: GetMapRef | null = null;\n    _ref = createRef<HTMLDivElement>();\n    _deckGLErrorsElapsed: {[id: string]: number} = {};\n\n    previousLayers = {\n      // [layers.id]: mapboxLayerConfig\n    };\n\n    _handleResize = dimensions => {\n      const {primary, index} = this.props;\n      if (primary) {\n        const {mapStateActions} = this.props;\n        if (dimensions && dimensions.width > 0 && dimensions.height > 0) {\n          mapStateActions.updateMap(dimensions, index);\n        }\n      }\n    };\n\n    layersSelector: PropSelector<VisState['layers']> = props => props.visState.layers;\n    layerDataSelector: PropSelector<VisState['layers']> = props => props.visState.layerData;\n    splitMapSelector: PropSelector<SplitMap[]> = props => props.visState.splitMaps;\n    splitMapIndexSelector: PropSelector<number | undefined> = props => props.index;\n    mapLayersSelector: PropSelector<SplitMapLayers | null | undefined> = createSelector(\n      this.splitMapSelector,\n      this.splitMapIndexSelector,\n      getMapLayersFromSplitMaps\n    );\n    layerOrderSelector: PropSelector<VisState['layerOrder']> = props => props.visState.layerOrder;\n    layersToRenderSelector: PropSelector<LayersToRender> = createSelector(\n      this.layersSelector,\n      this.layerDataSelector,\n      this.mapLayersSelector,\n      prepareLayersToRender\n    );\n    layersForDeckSelector = createSelector(\n      this.layersSelector,\n      this.layerDataSelector,\n      prepareLayersForDeck\n    );\n    filtersSelector = props => props.visState.filters;\n    polygonFiltersSelector = createSelector(this.filtersSelector, filters =>\n      filters.filter(f => f.type === FILTER_TYPES.polygon && f.enabled !== false)\n    );\n    featuresSelector = props => props.visState.editor.features;\n    selectedFeatureSelector = props => props.visState.editor.selectedFeature;\n    featureCollectionSelector = createSelector(\n      this.polygonFiltersSelector,\n      this.featuresSelector,\n      (polygonFilters, features) => ({\n        type: 'FeatureCollection',\n        features: features.concat(polygonFilters.map(f => f.value))\n      })\n    );\n    // @ts-ignore - No overload matches this call\n    selectedPolygonIndexSelector = createSelector(\n      this.featureCollectionSelector,\n      this.selectedFeatureSelector,\n      (collection, selectedFeature) =>\n        collection.features.findIndex(f => f.id === selectedFeature?.id)\n    );\n    selectedFeatureIndexArraySelector = createSelector(\n      (value: number) => value,\n      value => {\n        return value < 0 ? [] : [value];\n      }\n    );\n\n    generateMapboxLayerMethodSelector = props => props.generateMapboxLayers ?? generateMapboxLayers;\n\n    mapboxLayersSelector = createSelector(\n      this.layersSelector,\n      this.layerDataSelector,\n      this.layerOrderSelector,\n      this.layersToRenderSelector,\n      this.generateMapboxLayerMethodSelector,\n      (layer, layerData, layerOrder, layersToRender, generateMapboxLayerMethod) =>\n        generateMapboxLayerMethod(layer, layerData, layerOrder, layersToRender)\n    );\n\n    // merge in a background-color style if the basemap choice is NO_MAP_ID\n    // used by <StyledMap> inline style prop\n    mapStyleTypeSelector = props => props.mapStyle.styleType;\n    mapStyleBackgroundColorSelector = props => props.mapStyle.backgroundColor;\n    styleSelector = createSelector(\n      this.mapStyleTypeSelector,\n      this.mapStyleBackgroundColorSelector,\n      (styleType, backgroundColor) => ({\n        ...MAP_STYLE.container,\n        ...(styleType === NO_MAP_ID ? {backgroundColor: rgbToHex(backgroundColor)} : {})\n      })\n    );\n\n    /* component private functions */\n    _onCloseMapPopover = () => {\n      this.props.visStateActions.onLayerClick(null);\n    };\n\n    _onLayerHover = (_idx: number, info: PickInfo<any> | null) => {\n      this.props.visStateActions.onLayerHover(info, this.props.index);\n    };\n\n    _onLayerSetDomain = (\n      idx: number,\n      value: {domain: VisualChannelDomain; aggregatedBins: AggregatedBin[]}\n    ) => {\n      this.props.visStateActions.layerConfigChange(this.props.visState.layers[idx], {\n        colorDomain: value.domain,\n        aggregatedBins: value.aggregatedBins\n      } as Partial<LayerBaseConfig>);\n    };\n\n    _onRedrawNeeded = (_idx: number) => {\n      // updateMapUpdater always returns a new state object reference, which triggers re-render\n      const {mapStateActions, index} = this.props;\n      mapStateActions.updateMap({}, index);\n    };\n\n    _onLayerFilteredItemsChange = (idx, event) => {\n      this.props.visStateActions.layerFilteredItemsChange(this.props.visState.layers[idx], event);\n    };\n\n    _onWMSFeatureInfo = (\n      idx: number,\n      data: {\n        featureInfo: Array<{name: string; value: string}> | string | null;\n        coordinate?: [number, number] | null;\n      }\n    ) => {\n      this.props.visStateActions.wmsFeatureInfo(\n        this.props.visState.layers[idx],\n        data.featureInfo,\n        data.coordinate\n      );\n    };\n\n    _handleMapToggleLayer = layerId => {\n      const {index: mapIndex = 0, visStateActions} = this.props;\n      visStateActions.toggleLayerForMap(mapIndex, layerId);\n    };\n\n    _onMapboxStyleUpdate = update => {\n      // force refresh mapboxgl layers\n      this.previousLayers = {};\n      this._updateMapboxLayers();\n\n      if (update && update.style) {\n        // No attributions are needed if the style doesn't reference Mapbox sources\n        this.setState({\n          showBaseMapAttribution:\n            isStyleUsingMapboxTiles(update.style) || !isStyleUsingOpenStreetMapTiles(update.style)\n        });\n      }\n\n      if (typeof this.props.onMapStyleLoaded === 'function') {\n        this.props.onMapStyleLoaded(this._map);\n      }\n    };\n\n    _setMapRef = mapRef => {\n      // Handle change of the map library\n      if (this._map && mapRef) {\n        const map = mapRef.getMap();\n        if (map && this._map !== map) {\n          this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);\n          this._map?.off(MAPBOXGL_RENDER, nop);\n          this._map = null;\n        }\n      }\n\n      if (!this._map && mapRef) {\n        this._map = mapRef.getMap();\n        // i noticed in certain context we don't access the actual map element\n        if (!this._map) {\n          return;\n        }\n        // bind mapboxgl event listener\n        this._map.on(MAPBOXGL_STYLE_UPDATE, this._onMapboxStyleUpdate);\n\n        this._map.on(MAPBOXGL_RENDER, () => {\n          if (typeof this.props.onMapRender === 'function') {\n            this.props.onMapRender(this._map);\n          }\n        });\n      }\n\n      if (this.props.getMapboxRef) {\n        // The parent component can gain access to our MapboxGlMap by\n        // providing this callback. Note that 'mapbox' will be null when the\n        // ref is unset (e.g. when a split map is closed).\n        this.props.getMapboxRef(mapRef, this.props.index);\n      }\n    };\n\n    _onDeckInitialized(gl) {\n      if (this.props.onDeckInitialized) {\n        this.props.onDeckInitialized(this._deck, gl);\n      }\n    }\n\n    /**\n     * 1) Allow effects only for the first view.\n     * 2) Prevent effect:preRender call without valid generated viewports.\n     * @param viewIndex View index.\n     * @returns Returns true if effects can be used.\n     */\n    _isOKToRenderEffects(viewIndex?: number): boolean {\n      return !viewIndex && Boolean(this._deck?.viewManager?._viewports?.length);\n    }\n\n    _onBeforeRender = ({gl}) => {\n      setLayerBlending(gl, this.props.visState.layerBlending);\n    };\n\n    _onDeckError = (error, layer) => {\n      const errorMessage = error?.message || 'unknown-error';\n      const layerMessage = layer?.id ? ` in ${layer.id} layer` : '';\n      const errorMessageFull =\n        errorMessage === 'WebGL context is lost'\n          ? 'Your GPU was disconnected. This can happen if your computer goes to sleep. It can also occur for other reasons, such as if you are running too many GPU applications.'\n          : `An error in deck.gl: ${errorMessage}${layerMessage}.`;\n\n      // Throttle error notifications, as React doesn't like too many state changes from here.\n      const lastShown = this._deckGLErrorsElapsed[errorMessageFull];\n      if (!lastShown || lastShown < Date.now() - THROTTLE_NOTIFICATION_TIME) {\n        this._deckGLErrorsElapsed[errorMessageFull] = Date.now();\n\n        // Mark layer as invalid\n        let extraLayerMessage = '';\n        const {visStateActions} = this.props;\n        if (layer) {\n          let topMostLayer = layer;\n          while (topMostLayer.parent) {\n            topMostLayer = topMostLayer.parent;\n          }\n          if (topMostLayer.props?.id) {\n            visStateActions.layerSetIsValid(topMostLayer, false);\n            extraLayerMessage = 'The layer has been disabled and highlighted.';\n          }\n        }\n\n        // Create new error notification or update existing one with same id.\n        // Update is required to preserve the order of notifications as they probably are going to \"jump\" based on order of errors.\n        const {uiStateActions} = this.props;\n        uiStateActions.addNotification(\n          errorNotification({\n            message: `${errorMessageFull} ${extraLayerMessage}`,\n            id: errorMessageFull // treat the error message as id\n          })\n        );\n      }\n    };\n\n    /* component render functions */\n\n    /* eslint-disable complexity */\n    _renderMapPopover() {\n      // this check is for limiting the display of the `<MapPopover>` to the `<MapContainer>` the user is interacting with\n      // the DeckGL onHover event handler adds a `mapIndex` property which is available in the `hoverInfo` object of `visState`\n      if (this.props.index !== this.props.visState.hoverInfo?.mapIndex) {\n        return null;\n      }\n\n      // TODO: move this into reducer so it can be tested\n      const {\n        mapState,\n        visState: {\n          hoverInfo,\n          clicked,\n          datasets,\n          interactionConfig,\n          animationConfig,\n          layers,\n          mousePos: {mousePosition, coordinate, pinned}\n        }\n      } = this.props;\n      const layersToRender = this.layersToRenderSelector(this.props);\n\n      if (!mousePosition || !interactionConfig.tooltip) {\n        return null;\n      }\n\n      const layerHoverProp = getLayerHoverProp({\n        animationConfig,\n        interactionConfig,\n        hoverInfo,\n        layers,\n        layersToRender,\n        datasets\n      });\n\n      const compareMode = interactionConfig.tooltip.config\n        ? interactionConfig.tooltip.config.compareMode\n        : false;\n\n      let pinnedPosition = {x: 0, y: 0};\n      let layerPinnedProp: LayerHoverProp | null = null;\n      if (pinned || clicked) {\n        // project lnglat to screen so that tooltip follows the object on zoom\n        const viewport = getViewportFromMapState(mapState);\n        const lngLat = clicked ? clicked.coordinate : pinned.coordinate;\n        pinnedPosition = this._getHoverXY(viewport, lngLat);\n        layerPinnedProp = getLayerHoverProp({\n          animationConfig,\n          interactionConfig,\n          hoverInfo: clicked,\n          layers,\n          layersToRender,\n          datasets\n        });\n        if (layerHoverProp && layerPinnedProp) {\n          layerHoverProp.primaryData = layerPinnedProp.data;\n          layerHoverProp.compareType = interactionConfig.tooltip.config.compareType;\n        }\n      }\n\n      const commonProp = {\n        onClose: this._onCloseMapPopover,\n        zoom: mapState.zoom,\n        container: this._deck ? this._deck.canvas : undefined\n      };\n\n      return (\n        <ErrorBoundary>\n          {layerPinnedProp && (\n            <MapPopover\n              {...pinnedPosition}\n              {...commonProp}\n              layerHoverProp={layerPinnedProp}\n              coordinate={interactionConfig.coordinate.enabled && (pinned || {}).coordinate}\n              frozen={true}\n              isBase={compareMode}\n              onSetFeatures={this.props.visStateActions.setFeatures}\n              setSelectedFeature={this.props.visStateActions.setSelectedFeature}\n              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'\n              featureCollection={this.featureCollectionSelector(this.props)}\n            />\n          )}\n          {layerHoverProp && (!layerPinnedProp || compareMode) && (\n            <MapPopover\n              x={mousePosition[0]}\n              y={mousePosition[1]}\n              {...commonProp}\n              layerHoverProp={layerHoverProp}\n              frozen={false}\n              coordinate={interactionConfig.coordinate.enabled && coordinate}\n              onSetFeatures={this.props.visStateActions.setFeatures}\n              setSelectedFeature={this.props.visStateActions.setSelectedFeature}\n              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'\n              featureCollection={this.featureCollectionSelector(this.props)}\n            />\n          )}\n        </ErrorBoundary>\n      );\n    }\n\n    /* eslint-enable complexity */\n\n    _getHoverXY(viewport, lngLat) {\n      const screenCoord = !viewport || !lngLat ? null : viewport.project(lngLat);\n      return screenCoord && {x: screenCoord[0], y: screenCoord[1]};\n    }\n\n    _renderDeckOverlay(\n      layersForDeck,\n      options: {primaryMap: boolean; isInteractive?: boolean; children?: React.ReactNode} = {\n        primaryMap: false\n      }\n    ) {\n      const {\n        mapStyle,\n        visState,\n        mapState,\n        visStateActions,\n        mapboxApiAccessToken,\n        mapboxApiUrl,\n        deckGlProps,\n        index,\n        mapControls,\n        deckRenderCallbacks,\n        theme,\n        generateDeckGLLayers,\n        onMouseMove\n      } = this.props;\n\n      const {hoverInfo, editor} = visState;\n      const {primaryMap, isInteractive, children} = options;\n\n      // disable double click zoom when editor is in any draw mode\n      const {mapDraw} = mapControls;\n      const {active: editorMenuActive = false} = mapDraw || {};\n      const isEditorDrawingMode = EditorLayerUtils.isDrawingActive(editorMenuActive, editor.mode);\n\n      const internalViewState = this.context?.getInternalViewState(index);\n      const internalMapState = {...mapState, ...internalViewState};\n      const viewport = getViewportFromMapState(internalMapState);\n\n      const editorFeatureSelectedIndex = this.selectedPolygonIndexSelector(this.props);\n\n      const {setFeatures, onLayerClick, setSelectedFeature} = visStateActions;\n\n      const generateDeckGLLayersMethod = generateDeckGLLayers ?? computeDeckLayers;\n      const deckGlLayers = generateDeckGLLayersMethod(\n        {\n          visState,\n          mapState: internalMapState,\n          mapStyle\n        },\n        {\n          mapIndex: index,\n          primaryMap,\n          mapboxApiAccessToken,\n          mapboxApiUrl,\n          layersForDeck,\n          editorInfo: primaryMap\n            ? {\n                editor,\n                editorMenuActive,\n                onSetFeatures: setFeatures,\n                setSelectedFeature,\n                // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'\n                featureCollection: this.featureCollectionSelector(this.props),\n                selectedFeatureIndexes: this.selectedFeatureIndexArraySelector(\n                  // @ts-ignore Argument of type 'unknown' is not assignable to parameter of type 'number'.\n                  editorFeatureSelectedIndex\n                ),\n                viewport\n              }\n            : undefined\n        },\n        {\n          onLayerHover: this._onLayerHover,\n          onSetLayerDomain: this._onLayerSetDomain,\n          onFilteredItemsChange: this._onLayerFilteredItemsChange,\n          onWMSFeatureInfo: this._onWMSFeatureInfo,\n          onRedrawNeeded: this._onRedrawNeeded\n        },\n        deckGlProps\n      );\n\n      const extraDeckParams: {\n        getTooltip?: (info: any) => object | null;\n        getCursor?: ({isDragging}: {isDragging: boolean}) => string;\n      } = {};\n      if (primaryMap) {\n        // Omit hover updates when the pointer position is invalid, ie. over UI overlays or\n        // outside the map container. In those cases x/y may be < 0\n        extraDeckParams.getTooltip = info => {\n          const x = Number(info?.x);\n          const y = Number(info?.y);\n          if (Number.isNaN(x) || Number.isNaN(y) || x < 0 || y < 0) return null;\n\n          return EditorLayerUtils.getTooltip(info, {\n            editorMenuActive,\n            editor,\n            theme\n          });\n        };\n\n        extraDeckParams.getCursor = ({isDragging}: {isDragging: boolean}) => {\n          const editorCursor = EditorLayerUtils.getCursor({\n            editorMenuActive,\n            editor,\n            hoverInfo\n          });\n          if (editorCursor) return editorCursor;\n\n          if (isDragging) return 'grabbing';\n          if (hoverInfo?.layer) return 'pointer';\n          return 'grab';\n        };\n      }\n\n      const effects = this._isOKToRenderEffects(index)\n        ? computeDeckEffects({visState, mapState})\n        : [];\n\n      const views = deckGlProps?.views\n        ? deckGlProps?.views()\n        : new MapView({legacyMeterSizes: true});\n\n      let allDeckGlProps = {\n        ...deckGlProps,\n        pickingRadius: DEFAULT_PICKING_RADIUS,\n        views,\n        layers: deckGlLayers,\n        effects\n      };\n\n      if (typeof deckRenderCallbacks?.onDeckRender === 'function') {\n        allDeckGlProps = deckRenderCallbacks.onDeckRender(allDeckGlProps);\n        if (!allDeckGlProps) {\n          // if onDeckRender returns null, do not render deck.gl\n          return null;\n        }\n      }\n\n      return (\n        <div\n          {...(isInteractive\n            ? {\n                onMouseMove: primaryMap\n                  ? event => {\n                      onMouseMove?.(event);\n                      this._onMouseMoveDebounced(event, viewport);\n                    }\n                  : undefined\n              }\n            : {style: {pointerEvents: 'none'}})}\n        >\n          <DeckGL\n            id=\"default-deckgl-overlay\"\n            onLoad={() => {\n              if (typeof deckRenderCallbacks?.onDeckLoad === 'function') {\n                deckRenderCallbacks.onDeckLoad();\n              }\n            }}\n            {...allDeckGlProps}\n            controller={\n              isInteractive\n                ? {\n                    doubleClickZoom: !isEditorDrawingMode,\n                    dragRotate: this.props.mapState.dragRotate\n                  }\n                : false\n            }\n            initialViewState={internalViewState}\n            onBeforeRender={this._onBeforeRender}\n            onViewStateChange={isInteractive ? this._onViewportChange : undefined}\n            {...extraDeckParams}\n            onHover={\n              isInteractive\n                ? data => {\n                    const res = EditorLayerUtils.onHover(data, {\n                      editorMenuActive,\n                      editor,\n                      hoverInfo\n                    });\n                    if (res) return;\n\n                    this._onLayerHoverDebounced(data, index);\n                  }\n                : null\n            }\n            onClick={(data, event) => {\n              // @ts-ignore\n              normalizeEvent(event.srcEvent, viewport);\n              const res = EditorLayerUtils.onClick(data, event, {\n                editorMenuActive,\n                editor,\n                onLayerClick,\n                setSelectedFeature,\n                mapIndex: index\n              });\n              if (res) return;\n\n              visStateActions.onLayerClick(data);\n            }}\n            onError={this._onDeckError}\n            ref={comp => {\n              // @ts-ignore\n              if (comp && comp.deck && !this._deck) {\n                // @ts-ignore\n                this._deck = comp.deck;\n              }\n            }}\n            onWebGLInitialized={gl => this._onDeckInitialized(gl)}\n            onAfterRender={() => {\n              if (typeof deckRenderCallbacks?.onDeckAfterRender === 'function') {\n                deckRenderCallbacks.onDeckAfterRender(allDeckGlProps);\n              }\n\n              const anyActiveLayerLoading = areAnyDeckLayersLoading(allDeckGlProps.layers);\n              if (anyActiveLayerLoading !== this.anyActiveLayerLoading) {\n                this._onLayerLoadingStateChange();\n                this.anyActiveLayerLoading = anyActiveLayerLoading;\n              }\n            }}\n          >\n            {children}\n          </DeckGL>\n        </div>\n      );\n    }\n\n    _updateMapboxLayers() {\n      const mapboxLayers = this.mapboxLayersSelector(this.props);\n      if (!Object.keys(mapboxLayers).length && !Object.keys(this.previousLayers).length) {\n        return;\n      }\n\n      updateMapboxLayers(this._map, mapboxLayers, this.previousLayers);\n\n      this.previousLayers = mapboxLayers;\n    }\n\n    _renderMapboxOverlays() {\n      if (this._map && this._map.isStyleLoaded()) {\n        this._updateMapboxLayers();\n      }\n    }\n    _onViewportChangePropagateDebounced = debounce(() => {\n      const viewState = this.context?.getInternalViewState(this.props.index);\n      onViewPortChange(\n        viewState,\n        this.props.mapStateActions.updateMap,\n        this.props.onViewStateChange,\n        this.props.primary,\n        this.props.index\n      );\n    }, DEBOUNCE_VIEWPORT_PROPAGATE);\n\n    _onViewportChange = viewport => {\n      const {viewState} = viewport;\n      if (this.props.isExport) {\n        // Image export map shouldn't be interactive (otherwise this callback can\n        // lead to inadvertent changes to the state of the main map)\n        return;\n      }\n      const {setInternalViewState} = this.context;\n      setInternalViewState(viewState, this.props.index);\n      this._onViewportChangePropagateDebounced();\n    };\n\n    _onLayerHoverDebounced = debounce((data, index) => {\n      this.props.visStateActions.onLayerHover(data, index);\n    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);\n\n    _onMouseMoveDebounced = debounce((event, viewport) => {\n      this.props.visStateActions.onMouseMove(normalizeEvent(event, viewport));\n    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);\n\n    _onLayerLoadingStateChange = debounce(() => {\n      // trigger loading indicator update without any change to update UI\n      this.props.visStateActions.setLoadingIndicator({change: 0});\n    }, DEBOUNCE_LOADING_STATE_PROPAGATE);\n\n    _toggleMapControl = panelId => {\n      const {index, uiStateActions} = this.props;\n\n      uiStateActions.toggleMapControl(panelId, Number(index));\n    };\n\n    /* eslint-disable complexity */\n    _renderMap() {\n      const {\n        visState,\n        mapState,\n        mapStyle,\n        mapStateActions,\n        MapComponent = Map,\n        mapboxApiAccessToken,\n        // mapboxApiUrl,\n        mapControls,\n        isExport,\n        locale,\n        uiStateActions,\n        visStateActions,\n        index,\n        primary,\n        bottomMapContainerProps,\n        topMapContainerProps,\n        theme,\n        datasetAttributions = [],\n        containerId = 0,\n        isLoadingIndicatorVisible,\n        activeSidePanel,\n        sidePanelWidth\n      } = this.props;\n\n      const {layers, datasets, editor, interactionConfig} = visState;\n\n      const layersToRender = this.layersToRenderSelector(this.props);\n      const layersForDeck = this.layersForDeckSelector(this.props);\n\n      // Current style can be a custom style, from which we pull the mapbox API acccess token\n      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];\n      const baseMapLibraryName = getBaseMapLibrary(currentStyle);\n      const baseMapLibraryConfig =\n        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];\n\n      const internalViewState = this.context?.getInternalViewState(index);\n      const mapProps = {\n        ...internalViewState,\n        preserveDrawingBuffer: true,\n        mapboxAccessToken: currentStyle?.accessToken || mapboxApiAccessToken,\n        // baseApiUrl: mapboxApiUrl,\n        mapLib: baseMapLibraryConfig.getMapLib(),\n        transformRequest:\n          this.props.transformRequest ||\n          transformRequest(currentStyle?.accessToken || mapboxApiAccessToken)\n      };\n\n      const hasGeocoderLayer = Boolean(layers.find(l => l.id === GEOCODER_LAYER_ID));\n      const isSplit = Boolean(mapState.isSplit);\n\n      const deck = this._renderDeckOverlay(layersForDeck, {\n        primaryMap: true,\n        isInteractive: true,\n        children: (\n          <MapComponent\n            key={`bottom-${baseMapLibraryName}`}\n            {...mapProps}\n            mapStyle={mapStyle.bottomMapStyle ?? EMPTY_MAPBOX_STYLE}\n            {...bottomMapContainerProps}\n            ref={this._setMapRef}\n          />\n        )\n      });\n      if (!deck) {\n        // deckOverlay can be null if onDeckRender returns null\n        // in this case we don't want to render the map\n        return null;\n      }\n      return (\n        <>\n          <MapControl\n            mapState={mapState}\n            datasets={datasets}\n            availableLocales={LOCALE_CODES_ARRAY}\n            dragRotate={mapState.dragRotate}\n            isSplit={isSplit}\n            primary={Boolean(primary)}\n            isExport={isExport}\n            layers={layers}\n            layersToRender={layersToRender}\n            mapIndex={index || 0}\n            mapControls={mapControls}\n            readOnly={this.props.readOnly}\n            scale={mapState.scale || 1}\n            logoComponent={this.props.logoComponent}\n            top={\n              interactionConfig.geocoder && interactionConfig.geocoder.enabled\n                ? theme.mapControlTop\n                : 0\n            }\n            editor={editor}\n            locale={locale}\n            onTogglePerspective={mapStateActions.togglePerspective}\n            onToggleSplitMap={mapStateActions.toggleSplitMap}\n            onMapToggleLayer={this._handleMapToggleLayer}\n            onToggleMapControl={this._toggleMapControl}\n            onToggleSplitMapViewport={mapStateActions.toggleSplitMapViewport}\n            onSetEditorMode={visStateActions.setEditorMode}\n            onSetLocale={uiStateActions.setLocale}\n            onToggleEditorVisibility={visStateActions.toggleEditorVisibility}\n            onLayerVisConfigChange={visStateActions.layerVisConfigChange}\n            mapHeight={mapState.height}\n            setMapControlSettings={uiStateActions.setMapControlSettings}\n            activeSidePanel={activeSidePanel}\n          />\n          {isSplitSelector(this.props) && <Droppable containerId={containerId} />}\n\n          {deck}\n          {this._renderMapboxOverlays()}\n          <Editor\n            index={index || 0}\n            datasets={datasets}\n            editor={editor}\n            filters={this.polygonFiltersSelector(this.props)}\n            layers={layers}\n            onDeleteFeature={visStateActions.deleteFeature}\n            onSelect={visStateActions.setSelectedFeature}\n            onTogglePolygonFilter={visStateActions.setPolygonFilterLayer}\n            onSetEditorMode={visStateActions.setEditorMode}\n            style={{\n              pointerEvents: 'all',\n              position: 'absolute',\n              display: editor.visible ? 'block' : 'none'\n            }}\n          />\n          {this.props.children}\n          {mapStyle.topMapStyle ? (\n            <MapComponent\n              key={`top-${baseMapLibraryName}`}\n              viewState={internalViewState}\n              mapStyle={mapStyle.topMapStyle}\n              style={MAP_STYLE.top}\n              mapboxAccessToken={mapProps.mapboxAccessToken}\n              transformRequest={mapProps.transformRequest}\n              mapLib={baseMapLibraryConfig.getMapLib()}\n              {...topMapContainerProps}\n            />\n          ) : null}\n\n          {hasGeocoderLayer\n            ? this._renderDeckOverlay(\n                {[GEOCODER_LAYER_ID]: hasGeocoderLayer},\n                {primaryMap: false, isInteractive: false}\n              )\n            : null}\n          {this._renderMapPopover()}\n          {primary !== isSplit ? (\n            <LoadingIndicator\n              isVisible={Boolean(isLoadingIndicatorVisible || this.anyActiveLayerLoading)}\n              activeSidePanel={Boolean(activeSidePanel)}\n              sidePanelWidth={sidePanelWidth}\n            />\n          ) : null}\n          {this.props.primary ? (\n            <Attribution\n              showBaseMapLibLogo={this.state.showBaseMapAttribution}\n              showOsmBasemapAttribution={true}\n              datasetAttributions={datasetAttributions}\n              baseMapLibraryConfig={baseMapLibraryConfig}\n            />\n          ) : null}\n        </>\n      );\n    }\n\n    render() {\n      const {visState, mapStyle} = this.props;\n      const mapContent = this._renderMap();\n      if (!mapContent) {\n        // mapContent can be null if onDeckRender returns null\n        // in this case we don't want to render the map\n        return null;\n      }\n\n      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];\n      const baseMapLibraryName = getBaseMapLibrary(currentStyle);\n      const baseMapLibraryConfig =\n        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];\n\n      return (\n        <StyledMap\n          ref={this._ref}\n          style={this.styleSelector(this.props)}\n          onContextMenu={event => event.preventDefault()}\n          $mixBlendMode={visState.overlayBlending}\n          $mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}\n        >\n          {mapContent}\n        </StyledMap>\n      );\n    }\n  }\n\n  return withTheme(MapContainer);\n}\n"
  },
  {
    "path": "src/components/src/map-view-state-context.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, useEffect, createContext} from 'react';\nimport isEqual from 'lodash/isEqual';\nimport pick from 'lodash/pick';\nimport {MapViewState} from '@deck.gl/core/typed';\nimport {pickViewportPropsFromMapState} from '@kepler.gl/reducers';\n\nimport {MapState} from '@kepler.gl/types';\n\nexport type MapViewStateContextType = {\n  getInternalViewState: (index?: number) => MapViewState;\n  setInternalViewState: (viewState?: MapViewState, index?: number) => void;\n};\n\nexport const MapViewStateContext: React.Context<MapViewStateContextType> = createContext({\n  getInternalViewState: () => ({latitude: 0, longitude: 0, zoom: 0}),\n  setInternalViewState: () => {\n    return;\n  }\n});\n\n/**\n * This context provider is used to localize the map view state so\n * that changes to the map view state do not affect the rest of the app,\n * mainly to prevent issues we experienced with basemap/deck viewport syncing.\n */\n\nexport const MapViewStateContextProvider = ({\n  mapState,\n  children\n}: {\n  mapState: MapState;\n  children: React.ReactNode;\n}) => {\n  const {isSplit, isViewportSynced} = mapState || {};\n\n  // Store locally map view states by mapIndex\n  const [viewStates, setViewStates] = useState([mapState]);\n\n  // Detect and apply outside viewport changes\n  // (e.g. from geocoder or when switching to 3d mode)\n  useEffect(() => {\n    if (!mapState) return;\n    const primaryState = viewStates[0];\n    if (primaryState === mapState) return;\n    const props = Object.keys(primaryState).filter(key => !key.startsWith('transition'));\n    const hasChanged = (a, b) => !isEqual(pick(a, props), pick(b, props));\n    if (isSplit && !isViewportSynced) {\n      if (mapState.splitMapViewports?.some((s, i) => hasChanged(s, viewStates[i]))) {\n        setViewStates(mapState.splitMapViewports as MapState[]);\n      }\n    } else if (hasChanged(primaryState, mapState)) {\n      setViewStates([pickViewportPropsFromMapState(mapState)] as MapState[]);\n    }\n    // Only update internalViewState when viewState changes\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [mapState]);\n\n  const value = {\n    getInternalViewState: (index = 0) => viewStates[index] ?? viewStates[0],\n    setInternalViewState: (newViewState, index = 0) => {\n      setViewStates(prevViewStates => {\n        if (isSplit && !isViewportSynced) {\n          const nextViewStates = [...prevViewStates];\n          nextViewStates[index] = newViewState as MapState;\n          return nextViewStates;\n        }\n        return [newViewState] as MapState[];\n      });\n    }\n  } as MapViewStateContextType;\n  return <MapViewStateContext.Provider value={value}>{children}</MapViewStateContext.Provider>;\n};\n"
  },
  {
    "path": "src/components/src/maps-layout.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\nimport React from 'react';\n\nimport {MapState} from '@kepler.gl/types';\n\nimport {MapViewStateContextProvider} from './map-view-state-context';\n\nconst Outer = styled.div`\n  position: relative;\n  display: flex;\n  width: 100%;\n  height: 100%;\n`;\n\nMapsLayoutFactory.deps = [];\n\ninterface MapsLayoutProps {\n  mapState: MapState;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nexport default function MapsLayoutFactory(): React.ComponentType<MapsLayoutProps> {\n  class MapsLayout extends React.Component<MapsLayoutProps> {\n    render() {\n      return (\n        <Outer className={this.props.className}>\n          <MapViewStateContextProvider mapState={this.props.mapState}>\n            {this.props.children}\n          </MapViewStateContextProvider>\n        </Outer>\n      );\n    }\n  }\n  return MapsLayout;\n}\n"
  },
  {
    "path": "src/components/src/modal-container.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {css} from 'styled-components';\nimport get from 'lodash/get';\nimport document from 'global/document';\n\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {\n  exportData,\n  getFileFormatNames,\n  getFileExtensions,\n  MapStyle,\n  ProviderState\n} from '@kepler.gl/reducers';\nimport {exportHtml, exportMap, exportJson, exportImage} from '@kepler.gl/utils';\n\nimport ModalDialogFactory from './modals/modal-dialog';\n\n// modals\nimport DeleteDatasetModalFactory from './modals/delete-data-modal';\nimport OverWriteMapModalFactory from './modals/overwrite-map-modal';\nimport DataTableModalFactory from './modals/data-table-modal';\nimport LoadDataModalFactory from './modals/load-data-modal';\nimport ExportImageModalFactory from './modals/export-image-modal';\nimport ExportDataModalFactory from './modals/export-data-modal';\nimport ExportMapModalFactory from './modals/export-map-modal/export-map-modal';\nimport AddMapStyleModalFactory from './modals/add-map-style-modal';\nimport SaveMapModalFactory from './modals/save-map-modal';\nimport ShareMapModalFactory from './modals/share-map-modal';\n\n// Breakpoints\nimport {media} from '@kepler.gl/styles';\n\n// Template\nimport {\n  EXPORT_DATA_TYPE_OPTIONS,\n  EXPORT_MAP_FORMATS,\n  KeyEvent,\n  ADD_DATA_ID,\n  DATA_TABLE_ID,\n  DELETE_DATA_ID,\n  EXPORT_DATA_ID,\n  EXPORT_IMAGE_ID,\n  EXPORT_MAP_ID,\n  ADD_MAP_STYLE_ID,\n  SAVE_MAP_ID,\n  SHARE_MAP_ID,\n  OVERWRITE_MAP_ID\n} from '@kepler.gl/constants';\n\nimport {MapState, UiState, OnSuccessCallBack, OnErrorCallBack} from '@kepler.gl/types';\n\nimport {\n  VisStateActions,\n  UIStateActions,\n  MapStyleActions,\n  ProviderActions\n} from '@kepler.gl/actions';\nimport {ModalDialogProps} from './common/modal';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {VisState} from '@kepler.gl/schemas';\n\nconst DataTableModalStyle = css`\n  top: 70px;\n  padding: 0;\n  width: 90vw;\n  max-width: 90vw;\n\n  ${media.portable`\n    padding: 0;\n  `} ${media.palm`\n    padding: 0;\n    margin: 0 auto;\n  `};\n`;\nconst smallModalCss = css`\n  width: 40%;\n  padding: 40px 40px 32px 40px;\n`;\n\nconst LoadDataModalStyle = css`\n  top: 60px;\n`;\n\nconst DefaultStyle = css`\n  max-width: 960px;\n`;\n\nexport type ModalContainerProps = {\n  appName: string;\n  rootNode: React.ReactInstance | null | undefined;\n  containerW: number;\n  containerH: number;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl?: string;\n  mapState: MapState;\n  mapStyle: MapStyle;\n  uiState: UiState;\n  visState: VisState;\n  providerState: ProviderState;\n  visStateActions: typeof VisStateActions;\n  uiStateActions: typeof UIStateActions;\n  mapStyleActions: typeof MapStyleActions;\n  providerActions: typeof ProviderActions;\n  onSaveToStorage?: () => void;\n  cloudProviders: Provider[];\n  onLoadCloudMapSuccess?: OnSuccessCallBack;\n  onLoadCloudMapError?: OnErrorCallBack;\n  onExportToCloudSuccess?: OnSuccessCallBack;\n  onExportToCloudError?: OnErrorCallBack;\n};\n\nModalContainerFactory.deps = [\n  DeleteDatasetModalFactory,\n  OverWriteMapModalFactory,\n  DataTableModalFactory,\n  LoadDataModalFactory,\n  ExportImageModalFactory,\n  ExportDataModalFactory,\n  ExportMapModalFactory,\n  AddMapStyleModalFactory,\n  ModalDialogFactory,\n  SaveMapModalFactory,\n  ShareMapModalFactory\n];\n\nexport default function ModalContainerFactory(\n  DeleteDatasetModal: ReturnType<typeof DeleteDatasetModalFactory>,\n  OverWriteMapModal: ReturnType<typeof OverWriteMapModalFactory>,\n  DataTableModal: ReturnType<typeof DataTableModalFactory>,\n  LoadDataModal: ReturnType<typeof LoadDataModalFactory>,\n  ExportImageModal: ReturnType<typeof ExportImageModalFactory>,\n  ExportDataModal: ReturnType<typeof ExportDataModalFactory>,\n  ExportMapModal: ReturnType<typeof ExportMapModalFactory>,\n  AddMapStyleModal: ReturnType<typeof AddMapStyleModalFactory>,\n  ModalDialog: ReturnType<typeof ModalDialogFactory>,\n  SaveMapModal: ReturnType<typeof SaveMapModalFactory>,\n  ShareMapModal: ReturnType<typeof ShareMapModalFactory>\n): React.ElementType<ModalContainerProps> {\n  /** @augments React.Component<ModalContainerProps> */\n  class ModalContainer extends Component<ModalContainerProps> {\n    // TODO - remove when prop types are fully exported\n    componentDidMount = () => {\n      document.addEventListener('keyup', this._onKeyUp);\n    };\n    componentWillUnmount() {\n      document.removeEventListener('keyup', this._onKeyUp);\n    }\n\n    _onKeyUp = event => {\n      const keyCode = event.keyCode;\n      if (keyCode === KeyEvent.DOM_VK_ESCAPE) {\n        this._closeModal();\n      }\n    };\n\n    _closeModal = () => {\n      this.props.uiStateActions.toggleModal(null);\n    };\n\n    _deleteDataset = key => {\n      this.props.visStateActions.removeDataset(key);\n      this._closeModal();\n    };\n\n    _onAddCustomMapStyle = () => {\n      this.props.mapStyleActions.addCustomMapStyle();\n      this._closeModal();\n    };\n\n    _onFileUpload = fileList => {\n      this.props.visStateActions.loadFiles(fileList);\n    };\n\n    _onTilesetAdded = (\n      tileset: {name: string; type: string; metadata: Record<string, any>},\n      processedMetadata?: Record<string, any>\n    ) => {\n      this.props.visStateActions.updateVisData(\n        {\n          info: {label: tileset.name, type: tileset.type, format: 'rows'},\n          data: {\n            fields: processedMetadata?.fields || [],\n            rows: []\n          },\n          metadata: {\n            ...processedMetadata,\n            ...tileset.metadata\n          },\n          // Vector tile layer supports GPU filtering for numeric and boolean fields\n          supportedFilterTypes: [\n            ALL_FIELD_TYPES.real,\n            ALL_FIELD_TYPES.integer,\n            ALL_FIELD_TYPES.boolean\n          ],\n          disableDataOperation: true\n        },\n        {\n          autoCreateLayers: true,\n          centerMap: true\n        }\n      );\n      this._closeModal();\n    };\n\n    _onExportImage = () => {\n      if (!this.props.uiState.exportImage.processing) {\n        exportImage(this.props.uiState.exportImage, `${this.props.appName}.png`);\n        this.props.uiStateActions.cleanupExportImage();\n        this._closeModal();\n      }\n    };\n\n    _onExportData = () => {\n      exportData(this.props, this.props.uiState.exportData);\n      this._closeModal();\n    };\n\n    _onExportMap = () => {\n      const {uiState} = this.props;\n      const {format} = uiState.exportMap;\n      (format === EXPORT_MAP_FORMATS.HTML ? exportHtml : exportJson)(\n        this.props,\n        this.props.uiState.exportMap[format] || {}\n      );\n      this._closeModal();\n    };\n\n    _exportFileToCloud = ({provider, isPublic, overwrite, closeModal}) => {\n      const toSave = exportMap(this.props);\n\n      this.props.providerActions.exportFileToCloud({\n        mapData: toSave,\n        provider,\n        options: {\n          isPublic,\n          overwrite,\n          mapIdToOverwrite: this.props.providerState.savedMapId\n        },\n        closeModal,\n        onSuccess: this.props.onExportToCloudSuccess,\n        onError: this.props.onExportToCloudError\n      });\n    };\n\n    _onSaveMap = (provider, overwrite = false) => {\n      this._exportFileToCloud({\n        provider,\n        isPublic: false,\n        overwrite,\n        closeModal: true\n      });\n    };\n\n    _onOverwriteMap = provider => {\n      this._onSaveMap(provider, true);\n    };\n\n    _onShareMapUrl = provider => {\n      this._exportFileToCloud({provider, isPublic: true, overwrite: false, closeModal: false});\n    };\n\n    _onCloseSaveMap = () => {\n      this.props.providerActions.resetProviderStatus();\n      this._closeModal();\n    };\n\n    _onLoadCloudMap = payload => {\n      this.props.providerActions.loadCloudMap({\n        ...payload,\n        onSuccess: this.props.onLoadCloudMapSuccess,\n        onError: this.props.onLoadCloudMapError\n      });\n    };\n\n    /* eslint-disable complexity */\n    render() {\n      const {\n        containerW,\n        containerH,\n        mapStyle,\n        mapState,\n        uiState,\n        visState,\n        rootNode,\n        visStateActions,\n        uiStateActions,\n        providerState\n      } = this.props;\n      const {currentModal, datasetKeyToRemove} = uiState;\n      const {datasets, layers, editingDataset} = visState;\n\n      let template: JSX.Element | null = null;\n      let modalProps: Partial<ModalDialogProps> = {};\n\n      // TODO - currentModal is a string\n      // @ts-ignore\n      if (currentModal && currentModal.id && currentModal.template) {\n        // if currentMdoal template is already provided\n        // TODO: need to check whether template is valid\n        // @ts-ignore\n        template = <currentModal.template />;\n        // @ts-ignore\n        modalProps = currentModal.modalProps;\n      } else {\n        switch (currentModal) {\n          case DATA_TABLE_ID: {\n            const width = containerW * 0.9;\n            template = (\n              <DataTableModal\n                datasets={datasets}\n                dataId={editingDataset}\n                showDatasetTable={visStateActions.showDatasetTable}\n                sortTableColumn={visStateActions.sortTableColumn}\n                pinTableColumn={visStateActions.pinTableColumn}\n                copyTableColumn={visStateActions.copyTableColumn}\n                setColumnDisplayFormat={visStateActions.setColumnDisplayFormat}\n                uiStateActions={uiStateActions}\n                uiState={uiState}\n              />\n            );\n\n            // TODO: we need to make this width consistent with the css rule defined modal.js:32 max-width: 70vw\n            // @ts-ignore // TODO fix this after add types to Theme\n            modalProps.cssStyle = css`\n              ${DataTableModalStyle};\n              ${media.palm`\n                width: ${width}px;\n              `};\n            `;\n            break;\n          }\n          case DELETE_DATA_ID: {\n            // validate options\n            if (datasetKeyToRemove && datasets && datasets[datasetKeyToRemove]) {\n              template = (\n                <DeleteDatasetModal dataset={datasets[datasetKeyToRemove]} layers={layers} />\n              );\n              modalProps = {\n                title: 'modal.title.deleteDataset',\n                cssStyle: smallModalCss,\n                footer: true,\n                onConfirm: () => this._deleteDataset(datasetKeyToRemove),\n                onCancel: this._closeModal,\n                confirmButton: {\n                  negative: true,\n                  large: true,\n                  children: 'modal.button.delete'\n                }\n              };\n            }\n            break; // in case we add a new case after this one\n          }\n          case ADD_DATA_ID:\n            template = (\n              <LoadDataModal\n                {...providerState}\n                onClose={this._closeModal}\n                onFileUpload={this._onFileUpload}\n                onTilesetAdded={this._onTilesetAdded}\n                onLoadCloudMap={this._onLoadCloudMap}\n                loadFiles={uiState.loadFiles}\n                fileLoading={visState.fileLoading}\n                fileLoadingProgress={visState.fileLoadingProgress}\n                fileFormatNames={getFileFormatNames(this.props.visState)}\n                fileExtensions={getFileExtensions(this.props.visState)}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.addDataToMap',\n              cssStyle: LoadDataModalStyle,\n              footer: false,\n              onConfirm: this._closeModal\n            };\n            break;\n          case EXPORT_IMAGE_ID:\n            template = (\n              <ExportImageModal\n                exportImage={uiState.exportImage}\n                mapW={containerW}\n                mapH={containerH}\n                onUpdateImageSetting={uiStateActions.setExportImageSetting}\n                cleanupExportImage={uiStateActions.cleanupExportImage}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.exportImage',\n              cssStyle: '',\n              footer: true,\n              onCancel: this._closeModal,\n              onConfirm: this._onExportImage,\n              confirmButton: {\n                large: true,\n                disabled: uiState.exportImage.processing,\n                children: 'modal.button.download'\n              }\n            };\n            break;\n          case EXPORT_DATA_ID:\n            template = (\n              <ExportDataModal\n                {...uiState.exportData}\n                supportedDataTypes={EXPORT_DATA_TYPE_OPTIONS}\n                datasets={datasets}\n                applyCPUFilter={this.props.visStateActions.applyCPUFilter}\n                onChangeExportDataType={uiStateActions.setExportDataType}\n                onChangeExportSelectedDataset={uiStateActions.setExportSelectedDataset}\n                onChangeExportFiltered={uiStateActions.setExportFiltered}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.exportData',\n              cssStyle: '',\n              footer: true,\n              onCancel: this._closeModal,\n              onConfirm: this._onExportData,\n              confirmButton: {\n                large: true,\n                children: 'modal.button.export'\n              }\n            };\n            break;\n          case EXPORT_MAP_ID: {\n            const keplerGlConfig = visState.schema.getConfigToSave({\n              mapStyle,\n              visState,\n              mapState,\n              uiState\n            });\n            template = (\n              <ExportMapModal\n                config={keplerGlConfig}\n                options={uiState.exportMap}\n                onChangeExportMapFormat={uiStateActions.setExportMapFormat}\n                onEditUserMapboxAccessToken={uiStateActions.setUserMapboxAccessToken}\n                onChangeExportMapHTMLMode={uiStateActions.setExportHTMLMapMode}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.exportMap',\n              cssStyle: '',\n              footer: true,\n              onCancel: this._closeModal,\n              onConfirm: this._onExportMap,\n              confirmButton: {\n                large: true,\n                children: 'modal.button.export'\n              }\n            };\n            break;\n          }\n          case ADD_MAP_STYLE_ID:\n            template = (\n              <AddMapStyleModal\n                mapboxApiAccessToken={this.props.mapboxApiAccessToken}\n                mapboxApiUrl={this.props.mapboxApiUrl}\n                mapState={this.props.mapState}\n                inputStyle={mapStyle.inputStyle}\n                inputMapStyle={this.props.mapStyleActions.inputMapStyle}\n                loadCustomMapStyle={this.props.mapStyleActions.loadCustomMapStyle}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.addCustomMapboxStyle',\n              cssStyle: '',\n              footer: true,\n              onCancel: this._closeModal,\n              onConfirm: this._onAddCustomMapStyle,\n              confirmButton: {\n                large: true,\n                disabled:\n                  mapStyle.inputStyle.error ||\n                  !mapStyle.inputStyle.url ||\n                  !mapStyle.inputStyle.label,\n                children: 'modal.button.addStyle'\n              }\n            };\n            break;\n          case SAVE_MAP_ID:\n            template = (\n              <SaveMapModal\n                {...providerState}\n                exportImage={uiState.exportImage}\n                mapInfo={visState.mapInfo}\n                onSetMapInfo={visStateActions.setMapInfo}\n                cleanupExportImage={uiStateActions.cleanupExportImage}\n                onUpdateImageSetting={uiStateActions.setExportImageSetting}\n                onCancel={this._closeModal}\n                onConfirm={provider => this._onSaveMap(provider, false)}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.saveMap',\n              cssStyle: '',\n              footer: false\n            };\n            break;\n          case OVERWRITE_MAP_ID:\n            template = (\n              <OverWriteMapModal\n                {...providerState}\n                title={get(visState, ['mapInfo', 'title'])}\n                onUpdateImageSetting={uiStateActions.setExportImageSetting}\n                cleanupExportImage={uiStateActions.cleanupExportImage}\n                onConfirm={this._onOverwriteMap}\n                onCancel={this._closeModal}\n              />\n            );\n            modalProps = {\n              title: 'Overwrite Existing File?',\n              cssStyle: smallModalCss,\n              footer: false\n            };\n            break;\n          case SHARE_MAP_ID:\n            template = (\n              <ShareMapModal\n                {...providerState}\n                onExport={this._onShareMapUrl}\n                cleanupExportImage={uiStateActions.cleanupExportImage}\n                onUpdateImageSetting={uiStateActions.setExportImageSetting}\n              />\n            );\n            modalProps = {\n              title: 'modal.title.shareURL',\n              cssStyle: '',\n              onCancel: this._onCloseSaveMap\n            };\n            break;\n          default:\n            break;\n        }\n      }\n\n      return rootNode ? (\n        <ModalDialog\n          parentSelector={() => rootNode as HTMLElement}\n          isOpen={Boolean(currentModal)}\n          onCancel={this._closeModal}\n          {...modalProps}\n          cssStyle={DefaultStyle.concat(modalProps.cssStyle)}\n        >\n          {template}\n        </ModalDialog>\n      ) : null;\n    }\n    /* eslint-enable complexity */\n  }\n\n  return ModalContainer;\n}\n"
  },
  {
    "path": "src/components/src/modals/add-map-style-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {polyfill} from 'react-lifecycles-compat';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\nimport {Map, MapboxMap, MapRef} from 'react-map-gl';\nimport {\n  StyledModalContent,\n  InputLight,\n  StyledMapContainer,\n  StyledModalVerticalPanel,\n  StyledModalSection\n} from '../common/styled-components';\nimport {media} from '@kepler.gl/styles';\n\n// Utils\nimport {getApplicationConfig, getBaseMapLibrary, transformRequest} from '@kepler.gl/utils';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {NO_BASEMAP_ICON} from '@kepler.gl/constants';\nimport {InputStyle, MapState} from '@kepler.gl/types';\nimport {ActionHandler, inputMapStyle, loadCustomMapStyle} from '@kepler.gl/actions';\n\nconst MapH = 190;\nconst MapW = 264;\nconst ErrorMsg = {\n  styleError:\n    'Failed to load map style, make sure it is published. For private style, paste in your access token.'\n};\n\nconst PreviewMap = styled.div`\n  align-items: center;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  margin-left: 116px;\n  flex-shrink: 0;\n\n  .preview-title {\n    font-weight: 500;\n    font-size: 10px;\n    padding: 8px 0px;\n  }\n\n  .preview-title.error {\n    color: ${props => props.theme.errorColor};\n    max-width: 250px;\n  }\n\n  ${media.portable`\n    margin-left: 32px;\n  `};\n\n  ${media.palm`\n    margin-left: unset;\n    .preview-title {\n      margin-top: 0px;\n    }\n  `};\n`;\n\nconst StyledPreviewImage = styled.div`\n  background: ${props => props.theme.modalImagePlaceHolder};\n  border-radius: 4px;\n  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.18);\n  width: ${MapW}px;\n  height: ${MapH}px;\n  position: relative;\n\n  .preview-image-placeholder {\n    position: absolute;\n    top: 0;\n    left: 0;\n  }\n\n  .preview-image-spinner {\n    position: absolute;\n    left: calc(50% - 25px);\n    top: calc(50% - 25px);\n  }\n`;\n\nconst InlineLink = styled.a`\n  font-weight: 500;\n\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nconst nop = () => {\n  return;\n};\n\ninterface AddMapStyleModalProps {\n  inputMapStyle: ActionHandler<typeof inputMapStyle>;\n  inputStyle: InputStyle;\n  loadCustomMapStyle: ActionHandler<typeof loadCustomMapStyle>;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl?: string;\n  transformRequest?: (mapboxKey: string) => (\n    url: string,\n    resourceType: string\n  ) => {\n    url: string;\n  };\n  mapState: MapState;\n  intl: IntlShape;\n}\n\nfunction AddMapStyleModalFactory() {\n  class AddMapStyleModal extends Component<AddMapStyleModalProps> {\n    state = {\n      reRenderKey: 0,\n      previousToken: null\n    };\n\n    static getDerivedStateFromProps(props, state) {\n      if (\n        props.inputStyle &&\n        props.inputStyle.accessToken &&\n        props.inputStyle.accessToken !== state.previousToken\n      ) {\n        // toke has changed\n        // ReactMapGl doesn't re-create map when token has changed\n        // here we force the map to update\n\n        return {\n          reRenderKey: state.reRenderKey + 1,\n          previousToken: props.inputStyle.accessToken\n        };\n      }\n\n      return null;\n    }\n\n    _map: MapboxMap | undefined | null;\n\n    _setMapRef = (mapRef: MapRef) => {\n      // Handle change of the basemap library\n      if (this._map && mapRef) {\n        const map = mapRef.getMap();\n        if (map && this._map !== map) {\n          this._map.off('style.load', nop);\n          this._map.off('error', nop);\n          this._map = null;\n        }\n      }\n\n      const map = mapRef && mapRef.getMap();\n      if (map && this._map !== map) {\n        this._map = map;\n\n        map.on('style.load', () => {\n          const style = map.getStyle();\n          this.loadMapStyleJson(style);\n        });\n\n        map.on('error', () => {\n          this.loadMapStyleError();\n        });\n      }\n    };\n\n    loadMapStyleJson = style => {\n      this.props.loadCustomMapStyle({style, error: false});\n    };\n\n    loadMapStyleError = () => {\n      this.props.loadCustomMapStyle({error: true});\n    };\n\n    render() {\n      const {inputStyle, mapState, intl} = this.props;\n\n      const baseMapLibraryName = getBaseMapLibrary(inputStyle);\n      const baseMapLibraryConfig = getApplicationConfig().baseMapLibraryConfig[baseMapLibraryName];\n\n      const mapboxApiAccessToken = inputStyle.accessToken || this.props.mapboxApiAccessToken;\n      const mapProps = {\n        ...mapState,\n        // TODO baseApiUrl should be taken into account in transformRequest as we use dynamic mapLib import\n        // baseApiUrl: mapboxApiUrl,\n        mapboxAccessToken: mapboxApiAccessToken,\n        mapLib: baseMapLibraryConfig.getMapLib(),\n        preserveDrawingBuffer: true,\n        transformRequest:\n          this.props.transformRequest?.(mapboxApiAccessToken) ||\n          transformRequest(mapboxApiAccessToken)\n      };\n\n      return (\n        <div className=\"add-map-style-modal\">\n          <StyledModalContent>\n            <StyledModalVerticalPanel>\n              <StyledModalSection>\n                <div className=\"modal-section-title\">\n                  <FormattedMessage id={'modal.addStyle.pasteTitle'} />\n                </div>\n                <div className=\"modal-section-subtitle\">\n                  {intl.formatMessage({id: 'modal.addStyle.pasteSubtitle0'})}\n                  <InlineLink\n                    target=\"_blank\"\n                    href=\"https://www.mapbox.com/help/studio-manual-publish/#style-url\"\n                  >\n                    {' '}\n                    {intl.formatMessage({id: 'modal.addStyle.pasteSubtitle2'})}\n                  </InlineLink>{' '}\n                  {intl.formatMessage({id: 'modal.addStyle.pasteSubtitle3'})}\n                  <InlineLink\n                    target=\"_blank\"\n                    href=\"https://docs.mapbox.com/mapbox-gl-js/style-spec\"\n                  >\n                    {' '}\n                    {intl.formatMessage({id: 'modal.addStyle.pasteSubtitle4'})}\n                  </InlineLink>\n                </div>\n                <InputLight\n                  type=\"text\"\n                  value={inputStyle.url || ''}\n                  onChange={({target: {value}}) =>\n                    this.props.inputMapStyle({\n                      url: value,\n                      id: 'Custom Style',\n                      icon: `${getApplicationConfig().cdnUrl}/${NO_BASEMAP_ICON}`\n                    })\n                  }\n                  placeholder=\"e.g. https://basemaps.cartocdn.com/gl/positron-gl-style/style.json\"\n                />\n              </StyledModalSection>\n\n              <StyledModalSection>\n                <div className=\"modal-section-title\">\n                  <FormattedMessage id={'modal.addStyle.publishTitle'} />\n                </div>\n                <div className=\"modal-section-subtitle\">\n                  {intl.formatMessage({id: 'modal.addStyle.publishSubtitle1'})}\n                  <InlineLink target=\"_blank\" href=\"https://www.mapbox.com/studio/styles/\">\n                    {' '}\n                    mapbox\n                  </InlineLink>{' '}\n                  {intl.formatMessage({id: 'modal.addStyle.publishSubtitle2'})}\n                  <InlineLink\n                    target=\"_blank\"\n                    href=\"https://www.mapbox.com/help/studio-manual-publish/\"\n                  >\n                    {' '}\n                    {intl.formatMessage({id: 'modal.addStyle.publishSubtitle3'})}\n                  </InlineLink>{' '}\n                  {intl.formatMessage({id: 'modal.addStyle.publishSubtitle4'})}\n                </div>\n\n                <div className=\"modal-section-subtitle\">\n                  {intl.formatMessage({id: 'modal.addStyle.publishSubtitle5'})}\n                  <InlineLink\n                    target=\"_blank\"\n                    href=\"https://www.mapbox.com/help/how-access-tokens-work/\"\n                  >\n                    {' '}\n                    {intl.formatMessage({id: 'modal.addStyle.publishSubtitle6'})}\n                  </InlineLink>{' '}\n                  {intl.formatMessage({id: 'modal.addStyle.publishSubtitle7'})}\n                </div>\n                <InputLight\n                  type=\"text\"\n                  value={inputStyle.accessToken || ''}\n                  onChange={({target: {value}}) => this.props.inputMapStyle({accessToken: value})}\n                  placeholder={intl.formatMessage({id: 'modal.addStyle.exampleToken'})}\n                />\n              </StyledModalSection>\n\n              <StyledModalSection>\n                <div className=\"modal-section-title\">\n                  <FormattedMessage id={'modal.addStyle.namingTitle'} />\n                </div>\n                <InputLight\n                  type=\"text\"\n                  value={inputStyle.label || ''}\n                  onChange={({target: {value}}) => this.props.inputMapStyle({label: value})}\n                  placeholder=\"Name your style\"\n                />\n              </StyledModalSection>\n            </StyledModalVerticalPanel>\n            <PreviewMap>\n              <div\n                className={classnames('preview-title', {\n                  error: inputStyle.error\n                })}\n              >\n                {inputStyle.error\n                  ? ErrorMsg.styleError\n                  : (inputStyle.style && inputStyle.style.name) || ''}\n              </div>\n              <StyledPreviewImage className=\"preview-image\">\n                {/** Note, we need the Map to render with errored params to get style.error messages */}\n                {!inputStyle.isValid ? (\n                  <div className=\"preview-image-spinner\" />\n                ) : (\n                  <StyledMapContainer>\n                    <Map\n                      {...mapProps}\n                      ref={this._setMapRef}\n                      key={`${baseMapLibraryName}-${this.state.reRenderKey}-${inputStyle.url}-${mapboxApiAccessToken}`}\n                      style={{\n                        width: MapW,\n                        height: MapH\n                      }}\n                      mapStyle={inputStyle.url === null ? undefined : inputStyle.url}\n                    />\n                  </StyledMapContainer>\n                )}\n              </StyledPreviewImage>\n            </PreviewMap>\n          </StyledModalContent>\n        </div>\n      );\n    }\n  }\n\n  return injectIntl(polyfill(AddMapStyleModal));\n}\n\nexport default AddMapStyleModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/cloud-header.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport {Button} from '../../common/styled-components';\nimport {ArrowLeft} from '../../common/icons';\nimport InfoHelperFactory from '../../common/info-helper';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport styled from 'styled-components';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\nconst StyledStorageHeader = styled.div`\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: 16px;\n  font-size: 12px;\n  line-height: 14px;\n`;\n\nconst StyledBackBtn = styled.a`\n  margin-bottom: 16px;\n  color: #3a414c;\n  cursor: pointer;\n\n  &:hover {\n    font-weight: 500;\n  }\n`;\n\nconst LINK_STYLE = {textDecoration: 'underline'};\n\nconst Title = styled.span`\n  display: flex;\n  font-size: 14px;\n  line-height: 16px;\n  font-weight: 500;\n  margin-bottom: 16px;\n\n  span {\n    text-transform: capitalize;\n  }\n`;\n\ntype CloudHeaderProps = {\n  provider: Provider;\n  onBack: () => void;\n};\n\nCloudHeaderFactory.deps = [InfoHelperFactory];\n\nfunction CloudHeaderFactory(InfoHelper: ReturnType<typeof InfoHelperFactory>) {\n  const CloudHeader: React.FC<CloudHeaderProps> = ({provider, onBack}) => {\n    const managementUrl = useMemo(() => provider.getManagementUrl(), [provider]);\n    return (\n      <div data-testid={dataTestIds.cloudHeader}>\n        <StyledStorageHeader>\n          <StyledBackBtn>\n            <Button link onClick={onBack}>\n              <ArrowLeft height=\"14px\" />\n              <FormattedMessage id={'modal.loadStorageMap.back'} />\n            </Button>\n          </StyledBackBtn>\n          {managementUrl && (\n            <a\n              key={1}\n              href={managementUrl}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              style={LINK_STYLE}\n            >\n              {provider.displayName}\n            </a>\n          )}\n        </StyledStorageHeader>\n        <Title>\n          <div>\n            <span>{provider.displayName}</span>{' '}\n            <FormattedMessage id={'modal.loadStorageMap.storageMaps'} />\n          </div>\n          {provider.storageMessage ? (\n            <InfoHelper\n              id={`cloud-provider-storageMessage`}\n              description={provider.storageMessage}\n            />\n          ) : null}\n        </Title>\n      </div>\n    );\n  };\n  return CloudHeader;\n}\n\nexport default CloudHeaderFactory;\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/cloud-item.spec.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport React from 'react';\nimport {fireEvent} from '@testing-library/react';\nimport {renderWithTheme} from '../../../../../test/helpers/component-jest-utils';\n\nimport {CloudItem} from './cloud-item';\nimport moment from 'moment';\nconst nop = () => {\n  return;\n};\ndescribe('CloudItem', () => {\n  const mockVis = {\n    title: 'Test Title',\n    description: 'Test Description',\n    lastModification: new Date().toISOString(),\n    thumbnail: 'test-thumbnail.jpg',\n    privateMap: true\n  };\n\n  it('renders without crashing', () => {\n    const {getByText} = renderWithTheme(<CloudItem vis={mockVis} onClick={nop} />);\n    expect(getByText('Test Title')).toBeInTheDocument();\n  });\n\n  it('renders PrivacyBadge for private maps', () => {\n    const {getByText} = renderWithTheme(\n      <CloudItem vis={{...mockVis, privateMap: true}} onClick={nop} />\n    );\n    expect(getByText('Private')).toBeInTheDocument();\n  });\n\n  it('does not render PrivacyBadge for public maps', () => {\n    const {queryByText} = renderWithTheme(\n      <CloudItem vis={{...mockVis, privateMap: false}} onClick={nop} />\n    );\n    expect(queryByText('Private')).toBeNull();\n  });\n\n  it('displays correct thumbnail image', () => {\n    const {getByRole} = renderWithTheme(<CloudItem vis={mockVis} onClick={nop} />);\n    expect(getByRole('thumbnail-wrapper').style.backgroundImage).toContain('test-thumbnail.jpg');\n  });\n\n  it('displays MapIcon when no thumbnail is provided', () => {\n    const {getByRole} = renderWithTheme(\n      <CloudItem vis={{...mockVis, thumbnail: null}} onClick={nop} />\n    );\n    expect(getByRole('map-icon')).toBeInTheDocument();\n  });\n\n  it('displays title, description, and last modification date', () => {\n    const {getByText} = renderWithTheme(<CloudItem vis={mockVis} onClick={nop} />);\n    expect(getByText('Test Title')).toBeInTheDocument();\n    expect(getByText('Test Description')).toBeInTheDocument();\n    expect(\n      getByText(`Last modified ${moment.utc(mockVis.lastModification).fromNow()}`)\n    ).toBeInTheDocument();\n  });\n\n  it('calls onClick when component is clicked', () => {\n    const onClickMock = jest.fn();\n    const {getByText} = renderWithTheme(<CloudItem vis={mockVis} onClick={onClickMock} />);\n    fireEvent.click(getByText('Test Title'));\n    expect(onClickMock).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/cloud-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport moment from 'moment/moment';\nimport React from 'react';\nimport styled from 'styled-components';\nimport {Base} from '../../common/icons';\n\nconst MapIcon = props => {\n  return (\n    <div {...props}>\n      {props.children}\n      <Base height=\"32px\" viewBox={'0 0 16 16'}>\n        <path\n          fill=\"#d3d8d6\"\n          d=\"m13.6 11.572-3.2 2.1336v-9.2776l3.2-2.1336zm-12-7.144 3.2-2.1336v9.2776l-3.2 2.1336zm13.244 8.2376c0.2224-0.148 0.356-0.3984 0.356-0.6656v-11.2c0-0.2952-0.1624-0.5664-0.4224-0.7048-0.26-0.14-0.576-0.1248-0.8216 0.0392l-4.3128 2.876-3.5432-2.8352c-0.1208-0.0936-0.2952-0.1624-0.472-0.1688-0.1648-0.0064-0.348 0.0464-0.472 0.128l-4.8 3.2c-0.2224 0.1488-0.356 0.3984-0.356 0.6656v11.2c0 0.2952 0.1624 0.5664 0.4224 0.7056 0.1184 0.0632 0.248 0.0944 0.3776 0.0944 0.1552 0 0.3096-0.0448 0.444-0.1344l4.3128-2.876 3.5432 2.8352c0.1448 0.116 0.3216 0.1752 0.5 0.1752 0.1184 0 0.236-0.0248 0.3464-0.0784z\"\n        />\n      </Base>\n    </div>\n  );\n};\n\nconst PrivacyBadge = ({privateMap}) => (\n  <span className=\"vis_item-privacy\">{privateMap ? 'Private' : 'Public'}</span>\n);\n\nconst StyledVisualizationItem = styled.div`\n  flex: 0 0 auto;\n  width: 208px;\n  display: flex;\n  flex-direction: column;\n  padding: 16px 8px;\n  color: #3a414c;\n  cursor: pointer;\n  font-size: 12px;\n  line-height: 18px;\n  border: 1px solid transparent;\n\n  &:hover {\n    .vis_item-icon,\n    .vis_item-thumb,\n    .vis_item-description,\n    .vis_item-modification-date {\n      opacity: 1;\n    }\n    border: 1px solid #bbbbbb;\n  }\n\n  .vis_item-icon,\n  .vis_item-thumb,\n  .vis_item-description,\n  .vis_item-modification-date {\n    opacity: 0.9;\n    transition: opacity 0.4s ease;\n  }\n\n  .vis_item-icon {\n    position: relative;\n    flex: 0 0 108px;\n    background-color: #6a7484;\n    border-radius: 4px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .vis_item-thumb {\n    position: relative;\n    flex: 0 0 108px;\n    background-size: cover;\n    background-position: center;\n    border-radius: 4px;\n  }\n\n  .vis_item-privacy {\n    position: absolute;\n    top: 0;\n    left: 0;\n    padding: 3px 6px;\n    border-radius: 4px 0;\n    background-color: rgba(58, 65, 76, 0.7);\n    color: #fff;\n    font-size: 11px;\n    line-height: 18px;\n  }\n\n  .vis_item-title {\n    margin-top: 16px;\n    font-weight: 500;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  .vis_item-description {\n    flex: 1 1 auto;\n    margin-top: 8px;\n  }\n\n  .vis_item-modification-date {\n    margin-top: 16px;\n    flex: 1 0 auto;\n    color: #6a7484;\n    line-height: 15px;\n  }\n`;\n\nexport const CloudItem = ({vis, onClick}) => {\n  const thumbnailStyle = {backgroundImage: `url(${vis.thumbnail})`};\n  return (\n    <StyledVisualizationItem onClick={onClick}>\n      {vis.thumbnail ? (\n        <div role=\"thumbnail-wrapper\" className=\"vis_item-thumb\" style={thumbnailStyle}>\n          {Object.prototype.hasOwnProperty.call(vis, 'privateMap') ? (\n            <PrivacyBadge privateMap={vis.privateMap} />\n          ) : null}\n        </div>\n      ) : (\n        <MapIcon role=\"map-icon\" className=\"vis_item-icon\">\n          {Object.prototype.hasOwnProperty.call(vis, 'privateMap') ? (\n            <PrivacyBadge privateMap={vis.privateMap} />\n          ) : null}\n        </MapIcon>\n      )}\n      <span className=\"vis_item-title\">{vis.title}</span>\n      {vis.description?.length > 0 && (\n        <span className=\"vis_item-description\">{vis.description}</span>\n      )}\n      <span className=\"vis_item-modification-date\">\n        Last modified {moment.utc(vis.updatedAt).fromNow()}\n      </span>\n    </StyledVisualizationItem>\n  );\n};\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/cloud-maps.spec.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport React from 'react';\nimport {fireEvent} from '@testing-library/react';\nimport {renderWithTheme} from '../../../../../test/helpers/component-jest-utils';\nimport {CloudMaps} from './cloud-maps';\n\ndescribe('CloudMaps Component', () => {\n  it('renderWithThemes without crashing', () => {\n    const {getByText} = renderWithTheme(<CloudMaps isLoading={false} maps={[]} error={null} />);\n    expect(getByText(/noSavedMaps/i)).toBeInTheDocument();\n  });\n\n  it('displays error message when there is an error', () => {\n    const errorMessage = 'Test Error';\n    const {getByText} = renderWithTheme(\n      <CloudMaps isLoading={false} maps={[]} error={{message: errorMessage}} />\n    );\n    expect(getByText(`Error while fetching maps: ${errorMessage}`)).toBeInTheDocument();\n  });\n\n  it('displays loading spinner when isLoading is true', () => {\n    const {getByText} = renderWithTheme(<CloudMaps isLoading={true} maps={[]} error={null} />);\n    expect(getByText('modal.loadingDialog.loading')).toBeInTheDocument(); // Ensure your spinner has 'data-testid=\"loading-spinner\"'\n  });\n\n  it('renderWithThemes correct number of CloudItems based on maps prop', () => {\n    const mockMaps = [\n      {id: 1, title: 'map'},\n      {id: 2, title: 'map'},\n      {id: 3, title: 'map'}\n    ];\n    const {getAllByText} = renderWithTheme(\n      <CloudMaps isLoading={false} maps={mockMaps} error={null} />\n    );\n    expect(getAllByText('map')).toHaveLength(mockMaps.length); // Ensure your CloudItem has 'data-testid=\"cloud-item\"'\n  });\n\n  it('displays message when there are no maps', () => {\n    const {getByText} = renderWithTheme(<CloudMaps isLoading={false} maps={[]} error={null} />);\n    expect(getByText(/noSavedMaps/i)).toBeInTheDocument();\n  });\n\n  it('calls onSelectMap when a CloudItem is clicked', () => {\n    const mockMaps = [\n      {id: 1, title: 'map'},\n      {id: 2, title: 'map'},\n      {id: 3, title: 'map'}\n    ];\n    const onSelectMap = jest.fn();\n    const provider = 'testProvider';\n    const {getAllByText} = renderWithTheme(\n      <CloudMaps\n        provider={provider}\n        onSelectMap={onSelectMap}\n        isLoading={false}\n        maps={mockMaps}\n        error={null}\n      />\n    );\n\n    const firstItem = getAllByText('map')[0];\n    fireEvent.click(firstItem);\n    expect(onSelectMap).toHaveBeenCalledWith(provider, mockMaps[0]);\n  });\n});\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/cloud-maps.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport LoadingDialog from '../loading-dialog';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {CloudItem} from './cloud-item';\nimport {FlexContainer} from '../../common/flex-container';\n\nconst StyledSpinner = styled.div`\n  text-align: center;\n  span {\n    margin: 0 auto;\n  }\n`;\n\nconst StyledFlexContainer = styled(FlexContainer)`\n  justify-content: flex-start;\n  flex-wrap: wrap;\n`;\n\nexport const CloudMaps = ({provider, onSelectMap, isLoading, maps, error}) => {\n  if (error) {\n    return <div>Error while fetching maps: {error.message}</div>;\n  }\n\n  if (isLoading) {\n    return (\n      <StyledSpinner>\n        <LoadingDialog size={64} />\n      </StyledSpinner>\n    );\n  }\n\n  return (\n    <StyledFlexContainer>\n      {(maps ?? []).length ? (\n        maps.map(vis => (\n          <CloudItem key={vis.id} onClick={() => onSelectMap(provider, vis)} vis={vis} />\n        ))\n      ) : (\n        <div className=\"visualization-list__message\">\n          <FormattedMessage id={'modal.loadStorageMap.noSavedMaps'} />\n        </div>\n      )}\n    </StyledFlexContainer>\n  );\n};\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/provider-loading.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport LoadingDialog from '../loading-dialog';\nimport styled from 'styled-components';\n\nconst StyledSpinner = styled.div`\n  text-align: center;\n  span {\n    margin: 0 auto;\n  }\n`;\n\nexport const ProviderLoading = () => {\n  return (\n    <StyledSpinner>\n      <LoadingDialog size={64} />\n    </StyledSpinner>\n  );\n};\n"
  },
  {
    "path": "src/components/src/modals/cloud-components/provider-select.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport CloudTile from '../cloud-tile';\nimport React from 'react';\nimport styled from 'styled-components';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {dataTestIds} from '@kepler.gl/constants';\n\nconst StyledProviderSection = styled.div.attrs({\n  className: 'provider-selection'\n})`\n  display: flex;\n  gap: 8px;\n`;\n\ntype ProviderSelectProps = {\n  cloudProviders: Provider[];\n};\n\nexport const ProviderSelect: React.FC<ProviderSelectProps> = ({cloudProviders = []}) => (\n  <div data-testid={dataTestIds.providerSelect}>\n    {cloudProviders.length ? (\n      <StyledProviderSection>\n        {cloudProviders.map(provider => (\n          <CloudTile key={provider.name} provider={provider} />\n        ))}\n      </StyledProviderSection>\n    ) : (\n      <p>No storage provider available</p>\n    )}\n  </div>\n);\n"
  },
  {
    "path": "src/components/src/modals/cloud-tile.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useState} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport {Logout, Login} from '../common/icons';\nimport {CenterVerticalFlexbox, Button, CheckMark} from '../common/styled-components';\nimport {Provider, CloudUser} from '@kepler.gl/cloud-providers';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {BaseComponentProps} from '../types';\n\nexport type StyledTileWrapperProps = BaseComponentProps & {\n  selected?: boolean;\n};\n\nconst StyledTileWrapper: IStyledComponent<'web', StyledTileWrapperProps> = styled.div.attrs({\n  className: 'provider-tile__wrapper'\n})<StyledTileWrapperProps>`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: flex-start;\n  border-radius: 2px;\n  border: 1px solid\n    ${props => (props.selected ? props.theme.primaryBtnBgd : props.theme.selectBorderColorLT)};\n  color: ${props => (props.selected ? props.theme.primaryBtnBgd : props.theme.selectBorderColorLT)};\n  cursor: pointer;\n  font-weight: 500;\n  width: 120px;\n  height: 168px;\n  background-color: #ffffff;\n  transition: ${props => props.theme.transition};\n  position: relative;\n  &:hover {\n    border: 1px solid ${props => props.theme.primaryBtnBgd};\n    color: ${props => props.theme.primaryBtnBgd};\n  }\n\n  .button {\n    margin-top: 20px;\n  }\n`;\n\nconst StyledBox = styled(CenterVerticalFlexbox)`\n  margin-right: 12px;\n  position: relative;\n`;\n\nconst StyledCloudName = styled.div`\n  font-size: 12px;\n  margin-top: 12px;\n  margin-bottom: 4px;\n`;\n\nconst StyledUserName = styled.div`\n  font-size: 11px;\n  margin-top: 8px;\n  text-align: center;\n  color: ${props => props.theme.primaryBtnActBgd};\n  overflow: hidden;\n  width: 100px;\n  text-overflow: ellipsis;\n`;\n\ninterface OnClickProps {\n  onClick?: React.MouseEventHandler<HTMLButtonElement>;\n}\n\nconst LoginButton = ({onClick}: OnClickProps) => (\n  <Button className=\"login-button\" link small onClick={onClick}>\n    <Login />\n    Login\n  </Button>\n);\n\nconst LogoutButton = ({onClick}: OnClickProps) => (\n  <Button className=\"logout-button\" link small onClick={onClick}>\n    <Logout />\n    Logout\n  </Button>\n);\n\nconst NewTag = styled.div`\n  width: 37px;\n  height: 19px;\n  display: flex;\n  align-content: center;\n  justify-content: center;\n  border-radius: 8px;\n  padding: 4px 8px;\n  background-color: #EDE9F9;\n  color: #8863F8;\n  position: absolute;\n  left: 35%;\n  top: -8px\n  z-index: 500;\n  font-size: 11px;\n  line-height: 10px;\n`;\n\nexport const StyledWarning = styled.span`\n  color: ${props => props.theme.errorColor};\n  font-weight: ${props => props.theme.selectFontWeightBold};\n`;\n\ninterface CloudTileProps {\n  actionName?: string | null;\n  // cloud provider class\n  provider: Provider;\n}\n\n/**\n * this component display a provider and allows users to select and set the current provider\n * @param provider\n * @param actionName\n * @constructor\n */\nconst CloudTile: React.FC<CloudTileProps> = ({provider, actionName}) => {\n  const [user, setUser] = useState<CloudUser | null>(null);\n  const [error, setError] = useState<Error | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const {provider: currentProvider, setProvider} = useCloudListProvider();\n  const isSelected = provider === currentProvider;\n\n  useEffect(() => {\n    if (!provider) {\n      return;\n    }\n    setError(null);\n    setIsLoading(true);\n    setError(null);\n    provider\n      .getUser()\n      .then(setUser)\n      .catch(setError)\n      .finally(() => {\n        setError(null);\n        setIsLoading(false);\n      });\n  }, [provider]);\n\n  const onLogin = useCallback(async () => {\n    setError(null);\n    setIsLoading(true);\n    try {\n      const user = await provider.login();\n      setUser(user);\n      setProvider(provider);\n    } catch (error) {\n      setError(error as Error);\n    }\n    setIsLoading(false);\n  }, [provider, setProvider]);\n\n  const onSelect = useCallback(async () => {\n    if (isLoading) {\n      return;\n    }\n    if (user) {\n      setProvider(provider);\n      return;\n    }\n    try {\n      await onLogin();\n      setProvider(provider);\n    } catch (err) {\n      setError(err as Error);\n      setProvider(null);\n    }\n  }, [setProvider, provider, user, isLoading, onLogin]);\n\n  const onLogout = useCallback(async () => {\n    setIsLoading(true);\n    try {\n      await provider.logout();\n    } catch (error) {\n      setError(error as Error);\n    }\n    setIsLoading(false);\n    setUser(null);\n    setProvider(null);\n  }, [provider, setProvider]);\n\n  const {displayName, name} = provider;\n\n  return (\n    <StyledBox>\n      {provider.isNew ? <NewTag>New</NewTag> : null}\n      <div />\n      <StyledTileWrapper onClick={onSelect} selected={isSelected}>\n        <StyledCloudName>{displayName || name}</StyledCloudName>\n        {provider.icon ? <provider.icon height=\"64px\" /> : null}\n        {isLoading ? (\n          <div>Loading ...</div>\n        ) : (\n          <>{user ? <StyledUserName>{actionName || user.name}</StyledUserName> : null}</>\n        )}\n        {isSelected ? <CheckMark /> : null}\n      </StyledTileWrapper>\n      {user || error ? <LogoutButton onClick={onLogout} /> : <LoginButton onClick={onLogin} />}\n      {error ? <StyledWarning>{error.message}</StyledWarning> : null}\n    </StyledBox>\n  );\n};\n\nexport default CloudTile;\n"
  },
  {
    "path": "src/components/src/modals/data-table-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {withTheme, IStyledComponent} from 'styled-components';\nimport DatasetLabel from '../common/dataset-label';\nimport DataTableFactory from '../common/data-table';\nimport {createSelector} from 'reselect';\nimport {renderedSize} from '../common/data-table/cell-size';\nimport CanvasHack from '../common/data-table/canvas';\nimport KeplerTable, {Datasets} from '@kepler.gl/table';\nimport {UIStateActions} from '@kepler.gl/actions';\nimport {UiState} from '@kepler.gl/types';\nimport {Gear} from '../common/icons';\nimport Portaled from '../common/portaled';\nimport DataTableConfigFactory from '../common/data-table/display-format';\nimport {BaseComponentProps} from '../types';\n\nconst MIN_STATS_CELL_SIZE = 122;\nconst DEFAULT_SORT_COLUMN = {};\n\n// sidePadding changes from 38 to 68, 30px for configuration button\nconst dgSettings = {\n  sidePadding: '68px',\n  verticalPadding: '16px',\n  height: '36px'\n};\n\nconst StyledModal = styled.div`\n  min-height: 85vh;\n  overflow: hidden;\n  display: flex;\n`;\n\nconst DatasetCatalog = styled.div`\n  display: flex;\n  padding: ${dgSettings.verticalPadding} ${dgSettings.sidePadding} 0 0;\n\n  .overflow-horizontal {\n    display: flex;\n    overflow-x: auto;\n    overflow-y: hidden;\n    flex-direction: row;\n    ${props => props.theme.modalScrollBar}\n  }\n`;\n\nexport type DatasetModalTabProps = BaseComponentProps & {\n  active?: boolean;\n};\n\nexport const DatasetModalTab: IStyledComponent<\n  'web',\n  DatasetModalTabProps\n> = styled.div<DatasetModalTabProps>`\n  align-items: center;\n  border-bottom: 3px solid ${props => (props.active ? 'black' : 'transparent')};\n  cursor: pointer;\n  display: flex;\n  height: 35px;\n  margin: 0 3px;\n  padding: 0 5px;\n\n  &:hover {\n    border-bottom: 3px solid black;\n  }\n`;\n\nconst StyledConfigureButton = styled.div`\n  display: flex;\n  justify-content: flex-end;\n  position: absolute;\n  top: 24px;\n  right: 48px;\n  svg {\n    stroke: black;\n  }\n  cursor: pointer;\n`;\n\ninterface DatasetTabsUnmemoizedProps {\n  activeDataset: KeplerTable;\n  datasets: Datasets;\n  showDatasetTable: (id: string) => void;\n}\n\nconst DatasetTabsUnmemoized: React.FC<DatasetTabsUnmemoizedProps> = ({\n  activeDataset,\n  datasets,\n  showDatasetTable\n}) => (\n  <DatasetCatalog className=\"dataset-modal-catalog\">\n    <div className=\"overflow-horizontal\">\n      {Object.values(datasets).map((dataset: KeplerTable) => (\n        <DatasetModalTab\n          className=\"dataset-modal-tab\"\n          active={dataset === activeDataset}\n          key={dataset.id}\n          onClick={() => showDatasetTable(dataset.id)}\n        >\n          <DatasetLabel dataset={dataset} />\n        </DatasetModalTab>\n      ))}\n    </div>\n  </DatasetCatalog>\n);\n\nexport const DatasetTabs = React.memo(DatasetTabsUnmemoized);\n\nDatasetTabs.displayName = 'DatasetTabs';\n\nDataTableModalFactory.deps = [DataTableFactory, DataTableConfigFactory];\n\nconst TableContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  min-height: 100%;\n  max-height: 100%;\n  max-width: 100%;\n`;\n\ninterface DataTableModalProps {\n  theme: any;\n  dataId?: string;\n  sortTableColumn: (id: string, column: string, mode?: string) => void;\n  pinTableColumn: (id: string, column: string) => void;\n  copyTableColumn: (id: string, column: string) => void;\n  datasets: Datasets;\n  showDatasetTable: (id: string) => void;\n  showTab?: boolean;\n  setColumnDisplayFormat: (\n    dataId: string,\n    formats: {\n      column: string;\n      displayFormat: string;\n    }\n  ) => void;\n  uiStateActions: typeof UIStateActions;\n  uiState: UiState;\n}\n\nfunction DataTableModalFactory(\n  DataTable: ReturnType<typeof DataTableFactory>,\n  DataTableConfig: ReturnType<typeof DataTableConfigFactory>\n): React.ComponentType<Omit<DataTableModalProps, 'theme'>> {\n  class DataTableModal extends React.Component<DataTableModalProps> {\n    state = {\n      showConfig: false\n    };\n\n    datasetCellSizeCache = {};\n    dataId = ({dataId = ''}: DataTableModalProps) => dataId;\n    datasets = (props: DataTableModalProps) => props.datasets;\n    fields = ({datasets, dataId = ''}: DataTableModalProps) => (datasets[dataId] || {}).fields;\n    columns = createSelector(this.fields, fields => fields.map(f => f.name));\n    colMeta = createSelector([this.fields, this.datasets], fields =>\n      fields.reduce(\n        (acc, {name, displayName, type, filterProps, format, displayFormat}) => ({\n          ...acc,\n          [name]: {\n            name: displayName || name,\n            type,\n            ...(format ? {format} : {}),\n            ...(displayFormat ? {displayFormat} : {}),\n            ...(filterProps?.columnStats ? {columnStats: filterProps.columnStats} : {})\n          }\n        }),\n        {}\n      )\n    );\n\n    cellSizeCache = createSelector(this.dataId, this.datasets, (dataId, datasets) => {\n      if (!datasets[dataId]) {\n        return {};\n      }\n      const {fields, dataContainer} = datasets[dataId];\n\n      let showCalculate: boolean | null = null;\n      if (!this.datasetCellSizeCache[dataId]) {\n        showCalculate = true;\n      } else if (\n        this.datasetCellSizeCache[dataId].fields !== fields ||\n        this.datasetCellSizeCache[dataId].dataContainer !== dataContainer\n      ) {\n        showCalculate = true;\n      }\n\n      if (!showCalculate) {\n        return this.datasetCellSizeCache[dataId].cellSizeCache;\n      }\n\n      const cellSizeCache = fields.reduce(\n        (acc, field, colIdx) => ({\n          ...acc,\n          [field.name]: renderedSize({\n            text: {\n              dataContainer,\n              column: field.displayName\n            },\n            colIdx,\n            type: field.type,\n            fontSize: this.props.theme.cellFontSize,\n            font: this.props.theme.fontFamily,\n            minCellSize: MIN_STATS_CELL_SIZE\n          })\n        }),\n        {}\n      );\n      // save it to cache\n      this.datasetCellSizeCache[dataId] = {\n        cellSizeCache,\n        fields,\n        dataContainer\n      };\n      return cellSizeCache;\n    });\n\n    copyTableColumn = (column: string) => {\n      const {dataId = '', copyTableColumn} = this.props;\n      copyTableColumn(dataId, column);\n    };\n\n    pinTableColumn = (column: string) => {\n      const {dataId = '', pinTableColumn} = this.props;\n      pinTableColumn(dataId, column);\n    };\n\n    sortTableColumn = (column: string, mode?: string) => {\n      const {dataId = '', sortTableColumn} = this.props;\n      sortTableColumn(dataId, column, mode);\n    };\n\n    setColumnDisplayFormat = formats => {\n      const {dataId, setColumnDisplayFormat} = this.props;\n      if (dataId) setColumnDisplayFormat(dataId, formats);\n    };\n\n    onOpenConfig = () => {\n      this.setState({showConfig: true});\n    };\n\n    onCloseConfig = () => {\n      this.setState({showConfig: false});\n    };\n\n    render() {\n      const {datasets, dataId, showDatasetTable, showTab = true} = this.props;\n      if (!datasets || !dataId) {\n        return null;\n      }\n      const activeDataset = datasets[dataId];\n      const columns = this.columns(this.props);\n      const colMeta = this.colMeta(this.props);\n      const cellSizeCache = this.cellSizeCache(this.props);\n\n      return (\n        <StyledModal className=\"dataset-modal\" id=\"dataset-modal\">\n          <CanvasHack />\n          <TableContainer>\n            {showTab ? (\n              <DatasetTabs\n                activeDataset={activeDataset}\n                datasets={datasets}\n                showDatasetTable={showDatasetTable}\n              />\n            ) : null}\n            <StyledConfigureButton className=\"display-config-button\">\n              <Gear onClick={this.onOpenConfig} />\n              <Portaled\n                right={240}\n                top={20}\n                isOpened={this.state.showConfig}\n                onClose={this.onCloseConfig}\n              >\n                <DataTableConfig\n                  columns={columns}\n                  colMeta={colMeta}\n                  setColumnDisplayFormat={this.setColumnDisplayFormat}\n                  onClose={this.onCloseConfig}\n                />\n              </Portaled>\n            </StyledConfigureButton>\n            {datasets[dataId] ? (\n              <DataTable\n                key={dataId}\n                dataId={dataId}\n                columns={columns}\n                colMeta={colMeta}\n                cellSizeCache={cellSizeCache}\n                dataContainer={activeDataset.dataContainer}\n                pinnedColumns={activeDataset.pinnedColumns}\n                sortOrder={activeDataset.sortOrder}\n                sortColumn={activeDataset.sortColumn || DEFAULT_SORT_COLUMN}\n                copyTableColumn={this.copyTableColumn}\n                pinTableColumn={this.pinTableColumn}\n                sortTableColumn={this.sortTableColumn}\n                setColumnDisplayFormat={this.setColumnDisplayFormat}\n                hasStats={false}\n              />\n            ) : null}\n          </TableContainer>\n        </StyledModal>\n      );\n    }\n  }\n\n  // @ts-expect-error figure out the proper way to type\n  return withTheme(DataTableModal);\n}\n\nexport default DataTableModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/delete-data-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport DatasetLabel from '../common/dataset-label';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Layer} from '@kepler.gl/layers';\nimport {KeplerTable} from '@kepler.gl/table';\n\nconst StyledMsg = styled.div`\n  margin-top: 24px;\n`;\n\nexport interface DeleteDatasetModalProps {\n  dataset: KeplerTable;\n  layers: Layer[];\n}\n\nexport const DeleteDatasetModal: React.FC<DeleteDatasetModalProps> = ({dataset, layers = []}) => {\n  // retrieve only layers related to the current dataset\n  const currDatasetLayers = layers.filter(layer => layer.config.dataId === (dataset && dataset.id));\n\n  return (\n    <div className=\"delete-dataset-modal\">\n      <DatasetLabel dataset={dataset} />\n      <StyledMsg className=\"delete-dataset-msg\">\n        <FormattedMessage\n          id={'modal.deleteData.warning'}\n          values={{length: currDatasetLayers.length}}\n        />\n      </StyledMsg>\n    </div>\n  );\n};\n\nconst DeleteDatasetModalFactory = () => DeleteDatasetModal;\nexport default DeleteDatasetModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/error-display.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport ErrorBoundary from '../common/error-boundary';\nimport NotificationItemFactory from '../notification-panel/notification-item';\nconst NotificationItem = NotificationItemFactory();\n\ninterface ErrorDisplayProps {\n  error: string;\n}\n\nconst ErrorDisplay: React.FC<ErrorDisplayProps> = ({error}) => {\n  const notification = useMemo(\n    () => ({\n      type: 'error',\n      message: error,\n      id: 'cloud-export-error'\n    }),\n    [error]\n  );\n\n  return (\n    <ErrorBoundary>\n      <NotificationItem notification={notification} isExpanded />\n    </ErrorBoundary>\n  );\n};\n\nexport default ErrorDisplay;\n"
  },
  {
    "path": "src/components/src/modals/export-data-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {injectIntl, IntlShape} from 'react-intl';\n\nimport {DatasetType, EXPORT_DATA_TYPE_OPTIONS} from '@kepler.gl/constants';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Datasets} from '@kepler.gl/table';\n\nimport {FileType} from '../common/icons';\nimport {\n  StyledExportSection,\n  StyledFilteredOption,\n  StyledModalContent,\n  StyledType,\n  CheckMark\n} from '../common/styled-components';\nimport {StyledWarning} from './export-map-modal/components';\n\nconst getDataRowCount = (\n  datasets: Datasets,\n  selectedDataset: string | undefined,\n  filtered: boolean,\n  intl: IntlShape\n) => {\n  if (selectedDataset === undefined) {\n    return;\n  }\n  const selectedData = datasets[selectedDataset];\n  if (!selectedData) {\n    return intl.formatMessage(\n      {id: 'modal.exportData.fileCount'},\n      {fileCount: Object.keys(datasets).length}\n    );\n  }\n  const {dataContainer, filteredIdxCPU} = selectedData;\n\n  if (filtered && !filteredIdxCPU) {\n    return '-';\n  }\n\n  const rowCount = filtered ? filteredIdxCPU?.length : dataContainer.numRows();\n\n  return intl.formatMessage(\n    {id: 'modal.exportData.rowCount'},\n    {rowCount: rowCount?.toLocaleString()}\n  );\n};\n\nexport interface ExportDataModalProps {\n  datasets: Datasets;\n  selectedDataset?: string;\n  dataType: string;\n  filtered: boolean;\n  // callbacks\n  applyCPUFilter: (filter: string | string[]) => void;\n  onChangeExportSelectedDataset: (dataset: string) => void;\n  onChangeExportDataType: (type: string) => void;\n  onChangeExportFiltered: (isFiltered: boolean) => void;\n  intl: IntlShape;\n  supportedDataTypes: {\n    id: string;\n    label: string;\n    available: boolean;\n  }[];\n}\n\nconst ExportDataModalFactory = () => {\n  class ExportDataModal extends Component<ExportDataModalProps> {\n    componentDidMount() {\n      const toCPUFilter = this.props.selectedDataset || Object.keys(this.props.datasets);\n      this.props.applyCPUFilter(toCPUFilter);\n    }\n\n    _onSelectDataset: React.ChangeEventHandler<HTMLSelectElement> = ({target: {value}}) => {\n      this.props.applyCPUFilter(value);\n      this.props.onChangeExportSelectedDataset(value);\n    };\n\n    render() {\n      const {\n        supportedDataTypes = EXPORT_DATA_TYPE_OPTIONS,\n        datasets,\n        selectedDataset,\n        dataType,\n        filtered,\n        onChangeExportDataType,\n        onChangeExportFiltered,\n        intl\n      } = this.props;\n\n      const exportAllDatasets = selectedDataset ? !datasets[selectedDataset] : true;\n      const showTiledDatasetWarning = Object.keys(datasets).some(datasetId => {\n        return (\n          (datasets[datasetId].type === DatasetType.VECTOR_TILE ||\n            datasets[datasetId].type === DatasetType.RASTER_TILE ||\n            datasets[datasetId].type === DatasetType.WMS_TILE) &&\n          (selectedDataset === datasetId || exportAllDatasets)\n        );\n      });\n\n      return (\n        <StyledModalContent className=\"export-data-modal\">\n          <div>\n            <StyledExportSection>\n              <div className=\"description\">\n                <div className=\"title\">\n                  <FormattedMessage id={'modal.exportData.datasetTitle'} />\n                </div>\n                <div className=\"subtitle\">\n                  <FormattedMessage id={'modal.exportData.datasetSubtitle'} />\n                </div>\n              </div>\n              <div className=\"selection\">\n                <select value={selectedDataset} onChange={this._onSelectDataset}>\n                  {[intl.formatMessage({id: 'modal.exportData.allDatasets'})]\n                    .concat(Object.keys(datasets))\n                    .map(d => (\n                      <option key={d} value={d}>\n                        {(datasets[d] && datasets[d].label) || d}\n                      </option>\n                    ))}\n                </select>\n              </div>\n            </StyledExportSection>\n            <StyledExportSection>\n              <div className=\"description\">\n                <div className=\"title\">\n                  <FormattedMessage id={'modal.exportData.dataTypeTitle'} />\n                </div>\n                <div className=\"subtitle\">\n                  <FormattedMessage id={'modal.exportData.dataTypeSubtitle'} />\n                </div>\n              </div>\n              <div className=\"selection\">\n                {supportedDataTypes.map(op => (\n                  <StyledType\n                    key={op.id}\n                    selected={dataType === op.id}\n                    onClick={() => op.available && onChangeExportDataType(op.id)}\n                  >\n                    <FileType ext={op.label} height=\"80px\" fontSize=\"11px\" />\n                    {dataType === op.id && <CheckMark />}\n                  </StyledType>\n                ))}\n              </div>\n            </StyledExportSection>\n            <StyledExportSection>\n              <div className=\"description\">\n                <div className=\"title\">\n                  <FormattedMessage id={'modal.exportData.dataTypeTitle'} />\n                </div>\n                <div className=\"subtitle\">\n                  <FormattedMessage id={'modal.exportData.filterDataSubtitle'} />\n                </div>\n              </div>\n              <div className=\"selection\">\n                <StyledFilteredOption\n                  className=\"unfiltered-option\"\n                  selected={!filtered}\n                  onClick={() => onChangeExportFiltered(false)}\n                >\n                  <div className=\"filter-option-title\">\n                    <FormattedMessage id={'modal.exportData.unfilteredData'} />\n                  </div>\n                  <div className=\"filter-option-subtitle\">\n                    {getDataRowCount(datasets, selectedDataset, false, intl)}\n                  </div>\n                  {!filtered && <CheckMark />}\n                </StyledFilteredOption>\n                <StyledFilteredOption\n                  className=\"filtered-option\"\n                  selected={filtered}\n                  onClick={() => onChangeExportFiltered(true)}\n                >\n                  <div className=\"filter-option-title\">\n                    <FormattedMessage id={'modal.exportData.filteredData'} />\n                  </div>\n                  <div className=\"filter-option-subtitle\">\n                    {getDataRowCount(datasets, selectedDataset, true, intl)}\n                  </div>\n                  {filtered && <CheckMark />}\n                </StyledFilteredOption>\n              </div>\n            </StyledExportSection>\n            {showTiledDatasetWarning ? (\n              <div className=\"title\">\n                <StyledWarning>\n                  <FormattedMessage id={'modal.exportData.tiledDatasetWarning'} />\n                </StyledWarning>\n              </div>\n            ) : null}\n          </div>\n        </StyledModalContent>\n      );\n    }\n  }\n\n  return injectIntl(ExportDataModal);\n};\n\nexport default ExportDataModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/export-image-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useMemo} from 'react';\nimport styled from 'styled-components';\nimport ImagePreview from '../common/image-preview';\nimport {SetExportImageSettingUpdaterAction} from '@kepler.gl/actions';\n\nimport {\n  EXPORT_IMG_RATIO_OPTIONS,\n  EXPORT_IMG_RATIOS,\n  OneXResolutionOption,\n  TwoXResolutionOption,\n  Resolution1024x768Option,\n  Resolution1280x960Option,\n  Resolution1600x1200Option,\n  Resolution1920x1440Option,\n  Resolution1280x720Option,\n  Resolution1600x900Option,\n  Resolution1920x1080Option,\n  Resolution2560x1440Option\n} from '@kepler.gl/constants';\nimport type {ExportResolutionOption} from '@kepler.gl/constants';\nimport type {ExportImage} from '@kepler.gl/types';\nimport {StyledModalContent, SelectionButton, CheckMark} from '../common/styled-components';\nimport Switch from '../common/switch';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nconst ImageOptionList = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n  width: 250px;\n\n  .image-option-section {\n    .image-option-section-title {\n      font-weight: 500;\n      font-size: 14px;\n    }\n  }\n\n  .button-list {\n    display: flex;\n    flex-direction: row;\n    padding: 8px 0px;\n    flex-wrap: wrap;\n    gap: 4px;\n  }\n\n  .resolution-dropdown {\n    padding: 0px;\n    margin-top: 8px;\n    margin-bottom: 8px;\n\n    select {\n      width: 100%;\n      padding: 6px 12px;\n      border: 1px solid #d3d3d3;\n      border-radius: 2px;\n      font-size: 14px;\n      cursor: pointer;\n      background-color: white;\n      font-family: inherit;\n      appearance: none;\n      height: 32px;\n\n      &:hover {\n        border-color: #999;\n        background-color: #f9f9f9;\n      }\n\n      &:focus {\n        outline: none;\n        border-color: #0066cc;\n        box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n      }\n\n      &:disabled {\n        background-color: #f5f5f5;\n        color: #999;\n        cursor: not-allowed;\n      }\n    }\n  }\n\n  input {\n    margin-right: 8px;\n  }\n`;\n\n// Define resolution options for each ratio as constants\nconst SCREEN_RESOLUTION_OPTIONS = [OneXResolutionOption, TwoXResolutionOption];\n\nconst FOUR_BY_THREE_RESOLUTION_OPTIONS = [\n  Resolution1024x768Option,\n  Resolution1280x960Option,\n  Resolution1600x1200Option,\n  Resolution1920x1440Option\n];\n\nconst SIXTEEN_BY_NINE_RESOLUTION_OPTIONS = [\n  Resolution1280x720Option,\n  Resolution1600x900Option,\n  Resolution1920x1080Option,\n  Resolution2560x1440Option\n];\n\nexport interface ExportImageModalProps {\n  exportImage: ExportImage;\n  mapW: number;\n  mapH: number;\n  onUpdateImageSetting: (payload: SetExportImageSettingUpdaterAction['payload']) => void;\n  cleanupExportImage: () => void;\n  intl: IntlShape;\n}\n\nconst ExportImageModalFactory = () => {\n  const ExportImageModal: React.FC<ExportImageModalProps> = ({\n    mapW,\n    mapH,\n    exportImage,\n    onUpdateImageSetting,\n    cleanupExportImage,\n    intl\n  }) => {\n    const {legend, ratio, resolution} = exportImage;\n\n    // Filter resolutions based on selected ratio\n    const filteredResolutions = useMemo(() => {\n      if (ratio === EXPORT_IMG_RATIOS.SCREEN) {\n        return SCREEN_RESOLUTION_OPTIONS;\n      } else if (ratio === EXPORT_IMG_RATIOS.FOUR_BY_THREE) {\n        return FOUR_BY_THREE_RESOLUTION_OPTIONS;\n      } else if (ratio === EXPORT_IMG_RATIOS.SIXTEEN_BY_NINE) {\n        return SIXTEEN_BY_NINE_RESOLUTION_OPTIONS;\n      }\n      // For CUSTOM, don't show resolution options\n      return [];\n    }, [ratio]);\n\n    useEffect(() => {\n      onUpdateImageSetting({\n        exporting: true\n      });\n      return cleanupExportImage;\n    }, [onUpdateImageSetting, cleanupExportImage]);\n\n    useEffect(() => {\n      if (mapH !== exportImage.mapH || mapW !== exportImage.mapW) {\n        onUpdateImageSetting({\n          mapH,\n          mapW\n        });\n      }\n    }, [mapH, mapW, exportImage, onUpdateImageSetting]);\n\n    useEffect(() => {\n      // Keep resolution in sync with the selected ratio and available options.\n      // If the current resolution is not available for this ratio, reset it to the first option.\n      if (!filteredResolutions || !filteredResolutions.length) {\n        return;\n      }\n\n      const isValidResolution =\n        Boolean(resolution) && filteredResolutions.some(op => op.id === resolution);\n\n      if (!isValidResolution) {\n        onUpdateImageSetting({resolution: filteredResolutions[0].id});\n      }\n    }, [ratio, filteredResolutions, resolution, onUpdateImageSetting]);\n\n    return (\n      <StyledModalContent className=\"export-image-modal\">\n        <ImageOptionList>\n          <div className=\"image-option-section\">\n            <div className=\"image-option-section-title\">\n              <FormattedMessage id={'modal.exportImage.ratioTitle'} />\n            </div>\n            <FormattedMessage id={'modal.exportImage.ratioDescription'} />\n            <div className=\"button-list\" id=\"export-image-modal__option_ratio\">\n              {EXPORT_IMG_RATIO_OPTIONS.filter(op => !op.hidden).map(op => (\n                <SelectionButton\n                  key={op.id}\n                  selected={ratio === op.id}\n                  onClick={() => onUpdateImageSetting({ratio: op.id})}\n                >\n                  <FormattedMessage id={op.label} />\n                  {ratio === op.id && <CheckMark />}\n                </SelectionButton>\n              ))}\n            </div>\n          </div>\n          {ratio !== EXPORT_IMG_RATIOS.CUSTOM && (\n            <div className=\"image-option-section\">\n               <div\n                 className=\"image-option-section-title\"\n                 id=\"export-image-modal__resolution-title\"\n               >\n                <FormattedMessage id={'modal.exportImage.resolutionTitle'} />\n              </div>\n              <FormattedMessage id={'modal.exportImage.resolutionDescription'} />\n              {ratio === EXPORT_IMG_RATIOS.SCREEN ? (\n                <div className=\"button-list\" id=\"export-image-modal__option_resolution\">\n                  {filteredResolutions.map(op => (\n                    <SelectionButton\n                      key={op.id}\n                      selected={resolution === op.id}\n                      onClick={() => op.available && onUpdateImageSetting({resolution: op.id})}\n                    >\n                      {op.label}\n                      {resolution === op.id && <CheckMark />}\n                    </SelectionButton>\n                  ))}\n                </div>\n              ) : (\n                <div className=\"resolution-dropdown\" id=\"export-image-modal__option_resolution\">\n                  <select\n                    value={resolution || ''}\n                    aria-labelledby=\"export-image-modal__resolution-title\"\n                    onChange={e => {\n                      const value = e.target.value;\n                      // Only update if a valid resolution is selected\n                      if (value && filteredResolutions.some(op => op.id === value)) {\n                        // Type-safe: we've verified the value exists in filteredResolutions\n                        onUpdateImageSetting({resolution: value as ExportResolutionOption});\n                      }\n                      // If empty string selected, optionally reset to first available resolution\n                      else if (!value && filteredResolutions.length > 0) {\n                        onUpdateImageSetting({resolution: filteredResolutions[0].id});\n                      }\n                    }}\n                  >\n                    <option value=\"\">\n                      {intl.formatMessage({id: 'modal.exportImage.resolutionPlaceholder'})}\n                    </option>\n                    {filteredResolutions.map(op => (\n                      <option key={op.id} value={op.id} disabled={!op.available}>\n                        {op.label}\n                      </option>\n                    ))}\n                  </select>\n                </div>\n              )}\n            </div>\n          )}\n          <div className=\"image-option-section\">\n            <div className=\"image-option-section-title\">\n              <FormattedMessage id={'modal.exportImage.mapLegendTitle'} />\n            </div>\n            <Switch\n              type=\"checkbox\"\n              id=\"add-map-legend\"\n              checked={legend}\n              label={intl.formatMessage({id: 'modal.exportImage.mapLegendAdd'})}\n              onChange={() => onUpdateImageSetting({legend: !legend})}\n            />\n          </div>\n        </ImageOptionList>\n        <ImagePreview exportImage={exportImage} />\n      </StyledModalContent>\n    );\n  };\n\n  return injectIntl(ExportImageModal);\n};\n\nexport default ExportImageModalFactory;"
  },
  {
    "path": "src/components/src/modals/export-map-modal/components.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {StyledExportSection} from '../../common/styled-components';\n\nexport const StyledExportMapSection = styled(StyledExportSection)`\n  margin-top: ${props => props.theme.exportIntraSectionMargin}px;\n`;\n\nexport const StyledWarning = styled.span`\n  color: ${props => props.theme.errorColor};\n  font-weight: ${props => props.theme.selectFontWeightBold};\n`;\n\nexport const StyledExportLink = styled.a`\n  text-decoration-line: underline !important;\n`;\n\nexport const ExportMapLink = ({children, ...props}) => (\n  <StyledExportLink target=\"_blank\" rel=\"noopener noreferrer\" {...props}>\n    {children}\n  </StyledExportLink>\n);\n"
  },
  {
    "path": "src/components/src/modals/export-map-modal/export-html-map.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {StyledExportSection, StyledType, CheckMark} from '../../common/styled-components';\nimport {StyledExportMapSection, StyledWarning, ExportMapLink} from './components';\nimport {\n  EXPORT_HTML_MAP_MODE_OPTIONS,\n  EXPORT_HTML_MAP_DOC,\n  EXPORT_HTML_MAP_MODES_DOC\n} from '@kepler.gl/constants';\nimport styled from 'styled-components';\nimport {injectIntl} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {IntlShape} from 'react-intl';\n\nimport {setUserMapboxAccessToken, setExportHTMLMapMode, ActionHandler} from '@kepler.gl/actions';\n\nconst ExportMapStyledExportSection = styled(StyledExportSection)`\n  .disclaimer {\n    font-size: ${props => props.theme.inputFontSize};\n    color: ${props => props.theme.inputColor};\n    margin-top: 12px;\n  }\n`;\n\ninterface StyledInputProps {\n  error?: boolean;\n}\n\nconst StyledInput = styled.input<StyledInputProps>`\n  width: 100%;\n  padding: ${props => props.theme.inputPadding};\n  color: ${props => (props.error ? 'red' : props.theme.titleColorLT)};\n  height: ${props => props.theme.inputBoxHeight};\n  outline: 0;\n  font-size: ${props => props.theme.inputFontSize};\n\n  &:active,\n  &:focus,\n  &.focus,\n  &.active {\n    outline: 0;\n  }\n`;\n\nconst BigStyledTile = styled(StyledType)`\n  height: unset;\n  width: unset;\n  img {\n    width: 180px;\n    height: 120px;\n  }\n`;\n\ntype ExportHtmlMapProps = {\n  onChangeExportMapHTMLMode: ActionHandler<typeof setExportHTMLMapMode>;\n  onEditUserMapboxAccessToken: ActionHandler<typeof setUserMapboxAccessToken>;\n  options: {\n    userMapboxToken?: string;\n    mode?: string;\n  };\n};\n\ntype IntlProps = {\n  intl: IntlShape;\n};\n\nfunction ExportHtmlMapFactory(): React.ComponentType<ExportHtmlMapProps> {\n  /** @type {typeof import('./export-html-map').ExportHtmlMap} */\n  const ExportHtmlMap: React.FC<ExportHtmlMapProps & IntlProps> = ({\n    onChangeExportMapHTMLMode = () => {\n      return;\n    },\n    onEditUserMapboxAccessToken = () => {\n      return;\n    },\n    options = {},\n    intl\n  }) => (\n    <div>\n      <StyledExportMapSection>\n        <div className=\"description\" />\n        <div className=\"selection\">\n          <FormattedMessage id={'modal.exportMap.html.selection'} />\n        </div>\n      </StyledExportMapSection>\n      <ExportMapStyledExportSection className=\"export-map-modal__html-options\">\n        <div className=\"description\">\n          <div className=\"title\">\n            <FormattedMessage id={'modal.exportMap.html.tokenTitle'} />\n          </div>\n          <div className=\"subtitle\">\n            <FormattedMessage id={'modal.exportMap.html.tokenSubtitle'} />\n          </div>\n        </div>\n        <div className=\"selection\">\n          <StyledInput\n            onChange={e => onEditUserMapboxAccessToken(e.target.value)}\n            type=\"text\"\n            placeholder={intl.formatMessage({id: 'modal.exportMap.html.tokenPlaceholder'})}\n            value={options ? options.userMapboxToken : ''}\n          />\n          <div className=\"disclaimer\">\n            <StyledWarning>\n              <FormattedMessage id={'modal.exportMap.html.tokenMisuseWarning'} />\n            </StyledWarning>\n            <StyledWarning>\n              <FormattedMessage id={'modal.exportMap.html.tokenSecurityWarning'} />\n            </StyledWarning>\n            <FormattedMessage id={'modal.exportMap.html.tokenDisclaimer'} />\n            <ExportMapLink href={EXPORT_HTML_MAP_DOC}>\n              <FormattedMessage id={'modal.exportMap.html.tokenUpdate'} />\n            </ExportMapLink>\n          </div>\n        </div>\n      </ExportMapStyledExportSection>\n      <ExportMapStyledExportSection>\n        <div className=\"description\">\n          <div className=\"title\">\n            <FormattedMessage id={'modal.exportMap.html.modeTitle'} />\n          </div>\n          <div className=\"subtitle\">\n            <FormattedMessage id={'modal.exportMap.html.modeSubtitle1'} />\n            <a href={EXPORT_HTML_MAP_MODES_DOC}>\n              <FormattedMessage id={'modal.exportMap.html.modeSubtitle2'} />\n            </a>\n          </div>\n        </div>\n        <div className=\"selection\">\n          {EXPORT_HTML_MAP_MODE_OPTIONS.map(mode => (\n            <BigStyledTile\n              key={mode.id}\n              selected={options.mode === mode.id}\n              onClick={() => mode.available && onChangeExportMapHTMLMode(mode.id)}\n            >\n              <img src={mode.url} alt=\"\" />\n              <p>\n                <FormattedMessage\n                  id={'modal.exportMap.html.modeDescription'}\n                  values={{mode: intl.formatMessage({id: mode.label})}}\n                />\n              </p>\n              {options.mode === mode.id && <CheckMark />}\n            </BigStyledTile>\n          ))}\n        </div>\n      </ExportMapStyledExportSection>\n    </div>\n  );\n\n  ExportHtmlMap.displayName = 'ExportHtmlMap';\n\n  return injectIntl(ExportHtmlMap);\n}\n\nexport default ExportHtmlMapFactory;\n"
  },
  {
    "path": "src/components/src/modals/export-map-modal/export-json-map.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState} from 'react';\nimport JSONPretty from 'react-json-pretty';\nimport {ADD_DATA_TO_MAP_DOC} from '@kepler.gl/constants';\nimport styled from 'styled-components';\nimport {StyledExportSection, Button} from '../../common/styled-components';\nimport {StyledExportMapSection, StyledWarning, ExportMapLink} from './components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {CopyToClipboard} from 'react-copy-to-clipboard';\n\nconst StyledJsonExportSection = styled(StyledExportSection)`\n  .note {\n    color: ${props => props.theme.errorColor};\n    font-size: 11px;\n  }\n\n  .viewer {\n    position: relative;\n    border: 1px solid ${props => props.theme.selectBorderColorLT};\n    background-color: white;\n    border-radius: 2px;\n    display: inline-block;\n    font: inherit;\n    line-height: 1.5em;\n    padding: 0.5em 3.5em 0.5em 1em;\n    margin: 0;\n    box-sizing: border-box;\n    height: 180px;\n    width: 100%;\n    overflow-y: scroll;\n    overflow-x: auto;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    max-width: 600px;\n  }\n\n  .copy-button {\n    margin: 1em 1em 0 0;\n    position: absolute;\n    top: 0;\n    right: 0;\n  }\n`;\n\ntype ExportJsonPropTypes = {\n  config: any;\n};\n\nconst ExportJsonMapUnmemoized = ({config = {}}: ExportJsonPropTypes) => {\n  const [copied, setCopy] = useState(false);\n  return (\n    <div>\n      <StyledExportMapSection>\n        <div className=\"description\" />\n        <div className=\"selection\">\n          <FormattedMessage id={'modal.exportMap.json.selection'} />\n        </div>\n      </StyledExportMapSection>\n      <StyledJsonExportSection className=\"export-map-modal__json-options\">\n        <div className=\"description\">\n          <div className=\"title\">\n            <FormattedMessage id={'modal.exportMap.json.configTitle'} />\n          </div>\n          <div className=\"subtitle\">\n            <FormattedMessage id={'modal.exportMap.json.configDisclaimer'} />\n            <ExportMapLink href={ADD_DATA_TO_MAP_DOC}>addDataToMap</ExportMapLink>.\n          </div>\n        </div>\n        <div className=\"selection\">\n          <div className=\"viewer\">\n            <JSONPretty id=\"json-pretty\" json={config} />\n            <CopyToClipboard text={JSON.stringify(config)} onCopy={() => setCopy(true)}>\n              <Button width=\"80px\" className=\"copy-button\">\n                {copied ? 'Copied!' : 'Copy'}\n              </Button>\n            </CopyToClipboard>\n          </div>\n          <div className=\"disclaimer\">\n            <StyledWarning>\n              <FormattedMessage id={'modal.exportMap.json.disclaimer'} />\n            </StyledWarning>\n          </div>\n        </div>\n      </StyledJsonExportSection>\n    </div>\n  );\n};\n\nExportJsonMapUnmemoized.displayName = 'ExportJsonMap';\n\nconst ExportJsonMap = React.memo(ExportJsonMapUnmemoized);\n\nconst ExportJsonMapFactory = () => ExportJsonMap;\n\nexport default ExportJsonMapFactory;\n"
  },
  {
    "path": "src/components/src/modals/export-map-modal/export-map-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nimport {FileType} from '../../common/icons';\nimport {StyledModalContent, StyledType, CheckMark} from '../../common/styled-components';\nimport {EXPORT_MAP_FORMATS, EXPORT_MAP_FORMAT_OPTIONS} from '@kepler.gl/constants';\nimport {StyledExportMapSection} from './components';\nimport ExportHtmlMapFactory from './export-html-map';\nimport ExportJsonMapFactory from './export-json-map';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ActionHandler, setExportHTMLMapMode, setUserMapboxAccessToken} from '@kepler.gl/actions';\n\ninterface ExportMapModalFactoryProps {\n  options?: {format: string};\n  config: any;\n  onEditUserMapboxAccessToken: ActionHandler<typeof setUserMapboxAccessToken>;\n  onChangeExportMapHTMLMode?: ActionHandler<typeof setExportHTMLMapMode>;\n  onChangeExportMapFormat?: (format: string) => any;\n  mapFormat?: string;\n}\n\nconst style = {width: '100%'};\n\nconst NO_OP = () => ({} as any);\n\nExportMapModalFactory.deps = [ExportHtmlMapFactory, ExportJsonMapFactory];\n\nfunction ExportMapModalFactory(\n  ExportHtmlMap: ReturnType<typeof ExportHtmlMapFactory>,\n  ExportJsonMap: ReturnType<typeof ExportJsonMapFactory>\n) {\n  const ExportMapModalUnmemoized = ({\n    config = {},\n    onChangeExportMapFormat = NO_OP,\n    onChangeExportMapHTMLMode = NO_OP,\n    onEditUserMapboxAccessToken = NO_OP,\n    options = {format: ''}\n  }: ExportMapModalFactoryProps) => (\n    <StyledModalContent className=\"export-map-modal\">\n      <div style={style}>\n        <StyledExportMapSection>\n          <div className=\"description\">\n            <div className=\"title\">\n              <FormattedMessage id={'modal.exportMap.formatTitle'} />\n            </div>\n            <div className=\"subtitle\">\n              <FormattedMessage id={'modal.exportMap.formatSubtitle'} />\n            </div>\n          </div>\n          <div className=\"selection\">\n            {EXPORT_MAP_FORMAT_OPTIONS.map(op => (\n              <StyledType\n                key={op.id}\n                selected={options.format === op.id}\n                onClick={() => op.available && onChangeExportMapFormat(op.id)}\n              >\n                <FileType ext={op.label} height=\"80px\" fontSize=\"11px\" />\n\n                {options.format === op.id && <CheckMark />}\n              </StyledType>\n            ))}\n          </div>\n        </StyledExportMapSection>\n        {\n          {\n            [EXPORT_MAP_FORMATS.HTML]: (\n              <ExportHtmlMap\n                onChangeExportMapHTMLMode={onChangeExportMapHTMLMode}\n                onEditUserMapboxAccessToken={onEditUserMapboxAccessToken}\n                options={options[options.format]}\n              />\n            ),\n            [EXPORT_MAP_FORMATS.JSON]: <ExportJsonMap config={config} />\n          }[\n            // @ts-ignore\n            options.format\n          ]\n        }\n      </div>\n    </StyledModalContent>\n  );\n\n  ExportMapModalUnmemoized.displayName = 'ExportMapModal';\n\n  const ExportMapModal = React.memo(ExportMapModalUnmemoized);\n\n  return ExportMapModal;\n}\n\nexport default ExportMapModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/image-modal-container.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect} from 'react';\nimport get from 'lodash/get';\n\nimport {MAP_THUMBNAIL_DIMENSION, EXPORT_IMG_RATIOS} from '@kepler.gl/constants';\nimport {SetExportImageSettingUpdaterAction} from '@kepler.gl/actions';\nimport {Provider} from '@kepler.gl/cloud-providers';\n\nexport type ImageModalContainerProps = {\n  provider?: Provider | null;\n  onUpdateImageSetting: (newSetting: SetExportImageSettingUpdaterAction['payload']) => void;\n  cleanupExportImage: () => void;\n  children?: React.ReactNode;\n};\n\n// TODO: this should be turned into a custom hook\n/**\n * A wrapper component in modals contain a image preview of the map with cloud providers\n * It sets export image size based on provider thumbnail size\n * @type {React.FunctionComponent<ImageModalContainerProps>}\n */\nconst ImageModalContainer: React.FC<ImageModalContainerProps> = ({\n  onUpdateImageSetting,\n  cleanupExportImage,\n  provider,\n  children\n}) => {\n  useEffect(() => {\n    onUpdateImageSetting({exporting: true});\n    return () => {\n      cleanupExportImage();\n    };\n  }, [onUpdateImageSetting, cleanupExportImage]);\n\n  useEffect(() => {\n    if (provider) {\n      if (provider.thumbnail) {\n        onUpdateImageSetting({\n          mapW: get(provider, ['thumbnail', 'width']) || MAP_THUMBNAIL_DIMENSION.width,\n          mapH: get(provider, ['thumbnail', 'height']) || MAP_THUMBNAIL_DIMENSION.height,\n          ratio: EXPORT_IMG_RATIOS.CUSTOM,\n          legend: false\n        });\n      }\n    } else {\n      onUpdateImageSetting({\n        mapW: MAP_THUMBNAIL_DIMENSION.width,\n        mapH: MAP_THUMBNAIL_DIMENSION.height,\n        ratio: EXPORT_IMG_RATIOS.CUSTOM,\n        legend: false\n      });\n    }\n  }, [provider, onUpdateImageSetting]);\n\n  return <>{children}</>;\n};\n\nexport default ImageModalContainer;\n"
  },
  {
    "path": "src/components/src/modals/load-data-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState} from 'react';\nimport styled from 'styled-components';\nimport get from 'lodash/get';\nimport {IntlShape, useIntl} from 'react-intl';\n\nimport FileUploadFactory from '../common/file-uploader/file-upload';\nimport LoadStorageMapFactory from './load-storage-map';\nimport LoadTilesetFactory from './tilesets-modals/load-tileset';\nimport ModalTabsFactory from './modal-tabs';\nimport LoadingDialog from './loading-dialog';\n\nimport {LOADING_METHODS} from '@kepler.gl/constants';\nimport {FileLoading, FileLoadingProgress, LoadFiles} from '@kepler.gl/types';\n\nconst StyledLoadDataModal = styled.div.attrs({\n  className: 'load-data-modal'\n})`\n  padding: ${props => props.theme.modalPadding};\n  min-height: 440px;\n  display: flex;\n  flex-direction: column;\n`;\n\nconst noop = () => {\n  return;\n};\nconst getDefaultMethod = <T,>(methods: T[] = []) =>\n  Array.isArray(methods) ? get(methods, [0]) : null;\nexport interface LoadingMethod {\n  id: string;\n  label: string;\n  elementType: React.ComponentType<any>;\n  tabElementType?: React.ComponentType<{onClick: React.MouseEventHandler; intl: IntlShape}>;\n}\n\ntype LoadDataModalProps = {\n  // call backs\n  onFileUpload: (files: File[]) => void;\n  onLoadCloudMap: (provider: any, vis: any) => void;\n  onTilesetAdded: (\n    tileset: {name: string; type: string; metadata: Record<string, any>},\n    processedMetadata?: Record<string, any>\n  ) => void;\n  fileLoading: FileLoading | false;\n  loadingMethods?: LoadingMethod[];\n  /** A list of names of supported formats suitable to present to user */\n  fileFormatNames: string[];\n  /** A list of typically 3 letter extensions (without '.') for file matching */\n  fileExtensions: string[];\n  isCloudMapLoading: boolean;\n  /** Set to true if app wants to do its own file filtering */\n  disableExtensionFilter?: boolean;\n  onClose?: (...args: any) => any;\n\n  loadFiles: LoadFiles;\n  fileLoadingProgress: FileLoadingProgress;\n};\n\nLoadDataModalFactory.deps = [\n  ModalTabsFactory,\n  FileUploadFactory,\n  LoadStorageMapFactory,\n  LoadTilesetFactory\n];\n\nexport function LoadDataModalFactory(\n  ModalTabs: ReturnType<typeof ModalTabsFactory>,\n  FileUpload: ReturnType<typeof FileUploadFactory>,\n  LoadStorageMap: ReturnType<typeof LoadStorageMapFactory>,\n  LoadTileset: ReturnType<typeof LoadTilesetFactory>\n) {\n  const defaultLoadingMethods = [\n    {\n      id: LOADING_METHODS.upload,\n      label: 'modal.loadData.upload',\n      elementType: FileUpload\n    },\n    {\n      id: LOADING_METHODS.tileset,\n      label: 'modal.loadData.tileset',\n      elementType: LoadTileset\n    },\n    {\n      id: LOADING_METHODS.storage,\n      label: 'modal.loadData.storage',\n      elementType: LoadStorageMap\n    }\n  ];\n\n  const LoadDataModal: React.FC<LoadDataModalProps> & {\n    defaultLoadingMethods: LoadDataModalProps['loadingMethods'];\n  } = ({\n    onFileUpload = noop,\n    onTilesetAdded = noop,\n    fileLoading = false,\n    loadingMethods = defaultLoadingMethods,\n    isCloudMapLoading,\n    ...restProps\n  }) => {\n    const intl = useIntl();\n    const currentModalProps = {\n      ...restProps,\n      onFileUpload,\n      onTilesetAdded,\n      fileLoading,\n      isCloudMapLoading\n    };\n    // const {loadingMethods, isCloudMapLoading} = props;\n    const [currentMethod, toggleMethod] = useState(getDefaultMethod(loadingMethods));\n\n    const ElementType = currentMethod?.elementType;\n\n    return (\n      <StyledLoadDataModal>\n        <ModalTabs\n          currentMethod={currentMethod?.id}\n          loadingMethods={loadingMethods}\n          toggleMethod={toggleMethod}\n        />\n        {isCloudMapLoading ? (\n          <LoadingDialog size={64} />\n        ) : (\n          ElementType && <ElementType key={currentMethod?.id} intl={intl} {...currentModalProps} />\n        )}\n      </StyledLoadDataModal>\n    );\n  };\n\n  LoadDataModal.defaultLoadingMethods = defaultLoadingMethods;\n\n  return LoadDataModal;\n}\n\nexport default LoadDataModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/load-storage-map.spec.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport React from 'react';\nimport {fireEvent, waitFor} from '@testing-library/react';\nimport InfoHelperFactory from '../common/info-helper';\nimport CloudHeaderFactory from './cloud-components/cloud-header';\nimport LoadStorageMapFactory from './load-storage-map';\nimport {renderWithTheme} from 'test/helpers/component-jest-utils';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {dataTestIds} from '@kepler.gl/constants';\n\nconst InfoHelper = InfoHelperFactory();\nconst CloudHeader = CloudHeaderFactory(InfoHelper);\nconst LoadStorageMap = LoadStorageMapFactory(CloudHeader);\n\nconst DEFAULT_MAPS = [\n  {\n    id: '1234',\n    title: 'first map',\n    description: 'description 1',\n    loadParams: {\n      id: '1234'\n    }\n  },\n  {\n    id: '5678',\n    title: 'second map',\n    description: 'description 2',\n    loadParams: {\n      id: '5678'\n    }\n  }\n];\n\nconst DEFAULT_PROVIDER = {\n  name: 'test provider',\n  icon: jest.fn(),\n  getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n  listMaps: jest.fn().mockResolvedValue([])\n};\n\nconst DEFAULT_PROPS = {\n  onLoadCloudMap: jest.fn()\n};\n\njest.mock('../hooks/use-cloud-list-provider', () => ({\n  useCloudListProvider: jest.fn().mockImplementation(() => ({\n    provider: null,\n    setProvider: jest.fn(),\n    cloudProviders: []\n  }))\n}));\n\ndescribe('LoadStorageMap', () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  test('renders provider select and no cloud components when provider is set to null', () => {\n    const {getByTestId} = renderWithTheme(<LoadStorageMap {...DEFAULT_PROPS} />);\n    expect(getByTestId(dataTestIds.providerSelect)).toBeInTheDocument();\n  });\n\n  test('renders empty map list because fetchmaps return empty array', async () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const {getByText} = renderWithTheme(<LoadStorageMap {...DEFAULT_PROPS} />);\n    expect(DEFAULT_PROVIDER.listMaps).toHaveBeenCalled();\n\n    // first show loading icon\n    expect(getByText('modal.loadingDialog.loading')).toBeInTheDocument();\n\n    // show empty maps\n    await waitFor(() => {\n      expect(getByText('modal.loadStorageMap.noSavedMaps')).toBeInTheDocument();\n    });\n  });\n\n  test('renders map list because', async () => {\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue(DEFAULT_MAPS)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const {getByText} = renderWithTheme(<LoadStorageMap {...DEFAULT_PROPS} />);\n    expect(mapProvider.listMaps).toHaveBeenCalled();\n\n    // first show loading icon\n    expect(getByText('modal.loadingDialog.loading')).toBeInTheDocument();\n\n    // show empty maps\n    await waitFor(() => {\n      DEFAULT_MAPS.forEach(map => {\n        expect(getByText(map.title)).toBeInTheDocument();\n        expect(getByText(map.description)).toBeInTheDocument();\n      });\n    });\n  });\n\n  test('trigger onLoadCLoudMap when clicking on a map', async () => {\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue(DEFAULT_MAPS)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const {getByText} = renderWithTheme(<LoadStorageMap {...DEFAULT_PROPS} />);\n    expect(mapProvider.listMaps).toHaveBeenCalled();\n\n    // first show loading icon\n    expect(getByText('modal.loadingDialog.loading')).toBeInTheDocument();\n\n    // click on a map\n    await waitFor(() => {\n      const map = DEFAULT_MAPS[0];\n      // if the component doesn't exist this will throw an exception\n      const mapTitleComponent = getByText(map.title);\n      fireEvent.click(mapTitleComponent);\n      expect(DEFAULT_PROPS.onLoadCloudMap).toHaveBeenCalled();\n    });\n  });\n\n  test('renders errors because fetchmaps rejects', async () => {\n    const rejectableProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockRejectedValue(new Error('timeout'))\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: rejectableProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const {getByText} = renderWithTheme(<LoadStorageMap {...DEFAULT_PROPS} />);\n    expect(rejectableProvider.listMaps).toHaveBeenCalled();\n\n    // first show loading icon\n    expect(getByText('modal.loadingDialog.loading')).toBeInTheDocument();\n\n    // show empty maps\n    await waitFor(() => {\n      expect(getByText('Error while fetching maps: timeout')).toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/src/modals/load-storage-map.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState, useEffect} from 'react';\nimport CloudHeaderFactory from './cloud-components/cloud-header';\nimport {CloudMaps} from './cloud-components/cloud-maps';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {ProviderSelect} from './cloud-components/provider-select';\nimport {FlexColContainer} from '../common/flex-container';\nimport {Provider, MapListItem} from '@kepler.gl/cloud-providers';\n\nLoadStorageMapFactory.deps = [CloudHeaderFactory];\n\nfunction LoadStorageMapFactory(CloudHeader: ReturnType<typeof CloudHeaderFactory>) {\n  const LoadStorageMap = ({onLoadCloudMap}) => {\n    const {provider: currentProvider, setProvider, cloudProviders} = useCloudListProvider();\n    const [isLoading, setIsLoading] = useState(false);\n    const [maps, setMaps] = useState<MapListItem[] | null>(null);\n    const [error, setError] = useState(null);\n\n    const setProviderInfo = useCallback((provider: Provider | null) => {\n      setMaps(null);\n      setError(null);\n      if (provider) {\n        setIsLoading(true);\n        provider\n          .listMaps()\n          .then(setMaps)\n          .catch(setError)\n          .finally(() => setIsLoading(false));\n      } else {\n        setIsLoading(false);\n      }\n    }, []);\n\n    useEffect(() => {\n      setProviderInfo(currentProvider);\n    }, [currentProvider, setProviderInfo]);\n\n    const onSelectMap = useCallback(\n      (provider, map) => {\n        onLoadCloudMap({\n          loadParams: map.loadParams,\n          provider\n        });\n      },\n      [onLoadCloudMap]\n    );\n\n    return (\n      <FlexColContainer>\n        {!currentProvider ? (\n          <ProviderSelect cloudProviders={cloudProviders} />\n        ) : (\n          <>\n            <CloudHeader provider={currentProvider} onBack={() => setProvider(null)} />\n            <CloudMaps\n              isLoading={isLoading}\n              onSelectMap={onSelectMap}\n              provider={currentProvider}\n              error={error}\n              maps={maps}\n            />\n          </>\n        )}\n      </FlexColContainer>\n    );\n  };\n\n  return LoadStorageMap;\n}\n\nexport default LoadStorageMapFactory;\n"
  },
  {
    "path": "src/components/src/modals/loading-dialog.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport LoadingSpinner from '../common/loading-spinner';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nconst StyledSpinner = styled.div`\n  text-align: center;\n\n  span {\n    margin: 0 auto;\n  }\n`;\n\nconst StyledLoadingDialog = styled.div.attrs({\n  className: 'data-loading-dialog'\n})`\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-grow: 1;\n\n  .loading-content {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  }\n\n  .loading-message {\n    margin-left: 32px;\n    color: ${props => props.theme.titleColorLT};\n    font-weight: 500;\n    font-size: 14px;\n  }\n`;\n\ninterface LoadingDialogProps {\n  size?: number;\n  message?: string;\n}\n\nconst LoadingDialog: React.FC<LoadingDialogProps> = ({\n  size = 64,\n  message = 'modal.loadingDialog.loading'\n}) => (\n  <StyledLoadingDialog>\n    <div className=\"loading-content\">\n      <StyledSpinner>\n        <LoadingSpinner size={size} />\n      </StyledSpinner>\n      <div className=\"loading-message\">\n        <FormattedMessage id={message} />\n      </div>\n    </div>\n  </StyledLoadingDialog>\n);\n\nexport default LoadingDialog;\n"
  },
  {
    "path": "src/components/src/modals/modal-dialog.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Modal from '../common/modal';\n\nconst ModalDialogFactory = () => Modal;\n\nexport default ModalDialogFactory;\n"
  },
  {
    "path": "src/components/src/modals/modal-tabs.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\nimport {media} from '@kepler.gl/styles';\nimport {FormattedMessage, useIntl} from 'react-intl';\nimport {LoadingMethod} from './load-data-modal';\n\nconst ModalTab = styled.div`\n  align-items: flex-end;\n  display: flex;\n  border-bottom: 1px solid #d8d8d8;\n  margin-bottom: 32px;\n  justify-content: space-between;\n\n  .load-data-modal__tab__inner {\n    display: flex;\n    width: 100%;\n  }\n\n  .load-data-modal__tab__item.active {\n    color: ${props => props.theme.textColorLT};\n    border-bottom: 3px solid ${props => props.theme.textColorLT};\n    font-weight: 500;\n  }\n\n  ${media.portable`\n    font-size: 12px;\n  `};\n`;\n\nconst StyledLoadDataModalTabItem = styled.div`\n  border-bottom: 3px solid transparent;\n  cursor: pointer;\n  margin-left: 32px;\n  padding: 16px 0;\n  font-size: 14px;\n  font-weight: 400;\n  color: ${props => props.theme.subtextColorLT};\n\n  ${media.portable`\n    margin-left: 16px;\n    font-size: 12px;\n  `};\n\n  :first-child {\n    margin-left: 0;\n    padding-left: 0;\n  }\n\n  &:hover {\n    color: ${props => props.theme.textColorLT};\n  }\n`;\n\nconst noop = () => {\n  return;\n};\n\ninterface ModalTabItemProps {\n  currentMethod?: string;\n  method: LoadingMethod;\n  toggleMethod: (method: LoadingMethod) => void;\n}\n\ninterface ModalTabProps {\n  loadingMethods: LoadingMethod[];\n  toggleMethod: (method: LoadingMethod) => void;\n  currentMethod?: string;\n}\nexport const ModalTabItem: React.FC<ModalTabItemProps> = ({\n  currentMethod,\n  method,\n  toggleMethod\n}) => {\n  const onClick = useCallback(() => toggleMethod(method), [method, toggleMethod]);\n  const intl = useIntl();\n\n  return method.tabElementType ? (\n    <method.tabElementType onClick={onClick} intl={intl} />\n  ) : (\n    <StyledLoadDataModalTabItem\n      className={classnames('load-data-modal__tab__item', {\n        active: currentMethod && method.id === currentMethod\n      })}\n      onClick={onClick}\n    >\n      <div>{method.label ? <FormattedMessage id={method.label} /> : method.id}</div>\n    </StyledLoadDataModalTabItem>\n  );\n};\n\nfunction ModalTabsFactory() {\n  const ModalTabs: React.FC<ModalTabProps> = ({\n    currentMethod,\n    toggleMethod = noop,\n    loadingMethods = []\n  }) => (\n    <ModalTab className=\"load-data-modal__tab\">\n      <div className=\"load-data-modal__tab__inner\">\n        {loadingMethods.map(method => (\n          <ModalTabItem\n            key={method.id}\n            method={method}\n            currentMethod={currentMethod}\n            toggleMethod={toggleMethod}\n          />\n        ))}\n      </div>\n    </ModalTab>\n  );\n\n  return ModalTabs;\n}\n\nexport default ModalTabsFactory;\n"
  },
  {
    "path": "src/components/src/modals/overwrite-map-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport {CenterVerticalFlexbox} from '../common/styled-components';\nimport {UploadAnimation} from './status-panel';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport ImageModalContainer, {ImageModalContainerProps} from './image-modal-container';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {cleanupExportImage as cleanupExportImageAction} from '@kepler.gl/actions';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {ModalFooter} from '../common/modal';\n\nconst StyledMsg = styled.div`\n  margin-top: 24px;\n  font-size: 14px;\n`;\n\nconst StyledTitle = styled.span`\n  font-weight: 600;\n  color: black;\n`;\n\nconst StyledIcon = styled.div`\n  margin-top: 24px;\n`;\n\nconst StyledOverwriteMapModal = styled(CenterVerticalFlexbox)`\n  padding: 24px 12px;\n  min-height: 220px;\n`;\n\ntype OverwriteMapModalProps = {\n  mapSaved: string | null;\n  title: string;\n  isProviderLoading: boolean;\n\n  // callbacks\n  onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting'];\n  cleanupExportImage: typeof cleanupExportImageAction;\n  onConfirm: (provider: Provider) => void;\n  onCancel: () => void;\n};\n\nconst CONFIRM_BUTTON = {\n  large: true,\n  children: 'Yes',\n  disabled: false\n};\n\nconst OverwriteMapModalFactory = () => {\n  /**\n   * @type {React.FunctionComponent<OverwriteMapModalProps>}\n   */\n  const OverwriteMapModal: React.FC<OverwriteMapModalProps> = ({\n    mapSaved,\n    title,\n    isProviderLoading,\n    onUpdateImageSetting,\n    cleanupExportImage,\n    onCancel,\n    onConfirm\n  }) => {\n    const {provider} = useCloudListProvider();\n\n    const confirmButton = useMemo(\n      () => ({\n        ...CONFIRM_BUTTON,\n        disabled: !provider\n      }),\n      [provider]\n    );\n\n    return (\n      <ImageModalContainer\n        provider={provider}\n        onUpdateImageSetting={onUpdateImageSetting}\n        cleanupExportImage={cleanupExportImage}\n      >\n        <StyledOverwriteMapModal className=\"overwrite-map-modal\">\n          {isProviderLoading ? (\n            <StyledMsg>\n              <StyledTitle>\n                <FormattedMessage id={'modal.overwriteMap.title'} />\n              </StyledTitle>\n              <UploadAnimation icon={provider && provider.icon} />\n            </StyledMsg>\n          ) : (\n            <>\n              <StyledIcon>\n                {provider && provider.icon ? <provider.icon height=\"64px\" /> : null}\n              </StyledIcon>\n              <StyledMsg className=\"overwrite-map-msg\">\n                <StyledTitle>{title} </StyledTitle>\n                <FormattedMessage id={'modal.overwriteMap.alreadyExists'} values={{mapSaved}} />\n              </StyledMsg>\n            </>\n          )}\n        </StyledOverwriteMapModal>\n        <ModalFooter\n          cancel={onCancel}\n          confirm={() => provider && onConfirm(provider)}\n          confirmButton={confirmButton}\n        />\n      </ImageModalContainer>\n    );\n  };\n  return OverwriteMapModal;\n};\n\nexport const OverwriteMapModal = OverwriteMapModalFactory();\nexport default OverwriteMapModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/save-map-modal.spec.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\n\n/**\n * I decided to move the next to the actual file because it makes it\n * extremely easier to mock adn test features.\n * It's easier to mock items with jest using the relative path\n * rather than trying to mock imports like @kepler.gl/components\n * which creates several side effects.\n * Colocating tests is much easier\n */\n\nimport React from 'react';\nimport {fireEvent} from '@testing-library/react';\nimport SaveMapModalFactory from './save-map-modal';\nimport {renderWithTheme} from 'test/helpers/component-jest-utils';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {dataTestIds} from '@kepler.gl/constants';\n\nconst SaveMapModal = SaveMapModalFactory();\n\nconst DEFAULT_PROS = {\n  mapInfo: {\n    title: 'Test Map',\n    description: 'test'\n  },\n  exportImage: jest.fn(),\n  isProviderLoading: false,\n  providerError: null,\n  onUpdateImageSetting: jest.fn(),\n  cleanupExportImage: jest.fn(),\n  onSetMapInfo: jest.fn(),\n  onCancel: jest.fn(),\n  onConfirm: jest.fn()\n};\n\nconst UNDEFINED_MAP_TITLE_PROPS = {\n  ...DEFAULT_PROS,\n  mapInfo: {\n    description: undefined,\n    title: undefined\n  }\n};\n\nconst DEFAULT_PROVIDER = {\n  name: 'test provider',\n  icon: jest.fn(),\n  getManagementUrl: jest.fn().mockImplementation(() => 'provider.url')\n};\n\njest.mock('../hooks/use-cloud-list-provider', () => ({\n  useCloudListProvider: jest.fn().mockImplementation(() => ({\n    provider: null,\n    setProvider: jest.fn(),\n    cloudProviders: []\n  }))\n}));\n\ndescribe('SaveMapModal', () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  test('renders SaveMapModal component with provider set to null and map title set', () => {\n    const {getByText} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    const confirmButton = getByText('modal.button.save');\n    expect(confirmButton).toBeInTheDocument();\n    expect(confirmButton).toBeDisabled();\n  });\n\n  test('renders SaveMapModal component with provider set to null and map title not set', () => {\n    const {getByText} = renderWithTheme(<SaveMapModal {...UNDEFINED_MAP_TITLE_PROPS} />);\n    const confirmButton = getByText('modal.button.save');\n    expect(confirmButton).toBeInTheDocument();\n    expect(confirmButton).toBeDisabled();\n  });\n\n  test('renders SaveMapModal component with provider correctly set and map title not set', () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n    const {getByText} = renderWithTheme(<SaveMapModal {...UNDEFINED_MAP_TITLE_PROPS} />);\n    const confirmButton = getByText('modal.button.save');\n    expect(confirmButton).toBeInTheDocument();\n    expect(confirmButton).toBeDisabled();\n  });\n\n  test('renders SaveMapModal component with provider correctly set and map title set', () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n    const {getByText} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    const confirmButton = getByText('modal.button.save');\n    expect(confirmButton).toBeInTheDocument();\n    expect(confirmButton).toBeEnabled();\n  });\n\n  test('calls onCancel when cancel button is clicked', () => {\n    const {getByText} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    fireEvent.click(getByText('modal.button.defaultCancel'));\n    expect(DEFAULT_PROS.onCancel).toHaveBeenCalled();\n  });\n\n  test('calls onConfirm with provider when confirm button is clicked', () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n    const {getByText} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    const confirmButton = getByText('modal.button.save');\n    fireEvent.click(confirmButton);\n    expect(DEFAULT_PROS.onConfirm).toHaveBeenCalled();\n  });\n\n  test('does not render loading animation when isProviderLoading is true', () => {\n    const {queryAllByTestId} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    expect(queryAllByTestId(dataTestIds.providerLoading)).toHaveLength(0);\n  });\n\n  test('renders loading animation when isProviderLoading is true', () => {\n    const {getByTestId} = renderWithTheme(\n      <SaveMapModal {...DEFAULT_PROS} isProviderLoading={true} />\n    );\n    expect(getByTestId(dataTestIds.providerLoading)).toBeInTheDocument();\n  });\n\n  test('renders no error if provider error is undefined', () => {\n    const {queryAllByText} = renderWithTheme(<SaveMapModal {...DEFAULT_PROS} />);\n    expect(queryAllByText('modal.statusPanel.error')).toHaveLength(0);\n  });\n\n  test('displays provider error message when providerError is present', () => {\n    const {getByText} = renderWithTheme(\n      <SaveMapModal {...DEFAULT_PROS} providerError={{message: 'Error message'}} />\n    );\n    expect(getByText('modal.statusPanel.error')).toBeInTheDocument();\n  });\n\n  test('call onSetMapInfo upon typing map (provider is set)', () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n    const {getByTestId, getByPlaceholderText} = renderWithTheme(\n      <SaveMapModal {...UNDEFINED_MAP_TITLE_PROPS} />\n    );\n\n    const mapInfoPanel = getByTestId(dataTestIds.providerMapInfoPanel);\n    expect(mapInfoPanel).toBeInTheDocument();\n\n    const titleInput = getByPlaceholderText('Type map title');\n    expect(titleInput).toBeInTheDocument();\n\n    fireEvent.change(titleInput, {target: {value: 'first kepler map'}});\n    expect(DEFAULT_PROS.onSetMapInfo).toHaveBeenCalledWith({title: 'first kepler map'});\n  });\n\n  test('call onUpdateImageSetting', () => {\n    useCloudListProvider.mockImplementation(() => ({\n      provider: DEFAULT_PROVIDER,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    renderWithTheme(<SaveMapModal {...UNDEFINED_MAP_TITLE_PROPS} />);\n\n    // first time the component mount\n    expect(DEFAULT_PROS.onUpdateImageSetting).toHaveBeenCalledWith({\n      exporting: true\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/src/modals/save-map-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport ImageModalContainer, {ImageModalContainerProps} from './image-modal-container';\nimport {FlexContainer} from '../common/flex-container';\nimport StatusPanel, {UploadAnimation} from './status-panel';\nimport {ProviderSelect} from './cloud-components/provider-select';\nimport {MAP_THUMBNAIL_DIMENSION, MAP_INFO_CHARACTER, dataTestIds} from '@kepler.gl/constants';\n\nimport {\n  StyledModalContent,\n  InputLight,\n  TextAreaLight,\n  StyledExportSection,\n  StyledModalSection,\n  StyledModalInputFootnote\n} from '../common/styled-components';\nimport ImagePreview from '../common/image-preview';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {MapInfo, ExportImage} from '@kepler.gl/types';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {setMapInfo, cleanupExportImage as cleanupExportImageAction} from '@kepler.gl/actions';\nimport {ModalFooter} from '../common/modal';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\n\nconst StyledSaveMapModal = styled.div.attrs({\n  className: 'save-map-modal'\n})`\n  .save-map-modal-content {\n    min-height: 400px;\n    flex-direction: column;\n  }\n\n  .description {\n    width: 300px;\n  }\n\n  .image-preview-panel {\n    width: 300px;\n\n    .image-preview {\n      padding: 0;\n    }\n  }\n\n  .map-info-panel {\n    flex-direction: column;\n  }\n\n  .save-map-modal-description {\n    .modal-section-subtitle {\n      margin-left: 6px;\n    }\n  }\n`;\n\nconst StyledCompactExportSection = styled(StyledExportSection)`\n  margin: 5px 0;\n`;\n\nconst nop = () => {\n  return;\n};\nconst TEXT_AREA_LIGHT_STYLE = {resize: 'none'};\n\ntype CharacterLimits = {\n  title?: number;\n  description?: number;\n};\n\ntype SaveMapModalProps = {\n  mapInfo: MapInfo;\n  exportImage: ExportImage;\n  isProviderLoading: boolean;\n  providerError?: Error;\n  characterLimits?: CharacterLimits;\n\n  // callbacks\n  onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting'];\n  cleanupExportImage: typeof cleanupExportImageAction;\n  onSetMapInfo: typeof setMapInfo;\n  onConfirm: (provider: Provider) => void;\n  onCancel: () => void;\n};\n\ntype MapInfoPanelProps = Pick<SaveMapModalProps, 'mapInfo' | 'characterLimits'> & {\n  onChangeInput: (\n    type: string,\n    event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>\n  ) => void;\n};\n\nexport const MapInfoPanel: React.FC<MapInfoPanelProps> = ({\n  mapInfo,\n  characterLimits,\n  onChangeInput\n}) => {\n  const {description = '', title = ''} = mapInfo;\n  return (\n    <div className=\"selection map-info-panel\" data-testid={dataTestIds.providerMapInfoPanel}>\n      <StyledModalSection className=\"save-map-modal-name\">\n        <div className=\"modal-section-title\">Name*</div>\n        <div>\n          <InputLight\n            id=\"map-title\"\n            type=\"text\"\n            value={title}\n            onChange={e => onChangeInput('title', e)}\n            placeholder=\"Type map title\"\n          />\n        </div>\n      </StyledModalSection>\n      <StyledModalSection>\n        <FlexContainer className=\"save-map-modal-description\">\n          <div className=\"modal-section-title\">Description</div>\n          <div className=\"modal-section-subtitle\">(optional)</div>\n        </FlexContainer>\n        <div>\n          <TextAreaLight\n            rows={3}\n            id=\"map-description\"\n            style={TEXT_AREA_LIGHT_STYLE as React.CSSProperties}\n            value={description}\n            onChange={e => onChangeInput('description', e)}\n            placeholder=\"Type map description\"\n          />\n        </div>\n        <StyledModalInputFootnote\n          error={\n            Boolean(characterLimits?.description) &&\n            description.length > Number(characterLimits?.description)\n          }\n        >\n          {description.length}/{characterLimits?.description || MAP_INFO_CHARACTER.description}{' '}\n          characters\n        </StyledModalInputFootnote>\n      </StyledModalSection>\n    </div>\n  );\n};\n\nconst SaveMapHeader = ({cloudProviders}) => {\n  return (\n    <StyledExportSection>\n      <div className=\"description\">\n        <div className=\"title\">\n          <FormattedMessage id={'modal.saveMap.title'} />\n        </div>\n        <div className=\"subtitle\">\n          <FormattedMessage id={'modal.saveMap.subtitle'} />\n        </div>\n      </div>\n      <ProviderSelect cloudProviders={cloudProviders} />\n    </StyledExportSection>\n  );\n};\n\nconst STYLED_EXPORT_SECTION_STYLE = {margin: '2px 0'};\nconst PROVIDER_MANAGER_URL_STYLE = {textDecoration: 'underline'};\n\nfunction SaveMapModalFactory() {\n  const SaveMapModal: React.FC<SaveMapModalProps> = ({\n    mapInfo,\n    exportImage,\n    characterLimits = MAP_INFO_CHARACTER,\n    isProviderLoading,\n    providerError,\n    onUpdateImageSetting = nop,\n    cleanupExportImage,\n    onSetMapInfo,\n    onCancel,\n    onConfirm\n  }) => {\n    const {provider, cloudProviders} = useCloudListProvider();\n\n    const onChangeInput = (\n      key: string,\n      {target: {value}}: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>\n    ) => {\n      onSetMapInfo({[key]: value});\n    };\n\n    const confirmButton = useMemo(\n      () => ({\n        large: true,\n        disabled: Boolean(!(provider && mapInfo.title)),\n        children: 'modal.button.save'\n      }),\n      [provider, mapInfo]\n    );\n\n    const confirm = useCallback(() => {\n      if (provider) {\n        onConfirm(provider);\n      }\n    }, [onConfirm, provider]);\n\n    return (\n      <ImageModalContainer\n        provider={provider}\n        onUpdateImageSetting={onUpdateImageSetting}\n        cleanupExportImage={cleanupExportImage}\n      >\n        <StyledSaveMapModal>\n          <StyledModalContent className=\"save-map-modal-content\">\n            <SaveMapHeader cloudProviders={cloudProviders} />\n            {provider && (\n              <>\n                {provider.getManagementUrl ? (\n                  <StyledExportSection style={STYLED_EXPORT_SECTION_STYLE}>\n                    <div className=\"selection\">\n                      <a\n                        key={1}\n                        href={provider.getManagementUrl()}\n                        target=\"_blank\"\n                        rel=\"noopener noreferrer\"\n                        style={PROVIDER_MANAGER_URL_STYLE}\n                      >\n                        Go to your Kepler.gl {provider.displayName} page\n                      </a>\n                    </div>\n                  </StyledExportSection>\n                ) : null}\n                <StyledCompactExportSection>\n                  <div className=\"description image-preview-panel\">\n                    <ImagePreview\n                      exportImage={exportImage}\n                      width={MAP_THUMBNAIL_DIMENSION.width}\n                      showDimension={false}\n                    />\n                  </div>\n                  {isProviderLoading ? (\n                    <div\n                      data-testid={dataTestIds.providerLoading}\n                      className=\"selection map-saving-animation\"\n                    >\n                      <UploadAnimation icon={provider && provider.icon} />\n                    </div>\n                  ) : (\n                    <MapInfoPanel\n                      mapInfo={mapInfo}\n                      characterLimits={characterLimits}\n                      onChangeInput={onChangeInput}\n                    />\n                  )}\n                </StyledCompactExportSection>\n              </>\n            )}\n            {providerError ? (\n              <StatusPanel\n                isLoading={false}\n                error={providerError.message}\n                providerIcon={provider && provider.icon}\n              />\n            ) : null}\n          </StyledModalContent>\n        </StyledSaveMapModal>\n        <ModalFooter cancel={onCancel} confirm={confirm} confirmButton={confirmButton} />\n      </ImageModalContainer>\n    );\n  };\n\n  return SaveMapModal;\n}\n\nexport default SaveMapModalFactory;\n"
  },
  {
    "path": "src/components/src/modals/share-map-modal.spec.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport React from 'react';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {renderWithTheme} from 'test/helpers/component-jest-utils';\nimport ShareMapUrlModalFactory from './share-map-modal';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {act} from '@testing-library/react';\nimport {ThemeProvider} from 'styled-components';\nimport {IntlProvider} from 'react-intl';\nimport {theme} from '@kepler.gl/styles';\nimport {messages} from '@kepler.gl/localization';\n\njest.mock('../hooks/use-cloud-list-provider', () => ({\n  useCloudListProvider: jest.fn().mockImplementation(() => ({\n    provider: null,\n    setProvider: jest.fn(),\n    cloudProviders: []\n  }))\n}));\n\nconst ShareMapUrlModal = ShareMapUrlModalFactory();\n\nconst DEFAULT_PROPS = {\n  isProviderLoading: false,\n  onExport: jest.fn(),\n  providerError: null,\n  successInfo: undefined,\n  onUpdateImageSetting: jest.fn(),\n  cleanupExportImage: jest.fn()\n};\n\ndescribe('ShareMapModal', () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  test('renders only list of providers', () => {\n    const {getByText, queryByTestId} = renderWithTheme(<ShareMapUrlModal {...DEFAULT_PROPS} />);\n    expect(getByText('modal.shareMap.title')).toBeInTheDocument();\n    expect(queryByTestId(dataTestIds.providerShareMap)).toBeNull();\n  });\n\n  test('renders list of provider and sharing section', () => {\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue([]),\n      hasSharingUrl: jest.fn().mockImplementation(() => true)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const {getByText, getByTestId} = renderWithTheme(<ShareMapUrlModal {...DEFAULT_PROPS} />);\n    expect(getByText('modal.shareMap.title')).toBeInTheDocument();\n    expect(getByTestId(dataTestIds.providerShareMap)).toBeInTheDocument();\n  });\n\n  test('renders loading when isLoading is set to true', () => {\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue([]),\n      hasSharingUrl: jest.fn().mockImplementation(() => true)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    const providerLoadingProps = {\n      ...DEFAULT_PROPS,\n      isProviderLoading: true\n    };\n\n    const {getByText} = renderWithTheme(<ShareMapUrlModal {...providerLoadingProps} />);\n    expect(getByText('modal.statusPanel.mapUploading')).toBeInTheDocument();\n  });\n\n  test('calls onExport when provider is set correctly', () => {\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue([]),\n      hasSharingUrl: jest.fn().mockImplementation(() => true)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    renderWithTheme(<ShareMapUrlModal {...DEFAULT_PROPS} />);\n\n    expect(DEFAULT_PROPS.onExport).toHaveBeenCalled();\n  });\n\n  test('calls onExport after provider was updated', () => {\n    const {rerender} = renderWithTheme(<ShareMapUrlModal {...DEFAULT_PROPS} />);\n\n    const mapProvider = {\n      name: 'test provider',\n      icon: jest.fn(),\n      getManagementUrl: jest.fn().mockImplementation(() => 'provider.url'),\n      listMaps: jest.fn().mockResolvedValue([]),\n      hasSharingUrl: jest.fn().mockImplementation(() => true)\n    };\n    useCloudListProvider.mockImplementation(() => ({\n      provider: mapProvider,\n      setProvider: jest.fn(),\n      cloudProviders: []\n    }));\n\n    act(() => {\n      rerender(\n        <ThemeProvider theme={theme}>\n          <IntlProvider locale=\"en\" messages={messages}>\n            <ShareMapUrlModal {...DEFAULT_PROPS} />\n          </IntlProvider>\n        </ThemeProvider>\n      );\n    });\n\n    expect(DEFAULT_PROPS.onExport).toHaveBeenCalled();\n  });\n\n  it('displays share URL when provided', () => {\n    const shareUrl = 'http://example.com';\n    const {getByText} = renderWithTheme(\n      <ShareMapUrlModal {...DEFAULT_PROPS} successInfo={{shareUrl}} />\n    );\n    expect(getByText('Share Url')).toBeInTheDocument();\n  });\n\n  it('renders errors', () => {\n    const {getByText} = renderWithTheme(\n      <ShareMapUrlModal {...DEFAULT_PROPS} providerError={new Error('timeout')} />\n    );\n    expect(getByText('modal.statusPanel.error')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/components/src/modals/share-map-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useState, useMemo} from 'react';\nimport styled, {ThemeProvider} from 'styled-components';\nimport {CopyToClipboard} from 'react-copy-to-clipboard';\nimport {themeLT} from '@kepler.gl/styles';\nimport ImageModalContainer, {ImageModalContainerProps} from './image-modal-container';\n\nimport {\n  StyledModalContent,\n  StyledExportSection,\n  InputLight,\n  Button\n} from '../common/styled-components';\nimport StatusPanel from './status-panel';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {useCloudListProvider} from '../hooks/use-cloud-list-provider';\nimport {ProviderSelect} from './cloud-components/provider-select';\nimport {Provider} from '@kepler.gl/cloud-providers';\nimport {cleanupExportImage as cleanupExportImageAction} from '@kepler.gl/actions';\nimport {dataTestIds} from '@kepler.gl/constants';\n\nexport const StyledInputLabel = styled.label`\n  font-size: 12px;\n  color: ${props => props.theme.textColorLT};\n  letter-spacing: 0.2px;\n`;\n\nexport const StyleSharingUrl = styled.div.attrs({\n  className: 'sharing-url'\n})`\n  width: 100%;\n  display: flex;\n  margin-bottom: 14px;\n  flex-direction: column;\n\n  input {\n    border-right: 0;\n  }\n\n  .button {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n`;\n\ninterface SharingUrlProps {\n  url: string;\n  message?: string;\n}\n\nexport const SharingUrl: React.FC<SharingUrlProps> = ({url, message = ''}) => {\n  const [copied, setCopy] = useState(false);\n  return (\n    <StyleSharingUrl>\n      <StyledInputLabel>{message}</StyledInputLabel>\n      <div style={{display: 'flex'}}>\n        <InputLight type=\"text\" value={url} readOnly />\n        <CopyToClipboard text={url} onCopy={() => setCopy(true)}>\n          <Button width=\"80px\">{copied ? 'Copied!' : 'Copy'}</Button>\n        </CopyToClipboard>\n      </div>\n    </StyleSharingUrl>\n  );\n};\nconst nop = () => {\n  return;\n};\n\nconst StyledShareMapModal = styled(StyledModalContent)`\n  padding: 24px 72px 40px 72px;\n  margin: 0 -72px -40px -72px;\n  display: flex;\n  flex-direction: column;\n`;\n\nconst StyledInnerDiv = styled.div`\n  min-height: 500px;\n`;\n\nconst UNDERLINE_TEXT_DECORATION_STYLE = {textDecoration: 'underline'};\n\nconst ShareMapHeader = ({cloudProviders}) => {\n  const shareableCloudProviders = useMemo(\n    () => cloudProviders.filter(cp => cp.hasSharingUrl()),\n    [cloudProviders]\n  );\n\n  return (\n    <StyledExportSection>\n      <div className=\"description\">\n        <div className=\"title\">\n          <FormattedMessage id={'modal.shareMap.title'} />\n        </div>\n      </div>\n      <ProviderSelect cloudProviders={shareableCloudProviders} />\n    </StyledExportSection>\n  );\n};\n\ninterface ShareMapUrlModalFactoryProps {\n  isProviderLoading?: boolean;\n  onExport?: (provider: Provider) => void;\n  providerError?: string;\n  successInfo?: {shareUrl?: string; folderLink?: string};\n  onUpdateImageSetting: ImageModalContainerProps['onUpdateImageSetting'];\n  cleanupExportImage: typeof cleanupExportImageAction;\n}\n\nexport default function ShareMapUrlModalFactory() {\n  const ShareMapUrlModal: React.FC<ShareMapUrlModalFactoryProps> = ({\n    isProviderLoading = false,\n    onExport = nop,\n    providerError = null,\n    successInfo = {},\n    onUpdateImageSetting = nop,\n    cleanupExportImage\n  }) => {\n    const {provider, cloudProviders} = useCloudListProvider();\n    const {shareUrl, folderLink} = successInfo;\n\n    useEffect(() => {\n      if (provider) {\n        onExport(provider);\n      }\n    }, [onExport, provider]);\n\n    return (\n      <ThemeProvider theme={themeLT}>\n        <ImageModalContainer\n          provider={provider}\n          onUpdateImageSetting={onUpdateImageSetting}\n          cleanupExportImage={cleanupExportImage}\n        >\n          <StyledShareMapModal className=\"export-cloud-modal\">\n            <ShareMapHeader cloudProviders={cloudProviders} />\n            {provider?.hasSharingUrl() ? (\n              <StyledInnerDiv data-testid={dataTestIds.providerShareMap}>\n                <StyledExportSection>\n                  <div className=\"description\">\n                    <div className=\"title\">\n                      <FormattedMessage id={'modal.shareMap.shareUriTitle'} />\n                    </div>\n                    <div className=\"subtitle\">\n                      <FormattedMessage id={'modal.shareMap.shareUriSubtitle'} />\n                    </div>\n                  </div>\n                  <div className=\"selection\">\n                    <div className=\"title warning\">\n                      <FormattedMessage id={'modal.shareMap.shareDisclaimer'} />\n                    </div>\n                  </div>\n                </StyledExportSection>\n                <StyledExportSection disabled={isProviderLoading}>\n                  <div className=\"description\">\n                    <div className=\"title\">\n                      <FormattedMessage id={'modal.shareMap.cloudTitle'} />\n                    </div>\n                    <div className=\"subtitle\">\n                      <FormattedMessage id={'modal.shareMap.cloudSubtitle'} />\n                    </div>\n                  </div>\n                </StyledExportSection>\n                {isProviderLoading || providerError ? (\n                  <StatusPanel\n                    isLoading={isProviderLoading}\n                    error={providerError}\n                    providerIcon={provider.icon}\n                  />\n                ) : null}\n                {shareUrl && (\n                  <StyledExportSection>\n                    <div className=\"description\">\n                      <div className=\"title\">Share Url</div>\n                    </div>\n                    <div className=\"selection\">\n                      <SharingUrl key={0} url={shareUrl} />\n                      {provider && folderLink && (\n                        <a\n                          key={1}\n                          href={folderLink}\n                          target=\"_blank\"\n                          rel=\"noopener noreferrer\"\n                          style={UNDERLINE_TEXT_DECORATION_STYLE}\n                        >\n                          {provider.name}\n                        </a>\n                      )}\n                    </div>\n                  </StyledExportSection>\n                )}\n              </StyledInnerDiv>\n            ) : null}\n          </StyledShareMapModal>\n        </ImageModalContainer>\n      </ThemeProvider>\n    );\n  };\n\n  return ShareMapUrlModal;\n}\n"
  },
  {
    "path": "src/components/src/modals/status-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType} from 'react';\nimport styled from 'styled-components';\nimport {MapIcon} from '../common/icons';\nimport {StyledExportSection} from '../common/styled-components';\nimport ErrorDisplay from './error-display';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {IconProps} from '@kepler.gl/cloud-providers';\n\nconst StyledUploader = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: flex-start;\n`;\n\nconst StyledMapIcon = styled.div`\n  color: ${props => props.theme.textColorLT};\n  margin-right: 16px;\n  margin-top: 4px;\n`;\n\nconst StyledSvg = styled.svg`\n  margin-right: 16px;\n\n  line {\n    stroke: ${props => props.theme.selectBorderColorLT};\n    stroke-width: 4;\n    stroke-linecap: square;\n    stroke-dasharray: 5 12;\n    animation: dash-animation 25s infinite linear;\n  }\n  circle {\n    fill: ${props => props.theme.selectBorderColorLT};\n  }\n\n  @keyframes dash-animation {\n    to {\n      stroke-dashoffset: -1000;\n    }\n  }\n`;\n\nconst Line = () => (\n  <StyledSvg height=\"5px\" width=\"150px\">\n    <line x1=\"0\" y1=\"4\" x2=\"150\" y2=\"4\" />\n  </StyledSvg>\n);\n\ninterface UploadAnimationProps {\n  icon?: ComponentType<IconProps> | null;\n}\n\nexport const UploadAnimation: React.FC<UploadAnimationProps> = props => (\n  <StyledUploader>\n    <StyledMapIcon>\n      <MapIcon height=\"48px\" />\n    </StyledMapIcon>\n    <Line />\n    {props.icon && <props.icon height=\"64px\" />}\n  </StyledUploader>\n);\n\ninterface StatusPanelProps {\n  error?: string | null;\n  isLoading?: boolean;\n  providerIcon?: ComponentType<IconProps> | null;\n}\n\nconst StatusPanel: React.FC<StatusPanelProps> = ({error, isLoading, providerIcon}) => (\n  <StyledExportSection>\n    <div className=\"description\">\n      <div className=\"title\">\n        {isLoading ? (\n          <FormattedMessage id={'modal.statusPanel.mapUploading'} />\n        ) : error ? (\n          <FormattedMessage id={'modal.statusPanel.error'} />\n        ) : null}\n      </div>\n    </div>\n    <div className=\"selection\">\n      {isLoading && <UploadAnimation icon={providerIcon} />}\n      {error && <ErrorDisplay error={error} />}\n    </div>\n  </StyledExportSection>\n);\n\nexport default StatusPanel;\n"
  },
  {
    "path": "src/components/src/modals/storage-map-viewer.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport moment from 'moment';\nimport {LeftArrow} from '../common/icons';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nconst imageH = 108;\n\nconst StyledAssetGallery = styled.div.attrs({\n  className: 'storage-asset-gallery'\n})`\n  display: flex;\n  justify-content: flex-start;\n  flex-wrap: wrap;\n`;\n\nconst StyledAssetItem = styled.div.attrs({\n  className: 'asset__item'\n})`\n  width: 23%;\n  margin-right: 2%;\n  max-width: 500px;\n  margin-bottom: 40px;\n  height: 245px;\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n\n  :last {\n    margin-right: 0;\n  }\n\n  .asset__title {\n    font-size: 12px;\n    font-weight: 500;\n    color: ${props => props.theme.textColorLT};\n    line-height: 18px;\n    height: 32px;\n  }\n\n  .asset__image {\n    border-radius: 4px;\n    overflow: hidden;\n    margin-bottom: 12px;\n    opacity: 0.9;\n    transition: opacity 0.4s ease;\n    position: relative;\n    line-height: 0;\n    height: ${imageH}px;\n    flex-shrink: 0;\n\n    img {\n      max-width: 100%;\n    }\n    &:hover {\n      cursor: pointer;\n      opacity: 1;\n    }\n  }\n\n  .asset__image__caption {\n    font-size: 11px;\n    font-weight: 400;\n    line-height: 16px;\n    margin-top: 10px;\n    height: 48px;\n    overflow: hidden;\n    display: -webkit-box;\n    text-overflow: ellipsis;\n    -webkit-line-clamp: 3;\n    -webkit-box-orient: vertical;\n  }\n\n  .asset__last-updated {\n    font-size: 11px;\n    color: ${props => props.theme.textColorLT};\n  }\n`;\n\nconst BackLink = styled.div`\n  display: flex;\n  font-size: 14px;\n  align-items: center;\n  color: ${props => props.theme.titleColorLT};\n  cursor: pointer;\n  margin-bottom: 40px;\n\n  &:hover {\n    font-weight: 500;\n  }\n\n  span {\n    white-space: nowrap;\n  }\n  svg {\n    margin-right: 10px;\n  }\n`;\n\nconst StyledError = styled.div`\n  color: red;\n  font-size: 14px;\n  margin-bottom: 16px;\n`;\n\nconst getDuration = (last = 0) => moment.duration(new Date().valueOf() - last).humanize();\n\ninterface Asset {\n  imageUrl?: string;\n  label?: string;\n  title?: string;\n  description?: string;\n  lastUpdated?: number;\n  id?: string;\n}\n\ninterface AssetItemProps {\n  asset: Asset;\n  onClick: React.MouseEventHandler<HTMLDivElement>;\n}\n\nconst AssetItem: React.FC<AssetItemProps> = ({asset, onClick}) => (\n  <StyledAssetItem>\n    <div className=\"asset__image\" onClick={onClick}>\n      {asset.imageUrl && <img src={asset.imageUrl} />}\n    </div>\n    <div className=\"asset__title\">{asset.label || asset.title}</div>\n    <div className=\"asset__image__caption\">{asset.description}</div>\n    {asset.lastUpdated ? (\n      <div className=\"asset__last-updated\">\n        <FormattedMessage\n          id={'modal.storageMapViewer.lastModified'}\n          values={{lastUpdated: getDuration(asset.lastUpdated)}}\n        />\n      </div>\n    ) : null}\n  </StyledAssetItem>\n);\n\ninterface StorageAssetsViewerProps {\n  assets: Asset[];\n  onLoadAsset: (asset: Asset) => void;\n  back?: React.MouseEventHandler<HTMLDivElement>;\n  error?: {message?: string};\n}\n\nclass StorageAssetsViewer extends React.Component<StorageAssetsViewerProps> {\n  render() {\n    const {assets, onLoadAsset, back, error} = this.props;\n\n    return (\n      <div className=\"storage-asset-viewer\">\n        <BackLink onClick={back}>\n          <LeftArrow height=\"12px\" />\n          <span>\n            <FormattedMessage id={'modal.storageMapViewer.back'} />\n          </span>\n        </BackLink>\n        {error && <StyledError>{error.message}</StyledError>}\n        <StyledAssetGallery>\n          {assets.map(sp => (\n            <AssetItem asset={sp} key={sp.id} onClick={() => onLoadAsset(sp)} />\n          ))}\n        </StyledAssetGallery>\n      </div>\n    );\n  }\n}\n\nexport default StorageAssetsViewer;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/common.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport type {WMSCapabilities} from '@loaders.gl/wms';\n\nimport type {VectorTileMetadata} from '@kepler.gl/table';\nimport type {StacTypes} from '@kepler.gl/types';\n\nexport type DatasetCreationAttributes = {\n  name: string;\n  type: string;\n  metadata: Record<string, any>;\n};\n\nexport type MetaResponse = {\n  metadata?: VectorTileMetadata | StacTypes.CompleteSTACObject | WMSCapabilities | null;\n  dataset?: DatasetCreationAttributes | null;\n  loading?: boolean;\n  error?: Error | null;\n};\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/load-data-footer.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {injectIntl, WrappedComponentProps} from 'react-intl';\nimport styled, {withTheme} from 'styled-components';\n\nimport {Button} from '../../common';\nimport LoadingSpinner from '../../common/loading-spinner';\n\nconst AddDataButton = styled(Button)<{isLoading?: boolean}>`\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  opacity: ${props => (props.disabled ? 0.6 : 1)};\n  height: 40px;\n  padding: 9px 32px;\n  width: ${props => props.width};\n\n  svg {\n    margin-right: ${props => (props.isLoading ? '0' : '6px')};\n  }\n`;\n\nconst LoadDataFooterContainer = styled.div.attrs({\n  className: 'load-data-footer'\n})`\n  /* position: absolute; */\n  width: 100%;\n  bottom: 0;\n\n  display: flex;\n  justify-content: space-between;\n  padding: 21px 21px 0px 72px;\n  margin: 24px -72px 0;\n  align-items: center;\n`;\n\nconst ErrorContainer = styled.div`\n  color: red;\n  padding-left: 15px;\n  display: inline-block;\n`;\n\ntype LoadDataFooterProps = {\n  disabled?: boolean;\n  isLoading?: boolean;\n  onConfirm: () => void;\n  confirmText: string;\n  prependText?: string;\n  errorText?: string | null;\n};\n\ntype ThemeProps = {\n  theme: any;\n};\n\nconst LoadDataFooter = ({\n  disabled,\n  intl,\n  isLoading,\n  onConfirm,\n  confirmText,\n  prependText = '',\n  errorText = '',\n  theme\n}: LoadDataFooterProps & WrappedComponentProps & ThemeProps) => {\n  return (\n    <LoadDataFooterContainer>\n      <div>\n        {prependText}\n        <AddDataButton\n          disabled={disabled}\n          isLoading={isLoading}\n          onClick={onConfirm}\n          width=\"130px\"\n          cta\n        >\n          {isLoading && (\n            <LoadingSpinner color={theme.borderColorLT} borderColor=\"transparent\" size={24} />\n          )}\n          {isLoading\n            ? null\n            : intl.formatMessage({\n                id: confirmText\n              })}\n        </AddDataButton>\n        {errorText && <ErrorContainer>{errorText}</ErrorContainer>}\n      </div>\n    </LoadDataFooterContainer>\n  );\n};\n\nexport default withTheme(injectIntl(LoadDataFooter)) as React.FC<\n  Omit<LoadDataFooterProps, 'theme'>\n>;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/load-tileset.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, useState} from 'react';\nimport {IntlShape} from 'react-intl';\nimport JSONPretty from 'react-json-pretty';\nimport {AutoSizer} from 'react-virtualized';\nimport styled from 'styled-components';\n\nimport {VectorTileIcon, RasterTileIcon, WMSLayerIcon} from '@kepler.gl/layers';\nimport {getError, getApplicationConfig} from '@kepler.gl/utils';\n\nimport {MetaResponse} from './common';\nimport LoadDataFooter from './load-data-footer';\nimport TilesetIcon from './tileset-icon';\nimport TilesetVectorForm from './tileset-vector-form';\nimport TilesetRasterForm from './tileset-raster-form';\n\nimport TilesetWMSForm from './tileset-wms-form';\n\nconst WIDTH_ICON = '70px';\n\nconst LoadTilesetTabContainer = styled.div`\n  color: ${props => props.theme.AZURE};\n`;\n\nconst Container = styled.div`\n  display: grid;\n  grid-template-columns: repeat(2, minmax(0, 1fr));\n  grid-gap: 20px;\n  background-color: ${props => props.theme.WHITE};\n`;\n\nconst TilesetTypeContainer = styled.div`\n  display: grid;\n  grid-template-columns: repeat(4, ${WIDTH_ICON});\n  column-gap: 10px;\n  margin-bottom: 20px;\n`;\n\nconst MetaContainer = styled.div`\n  display: flex;\n  max-height: 400px;\n  background-color: ${({theme}) => theme.editorBackground};\n`;\n\nexport interface MetaInnerContainerProps {\n  width: number;\n  height: number;\n}\n\nconst MetaInnerContainer = styled.div<MetaInnerContainerProps>`\n  position: relative;\n  border: 1px solid ${props => props.theme.selectBorderColorLT};\n  background-color: white;\n  border-radius: 2px;\n  display: inline-block;\n  font: inherit;\n  line-height: 1.5em;\n  padding: 0.5em 3.5em 0.5em 1em;\n  box-sizing: border-box;\n  overflow-y: scroll;\n  overflow-x: auto;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  height: ${props => props.height}px;\n  width: ${props => props.width}px;\n  color: ${props => props.theme.textColorLT};\n  font-size: 11px;\n  font-family: ${props => props.theme.fontFamily};\n  max-width: 600px;\n`;\n\nconst StyledHeaderMessage = styled.div`\n  color: ${props => props.theme.textColorLT};\n  font-size: 14px;\n`;\n\ntype LoadTilesetTabProps = {\n  meta: {[key: string]: any};\n  isAddingDatasets: boolean;\n  onTilesetAdded: (tilesetInfo: any, metadata?: any) => void;\n  intl: IntlShape;\n};\n\nconst TILE_TYPES = [\n  {\n    id: 'vectorTile',\n    label: 'Vector Tile',\n    Icon: VectorTileIcon,\n    Component: TilesetVectorForm\n  },\n  {\n    id: 'rasterTile',\n    label: 'Raster Tile',\n    Icon: RasterTileIcon,\n    Component: TilesetRasterForm\n  },\n  {\n    id: 'wms',\n    label: 'WMS',\n    Icon: WMSLayerIcon,\n    Component: TilesetWMSForm\n  }\n];\n\nfunction isReady(response) {\n  return response.dataset && !response.loading && !response.error;\n}\n\nfunction LoadTilesetTabFactory() {\n  const LoadTilesetTab: React.FC<LoadTilesetTabProps> = ({onTilesetAdded, isAddingDatasets}) => {\n    const [typeIndex, setTypeIndex] = useState<number>(0);\n    const [response, setResponse] = useState<MetaResponse>({});\n\n    const error = response.error;\n    const loading = response.loading;\n    const data = response.metadata;\n    const jsonDataText = useMemo(() => JSON.stringify(data, null, 2), [data]);\n\n    const createTileDataset = useCallback(() => {\n      const {dataset, metadata} = response;\n      if (dataset) {\n        onTilesetAdded(dataset, metadata);\n      }\n    }, [onTilesetAdded, response]);\n\n    // temp patch to hide raster tile layer while in development\n    const enableRasterTileLayer = getApplicationConfig().enableRasterTileLayer;\n    const enableWMSLayer = getApplicationConfig().enableWMSLayer;\n\n    // Filter tile types based on application config\n    const tileTypes = useMemo(() => {\n      const types = TILE_TYPES.filter(tileType => {\n        if (tileType.id === 'rasterTile') {\n          return enableRasterTileLayer;\n        }\n        if (tileType.id === 'wms') {\n          return enableWMSLayer;\n        }\n        return true; // Include all other types by default\n      });\n      return types;\n    }, [enableRasterTileLayer, enableWMSLayer]);\n\n    const CurrentForm = tileTypes[typeIndex].Component;\n\n    return (\n      <LoadTilesetTabContainer>\n        <Container>\n          <div>\n            <StyledHeaderMessage>Tileset Type</StyledHeaderMessage>\n\n            <TilesetTypeContainer className=\"tileset-type\">\n              {tileTypes.map((tileType, index) => (\n                <TilesetIcon\n                  key={tileType.label}\n                  name={tileType.label}\n                  Icon={<tileType.Icon height={WIDTH_ICON} />}\n                  onClick={() => setTypeIndex(index)}\n                  selected={typeIndex === index}\n                />\n              ))}\n            </TilesetTypeContainer>\n            <div>\n              <CurrentForm setResponse={setResponse} />\n            </div>\n          </div>\n          <MetaContainer>\n            {data && (\n              <AutoSizer>\n                {({height, width}) => (\n                  <MetaInnerContainer height={height} width={width}>\n                    <JSONPretty id=\"json-pretty\" json={jsonDataText} />\n                  </MetaInnerContainer>\n                )}\n              </AutoSizer>\n            )}\n          </MetaContainer>\n        </Container>\n        <LoadDataFooter\n          disabled={Boolean(error) || !isReady(response)}\n          isLoading={loading || isAddingDatasets}\n          onConfirm={createTileDataset}\n          confirmText=\"tilesetSetup.addTilesetText\"\n          errorText={error && getError(error)}\n        />\n      </LoadTilesetTabContainer>\n    );\n  };\n\n  return LoadTilesetTab;\n}\n\nexport default LoadTilesetTabFactory;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/tileset-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nconst TileIconContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  text-align: center;\n  cursor: pointer;\n`;\n\ntype TileIconProps = {\n  selected?: boolean;\n};\n\nconst TileIcon = styled.div<TileIconProps>`\n  background-color: ${props => props.theme.GREY10};\n  color: ${props =>\n    props.selected ? props.theme.primaryBtnBgdHover : props.theme.secondaryBtnBgd};\n  opacity: ${props => (props.selected ? 1 : 0.5)};\n`;\n\nconst TileLabel = styled.div<TileIconProps>`\n  font-weight: ${props => (props.selected ? 'bold' : 'normal')};\n`;\n\ntype TilesetIconProps = {\n  Icon: React.ReactNode;\n  name: string;\n  onClick: () => void;\n  selected?: boolean;\n};\n\nconst TilesetIcon: React.FC<TilesetIconProps> = ({Icon, name, onClick, selected = false}) => (\n  <TileIconContainer onClick={onClick}>\n    <TileIcon selected={selected}>{Icon}</TileIcon>\n    <TileLabel selected={selected}>{name}</TileLabel>\n  </TileIconContainer>\n);\n\nexport default TilesetIcon;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/tileset-raster-form.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useState} from 'react';\nimport styled from 'styled-components';\n\nimport {PMTilesMetadata} from '@loaders.gl/pmtiles';\n\nimport {isPMTilesUrl, validateUrl} from '@kepler.gl/common-utils';\nimport {DatasetType, RasterTileType, PMTilesType} from '@kepler.gl/constants';\nimport {JsonObjectOrArray} from '@kepler.gl/types';\nimport {parseRasterMetadata, parseVectorMetadata} from '@kepler.gl/table';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {default as useFetchJson} from '../../hooks/use-fetch-raster-tile-metadata';\nimport {DatasetCreationAttributes, MetaResponse} from './common';\nimport {InputLight} from '../../common';\nimport {Help} from '../../common/icons';\n\nconst TilesetInputContainer = styled.div`\n  display: grid;\n  grid-template-rows: repeat(3, 1fr);\n  row-gap: 0px;\n  font-size: 12px;\n`;\n\nconst TilesetInputDescription = styled.div`\n  text-align: center;\n  color: ${props => props.theme.AZURE200};\n  font-size: 11px;\n`;\n\nconst LabelRow = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nexport type RasterTilesetMeta = {\n  name: string;\n  metadataUrl: string;\n  rasterTileServerUrls?: string[];\n};\n\nexport function getDatasetAttributesFromRasterTile({\n  name,\n  metadataUrl,\n  rasterTileServerUrls\n}: RasterTilesetMeta): DatasetCreationAttributes {\n  const appConfig = getApplicationConfig();\n  return {\n    name,\n    type: DatasetType.RASTER_TILE,\n    metadata: {\n      metadataUrl,\n      ...(rasterTileServerUrls ? {rasterTileServerUrls} : {}),\n      // Persist raster server-related application config with the layer\n      rasterServerUseLatestTitiler: appConfig.rasterServerUseLatestTitiler,\n      rasterServerSupportsElevation: appConfig.rasterServerSupportsElevation,\n      rasterServerMaxRetries: appConfig.rasterServerMaxRetries,\n      rasterServerRetryDelay: appConfig.rasterServerRetryDelay,\n      rasterServerServerErrorsToRetry: appConfig.rasterServerServerErrorsToRetry,\n      rasterServerMaxPerServerRequests: appConfig.rasterServerMaxPerServerRequests\n    }\n  };\n}\n\ntype RasterTileFormProps = {\n  setResponse: (response: MetaResponse) => void;\n};\n\nconst InfoIconLink = styled.a`\n  margin-left: 4px;\n  color: ${props => props.theme.labelColor};\n  text-decoration: none;\n  display: inline-flex;\n  align-items: center;\n  line-height: 0;\n  vertical-align: middle;\n  opacity: 0.7;\n\n  &:hover {\n    opacity: 1;\n  }\n\n  svg {\n    display: block;\n  }\n`;\n\nconst RASTER_TILE_DOCUMENTATION_URL =\n  'https://docs.kepler.gl/docs/user-guides/c-types-of-layers/n-raster-tile-layer';\n\nconst parseMetadataAllowCollections = (\n  metadata: JsonObjectOrArray | PMTilesMetadata,\n  {metadataUrl, rasterTileType}: {metadataUrl: string; rasterTileType: RasterTileType}\n) => {\n  return rasterTileType === RasterTileType.PMTILES\n    ? parseVectorMetadata(metadata as PMTilesMetadata, {\n        tileUrl: metadataUrl\n      })\n    : parseRasterMetadata(metadata as JsonObjectOrArray, {allowCollections: true});\n};\n\nconst RasterTileForm: React.FC<RasterTileFormProps> = ({setResponse}) => {\n  const [tileName, setTileName] = useState<string>('');\n  const [tileNameWasModified, setTileNameWasModified] = useState<boolean>(false);\n  const [metadataUrl, setMetadataUrl] = useState<string>('');\n  const [rasterTileServerUrls, setRasterTileServerUrls] = useState<string>(\n    (getApplicationConfig().rasterServerUrls || []).join(',')\n  );\n\n  // Remove trailing slash to prevent issues with raster tile servers\n  const clearedMetadataUrl = metadataUrl.endsWith('/') ? metadataUrl.slice(0, -1) : metadataUrl;\n\n  const onTileNameChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      setTileNameWasModified(true);\n      setTileName(event.target.value);\n    },\n    [setTileName]\n  );\n\n  const onMetadataUrlChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      const {value} = event.target;\n      setMetadataUrl(value);\n\n      if (!tileNameWasModified) {\n        setTileName(value.split('/').filter(Boolean).pop() || '');\n      }\n    },\n    [tileNameWasModified]\n  );\n\n  const onRasterTileServerUrlsChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      setRasterTileServerUrls(event.target.value);\n    },\n    [setRasterTileServerUrls]\n  );\n\n  const {\n    data: metadata,\n    loading,\n    error: metaError\n  } = useFetchJson({\n    url: clearedMetadataUrl,\n    rasterTileType: isPMTilesUrl(clearedMetadataUrl) ? RasterTileType.PMTILES : RasterTileType.STAC,\n    process: parseMetadataAllowCollections\n  });\n\n  useEffect(() => {\n    if (tileName && clearedMetadataUrl) {\n      const pmtilesType = metadata?.pmtilesType;\n\n      if (pmtilesType === PMTilesType.MVT) {\n        return setResponse({\n          metadata,\n          dataset: null,\n          loading,\n          error: new Error('For .pmtiles in mvt format, please use the Vector Tile form.')\n        });\n      }\n\n      let error = metaError;\n\n      // check for raster tile servers for STAC items and collections\n      let rasterTileServers;\n      if (\n        !error\n        // We still need raster tile servers for PMTiles when we plan to use elevation\n      ) {\n        rasterTileServers = rasterTileServerUrls\n          .split(',')\n          .map(server => server.trim())\n          .filter(server => server);\n        if (\n          rasterTileServers.length < 1 ||\n          !rasterTileServers.every(server => validateUrl(server))\n        ) {\n          if (pmtilesType) {\n            // For raster tiles elevation support is optional\n            // TODO display a warning, but not a blocking error\n            rasterTileServers = [];\n          } else {\n            error = new Error(\n              'Provide valid raster tile server urls to support STAC and elevations.'\n            );\n          }\n        }\n      }\n\n      const dataset = getDatasetAttributesFromRasterTile({\n        name: tileName,\n        metadataUrl: clearedMetadataUrl,\n        rasterTileServerUrls: rasterTileServers\n      });\n\n      setResponse({\n        metadata,\n        dataset,\n        loading,\n        error\n      });\n    } else {\n      setResponse({\n        metadata,\n        dataset: null,\n        loading,\n        error: metaError\n      });\n    }\n  }, [\n    metadata,\n    loading,\n    metaError,\n    tileName,\n    clearedMetadataUrl,\n    rasterTileServerUrls,\n    setResponse\n  ]);\n\n  const showServerInput = getApplicationConfig().rasterServerShowServerInput;\n\n  return (\n    <TilesetInputContainer>\n      <div>\n        <label htmlFor=\"tileset-name\">Name</label>\n        <InputLight\n          id=\"tileset-name\"\n          placeholder=\"Name your tileset\"\n          value={tileName}\n          onChange={onTileNameChange}\n        />\n      </div>\n      <div>\n        <LabelRow>\n          <label htmlFor=\"tile-metadata\">Tileset metadata URL</label>\n          <InfoIconLink\n            href={RASTER_TILE_DOCUMENTATION_URL}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            aria-label=\"Open Raster Tile Layer documentation\"\n          >\n            <Help height=\"16px\" />\n          </InfoIconLink>\n        </LabelRow>\n        <InputLight\n          id=\"tile-metadata\"\n          placeholder=\"Tileset metadata URL\"\n          value={metadataUrl ?? undefined}\n          onChange={onMetadataUrlChange}\n        />\n        <TilesetInputDescription>\n          Supports raster .pmtiles. Limited support for STAC Items and Collections.\n        </TilesetInputDescription>\n      </div>\n      {showServerInput && (\n        <div>\n          <LabelRow>\n            <label htmlFor=\"tileset-raster-servers\">Raster tile servers</label>\n            <InfoIconLink\n              href={RASTER_TILE_DOCUMENTATION_URL}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              aria-label=\"Open Raster Tile Layer documentation\"\n            >\n              <Help height=\"16px\" />\n            </InfoIconLink>\n          </LabelRow>\n          <InputLight\n            id=\"tileset-raster-servers\"\n            placeholder=\"Raster tile servers (separated by commas)\"\n            value={rasterTileServerUrls}\n            onChange={onRasterTileServerUrlsChange}\n          />\n          <TilesetInputDescription>\n            Raster tile server URLs for Cloud Optimized GeoTIFF tilesets and elevation.\n          </TilesetInputDescription>\n        </div>\n      )}\n    </TilesetInputContainer>\n  );\n};\n\nexport default RasterTileForm;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/tileset-vector-form.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useState, useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport {isPMTilesUrl} from '@kepler.gl/common-utils';\nimport {\n  DatasetType,\n  PMTilesType,\n  RemoteTileFormat,\n  VectorTileDatasetMetadata,\n  REMOTE_TILE\n} from '@kepler.gl/constants';\nimport {TileJSON} from '@loaders.gl/mvt';\nimport {PMTilesMetadata} from '@loaders.gl/pmtiles';\nimport {getMetaUrl, parseVectorMetadata, VectorTileMetadata} from '@kepler.gl/table';\nimport {Merge} from '@kepler.gl/types';\n\nimport {default as useFetchVectorTileMetadata} from '../../hooks/use-fetch-vector-tile-metadata';\nimport {DatasetCreationAttributes, MetaResponse} from './common';\nimport {InputLight} from '../../common';\n\nconst TilesetInputContainer = styled.div`\n  display: grid;\n  grid-template-rows: repeat(3, 1fr);\n  row-gap: 18px;\n  font-size: 12px;\n`;\n\nconst TilesetInputDescription = styled.div`\n  text-align: center;\n  color: ${props => props.theme.AZURE200};\n  font-size: 11px;\n`;\n\nexport type VectorTilesetFormData = {\n  name: string;\n  dataUrl: string;\n  metadataUrl?: string;\n};\n\nexport type VectorTileDatasetCreationAttributes = Merge<\n  DatasetCreationAttributes,\n  {\n    metadata: VectorTileDatasetMetadata;\n  }\n>;\n\nexport function getDatasetAttributesFromVectorTile({\n  name,\n  dataUrl,\n  metadataUrl\n}: VectorTilesetFormData): VectorTileDatasetCreationAttributes {\n  return {\n    name,\n    type: DatasetType.VECTOR_TILE,\n    metadata: {\n      type: REMOTE_TILE,\n      remoteTileFormat: isPMTilesUrl(dataUrl) ? RemoteTileFormat.PMTILES : RemoteTileFormat.MVT,\n      tilesetDataUrl: dataUrl,\n      tilesetMetadataUrl: metadataUrl\n    }\n  };\n}\n\ntype TilesetVectorFormProps = {\n  setResponse: (response: MetaResponse) => void;\n};\n\nconst TilesetVectorForm: React.FC<TilesetVectorFormProps> = ({setResponse}) => {\n  const [tileName, setTileName] = useState<string>('');\n  const [tileUrl, setTileUrl] = useState<string>('');\n  const [metadataUrl, setMetadataUrl] = useState<string | null>('');\n  const [initialFetchError, setInitialFetchError] = useState<Error | null>(null);\n\n  const onTileNameChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      setTileName(event.target.value);\n    },\n    [setTileName]\n  );\n\n  const onTileMetaUrlChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      setMetadataUrl(event.target.value);\n    },\n    [setMetadataUrl]\n  );\n\n  const onTileUrlChange = useCallback(\n    async (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      const newTileUrl = event.target.value;\n      setTileUrl(newTileUrl);\n\n      const usePMTiles = isPMTilesUrl(newTileUrl);\n      const potentialMetadataUrl = usePMTiles ? newTileUrl : getMetaUrl(newTileUrl);\n      if (!metadataUrl && potentialMetadataUrl) {\n        // check if URL exists before setting it as the metadata URL\n        // Note: The {method: HEAD} request often fails, likely due to individual storage settings.\n        const resp = usePMTiles ? ({ok: true} as Response) : await fetch(potentialMetadataUrl);\n        if (resp.ok) {\n          setInitialFetchError(null);\n          setMetadataUrl(potentialMetadataUrl);\n        } else {\n          setInitialFetchError(\n            new Error(`Metadata loading failed: ${resp.status} ${resp.statusText}`)\n          );\n        }\n      } else {\n        setInitialFetchError(null);\n      }\n      if (!tileName) {\n        setTileName(newTileUrl.split('/').pop() || newTileUrl);\n      }\n    },\n    [setTileUrl, tileName, setMetadataUrl, metadataUrl]\n  );\n\n  const process = useMemo(() => {\n    return (value: PMTilesMetadata | TileJSON) =>\n      parseVectorMetadata(value, {tileUrl: metadataUrl});\n  }, [metadataUrl]);\n\n  const {\n    data: metadata,\n    loading,\n    error: metaError\n  } = useFetchVectorTileMetadata({\n    metadataUrl,\n    tilesetUrl: tileUrl,\n    remoteTileFormat: isPMTilesUrl(metadataUrl) ? RemoteTileFormat.PMTILES : RemoteTileFormat.MVT,\n    process\n  });\n\n  // reset initial fetch error if the metadata is available\n  if (metadata && initialFetchError) {\n    setInitialFetchError(null);\n  }\n\n  useEffect(() => {\n    if (tileName && tileUrl) {\n      if (metadata?.pmtilesType === PMTilesType.RASTER) {\n        return setResponse({\n          metadata,\n          dataset: null,\n          loading,\n          error: new Error('For .pmtiles in raster format, please use the Raster Tile form.')\n        });\n      }\n\n      const dataset = getDatasetAttributesFromVectorTile({\n        name: tileName,\n        dataUrl: tileUrl,\n        metadataUrl: metadataUrl ?? undefined\n      });\n      setResponse({\n        metadata,\n        dataset,\n        loading,\n        error: metaError || initialFetchError\n      });\n    } else {\n      setResponse({\n        metadata,\n        dataset: null,\n        loading,\n        error: metaError || initialFetchError\n      });\n    }\n  }, [\n    setResponse,\n    metadata,\n    loading,\n    metaError,\n    initialFetchError,\n    tileUrl,\n    tileName,\n    metadataUrl\n  ]);\n\n  useEffect(() => {\n    if (metadata) {\n      const name = (metadata as VectorTileMetadata).name;\n      if (name) {\n        setTileName(name);\n      }\n    }\n  }, [metadata]);\n\n  return (\n    <TilesetInputContainer>\n      <div>\n        <label htmlFor=\"tileset-name\">Name</label>\n        <InputLight\n          id=\"tileset-name\"\n          placeholder=\"Name your tileset\"\n          value={tileName}\n          onChange={onTileNameChange}\n        />\n      </div>\n      <div>\n        <label htmlFor=\"tile-url\">Tileset URL</label>\n        <InputLight\n          id=\"tile-url\"\n          placeholder=\"Tileset URL\"\n          value={tileUrl}\n          onChange={onTileUrlChange}\n        />\n        <TilesetInputDescription>\n          Requires &#123;x&#125;, &#123;y&#125;, &#123;z&#125; placeholders in URL or .pmtile\n          extension.\n        </TilesetInputDescription>\n      </div>\n      <div>\n        <label htmlFor=\"tile-metadata\">Tileset metadata URL</label>\n        <InputLight\n          id=\"tile-metadata\"\n          placeholder=\"Tileset metadata\"\n          value={metadataUrl ?? undefined}\n          onChange={onTileMetaUrlChange}\n        />\n        <TilesetInputDescription>\n          Optional, but recommended. Supports json, txt\n        </TilesetInputDescription>\n      </div>\n    </TilesetInputContainer>\n  );\n};\n\nexport default TilesetVectorForm;\n"
  },
  {
    "path": "src/components/src/modals/tilesets-modals/tileset-wms-form.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useState} from 'react';\nimport styled from 'styled-components';\nimport {WMSCapabilities} from '@loaders.gl/wms';\n\nimport {validateUrl} from '@kepler.gl/common-utils';\nimport {DatasetType, REMOTE_TILE, RemoteTileFormat, WMSDatasetMetadata} from '@kepler.gl/constants';\nimport {getWMSCapabilities, wmsCapabilitiesToDatasetMetadata} from '@kepler.gl/table';\n\nimport {MetaResponse} from './common';\nimport {InputLight} from '../../common';\n\nconst TilesetInputContainer = styled.div`\n  display: grid;\n  grid-template-rows: repeat(3, auto);\n  row-gap: 18px;\n  font-size: 12px;\n`;\n\nconst TilesetInputDescription = styled.div`\n  text-align: center;\n  color: ${props => props.theme.AZURE200};\n  font-size: 11px;\n`;\n\nconst ExampleUrlsContainer = styled.div`\n  text-align: left;\n  color: ${props => props.theme.AZURE200};\n  font-size: 11px;\n\n  .example-url {\n    margin-top: 8px;\n    display: block;\n  }\n`;\n\ntype WMSTileFormProps = {\n  setResponse: (response: MetaResponse) => void;\n};\n\ntype WMSData = {\n  metadata: WMSCapabilities | null;\n  layers: WMSDatasetMetadata['layers'];\n  version: string;\n};\n\nconst TilesetWMSForm: React.FC<WMSTileFormProps> = ({setResponse}) => {\n  const [layerName, setLayerName] = useState<string>('');\n  const [wmsUrl, setWmsUrl] = useState<string>('');\n  const [loading, setLoading] = useState<boolean>(false);\n  const [error, setError] = useState<Error | null>(null);\n  const [wmsData, setWMSData] = useState<WMSData | null>(null);\n\n  const onLayerNameChange = useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      setLayerName(event.target.value);\n    },\n    [setLayerName]\n  );\n\n  const onWmsUrlChange = useCallback(\n    async (event: React.ChangeEvent<HTMLInputElement>) => {\n      event.preventDefault();\n      const newWmsUrl = event.target.value;\n      setWmsUrl(newWmsUrl);\n\n      if (!validateUrl(newWmsUrl)) {\n        setWMSData(null);\n        return;\n      }\n\n      // Fetch WMS GetCapabilities\n      try {\n        setLoading(true);\n        setError(null);\n\n        const data = await getWMSCapabilities(newWmsUrl);\n\n        const datasetMetadata = wmsCapabilitiesToDatasetMetadata(data);\n\n        // Extract name or title from GetCapabilities response\n        const serviceTitle = data?.title || data?.name;\n        if (serviceTitle && !layerName) {\n          setLayerName(serviceTitle);\n        }\n\n        setWMSData({\n          metadata: data,\n          layers: datasetMetadata.layers,\n          version: datasetMetadata.version\n        });\n      } catch (err) {\n        setError(err instanceof Error ? err : new Error('Unknown error'));\n        setWMSData(null);\n      } finally {\n        setLoading(false);\n      }\n    },\n    [layerName]\n  );\n\n  useEffect(() => {\n    if (layerName && wmsUrl) {\n      const dataset = {\n        name: layerName,\n        type: DatasetType.WMS_TILE,\n        metadata: {\n          type: REMOTE_TILE,\n          remoteTileFormat: RemoteTileFormat.WMS,\n          tilesetDataUrl: wmsUrl,\n          tilesetMetadataUrl: `${wmsUrl}?service=WMS&request=GetCapabilities`,\n          layers: wmsData?.layers || [],\n          wmsVersion: wmsData?.version || '1.3.0'\n        }\n      };\n      setResponse({\n        metadata: wmsData?.metadata ?? null,\n        dataset,\n        loading,\n        error\n      });\n    } else {\n      setResponse({\n        metadata: null,\n        dataset: null,\n        loading,\n        error\n      });\n    }\n  }, [setResponse, layerName, wmsUrl, wmsData, loading, error]);\n\n  return (\n    <TilesetInputContainer>\n      <div>\n        <label htmlFor=\"layer-name\">Name</label>\n        <InputLight\n          id=\"layer-name\"\n          placeholder=\"Name your WMS layer\"\n          value={layerName}\n          onChange={onLayerNameChange}\n        />\n      </div>\n      <div>\n        <label htmlFor=\"wms-url\">WMS URL</label>\n        <InputLight\n          id=\"wms-url\"\n          placeholder=\"Enter WMS URL\"\n          value={wmsUrl}\n          onChange={onWmsUrlChange}\n        />\n        <TilesetInputDescription>Provide a valid WMS service URL.</TilesetInputDescription>\n      </div>\n      <div>\n        <TilesetInputDescription>For example, try a public WMS URL:</TilesetInputDescription>\n        <ExampleUrlsContainer>\n          <div className=\"example-url\">• https://ows.terrestris.de/osm/service</div>\n          <div className=\"example-url\">\n            • https://opengeo.ncep.noaa.gov/geoserver/conus/conus_cref_qcd/ows\n          </div>\n          <div className=\"example-url\">\n            • https://gibs.earthdata.nasa.gov/wms/epsg4326/best/wms.cgi\n          </div>\n        </ExampleUrlsContainer>\n      </div>\n    </TilesetInputContainer>\n  );\n};\n\nexport default TilesetWMSForm;\n"
  },
  {
    "path": "src/components/src/notification-panel/notification-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\nimport {Delete, Info, Warning, Checkmark} from '../common/icons';\nimport Markdown from 'markdown-to-jsx';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {ActionHandler, removeNotification as removeNotificationActions} from '@kepler.gl/actions';\n\nimport LinkRenderer from '../common/link-renderer';\ninterface NotificationItemContentBlockProps {\n  isExpanded?: boolean;\n}\n\nconst NotificationItemContentBlock = styled.div.attrs({\n  className: 'notification-item--content-block'\n})<NotificationItemContentBlockProps>`\n  display: block;\n  position: relative;\n  width: ${props => props.theme.notificationPanelItemWidth * (1 + Number(props.isExpanded))}px;\n  margin-left: auto;\n`;\n\ninterface NotificationItemContentProps {\n  type: string;\n  isExpanded?: boolean;\n}\n\nconst NotificationItemContent = styled.div<NotificationItemContentProps>`\n  background-color: ${props => props.theme.notificationColors[props.type] || '#000'};\n  color: #fff;\n  display: flex;\n  flex-direction: row;\n  width: ${props => props.theme.notificationPanelItemWidth * (1 + Number(props.isExpanded))}px;\n  height: ${props => props.theme.notificationPanelItemHeight * (1 + Number(props.isExpanded))}px;\n  font-size: 11px;\n  margin-bottom: 1rem;\n  padding: 1em;\n  border-radius: 4px;\n  box-shadow: ${props => props.theme.boxShadow};\n  cursor: pointer;\n`;\n\nconst DeleteIcon = styled(Delete)`\n  cursor: pointer;\n  width: 13px;\n  height: 13px;\n`;\n\ninterface NotificationCounterProps {\n  type: string;\n}\n\nconst NotificationCounter = styled.div.attrs({\n  className: 'notification-item--counter'\n})<NotificationCounterProps>`\n  position: absolute;\n  font-size: 11px;\n  font-weight: bold;\n  text-align: center;\n  left: -4px;\n  bottom: -4px;\n  border-radius: 50%;\n  width: 20px;\n  height: 20px;\n  background-color: #ffffff;\n  border: 1px solid ${props => props.theme.notificationColors[props.type] || '#000'};\n  color: ${props => props.theme.notificationColors[props.type] || '#000'};\n  box-shadow: ${props => props.theme.boxShadow};\n`;\n\ninterface NotificationMessageProps {\n  isExpanded?: boolean;\n}\n\nconst NotificationMessage = styled.div.attrs({\n  className: 'notification-item--message'\n})<NotificationMessageProps>`\n  flex-grow: 2;\n  width: ${props => props.theme.notificationPanelItemWidth}px;\n  margin: 0 1em;\n  overflow: ${props => (props.isExpanded ? 'auto' : 'hidden')};\n  padding-right: ${props => (props.isExpanded ? '1em' : 0)};\n\n  p {\n    margin-top: 0;\n    a {\n      color: #fff;\n      text-decoration: underline;\n    }\n  }\n`;\n\nconst NotificationIcon = styled.div`\n  svg {\n    vertical-align: text-top;\n  }\n`;\n\nconst icons = {\n  info: <Info data-testid={dataTestIds.infoIcon} />,\n  warning: <Warning data-testid={dataTestIds.warningIcon} />,\n  error: <Warning data-testid={dataTestIds.errorIcon} />,\n  success: <Checkmark data-testid={dataTestIds.successIcon} />\n};\n\ninterface NotificationItemProps {\n  notification: {\n    id: string;\n    type: string;\n    message: string;\n    count?: number;\n  };\n  isExpanded?: boolean;\n  removeNotification?: ActionHandler<typeof removeNotificationActions>;\n  theme?: any;\n}\n\nexport default function NotificationItemFactory() {\n  return class NotificationItem extends Component<NotificationItemProps> {\n    state = {\n      isExpanded: false\n    };\n\n    componentDidMount() {\n      if (this.props.isExpanded) {\n        this.setState({isExpanded: true});\n      }\n    }\n\n    render() {\n      const {notification, removeNotification} = this.props;\n      const {isExpanded} = this.state;\n\n      return (\n        <NotificationItemContentBlock isExpanded={isExpanded} theme={this.props.theme}>\n          {(notification.count || 0) > 1 ? (\n            <NotificationCounter type={notification.type} theme={this.props.theme}>\n              {notification.count}\n            </NotificationCounter>\n          ) : null}\n          <NotificationItemContent\n            className=\"notification-item\"\n            type={notification.type}\n            isExpanded={isExpanded}\n            onClick={() => this.setState({isExpanded: !isExpanded})}\n          >\n            <NotificationIcon className=\"notification-item--icon\">\n              {icons[notification.type]}\n            </NotificationIcon>\n            <NotificationMessage isExpanded={isExpanded} theme={this.props.theme}>\n              <Markdown\n                options={{\n                  overrides: {\n                    a: {\n                      component: LinkRenderer\n                    }\n                  }\n                }}\n              >\n                {notification.message}\n              </Markdown>\n            </NotificationMessage>\n            {typeof removeNotification === 'function' ? (\n              <div className=\"notification-item--action\">\n                <DeleteIcon height=\"10px\" onClick={() => removeNotification(notification.id)} />\n              </div>\n            ) : null}\n          </NotificationItemContent>\n        </NotificationItemContentBlock>\n      );\n    }\n  };\n}\n"
  },
  {
    "path": "src/components/src/notification-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\n\nimport NotificationItemFactory from './notification-panel/notification-item';\nimport {DEFAULT_NOTIFICATION_TOPICS} from '@kepler.gl/constants';\nimport {Notifications} from '@kepler.gl/types';\nimport {removeNotification} from '@kepler.gl/actions';\n\nconst NotificationPanelContent = styled.div`\n  background: transparent;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-end;\n  padding: 4px;\n  overflow-y: auto;\n  overflow-x: hidden;\n  position: absolute;\n  top: 1em;\n  right: 1em;\n  z-index: 10000;\n  box-sizing: border-box;\n`;\n\nNotificationPanelFactory.deps = [NotificationItemFactory];\n\ninterface NotificationPanelProps {\n  removeNotification?: typeof removeNotification;\n  notifications: Notifications[];\n}\n\nexport default function NotificationPanelFactory(\n  NotificationItem: ReturnType<typeof NotificationItemFactory>\n): React.ComponentClass<NotificationPanelProps> {\n  class NotificationPanelUnmemoized extends Component<NotificationPanelProps> {\n    static displayName = 'NotificationPanel';\n\n    render() {\n      const globalNotifications = this.props.notifications.filter(\n        n => n.topic === DEFAULT_NOTIFICATION_TOPICS.global\n      );\n      return (\n        <NotificationPanelContent\n          className=\"notification-panel\"\n          style={{display: globalNotifications.length ? 'block' : 'none'}}\n        >\n          {globalNotifications.map(n => (\n            <NotificationItem\n              key={n.id}\n              notification={n}\n              removeNotification={this.props.removeNotification}\n            />\n          ))}\n        </NotificationPanelContent>\n      );\n    }\n  }\n\n  const NotificationPanel = React.memo(\n    NotificationPanelUnmemoized\n  ) as unknown as typeof NotificationPanelUnmemoized;\n  return NotificationPanel;\n}\n"
  },
  {
    "path": "src/components/src/plot-container.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// libraries\nimport React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport {Map} from 'react-map-gl';\nimport debounce from 'lodash/debounce';\nimport {\n  exportImageError,\n  scaleMapStyleByResolution,\n  getCenterAndZoomFromBounds,\n  convertToPng,\n  getScaleFromImageSize\n} from '@kepler.gl/utils';\nimport {findMapBounds} from '@kepler.gl/reducers';\nimport MapContainerFactory from './map-container';\nimport MapsLayoutFactory from './maps-layout';\nimport {MapViewStateContextProvider} from './map-view-state-context';\n\nimport {GEOCODER_LAYER_ID} from '@kepler.gl/constants';\nimport {Effect, SplitMap, ExportImage} from '@kepler.gl/types';\nimport {\n  ActionHandler,\n  addNotification,\n  setExportImageDataUri,\n  setExportImageError,\n  setExportImageSetting\n} from '@kepler.gl/actions';\nimport {mapFieldsSelector} from './kepler-gl';\n\nconst CLASS_FILTER = [\n  'maplibregl-control-container',\n  'mapboxgl-control-container',\n  'attrition-link',\n  'attrition-logo',\n  'map-control__panel-split-viewport-tools'\n];\nconst DOM_FILTER_FUNC = node => !CLASS_FILTER.includes(node.className);\nconst OUT_OF_SCREEN_POSITION = -9999;\n\n/**\n * Calculates legend zoom based on export height\n * Maps export height to a zoom factor between 0.5 and 3\n * @param height - export height in pixels\n * @returns zoom factor for legend panel\n */\nfunction calculateLegendZoom(height: number): number {\n  const baseHeight = 1080; // 1x zoom reference height\n  const minZoom = 0.5;\n  const maxZoom = 3;\n\n  // For initial/invalid heights, avoid shrinking the legend; use no scaling.\n  if (!Number.isFinite(height) || height <= 0) {\n    return 1;\n  }\n\n  // Linear mapping: height / baseHeight gives us the scale\n  const zoomFactor = height / baseHeight;\n  // Clamp between min and max\n  return Math.max(minZoom, Math.min(maxZoom, zoomFactor));\n}\n\nPlotContainerFactory.deps = [MapContainerFactory, MapsLayoutFactory];\n\n// Remove mapbox logo in exported map, because it contains non-ascii characters\n// Remove split viewport UI controls from exported images when the legend is shown\ninterface StyledPlotContainerProps {\n  legendZoom?: number;\n}\n\nconst StyledPlotContainer = styled.div<StyledPlotContainerProps>`\n  .maplibregl-ctrl-bottom-left,\n  .maplibregl-ctrl-bottom-right,\n  .maplibre-attribution-container,\n  .mapboxgl-ctrl-bottom-left,\n  .mapboxgl-ctrl-bottom-right,\n  .mapbox-attribution-container,\n  .map-control__panel-split-viewport-tools {\n    display: none;\n  }\n\n  position: absolute;\n  top: ${OUT_OF_SCREEN_POSITION}px;\n  left: ${OUT_OF_SCREEN_POSITION}px;\n\n  /* Apply zoom to legend panel based on export height */\n  .map-control-panel {\n    zoom: ${props => props.legendZoom || 1} !important;\n  }\n`;\n\ninterface StyledMapContainerProps {\n  width?: number;\n  height?: number;\n}\n\nconst StyledMapContainer = styled.div<StyledMapContainerProps>`\n  width: ${props => props.width}px;\n  height: ${props => props.height}px;\n  display: flex;\n`;\n\ninterface PlotContainerProps {\n  // Image export settings\n  ratio?: string;\n  resolution?: string;\n  legend?: boolean;\n  center?: boolean;\n  imageSize: ExportImage['imageSize'];\n  escapeXhtmlForWebpack?: boolean;\n\n  // Map settings\n  mapFields: ReturnType<typeof mapFieldsSelector>;\n  splitMaps?: SplitMap[];\n\n  // Callbacks\n  setExportImageSetting: typeof setExportImageSetting;\n  setExportImageDataUri: typeof setExportImageDataUri;\n  setExportImageError: typeof setExportImageError;\n  addNotification: ActionHandler<typeof addNotification>;\n\n  // Flags\n  enableErrorNotification?: boolean;\n\n  // Optional: override legend header logo during export\n  logoComponent?: React.ReactNode;\n}\n\nexport default function PlotContainerFactory(\n  MapContainer: ReturnType<typeof MapContainerFactory>,\n  MapsLayout: ReturnType<typeof MapsLayoutFactory>\n): React.ComponentType<PlotContainerProps> {\n  function PlotContainer({\n    // Image export settings\n    ratio,\n    resolution,\n    legend = false,\n    center,\n    imageSize,\n    escapeXhtmlForWebpack,\n\n    // Map settings\n    mapFields,\n    splitMaps = [],\n\n    // Callbacks\n    setExportImageSetting,\n    setExportImageDataUri,\n    setExportImageError,\n    addNotification,\n\n    // Flags\n    enableErrorNotification,\n    logoComponent\n  }: PlotContainerProps) {\n    const plottingAreaRef = useRef<HTMLDivElement>(null);\n    const [plotEffects] = useState<Effect[]>(() =>\n      mapFields.visState.effects.map(effect => effect.clone())\n    );\n\n    const {mapState} = mapFields;\n\n    // Memoize the scale calculation\n    const scale = useMemo(() => {\n      if (imageSize.scale) {\n        return imageSize.scale;\n      }\n\n      const calculatedScale = getScaleFromImageSize(\n        imageSize.imageW,\n        imageSize.imageH,\n        mapState.width * (mapState.isSplit ? 2 : 1),\n        mapState.height\n      );\n\n      return calculatedScale > 0 ? calculatedScale : 1;\n    }, [\n      imageSize.scale,\n      imageSize.imageW,\n      imageSize.imageH,\n      mapState.width,\n      mapState.height,\n      mapState.isSplit\n    ]);\n\n    // Memoize the legend zoom calculation based on export height\n    const legendZoom = useMemo(() => {\n      return calculateLegendZoom(imageSize.imageH);\n    }, [imageSize.imageH]);\n\n    // Memoize the map style\n    const scaledMapStyle = useMemo(() => {\n      const mapStyle = mapFields.mapStyle;\n      return {\n        ...mapStyle,\n        bottomMapStyle: scaleMapStyleByResolution(mapStyle.bottomMapStyle, scale),\n        topMapStyle: scaleMapStyleByResolution(mapStyle.topMapStyle, scale)\n      };\n    }, [mapFields.mapStyle, scale]);\n\n    // Memoize the retrieveNewScreenshot callback\n    const debouncedScreenshot = useMemo(\n      () =>\n        debounce(() => {\n          if (plottingAreaRef.current) {\n            convertToPng(plottingAreaRef.current, {\n              filter: DOM_FILTER_FUNC,\n              width: imageSize.imageW,\n              height: imageSize.imageH,\n              escapeXhtmlForWebpack\n            })\n              .then(setExportImageDataUri)\n              .catch(err => {\n                setExportImageError(err);\n                if (enableErrorNotification) {\n                  addNotification(exportImageError({err}));\n                }\n              });\n          }\n        }, 500),\n      [\n        imageSize.imageW,\n        imageSize.imageH,\n        escapeXhtmlForWebpack,\n        setExportImageDataUri,\n        setExportImageError,\n        enableErrorNotification,\n        addNotification\n      ]\n    );\n\n    const retrieveNewScreenshot = useCallback(debouncedScreenshot, [debouncedScreenshot]);\n\n    // Memoize the onMapRender callback\n    const debouncedMapRender = useMemo(\n      () =>\n        debounce(map => {\n          if (map.isStyleLoaded()) {\n            retrieveNewScreenshot();\n          }\n        }, 500),\n      [retrieveNewScreenshot]\n    );\n\n    const onMapRender = useCallback(debouncedMapRender, [debouncedMapRender]);\n\n    // Initial setup effect\n    useEffect(() => {\n      setExportImageSetting({processing: true});\n    }, [setExportImageSetting]);\n\n    // Screenshot update effect\n    useEffect(() => {\n      if (ratio !== undefined || resolution !== undefined || legend !== undefined) {\n        setExportImageSetting({processing: true});\n        retrieveNewScreenshot();\n      }\n    }, [ratio, resolution, legend, setExportImageSetting, retrieveNewScreenshot]);\n\n    // Memoize size calculations\n    const {size, width, height} = useMemo(() => {\n      const size = {\n        width: imageSize.imageW || 1,\n        height: imageSize.imageH || 1\n      };\n      const isSplit = splitMaps.length > 1;\n      return {\n        size,\n        width: size.width / (isSplit ? 2 : 1),\n        height: size.height\n      };\n    }, [imageSize.imageW, imageSize.imageH, splitMaps.length]);\n\n    // Memoize map state\n    const newMapState = useMemo(() => {\n      const baseMapState = {\n        ...mapState,\n        width,\n        height,\n        zoom: mapState.zoom + (Math.log2(scale) || 0)\n      };\n\n      if (center) {\n        const renderedLayers = mapFields.visState.layers.filter(\n          (layer, idx) =>\n            layer.id !== GEOCODER_LAYER_ID &&\n            layer.shouldRenderLayer(mapFields.visState.layerData[idx])\n        );\n        const bounds = findMapBounds(renderedLayers);\n        const centerAndZoom = getCenterAndZoomFromBounds(bounds, {width, height});\n        if (centerAndZoom) {\n          const zoom = Number.isFinite(centerAndZoom.zoom) ? centerAndZoom.zoom : mapState.zoom;\n          return {\n            ...baseMapState,\n            longitude: centerAndZoom.center[0],\n            latitude: centerAndZoom.center[1],\n            zoom: zoom + Number(Math.log2(scale) || 0)\n          };\n        }\n      }\n\n      return baseMapState;\n    }, [mapState, width, height, scale, center, mapFields.visState]);\n\n    // Memoize map props\n    const mapProps = useMemo(\n      () => ({\n        ...mapFields,\n        mapStyle: scaledMapStyle,\n        mapState: newMapState,\n        mapControls: {\n          mapLegend: {\n            show: Boolean(legend),\n            active: true,\n            settings: mapFields.mapControls?.mapLegend?.settings\n          }\n        },\n        MapComponent: Map,\n        onMapRender,\n        isExport: true,\n        deckGlProps: {\n          ...mapFields.deckGlProps,\n          glOptions: {\n            preserveDrawingBuffer: true,\n            useDevicePixels: false\n          }\n        },\n        visState: {\n          ...mapFields.visState,\n          effects: plotEffects\n        },\n        // allow overriding the legend panel logo in export\n        logoComponent\n      }),\n      [mapFields, scaledMapStyle, newMapState, legend, onMapRender, plotEffects, logoComponent]\n    );\n\n    const isSplit = splitMaps.length > 1;\n    const mapContainers = !isSplit ? (\n      <MapContainer index={0} primary={true} {...mapProps} />\n    ) : (\n      <MapsLayout className=\"plot-container-maps\" mapState={newMapState}>\n        {splitMaps.map((settings, index) => (\n          <MapContainer key={index} index={index} primary={index === 1} {...mapProps} />\n        ))}\n      </MapsLayout>\n    );\n\n    return (\n      <StyledPlotContainer className=\"export-map-instance\" legendZoom={legendZoom}>\n        <StyledMapContainer ref={plottingAreaRef} width={size.width} height={size.height}>\n          <MapViewStateContextProvider mapState={newMapState}>\n            {mapContainers}\n          </MapViewStateContextProvider>\n        </StyledMapContainer>\n      </StyledPlotContainer>\n    );\n  }\n\n  return React.memo(PlotContainer);\n}"
  },
  {
    "path": "src/components/src/side-panel/add-by-dataset-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, useState} from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Datasets, KeplerTable} from '@kepler.gl/table';\n\nimport Tippy from '@tippyjs/react';\nimport {Add} from '../common/icons';\nimport {Button, DatasetSquare} from '../common/styled-components';\nimport Typeahead from '../common/item-selector/typeahead';\nimport Accessor from '../common/item-selector/accessor';\nimport {useIntl} from 'react-intl';\nimport {RootContext} from '../context';\n\nconst DropdownContainer = styled.div.attrs({\n  className: 'add-layer-menu-dropdown'\n})`\n  .list-selector {\n    border-top: 1px solid ${props => props.theme.secondaryInputBorderColor};\n    width: 100%;\n    /* disable scrolling, currently set to 280px internally */\n    max-height: unset;\n  }\n  .list__item > div {\n    display: flex;\n    flex-direction: row;\n    justify-content: flex-start;\n    line-height: 18px;\n    padding: 0;\n    svg {\n      margin-right: 10px;\n    }\n  }\n`;\n\nconst DropdownMenu = styled.div.attrs({\n  className: 'dropdown-menu'\n})`\n  display: flex;\n  flex-direction: column;\n  min-width: 240px;\n  max-width: 240px;\n  position: absolute;\n  top: 100%;\n  left: -53px;\n  z-index: 5;\n`;\n\nconst ListItemWrapper = styled.div.attrs({\n  className: 'dropdown-menu-list-item-wrapper'\n})`\n  display: flex;\n  color: ${props => props.theme.textColor};\n  font-size: 11px;\n  letter-spacing: 0.2px;\n  overflow: auto;\n  .dataset-color {\n    flex-shrink: 0;\n    margin-top: 3px;\n  }\n  .dataset-name {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst TYPEAHEAD_CLASS = 'typeahead';\nconst TYPEAHEAD_INPUT_CLASS = 'typeahead__input';\n\nexport type AddByDatasetButtonProps = {\n  datasets: Datasets;\n  onAdd: (dataId: string) => void;\n  buttonIntlId: string;\n  inactive?: boolean;\n  className?: string;\n};\n\nconst ListItem = ({value}) => (\n  <ListItemWrapper>\n    <DatasetSquare className=\"dataset-color\" backgroundColor={value.color} />\n    <div className=\"dataset-name\" title={value.label}>\n      {value.label}\n    </div>\n  </ListItemWrapper>\n);\n\nconst AddByDatasetButton: React.FC<AddByDatasetButtonProps> = ({\n  datasets,\n  onAdd,\n  buttonIntlId,\n  className,\n  inactive\n}) => {\n  const [tippyInstance, setTippyInstance] = useState();\n\n  const options = useMemo(() => {\n    return Object.values(datasets).map((ds: KeplerTable) => ({\n      label: ds.label,\n      value: ds.id,\n      color: ds.color\n    }));\n  }, [datasets]);\n\n  const onClickBtn = useCallback(() => {\n    if (options.length === 1) {\n      onAdd(options[0].value);\n    }\n\n    return;\n  }, [options, onAdd]);\n\n  const onOptionSelected = useCallback(\n    option => {\n      if (!option) {\n        return;\n      }\n      onAdd(option.value);\n      if (tippyInstance) {\n        // @ts-ignore\n        tippyInstance.hide();\n      }\n    },\n    [onAdd, tippyInstance]\n  );\n\n  const intl = useIntl();\n\n  const buttonRendered = (\n    <Button\n      tabIndex={-1}\n      className={className || 'add-by-dataset-button'}\n      onClick={onClickBtn}\n      disabled={!options.length || inactive}\n    >\n      <Add height=\"12px\" />\n      <FormattedMessage id={buttonIntlId} />\n    </Button>\n  );\n\n  return options.length === 1 ? (\n    buttonRendered\n  ) : (\n    <RootContext.Consumer>\n      {context => (\n        <Tippy\n          trigger=\"click\"\n          arrow={false}\n          interactive\n          placement=\"bottom\"\n          appendTo={context?.current || 'parent'}\n          // @ts-ignore\n          onCreate={setTippyInstance}\n          duration={0}\n          content={\n            <DropdownMenu>\n              <DropdownContainer>\n                <Typeahead\n                  className={TYPEAHEAD_CLASS}\n                  customClasses={{\n                    results: 'list-selector',\n                    input: TYPEAHEAD_INPUT_CLASS,\n                    listItem: 'list__item'\n                  }}\n                  placeholder={intl ? intl.formatMessage({id: 'placeholder.search'}) : 'Search'}\n                  selectedItems={null}\n                  options={options}\n                  displayOption={Accessor.generateOptionToStringFor('label')}\n                  filterOption={'label'}\n                  searchable\n                  onOptionSelected={onOptionSelected}\n                  customListItemComponent={ListItem}\n                />\n              </DropdownContainer>\n            </DropdownMenu>\n          }\n        >\n          {buttonRendered}\n        </Tippy>\n      )}\n    </RootContext.Consumer>\n  );\n};\n\nexport default AddByDatasetButton;\n"
  },
  {
    "path": "src/components/src/side-panel/cloud-storage-dropdown.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {PanelHeaderDropdownFactory, Icons} from '..';\n\nconst CloudStorageItems = [\n  {\n    label: 'Save',\n    icon: Icons.Save2,\n    key: 'data',\n    onClick: props => props.onSaveMap\n  },\n  {\n    label: 'Settings',\n    icon: Icons.Gear,\n    key: 'settings',\n    onClick: props => props.onExportData\n  }\n];\n\nconst PanelHeaderDropdown = PanelHeaderDropdownFactory();\n\nconst CloudStorageDropdown = ({show, onClose}) => {\n  return (\n    <PanelHeaderDropdown\n      items={CloudStorageItems}\n      show={show}\n      onClose={onClose}\n      id=\"cloud-storage\"\n    />\n  );\n};\n\nexport default CloudStorageDropdown;\n"
  },
  {
    "path": "src/components/src/side-panel/common/dataset-info.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {format} from 'd3-format';\n\nimport {DatasetType} from '@kepler.gl/constants';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {DataContainerInterface} from '@kepler.gl/utils';\n\nconst numFormat = format(',');\n\ntype MiniDataset = {\n  dataContainer: DataContainerInterface;\n  type?: string;\n};\n\nexport type DatasetInfoProps = {\n  dataset: MiniDataset;\n};\n\nconst StyledDataRowCount = styled.div`\n  font-size: 11px;\n  color: ${props => props.theme.subtextColor};\n  padding-left: 19px;\n`;\n\nexport default function DatasetInfoFactory() {\n  const DatasetInfo: React.FC<DatasetInfoProps> = ({dataset}: DatasetInfoProps) => (\n    <StyledDataRowCount className=\"source-data-rows\">\n      <FormattedMessage\n        id={\n          dataset.type === DatasetType.VECTOR_TILE\n            ? 'datasetInfo.vectorTile'\n            : dataset.type === DatasetType.RASTER_TILE\n            ? 'datasetInfo.rasterTile'\n            : dataset.type === DatasetType.WMS_TILE\n            ? 'datasetInfo.wmsTile'\n            : 'datasetInfo.rowCount'\n        }\n        values={{rowCount: numFormat(dataset.dataContainer.numRows())}}\n      />\n    </StyledDataRowCount>\n  );\n\n  return DatasetInfo;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/common/dataset-tag.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport styled from 'styled-components';\nimport {DatasetSquare, Tooltip} from '../..';\nimport {UpdateTableColorTypes} from './types';\nimport {RGBColor} from '@kepler.gl/types';\nimport {VisStateActions, ActionHandler} from '@kepler.gl/actions';\n\nfunction nop() {\n  return;\n}\n\nconst DatasetTagWrapper = styled.div`\n  display: flex;\n  color: ${props => props.theme.textColor};\n  font-size: 11px;\n  letter-spacing: 0.2px;\n  overflow: auto;\n  display: flex;\n  align-items: center;\n  .dataset-color {\n    flex-shrink: 0;\n  }\n\n  .dataset-name {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst DatasetColorPicker = styled.div`\n  display: flex;\n`;\n\nconst UpdateTableColor = ({children, id}: UpdateTableColorTypes) => (\n  <DatasetColorPicker\n    className=\"dataset-action update-color\"\n    data-tip\n    data-for={`update-color-${id}`}\n  >\n    {children}\n  </DatasetColorPicker>\n);\n\ntype MiniDataset = {\n  id: string;\n  color: RGBColor;\n  label?: string;\n};\n\nexport type DatasetTagProps = {\n  id?: string;\n  dataset: MiniDataset;\n  updateTableColor?: ActionHandler<typeof VisStateActions.updateTableColor>;\n  onClick?: React.MouseEventHandler<HTMLDivElement>;\n  onClickSquare?: React.MouseEventHandler<HTMLDivElement>;\n};\n\nexport default function DatasetTagFactory(): React.FC<DatasetTagProps> {\n  const DatasetTag = ({\n    onClick = nop,\n    onClickSquare = nop,\n    dataset,\n    updateTableColor\n  }: DatasetTagProps) => (\n    <DatasetTagWrapper className=\"source-data-tag\">\n      <UpdateTableColor id={dataset.id}>\n        <DatasetSquare\n          className=\"dataset-color\"\n          backgroundColor={dataset.color}\n          onClick={onClickSquare}\n          data-tip\n          data-for={`update-color-${dataset.id}`}\n        />\n        {updateTableColor ? (\n          <Tooltip id={`update-color-${dataset.id}`} effect=\"solid\">\n            <span>\n              <FormattedMessage id={'Update color'} />\n            </span>\n          </Tooltip>\n        ) : null}\n      </UpdateTableColor>\n      <div className=\"dataset-name\" title={dataset.label} onClick={onClick}>\n        {dataset.label}\n      </div>\n    </DatasetTagWrapper>\n  );\n\n  return DatasetTag;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/common/dataset-title.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useRef, useState} from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {Table} from '@kepler.gl/layers';\nimport {CenterFlexbox, Tooltip} from '../../common/styled-components';\nimport {ArrowRight, Trash} from '../../common/icons';\nimport DatasetTagFactory from './dataset-tag';\nimport CustomPicker from '../layer-panel/custom-picker';\nimport {Portaled} from '../..';\nimport {rgbToHex} from '@kepler.gl/utils';\nimport {openDeleteModal, VisStateActions, ActionHandler} from '@kepler.gl/actions';\nimport {RGBColor} from '@kepler.gl/types';\nimport {StyledDatasetTitleProps, RemoveDatasetProps, ShowDataTableProps} from './types';\n\nconst StyledDatasetTitle = styled.div<StyledDatasetTitleProps>`\n  color: ${props => props.theme.textColor};\n  display: flex;\n  align-items: center;\n\n  .source-data-arrow {\n    height: 16px;\n  }\n  &:hover {\n    cursor: ${props => (props.$clickable ? 'pointer' : 'auto')};\n\n    .dataset-name {\n      color: ${props => (props.$clickable ? props.theme.textColorHl : props.theme.textColor)};\n    }\n\n    .dataset-action {\n      color: ${props => props.theme.textColor};\n      opacity: 1;\n    }\n\n    .dataset-action:hover {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst DataTagAction = styled.div`\n  margin-left: 12px;\n  height: 16px;\n  opacity: 0;\n`;\n\ntype MiniDataset = {\n  id: string;\n  color: RGBColor;\n  label?: string;\n  disableDataOperation?: boolean;\n};\n\nexport type DatasetTitleProps = {\n  dataset: MiniDataset;\n  showDeleteDataset: boolean;\n  onTitleClick?: () => void;\n  showDatasetTable?: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  removeDataset?: ActionHandler<typeof openDeleteModal>;\n};\n\nconst ShowDataTable = ({id, showDatasetTable}: ShowDataTableProps) => (\n  <DataTagAction className=\"dataset-action show-data-table\" data-tip data-for={`data-table-${id}`}>\n    <Table\n      height=\"16px\"\n      onClick={e => {\n        e.stopPropagation();\n        showDatasetTable?.(id);\n      }}\n    />\n    <Tooltip id={`data-table-${id}`} effect=\"solid\">\n      <span>\n        <FormattedMessage id={'datasetTitle.showDataTable'} />\n      </span>\n    </Tooltip>\n  </DataTagAction>\n);\n\nconst RemoveDataset = ({datasetKey, removeDataset}: RemoveDatasetProps) => (\n  <DataTagAction\n    className=\"dataset-action remove-dataset\"\n    data-tip\n    data-for={`delete-${datasetKey}`}\n  >\n    <Trash\n      height=\"16px\"\n      onClick={e => {\n        e.stopPropagation();\n        removeDataset?.(datasetKey);\n      }}\n    />\n    <Tooltip id={`delete-${datasetKey}`} effect=\"solid\" type=\"error\">\n      <span>\n        <FormattedMessage id={'datasetTitle.removeDataset'} />\n      </span>\n    </Tooltip>\n  </DataTagAction>\n);\n\nDatasetTitleFactory.deps = [DatasetTagFactory];\n\nexport default function DatasetTitleFactory(\n  DatasetTag: ReturnType<typeof DatasetTagFactory>\n): React.FC<DatasetTitleProps> {\n  const DatasetTitle: React.FC<DatasetTitleProps> = ({\n    showDatasetTable,\n    showDeleteDataset,\n    onTitleClick,\n    removeDataset,\n    dataset,\n    updateTableColor\n  }) => {\n    const [displayColorPicker, setDisplayColorPicker] = useState(false);\n    const root = useRef(null);\n    const datasetId = dataset.id;\n    const _handleClick = useCallback(() => {\n      setDisplayColorPicker(!displayColorPicker);\n    }, [setDisplayColorPicker, displayColorPicker]);\n\n    const _handleClosePicker = useCallback(() => {\n      setDisplayColorPicker(false);\n    }, [setDisplayColorPicker]);\n    const _handleCustomPicker = useCallback(\n      (color: {rgb: Record<string, number>}) => {\n        updateTableColor(datasetId, [color.rgb.r, color.rgb.g, color.rgb.b]);\n      },\n      [updateTableColor, datasetId]\n    );\n\n    const _onClickTitle = useCallback(\n      (e: React.MouseEvent<HTMLDivElement>) => {\n        e.stopPropagation();\n        if (typeof onTitleClick === 'function') {\n          onTitleClick();\n        } else if (typeof showDatasetTable === 'function') {\n          if (dataset.disableDataOperation) return;\n          showDatasetTable(datasetId);\n        }\n      },\n      [onTitleClick, showDatasetTable, datasetId, dataset.disableDataOperation]\n    );\n\n    return (\n      <div className=\"custom-palette-panel\" ref={root}>\n        <StyledDatasetTitle\n          className=\"source-data-title\"\n          $clickable={Boolean(showDatasetTable || onTitleClick)}\n        >\n          <DatasetTag\n            dataset={dataset}\n            onClick={_onClickTitle}\n            updateTableColor={updateTableColor}\n            onClickSquare={_handleClick}\n          />\n          <Portaled\n            isOpened={displayColorPicker !== false}\n            left={110}\n            top={-50}\n            onClose={_handleClosePicker}\n          >\n            <CustomPicker color={rgbToHex(dataset.color)} onChange={_handleCustomPicker} />\n          </Portaled>\n          {showDatasetTable ? (\n            <CenterFlexbox className=\"source-data-arrow\">\n              <ArrowRight height=\"12px\" />\n            </CenterFlexbox>\n          ) : null}\n          {showDatasetTable && !dataset.disableDataOperation ? (\n            <ShowDataTable id={datasetId} showDatasetTable={showDatasetTable} />\n          ) : null}\n          {showDeleteDataset ? (\n            <RemoveDataset datasetKey={datasetId} removeDataset={removeDataset} />\n          ) : null}\n        </StyledDatasetTitle>\n      </div>\n    );\n  };\n\n  return DatasetTitle;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/common/source-data-catalog.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {openDeleteModal, VisStateActions, ActionHandler} from '@kepler.gl/actions';\nimport {DataContainerInterface} from '@kepler.gl/utils';\nimport {RGBColor} from '@kepler.gl/types';\n\nimport {SidePanelSection} from '../../common/styled-components';\nimport DatasetTitleFactory from './dataset-title';\nimport DatasetInfoFactory from './dataset-info';\n\nconst SourceDataCatalogWrapper = styled.div`\n  transition: ${props => props.theme.transition};\n`;\n\ntype MiniDataset = {\n  id: string;\n  color: RGBColor;\n  label?: string;\n  dataContainer: DataContainerInterface;\n  type?: string;\n};\n\ntype MiniDatasets = {\n  [key: string]: MiniDataset;\n};\n\nexport type SourceDataCatalogProps = {\n  datasets: MiniDatasets;\n  showDeleteDataset?: boolean;\n  onTitleClick?: () => void;\n  showDatasetTable?: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  removeDataset?: ActionHandler<typeof openDeleteModal>;\n};\n\nSourceDataCatalogFactory.deps = [DatasetTitleFactory, DatasetInfoFactory];\n\nfunction SourceDataCatalogFactory(\n  DatasetTitle: ReturnType<typeof DatasetTitleFactory>,\n  DatasetInfo: ReturnType<typeof DatasetInfoFactory>\n) {\n  const SourceDataCatalog: React.FC<SourceDataCatalogProps> = ({\n    datasets,\n    showDatasetTable,\n    removeDataset,\n    onTitleClick,\n    updateTableColor,\n    showDeleteDataset = false\n  }: SourceDataCatalogProps) => (\n    <SourceDataCatalogWrapper className=\"source-data-catalog\">\n      {Object.values(datasets).map(dataset => (\n        <SidePanelSection key={dataset.id}>\n          <DatasetTitle\n            showDatasetTable={showDatasetTable}\n            showDeleteDataset={showDeleteDataset}\n            removeDataset={removeDataset}\n            dataset={dataset}\n            onTitleClick={onTitleClick}\n            updateTableColor={updateTableColor}\n          />\n          {showDatasetTable ? <DatasetInfo dataset={dataset} /> : null}\n        </SidePanelSection>\n      ))}\n    </SourceDataCatalogWrapper>\n  );\n\n  return SourceDataCatalog;\n}\n\nexport default SourceDataCatalogFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/common/source-data-selector-content.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\n\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport DatasetTagFactory from './dataset-tag';\nimport {SourceDataSelectorProps} from './types';\n\nSourceDataSelectorContentFactory.deps = [DatasetTagFactory];\n\nfunction SourceDataSelectorContentFactory(DatasetTag) {\n  const DatasetItem = ({value}) => <DatasetTag dataset={value} />;\n\n  const SourceDataSelectorContent = ({\n    className,\n    datasets,\n    dataId,\n    inputTheme,\n    onSelect,\n    defaultValue,\n    disabled\n  }: SourceDataSelectorProps) => {\n    const dsOptions = useMemo(\n      () =>\n        Object.values(datasets).map(ds => ({\n          label: ds.label,\n          value: ds.id,\n          color: ds.color\n        })),\n      [datasets]\n    );\n\n    const selectedItems = useMemo(\n      () => (dataId ? ((Array.isArray(dataId) && dataId) || [dataId]).map(id => datasets[id]) : []),\n      [dataId, datasets]\n    );\n\n    return (\n      <ItemSelector\n        className={className}\n        inputTheme={inputTheme}\n        selectedItems={selectedItems}\n        options={dsOptions}\n        getOptionValue={'value'}\n        filterOption={'label'}\n        multiSelect={false}\n        onChange={onSelect}\n        placeholder={defaultValue}\n        disabled={disabled}\n        displayOption={'label'}\n        DropDownLineItemRenderComponent={DatasetItem}\n      />\n    );\n  };\n\n  return SourceDataSelectorContent;\n}\n\nexport default SourceDataSelectorContentFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/common/source-data-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {PanelLabel, SidePanelSection} from '../../common/styled-components';\nimport SourceDataSelectorContentFactory from './source-data-selector-content';\nimport {SourceDataSelectorProps} from './types';\n\nSourceDataSelectorFactory.deps = [SourceDataSelectorContentFactory];\n\nexport default function SourceDataSelectorFactory(\n  DataSourceSelectorContent: ReturnType<typeof SourceDataSelectorContentFactory>\n): React.FC<SourceDataSelectorProps> {\n  const SourceDataSelector: React.FC<SourceDataSelectorProps> = React.memo(\n    ({\n      dataId,\n      datasets,\n      disabled,\n      onSelect,\n      defaultValue = 'Select A Dataset',\n      inputTheme\n    }: SourceDataSelectorProps) => (\n      <SidePanelSection className=\"data-source-selector\">\n        <PanelLabel>\n          <FormattedMessage id={'misc.dataSource'} />\n        </PanelLabel>\n        <DataSourceSelectorContent\n          inputTheme={inputTheme}\n          datasets={datasets}\n          dataId={dataId}\n          onSelect={onSelect}\n          defaultValue={defaultValue}\n          disabled={disabled}\n        />\n      </SidePanelSection>\n    )\n  );\n\n  SourceDataSelector.displayName = 'SourceDataSelector';\n  return SourceDataSelector;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/common/source-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport SourceDataSelectorContentFactory from './source-data-selector-content';\nimport FieldSelectorFactory from '../../common/field-selector';\n\nconst StyledSourceContainer = styled.div`\n  background-color: #1c2233;\n`;\n\nSourceSelectorFactory.deps = [SourceDataSelectorContentFactory, FieldSelectorFactory];\n\nfunction SourceSelectorFactory(SourceDataSelectorContent, FieldSelector) {\n  const StyledSourceDataSelectorContent = styled.div`\n    margin-bottom: 2px;\n\n    .item-selector__dropdown {\n      border-top-left-radius: 2px;\n      border-top-right-radius: 2px;\n      border-bottom-left-radius: 0;\n      border-bottom-right-radius: 0;\n    }\n  `;\n\n  const StyledFieldSelector = styled.div`\n    .item-selector__dropdown {\n      border-top-left-radius: 0;\n      border-top-right-radius: 0;\n      border-bottom-left-radius: 2px;\n      border-bottom-right-radius: 2px;\n    }\n  `;\n\n  const SourceSelector = ({\n    className,\n    datasets,\n    dataId,\n    disabled,\n    onSelectDataset,\n    fields,\n    fieldValue,\n    onFieldSelector,\n    inputTheme\n  }) => {\n    const datasetFields = useMemo(() => {\n      return fields || datasets[dataId]?.fields;\n    }, [dataId, datasets, fields]);\n\n    return (\n      <StyledSourceContainer className={className}>\n        <StyledSourceDataSelectorContent>\n          <SourceDataSelectorContent\n            inputTheme={inputTheme}\n            datasets={datasets}\n            disabled={disabled}\n            dataId={dataId}\n            onSelect={onSelectDataset}\n          />\n        </StyledSourceDataSelectorContent>\n        {dataId && (\n          <StyledFieldSelector>\n            <FieldSelector\n              inputTheme={inputTheme}\n              fields={datasetFields}\n              value={fieldValue}\n              erasable={false}\n              onSelect={onFieldSelector}\n            />\n          </StyledFieldSelector>\n        )}\n      </StyledSourceContainer>\n    );\n  };\n\n  SourceSelector.displayName = 'SourceSelector';\n\n  return SourceSelector;\n}\n\nexport default SourceSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/common/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {openDeleteModal, VisStateActions, ActionHandler} from '@kepler.gl/actions';\nimport {RGBColor} from '@kepler.gl/types';\nimport KeplerTable from '@kepler.gl/table';\n\nexport type PanelMeta = {\n  id: string;\n  label: string;\n  iconComponent: React.ElementType;\n  onClick: null;\n};\n\nexport type UpdateTableColorTypes = {\n  id: string;\n  children: React.ReactNode;\n};\n\nexport type ShowDataTableProps = {\n  id: string;\n  showDatasetTable?: ActionHandler<typeof VisStateActions.showDatasetTable>;\n};\n\nexport type RemoveDatasetProps = {\n  datasetKey: string;\n  removeDataset?: ActionHandler<typeof openDeleteModal>;\n};\n\nexport type StyledDatasetTitleProps = {\n  $clickable: boolean;\n};\n\nexport type DatasetItemProps = {\n  value: KeplerTable;\n};\n\nexport type SelectableDataset = {\n  label?: string;\n  id: string;\n  color: RGBColor;\n};\n\nexport type SourceDataSelectorProps = {\n  dataId: string | string[] | null;\n  datasets: {[id: string]: SelectableDataset};\n  disabled?: boolean;\n  defaultValue?: string;\n  inputTheme?: string;\n  onSelect: (\n    items:\n      | ReadonlyArray<string | number | boolean | object>\n      | string\n      | number\n      | boolean\n      | object\n      | null\n  ) => void;\n  className?: string;\n};\n"
  },
  {
    "path": "src/components/src/side-panel/custom-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {SidePanelItem, SidePanelProps} from '../types';\nimport {RGBColor} from '@kepler.gl/types';\n\nexport type CustomPanelsStaticProps<P> = {\n  panels: SidePanelItem[];\n  getProps?: (props: SidePanelProps) => P;\n};\nexport type CustomPanelsProps = {\n  activeSidePanel: string | null;\n  updateTableColor: (dataId: string, newColor: RGBColor) => void;\n};\n\n// This is a dummy component that can be replaced to inject more side panel sub panels into the side bar\nfunction CustomPanelsFactory<P>() {\n  const CustomPanels: React.FC<CustomPanelsProps> & CustomPanelsStaticProps<P> = () => {\n    return <div />;\n  };\n  // provide a list of additional panels\n  CustomPanels.panels = [\n    // {\n    //   id: 'rocket',\n    //   label: 'Rocket',\n    //   iconComponent: Icons.Rocket\n    // },\n    // {\n    //   id: 'chart',\n    //   label: 'Chart',\n    //   iconComponent: Icons.LineChart\n    // }\n  ];\n\n  // prop selector from side panel props\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  CustomPanels.getProps = (sidePanelProps: SidePanelProps) => ({} as P);\n\n  return CustomPanels;\n}\n\nexport default CustomPanelsFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/filter-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\nimport {useIntl} from 'react-intl';\nimport {SidePanelDivider, SidePanelSection} from '../common/styled-components';\nimport SourceDataCatalogFactory from './common/source-data-catalog';\nimport FilterPanelFactory from './filter-panel/filter-panel';\nimport {FILTER_VIEW_TYPES, PANEL_VIEW_TOGGLES} from '@kepler.gl/constants';\nimport {Filter} from '@kepler.gl/types';\nimport {Layer} from '@kepler.gl/layers';\nimport {isSideFilter} from '@kepler.gl/utils';\nimport {VisStateActions, ActionHandler, UIStateActions, ActionHandlers} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\n\nimport PanelViewListToggleFactory from './panel-view-list-toggle';\nimport PanelTitleFactory from './panel-title';\nimport AddFilterButtonFactory from './filter-panel/add-filter-button';\nimport DatasetSectionFactory from './layer-panel/dataset-section';\nimport {PanelMeta} from './common/types';\n\nexport type VisStateActionHandlers = ActionHandlers<typeof VisStateActions>;\nexport type UiStateActionHandlers = ActionHandlers<typeof UIStateActions>;\n\nexport type FilterManagerProps = {\n  filters: Filter[];\n  datasets: Datasets;\n  layers: Layer[];\n  showDatasetTable: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  removeDataset: ActionHandler<typeof VisStateActions.removeDataset>;\n  showAddDataModal: () => void;\n\n  panelMetadata: PanelMeta;\n  panelListView: string;\n  visStateActions: VisStateActionHandlers;\n  uiStateActions: UiStateActionHandlers;\n};\n\ntype FilterListProps = {\n  filters: Filter[];\n  datasets: Datasets;\n  layers: Layer[];\n  filtersByIndex: {\n    filter: Filter;\n    idx: number;\n  }[];\n  isAnyFilterAnimating: boolean;\n  visStateActions: VisStateActionHandlers;\n};\n\nFilterManagerFactory.deps = [\n  DatasetSectionFactory,\n  FilterPanelFactory,\n  PanelTitleFactory,\n  AddFilterButtonFactory,\n  PanelViewListToggleFactory,\n  SourceDataCatalogFactory\n];\n\nfunction FilterManagerFactory(\n  DatasetSection: ReturnType<typeof DatasetSectionFactory>,\n  FilterPanel: ReturnType<typeof FilterPanelFactory>,\n  PanelTitle: ReturnType<typeof PanelTitleFactory>,\n  AddFilterButton: ReturnType<typeof AddFilterButtonFactory>,\n  PanelViewListToggle: ReturnType<typeof PanelViewListToggleFactory>,\n  SourceDataCatalog: ReturnType<typeof SourceDataCatalogFactory>\n) {\n  const FilterList = ({\n    filtersByIndex,\n    filters,\n    datasets,\n    layers,\n    isAnyFilterAnimating,\n    visStateActions\n  }: FilterListProps) => {\n    const {\n      removeFilter,\n      setFilter,\n      setFilterPlot,\n      toggleFilterAnimation,\n      toggleFilterFeature,\n      setFilterView,\n      syncTimeFilterWithLayerTimeline\n    } = visStateActions;\n\n    const filterPanelCallbacks = useMemo(() => {\n      return filtersByIndex.reduce(\n        (accu, {filter, idx}) => ({\n          ...accu,\n          [filter.id]: {\n            removeFilter: () => removeFilter(idx),\n            toggleFilterView: () =>\n              setFilterView(\n                idx,\n                isSideFilter(filter) ? FILTER_VIEW_TYPES.enlarged : FILTER_VIEW_TYPES.side\n              ),\n            toggleAnimation: () => toggleFilterAnimation(idx),\n            toggleFilterFeature: () => toggleFilterFeature(idx)\n          }\n        }),\n        {}\n      );\n    }, [filtersByIndex, removeFilter, setFilterView, toggleFilterAnimation, toggleFilterFeature]);\n\n    return (\n      <SidePanelSection>\n        {[...filtersByIndex].reverse().map(({filter, idx}) => (\n          <FilterPanel\n            key={`${filter.id}-${idx}`}\n            idx={idx}\n            filters={filters}\n            filter={filter}\n            datasets={datasets}\n            layers={layers}\n            isAnyFilterAnimating={isAnyFilterAnimating}\n            removeFilter={filterPanelCallbacks[filter.id].removeFilter}\n            enlargeFilter={filterPanelCallbacks[filter.id].toggleFilterView}\n            toggleAnimation={filterPanelCallbacks[filter.id].toggleAnimation}\n            toggleFilterFeature={filterPanelCallbacks[filter.id].toggleFilterFeature}\n            setFilter={setFilter}\n            setFilterPlot={setFilterPlot}\n            syncTimeFilterWithLayerTimeline={syncTimeFilterWithLayerTimeline}\n          />\n        ))}\n      </SidePanelSection>\n    );\n  };\n\n  const DatasetFilterSection = ({\n    filtersByIndex,\n    filters,\n    dataset,\n    datasets,\n    layers,\n    isAnyFilterAnimating,\n    visStateActions,\n    showDatasetTable,\n    updateTableColor,\n    removeDataset,\n    showDeleteDataset\n  }) => {\n    const datasetCatalog = useMemo(() => {\n      return {[dataset.id]: dataset};\n    }, [dataset]);\n\n    return (\n      <>\n        <SourceDataCatalog\n          datasets={datasetCatalog}\n          showDatasetTable={showDatasetTable}\n          updateTableColor={updateTableColor}\n          removeDataset={removeDataset}\n          showDeleteDataset={showDeleteDataset}\n        />\n        <FilterList\n          filtersByIndex={filtersByIndex}\n          filters={filters}\n          datasets={datasets}\n          layers={layers}\n          isAnyFilterAnimating={isAnyFilterAnimating}\n          visStateActions={visStateActions}\n        />\n      </>\n    );\n  };\n\n  const FilterManager: React.FC<FilterManagerProps> = ({\n    filters = [],\n    datasets,\n    layers,\n    showDatasetTable,\n    updateTableColor,\n    removeDataset,\n    showAddDataModal,\n    panelMetadata,\n    panelListView,\n    visStateActions,\n    uiStateActions\n  }) => {\n    const {addFilter} = visStateActions;\n    const {togglePanelListView} = uiStateActions;\n    const isAnyFilterAnimating = filters.some(f => f.isAnimating);\n    const onClickAddFilter = useCallback(dataset => addFilter(dataset), [addFilter]);\n    const isSortByDatasetMode = panelListView === PANEL_VIEW_TOGGLES.byDataset;\n    const filtersByIndex = useMemo(\n      () =>\n        filters.map((f, idx) => ({\n          filter: f,\n          idx\n        })),\n      [filters]\n    );\n    const filtersByDatasets = useMemo(\n      () =>\n        Object.keys(datasets).reduce(\n          (accu, dataId) => ({\n            ...accu,\n            // if synced filter, show it unfder its the first dataset\n            [dataId]: filtersByIndex.filter(\n              fidx => fidx.filter.dataId && fidx.filter.dataId[0] === dataId\n            )\n          }),\n          {}\n        ),\n      [datasets, filtersByIndex]\n    );\n    const _TogglePanelListView = useCallback(\n      listView => {\n        togglePanelListView({panelId: 'filter', listView});\n      },\n      [togglePanelListView]\n    );\n\n    const intl = useIntl();\n    const filterListProps = {\n      datasets,\n      filters,\n      layers,\n      isAnyFilterAnimating,\n      visStateActions\n    };\n\n    const sourceDataCatalogProps = {\n      showDatasetTable,\n      updateTableColor,\n      removeDataset,\n      showDeleteDataset: true\n    };\n\n    return (\n      <div className=\"filter-manager\">\n        <SidePanelSection>\n          <PanelViewListToggle togglePanelListView={_TogglePanelListView} mode={panelListView} />\n        </SidePanelSection>\n        <DatasetSection\n          datasets={datasets}\n          {...sourceDataCatalogProps}\n          showDatasetList={!isSortByDatasetMode}\n          showAddDataModal={showAddDataModal}\n        />\n        <SidePanelDivider />\n        <SidePanelSection>\n          <PanelTitle\n            className=\"filter-manager-title\"\n            title={intl.formatMessage({id: panelMetadata.label})}\n          >\n            <AddFilterButton datasets={datasets} onAdd={onClickAddFilter} />\n          </PanelTitle>\n        </SidePanelSection>\n        <SidePanelSection>\n          {isSortByDatasetMode ? (\n            Object.keys(filtersByDatasets).map(dataId => (\n              <DatasetFilterSection\n                key={dataId}\n                filtersByIndex={filtersByDatasets[dataId]}\n                dataset={datasets[dataId]}\n                {...filterListProps}\n                {...sourceDataCatalogProps}\n              />\n            ))\n          ) : (\n            <FilterList filtersByIndex={filtersByIndex} {...filterListProps} />\n          )}\n        </SidePanelSection>\n      </div>\n    );\n  };\n\n  return FilterManager;\n}\n\nexport default FilterManagerFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/filter-panel/add-filter-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Datasets} from '@kepler.gl/table';\nimport AddByDatasetButton from '../add-by-dataset-button';\n\nexport type AddFilterButtonProps = {\n  datasets: Datasets;\n  onAdd: (dataId: string) => void;\n};\n\nfunction AddFilterButtonFactory() {\n  const AddFilterButton: React.FC<AddFilterButtonProps> = ({datasets, onAdd}) => {\n    return (\n      <AddByDatasetButton\n        datasets={datasets}\n        className=\"add-filter-button\"\n        onAdd={onAdd}\n        buttonIntlId=\"filterManager.addFilter\"\n      />\n    );\n  };\n\n  return AddFilterButton;\n}\n\nexport default AddFilterButtonFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/filter-panel/filter-panel-header.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType, useMemo} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport PanelHeaderActionFactory from '../../side-panel/panel-header-action';\nimport {Trash} from '../../common/icons';\nimport {createLinearGradient} from '@kepler.gl/utils';\nimport {StyledPanelHeader, StyledPanelHeaderProps} from '../../common/styled-components';\nimport {RGBColor, Filter, Field} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\n\ninterface StyledFilterHeaderProps extends StyledPanelHeaderProps {\n  $labelRCGColorValues: RGBColor[];\n}\n\nexport const StyledFilterHeader = styled(StyledPanelHeader)<StyledFilterHeaderProps>`\n  cursor: pointer;\n  padding: 10px 12px;\n\n  .field-selector {\n    width: 100%;\n    flex: 2;\n  }\n\n  border-left: 3px solid;\n  ${props =>\n    props.$labelRCGColorValues && props.$labelRCGColorValues.length > 0\n      ? `border-image: ${createLinearGradient('bottom', props.$labelRCGColorValues)} 3;`\n      : 'border-color: transparent;'};\n`;\n\nconst StyledChildrenContainer = styled.div`\n  display: flex;\n  flex: 1;\n  overflow: hidden;\n`;\n\nexport type FilterPanelHeaderProps = {\n  className?: string;\n  datasets: KeplerTable[];\n  filter: Filter;\n  removeFilter: () => void;\n  actionItems?: {\n    key: string;\n    tooltip: string;\n    onClick: () => void;\n    icon: React.ElementType;\n  }[];\n  actionIcons?: {\n    delete: ComponentType;\n  };\n  allAvailableFields?: Field[];\n  idx?: number;\n  children: React.ReactNode;\n};\n\nFilterPanelHeaderFactory.deps = [PanelHeaderActionFactory];\n\nfunction FilterPanelHeaderFactory(\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n): React.ComponentType<FilterPanelHeaderProps> {\n  const defaultActionIcons = {\n    delete: Trash\n  };\n  const FilterPanelHeader: React.FC<FilterPanelHeaderProps> = ({\n    children,\n    className = '',\n    datasets,\n    filter,\n    removeFilter,\n    actionItems,\n    actionIcons = defaultActionIcons\n  }: FilterPanelHeaderProps) => {\n    const items = useMemo(\n      () =>\n        actionItems ?? [\n          {\n            key: 'delete',\n            tooltip: 'tooltip.delete',\n            onClick: removeFilter,\n            icon: actionIcons.delete\n          }\n        ],\n      [removeFilter, actionIcons, actionItems]\n    );\n    return (\n      <StyledFilterHeader\n        className={classnames('filter-panel__header', className)}\n        $labelRCGColorValues={datasets.map((d: KeplerTable) => d.color)}\n      >\n        <StyledChildrenContainer>{children}</StyledChildrenContainer>\n        {items.map(item => (\n          <PanelHeaderAction\n            key={item.key}\n            id={filter.id}\n            tooltip={item.tooltip}\n            tooltipType=\"error\"\n            onClick={item.onClick}\n            hoverColor={'errorColor'}\n            IconComponent={item.icon}\n          />\n        ))}\n      </StyledFilterHeader>\n    );\n  };\n\n  return FilterPanelHeader;\n}\n\nexport default FilterPanelHeaderFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/filter-panel/filter-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {createSelector} from 'reselect';\nimport styled from 'styled-components';\nimport get from 'lodash/get';\nimport {ALL_FIELD_TYPES, FILTER_TYPES} from '@kepler.gl/constants';\n\nimport NewFilterPanelFactory from '../../filters/filter-panels/new-filter-panel';\nimport TimeRangeFilterPanelFactory from '../../filters/filter-panels/time-range-filter-panel';\nimport SingleSelectFilterPanelFactory from '../../filters/filter-panels/single-select-filter-panel';\nimport MultiSelectFilterPanelFactory from '../../filters/filter-panels/multi-select-filter-panel';\nimport RangeFilterPanelFactory from '../../filters/filter-panels/range-filter-panel';\nimport PolygonFilterPanelFactory from '../../filters/filter-panels/polygon-filter-panel';\nimport {Field, Filter} from '@kepler.gl/types';\nimport {FilterPanelProps} from '../../filters/filter-panels/types';\nimport {Layer} from '@kepler.gl/layers';\n\nconst StyledFilterPanel = styled.div`\n  margin-bottom: 12px;\n  border-radius: 1px;\n`;\n\ninterface FilterPanelPropsImpl extends Omit<FilterPanelProps, 'allAvailableFields'> {\n  filters: Filter[];\n  layers: ReadonlyArray<Layer>;\n  isAnyFilterAnimating: boolean;\n  toggleAnimation: () => void;\n  enlargeFilter: () => void;\n  toggleFilterFeature: () => void;\n}\n\nFilterPanelFactory.deps = [\n  NewFilterPanelFactory,\n  TimeRangeFilterPanelFactory,\n  SingleSelectFilterPanelFactory,\n  MultiSelectFilterPanelFactory,\n  RangeFilterPanelFactory,\n  PolygonFilterPanelFactory\n];\n\nfunction FilterPanelFactory(\n  NewFilterPanel: ReturnType<typeof NewFilterPanelFactory>,\n  TimeRangeFilterPanel: ReturnType<typeof TimeRangeFilterPanelFactory>,\n  SingleSelectFilterPanel: ReturnType<typeof SingleSelectFilterPanelFactory>,\n  MultiSelectFilterPanel: ReturnType<typeof MultiSelectFilterPanelFactory>,\n  RangeFilterPanel: ReturnType<typeof RangeFilterPanelFactory>,\n  PolygonFilterPanel: ReturnType<typeof PolygonFilterPanelFactory>\n): React.ComponentType<FilterPanelPropsImpl> {\n  const FilterPanelComponents = {\n    default: NewFilterPanel,\n    [FILTER_TYPES.timeRange]: TimeRangeFilterPanel,\n    [FILTER_TYPES.select]: SingleSelectFilterPanel,\n    [FILTER_TYPES.multiSelect]: MultiSelectFilterPanel,\n    [FILTER_TYPES.range]: RangeFilterPanel,\n    [FILTER_TYPES.polygon]: PolygonFilterPanel\n  };\n\n  return class FilterPanel extends Component<FilterPanelPropsImpl> {\n    /* selectors */\n    fieldsSelector = (props: FilterPanelPropsImpl) => {\n      const datasetId = props.filter.dataId[0];\n      if (!datasetId) {\n        return [];\n      }\n      return get(props, ['datasets', datasetId, 'fields'], []);\n    };\n\n    filterSelector = (props: FilterPanelPropsImpl) => props.filters;\n    nameSelector = (props: FilterPanelPropsImpl) => props.filter.name;\n    dataIdSelector = (props: FilterPanelPropsImpl) => props.filter.dataId[0];\n\n    // only show current field and field that's not already been used as a filter\n    availableFieldsSelector = createSelector(\n      this.fieldsSelector,\n      this.filterSelector,\n      this.nameSelector,\n      this.dataIdSelector,\n      (fields, filters, name, dataId) =>\n        fields.filter(\n          (f: Field) =>\n            f.type &&\n            f.type !== ALL_FIELD_TYPES.geojson &&\n            (name.includes(f.name) ||\n              !filters.find(d => d.name.includes(f.name) && d.dataId.includes(dataId)))\n        )\n    );\n\n    render() {\n      const {filter} = this.props;\n\n      const {type} = filter;\n      const FilterFilterComponent =\n        (type && FilterPanelComponents[type]) || FilterPanelComponents.default;\n      const allAvailableFields = this.availableFieldsSelector(this.props);\n\n      return (\n        <StyledFilterPanel className=\"filter-panel\">\n          <FilterFilterComponent allAvailableFields={allAvailableFields} {...this.props} />\n        </StyledFilterPanel>\n      );\n    }\n  };\n}\n\nexport default FilterPanelFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/interaction-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {useIntl} from 'react-intl';\n\nimport {InteractionConfig} from '@kepler.gl/types';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\n\nimport InteractionPanelFactory from './interaction-panel/interaction-panel';\nimport PanelTitleFactory from './panel-title';\n\nimport {PanelMeta} from './common/types';\n\ntype InteractionManagerProps = {\n  interactionConfig: InteractionConfig;\n  datasets: Datasets;\n  visStateActions: typeof VisStateActions;\n  panelMetadata: PanelMeta;\n};\n\nInteractionManagerFactory.deps = [InteractionPanelFactory, PanelTitleFactory];\n\nfunction InteractionManagerFactory(\n  InteractionPanel: ReturnType<typeof InteractionPanelFactory>,\n  PanelTitle: ReturnType<typeof PanelTitleFactory>\n) {\n  const InteractionManager: React.FC<InteractionManagerProps> = ({\n    interactionConfig,\n    datasets,\n    visStateActions,\n    panelMetadata\n  }) => {\n    const {interactionConfigChange: onConfigChange, setColumnDisplayFormat} = visStateActions;\n    const intl = useIntl();\n    return (\n      <div className=\"interaction-manager\">\n        <PanelTitle\n          className=\"interaction-manager-title\"\n          title={intl.formatMessage({id: panelMetadata.label})}\n        />\n        {Object.keys(interactionConfig).map(key => (\n          <InteractionPanel\n            datasets={datasets}\n            config={interactionConfig[key]}\n            key={key}\n            onConfigChange={onConfigChange}\n            setColumnDisplayFormat={setColumnDisplayFormat}\n          />\n        ))}\n      </div>\n    );\n  };\n\n  return InteractionManager;\n}\n\nexport default InteractionManagerFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/interaction-panel/brush-config.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport RangeSliderFactory from '../../common/range-slider';\n\nimport {PanelLabel, SidePanelSection} from '../../common/styled-components';\nimport {BRUSH_CONFIG} from '@kepler.gl/reducers';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nBrushConfigFactory.deps = [RangeSliderFactory];\n\ntype BrushConfigProps = {\n  config: {\n    size: number;\n  };\n  onChange: (config: {size: number}) => void;\n};\n\nfunction BrushConfigFactory(RangeSlider: ReturnType<typeof RangeSliderFactory>) {\n  const BrushConfig = ({config, onChange}: BrushConfigProps) => (\n    <SidePanelSection>\n      <PanelLabel>\n        <FormattedMessage id={'misc.brushRadius'} />\n      </PanelLabel>\n      <RangeSlider\n        range={BRUSH_CONFIG.range}\n        value0={0}\n        value1={config.size || 10 / 2}\n        step={0.1}\n        isRanged={false}\n        onChange={value => onChange({...config, size: value[1]})}\n        inputTheme=\"secondary\"\n      />\n    </SidePanelSection>\n  );\n\n  return BrushConfig;\n}\n\nexport default BrushConfigFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/interaction-panel/interaction-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, ComponentType, ReactElement, useCallback} from 'react';\nimport styled from 'styled-components';\nimport Switch from '../../common/switch';\nimport BrushConfigFactory from './brush-config';\nimport TooltipConfigFactory from './tooltip-config';\nimport {Datasets} from '@kepler.gl/table';\nimport {InteractionConfig, ValueOf} from '@kepler.gl/types';\nimport {\n  setColumnDisplayFormat as setColumnDisplayFormatAction,\n  ActionHandler\n} from '@kepler.gl/actions';\n\nimport {\n  StyledPanelHeader,\n  PanelHeaderTitle,\n  PanelHeaderContent,\n  PanelContent\n} from '../../common/styled-components';\nimport {Messages, Crosshairs, CursorClick, Pin} from '../../common/icons';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\n\ninterface InteractionPanelProps {\n  datasets: Datasets;\n  config: ValueOf<InteractionConfig>;\n  onConfigChange: any;\n  interactionConfigIcons?: {\n    [key: string]: React.ElementType;\n  };\n  setColumnDisplayFormat: ActionHandler<typeof setColumnDisplayFormatAction>;\n}\n\nconst StyledInteractionPanel = styled.div`\n  padding-bottom: 6px;\n  contain: layout paint;\n`;\n\nInteractionPanelFactory.deps = [TooltipConfigFactory, BrushConfigFactory];\n\nconst INTERACTION_CONFIG_ICONS: {[key: string]: React.ElementType} = {\n  tooltip: Messages,\n  geocoder: Pin,\n  brush: Crosshairs,\n  coordinate: CursorClick\n};\n\nfunction InteractionPanelFactory(\n  TooltipConfig: ReturnType<typeof TooltipConfigFactory>,\n  BrushConfig: ReturnType<typeof BrushConfigFactory>\n): ComponentType<InteractionPanelProps> {\n  const InteractionPanel: React.FC<InteractionPanelProps> = ({\n    config,\n    onConfigChange,\n    datasets,\n    setColumnDisplayFormat,\n    interactionConfigIcons = INTERACTION_CONFIG_ICONS\n  }) => {\n    const [isConfigActive, setIsConfigAction] = useState(false);\n\n    const _updateConfig = useCallback(\n      newProp => {\n        onConfigChange({\n          ...config,\n          ...newProp\n        });\n      },\n      [onConfigChange, config]\n    );\n\n    const onDisplayFormatChange = useCallback(\n      (dataId, column, displayFormat) => {\n        setColumnDisplayFormat(dataId, {[column]: displayFormat});\n      },\n      [setColumnDisplayFormat]\n    );\n\n    const togglePanelActive = useCallback(() => {\n      setIsConfigAction(!isConfigActive);\n    }, [setIsConfigAction, isConfigActive]);\n\n    const {enabled} = config;\n    const toggleEnableConfig = useCallback(() => {\n      _updateConfig({enabled: !enabled});\n    }, [_updateConfig, enabled]);\n\n    const onChange = useCallback(newConfig => _updateConfig({config: newConfig}), [_updateConfig]);\n\n    const IconComponent = interactionConfigIcons[config.id];\n\n    let template: ReactElement | null = null;\n\n    switch (config.id) {\n      case 'tooltip':\n        template = (\n          <TooltipConfig\n            datasets={datasets}\n            config={config.config}\n            onChange={onChange}\n            onDisplayFormatChange={onDisplayFormatChange}\n          />\n        );\n        break;\n      case 'brush':\n        template = <BrushConfig config={config.config} onChange={onChange} />;\n        break;\n\n      default:\n        break;\n    }\n    return (\n      <StyledInteractionPanel className=\"interaction-panel\">\n        <StyledPanelHeader className=\"interaction-panel__header\" onClick={togglePanelActive}>\n          <PanelHeaderContent className=\"interaction-panel__header__content\">\n            <div className=\"interaction-panel__header__icon icon\">\n              {IconComponent ? <IconComponent height=\"16px\" /> : null}\n            </div>\n            <div className=\"interaction-panel__header__title\">\n              <PanelHeaderTitle>\n                <FormattedMessage id={config.label} />\n              </PanelHeaderTitle>\n            </div>\n          </PanelHeaderContent>\n          <div className=\"interaction-panel__header__actions\">\n            <Switch\n              checked={config.enabled}\n              id={`${config.id}-toggle`}\n              onChange={toggleEnableConfig}\n              secondary\n            />\n          </div>\n        </StyledPanelHeader>\n        {config.enabled && template && (\n          <PanelContent className=\"interaction-panel__content\">{template}</PanelContent>\n        )}\n      </StyledInteractionPanel>\n    );\n  };\n\n  return InteractionPanel;\n}\n\nexport default InteractionPanelFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/interaction-panel/tooltip-config/tooltip-chicklet.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, CSSProperties} from 'react';\nimport styled, {IStyledComponent} from 'styled-components';\nimport classnames from 'classnames';\nimport {DraggableAttributes} from '@dnd-kit/core';\nimport {CSS, Transform} from '@dnd-kit/utilities';\nimport {ChickletButton} from '../../../common/item-selector/chickleted-input';\nimport {Hash, Delete, VertDots} from '../../../common/icons';\nimport DropdownList from '../../../common/item-selector/dropdown-list';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {TimeLabelFormat, TooltipFields} from '@kepler.gl/types';\nimport {getFormatValue, getFormatLabels} from '@kepler.gl/utils';\nimport TippyTooltip from '../../../common/tippy-tooltip';\nimport {TooltipFormat} from '@kepler.gl/constants';\nimport useOnClickOutside from '../../../hooks/use-on-click-outside';\nimport {BaseComponentProps} from '../../../types';\n\ninterface TooltipChickletProps {\n  disabled: boolean;\n  item: {name: string};\n  displayOption: (item: any) => string;\n  remove: any;\n\n  attributes: DraggableAttributes;\n  listeners: any;\n  setNodeRef: (node: HTMLElement | null) => void;\n  transform: Transform | null;\n  transition?: string;\n  isDragging: boolean;\n}\n\ntype TooltipConfig = {\n  fieldsToShow: {\n    [key: string]: {name: string; format: string | null}[];\n  };\n  compareMode: boolean;\n  compareType: string | null;\n};\n\nconst ChickletAddonWrapper = styled.div`\n  position: relative;\n`;\n\nconst ChickletAddon = styled.div`\n  margin-right: 4px;\n`;\n\nconst StyledPopover = styled.div`\n  margin-left: -64px;\n  position: absolute;\n  top: 20px;\n  width: 140px;\n  z-index: 101;\n`;\n\nconst hashStyles = {\n  SHOW: 'SHOW' as const,\n  ACTIVE: 'ACTIVE' as const\n};\n\nexport type IconDivProps = BaseComponentProps & {\n  $status?: 'SHOW' | 'ACTIVE' | null;\n};\n\nconst IconDiv: IStyledComponent<'web', IconDivProps> = styled.div.attrs({\n  className: 'tooltip-chicklet__icon'\n})<IconDivProps>`\n  color: ${props =>\n    props.$status === hashStyles.SHOW\n      ? props.theme.subtextColorActive\n      : props.$status === hashStyles.ACTIVE\n      ? props.theme.activeColor\n      : props.theme.textColor};\n`;\n\nexport type SortableStyledItemProps = BaseComponentProps & {\n  $transition?: CSSProperties['transition'];\n  $transform?: CSSProperties['transform'];\n  ref: (node: HTMLElement | null) => void;\n};\nconst SortableStyledItem: IStyledComponent<\n  'web',\n  SortableStyledItemProps\n> = styled.div<SortableStyledItemProps>`\n  transition: ${props => props.$transition};\n  transform: ${props => props.$transform};\n  &.sorting {\n    opacity: 0.3;\n    pointer-events: none;\n  }\n  &:hover {\n    .tooltip-chicklet__drag-handler {\n      opacity: 1;\n    }\n  }\n`;\n\nconst StyledDragHandle = styled.div.attrs({\n  className: 'tooltip-chicklet__drag-handler'\n})`\n  display: flex;\n  align-items: center;\n  z-index: 1000;\n  opacity: 0;\n  margin-left: -5px;\n  &:hover {\n    cursor: move;\n    color: ${props => props.theme.tooltipVerticalLineColor};\n  }\n`;\n\nconst StyledTag = styled.span`\n  margin-right: 5px;\n  text-overflow: ellipsis;\n  width: 100%;\n  overflow: hidden;\n  max-width: 160px;\n`;\n\nfunction getFormatTooltip(formatLabels: TimeLabelFormat[], format: string | null) {\n  if (!format) {\n    return null;\n  }\n  const formatLabel = formatLabels.find(fl => getFormatValue(fl) === format);\n  if (formatLabel) {\n    return formatLabel.label;\n  }\n  return typeof format === 'object' ? JSON.stringify(format, null, 2) : String(format);\n}\n\n// TODO: a factory should take other factories as input\nfunction TooltipChickletFactory(\n  dataId: string,\n  config: TooltipConfig,\n  onChange: (cfg: TooltipConfig) => void,\n  fields: TooltipFields[],\n  onDisplayFormatChange\n): React.FC<TooltipChickletProps> {\n  const TooltipChicklet: React.FC<TooltipChickletProps> = (props: TooltipChickletProps) => {\n    const {\n      disabled,\n      item,\n      displayOption,\n      remove,\n      attributes,\n      listeners,\n      setNodeRef,\n      transform,\n      transition,\n      isDragging\n    } = props;\n\n    const [show, setShow] = useState(false);\n    const ref = useOnClickOutside<HTMLDivElement>(() => setShow(false));\n    // const {show} = this.state;\n    const tooltipField = config.fieldsToShow[dataId].find(\n      fieldToShow => fieldToShow.name === item.name\n    );\n    if (!tooltipField) {\n      return null;\n    }\n    const field = fields.find(f => f.name === tooltipField.name);\n    if (!field) {\n      return null;\n    }\n    const formatLabels = getFormatLabels(fields, tooltipField.name);\n    const hasFormat = Boolean(field.displayFormat);\n    const selectionIndex = formatLabels.findIndex(fl => getFormatValue(fl) === field.displayFormat);\n    const hashStyle = show ? hashStyles.SHOW : hasFormat ? hashStyles.ACTIVE : null;\n\n    return (\n      <SortableStyledItem\n        ref={setNodeRef}\n        className={classnames('sortable-layer-items', {sorting: isDragging})}\n        $transform={CSS.Translate.toString(transform)}\n        $transition={transition || ''}\n        {...attributes}\n      >\n        <ChickletButton>\n          <StyledDragHandle {...listeners}>\n            <VertDots height=\"12px\" />\n          </StyledDragHandle>\n          <StyledTag title={displayOption(item)}>{displayOption(item)}</StyledTag>\n          {formatLabels.length > 1 && (\n            <ChickletAddonWrapper>\n              <TippyTooltip\n                placement=\"top\"\n                render={() => (\n                  <span>\n                    {hasFormat ? (\n                      getFormatTooltip(formatLabels, field.displayName)\n                    ) : (\n                      <FormattedMessage id={'fieldSelector.formatting'} />\n                    )}\n                  </span>\n                )}\n              >\n                <ChickletAddon>\n                  <IconDiv $status={hashStyle}>\n                    <Hash\n                      height=\"8px\"\n                      onClick={e => {\n                        e.stopPropagation();\n                        setShow(Boolean(!show));\n                      }}\n                    />\n                  </IconDiv>\n                </ChickletAddon>\n              </TippyTooltip>\n              {show && (\n                <StyledPopover ref={ref}>\n                  <DropdownList\n                    options={formatLabels}\n                    selectionIndex={selectionIndex}\n                    displayOption={option => (option as TooltipFormat).label}\n                    onOptionSelected={(result, e) => {\n                      e.stopPropagation();\n                      setShow(false);\n\n                      const displayFormat = getFormatValue(result);\n                      const oldFieldsToShow = config.fieldsToShow[dataId];\n                      const fieldsToShow = oldFieldsToShow.map(fieldToShow => {\n                        return fieldToShow.name === tooltipField.name\n                          ? {\n                              name: tooltipField.name,\n                              format: displayFormat\n                            }\n                          : fieldToShow;\n                      });\n                      const newConfig = {\n                        ...config,\n                        fieldsToShow: {\n                          ...config.fieldsToShow,\n                          [dataId]: fieldsToShow\n                        }\n                      };\n                      onChange(newConfig);\n                      onDisplayFormatChange(dataId, field.name, displayFormat);\n                    }}\n                  />\n                </StyledPopover>\n              )}\n            </ChickletAddonWrapper>\n          )}\n          <Delete height=\"16px\" onClick={disabled ? null : remove} />\n        </ChickletButton>\n      </SortableStyledItem>\n    );\n  };\n\n  return TooltipChicklet;\n}\n\nexport default TooltipChickletFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/interaction-panel/tooltip-config.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\nimport {injectIntl, IntlShape} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {\n  SidePanelSection,\n  SBFlexboxNoMargin,\n  Button,\n  PanelLabel\n} from '../../common/styled-components';\nimport DatasetTagFactory from '../common/dataset-tag';\nimport TooltipChickletFactory from './tooltip-config/tooltip-chicklet';\nimport Switch from '../../common/switch';\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport {COMPARE_TYPES, GEOCODER_DATASET_NAME} from '@kepler.gl/constants';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport KeplerTable, {Datasets} from '@kepler.gl/table';\n\nconst TooltipConfigWrapper = styled.div`\n  .item-selector > div > div {\n    overflow: visible;\n  }\n`;\n\nconst ButtonWrapper = styled.div`\n  display: inherit;\n  padding: 0;\n\n  .button.clear-all {\n    background: transparent;\n    color: ${props => props.theme.subtextColor};\n    margin: 0 0 0 8px;\n    padding: 0;\n\n    &:hover {\n      color: ${props => props.theme.textColor};\n    }\n  }\n`;\n\nconst CompareSwitchWrapper = styled.div`\n  color: ${props => props.theme.labelColor};\n  display: flex;\n  font-size: ${props => props.theme.inputFontSize};\n  justify-content: space-between;\n  line-height: 11px;\n  margin-bottom: 8px;\n`;\n\ntype TooltipConfigProps = {\n  config: {\n    fieldsToShow: {\n      [key: string]: {name: string; format: string | null}[];\n    };\n    compareMode: boolean;\n    compareType: string | null;\n  };\n  onChange: (config: {\n    fieldsToShow: {\n      [key: string]: {name: string; format: string | null}[];\n    };\n    compareMode: boolean;\n    compareType: string | null;\n  }) => void;\n  datasets: Datasets;\n  intl: IntlShape;\n  onDisplayFormatChange: (dataId, column, displayFormat) => void;\n};\n\ntype DatasetTooltipConfigProps = {\n  config: {\n    fieldsToShow: {\n      [key: string]: {name: string; format: string | null}[];\n    };\n    compareMode: boolean;\n    compareType: string | null;\n  };\n  onChange: (config: {\n    fieldsToShow: {\n      [key: string]: {name: string; format: string | null}[];\n    };\n    compareMode: boolean;\n    compareType: string | null;\n  }) => void;\n  dataset: KeplerTable;\n  onDisplayFormatChange: (dataId, column, displayFormat) => void;\n};\n\nTooltipConfigFactory.deps = [DatasetTagFactory, FieldSelectorFactory];\nfunction TooltipConfigFactory(\n  DatasetTag: ReturnType<typeof DatasetTagFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>\n) {\n  const DatasetTooltipConfig = ({\n    config,\n    onChange,\n    dataset,\n    onDisplayFormatChange\n  }: DatasetTooltipConfigProps) => {\n    const dataId = dataset.id;\n\n    const handleClick = useCallback(\n      () =>\n        onChange({\n          ...config,\n          fieldsToShow: {\n            ...config.fieldsToShow,\n            [dataId]: []\n          }\n        }),\n      [config, dataId, onChange]\n    );\n\n    const findSelectedHelper = useCallback((selected, tooltipFields) => {\n      return selected.map(\n        f =>\n          tooltipFields.find(tooltipField => tooltipField.name === f.name) || {\n            name: f.name,\n            // default initial tooltip is null\n            format: null\n          }\n      );\n    }, []);\n\n    const handleSelect = useCallback(\n      selected => {\n        const newConfig: DatasetTooltipConfigProps['config'] = {\n          ...config,\n          fieldsToShow: {\n            ...config.fieldsToShow,\n            [dataId]: findSelectedHelper(selected, config.fieldsToShow[dataId])\n          }\n        };\n        onChange(newConfig);\n      },\n      [config, dataId, onChange, findSelectedHelper]\n    );\n\n    const handleReorderItems = useCallback(\n      newOrder =>\n        onChange({\n          ...config,\n          fieldsToShow: {\n            ...config.fieldsToShow,\n            [dataId]: newOrder\n          }\n        }),\n      [config, dataId, onChange]\n    );\n    return (\n      <SidePanelSection key={dataId}>\n        <SBFlexboxNoMargin>\n          <DatasetTag dataset={dataset} />\n          {Boolean(config.fieldsToShow[dataId].length) && (\n            <ButtonWrapper>\n              <Button className=\"clear-all\" onClick={handleClick} width=\"54px\" secondary>\n                <FormattedMessage id=\"fieldSelector.clearAll\" />\n              </Button>\n            </ButtonWrapper>\n          )}\n        </SBFlexboxNoMargin>\n        <FieldSelector\n          fields={dataset.fields}\n          value={config.fieldsToShow[dataId]}\n          onSelect={handleSelect}\n          reorderItems={handleReorderItems}\n          closeOnSelect={false}\n          multiSelect\n          inputTheme=\"secondary\"\n          CustomChickletComponent={TooltipChickletFactory(\n            dataId,\n            config,\n            onChange,\n            dataset.fields,\n            onDisplayFormatChange\n          )}\n        />\n      </SidePanelSection>\n    );\n  };\n\n  const TooltipConfig = ({\n    config,\n    datasets,\n    onChange,\n    onDisplayFormatChange,\n    intl\n  }: TooltipConfigProps) => {\n    const handleChange = useCallback(\n      (option: string | number | boolean | object | null) =>\n        onChange({\n          ...config,\n          compareType: option as string | null\n        }),\n      [config, onChange]\n    );\n\n    return (\n      <TooltipConfigWrapper>\n        {Object.keys(config.fieldsToShow).map(dataId =>\n          dataId === GEOCODER_DATASET_NAME ? null : (\n            <DatasetTooltipConfig\n              key={dataId}\n              config={config}\n              onChange={onChange}\n              dataset={datasets[dataId]}\n              onDisplayFormatChange={onDisplayFormatChange}\n            />\n          )\n        )}\n        <CompareSwitchWrapper>\n          <FormattedMessage id=\"compare.modeLabel\" />\n          <Switch\n            checked={config.compareMode}\n            id=\"compare-mode-toggle\"\n            onChange={() => {\n              const newConfig = {\n                ...config,\n                compareMode: !config.compareMode\n              };\n              onChange(newConfig);\n            }}\n            secondary\n          />\n        </CompareSwitchWrapper>\n        <SidePanelSection>\n          <PanelLabel>\n            <FormattedMessage id=\"compare.typeLabel\" />\n          </PanelLabel>\n          <ItemSelector\n            disabled={!config.compareMode}\n            displayOption={d =>\n              intl.formatMessage({\n                id: `compare.types.${d}`\n              })\n            }\n            selectedItems={config.compareType}\n            options={Object.values(COMPARE_TYPES)}\n            multiSelect={false}\n            searchable={false}\n            inputTheme={'secondary'}\n            getOptionValue={d => d}\n            onChange={handleChange}\n          />\n        </SidePanelSection>\n      </TooltipConfigWrapper>\n    );\n  };\n\n  return injectIntl(TooltipConfig);\n}\n\nexport default TooltipConfigFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\n\nimport {injectIntl, WrappedComponentProps} from 'react-intl';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport LayerListFactory from './layer-panel/layer-list';\nimport DatasetLayerGroupFactory from './layer-panel/dataset-layer-group';\nimport PanelViewListToggleFactory from './panel-view-list-toggle';\nimport PanelTitleFactory from './panel-title';\nimport DatasetSectionFactory from './layer-panel/dataset-section';\nimport AddLayerButtonFactory from './layer-panel/add-layer-button';\n\nimport ItemSelector from '../common/item-selector/item-selector';\nimport {PanelLabel, SidePanelDivider, SidePanelSection} from '../common/styled-components';\nimport InfoHelperFactory from '../common/info-helper';\n\nimport {LAYER_BLENDINGS, OVERLAY_BLENDINGS, PANEL_VIEW_TOGGLES} from '@kepler.gl/constants';\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {UIStateActions, VisStateActions, MapStateActions, ActionHandler} from '@kepler.gl/actions';\nimport {SidePanelItem} from '../types';\nimport {PanelListView} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\ntype LayerBlendingSelectorProps = {\n  layerBlending: string;\n  updateLayerBlending: ActionHandler<typeof VisStateActions.updateLayerBlending>;\n  className?: string;\n} & WrappedComponentProps;\n\ntype OverlayBlendingSelectorProps = {\n  overlayBlending: string;\n  updateOverlayBlending: ActionHandler<typeof VisStateActions.updateOverlayBlending>;\n  infoHelper: React.ReactNode;\n} & WrappedComponentProps;\n\ntype LayerManagerProps = {\n  datasets: Datasets;\n  layers: Layer[];\n  layerOrder: string[];\n  layerClasses: LayerClassesType;\n  layerBlending: string;\n  overlayBlending: string;\n  uiStateActions: typeof UIStateActions;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n  showAddDataModal: () => void;\n  removeDataset: ActionHandler<typeof UIStateActions.openDeleteModal>;\n  showDatasetTable: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  panelListView: PanelListView;\n  panelMetadata: SidePanelItem;\n  showDeleteDataset?: boolean;\n} & WrappedComponentProps;\n\nexport const LayerBlendingSelector = React.memo(\n  ({layerBlending, updateLayerBlending, intl, className}: LayerBlendingSelectorProps) => {\n    const labeledLayerBlendings = Object.keys(LAYER_BLENDINGS).reduce(\n      (acc, current) => ({\n        ...acc,\n        [intl.formatMessage({id: LAYER_BLENDINGS[current].label})]: current\n      }),\n      {}\n    );\n\n    const onChange = useCallback(\n      blending => updateLayerBlending(labeledLayerBlendings[blending]),\n      [updateLayerBlending, labeledLayerBlendings]\n    );\n\n    return (\n      <SidePanelSection className={className}>\n        <PanelLabel>\n          <FormattedMessage id=\"layerBlending.title\" />\n        </PanelLabel>\n        <ItemSelector\n          selectedItems={intl.formatMessage({id: LAYER_BLENDINGS[layerBlending].label})}\n          options={Object.keys(labeledLayerBlendings)}\n          multiSelect={false}\n          searchable={false}\n          onChange={onChange}\n        />\n      </SidePanelSection>\n    );\n  }\n);\nLayerBlendingSelector.displayName = 'LayerBlendingSelector';\n\nconst InfoHelperWrapper = styled.div`\n  float: right;\n`;\n\nconst OverlayBlendingSelectorTitleRow = styled.div`\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n`;\n\nconst OverlayBlendingSelector = React.memo(\n  ({overlayBlending, updateOverlayBlending, intl, infoHelper}: OverlayBlendingSelectorProps) => {\n    const labeledOverlayBlendings = Object.keys(OVERLAY_BLENDINGS).reduce(\n      (acc, current) => ({\n        ...acc,\n        [intl.formatMessage({id: OVERLAY_BLENDINGS[current].label})]: current\n      }),\n      {}\n    );\n\n    const onChange = useCallback(\n      blending => updateOverlayBlending(labeledOverlayBlendings[blending]),\n      [updateOverlayBlending, labeledOverlayBlendings]\n    );\n\n    return (\n      <SidePanelSection>\n        <OverlayBlendingSelectorTitleRow>\n          <PanelLabel>\n            <FormattedMessage id=\"overlayBlending.title\" />\n          </PanelLabel>\n          <InfoHelperWrapper>{infoHelper}</InfoHelperWrapper>\n        </OverlayBlendingSelectorTitleRow>\n        <ItemSelector\n          selectedItems={intl.formatMessage({id: OVERLAY_BLENDINGS[overlayBlending].label})}\n          options={Object.keys(labeledOverlayBlendings)}\n          multiSelect={false}\n          searchable={false}\n          onChange={onChange}\n        />\n      </SidePanelSection>\n    );\n  }\n);\nOverlayBlendingSelector.displayName = 'OverlayBlendingSelector';\n\nLayerManagerFactory.deps = [\n  LayerListFactory,\n  DatasetLayerGroupFactory,\n  PanelViewListToggleFactory,\n  PanelTitleFactory,\n  DatasetSectionFactory,\n  AddLayerButtonFactory,\n  InfoHelperFactory\n];\n\nfunction LayerManagerFactory(\n  LayerList: ReturnType<typeof LayerListFactory>,\n  DatasetLayerGroup: ReturnType<typeof DatasetLayerGroupFactory>,\n  PanelViewListToggle: ReturnType<typeof PanelViewListToggleFactory>,\n  PanelTitle: ReturnType<typeof PanelTitleFactory>,\n  DatasetSection: ReturnType<typeof DatasetSectionFactory>,\n  AddLayerButton: ReturnType<typeof AddLayerButtonFactory>,\n  InfoHelper: ReturnType<typeof InfoHelperFactory>\n) {\n  const LayerManager: React.FC<LayerManagerProps> = ({\n    layers,\n    datasets,\n    intl,\n    layerOrder,\n    panelListView,\n    panelMetadata,\n    layerClasses,\n    layerBlending,\n    overlayBlending,\n    showAddDataModal,\n    showDeleteDataset = true,\n    updateTableColor,\n    showDatasetTable,\n    removeDataset,\n    uiStateActions,\n    visStateActions,\n    mapStateActions\n  }) => {\n    const {addLayer} = visStateActions;\n    const {togglePanelListView} = uiStateActions;\n    const onAddLayer = useCallback(\n      (dataset: string) => {\n        addLayer(undefined, dataset);\n      },\n      [addLayer]\n    );\n\n    const onTogglePanelListView = useCallback(\n      (listView: string) => {\n        togglePanelListView({panelId: 'layer', listView});\n      },\n      [togglePanelListView]\n    );\n\n    const isSortByDatasetMode = panelListView === PANEL_VIEW_TOGGLES.byDataset;\n\n    // temp patch to hide layers that are in development\n    const enableRasterTileLayer = getApplicationConfig().enableRasterTileLayer;\n    const enableWMSLayer = getApplicationConfig().enableWMSLayer;\n    const filteredLayerClasses = useMemo(() => {\n      let filteredClasses = layerClasses;\n      if (!enableRasterTileLayer) {\n        const {rasterTile: _rasterTile, ...rest} = filteredClasses;\n        filteredClasses = rest as LayerClassesType;\n      }\n      if (!enableWMSLayer) {\n        const {wms: _wms, ...rest} = filteredClasses;\n        filteredClasses = rest as LayerClassesType;\n      }\n      return filteredClasses as LayerClassesType;\n    }, [enableRasterTileLayer, enableWMSLayer, layerClasses]);\n\n    return (\n      <div className=\"layer-manager\">\n        <SidePanelSection>\n          <PanelViewListToggle togglePanelListView={onTogglePanelListView} mode={panelListView} />\n        </SidePanelSection>\n        <DatasetSection\n          datasets={datasets}\n          showDatasetTable={showDatasetTable}\n          updateTableColor={updateTableColor}\n          removeDataset={removeDataset}\n          showDeleteDataset={showDeleteDataset}\n          showDatasetList={!isSortByDatasetMode}\n          showAddDataModal={showAddDataModal}\n        />\n        <SidePanelDivider />\n        <SidePanelSection>\n          <PanelTitle\n            className=\"layer-manager-title\"\n            title={intl.formatMessage({id: panelMetadata.label})}\n          >\n            <AddLayerButton datasets={datasets} onAdd={onAddLayer} />\n          </PanelTitle>\n        </SidePanelSection>\n        <SidePanelSection>\n          {isSortByDatasetMode ? (\n            <DatasetLayerGroup\n              datasets={datasets}\n              showDatasetTable={showDatasetTable}\n              layers={layers}\n              updateTableColor={updateTableColor}\n              removeDataset={removeDataset}\n              layerOrder={layerOrder}\n              layerClasses={filteredLayerClasses}\n              uiStateActions={uiStateActions}\n              visStateActions={visStateActions}\n              mapStateActions={mapStateActions}\n              showDeleteDataset={showDeleteDataset}\n            />\n          ) : (\n            <LayerList\n              layers={layers}\n              datasets={datasets}\n              layerOrder={layerOrder}\n              uiStateActions={uiStateActions}\n              visStateActions={visStateActions}\n              mapStateActions={mapStateActions}\n              layerClasses={filteredLayerClasses}\n            />\n          )}\n        </SidePanelSection>\n        <LayerBlendingSelector\n          layerBlending={layerBlending}\n          updateLayerBlending={visStateActions.updateLayerBlending}\n          intl={intl}\n        />\n        <OverlayBlendingSelector\n          overlayBlending={overlayBlending}\n          updateOverlayBlending={visStateActions.updateOverlayBlending}\n          intl={intl}\n          infoHelper={\n            <InfoHelper\n              id={`overlayBlending-description`}\n              description={'overlayBlending.description'}\n            />\n          }\n        />\n      </div>\n    );\n  };\n\n  return injectIntl(LayerManager);\n}\n\nexport default LayerManagerFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/add-layer-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport AddByDatasetButton from '../add-by-dataset-button';\nimport {Datasets} from '@kepler.gl/table';\n\nexport type AddLayerButtonProps = {\n  datasets: Datasets;\n  onAdd: (dataId: string) => void;\n};\n\nfunction AddLayerButtonFactory() {\n  const AddLayerButton: React.FC<AddLayerButtonProps> = ({datasets, onAdd}) => {\n    return (\n      <AddByDatasetButton\n        datasets={datasets}\n        className=\"add-layer-button\"\n        onAdd={onAdd}\n        buttonIntlId=\"layerManager.addLayer\"\n      />\n    );\n  };\n\n  return AddLayerButton;\n}\nexport default AddLayerButtonFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/aggr-scale-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport DimensionScaleSelectorFactory from './dimension-scale-selector';\n\nAggrScaleSelectorFactory.deps = [DimensionScaleSelectorFactory];\nexport function AggrScaleSelectorFactory(DimensionScaleSelector) {\n  const AggrScaleSelector = ({channel, dataset, layer, onChange, setColorUI, label}) => {\n    const {key} = channel;\n    const scaleOptions = layer.getScaleOptions(key);\n\n    return Array.isArray(scaleOptions) && scaleOptions.length > 1 ? (\n      <DimensionScaleSelector\n        dataset={dataset}\n        layer={layer}\n        channel={channel}\n        label={label || `${key} Scale`}\n        onChange={onChange}\n        setColorUI={setColorUI}\n      />\n    ) : null;\n  };\n\n  return AggrScaleSelector;\n}\n\nexport default AggrScaleSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/channel-by-value-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\n\nimport {CHANNEL_SCALE_SUPPORTED_FIELDS} from '@kepler.gl/constants';\nimport {Layer, VisualChannel} from '@kepler.gl/layers';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {ColorUI, Field, LayerVisConfig, NestedPartial} from '@kepler.gl/types';\n\nimport DimensionScaleSelectorFactory from './dimension-scale-selector';\nimport VisConfigByFieldSelectorFactory from './vis-config-by-field-selector';\n\nexport type ChannelByValueSelectorProps = {\n  layer: Layer;\n  channel: VisualChannel;\n  onChange: (\n    newConfig: {[key: string]: Field | null | string},\n    key: string,\n    newVisConfig?: Partial<LayerVisConfig>\n  ) => void;\n  fields: Field[];\n  dataset: KeplerTable | undefined;\n  description?: string;\n  setColorUI: (prop: string, newConfig: NestedPartial<ColorUI>) => void;\n  updateLayerVisConfig: (newConfig: Partial<LayerVisConfig>) => void;\n  disabled?: boolean;\n};\n\nChannelByValueSelectorFactory.deps = [\n  VisConfigByFieldSelectorFactory,\n  DimensionScaleSelectorFactory\n];\n\nexport function ChannelByValueSelectorFactory(\n  VisConfigByFieldSelector: ReturnType<typeof VisConfigByFieldSelectorFactory>,\n  DimensionScaleSelector: ReturnType<typeof DimensionScaleSelectorFactory>\n): React.FC<ChannelByValueSelectorProps> {\n  const ChannelByValueSelector: React.FC<ChannelByValueSelectorProps> = ({\n    layer,\n    channel,\n    onChange,\n    fields,\n    dataset,\n    description,\n    setColorUI,\n    disabled\n  }) => {\n    const {channelScaleType, field, key, property, scale, defaultMeasure, supportedFieldTypes} =\n      channel;\n    const channelSupportedFieldTypes =\n      supportedFieldTypes || CHANNEL_SCALE_SUPPORTED_FIELDS[channelScaleType];\n    const supportedFields = fields.filter(({type}) => channelSupportedFieldTypes.includes(type));\n    const showScale = !layer.isAggregated && layer.config[scale] && layer.config[field];\n    const defaultDescription = 'layerConfiguration.defaultDescription';\n    const updateField = useCallback(\n      val => {\n        onChange({[field]: val}, key);\n      },\n      [onChange, field, key]\n    );\n\n    return (\n      <div className=\"channel-by-value-selector\">\n        <VisConfigByFieldSelector\n          description={description || defaultDescription}\n          fields={supportedFields}\n          id={layer.id}\n          key={`${key}-channel-selector`}\n          property={property}\n          disabled={disabled}\n          placeholder={defaultMeasure || 'placeholder.selectField'}\n          selectedField={layer.config[field]}\n          updateField={updateField}\n        />\n        {showScale && !disabled ? (\n          <DimensionScaleSelector\n            layer={layer}\n            channel={channel}\n            dataset={dataset}\n            label={`${property} scale`}\n            setColorUI={setColorUI}\n            onChange={onChange}\n          />\n        ) : null}\n      </div>\n    );\n  };\n\n  return ChannelByValueSelector;\n}\n\nexport default ChannelByValueSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-breaks-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {toArray} from '@kepler.gl/common-utils';\nimport {SCALE_TYPES} from '@kepler.gl/constants';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {Bin, ColorMap, ColorUI, Field} from '@kepler.gl/types';\nimport {\n  ColorBreak,\n  ColorBreakOrdinal,\n  colorBreaksToColorMap,\n  colorMapToColorBreaks,\n  isNumericColorBreaks as notOrdinalColorBreaks\n} from '@kepler.gl/utils';\nimport React, {useCallback, useMemo, useEffect} from 'react';\nimport styled from 'styled-components';\nimport ColumnStatsChartFactory from '../../common/column-stats-chart';\nimport {Edit} from '../../common/icons';\nimport {Button} from '../../common/styled-components';\nimport CustomPaletteFactory, {\n  ColorPaletteItem,\n  ColorSwatch,\n  EditableColorRange,\n  CategoricalSelector,\n  SetColorUIFunc\n} from './custom-palette';\n\nconst StyledColorBreaksPanel = styled.div.attrs({\n  className: 'styled-color-breaks-panel'\n})`\n  margin-bottom: 10px;\n`;\n\nconst StyledColorBreaksDisplay = styled.div.attrs({\n  className: 'styled-color-breaks-display'\n})`\n  padding: 8px 12px 0 12px;\n`;\n\nconst ColorBreaksPanelWrapper = styled.div``;\n\ntype EditButtonProps = {onClickEdit: () => void};\n\nexport const EditButton: React.FC<EditButtonProps> = ({onClickEdit}) => (\n  <Button className=\"editp__button\" link onClick={onClickEdit}>\n    <Edit height=\"16px\" />\n    Edit\n  </Button>\n);\n\nexport type ColorBreaksDisplayProps = {\n  currentBreaks?: ColorBreak[] | ColorBreakOrdinal[] | null;\n  onEdit: (() => void) | null;\n};\n\nexport const ColorBreaksDisplay: React.FC<ColorBreaksDisplayProps> = ({currentBreaks, onEdit}) => {\n  if (!notOrdinalColorBreaks(currentBreaks)) {\n    // don't display color breaks for ordinal breaks, user can change it in custom breaks\n    return null;\n  }\n  return (\n    <StyledColorBreaksDisplay>\n      {onEdit ? <EditButton onClickEdit={onEdit} /> : null}\n      {currentBreaks.map((item, index) => (\n        <ColorPaletteItem className=\"disabled\" key={index}>\n          <div className=\"custom-palette-input__left\">\n            <ColorSwatch color={item.data} />\n            <EditableColorRange\n              item={item}\n              isLast={index === currentBreaks.length - 1}\n              index={index}\n              editable={false}\n            />\n          </div>\n        </ColorPaletteItem>\n      ))}\n    </StyledColorBreaksDisplay>\n  );\n};\n\nexport type CategoricalColorDisplayProps = {\n  colorMap?: ColorMap;\n  onEdit: (() => void) | null;\n};\n\nexport const CategoricalColorDisplay: React.FC<CategoricalColorDisplayProps> = ({\n  colorMap,\n  onEdit\n}: CategoricalColorDisplayProps) => {\n  return (\n    <StyledColorBreaksDisplay>\n      {onEdit ? <EditButton onClickEdit={onEdit} /> : null}\n      {colorMap?.map((cm, index) => (\n        <ColorPaletteItem className=\"disabled\" key={index}>\n          <div className=\"custom-palette-input__left\">\n            <ColorSwatch color={cm[1]} />\n            <CategoricalSelector\n              index={index}\n              selectedValues={toArray(cm[0])}\n              allValues={[]}\n              editable={false}\n            />\n          </div>\n        </ColorPaletteItem>\n      ))}\n    </StyledColorBreaksDisplay>\n  );\n};\n\nexport type ColorBreaksPanelProps = {\n  colorBreaks: ColorBreak[] | ColorBreakOrdinal[] | null;\n  colorUIConfig: ColorUI;\n  dataset: KeplerTable | undefined;\n  colorField: Field;\n  isCustomBreaks: boolean;\n  allBins: Bin[];\n  filteredBins: Bin[];\n  isFiltered: boolean;\n  histogramDomain: number[];\n  ordinalDomain: number[] | string[];\n  setColorUI: SetColorUIFunc;\n  onScaleChange: (v: string, visConfg?: Record<string, any>) => void;\n  onApply: (e: React.MouseEvent) => void;\n  onCancel: () => void;\n};\n\nColorBreaksPanelFactory.deps = [CustomPaletteFactory, ColumnStatsChartFactory];\n\nfunction ColorBreaksPanelFactory(\n  CustomPalette: ReturnType<typeof CustomPaletteFactory>,\n  ColumnStatsChart: ReturnType<typeof ColumnStatsChartFactory>\n): React.FC<ColorBreaksPanelProps> {\n  // eslint-disable-next-line complexity\n  const ColorBreaksPanel: React.FC<ColorBreaksPanelProps> = ({\n    colorBreaks,\n    colorUIConfig,\n    dataset,\n    colorField,\n    isCustomBreaks,\n    allBins,\n    filteredBins,\n    isFiltered,\n    histogramDomain,\n    ordinalDomain,\n    setColorUI,\n    onScaleChange,\n    onApply,\n    onCancel\n  }) => {\n    const {customPalette, showSketcher, colorRangeConfig} = colorUIConfig;\n    const isEditingCustomBreaks = Boolean(colorRangeConfig.customBreaks);\n\n    const currentBreaks = useMemo(\n      () => (isEditingCustomBreaks ? colorMapToColorBreaks(customPalette.colorMap) : colorBreaks),\n      [customPalette.colorMap, isEditingCustomBreaks, colorBreaks]\n    );\n\n    // Update layers on editing custom breaks\n    useEffect(() => {\n      const {type} = customPalette || {};\n      if (\n        isEditingCustomBreaks &&\n        (type === SCALE_TYPES.customOrdinal || type === SCALE_TYPES.custom)\n      ) {\n        onScaleChange(type, customPalette);\n      }\n    }, [isEditingCustomBreaks, customPalette, onScaleChange]);\n\n    const onClickEditCustomBreaks = useCallback(() => {\n      setColorUI({\n        colorRangeConfig: {\n          customBreaks: true\n        }\n      });\n    }, [setColorUI]);\n\n    const onCilckCancel = useCallback(() => {\n      setColorUI({\n        showSketcher: false,\n        colorRangeConfig: {\n          customBreaks: false\n        }\n      });\n      onCancel();\n    }, [setColorUI, onCancel]);\n\n    const onColumnStatsChartChanged = useCallback(\n      newColorBreaks => {\n        const newColors = newColorBreaks.map(cb => cb.data);\n        const newColorMap = colorBreaksToColorMap(newColorBreaks);\n\n        const newCustomPalette = {\n          ...customPalette,\n          colorMap: newColorMap,\n          colors: newColors\n        };\n\n        // update custom pallette editor\n        if (!isEditingCustomBreaks) {\n          setColorUI({\n            colorRangeConfig: {\n              customBreaks: true\n            },\n            customPalette: newCustomPalette\n          });\n        } else {\n          setColorUI({customPalette: newCustomPalette});\n        }\n\n        // trigger the map to re-render using newCustomPalette\n        onScaleChange(SCALE_TYPES.custom, newCustomPalette);\n      },\n      [setColorUI, customPalette, isEditingCustomBreaks, onScaleChange]\n    );\n\n    return (\n      <ColorBreaksPanelWrapper>\n        {dataset && allBins.length > 1 && notOrdinalColorBreaks(colorBreaks) ? (\n          <ColumnStatsChart\n            colorField={colorField}\n            dataset={dataset}\n            colorBreaks={currentBreaks}\n            allBins={allBins}\n            filteredBins={filteredBins}\n            isFiltered={isFiltered}\n            histogramDomain={histogramDomain}\n            onChangedUpdater={onColumnStatsChartChanged}\n          />\n        ) : null}\n        <StyledColorBreaksPanel>\n          {isEditingCustomBreaks ? (\n            <CustomPalette\n              ordinalDomain={ordinalDomain}\n              customPalette={customPalette}\n              setColorPaletteUI={setColorUI}\n              showSketcher={showSketcher}\n              onApply={onApply}\n              onCancel={onCilckCancel}\n            />\n          ) : currentBreaks && allBins.length > 1 && notOrdinalColorBreaks(colorBreaks) ? (\n            <ColorBreaksDisplay\n              currentBreaks={currentBreaks}\n              onEdit={isCustomBreaks ? onClickEditCustomBreaks : null}\n            />\n          ) : (isCustomBreaks || customPalette.type === SCALE_TYPES.customOrdinal) &&\n            customPalette.colorMap &&\n            customPalette.name?.endsWith(colorField.name) ? (\n            <CategoricalColorDisplay\n              colorMap={customPalette.colorMap}\n              onEdit={isCustomBreaks ? onClickEditCustomBreaks : null}\n            />\n          ) : null}\n        </StyledColorBreaksPanel>\n      </ColorBreaksPanelWrapper>\n    );\n  };\n\n  return React.memo(ColorBreaksPanel);\n}\n\nexport default ColorBreaksPanelFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-palette-preset.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {range} from 'd3-array';\n\nimport {ColorsByTheme} from '@kepler.gl/constants';\nimport {HexColor} from '@kepler.gl/types';\n\nconst PALETTE_HEIGHT = '8px';\nconst ROWS = 22;\n\nconst StyledColorPalette = styled.div`\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  padding: 12px;\n\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nconst StyledColorColumn = styled.div`\n  display: flex;\n  flex-grow: 1;\n  flex-direction: column;\n  justify-content: space-between;\n`;\n\ntype StyledColorBlockProps = {\n  selected?: boolean;\n};\n\nconst StyledColorBlock = styled.div<StyledColorBlockProps>`\n  flex-grow: 1;\n  height: ${PALETTE_HEIGHT};\n  border-width: 1px;\n  border-style: solid;\n`;\n\nexport type PresetColorPaletteProps = {\n  themes: string[];\n  selectedColor: HexColor;\n  onSelectColor: (c: HexColor, e?: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;\n};\n\nconst PresetColorPalette: React.FC<PresetColorPaletteProps> = ({\n  themes,\n  onSelectColor,\n  selectedColor\n}: PresetColorPaletteProps) => (\n  <StyledColorPalette>\n    {themes.map(theme => (\n      <StyledColorColumn key={theme} className=\"single-color-palette__column\">\n        {range(1, ROWS + 1, 1).map(key => (\n          <StyledColorBlock\n            className=\"single-color-palette__block\"\n            style={{\n              backgroundColor: ColorsByTheme[theme][key],\n              borderColor:\n                selectedColor === ColorsByTheme[theme][key].toUpperCase()\n                  ? 'white'\n                  : ColorsByTheme[theme][key]\n            }}\n            key={`${theme}_${key}`}\n            selected={selectedColor === ColorsByTheme[theme][key].toUpperCase()}\n            onClick={e => onSelectColor(ColorsByTheme[theme][key], e)}\n          />\n        ))}\n      </StyledColorColumn>\n    ))}\n  </StyledColorPalette>\n);\n\nexport default PresetColorPalette;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-palette.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport {HexColor, RGBColor} from '@kepler.gl/types';\nimport styled, {IStyledComponent} from 'styled-components';\nimport classnames from 'classnames';\nimport {BaseComponentProps} from '../../types';\n\ntype ColorPaletteProps = BaseComponentProps & {\n  colors: RGBColor | HexColor[];\n  colorWidths?: number[] | null;\n  height?: number;\n  isSelected?: boolean;\n  isReversed?: boolean;\n  className?: string;\n};\n\nconst PaletteWrapper = styled.div.attrs({\n  className: 'color-range-palette__inner'\n})`\n  border-radius: 2px;\n  display: flex;\n  flex-direction: row;\n  flex-grow: 1;\n  justify-content: space-between;\n  overflow: hidden;\n`;\n\nexport type PaletteContainerProps = BaseComponentProps & {\n  $isColorChart?: boolean;\n  $isSelected?: boolean;\n};\n\nconst PaletteContainer: IStyledComponent<'web', PaletteContainerProps> = styled.div.attrs(\n  props => ({\n    className: classnames('color-range-palette', props.className)\n  })\n)<PaletteContainerProps>`\n  display: flex;\n  flex-grow: 1;\n  border-width: ${props => (props.$isColorChart ? '0px' : '1px')};\n  border-style: solid;\n  border-color: ${props => (props.$isSelected ? '#FFFFFF' : 'transparent')};\n  padding: ${props => (props.$isColorChart ? '0px' : '4px')};\n  border-radius: 4px;\n`;\n\nconst StyledColorBlock = styled.div.attrs({\n  className: 'color-range-palette__block'\n})`\n  flex-grow: 1;\n`;\n\nconst ColorPalette = ({\n  colors = [],\n  height = 10,\n  colorWidths = null,\n  className = '',\n  isSelected = false,\n  isReversed = false\n}: ColorPaletteProps) => {\n  const paletteWrapperStyle = useMemo(\n    () => ({height, transform: `scale(${isReversed ? -1 : 1}, 1)`}),\n    [height, isReversed]\n  );\n  return (\n    <PaletteContainer\n      className={className}\n      $isSelected={isSelected}\n      $isColorChart={Boolean(colorWidths)}\n    >\n      <PaletteWrapper style={paletteWrapperStyle}>\n        {colors.map((color: number | string, index: number) =>\n          colorWidths && colorWidths[index] ? (\n            <StyledColorBlock\n              key={`${color}-${index}`}\n              style={{backgroundColor: String(color), width: colorWidths[index]}}\n            />\n          ) : (\n            <StyledColorBlock key={`${color}-${index}`} style={{backgroundColor: String(color)}} />\n          )\n        )}\n      </PaletteWrapper>\n    </PaletteContainer>\n  );\n};\n\nexport default ColorPalette;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-range-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {MouseEvent, useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\nimport {KEPLER_COLOR_PALETTES, PALETTE_TYPES, ColorPalette} from '@kepler.gl/constants';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ColorRange, ColorUI, NestedPartial} from '@kepler.gl/types';\nimport {\n  hasColorMap,\n  updateColorRangeBySelectedPalette,\n  paletteIsSteps,\n  paletteIsType,\n  paletteIsColorBlindSafe\n} from '@kepler.gl/utils';\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport {PanelLabel, Tooltip} from '../../common/styled-components';\nimport Switch from '../../common/switch';\nimport ColorPalettePanel from './color-palette';\nimport CustomPaletteFactory from './custom-palette';\n\nimport {capitalizeFirstLetter} from '@kepler.gl/utils';\nimport {range} from 'd3-array';\n\ntype ColorRangeSelectorProps = {\n  colorPalettes?: ColorPalette[];\n  colorPaletteUI: ColorUI;\n  selectedColorRange: ColorRange;\n  onSelectColorRange: (p: ColorRange, e: MouseEvent) => void;\n  setColorPaletteUI: (newConfig: NestedPartial<ColorUI>) => void;\n};\n\ntype PaletteConfigProps = {\n  label: string;\n  value: string | number | boolean;\n  config: {\n    type: string;\n    options: (string | number | boolean)[];\n  };\n  onChange: (v: any) => void;\n  disabled?: boolean;\n  prop: string;\n  reason?: string;\n};\n\n// @ts-ignore cant concat 'all' to PALETTE_TYPES values\nexport const ALL_TYPES = Object.values(PALETTE_TYPES).concat(['all']);\nconst MAX_STEPS = 20;\nconst ALL_STEPS = range(2, MAX_STEPS + 1, 1);\n\nconst StyledColorConfig = styled.div`\n  padding: 12px 12px 0 12px;\n`;\n\nconst StyledColorRangeSelector = styled.div.attrs({\n  className: 'color-range-selector'\n})`\n  padding-bottom: 12px;\n`;\nconst StyledColorPalette = styled.div`\n  padding: 0 8px 8px 8px;\n`;\n\nconst StyledPaletteConfig = styled.div`\n  margin-bottom: 8px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  .color-palette__config__select {\n    width: 40%;\n    display: flex;\n    flex-direction: row-reverse;\n\n    .item-selector {\n      width: 100%;\n    }\n  }\n`;\n\nconst CONFIG_SETTINGS = {\n  type: {\n    type: 'select',\n    options: ALL_TYPES\n  },\n  steps: {\n    type: 'select',\n    options: ALL_STEPS,\n    disabled: colorRange => hasColorMap(colorRange),\n    reason: 'color.disableStepReason'\n  },\n  reversed: {\n    type: 'switch',\n    options: [true, false]\n  },\n  colorBlindSafe: {\n    type: 'switch',\n    options: [true, false]\n  },\n  custom: {\n    label: 'customPalette',\n    type: 'switch',\n    options: [true, false]\n  }\n};\nconst displayOption = d => capitalizeFirstLetter(d);\nconst getOptionValue = d => d;\nconst noop = () => {\n  // do nothing\n};\n\nColorRangeSelectorFactory.deps = [CustomPaletteFactory];\nfunction ColorRangeSelectorFactory(\n  CustomPalette: ReturnType<typeof CustomPaletteFactory>\n): React.FC<ColorRangeSelectorProps> {\n  const ColorRangeSelector: React.FC<ColorRangeSelectorProps> = ({\n    colorPalettes = KEPLER_COLOR_PALETTES,\n    colorPaletteUI,\n    setColorPaletteUI = noop,\n    onSelectColorRange = noop,\n    selectedColorRange\n  }) => {\n    const {customPalette, showSketcher, colorRangeConfig} = colorPaletteUI;\n    const {type, steps, colorBlindSafe, reversed} = colorRangeConfig;\n\n    const filteredColorPalettes = useMemo(() => {\n      return (\n        colorPalettes?.filter(\n          palette =>\n            paletteIsType(palette, type) &&\n            paletteIsSteps(palette, steps) &&\n            paletteIsColorBlindSafe(palette, colorBlindSafe)\n        ) ?? []\n      );\n    }, [colorPalettes, colorBlindSafe, steps, type]);\n\n    const _updateConfig = useCallback(\n      ({key, value}) => {\n        setColorPaletteUI({colorRangeConfig: {[key]: value}});\n      },\n      [setColorPaletteUI]\n    );\n\n    const _onCustomPaletteCancel = useCallback(() => {\n      setColorPaletteUI({\n        showSketcher: false,\n        colorRangeConfig: {custom: false}\n      });\n    }, [setColorPaletteUI]);\n\n    const _onApply = useCallback(\n      e => onSelectColorRange(customPalette, e),\n      [customPalette, onSelectColorRange]\n    );\n\n    const _onSelectPalette = useCallback(\n      (colorPalette, e) => {\n        const newColorRange = updateColorRangeBySelectedPalette(selectedColorRange, colorPalette, {\n          steps,\n          reversed\n        });\n\n        onSelectColorRange(newColorRange, e);\n      },\n      [selectedColorRange, reversed, steps, onSelectColorRange]\n    );\n    return (\n      <StyledColorRangeSelector>\n        <StyledColorConfig>\n          {(colorRangeConfig.custom ? ['custom'] : Object.keys(colorRangeConfig)).map(key =>\n            CONFIG_SETTINGS[key] ? (\n              <PaletteConfig\n                key={key}\n                prop={key}\n                label={CONFIG_SETTINGS[key].label || key}\n                config={CONFIG_SETTINGS[key]}\n                value={colorRangeConfig[key]}\n                onChange={_updateConfig}\n                disabled={\n                  CONFIG_SETTINGS[key].disabled\n                    ? CONFIG_SETTINGS[key].disabled(selectedColorRange)\n                    : false\n                }\n                reason={CONFIG_SETTINGS[key].reason}\n              />\n            ) : null\n          )}\n        </StyledColorConfig>\n        {colorRangeConfig.custom ? (\n          <>\n            <StyledColorPalette>\n              <ColorPalettePanel colors={customPalette.colors} />\n            </StyledColorPalette>\n            <CustomPalette\n              customPalette={customPalette}\n              showSketcher={showSketcher}\n              onApply={_onApply}\n              setColorPaletteUI={setColorPaletteUI}\n              onCancel={_onCustomPaletteCancel}\n            />\n          </>\n        ) : (\n          <div className=\"color-palette__group\">\n            {filteredColorPalettes.map((colorPalette, i) => (\n              <ColorPaletteItem\n                key={`${colorPalette.name}-${i}`}\n                colorPalette={colorPalette}\n                selectedColorRange={selectedColorRange}\n                onSelect={_onSelectPalette}\n                reversed={reversed}\n                steps={steps}\n              />\n            ))}\n          </div>\n        )}\n      </StyledColorRangeSelector>\n    );\n  };\n\n  return ColorRangeSelector;\n}\n\nexport const PaletteConfig: React.FC<PaletteConfigProps> = ({\n  prop,\n  label,\n  value,\n  config: {type, options},\n  onChange,\n  disabled,\n  reason\n}) => {\n  const updateSelect = useCallback(val => onChange({key: prop, value: val}), [onChange, prop]);\n  const updateSwitch = useCallback(\n    () => onChange({key: prop, value: !value}),\n    [onChange, prop, value]\n  );\n\n  return (\n    <StyledPaletteConfig className=\"color-palette__config\" onClick={e => e.stopPropagation()}>\n      <div className=\"color-palette__config__label\">\n        <PanelLabel>\n          <FormattedMessage id={`color.${label}`} />\n        </PanelLabel>\n      </div>\n      <div\n        className=\"color-palette__config__select\"\n        data-tip\n        data-for={`color-range-config-${prop}`}\n      >\n        {type === 'select' && (\n          <ItemSelector\n            selectedItems={value}\n            options={options}\n            multiSelect={false}\n            searchable={false}\n            onChange={updateSelect}\n            disabled={disabled}\n            inputTheme=\"secondary\"\n            displayOption={displayOption}\n            getOptionValue={getOptionValue}\n          />\n        )}\n        {type === 'switch' && (\n          <Switch\n            checked={Boolean(value)}\n            id={`${label}-toggle`}\n            onChange={updateSwitch}\n            disabled={disabled}\n            secondary\n          />\n        )}\n        {disabled && reason ? (\n          <Tooltip id={`color-range-config-${prop}`} place=\"right\">\n            <div style={{maxWidth: '214px'}}>\n              <FormattedMessage id={reason} />\n            </div>\n          </Tooltip>\n        ) : null}\n      </div>\n    </StyledPaletteConfig>\n  );\n};\nconst StyledColorRange = styled.div.attrs({\n  className: 'color-palette-outer'\n})`\n  padding: 0 8px;\n  &:hover {\n    background-color: ${props => props.theme.panelBackgroundHover};\n    cursor: pointer;\n  }\n`;\n\ntype ColorPaletteItemProps = {\n  reversed?: boolean;\n  selected?: ColorRange;\n  colorPalette: ColorPalette;\n  selectedColorRange: ColorRange;\n  onSelect: (colorPalette: ColorPalette, e: MouseEvent) => void;\n  steps: number;\n};\n\nexport const ColorPaletteItem: React.FC<ColorPaletteItemProps> = ({\n  colorPalette,\n  steps,\n  selectedColorRange,\n  onSelect,\n  reversed\n}) => {\n  const colors = useMemo(() => colorPalette.colors(steps), [colorPalette, steps]);\n  const onClick = useCallback(e => onSelect(colorPalette, e), [colorPalette, onSelect]);\n  return (\n    <StyledColorRange onClick={onClick}>\n      <ColorPalettePanel\n        colors={colors}\n        isReversed={reversed}\n        isSelected={colorPalette.name === selectedColorRange.name}\n      />\n    </StyledColorRange>\n  );\n};\n\nexport default ColorRangeSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-scale-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, useState} from 'react';\nimport styled from 'styled-components';\n\nimport {ALL_FIELD_TYPES, SCALE_TYPES} from '@kepler.gl/constants';\nimport {AggregatedBin, Layer, VisualChannelDomain} from '@kepler.gl/layers';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {ColorRange, ColorUI, Field} from '@kepler.gl/types';\nimport {\n  getLayerColorScale,\n  getLegendOfScale,\n  initCustomPaletteByCustomScale,\n  histogramFromValues,\n  histogramFromOrdinal,\n  histogramFromThreshold,\n  getHistogramDomain,\n  hasColorMap\n} from '@kepler.gl/utils';\n\nimport ColorBreaksPanelFactory, {ColorBreaksPanelProps} from './color-breaks-panel';\nimport {SetColorUIFunc} from './custom-palette';\nimport DropdownSelect from '../../common/item-selector/dropdown-select';\nimport Accessor from '../../common/item-selector/accessor';\nimport DropdownList from '../../common/item-selector/dropdown-list';\nimport LazyTippy from '../../map/lazy-tippy';\nimport Typeahead from '../../common/item-selector/typeahead';\n\ntype TippyInstance = any; // 'tippy-js'\n\nconst HISTOGRAM_BINS = 30;\n\nexport type ScaleOption = {\n  label: string;\n  value: string;\n};\nexport type OnSelectFunc = (v: string, visConfg?: Record<string, any>) => void;\n\nexport type ContextProps = ColorBreaksPanelProps;\n\nexport type ColorScaleSelectorProps = {\n  layer: Layer;\n  field: Field;\n  dataset: KeplerTable;\n  scaleType: string;\n  domain: VisualChannelDomain;\n  range: ColorRange;\n  onSelect: OnSelectFunc;\n  setColorUI: SetColorUIFunc;\n  colorUIConfig: ColorUI;\n  options: ScaleOption[];\n  disabled?: boolean;\n  selectedItems: ScaleOption[];\n  multiSelect: boolean;\n  searchable: boolean;\n  displayOption: string;\n  getOptionValue: string;\n  aggregatedBins?: AggregatedBin[];\n  channelKey: string;\n};\n\nconst DropdownPropContext = React.createContext({});\nconst POPPER_OPTIONS = {\n  modifiers: [\n    // zero offsets since they are already added in VerticalToolbar\n    {name: 'offset', options: {offset: [0, 0]}}\n  ]\n};\n\nconst DropdownBottom = styled.div<{light?: boolean}>`\n  border-top: 1px solid\n    ${props =>\n      props.light ? props.theme.dropdownListBorderTopLT : props.theme.dropdownListBorderTop};\n`;\n\nconst StyledScaleSelectDropdown = styled.div`\n  box-shadow: ${props => props.theme.dropdownListShadow};\n  .list-selector {\n    box-shadow: none;\n    border-top: 0;\n  }\n  .list__item {\n    padding: 4px 9px;\n  }\n`;\nconst DropdownWrapper = styled.div`\n  border: 0;\n  width: 100%;\n  left: 0;\n  z-index: ${props => props.theme.dropdownWrapperZ};\n  position: absolute;\n  margin-top: ${props => props.theme.dropdownWapperMargin}px;\n`;\n\nconst StyledColorScaleSelector = styled.div`\n  position: relative;\n  .typeahead {\n    // adds padding to bottom of dropdown\n    margin-bottom: 40px;\n  }\n  [data-tippy-root] {\n    width: 100%;\n  }\n`;\n\nfunction hideTippy(tippyInstance) {\n  if (tippyInstance) {\n    tippyInstance.hide();\n  }\n}\nColorScaleSelectorFactory.deps = [ColorBreaksPanelFactory];\n\nfunction ColorScaleSelectorFactory(\n  ColorBreaksPanel: ReturnType<typeof ColorBreaksPanelFactory>\n): React.FC<ColorScaleSelectorProps> {\n  const ColorScaleSelectDropdown = props => (\n    <StyledScaleSelectDropdown>\n      <DropdownList {...props} />\n      <DropdownPropContext.Consumer>\n        {(context: any) => (\n          <DropdownBottom>\n            <ColorBreaksPanel {...context} />\n          </DropdownBottom>\n        )}\n      </DropdownPropContext.Consumer>\n    </StyledScaleSelectDropdown>\n  );\n\n  const ColorScaleSelector: React.FC<ColorScaleSelectorProps> = ({\n    layer,\n    field,\n    dataset,\n    onSelect,\n    scaleType,\n    domain,\n    aggregatedBins,\n    range,\n    setColorUI,\n    colorUIConfig,\n    channelKey,\n    ...dropdownSelectProps\n  }) => {\n    const displayOption = Accessor.generateOptionToStringFor(dropdownSelectProps.displayOption);\n    const getOptionValue = useMemo(\n      () => Accessor.generateOptionToStringFor(dropdownSelectProps.getOptionValue),\n      [dropdownSelectProps.getOptionValue]\n    );\n    const [tippyInstance, setTippyInstance] = useState<TippyInstance>();\n    const isEditingColorBreaks = colorUIConfig?.colorRangeConfig?.customBreaks;\n\n    // Stores the previous selection for live preview: when choosing Custom/Custom Ordinal, we apply a temporary palette.\n    // Cancel restores {scale, range} from this ref; Confirm keeps the change and clears the ref.\n    // If the user switches between different custom scale types (e.g., from \"Custom\" to \"Custom Ordinal\") or is already in a custom scale state,\n    // this ref is updated to always store the most recent non-custom selection. Only the latest non-custom selection is restorable on cancel.\n    const prevSelectionRef = React.useRef<{scale: string; range: ColorRange} | null>(null);\n\n    // when custom color scale - but Confirm is not clicked yet\n    const pendingOption = useMemo(\n      () =>\n        isEditingColorBreaks\n          ? (dropdownSelectProps.options || []).find(\n              o => getOptionValue(o) === colorUIConfig?.customPalette?.type\n            ) || null\n          : null,\n      [\n        isEditingColorBreaks,\n        dropdownSelectProps.options,\n        getOptionValue,\n        colorUIConfig?.customPalette?.type\n      ]\n    );\n    const colorScale = useMemo(\n      () =>\n        getLayerColorScale({\n          range,\n          domain,\n          scaleType,\n          layer\n        }),\n      [range, domain, scaleType, layer]\n    );\n\n    const colorBreaks = useMemo(() => {\n      return colorScale\n        ? getLegendOfScale({\n            scale: colorScale.byZoom && domain ? colorScale(domain?.length - 1) : colorScale,\n            scaleType,\n            fieldType: field?.type ?? ALL_FIELD_TYPES.real\n          })\n        : null;\n    }, [colorScale, scaleType, field?.type, domain]);\n\n    const columnStats = field?.filterProps?.columnStats;\n\n    const fieldValueAccessor = useMemo(() => {\n      return field\n        ? idx => dataset.getValue(field.name, idx)\n        : idx => dataset.dataContainer.rowAsArray(idx);\n    }, [dataset, field]);\n\n    const ordinalDomain = useMemo(() => {\n      return layer.config[layer.visualChannels[channelKey].domain] || [];\n    }, [channelKey, layer.config, layer.visualChannels]);\n\n    // aggregatedBins should be the raw data\n    const allBins = useMemo(() => {\n      if (field?.type === ALL_FIELD_TYPES.string) {\n        // Use ordinal bins for string columns, as d3 could potentially generate invalid numeric bins, and crash\n        return histogramFromOrdinal(ordinalDomain, dataset.allIndexes, fieldValueAccessor);\n      }\n\n      if (aggregatedBins) {\n        return histogramFromValues(\n          Object.values(aggregatedBins).map(bin => bin.i),\n          HISTOGRAM_BINS,\n          idx => aggregatedBins[idx].value\n        );\n      }\n      return columnStats?.bins\n        ? columnStats?.bins\n        : histogramFromValues(dataset.allIndexes, HISTOGRAM_BINS, fieldValueAccessor);\n    }, [aggregatedBins, columnStats, dataset, fieldValueAccessor, field?.type, ordinalDomain]);\n\n    const histogramDomain = useMemo(() => {\n      return getHistogramDomain({aggregatedBins, columnStats, dataset, fieldValueAccessor});\n    }, [dataset, fieldValueAccessor, aggregatedBins, columnStats]);\n\n    const isFiltered = aggregatedBins\n      ? false\n      : dataset.filteredIndexForDomain.length !== dataset.allIndexes.length;\n\n    // get filteredBins (not apply to aggregate layer)\n    const filteredBins = useMemo(() => {\n      if (!isFiltered) {\n        return allBins;\n      }\n      if (field?.type === ALL_FIELD_TYPES.string) {\n        return histogramFromOrdinal(\n          ordinalDomain as any,\n          dataset.filteredIndexForDomain,\n          fieldValueAccessor\n        );\n      }\n      // numeric thresholds\n      const filterEmptyBins = false;\n      const thresholds = allBins.map(b => b.x0);\n      return histogramFromThreshold(\n        thresholds,\n        dataset.filteredIndexForDomain,\n        fieldValueAccessor,\n        filterEmptyBins\n      );\n    }, [dataset, fieldValueAccessor, allBins, isFiltered, field?.type, ordinalDomain]);\n\n    const onSelectScale = useCallback(\n      val => {\n        // highlight selected option\n        if (!val) return;\n\n        const selectedScale = getOptionValue(val);\n        if (selectedScale === SCALE_TYPES.custom || selectedScale === SCALE_TYPES.customOrdinal) {\n          const customPalette = initCustomPaletteByCustomScale({\n            scale: selectedScale,\n            field,\n            range,\n            colorBreaks,\n            ...(selectedScale === SCALE_TYPES.customOrdinal ? {ordinalDomain} : {})\n          });\n          setColorUI({\n            showColorChart: true,\n            colorRangeConfig: {\n              customBreaks: true\n            },\n            customPalette\n          });\n          // store previous selection for cancel, then preview custom on the map\n          if (!prevSelectionRef.current) {\n            prevSelectionRef.current = {scale: scaleType, range};\n          }\n          onSelect(selectedScale, customPalette);\n        } else if (hasColorMap(range)) {\n          // not custom\n          // remove colorMap\n          // eslint-disable-next-line no-unused-vars\n          const {colorMap: _, ...newRange} = range;\n          // reset colorUI before changing the scale\n          setColorUI({\n            showColorChart: false,\n            colorRangeConfig: {\n              customBreaks: false\n            }\n          });\n          onSelect(selectedScale, newRange);\n        } else {\n          // reset colorUI before changing the scale\n          setColorUI({\n            showColorChart: false,\n            colorRangeConfig: {\n              customBreaks: false\n            }\n          });\n          onSelect(selectedScale);\n        }\n      },\n      [field, setColorUI, onSelect, range, getOptionValue, colorBreaks, ordinalDomain, scaleType]\n    );\n\n    const onApply = useCallback(() => {\n      // change scale type only if confirmed\n      const nextScaleType = colorUIConfig?.customPalette?.type || scaleType;\n      onSelect(nextScaleType, colorUIConfig.customPalette);\n      hideTippy(tippyInstance);\n      prevSelectionRef.current = null;\n    }, [onSelect, colorUIConfig.customPalette, tippyInstance, scaleType]);\n\n    const onCancel = useCallback(() => {\n      // restore previous selection if any\n      if (prevSelectionRef.current) {\n        const {scale: prevScale, range: prevRange} = prevSelectionRef.current;\n        onSelect(prevScale, prevRange);\n      }\n      hideTippy(tippyInstance);\n      prevSelectionRef.current = null;\n    }, [tippyInstance, onSelect]);\n\n    const isCustomBreaks =\n      scaleType === SCALE_TYPES.custom || scaleType === SCALE_TYPES.customOrdinal;\n\n    return (\n      <DropdownPropContext.Provider\n        value={{\n          setColorUI,\n          colorField: field,\n          dataset,\n          colorUIConfig,\n          colorBreaks,\n          isCustomBreaks,\n          allBins,\n          filteredBins,\n          isFiltered,\n          histogramDomain,\n          ordinalDomain,\n          onScaleChange: onSelect,\n          onApply,\n          onCancel\n        }}\n      >\n        <StyledColorScaleSelector>\n          <LazyTippy\n            trigger=\"click\"\n            placement=\"bottom-start\"\n            appendTo=\"parent\"\n            interactive={true}\n            hideOnClick={!isEditingColorBreaks}\n            onCreate={setTippyInstance}\n            popperOptions={POPPER_OPTIONS}\n            render={attrs => (\n              <DropdownWrapper {...attrs}>\n                {/* @ts-ignore*/}\n                {!dropdownSelectProps.disabled && (\n                  <Typeahead\n                    {...dropdownSelectProps}\n                    displayOption={displayOption}\n                    // @ts-ignore\n                    getOptionValue={getOptionValue}\n                    onOptionSelected={onSelectScale}\n                    customListComponent={ColorScaleSelectDropdown}\n                    searchable={false}\n                    showOptionsWhenEmpty\n                    selectedItems={\n                      pendingOption ? [pendingOption] : dropdownSelectProps.selectedItems\n                    }\n                  />\n                )}\n              </DropdownWrapper>\n            )}\n          >\n            <div className=\"dropdown-select\">\n              {/* @ts-ignore*/}\n              <DropdownSelect\n                {...dropdownSelectProps}\n                displayOption={displayOption}\n                value={pendingOption || dropdownSelectProps.selectedItems[0]}\n              />\n            </div>\n          </LazyTippy>\n        </StyledColorScaleSelector>\n      </DropdownPropContext.Provider>\n    );\n  };\n  return ColorScaleSelector;\n}\nexport default ColorScaleSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/color-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState, MouseEvent} from 'react';\nimport {FormattedMessage} from 'react-intl';\nimport styled from 'styled-components';\nimport {useDismiss, useFloating, useInteractions} from '@floating-ui/react';\n\nimport {ColorRange} from '@kepler.gl/types';\nimport {ColorUI, NestedPartial, RGBAColor, RGBColor} from '@kepler.gl/types';\nimport {rgbToHex} from '@kepler.gl/utils';\n\nimport RangeSliderFactory from '../../common/range-slider';\nimport {PanelLabel, shouldForwardProp, StyledPanelDropdown} from '../../common/styled-components';\nimport ColorPalette from './color-palette';\nimport ColorRangeSelectorFactory from './color-range-selector';\nimport SingleColorPalette from './single-color-palette';\n\ntype ColorSelectorInputProps = {\n  active: boolean;\n  disabled?: boolean;\n  inputTheme?: string;\n};\n\nexport type ColorSet = {\n  selectedColor: RGBColor | RGBAColor | ColorRange;\n  setColor: (v: RGBColor | RGBAColor | ColorRange) => void;\n  isRange?: boolean;\n  label?: string;\n};\n\ntype ColorSelectorProps = {\n  colorSets: ColorSet[];\n  colorUI?: ColorUI;\n  inputTheme?: string;\n  disabled?: boolean;\n  useOpacity?: boolean;\n  setColorUI?: (newConfig: NestedPartial<ColorUI>) => void;\n};\n\nconst OpacitySliderWrapper = styled.div`\n  padding: 0px 12px 12px 12px;\n`;\n\nconst OPACITY_SLIDER_PROPS = {\n  type: 'number',\n  range: [0, 1],\n  value0: 0,\n  step: 0.01,\n  isRanged: false,\n  label: 'Opacity',\n  showInput: true\n};\n\nexport const ColorBlock = styled.div.withConfig({\n  shouldForwardProp\n})<{backgroundcolor: RGBColor}>`\n  width: 32px;\n  height: 18px;\n  border-radius: 1px;\n  background-color: ${props =>\n    Array.isArray(props.backgroundcolor)\n      ? `rgb(${props.backgroundcolor.slice(0, 3).join(',')})`\n      : 'transparent'};\n`;\n\nconst StyledColorSelectorWrapper = styled.div`\n  .selector__dropdown {\n    max-height: 600px; /* increase from the default 500px defined by StyledPanelDropdown */\n  }\n`;\n\nexport const ColorSelectorInput = styled.div.withConfig({\n  shouldForwardProp\n})<ColorSelectorInputProps>`\n  ${props => (props.inputTheme === 'secondary' ? props.theme.secondaryInput : props.theme.input)};\n  height: ${props => props.theme.inputBoxHeight};\n\n  .color-selector__selector__label {\n    text-transform: capitalize;\n    font-size: 12px;\n    text-align: center;\n    color: ${props => props.theme.inputPlaceholderColor};\n  }\n`;\n\nexport const InputBoxContainer = styled.div`\n  display: flex;\n  justify-content: space-between;\n\n  .color-select__input-group {\n    flex-grow: 1;\n  }\n  .color-select__input-group:nth-child(2) {\n    margin-left: 12px;\n  }\n`;\n\nColorSelectorFactory.deps = [ColorRangeSelectorFactory, RangeSliderFactory];\n\nfunction ColorSelectorFactory(\n  ColorRangeSelector: ReturnType<typeof ColorRangeSelectorFactory>,\n  RangeSlider: ReturnType<typeof RangeSliderFactory>\n): React.FC<ColorSelectorProps> {\n  const ColorSelector: React.FC<ColorSelectorProps> = ({\n    colorSets = [],\n    colorUI,\n    inputTheme,\n    disabled,\n    useOpacity,\n    setColorUI\n  }: ColorSelectorProps) => {\n    const [showDropdown, setShowDropdown] = useState(colorUI ? colorUI.showDropdown : false);\n    const showSketcher = colorUI ? colorUI.showSketcher : false;\n    const editingLookup = colorUI ? colorUI.showDropdown : showDropdown;\n    const editingColorSet: ColorSet | false =\n      typeof editingLookup === 'number' && colorSets[editingLookup]\n        ? colorSets[editingLookup]\n        : false;\n\n    const closePanelDropdown = useCallback(() => {\n      if (editingLookup === false) {\n        return;\n      }\n      if (setColorUI) {\n        setColorUI({showDropdown: false, showSketcher: false});\n      } else {\n        setShowDropdown(false);\n      }\n    }, [editingLookup, setColorUI, setShowDropdown]);\n\n    const handleClickOutside = useCallback(() => {\n      if (Number.isInteger(showSketcher)) {\n        // if sketcher is open, let sketch to close itself first\n        return;\n      }\n      closePanelDropdown();\n    }, [showSketcher, closePanelDropdown]);\n\n    // floating-ui boilerplate to establish close on outside click\n    const {refs, context} = useFloating({\n      open: true,\n      onOpenChange: v => {\n        if (!v) {\n          handleClickOutside();\n        }\n      }\n    });\n    const dismiss = useDismiss(context);\n    const {getFloatingProps} = useInteractions([dismiss]);\n\n    const setColor = useCallback(\n      (colorSet: ColorSet, color: RGBColor | RGBAColor | ColorRange, opacity: number) => {\n        const {setColor} = colorSet || {};\n        if (!setColor) {\n          return;\n        }\n        if (useOpacity && Array.isArray(color)) {\n          setColor([...color.slice(0, 3), opacity] as RGBAColor);\n        } else {\n          setColor(color);\n        }\n      },\n      [useOpacity]\n    );\n\n    const onSelectColor = useCallback(\n      (color: RGBColor | ColorRange, e: MouseEvent) => {\n        if (e) e.stopPropagation();\n        const colorSet = editingColorSet;\n        if (colorSet) {\n          setColor(colorSet, color, colorSet.selectedColor[3]);\n        }\n      },\n      [editingColorSet, setColor]\n    );\n\n    const onSelectOpacity = useCallback(\n      (opacity: number[], e: Event | null | undefined) => {\n        if (e) e.stopPropagation();\n        const colorSet = editingColorSet;\n        if (colorSet) {\n          setColor(colorSet, colorSet.selectedColor, Math.round(opacity[1] * 255));\n        }\n      },\n      [editingColorSet, setColor]\n    );\n\n    const onToggleDropdown = useCallback(\n      (e, i) => {\n        e.stopPropagation();\n        e.preventDefault();\n        const showDropdownValue =\n          editingLookup === false\n            ? i // open it for the specific color set index\n            : false; // close it\n        if (setColorUI) {\n          setColorUI({showDropdown: showDropdownValue});\n        } else {\n          setShowDropdown(showDropdownValue);\n        }\n      },\n      [editingLookup, setColorUI, setShowDropdown]\n    );\n\n    return (\n      <StyledColorSelectorWrapper\n        className=\"color-selector\"\n        ref={refs.setFloating}\n        {...getFloatingProps()}\n      >\n        <InputBoxContainer>\n          {colorSets.map((cSet, i) => (\n            <div className=\"color-select__input-group\" key={i}>\n              <ColorSelectorInput\n                className=\"color-selector__selector\"\n                active={editingLookup === i}\n                disabled={disabled}\n                inputTheme={inputTheme}\n                onClick={e => onToggleDropdown(e, i)}\n              >\n                {cSet.isRange ? (\n                  <ColorPalette colors={(cSet.selectedColor as ColorRange).colors} />\n                ) : (\n                  <ColorBlock\n                    className=\"color-selector__selector__block\"\n                    backgroundcolor={cSet.selectedColor as RGBColor}\n                  />\n                )}\n                {cSet.label ? (\n                  <div className=\"color-selector__selector__label\">{cSet.label}</div>\n                ) : null}\n              </ColorSelectorInput>\n            </div>\n          ))}\n        </InputBoxContainer>\n        {editingColorSet ? (\n          <StyledPanelDropdown className=\"color-selector__dropdown\">\n            {editingColorSet.isRange && colorUI && setColorUI ? (\n              <ColorRangeSelector\n                selectedColorRange={editingColorSet.selectedColor as ColorRange}\n                onSelectColorRange={onSelectColor}\n                setColorPaletteUI={setColorUI}\n                colorPaletteUI={colorUI as ColorUI}\n              />\n            ) : (\n              <SingleColorPalette\n                selectedColor={rgbToHex(editingColorSet.selectedColor as RGBColor)}\n                onSelectColor={onSelectColor}\n              />\n            )}\n            {useOpacity ? (\n              <OpacitySliderWrapper>\n                <PanelLabel>\n                  <FormattedMessage id=\"color.opacity\" />\n                </PanelLabel>\n                <RangeSlider\n                  {...OPACITY_SLIDER_PROPS}\n                  value1={editingColorSet.selectedColor[3] / 255}\n                  onChange={onSelectOpacity}\n                />\n              </OpacitySliderWrapper>\n            ) : null}\n          </StyledPanelDropdown>\n        ) : null}\n      </StyledColorSelectorWrapper>\n    );\n  };\n\n  return ColorSelector;\n}\n\nexport default ColorSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/column-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo, ReactNode} from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {PanelLabel} from '../../common/styled-components';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport {validateColumn} from '@kepler.gl/reducers';\nimport {LayerColumn, LayerColumns, EnhancedFieldPair} from '@kepler.gl/types';\nimport {MinimalField} from '../../common/field-selector';\n\nexport type ColumnSelectorProps<FieldOption extends MinimalField> = {\n  column: LayerColumn;\n  columns: LayerColumns;\n  label: string;\n  allFields: FieldOption[];\n  onSelect: (\n    items:\n      | ReadonlyArray<string | number | boolean | object>\n      | string\n      | number\n      | boolean\n      | object\n      | null\n  ) => void;\n  fieldPairs: EnhancedFieldPair[] | null;\n  isActive?: boolean;\n};\n\nconst ColumnRow = styled.div`\n  display: flex;\n  margin-bottom: 8px;\n  align-items: center;\n`;\n\nconst ColumnName = styled.div`\n  width: 32%;\n  line-height: 1.2;\n  padding-right: 6px;\n`;\n\nconst ColumnSelect = styled.div`\n  width: 68%;\n`;\n\nColumnSelectorFactory.deps = [FieldSelectorFactory];\n\nconst ColumnPanelLabel = styled(PanelLabel).attrs<{children: ReactNode}>({\n  className: 'side-panel-subpanel__label'\n})`\n  font-size: 10px;\n`;\n\nfunction ColumnSelectorFactory(FieldSelector: ReturnType<typeof FieldSelectorFactory>) {\n  const ColumnSelector: React.FC<ColumnSelectorProps<any>> = ({\n    column,\n    columns,\n    label,\n    allFields,\n    onSelect,\n    fieldPairs,\n    isActive = true\n  }) => {\n    const isError = useMemo(\n      () => isActive && !validateColumn(column, columns, allFields),\n      [isActive, column, columns, allFields]\n    );\n    return (\n      <ColumnRow className=\"layer-config__column__selector\">\n        <ColumnName className=\"layer-config__column__name\">\n          <ColumnPanelLabel>\n            <FormattedMessage id={`columns.${label}`} />\n            {!column.optional ? `  *` : null}\n          </ColumnPanelLabel>\n        </ColumnName>\n        <ColumnSelect className=\"layer-config__column__select\">\n          <FieldSelector\n            suggested={fieldPairs as any}\n            error={isError}\n            fields={allFields}\n            value={column.value}\n            erasable\n            onSelect={onSelect}\n          />\n        </ColumnSelect>\n      </ColumnRow>\n    );\n  };\n  return ColumnSelector;\n}\n\nexport default ColumnSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/custom-palette.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport React, {\n  ElementType,\n  PropsWithChildren,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState\n} from 'react';\nimport uniq from 'lodash/uniq';\nimport {\n  DndContext,\n  closestCenter,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n  DragEndEvent\n} from '@dnd-kit/core';\nimport {SortableContext, useSortable, verticalListSortingStrategy} from '@dnd-kit/sortable';\nimport {CSS} from '@dnd-kit/utilities';\nimport styled, {css} from 'styled-components';\nimport Portaled from '../../common/portaled';\nimport {Tooltip} from '../../common/styled-components';\nimport Typeahead from '../../common/item-selector/typeahead';\nimport ChickletedInput from '../../common/item-selector/chickleted-input';\nimport DropdownList, {ListItem} from '../../common/item-selector/dropdown-list';\nimport {shouldForwardProp} from '../../common/styled-components';\nimport {toArray} from '@kepler.gl/common-utils';\nimport {KeyEvent} from '@kepler.gl/constants';\nimport {ColorMap, ColorUI, HexColor, NestedPartial} from '@kepler.gl/types';\nimport {\n  addCategoricalValuesToColorMap,\n  addCustomPaletteColor,\n  colorMapToCategoricalColorBreaks,\n  colorMapToColorBreaks,\n  isNumericColorBreaks,\n  resetCategoricalColorMapByIndex,\n  removeCategoricalValueFromColorMap,\n  removeCustomPaletteColor,\n  selectRestCategoricalColorMapByIndex,\n  sortCustomPaletteColor,\n  updateCustomPaletteColor\n} from '@kepler.gl/utils';\nimport {ColorBreak, ColorBreakOrdinal} from '@kepler.gl/utils';\nimport {Add, Trash, VertDots} from '../../common/icons';\nimport {Button, Input} from '../../common/styled-components';\nimport CustomPicker from './custom-picker';\n\nexport type ActionIcons = {\n  delete: ElementType;\n  sort: ElementType;\n  add: ElementType;\n};\n\nexport type EditColorMapFunc = (v: number, i: number) => void;\nexport type SetColorUIFunc = (newConfig: NestedPartial<ColorUI>) => void;\n\n/**\n * EditableColorRange\n */\nexport type EditableColorRangeProps = {\n  item: ColorBreak;\n  isLast: boolean;\n  index: number;\n  editColorMap?: EditColorMapFunc;\n  editable: boolean;\n};\n\nexport type CustomPaletteProps = {\n  customPalette: ColorUI['customPalette'];\n  setColorPaletteUI: SetColorUIFunc;\n  showSketcher: number | boolean;\n  ordinalDomain?: string[] | number[];\n  actionIcons?: ActionIcons;\n  onApply: (e: React.MouseEvent) => void;\n  onCancel: () => void;\n};\n\nexport type CustomPaletteInputProps = {\n  index: number;\n  isSorting: boolean;\n  color: HexColor;\n  colorBreaks: ColorBreakOrdinal[] | ColorBreak[] | null;\n  inputColorHex: (index: number, v: HexColor) => void;\n  editColorMapValue: EditColorMapFunc;\n  actionIcons?: ActionIcons;\n  disableAppend?: boolean;\n  disableDelete?: boolean;\n  onDelete: (index: number) => void;\n  onAdd: (index: number) => void;\n  onToggleSketcher: (index: number) => void;\n};\n\nconst defaultActionIcons = {\n  delete: Trash,\n  sort: VertDots,\n  add: Add\n};\n\nconst dragHandleActive = css`\n  .layer__drag-handle {\n    color: ${props => props.theme.textColorHl};\n    opacity: 1;\n    cursor: move;\n  }\n`;\n\nexport const ColorPaletteItem = styled.div`\n  display: flex;\n  align-items: center;\n  padding-top: 2px;\n  padding-bottom: 2px;\n  z-index: ${props => props.theme.dropdownWrapperZ + 1};\n  justify-content: space-between;\n\n  .custom-palette-input__left {\n    display: flex;\n    align-items: center;\n  }\n\n  .custom-palette-input__right {\n    display: flex;\n    align-items: center;\n    padding-right: 6px;\n  }\n\n  &:not(.sorting):not(.disabled) {\n    &:hover {\n      background-color: ${props => props.theme.panelBackgroundHover};\n      ${dragHandleActive};\n    }\n  }\n\n  &.sorting-colors {\n    background-color: ${props => props.theme.panelBackgroundHover};\n    ${dragHandleActive};\n  }\n`;\n\nconst StyledDragHandle = styled.div`\n  display: flex;\n  align-items: center;\n  opacity: 0;\n`;\n\nconst StyledAction = styled.div`\n  color: ${props => props.theme.subtextColor};\n  svg {\n    &:hover {\n      color: ${props => props.theme.subtextColorActive};\n    }\n  }\n\n  margin-left: 4px;\n  &:hover {\n    cursor: pointer;\n  }\n`;\n\nexport const DividerLine = styled.div`\n  height: 1px;\n  background-color: ${props => props.theme.dropdownListBorderTop};\n  margin-top: 8px;\n`;\n\nexport const ColorSwatch = styled.div.attrs({\n  className: 'custom-palette__swatch'\n})`\n  background-color: ${props => props.color};\n  width: 32px;\n  height: 18px;\n  display: inline-block;\n  &:hover {\n    box-shadow: ${props => props.theme.boxShadow};\n    cursor: pointer;\n  }\n`;\n\nconst StyledButtonContainer = styled.div`\n  margin-top: 11px;\n  display: flex;\n  direction: rtl;\n  padding: 0 12px;\n`;\n\nconst StyledAddStepContainer = styled.div`\n  margin-top: 11px;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  align-items: center;\n  padding: 0 12px;\n  color: ${props => props.theme.inputColor};\n  .addcolor {\n    margin-top: 4px;\n  }\n`;\n\nconst StyledInput = styled(Input).withConfig({shouldForwardProp})<{\n  width: string;\n  textAlign: string;\n}>`\n  width: ${props => props.width ?? '100%'};\n  text-align: ${props => props.textAlign ?? 'end'};\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n`;\n\nconst InputText = styled.div.withConfig({shouldForwardProp})<{width: string; textAlign: string}>`\n  ${props => props.theme.input};\n  background-color: transparent;\n  border-color: transparent;\n  width: ${props => props.width ?? '100%'};\n  text-align: ${props => props.textAlign ?? 'end'};\n\n  &:hover {\n    cursor: auto;\n    background-color: transparent;\n    border-color: transparent;\n  }\n`;\n\ntype SortableItemProps = {\n  id: string;\n  children: (listeners: any) => React.ReactNode;\n  className?: string;\n  isSorting: boolean;\n};\n\nconst SortableItem = ({id, children, isSorting}: SortableItemProps) => {\n  const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({id});\n  const style = {\n    transform: CSS.Transform.toString(transform),\n    transition,\n    zIndex: isDragging ? 1 : 0\n  };\n  return (\n    <ColorPaletteItem\n      ref={setNodeRef}\n      style={style}\n      className={classnames('custom-palette__sortable-items', {sorting: isSorting || isDragging})}\n      {...attributes}\n    >\n      {children(listeners)}\n    </ColorPaletteItem>\n  );\n};\n\ntype WrappedSortableContainerProps = {\n  children?: React.ReactNode;\n  className?: string;\n  onSortEnd: (event: DragEndEvent) => void;\n  onSortStart: () => void;\n};\n\nconst WrappedSortableContainer = ({\n  children,\n  className,\n  onSortEnd,\n  onSortStart\n}: WrappedSortableContainerProps) => {\n  const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor));\n  return (\n    <DndContext\n      sensors={sensors}\n      collisionDetection={closestCenter}\n      onDragEnd={onSortEnd}\n      onDragStart={onSortStart}\n    >\n      <SortableContext\n        items={React.Children.map(children, (_, index) => `${index}`) || []}\n        strategy={verticalListSortingStrategy}\n      >\n        <div className={className}>{children}</div>\n      </SortableContext>\n    </DndContext>\n  );\n};\n\ntype DragHandleProps = PropsWithChildren<{className?: string}>;\nconst DragHandle = ({className, children, ...listeners}: DragHandleProps) => (\n  <StyledDragHandle className={className} {...listeners}>\n    {children}\n  </StyledDragHandle>\n);\n\nexport type ColorPaletteInputProps = {\n  value: string | number;\n  onChange: (val: unknown) => void;\n  id: string;\n  width: string;\n  textAlign: string;\n  editable: boolean;\n};\n\nexport const ColorPaletteInput = ({\n  value,\n  onChange,\n  id,\n  width,\n  textAlign,\n  editable\n}: ColorPaletteInputProps) => {\n  const [stateValue, setValue] = useState(value);\n  const inputRef = useRef(null);\n  useEffect(() => {\n    setValue(value);\n  }, [value]);\n\n  const onKeyDown = useCallback(\n    e => {\n      switch (e.keyCode) {\n        case KeyEvent.DOM_VK_ENTER:\n        case KeyEvent.DOM_VK_RETURN:\n          onChange(stateValue);\n          if (inputRef !== null) {\n            // @ts-ignore\n            inputRef?.current.blur();\n          }\n          break;\n        default:\n          break;\n      }\n    },\n    [onChange, stateValue]\n  );\n\n  const _onChange = useCallback(e => setValue(e.target.value), [setValue]);\n  const onBlur = useCallback(() => onChange(stateValue), [onChange, stateValue]);\n\n  return editable ? (\n    <StyledInput\n      ref={inputRef}\n      className=\"custom-palette-hex__input\"\n      value={stateValue}\n      onChange={_onChange}\n      onBlur={onBlur}\n      onKeyDown={onKeyDown}\n      id={id}\n      width={width}\n      textAlign={textAlign}\n      secondary\n    />\n  ) : (\n    <InputText className=\"custom-palette-hex__input__text\" width={width} textAlign={textAlign}>\n      {value}\n    </InputText>\n  );\n};\n\nconst Dash = styled.div`\n  width: 6px;\n  border-top: 1px solid ${props => props.theme.subtextColor};\n  margin-left: 4px;\n  margin-right: 4px;\n`;\n\nconst StyledRangeInput = styled.div`\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  margin-left: 12px;\n`;\n\nconst StyledColorHexInput = styled.div`\n  margin-left: 12px;\n`;\n\nexport const EditableColorRange: React.FC<EditableColorRangeProps> = ({\n  item,\n  isLast,\n  index,\n  editColorMap,\n  editable\n}) => {\n  const hasInputs = Array.isArray(item?.inputs);\n  const leftInput = hasInputs ? item.inputs[0] : undefined;\n  const rightInput = hasInputs ? item.inputs[1] : undefined;\n  const noMinBound = !Number.isFinite(leftInput) && index === 0;\n  const noMaxBound = !Number.isFinite(rightInput) && isLast;\n  const onChangeLeft = useCallback(\n    val => {\n      if (editable && editColorMap) editColorMap(parseFloat(val), index - 1);\n    },\n    [editColorMap, index, editable]\n  );\n  const onChangeRight = useCallback(\n    val => {\n      if (editable && editColorMap) editColorMap(parseFloat(val), index);\n    },\n    [editColorMap, index, editable]\n  );\n\n  return (\n    <StyledRangeInput>\n      <ColorPaletteInput\n        value={noMinBound ? 'Less' : String(leftInput ?? '')}\n        id={`color-palette-input-${index}-left`}\n        width=\"50px\"\n        textAlign=\"end\"\n        editable={noMinBound ? false : editable}\n        onChange={onChangeLeft}\n      />\n      <Dash />\n      <ColorPaletteInput\n        value={noMaxBound ? 'More' : String(rightInput ?? '')}\n        id={`color-palette-input-${index}-right`}\n        width=\"50px\"\n        textAlign=\"end\"\n        onChange={onChangeRight}\n        editable={noMaxBound ? false : editable}\n      />\n    </StyledRangeInput>\n  );\n};\n\nexport const AddColorStop = ({onColorAdd, IconComponent}) => (\n  <StyledAction onClick={onColorAdd} className=\"addcolor\">\n    <IconComponent height=\"14px\" />\n  </StyledAction>\n);\n\nexport const DeleteColorStop = ({onColorDelete, IconComponent}) => (\n  <StyledAction onClick={onColorDelete} className=\"trashbin\">\n    <IconComponent height=\"14px\" />\n  </StyledAction>\n);\n\nexport const CustomPaletteInput: React.FC<CustomPaletteInputProps> = ({\n  index,\n  isSorting,\n  color,\n  colorBreaks,\n  inputColorHex,\n  editColorMapValue,\n  actionIcons = defaultActionIcons,\n  disableAppend,\n  disableDelete,\n  onDelete,\n  onAdd,\n  onToggleSketcher\n}) => {\n  const onClickSwtach = useCallback(() => onToggleSketcher(index), [onToggleSketcher, index]);\n  const onColorInput = useCallback(v => inputColorHex(index, v), [inputColorHex, index]);\n  const onColorDelete = useCallback(() => onDelete(index), [onDelete, index]);\n  const onColorAdd = useCallback(() => onAdd(index), [onAdd, index]);\n  const showHexInput = !colorBreaks;\n\n  return (\n    <SortableItem id={`${index}`} isSorting={isSorting}>\n      {listeners => (\n        <>\n          <div className=\"custom-palette-input__left\">\n            <DragHandle className=\"layer__drag-handle\" {...listeners}>\n              <actionIcons.sort height=\"20px\" />\n            </DragHandle>\n            <ColorSwatch color={color} onClick={onClickSwtach} />\n            {showHexInput ? (\n              <StyledColorHexInput>\n                <ColorPaletteInput\n                  value={color.toUpperCase()}\n                  onChange={onColorInput}\n                  id={`input-layer-label-${index}`}\n                  editable\n                  textAlign=\"left\"\n                  width=\"70px\"\n                />\n              </StyledColorHexInput>\n            ) : null}\n            {colorBreaks && index < colorBreaks.length && isNumericColorBreaks(colorBreaks) ? (\n              <EditableColorRange\n                item={colorBreaks[index]}\n                isLast={index === colorBreaks.length - 1}\n                index={index}\n                editColorMap={editColorMapValue}\n                editable\n              />\n            ) : null}\n          </div>\n          <div className=\"custom-palette-input__right\">\n            {!disableAppend ? (\n              <AddColorStop onColorAdd={onColorAdd} IconComponent={actionIcons.add} />\n            ) : null}\n            {!disableDelete ? (\n              <DeleteColorStop onColorDelete={onColorDelete} IconComponent={actionIcons.delete} />\n            ) : null}\n          </div>\n        </>\n      )}\n    </SortableItem>\n  );\n};\n\nconst StyledCategoricalValuePickerWrapper = styled.div.attrs({\n  className: 'categorical-value-picker'\n})`\n  width: 150px;\n  color: ${props => props.theme.inputColor};\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n  column-gap: 8px;\n  align-items: center;\n  cursor: pointer;\n`;\n\ntype StyledCategoricalValuePickerProps = {noBorder: boolean};\nconst StyledCategoricalValuePicker = styled.div<StyledCategoricalValuePickerProps>`\n  width: fit-content;\n  font-size: 11px;\n  border-bottom: ${props => (props.noBorder ? '' : '1px dashed')};\n  cursor: pointer;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  overflow: hidden;\n  max-width: 100px;\n`;\n\ntype DropdownValuesWrapperProps = {width: number};\nconst DropdownValuesWrapper = styled.div<DropdownValuesWrapperProps>`\n  border: 0;\n  width: 100%;\n  left: 0;\n  z-index: ${props => props.theme.dropdownWrapperZ};\n  width: ${props => props.width}px;\n`;\n\ntype SelectedValuesWrapperProps = {width: number; height: number};\nconst SelectedValuesWrapper = styled(DropdownValuesWrapper)<SelectedValuesWrapperProps>`\n  width: ${props => props.width}px;\n  max-height: ${props => props.height}px;\n  overflow: auto;\n\n  .custom-palette-chickleted-input {\n    padding: 8px;\n    background-color: ${props => props.theme.dropdownWrapperZ};\n  }\n`;\n\nconst StyledDropdownHeader = styled.div`\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  color: ${props => props.theme.inputColor};\n  padding: 0 8px;\n  font-size: 10px;\n\n  .button {\n    margin: 0;\n    padding: 0;\n    width: fit-content;\n  }\n`;\n\nconst StyledTooltipContent = styled.div`\n  padding: 8px;\n  width: 150px;\n  box-sizing: border-box;\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n\n  div {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst NUMBER_VALUES_IN_TOOLTIP = 10;\n\nconst CategoricalSelectorContext = React.createContext({\n  onSelectRest: () => null,\n  onReset: () => null\n});\n\n// Categorical values dropdownlist:\n// extending DropdownList and adding 'Select the Rest' and 'Reset' buttons\nclass ModifiedDropdownList extends DropdownList {\n  constructor(props) {\n    super(props);\n  }\n\n  render() {\n    return (\n      <>\n        <CategoricalSelectorContext.Consumer>\n          {context => (\n            <>\n              <StyledDropdownHeader>\n                <Button link size=\"smal\" onClick={context.onSelectRest}>\n                  Select the Rest\n                </Button>\n                <Button link size=\"smal\" onClick={context.onReset}>\n                  Reset\n                </Button>\n              </StyledDropdownHeader>\n              <DividerLine />\n              <DropdownList {...this.props} />\n            </>\n          )}\n        </CategoricalSelectorContext.Consumer>\n      </>\n    );\n  }\n}\n\nexport type CategoricalSelectorProps = {\n  index: number;\n  selectedValues: (string | number | null)[];\n  allValues: string[] | number[];\n  addColorMapValue?: (v: (number | string | null)[], i: number) => void;\n  removeColorMapValue?: (v: number | string, i: number) => void;\n  resetColorMapValue?: (i: number) => void;\n  selectRestColorMapValue?: (i: number) => void;\n  editable?: boolean;\n};\n\n// Categorical values selector for editing categorical values\nexport const CategoricalSelector: React.FC<CategoricalSelectorProps> = ({\n  index,\n  selectedValues,\n  allValues,\n  addColorMapValue,\n  removeColorMapValue,\n  resetColorMapValue,\n  selectRestColorMapValue,\n  editable = true\n}: CategoricalSelectorProps) => {\n  const [showTypeahead, setShowTypeahead] = useState(false);\n\n  const onOptionSelected = useCallback(\n    value => {\n      const previousSelected = toArray(selectedValues);\n      const items = uniq(previousSelected.concat(toArray(value)));\n      addColorMapValue?.(items, index);\n    },\n    [selectedValues, index, addColorMapValue]\n  );\n\n  const onOpenDropdown = useCallback(() => {\n    setShowTypeahead(true);\n  }, []);\n\n  const onCloseDropdown = useCallback(() => {\n    setShowTypeahead(false);\n  }, []);\n\n  const onRemoveItem = useCallback(\n    value => {\n      removeColorMapValue?.(value, index);\n    },\n    [index, removeColorMapValue]\n  );\n\n  const onReset = useCallback(() => {\n    resetColorMapValue?.(index);\n    setShowTypeahead(false);\n    return null;\n  }, [resetColorMapValue, index]);\n\n  const onSelectRest = useCallback(() => {\n    selectRestColorMapValue?.(index);\n    setShowTypeahead(false);\n    return null;\n  }, [selectRestColorMapValue, index]);\n\n  return (\n    <StyledCategoricalValuePickerWrapper>\n      {editable && <Add height=\"12px\" onClick={onOpenDropdown} />}\n      <StyledCategoricalValuePicker\n        noBorder={selectedValues.length === 0 || !editable}\n        onClick={onOpenDropdown}\n        data-tip\n        data-for={`category-values-${index}`}\n      >\n        {selectedValues.length === 0\n          ? 'Add Value'\n          : selectedValues.length === 1\n          ? selectedValues[0]\n          : `${selectedValues.length} selected`}\n        {selectedValues.length > 1 && (\n          <Tooltip id={`category-values-${index}`} place=\"top\" interactive={true}>\n            <StyledTooltipContent>\n              {selectedValues.slice(0, NUMBER_VALUES_IN_TOOLTIP).map((value, i) => (\n                <div key={i}>{value}</div>\n              ))}\n              {selectedValues.length > NUMBER_VALUES_IN_TOOLTIP && <div>...</div>}\n            </StyledTooltipContent>\n          </Tooltip>\n        )}\n      </StyledCategoricalValuePicker>\n      {editable && (\n        <Portaled left={0} top={0} isOpened={showTypeahead} onClose={onCloseDropdown}>\n          {selectedValues.length > 1 && (\n            <SelectedValuesWrapper width={250} height={200}>\n              <ChickletedInput\n                className={'custom-palette-chickleted-input'}\n                selectedItems={selectedValues}\n                placeholder={''}\n                removeItem={onRemoveItem}\n                onClick={() => null}\n                CustomChickletComponent={null}\n              />\n            </SelectedValuesWrapper>\n          )}\n          <DropdownValuesWrapper width={250}>\n            <div style={{position: 'relative'}}>\n              <CategoricalSelectorContext.Provider\n                value={{\n                  onReset,\n                  onSelectRest\n                }}\n              >\n                <Typeahead\n                  customClasses={{\n                    results: 'list-selector',\n                    input: 'typeahead__input',\n                    listItem: 'list__item',\n                    listAnchor: 'list__item__anchor'\n                  }}\n                  options={allValues}\n                  // add safe string casting for the Typeahead, so fuzzy search never receives non-strings, preventing the toLowerCase crash\n                  displayOption={o => String(o ?? '')}\n                  filterOption={(input, o) => String(o ?? '').includes(String(input ?? ''))}\n                  placeholder={'Search'}\n                  onOptionSelected={onOptionSelected}\n                  customListComponent={ModifiedDropdownList}\n                  customListItemComponent={ListItem}\n                  searchable={true}\n                  showOptionsWhenEmpty\n                  selectedItems={selectedValues}\n                />\n              </CategoricalSelectorContext.Provider>\n            </div>\n          </DropdownValuesWrapper>\n        </Portaled>\n      )}\n    </StyledCategoricalValuePickerWrapper>\n  );\n};\n\nexport type CategoricalCustomPaletteInputProps = {\n  index: number;\n  isSorting: boolean;\n  color: HexColor;\n  colorMap?: ColorMap | null;\n  addColorMapValue: (v: (number | string | null)[], i: number) => void;\n  removeColorMapValue: (v: number | string, i: number) => void;\n  resetColorMapValue: (i: number) => void;\n  selectRestColorMapValue: (i: number) => void;\n  actionIcons?: ActionIcons;\n  onDelete: (index: number) => void;\n  onAdd: (index: number) => void;\n  onToggleSketcher: (index: number) => void;\n  allValues: string[] | number[];\n  disableDelete?: boolean;\n};\n\nexport const CategoricalCustomPaletteInput: React.FC<CategoricalCustomPaletteInputProps> = ({\n  index,\n  isSorting,\n  color,\n  colorMap,\n  actionIcons = defaultActionIcons,\n  onDelete,\n  disableDelete,\n  onToggleSketcher,\n  addColorMapValue,\n  removeColorMapValue,\n  resetColorMapValue,\n  selectRestColorMapValue,\n  allValues\n}: CategoricalCustomPaletteInputProps) => {\n  const selectedValues: (number | string | null)[] = useMemo(() => {\n    if (!colorMap || !colorMap[index]) return [];\n    const value = colorMap[index][0];\n    const values = Array.isArray(value) ? value : value !== null ? [value] : [];\n    return values;\n  }, [colorMap, index]);\n\n  const onClickSwtach = useCallback(() => onToggleSketcher(index), [onToggleSketcher, index]);\n  const onColorDelete = useCallback(() => onDelete(index), [onDelete, index]);\n\n  return (\n    <SortableItem id={`${index}`} isSorting={isSorting}>\n      {listeners => (\n        <>\n          <div className=\"custom-palette-input__left\">\n            <DragHandle className=\"layer__drag-handle\" {...listeners}>\n              <actionIcons.sort height=\"20px\" />\n            </DragHandle>\n            <ColorSwatch color={color} onClick={onClickSwtach} />\n            {colorMap && colorMap[index] && (\n              <CategoricalSelector\n                selectedValues={selectedValues}\n                allValues={allValues}\n                addColorMapValue={addColorMapValue}\n                removeColorMapValue={removeColorMapValue}\n                resetColorMapValue={resetColorMapValue}\n                selectRestColorMapValue={selectRestColorMapValue}\n                index={index}\n              />\n            )}\n          </div>\n          <div className=\"custom-palette-input__right\">\n            {!disableDelete ? (\n              <DeleteColorStop onColorDelete={onColorDelete} IconComponent={actionIcons.delete} />\n            ) : null}\n          </div>\n        </>\n      )}\n    </SortableItem>\n  );\n};\n\nexport const BottomAction = ({onCancel, onConfirm}) => (\n  <StyledButtonContainer>\n    <Button className=\"confirm-apply__button\" small onClick={onConfirm}>\n      Confirm\n    </Button>\n    <Button link small onClick={onCancel}>\n      Cancel\n    </Button>\n  </StyledButtonContainer>\n);\n\nconst StyledCustomPalette = styled.div.attrs({\n  className: 'custom-palette'\n})`\n  margin-top: 8px;\n`;\n\nfunction CustomPaletteFactory(): React.FC<CustomPaletteProps> {\n  const CustomPalette: React.FC<CustomPaletteProps> = ({\n    ordinalDomain,\n    customPalette,\n    setColorPaletteUI,\n    showSketcher,\n    actionIcons = defaultActionIcons,\n    onCancel,\n    onApply\n  }) => {\n    const [isSorting, setIsSorting] = useState(false);\n    const {colors, colorMap} = customPalette;\n    const colorBreaks = useMemo(\n      () =>\n        colorMap\n          ? customPalette.type === 'custom'\n            ? colorMapToColorBreaks(colorMap)\n            : colorMapToCategoricalColorBreaks(colorMap)\n          : null,\n      [customPalette.type, colorMap]\n    );\n\n    const onPickerUpdate = useCallback(\n      color => {\n        if (color && Number.isFinite(showSketcher)) {\n          const newCustomPalette = updateCustomPaletteColor(\n            customPalette,\n            Number(showSketcher),\n            color.hex\n          );\n          setColorPaletteUI({\n            customPalette: newCustomPalette\n          });\n        }\n      },\n      [customPalette, showSketcher, setColorPaletteUI]\n    );\n    const onToggleSketcher = useCallback(\n      val => {\n        setColorPaletteUI({\n          showSketcher: val\n        });\n      },\n      [setColorPaletteUI]\n    );\n    const onDelete = useCallback(\n      index => {\n        const newCustomPalette = removeCustomPaletteColor(customPalette, index);\n        setColorPaletteUI({\n          customPalette: newCustomPalette\n        });\n      },\n      [customPalette, setColorPaletteUI]\n    );\n\n    const onAdd = useCallback(\n      index => {\n        // add color at the end\n        const newCustomPalette = addCustomPaletteColor(customPalette, index);\n        setColorPaletteUI({\n          customPalette: newCustomPalette\n        });\n      },\n      [customPalette, setColorPaletteUI]\n    );\n\n    const onAddCategoricalStep = useCallback(() => {\n      onAdd(colors.length - 1);\n    }, [colors.length, onAdd]);\n\n    const onSwatchClose = useCallback(() => {\n      onToggleSketcher(false);\n    }, [onToggleSketcher]);\n\n    const onConfirm = useCallback(\n      event => {\n        event.stopPropagation();\n        event.preventDefault();\n        onCancel();\n        onApply(event);\n      },\n      [onCancel, onApply]\n    );\n\n    const onSortEnd = useCallback(\n      (event: DragEndEvent) => {\n        const {active, over} = event;\n        if (over && active.id !== over.id) {\n          const oldIndex = colors.findIndex((_, index) => `${index}` === active.id);\n          const newIndex = colors.findIndex((_, index) => `${index}` === over.id);\n          const newCustomPalette = sortCustomPaletteColor(customPalette, oldIndex, newIndex);\n          setColorPaletteUI({\n            customPalette: newCustomPalette\n          });\n        }\n        setIsSorting(false);\n      },\n      [colors, customPalette, setIsSorting, setColorPaletteUI]\n    );\n\n    const onSortStart = useCallback(() => {\n      setIsSorting(true);\n    }, [setIsSorting]);\n\n    const inputColorHex = useCallback(\n      (index, value) => {\n        const newCustomPalette = updateCustomPaletteColor(customPalette, index, value);\n        // setColors(newColors);\n        setColorPaletteUI({\n          customPalette: newCustomPalette\n        });\n      },\n      [customPalette, setColorPaletteUI]\n    );\n\n    const editColorMapValue = useCallback(\n      (value, index) => {\n        if (!customPalette.colorMap) {\n          return;\n        }\n        const newColorMap = customPalette.colorMap.map(\n          (cm, i) => (i === index ? [value, cm[1]] : cm) as [string, string]\n        );\n\n        // sort the user inputs in case the break values are not ordered\n        const breaks = newColorMap\n          .map(cm => cm[0] as string | null)\n          .slice(0, -1)\n          .sort((a, b) => Number(a) - Number(b))\n          .concat(null);\n        const sortedNewColorMap: ColorMap = newColorMap.map((cm, i) => [breaks[i], cm[1]]);\n\n        setColorPaletteUI({\n          customPalette: {\n            ...customPalette,\n            colorMap: sortedNewColorMap\n          }\n        });\n      },\n      [setColorPaletteUI, customPalette]\n    );\n\n    // remove a selected category item from a color map\n    const removeCategoricalColorMapValue = useCallback(\n      (item, index) => {\n        if (!colorMap) {\n          return;\n        }\n        setColorPaletteUI({\n          customPalette: {\n            ...customPalette,\n            colorMap: removeCategoricalValueFromColorMap(colorMap, item, index)\n          }\n        });\n      },\n      [setColorPaletteUI, customPalette, colorMap]\n    );\n\n    // add selected categorical items to a color map\n    const addCategoricalColorMapValue = useCallback(\n      (items, index) => {\n        if (!colorMap) {\n          return;\n        }\n        setColorPaletteUI({\n          customPalette: {\n            ...customPalette,\n            colorMap: addCategoricalValuesToColorMap(colorMap, items, index)\n          }\n        });\n      },\n      [setColorPaletteUI, customPalette, colorMap]\n    );\n\n    // reset a color map\n    const resetCategoricalColorMapValue = useCallback(\n      index => {\n        if (!colorMap) {\n          return;\n        }\n        setColorPaletteUI({\n          customPalette: {\n            ...customPalette,\n            colorMap: resetCategoricalColorMapByIndex(colorMap, index)\n          }\n        });\n      },\n      [setColorPaletteUI, customPalette, colorMap]\n    );\n\n    // select the rest values for a color map\n    const selectRestCategoricalColorMap = useCallback(\n      index => {\n        if (!colorMap) {\n          return;\n        }\n        setColorPaletteUI({\n          customPalette: {\n            ...customPalette,\n            colorMap: selectRestCategoricalColorMapByIndex(colorMap, index, ordinalDomain)\n          }\n        });\n      },\n      [setColorPaletteUI, customPalette, colorMap, ordinalDomain]\n    );\n\n    return (\n      <StyledCustomPalette>\n        <WrappedSortableContainer\n          className=\"custom-palette__sortable-container\"\n          onSortEnd={onSortEnd}\n          onSortStart={onSortStart}\n        >\n          {colors.map((color, index) =>\n            customPalette.type === 'custom' ? (\n              <CustomPaletteInput\n                key={index}\n                colorBreaks={colorBreaks}\n                index={index}\n                isSorting={isSorting}\n                color={color}\n                inputColorHex={inputColorHex}\n                disableAppend={colors.length >= 20}\n                disableDelete={colors.length <= 2}\n                actionIcons={actionIcons}\n                onAdd={onAdd}\n                onDelete={onDelete}\n                onToggleSketcher={onToggleSketcher}\n                editColorMapValue={editColorMapValue}\n              />\n            ) : (\n              ordinalDomain && (\n                <CategoricalCustomPaletteInput\n                  key={index}\n                  colorMap={colorMap}\n                  index={index}\n                  isSorting={isSorting}\n                  color={color}\n                  actionIcons={actionIcons}\n                  onAdd={onAdd}\n                  onDelete={onDelete}\n                  disableDelete={colors.length <= 2}\n                  onToggleSketcher={onToggleSketcher}\n                  addColorMapValue={addCategoricalColorMapValue}\n                  removeColorMapValue={removeCategoricalColorMapValue}\n                  resetColorMapValue={resetCategoricalColorMapValue}\n                  selectRestColorMapValue={selectRestCategoricalColorMap}\n                  allValues={ordinalDomain}\n                />\n              )\n            )\n          )}\n        </WrappedSortableContainer>\n        {customPalette.type === 'customOrdinal' && (\n          <StyledAddStepContainer>\n            <AddColorStop onColorAdd={onAddCategoricalStep} IconComponent={actionIcons.add} />\n            <Button link size=\"smal\" onClick={onAddCategoricalStep}>\n              Add Step\n            </Button>\n          </StyledAddStepContainer>\n        )}\n        <DividerLine />\n        {/* Cancel or Confirm Buttons */}\n        <BottomAction onCancel={onCancel} onConfirm={onConfirm} />\n        <Portaled isOpened={showSketcher !== false} left={280} top={-300} onClose={onSwatchClose}>\n          <CustomPicker color={colors[Number(showSketcher)]} onChange={onPickerUpdate} />\n        </Portaled>\n      </StyledCustomPalette>\n    );\n  };\n\n  return CustomPalette;\n}\n\nexport default CustomPaletteFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/custom-picker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled, {withTheme} from 'styled-components';\nimport {SketchPicker, ColorChangeHandler} from 'react-color';\n\nimport {HexColor} from '@kepler.gl/types';\nimport {panelBackground} from '@kepler.gl/styles';\n\nimport {BaseComponentProps} from '../../types';\n\n// This was put in because 3rd party library react-color doesn't yet cater for customized color of child component <SketchField> which contains HEX/RGB input text box\n// Issue raised: https://github.com/casesandberg/react-color/issues/631\n\ntype StyledPickerProps = {\n  type?: string;\n  active?: boolean;\n} & BaseComponentProps;\n\nconst StyledPicker = styled.div<StyledPickerProps>`\n  .sketch-picker {\n    span {\n      color: ${props => props.theme.labelColor} !important;\n      font-family: ${props => props.theme.fontFamily};\n    }\n    input {\n      text-align: center;\n      font-family: ${props => props.theme.fontFamily};\n      color: ${props => props.theme.inputColor} !important;\n      border-color: ${props => props.theme.secondaryInputBgd} !important;\n      box-shadow: none !important;\n      background-color: ${props => props.theme.inputBgdHover} !important;\n\n      &:hover {\n        cursor: ${props => (props.type === 'number' || props.type === 'text' ? 'text' : 'pointer')};\n        background-color: ${props =>\n          props.active ? props.theme.inputBgdActive : props.theme.inputBgdHover};\n        border-color: ${props =>\n          props.active ? props.theme.inputBorderActiveColor : props.theme.inputBorderHoverColor};\n      }\n\n      &:active,\n      &:focus,\n      &.focus,\n      &.active {\n        outline: 0;\n        background-color: ${props => props.theme.inputBgdActive};\n        border-color: ${props => props.theme.inputBorderActiveColor};\n        box-shadow: ${props => props.theme.inputBoxShadowActive};\n      }\n    }\n    label {\n      color: ${props => props.theme.subtextColor} !important;\n    }\n  }\n`;\n\nconst PRESET_COLORS = [];\n\ntype CustomPickerProps = {\n  color: HexColor;\n  theme: {\n    panelBackground: string;\n  };\n  onChange: ColorChangeHandler;\n};\n\nconst defaultProps: CustomPickerProps = {\n  color: '#f00',\n  theme: {\n    panelBackground\n  },\n  onChange: () => {\n    // no-op\n  }\n};\n\n// Note: When using SketchPicker, the parent component CustomPicker can be invoked as a function without props by ReactDOM.\nconst CustomPicker: React.FC<CustomPickerProps> = (props = defaultProps) => {\n  const {color, onChange, theme} = props;\n\n  const pickerStyle = useMemo(\n    () => ({\n      picker: {\n        width: '200px',\n        padding: '10px 10px 8px',\n        boxSizing: 'initial',\n        background: theme.panelBackground\n      }\n    }),\n    [theme.panelBackground]\n  );\n\n  return (\n    <StyledPicker>\n      <SketchPicker\n        color={color}\n        onChange={onChange}\n        presetColors={PRESET_COLORS}\n        styles={pickerStyle}\n        disableAlpha\n      />\n    </StyledPicker>\n  );\n};\n\nexport default withTheme(CustomPicker) as React.FC<Omit<CustomPickerProps, 'theme'>>;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/dataset-layer-group.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\n\nimport DatasetLayerSectionFactory from './dataset-layer-section';\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {UIStateActions, VisStateActions, ActionHandler, MapStateActions} from '@kepler.gl/actions';\nimport {KeplerTable, Datasets} from '@kepler.gl/table';\n\ntype DatasetLayerGroupProps = {\n  datasets: Datasets;\n  layers: Layer[];\n  layerOrder: string[];\n  layerClasses: LayerClassesType;\n  showDeleteDataset: boolean;\n  removeDataset: ActionHandler<typeof UIStateActions.openDeleteModal>;\n  showDatasetTable: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  uiStateActions: typeof UIStateActions;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n};\n\nDatasetLayerGroupFactory.deps = [DatasetLayerSectionFactory];\n\nfunction DatasetLayerGroupFactory(\n  DatasetLayerSection: ReturnType<typeof DatasetLayerSectionFactory>\n) {\n  const DatasetLayerGroup: React.FC<DatasetLayerGroupProps> = props => {\n    const {\n      datasets,\n      showDatasetTable,\n      layers,\n      updateTableColor,\n      showDeleteDataset,\n      removeDataset,\n      layerOrder,\n      layerClasses,\n      uiStateActions,\n      visStateActions,\n      mapStateActions\n    } = props;\n\n    const datasetLayerSectionData = useMemo(() => {\n      return Object.values(datasets).map((dataset: KeplerTable) => {\n        // Global layer order will contain the correct order of layers\n        // We just empty the positions in layers array (for each dataset)\n        // where the layer doesn't belong to a dataset and set it to null\n        const datasetLayers = layers\n          .map(layer => (layer.config.dataId === dataset.id ? layer : null))\n          .filter(layer => Boolean(layer));\n\n        return {dataset, datasetLayers};\n      });\n    }, [datasets, layers]);\n\n    return (\n      <>\n        {datasetLayerSectionData.map(dlsData => (\n          <DatasetLayerSection\n            key={dlsData.dataset.id}\n            dataset={dlsData.dataset}\n            layers={dlsData.datasetLayers as Layer[]}\n            datasets={datasets}\n            showDatasetTable={showDatasetTable}\n            updateTableColor={updateTableColor}\n            showDeleteDataset={showDeleteDataset}\n            removeDataset={removeDataset}\n            layerOrder={layerOrder}\n            layerClasses={layerClasses}\n            uiStateActions={uiStateActions}\n            visStateActions={visStateActions}\n            mapStateActions={mapStateActions}\n          />\n        ))}\n      </>\n    );\n  };\n\n  return DatasetLayerGroup;\n}\n\nexport default DatasetLayerGroupFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/dataset-layer-section.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport SourceDataCatalogFactory from '../common/source-data-catalog';\nimport LayerListFactory from './layer-list';\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {UIStateActions, ActionHandler, VisStateActions, MapStateActions} from '@kepler.gl/actions';\nimport {KeplerTable, Datasets} from '@kepler.gl/table';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\ntype DatasetLayerSectionProps = {\n  datasets: Datasets;\n  dataset: KeplerTable;\n  layers: Layer[];\n  layerOrder: string[];\n  layerClasses: LayerClassesType;\n  showDeleteDataset: boolean;\n  showDatasetTable: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  removeDataset: ActionHandler<typeof UIStateActions.openDeleteModal>;\n  uiStateActions: typeof UIStateActions;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n};\n\nconst DatasetLayerSectionWrapper = styled.div.attrs({\n  className: 'dataset-layer-section'\n})`\n  margin-bottom: 16px;\n`;\n\nDatasetLayerSectionFactory.deps = [SourceDataCatalogFactory, LayerListFactory];\n\nfunction DatasetLayerSectionFactory(\n  SourceDataCatalog: ReturnType<typeof SourceDataCatalogFactory>,\n  LayerList: ReturnType<typeof LayerListFactory>\n) {\n  const DatasetLayerSection: React.FC<DatasetLayerSectionProps> = props => {\n    const {\n      dataset,\n      datasets,\n      showDatasetTable,\n      layers,\n      updateTableColor,\n      showDeleteDataset,\n      removeDataset,\n      layerOrder,\n      layerClasses,\n      uiStateActions,\n      visStateActions,\n      mapStateActions\n    } = props;\n\n    const datasetCatalog = useMemo(() => {\n      return {[dataset.id]: dataset};\n    }, [dataset]);\n\n    // temp patch to hide layers that are in development\n    const enableRasterTileLayer = getApplicationConfig().enableRasterTileLayer;\n    const enableWMSLayer = getApplicationConfig().enableWMSLayer;\n    const filteredLayerClasses = useMemo(() => {\n      let filteredClasses = layerClasses;\n      if (!enableRasterTileLayer) {\n        const {rasterTile: _rasterTile, ...rest} = filteredClasses;\n        filteredClasses = rest as LayerClassesType;\n      }\n      if (!enableWMSLayer) {\n        const {wms: _wms, ...rest} = filteredClasses;\n        filteredClasses = rest as LayerClassesType;\n      }\n      return filteredClasses as LayerClassesType;\n    }, [enableRasterTileLayer, enableWMSLayer, layerClasses]);\n\n    return (\n      <DatasetLayerSectionWrapper>\n        <SourceDataCatalog\n          datasets={datasetCatalog}\n          showDatasetTable={showDatasetTable}\n          updateTableColor={updateTableColor}\n          removeDataset={removeDataset}\n          showDeleteDataset={showDeleteDataset}\n        />\n        <LayerList\n          datasets={datasets}\n          layerOrder={layerOrder}\n          layers={layers}\n          layerClasses={filteredLayerClasses}\n          uiStateActions={uiStateActions}\n          visStateActions={visStateActions}\n          mapStateActions={mapStateActions}\n          isSortable={false}\n        />\n      </DatasetLayerSectionWrapper>\n    );\n  };\n\n  return DatasetLayerSection;\n}\n\nexport default DatasetLayerSectionFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/dataset-section.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Add} from '../../common/icons';\nimport {Button} from '../../common/styled-components';\n\nimport SourceDataCatalogFactory from '../common/source-data-catalog';\nimport {UIStateActions, VisStateActions, ActionHandler} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\n\ntype AddDataButtonProps = {\n  onClick: () => void;\n  isInactive: boolean;\n};\n\ntype DatasetSectionProps = {\n  datasets: Datasets;\n  showDatasetList?: boolean;\n  showDeleteDataset?: boolean;\n  showDatasetTable: ActionHandler<typeof VisStateActions.showDatasetTable>;\n  updateTableColor: ActionHandler<typeof VisStateActions.updateTableColor>;\n  removeDataset: ActionHandler<typeof UIStateActions.openDeleteModal>;\n  showAddDataModal: () => void;\n};\n\nconst StyledDatasetTitle = styled.div<{$showDatasetList?: boolean}>`\n  line-height: ${props => props.theme.sidePanelTitleLineHeight};\n  font-weight: 400;\n  letter-spacing: 1.25px;\n  color: ${props => props.theme.subtextColor};\n  font-size: 11px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: ${props => (props.$showDatasetList ? '16px' : '4px')};\n`;\n\nconst StyledDatasetSection = styled.div`\n  border-bottom: 1px solid ${props => props.theme.sidePanelBorderColor};\n`;\n\nexport function AddDataButtonFactory() {\n  const AddDataButton: React.FC<AddDataButtonProps> = React.memo(({onClick, isInactive}) => (\n    <Button\n      className=\"add-data-button\"\n      onClick={onClick}\n      inactive={!isInactive}\n      width=\"105px\"\n      secondary\n    >\n      <Add height=\"12px\" />\n      <FormattedMessage id={'layerManager.addData'} />\n    </Button>\n  ));\n  AddDataButton.displayName = 'AddDataButton';\n  return AddDataButton;\n}\n\nDatasetSectionFactory.deps = [SourceDataCatalogFactory, AddDataButtonFactory];\n\nfunction DatasetSectionFactory(\n  SourceDataCatalog: ReturnType<typeof SourceDataCatalogFactory>,\n  AddDataButton: ReturnType<typeof AddDataButtonFactory>\n) {\n  const DatasetSection: React.FC<DatasetSectionProps> = props => {\n    const {\n      datasets,\n      showDatasetTable,\n      updateTableColor,\n      showDeleteDataset,\n      removeDataset,\n      showDatasetList,\n      showAddDataModal\n    } = props;\n    const datasetCount = Object.keys(datasets).length;\n\n    return (\n      <StyledDatasetSection>\n        <StyledDatasetTitle $showDatasetList={showDatasetList}>\n          <span>Datasets{datasetCount ? `(${datasetCount})` : ''}</span>\n          <AddDataButton onClick={showAddDataModal} isInactive={!datasetCount} />\n        </StyledDatasetTitle>\n        {showDatasetList && (\n          <SourceDataCatalog\n            datasets={datasets}\n            showDatasetTable={showDatasetTable}\n            updateTableColor={updateTableColor}\n            removeDataset={removeDataset}\n            showDeleteDataset={showDeleteDataset}\n          />\n        )}\n      </StyledDatasetSection>\n    );\n  };\n\n  return DatasetSection;\n}\n\nexport default DatasetSectionFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/dimension-scale-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CHANNEL_SCALES, SCALE_TYPE_NAMES} from '@kepler.gl/constants';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {ColorUI, LayerVisConfig} from '@kepler.gl/types';\nimport {camelize} from '@kepler.gl/utils';\nimport {Layer, VisualChannel} from '@kepler.gl/layers';\nimport {default as React, useCallback} from 'react';\nimport {Field} from '@kepler.gl/types';\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport {PanelLabel, SidePanelSection} from '../../common/styled-components';\nimport ColorScaleSelectorFactory from './color-scale-selector';\nimport {KeplerTable} from '@kepler.gl/table';\n\nconst SizeScaleSelector = ({...dropdownSelectProps}: any) => (\n  <ItemSelector {...dropdownSelectProps} />\n);\n\nexport type DimensionScaleSelectorProps = {\n  layer: Layer;\n  channel: VisualChannel;\n  label?: string;\n  dataset: KeplerTable | undefined;\n  onChange: (\n    newConfig: {[key: string]: Field | null | string},\n    key: string,\n    newVisConfig?: Partial<LayerVisConfig>\n  ) => void;\n  setColorUI: (range: string, newConfig: {[key in keyof ColorUI]: ColorUI[key]}) => void;\n};\n\nDimensionScaleSelectorFactory.deps = [ColorScaleSelectorFactory];\n\nfunction DimensionScaleSelectorFactory(\n  ColorScaleSelector: ReturnType<typeof ColorScaleSelectorFactory>\n): React.FC<DimensionScaleSelectorProps> {\n  const DimensionScaleSelector: React.FC<DimensionScaleSelectorProps> = ({\n    layer,\n    channel,\n    dataset,\n    label,\n    onChange,\n    setColorUI\n  }) => {\n    const {channelScaleType, domain, field, key, range, scale} = channel;\n    const scaleType = scale ? layer.config[scale] : null;\n    const layerScaleOptions = layer.getScaleOptions(key);\n    const scaleOptions = layerScaleOptions.map(op => ({\n      label: SCALE_TYPE_NAMES[op] || op,\n      value: op\n    }));\n    const disabled = scaleOptions.length < 2;\n    const isColorScale =\n      channelScaleType === CHANNEL_SCALES.color ||\n      (layer.config.aggregatedBins && channelScaleType === CHANNEL_SCALES.colorAggr);\n\n    const onSelect = useCallback(\n      (val, newRange) => onChange({[scale]: val}, key, newRange ? {[range]: newRange} : undefined),\n      [onChange, range, scale, key]\n    );\n    const _setColorUI = useCallback(newConfig => setColorUI(range, newConfig), [range, setColorUI]);\n\n    const dropdownSelectProps = {\n      disabled,\n      selectedItems: scaleOptions.filter(op => op.value === scaleType),\n      options: scaleOptions,\n      multiSelect: false,\n      searchable: false,\n      onChange: onSelect,\n      displayOption: 'label',\n      getOptionValue: 'value',\n      channelKey: key\n    };\n\n    return (\n      <SidePanelSection>\n        <PanelLabel>\n          <FormattedMessage\n            id={label ? `scale.${camelize(label)}` : 'misc.scale'}\n            defaultMessage={label}\n          />\n        </PanelLabel>\n        {isColorScale && dataset ? (\n          <ColorScaleSelector\n            {...dropdownSelectProps}\n            layer={layer}\n            field={layer.config[field]}\n            dataset={dataset}\n            onSelect={onSelect}\n            scaleType={scaleType}\n            domain={layer.config[domain]}\n            aggregatedBins={layer.config.aggregatedBins}\n            range={layer.config.visConfig[range]}\n            setColorUI={_setColorUI}\n            colorUIConfig={layer.config.colorUI?.[range]}\n          />\n        ) : (\n          <SizeScaleSelector {...dropdownSelectProps} />\n        )}\n      </SidePanelSection>\n    );\n  };\n  return DimensionScaleSelector;\n}\n\nexport default DimensionScaleSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/how-to-button.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {MouseEventHandler} from 'react';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport {Button} from '../../common/styled-components';\n\nconst StyledHowToButton = styled.div`\n  position: absolute;\n  right: 12px;\n  top: -4px;\n`;\n\nexport type HowToButtonProps = {\n  onClick: MouseEventHandler;\n};\n\nexport const HowToButton: React.FC<HowToButtonProps> = ({onClick}: HowToButtonProps) => (\n  <StyledHowToButton>\n    <Button link small onClick={onClick}>\n      <FormattedMessage id={'layerConfiguration.howTo'} />\n    </Button>\n  </StyledHowToButton>\n);\n\nexport default HowToButton;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-color-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\n\nimport {ColorRange} from '@kepler.gl/types';\nimport {Layer} from '@kepler.gl/layers';\nimport {NestedPartial, RGBColor, ColorUI} from '@kepler.gl/types';\n\nimport ColorSelectorFactory from './color-selector';\nimport {SidePanelSection} from '../../common/styled-components';\n\ntype LayerColorSelectorProps = {\n  layer: Layer;\n  onChange: (v: Record<string, RGBColor>) => void;\n  selectedColor?: RGBColor;\n  property?: string;\n  setColorUI: (prop: string, newConfig: NestedPartial<ColorUI>) => void;\n};\n\ntype ArcLayerColorSelectorProps = {\n  layer: Layer;\n  onChangeConfig: (v: {color: RGBColor}) => void;\n  onChangeVisConfig: (v: {targetColor: RGBColor}) => void;\n  property?: string;\n  setColorUI: (prop: string, newConfig: NestedPartial<ColorUI>) => void;\n};\n\ntype LayerColorRangeSelectorProps = {\n  layer: Layer;\n  onChange: (v: Record<string, ColorRange>) => void;\n  property?: string;\n  setColorUI: (prop: string, newConfig: NestedPartial<ColorUI>) => void;\n};\n\nLayerColorSelectorFactory.deps = [ColorSelectorFactory];\nexport function LayerColorSelectorFactory(ColorSelector) {\n  const LayerColorSelector = ({\n    layer,\n    onChange,\n    selectedColor,\n    property = 'color',\n    setColorUI\n  }: LayerColorSelectorProps) => {\n    const onSetColorUI = useCallback(\n      newConfig => setColorUI(property, newConfig),\n      [setColorUI, property]\n    );\n\n    return (\n      <SidePanelSection>\n        <ColorSelector\n          colorSets={[\n            {\n              selectedColor: selectedColor || layer.config.color,\n              setColor: (rgbValue: RGBColor) => onChange({[property]: rgbValue})\n            }\n          ]}\n          colorUI={layer.config.colorUI[property]}\n          setColorUI={onSetColorUI}\n        />\n      </SidePanelSection>\n    );\n  };\n  return LayerColorSelector;\n}\n\nLayerColorRangeSelectorFactory.deps = [ColorSelectorFactory];\nexport function LayerColorRangeSelectorFactory(ColorSelector) {\n  const LayerColorRangeSelector = ({\n    layer,\n    onChange,\n    property = 'colorRange',\n    setColorUI\n  }: LayerColorRangeSelectorProps) => {\n    const onSetColorUI = useCallback(\n      newConfig => setColorUI(property, newConfig),\n      [setColorUI, property]\n    );\n\n    return (\n      <SidePanelSection>\n        <ColorSelector\n          colorSets={[\n            {\n              selectedColor: layer.config.visConfig[property],\n              isRange: true,\n              setColor: (colorRange: ColorRange) => onChange({[property]: colorRange})\n            }\n          ]}\n          colorUI={layer.config.colorUI[property]}\n          setColorUI={onSetColorUI}\n        />\n      </SidePanelSection>\n    );\n  };\n  return LayerColorRangeSelector;\n}\n\nArcLayerColorSelectorFactory.deps = [ColorSelectorFactory];\nexport function ArcLayerColorSelectorFactory(ColorSelector) {\n  const ArcLayerColorSelector = ({\n    layer,\n    onChangeConfig,\n    onChangeVisConfig,\n    property = 'color',\n    setColorUI\n  }: ArcLayerColorSelectorProps) => {\n    const onSetColorUI = useCallback(\n      newConfig => setColorUI(property, newConfig),\n      [setColorUI, property]\n    );\n\n    return (\n      <SidePanelSection>\n        <ColorSelector\n          colorSets={[\n            {\n              selectedColor: layer.config.color,\n              setColor: (rgbValue: RGBColor) => onChangeConfig({color: rgbValue}),\n              label: 'Source'\n            },\n            {\n              selectedColor: layer.config.visConfig.targetColor || layer.config.color,\n              setColor: (rgbValue: RGBColor) => onChangeVisConfig({targetColor: rgbValue}),\n              label: 'Target'\n            }\n          ]}\n          colorUI={layer.config.colorUI[property]}\n          setColorUI={onSetColorUI}\n        />\n      </SidePanelSection>\n    );\n  };\n  return ArcLayerColorSelector;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-column-config.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\n\nimport {LayerBaseConfig} from '@kepler.gl/layers';\nimport {\n  FieldPair,\n  ColumnPairs,\n  LayerColumns,\n  ColumnLabels,\n  EnhancedFieldPair\n} from '@kepler.gl/types';\nimport {toArray} from '@kepler.gl/common-utils';\n\nimport ColumnSelectorFactory from './column-selector';\nimport {MinimalField} from '../../common/field-selector';\nimport {SidePanelSection} from '../../common/styled-components';\n\nexport type LayerColumnConfigProps<FieldOption extends MinimalField> = {\n  columns: LayerColumns;\n  fields: FieldOption[];\n  assignColumnPairs: (key: string, pair: FieldPair) => LayerColumns;\n  assignColumn: (key: string, field: FieldOption) => LayerColumns;\n  updateLayerConfig: (newConfig: Partial<LayerBaseConfig>) => void;\n  updateLayerType?: (newType: string) => void;\n  columnPairs?: ColumnPairs | null;\n  fieldPairs?: FieldPair[];\n  columnLabels: ColumnLabels | null;\n  isActive: boolean;\n};\n\n/**\n * only provide suggested field pairs if there is a match,\n * otherwise the user can select a suggested field pair that will create invalid columns and a hard crash\n */\nfunction getValidFieldPairsSuggestionsForColumn(\n  enhancedFieldPairs: EnhancedFieldPair[] | null,\n  columnPairs: ColumnPairs | null | undefined,\n  columnKey: string\n): EnhancedFieldPair[] | null {\n  if (enhancedFieldPairs && columnPairs?.[columnKey]) {\n    const columnPair = columnPairs[columnKey];\n    const matchingFieldPairs = enhancedFieldPairs.filter(({pair}) => {\n      return toArray(columnPair.fieldPairKey).some(fieldPairKey =>\n        Object.prototype.hasOwnProperty.call(pair, fieldPairKey)\n      );\n    });\n    return matchingFieldPairs.length > 0 ? matchingFieldPairs : null;\n  }\n  return null;\n}\n\nLayerColumnConfigFactory.deps = [ColumnSelectorFactory];\n\nfunction LayerColumnConfigFactory(ColumnSelector: ReturnType<typeof ColumnSelectorFactory>) {\n  const LayerColumnConfig: React.FC<LayerColumnConfigProps<MinimalField & {fieldIdx: number}>> = ({\n    columnPairs,\n    fieldPairs,\n    columns,\n    columnLabels,\n    fields,\n    updateLayerConfig,\n    assignColumn,\n    assignColumnPairs,\n    isActive\n  }) => {\n    const enhancedFieldPairs: EnhancedFieldPair[] | null = useMemo(\n      () =>\n        columnPairs && fieldPairs\n          ? fieldPairs.map(fp => ({\n              name: fp.defaultName,\n              type: 'point',\n              pair: fp.pair\n            }))\n          : null,\n      [columnPairs, fieldPairs]\n    );\n\n    const onUpdateColumn = useCallback(\n      (key, value) => {\n        const assignedColumns =\n          value && value.pair && columnPairs\n            ? assignColumnPairs(key, value.pair)\n            : assignColumn(key, value);\n\n        updateLayerConfig({columns: assignedColumns});\n      },\n      [updateLayerConfig, columnPairs, assignColumnPairs, assignColumn]\n    );\n\n    if (!Object.keys(columns).length) {\n      // don't render if columns is empty\n      return <div />;\n    }\n\n    return (\n      <div>\n        <SidePanelSection>\n          <div className=\"layer-config__column\">\n            {Object.keys(columns).map(key => (\n              <ColumnSelector\n                column={columns[key]}\n                columns={columns}\n                label={(columnLabels && columnLabels[key]) || key}\n                key={key}\n                allFields={fields}\n                fieldPairs={getValidFieldPairsSuggestionsForColumn(\n                  enhancedFieldPairs,\n                  columnPairs,\n                  key\n                )}\n                onSelect={val => onUpdateColumn(key, val)}\n                isActive={isActive}\n              />\n            ))}\n          </div>\n        </SidePanelSection>\n      </div>\n    );\n  };\n\n  return LayerColumnConfig;\n}\n\nexport default LayerColumnConfigFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-column-mode-config.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Fragment, useCallback, useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport {\n  PanelHeaderContent,\n  PanelLabel,\n  StyledPanelHeader,\n  PanelContent\n} from '../../common/styled-components';\nimport Checkbox from '../../common/checkbox';\nimport {MinimalField} from '../../common/field-selector';\nimport PanelHeaderActionFactory from '../panel-header-action';\nimport LayerColumnConfigFactory from './layer-column-config';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Layer, LayerInfoModal, LayerBaseConfig} from '@kepler.gl/layers';\nimport {SupportedColumnMode, FieldPair, LayerColumns} from '@kepler.gl/types';\nimport {BaseComponentProps} from '../../types';\n\nimport {Help} from '../../common/icons';\n\nconst TopRow = styled.div`\n  display: flex;\n  justify-content: space-between;\n`;\n\nconst Separator = styled(({children, className}: BaseComponentProps) => (\n  <div className={className}>\n    <div className=\"separator-line-cell\">\n      <div className=\"separator-line\" />\n    </div>\n    <div className=\"separator-content\">{children}</div>\n    <div className=\"separator-line-cell\">\n      <div className=\"separator-line\" />\n    </div>\n  </div>\n))`\n  display: flex;\n  flex-direction: row;\n  & > * + * {\n    margin-left: 10px;\n  }\n  & > .separator-line-cell {\n    display: flex;\n    flex-grow: 1;\n    align-items: center;\n  }\n  & > .separator-content {\n    color: ${props => props.theme.labelColor};\n  }\n  & > .separator-line-cell > .separator-line {\n    display: flex;\n    border-bottom: 1px solid ${props => props.theme.labelColor};\n    width: 100%;\n    line-height: 1px;\n    height: 1px;\n  }\n`;\n\nconst ConfigPanesContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  & > * + * {\n    margin-top: 10px;\n  }\n  fieldset {\n    border: 1px solid ${props => props.theme.labelColor};\n    margin: 0;\n  }\n  legend {\n    display: flex;\n    label {\n      color: ${props => props.theme.labelColor};\n    }\n  }\n`;\n\ninterface FieldOption extends MinimalField {\n  fieldIdx: number;\n}\nexport type SupportedColumnModeConfig = {\n  key: string;\n  label: string;\n  columns: LayerColumns;\n};\nexport type ColumnModeConfigProps = {\n  supportedColumnModes: SupportedColumnMode[] | null;\n  selectedColumnMode?: string;\n  id: string;\n  columns: LayerColumns;\n  renderColumnConfig: (\n    mode: {key: string; label: string; columns: any},\n    selected: boolean\n  ) => JSX.Element;\n  selectColumnMode: (mode: SupportedColumnModeConfig) => void;\n  getHelpHandler?: (mode: SupportedColumnModeConfig) => (() => void) | null;\n};\n\nColumnModeConfigFactory.deps = [PanelHeaderActionFactory];\n\nexport function ColumnModeConfigFactory(\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n) {\n  const ColumnModeConfig: React.FC<ColumnModeConfigProps> = ({\n    id,\n    supportedColumnModes,\n    selectedColumnMode,\n    columns,\n    renderColumnConfig,\n    selectColumnMode,\n    getHelpHandler = () => null\n  }: ColumnModeConfigProps) => {\n    const columnModes = useMemo(\n      () =>\n        supportedColumnModes\n          ? supportedColumnModes.map(({key, label, requiredColumns, optionalColumns}) => {\n              const allColumns = (requiredColumns || [])\n                .concat(optionalColumns || [])\n                .reduce((acc, k) => {\n                  acc[k] = columns[k];\n                  return acc;\n                }, {});\n              return {key, label, columns: allColumns};\n            })\n          : Object.keys(columns).length > 0\n          ? [{key: 'default', label: '', columns}]\n          : [],\n      [supportedColumnModes, columns]\n    );\n\n    return (\n      <>\n        {columnModes.length > 0 ? (\n          <TopRow>\n            <PanelLabel>\n              <FormattedMessage id={'columns.title'} />\n            </PanelLabel>\n            <PanelLabel>\n              <FormattedMessage id=\"layer.required\" />\n            </PanelLabel>\n          </TopRow>\n        ) : null}\n        <ConfigPanesContainer>\n          {columnModes.map((modeConfig, i) => {\n            const {key: columnMode, label} = modeConfig;\n\n            const isSelected = selectedColumnMode === columnMode || columnModes.length === 1;\n            const columnPanel = renderColumnConfig(modeConfig, isSelected);\n            const helpHandler = getHelpHandler(modeConfig);\n            const selectColumnModeHandler = () => selectColumnMode(modeConfig);\n\n            return (\n              <Fragment key={columnMode}>\n                {i > 0 ? (\n                  <Separator>\n                    <FormattedMessage id=\"layer.columnModesSeparator\" />\n                  </Separator>\n                ) : null}\n                {columnModes.length > 1 ? (\n                  <div className=\"layer-column-mode-panel\">\n                    <StyledPanelHeader className=\"interaction-panel__header\">\n                      <PanelHeaderContent className=\"interaction-panel__header__content\">\n                        <Checkbox\n                          type=\"radio\"\n                          name={`layer-${id}-input-modes`}\n                          checked={isSelected}\n                          id={`${id}-input-column-${columnMode}`}\n                          label={label}\n                          onChange={selectColumnModeHandler}\n                        />\n                      </PanelHeaderContent>\n                      {helpHandler ? (\n                        <div className=\"interaction-panel__header__actions\">\n                          <PanelHeaderAction\n                            id={`${id}-help-button`}\n                            className=\"layer__help-button\"\n                            tooltip={'layerConfiguration.howTo'}\n                            onClick={helpHandler}\n                            IconComponent={Help}\n                          />\n                        </div>\n                      ) : null}\n                    </StyledPanelHeader>\n                    <PanelContent className=\"interaction-panel__content\">\n                      {columnPanel}\n                    </PanelContent>\n                  </div>\n                ) : (\n                  columnPanel\n                )}\n              </Fragment>\n            );\n          })}\n        </ConfigPanesContainer>\n      </>\n    );\n  };\n\n  return ColumnModeConfig;\n}\n\nexport type LayerColumnModeConfigProps = {\n  layer: Layer;\n  layerConfig: LayerBaseConfig;\n  supportedColumnModes: SupportedColumnMode[] | null;\n  id: string;\n  fields: FieldOption[];\n  fieldPairs?: FieldPair[];\n  openModal: (l: LayerInfoModal) => void;\n  updateLayerConfig: (config: Partial<LayerBaseConfig>) => void;\n  updateLayerType: (type: string) => void;\n};\n\nLayerColumnModeConfigFactory.deps = [LayerColumnConfigFactory, ColumnModeConfigFactory];\n\nfunction LayerColumnModeConfigFactory(\n  LayerColumnConfig: ReturnType<typeof LayerColumnConfigFactory>,\n  ColumnModeConfig: ReturnType<typeof ColumnModeConfigFactory>\n) {\n  const LayerColumnModeConfig = ({\n    id,\n    layer,\n    supportedColumnModes,\n    layerConfig,\n    fields,\n    fieldPairs,\n    openModal,\n    updateLayerConfig\n  }: LayerColumnModeConfigProps) => {\n    const {columns} = layerConfig;\n\n    const selectColumnMode = useCallback(\n      ({key: columnMode}) => {\n        updateLayerConfig({columnMode});\n      },\n      [updateLayerConfig]\n    );\n\n    const renderColumnConfig = useCallback(\n      ({key: columnMode, columns: cols}, isSelected) => (\n        <LayerColumnConfig\n          columnPairs={layer.columnPairs}\n          columns={cols}\n          assignColumnPairs={layer.assignColumnPairs.bind(layer)}\n          assignColumn={layer.assignColumn.bind(layer)}\n          columnLabels={layer.columnLabels}\n          fields={fields}\n          fieldPairs={fieldPairs}\n          updateLayerConfig={config =>\n            updateLayerConfig({\n              ...config,\n              // if column mode not currently selected\n              // set column mode along with the columns\n              ...(!isSelected && columnMode !== 'defaut' ? {columnMode} : {})\n            })\n          }\n          isActive={isSelected}\n        />\n      ),\n      [layer, updateLayerConfig, fieldPairs, fields]\n    );\n\n    const getHelpHandler = useCallback(\n      ({key: columnMode}) => {\n        const modal = layer.layerInfoModal?.[columnMode];\n        if (modal) {\n          return () => openModal(modal);\n        }\n        return null;\n      },\n      [layer, openModal]\n    );\n\n    return (\n      <ColumnModeConfig\n        id={id}\n        supportedColumnModes={supportedColumnModes}\n        selectedColumnMode={layerConfig.columnMode}\n        columns={columns}\n        selectColumnMode={selectColumnMode}\n        renderColumnConfig={renderColumnConfig}\n        getHelpHandler={getHelpHandler}\n      />\n    );\n  };\n\n  return LayerColumnModeConfig;\n}\n\nexport default LayerColumnModeConfigFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-config-group.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport {FormattedMessage} from 'react-intl';\nimport Switch from '../../common/switch';\nimport InfoHelperFactory from '../../common/info-helper';\nimport {VertThreeDots} from '../../common/icons';\nimport {shouldForwardProp} from '../../common/styled-components';\nimport {Layer} from '@kepler.gl/layers';\nimport {LayerVisConfig} from '@kepler.gl/types';\n\nexport type LayerConfigGroupLabelProps = {\n  label?: string;\n  description?: string;\n  collapsed?: boolean;\n};\n\nexport type LayerConfigGroupProps = {\n  layer?: Layer;\n  label: string;\n  property?: string;\n  description?: string;\n  collapsible?: boolean;\n  expanded?: boolean;\n  disabled?: boolean;\n  onChange?: (newVisConfig: Partial<LayerVisConfig>) => void;\n  IconComponent?: React.ElementType;\n  children?: React.ReactNode;\n};\n\nexport const StyledLayerConfigGroupAction = styled.div`\n  display: flex;\n  align-items: center;\n  color: ${props => props.theme.textColor};\n`;\n\nexport const ConfigGroupCollapsibleContent = styled.div.attrs({\n  className: 'layer-config-group__content__collapsible'\n})`\n  transition: max-height 0.3s ease-out;\n  height: max-content;\n  max-height: 1200px;\n  overflow: auto;\n`;\n\nexport const ConfigGroupCollapsibleHeader = styled.div.attrs({\n  className: 'layer-config-group__header__collapsible'\n})`\n  overflow: visible;\n  overflow: hidden;\n  max-height: 0;\n`;\n\nexport const StyledLayerConfigGroup = styled.div`\n  padding-left: ${props => props.theme.layerConfigGroupPaddingLeft}px;\n  margin-bottom: ${props => props.theme.layerConfigGroupMarginBottom}px;\n\n  &.disabled {\n    opacity: 0.3;\n    pointer-events: none;\n  }\n  &.collapsed {\n    .layer-config-group__header__collapsible {\n      overflow: visible;\n      max-height: 600px;\n    }\n    .layer-config-group__content {\n      .layer-config-group__content__collapsible {\n        overflow: hidden;\n        max-height: 0;\n      }\n    }\n  }\n`;\n\ninterface StyledConfigGroupHeaderProps {\n  collapsible?: boolean;\n}\n\nexport const StyledConfigGroupHeader = styled.div.withConfig({shouldForwardProp}).attrs({\n  className: 'layer-config-group__header'\n})<StyledConfigGroupHeaderProps>`\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-bottom: 12px;\n  cursor: default;\n\n  &:hover {\n    ${props => props.collapsible && 'cursor: pointer;'}\n    .layer-config-group__label {\n      color: ${props => props.theme.textColorHl};\n    }\n\n    .layer-config-group__action {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst ConfigGroupContent = styled.div`\n  &.disabled {\n    opacity: 0.3;\n    pointer-events: none;\n    * {\n      pointer-events: none;\n    }\n  }\n`;\n\nLayerConfigGroupLabelFactory.deps = [InfoHelperFactory];\n\nexport function LayerConfigGroupLabelFactory(InfoHelper: ReturnType<typeof InfoHelperFactory>) {\n  const StyledLayerConfigGroupLabel = styled.div`\n    border-left: ${props => props.theme.layerConfigGroupLabelBorderLeft} solid\n      ${props => props.theme.labelColor};\n    line-height: 12px;\n    margin-left: ${props => props.theme.layerConfigGroupLabelMargin};\n    padding-left: ${props => props.theme.layerConfigGroupLabelPadding};\n\n    display: flex;\n    align-items: center;\n\n    span {\n      color: ${props => props.theme.textColor};\n      font-weight: 500;\n      letter-spacing: 0.2px;\n      text-transform: capitalize;\n      margin-left: ${props => props.theme.layerConfigGroupLabelLabelMargin};\n      font-size: ${props => props.theme.layerConfigGroupLabelLabelFontSize};\n    }\n  `;\n\n  const LayerConfigGroupLabel: React.FC<LayerConfigGroupLabelProps> = ({label, description}) => (\n    <StyledLayerConfigGroupLabel className=\"layer-config-group__label\">\n      <span>\n        <FormattedMessage id={label || 'misc.empty'} defaultMessage={label} />\n      </span>\n      {description && <InfoHelper description={description} id={label} />}\n    </StyledLayerConfigGroupLabel>\n  );\n\n  return LayerConfigGroupLabel;\n}\n\nLayerConfigGroupFactory.deps = [LayerConfigGroupLabelFactory];\n\nfunction nop() {\n  return;\n}\nfunction LayerConfigGroupFactory(\n  LayerConfigGroupLabel: ReturnType<typeof LayerConfigGroupLabelFactory>\n) {\n  const LayerConfigGroup: React.FC<LayerConfigGroupProps> = ({\n    label,\n    children,\n    property,\n    layer,\n    onChange = nop,\n    collapsible = false,\n    description = '',\n    disabled = false,\n    expanded = false,\n    IconComponent = VertThreeDots\n  }) => {\n    const [collapsed, toggleCollapsed] = useState(!expanded);\n    const onToggleCollapsed = useCallback(() => {\n      collapsible && toggleCollapsed(!collapsed);\n    }, [collapsed, toggleCollapsed, collapsible]);\n\n    return (\n      <StyledLayerConfigGroup className={classnames('layer-config-group', {collapsed, disabled})}>\n        <StyledConfigGroupHeader onClick={onToggleCollapsed} collapsible={collapsible}>\n          <LayerConfigGroupLabel label={label} description={description} collapsed={collapsed} />\n          <StyledLayerConfigGroupAction className=\"layer-config-group__action\">\n            {property ? (\n              <Switch\n                checked={layer?.config.visConfig[property]}\n                id={`${layer?.id}-${property}`}\n                onChange={() => onChange?.({[property]: !layer?.config.visConfig[property]})}\n              />\n            ) : null}\n            {collapsible ? <IconComponent height=\"18px\" /> : null}\n          </StyledLayerConfigGroupAction>\n        </StyledConfigGroupHeader>\n        <ConfigGroupContent\n          className={classnames('layer-config-group__content', {\n            disabled: property && !layer?.config.visConfig[property]\n          })}\n        >\n          {children}\n        </ConfigGroupContent>\n      </StyledLayerConfigGroup>\n    );\n  };\n\n  return LayerConfigGroup;\n}\n\nexport default LayerConfigGroupFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-configurator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable complexity */\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport React, {Component, Fragment} from 'react';\nimport styled from 'styled-components';\n\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport {Input, PanelLabel, SidePanelSection} from '../../common/styled-components';\n\nimport SourceDataSelectorFactory from '../common/source-data-selector';\nimport AggrScaleSelectorFactory from './aggr-scale-selector';\nimport ChannelByValueSelectorFactory from './channel-by-value-selector';\nimport HowToButton from './how-to-button';\nimport {\n  ArcLayerColorSelectorFactory,\n  LayerColorRangeSelectorFactory,\n  LayerColorSelectorFactory\n} from './layer-color-selector';\nimport LayerColumnModeConfigFactory from './layer-column-mode-config';\nimport LayerConfigGroupFactory, {ConfigGroupCollapsibleContent} from './layer-config-group';\nimport LayerErrorMessage from './layer-error-message';\nimport LayerTypeSelectorFactory from './layer-type-selector';\nimport TextLabelPanelFactory from './text-label-panel';\nimport VisConfigSliderFactory from './vis-config-slider';\nimport VisConfigSwitchFactory from './vis-config-switch';\n\nimport RasterTileLayerConfiguratorFactory from './raster-tile-layer-configurator';\nimport VectorTileLayerConfiguratorFactory from './vector-tile-layer-configurator';\n\nimport {ActionHandler, toggleModal} from '@kepler.gl/actions';\nimport {AGGREGATION_TYPE_OPTIONS, LAYER_TYPES} from '@kepler.gl/constants';\nimport {AggregationLayer, Layer, LayerBaseConfig, VisualChannel} from '@kepler.gl/layers';\n\nimport {matchDatasetType, Datasets} from '@kepler.gl/table';\nimport {ColorUI, LayerVisConfig, NestedPartial} from '@kepler.gl/types';\nimport {capitalizeFirstLetter} from '@kepler.gl/utils';\n\ntype LayerConfiguratorProps = {\n  layer: Layer;\n  datasets: Datasets;\n  layerTypeOptions: {\n    id: string;\n    label: string;\n    icon: React.ElementType;\n    requireData: boolean;\n  }[];\n  openModal: ActionHandler<typeof toggleModal>;\n  updateLayerConfig: (newConfig: Partial<LayerBaseConfig>) => void;\n  updateLayerType: (newType: string) => void;\n  updateLayerVisConfig: (newVisConfig: Partial<LayerVisConfig>) => void;\n  updateLayerVisualChannelConfig: (\n    newConfig: Partial<LayerBaseConfig>,\n    channel: string,\n    newVisConfig?: Partial<LayerVisConfig>\n  ) => void;\n  updateLayerColorUI: (prop: string, newConfig: NestedPartial<ColorUI>) => void;\n  updateLayerTextLabel: (idx: number | 'all', prop: string, value: any) => void;\n  disableTypeSelect?: boolean;\n};\n\ntype AggregationSelectorProps = {\n  channel: VisualChannel;\n  layer: AggregationLayer;\n  onChange: (\n    val: Record<\n      string,\n      string | number | boolean | object | readonly (string | number | boolean | object)[] | null\n    >,\n    key: string\n  ) => void;\n};\n\nconst StyledLayerConfigurator = styled.div.attrs({\n  className: 'layer-panel__config'\n})`\n  position: relative;\n  margin-top: ${props => props.theme.layerConfiguratorMargin};\n  padding: ${props => props.theme.layerConfiguratorPadding};\n  border-left: ${props => props.theme.layerConfiguratorBorder} dashed\n    ${props => props.theme.layerConfiguratorBorderColor};\n`;\n\nconst StyledLayerVisualConfigurator = styled.div.attrs({\n  className: 'layer-panel__config__visualC-config'\n})`\n  margin-top: 12px;\n`;\n\nexport const getLayerFields = (datasets: Datasets, layer: Layer) =>\n  datasets[layer.config?.dataId || ''] ? datasets[layer.config.dataId].fields : [];\n\n/** Return any to be able to customize the Dataset entity */\nexport const getLayerDataset = (datasets: Datasets, layer: Layer): any =>\n  datasets[layer.config?.dataId || ''];\n\nexport const getLayerConfiguratorProps = (props: LayerConfiguratorProps) => ({\n  layer: props.layer,\n  fields: getLayerFields(props.datasets, props.layer),\n  onChange: props.updateLayerConfig,\n  setColorUI: props.updateLayerColorUI\n});\n\nexport const getVisConfiguratorProps = (props: LayerConfiguratorProps) => ({\n  layer: props.layer,\n  fields: getLayerFields(props.datasets, props.layer),\n  onChange: props.updateLayerVisConfig,\n  setColorUI: props.updateLayerColorUI\n});\n\nexport const getLayerChannelConfigProps = (props: LayerConfiguratorProps) => ({\n  layer: props.layer,\n  dataset: getLayerDataset(props.datasets, props.layer),\n  fields: getLayerFields(props.datasets, props.layer),\n  onChange: props.updateLayerVisualChannelConfig,\n  setColorUI: props.updateLayerColorUI\n});\n\nLayerConfiguratorFactory.deps = [\n  SourceDataSelectorFactory,\n  VisConfigSliderFactory,\n  TextLabelPanelFactory,\n  LayerConfigGroupFactory,\n  ChannelByValueSelectorFactory,\n  LayerColumnModeConfigFactory,\n  LayerTypeSelectorFactory,\n  VisConfigSwitchFactory,\n  LayerColorSelectorFactory,\n  LayerColorRangeSelectorFactory,\n  ArcLayerColorSelectorFactory,\n  AggrScaleSelectorFactory,\n  VectorTileLayerConfiguratorFactory,\n  RasterTileLayerConfiguratorFactory\n];\n\nexport default function LayerConfiguratorFactory(\n  SourceDataSelector: ReturnType<typeof SourceDataSelectorFactory>,\n  VisConfigSlider: ReturnType<typeof VisConfigSliderFactory>,\n  TextLabelPanel: ReturnType<typeof TextLabelPanelFactory>,\n  LayerConfigGroup: ReturnType<typeof LayerConfigGroupFactory>,\n  ChannelByValueSelector: ReturnType<typeof ChannelByValueSelectorFactory>,\n  LayerColumnModeConfig: ReturnType<typeof LayerColumnModeConfigFactory>,\n  LayerTypeSelector: ReturnType<typeof LayerTypeSelectorFactory>,\n  VisConfigSwitch: ReturnType<typeof VisConfigSwitchFactory>,\n  LayerColorSelector: ReturnType<typeof LayerColorSelectorFactory>,\n  LayerColorRangeSelector: ReturnType<typeof LayerColorRangeSelectorFactory>,\n  ArcLayerColorSelector: ReturnType<typeof ArcLayerColorSelectorFactory>,\n  AggrScaleSelector: ReturnType<typeof AggrScaleSelectorFactory>,\n  VectorTileLayerConfigurator: ReturnType<typeof VectorTileLayerConfiguratorFactory>,\n  RasterTileLayerConfigurator: ReturnType<typeof RasterTileLayerConfiguratorFactory>\n): React.ComponentType<LayerConfiguratorProps> {\n  class LayerConfigurator extends Component<LayerConfiguratorProps> {\n    _renderPointLayerConfig(props) {\n      return this._renderScatterplotLayerConfig(props, true);\n    }\n\n    _renderIconLayerConfig(props) {\n      return this._renderScatterplotLayerConfig(props, false);\n    }\n\n    _renderVectorTileLayerConfig(props) {\n      return <VectorTileLayerConfigurator {...props} />;\n    }\n\n    _renderRasterTileLayerConfig(props) {\n      return <RasterTileLayerConfigurator {...props} />;\n    }\n\n    _renderScatterplotLayerConfig(\n      {layer, visConfiguratorProps, layerChannelConfigProps, layerConfiguratorProps},\n      showInteractionControls\n    ) {\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Fill Color */}\n          <LayerConfigGroup\n            {...(layer.visConfigSettings.filled || {label: 'layer.color'})}\n            {...visConfiguratorProps}\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.colorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} />\n            ) : (\n              <LayerColorSelector {...layerConfiguratorProps} />\n            )}\n\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* outline color */}\n          {layer.type === LAYER_TYPES.point ? (\n            <LayerConfigGroup\n              {...layer.visConfigSettings.outline}\n              {...visConfiguratorProps}\n              collapsible\n            >\n              <ChannelByValueSelector\n                channel={layer.visualChannels.strokeColor}\n                {...layerChannelConfigProps}\n              />\n              {layer.config.strokeColorField ? (\n                <LayerColorRangeSelector {...visConfiguratorProps} property=\"strokeColorRange\" />\n              ) : (\n                <LayerColorSelector\n                  {...visConfiguratorProps}\n                  selectedColor={layer.config.visConfig.strokeColor}\n                  property=\"strokeColor\"\n                />\n              )}\n              <ConfigGroupCollapsibleContent>\n                <VisConfigSlider\n                  {...layer.visConfigSettings.thickness}\n                  {...visConfiguratorProps}\n                  disabled={!layer.config.visConfig.outline}\n                />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n\n          {/* Radius */}\n          <LayerConfigGroup label={'layer.radius'} collapsible>\n            {!layer.config.sizeField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.radius}\n                {...visConfiguratorProps}\n                label={false}\n                disabled={Boolean(layer.config.sizeField)}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.radiusRange}\n                {...visConfiguratorProps}\n                label={false}\n                disabled={!layer.config.sizeField || layer.config.visConfig.fixedRadius}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <ChannelByValueSelector\n                channel={layer.visualChannels.size}\n                {...layerChannelConfigProps}\n              />\n              {layer.config.sizeField ? (\n                <VisConfigSwitch\n                  {...layer.visConfigSettings.fixedRadius}\n                  {...visConfiguratorProps}\n                />\n              ) : null}\n              <VisConfigSwitch {...layer.visConfigSettings.billboard} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* text label */}\n          <TextLabelPanel\n            id={layer.id}\n            fields={visConfiguratorProps.fields}\n            updateLayerTextLabel={this.props.updateLayerTextLabel}\n            textLabel={layer.config.textLabel}\n          />\n\n          {/* Interaction */}\n          {showInteractionControls ? (\n            <LayerConfigGroup label={'layer.interaction'} collapsible>\n              <VisConfigSwitch {...layer.visConfigSettings.allowHover} {...visConfiguratorProps} />\n              <ConfigGroupCollapsibleContent>\n                <VisConfigSwitch\n                  {...layer.visConfigSettings.showNeighborOnHover}\n                  {...visConfiguratorProps}\n                />\n                <VisConfigSwitch\n                  {...layer.visConfigSettings.showHighlightColor}\n                  {...visConfiguratorProps}\n                />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderClusterLayerConfig({layer, visConfiguratorProps, layerChannelConfigProps}) {\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup label={'layer.color'} collapsible>\n            <LayerColorRangeSelector {...visConfiguratorProps} />\n            <AggrScaleSelector {...layerChannelConfigProps} channel={layer.visualChannels.color} />\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            <ConfigGroupCollapsibleContent>\n              {layer.visConfigSettings.colorAggregation.condition(layer.config) ? (\n                <AggregationTypeSelector\n                  {...layer.visConfigSettings.colorAggregation}\n                  {...layerChannelConfigProps}\n                  channel={layer.visualChannels.color}\n                />\n              ) : null}\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Cluster Radius */}\n          <LayerConfigGroup label={'layer.radius'} collapsible>\n            <VisConfigSlider {...layer.visConfigSettings.clusterRadius} {...visConfiguratorProps} />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.radiusRange} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderHeatmapLayerConfig({layer, visConfiguratorProps, layerChannelConfigProps}) {\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup label={'layer.color'} collapsible>\n            <LayerColorRangeSelector {...visConfiguratorProps} />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n          {/* Radius */}\n          <LayerConfigGroup label={'layer.radius'}>\n            <VisConfigSlider\n              {...layer.visConfigSettings.radius}\n              {...visConfiguratorProps}\n              label={false}\n            />\n          </LayerConfigGroup>\n          {/* Weight */}\n          <LayerConfigGroup label={'layer.weight'}>\n            <ChannelByValueSelector\n              channel={layer.visualChannels.weight}\n              {...layerChannelConfigProps}\n            />\n          </LayerConfigGroup>\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderGridLayerConfig(props) {\n      return this._renderAggregationLayerConfig(props);\n    }\n\n    _renderHexagonLayerConfig(props) {\n      return this._renderAggregationLayerConfig(props);\n    }\n\n    _renderAggregationLayerConfig({layer, visConfiguratorProps, layerChannelConfigProps}) {\n      const {config} = layer;\n      const {\n        visConfig: {enable3d, fixedHeight}\n      } = config;\n      const elevationByDescription = 'layer.elevationByDescription';\n      const colorByDescription = 'layer.colorByDescription';\n\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup label={'layer.color'} collapsible>\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            <AggrScaleSelector {...layerChannelConfigProps} channel={layer.visualChannels.color} />\n            <LayerColorRangeSelector {...visConfiguratorProps} />\n\n            <ConfigGroupCollapsibleContent>\n              {layer.visConfigSettings.colorAggregation.condition(layer.config) ? (\n                <AggregationTypeSelector\n                  {...layer.visConfigSettings.colorAggregation}\n                  {...layerChannelConfigProps}\n                  description={colorByDescription}\n                  channel={layer.visualChannels.color}\n                />\n              ) : null}\n              {layer.visConfigSettings.percentile &&\n              layer.visConfigSettings.percentile.condition(layer.config) ? (\n                <VisConfigSlider\n                  {...layer.visConfigSettings.percentile}\n                  {...visConfiguratorProps}\n                />\n              ) : null}\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Cell size */}\n          <LayerConfigGroup label={'layer.radius'} collapsible>\n            <VisConfigSlider {...layer.visConfigSettings.worldUnitSize} {...visConfiguratorProps} />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.coverage} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Elevation */}\n          {layer.visConfigSettings.enable3d ? (\n            <LayerConfigGroup\n              {...layer.visConfigSettings.enable3d}\n              {...visConfiguratorProps}\n              collapsible\n            >\n              <VisConfigSlider\n                {...layer.visConfigSettings.elevationScale}\n                {...visConfiguratorProps}\n                label={'layerVisConfigs.heightMultiplier'}\n              />\n              <ConfigGroupCollapsibleContent>\n                <ChannelByValueSelector\n                  {...layerChannelConfigProps}\n                  channel={layer.visualChannels.size}\n                  description={elevationByDescription}\n                  disabled={!enable3d}\n                />\n                {layer.visConfigSettings.sizeAggregation.condition(layer.config) ? (\n                  <AggregationTypeSelector\n                    {...layer.visConfigSettings.sizeAggregation}\n                    {...layerChannelConfigProps}\n                    channel={layer.visualChannels.size}\n                  />\n                ) : null}\n                <AggrScaleSelector\n                  {...layerChannelConfigProps}\n                  channel={layer.visualChannels.size}\n                  label={'Height Scale'}\n                />\n                <VisConfigSlider\n                  {...layer.visConfigSettings.sizeRange}\n                  {...visConfiguratorProps}\n                  label={'layerVisConfigs.heightRange'}\n                  disabled={fixedHeight}\n                />\n                <VisConfigSwitch\n                  {...layer.visConfigSettings.fixedHeight}\n                  {...visConfiguratorProps}\n                />\n                {layer.visConfigSettings.elevationPercentile.condition(layer.config) ? (\n                  <VisConfigSlider\n                    {...layer.visConfigSettings.elevationPercentile}\n                    {...visConfiguratorProps}\n                  />\n                ) : null}\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    // TODO: Shan move these into layer class\n    _renderHexagonIdLayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerConfiguratorProps,\n      layerChannelConfigProps\n    }) {\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Fill */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.filled}\n            {...visConfiguratorProps}\n            label={'layer.fillColor'}\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.colorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} />\n            ) : (\n              <LayerColorSelector {...layerConfiguratorProps} />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider\n                {...layer.visConfigSettings.opacity}\n                {...visConfiguratorProps}\n                disabled={!layer.config.visConfig.filled}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Outline */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.outline}\n            {...visConfiguratorProps}\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.strokeColor}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.strokeColorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} property=\"strokeColorRange\" />\n            ) : (\n              <LayerColorSelector\n                {...visConfiguratorProps}\n                selectedColor={layer.config.visConfig.strokeColor}\n                property=\"strokeColor\"\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider\n                {...layer.visConfigSettings.strokeOpacity}\n                {...visConfiguratorProps}\n                disabled={!layer.config.visConfig.outline}\n              />\n              <VisConfigSlider\n                {...layer.visConfigSettings.thickness}\n                {...visConfiguratorProps}\n                disabled={!layer.config.visConfig.outline}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Coverage */}\n          <LayerConfigGroup label={'layer.coverage'} collapsible>\n            {!layer.config.coverageField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.coverage}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.coverageRange}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <ChannelByValueSelector\n                channel={layer.visualChannels.coverage}\n                {...layerChannelConfigProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* height */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.enable3d}\n            {...visConfiguratorProps}\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.size}\n              {...layerChannelConfigProps}\n            />\n            <VisConfigSlider\n              {...layer.visConfigSettings.elevationScale}\n              {...visConfiguratorProps}\n              label={'layerVisConfigs.heightMultiplier'}\n            />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider\n                {...layer.visConfigSettings.sizeRange}\n                {...visConfiguratorProps}\n                label=\"layerVisConfigs.heightRange\"\n              />\n              <VisConfigSwitch {...layer.visConfigSettings.fixedHeight} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* text label */}\n          <TextLabelPanel\n            id={layer.id}\n            fields={visConfiguratorProps.fields}\n            updateLayerTextLabel={this.props.updateLayerTextLabel}\n            textLabel={layer.config.textLabel}\n          />\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderArcLayerConfig(args) {\n      return this._renderLineLayerConfig(args);\n    }\n\n    _renderLineLayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerConfiguratorProps,\n      layerChannelConfigProps\n    }) {\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup label={'layer.color'} collapsible>\n            <ChannelByValueSelector\n              channel={layer.visualChannels.sourceColor}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.colorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} />\n            ) : (\n              <ArcLayerColorSelector\n                layer={layer}\n                setColorUI={layerConfiguratorProps.setColorUI}\n                onChangeConfig={layerConfiguratorProps.onChange}\n                onChangeVisConfig={visConfiguratorProps.onChange}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* thickness */}\n          <LayerConfigGroup label={'layer.stroke'} collapsible>\n            {layer.config.sizeField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.sizeRange}\n                {...visConfiguratorProps}\n                disabled={!layer.config.sizeField}\n                label={false}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.thickness}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <ChannelByValueSelector\n                channel={layer.visualChannels.size}\n                {...layerChannelConfigProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* elevation scale */}\n          {layer.visConfigSettings.elevationScale ? (\n            <LayerConfigGroup label=\"layerVisConfigs.elevationScale\" collapsible>\n              <VisConfigSlider\n                {...layer.visConfigSettings.elevationScale}\n                {...visConfiguratorProps}\n              />\n            </LayerConfigGroup>\n          ) : null}\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderTripLayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerConfiguratorProps,\n      layerChannelConfigProps\n    }) {\n      const {\n        meta: {featureTypes = {}}\n      } = layer;\n\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup label={'layer.color'} collapsible>\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.colorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} />\n            ) : (\n              <LayerColorSelector {...layerConfiguratorProps} />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Stroke Width */}\n          <LayerConfigGroup {...visConfiguratorProps} label=\"layer.strokeWidth\" collapsible>\n            {layer.config.sizeField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.sizeRange}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.thickness}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            )}\n\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSwitch {...layer.visConfigSettings.billboard} {...visConfiguratorProps} />\n              <ChannelByValueSelector\n                channel={layer.visualChannels.size}\n                {...layerChannelConfigProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Trail Length*/}\n          <LayerConfigGroup\n            {...visConfiguratorProps}\n            {...(featureTypes.polygon ? layer.visConfigSettings.stroked : {})}\n            label=\"layer.trailLength\"\n            description=\"layer.trailLengthDescription\"\n            collapsible\n          >\n            <VisConfigSlider\n              {...layer.visConfigSettings.trailLength}\n              {...visConfiguratorProps}\n              label={false}\n            />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSwitch {...layer.visConfigSettings.fadeTrail} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderGeojsonLayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerConfiguratorProps,\n      layerChannelConfigProps\n    }) {\n      const {\n        meta: {featureTypes = {}},\n        config: {visConfig}\n      } = layer;\n\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Fill Color */}\n          {featureTypes.polygon || featureTypes.point ? (\n            <LayerConfigGroup\n              {...layer.visConfigSettings.filled}\n              {...visConfiguratorProps}\n              label=\"layer.fillColor\"\n              collapsible\n            >\n              <ChannelByValueSelector\n                channel={layer.visualChannels.color}\n                {...layerChannelConfigProps}\n              />\n              {layer.config.colorField ? (\n                <LayerColorRangeSelector {...visConfiguratorProps} />\n              ) : (\n                <LayerColorSelector {...layerConfiguratorProps} />\n              )}\n              <ConfigGroupCollapsibleContent>\n                <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n\n          {/* stroke color */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.stroked}\n            {...visConfiguratorProps}\n            label=\"layer.strokeColor\"\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.strokeColor}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.strokeColorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} property=\"strokeColorRange\" />\n            ) : (\n              <LayerColorSelector\n                {...visConfiguratorProps}\n                selectedColor={layer.config.visConfig.strokeColor}\n                property=\"strokeColor\"\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider\n                {...layer.visConfigSettings.strokeOpacity}\n                {...visConfiguratorProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Stroke Width */}\n          <LayerConfigGroup\n            {...visConfiguratorProps}\n            {...(featureTypes.polygon ? layer.visConfigSettings.stroked : {})}\n            label=\"layer.strokeWidth\"\n            collapsible\n          >\n            {layer.config.sizeField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.sizeRange}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.thickness}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <ChannelByValueSelector\n                channel={layer.visualChannels.size}\n                {...layerChannelConfigProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Elevation */}\n          {featureTypes.polygon ? (\n            <LayerConfigGroup\n              {...visConfiguratorProps}\n              {...layer.visConfigSettings.enable3d}\n              disabled={!visConfig.filled}\n              collapsible\n            >\n              <VisConfigSlider\n                {...layer.visConfigSettings.elevationScale}\n                {...visConfiguratorProps}\n                label={false}\n              />\n              <ConfigGroupCollapsibleContent>\n                <ChannelByValueSelector\n                  channel={layer.visualChannels.height}\n                  {...layerChannelConfigProps}\n                />\n                <VisConfigSwitch\n                  {...layer.visConfigSettings.fixedHeight}\n                  {...visConfiguratorProps}\n                />\n                <VisConfigSwitch {...visConfiguratorProps} {...layer.visConfigSettings.wireframe} />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n\n          {/* Radius */}\n          {featureTypes.point ? (\n            <LayerConfigGroup label={'layer.radius'} collapsible>\n              {!layer.config.radiusField ? (\n                <VisConfigSlider\n                  {...layer.visConfigSettings.radius}\n                  {...visConfiguratorProps}\n                  label={false}\n                  disabled={Boolean(layer.config.radiusField)}\n                />\n              ) : (\n                <VisConfigSlider\n                  {...layer.visConfigSettings.radiusRange}\n                  {...visConfiguratorProps}\n                  label={false}\n                  disabled={!layer.config.radiusField}\n                />\n              )}\n              <ConfigGroupCollapsibleContent>\n                <ChannelByValueSelector\n                  channel={layer.visualChannels.radius}\n                  {...layerChannelConfigProps}\n                />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          ) : null}\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _render3DLayerConfig({layer, visConfiguratorProps}) {\n      return (\n        <Fragment>\n          <LayerConfigGroup label={'layer.3DModel'} collapsible>\n            <Input\n              type=\"file\"\n              accept=\".glb,.gltf\"\n              onChange={e => {\n                if (e.target.files && e.target.files[0]) {\n                  const url = URL.createObjectURL(e.target.files[0]);\n                  visConfiguratorProps.onChange({scenegraph: url});\n                }\n              }}\n            />\n          </LayerConfigGroup>\n          <LayerConfigGroup label={'layer.3DModelOptions'} collapsible>\n            <VisConfigSlider\n              {...layer.visConfigSettings.sizeScale}\n              {...visConfiguratorProps}\n              disabled={false}\n            />\n            <VisConfigSlider\n              {...layer.visConfigSettings.angleX}\n              {...visConfiguratorProps}\n              disabled={false}\n            />\n            <VisConfigSlider\n              {...layer.visConfigSettings.angleY}\n              {...visConfiguratorProps}\n              disabled={false}\n            />\n            <VisConfigSlider\n              {...layer.visConfigSettings.angleZ}\n              {...visConfiguratorProps}\n              disabled={false}\n            />\n          </LayerConfigGroup>\n        </Fragment>\n      );\n    }\n\n    _renderS2LayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerConfiguratorProps,\n      layerChannelConfigProps\n    }) {\n      const {\n        config: {visConfig}\n      } = layer;\n\n      return (\n        <StyledLayerVisualConfigurator>\n          {/* Color */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.filled}\n            {...visConfiguratorProps}\n            label=\"layer.fillColor\"\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.color}\n              {...layerChannelConfigProps}\n            />\n            {layer.config.colorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} />\n            ) : (\n              <LayerColorSelector {...layerConfiguratorProps} />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Stroke */}\n          <LayerConfigGroup\n            {...layer.visConfigSettings.stroked}\n            {...visConfiguratorProps}\n            label=\"layer.strokeColor\"\n            collapsible\n          >\n            {layer.config.strokeColorField ? (\n              <LayerColorRangeSelector {...visConfiguratorProps} property=\"strokeColorRange\" />\n            ) : (\n              <LayerColorSelector\n                {...visConfiguratorProps}\n                selectedColor={layer.config.visConfig.strokeColor}\n                property=\"strokeColor\"\n              />\n            )}\n            <ChannelByValueSelector\n              channel={layer.visualChannels.strokeColor}\n              {...layerChannelConfigProps}\n            />\n          </LayerConfigGroup>\n\n          {/* Stroke Width */}\n          <LayerConfigGroup {...visConfiguratorProps} label=\"layer.strokeWidth\" collapsible>\n            {layer.config.sizeField ? (\n              <VisConfigSlider\n                {...layer.visConfigSettings.sizeRange}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.thickness}\n                {...visConfiguratorProps}\n                label={false}\n              />\n            )}\n            <ConfigGroupCollapsibleContent>\n              <ChannelByValueSelector\n                channel={layer.visualChannels.size}\n                {...layerChannelConfigProps}\n              />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n\n          {/* Elevation */}\n          <LayerConfigGroup\n            {...visConfiguratorProps}\n            {...layer.visConfigSettings.enable3d}\n            disabled={!visConfig.filled}\n            collapsible\n          >\n            <ChannelByValueSelector\n              channel={layer.visualChannels.height}\n              {...layerChannelConfigProps}\n            />\n            <VisConfigSlider\n              {...layer.visConfigSettings.elevationScale}\n              {...visConfiguratorProps}\n              label={'layerVisConfigs.heightMultiplier'}\n            />\n            <ConfigGroupCollapsibleContent>\n              <VisConfigSlider\n                {...layer.visConfigSettings.heightRange}\n                {...visConfiguratorProps}\n                label=\"layerVisConfigs.heightRange\"\n              />\n              <VisConfigSwitch {...layer.visConfigSettings.fixedHeight} {...visConfiguratorProps} />\n              <VisConfigSwitch {...visConfiguratorProps} {...layer.visConfigSettings.wireframe} />\n            </ConfigGroupCollapsibleContent>\n          </LayerConfigGroup>\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    _renderWmsLayerConfig({\n      layer,\n      visConfiguratorProps,\n      layerChannelConfigProps,\n      layerConfiguratorProps\n    }) {\n      return (\n        <StyledLayerVisualConfigurator>\n          <LayerConfigGroup label={'layer.service'}>\n            <PanelLabel>\n              <FormattedMessage id={'layer.layer'} />\n            </PanelLabel>\n            <ItemSelector\n              selectedItems={layer.config.visConfig.wmsLayer}\n              options={layerChannelConfigProps.dataset.metadata.layers}\n              displayOption=\"title\"\n              getOptionValue=\"name\"\n              multiSelect={false}\n              searchable={false}\n              onChange={value => {\n                if (!value) {\n                  return;\n                }\n                const selectedLayer = layerChannelConfigProps.dataset.metadata.layers.find(\n                  l => l.name === value\n                );\n                if (!selectedLayer) {\n                  return;\n                }\n\n                layerConfiguratorProps.onChange({\n                  visConfig: {\n                    ...layer.config.visConfig,\n                    wmsLayer: selectedLayer\n                  }\n                });\n              }}\n            />\n          </LayerConfigGroup>\n          <LayerConfigGroup label={'layer.appearance'}>\n            <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n            <VisConfigSwitch\n              label={'layerVisConfigs.transparentBackground'}\n              {...layer.visConfigSettings.transparent}\n              {...visConfiguratorProps}\n              property=\"transparent\"\n            />\n          </LayerConfigGroup>\n        </StyledLayerVisualConfigurator>\n      );\n    }\n\n    handleSelectColumnMode = (key: string) => {\n      const {updateLayerConfig} = this.props;\n      updateLayerConfig({columnMode: key});\n    };\n\n    render() {\n      const {\n        layer,\n        datasets,\n        openModal,\n        updateLayerConfig,\n        layerTypeOptions,\n        updateLayerType,\n        disableTypeSelect = false\n      } = this.props;\n      const {fields = [], fieldPairs = undefined} = layer.config.dataId\n        ? datasets[layer.config.dataId]\n        : {};\n      const {config} = layer;\n\n      const visConfiguratorProps = getVisConfiguratorProps(this.props);\n      const layerConfiguratorProps = getLayerConfiguratorProps(this.props);\n      const layerChannelConfigProps = getLayerChannelConfigProps(this.props);\n      const dataset = getLayerDataset(datasets, layer);\n      const renderTemplate = layer.type && `_render${capitalizeFirstLetter(layer.type)}LayerConfig`;\n\n      // show only datasets that can be used by the layer\n      const sourceDataSelectorOptions = Object.keys(datasets).reduce(\n        (acc, id) => (matchDatasetType(datasets[id], layer) ? {...acc, [id]: datasets[id]} : acc),\n        {}\n      );\n\n      return (\n        <StyledLayerConfigurator>\n          {layer.layerInfoModal && !layer.supportedColumnModes ? (\n            // TODO figure out handler type. String or return type of layer.layerInfoModal ?\n            <HowToButton onClick={() => openModal(layer.layerInfoModal as any)} />\n          ) : null}\n          <LayerConfigGroup label={'layer.basic'} collapsible expanded={!layer.hasAllColumns()}>\n            <LayerTypeSelector\n              selected={layer.type}\n              disabled={disableTypeSelect}\n              options={layerTypeOptions}\n              // @ts-ignore\n              onSelect={updateLayerType}\n            />\n            <ConfigGroupCollapsibleContent>\n              <SourceDataSelector\n                datasets={sourceDataSelectorOptions}\n                id={layer.id}\n                dataId={config.dataId}\n                // @ts-ignore\n                onSelect={(value: string) => updateLayerConfig({dataId: value})}\n              />\n              <LayerColumnModeConfig\n                layer={layer}\n                supportedColumnModes={layer.supportedColumnModes}\n                id={layer.id}\n                layerConfig={layer.config}\n                // TODO figure out handler type. String or return type of layer.layerInfoModal ?\n                openModal={openModal as any}\n                updateLayerConfig={updateLayerConfig}\n                updateLayerType={updateLayerType}\n                fields={fields}\n                fieldPairs={fieldPairs}\n              />\n            </ConfigGroupCollapsibleContent>\n            {layer.errorMessage ? <LayerErrorMessage errorMessage={layer.errorMessage} /> : null}\n          </LayerConfigGroup>\n          {renderTemplate &&\n            this[renderTemplate] &&\n            this[renderTemplate]({\n              layer,\n              dataset,\n              visConfiguratorProps,\n              layerChannelConfigProps,\n              layerConfiguratorProps\n            })}\n        </StyledLayerConfigurator>\n      );\n    }\n  }\n\n  return LayerConfigurator;\n}\n\nexport const AggregationTypeSelector = ({channel, layer, onChange}: AggregationSelectorProps) => {\n  const {field, aggregation, key} = channel;\n  const selectedField = layer.config[field];\n  const {visConfig} = layer.config;\n\n  // aggregation should only be selectable when field is selected\n  const layerAggregationTypes = layer.getAggregationOptions(key);\n\n  const aggregationOptions = AGGREGATION_TYPE_OPTIONS.filter(({id}) =>\n    layerAggregationTypes.includes(id)\n  );\n\n  const selectedAggregation = aggregation\n    ? aggregationOptions.find(({id}) => id === visConfig[aggregation])\n    : [];\n\n  return (\n    <SidePanelSection>\n      <PanelLabel>\n        <FormattedMessage\n          id={'layer.aggregateBy'}\n          values={{\n            field: selectedField.displayName\n          }}\n        />\n      </PanelLabel>\n      <ItemSelector\n        selectedItems={selectedAggregation}\n        options={aggregationOptions}\n        displayOption=\"label\"\n        getOptionValue=\"id\"\n        multiSelect={false}\n        searchable={false}\n        onChange={value =>\n          onChange(\n            {\n              visConfig: {\n                ...layer.config.visConfig,\n                [aggregation as string]: value\n              }\n            },\n            channel.key\n          )\n        }\n      />\n    </SidePanelSection>\n  );\n};\n/* eslint-enable max-params */\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-error-message.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nconst LayerErrorMessageContent = styled.div.attrs({\n  className: 'layer-error-message-content'\n})`\n  background-color: ${props => props.theme.notificationColors.error || '#000'};\n  color: #fff;\n  display: block;\n  width: 100%;\n  font-size: 11px;\n  padding: 1em;\n  border-radius: 4px;\n`;\n\ntype LayerErrorMessageProps = {\n  errorMessage: string;\n};\n\nconst LayerErrorMessage: React.FC<LayerErrorMessageProps> = ({errorMessage}) => {\n  return (\n    <LayerErrorMessageContent>\n      <FormattedMessage id={'layer.layerUpdateError'} values={{errorMessage}} />\n    </LayerErrorMessageContent>\n  );\n};\n\nexport default LayerErrorMessage;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\n\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {Datasets} from '@kepler.gl/table';\nimport {UIStateActions, VisStateActions, MapStateActions} from '@kepler.gl/actions';\n\nimport {useSortable, SortableContext, verticalListSortingStrategy} from '@dnd-kit/sortable';\nimport {CSS} from '@dnd-kit/utilities';\nimport LayerPanelFactory from './layer-panel';\nimport {findById} from '@kepler.gl/utils';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {SplitMap} from '@kepler.gl/types';\nimport {SORTABLE_LAYER_TYPE, SORTABLE_SIDE_PANEL_TYPE} from '../../common/dnd-layer-items';\n\nexport type LayerListProps = {\n  datasets: Datasets;\n  layers: Layer[];\n  layerOrder: string[];\n  layerClasses: LayerClassesType;\n  isSortable?: boolean;\n  splitMap?: SplitMap;\n  uiStateActions: typeof UIStateActions;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n};\n\nexport type LayerListFactoryDeps = [typeof LayerPanelFactory];\n\n// make sure the element is always visible while is being dragged\n// item being dragged is appended in body, here to reset its global style\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n`;\n\ninterface SortableStyledItemProps {\n  $transition?: string;\n  $transform?: string;\n}\n\nconst SortableStyledItem = styled.div<SortableStyledItemProps>`\n  transition: ${props => props.$transition};\n  transform: ${props => props.$transform};\n  &.sorting {\n    opacity: 0.3;\n    pointer-events: none;\n  }\n  &.sorting-layers .layer-panel__header {\n    background-color: ${props => props.theme.panelBackgroundHover};\n    font-family: ${props => props.theme.fontFamily};\n    font-weight: ${props => props.theme.fontWeight};\n    font-size: ${props => props.theme.fontSize};\n    line-height: ${props => props.theme.lineHeight};\n    *,\n    *:before,\n    *:after {\n      box-sizing: border-box;\n    }\n    .layer__drag-handle {\n      opacity: 1;\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst INITIAL_LAYERS_TO_SHOW: Layer[] = [];\n\nLayerListFactory.deps = [LayerPanelFactory];\n\nfunction LayerListFactory(LayerPanel: ReturnType<typeof LayerPanelFactory>) {\n  // By wrapping layer panel using a sortable element we don't have to implement the drag and drop logic into the panel itself;\n  // Developers can provide any layer panel implementation and it will still be sortable\n  const SortableItem = ({layer, idx, panelProps, layerActions, disabled}) => {\n    const {attributes, listeners, setNodeRef, isDragging, transform, transition} = useSortable({\n      id: layer.id,\n      data: {\n        type: SORTABLE_LAYER_TYPE,\n        parent: SORTABLE_SIDE_PANEL_TYPE\n      },\n      disabled\n    });\n\n    return (\n      <SortableStyledItem\n        ref={setNodeRef}\n        className={classnames(\n          {[dataTestIds.sortableLayerItem]: !disabled},\n          {[dataTestIds.staticLayerItem]: disabled},\n          {sorting: isDragging}\n        )}\n        data-testid={disabled ? dataTestIds.staticLayerItem : dataTestIds.sortableLayerItem}\n        $transform={CSS.Transform.toString(transform)}\n        $transition={transition}\n        {...attributes}\n      >\n        <LayerPanel\n          {...panelProps}\n          {...layerActions}\n          key={layer.id}\n          idx={idx}\n          layer={layer}\n          listeners={listeners}\n          isDraggable={!disabled}\n        />\n      </SortableStyledItem>\n    );\n  };\n\n  const LayerList: React.FC<LayerListProps> = props => {\n    const {\n      layers,\n      datasets,\n      layerOrder,\n      uiStateActions,\n      visStateActions,\n      mapStateActions,\n      layerClasses,\n      isSortable = true,\n      splitMap\n    } = props;\n    const {toggleModal: openModal} = uiStateActions;\n\n    const layersToShow = useMemo(() => {\n      return layerOrder.reduce((acc, layerId) => {\n        const layer = findById(layerId)(layers.filter(Boolean));\n        if (!layer) {\n          return acc;\n        }\n        return !layer.config.hidden ? [...acc, layer] : acc;\n      }, INITIAL_LAYERS_TO_SHOW);\n    }, [layers, layerOrder]);\n\n    const sidePanelDndItems = useMemo(() => {\n      return layersToShow.map(({id}) => id);\n    }, [layersToShow]);\n\n    const layerTypeOptions = useMemo(\n      () =>\n        Object.keys(layerClasses).map(key => {\n          const layer = new layerClasses[key]({dataId: ''});\n          return {\n            id: key,\n            label: layer.name,\n            icon: layer.layerIcon,\n            requireData: layer.requireData\n          };\n        }),\n      [layerClasses]\n    );\n\n    const layerActions = useMemo(\n      () => ({\n        layerColorUIChange: visStateActions.layerColorUIChange,\n        layerConfigChange: visStateActions.layerConfigChange,\n        layerVisualChannelConfigChange: visStateActions.layerVisualChannelConfigChange,\n        layerToggleVisibility: visStateActions.layerToggleVisibility,\n        layerTypeChange: visStateActions.layerTypeChange,\n        layerVisConfigChange: visStateActions.layerVisConfigChange,\n        layerTextLabelChange: visStateActions.layerTextLabelChange,\n        removeLayer: visStateActions.removeLayer,\n        zoomToLayer: mapStateActions.fitBounds,\n        duplicateLayer: visStateActions.duplicateLayer,\n        layerSetIsValid: visStateActions.layerSetIsValid\n      }),\n      [visStateActions, mapStateActions]\n    );\n\n    const panelProps = useMemo(\n      () => ({\n        datasets,\n        openModal,\n        layerTypeOptions,\n        splitMap\n      }),\n      [datasets, openModal, layerTypeOptions, splitMap]\n    );\n\n    return (\n      <Container>\n        <SortableContext\n          id={SORTABLE_SIDE_PANEL_TYPE}\n          items={sidePanelDndItems}\n          strategy={verticalListSortingStrategy}\n          disabled={!isSortable}\n        >\n          {/* warning: containerId should be similar to the first key in dndItems defined in kepler-gl.js*/}\n          {layersToShow.map(layer => (\n            <SortableItem\n              key={layer.id}\n              layer={layer}\n              idx={layers.findIndex(l => l?.id === layer.id)}\n              panelProps={panelProps}\n              layerActions={layerActions}\n              disabled={!isSortable}\n            />\n          ))}\n        </SortableContext>\n      </Container>\n    );\n  };\n  return LayerList;\n}\nexport default LayerListFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-panel-header.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {\n  useState,\n  ComponentType,\n  MouseEventHandler,\n  MouseEvent,\n  ChangeEventHandler\n} from 'react';\nimport classnames from 'classnames';\nimport styled, {css} from 'styled-components';\nimport PanelHeaderActionFactory from '../panel-header-action';\nimport {Tooltip, shouldForwardProp} from '../../common/styled-components';\nimport {\n  Copy,\n  ArrowDown,\n  EyeSeen,\n  EyeUnseen,\n  Trash,\n  VertDots,\n  WarningSign,\n  Reset,\n  ZoomIn\n} from '../../common/icons';\n\nimport {InlineInput, StyledPanelHeader} from '../../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {RGBColor} from '@kepler.gl/types';\nimport {BaseProps} from '../../common/icons';\n\nexport type LayerLabelEditorProps = {\n  layerId: string;\n  label?: string;\n  onEdit: ChangeEventHandler;\n  onFocus: ChangeEventHandler;\n  onBlur: ChangeEventHandler;\n};\n\nexport type LayerTitleSectionProps = {\n  layerType?: string | null;\n  layerId: string;\n  label?: string;\n  onUpdateLayerLabel: ChangeEventHandler;\n  onFocus: ChangeEventHandler;\n  onBlur: ChangeEventHandler;\n};\n\nexport type LayerPanelHeaderProps = {\n  layerId: string;\n  isVisible: boolean;\n  isValid: boolean;\n  onToggleVisibility: MouseEventHandler;\n  onUpdateLayerLabel: ChangeEventHandler;\n  onToggleEnableConfig: MouseEventHandler;\n  onRemoveLayer: MouseEventHandler;\n  onZoomToLayer: MouseEventHandler;\n  onDuplicateLayer: MouseEventHandler;\n  onResetIsValid: MouseEventHandler;\n  isConfigActive: boolean;\n  showRemoveLayer?: boolean;\n  label?: string;\n  layerType?: string | null;\n  allowDuplicate?: boolean;\n  isDragNDropEnabled?: boolean;\n  warning?: boolean;\n  labelRCGColorValues?: RGBColor | null;\n  actionIcons?: {\n    remove: ComponentType<Partial<BaseProps>>;\n    visible: ComponentType<Partial<BaseProps>>;\n    hidden: ComponentType<Partial<BaseProps>>;\n    enableConfig: ComponentType<Partial<BaseProps>>;\n    disableConfig?: ComponentType<Partial<BaseProps>>;\n    resetIsValid: ComponentType<Partial<BaseProps>>;\n    duplicate: ComponentType<Partial<BaseProps>>;\n    crosshairs: ComponentType<Partial<BaseProps>>;\n  };\n  listeners?: React.ElementType;\n};\n\ntype HeaderActionSectionProps = {\n  isEditingLabel: boolean;\n};\n\nexport type LayerPanelHeaderActionSectionProps = LayerPanelHeaderProps & HeaderActionSectionProps;\n\nconst getBorderCss = status => css`\n  border-top: 2px solid ${({theme}) => theme.notificationColors[status]};\n  border-bottom: 2px solid ${({theme}) => theme.notificationColors[status]};\n  border-right: 2px solid ${({theme}) => theme.notificationColors[status]};\n`;\n\nconst StyledLayerPanelHeader = styled(StyledPanelHeader)`\n  height: ${props => props.theme.layerPanelHeaderHeight}px;\n  position: relative;\n  align-items: stretch;\n\n  .layer__remove-layer {\n    opacity: 0;\n  }\n\n  .layer__drag-handle__placeholder {\n    height: 20px;\n    padding: 10px;\n  }\n\n  ${props => (props.warning ? getBorderCss('warning') : props.isValid ? '' : getBorderCss('error'))}\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.panelBackgroundHover};\n\n    .layer__drag-handle {\n      opacity: 1;\n    }\n\n    .layer__remove-layer {\n      opacity: 1;\n    }\n  }\n`;\n\nconst HeaderLabelSection = styled.div`\n  display: flex;\n  color: ${props => props.theme.textColor};\n  flex-grow: 1;\n  align-items: stretch;\n  // leave space for eye and collapse icon\n  padding-right: 50px;\n`;\n\nconst HeaderActionSection = styled.div.withConfig({shouldForwardProp})<HeaderActionSectionProps>`\n  display: flex;\n  position: absolute;\n  height: 100%;\n  align-items: stretch;\n  right: 10px;\n  pointer-events: ${props => (props.isEditingLabel ? 'none' : 'all')};\n  &:hover {\n    .layer-panel__header__actions__hidden {\n      opacity: 1;\n      background-color: ${props => props.theme.panelBackgroundHover};\n    }\n  }\n`;\n\ntype StyledPanelHeaderHiddenActionsProps = {\n  isConfigActive: LayerPanelHeaderProps['isConfigActive'];\n};\n\n// Hiden actions only show up on hover\nconst StyledPanelHeaderHiddenActions = styled.div.withConfig({shouldForwardProp}).attrs({\n  className: 'layer-panel__header__actions__hidden'\n})<StyledPanelHeaderHiddenActionsProps>`\n  opacity: 0;\n  display: flex;\n  align-items: center;\n  background-color: ${props =>\n    props.isConfigActive ? props.theme.panelBackgroundHover : props.theme.panelBackground};\n  transition:\n    opacity 0.4s ease,\n    background-color 0.4s ease;\n\n  &:hover {\n    opacity: 1;\n  }\n`;\n\nconst StyledDragHandle = styled.div`\n  display: flex;\n  align-items: center;\n  opacity: 0;\n  z-index: 1000;\n\n  &:hover {\n    cursor: move;\n    opacity: 1;\n    color: ${props => props.theme.textColorHl};\n  }\n`;\n\nexport const DragHandle: React.FC<{\n  className?: string;\n  listeners?: any;\n  children?: React.ReactNode;\n}> = ({className, listeners, children}) => (\n  <StyledDragHandle className={className} {...(listeners ? listeners : {})}>\n    {children}\n  </StyledDragHandle>\n);\n\nconst noOp = (event: MouseEvent) => {\n  event.stopPropagation();\n  event.preventDefault();\n};\n\nexport const LayerLabelEditor: React.FC<LayerLabelEditorProps> = ({\n  layerId,\n  label,\n  onEdit,\n  onFocus,\n  onBlur\n}) => (\n  <InlineInput\n    type=\"text\"\n    className=\"layer__title__editor\"\n    data-testid={dataTestIds.layerTitleEditor}\n    value={label}\n    onClick={noOp}\n    onChange={onEdit}\n    onFocus={onFocus}\n    onBlur={onBlur}\n    id={`${layerId}:input-layer-label`}\n  />\n);\n\nexport function LayerTitleSectionFactory() {\n  const StyledLayerTitleSection = styled.div`\n    margin-left: 4px;\n    flex-grow: 1;\n    align-items: center;\n    display: flex;\n    .layer__title__inner {\n      flex-grow: 1;\n    }\n\n    .layer__title__type {\n      color: ${props => props.theme.subtextColor};\n      font-size: 10px;\n      line-height: 12px;\n      letter-spacing: 0.37px;\n      text-transform: capitalize;\n    }\n  `;\n  const LayerTitleSection: React.FC<LayerTitleSectionProps> = ({\n    layerType,\n    layerId,\n    label,\n    onUpdateLayerLabel,\n    onFocus,\n    onBlur\n  }) => (\n    <StyledLayerTitleSection className=\"layer__title\">\n      <div>\n        <LayerLabelEditor\n          layerId={layerId}\n          label={label}\n          onEdit={onUpdateLayerLabel}\n          onFocus={onFocus}\n          onBlur={onBlur}\n        />\n        <div className=\"layer__title__type\">\n          {layerType && <FormattedMessage id={`layer.type.${layerType.toLowerCase()}`} />}\n        </div>\n      </div>\n    </StyledLayerTitleSection>\n  );\n  return LayerTitleSection;\n}\n\nLayerPanelHeaderActionSectionFactory.deps = [PanelHeaderActionFactory];\n\nexport function LayerPanelHeaderActionSectionFactory(\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>\n) {\n  const LayerPanelHeaderActionSection: React.FC<LayerPanelHeaderActionSectionProps> = (\n    props: LayerPanelHeaderActionSectionProps\n  ) => {\n    const {\n      isConfigActive,\n      allowDuplicate,\n      isVisible,\n      isValid,\n      layerId,\n      onToggleVisibility,\n      onResetIsValid,\n      onToggleEnableConfig,\n      onDuplicateLayer,\n      onRemoveLayer,\n      onZoomToLayer,\n      showRemoveLayer = true,\n      isEditingLabel,\n      actionIcons: customActionIcons\n    } = props;\n    // Merge custom actionIcons with defaults to avoid breaking changes\n    const actionIcons = {...defaultActionIcons, ...customActionIcons};\n    return (\n      <HeaderActionSection className=\"layer-panel__header__actions\" isEditingLabel={isEditingLabel}>\n        <StyledPanelHeaderHiddenActions isConfigActive={isConfigActive}>\n          {showRemoveLayer ? (\n            <PanelHeaderAction\n              className=\"layer__remove-layer\"\n              testId=\"remove-layer-action\"\n              id={layerId}\n              tooltip={'tooltip.removeLayer'}\n              onClick={onRemoveLayer}\n              tooltipType=\"error\"\n              IconComponent={actionIcons.remove}\n            />\n          ) : null}\n          <PanelHeaderAction\n            className=\"layer__duplicate\"\n            id={layerId}\n            tooltip={'tooltip.duplicateLayer'}\n            onClick={onDuplicateLayer}\n            IconComponent={actionIcons.duplicate}\n            disabled={!allowDuplicate}\n          />\n          <PanelHeaderAction\n            className=\"layer__zoom-to-layer\"\n            id={layerId}\n            tooltip={'tooltip.zoomToLayer'}\n            onClick={onZoomToLayer}\n            IconComponent={actionIcons.crosshairs}\n          />\n        </StyledPanelHeaderHiddenActions>\n        {isValid ? (\n          <PanelHeaderAction\n            className=\"layer__visibility-toggle\"\n            id={layerId}\n            tooltip={isVisible ? 'tooltip.hideLayer' : 'tooltip.showLayer'}\n            onClick={onToggleVisibility}\n            IconComponent={isVisible ? actionIcons.visible : actionIcons.hidden}\n          />\n        ) : (\n          <PanelHeaderAction\n            className=\"layer__is-valid-refresh\"\n            id={layerId}\n            tooltip={'tooltip.resetAfterError'}\n            onClick={onResetIsValid}\n            IconComponent={actionIcons.resetIsValid}\n          />\n        )}\n\n        <PanelHeaderAction\n          className={classnames('layer__enable-config ', {\n            'is-open': isConfigActive\n          })}\n          id={layerId}\n          tooltip={'tooltip.layerSettings'}\n          onClick={onToggleEnableConfig}\n          IconComponent={\n            isConfigActive\n              ? actionIcons.disableConfig || actionIcons.enableConfig\n              : actionIcons.enableConfig\n          }\n        />\n      </HeaderActionSection>\n    );\n  };\n\n  return LayerPanelHeaderActionSection;\n}\n\nconst StyledHeaderWaring = styled.div`\n  position: absolute;\n  right: -9px;\n  top: calc(50% - 9px);\n  color: ${({theme}) => theme.notificationColors.warning};\n`;\n\nexport const HeaderWarning = ({warning, id}) => (\n  <StyledHeaderWaring>\n    <WarningSign data-tip data-for={`layer-${id}-warning`} height=\"16px\" />\n    <Tooltip id={`layer-${id}-warning`} type=\"warning\" effect=\"solid\" textColor=\"black\">\n      {warning}\n    </Tooltip>\n  </StyledHeaderWaring>\n);\n\nconst RotatedIcon = styled.div`\n  display: inline-flex;\n  transform: rotate(180deg);\n`;\n\nconst defaultActionIcons = {\n  remove: props => <Trash {...props} height=\"16px\" />,\n  visible: props => <EyeSeen {...props} height=\"16px\" />,\n  hidden: props => <EyeUnseen {...props} height=\"16px\" />,\n  enableConfig: props => <ArrowDown {...props} height=\"18px\" />,\n  disableConfig: props => (\n    <RotatedIcon>\n      <ArrowDown {...props} height=\"18px\" />\n    </RotatedIcon>\n  ),\n  duplicate: props => <Copy {...props} height=\"14px\" />,\n  resetIsValid: Reset,\n  crosshairs: props => <ZoomIn {...props} height=\"14px\" />\n};\n\nLayerPanelHeaderFactory.deps = [LayerTitleSectionFactory, LayerPanelHeaderActionSectionFactory];\n\nfunction LayerPanelHeaderFactory(\n  LayerTitleSection: ReturnType<typeof LayerTitleSectionFactory>,\n  LayerPanelHeaderActionSection: ReturnType<typeof LayerPanelHeaderActionSectionFactory>\n) {\n  const LayerPanelHeader: React.FC<LayerPanelHeaderProps> = props => {\n    const {\n      isConfigActive,\n      isDragNDropEnabled = true,\n      isValid,\n      warning,\n      label,\n      layerId,\n      layerType,\n      labelRCGColorValues,\n      onUpdateLayerLabel,\n      onToggleEnableConfig,\n      listeners\n    } = props;\n    const [isEditingLabel, setIsEditingLabel] = useState(false);\n    return (\n      <StyledLayerPanelHeader\n        className={classnames('layer-panel__header', {\n          'sort--handle': !isConfigActive\n        })}\n        isValid={isValid}\n        warning={warning}\n        active={isConfigActive}\n        labelRCGColorValues={labelRCGColorValues}\n        onClick={onToggleEnableConfig}\n      >\n        {warning ? <HeaderWarning warning={warning} id={layerId} /> : null}\n        <HeaderLabelSection className=\"layer-panel__header__content\">\n          {isDragNDropEnabled ? (\n            <DragHandle className=\"layer__drag-handle\" listeners={listeners}>\n              <VertDots height=\"20px\" />\n            </DragHandle>\n          ) : (\n            <div className=\"layer__drag-handle__placeholder\" />\n          )}\n          <LayerTitleSection\n            layerId={layerId}\n            label={label}\n            onUpdateLayerLabel={onUpdateLayerLabel}\n            layerType={layerType}\n            onFocus={() => {\n              setIsEditingLabel(true);\n            }}\n            onBlur={() => {\n              setIsEditingLabel(false);\n            }}\n          />\n        </HeaderLabelSection>\n        <LayerPanelHeaderActionSection {...props} isEditingLabel={isEditingLabel} />\n      </StyledLayerPanelHeader>\n    );\n  };\n\n  return LayerPanelHeader;\n}\n\nexport default LayerPanelHeaderFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {\n  CSSProperties,\n  ChangeEventHandler,\n  Component,\n  MouseEventHandler,\n  TouchEventHandler\n} from 'react';\nimport styled from 'styled-components';\n\nimport {ActionHandler, MapStateActions, VisStateActions, toggleModal} from '@kepler.gl/actions';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {Layer, LayerBaseConfig} from '@kepler.gl/layers';\nimport {Datasets} from '@kepler.gl/table';\nimport {ColorUI, LayerVisConfig, NestedPartial, SplitMap} from '@kepler.gl/types';\nimport LayerConfiguratorFactory from './layer-configurator';\nimport LayerPanelHeaderFactory from './layer-panel-header';\n\ntype LayerPanelProps = {\n  className?: string;\n  style?: CSSProperties;\n  onMouseDown?: MouseEventHandler;\n  onTouchStart?: TouchEventHandler;\n  layer: Layer;\n  datasets: Datasets;\n  layerTypeOptions: {\n    id: string;\n    label: string;\n    icon: any; //\n    requireData: any; //\n  }[];\n  isDraggable?: boolean;\n  idx: number;\n  openModal: ActionHandler<typeof toggleModal>;\n  layerColorUIChange: ActionHandler<typeof VisStateActions.layerColorUIChange>;\n  layerConfigChange: ActionHandler<typeof VisStateActions.layerConfigChange>;\n  layerVisualChannelConfigChange: ActionHandler<\n    typeof VisStateActions.layerVisualChannelConfigChange\n  >;\n  layerSetIsValid: ActionHandler<typeof VisStateActions.layerSetIsValid>;\n  layerTypeChange: ActionHandler<typeof VisStateActions.layerTypeChange>;\n  layerVisConfigChange: ActionHandler<typeof VisStateActions.layerVisConfigChange>;\n  layerTextLabelChange: ActionHandler<typeof VisStateActions.layerTextLabelChange>;\n  removeLayer: ActionHandler<typeof VisStateActions.removeLayer>;\n  zoomToLayer: ActionHandler<typeof MapStateActions.fitBounds>;\n  duplicateLayer: ActionHandler<typeof VisStateActions.duplicateLayer>;\n  listeners?: React.ElementType;\n  layerToggleVisibility: ActionHandler<typeof VisStateActions.layerToggleVisibility>;\n  splitMap?: SplitMap;\n};\n\nconst PanelWrapper = styled.div`\n  font-size: 12px;\n  border-radius: 1px;\n  z-index: 1000;\n  &.dragging {\n    cursor: move;\n  }\n`;\n\nLayerPanelFactory.deps = [LayerConfiguratorFactory, LayerPanelHeaderFactory];\n\nfunction LayerPanelFactory(\n  LayerConfigurator: ReturnType<typeof LayerConfiguratorFactory>,\n  LayerPanelHeader: ReturnType<typeof LayerPanelHeaderFactory>\n): React.ComponentType<LayerPanelProps> {\n  class LayerPanel extends Component<LayerPanelProps> {\n    updateLayerConfig = (newProp: Partial<LayerBaseConfig>) => {\n      this.props.layerConfigChange(this.props.layer, newProp);\n    };\n\n    updateLayerType = (newType: string) => {\n      this.props.layerTypeChange(this.props.layer, newType);\n    };\n\n    updateLayerVisConfig = (newVisConfig: Partial<LayerVisConfig>) => {\n      this.props.layerVisConfigChange(this.props.layer, newVisConfig);\n    };\n\n    updateLayerColorUI = (...args: [string, NestedPartial<ColorUI>]) => {\n      this.props.layerColorUIChange(this.props.layer, ...args);\n    };\n\n    updateLayerTextLabel = (...args: [number | 'all', string, any]) => {\n      this.props.layerTextLabelChange(this.props.layer, ...args);\n    };\n\n    updateLayerVisualChannelConfig = (\n      newConfig: Partial<LayerBaseConfig>,\n      channel: string,\n      newVisConfig?: Partial<LayerVisConfig>\n    ) => {\n      this.props.layerVisualChannelConfigChange(this.props.layer, newConfig, channel, newVisConfig);\n    };\n\n    _updateLayerLabel: ChangeEventHandler<HTMLInputElement> = ({target: {value}}) => {\n      this.updateLayerConfig({label: value});\n    };\n\n    _toggleVisibility: MouseEventHandler = e => {\n      e.stopPropagation();\n      const isVisible = !this.props.layer.config.isVisible;\n      this.props.layerToggleVisibility(this.props.layer.id, isVisible);\n    };\n\n    _resetIsValid: MouseEventHandler = e => {\n      e?.stopPropagation();\n      // Make the layer valid and visible again after an error\n      this.props.layerSetIsValid(this.props.layer, true);\n    };\n\n    _toggleEnableConfig: MouseEventHandler = e => {\n      e?.stopPropagation();\n      const {\n        layer: {\n          config: {isConfigActive}\n        }\n      } = this.props;\n      this.updateLayerConfig({isConfigActive: !isConfigActive});\n    };\n\n    _removeLayer: MouseEventHandler = e => {\n      e?.stopPropagation();\n      this.props.removeLayer(this.props.layer.id);\n    };\n\n    _zoomToLayer: MouseEventHandler = e => {\n      e?.stopPropagation();\n      const bounds = this.props.layer?.meta?.bounds;\n      bounds && this.props.zoomToLayer(bounds);\n    };\n\n    _duplicateLayer: MouseEventHandler = e => {\n      e?.stopPropagation();\n      this.props.duplicateLayer(this.props.layer.id);\n    };\n\n    render() {\n      const {layer, datasets, isDraggable, layerTypeOptions, listeners, splitMap} = this.props;\n      const {config, isValid} = layer;\n      const {isConfigActive} = config;\n      const allowDuplicate =\n        typeof layer.isValidToSave === 'function' && layer.isValidToSave() && isValid;\n      const layerVisInSplitMap = splitMap?.layers?.[layer.id];\n\n      return (\n        <PanelWrapper\n          className={`layer-panel ${this.props.className}`}\n          data-testid={dataTestIds.layerPanel}\n          style={this.props.style}\n          onMouseDown={this.props.onMouseDown}\n          onTouchStart={this.props.onTouchStart}\n        >\n          <LayerPanelHeader\n            isConfigActive={isConfigActive}\n            layerId={layer.id}\n            isVisible={layerVisInSplitMap ?? config.isVisible}\n            isValid={isValid}\n            label={config.label}\n            labelRCGColorValues={config.dataId ? datasets[config.dataId].color : null}\n            layerType={layer.type}\n            allowDuplicate={allowDuplicate}\n            onToggleEnableConfig={this._toggleEnableConfig}\n            onToggleVisibility={this._toggleVisibility}\n            onResetIsValid={this._resetIsValid}\n            onUpdateLayerLabel={this._updateLayerLabel}\n            onRemoveLayer={this._removeLayer}\n            onZoomToLayer={this._zoomToLayer}\n            onDuplicateLayer={this._duplicateLayer}\n            isDragNDropEnabled={isDraggable}\n            listeners={listeners}\n          />\n          {isConfigActive && (\n            <LayerConfigurator\n              layer={layer}\n              datasets={datasets}\n              layerTypeOptions={layerTypeOptions}\n              openModal={this.props.openModal}\n              updateLayerColorUI={this.updateLayerColorUI}\n              updateLayerConfig={this.updateLayerConfig}\n              updateLayerVisualChannelConfig={this.updateLayerVisualChannelConfig}\n              updateLayerType={this.updateLayerType}\n              updateLayerTextLabel={this.updateLayerTextLabel}\n              updateLayerVisConfig={this.updateLayerVisConfig}\n            />\n          )}\n        </PanelWrapper>\n      );\n    }\n  }\n\n  return LayerPanel;\n}\n\nexport default LayerPanelFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-type-dropdown-list.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, MouseEvent} from 'react';\nimport styled from 'styled-components';\nimport classNames from 'classnames';\nimport {classList} from '../../common/item-selector/dropdown-list';\n\nexport type LayerTypeOption = {\n  disabled?: boolean;\n  id: string;\n  label: string;\n  icon: React.ElementType;\n};\n\ntype LayerTypeDropdownListProps = {\n  onOptionSelected: (\n    value: {\n      icon: React.ElementType;\n      label: string;\n    },\n    e: MouseEvent\n  ) => void;\n  options: LayerTypeOption[];\n  selectedItems: LayerTypeOption[];\n  selectionIndex: number | null;\n  customListItemComponent: React.FC<{value: LayerTypeOption; isTile?: boolean}>;\n};\n\nconst DropdownListWrapper = styled.div`\n  ${props => props.theme.dropdownList};\n  background-color: ${props => props.theme.dropdownListBgd};\n  border-top: 1px solid ${props => props.theme.dropdownListBorderTop};\n  display: flex;\n  flex-wrap: wrap;\n  align-items: flex-start;\n  padding: ${props => props.theme.layerTypeIconPdL}px 0 0 ${props => props.theme.layerTypeIconPdL}px;\n`;\n\nconst StyledDropdownListItem = styled.div`\n  padding-bottom: ${props => props.theme.layerTypeIconPdL}px;\n  padding-right: ${props => props.theme.layerTypeIconPdL}px;\n\n  &.disabled {\n    pointer-events: none;\n    opacity: 0.3;\n  }\n\n  &.selected {\n    .layer-type-selector__item__icon {\n      border: 1px solid #caf2f4;\n    }\n  }\n\n  &:hover,\n  &.selected {\n    cursor: pointer;\n    .layer-type-selector__item__icon {\n      color: ${props => props.theme.activeColor};\n    }\n\n    .layer-type-selector__item__label {\n      color: ${props => props.theme.textColor};\n    }\n  }\n`;\n\nexport function LayerTypeDropdownListFactory() {\n  const LayerTypeDropdownList: React.FC<LayerTypeDropdownListProps> = ({\n    onOptionSelected,\n    options,\n    selectedItems,\n    selectionIndex,\n    customListItemComponent\n  }) => {\n    const onSelectOption = useCallback(\n      (e, value) => {\n        e.preventDefault();\n        onOptionSelected(value, e);\n      },\n      [onOptionSelected]\n    );\n\n    const ListItemComponent = customListItemComponent;\n\n    return (\n      <DropdownListWrapper className={classList.list}>\n        {options.map((value, i) => (\n          <StyledDropdownListItem\n            className={classNames('layer-type-selector__item', {\n              selected: selectedItems.find(it => it.id === value.id),\n              hover: selectionIndex === i,\n              disabled: value.disabled\n            })}\n            key={`${value.id}_${i}`}\n            onMouseDown={(e: MouseEvent) => onSelectOption(e, value)}\n            onClick={(e: MouseEvent) => onSelectOption(e, value)}\n          >\n            <ListItemComponent value={value} isTile />\n          </StyledDropdownListItem>\n        ))}\n      </DropdownListWrapper>\n    );\n  };\n\n  return LayerTypeDropdownList;\n}\n\nexport default LayerTypeDropdownListFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-type-list-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classNames from 'classnames';\nimport React, {ComponentType} from 'react';\nimport styled, {withTheme} from 'styled-components';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {BaseProps} from '../../common/icons';\n\nexport type LayerTypeListItemProps = {\n  value: {\n    icon: ComponentType<Partial<BaseProps>>;\n    label: string;\n  };\n  isTile?: boolean;\n  className?: string;\n};\n\ntype WithThemeProps = LayerTypeListItemProps & {theme: Record<string, string>};\n\nexport type LayerTypeListItemType = React.FC<LayerTypeListItemProps>;\n\ntype StyledListItemProps = {\n  cdnUrl: string;\n};\n\nconst StyledListItem = styled.div<StyledListItemProps>`\n  &.list {\n    display: flex;\n    align-items: center;\n    overflow: hidden;\n\n    .layer-type-selector__item__icon {\n      color: ${props => props.theme.activeColor};\n      background-size: ${props => props.theme.layerTypeIconSizeSM}px\n        ${props => props.theme.layerTypeIconSizeSM}px;\n      height: ${props => props.theme.layerTypeIconSizeSM}px;\n      width: ${props => props.theme.layerTypeIconSizeSM}px;\n      margin-right: 12px;\n    }\n\n    .layer-type-selector__item__label {\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n  }\n\n  .layer-type-selector__item__icon {\n    color: ${props => props.theme.labelColor};\n    display: flex;\n    background-image: url(${props => `${props.cdnUrl}/images/kepler.gl-layer-icon-bg.png`});\n    background-size: ${props => props.theme.layerTypeIconSizeL}px\n      ${props => props.theme.layerTypeIconSizeL}px;\n    height: ${props => props.theme.layerTypeIconSizeL}px;\n    width: ${props => props.theme.layerTypeIconSizeL}px;\n  }\n\n  .layer-type-selector__item__label {\n    text-transform: capitalize;\n    font-size: 12px;\n    text-align: center;\n    color: ${props => props.theme.selectColor};\n    max-width: ${props => props.theme.layerTypeIconSizeL}px;\n  }\n`;\n\nexport function LayerTypeListItemFactory() {\n  const LayerTypeListItem: React.FC<WithThemeProps> = ({value, isTile, theme, className}) => (\n    <StyledListItem\n      className={classNames('layer-type-selector__item__inner', className, {\n        list: !isTile\n      })}\n      cdnUrl={getApplicationConfig().cdnUrl}\n    >\n      <div className=\"layer-type-selector__item__icon\">\n        <value.icon height={`${isTile ? theme.layerTypeIconSizeL : theme.layerTypeIconSizeSM}px`} />\n      </div>\n      <div className=\"layer-type-selector__item__label\">\n        <FormattedMessage\n          id={`layer.type.${value.label.toLowerCase()}`}\n          defaultMessage={value.label}\n        />\n      </div>\n    </StyledListItem>\n  );\n\n  return withTheme(LayerTypeListItem) as React.FC<LayerTypeListItemProps>;\n}\n\nexport default LayerTypeListItemFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/layer-type-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled, {withTheme} from 'styled-components';\n\nimport LayerTypeDropdownListFactory, {LayerTypeOption} from './layer-type-dropdown-list';\nimport LayerTypeListItemFactory from './layer-type-list-item';\nimport ItemSelector from '../../common/item-selector/item-selector';\n\nimport {SidePanelSection} from '../../common/styled-components';\n\ntype Option = {\n  id: string;\n  label: string;\n  icon: any; //\n  requireData: any; //\n};\n\nexport type LayerTypeSelectorProps = {\n  selected: string | null;\n  onSelect: (\n    items:\n      | readonly (string | number | boolean | object)[]\n      | string\n      | number\n      | boolean\n      | object\n      | null\n  ) => void;\n  options: LayerTypeOption[];\n  // TODO add correct type after Theme typing\n  theme: Record<string, string>;\n  disabled: boolean;\n};\n\nconst StyledLayerTypeSelector = styled.div`\n  .item-selector .item-selector__dropdown {\n    padding: 4px 10px 4px 10px;\n  }\n`;\n\nLayerTypeSelectorFactory.deps = [LayerTypeListItemFactory, LayerTypeDropdownListFactory];\n\nconst getDisplayOption = (op: Option) => op.label;\nconst getOptionValue = (op: Option) => op.id;\n\nfunction LayerTypeSelectorFactory(\n  LayerTypeListItem: ReturnType<typeof LayerTypeListItemFactory>,\n  LayerTypeDropdownList: ReturnType<typeof LayerTypeDropdownListFactory>\n) {\n  const LayerTypeSelector: React.FC<LayerTypeSelectorProps> = ({\n    selected,\n    options,\n    onSelect,\n    disabled\n  }) => {\n    const selectedItems = useMemo(\n      () => options.find(op => op.id === selected),\n      [options, selected]\n    );\n\n    return (\n      <SidePanelSection>\n        <StyledLayerTypeSelector className=\"layer-config__type\">\n          <ItemSelector\n            selectedItems={selectedItems}\n            options={options}\n            multiSelect={false}\n            disabled={disabled}\n            placeholder=\"placeholder.selectType\"\n            onChange={onSelect}\n            getOptionValue={getOptionValue}\n            filterOption=\"label\"\n            displayOption={getDisplayOption}\n            DropDownLineItemRenderComponent={LayerTypeListItem}\n            DropDownRenderComponent={LayerTypeDropdownList}\n          />\n        </StyledLayerTypeSelector>\n      </SidePanelSection>\n    );\n  };\n\n  return withTheme(LayerTypeSelector) as React.FC<Omit<LayerTypeSelectorProps, 'theme'>>;\n}\n\nexport default LayerTypeSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/radius-by-zoom-input.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport classnames from 'classnames';\nimport React, {useCallback, useState} from 'react';\n// @ts-expect-error - react-sortable-hoc libdef does not match true exports\nimport {sortableContainer, sortableElement, sortableHandle} from 'react-sortable-hoc';\nimport styled, {css} from 'styled-components';\n\nimport {ZoomStops, ZoomStopsConfig} from '@kepler.gl/types';\nimport {arrayMove} from '@kepler.gl/common-utils';\n\nimport {Icons, Input, PanelLabel, Button} from '../../common';\n\nconst SliderInput = styled(Input)`\n  width: 48px;\n  margin: 0 4px 0 8px;\n  font-size: ${props => props.theme.list1Size};\n`;\n\ntype StyledInputRowProps = {\n  isEditing?: boolean;\n};\n\nconst StyledInputRow = styled.div<StyledInputRowProps>`\n  display: flex;\n  align-items: center;\n  width: 100%;\n  justify-content: space-between;\n  .layer__drag-handle {\n    visibility: ${props => (props.isEditing ? 'visible' : 'hidden')};\n  }\n  .side-panel-panel__label {\n    margin-bottom: 0;\n    text-transform: none;\n  }\n`;\n\ntype StyledTrashProps = {\n  isEditing?: boolean;\n};\n\nconst StyledTrash = styled.div<StyledTrashProps>`\n  color: ${props => props.theme.subtextColor};\n  display: flex;\n  align-items: center;\n  margin-left: 8px;\n  visibility: ${props => (props.isEditing ? 'visible' : 'hidden')};\n\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.subtextColorActive};\n  }\n`;\n\nconst StyledDragHandle = styled.div`\n  display: flex;\n  align-items: center;\n  opacity: 0;\n`;\n\nconst dragHandleActive = css`\n  .layer__drag-handle {\n    color: ${props => props.theme.textColorHl};\n    opacity: 1;\n    cursor: move;\n  }\n`;\n\nconst StyledSortableItem = styled.div`\n  display: flex;\n  align-items: center;\n  padding: 4px 0;\n  z-index: ${props => props.theme.dropdownWrapperZ + 1};\n  margin-left: -6px;\n\n  &:not(.sorting) {\n    &:hover {\n      ${dragHandleActive};\n    }\n  }\n\n  &.sorting-colors {\n    background-color: ${props => props.theme.panelBackgroundHover};\n    ${dragHandleActive};\n  }\n`;\n\nconst SortableContainer = sortableContainer(({children}) => <div>{children}</div>);\nconst DragHandle = sortableHandle(({className, children}) => (\n  <StyledDragHandle className={className}>{children}</StyledDragHandle>\n));\nconst SortableItem = sortableElement(({children, isSorting}) => (\n  <StyledSortableItem\n    className={classnames('custom-palette__sortable-items', {sorting: isSorting})}\n  >\n    {children}\n  </StyledSortableItem>\n));\n\nfunction stringToNumber(val) {\n  return val === '' ? null : Number(val);\n}\n\ntype InputRowProps = {\n  idx: number;\n  stop: number;\n  value: number;\n  isSorting: boolean;\n  isEditing: boolean;\n  onChange: (value: [number | null, number | null]) => void;\n  onRemove: () => void;\n};\nconst InputRow: React.FC<InputRowProps> = ({\n  idx,\n  stop,\n  value,\n  isSorting,\n  isEditing,\n  onChange,\n  onRemove\n}) => {\n  return (\n    <SortableItem key={idx} index={idx} isSorting={isSorting}>\n      <StyledInputRow isEditing={isEditing}>\n        <DragHandle className=\"layer__drag-handle\">\n          <Icons.VertDots height=\"20px\" />\n        </DragHandle>\n        <PanelLabel>zoom</PanelLabel>\n        <SliderInput\n          className=\"vis-config-zoom__input__stop\"\n          type=\"number\"\n          id={`${idx}-stop`}\n          key={`${idx}-stop`}\n          value={stop}\n          onChange={e => onChange([stringToNumber(e.target.value), value])}\n          disabled={!isEditing}\n        />\n        <SliderInput\n          className=\"vis-config-zoom__input__value\"\n          type=\"number\"\n          id={`${idx}-value`}\n          key={`${idx}-value`}\n          value={value}\n          onChange={e => onChange([stop, stringToNumber(e.target.value)])}\n          disabled={!isEditing}\n        />\n        <PanelLabel>px</PanelLabel>\n        <StyledTrash isEditing={isEditing}>\n          <Icons.Trash onClick={onRemove} height=\"16px\" />\n        </StyledTrash>\n      </StyledInputRow>\n    </SortableItem>\n  );\n};\n\nfunction insertStop(stops: ZoomStops): ZoomStops {\n  let newStops: ZoomStops | null = null;\n  let i = 0;\n  while (!newStops && i < stops.length) {\n    if (stops[i][0] + 1 < stops[i + 1][0]) {\n      const st = stops[i][0] + 1;\n      const value = (stops[i][1] + stops[i + 1][1]) / 2;\n      newStops = [...stops.slice(0, i + 1), [st, value], ...stops.slice(i + 1)];\n    } else {\n      i++;\n    }\n  }\n\n  if (!newStops) {\n    newStops = [\n      ...stops.slice(0, i),\n      [stops[i][0], (stops[i][1] + stops[i + 1][1]) / 2],\n      ...stops.slice(i)\n    ];\n  }\n\n  return newStops;\n}\n\ntype VisConfigByZoomInputContainerProps = {\n  isEditing?: boolean;\n};\n\nconst VisConfigByZoomInputContainer = styled.div<VisConfigByZoomInputContainerProps>`\n  background-color: ${props => (props.isEditing ? props.theme.AZURE950 : 'transparent')};\n  margin: 8px 8px 12px 8px;\n\n  .bottom-action {\n    margin-top: 8px;\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n  }\n\n  .bottom-action.editing {\n    justify-content: space-between;\n  }\n`;\n\ntype Props = {\n  config: ZoomStopsConfig;\n  property: string;\n  label: string;\n  unit: string;\n  onChange: (update: Record<string, ZoomStopsConfig>) => void;\n};\n\nconst VisConfigByZoomInput: React.FC<Props> = ({config = {}, property, onChange}) => {\n  const [stopsState, setStops] = useState(config.stops || []);\n  const [isSorting, toggleSorting] = useState(false);\n  const [isEditing, toggleEditing] = useState(false);\n\n  const onConfirm = useCallback(() => {\n    onChange({\n      [property]: {\n        ...config,\n        stops: stopsState\n      }\n    });\n    toggleEditing(false);\n  }, [property, config, stopsState, onChange, toggleEditing]);\n  const addStop = useCallback(() => setStops(insertStop(stopsState)), [setStops, stopsState]);\n  const removeStop = useCallback(\n    i => setStops([...stopsState.slice(0, i), ...stopsState.slice(i + 1)]),\n    [setStops, stopsState]\n  );\n  const onSortEnd = useCallback(\n    ({oldIndex, newIndex}) => {\n      const newStopsState = arrayMove(stopsState, oldIndex, newIndex);\n      setStops(newStopsState);\n      toggleSorting(false);\n    },\n    [stopsState, setStops, toggleSorting]\n  );\n  const onSortStart = useCallback(() => {\n    toggleSorting(true);\n  }, [toggleSorting]);\n\n  return (\n    <VisConfigByZoomInputContainer isEditing={isEditing}>\n      <SortableContainer\n        className=\"custom-palette-container\"\n        onSortEnd={onSortEnd}\n        onSortStart={onSortStart}\n        lockAxis=\"y\"\n        helperClass=\"sorting-colors\"\n        useDragHandle\n      >\n        {stopsState.map((stop, idx) => (\n          <InputRow\n            isEditing={isEditing}\n            key={`input-${idx}`}\n            idx={idx}\n            stop={stop[0]}\n            value={stop[1]}\n            isSorting={isSorting}\n            onChange={v => setStops(Object.assign([...(stopsState || [])], {[idx]: v}))}\n            onRemove={() => removeStop(idx)}\n          />\n        ))}\n      </SortableContainer>\n      {isEditing ? (\n        <div className=\"bottom-action editing\">\n          <Button secondary onClick={addStop} small>\n            <Icons.Add height=\"16px\" /> Add Stop\n          </Button>\n          <Button onClick={onConfirm} small>\n            Confirm\n          </Button>\n        </div>\n      ) : (\n        <div className=\"bottom-action\">\n          <Button onClick={() => toggleEditing(true)} small>\n            Edit\n          </Button>\n        </div>\n      )}\n    </VisConfigByZoomInputContainer>\n  );\n};\n\nexport default VisConfigByZoomInput;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/raster-tile-colormap-list-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport {getCategoricalColormapDataUrl, RasterLayerResources} from '@kepler.gl/layers';\nimport type {CategoricalColormapOptions} from '@kepler.gl/layers';\n\nconst StyledColorMapListItem = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  .color-name {\n    width: calc(40% - 8px);\n    margin-right: 8px;\n    color: ${props => props.theme.subtextColor};\n  }\n  .color-image {\n    width: 60%;\n    height: 10px;\n    border-radius: 2px;\n    background: ${props => props.theme.panelContentBackground};\n    overflow: hidden;\n    display: flex;\n  }\n`;\n\nconst ColorMapImg: React.FC<{id: string; categoricalOptions: CategoricalColormapOptions}> = ({\n  id,\n  categoricalOptions\n}) => {\n  let url = '';\n  if (id === '_categorical') {\n    const dataUrl = getCategoricalColormapDataUrl(categoricalOptions);\n    if (!dataUrl) {\n      return null;\n    }\n    url = dataUrl;\n  } else {\n    url = RasterLayerResources.rasterColorMap(id);\n  }\n  return <img src={url} style={{width: '100%', height: '100%'}} />;\n};\n\nexport const ColorMapListItem: React.FC<{\n  value: {id: string; label: string};\n  categoricalOptions: CategoricalColormapOptions;\n}> = ({value, categoricalOptions}) => (\n  <StyledColorMapListItem>\n    <div className=\"color-name\">{value?.label}</div>\n    <div className=\"color-image\">\n      <ColorMapImg id={value?.id} categoricalOptions={categoricalOptions} />\n    </div>\n  </StyledColorMapListItem>\n);\n\nexport const getColorMapListItemComponent = (\n  categoricalOptions: CategoricalColormapOptions\n): React.FC<{\n  value: {id: string; label: string};\n}> => {\n  return (props: {value: {id: string; label: string}}) => (\n    <ColorMapListItem {...props} categoricalOptions={categoricalOptions} />\n  );\n};\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/raster-tile-layer-configurator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useEffect, useMemo} from 'react';\nimport styled from 'styled-components';\n\nimport {PMTilesType} from '@kepler.gl/constants';\nimport {\n  filterAvailablePresets,\n  getEOBands,\n  getRasterStatisticsMinMax,\n  getSingleBandPresetOptions,\n  isColormapAllowed,\n  isFilterAllowed,\n  isRescalingAllowed,\n  isSearchableStac,\n  CompleteSTACObject,\n  GetTileDataCustomProps,\n  DataSourceParams,\n  PresetOption,\n  RasterTileLayer,\n  CATEGORICAL_COLORMAP_ID,\n  PRESET_OPTIONS,\n  RASTER_COLOR_RESET_PARAMS,\n  DATA_SOURCE_COLOR_DEFAULTS\n} from '@kepler.gl/layers';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {capitalizeFirstLetter, getApplicationConfig} from '@kepler.gl/utils';\nimport {KeplerTable as KeplerDataset} from '@kepler.gl/table';\nimport type {StacTypes} from '@kepler.gl/types';\n\nimport {getColorMapListItemComponent} from './raster-tile-colormap-list-item';\n\nimport {\n  Input,\n  PanelLabel,\n  PanelLabelWrapper,\n  SidePanelSection\n} from '../../common/styled-components';\nimport Switch from '../../common/switch';\nimport InfoHelperFactory from '../../common/info-helper';\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport VisConfigSliderFactory from '../../side-panel/layer-panel/vis-config-slider';\nimport LayerConfigGroupFactory, {\n  ConfigGroupCollapsibleContent\n} from '../../side-panel/layer-panel/layer-config-group';\nimport VisConfigSwitchFactory from '../../side-panel/layer-panel/vis-config-switch';\n\ntype EOBand = StacTypes.Band;\n\nconst STAC_SEARCH_UI_ENABLED = true;\n\nconst StyledVisConfigSwitch = styled.div`\n  display: flex;\n  justify-content: space-between;\n\n  .vis-config-switch__title {\n    display: flex;\n  }\n`;\n\nconst CustomVisConfigSwitch = ({\n  layer: {id, config},\n  property,\n  onChange,\n  label,\n  disabled\n}: {\n  layer: RasterTileLayer;\n  property: string;\n  onChange: (value: any) => void;\n  label: string;\n  disabled: boolean;\n}) => (\n  <SidePanelSection disabled={Boolean(disabled)}>\n    <StyledVisConfigSwitch className=\"vis-config-switch\">\n      <div className=\"vis-config-switch__title\">\n        <PanelLabelWrapper>\n          {label ? (\n            <PanelLabel>\n              {(label && <FormattedMessage id={label} />) || capitalizeFirstLetter(property)}\n            </PanelLabel>\n          ) : null}\n        </PanelLabelWrapper>\n      </div>\n      <div className=\"vis-config-switch__switch\">\n        <Switch\n          checked={config.visConfig[property]}\n          id={`${id}-${property}-switch`}\n          onChange={() => onChange({[property]: !config.visConfig[property]})}\n        />\n      </div>\n    </StyledVisConfigSwitch>\n  </SidePanelSection>\n);\n\nconst StyledLayerConfigurator = styled.div`\n  margin-top: 12px;\n`;\n\nconst DescriptionText = styled(PanelLabel)`\n  text-transform: none;\n  display: inline;\n  color: ${props => props.theme.subtextColor};\n\n  a {\n    text-decoration: underline;\n    margin-left: 4px;\n  }\n`;\n\n// TODO: combine these two helpers into one\nfunction findVisConfigItemById(layer, prop) {\n  return layer.visConfigSettings[prop].options.find(op => op.id === layer.config.visConfig[prop]);\n}\n\nfunction findItemById(layer, options, prop) {\n  // For now it's possible that non-raster metadata will be passed to the raster configurator, so\n  // options may be null\n  return options?.find(op => op.id === layer.config.visConfig[prop]);\n}\n\n/**\n * Generate drop down labels for single band selector\n * @param stac - STAC item metadata object\n * @returns array of {id, label} elements\n */\nfunction getBandSelectorOptions(stac: CompleteSTACObject): {id?: string; label?: string}[] {\n  const eoBands = getEOBands(stac) || [];\n  return eoBands.map((eoBand: EOBand) => ({\n    id: eoBand.name || eoBand.common_name,\n    label: eoBand.common_name\n      ? `${eoBand.name || eoBand.common_name} (${eoBand.common_name})`\n      : eoBand.name\n  }));\n}\n\nfunction getCategoricalColormapListItem(categoricalColorMap) {\n  let categoricalItem: {label: string; id: string} | undefined;\n  if (categoricalColorMap) {\n    categoricalItem = {label: 'Categorical', id: CATEGORICAL_COLORMAP_ID};\n  }\n  return categoricalItem;\n}\n\n/**\n * Check if data source global pixel color range is calculated correctly\n * @param layer - raster layer\n * @param stac - STAC metadata object\n * @param preset - preset name\n * @param singleBandPresetOptions - options for single band preset\n * @returns boolean value\n */\nfunction isDataSourceColorRangeAvailable(\n  layer: RasterTileLayer,\n  stac: CompleteSTACObject,\n  preset: string,\n  singleBandPresetOptions: PresetOption['singleBand']\n): boolean {\n  let minPixelValue: number | null = null;\n  let maxPixelValue: number | null = null;\n  const dataSourceParams: DataSourceParams | null = layer.getDataSourceParams(stac, preset, {\n    singleBand: singleBandPresetOptions\n  });\n  if (dataSourceParams) {\n    minPixelValue = dataSourceParams.minPixelValue;\n    maxPixelValue = dataSourceParams.maxPixelValue;\n  }\n  return minPixelValue !== null && maxPixelValue !== null;\n}\n\n/**\n * Updates color parameters based on a change in visualization preset.\n *\n * This function adjusts the color processing parameters when the visualization\n * preset changes, based on whether the preset is switching to or from a\n * single-band. Determines whether to apply color\n * enhancements or reset parameters based on the preset transition.\n *\n * @param stac A STAC (SpatioTemporal Asset Catalog) object representing the dataset.\n * @param newPreset The old visualization preset being applied.\n * @param newPreset The new visualization preset being applied.\n * @returns The updated color parameters to apply.\n */\nfunction updateColorParamsOnPresetChange(\n  stac: CompleteSTACObject,\n  oldPreset: string,\n  newPreset: string\n) {\n  let colorParams = {};\n  const colorOverrides = DATA_SOURCE_COLOR_DEFAULTS[stac.id];\n  if (colorOverrides) return colorParams;\n\n  const bandOfInterest = 'singleBand';\n  if (oldPreset === bandOfInterest && newPreset !== bandOfInterest) {\n    // enchance colors for multiband combinations\n    colorParams = colorOverrides;\n  } else if (oldPreset !== bandOfInterest && newPreset === bandOfInterest) {\n    // reset image processng params\n    colorParams = RASTER_COLOR_RESET_PARAMS;\n  }\n\n  return colorParams;\n}\n\ntype RasterTileLayerConfiguratorProps = {\n  layer: RasterTileLayer;\n  visConfiguratorProps: any;\n  dataset: KeplerDataset;\n};\n\nRasterTileLayerConfiguratorFactory.deps = [\n  LayerConfigGroupFactory,\n  VisConfigSliderFactory,\n  InfoHelperFactory,\n  VisConfigSwitchFactory\n];\n\nfunction RasterTileLayerConfiguratorFactory(\n  LayerConfigGroup: ReturnType<typeof LayerConfigGroupFactory>,\n  VisConfigSlider: ReturnType<typeof VisConfigSliderFactory>,\n  InfoHelper: ReturnType<typeof InfoHelperFactory>,\n  VisConfigSwitch: ReturnType<typeof VisConfigSwitchFactory>\n): React.FC<RasterTileLayerConfiguratorProps> {\n  /**\n   * Wrapper around configurator to check for dataset.metadata being null/undefined\n   */\n  const STACCheckConfiguratorWrapper = ({\n    layer,\n    visConfiguratorProps,\n    dataset\n  }: RasterTileLayerConfiguratorProps) => {\n    const stac = dataset?.metadata;\n\n    // If no dataset is loaded into Kepler, stac can be undefined\n    if (!stac) {\n      return null;\n    }\n\n    return (\n      <RasterTileLayerConfigurator\n        layer={layer}\n        visConfiguratorProps={visConfiguratorProps}\n        dataset={dataset}\n      />\n    );\n  };\n\n  // eslint-disable-next-line complexity\n  const RasterTileLayerConfigurator: React.FC<RasterTileLayerConfiguratorProps> = ({\n    layer,\n    visConfiguratorProps,\n    dataset\n  }) => {\n    const {\n      preset,\n      nonLinearRescaling,\n      useSTACSearching,\n      colorRange: {colorMap: categoricalColorMap},\n      dynamicColor,\n      singleBandName\n    } = layer.config.visConfig;\n\n    const stac = dataset?.metadata as GetTileDataCustomProps['stac'];\n\n    const availablePresets = useMemo(() => filterAvailablePresets(stac, PRESET_OPTIONS), [stac]);\n\n    const presetOptions = useMemo(\n      () =>\n        (\n          layer.visConfigSettings.preset.options as unknown as {\n            id: string;\n            label: string;\n          }[]\n        ).filter(({id}) => availablePresets?.includes(id)),\n      [layer.visConfigSettings.preset.options, availablePresets]\n    );\n    const singleBandOptions = useMemo(() => getBandSelectorOptions(stac), [stac]);\n    const colormapOptions = useMemo(() => {\n      const options = [\n        ...(layer.visConfigSettings.colormapId.options as unknown as {\n          id: string;\n          label: string;\n        }[])\n      ];\n      const categoricalListItem = getCategoricalColormapListItem(categoricalColorMap);\n      if (categoricalListItem) {\n        options.push(categoricalListItem);\n      }\n      return options;\n    }, [layer.visConfigSettings.colormapId.options, categoricalColorMap]);\n\n    const {bandCombination} = PRESET_OPTIONS[preset] || {};\n    const colormapAllowed = isColormapAllowed(bandCombination);\n    const rescalingAllowed = !categoricalColorMap && isRescalingAllowed(bandCombination);\n    const filterAllowed = isFilterAllowed(bandCombination);\n\n    // Here we show the UI when useSTACSearching is explicitly set to true so that the UI shows up\n    const stacSearchAllowed = isSearchableStac(stac) || useSTACSearching;\n\n    const selectedColormap =\n      findVisConfigItemById(layer, 'colormapId') ||\n      getCategoricalColormapListItem(categoricalColorMap);\n    const selectedPreset = findVisConfigItemById(layer, 'preset');\n    const selectedSingleBandName = findItemById(layer, singleBandOptions, 'singleBandName');\n\n    const singleBandPresetOptions = getSingleBandPresetOptions(stac, singleBandName);\n    const [minCategoricalBandValue, maxCategoricalBandValue] = getRasterStatisticsMinMax(\n      stac,\n      preset,\n      singleBandPresetOptions\n    );\n    const ColorMapListItem = useMemo(\n      () =>\n        getColorMapListItemComponent({\n          colorMap: categoricalColorMap,\n          minValue: minCategoricalBandValue,\n          maxValue: maxCategoricalBandValue\n        }),\n      [categoricalColorMap, minCategoricalBandValue, maxCategoricalBandValue]\n    );\n    const isDynamicColorsOnly = !isDataSourceColorRangeAvailable(\n      layer,\n      stac,\n      preset,\n      singleBandPresetOptions\n    );\n\n    // Default of `dynamicColor` is false. Set it true if it is not possible to get data source\n    // wide color range\n    useEffect(() => {\n      if (isDynamicColorsOnly && !dynamicColor) {\n        visConfiguratorProps.onChange({dynamicColor: true});\n      }\n    }, [visConfiguratorProps, dynamicColor, isDynamicColorsOnly]);\n\n    const elevationUI = (\n      <>\n        {(stac.rasterServerSupportsElevation ??\n          getApplicationConfig().rasterServerSupportsElevation) &&\n          stac.rasterTileServerUrls?.length && (\n            <LayerConfigGroup\n              {...(layer.visConfigSettings.enableTerrain || {label: 'layer.color'})}\n              {...visConfiguratorProps}\n              collapsible\n            >\n              <ConfigGroupCollapsibleContent>\n                <VisConfigSwitch\n                  {...visConfiguratorProps}\n                  {...layer.visConfigSettings.enableTerrainTopView}\n                />\n              </ConfigGroupCollapsibleContent>\n            </LayerConfigGroup>\n          )}\n      </>\n    );\n\n    // For PMTiles in raster format, only show opacity and terrain options for now\n    if (stac.pmtilesType === PMTilesType.RASTER) {\n      return (\n        <StyledLayerConfigurator>\n          <LayerConfigGroup {...visConfiguratorProps} label=\"Visual Settings\" collapsible={false}>\n            <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n          </LayerConfigGroup>\n          {elevationUI}\n        </StyledLayerConfigurator>\n      );\n    }\n\n    return (\n      <StyledLayerConfigurator>\n        {availablePresets && (\n          <LayerConfigGroup {...visConfiguratorProps} label=\"Image Selection\" collapsible={false}>\n            <SidePanelSection>\n              <PanelLabelWrapper>\n                <PanelLabel>Preset</PanelLabel>\n                <InfoHelper\n                  id=\"preset\"\n                  description=\"Select a preset to describe how to combine spectral bands.\"\n                />\n              </PanelLabelWrapper>\n              <ItemSelector\n                selectedItems={selectedPreset}\n                options={presetOptions}\n                multiSelect={false}\n                searchable={false}\n                displayOption=\"label\"\n                getOptionValue=\"id\"\n                onChange={newPreset => {\n                  const overrides = updateColorParamsOnPresetChange(\n                    stac,\n                    visConfiguratorProps.layer.config.visConfig.preset,\n                    newPreset as string\n                  );\n                  visConfiguratorProps.onChange({...overrides, preset: newPreset});\n                }}\n              />\n              {selectedPreset?.description ? (\n                <DescriptionText>\n                  {selectedPreset?.description}\n                  {selectedPreset?.infoUrl ? (\n                    <a target=\"_blank\" rel=\"noopener noreferrer\" href={selectedPreset?.infoUrl}>\n                      More Info\n                    </a>\n                  ) : null}\n                </DescriptionText>\n              ) : null}\n            </SidePanelSection>\n\n            {selectedPreset.id === 'singleBand' && (\n              <SidePanelSection>\n                <PanelLabelWrapper>\n                  <PanelLabel>Single Band Name</PanelLabel>\n                  <InfoHelper\n                    id={`${layer.id}-single-band-name`}\n                    description=\"Select a single band.\"\n                  />\n                </PanelLabelWrapper>\n                <ItemSelector\n                  selectedItems={selectedSingleBandName}\n                  options={singleBandOptions}\n                  multiSelect={false}\n                  searchable={false}\n                  displayOption=\"label\"\n                  getOptionValue=\"id\"\n                  onChange={val => {\n                    visConfiguratorProps.onChange({singleBandName: val});\n                  }}\n                />\n              </SidePanelSection>\n            )}\n\n            {STAC_SEARCH_UI_ENABLED && stacSearchAllowed && (\n              <CustomVisConfigSwitch\n                {...layer.visConfigSettings.useSTACSearching}\n                {...visConfiguratorProps}\n              />\n            )}\n\n            {STAC_SEARCH_UI_ENABLED && (\n              <>\n                {stacSearchAllowed && useSTACSearching ? (\n                  <SidePanelSection>\n                    <PanelLabelWrapper>\n                      <PanelLabel>STAC Search Provider</PanelLabel>\n                    </PanelLabelWrapper>\n                    <ItemSelector\n                      {...layer.visConfigSettings.stacSearchProvider}\n                      selectedItems={findVisConfigItemById(layer, 'stacSearchProvider')}\n                      placeholder=\"Choose search provider\"\n                      multiSelect={false}\n                      searchable={false}\n                      displayOption=\"label\"\n                      getOptionValue=\"id\"\n                      onChange={val => {\n                        // TODO: check when switching layers so that you don't mismatch allowed mosaics with layers\n                        visConfiguratorProps.onChange({stacSearchProvider: val});\n                      }}\n                    />\n\n                    <PanelLabelWrapper>\n                      <PanelLabel>Date Range</PanelLabel>\n                    </PanelLabelWrapper>\n                    <Input\n                      type=\"text\"\n                      id={`${layer.id}-startDate`}\n                      onChange={({target: {value}}) => {\n                        visConfiguratorProps.onChange({startDate: value});\n                      }}\n                      value={layer.config.visConfig.startDate}\n                    />\n                    <Input\n                      type=\"text\"\n                      id={`${layer.id}-endDate`}\n                      onChange={({target: {value}}) => {\n                        visConfiguratorProps.onChange({endDate: value});\n                      }}\n                      value={layer.config.visConfig.endDate}\n                    />\n                    <DescriptionText>Date format must be \"YYYY-MM-DD\"</DescriptionText>\n                  </SidePanelSection>\n                ) : null}\n              </>\n            )}\n          </LayerConfigGroup>\n        )}\n\n        <LayerConfigGroup {...visConfiguratorProps} label=\"Visual Settings\" collapsible={false}>\n          <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n          {colormapAllowed && (\n            <SidePanelSection>\n              <PanelLabel>Colormap</PanelLabel>\n              <ItemSelector\n                selectedItems={selectedColormap}\n                options={colormapOptions}\n                multiSelect={false}\n                displayOption=\"label\"\n                getOptionValue=\"id\"\n                onChange={val => {\n                  visConfiguratorProps.onChange({colormapId: val});\n                }}\n                DropDownLineItemRenderComponent={ColorMapListItem}\n                filterOption=\"label\"\n                searchable\n              />\n            </SidePanelSection>\n          )}\n        </LayerConfigGroup>\n\n        {/* Rescaling */}\n        {rescalingAllowed && (\n          <LayerConfigGroup {...visConfiguratorProps} label=\"Rescaling\">\n            <VisConfigSwitch\n              {...visConfiguratorProps}\n              {...layer.visConfigSettings.dynamicColor}\n              disabled={isDynamicColorsOnly}\n            />\n            <VisConfigSwitch\n              {...layer.visConfigSettings.nonLinearRescaling}\n              {...visConfiguratorProps}\n              label={nonLinearRescaling ? 'Non-Linear Rescaling' : 'Linear Rescaling'}\n            />\n\n            {/* TODO: add sliders for red, green, blue, not a single slider */}\n            {nonLinearRescaling ? (\n              <div>\n                <VisConfigSlider\n                  {...layer.visConfigSettings.gammaContrastFactor}\n                  {...visConfiguratorProps}\n                />\n                <VisConfigSlider\n                  {...layer.visConfigSettings.sigmoidalContrastFactor}\n                  {...visConfiguratorProps}\n                />\n                <VisConfigSlider\n                  {...layer.visConfigSettings.sigmoidalBiasFactor}\n                  {...visConfiguratorProps}\n                />\n              </div>\n            ) : (\n              <VisConfigSlider\n                {...layer.visConfigSettings.linearRescalingFactor}\n                {...visConfiguratorProps}\n              />\n            )}\n\n            <VisConfigSlider\n              {...layer.visConfigSettings.saturationValue}\n              {...visConfiguratorProps}\n            />\n          </LayerConfigGroup>\n        )}\n\n        {filterAllowed && (\n          <LayerConfigGroup\n            {...layer.visConfigSettings.filterEnabled}\n            {...visConfiguratorProps}\n            collapsible\n          >\n            <VisConfigSlider {...layer.visConfigSettings.filterRange} {...visConfiguratorProps} />\n          </LayerConfigGroup>\n        )}\n\n        {elevationUI}\n      </StyledLayerConfigurator>\n    );\n  };\n\n  return STACCheckConfiguratorWrapper;\n}\n\nexport default RasterTileLayerConfiguratorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/single-color-palette.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {MouseEvent, useCallback, useState} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\n\nimport {hexToRgb} from '@kepler.gl/utils';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Themes} from '@kepler.gl/constants';\nimport {ColorRange, HexColor, RGBColor} from '@kepler.gl/types';\n\nimport CustomPicker from './custom-picker';\nimport PresetColorPalette from './color-palette-preset';\n\nconst MODE = {\n  preset: 'preset',\n  picker: 'picker'\n};\n\nexport type SingleColorPaletteProps = {\n  selectedColor: HexColor;\n  onSelectColor: (color: RGBColor | ColorRange, e: MouseEvent) => void;\n};\n\nconst StyledColorPickerTop = styled.div`\n  border-bottom: 1px solid ${({theme}) => theme.dropdownListBorderTop};\n  display: flex;\n  padding-top: 2px 4px 0 4px;\n  .color-palette-tab {\n    padding: 8px 0;\n    margin: 0 8px;\n    color: ${({theme}) => theme.subtextColor};\n    border-bottom: 2px;\n    border-bottom-style: solid;\n    border-bottom-color: transparent;\n    &.active {\n      color: ${({theme}) => theme.textColorHl};\n      border-bottom-color: ${({theme}) => theme.panelToggleBorderColor};\n    }\n    &:hover {\n      cursor: pointer;\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst ColorPickerTop = ({setMode, mode}) => (\n  <StyledColorPickerTop>\n    {Object.keys(MODE).map(modeId => (\n      <div\n        onClick={() => setMode(modeId)}\n        key={modeId}\n        className={classnames('color-palette-tab', {active: mode === modeId})}\n      >\n        <FormattedMessage id={`color.${modeId}`} />\n      </div>\n    ))}\n  </StyledColorPickerTop>\n);\n\nconst SingleColorPalette: React.FC<SingleColorPaletteProps> = ({\n  selectedColor,\n  onSelectColor\n}: SingleColorPaletteProps) => {\n  const [mode, setMode] = useState(MODE.preset);\n  const onSetColor = useCallback(\n    (color, e) => {\n      // color picker return an object, with color.hex\n      const hex = color.hex || color;\n      onSelectColor(hexToRgb(hex), e);\n    },\n    [onSelectColor]\n  );\n  return (\n    <div className=\"single-color-palette\">\n      <ColorPickerTop mode={mode} setMode={setMode} />\n      {mode === MODE.preset ? (\n        <PresetColorPalette\n          themes={Themes}\n          onSelectColor={onSetColor}\n          selectedColor={selectedColor}\n        />\n      ) : null}\n      {mode === MODE.picker ? <CustomPicker color={selectedColor} onChange={onSetColor} /> : null}\n    </div>\n  );\n};\n\nexport default SingleColorPalette;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/text-label-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ColorRange} from '@kepler.gl/types';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\n\nimport {Add} from '../../common/icons';\nimport ItemSelector from '../../common/item-selector/item-selector';\nimport RangeSliderFactory from '../../common/range-slider';\nimport {\n  Button,\n  PanelLabel,\n  SBFlexboxItem,\n  SidePanelSection,\n  SpaceBetweenFlexbox\n} from '../../common/styled-components';\nimport Switch from '../../common/switch';\nimport LayerConfigGroupFactory, {\n  ConfigGroupCollapsibleContent,\n  ConfigGroupCollapsibleHeader\n} from './layer-config-group';\n\nimport {LAYER_TEXT_CONFIGS} from '@kepler.gl/constants';\nimport {Field, LayerTextLabel, RGBAColor, RGBColor} from '@kepler.gl/types';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport ColorSelectorFactory from './color-selector';\n\nconst SwitchWrapper = styled.div`\n  display: flex;\n  justify-content: space-between;\n  line-height: 11px;\n  margin-bottom: 8px;\n`;\n\ntype TextLabelPanelProps = {\n  id?: string;\n  fields: Field[];\n  textLabel: LayerTextLabel[];\n  updateLayerTextLabel: (idx: number | 'all', prop: string, value: any) => void;\n};\n\nTextLabelPanelFactory.deps = [\n  RangeSliderFactory,\n  LayerConfigGroupFactory,\n  FieldSelectorFactory,\n  ColorSelectorFactory\n];\n\nfunction TextLabelPanelFactory(\n  RangeSlider: ReturnType<typeof RangeSliderFactory>,\n  LayerConfigGroup: ReturnType<typeof LayerConfigGroupFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>,\n  ColorSelector: ReturnType<typeof ColorSelectorFactory>\n): React.ComponentType<TextLabelPanelProps> {\n  class TextLabelPanel extends Component<TextLabelPanelProps> {\n    render() {\n      const {updateLayerTextLabel, textLabel, fields} = this.props;\n      const currentFields = textLabel.map(tl => tl.field && tl.field.name).filter(d => Boolean(d));\n\n      return (\n        <LayerConfigGroup label={'panel.text.label'} collapsible>\n          <ConfigGroupCollapsibleHeader>\n            <FieldSelector\n              fields={fields}\n              value={currentFields as string[]}\n              onSelect={selected => updateLayerTextLabel('all', 'fields', selected)}\n              multiSelect\n            />\n          </ConfigGroupCollapsibleHeader>\n          <ConfigGroupCollapsibleContent>\n            {textLabel.map((tl, idx) => (\n              <div key={tl.field ? tl.field.name : `null-${idx}`}>\n                <PanelLabel>\n                  <FormattedMessage id={'panel.text.labelWithId'} values={{labelId: idx + 1}} />\n                </PanelLabel>\n                <SidePanelSection>\n                  <FieldSelector\n                    fields={fields}\n                    value={(tl.field && tl.field.name) || 'placeholder.selectField'}\n                    placeholder={'placeholder.empty'}\n                    onSelect={v => updateLayerTextLabel(idx, 'field', v)}\n                    erasable\n                  />\n                </SidePanelSection>\n                <SidePanelSection>\n                  <PanelLabel>\n                    <FormattedMessage id=\"panel.text.fontSize\" />\n                  </PanelLabel>\n                  <RangeSlider\n                    {...LAYER_TEXT_CONFIGS.fontSize}\n                    value1={tl.size}\n                    isRanged={false}\n                    onChange={v => updateLayerTextLabel(idx, 'size', v[1])}\n                  />\n                </SidePanelSection>\n                <SidePanelSection>\n                  <PanelLabel>\n                    <FormattedMessage id=\"panel.text.fontColor\" />\n                  </PanelLabel>\n                  <ColorSelector\n                    colorSets={[\n                      {\n                        selectedColor: tl.color,\n                        setColor: (v: RGBColor | RGBAColor | ColorRange) =>\n                          updateLayerTextLabel(idx, 'color', v)\n                      }\n                    ]}\n                  />\n                </SidePanelSection>\n\n                <SidePanelSection>\n                  <PanelLabel>\n                    <FormattedMessage id=\"panel.text.outlineWidth\" />\n                  </PanelLabel>\n                  <RangeSlider\n                    {...LAYER_TEXT_CONFIGS.outlineWidth}\n                    value1={tl.outlineWidth}\n                    isRanged={false}\n                    onChange={v => updateLayerTextLabel(idx, 'outlineWidth', v[1])}\n                  />\n                </SidePanelSection>\n                <SidePanelSection>\n                  <PanelLabel>\n                    <FormattedMessage id=\"panel.text.outlineColor\" />\n                  </PanelLabel>\n                  <ColorSelector\n                    colorSets={[\n                      {\n                        selectedColor: tl.outlineColor,\n                        setColor: v => updateLayerTextLabel(idx, 'outlineColor', v)\n                      }\n                    ]}\n                    useOpacity={true}\n                  />\n                </SidePanelSection>\n\n                <SidePanelSection>\n                  <SwitchWrapper>\n                    <PanelLabel>\n                      <FormattedMessage id=\"panel.text.backgroundColor\" />\n                    </PanelLabel>\n                    <Switch\n                      checked={tl.background}\n                      id={`${this.props.id}-textBackgroundEnabled-${idx}`}\n                      onChange={() => updateLayerTextLabel(idx, 'background', !tl.background)}\n                    />\n                  </SwitchWrapper>\n\n                  <ColorSelector\n                    colorSets={[\n                      {\n                        selectedColor: tl.backgroundColor,\n                        setColor: v => updateLayerTextLabel(idx, 'backgroundColor', v)\n                      }\n                    ]}\n                    useOpacity={true}\n                    disabled={!tl.background}\n                  />\n                </SidePanelSection>\n\n                <SidePanelSection>\n                  <SpaceBetweenFlexbox>\n                    <SBFlexboxItem>\n                      <PanelLabel>\n                        <FormattedMessage id=\"panel.text.textAnchor\" />\n                      </PanelLabel>\n                      <ItemSelector\n                        {...LAYER_TEXT_CONFIGS.textAnchor}\n                        selectedItems={tl.anchor}\n                        onChange={val => updateLayerTextLabel(idx, 'anchor', val)}\n                      />\n                    </SBFlexboxItem>\n                    <SBFlexboxItem>\n                      <PanelLabel>\n                        <FormattedMessage id=\"panel.text.alignment\" />\n                      </PanelLabel>\n                      <ItemSelector\n                        {...LAYER_TEXT_CONFIGS.textAlignment}\n                        selectedItems={tl.alignment}\n                        onChange={val => updateLayerTextLabel(idx, 'alignment', val)}\n                      />\n                    </SBFlexboxItem>\n                  </SpaceBetweenFlexbox>\n                </SidePanelSection>\n              </div>\n            ))}\n            <SidePanelSection>\n              <Button link onClick={() => updateLayerTextLabel(textLabel.length, '', null)}>\n                <Add height=\"12px\" />\n                <FormattedMessage id=\"panel.text.addMoreLabel\" />\n              </Button>\n            </SidePanelSection>\n          </ConfigGroupCollapsibleContent>\n        </LayerConfigGroup>\n      );\n    }\n  }\n\n  return TextLabelPanel;\n}\n\nexport default TextLabelPanelFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/vector-tile-layer-configurator.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport styled from 'styled-components';\n\nimport {VectorTileLayer} from '@kepler.gl/layers';\nimport {KeplerTable as KeplerDataset} from '@kepler.gl/table';\n\nimport SourceDataSelectorFactory from '../common/source-data-selector';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport ChannelByValueSelectorFactory from './channel-by-value-selector';\nimport LayerConfigGroupFactory, {ConfigGroupCollapsibleContent} from './layer-config-group';\nimport {LayerColorRangeSelectorFactory, LayerColorSelectorFactory} from './layer-color-selector';\nimport VisConfigByZoomInput from './radius-by-zoom-input';\nimport VisConfigSliderFactory from './vis-config-slider';\nimport VisConfigSwitchFactory from './vis-config-switch';\n\nconst StyledLayerConfigurator = styled.div`\n  margin-top: 12px;\n`;\n\ntype GetProps<T extends (...args: any[]) => React.FC<any>> = Parameters<ReturnType<T>>[0];\n\ntype Props = {\n  layer: VectorTileLayer;\n  dataset?: KeplerDataset;\n  visConfiguratorProps: any;\n  layerChannelConfigProps: GetProps<typeof ChannelByValueSelectorFactory>;\n  layerConfiguratorProps: any;\n};\n\nVectorTileLayerConfiguratorFactory.deps = [\n  ChannelByValueSelectorFactory,\n  LayerColorRangeSelectorFactory,\n  LayerColorSelectorFactory,\n  LayerConfigGroupFactory,\n  VisConfigSliderFactory,\n  VisConfigSwitchFactory,\n  SourceDataSelectorFactory,\n  FieldSelectorFactory\n];\n\nfunction VectorTileLayerConfiguratorFactory(\n  ChannelByValueSelector: ReturnType<typeof ChannelByValueSelectorFactory>,\n  LayerColorRangeSelector: ReturnType<typeof LayerColorRangeSelectorFactory>,\n  LayerColorSelector: ReturnType<typeof LayerColorSelectorFactory>,\n  LayerConfigGroup: ReturnType<typeof LayerConfigGroupFactory>,\n  VisConfigSlider: ReturnType<typeof VisConfigSliderFactory>,\n  VisConfigSwitch: ReturnType<typeof VisConfigSwitchFactory>,\n  _SourceDataSelector: ReturnType<typeof SourceDataSelectorFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>\n): React.FC<Props> {\n  const VectorTileLayerConfigurator = ({\n    layer,\n    visConfiguratorProps,\n    layerChannelConfigProps,\n    layerConfiguratorProps\n  }: Props) => {\n    return (\n      <StyledLayerConfigurator>\n        {/* Fill Color */}\n        <LayerConfigGroup {...visConfiguratorProps} label=\"layer.fillColor\">\n          {layerChannelConfigProps.fields ? (\n            <ChannelByValueSelector\n              {...layerChannelConfigProps}\n              channel={layer.visualChannels.color}\n            />\n          ) : null}\n          {layer.config.colorField ? (\n            <>\n              <VisConfigSwitch\n                {...visConfiguratorProps}\n                {...layer.visConfigSettings.dynamicColor}\n              />\n              <LayerColorRangeSelector\n                {...visConfiguratorProps}\n                channel={layer.visualChannels.color}\n              />\n            </>\n          ) : (\n            <LayerColorSelector {...layerConfiguratorProps} />\n          )}\n          <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} />\n        </LayerConfigGroup>\n\n        {/* Stroke color */}\n        <LayerConfigGroup\n          {...layer.visConfigSettings.stroked}\n          {...visConfiguratorProps}\n          label=\"layer.strokeColor\"\n          collapsible\n        >\n          <ChannelByValueSelector\n            {...layerChannelConfigProps}\n            channel={layer.visualChannels.strokeColor}\n          />\n          {layer.config.strokeColorField ? (\n            <LayerColorRangeSelector\n              {...visConfiguratorProps}\n              property=\"strokeColorRange\"\n              channel={layer.visualChannels.strokeColor}\n            />\n          ) : (\n            <LayerColorSelector\n              {...visConfiguratorProps}\n              selectedColor={layer.config.visConfig.strokeColor}\n              property=\"strokeColor\"\n            />\n          )}\n          <ConfigGroupCollapsibleContent>\n            <VisConfigSlider {...layer.visConfigSettings.strokeOpacity} {...visConfiguratorProps} />\n          </ConfigGroupCollapsibleContent>\n        </LayerConfigGroup>\n\n        {/* Stroke Width */}\n        <LayerConfigGroup\n          {...visConfiguratorProps}\n          {...layer.visConfigSettings.stroked}\n          label=\"layer.strokeWidth\"\n          collapsible\n        >\n          {layer.config.sizeField ? (\n            <VisConfigSlider\n              {...layer.visConfigSettings.sizeRange}\n              {...visConfiguratorProps}\n              label={false}\n            />\n          ) : (\n            <VisConfigSlider\n              {...layer.visConfigSettings.strokeWidth}\n              {...visConfiguratorProps}\n              label={false}\n            />\n          )}\n          <ConfigGroupCollapsibleContent>\n            <ChannelByValueSelector\n              {...layerChannelConfigProps}\n              channel={layer.visualChannels.size}\n            />\n          </ConfigGroupCollapsibleContent>\n        </LayerConfigGroup>\n\n        {/* Elevation */}\n        <LayerConfigGroup\n          {...visConfiguratorProps}\n          {...layer.visConfigSettings.enable3d}\n          collapsible\n        >\n          <VisConfigSlider\n            {...layer.visConfigSettings.elevationScale}\n            {...visConfiguratorProps}\n            label={false}\n          />\n          {layerChannelConfigProps.fields ? (\n            <ChannelByValueSelector\n              {...layerChannelConfigProps}\n              channel={layer.visualChannels.height}\n            />\n          ) : null}\n        </LayerConfigGroup>\n\n        {/* Radius */}\n        <LayerConfigGroup\n          {...visConfiguratorProps}\n          label={'layer.radius'}\n          description=\"Point radius in pixels or meters\"\n          collapsible\n        >\n          {layer.config.visConfig.radiusByZoom?.enabled && visConfiguratorProps.onChange ? (\n            <VisConfigByZoomInput\n              config={layer.config.visConfig.radiusByZoom}\n              onChange={visConfiguratorProps.onChange}\n              label=\"Radius\"\n              property=\"radiusByZoom\"\n              unit=\"px\"\n            />\n          ) : (\n            <VisConfigSlider\n              {...layer.visConfigSettings.radius}\n              {...visConfiguratorProps}\n              label={false}\n            />\n          )}\n\n          <ConfigGroupCollapsibleContent>\n            {layerChannelConfigProps.fields ? (\n              <ChannelByValueSelector\n                {...layerChannelConfigProps}\n                channel={layer.visualChannels.radius}\n              />\n            ) : null}\n            <VisConfigSwitch {...layer.visConfigSettings.radiusUnits} {...visConfiguratorProps} />\n          </ConfigGroupCollapsibleContent>\n        </LayerConfigGroup>\n\n        {/* Unique ID Field */}\n        <LayerConfigGroup {...visConfiguratorProps} label=\"layer.uniqueIdField\">\n          <FieldSelector\n            fields={layerChannelConfigProps.fields || []}\n            value={layer.config.uniqueIdField || null}\n            onSelect={(val: any) =>\n              layerConfiguratorProps.onChange?.({uniqueIdField: val?.name || null})\n            }\n            placeholder={'placeholder.selectField'}\n            erasable\n          />\n        </LayerConfigGroup>\n      </StyledLayerConfigurator>\n    );\n  };\n\n  const ConnectedVectorTileLayerConfigurator = connect(state => state)(VectorTileLayerConfigurator);\n  return ConnectedVectorTileLayerConfigurator;\n}\n\nexport default VectorTileLayerConfiguratorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/vis-config-by-field-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';\n\nimport {Field} from '@kepler.gl/types';\n\nimport {camelize} from '@kepler.gl/utils';\nimport FieldSelectorFactory from '../../common/field-selector';\nimport InfoHelperFactory from '../../common/info-helper';\nimport {PanelLabel, PanelLabelWrapper, SidePanelSection} from '../../common/styled-components';\n\ntype VisConfigByFieldSelectorProps = {\n  fields: Field[];\n  id: string;\n  property: string;\n  updateField: (val: string | Field | null) => void;\n  scaleType?: string;\n  selectedField?: Field;\n  description?: string;\n  label?: string;\n  placeholder?: string;\n  disabled?: boolean;\n} & WrappedComponentProps;\n\nVisConfigByFieldSelectorFactory.deps = [InfoHelperFactory, FieldSelectorFactory];\n\nfunction VisConfigByFieldSelectorFactory(\n  InfoHelper: ReturnType<typeof InfoHelperFactory>,\n  FieldSelector: ReturnType<typeof FieldSelectorFactory>\n) {\n  const VisConfigByFieldSelector: React.FC<VisConfigByFieldSelectorProps> = ({\n    id,\n    property,\n    selectedField,\n    description,\n    label,\n    intl,\n    updateField,\n    fields,\n    placeholder,\n    disabled\n  }) => {\n    return (\n      <SidePanelSection disabled={disabled}>\n        <PanelLabelWrapper>\n          <PanelLabel>\n            {(label && <FormattedMessage id={label} />) || (\n              <FormattedMessage\n                id=\"layer.propertyBasedOn\"\n                values={{\n                  property: intl.formatMessage({\n                    id: `property.${camelize(property)}`,\n                    defaultMessage: property\n                  })\n                }}\n              />\n            )}\n          </PanelLabel>\n          {description && (\n            <InfoHelper description={description} property={property} id={`${id}-${property}`} />\n          )}\n        </PanelLabelWrapper>\n        <FieldSelector\n          fields={fields}\n          value={selectedField && selectedField.name}\n          placeholder={placeholder}\n          onSelect={items => updateField(Array.isArray(items) ? items?.[0] : items)}\n          disabled={disabled}\n          erasable\n        />\n      </SidePanelSection>\n    );\n  };\n  return injectIntl(VisConfigByFieldSelector);\n}\n\nexport default VisConfigByFieldSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/vis-config-slider.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, useCallback, useEffect, useRef} from 'react';\nimport styled from 'styled-components';\n\nimport {PanelLabel, SidePanelSection} from '../../common/styled-components';\nimport RangeSliderFactory from '../../common/range-slider';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {KeyEvent} from '@kepler.gl/constants';\nimport {Checkbox} from '../..';\nimport {Layer, LayerBaseConfig} from '@kepler.gl/layers';\nimport {isInRange, clamp} from '@kepler.gl/utils';\n\ntype LazyInputProps = {\n  value: string | [string, string];\n  name: string;\n  onChange: (n: string | [string, string], v?: string | [string, string]) => void;\n};\n\ntype CustomInputProps = {\n  value: string | [string, string];\n  isRanged: boolean;\n  onChangeCustomInput: (v: [string, string]) => void;\n};\n\ntype VisConfigSliderProps = {\n  layer: Layer;\n  property: string;\n  onChange: (v: Record<string, number | string | number[] | string[]>) => void;\n  label?: string | ((c: LayerBaseConfig) => string);\n  range: [number, number];\n  step?: number;\n  isRanged: boolean;\n  disabled?: boolean;\n  inputTheme?: string;\n  allowCustomValue?: boolean;\n};\n\nconst InputWrapper = styled.div`\n  display: flex;\n  line-height: 12px;\n  margin-bottom: 12px;\n`;\n\nconst CustomInputWrapper = styled.div`\n  display: flex;\n`;\n\nconst CustomInputLabel = styled.label`\n  color: ${props => props.theme.textColor};\n  font-weight: 500;\n  letter-spacing: 0.2px;\n  font-size: ${props => props.theme.layerConfigGroupLabelLabelFontSize};\n  padding-right: 15px;\n\n  &:last-child {\n    position: absolute;\n    right: 0;\n    padding: 0;\n  }\n`;\n\nconst RangeInput = styled.input`\n  ${props => props.theme.input};\n  font-size: ${props => props.theme.sliderInputFontSize};\n  width: ${props => props.theme.customRangeInputWidth}px;\n  overflow: auto;\n  height: 20px;\n  margin-top: 5px;\n`;\n\nconst LazyInput: React.FC<LazyInputProps> = ({value, onChange, name}) => {\n  const [stateValue, setValue] = useState(value);\n  const inputRef = useRef(null);\n  useEffect(() => {\n    setValue(value);\n  }, [value]);\n\n  const onKeyDown = useCallback(\n    e => {\n      switch (e.keyCode) {\n        case KeyEvent.DOM_VK_ENTER:\n        case KeyEvent.DOM_VK_RETURN:\n          onChange(name, stateValue);\n          if (inputRef !== null) {\n            // @ts-ignore\n            inputRef?.current.blur();\n          }\n          break;\n        default:\n          break;\n      }\n    },\n    [onChange, name, stateValue]\n  );\n\n  const _onChange = useCallback(e => setValue(e.target.value), [setValue]);\n  const onBlur = useCallback(() => onChange(name, stateValue), [onChange, name, stateValue]);\n\n  return (\n    <RangeInput\n      type=\"number\"\n      ref={inputRef}\n      value={stateValue}\n      onChange={_onChange}\n      onBlur={onBlur}\n      onKeyDown={onKeyDown}\n      id={name}\n    />\n  );\n};\n\nconst CustomInput: React.FC<CustomInputProps> = ({isRanged, value, onChangeCustomInput}) => {\n  const onChangeInput = useCallback(\n    (name, v) => {\n      const prevValue = isRanged ? (name === 'value1' ? value[0] : value[1]) : value;\n      const valueAsNumber = Number(v);\n      const convertedValue =\n        typeof prevValue === 'number' ? (isNaN(valueAsNumber) ? prevValue : valueAsNumber) : v;\n      if (isRanged)\n        onChangeCustomInput(\n          name === 'value0' ? [convertedValue, value[1]] : [value[0], convertedValue]\n        );\n      else onChangeCustomInput(convertedValue);\n    },\n    [isRanged, value, onChangeCustomInput]\n  );\n\n  return (\n    <CustomInputWrapper>\n      {isRanged ? (\n        <InputWrapper>\n          <CustomInputLabel>\n            min\n            <LazyInput name=\"value0\" value={value[0]} onChange={onChangeInput} />\n          </CustomInputLabel>\n          <CustomInputLabel>\n            max\n            <LazyInput name=\"value1\" value={value[1]} onChange={onChangeInput} />\n          </CustomInputLabel>\n        </InputWrapper>\n      ) : (\n        <InputWrapper>\n          <LazyInput name=\"value\" value={value} onChange={onChangeInput} />\n        </InputWrapper>\n      )}\n    </CustomInputWrapper>\n  );\n};\n\nVisConfigSliderFactory.deps = [RangeSliderFactory];\n\nexport default function VisConfigSliderFactory(RangeSlider: ReturnType<typeof RangeSliderFactory>) {\n  const VisConfigSlider: React.FC<VisConfigSliderProps> = ({\n    layer: {config},\n    property,\n    label,\n    range,\n    step,\n    isRanged,\n    allowCustomValue,\n    disabled,\n    onChange,\n    inputTheme\n  }) => {\n    const value = config.visConfig[property];\n    const [custom, setCustom] = useState(false || !isInRange(value, range));\n\n    const onChangeCheckbox = useCallback(() => {\n      if (custom) {\n        // we are swithcing from custom to not custom\n        // adjust value to range\n        const adjustedValue = isRanged\n          ? [clamp(range, value[0]), clamp(range, value[1])]\n          : clamp(range, value);\n        onChange({[property]: adjustedValue});\n      }\n      setCustom(!custom);\n    }, [onChange, property, isRanged, value, range, custom, setCustom]);\n\n    return (\n      <SidePanelSection disabled={Boolean(disabled)}>\n        {label ? (\n          <PanelLabel>\n            {typeof label === 'string' ? (\n              <FormattedMessage id={label} />\n            ) : typeof label === 'function' ? (\n              <FormattedMessage id={label(config)} />\n            ) : (\n              <FormattedMessage id={`property.${property}`} />\n            )}\n          </PanelLabel>\n        ) : null}\n\n        {allowCustomValue ? (\n          <InputWrapper>\n            <CustomInputLabel>custom input</CustomInputLabel>\n            <Checkbox id={`property.${property}`} checked={custom} onChange={onChangeCheckbox} />\n          </InputWrapper>\n        ) : null}\n\n        {!custom ? (\n          <RangeSlider\n            range={range}\n            value0={isRanged ? value[0] : range[0]}\n            value1={isRanged ? value[1] : value}\n            step={step}\n            isRanged={Boolean(isRanged)}\n            onChange={v => onChange({[property]: isRanged ? v : v[1]})}\n            inputTheme={inputTheme}\n            showInput\n          />\n        ) : (\n          <CustomInput\n            isRanged={isRanged}\n            value={value}\n            onChangeCustomInput={v => onChange({[property]: v})}\n          />\n        )}\n      </SidePanelSection>\n    );\n  };\n\n  return VisConfigSlider;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/layer-panel/vis-config-switch.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport InfoHelperFactory from '../../common/info-helper';\nimport Switch from '../../common/switch';\nimport {SidePanelSection, PanelLabel} from '../../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {capitalizeFirstLetter} from '@kepler.gl/utils';\nimport {Layer} from '@kepler.gl/layers';\n\ntype VisConfigSwitchProps = {\n  layer: Layer;\n  property: string;\n  onChange: (v: Record<string, boolean>) => void;\n  label?: string;\n  description?: string;\n  disabled?: boolean;\n};\n\nconst StyledVisConfigSwitch = styled.div`\n  display: flex;\n  justify-content: space-between;\n\n  .vis-config-switch__title {\n    display: flex;\n  }\n`;\n\nVisConfigSwitchFactory.deps = [InfoHelperFactory];\nfunction VisConfigSwitchFactory(InfoHelper: ReturnType<typeof InfoHelperFactory>) {\n  const VisConfigSwitch: React.FC<VisConfigSwitchProps> = ({\n    layer: {id, config},\n    property,\n    onChange,\n    label,\n    description,\n    disabled\n  }) => (\n    <SidePanelSection disabled={Boolean(disabled)}>\n      <StyledVisConfigSwitch className=\"vis-config-switch\">\n        <div className=\"vis-config-switch__title\">\n          {label ? (\n            <PanelLabel>\n              {(label && <FormattedMessage id={label} />) || capitalizeFirstLetter(property)}\n            </PanelLabel>\n          ) : null}\n          {description ? (\n            <div>\n              <InfoHelper description={description} id={`${id}-${property}-description`} />\n            </div>\n          ) : null}\n        </div>\n        <div className=\"vis-config-switch__switch\">\n          <Switch\n            checked={config.visConfig[property]}\n            id={`${id}-${property}-switch`}\n            onChange={() => onChange({[property]: !config.visConfig[property]})}\n          />\n        </div>\n      </StyledVisConfigSwitch>\n    </SidePanelSection>\n  );\n\n  return VisConfigSwitch;\n}\nexport default VisConfigSwitchFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/map-manager.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useState, useMemo, useCallback} from 'react';\n\nimport {Button} from '../common/styled-components';\nimport MapStyleSelectorFactory from './map-style-panel/map-style-selector';\nimport LayerGroupSelectorFactory from './map-style-panel/map-layer-selector';\nimport PanelTitleFactory from '../side-panel/panel-title';\n\nimport {Add, Trash} from '../common/icons';\nimport {PanelMeta} from './common/types';\nimport {injectIntl, WrappedComponentProps} from 'react-intl';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {MapStyle} from '@kepler.gl/reducers';\nimport {MapStyleActions} from '@kepler.gl/actions';\n\nexport type MapManagerProps = {\n  mapStyle: MapStyle;\n  mapStyleActions: {\n    mapStyleChange: typeof MapStyleActions.mapStyleChange;\n    mapConfigChange: typeof MapStyleActions.mapConfigChange;\n    set3dBuildingColor: typeof MapStyleActions.set3dBuildingColor;\n    setBackgroundColor: typeof MapStyleActions.setBackgroundColor;\n    removeCustomMapStyle: typeof MapStyleActions.removeCustomMapStyle;\n  };\n  showAddMapStyleModal: () => void;\n  panelMetadata: PanelMeta;\n} & WrappedComponentProps;\n\nMapManagerFactory.deps = [MapStyleSelectorFactory, LayerGroupSelectorFactory, PanelTitleFactory];\n\nfunction MapManagerFactory(\n  MapStyleSelector: ReturnType<typeof MapStyleSelectorFactory>,\n  LayerGroupSelector: ReturnType<typeof LayerGroupSelectorFactory>,\n  PanelTitle: ReturnType<typeof PanelTitleFactory>\n) {\n  const MapManager: React.FC<MapManagerProps> = ({\n    mapStyle,\n    intl,\n    mapStyleActions,\n    showAddMapStyleModal,\n    panelMetadata\n  }) => {\n    const [isSelecting, setIsSelecting] = useState(false);\n    const {mapStyleChange, removeCustomMapStyle} = mapStyleActions;\n    const currentStyle = mapStyle.mapStyles[mapStyle.styleType] || {};\n    const editableLayers = currentStyle.layerGroups || [];\n\n    const toggleSelecting = useCallback(() => {\n      setIsSelecting(prev => !prev);\n    }, [setIsSelecting]);\n\n    const mapStyles = mapStyle.mapStyles;\n    const selectStyle = useCallback(\n      (val: string) => {\n        mapStyleChange(val);\n        setIsSelecting(false);\n      },\n      [mapStyleChange, setIsSelecting]\n    );\n\n    const customMapStylesActions = useMemo(() => {\n      const actionsPerCustomStyle = {};\n      Object.values(mapStyles)\n        .filter(mapStyle => {\n          return Boolean(mapStyle.custom);\n        })\n        .forEach(({id}) => {\n          actionsPerCustomStyle[id] = [\n            {\n              id: `remove-map-style-${id}`,\n              IconComponent: Trash,\n              tooltip: 'tooltip.removeBaseMapStyle',\n              onClick: () => removeCustomMapStyle({id})\n            }\n          ];\n        });\n      return actionsPerCustomStyle;\n    }, [mapStyles, removeCustomMapStyle]);\n\n    return (\n      <div className=\"map-style-panel\">\n        <PanelTitle\n          className=\"map-manager-title\"\n          title={intl.formatMessage({id: panelMetadata.label})}\n        >\n          <Button className=\"add-map-style-button\" onClick={showAddMapStyleModal}>\n            <Add height=\"12px\" />\n            <FormattedMessage id={'mapManager.addMapStyle'} />\n          </Button>\n        </PanelTitle>\n        <div>\n          <MapStyleSelector\n            mapStyle={mapStyle}\n            isSelecting={isSelecting}\n            onChange={selectStyle}\n            toggleActive={toggleSelecting}\n            customMapStylesActions={customMapStylesActions}\n          />\n          {editableLayers.length ? (\n            <LayerGroupSelector\n              layers={mapStyle.visibleLayerGroups}\n              editableLayers={editableLayers}\n              topLayers={mapStyle.topLayerGroups}\n              onChange={mapStyleActions.mapConfigChange}\n              threeDBuildingColor={mapStyle.threeDBuildingColor}\n              on3dBuildingColorChange={mapStyleActions.set3dBuildingColor}\n              backgroundColor={mapStyle.backgroundColor}\n              onBackgroundColorChange={mapStyleActions.setBackgroundColor}\n            />\n          ) : null}\n        </div>\n      </div>\n    );\n  };\n\n  return injectIntl(MapManager);\n}\n\nexport default MapManagerFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/map-style-panel/map-layer-group-color-picker.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled, {css} from 'styled-components';\n\nimport {rgbToHex} from '@kepler.gl/utils';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {RGBColor} from '@kepler.gl/types';\n\nimport {Portaled} from '../..';\nimport {Tooltip} from '../../common/styled-components';\nimport CustomPicker from '../layer-panel/custom-picker';\nimport {ColorBlock} from '../layer-panel/color-selector';\n\nconst LayerGroupColorPickerWrapper = styled.div<{$extraMarginRight?: boolean; disabled?: boolean}>`\n  margin-right: ${props => (props.$extraMarginRight ? 0 : 24)}px;\n  cursor: pointer;\n  ${props =>\n    props.disabled &&\n    css`\n      cursor: none;\n      pointer-events: none;\n      opacity: 0.3;\n    `}\n`;\n\nexport type LayerGroupColorPickerProps = {\n  slug: string;\n  color: RGBColor;\n  onColorChange: (pd: RGBColor) => void;\n  extraMarginRight: boolean;\n  disabled: boolean;\n};\n\nLayerGroupColorPickerFactory.deps = [];\n\nfunction LayerGroupColorPickerFactory() {\n  const LayerGroupColorPicker: React.FC<LayerGroupColorPickerProps> = ({\n    slug,\n    color,\n    onColorChange,\n    extraMarginRight,\n    disabled\n  }) => {\n    const [displayColorPicker, setDisplayColorPicker] = useState(false);\n\n    const onColorBlockClick = useCallback(() => {\n      setDisplayColorPicker(!displayColorPicker);\n    }, [setDisplayColorPicker, displayColorPicker]);\n\n    const onClosePicker = useCallback(() => {\n      setDisplayColorPicker(false);\n    }, [setDisplayColorPicker]);\n\n    const onCustomPickerChange = useCallback(\n      newColor => {\n        onColorChange([newColor.rgb.r, newColor.rgb.g, newColor.rgb.b]);\n      },\n      [onColorChange]\n    );\n\n    return (\n      <LayerGroupColorPickerWrapper $extraMarginRight={extraMarginRight} disabled={disabled}>\n        <ColorBlock\n          backgroundcolor={color}\n          onClick={onColorBlockClick}\n          className=\"color-selector__selector__block\"\n          data-tip\n          data-for={`update-color-${slug}`}\n        />\n        <Tooltip id={`update-color-${slug}`} effect=\"solid\" delayShow={500}>\n          <span>\n            <FormattedMessage id={'Update color'} />\n          </span>\n        </Tooltip>\n        <Portaled\n          isOpened={displayColorPicker !== false}\n          left={110}\n          top={-50}\n          onClose={onClosePicker}\n        >\n          <CustomPicker color={rgbToHex(color)} onChange={onCustomPickerChange} />\n        </Portaled>\n      </LayerGroupColorPickerWrapper>\n    );\n  };\n\n  return LayerGroupColorPicker;\n}\n\nexport default LayerGroupColorPickerFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/map-style-panel/map-layer-group-item.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {camelize} from '@kepler.gl/utils';\nimport {RGBColor} from '@kepler.gl/types';\nimport {MapConfigChangeUpdaterAction} from '@kepler.gl/actions';\nimport {MapStyle} from '@kepler.gl/reducers';\n\nimport LayerGroupColorPickerFactory from './map-layer-group-color-picker';\nimport {PanelHeaderActionProps, PanelHeaderActionIcon} from '../panel-header-action';\nimport {PanelLabelBold, CenterFlexbox, PanelLabelWrapper} from '../../common/styled-components';\n\nconst StyledLayerGroupItem = styled.div`\n  margin-bottom: 10px;\n  display: flex;\n  justify-content: space-between;\n\n  &:last-child {\n    margin-bottom: 0;\n  }\n\n  .layer-group__visibility-toggle {\n    margin-right: 12px;\n  }\n`;\n\nconst LayerLabel = styled(PanelLabelBold)<{$active: boolean}>`\n  color: ${props => (props.$active ? props.theme.textColor : props.theme.labelColor)};\n`;\n\nexport type LayerGroupItemActionIcons = {\n  visible: PanelHeaderActionIcon;\n  hidden: PanelHeaderActionIcon;\n  top: PanelHeaderActionIcon;\n};\n\nexport type LayerGroupItemProps = {\n  PanelHeaderAction: React.FC<PanelHeaderActionProps>;\n  onChange: (pd: MapConfigChangeUpdaterAction['payload']) => void;\n  slug: string;\n  layers: MapStyle['visibleLayerGroups'];\n  topLayers: MapStyle['topLayerGroups'];\n  actionIcons: LayerGroupItemActionIcons;\n  color: RGBColor | null;\n  onColorChange: (pd: RGBColor) => void;\n  isVisibilityToggleAvailable?: boolean;\n  isMoveToTopAvailable?: boolean;\n  isColorPickerAvailable?: boolean;\n};\n\nLayerGroupItemFactory.deps = [LayerGroupColorPickerFactory];\n\nfunction LayerGroupItemFactory(LayerGroupColorPicker) {\n  const LayerGroupItem: React.FC<LayerGroupItemProps> = ({\n    PanelHeaderAction,\n    onChange,\n    slug,\n    layers,\n    topLayers,\n    actionIcons,\n    color,\n    onColorChange,\n    isVisibilityToggleAvailable = true,\n    isMoveToTopAvailable = true,\n    isColorPickerAvailable = false\n  }) => {\n    const onVisibilityToggle = useCallback(() => {\n      onChange({\n        visibleLayerGroups: {\n          ...layers,\n          [slug]: !layers[slug]\n        }\n      });\n    }, [onChange, layers, slug]);\n\n    const onMoveToTopToggle = useCallback(() => {\n      onChange({\n        topLayerGroups: {\n          ...topLayers,\n          [slug]: !topLayers[slug]\n        }\n      });\n    }, [onChange, topLayers, slug]);\n\n    return (\n      <StyledLayerGroupItem className=\"layer-group__select\">\n        {isVisibilityToggleAvailable ? (\n          <PanelLabelWrapper>\n            <PanelHeaderAction\n              className=\"layer-group__visibility-toggle\"\n              id={`${slug}-toggle`}\n              tooltip={layers[slug] ? 'tooltip.hide' : 'tooltip.show'}\n              onClick={onVisibilityToggle}\n              IconComponent={layers[slug] ? actionIcons.visible : actionIcons.hidden}\n              active={layers[slug]}\n              flush\n            />\n            <LayerLabel $active={layers[slug]}>\n              <FormattedMessage id={`mapLayers.${camelize(slug)}`} />\n            </LayerLabel>\n          </PanelLabelWrapper>\n        ) : (\n          <CenterFlexbox>\n            <LayerLabel style={{marginLeft: '28px'}} $active={true}>\n              <FormattedMessage id={`mapLayers.${camelize(slug)}`} />\n            </LayerLabel>\n          </CenterFlexbox>\n        )}\n        <CenterFlexbox className=\"layer-group__trailing-actions\">\n          {isColorPickerAvailable && color ? (\n            <LayerGroupColorPicker\n              slug={slug}\n              color={color}\n              onColorChange={onColorChange}\n              extraMarginRight={isMoveToTopAvailable}\n              disabled={isVisibilityToggleAvailable && !layers[slug]}\n            />\n          ) : null}\n          {isMoveToTopAvailable ? (\n            <PanelHeaderAction\n              id={`${slug}-top`}\n              tooltip=\"tooltip.moveToTop\"\n              disabled={!layers[slug]}\n              IconComponent={actionIcons.top}\n              active={topLayers[slug]}\n              onClick={onMoveToTopToggle}\n            />\n          ) : null}\n        </CenterFlexbox>\n      </StyledLayerGroupItem>\n    );\n  };\n\n  return LayerGroupItem;\n}\n\nexport default LayerGroupItemFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/map-style-panel/map-layer-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport PanelHeaderActionFactory from '../panel-header-action';\nimport LayerGroupItemFactory, {LayerGroupItemActionIcons} from './map-layer-group-item';\nimport {EyeSeen, EyeUnseen} from '../../common/icons';\n\nimport {PanelLabel, PanelContent} from '../../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {VisibleLayerGroups} from '@kepler.gl/types';\nimport {Upload} from '@kepler.gl/cloud-providers';\nimport {\n  THREE_D_BUILDING_LAYER_GROUP_SLUG,\n  BACKGROUND_LAYER_GROUP_SLUG,\n  DEFAULT_LAYER_GROUP\n} from '@kepler.gl/constants';\nimport {\n  MapConfigChangeUpdaterAction,\n  Set3dBuildingColorUpdaterAction,\n  SetBackgroundColorUpdaterAction\n} from '@kepler.gl/actions';\nimport {MapStyle} from '@kepler.gl/reducers';\n\nfunction noop() {\n  return;\n}\n\nconst StyledInteractionPanel = styled.div`\n  padding-bottom: 12px;\n`;\n\nexport type LayerGroupSelectorProps = {\n  layers: VisibleLayerGroups;\n  editableLayers: DEFAULT_LAYER_GROUP[];\n  onChange: (payload: MapConfigChangeUpdaterAction['payload']) => void;\n  topLayers: MapStyle['topLayerGroups'];\n  threeDBuildingColor: MapStyle['threeDBuildingColor'];\n  on3dBuildingColorChange: (pd: Set3dBuildingColorUpdaterAction['payload']) => void;\n  backgroundColor: MapStyle['backgroundColor'];\n  onBackgroundColorChange: (pd: SetBackgroundColorUpdaterAction['payload']) => void;\n  actionIcons?: LayerGroupItemActionIcons;\n};\n\nLayerGroupSelectorFactory.deps = [PanelHeaderActionFactory, LayerGroupItemFactory];\n\nfunction LayerGroupSelectorFactory(\n  PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>,\n  LayerGroupItem: ReturnType<typeof LayerGroupItemFactory>\n) {\n  const defaultActionIcons: LayerGroupItemActionIcons = {\n    visible: EyeSeen,\n    hidden: EyeUnseen,\n    top: Upload\n  };\n  const LayerGroupSelector = ({\n    layers,\n    editableLayers,\n    onChange,\n    topLayers,\n    threeDBuildingColor,\n    on3dBuildingColorChange,\n    backgroundColor,\n    onBackgroundColorChange,\n    actionIcons = defaultActionIcons\n  }: LayerGroupSelectorProps) => {\n    return (\n      <StyledInteractionPanel className=\"map-style__layer-group__selector\">\n        <div className=\"layer-group__header\">\n          <PanelLabel>\n            <FormattedMessage id={'mapLayers.title'} />\n          </PanelLabel>\n        </div>\n        <PanelContent className=\"map-style__layer-group\">\n          {editableLayers.map(\n            ({slug, isVisibilityToggleAvailable, isMoveToTopAvailable, isColorPickerAvailable}) => (\n              <LayerGroupItem\n                key={slug}\n                PanelHeaderAction={PanelHeaderAction}\n                onChange={onChange}\n                slug={slug}\n                layers={layers}\n                topLayers={topLayers}\n                actionIcons={actionIcons}\n                isVisibilityToggleAvailable={isVisibilityToggleAvailable}\n                isMoveToTopAvailable={isMoveToTopAvailable}\n                isColorPickerAvailable={isColorPickerAvailable}\n                color={\n                  isColorPickerAvailable && slug === THREE_D_BUILDING_LAYER_GROUP_SLUG\n                    ? threeDBuildingColor\n                    : slug === BACKGROUND_LAYER_GROUP_SLUG\n                    ? backgroundColor\n                    : null\n                }\n                onColorChange={\n                  isColorPickerAvailable && slug === THREE_D_BUILDING_LAYER_GROUP_SLUG\n                    ? on3dBuildingColorChange\n                    : slug === BACKGROUND_LAYER_GROUP_SLUG\n                    ? onBackgroundColorChange\n                    : noop\n                }\n              />\n            )\n          )}\n        </PanelContent>\n      </StyledInteractionPanel>\n    );\n  };\n\n  return LayerGroupSelector;\n}\n\nexport default LayerGroupSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/map-style-panel/map-style-selector.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {MapStyle} from '@kepler.gl/reducers';\nimport {NO_BASEMAP_ICON} from '@kepler.gl/constants';\nimport {MapStyles} from '@kepler.gl/types';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {ArrowDown} from '../../common/icons';\nimport PanelHeaderActionFactory from '../panel-header-action';\nimport {\n  PanelHeaderContent,\n  PanelHeaderTitle,\n  PanelLabel,\n  StyledPanelHeader,\n  StyledPanelHeaderProps\n} from '../../common/styled-components';\nimport {BaseProps} from '../../common/icons';\nimport {PanelHeaderActionIcon} from '../panel-header-action';\n\ntype StyledMapDropdownProps = StyledPanelHeaderProps & {hasCallout: boolean};\n\nconst StyledMapDropdown = styled(StyledPanelHeader)<StyledMapDropdownProps>`\n  height: 48px;\n  margin-bottom: 5px;\n  opacity: 1;\n  position: relative;\n  transition: opacity 0.05s ease-in, height 0.25s ease-out;\n\n  &.collapsed {\n    height: 0;\n    margin-bottom: 0;\n    opacity: 0;\n  }\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.panelBackgroundHover};\n  }\n\n  .map-title-block img {\n    margin-right: 12px;\n  }\n\n  .map-preview {\n    border-radius: 3px;\n    height: 30px;\n    width: 40px;\n  }\n\n  &.selected {\n    outline: 1px solid #caf2f4;\n  }\n\n  /* show callout dot if props.hasCallout and theme provides calloutDot base styles */\n  &:after {\n    ${({theme}) => theme.calloutDot}\n    background-color: #00ACF5;\n    top: 12px;\n    left: 15px;\n    display: ${({theme, hasCallout}) => (theme.calloutDot && hasCallout ? 'block' : 'none')};\n  }\n\n  .custom-style-actions {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n`;\n\nexport type MapStyleSelectorProps = {\n  mapStyle: MapStyle;\n  onChange: (payload: string) => void;\n  toggleActive: () => void;\n  isSelecting: boolean;\n  customMapStylesActions?: Record<\n    string,\n    {\n      id: string;\n      IconComponent: PanelHeaderActionIcon;\n      tooltip: string;\n      onClick: () => void;\n    }[]\n  >;\n  actionIcons?: Record<string, ComponentType<Partial<BaseProps>>>;\n};\n\nMapStyleSelectorFactory.deps = [PanelHeaderActionFactory];\n\nfunction MapStyleSelectorFactory(PanelHeaderAction: ReturnType<typeof PanelHeaderActionFactory>) {\n  const defaultActionIcons = {\n    arrowDown: ArrowDown\n  };\n\n  const MapStyleSelector = ({\n    mapStyle,\n    onChange,\n    toggleActive,\n    isSelecting,\n    customMapStylesActions,\n    actionIcons = defaultActionIcons\n  }: MapStyleSelectorProps) => {\n    const {mapStyles, styleType}: {mapStyles: MapStyles; styleType: string} = mapStyle;\n\n    return (\n      <div>\n        <PanelLabel>\n          <FormattedMessage id={'mapManager.mapStyle'} />\n        </PanelLabel>\n\n        {Object.values(mapStyles).map(\n          ({\n            id,\n            custom,\n            icon = `${getApplicationConfig().cdnUrl}/${NO_BASEMAP_ICON}`,\n            label = 'Untitled'\n          }) => (\n            <StyledMapDropdown\n              className={classnames('map-dropdown-option', {\n                collapsed: !isSelecting && id !== styleType,\n                selected: isSelecting && id === styleType\n              })}\n              key={id}\n              onClick={isSelecting ? () => onChange(id) : toggleActive}\n              hasCallout={Boolean(custom)}\n            >\n              <PanelHeaderContent className=\"map-title-block\">\n                <img className=\"map-preview\" src={icon} />\n                <PanelHeaderTitle className=\"map-preview-name\">{label}</PanelHeaderTitle>\n              </PanelHeaderContent>\n              {!isSelecting ? (\n                <PanelHeaderAction\n                  className=\"map-dropdown-option__enable-config\"\n                  id=\"map-enable-config\"\n                  IconComponent={actionIcons.arrowDown}\n                  tooltip={'tooltip.selectBaseMapStyle'}\n                  onClick={toggleActive}\n                />\n              ) : null}\n              {isSelecting && custom ? (\n                <div className=\"custom-style-actions\">\n                  {(customMapStylesActions?.[id] || []).map(action => (\n                    <PanelHeaderAction\n                      key={action.id}\n                      className=\"map-dropdown-option__enable-config\"\n                      id={action.id}\n                      IconComponent={action.IconComponent}\n                      tooltip={action.tooltip}\n                      onClick={e => {\n                        e.stopPropagation();\n                        action.onClick();\n                      }}\n                    />\n                  ))}\n                </div>\n              ) : null}\n            </StyledMapDropdown>\n          )\n        )}\n      </div>\n    );\n  };\n\n  return MapStyleSelector;\n}\n\nexport default MapStyleSelectorFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/panel-header-action.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {ComponentType, MouseEventHandler} from 'react';\nimport {TooltipProps} from 'react-tooltip';\nimport classnames from 'classnames';\nimport styled from 'styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {Tooltip} from '../common/styled-components';\nimport {BaseProps} from '../common/icons';\nimport {BaseComponentProps} from '../types';\n\nexport type PanelHeaderActionIcon = ComponentType<Partial<BaseProps>>;\n\nexport interface PanelHeaderActionProps {\n  id?: string;\n  tooltip?: string;\n  hoverColor?: string;\n  className?: string;\n  active?: boolean;\n  flush?: boolean;\n  disabled?: boolean;\n  onClick?: MouseEventHandler;\n  tooltipType?: TooltipProps['type'];\n  IconComponent: PanelHeaderActionIcon;\n  testId?: string;\n}\n\ntype HeaderActionWrapperProps = {\n  $flush?: boolean;\n  $active?: boolean;\n  $hoverColor?: string | null;\n  $dataTestId?: any;\n};\n\ntype HeaderActionWrapperCssProps = {$testId?: string} & HeaderActionWrapperProps &\n  BaseComponentProps;\n\nconst HeaderActionWrapper = styled.div.attrs<HeaderActionWrapperCssProps>(props => ({\n  $dataTestId: props.$testId\n}))<HeaderActionWrapperProps>`\n  margin-left: ${props => (props.$flush ? 0 : 8)}px;\n  display: flex;\n  align-items: center;\n  color: ${props =>\n    props.$active ? props.theme.panelHeaderIconActive : props.theme.panelHeaderIcon};\n\n  cursor: pointer;\n\n  &:hover {\n    color: ${props =>\n      props.$hoverColor ? props.theme[props.$hoverColor] : props.theme.panelHeaderIconHover};\n  }\n\n  &.disabled {\n    cursor: none;\n    pointer-events: none;\n    opacity: 0.3;\n  }\n`;\n\nPanelHeaderActionFactory.deps = [];\n// Need to use react class to access props.component\nexport default function PanelHeaderActionFactory(): React.FC<PanelHeaderActionProps> {\n  const PanelHeaderActionUnmemoized: React.FC<PanelHeaderActionProps> = ({\n    onClick,\n    tooltip,\n    id,\n    active = false,\n    flush,\n    hoverColor,\n    tooltipType,\n    disabled,\n    className,\n    IconComponent,\n    testId\n  }) => {\n    return (\n      <HeaderActionWrapper\n        className={classnames('panel--header__action', {\n          disabled,\n          ...(className ? {[className]: true} : {})\n        })}\n        $active={active}\n        $hoverColor={hoverColor}\n        $flush={flush}\n      >\n        {IconComponent ? (\n          <IconComponent\n            className=\"panel--header__action__component\"\n            data-testid={testId}\n            data-tip\n            data-for={`${tooltip}_${id}`}\n            height=\"16px\"\n            onClick={onClick}\n          />\n        ) : null}\n        {tooltip ? (\n          <Tooltip id={`${tooltip}_${id}`} effect=\"solid\" delayShow={500} type={tooltipType}>\n            <span>\n              <FormattedMessage id={tooltip} />\n            </span>\n          </Tooltip>\n        ) : null}\n      </HeaderActionWrapper>\n    );\n  };\n\n  const PanelHeaderAction = React.memo(PanelHeaderActionUnmemoized);\n  PanelHeaderAction.displayName = 'PanelHeaderAction';\n  return PanelHeaderAction;\n}\n"
  },
  {
    "path": "src/components/src/side-panel/panel-header.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, useCallback} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\nimport {createSelector} from 'reselect';\nimport {StyledPanelDropdown, Tooltip} from '../common/styled-components';\nimport KeplerGlLogo from '../common/logo';\nimport {Save, DataTable, Save2, Picture, Db, BaseMap, Share} from '../common/icons';\nimport Toolbar, {ToolbarProps} from '../common/toolbar';\nimport ToolbarItem, {ToolbarItemProps} from '../common/toolbar-item';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {UiState} from '@kepler.gl/types';\nimport {BaseProps} from '../common/icons';\nimport useOnClickOutside from '../hooks/use-on-click-outside';\n\ntype StyledPanelActionProps = {\n  active?: boolean;\n};\n\ntype ActionItem = {\n  id: string;\n  label?: string;\n  blank?: boolean;\n  href?: string;\n  tooltip?: string;\n  iconComponent: React.ComponentType<Partial<BaseProps>>;\n  iconComponentProps?: BaseProps;\n  dropdownComponent?: React.ComponentType<DropdownComponentProps>;\n  onClick?: () => void;\n};\n\ntype PanelActionProps = {\n  item: ActionItem;\n  showExportDropdown: (string) => void;\n};\n\ntype PanelHeaderDropdownProps = {\n  items: ToolbarItemProps[];\n  show?: boolean;\n  onClose: () => void;\n  id: string;\n};\n\ntype LogoComponentProps = {\n  appName: string;\n  appWebsite: string;\n  version: string;\n};\n\ntype DropdownCallbacks = {\n  logoComponent?: React.FC<LogoComponentProps> | React.ComponentType<LogoComponentProps>;\n  onExportImage: () => void;\n  onExportData: () => void;\n  onExportConfig?: () => void;\n  onExportMap: () => void;\n  onSaveToStorage: (() => void) | null;\n  onSaveAsToStorage: (() => void) | null;\n  onSaveMap?: () => void;\n  onShareMap: (() => void) | null;\n};\n\ntype Item = {\n  label: string;\n  icon: React.ComponentType<Partial<BaseProps>>;\n  key: string;\n  onClick: (p: DropdownComponentProps) => (() => void) | null;\n};\n\ntype DropdownComponentProps = {\n  show: boolean;\n  onClose: () => void;\n  items?: Item[];\n} & DropdownCallbacks;\n\nexport type PanelHeaderProps = {\n  appName: string;\n  appWebsite: string;\n  version: string;\n  visibleDropdown: UiState['visibleDropdown'];\n  actionItems?: ActionItem[];\n  showExportDropdown: (i: string) => void;\n  hideExportDropdown: () => void;\n} & DropdownCallbacks;\n\nconst StyledPanelHeader = styled.div.attrs(props => ({\n  className: classnames('side-side-panel__header', props.className)\n}))`\n  background-color: ${props => props.theme.sidePanelHeaderBg};\n  padding: 12px 16px 0 16px;\n`;\n\nconst StyledPanelHeaderTop = styled.div.attrs(props => ({\n  className: classnames('side-panel__header__top', props.className)\n}))`\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 16px;\n  width: 100%;\n`;\n\nconst StyledPanelTopActions = styled.div.attrs({\n  className: 'side-panel__top__actions'\n})`\n  display: flex;\n`;\n\nconst StyledPanelAction = styled.div.attrs({\n  className: 'side-panel__panel-header__action'\n})<StyledPanelActionProps>`\n  align-items: center;\n  border-radius: 2px;\n  color: ${props => (props.active ? props.theme.textColorHl : props.theme.subtextColor)};\n  display: flex;\n  height: 26px;\n  justify-content: space-between;\n  margin-left: 4px;\n  padding: 5px;\n  font-weight: bold;\n  p {\n    display: inline-block;\n    margin-right: 6px;\n  }\n  a {\n    height: 20px;\n  }\n\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.textColorHl};\n\n    a {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n`;\n\nconst StyledToolbar = styled(Toolbar)<ToolbarProps>`\n  position: absolute;\n`;\n\nconst PanelAction: React.FC<PanelActionProps> = React.memo(({item, showExportDropdown}) => {\n  const onClick = useCallback(() => {\n    if (item.dropdownComponent) {\n      showExportDropdown(item.id);\n    } else {\n      item.onClick && item.onClick();\n    }\n  }, [item, showExportDropdown]);\n\n  return (\n    <StyledPanelAction\n      id={`${item.id}-action`}\n      data-tip\n      data-for={`${item.id}-action`}\n      onClick={onClick}\n    >\n      {item.label ? <p>{item.label}</p> : null}\n      <a target={item.blank ? '_blank' : ''} href={item.href} rel=\"noreferrer\">\n        <item.iconComponent height=\"18px\" {...item.iconComponentProps} />\n      </a>\n      {item.tooltip ? (\n        <Tooltip id={`${item.id}-action`} place=\"bottom\" delayShow={500} effect=\"solid\">\n          <FormattedMessage id={item.tooltip} />\n        </Tooltip>\n      ) : null}\n    </StyledPanelAction>\n  );\n});\nPanelAction.displayName = 'PanelAction';\nexport {PanelAction};\n\nexport const PanelHeaderDropdownFactory = () => {\n  const PanelHeaderDropdown: React.FC<PanelHeaderDropdownProps> = ({items, show, onClose, id}) => {\n    const ref = useOnClickOutside<HTMLDivElement>(onClose);\n    return (\n      <StyledToolbar show={show} className={`${id}-dropdown`}>\n        {show ? (\n          <StyledPanelDropdown type=\"dark\" ref={ref} className=\"panel-header-dropdown__inner\">\n            {items.map(item => (\n              <ToolbarItem\n                id={item.key}\n                key={item.key}\n                label={item.label}\n                icon={item.icon}\n                onClick={item.onClick}\n                onClose={onClose}\n              />\n            ))}\n          </StyledPanelDropdown>\n        ) : null}\n      </StyledToolbar>\n    );\n  };\n\n  return PanelHeaderDropdown;\n};\n\nconst getDropdownItemsSelector = () =>\n  createSelector(\n    (props: DropdownComponentProps) => props,\n    props =>\n      (props.items || [])\n        .map(t => ({\n          ...t,\n          onClick: t.onClick && t.onClick(props) ? t.onClick(props) : null\n        }))\n        .filter(l => l.onClick)\n  );\n\nexport const SaveExportDropdownFactory = (\n  PanelHeaderDropdown: ReturnType<typeof PanelHeaderDropdownFactory>\n) => {\n  const dropdownItemsSelector = getDropdownItemsSelector();\n\n  const defaultItems = [\n    {\n      label: 'toolbar.exportImage',\n      icon: Picture,\n      key: 'image',\n      onClick: props => props.onExportImage\n    },\n    {\n      label: 'toolbar.exportData',\n      icon: DataTable,\n      key: 'data',\n      onClick: props => props.onExportData\n    },\n    {\n      label: 'toolbar.exportMap',\n      icon: BaseMap,\n      key: 'map',\n      onClick: props => props.onExportMap\n    },\n    {\n      label: 'toolbar.saveMap',\n      icon: Save2,\n      key: 'save',\n      onClick: props => props.onSaveMap\n    },\n    {\n      label: 'toolbar.shareMapURL',\n      icon: Share,\n      key: 'share',\n      onClick: props => props.onShareMap\n    }\n  ];\n\n  const SaveExportDropdown: React.FC<DropdownComponentProps> & {\n    defaultItems: ToolbarItemProps[];\n  } = ({items = defaultItems, ...restProps}) => {\n    const props = {...restProps, items};\n    return (\n      <PanelHeaderDropdown\n        items={dropdownItemsSelector(props)}\n        show={props.show}\n        onClose={props.onClose}\n        id=\"save-export\"\n      />\n    );\n  };\n  SaveExportDropdown.defaultItems = defaultItems;\n  return SaveExportDropdown;\n};\nSaveExportDropdownFactory.deps = [PanelHeaderDropdownFactory];\n\nexport const CloudStorageDropdownFactory = (\n  PanelHeaderDropdown: ReturnType<typeof PanelHeaderDropdownFactory>\n) => {\n  const dropdownItemsSelector = getDropdownItemsSelector();\n  const defaultItems = [\n    {\n      label: 'Save',\n      icon: Save2,\n      key: 'save',\n      onClick: props => props.onSaveToStorage\n    },\n    {\n      label: 'Save As',\n      icon: Save2,\n      key: 'saveAs',\n      onClick: props => props.onSaveAsToStorage\n    }\n  ];\n  const CloudStorageDropdown: React.FC<DropdownComponentProps> & {\n    defaultItems: DropdownComponentProps['items'];\n  } = ({items = defaultItems, ...restProps}) => {\n    const props = {...restProps, items};\n    return (\n      <PanelHeaderDropdown\n        items={dropdownItemsSelector(props)}\n        show={props.show}\n        onClose={props.onClose}\n        id=\"cloud-storage\"\n      />\n    );\n  };\n  CloudStorageDropdown.defaultItems = defaultItems;\n  return CloudStorageDropdown;\n};\nCloudStorageDropdownFactory.deps = [PanelHeaderDropdownFactory];\n\nPanelHeaderFactory.deps = [SaveExportDropdownFactory, CloudStorageDropdownFactory];\n\nfunction PanelHeaderFactory(\n  SaveExportDropdown: ReturnType<typeof SaveExportDropdownFactory>,\n  CloudStorageDropdown: ReturnType<typeof CloudStorageDropdownFactory>\n): React.ComponentType<PanelHeaderProps> {\n  return class PanelHeader extends Component<PanelHeaderProps> {\n    static defaultProps = {\n      logoComponent: KeplerGlLogo,\n      actionItems: [\n        {\n          id: 'storage',\n          iconComponent: Db,\n          tooltip: 'tooltip.cloudStorage',\n          onClick: () => {\n            return;\n          },\n          dropdownComponent: CloudStorageDropdown\n        },\n        {\n          id: 'save',\n          iconComponent: Save,\n          onClick: () => {\n            return;\n          },\n          label: 'Share',\n          dropdownComponent: SaveExportDropdown\n        }\n      ]\n    };\n\n    render() {\n      const {\n        appName,\n        appWebsite,\n        version,\n        actionItems,\n        visibleDropdown,\n        showExportDropdown,\n        hideExportDropdown,\n        ...dropdownCallbacks\n      } = this.props;\n      let items = actionItems || [];\n\n      // don't render cloud storage icon if onSaveToStorage is not provided\n      if (typeof this.props.onSaveToStorage !== 'function') {\n        items = items.filter(ai => ai.id !== 'storage');\n      }\n\n      return (\n        <StyledPanelHeader className=\"side-panel__panel-header\">\n          <StyledPanelHeaderTop className=\"side-panel__panel-header__top\">\n            {this.props.logoComponent && (\n              <this.props.logoComponent\n                appName={appName}\n                version={version}\n                appWebsite={appWebsite}\n              />\n            )}\n            <StyledPanelTopActions>\n              {items.map(item => (\n                <div\n                  className=\"side-panel__panel-header__right\"\n                  key={item.id}\n                  style={{position: 'relative'}}\n                >\n                  <PanelAction item={item} showExportDropdown={showExportDropdown} />\n                  {item.dropdownComponent ? (\n                    <item.dropdownComponent\n                      onClose={hideExportDropdown}\n                      show={visibleDropdown === item.id}\n                      {...dropdownCallbacks}\n                    />\n                  ) : null}\n                </div>\n              ))}\n            </StyledPanelTopActions>\n          </StyledPanelHeaderTop>\n        </StyledPanelHeader>\n      );\n    }\n  };\n}\n\nexport default PanelHeaderFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/panel-tab.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {Tooltip} from '../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {BaseProps} from '../common/icons';\n\ntype StyledPanelTabProps = {\n  active?: boolean;\n};\n\nexport type PanelItem = {\n  id: string;\n  label: string;\n  iconComponent: React.ComponentType<Partial<BaseProps>>;\n};\n\nexport type PanelTabProps = {\n  isActive: boolean;\n  panel: PanelItem;\n  onClick: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nexport const StyledPanelTab = styled.div.attrs({\n  className: 'side-panel__tab'\n})<StyledPanelTabProps>`\n  align-items: flex-end;\n  border-bottom-style: solid;\n  border-bottom-width: 2px;\n  border-bottom-color: ${props =>\n    props.active ? props.theme.panelToggleBorderColor : 'transparent'};\n  color: ${props => (props.active ? props.theme.subtextColorActive : props.theme.panelTabColor)};\n  display: flex;\n  justify-content: center;\n  margin-right: ${props => props.theme.panelToggleMarginRight}px;\n  padding-bottom: ${props => props.theme.panelToggleBottomPadding}px;\n  width: ${props => props.theme.panelTabWidth};\n\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.textColorHl};\n  }\n`;\n\nexport function PanelTabFactory() {\n  const PanelTab: React.FC<PanelTabProps> = ({isActive, onClick, panel}) => (\n    <StyledPanelTab data-tip data-for={`${panel.id}-nav`} active={isActive} onClick={onClick}>\n      <panel.iconComponent height=\"20px\" />\n      <Tooltip id={`${panel.id}-nav`} effect=\"solid\" delayShow={500} place=\"bottom\">\n        <span>\n          <FormattedMessage id={panel.label || panel.id} />\n        </span>\n      </Tooltip>\n    </StyledPanelTab>\n  );\n\n  return PanelTab;\n}\n\nexport default PanelTabFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/panel-title.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PropsWithChildren} from 'react';\nimport styled from 'styled-components';\nimport classnames from 'classnames';\n\nconst StyledPanelTitle = styled.div.attrs(props => ({\n  className: classnames('panel-title', props.className)\n}))`\n  color: ${props => props.theme.titleTextColor};\n  font-size: ${props => props.theme.sidePanelTitleFontsize};\n  line-height: ${props => props.theme.sidePanelTitleLineHeight};\n  font-weight: 400;\n  letter-spacing: 1.25px;\n`;\n\nconst PanelHeaderRow = styled.div.attrs({\n  className: 'layer-manager-header'\n})`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-top: 16px;\n  margin-bottom: 32px;\n`;\n\nexport type PanelTitleProps = PropsWithChildren<{\n  title: string;\n  className?: string;\n}>;\n\nconst PanelTitleFactory = () => {\n  const PanelTitle: React.FC<PanelTitleProps> = ({title, className, children}) => (\n    <PanelHeaderRow>\n      <StyledPanelTitle className={className || 'panel-title'}>{title}</StyledPanelTitle>\n      {children}\n    </PanelHeaderRow>\n  );\n\n  return PanelTitle;\n};\n\nexport default PanelTitleFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/panel-toggle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback} from 'react';\nimport styled from 'styled-components';\nimport PanelTabFactory, {PanelItem} from './panel-tab';\nimport {toggleSidePanel, ActionHandler} from '@kepler.gl/actions';\n\ntype PanelToggleProps = {\n  panels: PanelItem[];\n  activePanel: string | null;\n  togglePanel: ActionHandler<typeof toggleSidePanel>;\n};\n\nconst PanelHeaderBottom = styled.div.attrs({\n  className: 'side-side-panel__header__bottom'\n})`\n  background-color: ${props => props.theme.sidePanelHeaderBg};\n  border-bottom: 1px solid ${props => props.theme.sidePanelHeaderBorder};\n  padding: 0 16px;\n  display: flex;\n  min-height: 30px;\n`;\n\nPanelToggleFactory.deps = [PanelTabFactory];\n\nfunction PanelToggleFactory(PanelTab: ReturnType<typeof PanelTabFactory>) {\n  const PanelToggle: React.FC<PanelToggleProps> = ({activePanel, panels, togglePanel}) => {\n    const onClick = useCallback(\n      panel => {\n        const callback = panel.onClick || togglePanel;\n        callback(panel.id);\n      },\n      [togglePanel]\n    );\n\n    return (\n      <PanelHeaderBottom>\n        {panels.map(panel => (\n          <PanelTab\n            key={panel.id}\n            panel={panel}\n            isActive={activePanel === panel.id}\n            onClick={() => onClick(panel)}\n          />\n        ))}\n      </PanelHeaderBottom>\n    );\n  };\n\n  return PanelToggle;\n}\n\nexport default PanelToggleFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/panel-view-list-toggle.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useMemo} from 'react';\nimport styled from 'styled-components';\nimport OrderByList from '../common/icons/order-by-list';\nimport OrderByDataset from '../common/icons/order-by-dataset';\nimport {Tooltip} from '../common/styled-components';\nimport {FormattedMessage} from '@kepler.gl/localization';\nimport {PanelListView} from '@kepler.gl/types';\nimport {PANEL_VIEW_TOGGLES} from '@kepler.gl/constants';\n\ntype ToggleOptionProps = {\n  isActive: boolean;\n  onClick: () => void;\n  option: (typeof TOGGLE_OPTIONS)[0];\n};\n\ntype PanelViewListToggleProps = {\n  mode: PanelListView;\n  togglePanelListView: (view: string) => void;\n};\n\nconst PanelViewListToggleContainer = styled.div.attrs({\n  className: 'panel-view-list-toggle'\n})``;\n\nconst PanelViewListToggleWrapper = styled.div.attrs({\n  className: 'panel-view-list-toggle-inner'\n})`\n  display: flex;\n  justify-content: flex-end;\n  align-content: center;\n  gap: 10px;\n`;\n\nexport const StyledToggleOption = styled.div.attrs({\n  className: 'layer-panel-toggle-option'\n})<{$active: boolean}>`\n  color: ${props => (props.$active ? props.theme.subtextColorActive : props.theme.panelTabColor)};\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.subtextColorActive};\n  }\n`;\n\nfunction ToggleOptionFactory() {\n  const ToggleOption: React.FC<ToggleOptionProps> = ({isActive, onClick, option}) => (\n    <StyledToggleOption\n      data-tip\n      data-for={`${option.id}-toggle-option`}\n      $active={isActive}\n      onClick={onClick}\n    >\n      <option.iconComponent height=\"20px\" />\n      <Tooltip id={`${option.id}-toggle-option`} effect=\"solid\" delayShow={500} place=\"bottom\">\n        <span>\n          <FormattedMessage id={option.label} />\n        </span>\n      </Tooltip>\n    </StyledToggleOption>\n  );\n\n  return ToggleOption;\n}\n\nconst TOGGLE_OPTIONS = [\n  {\n    id: PANEL_VIEW_TOGGLES.list,\n    iconComponent: OrderByList,\n    label: 'sidebar.panelViewToggle.list'\n  },\n  {\n    id: PANEL_VIEW_TOGGLES.byDataset,\n    iconComponent: OrderByDataset,\n    label: 'sidebar.panelViewToggle.byDataset'\n  }\n];\n\nPanelViewListToggleFactory.deps = [ToggleOptionFactory];\n\nfunction PanelViewListToggleFactory(ToggleOption: ReturnType<typeof ToggleOptionFactory>) {\n  const PanelViewListToggle: React.FC<PanelViewListToggleProps> = ({mode, togglePanelListView}) => {\n    const toggleListView = listView => togglePanelListView(listView);\n\n    const options = useMemo(\n      () => TOGGLE_OPTIONS.map(opt => ({...opt, isActive: mode === opt.id})),\n      [mode]\n    );\n\n    return (\n      <PanelViewListToggleContainer>\n        <PanelViewListToggleWrapper>\n          {options.map(opt => (\n            <ToggleOption\n              key={opt.id}\n              onClick={() => toggleListView(opt.id)}\n              option={opt}\n              isActive={opt.isActive}\n            />\n          ))}\n        </PanelViewListToggleWrapper>\n      </PanelViewListToggleContainer>\n    );\n  };\n\n  return PanelViewListToggle;\n}\n\nexport default PanelViewListToggleFactory;\n"
  },
  {
    "path": "src/components/src/side-panel/side-bar.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\nimport {ArrowRight} from '../common/icons';\n\nexport type CollapseButtonProps = {\n  isOpen: boolean;\n  onClick: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nexport type SideBarProps = {\n  width: number;\n  isOpen: boolean;\n  minifiedWidth: number;\n  onOpenOrClose: (arg: {isOpen: boolean}) => void;\n  shouldShowCollapseButton?: boolean | null;\n  children?: React.ReactNode;\n};\n\nconst StyledSidePanelContainer = styled.div<{width: number}>`\n  z-index: 99;\n  height: 100%;\n  width: ${props => props.width + 2 * props.theme.sidePanel.margin.left}px;\n  display: flex;\n  transition: width 250ms;\n  position: absolute;\n  padding-top: ${props => props.theme.sidePanel.margin.top}px;\n  padding-right: ${props => props.theme.sidePanel.margin.right}px;\n  padding-bottom: ${props => props.theme.sidePanel.margin.bottom}px;\n  padding-left: ${props => props.theme.sidePanel.margin.left}px;\n  pointer-events: none; /* prevent padding from blocking input */\n  & > * {\n    /* all children should allow input */\n    pointer-events: all;\n  }\n`;\n\nconst SideBarContainer = styled.div<{left: number}>`\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n  transition: left 250ms, right 250ms;\n  left: ${props => props.left}px;\n  align-items: stretch;\n  flex-grow: 1;\n`;\n\nconst SideBarInner = styled.div`\n  background-color: ${props => props.theme.sidePanelBg};\n  border-radius: 1px;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  border-left: ${props => props.theme.sidePanelBorder}px solid\n    ${props => props.theme.sidePanelBorderColor};\n`;\n\nconst StyledCollapseButton = styled.div`\n  align-items: center;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n  justify-content: center;\n  background-color: ${props => props.theme.sideBarCloseBtnBgd};\n  border-radius: 1px;\n  color: ${props => props.theme.sideBarCloseBtnColor};\n  display: flex;\n  height: 20px;\n  position: absolute;\n  right: -8px;\n  top: ${props => props.theme.sidePanel.margin.top}px;\n  width: 20px;\n\n  &:hover {\n    cursor: pointer;\n    box-shadow: none;\n    background-color: ${props => props.theme.sideBarCloseBtnBgdHover};\n  }\n`;\n\nexport const CollapseButtonFactory = () => {\n  const CollapseButton = ({onClick, isOpen}: CollapseButtonProps) => (\n    <StyledCollapseButton className=\"side-bar__close\" onClick={onClick}>\n      <ArrowRight height=\"12px\" style={{transform: `rotate(${isOpen ? 180 : 0}deg)`}} />\n    </StyledCollapseButton>\n  );\n  return CollapseButton;\n};\n\nSidebarFactory.deps = [CollapseButtonFactory];\n\nfunction SidebarFactory(CollapseButton: ReturnType<typeof CollapseButtonFactory>) {\n  return class SideBar extends Component<SideBarProps> {\n    static defaultProps = {\n      width: 300,\n      minifiedWidth: 0,\n      isOpen: true,\n      onOpenOrClose: function noop() {\n        return;\n      },\n      shouldShowCollapseButton: true\n    };\n\n    _onOpenOrClose = () => {\n      this.props.onOpenOrClose({isOpen: !this.props.isOpen});\n    };\n\n    render() {\n      const {isOpen, minifiedWidth, width, shouldShowCollapseButton} = this.props;\n      const horizontalOffset = isOpen ? 0 : minifiedWidth - width;\n\n      return (\n        <StyledSidePanelContainer width={isOpen ? width : 0} className=\"side-panel--container\">\n          <SideBarContainer\n            className=\"side-bar\"\n            style={{width: `${width}px`}}\n            left={horizontalOffset}\n          >\n            {isOpen ? (\n              <SideBarInner className=\"side-bar__inner\">{this.props.children}</SideBarInner>\n            ) : null}\n            {shouldShowCollapseButton ? (\n              <CollapseButton isOpen={isOpen} onClick={this._onOpenOrClose} />\n            ) : null}\n          </SideBarContainer>\n        </StyledSidePanelContainer>\n      );\n    }\n  };\n}\n\nexport default SidebarFactory;\n"
  },
  {
    "path": "src/components/src/side-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo} from 'react';\n\nimport {\n  EXPORT_DATA_ID,\n  EXPORT_MAP_ID,\n  SHARE_MAP_ID,\n  SIDEBAR_PANELS,\n  OVERWRITE_MAP_ID,\n  SAVE_MAP_ID,\n  EXPORT_IMAGE_ID,\n  ADD_DATA_ID,\n  ADD_MAP_STYLE_ID\n} from '@kepler.gl/constants';\n\nimport {BaseMap, Layers, FilterFunnel, PointerClick} from './common/icons';\n\nimport SidebarFactory from './side-panel/side-bar';\nimport PanelHeaderFactory from './side-panel/panel-header';\nimport PanelToggleFactory from './side-panel/panel-toggle';\nimport LayerManagerFactory from './side-panel/layer-manager';\nimport FilterManagerFactory from './side-panel/filter-manager';\nimport InteractionManagerFactory from './side-panel/interaction-manager';\nimport MapManagerFactory from './side-panel/map-manager';\nimport CustomPanelsFactory from './side-panel/custom-panel';\n\nimport styled from 'styled-components';\nimport {SidePanelProps, SidePanelItem} from './types';\n\nexport const StyledSidePanelContent = styled.div`\n  ${props => props.theme.sidePanelScrollBar};\n  flex-grow: 1;\n  padding: ${props => props.theme.sidePanelInnerPadding}px;\n  overflow-y: scroll;\n  overflow-x: hidden;\n\n  .side-panel__content__inner {\n    display: flex;\n    height: 100%;\n    flex-direction: column;\n  }\n`;\n\nSidePanelFactory.deps = [\n  SidebarFactory,\n  PanelHeaderFactory,\n  PanelToggleFactory,\n  LayerManagerFactory,\n  FilterManagerFactory,\n  InteractionManagerFactory,\n  MapManagerFactory,\n  CustomPanelsFactory\n];\n\n/**\n * Vertical sidebar containing input components for the rendering layers\n */\nexport default function SidePanelFactory(\n  Sidebar: ReturnType<typeof SidebarFactory>,\n  PanelHeader: ReturnType<typeof PanelHeaderFactory>,\n  PanelToggle: ReturnType<typeof PanelToggleFactory>,\n  LayerManager: ReturnType<typeof LayerManagerFactory>,\n  FilterManager: ReturnType<typeof FilterManagerFactory>,\n  InteractionManager: ReturnType<typeof InteractionManagerFactory>,\n  MapManager: ReturnType<typeof MapManagerFactory>,\n  CustomPanels: ReturnType<typeof CustomPanelsFactory>\n) {\n  // inject components\n  const SIDEBAR_COMPONENTS = {\n    layer: LayerManager,\n    filter: FilterManager,\n    interaction: InteractionManager,\n    map: MapManager\n  };\n\n  const SIDEBAR_ICONS = {\n    layer: props => <Layers {...props} height=\"18px\" />,\n    filter: props => <FilterFunnel {...props} height=\"18px\" />,\n    interaction: props => <PointerClick {...props} height=\"18px\" />,\n    map: props => <BaseMap {...props} height=\"18px\" />\n  };\n\n  // We should defined sidebar panels here but keeping them for backward compatible\n  const defaultSidePanels: SidePanelItem[] = SIDEBAR_PANELS.map(component => ({\n    ...component,\n    component: SIDEBAR_COMPONENTS[component.id],\n    iconComponent: SIDEBAR_ICONS[component.id]\n  }));\n\n  const fullPanels = [...defaultSidePanels, ...(CustomPanels.panels || [])];\n\n  const getCustomPanelProps = CustomPanels.getProps || (() => ({}));\n\n  // eslint-disable-next-line max-statements\n  const SidePanel: React.FC<SidePanelProps> & {defaultPanels: SidePanelProps['panels']} = (\n    props: SidePanelProps\n  ) => {\n    const {\n      appName,\n      appWebsite,\n      availableProviders = {},\n      datasets,\n      filters,\n      layers,\n      layerBlending,\n      overlayBlending,\n      layerClasses,\n      layerOrder,\n      interactionConfig,\n      panels = fullPanels,\n      mapInfo = {},\n      mapSaved,\n      mapStateActions,\n      mapStyle,\n      mapStyleActions,\n      onSaveMap,\n      uiState,\n      uiStateActions,\n      visStateActions,\n      version,\n      width\n    } = props;\n    const {openDeleteModal, toggleModal, toggleSidePanel} = uiStateActions;\n    const {activeSidePanel} = uiState;\n    const {setMapInfo, showDatasetTable, updateTableColor} = visStateActions;\n    const {hasShare, hasStorage} = availableProviders;\n\n    const {title} = mapInfo;\n\n    const isOpen = Boolean(activeSidePanel);\n\n    const _onOpenOrClose = useCallback(\n      () => toggleSidePanel(activeSidePanel ? '' : 'layer'),\n      [activeSidePanel, toggleSidePanel]\n    );\n\n    const onClickExportImage = useCallback(() => toggleModal(EXPORT_IMAGE_ID), [toggleModal]);\n    const onClickExportData = useCallback(() => toggleModal(EXPORT_DATA_ID), [toggleModal]);\n    const onClickExportMap = useCallback(() => toggleModal(EXPORT_MAP_ID), [toggleModal]);\n    const onClickSaveToStorage = useCallback(\n      () => toggleModal(mapSaved ? OVERWRITE_MAP_ID : SAVE_MAP_ID),\n      [mapSaved, toggleModal]\n    );\n    const onClickSaveAsToStorage = useCallback(() => {\n      setMapInfo({\n        title: `${title || 'Kepler.gl'} (Copy)`\n      });\n\n      toggleModal(SAVE_MAP_ID);\n    }, [title, setMapInfo, toggleModal]);\n    const onClickShareMap = useCallback(() => toggleModal(SHARE_MAP_ID), [toggleModal]);\n    const onShowDatasetTable = useCallback(dataId => showDatasetTable(dataId), [showDatasetTable]);\n    const onUpdateTableColor = useCallback(\n      (dataId, newColor) => updateTableColor(dataId, newColor),\n      [updateTableColor]\n    );\n    const onShowAddDataModal = useCallback(() => toggleModal(ADD_DATA_ID), [toggleModal]);\n    const onShowAddMapStyleModal = useCallback(() => toggleModal(ADD_MAP_STYLE_ID), [toggleModal]);\n    const onRemoveDataset = useCallback(dataId => openDeleteModal(dataId), [openDeleteModal]);\n\n    const currentPanel = useMemo(\n      () => panels.find(({id}) => id === activeSidePanel) || null,\n      [activeSidePanel, panels]\n    );\n\n    const customPanelProps = useMemo(() => getCustomPanelProps(props), [props]) as Record<\n      string,\n      any\n    >;\n    const PanelComponent = currentPanel?.component;\n\n    return (\n      <Sidebar\n        width={width}\n        isOpen={isOpen}\n        shouldShowCollapseButton={uiState.isSidePanelCloseButtonVisible}\n        minifiedWidth={0}\n        onOpenOrClose={_onOpenOrClose}\n      >\n        <PanelHeader\n          appName={appName}\n          version={version}\n          appWebsite={appWebsite}\n          visibleDropdown={uiState.visibleDropdown}\n          showExportDropdown={uiStateActions.showExportDropdown}\n          hideExportDropdown={uiStateActions.hideExportDropdown}\n          onExportImage={onClickExportImage}\n          onExportData={onClickExportData}\n          onExportMap={onClickExportMap}\n          onSaveMap={hasStorage ? onSaveMap : undefined}\n          onSaveToStorage={hasStorage ? onClickSaveToStorage : null}\n          onSaveAsToStorage={hasStorage && mapSaved ? onClickSaveAsToStorage : null}\n          onShareMap={hasShare ? onClickShareMap : null}\n        />\n        {/* the next two components should be moved into one */}\n        {/* but i am keeping them because of backward compatibility */}\n        <PanelToggle\n          panels={panels}\n          activePanel={activeSidePanel}\n          togglePanel={uiStateActions.toggleSidePanel}\n        />\n        <StyledSidePanelContent className=\"side-panel__content\">\n          <div className=\"side-panel__content__inner\">\n            {PanelComponent ? (\n              <PanelComponent\n                datasets={datasets}\n                filters={filters}\n                layers={layers}\n                layerClasses={layerClasses}\n                layerOrder={layerOrder}\n                layerBlending={layerBlending}\n                overlayBlending={overlayBlending}\n                mapStyle={mapStyle}\n                mapStyleActions={mapStyleActions}\n                mapStateActions={mapStateActions}\n                interactionConfig={interactionConfig}\n                removeDataset={onRemoveDataset}\n                showDatasetTable={onShowDatasetTable}\n                updateTableColor={onUpdateTableColor}\n                showAddDataModal={onShowAddDataModal}\n                showAddMapStyleModal={onShowAddMapStyleModal}\n                uiStateActions={uiStateActions}\n                visStateActions={visStateActions}\n                panelMetadata={currentPanel}\n                panelListView={\n                  currentPanel?.id === 'layer'\n                    ? uiState.layerPanelListView\n                    : currentPanel?.id === 'filter'\n                    ? uiState.filterPanelListView\n                    : null\n                }\n              />\n            ) : null}\n            <CustomPanels\n              {...customPanelProps}\n              activeSidePanel={activeSidePanel}\n              updateTableColor={onUpdateTableColor}\n            />\n          </div>\n        </StyledSidePanelContent>\n      </Sidebar>\n    );\n  };\n\n  SidePanel.defaultPanels = fullPanels;\n  return SidePanel;\n}\n"
  },
  {
    "path": "src/components/src/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {HTMLAttributes, PropsWithChildren} from 'react';\nimport {MapStyle} from '@kepler.gl/reducers';\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {Filter, InteractionConfig, UiState} from '@kepler.gl/types';\n\nimport {\n  MapStyleActions,\n  VisStateActions,\n  MapStateActions,\n  UIStateActions\n} from '@kepler.gl/actions';\nimport {Datasets} from '@kepler.gl/table';\n\nexport type BaseComponentProps = PropsWithChildren<HTMLAttributes<unknown>>;\n\nexport type SidePanelItem = {\n  id: string;\n  label: string;\n  iconComponent: React.ComponentType<any>;\n  component: React.ComponentType<any>;\n};\n\nexport type SidePanelProps = {\n  appName: string;\n  appWebsite: string;\n  filters: Filter[];\n  interactionConfig: InteractionConfig;\n  layerBlending: string;\n  overlayBlending?: string;\n  layers: Layer[];\n  layerClasses: LayerClassesType;\n  layerOrder: string[];\n  mapStyle: MapStyle;\n  mapInfo: {title?: string; description?: string};\n  width: number;\n  datasets: Datasets;\n  uiStateActions: typeof UIStateActions;\n  visStateActions: typeof VisStateActions;\n  mapStateActions: typeof MapStateActions;\n  mapStyleActions: typeof MapStyleActions;\n  uiState: UiState;\n  availableProviders: {[k: string]: {hasShare?: boolean; hasStorage?: boolean}};\n  mapSaved?: string | null;\n  panels?: SidePanelItem[];\n  onSaveMap?: () => void;\n  version: string;\n};\n"
  },
  {
    "path": "src/components/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": false, //TODO needs to be removed once all isolations are ready\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/constants/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/constants/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/constants\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:umd && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@types/d3-scale\": \"^3.2.2\",\n    \"@types/keymirror\": \"^0.1.1\",\n    \"chroma-js\": \"2.1.2\",\n    \"colorbrewer\": \"^1.5.0\",\n    \"d3-array\": \"^2.8.0\",\n    \"d3-color\": \"^2.0.0\",\n    \"d3-scale\": \"^3.2.3\",\n    \"d3-scale-chromatic\": \"2.0.0\",\n    \"d3-time\": \"^2.0.0\",\n    \"global\": \"^4.3.0\",\n    \"keymirror\": \"^0.1.1\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\",\n  \"devDependencies\": {\n    \"@types/d3-time\": \"^2\"\n  }\n}\n"
  },
  {
    "path": "src/constants/src/color-palettes.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as d3ScaleChromatic from 'd3-scale-chromatic';\nimport {range} from 'd3-array';\nimport chroma from 'chroma-js';\nimport Console from 'global/console';\nimport {color as d3Color} from 'd3-color';\nimport {HexColor, MiniColorRange, ValueOf} from '@kepler.gl/types';\n\ntype GetColors = (steps: number) => HexColor[];\n// linear interpolator\ntype GetLinear = () => (n: number) => string;\n\nexport type CategoricalPalette = {\n  name: string;\n  type: 'qualitative';\n  category: ValueOf<typeof CATEGORIES>;\n  colorBlindSafe: boolean;\n\n  colors: GetColors;\n  // categorical palette\n  maxStep: number;\n};\n\nexport type SequentialPalette = {\n  name: string;\n  type: 'sequential' | 'diverging' | 'cyclical';\n  category: ValueOf<typeof CATEGORIES>;\n  colorBlindSafe: boolean;\n  colors: GetColors;\n  // sequential palette\n  linear: GetLinear;\n};\n\nexport type ColorPalette = CategoricalPalette | SequentialPalette;\n\nexport const CATEGORIES = {\n  COLORBREWER: 'ColorBrewer',\n  D3: 'D3',\n  UBER: 'Uber',\n  COLORBLIND: 'ColorBlind'\n};\n\nexport const PALETTE_TYPES: {\n  SEQ: 'sequential';\n  QUA: 'qualitative';\n  DIV: 'diverging';\n  CYC: 'cyclical';\n} = {\n  SEQ: 'sequential',\n  QUA: 'qualitative',\n  DIV: 'diverging',\n  CYC: 'cyclical'\n};\n\nexport const COLORBREWER_SCHEME = {\n  [PALETTE_TYPES.SEQ]: [\n    'BuGn',\n    'BuPu',\n    'GnBu',\n    'OrRd',\n    'PuBu',\n    'PuBuGn',\n    'PuRd',\n    'RdPu',\n    'YlGn',\n    'YlGnBu',\n    'YlOrBr',\n    'YlOrRd',\n    'Blues',\n    'Greens',\n    'Greys',\n    'Oranges',\n    'Purples',\n    'Reds'\n  ], // 18 + 9 + 8\n  // singlehue: ['Blues', 'Greens', 'Greys', 'Oranges', 'Purples', 'Reds'],\n  [PALETTE_TYPES.DIV]: [\n    'BrBG',\n    'PiYG',\n    'PRGn',\n    'PuOr',\n    'RdBu',\n    'RdGy',\n    'RdYlBu',\n    'RdYlGn',\n    'Spectral'\n  ],\n  [PALETTE_TYPES.QUA]: ['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3']\n};\n\n// https://rdrr.io/cran/RColorBrewer/man/ColorBrewer.html\nconst COLOR_BLIND_SAFE_MAP = {\n  // colorbrewer\n  BrBG: true,\n  PiYG: true,\n  PRGn: true,\n  PuOr: true,\n  RdBu: true,\n  RdGy: false,\n  RdYlBu: true,\n  RdYlGn: false,\n  Spectral: false,\n  Accent: false,\n  Dark2: true,\n  Paired: true,\n  Pastel1: false,\n  Pastel2: false,\n  Set1: false,\n  Set2: true,\n  Set3: false,\n  Blues: true,\n  BuGn: true,\n  BuPu: true,\n  GnBu: true,\n  Greens: true,\n  Greys: true,\n  Oranges: true,\n  OrRd: true,\n  PuBu: true,\n  PuBuGn: true,\n  PuRd: true,\n  Purples: true,\n  RdPu: true,\n  Reds: true,\n  YlGn: true,\n  YlGnBu: true,\n  YlOrBr: true,\n  YlOrRd: true,\n\n  // d3 scale chromatic\n  Sinebow: true,\n  Rainbow: false,\n  Turbo: true,\n  Viridis: true,\n  Inferno: true,\n  Magma: true,\n  Plasma: true,\n  Cividis: true,\n  Warm: true,\n  Cool: false,\n  CubehelixDefault: true,\n  Tableau10: false\n};\n\nexport const D3_COLOR_CHROMATIC_SCHEME = {\n  [PALETTE_TYPES.CYC]: ['Sinebow', 'Rainbow'],\n  [PALETTE_TYPES.SEQ]: [\n    'Turbo',\n    'Viridis',\n    'Inferno',\n    'Magma',\n    'Plasma',\n    'Cividis',\n    'Warm',\n    'Cool',\n    'CubehelixDefault'\n  ],\n  [PALETTE_TYPES.QUA]: ['Tableau10']\n};\n\nexport const DataVizColors = {\n  aqua: '#12939A',\n  tumbleweed: '#DDB27C',\n  mule_fawn: '#88572C',\n  tree_poppy: '#FF991F',\n  flame: '#F15C17',\n  sapphire: '#223F9A',\n  orchid: '#DA70BF',\n  chathams_blue: '#125C77',\n  med_aquamarine: '#4DC19C',\n  crocodile: '#776E57',\n  java: '#17B8BE',\n  chalky: '#F6D18A',\n  light_taupe: '#B7885E',\n  peach_orange: '#FFCB99',\n  apricot: '#F89570',\n  portage: '#829AE3',\n  light_orchid: '#E79FD5',\n  blue_green: '#1E96BE',\n  bermuda: '#89DAC1',\n  cloudy: '#B3AD9E'\n};\n\nconst UberVizDiverging = {\n  name: 'Uber Viz Diverging',\n  category: CATEGORIES.UBER,\n  type: PALETTE_TYPES.DIV,\n  colors: ['#00939C', '#E6FAFA'],\n  colors2: ['#FEEEE8', '#C22E00'],\n  diverging: true,\n  correctLightness: false,\n  colorBlindSafe: true\n};\n\nconst UberVizSequential = {\n  name: 'Uber Viz Sequential',\n  category: CATEGORIES.UBER,\n  type: PALETTE_TYPES.SEQ,\n  colors: ['#00939C', '#E6FAFA'],\n  colorBlindSafe: true\n};\n\nconst UberPool = {\n  name: 'UberPool',\n  type: PALETTE_TYPES.DIV,\n  category: CATEGORIES.UBER,\n  colors: ['#223F9A', '#CF1750', '#FAE300'],\n  correctLightness: false,\n  colorBlindSafe: true\n};\n\nconst IceAndFire = {\n  name: 'Ice And Fire',\n  type: PALETTE_TYPES.DIV,\n  category: CATEGORIES.UBER,\n  colors: ['#0198BD', '#FAFEB3'],\n  colors2: ['#FEEDB1', '#D50255'],\n  diverging: true,\n  colorBlindSafe: true\n};\n\nconst GlobalWarming = {\n  name: 'Global Warming',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#4C0035', '#AC1C17', '#FFC300'],\n  colorBlindSafe: true\n};\n\nconst Sunrise = {\n  name: 'Sunrise',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#355C7D', '#C06C84', '#F8B195'],\n  colorBlindSafe: true\n};\n\nconst OceanGreen = {\n  name: 'Ocean Green',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#37535E', '#3EACA8', '#E5EEC1'],\n  colorBlindSafe: true\n};\n\nconst PinkWine = {\n  name: 'Pink Wine',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#2C1E3D', '#956485', '#EDD1CA'],\n  colorBlindSafe: true\n};\n\nconst PurpleBlueYellow = {\n  name: 'Purple Blue Yellow',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#383C65', '#49838A', '#D6DEBF'],\n  mode: 'hsl',\n  colorBlindSafe: true\n};\n\nconst ViovetOcean = {\n  name: 'ViovetOcean',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#7400B8', '#5E60CE', '#4EA8DE', '#56CFE1', '#72EFDD'],\n  colorBlindSafe: false\n};\n\nconst SummerSky = {\n  name: 'SummerSky',\n  type: PALETTE_TYPES.SEQ,\n  category: CATEGORIES.UBER,\n  colors: ['#184E77', '#168AAD', '#76C893', '#D9ED92'],\n  colorBlindSafe: false\n};\n\nconst UberVizQualitative = {\n  name: 'Uber Viz Qualitative',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.UBER,\n  colors: Object.values(DataVizColors),\n  colorBlindSafe: false\n};\n\n// https://personal.sron.nl/~pault/#sec:qualitative\n// A set of Qualitative Colors designed by Paul Tol that are color blind friendly\nconst TolBright = {\n  name: 'Tol Bright',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE', '#AA3377', '#BBBBBB'],\n  colorBlindSafe: true\n};\n// Bad Data: #BBBBBB\n\nconst TolVibrant = {\n  name: 'Tol Vibrant',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#EE7733', '#0077BB', '#33BBEE', '#EE3377', '#CC3311', '#009988', '#BBBBBB'],\n  colorBlindSafe: true\n};\n// Bad Data: #BBBBBB\n\nconst TolMuted = {\n  name: 'Tol Muted',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: [\n    '#CC6677',\n    '#332288',\n    '#DDCC77',\n    '#117733',\n    '#88CCEE',\n    '#882255',\n    '#44AA99',\n    '#999933',\n    '#AA4499'\n  ],\n  colorBlindSafe: true\n};\n// Bad Data: #DDDDDD\n\nconst TolMediumContrast = {\n  name: 'Tol Medium Contrast',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#6699CC', '#004488', '#EECC66', '#994455', '#997700', '#EE99AA'],\n  colorBlindSafe: true\n};\n\nconst TolLight = {\n  name: 'Tol Light',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#77AADD', '#EE8866', '#EEDD88', '#FFAABB', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00'],\n  colorBlindSafe: true\n};\n\n// https://jfly.uni-koeln.de/color/\nconst OkabeIto = {\n  name: 'Okabe Ito',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7', '#000000'],\n  colorBlindSafe: true\n};\n// Bad Data: #DDDDDD\n\nconst FSQBrand = {\n  name: 'FSQ Brand',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#3333FF', '#6166EB', '#2ED9C3', '#82E8DB', '#FCCC0A', '#FFDAAF', '#30A5D9', '#97DAF8'],\n  colorBlindSafe: true\n};\n\nconst FSQWarmTone = {\n  name: 'FSQ Warm Tone',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#C00B05', '#D150A5', '#E98ECA', '#FECE5A', '#FFDDBF', '#FFB4D3', '#EE5D86', '#D8D2D2'],\n  colorBlindSafe: true\n};\n\nconst FSQCoolTone = {\n  name: 'FSQ Cool Tone',\n  type: PALETTE_TYPES.QUA,\n  category: CATEGORIES.COLORBLIND,\n  colors: ['#11439F', '#297EE8', '#95C6C9', '#FECE5A', '#FFDDBF', '#9FB1B7', '#5281B5', '#B9D0FB'],\n  colorBlindSafe: true\n};\n\n/**\n * Build Categorical color palette\n */\nexport function buildCategoricalPalette({\n  name,\n  category,\n  colors,\n  colorBlindSafe\n}: {\n  name: string;\n  category: ValueOf<typeof CATEGORIES>;\n  colors?: HexColor[];\n  colorBlindSafe?: boolean;\n}): CategoricalPalette {\n  let allColors;\n  // find d3 color scheme\n  const scheme = d3ScaleChromatic[`scheme${name}`];\n\n  if (!scheme && !colors) {\n    Console.warn(`scheme${name} cant not be found in d3 scale chromatic, needs to provide colors`);\n    allColors = ['#DDDDDD'];\n  } else if (!scheme) {\n    // build from colors\n    allColors = colors;\n  } else {\n    allColors = scheme;\n  }\n\n  if (!allColors.length) {\n    Console.warn('Needs to provide valid d3 color scheme name or an array of colors');\n  }\n\n  return {\n    name,\n    category,\n    type: PALETTE_TYPES.QUA,\n    colorBlindSafe: colorBlindSafe ?? COLOR_BLIND_SAFE_MAP[name],\n    maxStep: allColors.length,\n    colors: numColors => {\n      // if numColors > maxSteps,  will return allColors\n      return allColors.slice(0, numColors).map(_colorToUppercase);\n    }\n  };\n}\n\nfunction _colorToUppercase(c) {\n  return d3Color(c).formatHex().toUpperCase();\n}\n/**\n * All sequantial palette is based on palette in d3-scale-chromatic\n * https://github.com/d3/d3-scale-chromatic/blob/main/src/index.js\n */\nfunction buildSequentialPalette({name, type, category}) {\n  if (!Object.prototype.hasOwnProperty.call(COLOR_BLIND_SAFE_MAP, name)) {\n    Console.warn(`${name} does not exists in COLOR_BLIND_SAFE_MAP`);\n  }\n  const interpolator = d3ScaleChromatic[`interpolate${name}`];\n\n  return {\n    name,\n    type,\n    category,\n    colorBlindSafe: COLOR_BLIND_SAFE_MAP[name],\n    colors: numColors => {\n      return range(0, numColors, 1)\n        .map(d => interpolator(d / (numColors - 1)))\n        .map(_colorToUppercase);\n    },\n    linear: () => {\n      return interpolator;\n    }\n  };\n}\n\nfunction buildCustomPalette({\n  colors,\n  colors2 = [],\n  correctLightness = true,\n  bezier = false,\n  diverging = false,\n  mode = 'lch',\n  name,\n  type,\n  category,\n  colorBlindSafe\n}: {\n  colors: HexColor[];\n  colors2?: HexColor[];\n  correctLightness?: boolean;\n  bezier?: boolean;\n  diverging?: boolean;\n  mode?: string;\n  name: string;\n  type: SequentialPalette['type'];\n  category: ValueOf<typeof CATEGORIES>;\n  colorBlindSafe: boolean;\n}): SequentialPalette | undefined {\n  const palette: SequentialPalette = {\n    name,\n    type,\n    category,\n    colorBlindSafe,\n    colors: () => [],\n    linear: () => () => ''\n  };\n\n  if (!colors.length) {\n    Console.error('colors has to be an array of colors');\n    return;\n  }\n\n  const scaleLeft = chroma\n    .scale(bezier && colors.length > 1 ? chroma.bezier(colors) : colors)\n    .mode(mode)\n    .correctLightness(correctLightness);\n  let scaleRight;\n  let scaleFull;\n  if (diverging) {\n    if (!colors.length) {\n      Console.error('colors2 has to be an array of colors when diverging = true');\n      return;\n    }\n    scaleRight = chroma\n      .scale(bezier && colors2.length > 1 ? chroma.bezier(colors2) : colors2)\n      .mode(mode)\n      .correctLightness(correctLightness);\n\n    scaleFull = chroma\n      .scale(bezier ? chroma.bezier(colors.concat(colors2)) : colors.concat(colors2))\n      .mode(mode)\n      .correctLightness(correctLightness);\n  }\n\n  // return numColors => (scaleLeft ? stepsLeft.colors(numColors) : []);\n\n  // given number of colors return color steps\n  palette.colors = numColors => {\n    if (diverging) {\n      const even = numColors % 2 === 0;\n\n      const numColorsLeft = Math.ceil(numColors / 2) + (even ? 1 : 0);\n      const numColorsRight = Math.ceil(numColors / 2) + (even ? 1 : 0);\n\n      const colorsLeft = scaleLeft ? scaleLeft.colors(numColorsLeft) : [];\n      const colorsRight = scaleRight ? scaleRight.colors(numColorsRight) : [];\n      const steps = (even ? colorsLeft.slice(0, colorsLeft.length - 1) : colorsLeft)\n        .concat(colorsRight.slice(1))\n        .map(_colorToUppercase);\n\n      return steps;\n    }\n\n    return scaleLeft ? scaleLeft.colors(numColors).map(_colorToUppercase) : [];\n  };\n\n  palette.linear = () => {\n    return diverging ? scaleFull : scaleLeft;\n  };\n\n  return palette;\n}\n\nfunction buildPaletteBySchemeGroups(\n  schemeGroups: typeof COLORBREWER_SCHEME | typeof D3_COLOR_CHROMATIC_SCHEME,\n  category: ValueOf<typeof CATEGORIES>\n): ColorPalette[] {\n  return Object.entries(schemeGroups).reduce((accu, [type, palettes]) => {\n    return [\n      ...accu,\n      ...palettes.reduce((group, name) => {\n        const colorPalette =\n          type === PALETTE_TYPES.QUA\n            ? buildCategoricalPalette({name, category})\n            : // @ts-ignore jsdoc cant figure out type of 'type'\n              buildSequentialPalette({name, type, category});\n        // @ts-ignore type is not assignable to never[]\n        group.push(colorPalette);\n        return group;\n      }, [])\n    ];\n  }, []);\n}\n\nconst COLORBREWER_PALETTES = buildPaletteBySchemeGroups(COLORBREWER_SCHEME, CATEGORIES.COLORBREWER);\nconst D3_COLOR_PALETTES = buildPaletteBySchemeGroups(D3_COLOR_CHROMATIC_SCHEME, CATEGORIES.D3);\nconst BRANDED_PALETTES: ColorPalette[] = [\n  UberVizDiverging,\n  UberVizSequential,\n  UberPool,\n  IceAndFire,\n  GlobalWarming,\n  Sunrise,\n  OceanGreen,\n  PinkWine,\n  PurpleBlueYellow,\n  ViovetOcean,\n  SummerSky,\n  UberVizQualitative,\n  TolBright,\n  TolVibrant,\n  TolMuted,\n  TolMediumContrast,\n  TolLight,\n  OkabeIto,\n  FSQBrand,\n  FSQWarmTone,\n  FSQCoolTone\n]\n  .map(recipe =>\n    recipe.type === PALETTE_TYPES.QUA ? buildCategoricalPalette(recipe) : buildCustomPalette(recipe)\n  )\n  .filter(Boolean) as ColorPalette[];\n\nexport const KEPLER_COLOR_PALETTES: ColorPalette[] = [\n  ...BRANDED_PALETTES,\n  ...COLORBREWER_PALETTES,\n  ...D3_COLOR_PALETTES\n];\n\n/**\n * create color range from palette, with steps and reversed as config\n */\nexport function colorPaletteToColorRange(\n  colorPalette: ColorPalette,\n  colorConfig: {\n    reversed: boolean;\n    steps: number;\n  }\n): MiniColorRange {\n  const {steps, reversed} = colorConfig;\n  const colors = colorPalette.colors(steps).slice();\n  if (reversed) {\n    colors.reverse();\n  }\n\n  return {\n    name: colorPalette.name,\n    type: colorPalette.type,\n    category: colorPalette.category,\n    colors,\n    ...(reversed ? {reversed} : {})\n  };\n}\n"
  },
  {
    "path": "src/constants/src/colors-by-theme.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {HexColor} from '@kepler.gl/types';\n\n/* eslint-disable quote-props */\nconst COLORS: {\n  [key: string]: HexColor;\n} = {\n  grey_7: '#898989',\n  brick_20: '#B77B6A',\n  brick_21: '#8B574F',\n  brick_22: '#392421',\n  lime_8: '#7DC240',\n  lime_9: '#76B73D',\n  lime_4: '#CFEDB5',\n  lime_5: '#B6E490',\n  lime_6: '#9EDB6B',\n  lime_7: '#8ECF56',\n  lime_1: '#F3FBED',\n  lime_2: '#EBF8E1',\n  lime_3: '#E3F5D4',\n  orange_22: '#32221D',\n  orange_20: '#DDA37C',\n  orange_21: '#A2705B',\n  stone_9: '#716852',\n  stone_8: '#776E57',\n  stone_20: '#4D4A44',\n  stone_1: '#F3F2EF',\n  amber_22: '#32251D',\n  stone_21: '#282623',\n  amber_20: '#DDB27C',\n  amber_21: '#A2785B',\n  navy_17: '#CAD1E6',\n  navy_16: '#18264D',\n  navy_15: '#132559',\n  navy_14: '#162A65',\n  navy_13: '#1A3177',\n  navy_12: '#1E3788',\n  navy_11: '#223F9A',\n  navy_10: '#2647AC',\n  navy_19: '#7989B7',\n  navy_18: '#9CAAD3',\n  teal_9: '#1C8EB4',\n  teal_8: '#1E96BE',\n  teal_5: '#79C7E3',\n  teal_4: '#A6DAEC',\n  teal_7: '#35A6CC',\n  teal_6: '#4DB5D9',\n  teal_1: '#E9F6FA',\n  teal_3: '#CBE9F4',\n  teal_2: '#DAF0F7',\n  teal_19: '#73A6B7',\n  teal_18: '#96C3D2',\n  teal_11: '#17779A',\n  teal_10: '#1A85AB',\n  teal_13: '#125C77',\n  teal_12: '#146988',\n  teal_15: '#0D4559',\n  teal_14: '#0F4E65',\n  teal_17: '#C7DDE5',\n  teal_16: '#103B49',\n  gold_20: '#DDBE7C',\n  gold_21: '#A27E5B',\n  gold_22: '#32271D',\n  olive_9: '#B8C436',\n  olive_8: '#C3D039',\n  olive_7: '#CFDC4F',\n  olive_6: '#DBE765',\n  olive_5: '#E4ED8B',\n  olive_4: '#EDF3B2',\n  olive_3: '#F5F8D2',\n  olive_2: '#F8FADF',\n  olive_1: '#FBFCEC',\n  green_22: '#212A1F',\n  green_20: '#5A735A',\n  green_21: '#2F3C2F',\n  indigo_17: '#CCC7E4',\n  indigo_16: '#1B1247',\n  indigo_15: '#1C0F58',\n  indigo_14: '#201163',\n  indigo_13: '#261474',\n  indigo_12: '#2C1685',\n  indigo_11: '#321A97',\n  indigo_10: '#371DA8',\n  indigo_19: '#7E71B2',\n  indigo_18: '#A095CF',\n  blue_22: '#222A34',\n  blue_20: '#536A81',\n  blue_21: '#2D3946',\n  olive_22: '#28291C',\n  olive_20: '#878A5E',\n  olive_21: '#484A32',\n  violet_16: '#510E30',\n  violet_17: '#E0C1D8',\n  violet_14: '#6B083F',\n  violet_15: '#620736',\n  violet_12: '#860A5A',\n  violet_13: '#79094D',\n  violet_10: '#A40D77',\n  violet_11: '#950C69',\n  dirt_16: '#261A10',\n  dirt_17: '#BCB6B1',\n  dirt_14: '#322113',\n  dirt_15: '#2D1D10',\n  dirt_12: '#442D19',\n  dirt_13: '#3B2716',\n  violet_18: '#C78BB8',\n  violet_19: '#B1699C',\n  stone_15: '#373227',\n  stone_14: '#3E392D',\n  stone_17: '#C6C4C0',\n  stone_16: '#2E2A22',\n  stone_11: '#5F5845',\n  stone_10: '#6A624D',\n  stone_13: '#494335',\n  stone_12: '#544D3D',\n  stone_19: '#706C63',\n  stone_18: '#938F87',\n  aqua_3: '#CAF2F4',\n  aqua_2: '#D9F6F7',\n  aqua_1: '#E8FAFA',\n  aqua_7: '#2FC5CC',\n  aqua_6: '#47D3D9',\n  aqua_5: '#75DEE3',\n  aqua_4: '#A3E9EC',\n  aqua_9: '#15AEB4',\n  aqua_8: '#17B8BE',\n  orange_7: '#FE891A',\n  orange_6: '#FF9833',\n  orange_5: '#FFB266',\n  orange_4: '#FFCB99',\n  orange_3: '#FFE1C4',\n  orange_2: '#FFEAD5',\n  orange_1: '#FFF2E6',\n  orange_9: '#FA7400',\n  orange_8: '#FD7900',\n  purple_13: '#510869',\n  purple_11: '#670B8C',\n  purple_10: '#720C9D',\n  purple_7: '#9226BE',\n  purple_6: '#A13ECD',\n  purple_5: '#B86EDA',\n  purple_4: '#D09FE6',\n  purple_3: '#E4C7F1',\n  purple_2: '#ECD7F5',\n  purple_1: '#F3E7F9',\n  blue_18: '#99B6D3',\n  blue_17: '#C9D7E6',\n  blue_16: '#16314D',\n  blue_15: '#103459',\n  blue_14: '#133B65',\n  blue_13: '#164677',\n  blue_12: '#195188',\n  purple_9: '#7A0DA6',\n  blue_10: '#2067AC',\n  orange_13: '#D65200',\n  orange_12: '#E45D00',\n  orange_11: '#ED6600',\n  orange_10: '#F26C00',\n  orange_17: '#F8E2CC',\n  orange_16: '#8C330A',\n  orange_15: '#B93C00',\n  orange_14: '#C84600',\n  orange_19: '#F6BD8A',\n  orange_18: '#F8CCA1',\n  magenta_11: '#B51241',\n  magenta_10: '#C5154A',\n  magenta_13: '#970E2D',\n  magenta_12: '#A51037',\n  magenta_15: '#7E0A1D',\n  magenta_14: '#880B23',\n  magenta_17: '#EDCAD6',\n  magenta_16: '#63101D',\n  magenta_19: '#D2809A',\n  magenta_18: '#E29DB4',\n  stone_3: '#E2E0DA',\n  stone_2: '#EBE9E4',\n  stone_5: '#B3AD9E',\n  stone_4: '#CDC9BF',\n  stone_7: '#89806B',\n  stone_6: '#9A927E',\n  blue_9: '#226DB5',\n  blue_8: '#2473BD',\n  blue_7: '#3B85CC',\n  blue_6: '#5297DA',\n  blue_5: '#7DB1E3',\n  blue_4: '#A9CBED',\n  blue_3: '#CDE1F4',\n  blue_2: '#DCEAF7',\n  blue_1: '#EAF2FA',\n  gold_9: '#FAC200',\n  gold_8: '#FDC900',\n  gold_5: '#FFE466',\n  gold_4: '#FFEB8C',\n  gold_7: '#FED21A',\n  gold_6: '#FFDB33',\n  gold_1: '#FFFBE6',\n  gold_3: '#FFF5C4',\n  gold_2: '#FFF8D5',\n  dirt_22: '#191410',\n  dirt_21: '#201B17',\n  dirt_20: '#3D342C',\n  white: '#FFFFFF',\n  gold_19: '#F6E08A',\n  gold_18: '#F8E8A1',\n  grey_8: '#777777',\n  grey_9: '#717171',\n  gold_11: '#EDAB00',\n  gold_10: '#F6BA00',\n  gold_13: '#D68800',\n  gold_12: '#E49B00',\n  gold_15: '#B96500',\n  gold_14: '#C87500',\n  gold_17: '#F8F0CC',\n  gold_16: '#8C500A',\n  grey_10: '#6A6A6A',\n  grey_11: '#5F5F5F',\n  grey_12: '#545454',\n  grey_13: '#494949',\n  grey_14: '#3E3E3E',\n  grey_15: '#373636',\n  grey_16: '#2E2D2D',\n  grey_17: '#C6C6C6',\n  grey_18: '#939393',\n  grey_19: '#707070',\n  dirt_19: '#5B4D42',\n  yellow_20: '#DDC97C',\n  yellow_21: '#A2845B',\n  yellow_22: '#32291D',\n  dirt_10: '#573921',\n  dirt_11: '#4E331D',\n  brown_8: '#986232',\n  brown_9: '#905D2F',\n  stone_22: '#1C1A18',\n  brown_6: '#B7885E',\n  brown_7: '#A87548',\n  brick_1: '#FEEEE8',\n  brick_3: '#FCD6C8',\n  brick_2: '#FDE2D8',\n  brick_5: '#F89570',\n  brick_4: '#FAB8A0',\n  brick_7: '#EF5D28',\n  brick_6: '#F57141',\n  brick_9: '#DF4916',\n  brick_8: '#E7531F',\n  amber_7: '#FEB31A',\n  amber_6: '#FFBE33',\n  amber_5: '#FFCE66',\n  amber_4: '#FFDF99',\n  amber_3: '#FFECC4',\n  amber_2: '#FFF2D5',\n  amber_1: '#FFF7E6',\n  amber_9: '#FAA100',\n  amber_8: '#FDA700',\n  grey_2: '#F1F1F1',\n  grey_3: '#E5E5E4',\n  violet_22: '#301C25',\n  violet_21: '#51303F',\n  violet_20: '#7F4B6D',\n  grey_1: '#F8F8F9',\n  red_19: '#D37676',\n  red_18: '#E49595',\n  red_15: '#880000',\n  red_14: '#910000',\n  red_17: '#EEC7C7',\n  red_16: '#6D0A0A',\n  red_11: '#BB0000',\n  red_10: '#C90000',\n  red_13: '#9F0000',\n  red_12: '#AC0000',\n  black: '#000000',\n  grey_4: '#D6D6D5',\n  grey_5: '#C0C0C0',\n  navy_7: '#4265CC',\n  navy_6: '#5879DA',\n  navy_5: '#829AE3',\n  navy_4: '#ABBCED',\n  navy_3: '#CFD8F4',\n  navy_2: '#DDE3F7',\n  navy_1: '#EBEFFA',\n  navy_9: '#294CB5',\n  navy_8: '#2C51BE',\n  yellow_15: '#B97600',\n  yellow_14: '#C88900',\n  yellow_17: '#F8F6CC',\n  yellow_16: '#8C5C0A',\n  yellow_11: '#EDC800',\n  yellow_10: '#F6DA00',\n  yellow_13: '#D6A000',\n  yellow_12: '#E4B600',\n  yellow_19: '#F6EF8A',\n  yellow_18: '#F8F5A1',\n  purple_20: '#664473',\n  purple_21: '#352139',\n  magenta_1: '#FCE9EF',\n  magenta_3: '#F9CADA',\n  magenta_2: '#FBDAE4',\n  magenta_5: '#EF769E',\n  magenta_4: '#F4A3BF',\n  magenta_7: '#E1316A',\n  magenta_6: '#E9487E',\n  magenta_9: '#CF1750',\n  magenta_8: '#D91955',\n  indigo_3: '#D4CCF3',\n  indigo_2: '#E1DBF6',\n  indigo_1: '#EDE9FA',\n  indigo_7: '#5438C8',\n  indigo_6: '#694FD6',\n  indigo_5: '#8F7BE0',\n  indigo_4: '#B4A7EB',\n  indigo_9: '#3B1EB1',\n  indigo_8: '#482BBD',\n  magenta_20: '#9E5F70',\n  magenta_21: '#6D4046',\n  magenta_22: '#2C1B1E',\n  purple_22: '#231727',\n  red_9: '#D20000',\n  red_8: '#DA0000',\n  red_5: '#F06D6D',\n  red_4: '#F59999',\n  red_7: '#E31A1A',\n  red_6: '#EA4444',\n  red_1: '#FDE6E6',\n  red_3: '#F9C4C4',\n  red_2: '#FBD5D5',\n  grey_21: '#282727',\n  grey_20: '#4D4D4D',\n  grey_22: '#1C1B1B',\n  lime_16: '#30471D',\n  lime_17: '#D9E6CE',\n  lime_14: '#406422',\n  lime_15: '#39581E',\n  lime_12: '#57882E',\n  lime_13: '#4C7628',\n  lime_10: '#6EAC3A',\n  lime_11: '#639A34',\n  lime_18: '#BAD4A4',\n  lime_19: '#9EBB84',\n  dirt_18: '#81746B',\n  violet_8: '#B80F87',\n  violet_9: '#AE0E7F',\n  violet_4: '#E79FD5',\n  violet_5: '#DA70BF',\n  violet_6: '#CE40AA',\n  violet_7: '#C32899',\n  violet_1: '#F9E8F5',\n  violet_2: '#F5D8EE',\n  violet_3: '#F1C8E6',\n  red_20: '#A55C5C',\n  red_21: '#784343',\n  red_22: '#361F1F',\n  amber_19: '#F6D18A',\n  amber_18: '#F8DCA1',\n  amber_13: '#D67100',\n  amber_12: '#E48000',\n  amber_11: '#ED8D00',\n  amber_10: '#F69A00',\n  amber_17: '#F8EACC',\n  amber_16: '#8C440A',\n  amber_15: '#B95300',\n  amber_14: '#C86100',\n  green_17: '#CDDDCD',\n  green_16: '#254325',\n  green_15: '#274F28',\n  green_14: '#2C5A2E',\n  green_13: '#356A36',\n  green_12: '#3D7A3E',\n  green_11: '#458A46',\n  green_10: '#4C9A4E',\n  green_19: '#82A682',\n  green_18: '#A2C2A2',\n  turquoise_19: '#77AC9B',\n  turquoise_18: '#99C7B9',\n  turquoise_15: '#185240',\n  turquoise_14: '#1B5D48',\n  turquoise_17: '#C9E0D9',\n  turquoise_16: '#1A4538',\n  turquoise_11: '#2B8F70',\n  turquoise_10: '#309F7D',\n  turquoise_13: '#216E56',\n  turquoise_12: '#267E63',\n  aqua_22: '#1A2C2B',\n  aqua_20: '#4F7E81',\n  aqua_21: '#2B4447',\n  yellow_9: '#FAE300',\n  yellow_8: '#FDEC00',\n  yellow_5: '#FFFA66',\n  yellow_4: '#FFFB98',\n  yellow_7: '#FEF21A',\n  yellow_6: '#FFF833',\n  yellow_1: '#FFFEE6',\n  yellow_3: '#FFFDC4',\n  yellow_2: '#FFFED5',\n  teal_20: '#507481',\n  teal_21: '#2C3F46',\n  teal_22: '#152629',\n  indigo_22: '#1A1724',\n  purple_12: '#5C097A',\n  indigo_20: '#59507E',\n  indigo_21: '#312C45',\n  purple_17: '#D5C1DF',\n  purple_16: '#360B40',\n  purple_15: '#3F054C',\n  purple_14: '#460658',\n  purple_19: '#9563A8',\n  purple_18: '#B389C6',\n  blue_19: '#7597B6',\n  lime_22: '#1E241A',\n  lime_21: '#3A4531',\n  lime_20: '#6C815B',\n  turquoise_1: '#ECF9F5',\n  turquoise_3: '#D2F1E7',\n  turquoise_2: '#DFF5EE',\n  turquoise_5: '#89DAC1',\n  turquoise_4: '#B1E7D6',\n  turquoise_7: '#4DC19C',\n  turquoise_6: '#62CEAD',\n  turquoise_9: '#34A984',\n  turquoise_8: '#37B38B',\n  brown_4: '#DBC3AF',\n  brown_5: '#C9A686',\n  brown_2: '#F0E7DE',\n  brown_3: '#EADDD0',\n  brown_1: '#F6F0EB',\n  blue_11: '#1C5C9A',\n  brown_14: '#4F3319',\n  purple_8: '#820DAF',\n  brown_15: '#452D16',\n  brown_16: '#3C2A18',\n  brown_18: '#B09C8A',\n  brick_19: '#E19D84',\n  brick_18: '#EEB5A1',\n  brick_15: '#9B1C04',\n  brick_14: '#A42105',\n  brick_17: '#F3D7CC',\n  brick_16: '#7A1F0E',\n  brick_11: '#CB380B',\n  brick_10: '#D73F0D',\n  brick_13: '#B12907',\n  brick_12: '#BE3009',\n  olive_13: '#777E23',\n  olive_12: '#899128',\n  olive_11: '#9BA52E',\n  olive_10: '#ADB933',\n  olive_17: '#E9ECD0',\n  olive_16: '#494C1B',\n  olive_15: '#595E1A',\n  olive_14: '#656B1D',\n  olive_19: '#C3C989',\n  olive_18: '#DBE0A8',\n  navy_22: '#232734',\n  navy_20: '#555F81',\n  navy_21: '#2E3446',\n  brown_21: '#352D24',\n  brown_20: '#645446',\n  brown_22: '#29231C',\n  dirt_4: '#C3B5A9',\n  dirt_5: '#A68F7E',\n  dirt_6: '#886A53',\n  dirt_7: '#75553C',\n  dirt_1: '#F0EDEA',\n  dirt_2: '#E7E1DC',\n  dirt_3: '#DDD4CD',\n  dirt_8: '#624025',\n  dirt_9: '#5D3D23',\n  green_3: '#DAEFDA',\n  green_2: '#E4F3E4',\n  green_1: '#EFF8EF',\n  green_7: '#6ABB6B',\n  green_6: '#7DC97F',\n  green_5: '#9ED79F',\n  green_4: '#BEE4BF',\n  green_9: '#52A353',\n  green_8: '#57AD57',\n  brown_10: '#88572C',\n  brown_11: '#7A4E28',\n  brown_12: '#6B4523',\n  brown_13: '#5D3C1E',\n  turquoise_20: '#52776C',\n  turquoise_21: '#2C403A',\n  turquoise_22: '#1E2C27',\n  brown_17: '#D4CAC1',\n  grey_6: '#A6A5A5',\n  brown_19: '#917A66',\n  aqua_19: '#70B4B7',\n  aqua_18: '#94CFD2',\n  aqua_13: '#0E7077',\n  aqua_12: '#108188',\n  aqua_11: '#12939A',\n  aqua_10: '#13A4AB',\n  aqua_17: '#C6E4E5',\n  aqua_16: '#0F474A',\n  aqua_15: '#0A545A',\n  aqua_14: '#0B5F65'\n};\n/* eslint-enable quote-props */\n\nexport default COLORS;\n\nexport const ColorsByTheme: {\n  [theme: string]: {\n    [idx: string]: HexColor;\n  };\n} = Object.keys(COLORS).reduce((accu, key) => {\n  if (!key.includes('_')) {\n    return accu;\n  }\n  const [theme, idx] = key.split('_');\n\n  return {\n    ...accu,\n    [theme]: {\n      ...accu[theme],\n      [idx]: COLORS[key]\n    }\n  };\n}, {});\n\n// theme name in order wheel order\nexport const Themes: string[] = [\n  'yellow',\n  'gold',\n  'amber',\n  'orange',\n  'brick',\n  'red',\n  'magenta',\n  'violet',\n  'purple',\n  'indigo',\n  'navy',\n  'blue',\n  'teal',\n  'aqua',\n  'turquoise',\n  'green',\n  'lime',\n  'olive',\n  'grey',\n  'stone',\n  'brown',\n  'dirt'\n];\n"
  },
  {
    "path": "src/constants/src/dataset.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport enum DatasetType {\n  LOCAL = 'local',\n  VECTOR_TILE = 'vector-tile',\n  RASTER_TILE = 'raster-tile',\n  WMS_TILE = 'wms-tile'\n}\n\nexport enum RemoteTileFormat {\n  MVT = 'mvt',\n  PMTILES = 'pmtiles',\n  WMS = 'wms'\n}\n\nexport enum PMTilesType {\n  RASTER = 'raster',\n  MVT = 'mvt'\n}\n\nexport const REMOTE_TILE = 'remote';\n\nexport type VectorTileDatasetMetadata = {\n  type: typeof REMOTE_TILE;\n  remoteTileFormat: RemoteTileFormat;\n  tilesetDataUrl: string;\n  tilesetMetadataUrl?: string;\n};\n\n/**\n * Raster tileset metadata in STAC Item format. STAC version must be >= 1.0.0,\n * and the EO and Raster STAC extensions are required. This metadata shape can\n * be passed to the map to synchronously add a raster tileset.\n * @see https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md\n */\nexport type RasterTileLocalMetadata = {\n  type: 'Feature';\n\n  /** URL for tileset metadata. */\n  metadataUrl?: string;\n  stac_version: string;\n  stac_extensions: string[];\n  assets: Record<string, any>;\n};\n\n/**\n * Raster tileset metadata with a remote metadata URL. This metadata can\n * be passed to the map to asynchronously load a raster tileset.\n */\nexport type RasterTileRemoteMetadata = {\n  metadataUrl: string;\n};\n\nexport enum RasterTileType {\n  STAC = 'stac',\n  PMTILES = 'pmtiles'\n}\n\nexport type RasterTileMetadataSourceType = {\n  pmtilesType?: PMTilesType;\n};\n\n/**\n * Raster tileset metadata.\n */\nexport type RasterTileDatasetMetadata = (RasterTileLocalMetadata | RasterTileRemoteMetadata) &\n  RasterTileMetadataSourceType;\n\nexport type WMSDatasetMetadata = {\n  type: typeof REMOTE_TILE;\n  remoteTileFormat: RemoteTileFormat.WMS;\n  tilesetDataUrl: string;\n  tilesetMetadataUrl: string;\n  version: string;\n  layers: {name: string; title: string; boundingBox: number[] | null}[];\n};\n"
  },
  {
    "path": "src/constants/src/default-settings.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\n\nimport {\n  BaseMapStyle,\n  EffectDescription,\n  RGBAColor,\n  SyncTimelineMode,\n  BaseMapColorModes,\n  Merge\n} from '@kepler.gl/types';\nimport {\n  scaleLinear,\n  scaleLog,\n  scaleOrdinal,\n  scalePoint,\n  scaleQuantile,\n  scaleQuantize,\n  scaleSqrt,\n  scaleThreshold\n} from 'd3-scale';\nimport {TOOLTIP_FORMAT_TYPES} from './tooltip';\n\nexport const ACTION_PREFIX = '@@kepler.gl/';\nexport const KEPLER_UNFOLDED_BUCKET = 'https://studio-public-data.foursquare.com/statics/keplergl';\nexport const BASEMAP_ICON_PREFIX = `geodude`;\nexport const DEFAULT_MAPBOX_API_URL = 'https://api.mapbox.com';\nexport const TRANSITION_DURATION = 0;\n\n// Modal Ids\n/**\n * Modal id: data table\n * @constant\n * @type {string}\n * @public\n */\nexport const DATA_TABLE_ID = 'dataTable';\n/**\n * Modal id: delete dataset confirm dialog\n * @constant\n * @type {string}\n * @public\n */\nexport const DELETE_DATA_ID = 'deleteData';\n/**\n * Modal id: add data modal\n * @constant\n * @type {string}\n * @public\n */\nexport const ADD_DATA_ID = 'addData';\n/**\n * Modal id: export image modal\n * @constant\n * @type {string}\n * @public\n */\nexport const EXPORT_IMAGE_ID = 'exportImage';\n/**\n * Modal id: export data modal\n * @constant\n * @type {string}\n * @public\n */\nexport const EXPORT_DATA_ID = 'exportData';\n/**\n * Modal id: add custom map style modal\n * @constant\n * @type {string}\n * @public\n */\nexport const ADD_MAP_STYLE_ID = 'addMapStyle';\n/**\n * Modal id: export map modal\n * @constant\n * @type {string}\n * @public\n */\nexport const EXPORT_MAP_ID = 'exportMap';\n/**\n * Modal id: save map modal\n * @constant\n * @type {string}\n * @public\n */\nexport const SAVE_MAP_ID = 'saveMap';\n/**\n * Modal id: confirm to overwrite saved map\n * @constant\n * @type {string}\n * @public\n */\nexport const OVERWRITE_MAP_ID = 'overwriteMap';\n/**\n * Modal id: share map url modal\n * @constant\n * @type {string}\n * @public\n */\nexport const SHARE_MAP_ID = 'shareMap';\n\nexport const KEPLER_GL_NAME = 'kepler.gl';\n\n// __PACKAGE_VERSION__ is automatically injected by Babel/Esbuild during the build process\n// Since we are injecting this during the build process with babel\n// while developing VERSION is not defined, we capture the exception and return\n// an empty string which will allow us to retrieve the latest umd version\nexport const KEPLER_GL_VERSION = '__PACKAGE_VERSION__';\nexport const KEPLER_GL_WEBSITE = 'http://kepler.gl/';\n\nexport const DIMENSIONS = {\n  sidePanel: {\n    width: 300,\n    margin: {top: 12, left: 12, bottom: 12, right: 20},\n    headerHeight: 96\n  },\n  mapControl: {\n    width: 184,\n    padding: 12,\n    mapLegend: {\n      pinned: {\n        bottom: 22,\n        right: 12\n      }\n    }\n  }\n};\n\n/**\n * Theme name that can be passed to `KeplerGl` `prop.theme`.\n * Available themes are `THEME.light` and `THEME.dark`. Default theme is `THEME.dark`\n * @constant\n * @type {object}\n * @public\n * @example\n * ```js\n * const Map = () => <KeplerGl theme={THEME.light} id=\"map\"/>\n * ```\n */\nexport const THEME = keyMirror({\n  light: null,\n  dark: null,\n  base: null\n});\n\nexport const SIDEBAR_PANELS = [\n  {\n    id: 'layer',\n    label: 'sidebar.panels.layer',\n    onClick: null\n  },\n  {\n    id: 'filter',\n    label: 'sidebar.panels.filter',\n    onClick: null\n  },\n  {\n    id: 'interaction',\n    label: 'sidebar.panels.interaction',\n    onClick: null\n  },\n  {\n    id: 'map',\n    label: 'sidebar.panels.basemap',\n    onClick: null\n  }\n];\n\nexport const PANEL_VIEW_TOGGLES = keyMirror({\n  list: null,\n  byDataset: null\n});\n\n// backward compatibility\nexport const PANELS = SIDEBAR_PANELS;\n\n// MAP STYLES\n\nexport const DEFAULT_BLDG_COLOR = '#D1CEC7';\n\nexport const DEFAULT_BACKGROUND_COLOR = '#000000';\n\n// assists in identifying basemap background layers when auto-determining the backgroundColor\nexport const BASE_MAP_BACKGROUND_LAYER_IDS = ['background', 'bg', 'land', 'water'];\n\nexport const BACKGROUND_LAYER_GROUP_SLUG = 'Background';\n\nexport const THREE_D_BUILDING_LAYER_GROUP_SLUG = '3d building';\n\nexport type DEFAULT_LAYER_GROUP = {\n  slug: string;\n  filter: (value) => boolean;\n  defaultVisibility: boolean;\n  isVisibilityToggleAvailable?: boolean;\n  isMoveToTopAvailable?: boolean;\n  isColorPickerAvailable?: boolean;\n};\n\nexport const BACKGROUND_LAYER_GROUP: DEFAULT_LAYER_GROUP = {\n  slug: BACKGROUND_LAYER_GROUP_SLUG,\n  filter: () => false,\n  defaultVisibility: false,\n  isVisibilityToggleAvailable: false,\n  isMoveToTopAvailable: false,\n  isColorPickerAvailable: true\n};\n\nexport const DEFAULT_LAYER_GROUPS: DEFAULT_LAYER_GROUP[] = [\n  {\n    slug: 'label',\n    filter: ({id, type}) => id.match(/(?=(label|place-|poi-))/) || type?.match(/(?=(symbol))/),\n    defaultVisibility: true,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: 'road',\n    filter: ({id}) => id.match(/(?=(road|railway|tunnel|street|bridge))(?!.*label)/),\n    defaultVisibility: true,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: 'border',\n    filter: ({id}) => id.match(/border|boundaries|boundary/),\n    defaultVisibility: false,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: 'building',\n    filter: ({id}) => id.match(/building/),\n    defaultVisibility: true,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: 'water',\n    filter: ({id}) => id.match(/(?=(water|stream|ferry))/),\n    defaultVisibility: true,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: 'land',\n    filter: ({id}) => id.match(/(?=(parks|landcover|industrial|sand|hillshade))/),\n    defaultVisibility: true,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: false\n  },\n  {\n    slug: THREE_D_BUILDING_LAYER_GROUP_SLUG,\n    filter: () => false,\n    defaultVisibility: false,\n    isVisibilityToggleAvailable: true,\n    isMoveToTopAvailable: true,\n    isColorPickerAvailable: true\n  }\n];\n\nexport const BASE_MAP_COLOR_MODES = keyMirror({\n  NONE: null,\n  DARK: null,\n  LIGHT: null\n});\n\nexport const NO_MAP_ID = 'no_map';\n\n// Fallback style to use when styles are being fetched, or when\n// a style fails to fetch\nexport const EMPTY_MAPBOX_STYLE = {\n  version: 8,\n  sources: {},\n  layers: []\n};\n\nexport const MAP_LIB_OPTIONS = {\n  MAPBOX: 'mapbox' as const,\n  MAPLIBRE: 'maplibre' as const\n};\n\nexport type BaseMapLibraryType = 'mapbox' | 'maplibre';\n\nexport const NO_BASEMAP_ICON = `${BASEMAP_ICON_PREFIX}/NO_BASEMAP.png`;\n\nexport const DEFAULT_BASE_MAP_STYLE = 'dark-matter';\n\ntype DefaultBaseMapStyle = Merge<\n  BaseMapStyle,\n  {\n    colorMode: BaseMapColorModes;\n  }\n>;\n\nexport const DEFAULT_NO_BASEMAP_STYLE: DefaultBaseMapStyle = {\n  id: NO_MAP_ID,\n  label: 'No Basemap',\n  url: '',\n  icon: NO_BASEMAP_ICON,\n  layerGroups: [BACKGROUND_LAYER_GROUP],\n  colorMode: BASE_MAP_COLOR_MODES.NONE,\n  style: EMPTY_MAPBOX_STYLE\n};\n\nexport const DEFAULT_MAPBOX_STYLES: DefaultBaseMapStyle[] = [\n  {\n    id: 'dark',\n    label: 'Dark',\n    url: 'mapbox://styles/uberdata/cjoqbbf6l9k302sl96tyvka09',\n    icon: `${BASEMAP_ICON_PREFIX}/UBER_DARK_V2.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.DARK,\n    complimentaryStyleId: 'light'\n  },\n  {\n    id: 'light',\n    label: 'Light',\n    url: 'mapbox://styles/uberdata/cjoqb9j339k1f2sl9t5ic5bn4',\n    icon: `${BASEMAP_ICON_PREFIX}/UBER_LIGHT_V2.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.LIGHT,\n    complimentaryStyleId: 'dark'\n  },\n  {\n    id: 'muted',\n    label: 'Muted Light',\n    url: 'mapbox://styles/uberdata/cjfyl03kp1tul2smf5v2tbdd4',\n    icon: `${BASEMAP_ICON_PREFIX}/UBER_MUTED_LIGHT.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.LIGHT,\n    complimentaryStyleId: 'muted_night'\n  },\n  {\n    id: 'muted_night',\n    label: 'Muted Night',\n    url: 'mapbox://styles/uberdata/cjfxhlikmaj1b2soyzevnywgs',\n    icon: `${BASEMAP_ICON_PREFIX}/UBER_MUTED_NIGHT.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.DARK,\n    complimentaryStyleId: 'muted'\n  }\n];\n\nexport const DEFAULT_MAPBOX_SATELITE_STYLES: DefaultBaseMapStyle[] = [\n  {\n    id: 'satellite',\n    label: 'Satellite with streets',\n    url: `mapbox://styles/mapbox/satellite-streets-v11`,\n    icon: `${BASEMAP_ICON_PREFIX}/UBER_SATELLITE.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.NONE\n  }\n];\n\nexport const DEFAULT_MAPLIBRE_STYLES: DefaultBaseMapStyle[] = [\n  {\n    id: 'dark-matter',\n    label: 'DarkMatter',\n    url: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n    icon: `${BASEMAP_ICON_PREFIX}/DARKMATTER.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.DARK,\n    complimentaryStyleId: 'positron'\n  },\n  {\n    id: 'positron',\n    label: 'Positron',\n    url: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n    icon: `${BASEMAP_ICON_PREFIX}/POSITRON.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.LIGHT,\n    complimentaryStyleId: 'dark-matter'\n  },\n  {\n    id: 'voyager',\n    label: 'Voyager',\n    url: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',\n    icon: `${BASEMAP_ICON_PREFIX}/VOYAGER.png`,\n    layerGroups: DEFAULT_LAYER_GROUPS,\n    colorMode: BASE_MAP_COLOR_MODES.LIGHT,\n    complimentaryStyleId: 'dark-matter'\n  }\n];\n\nexport const DEFAULT_MAP_STYLES = [\n  DEFAULT_NO_BASEMAP_STYLE,\n  ...DEFAULT_MAPLIBRE_STYLES,\n  ...DEFAULT_MAPBOX_SATELITE_STYLES,\n  ...DEFAULT_MAPBOX_STYLES\n];\n\nexport const GEOJSON_FIELDS = {\n  geojson: ['_geojson', 'all_points', 'geojson']\n};\n\nexport const ICON_FIELDS = {\n  icon: ['icon']\n};\n\nexport const TRIP_POINT_FIELDS: [string, string][] = [\n  ['lat', 'lng'],\n  ['lat', 'lon'],\n  ['lat', 'long'],\n  ['latitude', 'longitude']\n];\n\nexport const ALTITUDE_FIELDS = ['alt', 'altitude'];\nexport const TRIP_ARC_FIELDS = {\n  lat0: 'begintrip',\n  lng0: 'begintrip',\n  lat1: 'dropoff',\n  lng1: 'dropoff'\n};\n\nexport const FILTER_TYPES = keyMirror({\n  range: null,\n  select: null,\n  input: null,\n  timeRange: null,\n  multiSelect: null,\n  polygon: null\n});\n\nexport const FILTER_VIEW_TYPES = keyMirror({\n  side: null,\n  enlarged: null,\n  minified: null\n});\n\nexport const DEFAULT_FILTER_VIEW_TYPE = FILTER_VIEW_TYPES.side;\n\nexport type SCALE_TYPES_DEF = {\n  ordinal: 'ordinal';\n  quantile: 'quantile';\n  quantize: 'quantize';\n  linear: 'linear';\n  sqrt: 'sqrt';\n  log: 'log';\n  point: 'point';\n  threshold: 'threshold';\n  custom: 'custom';\n  customOrdinal: 'customOrdinal';\n};\n\nexport const SCALE_TYPES: SCALE_TYPES_DEF = keyMirror({\n  ordinal: null,\n  quantile: null,\n  quantize: null,\n  linear: null,\n  sqrt: null,\n  log: null,\n  threshold: null,\n  custom: null,\n  customOrdinal: null,\n  // ordinal domain to linear range\n  point: null\n});\nexport const SCALE_TYPE_NAMES: {[key in keyof SCALE_TYPES_DEF]: string} = {\n  ordinal: 'Ordinal',\n  quantile: 'Quantile',\n  quantize: 'Quantize',\n  linear: 'Linear',\n  sqrt: 'Sqrt',\n  log: 'Log',\n  threshold: 'Threshold',\n  custom: 'Custom Breaks',\n  customOrdinal: 'Custom Ordinal',\n  point: 'Point'\n};\n\nexport type SCALE_FUNC_TYPE = {\n  [key in keyof SCALE_TYPES_DEF]: () => number;\n};\n\nexport const SCALE_FUNC = {\n  [SCALE_TYPES.linear]: scaleLinear,\n  [SCALE_TYPES.quantize]: scaleQuantize,\n  [SCALE_TYPES.quantile]: scaleQuantile,\n  [SCALE_TYPES.ordinal]: scaleOrdinal,\n  [SCALE_TYPES.sqrt]: scaleSqrt,\n  [SCALE_TYPES.log]: scaleLog,\n  [SCALE_TYPES.point]: scalePoint,\n  [SCALE_TYPES.threshold]: scaleThreshold,\n  [SCALE_TYPES.custom]: scaleThreshold,\n  [SCALE_TYPES.customOrdinal]: scaleOrdinal\n};\n\nexport const ALL_FIELD_TYPES = keyMirror({\n  boolean: null,\n  date: null,\n  geojson: null,\n  integer: null,\n  real: null,\n  string: null,\n  timestamp: null,\n  point: null,\n  array: null,\n  object: null,\n  geoarrow: null,\n  h3: null\n});\n\n// Data Table\nexport const SORT_ORDER = keyMirror({\n  ASCENDING: null,\n  DESCENDING: null,\n  UNSORT: null\n});\n\nexport const TABLE_OPTION = keyMirror({\n  SORT_ASC: null,\n  SORT_DES: null,\n  UNSORT: null,\n  PIN: null,\n  UNPIN: null,\n  COPY: null,\n  FORMAT_COLUMN: null\n});\n\nexport type TableOption = {\n  value: 'SORT_ASC' | 'SORT_DES' | 'UNSORT' | 'PIN' | 'UNPIN' | 'COPY' | 'FORMAT_COLUMN';\n  display: string;\n  icon: string;\n  condition?: (props: any) => boolean;\n};\nexport const TABLE_OPTION_LIST: TableOption[] = [\n  {\n    value: TABLE_OPTION.SORT_ASC,\n    display: 'Sort Ascending',\n    icon: 'ArrowUp',\n    condition: props => props.sortTableColumn && props.sortMode !== SORT_ORDER.ASCENDING\n  },\n  {\n    value: TABLE_OPTION.SORT_DES,\n    display: 'Sort Descending',\n    icon: 'ArrowDown',\n    condition: props => props.sortTableColumn && props.sortMode !== SORT_ORDER.DESCENDING\n  },\n  {\n    value: TABLE_OPTION.UNSORT,\n    display: 'Unsort Column',\n    icon: 'Cancel',\n    condition: props => props.isSorted\n  },\n  {\n    value: TABLE_OPTION.PIN,\n    display: 'Pin Column',\n    icon: 'Pin',\n    condition: props => !props.isPinned\n  },\n  {\n    value: TABLE_OPTION.UNPIN,\n    display: 'Unpin Column',\n    icon: 'Cancel',\n    condition: props => props.isPinned\n  },\n  {value: TABLE_OPTION.COPY, display: 'Copy Column', icon: 'Clipboard'},\n  {\n    value: TABLE_OPTION.FORMAT_COLUMN,\n    display: 'Format Column',\n    icon: 'Hash',\n    condition: props => props.setDisplayFormat\n  }\n];\n\nconst YELLOW = '248, 194, 28';\nconst PINK = '242, 152, 163';\nconst PURPLE = '160, 106, 206';\nconst BLUE = '140, 210, 205';\nconst BLUE2 = '106, 160, 206';\nconst BLUE3 = '0, 172, 237';\nconst GREEN = '106, 160, 56';\nconst GREEN2 = '74, 165, 150';\nconst RED = '237, 88, 106';\nconst ORANGE = '231, 110, 58';\n\nexport const FIELD_TYPE_DISPLAY = {\n  [ALL_FIELD_TYPES.boolean]: {\n    label: 'bool',\n    color: PINK\n  },\n  [ALL_FIELD_TYPES.date]: {\n    label: 'date',\n    color: PURPLE\n  },\n  [ALL_FIELD_TYPES.geojson]: {\n    label: 'geo',\n    color: BLUE2\n  },\n  [ALL_FIELD_TYPES.geoarrow]: {\n    label: 'geo',\n    color: BLUE2\n  },\n  [ALL_FIELD_TYPES.integer]: {\n    label: 'int',\n    color: YELLOW\n  },\n  [ALL_FIELD_TYPES.real]: {\n    label: 'float',\n    color: YELLOW\n  },\n  [ALL_FIELD_TYPES.string]: {\n    label: 'string',\n    color: BLUE\n  },\n  [ALL_FIELD_TYPES.timestamp]: {\n    label: 'time',\n    color: GREEN\n  },\n  // field pairs\n  [ALL_FIELD_TYPES.point]: {\n    label: 'point',\n    color: BLUE3\n  },\n  [ALL_FIELD_TYPES.array]: {\n    label: 'array',\n    color: ORANGE\n  },\n  [ALL_FIELD_TYPES.object]: {\n    label: 'object',\n    color: GREEN2\n  },\n  [ALL_FIELD_TYPES.h3]: {\n    label: 'h3',\n    color: BLUE\n  }\n};\n\nexport const FIELD_COLORS = {\n  default: RED\n};\nexport const HIGHLIGH_COLOR_3D: RGBAColor = [255, 255, 255, 60];\nexport const CHANNEL_SCALES = keyMirror({\n  color: null,\n  radius: null,\n  size: null,\n  colorAggr: null,\n  sizeAggr: null\n});\n\nexport const AGGREGATION_TYPES: {\n  // default\n  count: 'count';\n  // linear\n  average: 'average';\n  maximum: 'maximum';\n  minimum: 'minimum';\n  median: 'median';\n  stdev: 'stdev';\n  sum: 'sum';\n  variance: 'variance';\n  // ordinal\n  mode: 'mode';\n  countUnique: 'countUnique';\n} = {\n  // default\n  count: 'count',\n  // linear\n  average: 'average',\n  maximum: 'maximum',\n  minimum: 'minimum',\n  median: 'median',\n  stdev: 'stdev',\n  sum: 'sum',\n  variance: 'variance',\n  // ordinal\n  mode: 'mode',\n  countUnique: 'countUnique'\n};\n\nexport const AGGREGATION_TYPE_OPTIONS: {id: string; label: string}[] = Object.entries(\n  AGGREGATION_TYPES\n).map(([key, value]) => ({\n  id: key,\n  label:\n    key === 'stdev'\n      ? 'Std Deviation'\n      : key === 'countUnique'\n      ? 'Count Unique'\n      : typeof value === 'string'\n      ? value.charAt(0).toUpperCase() + value.slice(1)\n      : value\n}));\n\nexport const linearFieldScaleFunctions = {\n  [CHANNEL_SCALES.color]: [SCALE_TYPES.quantize, SCALE_TYPES.quantile, SCALE_TYPES.custom],\n  [CHANNEL_SCALES.radius]: [SCALE_TYPES.sqrt],\n  [CHANNEL_SCALES.size]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log]\n};\n\nconst DEFAULT_AGGREGATION_COLOR_SCALES = [\n  SCALE_TYPES.quantize,\n  SCALE_TYPES.quantile,\n  SCALE_TYPES.custom\n];\n\nexport const linearFieldAggrScaleFunctions = {\n  [CHANNEL_SCALES.colorAggr]: [\n    AGGREGATION_TYPES.average,\n    AGGREGATION_TYPES.maximum,\n    AGGREGATION_TYPES.minimum,\n    AGGREGATION_TYPES.median,\n    AGGREGATION_TYPES.stdev,\n    AGGREGATION_TYPES.sum,\n    AGGREGATION_TYPES.variance,\n    AGGREGATION_TYPES.count\n  ].reduce((prev, cur) => {\n    prev[cur] = DEFAULT_AGGREGATION_COLOR_SCALES;\n    return prev;\n  }, {}),\n\n  [CHANNEL_SCALES.sizeAggr]: {\n    [AGGREGATION_TYPES.average]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.maximum]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.minimum]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.median]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.stdev]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.sum]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],\n    [AGGREGATION_TYPES.variance]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log]\n  }\n};\n\nexport const ordinalFieldScaleFunctions = {\n  [CHANNEL_SCALES.color]: [SCALE_TYPES.ordinal, SCALE_TYPES.customOrdinal],\n  [CHANNEL_SCALES.radius]: [SCALE_TYPES.point],\n  [CHANNEL_SCALES.size]: [SCALE_TYPES.point]\n};\n\nexport const ordinalFieldAggrScaleFunctions = {\n  // [CHANNEL_SCALES.colorAggr]: [SCALE_TYPES.ordinal, SCALE_TYPES.linear],\n  [CHANNEL_SCALES.colorAggr]: {\n    [AGGREGATION_TYPES.mode]: [SCALE_TYPES.ordinal],\n    [AGGREGATION_TYPES.countUnique]: [SCALE_TYPES.quantize, SCALE_TYPES.quantile]\n  },\n\n  // Currently doesn't support yet\n  [CHANNEL_SCALES.sizeAggr]: {}\n};\n\nexport const notSupportedScaleOpts = {\n  [CHANNEL_SCALES.color]: [],\n  [CHANNEL_SCALES.radius]: [],\n  [CHANNEL_SCALES.size]: []\n};\n\nexport const notSupportAggrOpts = {\n  [CHANNEL_SCALES.colorAggr]: {},\n  [CHANNEL_SCALES.sizeAggr]: {}\n};\n\n/**\n * Default aggregation are based on ocunt\n */\nexport const DEFAULT_AGGREGATION = {\n  [CHANNEL_SCALES.colorAggr]: {\n    [AGGREGATION_TYPES.count]: DEFAULT_AGGREGATION_COLOR_SCALES\n  },\n  [CHANNEL_SCALES.sizeAggr]: {\n    [AGGREGATION_TYPES.count]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log]\n  }\n};\n\n/**\n * Define what type of scale operation is allowed on each type of fields\n */\nexport const FIELD_OPTS = {\n  [ALL_FIELD_TYPES.string]: {\n    type: 'categorical',\n    scale: {\n      ...ordinalFieldScaleFunctions,\n      ...ordinalFieldAggrScaleFunctions\n    },\n    format: {\n      legend: d => d,\n      tooltip: []\n    }\n  },\n  [ALL_FIELD_TYPES.real]: {\n    type: 'numerical',\n    scale: {\n      ...linearFieldScaleFunctions,\n      ...linearFieldAggrScaleFunctions\n    },\n    format: {\n      legend: d => d,\n      tooltip: [\n        TOOLTIP_FORMAT_TYPES.NONE,\n        TOOLTIP_FORMAT_TYPES.DECIMAL,\n        TOOLTIP_FORMAT_TYPES.PERCENTAGE\n      ]\n    }\n  },\n  [ALL_FIELD_TYPES.timestamp]: {\n    type: 'time',\n    scale: {\n      ...linearFieldScaleFunctions,\n      ...notSupportAggrOpts\n    },\n    format: {\n      legend: d => d,\n      tooltip: [\n        TOOLTIP_FORMAT_TYPES.NONE,\n        TOOLTIP_FORMAT_TYPES.DATE,\n        TOOLTIP_FORMAT_TYPES.DATE_TIME\n      ]\n    }\n  },\n  [ALL_FIELD_TYPES.integer]: {\n    type: 'numerical',\n    scale: {\n      ...{\n        ...linearFieldScaleFunctions,\n        [CHANNEL_SCALES.color]: [\n          ...linearFieldScaleFunctions[CHANNEL_SCALES.color],\n          SCALE_TYPES.customOrdinal\n        ]\n      },\n      ...linearFieldAggrScaleFunctions\n    },\n    format: {\n      legend: d => d,\n      tooltip: [\n        TOOLTIP_FORMAT_TYPES.NONE,\n        TOOLTIP_FORMAT_TYPES.DECIMAL,\n        TOOLTIP_FORMAT_TYPES.PERCENTAGE\n      ]\n    }\n  },\n  [ALL_FIELD_TYPES.boolean]: {\n    type: 'boolean',\n    scale: {\n      ...ordinalFieldScaleFunctions,\n      ...ordinalFieldAggrScaleFunctions\n    },\n    format: {\n      legend: d => d,\n      tooltip: [TOOLTIP_FORMAT_TYPES.NONE, TOOLTIP_FORMAT_TYPES.BOOLEAN]\n    }\n  },\n  [ALL_FIELD_TYPES.date]: {\n    type: 'time',\n    scale: {\n      ...ordinalFieldScaleFunctions,\n      ...ordinalFieldAggrScaleFunctions\n    },\n    format: {\n      legend: d => d,\n      tooltip: [TOOLTIP_FORMAT_TYPES.NONE, TOOLTIP_FORMAT_TYPES.DATE]\n    }\n  },\n  [ALL_FIELD_TYPES.geojson]: {\n    type: 'geometry',\n    scale: {\n      ...notSupportedScaleOpts,\n      ...notSupportAggrOpts\n    },\n    format: {\n      legend: () => '...',\n      tooltip: []\n    }\n  },\n  [ALL_FIELD_TYPES.geoarrow]: {\n    type: 'geometry',\n    scale: {\n      ...notSupportedScaleOpts,\n      ...notSupportAggrOpts\n    },\n    format: {\n      legend: () => '...',\n      tooltip: []\n    }\n  },\n  [ALL_FIELD_TYPES.object]: {\n    type: 'numerical',\n    scale: {},\n    format: {\n      legend: () => '...',\n      tooltip: []\n    }\n  },\n  [ALL_FIELD_TYPES.array]: {\n    type: 'numerical',\n    scale: {},\n    format: {\n      legend: () => '...',\n      tooltip: []\n    }\n  },\n  [ALL_FIELD_TYPES.h3]: {\n    type: 'h3',\n    scale: {\n      ...notSupportedScaleOpts,\n      ...notSupportAggrOpts\n    },\n    format: {\n      legend: () => '...',\n      tooltip: []\n    }\n  }\n};\n\nexport const CHANNEL_SCALE_SUPPORTED_FIELDS = Object.keys(CHANNEL_SCALES).reduce(\n  (accu, key) => ({\n    ...accu,\n    [key]: Object.keys(FIELD_OPTS).filter(\n      ft => FIELD_OPTS[ft].scale[key] && Object.keys(FIELD_OPTS[ft].scale[key]).length\n    )\n  }),\n  {} as {[id: string]: string[]}\n);\n\nexport const DEFAULT_LAYER_COLOR = {\n  tripArc: '#9226C6',\n  begintrip_lat: '#1E96BE',\n  dropoff_lat: '#FF991F',\n  request_lat: '#52A353'\n};\n\nexport const DEFAULT_LAYER_COLOR_PALETTE = 'Global Warming';\nexport const DEFAULT_LAYER_COLOR_PALETTE_STEPS = 6;\n\n// let user pass in default tooltip fields\nexport const DEFAULT_TOOLTIP_FIELDS: any[] = [];\n\nexport const NO_VALUE_COLOR: RGBAColor = [0, 0, 0, 0];\n\nexport const DEFAULT_PICKING_RADIUS = 3;\n\nexport const OVERLAY_BLENDINGS = {\n  normal: {\n    label: 'overlayBlending.normal',\n    value: 'normal'\n  },\n  screen: {\n    label: 'overlayBlending.screen',\n    value: 'screen'\n  },\n  darken: {\n    label: 'overlayBlending.darken',\n    value: 'darken'\n  }\n};\n\nexport const LAYER_BLENDINGS = {\n  additive: {\n    label: 'layerBlending.additive',\n    blendFunc: ['SRC_ALPHA', 'DST_ALPHA'],\n    blendEquation: 'FUNC_ADD'\n  },\n  normal: {\n    // reference to\n    // https://limnu.com/webgl-blending-youre-probably-wrong/\n    label: 'layerBlending.normal',\n    blendFunc: ['SRC_ALPHA', 'ONE_MINUS_SRC_ALPHA', 'ONE', 'ONE_MINUS_SRC_ALPHA'],\n    blendEquation: ['FUNC_ADD', 'FUNC_ADD']\n  },\n  subtractive: {\n    label: 'layerBlending.subtractive',\n    blendFunc: ['ONE', 'ONE_MINUS_DST_COLOR', 'SRC_ALPHA', 'DST_ALPHA'],\n    blendEquation: ['FUNC_SUBTRACT', 'FUNC_ADD']\n  }\n};\n\nexport const MAX_DEFAULT_TOOLTIPS = 5;\n\nexport const RESOLUTIONS = keyMirror({\n  ONE_X: null,\n  TWO_X: null\n});\n\nexport const EXPORT_IMG_RATIOS = keyMirror({\n  SCREEN: null,\n  FOUR_BY_THREE: null,\n  SIXTEEN_BY_NINE: null,\n  CUSTOM: null\n});\n\nexport type ImageRatioOption = {\n  id: keyof typeof EXPORT_IMG_RATIOS;\n  label: string;\n  hidden?: boolean;\n  getSize: (screenW: number, screenH: number) => {width: number; height: number};\n};\n\nexport const ScreenRatioOption: ImageRatioOption = {\n  id: EXPORT_IMG_RATIOS.SCREEN,\n  label: 'modal.exportImage.ratioOriginalScreen',\n  getSize: (screenW, screenH) => ({width: screenW, height: screenH})\n};\nexport const CustomRatioOption: ImageRatioOption = {\n  id: EXPORT_IMG_RATIOS.CUSTOM,\n  hidden: true,\n  label: 'modal.exportImage.ratioCustom',\n  getSize: (mapW, mapH) => ({width: mapW, height: mapH})\n};\nexport const FourByThreeRatioOption: ImageRatioOption = {\n  id: EXPORT_IMG_RATIOS.FOUR_BY_THREE,\n  label: 'modal.exportImage.ratio4_3',\n  getSize: screenW => ({\n    width: screenW,\n    height: Math.round(screenW * 0.75)\n  })\n};\nexport const SixteenByNineRatioOption: ImageRatioOption = {\n  id: EXPORT_IMG_RATIOS.SIXTEEN_BY_NINE,\n  label: 'modal.exportImage.ratio16_9',\n  getSize: screenW => ({\n    width: screenW,\n    height: Math.round(screenW * 0.5625)\n  })\n};\n\nexport const EXPORT_IMG_RATIO_OPTIONS: ReadonlyArray<ImageRatioOption> = [\n  ScreenRatioOption,\n  FourByThreeRatioOption,\n  SixteenByNineRatioOption,\n  CustomRatioOption\n];\n\nexport type ExportResolutionOption =\n  | keyof typeof RESOLUTIONS\n  | '1280x720'\n  | '1920x1080'\n  | '2560x1440'\n  | '1600x900'\n  | '1024x768'\n  | '1280x960'\n  | '1600x1200'\n  | '1920x1440';\n\nexport type ImageResolutionOption = {\n  id: ExportResolutionOption;\n  label: string;\n  available: boolean;\n  scale?: number;\n  getSize: (screenW: number, screenH: number) => {width: number; height: number};\n};\n\nexport const OneXResolutionOption: ImageResolutionOption = {\n  id: RESOLUTIONS.ONE_X,\n  label: '1x',\n  available: true,\n  scale: 1,\n  getSize: (screenW, screenH) => ({\n    width: screenW,\n    height: screenH\n  })\n};\n\nexport const TwoXResolutionOption: ImageResolutionOption = {\n  id: RESOLUTIONS.TWO_X,\n  label: '2x',\n  available: true,\n  scale: 2,\n  getSize: (screenW, screenH) => ({\n    width: screenW * 2,\n    height: screenH * 2\n  })\n};\n\n// Fixed dimension options\nexport const Resolution1920x1080Option: ImageResolutionOption = {\n  id: '1920x1080',\n  label: '1920 × 1080 (16:9)',\n  available: true,\n  getSize: () => ({width: 1920, height: 1080})\n};\n\nexport const Resolution1280x720Option: ImageResolutionOption = {\n  id: '1280x720',\n  label: '1280 × 720 (16:9)',\n  available: true,\n  getSize: () => ({width: 1280, height: 720})\n};\n\nexport const Resolution2560x1440Option: ImageResolutionOption = {\n  id: '2560x1440',\n  label: '2560 × 1440 (16:9)',\n  available: true,\n  getSize: () => ({width: 2560, height: 1440})\n};\n\nexport const Resolution1600x900Option: ImageResolutionOption = {\n  id: '1600x900',\n  label: '1600 × 900 (16:9)',\n  available: true,\n  getSize: () => ({width: 1600, height: 900})\n};\n\nexport const Resolution1024x768Option: ImageResolutionOption = {\n  id: '1024x768',\n  label: '1024 × 768 (4:3)',\n  available: true,\n  getSize: () => ({width: 1024, height: 768})\n};\n\nexport const Resolution1280x960Option: ImageResolutionOption = {\n  id: '1280x960',\n  label: '1280 × 960 (4:3)',\n  available: true,\n  getSize: () => ({width: 1280, height: 960})\n};\n\nexport const Resolution1600x1200Option: ImageResolutionOption = {\n  id: '1600x1200',\n  label: '1600 × 1200 (4:3)',\n  available: true,\n  getSize: () => ({width: 1600, height: 1200})\n};\n\nexport const Resolution1920x1440Option: ImageResolutionOption = {\n  id: '1920x1440',\n  label: '1920 × 1440 (4:3)',\n  available: true,\n  getSize: () => ({width: 1920, height: 1440})\n};\n\nexport const EXPORT_IMG_RESOLUTION_OPTIONS: ReadonlyArray<ImageResolutionOption> = [\n  OneXResolutionOption,\n  TwoXResolutionOption,\n  Resolution1280x720Option,\n  Resolution1920x1080Option,\n  Resolution2560x1440Option,\n  Resolution1600x900Option,\n  Resolution1024x768Option,\n  Resolution1280x960Option,\n  Resolution1600x1200Option,\n  Resolution1920x1440Option\n];\n\nexport const EXPORT_DATA_TYPE = keyMirror({\n  CSV: null\n  // SHAPEFILE: null,\n  // JSON: null,\n  // GEOJSON: null,\n  // TOPOJSON: null\n});\n\nexport const EXPORT_DATA_TYPE_OPTIONS = [\n  {\n    id: EXPORT_DATA_TYPE.CSV,\n    label: EXPORT_DATA_TYPE.CSV.toLowerCase(),\n    available: true\n  }\n  // {\n  //   id: EXPORT_DATA_TYPE.SHAPEFILE,\n  //   label: 'shapefile',\n  //   available: false\n  // },\n  // {\n  //   id: EXPORT_DATA_TYPE.JSON,\n  //   label: 'json',\n  //   available: false\n  // },\n  // {\n  //   id: EXPORT_DATA_TYPE.GEOJSON,\n  //   label: 'geojson',\n  //   available: false\n  // },\n  // {\n  //   id: EXPORT_DATA_TYPE.TOPOJSON,\n  //   label: 'topojson',\n  //   available: false\n  // }\n];\n\n// Export map types\nexport const EXPORT_MAP_FORMATS = keyMirror({\n  HTML: null,\n  JSON: null\n});\n\nexport const EXPORT_HTML_MAP_MODES = keyMirror({\n  READ: null,\n  EDIT: null\n});\n\n// Export map options\nexport const EXPORT_MAP_FORMAT_OPTIONS = Object.entries(EXPORT_MAP_FORMATS).map(\n  (entry: [string, any]) => ({\n    id: entry[0],\n    label: entry[1].toLowerCase(),\n    available: true\n  })\n);\n\nexport function getHTMLMapModeTileUrl(mode: string): string {\n  return `https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/documentation/map-${mode.toLowerCase()}-mode.png`;\n}\n\nexport const EXPORT_HTML_MAP_MODE_OPTIONS = Object.entries(EXPORT_HTML_MAP_MODES).map(\n  (entry: [string, any]) => ({\n    id: entry[0],\n    label: `modal.exportMap.html.${entry[1].toLowerCase()}`,\n    available: true,\n    url: getHTMLMapModeTileUrl(entry[1])\n  })\n);\n\nexport const DEFAULT_UUID_COUNT = 6;\n\nexport const DEFAULT_NOTIFICATION_MESSAGE = 'MESSAGE_NOT_PROVIDED';\n\nexport const DEFAULT_NOTIFICATION_TYPES = keyMirror({\n  info: null,\n  error: null,\n  warning: null,\n  success: null\n});\n\nexport const DEFAULT_NOTIFICATION_TOPICS = keyMirror({\n  global: null,\n  file: null\n});\n\n// Minimum time between identical notifications about deck.gl errors\nexport const THROTTLE_NOTIFICATION_TIME = 330;\n\n// Animation\nexport const BASE_SPEED = 600;\nexport const FPS = 60;\n\n/**\n * 4 Animation Window Types\n * 1. free\n *  |->  |->\n * Current time is a fixed range, animation controller calls next animation frames continuously to animation a moving window\n * The increment id based on domain / BASE_SPEED * SPEED\n *\n * 2. incremental\n * |    |->\n * Same as free, current time is a growing range, only the max value of range increment during animation.\n * The increment is also based on domain / BASE_SPEED * SPEED\n *\n * 3. point\n * o -> o\n * Current time is a point, animation controller calls next animation frame continuously to animation a moving point\n * The increment is based on domain / BASE_SPEED * SPEED\n *\n * 4. interval\n * o ~> o\n * Current time is a point. An array of sorted time steps need to be provided.\n * animation controller calls next animation at a interval when the point jumps to the next step\n */\nexport const ANIMATION_WINDOW = keyMirror({\n  free: null,\n  incremental: null,\n  point: null,\n  interval: null\n});\nexport const DEFAULT_TIME_FORMAT = 'MM/DD/YY HH:mm:ssa';\nexport const SPEED_CONTROL_RANGE: [number, number] = [0, 10];\nexport const SPEED_CONTROL_STEP = 0.001;\n\n// Geocoder\nexport const GEOCODER_DATASET_NAME = 'geocoder_dataset';\nexport const GEOCODER_LAYER_ID = 'geocoder_layer';\nexport const GEOCODER_GEO_OFFSET = 0.05;\nexport const GEOCODER_ICON_COLOR: [number, number, number] = [255, 0, 0];\nexport const GEOCODER_ICON_SIZE = 80;\n\n// Editor\nexport const EDITOR_LAYER_ID = 'kepler_editor_layer';\nexport const EDITOR_LAYER_PICKING_RADIUS = 6;\nexport const EDITOR_MODES = {\n  DRAW_POLYGON: 'DRAW_POLYGON',\n  DRAW_RECTANGLE: 'DRAW_RECTANGLE',\n  EDIT: 'EDIT_VERTEX'\n};\n\nexport const PLOT_TYPES = keyMirror({\n  histogram: null,\n  lineChart: null\n});\n\n// Filter\nexport const INIT_FILTER_ITEMS_IN_DROPDOWN = 100;\n\n// GPU Filtering\n/**\n * Max number of filter value buffers that deck.gl provides\n */\nexport const MAX_GPU_FILTERS = 4;\nexport const MAP_THUMBNAIL_DIMENSION = {\n  width: 300,\n  height: 200\n};\n\nexport const MAP_INFO_CHARACTER = {\n  title: 100,\n  description: 100\n};\n\n// Load data\nexport const LOADING_METHODS = keyMirror({\n  upload: null,\n  storage: null,\n  tileset: null\n});\n\nexport const DEFAULT_FEATURE_FLAGS = {};\n\nexport const DATASET_FORMATS = keyMirror({\n  row: null,\n  geojson: null,\n  csv: null,\n  keplergl: null,\n  arrow: null,\n  duckdb: null\n});\n\nexport const MAP_CONTROLS = keyMirror({\n  visibleLayers: null,\n  mapLegend: null,\n  toggle3d: null,\n  splitMap: null,\n  mapDraw: null,\n  mapLocale: null,\n  effect: null,\n  aiAssistant: null\n});\n\n/**\n * A multiplier for screen-space width/scale for Arc, Line, Icon and Text layers.\n * Required in order to maintain the same appearance after upgrading to deck.gl v8.5.\n * https://github.com/visgl/deck.gl/blob/master/docs/upgrade-guide.md\n */\nexport const PROJECTED_PIXEL_SIZE_MULTIPLIER = 2 / 3;\n\n/**\n * Maximum value for text outline width\n */\nexport const TEXT_OUTLINE_MULTIPLIER = 5;\n\nexport const dataTestIds: Record<string, string> = {\n  infoIcon: 'info-icon',\n  warningIcon: 'warning-icon',\n  errorIcon: 'error-icon',\n  successIcon: 'success-icon',\n  checkmarkIcon: 'checkmark-icon',\n  sortableLayerItem: 'sortable-layer-item',\n  staticLayerItem: 'static-layer-item',\n  layerTitleEditor: 'layer__title__editor',\n  removeLayerAction: 'remove-layer-action',\n  layerPanel: 'layer-panel',\n  sortableEffectItem: 'sortable-effect-item',\n  staticEffectItem: 'static-effect-item',\n  providerLoading: 'provider-loading',\n  providerMapInfoPanel: 'provider-map-info-panel',\n  providerSelect: 'provider-select',\n  cloudHeader: 'cloud-header',\n  providerShareMap: 'provider-share-map'\n};\n\n// Effects\nexport const DEFAULT_TIMEZONE = 'UTC';\nexport const DEFAULT_POST_PROCESSING_EFFECT_TYPE = 'ink';\n\nexport const DEFAULT_LIGHT_COLOR: [number, number, number] = [255, 255, 255];\nexport const DEFAULT_LIGHT_INTENSITY = 1;\nexport const DEFAULT_SHADOW_INTENSITY = 0.5;\nexport const DEFAULT_SHADOW_COLOR: [number, number, number] = [0, 0, 0];\n\nexport const LIGHT_AND_SHADOW_EFFECT_TIME_MODES = {\n  pick: 'pick' as const,\n  current: 'current' as const,\n  animation: 'animation' as const\n};\nexport type LightAndShadowEffectTimeMode = 'pick' | 'current' | 'animation';\nexport const DEFAULT_LIGHT_AND_SHADOW_PROPS: {\n  timestamp: number;\n  timeMode: LightAndShadowEffectTimeMode;\n  shadowIntensity: number;\n  shadowColor: [number, number, number];\n  sunLightColor: [number, number, number];\n  sunLightIntensity: number;\n  ambientLightColor: [number, number, number];\n  ambientLightIntensity: number;\n} = {\n  timestamp: Date.now(),\n  timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick as LightAndShadowEffectTimeMode,\n  shadowIntensity: DEFAULT_SHADOW_INTENSITY,\n  shadowColor: [...DEFAULT_SHADOW_COLOR] as [number, number, number],\n  sunLightColor: [...DEFAULT_LIGHT_COLOR] as [number, number, number],\n  sunLightIntensity: DEFAULT_LIGHT_INTENSITY,\n  ambientLightColor: [...DEFAULT_LIGHT_COLOR] as [number, number, number],\n  ambientLightIntensity: DEFAULT_LIGHT_INTENSITY\n};\n\nexport const LIGHT_AND_SHADOW_EFFECT: EffectDescription = {\n  type: 'lightAndShadow',\n  name: 'Light & Shadow',\n  parameters: [\n    {name: 'timestamp', min: 0, max: Number.MAX_SAFE_INTEGER},\n    {name: 'shadowIntensity', min: 0, max: 1, defaultValue: DEFAULT_SHADOW_INTENSITY},\n    {name: 'sunLightIntensity', min: 0, max: 1, defaultValue: DEFAULT_LIGHT_INTENSITY},\n    {name: 'ambientLightIntensity', min: 0, max: 1, defaultValue: DEFAULT_LIGHT_INTENSITY},\n    {name: 'shadowColor', type: 'color', min: 0, max: 255, defaultValue: DEFAULT_SHADOW_COLOR},\n    {name: 'sunLightColor', type: 'color', min: 0, max: 255, defaultValue: DEFAULT_LIGHT_COLOR},\n    {name: 'ambientLightColor', type: 'color', min: 0, max: 255, defaultValue: DEFAULT_LIGHT_COLOR}\n  ]\n};\n\nexport const POSTPROCESSING_EFFECTS: {[key: string]: EffectDescription} = {\n  ink: {\n    type: 'ink',\n    name: 'Ink',\n    parameters: [{name: 'strength', min: 0, max: 1}]\n  },\n  brightnessContrast: {\n    type: 'brightnessContrast',\n    name: 'Brightness & Contrast',\n    parameters: [\n      {name: 'brightness', min: -1, max: 1},\n      {name: 'contrast', min: -1, max: 1}\n    ]\n  },\n  hueSaturation: {\n    type: 'hueSaturation',\n    name: 'Hue & Saturation',\n    parameters: [\n      {name: 'hue', min: -1, max: 1},\n      {name: 'saturation', defaultValue: 0.25, min: -1, max: 1}\n    ]\n  },\n  vibrance: {\n    type: 'vibrance',\n    name: 'Vibrance',\n    parameters: [{name: 'amount', defaultValue: 0.5, min: -1, max: 1}]\n  },\n  sepia: {\n    type: 'sepia',\n    name: 'Sepia',\n    parameters: [{name: 'amount', min: 0, max: 1}]\n  },\n  dotScreen: {\n    type: 'dotScreen',\n    name: 'Dot Screen',\n    parameters: [\n      {\n        name: 'angle',\n        min: 0,\n        max: Math.PI / 2\n      },\n      {\n        name: 'size',\n        min: 1,\n        max: 20\n      },\n      {\n        name: 'center',\n        type: 'array',\n        label: ['Center X', 'Center Y'],\n        defaultValue: [0.5, 0.5],\n        min: 0,\n        max: 1\n      }\n    ]\n  },\n  colorHalftone: {\n    type: 'colorHalftone',\n    name: 'Color Halftone',\n    parameters: [\n      {\n        name: 'angle',\n        min: 0,\n        max: Math.PI / 2\n      },\n      {\n        name: 'size',\n        min: 1,\n        max: 20\n      },\n      {\n        name: 'center',\n        type: 'array',\n        label: ['Center X', 'Center Y'],\n        defaultValue: [0.5, 0.5],\n        min: 0,\n        max: 1\n      }\n    ]\n  },\n  noise: {\n    type: 'noise',\n    name: 'Noise',\n    parameters: [{name: 'amount', min: 0, max: 1}]\n  },\n  triangleBlur: {\n    type: 'triangleBlur',\n    name: 'Blur (Triangle)',\n    parameters: [{name: 'radius', min: 0, max: 100}]\n  },\n  zoomBlur: {\n    type: 'zoomBlur',\n    name: 'Blur (Zoom)',\n    parameters: [\n      {\n        name: 'strength',\n        defaultValue: 0.05,\n        min: 0,\n        max: 1\n      },\n      {\n        name: 'center',\n        type: 'array',\n        label: ['Center X', 'Center Y'],\n        defaultValue: [0.5, 0.5],\n        min: 0,\n        max: 1\n      }\n    ]\n  },\n  tiltShift: {\n    type: 'tiltShift',\n    name: 'Blur (Tilt Shift)',\n    parameters: [\n      {\n        name: 'blurRadius',\n        label: 'Blur',\n        min: 0,\n        max: 50\n      },\n      {\n        name: 'gradientRadius',\n        label: 'Gradient',\n        min: 0,\n        max: 400\n      },\n      {\n        name: 'start',\n        type: 'array',\n        label: ['Start', false],\n        defaultValue: [0.0, 0.0],\n        min: 0,\n        max: 1\n      },\n      {\n        name: 'end',\n        type: 'array',\n        label: ['End', false],\n        defaultValue: [1, 1],\n        min: 0,\n        max: 1\n      }\n    ]\n  },\n  edgeWork: {\n    type: 'edgeWork',\n    name: 'Edge work',\n    parameters: [{name: 'radius', min: 1, max: 50}]\n  },\n  vignette: {\n    type: 'vignette',\n    name: 'Vignette',\n    parameters: [\n      {name: 'amount', min: 0, max: 1},\n      {name: 'radius', min: 0, max: 1}\n    ]\n  },\n  magnify: {\n    type: 'magnify',\n    name: 'Magnify',\n    parameters: [\n      {\n        name: 'screenXY',\n        type: 'array',\n        label: ['Position X', 'Position Y'],\n        defaultValue: [0.5, 0.5],\n        min: 0,\n        max: 1\n      },\n      {\n        name: 'radiusPixels',\n        label: 'Size',\n        min: 10,\n        max: 500\n      },\n      {\n        name: 'zoom',\n        min: 0.5,\n        max: 50\n      },\n      {\n        name: 'borderWidthPixels',\n        label: 'Border Width',\n        defaultValue: 3,\n        min: 0,\n        max: 50\n      }\n    ]\n  },\n  hexagonalPixelate: {\n    type: 'hexagonalPixelate',\n    name: 'Hexagonal Pixelate',\n    parameters: [{name: 'scale', defaultValue: 20, min: 1, max: 50}]\n  }\n};\n\nexport const EFFECT_DESCRIPTIONS: EffectDescription[] = [\n  LIGHT_AND_SHADOW_EFFECT,\n  ...Object.keys(POSTPROCESSING_EFFECTS).map(keyName => POSTPROCESSING_EFFECTS[keyName])\n];\n\nexport type EffectType =\n  | 'ink'\n  | 'brightnessContrast'\n  | 'hueSaturation'\n  | 'vibrance'\n  | 'sepia'\n  | 'dotScreen'\n  | 'colorHalftone'\n  | 'noise'\n  | 'triangleBlur'\n  | 'zoomBlur'\n  | 'tiltShift'\n  | 'edgeWork'\n  | 'vignette'\n  | 'magnify'\n  | 'hexagonalPixelate'\n  | 'lightAndShadow';\n\nexport const SYNC_TIMELINE_MODES: Record<string, SyncTimelineMode> = {\n  start: 0,\n  end: 1\n};\n\nexport const GEOARROW_METADATA_KEY = 'ARROW:extension:name';\n\n/**\n * Enum holding GeoArrow extension type names\n */\nexport enum GEOARROW_EXTENSIONS {\n  POINT = 'geoarrow.point',\n  LINESTRING = 'geoarrow.linestring',\n  POLYGON = 'geoarrow.polygon',\n  MULTIPOINT = 'geoarrow.multipoint',\n  MULTILINESTRING = 'geoarrow.multilinestring',\n  MULTIPOLYGON = 'geoarrow.multipolygon',\n  WKB = 'geoarrow.wkb'\n}\n\nexport const LOADERS_CDN_VERSION = '4.3.2';\nexport const LOADERS_CDN_URL = 'https://unpkg.com/@loaders.gl';\n\nexport const getLoaderOptions = () => {\n  return {\n    mvt: {\n      workerUrl: `${LOADERS_CDN_URL}/mvt@${LOADERS_CDN_VERSION}/dist/mvt-worker.js`\n    },\n    'quantized-mesh': {\n      workerUrl: `${LOADERS_CDN_URL}/terrain@${LOADERS_CDN_VERSION}/dist/quantized-mesh-worker.js`\n    },\n    npy: {\n      workerUrl: `${LOADERS_CDN_URL}/textures@${LOADERS_CDN_VERSION}/dist/npy-worker.js`\n    }\n  };\n};"
  },
  {
    "path": "src/constants/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Constants\n\nexport * from './colors-by-theme';\nexport * from './color-palettes';\nexport * from './default-settings';\nexport * from './layers';\nexport * from './dataset';\nexport {default as KeyEvent} from './keyevent';\nexport * from './tooltip';\nexport * from './user-feedbacks';\nexport * from './user-guides';\nexport * from './time';\n"
  },
  {
    "path": "src/constants/src/keyevent.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Window from 'global/window';\n/* eslint-disable no-use-before-define */\nconst KeyEvent = Object.assign({}, Window.KeyEvent);\n/* eslint-enable no-use-before-define */\n\n// event event.keyCode is deprecated, should use event.key in the future\nKeyEvent.DOM_VK_UP = KeyEvent.DOM_VK_UP || 38;\nKeyEvent.DOM_VK_DOWN = KeyEvent.DOM_VK_DOWN || 40;\nKeyEvent.DOM_VK_BACK_SPACE = KeyEvent.DOM_VK_BACK_SPACE || 8;\nKeyEvent.DOM_VK_RETURN = KeyEvent.DOM_VK_RETURN || 13;\nKeyEvent.DOM_VK_ENTER = KeyEvent.DOM_VK_ENTER || 14;\nKeyEvent.DOM_VK_ESCAPE = KeyEvent.DOM_VK_ESCAPE || 27;\nKeyEvent.DOM_VK_TAB = KeyEvent.DOM_VK_TAB || 9;\nKeyEvent.DOM_VK_DELETE = KeyEvent.DOM_VK_DELETE || 46;\n\nexport default KeyEvent;\n"
  },
  {
    "path": "src/constants/src/layers.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\n\nimport {\n  AGGREGATION_TYPES,\n  DEFAULT_LAYER_COLOR_PALETTE,\n  DEFAULT_LAYER_COLOR_PALETTE_STEPS\n} from './default-settings';\nimport {\n  ColorRange,\n  ColorUI,\n  LayerTextConfig,\n  LayerTextLabel,\n  LayerVisConfigSettings,\n  RGBAColor\n} from '@kepler.gl/types';\nimport {ColorPalette, KEPLER_COLOR_PALETTES, colorPaletteToColorRange} from './color-palettes';\n\nexport type AggregationTypes = keyof typeof AGGREGATION_TYPES;\n\nexport const PROPERTY_GROUPS = keyMirror({\n  color: null,\n  stroke: null,\n  radius: null,\n  height: null,\n  angle: null,\n  // for heatmap aggregation\n  cell: null,\n  precision: null,\n  display: null,\n  interaction: null\n});\n\nexport const DEFAULT_LAYER_OPACITY = 0.8;\nexport const DEFAULT_HIGHLIGHT_COLOR: RGBAColor = [252, 242, 26, 255];\nexport const DEFAULT_LAYER_LABEL = 'new layer';\n\nexport const DEFAULT_TEXT_LABEL: LayerTextLabel = {\n  field: null,\n  color: [255, 255, 255],\n  size: 18,\n  offset: [0, 0],\n  anchor: 'start',\n  alignment: 'center',\n  outlineWidth: 0,\n  outlineColor: [255, 0, 0, 255],\n  background: false,\n  backgroundColor: [0, 0, 200, 255]\n};\n\nconst DEFAULT_COLOR_PALETTE = KEPLER_COLOR_PALETTES.find(\n  ({name}) => name === DEFAULT_LAYER_COLOR_PALETTE\n) as ColorPalette;\n\nexport const DEFAULT_COLOR_RANGE = colorPaletteToColorRange(DEFAULT_COLOR_PALETTE, {\n  reversed: false,\n  steps: DEFAULT_LAYER_COLOR_PALETTE_STEPS\n});\n\nexport const DEFAULT_CUSTOM_PALETTE: ColorRange = {\n  name: 'color.customPalette',\n  type: 'custom',\n  category: 'Custom',\n  colors: []\n};\n\nexport const UNKNOWN_COLOR_KEY = '__unknownColor__';\n\nexport const DEFAULT_COLOR_UI: ColorUI = {\n  // customPalette in edit\n  customPalette: DEFAULT_CUSTOM_PALETTE,\n  // show color sketcher modal\n  showSketcher: false,\n  // show color range selection panel\n  showDropdown: false,\n  // show color chart\n  showColorChart: false,\n  // color range selector config\n  colorRangeConfig: {\n    type: 'all',\n    steps: 6,\n    reversed: false,\n    colorBlindSafe: false,\n    custom: false,\n    customBreaks: false\n  }\n};\n\nexport const LAYER_VIS_CONFIGS: LayerVisConfigSettings = {\n  thickness: {\n    type: 'number',\n    defaultValue: 2,\n    label: 'layerVisConfigs.strokeWidth',\n    isRanged: false,\n    range: [0, 100],\n    step: 0.1,\n    group: PROPERTY_GROUPS.stroke,\n    property: 'thickness',\n    allowCustomValue: true\n  },\n  strokeWidthRange: {\n    type: 'number',\n    defaultValue: [0, 10],\n    label: 'layerVisConfigs.strokeWidthRange',\n    isRanged: true,\n    range: [0, 200],\n    step: 0.1,\n    group: PROPERTY_GROUPS.stroke,\n    property: 'sizeRange',\n    allowCustomValue: true\n  },\n  trailLength: {\n    type: 'number',\n    defaultValue: 180,\n    label: 'layerVisConfigs.strokeWidth',\n    isRanged: false,\n    range: [1, 1000],\n    step: 1,\n    group: PROPERTY_GROUPS.stroke,\n    property: 'trailLength',\n    allowCustomValue: true\n  },\n  fadeTrail: {\n    defaultValue: true,\n    type: 'boolean',\n    label: 'layerVisConfigs.fadeTrail',\n    group: PROPERTY_GROUPS.stroke,\n    property: 'fadeTrail'\n  },\n  billboard: {\n    defaultValue: false,\n    type: 'boolean',\n    label: 'layerVisConfigs.billboard',\n    description: 'layerVisConfigs.billboardDescription',\n    group: PROPERTY_GROUPS.display,\n    property: 'billboard'\n  },\n  // radius is actually radiusScale in deck.gl\n  radius: {\n    type: 'number',\n    defaultValue: 10,\n    label: 'layerVisConfigs.radius',\n    isRanged: false,\n    range: [0, 100],\n    step: 0.1,\n    group: PROPERTY_GROUPS.radius,\n    property: 'radius',\n    allowCustomValue: true\n  },\n  fixedRadius: {\n    defaultValue: false,\n    type: 'boolean',\n    label: 'layerVisConfigs.fixedRadius',\n    description: 'layerVisConfigs.fixedRadiusDescription',\n    group: PROPERTY_GROUPS.radius,\n    property: 'fixedRadius'\n  },\n  fixedHeight: {\n    defaultValue: false,\n    type: 'boolean',\n    label: 'layerVisConfigs.fixedHeight',\n    description: 'layerVisConfigs.fixedHeightDescription',\n    group: PROPERTY_GROUPS.height,\n    property: 'fixedHeight'\n  },\n  radiusRange: {\n    type: 'number',\n    defaultValue: [0, 50],\n    isRanged: true,\n    range: [0, 500],\n    step: 0.1,\n    label: 'layerVisConfigs.radiusRange',\n    group: PROPERTY_GROUPS.radius,\n    property: 'radiusRange',\n    allowCustomValue: true\n  },\n  clusterRadius: {\n    type: 'number',\n    label: 'layerVisConfigs.clusterRadius',\n    defaultValue: 40,\n    isRanged: false,\n    range: [1, 500],\n    step: 0.1,\n    group: PROPERTY_GROUPS.radius,\n    property: 'clusterRadius',\n    allowCustomValue: true\n  },\n  clusterRadiusRange: {\n    type: 'number',\n    label: 'layerVisConfigs.radiusRangePixels',\n    defaultValue: [1, 40],\n    isRanged: true,\n    range: [1, 150],\n    step: 0.1,\n    group: PROPERTY_GROUPS.radius,\n    property: 'radiusRange',\n    allowCustomValue: true\n  },\n  opacity: {\n    type: 'number',\n    defaultValue: DEFAULT_LAYER_OPACITY,\n    label: 'layerVisConfigs.opacity',\n    isRanged: false,\n    range: [0, 1],\n    step: 0.01,\n    group: PROPERTY_GROUPS.color,\n    property: 'opacity',\n    allowCustomValue: false\n  },\n  coverage: {\n    type: 'number',\n    defaultValue: 1,\n    label: 'layerVisConfigs.coverage',\n    isRanged: false,\n    range: [0, 1],\n    step: 0.01,\n    group: PROPERTY_GROUPS.cell,\n    property: 'coverage',\n    allowCustomValue: false\n  },\n  // used in point layer\n  outline: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layer.outline',\n    group: PROPERTY_GROUPS.display,\n    property: 'outline'\n  },\n  colorRange: {\n    type: 'color-range-select',\n    defaultValue: DEFAULT_COLOR_RANGE,\n    label: 'layerVisConfigs.colorRange',\n    group: PROPERTY_GROUPS.color,\n    property: 'colorRange'\n  },\n  strokeColorRange: {\n    type: 'color-range-select',\n    defaultValue: DEFAULT_COLOR_RANGE,\n    label: 'layerVisConfigs.strokeColorRange',\n    group: PROPERTY_GROUPS.color,\n    property: 'strokeColorRange'\n  },\n  targetColor: {\n    type: 'color-select',\n    label: 'layerVisConfigs.targetColor',\n    defaultValue: null,\n    group: PROPERTY_GROUPS.color,\n    property: 'targetColor'\n  },\n  strokeColor: {\n    type: 'color-select',\n    label: 'layerVisConfigs.strokeColor',\n    defaultValue: null,\n    group: PROPERTY_GROUPS.color,\n    property: 'strokeColor'\n  },\n  colorAggregation: {\n    type: 'select',\n    defaultValue: AGGREGATION_TYPES.average,\n    label: 'layerVisConfigs.colorAggregation',\n    // aggregation options are based on color field types\n    options: Object.keys(AGGREGATION_TYPES) as AggregationTypes[],\n    group: PROPERTY_GROUPS.color,\n    property: 'colorAggregation',\n    condition: config => Boolean(config.colorField)\n  },\n  sizeAggregation: {\n    type: 'select',\n    defaultValue: AGGREGATION_TYPES.average,\n    label: 'layerVisConfigs.heightAggregation',\n    // aggregation options are based on color field types\n    options: Object.keys(AGGREGATION_TYPES) as AggregationTypes[],\n    group: PROPERTY_GROUPS.height,\n    property: 'sizeAggregation',\n    condition: config => Boolean(config.sizeField)\n  },\n  percentile: {\n    type: 'number',\n    defaultValue: [0, 100],\n    label: config =>\n      `Filter by ${\n        config.colorField\n          ? `${config.visConfig.colorAggregation} ${config.colorField.name}`\n          : 'count'\n      } percentile`,\n    isRanged: true,\n    range: [0, 100],\n    step: 0.01,\n    group: PROPERTY_GROUPS.color,\n    property: 'percentile',\n\n    // percentile filter only makes sense with linear aggregation\n    condition: config => config.colorScale !== 'ordinal',\n    allowCustomValue: false\n  },\n  elevationPercentile: {\n    type: 'number',\n    defaultValue: [0, 100],\n    label: config =>\n      `Filter by ${\n        config.sizeField ? `${config.visConfig.sizeAggregation} ${config.sizeField.name}` : 'count'\n      } percentile`,\n    isRanged: true,\n    range: [0, 100],\n    step: 0.01,\n    group: PROPERTY_GROUPS.height,\n    property: 'elevationPercentile',\n    // percentile filter only makes sense with linear aggregation\n    condition: config =>\n      Boolean(config.visConfig.enable3d && (config.colorField || config.sizeField)),\n    allowCustomValue: false\n  },\n  resolution: {\n    type: 'number',\n    defaultValue: 8,\n    label: 'layerVisConfigs.resolution',\n    isRanged: false,\n    range: [0, 13],\n    step: 1,\n    group: PROPERTY_GROUPS.cell,\n    property: 'resolution',\n    allowCustomValue: true\n  },\n  sizeScale: {\n    type: 'number',\n    defaultValue: 10,\n    label: 'layerVisConfigs.sizeScale',\n    isRanged: false,\n    range: [1, 1000],\n    step: 1,\n    group: PROPERTY_GROUPS.stroke,\n    property: 'sizeScale',\n    allowCustomValue: true\n  },\n  angle: {\n    type: 'number',\n    label: 'layerVisConfigs.angle',\n    defaultValue: 0,\n    isRanged: false,\n    range: [0, 360],\n    group: PROPERTY_GROUPS.angle,\n    step: 1,\n    property: 'angle',\n    allowCustomValue: true\n  },\n  worldUnitSize: {\n    type: 'number',\n    defaultValue: 1,\n    label: 'layerVisConfigs.worldUnitSize',\n    isRanged: false,\n    range: [0, 500],\n    step: 0.0001,\n    group: PROPERTY_GROUPS.cell,\n    property: 'worldUnitSize',\n    allowCustomValue: true\n  },\n  elevationScale: {\n    type: 'number',\n    defaultValue: 5,\n    label: 'layerVisConfigs.elevationScale',\n    isRanged: false,\n    range: [0, 1000],\n    step: 0.1,\n    group: PROPERTY_GROUPS.height,\n    property: 'elevationScale',\n    allowCustomValue: true\n  },\n  enableElevationZoomFactor: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'layerVisConfigs.enableElevationZoomFactor',\n    group: PROPERTY_GROUPS.height,\n    property: 'enableElevationZoomFactor',\n    description: 'layerVisConfigs.enableElevationZoomFactorDescription'\n  },\n  elevationRange: {\n    type: 'number',\n    defaultValue: [0, 500],\n    label: 'layerVisConfigs.heightScale',\n    isRanged: true,\n    range: [0, 1000],\n    step: 0.01,\n    group: PROPERTY_GROUPS.height,\n    property: 'sizeRange',\n    allowCustomValue: true\n  },\n  heightRange: {\n    type: 'number',\n    defaultValue: [0, 500],\n    label: 'Height Scale',\n    isRanged: true,\n    range: [0, 1000],\n    step: 0.01,\n    group: PROPERTY_GROUPS.height,\n    property: 'heightRange',\n    allowCustomValue: true\n  },\n  coverageRange: {\n    type: 'number',\n    defaultValue: [0, 1],\n    label: 'layerVisConfigs.coverageRange',\n    isRanged: true,\n    range: [0, 1],\n    step: 0.01,\n    group: PROPERTY_GROUPS.radius,\n    property: 'coverageRange',\n    allowCustomValue: false\n  },\n  // hi precision is deprecated by deck.gl\n  'hi-precision': {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layerVisConfigs.highPrecisionRendering',\n    group: PROPERTY_GROUPS.precision,\n    property: 'hi-precision',\n    description: 'layerVisConfigs.highPrecisionRenderingDescription'\n  },\n  enable3d: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layerVisConfigs.height',\n    group: PROPERTY_GROUPS.height,\n    property: 'enable3d',\n    description: 'layerVisConfigs.heightDescription'\n  },\n  stroked: {\n    type: 'boolean',\n    label: 'layerVisConfigs.stroke',\n    defaultValue: true,\n    group: PROPERTY_GROUPS.display,\n    property: 'stroked'\n  },\n  filled: {\n    type: 'boolean',\n    label: 'layerVisConfigs.fill',\n    defaultValue: false,\n    group: PROPERTY_GROUPS.display,\n    property: 'filled'\n  },\n  extruded: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layerVisConfigs.enablePolygonHeight',\n    group: PROPERTY_GROUPS.display,\n    property: 'extruded'\n  },\n  wireframe: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layerVisConfigs.showWireframe',\n    group: PROPERTY_GROUPS.display,\n    property: 'wireframe'\n  },\n  // used for heatmap\n  weight: {\n    type: 'number',\n    defaultValue: 1,\n    label: 'layerVisConfigs.weightIntensity',\n    isRanged: false,\n    range: [0.01, 500],\n    step: 0.01,\n    group: PROPERTY_GROUPS.cell,\n    property: 'weight',\n    condition: config => Boolean(config.weightField),\n    allowCustomValue: true\n  },\n  heatmapRadius: {\n    type: 'number',\n    defaultValue: 20,\n    label: 'layerVisConfigs.radius',\n    isRanged: false,\n    range: [0, 100],\n    step: 0.1,\n    group: PROPERTY_GROUPS.cell,\n    property: 'radius',\n    allowCustomValue: true\n  },\n  darkBaseMapEnabled: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'layerVisConfigs.darkModeEnabled',\n    property: 'darkBaseMapEnabled',\n    group: PROPERTY_GROUPS.display\n  },\n  allowHover: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'layerVisConfigs.allowHover',\n    group: PROPERTY_GROUPS.interaction,\n    property: 'allowHover'\n  },\n  showNeighborOnHover: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'layerVisConfigs.showNeighborOnHover',\n    group: PROPERTY_GROUPS.interaction,\n    property: 'showNeighborOnHover'\n  },\n  showHighlightColor: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'layerVisConfigs.showHighlightColor',\n    group: PROPERTY_GROUPS.interaction,\n    property: 'showHighlightColor'\n  }\n};\n\nexport const LAYER_TEXT_CONFIGS: LayerTextConfig = {\n  fontSize: {\n    type: 'number',\n    range: [1, 100],\n    value0: 1,\n    step: 1,\n    isRanged: false,\n    label: 'Font size',\n    showInput: true\n  },\n  outlineWidth: {\n    type: 'number',\n    range: [0, 1],\n    value0: 0,\n    step: 0.01,\n    isRanged: false,\n    label: 'Outline width',\n    showInput: true\n  },\n  textAnchor: {\n    type: 'select',\n    options: ['start', 'middle', 'end'],\n    multiSelect: false,\n    searchable: false\n  },\n  textAlignment: {\n    type: 'select',\n    options: ['top', 'center', 'bottom'],\n    multiSelect: false,\n    searchable: false\n  }\n};\nexport const LAYER_TYPES = keyMirror({\n  point: null,\n  arc: null,\n  line: null,\n  grid: null,\n  hexagon: null,\n  geojson: null,\n  cluster: null,\n  icon: null,\n  heatmap: null,\n  hexagonId: null,\n  '3D': null,\n  trip: null,\n  s2: null,\n  vectorTile: null,\n  rasterTile: null,\n  wms: null\n});\n\nexport const EDITOR_AVAILABLE_LAYERS: string[] = [\n  LAYER_TYPES.point,\n  LAYER_TYPES.hexagon,\n  LAYER_TYPES.arc,\n  LAYER_TYPES.line,\n  LAYER_TYPES.hexagonId,\n  LAYER_TYPES.geojson\n];\n"
  },
  {
    "path": "src/constants/src/plot.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\n\nexport const PLOT_TYPES = keyMirror({\n  histogram: null,\n  lineChart: null\n});\n"
  },
  {
    "path": "src/constants/src/time.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\nimport {\n  utcDay,\n  utcHour,\n  utcMillisecond,\n  utcMinute,\n  utcMonth,\n  utcSecond,\n  utcWeek,\n  utcYear\n} from 'd3-time';\n\nimport {ValueOf} from '@kepler.gl/types';\n\nimport {AGGREGATION_TYPES, PLOT_TYPES} from './';\n\nexport interface IntervalOption {\n  label: string;\n  id: string;\n}\n\nexport interface TimeAggregation {\n  label: string;\n  id: string;\n}\n\nexport interface TimeIntervals {\n  year: any;\n  month: any;\n  week: any;\n  day: any;\n  hour: any;\n  minute: any;\n  second: any;\n  millisecond: any;\n}\n\nexport interface Durations {\n  years: number;\n  months: number;\n  weeks: number;\n  days: number;\n  hours: number;\n  minutes: number;\n  seconds: number;\n  milliseconds: number;\n}\n\nexport type TimeInterval = string; // TODO: TimeInterval should be an enum\n\nexport interface TickInterval {\n  interval: TimeInterval;\n  step: number;\n  duration: number;\n}\n\nexport interface TickIntervals {\n  [key: ValueOf<Interval>]: TickInterval;\n}\n\nexport interface Interval {\n  '1-second': string;\n  '5-second': string;\n  '15-second': string;\n  '30-second': string;\n  '1-minute': string;\n  '5-minute': string;\n  '15-minute': string;\n  '30-minute': string;\n  '1-hour': string;\n  '3-hour': string;\n  '6-hour': string;\n  '12-hour': string;\n  '1-day': string;\n  '2-day': string;\n  '1-week': string;\n  '1-month': string;\n  '3-month': string;\n  '1-year': string;\n}\n\nexport interface AnimationType {\n  continuous: string;\n  interval: string;\n}\n\nexport type AnimationWindow = any;\n\nexport const durationMillisecond = 1;\nexport const durationSecond = 1000;\nexport const durationMinute = durationSecond * 60;\nexport const durationHour = durationMinute * 60;\nexport const durationDay = durationHour * 24;\nexport const durationWeek = durationDay * 7;\nexport const durationMonth = durationDay * 30;\nexport const durationYear = durationDay * 365;\n\n// moment.duration functions\nexport const DURATIONS: Durations = {\n  years: durationYear,\n  months: durationMonth,\n  weeks: durationWeek,\n  days: durationDay,\n  hours: durationHour,\n  minutes: durationMinute,\n  seconds: durationSecond,\n  milliseconds: durationMillisecond\n};\n\n// interval key to d3 time interval function\nexport const TIME_INTERVALS: TimeIntervals = {\n  year: utcYear,\n  month: utcMonth,\n  week: utcWeek,\n  day: utcDay,\n  hour: utcHour,\n  minute: utcMinute,\n  second: utcSecond,\n  millisecond: utcMillisecond\n};\n\nexport const INTERVAL: Interval = keyMirror({\n  '1-second': null,\n  '5-second': null,\n  '15-second': null,\n  '30-second': null,\n  '1-minute': null,\n  '5-minute': null,\n  '15-minute': null,\n  '30-minute': null,\n  '1-hour': null,\n  '3-hour': null,\n  '6-hour': null,\n  '12-hour': null,\n  '1-day': null,\n  '2-day': null,\n  '1-week': null,\n  '1-month': null,\n  '3-month': null,\n  '1-year': null\n});\n\n// interval eligible for calculate week over week\nexport const WOW: {\n  [key: ValueOf<Interval>]: number;\n} = {\n  [INTERVAL['1-day']]: 7,\n  [INTERVAL['1-week']]: 1\n};\n\nexport const INTERVAL_OPTIONS: IntervalOption[] = Object.keys(INTERVAL).map(id => {\n  const [step, interval] = id.split('-');\n  // capitalizeFirstLetter(interval)\n  return {id, label: `${step} ${interval}`};\n});\n\nexport const TICK_INTERVALS: TickIntervals = {\n  [INTERVAL['1-millisecond']]: {interval: 'millisecond', step: 1, duration: durationMillisecond},\n  [INTERVAL['1-second']]: {interval: 'second', step: 1, duration: durationSecond},\n  [INTERVAL['5-second']]: {interval: 'second', step: 5, duration: 5 * durationSecond},\n  [INTERVAL['15-second']]: {interval: 'second', step: 15, duration: 15 * durationSecond},\n  [INTERVAL['30-second']]: {interval: 'second', step: 30, duration: 30 * durationSecond},\n  [INTERVAL['1-minute']]: {interval: 'minute', step: 1, duration: durationMinute},\n  [INTERVAL['5-minute']]: {interval: 'minute', step: 5, duration: 5 * durationMinute},\n  [INTERVAL['15-minute']]: {interval: 'minute', step: 15, duration: 15 * durationMinute},\n  [INTERVAL['30-minute']]: {interval: 'minute', step: 30, duration: 30 * durationMinute},\n  [INTERVAL['1-hour']]: {interval: 'hour', step: 1, duration: durationHour},\n  [INTERVAL['3-hour']]: {interval: 'hour', step: 3, duration: 3 * durationHour},\n  [INTERVAL['6-hour']]: {interval: 'hour', step: 6, duration: 6 * durationHour},\n  [INTERVAL['12-hour']]: {interval: 'hour', step: 12, duration: 12 * durationHour},\n  [INTERVAL['1-day']]: {interval: 'day', step: 1, duration: durationDay},\n  [INTERVAL['2-day']]: {interval: 'day', step: 2, duration: 2 * durationDay},\n  [INTERVAL['1-week']]: {interval: 'week', step: 1, duration: durationWeek},\n  [INTERVAL['1-month']]: {interval: 'month', step: 1, duration: durationMonth},\n  [INTERVAL['3-month']]: {interval: 'month', step: 3, duration: 3 * durationMonth},\n  [INTERVAL['1-year']]: {interval: 'year', step: 1, duration: durationYear}\n};\n\nexport const PLOT_TYPE_OPTIONS = {\n  [PLOT_TYPES.lineChart]: {\n    id: PLOT_TYPES.lineChart,\n    label: 'Chart',\n    icon: 'LineChart'\n  },\n  [PLOT_TYPES.histogram]: {\n    id: PLOT_TYPES.histogram,\n    label: 'Histogram',\n    icon: 'Histogram'\n  }\n};\n\nexport const BINS_LARGE = 100;\nexport const BINS = 30;\n\nexport const TIME_AGGREGATION: TimeAggregation[] = [\n  {\n    id: AGGREGATION_TYPES.average,\n    label: 'Average'\n  },\n  {\n    id: AGGREGATION_TYPES.sum,\n    label: 'Sum'\n  },\n  {\n    id: AGGREGATION_TYPES.maximum,\n    label: 'Maximum'\n  },\n  {\n    id: AGGREGATION_TYPES.minimum,\n    label: 'Minimum'\n  },\n  {\n    id: AGGREGATION_TYPES.median,\n    label: 'Median'\n  },\n  {\n    id: AGGREGATION_TYPES.stdev,\n    label: 'Std Deviation'\n  }\n];\n\nexport const ANIMATION_TYPE: AnimationType = keyMirror({\n  interval: null,\n  continuous: null\n});\n"
  },
  {
    "path": "src/constants/src/tooltip.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ValueOf} from '@kepler.gl/types';\n\nexport const TOOLTIP_FORMAT_TYPES = {\n  NONE: 'none',\n  DATE: 'date',\n  DATE_TIME: 'date_time',\n  DECIMAL: 'decimal',\n  PERCENTAGE: 'percentage',\n  BOOLEAN: 'boolean'\n};\n\nexport const TOOLTIP_KEY = 'format';\n\nexport type TooltipFormat = {\n  id: string;\n  label: string;\n  format: null | string;\n  type: ValueOf<typeof TOOLTIP_FORMAT_TYPES>;\n};\n\nexport const TOOLTIP_FORMATS = {\n  NONE: {\n    id: 'NONE',\n    label: 'None',\n    format: null,\n    type: TOOLTIP_FORMAT_TYPES.NONE\n  },\n  DECIMAL_SHORT: {\n    id: 'DECIMAL_SHORT',\n    label: '12345 → 10k',\n    format: '.1s',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_COMMA: {\n    id: 'DECIMAL_COMMA',\n    label: '12345 → 12,345',\n    format: ',',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_SHORT_COMMA: {\n    id: 'DECIMAL_SHORT_COMMA',\n    label: '12345 → 12.3k',\n    format: '.3~s',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_PERCENT_FULL_0: {\n    id: 'DECIMAL_PERCENT_FULL_0',\n    label: '.01 → 1%',\n    format: '.0%',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_PERCENT_FULL_1: {\n    id: 'DECIMAL_PERCENT_FULL_1',\n    label: '.01 → 1.0%',\n    format: '.1%',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_PERCENT_FULL_2: {\n    id: 'DECIMAL_PERCENT_FULL_2',\n    label: '.01 → 1.00%',\n    format: '.2%',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_PRECENT_REGULAR: {\n    id: 'DECIMAL_PRECENT_REGULAR',\n    label: '12.345 → 12.35%',\n    format: '~%',\n    type: TOOLTIP_FORMAT_TYPES.PERCENTAGE\n  },\n  DECIMAL_DECIMAL_FIXED_2: {\n    id: 'DECIMAL_DECIMAL_FIXED_2',\n    label: '1.2345 → 1.23',\n    format: '.2~f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_DECIMAL_FIXED_3: {\n    id: 'DECIMAL_DECIMAL_FIXED_3',\n    label: '1.2345 → 1.234',\n    format: '.3~f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_DECIMAL_FIXED_4: {\n    id: 'DECIMAL_DECIMAL_FIXED_4',\n    label: '1.23456 → 1.2346',\n    format: '.4~f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_SCIENTIFIC_FIXED_2: {\n    id: 'DECIMAL_SCIENTIFIC_FIXED_2',\n    label: '0.12345 → 1.23e-1',\n    format: '.2~e',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_SCIENTIFIC_FIXED_3: {\n    id: 'DECIMAL_SCIENTIFIC_FIXED_3',\n    label: '0.12345 → 1.235e-1',\n    format: '.3~e',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_SCIENTIFIC_FIXED_4: {\n    id: 'DECIMAL_SCIENTIFIC_FIXED_4',\n    label: '0.123456 → 1.2346e-1',\n    format: '.4~e',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_INT: {\n    id: 'DECIMAL_INT',\n    label: '12345 → 12350',\n    format: '.4~r',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_THREE: {\n    id: 'DECIMAL_THREE',\n    label: '12345.4321 → 12,345.432',\n    format: ',.3~f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_FOUR: {\n    id: 'DECIMAL_FOUR',\n    label: '12345.54321 → 12,345.5432',\n    format: ',.4~f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_DELTA: {\n    id: 'DECIMAL_DELTA',\n    label: '12345.4321 → +12,345.432',\n    format: '+,.3f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DECIMAL_CURRENCY: {\n    id: 'DECIMAL_CURRENCY',\n    label: '12345.4321 → $12,345.43',\n    format: '$,.2f',\n    type: TOOLTIP_FORMAT_TYPES.DECIMAL\n  },\n  DATE_L: {\n    // 05/29/2020\n    id: 'DATE_L',\n    label: '',\n    format: 'L',\n    type: TOOLTIP_FORMAT_TYPES.DATE\n  },\n  DATE_LL: {\n    // September 5 2016\n    id: 'DATE_LL',\n    label: '',\n    format: 'LL',\n    type: TOOLTIP_FORMAT_TYPES.DATE\n  },\n  DATE_dddd_LL: {\n    // Monday September 5, 2016\n    id: 'DATE_dddd_LL',\n    label: '',\n    format: 'dddd LL',\n    type: TOOLTIP_FORMAT_TYPES.DATE\n  },\n  DATE_ddd_LL: {\n    // Mon September 5, 2016\n    id: 'DATE_ddd_LL',\n    label: '',\n    format: 'ddd LL',\n    type: TOOLTIP_FORMAT_TYPES.DATE\n  },\n  DATE_TIME_L_LT: {\n    // 09/05/2016 12:00 AM\n    id: 'DATE_TIME_L_LT',\n    label: '',\n    format: 'L LT',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  DATE_TIME_L_LTS: {\n    // 09/05/2016 12:00:00 AM\n    id: 'DATE_TIME_L_LTS',\n    label: '',\n    format: 'L LTS',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  DATE_TIME_LLL: {\n    // September 5, 2016 12:00 AM\n    id: 'DATE_TIME_LLL',\n    label: '',\n    format: 'LLL',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  DATE_TIME_LL_LTS: {\n    // September 5, 2016 12:00:00 AM\n    id: 'DATE_TIME_LL_LTS',\n    label: '',\n    format: 'LL LTS',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  DATE_TIME_ddd_LLL: {\n    // Mon September 5, 2016 12:00 AM\n    id: 'DATE_TIME_ddd_LLL',\n    label: '',\n    format: 'ddd LLL',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  DATE_TIME_LTS: {\n    // 12:00:00 AM\n    id: 'DATE_TIME_LTS',\n    label: '',\n    format: 'LTS',\n    type: TOOLTIP_FORMAT_TYPES.DATE_TIME\n  },\n  BOOLEAN_NUM: {\n    id: 'BOOLEAN_NUM',\n    label: '0 | 1',\n    format: '01',\n    type: TOOLTIP_FORMAT_TYPES.BOOLEAN\n  },\n  BOOLEAN_Y_N: {\n    id: 'BOOLEAN_Y_N',\n    label: 'yes | no',\n    format: 'yn',\n    type: TOOLTIP_FORMAT_TYPES.BOOLEAN\n  }\n};\n\nexport const COMPARE_TYPES = {\n  ABSOLUTE: 'absolute',\n  RELATIVE: 'relative'\n};\n"
  },
  {
    "path": "src/constants/src/user-feedbacks.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const MISSING_MAPBOX_TOKEN =\n  'Mapbox Token not valid. ' +\n  '[Click here](https://github.com/keplergl/kepler.gl#mapboxapiaccesstoken-string-required)';\n\nexport const IMAGE_EXPORT_ERRORS = {\n  dataUri: `[kepler.gl] Failed to create image from data uri.\n  Copy the uri in the javascript console when reporting this bug.\n  The uri is the string starts with \"data:image/png\"`,\n  styleSheet: `[kepler.gl] Failed to fetch stylesheet when exporting image.\n    This probably will not affect the map. It might affect the legend.\n    The stylesheet failed to load is: `\n};\n"
  },
  {
    "path": "src/constants/src/user-guides.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const USER_GUIDE_DOC = 'https://docs.kepler.gl/docs/user-guides';\nexport const ACTIONS_DOC = 'https://docs.kepler.gl/docs/api-reference/actions/actions';\nexport const API_REFERENCE_DOC = 'https://docs.kepler.gl/docs/api-reference';\n\nexport const EXPORT_HTML_MAP_DOC = `${USER_GUIDE_DOC}/k-save-and-export`;\nexport const ADD_DATA_TO_MAP_DOC = `${ACTIONS_DOC}#adddatatomap`;\nexport const MAPBOX_ACCESS_TOKEN = 'https://docs.mapbox.com/help/how-mapbox-works/access-tokens/';\n\nexport const EXPORT_HTML_MAP_MODES_DOC = `${USER_GUIDE_DOC}/k-save-and-export#export-html-map`;\nexport const GUIDES_FILE_FORMAT_DOC = `${USER_GUIDE_DOC}/b-kepler-gl-workflow/a-add-data-to-the-map#supported-file-formats`;\nexport const BUG_REPORT_LINK =\n  'https://github.com/keplergl/kepler.gl/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBug%5D';\n"
  },
  {
    "path": "src/constants/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": []\n}\n"
  },
  {
    "path": "src/constants/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/deckgl-arrow-layers/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/deckgl-arrow-layers/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/deckgl-arrow-layers\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"Deck.gl layers with GeoArrow and GeoParquet support\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@geoarrow/geoarrow-js\": \"^0.3.0\",\n    \"@kepler.gl/constants\": \"^3.2.6\",\n    \"@kepler.gl/utils\": \"^3.2.6\",\n    \"@math.gl/core\": \"^4.0.0\",\n    \"@math.gl/polygon\": \"^4.0.0\",\n    \"@math.gl/types\": \"^4.0.0\",\n    \"apache-arrow\": \">=15\",\n    \"threads\": \"^1.7.0\"\n  },\n  \"peerDependencies\": {\n    \"@deck.gl/aggregation-layers\": \"^8.9.27\",\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/geo-layers\": \"^8.9.27\",\n    \"@deck.gl/layers\": \"^8.9.27\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/constants.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nexport const DEFAULT_COLOR: [number, number, number, number] = [0, 0, 0, 255];\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {GeoArrowScatterplotLayer} from './layers/geo-arrow-scatterplot-layer';\nexport {GeoArrowTextLayer} from './layers/geo-arrow-text-layer';\nexport {GeoArrowArcLayer} from './layers/geo-arrow-arc-layer';\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/layers/geo-arrow-arc-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {\n  CompositeLayer,\n  CompositeLayerProps,\n  DefaultProps,\n  GetPickingInfoParams,\n  Layer,\n  LayersList,\n  assert\n} from '@deck.gl/core/typed';\nimport {ArcLayer} from '@deck.gl/layers/typed';\nimport type {ArcLayerProps} from '@deck.gl/layers/typed';\nimport * as arrow from 'apache-arrow';\nimport * as ga from '@geoarrow/geoarrow-js';\nimport {assignAccessor, extractAccessorsFromProps} from '../utils/utils';\nimport {child} from '@geoarrow/geoarrow-js';\nimport {GeoArrowExtraPickingProps, computeChunkOffsets, getPickingInfo} from '../utils/picking';\nimport {ColorAccessor, FloatAccessor, GeoArrowPickingInfo, ExtensionProps} from '../types';\nimport {validateAccessors} from '../utils/validate';\n\n/** All properties supported by GeoArrowArcLayer */\nexport type GeoArrowArcLayerProps = Omit<\n  ArcLayerProps<any>,\n  | 'data'\n  | 'getSourcePosition'\n  | 'getTargetPosition'\n  | 'getSourceColor'\n  | 'getTargetColor'\n  | 'getWidth'\n  | 'getHeight'\n  | 'getTilt'\n> &\n  _GeoArrowArcLayerProps &\n  CompositeLayerProps;\n\n/** Properties added by GeoArrowArcLayer */\ntype _GeoArrowArcLayerProps = {\n  data: arrow.Table;\n\n  /**\n   * Method called to retrieve the source position of each object.\n   */\n  getSourcePosition: ga.vector.PointVector;\n\n  /**\n   * Method called to retrieve the target position of each object.\n   */\n  getTargetPosition: ga.vector.PointVector;\n\n  /**\n   * The rgba color is in the format of `[r, g, b, [a]]`.\n   * @default [0, 0, 0, 255]\n   */\n  getSourceColor?: ColorAccessor;\n\n  /**\n   * The rgba color is in the format of `[r, g, b, [a]]`.\n   * @default [0, 0, 0, 255]\n   */\n  getTargetColor?: ColorAccessor;\n\n  /**\n   * The line width of each object, in units specified by `widthUnits`.\n   * @default 1\n   */\n  getWidth?: FloatAccessor;\n\n  /**\n   * Multiplier of layer height. `0` will make the layer flat.\n   * @default 1\n   */\n  getHeight?: FloatAccessor;\n\n  /**\n   * Use to tilt the arc to the side if you have multiple arcs with the same source and target positions.\n   * @default 0\n   */\n  getTilt?: FloatAccessor;\n\n  /**\n   * If `true`, validate the arrays provided (e.g. chunk lengths)\n   * @default true\n   */\n  _validate?: boolean;\n};\n\n// Remove data from the upstream default props\nconst {\n  data: _data,\n  getSourcePosition: _getSourcePosition,\n  getTargetPosition: _getTargetPosition,\n  ..._defaultProps\n} = ArcLayer.defaultProps;\n\n// Default props added by us\nconst ourDefaultProps = {\n  _validate: true\n};\n\n// @ts-expect-error\nconst defaultProps: DefaultProps<GeoArrowArcLayerProps> = {\n  ..._defaultProps,\n  ...ourDefaultProps\n};\n\nexport class GeoArrowArcLayer<ExtraProps extends object = object> extends CompositeLayer<\n  GeoArrowArcLayerProps & ExtraProps\n> {\n  static defaultProps = defaultProps;\n  static layerName = 'GeoArrowArcLayer';\n\n  getPickingInfo(\n    params: GetPickingInfoParams & {\n      sourceLayer: {props: GeoArrowExtraPickingProps};\n    }\n  ): GeoArrowPickingInfo {\n    return getPickingInfo(params, this.props.data);\n  }\n\n  renderLayers(): Layer<object> | LayersList | null {\n    return this._renderLayersPoint();\n  }\n\n  _renderLayersPoint(): Layer<object> | LayersList | null {\n    const {\n      data: table,\n      getSourcePosition: sourcePosition,\n      getTargetPosition: targetPosition\n    } = this.props;\n\n    if (this.props._validate) {\n      validateAccessors(this.props, table);\n\n      // Note: below we iterate over table batches anyways, so this layer won't\n      // work as-is if data/table is null\n      assert(ga.vector.isPointVector(sourcePosition));\n      assert(ga.vector.isPointVector(targetPosition));\n    }\n\n    // Exclude manually-set accessors\n    const [accessors, otherProps] = extractAccessorsFromProps(this.props, [\n      'getSourcePosition',\n      'getTargetPosition'\n    ]);\n    const tableOffsets = computeChunkOffsets(table.data);\n\n    const layers: ArcLayer<any>[] = [];\n    for (let recordBatchIdx = 0; recordBatchIdx < table.batches.length; recordBatchIdx++) {\n      const sourceData = sourcePosition.data[recordBatchIdx];\n      const sourceValues = child.getPointChild(sourceData).values;\n      const targetData = targetPosition.data[recordBatchIdx];\n      const targetValues = child.getPointChild(targetData).values;\n\n      // @ts-expect-error how to properly retrieve batch offset?\n      const batchOffset = sourcePosition._offsets[recordBatchIdx];\n\n      const props: ArcLayerProps<any> & ExtensionProps = {\n        // Note: because this is a composite layer and not doing the rendering\n        // itself, we still have to pass in our defaultProps\n        ...ourDefaultProps,\n        ...otherProps,\n\n        // used for picking purposes\n        recordBatchIdx,\n        tableOffsets,\n\n        id: `${this.props.id}-geoarrow-arc-${recordBatchIdx}`,\n        data: {\n          // @ts-expect-error passed through to enable use by function accessors\n          data: table.batches[recordBatchIdx],\n          length: sourceData.length,\n          attributes: {\n            getSourcePosition: {\n              value: sourceValues,\n              size: sourceData.type.listSize\n            },\n            getTargetPosition: {\n              value: targetValues,\n              size: targetData.type.listSize\n            }\n          }\n        }\n      };\n\n      for (const [propName, propInput] of Object.entries(accessors)) {\n        assignAccessor({\n          props,\n          propName,\n          propInput,\n          chunkIdx: recordBatchIdx,\n          batchOffset\n        });\n      }\n\n      const SubLayerClass = this.getSubLayerClass('geo-arrow-arc-layer', ArcLayer);\n      const layer = new SubLayerClass({\n        ...this.getSubLayerProps(props),\n        // preserve binded accessors, as they are overwriten back by pass-through accessors from extensions\n        getFiltered: props.getFiltered,\n        getFilterValue: props.getFilterValue\n      });\n      layers.push(layer);\n    }\n\n    return layers;\n  }\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/layers/geo-arrow-scatterplot-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {\n  CompositeLayer,\n  CompositeLayerProps,\n  DefaultProps,\n  GetPickingInfoParams,\n  Layer,\n  LayersList,\n  assert\n} from '@deck.gl/core/typed';\nimport {ScatterplotLayer} from '@deck.gl/layers/typed';\nimport type {ScatterplotLayerProps} from '@deck.gl/layers/typed';\nimport * as arrow from 'apache-arrow';\nimport * as ga from '@geoarrow/geoarrow-js';\n\nimport {GEOARROW_EXTENSIONS} from '@kepler.gl/constants';\n\nimport {\n  assignAccessor,\n  extractAccessorsFromProps,\n  getGeometryVector,\n  invertOffsets\n} from '../utils/utils';\nimport {GeoArrowExtraPickingProps, computeChunkOffsets, getPickingInfo} from '../utils/picking';\nimport {ColorAccessor, FloatAccessor, GeoArrowPickingInfo, ExtensionProps} from '../types';\nimport {validateAccessors} from '../utils/validate';\n\n/** All properties supported by GeoArrowScatterplotLayer */\nexport type GeoArrowScatterplotLayerProps = Omit<\n  ScatterplotLayerProps<arrow.Table>,\n  'data' | 'getPosition' | 'getRadius' | 'getFillColor' | 'getLineColor'\n> &\n  _GeoArrowScatterplotLayerProps &\n  CompositeLayerProps;\n\n/** Properties added by GeoArrowScatterplotLayer */\ntype _GeoArrowScatterplotLayerProps = {\n  data: arrow.Table;\n\n  /**\n   * If `true`, validate the arrays provided (e.g. chunk lengths)\n   * @default true\n   */\n  _validate?: boolean;\n  /**\n   * Center position accessor.\n   * If not provided, will be inferred by finding a column with extension type\n   * `\"geoarrow.point\"` or `\"geoarrow.multipoint\"`.\n   */\n  getPosition?: ga.vector.PointVector | ga.vector.MultiPointVector;\n  /**\n   * Radius accessor.\n   * @default 1\n   */\n  getRadius?: FloatAccessor;\n  /**\n   * Fill color accessor.\n   * @default [0, 0, 0, 255]\n   */\n  getFillColor?: ColorAccessor;\n  /**\n   * Stroke color accessor.\n   * @default [0, 0, 0, 255]\n   */\n  getLineColor?: ColorAccessor;\n  /**\n   * Stroke width accessor.\n   * @default 1\n   */\n  getLineWidth?: FloatAccessor;\n};\n\n// Remove data and getPosition from the upstream default props\nconst {\n  data: _data,\n  getPosition: _getPosition,\n  ..._upstreamDefaultProps\n} = ScatterplotLayer.defaultProps;\n\n// Default props added by us\nconst ourDefaultProps = {\n  _validate: true\n};\n\n// @ts-expect-error\nconst defaultProps: DefaultProps<GeoArrowScatterplotLayerProps> = {\n  ..._upstreamDefaultProps,\n  ...ourDefaultProps\n};\n\nexport class GeoArrowScatterplotLayer<ExtraProps extends object = object> extends CompositeLayer<\n  GeoArrowScatterplotLayerProps & ExtraProps\n> {\n  static defaultProps = defaultProps;\n  static layerName = 'GeoArrowScatterplotLayer';\n\n  getPickingInfo(\n    params: GetPickingInfoParams & {\n      sourceLayer: {props: GeoArrowExtraPickingProps};\n    }\n  ): GeoArrowPickingInfo {\n    return getPickingInfo(params, this.props.data);\n  }\n\n  renderLayers(): Layer<object> | LayersList | null {\n    const {data: table} = this.props;\n\n    if (this.props.getPosition !== undefined) {\n      const geometryColumn = this.props.getPosition;\n      if (geometryColumn !== undefined && ga.vector.isPointVector(geometryColumn)) {\n        return this._renderLayersPoint(geometryColumn);\n      }\n\n      if (geometryColumn !== undefined && ga.vector.isMultiPointVector(geometryColumn)) {\n        return this._renderLayersMultiPoint(geometryColumn);\n      }\n\n      throw new Error('getPosition should pass in an arrow Vector of Point or MultiPoint type');\n    } else {\n      const pointVector = getGeometryVector(table, GEOARROW_EXTENSIONS.POINT);\n      if (pointVector !== null) {\n        return this._renderLayersPoint(pointVector);\n      }\n\n      const multiPointVector = getGeometryVector(table, GEOARROW_EXTENSIONS.MULTIPOINT);\n      if (multiPointVector !== null) {\n        return this._renderLayersMultiPoint(multiPointVector);\n      }\n    }\n\n    throw new Error('getPosition not GeoArrow point or multipoint');\n  }\n\n  _renderLayersPoint(geometryColumn: ga.vector.PointVector): Layer<object> | LayersList | null {\n    const {data: table} = this.props;\n\n    if (this.props._validate) {\n      assert(ga.vector.isPointVector(geometryColumn));\n      validateAccessors(this.props, table);\n    }\n\n    // Exclude manually-set accessors\n    const [accessors, otherProps] = extractAccessorsFromProps(this.props, ['getPosition']);\n    const tableOffsets = computeChunkOffsets(table.data);\n\n    const layers: ScatterplotLayer<any>[] = [];\n    for (let recordBatchIdx = 0; recordBatchIdx < table.batches.length; recordBatchIdx++) {\n      const geometryData = geometryColumn.data[recordBatchIdx];\n      const flatCoordsData = ga.child.getPointChild(geometryData);\n      const flatCoordinateArray = flatCoordsData.values;\n\n      // @ts-expect-error how to properly retrieve batch offset?\n      const batchOffset = geometryColumn._offsets[recordBatchIdx];\n\n      const props: ScatterplotLayerProps<any> & ExtensionProps = {\n        // Note: because this is a composite layer and not doing the rendering\n        // itself, we still have to pass in our defaultProps\n        ...ourDefaultProps,\n        ...otherProps,\n\n        // used for picking purposes\n        recordBatchIdx,\n        tableOffsets,\n\n        id: `${this.props.id}-geoarrow-scatterplot-${recordBatchIdx}`,\n        data: {\n          // @ts-expect-error\n          data: table.batches[recordBatchIdx],\n          length: geometryData.length,\n          attributes: {\n            getPosition: {\n              value: flatCoordinateArray,\n              size: geometryData.type.listSize\n            }\n          }\n        }\n      };\n\n      for (const [propName, propInput] of Object.entries(accessors)) {\n        assignAccessor({\n          props,\n          propName,\n          propInput,\n          chunkIdx: recordBatchIdx,\n          batchOffset\n        });\n      }\n\n      const layer = new ScatterplotLayer({\n        ...this.getSubLayerProps(props),\n        // preserve binded accessors, as they are overwriten back by pass-through accessors from extensions\n        getFiltered: props.getFiltered,\n        getFilterValue: props.getFilterValue\n      });\n      layers.push(layer);\n    }\n\n    return layers;\n  }\n\n  _renderLayersMultiPoint(\n    geometryColumn: ga.vector.MultiPointVector\n  ): Layer<object> | LayersList | null {\n    const {data: table} = this.props;\n\n    // TODO: validate that if nested, accessor props have the same nesting\n    // structure as the main geometry column.\n    if (this.props._validate) {\n      assert(ga.vector.isMultiPointVector(geometryColumn));\n      validateAccessors(this.props, table);\n    }\n\n    // Exclude manually-set accessors\n    const [accessors, otherProps] = extractAccessorsFromProps(this.props, ['getPosition']);\n    const tableOffsets = computeChunkOffsets(table.data);\n\n    const layers: ScatterplotLayer[] = [];\n    for (let recordBatchIdx = 0; recordBatchIdx < table.batches.length; recordBatchIdx++) {\n      const multiPointData = geometryColumn.data[recordBatchIdx];\n      const pointData = ga.child.getMultiPointChild(multiPointData);\n      const geomOffsets = multiPointData.valueOffsets;\n      const flatCoordsData = ga.child.getPointChild(pointData);\n      const flatCoordinateArray = flatCoordsData.values;\n\n      // @ts-expect-error how to properly retrieve batch offset?\n      const batchOffset = geometryColumn._offsets[recordBatchIdx];\n\n      const props: ScatterplotLayerProps & ExtensionProps = {\n        // Note: because this is a composite layer and not doing the rendering\n        // itself, we still have to pass in our defaultProps\n        ...ourDefaultProps,\n        ...otherProps,\n\n        // used for picking purposes\n        recordBatchIdx,\n        tableOffsets,\n\n        id: `${this.props.id}-geoarrow-scatterplot-${recordBatchIdx}`,\n        data: {\n          // @ts-expect-error\n          data: table.batches[recordBatchIdx],\n          // Map from expanded multi-geometry index to original index\n          // Used both in picking and for function callbacks\n          invertedGeomOffsets: invertOffsets(geomOffsets),\n          // Note: this needs to be the length one level down.\n          length: pointData.length,\n          attributes: {\n            getPosition: {\n              value: flatCoordinateArray,\n              size: pointData.type.listSize\n            }\n          }\n        }\n      };\n\n      for (const [propName, propInput] of Object.entries(accessors)) {\n        assignAccessor({\n          props,\n          propName,\n          propInput,\n          chunkIdx: recordBatchIdx,\n          geomCoordOffsets: geomOffsets,\n          batchOffset\n        });\n      }\n\n      const layer = new ScatterplotLayer({\n        ...this.getSubLayerProps(props),\n        // preserve binded accessors, as they are overwriten back by pass-through accessors from extensions\n        getFiltered: props.getFiltered,\n        getFilterValue: props.getFilterValue\n      });\n      layers.push(layer);\n    }\n\n    return layers;\n  }\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/layers/geo-arrow-text-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {\n  CompositeLayer,\n  CompositeLayerProps,\n  DefaultProps,\n  GetPickingInfoParams,\n  Layer,\n  LayersList,\n  assert\n} from '@deck.gl/core/typed';\nimport {TextLayer} from '@deck.gl/layers/typed';\nimport type {TextLayerProps} from '@deck.gl/layers';\nimport * as arrow from 'apache-arrow';\nimport * as ga from '@geoarrow/geoarrow-js';\n\nimport {GEOARROW_EXTENSIONS} from '@kepler.gl/constants';\n\nimport {\n  assignAccessor,\n  expandArrayToCoords,\n  extractAccessorsFromProps,\n  getGeometryVector\n} from '../utils/utils';\nimport {GeoArrowExtraPickingProps, computeChunkOffsets, getPickingInfo} from '../utils/picking';\nimport {ColorAccessor, FloatAccessor, GeoArrowPickingInfo, ExtensionProps} from '../types';\nimport {validateAccessors} from '../utils/validate';\n\n/** All properties supported by GeoArrowTextLayer */\nexport type GeoArrowTextLayerProps = Omit<\n  TextLayerProps<arrow.Table>,\n  // We remove background for now because there are special requirements for\n  // using binary attributes with background\n  // https://deck.gl/docs/api-reference/layers/text-layer#use-binary-attributes-with-background\n  | 'background'\n  | 'data'\n  | 'getBackgroundColor'\n  | 'getBorderColor'\n  | 'getBorderWidth'\n  | 'getText'\n  | 'getPosition'\n  | 'getColor'\n  | 'getSize'\n  | 'getAngle'\n  | 'getTextAnchor'\n  | 'getAlignmentBaseline'\n  | 'getPixelOffset'\n> &\n  _GeoArrowTextLayerProps &\n  CompositeLayerProps;\n\n/** Properties added by GeoArrowTextLayer */\ntype _GeoArrowTextLayerProps = {\n  data: arrow.Table;\n\n  /** Background color accessor.\n   * @default [255, 255, 255, 255]\n   */\n  getBackgroundColor?: ColorAccessor;\n  /** Border color accessor.\n   * @default [0, 0, 0, 255]\n   */\n  getBorderColor?: ColorAccessor;\n  /** Border width accessor.\n   * @default 0\n   */\n  getBorderWidth?: FloatAccessor;\n  /**\n   * Label text accessor\n   */\n  getText: arrow.Vector<arrow.Utf8>;\n  /**\n   * Anchor position accessor\n   */\n  getPosition?: ga.vector.PointVector;\n  /**\n   * Label color accessor\n   * @default [0, 0, 0, 255]\n   */\n  getColor?: ColorAccessor;\n  /**\n   * Label size accessor\n   * @default 32\n   */\n  getSize?: FloatAccessor;\n  /**\n   * Label rotation accessor, in degrees\n   * @default 0\n   */\n  getAngle?: FloatAccessor;\n  /**\n   * Horizontal alignment accessor\n   * @default 'middle'\n   */\n  getTextAnchor?: arrow.Vector<arrow.Utf8> | 'start' | 'middle' | 'end';\n  /**\n   * Vertical alignment accessor\n   * @default 'center'\n   */\n  getAlignmentBaseline?: arrow.Vector<arrow.Utf8> | 'top' | 'center' | 'bottom';\n  /**\n   * Label offset from the anchor position, [x, y] in pixels\n   * @default [0, 0]\n   */\n  getPixelOffset?: arrow.Vector<arrow.FixedSizeList<arrow.Int>> | [number, number];\n\n  /**\n   * If `true`, validate the arrays provided (e.g. chunk lengths)\n   * @default true\n   */\n  _validate?: boolean;\n};\n\n// Remove data and getPosition from the upstream default props\nconst {\n  data: _data,\n  getPosition: _getPosition,\n  getText: _getText,\n  getTextAnchor: _getTextAnchor,\n  getAlignmentBaseline: _getAlignmentBaseline,\n  getPixelOffset: _getPixelOffset,\n  ..._defaultProps\n} = TextLayer.defaultProps;\n\n// Default props added by us\nconst ourDefaultProps: Pick<\n  GeoArrowTextLayerProps,\n  'getTextAnchor' | 'getAlignmentBaseline' | 'getPixelOffset' | '_validate'\n> = {\n  getTextAnchor: 'middle',\n  getAlignmentBaseline: 'center',\n  getPixelOffset: [0, 0],\n  _validate: true\n};\n\n// @ts-expect-error Type 'Uint8Array' is not assignable to type 'RGBAColor'\nconst defaultProps: DefaultProps<GeoArrowTextLayerProps> = {\n  ..._defaultProps,\n  ...ourDefaultProps\n};\n\nexport class GeoArrowTextLayer<ExtraProps extends object = object> extends CompositeLayer<\n  GeoArrowTextLayerProps & ExtraProps\n> {\n  static defaultProps = defaultProps;\n  static layerName = 'GeoArrowTextLayer';\n\n  getPickingInfo(\n    params: GetPickingInfoParams & {\n      sourceLayer: {props: GeoArrowExtraPickingProps};\n    }\n  ): GeoArrowPickingInfo {\n    return getPickingInfo(params, this.props.data);\n  }\n\n  renderLayers(): Layer<object> | LayersList | null {\n    const {data: table} = this.props;\n\n    if (this.props.getPosition !== undefined) {\n      const geometryColumn = this.props.getPosition;\n      if (geometryColumn !== undefined && ga.vector.isPointVector(geometryColumn)) {\n        return this._renderLayersPoint(geometryColumn);\n      }\n\n      throw new Error('getPosition should pass in an arrow Vector of Point type');\n    } else {\n      const pointVector = getGeometryVector(table, GEOARROW_EXTENSIONS.POINT);\n      if (pointVector !== null) {\n        return this._renderLayersPoint(pointVector);\n      }\n    }\n\n    throw new Error('getPosition not GeoArrow point');\n  }\n\n  _renderLayersPoint(geometryColumn: ga.vector.PointVector): Layer<object> | LayersList | null {\n    const {data: table} = this.props;\n\n    if (this.props._validate) {\n      assert(ga.vector.isPointVector(geometryColumn));\n      validateAccessors(this.props, table);\n    }\n\n    // Exclude manually-set accessors\n    const [accessors, otherProps] = extractAccessorsFromProps(this.props, [\n      'getPosition',\n      'getText'\n    ]);\n    const tableOffsets = computeChunkOffsets(table.data);\n\n    const layers: TextLayer<any>[] = [];\n    for (let recordBatchIdx = 0; recordBatchIdx < table.batches.length; recordBatchIdx++) {\n      const geometryData = geometryColumn.data[recordBatchIdx];\n      const flatCoordsData = ga.child.getPointChild(geometryData);\n      const flatCoordinateArray = flatCoordsData.values;\n\n      const textData = this.props.getText.data[recordBatchIdx];\n      const numLabels = textData.length;\n      const textValues = textData.values;\n      const characterOffsets = textData.valueOffsets;\n\n      // @ts-expect-error how to properly retrieve batch offset?\n      const batchOffset = geometryColumn._offsets[recordBatchIdx];\n\n      const props: TextLayerProps<any> & ExtensionProps = {\n        // Note: because this is a composite layer and not doing the rendering\n        // itself, we still have to pass in our defaultProps\n        ...ourDefaultProps,\n        ...otherProps,\n\n        // used for picking purposes\n        // @ts-expect-error\n        recordBatchIdx,\n        tableOffsets,\n\n        id: `${this.props.id}-geoarrow-text-layer-${recordBatchIdx}`,\n        data: {\n          data: table.batches[recordBatchIdx],\n          length: geometryData.length,\n          startIndices: characterOffsets,\n          attributes: {\n            // Positions need to be expanded to be one per character!\n            getPosition: {\n              value: expandArrayToCoords(\n                flatCoordinateArray,\n                geometryData.type.listSize,\n                characterOffsets,\n                numLabels\n              ),\n              size: geometryData.type.listSize\n            },\n            // TODO: support non-ascii characters\n            getText: {\n              value: textValues\n              // size: 1,\n            }\n          }\n        },\n        // TODO privide more robust data comparators\n        dataComparator: (d1, d2) => {\n          return d1.data === d2.data;\n        },\n        _subLayerProps: {\n          characters: {\n            dataComparator: (d1, d2) => {\n              return d1.data === d2.data;\n            }\n          }\n        }\n      };\n\n      for (const [propName, propInput] of Object.entries(accessors)) {\n        assignAccessor({\n          props,\n          propName,\n          propInput,\n          chunkIdx: recordBatchIdx,\n          geomCoordOffsets: characterOffsets,\n          batchOffset\n        });\n      }\n\n      const layer = new TextLayer({\n        ...this.getSubLayerProps(props),\n        // preserve binded accessors, as they are overwriten back by pass-through accessors from extensions\n        getFiltered: props.getFiltered,\n        getFilterValue: props.getFilterValue\n      });\n      layers.push(layer);\n    }\n\n    return layers;\n  }\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport type {BinaryAttribute, Color, PickingInfo} from '@deck.gl/core/typed';\nimport {TypedArray} from '@math.gl/types';\nimport * as arrow from 'apache-arrow';\n\n/**\n * An individual layer's data\n */\nexport type GeoArrowLayerData<T> = {\n  data: T;\n  length: number;\n  attributes?: Record<string, TypedArray | Buffer | BinaryAttribute>;\n};\n\n/**\n * Internal type for layer data handling, used in `wrapAccessorFunction`.\n */\nexport type _GeoArrowInternalLayerData<T> = GeoArrowLayerData<T> & {\n  /**\n   * A lookup table from expanded multi-geometry index to original index.\n   *\n   * This is omitted from the user-facing type because in `wrapAccessorFunction`\n   * in `utils.ts` we apply the lookup from \"exploded\" row to \"original\" row.\n   */\n  invertedGeomOffsets?: Uint8Array | Uint16Array | Uint32Array;\n};\n\nexport type AccessorContext<T> = {\n  /** The current row index of the current iteration */\n  index: number;\n  /** The value of the `data` prop */\n  data: GeoArrowLayerData<T>;\n  /** A pre-allocated array. The accessor function can optionally fill data into this array and return it,\n   * instead of creating a new array for every object. In some browsers this improves performance significantly\n   * by reducing garbage collection. */\n  target: number[];\n};\n\n/**\n * Internal type for layer data handling, used in `wrapAccessorFunction`.\n */\nexport type _InternalAccessorContext<T> = AccessorContext<T> & {\n  /** The value of the `data` prop */\n  data: _GeoArrowInternalLayerData<T>;\n};\n\n/** Function that returns a value for each object. */\nexport type AccessorFunction<In, Out> = (\n  /** Contextual information of the current element. */\n  objectInfo: AccessorContext<In>\n) => Out;\n\n/** Either a uniform value for all objects, or a function that returns a value for each object. */\nexport type Accessor<In, Out> = Out | AccessorFunction<In, Out>;\n\nexport type GeoArrowPickingInfo = PickingInfo & {\n  object?: arrow.StructRowProxy;\n};\n\nexport type FloatAccessor = arrow.Vector<arrow.Float> | Accessor<arrow.RecordBatch, number>;\nexport type TimestampAccessor = arrow.Vector<arrow.List<arrow.Float>>;\nexport type ColorAccessor =\n  | arrow.Vector<arrow.FixedSizeList<arrow.Uint8>>\n  | Accessor<arrow.RecordBatch, Color | Color[]>;\nexport type NormalAccessor =\n  | arrow.Vector<arrow.FixedSizeList<arrow.Float32>>\n  | Accessor<arrow.Table, arrow.Vector<arrow.FixedSizeList<arrow.Float32>>>;\n\nexport type ExtensionProps = {\n  getFiltered?: ({index}: {index: number}) => number;\n  getFilterValue?: (d: any, objectInfo?: {index: number}) => (number | number[])[];\n};\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/utils/picking.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport * as arrow from 'apache-arrow';\nimport {GetPickingInfoParams} from '@deck.gl/core/typed';\nimport {GeoArrowPickingInfo} from '../types';\n\nexport interface GeoArrowExtraPickingProps {\n  recordBatchIdx: number;\n  tableOffsets: Uint32Array;\n  data: {\n    invertedGeomOffsets?: Uint8Array | Uint16Array | Uint32Array;\n  };\n}\n\nexport function getPickingInfo(\n  {\n    info,\n    sourceLayer\n  }: GetPickingInfoParams & {\n    sourceLayer: {props: GeoArrowExtraPickingProps};\n  },\n  table: arrow.Table\n): GeoArrowPickingInfo {\n  // Geometry index as rendered\n  let index = info.index;\n\n  // if a Multi- geometry dataset, map from the rendered index back to the\n  // feature index\n  if (sourceLayer.props.data.invertedGeomOffsets) {\n    index = sourceLayer.props.data.invertedGeomOffsets[index];\n  }\n\n  const recordBatchIdx = sourceLayer.props.recordBatchIdx;\n  const tableOffsets = sourceLayer.props.tableOffsets;\n\n  const batch = table.batches[recordBatchIdx];\n  const row = batch.get(index);\n  if (row === null) {\n    return info;\n  }\n\n  const currentBatchOffset = tableOffsets[recordBatchIdx];\n\n  // Update index to be _global_ index, not within the specific record batch\n  index += currentBatchOffset;\n  return {\n    ...info,\n    index,\n    object: row\n  };\n}\n\n// This is vendored from Arrow JS because it's a private API\nexport function computeChunkOffsets<T extends arrow.DataType>(\n  chunks: ReadonlyArray<arrow.Data<T>>\n) {\n  return chunks.reduce((offsets, chunk, index) => {\n    offsets[index + 1] = offsets[index] + chunk.length;\n    return offsets;\n  }, new Uint32Array(chunks.length + 1));\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/utils/utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {assert} from '@deck.gl/core/typed';\nimport * as arrow from 'apache-arrow';\nimport * as ga from '@geoarrow/geoarrow-js';\nimport {AccessorContext, AccessorFunction, _InternalAccessorContext} from '../types';\nimport {isArrowFixedSizeList, isArrowStruct, isArrowVector} from '@kepler.gl/utils';\n\nexport type TypedArray =\n  | Uint8Array\n  | Uint8ClampedArray\n  | Uint16Array\n  | Uint32Array\n  | Int8Array\n  | Int16Array\n  | Int32Array\n  | Float32Array\n  | Float64Array;\n\nexport function findGeometryColumnIndex(\n  schema: arrow.Schema,\n  extensionName: string,\n  geometryColumnName?: string | null\n): number | null {\n  const index = schema.fields.findIndex(\n    field =>\n      field.name === geometryColumnName ||\n      field.metadata.get('ARROW:extension:name') === extensionName\n  );\n  return index !== -1 ? index : null;\n}\n\n/**\n * Returns `true` if the input is a reference to a column in the table\n */\nexport function isColumnReference(input: any): input is string {\n  return typeof input === 'string';\n}\n\nfunction isDataInterleavedCoords(\n  data: arrow.Data\n): data is arrow.Data<arrow.FixedSizeList<arrow.Float64>> {\n  // TODO: also check 2 or 3d? Float64?\n  return isArrowFixedSizeList(data.type);\n}\n\nfunction isDataSeparatedCoords(\n  data: arrow.Data\n): data is arrow.Data<arrow.Struct<{x: arrow.Float64; y: arrow.Float64}>> {\n  // TODO: also check child names? Float64?\n  return isArrowStruct(data.type);\n}\n\n/**\n * Convert geoarrow Struct coordinates to FixedSizeList coords\n *\n * The GeoArrow spec allows for either separated or interleaved coords, but at\n * this time deck.gl only supports interleaved.\n */\n// TODO: this hasn't been tested yet\nexport function convertStructToFixedSizeList(\n  coords:\n    | arrow.Data<arrow.FixedSizeList<arrow.Float64>>\n    | arrow.Data<arrow.Struct<{x: arrow.Float64; y: arrow.Float64}>>\n): arrow.Data<arrow.FixedSizeList<arrow.Float64>> {\n  if (isDataInterleavedCoords(coords)) {\n    return coords;\n  } else if (isDataSeparatedCoords(coords)) {\n    // TODO: support 3d\n    const interleavedCoords = new Float64Array(coords.length * 2);\n    const [xChild, yChild] = coords.children;\n    for (let i = 0; i < coords.length; i++) {\n      interleavedCoords[i * 2] = xChild.values[i];\n      interleavedCoords[i * 2 + 1] = yChild.values[i];\n    }\n\n    const childDataType = new arrow.Float64();\n    const dataType = new arrow.FixedSizeList(2, new arrow.Field('coords', childDataType));\n\n    const interleavedCoordsData = arrow.makeData({\n      type: childDataType,\n      length: interleavedCoords.length\n    });\n\n    const data = arrow.makeData({\n      type: dataType,\n      length: coords.length,\n      nullCount: coords.nullCount,\n      nullBitmap: coords.nullBitmap,\n      child: interleavedCoordsData\n    });\n    return data;\n  }\n\n  assert(false);\n}\n\ntype AssignAccessorProps = {\n  /** The object on which to assign the resolved accesor */\n  props: Record<string, any>;\n  /** The name of the prop to set */\n  propName: string;\n  /** The user-supplied input to the layer. Must either be a scalar value or a reference to a column in the table. */\n  propInput: any;\n  /** Numeric index in the table */\n  chunkIdx: number;\n  /** a map from the geometry index to the coord offsets for that geometry. */\n  geomCoordOffsets?: Int32Array | null;\n  /** Absolute offset of the batch in the table/vector. Added to the sampling index. */\n  batchOffset?: number;\n};\n\n/**\n * A wrapper around a user-provided accessor function\n *\n * For layers like Scatterplot, Path, and Polygon, we automatically handle\n * \"exploding\" the table when multi-geometry input are provided. This means that\n * the upstream `index` value passed to the user will be the correct row index\n * _only_ for non-exploded data.\n *\n * With this function, we simplify the user usage by automatically converting\n * back from \"exploded\" index back to the original row index.\n */\nfunction wrapAccessorFunction<In, Out>(\n  objectInfo: _InternalAccessorContext<In>,\n  userAccessorFunction: AccessorFunction<In, Out>,\n  batchOffset: number\n): Out {\n  const {index, data} = objectInfo;\n  let newIndex = index + batchOffset;\n  if (data.invertedGeomOffsets !== undefined) {\n    newIndex = data.invertedGeomOffsets[index];\n  }\n  const newObjectData = {\n    data: data.data,\n    length: data.length,\n    attributes: data.attributes\n  };\n  const newObjectInfo = {\n    index: newIndex,\n    data: newObjectData,\n    target: objectInfo.target\n  };\n  return userAccessorFunction(newObjectInfo);\n}\n\n/**\n * Resolve accessor and assign to props object\n *\n * This is useful as a helper function because a scalar prop is set at the top\n * level while a vectorized prop is set inside data.attributes\n *\n */\nexport function assignAccessor(args: AssignAccessorProps) {\n  const {props, propName, propInput, chunkIdx, geomCoordOffsets, batchOffset = 0} = args;\n\n  if (propInput === undefined) {\n    return;\n  }\n\n  if (isArrowVector(propInput)) {\n    const columnData = propInput.data[chunkIdx];\n\n    if (arrow.DataType.isFixedSizeList(columnData)) {\n      assert(columnData.children.length === 1);\n      let values = columnData.children[0].values;\n\n      if (geomCoordOffsets) {\n        values = expandArrayToCoords(values, columnData.type.listSize, geomCoordOffsets);\n      }\n\n      props.data.attributes[propName] = {\n        value: values,\n        size: columnData.type.listSize,\n        // Set to `true` to signify that colors are already 0-255, and deck/luma\n        // does not need to rescale\n        // https://github.com/visgl/deck.gl/blob/401d624c0529faaa62125714c376b3ba3b8f379f/docs/api-reference/core/attribute-manager.md?plain=1#L66\n        normalized: true\n      };\n    } else if (arrow.DataType.isFloat(columnData)) {\n      let values = columnData.values;\n\n      if (geomCoordOffsets) {\n        values = expandArrayToCoords(values, 1, geomCoordOffsets);\n      }\n\n      props.data.attributes[propName] = {\n        value: values,\n        size: 1\n      };\n    }\n  } else if (typeof propInput === 'function') {\n    props[propName] = <In>(object: any, objectInfo: AccessorContext<In>) => {\n      // Special case that doesn't have the same parameters\n      if (propName === 'getPolygonOffset') {\n        return propInput(object, objectInfo);\n      }\n\n      return wrapAccessorFunction(objectInfo, propInput, batchOffset);\n    };\n  } else {\n    props[propName] = propInput;\n  }\n}\n\n/**\n * Expand an array from \"one element per geometry\" to \"one element per coordinate\"\n *\n * @param input: the input array to expand\n * @param size : the number of nested elements in the input array per geometry. So for example, for RGB data this would be 3, for RGBA this would be 4. For radius, this would be 1.\n * @param geomOffsets : an offsets array mapping from the geometry to the coordinate indexes. So in the case of a LineStringArray, this is retrieved directly from the GeoArrow storage. In the case of a PolygonArray, this comes from the resolved indexes that need to be given to the SolidPolygonLayer anyways.\n * @param numPositions : end position in geomOffsets, as geomOffsets can potentially contain preallocated zeroes in the end of the buffer.\n *\n * @return  {TypedArray} values expanded to be per-coordinate\n */\nexport function expandArrayToCoords<T extends TypedArray>(\n  input: T,\n  size: number,\n  geomOffsets: Int32Array,\n  numPositions?: number\n): T {\n  const lastIndex = numPositions || geomOffsets.length - 1;\n  const numCoords = geomOffsets[lastIndex];\n  // @ts-expect-error\n  const outputArray: T = new input.constructor(numCoords * size);\n\n  // geomIdx is an index into the geomOffsets array\n  // geomIdx is also the geometry/table index\n  for (let geomIdx = 0; geomIdx < lastIndex; geomIdx++) {\n    // geomOffsets maps from the geometry index to the coord index\n    // So here we get the range of coords that this geometry covers\n    const lastCoordIdx = geomOffsets[geomIdx];\n    const nextCoordIdx = geomOffsets[geomIdx + 1];\n\n    // Iterate over this range of coord indices\n    for (let coordIdx = lastCoordIdx; coordIdx < nextCoordIdx; coordIdx++) {\n      // Iterate over size\n      for (let i = 0; i < size; i++) {\n        // Copy from the geometry index in `input` to the coord index in\n        // `output`\n        outputArray[coordIdx * size + i] = input[geomIdx * size + i];\n      }\n    }\n  }\n\n  return outputArray;\n}\n\n/**\n * Get a geometry vector with the specified extension type name from the table.\n */\nexport function getGeometryVector(\n  table: arrow.Table,\n  geoarrowTypeName: string\n): arrow.Vector | null {\n  const geometryColumnIdx = findGeometryColumnIndex(table.schema, geoarrowTypeName);\n\n  if (geometryColumnIdx === null) {\n    return null;\n    // throw new Error(`No column found with extension type ${geoarrowTypeName}`);\n  }\n\n  return table.getChildAt(geometryColumnIdx);\n}\n\nexport function getListNestingLevels(data: arrow.Data): number {\n  let nestingLevels = 0;\n  if (arrow.DataType.isList(data.type)) {\n    nestingLevels += 1;\n    data = data.children[0];\n  }\n  return nestingLevels;\n}\n\nexport function getMultiLineStringResolvedOffsets(data: ga.data.MultiLineStringData): Int32Array {\n  const geomOffsets = data.valueOffsets;\n  const lineStringData = ga.child.getMultiLineStringChild(data);\n  const ringOffsets = lineStringData.valueOffsets;\n\n  const resolvedRingOffsets = new Int32Array(geomOffsets.length);\n  for (let i = 0; i < resolvedRingOffsets.length; ++i) {\n    // Perform the lookup into the ringIndices array using the geomOffsets\n    // array\n    resolvedRingOffsets[i] = ringOffsets[geomOffsets[i]];\n  }\n\n  return resolvedRingOffsets;\n}\n\nexport function getPolygonResolvedOffsets(data: ga.data.PolygonData): Int32Array {\n  const geomOffsets = data.valueOffsets;\n  const ringData = ga.child.getPolygonChild(data);\n  const ringOffsets = ringData.valueOffsets;\n\n  const resolvedRingOffsets = new Int32Array(geomOffsets.length);\n  for (let i = 0; i < resolvedRingOffsets.length; ++i) {\n    // Perform the lookup into the ringIndices array using the geomOffsets\n    // array\n    resolvedRingOffsets[i] = ringOffsets[geomOffsets[i]];\n  }\n\n  return resolvedRingOffsets;\n}\n\nexport function getMultiPolygonResolvedOffsets(data: ga.data.MultiPolygonData): Int32Array {\n  const polygonData = ga.child.getMultiPolygonChild(data);\n  const ringData = ga.child.getPolygonChild(polygonData);\n\n  const geomOffsets = data.valueOffsets;\n  const polygonOffsets = polygonData.valueOffsets;\n  const ringOffsets = ringData.valueOffsets;\n\n  const resolvedRingOffsets = new Int32Array(geomOffsets.length);\n  for (let i = 0; i < resolvedRingOffsets.length; ++i) {\n    resolvedRingOffsets[i] = ringOffsets[polygonOffsets[geomOffsets[i]]];\n  }\n\n  return resolvedRingOffsets;\n}\n\n/**\n * Invert offsets so that lookup can go in the opposite direction\n */\nexport function invertOffsets(offsets: Int32Array): Uint8Array | Uint16Array | Uint32Array {\n  const largestOffset = offsets[offsets.length - 1];\n\n  const arrayConstructor =\n    offsets.length < Math.pow(2, 8)\n      ? Uint8Array\n      : offsets.length < Math.pow(2, 16)\n      ? Uint16Array\n      : Uint32Array;\n\n  const invertedOffsets = new arrayConstructor(largestOffset);\n  for (let arrayIdx = 0; arrayIdx < offsets.length - 1; arrayIdx++) {\n    const thisOffset = offsets[arrayIdx];\n    const nextOffset = offsets[arrayIdx + 1];\n    for (let offset = thisOffset; offset < nextOffset; offset++) {\n      invertedOffsets[offset] = arrayIdx;\n    }\n  }\n\n  return invertedOffsets;\n}\n\n// TODO: better typing\nexport function extractAccessorsFromProps(\n  props: Record<string, any>,\n  excludeKeys: string[]\n): [Record<string, any>, Record<string, any>] {\n  const accessors: Record<string, any> = {};\n  const otherProps: Record<string, any> = {};\n  for (const [key, value] of Object.entries(props)) {\n    if (excludeKeys.includes(key)) {\n      continue;\n    }\n\n    if (key.startsWith('get')) {\n      accessors[key] = value;\n    } else {\n      otherProps[key] = value;\n    }\n  }\n\n  return [accessors, otherProps];\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/src/utils/validate.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// deck.gl-community\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {assert} from '@deck.gl/core/typed';\nimport {isArrowVector} from '@kepler.gl/utils';\nimport * as arrow from 'apache-arrow';\n\nexport function validateAccessors(props: Record<string, any>, table: arrow.Table): void {\n  const vectorAccessors: arrow.Vector[] = [];\n  const colorVectorAccessors: arrow.Vector[] = [];\n  for (const [accessorName, accessorValue] of Object.entries(props)) {\n    // Is it an accessor\n    if (accessorName.startsWith('get')) {\n      // Is it a vector accessor\n      if (isArrowVector(accessorValue)) {\n        vectorAccessors.push(accessorValue);\n\n        // Is it a color vector accessor\n        if (accessorName.endsWith('Color')) {\n          colorVectorAccessors.push(accessorValue);\n        }\n      }\n    }\n  }\n\n  validateVectorAccessors(table, vectorAccessors);\n  for (const colorVectorAccessor of colorVectorAccessors) {\n    validateColorVector(colorVectorAccessor);\n  }\n}\n\n/**\n * Provide validation for accessors provided\n *\n * - Assert that all vectors have the same number of chunks as the main table\n * - Assert that all chunks in each vector have the same number of rows as the\n *   relevant batch in the main table.\n *\n */\nexport function validateVectorAccessors(table: arrow.Table, vectorAccessors: arrow.Vector[]) {\n  // Check the same number of chunks as the table's batches\n  for (const vectorAccessor of vectorAccessors) {\n    assert(table.batches.length === vectorAccessor.data.length);\n  }\n\n  // Check that each table batch/vector data has the same number of rows\n  for (const vectorAccessor of vectorAccessors) {\n    for (let i = 0; i < table.batches.length; i++) {\n      assert(table.batches[i].numRows === vectorAccessor.data[i].length);\n    }\n  }\n}\n\nexport function validateColorVector(vector: arrow.Vector) {\n  // Assert the color vector is a FixedSizeList\n  assert(arrow.DataType.isFixedSizeList(vector.type));\n\n  // Assert it has 3 or 4 values\n  assert(vector.type.listSize === 3 || vector.type.listSize === 4);\n\n  // Assert the child type is an integer\n  assert(arrow.DataType.isInt(vector.type.children[0]));\n\n  // Assert the child type is a Uint8\n  // Property 'type' does not exist on type 'Int_<Ints>'. Did you mean 'TType'?\n  assert(vector.type.children[0].type.bitWidth === 8);\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\" //TODO change once all dependencies are isolated\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/deckgl-arrow-layers/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/deckgl-layers/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/deckgl-layers/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/deckgl-layers\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@danmarshall/deckgl-typings\": \"4.9.22\",\n    \"@deck.gl/aggregation-layers\": \"^8.9.27\",\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/geo-layers\": \"^8.9.27\",\n    \"@deck.gl/layers\": \"^8.9.27\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/wms\": \"4.3.2\",\n    \"@luma.gl/constants\": \"^8.5.20\",\n    \"@luma.gl/core\": \"^8.5.20\",\n    \"@mapbox/geo-viewport\": \"^0.4.1\",\n    \"@mapbox/vector-tile\": \"^1.3.1\",\n    \"@math.gl/web-mercator\": \"^3.6.2\",\n    \"@types/d3-array\": \"^2.8.0\",\n    \"@types/geojson\": \"^7946.0.8\",\n    \"@types/lodash\": \"4.17.5\",\n    \"@types/supercluster\": \"^7.1.0\",\n    \"d3-array\": \"^2.8.0\",\n    \"global\": \"^4.3.0\",\n    \"lodash\": \"4.17.21\",\n    \"pbf\": \"^3.1.0\",\n    \"supercluster\": \"^7.1.0\",\n    \"viewport-mercator-project\": \"^6.0.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/3d-building-layer/3d-building-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport GL from '@luma.gl/constants';\nimport {CompositeLayer} from '@deck.gl/core/typed';\nimport {TileLayer as DeckGLTileLayer} from '@deck.gl/geo-layers/typed';\nimport {SolidPolygonLayer, SolidPolygonLayerProps} from '@deck.gl/layers/typed';\n\nimport {getTileData} from './3d-building-utils';\nimport {ThreeDBuildingLayerProps, TileDataItem, TileLoadProps} from './types';\n\nexport default class ThreeDBuildingLayer extends CompositeLayer<ThreeDBuildingLayerProps> {\n  // this layer add its subLayers to the redux store, and push sample data\n\n  renderSubLayers(props: SolidPolygonLayerProps<any>) {\n    return new SolidPolygonLayer<TileDataItem>({\n      ...props,\n      parameters: {\n        blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA],\n        blendEquation: [GL.FUNC_ADD, GL.FUNC_ADD]\n      },\n      extruded: true,\n      opacity: 1,\n      filled: true,\n      getElevation: (feature: TileDataItem) => feature.properties.height || 0,\n      getPolygon: (feature: TileDataItem) => feature.coordinates,\n      getFillColor: this.props.threeDBuildingColor\n    });\n  }\n\n  renderLayers() {\n    return [\n      new DeckGLTileLayer({\n        id: `${this.id}-deck-3d-building` as string,\n        getTileData: (tile: TileLoadProps) =>\n          getTileData(this.props.mapboxApiUrl, this.props.mapboxApiAccessToken, tile),\n        minZoom: 13,\n        renderSubLayers: this.renderSubLayers.bind(this),\n        updateTriggers: this.props.updateTriggers\n      })\n    ];\n  }\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/3d-building-layer/3d-building-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Protobuf from 'pbf';\nimport {VectorTile} from '@mapbox/vector-tile';\nimport {worldToLngLat} from 'viewport-mercator-project';\nimport {\n  Coordinates,\n  FlatFigure,\n  TileDataItem,\n  VectorTileFeature,\n  VectorTileFeatureProperties\n} from './types';\n\n/* global fetch */\nconst TILE_SIZE = 512;\nconst MAPBOX_HOST = 'https://a.tiles.mapbox.com';\nconst MAP_SOURCE = '/v4/mapbox.mapbox-streets-v7';\n\nexport function getTileData(\n  host: string,\n  token: string,\n  {index: {x, y, z}}: {index: Coordinates}\n): Promise<TileDataItem[]> {\n  const mapSource = `${\n    host || MAPBOX_HOST\n  }${MAP_SOURCE}/${z}/${x}/${y}.vector.pbf?access_token=${token}`;\n\n  return fetch(mapSource)\n    .then(response => response.arrayBuffer())\n    .then(buffer => decodeTile(x, y, z, buffer));\n}\n\nexport function decodeTile(\n  x: number,\n  y: number,\n  z: number,\n  arrayBuffer: ArrayBuffer\n): TileDataItem[] {\n  const tile = new VectorTile(new Protobuf(arrayBuffer));\n\n  const result: TileDataItem[] = [];\n  const xProj = x * TILE_SIZE;\n  const yProj = y * TILE_SIZE;\n  const scale = Math.pow(2, z);\n\n  const projectFunc = project.bind(null, xProj, yProj, scale);\n\n  /* eslint-disable guard-for-in */\n  const layerName = 'building';\n  const vectorTileLayer = tile.layers[layerName];\n  if (!vectorTileLayer) {\n    return [];\n  }\n  for (let i = 0; i < vectorTileLayer.length; i++) {\n    const vectorTileFeature = vectorTileLayer.feature(i);\n    // @ts-ignore\n    const features = vectorTileFeatureToProp(vectorTileFeature, projectFunc);\n    features.forEach(f => {\n      f.properties.layer = layerName;\n      if (f.properties.height) {\n        result.push(f);\n      }\n    });\n  }\n  return result;\n}\n\nfunction project(x: number, y: number, scale: number, line: FlatFigure, extent: number): void {\n  const sizeToPixel = extent / TILE_SIZE;\n\n  for (let ii = 0; ii < line.length; ii++) {\n    const p = line[ii];\n    // LNGLAT\n    line[ii] = worldToLngLat([x + p[0] / sizeToPixel, y + p[1] / sizeToPixel], scale);\n  }\n}\n\n/* adapted from @mapbox/vector-tile/lib/vectortilefeature.js for better perf */\n/* eslint-disable */\nexport function vectorTileFeatureToProp(\n  vectorTileFeature: VectorTileFeature,\n  project: (r: FlatFigure, n: number) => void\n): {coordinates: FlatFigure[]; properties: VectorTileFeatureProperties}[] {\n  let coords: FlatFigure[][] | FlatFigure[] = getCoordinates(vectorTileFeature);\n  const extent = vectorTileFeature.extent;\n  let i: number;\n  let j: number;\n\n  coords = classifyRings(coords);\n  for (i = 0; i < coords.length; i++) {\n    for (j = 0; j < coords[i].length; j++) {\n      project(coords[i][j], extent);\n    }\n  }\n\n  return coords.map(coordinates => ({\n    coordinates,\n    properties: vectorTileFeature.properties\n  }));\n}\n\nfunction getCoordinates(vectorTileFeature: VectorTileFeature): FlatFigure[] {\n  const pbf = vectorTileFeature._pbf;\n  pbf.pos = vectorTileFeature._geometry;\n\n  const end = pbf.readVarint() + pbf.pos;\n  let cmd = 1;\n  let length = 0;\n  let x = 0;\n  let y = 0;\n\n  const lines: FlatFigure[] = [];\n  let line: FlatFigure | undefined;\n\n  while (pbf.pos < end) {\n    if (length <= 0) {\n      const cmdLen = pbf.readVarint();\n      cmd = cmdLen & 0x7;\n      length = cmdLen >> 3;\n    }\n\n    length--;\n\n    if (cmd === 1 || cmd === 2) {\n      x += pbf.readSVarint();\n      y += pbf.readSVarint();\n\n      if (cmd === 1) {\n        // moveTo\n        if (line) lines.push(line);\n        line = [];\n      }\n\n      if (line) line.push([x, y]);\n    } else if (cmd === 7) {\n      // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90\n      if (line) {\n        line.push(line[0].slice() as [number, number] | [number, number, number]); // closePolygon\n      }\n    } else {\n      throw new Error(`unknown command ${cmd}`);\n    }\n  }\n\n  if (line) lines.push(line);\n\n  return lines;\n}\n\n// classifies an array of rings into polygons with outer rings and holes\n\nfunction classifyRings(rings: FlatFigure[]): FlatFigure[][] {\n  const len = rings.length;\n\n  if (len <= 1) return [rings];\n\n  const polygons: FlatFigure[][] = [];\n  let polygon: FlatFigure[] | undefined;\n  let ccw: boolean | undefined;\n\n  for (let i = 0; i < len; i++) {\n    const area = signedArea(rings[i]);\n    if (area === 0) {\n      continue;\n    }\n\n    if (ccw === undefined) {\n      ccw = area < 0;\n    }\n\n    if (ccw === area < 0) {\n      if (polygon) {\n        polygons.push(polygon);\n      }\n      polygon = [rings[i]];\n    } else if (polygon) {\n      polygon.push(rings[i]);\n    }\n  }\n  if (polygon) {\n    polygons.push(polygon);\n  }\n\n  return polygons;\n}\n\nfunction signedArea(ring: FlatFigure): number {\n  let sum = 0;\n  for (let i = 0, len = ring.length, j = len - 1, p1: number[], p2: number[]; i < len; j = i++) {\n    p1 = ring[i];\n    p2 = ring[j];\n    sum += (p2[0] - p1[0]) * (p1[1] + p2[1]);\n  }\n  return sum;\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/3d-building-layer/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {RGBColor} from '@kepler.gl/types';\n\nexport type TileIndex = {x: number; y: number; z: number};\n\nexport type TileLoadProps = {\n  id: string;\n  index: TileIndex;\n  bbox: any;\n  url?: string | null;\n  signal?: AbortSignal;\n  userData?: Record<string, any>;\n  zoom?: number;\n};\n\nexport type ThreeDBuildingLayerProps = {\n  id: string;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl: string;\n  threeDBuildingColor: RGBColor;\n  updateTriggers: {\n    getFillColor: RGBColor;\n  };\n};\nexport type Coordinates = {x: number; y: number; z: number};\n// TODO rename\nexport type FlatFigure = ([number, number] | [number, number, number])[];\nexport type TileDataItem = {coordinates: FlatFigure[]; properties: VectorTileFeatureProperties};\nexport type VectorTileFeatureProperties = {layer: string; height?: number};\nexport type VectorTileFeature = {\n  extent: number;\n  properties: VectorTileFeatureProperties;\n  _pbf: {\n    buf: ArrayBuffer;\n    pos: number;\n    type: number;\n    length: number;\n    readVarint: (b?: boolean) => number;\n    readSVarint: () => number;\n  };\n  _geometry: number;\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/cluster-layer/cluster-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ScatterplotLayer} from '@deck.gl/layers';\nimport {_AggregationLayer as AggregationLayer} from '@deck.gl/aggregation-layers';\n\nimport geoViewport from '@mapbox/geo-viewport';\nimport CPUAggregator, {\n  AggregationType,\n  defaultColorDimension,\n  DimensionType,\n  getDimensionScale\n} from '../layer-utils/cpu-aggregator';\nimport {getDistanceScales} from 'viewport-mercator-project';\nimport {max} from 'd3-array';\n\nimport {SCALE_TYPES, DEFAULT_COLOR_RANGE, LAYER_VIS_CONFIGS} from '@kepler.gl/constants';\nimport ClusterBuilder, {getGeoJSON} from '../layer-utils/cluster-utils';\nimport {RGBAColor} from '@kepler.gl/types';\nimport {AggregationLayerProps} from '@deck.gl/aggregation-layers/aggregation-layer';\n\nconst defaultRadius = LAYER_VIS_CONFIGS.clusterRadius.defaultValue;\nconst defaultRadiusRange = LAYER_VIS_CONFIGS.clusterRadiusRange.defaultValue;\n\nconst defaultGetColorValue = points => points.length;\nconst defaultGetRadiusValue = cell =>\n  cell.filteredPoints ? cell.filteredPoints.length : cell.points.length;\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction processGeoJSON(this: CPUAggregator, step, props, aggregation, {viewport}) {\n  const {data, getPosition, filterData} = props;\n  const geoJSON = getGeoJSON(data, getPosition, filterData);\n  const clusterBuilder = new ClusterBuilder();\n\n  this.setState({geoJSON, clusterBuilder});\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction getClusters(this: CPUAggregator, step, props, aggregation, {viewport}) {\n  const {geoJSON, clusterBuilder} = this.state;\n  const {clusterRadius, zoom, width, height} = props;\n  const {longitude, latitude} = viewport;\n\n  // zoom needs to be an integer for the different map utils. Also helps with cache key.\n  const bbox = geoViewport.bounds([longitude, latitude], zoom, [width, height]);\n  const clusters = clusterBuilder.clustersAtZoom({bbox, clusterRadius, geoJSON, zoom});\n\n  this.setState({\n    layerData: {data: clusters}\n  });\n}\n\nfunction getSubLayerRadius(dimensionState, dimension, layerProps) {\n  return cell => {\n    const {getRadiusValue} = layerProps;\n    const {scaleFunc} = dimensionState;\n    return scaleFunc(getRadiusValue(cell));\n  };\n}\n\nexport const clusterAggregation: AggregationType = {\n  key: 'position',\n  updateSteps: [\n    {\n      key: 'geojson',\n      triggers: {\n        position: {\n          prop: 'getPosition',\n          updateTrigger: 'getPosition'\n        },\n        filterData: {\n          prop: 'filterData',\n          updateTrigger: 'filterData'\n        }\n      },\n      updater: processGeoJSON\n    },\n    {\n      key: 'clustering',\n      triggers: {\n        clusterRadius: {\n          prop: 'clusterRadius'\n        },\n        zoom: {\n          prop: 'zoom'\n        },\n        width: {\n          prop: 'width'\n        },\n        height: {\n          prop: 'height'\n        }\n      },\n      updater: getClusters\n    }\n  ]\n};\n\nfunction getRadiusValueDomain(this: CPUAggregator, step, props, dimensionUpdater) {\n  const {key} = dimensionUpdater;\n  const {getRadiusValue} = props;\n  const {layerData} = this.state;\n\n  const valueDomain = [0, max(layerData.data, getRadiusValue)];\n  this._setDimensionState(key, {valueDomain});\n}\n\nconst clusterLayerDimensions: [DimensionType<RGBAColor>, DimensionType<number>] = [\n  defaultColorDimension,\n  {\n    key: 'radius',\n    accessor: 'getRadius',\n    nullValue: 0,\n    updateSteps: [\n      {\n        key: 'getDomain',\n        triggers: {\n          value: {\n            prop: 'getRadiusValue',\n            updateTrigger: 'getRadiusValue'\n          }\n        },\n        updater: getRadiusValueDomain\n      },\n      {\n        key: 'getScaleFunc',\n        triggers: {\n          domain: {prop: 'radiusDomain'},\n          range: {prop: 'radiusRange'},\n          scaleType: {prop: 'radiusScaleType'}\n        },\n        updater: getDimensionScale\n      }\n    ],\n    getSubLayerAccessor: getSubLayerRadius,\n    getPickingInfo: (dimensionState, cell, layerProps) => {\n      const radiusValue = layerProps.getRadiusValue(cell);\n      const {scaleFunc} = dimensionState;\n      const scaledRadiusValue = scaleFunc ? scaleFunc(radiusValue) : radiusValue;\n\n      return {radiusValue, scaledRadiusValue};\n    }\n  }\n];\n\nconst defaultProps = {\n  clusterRadius: defaultRadius,\n  colorDomain: null,\n  colorRange: DEFAULT_COLOR_RANGE,\n  colorScaleType: SCALE_TYPES.quantize,\n  radiusScaleType: SCALE_TYPES.sqrt,\n  radiusRange: defaultRadiusRange,\n  getPosition: {type: 'accessor', value: x => x.position},\n  getColorValue: {type: 'accessor', value: defaultGetColorValue},\n  getRadiusValue: {type: 'accessor', value: defaultGetRadiusValue}\n};\n\nexport default class ClusterLayer extends AggregationLayer<\n  any,\n  AggregationLayerProps<any> & {radiusScale: number}\n> {\n  initializeState() {\n    const cpuAggregator = new CPUAggregator({\n      aggregation: clusterAggregation,\n      dimensions: clusterLayerDimensions\n    });\n\n    this.state = {\n      cpuAggregator,\n      aggregatorState: cpuAggregator.state\n    };\n    const attributeManager = this.getAttributeManager();\n    attributeManager.add({\n      positions: {size: 3, accessor: 'getPosition'}\n    });\n  }\n\n  updateState({oldProps, props, changeFlags}) {\n    this.setState({\n      // make a copy of the internal state of cpuAggregator for testing\n      aggregatorState: this.state.cpuAggregator.updateState(\n        {oldProps, props, changeFlags},\n        {\n          viewport: this.context.viewport,\n          attributes: this.getAttributes(),\n          numInstances: this.getNumInstances(props)\n        }\n      )\n    });\n  }\n\n  getPickingInfo({info}) {\n    const obj = this.state.cpuAggregator.getPickingInfo({info}, this.props);\n    if (obj?.object) {\n      // @ts-expect-error\n      const distanceScale = getDistanceScales(this.context.viewport);\n      const metersPerPixel = distanceScale.metersPerPixel[0];\n      obj.object.scaledRadiusValue = obj.object.scaledRadiusValue * metersPerPixel;\n    }\n\n    return obj;\n  }\n\n  _getSublayerUpdateTriggers() {\n    return this.state.cpuAggregator.getUpdateTriggers(this.props);\n  }\n\n  _getSubLayerAccessors() {\n    return {\n      getRadius: this.state.cpuAggregator.getAccessor('radius', this.props),\n      getFillColor: this.state.cpuAggregator.getAccessor('fillColor', this.props)\n    };\n  }\n\n  renderLayers() {\n    // for subclassing, override this method to return\n    // customized sub layer props\n    const {id, radiusScale} = this.props;\n    const {cpuAggregator} = this.state;\n\n    // base layer props\n    const {visible, opacity, pickable, autoHighlight, highlightColor} = this.props;\n    const updateTriggers = this._getSublayerUpdateTriggers();\n    const accessors = this._getSubLayerAccessors();\n\n    // @ts-expect-error\n    const distanceScale = getDistanceScales(this.context.viewport);\n    const metersPerPixel = distanceScale.metersPerPixel[0];\n\n    // return props to the sublayer constructor\n    return new ScatterplotLayer({\n      id: `${id}-cluster`,\n      data: cpuAggregator.state.layerData.data,\n      radiusScale: metersPerPixel * radiusScale,\n      visible,\n      opacity,\n      pickable,\n      autoHighlight,\n      highlightColor,\n      updateTriggers,\n      parameters: {\n        depthMask: false\n      },\n      ...accessors\n    });\n  }\n}\n\nClusterLayer.layerName = 'ClusterLayer';\nClusterLayer.defaultProps = defaultProps;\n"
  },
  {
    "path": "src/deckgl-layers/src/column-layer/enhanced-column-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {UNIT} from '@deck.gl/core';\nimport {ColumnLayer, ColumnLayerProps} from '@deck.gl/layers/typed';\nimport GL from '@luma.gl/constants';\n\nimport {editShader} from '../';\n\nfunction addInstanceCoverage(vs) {\n  const addDecl = editShader(\n    vs,\n    'hexagon cell vs add instance 1',\n    'in vec3 instancePickingColors;',\n    `in vec3 instancePickingColors;\n     in float instanceCoverage;`\n  );\n\n  return editShader(\n    addDecl,\n    'hexagon cell vs add instance 2',\n    'float dotRadius = radius * coverage * shouldRender;',\n    'float dotRadius = radius * coverage * instanceCoverage * shouldRender;'\n  );\n}\n\ntype EnhancedColumnLayerProps = ColumnLayerProps<any> & {\n  strokeOpacity: any;\n};\n\n// TODO: export all deck.gl layers from kepler.gl\nclass EnhancedColumnLayer extends ColumnLayer<any, EnhancedColumnLayerProps> {\n  getShaders() {\n    const shaders = super.getShaders();\n\n    return {\n      ...shaders,\n      vs: addInstanceCoverage(shaders.vs)\n    };\n  }\n\n  initializeState() {\n    super.initializeState();\n\n    this.getAttributeManager()?.addInstanced({\n      instanceCoverage: {size: 1, accessor: 'getCoverage'}\n    });\n  }\n\n  draw({uniforms}) {\n    const {\n      lineWidthUnits,\n      lineWidthScale,\n      lineWidthMinPixels,\n      lineWidthMaxPixels,\n      radiusUnits,\n      elevationScale,\n      extruded,\n      filled,\n      stroked,\n      strokeOpacity,\n      wireframe,\n      offset,\n      coverage,\n      radius,\n      angle\n    } = this.props;\n    const {model, fillVertexCount, wireframeVertexCount, edgeDistance} = this.state;\n\n    model.setUniforms(uniforms).setUniforms({\n      radius,\n      angle: (angle / 180) * Math.PI,\n      offset,\n      extruded,\n      stroked,\n      coverage,\n      elevationScale,\n      edgeDistance,\n      radiusUnits: UNIT[radiusUnits],\n      widthUnits: UNIT[lineWidthUnits],\n      widthScale: lineWidthScale,\n      widthMinPixels: lineWidthMinPixels,\n      widthMaxPixels: lineWidthMaxPixels\n    });\n\n    // When drawing 3d: draw wireframe first so it doesn't get occluded by depth test\n    if (extruded && wireframe) {\n      model.setProps({isIndexed: true});\n      model\n        .setVertexCount(wireframeVertexCount)\n        .setDrawMode(GL.LINES)\n        .setUniforms({isStroke: true})\n        .draw();\n    }\n    if (filled) {\n      model.setProps({isIndexed: false});\n      model\n        .setVertexCount(fillVertexCount)\n        .setDrawMode(GL.TRIANGLE_STRIP)\n        .setUniforms({isStroke: false})\n        .draw();\n    }\n    // When drawing 2d: draw fill before stroke so that the outline is always on top\n    if (!extruded && stroked) {\n      model.setProps({isIndexed: false});\n      // The width of the stroke is achieved by flattening the side of the cylinder.\n      // Skip the last 1/3 of the vertices which is the top.\n      model\n        .setVertexCount((fillVertexCount * 2) / 3)\n        .setDrawMode(GL.TRIANGLE_STRIP)\n        .setUniforms({isStroke: true, opacity: strokeOpacity})\n        .draw();\n    }\n  }\n}\n\nEnhancedColumnLayer.layerName = 'EnhancedColumnLayer';\n\nexport default EnhancedColumnLayer;\n"
  },
  {
    "path": "src/deckgl-layers/src/deckgl-extensions/filter-arrow-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Layer, LayerExtension} from '@deck.gl/core';\nimport GL from '@luma.gl/constants';\n\nimport shaderModule from './filter-shader-module';\n\nconst VALUE_FILTERED = 1;\n\nconst defaultProps = {\n  getFiltered: {type: 'accessor', value: VALUE_FILTERED}\n};\n\nexport type FilterArrowExtensionProps = {\n  getFiltered?: () => number;\n};\n\n/**\n * FilterArrowExtension - a deck.gl extension to filter arrow layer\n *\n * A simple extension to filter arrow layer based on the result of CPU filteredIndex,\n * so we can avoid filtering on the raw Arrow table and recreating geometry attributes.\n * Specifically, an attribute `filtered` is added to the layer to indicate whether the feature has been Filtered\n * the shader module is modified to discard the feature if filtered value is 0\n * the accessor getFiltered is used to get the value of `filtered` based on the value `filteredIndex` in Arrowlayer\n */\nexport default class FilterArrowExtension extends LayerExtension {\n  static defaultProps = defaultProps;\n  static extensionName = 'FilterArrowExtension';\n\n  getShaders() {\n    return {\n      modules: [shaderModule],\n      defines: {}\n    };\n  }\n\n  initializeState(this: Layer<FilterArrowExtensionProps>) {\n    const attributeManager = this.getAttributeManager();\n    if (attributeManager) {\n      attributeManager.add({\n        filtered: {\n          size: 1,\n          type: GL.FLOAT,\n          accessor: 'getFiltered',\n          shaderAttributes: {\n            filtered: {\n              divisor: 0\n            },\n            instanceFiltered: {\n              divisor: 1\n            }\n          }\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/deckgl-extensions/filter-shader-module.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {project} from '@deck.gl/core';\n\nconst vs = `\n  #ifdef NON_INSTANCED_MODEL\n    #define FILTER_ARROW_ATTRIB filtered\n  #else\n    #define FILTER_ARROW_ATTRIB instanceFiltered\n  #endif\n  attribute float FILTER_ARROW_ATTRIB;\n`;\n\nconst fs = ``;\n\nconst inject = {\n  // create degenerate vertices in vertex shader instead of discarding pixels in the fragment shader.\n  'vs:#main-start': `\n    if (FILTER_ARROW_ATTRIB == 0.) {\n      gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n      return;\n    }\n  `\n};\n\nexport default {\n  name: 'filter-arrow',\n  dependencies: [project],\n  vs,\n  fs,\n  inject,\n  getUniforms: () => {\n    return;\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/grid-layer/enhanced-cpu-grid-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CPUGridLayer} from '@deck.gl/aggregation-layers';\nimport CPUAggregator, {AggregationType, getAggregatedData} from '../layer-utils/cpu-aggregator';\n\nexport const gridAggregation: AggregationType = {\n  key: 'position',\n  updateSteps: [\n    {\n      key: 'aggregate',\n      triggers: {\n        cellSize: {\n          prop: 'cellSize'\n        },\n        position: {\n          prop: 'getPosition',\n          updateTrigger: 'getPosition'\n        },\n        aggregator: {\n          prop: 'gridAggregator'\n        }\n      },\n      updater: getAggregatedData\n    }\n  ]\n};\n\nexport default class ScaleEnhancedGridLayer extends CPUGridLayer<any> {\n  initializeState() {\n    const cpuAggregator = new CPUAggregator({\n      aggregation: gridAggregation\n    });\n\n    this.state = {\n      cpuAggregator,\n      aggregatorState: cpuAggregator.state\n    };\n    const attributeManager = this.getAttributeManager();\n    attributeManager.add({\n      positions: {size: 3, accessor: 'getPosition'}\n    });\n  }\n}\n\nScaleEnhancedGridLayer.layerName = 'ScaleEnhancedGridLayer';\n"
  },
  {
    "path": "src/deckgl-layers/src/hexagon-layer/enhanced-hexagon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {HexagonLayer} from '@deck.gl/aggregation-layers';\nimport CPUAggregator, {AggregationType, getAggregatedData} from '../layer-utils/cpu-aggregator';\n\nexport const hexagonAggregation: AggregationType = {\n  key: 'position',\n  updateSteps: [\n    {\n      key: 'aggregate',\n      triggers: {\n        cellSize: {\n          prop: 'radius'\n        },\n        position: {\n          prop: 'getPosition',\n          updateTrigger: 'getPosition'\n        },\n        aggregator: {\n          prop: 'hexagonAggregator'\n        }\n      },\n      updater: getAggregatedData\n    }\n  ]\n};\n\nexport default class ScaleEnhancedHexagonLayer extends HexagonLayer<any> {\n  initializeState() {\n    const cpuAggregator = new CPUAggregator({\n      aggregation: hexagonAggregation\n    });\n\n    this.state = {\n      cpuAggregator,\n      aggregatorState: cpuAggregator.state\n    };\n    const attributeManager = this.getAttributeManager();\n    attributeManager.add({\n      positions: {size: 3, accessor: 'getPosition'}\n    });\n  }\n}\n\nScaleEnhancedHexagonLayer.layerName = 'ScaleEnhancedHexagonLayer';\n"
  },
  {
    "path": "src/deckgl-layers/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as ThreeDBuildingLayer} from './3d-building-layer/3d-building-layer';\n\nexport {default as DeckGLClusterLayer} from './cluster-layer/cluster-layer';\n\nexport {default as EnhancedColumnLayer} from './column-layer/enhanced-column-layer';\n\nexport {default as EnhancedGridLayer} from './grid-layer/enhanced-cpu-grid-layer';\n\nexport {default as EnhancedHexagonLayer} from './hexagon-layer/enhanced-hexagon-layer';\nexport {default as EnhancedLineLayer} from './line-layer/line-layer';\nexport {default as SvgIconLayer} from './svg-icon-layer/svg-icon-layer';\nexport {default as FilterArrowExtension} from './deckgl-extensions/filter-arrow-layer';\n\nexport {default as RasterLayer} from './raster/raster-layer/raster-layer';\nexport {default as RasterMeshLayer} from './raster/raster-mesh-layer/raster-mesh-layer';\nexport * as RasterWebGL from './raster/webgl';\n\nexport {default as WMSLayer} from './wms/wms-layer';\n\nexport * from './layer-utils/shader-utils';\n\nexport * from './3d-building-layer/types';\nexport * from './3d-building-layer/3d-building-utils';\n"
  },
  {
    "path": "src/deckgl-layers/src/layer-utils/cluster-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Supercluster from 'supercluster';\nimport memoize from 'lodash/memoize';\nimport {MemoizedFunction} from 'lodash';\nimport {BBox, Position} from 'geojson';\n\nexport function getGeoJSON(data, getPosition, filterData) {\n  const raw = typeof filterData === 'function' ? data.filter(filterData) : data;\n\n  return raw\n    .map(d => ({\n      type: 'Point',\n      properties: {\n        data: d,\n        points: [d],\n        point_count: 1,\n        point_count_abbreviated: '1'\n      },\n      geometry: {\n        coordinates: getPosition(d)\n      }\n    }))\n    .filter(d => d.geometry.coordinates.every(Number.isFinite));\n}\n\nconst clusterResolver = ({clusterRadius}: {clusterRadius: number}) => `${clusterRadius}`;\n\nconst getClusterer = ({clusterRadius, geoJSON}: {clusterRadius: number; geoJSON}) =>\n  new Supercluster({\n    maxZoom: 20,\n    radius: clusterRadius,\n    reduce: (accumulated, props) => {\n      accumulated.points = [...accumulated.points, ...props.points];\n    },\n    map: props => ({points: [props.data]})\n  }).load(geoJSON);\n\nexport default class ClusterBuilder {\n  clusterer: (({clusterRadius, geoJSON}: {clusterRadius: number; geoJSON}) => Supercluster) &\n    MemoizedFunction;\n\n  constructor() {\n    this.clusterer = memoize(getClusterer, clusterResolver);\n  }\n\n  clustersAtZoom({\n    bbox,\n    clusterRadius,\n    geoJSON,\n    zoom\n  }: {\n    bbox: BBox;\n    clusterRadius: number;\n    geoJSON;\n    zoom: number;\n  }): {\n    points: any;\n    position: Position;\n    index: number;\n  }[] {\n    const clusterer = this.clusterer({clusterRadius, geoJSON});\n\n    // map clusters to formatted bins to be passed to deck.gl bin-sorter\n    const clusters = clusterer.getClusters(bbox, zoom).map((c, i) => ({\n      points: c.properties.points,\n      position: c.geometry.coordinates,\n      index: i\n    }));\n\n    return clusters;\n  }\n\n  clearClustererCache() {\n    this.clusterer.cache.clear?.();\n  }\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/layer-utils/cpu-aggregator.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable guard-for-in */\nimport {AGGREGATION_OPERATION, _BinSorter as BinSorter} from '@deck.gl/aggregation-layers';\nimport {console as Console} from 'global/window';\n\nimport {aggregate} from '@kepler.gl/utils';\nimport {AGGREGATION_TYPES, SCALE_FUNC} from '@kepler.gl/constants';\nimport {RGBAColor} from '@kepler.gl/types';\n\nexport type UpdaterType = (this: CPUAggregator, step, props, dimensionUpdater) => void;\nexport type BindedUpdaterType = () => void;\nexport type AggregatedUpdaterType = (\n  this: CPUAggregator,\n  step,\n  props,\n  aggregation,\n  aggregationParams\n) => void;\nexport type BindedAggregatedUpdaterType = (aggregationParams) => void;\n\nexport type UpdateStepsType = {\n  key: string;\n  triggers: {\n    [key: string]: {\n      prop: string;\n      updateTrigger?: string;\n    };\n  };\n  onSet?: {\n    props: string;\n  };\n  updater: UpdaterType;\n};\n\nexport type DimensionType<ValueType = any> = {\n  key: string;\n  accessor: string;\n  getPickingInfo: (dimensionState, cell, layerProps?) => any;\n  nullValue: ValueType;\n  updateSteps: UpdateStepsType[];\n  getSubLayerAccessor;\n};\n\nexport type AggregationUpdateStepsType = {\n  key: string;\n  triggers: {\n    [key: string]: {\n      prop: string;\n      updateTrigger?: string;\n    };\n  };\n  updater: AggregatedUpdaterType;\n};\n\nexport type AggregationType = {\n  key: string;\n  updateSteps: AggregationUpdateStepsType[];\n};\n\nexport const DECK_AGGREGATION_MAP = {\n  [AGGREGATION_OPERATION.SUM]: AGGREGATION_TYPES.sum,\n  [AGGREGATION_OPERATION.MEAN]: AGGREGATION_TYPES.average,\n  [AGGREGATION_OPERATION.MIN]: AGGREGATION_TYPES.minimum,\n  [AGGREGATION_OPERATION.MAX]: AGGREGATION_TYPES.maximum\n};\n\nexport function getValueFunc(aggregation, accessor) {\n  if (!aggregation || !AGGREGATION_OPERATION[aggregation.toUpperCase()]) {\n    Console.warn(`Aggregation ${aggregation} is not supported`);\n  }\n\n  const op = AGGREGATION_OPERATION[aggregation.toUpperCase()] || AGGREGATION_OPERATION.SUM;\n  const keplerOp = DECK_AGGREGATION_MAP[op];\n\n  return pts => aggregate(pts.map(accessor), keplerOp);\n}\n\nexport function getScaleFunctor(scaleType) {\n  if (!scaleType || !SCALE_FUNC[scaleType]) {\n    Console.warn(`Scale ${scaleType} is not supported`);\n  }\n  return SCALE_FUNC[scaleType] || SCALE_FUNC.quantize;\n}\n\nfunction nop() {\n  return;\n}\n\nexport function getGetValue(this: CPUAggregator, step, props, dimensionUpdater) {\n  const {key} = dimensionUpdater;\n  const {value, weight, aggregation} = step.triggers;\n\n  let getValue = props[value.prop];\n\n  if (getValue === null) {\n    // If `getValue` is not provided from props, build it with aggregation and weight.\n    getValue = getValueFunc(props[aggregation.prop], props[weight.prop]);\n  }\n\n  if (getValue) {\n    this._setDimensionState(key, {getValue});\n  }\n}\n\nexport function getDimensionSortedBins(this: CPUAggregator, step, props, dimensionUpdater) {\n  const {key} = dimensionUpdater;\n  const {getValue} = this.state.dimensions[key];\n  // @ts-expect-error\n  const sortedBins = new BinSorter(this.state.layerData.data || [], {\n    getValue,\n    filterData: props._filterData\n  });\n  this._setDimensionState(key, {sortedBins});\n}\n\nexport function getDimensionValueDomain(this: CPUAggregator, step, props, dimensionUpdater) {\n  const {key} = dimensionUpdater;\n  const {\n    triggers: {lowerPercentile, upperPercentile, scaleType}\n  } = step;\n\n  if (!this.state.dimensions[key].sortedBins) {\n    // the previous step should set sortedBins, if not, something went wrong\n    return;\n  }\n\n  let valueDomain =\n    // for log and sqrt scale, returns linear domain by default\n    // TODO: support other scale function domain in bin sorter\n    this.state.dimensions[key].sortedBins.getValueDomainByScale(props[scaleType.prop], [\n      props[lowerPercentile.prop],\n      props[upperPercentile.prop]\n    ]);\n\n  if (props.colorScaleType === 'custom' && props.colorMap) {\n    // for custom scale, return custom breaks as value domain directly\n    valueDomain = props.colorMap.reduce(\n      (prev, cur) => (Number.isFinite(cur[0]) ? prev.concat(cur[0]) : prev),\n      []\n    );\n  }\n\n  this._setDimensionState(key, {valueDomain});\n}\n\nexport function getDimensionScale(this: CPUAggregator, step, props, dimensionUpdater) {\n  const {key} = dimensionUpdater;\n  const {domain, range, scaleType, fixed} = step.triggers;\n  const {onSet} = step;\n  if (!this.state.dimensions[key].valueDomain) {\n    // the previous step should set valueDomain, if not, something went wrong\n    return;\n  }\n\n  const dimensionRange = props[range.prop];\n  const dimensionDomain = props[domain.prop] || this.state.dimensions[key].valueDomain;\n  const dimensionFixed = Boolean(fixed && props[fixed.prop]);\n\n  const scaleFunctor = getScaleFunctor(scaleType && props[scaleType.prop])();\n\n  const scaleFunc = scaleFunctor\n    .domain(dimensionDomain)\n    .range(dimensionFixed ? dimensionDomain : dimensionRange);\n  scaleFunc.scaleType = props.colorScaleType;\n\n  if (typeof onSet === 'object' && typeof props[onSet.props] === 'function') {\n    const sortedBins = this.state.dimensions[key].sortedBins;\n    props[onSet.props]({domain: scaleFunc.domain(), aggregatedBins: sortedBins.binMap});\n  }\n  this._setDimensionState(key, {scaleFunc});\n}\n\nfunction normalizeResult(result: {hexagons?; layerData?} = {}) {\n  // support previous hexagonAggregator API\n  if (result.hexagons) {\n    return Object.assign({data: result.hexagons}, result);\n  } else if (result.layerData) {\n    return Object.assign({data: result.layerData}, result);\n  }\n\n  return result;\n}\n\nexport function getAggregatedData(\n  this: CPUAggregator,\n  step,\n  props,\n  aggregation,\n  aggregationParams\n) {\n  const {\n    triggers: {aggregator: aggr}\n  } = step;\n  const aggregator = props[aggr.prop];\n\n  // result should contain a data array and other props\n  // result = {data: [], ...other props}\n  const result = aggregator(props, aggregationParams);\n  this.setState({\n    layerData: normalizeResult(result)\n  });\n}\n\nexport const defaultAggregation: AggregationType = {\n  key: 'position',\n  updateSteps: [\n    {\n      key: 'aggregate',\n      triggers: {\n        cellSize: {\n          prop: 'cellSize'\n        },\n        position: {\n          prop: 'getPosition',\n          updateTrigger: 'getPosition'\n        },\n        aggregator: {\n          prop: 'gridAggregator'\n        }\n      },\n      updater: getAggregatedData\n    }\n  ]\n};\n\nfunction getSubLayerAccessor(dimensionState, dimension) {\n  return cell => {\n    const {sortedBins, scaleFunc} = dimensionState;\n    const bin = sortedBins.binMap[cell.index];\n\n    if (bin && bin.counts === 0) {\n      // no points left in bin after filtering\n      return dimension.nullValue;\n    }\n\n    const cv = bin && bin.value;\n    const domain = scaleFunc.domain();\n\n    const isValueInDomain =\n      scaleFunc.scaleType === 'custom'\n        ? cv >= sortedBins.minValue && cv <= sortedBins.maxValue\n        : cv >= domain[0] && cv <= domain[domain.length - 1];\n\n    // if cell value is outside domain, set alpha to 0\n    return isValueInDomain ? scaleFunc(cv) : dimension.nullValue;\n  };\n}\n\nexport const defaultColorDimension: DimensionType<RGBAColor> = {\n  key: 'fillColor',\n  accessor: 'getFillColor',\n  getPickingInfo: (dimensionState, cell) => {\n    if (!cell) {\n      return {};\n    }\n    const {sortedBins} = dimensionState;\n    const colorValue = sortedBins.binMap[cell.index] && sortedBins.binMap[cell.index].value;\n    return {colorValue};\n  },\n  nullValue: [0, 0, 0, 0],\n  updateSteps: [\n    {\n      key: 'getValue',\n      triggers: {\n        value: {\n          prop: 'getColorValue',\n          updateTrigger: 'getColorValue'\n        },\n        weight: {\n          prop: 'getColorWeight',\n          updateTrigger: 'getColorWeight'\n        },\n        aggregation: {\n          prop: 'colorAggregation'\n        }\n      },\n      updater: getGetValue\n    },\n    {\n      key: 'getBins',\n      triggers: {\n        _filterData: {\n          prop: '_filterData',\n          updateTrigger: '_filterData'\n        }\n      },\n      updater: getDimensionSortedBins\n    },\n    {\n      key: 'getDomain',\n      triggers: {\n        lowerPercentile: {\n          prop: 'lowerPercentile'\n        },\n        upperPercentile: {\n          prop: 'upperPercentile'\n        },\n        scaleType: {prop: 'colorScaleType'}\n      },\n      updater: getDimensionValueDomain\n    },\n    {\n      key: 'getScaleFunc',\n      triggers: {\n        domain: {prop: 'colorDomain'},\n        range: {prop: 'colorRange'},\n        scaleType: {prop: 'colorScaleType'}\n      },\n      onSet: {\n        props: 'onSetColorDomain'\n      },\n      updater: getDimensionScale\n    }\n  ],\n  getSubLayerAccessor\n};\n\nexport const defaultElevationDimension: DimensionType<number> = {\n  key: 'elevation',\n  accessor: 'getElevation',\n  getPickingInfo: (dimensionState, cell) => {\n    if (!cell) {\n      return {};\n    }\n    const {sortedBins} = dimensionState;\n    const elevationValue = sortedBins.binMap[cell.index] && sortedBins.binMap[cell.index].value;\n    return {elevationValue};\n  },\n  nullValue: -1,\n  updateSteps: [\n    {\n      key: 'getValue',\n      triggers: {\n        value: {\n          prop: 'getElevationValue',\n          updateTrigger: 'getElevationValue'\n        },\n        weight: {\n          prop: 'getElevationWeight',\n          updateTrigger: 'getElevationWeight'\n        },\n        aggregation: {\n          prop: 'elevationAggregation'\n        }\n      },\n      updater: getGetValue\n    },\n    {\n      key: 'getBins',\n      triggers: {\n        _filterData: {\n          prop: '_filterData',\n          updateTrigger: '_filterData'\n        }\n      },\n      updater: getDimensionSortedBins\n    },\n    {\n      key: 'getDomain',\n      triggers: {\n        lowerPercentile: {\n          prop: 'elevationLowerPercentile'\n        },\n        upperPercentile: {\n          prop: 'elevationUpperPercentile'\n        },\n        scaleType: {prop: 'elevationScaleType'}\n      },\n      updater: getDimensionValueDomain\n    },\n    {\n      key: 'getScaleFunc',\n      triggers: {\n        fixed: {prop: 'elevationFixed'},\n        domain: {prop: 'elevationDomain'},\n        range: {prop: 'elevationRange'},\n        scaleType: {prop: 'elevationScaleType'}\n      },\n      onSet: {\n        props: 'onSetElevationDomain'\n      },\n      updater: getDimensionScale\n    }\n  ],\n  getSubLayerAccessor\n};\n\nexport const defaultDimensions = [defaultColorDimension, defaultElevationDimension];\n\nexport type CPUAggregatorState = {\n  layerData: {data?};\n  dimensions: object;\n  geoJSON?;\n  clusterBuilder?;\n};\n\nexport default class CPUAggregator {\n  static getDimensionScale: any;\n  state: CPUAggregatorState;\n  dimensionUpdaters: {[key: string]: DimensionType};\n  aggregationUpdater: AggregationType;\n\n  constructor(\n    opts: {\n      initialState?: CPUAggregatorState;\n      dimensions?: DimensionType[];\n      aggregation?: AggregationType;\n    } = {}\n  ) {\n    this.state = {\n      layerData: {},\n      dimensions: {\n        // color: {\n        //   getValue: null,\n        //   domain: null,\n        //   sortedBins: null,\n        //   scaleFunc: nop\n        // },\n        // elevation: {\n        //   getValue: null,\n        //   domain: null,\n        //   sortedBins: null,\n        //   scaleFunc: nop\n        // }\n      },\n      ...opts.initialState\n    };\n\n    this.dimensionUpdaters = {};\n    this.aggregationUpdater = opts.aggregation || defaultAggregation;\n\n    this._addDimension(opts.dimensions || defaultDimensions);\n  }\n\n  static defaultDimensions() {\n    return defaultDimensions;\n  }\n\n  updateAllDimensions(props) {\n    let dimensionChanges: BindedUpdaterType[] = [];\n    // update all dimensions\n    for (const dim in this.dimensionUpdaters) {\n      const updaters = this._accumulateUpdaters(0, props, this.dimensionUpdaters[dim]);\n      dimensionChanges = dimensionChanges.concat(updaters);\n    }\n\n    dimensionChanges.forEach(f => typeof f === 'function' && f());\n  }\n\n  updateAggregation(props, aggregationParams) {\n    const updaters = this._accumulateUpdaters(0, props, this.aggregationUpdater);\n    updaters.forEach(f => typeof f === 'function' && f(aggregationParams));\n  }\n\n  updateState(opts, aggregationParams) {\n    const {oldProps, props, changeFlags} = opts;\n    let dimensionChanges: BindedUpdaterType[] = [];\n\n    if (changeFlags.dataChanged) {\n      // if data changed update everything\n      this.updateAggregation(props, aggregationParams);\n      this.updateAllDimensions(props);\n\n      return this.state;\n    }\n\n    const aggregationChanges = this._getAggregationChanges(oldProps, props, changeFlags);\n\n    if (aggregationChanges && aggregationChanges.length) {\n      // get aggregatedData\n      aggregationChanges.forEach(f => typeof f === 'function' && f(aggregationParams));\n      this.updateAllDimensions(props);\n    } else {\n      // only update dimensions\n      dimensionChanges = this._getDimensionChanges(oldProps, props, changeFlags) || [];\n      dimensionChanges.forEach(f => typeof f === 'function' && f());\n    }\n\n    return this.state;\n  }\n\n  // Update private state\n  setState(updateObject) {\n    this.state = Object.assign({}, this.state, updateObject);\n  }\n\n  // Update private state.dimensions\n  _setDimensionState(key, updateObject) {\n    this.setState({\n      dimensions: Object.assign({}, this.state.dimensions, {\n        [key]: Object.assign({}, this.state.dimensions[key], updateObject)\n      })\n    });\n  }\n\n  _addAggregation(aggregation: AggregationType) {\n    this.aggregationUpdater = aggregation;\n  }\n\n  _addDimension(dimensions: DimensionType[] = []) {\n    dimensions.forEach(dimension => {\n      const {key} = dimension;\n      this.dimensionUpdaters[key] = dimension;\n    });\n  }\n\n  _needUpdateStep(\n    dimensionStep: UpdateStepsType | AggregationUpdateStepsType,\n    oldProps,\n    props,\n    changeFlags\n  ) {\n    // whether need to update current dimension step\n    // dimension step is the value, domain, scaleFunction of each dimension\n    // each step is an object with properties links to layer prop and whether the prop is\n    // controlled by updateTriggers\n    return Object.values(dimensionStep.triggers).some(item => {\n      if (item.updateTrigger) {\n        // check based on updateTriggers change first\n        return (\n          changeFlags.updateTriggersChanged &&\n          (changeFlags.updateTriggersChanged.all ||\n            changeFlags.updateTriggersChanged[item.updateTrigger])\n        );\n      }\n      // fallback to direct comparison\n      return oldProps[item.prop] !== props[item.prop];\n    });\n  }\n\n  _accumulateUpdaters<UpdaterObjectType extends DimensionType | AggregationType>(\n    step,\n    props,\n    dimension: UpdaterObjectType\n  ) {\n    type LocalUpdaterType = UpdaterObjectType extends DimensionType\n      ? BindedUpdaterType\n      : BindedAggregatedUpdaterType;\n    const updaters: LocalUpdaterType[] = [];\n    for (let i = step; i < dimension.updateSteps.length; i++) {\n      const updater = dimension.updateSteps[i].updater;\n      if (typeof updater === 'function') {\n        updaters.push(\n          updater.bind(this, dimension.updateSteps[i], props, dimension) as LocalUpdaterType\n        );\n      }\n    }\n\n    return updaters;\n  }\n\n  _getAllUpdaters<UpdaterObjectType extends DimensionType | AggregationType>(\n    dimension: UpdaterObjectType,\n    oldProps,\n    props,\n    changeFlags\n  ) {\n    type LocalUpdaterType = UpdaterObjectType extends DimensionType\n      ? BindedUpdaterType\n      : BindedAggregatedUpdaterType;\n    let updaters: LocalUpdaterType[] = [];\n    const needUpdateStep = dimension.updateSteps.findIndex(step =>\n      this._needUpdateStep(step, oldProps, props, changeFlags)\n    );\n\n    if (needUpdateStep > -1) {\n      updaters = updaters.concat(this._accumulateUpdaters(needUpdateStep, props, dimension));\n    }\n\n    return updaters;\n  }\n\n  _getAggregationChanges(oldProps, props, changeFlags) {\n    const updaters = this._getAllUpdaters(this.aggregationUpdater, oldProps, props, changeFlags);\n    return updaters.length ? updaters : null;\n  }\n\n  _getDimensionChanges(oldProps, props, changeFlags) {\n    let updaters: BindedUpdaterType[] = [];\n\n    // get dimension to be updated\n    for (const key in this.dimensionUpdaters) {\n      // return the first triggered updater for each dimension\n      const dimension = this.dimensionUpdaters[key];\n      const dimensionUpdaters = this._getAllUpdaters(dimension, oldProps, props, changeFlags);\n      updaters = updaters.concat(dimensionUpdaters);\n    }\n\n    return updaters.length ? updaters : null;\n  }\n\n  getUpdateTriggers(props) {\n    const _updateTriggers = props.updateTriggers || {};\n    const updateTriggers = {};\n\n    for (const key in this.dimensionUpdaters) {\n      const {accessor, updateSteps}: {accessor; updateSteps: UpdateStepsType[]} =\n        this.dimensionUpdaters[key];\n      // fold dimension triggers into each accessor\n      updateTriggers[accessor] = {};\n\n      updateSteps.forEach(step => {\n        Object.values(step.triggers || []).forEach(({prop, updateTrigger}) => {\n          if (updateTrigger) {\n            // if prop is based on updateTrigger e.g. getColorValue, getColorWeight\n            // and updateTriggers is passed in from layer prop\n            // fold the updateTriggers into accessor\n            const fromProp = _updateTriggers[updateTrigger];\n            if (typeof fromProp === 'object' && !Array.isArray(fromProp)) {\n              // if updateTrigger is an object spread it\n              Object.assign(updateTriggers[accessor], fromProp);\n            } else if (fromProp !== undefined) {\n              updateTriggers[accessor][prop] = fromProp;\n            }\n          } else {\n            // if prop is not based on updateTrigger\n            updateTriggers[accessor][prop] = props[prop];\n          }\n        });\n      });\n    }\n\n    return updateTriggers;\n  }\n\n  getPickingInfo({info}, layerProps) {\n    const isPicked = info.picked && info.index > -1;\n    let object = null;\n    const cell = isPicked ? this.state.layerData.data[info.index] : null;\n    if (cell) {\n      let binInfo = {};\n      for (const key in this.dimensionUpdaters) {\n        const {getPickingInfo} = this.dimensionUpdaters[key];\n        if (typeof getPickingInfo === 'function') {\n          binInfo = Object.assign(\n            {},\n            binInfo,\n            getPickingInfo(this.state.dimensions[key], cell, layerProps)\n          );\n        }\n      }\n\n      object = Object.assign(binInfo, cell, {\n        points: cell.filteredPoints || cell.points\n      });\n    }\n\n    // add bin  and  to info\n    return Object.assign(info, {\n      picked: Boolean(object),\n      // override object with picked cell\n      object\n    });\n  }\n\n  getAccessor(dimensionKey, layerProps) {\n    if (!Object.prototype.hasOwnProperty.call(this.dimensionUpdaters, dimensionKey)) {\n      return nop;\n    }\n    return this.dimensionUpdaters[dimensionKey].getSubLayerAccessor(\n      this.state.dimensions[dimensionKey],\n      this.dimensionUpdaters[dimensionKey],\n      layerProps\n    );\n  }\n}\n\nCPUAggregator.getDimensionScale = getDimensionScale;\n"
  },
  {
    "path": "src/deckgl-layers/src/layer-utils/shader-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {console as Console} from 'global/window';\n\n/*\n * Amendment to default layer vertex shader\n * @param {string} vs\n * @param {string} type\n * @param {string} originalText\n * @param {string} testToReplace\n * @return {string} output shader\n *\n */\nexport function editShader(vs: string, type: string, originalText: string, testToReplace: string) {\n  if (!vs.includes(originalText)) {\n    // Here we call Console.error when we fail to edit deck.gl shader\n    // This should be caught by layer test\n    Console.error(`Cannot edit ${type} layer shader`);\n    return vs;\n  }\n\n  return vs.replace(originalText, testToReplace);\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/line-layer/line-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LineLayer, LineLayerProps} from '@deck.gl/layers';\nimport GL from '@luma.gl/constants';\nimport {RGBAColor} from 'deck.gl';\nimport {editShader} from '../';\n\nconst defaultProps = {\n  ...LineLayer.defaultProps,\n  getTargetColor: x => x.color || [0, 0, 0, 255]\n};\n\nfunction addInstanceColorShader(vs) {\n  const targetColorVs = editShader(\n    vs,\n    'line target color vs',\n    'attribute vec4 instanceColors;',\n    'attribute vec4 instanceColors; attribute vec4 instanceTargetColors;'\n  );\n\n  return editShader(\n    targetColorVs,\n    'line color vs',\n    'vColor = vec4(instanceColors.rgb, instanceColors.a * opacity);',\n    `vec4 color = mix(instanceColors, instanceTargetColors, positions.x);` +\n      `vColor = vec4(color.rgb, color.a * opacity);`\n  );\n}\n\nfunction addElevationScale(vs) {\n  let elevationVs = editShader(\n    vs,\n    'line elevation scale 1 vs - inject elevation scale',\n    'uniform float widthMaxPixels;',\n    `uniform float widthMaxPixels;\n     uniform float elevationScale;`\n  );\n\n  elevationVs = editShader(\n    elevationVs,\n    'line elevation scale 2 vs - multiply by elevation scale',\n    `geometry.worldPosition = instanceSourcePositions;\n  geometry.worldPositionAlt = instanceTargetPositions;`,\n    `vec3 source_world = instanceSourcePositions;\n     vec3 target_world = instanceTargetPositions;\n     source_world.z *= elevationScale;\n     target_world.z *= elevationScale;\n     \n     geometry.worldPosition = source_world;\n     geometry.worldPositionAlt = target_world;`\n  );\n\n  elevationVs = editShader(\n    elevationVs,\n    'line elevation scale 3 vs',\n    `vec3 source_world = instanceSourcePositions;\n  vec3 target_world = instanceTargetPositions;`,\n    ''\n  );\n\n  return elevationVs;\n}\n\nexport default class EnhancedLineLayer extends LineLayer<\n  any,\n  LineLayerProps<any> & {elevationScale: number; getTargetColor: RGBAColor}\n> {\n  getShaders() {\n    const shaders = super.getShaders();\n\n    let vs = addInstanceColorShader(shaders.vs);\n    vs = addElevationScale(vs);\n\n    return {\n      ...shaders,\n      vs\n    };\n  }\n\n  draw({uniforms}) {\n    const {elevationScale} = this.props;\n    super.draw({uniforms: {...uniforms, elevationScale}});\n  }\n\n  initializeState() {\n    super.initializeState(undefined);\n    const {attributeManager} = this.state;\n    attributeManager.addInstanced({\n      instanceTargetColors: {\n        size: this.props.colorFormat?.length,\n        type: GL.UNSIGNED_BYTE,\n        normalized: true,\n        transition: true,\n        accessor: 'getTargetColor',\n        defaultValue: [0, 0, 0, 255]\n      }\n    });\n  }\n}\n\nEnhancedLineLayer.layerName = 'EnhancedLineLayer';\nEnhancedLineLayer.defaultProps = defaultProps;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/images.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport GL from '@luma.gl/constants';\nimport {isWebGL2, Texture2D} from '@luma.gl/core';\nimport type {Texture2DProps} from '@luma.gl/webgl';\nimport isEqual from 'lodash/isEqual';\n\nimport type {ImageInput, ImageState} from './types';\n\n/**\n * Texture parameters that should work for every texture on both WebGL1 and WebGL2\n */\nconst DEFAULT_UNIVERSAL_TEXTURE_PARAMETERS = {\n  [GL.TEXTURE_MIN_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_MAG_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,\n  [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE\n};\n\ntype LoadImagesOptions = {\n  gl: WebGLRenderingContext | WebGL2RenderingContext;\n  images: ImageState;\n  imagesData: ImageInput;\n  oldImagesData: ImageInput;\n};\n\n/**\n * Load image items to webgl context\n * @param gl webgl rendering context\n * @param imageItem image item, might be single texture or array of textures\n * @returns loaded single webgl texture or array of webgl texture or null\n */\nfunction loadImageItem(\n  gl: WebGLRenderingContext | WebGL2RenderingContext,\n  imageItem: Texture2DProps | Texture2D | (Texture2DProps | Texture2D)[]\n): null | Texture2D | Texture2D[] {\n  let result: null | Texture2D | Texture2D[];\n  if (Array.isArray(imageItem)) {\n    const dirtyResult = imageItem.map(x => loadTexture(gl, x));\n    result = [];\n    for (const texture of dirtyResult) {\n      if (texture) {\n        result.push(texture);\n      }\n    }\n    if (!result.length) {\n      result = null;\n    }\n  } else {\n    result = loadTexture(gl, imageItem);\n  }\n  return result;\n}\n\n// eslint-disable-next-line complexity\nexport function loadImages({\n  gl,\n  images,\n  imagesData,\n  oldImagesData\n}: LoadImagesOptions): ImageState | null {\n  // Change to `true` if we need to setState with a new `images` object\n  let imagesDirty = false;\n\n  // If there are any removed keys, which previously existed in oldProps and\n  // this.state.images but no longer exist in props, remove from the images\n  // object\n  if (oldImagesData) {\n    for (const key in oldImagesData) {\n      if (imagesData && !(key in imagesData) && key in images) {\n        delete images[key];\n        imagesDirty = true;\n      }\n    }\n  }\n\n  // Check if any keys of props.images have changed\n  const changedKeys: string[] = [];\n  for (const key in imagesData) {\n    // If oldProps.images didn't exist or it existed and this key didn't exist\n    if (!oldImagesData || (oldImagesData && !(key in oldImagesData))) {\n      changedKeys.push(key);\n      continue;\n    }\n\n    // Deep compare when the key previously existed to see if it changed\n    if (!isEqual(imagesData[key], oldImagesData[key])) {\n      changedKeys.push(key);\n    }\n  }\n\n  for (const key of changedKeys) {\n    const imageData = imagesData[key];\n    if (!imageData) {\n      continue;\n    }\n\n    const loadedItem = loadImageItem(gl, imageData);\n    if (loadedItem) {\n      images[key] = loadedItem;\n    }\n    imagesDirty = true;\n  }\n\n  if (imagesDirty) {\n    return images;\n  }\n\n  return null;\n}\n\n/**\n * Create Texture2D object from image data\n */\nfunction loadTexture(\n  gl: WebGLRenderingContext | WebGL2RenderingContext,\n  imageData: Texture2D | Texture2DProps\n): Texture2D | null {\n  if (!imageData) {\n    return null;\n  }\n\n  if (imageData instanceof Texture2D) {\n    return imageData;\n  }\n\n  let textureParams: Texture2DProps = {\n    parameters: DEFAULT_UNIVERSAL_TEXTURE_PARAMETERS,\n    ...imageData\n  };\n\n  if (!isWebGL2(gl)) {\n    textureParams = webgl1TextureFallbacks(textureParams);\n  }\n\n  return new Texture2D(gl, textureParams);\n}\n\n/**\n * Texture fallbacks for WebGL1\n * Fallback ideas derived from viv\n * https://github.com/hms-dbmi/viv/blob/5bcec429eeba55914ef3d7155a610d82048520a0/src/layers/XRLayer/XRLayer.js#L280-L302\n */\nfunction webgl1TextureFallbacks(textureParams: Texture2DProps): Texture2DProps {\n  // Set mipmaps to false\n  // Not sure if this is necessary?\n  // Might actually only be necessary for uint textures\n  textureParams.mipmaps = false;\n\n  // Change format to Luminance\n  if (textureParams.format && [GL.R8UI, GL.R16UI, GL.R32UI].includes(textureParams.format)) {\n    textureParams.format = GL.LUMINANCE;\n  }\n\n  // Change dataFormat to Luminance\n  if (textureParams.dataFormat === GL.RED_INTEGER) {\n    textureParams.dataFormat = GL.LUMINANCE;\n  }\n\n  // Set data type to float\n  if (\n    textureParams.type &&\n    [GL.UNSIGNED_BYTE, GL.UNSIGNED_SHORT, GL.UNSIGNED_INT].includes(textureParams.type)\n  ) {\n    textureParams.type = GL.FLOAT;\n  }\n\n  // Cast data to float 32 if one of the uint types\n  if (\n    textureParams.data instanceof Uint8Array ||\n    textureParams.data instanceof Uint16Array ||\n    textureParams.data instanceof Uint32Array\n  ) {\n    textureParams.data = new Float32Array(textureParams.data);\n  }\n\n  // Override texture parameters to make sure they're valid on WebGL1\n  textureParams.parameters = {...textureParams.parameters, ...DEFAULT_UNIVERSAL_TEXTURE_PARAMETERS};\n\n  return textureParams;\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-layer/raster-layer-webgl1.fs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#define SHADER_NAME raster-layer-fragment-shader\n\n#ifdef GL_ES\nprecision highp float;\n#endif\n\nvarying vec2 vTexCoord;\nvarying vec2 vTexPos;\n\nuniform float desaturate;\nuniform vec4 transparentColor;\nuniform vec3 tintColor;\nuniform float opacity;\n\nuniform float coordinateConversion;\nuniform vec4 bounds;\n\n/* projection utils */\nconst float TILE_SIZE = 512.0;\nconst float PI = 3.1415926536;\nconst float WORLD_SCALE = TILE_SIZE / PI / 2.0;\n\n// from degrees to Web Mercator\nvec2 lnglat_to_mercator(vec2 lnglat) {\n  float x = lnglat.x;\n  float y = clamp(lnglat.y, -89.9, 89.9);\n  return vec2(\n    radians(x) + PI,\n    PI + log(tan(PI * 0.25 + radians(y) * 0.5))\n  ) * WORLD_SCALE;\n}\n\n// from Web Mercator to degrees\nvec2 mercator_to_lnglat(vec2 xy) {\n  xy /= WORLD_SCALE;\n  return degrees(vec2(\n    xy.x - PI,\n    atan(exp(xy.y - PI)) * 2.0 - PI * 0.5\n  ));\n}\n/* End projection utils */\n\n// apply desaturation\nvec3 color_desaturate(vec3 color) {\n  float luminance = (color.r + color.g + color.b) * 0.333333333;\n  return mix(color, vec3(luminance), desaturate);\n}\n\n// apply tint\nvec3 color_tint(vec3 color) {\n  return color * tintColor;\n}\n\n// blend with background color\nvec4 apply_opacity(vec3 color, float alpha) {\n  if (transparentColor.a == 0.0) {\n    return vec4(color, alpha);\n  }\n  float blendedAlpha = alpha + transparentColor.a * (1.0 - alpha);\n  float highLightRatio = alpha / blendedAlpha;\n  vec3 blendedRGB = mix(transparentColor.rgb, color, highLightRatio);\n  return vec4(blendedRGB, blendedAlpha);\n}\n\nvec2 getUV(vec2 pos) {\n  return vec2(\n    (pos.x - bounds[0]) / (bounds[2] - bounds[0]),\n    (pos.y - bounds[3]) / (bounds[1] - bounds[3])\n  );\n}\n\nvoid main(void) {\n  vec2 uv = vTexCoord;\n  if (coordinateConversion < -0.5) {\n    vec2 lnglat = mercator_to_lnglat(vTexPos);\n    uv = getUV(lnglat);\n  } else if (coordinateConversion > 0.5) {\n    vec2 commonPos = lnglat_to_mercator(vTexPos);\n    uv = getUV(commonPos);\n  }\n\n  vec4 image;\n  DECKGL_CREATE_COLOR(image, vTexCoord);\n\n  DECKGL_MUTATE_COLOR(image, vTexCoord);\n\n  gl_FragColor = apply_opacity(color_tint(color_desaturate(image.rgb)), opacity);\n\n  geometry.uv = uv;\n  DECKGL_FILTER_COLOR(gl_FragColor, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-layer/raster-layer-webgl1.vs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#define SHADER_NAME raster-layer-vertex-shader\n\nattribute vec2 texCoords;\nattribute vec3 positions;\nattribute vec3 positions64Low;\n\nvarying vec2 vTexCoord;\nvarying vec2 vTexPos;\n\nuniform float coordinateConversion;\n\nconst vec3 pickingColor = vec3(1.0, 0.0, 0.0);\n\nvoid main(void) {\n  geometry.worldPosition = positions;\n  geometry.uv = texCoords;\n  geometry.pickingColor = pickingColor;\n\n  gl_Position = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position);\n  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);\n\n  vTexCoord = texCoords;\n\n  if (coordinateConversion < -0.5) {\n    vTexPos = geometry.position.xy;\n  } else if (coordinateConversion > 0.5) {\n    vTexPos = geometry.worldPosition.xy;\n  }\n\n  vec4 color = vec4(0.0);\n  DECKGL_FILTER_COLOR(color, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-layer/raster-layer-webgl2.fs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#version 300 es\n#define SHADER_NAME raster-layer-fragment-shader\n\n// Ref https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf#page=60\nprecision mediump float;\nprecision mediump int;\nprecision mediump usampler2D;\n\nin vec2 vTexCoord;\nin vec2 vTexPos;\n\nout vec4 color;\n\nuniform float desaturate;\nuniform vec4 transparentColor;\nuniform vec3 tintColor;\nuniform float opacity;\n\nuniform float coordinateConversion;\nuniform vec4 bounds;\n\n/* projection utils */\nconst float TILE_SIZE = 512.0;\nconst float PI = 3.1415926536;\nconst float WORLD_SCALE = TILE_SIZE / PI / 2.0;\n\n// from degrees to Web Mercator\nvec2 lnglat_to_mercator(vec2 lnglat) {\n  float x = lnglat.x;\n  float y = clamp(lnglat.y, -89.9, 89.9);\n  return vec2(\n    radians(x) + PI,\n    PI + log(tan(PI * 0.25 + radians(y) * 0.5))\n  ) * WORLD_SCALE;\n}\n\n// from Web Mercator to degrees\nvec2 mercator_to_lnglat(vec2 xy) {\n  xy /= WORLD_SCALE;\n  return degrees(vec2(\n    xy.x - PI,\n    atan(exp(xy.y - PI)) * 2.0 - PI * 0.5\n  ));\n}\n/* End projection utils */\n\n// apply desaturation\nvec3 color_desaturate(vec3 color) {\n  float luminance = (color.r + color.g + color.b) * 0.333333333;\n  return mix(color, vec3(luminance), desaturate);\n}\n\n// apply tint\nvec3 color_tint(vec3 color) {\n  return color * tintColor;\n}\n\n// blend with background color\nvec4 apply_opacity(vec3 color, float alpha) {\n  if (transparentColor.a == 0.0) {\n    return vec4(color, alpha);\n  }\n  float blendedAlpha = alpha + transparentColor.a * (1.0 - alpha);\n  float highLightRatio = alpha / blendedAlpha;\n  vec3 blendedRGB = mix(transparentColor.rgb, color, highLightRatio);\n  return vec4(blendedRGB, blendedAlpha);\n}\n\nvec2 getUV(vec2 pos) {\n  return vec2(\n    (pos.x - bounds[0]) / (bounds[2] - bounds[0]),\n    (pos.y - bounds[3]) / (bounds[1] - bounds[3])\n  );\n}\n\nvoid main(void) {\n  vec2 uv = vTexCoord;\n  if (coordinateConversion < -0.5) {\n    vec2 lnglat = mercator_to_lnglat(vTexPos);\n    uv = getUV(lnglat);\n  } else if (coordinateConversion > 0.5) {\n    vec2 commonPos = lnglat_to_mercator(vTexPos);\n    uv = getUV(commonPos);\n  }\n\n  vec4 image;\n  DECKGL_CREATE_COLOR(image, vTexCoord);\n\n  DECKGL_MUTATE_COLOR(image, vTexCoord);\n\n  color = apply_opacity(color_tint(color_desaturate(image.rgb)), opacity);\n\n  geometry.uv = uv;\n  DECKGL_FILTER_COLOR(color, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-layer/raster-layer-webgl2.vs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#version 300 es\n#define SHADER_NAME raster-layer-vertex-shader\n\nprecision mediump float;\n\nin vec2 texCoords;\nin vec3 positions;\nin vec3 positions64Low;\n\nout vec2 vTexCoord;\nout vec2 vTexPos;\n\nuniform float coordinateConversion;\n\nconst vec3 pickingColor = vec3(1.0, 0.0, 0.0);\n\nvoid main(void) {\n  geometry.worldPosition = positions;\n  geometry.uv = texCoords;\n  geometry.pickingColor = pickingColor;\n\n  gl_Position = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position);\n  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);\n\n  vTexCoord = texCoords;\n\n  if (coordinateConversion < -0.5) {\n    vTexPos = geometry.position.xy;\n  } else if (coordinateConversion > 0.5) {\n    vTexPos = geometry.worldPosition.xy;\n  }\n\n  vec4 color = vec4(0.0);\n  DECKGL_FILTER_COLOR(color, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-layer/raster-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {project32, UpdateParameters} from '@deck.gl/core/typed';\nimport {BitmapLayer} from '@deck.gl/layers/typed';\nimport {isWebGL2} from '@luma.gl/core';\nimport {ProgramManager} from '@luma.gl/engine';\n\nimport fsWebGL1 from './raster-layer-webgl1.fs';\nimport vsWebGL1 from './raster-layer-webgl1.vs';\nimport fsWebGL2 from './raster-layer-webgl2.fs';\nimport vsWebGL2 from './raster-layer-webgl2.vs';\nimport {loadImages} from '../images';\nimport type {RasterLayerAddedProps, ImageState} from '../types';\nimport {modulesEqual} from '../util';\n\nconst defaultProps = {\n  ...BitmapLayer.defaultProps,\n  modules: {type: 'array', value: [], compare: true},\n  images: {type: 'object', value: {}, compare: true},\n  moduleProps: {type: 'object', value: {}, compare: true}\n};\n\nexport default class RasterLayer extends BitmapLayer<RasterLayerAddedProps> {\n  declare state: BitmapLayer<RasterLayerAddedProps>['state'] & {\n    images: ImageState;\n  };\n\n  initializeState(): void {\n    const {gl} = this.context;\n    const programManager = ProgramManager.getDefaultProgramManager(gl);\n\n    const fsStr1 = 'fs:DECKGL_MUTATE_COLOR(inout vec4 image, in vec2 coord)';\n    const fsStr2 = 'fs:DECKGL_CREATE_COLOR(inout vec4 image, in vec2 coord)';\n\n    // Only initialize shader hook functions _once globally_\n    // Since the program manager is shared across all layers, but many layers\n    // might be created, this solves the performance issue of always adding new\n    // hook functions.\n    if (!programManager._hookFunctions.includes(fsStr1)) {\n      programManager.addShaderHook(fsStr1);\n    }\n    if (!programManager._hookFunctions.includes(fsStr2)) {\n      programManager.addShaderHook(fsStr2);\n    }\n\n    // images is a mapping from keys to Texture2D objects. The keys should match\n    // names of uniforms in shader modules\n    this.setState({images: {}});\n\n    super.initializeState();\n  }\n\n  draw({uniforms}: {uniforms: {[key: string]: any}}): void {\n    const {model, images, coordinateConversion, bounds} = this.state;\n    const {desaturate, transparentColor, tintColor, moduleProps} = this.props;\n\n    // Render the image\n    if (\n      !model ||\n      !images ||\n      Object.keys(images).length === 0 ||\n      !Object.values(images).every(item => item)\n    ) {\n      return;\n    }\n\n    model\n      .setUniforms({\n        ...uniforms,\n        desaturate,\n        transparentColor: transparentColor?.map(x => (x ? x / 255 : 0)),\n        tintColor: tintColor?.slice(0, 3).map(x => x / 255),\n        coordinateConversion,\n        bounds\n      })\n      .updateModuleSettings({\n        ...moduleProps,\n        ...images\n      })\n      .draw();\n  }\n\n  getShaders(): any {\n    const {gl} = this.context;\n    const {modules = []} = this.props;\n    const webgl2 = isWebGL2(gl);\n\n    // Choose webgl version for module\n    // If fs2 or fs1 keys exist, prefer them, but fall back to fs, so that\n    // version-independent modules don't need to care\n    for (const module of modules) {\n      module.fs = webgl2 ? module.fs2 || module.fs : module.fs1 || module.fs;\n\n      // Sampler type is always float for WebGL1\n      if (!webgl2 && module.defines) {\n        module.defines.SAMPLER_TYPE = 'sampler2D';\n      }\n    }\n\n    return {\n      ...super.getShaders(),\n      vs: webgl2 ? vsWebGL2 : vsWebGL1,\n      fs: webgl2 ? fsWebGL2 : fsWebGL1,\n      modules: [project32, ...modules]\n    };\n  }\n\n  // eslint-disable-next-line complexity\n  updateState(params: UpdateParameters<BitmapLayer<RasterLayerAddedProps>>): void {\n    const {props, oldProps, changeFlags} = params;\n    const modules = props && props.modules;\n    const oldModules = oldProps && oldProps.modules;\n\n    // setup model first\n    // If the list of modules changed, need to recompile the shaders\n    if (changeFlags.extensionsChanged || !modulesEqual(modules, oldModules)) {\n      const {gl} = this.context;\n      this.state.model?.delete();\n      this.state.model = this._getModel(gl);\n      this.getAttributeManager()?.invalidateAll();\n    }\n\n    if (props && props.images) {\n      this.updateImages({props, oldProps});\n    }\n\n    const attributeManager = this.getAttributeManager();\n\n    if (props.bounds !== oldProps.bounds) {\n      const oldMesh = this.state.mesh;\n      const mesh = this._createMesh();\n      this.state.model?.setVertexCount(mesh.vertexCount);\n      for (const key in mesh) {\n        if (oldMesh && oldMesh[key] !== mesh[key]) {\n          attributeManager?.invalidate(key);\n        }\n      }\n      this.setState({mesh, ...this._getCoordinateUniforms()});\n    } else if (props._imageCoordinateSystem !== oldProps._imageCoordinateSystem) {\n      this.setState(this._getCoordinateUniforms());\n    }\n  }\n\n  updateImages({\n    props,\n    oldProps\n  }: {\n    props: RasterLayerAddedProps;\n    oldProps: RasterLayerAddedProps;\n  }): void {\n    const {images} = this.state;\n    const {gl} = this.context;\n\n    const newImages = loadImages({\n      gl,\n      images,\n      imagesData: props.images,\n      oldImagesData: oldProps.images\n    });\n    if (newImages) {\n      this.setState({images: newImages});\n    }\n  }\n\n  finalizeState(): void {\n    super.finalizeState(this.context);\n\n    if (this.state.images) {\n      for (const image of Object.values(this.state.images)) {\n        if (Array.isArray(image)) {\n          image.map(x => x && x.delete());\n        } else if (image) {\n          image.delete();\n        }\n      }\n    }\n  }\n}\n\nRasterLayer.defaultProps = defaultProps;\nRasterLayer.layerName = 'RasterLayer';\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/matrix.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {COORDINATE_SYSTEM, Viewport, CoordinateSystem} from '@deck.gl/core/typed';\n\n// only apply composeModelMatrix when in cartesian or meter_offsets coordinate system\n// with `composeModelMatrix` enabled, the rotation part of the layer's modelMatrix will be composed to instance's transformations\n// since rotating latitude and longitude can not provide meaningful results, hence `composeModelMatrix` is disabled\n// when in LNGLAT and LNGLAT_OFFSET coordinates.\nexport function shouldComposeModelMatrix(\n  viewport: Viewport,\n  coordinateSystem: CoordinateSystem\n): boolean {\n  return (\n    coordinateSystem === COORDINATE_SYSTEM.CARTESIAN ||\n    coordinateSystem === COORDINATE_SYSTEM.METER_OFFSETS ||\n    (coordinateSystem === COORDINATE_SYSTEM.DEFAULT && !viewport.isGeospatial)\n  );\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/raster-mesh-layer-webgl1.fs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#define SHADER_NAME raster-mesh-layer-fs\n\nprecision highp float;\n\nuniform bool hasTexture;\n\nuniform bool flatShading;\nuniform float opacity;\n\nvarying vec2 vTexCoord;\nvarying vec3 cameraPosition;\nvarying vec3 normals_commonspace;\nvarying vec4 position_commonspace;\nvarying vec4 vColor;\n\nvoid main(void) {\n  geometry.uv = vTexCoord;\n  vec4 image;\n  DECKGL_CREATE_COLOR(image, vTexCoord);\n\n  DECKGL_MUTATE_COLOR(image, vTexCoord);\n\n  vec3 normal;\n  if (flatShading) {\n\n// This is necessary because\n// headless.gl reports the extension as\n// available but does not support it in\n// the shader.\n#ifdef DERIVATIVES_AVAILABLE\n    normal = normalize(cross(dFdx(position_commonspace.xyz), dFdy(position_commonspace.xyz)));\n#else\n    normal = vec3(0.0, 0.0, 1.0);\n#endif\n  } else {\n    normal = normals_commonspace;\n  }\n\n  vec3 lightColor = lighting_getLightColor(image.rgb, cameraPosition, position_commonspace.xyz, normal);\n  gl_FragColor = vec4(lightColor, opacity);\n\n  DECKGL_FILTER_COLOR(gl_FragColor, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/raster-mesh-layer-webgl1.vs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#define SHADER_NAME raster-mesh-layer-vs\n\n// Scale the model\nuniform float sizeScale;\nuniform bool composeModelMatrix;\n\n// Primitive attributes\nattribute vec3 positions;\nattribute vec3 normals;\nattribute vec3 colors;\nattribute vec2 texCoords;\n\n// Instance attributes\nattribute vec3 instancePositions;\nattribute vec3 instancePositions64Low;\nattribute vec4 instanceColors;\nattribute vec3 instancePickingColors;\nattribute mat3 instanceModelMatrix;\nattribute vec3 instanceTranslation;\n\n// Outputs to fragment shader\nvarying vec2 vTexCoord;\nvarying vec3 cameraPosition;\nvarying vec3 normals_commonspace;\nvarying vec4 position_commonspace;\nvarying vec4 vColor;\n\nvoid main(void) {\n  geometry.worldPosition = instancePositions;\n  geometry.uv = texCoords;\n  geometry.pickingColor = instancePickingColors;\n\n  vTexCoord = texCoords;\n  cameraPosition = project_uCameraPosition;\n  normals_commonspace = project_normal(instanceModelMatrix * normals);\n  vColor = vec4(colors * instanceColors.rgb, instanceColors.a);\n  geometry.normal = normals_commonspace;\n\n  vec3 pos = (instanceModelMatrix * positions) * sizeScale + instanceTranslation;\n\n  if (composeModelMatrix) {\n    DECKGL_FILTER_SIZE(pos, geometry);\n    gl_Position = project_position_to_clipspace(pos + instancePositions, instancePositions64Low, vec3(0.0), position_commonspace);\n  }\n  else {\n    pos = project_size(pos);\n    DECKGL_FILTER_SIZE(pos, geometry);\n    gl_Position = project_position_to_clipspace(instancePositions, instancePositions64Low, pos, position_commonspace);\n  }\n\n  geometry.position = position_commonspace;\n  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);\n\n  DECKGL_FILTER_COLOR(vColor, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/raster-mesh-layer-webgl2.fs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#version 300 es\n#define SHADER_NAME raster-mesh-layer-fs\n\nprecision highp float;\n\nuniform bool hasTexture;\n\nuniform bool flatShading;\nuniform float opacity;\n\nin vec2 vTexCoord;\nin vec3 cameraPosition;\nin vec3 normals_commonspace;\nin vec4 position_commonspace;\nin vec4 vColor;\n\nout vec4 fragColor;\n\nvoid main(void) {\n  geometry.uv = vTexCoord;\n  vec4 image;\n  DECKGL_CREATE_COLOR(image, vTexCoord);\n\n  DECKGL_MUTATE_COLOR(image, vTexCoord);\n\n  vec3 normal;\n  if (flatShading) {\n\n// This is necessary because\n// headless.gl reports the extension as\n// available but does not support it in\n// the shader.\n#ifdef DERIVATIVES_AVAILABLE\n    normal = normalize(cross(dFdx(position_commonspace.xyz), dFdy(position_commonspace.xyz)));\n#else\n    normal = vec3(0.0, 0.0, 1.0);\n#endif\n  } else {\n    normal = normals_commonspace;\n  }\n\n  vec3 lightColor = lighting_getLightColor(image.rgb, cameraPosition, position_commonspace.xyz, normal);\n  fragColor = vec4(lightColor, opacity);\n\n  DECKGL_FILTER_COLOR(fragColor, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/raster-mesh-layer-webgl2.vs.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `\\\n#version 300 es\n#define SHADER_NAME raster-mesh-layer-vs\n\n// Primitive attributes\nin vec3 positions;\nin vec3 positions64Low;\nin vec3 normals;\nin vec3 colors;\nin vec2 texCoords;\n\n// Outputs to fragment shader\nout vec2 vTexCoord;\nout vec3 cameraPosition;\nout vec3 normals_commonspace;\nout vec4 position_commonspace;\nout vec4 vColor;\n\nconst vec3 pickingColor = vec3(1.0, 0.0, 0.0);\nconst vec3 defaultNormal = vec3(0.0, 0.0, 1.0);\n\nvoid main(void) {\n  geometry.worldPosition = positions;\n  geometry.uv = texCoords;\n  geometry.pickingColor = pickingColor;\n\n  gl_Position = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position);\n  position_commonspace = geometry.position;\n  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);\n\n  vTexCoord = texCoords;\n  cameraPosition = project_uCameraPosition;\n  \n  vColor = vec4(colors, 1.0);\n  DECKGL_FILTER_COLOR(vColor, geometry);\n}\n`;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/raster-mesh-layer/raster-mesh-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {project32, phongLighting, log, UpdateParameters} from '@deck.gl/core/typed';\nimport {SimpleMeshLayer, SimpleMeshLayerProps} from '@deck.gl/mesh-layers/typed';\nimport GL from '@luma.gl/constants';\nimport {Model, Geometry, isWebGL2} from '@luma.gl/core';\nimport {ProgramManager} from '@luma.gl/engine';\nimport {UniformsOptions} from '@luma.gl/webgl/src/classes/uniforms';\n\nimport fsWebGL1 from './raster-mesh-layer-webgl1.fs';\nimport vsWebGL1 from './raster-mesh-layer-webgl1.vs';\nimport fsWebGL2 from './raster-mesh-layer-webgl2.fs';\nimport vsWebGL2 from './raster-mesh-layer-webgl2.vs';\nimport {loadImages} from '../images';\nimport type {RasterLayerAddedProps, ImageState} from '../types';\nimport {modulesEqual} from '../util';\n\ntype Mesh = SimpleMeshLayerProps['mesh'];\n\nfunction validateGeometryAttributes(attributes) {\n  log.assert(\n    attributes.positions || attributes.POSITION,\n    'RasterMeshLayer requires \"postions\" or \"POSITION\" attribute in mesh property.'\n  );\n}\n\n/*\n * Convert mesh data into geometry\n * @returns geometry\n */\nfunction getGeometry(data): Geometry {\n  if (data.attributes) {\n    validateGeometryAttributes(data.attributes);\n    if (data instanceof Geometry) {\n      return data;\n    }\n    return new Geometry(data);\n  } else if (data.positions || data.POSITION) {\n    validateGeometryAttributes(data);\n    return new Geometry({\n      attributes: data\n    });\n  }\n  throw Error('Invalid mesh');\n}\n\nconst defaultProps = {\n  ...SimpleMeshLayer.defaultProps,\n  modules: {type: 'array', value: [], compare: true},\n  images: {type: 'object', value: {}, compare: true},\n  moduleProps: {type: 'object', value: {}, compare: true}\n};\n\nexport default class RasterMeshLayer extends SimpleMeshLayer<any, RasterLayerAddedProps> {\n  declare state: SimpleMeshLayer<RasterLayerAddedProps>['state'] & {\n    images: ImageState;\n  };\n\n  initializeState(): void {\n    const {gl} = this.context;\n    const programManager = ProgramManager.getDefaultProgramManager(gl);\n\n    const fsStr1 = 'fs:DECKGL_MUTATE_COLOR(inout vec4 image, in vec2 coord)';\n    const fsStr2 = 'fs:DECKGL_CREATE_COLOR(inout vec4 image, in vec2 coord)';\n\n    // Only initialize shader hook functions _once globally_\n    // Since the program manager is shared across all layers, but many layers\n    // might be created, this solves the performance issue of always adding new\n    // hook functions.\n    if (!programManager._hookFunctions.includes(fsStr1)) {\n      programManager.addShaderHook(fsStr1);\n    }\n    if (!programManager._hookFunctions.includes(fsStr2)) {\n      programManager.addShaderHook(fsStr2);\n    }\n\n    // images is a mapping from keys to Texture2D objects. The keys should match\n    // names of uniforms in shader modules\n    this.setState({images: {}});\n\n    super.initializeState();\n  }\n\n  getShaders(): any {\n    const {gl} = this.context;\n    const {modules = []} = this.props;\n    const webgl2 = isWebGL2(gl);\n\n    // Choose webgl version for module\n    // If fs2 or fs1 keys exist, prefer them, but fall back to fs, so that\n    // version-independent modules don't need to care\n    for (const module of modules) {\n      module.fs = webgl2 ? module.fs2 || module.fs : module.fs1 || module.fs;\n\n      // Sampler type is always float for WebGL1\n      if (!webgl2 && module.defines) {\n        module.defines.SAMPLER_TYPE = 'sampler2D';\n      }\n    }\n\n    return {\n      ...super.getShaders(),\n      vs: webgl2 ? vsWebGL2 : vsWebGL1,\n      fs: webgl2 ? fsWebGL2 : fsWebGL1,\n      modules: [project32, phongLighting, ...modules]\n    };\n  }\n\n  // eslint-disable-next-line complexity\n  updateState(params: UpdateParameters<SimpleMeshLayer<any, RasterLayerAddedProps>>): void {\n    const {props, oldProps, changeFlags, context} = params;\n    super.updateState({props, oldProps, changeFlags, context});\n\n    const modules = props && props.modules;\n    const oldModules = oldProps && oldProps.modules;\n\n    // If the list of modules changed, need to recompile the shaders\n    if (\n      props.mesh !== oldProps.mesh ||\n      changeFlags.extensionsChanged ||\n      !modulesEqual(modules, oldModules)\n    ) {\n      if (this.state.model) {\n        this.state.model.delete();\n      }\n      if (props.mesh) {\n        this.state.model = this.getModel(props.mesh as Mesh);\n\n        const attributes = (props.mesh as any).attributes || props.mesh;\n        this.setState({\n          hasNormals: Boolean(attributes.NORMAL || attributes.normals)\n        });\n      }\n      this.getAttributeManager()?.invalidateAll();\n    }\n\n    if (props && props.images) {\n      this.updateImages({props, oldProps});\n    }\n\n    if (this.state.model) {\n      this.state.model.setDrawMode(this.props.wireframe ? GL.LINE_STRIP : GL.TRIANGLES);\n    }\n  }\n\n  updateImages({\n    props,\n    oldProps\n  }: {\n    props: RasterLayerAddedProps;\n    oldProps: RasterLayerAddedProps;\n  }): void {\n    const {images} = this.state;\n    const {gl} = this.context;\n\n    const newImages = loadImages({\n      gl,\n      images,\n      imagesData: props.images,\n      oldImagesData: oldProps.images\n    });\n\n    if (newImages) {\n      this.setState({images: newImages});\n    }\n  }\n\n  draw({uniforms}: UniformsOptions): void {\n    const {model, images} = this.state;\n    const {moduleProps} = this.props;\n\n    // Render the image\n    if (\n      !model ||\n      !images ||\n      Object.keys(images).length === 0 ||\n      !Object.values(images).every(item => item)\n    ) {\n      return;\n    }\n\n    const {sizeScale} = this.props;\n\n    model\n      .setUniforms(\n        Object.assign({}, uniforms, {\n          sizeScale,\n          flatShading: !this.state.hasNormals\n        })\n      )\n      .updateModuleSettings({\n        ...moduleProps,\n        ...images\n      })\n      .draw();\n  }\n\n  finalizeState(): void {\n    super.finalizeState(this.context);\n\n    if (this.state.images) {\n      for (const image of Object.values(this.state.images)) {\n        if (Array.isArray(image)) {\n          image.map(x => x && x.delete());\n        } else if (image) {\n          image.delete();\n        }\n      }\n    }\n  }\n\n  protected getModel(mesh: Mesh): Model {\n    const {gl} = this.context;\n\n    const model = new Model(\n      gl,\n      Object.assign({}, this.getShaders(), {\n        id: this.props.id,\n        geometry: getGeometry(mesh),\n        isInstanced: false\n      })\n    );\n\n    return model;\n  }\n}\n\nRasterMeshLayer.layerName = 'RasterMeshLayer';\nRasterMeshLayer.defaultProps = defaultProps;\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport type {Texture2DProps, Texture2D} from '@luma.gl/webgl';\n\nimport type {ShaderModule} from './webgl';\n\n/** Allowed input for images prop\n * Texture2D is already on the GPU, while Texture2DProps can be data on the CPU that is not yet copied to the GPU.\n */\nexport type ImageInput = Record<\n  string,\n  Texture2DProps | Texture2D | (Texture2DProps | Texture2D)[]\n>;\n\n/** Internal storage of images\n * The Texture2D object references data on the GPU\n */\nexport type ImageState = Record<string, Texture2D | Texture2D[]>;\n\n/** Properties added by RasterLayer. */\nexport type RasterLayerAddedProps = {\n  modules: ShaderModule[];\n  images: ImageInput;\n  moduleProps: Record<string, number>;\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/util.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule} from './webgl/types';\n\n/**\n * Test if two lists of modules are equal\n *\n * @param modules     Modules list\n * @param oldModules  Modules list\n *\n * @return true if both lists are equal\n */\nexport function modulesEqual(modules: ShaderModule[], oldModules: ShaderModule[]): boolean {\n  if (modules.length !== oldModules.length) {\n    return false;\n  }\n\n  for (let i = 0; i < modules.length; i++) {\n    if (modules[i].name !== oldModules[i].name) {\n      return false;\n    }\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/colormap.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nconst fs = `\\\nuniform sampler2D uColormapTexture;\nuniform int uHasCategoricalColors;\nuniform int uCategoricalMinValue;\nuniform int uCategoricalMaxValue;\nuniform int uMaxPixelValue;\n\n// Apply colormap texture given value\n// Since the texture only varies in the x direction, setting v to 0.5 as a\n// constant is fine\n// Assumes the input range of value is -1 to 1\nvec4 colormap(sampler2D cmap, vec4 image) {\n  vec2 uv;\n  if (uHasCategoricalColors == 1) {\n    float step = float(uMaxPixelValue) / float(uCategoricalMaxValue - uCategoricalMinValue);\n    uv = vec2(image.r * step, 0.5);\n  } else {\n    uv = vec2(0.5 * image.r + 0.5, 0.5);\n  }\n  vec4 color = texture2D(cmap, uv);\n  if(color.a <= 0.0) discard;\n  return color;\n}\n`;\n\nfunction getUniforms(\n  opts: {\n    imageColormap?: Texture2D;\n    minCategoricalBandValue?: number;\n    maxCategoricalBandValue?: number;\n    dataTypeMaxValue?: number;\n    maxPixelValue?: number;\n  } = {}\n): GetUniformsOutput {\n  const {\n    imageColormap,\n    minCategoricalBandValue,\n    maxCategoricalBandValue,\n    dataTypeMaxValue,\n    maxPixelValue\n  } = opts;\n\n  if (!imageColormap) {\n    return null;\n  }\n\n  const isSupportedDataType = Number.isFinite(dataTypeMaxValue);\n  const isCategorical =\n    isSupportedDataType &&\n    Number.isFinite(maxPixelValue) &&\n    Number.isFinite(minCategoricalBandValue) &&\n    Number.isFinite(maxCategoricalBandValue);\n  return {\n    uColormapTexture: imageColormap,\n    uHasCategoricalColors: isCategorical ? 1 : 0,\n    uCategoricalMinValue: Number.isFinite(minCategoricalBandValue) ? minCategoricalBandValue : 0,\n    uCategoricalMaxValue: Number.isFinite(maxCategoricalBandValue) ? maxCategoricalBandValue : 0,\n    uMaxPixelValue: Number.isFinite(maxPixelValue) ? maxPixelValue : 0\n  };\n}\n\nexport const colormap: ShaderModule = {\n  name: 'colormap',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = colormap(uColormapTexture, image);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/filter.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nconst fs = `\\\nuniform float filterMin1;\nuniform float filterMax1;\nuniform float filterMin2;\nuniform float filterMax2;\nuniform float filterMin3;\nuniform float filterMax3;\nuniform float filterMin4;\nuniform float filterMax4;\n`;\n\n// You can't pass JS' -Infinity or Infinity to a shader as a uniform\nconst inf = Math.pow(2, 62);\n\n// eslint-disable-next-line complexity\nfunction getUniforms(\n  opts: {\n    filterMin1?: number;\n    filterMin2?: number;\n    filterMin3?: number;\n    filterMin4?: number;\n    filterMax1?: number;\n    filterMax2?: number;\n    filterMax3?: number;\n    filterMax4?: number;\n  } = {}\n): GetUniformsOutput {\n  const {\n    filterMin1,\n    filterMin2,\n    filterMin3,\n    filterMin4,\n    filterMax1,\n    filterMax2,\n    filterMax3,\n    filterMax4\n  } = opts;\n\n  if (\n    Number.isFinite(filterMin1) ||\n    Number.isFinite(filterMin2) ||\n    Number.isFinite(filterMin3) ||\n    Number.isFinite(filterMin4) ||\n    Number.isFinite(filterMax1) ||\n    Number.isFinite(filterMax2) ||\n    Number.isFinite(filterMax3) ||\n    Number.isFinite(filterMax4)\n  ) {\n    return {\n      filterMin1: Number.isFinite(filterMin1) ? filterMin1 : -inf,\n      filterMin2: Number.isFinite(filterMin2) ? filterMin2 : -inf,\n      filterMin3: Number.isFinite(filterMin3) ? filterMin3 : -inf,\n      filterMin4: Number.isFinite(filterMin4) ? filterMin4 : -inf,\n      filterMax1: Number.isFinite(filterMax1) ? filterMax1 : inf,\n      filterMax2: Number.isFinite(filterMax2) ? filterMax2 : inf,\n      filterMax3: Number.isFinite(filterMax3) ? filterMax3 : inf,\n      filterMax4: Number.isFinite(filterMax4) ? filterMax4 : inf\n    };\n  }\n\n  return null;\n}\n\nexport const filter: ShaderModule = {\n  name: 'filter',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    if (image.r < filterMin1) discard;\n    if (image.g < filterMin2) discard;\n    if (image.b < filterMin3) discard;\n    if (image.a < filterMin4) discard;\n    if (image.r > filterMax1) discard;\n    if (image.g > filterMax2) discard;\n    if (image.b > filterMax3) discard;\n    if (image.a > filterMax4) discard;\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/gamma-contrast.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\n// Gamma correction is a nonlinear operation that\n// adjusts the image's channel values pixel-by-pixel according\n// to a power-law:\n//\n// .. math:: pixel_{out} = pixel_{in} ^ {gamma}\n//\n// Setting gamma (:math:gamma) to be less than 1.0 darkens the image and\n// setting gamma to be greater than 1.0 lightens it.\n\n// Parameters\n// ----------\n// gamma (:math:gamma): float\n//     Reasonable values range from 0.8 to 2.4.\n\n// NOTE: Input array must have float values between 0 and 1!\n// NOTE: gamma must be >= 0\nconst fs = `\\\n#define epsilon 0.00000001\n\nuniform float gamma1;\nuniform float gamma2;\nuniform float gamma3;\nuniform float gamma4;\n\nfloat gammaContrast(float arr, float g) {\n  // Gamma must be > 0\n  g = clamp(g, epsilon, g);\n\n  return pow(arr, 1.0 / g);\n}\n\nvec4 gammaContrast(vec4 arr, float g1, float g2, float g3, float g4) {\n  arr.r = gammaContrast(arr.r, g1);\n  arr.g = gammaContrast(arr.g, g2);\n  arr.b = gammaContrast(arr.b, g3);\n  arr.a = gammaContrast(arr.a, g4);\n\n  return arr;\n}\n`;\n\nfunction getUniforms(\n  opts: {\n    gammaContrastValue?: number;\n    gammaContrastValue1?: number;\n    gammaContrastValue2?: number;\n    gammaContrastValue3?: number;\n    gammaContrastValue4?: number;\n  } = {}\n): GetUniformsOutput {\n  const {\n    gammaContrastValue,\n    gammaContrastValue1,\n    gammaContrastValue2,\n    gammaContrastValue3,\n    gammaContrastValue4\n  } = opts;\n\n  // Gamma must be > 0, so not using Number.isFinite is fine\n\n  if (gammaContrastValue) {\n    return {\n      gamma1: gammaContrastValue,\n      gamma2: gammaContrastValue,\n      gamma3: gammaContrastValue,\n      gamma4: gammaContrastValue\n    };\n  } else if (\n    gammaContrastValue1 ||\n    gammaContrastValue2 ||\n    gammaContrastValue3 ||\n    gammaContrastValue4\n  ) {\n    return {\n      gamma1: gammaContrastValue1 || 1,\n      gamma2: gammaContrastValue2 || 1,\n      gamma3: gammaContrastValue3 || 1,\n      gamma4: gammaContrastValue4 || 1\n    };\n  }\n\n  return null;\n}\n\nexport const gammaContrast: ShaderModule = {\n  name: 'gamma_contrast',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = gammaContrast(image, gamma1, gamma2, gamma3, gamma4);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/linear-rescale.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nconst fs = `\\\nuniform float linearRescaleScaler;\nuniform float linearRescaleOffset;\n\n// Perform a linear rescaling of image\nvec4 linear_rescale(vec4 arr, float scaler, float offset) {\n  return arr * scaler + offset;\n}\n`;\n\nfunction getUniforms(\n  opts: {linearRescaleScaler?: number; linearRescaleOffset?: number} = {}\n): GetUniformsOutput {\n  const {linearRescaleScaler, linearRescaleOffset} = opts;\n\n  if (!Number.isFinite(linearRescaleScaler) && !Number.isFinite(linearRescaleOffset)) {\n    return null;\n  }\n\n  return {\n    linearRescaleScaler: Number.isFinite(linearRescaleScaler) ? linearRescaleScaler : 1,\n    linearRescaleOffset: Number.isFinite(linearRescaleOffset) ? linearRescaleOffset : 0\n  };\n}\n\nexport const linearRescale: ShaderModule = {\n  name: 'linear_rescale',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = linear_rescale(image, linearRescaleScaler, linearRescaleOffset);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/saturation.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\n/**\n * Adjusts the saturation of a color.\n * From cesium:\n * https://github.com/CesiumGS/cesium/blob/master/Source/Shaders/Builtin/Functions/saturation.glsl\n *\n * @param {vec3} rgb The color.\n * @param {float} adjustment The amount to adjust the saturation of the color. Usually between 0 and 2.\n *\n * @returns {vec3} The color with the saturation adjusted.\n */\nconst fs = `\\\nuniform float uSaturationValue;\nvec3 saturate(vec3 rgb, float adjustment) {\n    // Algorithm from Chapter 16 of OpenGL Shading Language\n    const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n    vec3 intensity = vec3(dot(rgb, W));\n    return mix(intensity, rgb, adjustment);\n}\n`;\n\nfunction getUniforms(opts: {saturationValue?: number} = {}): GetUniformsOutput {\n  const {saturationValue} = opts;\n\n  if (!saturationValue) {\n    return null;\n  }\n\n  return {\n    uSaturationValue: Number.isFinite(saturationValue) ? saturationValue : 1\n  };\n}\n\nexport const saturation: ShaderModule = {\n  name: 'saturation',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = vec4(saturate(image.rgb, uSaturationValue), image.a);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/color/sigmoidal-contrast.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\n// From mapbox/rio-color under the MIT License\n//\n// Sigmoidal contrast is a type of contrast control that\n// adjusts the contrast without saturating highlights or shadows.\n// It allows control over two factors:\n// the contrast range from light to dark, and where the middle value\n// of the mid-tones falls. The result is a non-linear and smooth\n// contrast change.\n// Parameters\n// ----------\n// arr : ndarray, float, 0 .. 1\n//     Array of color values to adjust\n// contrast : integer\n//     Enhances the intensity differences between the lighter and darker\n//     elements of the image. For example, 0 is none, 3 is typical and\n//     20 is a lot.\n// bias : float, between 0 and 1\n//     Threshold level for the contrast function to center on\n//     (typically centered at 0.5)\n// Notes\n// ----------\n// Sigmoidal contrast is based on the sigmoidal transfer function:\n// .. math:: g(u) = ( 1/(1 + e^{- \\alpha * u + \\beta)})\n// This sigmoid function is scaled so that the output is bound by\n// the interval [0, 1].\n// .. math:: ( 1/(1 + e^(\\beta * (\\alpha - u))) - 1/(1 + e^(\\beta * \\alpha)))/\n//     ( 1/(1 + e^(\\beta*(\\alpha - 1))) - 1/(1 + e^(\\beta * \\alpha)) )\n// Where :math: `\\alpha` is the threshold level, and :math: `\\beta` the\n// contrast factor to be applied.\n// References\n// ----------\n// .. [CT] Hany Farid \"Fundamentals of Image Processing\"\n//         http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf\nconst fs = `\\\n#define epsilon 0.00000001\n\nuniform float sigmoidalContrast;\nuniform float sigmoidalBias;\n\n// NOTE: Input array must have float values between 0 and 1!\n// NOTE: bias must be a scalar float between 0 and 1!\nvec4 calculateSigmoidalContrast(vec4 arr, float contrast, float bias) {\n  // We use the names alpha and beta to match documentation.\n  float alpha = bias;\n  float beta = contrast;\n\n  // alpha must be >= 0\n  alpha = clamp(alpha, epsilon, alpha);\n\n  if (beta > 0.) {\n    vec4 numerator = 1. / (1. + exp(beta * (alpha - arr))) - 1. / (\n      1. + exp(beta * alpha)\n    );\n    float denominator = 1. / (1. + exp(beta * (alpha - 1.))) - 1. / (\n      1. + exp(beta * alpha)\n    );\n    arr = numerator / denominator;\n  } else if (beta < 0.) {\n    arr = (\n      (beta * alpha) - log(\n        (\n          1.0 / (\n            (arr / (1.0 + exp((beta * alpha) - beta))) -\n            (arr / (1.0 + exp(beta * alpha))) +\n            (1.0 / (1.0 + exp(beta * alpha)))\n          )\n        ) - 1.0)\n    ) / beta;\n  }\n\n  return arr;\n}\n`;\n\nfunction getUniforms(\n  opts: {sigmoidalContrast?: number; sigmoidalBias?: number} = {}\n): GetUniformsOutput {\n  const {sigmoidalContrast, sigmoidalBias} = opts;\n\n  if (!(Number.isFinite(sigmoidalContrast) || Number.isFinite(sigmoidalBias))) {\n    return null;\n  }\n\n  return {\n    sigmoidalContrast: Number.isFinite(sigmoidalContrast) ? sigmoidalContrast : 0,\n    sigmoidalBias: Number.isFinite(sigmoidalBias) ? sigmoidalBias : 0.5\n  };\n}\n\nexport const sigmoidalContrast: ShaderModule = {\n  name: 'sigmoidalContrast',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = calculateSigmoidalContrast(image, sigmoidalContrast, sigmoidalBias);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Create texture\nexport {combineBandsFloat, combineBandsUint, combineBandsInt} from './texture/combine-bands';\nexport {rgbaImage} from './texture/rgba-image';\nexport {maskFloat, maskUint, maskInt} from './texture/mask';\nexport {reorderBands} from './texture/reorder-bands';\n\n// Color operations\nexport {colormap} from './color/colormap';\nexport {linearRescale} from './color/linear-rescale';\nexport {sigmoidalContrast} from './color/sigmoidal-contrast';\nexport {gammaContrast} from './color/gamma-contrast';\nexport {saturation} from './color/saturation';\nexport {filter} from './color/filter';\n\n// Pansharpening\nexport {pansharpenBrovey} from './pansharpen/pansharpen-brovey';\n\n// Spectral indices\nexport {enhancedVegetationIndex} from './spectral-indices/evi';\nexport {modifiedSoilAdjustedVegetationIndex} from './spectral-indices/msavi';\nexport {normalizedDifference} from './spectral-indices/normalized-difference';\nexport {soilAdjustedVegetationIndex} from './spectral-indices/savi';\n\nexport type {ShaderModule} from './types';\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/pansharpen/pansharpen-brovey.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\n// Brovey Method: Each resampled, multispectral pixel is\n// multiplied by the ratio of the corresponding\n// panchromatic pixel intensity to the sum of all the\n// multispectral intensities.\n// Original code from https://github.com/mapbox/rio-pansharpen\n//\nconst fs1 = `\\\nuniform sampler2D bitmapTexturePan;\nuniform float panWeight;\n\nfloat pansharpen_brovey_ratio(vec4 rgb, float pan, float weight) {\n  return pan / ((rgb.r + rgb.g + rgb.b * weight) / (2. + weight));\n}\n\nvec4 pansharpen_brovey_calc(vec4 rgb, float pan, float weight) {\n  float ratio = pansharpen_brovey_ratio(rgb, pan, weight);\n  return ratio * rgb;\n}\n`;\n\nconst fs2 = `\\\nprecision mediump usampler2D;\n\n#ifdef SAMPLER_TYPE\n  uniform SAMPLER_TYPE bitmapTexturePan;\n#else\n  uniform sampler2D bitmapTexturePan;\n#endif\n\nuniform float panWeight;\n\nfloat pansharpen_brovey_ratio(vec4 rgb, float pan, float weight) {\n  return pan / ((rgb.r + rgb.g + rgb.b * weight) / (2. + weight));\n}\n\nvec4 pansharpen_brovey_calc(vec4 rgb, float pan, float weight) {\n  float ratio = pansharpen_brovey_ratio(rgb, pan, weight);\n  return ratio * rgb;\n}\n`;\n\nfunction getUniforms(opts: {imagePan?: Texture2D; panWeight?: number} = {}): GetUniformsOutput {\n  const {imagePan, panWeight = 0.2} = opts;\n\n  if (!imagePan) {\n    return null;\n  }\n\n  return {\n    bitmapTexturePan: imagePan,\n    panWeight\n  };\n}\n\nexport const pansharpenBrovey: ShaderModule = {\n  name: 'pansharpen_brovey',\n  fs1,\n  fs2,\n  defines: {\n    SAMPLER_TYPE: 'sampler2D'\n  },\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    float pan_band = float(texture2D(bitmapTexturePan, coord).r);\n    image = pansharpen_brovey_calc(image, pan_band, panWeight);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/spectral-indices/evi.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule} from '../types';\n\n// Calculate enhanced vegetation index\n// Expected to be ordered:\n// red: Landsat 8 band 5\n// green: Landsat 8 band 4\n// blue: Landsat 8 band 2\n//\n// EVI = 2.5 * ((Band 5 – Band 4) / (Band 5 + 6 * Band 4 – 7.5 * Band 2 + 1))\n// https://www.usgs.gov/land-resources/nli/landsat/landsat-enhanced-vegetation-index\nconst fs = `\\\nfloat enhanced_vegetation_index_calc(vec4 image) {\n  float band5 = image.r;\n  float band4 = image.g;\n  float band2 = image.b;\n\n  float numerator = band5 - band4;\n  float denominator = band5 + (6. * band4) - (7.5 * band2) + 1.;\n  return 2.5 * (numerator / denominator);\n}\n`;\n\nexport const enhancedVegetationIndex: ShaderModule = {\n  name: 'enhanced_vegetation_index',\n  fs,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = vec4(enhanced_vegetation_index_calc(image), 0., 0., 0.);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/spectral-indices/msavi.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule} from '../types';\n\n// Calculate modified soil-adjusted vegetation index\n// Expected to be ordered:\n// red: Landsat 8 band 5\n// green: Landsat 8 band 4\n//\n// MSAVI = (2 * Band 5 + 1 – sqrt ((2 * Band 5 + 1)^2 – 8 * (Band 5 – Band 4))) / 2\n// https://www.usgs.gov/land-resources/nli/landsat/landsat-modified-soil-adjusted-vegetation-index\nconst fs = `\\\nfloat modified_soil_adjusted_vegetation_index_calc(vec4 image) {\n  float band5 = image.r;\n  float band4 = image.g;\n\n  float to_sqrt = ((2. * band5 + 1.) * (2. * band5 + 1.)) - (8. * (band5 - band4));\n  return ((2. * band5) + 1. - sqrt(to_sqrt)) / 2.;\n}\n`;\n\nexport const modifiedSoilAdjustedVegetationIndex: ShaderModule = {\n  name: 'modified_soil_adjusted_vegetation_index',\n  fs,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = vec4(modified_soil_adjusted_vegetation_index_calc(image), 0., 0., 0.);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/spectral-indices/normalized-difference.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule} from '../types';\n\n// Calculate standard normalized difference\nconst fs = `\\\nfloat normalized_difference_calc(vec4 image) {\n  return ((image.r - image.g) / (image.r + image.g));\n}\n`;\n\nexport const normalizedDifference: ShaderModule = {\n  name: 'normalized_difference',\n  fs,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = vec4(normalized_difference_calc(image), 0., 0., 0.);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/spectral-indices/savi.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule} from '../types';\n\n// Calculate soil-adjusted vegetation index\n// SAVI = ((Band 5 – Band 4) / (Band 5 + Band 4 + 0.5)) * (1.5).\n// https://www.usgs.gov/land-resources/nli/landsat/landsat-soil-adjusted-vegetation-index\nconst fs = `\\\nfloat soil_adjusted_vegetation_index_calc(vec4 image) {\n  float band5 = image.r;\n  float band4 = image.g;\n\n  float numerator = band5 - band4;\n  float denominator = (band5 + band4 + 0.5) * 1.5;\n  return numerator / denominator;\n}\n`;\n\nexport const soilAdjustedVegetationIndex: ShaderModule = {\n  name: 'soil_adjusted_vegetation_index',\n  fs,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = vec4(soil_adjusted_vegetation_index_calc(image), 0., 0., 0.);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/texture/combine-bands.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nfunction getUniforms(opts: {imageBands?: Texture2D[]} = {}): GetUniformsOutput {\n  const {imageBands} = opts;\n\n  if (!imageBands || imageBands.length === 0) {\n    return null;\n  }\n\n  const [bitmapTextureR, bitmapTextureG, bitmapTextureB, bitmapTextureA] = imageBands;\n\n  // return default values to prevent deck.gl validation warnings.\n  // Note: all bands must be set for the shadow effect to work as expected.\n  return {\n    bitmapTextureR: bitmapTextureR || bitmapTextureR,\n    bitmapTextureG: bitmapTextureG || bitmapTextureR,\n    bitmapTextureB: bitmapTextureB || bitmapTextureR,\n    bitmapTextureA: bitmapTextureA || bitmapTextureR\n  };\n}\n\nconst fs1 = `\\\nuniform sampler2D bitmapTextureR;\nuniform sampler2D bitmapTextureG;\nuniform sampler2D bitmapTextureB;\nuniform sampler2D bitmapTextureA;\n`;\n\nconst fs2 = `\\\nprecision mediump float;\nprecision mediump int;\nprecision mediump usampler2D;\n\n#ifdef SAMPLER_TYPE\n  uniform SAMPLER_TYPE bitmapTextureR;\n  uniform SAMPLER_TYPE bitmapTextureG;\n  uniform SAMPLER_TYPE bitmapTextureB;\n  uniform SAMPLER_TYPE bitmapTextureA;\n#else\n  uniform sampler2D bitmapTextureR;\n  uniform sampler2D bitmapTextureG;\n  uniform sampler2D bitmapTextureB;\n  uniform sampler2D bitmapTextureA;\n#endif\n`;\n\nconst combineBands: ShaderModule = {\n  name: 'combine-bands',\n  fs1,\n  fs2,\n  getUniforms,\n  defines: {\n    SAMPLER_TYPE: 'sampler2D'\n  },\n  inject: {\n    'fs:DECKGL_CREATE_COLOR': `\n    float channel1 = float(texture2D(bitmapTextureR, coord).r);\n    float channel2 = float(texture2D(bitmapTextureG, coord).r);\n    float channel3 = float(texture2D(bitmapTextureB, coord).r);\n    float channel4 = float(texture2D(bitmapTextureA, coord).r);\n\n    image = vec4(channel1, channel2, channel3, channel4);\n    `\n  }\n};\n\nexport const combineBandsFloat: ShaderModule = {\n  ...combineBands,\n  name: 'combine-bands-float'\n};\nexport const combineBandsUint: ShaderModule = {\n  ...combineBands,\n  name: 'combine-bands-uint',\n  defines: {\n    SAMPLER_TYPE: 'usampler2D'\n  }\n};\nexport const combineBandsInt: ShaderModule = {\n  ...combineBands,\n  name: 'combine-bands-int',\n  defines: {\n    SAMPLER_TYPE: 'isampler2D'\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/texture/mask.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nconst inf = Math.pow(2, 62);\n\nfunction getUniforms(\n  opts: {imageMask?: Texture2D; maskKeepMin?: number; maskKeepMax?: number} = {}\n): GetUniformsOutput {\n  const {imageMask, maskKeepMin, maskKeepMax} = opts;\n  if (!imageMask) {\n    return null;\n  }\n\n  return {\n    bitmapTextureMask: imageMask,\n    uMaskKeepMin: Number.isFinite(maskKeepMin) ? maskKeepMin : -inf,\n    uMaskKeepMax: Number.isFinite(maskKeepMax) ? maskKeepMax : inf\n  };\n}\n\nconst fs1 = `\\\nuniform sampler2D bitmapTextureMask;\nuniform float uMaskKeepMin;\nuniform float uMaskKeepMax;\n`;\n\nconst fs2 = `\\\nprecision mediump float;\nprecision mediump int;\nprecision mediump usampler2D;\n\n#ifdef SAMPLER_TYPE\n  uniform SAMPLER_TYPE bitmapTextureMask;\n#else\n  uniform sampler2D bitmapTextureMask;\n#endif\n\nuniform float uMaskKeepMin;\nuniform float uMaskKeepMax;\n`;\n\nconst mask: ShaderModule = {\n  name: 'mask-image',\n  fs1,\n  fs2,\n  getUniforms,\n  defines: {\n    SAMPLER_TYPE: 'sampler2D'\n  },\n  inject: {\n    'fs:DECKGL_CREATE_COLOR': `\n    float mask_value = float(texture2D(bitmapTextureMask, coord).r);\n    if (mask_value < uMaskKeepMin) discard;\n    if (mask_value > uMaskKeepMax) discard;\n    `\n  }\n};\n\nexport const maskFloat: ShaderModule = {\n  ...mask,\n  name: 'mask-image-float'\n};\nexport const maskUint: ShaderModule = {\n  ...mask,\n  name: 'mask-image-uint',\n  defines: {\n    SAMPLER_TYPE: 'usampler2D'\n  }\n};\nexport const maskInt: ShaderModule = {\n  ...mask,\n  name: 'mask-image-int',\n  defines: {\n    SAMPLER_TYPE: 'isampler2D'\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/texture/reorder-bands.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ShaderModule, GetUniformsOutput} from '../types';\n\n/**\n * Reorder image bands on GPU\n * Uses a permutation matrix to reorder a vec4\n */\n\nconst fs = `\\\nuniform mat4 uReorder;\n\nvec4 reorder_image(vec4 image, mat4 ordering) {\n  return image.rgba * ordering;\n}\n`;\n\nfunction getUniforms(opts: {ordering?: number[]} = {}): GetUniformsOutput {\n  const {ordering} = opts;\n\n  if (!ordering) {\n    return null;\n  }\n\n  return {\n    uReorder: constructPermutationMatrix(ordering)\n  };\n}\n\n/**\n * Construct permutation matrix from vector\n *\n * @param vector  Vector describing how to reorder bands\n *\n * @return a mat4 permutation matrix representing how to reorder bands\n */\nexport function constructPermutationMatrix(vector: number[]): number[] {\n  const mat4 = Array(16).fill(0);\n  let row = 0;\n  for (const index of vector) {\n    mat4[row * 4 + index] = 1;\n    row += 1;\n  }\n\n  // If input vector wasn't of length 4, add identity in final places\n  for (let r = row; r < 4; r++) {\n    mat4[r * 4 + r] = 1;\n  }\n\n  return mat4;\n}\n\nexport const reorderBands: ShaderModule = {\n  name: 'reorder-bands',\n  fs,\n  getUniforms,\n  inject: {\n    'fs:DECKGL_MUTATE_COLOR': `\n    image = reorder_image(image, uReorder);\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/texture/rgba-image.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nimport {GetUniformsOutput, ShaderModule} from '../types';\n\nfunction getUniforms(opts: {imageRgba?: Texture2D} = {}): GetUniformsOutput {\n  const {imageRgba} = opts;\n\n  if (!imageRgba) {\n    return null;\n  }\n\n  return {\n    bitmapTextureRgba: imageRgba\n  };\n}\n\nconst fs1 = `\\\nuniform sampler2D bitmapTextureRgba;\n`;\n\nconst fs2 = `\\\nprecision mediump float;\nprecision mediump int;\nprecision mediump usampler2D;\n\n#ifdef SAMPLER_TYPE\n  uniform SAMPLER_TYPE bitmapTextureRgba;\n#else\n  uniform sampler2D bitmapTextureRgba;\n#endif\n`;\n\nexport const rgbaImage: ShaderModule = {\n  name: 'rgba-image',\n  fs1,\n  fs2,\n  getUniforms,\n  defines: {\n    SAMPLER_TYPE: 'sampler2D'\n  },\n  inject: {\n    'fs:DECKGL_CREATE_COLOR': `\n    image = vec4(texture2D(bitmapTextureRgba, coord));\n    if (image.a < 0.5) {\n      discard;\n    }\n    `\n  }\n};\n"
  },
  {
    "path": "src/deckgl-layers/src/raster/webgl/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Texture2D} from '@luma.gl/webgl';\n\nexport interface ShaderModule {\n  /** A unique name for this shader module */\n  name: string;\n\n  /** A fragment shader to be used in both WebGL1 and WebGL2 environments */\n  fs?: string;\n\n  /** A fragment shader to be used only in WebGL1 environments */\n  fs1?: string;\n\n  /** A fragment shader to be used only in WebGL2 environments */\n  fs2?: string;\n\n  /** A vertex shader to inject */\n  vs?: string;\n  uniforms?: Record<string, any>;\n  getUniforms?: (opts: object) => GetUniformsOutput;\n\n  /** Optional constants to define when injecting */\n  defines?: Record<string, string>;\n  inject?: Record<string, string>;\n  dependencies?: ShaderModule[];\n  deprecations?: any[];\n}\n\nexport type UniformType = number | number[] | Texture2D | undefined;\n\nexport type GetUniformsOutput = Record<string, UniformType> | null;\n"
  },
  {
    "path": "src/deckgl-layers/src/svg-icon-layer/scatterplot-icon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ScatterplotLayer, ScatterplotLayerProps} from '@deck.gl/layers';\nimport {Geometry, Model} from '@luma.gl/core';\nimport GL from '@luma.gl/constants';\n\nconst DEFAULT_POS = [-1, -1, 0, -1, 1, 0, 1, 1, 0, 1, -1, 0];\n\nexport interface ScatterplotIconLayerProps extends ScatterplotLayerProps<any> {\n  iconGeometry: number;\n}\n\nexport default class ScatterplotIconLayer extends ScatterplotLayer<any, ScatterplotIconLayerProps> {\n  _getModel(gl: WebGLRenderingContext) {\n    // use default scatterplot shaders\n    const shaders = this.getShaders(undefined);\n\n    const {iconGeometry} = this.props;\n\n    const geometry = iconGeometry\n      ? new Geometry({\n          drawMode: GL.TRIANGLES,\n          attributes: {\n            positions: new Float32Array(iconGeometry)\n          }\n        })\n      : new Geometry({\n          drawMode: GL.TRIANGLE_FAN,\n          attributes: {\n            positions: new Float32Array(DEFAULT_POS)\n          }\n        });\n\n    return new Model(gl, {\n      ...shaders,\n      id: this.props.id,\n      geometry,\n      isInstanced: true,\n      // @ts-ignore\n      shaderCache: this.context.shaderCache\n    });\n  }\n}\n\nScatterplotIconLayer.layerName = 'ScatterplotIconLayer';\n"
  },
  {
    "path": "src/deckgl-layers/src/svg-icon-layer/svg-icon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CompositeLayer, Position} from '@deck.gl/core';\nimport {CompositeLayerProps} from '@deck.gl/core/lib/composite-layer';\n\nimport {RGBColor, RGBAColor} from '@kepler.gl/types';\nimport ScatterplotIconLayer from './scatterplot-icon-layer';\n\n// default icon geometry is a square\nconst DEFAULT_ICON_GEOMETRY = [1, 1, 0, 1, -1, 0, -1, -1, 0, -1, -1, 0, -1, 1, 0, 1, 1, 0];\n\nconst defaultProps = {\n  getIconGeometry: () => DEFAULT_ICON_GEOMETRY,\n  getIcon: (d: {icon: string}) => d.icon\n};\n\nexport interface SvgIconLayerProps extends CompositeLayerProps<any> {\n  getIconGeometry: (i: string) => number[];\n  getIcon: (d: {icon: string}) => string;\n  getPosition: (d: any) => Position;\n  getRadius: ((d: any) => number) | number;\n  getFillColor: RGBColor | RGBAColor;\n}\n\nexport default class SvgIconLayer extends CompositeLayer<any, SvgIconLayerProps> {\n  // Must be defined\n  initializeState() {\n    this.state = {\n      data: {}\n    };\n  }\n\n  updateState({changeFlags}) {\n    if (changeFlags.dataChanged) {\n      this._extractSublayers();\n    }\n  }\n\n  _extractSublayers() {\n    const {data, getIconGeometry, getIcon} = this.props;\n\n    const iconLayers = {};\n    for (let i = 0; i < data.length; i++) {\n      const iconId = getIcon(data[i]);\n      iconLayers[iconId] = iconLayers[iconId] || {\n        id: iconId,\n        geometry: getIconGeometry(iconId) || DEFAULT_ICON_GEOMETRY,\n        data: []\n      };\n      iconLayers[iconId].data.push(data[i]);\n    }\n    this.setState({\n      data: Object.values(iconLayers)\n    });\n  }\n\n  _updateAutoHighlight(info) {\n    info?.sourceLayer?.updateAutoHighlight(info);\n  }\n\n  renderLayers() {\n    const layerId = this.props.id;\n\n    const layers =\n      this.state.data &&\n      this.state.data.length &&\n      this.state.data.map(\n        ({id, data, geometry}) =>\n          new ScatterplotIconLayer({\n            ...this.props,\n            id: `${layerId}-${id}`,\n            data,\n            iconGeometry: geometry\n          })\n      );\n\n    return layers && layers.length > 0 ? layers : null;\n  }\n}\n\nSvgIconLayer.layerName = 'SvgIconLayer';\nSvgIconLayer.defaultProps = defaultProps;\n"
  },
  {
    "path": "src/deckgl-layers/src/typedefs/deckgl.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Eslint does not seem to be able to understand the namespace re-export here\n/* eslint-disable */\n\nimport * as DeckTypings from '@danmarshall/deckgl-typings';\n\ndeclare module 'deck.gl' {\n  export namespace DeckTypings {}\n}\n"
  },
  {
    "path": "src/deckgl-layers/src/wms/wms-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {lngLatToWorld} from '@math.gl/web-mercator';\n\nimport {\n  Layer,\n  CompositeLayer,\n  CompositeLayerProps,\n  UpdateParameters,\n  DefaultProps,\n  Viewport,\n  COORDINATE_SYSTEM,\n  _deepEqual as deepEqual\n} from '@deck.gl/core/typed';\nimport {BitmapLayer} from '@deck.gl/layers/typed';\nimport {ImageSource, createImageSource} from '@loaders.gl/wms';\n\nimport type {ImageSourceMetadata} from '@loaders.gl/loader-utils';\nimport type {ImageType, ImageServiceType} from '@loaders.gl/wms';\n\n// TODO: This is a modified copy of WMSLayer from deck.gl. Remove this once we upgrade deck.gl and loaders.gl.\n\n/** All props supported by the TileLayer */\nexport type WMSLayerProps = CompositeLayerProps & _WMSLayerProps;\n\n/** Props added by the TileLayer */\ntype _WMSLayerProps = {\n  data: string | ImageSource;\n  serviceType?: ImageServiceType | 'auto';\n  layers?: string[];\n  srs?: 'EPSG:4326' | 'EPSG:3857' | 'auto';\n  transparent?: boolean;\n  onMetadataLoad?: (metadata: ImageSourceMetadata) => void;\n  onMetadataLoadError?: (error: Error) => void;\n  onImageLoadStart?: (requestId: unknown) => void;\n  onImageLoad?: (requestId: unknown) => void;\n  onImageLoadError?: (requestId: unknown, error: Error) => void;\n};\n\nconst defaultProps: DefaultProps<WMSLayerProps> = {\n  id: 'imagery-layer',\n  data: '',\n  serviceType: 'auto',\n  srs: 'auto',\n  layers: {type: 'array', compare: true, value: []},\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  onMetadataLoad: {type: 'function', value: () => {}},\n  // eslint-disable-next-line\n  onMetadataLoadError: {type: 'function', value: console.error},\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  onImageLoadStart: {type: 'function', value: () => {}},\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  onImageLoad: {type: 'function', value: () => {}},\n  onImageLoadError: {\n    type: 'function',\n    compare: false,\n    // eslint-disable-next-line\n    value: (requestId: unknown, error: Error) => console.error(error, requestId)\n  }\n};\n\nexport default class WMSLayer extends CompositeLayer<Required<_WMSLayerProps>> {\n  static layerName = 'WMSLayer';\n  static defaultProps: DefaultProps = defaultProps;\n\n  declare state: {\n    imageSource: ImageSource;\n    image: ImageType;\n    bounds: [number, number, number, number];\n    lastRequestParameters: {\n      // TODO: remove bbox once deck.gl is upgraded to ^8.10 with loaders.gl ^4\n      bbox: [number, number, number, number];\n      boundingBox: [[number, number], [number, number]];\n      layers: string[];\n      srs: 'EPSG:4326' | 'EPSG:3857';\n      width: number;\n      height: number;\n    };\n    lastRequestId: number;\n    _nextRequestId: number;\n    /** TODO: Change any => setTimeout return type. Different between Node and browser... */\n    _timeoutId: any;\n    loadCounter: number;\n  };\n\n  /** Returns true if all async resources are loaded */\n  get isLoaded(): boolean {\n    // Track the explicit loading done by this layer\n    return Boolean(this.state) && this.state.loadCounter === 0 && super.isLoaded;\n  }\n\n  /** Lets deck.gl know that we want viewport change events */\n  override shouldUpdateState(): boolean {\n    return true;\n  }\n\n  override initializeState(): void {\n    // intentionally empty, initialization is done in updateState\n    this.state._nextRequestId = 0;\n    this.state.lastRequestId = -1;\n    this.state.loadCounter = 0;\n  }\n\n  override updateState({changeFlags, props, oldProps}: UpdateParameters<this>): void {\n    const {viewport} = this.context;\n\n    // Check if data source has changed\n    if (changeFlags.dataChanged || props.serviceType !== oldProps.serviceType) {\n      this.state.imageSource = this._createImageSource(props);\n\n      this._loadMetadata();\n      this.debounce(() => this.loadImage(viewport, 'image source changed'), 0);\n    } else if (!deepEqual(props.layers, oldProps.layers, 1)) {\n      this.debounce(() => this.loadImage(viewport, 'layers changed'), 0);\n    } else if (changeFlags.viewportChanged) {\n      this.debounce(() => this.loadImage(viewport, 'viewport changed'));\n    }\n  }\n\n  override renderLayers(): Layer<any> {\n    const {bounds, image, lastRequestParameters} = this.state;\n\n    return (\n      image &&\n      new BitmapLayer({\n        ...this.getSubLayerProps({id: 'bitmap'}),\n        _imageCoordinateSystem:\n          lastRequestParameters.srs === 'EPSG:4326'\n            ? COORDINATE_SYSTEM.LNGLAT\n            : COORDINATE_SYSTEM.CARTESIAN,\n        bounds,\n        image,\n        pickable: this.props.pickable\n      })\n    );\n  }\n\n  async getFeatureInfoText(x: number, y: number): Promise<string | null> {\n    const {lastRequestParameters} = this.state;\n    if (lastRequestParameters) {\n      // @ts-expect-error Undocumented method\n      const featureInfo = await this.state.imageSource.getFeatureInfoText?.({\n        ...lastRequestParameters,\n        query_layers: lastRequestParameters.layers,\n        x,\n        y,\n        info_format: 'application/vnd.ogc.gml'\n      });\n      return featureInfo;\n    }\n    return '';\n  }\n\n  _createImageSource(props: WMSLayerProps): ImageSource {\n    if (props.data instanceof ImageSource) {\n      return props.data;\n    }\n\n    if (typeof props.data === 'string') {\n      return createImageSource({\n        url: props.data,\n        loadOptions: props.loadOptions,\n        type: props.serviceType\n      });\n    }\n\n    throw new Error('invalid image source in props.data');\n  }\n\n  /** Run a getMetadata on the image service */\n  async _loadMetadata(): Promise<void> {\n    const {imageSource} = this.state;\n    try {\n      this.state.loadCounter++;\n      const metadata = await imageSource.getMetadata();\n\n      // If a request takes a long time, it may no longer be expected\n      if (this.state.imageSource === imageSource) {\n        this.getCurrentLayer()?.props.onMetadataLoad(metadata);\n      }\n    } catch (error) {\n      this.getCurrentLayer()?.props.onMetadataLoadError(error as Error);\n    } finally {\n      this.state.loadCounter--;\n    }\n  }\n\n  /** Load an image */\n  async loadImage(viewport: Viewport, _reason: string): Promise<void> {\n    const {layers, serviceType, transparent} = this.props;\n\n    // TODO - move to ImageSource?\n    if (serviceType === 'wms' && layers.length === 0) {\n      return;\n    }\n\n    const bounds = viewport.getBounds();\n    const {width, height} = viewport;\n    const requestId = this.getRequestId();\n    let {srs} = this.props;\n    if (srs === 'auto') {\n      // BitmapLayer only supports LNGLAT or CARTESIAN (Web-Mercator)\n      srs = viewport.resolution ? 'EPSG:4326' : 'EPSG:3857';\n    }\n    const requestParams = {\n      width,\n      height,\n      // TODO: remove bbox once deck.gl is upgraded to ^8.10 with loaders.gl ^4\n      bbox: bounds,\n      boundingBox: [\n        [bounds[0], bounds[1]],\n        [bounds[2], bounds[3]]\n      ] as [[number, number], [number, number]],\n      layers,\n      srs,\n      transparent\n    };\n    if (srs === 'EPSG:3857') {\n      const [minX, minY] = WGS84ToPseudoMercator([bounds[0], bounds[1]]);\n      const [maxX, maxY] = WGS84ToPseudoMercator([bounds[2], bounds[3]]);\n      requestParams.boundingBox = [\n        [minX, minY],\n        [maxX, maxY]\n      ];\n      // TODO: remove bbox once deck.gl is upgraded to ^8.10 with loaders.gl ^4\n      requestParams.bbox = [minX, minY, maxX, maxY];\n    }\n\n    try {\n      this.state.loadCounter++;\n      this.props.onImageLoadStart(requestId);\n\n      const image = await this.state.imageSource.getImage(requestParams);\n\n      // If a request takes a long time, later requests may have already loaded.\n      if (this.state.lastRequestId < requestId) {\n        this.getCurrentLayer()?.props.onImageLoad(requestId);\n        // Not type safe...\n        this.setState({\n          image,\n          bounds,\n          lastRequestParameters: requestParams,\n          lastRequestId: requestId\n        });\n      }\n    } catch (error) {\n      this.context.onError?.(error as Error, this);\n      this.getCurrentLayer()?.props.onImageLoadError(requestId, error as Error);\n    } finally {\n      this.state.loadCounter--;\n    }\n  }\n\n  // HELPERS\n\n  /** Global counter for issuing unique request ids */\n  private getRequestId(): number {\n    return this.state._nextRequestId++;\n  }\n\n  /** Runs an action in the future, cancels it if the new action is issued before it executes */\n  private debounce(fn: () => void, ms = 500): void {\n    clearTimeout(this.state._timeoutId);\n    this.state._timeoutId = setTimeout(() => fn(), ms);\n  }\n}\n\n// https://epsg.io/3857\n// +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs\nconst HALF_EARTH_CIRCUMFERENCE = 6378137 * Math.PI;\n\n/** Projects EPSG:4326 to EPSG:3857\n * This is a lightweight replacement of proj4. Use tests to ensure conformance.\n */\nexport function WGS84ToPseudoMercator(coord: [number, number]): [number, number] {\n  const mercator = lngLatToWorld(coord);\n  mercator[0] = (mercator[0] / 256 - 1) * HALF_EARTH_CIRCUMFERENCE;\n  mercator[1] = (mercator[1] / 256 - 1) * HALF_EARTH_CIRCUMFERENCE;\n  return mercator;\n}\n"
  },
  {
    "path": "src/deckgl-layers/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\" //TODO change once all dependencies are isolated\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/deckgl-layers/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/duckdb/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/duckdb/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/duckdb\",\n  \"author\": \"Shan He <heshan0131@gmail.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"DuckDB plugin for Kepler.gl\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.js\"\n    },\n    \"./components\": {\n      \"types\": \"./dist/components/index.d.ts\",\n      \"import\": \"./dist/components/index.js\",\n      \"require\": \"./dist/components/index.js\"\n    },\n    \"./table\": {\n      \"types\": \"./dist/table/index.d.ts\",\n      \"import\": \"./dist/table/index.js\",\n      \"require\": \"./dist/table/index.js\"\n    }\n  },\n  \"keywords\": [\n    \"duckdb\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist/components dist/table && echo \\\"export * from '../src';\\\" > dist/index.js && echo \\\"export * from '../../src/components';\\\" > dist/components/index.js && echo \\\"export * from '../../src/table';\\\" > dist/table/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@duckdb/duckdb-wasm\": \"^1.28.0\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/processors\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@monaco-editor/react\": \"^4.6.0\",\n    \"@radix-ui/react-collapsible\": \"^1.1.0\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"monaco-editor\": \"^0.52.0\",\n    \"react-resizable-panels\": \"^2.1.7\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Igor Dykhta <dikhta.igor@gmail.com>\",\n    \"Ilya Boyandin <ilya@boyandin.me>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/duckdb/src/adapters/duckdb-wasm-adapter.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport * as duckdb from '@duckdb/duckdb-wasm';\nimport {AsyncDuckDB, DuckDBConfig, AsyncDuckDBConnection} from '@duckdb/duckdb-wasm';\n\nimport {DatabaseAdapter, DatabaseConnection} from '@kepler.gl/utils';\n\nimport {logElapsedTime} from '../utils/perf';\n\nclass Connection implements DatabaseConnection {\n  private connection: AsyncDuckDBConnection;\n\n  constructor(connection: AsyncDuckDBConnection) {\n    this.connection = connection;\n  }\n\n  async query(statement: string): Promise<arrow.Table> {\n    return this.connection.query(statement);\n  }\n\n  async insertArrowTable(arrowTable: arrow.Table, params: {name: string}): Promise<void> {\n    await this.connection.insertArrowTable(arrowTable, params);\n  }\n\n  async close() {\n    await this.connection.close();\n  }\n}\n\ntype DuckDBWasmAdapterProps =\n  | {\n      debug?: boolean;\n      config?: DuckDBConfig;\n    }\n  | Promise<AsyncDuckDB>;\n\nexport class DuckDBWasmAdapter implements DatabaseAdapter {\n  private duckDB: Promise<AsyncDuckDB>;\n\n  constructor(options: DuckDBWasmAdapterProps) {\n    // pass existing AsyncDuckDB object created elsewhere\n    if (options instanceof Promise || options instanceof AsyncDuckDB) {\n      this.duckDB = options as any;\n      return;\n    }\n\n    // or create a new AsyncDuckDB object\n    const {debug = false, config} = options || {};\n    this.duckDB = initializeDuckDbWasm(config, debug);\n  }\n\n  async connect() {\n    const db = await this.duckDB;\n    const c = await db.connect();\n    return new Connection(c);\n  }\n\n  async registerFileText(filename: string, content) {\n    const db = await this.duckDB;\n    await db.registerFileText(filename, content);\n  }\n\n  async registerFileHandle(\n    name: string,\n    handle: any,\n    protocol: duckdb.DuckDBDataProtocol,\n    directIO: boolean\n  ): Promise<void> {\n    const db = await this.duckDB;\n    await db.registerFileHandle(name, handle, protocol, directIO);\n  }\n}\n\n/**\n * Initialize DuckDB with a browser-specific Wasm bundle.\n */\nconst initializeDuckDbWasm = async (\n  config?: DuckDBConfig,\n  debug?: boolean\n): Promise<AsyncDuckDB> => {\n  const start = performance.now();\n\n  // Select a bundle based on browser checks\n  const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();\n  const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);\n  if (!bundle.mainWorker) {\n    throw new Error('Failed to initialize DuckDB');\n  }\n\n  const worker_url = URL.createObjectURL(\n    new Blob([`importScripts(\"${bundle.mainWorker}\");`], {\n      type: 'text/javascript'\n    })\n  );\n\n  // Instantiate the async version of DuckDB-wasm\n  const worker = new Worker(worker_url);\n  const logger = debug ? new duckdb.ConsoleLogger() : new duckdb.VoidLogger();\n  const db = new AsyncDuckDB(logger, worker);\n  await db.instantiate(bundle.mainModule, bundle.pthreadWorker);\n  URL.revokeObjectURL(worker_url);\n\n  if (config) {\n    if (config.path) {\n      const res = await fetch(config.path);\n      const buffer = await res.arrayBuffer();\n      const fileNameMatch = config.path.match(/[^/]*$/);\n      if (fileNameMatch) {\n        config.path = fileNameMatch[0];\n      }\n      await db.registerFileBuffer(config.path, new Uint8Array(buffer));\n    }\n    await db.open(config);\n  }\n\n  if (debug) {\n    logElapsedTime('DuckDB initialized', start);\n    if (config) {\n      console.debug(`DuckDbConfig: ${JSON.stringify(config, null, 2)}`);\n    }\n  }\n  return db;\n};\n"
  },
  {
    "path": "src/duckdb/src/components/index.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {SqlPanel} from './sql-panel';\nexport {SchemaPanel, SchemaPanelDropMessage} from './schema-panel';\nexport {PreviewDataPanel} from './preview-data-panel';\nexport {Tree, DatasetNode, ColumnNode} from './tree';\nexport {default as MonacoEditor} from './monaco-editor';\n\nexport type {SchemaSuggestion} from './schema-panel';\nexport type {PreviewDataPanelProps, QueryResult, DataTableStyle} from './preview-data-panel';\nexport type {TreeNodeData, TreeNodeProps, TreeProps} from './tree';\n"
  },
  {
    "path": "src/duckdb/src/components/monaco-editor.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useMemo, useRef} from 'react';\nimport Editor, {OnChange, OnMount} from '@monaco-editor/react';\nimport * as monaco from 'monaco-editor';\n// import {tableSchema as DEFAULT_SCHEMA} from './table-schema';\nimport uniq from 'lodash/uniq';\nimport uniqBy from 'lodash/uniqBy';\n\nconst MONACO_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = {\n  minimap: {enabled: false},\n  language: 'sql',\n  contextmenu: false,\n  renderLineHighlight: 'none',\n  scrollBeyondLastLine: false,\n  scrollbar: {alwaysConsumeMouseWheel: false},\n  overviewRulerLanes: 0,\n  automaticLayout: true,\n  acceptSuggestionOnEnter: 'on',\n  quickSuggestionsDelay: 400,\n  matchOnWordStartOnly: false,\n  tabCompletion: 'off',\n  lineNumbers: 'off'\n};\n\nfunction parseSqlAndFindTableNameAndAliases(sql: string) {\n  const regex = /\\b(?:FROM|JOIN)\\s+([^\\s.]+(?:\\.[^\\s.]+)?)\\s*(?:AS)?\\s*([^\\s,]+)?/gi;\n  const tables: {table_name: string; alias: string}[] = [];\n\n  while (true) {\n    const match = regex.exec(sql);\n    if (!match) {\n      break;\n    }\n    const table_name = match[1];\n    if (!/\\(/.test(table_name)) {\n      // exclude function calls\n      let alias = match[2] as string | null;\n      if (alias && /on|where|inner|left|right|join/.test(alias)) {\n        alias = null;\n      }\n      tables.push({\n        table_name,\n        alias: alias || table_name\n      });\n    }\n  }\n\n  return tables;\n}\n\ninterface MonacoEditorProps {\n  code: string;\n  isReadOnly?: boolean;\n  onChange: OnChange;\n  onRunQuery: () => void;\n  tableSchema?: {table_name: string; column_name: string}[];\n}\n\nconst MonacoEditor: React.FC<MonacoEditorProps> = ({\n  onRunQuery,\n  onChange,\n  code,\n  tableSchema,\n  isReadOnly\n}) => {\n  // private editor?: monaco.editor.IStandaloneCodeEditor;\n  const schemaTableNames = useMemo(\n    () => (tableSchema ? uniq(tableSchema.map(d => d.table_name)) : []),\n    [tableSchema]\n  );\n  const schemaTableNamesSet = useMemo(() => new Set(schemaTableNames), [schemaTableNames]);\n  const handleRunQueryRef = useRef(onRunQuery);\n  handleRunQueryRef.current = onRunQuery;\n\n  const handleEditorDidMount: OnMount = useCallback(\n    editor => {\n      // this.editor = editor;\n      editor.focus();\n\n      editor.addAction({\n        id: 'run-query',\n        label: 'Run Query',\n        keybindings: [\n          monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,\n          monaco.KeyMod.Shift | monaco.KeyCode.Enter\n        ],\n        contextMenuGroupId: 'custom',\n        contextMenuOrder: 0,\n        run: () => handleRunQueryRef.current()\n      });\n\n      monaco.languages.registerCompletionItemProvider('*', {\n        provideCompletionItems: (model, position, _context, _cancelationToken) => {\n          const suggestions: monaco.languages.CompletionItem[] = [\n            {\n              label: 'myCustomSnippet',\n              kind: monaco.languages.CompletionItemKind.Snippet,\n              insertText: 'This is a piece of custom code',\n              insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,\n              documentation: 'This is a piece of custom code'\n              // TODO: range is missing\n            } as monaco.languages.CompletionItem\n          ];\n\n          const fullQueryText = model.getValue();\n\n          const tableNamesAndAliases = new Map<string, string>(\n            parseSqlAndFindTableNameAndAliases(fullQueryText).map(({table_name, alias}) => [\n              alias,\n              table_name\n            ])\n          );\n\n          const thisLine = model.getValueInRange({\n            startLineNumber: position.lineNumber,\n            startColumn: 1,\n            endLineNumber: position.lineNumber,\n            endColumn: position.column\n          });\n          const thisToken = thisLine.trim().split(' ').slice(-1)?.[0] || '';\n\n          const lastTokenBeforeSpace = /\\s?(\\w+)\\s+\\w+$/.exec(thisLine.trim())?.[1];\n          const lastTokenBeforeDot = /(\\w+)\\.\\w*$/.exec(thisToken)?.[1];\n\n          // console.log(tableNamesAndAliases, thisToken, lastTokenBeforeSpace, lastTokenBeforeDot);\n\n          if (lastTokenBeforeSpace && /from|join|update|into/.test(lastTokenBeforeSpace)) {\n            suggestions.push(\n              ...schemaTableNames.map(\n                table_name =>\n                  ({\n                    label: table_name,\n                    kind: monaco.languages.CompletionItemKind.Field,\n                    insertText: table_name\n                    // TODO: range is missing\n                  } as monaco.languages.CompletionItem)\n              )\n            );\n          }\n\n          if (lastTokenBeforeDot) {\n            let table_name = null as string | null;\n            if (schemaTableNamesSet.has(lastTokenBeforeDot)) {\n              table_name = lastTokenBeforeDot;\n            } else if (tableNamesAndAliases.get(lastTokenBeforeDot)) {\n              table_name = tableNamesAndAliases.get(lastTokenBeforeDot) as string;\n            }\n            if (table_name && tableSchema) {\n              suggestions.push(\n                ...tableSchema\n                  .filter(d => d.table_name === table_name)\n                  .map(\n                    ({column_name}) =>\n                      ({\n                        label: column_name,\n                        kind: monaco.languages.CompletionItemKind.Field,\n                        insertText: column_name\n                        // TODO: range is missing\n                      } as monaco.languages.CompletionItem)\n                  )\n              );\n            }\n          }\n\n          return {\n            suggestions: uniqBy(suggestions, s => s.insertText)\n          };\n        }\n      });\n    },\n    [tableSchema, schemaTableNames, schemaTableNamesSet]\n  );\n\n  return (\n    <Editor\n      height=\"100%\"\n      theme=\"vs-dark\"\n      defaultLanguage=\"sql\"\n      defaultValue={code}\n      onChange={onChange}\n      onMount={handleEditorDidMount}\n      options={{\n        ...MONACO_OPTIONS,\n        readOnly: isReadOnly\n      }}\n    />\n  );\n};\n\nexport default MonacoEditor;\n"
  },
  {
    "path": "src/duckdb/src/components/preview-data-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport React, {useCallback, useMemo, useState, CSSProperties} from 'react';\nimport {withTheme} from 'styled-components';\n\nimport {DataTable, renderedSize} from '@kepler.gl/components';\nimport {arrowSchemaToFields} from '@kepler.gl/processors';\nimport {parseFieldValue, createDataContainer, DataForm} from '@kepler.gl/utils';\n\ntype BaseComponentProps = {\n  className?: string;\n  style?: CSSProperties;\n};\n\nconst DEFAULT_ROWS_TO_CALCULATE_PREVIEW = 100;\n// min Cellsize should take into account option button and field token\nconst minCellSize = 80;\n// option buttons and field token\nconst optionButtonWidth = 20;\nconst pinButton = 20;\nconst cellPadding = 20;\n\nexport type ColMeta = {\n  [key: string]: {\n    colIdx: number;\n    name: string;\n    displayName: string;\n    type: string;\n  };\n};\n\nexport type DataTableStyle = {\n  minCellSize?: number;\n  cellPadding?: number;\n  fontSize?: number;\n  font?: string;\n  optionsButton?: number;\n};\n\nexport type QueryResult = {\n  table: arrow.Table;\n  tableDuckDBTypes: Record<string, string>;\n};\n\nexport type PreviewDataPanelProps = BaseComponentProps & {\n  result: QueryResult;\n  rowsToCalculatePreview?: number;\n  theme?: any;\n  setColumnDisplayFormat?: (formats: {[key: string]: string}) => void;\n  defaultPinnedColumns?: string[];\n  dataTableStyle: DataTableStyle;\n  onAddResultToMap: (result: QueryResult) => void;\n};\n\nconst PreviewDataPanelWOTheme: React.FC<PreviewDataPanelProps> = ({\n  result,\n  rowsToCalculatePreview = DEFAULT_ROWS_TO_CALCULATE_PREVIEW,\n  defaultPinnedColumns = [],\n  theme\n}) => {\n  const [pinnedColumns, setPinnedColumns] = useState<string[]>(defaultPinnedColumns);\n  const fields = useMemo(\n    () => arrowSchemaToFields(result.table, result.tableDuckDBTypes),\n    [result]\n  );\n  const dataContainer = useMemo(() => {\n    const cols = [...Array(result.table.numCols).keys()].map(i => result.table.getChildAt(i));\n\n    const dataContainer = createDataContainer(cols, {\n      fields,\n      inputDataFormat: DataForm.COLS_ARRAY\n    });\n    return dataContainer;\n  }, [result, fields]);\n\n  const columns = useMemo(() => fields.map(f => f.name), [fields]);\n  const colMeta = useMemo(\n    () =>\n      fields.reduce(\n        (acc, {name, displayName, type, displayFormat}, colIdx) => ({\n          ...acc,\n          [name]: {\n            // because '' || 'aaa' = 'aaa'\n            name: displayName !== undefined ? displayName : name,\n            displayName,\n            displayFormat,\n            type,\n            colIdx\n          }\n        }),\n        {}\n      ),\n    [fields]\n  );\n  const copyTableColumn = useCallback(\n    column => {\n      const {colIdx, type} = colMeta[column];\n      const text = dataContainer\n        .mapIndex(row => parseFieldValue(dataContainer.valueAt(row.index, colIdx), type))\n        .join('\\n');\n      navigator?.clipboard.writeText(text);\n    },\n    [colMeta, dataContainer]\n  );\n  const pinTableColumn = useCallback(\n    column =>\n      pinnedColumns.includes(column)\n        ? setPinnedColumns(pinnedColumns.filter(c => c !== column))\n        : setPinnedColumns([...pinnedColumns, column]),\n    [pinnedColumns]\n  );\n\n  // TODO Potentially costly operation for non row based data containers. Revisit sorting below.\n  const dataTableStyle = useMemo(\n    () => ({\n      minCellSize,\n      cellPadding,\n      optionsButton:\n        theme.fieldTokenWidth + theme.fieldTokenRightMargin + optionButtonWidth + pinButton,\n      fontSize: theme.cellFontSize,\n      font: theme.fontFamily\n    }),\n    [theme]\n  );\n  const cellSizeCache = useMemo(() => {\n    return columns.reduce((acc, column) => {\n      const {colIdx, displayName, type} = colMeta[column];\n      return {\n        ...acc,\n        [column]: renderedSize({\n          text: {\n            dataContainer,\n            column: displayName\n          },\n          colIdx,\n          type,\n          numRowsToCalculate: rowsToCalculatePreview,\n          ...dataTableStyle\n        })\n      };\n    }, {});\n  }, [columns, colMeta, dataContainer, rowsToCalculatePreview, dataTableStyle]);\n\n  return (\n    <DataTable\n      colMeta={colMeta}\n      columns={columns}\n      cellSizeCache={cellSizeCache}\n      dataContainer={dataContainer}\n      pinnedColumns={pinnedColumns}\n      copyTableColumn={copyTableColumn}\n      pinTableColumn={pinTableColumn}\n    />\n  );\n};\n\nexport const PreviewDataPanel = withTheme(\n  PreviewDataPanelWOTheme\n) as React.FC<PreviewDataPanelProps>;\n"
  },
  {
    "path": "src/duckdb/src/components/schema-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useEffect, useState} from 'react';\nimport {useSelector} from 'react-redux';\nimport styled from 'styled-components';\n\nimport {LoadingSpinner, Icons} from '@kepler.gl/components';\nimport {arrowSchemaToFields} from '@kepler.gl/processors';\nimport {VisState} from '@kepler.gl/schemas';\nimport {getApplicationConfig, DatabaseConnection} from '@kepler.gl/utils';\n\nimport {Tree, DatasetNode, ColumnNode, TreeNodeData} from './tree';\nimport {getDuckDBColumnTypes, getDuckDBColumnTypesMap} from '../table/duckdb-table-utils';\n\n// TODO note that demo state is available in demo-app, but not when add modules to dependencies in a custom map\ntype State = {\n  demo?: {\n    keplerGl: {\n      map: {\n        visState: VisState;\n      };\n    };\n  };\n};\n\nconst StyledSchemaPanel = styled.div`\n  color: ${props => props.theme.textColor};\n  font-size: 12px;\n  padding: 12px;\n  font-family: ${props => props.theme.fontFamily};\n  height: 100%;\n`;\n\nconst StyledLoadingSpinnerWrapper = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n`;\n\nasync function getColumnSchema(connection: DatabaseConnection, tableName: string) {\n  const columnResult = await connection.query(`Select * from '${tableName}' LIMIT 1;`);\n\n  const columnDescribe = await getDuckDBColumnTypes(connection, tableName);\n  const keplerFields = arrowSchemaToFields(columnResult, getDuckDBColumnTypesMap(columnDescribe));\n\n  return {\n    key: tableName,\n    object: {\n      type: 'dataset',\n      tableName: tableName\n    },\n    children: columnResult.schema.fields.map((field, fieldIndex) => {\n      return {\n        key: field.name,\n        object: {\n          type: 'column',\n          name: field.name,\n          arrowType: field.type,\n          fieldType: keplerFields[fieldIndex].type\n        }\n      };\n    })\n  };\n}\n\nexport type SchemaSuggestion = {column_name: string; table_name: string};\n\nfunction getSchemaSuggestion(result: {key: string; children: {key: string}[]}[]) {\n  return result.reduce((accu, data) => {\n    const columns = data.children.map(child => ({\n      column_name: child.key,\n      table_name: data.key\n    }));\n    return accu.concat(columns);\n  }, [] as SchemaSuggestion[]);\n}\n\ntype SchemaPanelProps = {\n  setTableSchema: (tableSchema: SchemaSuggestion[]) => void;\n  droppedFile: File | null;\n  schemaUpdateTrigger: number;\n};\n\nconst StyledSchemaPanelDropMessage = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  flex-direction: column;\n  text-align: center;\n\n  div {\n    margin: 5px;\n  }\n  .header {\n    font-size: 15px;\n  }\n  .bold {\n    font-weight: 700;\n  }\n`;\n\nconst StyledAddIcon = styled(Icons.Add)`\n  display: inline;\n  margin-top: -3px;\n`;\n\nexport const SchemaPanelDropMessage = () => {\n  return (\n    <StyledSchemaPanelDropMessage>\n      <div className=\"header\">\n        <StyledAddIcon /> Add files to DuckDB\n      </div>\n      <div className=\"bold\">Supported formats: </div>\n      <div>.csv, .json, .geojson, .parquet, .arrow</div>\n      <div>Files you add will stay local to your browser.</div>\n    </StyledSchemaPanelDropMessage>\n  );\n};\n\nexport const SchemaPanel = ({\n  setTableSchema,\n  droppedFile,\n  schemaUpdateTrigger\n}: SchemaPanelProps) => {\n  const [columnSchemas, setColumnSchemas] = useState<TreeNodeData<{type: string}>[]>([]);\n  const datasets = useSelector((state: State) => state?.demo?.keplerGl?.map?.visState.datasets);\n\n  const getTableSchema = useCallback(async () => {\n    const db = getApplicationConfig().database;\n    if (!db) {\n      console.error('The database is not configured properly.');\n      return;\n    }\n    const c = await db.connect();\n\n    const tableResult = await c.query('SHOW TABLES;');\n\n    const tableNames: string[] | undefined = tableResult.getChildAt(0)?.toJSON();\n\n    const result = await Promise.all((tableNames || [])?.map(name => getColumnSchema(c, name)));\n    const tableSchema = getSchemaSuggestion(result);\n\n    setColumnSchemas(result);\n    setTableSchema(tableSchema);\n    await c.close();\n\n    // schemaUpdateTrigger indicates possible change in DuckDB\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [setColumnSchemas, setTableSchema, schemaUpdateTrigger]);\n\n  useEffect(() => {\n    getTableSchema();\n  }, [datasets, droppedFile, getTableSchema]);\n\n  return (\n    <StyledSchemaPanel>\n      {columnSchemas?.length ? (\n        columnSchemas.map(data => (\n          <Tree\n            key={data.key}\n            treeData={data}\n            renderNode={node => {\n              if (node.object.type === 'dataset') {\n                return <DatasetNode node={node} />;\n              } else if (node.object.type === 'column') {\n                return <ColumnNode node={node} />;\n              }\n              return null;\n            }}\n          />\n        ))\n      ) : droppedFile ? (\n        <StyledLoadingSpinnerWrapper>\n          <LoadingSpinner />\n        </StyledLoadingSpinnerWrapper>\n      ) : (\n        <SchemaPanelDropMessage />\n      )}\n    </StyledSchemaPanel>\n  );\n};\n"
  },
  {
    "path": "src/duckdb/src/components/sql-panel.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport React, {useCallback, useState, useEffect, useRef} from 'react';\nimport {useDispatch} from 'react-redux';\nimport styled from 'styled-components';\nimport {Panel, PanelGroup, PanelResizeHandle} from 'react-resizable-panels';\n\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {generateHashId} from '@kepler.gl/common-utils';\nimport {Button, FileDrop, IconButton, Icons, LoadingSpinner, Tooltip} from '@kepler.gl/components';\nimport {arrowSchemaToFields} from '@kepler.gl/processors';\nimport {sidePanelBg, panelBorderColor} from '@kepler.gl/styles';\nimport {isAppleDevice, getApplicationConfig} from '@kepler.gl/utils';\n\nimport MonacoEditor from './monaco-editor';\nimport {SchemaPanel, SchemaSuggestion} from './schema-panel';\nimport {PreviewDataPanel, QueryResult} from './preview-data-panel';\n\nimport {\n  castDuckDBTypesForKepler,\n  getDuckDBColumnTypes,\n  getDuckDBColumnTypesMap,\n  setGeoArrowWKBExtension,\n  splitSqlStatements,\n  checkIsSelectQuery,\n  removeSQLComments,\n  tableFromFile,\n  SUPPORTED_DUCKDB_DROP_EXTENSIONS\n} from '../table/duckdb-table-utils';\n\nconst StyledSqlPanel = styled.div`\n  display: flex;\n  height: 100%;\n  background-color: ${sidePanelBg};\n  border-left: 1px solid #333;\n`;\n\nconst StyledSqlActions = styled.div`\n  flex: 0 0 50px;\n  display: flex;\n  padding-top: 12px;\n  align-items: center;\n  flex-direction: column;\n  justify-content: flex-start;\n  border-bottom: 1px solid #333;\n`;\n\nconst StyledSqlEditor = styled.div`\n  display: flex;\n  flex-direction: row;\n  flex: 1;\n  min-width: 0; // Prevents flex child from overflowing\n  height: 100%;\n`;\n\nconst StyledEditorPanel = styled.div`\n  background-color: ${sidePanelBg};\n  display: flex;\n  height: 100%;\n  flex-direction: row;\n  gap: 5px;\n  flex-grow: 1;\n`;\n\nconst StyledDataPanel = styled.div`\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  background: white;\n`;\n\nconst StyledEditor = styled.div<{isRunning: boolean}>`\n  display: flex;\n  padding: 12px 12px 12px 0;\n  background-color: rgb(30, 30, 30);\n  opacity: ${props => (props.isRunning ? 0.5 : 1)};\n  width: 100%;\n  height: 100%;\n  min-height: 0; // Prevents flex child from overflowing\n`;\n\nconst StyledResultActions = styled.div`\n  background-color: white;\n  padding: 2px 12px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 12px;\n`;\n\nconst StyledResizeHandle = styled(PanelResizeHandle)`\n  background-color: ${panelBorderColor};\n  width: 4px;\n  cursor: col-resize;\n\n  &:hover {\n    background-color: #555;\n  }\n`;\n\nconst StyledVerticalResizeHandle = styled(PanelResizeHandle)`\n  background-color: ${panelBorderColor};\n  height: 4px;\n  cursor: row-resize;\n\n  &:hover {\n    background-color: #555;\n  }\n`;\n\nconst StyledLoadingContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n`;\n\nconst StyledErrorContainer = styled.pre`\n  padding: 12px;\n  color: red;\n  font-size: 12px;\n  background-color: ${sidePanelBg};\n  height: 100%;\n  overflow: auto;\n`;\n\ninterface StyledDragPanelProps {\n  dragOver?: boolean;\n}\n\nconst StyledFileDropArea = styled(FileDrop)<StyledDragPanelProps>`\n  height: 100%;\n  border-width: 1px;\n  border: 1px ${props => (props.dragOver ? 'solid' : 'dashed')}\n    ${props => (props.dragOver ? props.theme.subtextColorLT : 'transparent')};\n  .file-drop-target {\n    height: 100%;\n  }\n`;\n\ntype SqlPanelProps = {\n  initialSql?: string;\n};\n\nconst SCHEMA_PANEL_STYLE = {overflow: 'auto'};\n\nexport const SqlPanel: React.FC<SqlPanelProps> = ({initialSql = ''}) => {\n  const [sql, setSql] = useState(() => {\n    const params = new URLSearchParams(window.location.search);\n    return params.get('sql') || initialSql;\n  });\n  const [droppedFile, setDroppedFile] = useState<File | null>(null);\n  const [dragState, setDragState] = useState(false);\n  const [result, setResult] = useState<null | QueryResult>(null);\n  const [schemaUpdateTrigger, setSchemaUpdateTrigger] = useState<number>(0);\n  const [error, setError] = useState<Error | null>(null);\n  const [counter, setCounter] = useState(0);\n  const [tableSchema, setTableSchema] = useState<SchemaSuggestion[]>([]);\n  const [isRunning, setIsRunning] = useState(false);\n  const [isMac] = useState(() => isAppleDevice());\n  const dispatch = useDispatch();\n\n  const droppedFileAreaRef = useRef(null);\n\n  useEffect(() => {\n    const currentUrl = new URL(window.location.href);\n    if (sql) {\n      currentUrl.searchParams.set('sql', sql);\n    } else {\n      currentUrl.searchParams.delete('sql');\n    }\n    window.history.replaceState({}, '', currentUrl.toString());\n  }, [sql]);\n\n  const runQuery = useCallback(async () => {\n    setIsRunning(true);\n    try {\n      const adjustedQuery = sql ? removeSQLComments(sql) : null;\n      if (!adjustedQuery) {\n        setError(new Error('Query is empty'));\n        return;\n      }\n\n      const db = getApplicationConfig().database;\n      if (!db) {\n        setError(new Error('The database is not configured properly.'));\n        return;\n      }\n      const connection = await db.connect();\n\n      // TODO find a cheap way to get DuckDb types with a single query to a remote resource - temp table? cte?\n      const tempTableName = 'temp_keplergl_table';\n\n      // remove comments\n      const sqlStatements = splitSqlStatements(adjustedQuery);\n\n      let arrowResult: arrow.Table | null = null;\n      let tableDuckDBTypes = {};\n\n      for (const statement of sqlStatements) {\n        const isLastQuery = statement === sqlStatements[sqlStatements.length - 1];\n        if (isLastQuery && (await checkIsSelectQuery(connection, statement))) {\n          // 1) create temp table from the original query.\n          await connection.query(`CREATE OR REPLACE TABLE '${tempTableName}' AS ${statement}`);\n\n          // 2) query duckdb types and detect candidate columns for ST_asWKB transform.\n          const duckDbColumns = await getDuckDBColumnTypes(connection, tempTableName);\n          tableDuckDBTypes = getDuckDBColumnTypesMap(duckDbColumns);\n\n          // 3) cast GEOMETRY columns to WKB and BigInt to double.\n          const adjustedQuery = castDuckDBTypesForKepler(tempTableName, duckDbColumns);\n          arrowResult = await connection.query(adjustedQuery);\n\n          // 4) set geoarrow extension for the arrow table as DuckDB doesn't support the geoarrow extension.\n          setGeoArrowWKBExtension(arrowResult, duckDbColumns);\n\n          // 5) remove temp table\n          await connection.query(`DROP TABLE ${tempTableName};`);\n        } else {\n          await connection.query(statement);\n        }\n      }\n\n      // Show preview only for the result of the last query\n      if (arrowResult) {\n        setResult({table: arrowResult, tableDuckDBTypes});\n        setError(null);\n      }\n\n      await connection.close();\n    } catch (e) {\n      setError(e as Error);\n    } finally {\n      setIsRunning(false);\n    }\n\n    // Indicate that there may be a possible change in DuckDB.\n    setSchemaUpdateTrigger(Date.now());\n  }, [sql]);\n\n  const onChange = useCallback(\n    value => {\n      setSql(value);\n    },\n    [setSql]\n  );\n\n  const onAddResultToMap = useCallback(() => {\n    if (!result) return;\n\n    const keplerFields = arrowSchemaToFields(result.table, result.tableDuckDBTypes);\n\n    const datasetToAdd = {\n      data: {\n        fields: keplerFields,\n        // TODO type AddDataToMapPayload -> rows -> + arrow.Table\n        rows: result.table as any\n      },\n      info: {\n        id: generateHashId(),\n        label: `query_result${counter > 0 ? `_${counter}` : ''}`,\n        format: 'arrow'\n      }\n    };\n\n    dispatch(addDataToMap({datasets: [datasetToAdd]}));\n    setCounter(counter + 1);\n  }, [result, counter, dispatch]);\n\n  const isValidFileType = useCallback(filename => {\n    const fileExt = SUPPORTED_DUCKDB_DROP_EXTENSIONS.find(ext => filename.endsWith(ext));\n    return Boolean(fileExt);\n  }, []);\n\n  const createTableFromDroppedFile = useCallback(async (droppedFile: File | null) => {\n    if (droppedFile) {\n      const error = await tableFromFile(droppedFile);\n      if (error) {\n        setError(error);\n      } else {\n        setError(null);\n      }\n    }\n\n    setDroppedFile(null);\n    setDragState(false);\n  }, []);\n\n  useEffect(() => {\n    createTableFromDroppedFile(droppedFile);\n  }, [droppedFile, createTableFromDroppedFile]);\n\n  const handleFileInput = useCallback(\n    (fileList: FileList, event: DragEvent) => {\n      if (event) {\n        event.preventDefault();\n        event.stopPropagation();\n      }\n\n      const files = [...fileList].filter(Boolean);\n\n      const disableExtensionFilter = false;\n\n      const filesToLoad: File[] = [];\n      const errorFiles: string[] = [];\n      for (const file of files) {\n        if (disableExtensionFilter || isValidFileType(file.name)) {\n          filesToLoad.push(file);\n        } else {\n          errorFiles.push(file.name);\n        }\n      }\n\n      if (filesToLoad.length > 0) {\n        setDroppedFile(filesToLoad[0]);\n      } else if (errorFiles.length > 0) {\n        setError(new Error(`Unsupported file formats: ${errorFiles.join(', ')}`));\n      }\n    },\n    [isValidFileType]\n  );\n\n  return (\n    <StyledSqlPanel>\n      <PanelGroup direction=\"horizontal\">\n        <Panel defaultSize={20} minSize={15} style={SCHEMA_PANEL_STYLE} className=\"schema-panel\">\n          <StyledFileDropArea\n            dragOver={dragState}\n            onDragOver={() => setDragState(true)}\n            onDragLeave={() => setDragState(false)}\n            frame={droppedFileAreaRef.current || document}\n            onDrop={handleFileInput}\n            className=\"file-uploader__file-drop\"\n          >\n            <SchemaPanel\n              setTableSchema={setTableSchema}\n              droppedFile={droppedFile}\n              schemaUpdateTrigger={schemaUpdateTrigger}\n            />\n          </StyledFileDropArea>\n        </Panel>\n\n        <StyledResizeHandle />\n\n        <Panel minSize={40}>\n          <StyledSqlEditor>\n            <PanelGroup direction=\"vertical\">\n              <Panel defaultSize={30} className=\"editor-panel\">\n                <StyledEditorPanel>\n                  <StyledSqlActions>\n                    <IconButton\n                      data-tip\n                      data-for=\"run-query-tooltip\"\n                      onClick={runQuery}\n                      disabled={isRunning || !sql}\n                    >\n                      <Icons.Play height=\"18px\" />\n                      <Tooltip id=\"run-query-tooltip\" place=\"top\" effect=\"solid\">\n                        Run Query ({isMac ? '⌘Enter' : 'Ctrl+Enter'} or Shift+Enter)\n                      </Tooltip>\n                    </IconButton>\n                  </StyledSqlActions>\n                  <StyledEditor isRunning={isRunning}>\n                    <MonacoEditor\n                      isReadOnly={isRunning}\n                      onRunQuery={runQuery}\n                      onChange={onChange}\n                      tableSchema={tableSchema}\n                      code={sql}\n                    />\n                  </StyledEditor>\n                </StyledEditorPanel>\n              </Panel>\n\n              <StyledVerticalResizeHandle />\n              <Panel className=\"preview-panel\">\n                {isRunning ? (\n                  <StyledLoadingContainer>\n                    <LoadingSpinner />\n                  </StyledLoadingContainer>\n                ) : error ? (\n                  <StyledErrorContainer>\n                    <div>{error.message}</div>\n                  </StyledErrorContainer>\n                ) : result ? (\n                  <StyledDataPanel>\n                    <StyledResultActions>\n                      <Button secondary onClick={onAddResultToMap}>\n                        Add to Map\n                      </Button>\n                      <div>{result.table.numRows} rows</div>\n                    </StyledResultActions>\n                    <PreviewDataPanel\n                      result={result}\n                      dataTableStyle={{}}\n                      onAddResultToMap={onAddResultToMap}\n                    />\n                  </StyledDataPanel>\n                ) : null}\n              </Panel>\n            </PanelGroup>\n          </StyledSqlEditor>\n        </Panel>\n      </PanelGroup>\n    </StyledSqlPanel>\n  );\n};\n"
  },
  {
    "path": "src/duckdb/src/components/tree.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Collapsible, CollapsibleTrigger, CollapsibleContent} from '@radix-ui/react-collapsible';\nimport styled from 'styled-components';\nimport React, {useEffect, useState} from 'react';\nimport classNames from 'classnames';\n\nimport {Icons, FieldToken} from '@kepler.gl/components';\n\nexport type TreeNodeData<T> = {\n  key: string;\n  object: T;\n  children?: TreeNodeData<T>[];\n  isOpen?: boolean;\n};\n\nexport type TreeProps<T> = {\n  className?: string;\n  treeData: TreeNodeData<T>;\n  renderNode: TreeNodeProps<T>['renderNode'];\n};\n\n// TODO: consider using react-vtree/react-window for virtualization\n\n/**\n * Component that renders a generic tree.\n * @param treeData - The tree data.\n * @param renderNode - A function that renders a tree node.\n */\nexport function Tree<T>(props: TreeProps<T>): React.ReactElement {\n  const {treeData, renderNode} = props;\n  return (\n    <div className=\"flex flex-col\">\n      <TreeNode<T> treeData={treeData} renderNode={renderNode} />\n    </div>\n  );\n}\n\nexport type TreeNodeProps<T> = {\n  treeData: TreeNodeData<T>;\n  renderNode: (node: TreeNodeData<T>, isOpen: boolean) => JSX.Element | null;\n};\n\nconst StyledCollapsibleTriggerContent = styled.div`\n  display: flex;\n  width: 100%;\n  cursor: pointer;\n  align-items: center;\n`;\n/**\n * Component that renders a tree node.\n */\nfunction TreeNode<T>(props: TreeNodeProps<T>): JSX.Element | null {\n  const {treeData, renderNode} = props;\n  const {children} = treeData;\n  const [isOpen, setIsOpen] = useState(Boolean(treeData.isOpen));\n  useEffect(() => {\n    setIsOpen(Boolean(treeData.isOpen));\n  }, [treeData.isOpen]);\n  if (!children) {\n    return renderNode(treeData, isOpen);\n  }\n  return (\n    <Collapsible open={isOpen} onOpenChange={setIsOpen}>\n      <CollapsibleTrigger className=\"w-full\" asChild>\n        <StyledCollapsibleTriggerContent>\n          <Icons.ArrowRight\n            className={classNames('flex-shrink-0 text-gray-500', {'rotate-90 transform': isOpen})}\n            height=\"18px\"\n          />\n          {renderNode(treeData, isOpen)}\n        </StyledCollapsibleTriggerContent>\n      </CollapsibleTrigger>\n      <CollapsibleContent className=\"pl-4\">\n        {isOpen\n          ? children.map(child => (\n              <TreeNode<T> key={child.key} treeData={child} renderNode={renderNode} />\n            ))\n          : null}\n      </CollapsibleContent>\n    </Collapsible>\n  );\n}\n\nconst StyledDatasetNode = styled.div`\n  font-weight: bold;\n`;\n\nexport const DatasetNode = ({node}) => {\n  return <StyledDatasetNode>{node.object.tableName}</StyledDatasetNode>;\n};\n\nconst StyledColumnNode = styled.div`\n  display: flex;\n  align-items: center;\n  padding: 4px 0;\n  margin-left: 16px;\n\n  .column-name {\n    margin-left: 8px;\n  }\n`;\nexport const ColumnNode = ({node}) => {\n  return (\n    <StyledColumnNode>\n      <FieldToken type={node.object.fieldType} />\n      <div className=\"column-name\">{node.object.name}</div>\n    </StyledColumnNode>\n  );\n};\n"
  },
  {
    "path": "src/duckdb/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {DuckDBWasmAdapter} from './adapters/duckdb-wasm-adapter';\nexport {keplerGlDuckDBPlugin, keplerGlDuckDBPlugin as default} from './plugin';\nexport * from './table/duckdb-table';\nexport * from './table/duckdb-table-utils';\nexport * from './processors/data-processor';\n"
  },
  {
    "path": "src/duckdb/src/plugin.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const keplerGlDuckDBPlugin = {\n  name: 'duckdb',\n  async init() {\n    console.log('kepler.gl DuckDB Plugin initialized');\n  }\n};\n"
  },
  {
    "path": "src/duckdb/src/processors/data-processor.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport normalize from '@mapbox/geojson-normalize';\n\nimport {\n  getSampleForTypeAnalyze,\n  getFieldsFromData,\n  generateHashId,\n  notNullorUndefined\n} from '@kepler.gl/common-utils';\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport {ALL_FIELD_TYPES, GUIDES_FILE_FORMAT_DOC} from '@kepler.gl/constants';\nimport {processKeplerglJSON} from '@kepler.gl/processors';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nexport const CSV_NULLS = /^(null|NULL|Null|NaN|\\/N||)$/;\n\ntype RowsAsArray = any[][];\ntype RowsAsObject = Record<string, unknown>[];\ntype RowData = RowsAsArray | RowsAsObject;\n\nexport type ProcessorResult = {\n  cols?: arrow.Vector[];\n  rows: RowData;\n  fields: ProtoDatasetField[];\n};\n\nenum COLUMN_TYPES {\n  GEOMETRY = 'GEOMETRY',\n  BOOLEAN = 'BOOLEAN',\n  INTEGER = 'INTEGER',\n  FLOAT = 'FLOAT',\n  TIME = 'TIME',\n  DATE = 'DATE',\n  TIMESTAMP = 'TIMESTAMP',\n  VARCHAR = 'VARCHAR'\n}\n// https://duckdb.org/docs/data/csv/auto_detection.html#type-detection\nexport const COLUMN_TYPES_PRIORITIES = [\n  'GEOMETRY',\n  'BOOLEAN',\n  'INTEGER',\n  'FLOAT',\n  'TIME',\n  'DATE',\n  'TIMESTAMP',\n  'VARCHAR'\n];\n\nexport function columnTypeToFieldType(columnType: string): string | null {\n  switch (columnType) {\n    case COLUMN_TYPES.BOOLEAN:\n      return ALL_FIELD_TYPES.boolean;\n    case COLUMN_TYPES.DATE:\n      return ALL_FIELD_TYPES.date;\n    case COLUMN_TYPES.INTEGER:\n      return ALL_FIELD_TYPES.integer;\n    case COLUMN_TYPES.FLOAT:\n      return ALL_FIELD_TYPES.real;\n    case COLUMN_TYPES.VARCHAR:\n      return ALL_FIELD_TYPES.string;\n    case COLUMN_TYPES.TIMESTAMP:\n      return ALL_FIELD_TYPES.timestamp;\n    case COLUMN_TYPES.GEOMETRY:\n      return ALL_FIELD_TYPES.geojson;\n    default:\n      // is there any other type we didn't cover?\n      console.warn(`Unsupported DuckDB Column type: ${columnType}`);\n      return null;\n  }\n}\n\nexport function fieldTypeToColumnType(fieldType: string): string | null {\n  switch (fieldType) {\n    case ALL_FIELD_TYPES.boolean:\n      return COLUMN_TYPES.BOOLEAN;\n    case ALL_FIELD_TYPES.date:\n      return COLUMN_TYPES.DATE;\n    case ALL_FIELD_TYPES.integer:\n      return COLUMN_TYPES.INTEGER;\n    case ALL_FIELD_TYPES.real:\n      return COLUMN_TYPES.FLOAT;\n    case ALL_FIELD_TYPES.string:\n      return COLUMN_TYPES.VARCHAR;\n    case ALL_FIELD_TYPES.timestamp:\n      return COLUMN_TYPES.TIMESTAMP;\n    case ALL_FIELD_TYPES.geojson:\n      return COLUMN_TYPES.GEOMETRY;\n    default:\n      // is there any other type we didn't cover?\n      console.warn(`Unsupported Field type for DuckDb: ${fieldType}`);\n      return null;\n  }\n}\n\n/*\n * Process uploaded exported Keplergl json map.\n * We need to populate field with proper DuckDb compatible type.\n */\n// TODO: merge with logic from processCsvRowObject. Different formats: [[]] vs [{}]\nexport async function processKeplerglJSONforDuckDb(\n  keplerJson\n): Promise<ReturnType<typeof processKeplerglJSON>> {\n  const res = processKeplerglJSON(keplerJson);\n\n  const datasets = res?.datasets || [];\n  for (const dataset of datasets) {\n    const rowsAll = dataset.data.rows;\n    const fieldsAll = dataset.data.fields;\n\n    const header = fieldsAll.map(f => f.name);\n    const sample = getSampleForTypeAnalyze({fields: header, rows: rowsAll});\n\n    const schema =\n      sample.length > 0\n        ? await sniffCsvSchema(sample)\n        : fieldsAll.reduce((acc, field) => {\n            acc[field.name] = fieldTypeToColumnType(field.type);\n            return acc;\n          }, {});\n    const fieldsUpd = consolidateFieldTypes(dataset.data.fields, schema);\n\n    dataset.data.fields = fieldsAll.map((f, i) => {\n      return {...fieldsUpd[i], ...f};\n    });\n  }\n\n  return res;\n}\n\n/*\n * Process uploaded csv returned by loaders.gl as row object and string value\n */\nexport async function processCsvRowObject(\n  rawData: Record<string, string | null>[]\n): Promise<ProcessorResult> {\n  if (!Array.isArray(rawData) || !rawData.length) {\n    return {\n      fields: [],\n      rows: []\n    };\n  }\n\n  const header = Object.keys(rawData[0]); // [lat, lng, value]\n\n  // row object can still contain values like `Null` or `N/A`\n  cleanUpFalsyCsvValue(rawData);\n  const sample = getSampleForTypeAnalyze({fields: header, rows: rawData});\n  // add sample data to duckdb and get schema\n  const schema = await sniffCsvSchema(sample);\n\n  const fieldsWAnalyzerType = getFieldsFromData(sample, header);\n\n  const fields = consolidateFieldTypes(fieldsWAnalyzerType, schema);\n\n  return {rows: rawData, fields};\n}\n\nfunction cleanUpFalsyCsvValue(rows: RowData): void {\n  const re = new RegExp(CSV_NULLS, 'g');\n  for (let i = 0; i < rows.length; i++) {\n    Object.keys(rows[i]).forEach(key => {\n      // here we parse empty data as null\n      if (typeof rows[i][key] === 'string' && (rows[i][key] as string).match(re)) {\n        rows[i][key] = null;\n      }\n    });\n  }\n}\n\n// align type analyzer types with DuckDB csv auto detected column types\nexport function consolidateFieldTypes(fields, schema) {\n  return fields.map(field => {\n    const columnName = field.name;\n\n    const detectedColumnType = schema[columnName];\n\n    // TODO columnTypeToFieldType tranforms detected timestamps to string,\n    // completely breaking time filter logic.\n    // const fieldType = columnTypeToFieldType(detectedColumnType) || field.type;\n\n    return {\n      ...field,\n      // type: fieldType,\n      duckDBColumnType: detectedColumnType\n    };\n  });\n}\n\nfunction toCSVRow(row: unknown[]): string {\n  return `${row\n    .map(r => {\n      const rToStr = notNullorUndefined(r) ? String(r).replace(/\"/g, '\\\\\"') : '';\n      return rToStr.includes(',') ? `\"${rToStr}\"` : rToStr;\n    })\n    .join(',')}\\n`;\n}\n\n// Use DucckDB to detect csv column schema\nasync function sniffCsvSchema(sample: RowData) {\n  if (!Array.isArray(sample) || sample.length < 0) {\n    return;\n  }\n  const headerRow = toCSVRow(Object.keys(sample[0]));\n\n  const csvContent = (sample as (any[] | Record<string, unknown>)[]).reduce(\n    (accu: string, row) => `${accu}${toCSVRow(Object.values(row))}`,\n    headerRow\n  );\n\n  const db = getApplicationConfig().database;\n  if (!db) {\n    console.error('The database is not configured properly.');\n    return;\n  }\n  const c = await db.connect();\n\n  const fileName = generateHashId();\n  await db.registerFileText(`${fileName}-${generateHashId}.csv`, csvContent);\n\n  // https://duckdb.org/docs/data/csv/auto_detection.html\n  const result = await c.query(`\n        FROM sniff_csv('${fileName}-${generateHashId}.csv', \n        sample_size = 500,\n        auto_type_candidates = ['FLOAT', 'INTEGER', 'TIMESTAMP', 'DATE', 'TIME', 'VARCHAR', 'BOOLEAN']);\n    `);\n\n  const schemaResult = result.toArray().map(row => row.toJSON());\n\n  const columns = schemaResult[0].Columns;\n  const schema = {};\n  for (let i = 0; i < columns.length; i++) {\n    schema[columns.get(i).name] = columns.get(i).type;\n  }\n\n  return schema;\n}\n\nexport function processGeojson(rawData: unknown): ProcessorResult {\n  const normalizedGeojson = normalize(rawData);\n\n  if (!normalizedGeojson || !Array.isArray(normalizedGeojson.features)) {\n    const error = new Error(\n      `Read File Failed: File is not a valid GeoJSON. Read more about [supported file format](${GUIDES_FILE_FORMAT_DOC})`\n    );\n    throw error;\n  }\n\n  // @ts-expect-error Don't pass empty fields, as duck db outputs an empty dataset\n  return {\n    rows: normalizedGeojson\n    // TODO get fields to preserve field names?\n    // fields: []\n  };\n}\n\n/**\n * A utility function to extend existing fields with native DuckDB column type.\n * @param fields Array of fields to extend.\n * @param typeOverrides An optional mapping of DuckDB column types to override.\n * @returns An array of fields with the DuckDB column type extended.\n */\nexport function extendFieldsWithDuckDBColumnType(\n  fields: ProtoDatasetField[],\n  typeOverrides: Record<string, string>\n): Promise<ProtoDatasetField[]> {\n  const schema = fields.reduce((acc, field) => {\n    acc[field.name] = fieldTypeToColumnType(field.type);\n    return acc;\n  }, {});\n\n  const updatedFields = consolidateFieldTypes(fields, schema);\n  updatedFields.forEach(field => {\n    if (typeOverrides[field.duckDBColumnType]) {\n      field.duckDBColumnType = typeOverrides[field.duckDBColumnType];\n    }\n  });\n  return updatedFields;\n}\n"
  },
  {
    "path": "src/duckdb/src/table/duckdb-table-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// Copied from loaders.gl/geoarrow\n\n// TODO: Remove isGeoArrow* once Kepler.gl is upgraded to loaders.gl 4.4+\n\nimport * as arrow from 'apache-arrow';\nimport {DataType} from 'apache-arrow/type';\nimport {DuckDBDataProtocol} from '@duckdb/duckdb-wasm';\n\nimport {GEOARROW_EXTENSIONS, GEOARROW_METADATA_KEY} from '@kepler.gl/constants';\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport {DatabaseConnection, getApplicationConfig} from '@kepler.gl/utils';\n\nexport const SUPPORTED_DUCKDB_DROP_EXTENSIONS = ['arrow', 'csv', 'geojson', 'json', 'parquet'];\n\nexport type DuckDBColumnDesc = {name: string; type: string};\n\n/**\n * Queries a DuckDB table for the schema description.\n * @param connection An active DuckDB connection.\n * @param tableName A name of DuckDB table to query.\n * @returns An array of column names and DuckDB types.\n */\nexport async function getDuckDBColumnTypes(\n  connection: DatabaseConnection,\n  tableName: string\n): Promise<DuckDBColumnDesc[]> {\n  const quotedTableName = quoteTableName(tableName);\n  const duckDbTypes: DuckDBColumnDesc[] = [];\n  try {\n    // PRAGMA table_info is less likely to bind/execute view SQL than DESCRIBE,\n    // so it avoids triggering remote access (e.g., S3) for view-backed schemas.\n    const resInfo = await connection.query(\n      `PRAGMA table_info(${quotedTableName})`\n    );\n    const numRows = resInfo.numRows;\n    const columnNames = resInfo.getChild('name');\n    const columnTypes = resInfo.getChild('type');\n    for (let i = 0; i < numRows; ++i) {\n      duckDbTypes.push({\n        name: columnNames?.get(i),\n        type: columnTypes?.get(i)\n      });\n    }\n  } catch (primaryError) {\n    try {\n      const resDescribe = await connection.query(`DESCRIBE ${quotedTableName}`);\n      const numRows = resDescribe.numRows;\n      for (let i = 0; i < numRows; ++i) {\n        const columnName = resDescribe.getChildAt(0)?.get(i);\n        const columnType = resDescribe.getChildAt(1)?.get(i);\n\n        duckDbTypes.push({\n          name: columnName,\n          type: columnType\n        });\n      }\n    } catch (fallbackError) {\n      const error = new Error(\n        `[DuckDB] Failed to load column types for ${tableName} (PRAGMA + DESCRIBE).`\n      );\n      (error as Error & {cause?: unknown}).cause = {\n        primaryError,\n        fallbackError\n      };\n      throw error;\n    }\n  }\n\n  return duckDbTypes;\n}\n\n/**\n * Generates a mapping of column names to their corresponding DuckDB data types.\n * @param columns An array of column descriptions from DuckDB. Check getDuckDBColumnTypes.\n * @returns A record where keys are column names and values are their data types.\n */\nexport function getDuckDBColumnTypesMap(columns: DuckDBColumnDesc[]) {\n  return columns.reduce((acc, value) => {\n    acc[value.name] = value.type;\n    return acc;\n  }, {} as Record<string, string>);\n}\n\n/**\n * Quotes a table name for safe SQL usage.\n * Always quotes to handle all edge cases (spaces, special characters, reserved words).\n * For fully qualified names (containing dots), preserves the existing structure.\n * @param tableName The table name to quote.\n * @returns The table name, properly quoted.\n */\nexport function quoteTableName(tableName: string): string {\n  // Return as-is if:\n  // 1. It's already a properly quoted simple identifier (starts and ends with quotes)\n  // 2. It contains both dots and quotes (assume it's a qualified name)\n  if (\n    (tableName.startsWith('\"') && tableName.endsWith('\"')) ||\n    (tableName.includes('.') && tableName.includes('\"'))\n  ) {\n    return tableName;\n  }\n\n  return `\"${tableName.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Quotes a column name for safe SQL usage.\n * Always quotes to handle all edge cases (spaces, special characters, reserved words).\n * @param columnName The column name to quote.\n * @returns The column name, properly quoted.\n */\nfunction quoteColumnName(columnName: string): string {\n  return `\"${columnName.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Constructs an SQL query to select all columns from a given table,\n * converting specified columns to Well-Known Binary (WKB) format using ST_AsWKB,\n * and casting BIGINT columns to DOUBLE if specified.\n * @param tableName The name of the table from which to select data.\n * @param columns An array of column descriptors, each with a type and name.\n * @param options Optional parameters to control the conversion behavior.\n * @returns The constructed SQL query.\n */\nexport function castDuckDBTypesForKepler(\n  tableName: string,\n  columns: DuckDBColumnDesc[],\n  options = {geometryToWKB: true, bigIntToDouble: true}\n): string {\n  const modifiedColumns = columns.map(column => {\n    const {name, type} = column;\n    const quotedColumnName = quoteColumnName(name);\n    if (type === 'GEOMETRY' && options.geometryToWKB) {\n      return `ST_AsWKB(${quotedColumnName}) as ${quotedColumnName}`;\n    } else if (\n      options.bigIntToDouble &&\n      (type === 'BIGINT' ||\n        type === 'UBIGINT' ||\n        type === 'HUGEINT' ||\n        type === 'UHUGEINT' ||\n        type.startsWith('DECIMAL'))\n    ) {\n      // Cast 64-bit and larger integer types and DECIMAL to DOUBLE to avoid BigInt in JS\n      return `CAST(${quotedColumnName} AS DOUBLE) as ${quotedColumnName}`;\n    }\n    return quotedColumnName;\n  });\n\n  const quotedTableName = quoteTableName(tableName);\n  return `SELECT ${modifiedColumns.join(', ')} FROM ${quotedTableName}`;\n}\n\n/**\n * Sets the GeoArrow WKB extension metadata for columns of type GEOMETRY in an Arrow table.\n * @param table The Apache Arrow table whose schema fields will be modified.\n * @param columns An array of column descriptors from a DuckDB table.\n */\nexport function setGeoArrowWKBExtension(table: arrow.Table, columns: DuckDBColumnDesc[]) {\n  table.schema.fields.forEach(field => {\n    const info = columns.find(t => t.name === field.name);\n    if (info?.type === 'GEOMETRY') {\n      field.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.WKB);\n    }\n  });\n}\n\n/**\n * Creates an arrow table from an array of arrow vectors and fields.\n * @param columns An array of arrow vectors.\n * @param fields An array of fields per arrow vector.\n * @param arrowSchema Optional arrow table schema when available.\n * @returns An arrow table.\n */\nexport const restoreArrowTable = (\n  columns: arrow.Vector[],\n  fields: ProtoDatasetField[],\n  arrowSchema?: arrow.Schema\n) => {\n  const creaOpts = {};\n  fields.map((field, index) => {\n    creaOpts[field.name] = columns[index];\n  });\n\n  return arrowSchema ? new arrow.Table(arrowSchema, creaOpts) : new arrow.Table(creaOpts);\n};\n\n/**\n * DuckDb throws when geoarrow extensions are present in metadata.\n * @param table An arrow table to clear from extensions.\n * @returns A map of removed per field geoarrow extensions.\n */\nexport const removeUnsupportedExtensions = (table: arrow.Table): Record<string, string> => {\n  const removedMetadata: Record<string, string> = {};\n  table.schema.fields.forEach(field => {\n    const extension = field.metadata.get(GEOARROW_METADATA_KEY);\n    if (extension?.startsWith('geoarrow')) {\n      removedMetadata[field.name] = extension;\n      field.metadata.delete(GEOARROW_METADATA_KEY);\n    }\n  });\n  return removedMetadata;\n};\n\n/**\n * Restore removed metadata extensions after a call to removeUnsupportedExtensions.\n * @param table An arrow table to restore geoarrow extensions.\n * @param removedExtensions A map of per field geoarrow extensions to restore.\n */\nexport const restoreUnsupportedExtensions = (\n  table: arrow.Table,\n  removedExtensions: Record<string, string>\n) => {\n  table.schema.fields.forEach(field => {\n    const extension = removedExtensions[field.name];\n    if (extension) {\n      field.metadata.set(GEOARROW_METADATA_KEY, extension);\n    }\n  });\n};\n\n/** Checks whether the given Apache Arrow JS type is a Point data type */\nexport function isGeoArrowPoint(type: DataType) {\n  if (DataType.isFixedSizeList(type)) {\n    // Check list size\n    if (![2, 3, 4].includes(type.listSize)) {\n      return false;\n    }\n\n    // Check child of FixedSizeList is floating type\n    if (!DataType.isFloat(type.children[0])) {\n      return false;\n    }\n\n    return true;\n  }\n\n  return false;\n}\n\n/** Checks whether the given Apache Arrow JS type is a Point data type */\nexport function isGeoArrowLineString(type: DataType) {\n  // Check the outer type is a List\n  if (!DataType.isList(type)) {\n    return false;\n  }\n\n  // Check the child is a point type\n  if (!isGeoArrowPoint(type.children[0].type)) {\n    return false;\n  }\n\n  return true;\n}\n\n/** Checks whether the given Apache Arrow JS type is a Polygon data type */\nexport function isGeoArrowPolygon(type: DataType) {\n  // Check the outer vector is a List\n  if (!DataType.isList(type)) {\n    return false;\n  }\n\n  // Check the child is a linestring vector\n  if (!isGeoArrowLineString(type.children[0].type)) {\n    return false;\n  }\n\n  return true;\n}\n\n/** Checks whether the given Apache Arrow JS type is a Polygon data type */\nexport function isGeoArrowMultiPoint(type: DataType) {\n  // Check the outer vector is a List\n  if (!DataType.isList(type)) {\n    return false;\n  }\n\n  // Check the child is a point vector\n  if (!isGeoArrowPoint(type.children[0].type)) {\n    return false;\n  }\n\n  return true;\n}\n\n/** Checks whether the given Apache Arrow JS type is a Polygon data type */\nexport function isGeoArrowMultiLineString(type: DataType) {\n  // Check the outer vector is a List\n  if (!DataType.isList(type)) {\n    return false;\n  }\n\n  // Check the child is a linestring vector\n  if (!isGeoArrowLineString(type.children[0].type)) {\n    return false;\n  }\n\n  return true;\n}\n\n/** Checks whether the given Apache Arrow JS type is a Polygon data type */\nexport function isGeoArrowMultiPolygon(type: DataType) {\n  // Check the outer vector is a List\n  if (!DataType.isList(type)) {\n    return false;\n  }\n\n  // Check the child is a polygon vector\n  if (!isGeoArrowPolygon(type.children[0].type)) {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Checks if the given SQL query is a SELECT query by using the EXPLAIN command.\n * @param connection The DuckDB connection instance.\n * @param query The SQL query to check.\n * @returns Resolves to `true` if the query is a SELECT statement, otherwise `false`.\n */\nexport async function checkIsSelectQuery(\n  connection: DatabaseConnection,\n  query: string\n): Promise<boolean> {\n  try {\n    const result = await connection.query(`EXPLAIN (${query})`);\n    return result.numRows > 0;\n  } catch (error) {\n    return false;\n  }\n}\n\n/**\n * Split a string with potentially multiple SQL queries (separated as usual by ';') into an array of queries.\n * This implementation:\n *  - Handles single and double quoted strings with proper escaping\n *  - Ignores semicolons in line comments (--) and block comments (slash asterisk)\n *  - Trims whitespace from queries\n *  - Handles SQL-style escaped quotes ('' inside strings)\n *  - Returns only non-empty queries\n * @param input A string with potentially multiple SQL queries.\n * @returns An array of queries.\n */\nexport function splitSqlStatements(input: string): string[] {\n  const queries: string[] = [];\n  let currentQuery = '';\n  let inSingleQuote = false;\n  let inDoubleQuote = false;\n  let inLineComment = false;\n  let inBlockComment = false;\n\n  for (let i = 0; i < input.length; i++) {\n    const char = input[i];\n\n    if (inLineComment) {\n      currentQuery += char;\n      if (char === '\\n') {\n        inLineComment = false;\n      }\n      continue;\n    }\n\n    if (inBlockComment) {\n      currentQuery += char;\n      if (char === '*' && input[i + 1] === '/') {\n        inBlockComment = false;\n        currentQuery += input[++i]; // Consume '/'\n      }\n      continue;\n    }\n\n    if (inSingleQuote) {\n      currentQuery += char;\n      if (char === \"'\") {\n        // Handle escaped single quotes in SQL\n        if (i + 1 < input.length && input[i + 1] === \"'\") {\n          currentQuery += input[++i];\n        } else {\n          inSingleQuote = false;\n        }\n      }\n      continue;\n    }\n\n    if (inDoubleQuote) {\n      currentQuery += char;\n      if (char === '\"') {\n        // Handle escaped double quotes\n        if (i + 1 < input.length && input[i + 1] === '\"') {\n          currentQuery += input[++i];\n        } else {\n          inDoubleQuote = false;\n        }\n      }\n      continue;\n    }\n\n    // Check for comment starts\n    if (char === '-' && input[i + 1] === '-') {\n      inLineComment = true;\n      currentQuery += char + input[++i];\n      continue;\n    }\n\n    if (char === '/' && input[i + 1] === '*') {\n      inBlockComment = true;\n      currentQuery += char + input[++i];\n      continue;\n    }\n\n    // Check for quote starts\n    if (char === \"'\") {\n      inSingleQuote = true;\n      currentQuery += char;\n      continue;\n    }\n\n    if (char === '\"') {\n      inDoubleQuote = true;\n      currentQuery += char;\n      continue;\n    }\n\n    // Handle query separator\n    if (char === ';') {\n      const trimmed = currentQuery.trim();\n      if (trimmed.length > 0) {\n        queries.push(trimmed);\n      }\n      currentQuery = '';\n      continue;\n    }\n\n    currentQuery += char;\n  }\n\n  // Add the final query\n  const trimmed = currentQuery.trim();\n  if (trimmed.length > 0) {\n    queries.push(trimmed);\n  }\n\n  return queries;\n}\n\n/**\n * Removes SQL comments from a given SQL string.\n * @param sql The SQL query string from which comments should be removed.\n * @returns The cleaned SQL string without comments.\n */\nexport function removeSQLComments(sql: string): string {\n  // Remove multi-line comments (/* ... */)\n  sql = sql.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n  // Remove single-line comments (-- ...)\n  sql = sql.replace(/--.*$/gm, '');\n  return sql.trim();\n}\n\n/**\n * Drops a table if it exists in the DuckDB database.\n * @param connection The DuckDB connection instance.\n * @param tableName The name of the table to drop.\n * @returns A promise that resolves when the operation is complete.\n * @throws Logs an error if the table drop operation fails.\n */\nexport const dropTableIfExists = async (connection: DatabaseConnection, tableName: string) => {\n  try {\n    await connection.query(`DROP TABLE IF EXISTS \"${tableName}\";`);\n  } catch (error) {\n    console.error('Dropping table failed', tableName, error);\n  }\n};\n\n/**\n * Imports a file into DuckDB as a table, supporting multiple formats from SUPPORTED_DUCKDB_DROP_EXTENSIONS.\n * @param file The file to be imported.\n * @returns A promise that resolves when the file has been processed into a DuckDB table.\n */\nexport async function tableFromFile(file: File | null): Promise<null | Error> {\n  if (!file) return new Error('File Drag & Drop: No file');\n\n  const fileExt = SUPPORTED_DUCKDB_DROP_EXTENSIONS.find(ext => file.name.endsWith(ext));\n  if (!fileExt) {\n    return new Error(\"File Drag & Drop: File extension isn't supported\");\n  }\n\n  const db = await getApplicationConfig().database;\n  if (!db) {\n    return new Error('The database is not configured properly.');\n  }\n  const c = await db.connect();\n\n  let error: Error | null = null;\n\n  try {\n    const tableName = sanitizeDuckDBTableName(file.name);\n    const sourceName = 'temp_file_handle';\n\n    c.query(`install spatial;\n      load spatial;`);\n\n    if (fileExt === 'arrow') {\n      const arrayBuffer = await file.arrayBuffer();\n      const uint8Array = new Uint8Array(arrayBuffer);\n      const arrowTable = arrow.tableFromIPC(uint8Array);\n\n      await c.insertArrowTable(arrowTable, {name: tableName});\n    } else {\n      await db.registerFileHandle(sourceName, file, DuckDBDataProtocol.BROWSER_FILEREADER, true);\n\n      if (fileExt === 'csv') {\n        await c.query(`\n            CREATE TABLE '${tableName}' AS\n            SELECT *\n            FROM read_csv('${sourceName}', header = true, auto_detect = true, sample_size = -1);\n          `);\n      } else if (fileExt === 'json') {\n        await c.query(`\n            CREATE TABLE '${tableName}' AS\n            SELECT *\n            FROM read_json_auto('${sourceName}');\n          `);\n      } else if (fileExt === 'geojson') {\n        await c.query(`\n            CREATE TABLE '${tableName}' AS\n            SELECT *\n            FROM ST_READ('${sourceName}', keep_wkb = TRUE);\n          `);\n      } else if (fileExt === 'parquet') {\n        await c.query(`\n            CREATE TABLE '${tableName}' AS\n            SELECT *\n            FROM read_parquet('${sourceName}')\n          `);\n      }\n    }\n  } catch (errorData) {\n    if (errorData instanceof Error) {\n      const message = errorData.message || '';\n      // output more readable errors for known issues\n      if (message.includes('Arrow Type with extension name: geoarrow')) {\n        error = new Error(\n          'The GeoArrow extensions are not implemented in the connected DuckDB version.'\n        );\n      } else if (message.includes(\"Geoparquet column 'geometry' does not have geometry types\")) {\n        error = new Error(\n          `Invalid Input Error: Geoparquet column 'geometry' does not have geometry types.\nPossible reasons:\n  - Old .parquet files that don't match the Parquet format specification.\n  - Unsupported compression.`\n        );\n      }\n    }\n\n    if (!error) {\n      error = errorData as Error;\n    }\n  }\n\n  await c.close();\n\n  return error;\n}\n\n/**\n * Sanitizes a file name to be a valid DuckDB table name.\n * @param fileName The input file name to be sanitized.\n * @returns A valid DuckDB table name.\n */\nexport function sanitizeDuckDBTableName(fileName: string): string {\n  // Replace invalid characters with underscores\n  let name = fileName.replace(/[^a-zA-Z0-9_]/g, '_');\n  // Ensure it doesn't start with a digit\n  if (/^\\d/.test(name)) {\n    name = `t_${name}`;\n  }\n  return name || 'default_table';\n}\n"
  },
  {
    "path": "src/duckdb/src/table/duckdb-table.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\n\nimport {\n  DatasetType,\n  DATASET_FORMATS,\n  GEOARROW_EXTENSIONS,\n  GEOARROW_METADATA_KEY\n} from '@kepler.gl/constants';\nimport {\n  arrowSchemaToFields,\n  isArrowData,\n  isGeoJson,\n  isKeplerGlMap,\n  isRowObject,\n  processArrowBatches\n} from '@kepler.gl/processors';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {Field} from '@kepler.gl/types';\nimport {\n  getApplicationConfig,\n  DatabaseAdapter,\n  DatabaseConnection,\n  isArrowTable,\n  isArrowVector\n} from '@kepler.gl/utils';\n\nimport {\n  processCsvRowObject,\n  processGeojson,\n  processKeplerglJSONforDuckDb,\n  ProcessorResult\n} from '../processors/data-processor';\n\nimport {\n  isGeoArrowPoint,\n  isGeoArrowLineString,\n  isGeoArrowPolygon,\n  isGeoArrowMultiPoint,\n  isGeoArrowMultiLineString,\n  isGeoArrowMultiPolygon\n} from './duckdb-table-utils';\n\nimport {\n  castDuckDBTypesForKepler,\n  dropTableIfExists,\n  getDuckDBColumnTypes,\n  getDuckDBColumnTypesMap,\n  removeUnsupportedExtensions,\n  restoreArrowTable,\n  restoreUnsupportedExtensions\n} from '../table/duckdb-table-utils';\n\n// TODO use files from disk or url directly, without parsing by loaders and then ingection into DeckDb\n\n/**\n * Default DuckDb geometry columns created by ST_READ\n */\nconst DUCKDB_GEOM_COLUMN = 'geom';\nconst DUCKDB_WKB_COLUMN = 'wkb_geometry';\n\n/**\n Default column name for processed geojson in Kepler.\n Use this name to rename default 'geom' column from DuckDb\n in order to support Kepler maps saved before introduction of DuckDb plugin.\n */\nconst KEPLER_GEOM_FROM_GEOJSON_COLUMN = '_geojson';\n\n/**\n * Names of columns that most likely contain binary wkb geometry\n */\nconst SUGGESTED_GEOM_COLUMNS = {\n  [KEPLER_GEOM_FROM_GEOJSON_COLUMN]: GEOARROW_EXTENSIONS.WKB,\n  [DUCKDB_GEOM_COLUMN]: GEOARROW_EXTENSIONS.WKB,\n  geometry: GEOARROW_EXTENSIONS.WKB\n};\n\ntype ImportDataToDuckProps = {\n  data: ProcessorResult & {arrowSchema: arrow.Schema};\n  db: DatabaseAdapter;\n  c: DatabaseConnection;\n};\n\ntype ImportDataToDuckResult = {\n  // DuckDb drops geoarrow metadata, so try to preserve and then restore the extension name\n  geoarrowMetadata?: Record<string, string>;\n  // Use fields from arrow table even if fields are provided\n  useNewFields?: boolean;\n};\n\nexport class KeplerGlDuckDbTable extends KeplerTable {\n  declare readonly id: string;\n  declare label: string;\n  declare type: string;\n  declare metadata: Record<string, any>;\n\n  constructor(props) {\n    super(props);\n  }\n\n  async importRowData({data, db, c}: ImportDataToDuckProps): Promise<void> {\n    try {\n      const {fields, rows} = data;\n\n      // DATASET_FORMATS.keplergl loads data as any[][] instead of [{}]\n      // TODO potential memory usage explosion: original data, new object and then DuckDb table\n      const rowsForDb = Array.isArray(rows[0])\n        ? rows.map(row => {\n            const newRow = {};\n            row.forEach((value, index) => {\n              newRow[fields[index].name] = value;\n            });\n            return newRow;\n          })\n        : rows;\n\n      await db.registerFileText(this.id, JSON.stringify(rowsForDb));\n\n      const columns = fields.reduce((acc, field, index) => {\n        // @ts-expect-error TODO extend fields to contain duckDBColumnType\n        return `${acc}${index > 0 ? ',' : ''} '${field.name}': '${field.duckDBColumnType}'`;\n      }, '');\n\n      const createTableSql = `\n        CREATE TABLE '${this.label}' AS\n        SELECT *\n        FROM read_json('${this.id}',\n                       columns = {${columns}});\n      `;\n      await c.query(createTableSql);\n    } catch (error) {\n      console.log('importRowData', error);\n      throw error;\n    }\n  }\n\n  async importGeoJsonData({data, db, c}: ImportDataToDuckProps): Promise<ImportDataToDuckResult> {\n    try {\n      const {rows} = data;\n      await db.registerFileText(this.id, JSON.stringify(rows));\n\n      const createTableSql = `\n        install spatial;\n        load spatial;\n        CREATE TABLE '${this.label}' AS\n        SELECT *\n        FROM ST_READ('${this.id}', keep_wkb = TRUE);\n        ALTER TABLE '${this.label}' RENAME '${DUCKDB_WKB_COLUMN}' TO '${KEPLER_GEOM_FROM_GEOJSON_COLUMN}';\n      `;\n\n      await c.query(createTableSql);\n    } catch (error) {\n      console.error('importGeoJsonData', error);\n      throw error;\n    }\n\n    return {\n      // _geojson column is created from geometry with keep_wkb flag and contains valid WKB data.\n      geoarrowMetadata: {[KEPLER_GEOM_FROM_GEOJSON_COLUMN]: GEOARROW_EXTENSIONS.WKB}\n    };\n  }\n\n  async importArrowData({data, c}: ImportDataToDuckProps): Promise<ImportDataToDuckResult> {\n    let adjustedMetadata = {};\n    try {\n      // 1) data.rows contains an arrow table created by Add to Map data from DuckDb query.\n      // 2) arrow table is in cols & fields when a file is dragged & dropped into Add Data To Map dialog.\n      const arrowTable = isArrowTable(data.rows)\n        ? data.rows\n        : restoreArrowTable(data.cols || [], data.fields, data.arrowSchema);\n\n      // remove unsupported extensions from an arrow table that throw exceptions in DuckDB.\n      adjustedMetadata = removeUnsupportedExtensions(arrowTable);\n\n      const setupSql = `\n        install spatial;\n        load spatial;\n      `;\n      await c.query(setupSql);\n      await c.insertArrowTable(arrowTable, {name: this.label});\n\n      // restore unsupported extensions that throw exceptions in DuckDb\n      restoreUnsupportedExtensions(arrowTable, adjustedMetadata);\n    } catch (error) {\n      // Known issues:\n      // 1) Arrow Type with extension name: geoarrow.point and format: +w:2 is not currently supported in DuckDB.\n      console.error('importArrowData', error);\n      throw error;\n    }\n\n    return {\n      geoarrowMetadata: {...SUGGESTED_GEOM_COLUMNS, ...adjustedMetadata},\n      // use fields generated from the created arrow table\n      useNewFields: true\n    };\n  }\n\n  /**\n   * Creates a table from data, returns an arrow table with the data\n   * @param data\n   * @returns {Promise<{fields: Field[], cols: any[]}>}\n   */\n  protected async createTableAndGetArrow(data): Promise<{fields: any[]; cols: arrow.Vector[]}> {\n    const db = await getApplicationConfig().database;\n    if (!db) {\n      console.error('The database is not configured properly.');\n      return {fields: [], cols: []};\n    }\n    const c = await db.connect();\n\n    const tableName = this.label;\n    await dropTableIfExists(c, tableName);\n\n    let format = this.metadata.format;\n    if (!format) {\n      // format is missing when we load Kepler.gl examples\n      if (Array.isArray(data.rows?.[0]) || typeof data.rows?.[0] === 'object') {\n        format = DATASET_FORMATS.row;\n      } else if (data.rows?.type === 'FeatureCollection') {\n        format = DATASET_FORMATS.geojson;\n      } else if (isArrowVector(data.cols?.[0])) {\n        format = DATASET_FORMATS.arrow;\n      }\n    }\n\n    let importDetails: ImportDataToDuckResult | undefined;\n    if (format === DATASET_FORMATS.row) {\n      await this.importRowData({data, db, c});\n    } else if (format === DATASET_FORMATS.geojson) {\n      importDetails = await this.importGeoJsonData({data, db, c});\n    } else if (format === DATASET_FORMATS.arrow) {\n      importDetails = await this.importArrowData({data, db, c});\n    } else {\n      console.error('Unrecognized format', format);\n    }\n\n    let fields: Field[] = [];\n    let cols: arrow.Vector[] = [];\n\n    try {\n      const {geoarrowMetadata = {}, useNewFields = false} = importDetails || {};\n\n      const duckDbColumns = await getDuckDBColumnTypes(c, tableName);\n      const tableDuckDBTypes = getDuckDBColumnTypesMap(duckDbColumns);\n      const adjustedQuery = castDuckDBTypesForKepler(tableName, duckDbColumns);\n      const arrowResult = await c.query(adjustedQuery);\n\n      // TODO if format is an arrow table then just use the original one, instead of the new table from the query?\n\n      restoreGeoarrowMetadata(arrowResult, geoarrowMetadata);\n\n      fields = useNewFields\n        ? arrowSchemaToFields(arrowResult, tableDuckDBTypes)\n        : data.fields ?? arrowSchemaToFields(arrowResult, tableDuckDBTypes);\n      cols = [...Array(arrowResult.numCols).keys()]\n        .map(i => arrowResult.getChildAt(i))\n        .filter(col => col) as arrow.Vector[];\n    } catch (error) {\n      console.error('DuckDB table: createTableAndGetArrow', error);\n      throw error;\n    }\n\n    await c.close();\n\n    return {fields, cols};\n  }\n\n  async importData({data}: {data: ProcessorResult}): Promise<void> {\n    // VectorTile is a special case, ignore for now\n    if (this.type === DatasetType.VECTOR_TILE) {\n      super.importData({data: {...data, rows: []}});\n      return;\n    }\n\n    const {fields, cols} = await this.createTableAndGetArrow(data);\n\n    super.importData({data: {fields, cols, rows: []}});\n  }\n\n  async update(data) {\n    // VectorTile is a special case, ignore for now\n    if (this.type === DatasetType.VECTOR_TILE) {\n      super.importData({data});\n      return this;\n    }\n\n    const {cols} = await this.createTableAndGetArrow(data);\n\n    return super.update({cols, rows: [], fields: []});\n  }\n\n  static getFileProcessor = function (data: any, inputFormat?: string) {\n    let processor;\n    let format;\n    if (inputFormat === DATASET_FORMATS.arrow || isArrowData(data)) {\n      format = DATASET_FORMATS.arrow;\n      processor = processArrowBatches;\n    } else if (inputFormat === DATASET_FORMATS.keplergl || isKeplerGlMap(data)) {\n      format = DATASET_FORMATS.keplergl;\n      processor = processKeplerglJSONforDuckDb;\n    } else if (inputFormat === DATASET_FORMATS.row || isRowObject(data)) {\n      // csv file goes here\n      format = DATASET_FORMATS.row;\n      processor = processCsvRowObject; // directly import json object into duckdb-wasm\n    } else if (inputFormat === DATASET_FORMATS.geojson || isGeoJson(data)) {\n      format = DATASET_FORMATS.geojson;\n      processor = processGeojson;\n    }\n    return {processor, format};\n  };\n\n  static getInputDataValidator = function () {\n    // In DuckDB mode data is validated later during import.\n    return d => d;\n  };\n}\n\n/**\n * Try to restore geoarrow metadata lost during DuckDb ingestion.\n * Note that this function can generate wrong geometry types.\n * @param arrowTable Arrow table to update.\n * @param geoarrowMetadata A map with field names that usually used to store geoarrow geometry.\n */\nexport const restoreGeoarrowMetadata = (\n  arrowTable: arrow.Table,\n  geoarrowMetadata: Record<string, string>\n) => {\n  arrowTable.schema.fields.forEach(f => {\n    if (arrow.DataType.isBinary(f.type) && geoarrowMetadata[f.name]) {\n      f.metadata.set(GEOARROW_METADATA_KEY, geoarrowMetadata[f.name]);\n    } else if (isGeoArrowPoint(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.POINT);\n    } else if (isGeoArrowLineString(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.LINESTRING);\n    } else if (isGeoArrowPolygon(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.POLYGON);\n    } else if (isGeoArrowMultiPoint(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.MULTIPOINT);\n    } else if (isGeoArrowMultiLineString(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.MULTILINESTRING);\n    } else if (isGeoArrowMultiPolygon(f.type)) {\n      f.metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.MULTIPOLYGON);\n    }\n  });\n};\n"
  },
  {
    "path": "src/duckdb/src/table/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './duckdb-table-utils';\n\n"
  },
  {
    "path": "src/duckdb/src/utils/perf.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// git@github.com:holdenmatt/duckdb-wasm-kit.git\nconst FgRed = '\\x1b[31m';\nconst ResetColors = '\\x1b[0m';\n\n/**\n * Format a time interval between start/end timestamps.\n *\n * If no end time is given, use the current time.\n */\nconst formatElapsedTime = (\n  label: string,\n  start: number,\n  end: number | undefined = undefined\n): string => {\n  const endTime = end ?? performance.now();\n  const elapsed = endTime - start;\n\n  let timeString: string;\n  switch (true) {\n    case elapsed >= 1000:\n      timeString = `${(elapsed / 1000).toFixed(1)}s`;\n      break;\n    case elapsed >= 1:\n      timeString = `${elapsed.toFixed(0)}ms`;\n      break;\n    default:\n      timeString = `${elapsed.toFixed(3)}ms`;\n      break;\n  }\n\n  const message = `${FgRed}[${timeString}] ${ResetColors}${label}`;\n  return message;\n};\n\n/**\n * Print the elapsed time to console.debug.\n */\nexport const logElapsedTime = (\n  label: string,\n  start: number,\n  end: number | undefined = undefined\n) => {\n  console.debug(formatElapsedTime(label, start, end));\n};\n"
  },
  {
    "path": "src/duckdb/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/effects/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/effects/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/effects\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl viaual effects\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@luma.gl/core\": \"^8.5.20\",\n    \"@luma.gl/shadertools\": \"^8.5.20\",\n    \"moment-timezone\": \"^0.5.35\",\n    \"suncalc\": \"^1.9.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/effects/src/custom-deck-lighting-effect.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck This is a hack, don't check types\n\n// import {console as Console} from 'global/window';\nimport {LightingEffect, shadow} from '@deck.gl/core';\nimport {Texture2D, ProgramManager} from '@luma.gl/core';\n\n/**\n * Inserts shader code before detected part.\n * @param {string} vs Original shader code.\n * @param {string} type Debug string.\n * @param {string} insertBeforeText Text chunk to insert before.\n * @param {string} textToInsert Text to insert.\n * @returns Modified shader code.\n */\nexport function insertBefore(vs, type, insertBeforeText, textToInsert) {\n  const at = vs.indexOf(insertBeforeText);\n  if (at < 0) {\n    // Console.error(`Cannot edit ${type} layer shader`);\n    return vs;\n  }\n\n  return vs.slice(0, at) + textToInsert + vs.slice(at);\n}\n\nconst CustomShadowModule = shadow ? {...shadow} : undefined;\n\n/**\n * Custom shadow module\n * 1) Add u_outputUniformShadow uniform\n * 2) always produce full shadow when the uniform is set to true.\n */\nCustomShadowModule.fs = insertBefore(\n  CustomShadowModule.fs,\n  'custom shadow #1',\n  'uniform vec4 shadow_uColor;',\n  'uniform bool u_outputUniformShadow;'\n);\n\nCustomShadowModule.fs = insertBefore(\n  CustomShadowModule.fs,\n  'custom shadow #1',\n  'vec4 rgbaDepth = texture2D(shadowMap, position.xy);',\n  'if(u_outputUniformShadow) return 1.0;'\n);\n\nCustomShadowModule.getUniforms = (opts = {}, context = {}) => {\n  const u = shadow.getUniforms(opts, context);\n  if (opts.outputUniformShadow !== undefined) {\n    u.u_outputUniformShadow = opts.outputUniformShadow;\n  }\n  return u;\n};\n\n/**\n * Custom LightingEffect\n * 1) adds CustomShadowModule\n * 2) pass outputUniformShadow as module parameters\n * 3) properly removes CustomShadowModule\n */\nclass CustomDeckLightingEffect extends LightingEffect {\n  constructor(props) {\n    super(props);\n    this.useOutputUniformShadow = false;\n  }\n\n  preRender(gl, {layers, layerFilter, viewports, onViewportActive, views}) {\n    if (!this.shadow) return;\n\n    // create light matrix every frame to make sure always updated from light source\n    this.shadowMatrices = this._calculateMatrices();\n\n    if (this.shadowPasses.length === 0) {\n      this._createShadowPasses(gl);\n    }\n    if (!this.programManager) {\n      this.programManager = ProgramManager.getDefaultProgramManager(gl);\n      if (CustomShadowModule) {\n        this.programManager.addDefaultModule(CustomShadowModule);\n      }\n    }\n\n    if (!this.dummyShadowMap) {\n      this.dummyShadowMap = new Texture2D(gl, {\n        width: 1,\n        height: 1\n      });\n    }\n\n    for (let i = 0; i < this.shadowPasses.length; i++) {\n      const shadowPass = this.shadowPasses[i];\n      shadowPass.render({\n        layers,\n        layerFilter,\n        viewports,\n        onViewportActive,\n        views,\n        moduleParameters: {\n          shadowLightId: i,\n          dummyShadowMap: this.dummyShadowMap,\n          shadowMatrices: this.shadowMatrices,\n          useOutputUniformShadow: false\n        }\n      });\n    }\n  }\n\n  getModuleParameters(layer) {\n    const parameters = super.getModuleParameters(layer);\n    parameters.outputUniformShadow = this.outputUniformShadow;\n    return parameters;\n  }\n\n  cleanup() {\n    for (const shadowPass of this.shadowPasses) {\n      shadowPass.delete();\n    }\n    this.shadowPasses.length = 0;\n    this.shadowMaps.length = 0;\n\n    if (this.dummyShadowMap) {\n      this.dummyShadowMap.delete();\n      this.dummyShadowMap = null;\n    }\n\n    if (this.shadow && this.programManager) {\n      this.programManager.removeDefaultModule(CustomShadowModule);\n      this.programManager = null;\n    }\n  }\n}\n\nexport default CustomDeckLightingEffect;\n"
  },
  {
    "path": "src/effects/src/effect.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {validateEffectParameters} from '@kepler.gl/utils';\nimport {generateHashId} from '@kepler.gl/common-utils';\nimport {\n  Effect as EffectInterface,\n  EffectProps,\n  EffectPropsPartial,\n  EffectParameterDescription\n} from '@kepler.gl/types';\nimport {\n  DEFAULT_POST_PROCESSING_EFFECT_TYPE,\n  POSTPROCESSING_EFFECTS,\n  LIGHT_AND_SHADOW_EFFECT\n} from '@kepler.gl/constants';\n\nexport class Effect implements EffectInterface {\n  id: string;\n  type: string;\n  isEnabled: boolean;\n  // effect specific parameters for a deck.gl effect (uniforms)\n  parameters: {[key: string]: any};\n  // runtime props\n  isConfigActive: boolean;\n  isJsonEditorActive: boolean;\n  deckEffect: any;\n  _uiConfig: EffectParameterDescription[];\n\n  constructor(props: EffectPropsPartial = {}) {\n    this.id = props.id || `e_${generateHashId(6)}`;\n\n    const _props = this.getDefaultProps(props);\n    this.type = _props.type;\n    this.isEnabled = _props.isEnabled;\n    this.isConfigActive = _props.isConfigActive;\n    this.isJsonEditorActive = _props.isJsonEditorActive;\n\n    this._uiConfig =\n      LIGHT_AND_SHADOW_EFFECT.type === this.type\n        ? LIGHT_AND_SHADOW_EFFECT.parameters\n        : POSTPROCESSING_EFFECTS[this.type]?.parameters || [];\n    this.parameters = validateEffectParameters(_props.parameters, this._uiConfig);\n\n    this.deckEffect = null;\n    this._initializeEffect();\n  }\n\n  _initializeEffect() {\n    // implemented in subclasses\n  }\n\n  clone(): Effect {\n    const props = this.getDefaultProps(this);\n    return new (this.constructor as new (props: EffectPropsPartial) => this)(props);\n  }\n\n  getDefaultProps(props: EffectPropsPartial = {}): EffectProps {\n    return {\n      id: props.id || `e_${generateHashId(6)}`,\n      type: props.type || DEFAULT_POST_PROCESSING_EFFECT_TYPE,\n      isEnabled: props.isEnabled ?? true,\n      isConfigActive: props.isConfigActive ?? true,\n      isJsonEditorActive: props.isJsonEditorActive ?? false,\n      parameters: {...props.parameters}\n    };\n  }\n\n  setProps(props: EffectPropsPartial) {\n    this.id = props.id ?? this.id;\n    this.type = props.type ?? this.type;\n    this.isEnabled = props.isEnabled ?? this.isEnabled;\n    this.isConfigActive = props.isConfigActive ?? this.isConfigActive;\n    this.isJsonEditorActive = props.isJsonEditorActive ?? this.isJsonEditorActive;\n    this.parameters = {\n      ...this.parameters,\n      ...validateEffectParameters(props.parameters, this._uiConfig)\n    };\n  }\n\n  isValidToSave() {\n    return Boolean(this.type && this.id && this.deckEffect);\n  }\n\n  /**\n   * Effect specific list of configurable parameters.\n   * @returns All parameters are in preffered order.\n   */\n  getParameterDescriptions() {\n    return this._uiConfig || [];\n  }\n}\n\nexport default Effect;\n"
  },
  {
    "path": "src/effects/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as Effect} from './effect';\nexport {createEffect} from './utils';\n"
  },
  {
    "path": "src/effects/src/lighting-effect.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {AmbientLight, _SunLight as SunLight} from '@deck.gl/core';\nimport moment from 'moment-timezone';\n\nimport {LIGHT_AND_SHADOW_EFFECT, DEFAULT_LIGHT_AND_SHADOW_PROPS} from '@kepler.gl/constants';\nimport {normalizeColor} from '@kepler.gl/utils';\nimport {EffectProps, EffectPropsPartial} from '@kepler.gl/types';\n\nimport Effect from './effect';\nimport CustomDeckLightingEffect from './custom-deck-lighting-effect';\n\nconst LIGHT_AND_SHADOW_EFFECT_DESC = {\n  ...LIGHT_AND_SHADOW_EFFECT,\n  class: null\n};\n\nclass LightingEffect extends Effect {\n  // deckEffect: PostProcessEffect | LightingEffect | null;\n\n  constructor(props: EffectPropsPartial) {\n    super(props);\n  }\n\n  _initializeEffect() {\n    this.parameters = {\n      ...DEFAULT_LIGHT_AND_SHADOW_PROPS,\n      timezone: moment.tz.guess(true),\n      ...this.parameters\n    };\n    const {parameters} = this;\n\n    const ambientLight = new AmbientLight({\n      color: parameters.ambientLightColor,\n      intensity: parameters.ambientLightIntensity\n    });\n\n    const sunLight = new SunLight({\n      timestamp: parameters.timestamp,\n      color: parameters.sunLightColor,\n      intensity: parameters.sunLightIntensity,\n      _shadow: true\n    });\n\n    this.deckEffect = new CustomDeckLightingEffect({\n      ambientLight,\n      sunLight\n    });\n    if (this.deckEffect) {\n      this.deckEffect.shadowColor = [\n        ...normalizeColor(parameters.shadowColor),\n        parameters.shadowIntensity\n      ];\n    }\n  }\n\n  getDefaultProps(props: EffectPropsPartial = {}): EffectProps {\n    return super.getDefaultProps({type: LIGHT_AND_SHADOW_EFFECT_DESC.type, ...props});\n  }\n\n  setProps(props: EffectPropsPartial) {\n    super.setProps(props);\n\n    // any uniform updated?\n    if (props.parameters) {\n      const {parameters} = this;\n\n      if (this.type === LIGHT_AND_SHADOW_EFFECT_DESC.type) {\n        /** @type {LightingEffect} */\n        const effect = this.deckEffect;\n        if (effect) {\n          effect.shadowColor = [\n            ...normalizeColor(parameters.shadowColor),\n            parameters.shadowIntensity\n          ];\n\n          effect.ambientLight.intensity = parameters.ambientLightIntensity;\n          effect.ambientLight.color = parameters.ambientLightColor.slice();\n\n          const sunLight = effect.directionalLights[0];\n          if (sunLight) {\n            sunLight.intensity = parameters.sunLightIntensity;\n            sunLight.color = parameters.sunLightColor.slice();\n            sunLight.timestamp = parameters.timestamp;\n          }\n        }\n      }\n    }\n  }\n}\n\nexport default LightingEffect;\n"
  },
  {
    "path": "src/effects/src/post-processing-effect.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {PostProcessEffect as DeckPostProcessEffect} from '@deck.gl/core';\nimport {\n  brightnessContrast,\n  ink,\n  triangleBlur,\n  hueSaturation,\n  vibrance,\n  colorHalftone,\n  dotScreen,\n  edgeWork,\n  noise,\n  sepia,\n  tiltShift,\n  vignette,\n  zoomBlur,\n  magnify,\n  hexagonalPixelate\n} from '@luma.gl/shadertools';\n\nimport {POSTPROCESSING_EFFECTS, DEFAULT_POST_PROCESSING_EFFECT_TYPE} from '@kepler.gl/constants';\nimport {EffectPropsPartial, EffectParameterDescription} from '@kepler.gl/types';\n\nimport Effect from './effect';\n\nconst POSTPROCESSING_EFFECTS_DESCS = [\n  {\n    ...POSTPROCESSING_EFFECTS.ink,\n    class: ink\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.brightnessContrast,\n    class: brightnessContrast\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.hueSaturation,\n    class: hueSaturation\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.vibrance,\n    class: vibrance\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.sepia,\n    class: sepia\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.dotScreen,\n    class: dotScreen\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.colorHalftone,\n    class: colorHalftone\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.noise,\n    class: noise\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.triangleBlur,\n    class: triangleBlur\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.zoomBlur,\n    class: zoomBlur\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.tiltShift,\n    class: tiltShift\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.edgeWork,\n    class: edgeWork\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.vignette,\n    class: vignette\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.magnify,\n    class: magnify\n  },\n  {\n    ...POSTPROCESSING_EFFECTS.hexagonalPixelate,\n    class: hexagonalPixelate\n  }\n];\n\n/**\n * Returns default parameter value based on effect description.\n * @param name Name of the parameter.\n * @param effectDescription Effect's description.\n * @param uniformsDesc Effect's uniforms.\n * @returns\n */\nexport const getDefaultValueForParameter = (\n  name: string,\n  effectDescription: EffectParameterDescription[],\n  uniformsDesc: any\n) => {\n  const description = effectDescription.find(param => param.name === name);\n  const uniform = uniformsDesc[name];\n  return description?.defaultValue ?? uniform?.value ?? uniform ?? description?.min;\n};\n\nclass PostProcessingEffect extends Effect {\n  // deckEffect: PostProcessEffect | LightingEffect | null;\n\n  constructor(props: EffectPropsPartial) {\n    super(props);\n  }\n\n  _initializeEffect() {\n    const effectDesc = POSTPROCESSING_EFFECTS_DESCS.find(desc => desc.type === this.type);\n    if (effectDesc) {\n      this.deckEffect = new DeckPostProcessEffect(effectDesc.class, this.parameters);\n\n      const uniforms = this.deckEffect?.module?.uniforms;\n      if (uniforms) {\n        // get default parameters\n        const keys = Object.keys(uniforms);\n        const defaultParameters = {};\n        keys.forEach(key => {\n          defaultParameters[key] = getDefaultValueForParameter(key, this._uiConfig, uniforms);\n        });\n        this.parameters = {...defaultParameters, ...this.parameters};\n        this.deckEffect?.setProps(this.parameters);\n      }\n    }\n  }\n\n  getDefaultProps(props: EffectPropsPartial = {}) {\n    return super.getDefaultProps({type: DEFAULT_POST_PROCESSING_EFFECT_TYPE, ...props});\n  }\n\n  setProps(props: EffectPropsPartial) {\n    super.setProps(props);\n\n    // any uniform updated?\n    if (props.parameters) {\n      this.deckEffect?.setProps(this.parameters);\n    }\n  }\n}\n\nexport default PostProcessingEffect;\n"
  },
  {
    "path": "src/effects/src/utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Effect as EffectInterface, EffectPropsPartial} from '@kepler.gl/types';\nimport {LIGHT_AND_SHADOW_EFFECT} from '@kepler.gl/constants';\n\nimport LightingEffect from './lighting-effect';\nimport PostProcessEffect from './post-processing-effect';\n\nexport function createEffect(params: EffectPropsPartial): EffectInterface {\n  if (params?.type === LIGHT_AND_SHADOW_EFFECT.type) {\n    return new LightingEffect(params);\n  }\n  return new PostProcessEffect(params);\n}\n"
  },
  {
    "path": "src/effects/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/effects/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/index.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './components';\n"
  },
  {
    "path": "src/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Reducers\nexport * from '@kepler.gl/reducers';\n\n// Schemas\nexport * from '@kepler.gl/schemas';\n\n// Actions\nexport * from '@kepler.gl/actions';\n\n// Constants\nexport * from '@kepler.gl/constants';\n\n// Processors\nexport * from '@kepler.gl/processors';\n\n// Components\nexport * from '@kepler.gl/components';\n\n// Layers\nexport * from '@kepler.gl/layers';\n\n// Styles\nexport * from '@kepler.gl/styles';\n\n// Utils\nexport * from '@kepler.gl/utils';\n\n// AI Assistant\nexport * from '@kepler.gl/ai-assistant';\n\n// Default export\nexport {default} from '@kepler.gl/components';\n"
  },
  {
    "path": "src/layers/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/layers/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/layers\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@danmarshall/deckgl-typings\": \"4.9.22\",\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@deck.gl/extensions\": \"^8.9.27\",\n    \"@deck.gl/geo-layers\": \"^8.9.27\",\n    \"@deck.gl/layers\": \"^8.9.27\",\n    \"@deck.gl/mesh-layers\": \"^8.9.27\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/deckgl-arrow-layers\": \"3.2.6\",\n    \"@kepler.gl/deckgl-layers\": \"3.2.6\",\n    \"@kepler.gl/localization\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/arrow\": \"^4.3.2\",\n    \"@loaders.gl/core\": \"^4.3.2\",\n    \"@loaders.gl/gis\": \"^4.3.2\",\n    \"@loaders.gl/gltf\": \"^4.3.2\",\n    \"@loaders.gl/mvt\": \"^4.3.2\",\n    \"@loaders.gl/parquet\": \"^4.3.2\",\n    \"@loaders.gl/pmtiles\": \"^4.3.2\",\n    \"@loaders.gl/schema\": \"^4.3.2\",\n    \"@loaders.gl/wkt\": \"^4.3.2\",\n    \"@luma.gl/constants\": \"^8.5.20\",\n    \"@mapbox/geojson-normalize\": \"0.0.1\",\n    \"@nebula.gl/edit-modes\": \"1.0.2-alpha.1\",\n    \"@nebula.gl/layers\": \"1.0.2-alpha.1\",\n    \"@turf/bbox\": \"^6.0.1\",\n    \"@turf/boolean-within\": \"^6.0.1\",\n    \"@turf/center\": \"^6.0.1\",\n    \"@turf/helpers\": \"^6.1.4\",\n    \"@types/geojson\": \"^7946.0.8\",\n    \"@types/keymirror\": \"^0.1.1\",\n    \"@types/lodash\": \"4.17.5\",\n    \"@types/styled-components\": \"^5.1.32\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"buffer\": \"6.0.3\",\n    \"d3-array\": \"^2.8.0\",\n    \"d3-shape\": \"^1.2.0\",\n    \"global\": \"^4.3.0\",\n    \"keymirror\": \"^0.1.1\",\n    \"lodash\": \"4.17.21\",\n    \"long\": \"^4.0.0\",\n    \"markdown-to-jsx\": \"^7.7.6\",\n    \"prop-types\": \"^15.6.0\",\n    \"react\": \"^18.2.0\",\n    \"react-intl\": \"^6.3.0\",\n    \"reselect\": \"^4.1.0\",\n    \"s2-geometry\": \"^1.2.10\",\n    \"styled-components\": \"6.1.8\",\n    \"type-analyzer\": \"0.4.0\",\n    \"viewport-mercator-project\": \"^6.0.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/layers/src/aggregation-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport memoize from 'lodash/memoize';\nimport Layer, {\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  LayerColorConfig,\n  LayerSizeConfig,\n  VisualChannelDescription,\n  VisualChannels\n} from './base-layer';\nimport {hexToRgb, aggregate, DataContainerInterface} from '@kepler.gl/utils';\nimport {\n  HIGHLIGH_COLOR_3D,\n  CHANNEL_SCALES,\n  FIELD_OPTS,\n  DEFAULT_AGGREGATION,\n  AGGREGATION_TYPES\n} from '@kepler.gl/constants';\nimport {ColorRange, Field, LayerColumn, Merge} from '@kepler.gl/types';\nimport {KeplerTable, Datasets} from '@kepler.gl/table';\n\ntype AggregationLayerColumns = {\n  lat: LayerColumn;\n  lng: LayerColumn;\n};\n\nexport type AggregationLayerData = {\n  index: number;\n};\n\nexport const pointPosAccessor =\n  ({lat, lng}: AggregationLayerColumns) =>\n  dc =>\n  d =>\n    [dc.valueAt(d.index, lng.fieldIdx), dc.valueAt(d.index, lat.fieldIdx)];\n\nexport const pointPosResolver = ({lat, lng}: AggregationLayerColumns) =>\n  `${lat.fieldIdx}-${lng.fieldIdx}`;\n\nexport const getValueAggrFunc = getPointData => (field, aggregation) => points =>\n  field\n    ? aggregate(\n        points.map(p => field.valueAccessor(getPointData(p))),\n        aggregation\n      )\n    : points.length;\n\nexport const getFilterDataFunc =\n  (\n    filterRange: number[][],\n    getFilterValue: (d: unknown) => (number | number[])[]\n  ): ((d: unknown) => boolean) =>\n  pt =>\n    getFilterValue(pt).every((val, i) => {\n      return typeof val === 'number' ? val >= filterRange[i][0] && val <= filterRange[i][1] : false;\n    });\n\nconst getLayerColorRange = (colorRange: ColorRange) => colorRange.colors.map(hexToRgb);\n\nexport const aggregateRequiredColumns: ['lat', 'lng'] = ['lat', 'lng'];\n\nexport type AggregationLayerVisualChannelConfig = LayerColorConfig & LayerSizeConfig;\nexport type AggregationLayerConfig = Merge<LayerBaseConfig, {columns: AggregationLayerColumns}> &\n  AggregationLayerVisualChannelConfig;\nexport default class AggregationLayer extends Layer {\n  getColorRange: any;\n  declare config: AggregationLayerConfig;\n  declare getPointData: (any) => any;\n  declare gpuFilterGetIndex: (any) => number;\n  declare gpuFilterGetData: (dataContainer, data, fieldIndex) => any;\n\n  constructor(\n    props: {\n      id?: string;\n    } & LayerBaseConfigPartial\n  ) {\n    super(props);\n\n    this.getPositionAccessor = dataContainer =>\n      pointPosAccessor(this.config.columns)(dataContainer);\n    this.getColorRange = memoize(getLayerColorRange);\n\n    // Access data of a point from aggregated bins, depends on how BinSorter works\n    // Deck.gl's BinSorter puts data in point.source\n    this.getPointData = pt => pt.source;\n\n    this.gpuFilterGetIndex = pt => this.getPointData(pt).index;\n    this.gpuFilterGetData = (dataContainer, data, fieldIndex) =>\n      dataContainer.valueAt(data.index, fieldIndex);\n  }\n\n  get isAggregated(): true {\n    return true;\n  }\n\n  get requiredLayerColumns() {\n    return aggregateRequiredColumns;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get noneLayerDataAffectingProps() {\n    return [\n      ...super.noneLayerDataAffectingProps,\n      'enable3d',\n      'colorRange',\n      'colorDomain',\n      'sizeRange',\n      'sizeScale',\n      'sizeDomain',\n      'percentile',\n      'coverage',\n      'elevationPercentile',\n      'elevationScale',\n      'enableElevationZoomFactor',\n      'fixedHeight'\n    ];\n  }\n\n  get visualChannels(): VisualChannels {\n    return {\n      color: {\n        aggregation: 'colorAggregation',\n        channelScaleType: CHANNEL_SCALES.colorAggr,\n        defaultMeasure: 'property.pointCount',\n        domain: 'colorDomain',\n        field: 'colorField',\n        key: 'color',\n        property: 'color',\n        range: 'colorRange',\n        scale: 'colorScale'\n      },\n      size: {\n        aggregation: 'sizeAggregation',\n        channelScaleType: CHANNEL_SCALES.sizeAggr,\n        condition: config => config.visConfig.enable3d,\n        defaultMeasure: 'property.pointCount',\n        domain: 'sizeDomain',\n        field: 'sizeField',\n        key: 'size',\n        property: 'height',\n        range: 'sizeRange',\n        scale: 'sizeScale'\n      }\n    };\n  }\n\n  /**\n   * Get the description of a visualChannel config\n   * @param key\n   * @returns\n   */\n  getVisualChannelDescription(key: string): VisualChannelDescription {\n    const channel = this.visualChannels[key];\n    if (!channel) return {label: '', measure: undefined};\n    // e.g. label: Color, measure: Average of ETA\n    const {range, field, defaultMeasure, aggregation} = channel;\n    const fieldConfig = this.config[field];\n    const label = this.visConfigSettings[range]?.label;\n\n    return {\n      label: typeof label === 'function' ? label(this.config) : label || '',\n      measure:\n        fieldConfig && aggregation\n          ? `${this.config.visConfig[aggregation]} of ${\n              fieldConfig.displayName || fieldConfig.name\n            }`\n          : defaultMeasure\n    };\n  }\n\n  getHoverData(object: any, dataContainer: DataContainerInterface, fields: Field[]): any {\n    if (!object) return object;\n    const measure = this.config.visConfig.colorAggregation;\n    // aggregate all fields for the hovered group\n    const aggregatedData = fields.reduce((accu, field) => {\n      accu[field.name] = {\n        measure,\n        value: aggregate(object.points, measure, (d: {index: number}) => {\n          return dataContainer.valueAt(d.index, field.fieldIdx);\n        })\n      };\n      return accu;\n    }, {});\n\n    // return aggregated object\n    return {aggregatedData, ...object};\n  }\n\n  getFilteredItemCount() {\n    // gpu filter not supported\n    return null;\n  }\n\n  /**\n   * Aggregation layer handles visual channel aggregation inside deck.gl layer\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  updateLayerVisualChannel({dataContainer}, channel) {\n    this.validateVisualChannel(channel);\n  }\n\n  /**\n   * Validate aggregation type on top of basic layer visual channel validation\n   * @param channel\n   */\n  validateVisualChannel(channel) {\n    // field type decides aggregation type decides scale type\n    this.validateFieldType(channel);\n    this.validateAggregationType(channel);\n    this.validateScale(channel);\n  }\n\n  /**\n   * Validate aggregation type based on selected field\n   */\n  validateAggregationType(channel) {\n    const visualChannel = this.visualChannels[channel];\n    const {field, aggregation} = visualChannel;\n    const aggregationOptions = this.getAggregationOptions(channel);\n\n    if (!aggregation) {\n      return;\n    }\n\n    if (!aggregationOptions.length) {\n      // if field cannot be aggregated, set field to null\n      this.updateLayerConfig({[field]: null});\n    } else if (!aggregationOptions.includes(this.config.visConfig[aggregation])) {\n      // current aggregation type is not supported by this field\n      // set aggregation to the first supported option\n      this.updateLayerVisConfig({[aggregation]: aggregationOptions[0]});\n    }\n  }\n\n  getAggregationOptions(channel) {\n    const visualChannel = this.visualChannels[channel];\n    const {field, channelScaleType} = visualChannel;\n\n    return Object.keys(\n      this.config[field]\n        ? FIELD_OPTS[this.config[field].type].scale[channelScaleType]\n        : DEFAULT_AGGREGATION[channelScaleType]\n    );\n  }\n\n  /**\n   * Get scale options based on current field and aggregation type\n   * @param channel\n   * @returns\n   */\n  getScaleOptions(channel: string): string[] {\n    const visualChannel = this.visualChannels[channel];\n    const {field, aggregation, channelScaleType} = visualChannel;\n    const aggregationType = aggregation ? this.config.visConfig[aggregation] : null;\n\n    if (!aggregationType) {\n      return [];\n    }\n\n    return this.config[field]\n      ? // scale options based on aggregation\n        FIELD_OPTS[this.config[field].type].scale[channelScaleType][aggregationType]\n      : // default scale options for point count: aggregationType should be count since\n        // LAYER_VIS_CONFIGS.aggregation.defaultValue is AGGREGATION_TYPES.average,\n        DEFAULT_AGGREGATION[channelScaleType][AGGREGATION_TYPES.count];\n  }\n\n  /**\n   * Aggregation layer handles visual channel aggregation inside deck.gl layer\n   */\n  updateLayerDomain(): AggregationLayer {\n    return this;\n  }\n\n  updateLayerMeta(dataset: KeplerTable, getPosition) {\n    const {dataContainer} = dataset;\n    // get bounds from points\n    const bounds = this.getPointsBounds(dataContainer, getPosition);\n\n    this.updateMeta({bounds});\n  }\n\n  calculateDataAttribute({filteredIndex}: KeplerTable, getPosition) {\n    const data: AggregationLayerData[] = [];\n\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const pos = getPosition({index});\n\n      // if doesn't have point lat or lng, do not add the point\n      // deck.gl can't handle position = null\n      if (pos.every(Number.isFinite)) {\n        data.push({\n          index\n        });\n      }\n    }\n\n    return data;\n  }\n\n  formatLayerData(datasets: Datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const getPosition = this.getPositionAccessor(dataContainer);\n\n    const aggregatePoints = getValueAggrFunc(this.getPointData);\n    const getColorValue = aggregatePoints(\n      this.config.colorField,\n      this.config.visConfig.colorAggregation\n    );\n\n    const getElevationValue = aggregatePoints(\n      this.config.sizeField,\n      this.config.visConfig.sizeAggregation\n    );\n    const hasFilter = Object.values(gpuFilter.filterRange).some((arr: any) =>\n      arr.some(v => v !== 0)\n    );\n\n    const getFilterValue = gpuFilter.filterValueAccessor(dataContainer)(\n      this.gpuFilterGetIndex,\n      this.gpuFilterGetData\n    );\n    const filterData = hasFilter\n      ? getFilterDataFunc(gpuFilter.filterRange, getFilterValue)\n      : undefined;\n\n    const {data} = this.updateData(datasets, oldLayerData);\n\n    return {\n      data,\n      getPosition,\n      _filterData: filterData,\n      // @ts-expect-error\n      ...(getColorValue ? {getColorValue} : {}),\n      // @ts-expect-error\n      ...(getElevationValue ? {getElevationValue} : {})\n    };\n  }\n\n  getDefaultDeckLayerProps(opts): any {\n    const baseProp = super.getDefaultDeckLayerProps(opts);\n    return {\n      ...baseProp,\n      highlightColor: HIGHLIGH_COLOR_3D,\n      // gpu data filtering is not supported in aggregation layer\n      extensions: [],\n      autoHighlight: this.config.visConfig.enable3d\n    };\n  }\n\n  getDefaultAggregationLayerProp(opts) {\n    const {gpuFilter, mapState, layerCallbacks = {}} = opts;\n    const {visConfig} = this.config;\n    const eleZoomFactor = this.getElevationZoomFactor(mapState);\n\n    const updateTriggers = {\n      getColorValue: {\n        colorField: this.config.colorField,\n        colorAggregation: this.config.visConfig.colorAggregation,\n        colorRange: visConfig.colorRange,\n        colorMap: visConfig.colorRange.colorMap\n      },\n      getElevationValue: {\n        sizeField: this.config.sizeField,\n        sizeAggregation: this.config.visConfig.sizeAggregation\n      },\n      _filterData: {\n        filterRange: gpuFilter.filterRange,\n        ...gpuFilter.filterValueUpdateTriggers\n      }\n    };\n\n    return {\n      ...this.getDefaultDeckLayerProps(opts),\n      coverage: visConfig.coverage,\n\n      // color\n      colorRange: this.getColorRange(visConfig.colorRange),\n      colorMap: visConfig.colorRange.colorMap,\n      colorScaleType: this.config.colorScale,\n      upperPercentile: visConfig.percentile[1],\n      lowerPercentile: visConfig.percentile[0],\n      colorAggregation: visConfig.colorAggregation,\n\n      // elevation\n      extruded: visConfig.enable3d,\n      elevationScale: visConfig.elevationScale * eleZoomFactor,\n      elevationScaleType: this.config.sizeScale,\n      elevationRange: visConfig.sizeRange,\n      elevationFixed: visConfig.fixedHeight,\n\n      elevationLowerPercentile: visConfig.elevationPercentile[0],\n      elevationUpperPercentile: visConfig.elevationPercentile[1],\n\n      // updateTriggers\n      updateTriggers,\n\n      // callbacks\n      onSetColorDomain: layerCallbacks.onSetLayerDomain\n    };\n  }\n}\n"
  },
  {
    "path": "src/layers/src/arc-layer/arc-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nclass ArcLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'point-layer-icon',\n    totalColor: 4\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M34.5,34.4c-0.6,0-1.2-0.4-1.4-1c-2.7-9.9-8.8-21.7-16.8-22.3c-3.1-0.2-5.6,1.5-7,4.8c-0.3,0.7-1.1,1.1-1.9,0.7\n\tc-0.7-0.3-1.1-1.1-0.7-1.9c1.9-4.3,5.6-6.8,9.8-6.5c9.5,0.7,16.3,13,19.4,24.4c0.2,0.8-0.2,1.5-1,1.7C34.8,34.3,34.6,34.4,34.5,34.4\n\tz\"\n          className=\"cr1\"\n        />\n        <path\n          d=\"M6.7,57c0,0-0.1,0-0.1,0c-0.5-0.1-0.9-0.6-0.8-1.1c2.4-17.3,9.6-30.3,17.5-31.8c3.1-0.6,7.8,0.4,12.1,8.3\n\tc0.3,0.5,0.1,1-0.4,1.3c-0.5,0.3-1,0.1-1.3-0.4c-2.1-3.8-5.6-8.2-10.1-7.4C16.6,27.3,9.9,40,7.6,56.2C7.6,56.7,7.2,57,6.7,57z\"\n          className=\"cr2\"\n        />\n        <path\n          d=\"M56.8,56.4c-0.8,0-1.4-0.6-1.4-1.4c0-13.5-6.8-24.4-12.9-25.8c-3.5-0.8-5.6,2-6.7,4.4c-0.3,0.7-1.2,1-1.9,0.7\n\tc-0.7-0.3-1-1.2-0.7-1.9c2.2-4.7,5.8-6.9,9.9-6c9,2,15.1,16.4,15.1,28.6C58.3,55.7,57.6,56.4,56.8,56.4z\"\n          className=\"cr3\"\n        />\n        <path\n          d=\"M34.5,32.7c-0.2,0-0.3,0-0.5,0c-1.3-0.3-2.1-1.5-1.8-2.8c3.5-17.4,10.3-20.7,14-21.2c4.4-0.5,8.6,2.3,11,7.4\n\tc0.6,1.2,0,2.6-1.1,3.1c-1.2,0.6-2.6,0-3.1-1.1c-1.5-3.2-3.8-5-6.1-4.7c-1.5,0.2-6.8,2-9.9,17.4C36.6,32,35.6,32.7,34.5,32.7z\"\n          className=\"cr4\"\n        />\n      </Base>\n    );\n  }\n}\n\nexport default ArcLayerIcon;\n"
  },
  {
    "path": "src/layers/src/arc-layer/arc-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\n\nimport Layer, {\n  LayerBaseConfig,\n  LayerColorConfig,\n  LayerSizeConfig,\n  LayerBounds,\n  LayerBaseConfigPartial,\n  VisualChannel\n} from '../base-layer';\nimport {BrushingExtension} from '@deck.gl/extensions';\nimport {GeoArrowArcLayer} from '@kepler.gl/deckgl-arrow-layers';\nimport {FilterArrowExtension} from '@kepler.gl/deckgl-layers';\nimport {ArcLayer as DeckArcLayer} from '@deck.gl/layers';\n\nimport {\n  hexToRgb,\n  DataContainerInterface,\n  maybeHexToGeo,\n  ArrowDataContainer\n} from '@kepler.gl/utils';\nimport ArcLayerIcon from './arc-layer-icon';\nimport {isLayerHoveredFromArrow, createGeoArrowPointVector, getFilteredIndex} from '../layer-utils';\nimport {\n  DEFAULT_LAYER_COLOR,\n  PROJECTED_PIXEL_SIZE_MULTIPLIER,\n  ALL_FIELD_TYPES\n} from '@kepler.gl/constants';\n\nimport {\n  ColorRange,\n  RGBColor,\n  Merge,\n  VisConfigColorRange,\n  VisConfigColorSelect,\n  VisConfigNumber,\n  VisConfigRange,\n  LayerColumn,\n  Field,\n  AnimationConfig\n} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\n\nexport type ArcLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  thickness: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  sizeRange: VisConfigRange;\n  targetColor: VisConfigColorSelect;\n};\n\nexport type ArcLayerColumnsConfig = {\n  // COLUMN_MODE_POINTS required columns\n  lat0: LayerColumn;\n  lat1: LayerColumn;\n  lng0: LayerColumn;\n  lng1: LayerColumn;\n\n  // COLUMN_MODE_NEIGHBORS required columns\n  lat: LayerColumn;\n  lng: LayerColumn;\n  neighbors: LayerColumn;\n\n  // COLUMN_MODE_GEOARROW\n  geoarrow0: LayerColumn;\n  geoarrow1: LayerColumn;\n};\n\nexport type ArcLayerVisConfig = {\n  colorRange: ColorRange;\n  opacity: number;\n  sizeRange: [number, number];\n  targetColor: RGBColor;\n  thickness: number;\n};\n\nexport type ArcLayerVisualChannelConfig = LayerColorConfig & LayerSizeConfig;\nexport type ArcLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: ArcLayerColumnsConfig; visConfig: ArcLayerVisConfig}\n> &\n  ArcLayerVisualChannelConfig;\n\nexport type ArcLayerData = {\n  index: number;\n  sourcePosition: [number, number, number];\n  targetPosition: [number, number, number];\n};\n\nexport type ArcLayerMeta = {\n  bounds: LayerBounds;\n};\n\nexport const arcRequiredColumns = ['lat0', 'lng0', 'lat1', 'lng1'];\nexport const neighborRequiredColumns = ['lat', 'lng', 'neighbors'];\nexport const geoarrowRequiredColumns = ['geoarrow0', 'geoarrow1'];\n\nexport const arcColumnLabels = {\n  lat0: 'arc.lat0',\n  lng0: 'arc.lng0',\n  lat1: 'arc.lat1',\n  lng1: 'arc.lng1',\n  neighbors: 'neighbors'\n};\n\nexport const arcVisConfigs: {\n  opacity: 'opacity';\n  thickness: 'thickness';\n  colorRange: 'colorRange';\n  sizeRange: 'strokeWidthRange';\n  targetColor: 'targetColor';\n} = {\n  opacity: 'opacity',\n  thickness: 'thickness',\n  colorRange: 'colorRange',\n  sizeRange: 'strokeWidthRange',\n  targetColor: 'targetColor'\n};\n\nexport const COLUMN_MODE_POINTS = 'points';\nexport const COLUMN_MODE_NEIGHBORS = 'neighbors';\nexport const COLUMN_MODE_GEOARROW = 'geoarrow';\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_POINTS,\n    label: 'Points',\n    requiredColumns: arcRequiredColumns\n  },\n  {\n    key: COLUMN_MODE_NEIGHBORS,\n    label: 'Point and Neighbors',\n    requiredColumns: neighborRequiredColumns\n  },\n  {\n    key: COLUMN_MODE_GEOARROW,\n    label: 'Geoarrow Points',\n    requiredColumns: geoarrowRequiredColumns\n  }\n];\nconst DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;\n\nconst brushingExtension = new BrushingExtension();\nconst arrowCPUFilterExtension = new FilterArrowExtension();\n\nfunction isH3Field(columns, allFields, key) {\n  const field = allFields[columns[key].fieldIdx];\n  return field?.type === ALL_FIELD_TYPES.h3;\n}\n\nexport const arcPosAccessor =\n  ({lat0, lng0, lat1, lng1, lat, lng, geoarrow0, geoarrow1}: ArcLayerColumnsConfig, columnMode) =>\n  (dc: DataContainerInterface) => {\n    switch (columnMode) {\n      case COLUMN_MODE_GEOARROW:\n        return d => {\n          const start = dc.valueAt(d.index, geoarrow0.fieldIdx);\n          const end = dc.valueAt(d.index, geoarrow1.fieldIdx);\n          return [start.get(0), start.get(1), 0, end.get(2), end.get(3), 0];\n        };\n      case COLUMN_MODE_NEIGHBORS:\n        return d => {\n          const startPos = maybeHexToGeo(dc, d, lat, lng);\n          // only return source point if columnMode is COLUMN_MODE_NEIGHBORS\n\n          return [\n            startPos ? startPos[0] : dc.valueAt(d.index, lng.fieldIdx),\n            startPos ? startPos[1] : dc.valueAt(d.index, lat.fieldIdx),\n            0\n          ];\n        };\n      default:\n        // COLUMN_MODE_POINTS\n        return d => {\n          // lat or lng column could be hex column\n          // we assume string value is hex and try to convert it to geo lat lng\n          const startPos = maybeHexToGeo(dc, d, lat0, lng0);\n          const endPos = maybeHexToGeo(dc, d, lat1, lng1);\n          return [\n            startPos ? startPos[0] : dc.valueAt(d.index, lng0.fieldIdx),\n            startPos ? startPos[1] : dc.valueAt(d.index, lat0.fieldIdx),\n            0,\n            endPos ? endPos[0] : dc.valueAt(d.index, lng1.fieldIdx),\n            endPos ? endPos[1] : dc.valueAt(d.index, lat1.fieldIdx),\n            0\n          ];\n        };\n    }\n  };\nexport default class ArcLayer extends Layer {\n  declare visConfigSettings: ArcLayerVisConfigSettings;\n  declare config: ArcLayerConfig;\n  declare meta: ArcLayerMeta;\n\n  dataContainer: DataContainerInterface | null = null;\n  geoArrowVector0: arrow.Vector | undefined = undefined;\n  geoArrowVector1: arrow.Vector | undefined = undefined;\n\n  /*\n   * CPU filtering an arrow table by values and assembling a partial copy of the raw table is expensive\n   * so we will use filteredIndex to create an attribute e.g. filteredIndex [0|1] for GPU filtering\n   * in deck.gl layer, see: FilterArrowExtension in @kepler.gl/deckgl-layers.\n   * Note that this approach can create visible lags in case of a lot of discarted geometry.\n   */\n  filteredIndex: Uint8ClampedArray | null = null;\n  filteredIndexTrigger: number[] = [];\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(arcVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      arcPosAccessor(this.config.columns, this.config.columnMode)(dataContainer);\n  }\n\n  get type() {\n    return 'arc';\n  }\n\n  get isAggregated() {\n    return false;\n  }\n\n  get layerIcon() {\n    return ArcLayerIcon;\n  }\n\n  get columnLabels(): Record<string, string> {\n    return arcColumnLabels;\n  }\n\n  get columnPairs() {\n    return this.defaultLinkColumnPairs;\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  get visualChannels() {\n    return {\n      sourceColor: {\n        ...super.visualChannels.color,\n        property: 'color',\n        key: 'sourceColor',\n        accessor: 'getSourceColor',\n        defaultValue: config => config.color\n      },\n      targetColor: {\n        ...super.visualChannels.color,\n        property: 'targetColor',\n        key: 'targetColor',\n        accessor: 'getTargetColor',\n        defaultValue: config => config.visConfig.targetColor || config.color\n      },\n      size: {\n        ...super.visualChannels.size,\n        accessor: 'getWidth',\n        property: 'stroke'\n      }\n    };\n  }\n\n  get columnValidators() {\n    // if one of the lat or lng column is string type, we allow it\n    // will try to pass it as hex\n    return {\n      lat0: (column, columns, allFields) => isH3Field(columns, allFields, 'lng0'),\n      lng0: (column, columns, allFields) => isH3Field(columns, allFields, 'lat0'),\n      lat1: (column, columns, allFields) => isH3Field(columns, allFields, 'lng1'),\n      lng1: (column, columns, allFields) => isH3Field(columns, allFields, 'lat1'),\n      lat: (column, columns, allFields) => isH3Field(columns, allFields, 'lng'),\n      lng: (column, columns, allFields) => isH3Field(columns, allFields, 'lat')\n    };\n  }\n\n  hasAllColumns() {\n    const {columns} = this.config;\n    if (this.config.columnMode === COLUMN_MODE_GEOARROW) {\n      return this.hasColumnValue(columns.geoarrow0) && this.hasColumnValue(columns.geoarrow1);\n    }\n    if (this.config.columnMode === COLUMN_MODE_POINTS) {\n      // TODO - this does not have access to allFields...\n      // So we can't do the same validation as for the field errors\n      const hasStart = this.hasColumnValue(columns.lat0) || this.hasColumnValue(columns.lng0);\n      const hasEnd = this.hasColumnValue(columns.lat1) || this.hasColumnValue(columns.lng1);\n      return hasStart && hasEnd;\n    }\n    const hasStart = this.hasColumnValue(columns.lat) || this.hasColumnValue(columns.lng);\n    const hasNeibors = this.hasColumnValue(columns.neighbors);\n    return hasStart && hasNeibors;\n  }\n\n  static findDefaultLayerProps({fieldPairs = []}: KeplerTable): {\n    props: {color?: RGBColor; columns: ArcLayerColumnsConfig; label: string}[];\n  } {\n    if (fieldPairs.length < 2) {\n      return {props: []};\n    }\n\n    const props: {\n      color: RGBColor;\n      columns: ArcLayerColumnsConfig;\n      label: string;\n    } = {\n      color: hexToRgb(DEFAULT_LAYER_COLOR.tripArc),\n      // connect the first two point layer with arc\n      // @ts-expect-error separate types for point / neighbor columns\n      columns: {\n        lat0: fieldPairs[0].pair.lat,\n        lng0: fieldPairs[0].pair.lng,\n        lat1: fieldPairs[1].pair.lat,\n        lng1: fieldPairs[1].pair.lng\n      },\n      label: `${fieldPairs[0].defaultName} -> ${fieldPairs[1].defaultName} arc`\n    };\n\n    return {props: [props]};\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial) {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props);\n\n    return {\n      ...defaultLayerConfig,\n      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE\n    };\n  }\n\n  calculateDataAttributeForGeoArrow(\n    {dataContainer, filteredIndex}: {dataContainer: ArrowDataContainer; filteredIndex: number[]},\n    getPosition\n  ) {\n    this.filteredIndex = getFilteredIndex(\n      dataContainer.numRows(),\n      filteredIndex,\n      this.filteredIndex\n    );\n    this.filteredIndexTrigger = filteredIndex;\n\n    if (this.config.columnMode === COLUMN_MODE_GEOARROW) {\n      this.geoArrowVector0 = dataContainer.getColumn(this.config.columns.geoarrow0.fieldIdx);\n      this.geoArrowVector1 = dataContainer.getColumn(this.config.columns.geoarrow1.fieldIdx);\n    } else {\n      // generate columns compatible with geoarrow point extension\n      // TODO remove excessive intermediate allocations\n      this.geoArrowVector0 = createGeoArrowPointVector(dataContainer, d => {\n        return getPosition(d).slice(0, 3);\n      });\n      this.geoArrowVector1 = createGeoArrowPointVector(dataContainer, d => {\n        return getPosition(d).slice(3, 6);\n      });\n    }\n\n    return dataContainer.getTable();\n  }\n\n  calculateDataAttributeForPoints(\n    {filteredIndex}: {dataContainer: DataContainerInterface; filteredIndex: number[]},\n    getPosition\n  ) {\n    const data: ArcLayerData[] = [];\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const pos = getPosition({index});\n\n      // if doesn't have point lat or lng, do not add the point\n      // deck.gl can't handle position = null\n      if (pos.every(Number.isFinite)) {\n        data.push({\n          index,\n          sourcePosition: [pos[0], pos[1], pos[2]],\n          targetPosition: [pos[3], pos[4], pos[5]]\n        });\n      }\n    }\n    return data;\n  }\n\n  calculateDataAttributeForPointNNeighbors(\n    {\n      dataContainer,\n      filteredIndex\n    }: {dataContainer: DataContainerInterface; filteredIndex: number[]},\n    getPosition\n  ) {\n    const data: {index: number; sourcePosition: number[]; targetPosition: number[]}[] = [];\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const pos = getPosition({index});\n      // if doesn't have point lat or lng, do not add the point\n      // deck.gl can't handle position = null\n      if (pos.every(Number.isFinite)) {\n        // push all neibors\n        const neighborIdx = this.config.columns.neighbors.value\n          ? dataContainer.valueAt(index, this.config.columns.neighbors.fieldIdx)\n          : [];\n        if (Array.isArray(neighborIdx)) {\n          neighborIdx.forEach(idx => {\n            // TODO prevent row materialization here\n            const tPos = dataContainer.rowAsArray(idx) ? getPosition({index: idx}) : null;\n            if (tPos && tPos.every(Number.isFinite)) {\n              data.push({\n                index,\n                sourcePosition: [pos[0], pos[1], pos[2]],\n                targetPosition: [tPos[0], tPos[1], tPos[2]]\n              });\n            }\n          });\n        }\n      }\n    }\n\n    return data;\n  }\n\n  calculateDataAttribute({dataContainer, filteredIndex}: KeplerTable, getPosition) {\n    const {columnMode} = this.config;\n\n    // 1) COLUMN_MODE_GEOARROW - when we have a geoarrow point column\n    // 2) COLUMN_MODE_POINTS + ArrowDataContainer > create geoarrow point column on the fly\n    if (\n      dataContainer instanceof ArrowDataContainer &&\n      (columnMode === COLUMN_MODE_GEOARROW || columnMode === COLUMN_MODE_POINTS)\n    ) {\n      return this.calculateDataAttributeForGeoArrow({dataContainer, filteredIndex}, getPosition);\n    }\n\n    // we don't need these in non-Arrow modes atm.\n    this.geoArrowVector0 = undefined;\n    this.geoArrowVector1 = undefined;\n    this.filteredIndex = null;\n\n    if (this.config.columnMode === COLUMN_MODE_POINTS) {\n      return this.calculateDataAttributeForPoints({dataContainer, filteredIndex}, getPosition);\n    }\n    return this.calculateDataAttributeForPointNNeighbors(\n      {dataContainer, filteredIndex},\n      getPosition\n    );\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const {data} = this.updateData(datasets, oldLayerData);\n    const accessors = this.getAttributeAccessors({dataContainer});\n    const isFilteredAccessor = (data: {index: number}) => {\n      // for GeoArrow data is a buffer, so use objectInfo\n      return this.filteredIndex ? this.filteredIndex[data.index] : 1;\n    };\n\n    return {\n      data,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),\n      getFiltered: isFilteredAccessor,\n      ...accessors\n    };\n  }\n  /* eslint-enable complexity */\n\n  updateLayerMeta(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n\n    this.dataContainer = dataContainer;\n\n    // get bounds from arcs\n    const getPosition = this.getPositionAccessor(dataContainer);\n\n    const sBounds = this.getPointsBounds(dataContainer, d => {\n      const pos = getPosition(d);\n      return [pos[0], pos[1]];\n    });\n\n    let tBounds: number[] | null = [];\n    if (this.config.columnMode === COLUMN_MODE_POINTS) {\n      tBounds = this.getPointsBounds(dataContainer, d => {\n        const pos = getPosition(d);\n        return [pos[3], pos[4]];\n      });\n    } else {\n      // when columnMode is neighbors, it reference the same collection of points\n      tBounds = sBounds;\n    }\n\n    const bounds =\n      tBounds && sBounds\n        ? [\n            Math.min(sBounds[0], tBounds[0]),\n            Math.min(sBounds[1], tBounds[1]),\n            Math.max(sBounds[2], tBounds[2]),\n            Math.max(sBounds[3], tBounds[3])\n          ]\n        : sBounds || tBounds;\n\n    this.updateMeta({bounds});\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, objectHovered, interactionConfig, dataset} = opts;\n    const updateTriggers = {\n      getPosition: this.config.columns,\n      getFilterValue: gpuFilter.filterValueUpdateTriggers,\n      getFiltered: this.filteredIndexTrigger,\n      ...this.getVisualChannelUpdateTriggers()\n    };\n    const widthScale = this.config.visConfig.thickness * PROJECTED_PIXEL_SIZE_MULTIPLIER;\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    const useArrowLayer = Boolean(this.geoArrowVector0);\n\n    let ArcLayerClass: typeof DeckArcLayer | typeof GeoArrowArcLayer = DeckArcLayer;\n    let experimentalPropOverrides: {\n      data?: arrow.Table;\n      getSourcePosition?: arrow.Vector;\n      getTargetPosition?: arrow.Vector;\n    } = {};\n\n    if (useArrowLayer) {\n      ArcLayerClass = GeoArrowArcLayer;\n      experimentalPropOverrides = {\n        data: dataset.dataContainer.getTable(),\n        getSourcePosition: this.geoArrowVector0,\n        getTargetPosition: this.geoArrowVector1\n      };\n    }\n\n    return [\n      // @ts-expect-error\n      new ArcLayerClass({\n        ...defaultLayerProps,\n        ...this.getBrushingExtensionProps(interactionConfig, 'source_target'),\n        ...data,\n        ...experimentalPropOverrides,\n        widthScale,\n        updateTriggers,\n        extensions: [\n          ...defaultLayerProps.extensions,\n          brushingExtension,\n          ...(useArrowLayer ? [arrowCPUFilterExtension] : [])\n        ]\n      }),\n      // hover layer\n      ...(hoveredObject\n        ? [\n            new DeckArcLayer({\n              ...this.getDefaultHoverLayerProps(),\n              visible: defaultLayerProps.visible,\n              data: [hoveredObject],\n              widthScale,\n              getSourceColor: this.config.highlightColor,\n              getTargetColor: this.config.highlightColor,\n              getWidth: data.getWidth\n            })\n          ]\n        : [])\n    ];\n  }\n\n  hasHoveredObject(objectInfo: {index: number}) {\n    if (\n      isLayerHoveredFromArrow(objectInfo, this.id) &&\n      objectInfo.index >= 0 &&\n      this.dataContainer\n    ) {\n      // objectInfo.index can point to data of arcs created in neighbor mode, so get index to source data.\n      const hoveredObject = super.hasHoveredObject(objectInfo);\n      return hoveredObject\n        ? {\n            index: hoveredObject.index,\n            position: this.getPositionAccessor(this.dataContainer)({index: hoveredObject.index})\n          }\n        : null;\n    }\n\n    return super.hasHoveredObject(objectInfo);\n  }\n\n  getHoverData(\n    object: {index: number} | arrow.StructRow | undefined,\n    dataContainer: DataContainerInterface,\n    fields: Field[],\n    animationConfig: AnimationConfig,\n    hoverInfo: {index: number}\n  ) {\n    // for arrow format, `object` is the Arrow row object Proxy,\n    // and index is passed in `hoverInfo`.\n    const index = this.geoArrowVector0 ? hoverInfo?.index : (object as {index: number}).index;\n    if (index >= 0) {\n      return dataContainer.row(index);\n    }\n    return null;\n  }\n\n  getLegendVisualChannels() {\n    let channels: {[key: string]: VisualChannel} = this.visualChannels;\n    if (channels.sourceColor?.field && this.config[channels.sourceColor.field]) {\n      // Remove targetColor to avoid duplicate legend\n      channels = {...channels};\n      delete channels.targetColor;\n    }\n    return channels;\n  }\n}\n"
  },
  {
    "path": "src/layers/src/base-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {COORDINATE_SYSTEM} from '@deck.gl/core';\nimport {GeoArrowTextLayer} from '@kepler.gl/deckgl-arrow-layers';\nimport {DataFilterExtension} from '@deck.gl/extensions';\nimport {TextLayer} from '@deck.gl/layers';\nimport {console as Console} from 'global/window';\nimport keymirror from 'keymirror';\nimport React from 'react';\nimport * as arrow from 'apache-arrow';\nimport DefaultLayerIcon from './default-layer-icon';\nimport {diffUpdateTriggers} from './layer-update';\nimport {getSatisfiedColumnMode, FindDefaultLayerPropsReturnValue} from './layer-utils';\n\nimport {\n  CHANNEL_SCALES,\n  CHANNEL_SCALE_SUPPORTED_FIELDS,\n  DEFAULT_COLOR_UI,\n  DEFAULT_HIGHLIGHT_COLOR,\n  DEFAULT_LAYER_LABEL,\n  DEFAULT_TEXT_LABEL,\n  DataVizColors,\n  FIELD_OPTS,\n  LAYER_VIS_CONFIGS,\n  MAX_GPU_FILTERS,\n  NO_VALUE_COLOR,\n  PROJECTED_PIXEL_SIZE_MULTIPLIER,\n  SCALE_FUNC,\n  SCALE_TYPES,\n  TEXT_OUTLINE_MULTIPLIER,\n  UNKNOWN_COLOR_KEY\n} from '@kepler.gl/constants';\nimport {\n  DataContainerInterface,\n  DomainQuantiles,\n  getApplicationConfig,\n  getLatLngBounds,\n  getSampleContainerData,\n  hasColorMap,\n  hexToRgb,\n  isPlainObject,\n  isDomainStops,\n  updateColorRangeByMatchingPalette,\n  isArrowTable\n} from '@kepler.gl/utils';\nimport {generateHashId, toArray, notNullorUndefined} from '@kepler.gl/common-utils';\nimport {Datasets, GpuFilter, KeplerTable} from '@kepler.gl/table';\nimport {\n  AggregatedBin,\n  ColorRange,\n  ColorUI,\n  Field,\n  Filter,\n  GetVisChannelScaleReturnType,\n  LayerVisConfigSettings,\n  MapState,\n  AnimationConfig,\n  KeplerLayer,\n  LayerBaseConfig,\n  LayerColumns,\n  LayerColumn,\n  ColumnPairs,\n  ColumnLabels,\n  SupportedColumnMode,\n  FieldPair,\n  NestedPartial,\n  RGBColor,\n  ValueOf,\n  VisualChannel,\n  VisualChannels,\n  VisualChannelDomain,\n  VisualChannelField,\n  VisualChannelScale\n} from '@kepler.gl/types';\nimport {\n  getScaleFunction,\n  initializeLayerColorMap,\n  getCategoricalColorScale,\n  updateCustomColorRangeByColorUI\n} from '@kepler.gl/utils';\nimport memoize from 'lodash/memoize';\nimport {\n  initializeCustomPalette,\n  isDomainQuantile,\n  getDomainStepsbyZoom,\n  getThresholdsFromQuantiles\n} from '@kepler.gl/utils';\n\nexport type {\n  AggregatedBin,\n  LayerBaseConfig,\n  VisualChannel,\n  VisualChannels,\n  VisualChannelDomain,\n  VisualChannelField,\n  VisualChannelScale\n};\n\nexport type LayerBaseConfigPartial = {dataId: LayerBaseConfig['dataId']} & Partial<LayerBaseConfig>;\n\nexport type LayerColorConfig = {\n  colorField: VisualChannelField;\n  colorDomain: VisualChannelDomain;\n  colorScale: VisualChannelScale;\n};\nexport type LayerSizeConfig = {\n  // color by size, domain is set by filters, field, scale type\n  sizeDomain: VisualChannelDomain;\n  sizeScale: VisualChannelScale;\n  sizeField: VisualChannelField;\n};\nexport type LayerHeightConfig = {\n  heightField: VisualChannelField;\n  heightDomain: VisualChannelDomain;\n  heightScale: VisualChannelScale;\n};\nexport type LayerStrokeColorConfig = {\n  strokeColorField: VisualChannelField;\n  strokeColorDomain: VisualChannelDomain;\n  strokeColorScale: VisualChannelScale;\n};\nexport type LayerCoverageConfig = {\n  coverageField: VisualChannelField;\n  coverageDomain: VisualChannelDomain;\n  coverageScale: VisualChannelScale;\n};\nexport type LayerRadiusConfig = {\n  radiusField: VisualChannelField;\n  radiusDomain: VisualChannelDomain;\n  radiusScale: VisualChannelScale;\n};\nexport type LayerWeightConfig = {\n  weightField: VisualChannelField;\n};\n\nexport type VisualChannelDescription = {\n  label: string;\n  measure: string | undefined;\n};\n\ntype ColumnValidator = (column: LayerColumn, columns: LayerColumns, allFields: Field[]) => boolean;\n\nexport type UpdateTriggers = {\n  [key: string]: UpdateTrigger;\n};\nexport type UpdateTrigger = {\n  [key: string]: any;\n};\nexport type LayerBounds = [number, number, number, number];\n\n/**\n * Approx. number of points to sample in a large data set\n */\nexport const LAYER_ID_LENGTH = 6;\n\nconst MAX_SAMPLE_SIZE = 5000;\nconst defaultDomain: [number, number] = [0, 1];\nconst dataFilterExtension = new DataFilterExtension({\n  filterSize: MAX_GPU_FILTERS,\n  // `countItems` option. It enables the GPU to report the number of objects that pass the filter criteria via the `onFilteredItemsChange` callback.\n  // @ts-expect-error not typed\n  countItems: getApplicationConfig().useOnFilteredItemsChange ?? false\n});\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst defaultDataAccessor = dc => d => d;\nconst identity = d => d;\n// Can't use fiedValueAccesor because need the raw data to render tooltip\n// SHAN: Revisit here\nexport const defaultGetFieldValue = (field, d) => field.valueAccessor(d);\n\nexport const OVERLAY_TYPE_CONST = keymirror({\n  deckgl: null,\n  mapboxgl: null\n});\n\nexport const layerColors = Object.values(DataVizColors).map(hexToRgb);\nfunction* generateColor(): Generator<RGBColor> {\n  let index = 0;\n  while (index < layerColors.length + 1) {\n    if (index === layerColors.length) {\n      index = 0;\n    }\n    yield layerColors[index++];\n  }\n}\n\nexport type LayerInfoModal = {\n  id: string;\n  template: React.FC<void>;\n  modalProps: {\n    title: string;\n  };\n};\n\nexport const colorMaker = generateColor();\n\nexport type BaseLayerConstructorProps = {\n  id?: string;\n} & LayerBaseConfigPartial;\n\nclass Layer implements KeplerLayer {\n  id: string;\n  meta: Record<string, any>;\n  visConfigSettings: {\n    [key: string]: ValueOf<LayerVisConfigSettings>;\n  };\n  config: LayerBaseConfig & Partial<LayerColorConfig & LayerSizeConfig>;\n  // TODO: define _oldDataUpdateTriggers\n  _oldDataUpdateTriggers: any;\n\n  isValid: boolean;\n  errorMessage: string | null;\n  filteredItemCount: {\n    [deckLayerId: string]: number;\n  };\n\n  constructor(props: BaseLayerConstructorProps) {\n    this.id = props.id || generateHashId(LAYER_ID_LENGTH);\n    // meta\n    this.meta = {};\n\n    // visConfigSettings\n    this.visConfigSettings = {};\n\n    this.config = this.getDefaultLayerConfig(props);\n\n    // set columnMode from supported columns\n    if (!this.config.columnMode) {\n      const {supportedColumnModes} = this;\n      if (supportedColumnModes?.length) {\n        this.config.columnMode = supportedColumnModes[0]?.key;\n      }\n    }\n    // then set column, columnMode should already been set\n    this.config.columns = this.getLayerColumns(props.columns);\n\n    // false indicates that the layer caused an error, and was disabled\n    this.isValid = true;\n    this.errorMessage = null;\n    // item count\n    this.filteredItemCount = {};\n  }\n\n  get layerIcon(): React.ElementType {\n    return DefaultLayerIcon;\n  }\n\n  get overlayType(): keyof typeof OVERLAY_TYPE_CONST {\n    return OVERLAY_TYPE_CONST.deckgl;\n  }\n\n  get type(): string | null {\n    return null;\n  }\n\n  get name(): string | null {\n    return this.type;\n  }\n\n  get isAggregated() {\n    return false;\n  }\n\n  get requiredLayerColumns(): string[] {\n    const {supportedColumnModes} = this;\n    if (supportedColumnModes) {\n      return supportedColumnModes.reduce<string[]>(\n        (acc, obj) => (obj.requiredColumns ? acc.concat(obj.requiredColumns) : acc),\n        []\n      );\n    }\n    return [];\n  }\n\n  get optionalColumns(): string[] {\n    const {supportedColumnModes} = this;\n    if (supportedColumnModes) {\n      return supportedColumnModes.reduce<string[]>(\n        (acc, obj) => (obj.optionalColumns ? acc.concat(obj.optionalColumns) : acc),\n        []\n      );\n    }\n    return [];\n  }\n\n  get noneLayerDataAffectingProps() {\n    return ['label', 'opacity', 'thickness', 'isVisible', 'hidden'];\n  }\n\n  get visualChannels(): VisualChannels {\n    return {\n      color: {\n        property: 'color',\n        field: 'colorField',\n        scale: 'colorScale',\n        domain: 'colorDomain',\n        range: 'colorRange',\n        key: 'color',\n        channelScaleType: CHANNEL_SCALES.color,\n        nullValue: NO_VALUE_COLOR,\n        defaultValue: config => config.color\n      },\n      size: {\n        property: 'size',\n        field: 'sizeField',\n        scale: 'sizeScale',\n        domain: 'sizeDomain',\n        range: 'sizeRange',\n        key: 'size',\n        channelScaleType: CHANNEL_SCALES.size,\n        nullValue: 0,\n        defaultValue: 1\n      }\n    };\n  }\n\n  get columnValidators(): {[key: string]: ColumnValidator} {\n    return {};\n  }\n  /*\n   * Column pairs maps layer column to a specific field pairs,\n   * By default, it is set to null\n   */\n  get columnPairs(): ColumnPairs | null {\n    return null;\n  }\n\n  /**\n   * Column labels if its different than column key\n   */\n  get columnLabels(): ColumnLabels | null {\n    return null;\n  }\n\n  /*\n   * Default point column pairs, can be used for point based layers: point, icon etc.\n   */\n  get defaultPointColumnPairs(): ColumnPairs {\n    return {\n      lat: {pair: ['lng', 'altitude'], fieldPairKey: 'lat'},\n      lng: {pair: ['lat', 'altitude'], fieldPairKey: 'lng'},\n      altitude: {pair: ['lng', 'lat'], fieldPairKey: 'altitude'}\n    };\n  }\n\n  /*\n   * Default link column pairs, can be used for link based layers: arc, line etc\n   */\n  get defaultLinkColumnPairs(): ColumnPairs {\n    return {\n      lat: {pair: ['lng', 'alt'], fieldPairKey: 'lat'},\n      lng: {pair: ['lat', 'alt'], fieldPairKey: 'lng'},\n      alt: {pair: ['lng', 'lat'], fieldPairKey: 'altitude'},\n\n      lat0: {pair: 'lng0', fieldPairKey: 'lat'},\n      lng0: {pair: 'lat0', fieldPairKey: 'lng'},\n      alt0: {pair: ['lng0', 'lat0'], fieldPairKey: 'altitude'},\n\n      lat1: {pair: 'lng1', fieldPairKey: 'lat'},\n      lng1: {pair: 'lat1', fieldPairKey: 'lng'},\n      alt1: {pair: ['lng1', 'lat1'], fieldPairKey: 'altitude'}\n    };\n  }\n\n  /**\n   * Return a React component for to render layer instructions in a modal\n   * @returns {object} - an object\n   * @example\n   *  return {\n   *    id: 'iconInfo',\n   *    template: IconInfoModal,\n   *    modalProps: {\n   *      title: 'How to draw icons'\n   *   };\n   * }\n   */\n  get layerInfoModal(): LayerInfoModal | Record<string, LayerInfoModal> | null {\n    return null;\n  }\n\n  /**\n   * Returns which column modes this layer supports\n   */\n  get supportedColumnModes(): SupportedColumnMode[] | null {\n    return null;\n  }\n\n  get supportedDatasetTypes(): string[] | null {\n    return null;\n  }\n\n  /*\n   * Given a dataset, automatically find props to create layer based on it\n   * and return the props and previous found layers.\n   * By default, no layers will be found\n   */\n  static findDefaultLayerProps(\n    dataset: KeplerTable,\n    foundLayers?: any[]\n  ): FindDefaultLayerPropsReturnValue {\n    return {props: [], foundLayers};\n  }\n\n  /**\n   * Given a array of preset required column names\n   * found field that has the same name to set as layer column\n   *\n   * @param {object} defaultFields\n   * @param {object[]} allFields\n   * @returns {object[] | null} all possible required layer column pairs\n   */\n  static findDefaultColumnField(defaultFields, allFields) {\n    // find all matched fields for each required col\n    const requiredColumns = Object.keys(defaultFields).reduce((prev, key) => {\n      const requiredFields = allFields.filter(\n        f => f.name === defaultFields[key] || defaultFields[key].includes(f.name)\n      );\n\n      prev[key] = requiredFields.length\n        ? requiredFields.map(f => ({\n            value: f.name,\n            fieldIdx: f.fieldIdx\n          }))\n        : null;\n      return prev;\n    }, {});\n\n    if (!Object.values(requiredColumns).every(Boolean)) {\n      // if any field missing, return null\n      return null;\n    }\n\n    return this.getAllPossibleColumnPairs(requiredColumns);\n  }\n\n  static getAllPossibleColumnPairs(requiredColumns) {\n    // for multiple matched field for one required column, return multiple\n    // combinations, e. g. if column a has 2 matched, column b has 3 matched\n    // 6 possible column pairs will be returned\n    const allKeys = Object.keys(requiredColumns);\n    const pointers = allKeys.map((k, i) => (i === allKeys.length - 1 ? -1 : 0));\n    const countPerKey = allKeys.map(k => requiredColumns[k].length);\n    // TODO: Better typings\n    const pairs: any[] = [];\n\n    /* eslint-disable no-loop-func */\n    while (incrementPointers(pointers, countPerKey, pointers.length - 1)) {\n      const newPair = pointers.reduce((prev, cuur, i) => {\n        prev[allKeys[i]] = requiredColumns[allKeys[i]][cuur];\n        return prev;\n      }, {});\n\n      pairs.push(newPair);\n    }\n    /* eslint-enable no-loop-func */\n\n    // recursively increment pointers\n    function incrementPointers(pts, counts, index) {\n      if (index === 0 && pts[0] === counts[0] - 1) {\n        // nothing to increment\n        return false;\n      }\n\n      if (pts[index] + 1 < counts[index]) {\n        pts[index] = pts[index] + 1;\n        return true;\n      }\n\n      pts[index] = 0;\n      return incrementPointers(pts, counts, index - 1);\n    }\n\n    return pairs;\n  }\n\n  static hexToRgb(c) {\n    return hexToRgb(c);\n  }\n\n  getDefaultLayerConfig(\n    props: LayerBaseConfigPartial\n  ): LayerBaseConfig & Partial<LayerColorConfig & LayerSizeConfig> {\n    return {\n      dataId: props.dataId,\n      label: props.label || DEFAULT_LAYER_LABEL,\n      color: props.color || colorMaker.next().value,\n      // set columns later\n      columns: {},\n      isVisible: props.isVisible ?? true,\n      isConfigActive: props.isConfigActive ?? false,\n      highlightColor: props.highlightColor || DEFAULT_HIGHLIGHT_COLOR,\n      hidden: props.hidden ?? false,\n\n      // TODO: refactor this into separate visual Channel config\n      // color by field, domain is set by filters, field, scale type\n      colorField: null,\n      colorDomain: [0, 1],\n      colorScale: SCALE_TYPES.quantile,\n\n      // color by size, domain is set by filters, field, scale type\n      sizeDomain: [0, 1],\n      sizeScale: SCALE_TYPES.linear,\n      sizeField: null,\n\n      visConfig: {},\n\n      textLabel: [DEFAULT_TEXT_LABEL],\n\n      colorUI: {\n        color: DEFAULT_COLOR_UI,\n        colorRange: DEFAULT_COLOR_UI\n      },\n      animation: {enabled: false},\n      ...(props.columnMode ? {columnMode: props.columnMode} : {})\n    };\n  }\n\n  /**\n   * Get the description of a visualChannel config\n   * @param key\n   * @returns\n   */\n  getVisualChannelDescription(key: string): VisualChannelDescription {\n    // e.g. label: Color, measure: Vehicle Type\n    const channel = this.visualChannels[key];\n    if (!channel) return {label: '', measure: undefined};\n    const rangeSettings = this.visConfigSettings[channel.range];\n    const fieldSettings = this.config[channel.field];\n    const label = rangeSettings?.label;\n    return {\n      label: typeof label === 'function' ? label(this.config) : label || '',\n      measure: fieldSettings\n        ? fieldSettings.displayName || fieldSettings.name\n        : channel.defaultMeasure\n    };\n  }\n\n  /**\n   * Assign a field to layer column, return column config\n   */\n  assignColumn(key: string, field: {name: string; fieldIdx: number}): LayerColumns {\n    // field value could be null for optional columns\n    const update = field\n      ? {\n          value: field.name,\n          fieldIdx: field.fieldIdx\n        }\n      : {value: null, fieldIdx: -1};\n\n    return {\n      ...this.config.columns,\n      [key]: {\n        ...this.config.columns?.[key],\n        ...update\n      }\n    };\n  }\n\n  /**\n   * Assign a field pair to column config, return column config\n   */\n  assignColumnPairs(key: string, fieldPairs: FieldPair): LayerColumns {\n    if (!this.columnPairs || !this.columnPairs?.[key]) {\n      // should not end in this state\n      return this.config.columns;\n    }\n    // key = 'lat'\n    const {pair, fieldPairKey} = this.columnPairs?.[key] || {};\n\n    if (typeof fieldPairKey === 'string' && !fieldPairs[fieldPairKey]) {\n      // do not allow `key: undefined` to creep into the `updatedColumn` object\n      return this.config.columns;\n    }\n\n    // pair = ['lng', 'alt] | 'lng'\n    const updatedColumn = {\n      ...this.config.columns,\n      // @ts-expect-error fieldPairKey can be string[] here?\n      [key]: fieldPairs[fieldPairKey]\n    };\n\n    const partnerKeys = toArray(pair);\n    for (const partnerKey of partnerKeys) {\n      if (\n        this.config.columns[partnerKey] &&\n        this.columnPairs?.[partnerKey] &&\n        // @ts-ignore\n        fieldPairs[this.columnPairs?.[partnerKey].fieldPairKey]\n      ) {\n        // @ts-ignore\n        updatedColumn[partnerKey] = fieldPairs[this.columnPairs?.[partnerKey].fieldPairKey];\n      }\n    }\n\n    return updatedColumn;\n  }\n\n  /**\n   * Calculate a radius zoom multiplier to render points, so they are visible in all zoom level\n   * @param {object} mapState\n   * @param {number} mapState.zoom - actual zoom\n   * @param {number | void} mapState.zoomOffset - zoomOffset when render in the plot container for export image\n   * @returns {number}\n   */\n  getZoomFactor({zoom, zoomOffset = 0}) {\n    return Math.pow(2, Math.max(14 - zoom + zoomOffset, 0));\n  }\n\n  /**\n   * Calculate a elevation zoom multiplier to render points, so they are visible in all zoom level\n   * @param {object} mapState\n   * @param {number} mapState.zoom - actual zoom\n   * @param {number=} mapState.zoomOffset - zoomOffset when render in the plot container for export image\n   * @returns {number}\n   */\n  getElevationZoomFactor({zoom, zoomOffset = 0}: {zoom: number; zoomOffset?: number}): number {\n    // enableElevationZoomFactor is used to support existing maps\n    const {fixedHeight, enableElevationZoomFactor} = this.config.visConfig;\n    return fixedHeight || enableElevationZoomFactor === false\n      ? 1\n      : Math.pow(2, Math.max(8 - zoom + zoomOffset, 0));\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  formatLayerData(datasets: Datasets, oldLayerData?: unknown, animationConfig?: AnimationConfig) {\n    return {};\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  renderLayer(...args: any[]): any[] {\n    return [];\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  getHoverData(\n    object: any,\n    dataContainer: DataContainerInterface,\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    fields?: Field[],\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    animationConfig?: AnimationConfig,\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    hoverInfo?: {index: number}\n  ): any {\n    if (!object) {\n      return null;\n    }\n\n    // By default, each entry of layerData should have an index of a row in the original data container.\n    // Each layer can implement its own getHoverData method\n    return dataContainer.row(object.index);\n  }\n\n  getFilteredItemCount(): number | null {\n    // use first layer\n    if (Object.keys(this.filteredItemCount).length) {\n      const firstLayer = Object.keys(this.filteredItemCount)[0];\n      return this.filteredItemCount[firstLayer];\n    }\n    return null;\n  }\n  /**\n   * When change layer type, try to copy over layer configs as much as possible\n   * @param configToCopy - config to copy over\n   * @param visConfigSettings - visConfig settings of config to copy\n   * @param datasets - current datasets.\n   * @param defaultLayerProps - default layer creation configurations for current layer and datasets.\n   */\n  assignConfigToLayer(\n    configToCopy: LayerBaseConfig & Partial<LayerColorConfig & LayerSizeConfig>,\n    visConfigSettings: {[key: string]: ValueOf<LayerVisConfigSettings>},\n    datasets?: Datasets,\n    defaultLayerProps?: FindDefaultLayerPropsReturnValue | null\n  ) {\n    // don't deep merge visualChannel field\n    // don't deep merge color range, reversed: is not a key by default\n    const shallowCopy = ['colorRange', 'strokeColorRange'].concat(\n      Object.values(this.visualChannels).map(v => v.field)\n    );\n\n    // don't copy over domain and animation\n    const notToCopy = ['animation'].concat(Object.values(this.visualChannels).map(v => v.domain));\n    // if range is for the same property group copy it, otherwise, not to copy\n    Object.values(this.visualChannels).forEach(v => {\n      if (\n        configToCopy.visConfig[v.range] &&\n        this.visConfigSettings[v.range] &&\n        visConfigSettings[v.range].group !== this.visConfigSettings[v.range].group\n      ) {\n        notToCopy.push(v.range);\n      }\n    });\n\n    // don't copy over visualChannel range\n    const currentConfig = this.config;\n    const copied = this.copyLayerConfig(currentConfig, configToCopy, {\n      shallowCopy,\n      notToCopy\n    });\n\n    // update columNode based on new columns\n    if (this.config.columnMode && this.supportedColumnModes) {\n      const dataset = datasets?.[this.config.dataId];\n      // try to find a mode with all requied columns from the source config\n      let satisfiedColumnMode = getSatisfiedColumnMode(\n        this.supportedColumnModes,\n        copied.columns,\n        dataset?.fields\n      );\n\n      // if no suitable column mode found or no such columMode exists for the layer\n      // then try use one of the automatically detected layer configs\n      if (!satisfiedColumnMode) {\n        const options = [\n          ...(defaultLayerProps?.props || []),\n          ...(defaultLayerProps?.altProps || [])\n        ];\n        if (options.length) {\n          // Use the first of the default configurations\n          const defaultColumnConfig = options[0].columns;\n\n          satisfiedColumnMode = getSatisfiedColumnMode(\n            this.supportedColumnModes,\n            defaultColumnConfig,\n            dataset?.fields\n          );\n\n          if (satisfiedColumnMode) {\n            copied.columns = {\n              ...copied.columns,\n              ...defaultColumnConfig\n            };\n          }\n        }\n      }\n\n      copied.columnMode = satisfiedColumnMode?.key || copied.columnMode;\n    }\n\n    this.updateLayerConfig(copied);\n    // validate visualChannel field type and scale types\n    Object.keys(this.visualChannels).forEach(channel => {\n      this.validateVisualChannel(channel);\n    });\n  }\n\n  /*\n   * Recursively copy config over to an empty layer\n   * when received saved config, or copy config over from a different layer type\n   * make sure to only copy over value to existing keys\n   * @param {object} currentConfig - existing config to be override\n   * @param {object} configToCopy - new Config to copy over\n   * @param {string[]} shallowCopy - array of properties to not to be deep copied\n   * @param {string[]} notToCopy - array of properties not to copy\n   * @returns {object} - copied config\n   */\n  copyLayerConfig(\n    currentConfig,\n    configToCopy,\n    {shallowCopy = [], notToCopy = []}: {shallowCopy?: string[]; notToCopy?: string[]} = {}\n  ) {\n    const copied: {columnMode?: string; columns?: LayerColumns} = {};\n    Object.keys(currentConfig).forEach(key => {\n      if (\n        isPlainObject(currentConfig[key]) &&\n        isPlainObject(configToCopy[key]) &&\n        !shallowCopy.includes(key) &&\n        !notToCopy.includes(key)\n      ) {\n        // recursively assign object value\n        copied[key] = this.copyLayerConfig(currentConfig[key], configToCopy[key], {\n          shallowCopy,\n          notToCopy\n        });\n      } else if (notNullorUndefined(configToCopy[key]) && !notToCopy.includes(key)) {\n        // copy\n        copied[key] = configToCopy[key];\n      } else {\n        // keep existing\n        copied[key] = currentConfig[key];\n      }\n    });\n\n    return copied;\n  }\n\n  registerVisConfig(layerVisConfigs: {\n    [key: string]: keyof LayerVisConfigSettings | ValueOf<LayerVisConfigSettings>;\n  }) {\n    Object.keys(layerVisConfigs).forEach(item => {\n      const configItem = layerVisConfigs[item];\n      if (typeof configItem === 'string' && LAYER_VIS_CONFIGS[configItem]) {\n        // if assigned one of default LAYER_CONFIGS\n        this.config.visConfig[item] = LAYER_VIS_CONFIGS[configItem].defaultValue;\n        this.visConfigSettings[item] = LAYER_VIS_CONFIGS[configItem];\n      } else if (\n        typeof configItem === 'object' &&\n        ['type', 'defaultValue'].every(p => Object.prototype.hasOwnProperty.call(configItem, p))\n      ) {\n        // if provided customized visConfig, and has type && defaultValue\n        // TODO: further check if customized visConfig is valid\n        this.config.visConfig[item] = configItem.defaultValue;\n        this.visConfigSettings[item] = configItem;\n      }\n    });\n  }\n\n  getLayerColumns(propsColumns = {}) {\n    const columnValidators = this.columnValidators || {};\n    const required = this.requiredLayerColumns.reduce(\n      (accu, key) => ({\n        ...accu,\n        [key]: columnValidators[key]\n          ? {\n              value: propsColumns[key]?.value ?? null,\n              fieldIdx: propsColumns[key]?.fieldIdx ?? -1,\n              validator: columnValidators[key]\n            }\n          : {value: propsColumns[key]?.value ?? null, fieldIdx: propsColumns[key]?.fieldIdx ?? -1}\n      }),\n      {}\n    );\n    const optional = this.optionalColumns.reduce(\n      (accu, key) => ({\n        ...accu,\n        [key]: {\n          value: propsColumns[key]?.value ?? null,\n          fieldIdx: propsColumns[key]?.fieldIdx ?? -1,\n          optional: true\n        }\n      }),\n      {}\n    );\n\n    const columns = {...required, ...optional};\n\n    return columns;\n  }\n\n  updateLayerConfig<\n    LayerConfig extends LayerBaseConfig &\n      Partial<LayerColorConfig & LayerSizeConfig> = LayerBaseConfig\n  >(newConfig: Partial<LayerConfig>): Layer {\n    this.config = {...this.config, ...newConfig};\n    return this;\n  }\n\n  updateLayerVisConfig(newVisConfig) {\n    this.config.visConfig = {...this.config.visConfig, ...newVisConfig};\n    return this;\n  }\n\n  updateLayerColorUI(prop: string, newConfig: NestedPartial<ColorUI>): Layer {\n    const {colorUI: previous, visConfig} = this.config;\n\n    if (!isPlainObject(newConfig) || typeof prop !== 'string') {\n      return this;\n    }\n\n    const colorUIProp = Object.entries(newConfig).reduce((accu, [key, value]) => {\n      return {\n        ...accu,\n        [key]:\n          isPlainObject(accu[key]) && isPlainObject(value)\n            ? {...accu[key], ...(value as Record<string, unknown>)}\n            : value\n      };\n    }, previous[prop] || DEFAULT_COLOR_UI);\n\n    const colorUI = {\n      ...previous,\n      [prop]: colorUIProp\n    };\n\n    this.updateLayerConfig({colorUI});\n    // if colorUI[prop] is colorRange\n    const isColorRange = visConfig[prop] && visConfig[prop].colors;\n\n    if (isColorRange) {\n      // if open dropdown and prop is color range\n      // Automatically set colorRangeConfig's step and reversed\n      this.updateColorUIByColorRange(newConfig, prop);\n\n      // if changes in UI is made to 'reversed', 'steps' or steps\n      // update current layer colorRange\n      this.updateColorRangeByColorUI(newConfig, previous, prop);\n\n      // if set colorRangeConfig to custom\n      // initiate customPalette to be edited in the ui\n      this.updateCustomPalette(newConfig, previous, prop);\n    }\n\n    return this;\n  }\n\n  // if set colorRangeConfig to custom palette or custom breaks\n  // initiate customPalette to be edited in the ui\n  updateCustomPalette(newConfig, previous, prop) {\n    if (!newConfig.colorRangeConfig?.custom && !newConfig.colorRangeConfig?.customBreaks) {\n      return;\n    }\n\n    if (newConfig.customPalette) {\n      // if new config also set customPalette, no need to initiate new\n      return;\n    }\n    const {colorUI, visConfig} = this.config;\n\n    if (!visConfig[prop]) return;\n    // make copy of current color range to customPalette\n    let customPalette = {\n      ...visConfig[prop]\n    };\n\n    if (newConfig.colorRangeConfig.customBreaks && !customPalette.colorMap) {\n      // find visualChanel\n      const visualChannels = this.visualChannels;\n      const channelKey = Object.keys(visualChannels).find(\n        key => visualChannels[key].range === prop\n      );\n      if (!channelKey) {\n        // should never happn\n        Console.warn(`updateColorUI: Can't find visual channel which range is ${prop}`);\n        return;\n      }\n      // add name|type|category to updateCustomPalette if customBreaks, so that\n      // colors will not be override as well when inverse palette with custom break\n      // initiate colorMap from current scale\n\n      const colorMap = initializeLayerColorMap(this, visualChannels[channelKey]);\n      customPalette = initializeCustomPalette(visConfig[prop], colorMap);\n    } else if (newConfig.colorRangeConfig.custom) {\n      customPalette = initializeCustomPalette(visConfig[prop]);\n    }\n\n    this.updateLayerConfig({\n      colorUI: {\n        ...colorUI,\n        [prop]: {\n          ...colorUI[prop],\n          customPalette\n        }\n      }\n    });\n  }\n\n  /**\n   * if open dropdown and prop is color range\n   * Automatically set colorRangeConfig's step and reversed\n   * @param {*} newConfig\n   * @param {*} prop\n   */\n  updateColorUIByColorRange(newConfig, prop) {\n    const {colorUI, visConfig} = this.config;\n\n    // when custom palette adds/removes step, the number in \"Steps\" input control\n    // should be updated as well\n    const isCustom = newConfig.customPalette?.category === 'Custom';\n    const customStepsChanged = isCustom\n      ? newConfig.customPalette.colors.length !== visConfig[prop].colors.length\n      : false;\n\n    if (typeof newConfig.showDropdown !== 'number' && !customStepsChanged) return;\n\n    this.updateLayerConfig({\n      colorUI: {\n        ...colorUI,\n        [prop]: {\n          ...colorUI[prop],\n          colorRangeConfig: {\n            ...colorUI[prop].colorRangeConfig,\n            steps: customStepsChanged\n              ? colorUI[prop].customPalette.colors.length\n              : visConfig[prop].colors.length,\n            reversed: Boolean(visConfig[prop].reversed)\n          }\n        }\n      }\n    });\n  }\n\n  updateColorRangeByColorUI(newConfig, previous, prop) {\n    // only update colorRange if changes in UI is made to 'reversed', 'steps' or steps\n    const shouldUpdate =\n      newConfig.colorRangeConfig &&\n      ['reversed', 'steps', 'colorBlindSafe', 'type'].some(\n        key =>\n          Object.prototype.hasOwnProperty.call(newConfig.colorRangeConfig, key) &&\n          newConfig.colorRangeConfig[key] !==\n            (previous[prop] || DEFAULT_COLOR_UI).colorRangeConfig[key]\n      );\n    if (!shouldUpdate) return;\n\n    const {colorUI, visConfig} = this.config;\n\n    // for custom palette, one can only 'reverse' the colors in custom palette.\n    // changing 'steps', 'colorBindSafe', 'type' should fall back to predefined palette.\n    const isCustomColorReversed =\n      visConfig.colorRange.category === 'Custom' &&\n      newConfig.colorRangeConfig &&\n      Object.prototype.hasOwnProperty.call(newConfig.colorRangeConfig, 'reversed');\n\n    const update = isCustomColorReversed\n      ? updateCustomColorRangeByColorUI(visConfig[prop], colorUI[prop].colorRangeConfig)\n      : updateColorRangeByMatchingPalette(visConfig[prop], colorUI[prop].colorRangeConfig);\n\n    if (update) {\n      this.updateLayerVisConfig({[prop]: update});\n    }\n  }\n  hasColumnValue(column?: LayerColumn) {\n    return Boolean(column && column.value && column.fieldIdx > -1);\n  }\n  hasRequiredColumn(column?: LayerColumn) {\n    return Boolean(column && (column.optional || this.hasColumnValue(column)));\n  }\n  /**\n   * Check whether layer has all columns\n   * @returns yes or no\n   */\n  hasAllColumns(): boolean {\n    const {columns, columnMode} = this.config;\n    // if layer has different column mode, check if have all required columns of current column Mode\n    if (columnMode) {\n      const currentColumnModes = (this.supportedColumnModes || []).find(\n        colMode => colMode.key === columnMode\n      );\n      return Boolean(\n        currentColumnModes !== undefined &&\n          currentColumnModes.requiredColumns?.every(colKey => this.hasColumnValue(columns[colKey]))\n      );\n    }\n    return Boolean(\n      columns &&\n        Object.values(columns).every((column?: LayerColumn) => this.hasRequiredColumn(column))\n    );\n  }\n\n  /**\n   * Check whether layer has data\n   *\n   * @param {Array | Object} layerData\n   * @returns {boolean} yes or no\n   */\n  hasLayerData(layerData: {data: unknown[] | arrow.Table}) {\n    if (!layerData) {\n      return false;\n    }\n\n    return Boolean(\n      layerData.data &&\n        ((layerData.data as unknown[]).length || (layerData.data as arrow.Table).numRows)\n    );\n  }\n\n  isValidToSave(): boolean {\n    return Boolean(this.type && this.hasAllColumns());\n  }\n\n  shouldRenderLayer(data): boolean {\n    return (\n      Boolean(this.type) &&\n      this.hasAllColumns() &&\n      this.hasLayerData(data) &&\n      typeof this.renderLayer === 'function'\n    );\n  }\n\n  getColorScale(\n    colorScale: string,\n    colorDomain: VisualChannelDomain,\n    colorRange: ColorRange\n  ): GetVisChannelScaleReturnType {\n    if (colorScale === SCALE_TYPES.customOrdinal) {\n      return getCategoricalColorScale(colorDomain, colorRange);\n    }\n\n    if (hasColorMap(colorRange) && colorScale === SCALE_TYPES.custom) {\n      const cMap = new Map();\n      colorRange.colorMap?.forEach(([k, v]) => {\n        cMap.set(k, typeof v === 'string' ? hexToRgb(v) : v);\n      });\n\n      const scaleType = colorScale === SCALE_TYPES.custom ? colorScale : SCALE_TYPES.ordinal;\n\n      const scale = getScaleFunction(scaleType, cMap.values(), cMap.keys(), false);\n      scale.unknown(cMap.get(UNKNOWN_COLOR_KEY) || NO_VALUE_COLOR);\n\n      return scale as GetVisChannelScaleReturnType;\n    }\n    return this.getVisChannelScale(colorScale, colorDomain, colorRange.colors.map(hexToRgb));\n  }\n\n  accessVSFieldValue(_field, _indexKey) {\n    return defaultGetFieldValue;\n  }\n  /**\n   * Mapping from visual channels to deck.gl accesors\n   * @param param Parameters\n   * @param param.dataAccessor Access kepler.gl layer data from deck.gl layer\n   * @param param.dataContainer DataContainer to use use with dataAccessor\n   * @return {Object} attributeAccessors - deck.gl layer attribute accessors\n   */\n  getAttributeAccessors({\n    dataAccessor = defaultDataAccessor,\n    dataContainer,\n    indexKey\n  }: {\n    dataAccessor?: typeof defaultDataAccessor;\n    dataContainer: DataContainerInterface;\n    indexKey?: number | null;\n  }) {\n    const attributeAccessors: {[key: string]: any} = {};\n\n    Object.keys(this.visualChannels).forEach(channel => {\n      const {\n        field,\n        fixed,\n        scale,\n        domain,\n        range,\n        accessor,\n        defaultValue,\n        getAttributeValue,\n        nullValue,\n        channelScaleType\n      } = this.visualChannels[channel];\n\n      if (accessor) {\n        const shouldGetScale = this.config[field];\n\n        if (shouldGetScale) {\n          const isFixed = fixed && this.config.visConfig[fixed];\n\n          const scaleFunction =\n            channelScaleType === CHANNEL_SCALES.color\n              ? this.getColorScale(\n                  this.config[scale],\n                  this.config[domain],\n                  this.config.visConfig[range]\n                )\n              : this.getVisChannelScale(\n                  this.config[scale],\n                  this.config[domain],\n                  this.config.visConfig[range],\n                  isFixed\n                );\n\n          const getFieldValue = this.accessVSFieldValue(this.config[field], indexKey);\n\n          if (scaleFunction) {\n            attributeAccessors[accessor] = scaleFunction.byZoom\n              ? memoize(z => {\n                  const scaleFunc = scaleFunction(z);\n                  return d =>\n                    this.getEncodedChannelValue(\n                      scaleFunc,\n                      dataAccessor(dataContainer)(d),\n                      this.config[field],\n                      nullValue,\n                      getFieldValue\n                    );\n                })\n              : d =>\n                  this.getEncodedChannelValue(\n                    scaleFunction,\n                    dataAccessor(dataContainer)(d),\n                    this.config[field],\n                    nullValue,\n                    getFieldValue\n                  );\n\n            // set getFillColorByZoom to true\n            if (scaleFunction.byZoom) {\n              attributeAccessors[`${accessor}ByZoom`] = true;\n            }\n          }\n        } else if (typeof getAttributeValue === 'function') {\n          attributeAccessors[accessor] = getAttributeValue(this.config);\n        } else {\n          attributeAccessors[accessor] =\n            typeof defaultValue === 'function' ? defaultValue(this.config) : defaultValue;\n        }\n\n        if (!attributeAccessors[accessor]) {\n          Console.warn(`Failed to provide accessor function for ${accessor || channel}`);\n        }\n      }\n    });\n\n    return attributeAccessors;\n  }\n\n  getVisChannelScale(\n    scale: string,\n    domain: VisualChannelDomain | DomainQuantiles,\n    range: any,\n    fixed?: boolean\n  ): GetVisChannelScaleReturnType {\n    // if quantile is provided per zoom\n    if (isDomainQuantile(domain) && scale === SCALE_TYPES.quantile) {\n      const zSteps = domain.z;\n\n      const getScale = function getScaleByZoom(z) {\n        const scaleDomain = getDomainStepsbyZoom(domain.quantiles, zSteps, z);\n        const thresholds = getThresholdsFromQuantiles(scaleDomain, range.length);\n\n        return getScaleFunction('threshold', range, thresholds, false);\n      };\n\n      getScale.byZoom = true;\n      return getScale;\n    } else if (isDomainStops(domain)) {\n      // color is based on zoom\n      const zSteps = domain.z;\n      // get scale function by z\n      // {\n      //  z: [z, z, z],\n      //  stops: [[min, max], [min, max]],\n      //  interpolation: 'interpolate'\n      // }\n\n      const getScale = function getScaleByZoom(z) {\n        const scaleDomain = getDomainStepsbyZoom(domain.stops, zSteps, z);\n\n        return getScaleFunction(scale, range, scaleDomain, fixed);\n      };\n\n      getScale.byZoom = true;\n      return getScale;\n    }\n\n    return SCALE_FUNC[fixed ? 'linear' : scale]()\n      .domain(domain)\n      .range(fixed ? domain : range);\n  }\n\n  /**\n   * Get longitude and latitude bounds of the data.\n   */\n  getPointsBounds(\n    dataContainer: DataContainerInterface,\n    getPosition: (x: any, dc: DataContainerInterface) => number[] = identity\n  ): number[] | null {\n    // no need to loop through the entire dataset\n    // get a sample of data to calculate bounds\n    const sampleData =\n      dataContainer.numRows() > MAX_SAMPLE_SIZE\n        ? getSampleContainerData(dataContainer, MAX_SAMPLE_SIZE)\n        : dataContainer;\n\n    const points = getPosition ? sampleData.mapIndex(getPosition) : [];\n\n    const latBounds = getLatLngBounds(points, 1, [-90, 90]);\n    const lngBounds = getLatLngBounds(points, 0, [-180, 180]);\n\n    if (!latBounds || !lngBounds) {\n      return null;\n    }\n\n    return [lngBounds[0], latBounds[0], lngBounds[1], latBounds[1]];\n  }\n\n  getChangedTriggers(dataUpdateTriggers) {\n    const triggerChanged = diffUpdateTriggers(dataUpdateTriggers, this._oldDataUpdateTriggers);\n    this._oldDataUpdateTriggers = dataUpdateTriggers;\n\n    return triggerChanged;\n  }\n\n  getEncodedChannelValue(\n    scale: (value) => any,\n    data: any[],\n    field: VisualChannelField,\n    nullValue = NO_VALUE_COLOR,\n    getValue = defaultGetFieldValue\n  ) {\n    const value = getValue(field, data);\n\n    if (!notNullorUndefined(value)) {\n      return nullValue;\n    }\n\n    let attributeValue;\n    if (Array.isArray(value)) {\n      attributeValue = value.map(scale);\n    } else {\n      attributeValue = scale(value);\n    }\n\n    if (!notNullorUndefined(attributeValue)) {\n      attributeValue = nullValue;\n    }\n\n    return attributeValue;\n  }\n\n  updateMeta(meta: Layer['meta']) {\n    this.meta = {...this.meta, ...meta};\n  }\n\n  getDataUpdateTriggers({filteredIndex, id, dataContainer}: KeplerTable): any {\n    const {columns} = this.config;\n\n    return {\n      getData: {datasetId: id, dataContainer, columns, filteredIndex},\n      getMeta: {datasetId: id, dataContainer, columns},\n      ...(this.config.textLabel || []).reduce(\n        (accu, tl, i) => ({\n          ...accu,\n          [`getLabelCharacterSet-${i}`]: tl.field ? tl.field.name : null\n        }),\n        {}\n      )\n    };\n  }\n\n  updateData(datasets: Datasets, oldLayerData: any) {\n    if (!this.config.dataId) {\n      return {};\n    }\n    const layerDataset = datasets[this.config.dataId];\n    const {dataContainer} = layerDataset;\n\n    const getPosition = this.getPositionAccessor(dataContainer, layerDataset);\n    const dataUpdateTriggers = this.getDataUpdateTriggers(layerDataset);\n    const triggerChanged = this.getChangedTriggers(dataUpdateTriggers);\n\n    if (triggerChanged && (triggerChanged.getMeta || triggerChanged.getData)) {\n      this.updateLayerMeta(layerDataset, getPosition);\n\n      // reset filteredItemCount\n      this.filteredItemCount = {};\n    }\n\n    let data = [];\n\n    if (!(triggerChanged && triggerChanged.getData) && oldLayerData && oldLayerData.data) {\n      // same data\n      data = oldLayerData.data;\n    } else {\n      data = this.calculateDataAttribute(layerDataset, getPosition);\n    }\n\n    return {data, triggerChanged};\n  }\n\n  /**\n   * helper function to update one layer domain when state.data changed\n   * if state.data change is due ot update filter, newFiler will be passed\n   * called by updateAllLayerDomainData\n   * @param datasets\n   * @param newFilter\n   * @returns layer\n   */\n  updateLayerDomain(datasets: Datasets, newFilter?: Filter): Layer {\n    const table = this.getDataset(datasets);\n    if (!table) {\n      return this;\n    }\n    Object.values(this.visualChannels).forEach(channel => {\n      const {scale} = channel;\n      const scaleType = this.config[scale];\n      // ordinal domain is based on dataContainer, if only filter changed\n      // no need to update ordinal domain\n      if (!newFilter || scaleType !== SCALE_TYPES.ordinal) {\n        const {domain} = channel;\n        const updatedDomain = this.calculateLayerDomain(table, channel);\n        this.updateLayerConfig({[domain]: updatedDomain});\n      }\n    });\n\n    return this;\n  }\n\n  getDataset(datasets) {\n    return this.config.dataId ? datasets[this.config.dataId] : null;\n  }\n\n  /**\n   * Validate visual channel field and scales based on supported field & scale type\n   * @param channel\n   */\n  validateVisualChannel(channel: string) {\n    this.validateFieldType(channel);\n    this.validateScale(channel);\n  }\n\n  /**\n   * Validate field type based on channelScaleType\n   */\n  validateFieldType(channel: string) {\n    const visualChannel = this.visualChannels[channel];\n    const {field, channelScaleType, supportedFieldTypes} = visualChannel;\n\n    if (this.config[field]) {\n      // if field is selected, check if field type is supported\n      const channelSupportedFieldTypes =\n        supportedFieldTypes || CHANNEL_SCALE_SUPPORTED_FIELDS[channelScaleType];\n\n      if (!channelSupportedFieldTypes.includes(this.config[field].type)) {\n        // field type is not supported, set it back to null\n        // set scale back to default\n        this.updateLayerConfig({[field]: null});\n      }\n    }\n  }\n\n  /**\n   * Validate scale type based on aggregation\n   */\n  validateScale(channel) {\n    const visualChannel = this.visualChannels[channel];\n    const {scale} = visualChannel;\n    if (!scale) {\n      // visualChannel doesn't have scale\n      return;\n    }\n    const scaleOptions = this.getScaleOptions(channel);\n    // check if current selected scale is\n    // supported, if not, change to default\n    if (!scaleOptions.includes(this.config[scale])) {\n      this.updateLayerConfig({[scale]: scaleOptions[0]});\n    }\n  }\n\n  /**\n   * Get scale options based on current field\n   * @param {string} channel\n   * @returns {string[]}\n   */\n  getScaleOptions(channel: string): string[] {\n    const visualChannel = this.visualChannels[channel];\n    const {field, scale, channelScaleType} = visualChannel;\n\n    return this.config[field]\n      ? FIELD_OPTS[this.config[field].type].scale[channelScaleType]\n      : [this.getDefaultLayerConfig({dataId: ''})[scale]];\n  }\n\n  updateLayerVisualChannel(dataset: KeplerTable, channel: string) {\n    const visualChannel = this.visualChannels[channel];\n    this.validateVisualChannel(channel);\n    // calculate layer channel domain\n    const updatedDomain = this.calculateLayerDomain(dataset, visualChannel);\n    this.updateLayerConfig({[visualChannel.domain]: updatedDomain});\n  }\n\n  getVisualChannelUpdateTriggers(): UpdateTriggers {\n    const updateTriggers: UpdateTriggers = {};\n    Object.values(this.visualChannels).forEach(visualChannel => {\n      // field range scale domain\n      const {accessor, field, scale, domain, range, defaultValue, fixed} = visualChannel;\n\n      if (accessor) {\n        updateTriggers[accessor] = {\n          [field]: this.config[field],\n          [scale]: this.config[scale],\n          [domain]: this.config[domain],\n          [range]: this.config.visConfig[range],\n          defaultValue:\n            typeof defaultValue === 'function' ? defaultValue(this.config) : defaultValue,\n          ...(fixed ? {[fixed]: this.config.visConfig[fixed]} : {})\n        };\n      }\n    });\n    return updateTriggers;\n  }\n\n  calculateLayerDomain(dataset, visualChannel) {\n    const {scale} = visualChannel;\n    const scaleType = this.config[scale];\n\n    const field = this.config[visualChannel.field];\n    if (!field) {\n      // if colorField or sizeField were set back to null\n      return defaultDomain;\n    }\n\n    return dataset.getColumnLayerDomain(field, scaleType) || defaultDomain;\n  }\n\n  hasHoveredObject(objectInfo) {\n    return this.isLayerHovered(objectInfo) && objectInfo.object ? objectInfo.object : null;\n  }\n\n  isLayerHovered(objectInfo): boolean {\n    return objectInfo?.picked && objectInfo?.layer?.props?.id === this.id;\n  }\n\n  getRadiusScaleByZoom(mapState: MapState, fixedRadius?: boolean) {\n    const radiusChannel = Object.values(this.visualChannels).find(vc => vc.property === 'radius');\n\n    if (!radiusChannel) {\n      return 1;\n    }\n\n    const field = radiusChannel.field;\n    const fixed = fixedRadius === undefined ? this.config.visConfig.fixedRadius : fixedRadius;\n    const {radius} = this.config.visConfig;\n\n    return fixed ? 1 : (this.config[field] ? 1 : radius) * this.getZoomFactor(mapState);\n  }\n\n  shouldCalculateLayerData(props: string[]) {\n    return props.some(p => !this.noneLayerDataAffectingProps.includes(p));\n  }\n\n  getBrushingExtensionProps(interactionConfig, brushingTarget?) {\n    const {brush} = interactionConfig;\n\n    return {\n      // brushing\n      autoHighlight: !brush.enabled,\n      brushingRadius: brush.config.size * 1000,\n      brushingTarget: brushingTarget || 'source',\n      brushingEnabled: brush.enabled\n    };\n  }\n\n  getDefaultDeckLayerProps({\n    idx,\n    gpuFilter,\n    mapState,\n    layerCallbacks,\n    visible\n  }: {\n    idx: number;\n    gpuFilter: GpuFilter;\n    mapState: MapState;\n    layerCallbacks: any;\n    visible: boolean;\n  }) {\n    return {\n      id: this.id,\n      idx,\n      coordinateSystem: COORDINATE_SYSTEM.LNGLAT,\n      pickable: true,\n      wrapLongitude: true,\n      parameters: {depthTest: Boolean(mapState.dragRotate || this.config.visConfig.enable3d)},\n      hidden: this.config.hidden,\n      // visconfig\n      opacity: this.config.visConfig.opacity,\n      highlightColor: this.config.highlightColor,\n      // data filtering\n      extensions: [dataFilterExtension],\n      filterRange: gpuFilter ? gpuFilter.filterRange : undefined,\n      onFilteredItemsChange: gpuFilter ? layerCallbacks?.onFilteredItemsChange : undefined,\n\n      // layer should be visible and if splitMap, shown in to one of panel\n      visible: this.config.isVisible && visible\n    };\n  }\n\n  getDefaultHoverLayerProps() {\n    return {\n      id: `${this.id}-hovered`,\n      pickable: false,\n      wrapLongitude: true,\n      coordinateSystem: COORDINATE_SYSTEM.LNGLAT\n    };\n  }\n\n  renderTextLabelLayer(\n    {\n      getPosition,\n      getFiltered,\n      getPixelOffset,\n      backgroundProps,\n      updateTriggers,\n      sharedProps\n    }: {\n      getPosition?: ((d: any) => number[]) | arrow.Vector;\n      getFiltered?: (data: {index: number}, objectInfo: {index: number}) => number;\n      getPixelOffset: (textLabel: any) => number[] | ((d: any) => number[]);\n      backgroundProps?: {background: boolean};\n      updateTriggers: {\n        [key: string]: any;\n      };\n      sharedProps: any;\n    },\n    renderOpts\n  ) {\n    const {data, mapState} = renderOpts;\n    const {textLabel} = this.config;\n\n    const TextLayerClass = isArrowTable(data.data) ? GeoArrowTextLayer : TextLayer;\n\n    return data.textLabels.reduce((accu, d, i) => {\n      if (d.getText) {\n        const background = textLabel[i].background || backgroundProps?.background;\n\n        accu.push(\n          // @ts-expect-error\n          new TextLayerClass({\n            ...sharedProps,\n            id: `${this.id}-label-${textLabel[i].field?.name}`,\n            data: data.data,\n            visible: this.config.isVisible,\n            getText: d.getText,\n            getPosition,\n            getFiltered,\n            characterSet: d.characterSet,\n            getPixelOffset: getPixelOffset(textLabel[i]),\n            getSize: PROJECTED_PIXEL_SIZE_MULTIPLIER,\n            sizeScale: textLabel[i].size,\n            getTextAnchor: textLabel[i].anchor,\n            getAlignmentBaseline: textLabel[i].alignment,\n            getColor: textLabel[i].color,\n            outlineWidth: textLabel[i].outlineWidth * TEXT_OUTLINE_MULTIPLIER,\n            outlineColor: textLabel[i].outlineColor,\n            background,\n            getBackgroundColor: textLabel[i].backgroundColor,\n            fontSettings: {\n              sdf: textLabel[i].outlineWidth > 0\n            },\n            parameters: {\n              // text will always show on top of all layers\n              depthTest: false\n            },\n\n            getFilterValue: data.getFilterValue,\n            updateTriggers: {\n              ...updateTriggers,\n              getText: textLabel[i].field?.name,\n              getPixelOffset: {\n                ...updateTriggers.getRadius,\n                mapState,\n                anchor: textLabel[i].anchor,\n                alignment: textLabel[i].alignment\n              },\n              getTextAnchor: textLabel[i].anchor,\n              getAlignmentBaseline: textLabel[i].alignment,\n              getColor: textLabel[i].color\n            },\n            _subLayerProps: {\n              ...(background\n                ? {\n                    background: {\n                      parameters: {\n                        cull: false\n                      }\n                    }\n                  }\n                : null)\n            }\n          })\n        );\n      }\n      return accu;\n    }, []);\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  calculateDataAttribute(keplerTable: KeplerTable, getPosition): any {\n    // implemented in subclasses\n    return [];\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  updateLayerMeta(dataset: KeplerTable, getPosition) {\n    // implemented in subclasses\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  getPositionAccessor(\n    _dataContainer?: DataContainerInterface,\n    // TODO refactor for the next major version to pass only dataset\n    _dataset?: KeplerTable\n  ): (...args: any[]) => any {\n    // implemented in subclasses\n    return () => null;\n  }\n\n  getLegendVisualChannels(): {[key: string]: VisualChannel} {\n    return this.visualChannels;\n  }\n}\n\nexport default Layer;\n"
  },
  {
    "path": "src/layers/src/base.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component, CSSProperties} from 'react';\n\nconst getStyleClassFromColor = (totalColor: number, colors: string[]) =>\n  new Array(totalColor)\n    .fill(1)\n    .reduce((accu, c, i) => `${accu}.cr${i + 1} {fill:${colors[i % colors.length]};}`, '');\n\nconst nop = () => {\n  return;\n};\n\nexport type BaseProps = {\n  /** Set the height of the icon, ex. '16px' */\n  height?: string;\n  /** Set the width of the icon, ex. '16px' */\n  width?: string;\n  /** Set the viewbox of the svg */\n  viewBox?: string;\n  /** Path element */\n\n  predefinedClassName?: string;\n  className?: string;\n  style?: CSSProperties;\n  colors?: string[];\n  totalColor?: number;\n} & React.SVGAttributes<SVGSVGElement> &\n  React.DOMAttributes<SVGSVGElement>;\n\nexport class Base extends Component<BaseProps> {\n  static displayName = 'Base Icon';\n\n  static defaultProps = {\n    height: null,\n    width: null,\n    viewBox: '0 0 64 64',\n    predefinedClassName: '',\n    className: '',\n    style: {\n      fill: 'currentColor'\n    }\n  };\n\n  render() {\n    const {\n      height,\n      width,\n      viewBox,\n      style,\n      children,\n      predefinedClassName,\n      className,\n      colors,\n      totalColor,\n      ...props\n    } = this.props;\n    const svgHeight = height;\n    const svgWidth = width || svgHeight;\n\n    const fillStyle =\n      Array.isArray(colors) && totalColor && getStyleClassFromColor(totalColor, colors);\n\n    return (\n      <svg\n        viewBox={viewBox}\n        width={svgWidth}\n        height={svgHeight}\n        style={style}\n        className={`${predefinedClassName} ${className}`}\n        onClick={nop}\n        {...props}\n      >\n        {fillStyle ? <style type=\"text/css\">{fillStyle}</style> : null}\n        {children}\n      </svg>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/cluster-layer/cluster-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nclass ClusterLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'cluster-layer-icon',\n    totalColor: 5\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M13.6,22.7c2.9-3.6,4.4-6.3,4.4-8c0-2.7-2.2-4.9-4.9-4.9S8.2,12,8.2,14.7c0,1.7,1.5,4.4,4.4,8l0,0\n\tC12.8,23,13.2,23,13.6,22.7C13.5,22.8,13.6,22.7,13.6,22.7z\"\n          className=\"cr1\"\n        />\n        <path\n          d=\"M22.9,57.4c2.5-3.1,3.8-5.5,3.8-7c0-2.4-2-4.4-4.4-4.4S18,48,18,50.4c0,1.5,1.3,3.8,3.8,7l0,0\n\tc0.3,0.3,0.7,0.4,1,0.1C22.9,57.4,22.9,57.4,22.9,57.4z\"\n          className=\"cr2\"\n        />\n        <path\n          d=\"M51.4,22.5c2.8-3.4,4.2-6,4.2-7.6c0-2.6-2.1-4.8-4.8-4.8c-2.6,0-4.8,2.1-4.8,4.8c0,1.6,1.4,4.2,4.2,7.6\n\tl0,0c0.3,0.3,0.8,0.4,1.1,0.1C51.3,22.5,51.4,22.5,51.4,22.5z\"\n          className=\"cr3\"\n        />\n        <path\n          d=\"M49.2,53.8c3.7-4.5,5.5-7.8,5.5-9.9c0-3.3-2.7-6.1-6.1-6.1c-3.3,0-6.1,2.7-6.1,6.1\n\tc0,2.1,1.8,5.4,5.5,9.9l0,0c0.3,0.3,0.7,0.4,1.1,0.1C49.1,53.8,49.1,53.8,49.2,53.8z\"\n          className=\"cr4\"\n        />\n        <path\n          d=\"M31.4,39.6C36.5,33.5,39,29,39,26.1c0-4.4-3.6-8-8-8s-8,3.6-8,8c0,2.9,2.5,7.4,7.6,13.5l0,0\n\tC30.8,39.8,31.1,39.9,31.4,39.6C31.3,39.7,31.4,39.6,31.4,39.6z\"\n          className=\"cr5\"\n        />\n      </Base>\n    );\n  }\n}\n\nexport default ClusterLayerIcon;\n"
  },
  {
    "path": "src/layers/src/cluster-layer/cluster-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport AggregationLayer, {AggregationLayerConfig} from '../aggregation-layer';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\nimport {DeckGLClusterLayer} from '@kepler.gl/deckgl-layers';\nimport ClusterLayerIcon from './cluster-layer-icon';\nimport {\n  ColorRange,\n  Merge,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  VisConfigSelection\n} from '@kepler.gl/types';\nimport {CHANNEL_SCALES, AggregationTypes} from '@kepler.gl/constants';\nimport {VisualChannels} from '../base-layer';\n\nexport type ClusterLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  clusterRadius: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  radiusRange: VisConfigRange;\n  colorAggregation: VisConfigSelection;\n};\n\nexport type ClusterLayerVisConfig = {\n  opacity: number;\n  clusterRadius: number;\n  colorRange: ColorRange;\n  radiusRange: [number, number];\n  colorAggregation: AggregationTypes;\n};\n\nexport type ClusterLayerConfig = Merge<AggregationLayerConfig, {visConfig: ClusterLayerVisConfig}>;\n\nexport const clusterVisConfigs: {\n  opacity: 'opacity';\n  clusterRadius: 'clusterRadius';\n  colorRange: 'colorRange';\n  radiusRange: 'clusterRadiusRange';\n  colorAggregation: 'colorAggregation';\n} = {\n  opacity: 'opacity',\n  clusterRadius: 'clusterRadius',\n  colorRange: 'colorRange',\n  radiusRange: 'clusterRadiusRange',\n  colorAggregation: 'colorAggregation'\n};\n\nexport default class ClusterLayer extends AggregationLayer {\n  declare visConfigSettings: ClusterLayerVisConfigSettings;\n  declare config: ClusterLayerConfig;\n\n  constructor(props) {\n    super(props);\n    this.registerVisConfig(clusterVisConfigs);\n\n    // Access data of a point from aggregated clusters, depends on how getClusterer works\n    this.getPointData = pt => pt;\n  }\n\n  get type(): 'cluster' {\n    return 'cluster';\n  }\n\n  get layerIcon() {\n    return ClusterLayerIcon;\n  }\n\n  get visualChannels(): VisualChannels {\n    return {\n      color: {\n        aggregation: 'colorAggregation',\n        channelScaleType: CHANNEL_SCALES.colorAggr,\n        defaultMeasure: 'property.pointCount',\n        domain: 'colorDomain',\n        field: 'colorField',\n        key: 'color',\n        property: 'color',\n        range: 'colorRange',\n        scale: 'colorScale'\n      }\n    };\n  }\n\n  renderLayer(opts) {\n    const {visConfig} = this.config;\n    const {data, gpuFilter, objectHovered, mapState, layerCallbacks} = opts;\n\n    const updateTriggers = {\n      getColorValue: {\n        colorField: this.config.colorField,\n        colorAggregation: this.config.visConfig.colorAggregation,\n        colorRange: visConfig.colorRange,\n        colorMap: visConfig.colorRange.colorMap\n      },\n      filterData: {\n        filterRange: gpuFilter.filterRange,\n        ...gpuFilter.filterValueUpdateTriggers\n      }\n    };\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n\n    const {_filterData: filterData, ...clusterData} = data;\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    return [\n      new DeckGLClusterLayer({\n        ...defaultLayerProps,\n        ...clusterData,\n        filterData,\n\n        // radius\n        radiusScale: 1,\n        radiusRange: visConfig.radiusRange,\n        clusterRadius: visConfig.clusterRadius,\n\n        // color\n        colorRange: this.getColorRange(visConfig.colorRange),\n        colorMap: visConfig.colorRange.colorMap,\n        colorScaleType: this.config.colorScale,\n        colorAggregation: visConfig.colorAggregation,\n\n        zoom: Math.round(mapState.zoom),\n        width: mapState.width,\n        height: mapState.height,\n\n        // updateTriggers\n        updateTriggers,\n\n        // call back from layer after calculate clusters\n        onSetColorDomain: layerCallbacks.onSetLayerDomain\n      }),\n      // hover layer\n      ...(hoveredObject\n        ? [\n            new ScatterplotLayer<{scaledRadiusValue: number}>({\n              id: `${this.id}-hovered`,\n              visible: defaultLayerProps.visible,\n              data: [hoveredObject],\n              getFillColor: this.config.highlightColor,\n              getRadius: (d: {scaledRadiusValue: number}) => d.scaledRadiusValue,\n              radiusScale: 1,\n              pickable: false\n            })\n          ]\n        : [])\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/default-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from './base';\n\nclass DefaultLayerIcon extends Component {\n  static propTypes = {\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'default-layer-icon'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <circle cx=\"29.4\" cy=\"31.6\" r=\"8.4\" />\n        <circle cx=\"48.5\" cy=\"15.7\" r=\"6.5\" />\n        <circle cx=\"11\" cy=\"44.2\" r=\"3\" />\n        <circle cx=\"50\" cy=\"44.2\" r=\"5\" />\n        <circle cx=\"34\" cy=\"54.2\" r=\"3\" />\n        <circle cx=\"14\" cy=\"16.2\" r=\"4\" />\n      </Base>\n    );\n  }\n}\n\nexport default DefaultLayerIcon;\n"
  },
  {
    "path": "src/layers/src/editor-layer/constants.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {RGBColor} from '@kepler.gl/types';\n\nexport const COLORS = {\n  // blue\n  PRIMARY: [0x26, 0xb5, 0xf2] as RGBColor,\n  // gray\n  SECONDARY: [0xaa, 0xaa, 0xaa] as RGBColor,\n  // yellow\n  HIGHLIGHT: [0xff, 0xff, 0x00] as RGBColor\n};\n\nexport const EDIT_TYPES = {\n  ADD_POSITION: 'addPosition',\n  MOVE_POSITION: 'movePosition',\n  TRANSLATING: 'translating',\n  ADD_FEATURE: 'addFeature'\n};\n"
  },
  {
    "path": "src/layers/src/editor-layer/editor-layer-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {PickInfo} from '@deck.gl/core/lib/deck';\nimport {Editor, Feature, FeatureSelectionContext} from '@kepler.gl/types';\nimport {EDITOR_LAYER_ID, EDITOR_MODES} from '@kepler.gl/constants';\n\n/**\n * Returns true if drawing is active.\n * @param editorMenuActive Indicates whether the editor side menu is active.\n * @param mode Current editing mode.\n * @returs Returns true if drawing is active.\n */\nexport function isDrawingActive(editorMenuActive: boolean, mode: string): boolean {\n  return (\n    editorMenuActive && (mode === EDITOR_MODES.DRAW_POLYGON || mode === EDITOR_MODES.DRAW_RECTANGLE)\n  );\n}\n\n/**\n * Handles click event for Editor layer.\n * @param info Information about clicked object.\n * @param event Event object.\n * @param params\n * @param params.editorMenuActive\n * @param params.editor\n * @param params.onLayerClick\n * @param params.setSelectedFeature\n * @param params.mapIndex\n * @returns Returns true is the click is handled.\n */\n// eslint-disable-next-line complexity\nexport function onClick(\n  info: PickInfo<any>,\n  event: any,\n  {\n    editorMenuActive,\n    editor,\n    setSelectedFeature,\n    onLayerClick,\n    mapIndex = 0\n  }: {\n    editorMenuActive: boolean;\n    editor: Editor;\n    onLayerClick: (data, clickEvent) => any;\n    setSelectedFeature: (\n      feature: Feature | null,\n      selectionContext?: FeatureSelectionContext\n    ) => any;\n    mapIndex?: number;\n  }\n): boolean {\n  const drawingActive = isDrawingActive(editorMenuActive, editor.mode);\n\n  if (info?.layer?.id === EDITOR_LAYER_ID && info?.object) {\n    const objectType = info.object.geometry?.type;\n\n    if (drawingActive) {\n      if (editor.selectedFeature) {\n        setSelectedFeature(null);\n      }\n    } else if (objectType?.endsWith('Polygon') || objectType?.endsWith('Point')) {\n      let clickContext;\n      if (event.rightButton && Array.isArray(event.srcEvent?.point)) {\n        const {point} = event.srcEvent;\n        clickContext = {\n          mapIndex,\n          rightClick: true,\n          position: {\n            x: point[0],\n            y: point[1]\n          }\n        };\n      }\n\n      if (objectType?.endsWith('Polygon')) {\n        setSelectedFeature(info.object, clickContext);\n      } else {\n        // don't select points\n        setSelectedFeature(editor.selectedFeature, clickContext);\n      }\n    }\n    // hide tooltips from regular data layers\n    onLayerClick(null, event);\n  } else if (drawingActive) {\n    // prevent interaction with other layers\n    onLayerClick(null, event);\n  } else {\n    if (editor.selectedFeature) {\n      if (event.rightButton) {\n        return true;\n      }\n\n      // click outside removes selection\n      setSelectedFeature(null);\n    }\n\n    return false;\n  }\n  return true;\n}\n\n/**\n * Handles hover event for Editor layer.\n * @param info Information about hovered object.\n * @param params\n * @param params.editorMenuActive\n * @param params.editor\n * @param params.hoverInfo\n * @returns Returns true is hover is handled.\n */\nexport function onHover(\n  info: PickInfo<any>,\n  {hoverInfo, editor, editorMenuActive}: {editorMenuActive: boolean; editor: Editor; hoverInfo}\n): boolean {\n  if (isDrawingActive(editorMenuActive, editor.mode)) {\n    return true;\n  }\n\n  return info?.layer?.id === EDITOR_LAYER_ID && hoverInfo?.layer?.id === EDITOR_LAYER_ID;\n}\n\n/**\n * For small tooltips with short messages, e.g. \"Drag to move the point\",\n * use the values below to decide when to position a tooltip to the left\n * of the cursor or above the cursor, depending on proximity to the edge of\n * the viewport to prevent the tooltip from being cut off.\n */\nconst MIN_DISTANCE_TO_LEFT_EDGE = 200;\nconst MIN_DISTANCE_TO_BOTTOM_EDGE = 100;\n\n/**\n * Returns tooltip based on interactions with Editor layer.\n * @param info Information about hovered object.\n * @param params\n * @param params.editorMenuActive\n * @param params.editor\n * @param params.theme\n * @raturns Returns a tooltip object compatible with Deck.getTooltip()\n */\n// eslint-disable-next-line complexity\nexport function getTooltip(\n  // TODO PickInfo type in deck typings doesn't include viewport and pixel\n  info: PickInfo<any> & {viewport: any; pixel: any[]},\n  {editor, theme, editorMenuActive}: {editorMenuActive: boolean; editor: Editor; theme: any}\n): object | null {\n  const {object, layer, viewport = {}, pixel = []} = info;\n  const closeToLeftEdge = viewport?.width - pixel[0] < MIN_DISTANCE_TO_LEFT_EDGE;\n  const closeToBottomEdge = viewport?.height - pixel[1] < MIN_DISTANCE_TO_BOTTOM_EDGE;\n\n  // don't show the tooltip when the menu is visible\n  if (editor.selectionContext?.rightClick) {\n    return null;\n  }\n\n  if (isDrawingActive(editorMenuActive, editor.mode)) {\n    // TODO save interaction state in editor object\n    if (layer?.state?.mode?._clickSequence?.length) {\n      return null;\n    }\n\n    return getTooltipObject('Click to start new feature', theme, {\n      leftOfCursor: closeToLeftEdge,\n      aboveCursor: closeToBottomEdge\n    });\n  }\n\n  if (layer?.id === EDITOR_LAYER_ID) {\n    const {selectedFeature} = editor;\n\n    if (selectedFeature) {\n      if (!object || (object.id && object.id === selectedFeature.id)) {\n        return getTooltipObject('Right click to view options\\nDrag to move the feature', theme, {\n          leftOfCursor: closeToLeftEdge,\n          aboveCursor: closeToBottomEdge\n        });\n      }\n    }\n\n    if (object?.properties?.editHandleType === 'intermediate') {\n      return getTooltipObject('Click to insert a point', theme, {\n        leftOfCursor: closeToLeftEdge,\n        aboveCursor: closeToBottomEdge\n      });\n    }\n\n    if (object?.geometry?.type === 'Point' || object?.properties?.guideType === 'tentative') {\n      return getTooltipObject('Drag to move the point', theme, {\n        leftOfCursor: closeToLeftEdge,\n        aboveCursor: closeToBottomEdge\n      });\n    }\n\n    return getTooltipObject('Click to select the feature\\nRight click to view options', theme, {\n      leftOfCursor: closeToLeftEdge,\n      aboveCursor: closeToBottomEdge\n    });\n  }\n\n  return null;\n}\n\n/**\n * Returns cursor type based on interactions with Editor layer.\n * @param params\n * @param params.editorMenuActive\n * @param params.editor\n * @param params.hoverInfo\n * @returns Returns cursor type.\n */\nexport function getCursor({\n  editorMenuActive,\n  editor,\n  hoverInfo\n}: {\n  editorMenuActive: boolean;\n  editor: Editor;\n  hoverInfo: any;\n}): string | null {\n  if (isDrawingActive(editorMenuActive, editor.mode)) {\n    return 'crosshair';\n  }\n\n  if (hoverInfo?.layer?.id === EDITOR_LAYER_ID && editor.selectedFeature) {\n    return 'move';\n  }\n\n  return null;\n}\n\n/**\n * Returns a tooltip object that can be used as a Deck tooltip.\n * Positioning can be modified if the cursor is close to the bottom or left edge of the viewport.\n * @param text Text to show.\n * @param theme Current theme.\n * @param position.leftOfCursor Tooltip should display to the left of the cursor.\n * @param position.aboveCursor Tooltip should display above cursor.\n */\nfunction getTooltipObject(\n  text: string,\n  theme: any,\n  position: {leftOfCursor: boolean; aboveCursor: boolean}\n): {text: string; style: object} {\n  const {leftOfCursor, aboveCursor} = position;\n  const marginTop = aboveCursor ? '-70px' : '15px';\n  const marginLeft = leftOfCursor ? '-200px' : '15px';\n  return {\n    text,\n    style: {\n      'margin-top': marginTop,\n      'margin-left': marginLeft,\n      'font-family': theme.fontFamily,\n      'font-size': theme.tooltipFontSize,\n      'font-weight': 400,\n      padding: '7px 18px',\n      'box-shadow': theme.tooltipBoxShadow,\n      'background-color': theme.tooltipBg,\n      color: theme.tooltipColor,\n      'border-radius': theme.primaryBtnRadius\n    }\n  };\n}\n"
  },
  {
    "path": "src/layers/src/editor-layer/editor-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {EditableGeoJsonLayer} from '@nebula.gl/layers';\nimport {Layer as DeckLayer, LayerProps as DeckLayerProps} from '@deck.gl/core/typed';\nimport {\n  DrawPolygonMode,\n  TranslateMode,\n  CompositeMode,\n  DrawRectangleMode\n} from '@nebula.gl/edit-modes';\nimport {PathStyleExtension} from '@deck.gl/extensions';\n\nimport {EDITOR_LAYER_ID, EDITOR_MODES, EDITOR_LAYER_PICKING_RADIUS} from '@kepler.gl/constants';\nimport {Viewport, Editor, Feature, FeatureSelectionContext} from '@kepler.gl/types';\nimport {generateHashId} from '@kepler.gl/common-utils';\n\nimport {EDIT_TYPES} from './constants';\nimport {LINE_STYLE, FEATURE_STYLE, EDIT_HANDLE_STYLE} from './feature-styles';\nimport {ModifyModeExtended} from './modify-mode-extended';\nimport {isDrawingActive} from './editor-layer-utils';\n\nconst DEFAULT_COMPOSITE_MODE = new CompositeMode([new TranslateMode(), new ModifyModeExtended()]);\n\nexport type GetEditorLayerProps = {\n  editorMenuActive: boolean;\n  editor: Editor;\n  onSetFeatures: (features: Feature[]) => any;\n  setSelectedFeature: (feature: Feature | null, selectionContext?: FeatureSelectionContext) => any;\n  viewport: Viewport;\n  featureCollection: {\n    type: string;\n    features: Feature[];\n  };\n  selectedFeatureIndexes: number[];\n};\n\n/**\n * Returns editable layer to edit polygon filters.\n * @param params\n * @param params.editorMenuActive Indicates whether the editor side menu is active.\n * @param params.editor\n * @param params.onSetFeatures A callback to set features.\n * @param params.setSelectedFeature A callback to set selected feature and selection context.\n * @param params.viewport Current viewport.\n * @param params.featureCollection Feature collection with an array of features\n * @param params.selectedFeatureIndexes An array with index of currently selected feature.\n */\nexport function getEditorLayer({\n  editorMenuActive,\n  editor,\n  onSetFeatures,\n  setSelectedFeature,\n  featureCollection,\n  selectedFeatureIndexes,\n  viewport\n}: GetEditorLayerProps): DeckLayer<DeckLayerProps> {\n  const {mode: editorMode} = editor;\n\n  let mode = DEFAULT_COMPOSITE_MODE;\n  if (editorMenuActive) {\n    // @ts-ignore\n    if (editorMode === EDITOR_MODES.DRAW_POLYGON) mode = DrawPolygonMode;\n    // @ts-ignore\n    else if (editorMode === EDITOR_MODES.DRAW_RECTANGLE) mode = DrawRectangleMode;\n  }\n\n  // @ts-ignore\n  return new EditableGeoJsonLayer({\n    id: EDITOR_LAYER_ID,\n    mode,\n    // @ts-ignore\n    data: featureCollection,\n    selectedFeatureIndexes,\n    visible: editor.visible,\n    pickable: true,\n    pickingRadius: EDITOR_LAYER_PICKING_RADIUS,\n    modeConfig: {\n      viewport,\n      screenSpace: true,\n      lockRectangles: true\n    },\n\n    pickingLineWidthExtraPixels: 5,\n\n    // Only show fill when polygons are selected,\n    // there is no way atm to enable fill for only one feature\n    filled: selectedFeatureIndexes.length > 0,\n\n    onEdit: ({updatedData, editType}) => {\n      switch (editType) {\n        case EDIT_TYPES.ADD_FEATURE: {\n          const {features: _features} = updatedData;\n          if (_features.length) {\n            const lastFeature = _features[_features.length - 1];\n            lastFeature.properties.isClosed = true;\n            lastFeature.id = generateHashId(6);\n            onSetFeatures(updatedData.features);\n            setSelectedFeature(lastFeature);\n          }\n          break;\n        }\n        case EDIT_TYPES.ADD_POSITION:\n        case EDIT_TYPES.MOVE_POSITION:\n        case EDIT_TYPES.TRANSLATING:\n          onSetFeatures(updatedData.features);\n          break;\n        default:\n          break;\n      }\n    },\n\n    // prevent self-highlights with tentative features\n    autoHighlight: !isDrawingActive(editorMenuActive, editorMode),\n    // @ts-ignore\n    highlightColor: info => {\n      // Note: lines are reported as parent polygon\n      const {object} = info;\n      if (object) {\n        if (object.id === editor.selectedFeature?.id) {\n          return FEATURE_STYLE.highlightMultiplierNone;\n        }\n\n        const type = object.properties.editHandleType;\n        if (type === 'intermediate') return EDIT_HANDLE_STYLE.highlightMultiplierNone;\n        else if (type === 'existing') return EDIT_HANDLE_STYLE.highlightMultiplier;\n      }\n\n      // Note: highlight color affects even transparent filled polygons\n      return selectedFeatureIndexes.length\n        ? FEATURE_STYLE.highlightMultiplier\n        : LINE_STYLE.highlightMultiplier;\n    },\n\n    extensions: [new PathStyleExtension({dash: true})],\n    dashGapPickable: true,\n    getDashArray: feature => {\n      if (feature?.properties?.guideType === 'tentative') {\n        return LINE_STYLE.dashArray;\n      }\n\n      if (feature?.id === editor.selectedFeature?.id) return LINE_STYLE.solidArray;\n\n      return LINE_STYLE.dashArray;\n    },\n\n    getLineColor: LINE_STYLE.getColor,\n    getFillColor: FEATURE_STYLE.getColor,\n\n    getRadius: EDIT_HANDLE_STYLE.getRadius,\n    // @ts-ignore\n    getLineWidth: LINE_STYLE.getWidth,\n\n    getEditHandlePointRadius: EDIT_HANDLE_STYLE.getRadius,\n    getEditHandlePointColor: EDIT_HANDLE_STYLE.getFillColor,\n    getEditHandlePointOutlineColor: EDIT_HANDLE_STYLE.getOutlineColor,\n\n    getTentativeLineColor: LINE_STYLE.getTentativeLineColor,\n    // @ts-ignore\n    getTentativeLineWidth: LINE_STYLE.getTentativeLineWidth,\n    getTentativeFillColor: LINE_STYLE.getTentativeFillColor,\n\n    parameters: {}\n  });\n}\n"
  },
  {
    "path": "src/layers/src/editor-layer/feature-styles.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {RGBAColor} from '@deck.gl/core';\nimport {Feature} from '@kepler.gl/types';\n\nimport {COLORS} from './constants';\n\nconst POINT_RADIUS = 5;\nconst STROKE_WIDTH_SELECTED = 2.5;\nconst STROKE_WIDTH_NOT_SELECTED = 2;\n\nconst STROKE_SOLID_ARRAY = [0, 0];\nconst STROKE_DASH_ARRAY = [4, 3];\n\nconst ALPHA_0 = 0x00;\nconst ALPHA_005 = 0x0d;\nconst ALPHA_01 = 0x1a;\nconst ALPHA_05 = 0x80;\nconst ALPHA_1 = 0xff;\n\nconst PRIMARY_COLOR_TRANSPARENT: RGBAColor = [...COLORS.PRIMARY, ALPHA_01];\nconst PRIMARY_COLOR: RGBAColor = [...COLORS.PRIMARY, ALPHA_1];\nconst SECONDARY_COLOR_TRANSPARENT: RGBAColor = [...COLORS.SECONDARY, ALPHA_0];\nconst TENTATIVE_FEATURE_COLOR: RGBAColor = [...COLORS.SECONDARY, ALPHA_1];\nconst TENTATIVE_FEATURE_COLOR_TRANSPARENT: RGBAColor = [...COLORS.SECONDARY, ALPHA_005];\n\nexport const EDIT_HANDLE_STYLE = {\n  getRadius: POINT_RADIUS,\n  getFillColor: SECONDARY_COLOR_TRANSPARENT,\n  getOutlineColor: handle =>\n    handle?.properties?.featureIndex < 0 ? TENTATIVE_FEATURE_COLOR : PRIMARY_COLOR,\n  highlightMultiplier: [...COLORS.HIGHLIGHT, ALPHA_05],\n  highlightMultiplierNone: SECONDARY_COLOR_TRANSPARENT\n};\n\nexport const FEATURE_STYLE = {\n  getColor: (feature: Feature, isSelected: boolean) =>\n    isSelected ? PRIMARY_COLOR_TRANSPARENT : SECONDARY_COLOR_TRANSPARENT,\n  highlightMultiplier: [...COLORS.HIGHLIGHT, ALPHA_01],\n  highlightMultiplierNone: SECONDARY_COLOR_TRANSPARENT\n};\n\nexport const LINE_STYLE = {\n  getColor: (_feature: Feature, isSelected: boolean): RGBAColor =>\n    isSelected ? PRIMARY_COLOR : PRIMARY_COLOR,\n  getWidth: (_feature: Feature, isSelected: boolean): number =>\n    isSelected ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_NOT_SELECTED,\n  getTentativeLineColor: (): RGBAColor => TENTATIVE_FEATURE_COLOR,\n  getTentativeLineWidth: (): number => STROKE_WIDTH_NOT_SELECTED,\n  getTentativeFillColor: TENTATIVE_FEATURE_COLOR_TRANSPARENT,\n  dashArray: STROKE_DASH_ARRAY,\n  solidArray: STROKE_SOLID_ARRAY,\n  highlightMultiplier: [...COLORS.HIGHLIGHT, ALPHA_1]\n};\n"
  },
  {
    "path": "src/layers/src/editor-layer/modify-mode-extended.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  ModifyMode,\n  FeatureOf,\n  LineString,\n  Point,\n  Viewport as NebulaViewport\n} from '@nebula.gl/edit-modes';\nimport {Viewport} from '@deck.gl/core';\n\nimport {EDITOR_LAYER_PICKING_RADIUS} from '@kepler.gl/constants';\n\nconst RIGHT_BUTTON = 2;\n\n/**\n * Show helper only when the point is close enough to the line.\n */\nexport class ModifyModeExtended extends ModifyMode {\n  // @ts-expect-error expect to return no point when object is too far\n  getNearestPoint(\n    line: FeatureOf<LineString>,\n    inPoint: FeatureOf<Point>,\n    viewport: Viewport | null | undefined\n  ) {\n    const p = super.getNearestPoint(line, inPoint, viewport as NebulaViewport | null | undefined);\n    if (p && viewport) {\n      const p1 = viewport.project(p.geometry.coordinates);\n      const p2 = viewport.project(inPoint.geometry.coordinates);\n      const d = Math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2);\n      if (d > EDITOR_LAYER_PICKING_RADIUS) {\n        return;\n      }\n    }\n    return p;\n  }\n\n  handleClick(event, props) {\n    // prevent insertion of points for right click\n    if (event?.sourceEvent?.button === RIGHT_BUTTON) {\n      return;\n    }\n    super.handleClick(event, props);\n  }\n}\n"
  },
  {
    "path": "src/layers/src/example-table.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\n\nexport const StyledTable = styled.table`\n  width: 100%;\n  border-spacing: 0;\n\n  thead {\n    tr th {\n      background: ${props => props.theme.panelBackgroundLT};\n      color: ${props => props.theme.titleColorLT};\n      padding: 18px 12px;\n      text-align: start;\n    }\n  }\n\n  tbody {\n    tr td {\n      border-bottom: ${props => props.theme.panelBorderLT};\n      padding: 12px;\n    }\n  }\n`;\n\nexport default StyledTable;\n"
  },
  {
    "path": "src/layers/src/geojson-layer/geojson-info-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport Markdown from 'markdown-to-jsx';\nimport {useIntl} from 'react-intl';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport Table from '../example-table';\n\nconst InfoModal = styled.div`\n  font-size: 13px;\n  color: ${props => props.theme.titleColorLT};\n\n  pre {\n    padding: 12px;\n    background-color: #f8f8f9;\n  }\n`;\n\nconst StyledTitle = styled.div`\n  font-size: 20px;\n  letter-spacing: 1.25px;\n  margin: 18px 0 14px 0;\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst StyledCode = styled.code`\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst exampleTableHeader = ['id', 'latitude', 'longitude', 'sort by'];\nconst exampleTabbleRows = [\n  ['A', '40.81773', '-74.20986', '0'],\n  ['A', '40.81765', '-74.20987', '1'],\n  ['A', '40.81746', '-74.20998', '2'],\n  ['B', '40.64375', '-74.33242', '0'],\n  ['B', '40.64353', '-74.20987', '1'],\n  ['B', '40.64222', '-74.33001', '2']\n];\n\nconst ExampleTable = () => (\n  <Table className=\"geojson-example-table\">\n    <thead>\n      <tr>\n        {exampleTableHeader.map(v => (\n          <th key={v}>{v}</th>\n        ))}\n      </tr>\n    </thead>\n    <tbody>\n      {exampleTabbleRows.map((row, i) => (\n        <tr key={i}>\n          {row.map((v, j) => (\n            <td key={j}>\n              <StyledCode>{v}</StyledCode>\n            </td>\n          ))}\n        </tr>\n      ))}\n    </tbody>\n  </Table>\n);\n\nconst GeojsonInfoModalFactory = columnMode => {\n  const GeojsonInfoModal = () => {\n    const intl = useIntl();\n    return (\n      <InfoModal className=\"geojson-info-modal\">\n        <div className=\"geojson-info-modal__description\">\n          <Markdown>\n            {intl.formatMessage({\n              id:\n                columnMode === 'geojson'\n                  ? 'modal.polygonInfo.description'\n                  : 'modal.polygonInfo.descriptionTable'\n            })}\n          </Markdown>\n        </div>\n        {columnMode === 'table' ? (\n          <div className=\"geojson-info-modal__example\">\n            <StyledTitle>\n              <FormattedMessage id=\"modal.polygonInfo.exampleTable\" />\n            </StyledTitle>\n            <ExampleTable />\n          </div>\n        ) : null}\n      </InfoModal>\n    );\n  };\n  return GeojsonInfoModal;\n};\n\nexport default GeojsonInfoModalFactory;\n"
  },
  {
    "path": "src/layers/src/geojson-layer/geojson-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class GeojsonLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: null,\n    size: 'tiny',\n    predefinedClassName: 'geojson-layer-icon'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon\n          className=\"cr1\"\n          points=\"25.04 23.08 9.72 31.79 8.19 43.2 19.57 53.83 28.79 53.83 35.6 46.57 39.45 30.08 25.04 23.08\"\n        />\n        <polygon\n          className=\"cr2\"\n          points=\"52.8 26.3 41.74 30.32 37.9 46.75 45.26 53.83 51.45 53.83 55.07 43.51 52.8 26.3\"\n          style={{opacity: 0.8}}\n        />\n        <polygon\n          className=\"cr3\"\n          points=\"36.69 48.75 31.93 53.83 41.96 53.83 36.69 48.75\"\n          style={{opacity: 0.4}}\n        />\n        <polygon\n          className=\"cr3\"\n          points=\"25.95 20.98 40.84 28.22 52.57 24.06 50.89 11.5 23.24 11.5 25.95 20.98\"\n          style={{opacity: 0.4}}\n        />\n        <polygon\n          className=\"cr4\"\n          points=\"20.79 11.9 11.73 15.72 10.08 28.96 23.64 21.25 20.79 11.9\"\n          style={{opacity: 0.8}}\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/geojson-layer/geojson-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport {point as turfPoint} from '@turf/helpers';\nimport booleanWithin from '@turf/boolean-within';\nimport {Feature, Polygon} from 'geojson';\nimport uniq from 'lodash/uniq';\nimport {DATA_TYPES} from 'type-analyzer';\nimport Layer, {\n  colorMaker,\n  defaultGetFieldValue,\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  LayerColorConfig,\n  LayerHeightConfig,\n  LayerRadiusConfig,\n  LayerSizeConfig,\n  LayerStrokeColorConfig\n} from '../base-layer';\nimport {GeoJsonLayer as DeckGLGeoJsonLayer} from '@deck.gl/layers';\nimport {\n  getGeojsonLayerMeta,\n  GeojsonDataMaps,\n  DeckGlGeoTypes,\n  detectTableColumns,\n  COLUMN_MODE_GEOJSON,\n  applyFiltersToTableColumns,\n  fieldIsGeoArrow\n} from './geojson-utils';\nimport {\n  getGeojsonLayerMetaFromArrow,\n  isLayerHoveredFromArrow,\n  getHoveredObjectFromArrow\n} from '../layer-utils';\nimport GeojsonLayerIcon from './geojson-layer-icon';\nimport {\n  GEOJSON_FIELDS,\n  HIGHLIGH_COLOR_3D,\n  CHANNEL_SCALES,\n  LAYER_VIS_CONFIGS,\n  DEFAULT_COLOR_UI\n} from '@kepler.gl/constants';\nimport {\n  ColorRange,\n  VisConfigNumber,\n  VisConfigColorSelect,\n  VisConfigColorRange,\n  VisConfigRange,\n  VisConfigBoolean,\n  Merge,\n  RGBColor,\n  ProtoDatasetField,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils';\nimport {FilterArrowExtension} from '@kepler.gl/deckgl-layers';\nimport GeojsonInfoModalFactory from './geojson-info-modal';\n\nconst SUPPORTED_ANALYZER_TYPES = {\n  [DATA_TYPES.GEOMETRY]: true,\n  [DATA_TYPES.GEOMETRY_FROM_STRING]: true,\n  [DATA_TYPES.PAIR_GEOMETRY_FROM_STRING]: true\n};\n\nexport const geojsonVisConfigs: {\n  opacity: 'opacity';\n  strokeOpacity: VisConfigNumber;\n  thickness: VisConfigNumber;\n  strokeColor: 'strokeColor';\n  colorRange: 'colorRange';\n  strokeColorRange: 'strokeColorRange';\n  radius: 'radius';\n\n  sizeRange: 'strokeWidthRange';\n  radiusRange: 'radiusRange';\n  heightRange: 'elevationRange';\n  elevationScale: 'elevationScale';\n  stroked: 'stroked';\n  filled: 'filled';\n  enable3d: 'enable3d';\n  wireframe: 'wireframe';\n  fixedHeight: 'fixedHeight';\n} = {\n  opacity: 'opacity',\n  strokeOpacity: {\n    ...LAYER_VIS_CONFIGS.opacity,\n    property: 'strokeOpacity'\n  },\n  thickness: {\n    ...LAYER_VIS_CONFIGS.thickness,\n    defaultValue: 0.5\n  },\n  strokeColor: 'strokeColor',\n  colorRange: 'colorRange',\n  strokeColorRange: 'strokeColorRange',\n  radius: 'radius',\n\n  sizeRange: 'strokeWidthRange',\n  radiusRange: 'radiusRange',\n  heightRange: 'elevationRange',\n  elevationScale: 'elevationScale',\n  stroked: 'stroked',\n  filled: 'filled',\n  enable3d: 'enable3d',\n  wireframe: 'wireframe',\n  fixedHeight: 'fixedHeight'\n};\n\nexport type GeoJsonVisConfigSettings = {\n  opacity: VisConfigNumber;\n  strokeOpacity: VisConfigNumber;\n  thickness: VisConfigNumber;\n  strokeColor: VisConfigColorSelect;\n  colorRange: VisConfigColorRange;\n  strokeColorRange: VisConfigColorRange;\n  radius: VisConfigNumber;\n\n  sizeRange: VisConfigRange;\n  radiusRange: VisConfigRange;\n  heightRange: VisConfigRange;\n  elevationScale: VisConfigNumber;\n  fixedHeight: VisConfigBoolean;\n  stroked: VisConfigBoolean;\n  filled: VisConfigBoolean;\n  enable3d: VisConfigBoolean;\n  wireframe: VisConfigBoolean;\n};\n\nexport type GeoJsonLayerColumnsConfig = {\n  geojson: LayerColumn;\n};\n\nexport type GeoJsonLayerVisConfig = {\n  opacity: number;\n  strokeOpacity: number;\n  thickness: number;\n  strokeColor: RGBColor;\n  colorRange: ColorRange;\n  strokeColorRange: ColorRange;\n  radius: number;\n\n  sizeRange: [number, number];\n  radiusRange: [number, number];\n  heightRange: [number, number];\n  elevationScale: number;\n  stroked: boolean;\n  filled: boolean;\n  enable3d: boolean;\n  wireframe: boolean;\n  fixedHeight: boolean;\n};\n\ntype GeoJsonLayerVisualChannelConfig = LayerColorConfig &\n  LayerStrokeColorConfig &\n  LayerSizeConfig &\n  LayerHeightConfig &\n  LayerRadiusConfig;\nexport type GeoJsonLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: GeoJsonLayerColumnsConfig; visConfig: GeoJsonLayerVisConfig}\n> &\n  GeoJsonLayerVisualChannelConfig;\n\nexport type GeoJsonLayerMeta = {\n  featureTypes?: DeckGlGeoTypes;\n  fixedRadius?: boolean;\n};\n\nexport const geoJsonRequiredColumns: ['geojson'] = ['geojson'];\n\ntype ObjectInfo = {\n  index: number;\n  object?: Feature | undefined;\n  picked: boolean;\n  layer: Layer;\n  radius?: number;\n  id?: string;\n};\n\nexport const featureAccessor =\n  ({geojson}: GeoJsonLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    dc.valueAt(d.index, geojson.fieldIdx);\n\nconst geoColumnAccessor =\n  ({geojson}: GeoJsonLayerColumnsConfig) =>\n  (dc: DataContainerInterface): arrow.Vector | null =>\n    dc.getColumn?.(geojson.fieldIdx) as arrow.Vector;\n\nconst getTableModeValueAccessor = feature => {\n  // Called from gpu-filter-utils.getFilterValueAccessor()\n  return field => feature.properties.values.map(v => field.valueAccessor(v));\n};\n\nconst getTableModeFieldValue = (field, data) => {\n  let rv;\n  if (typeof data === 'function') {\n    rv = data(field);\n  } else {\n    rv = defaultGetFieldValue(field, data);\n  }\n  return rv;\n};\n\nconst geoFieldAccessor =\n  ({geojson}: GeoJsonLayerColumnsConfig) =>\n  (dc: DataContainerInterface): ProtoDatasetField | null =>\n    dc.getField ? dc.getField(geojson.fieldIdx) : null;\n\n// access feature properties from geojson sub layer\nexport const defaultElevation = 500;\nexport const defaultLineWidth = 1;\nexport const defaultRadius = 1;\n\n// don't use strokes by default for datasets with large number of polygons\nconst DEFAULT_POLYGON_STROKE_LIMIT = 100000;\n\nexport const COLUMN_MODE_TABLE = 'table';\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_GEOJSON,\n    label: 'GeoJSON',\n    requiredColumns: ['geojson']\n  },\n  {\n    key: COLUMN_MODE_TABLE,\n    label: 'Table columns',\n    requiredColumns: ['id', 'lat', 'lng'],\n    optionalColumns: ['altitude', 'sortBy']\n  }\n];\nconst DEFAULT_COLUMN_MODE = COLUMN_MODE_GEOJSON;\n\nexport default class GeoJsonLayer extends Layer {\n  declare config: GeoJsonLayerConfig;\n  declare visConfigSettings: GeoJsonVisConfigSettings;\n  declare meta: GeoJsonLayerMeta;\n  declare geoArrowMode: boolean;\n\n  dataToFeature: GeojsonDataMaps = [];\n  dataContainer: DataContainerInterface | null = null;\n  filteredIndex: Uint8ClampedArray | null = null;\n  filteredIndexTrigger: number[] | null = null;\n  centroids: Array<number[] | null> = [];\n\n  _layerInfoModal: {\n    [COLUMN_MODE_TABLE]: () => React.JSX.Element;\n    [COLUMN_MODE_GEOJSON]: () => React.JSX.Element;\n  };\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(geojsonVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      featureAccessor(this.config.columns)(dataContainer);\n    this._layerInfoModal = {\n      [COLUMN_MODE_TABLE]: GeojsonInfoModalFactory(COLUMN_MODE_TABLE),\n      [COLUMN_MODE_GEOJSON]: GeojsonInfoModalFactory(COLUMN_MODE_GEOJSON)\n    };\n  }\n\n  get type() {\n    return GeoJsonLayer.type;\n  }\n  static get type(): 'geojson' {\n    return 'geojson';\n  }\n\n  get name(): 'Polygon' {\n    return 'Polygon';\n  }\n\n  get layerIcon() {\n    return GeojsonLayerIcon;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  get layerInfoModal() {\n    return {\n      [COLUMN_MODE_GEOJSON]: {\n        id: 'iconInfo',\n        template: this._layerInfoModal[COLUMN_MODE_GEOJSON],\n        modalProps: {\n          title: 'modal.polygonInfo.title'\n        }\n      },\n      [COLUMN_MODE_TABLE]: {\n        id: 'iconInfo',\n        template: this._layerInfoModal[COLUMN_MODE_TABLE],\n        modalProps: {\n          title: 'modal.polygonInfo.titleTable'\n        }\n      }\n    };\n  }\n\n  accessVSFieldValue() {\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      return defaultGetFieldValue;\n    }\n    return getTableModeFieldValue;\n  }\n\n  get visualChannels() {\n    const visualChannels = super.visualChannels;\n    return {\n      color: {\n        ...visualChannels.color,\n        accessor: 'getFillColor',\n        condition: config => config.visConfig.filled,\n        nullValue: visualChannels.color.nullValue,\n        getAttributeValue: config => d => d.properties.fillColor || config.color,\n        // used this to get updateTriggers\n        defaultValue: config => config.color\n      },\n      strokeColor: {\n        property: 'strokeColor',\n        field: 'strokeColorField',\n        scale: 'strokeColorScale',\n        domain: 'strokeColorDomain',\n        range: 'strokeColorRange',\n        key: 'strokeColor',\n        channelScaleType: CHANNEL_SCALES.color,\n        accessor: 'getLineColor',\n        condition: config => config.visConfig.stroked,\n        nullValue: visualChannels.color.nullValue,\n        getAttributeValue: config => d =>\n          d.properties.lineColor || config.visConfig.strokeColor || config.color,\n        // used this to get updateTriggers\n        defaultValue: config => config.visConfig.strokeColor || config.color\n      },\n      size: {\n        ...visualChannels.size,\n        property: 'stroke',\n        accessor: 'getLineWidth',\n        condition: config => config.visConfig.stroked,\n        nullValue: 0,\n        getAttributeValue: () => d => d.properties.lineWidth || defaultLineWidth\n      },\n      height: {\n        property: 'height',\n        field: 'heightField',\n        scale: 'heightScale',\n        domain: 'heightDomain',\n        range: 'heightRange',\n        key: 'height',\n        fixed: 'fixedHeight',\n        channelScaleType: CHANNEL_SCALES.size,\n        accessor: 'getElevation',\n        condition: config => config.visConfig.enable3d,\n        nullValue: 0,\n        getAttributeValue: () => d => d.properties.elevation || defaultElevation\n      },\n      radius: {\n        property: 'radius',\n        field: 'radiusField',\n        scale: 'radiusScale',\n        domain: 'radiusDomain',\n        range: 'radiusRange',\n        key: 'radius',\n        channelScaleType: CHANNEL_SCALES.radius,\n        accessor: 'getPointRadius',\n        nullValue: 0,\n        getAttributeValue: () => d => d.properties.radius || defaultRadius\n      }\n    };\n  }\n\n  static findDefaultLayerProps({label, fields = []}: KeplerTable) {\n    const geojsonColumns = fields\n      .filter(\n        f =>\n          (f.type === 'geojson' || f.type === 'geoarrow') &&\n          f.analyzerType &&\n          SUPPORTED_ANALYZER_TYPES[f.analyzerType]\n      )\n      .map(f => f.name);\n\n    const defaultColumns = {\n      geojson: uniq([...GEOJSON_FIELDS.geojson, ...geojsonColumns])\n    };\n\n    const foundColumns = this.findDefaultColumnField(defaultColumns, fields);\n    if (!foundColumns || !foundColumns.length) {\n      return {props: []};\n    }\n\n    return {\n      props: foundColumns.map(columns => ({\n        label: (typeof label === 'string' && label.replace(/\\.[^/.]+$/, '')) || this.type,\n        columns,\n        isVisible: true\n      }))\n    };\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial) {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {});\n    return {\n      ...defaultLayerConfig,\n\n      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,\n      // add height visual channel\n      heightField: null,\n      heightDomain: [0, 1],\n      heightScale: 'linear',\n\n      // add radius visual channel\n      radiusField: null,\n      radiusDomain: [0, 1],\n      radiusScale: 'linear',\n\n      // add stroke color visual channel\n      strokeColorField: null,\n      strokeColorDomain: [0, 1],\n      strokeColorScale: 'quantile',\n      colorUI: {\n        ...defaultLayerConfig.colorUI,\n        strokeColorRange: DEFAULT_COLOR_UI\n      }\n    };\n  }\n\n  getHoverData(object, dataContainer) {\n    // index of dataContainer is saved to feature.properties\n    // for arrow format, `object` is the index of the row returned from deck\n    const index = this.geoArrowMode ? object : object?.properties?.index;\n    if (index >= 0) {\n      return dataContainer.row(index);\n    }\n    return null;\n  }\n\n  getFilteredItemCount() {\n    // return -polygons-fill or -polygons-stroke\n    // + -linestrings\n    // + -points-circle\n    if (Object.keys(this.filteredItemCount).length) {\n      const polygonCount =\n        this.filteredItemCount[`${this.id}-polygons-fill`] ||\n        this.filteredItemCount[`${this.id}-polygons-stroke`] ||\n        0;\n      const linestringCount = this.filteredItemCount[`${this.id}-linestrings`] || 0;\n      const pointCount = this.filteredItemCount[`${this.id}-points-circle`] || 0;\n\n      return polygonCount + linestringCount + pointCount;\n    }\n\n    return null;\n  }\n\n  calculateDataAttribute(dataset: KeplerTable) {\n    this.geoArrowMode = fieldIsGeoArrow(\n      geoFieldAccessor(this.config.columns)(dataset.dataContainer)\n    );\n\n    const {dataContainer, filteredIndex} = dataset;\n    if (this.geoArrowMode) {\n      // TODO add columnMode logic here for ArrowDataContainer?\n      // filter geojson/arrow table by values and make a partial copy of the raw table are expensive\n      // so we will use filteredIndex to create an attribute e.g. filteredIndex [0|1] for GPU filtering\n      // in deck.gl layer, see: FilterArrowExtension in @kepler.gl/deckgl-layers\n      if (!this.filteredIndex || this.filteredIndex.length !== dataContainer.numRows()) {\n        // for incremental data loading, we need to update filteredIndex\n        this.filteredIndex = new Uint8ClampedArray(dataContainer.numRows());\n        this.filteredIndex.fill(1);\n      }\n\n      // check if filteredIndex is a range from 0 to numRows if it is, we don't need to update it\n      const isRange = filteredIndex && filteredIndex.length === dataContainer.numRows();\n      if (!isRange || this.filteredIndexTrigger !== null) {\n        this.filteredIndex.fill(0);\n        for (let i = 0; i < filteredIndex.length; ++i) {\n          this.filteredIndex[filteredIndex[i]] = 1;\n        }\n        this.filteredIndexTrigger = filteredIndex;\n      }\n      // for arrow, always return full dataToFeature instead of a filtered one, so there is no need to update attributes in GPU\n      return this.dataToFeature;\n    }\n\n    // for geojson, this should work as well and more efficient. But we need to update some test cases e.g. #GeojsonLayer -> formatLayerData\n    switch (this.config.columnMode) {\n      case COLUMN_MODE_GEOJSON: {\n        return filteredIndex.map(i => this.dataToFeature[i]).filter(d => d);\n      }\n\n      case COLUMN_MODE_TABLE:\n        return applyFiltersToTableColumns(dataset, this.dataToFeature);\n\n      default:\n        return [];\n    }\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const {data} = this.updateData(datasets, oldLayerData);\n\n    let filterValueAccessor;\n    let dataAccessor;\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      filterValueAccessor = (dc, d, fieldIndex) => dc.valueAt(d.properties.index, fieldIndex);\n      // For GEOJSON mode, properties.index is the row index in the data container\n      dataAccessor = () => d => ({index: d.properties.index});\n    } else {\n      filterValueAccessor = getTableModeValueAccessor;\n      // For TABLE mode, properties.index is the feature index (not row index).\n      // Use the first row from properties.values to get field values for color/size.\n      dataAccessor = () => d => d.properties.values[0];\n    }\n\n    const indexAccessor = f => f.properties.index;\n    const accessors = this.getAttributeAccessors({dataAccessor, dataContainer});\n\n    const isFilteredAccessor = d => {\n      return this.filteredIndex ? this.filteredIndex[d.properties.index] : 1;\n    };\n\n    return {\n      data,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(\n        indexAccessor,\n        filterValueAccessor\n      ),\n      getFiltered: isFilteredAccessor,\n      ...accessors\n    };\n  }\n\n  isInPolygon(data: DataContainerInterface, index: number, polygon: Feature<Polygon>): boolean {\n    if (this.centroids.length === 0 || !this.centroids[index]) {\n      return false;\n    }\n    const isReactangleSearchBox = polygon.properties?.shape === 'Rectangle';\n    const point = this.centroids[index];\n    // if no valid centroid, return false\n    if (!point) return false;\n    // quick check if centroid is within the query rectangle\n    if (isReactangleSearchBox && polygon.properties?.bbox) {\n      const [minX, minY, maxX, maxY] = polygon?.properties?.bbox || [];\n      return point[0] >= minX && point[0] <= maxX && point[1] >= minY && point[1] <= maxY;\n    }\n    // use turf.js to check if centroid is within query polygon\n    return booleanWithin(turfPoint(point), polygon);\n  }\n\n  updateLayerMeta(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n\n    this.dataContainer = dataContainer;\n\n    this.geoArrowMode = fieldIsGeoArrow(\n      geoFieldAccessor(this.config.columns)(dataset.dataContainer)\n    );\n\n    if (this.geoArrowMode && dataContainer instanceof ArrowDataContainer) {\n      const geoColumn = geoColumnAccessor(this.config.columns)(dataContainer);\n      const geoField = geoFieldAccessor(this.config.columns)(dataContainer);\n\n      // update the latest batch/chunk of geoarrow data when loading data incrementally\n      if (geoColumn && geoField && this.dataToFeature.length < dataContainer.numChunks()) {\n        // for incrementally loading data, we only load and render the latest batch; otherwise, we will load and render all batches\n        const isIncrementalLoad = dataContainer.numChunks() - this.dataToFeature.length === 1;\n        // TODO: add support for COLUMN_MODE_TABLE in getGeojsonLayerMetaFromArrow\n        const {dataToFeature, bounds, fixedRadius, featureTypes, centroids} =\n          getGeojsonLayerMetaFromArrow({\n            dataContainer,\n            geoColumn,\n            geoField,\n            ...(isIncrementalLoad ? {chunkIndex: this.dataToFeature.length} : null)\n          });\n        if (centroids) this.centroids = this.centroids.concat(centroids);\n        this.updateMeta({bounds, fixedRadius, featureTypes});\n        this.dataToFeature = [...this.dataToFeature, ...dataToFeature];\n      }\n    } else if (this.dataToFeature.length === 0 || this.config.columnMode === COLUMN_MODE_TABLE) {\n      const getFeature = this.getPositionAccessor(dataContainer);\n\n      const {dataToFeature, bounds, fixedRadius, featureTypes, centroids} = getGeojsonLayerMeta({\n        dataContainer,\n        getFeature,\n        config: this.config\n      });\n      if (centroids) this.centroids = centroids;\n      this.dataToFeature = dataToFeature;\n      this.updateMeta({bounds, fixedRadius, featureTypes});\n    }\n  }\n\n  setInitialLayerConfig(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n    if (!dataContainer.numRows()) {\n      return this;\n    }\n\n    // defefaultLayerProps will automatically find geojson column\n    // if not found, we try to set it to id / lat /lng /ts\n    if (!this.config.columns.geojson.value) {\n      // find columns from lat, lng, id, and ts\n      const columnConfig = detectTableColumns(dataset, this.config.columns, 'sortBy');\n      if (columnConfig) {\n        this.updateLayerConfig({\n          ...columnConfig,\n          columnMode: COLUMN_MODE_TABLE\n        });\n      } else {\n        return this;\n      }\n    }\n\n    this.updateLayerMeta(dataset);\n\n    const {featureTypes} = this.meta;\n    // default settings is stroke: true, filled: false\n    if (featureTypes && featureTypes.polygon) {\n      // set both fill and stroke to true\n      return this.updateLayerVisConfig({\n        filled: true,\n        stroked: dataContainer.numRows() < DEFAULT_POLYGON_STROKE_LIMIT,\n        strokeColor: colorMaker.next().value\n      });\n    } else if (featureTypes && featureTypes.point) {\n      // set fill to true if detect point\n      return this.updateLayerVisConfig({filled: true, stroked: false});\n    }\n\n    return this;\n  }\n\n  isLayerHovered(objectInfo: ObjectInfo): boolean {\n    return this.geoArrowMode\n      ? isLayerHoveredFromArrow(objectInfo, this.id)\n      : super.isLayerHovered(objectInfo);\n  }\n\n  hasHoveredObject(objectInfo: ObjectInfo): Feature | null {\n    return this.geoArrowMode\n      ? getHoveredObjectFromArrow(\n          objectInfo,\n          this.dataContainer,\n          this.id,\n          geoColumnAccessor(this.config.columns),\n          geoFieldAccessor(this.config.columns)\n        )\n      : super.hasHoveredObject(objectInfo);\n  }\n\n  getElevationZoomFactor({zoom, zoomOffset = 0}) {\n    return this.config.visConfig.fixedHeight ? 1 : Math.pow(2, Math.max(8 - zoom + zoomOffset, 0));\n  }\n\n  renderLayer(opts) {\n    const {data: dataProps, gpuFilter, objectHovered, mapState, interactionConfig} = opts;\n\n    const {fixedRadius, featureTypes} = this.meta;\n    const radiusScale = this.getRadiusScaleByZoom(mapState, fixedRadius);\n    const zoomFactor = this.getZoomFactor(mapState);\n    const eleZoomFactor = this.getElevationZoomFactor(mapState);\n\n    const {visConfig} = this.config;\n\n    const layerProps = {\n      lineWidthScale: visConfig.thickness * zoomFactor * 8,\n      elevationScale: visConfig.elevationScale * eleZoomFactor,\n      pointRadiusScale: radiusScale,\n      lineMiterLimit: 4\n    };\n\n    const updateTriggers = {\n      ...this.getVisualChannelUpdateTriggers(),\n      getFilterValue: gpuFilter.filterValueUpdateTriggers,\n      getFiltered: this.filteredIndexTrigger\n    };\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const opaOverwrite = {\n      opacity: visConfig.strokeOpacity\n    };\n\n    const pickable = interactionConfig.tooltip.enabled;\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    const {data, ...props} = dataProps;\n\n    // arrow table can have multiple chunks, a deck.gl layer is created for each chunk\n    const deckLayerData = this.geoArrowMode ? data : [data];\n    const deckLayers = deckLayerData.map((d, i) => {\n      return new DeckGLGeoJsonLayer({\n        ...defaultLayerProps,\n        ...layerProps,\n        ...props,\n        data: d,\n        id: deckLayerData.length > 1 ? `${this.id}-${i}` : this.id,\n        pickable,\n        highlightColor: HIGHLIGH_COLOR_3D,\n        autoHighlight: visConfig.enable3d && pickable,\n        stroked: visConfig.stroked,\n        filled: visConfig.filled,\n        extruded: visConfig.enable3d,\n        wireframe: visConfig.wireframe,\n        wrapLongitude: false,\n        lineMiterLimit: 2,\n        capRounded: true,\n        jointRounded: true,\n        updateTriggers,\n        extensions: [...defaultLayerProps.extensions, new FilterArrowExtension()],\n        _subLayerProps: {\n          ...(featureTypes?.polygon ? {'polygons-stroke': opaOverwrite} : {}),\n          ...(featureTypes?.line ? {linestrings: opaOverwrite} : {}),\n          ...(featureTypes?.point\n            ? {\n                points: {\n                  lineOpacity: visConfig.strokeOpacity\n                }\n              }\n            : {})\n        }\n      });\n    });\n\n    return [\n      ...deckLayers,\n      // hover layer\n      ...(hoveredObject && !visConfig.enable3d\n        ? [\n            new DeckGLGeoJsonLayer({\n              ...this.getDefaultHoverLayerProps(),\n              ...layerProps,\n              visible: defaultLayerProps.visible,\n              wrapLongitude: false,\n              data: [hoveredObject],\n              getLineWidth: props.getLineWidth,\n              getPointRadius: props.getPointRadius,\n              getElevation: props.getElevation,\n              getLineColor: this.config.highlightColor,\n              getFillColor: this.config.highlightColor,\n              // always draw outline\n              stroked: true,\n              filled: false\n            })\n          ]\n        : [])\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/geojson-layer/geojson-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Buffer} from 'buffer';\nimport {Feature, Position, BBox} from 'geojson';\nimport normalize from '@mapbox/geojson-normalize';\nimport bbox from '@turf/bbox';\nimport {ascending} from 'd3-array';\nimport center from '@turf/center';\nimport {AllGeoJSON} from '@turf/helpers';\nimport {parseSync} from '@loaders.gl/core';\nimport {WKBLoader, WKTLoader} from '@loaders.gl/wkt';\nimport {binaryToGeometry} from '@loaders.gl/gis';\nimport {BinaryFeatureCollection} from '@loaders.gl/schema';\nimport {DataContainerInterface, getSampleData} from '@kepler.gl/utils';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {LayerColumns, ProtoDatasetField} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\n\nimport {GeojsonLayerMetaProps, assignPointPairToLayerColumn} from '../layer-utils';\nimport {LayerBaseConfig} from '../base-layer';\n\nexport type GetFeature = (d: any) => Feature;\nexport type GeojsonDataMaps = (Feature | BinaryFeatureCollection | null)[];\nexport type GeojsonPointDataMaps = (number[] | number[][] | null)[];\n\nexport const COLUMN_MODE_GEOJSON = 'geojson';\n\n/* eslint-disable */\n// TODO: Re-enable eslint when we upgrade to handle enums and type maps\nexport enum FeatureTypes {\n  Point = 'Point',\n  MultiPoint = 'MultiPoint',\n  LineString = 'LineString',\n  MultiLineString = 'MultiLineString',\n  Polygon = 'Polygon',\n  MultiPolygon = 'MultiPolygon'\n}\n\n// @ts-expect-error return type of getGeojsonFeatureTypes ?\ntype FeatureTypeMap = {\n  [key in FeatureTypes]: boolean;\n};\n\n/* eslint-enable */\n\n/**\n * Returns true if the field has geoarrow extension.\n * @param geoField A field to test.\n * @returns\n */\nexport function fieldIsGeoArrow(geoField?: ProtoDatasetField | null): boolean {\n  return Boolean(geoField?.metadata?.get('ARROW:extension:name'));\n}\n\nexport function parseGeoJsonRawFeature(rawFeature: unknown): Feature | null {\n  const properties = null; // help ensure that properties is present on the returned geojson feature\n  // Support WKB geometry provided as binary (e.g., from Parquet/GeoParquet)\n  if (rawFeature instanceof Uint8Array || rawFeature instanceof ArrayBuffer) {\n    try {\n      const binaryInput =\n        rawFeature instanceof ArrayBuffer\n          ? rawFeature\n          : (rawFeature as Uint8Array).buffer.slice(\n              (rawFeature as Uint8Array).byteOffset,\n              (rawFeature as Uint8Array).byteOffset + (rawFeature as Uint8Array).byteLength\n            );\n      const binaryGeo = parseSync(binaryInput as ArrayBuffer, WKBLoader);\n      // @ts-expect-error loaders.gl binary type to GeoJSON geometry\n      const parsedGeo = binaryToGeometry(binaryGeo);\n      const normalized = normalize(parsedGeo);\n      if (!normalized || !Array.isArray(normalized.features) || !normalized.features.length) {\n        return null;\n      }\n      return {properties, ...normalized.features[0]};\n    } catch (e) {\n      return null;\n    }\n  }\n  if (typeof rawFeature === 'object') {\n    // Support GeoJson feature as object\n    // probably need to normalize it as well\n    const normalized = normalize(rawFeature);\n    if (!normalized || !Array.isArray(normalized.features)) {\n      // fail to normalize GeoJson\n      return null;\n    }\n\n    return {properties, ...normalized.features[0]};\n  } else if (typeof rawFeature === 'string') {\n    const parsedGeometry = parseGeometryFromString(rawFeature);\n    if (!parsedGeometry) return null;\n    // @ts-expect-error verify whether parsedGeometry always contains properties\n    return {properties, ...parsedGeometry};\n  } else if (Array.isArray(rawFeature)) {\n    // Support GeoJson  LineString as an array of points\n    return {\n      type: 'Feature',\n      geometry: {\n        // why do we need to flip it...\n        coordinates: rawFeature.map(pts => [pts[1], pts[0]]),\n        type: 'LineString'\n      },\n      properties\n    };\n  }\n\n  return null;\n}\n\nexport function getGeojsonLayerMeta({\n  dataContainer,\n  getFeature,\n  config\n}: {\n  dataContainer: DataContainerInterface;\n  getFeature: GetFeature;\n  config: LayerBaseConfig;\n}): GeojsonLayerMetaProps {\n  const dataToFeature =\n    config.columnMode === COLUMN_MODE_GEOJSON\n      ? getGeojsonDataMaps(dataContainer, getFeature)\n      : // COLUMN_MODE_TABLE\n        groupColumnsAsGeoJson(dataContainer, config.columns, 'sortBy');\n\n  // get bounds from features\n  const bounds = getGeojsonBounds(dataToFeature);\n  // if any of the feature has properties.radius set to be true\n  const fixedRadius = Boolean(\n    dataToFeature.find(d => d && 'properties' in d && d.properties?.radius)\n  );\n\n  // keep a record of what type of geometry the collection has\n  const featureTypes = getGeojsonFeatureTypes(dataToFeature);\n\n  const meanCenters: Array<number[] | null> = [];\n  for (let i = 0; i < dataToFeature.length; i++) {\n    const feature = dataToFeature[i];\n    if (feature) {\n      try {\n        // TODO: use line interpolate to get center of line for LineString\n        const cent = center(feature as AllGeoJSON);\n        meanCenters.push(cent.geometry.coordinates);\n      } catch (e) {\n        meanCenters.push(null);\n      }\n    }\n  }\n\n  return {\n    dataToFeature,\n    bounds,\n    fixedRadius,\n    featureTypes,\n    centroids: meanCenters\n  };\n}\n\n/**\n * Parse raw data to GeoJson feature\n */\nexport function getGeojsonDataMaps(\n  dataContainer: DataContainerInterface,\n  getFeature: GetFeature\n): GeojsonDataMaps {\n  const acceptableTypes = [\n    'Point',\n    'MultiPoint',\n    'LineString',\n    'MultiLineString',\n    'Polygon',\n    'MultiPolygon',\n    'GeometryCollection'\n  ];\n\n  const dataToFeature: GeojsonDataMaps = [];\n\n  for (let index = 0; index < dataContainer.numRows(); index++) {\n    const feature = parseGeoJsonRawFeature(getFeature({index}));\n\n    if (feature && feature.geometry && acceptableTypes.includes(feature.geometry.type)) {\n      const cleaned = {\n        ...feature,\n        // store index of the data in feature properties\n        properties: {\n          ...feature.properties,\n          index\n        }\n      };\n\n      dataToFeature[index] = cleaned;\n    } else {\n      dataToFeature[index] = null;\n    }\n  }\n\n  return dataToFeature;\n}\n\n/**\n * Parse raw data to GeoJson point feature coordinates\n */\nexport function getGeojsonPointDataMaps(\n  dataContainer: DataContainerInterface,\n  getFeature: GetFeature\n): GeojsonPointDataMaps {\n  const acceptableTypes = ['Point', 'MultiPoint', 'GeometryCollection'];\n\n  const dataToFeature: GeojsonPointDataMaps = [];\n\n  for (let index = 0; index < dataContainer.numRows(); index++) {\n    const feature = parseGeoJsonRawFeature(getFeature(dataContainer.rowAsArray(index)));\n\n    if (feature && feature.geometry && acceptableTypes.includes(feature.geometry.type)) {\n      dataToFeature[index] =\n        feature.geometry.type === 'Point' || feature.geometry.type === 'MultiPoint'\n          ? feature.geometry.coordinates\n          : // @ts-expect-error Property 'geometries' does not exist on type 'LineString'\n            (feature.geometry.geometries || []).reduce((accu, f) => {\n              if (f.type === 'Point') {\n                accu.push(f.coordinates);\n              } else if (f.type === 'MultiPoint') {\n                accu.push(...f.coordinates);\n              }\n\n              return accu;\n            }, []);\n    } else {\n      dataToFeature[index] = null;\n    }\n  }\n\n  return dataToFeature;\n}\n\n/**\n * Parse geojson from string\n * @param {String} geoString\n * @returns {null | Object} geojson object or null if failed\n */\nfunction parseGeometryFromString(geoString: string): Feature | null {\n  let parsedGeo;\n\n  // try parse as geojson string\n  // {\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.83594]]]}\n  try {\n    parsedGeo = JSON.parse(geoString);\n  } catch (e) {\n    // keep trying to parse\n  }\n\n  // try parse as wkt using loaders.gl WKTLoader\n  if (!parsedGeo) {\n    try {\n      parsedGeo = parseSync(geoString, WKTLoader);\n    } catch (e) {\n      return null;\n    }\n  }\n\n  // try parse as wkb using loaders.gl WKBLoader\n  if (!parsedGeo) {\n    try {\n      const buffer = Buffer.from(geoString, 'hex');\n      const binaryGeo = parseSync(buffer, WKBLoader);\n      // @ts-expect-error\n      parsedGeo = binaryToGeometry(binaryGeo);\n    } catch (e) {\n      return null;\n    }\n  }\n\n  if (!parsedGeo) {\n    return null;\n  }\n\n  const normalized = normalize(parsedGeo);\n\n  if (!normalized || !Array.isArray(normalized.features)) {\n    // fail to normalize geojson\n    return null;\n  }\n\n  return normalized.features[0];\n}\n\n/**\n * Get geojson bounds\n */\nexport function getGeojsonBounds(features: GeojsonDataMaps = []): BBox | null {\n  // 70 ms for 10,000 polygons\n  // here we only pick couple\n  const maxCount = 10000;\n  const samples = features.length > maxCount ? getSampleData(features, maxCount) : features;\n\n  const validSingleGeometry = geom => geom.coordinates && geom.coordinates.length;\n  const validGeometryCollection = geom =>\n    geom.geometries && geom.geometries.length && geom.geometries.every(validSingleGeometry);\n  const nonEmpty = samples.filter(\n    d => d && d.geometry && (validSingleGeometry(d.geometry) || validGeometryCollection(d.geometry))\n  );\n\n  try {\n    return bbox({\n      type: 'FeatureCollection',\n      features: nonEmpty\n    });\n  } catch (e) {\n    return null;\n  }\n}\n\nexport const featureToDeckGlGeoType = {\n  Point: 'point',\n  MultiPoint: 'point',\n  LineString: 'line',\n  MultiLineString: 'line',\n  Polygon: 'polygon',\n  MultiPolygon: 'polygon'\n};\n\nexport type DeckGlGeoTypes = {\n  point: boolean;\n  line: boolean;\n  polygon: boolean;\n};\n\n/**\n * Parse geojson from string\n */\nexport function getGeojsonFeatureTypes(allFeatures: GeojsonDataMaps): DeckGlGeoTypes {\n  // @ts-expect-error some test cases only have 1 geotype\n  const featureTypes: DeckGlGeoTypes = {};\n  for (let f = 0; f < allFeatures.length; f++) {\n    const feature = allFeatures[f];\n    if (feature && 'geometry' in feature) {\n      const geoType = featureToDeckGlGeoType[feature.geometry && feature.geometry.type];\n      if (geoType) {\n        featureTypes[geoType] = true;\n      }\n    }\n  }\n\n  return featureTypes;\n}\n\ntype CoordsType = [number, number, number, number | null] & {\n  datumIndex: number;\n  datum: number[];\n};\n\nexport function groupColumnsAsGeoJson(\n  dataContainer: DataContainerInterface,\n  columns: LayerColumns,\n  sortByColumn = 'timestamp'\n): Feature[] {\n  const groupedById: {[key: string]: CoordsType[]} = {};\n  const sortByFieldIdx = columns[sortByColumn].fieldIdx;\n  const sortByRequired = !columns[sortByColumn].optional;\n  for (let index = 0; index < dataContainer.numRows(); index++) {\n    // note: can materialize the row\n    const datum = dataContainer.rowAsArray(index);\n    // Convert id to string to ensure consistent object key behavior\n    // (numeric keys are sorted differently than string keys in Object.entries)\n    const rawId = datum[columns.id.fieldIdx];\n    if (rawId == null) {\n      // Skip rows with missing IDs to avoid grouping them under \"null\"/\"undefined\"\n      continue;\n    }\n    const id = String(rawId);\n    const lat = datum[columns.lat.fieldIdx];\n    const lon = datum[columns.lng.fieldIdx];\n    const altitude = columns.altitude ? datum[columns.altitude.fieldIdx] : 0;\n    const sortBy = sortByFieldIdx > -1 ? datum[sortByFieldIdx] : null;\n    // @ts-expect-error\n    const coords: CoordsType = [lon, lat, altitude, sortBy];\n    // Adding references to the original data to the coordinates array\n    coords.datumIndex = index;\n    coords.datum = datum;\n    if (!groupedById[id]) groupedById[id] = [];\n\n    if (\n      Number.isFinite(lon) &&\n      Number.isFinite(lat) &&\n      (!sortByRequired || (sortByRequired && sortBy))\n    ) {\n      // only push points if lat,lng,and sortby exist\n      groupedById[id].push(coords);\n    }\n  }\n\n  const result: Feature[] = Object.entries(groupedById).map(\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    ([_, items]: [string, CoordsType[]], index) => ({\n      type: 'Feature' as const,\n      geometry: {\n        type: 'LineString' as const,\n        // Sort by columns if has sortByField\n        // TODO: items are expected in Position[] format?\n        coordinates: (sortByFieldIdx > -1\n          ? items.sort((a, b) => ascending(a[3] as any, b[3] as any))\n          : items) as Position[]\n      },\n      properties: {\n        index,\n        // values are used for valueAccessor in TripLayer.formatLayerData()\n        // Note: this can cause row materialization in case of non-row based containers\n        values: items.map(item => dataContainer.rowAsArray(item.datumIndex))\n      }\n    })\n  );\n  return result;\n}\n\n/**\n * Find id / ts / lat / lng columns from a table and assign it to layer columns\n */\nexport function detectTableColumns(\n  dataset: KeplerTable,\n  layerColumns: LayerColumns,\n  sortBy = 'timestamp'\n) {\n  const {fields, fieldPairs} = dataset;\n  if (!fieldPairs.length || !fields.length) {\n    return null;\n  }\n  // find sort by field\n  const sortByFieldIdx = fields.findIndex(f => f.type === ALL_FIELD_TYPES.timestamp);\n  // find id column: support id, uuid, xxx_id and xxx_uuid columns\n  const idFieldIdx = fields.findIndex(f => f.name?.toLowerCase().match(/^(id|uuid|._id|._uuid)$/g));\n\n  if (sortByFieldIdx > -1 && idFieldIdx > -1) {\n    const pointColumns = assignPointPairToLayerColumn(fieldPairs[0], true);\n    return {\n      columns: {\n        ...Object.keys(layerColumns).reduce(\n          (accu, col) => ({\n            ...accu,\n            [col]: pointColumns[col] ?? layerColumns[col]\n          }),\n          {}\n        ),\n        geojson: {\n          value: null,\n          fieldIdx: -1\n          // optional: true\n        },\n        id: {\n          value: fields[idFieldIdx].name,\n          fieldIdx: idFieldIdx\n          // optional: false\n        },\n        [sortBy]: {\n          value: fields[sortByFieldIdx].name,\n          fieldIdx: sortByFieldIdx\n          // optional: false\n        }\n      },\n      label: fieldPairs[0].defaultName\n    };\n  }\n\n  return null;\n}\n\nexport function applyFiltersToTableColumns(\n  dataset: KeplerTable,\n  dataToFeature: GeojsonDataMaps\n): GeojsonDataMaps {\n  const {dataContainer, filteredIndex} = dataset;\n  if (filteredIndex.length === dataContainer.numRows()) {\n    // Only apply the filtering when something is to be filtered out\n    return dataToFeature;\n  }\n\n  const filteredIndexSet = new Set(filteredIndex);\n  const filteredFeatures: GeojsonDataMaps = [];\n  for (const feature of dataToFeature) {\n    // @ts-expect-error geometry.coordinates not available for GeometryCollection\n    const filteredCoords = feature.geometry.coordinates.filter(c =>\n      filteredIndexSet.has(c.datumIndex)\n    );\n    if (filteredCoords.length > 0 && feature) {\n      filteredFeatures.push({\n        ...feature,\n        geometry: {\n          // @ts-expect-error BinaryFeatureCollection\n          ...feature.geometry,\n          coordinates: filteredCoords\n        },\n        properties: {\n          // @ts-expect-error BinaryFeatureCollection\n          ...feature.properties,\n          // @ts-expect-error BinaryFeatureCollection\n          values: feature.geometry.coordinates.map(c => dataContainer.rowAsArray(c.datumIndex))\n        }\n      });\n    }\n  }\n  return filteredFeatures;\n}\n"
  },
  {
    "path": "src/layers/src/grid-layer/grid-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class GridLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'grid-layer-icon',\n    totalColor: 6\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <rect x=\"11.2\" y=\"11.2\" className=\"cr1\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.8}} />\n        <rect x=\"25.4\" y=\"11.2\" className=\"cr2\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.8}} />\n        <rect x=\"39.6\" y=\"11.2\" width=\"13.1\" height=\"13.1\" className=\"cr3\" />\n        <rect x=\"11.2\" y=\"25.4\" className=\"cr4\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.4}} />\n        <rect x=\"25.4\" y=\"25.4\" className=\"cr5\" width=\"13.1\" height=\"13.1\" />\n        <rect x=\"39.6\" y=\"25.4\" className=\"cr6\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.8}} />\n        <rect x=\"11.2\" y=\"39.6\" width=\"13.1\" className=\"cr1\" height=\"13.1\" />\n        <rect x=\"25.4\" y=\"39.6\" className=\"cr2\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.4}} />\n        <rect x=\"39.6\" y=\"39.6\" className=\"cr3\" width=\"13.1\" height=\"13.1\" style={{opacity: 0.4}} />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/grid-layer/grid-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GeoJsonLayer} from '@deck.gl/layers';\nimport {EnhancedGridLayer} from '@kepler.gl/deckgl-layers';\nimport AggregationLayer, {AggregationLayerConfig} from '../aggregation-layer';\nimport {pointToPolygonGeo} from './grid-utils';\nimport GridLayerIcon from './grid-layer-icon';\nimport {\n  ColorRange,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  VisConfigSelection,\n  Merge\n} from '@kepler.gl/types';\nimport {AggregationTypes} from '@kepler.gl/constants';\n\nexport type GridLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  worldUnitSize: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  coverage: VisConfigNumber;\n  sizeRange: VisConfigRange;\n  percentile: VisConfigRange;\n  elevationPercentile: VisConfigRange;\n  elevationScale: VisConfigNumber;\n  enableElevationZoomFactor: VisConfigBoolean;\n  colorAggregation: VisConfigSelection;\n  sizeAggregation: VisConfigSelection;\n  enable3d: VisConfigBoolean;\n};\n\nexport type GridLayerVisConfig = {\n  opacity: number;\n  worldUnitSize: number;\n  colorRange: ColorRange;\n  coverage: number;\n  sizeRange: [number, number];\n  percentile: [number, number];\n  elevationPercentile: [number, number];\n  elevationScale: number;\n  enableElevationZoomFactor: boolean;\n  colorAggregation: AggregationTypes;\n  sizeAggregation: AggregationTypes;\n  enable3d: boolean;\n};\n\nexport type GridLayerConfig = Merge<AggregationLayerConfig, {visConfig: GridLayerVisConfig}>;\n\nexport const gridVisConfigs: {\n  opacity: 'opacity';\n  worldUnitSize: 'worldUnitSize';\n  colorRange: 'colorRange';\n  coverage: 'coverage';\n  sizeRange: 'elevationRange';\n  percentile: 'percentile';\n  elevationPercentile: 'elevationPercentile';\n  elevationScale: 'elevationScale';\n  enableElevationZoomFactor: 'enableElevationZoomFactor';\n  fixedHeight: 'fixedHeight';\n  colorAggregation: 'colorAggregation';\n  sizeAggregation: 'sizeAggregation';\n  enable3d: 'enable3d';\n} = {\n  opacity: 'opacity',\n  worldUnitSize: 'worldUnitSize',\n  colorRange: 'colorRange',\n  coverage: 'coverage',\n  sizeRange: 'elevationRange',\n  percentile: 'percentile',\n  elevationPercentile: 'elevationPercentile',\n  elevationScale: 'elevationScale',\n  enableElevationZoomFactor: 'enableElevationZoomFactor',\n  fixedHeight: 'fixedHeight',\n  colorAggregation: 'colorAggregation',\n  sizeAggregation: 'sizeAggregation',\n  enable3d: 'enable3d'\n};\n\nexport default class GridLayer extends AggregationLayer {\n  declare visConfigSettings: GridLayerVisConfigSettings;\n  declare config: GridLayerConfig;\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(gridVisConfigs);\n    this.visConfigSettings.worldUnitSize.label = 'columns.grid.worldUnitSize';\n  }\n\n  get type(): 'grid' {\n    return 'grid';\n  }\n\n  get layerIcon() {\n    return GridLayerIcon;\n  }\n\n  renderLayer(opts) {\n    const {data, objectHovered, mapState} = opts;\n\n    const defaultAggregationLayerProps = this.getDefaultAggregationLayerProp(opts);\n    const zoomFactor = this.getZoomFactor(mapState);\n    const {visConfig} = this.config;\n    const cellSize = visConfig.worldUnitSize * 1000;\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    return [\n      new EnhancedGridLayer({\n        ...defaultAggregationLayerProps,\n        ...data,\n        wrapLongitude: false,\n        cellSize\n      }),\n\n      // render an outline of each cell if not extruded\n      ...(hoveredObject && !visConfig.enable3d\n        ? [\n            new GeoJsonLayer({\n              ...this.getDefaultHoverLayerProps(),\n              visible: defaultAggregationLayerProps.visible,\n              wrapLongitude: false,\n              data: [\n                pointToPolygonGeo({\n                  object: hoveredObject,\n                  cellSize,\n                  coverage: visConfig.coverage,\n                  mapState\n                })\n              ],\n              getLineColor: this.config.highlightColor,\n              lineWidthScale: 8 * zoomFactor\n            })\n          ]\n        : [])\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/grid-layer/grid-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {WebMercatorViewport} from '@deck.gl/core';\nimport {MapState} from '@kepler.gl/types';\n\n/**\n * top left of the grid to a square polygon for the hover layer\n * and current latitude\n * @param object\n * @param cellSize\n * @param coverage\n * @param properties\n * @param mapState\n * @returns - geojson feature\n */\n\n// TODO: TEST\nexport function pointToPolygonGeo({\n  object,\n  cellSize,\n  coverage,\n  properties,\n  mapState\n}: {\n  object: any;\n  cellSize: number;\n  coverage: number;\n  properties?: any;\n  mapState: MapState;\n}) {\n  const {position} = object;\n  const viewport = new WebMercatorViewport(mapState);\n\n  if (!position) {\n    return null;\n  }\n\n  return {\n    geometry: {\n      coordinates: [\n        viewport.addMetersToLngLat(position, [\n          cellSize * (0.5 - coverage / 2),\n          cellSize * (0.5 - coverage / 2)\n        ]),\n        viewport.addMetersToLngLat(position, [\n          cellSize * (0.5 + coverage / 2),\n          cellSize * (0.5 - coverage / 2)\n        ]),\n        viewport.addMetersToLngLat(position, [\n          cellSize * (0.5 + coverage / 2),\n          cellSize * (0.5 + coverage / 2)\n        ]),\n        viewport.addMetersToLngLat(position, [\n          cellSize * (0.5 - coverage / 2),\n          cellSize * (0.5 + coverage / 2)\n        ]),\n        viewport.addMetersToLngLat(position, [\n          cellSize * (0.5 - coverage / 2),\n          cellSize * (0.5 - coverage / 2)\n        ])\n      ],\n      type: 'LineString'\n    },\n    properties\n  };\n}\n"
  },
  {
    "path": "src/layers/src/h3-hexagon-layer/h3-hexagon-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nclass H3HexagonLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'h3-hexagon-layer-icon',\n    totalColor: 4\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M44.59,54.5H19.41L6.81,32.68,19.41,10.87H44.59L57.19,32.68Zm-23-3.83H42.38l10.38-18-10.38-18H21.62l-10.38,18Z\" />\n        <polygon points=\"24.65 32.86 24.65 37.79 22.53 37.79 22.53 26.3 24.65 26.3 24.65 30.91 29.32 30.91 29.32 26.3 31.43 26.3 31.43 37.79 29.32 37.79 29.32 32.86 24.65 32.86\" />\n        <path d=\"M33.79,37.05l.6-1.67a5.86,5.86,0,0,0,1.39.61,5.59,5.59,0,0,0,1.5.19A2.57,2.57,0,0,0,39,35.66a1.81,1.81,0,0,0,.61-1.46A1.29,1.29,0,0,0,38.94,33a4.55,4.55,0,0,0-2.05-.32H35.74V31h1.1A5.4,5.4,0,0,0,38,30.85a2.1,2.1,0,0,0,.77-.29,1.53,1.53,0,0,0,.51-.54,1.58,1.58,0,0,0,.15-.73,1.14,1.14,0,0,0-.51-1,2.67,2.67,0,0,0-1.5-.34,4.56,4.56,0,0,0-1.51.24,5,5,0,0,0-1.34.73l-.7-1.61a4.92,4.92,0,0,1,1.66-.83,6.91,6.91,0,0,1,2-.31,4.41,4.41,0,0,1,2.81.79,2.71,2.71,0,0,1,1,2.24,2.33,2.33,0,0,1-.54,1.62,3.45,3.45,0,0,1-1.46.93v0a3,3,0,0,1,1.67.81,2.3,2.3,0,0,1,.64,1.7A3.27,3.27,0,0,1,40.48,37a5,5,0,0,1-3.16.91A6.77,6.77,0,0,1,33.79,37.05Z\" />\n      </Base>\n    );\n  }\n}\n\nexport default H3HexagonLayerIcon;\n"
  },
  {
    "path": "src/layers/src/h3-hexagon-layer/h3-hexagon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Layer, {\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  LayerColorConfig,\n  LayerCoverageConfig,\n  LayerSizeConfig\n} from '../base-layer';\nimport {BrushingExtension} from '@deck.gl/extensions';\nimport {GeoJsonLayer} from '@deck.gl/layers';\nimport {H3HexagonLayer} from '@deck.gl/geo-layers';\nimport {EnhancedColumnLayer} from '@kepler.gl/deckgl-layers';\nimport {\n  getCentroid,\n  idToPolygonGeo,\n  h3IsValid,\n  getHexFields,\n  Centroid\n} from '@kepler.gl/common-utils';\nimport {findDefaultColorField, DataContainerInterface, createDataContainer} from '@kepler.gl/utils';\nimport H3HexagonLayerIcon from './h3-hexagon-layer-icon';\nimport {\n  ALL_FIELD_TYPES,\n  CHANNEL_SCALES,\n  HIGHLIGH_COLOR_3D,\n  DEFAULT_COLOR_UI,\n  DEFAULT_TEXT_LABEL,\n  LAYER_VIS_CONFIGS\n} from '@kepler.gl/constants';\n\nimport {\n  ColorRange,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  Merge,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {Datasets, KeplerTable} from '@kepler.gl/table';\n\nimport {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';\n\nexport type HexagonIdLayerColumnsConfig = {\n  hex_id: LayerColumn;\n};\n\nexport type HexagonIdLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  strokeOpacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  coverage: VisConfigNumber;\n  enable3d: VisConfigBoolean;\n  sizeRange: VisConfigRange;\n  coverageRange: VisConfigRange;\n  elevationScale: VisConfigNumber;\n  enableElevationZoomFactor: VisConfigBoolean;\n  filled: VisConfigBoolean;\n  outline: VisConfigBoolean;\n  thickness: VisConfigNumber;\n};\n\nexport type HexagonIdLayerVisConfig = {\n  opacity: number;\n  strokeOpacity: number;\n  colorRange: ColorRange;\n  coverage: number;\n  enable3d: boolean;\n  sizeRange: [number, number];\n  coverageRange: [number, number];\n  elevationScale: number;\n  enableElevationZoomFactor: boolean;\n  filled: boolean;\n  outline: boolean;\n  thickness: number;\n};\n\nexport type HexagonIdLayerVisualChannelConfig = LayerColorConfig &\n  LayerSizeConfig &\n  LayerCoverageConfig;\nexport type HexagonIdLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: HexagonIdLayerColumnsConfig; visConfig: HexagonIdLayerVisConfig}\n> &\n  HexagonIdLayerVisualChannelConfig;\n\nexport type HexagonIdLayerData = {index: number; id; centroid: Centroid};\n\nconst DEFAULT_LINE_SCALE_VALUE = 8;\n\nexport const hexIdRequiredColumns: ['hex_id'] = ['hex_id'];\nexport const hexIdAccessor =\n  ({hex_id}: HexagonIdLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    dc.valueAt(d.index, hex_id.fieldIdx);\n\n/** Accessor that tries to convert h3 indicies in decimal form to hex form */\nexport const hexIdExAccessor =\n  ({hex_id}: HexagonIdLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d => {\n    const value = dc.valueAt(d.index, hex_id.fieldIdx);\n    if (h3IsValid(value)) {\n      return value;\n    }\n    try {\n      return BigInt(value).toString(16);\n    } catch {\n      return null;\n    }\n  };\n\nexport const defaultElevation = 500;\nexport const defaultCoverage = 1;\n\nexport const HexagonIdVisConfigs: {\n  opacity: VisConfigNumber;\n  strokeOpacity: VisConfigNumber;\n  colorRange: 'colorRange';\n  filled: VisConfigBoolean;\n  outline: 'outline';\n  strokeColor: 'strokeColor';\n  strokeColorRange: 'strokeColorRange';\n  thickness: 'thickness';\n  coverage: 'coverage';\n  enable3d: 'enable3d';\n  sizeRange: 'elevationRange';\n  coverageRange: 'coverageRange';\n  elevationScale: 'elevationScale';\n  enableElevationZoomFactor: 'enableElevationZoomFactor';\n  fixedHeight: 'fixedHeight';\n} = {\n  colorRange: 'colorRange',\n  filled: {\n    ...LAYER_VIS_CONFIGS.filled,\n    defaultValue: true\n  },\n  opacity: {\n    ...LAYER_VIS_CONFIGS.opacity,\n    label: 'Fill Opacity',\n    range: [0, 1],\n    property: 'opacity'\n  },\n  outline: 'outline',\n  strokeColor: 'strokeColor',\n  strokeColorRange: 'strokeColorRange',\n  strokeOpacity: {\n    ...LAYER_VIS_CONFIGS.opacity,\n    label: 'Stroke Opacity',\n    range: [0, 1],\n    property: 'strokeOpacity'\n  },\n  thickness: 'thickness',\n  coverage: 'coverage',\n  enable3d: 'enable3d',\n  sizeRange: 'elevationRange',\n  coverageRange: 'coverageRange',\n  elevationScale: 'elevationScale',\n  enableElevationZoomFactor: 'enableElevationZoomFactor',\n  fixedHeight: 'fixedHeight'\n};\n\nconst brushingExtension = new BrushingExtension();\nexport default class HexagonIdLayer extends Layer {\n  dataToFeature: {centroids: Centroid[]};\n\n  declare config: HexagonIdLayerConfig;\n  declare visConfigSettings: HexagonIdLayerVisConfigSettings;\n  constructor(props) {\n    super(props);\n    this.dataToFeature = {centroids: []};\n    this.registerVisConfig(HexagonIdVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface, dataset?: KeplerTable) => {\n      const fieldType = dataset?.fields[this.config.columns.hex_id.fieldIdx].type;\n      return fieldType === ALL_FIELD_TYPES.h3\n        ? hexIdAccessor(this.config.columns)(dataContainer)\n        : hexIdExAccessor(this.config.columns)(dataContainer);\n    };\n  }\n\n  get type(): 'hexagonId' {\n    return 'hexagonId';\n  }\n\n  get name(): 'H3' {\n    return 'H3';\n  }\n\n  get requiredLayerColumns() {\n    return hexIdRequiredColumns;\n  }\n\n  get layerIcon() {\n    // use hexagon layer icon for now\n    return H3HexagonLayerIcon;\n  }\n\n  get visualChannels() {\n    const visualChannels = super.visualChannels;\n    return {\n      color: {\n        ...visualChannels.color,\n        accessor: 'getFillColor',\n        condition: config => config.visConfig.filled\n      },\n      strokeColor: {\n        property: 'strokeColor',\n        key: 'strokeColor',\n        field: 'strokeColorField',\n        scale: 'strokeColorScale',\n        domain: 'strokeColorDomain',\n        range: 'strokeColorRange',\n        channelScaleType: CHANNEL_SCALES.color,\n        accessor: 'getLineColor',\n        condition: config => config.visConfig.outline,\n        defaultValue: config => config.visConfig.strokeColor || config.color\n      },\n      size: {\n        ...visualChannels.size,\n        property: 'height',\n        accessor: 'getElevation',\n        nullValue: 0,\n        condition: config => config.visConfig.enable3d,\n        defaultValue: defaultElevation\n      },\n      coverage: {\n        property: 'coverage',\n        field: 'coverageField',\n        scale: 'coverageScale',\n        domain: 'coverageDomain',\n        range: 'coverageRange',\n        key: 'coverage',\n        channelScaleType: CHANNEL_SCALES.radius,\n        accessor: 'getCoverage',\n        nullValue: 0,\n        defaultValue: defaultCoverage\n      }\n    };\n  }\n\n  setInitialLayerConfig(dataset) {\n    if (!dataset.dataContainer.numRows()) {\n      return this;\n    }\n    const defaultColorField = findDefaultColorField(dataset);\n\n    if (defaultColorField) {\n      this.updateLayerConfig<HexagonIdLayerConfig>({\n        colorField: defaultColorField\n      });\n      this.updateLayerVisualChannel(dataset, 'color');\n    }\n\n    return this;\n  }\n\n  static findDefaultLayerProps({fields = [], dataContainer, label}: KeplerTable) {\n    const hexFields = getHexFields(fields, dataContainer);\n    if (!hexFields.length) {\n      return {props: []};\n    }\n\n    return {\n      props: hexFields.map(f => ({\n        isVisible: true,\n        // default layer name should be dataset name\n        label,\n        columns: {\n          hex_id: {\n            value: f.name,\n            fieldIdx: fields.findIndex(fid => fid.name === f.name)\n          }\n        }\n      }))\n    };\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial) {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props);\n\n    return {\n      ...defaultLayerConfig,\n\n      // add stroke color visual channel\n      strokeColorField: null,\n      strokeColorDomain: [0, 1],\n      strokeColorScale: 'quantile',\n      colorUI: {\n        ...defaultLayerConfig.colorUI,\n        strokeColorRange: DEFAULT_COLOR_UI\n      },\n\n      // add radius visual channel\n      coverageField: null,\n      coverageDomain: [0, 1],\n      coverageScale: 'linear',\n\n      // modify default textLabel anchor position to be appropriate for a hexagon shape\n      textLabel: [\n        {\n          ...DEFAULT_TEXT_LABEL,\n          anchor: 'middle'\n        }\n      ]\n    };\n  }\n\n  calculateDataAttribute({filteredIndex}: KeplerTable, getHexId) {\n    const data: HexagonIdLayerData[] = [];\n\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const id = getHexId({index});\n      const centroid = this.dataToFeature.centroids[index];\n\n      if (centroid) {\n        data.push({\n          index,\n          id,\n          centroid\n        });\n      }\n    }\n    return data;\n  }\n\n  // TODO: fix complexity\n  /* eslint-disable complexity */\n  formatLayerData(datasets: Datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n\n    const dataset = datasets[this.config.dataId];\n    const {gpuFilter, dataContainer} = dataset;\n    const getHexId = this.getPositionAccessor(dataContainer, dataset);\n    const {data, triggerChanged} = this.updateData(datasets, oldLayerData);\n    const accessors = this.getAttributeAccessors({dataContainer});\n    const {textLabel} = this.config;\n\n    // get all distinct characters in the text labels\n    const textLabels = formatTextLabelData({\n      textLabel,\n      triggerChanged,\n      oldLayerData,\n      data,\n      dataContainer\n    });\n\n    return {\n      data,\n      getHexId,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),\n      textLabels,\n      getPosition: d => d.centroid,\n      ...accessors\n    };\n  }\n  /* eslint-enable complexity */\n\n  updateLayerMeta(dataset: KeplerTable, getHexId) {\n    const {dataContainer} = dataset;\n\n    const centroids = dataContainer.map((d, index) => {\n      const id = getHexId({index});\n      if (!h3IsValid(id)) {\n        return null;\n      }\n      // save a reference of centroids to dataToFeature\n      // so we don't have to re calculate it again\n      return getCentroid({id});\n    }, true);\n\n    const centroidsDataContainer = createDataContainer(centroids);\n\n    const bounds = this.getPointsBounds(\n      centroidsDataContainer,\n      (d: any, dc: DataContainerInterface) => {\n        return [dc.valueAt(d.index, 0), dc.valueAt(d.index, 1)];\n      }\n    );\n    this.dataToFeature = {centroids};\n    this.updateMeta({bounds});\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, objectHovered, mapState, interactionConfig} = opts;\n\n    const zoomFactor = this.getZoomFactor(mapState);\n    const eleZoomFactor = this.getElevationZoomFactor(mapState);\n    const {config} = this;\n    const {visConfig} = config;\n    const updateTriggers = this.getVisualChannelUpdateTriggers();\n\n    const h3HexagonLayerTriggers = {\n      getHexagon: this.config.columns,\n      getFillColor: updateTriggers.getFillColor,\n      getLineColor: updateTriggers.getLineColor,\n      getElevation: updateTriggers.getElevation,\n      getFilterValue: gpuFilter.filterValueUpdateTriggers\n    };\n\n    const columnLayerTriggers = {\n      getCoverage: updateTriggers.getCoverage\n    };\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    // getPixelOffset with no radius\n    const radiusScale = 1.0;\n    const getRaidus = null;\n    const getPixelOffset = getTextOffsetByRadius(radiusScale, getRaidus, mapState);\n\n    const brushingProps = this.getBrushingExtensionProps(interactionConfig);\n    const extensions = [...defaultLayerProps.extensions, brushingExtension];\n    const sharedProps = {\n      getFilterValue: data.getFilterValue,\n      extensions,\n      filterRange: defaultLayerProps.filterRange,\n      visible: defaultLayerProps.visible,\n      ...brushingProps\n    };\n\n    return [\n      new H3HexagonLayer({\n        ...defaultLayerProps,\n        ...data,\n        ...brushingProps,\n        extensions,\n        wrapLongitude: false,\n\n        filled: visConfig.filled,\n        stroked: visConfig.outline,\n        lineWidthScale: visConfig.thickness,\n\n        getHexagon: (x: any) => x.id,\n\n        // coverage\n        coverage: config.coverageField ? 1 : visConfig.coverage,\n\n        // highlight\n        autoHighlight: visConfig.enable3d,\n        highlightColor: HIGHLIGH_COLOR_3D,\n\n        // elevation\n        extruded: visConfig.enable3d,\n        elevationScale: visConfig.elevationScale * eleZoomFactor,\n\n        // render\n        updateTriggers: h3HexagonLayerTriggers,\n        _subLayerProps: {\n          'hexagon-cell': {\n            type: EnhancedColumnLayer,\n            getCoverage: data.getCoverage,\n            updateTriggers: columnLayerTriggers,\n            strokeOpacity: visConfig.strokeOpacity\n          }\n        }\n      }),\n      // hover layer\n      ...(hoveredObject && !config.sizeField\n        ? [\n            new GeoJsonLayer({\n              ...this.getDefaultHoverLayerProps(),\n              visible: defaultLayerProps.visible,\n              data: [idToPolygonGeo(hoveredObject)],\n              getLineColor: config.highlightColor,\n              lineWidthScale: DEFAULT_LINE_SCALE_VALUE * zoomFactor,\n              wrapLongitude: false\n            })\n          ]\n        : []),\n      // text label layer\n      ...this.renderTextLabelLayer(\n        {\n          getPosition: data.getPosition,\n          sharedProps,\n          getPixelOffset,\n          updateTriggers\n        },\n        opts\n      )\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/h3-hexagon-layer/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as H3HexagonLayerIcon} from './h3-hexagon-layer-icon';\nexport * from './h3-hexagon-layer';\n"
  },
  {
    "path": "src/layers/src/heatmap-layer/heatmap-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class HeatmapLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'heatmap-layer-icon',\n    totalColor: 3\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M51.87,21C49.55,16.67,43.77,15.29,39,18a11.42,11.42,0,0,0-1.65,1.13c-2.73,2.14-2.12,3-6,4.89-2.27,1.07-3.42,1.08-6.88,1.4l-2.24.21a14,14,0,0,0-2.86.84c-6.64,2.73-10.11,9.86-7.76,15.94s9.63,8.79,16.27,6.07A14,14,0,0,0,31.77,46l0,0,.06-.07c.43-.4.8-.78,1.14-1.14a2.66,2.66,0,0,0,.32-.36l.17-.19c3-3.53,2-5,4.9-7.39,2.38-1.93,5.41-.95,9-3C52.19,31.15,54.19,25.43,51.87,21ZM26,44.59a8.7,8.7,0,0,1-2.26.59A7.16,7.16,0,0,1,16,40.85c-1.44-3.72.68-8.08,4.73-9.74A8.33,8.33,0,0,1,23,30.53a7.15,7.15,0,0,1,7.71,4.32C32.19,38.57,30.06,42.93,26,44.59Z\"\n          className=\"cr2\"\n          style={{opacity: 0.8}}\n        />\n        <path\n          d=\"M57,18.18A14.56,14.56,0,0,0,42.25,10.7a16.62,16.62,0,0,0-6.12,2,17.35,17.35,0,0,0-2.39,1.65,20.15,20.15,0,0,0-2.83,2.73,4.52,4.52,0,0,1-2,1.45,5.88,5.88,0,0,1-2.26.63l-1.45.14-1.27.12-2.33.22-.2,0-.18,0a18.88,18.88,0,0,0-4,1.18c-9.6,3.93-14.51,14.57-11,23.71A17.59,17.59,0,0,0,24.81,55.4,20.19,20.19,0,0,0,30,54.05a20,20,0,0,0,5.26-3.19l.82-.71.05-.08,1-1c.21-.22.41-.45.59-.66l.13-.15a20,20,0,0,0,3.39-5.48c.36-.87.36-.87.68-1.14a9.09,9.09,0,0,1,1.56-.32,18.79,18.79,0,0,0,6.69-2.19,16.56,16.56,0,0,0,7.88-9.9A14.93,14.93,0,0,0,57,18.18ZM47.63,34.27a13.93,13.93,0,0,1-5.06,1.61,7.75,7.75,0,0,0-3.86,1.36,7.06,7.06,0,0,0-2.33,3.24,14.17,14.17,0,0,1-2.51,4.09l-.1.11a5.11,5.11,0,0,1-.43.47c-.31.35-.7.73-1.14,1.14l-.09.09-.12.09a14.4,14.4,0,0,1-4,2.44,14.73,14.73,0,0,1-3.84,1c-5.87.69-11.13-2.27-13.08-7.35-2.45-6.32,1.16-13.76,8-16.59a15,15,0,0,1,3-.87l2.29-.22.9-.07,2-.2a10.88,10.88,0,0,0,3.85-1.08,9.43,9.43,0,0,0,3.77-2.76A14.75,14.75,0,0,1,37,18.71a11.5,11.5,0,0,1,1.71-1.17,11.08,11.08,0,0,1,4.16-1.36,9.26,9.26,0,0,1,9.42,4.64C54.75,25.42,52.65,31.47,47.63,34.27Z\"\n          className=\"cr1\"\n          style={{opacity: 0.36}}\n        />\n        <path\n          d=\"M33,44.79a9.53,9.53,0,0,1-1.13,1.14C32.3,45.53,32.67,45.15,33,44.79Z\"\n          className=\"cr1\"\n          style={{opacity: 0.36}}\n        />\n        <path\n          d=\"M25.83,44.13c-3.82,1.55-8,0-9.33-3.46s.65-7.55,4.45-9.1,8,0,9.33,3.46S29.63,42.57,25.83,44.13Z\"\n          className=\"cr3\"\n        />\n        <path d=\"M31.81,46a.09.09,0,0,1,0,0h0Z\" className=\"cr3\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/heatmap-layer/heatmap-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createSelector} from 'reselect';\nimport {CHANNEL_SCALES, ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport MapboxGLLayer, {MapboxLayerGLConfig} from '../mapboxgl-layer';\nimport HeatmapLayerIcon from './heatmap-layer-icon';\nimport {LayerBaseConfigPartial, LayerWeightConfig, VisualChannels} from '../base-layer';\nimport {\n  ColorRange,\n  VisConfigColorRange,\n  VisConfigNumber,\n  HexColor,\n  Merge,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {hexToRgb, DataContainerInterface} from '@kepler.gl/utils';\nimport {KeplerTable} from '@kepler.gl/table';\n\nimport {getGeoArrowPointLayerProps, FindDefaultLayerPropsReturnValue} from '../layer-utils';\n\nexport type HeatmapLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  radius: VisConfigNumber;\n};\n\nexport type HeatmapLayerColumnsConfig = {\n  // COLUMN_MODE_POINTS\n  lat: LayerColumn;\n  lng: LayerColumn;\n\n  // COLUMN_MODE_GEOARROW\n  geoarrow: LayerColumn;\n};\n\nexport type HeatmapLayerVisConfig = {\n  opacity: number;\n  colorRange: ColorRange;\n  radius: number;\n};\n\nexport type HeatmapLayerVisualChannelConfig = LayerWeightConfig;\nexport type HeatmapLayerConfig = Merge<\n  MapboxLayerGLConfig,\n  {columns: HeatmapLayerColumnsConfig; visConfig: HeatmapLayerVisConfig}\n> &\n  HeatmapLayerVisualChannelConfig;\n\nexport const MAX_ZOOM_LEVEL = 18;\n\nexport const pointPosAccessor =\n  ({lat, lng}: HeatmapLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  (d: {index: number}): number[] =>\n    [dc.valueAt(d.index, lng.fieldIdx), dc.valueAt(d.index, lat.fieldIdx)];\n\nexport const geoarrowPosAccessor =\n  ({geoarrow}: HeatmapLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  (d: {index: number}): number[] => {\n    const row = dc.valueAt(d.index, geoarrow.fieldIdx);\n    return [row.get(0), row.get(1)];\n  };\n\nexport const pointColResolver = ({lat, lng, geoarrow}: HeatmapLayerColumnsConfig, columnMode) => {\n  if (columnMode === COLUMN_MODE_POINTS) {\n    return `${lat.fieldIdx}-${lng.fieldIdx}`;\n  }\n  return `geoarrow-${geoarrow.fieldIdx}`;\n};\n\nexport const heatmapVisConfigs: {\n  opacity: 'opacity';\n  colorRange: 'colorRange';\n  radius: 'heatmapRadius';\n} = {\n  opacity: 'opacity',\n  colorRange: 'colorRange',\n  radius: 'heatmapRadius'\n};\n\nexport const pointRequiredColumns = ['lat', 'lng'];\nexport const geoarrowRequiredColumns = ['geoarrow'];\n\nexport const COLUMN_MODE_POINTS = 'points';\nexport const COLUMN_MODE_GEOARROW = 'geoarrow';\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_POINTS,\n    label: 'Points',\n    requiredColumns: pointRequiredColumns\n  },\n  {\n    key: COLUMN_MODE_GEOARROW,\n    label: 'Geoarrow Points',\n    requiredColumns: geoarrowRequiredColumns\n  }\n];\nconst DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;\n\n/**\n *\n * @param colorRange\n * @return [\n *  0, \"rgba(33,102,172,0)\",\n *  0.2, \"rgb(103,169,207)\",\n *  0.4, \"rgb(209,229,240)\",\n *  0.6, \"rgb(253,219,199)\",\n *  0.8, \"rgb(239,138,98)\",\n *  1, \"rgb(178,24,43)\"\n * ]\n */\nconst heatmapDensity = (colorRange: ColorRange): (string | number)[] => {\n  const colors: HexColor[] = ['#000000', ...colorRange.colors];\n\n  const colorDensity: (string | number)[] = [];\n  colors.forEach((color, index) => {\n    colorDensity.push(index / colors.length);\n    colorDensity.push(`rgb(${hexToRgb(color).join(',')})`);\n  });\n\n  colorDensity[1] = 'rgba(0,0,0,0)';\n  return colorDensity;\n};\n\nclass HeatmapLayer extends MapboxGLLayer {\n  declare visConfigSettings: HeatmapLayerVisConfigSettings;\n  declare config: HeatmapLayerConfig;\n\n  constructor(props) {\n    super(props);\n    this.registerVisConfig(heatmapVisConfigs);\n\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) => {\n      switch (this.config.columnMode) {\n        case COLUMN_MODE_GEOARROW:\n          return geoarrowPosAccessor(this.config.columns)(dataContainer);\n        default:\n          // COLUMN_MODE_POINTS\n          return pointPosAccessor(this.config.columns)(dataContainer);\n      }\n    };\n  }\n\n  get type(): 'heatmap' {\n    return 'heatmap';\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  hasAllColumns() {\n    const {columns, columnMode} = this.config;\n    if (columnMode === COLUMN_MODE_GEOARROW) {\n      return this.hasColumnValue(columns.geoarrow);\n    }\n    return super.hasAllColumns();\n  }\n\n  static findDefaultLayerProps(dataset: KeplerTable): FindDefaultLayerPropsReturnValue {\n    const altProps = getGeoArrowPointLayerProps(dataset);\n\n    return {\n      props: [],\n      altProps\n    };\n  }\n\n  get visualChannels(): VisualChannels {\n    return {\n      // @ts-expect-error\n      weight: {\n        property: 'weight',\n        field: 'weightField',\n        scale: 'weightScale',\n        domain: 'weightDomain',\n        key: 'weight',\n        // supportedFieldTypes can be determined by channelScaleType\n        // or specified here\n        defaultMeasure: 'property.density',\n        supportedFieldTypes: [ALL_FIELD_TYPES.real, ALL_FIELD_TYPES.integer],\n        channelScaleType: CHANNEL_SCALES.size\n      }\n    };\n  }\n\n  get layerIcon() {\n    return HeatmapLayerIcon;\n  }\n\n  getVisualChannelDescription(channel) {\n    return channel === 'color'\n      ? {\n          label: 'property.color',\n          measure: 'property.density'\n        }\n      : {\n          label: 'property.weight',\n          measure: this.config.weightField ? this.config.weightField.name : 'property.density'\n        };\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial): HeatmapLayerConfig {\n    // mapbox heatmap layer color is always based on density\n    // no need to set colorField, colorDomain and colorScale\n\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const {colorField, colorDomain, colorScale, ...layerConfig} = {\n      ...super.getDefaultLayerConfig(props),\n      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,\n\n      weightField: null,\n      weightDomain: [0, 1],\n      weightScale: 'linear'\n    };\n\n    // @ts-expect-error\n    return layerConfig;\n  }\n\n  updateLayerMeta(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n\n    const getPosition = this.getPositionAccessor(dataContainer);\n    const bounds = this.getPointsBounds(dataContainer, getPosition);\n    this.updateMeta({bounds});\n  }\n\n  columnsSelector = config => pointColResolver(config.columns, config.columnMode);\n  visConfigSelector = config => config.visConfig;\n  weightFieldSelector = config => (config.weightField ? config.weightField.name : null);\n  weightDomainSelector = config => config.weightDomain;\n\n  paintSelector = createSelector(\n    this.visConfigSelector,\n    this.weightFieldSelector,\n    this.weightDomainSelector,\n    (visConfig, weightField, weightDomain) => ({\n      'heatmap-weight': weightField\n        ? ['interpolate', ['linear'], ['get', weightField], weightDomain[0], 0, weightDomain[1], 1]\n        : 1,\n      'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, MAX_ZOOM_LEVEL, 3],\n      'heatmap-color': [\n        'interpolate',\n        ['linear'],\n        ['heatmap-density'],\n        ...heatmapDensity(visConfig.colorRange)\n      ],\n      'heatmap-radius': [\n        'interpolate',\n        ['linear'],\n        ['zoom'],\n        0,\n        2,\n        MAX_ZOOM_LEVEL,\n        visConfig.radius // radius\n      ],\n      'heatmap-opacity': visConfig.opacity\n    })\n  );\n\n  computeHeatmapConfiguration = createSelector(\n    this.sourceSelector,\n    this.filterSelector,\n    this.paintSelector,\n    (source, filter, paint) => {\n      return {\n        type: 'heatmap',\n        id: this.id,\n        source,\n        layout: {\n          visibility: 'visible'\n        },\n        paint,\n        ...(this.isValidFilter(filter) ? {filter} : {})\n      };\n    }\n  );\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {weightField} = this.config;\n    const {dataContainer} = datasets[this.config.dataId];\n    const getPosition = this.getPositionAccessor(dataContainer);\n    const {data} = this.updateData(datasets, oldLayerData);\n\n    // @ts-ignore\n    const newConfig = this.computeHeatmapConfiguration(this.config, datasets);\n    newConfig.id = this.id;\n\n    return {\n      columns: this.config.columns,\n      config: newConfig,\n      data,\n      weightField,\n      getPosition\n    };\n  }\n}\n\nexport default HeatmapLayer;\n"
  },
  {
    "path": "src/layers/src/hexagon-layer/hexagon-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class HexagonLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'hexagon-layer-icon',\n    totalColor: 6\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <polygon\n          className=\"cr1\"\n          points=\"23.9,10 30.9,14 30.9,22.1 23.9,26.2 16.8,22.1 16.8,14 \"\n          style={{opacity: 0.6}}\n        />\n        <polygon\n          className=\"cr2\"\n          points=\"23.9,37.8 30.9,41.9 30.9,50 23.9,54 16.8,50 16.8,41.9 \"\n          style={{opacity: 0.4}}\n        />\n        <polygon className=\"cr6\" points=\"40.1,10 47.2,14 47.2,22.1 40.1,26.2 33.1,22.1 33.1,14 \" />\n        <polygon\n          className=\"cr3\"\n          points=\"40.1,37.8 47.2,41.9 47.2,50 40.1,54 33.1,50 33.1,41.9 \"\n          style={{opacity: 0.8}}\n        />\n        <polygon\n          className=\"cr1\"\n          points=\"15.8,23.9 22.8,27.9 22.8,36.1 15.8,40.1 8.7,36.1 8.7,27.9 \"\n        />\n        <polygon\n          className=\"cr4\"\n          points=\"32,23.9 39,27.9 39,36.1 32,40.1 25,36.1 25,27.9 \"\n          style={{opacity: 0.8}}\n        />\n        <polygon\n          className=\"cr5\"\n          points=\"48.2,23.9 55.3,27.9 55.3,36.1 48.2,40.1 41.2,36.1 41.2,27.9 \"\n          style={{opacity: 0.4}}\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/hexagon-layer/hexagon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {GeoJsonLayer} from '@deck.gl/layers';\nimport AggregationLayer, {AggregationLayerConfig} from '../aggregation-layer';\nimport {EnhancedHexagonLayer} from '@kepler.gl/deckgl-layers';\nimport {hexagonToPolygonGeo} from './hexagon-utils';\nimport HexagonLayerIcon from './hexagon-layer-icon';\nimport {\n  ColorRange,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  VisConfigSelection,\n  Merge\n} from '@kepler.gl/types';\nimport {AggregationTypes} from '@kepler.gl/constants';\n\nexport type HexagonLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  worldUnitSize: VisConfigNumber;\n  resolution: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  coverage: VisConfigNumber;\n  sizeRange: VisConfigRange;\n  percentile: VisConfigRange;\n  elevationPercentile: VisConfigRange;\n  elevationScale: VisConfigNumber;\n  enableElevationZoomFactor: VisConfigBoolean;\n  colorAggregation: VisConfigSelection;\n  sizeAggregation: VisConfigSelection;\n  enable3d: VisConfigBoolean;\n};\n\nexport type HexagonLayerVisConfig = {\n  opacity: number;\n  worldUnitSize: number;\n  resolution: number;\n  colorRange: ColorRange;\n  coverage: number;\n  sizeRange: [number, number];\n  percentile: [number, number];\n  elevationPercentile: [number, number];\n  elevationScale: number;\n  enableElevationZoomFactor: boolean;\n  colorAggregation: AggregationTypes;\n  sizeAggregation: AggregationTypes;\n  enable3d: boolean;\n};\n\nexport type HexagonLayerConfig = Merge<AggregationLayerConfig, {visConfig: HexagonLayerVisConfig}>;\n\nexport const hexagonVisConfigs: {\n  opacity: 'opacity';\n  worldUnitSize: 'worldUnitSize';\n  resolution: 'resolution';\n  colorRange: 'colorRange';\n  coverage: 'coverage';\n  sizeRange: 'elevationRange';\n  percentile: 'percentile';\n  elevationPercentile: 'elevationPercentile';\n  elevationScale: 'elevationScale';\n  enableElevationZoomFactor: 'enableElevationZoomFactor';\n  fixedHeight: 'fixedHeight';\n  colorAggregation: 'colorAggregation';\n  sizeAggregation: 'sizeAggregation';\n  enable3d: 'enable3d';\n} = {\n  opacity: 'opacity',\n  worldUnitSize: 'worldUnitSize',\n  resolution: 'resolution',\n  colorRange: 'colorRange',\n  coverage: 'coverage',\n  sizeRange: 'elevationRange',\n  percentile: 'percentile',\n  elevationPercentile: 'elevationPercentile',\n  elevationScale: 'elevationScale',\n  enableElevationZoomFactor: 'enableElevationZoomFactor',\n  fixedHeight: 'fixedHeight',\n  colorAggregation: 'colorAggregation',\n  sizeAggregation: 'sizeAggregation',\n  enable3d: 'enable3d'\n};\n\nexport default class HexagonLayer extends AggregationLayer {\n  declare visConfigSettings: HexagonLayerVisConfigSettings;\n  declare config: HexagonLayerConfig;\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(hexagonVisConfigs);\n    this.visConfigSettings.worldUnitSize.label = 'columns.hexagon.worldUnitSize';\n  }\n\n  get type(): 'hexagon' {\n    return 'hexagon';\n  }\n\n  get name(): 'Hexbin' {\n    return 'Hexbin';\n  }\n\n  get layerIcon() {\n    return HexagonLayerIcon;\n  }\n\n  renderLayer(opts) {\n    const {data, objectHovered, mapState} = opts;\n\n    const defaultAggregationLayerProps = this.getDefaultAggregationLayerProp(opts);\n    const zoomFactor = this.getZoomFactor(mapState);\n    const {visConfig} = this.config;\n    const radius = visConfig.worldUnitSize * 1000;\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    return [\n      new EnhancedHexagonLayer({\n        ...defaultAggregationLayerProps,\n        ...data,\n        wrapLongitude: false,\n        radius\n      }),\n\n      // render an outline of each hexagon if not extruded\n      ...(hoveredObject && !visConfig.enable3d\n        ? [\n            new GeoJsonLayer({\n              ...this.getDefaultHoverLayerProps(),\n              visible: defaultAggregationLayerProps.visible,\n              wrapLongitude: false,\n              data: [\n                hexagonToPolygonGeo(hoveredObject, {}, radius * visConfig.coverage, mapState)\n              ].filter(d => d),\n              getLineColor: this.config.highlightColor,\n              lineWidthScale: 8 * zoomFactor\n            })\n          ]\n        : [])\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/hexagon-layer/hexagon-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {WebMercatorViewport} from '@deck.gl/core';\nimport Console from 'global/console';\nimport type {Centroid} from '@kepler.gl/common-utils';\n\nexport function hexagonToPolygonGeo(object, properties, radius, mapState) {\n  const viewport = new WebMercatorViewport(mapState);\n  if (!Array.isArray(object.position)) {\n    return null;\n  }\n\n  const screenCenter = viewport.projectFlat(object.position);\n  const {unitsPerMeter} = viewport.getDistanceScales(object.position);\n\n  if (!Array.isArray(unitsPerMeter)) {\n    Console.warn(`unitsPerMeter is undefined`);\n    return null;\n  }\n\n  const pixRadius = radius * unitsPerMeter[0];\n\n  const coordinates: any[] = [];\n\n  for (let i = 0; i < 6; i++) {\n    const vertex = hex_corner(screenCenter, pixRadius, i);\n    coordinates.push(viewport.unprojectFlat(vertex));\n  }\n\n  coordinates.push(coordinates[0]);\n\n  return {\n    geometry: {\n      coordinates,\n      type: 'LineString'\n    },\n    properties\n  };\n}\n\nfunction hex_corner(center: Centroid, radius: number, i: number) {\n  const angle_deg = 60 * i + 30;\n  const angle_rad = (Math.PI / 180) * angle_deg;\n\n  return [center[0] + radius * Math.cos(angle_rad), center[1] + radius * Math.sin(angle_rad)];\n}\n"
  },
  {
    "path": "src/layers/src/icon-layer/icon-info-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport {line} from 'd3-shape';\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport Table from '../example-table';\n\nconst CenterFlexbox = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nconst lineFunction = line()\n  .x(d => d[0] * 10)\n  .y(d => d[1] * 10);\n\nconst IconShape = ({mesh}) => (\n  <svg className=\"icon-table__item__shape\" width=\"20px\" height=\"20px\">\n    <g transform=\"translate(10, 10)\">\n      {mesh.cells.map((cell, i) => (\n        <path key={i} fill=\"#000000\" d={lineFunction(cell.map(idx => mesh.positions[idx]))} />\n      ))}\n    </g>\n  </svg>\n);\n\nconst StyledIconItem = styled(CenterFlexbox)`\n  padding-left: 6px;\n  width: 180px;\n  height: 48px;\n  margin-right: 12px;\n\n  .icon-table_item__name {\n    margin-left: 12px;\n  }\n`;\n\nconst StyledCode = styled.code`\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst StyledTitle = styled.div`\n  font-size: 20px;\n  letter-spacing: 1.25px;\n  margin: 18px 0 14px 0;\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst IconItem = ({icon: {id, mesh}}) => (\n  <StyledIconItem className=\"icon-table__item\">\n    <IconShape mesh={mesh} />\n    <div className=\"icon-table_item__name\">\n      <StyledCode>{id}</StyledCode>\n    </div>\n  </StyledIconItem>\n);\n\nconst ExampleTable = () => (\n  <Table className=\"icon-example-table\">\n    <thead>\n      <tr>\n        <th>point_lat</th>\n        <th>point_lng</th>\n        <th>icon</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <td>37.769897</td>\n        <td>-122.41168</td>\n        <td>\n          <StyledCode>android</StyledCode>\n        </td>\n      </tr>\n      <tr>\n        <td>37.806928</td>\n        <td>-122.40218</td>\n        <td />\n      </tr>\n      <tr>\n        <td>37.778564</td>\n        <td>-122.39096</td>\n        <td>\n          <StyledCode>calendar</StyledCode>\n        </td>\n      </tr>\n      <tr>\n        <td>37.745995</td>\n        <td>-122.30220</td>\n        <td />\n      </tr>\n      <tr>\n        <td>37.329841</td>\n        <td>-122.103847</td>\n        <td>\n          <StyledCode>control-off</StyledCode>\n        </td>\n      </tr>\n    </tbody>\n  </Table>\n);\n\nconst IconTable = styled.div`\n  display: flex;\n  align-items: flex-start;\n  flex-wrap: wrap;\n`;\n\nconst IconInfoModalFactory = (svgIcons: any[] = []) => {\n  const IconInfoModal = () => (\n    <div className=\"icon-info-modal\">\n      <div className=\"icon-info-modal__description\">\n        <FormattedMessage id={'modal.iconInfo.description1'} />{' '}\n        <code>\n          <FormattedMessage id={'modal.iconInfo.code'} />\n        </code>\n        <FormattedMessage id={'modal.iconInfo.description2'} />\n      </div>\n      <div className=\"icon-info-modal__example\">\n        <StyledTitle>\n          <FormattedMessage id={'modal.iconInfo.example'} />\n        </StyledTitle>\n        <ExampleTable />\n      </div>\n      <div className=\"icon-info-modal__icons\">\n        <StyledTitle>\n          <FormattedMessage id={'modal.iconInfo.icons'} />\n        </StyledTitle>\n        <IconTable className=\"icon-info-modal__icons__table\">\n          {svgIcons.map(icon => (\n            <IconItem key={icon.id} icon={icon} />\n          ))}\n        </IconTable>\n      </div>\n    </div>\n  );\n\n  return IconInfoModal;\n};\n\nexport default IconInfoModalFactory;\n"
  },
  {
    "path": "src/layers/src/icon-layer/icon-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class IconLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'icon-layer-icon',\n    totalColor: 3\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          className=\"cr1\"\n          d=\"M42.27,33.59l-4.34,4.34-4.34-4.34a13.25,13.25,0,0,1-8.9-12.52h0A13.24,13.24,0,0,1,37.93,7.83h0A13.24,13.24,0,0,1,51.17,21.07h0A13.25,13.25,0,0,1,42.27,33.59ZM37.93,28.3a7.22,7.22,0,1,0-7.22-7.22A7.22,7.22,0,0,0,37.93,28.3Z\"\n        />\n        <path\n          className=\"cr2\"\n          d=\"M18.68,48.79l-2.44,2.44L13.8,48.79a7.44,7.44,0,0,1-5-7h0a7.44,7.44,0,0,1,7.44-7.44h0a7.44,7.44,0,0,1,7.44,7.44h0A7.44,7.44,0,0,1,18.68,48.79Zm-2.44-3a4.06,4.06,0,1,0-4.06-4.06A4.06,4.06,0,0,0,16.24,45.81Z\"\n        />\n        <path\n          className=\"cr3\"\n          d=\"M48.85,55.52l-2.2,2.2-2.2-2.2a6.73,6.73,0,0,1-4.52-6.36h0a6.72,6.72,0,0,1,6.72-6.72h0a6.72,6.72,0,0,1,6.72,6.72h0A6.73,6.73,0,0,1,48.85,55.52Zm-2.2-2.69A3.67,3.67,0,1,0,43,49.17,3.67,3.67,0,0,0,46.65,52.83Z\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/icon-layer/icon-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Window from 'global/window';\nimport {BrushingExtension} from '@deck.gl/extensions';\nimport GL from '@luma.gl/constants';\n\nimport {SvgIconLayer} from '@kepler.gl/deckgl-layers';\nimport IconLayerIcon from './icon-layer-icon';\nimport {ICON_FIELDS} from '@kepler.gl/constants';\nimport IconInfoModalFactory from './icon-info-modal';\nimport Layer, {LayerBaseConfig, LayerBaseConfigPartial} from '../base-layer';\nimport {assignPointPairToLayerColumn, FindDefaultLayerPropsReturnValue} from '../layer-utils';\nimport {isTest} from '@kepler.gl/utils';\nimport {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport {getApplicationConfig, DataContainerInterface} from '@kepler.gl/utils';\nimport {\n  ColorRange,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  Merge,\n  LayerColumn,\n  BindedLayerCallbacks\n} from '@kepler.gl/types';\n\nexport type IconLayerColumnsConfig = {\n  lat: LayerColumn;\n  lng: LayerColumn;\n  altitude: LayerColumn;\n  icon: LayerColumn;\n};\n\ntype IconGeometry = object | null;\n\nexport type IconLayerVisConfigSettings = {\n  radius: VisConfigNumber;\n  fixedRadius: VisConfigBoolean;\n  opacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  radiusRange: VisConfigRange;\n};\n\nexport type IconLayerVisConfig = {\n  radius: number;\n  fixedRadius: boolean;\n  opacity: number;\n  colorRange: ColorRange;\n  radiusRange: [number, number];\n  billboard: boolean;\n};\n\nexport type IconLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: IconLayerColumnsConfig; visConfig: IconLayerVisConfig}\n>;\n\nexport type IconLayerData = {index: number; icon: string};\n\nconst brushingExtension = new BrushingExtension();\n\nexport const iconPosAccessor =\n  ({lat, lng, altitude}: IconLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    [\n      dc.valueAt(d.index, lng.fieldIdx),\n      dc.valueAt(d.index, lat.fieldIdx),\n      altitude?.fieldIdx > -1 ? dc.valueAt(d.index, altitude.fieldIdx) : 0\n    ];\n\nexport const iconAccessor =\n  ({icon}: IconLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    dc.valueAt(d.index, icon.fieldIdx);\n\nexport const iconRequiredColumns: ['lat', 'lng', 'icon'] = ['lat', 'lng', 'icon'];\nexport const iconOptionalColumns: ['altitude'] = ['altitude'];\n\nexport const pointVisConfigs: {\n  radius: 'radius';\n  fixedRadius: 'fixedRadius';\n  opacity: 'opacity';\n  colorRange: 'colorRange';\n  radiusRange: 'radiusRange';\n  billboard: 'billboard';\n} = {\n  radius: 'radius',\n  fixedRadius: 'fixedRadius',\n  opacity: 'opacity',\n  colorRange: 'colorRange',\n  radiusRange: 'radiusRange',\n  billboard: 'billboard'\n};\n\nfunction flatterIconPositions(icon) {\n  // had to flip y, since @luma modal has changed\n  return icon.mesh.cells.reduce((prev, cell) => {\n    cell.forEach(p => {\n      prev.push(\n        ...[icon.mesh.positions[p][0], -icon.mesh.positions[p][1], icon.mesh.positions[p][2]]\n      );\n    });\n    return prev;\n  }, []);\n}\n\nexport default class IconLayer extends Layer {\n  getIconAccessor: (dataContainer: DataContainerInterface) => (d: any) => any;\n  _layerInfoModal: () => JSX.Element;\n  iconGeometry: IconGeometry;\n  iconGeometryVersion: number;\n\n  onRedrawNeeded: BindedLayerCallbacks['onRedrawNeeded'];\n\n  declare visConfigSettings: IconLayerVisConfigSettings;\n  declare config: IconLayerConfig;\n\n  constructor(\n    props: {\n      id?: string;\n      iconGeometry?: IconGeometry;\n      svgIcons?: any[];\n    } & LayerBaseConfigPartial\n  ) {\n    super(props);\n\n    this.registerVisConfig(pointVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      iconPosAccessor(this.config.columns)(dataContainer);\n    this.getIconAccessor = dataContainer => iconAccessor(this.config.columns)(dataContainer);\n\n    this._layerInfoModal = IconInfoModalFactory(props.svgIcons);\n    this.iconGeometry = props.iconGeometry || null;\n    this.iconGeometryVersion = 0;\n\n    if (isTest()) {\n      return;\n    }\n    if (props.svgIcons) {\n      // if svg icons are passed in then bypass fetching of remote svg icons\n      this.setSvgIcons(props.svgIcons);\n    } else {\n      // prepare layer info modal and fetch remote svg icons\n      this.getSvgIcons();\n    }\n  }\n\n  get svgIconUrl() {\n    return `${getApplicationConfig().cdnUrl}/icons/svg-icons.json`;\n  }\n\n  get type(): 'icon' {\n    return 'icon';\n  }\n\n  get requiredLayerColumns() {\n    return iconRequiredColumns;\n  }\n\n  get optionalColumns() {\n    return iconOptionalColumns;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get layerIcon() {\n    return IconLayerIcon;\n  }\n\n  get visualChannels() {\n    return {\n      color: {\n        ...super.visualChannels.color,\n        accessor: 'getFillColor',\n        defaultValue: config => config.color\n      },\n      size: {\n        ...super.visualChannels.size,\n        property: 'radius',\n        range: 'radiusRange',\n        channelScaleType: 'radius',\n        accessor: 'getRadius',\n        defaultValue: 1\n      }\n    };\n  }\n\n  get layerInfoModal() {\n    return {\n      id: 'iconInfo',\n      template: this._layerInfoModal,\n      modalProps: {\n        title: 'modal.iconInfo.title'\n      }\n    };\n  }\n\n  getZoomFactor({zoom, zoomOffset = 0}: {zoom: number; zoomOffset?: number}) {\n    return Math.pow(2, 14 - zoom + zoomOffset);\n  }\n\n  getSvgIcons() {\n    const fetchConfig = {\n      method: 'GET',\n      mode: 'cors',\n      cache: 'no-cache'\n    };\n\n    if (Window.fetch && this.svgIconUrl) {\n      Window.fetch(this.svgIconUrl, fetchConfig)\n        .then(response => {\n          if (!response.ok) {\n            throw new Error(`Failed to load svg-icons.json: ${response.status}`);\n          }\n          return response.json();\n        })\n        .then((parsed: {svgIcons?: any[]} = {}) => {\n          this.setSvgIcons(parsed.svgIcons);\n        })\n        .catch(err => {\n          console.error('Error fetching or parsing svg-icons.json:', err);\n          // Fallback to empty geometry to allow default icon rendering\n          this.iconGeometry = {};\n          this.iconGeometryVersion += 1;\n        });\n    } else {\n      // No fetch available; set empty geometry so layer can render default icons\n      this.iconGeometry = {};\n      this.iconGeometryVersion += 1;\n    }\n  }\n\n  setSvgIcons(svgIcons: any[] = []) {\n    this.iconGeometry = svgIcons.reduce(\n      (accu, curr) => ({\n        ...accu,\n        [curr.id]: flatterIconPositions(curr)\n      }),\n      {}\n    );\n\n    // Increment version when SVG icons are loaded to trigger layer re-render\n    this.iconGeometryVersion += 1;\n\n    this._layerInfoModal = IconInfoModalFactory(svgIcons);\n\n    // Trigger a map redraw so deck.gl picks up the new geometry\n    this.onRedrawNeeded?.();\n  }\n\n  static findDefaultLayerProps({\n    fieldPairs = [],\n    fields = []\n  }: KeplerTable): FindDefaultLayerPropsReturnValue {\n    const notFound: FindDefaultLayerPropsReturnValue = {props: []};\n    if (!fieldPairs.length || !fields.length) {\n      return notFound;\n    }\n\n    const iconFields = fields.filter(({name}) =>\n      name\n        .replace(/[_,.]+/g, ' ')\n        .trim()\n        .split(' ')\n        .some(seg => ICON_FIELDS.icon.some(t => t.includes(seg)))\n    );\n\n    if (!iconFields.length) {\n      return notFound;\n    }\n\n    // create icon layers for first point pair\n    const ptPair = fieldPairs[0];\n    const columns = assignPointPairToLayerColumn(ptPair, true);\n\n    const props = iconFields.map(iconField => ({\n      label: iconField.name.replace(/[_,.]+/g, ' ').trim(),\n      columns: {\n        ...columns,\n        icon: {\n          value: iconField.name,\n          fieldIdx: iconField.fieldIdx\n        }\n      },\n      isVisible: true\n    }));\n\n    return {props};\n  }\n\n  getFilteredItemCount() {\n    // use total\n    if (Object.keys(this.filteredItemCount).length) {\n      return Object.values(this.filteredItemCount).reduce(\n        (total, curr) => (Number.isFinite(curr) ? total + curr : total),\n        0\n      );\n    }\n\n    return null;\n  }\n\n  calculateDataAttribute({dataContainer, filteredIndex}: KeplerTable, getPosition) {\n    const getIcon = this.getIconAccessor(dataContainer);\n    const data: IconLayerData[] = [];\n\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const rowIndex = {index};\n      const pos = getPosition(rowIndex);\n      const icon = getIcon(rowIndex);\n\n      // if doesn't have point lat or lng, do not add the point\n      // deck.gl can't handle position = null\n      if (pos.every(Number.isFinite) && typeof icon === 'string') {\n        data.push({\n          index,\n          icon\n        });\n      }\n    }\n\n    return data;\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {textLabel} = this.config;\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n\n    const getPosition = this.getPositionAccessor(dataContainer);\n\n    const {data, triggerChanged} = this.updateData(datasets, oldLayerData);\n\n    // get all distinct characters in the text labels\n    const textLabels = formatTextLabelData({\n      textLabel,\n      triggerChanged,\n      oldLayerData,\n      data,\n      dataContainer\n    });\n\n    const accessors = this.getAttributeAccessors({dataContainer});\n\n    return {\n      data,\n      getPosition,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),\n      textLabels,\n      ...accessors\n    };\n  }\n\n  updateLayerMeta(dataset: KeplerTable, getPosition) {\n    const {dataContainer} = dataset;\n    const bounds = this.getPointsBounds(dataContainer, getPosition);\n    this.updateMeta({bounds});\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, objectHovered, mapState, interactionConfig, layerCallbacks} = opts;\n\n    // Store callback to trigger map redraw when icon geometry loads asynchronously\n    this.onRedrawNeeded = layerCallbacks?.onRedrawNeeded;\n\n    const radiusScale = this.getRadiusScaleByZoom(mapState);\n\n    const layerProps = {\n      radiusScale,\n      billboard: this.config.visConfig.billboard,\n      ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})\n    };\n\n    const updateTriggers = {\n      getPosition: this.config.columns,\n      getFilterValue: gpuFilter.filterValueUpdateTriggers,\n      ...this.getVisualChannelUpdateTriggers()\n    };\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const brushingProps = this.getBrushingExtensionProps(interactionConfig);\n    const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius, mapState);\n    const extensions = [...defaultLayerProps.extensions, brushingExtension];\n\n    // shared Props between layer and label layer\n    const sharedProps = {\n      getFilterValue: data.getFilterValue,\n      extensions,\n      filterRange: defaultLayerProps.filterRange,\n      ...brushingProps\n    };\n\n    const labelLayers = [\n      ...this.renderTextLabelLayer(\n        {\n          getPosition: data.getPosition,\n          sharedProps,\n          getPixelOffset,\n          updateTriggers\n        },\n        opts\n      )\n    ];\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    const parameters = {\n      // icons will be flat on the map when the altitude column is not used\n      depthTest: this.config.columns.altitude?.fieldIdx > -1,\n      cullFace: GL.FRONT\n    };\n\n    // Append geometry version to layer id so deck.gl treats it as new layer when geometry changes\n    const baseLayerId = defaultLayerProps.id || this.id;\n    const layerIdWithVersion = `${baseLayerId}_${this.iconGeometryVersion}`;\n\n    return [\n      new SvgIconLayer({\n        ...defaultLayerProps,\n        id: layerIdWithVersion,\n        ...brushingProps,\n        ...layerProps,\n        ...data,\n        parameters,\n        getIconGeometry: id => this.iconGeometry?.[id],\n\n        // update triggers\n        updateTriggers,\n        extensions\n      }),\n\n      // hover layer\n      ...(hoveredObject\n        ? [\n            // @ts-expect-error SvgIconLayerProps needs getIcon Field\n            new SvgIconLayer({\n              ...this.getDefaultHoverLayerProps(),\n              id: `${layerIdWithVersion}-hover`,\n              ...layerProps,\n              visible: defaultLayerProps.visible,\n              data: [hoveredObject],\n              parameters,\n              getPosition: data.getPosition,\n              getRadius: data.getRadius,\n              getFillColor: this.config.highlightColor,\n              getIconGeometry: id => this.iconGeometry?.[id]\n            })\n          ]\n        : []),\n\n      // text label layer\n      ...labelLayers\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {\n  default as Layer,\n  OVERLAY_TYPE_CONST,\n  LAYER_ID_LENGTH,\n  colorMaker,\n  layerColors\n} from './base-layer';\nimport {default as PointLayer} from './point-layer/point-layer';\nimport {default as ArcLayer} from './arc-layer/arc-layer';\nimport {default as LineLayer} from './line-layer/line-layer';\nimport {default as GridLayer} from './grid-layer/grid-layer';\nexport {pointToPolygonGeo} from './grid-layer/grid-utils';\nimport {default as HexagonLayer} from './hexagon-layer/hexagon-layer';\nimport {default as GeojsonLayer} from './geojson-layer/geojson-layer';\nexport {\n  defaultElevation,\n  defaultLineWidth,\n  defaultRadius,\n  COLUMN_MODE_TABLE\n} from './geojson-layer/geojson-layer';\nimport {default as ClusterLayer} from './cluster-layer/cluster-layer';\nimport {default as IconLayer} from './icon-layer/icon-layer';\nimport {default as HeatmapLayer} from './heatmap-layer/heatmap-layer';\nexport {MAX_ZOOM_LEVEL} from './heatmap-layer/heatmap-layer';\nimport {default as H3Layer} from './h3-hexagon-layer/h3-hexagon-layer';\nexport {defaultElevation as h3DefaultElevation} from './h3-hexagon-layer/h3-hexagon-layer';\nimport {default as ScenegraphLayer} from './scenegraph-layer/scenegraph-layer';\nimport {default as TripLayer} from './trip-layer/trip-layer';\nexport {defaultLineWidth as tripDefaultLineWidth} from './trip-layer/trip-layer';\nexport {\n  coordHasLength4,\n  isTripGeoJsonField,\n  parseTripGeoJsonTimestamp,\n  getAnimationDomainFromTimestamps\n} from './trip-layer/trip-utils';\nimport {default as S2GeometryLayer} from './s2-geometry-layer/s2-geometry-layer';\nexport {defaultElevation as s2DefaultElevation} from './s2-geometry-layer/s2-geometry-layer';\nexport {getS2Center} from './s2-geometry-layer/s2-utils';\nexport {default as AggregationLayer} from './aggregation-layer';\nimport {default as VectorTileLayer} from './vector-tile/vector-tile-layer';\n\nexport {default as VectorTileIcon} from './vector-tile/vector-tile-icon';\nexport {default as VectorTileLayer} from './vector-tile/vector-tile-layer';\nexport {getNumVectorTilesBeingLoaded} from './vector-tile/loading-counter';\n\nimport {default as RasterTileLayer} from './raster-tile/raster-tile-layer';\nexport {default as RasterTileIcon} from './raster-tile/raster-tile-icon';\nexport {\n  default as RasterTileLayer,\n  getNumRasterTilesBeingLoaded\n} from './raster-tile/raster-tile-layer';\nexport {\n  CATEGORICAL_COLORMAP_ID,\n  DATA_SOURCE_COLOR_DEFAULTS,\n  RASTER_COLOR_RESET_PARAMS,\n  PRESET_OPTIONS\n} from './raster-tile/config';\nexport {RasterLayerResources} from './raster-tile/url';\nexport {getCategoricalColormapDataUrl} from './raster-tile/image';\nexport * from './raster-tile/types';\nexport * from './raster-tile/raster-tile-utils';\n\nexport {default as WMSLayerIcon} from './wms-layer/wms-layer-icon';\n\nimport {LAYER_TYPES} from '@kepler.gl/constants';\nexport {parseGeoJsonRawFeature} from './geojson-layer/geojson-utils';\n// base layer\n// eslint-disable-next-line prettier/prettier\nexport type {\n  LayerBaseConfig,\n  VisualChannelDomain,\n  VisualChannel,\n  VisualChannelDescription\n} from './base-layer';\nexport * from './base-layer';\n\n// individual layers\nexport const KeplerGlLayers = {\n  PointLayer,\n  ArcLayer,\n  LineLayer,\n  GridLayer,\n  HexagonLayer,\n  GeojsonLayer,\n  ClusterLayer,\n  IconLayer,\n  HeatmapLayer,\n  H3Layer,\n  ScenegraphLayer,\n  TripLayer,\n  S2GeometryLayer,\n  VectorTileLayer,\n  RasterTileLayer,\n  WMSLayer\n};\n\nexport type LayerClassesType = typeof LayerClasses;\nexport const LayerClasses = {\n  [LAYER_TYPES.point]: PointLayer,\n  [LAYER_TYPES.arc]: ArcLayer,\n  [LAYER_TYPES.line]: LineLayer,\n  [LAYER_TYPES.grid]: GridLayer,\n  [LAYER_TYPES.hexagon]: HexagonLayer,\n  [LAYER_TYPES.geojson]: GeojsonLayer,\n  [LAYER_TYPES.cluster]: ClusterLayer,\n  [LAYER_TYPES.icon]: IconLayer,\n  [LAYER_TYPES.heatmap]: HeatmapLayer,\n  [LAYER_TYPES.hexagonId]: H3Layer,\n  [LAYER_TYPES['3D']]: ScenegraphLayer,\n  [LAYER_TYPES.trip]: TripLayer,\n  [LAYER_TYPES.s2]: S2GeometryLayer,\n  [LAYER_TYPES['vectorTile']]: VectorTileLayer,\n  [LAYER_TYPES['rasterTile']]: RasterTileLayer,\n  [LAYER_TYPES.wms]: WMSLayer\n};\n\nexport * from './mapbox-utils';\nexport * from './h3-hexagon-layer';\nexport {default as Table} from './table';\n\nexport {getEditorLayer} from './editor-layer/editor-layer';\n\nexport {\n  default as ScenegraphLayer,\n  scenegraphVisConfigs\n} from './scenegraph-layer/scenegraph-layer';\nexport {default as ScenegraphLayerIcon} from './scenegraph-layer/scenegraph-layer-icon';\n\nimport {\n  isDrawingActive,\n  onClick,\n  onHover,\n  getTooltip,\n  getCursor\n} from './editor-layer/editor-layer-utils';\nimport WMSLayer from './wms-layer/wms-layer';\nexport const EditorLayerUtils = {\n  isDrawingActive,\n  onClick,\n  onHover,\n  getTooltip,\n  getCursor\n};\n\nexport {getFilterDataFunc} from './aggregation-layer';\n\nexport * from './layer-update';\nexport * from './layer-utils';\n"
  },
  {
    "path": "src/layers/src/layer-text-label.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport {getDistanceScales} from 'viewport-mercator-project';\nimport {DataContainerInterface, ArrowDataContainer, isArrowTable} from '@kepler.gl/utils';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport uniq from 'lodash/uniq';\n\nexport const defaultPadding = 20;\n\nexport function getTextOffsetByRadius(radiusScale, getRadius, mapState) {\n  return textLabel => {\n    const distanceScale = getDistanceScales(mapState);\n    const xMult = textLabel.anchor === 'middle' ? 0 : textLabel.anchor === 'start' ? 1 : -1;\n    const yMult = textLabel.alignment === 'center' ? 0 : textLabel.alignment === 'bottom' ? 1 : -1;\n\n    const sizeOffset =\n      textLabel.alignment === 'center'\n        ? 0\n        : textLabel.alignment === 'bottom'\n        ? textLabel.size\n        : textLabel.size;\n\n    const pixelRadius = radiusScale * distanceScale.pixelsPerMeter[0];\n    const padding = defaultPadding;\n\n    return typeof getRadius === 'function'\n      ? d => [\n          xMult * (getRadius(d) * pixelRadius + padding),\n          yMult * (getRadius(d) * pixelRadius + padding + sizeOffset)\n        ]\n      : [\n          xMult * (getRadius * pixelRadius + padding),\n          yMult * (getRadius * pixelRadius + padding + sizeOffset)\n        ];\n  };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport const textLabelAccessor = textLabel => dc => d => {\n  const val = textLabel.field.valueAccessor(d);\n  return notNullorUndefined(val) ? String(val) : '';\n};\n\nexport const formatTextLabelData = ({\n  textLabel,\n  triggerChanged,\n  oldLayerData,\n  data,\n  dataContainer,\n  filteredIndex\n}: {\n  textLabel: any;\n  triggerChanged?: boolean | {[key: string]: boolean};\n  oldLayerData: any;\n  data: any;\n  dataContainer: DataContainerInterface;\n  filteredIndex?: Uint8ClampedArray | null;\n}) => {\n  return textLabel.map((tl, i) => {\n    if (!tl.field) {\n      // if no field selected,\n      return {\n        getText: null,\n        characterSet: []\n      };\n    }\n\n    const getTextAccessor: (d: {index: number}) => string = textLabelAccessor(tl)(dataContainer);\n    let characterSet;\n    let getText: typeof getTextAccessor | arrow.Vector = getTextAccessor;\n\n    let rebuildArrowTextVector = true;\n    if (\n      !triggerChanged?.[`getLabelCharacterSet-${i}`] &&\n      oldLayerData &&\n      oldLayerData.textLabels &&\n      oldLayerData.textLabels[i]\n    ) {\n      characterSet = oldLayerData.textLabels[i].characterSet;\n      getText = oldLayerData.textLabels[i].getText;\n      rebuildArrowTextVector = false;\n    } else {\n      if (isArrowTable(data)) {\n        // we don't filter out arrow tables,\n        // so we use filteredIndex array instead\n        const allLabels: string[] = [];\n        if (tl.field) {\n          if (filteredIndex) {\n            filteredIndex.forEach((value, index) => {\n              if (value > 0) allLabels.push(getTextAccessor({index}));\n            });\n          } else {\n            for (let index = 0; index < dataContainer.numRows(); ++index) {\n              allLabels.push(getTextAccessor({index}));\n            }\n          }\n        }\n        characterSet = uniq(allLabels.join(''));\n      } else {\n        const allLabels = tl.field ? data.map(getTextAccessor) : [];\n        characterSet = uniq(allLabels.join(''));\n      }\n    }\n\n    // For Arrow Layers getText has to be an arrow vector.\n    // For now check here for ArrowTable, not ArrowDataContainer.\n    if (\n      rebuildArrowTextVector &&\n      isArrowTable(data) &&\n      dataContainer instanceof ArrowDataContainer\n    ) {\n      getText = dataContainer.getColumn(tl.field.fieldIdx);\n      try {\n        getText = getArrowTextVector(getText as arrow.Vector, getTextAccessor);\n      } catch (error) {\n        // empty text labels\n        getText = getArrowTextVector(getText, () => ' ');\n      }\n    }\n\n    return {\n      characterSet,\n      getText\n    };\n  });\n};\n\n/**\n * Get an arrow vector suitable to render text labels with arrow layers.\n * @param getText A candidate arrow vector to use for text labels.\n * @param getTextAccessor Text label accessor.\n */\nexport const getArrowTextVector = (\n  candidateTextVector: arrow.Vector,\n  getTextAccessor: ({index}: {index: number}) => string\n): arrow.Vector => {\n  // if the passed vector is suitable for text labels\n  if (arrow.DataType.isUtf8(candidateTextVector?.type)) {\n    return candidateTextVector;\n  }\n\n  // create utf8 vector from source vector with the same number of batches.\n  // @ts-expect-error\n  const offsets = candidateTextVector._offsets;\n  const numOffsets = offsets.length;\n  const batchVectors: arrow.Vector[] = [];\n  const datum = {index: 0};\n  for (let batchIndex = 0; batchIndex < numOffsets - 1; batchIndex++) {\n    const batchStart = offsets[batchIndex];\n    const batchEnd = offsets[batchIndex + 1];\n\n    const batchLabels: string[] = [];\n    for (let rowIndex = batchStart; rowIndex < batchEnd; ++rowIndex) {\n      datum.index = rowIndex;\n      batchLabels.push(getTextAccessor(datum));\n    }\n\n    batchVectors.push(arrow.vectorFromArray(batchLabels, new arrow.Utf8()));\n  }\n\n  const input = batchVectors.flatMap(x => x.data).flat(Number.POSITIVE_INFINITY);\n\n  return new arrow.Vector(input);\n};\n"
  },
  {
    "path": "src/layers/src/layer-update.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable guard-for-in */\n\n/**\n *\n * @param {Object} updateTriggers {getPosition: {column}, getData: {filteredIndex}}\n * @param {Object} oldUpdateTriggers\n * @returns {boolean|object} `false` if nothing changed, or `triggerChanged` as an object\n */\nexport function diffUpdateTriggers(updateTriggers, oldUpdateTriggers = {}) {\n  const triggerChanged: {[key: string]: true} = {};\n  let reason: boolean | {[key: string]: true} = false;\n\n  for (const triggerName in updateTriggers) {\n    const newTriggers = updateTriggers[triggerName] || {};\n    const oldTriggers = oldUpdateTriggers[triggerName] || {};\n    const diffReason = compareUpdateTrigger(newTriggers, oldTriggers, triggerName);\n\n    if (diffReason) {\n      triggerChanged[triggerName] = true;\n      reason = triggerChanged;\n    }\n  }\n\n  return reason;\n}\n\nfunction compareUpdateTrigger(newTriggers, oldTriggers, triggerName) {\n  if (typeof oldTriggers !== 'object') {\n    return oldTriggers === newTriggers ? null : `${triggerName} changed shallowly`;\n  }\n\n  for (const key in oldTriggers) {\n    if (!(key in newTriggers)) {\n      return `${triggerName}.${key} deleted`;\n    }\n\n    // shallow compare\n    if (oldTriggers[key] !== newTriggers[key]) {\n      return `${triggerName}.${key} changed shallowly`;\n    }\n  }\n\n  for (const key in newTriggers) {\n    if (!(key in oldTriggers)) {\n      return `${triggerName}.${key} added`;\n    }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "src/layers/src/layer-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport {Feature, BBox} from 'geojson';\nimport {getGeoMetadata} from '@loaders.gl/gis';\n\nimport {GEOARROW_EXTENSIONS, GEOARROW_METADATA_KEY} from '@kepler.gl/constants';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {\n  Field,\n  ProtoDatasetField,\n  FieldPair,\n  SupportedColumnMode,\n  LayerColumn,\n  LayerColumns,\n  RGBColor\n} from '@kepler.gl/types';\nimport {DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils';\nimport {\n  getBinaryGeometriesFromArrow,\n  parseGeometryFromArrow,\n  BinaryGeometriesFromArrowOptions,\n  updateBoundsFromGeoArrowSamples\n} from '@loaders.gl/arrow';\n\nimport {WKBLoader} from '@loaders.gl/wkt';\nimport {geojsonToBinary} from '@loaders.gl/gis';\nimport {\n  BinaryFeatureCollection,\n  Geometry,\n  BinaryPointFeature,\n  BinaryLineFeature,\n  BinaryPolygonFeature\n} from '@loaders.gl/schema';\n\nimport {DeckGlGeoTypes, GeojsonDataMaps} from './geojson-layer/geojson-utils';\n\nexport type FindDefaultLayerProps = {\n  label: string;\n  color?: RGBColor;\n  isVisible?: boolean;\n  columns?: Record<string, LayerColumn>;\n};\n\nexport type FindDefaultLayerPropsReturnValue = {\n  /** Layer props to create layers by default when a dataset is added */\n  props: FindDefaultLayerProps[];\n  /** layer props of possible alternative layer configurations, not created by default */\n  altProps?: FindDefaultLayerProps[];\n  /** Already found layer configurations */\n  foundLayers?: (FindDefaultLayerProps & {type: string})[];\n};\n\nexport function assignPointPairToLayerColumn(\n  pair: FieldPair,\n  hasAlt: boolean\n): Record<string, LayerColumn> {\n  const {lat, lng, altitude} = pair.pair;\n  if (!hasAlt) {\n    return {lat, lng};\n  }\n\n  const defaultAltColumn = {value: null, fieldIdx: -1, optional: true};\n\n  return {\n    lat,\n    lng,\n    altitude: altitude ? {...defaultAltColumn, ...altitude} : defaultAltColumn\n  };\n}\n\nexport type GeojsonLayerMetaProps = {\n  dataToFeature: GeojsonDataMaps;\n  featureTypes: DeckGlGeoTypes;\n  bounds: BBox | null;\n  fixedRadius: boolean;\n  centroids?: Array<number[] | null>;\n};\n\n/**\n * Converts a geoarrow.wkb vector into an array of BinaryFeatureCollections.\n * @param geoColumn A vector column with geoarrow.wkb extension.\n * @param options Options for geometry transformation.\n * @returns\n */\nfunction getBinaryGeometriesFromWKBArrow(\n  geoColumn: arrow.Vector,\n  options: {chunkIndex?: number; chunkOffset?: number}\n): GeojsonLayerMetaProps {\n  const dataToFeature: BinaryFeatureCollection[] = [];\n  const featureTypes: GeojsonLayerMetaProps['featureTypes'] = {\n    point: false,\n    line: false,\n    polygon: false\n  };\n\n  const chunks =\n    options?.chunkIndex !== undefined && options?.chunkIndex >= 0\n      ? [geoColumn.data[options?.chunkIndex]]\n      : geoColumn.data;\n  const globalFeatureIdOffset = options?.chunkOffset || 0;\n  let featureIndex = globalFeatureIdOffset;\n  let bounds: [number, number, number, number] = [Infinity, Infinity, -Infinity, -Infinity];\n\n  chunks.forEach(chunk => {\n    const geojsonFeatures: Feature[] = [];\n    for (let i = 0; i < chunk.length; ++i) {\n      // ignore features without any geometry\n      if (chunk.valueOffsets[i + 1] - chunk.valueOffsets[i] > 0) {\n        const valuesSlice = chunk.values.slice(chunk.valueOffsets[i], chunk.valueOffsets[i + 1]);\n\n        const geometry = WKBLoader?.parseSync?.(valuesSlice.buffer, {\n          wkb: {shape: 'geojson-geometry'}\n        }) as Geometry;\n        const feature: Feature = {\n          type: 'Feature',\n          geometry,\n          properties: {index: featureIndex}\n        };\n        geojsonFeatures.push(feature);\n\n        const {type} = geometry;\n        featureTypes.polygon = type === 'Polygon' || type === 'MultiPolygon';\n        featureTypes.point = type === 'Point' || type === 'MultiPoint';\n        featureTypes.line = type === 'LineString' || type === 'MultiLineString';\n      }\n\n      featureIndex++;\n    }\n\n    const geojsonToBinaryOptions = {\n      triangulate: true,\n      fixRingWinding: true\n    };\n    const binaryFeatures = geojsonToBinary(geojsonFeatures, geojsonToBinaryOptions);\n\n    // Need to update globalFeatureIds, to take into account previous batches,\n    // as geojsonToBinary doesn't have such option.\n    const featureTypesArr = ['points', 'lines', 'polygons'];\n    featureTypesArr.forEach(prop => {\n      const features = binaryFeatures[prop] as\n        | BinaryPointFeature\n        | BinaryLineFeature\n        | BinaryPolygonFeature;\n      if (features) {\n        bounds = updateBoundsFromGeoArrowSamples(\n          features.positions.value as Float64Array,\n          features.positions.size,\n          bounds\n        );\n\n        const {globalFeatureIds, numericProps} = features;\n        const {index} = numericProps;\n        const len = globalFeatureIds.value.length;\n        for (let i = 0; i < len; ++i) {\n          globalFeatureIds.value[i] = index.value[i];\n        }\n      }\n    });\n\n    dataToFeature.push(binaryFeatures);\n  });\n\n  return {\n    dataToFeature: dataToFeature,\n    featureTypes: featureTypes,\n    bounds,\n    fixedRadius: false\n  };\n}\n\nexport function getGeojsonLayerMetaFromArrow({\n  geoColumn,\n  geoField,\n  chunkIndex\n}: {\n  dataContainer: DataContainerInterface;\n  geoColumn: arrow.Vector;\n  geoField: ProtoDatasetField;\n  chunkIndex?: number;\n}): GeojsonLayerMetaProps {\n  const encoding = geoField?.metadata?.get(GEOARROW_METADATA_KEY);\n  const options: BinaryGeometriesFromArrowOptions = {\n    ...(chunkIndex !== undefined && chunkIndex >= 0\n      ? {\n          chunkIndex,\n          chunkOffset: geoColumn.data[0].length * chunkIndex\n        }\n      : {}),\n    triangulate: true,\n    calculateMeanCenters: true\n  };\n\n  // getBinaryGeometriesFromArrow doesn't support geoarrow.wkb\n  if (encoding === GEOARROW_EXTENSIONS.WKB) {\n    return getBinaryGeometriesFromWKBArrow(geoColumn, options);\n  }\n\n  // create binary data from arrow data for GeoJsonLayer\n  const {binaryGeometries, featureTypes, bounds, meanCenters} = getBinaryGeometriesFromArrow(\n    // @ts-ignore\n    geoColumn,\n    encoding,\n    options\n  );\n\n  // since there is no feature.properties.radius, we set fixedRadius to false\n  const fixedRadius = false;\n\n  return {\n    dataToFeature: binaryGeometries,\n    featureTypes,\n    bounds,\n    fixedRadius,\n    centroids: meanCenters\n  };\n}\n\nexport function isLayerHoveredFromArrow(objectInfo, layerId: string): boolean {\n  // there could be multiple deck.gl layers created from multiple chunks in arrow table\n  // the objectInfo.layer id should be `${this.id}-${i}`\n  if (objectInfo?.picked) {\n    const deckLayerId = objectInfo?.layer?.props?.id;\n    return deckLayerId.startsWith(layerId);\n  }\n  return false;\n}\n\nexport function getHoveredObjectFromArrow(\n  objectInfo,\n  dataContainer,\n  layerId,\n  columnAccessor,\n  fieldAccessor\n): Feature | null {\n  // hover object returns the index of the object in the data array\n  // NOTE: this could be done in Deck.gl getPickingInfo(params) and binaryToGeojson()\n  if (isLayerHoveredFromArrow(objectInfo, layerId) && objectInfo.index >= 0 && dataContainer) {\n    const col = columnAccessor(dataContainer);\n    const rawGeometry = col?.get(objectInfo.index);\n\n    const field = fieldAccessor(dataContainer);\n    const encoding = field?.metadata?.get(GEOARROW_METADATA_KEY);\n\n    const hoveredFeature = parseGeometryFromArrow(rawGeometry, encoding);\n\n    const properties = dataContainer.rowAsArray(objectInfo.index).reduce((prev, cur, i) => {\n      const fieldName = dataContainer?.getField?.(i).name;\n      if (fieldName !== field.name) {\n        prev[fieldName] = cur;\n      }\n      return prev;\n    }, {});\n\n    return hoveredFeature\n      ? {\n          type: 'Feature',\n          geometry: hoveredFeature,\n          properties: {\n            ...properties,\n            index: objectInfo.index\n          }\n        }\n      : null;\n  }\n  return null;\n}\n\n/**\n * find requiredColumns of supported column mode based on column mode\n */\nexport function getColumnModeRequiredColumns(\n  supportedColumnModes: SupportedColumnMode[] | null,\n  columnMode?: string\n): string[] | undefined {\n  return supportedColumnModes?.find(({key}) => key === columnMode)?.requiredColumns;\n}\n\n/**\n * Returns geoarrow fields with ARROW:extension:name POINT metadata\n * @param fields Any fields\n * @returns geoarrow fields with ARROW:extension:name POINT metadata\n */\nexport function getGeoArrowPointFields(fields: Field[]): Field[] {\n  return fields.filter(field => {\n    return (\n      field.type === 'geoarrow' &&\n      field.metadata?.get(GEOARROW_METADATA_KEY) === GEOARROW_EXTENSIONS.POINT\n    );\n  });\n}\n\n/**\n * Builds an arrow vector compatible with ARROW:extension:name geoarrow.point.\n * @param getPosition Position accessor.\n * @param numElements Number of elements in the vector.\n * @returns An arrow vector compatible with ARROW:extension:name geoarrow.point.\n */\nexport function createGeoArrowPointVector(\n  dataContainer: ArrowDataContainer,\n  getPosition: ({index}: {index: number}) => number[]\n): arrow.Vector {\n  // TODO update/resize existing vector?\n  // TODO find an easier way to create point geo columns\n  // in a correct arrow format, as this approach seems too excessive for just a simple interleaved buffer.\n\n  const numElements = dataContainer.numRows();\n  const table = dataContainer.getTable();\n\n  const numCoords = numElements > 0 ? getPosition({index: 0}).length : 2;\n  const precision = 2;\n\n  const metadata = new Map();\n  metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.POINT);\n\n  const childField = new arrow.Field('xyz', new arrow.Float(precision), false, metadata);\n  const fixedSizeList = new arrow.FixedSizeList(numCoords, childField);\n  const floatBuilder = new arrow.FloatBuilder({type: new arrow.Float(precision)});\n  const fixedSizeListBuilder = new arrow.FixedSizeListBuilder({type: fixedSizeList});\n  fixedSizeListBuilder.addChild(floatBuilder);\n\n  const assembledBatches: arrow.Data[] = [];\n  const indexData = {index: 0};\n  for (let batchIndex = 0; batchIndex < table.batches.length; ++batchIndex) {\n    const numRowsInBatch = table.batches[batchIndex].numRows;\n\n    for (let i = 0; i < numRowsInBatch; ++i) {\n      const pos = getPosition(indexData);\n      fixedSizeListBuilder.append(pos);\n\n      ++indexData.index;\n    }\n    assembledBatches.push(fixedSizeListBuilder.flush());\n  }\n\n  return arrow.makeVector(assembledBatches);\n}\n\n/**\n * Builds a filtered index suitable for FilterArrowExtension.\n * @param numElements Size for filtered index array.\n * @param visibleIndices An array with indices of elements that aren't filtered out.\n * @returns filteredIndex [0|1] array for GPU filtering\n */\nexport function getFilteredIndex(\n  numElements: number,\n  visibleIndices: number[],\n  existingFilteredIndex: Uint8ClampedArray | null\n) {\n  // contents are initialized with zeros by default, meaning not visible\n  const filteredIndex =\n    existingFilteredIndex && existingFilteredIndex.length === numElements\n      ? existingFilteredIndex\n      : new Uint8ClampedArray(numElements);\n  filteredIndex.fill(0);\n\n  if (visibleIndices) {\n    for (let i = 0; i < visibleIndices.length; ++i) {\n      filteredIndex[visibleIndices[i]] = 1;\n    }\n  }\n  return filteredIndex;\n}\n\n/**\n * Returns an array of neighbors to the specified index.\n * @param neighborsField LayerColumn field with information about neighbors.\n * @param dataContainer Data container.\n * @param index Index of interest.\n * @param getPosition Position accessor.\n * @returns An array with information about neighbors.\n */\nexport function getNeighbors(\n  neighborsField: LayerColumn | undefined,\n  dataContainer: DataContainerInterface,\n  index: number,\n  getPosition: ({index}: {index: number}) => number[]\n): {index: number; position: number[]}[] {\n  if (!neighborsField || neighborsField.fieldIdx < 0) return [];\n\n  let neighborIndices = dataContainer.valueAt(index, neighborsField.fieldIdx);\n  // In case of arrow column with an array of indices.\n  if (neighborIndices.toArray) {\n    neighborIndices = Array.from(neighborIndices.toArray());\n  }\n  if (!Array.isArray(neighborIndices)) return [];\n\n  // find neighbor\n  const neighborsData = neighborIndices.map(idx => ({\n    index: idx,\n    position: getPosition({index: idx})\n  }));\n\n  return neighborsData;\n}\n\n/**\n * Returns bounds from a geoarrow field.\n * TODO: refactor once metadata extraction from parquet to arrow vectors is in place.\n * @param layerColumn Layer columns for which to check for a bounding box.\n * @param dataContainer Data container with geoarrow metadata.\n * @returns Returns bounding box if exists.\n */\nexport function getBoundsFromArrowMetadata(\n  layerColumn: LayerColumn,\n  dataContainer: ArrowDataContainer\n): [number, number, number, number] | false {\n  try {\n    const field = dataContainer.getField(layerColumn.fieldIdx);\n    const table = dataContainer.getTable();\n\n    const geoMetadata = getGeoMetadata({\n      metadata: {\n        // @ts-expect-error\n        geo: table.schema.metadata.get('geo')\n      }\n    });\n\n    if (geoMetadata) {\n      const fieldMetadata = geoMetadata.columns[field.name];\n      if (fieldMetadata) {\n        const boundsFromMetadata = fieldMetadata['bbox'];\n        if (Array.isArray(boundsFromMetadata) && boundsFromMetadata.length === 4) {\n          return boundsFromMetadata;\n        }\n      }\n    }\n  } catch (error) {\n    // ignore for now\n  }\n\n  return false;\n}\n\n/**\n * Finds and returns the first satisfied column mode based on the provided columns and fields.\n * @param supportedColumnModes - An array of supported column modes to check.\n * @param columns - The available columns.\n * @param fields - Optional table fields to be used for extra verification.\n * @returns The first column mode that satisfies the required conditions, or undefined if none match.\n */\nexport function getSatisfiedColumnMode(\n  columnModes: SupportedColumnMode[] | null,\n  columns: LayerColumns | undefined,\n  fields?: KeplerTable['fields']\n): SupportedColumnMode | undefined {\n  return columnModes?.find(mode => {\n    return mode.requiredColumns?.every(requriedCol => {\n      const column = columns?.[requriedCol];\n      if (column?.value) {\n        if (mode.verifyField && fields?.[column.fieldIdx]) {\n          const field = fields[column.fieldIdx];\n          return mode.verifyField(field);\n        }\n        return true;\n      }\n      return false;\n    });\n  });\n}\n\n/**\n * Returns true if the field is of geoarrow point format.\n * @param field A field.\n * @returns Returns true if the field is of geoarrow point format.\n */\nexport function isGeoArrowPointField(field: Field) {\n  return (\n    field.type === 'geoarrow' &&\n    field.metadata?.get(GEOARROW_METADATA_KEY) === GEOARROW_EXTENSIONS.POINT\n  );\n}\n\n/**\n * Create default geoarrow column props based on the dataset.\n * @param dataset A dataset to create layer props from.\n * @returns  geoarrow column props.\n */\nexport function getGeoArrowPointLayerProps(dataset: KeplerTable) {\n  const {label} = dataset;\n  const altProps: FindDefaultLayerProps[] = [];\n  dataset.fields.forEach(field => {\n    if (isGeoArrowPointField(field)) {\n      altProps.push({\n        label: (typeof label === 'string' && label.replace(/\\.[^/.]+$/, '')) || field.name,\n        columns: {geoarrow: {value: field.name, fieldIdx: field.fieldIdx}}\n      });\n    }\n  });\n  return altProps;\n}\n"
  },
  {
    "path": "src/layers/src/line-layer/line-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class LineLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'line-layer-icon',\n    totalColor: 5\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path\n          d=\"M57.8,58.3c-0.4,0-0.8-0.2-1.1-0.5L33.1,32.1c-0.6-0.6-0.5-1.6,0.1-2.1c0.6-0.6,1.6-0.5,2.1,0.1l23.7,25.8\n          c0.6,0.6,0.5,1.6-0.1,2.1C58.5,58.2,58.2,58.3,57.8,58.3z\"\n          className=\"cr1\"\n        />\n        <path\n          d=\"M34.2,33.6c-0.6,0-1.2-0.2-1.7-0.6c-1-0.9-1.1-2.5-0.2-3.5l18.5-21c0.9-1,2.5-1.1,3.5-0.2c1,0.9,1.1,2.5,0.2,3.5L36,32.7\n          C35.5,33.3,34.9,33.6,34.2,33.6z\"\n          className=\"cr2\"\n        />\n        <path\n          d=\"M34.2,32.6c-0.5,0-1-0.3-1.3-0.8L20.7,10.2c-0.4-0.7-0.1-1.6,0.6-2c0.7-0.4,1.6-0.1,2,0.6l12.1,21.6c0.4,0.7,0.1,1.6-0.6,2\n          C34.7,32.5,34.4,32.6,34.2,32.6z\"\n          className=\"cr3\"\n        />\n        <path\n          d=\"M15.8,58.4c-0.3,0-0.6-0.1-0.9-0.3c-0.7-0.5-0.8-1.4-0.4-2.1l18.3-25.8c0.5-0.7,1.4-0.8,2.1-0.4s0.8,1.4,0.4,2.1L17.1,57.7\n          C16.8,58.2,16.3,58.4,15.8,58.4z\"\n          className=\"cr4\"\n        />\n        <path\n          d=\"M34.2,32.1c-0.1,0-0.3,0-0.4-0.1l-28.5-14c-0.5-0.2-0.7-0.8-0.5-1.3c0.2-0.5,0.8-0.7,1.3-0.5l28.5,14\n          c0.5,0.2,0.7,0.8,0.5,1.3C34.9,31.9,34.5,32.1,34.2,32.1z\"\n          className=\"cr5\"\n        />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/line-layer/line-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\n\nimport {BrushingExtension} from '@deck.gl/extensions';\n\nimport {GeoArrowArcLayer} from '@kepler.gl/deckgl-arrow-layers';\nimport {FilterArrowExtension} from '@kepler.gl/deckgl-layers';\nimport {EnhancedLineLayer} from '@kepler.gl/deckgl-layers';\nimport LineLayerIcon from './line-layer-icon';\nimport {VisualChannel} from '../base-layer';\nimport ArcLayer, {ArcLayerConfig} from '../arc-layer/arc-layer';\nimport {LAYER_VIS_CONFIGS, PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\nimport {\n  ColorRange,\n  Merge,\n  RGBColor,\n  VisConfigColorRange,\n  VisConfigColorSelect,\n  VisConfigNumber,\n  VisConfigRange,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport {DataContainerInterface, maybeHexToGeo} from '@kepler.gl/utils';\n\nexport type LineLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  thickness: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  sizeRange: VisConfigRange;\n  targetColor: VisConfigColorSelect;\n  elevationScale: VisConfigNumber;\n};\n\nexport type LineLayerColumnsConfig = {\n  // COLUMN_MODE_POINTS required columns\n  lat0: LayerColumn;\n  lng0: LayerColumn;\n  lat1: LayerColumn;\n  lng1: LayerColumn;\n  alt0?: LayerColumn;\n  alt1?: LayerColumn;\n\n  // COLUMN_MODE_NEIGHBORS required columns\n  lat: LayerColumn;\n  lng: LayerColumn;\n  alt: LayerColumn;\n  neighbors: LayerColumn;\n\n  // COLUMN_MODE_GEOARROW\n  geoarrow0: LayerColumn;\n  geoarrow1: LayerColumn;\n};\n\nexport type LineLayerVisConfig = {\n  colorRange: ColorRange;\n  opacity: number;\n  sizeRange: [number, number];\n  targetColor: RGBColor;\n  thickness: number;\n  elevationScale: number;\n};\n\nexport type LineLayerConfig = Merge<\n  ArcLayerConfig,\n  {columns: LineLayerColumnsConfig; visConfig: LineLayerVisConfig}\n>;\n\nexport const lineRequiredColumns: ['lat0', 'lng0', 'lat1', 'lng1'] = [\n  'lat0',\n  'lng0',\n  'lat1',\n  'lng1'\n];\nexport const lineOptionalColumns: ['alt0', 'alt1'] = ['alt0', 'alt1'];\nexport const neighborRequiredColumns = ['lat', 'lng', 'neighbors'];\nexport const neighborOptionalColumns = ['alt'];\nexport const geoarrowRequiredColumns = ['geoarrow0', 'geoarrow1'];\n\nexport const lineColumnLabels = {\n  lat0: 'arc.lat0',\n  lng0: 'arc.lng0',\n  lat1: 'arc.lat1',\n  lng1: 'arc.lng1',\n  alt0: 'line.alt0',\n  alt1: 'line.alt1'\n};\n\nexport const lineVisConfigs: {\n  opacity: 'opacity';\n  thickness: 'thickness';\n  colorRange: 'colorRange';\n  sizeRange: 'strokeWidthRange';\n  targetColor: 'targetColor';\n  elevationScale: VisConfigNumber;\n} = {\n  opacity: 'opacity',\n  thickness: 'thickness',\n  colorRange: 'colorRange',\n  sizeRange: 'strokeWidthRange',\n  targetColor: 'targetColor',\n  elevationScale: {\n    ...LAYER_VIS_CONFIGS.elevationScale,\n    defaultValue: 1\n  }\n};\n\nexport const COLUMN_MODE_POINTS = 'points';\nexport const COLUMN_MODE_NEIGHBORS = 'neighbors';\nexport const COLUMN_MODE_GEOARROW = 'geoarrow';\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_POINTS,\n    label: 'Points',\n    requiredColumns: lineRequiredColumns,\n    optionalColumns: lineOptionalColumns\n  },\n  {\n    key: COLUMN_MODE_NEIGHBORS,\n    label: 'Point and Neighbors',\n    requiredColumns: neighborRequiredColumns,\n    optionalColumns: neighborOptionalColumns\n  },\n  {\n    key: COLUMN_MODE_GEOARROW,\n    label: 'Geoarrow Points',\n    requiredColumns: geoarrowRequiredColumns\n  }\n];\n\nconst brushingExtension = new BrushingExtension();\nconst arrowCPUFilterExtension = new FilterArrowExtension();\n\nexport const linePosAccessor =\n  (\n    {\n      lat0,\n      lng0,\n      lat1,\n      lng1,\n      alt0,\n      alt1,\n      lat,\n      lng,\n      alt,\n      geoarrow0,\n      geoarrow1\n    }: LineLayerColumnsConfig,\n    columnMode\n  ) =>\n  (dc: DataContainerInterface) => {\n    switch (columnMode) {\n      case COLUMN_MODE_GEOARROW:\n        return d => {\n          const start = dc.valueAt(d.index, geoarrow0.fieldIdx);\n          const end = dc.valueAt(d.index, geoarrow1.fieldIdx);\n          return [start.get(0), start.get(1), 0, end.get(2), end.get(3), 0];\n        };\n      case COLUMN_MODE_NEIGHBORS:\n        return d => {\n          const startPos = maybeHexToGeo(dc, d, lat, lng);\n          // only return source point if columnMode is COLUMN_MODE_NEIGHBORS\n\n          return [\n            startPos ? startPos[0] : dc.valueAt(d.index, lng.fieldIdx),\n            startPos ? startPos[1] : dc.valueAt(d.index, lat.fieldIdx),\n            alt?.fieldIdx > -1 ? dc.valueAt(d.index, alt.fieldIdx) : 0\n          ];\n        };\n      default:\n        // COLUMN_MODE_POINTS\n        return d => {\n          // lat or lng column could be hex column\n          // we assume string value is hex and try to convert it to geo lat lng\n          const startPos = maybeHexToGeo(dc, d, lat0, lng0);\n          const endPos = maybeHexToGeo(dc, d, lat1, lng1);\n          return [\n            startPos ? startPos[0] : dc.valueAt(d.index, lng0.fieldIdx),\n            startPos ? startPos[1] : dc.valueAt(d.index, lat0.fieldIdx),\n            alt0 && alt0.fieldIdx > -1 ? dc.valueAt(d.index, alt0.fieldIdx) : 0,\n            endPos ? endPos[0] : dc.valueAt(d.index, lng1.fieldIdx),\n            endPos ? endPos[1] : dc.valueAt(d.index, lat1.fieldIdx),\n            alt1 && alt1?.fieldIdx > -1 ? dc.valueAt(d.index, alt1.fieldIdx) : 0\n          ];\n        };\n    }\n  };\n\nexport default class LineLayer extends ArcLayer {\n  declare visConfigSettings: LineLayerVisConfigSettings;\n  declare config: LineLayerConfig;\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(lineVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      linePosAccessor(this.config.columns, this.config.columnMode)(dataContainer);\n  }\n\n  get type() {\n    return 'line';\n  }\n\n  get layerIcon() {\n    return LineLayerIcon;\n  }\n\n  get columnLabels() {\n    return lineColumnLabels;\n  }\n\n  get columnPairs() {\n    return this.defaultLinkColumnPairs;\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  get visualChannels() {\n    const visualChannels = super.visualChannels;\n    return {\n      ...visualChannels,\n      sourceColor: {\n        ...visualChannels.sourceColor,\n        accessor: 'getColor'\n      }\n    };\n  }\n\n  static findDefaultLayerProps({fieldPairs = []}: KeplerTable): {\n    props: {color?: RGBColor; columns: LineLayerColumnsConfig; label: string}[];\n  } {\n    if (fieldPairs.length < 2) {\n      return {props: []};\n    }\n\n    const defaultAltColumn = {value: null, fieldIdx: -1, optional: true};\n    const props: {columns: LineLayerColumnsConfig; label: string; isVisible: boolean} = {\n      // connect the first two point layer with line\n      // TODO: fill default columns by parsing supported_column_modes\n      columns: {\n        lat0: fieldPairs[0].pair.lat,\n        lng0: fieldPairs[0].pair.lng,\n        alt0: fieldPairs[0].pair.altitude\n          ? {...defaultAltColumn, ...fieldPairs[0].pair.altitude}\n          : {...defaultAltColumn},\n        lat1: fieldPairs[1].pair.lat,\n        lng1: fieldPairs[1].pair.lng,\n        alt1: fieldPairs[1].pair.altitude\n          ? {...defaultAltColumn, ...fieldPairs[1].pair.altitude}\n          : {...defaultAltColumn},\n        lat: {...defaultAltColumn},\n        lng: {...defaultAltColumn},\n        alt: {...defaultAltColumn},\n        neighbors: {...defaultAltColumn},\n        geoarrow0: {...defaultAltColumn},\n        geoarrow1: {...defaultAltColumn}\n      },\n      label: `${fieldPairs[0].defaultName} -> ${fieldPairs[1].defaultName} line`,\n      isVisible: false\n    };\n\n    return {props: [props]};\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, objectHovered, interactionConfig, dataset} = opts;\n\n    const layerProps = {\n      widthScale: this.config.visConfig.thickness * PROJECTED_PIXEL_SIZE_MULTIPLIER,\n      elevationScale: this.config.visConfig.elevationScale\n    };\n\n    const updateTriggers = {\n      getPosition: this.config.columns,\n      getFilterValue: gpuFilter.filterValueUpdateTriggers,\n      getFiltered: this.filteredIndexTrigger,\n      ...this.getVisualChannelUpdateTriggers()\n    };\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n\n    const useArrowLayer = Boolean(this.geoArrowVector0);\n\n    let LineLayerClass: typeof EnhancedLineLayer | typeof GeoArrowArcLayer = EnhancedLineLayer;\n    let experimentalPropOverrides: {\n      data?: arrow.Table;\n      getSourcePosition?: arrow.Vector;\n      getTargetPosition?: arrow.Vector;\n    } = {};\n\n    if (useArrowLayer) {\n      LineLayerClass = GeoArrowArcLayer;\n      experimentalPropOverrides = {\n        data: dataset.dataContainer.getTable(),\n        getSourcePosition: this.geoArrowVector0,\n        getTargetPosition: this.geoArrowVector1\n      };\n    }\n\n    return [\n      // base layer\n      new LineLayerClass({\n        ...defaultLayerProps,\n        ...this.getBrushingExtensionProps(interactionConfig, 'source_target'),\n        ...data,\n        ...experimentalPropOverrides,\n        ...layerProps,\n        updateTriggers,\n        extensions: [\n          ...defaultLayerProps.extensions,\n          brushingExtension,\n          ...(useArrowLayer ? [arrowCPUFilterExtension] : [])\n        ],\n        _subLayerProps: {\n          'geo-arrow-arc-layer': {\n            type: EnhancedLineLayer\n          }\n        }\n      }),\n      // hover layer\n      ...(hoveredObject\n        ? [\n            new EnhancedLineLayer({\n              ...this.getDefaultHoverLayerProps(),\n              ...layerProps,\n              data: [hoveredObject],\n              getColor: this.config.highlightColor,\n              getTargetColor: this.config.highlightColor,\n              getWidth: data.getWidth\n            })\n          ]\n        : [])\n    ];\n  }\n\n  getLegendVisualChannels() {\n    let channels: {[key: string]: VisualChannel} = this.visualChannels;\n    if (channels.sourceColor?.field && this.config[channels.sourceColor.field]) {\n      // Remove targetColor to avoid duplicate legend\n      channels = {...channels};\n      delete channels.targetColor;\n    }\n    return channels;\n  }\n}\n"
  },
  {
    "path": "src/layers/src/mapbox-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Layer, {OVERLAY_TYPE_CONST} from './base-layer';\nimport {Feature} from 'geojson';\n\nimport {findById} from '@kepler.gl/utils';\n\n/**\n * This function will convert layers to mapbox layers\n * @param layers the layers to be converted\n * @param layerData extra layer information\n * @param layerOrder the order by which we should convert layers\n * @param layersToRender {[id]: true | false} object whether each layer should be rendered\n * @returns\n */\nexport function generateMapboxLayers(\n  layers: Layer[] = [],\n  layerData: any[] = [],\n  layerOrder: string[] = [],\n  layersToRender: {[key: string]: boolean} = {}\n): {[key: string]: Layer} {\n  if (layerData.length > 0) {\n    return layerOrder\n      .slice()\n      .reverse()\n      .filter(layerId => {\n        const layer = findById(layerId)(layers);\n        return layer?.overlayType === OVERLAY_TYPE_CONST.mapboxgl && layersToRender[layerId];\n      })\n      .reduce((acc, layerId) => {\n        const layerIndex = layers.findIndex(l => l.id === layerId);\n        if (layerIndex === -1) {\n          return acc;\n        }\n\n        const layer = layers[layerIndex];\n\n        if (!(layer.overlayType === OVERLAY_TYPE_CONST.mapboxgl && layersToRender[layerId])) {\n          return acc;\n        }\n\n        return {\n          ...acc,\n          [layer.id]: {\n            id: layer.id,\n            data: layerData[layerIndex].data,\n            isVisible: layer.config.isVisible,\n            config: layerData[layerIndex].config,\n            hidden: layer.config.hidden,\n            sourceId: layerData[layerIndex].config.source\n          }\n        };\n      }, {});\n  }\n\n  return {};\n}\n\ntype newLayersType = {\n  [key: string]: Layer & Partial<{data: any; sourceId: any; isVisible: boolean}>;\n};\ntype oldLayersType = {[key: string]: Layer & {data?: any}};\n/**\n * Update mapbox layers on the given map\n * @param map\n * @param newLayers Map of new mapbox layers to be displayed\n * @param oldLayers Map of the old layers to be compare with the current ones to detect deleted layers\n *                  {layerId: sourceId}\n */\nexport function updateMapboxLayers(\n  map,\n  newLayers: newLayersType = {},\n  oldLayers: oldLayersType | null = null\n) {\n  // delete no longer existed old layers\n  if (oldLayers) {\n    checkAndRemoveOldLayers(map, oldLayers, newLayers);\n  }\n\n  // insert or update new layer\n  Object.values(newLayers).forEach(overlay => {\n    const {id: layerId, config, data, sourceId, isVisible} = overlay;\n    if (!data && !config) {\n      return;\n    }\n\n    const {data: oldData, config: oldConfig} = (oldLayers && oldLayers[layerId]) || {};\n\n    if (data && data !== oldData) {\n      updateSourceData(map, sourceId, data);\n    }\n\n    // compare with previous configs\n    if (oldConfig !== config) {\n      updateLayerConfig(map, layerId, config, isVisible);\n    }\n  });\n}\n\nfunction checkAndRemoveOldLayers(map, oldLayers: oldLayersType, newLayers: newLayersType) {\n  Object.keys(oldLayers).forEach(layerId => {\n    if (!newLayers[layerId]) {\n      map.removeLayer(layerId);\n    }\n  });\n}\n\nfunction updateLayerConfig(map, layerId, config, isVisible) {\n  const mapboxLayer = map.getLayer(layerId);\n\n  if (mapboxLayer) {\n    // check if layer already is set\n    // remove it if exists\n    map.removeLayer(layerId);\n  }\n\n  map.addLayer(config);\n  map.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none');\n}\n\nfunction updateSourceData(map, sourceId, data) {\n  const source = map.getSource(sourceId);\n\n  if (!source) {\n    map.addSource(sourceId, {\n      type: 'geojson',\n      data\n    });\n  } else {\n    source.setData(data);\n  }\n}\n\n/**\n *\n * @param filteredIndex\n * @param getGeometry {({index: number}) => any}\n * @param getProperties {({index: number}) => any}\n * @returns FeatureCollection\n */\nexport function geoJsonFromData(\n  filteredIndex: number[] = [],\n  getGeometry: (arg: {index: number}) => any,\n  getProperties: (arg: {index: number}) => object\n) {\n  const geojson: {type: string; features: Feature[]} = {\n    type: 'FeatureCollection',\n    features: []\n  };\n\n  for (let i = 0; i < filteredIndex.length; i++) {\n    const index = filteredIndex[i];\n    const rowIndex = {index};\n    const geometry = getGeometry(rowIndex);\n\n    if (geometry) {\n      geojson.features.push({\n        type: 'Feature',\n        properties: {\n          index,\n          ...getProperties(rowIndex)\n        },\n        geometry\n      });\n    }\n  }\n\n  return geojson;\n}\n\nexport const prefixGpuField = name => `gpu:${name}`;\n\nexport function gpuFilterToMapboxFilter(gpuFilter) {\n  const {filterRange, filterValueUpdateTriggers} = gpuFilter;\n\n  const hasFilter = Object.values(filterValueUpdateTriggers).filter(d => d);\n\n  if (!hasFilter.length) {\n    return null;\n  }\n\n  const condition = ['all'];\n\n  // [\">=\", key, value]\n  // [\"<=\", key, value]\n  const expressions = Object.values(filterValueUpdateTriggers as ({name: string} | null)[]).reduce(\n    (accu: any[], gpu, i) =>\n      gpu?.name\n        ? [\n            ...accu,\n            ['>=', prefixGpuField(gpu.name), filterRange[i][0]],\n            ['<=', prefixGpuField(gpu.name), filterRange[i][1]]\n          ]\n        : accu,\n    condition\n  );\n\n  return expressions;\n}\n"
  },
  {
    "path": "src/layers/src/mapboxgl-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Layer, {LayerBaseConfig, OVERLAY_TYPE_CONST, VisualChannels} from './base-layer';\nimport {createSelector} from 'reselect';\n\nimport {geoJsonFromData, prefixGpuField, gpuFilterToMapboxFilter} from './mapbox-utils';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport {Merge, LayerColumn} from '@kepler.gl/types';\n\ntype MapboxLayerGLColumns = {\n  lat: LayerColumn;\n  lng: LayerColumn;\n\n  // COLUMN_MODE_GEOARROW\n  geoarrow?: LayerColumn;\n};\n\nexport type MapboxLayerGLConfig = Merge<LayerBaseConfig, {columns: MapboxLayerGLColumns}>;\n\nexport const COLUMN_MODE_POINTS = 'points';\nexport const mapboxRequiredColumns = ['lat', 'lng'];\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_POINTS,\n    label: 'Points',\n    requiredColumns: mapboxRequiredColumns\n  }\n];\n\nexport const pointColResolver = ({lat, lng, geoarrow}: MapboxLayerGLColumns, columnMode?: string) =>\n  `${columnMode}-${lat.fieldIdx}-${lng.fieldIdx}-${geoarrow?.fieldIdx}`;\n\nclass MapboxLayerGL extends Layer {\n  declare config: MapboxLayerGLConfig;\n\n  get overlayType() {\n    return OVERLAY_TYPE_CONST.mapboxgl;\n  }\n\n  get type(): string | null {\n    return null;\n  }\n\n  get isAggregated(): true {\n    return true;\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get noneLayerDataAffectingProps() {\n    return [];\n  }\n\n  get visualChannels(): VisualChannels {\n    return {};\n  }\n  datasetSelector = (config: MapboxLayerGLConfig) => config.dataId;\n  gpuFilterSelector = (config: MapboxLayerGLConfig, datasets) =>\n    ((config.dataId && datasets[config.dataId]) || {}).gpuFilter;\n  columnsSelector = (config: MapboxLayerGLConfig) =>\n    pointColResolver(config.columns, config.columnMode);\n\n  sourceSelector = createSelector(\n    this.datasetSelector,\n    this.columnsSelector,\n    (datasetId, columns) => `${datasetId}-${columns}`\n  );\n\n  filterSelector = createSelector(this.gpuFilterSelector, gpuFilter =>\n    gpuFilterToMapboxFilter(gpuFilter)\n  );\n\n  isValidFilter(filter) {\n    // mapbox will crash if filter is not an array or empty\n    return Array.isArray(filter) && filter.length;\n  }\n\n  getDataUpdateTriggers({filteredIndex, gpuFilter, id}: KeplerTable): any {\n    const {columns} = this.config;\n\n    const visualChannelFields = Object.values(this.visualChannels).reduce(\n      (accu, v) => ({\n        ...accu,\n        ...(this.config[v.field] ? {[v.field]: this.config[v.field].name} : {})\n      }),\n      {}\n    );\n\n    const updateTriggers = {\n      getData: {\n        datasetId: id,\n        columns,\n        filteredIndex,\n        ...visualChannelFields,\n        ...gpuFilter.filterValueUpdateTriggers\n      },\n      getMeta: {datasetId: id, columns}\n    };\n\n    return updateTriggers;\n  }\n\n  getGeometry(position) {\n    return position.every(Number.isFinite)\n      ? {\n          type: 'Point',\n          coordinates: position\n        }\n      : null;\n  }\n\n  calculateDataAttribute({dataContainer, filteredIndex, gpuFilter}: KeplerTable, getPosition) {\n    const getGeometry = d => this.getGeometry(getPosition(d));\n\n    const vcFields = Object.values(this.visualChannels)\n      .map(v => this.config[v.field])\n      .filter(v => v);\n\n    const getPropertyFromVisualChanel = vcFields.length\n      ? d =>\n          vcFields.reduce(\n            (accu, field) => ({\n              ...accu,\n              [field.name]: field.valueAccessor(d)\n            }),\n            {}\n          )\n      : () => ({});\n\n    const {filterValueUpdateTriggers, filterValueAccessor} = gpuFilter;\n\n    // gpuField To property\n    const hasFilter = Object.values(filterValueUpdateTriggers).filter(d => d).length;\n    const valueAccessor = filterValueAccessor(dataContainer)();\n\n    const getPropertyFromFilter = hasFilter\n      ? d => {\n          const filterValue = valueAccessor(d);\n          return Object.values(filterValueUpdateTriggers).reduce(\n            (accu: any, gpu: any, i) => ({\n              ...accu,\n              ...(gpu?.name ? {[prefixGpuField(gpu.name)]: filterValue[i]} : {})\n            }),\n            {} as {[id: string]: number | number[]}\n          ) as Record<string, number | number[]>;\n        }\n      : () => ({} as Record<string, number | number[]>);\n\n    const getProperties = d => ({\n      ...getPropertyFromVisualChanel(d),\n      ...getPropertyFromFilter(d)\n    });\n\n    return geoJsonFromData(filteredIndex, getGeometry, getProperties);\n  }\n\n  // this layer is rendered at mapbox level\n  // todo: maybe need to find a better solution for this one\n  shouldRenderLayer() {\n    return typeof this.type === 'string' && this.config.isVisible && this.hasAllColumns();\n  }\n}\n\nexport default MapboxLayerGL;\n"
  },
  {
    "path": "src/layers/src/point-layer/point-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nclass PointLayerIcon extends Component {\n  static propTypes = {\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'point-layer-icon',\n    totalColor: 6\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <circle cx=\"29.4\" cy=\"31.6\" r=\"8.4\" className=\"cr1\" />\n        <circle cx=\"48.5\" cy=\"15.7\" r=\"6.5\" className=\"cr2\" />\n        <circle cx=\"11\" cy=\"44.2\" r=\"3\" className=\"cr3\" />\n        <circle cx=\"50\" cy=\"44.2\" r=\"5\" className=\"cr4\" />\n        <circle cx=\"34\" cy=\"54.2\" r=\"3\" className=\"cr5\" />\n        <circle cx=\"14\" cy=\"16.2\" r=\"4\" className=\"cr6\" />\n      </Base>\n    );\n  }\n}\n\nexport default PointLayerIcon;\n"
  },
  {
    "path": "src/layers/src/point-layer/point-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\n\nimport {BrushingExtension} from '@deck.gl/extensions';\nimport {ScatterplotLayer} from '@deck.gl/layers';\n\nimport {GeoArrowScatterplotLayer} from '@kepler.gl/deckgl-arrow-layers';\nimport {FilterArrowExtension} from '@kepler.gl/deckgl-layers';\n\nimport Layer, {\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  LayerColorConfig,\n  LayerSizeConfig,\n  LayerStrokeColorConfig\n} from '../base-layer';\nimport {\n  hexToRgb,\n  findDefaultColorField,\n  DataContainerInterface,\n  ArrowDataContainer\n} from '@kepler.gl/utils';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport PointLayerIcon from './point-layer-icon';\nimport {\n  DatasetType,\n  LAYER_VIS_CONFIGS,\n  DEFAULT_LAYER_COLOR,\n  CHANNEL_SCALES,\n  DEFAULT_COLOR_UI\n} from '@kepler.gl/constants';\nimport {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';\nimport {\n  assignPointPairToLayerColumn,\n  isLayerHoveredFromArrow,\n  getBoundsFromArrowMetadata,\n  getGeoArrowPointLayerProps,\n  isGeoArrowPointField,\n  createGeoArrowPointVector,\n  getFilteredIndex,\n  getNeighbors,\n  FindDefaultLayerProps\n} from '../layer-utils';\nimport {getGeojsonPointDataMaps, GeojsonPointDataMaps} from '../geojson-layer/geojson-utils';\nimport {\n  ColorRange,\n  Merge,\n  RGBColor,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigColorSelect,\n  VisConfigNumber,\n  VisConfigRange,\n  LayerColumn,\n  Field,\n  AnimationConfig\n} from '@kepler.gl/types';\n\nexport type PointLayerVisConfigSettings = {\n  radius: VisConfigNumber;\n  fixedRadius: VisConfigBoolean;\n  opacity: VisConfigNumber;\n  outline: VisConfigBoolean;\n  thickness: VisConfigNumber;\n  strokeColor: VisConfigColorSelect;\n  colorRange: VisConfigColorRange;\n  strokeColorRange: VisConfigColorRange;\n  radiusRange: VisConfigRange;\n  filled: VisConfigBoolean;\n};\n\nexport type PointLayerColumnsConfig = {\n  lat: LayerColumn;\n  lng: LayerColumn;\n  altitude?: LayerColumn;\n  neighbors?: LayerColumn;\n  geojson: LayerColumn;\n  geoarrow: LayerColumn;\n};\n\nexport type PointLayerVisConfig = {\n  radius: number;\n  fixedRadius: boolean;\n  opacity: number;\n  outline: boolean;\n  thickness: number;\n  strokeColor: RGBColor;\n  colorRange: ColorRange;\n  strokeColorRange: ColorRange;\n  radiusRange: [number, number];\n  filled: boolean;\n  billboard: boolean;\n  allowHover: boolean;\n  showNeighborOnHover: boolean;\n  showHighlightColor: boolean;\n};\nexport type PointLayerVisualChannelConfig = LayerColorConfig &\n  LayerSizeConfig &\n  LayerStrokeColorConfig;\nexport type PointLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: PointLayerColumnsConfig; visConfig: PointLayerVisConfig}\n> &\n  PointLayerVisualChannelConfig;\n\nexport type PointLayerData = {\n  position: number[];\n  index: number;\n  neighbors: any[];\n};\n\nexport const pointPosAccessor =\n  ({lat, lng, altitude}: PointLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  (d: {index: number}) =>\n    [\n      dc.valueAt(d.index, lng.fieldIdx),\n      dc.valueAt(d.index, lat.fieldIdx),\n      altitude && altitude.fieldIdx > -1 ? dc.valueAt(d.index, altitude.fieldIdx) : 0\n    ];\n\nexport const geojsonPosAccessor =\n  ({geojson}: {geojson: LayerColumn}) =>\n  d =>\n    d[geojson.fieldIdx];\n\nexport const geoarrowPosAccessor =\n  ({geoarrow}: PointLayerColumnsConfig) =>\n  (dataContainer: DataContainerInterface) =>\n  (d: {index: number}) => {\n    const row = dataContainer.valueAt(d.index, geoarrow.fieldIdx);\n    return [row.get(0), row.get(1), 0];\n  };\n\nexport const COLUMN_MODE_POINTS = 'points';\nexport const COLUMN_MODE_GEOJSON = 'geojson';\nexport const COLUMN_MODE_GEOARROW = 'geoarrow';\n\nexport const pointRequiredColumns: ['lat', 'lng'] = ['lat', 'lng'];\nexport const pointOptionalColumns: ['altitude', 'neighbors'] = ['altitude', 'neighbors'];\nexport const geojsonRequiredColumns: ['geojson'] = ['geojson'];\nexport const geoarrowRequiredColumns: ['geoarrow'] = ['geoarrow'];\n\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_POINTS,\n    label: 'Point Columns',\n    requiredColumns: pointRequiredColumns,\n    optionalColumns: pointOptionalColumns\n  },\n  {\n    key: COLUMN_MODE_GEOJSON,\n    label: 'GeoJSON Feature',\n    requiredColumns: geojsonRequiredColumns,\n    verifyField: f => !isGeoArrowPointField(f)\n  },\n  {\n    key: COLUMN_MODE_GEOARROW,\n    label: 'Geoarrow Points',\n    requiredColumns: geoarrowRequiredColumns,\n    verifyField: f => isGeoArrowPointField(f)\n  }\n];\nconst DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;\n\nconst brushingExtension = new BrushingExtension();\nconst arrowCPUFilterExtension = new FilterArrowExtension();\n\nfunction pushPointPosition(data: any[], pos: number[], index: number, neighbors?: number[]) {\n  if (pos.every(Number.isFinite)) {\n    data.push({\n      position: pos,\n      // index is important for filter\n      index,\n      ...(neighbors ? {neighbors} : {})\n    });\n  }\n}\n\nexport const pointVisConfigs: {\n  radius: 'radius';\n  fixedRadius: 'fixedRadius';\n  opacity: 'opacity';\n  outline: 'outline';\n  thickness: 'thickness';\n  strokeColor: 'strokeColor';\n  colorRange: 'colorRange';\n  strokeColorRange: 'strokeColorRange';\n  radiusRange: 'radiusRange';\n  filled: VisConfigBoolean;\n  billboard: 'billboard';\n  allowHover: 'allowHover';\n  showNeighborOnHover: 'showNeighborOnHover';\n  showHighlightColor: 'showHighlightColor';\n} = {\n  radius: 'radius',\n  fixedRadius: 'fixedRadius',\n  opacity: 'opacity',\n  outline: 'outline',\n  thickness: 'thickness',\n  strokeColor: 'strokeColor',\n  colorRange: 'colorRange',\n  strokeColorRange: 'strokeColorRange',\n  radiusRange: 'radiusRange',\n  filled: {\n    ...LAYER_VIS_CONFIGS.filled,\n    type: 'boolean',\n    label: 'layer.fillColor',\n    defaultValue: true,\n    property: 'filled'\n  },\n  billboard: 'billboard',\n  allowHover: 'allowHover',\n  showNeighborOnHover: 'showNeighborOnHover',\n  showHighlightColor: 'showHighlightColor'\n};\n\nexport default class PointLayer extends Layer {\n  declare config: PointLayerConfig;\n  declare visConfigSettings: PointLayerVisConfigSettings;\n  dataToFeature: GeojsonPointDataMaps = [];\n\n  dataContainer: DataContainerInterface | null = null;\n  geoArrowVector: arrow.Vector | undefined = undefined;\n\n  /*\n   * CPU filtering an arrow table by values and assembling a partial copy of the raw table is expensive\n   * so we will use filteredIndex to create an attribute e.g. filteredIndex [0|1] for GPU filtering\n   * in deck.gl layer, see: FilterArrowExtension in @kepler.gl/deckgl-layers.\n   * Note that this approach can create visible lags in case of a lot of discarted geometry.\n   */\n  filteredIndex: Uint8ClampedArray | null = null;\n  filteredIndexTrigger: number[] = [];\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(pointVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) => {\n      switch (this.config.columnMode) {\n        case COLUMN_MODE_GEOARROW:\n          return geoarrowPosAccessor(this.config.columns)(dataContainer);\n        case COLUMN_MODE_GEOJSON:\n          return geojsonPosAccessor(this.config.columns);\n        default:\n          // COLUMN_MODE_POINTS\n          return pointPosAccessor(this.config.columns)(dataContainer);\n      }\n    };\n  }\n\n  get type(): 'point' {\n    return 'point';\n  }\n\n  get isAggregated(): false {\n    return false;\n  }\n\n  get layerIcon() {\n    return PointLayerIcon;\n  }\n\n  get optionalColumns() {\n    return pointOptionalColumns;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  get noneLayerDataAffectingProps() {\n    return [...super.noneLayerDataAffectingProps, 'radius'];\n  }\n\n  get visualChannels() {\n    return {\n      color: {\n        ...super.visualChannels.color,\n        accessor: 'getFillColor',\n        condition: config => config.visConfig.filled,\n        defaultValue: config => config.color\n      },\n      strokeColor: {\n        property: 'strokeColor',\n        key: 'strokeColor',\n        field: 'strokeColorField',\n        scale: 'strokeColorScale',\n        domain: 'strokeColorDomain',\n        range: 'strokeColorRange',\n        channelScaleType: CHANNEL_SCALES.color,\n        accessor: 'getLineColor',\n        condition: config => config.visConfig.outline,\n        defaultValue: config => config.visConfig.strokeColor || config.color\n      },\n      size: {\n        ...super.visualChannels.size,\n        property: 'radius',\n        range: 'radiusRange',\n        fixed: 'fixedRadius',\n        channelScaleType: 'radius',\n        accessor: 'getRadius',\n        defaultValue: 1\n      }\n    };\n  }\n\n  setInitialLayerConfig(dataset) {\n    if (!dataset.dataContainer.numRows()) {\n      return this;\n    }\n    const defaultColorField = findDefaultColorField(dataset);\n\n    if (defaultColorField) {\n      this.updateLayerConfig({\n        colorField: defaultColorField\n      });\n      this.updateLayerVisualChannel(dataset, 'color');\n    }\n\n    return this;\n  }\n\n  static findDefaultLayerProps(dataset: KeplerTable) {\n    const {fieldPairs = [], type, label} = dataset;\n\n    const props: FindDefaultLayerProps[] = [];\n\n    if (type === DatasetType.VECTOR_TILE) {\n      return {props};\n    }\n\n    // Make layer for each pair\n    fieldPairs.forEach(pair => {\n      const latField = pair.pair.lat;\n\n      const prop: {\n        label: string;\n        color?: RGBColor;\n        isVisible?: boolean;\n        columns?: PointLayerColumnsConfig;\n      } = {\n        label:\n          // Skip the generic 'point' fallback from findPointFieldPairs and use the dataset label instead\n          pair.defaultName && pair.defaultName !== 'point'\n            ? pair.defaultName\n            : (typeof label === 'string' && label.replace(/\\.[^/.]+$/, '')) || 'Point'\n      };\n\n      // default layer color for begintrip and dropoff point\n      if (latField.value in DEFAULT_LAYER_COLOR) {\n        prop.color = hexToRgb(DEFAULT_LAYER_COLOR[latField.value]);\n      }\n\n      // set the first layer to be visible\n      if (props.length === 0) {\n        prop.isVisible = true;\n      }\n      // @ts-expect-error logically separate geojson column type?\n      prop.columns = assignPointPairToLayerColumn(pair, true);\n\n      props.push(prop);\n    });\n\n    const altProps = getGeoArrowPointLayerProps(dataset);\n\n    return {props, altProps};\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial) {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {});\n    return {\n      ...defaultLayerConfig,\n\n      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,\n\n      // add stroke color visual channel\n      strokeColorField: null,\n      strokeColorDomain: [0, 1],\n      strokeColorScale: 'quantile',\n      colorUI: {\n        ...defaultLayerConfig.colorUI,\n        strokeColorRange: DEFAULT_COLOR_UI\n      }\n    };\n  }\n\n  calculateDataAttribute({filteredIndex, dataContainer}: KeplerTable, getPosition) {\n    const {columnMode} = this.config;\n\n    // 1) COLUMN_MODE_GEOARROW - when we have a geoarrow point column\n    // 2) COLUMN_MODE_POINTS + ArrowDataContainer > create geoarrow point column on the fly\n    if (\n      dataContainer instanceof ArrowDataContainer &&\n      (columnMode === COLUMN_MODE_GEOARROW || columnMode === COLUMN_MODE_POINTS)\n    ) {\n      this.filteredIndex = getFilteredIndex(\n        dataContainer.numRows(),\n        filteredIndex,\n        this.filteredIndex\n      );\n      this.filteredIndexTrigger = filteredIndex;\n\n      if (this.config.columnMode === COLUMN_MODE_GEOARROW) {\n        this.geoArrowVector = dataContainer.getColumn(this.config.columns.geoarrow.fieldIdx);\n      } else {\n        // generate a column compatible with geoarrow point\n        this.geoArrowVector = createGeoArrowPointVector(dataContainer, getPosition);\n      }\n\n      return dataContainer.getTable();\n    }\n\n    // we don't need these in non-Arrow modes atm.\n    this.geoArrowVector = undefined;\n    this.filteredIndex = null;\n\n    const data: PointLayerData[] = [];\n\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      let neighbors;\n\n      if (this.config.columnMode === COLUMN_MODE_POINTS) {\n        if (this.config.columns.neighbors?.value) {\n          const {fieldIdx} = this.config.columns.neighbors;\n          neighbors = Array.isArray(dataContainer.valueAt(index, fieldIdx))\n            ? dataContainer.valueAt(index, fieldIdx)\n            : [];\n        }\n        const pos = getPosition({index});\n\n        // if doesn't have point lat or lng, do not add the point\n        // deck.gl can't handle position = null\n        pushPointPosition(data, pos, index, neighbors);\n      } else {\n        // COLUMN_MODE_GEOJSON mode - point from geojson coordinates\n        const coordinates = this.dataToFeature[i];\n        // if multi points\n        if (coordinates && Array.isArray(coordinates[0])) {\n          coordinates.forEach(coord => {\n            pushPointPosition(data, coord, index);\n          });\n        } else if (coordinates && Number.isFinite(coordinates[0])) {\n          pushPointPosition(data, coordinates as number[], index);\n        }\n      }\n    }\n\n    return data;\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {textLabel} = this.config;\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const {data, triggerChanged} = this.updateData(datasets, oldLayerData);\n    const getPosition = d => d.position;\n\n    // get all distinct characters in the text labels\n    const textLabels = formatTextLabelData({\n      textLabel,\n      triggerChanged,\n      oldLayerData,\n      data,\n      dataContainer,\n      filteredIndex: this.filteredIndex\n    });\n\n    const accessors = this.getAttributeAccessors({dataContainer});\n\n    const isFilteredAccessor = (data: {index: number}) => {\n      return this.filteredIndex ? this.filteredIndex[data.index] : 1;\n    };\n\n    return {\n      data,\n      getPosition,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),\n      getFiltered: isFilteredAccessor,\n      textLabels,\n      ...accessors\n    };\n  }\n  /* eslint-enable complexity */\n\n  updateLayerMeta(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n    this.dataContainer = dataContainer;\n\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      const getFeature = this.getPositionAccessor();\n      this.dataToFeature = getGeojsonPointDataMaps(dataContainer, getFeature);\n    } else if (this.config.columnMode === COLUMN_MODE_GEOARROW) {\n      const boundsFromMetadata = getBoundsFromArrowMetadata(\n        this.config.columns.geoarrow,\n        dataContainer as ArrowDataContainer\n      );\n      if (boundsFromMetadata) {\n        this.updateMeta({bounds: boundsFromMetadata});\n      } else {\n        const getPosition = this.getPositionAccessor(dataContainer);\n        const bounds = this.getPointsBounds(dataContainer, getPosition);\n        this.updateMeta({bounds});\n      }\n    } else {\n      const getPosition = this.getPositionAccessor(dataContainer);\n      const bounds = this.getPointsBounds(dataContainer, getPosition);\n      this.updateMeta({bounds});\n    }\n  }\n\n  // eslint-disable-next-line complexity\n  renderLayer(opts) {\n    const {data, gpuFilter, objectHovered, mapState, interactionConfig, dataset} = opts;\n\n    // if no field size is defined we need to pass fixed radius = false\n    const fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);\n    const radiusScale = this.getRadiusScaleByZoom(mapState, fixedRadius);\n\n    const layerProps = {\n      stroked: this.config.visConfig.outline,\n      filled: this.config.visConfig.filled,\n      lineWidthScale: this.config.visConfig.thickness,\n      billboard: this.config.visConfig.billboard,\n      radiusScale,\n      ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})\n    };\n\n    const updateTriggers = {\n      getPosition: this.config.columns,\n      getFilterValue: gpuFilter.filterValueUpdateTriggers,\n      getFiltered: this.filteredIndexTrigger,\n      ...this.getVisualChannelUpdateTriggers()\n    };\n\n    const useArrowLayer = Boolean(this.geoArrowVector);\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const brushingProps = this.getBrushingExtensionProps(interactionConfig);\n    const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius, mapState);\n    const extensions = [\n      ...defaultLayerProps.extensions,\n      brushingExtension,\n      ...(useArrowLayer ? [arrowCPUFilterExtension] : [])\n    ];\n\n    const sharedProps = {\n      getFilterValue: data.getFilterValue,\n      extensions,\n      filterRange: defaultLayerProps.filterRange,\n      visible: defaultLayerProps.visible,\n      ...brushingProps\n    };\n    const hoveredObject = this.hasHoveredObject(objectHovered);\n    const {showNeighborOnHover, allowHover} = this.config.visConfig;\n    let neighborsData: ReturnType<typeof getNeighbors> = [];\n    if (allowHover && showNeighborOnHover && hoveredObject) {\n      // find neighbors\n      neighborsData = getNeighbors(\n        this.config.columns.neighbors,\n        dataset.dataContainer,\n        hoveredObject.index,\n        this.getPositionAccessor(dataset.dataContainer)\n      );\n    }\n\n    let ScatterplotLayerClass: typeof ScatterplotLayer | typeof GeoArrowScatterplotLayer =\n      ScatterplotLayer;\n    let deckLayerData = data.data;\n    let getPosition = data.getPosition;\n    if (useArrowLayer) {\n      ScatterplotLayerClass = GeoArrowScatterplotLayer;\n      deckLayerData = dataset.dataContainer.getTable();\n      getPosition = this.geoArrowVector;\n    }\n\n    return [\n      // @ts-expect-error\n      new ScatterplotLayerClass({\n        ...defaultLayerProps,\n        ...brushingProps,\n        ...layerProps,\n        ...data,\n        data: deckLayerData,\n        getPosition,\n        parameters: {\n          // circles will be flat on the map when the altitude column is not used\n          depthTest: (this.config.columns.altitude?.fieldIdx as number) > -1\n        },\n        lineWidthUnits: 'pixels',\n        updateTriggers,\n        extensions,\n        opacity: hoveredObject && showNeighborOnHover ? 0.2 : this.config.visConfig.opacity,\n        pickable: allowHover,\n        autoHighlight: false\n      }),\n      // hover layer\n      ...(hoveredObject\n        ? [\n            new ScatterplotLayer({\n              ...this.getDefaultHoverLayerProps(),\n              ...layerProps,\n              visible: defaultLayerProps.visible,\n              data: [...neighborsData, hoveredObject],\n              getLineColor: this.config.visConfig.showHighlightColor\n                ? this.config.highlightColor\n                : data.getLineColor,\n              getFillColor: this.config.visConfig.showHighlightColor\n                ? this.config.highlightColor\n                : data.getFillColor,\n              getRadius: data.getRadius,\n              getPosition: data.getPosition\n            })\n          ]\n        : []),\n      // text label layer\n      ...this.renderTextLabelLayer(\n        {\n          getPosition,\n          sharedProps,\n          getPixelOffset,\n          updateTriggers,\n          getFiltered: data.getFiltered\n        },\n        this.geoArrowVector\n          ? {\n              ...opts,\n              data: {...opts.data, getPosition}\n            }\n          : opts\n      )\n    ];\n  }\n\n  hasHoveredObject(objectInfo: {index: number}) {\n    if (\n      isLayerHoveredFromArrow(objectInfo, this.id) &&\n      objectInfo.index >= 0 &&\n      this.dataContainer\n    ) {\n      return {\n        index: objectInfo.index,\n        position: this.getPositionAccessor(this.dataContainer)(objectInfo)\n      };\n    }\n\n    return super.hasHoveredObject(objectInfo);\n  }\n\n  getHoverData(\n    object: {index: number} | arrow.StructRow | undefined,\n    dataContainer: DataContainerInterface,\n    fields: Field[],\n    animationConfig: AnimationConfig,\n    hoverInfo: {index: number}\n  ) {\n    // for arrow format, `object` is the Arrow row object Proxy,\n    // and index is passed in `hoverInfo`.\n    const index = this.geoArrowVector ? hoverInfo?.index : (object as {index: number}).index;\n    if (index >= 0) {\n      return dataContainer.row(index);\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/config.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {RasterTileLayerColorMaps, RasterTileLayerPresets} from './raster-tile-layer-schema';\nimport {\n  VisConfigBoolean,\n  VisConfigNumber,\n  VisConfigRange,\n  VisConfigObjectSelection,\n  VisConfigInput\n} from '@kepler.gl/types';\n\nimport {ColorRescaling, ConfigOption, PresetData, BandCombination} from './types';\n\n/**\n * Known Data Source IDs that work with current STAC Layer limitations\n */\nexport enum DATA_SOURCE_IDS {\n  SENTINEL_2_L1C = 'sentinel-2-l1c',\n  SENTINEL_2_L1A = 'sentinel-2-l2a',\n  SENTINEL_2_C1_L2A = 'sentinel-2-c1-l2a',\n  SENTINEL_2_PRE_C1_L2A = 'sentinel-2-pre-c1-l2a',\n  LANDSAT_C2_L1 = 'landsat-c2-l1',\n  LANDSAT_C2_L2 = 'landsat-c2-l2',\n  MODIS_09A1_061 = 'modis-09A1-061',\n  MODIS_43A4_061 = 'modis-43A4-061',\n  MOIDS_09Q1_061 = 'modis-09Q1-061'\n}\n\nexport const RASTER_COLOR_RESET_PARAMS = {\n  gammaContrastFactor: 1,\n  sigmoidalContrastFactor: 0,\n  sigmoidalBiasFactor: 0.5,\n  saturationValue: 1.0,\n  dynamicColor: false\n};\n\nconst DEFAULT_SENTINEL_COLOR_DEFAULTS = {\n  gammaContrastFactor: 2.2,\n  sigmoidalContrastFactor: 23,\n  sigmoidalBiasFactor: 0.12,\n  saturationValue: 2.0\n};\n\nconst DEFAULT_MODIS_COLOR_DEFAULTS = {\n  gammaContrastFactor: 2.0,\n  sigmoidalContrastFactor: 8,\n  sigmoidalBiasFactor: 0.13,\n  saturationValue: 1.1\n};\n\nconst DEFAULT_SENTINEL_BAND_MAPPING = {\n  coastal: 'B01',\n  blue: 'B02',\n  green: 'B03',\n  red: 'B04',\n  rededge1: 'B05',\n  rededge2: 'B06',\n  rededge3: 'B07',\n  nir: 'B08',\n  nir08: 'B8A',\n  nir09: 'B09',\n  cirrus: 'B10',\n  swir11: 'B11',\n  swir16: 'B11',\n  swir12: 'B12',\n  swir22: 'B12'\n};\n\nconst DEFAULT_MODIS_BAND_MAPPING = {\n  sur_refl_b01: 'B01',\n  sur_refl_b02: 'B02',\n  sur_refl_b03: 'B03',\n  sur_refl_b04: 'B04',\n  sur_refl_b05: 'B05',\n  sur_refl_b06: 'B06',\n  sur_refl_b07: 'B07',\n  Nadir_Reflectance_Band1: 'B01',\n  Nadir_Reflectance_Band2: 'B02',\n  Nadir_Reflectance_Band3: 'B03',\n  Nadir_Reflectance_Band4: 'B04',\n  Nadir_Reflectance_Band5: 'B05',\n  Nadir_Reflectance_Band6: 'B06',\n  Nadir_Reflectance_Band7: 'B07'\n};\n\nconst DEFAULT_LANDSAT_C2_L1_BAND_MAPPING = {\n  green: 'B01',\n  red: 'B02',\n  nir08: 'B03',\n  nir09: 'B04'\n};\n\nconst DEFAULT_LANDSAT_C2_L2_BAND_MAPPING = {\n  blue: 'B01',\n  green: 'B02',\n  red: 'B03',\n  nir08: 'B04',\n  swir16: 'B05',\n  lwir: 'B06',\n  swir22: 'B07',\n  coastal: 'B01',\n  lwir11: 'B06'\n};\n\nexport const DEFAULT_BAND_MAPPINGS = {\n  [DATA_SOURCE_IDS.SENTINEL_2_L1C]: DEFAULT_SENTINEL_BAND_MAPPING,\n  [DATA_SOURCE_IDS.SENTINEL_2_L1A]: DEFAULT_SENTINEL_BAND_MAPPING,\n  [DATA_SOURCE_IDS.SENTINEL_2_C1_L2A]: DEFAULT_SENTINEL_BAND_MAPPING,\n  [DATA_SOURCE_IDS.SENTINEL_2_PRE_C1_L2A]: DEFAULT_SENTINEL_BAND_MAPPING,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L1]: DEFAULT_LANDSAT_C2_L1_BAND_MAPPING,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L2]: DEFAULT_LANDSAT_C2_L2_BAND_MAPPING,\n  [DATA_SOURCE_IDS.MODIS_09A1_061]: DEFAULT_MODIS_BAND_MAPPING,\n  [DATA_SOURCE_IDS.MODIS_43A4_061]: DEFAULT_MODIS_BAND_MAPPING,\n  [DATA_SOURCE_IDS.MOIDS_09Q1_061]: DEFAULT_MODIS_BAND_MAPPING\n};\n\n/**\n * Per-data source color rescaling defaults for known collections\n */\nexport const DATA_SOURCE_COLOR_DEFAULTS: Record<DATA_SOURCE_IDS, ColorRescaling> = {\n  // Note: good for True Color, too saturated for other channels\n  [DATA_SOURCE_IDS.SENTINEL_2_L1C]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.SENTINEL_2_L1A]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.SENTINEL_2_C1_L2A]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.SENTINEL_2_PRE_C1_L2A]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L1]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L2]: DEFAULT_SENTINEL_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.MODIS_09A1_061]: DEFAULT_MODIS_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.MODIS_43A4_061]: DEFAULT_MODIS_COLOR_DEFAULTS,\n  [DATA_SOURCE_IDS.MOIDS_09Q1_061]: DEFAULT_MODIS_COLOR_DEFAULTS\n};\n\n/**\n * Available \"presets\"\n *\n * I define a \"preset\" as one specific manner of loading bands and combining them on the frontend.\n * In the future I expect we'll want a UI that gives the user full flexibility.\n */\nexport const PRESET_OPTIONS: Record<string, PresetData> = {\n  trueColor: {\n    label: 'True Color',\n    id: RasterTileLayerPresets.trueColor,\n    bandCombination: BandCombination.Rgb,\n    commonNames: ['red', 'green', 'blue']\n  },\n  infrared: {\n    label: 'Infrared',\n    id: RasterTileLayerPresets.infrared,\n    bandCombination: BandCombination.Rgb,\n    commonNames: ['nir', 'red', 'green'],\n    description: 'False-color infrared composite. Near-infrared, red, green mapped to RGB'\n  },\n  agriculture: {\n    label: 'Agriculture',\n    id: RasterTileLayerPresets.agriculture,\n    bandCombination: BandCombination.Rgb,\n    commonNames: ['swir16', 'nir', 'blue'],\n    description:\n      'False-color agriculture composite. Short-wave infrared 1, near-infrared, blue mapped to RGB.'\n  },\n  forestBurn: {\n    label: 'Forest Burn',\n    id: RasterTileLayerPresets.forestBurn,\n    bandCombination: BandCombination.Rgb,\n    commonNames: ['swir22', 'nir', 'blue'],\n    description:\n      'False-color forest burn composite. Short-wave infrared 2, near-infrared, blue mapped to RGB'\n  },\n  ndvi: {\n    label: 'NDVI',\n    id: RasterTileLayerPresets.ndvi,\n    bandCombination: BandCombination.NormalizedDifference,\n    commonNames: ['nir', 'red'],\n    description: 'Normalized Difference Vegetation Index',\n    infoUrl:\n      'https://www.usgs.gov/core-science-systems/nli/landsat/landsat-normalized-difference-vegetation-index'\n  },\n  // Comment this out for now, because in testing it looks like there's an overflow of some sort;\n  // index values should be between -1 and 1, but values look way out of bounds\n  // evi: {\n  //   label: 'EVI',\n  //   id: RasterTileLayerPresets.evi,\n  //   bandCombination: 'enhancedVegetationIndex',\n  //   commonNames: ['nir', 'red', 'blue']\n  // },\n  savi: {\n    label: 'SAVI',\n    id: RasterTileLayerPresets.savi,\n    bandCombination: BandCombination.SoilAdjustedVegetationIndex,\n    commonNames: ['nir', 'red'],\n    description: 'Soil Adjusted Vegetation Index',\n    infoUrl:\n      'https://www.usgs.gov/core-science-systems/nli/landsat/landsat-soil-adjusted-vegetation-index'\n  },\n  msavi: {\n    label: 'MSAVI',\n    id: RasterTileLayerPresets.msavi,\n    bandCombination: BandCombination.ModifiedSoilAdjustedVegetationIndex,\n    commonNames: ['nir', 'red'],\n    description: 'Modified Soil Adjusted Vegetation Index',\n    infoUrl:\n      'https://www.usgs.gov/core-science-systems/nli/landsat/landsat-modified-soil-adjusted-vegetation-index'\n  },\n  ndmi: {\n    label: 'NDMI',\n    id: RasterTileLayerPresets.ndmi,\n    bandCombination: BandCombination.NormalizedDifference,\n    commonNames: ['nir', 'swir16'],\n    description: 'Normalized Difference Moisture Index',\n    infoUrl:\n      'https://www.usgs.gov/core-science-systems/nli/landsat/normalized-difference-moisture-index'\n  },\n  nbr: {\n    label: 'NBR',\n    id: RasterTileLayerPresets.nbr,\n    bandCombination: BandCombination.NormalizedDifference,\n    commonNames: ['nir', 'swir22'],\n    description: 'Normalized Burn Ratio',\n    infoUrl: 'https://www.usgs.gov/core-science-systems/nli/landsat/landsat-normalized-burn-ratio'\n  },\n  nbr2: {\n    label: 'NBR 2',\n    id: RasterTileLayerPresets.nbr2,\n    bandCombination: BandCombination.NormalizedDifference,\n    commonNames: ['swir16', 'swir22'],\n    description: 'Normalized Burn Ratio 2',\n    infoUrl: 'https://www.usgs.gov/core-science-systems/nli/landsat/landsat-normalized-burn-ratio-2'\n  },\n  singleBand: {\n    label: 'Single Band',\n    id: RasterTileLayerPresets.singleBand,\n    bandCombination: BandCombination.Single,\n    description: 'Render a single band data'\n  }\n};\n\n/**\n * Valid zoom ranges for each data source\n *\n * The maximum zoom is derived from the resolution of each data source.\n * The minimum zoom relates to the number of overviews in the COGs at the source. The more\n * overviews, the easier it is to create downsampled tiles. The minimum zooms can be reduced but it\n * takes longer to serve low-zoom tiles since the server must read from many image sources.\n */\n// TODO: use render:max_overview_gsd from STAC\nexport const ZOOM_RANGES: Record<DATA_SOURCE_IDS, [number, number]> = {\n  [DATA_SOURCE_IDS.SENTINEL_2_C1_L2A]: [8, 13],\n  [DATA_SOURCE_IDS.SENTINEL_2_L1A]: [8, 13],\n  [DATA_SOURCE_IDS.SENTINEL_2_L1C]: [8, 13],\n  [DATA_SOURCE_IDS.SENTINEL_2_PRE_C1_L2A]: [8, 13],\n  [DATA_SOURCE_IDS.LANDSAT_C2_L1]: [7, 12],\n  [DATA_SOURCE_IDS.LANDSAT_C2_L2]: [7, 12],\n  [DATA_SOURCE_IDS.MODIS_09A1_061]: [7, 13],\n  [DATA_SOURCE_IDS.MODIS_43A4_061]: [7, 13],\n  [DATA_SOURCE_IDS.MOIDS_09Q1_061]: [7, 13]\n};\n\n/**\n * Bit depth for each data source\n * Sentinel-2 is 12-bit; Landsat-8 is 16-bit; Planet is 12-bit; NAIP is 8-bit\n */\n// TODO: use range value and dtype in STAC collection instead of this\nexport const MAX_PIXEL_VALUES: Record<DATA_SOURCE_IDS, number> = {\n  [DATA_SOURCE_IDS.SENTINEL_2_C1_L2A]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.SENTINEL_2_L1A]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.SENTINEL_2_L1C]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.SENTINEL_2_PRE_C1_L2A]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L1]: Math.pow(2, 16) - 1,\n  [DATA_SOURCE_IDS.LANDSAT_C2_L2]: Math.pow(2, 16) - 1,\n  [DATA_SOURCE_IDS.MODIS_09A1_061]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.MODIS_43A4_061]: Math.pow(2, 12) - 1,\n  [DATA_SOURCE_IDS.MOIDS_09Q1_061]: Math.pow(2, 12) - 1\n};\n\n/**\n * Id for categorical colormap. Unlike most colormaps. Categorical colormap image is created\n * from colormap set in visConfig\n */\nexport const CATEGORICAL_COLORMAP_ID = '_categorical';\n\n/**\n * A list of available colormaps. Colormaps are originally derived from\n * matplotlib, then via rio-tiler. Colormaps are 10x256 PNG images that are\n * loaded as textures\n */\n\nexport const COLORMAP_OPTIONS: readonly ConfigOption[] = [\n  {\n    label: 'Cfastie',\n    id: RasterTileLayerColorMaps.cfastie\n  },\n  {\n    label: 'Rplumbo',\n    id: RasterTileLayerColorMaps.rplumbo\n  },\n  {\n    label: 'Schwarzwald',\n    id: RasterTileLayerColorMaps.schwarzwald\n  },\n  {\n    label: 'Viridis',\n    id: RasterTileLayerColorMaps.viridis\n  },\n  {\n    label: 'Plasma',\n    id: RasterTileLayerColorMaps.plasma\n  },\n  {\n    label: 'Inferno',\n    id: RasterTileLayerColorMaps.inferno\n  },\n  {\n    label: 'Magma',\n    id: RasterTileLayerColorMaps.magma\n  },\n  {\n    label: 'Cividis',\n    id: RasterTileLayerColorMaps.cividis\n  },\n  {\n    label: 'Greys',\n    id: RasterTileLayerColorMaps.greys\n  },\n  {\n    label: 'Purples',\n    id: RasterTileLayerColorMaps.purples\n  },\n  {\n    label: 'Blues',\n    id: RasterTileLayerColorMaps.blues\n  },\n  {\n    label: 'Greens',\n    id: RasterTileLayerColorMaps.greens\n  },\n  {\n    label: 'Oranges',\n    id: RasterTileLayerColorMaps.oranges\n  },\n  {\n    label: 'Reds',\n    id: RasterTileLayerColorMaps.reds\n  },\n  {\n    label: 'Yl-Or-Br',\n    id: RasterTileLayerColorMaps.ylorbr\n  },\n  {\n    label: 'Yl-Or-Rd',\n    id: RasterTileLayerColorMaps.ylorrd\n  },\n  {\n    label: 'Or-Rd',\n    id: RasterTileLayerColorMaps.orrd\n  },\n  {\n    label: 'Pu-Rd',\n    id: RasterTileLayerColorMaps.purd\n  },\n  {\n    label: 'Rd-Pu',\n    id: RasterTileLayerColorMaps.rdpu\n  },\n  {\n    label: 'Bu-Pu',\n    id: RasterTileLayerColorMaps.bupu\n  },\n  {\n    label: 'Gn-Bu',\n    id: RasterTileLayerColorMaps.gnbu\n  },\n  {\n    label: 'Pu-Bu',\n    id: RasterTileLayerColorMaps.pubu\n  },\n  {\n    label: 'Yl-Gn-Bu',\n    id: RasterTileLayerColorMaps.ylgnbu\n  },\n  {\n    label: 'Pu-Bu-Gn',\n    id: RasterTileLayerColorMaps.pubugn\n  },\n  {\n    label: 'Blue-Gn',\n    id: RasterTileLayerColorMaps.bugn\n  },\n  {\n    label: 'Yl-Gn',\n    id: RasterTileLayerColorMaps.ylgn\n  },\n  {\n    label: 'W-n-B',\n    id: RasterTileLayerColorMaps.binary\n  },\n  {\n    label: 'B-n-W',\n    id: RasterTileLayerColorMaps.gray\n  },\n  {\n    label: 'Bone',\n    id: RasterTileLayerColorMaps.bone\n  },\n  {\n    label: 'Pink',\n    id: RasterTileLayerColorMaps.pink\n  },\n  {\n    label: 'Spring',\n    id: RasterTileLayerColorMaps.spring\n  },\n  {\n    label: 'Summer',\n    id: RasterTileLayerColorMaps.summer\n  },\n  {\n    label: 'Autumn',\n    id: RasterTileLayerColorMaps.autumn\n  },\n  {\n    label: 'Winter',\n    id: RasterTileLayerColorMaps.winter\n  },\n  {\n    label: 'Cool',\n    id: RasterTileLayerColorMaps.cool\n  },\n  {\n    label: 'Wistia',\n    id: RasterTileLayerColorMaps.wistia\n  },\n  {\n    label: 'Hot',\n    id: RasterTileLayerColorMaps.hot\n  },\n  {\n    label: 'Afmhot',\n    id: RasterTileLayerColorMaps.afmhot\n  },\n  {\n    label: 'Heat',\n    id: RasterTileLayerColorMaps.gist_heat\n  },\n  {\n    label: 'Copper',\n    id: RasterTileLayerColorMaps.copper\n  },\n  {\n    label: 'Pi-Green',\n    id: RasterTileLayerColorMaps.piyg\n  },\n  {\n    label: 'Pr-Gn',\n    id: RasterTileLayerColorMaps.prgn\n  },\n  {\n    label: 'Br-Bg',\n    id: RasterTileLayerColorMaps.brbg\n  },\n  {\n    label: 'Pu-Or',\n    id: RasterTileLayerColorMaps.puor\n  },\n  {\n    label: 'Rd-Gy',\n    id: RasterTileLayerColorMaps.rdgy\n  },\n  {\n    label: 'Rd-Bu',\n    id: RasterTileLayerColorMaps.rdbu\n  },\n  {\n    label: 'Rd-Yl-Bu',\n    id: RasterTileLayerColorMaps.rdylbu\n  },\n  {\n    label: 'Rd-Yl-Gn',\n    id: RasterTileLayerColorMaps.rdylgn\n  },\n  {\n    label: 'Spectral',\n    id: RasterTileLayerColorMaps.spectral\n  },\n  {\n    label: 'Cool-Warm',\n    id: RasterTileLayerColorMaps.coolwarm\n  },\n  {\n    label: 'B-W-R',\n    id: RasterTileLayerColorMaps.bwr\n  },\n  {\n    label: 'Seismic',\n    id: RasterTileLayerColorMaps.seismic\n  },\n  {\n    label: 'Twilight',\n    id: RasterTileLayerColorMaps.twilight\n  },\n  {\n    label: 'Twilight Shifted',\n    id: RasterTileLayerColorMaps.twilight_shifted\n  },\n  {\n    label: 'HSV',\n    id: RasterTileLayerColorMaps.hsv\n  },\n  {\n    label: 'Flag',\n    id: RasterTileLayerColorMaps.flag\n  },\n  {\n    label: 'Prism',\n    id: RasterTileLayerColorMaps.prism\n  },\n  {\n    label: 'Ocean',\n    id: RasterTileLayerColorMaps.ocean\n  },\n  {\n    label: 'Gist Earth',\n    id: RasterTileLayerColorMaps.gist_earth\n  },\n  {\n    label: 'Terrain',\n    id: RasterTileLayerColorMaps.terrain\n  },\n  {\n    label: 'Gist Stern',\n    id: RasterTileLayerColorMaps.gist_stern\n  },\n  {\n    label: 'Gnuplot',\n    id: RasterTileLayerColorMaps.gnuplot\n  },\n  {\n    label: 'Gnuplot2',\n    id: RasterTileLayerColorMaps.gnuplot2\n  },\n  {\n    label: 'Cmrmap',\n    id: RasterTileLayerColorMaps.cmrmap\n  },\n  {\n    label: 'Cubehelix',\n    id: RasterTileLayerColorMaps.cubehelix\n  },\n  {\n    label: 'Brg',\n    id: RasterTileLayerColorMaps.brg\n  },\n  {\n    label: 'Gist Rainbow',\n    id: RasterTileLayerColorMaps.gist_rainbow\n  },\n  {\n    label: 'Rainbow',\n    id: RasterTileLayerColorMaps.rainbow\n  },\n  {\n    label: 'Jet',\n    id: RasterTileLayerColorMaps.jet\n  },\n  {\n    label: 'Nipy Spectral',\n    id: RasterTileLayerColorMaps.nipy_spectral\n  },\n  {\n    label: 'Gist NCAR',\n    id: RasterTileLayerColorMaps.gist_ncar\n  }\n];\n\nconst STAC_SEARCH_PROVIDERS: ConfigOption[] = [\n  // {\n  //   label: 'Microsoft Planetary Computer',\n  //   id: 'microsoft'\n  // },\n  {\n    label: 'Element 84 Earth Search (AWS)',\n    id: 'earth-search'\n  }\n];\n\n/**\n * Configuration settings to be exposed through the UI\n */\nexport const rasterVisConfigs = {\n  preset: {\n    type: 'object-select',\n    defaultValue: 'trueColor',\n    label: 'Preset',\n    options: Object.values(PRESET_OPTIONS),\n    property: 'preset',\n    group: ''\n  } as VisConfigObjectSelection,\n  useSTACSearching: {\n    type: 'boolean',\n    property: 'useSTACSearching',\n    defaultValue: false,\n    label: 'Use STAC Searching'\n  } as VisConfigBoolean,\n  stacSearchProvider: {\n    type: 'object-select',\n    defaultValue: 'earth-search',\n    label: 'STAC Search Provider',\n    options: STAC_SEARCH_PROVIDERS,\n    property: 'stacSearchProvider'\n  } as VisConfigObjectSelection,\n  startDate: {\n    type: 'input',\n    defaultValue: '2020-02-02',\n    label: 'Start Date',\n    property: 'startDate'\n  } as VisConfigInput,\n  endDate: {\n    type: 'input',\n    defaultValue: '2020-03-02',\n    label: 'End Date',\n    property: 'endDate'\n  } as VisConfigInput,\n  dynamicColor: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'Dynamic Color',\n    group: '',\n    property: 'dynamicColor',\n    description: 'Use a dynamic color scale based on data visible in the viewport'\n  } as VisConfigBoolean,\n  colormapId: {\n    type: 'object-select',\n    defaultValue: 'cfastie',\n    label: 'Colormap',\n    options: COLORMAP_OPTIONS,\n    property: 'colormapId',\n    group: ''\n  } as VisConfigObjectSelection,\n  // kepler's visConfig from kepler.gl https://github.com/foursquare/kepler.gl/blob/490a8938ffa1fac025e8d1997481acc1bffe4abd/src/layers/layer-factory.js#L228\n  // key `colorRange` is required becuase it is hardcoded in kepler.gl\n  // to make shallow copy of the visConfig item\n  // https://github.com/foursquare/kepler.gl/blob/490a8938ffa1fac025e8d1997481acc1bffe4abd/src/reducers/vis-state-merger.js#L736\n  colorRange: 'colorRange' as const,\n  linearRescalingFactor: {\n    defaultValue: [0, 1],\n    // group: \"color\"\n    isRanged: true,\n    label: 'Linear Rescaling Factor',\n    property: 'linearRescalingFactor',\n    range: [0, 1],\n    step: 0.01,\n    type: 'number'\n  } as VisConfigRange,\n  // Non-linear rescaling for true-color images\n  // If false, implies linear rescaling\n  nonLinearRescaling: {\n    type: 'boolean',\n    property: 'nonLinearRescaling',\n    defaultValue: true,\n    // group: undefined\n    label: 'Non-Linear Rescaling'\n  } as VisConfigBoolean,\n  gammaContrastFactor: {\n    defaultValue: 1,\n    // group: \"color\"\n    isRanged: false,\n    label: 'Gamma Contrast',\n    property: 'gammaContrastFactor',\n    range: [0, 3],\n    step: 0.05,\n    type: 'number'\n  } as VisConfigNumber,\n  sigmoidalContrastFactor: {\n    defaultValue: 0,\n    // group: \"color\"\n    isRanged: false,\n    label: 'Sigmoidal Contrast',\n    property: 'sigmoidalContrastFactor',\n    range: [0, 50],\n    step: 1,\n    type: 'number'\n  } as VisConfigNumber,\n  sigmoidalBiasFactor: {\n    defaultValue: 0.5,\n    // group: \"color\"\n    isRanged: false,\n    label: 'Sigmoidal Bias',\n    property: 'sigmoidalBiasFactor',\n    range: [0, 1],\n    step: 0.01,\n    type: 'number'\n  } as VisConfigNumber,\n  saturationValue: {\n    defaultValue: 1,\n    // group: \"color\"\n    isRanged: false,\n    label: 'Saturation',\n    property: 'saturationValue',\n    range: [0, 2],\n    step: 0.05,\n    type: 'number'\n  } as VisConfigNumber,\n  filterEnabled: {\n    type: 'boolean',\n    property: 'filterEnabled',\n    defaultValue: false,\n    // group: undefined\n    label: 'Filter'\n  } as VisConfigBoolean,\n  filterRange: {\n    defaultValue: [-1, 1],\n    // group: \"color\"\n    isRanged: true,\n    label: 'Filter',\n    property: 'filterRange',\n    range: [-1, 1],\n    step: 0.01,\n    type: 'number'\n  } as VisConfigRange,\n  opacity: {\n    defaultValue: 1,\n    // group: \"color\"\n    isRanged: false,\n    label: 'Opacity',\n    property: 'opacity',\n    range: [0, 1],\n    step: 0.05,\n    type: 'number'\n  } as VisConfigNumber,\n  _stacQuery: {\n    defaultValue: null,\n    type: 'input'\n  } as VisConfigInput,\n  singleBandName: {\n    type: 'object-select',\n    defaultValue: null,\n    label: 'Name of single band to render',\n    // defined dynamically from STAC item/collection\n    options: [],\n    property: 'singleBandName',\n    group: ''\n  } as VisConfigObjectSelection,\n  enableTerrain: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'Enable 3D terrain',\n    group: '',\n    property: 'enableTerrain',\n    description: 'Use terrain when terrain data is available. By default enabled for 3D Map.'\n  } as VisConfigBoolean,\n  enableTerrainTopView: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'Enable in Top view',\n    group: '',\n    property: 'enableTerrainTopView'\n  } as VisConfigBoolean\n};\n"
  },
  {
    "path": "src/layers/src/raster-tile/gpu-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Functions and constants for handling webgl/luma.gl/deck.gl entities\n */\n\nimport {parse, fetchFile, load, FetchError} from '@loaders.gl/core';\nimport {ImageLoader} from '@loaders.gl/images';\nimport {NPYLoader} from '@loaders.gl/textures';\nimport GL from '@luma.gl/constants';\nimport {Texture2DProps} from '@luma.gl/webgl';\n\nimport {sleep} from '@kepler.gl/common-utils';\nimport {getLoaderOptions} from '@kepler.gl/constants';\nimport {RasterWebGL} from '@kepler.gl/deckgl-layers';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\ntype ShaderModule = RasterWebGL.ShaderModule;\nconst {\n  combineBandsFloat,\n  combineBandsInt,\n  combineBandsUint,\n  maskFloat,\n  maskInt,\n  maskUint,\n  linearRescale,\n  gammaContrast,\n  sigmoidalContrast,\n  normalizedDifference,\n  enhancedVegetationIndex,\n  soilAdjustedVegetationIndex,\n  modifiedSoilAdjustedVegetationIndex,\n  colormap: colormapModule,\n  filter,\n  saturation,\n  reorderBands,\n  rgbaImage\n} = RasterWebGL;\n\nimport {\n  CATEGORICAL_TEXTURE_WIDTH,\n  dtypeMaxValue,\n  generateCategoricalBitmapArray,\n  isColormapAllowed,\n  isFilterAllowed,\n  isRescalingAllowed\n} from './raster-tile-utils';\nimport {\n  CategoricalColormapOptions,\n  ImageData,\n  NPYLoaderDataTypes,\n  NPYLoaderResponse,\n  RenderSubLayersProps\n} from './types';\nimport {getRequestThrottle} from './request-throttle';\n\n/**\n * Describe WebGL2 Texture parameters to use for given input data type\n */\ninterface WebGLTextureFormat {\n  format: number;\n  dataFormat: number;\n  type: number;\n}\n\n/**\n * Convert TypedArray to WebGL2 Texture Parameters\n */\nfunction getWebGL2TextureParameters(data: NPYLoaderDataTypes): WebGLTextureFormat | never {\n  if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {\n    return {\n      // Note: texture data has no auto-rescaling; pixel values stay as 0-255\n      format: GL.R8UI,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.UNSIGNED_BYTE\n    };\n  }\n\n  if (data instanceof Uint16Array) {\n    return {\n      format: GL.R16UI,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.UNSIGNED_SHORT\n    };\n  }\n\n  if (data instanceof Uint32Array) {\n    return {\n      format: GL.R32UI,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.UNSIGNED_INT\n    };\n  }\n\n  if (data instanceof Int8Array) {\n    return {\n      format: GL.R8I,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.BYTE\n    };\n  }\n\n  if (data instanceof Int16Array) {\n    return {\n      format: GL.R16I,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.SHORT\n    };\n  }\n  if (data instanceof Int32Array) {\n    return {\n      format: GL.R32I,\n      dataFormat: GL.RED_INTEGER,\n      type: GL.INT\n    };\n  }\n  if (data instanceof Float32Array) {\n    return {\n      format: GL.R32F,\n      dataFormat: GL.RED,\n      type: GL.FLOAT\n    };\n  }\n\n  if (data instanceof Float64Array) {\n    return {\n      format: GL.R32F,\n      dataFormat: GL.RED,\n      type: GL.FLOAT\n    };\n  }\n\n  // For exhaustive check above; following should never occur\n  // https://stackoverflow.com/a/58009992\n  const unexpectedInput: never = data;\n  throw new Error(unexpectedInput);\n}\n\n/**\n * Discrete-valued colormaps (e.g. from the output of\n * classification algorithms) in the raster layer. Previously, the values passed to\n * `TEXTURE_MIN_FILTER` and `TEXTURE_MAG_FILTER` were `GL.LINEAR`, which meant that the GPU would\n * linearly interpolate values between two neighboring colormap pixel values. Setting these values\n * to NEAREST means that the GPU will choose the nearest value on the texture2D lookup operation,\n * which fixes precision issues for discrete-valued colormaps. This should be ok for continuous\n * colormaps as long as the color difference between each pixel on the colormap is small.\n */\nexport const COLORMAP_TEXTURE_PARAMETERS = {\n  [GL.TEXTURE_MIN_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_MAG_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,\n  [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE\n};\n\nconst DEFAULT_8BIT_TEXTURE_PARAMETERS = {\n  [GL.TEXTURE_MIN_FILTER]: GL.LINEAR_MIPMAP_LINEAR,\n  [GL.TEXTURE_MAG_FILTER]: GL.LINEAR,\n  [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,\n  [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE\n};\n\nconst DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS = {\n  [GL.TEXTURE_MIN_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_MAG_FILTER]: GL.NEAREST,\n  [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE,\n  [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE\n};\n\n/**\n * Select correct module type for \"combineBands\"\n *\n * combineBands joins up to four 2D arrays (contained in imageBands) into a single \"rgba\" image\n * texture on the GPU. That shader code needs to have the same data type as the actual image data.\n * E.g. for float data the texture needs to be `sampler2D`, for uint data the texture needs to be\n * `usampler2D` and for int data the texture needs to be `isampler2D`.\n */\nexport function getCombineBandsModule(imageBands: Texture2DProps[]): ShaderModule {\n  // Each image array is expected/required to be of the same data type\n  switch (imageBands[0].format) {\n    case GL.R8UI:\n      return combineBandsUint;\n    case GL.R16UI:\n      return combineBandsUint;\n    case GL.R32UI:\n      return combineBandsUint;\n    case GL.R8I:\n      return combineBandsInt;\n    case GL.R16I:\n      return combineBandsInt;\n    case GL.R32I:\n      return combineBandsInt;\n    case GL.R32F:\n      return combineBandsFloat;\n    default:\n      throw new Error('bad format');\n  }\n}\n\n/** Select correct image masking shader module for mask data type\n * The imageMask could (at least in the future, theoretically) be of a different data format than\n * the imageBands data itself.\n */\nexport function getImageMaskModule(imageMask: Texture2DProps): ShaderModule {\n  switch (imageMask.format) {\n    case GL.R8UI:\n      return maskUint;\n    case GL.R16UI:\n      return maskUint;\n    case GL.R32UI:\n      return maskUint;\n    case GL.R8I:\n      return maskInt;\n    case GL.R16I:\n      return maskInt;\n    case GL.R32I:\n      return maskInt;\n    case GL.R32F:\n      return maskFloat;\n    default:\n      throw new Error('bad format');\n  }\n}\n\n/**\n * Load image and wrap with default WebGL texture parameters\n *\n * @param url URL to load image\n * @param textureParams parameters to pass to Texture2D\n *\n * @return image object to pass to Texture2D constructor\n */\nexport async function loadImage(\n  url: string,\n  textureParams: Texture2DProps = {},\n  requestOptions: RequestInit = {}\n): Promise<Texture2DProps> {\n  const response = await fetchFile(url, requestOptions);\n  const image = await parse(response, ImageLoader);\n\n  return {\n    data: image,\n    parameters: DEFAULT_8BIT_TEXTURE_PARAMETERS,\n    format: GL.RGB,\n    ...textureParams\n  };\n}\n\ntype FetchLike = (url: string, options?: RequestInit) => Promise<Response>;\ntype LoadingOptions = {\n  fetch?: typeof fetch | FetchLike;\n};\n\ntype NpyRequest = {\n  url: string;\n  rasterServerUrl: string;\n  options: RequestInit;\n  rasterServerMaxRetries?: number;\n  rasterServerRetryDelay?: number;\n  rasterServerServerErrorsToRetry?: number[];\n  rasterServerMaxPerServerRequests?: number;\n};\n\n/**\n * Load NPY Array\n *\n * The NPY format is described here: https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html.\n * It's designed to be a very simple file format to hold an N-dimensional block of data. The header describes the data type, shape, and order (either C or Fortran) of the array.\n *\n * @param url URL to load NPY Array\n * @param split Whether to split single typed array representing an N-dimensional array into an Array with each dimension as its own typed array\n *\n * @return image object to pass to Texture2D constructor\n */\nexport async function loadNpyArray(\n  request: NpyRequest,\n  split: true,\n  options?: LoadingOptions\n): Promise<Texture2DProps[] | null>;\nexport async function loadNpyArray(\n  request: NpyRequest,\n  split: false,\n  options?: LoadingOptions\n): Promise<Texture2DProps | null>;\nexport async function loadNpyArray(\n  request: NpyRequest,\n  split: boolean,\n  options?: LoadingOptions\n): Promise<Texture2DProps | Texture2DProps[] | null> {\n  const numAttempts =\n    1 + (request.rasterServerMaxRetries ?? getApplicationConfig().rasterServerMaxRetries);\n\n  const asset = await getRequestThrottle().throttleRequest(\n    request.rasterServerUrl,\n    async () => {\n      for (let attempt = 0; attempt < numAttempts; attempt++) {\n        try {\n          const {npy: npyOptions} = getLoaderOptions();\n          const response: NPYLoaderResponse = await load(request.url, NPYLoader, {\n            npy: npyOptions,\n            fetch: options?.fetch\n          });\n\n          if (!response || !response.data || request.options.signal?.aborted) {\n            return null;\n          }\n\n          // Float64 data needs to be coerced to Float32 for the GPU\n          if (response.data instanceof Float64Array) {\n            response.data = Float32Array.from(response.data);\n          }\n\n          const {data, header} = response;\n          const {shape} = header;\n          const {format, dataFormat, type} = getWebGL2TextureParameters(data);\n\n          // TODO: check height-width or width-height\n          // Regardless, images usually square\n          // TODO: handle cases of 256x256x1 instead of 1x256x256\n          const [z, height, width] = shape;\n\n          // Since we now use WebGL2 data types for 8-bit textures, we set the following for all textures\n          const mipmaps = false;\n          const parameters = DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS;\n\n          if (!split) {\n            return {\n              data,\n              width,\n              height,\n              format,\n              dataFormat,\n              type,\n              parameters,\n              mipmaps\n            };\n          }\n\n          // Split into individual arrays\n          const channels: Texture2DProps[] = [];\n          const channelSize = height * width;\n          for (let i = 0; i < z; i++) {\n            channels.push({\n              data: data.subarray(i * channelSize, (i + 1) * channelSize),\n              width,\n              height,\n              format,\n              dataFormat,\n              type,\n              parameters,\n              mipmaps\n            });\n          }\n          return channels;\n        } catch (error) {\n          // Retry if Service Temporarily Unavailable 503 error etc.\n          if (\n            attempt < numAttempts &&\n            error instanceof FetchError &&\n            (\n              request.rasterServerServerErrorsToRetry ??\n              getApplicationConfig().rasterServerServerErrorsToRetry\n            )?.includes(error.response?.status as number)\n          ) {\n            await sleep(\n              request.rasterServerRetryDelay ?? getApplicationConfig().rasterServerRetryDelay\n            );\n            continue;\n          }\n        }\n      }\n      return null;\n    },\n    request.rasterServerMaxPerServerRequests\n  );\n\n  return asset;\n}\n\n/**\n * Create texture data for categorical colormap scale\n * @param categoricalOptions - color map configuration and min-max values of categorical band\n * @returns texture data\n */\nexport function generateCategoricalColormapTexture(\n  categoricalOptions: CategoricalColormapOptions\n): Texture2DProps {\n  const data = generateCategoricalBitmapArray(categoricalOptions);\n  return {\n    data,\n    width: CATEGORICAL_TEXTURE_WIDTH,\n    height: 1,\n    format: GL.RGBA,\n    dataFormat: GL.RGBA,\n    type: GL.UNSIGNED_BYTE,\n    parameters: COLORMAP_TEXTURE_PARAMETERS,\n    mipmaps: false\n  };\n}\n\n// TODO: would probably be simpler to only pass in the props actually used by this function. That\n// would mean a smaller object than RenderSubLayersProps\n// eslint-disable-next-line max-statements, complexity\nexport function getModules({\n  images,\n  props\n}: {\n  images: Partial<ImageData>;\n  props?: RenderSubLayersProps;\n}): {\n  modules: ShaderModule[];\n  moduleProps: Record<string, any>;\n} {\n  const moduleProps: Record<string, any> = {};\n  // Array of luma.gl WebGL modules to pass to the RasterLayer\n  const modules: ShaderModule[] = [];\n\n  // use rgba image directly. Used for raster .pmtiles rendering\n  if (images.imageRgba) {\n    modules.push(rgbaImage);\n\n    // no support for other modules atm for direct rgba mode\n    return {modules, moduleProps};\n  }\n\n  if (!props) {\n    return {modules, moduleProps};\n  }\n\n  const {\n    renderBandIndexes,\n    nonLinearRescaling,\n    linearRescalingFactor,\n    minPixelValue,\n    maxPixelValue,\n    gammaContrastFactor,\n    sigmoidalContrastFactor,\n    sigmoidalBiasFactor,\n    saturationValue,\n    bandCombination,\n    filterEnabled,\n    filterRange,\n    dataType,\n    minCategoricalBandValue,\n    maxCategoricalBandValue,\n    hasCategoricalColorMap\n  } = props;\n\n  if (Array.isArray(images.imageBands) && images.imageBands.length > 0) {\n    modules.push(getCombineBandsModule(images.imageBands));\n  }\n\n  if (images.imageMask) {\n    modules.push(getImageMaskModule(images.imageMask));\n    // In general, data masks are 0 for nodata and the maximum value for valid data, e.g. 255 or\n    // 65535 for uint8 or uint16 data, respectively\n    moduleProps.maskKeepMin = 1;\n  }\n\n  if (Array.isArray(renderBandIndexes)) {\n    modules.push(reorderBands);\n    moduleProps.ordering = renderBandIndexes;\n  }\n\n  const globalRange = maxPixelValue - minPixelValue;\n  // Fix rescaling if we are sure that dataset is categorical\n  if (hasCategoricalColorMap) {\n    modules.push(linearRescale);\n    moduleProps.linearRescaleScaler = 1 / maxPixelValue;\n    moduleProps.linearRescaleOffset = 0;\n  } else if (isRescalingAllowed(bandCombination)) {\n    if (!nonLinearRescaling) {\n      const [min, max] = linearRescalingFactor;\n      const localRange = max - min;\n\n      // Add linear rescaling module\n      modules.push(linearRescale);\n\n      // Divide by local range * global range\n      moduleProps.linearRescaleScaler = 1 / (localRange * globalRange);\n\n      // Subtract off the local min\n      moduleProps.linearRescaleOffset = -min;\n\n      // Clamp to [0, 1] done automatically?\n    } else {\n      modules.push(linearRescale);\n      moduleProps.linearRescaleScaler = 1 / maxPixelValue;\n      moduleProps.linearRescaleOffset = 0;\n\n      modules.push(gammaContrast);\n      moduleProps.gammaContrastValue = gammaContrastFactor;\n\n      modules.push(sigmoidalContrast);\n      moduleProps.sigmoidalContrast = sigmoidalContrastFactor;\n      moduleProps.sigmoidalBias = sigmoidalBiasFactor;\n    }\n\n    if (Number.isFinite(saturationValue) && saturationValue !== 1) {\n      modules.push(saturation);\n      moduleProps.saturationValue = saturationValue;\n    }\n  }\n\n  switch (bandCombination) {\n    case 'normalizedDifference':\n      modules.push(normalizedDifference);\n      break;\n    case 'enhancedVegetationIndex':\n      modules.push(enhancedVegetationIndex);\n      break;\n    case 'soilAdjustedVegetationIndex':\n      modules.push(soilAdjustedVegetationIndex);\n      break;\n    case 'modifiedSoilAdjustedVegetationIndex':\n      modules.push(modifiedSoilAdjustedVegetationIndex);\n      break;\n    default:\n      break;\n  }\n\n  if (isFilterAllowed(bandCombination) && filterEnabled) {\n    modules.push(filter);\n    moduleProps.filterMin1 = filterRange[0];\n    moduleProps.filterMax1 = filterRange[1];\n  }\n\n  // Apply colormap\n  if (isColormapAllowed(bandCombination) && images.imageColormap) {\n    modules.push(colormapModule);\n    moduleProps.minCategoricalBandValue = minCategoricalBandValue;\n    moduleProps.maxCategoricalBandValue = maxCategoricalBandValue;\n    moduleProps.dataTypeMaxValue = dtypeMaxValue[dataType];\n    moduleProps.maxPixelValue = maxPixelValue;\n  }\n\n  return {modules, moduleProps};\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/image.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Utility functions to create objects to pass to deck.gl-raster\n */\n\nimport {load, FetchError} from '@loaders.gl/core';\nimport {QuantizedMeshLoader} from '@loaders.gl/terrain';\nimport memoize from 'lodash/memoize';\n\nimport {sleep} from '@kepler.gl/common-utils';\nimport {getLoaderOptions} from '@kepler.gl/constants';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {CATEGORICAL_COLORMAP_ID} from './config';\nimport {\n  loadNpyArray,\n  generateCategoricalColormapTexture,\n  loadImage,\n  COLORMAP_TEXTURE_PARAMETERS\n} from './gpu-utils';\nimport {CATEGORICAL_TEXTURE_WIDTH, generateCategoricalBitmapArray} from './raster-tile-utils';\nimport {\n  GetTileDataProps,\n  ImageData,\n  TerrainData,\n  AssetRequestData,\n  CategoricalColormapOptions,\n  ColormapImageData\n} from './types';\nimport {\n  getTitilerUrl,\n  getTerrainUrl,\n  getSingleCOGUrlParams,\n  getStacApiUrlParams,\n  getMeshMaxError,\n  RasterLayerResources\n} from './url';\nimport {getRequestThrottle} from './request-throttle';\n\n/**\n * Create dataUrl image from bitmap array\n * @param bitmap - RGBA byte bitmap array\n * @param width - width of the image\n * @param height - height of the image\n * @returns base64 data url of the image\n */\nexport function getImageDataURL(bitmap: Uint8Array, width: number, height: number): string | null {\n  const canvas = document.createElement('canvas');\n  if (!canvas.getContext) {\n    return null;\n  }\n  canvas.setAttribute('width', `${width}`);\n  canvas.setAttribute('height', `${height}`);\n  const ctx = canvas.getContext('2d');\n  if (!ctx) {\n    return null;\n  }\n  for (let i = 0; i < width; i++) {\n    const [r, g, b, a] = bitmap.subarray(i * 4, i * 4 + 4);\n    if (a !== 0) {\n      ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n      ctx.fillRect(i, 0, 1, 1);\n    }\n  }\n  return canvas.toDataURL('image/png');\n}\n\n// Global multiplier to be applied to terrain mesh resolution\n// A smaller number towards 0 will cause generation of mesh data with a smaller max error and thus a\n// higher resolution mesh\nconst MESH_MULTIPLIER = 0.8;\n\n/**\n * Multiplier used to calculate the height of terrain mesh skirts.\n * The skirt is an extension of the terrain mesh that helps prevent gaps between terrain tiles\n * by extending the mesh edges downward. This multiplier is applied to the mesh's maximum error\n * to determine the skirt height, ensuring no gaps are visible.\n */\nconst SKIRT_HEIGHT_MULTIPLIER = 3;\n\n/**\n * Load images required for raster tile\n * @param assetRequests - STAC asset requests\n * @param colormapId - colormap id\n * @param categoricalOptions - categorical options requred for making of categorical colormap\n * @returns images map\n */\nexport async function loadImages(\n  assetRequests: AssetRequestData[],\n  colormapId: string,\n  categoricalOptions: CategoricalColormapOptions\n): Promise<ImageData> {\n  // We load image data (single or multi asset based on number of requests) and colormap in parallel\n  const [stacImages, colormap] = await Promise.all([\n    loadSTACImageData(assetRequests),\n    (colormapId !== CATEGORICAL_COLORMAP_ID && memoizedLoadColormap(colormapId)) || null\n  ]);\n  let imageColormap = colormap;\n  if (colormapId === CATEGORICAL_COLORMAP_ID) {\n    imageColormap = generateCategoricalColormapTexture(categoricalOptions);\n  }\n  return {\n    ...stacImages,\n    imageColormap\n  };\n}\n\n/**\n * Cache loading of colormap. Each colormap file is very small, on the order of a few hundred bytes,\n * so a global cache like this is not expected to eat up too much memory.\n * Most colormaps are loaded from CDN. In case colormapId is categorical, the image is created from\n * categorical colormap options.\n * NOTE: this implementation will cache promise rejections as well, but since these are small files\n * on the CDN, that's an acceptable risk for the time being.\n */\nconst memoizedLoadColormap: (colormapId: string) => Promise<ColormapImageData | null> | null =\n  memoize((colormapId: string) => {\n    if (colormapId === CATEGORICAL_COLORMAP_ID) {\n      return null;\n    } else {\n      return loadColormap(colormapId);\n    }\n  });\n\n/**\n * Load PNG colormap.\n * Since the colormap files are so small (couple hundred bytes), we load them always, even for an\n * RGB composite where the colormap won't be used. After the first load, the colormap should be\n * loaded from the disk cache. By loading always, we can prevent unnecessarily triggering\n * getTileData, which reduces flashing from reloading the images on the GPU.\n *\n * @param colormapId      ID of colormap PNG\n *\n * @returns image object to pass to Texture2D constructor\n */\nasync function loadColormap(colormapId: string): Promise<ColormapImageData | null> {\n  if (!colormapId) {\n    return null;\n  }\n\n  const request = await getAssetRequest({\n    url: RasterLayerResources.rasterColorMap(colormapId),\n    rasterServerUrl: '',\n    options: {}\n  });\n  return loadImage(request.url, COLORMAP_TEXTURE_PARAMETERS, request.options);\n}\n\n/**\n * Get the request data for loading a single or multi asset STAC metadata object.\n * @param options STAC metadata object and user-defined parameters.\n * @returns Image data, or null if the STAC metadata object is not supported.\n */\nexport async function getSTACImageRequests(\n  options: GetTileDataProps\n): Promise<AssetRequestData[] | null> {\n  const {loadAssetIds} = options;\n\n  if (loadAssetIds.length === 1) {\n    const request = await getSingleAssetSTACRequest(options);\n    return request ? [request] : null;\n  }\n\n  return await getMultiAssetSTACRequest(options);\n}\n\n// TODO: image loading should really be _driven by the modules_\n// E.g. the user chooses what kind of pipeline they want on the GPU, then from those modules we can\n// see whether the user wants the pansharpened band, colormap, etc\n/**\n * Load images defined by a STAC metadata object plus user-defined parameters\n */\nexport async function loadSTACImageData(requests: AssetRequestData[]): Promise<ImageData> {\n  return requests.length === 1 ? loadSingleAssetSTAC(requests[0]) : loadMultiAssetSTAC(requests);\n}\n\n/**\n * Utility function that forms an asset request based on given parameters.\n */\nexport async function getAssetRequest({\n  url,\n  rasterServerUrl,\n  params,\n  options,\n  useMask = true,\n  responseRequiredBandIndices\n}: {\n  url: string;\n  rasterServerUrl: string;\n  params?: URLSearchParams | null;\n  options: RequestInit;\n  useMask?: boolean;\n  responseRequiredBandIndices?: number[] | null;\n}): Promise<AssetRequestData> {\n  const requestUrl = url;\n  const requestParams = params ?? new URLSearchParams();\n  const requestOptions = options;\n\n  const assetUrl = requestParams ? `${requestUrl}?${requestParams.toString()}` : requestUrl;\n  return {\n    url: assetUrl,\n    rasterServerUrl,\n    options: requestOptions,\n    useMask,\n    responseRequiredBandIndices\n  };\n}\n\n/**\n * Utility function for forming a request for a single asset STAC metadata object.\n */\n// eslint-disable-next-line complexity, max-statements\nasync function getSingleAssetSTACRequest(\n  options: GetTileDataProps\n): Promise<AssetRequestData | null> {\n  const {\n    stac,\n    loadAssetIds,\n    loadBandIndexes,\n    signal,\n    useSTACSearching,\n    index: {x, y, z},\n    stacSearchProvider,\n    startDate,\n    endDate,\n    _stacQuery\n  } = options;\n\n  const useMask = true;\n\n  // Only a single URL because only a single asset\n  let urlInfo: {url: string; rasterServerUrl: string} | null = null;\n  let urlParams: URLSearchParams | null = new URLSearchParams();\n  let responseRequiredBandIndices: number[] | null = loadBandIndexes;\n  if (useSTACSearching && stac.type !== 'Feature') {\n    urlParams = getStacApiUrlParams({\n      stac,\n      stacSearchProvider,\n      startDate,\n      endDate,\n      mask: useMask,\n      loadAssetIds,\n      _stacQuery\n    });\n    urlInfo = getTitilerUrl({stac, useSTACSearching, x, y, z});\n  } else if (stac.type === 'Feature') {\n    // stac is an Item\n    urlParams = getSingleCOGUrlParams({\n      stac,\n      loadAssetId: loadAssetIds[0],\n      loadBandIndexes,\n      mask: useMask\n    });\n    urlInfo = getTitilerUrl({stac, useSTACSearching, x, y, z});\n    responseRequiredBandIndices = null;\n  } else {\n    return null;\n  }\n\n  if (!urlInfo.url) {\n    return null;\n  }\n\n  const asset = await getAssetRequest({\n    ...urlInfo,\n    params: urlParams,\n    options: {signal},\n    useMask,\n    responseRequiredBandIndices\n  });\n  // Pass per-layer override for retries if present on STAC metadata\n  return {\n    ...asset,\n    rasterServerMaxRetries: stac.rasterServerMaxRetries,\n    rasterServerRetryDelay: stac.rasterServerRetryDelay,\n    rasterServerServerErrorsToRetry: stac.rasterServerServerErrorsToRetry,\n    rasterServerMaxPerServerRequests: stac.rasterServerMaxPerServerRequests\n  };\n}\n\n/**\n * Utility function for forming requests for a multi asset STAC metadata object.\n */\nasync function getMultiAssetSTACRequest(\n  options: GetTileDataProps\n): Promise<AssetRequestData[] | null> {\n  const {\n    stac,\n    loadAssetIds,\n    loadBandIndexes,\n    signal,\n    useSTACSearching,\n    stacSearchProvider,\n    startDate,\n    endDate,\n    index: {x, y, z},\n    _stacQuery\n  } = options;\n\n  // Multiple urls, one for each asset\n  let requestData: {\n    url: string;\n    rasterServerUrl: string;\n    params: URLSearchParams | null;\n    options?: RequestInit;\n  }[] = [];\n\n  // We assume that there's only one validity mask for the entire asset, and therefore any of the\n  // requests would return the same validity bitmap. Therefore we only request a mask for the first\n  // asset\n  const requestMask: boolean[] = new Array(loadAssetIds.length).fill(false);\n  requestMask[0] = true;\n\n  if (useSTACSearching && stac.type !== 'Feature') {\n    requestData = zip(loadAssetIds, requestMask).map(([assetId, mask]) => {\n      const params = getStacApiUrlParams({\n        stac,\n        stacSearchProvider,\n        startDate,\n        endDate,\n        mask,\n        loadAssetIds: [assetId],\n        _stacQuery\n      });\n      const urlInfo = getTitilerUrl({stac, useSTACSearching, x, y, z});\n      return {...urlInfo, params};\n    });\n  } else if (stac.type === 'Feature') {\n    requestData = zip3(loadAssetIds, loadBandIndexes, requestMask).map(\n      ([assetId, bandIndex, mask]) => {\n        const params = getSingleCOGUrlParams({\n          stac,\n          loadAssetId: assetId,\n          loadBandIndexes: [bandIndex],\n          mask\n        });\n        const urlInfo = getTitilerUrl({stac, useSTACSearching, x, y, z});\n        return {...urlInfo, params};\n      }\n    );\n  }\n\n  if (isValidRequestData(requestData)) {\n    const assets = await Promise.all(\n      requestData.map(request =>\n        getAssetRequest({...request, options: request.options ?? {signal}})\n      )\n    );\n    // Propagate per-layer retry override\n    return assets.map(asset => ({\n      ...asset,\n      rasterServerMaxRetries: options.stac.rasterServerMaxRetries,\n      rasterServerRetryDelay: options.stac.rasterServerRetryDelay,\n      rasterServerServerErrorsToRetry: options.stac.rasterServerServerErrorsToRetry,\n      rasterServerMaxPerServerRequests: options.stac.rasterServerMaxPerServerRequests\n    }));\n  }\n\n  return null;\n}\n\n/**\n * Load image data when consolidated in bands within a single STAC Asset\n */\nasync function loadSingleAssetSTAC(request: AssetRequestData): Promise<ImageData> {\n  const imageBands = await loadNpyArray(request, true);\n\n  if (!imageBands) {\n    return {\n      imageBands\n    };\n  }\n\n  const imageMask = (request.useMask && imageBands.pop()) || null;\n  let mappedImageBands = imageBands;\n  if (request.responseRequiredBandIndices) {\n    mappedImageBands = request.responseRequiredBandIndices.map(i => imageBands[i]);\n  }\n  return {imageBands: mappedImageBands, imageMask};\n}\n\n/**\n * Load image data when split among multiple STAC Assets\n */\nasync function loadMultiAssetSTAC(requests: AssetRequestData[]): Promise<ImageData> {\n  const results = await Promise.all(requests.map(request => loadNpyArray(request, true)));\n  // The first request includes a mask\n  const imageMask = results[0]?.pop() || null;\n  const imageBands = results.flat().filter(band => band !== null) as ImageData['imageBands'];\n  return {imageBands, imageMask};\n}\n\n/**\n * Iterate over two arrays simultaneously, similar to Python's zip builtin\n */\nfunction zip<T1, T2>(a: T1[], b: T2[]): [T1, T2][] {\n  return a.map((k, i) => [k, b[i]]);\n}\n\n/**\n * Iterate over three arrays simultaneously, similar to Python's zip builtin\n */\nfunction zip3<T1, T2, T3>(a: T1[], b: T2[], c: T3[]): [T1, T2, T3][] {\n  return a.map((k, i) => [k, b[i], c[i]]);\n}\n\n/**\n * Type guard to check if all array elements are strings\n */\nfunction isValidRequestData(\n  arr: {url: unknown; params: unknown; options?: unknown}[]\n): arr is {url: string; params: URLSearchParams; options: unknown}[] {\n  return arr.every(x => typeof x?.url === 'string' && x?.params !== null);\n}\n\n/**\n * Create base64 image data url for categorical colormap\n * @param categoricalOptions - color map configuration and min-max values of categorical band\n * @returns base64 image data url\n */\nexport function getCategoricalColormapDataUrl(\n  categoricalOptions: CategoricalColormapOptions\n): string | null {\n  const bitmap = generateCategoricalBitmapArray(categoricalOptions);\n  if (!bitmap) {\n    return null;\n  }\n  return getImageDataURL(bitmap, CATEGORICAL_TEXTURE_WIDTH, 1);\n}\n\n/**\n * Load terrain mesh from raster tile server\n * @param props - properties to load terrain data\n * @returns terrain mesh data\n */\nexport async function loadTerrain(props: {\n  index: {x: number; y: number; z: number};\n  signal: AbortSignal;\n  rasterTileServerUrls: string[];\n  boundsForGeometry?: [number, number, number, number];\n  rasterServerMaxRetries: number | undefined;\n  rasterServerRetryDelay: number | undefined;\n  rasterServerServerErrorsToRetry?: number[];\n  rasterServerMaxPerServerRequests?: number;\n}): Promise<TerrainData | null> {\n  const {\n    index: {x, y, z},\n    boundsForGeometry,\n    signal,\n    rasterTileServerUrls,\n    rasterServerMaxRetries,\n    rasterServerMaxPerServerRequests\n  } = props;\n\n  const meshMaxError = getMeshMaxError(z, MESH_MULTIPLIER);\n  const terrainUrlInfo = getTerrainUrl(rasterTileServerUrls, x, y, z, meshMaxError);\n  const loaderOptions = getLoaderOptions();\n  const numAttempts = 1 + (rasterServerMaxRetries ?? getApplicationConfig().rasterServerMaxRetries);\n\n  const mesh = await getRequestThrottle().throttleRequest(\n    terrainUrlInfo.rasterServerUrl,\n    async () => {\n      for (let attempt = 0; attempt < numAttempts; attempt++) {\n        try {\n          return (await load(terrainUrlInfo.url, QuantizedMeshLoader, {\n            fetch: {signal},\n            'quantized-mesh': {\n              ...loaderOptions['quantized-mesh'],\n              bounds: boundsForGeometry,\n              skirtHeight: meshMaxError * SKIRT_HEIGHT_MULTIPLIER\n            }\n          })) as Promise<TerrainData>;\n        } catch (error) {\n          // Retry if Service Temporarily Unavailable 503 error etc.\n          if (\n            attempt < numAttempts &&\n            error instanceof FetchError &&\n            (\n              props.rasterServerServerErrorsToRetry ??\n              getApplicationConfig().rasterServerServerErrorsToRetry\n            )?.includes(error.response?.status as number)\n          ) {\n            await sleep(\n              props.rasterServerRetryDelay ?? getApplicationConfig().rasterServerRetryDelay\n            );\n            continue;\n          }\n        }\n      }\n      return null;\n    },\n    rasterServerMaxPerServerRequests\n  );\n\n  return mesh;\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/raster-tile-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport PropTypes from 'prop-types';\nimport React, {Component} from 'react';\n\nimport {Base} from '../base';\n\nclass RasterTileIcon extends Component {\n  static propTypes = {\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string.isRequired)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'raster-tile-layer-icon',\n    totalColor: 2,\n    viewBox: '6 2 50 50'\n  };\n\n  render(): React.ReactNode {\n    return (\n      <Base {...this.props}>\n        <g>\n          <path\n            d=\"M38.244 36.0125C39.0513 35.2052 39.0513 33.8928 38.244 33.0855C37.6319 32.4735 36.6666 32.3479 35.9007 32.6936L33.8528 30.6463L37.2683 27.2302C37.398 27.1004 37.4705 26.9252 37.4705 26.7423C37.4705 26.5595 37.398 26.3835 37.2683 26.2545L31.9001 20.887L32.8764 19.9113L34.3399 21.3755C34.4745 21.5101 34.6511 21.5777 34.8278 21.5777C35.0044 21.5777 35.181 21.5101 35.3156 21.3755L42.1459 14.5452C42.4157 14.2754 42.4157 13.8393 42.1459 13.5695L38.2433 9.66621C37.9735 9.39642 37.5374 9.39642 37.2676 9.66621L30.4366 16.4965C30.3069 16.6262 30.2344 16.8015 30.2344 16.9843C30.2344 17.1672 30.3069 17.3431 30.4366 17.4722L31.9001 18.9364L30.9244 19.912L28.4853 17.4729C28.0513 17.0389 27.4392 16.8091 26.7161 16.8091C25.2078 16.8091 23.2682 17.811 21.655 19.4242C20.5379 20.5413 19.7016 21.8199 19.3 23.0253C18.8439 24.3922 18.9874 25.539 19.703 26.2545L22.1428 28.6943L21.1671 29.6707L19.703 28.2065C19.5732 28.0768 19.398 28.0043 19.2151 28.0043C19.0323 28.0043 18.8563 28.0768 18.7273 28.2065L11.897 35.0368C11.7673 35.1658 11.6948 35.3411 11.6948 35.5246C11.6948 35.7082 11.7673 35.8834 11.897 36.0125L15.8003 39.9151C15.9349 40.0497 16.1115 40.1173 16.2882 40.1173C16.4648 40.1173 16.6414 40.0497 16.776 39.9151L23.6063 33.0855C23.736 32.9558 23.8085 32.7812 23.8085 32.5977C23.8085 32.4141 23.736 32.2389 23.6063 32.1098L22.1421 30.6463L23.1185 29.67L28.4853 35.0368C28.615 35.1665 28.7903 35.239 28.9731 35.239C29.156 35.239 29.3319 35.1665 29.4609 35.0368L32.8764 31.622L34.9182 33.663C34.5538 34.4317 34.6808 35.377 35.3156 36.0125C35.7061 36.403 36.2264 36.619 36.7798 36.619C37.3325 36.619 37.8527 36.403 38.244 36.0125Z\"\n            fill=\"currentColor\"\n          />\n          <path\n            d=\"M36.6146 39.3087C37.9049 39.3087 39.1173 38.8063 40.0281 37.8955C40.9402 36.984 41.4426 35.771 41.4419 34.48C41.4419 34.0985 41.1328 33.79 40.7519 33.79C40.3703 33.79 40.0619 34.0992 40.0619 34.48C40.0619 35.4026 39.7031 36.2692 39.0524 36.9199C38.4024 37.5699 37.5365 37.9287 36.6146 37.9287C36.6133 37.9287 36.6119 37.9287 36.6119 37.9287C36.231 37.9287 35.9219 38.2371 35.9219 38.618C35.9219 38.9995 36.2303 39.3087 36.6112 39.3087C36.6126 39.3087 36.6133 39.3087 36.6146 39.3087Z\"\n            fill=\"currentColor\"\n          />\n          <path\n            d=\"M36.6119 40.69C36.2303 40.69 35.9219 40.9985 35.9219 41.38C35.9219 41.7616 36.2303 42.07 36.6119 42.07C40.7974 42.07 44.2026 38.6649 44.2026 34.48C44.2026 34.0985 43.8941 33.79 43.5126 33.79C43.131 33.79 42.8226 34.0985 42.8226 34.48C42.8226 37.9045 40.0363 40.69 36.6119 40.69Z\"\n            fill=\"currentColor\"\n          />\n          <path\n            d=\"M46.2719 33.79C45.8903 33.79 45.5819 34.0985 45.5819 34.48C45.5819 39.426 41.5578 43.45 36.6119 43.45C36.2303 43.45 35.9219 43.7585 35.9219 44.14C35.9219 44.5216 36.2303 44.83 36.6119 44.83C42.3189 44.83 46.9619 40.187 46.9619 34.48C46.9619 34.0985 46.6534 33.79 46.2719 33.79Z\"\n            fill=\"currentColor\"\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n\nexport default RasterTileIcon;\n"
  },
  {
    "path": "src/layers/src/raster-tile/raster-tile-layer-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\n\nexport const RasterTileLayerColorMaps = keyMirror({\n  cfastie: null,\n  rplumbo: null,\n  schwarzwald: null,\n  viridis: null,\n  plasma: null,\n  inferno: null,\n  magma: null,\n  cividis: null,\n  greys: null,\n  purples: null,\n  blues: null,\n  greens: null,\n  oranges: null,\n  reds: null,\n  ylorbr: null,\n  ylorrd: null,\n  orrd: null,\n  purd: null,\n  rdpu: null,\n  bupu: null,\n  gnbu: null,\n  pubu: null,\n  ylgnbu: null,\n  pubugn: null,\n  bugn: null,\n  ylgn: null,\n  binary: null,\n  gray: null,\n  bone: null,\n  pink: null,\n  spring: null,\n  summer: null,\n  autumn: null,\n  winter: null,\n  cool: null,\n  wistia: null,\n  hot: null,\n  afmhot: null,\n  gist_heat: null,\n  copper: null,\n  piyg: null,\n  prgn: null,\n  brbg: null,\n  puor: null,\n  rdgy: null,\n  rdbu: null,\n  rdylbu: null,\n  rdylgn: null,\n  spectral: null,\n  coolwarm: null,\n  bwr: null,\n  seismic: null,\n  twilight: null,\n  twilight_shifted: null,\n  hsv: null,\n  flag: null,\n  prism: null,\n  ocean: null,\n  gist_earth: null,\n  terrain: null,\n  gist_stern: null,\n  gnuplot: null,\n  gnuplot2: null,\n  cmrmap: null,\n  cubehelix: null,\n  brg: null,\n  gist_rainbow: null,\n  rainbow: null,\n  jet: null,\n  nipy_spectral: null,\n  gist_ncar: null\n});\n\nexport const RasterTileLayerPresets = keyMirror({\n  trueColor: null,\n  infrared: null,\n  agriculture: null,\n  forestBurn: null,\n  ndvi: null,\n  savi: null,\n  msavi: null,\n  ndmi: null,\n  nbr: null,\n  nbr2: null,\n  singleBand: null\n});\n"
  },
  {
    "path": "src/layers/src/raster-tile/raster-tile-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {COORDINATE_SYSTEM, Layer as DeckLayer} from '@deck.gl/core/typed';\nimport {TileLayer, GeoBoundingBox} from '@deck.gl/geo-layers/typed';\nimport {PMTilesSource, PMTilesTileSource} from '@loaders.gl/pmtiles';\nimport {Texture2DProps} from '@luma.gl/webgl';\nimport memoize from 'lodash/memoize';\n\nimport {PathLayer} from '@deck.gl/layers/typed';\nimport {DatasetType, PMTilesType, LAYER_TYPES} from '@kepler.gl/constants';\nimport {RasterLayer, RasterMeshLayer} from '@kepler.gl/deckgl-layers';\nimport {\n  LayerBaseConfig,\n  LayerColorConfig,\n  LayerHeightConfig,\n  VisualChannel,\n  VisConfigColorRange,\n  VisConfigSelection,\n  VisConfigBoolean,\n  VisConfigInput,\n  VisConfigRange,\n  VisConfigNumber\n} from '@kepler.gl/types';\n\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {Datasets, KeplerTable as KeplerDataset, VectorTileMetadata} from '@kepler.gl/table';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {rasterVisConfigs, PRESET_OPTIONS, DATA_SOURCE_COLOR_DEFAULTS} from './config';\nimport {getModules} from './gpu-utils';\nimport {getSTACImageRequests, loadImages, loadTerrain} from './image';\nimport RasterIcon from './raster-tile-icon';\nimport {\n  getDataSourceParams,\n  getMaxRequests,\n  bboxIntersects,\n  computeZRange,\n  getSTACBounds,\n  getEOBands,\n  filterAvailablePresets,\n  getSingleBandPresetOptions,\n  getImageMinMax,\n  getMinMaxFromTile2DHeaders\n} from './raster-tile-utils';\nimport {\n  GetTileDataCustomProps,\n  GetTileDataDefaultProps,\n  GetTileDataProps,\n  RenderSubLayersProps,\n  GetTileDataOutput,\n  DataSourceParams,\n  KeplerRasterDataset,\n  Tile2DHeader,\n  PresetOption,\n  CompleteSTACObject\n} from './types';\n\nimport {FindDefaultLayerPropsReturnValue} from '../layer-utils';\nimport {default as KeplerLayer, LayerBaseConfigPartial} from '../base-layer';\n\n// Adjust tileSize to devicePixelRatio, but not higher than 2 to reduce requests to server\nconst devicePixelRatio: number = Math.min(\n  2,\n  (typeof window !== 'undefined' && window.devicePixelRatio) || 1\n);\n\n// Global counter that represents the number of tiles currently being loaded across all the raster tile layers\nlet tilesBeingLoaded = 0;\n\n// This is a temp solution to track loading\nexport const getNumRasterTilesBeingLoaded = () => {\n  return tilesBeingLoaded;\n};\n\nexport const LOAD_ELEVATION_AFTER_ZOOM = 8.9;\n\nconst getShouldLoadTerrain = (stac, mapState, visConfig) => {\n  return Boolean(\n    // we need a raster tile server for elevations even when we user PMTiles\n    stac.rasterTileServerUrls?.length > 0 &&\n      // check the switch from the layer configurator\n      visConfig.enableTerrain &&\n      // disabled in Top view by default\n      (mapState.dragRotate || visConfig.enableTerrainTopView) &&\n      (stac.rasterServerSupportsElevation ?? getApplicationConfig().rasterServerSupportsElevation)\n  );\n};\n\nexport type RasterTileLayerVisConfigCommonSettings = {\n  opacity: VisConfigNumber;\n  enableTerrain: VisConfigBoolean;\n  enableTerrainTopView: VisConfigBoolean;\n};\n\nexport type RasterTileLayerVisConfigSettings = RasterTileLayerVisConfigCommonSettings & {\n  preset: VisConfigSelection;\n  useSTACSearching: VisConfigBoolean;\n  stacSearchProvider: VisConfigSelection;\n  startDate: VisConfigInput;\n  endDate: VisConfigInput;\n  dynamicColor: VisConfigBoolean;\n  colormapId: VisConfigSelection;\n  colorRange: VisConfigColorRange;\n  linearRescalingFactor: VisConfigRange;\n  nonLinearRescaling: VisConfigBoolean;\n  gammaContrastFactor: VisConfigNumber;\n  sigmoidalContrastFactor: VisConfigNumber;\n  sigmoidalBiasFactor: VisConfigNumber;\n  saturationValue: VisConfigNumber;\n  filterEnabled: VisConfigBoolean;\n  filterRange: VisConfigRange;\n  _stacQuery: VisConfigInput;\n  singleBandName: VisConfigSelection;\n};\n\ntype RasterTileLayerMeta = {\n  zRange: number[] | null;\n};\n\nexport default class RasterTileLayer extends KeplerLayer {\n  declare visConfigSettings: RasterTileLayerVisConfigSettings;\n  declare meta: RasterTileLayerMeta;\n\n  /** Min bands image data value, based on the current viewport */\n  minViewportPixelValue = Infinity;\n  /** Max bands image data value, based on the current viewport */\n  maxViewportPixelValue = -Infinity;\n  /** Memoized method that calculates data source params */\n\n  getDataSourceParams: (\n    stac: CompleteSTACObject,\n    preset: string,\n    presetOptions: PresetOption\n  ) => DataSourceParams | null;\n\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  constructor(props) {\n    super(props);\n    this.registerVisConfig(rasterVisConfigs);\n\n    // No two STAC ids may be the same within a single collection, though it's conceivably possible\n    // across multiple STAC collections there could be an id collision. Note: STAC Collection\n    // objects don't have a `collection` key; STAC Item objects have a `collection` key (which\n    // matches the collection's `id`).\n    const resolver = (stac: CompleteSTACObject, preset: string, presetOptions: PresetOption) =>\n      `${stac.id}-${stac.collection}-${preset}-${presetOptions.singleBand?.assetId}-${presetOptions.singleBand?.bandIndex}`;\n    this.getDataSourceParams = memoize(\n      (stac, preset, presetOptions) => getDataSourceParams(stac, preset, presetOptions),\n      resolver\n    );\n\n    this.meta = {zRange: null};\n  }\n\n  static findDefaultLayerProps(dataset: KeplerDataset): FindDefaultLayerPropsReturnValue {\n    if (dataset.type !== DatasetType.RASTER_TILE) {\n      return {props: []};\n    }\n\n    const newLayerProp = {\n      dataId: dataset.id,\n      label: dataset.label,\n      isVisible: true\n    };\n\n    return {props: [newLayerProp]};\n  }\n\n  get type(): string {\n    return LAYER_TYPES.rasterTile;\n  }\n\n  get name(): string {\n    return 'Raster Tile';\n  }\n\n  get layerIcon(): KeplerLayer['layerIcon'] {\n    return RasterIcon;\n  }\n\n  get supportedDatasetTypes(): DatasetType[] {\n    return [DatasetType.RASTER_TILE];\n  }\n\n  get visualChannels(): Record<string, VisualChannel> {\n    const {\n      visConfig: {\n        colorRange: {colorLegends: categoricalColorLegends}\n      }\n    } = this.config;\n    if (categoricalColorLegends) {\n      return {\n        color: super.visualChannels.color\n      };\n    } else {\n      return {};\n    }\n  }\n\n  get requireData(): boolean {\n    return true;\n  }\n\n  getDefaultLayerConfig(\n    props: LayerBaseConfigPartial\n  ): LayerBaseConfig & Partial<LayerColorConfig & LayerHeightConfig> {\n    return {\n      ...super.getDefaultLayerConfig(props),\n      colorField: {\n        analyzerType: '',\n        name: 'Category',\n        format: '',\n        type: 'string',\n        fieldIdx: -1,\n        valueAccessor: () => '',\n        displayName: 'Category'\n      }\n    };\n  }\n\n  getHoverData() {\n    return null;\n  }\n\n  // We can render without columns, so we redefine this method\n  shouldRenderLayer(): boolean {\n    return Boolean(this.type && this.config.isVisible);\n  }\n\n  formatLayerData(datasets: Datasets, oldLayerData?: any) {\n    const {dataId} = this.config;\n    if (!notNullorUndefined(dataId)) {\n      return {};\n    }\n    const dataset = datasets[dataId];\n\n    // call updateData to updateLayerMeta\n    const dataUpdateTriggers = this.getDataUpdateTriggers(dataset);\n    const triggerChanged = this.getChangedTriggers(dataUpdateTriggers);\n\n    if (triggerChanged && triggerChanged.getMeta) {\n      this.updateLayerMeta(dataset as KeplerRasterDataset);\n    }\n\n    let tileSource: PMTilesTileSource | null = null;\n    if (dataset.metadata?.pmtilesType === PMTilesType.RASTER) {\n      const metadataUrl = dataset.metadata.metadataUrl;\n      // use the old tile source if it exists and matches the new metadataUrl\n      const {dataset: oldDataset, tileSource: oldTileSource} = oldLayerData || {};\n      if (oldDataset === dataset && oldTileSource?.data === metadataUrl) {\n        tileSource = oldLayerData.tileSource;\n      } else {\n        tileSource = metadataUrl ? PMTilesSource.createDataSource(metadataUrl, {}) : null;\n      }\n    }\n\n    return {dataset, tileSource};\n  }\n\n  updateLayerMeta(dataset: KeplerRasterDataset): void {\n    if (dataset.type !== DatasetType.RASTER_TILE) {\n      return;\n    }\n\n    const stac = dataset.metadata;\n    if (stac.pmtilesType === PMTilesType.RASTER) {\n      return this.updateMeta({\n        bounds: stac.bounds\n      });\n    }\n\n    const bounds = getSTACBounds(stac);\n    if (bounds) {\n      // If a STAC Item (i.e. stac.type === 'Feature'), then set map bounds.\n      // But we don't want to set bounds for Landsat or Sentinel because it would zoom out to zoom\n      // 0, and we don't have low-zoom tiles.\n      if (stac.type === 'Feature') {\n        this.updateMeta({bounds});\n      }\n    }\n  }\n\n  /**\n   * Run when adding a new dataset\n   */\n  setInitialLayerConfig(dataset: KeplerDataset): RasterTileLayer {\n    const stac = dataset?.metadata as GetTileDataCustomProps['stac'];\n\n    if (!stac) {\n      return this;\n    }\n\n    if (stac.pmtilesType === PMTilesType.RASTER) {\n      return this;\n    }\n\n    // When a STAC Collection is added, useStacSearching should be true to prevent a black screen\n    this.updateLayerVisConfig({\n      useSTACSearching: stac.type === 'Collection'\n    });\n\n    // Set singleBandName if there is only one band\n    const availableBands = getEOBands(stac) || [];\n    if (availableBands?.length === 1) {\n      this.updateLayerVisConfig({singleBandName: availableBands[0].name});\n    }\n\n    // Change the preset if the default one is not allowed\n    const availablePresets = filterAvailablePresets(stac, PRESET_OPTIONS);\n    if (\n      availablePresets?.length &&\n      !availablePresets?.includes(this.visConfigSettings.preset.defaultValue as string)\n    ) {\n      this.updateLayerVisConfig({preset: availablePresets[0]});\n    }\n\n    // Apply improved image processing props only for non single band modes\n    const preset = this.config.visConfig.preset;\n    const colorDefaults = DATA_SOURCE_COLOR_DEFAULTS[stac.id];\n    if (colorDefaults && preset !== 'singleBand') {\n      this.updateLayerVisConfig(colorDefaults);\n    }\n\n    /*\n    LandSat specifies temporal bounds but the valid data is after end date\n    const updatedDates = timeRangeToStacTemporalInterval(\n      stac as StacTypes.STACCollection,\n      this.config.visConfig.startDate,\n      this.config.visConfig.endDate\n    );\n    this.updateLayerVisConfig(updatedDates);\n    */\n\n    return this;\n  }\n\n  /**\n   * Update zRange of viewport. This is necessary so that tiles in extruded mode are shown properly\n   * at high elevations: https://github.com/foursquare/studio-monorepo/pull/1892/files\n   * Derived from https://github.com/visgl/deck.gl/blob/8d824a4b836fee3bfebe6fc962e0f03d8c1dbd0d/modules/geo-layers/src/terrain-layer/terrain-layer.js#L173-L196\n   *\n   * @param tiles Array of tiles in current viewport\n   */\n  onViewportLoad(tiles: (Tile2DHeader | null)[]): void {\n    this.updateMinMaxPixelValue(tiles);\n    const newZRange = computeZRange(tiles);\n    if (!newZRange) {\n      return;\n    }\n\n    const {zRange} = this.meta;\n    if (!zRange || newZRange[0] < zRange[0] || newZRange[1] > zRange[1]) {\n      this.meta.zRange = newZRange;\n    }\n  }\n\n  /**\n   * Update viewport-related minPxelValue and maxPixelValue\n   * @param tiles - deck.gl tiles\n   */\n  updateMinMaxPixelValue(tiles: (Tile2DHeader | null)[]): void {\n    const [min, max] = getMinMaxFromTile2DHeaders(tiles);\n    this.minViewportPixelValue = min;\n    this.maxViewportPixelValue = max;\n  }\n\n  /**\n   * Calculate min and max values of input raster band image properties\n   * and update `minBandsValue` and `maxBandsValue`\n   * @param images - raster band image properties\n   * @return [min, max] pixel values for the tile\n   */\n  getMinMaxPixelValues(images: Texture2DProps[] | null): [number, number] {\n    let minPixelValue = Infinity;\n    let maxPixelValue = -Infinity;\n    if (images) {\n      for (const image of images) {\n        const [min, max] = getImageMinMax(image.data);\n        if (typeof min === 'number') {\n          minPixelValue = Math.min(min, minPixelValue);\n        }\n        if (typeof max === 'number') {\n          maxPixelValue = Math.max(max, maxPixelValue);\n        }\n      }\n    }\n    return [minPixelValue, maxPixelValue];\n  }\n\n  // generate a deck layer\n  renderLayer(opts): TileLayer<any>[] {\n    const {data} = opts;\n\n    if (data?.dataset?.metadata?.pmtilesType === PMTilesType.RASTER) {\n      return this.renderPMTilesLayer(opts);\n    }\n    return this.renderStacLayer(opts);\n  }\n\n  private renderStacLayer(opts): TileLayer<any>[] {\n    const {data, mapState, experimentalContext} = opts;\n    const stac = data?.dataset?.metadata as GetTileDataCustomProps['stac'];\n\n    // If a tabular dataset is loaded, and then the layer type is switched from Point to Raster Tile\n    // layer, this `stac` object will exist but will not be raster metadata\n    if (!stac || !stac.stac_version) {\n      return [];\n    }\n\n    const {visConfig} = this.config;\n    const {id, opacity, visible} = this.getDefaultDeckLayerProps(opts);\n\n    const hasShadowEffect = experimentalContext?.hasShadowEffect;\n\n    const {\n      preset,\n      colormapId,\n      linearRescaling,\n      linearRescalingFactor,\n      nonLinearRescaling,\n      gammaContrastFactor,\n      sigmoidalContrastFactor,\n      sigmoidalBiasFactor,\n      saturationValue,\n      filterEnabled,\n      filterRange,\n      useSTACSearching,\n      stacSearchProvider,\n      startDate,\n      endDate,\n      _stacQuery,\n      singleBandName,\n      colorRange: {colorMap: categoricalColorMap},\n      dynamicColor\n    } = visConfig;\n\n    const shouldLoadTerrain = getShouldLoadTerrain(stac, mapState, visConfig);\n\n    if (new Date(endDate) < new Date(startDate)) {\n      return [];\n    }\n\n    const {bandCombination} = PRESET_OPTIONS[preset];\n\n    const singleBand = getSingleBandPresetOptions(stac, singleBandName);\n    const dataSourceParams: DataSourceParams | null = this.getDataSourceParams(stac, preset, {\n      singleBand\n    });\n\n    if (!dataSourceParams) {\n      return [];\n    }\n\n    const {\n      minZoom,\n      maxZoom,\n      globalBounds,\n      loadAssetIds,\n      loadBandIndexes,\n      renderBandIndexes,\n      dataType,\n      minPixelValue: dsMinPixelValue,\n      maxPixelValue: dsMaxPixelValue,\n      minCategoricalBandValue,\n      maxCategoricalBandValue\n    } = dataSourceParams;\n\n    const minPixelValue =\n      !dynamicColor && Number.isFinite(dsMinPixelValue)\n        ? dsMinPixelValue\n        : this.minViewportPixelValue;\n    const maxPixelValue =\n      !dynamicColor && Number.isFinite(dsMaxPixelValue)\n        ? dsMaxPixelValue\n        : this.maxViewportPixelValue;\n\n    const getTileDataCustomProps: GetTileDataCustomProps = {\n      stac,\n      loadAssetIds,\n      loadBandIndexes,\n      colormapId,\n      shouldLoadTerrain,\n      globalBounds,\n      useSTACSearching,\n      stacSearchProvider,\n      startDate,\n      endDate,\n      _stacQuery,\n      categoricalColorMap,\n      minCategoricalBandValue,\n      maxCategoricalBandValue\n    };\n\n    const tileLayer = new TileLayer<any, RenderSubLayersProps>({\n      id,\n      minZoom,\n      maxZoom,\n      tileSize: 512 / devicePixelRatio,\n      getTileData: (args: any) => this.getTileData({...args, ...getTileDataCustomProps}),\n      onViewportLoad: this.onViewportLoad.bind(this),\n      // @ts-expect-error - TS doesn't know we'll pass appropriate props here\n      renderSubLayers: renderSubLayersStac,\n      maxRequests: getMaxRequests(stac.rasterTileServerUrls || []),\n      // Passing visible on to TileLayer is necessary for split view to work\n      visible,\n      updateTriggers: {\n        getTileData: [\n          shouldLoadTerrain,\n          // Colormap has changed\n          colormapId,\n          // assets to be loaded has changed\n          ...loadAssetIds,\n          // band indexes to be loaded has changed\n          ...loadBandIndexes,\n          useSTACSearching,\n          stacSearchProvider,\n          startDate,\n          endDate,\n          _stacQuery,\n          categoricalColorMap,\n          minCategoricalBandValue,\n          maxCategoricalBandValue\n        ]\n      },\n      // Props for 3D mode\n      ...(shouldLoadTerrain\n        ? {\n            zRange: this.meta.zRange || null,\n            refinementStrategy: 'no-overlap'\n          }\n        : {}),\n      renderBandIndexes,\n      opacity,\n      linearRescaling,\n      linearRescalingFactor,\n      nonLinearRescaling,\n      minPixelValue,\n      maxPixelValue,\n      gammaContrastFactor,\n      sigmoidalContrastFactor,\n      sigmoidalBiasFactor,\n      saturationValue,\n      bandCombination,\n      filterEnabled,\n      filterRange,\n      dataType,\n      minCategoricalBandValue,\n      maxCategoricalBandValue,\n      hasCategoricalColorMap: Boolean(categoricalColorMap),\n      hasShadowEffect\n    });\n\n    return [tileLayer];\n  }\n\n  private renderPMTilesLayer(opts): TileLayer<any>[] {\n    const {id, opacity, visible} = this.getDefaultDeckLayerProps(opts);\n\n    const {data, mapState} = opts;\n    const metadata = data?.dataset?.metadata as VectorTileMetadata;\n    const {visConfig} = this.config;\n\n    const tileSource = data.tileSource;\n    const showTileBorders = false;\n    const minZoom = metadata.minZoom || 0;\n    const maxZoom = metadata.maxZoom || 30;\n\n    const shouldLoadTerrain = getShouldLoadTerrain(metadata, mapState, visConfig);\n\n    return [\n      new TileLayer<any, RasterTileLayerVisConfigCommonSettings>({\n        id,\n        getTileData: (args: any) =>\n          this.getTileDataPMTiles({...args, shouldLoadTerrain, metadata}, tileSource),\n\n        // Assume the pmtiles file support HTTP/2, so we aren't limited by the browser to a certain number per domain.\n        maxRequests: 20,\n\n        pickable: true,\n        autoHighlight: showTileBorders,\n\n        onViewportLoad: this.onViewportLoad.bind(this),\n\n        minZoom,\n        maxZoom,\n        tileSize: 512 / devicePixelRatio,\n        zoomOffset: devicePixelRatio === 1 ? -1 : 0,\n        // @ts-expect-error - TS doesn't know we'll pass appropriate props here\n        renderSubLayers: renderSubLayersPMTiles,\n\n        tileSource,\n        showTileBorders,\n\n        visible,\n        opacity,\n\n        updateTriggers: {\n          getTileData: [shouldLoadTerrain]\n        },\n        // Props for 3D mode\n        ...(shouldLoadTerrain\n          ? {\n              zRange: this.meta.zRange || null,\n              refinementStrategy: 'no-overlap'\n            }\n          : {})\n      })\n    ];\n  }\n\n  async getTileData(props: GetTileDataProps): Promise<GetTileDataOutput> {\n    const {\n      shouldLoadTerrain,\n      globalBounds,\n      bbox: {west, south, east, north},\n      index: {z: zoom}\n    } = props;\n\n    if (globalBounds && !bboxIntersects(globalBounds, [west, south, east, north])) {\n      // tile is outside of STAC object's bounding box; don't fetch tile\n      return null;\n    }\n\n    const assetRequests = await getSTACImageRequests(props);\n    // No assets to load, most likely due to props being invalid/unsupported\n    if (!assetRequests) {\n      // We still issue the loadTerrain request if applicable\n      const terrain =\n        shouldLoadTerrain &&\n        LOAD_ELEVATION_AFTER_ZOOM < zoom &&\n        (await loadTerrain({\n          boundsForGeometry: [west, north, east, south],\n          index: props.index,\n          signal: props.signal,\n          rasterTileServerUrls: props.stac.rasterTileServerUrls || [],\n          rasterServerMaxRetries: props.stac.rasterServerMaxRetries,\n          rasterServerRetryDelay: props.stac.rasterServerRetryDelay,\n          rasterServerServerErrorsToRetry: props.stac.rasterServerServerErrorsToRetry,\n          rasterServerMaxPerServerRequests: props.stac.rasterServerMaxPerServerRequests\n        }));\n      return {images: null, ...(terrain ? {terrain} : {})};\n    }\n\n    tilesBeingLoaded++;\n\n    // Wrap loading images into a try/catch because tiles that are only briefly in view will be\n    // aborted. The `AbortController.abort()` creates a DOMException error.\n    // Note that since loading is async, try/catch will only work when awaited inside the try block\n    try {\n      const [images, terrain] = await Promise.all([\n        loadImages(assetRequests, props.colormapId, {\n          colorMap: props.categoricalColorMap,\n          minValue: props.minCategoricalBandValue,\n          maxValue: props.maxCategoricalBandValue\n        }),\n        shouldLoadTerrain && LOAD_ELEVATION_AFTER_ZOOM < zoom\n          ? loadTerrain({\n              boundsForGeometry: [west, north, east, south],\n              index: props.index,\n              signal: props.signal,\n              rasterTileServerUrls: props.stac.rasterTileServerUrls || [],\n              rasterServerMaxRetries: props.stac.rasterServerMaxRetries,\n              rasterServerRetryDelay: props.stac.rasterServerRetryDelay,\n              rasterServerServerErrorsToRetry: props.stac.rasterServerServerErrorsToRetry,\n              rasterServerMaxPerServerRequests: props.stac.rasterServerMaxPerServerRequests\n            })\n          : null\n      ]);\n\n      const [min, max] = this.getMinMaxPixelValues(images.imageBands);\n      tilesBeingLoaded--;\n\n      // Add terrain data only if we requested it above\n      return {\n        images,\n        ...(terrain ? {terrain} : {}),\n        minPixelValue: min,\n        maxPixelValue: max\n      };\n    } catch (error) {\n      tilesBeingLoaded--;\n      if ((error as any).name === 'AbortError') {\n        // tile was aborted\n        return null;\n      }\n\n      // Some other unhandled error\n      throw error;\n    }\n  }\n\n  async getTileDataPMTiles(\n    props: GetTileDataDefaultProps & {\n      shouldLoadTerrain: boolean;\n      metadata: {\n        rasterTileServerUrls: string[];\n        rasterServerMaxRetries?: number;\n        rasterServerRetryDelay?: number;\n        rasterServerServerErrorsToRetry?: number[];\n        rasterServerMaxPerServerRequests?: number;\n      };\n      globalBounds: DataSourceParams['globalBounds'];\n    },\n    tileSource\n  ): Promise<any> {\n    const {\n      shouldLoadTerrain,\n      globalBounds,\n      bbox: {west, south, east, north},\n      index: {z: zoom},\n      metadata\n    } = props;\n\n    if (globalBounds && !bboxIntersects(globalBounds, [west, south, east, north])) {\n      // tile is outside of STAC object's bounding box; don't fetch tile\n      return null;\n    }\n\n    tilesBeingLoaded++;\n\n    try {\n      const [image] = await Promise.all([tileSource.getTileData(props)]);\n      // For nwo check if the base tile exists, as this can cause loading to get stuck with \"sparse\" PMTiles.\n      const terrain =\n        image && shouldLoadTerrain && LOAD_ELEVATION_AFTER_ZOOM < zoom\n          ? await loadTerrain({\n              boundsForGeometry: [west, north, east, south],\n              index: props.index,\n              signal: props.signal,\n              rasterTileServerUrls: metadata.rasterTileServerUrls,\n              rasterServerMaxRetries: metadata.rasterServerMaxRetries,\n              rasterServerRetryDelay: metadata.rasterServerRetryDelay,\n              rasterServerServerErrorsToRetry: metadata.rasterServerServerErrorsToRetry,\n              rasterServerMaxPerServerRequests: metadata.rasterServerMaxPerServerRequests\n            })\n          : null;\n\n      let minPixelValue: number | null = null;\n      let maxPixelValue: number | null = null;\n      if (image) {\n        const [min, max] = this.getMinMaxPixelValues([{data: image}]);\n        minPixelValue = min;\n        maxPixelValue = max;\n      }\n      tilesBeingLoaded--;\n\n      // Add terrain data only if we requested it above\n      return {image, minPixelValue, maxPixelValue, ...(terrain ? {terrain} : {})};\n    } catch (error) {\n      tilesBeingLoaded--;\n\n      if ((error as Error).name === 'AbortError') {\n        // tile was aborted\n        return null;\n      }\n\n      // Some other unhandled error\n      throw error;\n    }\n  }\n}\n\nfunction renderSubLayersStac(props: RenderSubLayersProps): DeckLayer<any> | DeckLayer<any>[] {\n  const {\n    tile,\n    data,\n    minPixelValue: globalMinPixelValue,\n    maxPixelValue: globalMaxPixelValue\n  } = props;\n  const bbox = tile.bbox as GeoBoundingBox;\n  const {west, south, east, north} = bbox;\n\n  if (!data) {\n    return [];\n  }\n\n  const {\n    images,\n    terrain,\n    minPixelValue: tileMinPixelValue,\n    maxPixelValue: tileMaxPixelValue\n  } = data;\n  if (!images || (Array.isArray(images) && images.length === 0)) {\n    return [];\n  }\n\n  const minPixelValue = Number.isFinite(globalMinPixelValue)\n    ? globalMinPixelValue\n    : tileMinPixelValue;\n  const maxPixelValue = Number.isFinite(globalMaxPixelValue)\n    ? globalMaxPixelValue\n    : tileMaxPixelValue;\n  if (typeof minPixelValue !== 'number' || typeof maxPixelValue !== 'number') {\n    return [];\n  }\n\n  const {modules, moduleProps} = getModules({\n    images,\n    props: {...props, minPixelValue, maxPixelValue}\n  });\n\n  const idSuffix = props.hasShadowEffect ? 'shadow' : '';\n\n  return terrain\n    ? new RasterMeshLayer(props, {\n        id: `raster-3d-layer-${props.id}-${idSuffix}`,\n        // Dummy data\n        data: [1],\n        mesh: terrain,\n        images,\n        modules,\n        moduleProps,\n\n        getPolygonOffset: null,\n        coordinateSystem: COORDINATE_SYSTEM.LNGLAT,\n        // Color to use if surfaceImage is unavailable\n        getColor: [255, 255, 255]\n        // material: false\n      })\n    : new RasterLayer(props, {\n        id: `raster-2d-layer-${props.id}-${idSuffix}`,\n        images,\n        modules,\n        moduleProps,\n        bounds: [west, south, east, north],\n        _imageCoordinateSystem: COORDINATE_SYSTEM.CARTESIAN\n      });\n}\n\nfunction renderSubLayersPMTiles(props: {\n  id: string;\n  data: any;\n  tileSource: any;\n  showTileBorders: any;\n  minZoom: any;\n  maxZoom: any;\n  tile: any;\n}) {\n  const {\n    tileSource,\n    showTileBorders,\n    minZoom,\n    maxZoom,\n    tile: {\n      index: {z: zoom},\n      bbox: {west, south, east, north}\n    }\n  } = props;\n\n  const layers: any[] = [];\n\n  if (props.data?.image) {\n    switch (tileSource.mimeType) {\n      case 'image/png':\n      case 'image/jpeg':\n      case 'image/webp':\n      case 'image/avif':\n        layers.push(getRasterLayerPMTiles(props));\n        break;\n\n      default:\n        break;\n    }\n  }\n\n  // Debug tile borders\n  const borderColor = zoom <= minZoom || zoom >= maxZoom ? [255, 0, 0, 255] : [0, 0, 255, 255];\n  if (showTileBorders) {\n    layers.push(\n      new PathLayer({\n        id: `${props.id}-border`,\n        data: [\n          [\n            [west, north],\n            [west, south],\n            [east, south],\n            [east, north],\n            [west, north]\n          ]\n        ],\n        getPath: d => d,\n        getColor: borderColor as any,\n        widthMinPixels: 4\n      })\n    );\n  }\n\n  return layers;\n}\n\nfunction getRasterLayerPMTiles(props: {id: string; data: any; tile: any}) {\n  const {tile, data} = props;\n  const bbox = tile.bbox as GeoBoundingBox;\n  const {west, south, east, north} = bbox;\n\n  if (!data?.image) {\n    return [];\n  }\n\n  const images = {imageRgba: {data: data.image}};\n  const {terrain} = data;\n\n  const {modules, moduleProps} = getModules({\n    images\n  });\n\n  return terrain\n    ? new RasterMeshLayer(props, {\n        id: `raster-3d-layer-${props.id}`,\n        // Dummy data\n        data: [1],\n        mesh: terrain,\n        images,\n        modules,\n        moduleProps,\n\n        getPolygonOffset: null,\n        coordinateSystem: COORDINATE_SYSTEM.LNGLAT,\n        // Color to use if surfaceImage is unavailable\n        getColor: [255, 255, 255]\n        // material: false\n      })\n    : new RasterLayer(props, {\n        id: `raster-2d-layer-${props.id}`,\n        data: null as never,\n        images,\n        modules,\n        moduleProps,\n        bounds: [west, south, east, north],\n        _imageCoordinateSystem: COORDINATE_SYSTEM.CARTESIAN\n      });\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/raster-tile-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Utility functions and constants for processing STAC metadata and other raster tile data\n */\n\nimport {TypedArray} from '@loaders.gl/loader-utils/src/types';\nimport {isArray} from '@math.gl/core';\n\nimport {StacTypes} from '@kepler.gl/types';\nimport {hexToRgb} from '@kepler.gl/utils';\n\nimport {PRESET_OPTIONS, ZOOM_RANGES} from './config';\nimport {\n  DataSourceParams,\n  PresetData,\n  CompleteSTACObject,\n  CompleteSTACAssetLinks,\n  AssetIds,\n  BandIndexes,\n  RenderBandIndexes,\n  Tile2DHeader,\n  AssetRequestInfo,\n  PresetOption,\n  BandCombination,\n  CategoricalColormapOptions\n} from './types';\n\ntype Item = StacTypes.STACItem;\ntype Collection = StacTypes.STACCollection;\ntype EOBand = StacTypes.Band;\ntype DataTypeOfTheBand = StacTypes.DataTypeOfTheBand;\n\nexport const CATEGORICAL_TEXTURE_WIDTH = 256;\n\nexport function isColormapAllowed(bandCombination: BandCombination): boolean {\n  return bandCombination !== BandCombination.Rgb;\n}\n\nexport function isRescalingAllowed(bandCombination: BandCombination): boolean {\n  return bandCombination === BandCombination.Rgb || bandCombination === BandCombination.Single;\n}\n\nexport function isFilterAllowed(bandCombination: BandCombination): boolean {\n  return bandCombination !== BandCombination.Rgb;\n}\n\n/**\n * Max value for data type\n *\n * Values of null might be calculated in runtime\n */\nexport const dtypeMaxValue: Record<DataTypeOfTheBand, number | null> = {\n  uint8: Math.pow(2, 8) - 1,\n  uint16: Math.pow(2, 16) - 1,\n  uint32: Math.pow(2, 32) - 1,\n  uint64: null,\n  int8: Math.pow(2, 7) - 1,\n  int16: Math.pow(2, 15) - 1,\n  int32: Math.pow(2, 31) - 1,\n  int64: null,\n  float16: null,\n  float32: null,\n  float64: null,\n  cint16: null,\n  cint32: null,\n  cfloat32: null,\n  cfloat64: null,\n  other: null\n};\n\n/**\n * Is this a STAC Collection that supports custom searching\n * TODO: currently this is a custom hack to support Sentinel. It will need to be generalized before being accessible\n * @param stac  STAC object\n * @return If True, supports searching\n */\nexport function isSearchableStac(stac: CompleteSTACObject): boolean {\n  return stac.type === 'Collection';\n\n  // return stac.type !== 'Feature' && stac?.providers.some(\n  //   provider =>\n  //     provider.name.toLowerCase() === 'microsoft' &&\n  //     provider.url === 'https://planetarycomputer.microsoft.com'\n  // );\n}\n\nfunction getZoomRange(stac: CompleteSTACObject): [number, number] {\n  if (ZOOM_RANGES[stac.id]) {\n    return ZOOM_RANGES[stac.id];\n  }\n\n  // For a single COG, having a full zoom range isn't really a problem.\n  // the /cog/info endpoint doesn't describe zoom levels because it doesn't know the projection to serve the image in.\n  // Default minzoom, maxzoom: [0, 20]\n  return [0, 20];\n}\n\n/**\n * Infer data type from STAC item\n * This uses the `raster` extension, which is not yet very common\n * @param stac stac object\n * @return Data type of the band\n */\nfunction getDataType(\n  usableAssets: CompleteSTACAssetLinks,\n  typesToCheck?: string[]\n): DataTypeOfTheBand | null {\n  const dataTypes = new Set<DataTypeOfTheBand>();\n  for (const assetName in usableAssets) {\n    const asset = usableAssets[assetName];\n    if (!asset) {\n      continue;\n    }\n    if (typesToCheck && !typesToCheck.includes(assetName)) {\n      continue;\n    }\n\n    asset['raster:bands']?.forEach(band => band.data_type && dataTypes.add(band.data_type));\n  }\n\n  // TODO: support multiple data types across assets\n  if (dataTypes.size !== 1) {\n    return null;\n  }\n\n  return Array.from(dataTypes)[0];\n}\n\n/**\n * Get min and max values of the band from the band's data type values range\n * @param stac - stac metadata object\n * @param dtype - data type of the raster band\n * @returns min and max values for the band\n */\nfunction getPixelRange(\n  stac: CompleteSTACObject,\n  dtype: DataTypeOfTheBand | null,\n  [minRasterStatsValue, maxRasterStatsValue]: [number | undefined, number | undefined]\n): [number, number] | null {\n  if (!dtype) {\n    return null;\n  }\n\n  // TODO: might not always be desired to leave min pixel value at 0\n  const minPixelValue = 0;\n  const maxPixelValue = dtypeMaxValue[dtype];\n\n  // TODO check if this early return is expected\n  if (!maxPixelValue) {\n    return null;\n  }\n\n  if (\n    !Number.isFinite(maxPixelValue) &&\n    typeof minRasterStatsValue === 'number' &&\n    typeof maxRasterStatsValue === 'number'\n  ) {\n    return [minRasterStatsValue, maxRasterStatsValue];\n  }\n\n  return [minPixelValue, maxPixelValue];\n}\n\n/**\n * Find min and max values throughout a raster band image\n * @param imageData raster band image data\n * @returns min and max values throughout the band\n */\nexport function getImageMinMax(imageData: TypedArray): [number | null, number | null] {\n  // We cannot calculate min/max for Image/ImageBitmap\n  if (!isArray(imageData)) {\n    return [null, null];\n  }\n  let min = Infinity;\n  let max = -Infinity;\n  for (const value of imageData) {\n    if (!isNaN(value)) {\n      min = Math.min(min, value);\n      max = Math.max(max, value);\n    }\n  }\n  return [min, max];\n}\n\n/**\n * Find asset names and band indexes for given commonNames\n * Right now in Kepler, our available methods of rendering raster data are quite simple. We only allow a user to choose among `presets`, and under the hood we select which data files to load.\n * @param usableAssets [stac description]\n * @param commonNames  an array of eo:common_name to search within bands\n * @return Band information or null if not all bands exist\n */\nexport function getBandIdsForCommonNames(\n  stac: CompleteSTACObject,\n  usableAssets: CompleteSTACAssetLinks,\n  commonNames: string[]\n): AssetRequestInfo | null {\n  // An array of strings describing asset identifiers, e.g. the \"key\" in the assets object\n  const assetIds: AssetIds = [];\n  // An array of integers giving the index of the band within that asset's data\n  // In general, data sources either have a single band per asset or all assets in a single band\n  const bandIndexes: BandIndexes = [];\n\n  for (const commonName of commonNames) {\n    // Find asset that includes a band with this common name\n    const result = findAssetWithName(usableAssets, commonName, 'common_name');\n    if (result) {\n      const [assetName, bandIndex] = result;\n      assetIds.push(assetName);\n      bandIndexes.push(bandIndex);\n    }\n  }\n\n  if (assetIds.length === 0) {\n    return null;\n  }\n\n  const {loadAssetIds, loadBandIndexes, renderBandIndexes} = consolidateBandIndexes(\n    stac,\n    assetIds,\n    bandIndexes\n  );\n\n  return {loadAssetIds, loadBandIndexes, renderBandIndexes};\n}\n\n/**\n * Consolidate asset/band info into load info and render info\n * @param stac STAC object\n * @param assetIds\n * @param bandIndexes\n * @return Asset information\n */\nfunction consolidateBandIndexes(\n  stac: CompleteSTACObject,\n  assetIds: AssetIds,\n  bandIndexes: BandIndexes\n): AssetRequestInfo {\n  let loadAssetIds: AssetIds;\n  let loadBandIndexes: BandIndexes;\n  let renderBandIndexes: RenderBandIndexes;\n\n  // All bands we want to load are in a single asset\n  if (assetIds.length === 1 || Array.from(new Set(assetIds)).length === 1) {\n    loadAssetIds = [assetIds[0]];\n\n    const asset = getAssets(stac)[loadAssetIds[0]];\n\n    // Request all bands in order, then reorder with renderBandIndexes\n    // NOTE: usually (e.g. with NAIP) if all bands are in a single asset, then there are only ~4\n    // bands. This approach of requesting all bands may be less efficient with more than 4 bands\n    // (because of larger download sizes). In the future may want separate loading paths if we\n    // encounter single asset objects with > 4 bands.\n\n    // TODO: eo:bands can be _either_ on each asset _or_ on the STAC's properties, which is not currently handled\n    loadBandIndexes = (asset['eo:bands'] as EOBand[])?.map((_, idx) => idx);\n    renderBandIndexes = bandIndexes;\n  } else {\n    loadAssetIds = assetIds;\n    loadBandIndexes = bandIndexes;\n    renderBandIndexes = null;\n  }\n\n  return {loadAssetIds, loadBandIndexes, renderBandIndexes};\n}\n\n/**\n * Find the Asset in the STAC object that containing an eo:band with the desired common_name or name\n * @param assets assets object. Keys should be asset name and values should be asset data\n * @param name  name or common_name in eo:bands\n * @param property 'name' or 'common_name'\n * @return name of asset containing desired band, index of band within asset\n */\nexport function findAssetWithName(\n  assets: CompleteSTACAssetLinks,\n  name: string,\n  property: 'name' | 'common_name'\n): [string, number] | null {\n  for (const [assetName, assetData] of Object.entries(assets)) {\n    const eoBands = assetData['eo:bands'] || [];\n\n    // Search bands array of this asset\n    for (let i = 0; i < eoBands.length; i++) {\n      const eoBand = eoBands[i];\n      if (name === eoBand[property]) {\n        return [assetName, i];\n      }\n    }\n  }\n\n  return null;\n}\n\nfunction getSingleBandInfo(\n  usableAssets: CompleteSTACAssetLinks,\n  singleBandInfo: PresetOption['singleBand']\n): AssetRequestInfo | null {\n  if (!singleBandInfo?.assetId && Object.keys(usableAssets).length === 1) {\n    singleBandInfo = {\n      assetId: Object.keys(usableAssets)[0]\n    };\n  }\n\n  if (!singleBandInfo) {\n    return null;\n  }\n\n  const {assetId, bandIndex} = singleBandInfo;\n  if (!Object.keys(usableAssets).includes(assetId)) {\n    return null;\n  }\n\n  return {\n    loadAssetIds: [assetId],\n    loadBandIndexes: bandIndex ? [bandIndex] : [0],\n    // No reordering of bands on the GPU\n    renderBandIndexes: null\n  };\n}\n\nexport function getRasterStatisticsMinMax(\n  stac: CompleteSTACObject,\n  presetId: string,\n  singleBandInfo: PresetOption['singleBand']\n): [number | undefined, number | undefined] {\n  if (presetId !== 'singleBand') {\n    return [undefined, undefined];\n  }\n  let minCategoricalBandValue: number | undefined;\n  let maxCategoricalBandValue: number | undefined;\n  const usableAssets = getUsableAssets(stac);\n  let bandMetadata = usableAssets[singleBandInfo?.assetId || ''];\n  if (!bandMetadata) {\n    const assetId = Object.keys(usableAssets)[0];\n    bandMetadata = usableAssets[assetId];\n  }\n\n  if (bandMetadata) {\n    minCategoricalBandValue = bandMetadata['raster:bands']?.[0].statistics?.minimum;\n    maxCategoricalBandValue = bandMetadata['raster:bands']?.[0].statistics?.maximum;\n  }\n\n  return [minCategoricalBandValue, maxCategoricalBandValue];\n}\n\nexport function getSingleBandPresetOptions(\n  stac: CompleteSTACObject,\n  singleBandName: string\n): PresetOption['singleBand'] {\n  const usableAssets = getUsableAssets(stac);\n  let assetData = findAssetWithName(usableAssets, singleBandName, 'name');\n  if (!assetData) {\n    // some collections have common_name instead of name\n    assetData = findAssetWithName(usableAssets, singleBandName, 'common_name');\n  }\n  let singleBand: PresetOption['singleBand'];\n  if (assetData) {\n    const [assetId, bandIndex] = assetData;\n    singleBand = {assetId, bandIndex};\n  }\n  return singleBand;\n}\n\n/**\n * Calculate viewport-related min and max raster values from loaded tiles\n * @param tiles - deck.gl tiles\n * @returns [minPixelValue, maxPixelValue]\n */\nexport function getMinMaxFromTile2DHeaders(tiles: (Tile2DHeader | null)[]): [number, number] {\n  const minValues: (number | null)[] = tiles\n    .map((tile: Tile2DHeader | null) => {\n      if (!tile || !tile.data) {\n        return null;\n      }\n      const {minPixelValue} = tile.data;\n      return minPixelValue;\n    })\n    .filter(value => value);\n  const maxValues: (number | null)[] = tiles\n    .map((tile: Tile2DHeader | null) => {\n      if (!tile || !tile.data) {\n        return null;\n      }\n      const {maxPixelValue} = tile.data;\n      return maxPixelValue;\n    })\n    .filter(value => value);\n  // We can cast to number[] because we have filtered null values\n  return [Math.min(...(minValues as number[])), Math.max(...(maxValues as number[]))];\n}\n\n/**\n * Get loading params\n * @param stac STAC Object\n * @param preset Preset for display\n * @return Parameters for loading data\n */\nexport function getDataSourceParams(\n  stac: CompleteSTACObject,\n  presetId: string,\n  presetOptions?: PresetOption\n): DataSourceParams | null {\n  const usableAssets = getUsableAssets(stac);\n  const {commonNames, bandCombination} = PRESET_OPTIONS[presetId];\n  const [minZoom, maxZoom] = getZoomRange(stac);\n\n  let bandInfo: AssetRequestInfo | null = null;\n  if (bandCombination === 'single') {\n    bandInfo = getSingleBandInfo(usableAssets, presetOptions?.singleBand);\n  } else if (commonNames) {\n    bandInfo = getBandIdsForCommonNames(stac, usableAssets, commonNames);\n  } else {\n    return null;\n  }\n\n  if (!bandInfo) return null;\n\n  const dtype = getDataType(usableAssets, bandInfo.loadAssetIds);\n  const [minRasterStatsValue, maxRasterStatsValue] = getRasterStatisticsMinMax(\n    stac,\n    presetId,\n    presetOptions?.singleBand\n  );\n  const pixelRange = getPixelRange(stac, dtype, [minRasterStatsValue, maxRasterStatsValue]);\n\n  if (!pixelRange || !dtype) {\n    return null;\n  }\n\n  const [minPixelValue, maxPixelValue] = pixelRange;\n  const {loadAssetIds, loadBandIndexes, renderBandIndexes} = bandInfo;\n  const globalBounds = getSTACBounds(stac);\n\n  return {\n    loadAssetIds,\n    loadBandIndexes,\n    renderBandIndexes,\n    minZoom,\n    maxZoom,\n    minPixelValue,\n    maxPixelValue,\n    // use min and max statistics values for categorical re-scale\n    minCategoricalBandValue: minRasterStatsValue,\n    maxCategoricalBandValue: maxRasterStatsValue,\n    globalBounds,\n    dataType: dtype\n  };\n}\n\n/**\n * Get Bounding Box of STAC object\n * @param stac\n * @return bounding box\n */\nexport function getSTACBounds(stac: Item | Collection): number[] | null {\n  // Check if Item\n  if (stac.type === 'Feature') {\n    // bbox may be missing\n    return stac.bbox || null;\n  }\n\n  // Then must be collection\n  return stac.extent.spatial.bbox[0];\n}\n\n/**\n * Get all eo:band objects from STAC object\n * @param stac  STAC object\n * @return array of eo:band objects or null\n */\nexport function getEOBands(stac: CompleteSTACObject): EOBand[] | null {\n  const assets = getAssets(stac);\n  if (!assets) {\n    return null;\n  }\n\n  const eoBands: EOBand[] = [];\n  for (const data of Object.values(assets)) {\n    const assetBands = data['eo:bands'];\n    if (Array.isArray(assetBands) && assetBands.length > 0) {\n      eoBands.push(...assetBands);\n    }\n  }\n\n  return eoBands;\n}\n\n/**\n * Filter presets by those that can be used with the given stac item\n * Each preset has a commonNames key, which is a list of eo `common_name` values. Some STAC items\n * may not have assets that span all of these combinations of `common_name`, so this filters the\n * input array.\n * @param stac           STAC object\n * @param presetData  object with requirements for each preset\n * @return An array of preset option ids that can be used with the given STAC item\n */\nexport function filterAvailablePresets(\n  stac: CompleteSTACObject,\n  presetData: Record<string, PresetData>\n): string[] | null {\n  if (!stac) {\n    return null;\n  }\n\n  const eoBands = getEOBands(stac);\n\n  if (!eoBands) {\n    return null;\n  }\n\n  const availablePresetIds: string[] = [];\n  for (const preset of Object.values(presetData)) {\n    const {commonNames, bandCombination} = preset;\n\n    // True if all required common names of preset exist in STAC object\n    const allBandsExist = commonNames?.every(commonName =>\n      eoBands.some(eoBand => eoBand.common_name === commonName)\n    );\n\n    if (allBandsExist || bandCombination === 'single') {\n      availablePresetIds.push(preset.id);\n    }\n  }\n\n  return availablePresetIds;\n}\n\n// TODO: would be better to have a generic here to relax the requirement that all input STACs have all extensions we list.\nexport function getAssets(stac: CompleteSTACObject): CompleteSTACAssetLinks {\n  // stac is an Item\n  if (stac.type === 'Feature') {\n    return stac.assets;\n  }\n\n  // stac is a Collection\n  // A STAC Collection optionally contains (if it includes the Item Assets Extension) an item_assets\n  // key that describes the assets included in every Item in the Collection.\n  return stac.item_assets;\n}\n\n/**\n * Find usable assets in main assets object\n * The `assets` object can point to many different objects, including original XML or JSON metadata,\n * thumbnails, etc. These aren't usable as image data in Studio.\n * @param stac  STAC object\n * @return asset mapping including only assets usable in Studio\n */\nexport function getUsableAssets(stac: CompleteSTACObject): CompleteSTACAssetLinks {\n  const allAssets = getAssets(stac);\n  const usableAssets = {};\n  for (const assetName in allAssets) {\n    const assetData = allAssets[assetName];\n\n    // We don't require data assets to have the \"data\" role, but if the asset has a non-data role,\n    // we exclude it\n    if (\n      Array.isArray(assetData.roles) &&\n      assetData.roles.some(role =>\n        ['thumbnail', 'overview', 'metadata', 'visual'].includes(role)\n      ) &&\n      !assetData.roles.includes('data')\n    ) {\n      continue;\n    }\n\n    // raster:bands array exists\n    if (assetData['raster:bands']) {\n      usableAssets[assetName] = assetData;\n    }\n\n    // TODO: Could also mark the asset as unusable if the asset has a non-GeoTIFF or COG Media Type\n  }\n\n  return usableAssets;\n}\n\n/**\n * Get the max number of requests the TileLayer should be able to send at once\n * With HTTP 1 under Chrome, you can make a max of 6 concurrent requests per domain, so this number\n * should be 6 * number of domains tiles are loaded from.\n * @param stac STAC object\n * @return Number of permissible concurrent requests\n */\nexport function getMaxRequests(rasterTileServerUrls: []): number {\n  return (rasterTileServerUrls.length || 1) * 6;\n}\n\n/**\n * Determine if two axis-aligned boxes intersect\n * @param bbox1  axis-aligned box\n * @param bbox2  axis-aligned box\n * @return true if boxes intersect\n */\nexport function bboxIntersects(bbox1: number[], bbox2: number[]): boolean {\n  if (!Array.isArray(bbox1) || bbox1.length !== 4 || !Array.isArray(bbox2) || bbox2.length !== 4) {\n    // Invalid input; can't make determination\n    return true;\n  }\n\n  return !(\n    bbox2[0] > bbox1[2] ||\n    bbox2[2] < bbox1[0] ||\n    bbox2[3] < bbox1[1] ||\n    bbox2[1] > bbox1[3]\n  );\n}\n\n/**\n * Compute zRange of tiles in viewport.\n * Derived from https://github.com/visgl/deck.gl/blob/8d824a4b836fee3bfebe6fc962e0f03d8c1dbd0d/modules/geo-layers/src/terrain-layer/terrain-layer.js#L173-L196\n * @param tiles Array of tiles in current viewport\n */\nexport function computeZRange(tiles: (Tile2DHeader | null)[] | null): [number, number] | null {\n  // Abort if no tiles visible\n  if (!tiles) {\n    return null;\n  }\n\n  // Since getTileData returns an object with either {terrain, images} in 3d mode or {images} in\n  // 2D mode, we can grab that object using tile.content.terrain, then grab the bounding box from\n  // the header.\n  const ranges: [number, number][] = tiles\n    .map(tile => tile?.content?.terrain?.header?.boundingBox)\n    .flatMap(bounds => (bounds ? [[bounds[0][2], bounds[1][2]]] : []));\n\n  if (ranges.length === 0) {\n    return null;\n  }\n\n  const minZ = Math.min(...ranges.map(x => x[0]));\n  const maxZ = Math.max(...ranges.map(x => x[1]));\n\n  return [minZ, maxZ];\n}\n\n/**\n * Create RGBA bitmap array for categorical color scale from categorical color map\n * @param categoricalOptions - color map configuration and min-max values of categorical band\n * @returns typed array with bitmap data\n */\nexport function generateCategoricalBitmapArray(\n  categoricalOptions: CategoricalColormapOptions\n): Uint8Array | null {\n  const {colorMap, minValue = 0, maxValue = CATEGORICAL_TEXTURE_WIDTH - 1} = categoricalOptions;\n  if (!colorMap) {\n    return null;\n  }\n  const colorScaleMaxValue = maxValue - minValue;\n  const step = CATEGORICAL_TEXTURE_WIDTH / (colorScaleMaxValue + 1);\n  const data = new Uint8Array(CATEGORICAL_TEXTURE_WIDTH * 4);\n  for (const [value, color] of colorMap) {\n    if (typeof value !== 'number') {\n      continue;\n    }\n    const rgb = hexToRgb(color);\n    const minPixel = Math.floor(value * step);\n    const maxPixel = Math.floor((value + 1) * step - 1);\n    for (let i = minPixel; i <= maxPixel; i++) {\n      data.set([...rgb, CATEGORICAL_TEXTURE_WIDTH - 1], i * 4);\n    }\n  }\n  return data;\n}\n\nexport function timeRangeToStacTemporalInterval(\n  stac: StacTypes.STACCollection,\n  startDate: string,\n  endDate: string\n): {startDate: string; endDate: string} {\n  // TODO support multiple temporal intervals\n  const interval = stac.extent?.temporal?.interval?.[0];\n  if (!interval || !interval[0]) return {startDate, endDate};\n\n  const collectionStart = interval[0] ? new Date(interval[0]) : new Date();\n  const collectionEnd = interval[1] ? new Date(interval[1]) : new Date();\n\n  const layerStart = new Date(startDate);\n  const layerEnd = new Date(endDate);\n\n  const clippedStart =\n    layerStart < collectionStart || layerStart > collectionEnd ? collectionStart : layerStart;\n  let clippedEnd = layerEnd > collectionEnd ? collectionEnd : layerEnd;\n  if (clippedStart > clippedEnd) {\n    clippedEnd = new Date(clippedStart);\n    clippedEnd.setDate(clippedEnd.getDate() + 1);\n  }\n\n  return {\n    startDate: clippedStart.toISOString().split('T')[0],\n    endDate: clippedEnd.toISOString().split('T')[0]\n  };\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/request-throttle.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\ninterface RequestQueue {\n  activeRequests: number;\n  queue: Array<() => Promise<any>>;\n}\n\nclass RequestThrottle {\n  private serverQueues: Record<string, RequestQueue>;\n  private maxConcurrentRequests: number;\n\n  constructor() {\n    this.serverQueues = {};\n    this.maxConcurrentRequests = getApplicationConfig().rasterServerMaxPerServerRequests;\n  }\n\n  private getServerQueue(serverKey: string): RequestQueue {\n    if (!this.serverQueues[serverKey]) {\n      this.serverQueues[serverKey] = {\n        activeRequests: 0,\n        queue: []\n      };\n    }\n    return this.serverQueues[serverKey];\n  }\n\n  getDebugInfo(): string {\n    const stats = Object.entries(this.serverQueues).map(([serverKey, queue]) => {\n      return `Server: ${serverKey}\n  Active Requests: ${queue.activeRequests}\n  Queued Requests: ${queue.queue.length}`;\n    });\n\n    return stats.length > 0\n      ? `Request Throttle Stats:\\n${stats.join('\\n')}`\n      : 'No active server queues';\n  }\n\n  async throttleRequest<T>(\n    serverKey: string,\n    requestFunction: () => Promise<T>,\n    maxConcurrentRequestsOverride?: number\n  ): Promise<T> {\n    const serverQueue = this.getServerQueue(serverKey);\n    const maxConcurrentRequests =\n      typeof maxConcurrentRequestsOverride === 'number'\n        ? maxConcurrentRequestsOverride\n        : this.maxConcurrentRequests;\n\n    if (serverQueue.activeRequests >= maxConcurrentRequests && Boolean(maxConcurrentRequests)) {\n      // Wait for a slot to become available\n      await new Promise<void>(resolve => {\n        serverQueue.queue.push(async () => {\n          try {\n            const result = await requestFunction();\n            resolve();\n            return result;\n          } catch (error) {\n            resolve();\n            return null;\n          }\n        });\n      });\n    }\n\n    serverQueue.activeRequests++;\n    try {\n      return await requestFunction();\n    } finally {\n      serverQueue.activeRequests--;\n      // Process next request in queue if any\n      const nextRequest = serverQueue.queue.shift();\n      if (nextRequest) {\n        nextRequest();\n      }\n    }\n  }\n}\n\n// Create a singleton instance\nlet requestThrottle: RequestThrottle | null = null;\n\nexport function getRequestThrottle(): RequestThrottle {\n  if (!requestThrottle) requestThrottle = new RequestThrottle();\n  return requestThrottle;\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {_Tile2DHeader} from '@deck.gl/geo-layers/typed';\nimport {TypedArray} from '@loaders.gl/loader-utils/src/types';\nimport {Texture2DProps} from '@luma.gl/webgl';\n\nimport {KeplerTable as KeplerDataset} from '@kepler.gl/table';\nimport type {ColorMap, StacTypes} from '@kepler.gl/types';\n\nexport type STACObject = StacTypes.STACObject;\nexport type CompleteSTACItem = StacTypes.CompleteSTACItem;\nexport type CompleteSTACCollection = StacTypes.CompleteSTACCollection;\nexport type CompleteSTACObject = StacTypes.CompleteSTACObject;\n\ntype DataTypeOfTheBand = StacTypes.DataTypeOfTheBand;\n\nexport type CompleteSTACAssetLinks =\n  | CompleteSTACItem['assets']\n  | CompleteSTACCollection['item_assets'];\n\nexport enum BandCombination {\n  /** Render a single raster band. */\n  Single = 'single',\n\n  /** Render three bands together on the GPU.\n   * Note that this could be \"true color\" if the first band is red, the second is green, and the\n   * third is blue. But it could also be \"false color\" if the three bands chosen are not RGB.\n   */\n  Rgb = 'rgb',\n\n  /** Calculate the Normalized Difference Vegetation Index (NDVI) on two bands. */\n  NormalizedDifference = 'normalizedDifference',\n\n  /** Calculate the EVI on two bands. */\n  EnhancedVegetationIndex = 'enhancedVegetationIndex',\n\n  /** Calculate SAVI on two bands. */\n  SoilAdjustedVegetationIndex = 'soilAdjustedVegetationIndex',\n\n  /** Calculate MSAVI on two bands. */\n  ModifiedSoilAdjustedVegetationIndex = 'modifiedSoilAdjustedVegetationIndex'\n}\n\n/**\n * Identifiers of STAC assets to load\n *\n * These identifiers should match the keys of the STAC assets object. Refer to\n * https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md#assets\n */\nexport type AssetIds = string[];\n\n/**\n * Indexes of bands within each asset that should be loaded.\n *\n * Each asset refers to a single Cloud-Optimized GeoTIFF file on disk. Each COG has a width, height,\n * and one or more bands.\n *\n * This array of indexes must be the same length as the list of AssetIds. The nth band index\n * describes the which index from the nth asset id to load.\n */\nexport type BandIndexes = number[];\n\n/**\n * An array of integers representing how bands should be reordered on the GPU. This allows for\n * faster changing of bands for single-asset raster data, such as NAIP. Will be null for multi-asset\n * STAC items.\n */\nexport type RenderBandIndexes = BandIndexes | null;\n\nexport type AssetRequestInfo = {\n  loadAssetIds: AssetIds;\n  loadBandIndexes: BandIndexes;\n  renderBandIndexes: RenderBandIndexes;\n};\n\n/** User-provided information for how to render a preset */\nexport type PresetOption = {\n  singleBand?: {\n    assetId: string;\n    bandIndex?: number;\n  };\n};\n\nexport type DataSourceParams = AssetRequestInfo & {\n  minZoom: number;\n  maxZoom: number;\n  minPixelValue: number;\n  maxPixelValue: number;\n  minCategoricalBandValue?: number;\n  maxCategoricalBandValue?: number;\n  globalBounds: number[] | null;\n  dataType: DataTypeOfTheBand;\n};\n\nexport type CategoricalColormapOptions = {\n  colorMap?: ColorMap;\n  minValue?: number;\n  maxValue?: number;\n};\n\nexport type ExtendedKeplerSTAC = {\n  rasterTileServerUrls?: [];\n  /** Optional per-layer override for max retries when fetching raster data */\n  rasterServerMaxRetries?: number;\n  /** Optional per-layer override for retry delay between attempts (ms) */\n  rasterServerRetryDelay?: number;\n  /** Optional per-layer override for which server errors are retried */\n  rasterServerServerErrorsToRetry?: number[];\n  /** Optional per-layer override for max concurrent requests per server */\n  rasterServerMaxPerServerRequests?: number;\n};\n\n/**\n * Custom fields we pass on to the getTileData callback\n * Anything required to know _what data to load_ should be passed in here.\n */\nexport type GetTileDataCustomProps = Pick<AssetRequestInfo, 'loadAssetIds' | 'loadBandIndexes'> & {\n  stac: CompleteSTACObject & ExtendedKeplerSTAC;\n  colormapId: string;\n  categoricalColorMap: ColorMap;\n  minCategoricalBandValue?: number;\n  maxCategoricalBandValue?: number;\n\n  /** If true, fetch terrain data as well as texture data. */\n  shouldLoadTerrain: boolean;\n  globalBounds: number[] | null;\n  useSTACSearching: boolean;\n  stacSearchProvider: string;\n  startDate: string;\n  endDate: string;\n\n  /* Stringified JSON representing a STAC API Search Query the backend should request from a server. */\n  _stacQuery?: string;\n};\n\n/** Properties provided to getTileData by the deck.gl TileLayer */\nexport interface GetTileDataDefaultProps {\n  index: {x: number; y: number; z: number};\n  url?: string;\n\n  /** Bounding box of the current web mercator tile. */\n  bbox: {west: number; north: number; east: number; south: number};\n\n  /** AbortSignal used for cancelling requests */\n  signal: AbortSignal;\n}\n\n/** Properties passed into the deck.gl TileLayer getTileData callback */\nexport type GetTileDataProps = GetTileDataCustomProps & GetTileDataDefaultProps;\n\nexport type GetTileDataOutput = any | null;\n\nexport type RenderSubLayersCustomProps = ColorRescaling &\n  Pick<AssetRequestInfo, 'renderBandIndexes'> & {\n    opacity: number;\n    linearRescaling: boolean;\n    linearRescalingFactor: [number, number];\n    nonLinearRescaling: boolean;\n    minPixelValue: number;\n    maxPixelValue: number;\n    bandCombination: BandCombination;\n    filterEnabled: boolean;\n    filterRange: [number, number];\n    currentTime: number;\n    dataType: DataTypeOfTheBand;\n    minCategoricalBandValue?: number;\n    maxCategoricalBandValue?: number;\n    hasCategoricalColorMap: boolean;\n    hasShadowEffect?: boolean;\n  };\n\nexport interface RenderSubLayersDefaultProps {\n  id: string;\n  tile: _Tile2DHeader<GetTileDataOutput>;\n  data: GetTileDataOutput;\n}\n\nexport type RenderSubLayersProps = RenderSubLayersCustomProps & RenderSubLayersDefaultProps;\n\n/** Required information on how to render each preset */\nexport interface PresetData {\n  label: string;\n  id: string;\n  bandCombination: BandCombination;\n  commonNames?: string[];\n  description?: string;\n  infoUrl?: string;\n}\n\nexport interface ColorRescaling {\n  gammaContrastFactor: number;\n  sigmoidalContrastFactor: number;\n  sigmoidalBiasFactor: number;\n  saturationValue: number;\n}\n\nexport interface ConfigOption {\n  label: string;\n  id: string;\n}\n\nexport type ColormapImageData = Texture2DProps;\n\nexport interface ImageData {\n  imageBands: Texture2DProps[] | null;\n  imageColormap?: Texture2DProps | null;\n  imageMask?: Texture2DProps | null;\n  imagePan?: Texture2DProps | null;\n  imageRgba?: Texture2DProps | null;\n}\n\nexport type AssetRequestData = {\n  url: string;\n  rasterServerUrl: string;\n  options: RequestInit;\n  useMask: boolean;\n  /** Pass this property through the request to pick specific bands from the response */\n  responseRequiredBandIndices?: number[] | null;\n  /** Optional per-request override for max retries when fetching NPY arrays */\n  rasterServerMaxRetries?: number;\n  /** Optional per-request override for retry delay between attempts (ms) */\n  rasterServerRetryDelay?: number;\n  /** Optional per-request override for which server errors are retried */\n  rasterServerServerErrorsToRetry?: number[];\n  /** Optional per-request override for max concurrent requests per server */\n  rasterServerMaxPerServerRequests?: number;\n};\n\nexport type NPYLoaderDataTypes =\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int8Array\n  | Uint16Array\n  | Int16Array\n  | Uint32Array\n  | Int32Array\n  | Float32Array\n  | Float64Array;\n\nexport interface NPYLoaderResponse {\n  data: NPYLoaderDataTypes;\n  header: {\n    descr: string;\n    shape: number[];\n  };\n}\n\nexport interface TerrainData {\n  header: {\n    vertexCount: number;\n    boundingBox: [[number, number, number], [number, number, number]];\n  };\n  mode: 4;\n  indices: {\n    value: TypedArray;\n    size: 1;\n  };\n  attributes: {\n    POSITION: {\n      value: TypedArray;\n      size: 3;\n    };\n    TEXCOORD_0: {\n      value: TypedArray;\n      size: 2;\n    };\n  };\n}\n\nexport type KeplerRasterDataset = KeplerDataset & {\n  metadata: CompleteSTACObject;\n};\n\nexport interface Tile2DHeader {\n  content?: {\n    terrain?: TerrainData;\n    images: GetTileDataOutput;\n  } | null;\n  get data(): GetTileDataOutput;\n}\n"
  },
  {
    "path": "src/layers/src/raster-tile/url.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* Utilities for creating request urls */\n/* global URLSearchParams */\n\nimport {StacTypes} from '@kepler.gl/types';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\nimport {DEFAULT_BAND_MAPPINGS} from './config';\nimport {\n  AssetIds,\n  BandIndexes,\n  CompleteSTACItem,\n  CompleteSTACCollection,\n  GetTileDataProps\n} from './types';\n\ntype Item = StacTypes.STACItem;\ntype Collection = StacTypes.STACCollection;\n\nconst TILE_SIZE: 256 | 512 = 256;\n\ninterface StacSearchInfo {\n  sentinelCollectionName: string[];\n  stacSearchUrl: string;\n}\n\nconst STAC_SEARCH_DATA: Record<string, StacSearchInfo> = {\n  // Commenting out Microsoft for now\n  // microsoft: {\n  //   sentinelCollectionName: ['sentinel-2-l2a'],\n  //   stacSearchUrl: 'https://planetarycomputer.microsoft.com/api/stac/v1/search'\n  // },\n  'earth-search': {\n    sentinelCollectionName: ['sentinel-s2-l2a-cogs'],\n    stacSearchUrl: 'https://earth-search.aws.element84.com/v0/search'\n  }\n};\n\n/**\n * Construct query parameters to be sent to STAC API instance\n */\nfunction constructStacApiQuery(options: {\n  stac: Item | Collection;\n  startDate: string;\n  endDate: string;\n  stacSearchProvider: string;\n}): {collections: string[]; datetime: string} {\n  const {stac, startDate, endDate, stacSearchProvider} = options;\n\n  // This is a quick hack to support the same Sentinel 2 STAC object for searching both microsoft\n  // and AWS\n  const collections = STAC_SEARCH_DATA[stacSearchProvider]?.sentinelCollectionName || [stac.id];\n\n  return {\n    collections,\n    datetime: `${startDate}T00:00:00Z/${endDate}T23:59:59Z`\n  };\n}\n\n/**\n * Perform lookup to find url of desired STAC search provider\n */\nfunction getStacApiUrl(stacSearchProvider: string): string {\n  return STAC_SEARCH_DATA[stacSearchProvider].stacSearchUrl;\n}\n\nexport function getStacApiUrlParams(options: {\n  stac: CompleteSTACCollection;\n  stacSearchProvider: string;\n  startDate: string;\n  endDate: string;\n  mask?: boolean;\n  loadAssetIds: AssetIds;\n  _stacQuery?: string;\n}): URLSearchParams | null {\n  const {stac, loadAssetIds, stacSearchProvider, mask = false} = options;\n  const query = options._stacQuery || JSON.stringify(constructStacApiQuery(options));\n  const searchUrl = getStacApiUrl(stacSearchProvider);\n\n  if (!searchUrl) {\n    return null;\n  }\n\n  const bandIndexAssets = loadAssetIds.map(assetId => {\n    const mapping = DEFAULT_BAND_MAPPINGS[stac.id];\n    if (!mapping) {\n      // TODO provide a UI to setup custom band mapping\n      return assetId;\n    }\n\n    const bandIndex = mapping[assetId];\n    if (bandIndex) {\n      return bandIndex;\n    }\n\n    // This is most likely incorrect as BXX is expected, not common name\n    return assetId;\n  });\n\n  return new URLSearchParams({\n    assets: bandIndexAssets.join(','),\n    return_mask: String(mask),\n    url: searchUrl,\n    query\n  });\n}\n\nexport function bandIndexesToURLParams(\n  urlParams: URLSearchParams,\n  bandIndexes: BandIndexes,\n  useNewFormat: boolean\n): URLSearchParams {\n  if (useNewFormat) {\n    // for newer titiler versions\n    bandIndexes.forEach(bandIndex => {\n      urlParams.append('bidx', String(bandIndex + 1));\n    });\n  } else {\n    // The parameter in titiler is `bands` for landsat/sentinel and `bidx` for COG\n    // GDAL/Rasterio/rio-tiler start band indexing at one\n    // older titiler versions\n    urlParams.append('bidx', bandIndexes.map(val => val + 1).join(','));\n  }\n\n  return urlParams;\n}\n\nexport function getSingleCOGUrlParams(options: {\n  stac: CompleteSTACItem;\n  loadAssetId: string;\n  loadBandIndexes: BandIndexes;\n  mask?: boolean;\n}): URLSearchParams | null {\n  const {stac, loadAssetId, loadBandIndexes, mask = false} = options;\n  const url = stac.assets[loadAssetId].href;\n\n  if (!url) {\n    return null;\n  }\n\n  const urlParams = new URLSearchParams({\n    return_mask: String(mask),\n    url\n  });\n  return bandIndexesToURLParams(\n    urlParams,\n    loadBandIndexes,\n    Boolean(\n      stac.rasterServerUseLatestTitiler ?? getApplicationConfig().rasterServerUseLatestTitiler\n    )\n  );\n}\n\n/**\n * Construct full URL to load tile from a Titiler-based backend\n */\nexport function getTitilerUrl(options: {\n  stac: GetTileDataProps['stac'];\n  useSTACSearching: boolean;\n  x: number;\n  y: number;\n  z: number;\n}): {url: string; rasterServerUrl: string} {\n  // mask Set to false for mosaics because entire image is assumed to be valid\n  const {stac, useSTACSearching, x, y, z} = options;\n\n  if (!stac.rasterTileServerUrls?.length) {\n    throw new Error('No raster tile servers');\n  }\n\n  const pathStem = getTitilerPathMapping(stac, useSTACSearching);\n  const scale = TILE_SIZE === 512 ? '@2x' : '';\n  const domain = chooseDomain(stac.rasterTileServerUrls, x, y);\n  return {\n    url: `${domain}/${pathStem}/tiles/WebMercatorQuad/${z}/${x}/${y}${scale}.npy`,\n    rasterServerUrl: domain\n  };\n}\n\nexport function getTitilerPathMapping(\n  stac: GetTileDataProps['stac'],\n  useSTACSearching = false\n): string {\n  if (useSTACSearching) {\n    return 'stac/mosaic';\n  }\n\n  return 'cog';\n}\n\n/**\n * Choose from available domains to load images from\n *\n * @param x  x tile index\n * @param y  y tile index\n *\n * @return domain\n */\nfunction chooseDomain(domains: string[], x: number, y: number): string {\n  const index = Math.abs(x + y) % domains.length;\n  return domains[index];\n}\n\nexport function getTerrainUrl(\n  rasterTileServerUrls: string[],\n  x: number,\n  y: number,\n  z: number,\n  meshMaxError: number\n): {url: string; rasterServerUrl: string} {\n  const scale = TILE_SIZE === 512 ? '@2x' : '';\n\n  const params = new URLSearchParams({\n    url: 'terrarium',\n    mesh_max_error: meshMaxError.toFixed(2)\n  });\n  const domain = chooseDomain(rasterTileServerUrls, x, y);\n  const baseUrl = `${domain}/mesh/tiles/${z}/${x}/${y}${scale}.terrain?`;\n  return {url: baseUrl + params.toString(), rasterServerUrl: domain};\n}\n\n/**\n * get mesh max error for z value\n * @param z mercator tile z coord\n * @param multiplier multipler applied to default error\n *\n * Uses suggestion from here\n * https://www.linkedin.com/pulse/fast-cesium-terrain-rendering-new-quantized-mesh-output-alvaro-huarte/\n */\nexport function getMeshMaxError(z: number, multiplier: number): number {\n  return (77067.34 / (1 << z)) * multiplier;\n}\n\nexport const RasterLayerResources = {\n  rasterColorMap: (colormapId: string) => {\n    return `${getApplicationConfig().cdnUrl}/raster/colormaps/${colormapId}.png`;\n  }\n};\n"
  },
  {
    "path": "src/layers/src/s2-geometry-layer/s2-geometry-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {S2Layer} from '@deck.gl/geo-layers';\nimport {\n  HIGHLIGH_COLOR_3D,\n  CHANNEL_SCALES,\n  LAYER_VIS_CONFIGS,\n  DEFAULT_COLOR_UI\n} from '@kepler.gl/constants';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport Layer, {\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  LayerColorConfig,\n  LayerSizeConfig,\n  LayerStrokeColorConfig,\n  LayerHeightConfig\n} from '../base-layer';\nimport {\n  ColorRange,\n  Merge,\n  RGBColor,\n  VisConfigBoolean,\n  VisConfigColorRange,\n  VisConfigColorSelect,\n  VisConfigNumber,\n  VisConfigRange,\n  LayerColumn\n} from '@kepler.gl/types';\nimport S2LayerIcon from './s2-layer-icon';\nimport {getS2Center, validS2Token} from './s2-utils';\nimport {DataContainerInterface, createDataContainer} from '@kepler.gl/utils';\n\nexport type S2GeometryLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  filled: VisConfigBoolean;\n  thickness: VisConfigNumber;\n  strokeColor: VisConfigColorSelect;\n  strokeColorRange: VisConfigColorRange;\n  sizeRange: VisConfigRange;\n  stroked: VisConfigBoolean;\n  enable3d: VisConfigBoolean;\n  elevationScale: VisConfigNumber;\n  enableElevationZoomFactor: VisConfigBoolean;\n  heightRange: VisConfigRange;\n  wireframe: VisConfigBoolean;\n};\n\nexport type S2GeometryLayerColumnsConfig = {\n  token: LayerColumn;\n};\n\nexport type S2GeometryLayerVisConfig = {\n  opacity: number;\n  colorRange: ColorRange;\n  filled: boolean;\n  thickness: number;\n  strokeColor: RGBColor;\n  strokeColorRange: ColorRange;\n  sizeRange: [number, number];\n  stroked: boolean;\n  enable3d: boolean;\n  elevationScale: number;\n  enableElevationZoomFactor: boolean;\n  heightRange: [number, number];\n  wireframe: boolean;\n};\n\nexport type S2GeometryLayerVisualChannelConfig = LayerColorConfig &\n  LayerSizeConfig &\n  LayerStrokeColorConfig &\n  LayerHeightConfig;\nexport type S2GeometryLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: S2GeometryLayerColumnsConfig; visConfig: S2GeometryLayerVisConfig}\n> &\n  S2GeometryLayerVisualChannelConfig;\n\nexport type S2GeometryLayerData = {\n  index: number;\n  token: any;\n};\n\nconst zoomFactorValue = 8;\n\nexport const S2_TOKEN_FIELDS: {\n  token: ['s2', 's2_token'];\n} = {\n  token: ['s2', 's2_token']\n};\n\nexport const s2RequiredColumns: ['token'] = ['token'];\nexport const S2TokenAccessor =\n  ({token}: S2GeometryLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    dc.valueAt(d.index, token.fieldIdx);\n\nexport const defaultElevation = 500;\nexport const defaultLineWidth = 1;\n\nexport const S2VisConfigs: {\n  // Filled color\n  opacity: 'opacity';\n  colorRange: 'colorRange';\n  filled: VisConfigBoolean;\n\n  // stroke\n  thickness: VisConfigNumber;\n  strokeColor: 'strokeColor';\n  strokeColorRange: 'strokeColorRange';\n  sizeRange: 'strokeWidthRange';\n  stroked: 'stroked';\n\n  // height\n  enable3d: 'enable3d';\n  elevationScale: 'elevationScale';\n  enableElevationZoomFactor: 'enableElevationZoomFactor';\n  fixedHeight: 'fixedHeight';\n  heightRange: 'elevationRange';\n\n  // wireframe\n  wireframe: 'wireframe';\n} = {\n  // Filled color\n  opacity: 'opacity',\n  colorRange: 'colorRange',\n  filled: {\n    ...LAYER_VIS_CONFIGS.filled,\n    type: 'boolean',\n    label: 'Fill Color',\n    defaultValue: true,\n    property: 'filled'\n  },\n\n  // stroke\n  thickness: {\n    ...LAYER_VIS_CONFIGS.thickness,\n    defaultValue: 0.5\n  },\n  strokeColor: 'strokeColor',\n  strokeColorRange: 'strokeColorRange',\n  sizeRange: 'strokeWidthRange',\n  stroked: 'stroked',\n\n  // height\n  enable3d: 'enable3d',\n  elevationScale: 'elevationScale',\n  enableElevationZoomFactor: 'enableElevationZoomFactor',\n  fixedHeight: 'fixedHeight',\n  heightRange: 'elevationRange',\n\n  // wireframe\n  wireframe: 'wireframe'\n};\n\nexport default class S2GeometryLayer extends Layer {\n  dataToFeature: any;\n  declare visConfigSettings: S2GeometryLayerVisConfigSettings;\n  declare config: S2GeometryLayerConfig;\n  constructor(props) {\n    super(props);\n    this.registerVisConfig(S2VisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      S2TokenAccessor(this.config.columns)(dataContainer);\n  }\n\n  get type(): 's2' {\n    return 's2';\n  }\n\n  get name(): 'S2' {\n    return 'S2';\n  }\n\n  get requiredLayerColumns() {\n    return s2RequiredColumns;\n  }\n\n  get layerIcon() {\n    return S2LayerIcon;\n  }\n\n  get visualChannels() {\n    const visualChannels = super.visualChannels;\n    return {\n      color: {\n        ...visualChannels.color,\n        accessor: 'getFillColor'\n      },\n      size: {\n        ...visualChannels.size,\n        property: 'stroke',\n        accessor: 'getLineWidth',\n        condition: config => config.visConfig.stroked,\n        defaultValue: defaultLineWidth\n      },\n      strokeColor: {\n        property: 'strokeColor',\n        field: 'strokeColorField',\n        scale: 'strokeColorScale',\n        domain: 'strokeColorDomain',\n        range: 'strokeColorRange',\n        key: 'strokeColor',\n        channelScaleType: CHANNEL_SCALES.color,\n        accessor: 'getLineColor',\n        condition: config => config.visConfig.stroked,\n        nullValue: visualChannels.color.nullValue,\n        defaultValue: config => config.visConfig.strokeColor || config.color\n      },\n      height: {\n        property: 'height',\n        field: 'heightField',\n        scale: 'heightScale',\n        domain: 'heightDomain',\n        range: 'heightRange',\n        key: 'height',\n        channelScaleType: CHANNEL_SCALES.size,\n        accessor: 'getElevation',\n        condition: config => config.visConfig.enable3d,\n        nullValue: 0,\n        defaultValue: defaultElevation\n      }\n    };\n  }\n\n  getDefaultLayerConfig(props: LayerBaseConfigPartial) {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {});\n    return {\n      ...defaultLayerConfig,\n\n      // add height visual channel\n      heightField: null,\n      heightDomain: [0, 1],\n      heightScale: 'linear',\n\n      // add stroke color visual channel\n      strokeColorField: null,\n      strokeColorDomain: [0, 1],\n      strokeColorScale: 'quantile',\n      colorUI: {\n        ...defaultLayerConfig.colorUI,\n        strokeColorRange: DEFAULT_COLOR_UI\n      }\n    };\n  }\n\n  static findDefaultLayerProps({fields = []}: KeplerTable) {\n    const foundColumns = this.findDefaultColumnField(S2_TOKEN_FIELDS, fields);\n    if (!foundColumns || !foundColumns.length) {\n      return {props: []};\n    }\n\n    return {\n      props: foundColumns.map(columns => ({\n        isVisible: true,\n        label: 'S2',\n        columns\n      }))\n    };\n  }\n\n  calculateDataAttribute({filteredIndex}: KeplerTable, getS2Token) {\n    const data: S2GeometryLayerData[] = [];\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const token = getS2Token({index});\n      if (validS2Token(token)) {\n        data.push({\n          index,\n          token\n        });\n      }\n    }\n    return data;\n  }\n\n  updateLayerMeta(dataset: KeplerTable, getS2Token) {\n    const {dataContainer} = dataset;\n    // add safe row flag\n    const centroids = dataContainer.reduce(\n      (acc, entry, index) => {\n        const s2Token = getS2Token({index});\n        if (validS2Token(s2Token)) {\n          acc.push(getS2Center(s2Token));\n        }\n\n        return acc;\n      },\n      [],\n      true\n    );\n\n    const centroidsDataContainer = createDataContainer(centroids);\n    const bounds = this.getPointsBounds(centroidsDataContainer, (d, dc) => [\n      dc.valueAt(d.index, 0),\n      dc.valueAt(d.index, 1)\n    ]);\n    this.dataToFeature = {centroids};\n    this.updateMeta({bounds});\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const getS2Token = this.getPositionAccessor(dataContainer);\n    const {data} = this.updateData(datasets, oldLayerData);\n\n    const accessors = this.getAttributeAccessors({dataContainer});\n\n    return {\n      data,\n      getS2Token,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),\n      ...accessors\n    };\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, interactionConfig, mapState} = opts;\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n\n    const eleZoomFactor = this.getElevationZoomFactor(mapState);\n    const zoomFactor = this.getZoomFactor(mapState);\n    const {config} = this;\n    const {visConfig} = config;\n\n    const updateTriggers = {\n      ...this.getVisualChannelUpdateTriggers(),\n      getFilterValue: gpuFilter.filterValueUpdateTriggers\n    };\n\n    return [\n      new S2Layer({\n        ...defaultLayerProps,\n        ...interactionConfig,\n        ...data,\n        getS2Token: (d: any) => d.token,\n\n        autoHighlight: visConfig.enable3d,\n        highlightColor: HIGHLIGH_COLOR_3D,\n\n        // stroke\n        lineWidthScale: visConfig.thickness * zoomFactor * zoomFactorValue,\n        stroked: visConfig.stroked,\n        lineMiterLimit: 2,\n\n        // Filled color\n        filled: visConfig.filled,\n        opacity: visConfig.opacity,\n        wrapLongitude: false,\n\n        // Elevation\n        elevationScale: visConfig.elevationScale * eleZoomFactor,\n        extruded: visConfig.enable3d,\n\n        wireframe: visConfig.wireframe,\n\n        pickable: true,\n\n        updateTriggers\n      })\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/s2-geometry-layer/s2-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nclass S2LayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n  static defaultProps = {\n    height: '18px',\n    predefinedClassName: 's2-layer-icon'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <path d=\"M14.76,58,15,20.54,50.06,6.75V44Zm4-34.86L18.6,52.38l27.66-11V12.33Z\" />\n        <path d=\"M27.21,38.58a7.38,7.38,0,0,1-3.62-.9l.3-2a6.49,6.49,0,0,0,3.3.9c1.49,0,2.18-.59,2.18-1.63,0-2.26-5.71-1.28-5.71-5.3,0-2,1.26-3.54,4.16-3.54a8.38,8.38,0,0,1,3.28.64l-.29,1.9a8.41,8.41,0,0,0-2.88-.54c-1.63,0-2.14.66-2.14,1.42,0,2.19,5.71,1.18,5.71,5.27C31.5,37.16,29.93,38.58,27.21,38.58Z\" />\n        <path d=\"M36.17,36.36v0h5.06l0,2H33.32V36.9c3-2.88,5.67-5.09,5.67-7,0-1-.64-1.67-2.19-1.67a5,5,0,0,0-3,1.1l-.53-1.79a6.31,6.31,0,0,1,3.91-1.28c2.66,0,4,1.34,4,3.41C41.21,31.94,39.13,33.89,36.17,36.36Z\" />\n      </Base>\n    );\n  }\n}\n\nexport default S2LayerIcon;\n"
  },
  {
    "path": "src/layers/src/s2-geometry-layer/s2-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Long from 'long';\nimport {S2} from 's2-geometry';\n\nconst MAXIMUM_TOKEN_LENGTH = 16;\n\n/**\n * Retrieve S2 geometry center\n * @param s2Token string | number\n * @return {*[]}\n */\nexport function getS2Center(s2Token) {\n  const paddedToken = s2Token.toString().padEnd(MAXIMUM_TOKEN_LENGTH, '0');\n  const s2Id = Long.fromString(paddedToken, MAXIMUM_TOKEN_LENGTH);\n  const {lat, lng} = S2.idToLatLng(s2Id.toString());\n  return [lng, lat];\n}\n\nconst re = new RegExp('^[0-9a-z]*$', 'g');\n// simple test to see if token only contains numbers and letters\nexport function validS2Token(token) {\n  return typeof token === 'string' && Boolean(token.match(re));\n}\n"
  },
  {
    "path": "src/layers/src/scenegraph-layer/scenegraph-info-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nimport Table from '../example-table';\n\nconst StyledTitle = styled.div`\n  font-size: 20px;\n  letter-spacing: 1.25px;\n  margin: 18px 0 14px 0;\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst ExampleTable = () => (\n  <Table className=\"scenegraph-example-table\">\n    <thead>\n      <tr>\n        <th>point_lat</th>\n        <th>point_lng</th>\n        <th>alt</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr>\n        <td>37.769897</td>\n        <td>-122.41168</td>\n        <td>0</td>\n      </tr>\n      <tr>\n        <td>37.806928</td>\n        <td>-122.40218</td>\n        <td>0</td>\n      </tr>\n      <tr>\n        <td>37.778564</td>\n        <td>-122.39096</td>\n        <td>1000</td>\n      </tr>\n      <tr>\n        <td>37.745995</td>\n        <td>-122.30220</td>\n        <td>2000</td>\n      </tr>\n      <tr>\n        <td>37.329841</td>\n        <td>-122.103847</td>\n        <td>3000</td>\n      </tr>\n    </tbody>\n  </Table>\n);\n\nconst ScenegraphInfoModalFactory = () => {\n  const ScenegraphInfoModal = () => (\n    <div className=\"scenegraph-info-modal\">\n      <div className=\"scenegraph-info-modal__description\">\n        <span>\n          In your csv you can specify points with optional altitude. The models will show at each\n          point you specify. You can use a sample model or upload one in{' '}\n        </span>\n        <code>glTF (GLB or Embedded)</code>\n        <span> format.</span>\n      </div>\n      <div className=\"scenegraph-info-modal__example\">\n        <StyledTitle>Example:</StyledTitle>\n        <ExampleTable />\n      </div>\n      <div className=\"scenegraph-info-modal__icons\">\n        <StyledTitle>Sample Models</StyledTitle>\n        <div>Duck</div>\n        <div>Use your own model</div>\n      </div>\n    </div>\n  );\n\n  return ScenegraphInfoModal;\n};\n\nexport default ScenegraphInfoModalFactory;\n"
  },
  {
    "path": "src/layers/src/scenegraph-layer/scenegraph-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class ScenegraphLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'scenegraph-layer-icon',\n    totalColor: 3\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g transform=\"translate(-20, -99)\" id=\"layer1\">\n          <g transform=\"matrix(0.26458333,0,0,0.26458333,-191.59545,42.388813)\" id=\"g3042\">\n            <ellipse\n              id=\"path2999\"\n              cx=\"931.42627\"\n              cy=\"431.70096\"\n              rx=\"62.283249\"\n              ry=\"3.8673975\"\n              transform=\"matrix(1.0332818,0,0,0.76491127,-30.674511,101.23938)\"\n            />\n            <g id=\"g7696\" transform=\"translate(525.68251,-207.68859)\">\n              <path\n                id=\"path7668\"\n                d=\"m 393.98828,480.0918 c -10.68594,0.80336 -21.57673,7.18633 -28.60547,16.5332 -1.00324,0.62224 -3.5626,2.19214 -5.32812,2.97656 -2.56279,1.13864 -5.17733,2.74246 -7.98047,2.66016 -1.90943,-0.0561 -3.53566,-1.4477 -5.32031,-2.12891 -4.53998,-1.72443 -9.58546,-5.01773 -13.83594,-1.06445 -1.06796,1.52824 -0.66318,3.76636 -0.26563,5.58789 0.61739,2.82883 2.18398,5.45191 3.99024,7.71484 2.3163,2.9019 8.51367,7.1836 8.51367,7.1836 0,0 -5.90097,1.56898 -7.18164,3.99023 -0.45797,0.86587 -0.24013,2.08692 0.26562,2.92578 1.09219,1.81161 3.30762,2.80797 5.32032,3.45899 4.02627,1.30232 8.51745,-0.13838 12.67968,0.625 1.31495,0.24117 2.49566,1.01912 3.81446,1.23828 0.60636,0.10077 1.45382,0.0869 2.07422,0.0664 1.72295,3.33668 4.08103,6.36876 7.15625,8.80468 0,0 -2.22425,5.11025 -3.72266,7.44532 -8.94053,13.58909 -24.40686,25.32714 -26.73438,43.99218 0.0505,12.91431 6.57316,25.37407 16.24415,32.82422 16.96401,13.06839 41.70379,15.60258 62.9414,12.85938 19.21169,-2.48152 39.64942,-10.88975 52.11328,-25.71875 8.24478,-9.80932 10.46731,-24.07583 10.82813,-36.88477 0.29354,-10.4205 0.0459,-22.87017 -7.10547,-30.45508 -2.17907,-2.31118 -6.14999,-3.78838 -9.13672,-2.70703 -3.82412,1.38452 -4.00601,7.16466 -6.76758,10.15039 -2.72735,2.94875 -5.62298,6.30576 -9.47461,7.44531 -2.38943,0.70694 -5.03518,-0.0449 -7.44531,-0.67773 -7.17835,-1.88468 -13.0331,-7.65187 -20.30469,-9.13672 -5.52704,-1.1286 -16.91992,0.33984 -16.91992,0.33984 l 6.0918,-3.38476 c 0,0 10.28858,-6.55346 13.53515,-11.50586 4.26486,-6.50575 7.12111,-14.56296 6.76758,-22.33398 -0.86105,-19.16955 -13.62762,-32.46378 -36.20703,-32.82422 z\"\n              />\n              <path\n                id=\"path7007\"\n                d=\"m 338.82884,592.10146 c 2.32751,-18.66504 17.79291,-30.40265 26.73344,-43.99174 1.49841,-2.33507 3.72237,-7.44474 3.72237,-7.44474 -9.72152,-7.70054 -12.30749,-21.34514 -10.15193,-31.80941 4.68877,-15.70277 19.97053,-27.64482 34.85497,-28.76382 22.57941,0.36044 35.34754,13.65505 36.20859,32.8246 0.35353,7.77102 -2.5031,15.82851 -6.76796,22.33426 -3.24657,4.9524 -13.53592,11.50552 -13.53592,11.50552 l -6.09116,3.38399 c 0,0 11.39286,-1.467 16.9199,-0.3384 7.27159,1.48485 13.12552,7.25206 20.30387,9.13674 2.41013,0.63278 5.05533,1.38374 7.44476,0.6768 3.85163,-1.13955 6.74779,-4.496 9.47514,-7.44475 2.76157,-2.98573 2.94384,-8.76743 6.76796,-10.15195 2.98673,-1.08135 6.95767,0.39601 9.13674,2.70719 7.15138,7.58491 7.3999,20.03532 7.10636,30.45582 -0.36082,12.80894 -2.58395,27.07605 -10.82873,36.88537 -12.46386,14.829 -32.9016,23.23673 -52.11329,25.71825 -21.23761,2.7432 -45.97799,0.20927 -62.942,-12.85912 -9.67099,-7.45015 -16.19266,-19.9103 -16.24311,-32.82461 z\"\n              />\n              <path\n                id=\"path7009\"\n                d=\"m 338.82929,592.10139 c 0.0504,12.91431 6.57076,25.37435 16.24175,32.82449 16.96402,13.0684 41.70592,15.60201 62.94353,12.85882 16.49647,-2.13082 33.88089,-8.64571 46.3608,-19.82886 -11.1421,7.01421 -24.41974,11.29014 -37.17209,12.93733 -21.23762,2.74319 -45.97952,0.20958 -62.94353,-12.85882 -9.67099,-7.45014 -16.19132,-13.1378 -16.24175,-26.05211 1.29011,-12.22719 20.91701,-42.26515 18.15858,-41.25572 -2.75843,1.00943 -27.16334,24.82083 -27.34729,41.37487 z\"\n              />\n              <path\n                id=\"path7019\"\n                d=\"m 377.39451,569.59751 c 5.20454,-4.30421 12.0709,-7.93589 18.81215,-7.52486 10.1405,0.6183 16.82414,12.34588 26.71325,14.67347 9.16319,2.15674 28.21822,-1.12873 28.21822,-1.12873 0,0 -5.26736,15.74679 -10.91105,21.44586 -7.06529,7.13462 -16.80867,12.27156 -26.71325,13.92098 -8.25808,1.37523 -17.24717,-0.21883 -24.83203,-3.76243 -6.52035,-3.04627 -12.9578,-7.86122 -16.17845,-14.29723 -2.08339,-4.16338 -2.25437,-9.40356 -1.12872,-13.92099 0.90003,-3.61206 3.15128,-7.0337 6.01988,-9.40607 z\"\n              />\n              <circle\n                transform=\"matrix(1.0416624,0,0,1.0416624,-10.12887,-52.065753)\"\n                r=\"8\"\n                cy=\"542.64807\"\n                cx=\"388.79144\"\n                id=\"path7031\"\n              />\n              <circle\n                transform=\"matrix(1.1485883,0,0,1.1485883,-51.700726,-110.08886)\"\n                id=\"circle7125\"\n                cx=\"384.53302\"\n                cy=\"599.31769\"\n                r=\"8\"\n              />\n              <path\n                id=\"path7130\"\n                d=\"m 362.8314,531.83625 c 0,0 -1.86312,0.10762 -2.77606,-0.0441 -1.3188,-0.21916 -2.50024,-0.99563 -3.81519,-1.2368 -4.16223,-0.76338 -8.65327,0.67681 -12.67954,-0.62551 -2.0127,-0.65102 -4.22868,-1.64695 -5.32087,-3.45856 -0.50575,-0.83886 -0.72402,-2.06062 -0.26605,-2.92649 1.28067,-2.42125 7.18318,-3.99065 7.18318,-3.99065 0,0 -6.1971,-4.28129 -8.5134,-7.18319 -1.80626,-2.26293 -3.37327,-4.88645 -3.99066,-7.71528 -0.39755,-1.82153 -0.80191,-4.05867 0.26605,-5.58691 4.25048,-3.95328 9.29431,-0.66025 13.83429,1.06418 1.78465,0.68121 3.41144,2.07223 5.32087,2.12834 2.80314,0.0823 5.41853,-1.5218 7.98132,-2.66044 2.14112,-0.9513 6.11901,-3.45857 6.11901,-3.45857 0,0 -2.51761,3.36571 -3.19252,5.32088 -0.6999,2.02751 -0.77532,4.24025 -0.79815,6.38505 -0.0274,2.58533 0.36815,5.1658 0.79815,7.71527 0.51514,3.05437 2.24347,5.95014 2.12834,9.0455 -0.0938,2.52426 -2.27877,7.22725 -2.27877,7.22725 z\"\n              />\n              <ellipse\n                transform=\"rotate(-70.917917)\"\n                ry=\"8.876749\"\n                rx=\"11.292704\"\n                cy=\"523.04565\"\n                cx=\"-351.69467\"\n                id=\"path7132\"\n              />\n              <ellipse\n                id=\"ellipse7134\"\n                cx=\"-371.65863\"\n                cy=\"539.63354\"\n                rx=\"6.4400997\"\n                ry=\"5.6922021\"\n                transform=\"matrix(0.3727878,-0.92791662,0.95838646,0.28547397,0,0)\"\n              />\n              <ellipse\n                transform=\"matrix(1.2858994,0,0,1.2858994,493.87481,-538.72884)\"\n                ry=\"1.7910415\"\n                rx=\"1.877719\"\n                cy=\"810.7829\"\n                cx=\"-89.047676\"\n                id=\"path7136\"\n              />\n              <path\n                transform=\"matrix(1.1485883,0,0,1.1485883,-51.700726,-110.08886)\"\n                id=\"path7198\"\n                d=\"m 383.88781,586.81856 c 0,0 4.75583,1.20253 7.03749,2.08594 2.91222,1.12756 8.43329,4.0806 8.43329,4.0806 l 2.17633,-1.0882 c 0,0 -6.13833,-5.82773 -10.06554,-6.2569 -5.46591,-0.59732 -7.58157,1.17856 -7.58157,1.17856 z\"\n              />\n              <path\n                transform=\"matrix(1.1485883,0,0,1.1485883,-51.700726,-110.08886)\"\n                id=\"path7248\"\n                d=\"m 397.87853,516.42095 v 2 c 11.54716,4.14107 17.99592,13.80846 18.56055,26.37891 9.2e-4,0.0202 9.5e-4,0.0404 0.002,0.0605 0.0245,-0.68856 0.0292,-1.37543 -0.002,-2.06054 -0.56463,-12.57045 -7.01339,-22.23784 -18.56055,-26.37891 z m -43.76562,25.92578 c -0.13422,1.35759 -0.19199,2.74885 -0.14649,4.16016 0.0423,-1.42272 0.18091,-2.81599 0.4043,-4.16016 z\"\n              />\n              <ellipse\n                transform=\"matrix(1.1796431,0.18084016,-0.22761203,0.93723889,-70.598803,-9.4836272)\"\n                ry=\"2.1433234\"\n                rx=\"6.0402751\"\n                cy=\"433.62253\"\n                cx=\"484.71768\"\n                id=\"path7276\"\n              />\n              <path\n                transform=\"matrix(1.1485883,0,0,1.1485883,-51.700726,-110.08886)\"\n                id=\"path7358\"\n                d=\"m 450.56176,570.21133 c 0.31422,-0.51687 0.84685,-1.08434 1.45172,-1.08879 0.8227,-0.006 1.51933,0.78122 1.99611,1.45172 1.20869,1.69982 1.81464,5.98831 1.81464,5.98831 0,0 -4.7514,-2.00855 -5.62539,-4.17367 -0.27546,-0.68237 -0.0193,-1.54878 0.36292,-2.17757 z\"\n              />\n              <path\n                transform=\"matrix(1.1485883,0,0,1.1485883,-51.700726,-110.08886)\"\n                id=\"path7436\"\n                d=\"m 399.55635,576.28702 c 0,0 5.77103,-0.38519 8.54224,0.27556 7.19511,1.71555 13.03674,7.22358 20.1156,9.36891 2.31239,0.7008 7.16446,1.10222 7.16446,1.10222 0,0 -9.1879,1.00957 -13.50225,-0.27555 -4.98282,-1.48424 -8.43433,-6.25057 -13.22669,-8.26669 -2.87487,-1.20944 -9.09336,-2.20445 -9.09336,-2.20445 z\"\n              />\n            </g>\n          </g>\n        </g>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/scenegraph-layer/scenegraph-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ScenegraphLayer as DeckScenegraphLayer} from '@deck.gl/mesh-layers';\nimport {load} from '@loaders.gl/core';\nimport {GLTFLoader, postProcessGLTF} from '@loaders.gl/gltf';\n\nimport Layer, {LayerBaseConfig} from '../base-layer';\nimport ScenegraphLayerIcon from './scenegraph-layer-icon';\nimport ScenegraphInfoModalFactory from './scenegraph-info-modal';\nimport {LAYER_VIS_CONFIGS} from '@kepler.gl/constants';\nimport {\n  ColorRange,\n  Merge,\n  VisConfigColorRange,\n  VisConfigNumber,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {default as KeplerTable} from '@kepler.gl/table';\nimport {DataContainerInterface} from '@kepler.gl/utils';\n\nexport type ScenegraphLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  sizeScale: VisConfigNumber;\n  angleX: VisConfigNumber;\n  angleY: VisConfigNumber;\n  angleZ: VisConfigNumber;\n};\n\nexport type ScenegraphLayerColumnsConfig = {\n  lat: LayerColumn;\n  lng: LayerColumn;\n  altitude?: LayerColumn;\n};\n\nexport type ScenegraphLayerVisConfig = {\n  opacity: number;\n  colorRange: ColorRange;\n  sizeScale: number;\n  angleX: number;\n  angleY: number;\n  angleZ: number;\n  scenegraph: string;\n};\n\nexport type ScenegraphLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: ScenegraphLayerColumnsConfig; visConfig: ScenegraphLayerVisConfig}\n>;\n\nexport type ScenegraphLayerData = {position: number[]; index: number};\n\nexport const scenegraphRequiredColumns: ['lat', 'lng'] = ['lat', 'lng'];\nexport const scenegraphOptionalColumns: ['altitude'] = ['altitude'];\n\nexport function fetchGltf(url, {propName, layer}: {propName?: string; layer?: any} = {}) {\n  if (propName === 'scenegraph') {\n    return load(url, GLTFLoader, layer.getLoadOptions()).then(gltfWithBuffers =>\n      postProcessGLTF(gltfWithBuffers)\n    );\n  }\n\n  return fetchGltf(url).then(response => response.json());\n}\n\nexport const scenegraphPosAccessor =\n  ({lat, lng, altitude}: ScenegraphLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    [\n      dc.valueAt(d.index, lng.fieldIdx),\n      dc.valueAt(d.index, lat.fieldIdx),\n      altitude && altitude.fieldIdx > -1 ? dc.valueAt(d.index, altitude.fieldIdx) : 0\n    ];\n\nexport const scenegraphVisConfigs: {\n  opacity: 'opacity';\n  colorRange: 'colorRange';\n  //\n  sizeScale: 'sizeScale';\n  angleX: VisConfigNumber;\n  angleY: VisConfigNumber;\n  angleZ: VisConfigNumber;\n} = {\n  opacity: 'opacity',\n  colorRange: 'colorRange',\n  //\n  sizeScale: 'sizeScale',\n  angleX: {\n    ...LAYER_VIS_CONFIGS.angle,\n    property: 'angleX',\n    label: 'angle X'\n  },\n  angleY: {\n    ...LAYER_VIS_CONFIGS.angle,\n    property: 'angleY',\n    label: 'angle Y'\n  },\n  angleZ: {\n    ...LAYER_VIS_CONFIGS.angle,\n    property: 'angleZ',\n    defaultValue: 90,\n    label: 'angle Z'\n  }\n};\n\nconst DEFAULT_MODEL =\n  'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Duck/glTF-Binary/Duck.glb';\nconst DEFAULT_TRANSITION: [0, 0, 0] = [0, 0, 0];\nconst DEFAULT_SCALE: [1, 1, 1] = [1, 1, 1];\nconst DEFAULT_COLOR: [255, 255, 255, 255] = [255, 255, 255, 255];\n\nexport default class ScenegraphLayer extends Layer {\n  declare visConfigSettings: ScenegraphLayerVisConfigSettings;\n  declare config: ScenegraphLayerConfig;\n\n  _layerInfoModal: () => JSX.Element;\n\n  constructor(props) {\n    super(props);\n\n    this.registerVisConfig(scenegraphVisConfigs);\n    this.getPositionAccessor = (dataContainer: DataContainerInterface) =>\n      scenegraphPosAccessor(this.config.columns)(dataContainer);\n\n    // prepare layer info modal\n    this._layerInfoModal = ScenegraphInfoModalFactory();\n  }\n\n  get type(): '3D' {\n    return '3D';\n  }\n\n  get requiredLayerColumns() {\n    return scenegraphRequiredColumns;\n  }\n\n  get optionalColumns() {\n    return scenegraphOptionalColumns;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  get layerIcon() {\n    return ScenegraphLayerIcon;\n  }\n\n  get layerInfoModal() {\n    return {\n      id: 'scenegraphInfo',\n      template: this._layerInfoModal,\n      modalProps: {\n        title: 'How to use Scenegraph'\n      }\n    };\n  }\n\n  calculateDataAttribute({filteredIndex}: KeplerTable, getPosition) {\n    const data: ScenegraphLayerData[] = [];\n\n    for (let i = 0; i < filteredIndex.length; i++) {\n      const index = filteredIndex[i];\n      const pos: number[] = getPosition({index});\n\n      // if doesn't have point lat or lng, do not add the point\n      // deck.gl can't handle position = null\n      if (pos.every(Number.isFinite)) {\n        data.push({\n          position: pos,\n          index\n        });\n      }\n    }\n    return data;\n  }\n\n  formatLayerData(datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    const {gpuFilter, dataContainer} = datasets[this.config.dataId];\n    const {data} = this.updateData(datasets, oldLayerData);\n    const getPosition = this.getPositionAccessor(dataContainer);\n    return {\n      data,\n      getPosition,\n      getFilterValue: gpuFilter.filterValueAccessor(dataContainer)()\n    };\n  }\n\n  updateLayerMeta(dataset: KeplerTable, getPosition) {\n    const {dataContainer} = dataset;\n    const bounds = this.getPointsBounds(dataContainer, getPosition);\n    this.updateMeta({bounds});\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter} = opts;\n\n    const {\n      visConfig: {sizeScale = 1, angleX = 0, angleY = 0, angleZ = 90}\n    } = this.config;\n\n    return [\n      new DeckScenegraphLayer({\n        ...this.getDefaultDeckLayerProps(opts),\n        // gpu data filtering is not supported at the moment in scenegraphLayer https://github.com/visgl/deck.gl/issues/8099\n        extensions: [],\n        ...data,\n        fetch: fetchGltf,\n        scenegraph: this.config.visConfig.scenegraph || DEFAULT_MODEL,\n        sizeScale,\n        getTranslation: DEFAULT_TRANSITION,\n        getScale: DEFAULT_SCALE,\n        getOrientation: [angleX, angleY, angleZ],\n        getColor: DEFAULT_COLOR,\n        // parameters\n        parameters: {depthTest: true, blend: false},\n        // update triggers\n        updateTriggers: {\n          getOrientation: {angleX, angleY, angleZ},\n          getPosition: this.config.columns,\n          getFilterValue: gpuFilter.filterValueUpdateTriggers\n        }\n      })\n    ];\n  }\n}\n"
  },
  {
    "path": "src/layers/src/table.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport {Base, BaseProps} from './base';\n\nexport default class Table extends Component<Partial<BaseProps>> {\n  static defaultProps = {\n    height: '16px',\n    viewBox: '0 0 24 24',\n    predefinedClassName: 'data-ex-icons-table'\n  };\n\n  render() {\n    return (\n      <Base\n        {...this.props}\n        style={{fill: 'none', stroke: 'currentcolor'}}\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <path d=\"M12 3v18\" />\n        <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />\n        <path d=\"M3 9h18\" />\n        <path d=\"M3 15h18\" />\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/trip-layer/trip-info-modal.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport Markdown from 'markdown-to-jsx';\nimport {useIntl} from 'react-intl';\n\nimport {FormattedMessage} from '@kepler.gl/localization';\n\nimport Table from '../example-table';\n\nconst InfoModal = styled.div`\n  font-size: 13px;\n  color: ${props => props.theme.titleColorLT};\n\n  pre {\n    padding: 12px;\n    background-color: #f8f8f9;\n  }\n`;\n\nconst StyledTitle = styled.div`\n  font-size: 20px;\n  letter-spacing: 1.25px;\n  margin: 18px 0 14px 0;\n  color: ${props => props.theme.titleColorLT};\n`;\nconst StyledCode = styled.code`\n  color: ${props => props.theme.titleColorLT};\n`;\n\nconst codeExampleGeojson = `\n${'```json'}\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"properties\": { \n        \"vendor\":  \"A\",\n        \"vol\":20\n      },\n      \"geometry\": {\n        \"type\": \"LineString\",\n        \"coordinates\": [\n          [-74.20986, 40.81773, 0, 1564184363],\n          [-74.20987, 40.81765, 0, 1564184396],\n          [-74.20998, 40.81746, 0, 1564184409]\n        ]\n      }\n    }\n  ]\n}\n${'```'}\n`;\n\nconst exampleTableHeader = ['id', 'latitude', 'longitude', 'timestamp'];\nconst exampleTabbleRows = [\n  ['A', '40.81773', '-74.20986', '1564184363'],\n  ['A', '40.81765', '-74.20987', '1564184396'],\n  ['A', '40.81746', '-74.20998', '1564184409'],\n  ['B', '40.64375', '-74.33242', '1565578213'],\n  ['B', '40.64353', '-74.20987', '1565578217'],\n  ['B', '40.64222', '-74.33001', '1565578243']\n];\n\nconst ExampleTable = () => (\n  <Table className=\"trip-example-table\">\n    <thead>\n      <tr>\n        {exampleTableHeader.map(v => (\n          <th key={v}>{v}</th>\n        ))}\n      </tr>\n    </thead>\n    <tbody>\n      {exampleTabbleRows.map((row, i) => (\n        <tr key={i}>\n          {row.map((v, j) => (\n            <td key={j}>\n              <StyledCode>{v}</StyledCode>\n            </td>\n          ))}\n        </tr>\n      ))}\n    </tbody>\n  </Table>\n);\n\nconst TripInfoModalFactory = columnMode => {\n  const TripInfoModal = () => {\n    const intl = useIntl();\n    return (\n      <InfoModal className=\"trip-info-modal\">\n        <div className=\"trip-info-modal__description\">\n          <Markdown>\n            {intl.formatMessage({\n              id:\n                columnMode === 'geojson'\n                  ? 'modal.tripInfo.description1'\n                  : 'modal.tripInfo.descriptionTable1'\n            })}\n          </Markdown>\n        </div>\n        <div className=\"trip-info-modal__example\">\n          <StyledTitle>\n            <FormattedMessage\n              id={\n                columnMode === 'geojson' ? 'modal.tripInfo.example' : 'modal.tripInfo.exampleTable'\n              }\n            />\n          </StyledTitle>\n          {columnMode === 'geojson' ? <Markdown>{codeExampleGeojson}</Markdown> : <ExampleTable />}\n        </div>\n      </InfoModal>\n    );\n  };\n  return TripInfoModal;\n};\n\nexport default TripInfoModalFactory;\n"
  },
  {
    "path": "src/layers/src/trip-layer/trip-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport PropTypes from 'prop-types';\nimport {Base} from '../base';\n\nexport default class TripLayerIcon extends Component {\n  static propTypes = {\n    /** Set the height of the icon, ex. '16px' */\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string)\n  };\n\n  static defaultProps = {\n    size: 'tiny',\n    height: '16px',\n    predefinedClassName: 'trip-layer-icon'\n  };\n\n  render() {\n    return (\n      <Base {...this.props}>\n        <g clipPath=\"url(#clip0)\" className=\"cr1\">\n          <path d=\"M53.025 4.85005C50.25 2.07505 45.75 2.07505 42.975 4.85005C40.2 7.62505 40.2 12.2 42.975 14.975L48 20L53.025 14.9C55.8 12.2 55.8 7.62505 53.025 4.85005ZM48 11.375C47.175 11.375 46.5 10.7 46.5 9.87505C46.5 9.05005 47.175 8.37505 48 8.37505C48.825 8.37505 49.5 9.05005 49.5 9.87505C49.5 10.7 48.825 11.375 48 11.375Z\" />\n        </g>\n        <g clipPath=\"url(#clip1)\" className=\"cr2\">\n          <path d=\"M20.025 36.85C17.25 34.075 12.75 34.075 9.97502 36.85C7.20002 39.625 7.20002 44.2 9.97502 46.975L15 52L20.025 46.9C22.8 44.2 22.8 39.625 20.025 36.85ZM15 43.375C14.175 43.375 13.5 42.7 13.5 41.875C13.5 41.05 14.175 40.375 15 40.375C15.825 40.375 16.5 41.05 16.5 41.875C16.5 42.7 15.825 43.375 15 43.375Z\" />\n        </g>\n        <path\n          className=\"cr3\"\n          d=\"M45.9943 19.8697C46.0661 20.6951 45.4552 21.4223 44.6299 21.4941L34.782 22.3504L38.1515 40.1604L17.8748 54.7185C17.2019 55.2016 16.2647 55.0478 15.7815 54.3748C15.2984 53.7019 15.4522 52.7647 16.1252 52.2815L34.8483 38.8389L31.2177 19.6491L44.37 18.5053C45.1953 18.4336 45.9225 19.0444 45.9943 19.8697Z\"\n        />\n        <defs>\n          <clipPath id=\"clip0\">\n            <rect width=\"18\" height=\"18\" transform=\"translate(39 2)\" />\n          </clipPath>\n          <clipPath id=\"clip1\">\n            <rect width=\"18\" height=\"18\" transform=\"translate(6 34)\" />\n          </clipPath>\n        </defs>\n      </Base>\n    );\n  }\n}\n"
  },
  {
    "path": "src/layers/src/trip-layer/trip-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport memoize from 'lodash/memoize';\nimport uniq from 'lodash/uniq';\nimport Layer, {LayerBaseConfig, defaultGetFieldValue} from '../base-layer';\nimport {TripsLayer as DeckGLTripsLayer} from '@deck.gl/geo-layers';\n\nimport {GEOJSON_FIELDS, PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\nimport TripLayerIcon from './trip-layer-icon';\n\nimport {\n  getGeojsonDataMaps,\n  getGeojsonBounds,\n  getGeojsonFeatureTypes,\n  GeojsonDataMaps,\n  detectTableColumns,\n  groupColumnsAsGeoJson,\n  applyFiltersToTableColumns\n} from '../geojson-layer/geojson-utils';\n\nimport {isTripGeoJsonField, parseTripGeoJsonTimestamp} from './trip-utils';\nimport TripInfoModalFactory from './trip-info-modal';\nimport {bisectRight} from 'd3-array';\nimport {\n  ColorRange,\n  Merge,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigRange,\n  LayerColumn\n} from '@kepler.gl/types';\nimport {default as KeplerTable, Datasets} from '@kepler.gl/table';\nimport {DataContainerInterface} from '@kepler.gl/utils';\n\nexport type TripLayerVisConfigSettings = {\n  opacity: VisConfigNumber;\n  thickness: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  trailLength: VisConfigNumber;\n  sizeRange: VisConfigRange;\n};\n\nexport type TripLayerColumnsConfig = {\n  geojson: LayerColumn;\n};\n\nexport type TripLayerVisConfig = {\n  opacity: number;\n  thickness: number;\n  colorRange: ColorRange;\n  trailLength: number;\n  sizeRange: [number, number];\n  billboard: boolean;\n  fadeTrail: boolean;\n};\n\nexport type TripLayerConfig = Merge<\n  LayerBaseConfig,\n  {columns: TripLayerColumnsConfig; visConfig: TripLayerVisConfig}\n>;\n\nexport type TripLayerMeta = {\n  getFeature: any;\n};\n\nconst zoomFactorValue = 8;\n\nexport const defaultThickness = 0.5;\nexport const defaultLineWidth = 1;\n\nexport const tripVisConfigs: {\n  opacity: 'opacity';\n  thickness: VisConfigNumber;\n  colorRange: 'colorRange';\n  trailLength: 'trailLength';\n  fadeTrail: 'fadeTrail';\n  billboard: 'billboard';\n  sizeRange: 'strokeWidthRange';\n} = {\n  opacity: 'opacity',\n  thickness: {\n    type: 'number',\n    defaultValue: defaultThickness,\n    label: 'Stroke Width',\n    isRanged: false,\n    range: [0, 100],\n    step: 0.1,\n    group: 'stroke',\n    property: 'thickness'\n  },\n  colorRange: 'colorRange',\n  trailLength: 'trailLength',\n  fadeTrail: 'fadeTrail',\n  billboard: 'billboard',\n  sizeRange: 'strokeWidthRange'\n};\n\nexport const featureAccessor =\n  ({geojson}: TripLayerColumnsConfig) =>\n  (dc: DataContainerInterface) =>\n  d =>\n    dc.valueAt(d.index, geojson.fieldIdx);\nexport const featureResolver = ({geojson}: TripLayerColumnsConfig) => geojson.fieldIdx;\nconst getTableModeValueAccessor = f => {\n  // Called from gpu-filter-utils.getFilterValueAccessor()\n  return field => f.properties.values.map(v => field.valueAccessor(v));\n};\nconst getTableModeFieldValue = (field, data) => {\n  let rv;\n  if (typeof data === 'function') {\n    rv = data(field);\n  } else {\n    rv = defaultGetFieldValue(field, data);\n  }\n  return rv;\n};\n\nexport const COLUMN_MODE_GEOJSON = 'geojson';\nexport const COLUMN_MODE_TABLE = 'table';\nconst SUPPORTED_COLUMN_MODES = [\n  {\n    key: COLUMN_MODE_GEOJSON,\n    label: 'GeoJSON',\n    requiredColumns: ['geojson']\n  },\n  {\n    key: COLUMN_MODE_TABLE,\n    label: 'Table columns',\n    requiredColumns: ['id', 'lat', 'lng', 'timestamp'],\n    optionalColumns: ['altitude']\n  }\n];\nconst DEFAULT_COLUMN_MODE = COLUMN_MODE_GEOJSON;\n\nexport default class TripLayer extends Layer {\n  declare visConfigSettings: TripLayerVisConfigSettings;\n  declare config: TripLayerConfig;\n  declare meta: TripLayerMeta;\n  declare dataContainer: DataContainerInterface | null;\n\n  dataToFeature: GeojsonDataMaps;\n  dataToTimeStamp: any[];\n  getFeature: (columns: TripLayerColumnsConfig) => (dataContainer: DataContainerInterface) => any;\n  _layerInfoModal: Record<string, () => JSX.Element>;\n\n  constructor(props) {\n    super(props);\n\n    this.dataToFeature = [];\n    this.dataToTimeStamp = [];\n    this.dataContainer = null;\n    this.registerVisConfig(tripVisConfigs);\n    this.getFeature = memoize(featureAccessor, featureResolver);\n    this._layerInfoModal = {\n      [COLUMN_MODE_TABLE]: TripInfoModalFactory(COLUMN_MODE_TABLE),\n      [COLUMN_MODE_GEOJSON]: TripInfoModalFactory(COLUMN_MODE_GEOJSON)\n    };\n  }\n\n  get supportedColumnModes() {\n    return SUPPORTED_COLUMN_MODES;\n  }\n\n  static get type(): 'trip' {\n    return 'trip';\n  }\n  get type() {\n    return TripLayer.type;\n  }\n\n  get name(): 'Trip' {\n    return 'Trip';\n  }\n\n  get layerIcon() {\n    return TripLayerIcon;\n  }\n\n  get columnPairs() {\n    return this.defaultPointColumnPairs;\n  }\n\n  accessVSFieldValue() {\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      return defaultGetFieldValue;\n    }\n    return getTableModeFieldValue;\n  }\n\n  get visualChannels() {\n    const visualChannels = super.visualChannels;\n\n    return {\n      ...visualChannels,\n      color: {\n        ...visualChannels.color,\n        accessor: 'getColor',\n        nullValue: visualChannels.color.nullValue,\n        getAttributeValue: config => d => d.properties.lineColor || config.color,\n        // used this to get updateTriggers\n        defaultValue: config => config.color\n      },\n      size: {\n        ...visualChannels.size,\n        property: 'stroke',\n        accessor: 'getWidth',\n        condition: config => config.visConfig.stroked,\n        nullValue: 0,\n        getAttributeValue: () => d => d.properties.lineWidth || defaultLineWidth\n      }\n    };\n  }\n\n  get animationDomain() {\n    return this.config.animation.domain;\n  }\n\n  get layerInfoModal() {\n    return {\n      [COLUMN_MODE_GEOJSON]: {\n        id: 'iconInfo',\n        template: this._layerInfoModal[COLUMN_MODE_GEOJSON],\n        modalProps: {\n          title: 'modal.tripInfo.title'\n        }\n      },\n      [COLUMN_MODE_TABLE]: {\n        id: 'iconInfo',\n        template: this._layerInfoModal[COLUMN_MODE_TABLE],\n        modalProps: {\n          title: 'modal.tripInfo.titleTable'\n        }\n      }\n    };\n  }\n\n  getPositionAccessor(dataContainer: DataContainerInterface) {\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      return this.getFeature(this.config.columns)(dataContainer);\n    }\n    return null;\n  }\n\n  static findDefaultLayerProps(\n    {label, fields = [], dataContainer, id, fieldPairs = []}: KeplerTable,\n    foundLayers?: any[]\n  ) {\n    const geojsonColumns = fields.filter(f => f.type === 'geojson' || f.type === 'geoarrow').map(f => f.name);\n\n    const defaultColumns = {\n      geojson: uniq([...GEOJSON_FIELDS.geojson, ...geojsonColumns])\n    };\n\n    const geoJsonColumns = this.findDefaultColumnField(defaultColumns, fields);\n\n    const tripGeojsonColumns = (geoJsonColumns || []).filter(col =>\n      isTripGeoJsonField(dataContainer, fields[col.geojson.fieldIdx])\n    );\n\n    if (tripGeojsonColumns.length) {\n      return {\n        props: tripGeojsonColumns.map(columns => ({\n          label: (typeof label === 'string' && label.replace(/\\.[^/.]+$/, '')) || this.type,\n          columns,\n          isVisible: true,\n          columnMode: COLUMN_MODE_GEOJSON\n        })),\n\n        // if a geojson layer is created from this column, delete it\n        foundLayers: foundLayers?.filter(\n          prop =>\n            prop.type !== 'geojson' ||\n            prop.dataId !== id ||\n            !tripGeojsonColumns.find(c => prop.columns.geojson.name === c.geojson.name)\n        )\n      };\n    }\n\n    // Try to detect table columns (id/lat/lng/timestamp) for table column mode\n    // This allows creating trip layers from tabular data without GeoJSON\n    if (fieldPairs.length && fields.length) {\n      // Default layer columns for table mode\n      const defaultTableColumns = {\n        id: {value: null, fieldIdx: -1},\n        lat: {value: null, fieldIdx: -1},\n        lng: {value: null, fieldIdx: -1},\n        timestamp: {value: null, fieldIdx: -1},\n        altitude: {value: null, fieldIdx: -1, optional: true},\n        geojson: {value: null, fieldIdx: -1}\n      };\n\n      const tableColumns = detectTableColumns(\n        {fields, fieldPairs} as KeplerTable,\n        defaultTableColumns,\n        'timestamp'\n      );\n\n      if (tableColumns) {\n        // Found required columns for table mode\n        return {\n          props: [{\n            label: tableColumns.label || (typeof label === 'string' && label.replace(/\\.[^/.]+$/, '')) || this.type,\n            columns: tableColumns.columns,\n            isVisible: true,\n            columnMode: COLUMN_MODE_TABLE\n          }],\n          foundLayers\n        };\n      }\n    }\n\n    return {props: []};\n  }\n\n  getDefaultLayerConfig(props) {\n    return {\n      ...super.getDefaultLayerConfig(props),\n      columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE,\n      animation: {\n        enabled: true,\n        domain: null\n      }\n    };\n  }\n\n  getHoverData(object, dataContainer: DataContainerInterface, fields, animationConfig) {\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      // index for dataContainer is saved to feature.properties\n      return dataContainer.row(object.properties.index);\n    }\n    return this._findColumnModeDatumForFeature(object.properties.index, animationConfig.currentTime)\n      ?.datum;\n  }\n\n  calculateDataAttribute(dataset: KeplerTable) {\n    switch (this.config.columnMode) {\n      case COLUMN_MODE_GEOJSON: {\n        return (\n          dataset.filteredIndex\n            .map(i => this.dataToFeature[i])\n            // TODO d can be BinaryFeatureCollection, fix logic\n            .filter(d => d && (d as any).geometry?.type === 'LineString')\n        );\n      }\n\n      case COLUMN_MODE_TABLE:\n        return applyFiltersToTableColumns(dataset, this.dataToFeature);\n\n      default:\n        return [];\n    }\n  }\n\n  formatLayerData(datasets: Datasets, oldLayerData) {\n    if (this.config.dataId === null) {\n      return {};\n    }\n    // to-do: parse segment from dataContainer\n\n    const {dataContainer, gpuFilter} = datasets[this.config.dataId];\n    const {data} = this.updateData(datasets, oldLayerData);\n\n    let valueAccessor;\n    let dataAccessor;\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      valueAccessor = (dc: DataContainerInterface, f, fieldIndex: number) => {\n        return dc.valueAt(f.properties.index, fieldIndex);\n      };\n      // For GEOJSON mode, properties.index is the row index in the data container\n      dataAccessor = () => d => ({index: d.properties.index});\n    } else {\n      valueAccessor = getTableModeValueAccessor;\n      // For TABLE mode, properties.index is the feature index (not row index).\n      // Use the first row from properties.values to get field values for color/size.\n      dataAccessor = () => d => d.properties.values[0];\n    }\n    const indexAccessor = f => f.properties.index;\n    const accessors = this.getAttributeAccessors({dataAccessor, dataContainer});\n    const getFilterValue = gpuFilter.filterValueAccessor(dataContainer)(\n      indexAccessor,\n      valueAccessor\n    );\n\n    return {\n      data,\n      getFilterValue,\n      getPath: d => d.geometry.coordinates,\n      getTimestamps: d => this.dataToTimeStamp[d.properties.index],\n      ...accessors\n    };\n  }\n\n  updateAnimationDomain(domain) {\n    this.updateLayerConfig({\n      animation: {\n        ...this.config.animation,\n        domain\n      }\n    });\n  }\n\n  updateLayerMeta(dataset: KeplerTable) {\n    const {dataContainer} = dataset;\n    let getFeature;\n    if (this.config.columnMode === COLUMN_MODE_GEOJSON) {\n      getFeature = this.getPositionAccessor(dataContainer);\n      if (getFeature === this.meta.getFeature) {\n        // TODO: revisit this after gpu filtering\n        return;\n      }\n      this.dataToFeature = getGeojsonDataMaps(dataContainer, getFeature);\n    } else {\n      this.dataContainer = dataContainer;\n      this.dataToFeature = groupColumnsAsGeoJson(dataContainer, this.config.columns, 'timestamp');\n    }\n\n    const {dataToTimeStamp, animationDomain} = parseTripGeoJsonTimestamp(this.dataToFeature);\n\n    this.dataToTimeStamp = dataToTimeStamp;\n    this.updateAnimationDomain(animationDomain);\n\n    // get bounds from features\n    const bounds = getGeojsonBounds(this.dataToFeature);\n\n    // keep a record of what type of geometry the collection has\n    const featureTypes = getGeojsonFeatureTypes(this.dataToFeature);\n\n    this.updateMeta({bounds, featureTypes, getFeature});\n  }\n\n  setInitialLayerConfig(dataset) {\n    const {dataContainer} = dataset;\n    if (!dataContainer.numRows()) {\n      return this;\n    }\n\n    // defefaultLayerProps will automatically find geojson column\n    // if not found, we try to set it to id / lat /lng /ts\n    if (!this.config.columns.geojson.value) {\n      // find columns from lat, lng, id, and ts\n      const columnConfig = detectTableColumns(dataset, this.config.columns);\n      if (columnConfig) {\n        this.updateLayerConfig({\n          ...columnConfig,\n          columnMode: COLUMN_MODE_TABLE\n        });\n      } else {\n        return this;\n      }\n    }\n\n    this.updateLayerMeta(dataset);\n    return this;\n  }\n\n  renderLayer(opts) {\n    const {data, gpuFilter, mapState, animationConfig} = opts;\n    const {visConfig} = this.config;\n    const zoomFactor = this.getZoomFactor(mapState);\n    const isValidTime =\n      animationConfig &&\n      Array.isArray(animationConfig.domain) &&\n      animationConfig.domain.every(Number.isFinite) &&\n      Number.isFinite(animationConfig.currentTime);\n\n    if (!isValidTime) {\n      return [];\n    }\n\n    const domain0 = animationConfig.domain?.[0];\n\n    const gpuFilterUpdateTriggers = {getFilterValue: gpuFilter.filterValueUpdateTriggers};\n    const updateTriggers = {\n      ...this.getVisualChannelUpdateTriggers(),\n      getTimestamps: {\n        columns: this.config.columns,\n        domain0\n      },\n      ...gpuFilterUpdateTriggers\n    };\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n\n    const billboardWidthFactor = visConfig.billboard ? PROJECTED_PIXEL_SIZE_MULTIPLIER : 1;\n\n    const layerProps = {\n      ...defaultLayerProps,\n      ...data,\n      getTimestamps: d => (data.getTimestamps(d) || []).map(ts => ts - domain0),\n      widthScale: visConfig.thickness * zoomFactor * zoomFactorValue * billboardWidthFactor,\n      capRounded: true,\n      jointRounded: true,\n      wrapLongitude: false,\n      parameters: {\n        depthTest: mapState.dragRotate,\n        depthMask: false\n      },\n      trailLength: visConfig.trailLength * 1000,\n      fadeTrail: visConfig.fadeTrail,\n      billboard: visConfig.billboard,\n      // TODO: giuseppe this values becomes negative\n      currentTime: animationConfig.currentTime - domain0,\n      updateTriggers,\n      id: `${defaultLayerProps.id}${mapState.globe?.enabled ? '-globe' : ''}`\n    };\n    return [new DeckGLTripsLayer(layerProps)];\n  }\n\n  /**\n   * Finds coordinates and datum at the current animation time by the specified feature index.\n   * @param featureIndex\n   * @param time\n   * @returns {{datum: (null|string|*), idx: *, coords}|{datum: null, idx: number, coords: null}}\n   */\n  private _findColumnModeDatumForFeature(\n    featureIndex: number,\n    time: number\n  ): {\n    idx: number;\n    coords: number[] | null;\n    datum: any;\n  } {\n    if (this.config.columnMode === COLUMN_MODE_TABLE) {\n      const object = this.dataToFeature[featureIndex];\n      const idx = bisectRight(this.dataToTimeStamp[featureIndex], time);\n      // @ts-expect-error type geometry?\n      const {coordinates} = object?.geometry || {coordinates: []};\n      if (idx >= 0 && idx < coordinates.length) {\n        const coords = coordinates[idx];\n        return {\n          idx,\n          coords,\n          datum: coords?.datum\n        };\n      }\n    }\n    return {\n      idx: -1,\n      coords: null,\n      datum: null\n    };\n  }\n}\n"
  },
  {
    "path": "src/layers/src/trip-layer/trip-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {parseGeoJsonRawFeature, getGeojsonFeatureTypes} from '../geojson-layer/geojson-utils';\nimport {DataContainerInterface, getSampleContainerData, timeToUnixMilli} from '@kepler.gl/utils';\nimport {containValidTime, notNullorUndefined} from '@kepler.gl/common-utils';\nimport {Feature} from '@turf/helpers';\nimport {GeoJsonProperties, Geometry} from 'geojson';\n\n/**\n * Parse geojson from string\n * @param {array} samples feature object values\n * @returns whether the geometry coordinates has length of 4\n */\nexport function coordHasLength4(samples): boolean {\n  let hasLength4 = true;\n  for (let i = 0; i < samples.length; i += 1) {\n    hasLength4 =\n      Array.isArray(samples[i]?.geometry?.coordinates) &&\n      !samples[i].geometry.coordinates.find(c => c.length < 4);\n\n    if (!hasLength4) {\n      break;\n    }\n  }\n  return hasLength4;\n}\n\n/**\n * Check if geojson features are trip layer animatable by meeting 3 conditions\n * @param dataContainer geojson feature objects container\n * @param {object} field array of geojson feature objects\n * @returns whether it is trip layer animatable\n */\nexport function isTripGeoJsonField(dataContainer: DataContainerInterface, field): boolean {\n  if (dataContainer.numRows() < 1) {\n    return false;\n  }\n\n  const maxCount = 10000;\n  const sampleRawFeatures =\n    dataContainer.numRows() > maxCount\n      ? getSampleContainerData(dataContainer, maxCount)\n      : dataContainer;\n\n  const features: Feature<Geometry, GeoJsonProperties>[] = sampleRawFeatures\n    .mapIndex(field.valueAccessor)\n    .map(parseGeoJsonRawFeature)\n    .filter(notNullorUndefined);\n  const featureTypes = getGeojsonFeatureTypes(features);\n\n  // condition 1: contain line string\n  if (!featureTypes.line) {\n    return false;\n  }\n\n  // condition 2:sample line strings contain 4 coordinates\n  if (!coordHasLength4(features)) {\n    return false;\n  }\n\n  // condition 3:the 4th coordinate of the first feature line strings is valid time\n  // @ts-expect-error\n  const tsHolder = features[0].geometry.coordinates.map(coord => coord[3]);\n\n  return Boolean(containValidTime(tsHolder));\n}\n\n/**\n * Get unix timestamp from animatable geojson for deck.gl trip layer\n * @param dataToFeature array of geojson feature objects, can be null\n * @returns\n */\nexport function parseTripGeoJsonTimestamp(dataToFeature: any[]) {\n  // Analyze type based on coordinates of the 1st lineString\n  // select a sample trip to analyze time format\n  const empty = {dataToTimeStamp: [], animationDomain: null};\n  const sampleTrip = dataToFeature.find(f => f?.geometry?.coordinates?.[0]?.length > 3);\n\n  // if no valid geometry\n  if (!sampleTrip) {\n    return empty;\n  }\n\n  const analyzedType = containValidTime(sampleTrip.geometry.coordinates.map(coord => coord[3]));\n\n  if (!analyzedType) {\n    return empty;\n  }\n\n  const {format} = analyzedType;\n  const getTimeValue = coord =>\n    coord && notNullorUndefined(coord[3]) ? timeToUnixMilli(coord[3], format) : null;\n\n  const dataToTimeStamp: number[][] = dataToFeature.map(f =>\n    f && f.geometry && Array.isArray(f.geometry.coordinates)\n      ? f.geometry.coordinates.map(getTimeValue)\n      : null\n  );\n\n  const animationDomain = getAnimationDomainFromTimestamps(dataToTimeStamp);\n\n  return {dataToTimeStamp, animationDomain};\n}\n\nfunction findMinFromSorted(list: number[]) {\n  // check if list is null since the default value [] will only be applied when the param is undefined\n  return list?.find(d => notNullorUndefined(d) && Number.isFinite(d)) || null;\n}\n\nfunction findMaxFromSorted(list: number[] = []) {\n  let i = list.length - 1;\n  while (i > 0) {\n    if (notNullorUndefined(list[i]) && Number.isFinite(list[i])) {\n      return list[i];\n    }\n    i--;\n  }\n  return null;\n}\n\nexport function getAnimationDomainFromTimestamps(dataToTimeStamp: number[][] = []) {\n  return dataToTimeStamp.reduce(\n    (accu: [number, number], tss) => {\n      const tsMin = findMinFromSorted(tss);\n      const tsMax = findMaxFromSorted(tss);\n      if (\n        notNullorUndefined(tsMin) &&\n        notNullorUndefined(tsMax) &&\n        Number.isFinite(tsMin) &&\n        Number.isFinite(tsMax)\n      ) {\n        accu[0] = Math.min(accu[0], tsMin);\n        accu[1] = Math.max(accu[1], tsMax);\n      }\n      return accu;\n    },\n    [Infinity, -Infinity]\n  );\n}\n"
  },
  {
    "path": "src/layers/src/typedefs/deckgl.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Eslint does not seem to be able to understand the namespace re-export here\n/* eslint-disable */\n\nimport * as DeckTypings from '@danmarshall/deckgl-typings';\n\ndeclare module 'deck.gl' {\n  export namespace DeckTypings {}\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/abstract-tile-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport throttle from 'lodash/throttle';\n\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {SCALE_TYPES, ALL_FIELD_TYPES, LAYER_VIS_CONFIGS} from '@kepler.gl/constants';\nimport {\n  isTileDataset,\n  KeplerTable as KeplerDataset,\n  Datasets as KeplerDatasets,\n  GpuFilter\n} from '@kepler.gl/table';\nimport {\n  AnimationConfig,\n  Field,\n  BindedLayerCallbacks,\n  VisConfigBoolean,\n  VisConfigRange,\n  VisConfigColorRange,\n  VisConfigNumber,\n  VisConfigColorSelect,\n  LayerColorConfig,\n  LayerHeightConfig,\n  Filter,\n  Field as KeplerField,\n  MapState,\n  Merge,\n  ZoomStopsConfig\n} from '@kepler.gl/types';\nimport {findDefaultColorField, DataContainerInterface} from '@kepler.gl/utils';\n\nimport {\n  default as KeplerLayer,\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  VisualChannel,\n  VisualChannelDomain,\n  VisualChannelField\n} from '../base-layer';\nimport TileDataset from './common-tile/tile-dataset';\nimport {isIndexedField, isDomainQuantiles} from './common-tile/tile-utils';\nimport {FindDefaultLayerPropsReturnValue} from '../layer-utils';\n\nconst DEFAULT_ELEVATION = 500;\nexport const DEFAULT_RADIUS = 1;\n\nexport type AbstractTileLayerVisConfigSettings = {\n  strokeColor: VisConfigColorSelect;\n  strokeOpacity: VisConfigNumber;\n  radius: VisConfigNumber;\n  radiusUnits: VisConfigBoolean;\n  enable3d: VisConfigBoolean;\n  stroked: VisConfigBoolean;\n  transition: VisConfigBoolean;\n  heightRange: VisConfigRange;\n  elevationScale: VisConfigNumber;\n  opacity: VisConfigNumber;\n  colorRange: VisConfigColorRange;\n  // TODO: figure out type for radiusByZoom vis config\n  radiusByZoom: any;\n  dynamicColor: VisConfigBoolean;\n};\n\nexport type LayerData = {\n  minZoom?: number;\n  maxZoom?: number;\n  bounds?: number[];\n  getPointRadius?: () => number;\n};\n\nexport type LayerOpts = {\n  idx: number;\n  mapState: MapState;\n  data: LayerData;\n  gpuFilter: GpuFilter;\n  animationConfig: AnimationConfig;\n  tilesetDataUrl?: string | null;\n  layerCallbacks: BindedLayerCallbacks;\n};\n\nexport const commonTileVisConfigs = {\n  strokeColor: 'strokeColor' as const,\n  strokeOpacity: {\n    ...LAYER_VIS_CONFIGS.opacity,\n    property: 'strokeOpacity'\n  },\n  radius: {\n    ...LAYER_VIS_CONFIGS.radius,\n    range: [0.1, 100],\n    step: 0.1,\n    defaultValue: DEFAULT_RADIUS,\n    allowCustomValue: false\n  } as VisConfigNumber,\n  radiusUnits: {\n    type: 'boolean',\n    defaultValue: true,\n    label: 'Radius in pixels',\n    group: '',\n    property: 'radiusUnits',\n    description: 'Radius in pixels or in meters'\n  } as VisConfigBoolean,\n  enable3d: 'enable3d' as const,\n  stroked: {\n    ...LAYER_VIS_CONFIGS.stroked,\n    defaultValue: false\n  },\n  transition: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'Transition',\n    group: '',\n    property: 'transition',\n    description:\n      'Smoother transition during animation (enable transition will decrease performance)'\n  } as VisConfigBoolean,\n  heightRange: 'elevationRange' as const,\n  elevationScale: {...LAYER_VIS_CONFIGS.elevationScale, allowCustomValue: false},\n  opacity: 'opacity' as const,\n  colorRange: 'colorRange' as const,\n  // TODO: figure out type for radiusByZoom vis config\n  radiusByZoom: {\n    ...LAYER_VIS_CONFIGS.radius,\n    defaultValue: {\n      enabled: false,\n      stops: null\n    } as ZoomStopsConfig\n  } as any,\n  dynamicColor: {\n    type: 'boolean',\n    defaultValue: false,\n    label: 'Dynamic Color',\n    group: '',\n    property: 'dynamicColor',\n    description: 'Use a dynamic color scale based on data visible in the viewport'\n  } as VisConfigBoolean\n};\n\nexport type AbstractTileLayerConfig = Merge<\n  LayerBaseConfig,\n  {colorField?: VisualChannelField; colorScale?: string; colorDomain?: VisualChannelDomain}\n>;\n\n/**\n * Abstract tile layer, including common functionality for viewport-based datasets,\n * dynamic scales, and tile-based animation\n */\nexport default abstract class AbstractTileLayer<\n  // Type of the tile itself\n  T,\n  // Type of the iterable data object\n  I extends Iterable<any> = T extends Iterable<any> ? T : never\n> extends KeplerLayer {\n  declare config: AbstractTileLayerConfig;\n  declare visConfigSettings: AbstractTileLayerVisConfigSettings;\n\n  constructor(props: ConstructorParameters<typeof KeplerLayer>[0]) {\n    super(props);\n    this.registerVisConfig(commonTileVisConfigs);\n    this.tileDataset = this.initTileDataset();\n  }\n\n  protected tileDataset: TileDataset<T, I>;\n  protected setLayerDomain: BindedLayerCallbacks['onSetLayerDomain'] = undefined;\n\n  protected abstract initTileDataset(): TileDataset<T, I>;\n\n  static findDefaultLayerProps(dataset: KeplerDataset): FindDefaultLayerPropsReturnValue {\n    if (!isTileDataset(dataset)) {\n      return {props: []};\n    }\n\n    const newLayerProp = {\n      dataId: dataset.id,\n      label: dataset.label,\n      isVisible: true\n    };\n\n    return {props: [newLayerProp]};\n  }\n\n  get requireData(): boolean {\n    return true;\n  }\n\n  get requiredLayerColumns(): string[] {\n    return [];\n  }\n\n  get visualChannels(): Record<string, VisualChannel> {\n    return {\n      color: {\n        ...super.visualChannels.color,\n        accessor: 'getFillColor',\n        defaultValue: config => config.color\n      },\n      height: {\n        property: 'height',\n        field: 'heightField',\n        scale: 'heightScale',\n        domain: 'heightDomain',\n        range: 'heightRange',\n        key: 'height',\n        channelScaleType: 'size',\n        accessor: 'getElevation',\n        condition: config => config.visConfig.enable3d,\n        nullValue: 0,\n        defaultValue: DEFAULT_ELEVATION\n      }\n    };\n  }\n\n  /**\n   * Callback to invoke when the viewport changes\n   */\n  onViewportLoad = (tiles: T[]): void => {\n    this.tileDataset.updateTiles(tiles);\n    // Update dynamic color domain if required\n    if (this.config.visConfig.dynamicColor) {\n      this.setDynamicColorDomain();\n    }\n  };\n\n  abstract accessRowValue(\n    field?: KeplerField,\n    indexKey?: number | null\n  ): (field: KeplerField, datum: T extends Iterable<infer V> ? V : never) => string | number | null;\n\n  accessVSFieldValue(inputField?: Field, indexKey?: number | null): any {\n    return this.accessRowValue(inputField, indexKey);\n  }\n\n  getScaleOptions(channelKey: string): string[] {\n    if (channelKey === 'color') {\n      const channel = this.visualChannels.color;\n      const field = this.config[channel.field];\n\n      if (\n        isDomainQuantiles(field?.filterProps?.domainQuantiles) ||\n        this.config.visConfig.dynamicColor ||\n        // If we've set the scale to quantile, we need to include it - there's a loading\n        // period in which the visConfig isn't set yet, but if we don't return the right\n        // scale type we lose it\n        this.config.colorScale === SCALE_TYPES.quantile\n      ) {\n        return [SCALE_TYPES.quantize, SCALE_TYPES.quantile, SCALE_TYPES.custom];\n      }\n      return [SCALE_TYPES.quantize, SCALE_TYPES.custom];\n    }\n\n    return [SCALE_TYPES.linear];\n  }\n\n  setDynamicColorDomain = throttle((): void => {\n    const {config, tileDataset, setLayerDomain} = this;\n    const field = config.colorField;\n    const {colorDomain, colorScale} = config;\n    if (!tileDataset || !setLayerDomain || !field) return;\n\n    if (colorScale === SCALE_TYPES.quantize) {\n      const [min, max] = tileDataset.getExtent(field);\n      if (!Array.isArray(colorDomain) || min !== colorDomain[0] || max !== colorDomain[1]) {\n        setLayerDomain({domain: [min, max]});\n      }\n    } else if (colorScale === SCALE_TYPES.quantile) {\n      const domain = tileDataset.getQuantileSample(field);\n      setLayerDomain({domain});\n    } else if (colorScale === SCALE_TYPES.ordinal) {\n      const domain = tileDataset.getUniqueValues(field);\n      setLayerDomain({domain});\n    }\n  }, 500);\n\n  resetColorDomain(): void {\n    const {datasetId, datasets} = this.meta;\n    this.updateLayerVisualChannel(datasets?.[datasetId || ''], 'color');\n    this.setLayerDomain?.({domain: this.config.colorDomain});\n  }\n\n  updateLayerConfig(\n    newConfig: Partial<LayerBaseConfig> & Partial<LayerColorConfig>\n  ): AbstractTileLayer<T, I> {\n    // When the dynamic color setting changes, we need to recalculate the layer domain\n    const old = this.config.visConfig.dynamicColor ?? false;\n    const next = newConfig.visConfig?.dynamicColor ?? old;\n    const scaleTypeChanged =\n      newConfig.colorScale && this.config.colorScale !== newConfig.colorScale;\n\n    super.updateLayerConfig(newConfig);\n    const {colorField} = this.config;\n\n    if (colorField) {\n      // When we switch from dynamic to non-dynamic or vice versa, we need to update\n      // the color domain. This is downstream from a dispatch call, so we use\n      // setTimeout to avoid \"reducers may not dispatch actions\" errors\n      if (next && (!old || scaleTypeChanged)) {\n        setTimeout(() => this.setDynamicColorDomain(), 0);\n      } else if (old && !next) {\n        setTimeout(() => this.resetColorDomain(), 0);\n      }\n    }\n\n    return this;\n  }\n\n  get animationDomain(): [number, number] | null | undefined {\n    return this.config.animation.domain;\n  }\n\n  setInitialLayerConfig(dataset: KeplerDataset): AbstractTileLayer<T, I> {\n    const defaultColorField = findDefaultColorField(dataset);\n\n    if (defaultColorField) {\n      this.updateLayerConfig({\n        colorField: defaultColorField\n      });\n      this.updateLayerVisualChannel(dataset, 'color');\n    }\n\n    return this;\n  }\n\n  getDefaultLayerConfig(\n    props: LayerBaseConfigPartial\n  ): LayerBaseConfig & Partial<LayerColorConfig & LayerHeightConfig> {\n    return {\n      ...super.getDefaultLayerConfig(props),\n      colorScale: SCALE_TYPES.quantize,\n\n      // add height visual channel\n      heightField: null,\n      heightDomain: [0, 1],\n      heightScale: 'linear'\n    };\n  }\n\n  // We can render without columns, so we redefine this method\n  shouldRenderLayer(): boolean {\n    return Boolean(this.type && this.config.isVisible);\n  }\n\n  updateAnimationDomainByField(channel: string): void {\n    const field = this.config[this.visualChannels[channel].field];\n\n    if (isIndexedField(field) && field.indexBy.type === ALL_FIELD_TYPES.timestamp) {\n      const {timeDomain} = field.indexBy;\n\n      this.updateLayerConfig({\n        animation: {\n          ...timeDomain,\n          enabled: true,\n          startTime: timeDomain.domain[0]\n        }\n      });\n    }\n  }\n\n  updateAnimationDomain(domain: [number, number] | null | undefined): void {\n    this.updateLayerConfig({\n      animation: {\n        ...this.config.animation,\n        domain\n      }\n    });\n  }\n\n  updateLayerDomain(datasets: KeplerDatasets, newFilter?: Filter): AbstractTileLayer<T, I> {\n    super.updateLayerDomain(datasets, newFilter);\n    if (newFilter) {\n      // invalidate cachedVisibleDataset when e.g. changing filters\n      this.tileDataset.invalidateCache();\n    }\n    Object.keys(this.visualChannels).forEach(channel => {\n      this.updateAnimationDomainByField(channel);\n    });\n    return this;\n  }\n\n  updateLayerVisualChannel(dataset: KeplerDataset, channel: string): void {\n    super.updateLayerVisualChannel(dataset, channel);\n\n    // create animation if field is indexed by time\n    this.updateAnimationDomainByField(channel);\n  }\n\n  formatLayerData(\n    datasets: KeplerDatasets,\n    oldLayerData: unknown,\n    animationConfig: AnimationConfig\n  ): LayerData {\n    const {dataId} = this.config;\n    if (!notNullorUndefined(dataId)) {\n      return {};\n    }\n    const dataset = datasets[dataId];\n\n    const dataUpdateTriggers = this.getDataUpdateTriggers(dataset);\n    const triggerChanged = this.getChangedTriggers(dataUpdateTriggers);\n\n    if (triggerChanged && triggerChanged.getMeta) {\n      this.updateLayerMeta(dataset, datasets);\n    }\n\n    const indexKey = this.config.animation.enabled ? animationConfig.currentTime : null;\n    const accessors = this.getAttributeAccessors({\n      dataAccessor: () => d => d,\n      dataContainer: dataset.dataContainer,\n      indexKey\n    });\n\n    const metadata = dataset?.metadata as LayerData | undefined;\n    const metadataToData = metadata\n      ? {\n          minZoom: metadata.minZoom,\n          maxZoom: metadata.maxZoom,\n          bounds: metadata.bounds\n        }\n      : {};\n\n    return {\n      ...metadataToData,\n      ...accessors\n    };\n  }\n\n  getGpuFilterValueAccessor({gpuFilter, animationConfig}: LayerOpts): any {\n    const indexKey = this.config.animation.enabled ? animationConfig.currentTime : null;\n    const valueAccessor = (dataContainer: DataContainerInterface, d) => field =>\n      this.accessVSFieldValue(field, indexKey)(field, d);\n    return gpuFilter.filterValueAccessor(null as any)(undefined, valueAccessor);\n  }\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/common-tile/iterable-tile-set.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Custom iterator for data in a set of tiles\n */\nclass TileSetIterator<T extends Iterable<any>> {\n  private tileIterator: Iterator<T extends Iterable<infer V> ? V : never> | null = null;\n  private tileIndex = -1;\n\n  constructor(private readonly tiles: readonly T[]) {}\n\n  nextTile() {\n    if (this.tileIndex < this.tiles.length - 1) {\n      this.tileIndex++;\n      this.tileIterator = this.tiles[this.tileIndex][Symbol.iterator]();\n    } else {\n      this.tileIterator = null;\n    }\n  }\n\n  next(): IteratorResult<T extends Iterable<infer V> ? V : never> {\n    // Startup: Get the iterator for the next tile\n    if (this.tileIterator === null) this.nextTile();\n    // Finish: We have no more tiles, we're done\n    if (this.tileIterator === null) return {done: true, value: null};\n\n    const next = this.tileIterator.next();\n    // If we have a value for the current tile, return it\n    if (!next.done) {\n      return next;\n    }\n\n    // Otherwise, go on to the next tile\n    this.tileIterator = null;\n    return this.next();\n  }\n\n  [Symbol.iterator]() {\n    return this;\n  }\n}\n\nexport type RowCountAccessor<T> = (tile: T) => number;\n\n/**\n * An iterable object that can iterate over all rows\n * in a set of tiles, treat the set as a single iterable\n */\nexport default class IterableTileSet<T extends Iterable<any>> {\n  rowCount = 0;\n  private rowCounts: number[] = [];\n\n  constructor(private readonly tiles: readonly T[], getRowCount: RowCountAccessor<T>) {\n    for (const tile of tiles) {\n      const count = getRowCount(tile);\n      this.rowCount += count;\n      this.rowCounts.push(count);\n    }\n  }\n\n  /**\n   * Iterate over all values in the set\n   */\n  [Symbol.iterator](): Iterator<T extends Iterable<infer V> ? V : never> {\n    return new TileSetIterator<T>(this.tiles);\n  }\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/common-tile/tile-dataset.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Field as KeplerField} from '@kepler.gl/types';\nimport {quickInsertionSort} from '@kepler.gl/utils';\n\nimport IterableTileSet, {RowCountAccessor} from './iterable-tile-set';\nimport {pruneQuantiles} from './tile-utils';\n\nexport type Datum = number | string | null;\n\ntype RowValueAccessor<T> = (\n  field: KeplerField,\n  row: T extends Iterable<infer V> ? V : never\n) => Datum;\ntype RowValueAccessorFactory<T> = (\n  field?: KeplerField,\n  indexKey?: number | null\n) => RowValueAccessor<T>;\n\ntype TileAccessors<T, I extends Iterable<any> = T extends Iterable<any> ? T : never> = {\n  getTileId: (tile: T) => string;\n  getIterable: (tile: T) => I;\n  getRowCount: RowCountAccessor<I>;\n  getRowValue: RowValueAccessorFactory<I>;\n};\n\n/**\n * Per-tile stats, for caching\n */\ntype TileFieldStats = {\n  extent?: [number, number];\n  sample?: Datum[];\n  uniqueValues?: Set<Datum>;\n};\n\n/**\n * Stateful class offering dataset-style functions for the set of tiles.\n */\nexport default class TileDataset<T, I extends Iterable<any> = T extends Iterable<any> ? T : never> {\n  private accessors: TileAccessors<T, I>;\n  private tiles: readonly T[];\n  private tileSet: IterableTileSet<I>;\n  private tileIds: Set<string> = new Set();\n\n  /** Cache for per-tile field stats: tileId -> fieldId -> stats */\n  private tileStats: Map<string, Map<string, TileFieldStats>> = new Map();\n\n  constructor(accessors: TileAccessors<T, I>, tiles?: readonly T[]) {\n    this.accessors = accessors;\n    this.tiles = [];\n    this.tileSet = new IterableTileSet([], accessors.getRowCount);\n    if (tiles) {\n      this.updateTiles(tiles);\n    }\n  }\n\n  /**\n   * Invalidate the cached data\n   */\n  invalidateCache(): void {\n    // TODO: implement later\n  }\n\n  /**\n   * Update the set of tiles in the viewport\n   */\n  updateTiles(tiles: readonly T[]): void {\n    const {getTileId, getIterable, getRowCount} = this.accessors;\n    const tileIds = new Set<string>(tiles.map(getTileId));\n    if (!areEqualSets(tileIds, this.tileIds)) {\n      this.invalidateCache();\n    }\n    this.tiles = tiles;\n    this.tileIds = tileIds;\n    this.tileSet = new IterableTileSet(tiles.map(getIterable), getRowCount);\n  }\n\n  /**\n   * Return current tiles\n   */\n  getTiles(): readonly T[] {\n    return this.tiles;\n  }\n\n  /**\n   * Get the min/max domain of a field\n   */\n  getExtent(field: KeplerField): [number, number] {\n    const {getRowValue, getIterable} = this.accessors;\n    const accessor = getRowValue(field);\n    let min = Infinity;\n    let max = -Infinity;\n\n    for (const tile of this.tiles) {\n      // Check the cache\n      let extent = this.getTileStat(tile, field, 'extent');\n      if (!extent) {\n        // Cache miss, calculate and cache\n        extent = getTileExtent(getIterable(tile), field, accessor);\n        this.setTileStat(tile, field, 'extent', extent);\n      }\n      if (extent) {\n        if (extent[0] < min) min = extent[0];\n        if (extent[1] > max) max = extent[1];\n      }\n    }\n    return Number.isFinite(min) && Number.isFinite(max) ? [min, max] : [0, 0];\n  }\n\n  /**\n   * Get a sample of field values to use in estimating quantiles\n   */\n  getQuantileSample(field: KeplerField, minRowCount = 1000): number[] {\n    // TODO: There should be reasonable per-tile caching possible here\n    const set = this.tileSet;\n    const accessor = this.accessors.getRowValue(field);\n    const sample: number[] = [];\n    const sampleStep = Math.max(Math.floor(set.rowCount / minRowCount), 1);\n    let i = 0;\n    for (const row of set) {\n      if (++i === sampleStep) {\n        const val = accessor(field, row) as number | null;\n        if (val !== null) sample.push(val);\n        i = 0;\n      }\n    }\n    quickInsertionSort(sample);\n    pruneQuantiles(sample);\n    return sample;\n  }\n\n  /**\n   * Get a set of unique values for a field\n   */\n  getUniqueValues(field: KeplerField): Datum[] {\n    const {getRowValue, getIterable} = this.accessors;\n    const accessor = getRowValue(field);\n    const uniques = new Set<Datum>();\n\n    for (const tile of this.tiles) {\n      // Check the cache\n      let tileUniques = this.getTileStat(tile, field, 'uniqueValues');\n      if (!tileUniques) {\n        // Cache miss, calculate and cache\n        tileUniques = getTileUniqueValues(getIterable(tile), field, accessor);\n        this.setTileStat(tile, field, 'uniqueValues', tileUniques);\n      }\n      for (const val of tileUniques ?? []) {\n        uniques.add(val);\n      }\n    }\n    return [...uniques];\n  }\n\n  private getTileStat<K extends keyof TileFieldStats>(\n    tile: T,\n    field: KeplerField,\n    stat: K\n  ): TileFieldStats[K] {\n    return this.tileStats.get(this.accessors.getTileId(tile))?.get(field.name)?.[stat];\n  }\n\n  private setTileStat<K extends keyof TileFieldStats>(\n    tile: T,\n    field: KeplerField,\n    stat: K,\n    value: TileFieldStats[K]\n  ): void {\n    const tileId = this.accessors.getTileId(tile);\n    const tileStats = this.tileStats.get(tileId) ?? new Map();\n    const tileFieldStats = tileStats.get(field.name) ?? {};\n    tileFieldStats[stat] = value;\n    tileStats.set(field.name, tileFieldStats);\n    this.tileStats.set(tileId, tileStats);\n  }\n}\n\n/**\n * Get the min/max domain of a field in a given tile\n */\nfunction getTileExtent<I extends Iterable<any>>(\n  iterable: I,\n  field: KeplerField,\n  accessor: RowValueAccessor<I>\n): [number, number] | undefined {\n  let min = Infinity;\n  let max = -Infinity;\n  for (const row of iterable) {\n    const val = accessor(field, row) as number | null;\n    if (val === null) continue;\n    if (val < min) min = val;\n    if (val > max) max = val;\n  }\n  return Number.isFinite(min) && Number.isFinite(max) ? [min, max] : undefined;\n}\n\n/**\n * Get unique values for a field in a given tile\n */\nfunction getTileUniqueValues<I extends Iterable<any>>(\n  iterable: I,\n  field: KeplerField,\n  accessor: RowValueAccessor<I>,\n  maxUniques = 20\n): Set<Datum> {\n  const uniques = new Set<Datum>();\n  for (const row of iterable) {\n    if (uniques.size >= maxUniques) return uniques;\n    uniques.add(accessor(field, row));\n  }\n  return uniques;\n}\n\nfunction areEqualSets(a: Set<string>, b: Set<string>): boolean {\n  if (a.size !== b.size) return false;\n  for (const val of a) {\n    if (!b.has(val)) {\n      return false;\n    }\n  }\n  return true;\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/common-tile/tile-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {bisectLeft} from 'd3-array';\n\nimport {DomainStops, Field as KeplerField, ZoomStopsConfig} from '@kepler.gl/types';\nimport {DomainQuantiles} from '@kepler.gl/utils';\n\n// helper functions\nexport function isDomainStops(domain: unknown): domain is DomainStops {\n  return (\n    typeof domain === 'object' &&\n    domain !== null &&\n    Array.isArray((domain as any).stops) &&\n    Array.isArray((domain as any).z)\n  );\n}\n\nexport function isDomainQuantiles(domain: unknown): domain is DomainQuantiles {\n  return (\n    typeof domain === 'object' &&\n    domain !== null &&\n    Array.isArray((domain as any).quantiles) &&\n    Array.isArray((domain as any).z)\n  );\n}\n\nexport function isIndexedField(field?: KeplerField | null): boolean {\n  return Boolean(field && field.indexBy);\n}\n\nexport function getPropertyByZoom(\n  config: ZoomStopsConfig | undefined,\n  defaultValue: number\n): (zoom: number) => number {\n  if (!config || !config.enabled || !Array.isArray(config.stops)) {\n    return () => defaultValue;\n  }\n  const {stops} = config;\n  const zSteps = stops.map(st => st[0]);\n\n  return zoom => {\n    const i = bisectLeft(zSteps, zoom);\n    if (i === 0) {\n      return stops[i][1];\n    }\n\n    return stops[i - 1][1];\n  };\n}\n\n/**\n * Remove null/0 values from the bottom of the quantiles. If the column has many nulls\n * or 0s at the bottom of the quantiles, it will wash out color scales and produce\n * meaningless \"no value\" legend entries. We want to keep the first 0 and no others.\n * Operates in place.\n */\nexport function pruneQuantiles(quantiles: number[]): void {\n  const firstNonZeroIdx = quantiles.findIndex(d => d !== null && d !== 0);\n  if (firstNonZeroIdx > 0) {\n    quantiles.splice(0, firstNonZeroIdx - 1);\n  }\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/loading-counter.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Global counter that represents the number of tiles currently being loaded across all the vector tile layers\nlet vectorTilesBeingLoaded = 0;\n\n// This is a temp solution to track loading\nexport const getNumVectorTilesBeingLoaded = () => {\n  return vectorTilesBeingLoaded;\n};\n\nexport const incrementVectorTileLoading = () => {\n  vectorTilesBeingLoaded++;\n};\n\nexport const decrementVectorTileLoading = () => {\n  vectorTilesBeingLoaded--;\n};\n"
  },
  {
    "path": "src/layers/src/vector-tile/mvt-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Layer, LayersList} from '@deck.gl/core/typed';\nimport {ClipExtension} from '@deck.gl/extensions/typed';\nimport {\n  MVTLayer as _MVTLayer,\n  TileLayer,\n  _getURLFromTemplate,\n  _TileLoadProps,\n  _Tile2DHeader\n} from '@deck.gl/geo-layers/typed';\nimport {incrementVectorTileLoading, decrementVectorTileLoading} from './loading-counter';\n\n/*\n  Custom MVT layer that works with MVTSource and PMTileSource.\n  Changes:\n    - getTileData: handles props.getTileData.\n    - renderSubLayers: removed coordinates logic present in original MVTLayer:renderSubLayers.\n    - renderSubLayers: set clipBounds.\n    - loaders.gl & older deck.gl: geojson-table: data = data.features\n*/\n\n// @ts-expect-error need to patch private methods because of newer loaders.gl\nexport class MVTLayer<ExtraProps> extends _MVTLayer<ExtraProps> {\n  async getTileData(tile: _TileLoadProps): Promise<any> {\n    const {getTileData} = this.props;\n    const {data} = this.state;\n\n    tile.url =\n      typeof data === 'string' || Array.isArray(data) ? _getURLFromTemplate(data, tile) : null;\n    if (getTileData) {\n      incrementVectorTileLoading();\n      try {\n        return await getTileData(tile);\n      } finally {\n        decrementVectorTileLoading();\n      }\n    }\n    return null;\n  }\n\n  renderSubLayers(\n    props: TileLayer['props'] & {\n      id: string;\n      data: any;\n      _offset: number;\n      tile: any;\n      clipBounds?: number[];\n    }\n  ): Layer | null | LayersList {\n    const {boundingBox} = props.tile;\n\n    props.autoHighlight = true;\n\n    if (boundingBox) {\n      props.clipBounds = [...boundingBox[0], ...boundingBox[1]];\n      props.extensions = [...(props.extensions || []), new ClipExtension()];\n    }\n\n    return this.props.renderSubLayers(props);\n  }\n\n  getHighlightedObjectIndex(tile: _Tile2DHeader): number {\n    const {hoveredFeatureId, hoveredFeatureLayerName} = this.state;\n    const {uniqueIdProperty, highlightedFeatureId} = this.props;\n    let data = tile.content;\n    data = data?.shape === 'geojson-table' ? data.features : data;\n\n    const isHighlighted = isFeatureIdDefined(highlightedFeatureId);\n    const isFeatureIdPresent = isFeatureIdDefined(hoveredFeatureId) || isHighlighted;\n\n    if (!isFeatureIdPresent) {\n      return -1;\n    }\n\n    const featureIdToHighlight = isHighlighted ? highlightedFeatureId : hoveredFeatureId;\n\n    // Iterable data\n    if (Array.isArray(data)) {\n      return data.findIndex(feature => {\n        const isMatchingId = getFeatureUniqueId(feature, uniqueIdProperty) === featureIdToHighlight;\n        const isMatchingLayer =\n          isHighlighted || getFeatureLayerName(feature) === hoveredFeatureLayerName;\n        return isMatchingId && isMatchingLayer;\n      });\n    }\n\n    return -1;\n  }\n}\n\nfunction getFeatureUniqueId(feature, uniqueIdProperty: string | undefined) {\n  if (feature.properties && uniqueIdProperty) {\n    return feature.properties[uniqueIdProperty];\n  }\n\n  if ('id' in feature) {\n    return feature.id;\n  }\n\n  return undefined;\n}\n\nfunction getFeatureLayerName(feature): string | null {\n  return feature.properties?.layerName || null;\n}\n\nfunction isFeatureIdDefined(value: unknown): boolean {\n  return value !== undefined && value !== null && value !== '';\n}\n"
  },
  {
    "path": "src/layers/src/vector-tile/vector-tile-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport PropTypes from 'prop-types';\nimport React, {Component} from 'react';\n\nimport {Base} from '../base';\n\nclass VectorTileIcon extends Component {\n  static propTypes = {\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string.isRequired)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'vector-tile-layer-icon',\n    totalColor: 2\n  };\n\n  render(): React.ReactNode {\n    return (\n      <Base {...this.props}>\n        <g id=\"vector-tile-layer\">\n          <polygon\n            id=\"Path\"\n            points=\"12.1949893 42.9771532 3 37.4596134 12.2699446 31.9771532 21 37.45407\"\n            style={{opacity: 0.6}}\n          />\n          <polygon\n            id=\"Path-Copy-3\"\n            points=\"22.2609912 37 13.0660019 31.4824602 22.3359466 26 31.0660019 31.4769168\"\n            style={{opacity: 1}}\n          />\n          <polygon\n            id=\"Path-Copy-6\"\n            points=\"32.1949893 21 23 15.4824602 32.2699446 10 41 15.4769168\"\n            style={{\n              opacity: 1\n            }}\n          />\n          <polygon\n            id=\"Path-Copy\"\n            points=\"22.2609912 49 13.0660019 43.4824602 22.3359466 38 31.0660019 43.4769168\"\n            style={{opacity: 1}}\n          />\n          <polygon\n            id=\"Path-Copy-4\"\n            points=\"32.3269931 43.0228468 23.1320038 37.505307 32.4019485 32.0228468 41.1320038 37.4997636\"\n            style={{opacity: 0.6}}\n          />\n          <polygon\n            id=\"Path-Copy-7\"\n            points=\"42.3269931 37.0228468 33.1320038 31.505307 42.4019485 26.0228468 51.1320038 31.4997636\"\n            style={{opacity: 1}}\n          />\n          <polygon\n            id=\"Path-Copy-2\"\n            points=\"32.2609912 55 23.0660019 49.4824602 32.3359466 44 41.0660019 49.4769168\"\n            style={{opacity: 0.6}}\n          />\n          <polygon\n            id=\"Path-Copy-5\"\n            points=\"42.3269931 49.0228468 33.1320038 43.505307 42.4019485 38.0228468 51.1320038 43.4997636\"\n            style={{opacity: 1}}\n          />\n          <polygon\n            id=\"Path-Copy-8\"\n            points=\"53.3269931 43.0228468 44.1320038 37.505307 53.4019485 32.0228468 62.1320038 37.4997636\"\n            style={{opacity: 0.4}}\n          />\n        </g>\n      </Base>\n    );\n  }\n}\n\nexport default VectorTileIcon;\n"
  },
  {
    "path": "src/layers/src/vector-tile/vector-tile-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {FeatureCollection, Feature} from 'geojson';\n\nimport {Layer as DeckLayer} from '@deck.gl/core/typed';\nimport {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers/typed';\nimport {GeoJsonLayer, PathLayer} from '@deck.gl/layers/typed';\nimport {MVTSource, MVTTileSource} from '@loaders.gl/mvt';\nimport {PMTilesSource, PMTilesTileSource} from '@loaders.gl/pmtiles';\nimport GL from '@luma.gl/constants';\nimport {ClipExtension} from '@deck.gl/extensions/typed';\n\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {\n  getLoaderOptions,\n  DatasetType,\n  LAYER_TYPES,\n  RemoteTileFormat,\n  VectorTileDatasetMetadata,\n  SCALE_TYPES,\n  CHANNEL_SCALES,\n  DEFAULT_COLOR_UI,\n  LAYER_VIS_CONFIGS\n} from '@kepler.gl/constants';\nimport {\n  getTileUrl,\n  KeplerTable as KeplerDataset,\n  Datasets as KeplerDatasets,\n  GpuFilter,\n  VectorTileMetadata\n} from '@kepler.gl/table';\nimport {\n  AnimationConfig,\n  Field as KeplerField,\n  LayerColorConfig,\n  LayerHeightConfig,\n  Merge,\n  MapState,\n  BindedLayerCallbacks,\n  VisConfigRange,\n  VisConfigNumber,\n  DomainStops\n} from '@kepler.gl/types';\nimport {DataContainerInterface} from '@kepler.gl/utils';\n\nimport {MVTLayer as CustomMVTLayer} from './mvt-layer';\nimport VectorTileIcon from './vector-tile-icon';\nimport {\n  default as KeplerLayer,\n  LayerBaseConfig,\n  LayerBaseConfigPartial,\n  VisualChannel,\n  VisualChannelDomain,\n  VisualChannelField\n} from '../base-layer';\nimport {FindDefaultLayerPropsReturnValue} from '../layer-utils';\n\nimport AbstractTileLayer, {\n  LayerData as CommonLayerData,\n  commonTileVisConfigs,\n  AbstractTileLayerConfig,\n  AbstractTileLayerVisConfigSettings\n} from './abstract-tile-layer';\nimport TileDataset from './common-tile/tile-dataset';\nimport {\n  isDomainStops,\n  isDomainQuantiles,\n  isIndexedField,\n  getPropertyByZoom\n} from './common-tile/tile-utils';\n\nexport {getNumVectorTilesBeingLoaded} from './loading-counter';\n\nexport const DEFAULT_HIGHLIGHT_FILL_COLOR = [252, 242, 26, 150];\nexport const DEFAULT_HIGHLIGHT_STROKE_COLOR = [252, 242, 26, 255];\nexport const MAX_CACHE_SIZE_MOBILE = 1; // Minimize caching, visible tiles will always be loaded\nexport const DEFAULT_STROKE_WIDTH = 1;\nexport const UUID_CANDIDATES = [\n  'ufid',\n  'UFID',\n  'id',\n  'ID',\n  'fid',\n  'FID',\n  'objectid',\n  'OBJECTID',\n  'gid',\n  'GID',\n  'feature_id',\n  'FEATURE_ID',\n  '_id'\n];\n/**\n * Type for transformRequest returned parameters.\n */\nexport type RequestParameters = {\n  /** The URL to be requested. */\n  url: string;\n  /** Search parameters to be added onto the URL. */\n  searchParams: URLSearchParams;\n  /** Options passed to fetch. */\n  options: RequestInit;\n};\n\n// This type *seems* to be what loaders.gl currently returns for tile content.\n// Apparently this might be different depending on the loaders version, and for...\n// reasons we use two different versions of loaders right now.\n// TODO: The Features[] version should not be needed when we update to a newer\n// version of Deck.gl and use only one version of loaders\ntype TileContent =\n  | (FeatureCollection & {shape: 'geojson-table'})\n  | (Feature[] & {shape: undefined});\n\ntype VectorTile = Tile2DHeader<TileContent>;\n\ntype LayerData = CommonLayerData & {\n  tilesetDataUrl?: string | null;\n  tileSource: MVTTileSource | PMTilesTileSource | null;\n};\n\ntype VectorTileLayerRenderOptions = Merge<\n  {\n    idx: number;\n    visible: boolean;\n    mapState: MapState;\n    data: any;\n    animationConfig: AnimationConfig;\n    gpuFilter: GpuFilter;\n    layerCallbacks: BindedLayerCallbacks;\n    objectHovered: {\n      index: number;\n      tile: VectorTile;\n      sourceLayer: typeof GeoJsonLayer;\n    };\n  },\n  LayerData\n>;\n\nexport const vectorTileVisConfigs = {\n  ...commonTileVisConfigs,\n\n  stroked: {\n    ...LAYER_VIS_CONFIGS.stroked,\n    defaultValue: false\n  },\n\n  // TODO figure out why strokeColorScale can't be const\n  strokeColorScale: 'strokeColorScale' as any,\n  strokeColorRange: 'strokeColorRange' as const,\n\n  sizeRange: 'strokeWidthRange' as const,\n  strokeWidth: {\n    ...LAYER_VIS_CONFIGS.thickness,\n    property: 'strokeWidth',\n    defaultValue: 0.5,\n    allowCustomValue: false\n  },\n\n  radiusScale: 'radiusScale' as any,\n  radiusRange: {\n    ...LAYER_VIS_CONFIGS.radiusRange,\n    type: 'number',\n    defaultValue: [0, 1],\n    isRanged: true,\n    range: [0, 1],\n    step: 0.01\n  } as VisConfigRange\n};\n\nexport type VectorTileLayerConfig = Merge<\n  AbstractTileLayerConfig,\n  {\n    sizeField?: VisualChannelField;\n    sizeScale?: string;\n    sizeDomain?: VisualChannelDomain;\n\n    strokeColorField: VisualChannelField;\n\n    radiusField?: VisualChannelField;\n    radiusScale?: string;\n    radiusDomain?: VisualChannelDomain;\n    radiusRange?: any;\n\n    uniqueIdField?: string | null;\n  }\n>;\n\nexport type VectorTileLayerVisConfigSettings = Merge<\n  AbstractTileLayerVisConfigSettings,\n  {\n    sizeRange: VisConfigRange;\n    strokeWidth: VisConfigNumber;\n  }\n>;\n\nexport function tileLayerBoundsLayer(id: string, props: {bounds?: number[]}): DeckLayer[] {\n  const {bounds} = props;\n  if (bounds?.length !== 4) return [];\n\n  const data = [\n    {\n      path: [\n        [bounds[0], bounds[1]],\n        [bounds[2], bounds[1]],\n        [bounds[2], bounds[3]],\n        [bounds[0], bounds[3]],\n        [bounds[0], bounds[1]]\n      ]\n    }\n  ];\n\n  const layer = new PathLayer({\n    id: `${id}-vector-tile-bounds`,\n    data,\n    getPath: d => d.path,\n    getColor: [128, 128, 128, 255],\n    getWidth: 1,\n    widthUnits: 'pixels',\n    pickable: false\n  });\n\n  return [layer];\n}\n\nexport default class VectorTileLayer extends AbstractTileLayer<VectorTile, Feature[]> {\n  declare config: VectorTileLayerConfig;\n  declare visConfigSettings: VectorTileLayerVisConfigSettings;\n\n  constructor(props: ConstructorParameters<typeof AbstractTileLayer>[0]) {\n    super(props);\n    this.registerVisConfig(vectorTileVisConfigs);\n    this.tileDataset = this.initTileDataset();\n  }\n\n  meta = {};\n\n  static findDefaultLayerProps(dataset: KeplerDataset): FindDefaultLayerPropsReturnValue {\n    if (dataset.type !== DatasetType.VECTOR_TILE) {\n      return {props: []};\n    }\n    return super.findDefaultLayerProps(dataset);\n  }\n\n  initTileDataset(): TileDataset<VectorTile, Feature[]> {\n    return new TileDataset({\n      getTileId: (tile: VectorTile): string => tile.id,\n      getIterable: (tile: VectorTile): Feature[] => {\n        if (tile.content) {\n          return tile.content.shape === 'geojson-table' ? tile.content.features : tile.content;\n        }\n        return [];\n      },\n      getRowCount: (features: Feature[]): number => features.length,\n      getRowValue: this.accessRowValue\n    });\n  }\n\n  get type(): string {\n    return LAYER_TYPES.vectorTile;\n  }\n\n  get name(): string {\n    return 'Vector Tile';\n  }\n\n  get layerIcon(): KeplerLayer['layerIcon'] {\n    return VectorTileIcon;\n  }\n\n  get supportedDatasetTypes(): DatasetType[] {\n    return [DatasetType.VECTOR_TILE];\n  }\n\n  get visualChannels(): Record<string, VisualChannel> {\n    const visualChannels = super.visualChannels;\n    return {\n      ...visualChannels,\n      strokeColor: {\n        property: 'strokeColor',\n        field: 'strokeColorField',\n        scale: 'strokeColorScale',\n        domain: 'strokeColorDomain',\n        range: 'strokeColorRange',\n        key: 'strokeColor',\n        channelScaleType: CHANNEL_SCALES.color,\n        accessor: 'getLineColor',\n        condition: config => config.visConfig.stroked,\n        nullValue: visualChannels.color.nullValue,\n        getAttributeValue: config => config.visConfig.strokeColor || config.color\n      },\n      size: {\n        property: 'stroke',\n        field: 'sizeField',\n        scale: 'sizeScale',\n        domain: 'sizeDomain',\n        range: 'sizeRange',\n        key: 'size',\n        channelScaleType: CHANNEL_SCALES.size,\n        nullValue: 0,\n        accessor: 'getLineWidth',\n        condition: config => config.visConfig.stroked,\n        getAttributeValue: config => config.visConfig.strokeWidth || DEFAULT_STROKE_WIDTH\n      },\n      radius: {\n        property: 'radius',\n        field: 'radiusField',\n        scale: 'radiusScale',\n        domain: 'radiusDomain',\n        range: 'radiusRange',\n        key: 'radius',\n        channelScaleType: CHANNEL_SCALES.size,\n        nullValue: 0,\n        getAttributeValue: config => {\n          return config.visConfig.radius || config.radius;\n        },\n        accessor: 'getPointRadius',\n        defaultValue: config => config.radius\n      }\n    };\n  }\n\n  getDefaultLayerConfig(\n    props: LayerBaseConfigPartial\n  ): LayerBaseConfig & Partial<LayerColorConfig & LayerHeightConfig> {\n    const defaultLayerConfig = super.getDefaultLayerConfig(props);\n    return {\n      ...defaultLayerConfig,\n      colorScale: SCALE_TYPES.quantize,\n\n      strokeColorField: null,\n      strokeColorDomain: [0, 1],\n      strokeColorScale: SCALE_TYPES.quantile,\n      colorUI: {\n        ...defaultLayerConfig.colorUI,\n        // @ts-expect-error LayerConfig\n        strokeColorRange: DEFAULT_COLOR_UI\n      },\n\n      radiusField: null,\n      radiusDomain: [0, 1],\n      radiusScale: SCALE_TYPES.linear,\n\n      uniqueIdField: null\n    };\n  }\n\n  getHoverData(\n    object: {properties?: Record<string, Record<string, unknown>>},\n    dataContainer: DataContainerInterface,\n    fields: KeplerField[]\n  ): (Record<string, unknown> | null)[] {\n    return fields.map(f => object.properties?.[f.name] ?? null);\n  }\n\n  calculateLayerDomain(\n    dataset: KeplerDataset,\n    visualChannel: VisualChannel\n  ): DomainStops | number[] {\n    const defaultDomain = [0, 1];\n\n    const field = this.config[visualChannel.field];\n    const scale = this.config[visualChannel.scale];\n    if (!field) {\n      // if colorField or sizeField were set back to null\n      return defaultDomain;\n    }\n    if (scale === SCALE_TYPES.quantile && isDomainQuantiles(field?.filterProps?.domainQuantiles)) {\n      return field.filterProps.domainQuantiles;\n    }\n    if (isDomainStops(field?.filterProps?.domainStops)) {\n      return field.filterProps.domainStops;\n    } else if (Array.isArray(field?.filterProps?.domain)) {\n      return field.filterProps.domain;\n    }\n\n    return defaultDomain;\n  }\n\n  getScaleOptions(channelKey: string): string[] {\n    let options = KeplerLayer.prototype.getScaleOptions.call(this, channelKey);\n\n    const channel = this.visualChannels.strokeColor;\n    const field = this.config[channel.field];\n    if (\n      !(\n        isDomainQuantiles(field?.filterProps?.domainQuantiles) ||\n        this.config.visConfig.dynamicColor ||\n        // If we've set the scale to quantile, we need to include it - there's a loading\n        // period in which the visConfig isn't set yet, but if we don't return the right\n        // scale type we lose it\n        this.config.colorScale === SCALE_TYPES.quantile\n      )\n    ) {\n      options = options.filter(scale => scale !== SCALE_TYPES.quantile);\n    }\n\n    return options;\n  }\n\n  accessRowValue(\n    field?: KeplerField,\n    indexKey?: number | null\n  ): (field: KeplerField, datum: Feature) => number | null {\n    // if is indexed field\n    if (isIndexedField(field) && indexKey !== null) {\n      const fieldName = indexKey && field?.indexBy?.mappedValue[indexKey];\n      if (fieldName) {\n        return (f, datum) => {\n          if (datum.properties) {\n            return datum.properties[fieldName];\n          }\n          // TODO debug this with indexed tiled dataset\n          return datum[fieldName];\n        };\n      }\n    }\n\n    // default\n    return (f, datum) => {\n      if (f && datum.properties) {\n        return datum.properties[f.name];\n      }\n      // support picking & highlighting\n      return f ? datum[f.fieldIdx] : null;\n    };\n  }\n\n  updateLayerMeta(dataset: KeplerDataset, datasets: KeplerDatasets): void {\n    if (dataset.type !== DatasetType.VECTOR_TILE) {\n      return;\n    }\n\n    const datasetMeta = dataset.metadata as VectorTileMetadata & VectorTileDatasetMetadata;\n    this.updateMeta({\n      datasetId: dataset.id,\n      datasets,\n      bounds: datasetMeta.bounds\n    });\n  }\n\n  formatLayerData(\n    datasets: KeplerDatasets,\n    oldLayerData: unknown,\n    animationConfig: AnimationConfig\n  ): LayerData {\n    const {dataId} = this.config;\n    if (!notNullorUndefined(dataId)) {\n      return {tileSource: null};\n    }\n    const dataset = datasets[dataId];\n\n    let tilesetDataUrl: string | undefined;\n    let tileSource: LayerData['tileSource'] = null;\n\n    if (dataset?.type === DatasetType.VECTOR_TILE) {\n      const datasetMetadata = dataset.metadata as VectorTileMetadata & VectorTileDatasetMetadata;\n      const remoteTileFormat = datasetMetadata?.remoteTileFormat;\n      if (remoteTileFormat === RemoteTileFormat.MVT) {\n        const transformFetch = async (input: RequestInfo | URL, init?: RequestInit | undefined) => {\n          const requestData: RequestParameters = {\n            url: input as string,\n            searchParams: new URLSearchParams(),\n            options: init ?? {}\n          };\n\n          return fetch(requestData.url, requestData.options);\n        };\n\n        tilesetDataUrl = datasetMetadata?.tilesetDataUrl;\n        tileSource = tilesetDataUrl\n          ? MVTSource.createDataSource(decodeURIComponent(tilesetDataUrl), {\n              mvt: {\n                metadataUrl: datasetMetadata?.tilesetMetadataUrl ?? null,\n                loadOptions: {\n                  fetch: transformFetch\n                }\n              }\n            })\n          : null;\n      } else if (remoteTileFormat === RemoteTileFormat.PMTILES) {\n        // TODO: to render image pmtiles need to use TileLayer and BitmapLayer (https://github.com/visgl/loaders.gl/blob/master/examples/website/tiles/components/tile-source-layer.ts)\n        tilesetDataUrl = datasetMetadata?.tilesetDataUrl;\n        tileSource = tilesetDataUrl ? PMTilesSource.createDataSource(tilesetDataUrl, {}) : null;\n      }\n    }\n\n    return {\n      ...super.formatLayerData(datasets, oldLayerData, animationConfig),\n      tilesetDataUrl: typeof tilesetDataUrl === 'string' ? getTileUrl(tilesetDataUrl) : null,\n      tileSource\n    };\n  }\n\n  hasHoveredObject(objectInfo) {\n    if (super.hasHoveredObject(objectInfo)) {\n      const features = objectInfo?.tile?.content?.features;\n      return features[objectInfo.index];\n    }\n    return null;\n  }\n\n  renderSubLayers(props: Record<string, any>): DeckLayer | DeckLayer[] {\n    let {data} = props;\n\n    data = data?.shape === 'geojson-table' ? data.features : data;\n    if (!data?.length) {\n      return [];\n    }\n\n    const tile: Tile2DHeader = props.tile;\n    const zoom = tile.index.z;\n\n    return new GeoJsonLayer({\n      ...props,\n      data,\n      getFillColor: props.getFillColorByZoom ? props.getFillColor(zoom) : props.getFillColor,\n      getElevation: props.getElevationByZoom ? props.getElevation(zoom) : props.getElevation,\n      // radius for points\n      pointRadiusScale: props.pointRadiusScale, // props.getPointRadiusScaleByZoom(zoom),\n      pointRadiusUnits: props.pointRadiusUnits,\n      getPointRadius: props.getPointRadius,\n      // For some reason tile Layer reset autoHighlight to false\n      pickable: true,\n      autoHighlight: true,\n      stroked: props.stroked,\n      // wrapLongitude: true causes missing side polygon when extrude is enabled\n      wrapLongitude: false\n    });\n  }\n\n  // generate a deck layer\n  renderLayer(opts: VectorTileLayerRenderOptions): DeckLayer[] {\n    const {mapState, data, animationConfig, gpuFilter, objectHovered, layerCallbacks} = opts;\n    const {animation, visConfig} = this.config;\n\n    this.setLayerDomain = layerCallbacks.onSetLayerDomain;\n\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const eleZoomFactor = this.getElevationZoomFactor(mapState);\n\n    const transitions = this.config.visConfig.transition\n      ? {\n          getFillColor: {\n            duration: animationConfig.duration\n          },\n          getElevation: {\n            duration: animationConfig.duration\n          }\n        }\n      : undefined;\n\n    const colorField = this.config.colorField as KeplerField;\n    const heightField = this.config.heightField as KeplerField;\n    const strokeColorField = this.config.strokeColorField as KeplerField;\n    const sizeField = this.config.sizeField as KeplerField;\n    const radiusField = this.config.radiusField as KeplerField;\n\n    if (data.tileSource) {\n      const hoveredObject = this.hasHoveredObject(objectHovered);\n\n      // Resolve unique id property: use configured uniqueIdField if set, otherwise infer\n      let uniqueIdProperty: string | undefined;\n      let highlightedFeatureId: string | number | undefined;\n      if (hoveredObject && hoveredObject.properties) {\n        uniqueIdProperty =\n          this.config.uniqueIdField ??\n          UUID_CANDIDATES.find(k => hoveredObject.properties && k in hoveredObject.properties);\n        highlightedFeatureId = uniqueIdProperty\n          ? hoveredObject.properties[uniqueIdProperty]\n          : (hoveredObject as any).id;\n      }\n\n      // Build per-tile clipped overlay to draw only the outer stroke of highlighted feature per tile\n      const perTileOverlays = this._getPerTileOverlays(hoveredObject, {\n        defaultLayerProps,\n        visConfig,\n        uniqueIdProperty\n      });\n\n      const layers = [\n        new CustomMVTLayer({\n          ...defaultLayerProps,\n          ...data,\n          onViewportLoad: this.onViewportLoad,\n          data: data.tilesetDataUrl,\n          getTileData: data.tileSource?.getTileData,\n          tileSource: data.tileSource,\n          getFilterValue: this.getGpuFilterValueAccessor(opts),\n          filterRange: gpuFilter.filterRange,\n          lineWidthUnits: 'pixels',\n\n          binary: false,\n          elevationScale: visConfig.elevationScale * eleZoomFactor,\n          extruded: visConfig.enable3d,\n          stroked: visConfig.stroked,\n\n          // TODO: this is hard coded, design a UI to allow user assigned unique property id\n          uniqueIdProperty,\n          highlightedFeatureId,\n          renderSubLayers: this.renderSubLayers,\n          // when radiusUnits is meter\n          getPointRadiusScaleByZoom: getPropertyByZoom(visConfig.radiusByZoom, visConfig.radius),\n          pointRadiusUnits: visConfig.radiusUnits ? 'pixels' : 'meters',\n          pointRadiusScale: radiusField ? visConfig.radius : 1,\n\n          pointRadiusMinPixels: 1,\n          autoHighlight: true,\n          highlightColor: DEFAULT_HIGHLIGHT_FILL_COLOR,\n          pickable: true,\n          transitions,\n          updateTriggers: {\n            getFilterValue: {\n              ...gpuFilter.filterValueUpdateTriggers,\n              currentTime: animation.enabled ? animationConfig.currentTime : null\n            },\n            getFillColor: {\n              color: this.config.color,\n              colorField: this.config.colorField,\n              colorScale: this.config.colorScale,\n              colorDomain: this.config.colorDomain,\n              colorRange: visConfig.colorRange,\n              currentTime: isIndexedField(colorField) ? animationConfig.currentTime : null\n            },\n            getElevation: {\n              heightField: this.config.heightField,\n              heightScaleType: this.config.heightScale,\n              heightRange: visConfig.heightRange,\n              currentTime: isIndexedField(heightField) ? animationConfig.currentTime : null\n            },\n            getLineColor: {\n              strokeColor: visConfig.strokeColor,\n              strokeColorField: this.config.strokeColorField,\n              // @ts-expect-error prop not in LayerConfig\n              strokeColorScale: this.config.strokeColorScale,\n              // @ts-expect-error prop not in LayerConfig\n              strokeColorDomain: this.config.strokeColorDomain,\n              // FIXME: Strip out empty arrays from individual color map steps, and replace with `null`, otherwise the layer may show the incorrect color.\n              // So far it seems that it uses the previous color chosen in the palette rather than the currently chosen color for the specific custom ordinal value when there are \"sparse\" color maps.\n              // In other words, a color map with \"holes\" of colors with unassigned field values, which may have been assigned in the past.\n              // For example \"abc\" was green, stored as `[\"abc\"]`. Then \"abc\" was reassigned to the red color map step, stored as `[\"abc\"]`. Now the green color map step's stored value is `[]`, and the layer will incorrectly still render \"abc\" in green.\n              // Quick patch example:\n              // strokeColorRange: visConfig?.strokeColorRange?.colorMap?.map(cm =>\n              //   cm[0]?.length === 0 ? [null, cm[1]] : cm\n              // ),\n              // Note: for regular scales the colorMap in the above patch is undefined and breaks strokeColorRange update trigger.\n              strokeColorRange: visConfig.strokeColorRange,\n              currentTime: isIndexedField(strokeColorField) ? animationConfig.currentTime : null\n            },\n            getLineWidth: {\n              sizeRange: visConfig.sizeRange,\n              strokeWidth: visConfig.strokeWidth,\n              sizeField: this.config.sizeField,\n              sizeScale: this.config.sizeScale,\n              sizeDomain: this.config.sizeDomain,\n              currentTime: isIndexedField(sizeField) ? animationConfig.currentTime : null\n            },\n            getPointRadius: {\n              radius: visConfig.radius,\n              radiusField: this.config.radiusField,\n              radiusScale: this.config.radiusScale,\n              radiusDomain: this.config.radiusDomain,\n              radiusRange: this.config.radiusRange,\n              currentTime: isIndexedField(radiusField) ? animationConfig.currentTime : null\n            }\n          },\n          _subLayerProps: {\n            'polygons-stroke': {opacity: visConfig.strokeOpacity},\n            'polygons-fill': {\n              parameters: {\n                cullFace: GL.BACK\n              }\n            }\n          },\n          loadOptions: {\n            mvt: getLoaderOptions().mvt\n          }\n        }),\n        // render hover layer for features with no unique id property and no highlighted feature id\n        ...(hoveredObject && !uniqueIdProperty && !highlightedFeatureId\n          ? [\n              new GeoJsonLayer({\n                // @ts-expect-error props not typed?\n                ...objectHovered.sourceLayer?.props,\n                ...(this.getDefaultHoverLayerProps() as any),\n                visible: true,\n                wrapLongitude: false,\n                data: [hoveredObject],\n                getLineColor: DEFAULT_HIGHLIGHT_STROKE_COLOR,\n                getFillColor: DEFAULT_HIGHLIGHT_FILL_COLOR,\n                getLineWidth: visConfig.strokeWidth + 1,\n                lineWidthUnits: 'pixels',\n                stroked: true,\n                filled: true\n              })\n            ]\n          : []),\n        ...perTileOverlays\n        // ...tileLayerBoundsLayer(defaultLayerProps.id, data),\n      ];\n\n      return layers;\n    }\n    return [];\n  }\n\n  /**\n   * Build per-tile clipped overlay to draw only the outer stroke of highlighted feature per tile\n   * @param hoveredObject\n   */\n  _getPerTileOverlays(\n    hoveredObject: Feature,\n    options: {defaultLayerProps: any; visConfig: any; uniqueIdProperty?: string}\n  ): DeckLayer[] {\n    let perTileOverlays: DeckLayer[] = [];\n    if (hoveredObject) {\n      try {\n        const tiles = this.tileDataset?.getTiles?.() || [];\n        // Derive hovered id from hoveredObject\n        const hoveredId = options.uniqueIdProperty\n          ? String(hoveredObject?.properties?.[options.uniqueIdProperty])\n          : String((hoveredObject as any)?.id);\n\n        // Group matched fragments by tile id\n        const byTile: Record<string, Feature[]> = {};\n        for (const tile of tiles) {\n          const content = (tile as any)?.content;\n          const features = content?.shape === 'geojson-table' ? content.features : content;\n          if (!Array.isArray(features)) continue;\n          const tileId = (tile as any).id;\n          for (const f of features) {\n            const fid = options.uniqueIdProperty\n              ? f.properties?.[options.uniqueIdProperty]\n              : (f as any).id;\n            if (fid !== undefined && String(fid) === hoveredId) {\n              (byTile[tileId] = byTile[tileId] || []).push(f as Feature);\n            }\n          }\n        }\n\n        perTileOverlays = Object.entries(byTile).map(([tileId, feats]) => {\n          const tile = tiles.find((t: any) => String(t.id) === String(tileId));\n          const bounds = tile?.boundingBox\n            ? [...tile.boundingBox[0], ...tile.boundingBox[1]]\n            : undefined;\n          return new GeoJsonLayer({\n            ...(this.getDefaultHoverLayerProps() as any),\n            id: `${options.defaultLayerProps.id}-hover-outline-${tileId}`,\n            visible: true,\n            wrapLongitude: false,\n            data: feats,\n            getLineColor: DEFAULT_HIGHLIGHT_STROKE_COLOR,\n            getFillColor: [0, 0, 0, 0],\n            getLineWidth: options.visConfig.strokeWidth + 1,\n            lineWidthUnits: 'pixels',\n            lineJointRounded: true,\n            lineCapRounded: true,\n            stroked: true,\n            filled: false,\n            clipBounds: bounds,\n            extensions: bounds ? [new ClipExtension()] : []\n          });\n        });\n      } catch {\n        perTileOverlays = [];\n      }\n    }\n    return perTileOverlays;\n  }\n}\n"
  },
  {
    "path": "src/layers/src/wms-layer/wms-layer-icon.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport PropTypes from 'prop-types';\nimport React, {Component} from 'react';\n\nimport {Base} from '../base';\n\nclass WMSLayerIcon extends Component {\n  static propTypes = {\n    height: PropTypes.string,\n    colors: PropTypes.arrayOf(PropTypes.string.isRequired)\n  };\n\n  static defaultProps = {\n    height: '16px',\n    predefinedClassName: 'wms-layer-icon',\n    totalColor: 2,\n    viewBox: '0 0 30 30'\n  };\n\n  render(): JSX.Element {\n    return (\n      <Base {...this.props}>\n        <g clipPath=\"url(#clip0_13806_131258)\">\n          <path\n            d=\"M14.7595 12.2358L1 19.9828L15.2405 28L29 20.2536L14.7595 12.2358Z\"\n            fill=\"currentColor\"\n          />\n          <path\n            d=\"M14.7595 7.66919L1 15.4156L15.2405 23.4334L29 15.687L14.7595 7.66919Z\"\n            fill=\"#9DA0B9\"\n          />\n          <path d=\"M14.7595 2L1 9.74639L15.2405 17.7636L29 10.0172L14.7595 2Z\" fill=\"#BFC0D1\" />\n          <path d=\"M25.9696 10.8737H4.02124V19.267H25.9696V10.8737Z\" fill=\"currentColor\" />\n          <path\n            d=\"M5.51221 12.2358H6.77682L7.643 15.9522L8.58367 12.2358H9.64733L10.629 15.9725L11.5235 12.2358H12.6784L11.3341 17.6116H10.0637L9.0693 13.8953L8.05934 17.6116H6.85362L5.51221 12.2358Z\"\n            fill=\"white\"\n          />\n          <path\n            d=\"M13.4314 12.2358H15.0812L16.3689 15.5817L17.6393 12.2358H19.2561V17.6116H18.0146V13.8389L16.4884 17.6116H16.0796L14.5453 13.8389V17.6116H13.4314V12.2358Z\"\n            fill=\"white\"\n          />\n          <path\n            d=\"M20.1084 15.9274H21.3037C21.3574 16.4088 21.5659 16.8069 22.383 16.8069C22.9304 16.8069 23.293 16.5141 23.293 16.0929C23.293 15.6717 23.0621 15.5141 22.2536 15.3936C20.8429 15.2134 20.2643 14.7995 20.2643 13.7606C20.2643 12.8433 21.0508 12.1592 22.2692 12.1592C23.5107 12.1592 24.2354 12.7003 24.3353 13.7679H23.1851C23.1077 13.2792 22.8149 13.0539 22.2675 13.0539C21.7201 13.0539 21.4423 13.3017 21.4423 13.6626C21.4423 14.0461 21.6196 14.2257 22.4679 14.3468C23.8018 14.5123 24.4889 14.8659 24.4889 15.9797C24.4889 16.937 23.6874 17.6938 22.3841 17.6938C20.8718 17.6943 20.1777 17.0029 20.1084 15.9274Z\"\n            fill=\"white\"\n          />\n        </g>\n        <defs>\n          <clipPath id=\"clip0_13806_131258\">\n            <rect width=\"28\" height=\"26\" fill=\"white\" transform=\"translate(1 2)\" />\n          </clipPath>\n        </defs>\n      </Base>\n    );\n  }\n}\n\nexport default WMSLayerIcon;\n"
  },
  {
    "path": "src/layers/src/wms-layer/wms-layer.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {DatasetType, WMSDatasetMetadata, LAYER_TYPES} from '@kepler.gl/constants';\nimport {WMSLayer as DeckWMSLayer} from '@kepler.gl/deckgl-layers';\nimport {KeplerTable as KeplerDataset} from '@kepler.gl/table';\nimport {\n  AnimationConfig,\n  Field,\n  LayerBaseConfig,\n  VisConfigBoolean,\n  VisConfigNumber,\n  VisConfigSelection\n} from '@kepler.gl/types';\nimport {DataContainerInterface} from '@kepler.gl/utils';\n\nimport TileDataset from '../vector-tile/common-tile/tile-dataset';\nimport WMSLayerIcon from './wms-layer-icon';\nimport {FindDefaultLayerPropsReturnValue} from '../layer-utils';\n\nimport AbstractTileLayer, {\n  AbstractTileLayerVisConfigSettings,\n  LayerData\n} from '../vector-tile/abstract-tile-layer';\n\n// Types\nexport type WMSTile = {\n  id: string;\n  url: string;\n};\n\nexport const wmsTileVisConfigs = {\n  opacity: 'opacity' as const,\n  transparent: 'transparent' as const\n};\n\nexport type WMSLayerVisConfig = {\n  opacity: number;\n  transparent?: boolean;\n  wmsLayer: {\n    name: string;\n    title: string;\n    boundingBox: number[][];\n    queryable: boolean;\n  } | null;\n};\n\nexport type WMSLayerConfig = LayerBaseConfig & {\n  visConfig: WMSLayerVisConfig;\n};\n\n// Extend visConfigSettings to satisfy AbstractTileLayer\nexport type WMSLayerVisConfigSettings = AbstractTileLayerVisConfigSettings & {\n  opacity: VisConfigNumber;\n  transparent: VisConfigBoolean;\n  wmsLayer: VisConfigSelection;\n};\n\n// Extend LayerData for WMS\nexport type WMSLayerData = LayerData & {\n  tilesetDataUrl?: string | null;\n  metadata?: any;\n};\n\n// Class Definition\nexport default class WMSLayer extends AbstractTileLayer<WMSTile, any[]> {\n  declare config: WMSLayerConfig;\n  declare visConfigSettings: WMSLayerVisConfigSettings;\n\n  // Store reference to the deck layer for feature info access\n  private deckLayerRef: DeckWMSLayer | null = null;\n\n  // Constructor\n  constructor(\n    props: ConstructorParameters<typeof AbstractTileLayer>[0] & {\n      layers?: WMSDatasetMetadata['layers'];\n    }\n  ) {\n    super(props);\n\n    const defaultWmsLayer = props.layers?.[0] ?? null;\n\n    this.registerVisConfig(wmsTileVisConfigs);\n    this.updateLayerVisConfig({\n      opacity: 0.8, // Default opacity\n      wmsLayer: defaultWmsLayer,\n      transparent: true\n    });\n  }\n\n  // Properties\n  get type() {\n    return LAYER_TYPES.wms;\n  }\n\n  get name() {\n    return 'WMS Tile';\n  }\n\n  get layerIcon() {\n    return WMSLayerIcon;\n  }\n\n  // Static Methods\n  static findDefaultLayerProps(dataset: KeplerDataset): FindDefaultLayerPropsReturnValue {\n    if (dataset.type !== DatasetType.WMS_TILE) {\n      return {props: []};\n    }\n    const {label} = dataset.metadata || {};\n    const props = {\n      label: label || 'WMS Layer',\n      layers: dataset.metadata?.layers || []\n    };\n\n    return {props: [props]};\n  }\n\n  // Instance Methods\n  get supportedDatasetTypes(): DatasetType[] {\n    return [DatasetType.WMS_TILE];\n  }\n\n  protected initTileDataset() {\n    // Provide dummy accessors for raster/WMS\n    return new TileDataset<WMSTile, any[]>({\n      getTileId: tile => tile?.id || 'wms',\n      getIterable: _tile => [],\n      getRowCount: () => 0,\n      getRowValue: () => () => null\n    });\n  }\n\n  accessRowValue(_field?: Field, _indexKey?: number | null) {\n    // WMS layers are raster, so no row access; return a dummy accessor\n    return () => null;\n  }\n\n  formatLayerData(datasets, oldLayerData, animationConfig): WMSLayerData {\n    const {dataId} = this.config;\n    if (!notNullorUndefined(dataId) || !datasets[dataId]) {\n      return {\n        tilesetDataUrl: null,\n        metadata: null\n      };\n    }\n    const dataset = datasets[dataId];\n    const metadata = dataset?.metadata;\n\n    return {\n      ...super.formatLayerData(datasets, oldLayerData, animationConfig),\n      tilesetDataUrl: metadata?.tilesetDataUrl || null, // URL for WMS tiles\n      metadata: dataset?.metadata\n    };\n  }\n\n  _getCurrentServiceLayer() {\n    const {visConfig} = this.config;\n    return visConfig.wmsLayer ?? null;\n  }\n\n  updateLayerMeta(dataset: KeplerDataset): void {\n    if (dataset.type !== DatasetType.WMS_TILE) {\n      return;\n    }\n\n    const currentLayer = this._getCurrentServiceLayer();\n    if (currentLayer && currentLayer.boundingBox) {\n      this.updateMeta({\n        bounds: currentLayer.boundingBox\n      });\n    }\n  }\n\n  hasHoveredObject(objectInfo: any) {\n    // For WMS layers, we consider it hovered if the layer is picked\n    // The actual feature info will be retrieved via getHoverData\n    if (this.isLayerHovered(objectInfo)) {\n      return {\n        index: 0, // WMS layers don't have discrete data points, so we use index 0\n        ...objectInfo\n      };\n    }\n    return null;\n  }\n\n  getHoverData(\n    object: any,\n    dataContainer: DataContainerInterface,\n    fields: Field[],\n    animationConfig: AnimationConfig,\n    hoverInfo: {index: number; x?: number; y?: number}\n  ) {\n    // Check if this is a WMS feature info object from clicked state\n    if (object?.wmsFeatureInfo) {\n      if (Array.isArray(object.wmsFeatureInfo)) {\n        return {\n          wmsFeatureData: object.wmsFeatureInfo\n        };\n      }\n\n      return {\n        wmsFeatureData: [\n          {\n            name: 'WMS Feature Info',\n            value: object.wmsFeatureInfo\n          }\n        ]\n      };\n    }\n\n    if (hoverInfo.x !== undefined && hoverInfo.y !== undefined) {\n      return {\n        fieldValues: [\n          {\n            labelMessage: 'layer.wms.hover',\n            value: 'Click to query WMS feature info'\n          }\n        ]\n      };\n    }\n\n    return null;\n  }\n\n  renderLayer(opts) {\n    const {visConfig} = this.config;\n    const {data, interactionConfig, layerCallbacks} = opts;\n    const wmsLayer = this._getCurrentServiceLayer();\n    if (!wmsLayer) {\n      return [];\n    }\n    const {name: wmsLayerName, queryable} = wmsLayer;\n    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);\n    const pickable = interactionConfig?.tooltip?.enabled && queryable;\n\n    const deckLayer = new DeckWMSLayer({\n      id: `${this.id}-WMSLayer` as string,\n      idx: defaultLayerProps.idx,\n      serviceType: 'wms',\n      data: data.tilesetDataUrl,\n      layers: [wmsLayerName],\n      opacity: visConfig.opacity,\n      transparent: visConfig.transparent,\n      pickable,\n      // @ts-ignore\n      onClick: pickable ? this._onClick.bind(this, layerCallbacks) : null\n    });\n\n    // Store reference to the deck layer for feature info access\n    this.deckLayerRef = deckLayer;\n\n    return [deckLayer];\n  }\n\n  protected async _onClick(layerCallbacks, {bitmap, coordinate}) {\n    if (!bitmap) return null;\n\n    const x = bitmap.pixel[0];\n    const y = bitmap.pixel[1];\n    const featureInfo = await this.getWMSFeatureInfo(x, y);\n\n    // Call the callback to update state with coordinate\n    if (layerCallbacks?.onWMSFeatureInfo) {\n      layerCallbacks.onWMSFeatureInfo({featureInfo, coordinate});\n    }\n\n    return featureInfo;\n  }\n\n  // Method to retrieve WMS feature info asynchronously\n  protected async getWMSFeatureInfo(\n    x: number,\n    y: number\n  ): Promise<Array<{name: string; value: string}> | null> {\n    try {\n      if (this.deckLayerRef && typeof this.deckLayerRef.getFeatureInfoText === 'function') {\n        const featureInfoXml = await this.deckLayerRef.getFeatureInfoText(x, y);\n        if (featureInfoXml) {\n          // Parse the XML response to extract attributes\n          const parsedAttributes = this.parseWMSFeatureInfo(featureInfoXml);\n          return parsedAttributes.length > 0 ? parsedAttributes : null;\n        }\n      }\n      return null;\n    } catch (error) {\n      console.warn('Failed to get WMS feature info:', error);\n      return null;\n    }\n  }\n\n  // Helper method to parse WMS XML response\n  protected parseWMSFeatureInfo(xmlString: string): Array<{name: string; value: string}> {\n    try {\n      // Simple XML parsing to extract feature attributes\n      const parser = new DOMParser();\n      const xmlDoc = parser.parseFromString(xmlString, 'text/xml');\n\n      const attributes: Array<{name: string; value: string}> = [];\n\n      // Look for feature members\n      const featureMembers = xmlDoc.getElementsByTagName('gml:featureMember');\n\n      for (let i = 0; i < featureMembers.length; i++) {\n        const featureMember = featureMembers[i];\n\n        // Get all child elements that contain attribute data\n        const children = featureMember.children;\n\n        for (let j = 0; j < children.length; j++) {\n          const feature = children[j];\n          const featureChildren = feature.children;\n\n          // Extract attribute name-value pairs\n          for (let k = 0; k < featureChildren.length; k++) {\n            const attr = featureChildren[k];\n            const tagName = attr.tagName;\n            const value = attr.textContent || '';\n\n            // Clean up the tag name (remove namespace prefix)\n            const cleanName = tagName.includes(':') ? tagName.split(':')[1] : tagName;\n\n            // Skip empty values and geometry elements\n            if (value.trim() && !cleanName.toLowerCase().includes('geom')) {\n              attributes.push({\n                name: cleanName.replace(/_/g, ' ').toUpperCase(),\n                value: value.trim()\n              });\n            }\n          }\n        }\n      }\n\n      return attributes;\n    } catch (error) {\n      console.warn('Error parsing WMS feature info XML:', error);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "src/layers/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/localization/TRANSLATION_GUIDE.md",
    "content": "# Translation Guide for kepler.gl\n\nThank you for helping translate kepler.gl! This guide explains how to add a new language or update existing translations.\n\n## Current Languages\n\n| Code | Language | File |\n|------|----------|------|\n| `en` | English | `src/translations/en.ts` |\n| `fi` | Suomi (Finnish) | `src/translations/fi.ts` |\n| `pt` | Português (Portuguese) | `src/translations/pt.ts` |\n| `es` | Español (Spanish) | `src/translations/es.ts` |\n| `ca` | Català (Catalan) | `src/translations/ca.ts` |\n| `ja` | 日本語 (Japanese) | `src/translations/ja.ts` |\n| `cn` | 简体中文 (Simplified Chinese) | `src/translations/cn.ts` |\n| `ru` | Русский (Russian) | `src/translations/ru.ts` |\n\n## Adding a New Language\n\n### 1. Create the translation file\n\nCopy `src/translations/en.ts` to `src/translations/<code>.ts` (use the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) two-letter code):\n\n```bash\ncp src/translations/en.ts src/translations/de.ts\n```\n\n### 2. Translate all strings\n\nEdit your new file and translate every string value. Keep the keys unchanged:\n\n```typescript\n// src/translations/de.ts\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'Gewicht',\n    label: 'Beschriftung',\n    fillColor: 'Füllfarbe',\n    // ... translate all entries\n  },\n  // ...\n};\n```\n\n**Tips:**\n- Use `src/translations/en.ts` as the reference for all keys\n- Keep placeholders like `{mapboxToken}` and `{errorMessage}` unchanged\n- Preserve HTML entities and formatting strings\n- For keys you're unsure about, leave the English text and add a `// TODO: translate` comment\n\n### 3. Register the language\n\n**a.** Add the locale to `src/locales.ts`:\n\n```typescript\nexport const LOCALES = {\n  en: 'English',\n  // ... existing locales\n  de: 'Deutsch'  // <-- add your language\n};\n```\n\n**b.** Import and add the translation in `src/messages.ts`:\n\n```typescript\nimport de from './translations/de';  // <-- add import\n\nexport const messages = {\n  // ... existing entries\n  de: flattenMessages(de)  // <-- add entry\n};\n```\n\n### 4. Test your translation\n\nSet the locale in a kepler.gl instance:\n\n```javascript\nimport {LOCALE_CODES} from '@kepler.gl/localization';\n\nconst customizedKeplerGlReducer = keplerGlReducer\n  .initialState({\n    uiState: {\n      locale: LOCALE_CODES.de\n    }\n  });\n```\n\nOr switch languages at runtime using the locale panel in the map controls (globe icon).\n\n## Updating Existing Translations\n\nWhen new English strings are added, other translation files may fall behind. To find missing translations:\n\n1. Compare your translation file against `en.ts` to identify missing keys\n2. Search for `// TODO` comments in existing translation files\n3. Check for keys that still have English values in non-English files\n\n## Translation Structure\n\nTranslations use a nested object structure that gets flattened by `flattenMessages()`:\n\n```typescript\n{\n  property: {\n    weight: 'weight',     // becomes 'property.weight'\n    label: 'label'        // becomes 'property.label'\n  },\n  toolbar: {\n    exportImage: 'Export Image'  // becomes 'toolbar.exportImage'\n  }\n}\n```\n\nIn components, strings are referenced via `<FormattedMessage id=\"property.weight\" />` or the `intl.formatMessage()` API.\n\n## Questions?\n\nOpen an issue on [GitHub](https://github.com/keplergl/kepler.gl/issues) with the `localization` label.\n"
  },
  {
    "path": "src/localization/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/localization/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/localization\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"react\": \"^18.2.0\",\n    \"react-intl\": \"^6.3.0\",\n    \"redux\": \"^4.2.1\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/localization/src/formatted-message.tsx",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {FormattedMessage} from 'react-intl';\n\ntype EnhancedFormattedMessageProps = {\n  id: string;\n  defaultMessage?: string;\n  defaultValue?: string;\n  values?: {\n    [key: string]: string | number | null;\n  };\n  children?: () => React.ReactElement;\n};\n\nconst EnhancedFormattedMessage: React.FC<EnhancedFormattedMessageProps> = props => (\n  <FormattedMessage\n    // Us id as default Message to prevent error being thrown\n    defaultMessage={props.defaultMessage || props.id}\n    {...props}\n  />\n);\n\nexport default EnhancedFormattedMessage;\n"
  },
  {
    "path": "src/localization/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {messages} from './messages';\nexport {LOCALE_CODES, LOCALES} from './locales';\nexport {default as FormattedMessage} from './formatted-message';\n"
  },
  {
    "path": "src/localization/src/locales.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const LOCALES = {\n  en: 'English',\n  fi: 'Suomi',\n  pt: 'Português',\n  es: 'Español',\n  ca: 'Català',\n  ja: '日本語',\n  cn: '简体中文',\n  ru: 'Русский'\n};\n\n/**\n * Localization can be passed to `KeplerGl` via uiState `locale`.\n * Available languages are `en` and `fi`. Default language is `en`\n * @constant\n * @public\n * @example\n * ```js\n * import {combineReducers} from 'redux';\n * import {LOCALE_CODES} from '@kepler.gl/localization';\n *\n * const customizedKeplerGlReducer = keplerGlReducer\n *   .initialState({\n *     uiState: {\n *       // use Finnish locale\n *       locale: LOCALE_CODES.fi\n *     }\n *   });\n *\n * ```\n */\n\nexport type LocaleCodesType = {\n  [key: string]: string;\n};\n\nexport const LOCALE_CODES: LocaleCodesType = Object.keys(LOCALES).reduce(\n  (acc, key) => ({...acc, [key]: key}),\n  {}\n);\n"
  },
  {
    "path": "src/localization/src/messages.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport en from './translations/en';\nimport fi from './translations/fi';\nimport pt from './translations/pt';\nimport es from './translations/es';\nimport ca from './translations/ca';\nimport cn from './translations/cn';\nimport ja from './translations/ja';\nimport ru from './translations/ru';\n\n// Flat messages since react-intl does not seem to support nested structures\n// Adapted from https://medium.com/siren-apparel-press/internationalization-and-localization-of-sirenapparel-eu-sirenapparel-us-and-sirenapparel-asia-ddee266066a2\nexport const flattenMessages = (\n  nestedMessages,\n  prefix = ''\n): {\n  [key: string]: string;\n} => {\n  return Object.keys(nestedMessages).reduce((messages, key) => {\n    const value = nestedMessages[key];\n    const prefixedKey = prefix ? `${prefix}.${key}` : key;\n    if (typeof value === 'string') {\n      messages[prefixedKey] = value;\n    } else {\n      Object.assign(messages, flattenMessages(value, prefixedKey));\n    }\n    return messages;\n  }, {});\n};\n\nconst enFlat = flattenMessages(en);\n\nexport const messages: {\n  [key: string]: {\n    [key: string]: string;\n  };\n} = {};\n\nmessages.en = enFlat;\nmessages.fi = {...enFlat, ...flattenMessages(fi)};\nmessages.pt = {...enFlat, ...flattenMessages(pt)};\nmessages.es = {...enFlat, ...flattenMessages(es)};\nmessages.ca = {...enFlat, ...flattenMessages(ca)};\nmessages.cn = {...enFlat, ...flattenMessages(cn)};\nmessages.ja = {...enFlat, ...flattenMessages(ja)};\nmessages.ru = {...enFlat, ...flattenMessages(ru)};\n\nexport default messages;\n"
  },
  {
    "path": "src/localization/src/translations/ca.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'pes',\n    label: 'etiqueta',\n    fillColor: 'color fons',\n    color: 'color',\n    coverage: 'cobertura',\n    strokeColor: 'color de traç',\n    radius: 'radi',\n    outline: 'outline',\n    stroke: 'traç',\n    density: 'densitat',\n    height: 'alçada',\n    sum: 'suma',\n    pointCount: 'Recompte de Punts'\n  },\n  placeholder: {\n    search: 'Cerca',\n    selectField: 'Selecciona un camp',\n    yAxis: 'Eix Y',\n    selectType: 'Selecciona un Tipus',\n    selectValue: 'Selecciona un Valor',\n    enterValue: 'Entra un valor',\n    empty: 'buit'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Valors a',\n    valueEquals: 'Valor igual a',\n    dataSource: 'Font de dades',\n    brushRadius: 'Radi del pinzell (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Capes del mapa',\n    label: 'Etiqueta',\n    road: 'Carretera',\n    border: 'Frontera',\n    building: 'Edifici',\n    water: 'Aigua',\n    land: 'Terra',\n    '3dBuilding': 'Edifici 3D',\n    background: 'Fons'\n  },\n  panel: {\n    text: {\n      label: 'etiqueta',\n      labelWithId: 'Etiqueta {labelId}',\n      fontSize: 'Mida de la font',\n      fontColor: 'Color de la font',\n      textAnchor: 'Àncora del text',\n      alignment: 'Alineació',\n      addMoreLabel: 'Afegeix més etiquetes'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Capes',\n      filter: 'Filtres',\n      interaction: 'Interaccions',\n      basemap: 'Mapa base'\n    }\n  },\n  layer: {\n    required: 'Requerit*',\n    radius: 'Radi',\n    color: 'Color',\n    fillColor: 'Color fons',\n    outline: 'Contorn',\n    weight: 'Gruix',\n    propertyBasedOn: '{property} basada en',\n    coverage: 'Cobertura',\n    stroke: 'Traç',\n    strokeWidth: 'Amplada de traç',\n    strokeColor: 'Color de traç',\n    basic: 'Basic',\n    trailLength: 'Longitud de pista',\n    trailLengthDescription: 'Nombre de segons fins que desapareix el camí',\n    newLayer: 'nova capa',\n    elevationByDescription: \"Si desactivat, l'alçada es basa en el recompte de punts\",\n    colorByDescription: 'Si desactivat, el color es basa en el recompte de punts',\n    aggregateBy: '{field} agregat per',\n    '3DModel': 'Model 3D',\n    '3DModelOptions': 'Opcions del model 3D',\n    type: {\n      point: 'punt',\n      arc: 'arc',\n      line: 'línia',\n      grid: 'malla',\n      hexbin: 'hexbin',\n      polygon: 'polígon',\n      geojson: 'geojson',\n      cluster: 'cluster',\n      icon: 'icona',\n      heatmap: 'heatmap',\n      hexagon: 'hexàgon',\n      hexagonid: 'H3',\n      trip: 'viatge',\n      s2: 'S2',\n      '3d': '3D'\n    },\n    layerUpdateError:\n      \"S'ha produït un error durant l'actualització de la capa: {errorMessage}. Assegureu-vos que el format de les dades d’entrada sigui vàlid.\"\n  },\n  layerVisConfigs: {\n    angle: 'Angle',\n    strokeWidth: 'Amplada traç',\n    strokeWidthRange: 'Rang amplada de traç',\n    radius: 'Radi',\n    fixedRadius: 'Radi fixe a mesurar',\n    fixedRadiusDescription: 'Ajusta el radi al radi absolut en metres, p.ex 5 a 5 metres',\n    radiusRange: 'Rang de radi',\n    clusterRadius: 'Radi Cluster en Pixels',\n    radiusRangePixels: 'Rang del radi en pixels',\n    billboard: 'Mode de cartellera',\n    billboardDescription: 'Orientar la geometria cap a la càmera',\n    fadeTrail: 'Rastre de desvaniment',\n    opacity: 'Opacitat',\n    coverage: 'Cobertura',\n    outline: 'Outline',\n    colorRange: 'Rang de color',\n    stroke: 'Traç',\n    strokeColor: 'Color de traç',\n    strokeColorRange: 'Rang de color de traç',\n    targetColor: 'Color destí',\n    colorAggregation: 'Agregació de color',\n    heightAggregation: 'Agregació alçada',\n    resolutionRange: 'Rang de resolució',\n    sizeScale: 'Mida escala',\n    worldUnitSize: 'Mida de la unitat mundial',\n    elevationScale: 'Escala elevació',\n    enableElevationZoomFactor: 'Utilitzeu el factor de zoom d’elevació',\n    enableElevationZoomFactorDescription:\n      \"'Ajusteu l'alçada / elevació en funció del factor de zoom actual\",\n    enableHeightZoomFactor: 'Utilitzeu el factor de zoom d’alçada',\n    heightScale: 'Escala alçada',\n    coverageRange: 'Rang ed cobertura',\n    highPrecisionRendering: 'Representació alta precisió',\n    highPrecisionRenderingDescription: 'La precisió alta tindrà rendiment més baix',\n    height: 'Alçada',\n    heightDescription: 'Fes clic al botó a dalt a la dreta del mapa per canviar a vista 3D',\n    fill: 'Omple',\n    enablePolygonHeight: 'Activa alçada del polígon',\n    showWireframe: 'Mostra Wireframe',\n    weightIntensity: 'Intensitat de pes',\n    zoomScale: 'Escala de zoom',\n    heightRange: 'Rang alçada',\n    heightMultiplier: \"Multiplicador d'alçada\",\n    fixedHeight: 'Alçada fixa',\n    fixedHeightDescription: \"Utilitzeu l'alçada sense modificacions\"\n  },\n  layerManager: {\n    addData: 'Afegeix Dades',\n    addLayer: 'Afegeix Capes',\n    layerBlending: 'Combinar capes'\n  },\n  mapManager: {\n    mapStyle: 'Estil de mapa',\n    addMapStyle: 'Afegeix estils de mapa',\n    '3dBuildingColor': 'Color edifici 3D',\n    backgroundColor: 'Color de fons'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Calcula {property} segons el camp seleccionat',\n    howTo: 'How to'\n  },\n  filterManager: {\n    addFilter: 'Afegeix Filtre'\n  },\n  datasetTitle: {\n    showDataTable: 'Mostra taula de dades',\n    removeDataset: 'Elimina conjunt de dades'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} files'\n  },\n  tooltip: {\n    hideLayer: 'oculta la capa',\n    showLayer: 'mostra la capa',\n    hideFeature: \"Amaga l'objecte\",\n    showFeature: \"Mostra l'objecte\",\n    hide: 'amaga',\n    show: 'mostra',\n    removeLayer: 'Elimina capa',\n    resetAfterError: 'Intenteu habilitar la capa després dun error',\n    layerSettings: 'Configuració de capa',\n    closePanel: 'Tanca panel actual',\n    switchToDualView: 'Canvia a la vista de mapa dual',\n    showLegend: 'mostra llegenda',\n    disable3DMap: 'Desactiva mapa 3D',\n    DrawOnMap: 'Dibuixa al mapa',\n    selectLocale: 'Selecciona configuració regional',\n    showAiAssistantPanel: 'Mostra el tauler de AI Assistant',\n    hideAiAssistantPanel: 'Oculta el tauler de AI Assistant',\n    hideLayerPanel: 'Oculta el tauler de capes',\n    showLayerPanel: 'Mostra el tauler de capes',\n    moveToTop: 'Desplaça a dalt de tot de les capes de dades',\n    selectBaseMapStyle: 'Selecciona estil de mapa base',\n    delete: 'Esborra',\n    timePlayback: 'Reproducció de temps',\n    cloudStorage: 'Emmagatzematge al núvol',\n    '3DMap': 'Mapa 3D',\n    animationByWindow: 'Finestra Temporal Mòbil',\n    animationByIncremental: 'Finestra Temporal Incremental',\n    speed: 'velocitat',\n    play: 'iniciar',\n    pause: 'pausar',\n    reset: 'reiniciar'\n  },\n  toolbar: {\n    exportImage: 'Exporta imatge',\n    exportData: 'Exporta dades',\n    exportMap: 'Exporta mapa',\n    shareMapURL: 'Comparteix URL del mapa',\n    saveMap: 'Desa mapa',\n    select: 'selecciona',\n    polygon: 'polígon',\n    rectangle: 'rectangle',\n    hide: 'amaga',\n    show: 'mostra',\n    ...LOCALES\n  },\n  modal: {\n    title: {\n      deleteDataset: 'Esborra conjunt de dades',\n      addDataToMap: 'Afegeix dades al mapa',\n      exportImage: 'Exporta imatge',\n      exportData: 'Exporta dades',\n      exportMap: 'Exporta mapa',\n      addCustomMapboxStyle: 'Afegeix estil Mapbox propi',\n      saveMap: 'Desa mapa',\n      shareURL: 'Comparteix URL'\n    },\n    button: {\n      delete: 'Esborra',\n      download: 'Descarrega',\n      export: 'Exporta',\n      addStyle: 'Afegeix estil',\n      save: 'Desa',\n      defaultCancel: 'Cancel·la',\n      defaultConfirm: 'Confirma'\n    },\n    exportImage: {\n      ratioTitle: 'Ràtio',\n      ratioDescription: 'Escull ràtio per diversos usos.',\n      ratioOriginalScreen: 'Pantalla original',\n      ratioCustom: 'Personalitzat',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Resolució',\n      resolutionDescription: 'Alta resolució és millor per a les impressions.',\n      resolutionPlaceholder: 'Selecciona resolució...',\n      mapLegendTitle: 'Llegenda del mapa',\n      mapLegendAdd: 'Afegir llegenda al mapa'\n    },\n    exportData: {\n      datasetTitle: 'Conjunt de dades',\n      datasetSubtitle: 'Escull els conjunts de dades que vols exportar',\n      allDatasets: 'Tots',\n      dataTypeTitle: 'Tipus de dades',\n      dataTypeSubtitle: 'Escull els tipus de dades que vols exportar',\n      filterDataTitle: 'Filtra dades',\n      filterDataSubtitle: 'Pots escollir exportar les dades originals o les filtrades',\n      filteredData: 'Dades filtrades',\n      unfilteredData: 'Dades sense filtrar',\n      fileCount: '{fileCount} Arxius',\n      rowCount: '{rowCount} Files'\n    },\n    deleteData: {\n      warning: \"estàs a punt d'esborrar aquest conjunt de dades. Afectarà {length} capes\"\n    },\n    addStyle: {\n      publishTitle: \"2. Publica el teu estil a Mapbox o proporciona el token d'accés\",\n      publishSubtitle1: 'Pots crear el teu propi estil de mapa a',\n      publishSubtitle2: 'i',\n      publishSubtitle3: 'publicar',\n      publishSubtitle4: 'ho.',\n      publishSubtitle5: 'Per utilitzar un estil privat, enganxa el teu',\n      publishSubtitle6: \"token d'accés\",\n      publishSubtitle7:\n        'aquí. *kepler.gl és una aplicació client, les dades romanen al teu navegador..',\n      exampleToken: 'p.ex. pk.abcdefg.xxxxxx',\n      pasteTitle: \"1. Enganxa la URL de l'estil\",\n      pasteSubtitle1: 'Què és un',\n      pasteSubtitle2: \"URL de l'estil\",\n      namingTitle: '3. Posa nom al teu estil'\n    },\n    shareMap: {\n      shareUriTitle: 'Comparteix URL del mapa',\n      shareUriSubtitle: 'Genera una URL del mapa per compartir amb altri',\n      cloudTitle: 'Emmagatzematge al núvol',\n      cloudSubtitle: 'Accedeix i carrega dades de mapa al teu emmagatzematge al núvol personal',\n      shareDisclaimer:\n        'kepler.gl desarà les dades del mapa al teu emmagatzematge al núvol personal, només qui tingui la URL podrà accedir al mapa i a les dades . ' +\n        \"Pots editar/esborrar l'arxiu de dades en el teu compte al núvol en qualsevol moment.\",\n      gotoPage: 'Ves a la pàgina de {currentProvider} de Kepler.gl'\n    },\n    statusPanel: {\n      mapUploading: 'Carregar un mapa',\n      error: 'Error'\n    },\n    saveMap: {\n      title: 'Emmagatzematge al núvol',\n      subtitle: 'Accedeix per desar el mapa al teu emmagatzematge al núvol'\n    },\n    exportMap: {\n      formatTitle: 'Format de mapa',\n      formatSubtitle: 'Escull el format amb què vols exportar el teu mapa',\n      html: {\n        selection: 'Exporta el teu mapa com un arxiu HTML interactiu.',\n        tokenTitle: \"Token d'accés de Mapbox\",\n        tokenSubtitle: \"Utilitza el teu token d'accés de Mapbox a l'arxiu HTML (opcional)\",\n        tokenPlaceholder: \"Enganxa el teu token d'accés a Mapbox\",\n        tokenMisuseWarning:\n          '* Si no proporciones el teu propi token, el mapa podria fallar en qualsevol moment quan reemplacem el nostre token per evitar abusos. ',\n        tokenDisclaimer:\n          'Pots canviar el toke de Mapbox més endavant fent servir aquestes instruccions: ',\n        tokenUpdate: 'Com actualitzar un token preexistent.',\n        modeTitle: 'Mode mapa',\n        modeSubtitle1: 'Selecciona mode app. Més ',\n        modeSubtitle2: 'informació',\n        modeDescription: 'Permet als usuaris {mode} el mapa',\n        read: 'llegir',\n        edit: 'editar'\n      },\n      json: {\n        configTitle: 'Configuració del mapa',\n        configDisclaimer:\n          \"La configuració del mapa s'inclourà a l'arxiu Json. Si utilitzes kepler.gl a la teva pròpia app pots copiar aquesta configuració i passar-la a  \",\n        selection:\n          'Exporta les dades del mapa i la configuració en un sol arxiu Json. Més endavant pots obrir aquest mateix mapa carregant aquest mateix arxiu a kepler.gl.',\n        disclaimer:\n          \"* La configuració del mapa es combina amb els conjunts de dades carregats. ‘dataId’ s'utilitza per lligar capes, filtres i suggeriments a un conjunt de dades específic. \" +\n          \"Quan passis aquesta configuració a addDataToMap, assegura que l'identificador del conjunt de dades coincideixi amb els ‘dataId’ d'aquesta configuració.\"\n      }\n    },\n    loadingDialog: {\n      loading: 'Carregant...'\n    },\n    loadData: {\n      upload: 'Carregar arxius',\n      storage: \"Carregar des d'emmagatzematge\"\n    },\n    tripInfo: {\n      title: 'Com habilitar l’animació de viatge',\n      description1:\n        'Per animar la ruta, les dades geoJSON han de contenir `LineString` en la seva geometria i les coordenades de LineString han de tenir 4 elements en els formats de ',\n      code: ' [longitude, latitude, altitude, timestamp] ',\n      description2:\n        'i el darrer element ha de ser la marca de temps. Els formats vàlids per a la marca de temps inclouen Unix en segons com `1564184363` o en milisegons com `1564184363000`.',\n      example: 'Exemple:'\n    },\n    iconInfo: {\n      title: 'Com dibuixar icones',\n      description1:\n        \"En el teu CSV crea una columna i posa-hi el nom de la icona que vols dibuixar. Pots deixar la cel·la buida quan no vulguis que es mostri per a certs punts. Quan la columna s'anomena\",\n      code: 'icon',\n      description2: \" kepler.gl automàticament crearà una capa d'icona.\",\n      example: 'Exemple:',\n      icons: 'Icones'\n    },\n    storageMapViewer: {\n      lastModified: 'Darrera modificació fa {lastUpdated}',\n      back: 'Enrere'\n    },\n    overwriteMap: {\n      title: 'Desant mapa...',\n      alreadyExists: 'ja existeix a {mapSaved}. El vols sobreescriure?'\n    },\n    loadStorageMap: {\n      back: 'Enrere',\n      goToPage: 'Ves a la pàgina {displayName} de Kepler.gl',\n      storageMaps: 'Emmagatzematge / Mapes',\n      noSavedMaps: 'Cap mapa desat encara'\n    }\n  },\n  header: {\n    visibleLayers: 'Capes visibles',\n    layerLegend: 'Llegenda de capes'\n  },\n  interactions: {\n    tooltip: 'Suggeriment',\n    brush: 'Pinzell',\n    coordinate: 'Coordenades',\n    geocoder: 'Geocodificador'\n  },\n  layerBlending: {\n    title: 'Combinació de capes',\n    additive: 'additiva',\n    normal: 'normal',\n    subtractive: 'substractiva'\n  },\n  columns: {\n    title: 'Columnes',\n    lat: 'lat',\n    lng: 'lon',\n    altitude: 'alçada',\n    icon: 'icona',\n    geojson: 'geojson',\n    arc: {\n      lat0: 'lat origen',\n      lng0: 'lng origen ',\n      lat1: 'lat destinació',\n      lng1: 'lng destinació'\n    },\n    line: {\n      alt0: 'alçada origen',\n      alt1: 'alçada destinació'\n    },\n    grid: {\n      worldUnitSize: 'Mida de malla (km)'\n    },\n    hexagon: {\n      worldUnitSize: \"Radi d'hexàgon (km)\"\n    },\n    hex_id: 'id hex'\n  },\n  color: {\n    customPalette: 'Paleta personalitzada',\n    steps: 'intervals',\n    type: 'tipus',\n    reversed: 'invertida'\n  },\n  scale: {\n    colorScale: 'Escala de color',\n    sizeScale: 'Escala de mides',\n    strokeScale: 'Escala de traç',\n    scale: 'Escala'\n  },\n  fileUploader: {\n    message: \"Arrossega i deixa anar l'arxiu aquí\",\n    chromeMessage:\n      '*usuari de Chrome: la mida màxima són 250mb, si has de carrgar un arxiu més gran fes servir Safari',\n    disclaimer:\n      '*kepler.gl és una aplicació a la banda client que no es recolza en cap servidor. Les dades només existeixen a la teva màquina/navegador. ' +\n      \"No s'envien dades ni mapes a cap servidor.\",\n    configUploadMessage:\n      'Carrega {fileFormatNames} o un mapa desat en **Json**. Més informació sobre [**supported file formats**]',\n    browseFiles: 'navega pels teus arxius',\n    uploading: 'Carregant',\n    fileNotSupported: \"L'arxiu {errorFiles} no és compatible.\",\n    or: 'o'\n  },\n  geocoder: {\n    title: 'Introdueix una adreça'\n  },\n  fieldSelector: {\n    clearAll: 'Treure tots',\n    formatting: 'Format'\n  },\n  compare: {\n    modeLabel: 'Mode Comparació',\n    typeLabel: 'Tipus de Comparació',\n    types: {\n      absolute: 'Absoluta',\n      relative: 'Relativa'\n    }\n  },\n  mapPopover: {\n    primary: 'Principal'\n  },\n  density: 'densitat',\n  'Bug Report': \"Informe d'errors\",\n  'User Guide': \"Guia d'usuari\",\n  Save: 'Desa',\n  Share: 'Comparteix'\n};\n"
  },
  {
    "path": "src/localization/src/translations/cn.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: '权重',\n    label: '标签',\n    fillColor: '填充色',\n    color: '颜色',\n    coverage: '覆盖范围',\n    strokeColor: '线条颜色',\n    radius: '半径',\n    outline: '轮廓线',\n    stroke: '线条粗细',\n    density: '密度',\n    height: '高度',\n    sum: '总和',\n    pointCount: '点数'\n  },\n  placeholder: {\n    search: '搜索',\n    selectField: '选择区域',\n    yAxis: 'Y轴',\n    selectType: '选择类型',\n    selectValue: '选择值',\n    enterValue: '输入值',\n    empty: '未选择'\n  },\n  misc: {\n    by: '',\n    valuesIn: '值包含',\n    valueEquals: '值等于',\n    dataSource: '数据源',\n    brushRadius: '画笔半径 (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: '图层',\n    label: '标签',\n    road: '道路',\n    border: '边界线',\n    building: '建筑物',\n    water: '水',\n    land: '地面',\n    '3dBuilding': '3D建筑'\n  },\n  panel: {\n    text: {\n      label: '标签',\n      labelWithId: '标签 {labelId}',\n      fontSize: '字体大小',\n      fontColor: '字体颜色',\n      textAnchor: '文本锚',\n      alignment: '对齐方式',\n      addMoreLabel: '添加更多标签'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: '图层',\n      filter: '过滤器',\n      interaction: '交互',\n      basemap: '底图'\n    }\n  },\n  layer: {\n    required: '必填*',\n    radius: '半径',\n    color: '颜色',\n    fillColor: '填充色',\n    outline: '轮廓线',\n    weight: '权重',\n    propertyBasedOn: '{property}的基准',\n    coverage: '覆盖范围',\n    stroke: '线条粗细',\n    strokeWidth: '线条宽度',\n    strokeColor: '线条颜色',\n    basic: '基础设置',\n    trailLength: '轨迹长度',\n    trailLengthDescription: '轨迹淡出的秒数',\n    newLayer: '新建图层',\n    elevationByDescription: '关闭时，高度取决于点数',\n    colorByDescription: '关闭时，颜色取决于点数',\n    aggregateBy: '{field}聚合如下: ',\n    '3DModel': '3D模型',\n    '3DModelOptions': '3D模型选项',\n    type: {\n      point: 'point',\n      arc: 'arc',\n      line: 'line',\n      grid: 'grid',\n      hexbin: 'hexbin',\n      polygon: 'polygon',\n      geojson: 'geojson',\n      cluster: 'cluster',\n      icon: 'icon',\n      heatmap: 'heatmap',\n      hexagon: 'hexagon',\n      hexagonid: 'H3',\n      trip: 'trip',\n      s2: 'S2',\n      '3d': '3D'\n    }\n  },\n  layerVisConfigs: {\n    angle: '角度',\n    strokeWidth: '线条宽度',\n    strokeWidthRange: '线条宽度范围',\n    radius: '半径',\n    fixedRadius: '以米为单位固定半径',\n    fixedRadiusDescription: '将半径映射到以米为单位的绝对半径（例: 5 → 5米）',\n    radiusRange: '半径范围',\n    clusterRadius: '聚类半径',\n    radiusRangePixels: '半径范围[像素]',\n    opacity: '透明度',\n    coverage: '覆盖范围',\n    outline: '轮廓',\n    colorRange: '色彩范围',\n    stroke: '线',\n    strokeColor: '线条颜色',\n    strokeColorRange: '线条色彩范围',\n    targetColor: '目标颜色',\n    colorAggregation: '颜色聚合',\n    heightAggregation: '高度聚合',\n    resolutionRange: '分辨率范围',\n    sizeScale: '大小比例',\n    worldUnitSize: '世界单位大小',\n    elevationScale: '海拔比例',\n    enableElevationZoomFactor: '使用高程缩放系数',\n    enableElevationZoomFactorDescription: '根据当前缩放系数调整海拔',\n    heightScale: '高度比例',\n    coverageRange: '覆盖范围',\n    highPrecisionRendering: '高精度渲染',\n    highPrecisionRenderingDescription: '高精度渲染会导致性能下降',\n    height: '高度',\n    heightDescription: '点击屏幕右上角的按钮切换到3D视图',\n    fill: '填充',\n    enablePolygonHeight: '启用多边形高度',\n    showWireframe: '显示线框',\n    weightIntensity: '加权强度',\n    zoomScale: '缩放比例',\n    heightRange: '高度范围'\n  },\n  layerManager: {\n    addData: '添加数据',\n    addLayer: '添加图层',\n    layerBlending: '混合图层'\n  },\n  mapManager: {\n    mapStyle: '地图样式',\n    addMapStyle: '添加地图样式',\n    '3dBuildingColor': '3D 建筑颜色'\n  },\n  layerConfiguration: {\n    defaultDescription: '根据所选字段计算 {property}',\n    howTo: '使用方法'\n  },\n  filterManager: {\n    addFilter: '添加过滤器'\n  },\n  datasetTitle: {\n    showDataTable: '显示数据表',\n    removeDataset: '删除数据集'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount}行'\n  },\n  tooltip: {\n    hideLayer: '隐藏图层',\n    showLayer: '显示图层',\n    hideFeature: '隐藏特征',\n    showFeature: '显示特征',\n    hide: '隐藏',\n    show: '显示',\n    removeLayer: '删除图层',\n    zoomToLayer: '缩放☞图层',\n    duplicateLayer: '复制图层',\n    layerSettings: '图层设置',\n    closePanel: '关闭当前面板',\n    switchToDualView: '切换到双地图视图',\n    showLegend: '显示图例',\n    disable3DMap: '禁用 3D 地图',\n    DrawOnMap: '在地图上绘制',\n    selectLocale: '选择语言',\n    showAiAssistantPanel: '显示 AI 助手面板',\n    hideAiAssistantPanel: '隐藏 AI 助手面板',\n    hideLayerPanel: '隐藏图层面板',\n    showLayerPanel: '显示图层面板',\n    moveToTop: '移至图层顶部',\n    selectBaseMapStyle: '选择底图样式',\n    delete: '删除',\n    timePlayback: '时空回放',\n    cloudStorage: '云存储',\n    '3DMap': '3D 地图',\n    animationByWindow: '移动时间窗口',\n    animationByIncremental: '增量时间窗口',\n    speed: '速度',\n    play: '播放',\n    pause: '暂停',\n    reset: '重置'\n  },\n  toolbar: {\n    exportImage: '导出图片',\n    exportData: '导出数据',\n    exportMap: '导出地图',\n    shareMapURL: '分享地图网址',\n    saveMap: '保存地图',\n    select: '选择',\n    polygon: 'polygon',\n    rectangle: 'rectangle',\n    hide: '隐藏',\n    show: '显示',\n    ...LOCALES\n  },\n  editor: {\n    filterLayer: '过滤图层',\n    copyGeometry: '复制几何图形'\n  },\n  modal: {\n    title: {\n      deleteDataset: '删除数据集',\n      addDataToMap: '添加数据到地图',\n      exportImage: '导出图片',\n      exportData: '导出数据',\n      exportMap: '导出地图',\n      addCustomMapboxStyle: '添加自定义地图',\n      saveMap: '保存地图',\n      shareURL: '分享网址'\n    },\n    button: {\n      delete: '删除',\n      download: '下载',\n      export: '出口',\n      addStyle: '添加样式',\n      save: '保存',\n      defaultCancel: '取消',\n      defaultConfirm: '确认'\n    },\n    exportImage: {\n      ratioTitle: '比率',\n      ratioDescription: '选择不同用途的比例。',\n      ratioOriginalScreen: '原始屏幕',\n      ratioCustom: '自定义',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: '分辨率',\n      resolutionDescription: '高分辨率更适合打印。',\n      resolutionPlaceholder: '选择分辨率...',\n      mapLegendTitle: '地图图例',\n      mapLegendAdd: '在地图上添加图例'\n    },\n    exportData: {\n      datasetTitle: '数据集',\n      datasetSubtitle: '选择要导出的数据集',\n      allDatasets: '全部',\n      dataTypeTitle: '数据类型',\n      dataTypeSubtitle: '选择要导出的数据类型',\n      filterDataTitle: '过滤数据',\n      filterDataSubtitle: '可以选择导出原始数据或过滤后的数据',\n      filteredData: '过滤数据',\n      unfilteredData: '元数据',\n      fileCount: '{fileCount} 个文件',\n      rowCount: '{rowCount} 行'\n    },\n    deleteData: {\n      warning: '确认要删除这个数据集。它会影响 {length} 个层'\n    },\n    addStyle: {\n      publishTitle:\n        '2. 如果在步骤1中输入了 mapbox 样式的 url，需要在 mapbox 上发布样式或提供访问令牌（access token）。（可选）',\n      publishSubtitle1: '可以在以下位置创建自己的地图样式',\n      publishSubtitle2: '并',\n      publishSubtitle3: '发布',\n      publishSubtitle4: '。',\n      publishSubtitle5: '使用私有样式，需粘贴',\n      publishSubtitle6: '访问令牌（access token）',\n      publishSubtitle7: '。* Kepler.gl 是一个客户端应用程序，数据保留在您的浏览器中。',\n      exampleToken: '例) pk.abcdefg.xxxxxx',\n      pasteTitle: '1. 粘贴样式 url',\n      pasteSubtitle0: '样式 url 可以是 Mapbox 的',\n      pasteSubtitle1: '什么是',\n      pasteSubtitle2: '样式 URL，',\n      pasteSubtitle3: '还可以使用遵从Mapbox GL样式的style.json的url：',\n      pasteSubtitle4: 'Mapbox GL 样式规范',\n      namingTitle: '3. 命名你的样式'\n    },\n    shareMap: {\n      shareUriTitle: '分享地图网址',\n      shareUriSubtitle: '生成分享地图的链接',\n      cloudTitle: '云存储',\n      cloudSubtitle: '登录并将地图数据上传到个人云存储',\n      shareDisclaimer:\n        'kepler.gl 将创建的地图存储在个人云存储中，因此只有知道 URL 的人才能访问地图及其数据。' +\n        '可以随时使用个人云存储帐户编辑/删除数据文件。',\n      gotoPage: '跳转到Kepler.gl的{currentProvider}页面'\n    },\n    statusPanel: {\n      mapUploading: '地图上传中',\n      error: '错误'\n    },\n    saveMap: {\n      title: '云存储',\n      subtitle: '登录以将地图保存到个人云存储'\n    },\n    exportMap: {\n      formatTitle: '地图的格式',\n      formatSubtitle: '选择导出地图的格式',\n      html: {\n        selection: '将地图导出至交互式的html文件中。',\n        tokenTitle: 'Mapbox的访问令牌（access token）',\n        tokenSubtitle: '在 html 中使用自己的 Mapbox 访问令牌（access token）（可选）',\n        tokenPlaceholder: '粘贴个人的 Mapbox 访问令牌access token）',\n        tokenMisuseWarning:\n          '* 如果您不提供自己的令牌，则在我们更换令牌时，地图可能随时无法显示，以免被滥用。',\n        tokenDisclaimer: '可以稍后使用以下说明更改 Mapbox 令牌：',\n        tokenUpdate: '如何更新现有的地图令牌。',\n        modeTitle: '地图模式',\n        modeSubtitle1: '选择地图模式。更多的',\n        modeSubtitle2: '信息',\n        modeDescription: '允许用户{mode}地图',\n        read: '阅读',\n        edit: '编辑'\n      },\n      json: {\n        configTitle: '地图配置',\n        configDisclaimer:\n          '地图配置将包含在 Json 文件中。如果您在自己的应用程序中使用 kepler.gl。您可以复制此配置并将其传递给',\n        selection:\n          '将当前地图数据和配置导出到单个 Json 文件中。稍后您可以通过将此文件上传到 kepler.gl 来打开同一张地图。',\n        disclaimer:\n          '* 地图配置与加载的数据集相结合。 “dataId”用于将图层、过滤器和工具提示绑定到特定数据集。' +\n          '将此配置传递给 addDataToMap 时，请确保数据集 ID 与此配置中的 dataId/s 匹配。'\n      }\n    },\n    loadingDialog: {\n      loading: '加载中...'\n    },\n    loadData: {\n      upload: '上传文件',\n      storage: '从存储中加载'\n    },\n    tripInfo: {\n      title: '如何启用移动动画',\n      description1:\n        '要路径设置动画，geoJSON 数据必须包含 `LineString` 作为要素几何。此外，LineString 的坐标有四个元素',\n      code: ' [经度，纬度，高程，时间戳] ',\n      description2:\n        '最后一个元素是时间戳。有效的时间戳格式包括以秒为单位的 unix，例如`1564184363`或以毫秒为单位的`1564184363000`。',\n      example: '例：'\n    },\n    iconInfo: {\n      title: '如何绘制图标',\n      description1:\n        '在您的 csv 中，创建一列，将您要绘制的图标的名称放入其中。如果不想在某些点上显示图标，可以将单元格留空。当列被命名为',\n      code: '图标',\n      description2: '时，kepler.gl 会自动为你创建一个图标层。',\n      example: '例:',\n      icons: '图标一览'\n    },\n    storageMapViewer: {\n      lastModified: '上次修改 {lastUpdated} 前',\n      back: '返回'\n    },\n    overwriteMap: {\n      title: '正在保存地图...',\n      alreadyExists: '已经存在于 {mapSaved} 中。你想覆盖吗？'\n    },\n    loadStorageMap: {\n      back: '返回',\n      goToPage: '跳转到 Kepler.gl 的 {displayName} 页面',\n      storageMaps: '存储 / 地図',\n      noSavedMaps: '还没有保存的地图'\n    }\n  },\n  header: {\n    visibleLayers: '可见图层',\n    layerLegend: '图层图例'\n  },\n  interactions: {\n    tooltip: '工具提示',\n    brush: '刷',\n    coordinate: '坐标',\n    geocoder: '地理编码器'\n  },\n  layerBlending: {\n    title: '图层混合',\n    additive: 'additive',\n    normal: 'normal',\n    subtractive: 'subtractive'\n  },\n  columns: {\n    title: '列',\n    lat: '纬度',\n    lng: '经度',\n    altitude: '海拔',\n    icon: '图标',\n    geojson: 'geojson',\n    token: '令牌',\n    arc: {\n      lat0: '起点 纬度',\n      lng0: '起点 经度',\n      lat1: '终点 纬度',\n      lng1: '终点 经度'\n    },\n    grid: {\n      worldUnitSize: '网格大小 (km)'\n    },\n    hexagon: {\n      worldUnitSize: '六边形半径 (km)'\n    },\n    hex_id: 'hex id'\n  },\n  color: {\n    customPalette: '自定义调色板',\n    steps: '步骤',\n    type: '类型',\n    reversed: '反转'\n  },\n  scale: {\n    colorScale: '色阶',\n    sizeScale: '大小比例',\n    strokeScale: '描边比例',\n    scale: '规模'\n  },\n  fileUploader: {\n    message: '将您的文件拖放到此处（可多个）',\n    chromeMessage:\n      '*对于 Chrome 用户：文件大小最大为 250mb。如果需要上传更多文件，请尝试使用 Safari。',\n    disclaimer:\n      '* kepler.gl 在客户端上工作。数据仅保留在您自己的设备/浏览器中。' +\n      '没有信息或地图数据被发送到任何服务器。',\n    configUploadMessage:\n      '上传 {fileFormatNames} 或保存的地图 **Json**。阅读更多关于[**支持的文件格式**]',\n    browseFiles: '浏览你的文件',\n    uploading: '上传',\n    fileNotSupported: '不支持文件 {errorFiles}。',\n    or: '或'\n  },\n  geocoder: {\n    title: '输入地址或坐标（例： 37.79,-122.40）'\n  },\n  fieldSelector: {\n    clearAll: '清除所有',\n    formatting: '格式化'\n  },\n  compare: {\n    modeLabel: '比较模式',\n    typeLabel: '比较类型',\n    types: {\n      absolute: '绝对',\n      relative: '相对'\n    }\n  },\n  mapPopover: {\n    primary: '主要'\n  },\n  density: '密度',\n  'Bug Report': '错误报告',\n  'User Guide': '用户指南',\n  Save: '保存',\n  Share: '分享'\n};\n"
  },
  {
    "path": "src/localization/src/translations/en.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'weight',\n    label: 'label',\n    fillColor: 'fill color',\n    color: 'color',\n    coverage: 'coverage',\n    strokeColor: 'stroke color',\n    radius: 'radius',\n    outline: 'outline',\n    stroke: 'stroke',\n    density: 'density',\n    height: 'height',\n    sum: 'sum',\n    pointCount: 'Point Count'\n  },\n  placeholder: {\n    search: 'Search',\n    selectField: 'Select a field',\n    yAxis: 'Y Axis',\n    selectType: 'Select A Type',\n    selectValue: 'Select A Value',\n    enterValue: 'Enter a value',\n    empty: 'empty',\n    selectLayer: 'Select a layer'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Values in',\n    valueEquals: 'Value equals',\n    dataSource: 'Data Source',\n    brushRadius: 'Brush Radius (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Map Layers',\n    label: 'Label',\n    road: 'Road',\n    border: 'Border',\n    building: 'Building',\n    water: 'Water',\n    land: 'Land',\n    '3dBuilding': '3d Building',\n    background: 'Background'\n  },\n  panel: {\n    text: {\n      label: 'label',\n      labelWithId: 'Label {labelId}',\n      fontSize: 'Font size',\n      fontColor: 'Font color',\n      backgroundColor: 'Background color',\n      textAnchor: 'Text anchor',\n      alignment: 'Alignment',\n      addMoreLabel: 'Add More Label',\n      outlineWidth: 'Outline width',\n      outlineColor: 'Outline color'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Layers',\n      filter: 'Filters',\n      interaction: 'Interactions',\n      basemap: 'Base map'\n    },\n    panelViewToggle: {\n      list: 'View List',\n      byDataset: 'View by Dataset'\n    }\n  },\n  layer: {\n    required: 'Required*',\n    columnModesSeparator: 'Or',\n    radius: 'Radius',\n    color: 'Color',\n    fillColor: 'Fill Color',\n    outline: 'Outline',\n    weight: 'Weight',\n    propertyBasedOn: '{property} based on',\n    coverage: 'Coverage',\n    stroke: 'Stroke',\n    strokeWidth: 'Stroke Width',\n    strokeColor: 'Stroke Color',\n    basic: 'Basic',\n    trailLength: 'Trail Length',\n    trailLengthDescription: 'Number of seconds for a path to completely fade out',\n    newLayer: 'new layer',\n    elevationByDescription: 'When off, height is based on count of points',\n    colorByDescription: 'When off, color is based on count of points',\n    aggregateBy: 'Aggregate {field} by',\n    '3DModel': '3D Model',\n    '3DModelOptions': '3D Model Options',\n    service: 'Service',\n    layer: 'Layer',\n    appearance: 'Appearance',\n    uniqueIdField: 'Unique ID Field',\n    type: {\n      point: 'point',\n      arc: 'arc',\n      line: 'line',\n      grid: 'grid',\n      hexbin: 'hexbin',\n      polygon: 'polygon',\n      geojson: 'geojson',\n      cluster: 'cluster',\n      icon: 'icon',\n      heatmap: 'heatmap',\n      hexagon: 'hexagon',\n      hexagonid: 'H3',\n      trip: 'trip',\n      s2: 'S2',\n      '3d': '3D',\n      vectortile: 'vector tile',\n      rastertile: 'raster tile',\n      wms: 'WMS'\n    },\n    wms: {\n      hover: 'Value:'\n    },\n    layerUpdateError:\n      'An error occurred during layer update: {errorMessage}. Make sure the format of the input data is valid.',\n    interaction: 'Interaction'\n  },\n  layerVisConfigs: {\n    angle: 'Angle',\n    strokeWidth: 'Stroke Width (Pixels)',\n    strokeWidthRange: 'Stroke Width Range',\n    radius: 'Radius',\n    fixedRadius: 'Fixed Radius to meter',\n    fixedRadiusDescription: 'Map radius to absolute radius in meters, e.g. 5 to 5 meters',\n    radiusRange: 'Radius Range',\n    clusterRadius: 'Cluster Radius in Pixels',\n    radiusRangePixels: 'Radius Range in pixels',\n    billboard: 'Billboard',\n    billboardDescription: 'Orient geometry towards the camera',\n    fadeTrail: 'Fade trail',\n    opacity: 'Opacity',\n    coverage: 'Coverage',\n    outline: 'Outline',\n    colorRange: 'Color range',\n    stroke: 'Stroke',\n    strokeColor: 'Stroke Color',\n    strokeColorRange: 'Stroke Color range',\n    targetColor: 'Target Color',\n    colorAggregation: 'Color Aggregation',\n    heightAggregation: 'Height Aggregation',\n    resolutionRange: 'Resolution range',\n    sizeScale: 'Size Scale',\n    worldUnitSize: 'World Unit Size',\n    elevationScale: 'Elevation Scale',\n    enableElevationZoomFactor: 'Use elevation zoom factor',\n    enableElevationZoomFactorDescription: 'Adjust height/elevation based on current zoom factor',\n    enableHeightZoomFactor: 'Use height zoom factor',\n    heightScale: 'Height Scale',\n    coverageRange: 'Coverage Range',\n    highPrecisionRendering: 'High Precision Rendering',\n    highPrecisionRenderingDescription: 'High precision will result in slower performance',\n    height: 'Height',\n    heightDescription: 'Click button at top right of the map to switch to 3d view',\n    fill: 'Fill',\n    enablePolygonHeight: 'Enable Polygon Height',\n    showWireframe: 'Show Wireframe',\n    weightIntensity: 'Weight Intensity',\n    zoomScale: 'Zoom Scale',\n    heightRange: 'Height Range',\n    heightMultiplier: 'Height Multiplier',\n    fixedHeight: 'Fixed height',\n    fixedHeightDescription: 'Use height without modifications',\n    allowHover: 'Allow Hover',\n    showNeighborOnHover: 'Highlight Neighbors On Hover',\n    showHighlightColor: 'Show highlight Color',\n    darkModeEnabled: 'Dark base map',\n    transparentBackground: 'Transparent Background'\n  },\n  layerManager: {\n    addData: 'Add Data',\n    addLayer: 'Add Layer',\n    layerBlending: 'Layer Blending',\n    overlayBlending: 'Overlay Blending'\n  },\n  mapManager: {\n    mapStyle: 'Map style',\n    addMapStyle: 'Add Map Style',\n    '3dBuildingColor': '3D Building Color',\n    backgroundColor: 'Background Color'\n  },\n  effectManager: {\n    effects: 'Effects',\n    addEffect: 'Add effect',\n    pickDateTime: 'Pick date/time',\n    currentTime: 'Current time',\n    pickCurrrentTime: 'Pick current time',\n    date: 'Date',\n    time: 'Time',\n    timezone: 'Timezone'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Calculate {property} based on selected field',\n    howTo: 'How to',\n    showColorChart: 'Show Color Chart',\n    hideColorChart: 'Hide Color Chart'\n  },\n  filterManager: {\n    addFilter: 'Add Filter',\n    timeFilterSync: 'Synced datasets',\n    timeLayerSync: 'Link with the layer timeline',\n    timeLayerUnsync: 'Unlink with the layer timeline',\n    column: 'Column'\n  },\n  datasetTitle: {\n    showDataTable: 'Show data table',\n    removeDataset: 'Remove dataset'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} rows',\n    vectorTile: 'Vector tile',\n    rasterTile: 'Raster tile',\n    wmsTile: 'WMS tile'\n  },\n  tooltip: {\n    hideLayer: 'Hide layer',\n    showLayer: 'Show layer',\n    hideFeature: 'Hide feature',\n    showFeature: 'Show feature',\n    hide: 'hide',\n    show: 'show',\n    removeLayer: 'Remove layer',\n    duplicateLayer: 'Duplicate layer',\n    zoomToLayer: 'Zoom to layer',\n    resetAfterError: 'Try to enable the layer after an error',\n    layerSettings: 'Layer settings',\n    closePanel: 'Close current panel',\n    switchToDualView: 'Switch to dual map view',\n    showLegend: 'Show legend',\n    disable3DMap: 'Disable 3D Map',\n    DrawOnMap: 'Draw on map',\n    selectLocale: 'Select locale',\n    showAiAssistantPanel: 'Show AI Assistant',\n    hideAiAssistantPanel: 'Hide AI Assistant',\n    hideLayerPanel: 'Hide layer panel',\n    showLayerPanel: 'Show layer panel',\n    moveToTop: 'Move to top of data layers',\n    selectBaseMapStyle: 'Select base map style',\n    removeBaseMapStyle: 'Remove base map style',\n    delete: 'Delete',\n    timePlayback: 'Time Playback',\n    timeFilterSync: 'Sync with a column from another dataset',\n    cloudStorage: 'Cloud Storage',\n    '3DMap': '3D Map',\n    animationByWindow: 'Moving Time Window',\n    animationByIncremental: 'Incremental Time Window',\n    speed: 'speed',\n    play: 'play',\n    pause: 'pause',\n    reset: 'reset',\n    export: 'export',\n    timeLayerSync: 'Link with the layer timeline',\n    timeLayerUnsync: 'Unlink with the layer timeline',\n    syncTimelineStart: 'Start of current filter timeframe',\n    syncTimelineEnd: 'End of current filter timeframe',\n    showEffectPanel: 'Show effect panel',\n    hideEffectPanel: 'Hide effect panel',\n    removeEffect: 'Remove effect',\n    disableEffect: 'Disable effect',\n    effectSettings: 'Effect settings'\n  },\n  toolbar: {\n    exportImage: 'Export Image',\n    exportData: 'Export Data',\n    exportMap: 'Export Map',\n    shareMapURL: 'Share Map URL',\n    saveMap: 'Save Map',\n    select: 'Select',\n    polygon: 'Polygon',\n    rectangle: 'Rectangle',\n    hide: 'Hide',\n    show: 'Show',\n    ...LOCALES\n  },\n  editor: {\n    filterLayer: 'Filter Layers',\n    filterLayerDisabled: 'Non-polygon geometries cannot be used for filtering',\n    copyGeometry: 'Copy Geometry',\n    noLayersToFilter: 'No layers to filter'\n  },\n\n  modal: {\n    title: {\n      deleteDataset: 'Delete Dataset',\n      addDataToMap: 'Add Data To Map',\n      exportImage: 'Export Image',\n      exportData: 'Export Data',\n      exportMap: 'Export Map',\n      addCustomMapboxStyle: 'Add Custom Map Style',\n      saveMap: 'Save Map',\n      shareURL: 'Share URL'\n    },\n    button: {\n      delete: 'Delete',\n      download: 'Download',\n      export: 'Export',\n      addStyle: 'Add Style',\n      save: 'Save',\n      defaultCancel: 'Cancel',\n      defaultConfirm: 'Confirm'\n    },\n    exportImage: {\n      ratioTitle: 'Ratio',\n      ratioDescription: 'Choose the ratio for various usages.',\n      ratioOriginalScreen: 'Original Screen',\n      ratioCustom: 'Custom',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Resolution',\n      resolutionDescription: 'High resolution is better for prints.',\n      resolutionPlaceholder: 'Select resolution...',\n      mapLegendTitle: 'Map Legend',\n      mapLegendAdd: 'Add legend on map'\n    },\n    exportData: {\n      datasetTitle: 'Dataset',\n      datasetSubtitle: 'Choose the datasets you want to export',\n      allDatasets: 'All',\n      dataTypeTitle: 'Data Type',\n      dataTypeSubtitle: 'Choose the type of data you want to export',\n      filterDataTitle: 'Filter Data',\n      filterDataSubtitle: 'You can choose exporting original data or filtered data',\n      filteredData: 'Filtered data',\n      unfilteredData: 'Unfiltered Data',\n      fileCount: '{fileCount} Files',\n      rowCount: '{rowCount} Rows',\n      tiledDatasetWarning: \"* Export Data for Tiled datasets isn't supported\"\n    },\n    deleteData: {\n      warning: 'you are going to delete this dataset. It will affect {length} layers'\n    },\n    addStyle: {\n      publishTitle:\n        '2. If entered mapbox style url in step.1, publish your style at mapbox or provide access token. (Optional)',\n      publishSubtitle1: 'You can create your own map style at',\n      publishSubtitle2: 'and',\n      publishSubtitle3: 'publish',\n      publishSubtitle4: 'it.',\n      publishSubtitle5: 'To use private style, paste your',\n      publishSubtitle6: 'access token',\n      publishSubtitle7:\n        'here. *kepler.gl is a client-side application, data stays in your browser..',\n      exampleToken: 'e.g. pk.abcdefg.xxxxxx',\n      pasteTitle: '1. Paste style url',\n      pasteSubtitle0: 'Style url can be a mapbox',\n      pasteSubtitle1: 'What is a',\n      pasteSubtitle2: 'style URL',\n      pasteSubtitle3: 'or a style.json using the',\n      pasteSubtitle4: 'Mapbox GL Style Spec',\n      namingTitle: '3. Name your style'\n    },\n    shareMap: {\n      title: 'Share Map',\n      shareUriTitle: 'Share Map Url',\n      shareUriSubtitle: 'Generate a map url to share with others',\n      cloudTitle: 'Cloud storage',\n      cloudSubtitle: 'Login and upload map data to your personal cloud storage',\n      shareDisclaimer:\n        'kepler.gl will save your map data to your personal cloud storage, only people with the URL can access your map and data. ' +\n        'You can edit/delete the data file in your cloud account anytime.',\n      gotoPage: 'Go to your Kepler.gl {currentProvider} page'\n    },\n    statusPanel: {\n      mapUploading: 'Map Uploading',\n      error: 'Error'\n    },\n    saveMap: {\n      title: 'Cloud storage',\n      subtitle: 'Login to save map to your personal cloud storage'\n    },\n    exportMap: {\n      formatTitle: 'Map format',\n      formatSubtitle: 'Choose the format to export your map to',\n      html: {\n        selection: 'Export your map into an interactive html file.',\n        tokenTitle: 'Mapbox access token',\n        tokenSubtitle: 'Use your own Mapbox access token in the html (optional)',\n        tokenPlaceholder: 'Paste your Mapbox access token',\n        tokenMisuseWarning:\n          '* If you do not provide your own token, the map may fail to display at any time when we replace ours to avoid misuse. ',\n        tokenSecurityWarning:\n          '* Warning: your Mapbox token will be embedded in the exported HTML file. Anyone with access to this file can see and use your token. Use a scoped token with URL restrictions when possible. ',\n        tokenDisclaimer: 'You can change the Mapbox token later using the following instructions: ',\n        tokenUpdate: 'How to update an existing map token.',\n        modeTitle: 'Map Mode',\n        modeSubtitle1: 'Select the app mode. More ',\n        modeSubtitle2: 'info',\n        modeDescription: 'Allow users to {mode} the map',\n        read: 'read',\n        edit: 'edit'\n      },\n      json: {\n        configTitle: 'Map Config',\n        configDisclaimer:\n          'Map config will be included in the Json file. If you are using kepler.gl in your own app. You can copy this config and pass it to ',\n        selection:\n          'Export current map data and config into a single Json file. You can later open the same map by uploading this file to kepler.gl.',\n        disclaimer:\n          '* Map config is coupled with loaded datasets. ‘dataId’ is used to bind layers, filters, and tooltips to a specific dataset. ' +\n          'When passing this config to addDataToMap, make sure the dataset id matches the dataId/s in this config.'\n      }\n    },\n    loadingDialog: {\n      loading: 'Loading...'\n    },\n    loadData: {\n      upload: 'Load Files',\n      tileset: 'Tileset',\n      storage: 'Load from Storage'\n    },\n    tripInfo: {\n      title: 'Create trips from GeoJson',\n      titleTable: 'Create trips from a list of points',\n      description1: `To animate the path, the GeoJSON data needs to contain \\`LineString\\` in its feature geometry, and the coordinates in the LineString need to have 4 elements in the formats of\n${'```json'}\n[longitude, latitude, altitude, timestamp]\n${'```'}\nThe 3rd element is a timestamp. Valid timestamp formats include unix in seconds such as \\`1564184363\\` or in milliseconds such as \\`1564184363000\\`.`,\n      descriptionTable1:\n        'Trips can be created by joining a list of points from latitude and longitude, sort by timestamps and group by uniq ids.',\n      example: 'Example GeoJSON',\n      exampleTable: 'Example Csv'\n    },\n    polygonInfo: {\n      title: 'Create polygon layer from GeoJSON feature',\n      titleTable: 'Create path from points',\n      description: `Polygon can be created from\n__1 .A GeoJSON Feature Collection__\n__2. A Csv contains geometry column__\n\n### 1. Create polygon from GeoJSON file\n\nWhen upload a GeoJSON file contains FeatureCollection, a polygon layer will be auto-created\n\nExample GeoJSON\n${'```json'}\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [{\n      \"type\": \"Feature\",\n      \"geometry\": {\n          \"type\": \"Point\",\n          \"coordinates\": [102.0, 0.5]\n      },\n      \"properties\": {\n          \"prop0\": \"value0\"\n      }\n  }, {\n      \"type\": \"Feature\",\n      \"geometry\": {\n          \"type\": \"LineString\",\n          \"coordinates\": [\n              [102.0, 0.0],\n              [103.0, 1.0],\n              [104.0, 0.0],\n              [105.0, 1.0]\n          ]\n      },\n      \"properties\": {\n        \"prop0\": \"value0\"\n      }\n  }]\n}\n${'```'}\n\n### 2. Create polygon from a Geometry column in Csv table\nGeometries (Polygons, Points, LindStrings etc) can be embedded into CSV as a \\`GeoJSON\\` or \\`WKT\\` formatted string.\n\n#### 2.1 \\`GeoJSON\\` string\nExample data.csv with \\`GeoJSON\\` string\n${'```txt'}\nid,_geojson\n1,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.158491,40.835947],[-74.157914,40.83902]]]}\"\n${'```'}\n\n#### 2.2 \\`WKT\\` string\nExample data.csv with \\`WKT\\` string\n[The Well-Known Text (WKT)](https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format) representation of geometry values is designed for exchanging geometry data in ASCII form.\n\nExample data.csv with WKT\n${'```txt'}\nid,_geojson\n1,\"POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))\"\n${'```'}\n`,\n      descriptionTable: `Paths can be created by joining a list of points from latitude and longitude, sort by an index field (e.g. timestamp) and group by uniq ids.\n\n  ### Layer columns:\n  - **id**: - *required*&nbsp;- A \\`id\\` column is used to group by points. Points with the same id will be joined into a single path.\n  - **lat**: - *required*&nbsp;- The latitude of the point\n  - **lon**: - *required*&nbsp;- The longitude of the point\n  - **alt**: - *optional*&nbsp;- The altitude of the point\n  - **sort by**: - *optional*&nbsp;- A \\`sort by\\` column is used to sort the points, if not specified, points will be sorted by row index.\n`,\n      exampleTable: 'Example CSV'\n    },\n    iconInfo: {\n      title: 'How to draw icons',\n      description1:\n        'In your csv, create a column, put the name of the icon you want to draw in it. You can leave the cell empty if you do not want the icon to show for some points. When the column is named',\n      code: 'icon',\n      description2: ' kepler.gl will automatically create a icon layer for you.',\n      example: 'Example:',\n      icons: 'Icons'\n    },\n    storageMapViewer: {\n      lastModified: 'Last modified {lastUpdated} ago',\n      back: 'Back'\n    },\n    overwriteMap: {\n      title: 'Saving map...',\n      alreadyExists: 'already exists in your {mapSaved}. Would you like to overwrite it?'\n    },\n    loadStorageMap: {\n      back: 'Back',\n      goToPage: 'Go to your Kepler.gl {displayName} page',\n      storageMaps: 'Storage / Maps',\n      noSavedMaps: 'No saved maps yet',\n      foursquareStorageMessage:\n        'Only maps saved with Kepler.gl > Save > Foursquare Storage option are shown here'\n    }\n  },\n  header: {\n    visibleLayers: 'Visible layers',\n    layerLegend: 'Legend'\n  },\n  interactions: {\n    tooltip: 'Tooltip',\n    brush: 'Brush',\n    coordinate: 'Coordinates',\n    geocoder: 'Geocoder'\n  },\n  layerBlending: {\n    title: 'Layer Blending',\n    additive: 'additive',\n    normal: 'normal',\n    subtractive: 'subtractive'\n  },\n  overlayBlending: {\n    title: 'Map overlay blending',\n    description: 'Blend layers with the base map so that both are visible.',\n    screen: 'dark base map',\n    normal: 'normal',\n    darken: 'light base map'\n  },\n  columns: {\n    title: 'Columns',\n    lat: 'lat',\n    lng: 'lng',\n    altitude: 'altitude',\n    alt: 'altitude',\n    id: 'id',\n    timestamp: 'time',\n    icon: 'icon',\n    geojson: 'geojson',\n    geoarrow: 'geoarrow',\n    geoarrow0: 'geoarrow source',\n    geoarrow1: 'geoarrow target',\n    token: 'token',\n    sortBy: 'sort by',\n    neighbors: 'neighbors',\n    arc: {\n      lat0: 'source lat or hex id',\n      lng0: 'source lng or hex id',\n      lat1: 'target lat or hex id',\n      lng1: 'target lng or hex id'\n    },\n    line: {\n      alt0: 'source altitude',\n      alt1: 'target altitude'\n    },\n    grid: {\n      worldUnitSize: 'Grid Size (km)'\n    },\n    hexagon: {\n      worldUnitSize: 'Hexagon Radius (km)'\n    },\n    hex_id: 'hex id'\n  },\n  color: {\n    customPalette: 'Custom Palette',\n    steps: 'Steps',\n    type: 'Type',\n    colorBlindSafe: 'Colorblind Safe',\n    reversed: 'Reversed',\n    disableStepReason: `Can't change number of steps with custom color breaks, use custom palette to edit steps`,\n    preset: 'Preset Colors',\n    picker: 'Color Picker'\n  },\n  scale: {\n    colorScale: 'Color Scale',\n    sizeScale: 'Size Scale',\n    strokeScale: 'Stroke Scale',\n    strokeColorScale: 'Stroke Color Scale',\n    scale: 'Scale'\n  },\n  fileUploader: {\n    message: 'Drag & Drop Your File(s) Here',\n    chromeMessage:\n      '*Chrome user: Limit file size to 250mb, if need to upload larger file, try Safari',\n    disclaimer:\n      '*kepler.gl is a client-side application with no server backend. Data lives only on your machine/browser. ' +\n      'No information or map data is sent to any server.',\n    configUploadMessage:\n      'Upload {fileFormatNames} or saved map **Json**. Read more about [**supported file formats**]',\n    browseFiles: 'browse your files',\n    uploading: 'Uploading',\n    fileNotSupported: 'File {errorFiles} is not supported.',\n    or: 'or'\n  },\n  tilesetSetup: {\n    header: 'Setup Vector Tiles',\n    rasterTileHeader: 'Setup Raster Tiles',\n    addTilesetText: 'Add Tileset'\n  },\n  geocoder: {\n    title: 'Enter an address or coordinates, ex 37.79,-122.40'\n  },\n  fieldSelector: {\n    clearAll: 'Clear All',\n    formatting: 'Formatting'\n  },\n  compare: {\n    modeLabel: 'Comparison Mode',\n    typeLabel: 'Comparison Type',\n    types: {\n      absolute: 'Absolute',\n      relative: 'Relative'\n    }\n  },\n  mapPopover: {\n    primary: 'Primary'\n  },\n  density: 'density',\n  'Bug Report': 'Bug Report',\n  'User Guide': 'User Guide',\n  Save: 'Save',\n  Share: 'Share',\n  mapLegend: {\n    layers: {\n      line: {\n        singleColor: {\n          sourceColor: 'Source',\n          targetColor: 'Target'\n        }\n      },\n      arc: {\n        singleColor: {\n          sourceColor: 'Source',\n          targetColor: 'Target'\n        }\n      },\n      default: {\n        singleColor: {\n          color: 'Fill color',\n          strokeColor: 'Outline'\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "src/localization/src/translations/es.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'peso',\n    label: 'etiqueta',\n    fillColor: 'color de relleno',\n    color: 'color',\n    coverage: 'cobertura',\n    strokeColor: 'color de trazo',\n    radius: 'radio',\n    outline: 'contorno',\n    stroke: 'trazo',\n    density: 'densidad',\n    height: 'altura',\n    sum: 'suma',\n    pointCount: 'Recuento de puntos'\n  },\n  placeholder: {\n    search: 'Busqueda',\n    selectField: 'Selecciona un campo',\n    yAxis: 'Eje Y',\n    selectType: 'Selecciona un Tipo',\n    selectValue: 'Selecciona un Valor',\n    enterValue: 'Entra un valor',\n    empty: 'vacio'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Valores en',\n    valueEquals: 'Valor igual a',\n    dataSource: 'Fuente de datos',\n    brushRadius: 'Radio del pincel (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Capas del mapa',\n    label: 'Etiqueta',\n    road: 'Carretera',\n    border: 'Frontera',\n    building: 'Edificio',\n    water: 'Agua',\n    land: 'Tierra',\n    '3dBuilding': 'Edificio 3D',\n    background: 'Fondo'\n  },\n  panel: {\n    text: {\n      label: 'etiqueta',\n      labelWithId: 'Etiqueta {labelId}',\n      fontSize: 'Tamaño de fuente',\n      fontColor: 'Color de fuente',\n      textAnchor: 'Anclaje del texto',\n      alignment: 'Alineación',\n      addMoreLabel: 'Añadir más etiquetas'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Capas',\n      filter: 'Filtros',\n      interaction: 'Interacciones',\n      basemap: 'Mapa base'\n    }\n  },\n  layer: {\n    required: 'Requerido*',\n    radius: 'Radio',\n    color: 'Color',\n    fillColor: 'Color de relleno',\n    outline: 'Contorno',\n    weight: 'Grueso',\n    propertyBasedOn: '{property} basado en',\n    coverage: 'Cobertura',\n    stroke: 'Trazo',\n    strokeWidth: 'Grosor de trazo',\n    strokeColor: 'Color de trazo',\n    basic: 'Básico',\n    trailLength: 'Longitud de pista',\n    trailLengthDescription: 'Numero de segundos hasta que desaparezca el camino',\n    newLayer: 'nueva capa',\n    elevationByDescription: 'Si desactivado, la altura se basa en el recuento de puntos',\n    colorByDescription: 'Si desactivado, el color se basa en el recuento de puntos',\n    aggregateBy: '{field} agregado por',\n    '3DModel': 'Modelo 3D',\n    '3DModelOptions': 'Opciones del modelo 3D',\n    type: {\n      point: 'punto',\n      arc: 'arco',\n      line: 'línea',\n      grid: 'malla',\n      hexbin: 'hexbin',\n      polygon: 'polígono',\n      geojson: 'geojson',\n      cluster: 'cluster',\n      icon: 'icono',\n      heatmap: 'concentración',\n      hexagon: 'hexágono',\n      hexagonid: 'H3',\n      trip: 'viaje',\n      s2: 'S2',\n      '3d': '3D'\n    },\n    layerUpdateError:\n      'Se produjo un error durante la actualización de la capa: {errorMessage}. Asegúrese de que el formato de los datos de entrada sea válido.'\n  },\n  layerVisConfigs: {\n    angle: 'Ángulo',\n    strokeWidth: 'Ancho del trazo',\n    strokeWidthRange: 'Rango del ancho del trazo',\n    radius: 'Radio',\n    fixedRadius: 'Radio fijo a medir',\n    fixedRadiusDescription: 'Ajustar el radio al radio absoluto en metros, p.e. 5 a 5 metros',\n    radiusRange: 'Rango de radio',\n    clusterRadius: 'Radio del cluster en píxeles',\n    radiusRangePixels: 'Rango del radio en píxeles',\n    billboard: 'Modo cartelera',\n    billboardDescription: 'Oriente la geometría hacia la cámara',\n    fadeTrail: 'Sendero de desvanecimiento',\n    opacity: 'Opacidad',\n    coverage: 'Cobertura',\n    outline: 'Contorno',\n    colorRange: 'Rango de color',\n    stroke: 'Trazo',\n    strokeColor: 'Color de trazo',\n    strokeColorRange: 'Rango de color de trazo',\n    targetColor: 'Color destino',\n    colorAggregation: 'Agregación de color',\n    heightAggregation: 'Agregación de la altura',\n    resolutionRange: 'Rango de resolución',\n    sizeScale: 'Medida de escala',\n    worldUnitSize: 'Medida de la unidad mundial',\n    elevationScale: 'Escala de elevación',\n    enableElevationZoomFactor: 'Usar factor de zoom de elevación',\n    enableElevationZoomFactorDescription:\n      'Ajuste la altura / elevación según el factor de zoom actual',\n    enableHeightZoomFactor: 'Usar factor de zoom de altura',\n    heightScale: 'Escala de altura',\n    coverageRange: 'Rango de cobertura',\n    highPrecisionRendering: 'Representación de alta precisión',\n    highPrecisionRenderingDescription: 'La precisión alta tendrá un rendimiento más bajo',\n    height: 'Altura',\n    heightDescription:\n      'Haz clic en el botón de arriba a la derecha del mapa per cambiar a vista 3D',\n    fill: 'Rellenar',\n    enablePolygonHeight: 'Activar la altura del polígono',\n    showWireframe: 'Muestra esquemàtico',\n    weightIntensity: 'Intensidad de peso',\n    zoomScale: 'Escala de zoom',\n    heightRange: 'Rango de alturas',\n    heightMultiplier: 'Multiplicador de altura',\n    fixedHeight: 'Altura fija',\n    fixedHeightDescription: 'Usar altura sin modificaciones'\n  },\n  layerManager: {\n    addData: 'Añadir datos',\n    addLayer: 'Añadir capa',\n    layerBlending: 'Combinar capas'\n  },\n  mapManager: {\n    mapStyle: 'Estilo de mapa',\n    addMapStyle: 'Añadir estilo de mapa',\n    '3dBuildingColor': 'Color edificios 3D',\n    backgroundColor: 'Color de fondo'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Calcular {property} según el campo seleccionado',\n    howTo: 'How to'\n  },\n  filterManager: {\n    addFilter: 'Añadir filtro'\n  },\n  datasetTitle: {\n    showDataTable: 'Mostar la tabla de datos',\n    removeDataset: 'Eliminar conjunto de datos'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} files'\n  },\n  tooltip: {\n    hideLayer: 'Ocultar la capa',\n    showLayer: 'Mostrar la capa',\n    hideFeature: 'Ocultar el objeto',\n    showFeature: 'Mostrar el objeto',\n    hide: 'Ocultar',\n    show: 'Mostrar',\n    removeLayer: 'Eliminar capa',\n    resetAfterError: 'Intente habilitar la capa después de un error',\n    layerSettings: 'Configuración de capa',\n    closePanel: 'Cerrar el panel actual',\n    switchToDualView: 'Cambiar a la vista de mapa dual',\n    showLegend: 'Mostrar leyenda',\n    disable3DMap: 'Desactivar mapa 3D',\n    DrawOnMap: 'Dibujar en el mapa',\n    selectLocale: 'Seleccionar configuración regional',\n    showAiAssistantPanel: 'Mostrar el panel de AI Assistant',\n    hideAiAssistantPanel: 'Ocultar el panel de AI Assistant',\n    hideLayerPanel: 'Ocultar la tabla de capas',\n    showLayerPanel: 'Mostrar la tabla  de capas',\n    moveToTop: 'Desplazar arriba de las capas de datos',\n    selectBaseMapStyle: 'Seleccionar estilo de mapa base',\n    delete: 'Borrar',\n    timePlayback: 'Reproducción de tiempo',\n    cloudStorage: 'Almacenaje en la nube',\n    '3DMap': 'Mapa 3D',\n    animationByWindow: 'Ventana Temporal Móvil',\n    animationByIncremental: 'Ventana Temporal Incremental',\n    speed: 'velocidad',\n    play: 'iniciar',\n    pause: 'pausar',\n    reset: 'reiniciar'\n  },\n  toolbar: {\n    exportImage: 'Exportar imagen',\n    exportData: 'Exportar datos',\n    exportMap: 'Exportar mapa',\n    shareMapURL: 'Compartir el enlace del mapa',\n    saveMap: 'Guardar mapa',\n    select: 'selecciona',\n    polygon: 'polígono',\n    rectangle: 'rectángulo',\n    hide: 'esconder',\n    show: 'mostrar',\n    ...LOCALES\n  },\n  modal: {\n    title: {\n      deleteDataset: 'Borrar conjunto de datos',\n      addDataToMap: 'Añadir datos al mapa',\n      exportImage: 'Exportar imagen',\n      exportData: 'Exportar datos',\n      exportMap: 'Exportar mapa',\n      addCustomMapboxStyle: 'Añadir estilo de Mapbox propio',\n      saveMap: 'Guardar mapa',\n      shareURL: 'Compartir enlace'\n    },\n    button: {\n      delete: 'Borrar',\n      download: 'Descargar',\n      export: 'Exportar',\n      addStyle: 'Añadir estilo',\n      save: 'Guardar',\n      defaultCancel: 'Cancelar',\n      defaultConfirm: 'Confirmar'\n    },\n    exportImage: {\n      ratioTitle: 'Ratio',\n      ratioDescription: 'Esoger ratio por diversos usos.',\n      ratioOriginalScreen: 'Pantalla original',\n      ratioCustom: 'Personalizado',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Resolución',\n      resolutionDescription: 'Una alta resolución es mejor para las impresiones.',\n      resolutionPlaceholder: 'Seleccionar resolución...',\n      mapLegendTitle: 'Leyenda del mapa',\n      mapLegendAdd: 'Añadir leyenda al mapa'\n    },\n    exportData: {\n      datasetTitle: 'Conjunto de datos',\n      datasetSubtitle: 'Escoger los conjuntos de datos a exportar',\n      allDatasets: 'Todos',\n      dataTypeTitle: 'Tipo de datos',\n      dataTypeSubtitle: 'Escoger el tipo de datos a exportar',\n      filterDataTitle: 'Filtrar datos',\n      filterDataSubtitle: 'Se puede escoger exportar los datos originales o filtrados',\n      filteredData: 'Datos filtrados',\n      unfilteredData: 'Datos sin filtrar',\n      fileCount: '{fileCount} Archivos',\n      rowCount: '{rowCount} Files'\n    },\n    deleteData: {\n      warning: 'estás a punto de borrar este conjunto de datos. Afectará a {length} capas'\n    },\n    addStyle: {\n      publishTitle: '1. Publicar tu estilo en Mapbox o proporcionar el token de acceso',\n      publishSubtitle1: 'Puedes crear el tu propio estilo de mapa en',\n      publishSubtitle2: 'y',\n      publishSubtitle3: 'publicar',\n      publishSubtitle4: 'lo.',\n      publishSubtitle5: 'Para utilizar un estilo privado, engancha tu',\n      publishSubtitle6: 'token de acceso',\n      publishSubtitle7:\n        'aquí. *kepler.gl es una aplicación cliente, los datos quedan en tu navegador..',\n      exampleToken: 'p.e. pk.abcdefg.xxxxxx',\n      pasteTitle: '2. Engancha el enlace del estilo',\n      pasteSubtitle1: 'Qué es un',\n      pasteSubtitle2: 'enlace del estilo',\n      namingTitle: '3. Poner nombre a tu estilo'\n    },\n    shareMap: {\n      shareUriTitle: 'Compartir el enlace del mapa',\n      shareUriSubtitle: 'Generar un enlace del mapa para compartir con otros',\n      cloudTitle: 'Almacenage en la nube',\n      cloudSubtitle: 'Acceder y cargar datos del mapa a tu almacenage a la nube personal',\n      shareDisclaimer:\n        'kepler.gl guardará los datos del mapa en el almacenage de tu nube personal, sólo quien tenga el enlace podra acceder al mapa y a los datos . ' +\n        'Puedes editar/borrar el archivo de datos en tu cuenta en la nube en cualquier momento.',\n      gotoPage: 'Ves a la página de {currentProvider} de Kepler.gl'\n    },\n    statusPanel: {\n      mapUploading: 'Cargar un mapa',\n      error: 'Error'\n    },\n    saveMap: {\n      title: 'Almacentage en la nube',\n      subtitle: 'Acceder para guardar el mapa en teu almacenage en la nube'\n    },\n    exportMap: {\n      formatTitle: 'Formato de mapa',\n      formatSubtitle: 'Escoger el formato al que se desea exportar el mapa',\n      html: {\n        selection: 'Exportar tu mapa como un archivo HTML interactivo.',\n        tokenTitle: 'Token de acceso de Mapbox',\n        tokenSubtitle: 'Utilizar tu token de acceso a Mapbox al archivo HTML (opcional)',\n        tokenPlaceholder: 'Enganchar tu token de acceso a Mapbox',\n        tokenMisuseWarning:\n          '* Si no proporcionas tu propio token, el mapa podría fallar en cualquier momento cuando reemplacemos nuestro token para evitar abusos. ',\n        tokenDisclaimer:\n          'Puedes cambiar el token de Mapbox posteriormente utilizando estas instrucciones: ',\n        tokenUpdate: 'Como actualitzar un token preexistente.',\n        modeTitle: 'Modo mapa',\n        modeSubtitle1: 'Seleccionar modo app. Más ',\n        modeSubtitle2: 'información',\n        modeDescription: 'Permmite a los usuarios {modo} el mapa',\n        read: 'leer',\n        edit: 'editar'\n      },\n      json: {\n        configTitle: 'Configuración del mapa',\n        configDisclaimer:\n          'La configuración del mapa será incluida en el archivo Json. Si utilitzas kepler.gl en tu propia app puedes copiar esta configuración y pasarla a  ',\n        selection:\n          'Exportar los datos del mapa y la configuración en un solo archivo Json. Posteriormente puedes abrir este mismo mapa cargando este mismo archivo a kepler.gl.',\n        disclaimer:\n          '* La configuración del mapa se combina con los conjuntos de datos cargados. ‘dataId’ se utiliza para vincular capas, filtros y sugerencias a un conjunto de datos específico. ' +\n          'Cuando pases esta configuración a addDataToMap, asegura que el identificador del conjunto de datos coincida con los ‘dataId’ de esta configuración.'\n      }\n    },\n    loadingDialog: {\n      loading: 'Cargando...'\n    },\n    loadData: {\n      upload: 'Cargar archivos',\n      storage: 'Cargar desde almacenage'\n    },\n    tripInfo: {\n      title: 'Como habilitar la animación de viaje',\n      description1:\n        'Para animar la ruta, los datos geoJSON han de contener `LineString` en su geometría y las coordenadas de LineString deben tener 4 elementos en los formats de ',\n      code: ' [longitude, latitude, altitude, timestamp] ',\n      description2:\n        'y el último elemento debe ser la marca del tiempo. Los formatos válidos para la marca de tiempo incluyen Unix en segundos como `1564184363` o en milisegundos como `1564184363000`.',\n      example: 'Ejemplo:'\n    },\n    iconInfo: {\n      title: 'Como dibujar íconos',\n      description1:\n        'En tu CSV crea una columna y pon el nombre del ícono que quieres dibujar. Puedes dejar la celda vacía cuando no quieras que se muestre para ciertos puntos. Cuando la columna se llama',\n      code: 'ícono',\n      description2: ' kepler.gl automáticamente creará una capa de ícono.',\n      example: 'Ejemplo:',\n      icons: 'Iconos'\n    },\n    storageMapViewer: {\n      lastModified: 'Última modificación hace {lastUpdated}',\n      back: 'Atrás'\n    },\n    overwriteMap: {\n      title: 'Guardando el mapa...',\n      alreadyExists: 'ja existe en {mapSaved}. Lo quieres sobreescrivir?'\n    },\n    loadStorageMap: {\n      back: 'Atrás',\n      goToPage: 'Ves a la página {displayName} de Kepler.gl',\n      storageMaps: 'Almancenage / Mapas',\n      noSavedMaps: 'No hay ningún mapa guardado todavía'\n    }\n  },\n  header: {\n    visibleLayers: 'Capas visibles',\n    layerLegend: 'Capa de leyenda'\n  },\n  interactions: {\n    tooltip: 'Sugerencias',\n    brush: 'Pincel',\n    coordinate: 'Coordenadas',\n    geocoder: 'Geocodificador'\n  },\n  layerBlending: {\n    title: 'Combinación de capas',\n    additive: 'aditiva',\n    normal: 'normal',\n    subtractive: 'substractiva'\n  },\n  columns: {\n    title: 'Columnas',\n    lat: 'lat',\n    lng: 'lon',\n    altitude: 'altura',\n    icon: 'ícono',\n    geojson: 'geojson',\n    arc: {\n      lat0: 'lat origen',\n      lng0: 'lng origen ',\n      lat1: 'lat destino',\n      lng1: 'lng destino'\n    },\n    line: {\n      alt0: 'altura origen',\n      alt1: 'altura destino'\n    },\n    grid: {\n      worldUnitSize: 'Tamaño de la malla (km)'\n    },\n    hexagon: {\n      worldUnitSize: 'Radio de hexágono (km)'\n    },\n    hex_id: 'id hex'\n  },\n  color: {\n    customPalette: 'Paleta personalizada',\n    steps: 'pasos',\n    type: 'tipo',\n    reversed: 'invertida'\n  },\n  scale: {\n    colorScale: 'Escala de color',\n    sizeScale: 'Escala de medidas',\n    strokeScale: 'Escala de trazo',\n    scale: 'Escala'\n  },\n  fileUploader: {\n    message: 'Arrastra y suelta el archivo aquí',\n    chromeMessage:\n      '*usuario de Chrome: la medida máxima son 250mb, si debes cargar un archivo más grande utiliza Safari',\n    disclaimer:\n      '*kepler.gl es una aplicación al lado cliente que no utiliza ningún servidor. Los datos sólo existen en tu máquina/navegador. ' +\n      'No se envian datos ni mapas a ningún servidor.',\n    configUploadMessage:\n      'Cargar {fileFormatNames} o un mapa guardado en **Json**. Más información sobre [**supported file formats**]',\n    browseFiles: 'navega por tus archivos',\n    uploading: 'Cargando',\n    fileNotSupported: 'El archivo {errorFiles} no es compatible.',\n    or: 'o'\n  },\n  geocoder: {\n    title: 'Introduce una dirección'\n  },\n  fieldSelector: {\n    clearAll: 'Quitar todos',\n    formatting: 'Formato'\n  },\n  compare: {\n    modeLabel: 'Modo Comparación',\n    typeLabel: 'Tipo de Comparación',\n    types: {\n      absolute: 'Absoluta',\n      relative: 'Relativa'\n    }\n  },\n  mapPopover: {\n    primary: 'Principal'\n  },\n  density: 'densidad',\n  'Bug Report': 'Informe de errores',\n  'User Guide': 'Guía de usuario',\n  Save: 'Guadar',\n  Share: 'Compartir'\n};\n"
  },
  {
    "path": "src/localization/src/translations/fi.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'painotus',\n    label: 'nimiö',\n    fillColor: 'täyttöväri',\n    color: 'väri',\n    strokeColor: 'viivan väri',\n    radius: 'säde',\n    outline: 'ääriviiva',\n    stroke: 'viiva',\n    density: 'tiheys',\n    coverage: 'kattavuus',\n    sum: 'summa',\n    pointCount: 'pisteiden lukumäärä'\n  },\n  placeholder: {\n    search: 'Etsi',\n    selectField: 'Valitse kenttä',\n    yAxis: 'Y-akseli',\n    selectType: 'Valitse tyyppi',\n    selectValue: 'Valitse arvo',\n    enterValue: 'Anna arvo',\n    empty: 'tyhjä'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Arvot joukossa:',\n    valueEquals: 'Arvo on yhtäsuuri kuin',\n    dataSource: 'Aineistolähde',\n    brushRadius: 'Harjan säde (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Kartan tasot',\n    label: 'Nimiöt',\n    road: 'Tiet',\n    border: 'Rajat',\n    building: 'Rakennukset',\n    water: 'Vesi',\n    land: 'Maa',\n    '3dBuilding': '3d-rakennukset',\n    background: 'Tausta'\n  },\n  panel: {\n    text: {\n      label: 'Nimiö',\n      labelWithId: 'Nimiö {labelId}',\n      fontSize: 'Fontin koko',\n      fontColor: 'Fontin väri',\n      textAnchor: 'Tekstin ankkuri',\n      alignment: 'Sijoittelu',\n      addMoreLabel: 'Lisää uusia nimiöitä'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Tasot',\n      filter: 'Suodattimet',\n      interaction: 'Interaktiot',\n      basemap: 'Taustakartta'\n    }\n  },\n  layer: {\n    required: 'Pakollinen*',\n    radius: 'Säde',\n    weight: 'Painotus',\n    propertyBasedOn: '{property} perustuen arvoon',\n    color: 'Väri',\n    fillColor: 'Täytön väri',\n    outline: 'ääriviiva',\n    coverage: 'Kattavuus',\n    stroke: 'Viiva',\n    strokeWidth: 'Viivan paksuus',\n    strokeColor: 'Viivan väri',\n    basic: 'Perus',\n    trailLength: 'Jäljen pituus',\n    trailLengthDescription: 'Jäljen kesto sekunteina, ennenkuin se himmenee näkyvistä',\n    newLayer: 'uusi taso',\n    elevationByDescription: 'Kun asetus on pois päältä, korkeus perustuu pisteiden määrään',\n    colorByDescription: 'Kun asetus on pois päältä, väri perustuu pisteiden määrään',\n    aggregateBy: 'Aggregoi kenttä {field} by',\n    '3DModel': '3D-malli',\n    '3DModelOptions': '3D-mallin asetukset',\n    type: {\n      point: 'piste',\n      arc: 'kaari',\n      line: 'viiva',\n      grid: 'ruudukko',\n      hexbin: 'hexbin',\n      polygon: 'polygoni',\n      geojson: 'geojson',\n      cluster: 'klusteri',\n      icon: 'kuva',\n      heatmap: 'lämpökartta',\n      hexagon: 'kuusikulmio',\n      hexagonid: 'H3',\n      trip: 'matka',\n      s2: 'S2',\n      '3d': '3D'\n    },\n    layerUpdateError:\n      'Tason päivityksen aikana tapahtui virhe: {errorMessage}. Varmista, että syötetietojen muoto on kelvollinen.'\n  },\n  layerVisConfigs: {\n    strokeWidth: 'Viivan paksuus',\n    strokeWidthRange: 'Viivan paksuuden rajat',\n    radius: 'Säde',\n    fixedRadius: 'Vakiosäde metreinä',\n    fixedRadiusDescription: 'Kartan säde absoluuttiseksi säteeksi metreinä, esim. 5 -> 5 metriin',\n    radiusRange: 'Säteen rajat',\n    clusterRadius: 'Klusterien säde pikseleinä',\n    radiusRangePixels: 'Säteen rajat pikseleinä',\n    billboard: 'Billboard -tila',\n    billboardDescription: 'Suuntaa geometria kameraa kohti',\n    fadeTrail: 'Häipyvä polku',\n    opacity: 'Läpinäkyvyys',\n    coverage: 'Kattavuus',\n    outline: 'Ääriviiva',\n    colorRange: 'Värien rajat',\n    stroke: 'Viiva',\n    strokeColor: 'Viivan väri',\n    strokeColorRange: 'Viivan värin rajat',\n    targetColor: 'Kohteen väri',\n    colorAggregation: 'Värien aggregointi',\n    heightAggregation: 'Korkeuden aggregointi',\n    resolutionRange: 'Resoluution rajat',\n    sizeScale: 'Koon skaala',\n    worldUnitSize: 'Yksikkö',\n    elevationScale: 'Korottamisen skaala',\n    enableElevationZoomFactor: 'Käytä korkeuden zoomauskerrointa',\n    enableElevationZoomFactorDescription:\n      'Säädä korkeus / korkeus nykyisen zoomauskertoimen perusteella',\n    enableHeightZoomFactor: 'Käytä korkeuden zoomauskerrointa',\n    heightScale: 'Korkeuden skaala',\n    coverageRange: 'Peittävyyden rajat',\n    highPrecisionRendering: 'Tarkka renderöinti',\n    highPrecisionRenderingDescription: 'Tarkka renderöinti johtaa hitaampaan suorittamiseen',\n    height: 'Korkeus',\n    heightDescription: 'Klikkaa oikeasta ylänurkasta nappia vaihtaaksesi 3D-näkymään',\n    fill: 'Täyttö',\n    enablePolygonHeight: 'Salli polygonien korkeus',\n    showWireframe: 'Näytä rautalankamalli',\n    weightIntensity: 'Painotuksen intensiteetti',\n    zoomScale: 'Zoomausskaala',\n    heightRange: 'Korkeuden rajat',\n    heightMultiplier: 'Korkeuskerroin',\n    fixedHeight: 'Kiinteä korkeus',\n    fixedHeightDescription: 'Käytä korkeutta ilman muutoksia'\n  },\n  layerManager: {\n    addData: 'Lisää aineisto',\n    addLayer: 'Lisää taso',\n    layerBlending: 'Tasojen sekoittuvuus'\n  },\n  mapManager: {\n    mapStyle: 'Kartan tyyli',\n    addMapStyle: 'Lisää tyyli kartalle',\n    '3dBuildingColor': '3D-rakennusten väri',\n    backgroundColor: 'Taustaväri'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Laske suureen {property} arvo valitun kentän perusteella',\n    howTo: 'Miten toimii'\n  },\n  filterManager: {\n    addFilter: 'Lisää suodatin'\n  },\n  datasetTitle: {\n    showDataTable: 'Näytä attribuuttitaulu',\n    removeDataset: 'Poista aineisto'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} riviä'\n  },\n  tooltip: {\n    hideLayer: 'Piilota taso',\n    showLayer: 'Näytä taso',\n    hideFeature: 'Piilota kohde',\n    showFeature: 'Näytä kohde',\n    hide: 'piilota',\n    show: 'näytä',\n    removeLayer: 'Poista taso',\n    resetAfterError: 'Yritä ottaa taso käyttöön virheen jälkeen',\n    layerSettings: 'Tason asetukset',\n    closePanel: 'Sulje paneeli',\n    switchToDualView: 'Vaihda kaksoiskarrtanäkymään',\n    showLegend: 'Näytä selite',\n    disable3DMap: 'Poistu 3D-näkymästä',\n    DrawOnMap: 'Piirrä kartalle',\n    selectLocale: 'Valitse kielisyys',\n    showAiAssistantPanel: 'Näytä AI-apuohjelman paneeli',\n    hideAiAssistantPanel: 'Piilota AI-apuohjelman paneeli',\n    hideLayerPanel: 'Piilota tasopaneeli',\n    showLayerPanel: 'Näytä tasopaneeli',\n    moveToTop: 'Siirrä tasojen päällimmäiseksi',\n    selectBaseMapStyle: 'Valitse taustakarttatyyli',\n    delete: 'Poista',\n    timePlayback: 'Ajan animointi',\n    cloudStorage: 'Pilvitallennus',\n    '3DMap': '3D-näkymä'\n  },\n  toolbar: {\n    exportImage: 'Vie kuva',\n    exportData: 'Vie aineistot',\n    exportMap: 'Vie kartta',\n    shareMapURL: 'Jaa kartan URL',\n    saveMap: 'Tallenna kartta',\n    select: 'valitse',\n    polygon: 'polygoni',\n    rectangle: 'nelikulmio',\n    hide: 'piilota',\n    show: 'näytä',\n    ...LOCALES\n  },\n  modal: {\n    title: {\n      deleteDataset: 'Poista aineisto',\n      addDataToMap: 'Lisää aineistoja kartalle',\n      exportImage: 'Vie kuva',\n      exportData: 'Vie aineistot',\n      exportMap: 'Vie kartta',\n      addCustomMapboxStyle: 'Lisää oma Mapbox-tyyli',\n      saveMap: 'Tallenna kartta',\n      shareURL: 'Jaa URL'\n    },\n    button: {\n      delete: 'Poista',\n      download: 'Lataa',\n      export: 'Vie',\n      addStyle: 'Lisää tyyli',\n      save: 'Tallenna',\n      defaultCancel: 'Peru',\n      defaultConfirm: 'Vahvista'\n    },\n    exportImage: {\n      ratioTitle: 'Kuvasuhde',\n      ratioDescription: 'Valitse sopiva kuvasuhde käyttötapaustasi varten.',\n      ratioOriginalScreen: 'Alkuperäinen näyttö',\n      ratioCustom: 'Kustomoitu',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Resoluutio',\n      resolutionDescription: 'Korkea resoluutio on parempi tulostamista varten.',\n      resolutionPlaceholder: 'Valitse resoluutio...',\n      mapLegendTitle: 'Kartan selite',\n      mapLegendAdd: 'Lisää selite karttaan'\n    },\n    exportData: {\n      datasetTitle: 'Aineistot',\n      datasetSubtitle: 'Valitse aineisto, jonka aiot viedä',\n      allDatasets: 'Kaikki',\n      dataTypeTitle: 'Aineistojen formaatti',\n      dataTypeSubtitle: 'Valitse aineistoformaatti valitsemillesi aineistoille',\n      filterDataTitle: 'Suodata aineistoja',\n      filterDataSubtitle: 'Voit viedä joko alkuperäiset aineistot tai suodatetut aineistot',\n      filteredData: 'Suodatetut aineistot',\n      unfilteredData: 'Suodattamattomat aineistot',\n      fileCount: '{fileCount} tiedostoa',\n      rowCount: '{rowCount} riviä'\n    },\n    deleteData: {\n      warning: 'aiot poistaa tämän aineiston. Aineostoa käyttävien tasojen lukumäärä: {length}'\n    },\n    addStyle: {\n      publishTitle: '1. Julkaise tyylisi Mapboxissa tai anna tunniste',\n      publishSubtitle1: 'Voit luoda oman karttatyylisi sivulla',\n      publishSubtitle2: 'ja',\n      publishSubtitle3: 'julkaista',\n      publishSubtitle4: 'sen.',\n      publishSubtitle5: 'Käyttääksesi yksityistä tyyliä, liitä',\n      publishSubtitle6: 'tunnisteesi',\n      publishSubtitle7:\n        'tänne. *kepler.gl on client-side sovellus, data pysyy vain selaimessasi...',\n      exampleToken: 'esim. pk.abcdefg.xxxxxx',\n      pasteTitle: '2. Liitä tyyli-URL',\n      pasteSubtitle1: 'Mikä on',\n      pasteSubtitle2: 'tyyli-URL?',\n      namingTitle: '3. Nimeä tyylisi'\n    },\n    shareMap: {\n      shareUriTitle: 'Jaa kartan URL',\n      shareUriSubtitle: 'Luo kartalle URL, jonka voit jakaa muiden kanssa',\n      cloudTitle: 'Pilvitallennus',\n      cloudSubtitle:\n        'Kirjaudu sisään ja lataa kartta ja aineistot henkilökohtaiseen pilvipalveluun',\n      shareDisclaimer:\n        'kepler.gl tallentaa kartan datan henkilökohtaiseen pilvitallennustilaasi, vain ihmiset, joilla on URL, voivat päästä käsiksi karttaan ja aineistoihin. ' +\n        'Voit muokata tiedostoja tai poistaa ne pilvipalvelustasi milloin vain.',\n      gotoPage: 'Mene Kepler.gl {currentProvider} sivullesi'\n    },\n    statusPanel: {\n      mapUploading: 'Karttaa ladataan',\n      error: 'Virhe'\n    },\n    saveMap: {\n      title: 'Pilvitallennus',\n      subtitle: 'Kirjaudu sisään pilvipalveluusi tallentaaksesi kartan'\n    },\n    exportMap: {\n      formatTitle: 'Kartan formaatti',\n      formatSubtitle: 'Valitse formaatti, jossa viet kartan',\n      html: {\n        selection: 'Vie kartta interaktiivisena html-tiedostona',\n        tokenTitle: 'Mapbox-tunniste',\n        tokenSubtitle: 'Käytä omaa Mapbox-tunnistettasi html-tiedostossa (valinnainen)',\n        tokenPlaceholder: 'Liitä Mapbox-tunnisteesi',\n        tokenMisuseWarning:\n          '* Jos et käytä omaa tunnistettasi, kartta voi lakata toimimasta milloin vain kun vaihdamme omaa tunnistettamme väärinkäytön estämiseksi. ',\n        tokenDisclaimer: 'Voit vaihtaa Mapbox-tunnisteesi näiden ohjeiden avulla: ',\n        tokenUpdate: 'Kuinka vaihtaa olemassaoleva Mapbox-tunniste',\n        modeTitle: 'Kartan tila',\n        modeSubtitle1: 'Valitse kartan tila.',\n        modeSubtitle2: 'Lisätietoja',\n        modeDescription: 'Anna käyttäjien {mode} karttaa',\n        read: 'lukea',\n        edit: 'muokata'\n      },\n      json: {\n        configTitle: 'Kartan asetukset',\n        configDisclaimer:\n          'Kartan asetukset sisältyvät Json-tiedostoon. Jos käytät kirjastoa kepler.gl omassa sovelluksessasi. Voit kopioida asetukset ja antaa ne funktiolle: ',\n        selection:\n          'Vie kyseisen kartan aineistot ja asetukset yhdessä json-tiedostossa. Voit myöhemmin avata saman kartan lataamalla tiedoston kepler.gl:n',\n        disclaimer:\n          '* Kartan asetukset ovat sidoksissa ladattuihin aineistoihin. Arvoa ‘dataId’ käytetään tasojen, suodattimien ja vihjeiden liittämiseksi tiettyyn aineistoon. ' +\n          'Varmista, että aineiston dataId:t vastaavat asetusten arvoja jos lataat asetukset käyttäen `addDataToMap`-funktiolle.'\n      }\n    },\n    loadingDialog: {\n      loading: 'Ladataan...'\n    },\n    loadData: {\n      upload: 'Lataa tiedostot',\n      storage: 'Lataa tallennustilasta'\n    },\n    tripInfo: {\n      title: 'Kuinka käyttää matka-animaatiota',\n      description1:\n        'Reitin animoimiseksi geoJSON-aineiston täytyy olla geometriatyypiltään `LineString`, LineString-koordinaattien täytyy sisältää 4 elementtiä formaatissa:',\n      code: ' [pituusaste, leveysaste, korkeus, aikaleima] ',\n      description2:\n        'siten, että viimeinen elementti on aikaleima. Aikaleima voi olla muodoltaan unix-sekunteja, kuten `1564184363` tai millisekunteja, kuten `1564184363000`.',\n      example: 'Esimerkki:'\n    },\n    iconInfo: {\n      title: 'Miten piirtää kuvia',\n      description1:\n        'csv-tiedostossasi, luo sarake nimeltä icon. Voit jättää sen tyhjäksi jos et halua piirtää kuvaa joillain pisteillä. Kun sarakkeen nimi on ',\n      code: 'icon',\n      description2: ' kepler.gl luo automaattisesti kuvatason sinua varten.',\n      example: 'Esimerkki:',\n      icons: 'Kuvat'\n    },\n    storageMapViewer: {\n      lastModified: 'Viimeksi muokattu {lastUpdated} sitten',\n      back: 'Takaisin'\n    },\n    overwriteMap: {\n      title: 'Tallennetaan karttaa...',\n      alreadyExists: 'on jo {mapSaved}:ssa. Haluatko ylikirjoittaa sen?'\n    },\n    loadStorageMap: {\n      back: 'Takaisin',\n      goToPage: 'Mene Kepler.gl {displayName} sivullesi',\n      storageMaps: 'Tallennus / Kartat',\n      noSavedMaps: 'Ei tallennettuja karttoja vielä'\n    }\n  },\n  header: {\n    visibleLayers: 'Näkyvissä olevat tasot',\n    layerLegend: 'Tason selite'\n  },\n  interactions: {\n    tooltip: 'Vihje',\n    brush: 'Harja',\n    coordinate: 'Koordinaatit'\n  },\n  layerBlending: {\n    title: 'Tasojen sekoittuvuus',\n    additive: 'lisäävä',\n    normal: 'normaali',\n    subtractive: 'vähentävä'\n  },\n  columns: {\n    title: 'Sarakkeet',\n    lat: 'lat',\n    lng: 'lng',\n    altitude: 'korkeus',\n    icon: 'kuva',\n    geojson: 'geojson',\n    arc: {\n      lat0: 'lähdön lat',\n      lng0: 'lähdön lng',\n      lat1: 'kohteen lat',\n      lng1: 'kohteen lng'\n    },\n    line: {\n      alt0: 'lähteen korkeus',\n      alt1: 'kohde korkeus'\n    },\n    grid: {\n      worldUnitSize: 'Ruutujen koko (km)'\n    },\n    hexagon: {\n      worldUnitSize: 'Hexagonien säde (km)'\n    }\n  },\n  color: {\n    customPalette: 'Mukautettu paletti',\n    steps: 'askeleet',\n    type: 'tyyppi',\n    reversed: 'käänteinen'\n  },\n  scale: {\n    colorScale: 'Värin skaala',\n    sizeScale: 'Koon skaala',\n    strokeScale: 'Viivan paksuuden skaala',\n    scale: 'Skaala'\n  },\n  fileUploader: {\n    message: 'Raahaa ja pudota tiedostosi tänne',\n    chromeMessage:\n      '*Chromen käyttäjä: Rajoita tiedostokokosi 250Mb:hen. Jos haluat suurempia tiedostoja, kokeile Safaria',\n    disclaimer:\n      '*kepler.gl on client-side sovellus, data pysyy vain selaimessasi...' +\n      'Tietoja ei lähetetä palvelimelle.',\n    configUploadMessage:\n      'Lisää {fileFormatNames} tai tallennettu kartta **Json**. Lue lisää [**tuetuista formaateista**]',\n    browseFiles: 'selaa tiedostojasi',\n    uploading: 'ladataan',\n    fileNotSupported: 'Tiedosto {errorFiles} ei ole tuettu.',\n    or: 'tai'\n  },\n  density: 'tiheys',\n  'Bug Report': 'Bugiraportointi',\n  'User Guide': 'Opas',\n  Save: 'Tallenna',\n  Share: 'Jaa'\n};\n"
  },
  {
    "path": "src/localization/src/translations/ja.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: '重み',\n    label: 'ラベル',\n    fillColor: '塗りつぶしの色',\n    color: '色',\n    coverage: 'カバー率',\n    strokeColor: '線の色',\n    radius: '半径',\n    outline: '輪郭線',\n    stroke: '線の太さ',\n    density: '密度',\n    height: '高さ',\n    sum: '合計',\n    pointCount: '点の数'\n  },\n  placeholder: {\n    search: '検索',\n    selectField: 'フィールドを選択',\n    yAxis: 'Y軸',\n    selectType: 'タイプを選択',\n    selectValue: '値を選択',\n    enterValue: '値を入力',\n    empty: '未選択'\n  },\n  misc: {\n    by: '',\n    valuesIn: '値が以下に含まれる',\n    valueEquals: '値が以下に等しい',\n    dataSource: 'データソース',\n    brushRadius: 'ブラシ半径 (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: '地図レイヤ',\n    label: 'ラベル',\n    road: '道路',\n    border: '境界線',\n    building: '建物',\n    water: '水',\n    land: '地面',\n    '3dBuilding': '3D建物',\n    background: '背景'\n  },\n  panel: {\n    text: {\n      label: 'ラベル',\n      labelWithId: 'ラベル {labelId}',\n      fontSize: '文字サイズ',\n      fontColor: '文字色',\n      textAnchor: '文字左右',\n      alignment: '文字上下',\n      addMoreLabel: 'ラベルを追加'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'レイヤ',\n      filter: 'フィルター',\n      interaction: 'インタラクション',\n      basemap: 'ベースマップ'\n    }\n  },\n  layer: {\n    required: '必須*',\n    radius: '半径',\n    color: '色',\n    fillColor: '塗りつぶしの色',\n    outline: '輪郭線',\n    weight: '重み',\n    propertyBasedOn: '{property}の基準',\n    coverage: 'カバー率',\n    stroke: '線',\n    strokeWidth: '線の太さ',\n    strokeColor: '線の色',\n    basic: '基本設定',\n    trailLength: '痕跡の長さ',\n    trailLengthDescription: '痕跡が完全に消えるまでの秒数',\n    newLayer: '新しいレイヤ',\n    elevationByDescription: 'オフの場合、高さは点の数に応じて決まります',\n    colorByDescription: 'オフの場合、色は点の数に応じて決まります',\n    aggregateBy: '{field}を以下で集計: ',\n    '3DModel': '3Dモデル',\n    '3DModelOptions': '3Dモデルのオプション',\n    type: {\n      point: 'point',\n      arc: 'arc',\n      line: 'line',\n      grid: 'grid',\n      hexbin: 'hexbin',\n      polygon: 'polygon',\n      geojson: 'geojson',\n      cluster: 'cluster',\n      icon: 'icon',\n      heatmap: 'heatmap',\n      hexagon: 'hexagon',\n      hexagonid: 'H3',\n      trip: 'trip',\n      s2: 'S2',\n      '3d': '3D'\n    }\n  },\n  layerVisConfigs: {\n    angle: '角度',\n    strokeWidth: '線の太さ (ピクセル)',\n    strokeWidthRange: '線の太さの範囲',\n    radius: '半径',\n    fixedRadius: '半径をメートルで固定',\n    fixedRadiusDescription: '半径をメートル単位の絶対半径に変換します（例: 5 → 5メートル）',\n    radiusRange: '半径の範囲',\n    clusterRadius: 'クラスターの範囲[ピクセル]',\n    radiusRangePixels: '半径の範囲[ピクセル]',\n    billboard: 'ビルボードモード',\n    billboardDescription: 'ジオメトリをカメラに向けます',\n    fadeTrail: 'フェージングパス',\n    opacity: '不透明度',\n    coverage: 'カバー率',\n    outline: '輪郭線',\n    colorRange: '色の範囲',\n    stroke: '線',\n    strokeColor: '線の色',\n    strokeColorRange: '線の色の範囲',\n    targetColor: 'Targetの色',\n    colorAggregation: '色の集計',\n    heightAggregation: '高さの集計',\n    resolutionRange: '解像度の範囲',\n    sizeScale: 'サイズのスケール',\n    worldUnitSize: 'World Unit Size',\n    elevationScale: '標高のスケール',\n    enableElevationZoomFactor: '標高ズーム係数を使用する',\n    enableElevationZoomFactorDescription: '現在のズーム率に基づいて高さ/標高を調整します',\n    enableHeightZoomFactor: '高さズーム係数を使用する',\n    heightScale: '高さのスケール',\n    coverageRange: 'カバー率の範囲',\n    highPrecisionRendering: '高精度レンダリング',\n    highPrecisionRenderingDescription: '高精度にすると速度は低下します',\n    height: '高さ',\n    heightDescription: '3Dビューに切り替えるには画面右上のボタンをクリックします',\n    fill: '塗りつぶし',\n    enablePolygonHeight: 'ポリゴンの高さを有効にする',\n    showWireframe: 'ワイヤーフレームを表示',\n    weightIntensity: '重みづけの強さ',\n    zoomScale: 'ズームのスケール',\n    heightRange: '高さの範囲',\n    heightMultiplier: '高さ乗数',\n    fixedHeight: '固定高さ',\n    fixedHeightDescription: '高さを変更せずに使用する'\n  },\n  layerManager: {\n    addData: 'データ追加',\n    addLayer: 'レイヤ追加',\n    layerBlending: 'レイヤのブレンド'\n  },\n  mapManager: {\n    mapStyle: 'マップスタイル',\n    addMapStyle: 'マップスタイル追加',\n    '3dBuildingColor': '3D建物の色',\n    backgroundColor: '背景色'\n  },\n  layerConfiguration: {\n    defaultDescription: '選択されたフィールドに基づいて{property}を計算します',\n    howTo: '使い方'\n  },\n  filterManager: {\n    addFilter: 'フィルター追加'\n  },\n  datasetTitle: {\n    showDataTable: 'データ表を表示',\n    removeDataset: 'データセットを削除'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount}行'\n  },\n  tooltip: {\n    hideLayer: 'レイヤを非表示',\n    showLayer: 'レイヤを表示',\n    hideFeature: 'フィーチャーを非表示',\n    showFeature: 'フィーチャーを表示',\n    hide: '非表示にする',\n    show: '表示する',\n    removeLayer: 'レイヤを削除',\n    duplicateLayer: 'レイヤを複製',\n    layerSettings: 'レイヤ設定',\n    closePanel: 'このパネルを閉じる',\n    switchToDualView: 'デュアルビューに切り替え',\n    showLegend: '凡例を表示',\n    disable3DMap: '3D地図を無効化',\n    DrawOnMap: '地図上に図形を描画',\n    selectLocale: '言語設定',\n    showAiAssistantPanel: 'AI 助手パネルを表示',\n    hideAiAssistantPanel: 'AI 助手パネルを非表示',\n    hideLayerPanel: 'レイヤパネルを非表示',\n    showLayerPanel: 'レイヤパネルを表示',\n    moveToTop: 'データレイヤの手前に移動',\n    selectBaseMapStyle: 'ベースマップのスタイルを選択',\n    delete: '削除',\n    timePlayback: '時系列で再生',\n    cloudStorage: 'クラウドストレージ',\n    '3DMap': '3D地図',\n    animationByWindow: '時間枠を移動',\n    animationByIncremental: '時間枠を増加',\n    speed: '速度',\n    play: '再生',\n    pause: '一時停止',\n    reset: 'リセット'\n  },\n  toolbar: {\n    exportImage: '画像を出力',\n    exportData: 'データを出力',\n    exportMap: '地図を出力',\n    shareMapURL: '地図のURLを共有',\n    saveMap: '地図を保存',\n    select: '選択',\n    polygon: 'ポリゴン',\n    rectangle: '長方形',\n    hide: '非表示',\n    show: '表示',\n    ...LOCALES\n  },\n  modal: {\n    title: {\n      deleteDataset: 'データセットを削除',\n      addDataToMap: '地図にデータを追加',\n      exportImage: '画像を出力',\n      exportData: 'データを出力',\n      exportMap: '地図を出力',\n      addCustomMapboxStyle: 'カスタムマップスタイルを追加',\n      saveMap: '地図を保存',\n      shareURL: 'URLを共有'\n    },\n    button: {\n      delete: '削除',\n      download: 'ダウンロード',\n      export: '出力',\n      addStyle: 'スタイル追加',\n      save: '保存',\n      defaultCancel: 'キャンセル',\n      defaultConfirm: '確認'\n    },\n    exportImage: {\n      ratioTitle: '縦横比',\n      ratioDescription: '用途に適した縦横比を選択します。',\n      ratioOriginalScreen: '元のスクリーンサイズ',\n      ratioCustom: 'カスタム',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: '解像度',\n      resolutionDescription: '印刷には高解像度が適しています。',\n      resolutionPlaceholder: '解像度を選択...',\n      mapLegendTitle: '地図の凡例',\n      mapLegendAdd: '地図に判例を追加'\n    },\n    exportData: {\n      datasetTitle: 'データセット',\n      datasetSubtitle: 'エクスポートしたいデータセットを選択します',\n      allDatasets: '全て',\n      dataTypeTitle: 'データ形式',\n      dataTypeSubtitle: 'エクスポートしたいデータ形式を選択します',\n      filterDataTitle: 'データのフィルタ',\n      filterDataSubtitle:\n        '元データ（フィルタなし）とフィルタ済データのどちらをエクスポートするか選択します',\n      filteredData: 'フィルタ済データ',\n      unfilteredData: '元データ',\n      fileCount: '{fileCount}個のファイル',\n      rowCount: '{rowCount}行'\n    },\n    deleteData: {\n      warning: 'このデータセットを削除します。{length}個のレイヤに影響します。'\n    },\n    addStyle: {\n      publishTitle:\n        '2. ステップ1でMapboxのスタイルURLを指定した場合、Mapboxでスタイルを公開するか、アクセストークンを以下に入力します（オプション）',\n      publishSubtitle1: '独自のスタイルを',\n      publishSubtitle2: 'で作成し、',\n      publishSubtitle3: '公開',\n      publishSubtitle4: 'することができます',\n      publishSubtitle5: '非公開のスタイルを使用するには、自身の',\n      publishSubtitle6: 'アクセストークン',\n      publishSubtitle7:\n        'をここに入力します。*kepler.glはクライアント上で動作するため、データは自身のブラウザに保持されます。',\n      exampleToken: '例) pk.abcdefg.xxxxxx',\n      pasteTitle: '1. スタイルのURLをペースト',\n      pasteSubtitle0: 'スタイルのURLはMapboxの',\n      pasteSubtitle1: 'What is a',\n      pasteSubtitle2: 'スタイルURL',\n      pasteSubtitle3: 'を指定するか、Mapbox GLの仕様に沿ったstyle.jsonのURLを指定します：',\n      pasteSubtitle4: 'Mapbox GL スタイル仕様',\n      namingTitle: '3. スタイルの名称を設定'\n    },\n    shareMap: {\n      shareUriTitle: '地図のURLを共有',\n      shareUriSubtitle: '共有用に地図のURLを生成',\n      cloudTitle: 'クラウドストレージ',\n      cloudSubtitle: 'ログインして地図データを個人用クラウドストレージにアップロード',\n      shareDisclaimer:\n        'kepler.glは作成した地図をあなたのクラウドストレージに保存するため、そのURLを知っている人のみが地図やそのデータにアクセス可能です。' +\n        'クラウドストレージのアカウントでいつでもデータファイルを編集/削除することができます。',\n      gotoPage: 'Kepler.glの{currentProvider}ページに移動'\n    },\n    statusPanel: {\n      mapUploading: '地図をアップロード中',\n      error: 'エラー'\n    },\n    saveMap: {\n      title: 'クラウドストレージ',\n      subtitle: '地図を個人用クラウドストレージに保存するためにログインする'\n    },\n    exportMap: {\n      formatTitle: '地図の形式',\n      formatSubtitle: '地図の出力形式を選択します',\n      html: {\n        selection: '地図をインタラクティブなHTMLファイルとして出力します。',\n        tokenTitle: 'Mapboxアクセストークン',\n        tokenSubtitle: 'HTMLファイルで自分のMapboxアクセストークンを使用します (オプション)',\n        tokenPlaceholder: '自分のMapboxアクセストークンをここに貼り付け',\n        tokenMisuseWarning:\n          '* 自分のトークンを使用しない場合は、デフォルトのトークンが悪用防止のために更新され、地図が表示されなくなる可能性があります。  ',\n        tokenDisclaimer: 'Mapboxのトークンは下記の方法に従って後から変更することも可能です：',\n        tokenUpdate: '既存の地図のトークンを更新する方法',\n        modeTitle: '地図のモード',\n        modeSubtitle1: '地図のモードを選択します。詳細は',\n        modeSubtitle2: 'こちら',\n        modeDescription: 'ユーザーに地図の{mode}を許可',\n        read: '閲覧',\n        edit: '編集'\n      },\n      json: {\n        configTitle: '地図の設定',\n        configDisclaimer:\n          '地図の設定はjsonファイルに収められます。他のアプリケーションでkepler.glを使用する場合、この設定をコピーペーストすることが可能です：',\n        selection:\n          '現在の地図データと設定を単一のjsonファイルに出力します。このファイルをkepler.glにアップロードすることで、同じ地図を後から開くことが可能になります。',\n        disclaimer:\n          '* 地図の設定は読み込まれたデータセットとセットになっています。‘dataId’によってレイヤ、フィルター、ツールチップは特定のデータセットに紐づけられます。 ' +\n          'この設定をaddDataToMapに渡す際は、データセットIDがこの設定内のdataIdと一致するようにしてください。'\n      }\n    },\n    loadingDialog: {\n      loading: 'ロード中...'\n    },\n    loadData: {\n      upload: 'ファイルをロード',\n      storage: 'ストレージからロード'\n    },\n    tripInfo: {\n      title: '移動アニメーションを有効にする方法',\n      description1:\n        '経路をアニメーション化するには、geoJSONデータはfeatureのgeometryとして `LineString` を含む必要があります。また、LineStringの座標は4つの要素を',\n      code: ' [経度, 緯度, 標高, timestamp] ',\n      description2:\n        'という形式（最後にタイムスタンプを含む）で保持する必要があります。タイムスタンプの形式は、 UNIX時間の秒単位（例: `1564184363`）またはミリ秒単位（例: `1564184363000`）が有効です。',\n      example: '例：'\n    },\n    iconInfo: {\n      title: 'アイコンの描画方法',\n      description1:\n        'CSVファイルに列を作成し、描画したいアイコンの名称を記載します。アイコンの描画が不要な点があれば、セルを空白にすることも可能です。列名が',\n      code: 'icon',\n      description2: 'の場合、kepler.glは自動的にアイコンレイヤを作成します。',\n      example: '例:',\n      icons: 'アイコン一覧'\n    },\n    storageMapViewer: {\n      lastModified: '最終編集：{lastUpdated} 前',\n      back: '戻る'\n    },\n    overwriteMap: {\n      title: '地図を保存中...',\n      alreadyExists: '既に{mapSaved}に存在します。上書きしますか？'\n    },\n    loadStorageMap: {\n      back: '戻る',\n      goToPage: 'Kepler.glの{displayName}ページに移動',\n      storageMaps: 'ストレージ / 地図',\n      noSavedMaps: '保存済の地図はまだありません'\n    }\n  },\n  header: {\n    visibleLayers: '表示中のレイヤ',\n    layerLegend: 'レイヤ判例'\n  },\n  interactions: {\n    tooltip: 'ツールチップ',\n    brush: 'ブラシ',\n    coordinate: '座標',\n    geocoder: 'ジオコーダー'\n  },\n  layerBlending: {\n    title: 'レイヤのブレンド',\n    additive: 'additive',\n    normal: 'normal',\n    subtractive: 'subtractive'\n  },\n  columns: {\n    title: '列',\n    lat: '緯度',\n    lng: '経度',\n    altitude: '標高',\n    icon: 'アイコン',\n    geojson: 'geojson',\n    token: 'トークン',\n    arc: {\n      lat0: '出発 緯度',\n      lng0: '出発 経度',\n      lat1: '到着 緯度',\n      lng1: '到着 経度'\n    },\n    grid: {\n      worldUnitSize: 'グリッドサイズ (km)'\n    },\n    hexagon: {\n      worldUnitSize: '六角形の半径 (km)'\n    },\n    hex_id: 'hex id'\n  },\n  color: {\n    customPalette: 'カスタムパレット',\n    steps: '段階数',\n    type: 'タイプ',\n    reversed: '反転'\n  },\n  scale: {\n    colorScale: 'カラースケール',\n    sizeScale: 'サイズのスケール',\n    strokeScale: '線のスケール',\n    scale: 'スケール'\n  },\n  fileUploader: {\n    message: 'ここにファイルをドロップ（複数可）',\n    chromeMessage:\n      '*Chromeユーザーの場合: ファイルサイズは250mbまでにしてください。それ以上のファイルをアップロードする必要がある場合、Safariを試してください。',\n    disclaimer:\n      '*kepler.glはクライアント上で動作します。データは自身の機器・ブラウザにのみ保持されます。' +\n      '情報や地図データは、いかなるサーバーにも送信されません。',\n    configUploadMessage:\n      '{fileFormatNames} または保存済地図の**Json**をアップロードします。詳細は以下を参照してください：[**対応ファイル形式**]',\n    browseFiles: 'デバイスのファイルを選択',\n    uploading: 'アップロード中',\n    fileNotSupported: '{errorFiles} はサポートされていないファイルです。',\n    or: 'or'\n  },\n  geocoder: {\n    title: '住所または座標を入力（例： 37.79,-122.40）'\n  },\n  fieldSelector: {\n    clearAll: '全て解除',\n    formatting: '値の形式'\n  },\n  compare: {\n    modeLabel: '比較モード',\n    typeLabel: '比較方式',\n    types: {\n      absolute: '絶対',\n      relative: '相対'\n    }\n  },\n  mapPopover: {\n    primary: 'プライマリ'\n  },\n  density: 'density',\n  'Bug Report': 'バグを報告',\n  'User Guide': 'ユーザーガイド',\n  Save: '保存',\n  Share: '共有'\n};\n"
  },
  {
    "path": "src/localization/src/translations/pt.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'Espessura do texto',\n    label: 'Rótulo',\n    fillColor: 'Cor do preenchimento',\n    color: 'Cor',\n    strokeColor: 'Cor da borda',\n    radius: 'Raio',\n    outline: 'Contorno',\n    stroke: 'Traçado',\n    density: 'Densidade',\n    height: 'Altura',\n    sum: 'Soma',\n    pointCount: 'Contagem de Pontos'\n  },\n  placeholder: {\n    search: 'Pesquisar',\n    selectField: 'Selecione um campo',\n    yAxis: 'Eixo Y',\n    selectType: 'Selecione um Tipo',\n    selectValue: 'Selecione um valor',\n    enterValue: 'Insira um valor',\n    empty: 'Vazio'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Valores em',\n    valueEquals: 'Valor igual a',\n    dataSource: 'Origem dos dados',\n    brushRadius: 'Raio do Traço (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Camadas do mapa',\n    label: 'Rótulo',\n    road: 'Estrada',\n    border: 'Fronteira',\n    building: 'Edifícios',\n    water: 'Água',\n    land: 'Terra',\n    '3dBuilding': 'Edifícios em 3d',\n    background: 'Fundo'\n  },\n  panel: {\n    text: {\n      label: 'Rótulo',\n      labelWithId: 'Rótulo {labelId}',\n      fontSize: 'Tamanho da fonte',\n      fontColor: 'Cor da fonte',\n      textAnchor: 'Âncora do texto',\n      alignment: 'Alinhamento',\n      addMoreLabel: 'Adicionar mais Rótulos'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Camadas',\n      filter: 'Filtros',\n      interaction: 'Interações',\n      basemap: 'Mapa base'\n    }\n  },\n  layer: {\n    required: 'Obrigatório*',\n    radius: 'Raio',\n    color: 'Cor',\n    fillColor: 'Cor de preenchimento',\n    outline: 'Contorno',\n    weight: 'Espessura',\n    propertyBasedOn: '{property} baseada em',\n    coverage: 'Cobertura',\n    stroke: 'Traço',\n    strokeWidth: 'Largura do Traçado',\n    strokeColor: 'Cor do Traçado',\n    basic: 'Básico',\n    trailLength: 'Comprimento da trilha',\n    trailLengthDescription: 'Número de segundos para um caminho completamente desaparecer',\n    newLayer: 'nova camada',\n    elevationByDescription: 'Quando desligado, a altura é baseada na contagem de pontos',\n    colorByDescription: 'Quando desligado, a cor é baseada na contagem de pontos',\n    aggregateBy: '{field} agregado por',\n    '3DModel': 'Modelo 3D',\n    '3DModelOptions': 'Opções do Modelo 3D',\n    type: {\n      point: 'ponto',\n      arc: 'arco',\n      line: 'linha',\n      grid: 'grade',\n      hexbin: 'hexágono',\n      polygon: 'polígono',\n      geojson: 'geojson',\n      cluster: 'grupo',\n      icon: 'icon',\n      heatmap: 'mapa de calor',\n      hexagon: 'hexágono',\n      hexagonid: 'H3',\n      trip: 'viagem',\n      s2: 'S2',\n      '3d': '3D'\n    },\n    layerUpdateError:\n      'Ocorreu um erro ao atualizar a camada: {errorMessage}. Certifique-se de que o formato dos dados de entrada seja válido.'\n  },\n  layerVisConfigs: {\n    strokeWidth: 'Largura do Traço',\n    strokeWidthRange: 'Alcance da Largura do Traço',\n    radius: 'Raio',\n    fixedRadius: 'Raio ajustado para metro',\n    fixedRadiusDescription: 'Raio do Mapa para Raio absoluto em metros, e.g. 5 para 5 metros',\n    radiusRange: 'Alcance do Raio',\n    clusterRadius: 'Raio do Agrupamento em pixels',\n    radiusRangePixels: 'Alcance do Raio em pixels',\n    billboard: 'Câmera de rosto',\n    billboardDescription: 'Oriente a geometria em direção à câmera',\n    fadeTrail: 'Fade trilha',\n    opacity: 'Opacidade',\n    coverage: 'Cobertura',\n    outline: 'Contorno',\n    colorRange: 'Alcance da Cor',\n    stroke: 'Traçado',\n    strokeColor: 'Cor do Traçado',\n    strokeColorRange: 'Alcance da Cor do Traçado',\n    targetColor: 'Cor de destino',\n    colorAggregation: 'Agregação da Cor',\n    heightAggregation: 'Agregação da Altura',\n    resolutionRange: 'Alcance da Resolução',\n    sizeScale: 'Escala de tamanho',\n    worldUnitSize: 'Tamanho unitário do mundo',\n    elevationScale: 'Escala de Elevação',\n    enableElevationZoomFactor: 'Use fator de zoom de elevação',\n    enableElevationZoomFactorDescription:\n      'Ajuste a altura / elevação com base no fator de zoom atual',\n    enableHeightZoomFactor: 'Usar fator de zoom de altura',\n    heightScale: 'Escala de Altura',\n    coverageRange: 'Alcance de cobertura',\n    highPrecisionRendering: 'Renderização de Alta Precisão',\n    highPrecisionRenderingDescription: 'Alta precisão irá em resultar em baixa performance',\n    height: 'Altura',\n    heightDescription:\n      'Clique no botão no canto superior direito para trocar para a visualização 3d',\n    fill: 'Preenchimento',\n    enablePolygonHeight: 'Habilitar Altura de Polígono',\n    showWireframe: 'Mostrar Wireframe',\n    weightIntensity: 'Intensidade da Espessura',\n    zoomScale: 'Escala do Zoom',\n    heightRange: 'Alcance da Altura',\n    heightMultiplier: 'Multiplicador de altura',\n    fixedHeight: 'Altura fixa',\n    fixedHeightDescription: 'Use a altura sem modificações'\n  },\n  layerManager: {\n    addData: 'Adicionar Dados',\n    addLayer: 'Adicionar Camada',\n    layerBlending: 'Mistura de Camada'\n  },\n  mapManager: {\n    mapStyle: 'Estilo do Mapa',\n    addMapStyle: 'Adicionar Estilo de Mapa',\n    '3dBuildingColor': 'Cor do Edifício 3D',\n    backgroundColor: 'Cor de Fundo'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Calcular {property} baseada no campo selecionado',\n    howTo: 'Como'\n  },\n  filterManager: {\n    addFilter: 'Adicionar Filtro'\n  },\n  datasetTitle: {\n    showDataTable: 'Mostrar tabela de dados',\n    removeDataset: 'Remover tabela de dados'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} linhas'\n  },\n  tooltip: {\n    hideLayer: 'esconder camada',\n    showLayer: 'mostrar camada',\n    hideFeature: 'Esconder propriedade',\n    showFeature: 'Mostrar propriedade',\n    hide: 'esconder',\n    show: 'mostrar',\n    removeLayer: 'Remover Camada',\n    resetAfterError: 'Tente habilitar a camada após um erro',\n    layerSettings: 'Configurações de Camada',\n    closePanel: 'Fechar painel atual',\n    switchToDualView: 'Trocar para visualização dupla de mapa',\n    showLegend: 'mostrar legenda',\n    disable3DMap: 'Desabilitar Mapa 3D',\n    DrawOnMap: 'Desenhar no mapa',\n    selectLocale: 'Selecionar língua',\n    showAiAssistantPanel: 'Mostrar painel de AI Assistant',\n    hideAiAssistantPanel: 'Esconder painel de AI Assistant',\n    hideLayerPanel: 'Esconder painel de camada',\n    showLayerPanel: 'Mostrar painel de camada',\n    moveToTop: 'Mover para o topo das camadas',\n    selectBaseMapStyle: 'Selecionar o Estilo do Mapa Base',\n    delete: 'Deletar',\n    timePlayback: 'Tempo de reprodução',\n    cloudStorage: 'Armazenamento Cloud',\n    '3DMap': ' Mapa 3D'\n  },\n  toolbar: {\n    exportImage: 'Exportar Imagem',\n    exportData: 'Exportar Dados',\n    exportMap: 'Exportar Mapa',\n    shareMapURL: 'Compartilhar URL do Mapa',\n    saveMap: 'Salvar Mapa',\n    select: 'selecionar',\n    polygon: 'polígono',\n    rectangle: 'retângulo',\n    hide: 'esconder',\n    show: 'mostrar',\n    ...LOCALES\n  },\n  modal: {\n    title: {\n      deleteDataset: 'Deletar Conjunto de Dados',\n      addDataToMap: 'Adicionar Dados ao Mapa',\n      exportImage: 'Exportar Imagem',\n      exportData: 'Exportar Dados',\n      exportMap: 'Exportar Mapa',\n      addCustomMapboxStyle: 'Adicionar Estilo Mapbox Customizado',\n      saveMap: 'Salvar Mapa',\n      shareURL: 'Compartilhar URL'\n    },\n    button: {\n      delete: 'Deletar',\n      download: 'Download',\n      export: 'Exportar',\n      addStyle: 'Adicionar Estilo',\n      save: 'Salvar',\n      defaultCancel: 'Cancelar',\n      defaultConfirm: 'Confirmar'\n    },\n    exportImage: {\n      ratioTitle: 'Proporção',\n      ratioDescription: 'Escolha a proporção para vários usos.',\n      ratioOriginalScreen: 'Tela Original',\n      ratioCustom: 'Customizado',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Resolução',\n      resolutionDescription: 'Alta resolução é melhor para impressões.',\n      resolutionPlaceholder: 'Selecionar resolução...',\n      mapLegendTitle: 'Legenda do Mapa',\n      mapLegendAdd: 'Adicionar Legenda no mapa'\n    },\n    exportData: {\n      datasetTitle: 'Conjunto de dados',\n      datasetSubtitle: 'Escolha o conjunto de dados que você quer exportar',\n      allDatasets: 'Todos',\n      dataTypeTitle: 'Tipo de Dado',\n      dataTypeSubtitle: 'Escolha o tipo de dados que você quer exportar',\n      filterDataTitle: 'Filtrar Dados',\n      filterDataSubtitle: 'Você pode escolher exportar os dados originais ou os dados filtrados',\n      filteredData: 'Dados Filtrados',\n      unfilteredData: 'Dados não filtrados',\n      fileCount: '{fileCount} Arquivos',\n      rowCount: '{rowCount} Linhas'\n    },\n    deleteData: {\n      warning: 'você irá deletar esse conjunto de dados. Isso irá afetar {length} camadas'\n    },\n    addStyle: {\n      publishTitle: '1. Publique o seu Estilo no Mapbox ou providencie a chave de acesso',\n      publishSubtitle1: 'Você pode criar o seu próprio estilo em',\n      publishSubtitle2: 'e',\n      publishSubtitle3: 'publicar',\n      publishSubtitle4: 'isso.',\n      publishSubtitle5: 'Para utilizar estilo privado, cole a sua',\n      publishSubtitle6: 'chave de acesso',\n      publishSubtitle7:\n        'aqui. *kepler.gl é uma aplicação client-side, os dados permanecem no seu browser..',\n      exampleToken: 'e.g. pk.abcdefg.xxxxxx',\n      pasteTitle: '2. Cole a url do seu estilo',\n      pasteSubtitle1: 'O que é uma',\n      pasteSubtitle2: 'URL de estilo',\n      namingTitle: '3. Nomeie o seu estilo'\n    },\n    shareMap: {\n      shareUriTitle: 'Compartilhar a URL do Mapa',\n      shareUriSubtitle: 'Gerar a url do mapa e compartilhar com outros',\n      cloudTitle: 'Armazenamento Cloud',\n      cloudSubtitle: 'Conecte-se e envie os dados do mapa para o seu armazenamento cloud pessoal',\n      shareDisclaimer:\n        'kepler.gl irá salvar os dados do mapa em seu armazenamento cloud pessoal, apenas pessoas com a URL podem acessar o seu mapa e dados. ' +\n        'Você pode editar/deletar o arquivo de dados na sua conta de armazenamento cloud em qualquer momento.',\n      gotoPage: 'Vá para a sua página Kepler.gl {currentProvider}'\n    },\n    statusPanel: {\n      mapUploading: 'Enviando Mapa',\n      error: 'Erro'\n    },\n    saveMap: {\n      title: 'Armazenamento Cloud',\n      subtitle: 'Conecte-se para salvar o mapa para o seu armazenamento cloud pessoal'\n    },\n    exportMap: {\n      formatTitle: 'Formato do mapa',\n      formatSubtitle: 'Escolher o formato de mapa para exportar',\n      html: {\n        selection: 'Exportar seu mapa em um arquivo html interativo.',\n        tokenTitle: 'Chave de acesso do Mapbox',\n        tokenSubtitle: 'Use a sua própria chave de acesso Mapbox em seu arquivo html (opcional)',\n        tokenPlaceholder: 'Cole a sua chave de acesso Mapbox',\n        tokenMisuseWarning:\n          '* Se você não fornecer a sua própria chave de acesso, o mapa pode falhar em exibir a qualquer momento quando nós substituirmos a nossa para evitar mau uso. ',\n        tokenDisclaimer:\n          'Você pode trocar a sua chave de acesso Mapbox mais tarde utizando as instruções seguintes: ',\n        tokenUpdate: 'Como atualizar a chave de acesso de um mapa existente.',\n        modeTitle: 'Modo do Mapa',\n        modeSubtitle1: 'Selecionar o modo do aplicativo. Mais ',\n        modeSubtitle2: 'info',\n        modeDescription: 'Permitir usuários a {mode} o mapa',\n        read: 'ler',\n        edit: 'editar'\n      },\n      json: {\n        configTitle: 'Configurações do Mapa',\n        configDisclaimer:\n          'A configuração do mapa será incluida no arquivo Json. Se você está utilizando kepler.gl no seu próprio mapa. Você pode copiar essa configuração e passar para ele ',\n        selection:\n          'Exportar atuais dados e configurações do mapa em um único arquivo Json. Você pode mais tarde abrir o mesmo mapa enviando esse arquivo para o kepler.gl.',\n        disclaimer:\n          '* Configuração do mapa é aclopado com conjunto de dados carregados. ‘dataId’ é utilizado para ligar as camadas, filtros, e dicas de contextos a conjunto de dados expecíficos. ' +\n          'Quando passando essa configuração para addDataToMap, tenha certeza de que o id do conjunto de dados combina com o(s) dataId/s nessa configuração.'\n      }\n    },\n    loadingDialog: {\n      loading: 'Carregando...'\n    },\n    loadData: {\n      upload: 'Carregar arquivo',\n      storage: 'Carregar do armazenamento'\n    },\n    tripInfo: {\n      title: 'Como habilitar animação de viagem',\n      description1:\n        'Para animar o caminho, o dado geoJSON deve conter `LineString` na sua propriedade geometry, e as coordenadas na LineString devem ter 4 elementos no seguinte formato',\n      code: ' [longitude, latitude, altitude, data] ',\n      description2:\n        'com um ultimo elemento sendo uma data. Um formato de data válida inclui segundos unix como `1564184363` ou em milisegundos como `1564184363000`.',\n      example: 'Exemplo:'\n    },\n    iconInfo: {\n      title: 'Como desenhar ícones',\n      description1:\n        'No seu csv, crie uma coluna, coloque o nome do ícone que você quer desenhar nele. Você pode deixar o campo vazio se você não desejar que o ícone apareça para alguns pontos. Quando a coluna tem nome',\n      code: 'icon',\n      description2: ' kepler.gl irá automaticamente criar uma camada de ícone para você.',\n      example: 'Exemplos:',\n      icons: 'Ícones'\n    },\n    storageMapViewer: {\n      lastModified: 'Modificado há {lastUpdated}',\n      back: 'Voltar'\n    },\n    overwriteMap: {\n      title: 'Salvando mapa...',\n      alreadyExists: 'já existe no mapa {mapSaved}. Você desejaria sobrescrever o mapa?'\n    },\n    loadStorageMap: {\n      back: 'Voltar',\n      goToPage: 'Vá para a sua página {displayName} do Kepler.gl',\n      storageMaps: 'Armazenamento / Mapas',\n      noSavedMaps: 'Nenhum mapa salvo'\n    }\n  },\n  header: {\n    visibleLayers: 'Camadas Visíveis',\n    layerLegend: 'Legenda da Camada'\n  },\n  interactions: {\n    tooltip: 'Dica de contexto',\n    brush: 'Pincel',\n    coordinate: 'Coordenadas'\n  },\n  layerBlending: {\n    title: 'Mistura de Camadas',\n    additive: 'aditivo',\n    normal: 'normal',\n    subtractive: 'subtrativo'\n  },\n  columns: {\n    title: 'Colunas',\n    lat: 'lat',\n    lng: 'lon',\n    altitude: 'altitude',\n    icon: 'ícone',\n    geojson: 'geojson',\n    arc: {\n      lat0: 'origem lat',\n      lng0: 'origem lng',\n      lat1: 'destino lat',\n      lng1: 'destino lng'\n    },\n    line: {\n      alt0: 'origem altitude',\n      alt1: 'destino altitude'\n    },\n    grid: {\n      worldUnitSize: 'Tamanho da Grade (km)'\n    },\n    hexagon: {\n      worldUnitSize: 'Raio do Hexágono (km)'\n    }\n  },\n  color: {\n    customPalette: 'Paletas customizadas',\n    steps: 'caminhos',\n    type: 'tipo',\n    reversed: 'reverso'\n  },\n  scale: {\n    colorScale: 'Escala da Cor',\n    sizeScale: 'Tamanho da Escala',\n    strokeScale: 'Escala do Traço',\n    scale: 'Escala'\n  },\n  fileUploader: {\n    message: 'Arraste e solte seu(s) arquivo(s) aqui',\n    chromeMessage:\n      '*Usuários do chrome: O limite de tamanho de arquivo é 250mb, se você precisa fazer upload de arquivos maiores tente o Safari',\n    disclaimer:\n      '*kepler.gl é uma aplicação client-side, sem um servidor backend. Os dados ficam apenas na sua máquina/browser. ' +\n      'Nenhuma informação ou dados de mapa é enviado para qualquer server.',\n    configUploadMessage:\n      'Envie {fileFormatNames} ou mapas salvos **Json**. Leia mais sobre [**tipos de arquivos suportados**]',\n    browseFiles: 'procure seus arquivos',\n    uploading: 'Enviando',\n    fileNotSupported: 'Arquivo {errorFiles} não é suportado.',\n    or: 'ou'\n  },\n  density: 'densidade',\n  'Bug Report': 'Reportar Bug',\n  'User Guide': 'Guia do Usuário',\n  Save: 'Salvar',\n  Share: 'Compartilhar'\n};\n"
  },
  {
    "path": "src/localization/src/translations/ru.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LOCALES} from '../locales';\n\nexport default {\n  property: {\n    weight: 'вес',\n    label: 'ярлык',\n    fillColor: 'цвет заливки',\n    color: 'цвет',\n    coverage: 'покрытие',\n    strokeColor: 'цвет обводки',\n    radius: 'радиус',\n    outline: 'контур',\n    stroke: 'обводка',\n    density: 'плотность',\n    height: 'высота',\n    sum: 'сумма',\n    pointCount: 'Кол-во точек'\n  },\n  placeholder: {\n    search: 'Поиск',\n    selectField: 'Выберите поле',\n    yAxis: 'Y Ось',\n    selectType: 'Выберите A тип',\n    selectValue: 'Выберите A значение',\n    enterValue: 'Введите значение',\n    empty: 'пустой'\n  },\n  misc: {\n    by: '',\n    valuesIn: 'Значение в',\n    valueEquals: 'Значение равно',\n    dataSource: 'Источник данных',\n    brushRadius: 'Радиус кисти (km)',\n    empty: ' '\n  },\n  mapLayers: {\n    title: 'Слои карты',\n    label: 'Обозначения',\n    road: 'Дороги',\n    border: 'Границы',\n    building: 'Здания',\n    water: 'Вода',\n    land: 'Земля',\n    '3dBuilding': '3d здания'\n  },\n  panel: {\n    text: {\n      label: 'Ярлык',\n      labelWithId: 'Ярлык {labelId}',\n      fontSize: 'Размер шрифта',\n      fontColor: 'Цвет шрифта',\n      textAnchor: 'Анкор текста',\n      alignment: 'Положение',\n      addMoreLabel: 'Добавить еще ярлык'\n    }\n  },\n  sidebar: {\n    panels: {\n      layer: 'Слои',\n      filter: 'Фильтры',\n      interaction: 'Взаимодействия',\n      basemap: 'Базовая карта'\n    }\n  },\n  layer: {\n    required: 'Требования*',\n    radius: 'Радиус',\n    color: 'Цвет',\n    fillColor: 'Цвет заливки',\n    outline: 'Контур',\n    weight: 'Вес',\n    propertyBasedOn: '{property} на основе',\n    coverage: 'Покрытие',\n    stroke: 'Обводка',\n    strokeWidth: 'Ширина обводки',\n    strokeColor: 'Цвет обводки',\n    basic: 'Basic',\n    trailLength: 'Trail Length',\n    trailLengthDescription: 'Number of seconds for a path to completely fade out',\n    newLayer: 'new layer',\n    elevationByDescription: 'When off, height is based on count of points',\n    colorByDescription: 'When off, color is based on count of points',\n    aggregateBy: 'Aggregate {field} by',\n    '3DModel': '3D Model',\n    '3DModelOptions': '3D Model Options',\n    type: {\n      point: 'точки',\n      arc: 'дуги',\n      line: 'линии',\n      grid: 'сетка',\n      hexbin: 'hexbin',\n      polygon: 'многоугольники',\n      geojson: 'geojson',\n      cluster: 'кластеры',\n      icon: 'значки',\n      heatmap: 'тепловая карта',\n      hexagon: 'шестиугольник',\n      hexagonid: 'H3',\n      trip: 'пути',\n      s2: 'S2',\n      '3d': '3D'\n    }\n  },\n  layerVisConfigs: {\n    angle: 'Угол',\n    strokeWidth: 'Ширина штриха (в пикселях)',\n    strokeWidthRange: 'Диапазон ширины штриха',\n    radius: 'Радиус',\n    fixedRadius: 'Фиксированный радиус в метрах',\n    fixedRadiusDescription:\n      'Сопоставьте радиус с абсолютным радиусом в метрах, например От 5 до 5 метров',\n    radiusRange: 'Диапазон радиуса',\n    clusterRadius: 'Радиус кластера в пикселях',\n    radiusRangePixels: 'Диапазон радиуса в пикселях',\n    opacity: 'Непрозрачность',\n    coverage: 'Покрытие',\n    outline: 'Контур',\n    colorRange: 'Цветовая гамма',\n    stroke: 'Обводка',\n    strokeColor: 'Цвет обводки',\n    strokeColorRange: 'Обводка Цветовой диапазон',\n    targetColor: 'Целевой цвет',\n    colorAggregation: 'Цветовая агрегация',\n    heightAggregation: 'Агрегация по высоте',\n    resolutionRange: 'Диапазон разрешения',\n    sizeScale: 'Шкала размеров',\n    worldUnitSize: 'Мировые ед.изм.',\n    elevationScale: 'Шкала возвышения',\n    enableElevationZoomFactor: 'Использовать коэффициент увеличения по высоте',\n    enableElevationZoomFactorDescription:\n      'Отрегулируйте высоту / возвышение на основе текущего коэффициента масштабирования',\n    enableHeightZoomFactor: 'вкл. коэффициент масштабирования по высоте',\n    heightScale: 'Масштаб высоты',\n    coverageRange: 'Диапазон покрытия',\n    highPrecisionRendering: 'Высокая точность рендеринга',\n    highPrecisionRenderingDescription: 'Высокая точность приведет к снижению производительности',\n    height: 'Высота',\n    heightDescription: 'Нажмите кнопку в правом верхнем углу карты, чтобы переключиться в 3D-вид',\n    fill: 'Наполнить',\n    enablePolygonHeight: 'Включить высоту многоугольника',\n    showWireframe: 'Показать каркас',\n    weightIntensity: 'Вес Интенсивность',\n    zoomScale: 'Масштаб увеличения',\n    heightRange: 'Диапазон высоты',\n    heightMultiplier: 'Множитель высоты'\n  },\n  layerManager: {\n    addData: 'Добавить данные',\n    addLayer: 'Добавить слой',\n    layerBlending: 'Смешивание слоев'\n  },\n  mapManager: {\n    mapStyle: 'Стиль карты',\n    addMapStyle: 'Добавить стиль карты',\n    '3dBuildingColor': '3D Цвет здания'\n  },\n  layerConfiguration: {\n    defaultDescription: 'Рассчитать {property} на основе выбранного поля',\n    howTo: 'How to'\n  },\n  filterManager: {\n    addFilter: 'Добавить фильтр'\n  },\n  datasetTitle: {\n    showDataTable: 'Показать таблицу данных ',\n    removeDataset: 'Удалить набор данных'\n  },\n  datasetInfo: {\n    rowCount: '{rowCount} строк'\n  },\n  tooltip: {\n    hideLayer: 'скрыть слой',\n    showLayer: 'показать слой',\n    hideFeature: 'Скрыть функцию',\n    showFeature: 'Показать функцию',\n    hide: 'скрыть',\n    show: 'показать',\n    removeLayer: 'Удалить слой',\n    duplicateLayer: 'Дублировать слой',\n    layerSettings: 'Настройки слоя',\n    closePanel: 'Закрыть текущую панель',\n    switchToDualView: 'Перейти в режим двойной карты',\n    showLegend: 'Показать легенду',\n    disable3DMap: 'Отключить 3D Карту',\n    DrawOnMap: 'Рисовать на карте',\n    selectLocale: 'Выберите регион',\n    showAiAssistantPanel: 'Показать панель AI Assistant',\n    hideAiAssistantPanel: 'Скрыть панель AI Assistant',\n    hideLayerPanel: 'Скрыть панель слоев',\n    showLayerPanel: 'Показать панель слоев',\n    moveToTop: 'Перейти к верхним слоям данных',\n    selectBaseMapStyle: 'Выберите стиль базовой карты',\n    delete: 'Удалить',\n    timePlayback: 'Воспроизведение времени',\n    cloudStorage: 'Облачное хранилище',\n    '3DMap': '3D Карта',\n    animationByWindow: 'Перемещение временного окна',\n    animationByIncremental: 'Дополнительное временное окно',\n    speed: 'скорость',\n    play: 'проиграть',\n    pause: 'пауза',\n    reset: 'перезапустить'\n  },\n  toolbar: {\n    exportImage: 'Экспорт изображения',\n    exportData: 'Экспорт данных',\n    exportMap: 'Экспорт карты',\n    shareMapURL: 'Share Map URL',\n    saveMap: 'Сохарнить Карту',\n    select: 'Выбрать',\n    polygon: 'Многоугольник',\n    rectangle: 'Квадрат',\n    hide: 'Скрыть',\n    show: 'Показать',\n    ...LOCALES\n  },\n  editor: {\n    filterLayer: 'Слои фильтров',\n    copyGeometry: 'Копировать геометрию'\n  },\n\n  modal: {\n    title: {\n      deleteDataset: 'Удалить данные',\n      addDataToMap: 'Добавить данные на карту',\n      exportImage: 'Экспорт изображения',\n      exportData: 'Экспорт данных',\n      exportMap: 'Экспорт карты',\n      addCustomMapboxStyle: 'Добавить собственный стиль карты',\n      saveMap: 'Поделиться Картой',\n      shareURL: 'Поделиться URL'\n    },\n    button: {\n      delete: 'Удалить',\n      download: 'Скачать',\n      export: 'Экспортировать',\n      addStyle: 'Добавить стиль',\n      save: 'Сохранить',\n      defaultCancel: 'Отменить',\n      defaultConfirm: 'Подтвердить'\n    },\n    exportImage: {\n      ratioTitle: 'Ratio',\n      ratioDescription: 'Выберите соотношение для различного использования',\n      ratioOriginalScreen: 'Исходный экран',\n      ratioCustom: 'Настройки',\n      ratio4_3: '4:3',\n      ratio16_9: '16:9',\n      resolutionTitle: 'Разрешение',\n      resolutionDescription: 'Для печати лучше использовать высокое разрешение',\n      resolutionPlaceholder: 'Выберите разрешение...',\n      mapLegendTitle: 'Легенда карты',\n      mapLegendAdd: 'Добавить легенду на карту'\n    },\n    exportData: {\n      datasetTitle: 'Набор данных',\n      datasetSubtitle: 'Выберите наборы данных, которые хотите экспортировать',\n      allDatasets: 'Все',\n      dataTypeTitle: 'Тип данных',\n      dataTypeSubtitle: 'Выберите тип данных, которые вы хотите экспортировать',\n      filterDataTitle: 'Отфильтрованные данные',\n      filterDataSubtitle: 'Вы можете выбрать экспорт исходных данных или отфильтрованных данных',\n      filteredData: 'Отфильтрованные данные',\n      unfilteredData: 'Нефильтрованные данные',\n      fileCount: '{fileCount} Файлов',\n      rowCount: '{rowCount} Строк'\n    },\n    deleteData: {\n      warning: 'вы собираетесь удалить этот набор данных. Это повлияет на {length} слой'\n    },\n    addStyle: {\n      publishTitle:\n        '2. Если вы указали URL-адрес файла mapbox на шаге 1, опубликуйте свой стиль на mapbox или предоставьте токен доступа. (Необязательно)',\n      publishSubtitle1: 'Вы можете создать свой собственный стиль карты',\n      publishSubtitle2: 'и',\n      publishSubtitle3: 'опубликовать',\n      publishSubtitle4: 'его.',\n      publishSubtitle5: 'Чтобы использовать частный стиль, вставьте свой',\n      publishSubtitle6: 'token доступа',\n      publishSubtitle7:\n        'прим. kepler.gl - это клиентское приложение, данные остаются в вашем браузере .',\n      exampleToken: 'например pk.abcdefg.xxxxxx',\n      pasteTitle: '1. Вставить URL стиля',\n      pasteSubtitle0: 'URL стиля может быть mapbox',\n      pasteSubtitle1: 'Или',\n      pasteSubtitle2: 'URL стиля',\n      pasteSubtitle3: 'style.json используя',\n      pasteSubtitle4: 'Mapbox GL Style Spec',\n      namingTitle: '3. Назови свой стиль'\n    },\n    shareMap: {\n      shareUriTitle: 'Поделиться URL карты',\n      shareUriSubtitle: 'Создать URL карты, чтобы поделиться с другими',\n      cloudTitle: 'Облачное хранилище',\n      cloudSubtitle: 'Войдите и загрузите данные карты в свое личное облачное хранилище',\n      shareDisclaimer:\n        'kepler.gl сохранит данные вашей карты в вашем личном облачном хранилище, только люди с URL-адресом могут получить доступ к вашей карте и данным. ' +\n        'Вы можете редактировать / удалить файл данных в своей облачной учетной записи в любое время.',\n      gotoPage: 'Перейти на страницу Kepler.gl {currentProvider}'\n    },\n    statusPanel: {\n      mapUploading: 'Загрузка карты',\n      error: 'Ошибка'\n    },\n    saveMap: {\n      title: 'Облачное хранилище',\n      subtitle: 'Авторизуйтесь, чтобы сохранить карту в вашем личном облачном хранилище'\n    },\n    exportMap: {\n      formatTitle: 'Формат карты',\n      formatSubtitle: 'Выберите формат для экспорта карты',\n      html: {\n        selection: 'Экспорт карты в интерактивный файл HTML.',\n        tokenTitle: 'Токен доступа к Mapbox',\n        tokenSubtitle: 'Используйте свой токен доступа Mapbox в html(необязательно)',\n        tokenPlaceholder: 'Вставьте токен доступа Mapbox',\n        tokenMisuseWarning:\n          '* If you do not provide your own token, the map may fail to display at any time when we replace ours to avoid misuse. ',\n        tokenDisclaimer:\n          'Если вы не предоставите свой собственный токен, карта может не отображаться в любое время, когда мы заменяем наш, чтобы избежать неправильного использования.',\n        tokenUpdate: 'Как обновить существующий токен карты.',\n        modeTitle: 'Режим карты',\n        modeSubtitle1: 'Выберите режим приложения. Подробнее',\n        modeSubtitle2: 'Информация',\n        modeDescription: 'Разрешить пользователям {mode} карту',\n        read: 'чтение',\n        edit: 'редактирование'\n      },\n      json: {\n        configTitle: 'Конфигурация карты',\n        configDisclaimer:\n          'Конфигурация карты будет включена в файл Json. Если вы используете kepler.gl в своем собственном приложении. Вы можете скопировать этот конфиг и передать его ',\n        selection:\n          'Экспорт текущих данных карты и конфигурации в один файл Json. Позже вы сможете открыть ту же карту, загрузив этот файл на kepler.gl.',\n        disclaimer:\n          '* Конфигурация карты связана с загруженными наборами данных. DataId используется для привязки слоев, фильтров и всплывающих подсказок к определенному набору данных. ' +\n          'При передаче этой конфигурации addDataToMap, убедитесь, что идентификатор набора данных совпадает с dataId / s в этой конфигурации.'\n      }\n    },\n    loadingDialog: {\n      loading: 'Загрузка...'\n    },\n    loadData: {\n      upload: 'Загрузить файлы',\n      storage: 'Загрузить из хранилища'\n    },\n    tripInfo: {\n      title: 'Как включить анимацию поездки',\n      description1:\n        'Чтобы анимировать путь, данные geoJSON должны содержать LineString в своей геометрии объекта, а координаты в LineString должны иметь 4 элемента в форматах',\n      code: ' [longitude, latitude, altitude, timestamp] ',\n      description2:\n        'с последним элементом, являющимся отметкой времени. Допустимые форматы меток времени включают unix в секундах, например 1564184363, или в миллисекундах, например 1564184363000',\n      example: ',Пример:'\n    },\n    iconInfo: {\n      title: 'Как рисовать значки',\n      description1:\n        'В вашем csv создайте столбец, поместите в него имя значка, который вы хотите нарисовать. Вы можете оставить ячейку пустой, если не хотите, чтобы значок отображался для некоторых точек. Когда столбец назван',\n      code: 'значек',\n      description2: ' kepler.gl автоматически создаст для вас слой значков.',\n      example: 'Пример:',\n      icons: 'Значки'\n    },\n    storageMapViewer: {\n      lastModified: 'Последнее изменение {lastUpdated} назад',\n      back: 'Назад'\n    },\n    overwriteMap: {\n      title: 'Сохранение карты...',\n      alreadyExists: 'уже существует в вашем {mapSaved}. Хотите его перезаписать?'\n    },\n    loadStorageMap: {\n      back: 'Назад',\n      goToPage: 'Перейти на страницу Kepler.gl {displayName}',\n      storageMaps: 'Хранилище / Карты',\n      noSavedMaps: 'Нет сохраненных карт'\n    }\n  },\n  header: {\n    visibleLayers: 'Видимые слои',\n    layerLegend: 'Легенда слоя'\n  },\n  interactions: {\n    tooltip: 'Подсказка',\n    brush: 'Кисть',\n    coordinate: 'Координаты',\n    geocoder: 'Геокодер'\n  },\n  layerBlending: {\n    title: 'Смешивание слоев',\n    additive: 'добавление',\n    normal: 'нормальное',\n    subtractive: 'вычитание'\n  },\n  columns: {\n    title: 'Столбцы',\n    lat: 'lat',\n    lng: 'lon',\n    altitude: 'высота',\n    icon: 'значек',\n    geojson: 'geojson',\n    token: 'token',\n    arc: {\n      lat0: 'lat источника',\n      lng0: 'lng источника',\n      lat1: 'lat цели',\n      lng1: 'lng цели'\n    },\n    line: {\n      alt0: 'высота источника',\n      alt1: 'высота цели'\n    },\n    grid: {\n      worldUnitSize: 'Размер сетки (km)'\n    },\n    hexagon: {\n      worldUnitSize: 'Hexagon радиус (km)'\n    },\n    hex_id: 'hex id'\n  },\n  color: {\n    customPalette: 'Ваша палитра',\n    steps: 'шагов',\n    type: 'тип',\n    reversed: 'перевернуть'\n  },\n  scale: {\n    colorScale: 'Цветовая шкала',\n    sizeScale: 'Масштаб размера',\n    strokeScale: 'Масштаб штриха',\n    scale: 'Масштаб'\n  },\n  fileUploader: {\n    message: 'Перетащите сюда ваши файлы',\n    chromeMessage:\n      '*Пользователь Chrome: ограничьте размер файла до 250 МБ, если нужно загрузить файл большего размера, попробуйте Safari',\n    disclaimer:\n      '*kepler.gl - это клиентское приложение без серверной части. Данные живут только на вашем компьютере. ' +\n      'Никакая информация или данные карты не отправляются ни на один сервер.',\n    configUploadMessage:\n      'Загрузите {fileFormatNames} или сохраненную карту **Json**. Подробнее [**supported file formats**]',\n    browseFiles: 'Просматреть файлы',\n    uploading: 'Загрузка',\n    fileNotSupported: 'File {errorFiles} is not supported.',\n    or: 'or'\n  },\n  geocoder: {\n    title: 'Введите адрес или координаты, например 37.79, -122.40'\n  },\n  fieldSelector: {\n    clearAll: 'Очистить все',\n    formatting: 'Форматирование'\n  },\n  compare: {\n    modeLabel: 'Режим сравнения',\n    typeLabel: 'Тип сравнения',\n    types: {\n      absolute: 'Абсолютный',\n      relative: 'Относительный'\n    }\n  },\n  mapPopover: {\n    primary: 'Первичный'\n  },\n  density: 'density',\n  'Bug Report': 'Отчет об ошибках',\n  'User Guide': 'Инструкции',\n  Save: 'Сохранить',\n  Share: 'Поделиться'\n};\n"
  },
  {
    "path": "src/localization/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/localization/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/processors/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/processors/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/processors\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@danmarshall/deckgl-typings\": \"4.9.22\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/schemas\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/arrow\": \"^4.3.2\",\n    \"@loaders.gl/core\": \"^4.3.2\",\n    \"@loaders.gl/csv\": \"^4.3.2\",\n    \"@loaders.gl/gis\": \"^4.3.2\",\n    \"@loaders.gl/json\": \"^4.3.2\",\n    \"@loaders.gl/loader-utils\": \"^4.3.2\",\n    \"@loaders.gl/parquet\": \"^4.3.2\",\n    \"@loaders.gl/schema\": \"^4.3.2\",\n    \"@loaders.gl/wkt\": \"^4.3.2\",\n    \"@mapbox/geojson-normalize\": \"0.0.1\",\n    \"@nebula.gl/edit-modes\": \"1.0.2-alpha.1\",\n    \"@turf/helpers\": \"^6.1.4\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"d3-dsv\": \"^2.0.0\",\n    \"type-analyzer\": \"0.4.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/processors/src/data-processor.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport {csvParseRows} from 'd3-dsv';\nimport {DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer';\nimport normalize from '@mapbox/geojson-normalize';\nimport {parseSync} from '@loaders.gl/core';\nimport {ArrowTable} from '@loaders.gl/schema';\nimport {WKBLoader} from '@loaders.gl/wkt';\n\nimport {\n  ALL_FIELD_TYPES,\n  DATASET_FORMATS,\n  GEOARROW_EXTENSIONS,\n  GEOARROW_METADATA_KEY,\n  GUIDES_FILE_FORMAT_DOC\n} from '@kepler.gl/constants';\nimport {ProcessorResult, Field} from '@kepler.gl/types';\nimport {\n  arrowDataTypeToAnalyzerDataType,\n  arrowDataTypeToFieldType,\n  hasOwnProperty,\n  isPlainObject\n} from '@kepler.gl/utils';\nimport {\n  analyzerTypeToFieldType,\n  getSampleForTypeAnalyze,\n  getSampleForTypeAnalyzeArrow,\n  getFieldsFromData,\n  h3IsValid,\n  notNullorUndefined,\n  toArray\n} from '@kepler.gl/common-utils';\nimport {KeplerGlSchema, ParsedDataset, SavedMap, LoadedMap} from '@kepler.gl/schemas';\nimport {Feature} from '@nebula.gl/edit-modes';\n\n// if any of these value occurs in csv, parse it to null;\n// const CSV_NULLS = ['', 'null', 'NULL', 'Null', 'NaN', '/N'];\n// matches empty string\nexport const CSV_NULLS = /^(null|NULL|Null|NaN|\\/N||)$/;\n\nfunction tryParseJsonString(str) {\n  try {\n    return JSON.parse(str);\n  } catch (e) {\n    return null;\n  }\n}\n\nexport const PARSE_FIELD_VALUE_FROM_STRING = {\n  [ALL_FIELD_TYPES.boolean]: {\n    valid: (d: unknown): boolean => typeof d === 'boolean',\n    parse: (d: unknown): boolean => d === 'true' || d === 'True' || d === 'TRUE' || d === '1'\n  },\n  [ALL_FIELD_TYPES.integer]: {\n    // @ts-ignore\n    valid: (d: unknown): boolean => parseInt(d, 10) === d,\n    // @ts-ignore\n    parse: (d: unknown): number => parseInt(d, 10)\n  },\n  [ALL_FIELD_TYPES.timestamp]: {\n    valid: (d: unknown, field: Field): boolean =>\n      ['x', 'X'].includes(field.format) ? typeof d === 'number' : typeof d === 'string',\n    parse: (d: any, field: Field) => (['x', 'X'].includes(field.format) ? Number(d) : d)\n  },\n  [ALL_FIELD_TYPES.real]: {\n    // @ts-ignore\n    valid: (d: unknown): boolean => parseFloat(d) === d,\n    // Note this will result in NaN for some string\n    parse: parseFloat\n  },\n  [ALL_FIELD_TYPES.object]: {\n    valid: isPlainObject,\n    parse: tryParseJsonString\n  },\n\n  [ALL_FIELD_TYPES.array]: {\n    valid: Array.isArray,\n    parse: tryParseJsonString\n  },\n\n  [ALL_FIELD_TYPES.h3]: {\n    valid: d => h3IsValid(d),\n    parse: d => d\n  }\n};\n\n/**\n * Process csv data, output a data object with `{fields: [], rows: []}`.\n * The data object can be wrapped in a `dataset` and pass to [`addDataToMap`](../actions/actions.md#adddatatomap)\n * @param rawData raw csv string\n * @returns data object `{fields: [], rows: []}` can be passed to addDataToMaps\n * @public\n * @example\n * import {processCsvData} from '@kepler.gl/processors';\n *\n * const testData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date\n * 2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23\n * 2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23\n * 2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23\n * 2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23`\n *\n * const dataset = {\n *  info: {id: 'test_data', label: 'My Csv'},\n *  data: processCsvData(testData)\n * };\n *\n * dispatch(addDataToMap({\n *  datasets: [dataset],\n *  options: {centerMap: true, readOnly: true}\n * }));\n */\nexport function processCsvData(rawData: unknown[][] | string, header?: string[]): ProcessorResult {\n  let rows: unknown[][] | undefined;\n  let headerRow: string[] | undefined;\n\n  if (typeof rawData === 'string') {\n    const parsedRows: string[][] = csvParseRows(rawData);\n\n    if (!Array.isArray(parsedRows) || parsedRows.length < 2) {\n      // looks like an empty file, throw error to be catch\n      throw new Error('process Csv Data Failed: CSV is empty');\n    }\n    headerRow = parsedRows[0];\n    rows = parsedRows.slice(1);\n  } else if (Array.isArray(rawData) && rawData.length) {\n    rows = rawData;\n    headerRow = header;\n\n    if (!Array.isArray(headerRow)) {\n      // if data is passed in as array of rows and missing header\n      // assume first row is header\n      // @ts-ignore\n      headerRow = rawData[0];\n      rows = rawData.slice(1);\n    }\n  }\n\n  if (!rows || !headerRow) {\n    throw new Error('invalid input passed to processCsvData');\n  }\n\n  // here we assume the csv file that people uploaded will have first row\n  // as name of the column\n\n  cleanUpFalsyCsvValue(rows);\n  // No need to run type detection on every data point\n  // here we get a list of none null values to run analyze on\n  const sample = getSampleForTypeAnalyze({fields: headerRow, rows});\n  const fields = getFieldsFromData(sample, headerRow);\n  const parsedRows = parseRowsByFields(rows, fields);\n\n  return {fields, rows: parsedRows};\n}\n\n/**\n * Parse rows of csv by analyzed field types. So that `'1'` -> `1`, `'True'` -> `true`\n * @param rows\n * @param fields\n */\nexport function parseRowsByFields(rows: any[][], fields: Field[]) {\n  // Edit rows in place\n  const geojsonFieldIdx = fields.findIndex(f => f.name === '_geojson');\n  fields.forEach(parseCsvRowsByFieldType.bind(null, rows, geojsonFieldIdx));\n\n  return rows;\n}\n\n/**\n * Convert falsy value in csv including `'', 'null', 'NULL', 'Null', 'NaN'` to `null`,\n * so that type-analyzer won't detect it as string\n *\n * @param rows\n */\nfunction cleanUpFalsyCsvValue(rows: unknown[][]): void {\n  const re = new RegExp(CSV_NULLS, 'g');\n  for (let i = 0; i < rows.length; i++) {\n    for (let j = 0; j < rows[i].length; j++) {\n      // analyzer will set any fields to 'string' if there are empty values\n      // which will be parsed as '' by d3.csv\n      // here we parse empty data as null\n      // TODO: create warning when deltect `CSV_NULLS` in the data\n      if (typeof rows[i][j] === 'string' && (rows[i][j] as string).match(re)) {\n        rows[i][j] = null;\n      }\n    }\n  }\n}\n\n/**\n * Process uploaded csv file to parse value by field type\n *\n * @param rows\n * @param geoFieldIdx field index\n * @param field\n * @param i\n */\nexport function parseCsvRowsByFieldType(\n  rows: unknown[][],\n  geoFieldIdx: number,\n  field: Field,\n  i: number\n): void {\n  const parser = PARSE_FIELD_VALUE_FROM_STRING[field.type];\n  if (parser) {\n    // check first not null value of it's already parsed\n    const first = rows.find(r => notNullorUndefined(r[i]));\n    if (!first || parser.valid(first[i], field)) {\n      return;\n    }\n    rows.forEach(row => {\n      // parse string value based on field type\n      if (row[i] !== null) {\n        row[i] = parser.parse(row[i], field);\n        if (\n          geoFieldIdx > -1 &&\n          isPlainObject(row[geoFieldIdx]) &&\n          // @ts-ignore\n          hasOwnProperty(row[geoFieldIdx], 'properties')\n        ) {\n          // @ts-ignore\n          row[geoFieldIdx].properties[field.name] = row[i];\n        }\n      }\n    });\n  }\n}\n\n/* eslint-enable complexity */\n\n/**\n * Process data where each row is an object, output can be passed to [`addDataToMap`](../actions/actions.md#adddatatomap)\n * NOTE: This function may mutate input.\n * @param rawData an array of row object, each object should have the same number of keys\n * @returns dataset containing `fields` and `rows`\n * @public\n * @example\n * import {addDataToMap} from '@kepler.gl/actions';\n * import {processRowObject} from '@kepler.gl/processors';\n *\n * const data = [\n *  {lat: 31.27, lng: 127.56, value: 3},\n *  {lat: 31.22, lng: 126.26, value: 1}\n * ];\n *\n * dispatch(addDataToMap({\n *  datasets: {\n *    info: {label: 'My Data', id: 'my_data'},\n *    data: processRowObject(data)\n *  }\n * }));\n */\nexport function processRowObject(rawData: unknown[]): ProcessorResult {\n  if (!Array.isArray(rawData)) {\n    return null;\n  } else if (!rawData.length) {\n    // data is empty\n    return {\n      fields: [],\n      rows: []\n    };\n  }\n\n  const firstRow = rawData[0] as Record<string, unknown>;\n  const keys = Object.keys(firstRow); // [lat, lng, value]\n  const rows = rawData.map(d => keys.map(key => (d as Record<string, unknown>)[key])); // [[31.27, 127.56, 3]]\n\n  // row object can still contain values like `Null` or `N/A`\n  cleanUpFalsyCsvValue(rows);\n\n  return processCsvData(rows, keys);\n}\n\n/**\n * Process GeoJSON [`FeatureCollection`](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection),\n * output a data object with `{fields: [], rows: []}`.\n * The data object can be wrapped in a `dataset` and passed to [`addDataToMap`](../actions/actions.md#adddatatomap)\n * NOTE: This function may mutate input.\n *\n * @param rawData raw geojson feature collection\n * @returns dataset containing `fields` and `rows`\n * @public\n * @example\n * import {addDataToMap} from '@kepler.gl/actions';\n * import {processGeojson} from '@kepler.gl/processors';\n *\n * const geojson = {\n * \t\"type\" : \"FeatureCollection\",\n * \t\"features\" : [{\n * \t\t\"type\" : \"Feature\",\n * \t\t\"properties\" : {\n * \t\t\t\"capacity\" : \"10\",\n * \t\t\t\"type\" : \"U-Rack\"\n * \t\t},\n * \t\t\"geometry\" : {\n * \t\t\t\"type\" : \"Point\",\n * \t\t\t\"coordinates\" : [ -71.073283, 42.417500 ]\n * \t\t}\n * \t}]\n * };\n *\n * dispatch(addDataToMap({\n *  datasets: {\n *    info: {\n *      label: 'Sample Taxi Trips in New York City',\n *      id: 'test_trip_data'\n *    },\n *    data: processGeojson(geojson)\n *  }\n * }));\n */\nexport function processGeojson(rawData: unknown): ProcessorResult {\n  const normalizedGeojson = normalize(rawData);\n\n  if (!normalizedGeojson || !Array.isArray(normalizedGeojson.features)) {\n    throw new Error(\n      `Read File Failed: File is not a valid GeoJSON. Read more about [supported file format](${GUIDES_FILE_FORMAT_DOC})`\n    );\n  }\n\n  // getting all feature fields\n  const allDataRows: Array<{_geojson: Feature} & keyof Feature> = [];\n  for (let i = 0; i < normalizedGeojson.features.length; i++) {\n    const f = normalizedGeojson.features[i];\n    if (f.geometry) {\n      allDataRows.push({\n        // add feature to _geojson field\n        _geojson: f,\n        ...(f.properties || {})\n      });\n    }\n  }\n  // get all the field\n  const fields = allDataRows.reduce<string[]>((accu, curr) => {\n    Object.keys(curr).forEach(key => {\n      if (!accu.includes(key)) {\n        accu.push(key);\n      }\n    });\n    return accu;\n  }, []);\n\n  // make sure each feature has exact same fields\n  allDataRows.forEach(d => {\n    fields.forEach(f => {\n      if (!(f in d)) {\n        d[f] = null;\n        if (d._geojson.properties) {\n          d._geojson.properties[f] = null;\n        }\n      }\n    });\n  });\n\n  return processRowObject(allDataRows);\n}\n\n/**\n * Process saved kepler.gl json to be pass to [`addDataToMap`](../actions/actions.md#adddatatomap).\n * The json object should contain `datasets` and `config`.\n * @param rawData\n * @param schema\n * @returns datasets and config `{datasets: {}, config: {}}`\n * @public\n * @example\n * import {addDataToMap} from '@kepler.gl/actions';\n * import {processKeplerglJSON} from '@kepler.gl/processors';\n *\n * dispatch(addDataToMap(processKeplerglJSON(keplerGlJson)));\n */\nexport function processKeplerglJSON(rawData: SavedMap, schema = KeplerGlSchema): LoadedMap | null {\n  return rawData ? schema.load(rawData.datasets, rawData.config) : null;\n}\n\n/**\n * Parse a single or an array of datasets saved using kepler.gl schema\n * @param rawData\n * @param schema\n */\nexport function processKeplerglDataset(\n  rawData: object | object[],\n  schema = KeplerGlSchema\n): ParsedDataset | ParsedDataset[] | null {\n  if (!rawData) {\n    return null;\n  }\n\n  const results = schema.parseSavedData(toArray(rawData));\n  if (!results) {\n    return null;\n  }\n  return Array.isArray(rawData) ? results : results[0];\n}\n\n/**\n * Parse arrow table and return a dataset\n *\n * @param arrowTable ArrowTable to parse, see loaders.gl/schema\n * @returns dataset containing `fields` and `rows` or null\n */\nexport function processArrowTable(arrowTable: ArrowTable): ProcessorResult | null {\n  // @ts-ignore - Unknown data type causing build failures\n  return processArrowBatches(arrowTable.data.batches);\n}\n\n/**\n * Extracts GeoArrow metadata from an Apache Arrow table schema.\n * For geoparquet files geoarrow metadata isn't present in fields, so extract extra info from schema.\n * @param table The Apache Arrow table to extract metadata from.\n * @returns An object mapping column names to their GeoArrow encoding type.\n * @throws Logs an error message if parsing of metadata fails.\n */\nexport function getGeoArrowMetadataFromSchema(table: arrow.Table): Record<string, string> {\n  const geoArrowMetadata: Record<string, string> = {};\n  try {\n    const geoString = table.schema.metadata?.get('geo');\n    if (geoString) {\n      const parsedGeoString = JSON.parse(geoString);\n      if (parsedGeoString.columns) {\n        Object.keys(parsedGeoString.columns).forEach(columnName => {\n          const columnData = parsedGeoString.columns[columnName];\n          if (columnData?.encoding === 'WKB') {\n            geoArrowMetadata[columnName] = GEOARROW_EXTENSIONS.WKB;\n          }\n          // TODO potentially there are other types but no datasets to test\n        });\n      }\n    }\n  } catch (error) {\n    console.error('An error during arrow table schema metadata parsing');\n  }\n  return geoArrowMetadata;\n}\n\n/**\n * Converts an Apache Arrow table schema into an array of Kepler.gl field objects.\n * @param table The Apache Arrow table whose schema needs to be converted.\n * @param fieldTypeSuggestions Optional mapping of field names to suggested field types.\n * @returns An array of field objects suitable for Kepler.gl.\n */\nexport function arrowSchemaToFields(\n  table: arrow.Table,\n  fieldTypeSuggestions: Record<string, string> = {}\n): Field[] {\n  const headerRow = table.schema.fields.map(f => f.name);\n  const sample = getSampleForTypeAnalyzeArrow(table, headerRow);\n  const keplerFields = getFieldsFromData(sample, headerRow);\n  const geoArrowMetadata = getGeoArrowMetadataFromSchema(table);\n\n  return table.schema.fields.map((field: arrow.Field, fieldIndex: number) => {\n    let type = arrowDataTypeToFieldType(field.type);\n    let analyzerType = arrowDataTypeToAnalyzerDataType(field.type);\n    let format = '';\n\n    const fieldTypeSuggestion = fieldTypeSuggestions[field.name];\n    const keplerField = keplerFields[fieldIndex];\n\n    // geometry fields produced by DuckDB's st_asgeojson()\n    if (fieldTypeSuggestion === 'JSON') {\n      type = ALL_FIELD_TYPES.geojson;\n      analyzerType = AnalyzerDATA_TYPES.GEOMETRY_FROM_STRING;\n    } else if (\n      fieldTypeSuggestion === 'GEOMETRY' ||\n      field.metadata.get(GEOARROW_METADATA_KEY)?.startsWith('geoarrow')\n    ) {\n      type = ALL_FIELD_TYPES.geoarrow;\n      analyzerType = AnalyzerDATA_TYPES.GEOMETRY;\n    } else if (geoArrowMetadata[field.name]) {\n      type = ALL_FIELD_TYPES.geoarrow;\n      analyzerType = AnalyzerDATA_TYPES.GEOMETRY;\n      field.metadata?.set(GEOARROW_METADATA_KEY, geoArrowMetadata[field.name]);\n    } else if (fieldTypeSuggestion === 'BLOB') {\n      // When arrow wkb column saved to DuckDB as BLOB without any metadata, then queried back\n      try {\n        const data = table.getChildAt(fieldIndex)?.get(0);\n        if (data) {\n          const binaryGeo = parseSync(data, WKBLoader);\n          if (binaryGeo) {\n            type = ALL_FIELD_TYPES.geoarrow;\n            analyzerType = AnalyzerDATA_TYPES.GEOMETRY;\n            field.metadata?.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.WKB);\n          }\n        }\n      } catch (error) {\n        // ignore, not WKB\n      }\n    } else if (\n      fieldTypeSuggestion === 'VARCHAR' &&\n      (keplerField.analyzerType === AnalyzerDATA_TYPES.GEOMETRY ||\n        keplerField.analyzerType === AnalyzerDATA_TYPES.GEOMETRY_FROM_STRING)\n    ) {\n      // When wkb/wkt was saved as varchar in DuckDB\n      type = keplerField.type;\n      analyzerType = keplerField.analyzerType;\n      format = keplerField.format;\n    } else if (fieldTypeSuggestion === 'VARCHAR' && keplerField.type === ALL_FIELD_TYPES.h3) {\n      // when kepler detected h3 column using getFieldsFromData(), set type to h3 and analyzerType to H3\n      type = ALL_FIELD_TYPES.h3;\n      analyzerType = keplerField.analyzerType;\n    } else {\n      // TODO should we use Kepler getFieldsFromData instead\n      // of arrowDataTypeToFieldType for all fields?\n      if (keplerField.type === ALL_FIELD_TYPES.timestamp) {\n        type = keplerField.type;\n        analyzerType = keplerField.analyzerType;\n        format = keplerField.format;\n      }\n    }\n\n    return {\n      ...field,\n      name: field.name,\n      id: field.name,\n      displayName: field.name,\n      format: format,\n      fieldIdx: fieldIndex,\n      type,\n      analyzerType,\n      valueAccessor: (dc: any) => d => {\n        return dc.valueAt(d.index, fieldIndex);\n      },\n      metadata: field.metadata\n    };\n  });\n}\n\n/**\n * Parse arrow batches returned from parseInBatches()\n *\n * @param arrowTable the arrow table to parse\n * @returns dataset containing `fields` and `rows` or null\n */\nexport function processArrowBatches(arrowBatches: arrow.RecordBatch[]): ProcessorResult | null {\n  if (arrowBatches.length === 0) {\n    return null;\n  }\n  const arrowTable = new arrow.Table(arrowBatches);\n  const fields = arrowSchemaToFields(arrowTable);\n\n  const cols = [...Array(arrowTable.numCols).keys()].map(i => arrowTable.getChildAt(i));\n\n  // return empty rows and use raw arrow table to construct column-wise data container\n  return {\n    fields,\n    rows: [],\n    cols,\n    metadata: arrowTable.schema.metadata,\n    // Save original arrow schema, for better ingestion into DuckDB.\n    // TODO consider returning arrowTable in cols, not an array of Vectors from arrowTable.\n    arrowSchema: arrowTable.schema\n  };\n}\n\nexport const DATASET_HANDLERS = {\n  [DATASET_FORMATS.row]: processRowObject,\n  [DATASET_FORMATS.geojson]: processGeojson,\n  [DATASET_FORMATS.csv]: processCsvData,\n  [DATASET_FORMATS.arrow]: processArrowTable,\n  [DATASET_FORMATS.keplergl]: processKeplerglDataset\n};\n\nexport const Processors: {\n  processGeojson: typeof processGeojson;\n  processCsvData: typeof processCsvData;\n  processArrowTable: typeof processArrowTable;\n  processArrowBatches: typeof processArrowBatches;\n  processRowObject: typeof processRowObject;\n  processKeplerglJSON: typeof processKeplerglJSON;\n  processKeplerglDataset: typeof processKeplerglDataset;\n  analyzerTypeToFieldType: typeof analyzerTypeToFieldType;\n  getFieldsFromData: typeof getFieldsFromData;\n  parseCsvRowsByFieldType: typeof parseCsvRowsByFieldType;\n} = {\n  processGeojson,\n  processCsvData,\n  processArrowTable,\n  processArrowBatches,\n  processRowObject,\n  processKeplerglJSON,\n  processKeplerglDataset,\n  analyzerTypeToFieldType,\n  getFieldsFromData,\n  parseCsvRowsByFieldType\n};\n"
  },
  {
    "path": "src/processors/src/file-handler.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {parseInBatches} from '@loaders.gl/core';\nimport {JSONLoader, _JSONPath} from '@loaders.gl/json';\nimport {CSVLoader} from '@loaders.gl/csv';\nimport {GeoArrowLoader} from '@loaders.gl/arrow';\nimport {ParquetWasmLoader} from '@loaders.gl/parquet';\nimport {Loader} from '@loaders.gl/loader-utils';\nimport {\n  isPlainObject,\n  generateHashIdFromString,\n  getApplicationConfig,\n  getError,\n  isArrowTable\n} from '@kepler.gl/utils';\nimport {generateHashId} from '@kepler.gl/common-utils';\nimport {DATASET_FORMATS} from '@kepler.gl/constants';\nimport {AddDataToMapPayload, Feature, LoadedMap, ProcessorResult} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {FeatureCollection} from '@turf/helpers';\n\nimport {\n  processArrowBatches,\n  processGeojson,\n  processKeplerglJSON,\n  processRowObject\n} from './data-processor';\n\nimport {FileCacheItem, ValidKeplerGlMap} from './types';\n\nconst BATCH_TYPE = {\n  METADATA: 'metadata',\n  PARTIAL_RESULT: 'partial-result',\n  FINAL_RESULT: 'final-result'\n};\n\nconst CSV_LOADER_OPTIONS = {\n  shape: 'object-row-table',\n  dynamicTyping: false // not working for now\n};\n\nconst ARROW_LOADER_OPTIONS = {\n  shape: 'arrow-table',\n  batchDebounceMs: 10 // time to delay between batches, for incremental loading\n};\n\nconst PARQUET_LOADER_OPTIONS = {\n  shape: 'arrow-table'\n};\n\nconst JSON_LOADER_OPTIONS = {\n  shape: 'object-row-table',\n  // instruct loaders.gl on what json paths to stream\n  jsonpaths: [\n    '$', // JSON Row array\n    '$.features', // GeoJSON\n    '$.datasets' // KeplerGL JSON\n  ]\n};\n\nexport type ProcessFileDataContent = {\n  data: unknown;\n  fileName: string;\n  length?: number;\n  progress?: {rowCount?: number; rowCountInBatch?: number; percent?: number};\n  /**  metadata e.g. for arrow data, metadata could be the schema.fields */\n  metadata?: Map<string, string>;\n};\n\nexport {isArrowTable};\n\n/**\n * check if data is an ArrowData object, which is an array of RecordBatch\n * @param data - object to check\n * @returns {boolean} - true if data is an ArrowData object type guarded\n */\nexport function isArrowData(data: any): boolean {\n  return Array.isArray(data) && Boolean(data.length && data[0].data && data[0].schema);\n}\n\nexport function isGeoJson(json: unknown): json is Feature | FeatureCollection {\n  // json can be feature collection\n  // or single feature\n  return isPlainObject(json) && (isFeature(json) || isFeatureCollection(json));\n}\n\nexport function isFeature(json: unknown): json is Feature {\n  return isPlainObject(json) && json.type === 'Feature' && Boolean(json.geometry);\n}\n\nexport function isFeatureCollection(json: unknown): json is FeatureCollection {\n  return isPlainObject(json) && json.type === 'FeatureCollection' && Boolean(json.features);\n}\n\nexport function isRowObject(json: any): boolean {\n  return Array.isArray(json) && isPlainObject(json[0]);\n}\n\nexport function isKeplerGlMap(json: unknown): json is ValidKeplerGlMap {\n  return Boolean(\n    isPlainObject(json) &&\n      json.datasets &&\n      json.config &&\n      json.info &&\n      isPlainObject(json.info) &&\n      json.info.app === 'kepler.gl'\n  );\n}\n\nexport async function* makeProgressIterator(\n  asyncIterator: AsyncIterable<any>,\n  info: {size: number}\n): AsyncGenerator {\n  let rowCount = 0;\n\n  for await (const batch of asyncIterator) {\n    // the length could be stored in `batch.length` for arrow batch\n    const rowCountInBatch = (batch.data && (batch.data.length || batch.length)) || 0;\n    rowCount += rowCountInBatch;\n    const percent = Number.isFinite(batch.bytesUsed) ? batch.bytesUsed / info.size : null;\n\n    // Update progress object\n    const progress = {\n      rowCount,\n      rowCountInBatch,\n      ...(Number.isFinite(percent) ? {percent} : {})\n    };\n\n    yield {...batch, progress};\n  }\n}\n\n// eslint-disable-next-line complexity\nexport async function* readBatch(\n  asyncIterator: AsyncIterable<any>,\n  fileName: string\n): AsyncGenerator {\n  let result = null;\n  const batches = <any>[];\n  for await (const batch of asyncIterator) {\n    // Last batch will have this special type and will provide all the root\n    // properties of the parsed document.\n    // Only json parse will have `FINAL_RESULT`\n    if (batch.batchType === BATCH_TYPE.FINAL_RESULT) {\n      if (batch.container) {\n        result = {...batch.container};\n      }\n      // Set the streamed data correctly is Batch json path is set\n      // and the path streamed is not the top level object (jsonpath = '$')\n      if (batch.jsonpath && batch.jsonpath.length > 1) {\n        const streamingPath = new _JSONPath(batch.jsonpath);\n        streamingPath.setFieldAtPath(result, batches);\n      } else if (batch.jsonpath && batch.jsonpath.length === 1) {\n        // The streamed object is a ROW JSON-batch (jsonpath = '$')\n        // row objects\n        result = batches;\n      }\n    } else {\n      const batchData = isArrowTable(batch.data) ? batch.data.batches : batch.data;\n      for (let i = 0; i < batchData?.length; i++) {\n        batches.push(batchData[i]);\n      }\n    }\n\n    yield {\n      ...batch,\n      ...(batch.schema ? {headers: Object.keys(batch.schema)} : {}),\n      fileName,\n      // if dataset is CSV, data is set to the raw batches\n      data: result ? result : batches\n    };\n  }\n}\n\nexport async function readFileInBatches({\n  file,\n  loaders = [],\n  loadOptions = {}\n}: {\n  file: File;\n  fileCache: FileCacheItem[];\n  loaders: Loader[];\n  loadOptions: any;\n}): Promise<AsyncGenerator> {\n  loaders = [JSONLoader, CSVLoader, GeoArrowLoader, ParquetWasmLoader, ...loaders];\n  loadOptions = {\n    csv: CSV_LOADER_OPTIONS,\n    arrow: ARROW_LOADER_OPTIONS,\n    json: JSON_LOADER_OPTIONS,\n    parquet: PARQUET_LOADER_OPTIONS,\n    metadata: true,\n    ...loadOptions\n  };\n\n  const batchIterator = await parseInBatches(file, loaders, loadOptions);\n  const progressIterator = makeProgressIterator(batchIterator, {size: file.size});\n\n  return readBatch(progressIterator, file.name);\n}\n\nexport async function processFileData({\n  content,\n  fileCache\n}: {\n  content: ProcessFileDataContent;\n  fileCache: FileCacheItem[];\n}): Promise<FileCacheItem[]> {\n  const {fileName, data} = content;\n  let format: string | undefined;\n  let processor: ((data: any) => ProcessorResult | LoadedMap | null) | undefined;\n  // generate unique id with length of 4 using fileName string\n  const id = generateHashIdFromString(fileName);\n  // decide on which table class to use based on application config\n  const table = getApplicationConfig().table ?? KeplerTable;\n\n  if (typeof table.getFileProcessor === 'function') {\n    // use custom processors from table class\n    const processorResult = table.getFileProcessor(data);\n    format = processorResult.format;\n    processor = processorResult.processor;\n  } else {\n    // use default processors\n    if (isArrowData(data)) {\n      format = DATASET_FORMATS.arrow;\n      processor = processArrowBatches;\n    } else if (isKeplerGlMap(data)) {\n      format = DATASET_FORMATS.keplergl;\n      processor = processKeplerglJSON;\n    } else if (isRowObject(data)) {\n      // csv file goes here\n      format = DATASET_FORMATS.row;\n      processor = processRowObject;\n    } else if (isGeoJson(data)) {\n      format = DATASET_FORMATS.geojson;\n      processor = processGeojson;\n    }\n  }\n  if (format && processor) {\n    // eslint-disable-next-line no-useless-catch\n    let result;\n    try {\n      result = await processor(data);\n    } catch (error) {\n      throw new Error(`Can not process uploaded file, ${getError(error as Error)}`);\n    }\n\n    return [\n      ...fileCache,\n      {\n        data: result,\n        info: {\n          id,\n          label: content.fileName,\n          format\n        }\n      }\n    ];\n  } else {\n    throw new Error('Can not process uploaded file, unknown file format');\n  }\n}\n\nexport function filesToDataPayload(fileCache: FileCacheItem[]): AddDataToMapPayload[] {\n  // seperate out files which could be a single datasets. or a keplergl map json\n  const collection = fileCache.reduce<{\n    datasets: FileCacheItem[];\n    keplerMaps: AddDataToMapPayload[];\n  }>(\n    (accu, file) => {\n      const {data, info} = file;\n      if (info?.format === DATASET_FORMATS.keplergl) {\n        // if file contains a single kepler map dataset & config\n        accu.keplerMaps.push({\n          ...data,\n          options: {\n            centerMap: !(data.config && data.config.mapState)\n          }\n        });\n      } else if (DATASET_FORMATS[info?.format]) {\n        // if file contains only data\n        const newDataset = {\n          data,\n          info: {\n            id: info?.id || generateHashId(4),\n            ...(info || {})\n          }\n        };\n        accu.datasets.push(newDataset);\n      }\n      return accu;\n    },\n    {datasets: [], keplerMaps: []}\n  );\n\n  // add kepler map first with config\n  // add datasets later in one add data call\n  return collection.keplerMaps.concat({datasets: collection.datasets});\n}\n"
  },
  {
    "path": "src/processors/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './data-processor';\nexport * from './file-handler';\nexport * from './types';\n"
  },
  {
    "path": "src/processors/src/typedefs/deckgl.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Eslint does not seem to be able to understand the namespace re-export here\n/* eslint-disable */\n\nimport * as DeckTypings from '@danmarshall/deckgl-typings';\n\ndeclare module 'deck.gl' {\n  export namespace DeckTypings {}\n}\n"
  },
  {
    "path": "src/processors/src/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport type FileCacheItem = {\n  data: any;\n  info: {\n    id?: string;\n    label: string;\n    format: string;\n  };\n};\n\nexport type ValidKeplerGlMap = {\n  datasets: unknown;\n  config: unknown;\n  info: Record<string, string>;\n};\n"
  },
  {
    "path": "src/processors/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/processors/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/reducers/UPGRADE-data-container.md",
    "content": "# Upgrade Guide (Data Containers)\n\n### Major changes\n- `KeplerTable.allData: any[][]` is substituted with `KeplerTable.dataContainer: DataContainerInterface`.\n\n### Step by step upgrade\n| File | Function | Change |\n| ---- | --- | --- |\n| base-layer.js & extended layers | accessors | Accessors in default Kepler layers now expect a data container as one of parameters: ```const pointPosAccessor = config => dc => d => {return ...}``` where `dc` is an instance of `DataContainerInterface`, and `d` is an object that is expected to contain an index of a row in the data container. | \n|            | updateLayerMeta() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | getPointsBounds() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | calculateDataAttribute() | 1st parameter has to be an instance of `KeplerTable`. |\n|            | getAttributeAccessors() | 1st parameter is now an object that to contains `dataContainer: : DataContainerInterface`. |\n|            | getHoverData() |  New 2nd parameter, an instance of `DataContainerInterface`. |\n|            | getPositionAccessor() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | setInitialLayerConfig() | 1st parameter is now object expected to contain `dataContainer: : DataContainerInterface`.  |\n|            | findDefaultLayerProps() | 2nd parameter has to be an instance of `DataContainerInterface`.  |\n| cell-size.js | renderedSize() | `text.rows` is substituted with `text.dataContainer: DataContainerInterface`. |\n| data-table/index.js |  | `DataTable.props.rows` is substituted with `DataTable.props.dataContainer: DataContainerInterface`. |\n| layer-text-label.js | formatTextLabelData() | `dataContainer: DataContainerInterface` is expected as part of the 1st parameter. |\n| geojson-utils.js | getGeojsonDataMaps() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| h3-utils.js | isHexField() | 3rd parameter has to be an instance of `DataContainerInterface`. |\n|             | getHexFields() | 2nd parameter has to be an instance of `DataContainerInterface`. |\n| mapbox-utils.js | geoJsonFromData() | `allData` is removed from the parameter list. `getGeometry`, `getProperties` parameters expect `{index}` object as input parameter. |\n| trip-utils.js | isTripGeoJsonField() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| data-processor.js | formatCsv() | `data` parameter has to be an instance of `DataContainerInterface`. |\n| data-scale-utils.js | getOrdinalDomain() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| filter-utils.js | getFilterFunction() | New 5th parameter, an instance of `DataContainerInterface`. Returned function expects `{index}` object as 1st parameter.  |\n|                 | filterDataByFilterTypes() | 2nd parameter has to be an instance of `DataContainerInterface`. |\t\n|                 | getTimestampFieldDomain() | 1st parameter has to be an instance of `DataContainerInterface`. valueAccessor parameter expects a function that accepts `{index}` object as 1st parameter. |\t\t\n|                 | getNumericFieldDomain() |\t1st parameter has to be an instance of `DataContainerInterface`. valueAccessor parameter expects a function that accepts `{index}` object as 1st parameter. |\n|                 | getPolygonFilterFunctor() | New 3rd parameter, an instance of `DataContainerInterface`. |\n| interaction-utils.js | getTooltipDisplayDeltaValue() | data and primaryData parameters are now of `DataRow` type. |\n|                      | getTooltipDisplayValue() | data parameter is now of `DataRow` type. |\n| comparison-utils.js |\tcmpGpuFilterProp() | New 4th parameter, an instance of `DataContainerInterface`. |\n| gpu-filter-utils.js | getGpuFilterProps() | Returned function now expects a data container. ```dc => (getIndex, getData) => d => {...}``` as parameter to the first call. |\n| data-utils.js | maybeToDate() | New 4th parameter, an instance of `DataContainerInterface`. |\n| kepler-table.js | Field.valueAccessor() | 1st parameter is expected to be an object that contain index property. | "
  },
  {
    "path": "src/reducers/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/reducers/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/reducers\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/actions\": \"3.2.6\",\n    \"@kepler.gl/cloud-providers\": \"3.2.6\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/deckgl-arrow-layers\": \"3.2.6\",\n    \"@kepler.gl/deckgl-layers\": \"3.2.6\",\n    \"@kepler.gl/effects\": \"3.2.6\",\n    \"@kepler.gl/layers\": \"3.2.6\",\n    \"@kepler.gl/localization\": \"3.2.6\",\n    \"@kepler.gl/processors\": \"3.2.6\",\n    \"@kepler.gl/schemas\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/tasks\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/loader-utils\": \"^4.3.2\",\n    \"@mapbox/geo-viewport\": \"^0.4.1\",\n    \"@math.gl/web-mercator\": \"^3.6.2\",\n    \"@turf/bbox\": \"^6.0.1\",\n    \"@turf/bbox-polygon\": \"^6.0.1\",\n    \"@turf/boolean-within\": \"^6.0.1\",\n    \"@types/lodash\": \"4.17.5\",\n    \"@types/redux-actions\": \"^2.6.2\",\n    \"copy-to-clipboard\": \"^3.3.1\",\n    \"d3-color\": \"^2.0.0\",\n    \"d3-dsv\": \"^2.0.0\",\n    \"deepmerge\": \"^4.2.2\",\n    \"global\": \"^4.3.0\",\n    \"lodash\": \"4.17.21\",\n    \"react-palm\": \"^3.3.8\",\n    \"redux\": \"^4.2.1\",\n    \"redux-actions\": \"^2.2.1\",\n    \"reselect\": \"^4.1.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/reducers/src/combined-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  toggleModalUpdater,\n  loadFilesSuccessUpdater as uiStateLoadFilesSuccessUpdater,\n  setMapControlSettingsUpdater as uiStateSetMapControlSettingsUpdater,\n  toggleMapControlUpdater as uiStateToggleMapControlUpdater,\n  toggleMapControlUpdater,\n  toggleSplitMapUpdater as uiStateToggleSplitMapUpdater\n} from './ui-state-updaters';\nimport {\n  updateVisDataUpdater as visStateUpdateVisDataUpdater,\n  setMapInfoUpdater,\n  layerTypeChangeUpdater,\n  toggleSplitMapUpdater as visStateToggleSplitMapUpdater,\n  prepareStateForDatasetReplace\n} from './vis-state-updaters';\nimport {\n  receiveMapConfigUpdater as stateMapConfigUpdater,\n  toggleSplitMapUpdater as mapStateToggleSplitMapUpdater\n} from './map-state-updaters';\nimport {\n  mapStyleChangeUpdater,\n  receiveMapConfigUpdater as styleMapConfigUpdater\n} from './map-style-updaters';\nimport {filesToDataPayload} from '@kepler.gl/processors';\nimport {payload_, apply_, with_, if_, compose_, merge_, pick_} from './composer-helpers';\nimport {MapState, UiState, AddDataToMapPayload, ParsedConfig} from '@kepler.gl/types';\nimport {MapStyle} from './map-style-updaters';\nimport {ProviderState} from './provider-state-updaters';\nimport {\n  loadFilesSuccessUpdaterAction,\n  MapStyleChangeUpdaterAction,\n  LayerTypeChangeUpdaterAction,\n  ToggleSplitMapUpdaterAction,\n  ReplaceDataInMapPayload\n} from '@kepler.gl/actions';\nimport {VisState} from '@kepler.gl/schemas';\nimport {Layer} from '@kepler.gl/layers';\nimport {isPlainObject} from '@kepler.gl/utils';\nimport {findMapBounds} from './data-utils';\nimport {BASE_MAP_COLOR_MODES, OVERLAY_BLENDINGS} from '@kepler.gl/constants';\n\nexport type KeplerGlState = {\n  visState: VisState;\n  mapState: MapState;\n  mapStyle: MapStyle;\n  uiState: UiState;\n  providerState: ProviderState;\n};\n\n// compose action to apply result multiple reducers, with the output of one\n\n/**\n * Some actions will affect the entire kepler.lg instance state.\n * The updaters for these actions is exported as `combinedUpdaters`. These updater take the entire instance state\n * as the first argument. Read more about [Using updaters](../advanced-usage/using-updaters.md)\n * @public\n * @example\n *\n * import keplerGlReducer, {combinedUpdaters} from '@kepler.gl/reducers';\n * // Root Reducer\n * const reducers = combineReducers({\n *  keplerGl: keplerGlReducer,\n *  app: appReducer\n * });\n *\n * const composedReducer = (state, action) => {\n *  switch (action.type) {\n *    // add data to map after receiving data from remote sources\n *    case 'LOAD_REMOTE_RESOURCE_SUCCESS':\n *      return {\n *        ...state,\n *        keplerGl: {\n *          ...state.keplerGl,\n *          // pass in kepler.gl instance state to combinedUpdaters\n *          map:  combinedUpdaters.addDataToMapUpdater(\n *           state.keplerGl.map,\n *           {\n *             payload: {\n *               datasets: action.datasets,\n *               options: {readOnly: true},\n *               config: action.config\n *              }\n *            }\n *          )\n *        }\n *      };\n *  }\n *  return reducers(state, action);\n * };\n *\n * export default composedReducer;\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst combinedUpdaters = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n\nexport const isValidConfig = config =>\n  isPlainObject(config) && isPlainObject(config.config) && config.version;\n\nexport const defaultAddDataToMapOptions = {\n  centerMap: true,\n  keepExistingConfig: false,\n  autoCreateLayers: true,\n  autoCreateTooltips: true\n};\n\n/**\n * Combine data and full configuration update in a single action\n *\n * @memberof combinedUpdaters\n * @param {Object} state kepler.gl instance state, containing all subreducer state\n * @param {Object} action\n * @param {Object} action.payload `{datasets, options, config}`\n * @param action.payload.datasets - ***required** datasets can be a dataset or an array of datasets\n * Each dataset object needs to have `info` and `data` property.\n * @param [action.payload.options] option object `{centerMap: true}`\n * @param [action.payload.config] map config\n * @param [action.payload.info] map info contains title and description\n * @returns nextState\n *\n * @typedef {Object} Dataset\n * @property info -info of a dataset\n * @property info.id - id of this dataset. If config is defined, `id` should matches the `dataId` in config.\n * @property info.label - A display name of this dataset\n * @property data - ***required** The data object, in a tabular format with 2 properties `fields` and `rows`\n * @property data.fields - ***required** Array of fields,\n * @property data.fields.name - ***required** Name of the field,\n * @property data.rows - ***required** Array of rows, in a tabular format with `fields` and `rows`\n *\n * @public\n */\nexport const addDataToMapUpdater = (\n  state: KeplerGlState,\n  {payload}: {payload: AddDataToMapPayload}\n): KeplerGlState => {\n  const {datasets, config, info} = payload;\n\n  const options = {\n    ...defaultAddDataToMapOptions,\n    ...payload.options\n  };\n\n  // check if progressive loading dataset by batches, and update visState directly\n  const isProgressiveLoading =\n    Array.isArray(datasets) &&\n    datasets[0]?.info.format === 'arrow' &&\n    datasets[0]?.info.id &&\n    datasets[0]?.info.id in state.visState.datasets;\n  if (isProgressiveLoading) {\n    return compose_<KeplerGlState>([\n      pick_('visState')(\n        apply_<VisState, any>(visStateUpdateVisDataUpdater, {\n          datasets,\n          options,\n          config\n        })\n      )\n    ])(state);\n  }\n\n  // @ts-expect-error\n  let parsedConfig: ParsedConfig = config;\n\n  if (isValidConfig(config)) {\n    // if passed in saved config\n    // @ts-expect-error\n    parsedConfig = state.visState.schema.parseSavedConfig(config);\n  }\n  const oldLayers = state.visState.layers;\n  const filterNewlyAddedLayers = (layers: Layer[]) =>\n    layers.filter(nl => !oldLayers.find(ol => ol === nl));\n\n  // Returns undefined if not found, to make typescript happy\n  const findMapBoundsIfCentered = (layers: Layer[]) => {\n    const bounds = options.centerMap && findMapBounds(layers);\n    return bounds ? bounds : undefined;\n  };\n\n  return compose_<KeplerGlState>([\n    pick_('visState')(\n      // this part can be async\n      apply_<VisState, any>(visStateUpdateVisDataUpdater, {\n        datasets,\n        options,\n        config: parsedConfig\n      })\n    ),\n\n    if_(Boolean(info), pick_('visState')(apply_<VisState, any>(setMapInfoUpdater, {info}))),\n    // Note that fit bounds here won't be called in case datasets are created in Tasks.\n    // A separate Task to update bounds is created once the datasets are ready.\n    with_(({visState}) =>\n      pick_('mapState')(\n        apply_(\n          stateMapConfigUpdater,\n          payload_({\n            config: parsedConfig,\n            options,\n            bounds: findMapBoundsIfCentered(filterNewlyAddedLayers(visState.layers))\n          })\n        )\n      )\n    ),\n    pick_('mapStyle')(apply_(styleMapConfigUpdater, payload_({config: parsedConfig, options}))),\n    pick_('uiState')(apply_(uiStateLoadFilesSuccessUpdater, payload_(null))),\n\n    if_(\n      Boolean(parsedConfig?.uiState?.mapControls?.mapLegend?.active),\n      pick_('uiState')(\n        apply_(uiStateToggleMapControlUpdater, payload_({panelId: 'mapLegend', index: 0}))\n      )\n    ),\n\n    if_(\n      Boolean(parsedConfig?.uiState?.mapControls?.mapLegend?.settings),\n      pick_('uiState')(\n        apply_(\n          uiStateSetMapControlSettingsUpdater,\n          payload_({\n            panelId: 'mapLegend',\n            settings: parsedConfig?.uiState?.mapControls?.mapLegend?.settings ?? {}\n          })\n        )\n      )\n    ),\n    pick_('uiState')(apply_(toggleModalUpdater, payload_(null))),\n    pick_('uiState')(\n      merge_(\n        Object.prototype.hasOwnProperty.call(options, 'readOnly')\n          ? {readOnly: options.readOnly}\n          : {}\n      )\n    )\n  ])(state);\n};\n\nexport const loadFilesSuccessUpdater = (\n  state: KeplerGlState,\n  action: loadFilesSuccessUpdaterAction\n): KeplerGlState => {\n  // still more to load\n  const payloads = filesToDataPayload(action.result);\n  const nextState = compose_([\n    pick_('visState')(\n      merge_({\n        fileLoading: false,\n        fileLoadingProgress: {}\n      })\n    )\n  ])(state);\n  // make multiple add data to map calls\n  const stateWithData = compose_(payloads.map(p => apply_(addDataToMapUpdater, payload_(p))))(\n    nextState\n  );\n  return stateWithData as KeplerGlState;\n};\n\nexport const addDataToMapComposed = addDataToMapUpdater;\n\n/**\n * Helper which updates map overlay blending mode in visState,\n * but only if it's not currently in the `normal` mode.\n */\nconst updateOverlayBlending = overlayBlending => visState => {\n  if (visState.overlayBlending !== OVERLAY_BLENDINGS.normal.value) {\n    return {\n      ...visState,\n      overlayBlending\n    };\n  }\n  return visState;\n};\n\n/**\n * Helper which updates `darkBaseMapEnabled` in all the layers in visState which\n * have this config setting (or in one specific layer if the `layerId` param is provided).\n */\nconst updateDarkBaseMapLayers =\n  (darkBaseMapEnabled: boolean, layerId: string | null = null) =>\n  visState => ({\n    ...visState,\n    layers: visState.layers.map(layer => {\n      if (!layerId || layer.id === layerId) {\n        if (Object.prototype.hasOwnProperty.call(layer.visConfigSettings, 'darkBaseMapEnabled')) {\n          const {visConfig} = layer.config;\n          return layer.updateLayerConfig({\n            visConfig: {...visConfig, darkBaseMapEnabled}\n          });\n        }\n      }\n      return layer;\n    })\n  });\n\n/**\n * Updater that changes the map style by calling mapStyleChangeUpdater on visState.\n * In addition to that, it does the following:\n *\n *   1. Update map overlay blending mode in accordance with the colorMode of the\n *      base map, but only if it's not in the `normal` mode.\n *\n *   2. Update all the layers which have the `darkBaseMapEnabled` config setting\n *      adjusting it in accordance with the colorMode of the base map.\n *\n */\nexport const combinedMapStyleChangeUpdater = (\n  state: KeplerGlState,\n  action: MapStyleChangeUpdaterAction\n): KeplerGlState => {\n  const {payload} = action;\n  const {mapStyle} = state;\n  const getColorMode = key => mapStyle.mapStyles[key]?.colorMode;\n  const prevColorMode = getColorMode(mapStyle.styleType);\n  const nextColorMode = getColorMode(payload.styleType);\n  let {visState} = state;\n  if (nextColorMode !== prevColorMode) {\n    switch (nextColorMode) {\n      case BASE_MAP_COLOR_MODES.DARK:\n        visState = compose_([\n          updateOverlayBlending(OVERLAY_BLENDINGS.screen.value),\n          updateDarkBaseMapLayers(true)\n        ])(visState);\n        break;\n      case BASE_MAP_COLOR_MODES.LIGHT:\n        visState = compose_([\n          updateOverlayBlending(OVERLAY_BLENDINGS.darken.value),\n          updateDarkBaseMapLayers(false)\n        ])(visState);\n        break;\n      default:\n      // do nothing\n    }\n  }\n  return {\n    ...state,\n    visState,\n    mapStyle: mapStyleChangeUpdater(mapStyle, {payload: {...payload}})\n  };\n};\n\n/**\n * Updater that changes the layer type by calling `layerTypeChangeUpdater` on visState.\n * In addition to that, if the new layer type has the `darkBaseMapEnabled` config\n * setting, we adjust it in accordance with the colorMode of the base map.s\n */\nexport const combinedLayerTypeChangeUpdater = (\n  state: KeplerGlState,\n  action: LayerTypeChangeUpdaterAction\n): KeplerGlState => {\n  let {visState} = state;\n  const oldLayerIndex = visState.layers.findIndex(layer => layer === action.oldLayer);\n  visState = layerTypeChangeUpdater(visState, action);\n  const newLayer = visState.layers[oldLayerIndex];\n  if (Object.prototype.hasOwnProperty.call(newLayer?.visConfigSettings, 'darkBaseMapEnabled')) {\n    const {mapStyle} = state;\n    const {colorMode} = mapStyle.mapStyles[mapStyle.styleType];\n    const {darkBaseMapEnabled} = newLayer.config.visConfig;\n    switch (colorMode) {\n      case BASE_MAP_COLOR_MODES.DARK:\n        if (!darkBaseMapEnabled) {\n          visState = updateDarkBaseMapLayers(true, newLayer.id)(visState);\n        }\n        break;\n      case BASE_MAP_COLOR_MODES.LIGHT:\n        if (darkBaseMapEnabled) {\n          visState = updateDarkBaseMapLayers(false, newLayer.id)(visState);\n        }\n        break;\n      default:\n      // do nothing\n    }\n  }\n  return {\n    ...state,\n    visState\n  };\n};\n\n/**\n * Make mapLegend active when toggleSplitMap action is called\n */\nexport const toggleSplitMapUpdater = (\n  state: KeplerGlState,\n  action: ToggleSplitMapUpdaterAction\n): KeplerGlState => {\n  const newState = {\n    ...state,\n    visState: visStateToggleSplitMapUpdater(state.visState, action),\n    uiState: uiStateToggleSplitMapUpdater(state.uiState),\n    mapState: mapStateToggleSplitMapUpdater(state.mapState)\n  };\n\n  const isSplit = newState.visState.splitMaps.length !== 0;\n  const isLegendActive = newState.uiState.mapControls?.mapLegend?.active;\n  if (isSplit && !isLegendActive) {\n    newState.uiState = toggleMapControlUpdater(newState.uiState, {\n      payload: {panelId: 'mapLegend', index: action.payload}\n    });\n  }\n\n  return newState;\n};\n\nconst defaultReplaceDataToMapOptions = {\n  keepExistingConfig: true,\n  centerMap: true,\n  autoCreateLayers: false\n};\n\n/**\n * Updater replace a dataset in state\n */\nexport const replaceDataInMapUpdater = (\n  state: KeplerGlState,\n  {payload}: {payload: ReplaceDataInMapPayload}\n): KeplerGlState => {\n  const {datasetToReplaceId, datasetToUse, options = {}} = payload;\n  const addDataToMapOptions = {...defaultReplaceDataToMapOptions, ...options};\n\n  // check if dataset is there\n  if (!state.visState.datasets[datasetToReplaceId]) {\n    return state;\n  }\n  // datasetToUse is ProtoDataset\n  const dataIdToUse = datasetToUse.info.id;\n  if (!dataIdToUse) {\n    return state;\n  }\n  // remove dataset and put dependencies in toBeMerged\n  const preparedState = {\n    ...state,\n    visState: prepareStateForDatasetReplace(state.visState, datasetToReplaceId, dataIdToUse)\n  };\n\n  const nextState = addDataToMapUpdater(\n    preparedState,\n    payload_({\n      datasets: datasetToUse,\n      // should zoom to new dataset\n      options: addDataToMapOptions\n    })\n  );\n\n  return nextState;\n};\n"
  },
  {
    "path": "src/reducers/src/composer-helpers.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Console from 'global/console';\n\nconst identity = state => state;\n\n/** Returns a function that logs a value with a given message */\nexport function log(text: string): (value: any) => void {\n  return value => Console.log(text, value);\n}\n/** Wraps a value in an object and stores it the `payload` field */\nexport function payload_<P>(payload: P) {\n  return {payload};\n}\n/** Wraps a value in an object and stores it the `payload` field */\nexport function apply_<State, P>(\n  updater: (state: State, nextPayload: P) => State,\n  payload: P\n): (state: State) => State {\n  return state => updater(state, payload);\n}\n\ntype Arg<State> = (state: State) => (nextState: State) => State;\nexport function with_<State>(fn: Arg<State>): (state: State) => State {\n  return state => fn(state)(state);\n}\n\nexport function if_<State>(pred: boolean, fn: (state: State) => State): (state: State) => State {\n  return pred ? fn : identity;\n}\n\nexport function compose_<State>(fns: Array<(s: State) => State>): (s: State) => State {\n  return state => fns.reduce((state2, fn) => fn(state2), state);\n}\n/** Returns a reducer function that merges props with state */\nexport function merge_<Props>(obj: Props): <State>(state: State) => State {\n  return state => ({...state, ...obj});\n}\n\nexport function pick_<Prop extends string>(\n  prop: Prop\n): <Value>(fn: (p: Value) => Value) => <State extends Record<Prop, Value>>(state: State) => State {\n  return fn => state => ({...state, [prop]: fn(state[prop])});\n}\n\nexport function swap_<X extends {id: string}>(item: X): (arr: X[]) => X[] {\n  return arr => arr.map(a => (a.id === item.id ? item : a));\n}\n\nexport function map_<X, T>(fn: (state: X) => T): (arr: X[]) => T[] {\n  return arr => arr.map(e => fn(e));\n}\n\nexport function filterOutById<X extends {id: string}>(id: string): (arr: X[]) => X[] {\n  return arr => arr.filter(e => e.id !== id);\n}\n\nexport function removeElementAtIndex<X>(index: number): (arr: X[]) => X[] {\n  return arr => [...arr.slice(0, index), ...arr.slice(index + 1, arr.length)];\n}\n"
  },
  {
    "path": "src/reducers/src/composers.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ActionTypes} from '@kepler.gl/actions';\nimport * as combinedUpdaters from './combined-updaters';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.ADD_DATA_TO_MAP]: combinedUpdaters.addDataToMapUpdater,\n  [ActionTypes.MAP_STYLE_CHANGE]: combinedUpdaters.combinedMapStyleChangeUpdater,\n  [ActionTypes.LAYER_TYPE_CHANGE]: combinedUpdaters.combinedLayerTypeChangeUpdater,\n  [ActionTypes.LOAD_FILES_SUCCESS]: combinedUpdaters.loadFilesSuccessUpdater,\n  [ActionTypes.TOGGLE_SPLIT_MAP]: combinedUpdaters.toggleSplitMapUpdater,\n  [ActionTypes.REPLACE_DATA_IN_MAP]: combinedUpdaters.replaceDataInMapUpdater\n};\n\nexport default actionHandler;\n"
  },
  {
    "path": "src/reducers/src/core.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {combineReducers} from 'redux';\n\nimport {visStateReducerFactory} from './vis-state';\nimport {mapStateReducerFactory} from './map-state';\nimport {mapStyleReducerFactory} from './map-style';\nimport {uiStateReducerFactory} from './ui-state';\nimport {providerStateReducerFactory} from './provider-state';\n\nimport composers from './composers';\n\nimport {VisState} from '@kepler.gl/schemas';\nimport {MapState, UiState} from '@kepler.gl/types';\nimport {MapStyle} from './map-style-updaters';\nimport {ProviderState} from './provider-state-updaters';\n\nexport type KeplerGlState = {\n  visState: VisState;\n  mapState: MapState;\n  mapStyle: MapStyle;\n  uiState: UiState;\n  providerState: ProviderState;\n};\n\nconst combined = (\n  initialState: Partial<KeplerGlState> = {},\n  extraReducers: {[x: string]: unknown} = {}\n) => {\n  return combineReducers({\n    visState: visStateReducerFactory(initialState.visState),\n    mapState: mapStateReducerFactory(initialState.mapState),\n    mapStyle: mapStyleReducerFactory(initialState.mapStyle),\n    uiState: uiStateReducerFactory(initialState.uiState),\n    providerState: providerStateReducerFactory(initialState.providerState),\n    ...extraReducers\n  });\n};\n\nexport const coreReducerFactory =\n  (initialState: Partial<KeplerGlState> = {}, extraReducers = {}) =>\n  (state, action) => {\n    if (composers[action.type]) {\n      return composers[action.type](state, action);\n    }\n    return combined(initialState, extraReducers)(state, action);\n  };\n\nexport default coreReducerFactory();\n\n/**\n * Connect subreducer `mapState`, used with `injectComponents`. Learn more at\n * [Replace UI Component](../advanced-usages/replace-ui-component.md#pass-custom-component-props)\n *\n * @param {*} reduxState\n * @public\n */\nexport const mapStateLens = (reduxState: KeplerGlState) => ({mapState: reduxState.mapState});\n\n/**\n * Connect subreducer `mapStyle`, used with `injectComponents`. Learn more at\n * [Replace UI Component](../advanced-usages/replace-ui-component.md#pass-custom-component-props)\n *\n * @param {*} reduxState\n * @public\n */\nexport const mapStyleLens = (reduxState: KeplerGlState) => ({mapStyle: reduxState.mapStyle});\n\n/**\n * Connect subreducer `visState`, used with `injectComponents`. Learn more at\n * [Replace UI Component](../advanced-usages/replace-ui-component.md#pass-custom-component-props)\n *\n * @param {*} reduxState\n * @public\n */\nexport const visStateLens = (reduxState: KeplerGlState) => ({visState: reduxState.visState});\n\n/**\n * Connect subreducer `uiState`, used with `injectComponents`. Learn more at\n * [Replace UI Component](../advanced-usages/replace-ui-component.md#pass-custom-component-props)\n *\n * @param {*} reduxState\n * @public\n */\nexport const uiStateLens = (reduxState: KeplerGlState) => ({uiState: reduxState.uiState});\n\n/**\n * Connect subreducer `providerState`, used with `injectComponents`. Learn more at\n * [Replace UI Component](../advanced-usages/replace-ui-component.md#pass-custom-component-props)\n *\n * @param {*} reduxState\n * @public\n */\nexport const providerStateLens = (reduxState: KeplerGlState) => ({\n  providerState: reduxState.providerState\n});\n"
  },
  {
    "path": "src/reducers/src/data-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Bounds} from '@kepler.gl/types';\nimport {Layer} from '@kepler.gl/layers';\nimport {\n  MAX_LATITUDE,\n  MIN_LATITUDE,\n  MAX_LONGITUDE,\n  MIN_LONGITUDE,\n  validateLongitude,\n  validateLatitude\n} from '@kepler.gl/utils';\n\n/**\n * takes a list of layer bounds and returns a single bound\n */\nexport function processLayerBounds(layerBounds: Bounds[]): Bounds {\n  return layerBounds.reduce(\n    (res, b) => {\n      const minLongitude = Math.min(res[0], b[0]);\n      const minLatitude = Math.min(res[1], b[1]);\n      const maxLongitude = Math.max(res[2], b[2]);\n      const maxLatitude = Math.max(res[3], b[3]);\n\n      // for some reason WebMercatorViewport can't handle latitude -90,90 and throws an error\n      // so we default to lat/lng (0,0)\n      // viewport.js:81 Uncaught Error: Pixel project matrix not invertible\n      // at WebMercatorViewport16.Viewport5 (viewport.js:81:13)\n      // at new WebMercatorViewport16 (web-mercator-viewport.js:92:5)\n      // at getViewportFromMapState (map-utils.js:46:66)\n      return [\n        validateLongitude(minLongitude),\n        validateLatitude(minLatitude),\n        validateLongitude(maxLongitude),\n        validateLatitude(maxLatitude)\n      ];\n    },\n    [MAX_LONGITUDE, MAX_LATITUDE, MIN_LONGITUDE, MIN_LATITUDE]\n  );\n}\n\n/**\n * return center of map from given points\n * @param layers\n * @returns coordinates of map center, empty if not found\n */\nexport function findMapBounds(layers: Layer[]): Bounds | null {\n  // find bounds in formatted layerData\n  // take ALL layers into account when finding map bounds\n  const availableLayerBounds = layers.reduce((res, l) => {\n    if (l.meta && l.meta.bounds) {\n      res.push(l.meta.bounds);\n    }\n    return res;\n  }, [] as Bounds[]);\n  // return null if no layer is available\n  if (availableLayerBounds.length === 0) {\n    return null;\n  }\n  // merge bounds in each layer\n  return processLayerBounds(availableLayerBounds);\n}\n"
  },
  {
    "path": "src/reducers/src/export-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Blob} from 'global/window';\nimport {csvFormatRows} from 'd3-dsv';\n\nimport {EXPORT_DATA_TYPE} from '@kepler.gl/constants';\nimport {Field} from '@kepler.gl/types';\nimport KeplerTable, {Datasets} from '@kepler.gl/table';\n\nimport {\n  createIndexedDataContainer,\n  DataContainerInterface,\n  parseFieldValue,\n  downloadFile\n} from '@kepler.gl/utils';\nimport {getApplicationConfig} from '@kepler.gl/utils';\n\ninterface StateType {\n  visState: {datasets: Datasets};\n  appName?: string;\n}\n\nexport function exportData(state: StateType, options) {\n  const {visState, appName} = state;\n  const {datasets} = visState;\n  const {selectedDataset, dataType, filtered} = options;\n  // get the selected data\n  const filename = appName ? appName : getApplicationConfig().defaultDataName;\n  const selectedDatasets = datasets[selectedDataset]\n    ? [datasets[selectedDataset]]\n    : Object.values(datasets);\n  if (!selectedDatasets.length) {\n    // error: selected dataset not found.\n    return;\n  }\n\n  selectedDatasets.forEach(selectedData => {\n    const {dataContainer, fields, label, filteredIdxCPU = []} = selectedData as KeplerTable;\n    const toExport = filtered\n      ? createIndexedDataContainer(dataContainer, filteredIdxCPU)\n      : dataContainer;\n\n    // start to export data according to selected data type\n    switch (dataType) {\n      case EXPORT_DATA_TYPE.CSV: {\n        const csv = formatCsv(toExport, fields);\n\n        const fileBlob = new Blob([csv], {type: 'text/csv'});\n        downloadFile(fileBlob, `${filename}_${label}.csv`);\n        break;\n      }\n      // TODO: support more file types.\n      default:\n        break;\n    }\n  });\n}\n\n/**\n * On export data to csv\n * @param dataContainer\n * @param fields `dataset.fields`\n * @returns csv string\n */\nexport function formatCsv(data: DataContainerInterface, fields: Field[]): string {\n  const columns = fields.map(f => f.displayName || f.name);\n  const formattedData = [columns];\n\n  // parse geojson object as string\n  for (const row of data.rows(true)) {\n    formattedData.push(row.map((d, i) => parseFieldValue(d, fields[i].type, fields[i])));\n  }\n\n  return csvFormatRows(formattedData);\n}\n\nconst exporters = {\n  exportData\n};\n\nexport default exporters;\n"
  },
  {
    "path": "src/reducers/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// TODO: Unnecessary when eslint-plugin-prettier is upgraded\n/* eslint-disable prettier/prettier */\n\n// Root Reducer, used to register, and remove core reducers of each instance\nexport {default} from './root';\nexport {default as keplerGlReducer} from './root';\n\n// Core Reducer\nexport {\n  default as keplerGlReducerCore,\n  visStateLens,\n  mapStateLens,\n  uiStateLens,\n  mapStyleLens\n} from './core';\n\n// Each individual reducer\nexport {default as visStateReducer} from './vis-state';\nexport {default as mapStateReducer} from './map-state';\nexport {default as uiStateReducer} from './ui-state';\nexport {default as mapStyleReducer} from './map-style';\nexport {default as providerReducer} from './provider-state';\n\n// reducer updaters\n\nexport * as visStateUpdaters from './vis-state-updaters';\nexport * as mapStateUpdaters from './map-state-updaters';\nexport * as mapStyleUpdaters from './map-style-updaters';\nexport * as uiStateUpdaters from './ui-state-updaters';\n\n// This will be deprecated\nexport * as combineUpdaters from './combined-updaters';\nexport * as combinedUpdaters from './combined-updaters';\nexport type {KeplerGlState} from './combined-updaters';\nexport {addDataToMapUpdater, replaceDataInMapUpdater} from './combined-updaters';\n\n// reducer merges\nexport * as visStateMergers from './vis-state-merger';\nexport * from './vis-state-selectors';\nexport * from './vis-state-merger';\nexport * from './provider-state-updaters';\nexport * from './provider-state';\nexport * from './ui-state';\nexport * from './map-state';\nexport {getInitialInputStyle, loadMapStylesUpdater, INITIAL_MAP_STYLE} from './map-style-updaters';\nexport {\n  fitBoundsUpdater,\n  pickViewportPropsFromMapState,\n  INITIAL_MAP_STATE\n} from './map-state-updaters';\n\n// Helpers\nexport * from './composer-helpers';\n\n// export types\nexport * from './vis-state-updaters';\n\nexport {INITIAL_UI_STATE} from './ui-state-updaters';\n\nexport {getDefaultMapStyles} from './map-style-updaters';\nexport type {MapboxStyleUrl, MapStyle} from './map-style-updaters';\n\nexport * from './data-utils';\nexport * from './export-utils';\nexport * from './interaction-utils';\nexport * from './layer-utils';\nexport * as providerStateUpdaters from './provider-state-updaters';\n\nexport {enhanceReduxMiddleware} from './middleware';\n"
  },
  {
    "path": "src/reducers/src/interaction-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  DEFAULT_TOOLTIP_FIELDS,\n  ALL_FIELD_TYPES,\n  TRIP_POINT_FIELDS,\n  TOOLTIP_FORMATS,\n  TOOLTIP_KEY,\n  COMPARE_TYPES\n} from '@kepler.gl/constants';\n\nimport {Field, TooltipField, CompareType} from '@kepler.gl/types';\nimport {parseFieldValue, getFormatter, isNumber, defaultFormatter} from '@kepler.gl/utils';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\n\n/**\n * Minus sign used in tooltip formatting.\n * \\u2212 or \\u002D is the minus sign that d3-format uses for decimal number formatting\n * d3-format 2.0 uses \\u002D\n */\nexport const TOOLTIP_MINUS_SIGN = '\\u2212';\n// both are posible negative signs\nexport const NEGATIVE_SIGNS = ['\\u002D', '\\u2212'];\n\nexport const BRUSH_CONFIG: {\n  range: [number, number];\n} = {\n  range: [0, 50]\n};\n\nexport function findFieldsToShow({\n  fields,\n  id,\n  maxDefaultTooltips\n}: {\n  fields: Field[];\n  id: string;\n  maxDefaultTooltips: number;\n}): {\n  [key: string]: string[];\n} {\n  // first find default tooltip fields for trips\n  const fieldsToShow = DEFAULT_TOOLTIP_FIELDS.reduce((prev, curr) => {\n    if (fields.find(({name}) => curr.name === name)) {\n      // @ts-ignore\n      prev.push(curr);\n    }\n    return prev;\n  }, []);\n\n  return {\n    [id]: fieldsToShow.length ? fieldsToShow : autoFindTooltipFields(fields, maxDefaultTooltips)\n  };\n}\n\nfunction autoFindTooltipFields(fields, maxDefaultTooltips) {\n  const ptFields = _mergeFieldPairs(TRIP_POINT_FIELDS);\n  // filter out the default fields that contains lat and lng and any geometry\n  const fieldsToShow = fields.filter(\n    ({name, type}) =>\n      name\n        .replace(/[_,.]+/g, ' ')\n        .trim()\n        .split(' ')\n        .every(seg => !ptFields.includes(seg)) &&\n      type !== ALL_FIELD_TYPES.geojson &&\n      type !== ALL_FIELD_TYPES.geoarrow &&\n      type !== 'object'\n  );\n\n  return fieldsToShow.slice(0, maxDefaultTooltips).map(({name}) => {\n    return {\n      name,\n      format: null\n    };\n  });\n}\n\nfunction _mergeFieldPairs(pairs) {\n  return pairs.reduce((prev, pair) => [...prev, ...pair], []);\n}\n\nexport function getTooltipDisplayDeltaValue({\n  field,\n  value,\n  primaryValue,\n  compareType\n}: {\n  field: Field;\n  value: any;\n  primaryValue: any;\n  compareType?: CompareType;\n}): string | null {\n  let displayDeltaValue: string | null = null;\n\n  if (\n    // comparison mode only works for numeric field\n    field.type === ALL_FIELD_TYPES.integer ||\n    field.type === ALL_FIELD_TYPES.real\n  ) {\n    if (isNumber(primaryValue) && isNumber(value)) {\n      const deltaValue =\n        compareType === COMPARE_TYPES.RELATIVE ? value / primaryValue - 1 : value - primaryValue;\n      const deltaFormat =\n        compareType === COMPARE_TYPES.RELATIVE\n          ? TOOLTIP_FORMATS.DECIMAL_PERCENT_FULL_2[TOOLTIP_KEY]\n          : field.displayFormat || TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_3[TOOLTIP_KEY];\n\n      displayDeltaValue = getFormatter(deltaFormat, field)(deltaValue);\n\n      // safely cast string\n      displayDeltaValue = defaultFormatter(displayDeltaValue);\n      const deltaFirstChar = displayDeltaValue.charAt(0);\n\n      if (deltaFirstChar !== '+' && !NEGATIVE_SIGNS.includes(deltaFirstChar)) {\n        displayDeltaValue = `+${displayDeltaValue}`;\n      }\n    } else {\n      displayDeltaValue = TOOLTIP_MINUS_SIGN;\n    }\n  }\n\n  return displayDeltaValue;\n}\n\nexport function getTooltipDisplayValue({\n  item,\n  field,\n  value\n}: {\n  item: TooltipField | undefined;\n  field: Field;\n  value: any;\n}): string {\n  if (!notNullorUndefined(value)) {\n    return '';\n  }\n\n  return item?.format\n    ? getFormatter(item?.format, field)(value)\n    : field.displayFormat\n    ? getFormatter(field.displayFormat, field)(value)\n    : parseFieldValue(value, field.type);\n}\n"
  },
  {
    "path": "src/reducers/src/layer-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Console from 'global/console';\n\nimport {GEOCODER_LAYER_ID, LIGHT_AND_SHADOW_EFFECT} from '@kepler.gl/constants';\nimport {Layer as DeckLayer, LayerProps as DeckLayerProps} from '@deck.gl/core/typed';\nimport {\n  Field,\n  TooltipField,\n  CompareType,\n  SplitMapLayers,\n  Editor,\n  Feature,\n  FeatureSelectionContext,\n  BindedLayerCallbacks,\n  LayerCallbacks,\n  Viewport\n} from '@kepler.gl/types';\nimport {\n  FindDefaultLayerPropsReturnValue,\n  FindDefaultLayerProps,\n  Layer,\n  LayerClassesType,\n  OVERLAY_TYPE_CONST,\n  getEditorLayer\n} from '@kepler.gl/layers';\n\nimport KeplerTable from '@kepler.gl/table';\nimport {VisState} from '@kepler.gl/schemas';\nimport {isFunction, getMapLayersFromSplitMaps, DataRow} from '@kepler.gl/utils';\nimport {arrayMove} from '@kepler.gl/common-utils';\n\nimport {ThreeDBuildingLayer} from '@kepler.gl/deckgl-layers';\n\nexport type LayersToRender = {\n  [layerId: string]: boolean;\n};\n\nexport type AggregationLayerHoverData = {\n  points: any[];\n  colorValue?: any;\n  elevationValue?: any;\n  aggregatedData?: Record<\n    string,\n    {\n      measure: string;\n      value?: any;\n    }\n  >;\n};\n\nexport type LayerHoverProp = {\n  data: DataRow | AggregationLayerHoverData | null;\n  fields: Field[];\n  fieldsToShow: TooltipField[];\n  layer: Layer;\n  primaryData?: DataRow | AggregationLayerHoverData | null;\n  compareType?: CompareType;\n  currentTime?: VisState['animationConfig']['currentTime'];\n};\n\n/**\n * Find default layers from fields\n */\nexport function findDefaultLayer(dataset: KeplerTable, layerClasses: LayerClassesType): Layer[] {\n  if (!dataset) {\n    return [];\n  }\n\n  const layerProps = (Object.keys(layerClasses) as Array<keyof LayerClassesType>).reduce(\n    (previous, lc) => {\n      const result: FindDefaultLayerPropsReturnValue =\n        typeof layerClasses[lc].findDefaultLayerProps === 'function'\n          ? layerClasses[lc].findDefaultLayerProps(dataset, previous)\n          : {props: []};\n\n      const props = Array.isArray(result) ? result : result.props || [];\n      const foundLayers = result.foundLayers || previous;\n\n      return foundLayers.concat(\n        props.map(p => ({\n          ...p,\n          type: lc,\n          dataId: dataset.id,\n          // set arc layer initial visiblity to false, because arcs tend to be too musy\n          ...(lc === 'arc' || lc === 'line' ? {isVisible: false} : {})\n        }))\n      );\n    },\n    [] as (FindDefaultLayerProps & {type: string})[]\n  );\n\n  // go through all layerProps to create layer\n  return layerProps.map(props => {\n    const layer = new layerClasses[props.type](props);\n    return typeof layer.setInitialLayerConfig === 'function' && dataset.dataContainer\n      ? layer.setInitialLayerConfig(dataset)\n      : layer;\n  });\n}\n\ntype MinVisStateForLayerData = {\n  datasets: VisState['datasets'];\n  animationConfig: VisState['animationConfig'];\n};\n\n/**\n * Calculate layer data based on layer type, col Config,\n * return updated layer if colorDomain, dataMap has changed.\n * Also, returns updated layer in case the input layer was in invalid state.\n * Adds an error message to the layer in case of an exception.\n */\nexport function calculateLayerData<S extends MinVisStateForLayerData>(\n  layer: Layer,\n  state: S,\n  oldLayerData?: any\n): {\n  layerData: any;\n  layer: Layer;\n} {\n  let layerData;\n  try {\n    // Make sure the layer updates data after an error\n    if (!layer.isValid) {\n      layer._oldDataUpdateTriggers = undefined;\n    }\n\n    if (!layer.type || !layer.hasAllColumns() || !layer.config.dataId) {\n      return {layer, layerData: {}};\n    }\n\n    layerData = layer.formatLayerData(state.datasets, oldLayerData);\n\n    // At this point the data for the layer is updated without errors\n    if (!layer.isValid) {\n      // Switch to visible after an error\n      layer = layer.updateLayerConfig({\n        isVisible: true\n      });\n    }\n    layer.isValid = true;\n    layer.errorMessage = null;\n  } catch (err) {\n    Console.error(err);\n    layer = layer.updateLayerConfig({\n      isVisible: false\n    });\n    layer.isValid = false;\n\n    layer.errorMessage =\n      err instanceof Error && err.message ? err.message.substring(0, 100) : 'Unknown error';\n\n    layerData = {};\n  }\n\n  return {\n    layer,\n    layerData\n  };\n}\n\n/**\n * Calculate props passed to LayerHoverInfo\n */\nexport function getLayerHoverProp({\n  animationConfig,\n  interactionConfig,\n  hoverInfo,\n  layers,\n  layersToRender,\n  datasets\n}: {\n  interactionConfig: VisState['interactionConfig'];\n  animationConfig: VisState['animationConfig'];\n  hoverInfo: VisState['hoverInfo'];\n  layers: VisState['layers'];\n  layersToRender: LayersToRender;\n  datasets: VisState['datasets'];\n}): LayerHoverProp | null {\n  if (interactionConfig.tooltip.enabled && hoverInfo && hoverInfo.picked) {\n    // if anything hovered\n    const {object, layer: overlay} = hoverInfo;\n\n    // deckgl layer to kepler-gl layer\n    const layer = layers[overlay.props.idx];\n\n    // NOTE: for binary format GeojsonLayer, deck will return object=null but hoverInfo.index >= 0\n    if (\n      (object || hoverInfo.index >= 0) &&\n      layer &&\n      layer.getHoverData &&\n      layersToRender[layer.id]\n    ) {\n      // if layer is visible and have hovered data\n      const {\n        config: {dataId}\n      } = layer;\n      if (!dataId) {\n        return null;\n      }\n      const {dataContainer, fields} = datasets[dataId];\n      const data: DataRow | null = layer.getHoverData(\n        object || hoverInfo.index,\n        dataContainer,\n        fields,\n        animationConfig,\n        hoverInfo\n      );\n      if (!data) {\n        return null;\n      }\n      const fieldsToShow = interactionConfig.tooltip.config.fieldsToShow[dataId];\n\n      return {\n        data,\n        fields,\n        fieldsToShow,\n        layer,\n        currentTime: animationConfig.currentTime\n      };\n    }\n  }\n\n  return null;\n}\n\nexport function renderDeckGlLayer(props: any, layerCallbacks: {[key: string]: any}) {\n  const {\n    datasets,\n    layer,\n    layerIndex,\n    data,\n    hoverInfo,\n    clicked,\n    mapState,\n    interactionConfig,\n    animationConfig,\n    mapLayers,\n    experimentalContext\n  } = props;\n  const dataset = datasets[layer.config.dataId];\n  const {gpuFilter} = dataset || {};\n  const objectHovered = clicked || hoverInfo;\n  const visible = !mapLayers || (mapLayers && mapLayers[layer.id]);\n  // Layer is Layer class\n  return layer.renderLayer({\n    data,\n    gpuFilter,\n    idx: layerIndex,\n    interactionConfig,\n    layerCallbacks,\n    mapState,\n    animationConfig,\n    objectHovered,\n    visible,\n    dataset,\n    experimentalContext\n  });\n}\n\nexport function isLayerRenderable(layer: Layer, layerData) {\n  return layer.id !== GEOCODER_LAYER_ID && layer.shouldRenderLayer(layerData);\n}\n\nexport function isLayerVisible(layer, mapLayers) {\n  return (\n    layer.config.isVisible &&\n    // if layer.id is not in mapLayers, don't render it\n    (!mapLayers || (mapLayers && mapLayers[layer.id]))\n  );\n}\n\n// Prepare a dict of layers rendered by the deck.gl\n// Note, isVisible: false layer is passed to deck.gl here\n// return {[id]: true \\ false}\nexport function prepareLayersForDeck(\n  layers: Layer[],\n  layerData: VisState['layerData']\n): {\n  [key: string]: boolean;\n} {\n  return layers.reduce(\n    (accu, layer, idx) => ({\n      ...accu,\n      [layer.id]:\n        isLayerRenderable(layer, layerData[idx]) && layer.overlayType === OVERLAY_TYPE_CONST.deckgl\n    }),\n    {}\n  );\n}\n\n// Prepare a dict of rendered layers rendered in the map\n// This includes only the visibile layers for single map view and split map view\n// return {[id]: true \\ false}\nexport function prepareLayersToRender(\n  layers: Layer[],\n  layerData: VisState['layerData'],\n  mapLayers?: SplitMapLayers | undefined | null\n): LayersToRender {\n  return layers.reduce(\n    (accu, layer, idx) => ({\n      ...accu,\n      [layer.id]: isLayerRenderable(layer, layerData[idx]) && isLayerVisible(layer, mapLayers)\n    }),\n    {}\n  );\n}\n\ntype CustomDeckLayer = DeckLayer<DeckLayerProps>;\n\nexport function getCustomDeckLayers(deckGlProps?: any): [CustomDeckLayer[], CustomDeckLayer[]] {\n  const bottomDeckLayers = Array.isArray(deckGlProps?.layers)\n    ? deckGlProps?.layers\n    : isFunction(deckGlProps?.layers)\n    ? deckGlProps?.layers()\n    : [];\n  const topDeckLayers = Array.isArray(deckGlProps?.topLayers)\n    ? deckGlProps?.topLayers\n    : isFunction(deckGlProps?.topLayers)\n    ? deckGlProps?.topLayers()\n    : [];\n\n  return [bottomDeckLayers, topDeckLayers];\n}\n\nexport type ComputeDeckLayersProps = {\n  mapIndex?: number;\n  mapboxApiAccessToken?: string;\n  mapboxApiUrl?: string;\n  primaryMap?: boolean;\n  layersForDeck?: {[key: string]: boolean};\n  editorInfo?: {\n    editor: Editor;\n    editorMenuActive: boolean;\n    onSetFeatures: (features: Feature[]) => any;\n    setSelectedFeature: (\n      feature: Feature | null,\n      selectionContext?: FeatureSelectionContext\n    ) => any;\n    featureCollection: {\n      type: string;\n      features: Feature[];\n    };\n    selectedFeatureIndexes: number[];\n    viewport: Viewport;\n  };\n};\n\nexport function bindLayerCallbacks(\n  layerCallbacks: LayerCallbacks = {},\n  idx: number\n): BindedLayerCallbacks {\n  return Object.keys(layerCallbacks).reduce(\n    (accu, key) => ({\n      ...accu,\n      [key]: val => layerCallbacks[key](idx, val)\n    }),\n    {} as Record<string, (val: unknown) => void>\n  );\n}\n\n// eslint-disable-next-line complexity\nexport function computeDeckLayers(\n  {visState, mapState, mapStyle}: any,\n  options?: ComputeDeckLayersProps,\n  layerCallbacks?: LayerCallbacks,\n  deckGlProps?: any\n): Layer[] {\n  const {\n    datasets,\n    effects,\n    layers,\n    layerOrder,\n    layerData,\n    hoverInfo,\n    clicked,\n    interactionConfig,\n    animationConfig,\n    splitMaps\n  } = visState;\n\n  const {mapIndex, mapboxApiAccessToken, mapboxApiUrl, primaryMap, layersForDeck, editorInfo} =\n    options || {};\n\n  let dataLayers: any[] = [];\n\n  const hasShadowEffect = effects.some(effect => {\n    return effect.type === LIGHT_AND_SHADOW_EFFECT.type;\n  });\n\n  if (layerData && layerData.length) {\n    const mapLayers = getMapLayersFromSplitMaps(splitMaps, mapIndex || 0);\n\n    const currentLayersForDeck = layersForDeck || prepareLayersForDeck(layers, layerData);\n\n    dataLayers = layerOrder\n      .slice()\n      .reverse()\n      .filter(id => currentLayersForDeck[id])\n      .reduce((overlays, layerId) => {\n        const layerIndex = layers.findIndex(({id}) => id === layerId);\n        const bindedLayerCallbacks = layerCallbacks\n          ? bindLayerCallbacks(layerCallbacks, layerIndex)\n          : {};\n        const layer = layers[layerIndex];\n        const data = layerData[layerIndex];\n        const layerOverlay = renderDeckGlLayer(\n          {\n            datasets,\n            layer,\n            layerIndex,\n            data,\n            hoverInfo,\n            clicked,\n            mapState,\n            interactionConfig,\n            animationConfig,\n            mapLayers,\n            experimentalContext: {\n              hasShadowEffect\n            }\n          },\n          bindedLayerCallbacks\n        );\n        return overlays.concat(layerOverlay || []);\n      }, []);\n  }\n\n  if (!primaryMap) {\n    return dataLayers;\n  }\n\n  if (\n    mapStyle?.visibleLayerGroups['3d building'] &&\n    primaryMap &&\n    mapboxApiAccessToken &&\n    mapboxApiUrl\n  ) {\n    dataLayers.push(\n      new ThreeDBuildingLayer({\n        id: '_keplergl_3d-building',\n        mapboxApiAccessToken,\n        mapboxApiUrl,\n        threeDBuildingColor: mapStyle.threeDBuildingColor,\n        updateTriggers: {\n          getFillColor: mapStyle.threeDBuildingColor\n        }\n      })\n    );\n  }\n\n  const [customBottomDeckLayers, customTopDeckLayers] = getCustomDeckLayers(deckGlProps);\n\n  const editorLayer: any[] = [];\n  if (editorInfo) {\n    editorLayer.push(\n      getEditorLayer({\n        ...editorInfo\n      })\n    );\n  }\n\n  return [...customBottomDeckLayers, ...dataLayers, ...customTopDeckLayers, ...editorLayer];\n}\n\nexport function getLayerOrderFromLayers<T extends {id: string}>(layers: T[]): string[] {\n  return layers.map(({id}) => id);\n}\n\nexport function reorderLayerOrder(\n  layerOrder: VisState['layerOrder'],\n  originLayerId: string,\n  destinationLayerId: string\n): VisState['layerOrder'] {\n  const activeIndex = layerOrder.indexOf(originLayerId);\n  const overIndex = layerOrder.indexOf(destinationLayerId);\n\n  return arrayMove(layerOrder, activeIndex, overIndex);\n}\n\nexport function addLayerToLayerOrder(\n  layerOrder: VisState['layerOrder'],\n  layerId: string\n): string[] {\n  return [layerId, ...layerOrder];\n}\n\nexport function getLayerHoverPropValue(\n  data: DataRow | AggregationLayerHoverData | null | undefined,\n  fieldIndex: number\n) {\n  if (!data) return undefined;\n  if (data instanceof DataRow) return data.valueAt(fieldIndex);\n  return data[fieldIndex];\n}\n\n/** Checks if any Deck layers are in the process of loading. */\nexport function areAnyDeckLayersLoading(layers: DeckLayer[]): boolean {\n  return layers.some(\n    // layer.isLoaded changes frequently in Deck (even on hover) so we check additional properties\n    layer => layer.internalState && !layer.isLoaded\n  );\n}\n"
  },
  {
    "path": "src/reducers/src/map-state-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport geoViewport from '@mapbox/geo-viewport';\nimport booleanWithin from '@turf/boolean-within';\nimport bboxPolygon from '@turf/bbox-polygon';\nimport {fitBounds} from '@math.gl/web-mercator';\nimport deepmerge from 'deepmerge';\nimport pick from 'lodash/pick';\n\nimport {\n  getCenterAndZoomFromBounds,\n  validateBounds,\n  MAPBOX_TILE_SIZE,\n  validateViewPort\n} from '@kepler.gl/utils';\nimport {MapStateActions, ReceiveMapConfigPayload, ActionTypes} from '@kepler.gl/actions';\nimport {MapState, Bounds, Viewport} from '@kepler.gl/types';\n\n/**\n * Updaters for `mapState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\n * Read more about [Using updaters](../advanced-usage/using-updaters.md)\n * @public\n * @example\n *\n * import keplerGlReducer, {mapStateUpdaters} from '@kepler.gl/reducers';\n * // Root Reducer\n * const reducers = combineReducers({\n *  keplerGl: keplerGlReducer,\n *  app: appReducer\n * });\n *\n * const composedReducer = (state, action) => {\n *  switch (action.type) {\n *    // click button to close side panel\n *    case 'CLICK_BUTTON':\n *      return {\n *        ...state,\n *        keplerGl: {\n *          ...state.keplerGl,\n *          foo: {\n *             ...state.keplerGl.foo,\n *             mapState: mapStateUpdaters.fitBoundsUpdater(\n *               mapState, {payload: [127.34, 31.09, 127.56, 31.59]]}\n *             )\n *          }\n *        }\n *      };\n *  }\n *  return reducers(state, action);\n * };\n *\n * export default composedReducer;\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst mapStateUpdaters = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n/**\n * Default initial `mapState`\n * @memberof mapStateUpdaters\n * @constant\n * @property pitch Default: `0`\n * @property bearing Default: `0`\n * @property latitude Default: `37.75043`\n * @property longitude Default: `-122.34679`\n * @property zoom Default: `9`\n * @property dragRotate Default: `false`\n * @property width Default: `800`\n * @property height Default: `800`\n * @property minZoom: `undefined`,\n * @property maxZoom: `undefined`,\n * @property maxBounds: `undefined`,\n * @property isSplit: `false`,\n * @property isViewportSynced: `true`,\n * @property isZoomLocked: `false`,\n * @property splitMapViewports: `[]`\n * @public\n */\nexport const INITIAL_MAP_STATE: MapState = {\n  pitch: 0,\n  bearing: 0,\n  latitude: 37.75043,\n  longitude: -122.34679,\n  zoom: 9,\n  dragRotate: false,\n  width: 800,\n  height: 800,\n  minZoom: undefined,\n  maxZoom: undefined,\n  maxBounds: undefined,\n  isSplit: false,\n  isViewportSynced: true,\n  isZoomLocked: false,\n  splitMapViewports: []\n};\n\n/* Updaters */\n/**\n * Update map viewport\n * @memberof mapStateUpdaters\n * @public\n */\nexport const updateMapUpdater = (\n  state: MapState,\n  action: MapStateActions.UpdateMapUpdaterAction\n): MapState => {\n  const {viewport: inputViewport, mapIndex = 0} = action.payload;\n  const viewport = validateViewPort(inputViewport);\n\n  if (state.isViewportSynced) {\n    // The `updateViewport` function is typed as (Viewport, Viewport) -> Viewport but here the\n    // expected typing is (MapState, Viewport) -> MapState.\n    // this could be a potential bug as we treat Viewport and MapState as equal seemingly\n    // @ts-expect-error Type 'Viewport' is missing the following properties from type 'MapState': isSplit, isViewportSynced, isZoomLocked, splitMapViewports\n    return updateViewport(state, viewport);\n  }\n\n  let otherViewportMapIndex = -1;\n  const splitMapViewports = state.splitMapViewports.map((currentViewport, i) => {\n    if (i === mapIndex) {\n      // update the matching viewport with the newViewport info in the action payload\n      return updateViewport(currentViewport, viewport);\n    }\n\n    otherViewportMapIndex = i;\n    // make no changes to the other viewport (yet)\n    return currentViewport;\n  });\n\n  // make conditional updates to the other viewport not matching this payload's `mapIndex`\n  if (Number.isFinite(otherViewportMapIndex) && otherViewportMapIndex > -1) {\n    // width and height are a special case and are always updated\n    splitMapViewports[otherViewportMapIndex] = {\n      ...splitMapViewports[otherViewportMapIndex],\n      width: splitMapViewports[mapIndex].width,\n      height: splitMapViewports[mapIndex].height\n    };\n\n    if (state.isZoomLocked) {\n      // update the other viewport with the new zoom from the split viewport that was updated with this payload's `mapIndex`\n      splitMapViewports[otherViewportMapIndex] = {\n        ...splitMapViewports[otherViewportMapIndex],\n        zoom: splitMapViewports[mapIndex].zoom\n      };\n    }\n  }\n\n  return {\n    // update the top-level mapState viewport with the most recently interacted-with split viewport\n    // WHY? this avoids zoom and bounds \"jumping\" due to a \"stale\" top-level mapState viewport when:\n    //  1. toggling off the unsynced viewports mode to switch to the synced viewports mode\n    //  2. toggling on the zoom lock during an unsynced viewports mode\n    ...state,\n    ...splitMapViewports[mapIndex],\n    // update the mapState with the new array of split viewports\n    splitMapViewports\n  };\n};\n\n/**\n * Fit map viewport to bounds\n * @memberof mapStateUpdaters\n * @public\n */\nexport const fitBoundsUpdater = (\n  state: MapState,\n  action: MapStateActions.FitBoundsUpdaterAction\n): MapState => {\n  const centerAndZoom = getCenterAndZoomFromBounds(action.payload, {\n    width: state.width,\n    height: state.height\n  });\n  if (!centerAndZoom) {\n    // bounds is invalid\n    return state;\n  }\n\n  const newState = {\n    ...state,\n    latitude: centerAndZoom.center[1],\n    longitude: centerAndZoom.center[0],\n    // For marginal or invalid bounds, zoom may be NaN. Make sure to provide a valid value in order\n    // to avoid corrupt state and potential crashes as zoom is expected to be a number\n    ...(Number.isFinite(centerAndZoom.zoom) ? {zoom: centerAndZoom.zoom} : {})\n  };\n\n  // if fitting to bounds while split and unsynced\n  // copy the new latitude, longitude, and zoom values to each split viewport\n  if (newState.splitMapViewports.length) {\n    newState.splitMapViewports = newState.splitMapViewports.map(currentViewport => ({\n      ...currentViewport,\n      latitude: newState.latitude,\n      longitude: newState.longitude,\n      zoom: newState.zoom\n    }));\n  }\n\n  return newState;\n};\n\n/**\n * Toggle between 3d and 2d map.\n * @memberof mapStateUpdaters\n * @public\n */\nexport const togglePerspectiveUpdater = (state: MapState): MapState => {\n  const newState = {\n    ...state,\n    ...{\n      pitch: state.dragRotate ? 0 : 50,\n      bearing: state.dragRotate ? 0 : 24\n    },\n    dragRotate: !state.dragRotate\n  };\n\n  // if toggling 3d and 2d while split and unsynced\n  // copy the new pitch, bearing, and dragRotate values to each split viewport\n  if (newState.splitMapViewports.length) {\n    newState.splitMapViewports = newState.splitMapViewports.map(currentViewport => ({\n      ...currentViewport,\n      pitch: newState.pitch,\n      bearing: newState.bearing,\n      dragRotate: newState.dragRotate\n    }));\n  }\n\n  return newState;\n};\n\n/**\n * reset mapState to initial State\n * @memberof mapStateUpdaters\n * @public\n */\nexport const resetMapConfigUpdater = (state: MapState): MapState => ({\n  ...INITIAL_MAP_STATE,\n  ...state.initialState,\n  initialState: state.initialState\n});\n\n// consider case where you have a split map and user wants to reset\n/**\n * Update `mapState` to propagate a new config\n * @memberof mapStateUpdaters\n * @public\n */\nexport const receiveMapConfigUpdater = (\n  state: MapState,\n  {\n    // @ts-expect-error\n    payload: {config = {}, options = {}, bounds = null}\n  }: {\n    type?: typeof ActionTypes.RECEIVE_MAP_CONFIG;\n    payload: ReceiveMapConfigPayload;\n  }\n): MapState => {\n  /**\n   * @type {Partial<MapState>}\n   */\n  const mapState = (config || {}).mapState || {};\n  // merged received mapState with previous state\n  // state also may include properties that are new to an existing, saved project's mapState\n\n  let mergedState = deepmerge<MapState>(state, mapState, {\n    // note: deepmerge by default will merge arrays by concatenating them\n    // but we need to overwrite destination arrays with source arrays, if present\n    // https://github.com/TehShrike/deepmerge#arraymerge-example-overwrite-target-array\n    arrayMerge: (_destinationArray, sourceArray) => sourceArray\n  });\n\n  // if center map\n  // center map will override mapState config\n  if (options.centerMap && bounds) {\n    mergedState = fitBoundsUpdater(mergedState, {\n      payload: bounds\n    });\n  }\n\n  // make sure we validate map state before we merge\n  mergedState = validateViewPort(mergedState);\n\n  return {\n    ...mergedState,\n    // update width if `isSplit` has changed\n    ...getMapDimForSplitMap(mergedState.isSplit, state)\n  };\n};\n\n/**\n * Toggle between one or split maps\n * @memberof mapStateUpdaters\n * @public\n */\nexport const toggleSplitMapUpdater = (state: MapState): MapState => ({\n  ...state,\n  ...getMapDimForSplitMap(!state.isSplit, state),\n  isSplit: !state.isSplit,\n  ...(!state.isSplit === false\n    ? {\n        // if toggling to no longer split (single mode) then reset a few properties\n        isViewportSynced: true,\n        isZoomLocked: false,\n        splitMapViewports: []\n      }\n    : {})\n});\n\n/**\n * Toggle between locked and unlocked split viewports\n * @memberof mapStateUpdaters\n * @public\n */\nexport const toggleSplitMapViewportUpdater = (\n  state: MapState,\n  action: MapStateActions.ToggleSplitMapViewportUpdaterAction\n) => {\n  // new map state immediately gets the new, optional payload values for isViewportSynced and/or isZoomLocked\n  const newMapState = {\n    ...state,\n    ...(action.payload || {})\n  };\n\n  if (newMapState.isViewportSynced) {\n    // switching from unsynced to synced viewports\n    newMapState.splitMapViewports = [];\n  } else {\n    // switching from synced to unsynced viewports\n    // or already in unsynced mode and toggling locked zoom\n\n    if (state.isZoomLocked && !newMapState.isZoomLocked) {\n      // switching off locked zoom while unsynced\n      // don't copy the mapStates to left and right viewports because there will be zoom \"jumping\"\n      return newMapState;\n    }\n\n    if (!state.isZoomLocked && newMapState.isZoomLocked) {\n      // switching on locked zoom while unsynced\n      // only copy zoom viewport property from the most recently interacted-with viewport to the other\n      // TODO: do we want to check for a match a different way, such as a combo of `latitude` and `longitude`?\n      const lastUpdatedViewportIndex = newMapState.splitMapViewports.findIndex(\n        v => newMapState.zoom === v.zoom\n      );\n\n      const splitMapViewports = newMapState.splitMapViewports.map((currentViewport, i) => {\n        if (i === lastUpdatedViewportIndex) {\n          // no zoom to modify here\n          return currentViewport;\n        }\n        // the other viewport gets the most recently interacted-with viewport's zoom\n        // WHY? the viewport the user was last interacting with will set zoom across the board for smooth UX\n        return {\n          ...currentViewport,\n          zoom: newMapState.splitMapViewports[lastUpdatedViewportIndex].zoom\n        };\n      });\n\n      newMapState.splitMapViewports = splitMapViewports;\n\n      return newMapState;\n    }\n\n    // if current viewport is synced, and we are unsyncing it\n    // or already in unsynced mode and NOT toggling locked zoom\n    // make a fresh copy of the current viewport object, assign it to splitMapViewports[]\n    // pickViewportPropsFromMapState is called twice to avoid memory allocation conflicts\n    const leftViewport = pickViewportPropsFromMapState(newMapState);\n    const rightViewport = pickViewportPropsFromMapState(newMapState);\n    newMapState.splitMapViewports = [leftViewport, rightViewport];\n  }\n\n  // return new state\n  return newMapState;\n};\n\n// Helpers\nexport function getMapDimForSplitMap(isSplit, state) {\n  // cases:\n  // 1. state split: true - isSplit: true\n  // do nothing\n  // 2. state split: false - isSplit: false\n  // do nothing\n  if (state.isSplit === isSplit) {\n    return {};\n  }\n\n  const width =\n    state.isSplit && !isSplit\n      ? // 3. state split: true - isSplit: false\n        // double width\n        state.width * 2\n      : // 4. state split: false - isSplit: true\n        // split width\n        state.width / 2;\n\n  return {\n    width\n  };\n}\n\nfunction updateViewportBasedOnBounds(state: MapState, newMapState: MapState) {\n  // Get the new viewport bounds\n  const viewportBounds = geoViewport.bounds(\n    [newMapState.longitude, newMapState.latitude],\n    newMapState.zoom,\n    [newMapState.width, newMapState.height],\n    MAPBOX_TILE_SIZE\n  );\n  // Generate turf Polygon from bounds for comparison\n  const viewportBoundsPolygon = bboxPolygon(viewportBounds);\n  // @ts-ignore\n  const newStateMaxBounds: Bounds = newMapState.maxBounds;\n  // @ts-ignore\n  const maxBoundsPolygon = bboxPolygon(newStateMaxBounds);\n\n  // If maxBounds has changed reset the viewport to snap to bounds\n  const hasMaxBoundsChanged =\n    !state.maxBounds || !state.maxBounds.every((val, idx) => val === newStateMaxBounds[idx]);\n  if (hasMaxBoundsChanged) {\n    // Check if the newMapState viewport is within maxBounds\n    if (!booleanWithin(viewportBoundsPolygon, maxBoundsPolygon)) {\n      const {latitude, longitude, zoom} = fitBounds({\n        width: newMapState.width,\n        height: newMapState.height,\n        bounds: [\n          [newStateMaxBounds[0], newStateMaxBounds[1]],\n          [newStateMaxBounds[2], newStateMaxBounds[3]]\n        ]\n      });\n\n      newMapState = {\n        ...newMapState,\n        latitude,\n        longitude,\n        // For marginal or invalid bounds, zoom may be NaN. Make sure to provide a valid value in order\n        // to avoid corrupt state and potential crashes as zoom is expected to be a number\n        ...(Number.isFinite(zoom) ? {zoom} : {})\n      };\n    }\n    return newMapState;\n  }\n\n  // Check if the newMapState viewport is within maxBounds\n  if (!booleanWithin(viewportBoundsPolygon, maxBoundsPolygon)) {\n    newMapState = {\n      ...newMapState,\n      longitude: state.longitude,\n      latitude: state.latitude,\n      zoom: state.zoom\n    };\n  }\n\n  return newMapState;\n}\n\nexport function pickViewportPropsFromMapState(state: MapState): Viewport {\n  return pick(state, [\n    'width',\n    'height',\n    'zoom',\n    'pitch',\n    'bearing',\n    'latitude',\n    'longitude',\n    'dragRotate',\n    'minZoom',\n    'maxZoom',\n    'maxBounds'\n  ]);\n}\n\n/** Select items from object whose value is not undefined */\nconst definedProps = obj =>\n  Object.entries(obj).reduce(\n    (accu, [k, v]) => ({...accu, ...(v !== undefined ? {[k]: v} : {})}),\n    {}\n  );\n\nfunction updateViewport(originalViewport: Viewport, viewportUpdates: Viewport): Viewport {\n  let newViewport = {\n    ...originalViewport,\n    ...(definedProps(viewportUpdates) || {})\n  };\n\n  // Make sure zoom level doesn't go bellow minZoom if defined\n  if (newViewport.minZoom && newViewport.zoom && newViewport.zoom < newViewport.minZoom) {\n    newViewport.zoom = newViewport.minZoom;\n  }\n  // Make sure zoom level doesn't go above maxZoom if defined\n  if (newViewport.maxZoom && newViewport.zoom && newViewport.zoom > newViewport.maxZoom) {\n    newViewport.zoom = newViewport.maxZoom;\n  }\n  // Limit viewport update based on maxBounds\n  if (newViewport.maxBounds && validateBounds(newViewport.maxBounds)) {\n    // @ts-expect-error Type 'Viewport' is missing the following properties from type 'MapState': isSplit, isViewportSynced, isZoomLocked, splitMapViewports\n    newViewport = updateViewportBasedOnBounds(originalViewport, newViewport);\n  }\n\n  return newViewport;\n}\n"
  },
  {
    "path": "src/reducers/src/map-state.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\nimport {ActionTypes} from '@kepler.gl/actions';\nimport * as mapStateUpdaters from './map-state-updaters';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.UPDATE_MAP]: mapStateUpdaters.updateMapUpdater,\n  [ActionTypes.FIT_BOUNDS]: mapStateUpdaters.fitBoundsUpdater,\n  [ActionTypes.TOGGLE_PERSPECTIVE]: mapStateUpdaters.togglePerspectiveUpdater,\n  [ActionTypes.RECEIVE_MAP_CONFIG]: mapStateUpdaters.receiveMapConfigUpdater,\n  [ActionTypes.RESET_MAP_CONFIG]: mapStateUpdaters.resetMapConfigUpdater,\n  [ActionTypes.TOGGLE_SPLIT_MAP]: mapStateUpdaters.toggleSplitMapUpdater,\n  [ActionTypes.TOGGLE_SPLIT_MAP_VIEWPORT]: mapStateUpdaters.toggleSplitMapViewportUpdater\n};\n\n/* Reducer */\nexport const mapStateReducerFactory = (initialState = {}) =>\n  // @ts-expect-error\n  handleActions(actionHandler, {\n    ...mapStateUpdaters.INITIAL_MAP_STATE,\n    ...initialState,\n    initialState\n  });\n\nexport default mapStateReducerFactory();\n"
  },
  {
    "path": "src/reducers/src/map-style-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Task, {withTask} from 'react-palm/tasks';\nimport cloneDeep from 'lodash/cloneDeep';\nimport Console from 'global/console';\n\n// Utils\nimport {\n  getDefaultLayerGroupVisibility,\n  getStyleDownloadUrl,\n  mergeLayerGroupVisibility,\n  editTopMapStyle,\n  editBottomMapStyle,\n  getStyleImageIcon,\n  isPlainObject,\n  hexToRgb,\n  colorMaybeToRGB,\n  getApplicationConfig\n} from '@kepler.gl/utils';\nimport {generateHashId} from '@kepler.gl/common-utils';\nimport {\n  DEFAULT_MAP_STYLES,\n  DEFAULT_LAYER_GROUPS,\n  DEFAULT_MAPBOX_API_URL,\n  NO_MAP_ID,\n  DEFAULT_BLDG_COLOR,\n  DEFAULT_BACKGROUND_COLOR,\n  BASE_MAP_BACKGROUND_LAYER_IDS,\n  DEFAULT_BASE_MAP_STYLE\n} from '@kepler.gl/constants';\nimport {ACTION_TASK, LOAD_MAP_STYLE_TASK} from '@kepler.gl/tasks';\nimport {rgb} from 'd3-color';\n\nimport {\n  RGBColor,\n  LayerGroup,\n  BaseMapStyle,\n  MapStyles,\n  InputStyle,\n  VisibleLayerGroups\n} from '@kepler.gl/types';\nimport {\n  ActionTypes,\n  ReceiveMapConfigPayload,\n  KeplerGlInitPayload,\n  MapStyleActions,\n  loadMapStyles,\n  loadMapStyleErr\n} from '@kepler.gl/actions';\n\nexport type MapboxStyleUrl = string;\n\nexport type MapStyle = {\n  styleType: string;\n  visibleLayerGroups: VisibleLayerGroups;\n  topLayerGroups: VisibleLayerGroups;\n  mapStyles: MapStyles;\n  // save mapbox access token\n  mapboxApiAccessToken: string | null;\n  mapboxApiUrl: string;\n  mapStylesReplaceDefault: boolean;\n  inputStyle: InputStyle;\n  threeDBuildingColor: RGBColor;\n  backgroundColor: RGBColor;\n  custom3DBuildingColor: boolean;\n  bottomMapStyle: any;\n  topMapStyle: any;\n  initialState?: MapStyle;\n  isLoading: {\n    [key: string]: boolean;\n  };\n};\n\nexport const getDefaultMapStyles = (cdnUrl: string) => {\n  return DEFAULT_MAP_STYLES.reduce(\n    (accu, curr) => ({\n      ...accu,\n      [curr.id]: {\n        ...curr,\n        icon: `${cdnUrl}/${curr.icon}`\n      }\n    }),\n    {}\n  );\n};\n\nconst getDefaultState = (): MapStyle => {\n  const visibleLayerGroups = {};\n  const topLayerGroups = {};\n\n  return {\n    styleType: DEFAULT_BASE_MAP_STYLE,\n    visibleLayerGroups,\n    topLayerGroups,\n    mapStyles: getDefaultMapStyles(getApplicationConfig().cdnUrl),\n    // save mapbox access token\n    mapboxApiAccessToken: null,\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: hexToRgb(DEFAULT_BLDG_COLOR),\n    custom3DBuildingColor: false,\n    backgroundColor: hexToRgb(DEFAULT_BACKGROUND_COLOR),\n    isLoading: {},\n    bottomMapStyle: undefined,\n    topMapStyle: undefined\n  };\n};\n\n/**\n * Updaters for `mapStyle`. Can be used in your root reducer to directly modify kepler.gl's state.\n * Read more about [Using updaters](../advanced-usage/using-updaters.md)\n * @public\n * @example\n *\n * import keplerGlReducer, {mapStyleUpdaters} from '@kepler.gl/reducers';\n * // Root Reducer\n * const reducers = combineReducers({\n *  keplerGl: keplerGlReducer,\n *  app: appReducer\n * });\n *\n * const composedReducer = (state, action) => {\n *  switch (action.type) {\n *    // click button to hide label from background map\n *    case 'CLICK_BUTTON':\n *      return {\n *        ...state,\n *        keplerGl: {\n *          ...state.keplerGl,\n *          foo: {\n *             ...state.keplerGl.foo,\n *             mapStyle: mapStyleUpdaters.mapConfigChangeUpdater(\n *               mapStyle,\n *               {payload: {visibleLayerGroups: {label: false, road: true, background: true}}}\n *             )\n *          }\n *        }\n *      };\n *  }\n *  return reducers(state, action);\n * };\n *\n * export default composedReducer;\n */\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst mapStyleUpdaters = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n/**\n * Default initial `mapStyle`\n * @memberof mapStyleUpdaters\n * @constant\n * @property styleType - Default: `'dark'`\n * @property visibleLayerGroups - Default: `{}`\n * @property topLayerGroups - Default: `{}`\n * @property mapStyles - mapping from style key to style object\n * @property mapboxApiAccessToken - Default: `null`\n * @Property mapboxApiUrl - Default null\n * @Property mapStylesReplaceDefault - Default: `false`\n * @property inputStyle - Default: `{}`\n * @property threeDBuildingColor - Default: `[r, g, b]`\n * @property backgroundColor - Default: `[r, g, b]`\n * @public\n */\nexport const INITIAL_MAP_STYLE: MapStyle = getDefaultState();\n\ninterface GetMapStylesParam {\n  styleType: string;\n  visibleLayerGroups: {[id: string]: LayerGroup | boolean};\n  topLayerGroups: {[id: string]: LayerGroup | boolean};\n  mapStyles: {[id: string]: any};\n}\n\n/**\n * Create two map styles from preset map style, one for top map one for bottom\n *\n * @param {string} styleType - current map style\n * @param {Object} visibleLayerGroups - visible layers of bottom map\n * @param {Object} topLayerGroups - visible layers of top map\n * @param {Object} mapStyles - a dictionary of all map styles\n * @returns {Object} bottomMapStyle | topMapStyle | isRaster\n */\nexport function getMapStyles({\n  styleType,\n  visibleLayerGroups,\n  topLayerGroups,\n  mapStyles\n}: GetMapStylesParam) {\n  const mapStyle = mapStyles[styleType];\n\n  // style might not be loaded yet\n  if (!mapStyle || !mapStyle.style) {\n    return {};\n  }\n\n  const editable = Object.keys(visibleLayerGroups).length;\n\n  const bottomMapStyle = !editable\n    ? mapStyle.style\n    : editBottomMapStyle({\n        id: styleType,\n        mapStyle,\n        visibleLayerGroups\n      });\n\n  const hasTopLayer = editable > 0 && Object.values(topLayerGroups).some(v => v);\n\n  // mute top layer if not visible in bottom layer\n  const topLayers =\n    hasTopLayer &&\n    Object.keys(topLayerGroups).reduce(\n      (accu, key) => ({\n        ...accu,\n        [key]: topLayerGroups[key] && visibleLayerGroups[key]\n      }),\n      {} as {[id: string]: LayerGroup | boolean}\n    );\n\n  const topMapStyle = hasTopLayer\n    ? editTopMapStyle({\n        id: styleType,\n        mapStyle,\n        visibleLayerGroups: topLayers\n      })\n    : null;\n\n  return {bottomMapStyle, topMapStyle, editable};\n}\n\nfunction findLayerFillColor(layer) {\n  return layer && layer.paint && layer.paint['background-color'];\n}\n\n// need to be careful because some basemap layer.paint['background-color'] values may be an interpolate array expression instead of a color string\n// https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-background-background-color\n// https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/#interpolate\nfunction getPaintColor(color) {\n  if (Array.isArray(color) && color[0] === 'interpolate') {\n    // get color of first zoom break\n    // [\"interpolate\", [\"linear\"], [\"zoom\"], 11, \"hsl(35, 32%, 91%)\", 13, \"hsl(35, 12%, 89%)\"]\n    return color[4];\n  }\n  return color;\n}\n\nfunction get3DBuildingColor(style): RGBColor {\n  // set building color to be the same as the background color.\n  if (!style.style) {\n    return hexToRgb(DEFAULT_BLDG_COLOR);\n  }\n\n  const backgroundLayer = (style.style.layers || []).find(({id}) => id === 'background');\n\n  const buildingLayer = (style.style.layers || []).find(({id}) => id.match(/building/));\n\n  const buildingColor =\n    findLayerFillColor(buildingLayer) || findLayerFillColor(backgroundLayer) || DEFAULT_BLDG_COLOR;\n\n  // brighten or darken building based on style\n  const operation = style.id.match(/(?=(dark|night))/) ? 'brighter' : 'darker';\n\n  const alpha = 0.2;\n  const rgbObj = rgb(buildingColor)[operation]([alpha]);\n  return [rgbObj.r, rgbObj.g, rgbObj.b];\n}\n\nfunction getBackgroundColorFromStyleBaseLayer(\n  style: BaseMapStyle,\n  backupBackgroundColor: RGBColor\n): RGBColor {\n  if (!style.style) {\n    return colorMaybeToRGB(backupBackgroundColor) || backupBackgroundColor;\n  }\n\n  // @ts-expect-error style.style not typed\n  const baseLayer = (style.style.layers || []).find(({id}) =>\n    BASE_MAP_BACKGROUND_LAYER_IDS.includes(id)\n  );\n\n  const backgroundColorOfBaseLayer = getPaintColor(findLayerFillColor(baseLayer));\n\n  const newBackgroundColor =\n    typeof backgroundColorOfBaseLayer === 'string'\n      ? backgroundColorOfBaseLayer\n      : backupBackgroundColor;\n\n  const newBackgroundColorAsRGBArray = colorMaybeToRGB(newBackgroundColor)\n    // if newBackgroundColor was in string HSL format it can introduce RGB numbers with decimals,\n    // which may render the background-color CSS of the <StyledMap> container incorrectly when using our own color utils `rgbToHex()`\n    // so we attempt to round to nearest integer here\n    ?.map(channelNumber => Math.round(channelNumber)) as RGBColor | null;\n\n  return newBackgroundColorAsRGBArray || backupBackgroundColor;\n}\n\n// determine new backgroundColor from either previous state basemap style, previous state backgroundColor, or the DEFAULT_BACKGROUND_COLOR\nfunction getBackgroundColor(previousState: MapStyle, styleType: string) {\n  const previousStateMapStyle = previousState.mapStyles[previousState.styleType];\n  const backupBackgroundColor = previousState.backgroundColor || DEFAULT_BACKGROUND_COLOR;\n  const backgroundColor =\n    styleType === NO_MAP_ID\n      ? // if the style has switched to the \"no basemap\" style,\n        // attempt to detect backgroundColor of the previous basemap if it was a mapbox basemap\n        // and set it as the \"no basemap\" backgroundColor\n        getBackgroundColorFromStyleBaseLayer(previousStateMapStyle, backupBackgroundColor)\n      : // otherwise leave it alone and rely on the previous state's preexisting backgroundColor\n        // or DEFAULT_BACKGROUND_COLOR as a last resort\n        backupBackgroundColor;\n\n  return backgroundColor;\n}\n\nfunction getLayerGroupsFromStyle(style) {\n  return Array.isArray(style?.layers)\n    ? DEFAULT_LAYER_GROUPS.filter(lg => style.layers.filter(lg.filter).length)\n    : [];\n}\n\n// Updaters\n\n/**\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const requestMapStylesUpdater = (\n  state: MapStyle,\n  {payload: {mapStyles, onSuccess}}: MapStyleActions.RequestMapStylesUpdaterAction\n): MapStyle => {\n  const toLoad = Object.keys(mapStyles).reduce(\n    (accu, id) => ({\n      ...accu,\n      ...(!state.isLoading[id] ? {[id]: mapStyles[id]} : {})\n    }),\n    {}\n  );\n  const loadMapStyleTasks = getLoadMapStyleTasks(\n    toLoad,\n    state.mapboxApiAccessToken,\n    state.mapboxApiUrl,\n    onSuccess\n  );\n\n  const isLoading = Object.keys(toLoad).reduce(\n    (accu, key) => ({\n      ...accu,\n      [key]: true\n    }),\n    {}\n  );\n  const nextState = {\n    ...state,\n    isLoading\n  };\n  return withTask(nextState, loadMapStyleTasks);\n};\n\n/**\n * Propagate `mapStyle` reducer with `mapboxApiAccessToken` and `mapStylesReplaceDefault`.\n * if mapStylesReplaceDefault is true mapStyles is emptied; loadMapStylesUpdater() will\n * populate mapStyles.\n *\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const initMapStyleUpdater = (\n  state: MapStyle,\n  {\n    payload = {}\n  }: {\n    type?: typeof ActionTypes.INIT;\n    payload: KeplerGlInitPayload;\n  }\n): MapStyle => ({\n  ...state,\n  // save mapbox access token to map style state\n  mapboxApiAccessToken: payload.mapboxApiAccessToken || state.mapboxApiAccessToken,\n  mapboxApiUrl: payload.mapboxApiUrl || state.mapboxApiUrl,\n  mapStyles: !payload.mapStylesReplaceDefault ? state.mapStyles : {},\n  mapStylesReplaceDefault: payload.mapStylesReplaceDefault || false\n});\n// });\n\n/**\n * Update `visibleLayerGroups`to change layer group visibility\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const mapConfigChangeUpdater = (\n  state: MapStyle,\n  action: MapStyleActions.MapConfigChangeUpdaterAction\n): MapStyle => {\n  return {\n    ...state,\n    ...action.payload,\n    ...getMapStyles({\n      ...state,\n      ...action.payload\n    })\n  };\n};\n\nconst hasStyleObject = style => isPlainObject(style?.style);\n\n/**\n * Change to another map style. The selected style should already been loaded into `mapStyle.mapStyles`\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const mapStyleChangeUpdater = (\n  state: MapStyle,\n  {payload: {styleType, onSuccess}}: MapStyleActions.MapStyleChangeUpdaterAction\n): MapStyle => {\n  if (\n    // we might not have received the style yet\n    !state.mapStyles[styleType] ||\n    // or if it is a managed custom style asset\n    // and if it has not been hydrated with URL info yet (during app first initialization)\n    // and it does not have a style object (during adding a custom style)\n    (state.mapStyles[styleType]?.custom === 'MANAGED' &&\n      !state.mapStyles[styleType]?.url &&\n      !hasStyleObject(state.mapStyles[styleType]))\n  ) {\n    return state;\n  }\n\n  if (!hasStyleObject(state.mapStyles[styleType])) {\n    // style hasn't loaded yet\n    return requestMapStylesUpdater(\n      {\n        ...state,\n        styleType\n      },\n      {\n        payload: {\n          mapStyles: {\n            [styleType]: state.mapStyles[styleType]\n          },\n          onSuccess\n        }\n      }\n    );\n  }\n\n  const defaultLGVisibility = getDefaultLayerGroupVisibility(state.mapStyles[styleType]);\n\n  const visibleLayerGroups = mergeLayerGroupVisibility(\n    defaultLGVisibility,\n    state.visibleLayerGroups\n  );\n\n  const threeDBuildingColor: RGBColor = state.custom3DBuildingColor\n    ? state.threeDBuildingColor\n    : get3DBuildingColor(state.mapStyles[styleType]);\n\n  // determine new backgroundColor from either previous state basemap style, previous state backgroundColor, or the DEFAULT_BACKGROUND_COLOR\n  const backgroundColor = getBackgroundColor(state, styleType);\n\n  return {\n    ...state,\n    styleType,\n    visibleLayerGroups,\n    threeDBuildingColor,\n    backgroundColor,\n    ...getMapStyles({\n      ...state,\n      visibleLayerGroups,\n      styleType\n    })\n  };\n};\n\n/**\n * Callback when load map style success\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const loadMapStylesUpdater = (\n  state: MapStyle,\n  action: MapStyleActions.LoadMapStylesUpdaterAction\n): MapStyle => {\n  const {newStyles, onSuccess} = action.payload || {};\n\n  const addLayerGroups = Object.keys(newStyles).reduce(\n    (accu, id) => ({\n      ...accu,\n      [id]: {\n        ...newStyles[id],\n        layerGroups: newStyles[id].layerGroups || getLayerGroupsFromStyle(newStyles[id].style)\n      }\n    }),\n    {}\n  );\n  // reset isLoading\n  const isLoading = Object.keys(state.isLoading).reduce(\n    (accu, key) => ({\n      ...accu,\n      ...(state.isLoading[key] && hasStyleObject(newStyles[key])\n        ? {[key]: false}\n        : {[key]: state.isLoading[key]})\n    }),\n    {}\n  );\n  // add new styles to state\n  const newState = {\n    ...state,\n    isLoading,\n    mapStyles: {\n      ...state.mapStyles,\n      ...addLayerGroups\n    }\n  };\n\n  const tasks = createActionTask(onSuccess, {styleType: state.styleType});\n\n  const nextState = newStyles[state.styleType]\n    ? mapStyleChangeUpdater(newState, {payload: {styleType: state.styleType}})\n    : newState;\n\n  return tasks ? withTask(nextState, tasks) : nextState;\n};\n\nfunction createActionTask(action, payload) {\n  if (typeof action === 'function') {\n    return ACTION_TASK().map(() => action(payload));\n  }\n\n  return null;\n}\n\n/**\n * Callback when load map style error\n * @memberof mapStyleUpdaters\n * @public\n */\n// do nothing for now, if didn't load, skip it\nexport const loadMapStyleErrUpdater = (\n  state: MapStyle,\n  {payload: {ids, error}}: MapStyleActions.LoadMapStyleErrUpdaterAction\n): MapStyle => {\n  Console.error(error);\n  // reset isLoading\n  const isLoading = Object.keys(state.isLoading).reduce(\n    (accu, key) => ({\n      ...accu,\n      ...(state.isLoading[key] && (ids || []).includes(key)\n        ? {[key]: false}\n        : {[key]: state.isLoading[key]})\n    }),\n    {}\n  );\n\n  return {\n    ...state,\n    isLoading\n  };\n};\n\n/**\n * Load map style object when pass in saved map config\n * @memberof mapStyleUpdaters\n * @param state `mapStyle`\n * @param action\n * @param action.payload saved map config `{mapStyle, visState, mapState}`\n * @returns nextState or `react-pam` tasks to load map style object\n */\nexport const receiveMapConfigUpdater = (\n  state: MapStyle,\n  {\n    payload: {config}\n  }: {\n    type?: typeof ActionTypes.RECEIVE_MAP_CONFIG;\n    payload: ReceiveMapConfigPayload;\n  }\n): MapStyle => {\n  const {mapStyle} = config || {};\n\n  if (!mapStyle) {\n    return state;\n  }\n\n  // merge default mapStyles\n  const merged = mapStyle.mapStyles\n    ? {\n        ...mapStyle,\n        mapStyles: {\n          ...mapStyle.mapStyles,\n          ...state.mapStyles\n        }\n      }\n    : mapStyle;\n\n  // set custom3DBuildingColor: true if mapStyle contains threeDBuildingColor\n  // @ts-expect-error\n  merged.custom3DBuildingColor =\n    // @ts-expect-error\n    Boolean(mapStyle.threeDBuildingColor) || merged.custom3DBuildingColor;\n  const newState = mapConfigChangeUpdater(state, {payload: merged});\n\n  return mapStyleChangeUpdater(newState, {payload: {styleType: newState.styleType}});\n};\n\nfunction getLoadMapStyleTasks(mapStyles, mapboxApiAccessToken, mapboxApiUrl, onSuccess) {\n  return [\n    Task.all(\n      Object.values(mapStyles)\n        // @ts-expect-error\n        .map(({id, url, accessToken}) => ({\n          id,\n          url: getStyleDownloadUrl(url, accessToken || mapboxApiAccessToken, mapboxApiUrl)\n        }))\n        .map(LOAD_MAP_STYLE_TASK)\n    ).bimap(\n      // success\n      results =>\n        loadMapStyles(\n          results.reduce(\n            (accu, {id, style}) => ({\n              ...accu,\n              [id]: {\n                ...mapStyles[id],\n                style\n              }\n            }),\n            {}\n          ),\n          onSuccess\n        ),\n      // error\n      err => loadMapStyleErr(Object.keys(mapStyles), err)\n    )\n  ];\n}\n/**\n * Reset map style config to initial state\n * @memberof mapStyleUpdaters\n * @param state `mapStyle`\n * @returns nextState\n * @public\n */\nexport const resetMapConfigMapStyleUpdater = (state: MapStyle): MapStyle => {\n  const emptyConfig = {\n    ...INITIAL_MAP_STYLE,\n    mapboxApiAccessToken: state.mapboxApiAccessToken,\n    mapboxApiUrl: state.mapboxApiUrl,\n    mapStylesReplaceDefault: state.mapStylesReplaceDefault,\n    ...state.initialState,\n    mapStyles: state.mapStyles,\n    initialState: state.initialState\n  };\n\n  return mapStyleChangeUpdater(emptyConfig, {payload: {styleType: emptyConfig.styleType}});\n};\n\n/**\n * Callback when a custom map style object is received\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const loadCustomMapStyleUpdater = (\n  state: MapStyle,\n  {payload: {icon, style, error}}: MapStyleActions.LoadCustomMapStyleUpdaterAction\n): MapStyle => ({\n  ...state,\n  // @ts-expect-error\n  inputStyle: {\n    ...state.inputStyle,\n    // style json and icon will load asynchronously\n    ...(style\n      ? {\n          id:\n            state.inputStyle.custom === 'MANAGED'\n              ? state.inputStyle.id // custom MANAGED type\n              : // @ts-expect-error\n                style.id || generateHashId(), // custom LOCAL type\n          // make a copy of the style object\n          style: cloneDeep(style),\n          // @ts-expect-error\n          label: state.inputStyle.label || style.name,\n          // gathering layer group info from style json\n          layerGroups: getLayerGroupsFromStyle(style)\n        }\n      : {}),\n    ...(icon ? {icon} : {}),\n    ...(error !== undefined ? {error} : {})\n  }\n});\n\n/**\n * Input a custom map style object\n * @memberof mapStyleUpdaters\n * @public\n */\nexport const inputMapStyleUpdater = (\n  state: MapStyle,\n  {payload: {inputStyle, mapState}}: MapStyleActions.InputMapStyleUpdaterAction\n): MapStyle => {\n  const updated = {\n    ...state.inputStyle,\n    ...inputStyle\n  };\n\n  // differentiate between either a url to hosted style json that needs an icon url,\n  // or an icon already available client-side as a data uri\n  const isUpdatedIconDataUri = updated.icon?.startsWith('data:image');\n  const isMapboxStyleUrl =\n    updated.url?.startsWith('mapbox://') || updated.url?.includes('mapbox.com');\n\n  const icon =\n    !isUpdatedIconDataUri && isMapboxStyleUrl\n      ? // Get image icon urls only for mapbox map lib.\n        getStyleImageIcon({\n          mapState,\n          styleUrl: updated.url || '',\n          mapboxApiAccessToken: updated.accessToken || state.mapboxApiAccessToken || '',\n          mapboxApiUrl: state.mapboxApiUrl || DEFAULT_MAPBOX_API_URL\n        })\n      : updated.icon;\n\n  return {\n    ...state,\n    inputStyle: {\n      ...updated,\n      isValid: true,\n      icon\n    }\n  };\n};\n\n/**\n * Add map style from user input to reducer and set it to current style\n * This action is called when user click confirm after putting in a valid style url in the custom map style dialog.\n * It should not be called from outside kepler.gl without a valid `inputStyle` in the `mapStyle` reducer.\n * @memberof mapStyleUpdaters\n */\nexport const addCustomMapStyleUpdater = (state: MapStyle): MapStyle => {\n  const styleId = state.inputStyle.id;\n  if (!styleId) return state;\n\n  const newState = getNewStateWithCustomMapStyle(state);\n  // set new style\n  return mapStyleChangeUpdater(newState, {payload: {styleType: styleId}});\n};\n\n/**\n * Edit map style from user input to reducer.\n * This action is called when user clicks confirm after editing an existing custom style in the custom map style dialog.\n * It should not be called from outside kepler.gl without a valid `inputStyle` in the `mapStyle` reducer.\n * @memberof mapStyleUpdaters\n */\nexport const editCustomMapStyleUpdater = (state: MapStyle): MapStyle => {\n  return getNewStateWithCustomMapStyle(state);\n};\n\nfunction getNewStateWithCustomMapStyle(state: MapStyle): MapStyle {\n  const styleId = state.inputStyle.id;\n  if (!styleId) return state;\n\n  return {\n    ...state,\n    // @ts-expect-error Property 'layerGroups' is missing in type 'InputStyle' but required in type 'BaseMapStyle'. Legacy case?\n    mapStyles: {\n      ...state.mapStyles,\n      [styleId]: {\n        ...state.mapStyles[styleId], // do not unintentionally drop any additional properties\n        ...state.inputStyle\n      }\n    },\n    // set to default\n    inputStyle: getInitialInputStyle()\n  };\n}\n\n/**\n * Remove a custom map style from `state.mapStyle.mapStyles`.\n * @memberof mapStyleUpdaters\n */\nexport const removeCustomMapStyleUpdater = (\n  state: MapStyle,\n  action: MapStyleActions.RemoveCustomMapStyleUpdaterAction\n): MapStyle => {\n  const {id} = action.payload;\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  const {[id]: _, ...restOfMapStyles} = state.mapStyles;\n\n  const newState = {\n    ...state,\n    mapStyles: restOfMapStyles\n  };\n\n  if (state.styleType === id) {\n    // if removing a custom style that is also the current active base map,\n    // then reset to the default active base map (`mapStyle.styleType`)\n    return mapStyleChangeUpdater(newState, {payload: {styleType: getDefaultState().styleType}});\n  }\n\n  return newState;\n};\n\n/**\n * Updates 3d building color\n * @memberof mapStyleUpdaters\n */\nexport const set3dBuildingColorUpdater = (\n  state: MapStyle,\n  {payload: color}: MapStyleActions.Set3dBuildingColorUpdaterAction\n): MapStyle => ({\n  ...state,\n  threeDBuildingColor: color,\n  custom3DBuildingColor: true\n});\n\n/**\n * Updates background color\n * @memberof mapStyleUpdaters\n */\nexport const setBackgroundColorUpdater = (\n  state: MapStyle,\n  {payload: color}: MapStyleActions.SetBackgroundColorUpdaterAction\n): MapStyle => ({\n  ...state,\n  backgroundColor: color\n});\n\n/**\n * Return the initial input style\n * @return Object\n */\nexport function getInitialInputStyle(): InputStyle {\n  return {\n    id: null,\n    accessToken: null,\n    error: false,\n    isValid: false,\n    label: null,\n    style: null,\n    url: null,\n    icon: null,\n    custom: 'LOCAL',\n    uploadedFile: null\n  };\n}\n"
  },
  {
    "path": "src/reducers/src/map-style.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\nimport {ActionTypes} from '@kepler.gl/actions';\nimport * as mapStyleUpdaters from './map-style-updaters';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.INIT]: mapStyleUpdaters.initMapStyleUpdater,\n  [ActionTypes.INPUT_MAP_STYLE]: mapStyleUpdaters.inputMapStyleUpdater,\n  [ActionTypes.MAP_CONFIG_CHANGE]: mapStyleUpdaters.mapConfigChangeUpdater,\n  [ActionTypes.MAP_STYLE_CHANGE]: mapStyleUpdaters.mapStyleChangeUpdater,\n  [ActionTypes.REQUEST_MAP_STYLES]: mapStyleUpdaters.requestMapStylesUpdater,\n  [ActionTypes.LOAD_MAP_STYLES]: mapStyleUpdaters.loadMapStylesUpdater,\n  [ActionTypes.LOAD_MAP_STYLE_ERR]: mapStyleUpdaters.loadMapStyleErrUpdater,\n  [ActionTypes.RECEIVE_MAP_CONFIG]: mapStyleUpdaters.receiveMapConfigUpdater,\n  [ActionTypes.LOAD_CUSTOM_MAP_STYLE]: mapStyleUpdaters.loadCustomMapStyleUpdater,\n  [ActionTypes.ADD_CUSTOM_MAP_STYLE]: mapStyleUpdaters.addCustomMapStyleUpdater,\n  [ActionTypes.EDIT_CUSTOM_MAP_STYLE]: mapStyleUpdaters.editCustomMapStyleUpdater,\n  [ActionTypes.REMOVE_CUSTOM_MAP_STYLE]: mapStyleUpdaters.removeCustomMapStyleUpdater,\n  [ActionTypes.RESET_MAP_CONFIG]: mapStyleUpdaters.resetMapConfigMapStyleUpdater,\n  [ActionTypes.SET_3D_BUILDING_COLOR]: mapStyleUpdaters.set3dBuildingColorUpdater,\n  [ActionTypes.SET_BACKGROUND_COLOR]: mapStyleUpdaters.setBackgroundColorUpdater\n};\n\nexport const mapStyleReducerFactory = (initialState = {}) =>\n  // @ts-expect-error\n  handleActions(actionHandler, {\n    ...mapStyleUpdaters.INITIAL_MAP_STYLE,\n    ...initialState,\n    initialState\n  });\n\nexport default mapStyleReducerFactory();\n"
  },
  {
    "path": "src/reducers/src/merger-handler.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {getGlobalTaskQueue} from 'react-palm/tasks';\nimport {isObject} from '@kepler.gl/utils';\nimport {toArray} from '@kepler.gl/common-utils';\nimport {ValueOf} from '@kepler.gl/types';\nimport {VisState, Merger, PostMergerPayload} from '@kepler.gl/schemas';\n\nexport function isValidMerger(merger: Merger<any>): boolean {\n  return (\n    isObject(merger) &&\n    typeof merger.merge === 'function' &&\n    (typeof merger.prop === 'string' || Array.isArray(merger.prop))\n  );\n}\n\n/**\n * Call state updater, return the tasks created by the state update with withTask()\n */\nfunction callFunctionGetTask(fn: () => any): [any, any] {\n  const before = getGlobalTaskQueue();\n  const ret = fn();\n  const after = getGlobalTaskQueue();\n  const diff = after.filter(t => !before.includes(t));\n  return [ret, diff];\n}\n\nexport function mergeStateFromMergers<State extends VisState>(\n  state: State,\n  initialState: State,\n  mergers: Merger<State>[],\n  postMergerPayload: PostMergerPayload\n): {\n  mergedState: State;\n  allMerged: boolean;\n} {\n  // const newDataIds = Object.keys(postMergerPayload.newDataEntries);\n  let mergedState = state;\n  // merge state with config to be merged\n  const mergerQueue = [...mergers];\n\n  while (mergerQueue.length) {\n    const merger = mergerQueue.shift();\n\n    if (\n      merger &&\n      isValidMerger(merger) &&\n      merger.toMergeProp &&\n      hasPropsToMerge(state, merger.toMergeProp)\n    ) {\n      // put the rest of mergers and payload for postMergeUpdater in mergerActionPayload\n      // and pass it to current merger, which (if async) knows to continue merging\n      const mergerActionPayload = {\n        mergers: mergerQueue,\n        postMergerPayload\n      };\n      // reset toMerge\n      const toMerge = getPropValueToMerger(mergedState, merger.toMergeProp, merger.toMergeProp);\n\n      mergedState = resetStateToMergeProps(mergedState, initialState, merger.toMergeProp);\n      // call merger\n      // eslint-disable-next-line no-loop-func\n      const mergeFunc = () => merger.merge(mergedState, toMerge, false, mergerActionPayload);\n      const [updatedState, newTasks] = callFunctionGetTask(mergeFunc);\n\n      mergedState = updatedState;\n\n      // check if asyncTask was created (time consuming tasks)\n      if (newTasks.length && merger.waitToFinish) {\n        // skip rest, the async merger will call applyMergerUpdater() to continue\n        return {mergedState, allMerged: false};\n      }\n    }\n  }\n\n  // we merged all mergers in the queue, and we can call post merger now\n  return {mergedState, allMerged: true};\n}\n\nexport function hasPropsToMerge<State extends object>(\n  state: State,\n  mergerProps?: string | string[]\n): boolean {\n  return Array.isArray(mergerProps)\n    ? Boolean(mergerProps.some(p => Object.prototype.hasOwnProperty.call(state, p)))\n    : typeof mergerProps === 'string' && Object.prototype.hasOwnProperty.call(state, mergerProps);\n}\n\nexport function getPropValueToMerger<State extends object>(\n  state: State,\n  mergerProps?: string | string[],\n  toMergeProps?: string | string[]\n): Partial<State> | ValueOf<State> {\n  return Array.isArray(mergerProps) && Array.isArray(toMergeProps)\n    ? mergerProps.reduce((accu, p, i) => {\n        if (!toMergeProps) return accu;\n        return {...accu, [toMergeProps[i]]: state[p]};\n      }, {})\n    : state[mergerProps as string];\n}\n\nexport function resetStateToMergeProps<State extends VisState>(\n  state: State,\n  initialState: State,\n  mergerProps: string | string[]\n) {\n  return toArray(mergerProps).reduce(\n    (accu, prop) => ({\n      ...accu,\n      [prop]: initialState[prop]\n    }),\n    state\n  );\n}\n"
  },
  {
    "path": "src/reducers/src/middleware.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Extra helpers for redux\n// We are exposing this secause react-palm has no UMD module and\n// users need taskMiddleware to initiate their redux middle ware\nimport {taskMiddleware} from 'react-palm/tasks';\nimport {Middleware} from 'redux';\n\n/**\n * This method is used to enhance redux middleware and provide\n * functionality to support react-palm\n * @param middlewares current redux middlewares\n * @returns {*[]} the original list of middlewares plus the react-palm middleware\n */\nexport function enhanceReduxMiddleware(middlewares: Middleware[] = []): Middleware[] {\n  return [...middlewares, taskMiddleware];\n}\n"
  },
  {
    "path": "src/reducers/src/provider-state-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Task, {withTask} from 'react-palm/tasks';\nimport Console from 'global/console';\nimport {getApplicationConfig, getError, isPlainObject} from '@kepler.gl/utils';\nimport {generateHashId, toArray} from '@kepler.gl/common-utils';\nimport {\n  EXPORT_FILE_TO_CLOUD_TASK,\n  ACTION_TASK,\n  DELAY_TASK,\n  LOAD_CLOUD_MAP_TASK\n} from '@kepler.gl/tasks';\nimport {\n  exportFileSuccess,\n  exportFileError,\n  postSaveLoadSuccess,\n  loadCloudMapSuccess,\n  loadCloudMapSuccess2,\n  loadCloudMapError,\n  resetProviderStatus,\n  removeNotification,\n  toggleModal,\n  addNotification,\n  addDataToMap,\n  ProviderActions\n} from '@kepler.gl/actions';\nimport {\n  DEFAULT_NOTIFICATION_TYPES,\n  DEFAULT_NOTIFICATION_TOPICS,\n  DATASET_FORMATS,\n  OVERWRITE_MAP_ID\n} from '@kepler.gl/constants';\nimport {AddDataToMapPayload, ExportFileToCloudPayload} from '@kepler.gl/types';\n\nimport {FILE_CONFLICT_MSG, MapListItem} from '@kepler.gl/cloud-providers';\nimport {DATASET_HANDLERS} from '@kepler.gl/processors';\nimport {KeplerTable} from '@kepler.gl/table';\n\ntype ActionPayload<P> = {\n  type?: string;\n  payload: P;\n};\n\nexport type ProviderState = {\n  isProviderLoading: boolean;\n  isCloudMapLoading: boolean;\n  providerError: any;\n  currentProvider: string | null;\n  successInfo: any;\n  mapSaved: null | string;\n  savedMapId: null | string;\n  initialState?: any;\n  visualizations: MapListItem[];\n};\n\nexport const INITIAL_PROVIDER_STATE: ProviderState = {\n  isProviderLoading: false,\n  isCloudMapLoading: false,\n  providerError: null,\n  currentProvider: null,\n  successInfo: {},\n  mapSaved: null,\n  savedMapId: null,\n  visualizations: []\n};\n\nfunction createActionTask(action, payload) {\n  if (typeof action === 'function') {\n    return ACTION_TASK().map(() => action(payload));\n  }\n\n  return null;\n}\n\nfunction _validateProvider(provider, method) {\n  if (!provider) {\n    Console.error(`provider is not defined`);\n    return false;\n  }\n\n  if (typeof provider[method] !== 'function') {\n    Console.error(`${method} is not a function of Cloud provider: ${provider.name}`);\n    return false;\n  }\n\n  return true;\n}\n\nfunction createGlobalNotificationTasks({\n  type,\n  message,\n  delayClose = true\n}: {\n  type?: string;\n  message: string;\n  delayClose?: boolean;\n}) {\n  const id = generateHashId();\n  const successNote = {\n    id,\n    type: DEFAULT_NOTIFICATION_TYPES[type || ''] || DEFAULT_NOTIFICATION_TYPES.success,\n    topic: DEFAULT_NOTIFICATION_TOPICS.global,\n    message\n  };\n  const task = ACTION_TASK().map(() => addNotification(successNote));\n  return delayClose ? [task, DELAY_TASK(3000).map(() => removeNotification(id))] : [task];\n}\n\n/**\n * This method will export the current kepler config file to the chosen cloud proder\n * add returns a share URL\n *\n */\nexport const exportFileToCloudUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ExportFileToCloudPayload>\n): ProviderState => {\n  const {mapData, provider, options = {}, onSuccess, onError, closeModal} = action.payload;\n\n  if (!_validateProvider(provider, 'uploadMap')) {\n    return state;\n  }\n\n  const newState = {\n    ...state,\n    isProviderLoading: true,\n    currentProvider: provider.name\n  };\n\n  // payload called by provider.uploadMap\n  const payload = {\n    mapData,\n    options\n  };\n  const uploadFileTask = EXPORT_FILE_TO_CLOUD_TASK({provider, payload}).bimap(\n    // success\n    response => exportFileSuccess({response, provider, options, onSuccess, closeModal}),\n    // error\n    error => exportFileError({error, provider, options, onError})\n  );\n\n  return withTask(newState, uploadFileTask);\n};\n\nexport const exportFileSuccessUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.ExportFileSuccessPayload>\n): ProviderState => {\n  const {response, provider, options = {}, onSuccess, closeModal} = action.payload;\n\n  const newState = {\n    ...state,\n    isProviderLoading: false,\n    // TODO: do we always have to store this?\n    successInfo: response,\n    ...(!options.isPublic\n      ? {\n          mapSaved: provider.name,\n          savedMapId: response?.info?.id ?? null\n        }\n      : {})\n  };\n\n  const tasks = [\n    createActionTask(onSuccess, {response, provider, options}),\n    closeModal &&\n      ACTION_TASK().map(() => postSaveLoadSuccess(`Map saved to ${state.currentProvider}!`))\n  ].filter(d => d);\n\n  return tasks.length ? withTask(newState, tasks) : newState;\n};\n\n/**\n * Close modal on success and display notification\n */\nexport const postSaveLoadSuccessUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.PostSaveLoadSuccessPayload>\n): ProviderState => {\n  const message = action.payload || `Saved / Load to ${state.currentProvider} Success`;\n\n  const tasks = [\n    ACTION_TASK().map(() => toggleModal(null)),\n    ACTION_TASK().map(() => resetProviderStatus()),\n    ...createGlobalNotificationTasks({message})\n  ];\n\n  return withTask(state, tasks);\n};\n\nexport const exportFileErrorUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.ExportFileErrorPayload>\n): ProviderState => {\n  const {error, provider, onError} = action.payload;\n\n  const newState = {\n    ...state,\n    isProviderLoading: false\n  };\n\n  if (isFileConflict(error)) {\n    newState.mapSaved = provider.name;\n    return withTask(newState, [ACTION_TASK().map(() => toggleModal(OVERWRITE_MAP_ID))]);\n  }\n\n  newState.providerError = getError(error);\n  const task = createActionTask(onError, {error, provider});\n\n  return task ? withTask(newState, task) : newState;\n};\n\nexport const loadCloudMapUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.LoadCloudMapPayload>\n): ProviderState => {\n  const {loadParams, provider, onSuccess, onError} = action.payload;\n  if (!loadParams) {\n    Console.warn('load map error: loadParams is undefined');\n    return state;\n  }\n  if (!_validateProvider(provider, 'downloadMap')) {\n    return state;\n  }\n\n  const newState = {\n    ...state,\n    isProviderLoading: true,\n    isCloudMapLoading: true\n  };\n\n  // payload called by provider.downloadMap\n  const uploadFileTask = LOAD_CLOUD_MAP_TASK({provider, payload: loadParams}).bimap(\n    // success\n    // @ts-expect-error\n    response => loadCloudMapSuccess({response, loadParams, provider, onSuccess, onError}),\n    // error\n    // @ts-expect-error\n    error => loadCloudMapError({error, provider, onError})\n  );\n\n  return withTask(newState, uploadFileTask);\n};\n\nfunction isFileConflict(error) {\n  return error && error.message === FILE_CONFLICT_MSG;\n}\n\nfunction checkLoadMapResponseError(response) {\n  if (!response || !isPlainObject(response)) {\n    return new Error('Load map response is empty');\n  }\n  if (!isPlainObject(response.map)) {\n    return new Error(`Load map response should be an object property \"map\"`);\n  }\n  if (!response.map.datasets || !response.map.config) {\n    return new Error(`Load map response.map should be an object with property datasets or config`);\n  }\n\n  return null;\n}\n\nfunction getDatasetHandler(format) {\n  const defaultHandler = DATASET_HANDLERS[DATASET_FORMATS.csv];\n  if (!format) {\n    Console.warn('format is not provided in load map response, will use csv by default');\n    return defaultHandler;\n  }\n\n  // use custom processors from table class\n  const TableClass = getApplicationConfig().table ?? KeplerTable;\n  if (typeof TableClass.getFileProcessor === 'function') {\n    const processorResult = TableClass.getFileProcessor(null, format);\n    if (!processorResult.processor) {\n      Console.warn(`No processor found for format ${format}, will use csv by default`);\n      return defaultHandler;\n    }\n    return processorResult.processor;\n  }\n\n  if (!DATASET_HANDLERS[format]) {\n    const supportedFormat = Object.keys(DATASET_FORMATS)\n      .map(k => `'${k}'`)\n      .join(', ');\n    Console.warn(\n      `unknown format ${format}. Please use one of ${supportedFormat}, will use csv by default`\n    );\n    return defaultHandler;\n  }\n\n  return DATASET_HANDLERS[format];\n}\n\n/**\n * A task to handle async processorMethod\n * @param param0\n * @returns\n */\nasync function parseLoadMapResponseTask({\n  response,\n  loadParams,\n  provider\n}: {\n  response: ProviderActions.LoadCloudMapSuccessPayload['response'];\n  loadParams: ProviderActions.LoadCloudMapSuccessPayload['loadParams'];\n  provider: ProviderActions.LoadCloudMapSuccessPayload['provider'];\n}) {\n  const {map, format} = response;\n  const processorMethod = getDatasetHandler(format);\n\n  let parsedDatasets: AddDataToMapPayload['datasets'] = [];\n\n  if (\n    format === DATASET_FORMATS.keplergl &&\n    processorMethod !== DATASET_HANDLERS[DATASET_FORMATS.keplergl]\n  ) {\n    // plugin table provides processor for keplergl map, not single dataset with allData\n    const parsedMap = await processorMethod(map);\n    parsedDatasets = parsedMap.datasets;\n  } else {\n    const datasets = toArray(map.datasets);\n    parsedDatasets = await Promise.all(\n      datasets.map(async ds => {\n        if (format === DATASET_FORMATS.keplergl) {\n          // no need to obtain id, directly pass them in\n          return await processorMethod(ds);\n        }\n        const info = (ds && ds.info) || {id: generateHashId(6)};\n        const data = await processorMethod(ds.data || ds);\n        return {info, data};\n      })\n    );\n  }\n\n  const info = {\n    ...map.info,\n    provider: provider.name,\n    loadParams\n  };\n  return {\n    datasets: parsedDatasets,\n    info,\n    ...(map.config ? {config: map.config} : {}),\n    options: {\n      // do not center map when loading cloud map\n      centerMap: false\n    }\n  };\n}\n\nconst PARSE_LOAD_MAP_RESPONSE_TASK = Task.fromPromise(\n  parseLoadMapResponseTask,\n  'PARSE_LOAD_MAP_RESPONSE_TASK'\n);\n\n/**\n * Used to load resources stored in a private storage.\n */\nexport const loadCloudMapSuccessUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.LoadCloudMapSuccessPayload>\n): ProviderState => {\n  const {response, loadParams, provider, onError} = action.payload;\n\n  const formatError = checkLoadMapResponseError(response);\n  if (formatError) {\n    // if response format is not correct\n    return exportFileErrorUpdater(state, {\n      payload: {error: formatError, provider, onError}\n    });\n  }\n\n  // processorMethod can be async so create a task\n  const parseLoadMapResponseTask = PARSE_LOAD_MAP_RESPONSE_TASK({\n    response,\n    loadParams,\n    provider\n  }).bimap(\n    (datasetsPayload: AddDataToMapPayload) => {\n      return loadCloudMapSuccess2({...action.payload, datasetsPayload});\n    },\n    error =>\n      exportFileErrorUpdater(state, {\n        payload: {error, provider, onError}\n      })\n  );\n\n  return withTask(state, parseLoadMapResponseTask);\n};\n\nexport const loadCloudMapSuccess2Updater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.LoadCloudMapSuccess2Payload>\n): ProviderState => {\n  const {datasetsPayload, response, loadParams, provider, onSuccess} = action.payload;\n\n  const newState = {\n    ...state,\n    mapSaved: provider.name,\n    currentProvider: provider.name,\n    isCloudMapLoading: false,\n    isProviderLoading: false\n  };\n\n  const tasks = [\n    ACTION_TASK().map(() => addDataToMap(datasetsPayload)),\n    createActionTask(onSuccess, {response, loadParams, provider}),\n    ACTION_TASK().map(() => postSaveLoadSuccess(`Map from ${provider.name} loaded`))\n  ].filter(d => d);\n\n  return tasks.length ? withTask(newState, tasks) : newState;\n};\n\nexport const loadCloudMapErrorUpdater = (\n  state: ProviderState,\n  action: ActionPayload<ProviderActions.LoadCloudMapErrorPayload>\n): ProviderState => {\n  const message = getError(action.payload.error) || `Error loading saved map`;\n\n  Console.warn(message);\n\n  const newState = {\n    ...state,\n    isProviderLoading: false,\n    isCloudMapLoading: false,\n    providerError: null\n  };\n\n  return withTask(\n    newState,\n    createGlobalNotificationTasks({type: 'error', message, delayClose: false})\n  );\n};\n\nexport const resetProviderStatusUpdater = (state: ProviderState): ProviderState => ({\n  ...state,\n  isProviderLoading: false,\n  providerError: null,\n  isCloudMapLoading: false,\n  successInfo: {}\n});\n"
  },
  {
    "path": "src/reducers/src/provider-state.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\nimport * as providerStateUpdaters from './provider-state-updaters';\nimport {ProviderActionTypes as ActionTypes} from '@kepler.gl/actions';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.EXPORT_FILE_TO_CLOUD]: providerStateUpdaters.exportFileToCloudUpdater,\n  [ActionTypes.EXPORT_FILE_SUCCESS]: providerStateUpdaters.exportFileSuccessUpdater,\n  [ActionTypes.EXPORT_FILE_ERROR]: providerStateUpdaters.exportFileErrorUpdater,\n  [ActionTypes.RESET_PROVIDER_STATUS]: providerStateUpdaters.resetProviderStatusUpdater,\n  [ActionTypes.POST_SAVE_LOAD_SUCCESS]: providerStateUpdaters.postSaveLoadSuccessUpdater,\n  [ActionTypes.LOAD_CLOUD_MAP]: providerStateUpdaters.loadCloudMapUpdater,\n  [ActionTypes.LOAD_CLOUD_MAP_SUCCESS]: providerStateUpdaters.loadCloudMapSuccessUpdater,\n  [ActionTypes.LOAD_CLOUD_MAP_SUCCESS_2]: providerStateUpdaters.loadCloudMapSuccess2Updater,\n  [ActionTypes.LOAD_CLOUD_MAP_ERROR]: providerStateUpdaters.loadCloudMapErrorUpdater\n};\n\n// construct provider-state reducer\nexport const providerStateReducerFactory = (initialState = {}) =>\n  // @ts-expect-error\n  handleActions(actionHandler, {\n    ...providerStateUpdaters.INITIAL_PROVIDER_STATE,\n    ...initialState,\n    initialState\n  });\n\nexport default providerStateReducerFactory();\n"
  },
  {
    "path": "src/reducers/src/root.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  ActionTypes,\n  keplerGlInit,\n  _actionFor,\n  _updateProperty,\n  RegisterEntryUpdaterAction,\n  RenameEntryUpdaterAction\n} from '@kepler.gl/actions';\nimport {handleActions} from 'redux-actions';\n\nimport {coreReducerFactory, KeplerGlState} from './core';\n\ntype KeplerGlStateMap = {\n  [id: string]: Partial<KeplerGlState>;\n};\n\ntype CombineRegisterUpdateActions = RegisterEntryUpdaterAction['payload'] &\n  RenameEntryUpdaterAction['payload']; // Extend this type with additional actions to enforce strict typings\n\n// INITIAL_STATE\nconst initialCoreState = {};\n\nexport function provideInitialState(initialState, extraReducers?) {\n  const coreReducer = coreReducerFactory(initialState, extraReducers);\n\n  const handleRegisterEntry = (\n    state: KeplerGlStateMap,\n    {\n      payload: {\n        id,\n        mint,\n        mapboxApiAccessToken,\n        mapboxApiUrl,\n        mapStylesReplaceDefault,\n        initialUiState\n      }\n    }: {\n      payload: RegisterEntryUpdaterAction['payload'];\n    }\n  ): KeplerGlStateMap => {\n    // by default, always create a mint state even if the same id already exist\n    // if state.id exist and mint=false, keep the existing state\n    const previousState = state[id] && mint === false ? state[id] : undefined;\n\n    return {\n      // register entry to kepler.gl passing in mapbox config to mapStyle\n      ...state,\n      [id]: coreReducer(\n        previousState,\n        keplerGlInit({mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault, initialUiState})\n      )\n    };\n  };\n\n  const handleDeleteEntry = (\n    state: KeplerGlStateMap,\n    {payload: {id}}: {payload: {id: string}}\n  ): KeplerGlStateMap =>\n    Object.keys(state).reduce(\n      (accu, curr) => ({\n        ...accu,\n        ...(curr === id ? {} : {[curr]: state[curr]})\n      }),\n      {}\n    );\n\n  const handleRenameEntry = (\n    state: KeplerGlStateMap,\n    {\n      payload: {oldId, newId}\n    }: {\n      payload: RenameEntryUpdaterAction['payload'];\n    }\n  ): KeplerGlStateMap =>\n    Object.keys(state).reduce(\n      (accu, curr) => ({\n        ...accu,\n        ...{[curr === oldId ? newId : curr]: state[curr]}\n      }),\n      {}\n    );\n\n  return (state = initialCoreState, action) => {\n    // update child states\n    Object.keys(state).forEach(id => {\n      const updateItemState = coreReducer(state[id], _actionFor(id, action));\n      state = _updateProperty(state, id, updateItemState);\n    });\n\n    // perform additional state reducing (e.g. switch action.type etc...)\n    const handlers = {\n      [ActionTypes.REGISTER_ENTRY]: handleRegisterEntry,\n      [ActionTypes.DELETE_ENTRY]: handleDeleteEntry,\n      [ActionTypes.RENAME_ENTRY]: handleRenameEntry\n    };\n\n    return handleActions<KeplerGlStateMap, CombineRegisterUpdateActions>(\n      handlers,\n      initialCoreState\n    )(state, action);\n  };\n}\n\nconst _keplerGlReducer = provideInitialState(initialCoreState);\n\nfunction mergeInitialState(saved = {}, provided = {}, extraInitialStateKeys: string[] = []) {\n  const keys = ['mapState', 'mapStyle', 'visState', 'uiState', ...extraInitialStateKeys];\n\n  // shallow merge each reducer\n  const newState = keys.reduce(\n    (accu, key) => ({\n      ...accu,\n      ...(saved[key] && provided[key]\n        ? {[key]: {...saved[key], ...provided[key]}}\n        : {[key]: saved[key] || provided[key] || {}})\n    }),\n    {}\n  );\n\n  return newState;\n}\n\nfunction decorate(target, savedInitialState = {}) {\n  const targetInitialState = savedInitialState;\n\n  /**\n   * Returns a kepler.gl reducer that will also pass each action through additional reducers spiecified.\n   * The parameter should be either a reducer map or a reducer function.\n   * The state passed into the additional action handler is the instance state.\n   * It will include all the subreducers `visState`, `uiState`, `mapState` and `mapStyle`.\n   * `.plugin` is only meant to be called once when mounting the keplerGlReducer to the store.\n   * **Note** This is an advanced option to give you more freedom to modify the internal state of the kepler.gl instance.\n   * You should only use this to adding additional actions instead of replacing default actions.\n   *\n   * @mixin keplerGlReducer.plugin\n   * @memberof keplerGlReducer\n   * @param {Object|Function} customReducer - A reducer map or a reducer\n   * @param {Object} options - options to be applied to custom reducer logic\n   * @param {Object} options.override - objects that describe which action to override, e.g. {[ActionTypes.LAYER_TYPE_CHANGE]: true}\n   * @public\n   * @example\n   * const myKeplerGlReducer = keplerGlReducer\n   *  .plugin({\n   *    // 1. as reducer map\n   *    HIDE_AND_SHOW_SIDE_PANEL: (state, action) => ({\n   *      ...state,\n   *      uiState: {\n   *        ...state.uiState,\n   *        readOnly: !state.uiState.readOnly\n   *      }\n   *    })\n   *  })\n   * .plugin(handleActions({\n   *   // 2. as reducer\n   *   'HIDE_MAP_CONTROLS': (state, action) => ({\n   *     ...state,\n   *     uiState: {\n   *       ...state.uiState,\n   *       mapControls: hiddenMapControl\n   *     }\n   *   })\n   * }, {}));\n   */\n  target.plugin = function plugin(customReducer, options) {\n    if (typeof customReducer === 'object') {\n      // if only provided a reducerMap, wrap it in a reducer\n      customReducer = handleActions(customReducer, {});\n    }\n\n    // use 'function' keyword to enable 'this'\n    // TODO: temporarily making action type to any, will fix that by creating a shared action interface\n    return decorate((state = {}, action: any = {}) => {\n      let nextState = state;\n      if (action.type && !options?.override?.[action.type]) {\n        nextState = this(state, action);\n      }\n\n      // for each entry in the staten\n      Object.keys(nextState).forEach(id => {\n        // update child states\n        nextState = _updateProperty(\n          nextState,\n          id,\n          customReducer(nextState[id], _actionFor(id, action))\n        );\n      });\n      return nextState;\n    });\n  };\n\n  /**\n   * Return a reducer that initiated with custom initial state.\n   * The parameter should be an object mapping from `subreducer` name to custom subreducer state,\n   * which will be shallow **merged** with default initial state.\n   *\n   * Default subreducer state:\n   *  - [`visState`](./vis-state.md#INITIAL_VIS_STATE)\n   *  - [`mapState`](./map-state.md#INITIAL_MAP_STATE)\n   *  - [`mapStyle`](./map-style.md#INITIAL_MAP_STYLE)\n   *  - [`uiState`](./ui-state.md#INITIAL_UI_STATE)\n   * @mixin keplerGlReducer.initialState\n   * @memberof keplerGlReducer\n   * @param {Object} iniSt - custom state to be merged with default initial state\n   * @param {Object} extraReducers - optional custom reducers in addition to the default `visState`, `mapState`, `mapStyle`, and `uiState`\n   * @public\n   * @example\n   * const myKeplerGlReducer = keplerGlReducer\n   *  .initialState({\n   *    uiState: {readOnly: true}\n   *  });\n   */\n  target.initialState = function initialState(iniSt, extraReducers = {}) {\n    // passing through extraInitialStateKeys and extraReducers allows external customization by adding additional subreducers and state beyond the default `visState`, `mapState`, `mapStyle`, and `uiState`\n    const extraInitialStateKeys = Object.keys(extraReducers);\n    const merged = mergeInitialState(targetInitialState, iniSt, extraInitialStateKeys);\n    const targetReducer = provideInitialState(merged, extraReducers);\n\n    return decorate(targetReducer, merged);\n  };\n\n  return target;\n}\n\n/**\n * Kepler.gl reducer to be mounted to your store. You can mount `keplerGlReducer` at property `keplerGl`, if you choose\n * to mount it at another address e.g. `foo` you will need to specify it when you mount `KeplerGl` component in your app with `getState: state => state.foo`\n * @public\n * @example\n * import keplerGlReducer from '@kepler.gl/reducers';\n * import {createStore, combineReducers, applyMiddleware, compose} from 'redux';\n * import {taskMiddleware} from 'react-palm/tasks';\n *\n * const initialState = {};\n * const reducers = combineReducers({\n *   // <-- mount kepler.gl reducer in your app\n *   keplerGl: keplerGlReducer,\n *\n *   // Your other reducers here\n *   app: appReducer\n * });\n *\n * // using createStore\n * export default createStore(reducer, initialState, applyMiddleware(taskMiddleware));\n */\nconst keplerGlReducer = decorate(_keplerGlReducer);\nexport default keplerGlReducer;\n"
  },
  {
    "path": "src/reducers/src/ui-state-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  ADD_DATA_ID,\n  DATA_TABLE_ID,\n  DEFAULT_NOTIFICATION_TOPICS,\n  DELETE_DATA_ID,\n  EXPORT_DATA_TYPE,\n  EXPORT_HTML_MAP_MODES,\n  EXPORT_IMG_RATIOS,\n  EXPORT_MAP_FORMATS,\n  RESOLUTIONS,\n  MAP_CONTROLS\n} from '@kepler.gl/constants';\nimport {LOCALE_CODES} from '@kepler.gl/localization';\nimport {\n  createNotification,\n  errorNotification,\n  calculateExportImageSize,\n  getApplicationConfig\n} from '@kepler.gl/utils';\nimport {payload_, apply_, compose_} from './composer-helpers';\n\nimport {\n  ActionTypes,\n  KeplerGlInitPayload,\n  LoadFilesErrUpdaterAction,\n  UIStateActions\n} from '@kepler.gl/actions';\nimport {\n  ExportData,\n  ExportHtml,\n  ExportJson,\n  ExportMap,\n  ExportImage,\n  MapControlItem,\n  MapControls,\n  UiState\n} from '@kepler.gl/types';\n\nexport const DEFAULT_ACTIVE_SIDE_PANEL = 'layer';\nexport const DEFAULT_MODAL = ADD_DATA_ID;\n\n/**\n * Updaters for `uiState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\n * Read more about [Using updaters](../advanced-usage/using-updaters.md)\n *\n * @public\n * @example\n *\n * import keplerGlReducer, {uiStateUpdaters} from '@kepler.gl/reducers';\n * // Root Reducer\n * const reducers = combineReducers({\n *  keplerGl: keplerGlReducer,\n *  app: appReducer\n * });\n *\n * const composedReducer = (state, action) => {\n *  switch (action.type) {\n *    // click button to close side panel\n *    case 'CLICK_BUTTON':\n *      return {\n *        ...state,\n *        keplerGl: {\n *          ...state.keplerGl,\n *          foo: {\n *             ...state.keplerGl.foo,\n *             uiState: uiStateUpdaters.toggleSidePanelUpdater(\n *               uiState, {payload: null}\n *             )\n *          }\n *        }\n *      };\n *  }\n *  return reducers(state, action);\n * };\n *\n * export default composedReducer;\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst uiStateUpdaters = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n\nconst DEFAULT_MAP_CONTROLS_FEATURES: MapControlItem = {\n  show: true,\n  active: false,\n  disableClose: false,\n  // defines which map index users are interacting with (through map controls)\n  activeMapIndex: 0\n};\n\nconst DEFAULT_MAP_LEGEND_CONTROL = {\n  ...DEFAULT_MAP_CONTROLS_FEATURES,\n  disableEdit: false\n};\n/**\n * A list of map control visibility and whether is it active.\n * @memberof uiStateUpdaters\n * @constant\n * @property visibleLayers Default: `{show: true, active: false}`\n * @property mapLegend Default: `{show: true, active: false}`\n * @property toggle3d Default: `{show: true}`\n * @property splitMap Default: `{show: true}`\n * @property mapDraw Default: `{show: true, active: false}`\n * @property mapLocale Default: `{show: false, active: false}`\n * @public\n */\nexport const DEFAULT_MAP_CONTROLS: MapControls = (\n  Object.keys(MAP_CONTROLS) as Array<keyof typeof MAP_CONTROLS>\n).reduce(\n  (final, current) => ({\n    ...final,\n    [current]:\n      current === MAP_CONTROLS.mapLegend\n        ? DEFAULT_MAP_LEGEND_CONTROL\n        : DEFAULT_MAP_CONTROLS_FEATURES\n  }),\n  {} as MapControls\n);\n\n/**\n * Default image export config\n * @memberof uiStateUpdaters\n * @constant\n * @property ratio Default: `'SCREEN'`,\n * @property resolution Default: `'ONE_X'`,\n * @property legend Default: `false`,\n * @property mapH Default: 0,\n * @property mapW Default: 0,\n * @property imageSize Default: {zoomOffset: 0, scale: 1, imageW: 0, imageH: 0},\n * @property imageDataUri Default: `''`,\n * @property exporting Default: `false`\n * @property error Default: `false`\n * @property escapeXhtmlForWebpack Default: from application config (auto-detected: `true` for webpack)\n * @public\n */\nexport const DEFAULT_EXPORT_IMAGE: ExportImage = {\n  // user options\n  ratio: EXPORT_IMG_RATIOS.SCREEN,\n  resolution: RESOLUTIONS.ONE_X,\n  legend: false,\n  mapH: 0,\n  mapW: 0,\n  imageSize: {\n    zoomOffset: 0,\n    scale: 1,\n    imageW: 0,\n    imageH: 0\n  },\n  // when this is set to true, the mock map viewport will move to the center of data\n  center: false,\n  // exporting state\n  imageDataUri: '',\n  // exporting: used to attach plot-container to dom\n  exporting: false,\n  // processing: used as loading indicator when export image is being produced\n  processing: false,\n  error: false,\n  // whether to apply fix for uglify error in dom-to-image (from application config, auto-detects build tool)\n  escapeXhtmlForWebpack: getApplicationConfig().escapeXhtmlForWebpack\n};\n\nexport const DEFAULT_LOAD_FILES = {\n  fileLoading: false\n};\n\n/**\n * Default initial `exportData` settings\n * @memberof uiStateUpdaters\n * @constant\n * @property selectedDataset Default: `''`,\n * @property dataType Default: `'csv'`,\n * @property filtered Default: `true`,\n * @public\n */\nexport const DEFAULT_EXPORT_DATA: ExportData = {\n  selectedDataset: '',\n  dataType: EXPORT_DATA_TYPE.CSV,\n  filtered: true\n};\n\n/**\n * @constant\n */\nexport const DEFAULT_NOTIFICATIONS = [];\n\n/**\n * @constant\n * @property exportMapboxAccessToken - Default: null, this is used when we provide a default mapbox token for users to take advantage of\n * @property userMapboxToken - Default: '', mapbox token provided by user through input field\n * @property mode - Default: 'READ', read only or editable\n * @public\n */\nexport const DEFAULT_EXPORT_HTML: ExportHtml = {\n  exportMapboxAccessToken: null,\n  userMapboxToken: '',\n  mode: EXPORT_HTML_MAP_MODES.READ\n};\n\n/**\n * @constant\n * @property hasData - Default: 'true',\n * @public\n */\nexport const DEFAULT_EXPORT_JSON: ExportJson = {\n  hasData: true\n};\n\n/**\n * Export Map Config\n * @constant\n * @property HTML - Default: 'DEFAULT_EXPORT_HTML',\n * @property JSON - Default: 'DEFAULT_EXPORT_JSON',\n * @property format - Default: 'HTML',\n * @public\n */\nexport const DEFAULT_EXPORT_MAP: ExportMap = {\n  [EXPORT_MAP_FORMATS.HTML]: DEFAULT_EXPORT_HTML,\n  [EXPORT_MAP_FORMATS.JSON]: DEFAULT_EXPORT_JSON,\n  format: EXPORT_MAP_FORMATS.HTML\n};\n\n/**\n * Default initial `uiState`\n * @memberof uiStateUpdaters\n * @constant\n * @property readOnly Default: `false`\n * @property activeSidePanel Default: `'layer'`\n * @property currentModal Default: `'addData'`\n * @property datasetKeyToRemove Default: `null`\n * @property visibleDropdown Default: `null`\n * @property exportImage Default: [`DEFAULT_EXPORT_IMAGE`](#default_export_image)\n * @property exportData Default: [`DEFAULT_EXPORT_DATA`](#default_export_data)\n * @property exportMap Default: [`DEFAULT_EXPORT_MAP`](#default_export_map)\n * @property mapControls Default: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @property notifications Default: `[]`\n * @property notifications Default: `[]`\n * @property loadFiles\n * @property isSidePanelCloseButtonVisible Default: `true`\n * @public\n */\nexport const INITIAL_UI_STATE: UiState = {\n  readOnly: false,\n  activeSidePanel: DEFAULT_ACTIVE_SIDE_PANEL,\n  currentModal: DEFAULT_MODAL,\n  datasetKeyToRemove: null,\n  visibleDropdown: null,\n  // export image modal ui\n  exportImage: DEFAULT_EXPORT_IMAGE,\n  // export data modal ui\n  exportData: DEFAULT_EXPORT_DATA,\n  // html export\n  exportMap: DEFAULT_EXPORT_MAP,\n  // map control panels\n  mapControls: DEFAULT_MAP_CONTROLS,\n  // ui notifications\n  notifications: DEFAULT_NOTIFICATIONS,\n  // load files\n  loadFiles: DEFAULT_LOAD_FILES,\n  // Locale of the UI\n  locale: LOCALE_CODES.en,\n  layerPanelListView: 'list',\n  filterPanelListView: 'list',\n  isSidePanelCloseButtonVisible: true\n};\n\n/* Updaters */\n/**\n * @memberof uiStateUpdaters\n */\nexport const initUiStateUpdater = (\n  state: UiState,\n  action: {\n    type?: (typeof ActionTypes)['INIT'];\n    payload: KeplerGlInitPayload;\n  }\n): UiState => ({\n  ...state,\n  ...(action.payload || {}).initialUiState\n});\n\n/**\n * Toggle active side panel\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload id of side panel to be shown, one of `layer`, `filter`, `interaction`, `map`. close side panel if `null`\n * @returns nextState\n * @public\n */\nexport const toggleSidePanelUpdater = (\n  state: UiState,\n  {payload: id}: UIStateActions.ToggleSidePanelUpdaterAction\n): UiState => {\n  return id === state.activeSidePanel\n    ? state\n    : {\n        ...state,\n        activeSidePanel: id\n      };\n};\n\n/**\n * Show and hide modal dialog\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @paramaction.payload id of modal to be shown, null to hide modals. One of:\n *  - [`DATA_TABLE_ID`](../constants/default-settings.md#data_table_id)\n *  - [`DELETE_DATA_ID`](../constants/default-settings.md#delete_data_id)\n *  - [`ADD_DATA_ID`](../constants/default-settings.md#add_data_id)\n *  - [`EXPORT_IMAGE_ID`](../constants/default-settings.md#export_image_id)\n *  - [`EXPORT_DATA_ID`](../constants/default-settings.md#export_data_id)\n *  - [`ADD_MAP_STYLE_ID`](../constants/default-settings.md#add_map_style_id)\n * @returns nextState\n * @public\n */\nexport const toggleModalUpdater = (\n  state: UiState,\n  {payload: id}: UIStateActions.ToggleModalUpdaterAction\n): UiState => ({\n  ...state,\n  currentModal: id\n});\n\n/**\n * Hide and show side panel header dropdown, activated by clicking the share link on top of the side panel\n * @memberof uiStateUpdaters\n * @public\n */\nexport const showExportDropdownUpdater = (\n  state: UiState,\n  {payload: id}: UIStateActions.ShowExportDropdownUpdaterAction\n): UiState => ({\n  ...state,\n  visibleDropdown: id\n});\n\n/**\n * Hide side panel header dropdown, activated by clicking the share link on top of the side panel\n * @memberof uiStateUpdaters\n * @public\n */\nexport const hideExportDropdownUpdater = (state: UiState): UiState => ({\n  ...state,\n  visibleDropdown: null\n});\n\n/**\n * Toggle side panel close button on top left of the side panel\n * @memberof uiStateUpdaters\n * @public\n */\nexport const toggleSidePanelCloseButtonUpdater = (\n  state: UiState,\n  {payload: {show}}: UIStateActions.ToggleSidePanelCloseButtonUpdaterAction\n): UiState => ({\n  ...state,\n  isSidePanelCloseButtonVisible: show\n});\n\n/**\n * Toggle active map control panel\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action action\n * @param action.payload map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @returns nextState\n * @public\n */\nexport const toggleMapControlUpdater = (\n  state: UiState,\n  {payload: {panelId, index = 0}}: UIStateActions.ToggleMapControlUpdaterAction\n): UiState => {\n  let updatedState = state;\n  // The effect panel and ai assistant panel can not be active at the same time\n  // so we need to deactivate the other panel when one is activated\n  const panelToDeactivate =\n    panelId === MAP_CONTROLS.effect\n      ? MAP_CONTROLS.aiAssistant\n      : panelId === MAP_CONTROLS.aiAssistant\n      ? MAP_CONTROLS.effect\n      : null;\n\n  // To to toggle the mapDraw and mapLocal dropdowns\n  // We have to deactivate the other active dropdown\n  const dropdownToDeactivate =\n    panelId === MAP_CONTROLS.mapDraw\n      ? MAP_CONTROLS.mapLocale\n      : panelId === MAP_CONTROLS.mapLocale\n      ? MAP_CONTROLS.mapDraw\n      : null;\n\n  // If we need to deactivate a competing panel and it's currently active\n  if (panelToDeactivate && state.mapControls[panelToDeactivate]?.active) {\n    updatedState = {\n      ...state,\n      mapControls: {\n        ...updatedState.mapControls,\n        [panelToDeactivate]: {\n          ...updatedState.mapControls[panelToDeactivate],\n          active: false\n        }\n      }\n    };\n  }\n\n  // If we need to deactivate a competing dropdown and it's currently active\n  if (dropdownToDeactivate && state.mapControls[dropdownToDeactivate]?.active) {\n    updatedState = {\n      ...state,\n      mapControls: {\n        ...updatedState.mapControls,\n        [dropdownToDeactivate]: {\n          ...updatedState.mapControls[dropdownToDeactivate],\n          active: false\n        }\n      }\n    };\n  }\n\n  return {\n    ...updatedState,\n    mapControls: {\n      ...updatedState.mapControls,\n      [panelId]: {\n        ...updatedState.mapControls[panelId],\n        active: !updatedState.mapControls[panelId].active,\n        activeMapIndex: index\n      }\n    }\n  };\n};\n\n/**\n * Toggle map control visibility\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action action\n * @param action.payload map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @returns nextState\n * @public\n */\nexport const setMapControlVisibilityUpdater = (\n  state: UiState,\n  {payload: {panelId, show}}: UIStateActions.setMapControlVisibilityUpdaterAction\n): UiState => {\n  if (!state.mapControls?.[panelId]) {\n    return state;\n  }\n\n  return {\n    ...state,\n    mapControls: {\n      ...state.mapControls,\n      [panelId]: {\n        ...state.mapControls[panelId],\n        show: Boolean(show)\n      }\n    }\n  };\n};\n\n/**\n * Toggle map control settings\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action action\n * @param action.payload map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`](#default_map_controls)\n * @returns nextState\n * @public\n */\nexport const setMapControlSettingsUpdater = (\n  state: UiState,\n  {payload: {panelId, settings}}: UIStateActions.setMapControlSettingsUpdaterAction\n): UiState => {\n  const mapControl = state.mapControls?.[panelId];\n  if (!mapControl) {\n    return state;\n  }\n\n  return {\n    ...state,\n    mapControls: {\n      ...state.mapControls,\n      [panelId]: {...mapControl, settings: {...mapControl.settings, ...settings}}\n    }\n  };\n};\n\n/**\n * Toggle active map control panel\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload dataset id\n * @returns nextState\n * @public\n */\nexport const openDeleteModalUpdater = (\n  state: UiState,\n  {payload: datasetKeyToRemove}: UIStateActions.OpenDeleteModalUpdaterAction\n): UiState => ({\n  ...state,\n  currentModal: DELETE_DATA_ID,\n  datasetKeyToRemove\n});\n\n/**\n * Set `exportImage.legend` to `true` or `false`\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @returns nextState\n * @public\n */\nexport const setExportImageSettingUpdater = (\n  state: UiState,\n  {payload: newSetting}: UIStateActions.SetExportImageSettingUpdaterAction\n): UiState => {\n  const updated = {...state.exportImage, ...newSetting};\n  const imageSize = calculateExportImageSize(updated) || state.exportImage.imageSize;\n\n  return {\n    ...state,\n    exportImage: {\n      ...updated,\n      // @ts-expect-error\n      // TODO: calculateExportImageSize does not return imageSize.zoomOffset,\n      // do we need take this value from current state, or return defaul value = 0\n      imageSize\n    }\n  };\n};\n\n/**\n * Set `exportImage.setExportImageDataUri` to a image dataUri\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload export image data uri\n * @returns nextState\n * @public\n */\nexport const setExportImageDataUriUpdater = (\n  state: UiState,\n  {payload: dataUri}: UIStateActions.SetExportImageDataUriUpdaterAction\n): UiState => {\n  if (dataUri === state.exportImage.imageDataUri) {\n    return state;\n  }\n  return {\n    ...state,\n    exportImage: {\n      ...state.exportImage,\n      processing: false,\n      imageDataUri: dataUri\n    }\n  };\n};\n\n/**\n * @memberof uiStateUpdaters\n * @public\n */\nexport const setExportImageErrorUpdater = (\n  state: UiState,\n  {payload: error}: UIStateActions.SetExportImageErrorUpdaterAction\n): UiState => ({\n  ...state,\n  exportImage: {\n    ...state.exportImage,\n    processing: false,\n    error\n  }\n});\n\n/**\n * Delete cached export image\n * @memberof uiStateUpdaters\n * @public\n */\nexport const cleanupExportImageUpdater = (state: UiState): UiState => ({\n  ...state,\n  exportImage: {\n    ...state.exportImage,\n    exporting: false,\n    imageDataUri: '',\n    error: false,\n    processing: false,\n    center: false\n  }\n});\n\n/**\n * Start image exporting flow\n * @memberof uiStateUpdaters\n * @param state\n * @param options\n * @returns {UiState}\n * @public\n */\nexport const startExportingImageUpdater = (\n  state: UiState,\n  {payload: options = {}}: {payload: Partial<ExportImage>}\n): UiState => {\n  const imageSettings = {\n    ...options,\n    exporting: true\n  };\n\n  return compose_([\n    cleanupExportImageUpdater,\n    apply_(setExportImageSettingUpdater, payload_(imageSettings))\n  ])(state);\n};\n\n/**\n * Set selected dataset for export\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload dataset id\n * @returns nextState\n * @public\n */\nexport const setExportSelectedDatasetUpdater = (\n  state: UiState,\n  {payload: dataset}: UIStateActions.SetExportSelectedDatasetUpdaterAction\n): UiState => ({\n  ...state,\n  exportData: {\n    ...state.exportData,\n    selectedDataset: dataset\n  }\n});\n\n/**\n * Set data format for exporting data\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload one of `'text/csv'`\n * @returns nextState\n * @public\n */\nexport const setExportDataTypeUpdater = (\n  state: UiState,\n  {payload: dataType}: UIStateActions.SetExportDataTypeUpdaterAction\n): UiState => ({\n  ...state,\n  exportData: {\n    ...state.exportData,\n    dataType\n  }\n});\n\n/**\n * Whether to export filtered data, `true` or `false`\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload\n * @returns nextState\n * @public\n */\nexport const setExportFilteredUpdater = (\n  state: UiState,\n  {payload: filtered}: UIStateActions.SetExportFilteredUpdaterAction\n): UiState => ({\n  ...state,\n  exportData: {\n    ...state.exportData,\n    filtered\n  }\n});\n\n/**\n * Whether to including data in map config, toggle between `true` or `false`\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @returns nextState\n * @public\n */\nexport const setExportDataUpdater = (state: UiState): UiState => ({\n  ...state,\n  exportMap: {\n    ...state.exportMap,\n    [EXPORT_MAP_FORMATS.JSON]: {\n      ...state.exportMap[EXPORT_MAP_FORMATS.JSON],\n      hasData: !state.exportMap[EXPORT_MAP_FORMATS.JSON].hasData\n    }\n  }\n});\n\n/**\n * whether to export a mapbox access to HTML single page\n * @param state - `uiState`\n * @param action\n * @param action.payload\n * @returns nextState\n * @public\n */\nexport const setUserMapboxAccessTokenUpdater = (\n  state: UiState,\n  {payload: userMapboxToken}: UIStateActions.SetUserMapboxAccessTokenUpdaterAction\n): UiState => ({\n  ...state,\n  exportMap: {\n    ...state.exportMap,\n    [EXPORT_MAP_FORMATS.HTML]: {\n      ...state.exportMap[EXPORT_MAP_FORMATS.HTML],\n      userMapboxToken\n    }\n  }\n});\n\n/**\n * Sets the export map format\n * @param state - `uiState`\n * @param action\n * @param action.payload format to use to export the map into\n * @return nextState\n */\nexport const setExportMapFormatUpdater = (\n  state: UiState,\n  {payload: format}: UIStateActions.SetExportMapFormatUpdaterAction\n): UiState => ({\n  ...state,\n  exportMap: {\n    ...state.exportMap,\n    // @ts-expect-error\n    format\n  }\n});\n\n/**\n * Set the export html map mode\n * @param state - `uiState`\n * @param action\n * @param action.payload to be set (available modes: EXPORT_HTML_MAP_MODES)\n * @return nextState\n */\nexport const setExportMapHTMLModeUpdater = (\n  state: UiState,\n  {payload: mode}: UIStateActions.SetExportHTMLMapModeUpdaterAction\n): UiState => ({\n  ...state,\n  exportMap: {\n    ...state.exportMap,\n    [EXPORT_MAP_FORMATS.HTML]: {\n      ...state.exportMap[EXPORT_MAP_FORMATS.HTML],\n      mode\n    }\n  }\n});\n\n/**\n * Adds a new notification.\n * Updates a notification in case of matching ids.\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload Params of a notification\n * @returns nextState\n * @public\n */\nexport const addNotificationUpdater = (\n  state: UiState,\n  {payload}: UIStateActions.AddNotificationUpdaterAction\n): UiState => {\n  const oldNotifications = state.notifications || [];\n  // @ts-expect-error\n  const payloadId = payload?.id;\n  const notificationToUpdate = payloadId ? oldNotifications.find(n => n.id === payloadId) : null;\n\n  let notifications;\n  if (notificationToUpdate) {\n    notifications = oldNotifications.map(n =>\n      n.id === payloadId ? createNotification({...payload, count: n.count + 1}) : n\n    );\n  } else {\n    notifications = [...oldNotifications, createNotification(payload)];\n  }\n\n  return {...state, notifications};\n};\n\n/**\n * Remove a notification\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload id of the notification to be removed\n * @returns nextState\n * @public\n */\nexport const removeNotificationUpdater = (\n  state: UiState,\n  {payload: id}: UIStateActions.RemoveNotificationUpdaterAction\n): UiState => ({\n  ...state,\n  notifications: state.notifications.filter(n => n.id !== id)\n});\n\n/**\n * Fired when file loading begin\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @returns nextState\n * @public\n */\nexport const loadFilesUpdater = (state: UiState): UiState => ({\n  ...state,\n  loadFiles: {\n    ...state.loadFiles,\n    fileLoading: true\n  }\n});\n\n/**\n * Handles loading file success and set fileLoading property to false\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @returns nextState\n */\nexport const loadFilesSuccessUpdater = (state: UiState): UiState => ({\n  ...state,\n  loadFiles: {\n    ...state.loadFiles,\n    fileLoading: false\n  }\n});\n\n/**\n * Handles load file error and set fileLoading property to false\n * @memberof uiStateUpdaters\n * @param state\n * @param action\n * @param action.error\n * @returns nextState\n * @public\n */\nexport const loadFilesErrUpdater = (state: UiState, {error}: LoadFilesErrUpdaterAction): UiState =>\n  addNotificationUpdater(\n    {\n      ...state,\n      loadFiles: {\n        ...state.loadFiles,\n        fileLoading: false\n      }\n    },\n    {\n      payload: errorNotification({\n        message: (error || {}).message || 'Failed to upload files',\n        topic: DEFAULT_NOTIFICATION_TOPICS.global\n      })\n    }\n  );\n\n/**\n * Handles toggle map split and reset all map control index to 0\n * @memberof uiStateUpdaters\n * @param state\n * @returns nextState\n * @public\n */\nexport const toggleSplitMapUpdater = (state: UiState): UiState => ({\n  ...state,\n  mapControls: Object.entries(state.mapControls).reduce(\n    (acc, entry) => ({\n      ...acc,\n      [entry[0]]: {\n        ...entry[1],\n        activeMapIndex: 0\n      }\n    }),\n    {} as MapControls\n  )\n});\n\n/**\n * Toggle modal data\n * @memberof uiStateUpdaters\n * @param state\n * @returns nextState\n * @public\n */\nexport const showDatasetTableUpdater = (state: UiState): UiState =>\n  toggleModalUpdater(state, {payload: DATA_TABLE_ID});\n\n/**\n * Set the locale of the UI\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload\n * @param action.payload.locale locale\n * @returns nextState\n * @public\n */\nexport const setLocaleUpdater = (\n  state: UiState,\n  {payload: {locale}}: UIStateActions.SetLocaleUpdaterAction\n): UiState => ({\n  ...state,\n  locale\n});\n\n/**\n * Toggle layer panel list view\n * @memberof uiStateUpdaters\n * @param state `uiState`\n * @param action\n * @param action.payload layer panel listView value. Can be 'list' or 'sortByDataset'\n * @returns nextState\n * @public\n */\nexport const togglePanelListViewUpdater = (\n  state: UiState,\n  {payload: {panelId, listView}}: UIStateActions.TogglePanelListViewAction\n): UiState => {\n  const stateProp =\n    panelId === 'layer'\n      ? 'layerPanelListView'\n      : panelId === 'filter'\n      ? 'filterPanelListView'\n      : null;\n  if (!stateProp || state[stateProp] === listView) {\n    return state;\n  }\n  return {\n    ...state,\n    [stateProp]: listView\n  };\n};\n"
  },
  {
    "path": "src/reducers/src/ui-state.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\nimport {ActionTypes} from '@kepler.gl/actions';\nimport * as uiStateUpdaters from './ui-state-updaters';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.INIT]: uiStateUpdaters.initUiStateUpdater,\n  [ActionTypes.TOGGLE_SIDE_PANEL]: uiStateUpdaters.toggleSidePanelUpdater,\n  [ActionTypes.TOGGLE_MODAL]: uiStateUpdaters.toggleModalUpdater,\n  [ActionTypes.SHOW_EXPORT_DROPDOWN]: uiStateUpdaters.showExportDropdownUpdater,\n  [ActionTypes.HIDE_EXPORT_DROPDOWN]: uiStateUpdaters.hideExportDropdownUpdater,\n  [ActionTypes.TOGGLE_SIDE_PANEL_CLOSE_BUTTON]: uiStateUpdaters.toggleSidePanelCloseButtonUpdater,\n  [ActionTypes.OPEN_DELETE_MODAL]: uiStateUpdaters.openDeleteModalUpdater,\n  [ActionTypes.TOGGLE_MAP_CONTROL]: uiStateUpdaters.toggleMapControlUpdater,\n  [ActionTypes.SET_MAP_CONTROL_VISIBILITY]: uiStateUpdaters.setMapControlVisibilityUpdater,\n  [ActionTypes.SET_MAP_CONTROL_SETTINGS]: uiStateUpdaters.setMapControlSettingsUpdater,\n  [ActionTypes.ADD_NOTIFICATION]: uiStateUpdaters.addNotificationUpdater,\n  [ActionTypes.REMOVE_NOTIFICATION]: uiStateUpdaters.removeNotificationUpdater,\n\n  [ActionTypes.SET_EXPORT_IMAGE_SETTING]: uiStateUpdaters.setExportImageSettingUpdater,\n  [ActionTypes.SET_EXPORT_IMAGE_DATA_URI]: uiStateUpdaters.setExportImageDataUriUpdater,\n  [ActionTypes.SET_EXPORT_IMAGE_ERROR]: uiStateUpdaters.setExportImageErrorUpdater,\n  [ActionTypes.CLEANUP_EXPORT_IMAGE]: uiStateUpdaters.cleanupExportImageUpdater,\n  [ActionTypes.START_EXPORTING_IMAGE]: uiStateUpdaters.startExportingImageUpdater,\n\n  [ActionTypes.SET_EXPORT_SELECTED_DATASET]: uiStateUpdaters.setExportSelectedDatasetUpdater,\n  [ActionTypes.SET_EXPORT_DATA_TYPE]: uiStateUpdaters.setExportDataTypeUpdater,\n  [ActionTypes.SET_EXPORT_FILTERED]: uiStateUpdaters.setExportFilteredUpdater,\n  [ActionTypes.SET_EXPORT_DATA]: uiStateUpdaters.setExportDataUpdater,\n  [ActionTypes.SET_USER_MAPBOX_ACCESS_TOKEN]: uiStateUpdaters.setUserMapboxAccessTokenUpdater,\n\n  [ActionTypes.SET_EXPORT_MAP_FORMAT]: uiStateUpdaters.setExportMapFormatUpdater,\n\n  [ActionTypes.SET_EXPORT_MAP_HTML_MODE]: uiStateUpdaters.setExportMapHTMLModeUpdater,\n  [ActionTypes.LOAD_FILES]: uiStateUpdaters.loadFilesUpdater,\n  [ActionTypes.LOAD_FILES_ERR]: uiStateUpdaters.loadFilesErrUpdater,\n\n  [ActionTypes.TOGGLE_SPLIT_MAP]: uiStateUpdaters.toggleSplitMapUpdater,\n  [ActionTypes.SHOW_DATASET_TABLE]: uiStateUpdaters.showDatasetTableUpdater,\n  [ActionTypes.SET_LOCALE]: uiStateUpdaters.setLocaleUpdater,\n  [ActionTypes.TOGGLE_PANEL_LIST_VIEW]: uiStateUpdaters.togglePanelListViewUpdater\n};\n\n/* Reducer */\nexport const uiStateReducerFactory = (initialState = {}) =>\n  // @ts-expect-error\n  handleActions(actionHandler, {\n    ...uiStateUpdaters.INITIAL_UI_STATE,\n    ...initialState,\n    // @ts-ignore\n    initialState\n  });\n\nexport default uiStateReducerFactory();\n"
  },
  {
    "path": "src/reducers/src/vis-state-merger.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport uniq from 'lodash/uniq';\nimport pick from 'lodash/pick';\nimport flattenDeep from 'lodash/flattenDeep';\nimport deepmerge from 'deepmerge';\nimport {\n  arrayInsert,\n  getInitialMapLayersForSplitMap,\n  applyFiltersToDatasets,\n  validateFiltersUpdateDatasets,\n  findById,\n  aggregate\n} from '@kepler.gl/utils';\n\nimport {Layer} from '@kepler.gl/layers';\nimport {createEffect} from '@kepler.gl/effects';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {AGGREGATION_TYPES, LAYER_BLENDINGS, OVERLAY_BLENDINGS} from '@kepler.gl/constants';\nimport {CURRENT_VERSION, VisState, VisStateMergers, KeplerGLSchemaClass} from '@kepler.gl/schemas';\n\nimport {\n  ParsedLayer,\n  ParsedVisState,\n  SavedInteractionConfig,\n  TooltipInfo,\n  SavedEditor,\n  ParsedConfig,\n  Filter,\n  Effect as EffectType,\n  ParsedEffect,\n  LayerColumns,\n  LayerColumn,\n  ParsedFilter,\n  NestedPartial,\n  SavedAnimationConfig\n} from '@kepler.gl/types';\nimport {KeplerTable, Datasets, assignGpuChannels, resetFilterGpuMode} from '@kepler.gl/table';\n\nimport {getLayerOrderFromLayers} from './layer-utils';\n\n/**\n * Merge loaded filters with current state, if no fields or data are loaded\n * save it for later\n *\n */\nexport function mergeFilters<S extends VisState>(\n  state: S,\n  filtersToMerge: NonNullable<ParsedConfig['visState']>['filters'],\n  fromConfig?: boolean\n): S {\n  const preserveFilterOrder = fromConfig\n    ? filtersToMerge?.map(l => l.id)\n    : state.preserveFilterOrder;\n\n  if (!Array.isArray(filtersToMerge) || !filtersToMerge.length) {\n    return state;\n  }\n\n  const {validated, failed, updatedDatasets} = validateFiltersUpdateDatasets(state, filtersToMerge);\n  let updatedFilters = insertItemBasedOnPreservedOrder(\n    state.filters,\n    validated,\n    preserveFilterOrder\n  );\n\n  // merge filter with existing\n  updatedFilters = resetFilterGpuMode(updatedFilters);\n  updatedFilters = assignGpuChannels(updatedFilters);\n  // filter data\n  const datasetsToFilter = uniq(flattenDeep(validated.map(f => f.dataId)));\n\n  const filtered = applyFiltersToDatasets(\n    datasetsToFilter,\n    updatedDatasets,\n    updatedFilters,\n    state.layers\n  );\n\n  return {\n    ...state,\n    filters: updatedFilters,\n    datasets: filtered,\n    preserveFilterOrder,\n    filterToBeMerged: [...state.filterToBeMerged, ...failed]\n  };\n}\n\n// replace dataId in saved Filter\nexport function replaceFilterDatasetIds(\n  savedFilter: Filter[],\n  dataId: string,\n  dataIdToUse: string\n) {\n  const replaced: Filter[] = [];\n  savedFilter.forEach(filter => {\n    if (filter.dataId.includes(dataId)) {\n      const newDataId = filter.dataId.map(d => (d === dataId ? dataIdToUse : d));\n      let plotType;\n      // TODO: more generic approach to save plotType.colorsByDataId\n      if (filter.plotType?.colorsByDataId?.[dataId]) {\n        // replace colorByDataId in filter.plotType\n        const {[dataId]: color, ...rest} = filter.plotType?.colorsByDataId || {};\n        plotType = {\n          ...filter.plotType,\n          colorsByDataId: {\n            ...rest,\n            [dataIdToUse]: color\n          }\n        };\n      }\n      replaced.push({\n        ...filter,\n        dataId: newDataId,\n        ...(plotType ? {plotType} : {})\n      });\n    }\n  });\n  return replaced.length ? replaced : null;\n}\n\nexport function isSavedLayerConfigV1(layerConfig: any): boolean {\n  // exported layer configuration contains visualChannels property\n  return layerConfig?.visualChannels;\n}\n\nexport function parseLayerConfig(\n  schema: KeplerGLSchemaClass,\n  layerConfig: any\n): ParsedLayer | undefined {\n  // assume the layer config is current version\n  const savedConfig = {\n    version: CURRENT_VERSION,\n    config: {\n      visState: {layers: [layerConfig], layerOrder: [layerConfig.id]}\n    }\n  };\n\n  return schema.parseSavedConfig(savedConfig)?.visState?.layers?.[0];\n}\n\nfunction insertItemBasedOnPreservedOrder(\n  currentItems: Filter[],\n  items: Filter[],\n  preservedOrder: any[] = [],\n  defaultStart?: boolean\n) {\n  let newItems = [...currentItems];\n\n  for (const item of items) {\n    const expectedIdx = preservedOrder.indexOf(item.id);\n    // insertAt the end by default\n    let insertAt = defaultStart ? 0 : newItems.length;\n    if (expectedIdx > 0) {\n      // look for layer to insert after\n      let i = expectedIdx + 1;\n      let preceedIdx = -1;\n      while (i-- > 0 && preceedIdx < 0) {\n        // keep looking for preceed layer that is already loaded\n        const preceedItemId = preservedOrder[i - 1];\n        preceedIdx = newItems.findIndex(d => d.id === preceedItemId);\n      }\n      if (preceedIdx > -1) {\n        // if found\n        insertAt = preceedIdx + 1;\n      }\n    }\n    newItems = arrayInsert(newItems, insertAt, item);\n  }\n  return newItems;\n}\n\nexport function createLayerFromConfig(state: VisState, layerConfig: any): Layer | null {\n  // check if the layer config is parsed\n  const parsedLayerConfig = isSavedLayerConfigV1(layerConfig)\n    ? parseLayerConfig(state.schema, layerConfig)\n    : layerConfig;\n\n  if (!parsedLayerConfig) {\n    return null;\n  }\n  // first validate config against dataset\n  const {validated, failed} = validateLayersByDatasets(\n    state.datasets,\n    state.layerClasses,\n    [parsedLayerConfig],\n    {allowEmptyColumn: true}\n  );\n\n  if (failed?.length || !validated.length) {\n    // failed\n    return null;\n  }\n\n  const newLayer = validated[0];\n  newLayer.updateLayerDomain(state.datasets);\n  return newLayer;\n}\n\n/**\n * Get loaded filter from state\n */\nexport function serializeFilter(\n  newFilter: Filter,\n  schema: KeplerGLSchemaClass\n): ParsedFilter | undefined {\n  const serializedVisState = serializeVisState({filters: [newFilter]}, schema);\n  return serializedVisState?.filters?.[0];\n}\n\n/**\n * Get loaded layer from state\n */\nexport function serializeLayer(\n  newLayer: Layer,\n  schema: KeplerGLSchemaClass\n): ParsedLayer | undefined {\n  const serializedVisState = serializeVisState(\n    {layers: [newLayer], layerOrder: [newLayer.id]},\n    schema\n  );\n  return serializedVisState?.layers?.[0];\n}\n\n/**\n * Get loaded effect from state\n */\nexport function serializeEffect(\n  newEffect: EffectType,\n  schema: KeplerGLSchemaClass\n): ParsedEffect | undefined {\n  const serializedVisState = serializeVisState(\n    {effects: [newEffect], effectOrder: [newEffect.id]},\n    schema\n  );\n  return serializedVisState?.effects?.[0];\n}\n\n/**\n * Get vis state config\n */\nexport function serializeVisState(\n  visState: Partial<VisState>,\n  schema: KeplerGLSchemaClass\n): ParsedVisState | undefined {\n  const savedState = schema.getConfigToSave({\n    visState\n  });\n  return savedState ? schema.parseSavedConfig(savedState)?.visState : undefined;\n}\n/**\n * Merge layers from de-serialized state, if no fields or data are loaded\n * save it for later\n *\n */\nexport function mergeLayers<S extends VisState>(\n  state: S,\n  layersToMerge: NonNullable<ParsedConfig['visState']>['layers'] = [],\n  fromConfig?: boolean\n): S {\n  const preserveLayerOrder = fromConfig\n    ? getLayerOrderFromLayers(layersToMerge)\n    : state.preserveLayerOrder;\n  if (!Array.isArray(layersToMerge) || !layersToMerge.length) {\n    return state;\n  }\n  // don't merge layer if dataset is being merged\n  const unmerged: ParsedLayer[] = [];\n  const toMerge: ParsedLayer[] = [];\n  layersToMerge.forEach((l: ParsedLayer) => {\n    if (l?.config?.dataId && state.isMergingDatasets[l.config.dataId]) {\n      unmerged.push(l);\n    } else {\n      toMerge.push(l);\n    }\n  });\n\n  const {validated: mergedLayer, failed} = validateLayersByDatasets(\n    state.datasets,\n    state.layerClasses,\n    toMerge\n  );\n  unmerged.push(...failed);\n  // put new layers in front of current layers\n  const {newLayerOrder, newLayers} = insertLayerAtRightOrder(\n    state.layers,\n    mergedLayer,\n    state.layerOrder,\n    preserveLayerOrder\n  );\n\n  return {\n    ...state,\n    layers: newLayers,\n    layerOrder: newLayerOrder,\n    preserveLayerOrder,\n    layerToBeMerged: [...state.layerToBeMerged, ...unmerged]\n  };\n}\n\nexport function insertLayerAtRightOrder(\n  currentLayers,\n  layersToInsert,\n  currentOrder,\n  preservedOrder: string[] = []\n) {\n  if (!layersToInsert?.length) {\n    return {newLayers: currentLayers, newLayerOrder: currentOrder};\n  }\n  // perservedOrder ['a', 'b', 'c'];\n  // layerOrder ['a', 'b', 'c']\n  const currentLayerQueue = currentOrder\n    .map(id => findById(id)(currentLayers))\n    .filter(layer => Boolean(layer));\n  const newLayers = currentLayers.concat(layersToInsert);\n  const newLayerOrderQueue = insertItemBasedOnPreservedOrder(\n    currentLayerQueue,\n    layersToInsert,\n    preservedOrder,\n    true\n  );\n\n  // reconstruct layerOrder after insert\n  const newLayerOrder = getLayerOrderFromLayers(newLayerOrderQueue);\n\n  return {\n    newLayerOrder,\n    newLayers\n  };\n}\n\n/**\n * Merge interactions with saved config\n *\n */\nexport function mergeInteractions<S extends VisState>(\n  state: S,\n  interactionToBeMerged: Partial<SavedInteractionConfig> | undefined\n): S {\n  const merged: NestedPartial<SavedInteractionConfig> = {};\n  const unmerged: Partial<SavedInteractionConfig> = {};\n\n  if (interactionToBeMerged) {\n    (Object.keys(interactionToBeMerged) as Array<keyof SavedInteractionConfig>).forEach(key => {\n      if (!state.interactionConfig[key]) {\n        return;\n      }\n\n      const currentConfig =\n        key === 'tooltip' || key === 'brush' ? state.interactionConfig[key].config : null;\n\n      const {enabled, ...configSaved} = interactionToBeMerged[key] || {};\n\n      let configToMerge = configSaved;\n\n      if (key === 'tooltip') {\n        const {mergedTooltip, unmergedTooltip} = mergeInteractionTooltipConfig(\n          state,\n          configSaved as SavedInteractionConfig['tooltip']\n        );\n\n        // merge new dataset tooltips with original dataset tooltips\n        configToMerge = {\n          fieldsToShow: {\n            ...(currentConfig as TooltipInfo['config']).fieldsToShow,\n            ...mergedTooltip\n          }\n        };\n\n        if (Object.keys(unmergedTooltip).length) {\n          // @ts-expect-error\n          unmerged.tooltip = {fieldsToShow: unmergedTooltip, enabled: Boolean(enabled)};\n        }\n      }\n\n      merged[key] = {\n        ...state.interactionConfig[key],\n        enabled: Boolean(enabled),\n        ...(currentConfig\n          ? {\n              config: pick(\n                {\n                  ...currentConfig,\n                  ...configToMerge\n                },\n                Object.keys(currentConfig)\n              )\n            }\n          : {})\n      };\n    });\n  }\n\n  const nextState = {\n    ...state,\n    interactionConfig: {\n      ...state.interactionConfig,\n      ...merged\n    },\n    interactionToBeMerged: savedUnmergedInteraction(state, unmerged)\n  };\n  return nextState;\n}\n\nfunction combineInteractionConfigs(configs: SavedInteractionConfig[]): SavedInteractionConfig {\n  const combined = {...configs[0]};\n  // handle each property key of an `InteractionConfig`, e.g. tooltip, geocoder, brush, coordinate\n  // by combining values for each among all passed in configs\n\n  for (const key in combined) {\n    const toBeCombinedProps = configs.map(c => c[key]);\n\n    // all of these have an enabled boolean\n    combined[key] = {\n      // are any of the configs' enabled values true?\n      enabled: toBeCombinedProps.some(p => p?.enabled)\n    };\n\n    if (key === 'tooltip') {\n      // are any of the configs' compareMode values true?\n      combined[key].compareMode = toBeCombinedProps.some(p => p?.compareMode);\n\n      // return the compare type mode, it will be either absolute or relative\n      combined[key].compareType = getValueWithHighestOccurrence(\n        toBeCombinedProps.map(p => p.compareType)\n      );\n\n      // combine fieldsToShow among all dataset ids\n      combined[key].fieldsToShow = toBeCombinedProps\n        .map(p => p.fieldsToShow)\n        .reduce((acc, nextFieldsToShow) => {\n          for (const nextDataIdKey in nextFieldsToShow) {\n            const nextTooltipFields = nextFieldsToShow[nextDataIdKey];\n            if (!acc[nextDataIdKey]) {\n              // if the dataset id is not present in the accumulator\n              // then add it with its tooltip fields\n              acc[nextDataIdKey] = nextTooltipFields;\n            } else {\n              // otherwise the dataset id is already present in the accumulator\n              // so only add the next tooltip fields for this dataset's array if they are not already present,\n              // using the tooltipField.name property for uniqueness\n              nextTooltipFields.forEach(nextTF => {\n                if (!acc[nextDataIdKey].find(({name}) => nextTF.name === name)) {\n                  acc[nextDataIdKey].push(nextTF);\n                }\n              });\n            }\n          }\n          return acc;\n        }, {});\n    }\n\n    if (key === 'brush') {\n      // keep the biggest brush size\n      combined[key].size =\n        aggregate(toBeCombinedProps, AGGREGATION_TYPES.maximum, p => p.size) ?? null;\n    }\n  }\n\n  return combined;\n}\n\nfunction savedUnmergedInteraction<S extends VisState>(\n  state: S,\n  unmerged: Partial<SavedInteractionConfig>\n) {\n  if (!unmerged?.tooltip?.fieldsToShow) {\n    return state.interactionToBeMerged;\n  }\n  return {\n    tooltip: {\n      ...state.interactionToBeMerged.tooltip,\n      ...(typeof unmerged?.tooltip?.enabled === 'boolean'\n        ? {enabled: unmerged.tooltip.enabled}\n        : {}),\n      fieldsToShow: {\n        ...state.interactionToBeMerged?.tooltip?.fieldsToShow,\n        ...unmerged?.tooltip?.fieldsToShow\n      }\n    }\n  };\n}\n\nfunction replaceInteractionDatasetIds(interactionConfig, dataId: string, dataIdToReplace: string) {\n  if (interactionConfig?.tooltip?.fieldsToShow[dataId]) {\n    return {\n      ...interactionConfig,\n      tooltip: {\n        ...interactionConfig.tooltip,\n        fieldsToShow: {\n          [dataIdToReplace]: interactionConfig?.tooltip?.fieldsToShow[dataId]\n        }\n      }\n    };\n  }\n  return null;\n}\n\n/**\n * Merge splitMaps config with current visStete.\n * 1. if current map is split, but splitMap DOESNOT contain maps\n *    : don't merge anything\n * 2. if current map is NOT split, but splitMaps contain maps\n *    : add to splitMaps, and add current layers to splitMaps\n */\nexport function mergeSplitMaps<S extends VisState>(\n  state: S,\n  splitMaps: NonNullable<ParsedConfig['visState']>['splitMaps'] = []\n): S {\n  const merged = [...state.splitMaps];\n  const unmerged = [];\n  splitMaps.forEach((sm, i) => {\n    const entries = Object.entries(sm.layers);\n    if (entries.length > 0) {\n      entries.forEach(([id, value]) => {\n        // check if layer exists\n        const pushTo = state.layers.find(l => l.id === id) ? merged : unmerged;\n\n        // create map panel if current map is not split\n        pushTo[i] = pushTo[i] || {\n          // keep id\n          ...sm,\n          layers: pushTo === merged ? getInitialMapLayersForSplitMap(state.layers) : []\n        };\n        pushTo[i].layers = {\n          ...pushTo[i].layers,\n          [id]: value\n        };\n      });\n    } else {\n      // We are merging if there are no layers in both split map\n      merged.push(sm);\n    }\n  });\n\n  return {\n    ...state,\n    splitMaps: merged,\n    splitMapsToBeMerged: [...state.splitMapsToBeMerged, ...unmerged]\n  };\n}\n\n/**\n * Merge effects with saved config\n */\nexport function mergeEffects<S extends VisState>(\n  state: S,\n  effects: NonNullable<ParsedConfig['visState']>['effects'],\n  fromConfig?: boolean\n): S {\n  const newEffects = [\n    ...state.effects,\n    ...(effects || [])\n      .map(effect => {\n        return fromConfig\n          ? createEffect(\n              deepmerge.all([\n                effect,\n                {\n                  // collapse all panels when loading effects\n                  isConfigActive: false\n                }\n              ])\n            )\n          : (effect as EffectType);\n      })\n      .filter(effect => {\n        return Boolean(effect && effect.isValidToSave());\n      })\n  ];\n  return {\n    ...state,\n    effects: newEffects,\n    effectOrder: newEffects.map(effect => effect.id)\n  };\n}\n\n/**\n * Merge interactionConfig.tooltip with saved config,\n * validate fieldsToShow\n *\n * @param state\n * @param tooltipConfig\n * @return - {mergedTooltip: {}, unmergedTooltip: {}}\n */\nexport function mergeInteractionTooltipConfig(\n  state: VisState,\n  tooltipConfig: Pick<TooltipInfo['config'], 'fieldsToShow'> | null = null\n) {\n  const unmergedTooltip: TooltipInfo['config']['fieldsToShow'] = {};\n  const mergedTooltip: TooltipInfo['config']['fieldsToShow'] = {};\n\n  if (\n    !tooltipConfig ||\n    !tooltipConfig.fieldsToShow ||\n    !Object.keys(tooltipConfig.fieldsToShow).length\n  ) {\n    return {mergedTooltip, unmergedTooltip};\n  }\n\n  for (const dataId in tooltipConfig.fieldsToShow) {\n    if (!state.datasets[dataId] || state.isMergingDatasets[dataId]) {\n      // is not yet loaded\n      unmergedTooltip[dataId] = tooltipConfig.fieldsToShow[dataId];\n    } else {\n      // if dataset is loaded\n      const allFields = state.datasets[dataId].fields.map(d => d.name);\n      const foundFieldsToShow = tooltipConfig.fieldsToShow[dataId].filter(field =>\n        allFields.includes(field.name)\n      );\n\n      mergedTooltip[dataId] = foundFieldsToShow;\n    }\n  }\n\n  return {mergedTooltip, unmergedTooltip};\n}\n/**\n * Merge layerBlending with saved\n *\n */\nexport function mergeLayerBlending<S extends VisState>(\n  state: S,\n  layerBlending: NonNullable<ParsedConfig['visState']>['layerBlending']\n): S {\n  if (layerBlending && LAYER_BLENDINGS[layerBlending]) {\n    return {\n      ...state,\n      layerBlending\n    };\n  }\n\n  return state;\n}\n\n/**\n * Combines multiple layer blending configs into a single string\n * by returning the one with the highest occurrence\n */\nfunction combineLayerBlendingConfigs(configs: string[]): string | null {\n  // return the mode of the layer blending type\n  return getValueWithHighestOccurrence(configs);\n}\n\n/**\n * Merge overlayBlending with saved\n */\nexport function mergeOverlayBlending<S extends VisState>(\n  state: S,\n  overlayBlending: NonNullable<ParsedConfig['visState']>['overlayBlending']\n): S {\n  if (overlayBlending && OVERLAY_BLENDINGS[overlayBlending]) {\n    return {\n      ...state,\n      overlayBlending\n    };\n  }\n\n  return state;\n}\n\n/**\n * Combines multiple overlay blending configs into a single string\n * by returning the one with the highest occurrence\n **/\nfunction combineOverlayBlendingConfigs(configs: string[]): string | null {\n  // return the mode of the overlay blending type\n  return getValueWithHighestOccurrence(configs);\n}\n\n/**\n * Merge animation config\n */\nexport function mergeAnimationConfig<S extends VisState>(\n  state: S,\n  animation: NonNullable<ParsedConfig['visState']>['animationConfig']\n): S {\n  if (animation && animation.currentTime) {\n    return {\n      ...state,\n      animationConfig: {\n        ...state.animationConfig,\n        ...animation,\n        domain: null\n      }\n    };\n  }\n\n  return state;\n}\n\nfunction combineAnimationConfigs(configs: SavedAnimationConfig[]): SavedAnimationConfig {\n  // get the smallest values of currentTime and speed among all configs\n  return {\n    currentTime: aggregate(configs, AGGREGATION_TYPES.minimum, c => c.currentTime) ?? null,\n    speed: aggregate(configs, AGGREGATION_TYPES.minimum, c => c.speed) ?? null\n  };\n}\n\n/**\n * Validate saved layer columns with new data,\n * update fieldIdx based on new fields\n *\n * @param fields\n * @param savedCols\n * @param emptyCols\n * @param options\n * @return - validated columns or null\n */\n\nexport function validateSavedLayerColumns(\n  fields: KeplerTable['fields'],\n  savedCols: {\n    [key: string]: string;\n  } = {},\n  emptyCols: LayerColumns,\n  options: {throwOnError?: boolean} = {}\n) {\n  // Prepare columns for the validator\n  const columns: typeof emptyCols = {};\n  for (const key of Object.keys(emptyCols)) {\n    columns[key] = {...emptyCols[key]};\n\n    const saved = savedCols[key];\n    if (saved) {\n      const fieldIdx = fields.findIndex(({name}) => name === saved);\n\n      if (fieldIdx > -1) {\n        // update found columns\n        columns[key].fieldIdx = fieldIdx;\n        columns[key].value = saved;\n      }\n    }\n  }\n\n  // find actual column fieldIdx, in case it has changed\n  const allColFound = Object.keys(columns).every(key =>\n    validateColumn(columns[key], columns, fields)\n  );\n\n  const rv = allColFound ? columns : null;\n  if (options.throwOnError) {\n    const requiredColumns = Object.keys(emptyCols).filter(k => !emptyCols[k].optional);\n    const missingColumns = requiredColumns.filter(k => !columns?.[k].value);\n    if (missingColumns.length) {\n      throw new Error(`Layer has missing or invalid columns: ${missingColumns.join(', ')}`);\n    }\n    const configColumns = Object.keys(savedCols);\n    const invalidColumns = configColumns.filter(k => !columns?.[k]?.value);\n    if (invalidColumns.length) {\n      throw new Error(`Layer has invalid columns: ${invalidColumns.join(', ')}`);\n    }\n  }\n\n  return rv;\n}\n\n/**\n * Validate layer column\n */\nexport function validateColumn(\n  column: LayerColumn & {validator?: typeof validateColumn},\n  columns: LayerColumns,\n  allFields: KeplerTable['fields']\n): boolean {\n  if (column.optional || column.value) {\n    return true;\n  }\n  if (column.validator) {\n    return column.validator(column, columns, allFields);\n  }\n  return false;\n}\n\n/**\n * Validate saved text label config with new data\n * refer to vis-state-schema.js TextLabelSchemaV1\n *\n * @param {Array<Object>} fields\n * @param {Object} savedTextLabel\n * @param {Object} options\n * @return {Object} - validated textlabel\n */\nexport function validateSavedTextLabel(\n  fields,\n  [layerTextLabel],\n  savedTextLabel,\n  options: {throwOnError?: boolean} = {}\n) {\n  const savedTextLabels = Array.isArray(savedTextLabel) ? savedTextLabel : [savedTextLabel];\n\n  // validate field\n  return savedTextLabels.map(textLabel => {\n    const field = textLabel.field\n      ? fields.find(fd =>\n          Object.keys(textLabel.field).every(key => textLabel.field[key] === fd[key])\n        )\n      : null;\n\n    if (field === undefined && options.throwOnError) {\n      throw new Error(`Layer has invalid text label field: ${JSON.stringify(textLabel.field)}`);\n    }\n\n    return Object.keys(layerTextLabel).reduce(\n      (accu, key) => ({\n        ...accu,\n        [key]: key === 'field' ? field : textLabel[key] || layerTextLabel[key]\n      }),\n      {}\n    );\n  });\n}\n\n/**\n * Validate saved visual channels config with new data,\n * refer to vis-state-schema.js VisualChannelSchemaV1\n */\nexport function validateSavedVisualChannels(\n  fields: KeplerTable['fields'],\n  newLayer: Layer,\n  savedLayer: ParsedLayer,\n  options: {throwOnError?: boolean} = {}\n): null | Layer {\n  Object.values(newLayer.visualChannels).forEach(({field, scale, key}) => {\n    let foundField;\n    if (savedLayer.config) {\n      if (savedLayer.config[field]) {\n        foundField = fields.find(\n          fd => savedLayer.config && fd.name === savedLayer.config[field].name\n        );\n      }\n\n      const foundChannel = {\n        ...(foundField ? {[field]: foundField} : {}),\n        ...(savedLayer.config[scale] ? {[scale]: savedLayer.config[scale]} : {})\n      };\n      if (Object.keys(foundChannel).length) {\n        newLayer.updateLayerConfig(foundChannel);\n      }\n\n      newLayer.validateVisualChannel(key);\n      if (options.throwOnError) {\n        const fieldName = savedLayer.config?.[field]?.name;\n        if (fieldName && fieldName !== newLayer.config[field]?.name) {\n          throw new Error(`Layer has invalid visual channel field: ${field}`);\n        }\n      }\n    }\n  });\n  return newLayer;\n}\n\ntype ValidateLayerOption = {\n  allowEmptyColumn?: boolean;\n  throwOnError?: boolean;\n};\n\nexport function validateLayersByDatasets(\n  datasets: Datasets,\n  layerClasses: VisState['layerClasses'],\n  layers: NonNullable<ParsedConfig['visState']>['layers'] = [],\n  options?: ValidateLayerOption\n) {\n  const validated: Layer[] = [];\n  const failed: NonNullable<ParsedConfig['visState']>['layers'] = [];\n\n  layers.forEach(layer => {\n    let validateLayer: Layer | null = null;\n\n    if (layer?.config?.dataId) {\n      if (datasets[layer.config.dataId]) {\n        // datasets are already loaded\n        validateLayer = validateLayerWithData(\n          datasets[layer.config.dataId],\n          layer,\n          layerClasses,\n          options\n        );\n      }\n    }\n\n    if (validateLayer) {\n      validated.push(validateLayer);\n    } else {\n      // datasets not yet loaded\n      failed.push(layer);\n    }\n  });\n\n  return {validated, failed};\n}\n\n/**\n * Get required columns for validation based on column mode\n */\nfunction _getColumnConfigForValidation(newLayer) {\n  // find column fieldIdx\n  let columnConfig = newLayer.getLayerColumns();\n  // if columnMode is defined, find column mode config\n  const colModeConfig = newLayer.config.columnMode\n    ? (newLayer.supportedColumnModes || []).find(\n        colMode => colMode.key === newLayer.config.columnMode\n      )\n    : null;\n\n  if (colModeConfig) {\n    // only validate columns in column mode\n    columnConfig = [\n      ...(colModeConfig.requiredColumns || []),\n      ...(colModeConfig.optionalColumns || [])\n    ].reduce(\n      (accu, key) => ({\n        ...accu,\n        [key]: columnConfig[key]\n      }),\n      {}\n    );\n  }\n\n  return columnConfig;\n}\n\n/**\n * Validate saved layer config with new data,\n * update fieldIdx based on new fields\n */\n// eslint-disable-next-line complexity\nexport function validateLayerWithData(\n  dataset: KeplerTable,\n  savedLayer: ParsedLayer,\n  layerClasses: VisState['layerClasses'],\n  options: ValidateLayerOption = {}\n): Layer | null {\n  const {fields, id: dataId} = dataset;\n  const {type} = savedLayer;\n  const {throwOnError} = options;\n  // layer doesnt have a valid type\n  if (!type || !Object.prototype.hasOwnProperty.call(layerClasses, type) || !savedLayer.config) {\n    if (throwOnError) {\n      throw new Error(`Layer has invalid type \"${type}\" or config is missing`);\n    }\n    return null;\n  }\n\n  let newLayer = new layerClasses[type]({\n    id: savedLayer.id,\n    dataId,\n    label: savedLayer.config.label,\n    color: savedLayer.config.color,\n    isVisible: savedLayer.config.isVisible,\n    hidden: savedLayer.config.hidden,\n    columnMode: savedLayer.config.columnMode,\n    highlightColor: savedLayer.config.highlightColor\n  });\n\n  const columnConfig = _getColumnConfigForValidation(newLayer);\n\n  if (Object.keys(columnConfig)) {\n    const columns = validateSavedLayerColumns(\n      fields,\n      savedLayer.config.columns,\n      columnConfig,\n      options\n    );\n    if (columns) {\n      newLayer.updateLayerConfig({\n        columns: {\n          ...newLayer.config.columns,\n          ...columns\n        }\n      });\n    } else if (!options.allowEmptyColumn) {\n      return null;\n    }\n  }\n\n  const textLabel =\n    savedLayer.config.textLabel && newLayer.config.textLabel\n      ? validateSavedTextLabel(\n          fields,\n          newLayer.config.textLabel,\n          savedLayer.config.textLabel,\n          options\n        )\n      : newLayer.config.textLabel;\n\n  // copy visConfig over to emptyLayer to make sure it has all the props\n  const copiedVisConfig = newLayer.copyLayerConfig(\n    newLayer.config.visConfig,\n    savedLayer.config.visConfig || {},\n    {\n      shallowCopy: ['colorRange', 'strokeColorRange']\n    }\n  );\n\n  // call layer methods to validate visConfig when switching dataset\n  const visConfig = newLayer.validateVisConfig\n    ? newLayer.validateVisConfig(dataset, copiedVisConfig)\n    : copiedVisConfig;\n\n  newLayer.updateLayerConfig({\n    visConfig,\n    textLabel\n  });\n\n  // visual channel field is saved to be {name, type}\n  // find visual channel field by matching both name and type\n  // refer to vis-state-schema.js VisualChannelSchemaV1\n  newLayer = validateSavedVisualChannels(fields, newLayer, savedLayer, options);\n\n  if (throwOnError) {\n    if (!newLayer.isValidToSave()) {\n      throw new Error(`Layer is not valid to save: ${newLayer.id}`);\n    }\n  }\n\n  return newLayer;\n}\n\nexport function mergeEditor<S extends VisState>(state: S, savedEditor: SavedEditor) {\n  if (!savedEditor) {\n    return state;\n  }\n  return {\n    ...state,\n    editor: {\n      ...state.editor,\n      features: [...state.editor.features, ...(savedEditor.features || [])],\n      // if savedEditor.visible is undefined keep state.editor.visible\n      visible: savedEditor.visible ?? state.editor.visible\n    }\n  };\n}\n\nfunction combineEditorConfigs(configs: SavedEditor[]): SavedEditor {\n  return configs.reduce(\n    (acc, nextConfig) => {\n      return {\n        ...acc,\n        features: [...acc.features, ...(nextConfig.features || [])]\n      };\n    },\n    {\n      // start with:\n      // - empty array for features accumulation\n      // - and are any of the configs' visible values true?\n      features: [],\n      visible: configs.some(c => c?.visible)\n    }\n  );\n}\n\n/**\n * Validate saved layer config with new data,\n * update fieldIdx based on new fields\n */\nexport function mergeDatasetsByOrder(state: VisState, newDataEntries: Datasets): Datasets {\n  const merged = {\n    ...state.datasets,\n    ...newDataEntries\n  };\n\n  if (Array.isArray(state.preserveDatasetOrder)) {\n    // preserveDatasetOrder  might not include the  new datasets\n    const newDatasetIds = Object.keys(merged).filter(\n      id => !state.preserveDatasetOrder?.includes(id)\n    );\n    return [...state.preserveDatasetOrder, ...newDatasetIds].reduce(\n      (accu, dataId) => ({\n        ...accu,\n        ...(merged[dataId] ? {[dataId]: merged[dataId]} : {})\n      }),\n      {}\n    );\n  }\n\n  return merged;\n}\n\n/**\n * Simliar purpose to aggregation utils `getMode` function,\n * but returns the mode in the same value type without coercing to a string.\n * It ignores `undefined` or `null` values, but returns `null` if no mode could be calculated.\n */\nfunction getValueWithHighestOccurrence<T>(arr: T[]): T | null {\n  const tallys = new Map();\n  arr.forEach(value => {\n    if (notNullorUndefined(value)) {\n      if (!tallys.has(value)) {\n        tallys.set(value, 1);\n      } else {\n        tallys.set(value, tallys.get(value) + 1);\n      }\n    }\n  });\n  // return the value with the highest total occurrence count\n  if (tallys.size === 0) {\n    return null;\n  }\n  return [...tallys.entries()]?.reduce((acc, next) => (next[1] > acc[1] ? next : acc))[0];\n}\n\nexport const VIS_STATE_MERGERS: VisStateMergers<any> = [\n  {\n    merge: mergeLayers,\n    prop: 'layers',\n    toMergeProp: 'layerToBeMerged',\n    preserveOrder: 'preserveLayerOrder'\n  },\n  {\n    merge: mergeFilters,\n    prop: 'filters',\n    toMergeProp: 'filterToBeMerged',\n    preserveOrder: 'preserveFilterOrder',\n    replaceParentDatasetIds: replaceFilterDatasetIds\n  },\n  {\n    merge: mergeEffects,\n    prop: 'effects'\n  },\n  {\n    merge: mergeInteractions,\n    prop: 'interactionConfig',\n    toMergeProp: 'interactionToBeMerged',\n    replaceParentDatasetIds: replaceInteractionDatasetIds,\n    saveUnmerged: savedUnmergedInteraction,\n    combineConfigs: combineInteractionConfigs\n  },\n  {merge: mergeLayerBlending, prop: 'layerBlending', combineConfigs: combineLayerBlendingConfigs},\n  {\n    merge: mergeOverlayBlending,\n    prop: 'overlayBlending',\n    combineConfigs: combineOverlayBlendingConfigs\n  },\n  {merge: mergeSplitMaps, prop: 'splitMaps', toMergeProp: 'splitMapsToBeMerged'},\n  {merge: mergeAnimationConfig, prop: 'animationConfig', combineConfigs: combineAnimationConfigs},\n  {merge: mergeEditor, prop: 'editor', combineConfigs: combineEditorConfigs}\n];\n"
  },
  {
    "path": "src/reducers/src/vis-state-selectors.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createSelector} from 'reselect';\n\n// NOTE: default formats must match file-handler-test.js\nconst DEFAULT_FILE_EXTENSIONS = ['csv', 'json', 'geojson', 'arrow', 'parquet'];\nconst DEFAULT_FILE_FORMATS = ['CSV', 'Json', 'GeoJSON', 'Arrow', 'Parquet'];\n\nexport const getFileFormatNames = createSelector(\n  state => state.loaders,\n  loaders => [...DEFAULT_FILE_FORMATS, ...loaders.map(loader => loader.name)]\n);\n\nexport const getFileExtensions = createSelector(\n  state => state.loaders,\n  loaders => [...DEFAULT_FILE_EXTENSIONS, ...loaders.flatMap(loader => loader.extensions)]\n);\n"
  },
  {
    "path": "src/reducers/src/vis-state-updaters.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport bbox from '@turf/bbox';\nimport copy from 'copy-to-clipboard';\nimport deepmerge from 'deepmerge';\nimport {console as Console} from 'global/window';\nimport cloneDeep from 'lodash/cloneDeep';\nimport get from 'lodash/get';\nimport isEqual from 'lodash/isEqual';\nimport pick from 'lodash/pick';\nimport uniq from 'lodash/uniq';\nimport xor from 'lodash/xor';\nimport Task, {disableStackCapturing, withTask} from 'react-palm/tasks';\n// Tasks\nimport {\n  DELAY_TASK,\n  ACTION_TASK,\n  LOAD_FILE_TASK,\n  PROCESS_FILE_DATA,\n  UNWRAP_TASK\n} from '@kepler.gl/tasks';\n// Actions\nimport {\n  addNotification,\n  ActionTypes,\n  CreateNewDatasetSuccessPayload,\n  MapStateActions,\n  ReceiveMapConfigPayload,\n  VisStateActions,\n  applyLayerConfig,\n  createNewDatasetSuccess,\n  layerConfigChange,\n  layerTypeChange,\n  layerVisConfigChange,\n  layerVisualChannelConfigChange,\n  loadFileStepSuccess,\n  loadFilesErr,\n  loadFilesSuccess,\n  loadNextFile,\n  nextFileBatch,\n  setFilter,\n  processFileContent,\n  fitBounds as fitMapBounds,\n  toggleLayerForMap,\n  applyFilterConfig,\n  SetLoadingIndicatorPayload\n} from '@kepler.gl/actions';\n\n// Utils\nimport {\n  FILTER_UPDATER_PROPS,\n  addNewLayersToSplitMap,\n  snapToMarks,\n  applyFilterFieldName,\n  applyFiltersToDatasets,\n  arrayInsert,\n  computeSplitMapLayers,\n  adjustValueToFilterDomain,\n  errorNotification,\n  featureToFilterValue,\n  filterDatasetCPU,\n  generatePolygonFilter,\n  getDefaultFilter,\n  getFilterIdInFeature,\n  getTimeWidgetTitleFormatter,\n  isInRange,\n  isObject,\n  isPlainObject,\n  isRgbColor,\n  parseFieldValue,\n  removeLayerFromSplitMaps,\n  set,\n  updateFilterPlot,\n  removeFilterPlot,\n  isLayerAnimatable,\n  isSideFilter,\n  getApplicationConfig\n} from '@kepler.gl/utils';\nimport {generateHashId, toArray} from '@kepler.gl/common-utils';\n// Mergers\nimport {\n  ANIMATION_WINDOW,\n  BASE_SPEED,\n  COMPARE_TYPES,\n  DEFAULT_TEXT_LABEL,\n  EDITOR_MODES,\n  FILTER_TYPES,\n  FILTER_VIEW_TYPES,\n  FPS,\n  LIGHT_AND_SHADOW_EFFECT,\n  MAX_DEFAULT_TOOLTIPS,\n  PLOT_TYPES,\n  SORT_ORDER,\n  SYNC_TIMELINE_MODES,\n  CHANNEL_SCALES,\n  SCALE_TYPES\n} from '@kepler.gl/constants';\nimport {LAYER_ID_LENGTH, Layer, LayerClasses} from '@kepler.gl/layers';\nimport {\n  apply_,\n  compose_,\n  filterOutById,\n  merge_,\n  payload_,\n  pick_,\n  removeElementAtIndex,\n  swap_\n} from './composer-helpers';\nimport {isValidMerger, mergeStateFromMergers} from './merger-handler';\nimport {\n  VIS_STATE_MERGERS,\n  createLayerFromConfig,\n  parseLayerConfig,\n  serializeFilter,\n  serializeLayer,\n  serializeVisState,\n  validateLayerWithData\n} from './vis-state-merger';\n\nimport KeplerGLSchema, {Merger, PostMergerPayload, VisState} from '@kepler.gl/schemas';\n\nimport {\n  Filter,\n  InteractionConfig,\n  AnimationConfig,\n  FilterAnimationConfig,\n  Editor,\n  Field,\n  TimeRangeFilter\n} from '@kepler.gl/types';\nimport {Loader} from '@loaders.gl/loader-utils';\n\nimport {\n  Datasets,\n  assignGpuChannel,\n  copyTableAndUpdate,\n  createNewDataEntry,\n  pinTableColumns,\n  setFilterGpuMode,\n  sortDatasetByColumn\n} from '@kepler.gl/table';\nimport {findFieldsToShow} from './interaction-utils';\nimport {calculateLayerData, findDefaultLayer, getLayerOrderFromLayers} from './layer-utils';\nimport {getPropValueToMerger, hasPropsToMerge} from './merger-handler';\nimport {mergeDatasetsByOrder} from './vis-state-merger';\nimport {\n  fixEffectOrder,\n  getAnimatableVisibleLayers,\n  getIntervalBasedAnimationLayers,\n  mergeTimeDomains,\n  adjustValueToAnimationWindow,\n  updateTimeFilterPlotType,\n  getDefaultTimeFormat,\n  LayerToFilterTimeInterval,\n  TIME_INTERVALS_ORDERED,\n  mergeFilterDomain,\n  initCustomPaletteByCustomScale\n} from '@kepler.gl/utils';\nimport {createEffect} from '@kepler.gl/effects';\nimport {PayloadAction} from '@reduxjs/toolkit';\n\nimport {findMapBounds} from './data-utils';\n\n// react-palm\n// disable capture exception for react-palm call to withTask\ndisableStackCapturing();\n\n/**\n * Updaters for `visState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.\n * Read more about [Using updaters](../advanced-usage/using-updaters.md)\n *\n * @public\n * @example\n *\n * import keplerGlReducer, {visStateUpdaters} from '@kepler.gl/reducers';\n * // Root Reducer\n * const reducers = combineReducers({\n *  keplerGl: keplerGlReducer,\n *  app: appReducer\n * });\n *\n * const composedReducer = (state, action) => {\n *  switch (action.type) {\n *    case 'CLICK_BUTTON':\n *      return {\n *        ...state,\n *        keplerGl: {\n *          ...state.keplerGl,\n *          foo: {\n *             ...state.keplerGl.foo,\n *             visState: visStateUpdaters.enlargeFilterUpdater(\n *               state.keplerGl.foo.visState,\n *               {idx: 0}\n *             )\n *          }\n *        }\n *      };\n *  }\n *  return reducers(state, action);\n * };\n *\n * export default composedReducer;\n */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n// @ts-ignore\nconst visStateUpdaters = null;\n/* eslint-enable @typescript-eslint/no-unused-vars */\n\nexport const defaultInteractionConfig: InteractionConfig = {\n  tooltip: {\n    id: 'tooltip',\n    label: 'interactions.tooltip',\n    enabled: true,\n    config: {\n      fieldsToShow: {},\n      compareMode: false,\n      compareType: COMPARE_TYPES.ABSOLUTE\n    }\n  },\n  geocoder: {\n    id: 'geocoder',\n    label: 'interactions.geocoder',\n    enabled: false,\n    position: null\n  },\n  brush: {\n    id: 'brush',\n    label: 'interactions.brush',\n    enabled: false,\n    config: {\n      // size is in km\n      size: 0.5\n    }\n  },\n  coordinate: {\n    id: 'coordinate',\n    label: 'interactions.coordinate',\n    enabled: false,\n    position: null\n  }\n};\n\nexport const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {\n  domain: null,\n  currentTime: null,\n  speed: 1,\n  isAnimating: false,\n  timeSteps: null,\n  timeFormat: null,\n  timezone: null,\n  defaultTimeFormat: null,\n  hideControl: false,\n  duration: null\n};\n\nexport const DEFAULT_EDITOR: Editor = {\n  mode: EDITOR_MODES.DRAW_POLYGON,\n  features: [],\n  selectedFeature: null,\n  visible: true\n};\n\n/**\n * Default initial `visState`\n * @memberof visStateUpdaters\n * @constant\n * @public\n */\nexport const INITIAL_VIS_STATE: VisState = {\n  // map info\n  mapInfo: {\n    title: '',\n    description: ''\n  },\n  // layers\n  layers: [],\n  layerData: [],\n  layerToBeMerged: [],\n  layerOrder: [],\n\n  // filters\n  filters: [],\n  filterToBeMerged: [],\n\n  // a collection of multiple dataset\n  datasets: {},\n  editingDataset: undefined,\n\n  // effects\n  effects: [],\n  effectOrder: [],\n\n  interactionConfig: defaultInteractionConfig,\n  interactionToBeMerged: {},\n\n  layerBlending: 'normal',\n  overlayBlending: 'normal',\n  hoverInfo: undefined,\n  clicked: undefined,\n  mousePos: {},\n  maxDefaultTooltips: MAX_DEFAULT_TOOLTIPS,\n\n  // this is used when user split maps\n  splitMaps: [\n    // this will contain a list of objects to\n    // describe the state of layer availability and visibility for each map\n    // [\n    //   {\n    //      layers: {layer_id: true | false}\n    //   }\n    // ]\n  ],\n  splitMapsToBeMerged: [],\n  isMergingDatasets: {},\n  // defaults layer classes\n  layerClasses: LayerClasses,\n\n  // default animation\n  // time in unix timestamp (milliseconds) (the number of seconds since the Unix Epoch)\n  animationConfig: DEFAULT_ANIMATION_CONFIG,\n\n  editor: DEFAULT_EDITOR,\n\n  fileLoading: false,\n  fileLoadingProgress: {},\n  // for loading datasets\n  loadingIndicatorValue: 0,\n\n  loaders: [],\n  loadOptions: {},\n\n  // visStateMergers\n  mergers: VIS_STATE_MERGERS,\n\n  // kepler schemas\n  schema: KeplerGLSchema\n};\n\nexport const ACTION_TASK_FIT_BOUNDS = Task.fromCallback(\n  (_, cb) => cb(),\n\n  'ACTION_TASK_FIT_BOUNDS'\n);\n\nexport const ACTION_TASK_ADD_NOTIFICATION = Task.fromCallback(\n  (_, cb) => cb(),\n\n  'ACTION_TASK_ADD_NOTIFICATION'\n);\n\ntype UpdateStateWithLayerAndDataType = {\n  layers: Layer[];\n  layerData: any[];\n};\n\n/**\n * Update state with updated layer and layerData\n *\n */\nexport function updateStateWithLayerAndData<S extends UpdateStateWithLayerAndDataType>(\n  state: S,\n  {layerData, layer, idx}: {layerData?: any; layer: Layer; idx: number}\n): S {\n  return {\n    ...state,\n    layers: state.layers.map((lyr, i) => (i === idx ? layer : lyr)),\n    layerData: layerData\n      ? state.layerData.map((d, i) => (i === idx ? layerData : d))\n      : state.layerData\n  };\n}\n\nexport function updateStateOnLayerVisibilityChange<S extends VisState>(state: S, layer: Layer): S {\n  let newState = state;\n  if (state.splitMaps.length) {\n    newState = {\n      ...state,\n      splitMaps: layer.config.isVisible\n        ? addNewLayersToSplitMap(state.splitMaps, layer)\n        : removeLayerFromSplitMaps(state.splitMaps, layer)\n    };\n  }\n\n  if (layer.config.animation.enabled) {\n    newState = updateAnimationDomain(newState);\n  }\n\n  return newState;\n}\n\n/**\n * Compares two objects (or arrays) and returns a new object with only the\n * properties that have changed between the two objects.\n */\nfunction pickChangedProps<T>(prev: T, next: T): Partial<T> {\n  const changedProps: Partial<T> = {};\n  const pickPropsOf = obj => {\n    Object.keys(obj).forEach(key => {\n      if (\n        !Object.prototype.hasOwnProperty.call(changedProps, key) &&\n        !isEqual(prev[key], next[key])\n      ) {\n        changedProps[key] = next[key];\n      }\n    });\n  };\n  pickPropsOf(prev);\n  pickPropsOf(next);\n  return changedProps;\n}\n\nconst VISUAL_CHANNEL_PROP_TYPES = ['field', 'scale', 'domain', 'aggregation'];\n\n/**\n * Apply layer config\n * @memberof visStateUpdaters\n * @returns nextState\n */\n// eslint-disable-next-line complexity, max-statements\nexport function applyLayerConfigUpdater(\n  state: VisState,\n  action: VisStateActions.ApplyLayerConfigUpdaterAction\n): VisState {\n  const {oldLayerId, newLayerConfig, layerIndex} = action;\n  const newParsedLayer =\n    // will move visualChannels to the config prop\n    parseLayerConfig(state.schema, newLayerConfig);\n  const oldLayer = state.layers.find(l => l.id === oldLayerId);\n  if (!oldLayer || !newParsedLayer) {\n    return state;\n  }\n  if (layerIndex !== null && layerIndex !== undefined && state.layers[layerIndex] !== oldLayer) {\n    // layerIndex is provided, but it doesn't match the oldLayer\n    return state;\n  }\n  const dataset = state.datasets[newParsedLayer.config.dataId];\n  if (!dataset) {\n    return state;\n  }\n  // Make sure the layer is valid and convert it to Layer\n  const newLayer = validateLayerWithData(dataset, newParsedLayer, state.layerClasses);\n  if (!newLayer) {\n    return state;\n  }\n\n  let nextState = state;\n\n  if (newLayer.type && newLayer.type !== oldLayer.type) {\n    const oldLayerIndex = state.layers.findIndex(l => l.id === oldLayerId);\n    if (oldLayerIndex >= 0) {\n      nextState = layerTypeChangeUpdater(nextState, layerTypeChange(oldLayer, newLayer.type));\n      // layerTypeChangeUpdater changes the id of the layer, so we need to obtain the new id\n      // but first make sure that the layer was not removed\n      if (nextState.layers.length === state.layers.length) {\n        const newLayerId = nextState.layers[oldLayerIndex].id;\n        nextState = applyLayerConfigUpdater(\n          nextState,\n          applyLayerConfig(newLayerId, {...newLayerConfig, id: newLayerId})\n        );\n      }\n    }\n    return nextState;\n  }\n\n  // serializeLayer() might return null if the old layer is not valid,\n  // we should still apply the changes in that case\n  const serializedOldLayer = serializeLayer(oldLayer, state.schema) ?? {config: {}};\n  const serializedNewLayer = serializeLayer(newLayer, state.schema);\n  if (!serializedNewLayer) {\n    return state;\n  }\n  if (!isEqual(serializedOldLayer, serializedNewLayer)) {\n    const changed = pickChangedProps(serializedOldLayer.config, serializedNewLayer.config);\n\n    if ('visConfig' in changed) {\n      if (changed.visConfig) {\n        nextState = layerVisConfigChangeUpdater(\n          nextState,\n          layerVisConfigChange(oldLayer, changed.visConfig)\n        );\n      }\n      delete changed.visConfig;\n    }\n\n    Object.keys(oldLayer.visualChannels).forEach(channelName => {\n      const channel = oldLayer.visualChannels[channelName];\n      const channelPropNames = VISUAL_CHANNEL_PROP_TYPES.map(prop => channel[prop]);\n      if (channelPropNames.some(prop => prop in changed)) {\n        nextState = layerVisualChannelChangeUpdater(\n          nextState,\n          layerVisualChannelConfigChange(\n            oldLayer,\n            pick(newLayer.config, channelPropNames),\n            channelName\n          )\n        );\n        for (const prop of channelPropNames) {\n          delete changed[prop];\n        }\n      }\n    });\n\n    if (Object.keys(changed).length > 0) {\n      nextState = layerConfigChangeUpdater(\n        nextState,\n        layerConfigChange(oldLayer, pick(newLayer.config, Object.keys(changed)))\n      );\n    }\n  }\n\n  return nextState;\n}\n\nfunction updatelayerVisibilty(state: VisState, newLayer: Layer, isVisible?: boolean): VisState {\n  let newState = updateStateOnLayerVisibilityChange(state, newLayer);\n  const filterIndex = filterSyncedWithTimeline(state);\n  if (isLayerAnimatable(newLayer) && filterIndex !== -1) {\n    // if layer is going to be visible we sync with filter otherwise we need to check whether other animatable layers exists and are visible\n    newState = syncTimeFilterWithLayerTimelineUpdater(newState, {\n      idx: filterIndex,\n      enable: isVisible ? isVisible : getAnimatableVisibleLayers(state.layers).length > 0\n    });\n  }\n  return newState;\n}\n\n/**\n * Update layer base config: dataId, label, column, isVisible\n * @memberof visStateUpdaters\n * @returns nextState\n */\n// eslint-disable-next-line complexity\nexport function layerConfigChangeUpdater(\n  state: VisState,\n  action: VisStateActions.LayerConfigChangeUpdaterAction\n): VisState {\n  const {oldLayer} = action;\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n  const props = Object.keys(action.newConfig);\n  if (\n    typeof action.newConfig.dataId === 'string' &&\n    action.newConfig.dataId !== oldLayer.config.dataId\n  ) {\n    const {dataId, ...restConfig} = action.newConfig;\n    const stateWithDataId = layerDataIdChangeUpdater(state, {\n      oldLayer,\n      newConfig: {dataId}\n    });\n    const nextLayer = stateWithDataId.layers.find(l => l.id === oldLayer.id);\n    return nextLayer && Object.keys(restConfig).length\n      ? layerConfigChangeUpdater(stateWithDataId, {oldLayer: nextLayer, newConfig: restConfig})\n      : stateWithDataId;\n  }\n\n  let newLayer = oldLayer.updateLayerConfig(action.newConfig);\n\n  let layerData;\n\n  if (newLayer.shouldCalculateLayerData(props)) {\n    const oldLayerData = state.layerData[idx];\n\n    const updateLayerDataResult = calculateLayerData(newLayer, state, oldLayerData);\n    newLayer = updateLayerDataResult.layer;\n    layerData = updateLayerDataResult.layerData;\n  }\n\n  let newState = state;\n  if ('isVisible' in action.newConfig) {\n    newState = updatelayerVisibilty(newState, newLayer, action.newConfig.isVisible);\n  }\n\n  if ('columns' in action.newConfig && newLayer.config.animation.enabled) {\n    // TODO: Shan, make the animation config function more robust\n    newState = updateAnimationDomain(newState);\n  }\n\n  return updateStateWithLayerAndData(newState, {\n    layer: newLayer,\n    layerData,\n    idx\n  });\n}\n\nexport function layerAnimationChangeUpdater<S extends VisState>(state: S, action): S {\n  const {oldLayer, prop, value} = action;\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n\n  const newLayer = oldLayer.updateLayerConfig({\n    animation: {\n      ...oldLayer.config.animation,\n      [prop]: value\n    }\n  });\n\n  const {layerData, layer} = calculateLayerData(newLayer, state, state.layerData[idx]);\n\n  return updateStateWithLayerAndData(state, {layerData, layer, idx});\n}\n\n/**\n * Update layerId, isVisible, splitMapId\n * handles two cases:\n * 1) toggle the visibility of local SplitMap layer (visState.splitMap.layers)\n * 2) toggle the visibility of global layer (visState.layers)\n\n * @memberof visStateUpdaters\n * @returns nextState\n */\nexport function layerToggleVisibilityUpdater(\n  state: VisState,\n  action: VisStateActions.LayerToggleVisibilityUpdaterAction\n): VisState {\n  const {layerId, isVisible, splitMapId} = action;\n  const layer = state.layers.find(d => d.id === layerId);\n\n  if (!layer) {\n    return state;\n  }\n\n  let newState = state;\n\n  if (splitMapId) {\n    // [case 1]: toggle local layer visibility for each SplitMap\n    const mapIndex = newState.splitMaps.findIndex(sm => sm.id === splitMapId);\n    if (isVisible) {\n      // 1) if the layer is invisible globally\n      // -> set global visibility to true\n      newState = layerConfigChangeUpdater(newState, layerConfigChange(layer, {isVisible: true}));\n\n      // -> set local visibility to true and the local visibilities of all other SplitMaps to false\n      return {\n        ...newState,\n        splitMaps: newState.splitMaps.map(sm =>\n          sm.id !== splitMapId\n            ? {\n                ...sm,\n                layers: {\n                  ...sm.layers,\n                  [layerId]: false\n                }\n              }\n            : {\n                ...sm,\n                layers: {\n                  ...sm.layers,\n                  [layerId]: true\n                }\n              }\n        )\n      };\n    }\n    // 2) else when the layer is visible globally\n    return toggleLayerForMapUpdater(newState, toggleLayerForMap(mapIndex, layerId));\n  } else {\n    // [case 2]: toggle global layer visibility\n    const newLayer = layer.updateLayerConfig({isVisible});\n    const idx = newState.layers.findIndex(l => l.id === layerId);\n\n    newState = updatelayerVisibilty(newState, newLayer, isVisible);\n    return updateStateWithLayerAndData(newState, {\n      layer: newLayer,\n      idx\n    });\n  }\n}\n\n/**\n *\n * @param state\n * @returns index of the filter synced to timeline or -1\n */\nfunction filterSyncedWithTimeline(state: VisState): number {\n  return state.filters.findIndex(f => (f as TimeRangeFilter).syncedWithLayerTimeline);\n}\n\n/**\n * Updates isValid flag of a layer.\n * Updates isVisible based on the value of isValid.\n * Triggers update of data for the layer in order to get errors again during next update iteration.\n * @memberof visStateUpdaters\n * @returns nextState\n */\nexport function layerSetIsValidUpdater(\n  state: VisState,\n  action: VisStateActions.LayerSetIsValidUpdaterAction\n): VisState {\n  const {oldLayer, isValid} = action;\n\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n  const layerToUpdate = state.layers[idx];\n  if (layerToUpdate) {\n    let newLayer;\n    let newData = null;\n\n    if (isValid) {\n      // Trigger data update in order to show errors again if present.\n      const {layer, layerData} = calculateLayerData(layerToUpdate, state, undefined);\n      newLayer = layer;\n      newData = layerData;\n    } else {\n      newLayer = layerToUpdate.updateLayerConfig({\n        isVisible: false\n      });\n      newLayer.isValid = false;\n    }\n\n    return updateStateWithLayerAndData(state, {idx, layer: newLayer, layerData: newData});\n  }\n\n  return state;\n}\n\nfunction addOrRemoveTextLabels(newFields, textLabel, defaultTextLabel = DEFAULT_TEXT_LABEL) {\n  let newTextLabel = textLabel.slice();\n\n  const currentFields = textLabel.map(tl => tl.field && tl.field.name).filter(d => d);\n\n  const addFields = newFields.filter(f => !currentFields.includes(f.name));\n  const deleteFields = currentFields.filter(f => !newFields.find(fd => fd.name === f));\n\n  // delete\n  newTextLabel = newTextLabel.filter(tl => tl.field && !deleteFields.includes(tl.field.name));\n  newTextLabel = !newTextLabel.length ? [defaultTextLabel] : newTextLabel;\n\n  // add\n  newTextLabel = [\n    ...newTextLabel.filter(tl => tl.field),\n    ...addFields.map(af => ({\n      ...defaultTextLabel,\n      field: af\n    }))\n  ];\n\n  return newTextLabel;\n}\n\nfunction updateTextLabelPropAndValue(idx, prop, value, textLabel) {\n  if (!Object.prototype.hasOwnProperty.call(textLabel[idx], prop)) {\n    return textLabel;\n  }\n\n  let newTextLabel = textLabel.slice();\n\n  if (prop === 'field' && value === null && textLabel.length > 1) {\n    // remove label when field value is set to null\n    newTextLabel.splice(idx, 1);\n  } else if (prop) {\n    newTextLabel = textLabel.map((tl, i) => (i === idx ? {...tl, [prop]: value} : tl));\n  }\n\n  return newTextLabel;\n}\n\n/**\n * Update layer base config: dataId, label, column, isVisible\n * @memberof visStateUpdaters\n * @returns nextState\n */\nexport function layerTextLabelChangeUpdater(\n  state: VisState,\n  action: VisStateActions.LayerTextLabelChangeUpdaterAction\n): VisState {\n  const {oldLayer, idx, prop, value} = action;\n  const {textLabel} = oldLayer.config;\n\n  // when adding a new empty text label,\n  // rely on the layer's default config, or use the constant DEFAULT_TEXT_LABEL\n  const defaultTextLabel =\n    oldLayer.getDefaultLayerConfig({dataId: ''})?.textLabel?.[0] ?? DEFAULT_TEXT_LABEL;\n\n  let newTextLabel = textLabel.slice();\n  if (!textLabel[idx] && idx === textLabel.length) {\n    // if idx is set to length, add empty text label\n    newTextLabel = [...textLabel, defaultTextLabel];\n  }\n\n  if (idx === 'all' && prop === 'fields') {\n    newTextLabel = addOrRemoveTextLabels(value, textLabel, defaultTextLabel);\n  } else {\n    newTextLabel = updateTextLabelPropAndValue(idx, prop, value, newTextLabel);\n  }\n  // update text label prop and value\n  return layerConfigChangeUpdater(state, {\n    oldLayer,\n    newConfig: {textLabel: newTextLabel}\n  });\n}\n\nfunction validateExistingLayerWithData(dataset, layerClasses, layer, schema) {\n  const loadedLayer = serializeLayer(layer, schema);\n  return loadedLayer\n    ? validateLayerWithData(dataset, loadedLayer, layerClasses, {\n        allowEmptyColumn: true\n      })\n    : null;\n}\n\n/**\n * Update layer config dataId\n * @memberof visStateUpdaters\n * @returns nextState\n */\nexport function layerDataIdChangeUpdater(\n  state: VisState,\n  action: {\n    oldLayer: Layer;\n    newConfig: {\n      dataId: string;\n    };\n  }\n): VisState {\n  const {oldLayer, newConfig} = action;\n  const {dataId} = newConfig;\n\n  if (!oldLayer || !state.datasets[dataId]) {\n    return state;\n  }\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n\n  let newLayer = oldLayer.updateLayerConfig({dataId});\n\n  // this may happen when a layer is new (type: null and no columns) but it's not ready to be saved\n  if (newLayer.isValidToSave()) {\n    const validated = validateExistingLayerWithData(\n      state.datasets[dataId],\n      state.layerClasses,\n      newLayer,\n      state.schema\n    );\n    // if cant validate it with data create a new one\n    if (!validated) {\n      const oldLayerType = oldLayer.type;\n      if (oldLayerType) {\n        newLayer = new state.layerClasses[oldLayerType]({dataId, id: oldLayer.id});\n      }\n    } else {\n      newLayer = validated;\n    }\n  }\n\n  newLayer = newLayer.updateLayerConfig({\n    isVisible: oldLayer.config.isVisible,\n    isConfigActive: true\n  });\n\n  newLayer.updateLayerDomain(state.datasets);\n  const {layerData, layer} = calculateLayerData(newLayer, state, undefined);\n\n  return updateStateWithLayerAndData(state, {layerData, layer, idx});\n}\n\nexport function setInitialLayerConfig(layer, datasets, layerClasses): Layer {\n  let newLayer = layer;\n  if (!Object.keys(datasets).length) {\n    // no data is loaded\n    return layer;\n  }\n  if (!layer.config.dataId) {\n    // set layer dataId\n    newLayer = layer.updateLayerConfig({dataId: Object.keys(datasets)[0]});\n  }\n  const dataset = datasets[newLayer.config.dataId];\n  if (!dataset) {\n    return layer;\n  }\n\n  // find defaut layer props\n  const result =\n    typeof layerClasses[newLayer.type].findDefaultLayerProps === 'function'\n      ? layerClasses[newLayer.type].findDefaultLayerProps(dataset, [])\n      : {props: []};\n\n  // an array of possible props, use 1st one\n  const props = Array.isArray(result) ? result : result.props || [];\n\n  if (props.length) {\n    newLayer = new layerClasses[layer.type]({\n      ...props[0],\n      label: newLayer.config.label,\n      dataId: newLayer.config.dataId,\n      isConfigActive: newLayer.config.isConfigActive\n    });\n  }\n  return typeof newLayer.setInitialLayerConfig === 'function'\n    ? newLayer.setInitialLayerConfig(dataset)\n    : newLayer;\n}\n/**\n * Update layer type. Previews layer config will be copied if applicable.\n * @memberof visStateUpdaters\n * @public\n */\nexport function layerTypeChangeUpdater(\n  state: VisState,\n  action: VisStateActions.LayerTypeChangeUpdaterAction\n): VisState {\n  const {oldLayer, newType} = action;\n  if (!oldLayer) {\n    return state;\n  }\n  const oldId = oldLayer.id;\n  const idx = state.layers.findIndex(l => l.id === oldId);\n\n  if (!state.layerClasses[newType]) {\n    Console.error(`${newType} is not a valid layer type`);\n    return state;\n  }\n  let newLayer = new state.layerClasses[newType]({\n    // keep old layer lable and isConfigActive\n    label: oldLayer.config.label,\n    isConfigActive: oldLayer.config.isConfigActive,\n    dataId: oldLayer.config.dataId\n  });\n\n  if (!oldLayer.type) {\n    // if setting layer type on an empty layer\n    newLayer = setInitialLayerConfig(newLayer, state.datasets, state.layerClasses);\n  } else {\n    // get a mint layer, with new id and type\n    // because deck.gl uses id to match between new and old layer.\n    // If type has changed but id is the same, it will break\n\n    const defaultLayerProps =\n      typeof state.layerClasses[newType].findDefaultLayerProps === 'function'\n        ? state.layerClasses[newType].findDefaultLayerProps(state.datasets[newLayer.config.dataId])\n        : null;\n\n    newLayer.assignConfigToLayer(\n      oldLayer.config,\n      oldLayer.visConfigSettings,\n      state.datasets,\n      defaultLayerProps\n    );\n    newLayer.updateLayerDomain(state.datasets);\n  }\n\n  const {clicked, hoverInfo} = state;\n\n  let newState = {\n    ...state,\n    clicked: oldLayer.isLayerHovered(clicked) ? undefined : clicked,\n    hoverInfo: oldLayer.isLayerHovered(hoverInfo) ? undefined : hoverInfo\n  };\n\n  const {layerData, layer} = calculateLayerData(newLayer, newState);\n  newState = updateStateWithLayerAndData(newState, {layerData, layer, idx});\n\n  if (layer.config.animation.enabled || oldLayer.config.animation.enabled) {\n    newState = updateAnimationDomain(newState);\n  }\n\n  // update splitMap layer id\n  if (state.splitMaps.length) {\n    newState = {\n      ...newState,\n      splitMaps: newState.splitMaps.map(settings => {\n        const {[oldId]: oldLayerMap, ...otherLayers} = settings.layers;\n        return oldId in settings.layers\n          ? {\n              ...settings,\n              layers: {\n                ...otherLayers,\n                [layer.id]: oldLayerMap\n              }\n            }\n          : settings;\n      })\n    };\n  }\n\n  // update layerOrder with new id\n  newState = {\n    ...newState,\n    layerOrder: newState.layerOrder.map(layerId =>\n      layerId === oldLayer.id ? newLayer.id : layerId\n    )\n  };\n\n  return newState;\n}\n\n/**\n * Update layer visual channel\n * @memberof visStateUpdaters\n * @returns {Object} nextState\n * @public\n */\nexport function layerVisualChannelChangeUpdater(\n  state: VisState,\n  action: VisStateActions.LayerVisualChannelConfigChangeUpdaterAction\n): VisState {\n  const {oldLayer, newConfig, newVisConfig, channel} = action;\n  if (!oldLayer.config.dataId) {\n    return state;\n  }\n\n  const dataset = state.datasets[oldLayer.config.dataId];\n\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n  let newLayer = oldLayer.updateLayerConfig(newConfig);\n  if (newVisConfig) newLayer = newLayer.updateLayerVisConfig(newVisConfig);\n\n  newLayer.updateLayerVisualChannel(dataset, channel);\n\n  // calling update animation domain first to merge all layer animation domain\n  let updatedState = updateAnimationDomain(state);\n\n  const visualChannel = oldLayer.visualChannels[channel];\n  if (visualChannel?.channelScaleType === CHANNEL_SCALES.color && newConfig[visualChannel.field]) {\n    // if color field changed, set customBreaks to false\n    newLayer.updateLayerColorUI(visualChannel.range, {\n      colorRangeConfig: {\n        ...newLayer.config.colorUI[visualChannel.range].colorRangeConfig,\n        customBreaks: false\n      }\n    });\n\n    updatedState = {\n      ...updatedState,\n      layers: updatedState.layers.map(l => (l.id === oldLayer.id ? newLayer : l))\n    };\n  }\n\n  const oldLayerData = updatedState.layerData[idx];\n  const {layerData, layer} = calculateLayerData(newLayer, updatedState, oldLayerData);\n\n  if (\n    visualChannel?.channelScaleType === CHANNEL_SCALES.color &&\n    newConfig[visualChannel?.scale] === SCALE_TYPES.customOrdinal &&\n    !newVisConfig\n  ) {\n    // when switching to customOrdinal scale, create a customPalette in colorUI with updated colorDomain\n    const customPalette = initCustomPaletteByCustomScale({\n      scale: SCALE_TYPES.customOrdinal,\n      field: layer.config[visualChannel.field],\n      ordinalDomain: layer.config[layer.visualChannels[channel].domain],\n      range: layer.config.visConfig[visualChannel.range],\n      colorBreaks: null\n    });\n    // update colorRange with new customPalette\n    layer.updateLayerColorUI(visualChannel.range, {\n      showColorChart: true,\n      colorRangeConfig: {\n        ...layer.config.colorUI[visualChannel.range].colorRangeConfig,\n        customBreaks: true\n      },\n      customPalette\n    });\n  }\n  return updateStateWithLayerAndData(updatedState, {layerData, layer, idx});\n}\n\n/**\n * Update layer `visConfig`\n * @memberof visStateUpdaters\n * @public\n */\nexport function layerVisConfigChangeUpdater(\n  state: VisState,\n  action: VisStateActions.LayerVisConfigChangeUpdaterAction\n): VisState {\n  const {oldLayer} = action;\n  const idx = state.layers.findIndex(l => l.id === oldLayer.id);\n  const props = Object.keys(action.newVisConfig);\n\n  const newVisConfig = {\n    ...oldLayer.config.visConfig,\n    ...action.newVisConfig\n  };\n\n  const newLayer = oldLayer.updateLayerConfig({visConfig: newVisConfig});\n\n  if (newLayer.shouldCalculateLayerData(props)) {\n    const oldLayerData = state.layerData[idx];\n    const {layerData, layer} = calculateLayerData(newLayer, state, oldLayerData);\n    return updateStateWithLayerAndData(state, {layerData, layer, idx});\n  }\n\n  return updateStateWithLayerAndData(state, {layer: newLayer, idx});\n}\n\n/**\n * Reset animation config current time to a specified value\n * @memberof visStateUpdaters\n * @public\n *\n */\nexport const setLayerAnimationTimeUpdater = <S extends VisState>(\n  state: S,\n  {value}: VisStateActions.SetLayerAnimationTimeUpdaterAction\n): S => {\n  const currentTime = Array.isArray(value) ? value[0] : value;\n  const nextState = {\n    ...state,\n    animationConfig: {\n      ...state.animationConfig,\n      currentTime\n    }\n  };\n  // update animation config for each layer\n  return state.layers.reduce((accu, l) => {\n    if (l.config.animation.enabled && l.type !== 'trip') {\n      return layerAnimationChangeUpdater(accu, {oldLayer: l, prop: 'currentTime', currentTime});\n    }\n    return accu;\n  }, nextState);\n};\n\n/**\n * Update filter property\n * @memberof visStateUpdaters\n * @public\n */\nexport function setFilterAnimationTimeUpdater(\n  state: VisState,\n  action: VisStateActions.SetFilterAnimationTimeUpdaterAction\n): VisState {\n  return setFilterUpdater(state, action);\n}\n\n/**\n * Update filter animation window\n * @memberof visStateUpdaters\n * @public\n */\nexport function setFilterAnimationWindowUpdater<S extends VisState>(\n  state: S,\n  {id, animationWindow}: VisStateActions.SetFilterAnimationWindowUpdaterAction\n): S {\n  const filter = state.filters.find(f => f.id === id);\n\n  if (!filter) {\n    return state;\n  }\n\n  const newFilter = {\n    ...filter,\n    animationWindow\n  };\n\n  const newState = {\n    ...state,\n    filters: swap_<Filter>(newFilter)(state.filters)\n  };\n\n  const newSyncTimelineMode = getSyncAnimationMode(newFilter as TimeRangeFilter);\n\n  return setTimeFilterTimelineModeUpdater(newState, {id, mode: newSyncTimelineMode});\n}\n\nexport function applyFilterConfigUpdater(\n  state: VisState,\n  action: VisStateActions.ApplyFilterConfigUpdaterAction\n): VisState {\n  const {filterId, newFilter} = action;\n  const oldFilter = state.filters.find(f => f.id === filterId);\n  if (!oldFilter) {\n    return state;\n  }\n\n  // Serialize the filters to only compare the saved properties\n  const serializedOldFilter = serializeFilter(oldFilter, state.schema) ?? {config: {}};\n  const serializedNewFilter = serializeFilter(newFilter, state.schema);\n  if (!serializedNewFilter || isEqual(serializedOldFilter, serializedNewFilter)) {\n    return state;\n  }\n\n  // If there are any changes to the filter, apply them\n  const changed = pickChangedProps(serializedOldFilter, serializedNewFilter);\n  delete changed['id']; // id should not be changed\n\n  const filterIndex = state.filters.findIndex(f => f.id === filterId);\n  if (filterIndex < 0) {\n    return state;\n  }\n  return setFilterUpdater(\n    state,\n    setFilter(filterIndex, Object.keys(changed), Object.values(changed))\n  );\n}\n\n/**\n * Update filter property\n * @memberof visStateUpdaters\n * @public\n */\nexport function setFilterUpdater<S extends VisState>(\n  state: S,\n  action: VisStateActions.SetFilterUpdaterAction\n): S {\n  const {idx, valueIndex = 0} = action;\n  const oldFilter = state.filters[idx];\n  if (!oldFilter) {\n    Console.error(`filters.${idx} is undefined`);\n    return state;\n  }\n  if (\n    Array.isArray(action.prop) &&\n    (!Array.isArray(action.value) || action.prop.length !== action.value.length)\n  ) {\n    Console.error('Expecting value to be an array of the same length, since prop is an array');\n    return state;\n  }\n  // convert prop and value to array\n  const props = toArray(action.prop);\n  const values = Array.isArray(action.prop) ? toArray(action.value) : [action.value];\n\n  let newFilter = oldFilter;\n  let newState = state;\n\n  let datasetIdsToFilter: string[] = [];\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    const value = values[i];\n    // We currently do not support passing in name as an array into _updateFilterProp, so we call it multiple times with each name\n    // See the comment in there as to what should be addressed\n    let res;\n    if (prop === 'name' && Array.isArray(value)) {\n      // eslint-disable-next-line no-loop-func\n      res = value.reduce((accu, v) => {\n        return _updateFilterProp(accu, newFilter, prop, v, valueIndex);\n      }, newState);\n    } else {\n      res = _updateFilterProp(newState, newFilter, prop, value, valueIndex);\n    }\n    newFilter = res.filter;\n    newState = res.state;\n    datasetIdsToFilter = datasetIdsToFilter.concat(res.datasetIdsToFilter);\n  }\n\n  const enlargedFilter = state.filters.find(f => f.view === FILTER_VIEW_TYPES.enlarged);\n\n  if (enlargedFilter && enlargedFilter.id !== newFilter.id) {\n    // there should be only one enlarged filter\n    newFilter.view = FILTER_VIEW_TYPES.side;\n  }\n\n  // save new filters to newState\n  newState = set(['filters', idx], newFilter, newState);\n\n  // filter data\n  const filteredDatasets = applyFiltersToDatasets(\n    uniq(datasetIdsToFilter),\n    newState.datasets,\n    newState.filters,\n    newState.layers\n  );\n\n  newState = set(['datasets'], filteredDatasets, newState);\n\n  // need to update filterPlot after filter Dataset for plot to update on filtered result\n  const filterWithPLot = updateFilterPlot(newState.datasets, newState.filters[idx]);\n\n  newState = set(['filters', idx], filterWithPLot, newState);\n\n  // dataId is an array\n  // pass only the dataset we need to update\n  newState = updateAllLayerDomainData(newState, datasetIdsToFilter, newFilter);\n\n  // If time range filter value was updated, adjust animation config\n  if (newFilter.type === FILTER_TYPES.timeRange && props.includes('value')) {\n    newState = adjustAnimationConfigWithFilter(newState, action.idx);\n  }\n\n  return newState;\n}\n\nfunction _updateFilterDataIdAtValueIndex(filter, valueIndex, value, datasets) {\n  let newFilter = filter;\n  if (filter.dataId[valueIndex]) {\n    // if dataId already exist\n    newFilter = _removeFilterDataIdAtValueIndex(filter, valueIndex, datasets);\n  }\n  if (value) {\n    const nextValue = newFilter.dataId.slice();\n    nextValue[valueIndex] = value;\n    newFilter = set(['dataId'], nextValue, newFilter);\n  }\n  return newFilter;\n}\n\nfunction _removeFilterDataIdAtValueIndex(filter, valueIndex, datasets) {\n  const dataId = filter.dataId[valueIndex];\n\n  if (filter.dataId.length === 1 && valueIndex === 0) {\n    // if remove the only dataId, create an empty filter instead;\n    return getDefaultFilter({id: filter.id});\n  }\n\n  if (dataId) {\n    filter = removeFilterPlot(filter, dataId);\n  }\n\n  for (const prop of ['dataId', 'name', 'fieldIdx', 'gpuChannel']) {\n    if (Array.isArray(filter[prop])) {\n      const nextVal = filter[prop].slice();\n      nextVal.splice(valueIndex, 1);\n      filter = set([prop], nextVal, filter);\n    }\n  }\n\n  // mergeFieldDomain for the remaining fields\n  const domainSteps = mergeFilterDomain(filter, datasets);\n\n  const nextFilter = {\n    ...filter,\n    // value: nextValue,\n    ...(domainSteps ? {domain: domainSteps?.domain, step: domainSteps?.step} : {})\n  };\n\n  const nextValue = adjustValueToFilterDomain(nextFilter.value, nextFilter);\n  return {\n    ...nextFilter,\n    value: nextValue\n  };\n}\n\n/** *\n * Updates a single property of a filter\n */\nfunction _updateFilterProp(state, filter, prop, value, valueIndex, datasetIds?) {\n  let datasetIdsToFilter: string[] = [];\n  switch (prop) {\n    // TODO: Next PR for UI if we update filterDataId, we need to consider two cases:\n    // 1. dataId is empty: create a default filter\n    // 2. Add a new dataset id\n    case FILTER_UPDATER_PROPS.dataId: {\n      const oldDataId = [...filter.dataId];\n      filter = _updateFilterDataIdAtValueIndex(filter, valueIndex, value, state.datasets);\n      datasetIdsToFilter = uniq([...oldDataId, ...filter.dataId]);\n      break;\n    }\n    case FILTER_UPDATER_PROPS.name: {\n      // we are supporting the current functionality\n      // TODO: Next PR for UI filter name will only update filter name but it won't have side effects\n      // we are gonna use pair of datasets and fieldIdx to update the filter\n      const datasetId = filter.dataId[valueIndex];\n      const {filter: updatedFilter, dataset: newDataset} = applyFilterFieldName(\n        filter,\n        state.datasets,\n        datasetId,\n        value,\n        valueIndex,\n        {mergeDomain: valueIndex > 0}\n      );\n      if (updatedFilter) {\n        filter = updatedFilter;\n        if (filter.gpu) {\n          filter = setFilterGpuMode(filter, state.filters);\n          filter = assignGpuChannel(filter, state.filters);\n        }\n        state = set(['datasets', datasetId], newDataset, state);\n        // remove filter Plot at datasetId, so it will be recalculated\n        filter = removeFilterPlot(filter, datasetId);\n\n        datasetIdsToFilter = updatedFilter.dataId;\n      }\n      // only filter the current dataset\n      break;\n    }\n\n    case FILTER_UPDATER_PROPS.layerId: {\n      // We need to update only datasetId/s if we have added/removed layers\n      // - check for layerId changes (XOR works because of string values)\n      // if no differences between layerIds, don't do any filtering\n      // @ts-ignore\n      const layerIdDifference = xor(value, filter.layerId);\n\n      const layerDataIds = uniq<string>(\n        layerIdDifference\n          .map(lid =>\n            get(\n              state.layers.find(l => l.id === lid),\n              ['config', 'dataId']\n            )\n          )\n          .filter(d => d) as string[]\n      );\n\n      // only filter datasetsIds\n      datasetIdsToFilter = layerDataIds;\n\n      // Update newFilter dataIds\n      const newDataIds = uniq<string>(\n        value\n          ?.map(lid =>\n            get(\n              state.layers.find(l => l.id === lid),\n              ['config', 'dataId']\n            )\n          )\n          .filter(d => d) as string[]\n      );\n\n      filter = {\n        ...filter,\n        layerId: value,\n        dataId: newDataIds\n      };\n      break;\n    }\n\n    default:\n      filter = set([prop], value, filter);\n      datasetIdsToFilter = [...filter.dataId];\n      break;\n  }\n\n  return {filter, datasetIds, datasetIdsToFilter, state};\n}\n/* eslint-enable max-statements */\n\n/**\n * Set the property of a filter plot\n * @memberof visStateUpdaters\n * @public\n */\nexport const setFilterPlotUpdater = (\n  state: VisState,\n  {idx, newProp}: VisStateActions.SetFilterPlotUpdaterAction\n): VisState => {\n  if (!state.filters[idx]) {\n    Console.error(`filters[${idx}] is undefined`);\n    return state;\n  }\n  let newFilter = state.filters[idx];\n\n  for (const prop in newProp) {\n    if (prop === 'plotType') {\n      newFilter = pick_('plotType')(merge_(newProp.plotType))(newFilter);\n    } else if (prop === 'yAxis') {\n      const chartType = newProp.yAxis ? PLOT_TYPES.lineChart : PLOT_TYPES.histogram;\n\n      newFilter = pick_('plotType')(merge_({type: chartType}))(merge_(newProp)(newFilter));\n    }\n  }\n\n  newFilter = updateFilterPlot(state.datasets, newFilter);\n\n  return {\n    ...state,\n    filters: state.filters.map((f, i) => (i === idx ? newFilter : f))\n  };\n};\n\n/**\n * Add a new filter\n * @memberof visStateUpdaters\n * @public\n */\nexport const addFilterUpdater = (\n  state: VisState,\n  action: VisStateActions.AddFilterUpdaterAction\n): VisState =>\n  !action.dataId\n    ? state\n    : {\n        ...state,\n        filters: [...state.filters, getDefaultFilter({dataId: action.dataId, id: action.id})]\n      };\n\n/**\n * Create or update a filter\n * @memberof visStateUpdaters\n * @public\n */\nexport const createOrUpdateFilterUpdater = (\n  state: VisState,\n  action: VisStateActions.CreateOrUpdateFilterUpdaterAction\n): VisState => {\n  const {id, dataId, field, value} = action;\n\n  let newState = state;\n  const originalIndex = newState.filters.findIndex(f => f.id === id);\n  let filterIndex = originalIndex;\n  if (!id && !dataId) {\n    return newState;\n  }\n  if (originalIndex < 0 && dataId) {\n    newState = addFilterUpdater(newState, {dataId});\n    if (newState.filters.length !== state.filters.length + 1) {\n      // No new filter was added\n      return state;\n    }\n    // Here we are assuming that the filter was added at the end\n    filterIndex = newState.filters.length - 1;\n    newState.filters[filterIndex] = {\n      ...newState.filters[filterIndex],\n      ...(id ? {id} : null)\n    };\n  }\n\n  // No need to update this if it's a newly created filter\n  // First we make sure all the dataIds that fields refer to are updated\n  if (originalIndex >= 0 && dataId) {\n    // If the dataId is an array, we need to update each one individually as they need a correct valueIndex passed\n    newState = (Array.isArray(dataId) ? dataId : [dataId]).reduce((accu, d, index) => {\n      return setFilterUpdater(accu, {\n        idx: filterIndex,\n        prop: 'dataId',\n        value: d,\n        valueIndex: index\n      });\n    }, newState);\n  }\n  // Then we update the fields\n  if (field) {\n    // If the field is an array, we need to update each field individually as they need a correct valueIndex passed\n    newState = (Array.isArray(field) ? field : [field]).reduce((accu, f, index) => {\n      return setFilterUpdater(accu, {\n        idx: filterIndex,\n        prop: 'name',\n        value: f,\n        valueIndex: index\n      });\n    }, newState);\n  }\n  // Then we update the value separately\n  if (value !== null && typeof value !== 'undefined') {\n    newState = setFilterUpdater(newState, {idx: filterIndex, prop: 'value', value});\n  }\n\n  return newState;\n};\n\n/**\n * Set layer color palette ui state\n * @memberof visStateUpdaters\n */\nexport const layerColorUIChangeUpdater = (\n  state: VisState,\n  {oldLayer, prop, newConfig}: VisStateActions.LayerColorUIChangeUpdaterAction\n): VisState => {\n  const oldVixConfig = oldLayer.config.visConfig[prop];\n  const newLayer = oldLayer.updateLayerColorUI(prop, newConfig);\n  const newVisConfig = newLayer.config.visConfig[prop];\n  if (oldVixConfig !== newVisConfig) {\n    return layerVisConfigChangeUpdater(state, {\n      oldLayer,\n      newVisConfig: {\n        [prop]: newVisConfig\n      }\n    });\n  }\n  return {\n    ...state,\n    layers: state.layers.map(l => (l.id === oldLayer.id ? newLayer : l))\n  };\n};\n\n/**\n * Start and end filter animation\n * @memberof visStateUpdaters\n * @public\n */\nexport const toggleFilterAnimationUpdater = (\n  state: VisState,\n  action: VisStateActions.ToggleFilterAnimationUpdaterAction\n): VisState => ({\n  ...state,\n  filters: state.filters.map((f, i) => (i === action.idx ? {...f, isAnimating: !f.isAnimating} : f))\n});\n\nexport function isFilterAnimationConfig(config: AnimationConfig | FilterAnimationConfig): boolean {\n  return 'dataId' in config && 'animationWindow' in config;\n}\n\nexport function setAnimationConfigUpdater(\n  state: VisState,\n  action: VisStateActions.SetAnimationConfigUpdaterAction\n): VisState {\n  const {config} = action;\n  if (isFilterAnimationConfig(config)) {\n    // Find filter used for animation\n    // Assuming there's only one filter used for animation, see setFilterViewUpdater\n    const filter = state.filters.find(f => !isSideFilter(f));\n    if (!filter) {\n      return state;\n    }\n    const newFilter = {...filter, ...config};\n    return applyFilterConfigUpdater(state, applyFilterConfig(filter.id, newFilter));\n  } else {\n    return {\n      ...state,\n      animationConfig: {\n        ...state.animationConfig,\n        ...config\n      }\n    };\n  }\n}\n\n/**\n * @memberof visStateUpdaters\n * @public\n */\nexport const toggleLayerAnimationUpdater = (\n  state: VisState,\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  action: VisStateActions.ToggleLayerAnimationUpdaterAction\n): VisState => ({\n  ...state,\n  animationConfig: {\n    ...state.animationConfig,\n    isAnimating: !state.animationConfig.isAnimating\n  }\n});\n\n/**\n * Hide and show layer animation control\n * @memberof visStateUpdaters\n * @public\n */\nexport const toggleLayerAnimationControlUpdater = (state: VisState): VisState => ({\n  ...state,\n  animationConfig: {\n    ...state.animationConfig,\n    hideControl: !state.animationConfig.hideControl\n  }\n});\n\n/**\n * Change filter animation speed\n * @memberof visStateUpdaters\n * @public\n */\nexport const updateFilterAnimationSpeedUpdater = (\n  state: VisState,\n  action: VisStateActions.UpdateFilterAnimationSpeedUpdaterAction\n): VisState => ({\n  ...state,\n  filters: state.filters.map((f, i) => (i === action.idx ? {...f, speed: action.speed} : f))\n});\n\n/**\n * Update animation speed with the vertical speed slider\n * @memberof visStateUpdaters\n * @public\n *\n */\nexport const updateLayerAnimationSpeedUpdater = (\n  state: VisState,\n  {speed}: VisStateActions.UpdateLayerAnimationSpeedUpdaterAction\n): VisState => {\n  return {\n    ...state,\n    animationConfig: {\n      ...state.animationConfig,\n      speed\n    }\n  };\n};\n\n/**\n * Show larger time filter at bottom for time playback (apply to time filter only)\n * @memberof visStateUpdaters\n * @public\n */\nexport const setFilterViewUpdater = (\n  state: VisState,\n  action: VisStateActions.SetFilterViewUpdaterAction\n) => {\n  const {view, idx} = action;\n  const shouldResetOtherFiltersView = view === FILTER_VIEW_TYPES.enlarged;\n  return {\n    ...state,\n    filters: state.filters.map((f, i) =>\n      i === idx\n        ? {\n            ...f,\n            view\n          }\n        : shouldResetOtherFiltersView\n        ? {\n            ...f,\n            view: FILTER_VIEW_TYPES.side\n          }\n        : f\n    )\n  };\n};\n\n/**\n * Toggles filter feature visibility\n * @memberof visStateUpdaters\n */\nexport const toggleFilterFeatureUpdater = (\n  state: VisState,\n  action: VisStateActions.ToggleFilterFeatureUpdaterAction\n): VisState => {\n  const filter = state.filters[action.idx];\n  const isVisible = get(filter, ['value', 'properties', 'isVisible']);\n\n  let newState = setFilterUpdater(state, {\n    idx: action.idx,\n    prop: 'enabled',\n    value: !isVisible\n  });\n\n  newState = setFilterUpdater(newState, {\n    idx: action.idx,\n    prop: 'value',\n    value: featureToFilterValue(filter.value, filter.id, {\n      isVisible: !isVisible\n    })\n  });\n\n  return newState;\n};\n\n/**\n * Remove a filter\n * @memberof visStateUpdaters\n * @public\n */\nexport const removeFilterUpdater = (\n  state: VisState,\n  action: VisStateActions.RemoveFilterUpdaterAction\n): VisState => {\n  const {idx} = action;\n  const {dataId, id} = state.filters[idx];\n\n  const newFilters = [\n    ...state.filters.slice(0, idx),\n    ...state.filters.slice(idx + 1, state.filters.length)\n  ];\n\n  const filteredDatasets = applyFiltersToDatasets(dataId, state.datasets, newFilters, state.layers);\n  const newEditor =\n    getFilterIdInFeature(state.editor.selectedFeature) === id\n      ? {\n          ...state.editor,\n          selectedFeature: null\n        }\n      : state.editor;\n\n  let newState = set(['filters'], newFilters, state);\n  newState = set(['datasets'], filteredDatasets, newState);\n  newState = set(['editor'], newEditor, newState);\n\n  return updateAllLayerDomainData(newState, dataId, undefined);\n};\n\n/**\n * Add a new layer\n * @memberof visStateUpdaters\n * @public\n */\nexport const addLayerUpdater = (\n  state: VisState,\n  action: VisStateActions.AddLayerUpdaterAction\n): VisState => {\n  let newLayer;\n  let newLayerData;\n  if (action.config) {\n    newLayer = createLayerFromConfig(state, action.config);\n    if (!newLayer) {\n      Console.warn(\n        'Failed to create layer from config, it usually means the config is not be in correct format',\n        action.config\n      );\n      return state;\n    }\n\n    const result = calculateLayerData(newLayer, state);\n    newLayer = result.layer;\n    newLayerData = result.layerData;\n  } else {\n    // create an empty layer with a specific dataset or a default one\n    const defaultDataset = action.datasetId ?? Object.keys(state.datasets)[0];\n    newLayer = new Layer({\n      isVisible: true,\n      isConfigActive: true,\n      dataId: defaultDataset\n    });\n    newLayerData = {};\n  }\n\n  let newState = {\n    ...state,\n    layers: [...state.layers, newLayer],\n    layerData: [...state.layerData, newLayerData],\n    // add new layer at the top\n    layerOrder: [newLayer.id, ...state.layerOrder],\n    splitMaps: addNewLayersToSplitMap(state.splitMaps, newLayer)\n  };\n\n  if (newLayer.config.animation.enabled) {\n    newState = updateAnimationDomain(newState);\n  }\n\n  return newState;\n};\n\n/**\n * remove layer\n * @memberof visStateUpdaters\n * @public\n */\nexport function removeLayerUpdater<T extends VisState>(\n  state: T,\n  {id}: VisStateActions.RemoveLayerUpdaterAction\n): T {\n  const idx = Number.isFinite(id)\n    ? // support older API, remove layer by idx\n      Number(id)\n    : state.layers.findIndex(l => l.id === id);\n  if (idx < 0 || idx >= state.layers.length) {\n    // invalid index\n    Console.warn(`can not remove layer with invalid id|idx ${id}`);\n    return state;\n  }\n\n  const {layers, layerData, layerOrder, clicked, hoverInfo} = state;\n  const layerToRemove = layers[idx];\n  const newState = {\n    ...state,\n    layers: filterOutById(layerToRemove.id)(layers),\n    layerData: removeElementAtIndex(idx)(layerData),\n    layerOrder: layerOrder.filter(layerId => layerId !== layerToRemove.id),\n    clicked: layerToRemove.isLayerHovered(clicked) ? undefined : clicked,\n    hoverInfo: layerToRemove.isLayerHovered(hoverInfo) ? undefined : hoverInfo,\n    splitMaps: removeLayerFromSplitMaps(state.splitMaps, layerToRemove)\n    // TODO: update filters, create helper to remove layer form filter (remove layerid and dataid) if mapped\n  };\n\n  return updateAnimationDomain(newState);\n}\n\n/**\n * Reorder layer\n * @memberof visStateUpdaters\n * @public\n */\nexport const reorderLayerUpdater = (\n  state: VisState,\n  {order}: VisStateActions.ReorderLayerUpdaterAction\n): VisState => ({\n  ...state,\n  layerOrder: order\n});\n\n/**\n * duplicate layer\n * @memberof visStateUpdaters\n * @public\n */\nexport const duplicateLayerUpdater = (\n  state: VisState,\n  {id}: VisStateActions.DuplicateLayerUpdaterAction\n): VisState => {\n  const idx = Number.isFinite(id)\n    ? // support older API, remove layer by idx\n      Number(id)\n    : state.layers.findIndex(l => l.id === id);\n  if (idx < 0 || !state.layers[idx]) {\n    Console.warn(`layer ${idx} not found in layerOrder`);\n    return state;\n  }\n\n  const {layers} = state;\n  const original = layers[idx];\n\n  const originalLayerOrderIdx = state.layerOrder.findIndex(lid => lid === original.id);\n  let newLabel = `Copy of ${original.config.label}`;\n  let postfix = 0;\n  // eslint-disable-next-line no-loop-func\n  while (layers.find(l => l.config.label === newLabel)) {\n    newLabel = `Copy of ${original.config.label} ${++postfix}`;\n  }\n\n  // collect layer config from original\n  const loadedLayer = serializeLayer(original, state.schema);\n\n  // assign new id and label to copied layer\n  if (!loadedLayer?.config) {\n    return state;\n  }\n  loadedLayer.config.label = newLabel;\n  loadedLayer.id = generateHashId(LAYER_ID_LENGTH);\n\n  // add layer to state\n  let nextState = addLayerUpdater(state, {config: loadedLayer});\n  // retrieve newly created layer\n  const newLayer = nextState.layers[nextState.layers.length - 1];\n  // update layer order with newLyaer.id\n  const newLayerOrder = arrayInsert(\n    nextState.layerOrder.slice(1, nextState.layerOrder.length),\n    originalLayerOrderIdx,\n    newLayer.id\n  );\n\n  nextState = reorderLayerUpdater(nextState, {order: newLayerOrder});\n\n  return updateAnimationDomain(nextState);\n};\n\n/**\n * Add a new effect\n * @memberof visStateUpdaters\n * @public\n */\nexport const addEffectUpdater = (\n  state: VisState,\n  action: VisStateActions.AddEffectUpdaterAction\n): VisState => {\n  if (\n    action.config?.type === LIGHT_AND_SHADOW_EFFECT.type &&\n    state.effects.some(effect => effect.type === LIGHT_AND_SHADOW_EFFECT.type)\n  ) {\n    Console.warn(`Can't add more than one ${LIGHT_AND_SHADOW_EFFECT.name} effect`);\n    return state;\n  }\n\n  const newEffect = createEffect(action.config);\n\n  // collapse configurators for other effects\n  state.effects.forEach(effect => effect.setProps({isConfigActive: false}));\n\n  const effects = [...state.effects, newEffect];\n  const effectOrder = fixEffectOrder(effects, [newEffect.id, ...state.effectOrder]);\n\n  return {\n    ...state,\n    effects,\n    effectOrder\n  };\n};\n\n/**\n * remove effect\n * @memberof visStateUpdaters\n * @public\n */\nexport const removeEffectUpdater = (\n  state: VisState,\n  {id}: VisStateActions.RemoveEffectUpdaterAction\n): VisState => {\n  const idx = state.effects.findIndex(l => l.id === id);\n  if (idx < 0 || idx >= state.effects.length) {\n    Console.warn(`can not remove effect with invalid id ${id}`);\n    return state;\n  }\n\n  const {effects, effectOrder} = state;\n  const effectToRemove = effects[idx];\n  return {\n    ...state,\n    // @ts-expect-error fixed in ts\n    effects: filterOutById(effectToRemove.id)(effects),\n    effectOrder: effectOrder.filter(effectId => effectId !== effectToRemove.id)\n  };\n};\n\n/**\n * Reorder effect\n * @memberof visStateUpdaters\n * @public\n */\nexport const reorderEffectUpdater = (\n  state: VisState,\n  {order}: VisStateActions.ReorderEffectUpdaterAction\n): VisState => ({\n  ...state,\n  effectOrder: fixEffectOrder(state.effects, [...order])\n});\n\n/**\n * Update effect\n * @memberof visStateUpdaters\n * @public\n */\nexport const updateEffectUpdater = (\n  state: VisState,\n  {id, props}: VisStateActions.UpdateEffectUpdaterAction\n): VisState => {\n  const idx = state.effects.findIndex(l => l.id === id);\n  if (idx < 0 || idx >= state.effects.length) {\n    Console.warn(`can not update effect with invalid id ${id}`);\n    return state;\n  }\n\n  let effectOrder = state.effectOrder;\n  if (props.id !== undefined && props.id !== id) {\n    const idx2 = state.effects.findIndex(l => l.id === props.id);\n    if (idx2 >= 0) {\n      Console.warn(`can not update effect with existing effect id ${id}`);\n      return state;\n    }\n\n    effectOrder = effectOrder.map(effectOrderId =>\n      effectOrderId === id ? (props.id as string) : effectOrderId\n    );\n  }\n\n  const newEffects = [...state.effects];\n  newEffects[idx].setProps(props);\n\n  return {\n    ...state,\n    effects: newEffects,\n    effectOrder\n  };\n};\n\n/**\n * Remove a dataset and all layers, filters, tooltip configs that based on it\n * @memberof visStateUpdaters\n * @public\n */\nexport function removeDatasetUpdater<T extends VisState>(\n  state: T,\n  action: VisStateActions.RemoveDatasetUpdaterAction\n): T {\n  // extract dataset key\n  const {dataId: datasetKey} = action;\n  const {datasets} = state;\n\n  // check if dataset is present\n  if (!datasets[datasetKey]) {\n    return state;\n  }\n\n  const {\n    layers,\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    datasets: {[datasetKey]: dataset, ...newDatasets}\n  } = state;\n\n  const layersToRemove = layers.filter(l => l.config.dataId === datasetKey).map(l => l.id);\n\n  // remove layers and datasets\n  let newState = layersToRemove.reduce((accu, id) => removeLayerUpdater(accu, {id}), {\n    ...state,\n    datasets: newDatasets\n  });\n\n  // update filters\n  const filters: Filter[] = [];\n  for (const filter of newState.filters) {\n    const valueIndex = filter.dataId.indexOf(datasetKey);\n    if (valueIndex >= 0 && filter.dataId.length > 1) {\n      // only remove one synced dataset from the filter\n      filters.push(_removeFilterDataIdAtValueIndex(filter, valueIndex, datasets));\n    } else if (valueIndex < 0) {\n      // leave the filter as is\n      filters.push(filter);\n    }\n  }\n\n  newState = {...newState, filters};\n\n  return removeDatasetFromInteractionConfig(newState, {dataId: datasetKey});\n}\n\nfunction removeDatasetFromInteractionConfig(state, {dataId}) {\n  let {interactionConfig} = state;\n  const {tooltip} = interactionConfig;\n  if (tooltip) {\n    const {config} = tooltip;\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const {[dataId]: fields, ...fieldsToShow} = config.fieldsToShow;\n    interactionConfig = {\n      ...interactionConfig,\n      tooltip: {...tooltip, config: {...config, fieldsToShow}}\n    };\n  }\n\n  return {...state, interactionConfig};\n}\n/**\n * update layer blending mode\n * @memberof visStateUpdaters\n * @public\n */\nexport const updateLayerBlendingUpdater = (\n  state: VisState,\n  action: VisStateActions.UpdateLayerBlendingUpdaterAction\n): VisState => ({\n  ...state,\n  layerBlending: action.mode\n});\n\n/**\n * update overlay blending mode\n * @memberof visStateUpdaters\n * @public\n */\nexport const updateOverlayBlendingUpdater = (\n  state: VisState,\n  action: VisStateActions.UpdateOverlayBlendingUpdaterAction\n): VisState => ({\n  ...state,\n  overlayBlending: action.mode\n});\n\n/**\n * Display dataset table in a modal\n * @memberof visStateUpdaters\n * @public\n */\nexport const showDatasetTableUpdater = (\n  state: VisState,\n  action: VisStateActions.ShowDatasetTableUpdaterAction\n): VisState => {\n  return {\n    ...state,\n    editingDataset: action.dataId\n  };\n};\n\n/**\n * Add custom color for datasets and layers\n * @memberof visStateUpdaters\n * @public\n */\nexport const updateTableColorUpdater = (\n  state: VisState,\n  action: VisStateActions.UpdateDatasetColorUpdater\n): VisState => {\n  return updateDatasetPropsUpdater(state, {dataId: action.dataId, props: {color: action.newColor}});\n};\n\n/**\n * reset visState to initial State\n * @memberof visStateUpdaters\n * @public\n */\nexport const resetMapConfigUpdater = (state: VisState): VisState => ({\n  ...INITIAL_VIS_STATE,\n  ...state.initialState,\n  initialState: state.initialState\n});\n\n/**\n * Propagate `visState` reducer with a new configuration. Current config will be override.\n * @memberof visStateUpdaters\n * @public\n */\nexport const receiveMapConfigUpdater = (\n  state: VisState,\n  {\n    payload: {config = {}, options = {}}\n  }: {\n    type?: typeof ActionTypes.RECEIVE_MAP_CONFIG;\n    payload: ReceiveMapConfigPayload;\n  }\n): VisState => {\n  if (!config.visState) {\n    return state;\n  }\n\n  const {keepExistingConfig} = options;\n\n  // reset config if keepExistingConfig is falsy\n  let mergedState = !keepExistingConfig ? resetMapConfigUpdater(state) : state;\n  for (const merger of state.mergers) {\n    if (isValidMerger(merger) && hasPropsToMerge(config.visState, merger.prop)) {\n      mergedState = merger.merge(\n        mergedState,\n        getPropValueToMerger(config.visState, merger.prop, merger.toMergeProp),\n        // fromConfig\n        true\n      );\n    }\n  }\n\n  return mergedState;\n};\n\n/**\n * Trigger layer hover event with hovered object\n * @memberof visStateUpdaters\n * @public\n */\nexport const layerHoverUpdater = (\n  state: VisState,\n  action: VisStateActions.OnLayerHoverUpdaterAction\n): VisState => ({\n  ...state,\n  hoverInfo: {\n    // deck.gl info is mutable\n    ...action.info,\n    ...(Number.isFinite(action.mapIndex) ? {mapIndex: action.mapIndex} : {})\n  }\n});\n\n/* eslint-enable max-statements */\n\n/**\n * Update `interactionConfig`\n * @memberof visStateUpdaters\n * @public\n */\nexport function interactionConfigChangeUpdater(\n  state: VisState,\n  action: VisStateActions.InteractionConfigChangeUpdaterAction\n): VisState {\n  const {config} = action;\n\n  const interactionConfig = {\n    ...state.interactionConfig,\n    ...{[config.id]: config}\n  };\n\n  // Don't enable tooltip and brush at the same time\n  // but coordinates can be shown at all time\n  const contradict = ['brush', 'tooltip'];\n\n  if (\n    contradict.includes(config.id) &&\n    config.enabled &&\n    !state.interactionConfig[config.id].enabled\n  ) {\n    // only enable one interaction at a time\n    contradict.forEach(k => {\n      if (k !== config.id) {\n        interactionConfig[k] = {...interactionConfig[k], enabled: false};\n      }\n    });\n  }\n\n  const newState = {\n    ...state,\n    interactionConfig\n  };\n\n  if (config.id === 'geocoder' && !config.enabled) {\n    return removeDatasetUpdater(newState, {dataId: 'geocoder_dataset'});\n  }\n\n  return newState;\n}\n\n/**\n * Trigger layer click event with clicked object\n * @memberof visStateUpdaters\n * @public\n */\nexport const layerClickUpdater = (\n  state: VisState,\n  action: VisStateActions.OnLayerClickUpdaterAction\n): VisState => ({\n  ...state,\n  mousePos: state.interactionConfig.coordinate.enabled\n    ? {\n        ...state.mousePos,\n        pinned: state.mousePos.pinned ? null : cloneDeep(state.mousePos)\n      }\n    : state.mousePos,\n  clicked: action.info && action.info.picked ? action.info : null\n});\n\n/**\n * Trigger map click event, unselect clicked object\n * @memberof visStateUpdaters\n * @public\n */\nexport const mapClickUpdater = (\n  state: VisState,\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  action: VisStateActions.OnMapClickUpdaterAction\n): VisState => {\n  return {\n    ...state,\n    clicked: null\n  };\n};\n\n/**\n * Trigger map move event\n * @memberof visStateUpdaters\n * @public\n */\nexport const mouseMoveUpdater = (\n  state: VisState,\n  {evt}: VisStateActions.OnMouseMoveUpdaterAction\n): VisState => {\n  if (Object.values(state.interactionConfig).some(config => config.enabled)) {\n    return {\n      ...state,\n      mousePos: {\n        ...state.mousePos,\n        ...(Array.isArray(evt.point) ? {mousePosition: [...evt.point]} : {}),\n        ...(Array.isArray(evt.lngLat) ? {coordinate: [...evt.lngLat]} : {})\n      }\n    };\n  }\n\n  return state;\n};\n/**\n * Toggle visibility of a layer for a split map\n * @memberof visStateUpdaters\n * @public\n */\nexport const toggleSplitMapUpdater = (\n  state: VisState,\n  action: MapStateActions.ToggleSplitMapUpdaterAction\n): VisState =>\n  state.splitMaps && state.splitMaps.length === 0\n    ? {\n        ...state,\n        // maybe we should use an array to store state for a single map as well\n        // if current maps length is equal to 0 it means that we are about to split the view\n        splitMaps: computeSplitMapLayers(state.layers, {duplicate: false})\n      }\n    : closeSpecificMapAtIndex(state, action);\n\n/**\n * Toggle visibility of a layer in a split map\n * @memberof visStateUpdaters\n * @public\n */\nexport const toggleLayerForMapUpdater = (\n  state: VisState,\n  {mapIndex, layerId}: VisStateActions.ToggleLayerForMapUpdaterAction\n): VisState => {\n  const {splitMaps} = state;\n\n  return {\n    ...state,\n    splitMaps: splitMaps.map((sm, i) =>\n      i === mapIndex\n        ? {\n            ...splitMaps[i],\n            layers: {\n              ...splitMaps[i].layers,\n              // if layerId not in layers, set it to visible\n              [layerId]: !splitMaps[i].layers[layerId]\n            }\n          }\n        : sm\n    )\n  };\n};\n\n/**\n * Add new dataset to `visState`, with option to load a map config along with the datasets\n * @memberof visStateUpdaters\n * @public\n */\n// eslint-disable-next-line complexity\nexport const updateVisDataUpdater = (\n  state: VisState,\n  action: VisStateActions.UpdateVisDataUpdaterAction\n): VisState => {\n  // datasets can be a single data entries or an array of multiple data entries\n  const {config, options} = action;\n\n  // apply config if passed from action\n  // TODO: we don't handle async mergers here yet\n  let updatedState = config\n    ? receiveMapConfigUpdater(state, {\n        payload: {config, options}\n      })\n    : state;\n\n  const datasets = toArray(action.datasets);\n  if (!datasets.length) {\n    return updatedState;\n  }\n\n  const createDatasetTasks: Task[] = [];\n  const notificationTasks: Task[] = [];\n\n  datasets.forEach(({info = {}, ...rest}, datasetIndex) => {\n    const task = createNewDataEntry({info, ...rest}, state.datasets);\n    if (task) {\n      createDatasetTasks.push(task);\n    } else {\n      notificationTasks.push(\n        ACTION_TASK_ADD_NOTIFICATION().map(() =>\n          addNotification(\n            errorNotification({\n              message: `Failed to create a new dataset due to data verification errors`,\n              id: `dataset-failed-${datasetIndex}`\n            })\n          )\n        )\n      );\n    }\n  });\n\n  const datasetsAllSettledTask = createDatasetTasks.length\n    ? Task.allSettled(createDatasetTasks).map(results =>\n        createNewDatasetSuccess({results, addToMapOptions: options})\n      )\n    : null;\n\n  if (datasetsAllSettledTask) {\n    updatedState = setLoadingIndicatorUpdater(updatedState, payload_({change: 1, type: ''}));\n  }\n\n  return withTask(updatedState, [\n    ...(datasetsAllSettledTask ? [datasetsAllSettledTask] : []),\n    ...notificationTasks\n  ]);\n};\n\nexport const createNewDatasetSuccessUpdater = (\n  state: VisState,\n  action: PayloadAction<CreateNewDatasetSuccessPayload>\n): VisState => {\n  const {results, addToMapOptions} = action.payload;\n  const notificationTasks: Task[] = [];\n\n  const newDataEntries = results.reduce((accu, result, idx) => {\n    if (result.status === 'fulfilled') {\n      const dataset = result.value;\n      return {...accu, [dataset.id]: dataset};\n    } else {\n      // show error notification on UI\n      notificationTasks.push(\n        ACTION_TASK().map(() =>\n          addNotification(\n            errorNotification({\n              message: `Dataset error: Failed to create a new dataset:\n              ${result.reason || (result as any).value}`,\n              id: `dataset-create-failed-${idx}`\n            })\n          )\n        )\n      );\n      return accu;\n    }\n  }, {} as Datasets);\n  // save new dataset entry to state\n  const mergedState = {\n    ...state,\n    datasets: mergeDatasetsByOrder(state, newDataEntries)\n  };\n\n  // merge state with config to be merged\n  const layerMergers = state.mergers.filter(m => m.waitForLayerData);\n  const datasetMergers = state.mergers.filter(m => !layerMergers.includes(m));\n\n  const newDataIds = Object.keys(newDataEntries);\n  const postMergerPayload = {\n    newDataIds,\n    options: addToMapOptions,\n    layerMergers\n  };\n\n  const updatedState = applyMergersUpdater(mergedState, {\n    mergers: datasetMergers,\n    postMergerPayload\n  });\n\n  return withTask(\n    setLoadingIndicatorUpdater(updatedState, payload_({change: -1})),\n    notificationTasks\n  );\n};\n\n/**\n * Add new dataset to `visState`, with option to load a map config along with the datasets\n */\nexport function applyMergersUpdater(\n  state: VisState,\n  action: {\n    mergers: Merger<any>[];\n    postMergerPayload: PostMergerPayload;\n  }\n): VisState {\n  const {mergers, postMergerPayload} = action;\n\n  // merge state with config to be merged\n  const mergeStateResult = mergeStateFromMergers(\n    state,\n    {\n      ...INITIAL_VIS_STATE,\n      ...state.initialState\n    },\n    mergers,\n    // newDataIds,\n    postMergerPayload\n  );\n\n  // if all merged, kickup post merge process\n  // if not wait\n  return mergeStateResult.allMerged\n    ? postMergeUpdater(mergeStateResult.mergedState, postMergerPayload)\n    : mergeStateResult.mergedState;\n}\n\n/**\n * Add new dataset to `visState`, with option to load a map config along with the datasets\n */\nfunction postMergeUpdater(mergedState: VisState, postMergerPayload: PostMergerPayload): VisState {\n  const {newDataIds, options, layerMergers} = postMergerPayload;\n  const newFilters = mergedState.filters.filter(f =>\n    f.dataId.find(fDataId => newDataIds.includes(fDataId))\n  );\n  const datasetFiltered: string[] = uniq(\n    newFilters.reduce((accu, f) => [...accu, ...f.dataId], [] as string[])\n  );\n  const dataEmpty = newDataIds.length < 1;\n\n  let newLayers = !dataEmpty\n    ? mergedState.layers.filter(l => l.config.dataId && newDataIds.includes(l.config.dataId))\n    : [];\n\n  const newDataEntries = newDataIds.reduce(\n    (accu, id) => ({\n      ...accu,\n      [id]: mergedState.datasets[id]\n    }),\n    {}\n  );\n\n  if (!newLayers.length && (options || {}).autoCreateLayers !== false) {\n    // no layer merged, find defaults\n    const result = addDefaultLayers(mergedState, newDataEntries);\n    mergedState = result.state;\n    newLayers = result.newLayers;\n  }\n\n  if (mergedState.splitMaps.length) {\n    // if map is split, add new layers to splitMaps\n    newLayers = mergedState.layers.filter(\n      l => l.config.dataId && newDataIds.includes(l.config.dataId)\n    );\n    mergedState = {\n      ...mergedState,\n      splitMaps: addNewLayersToSplitMap(mergedState.splitMaps, newLayers)\n    };\n  }\n\n  // if no tooltips merged add default tooltips\n  newDataIds.forEach(dataId => {\n    const tooltipFields = mergedState.interactionConfig.tooltip.config.fieldsToShow[dataId];\n    // loading dataset: autoCreateTooltips is false and we don't want to run addDefaultTooltips when tooltipFields is empty\n    if (\n      options?.autoCreateTooltips !== false &&\n      (!Array.isArray(tooltipFields) || !tooltipFields.length)\n    ) {\n      // adding dataset: autoCreateTooltips is true\n      mergedState = addDefaultTooltips(mergedState, newDataEntries[dataId]);\n    }\n  });\n\n  const updatedDatasets = dataEmpty\n    ? Object.keys(mergedState.datasets)\n    : uniq(Object.keys(newDataEntries).concat(datasetFiltered));\n\n  let updatedState = updateAllLayerDomainData(mergedState, updatedDatasets, undefined);\n\n  // register layer animation domain,\n  // need to be called after layer data is calculated\n  updatedState = updateAnimationDomain(updatedState);\n\n  // try to process layerMergers after dataset+datasetMergers\n  updatedState =\n    layerMergers && layerMergers.length > 0\n      ? applyMergersUpdater(updatedState, {\n          mergers: layerMergers,\n          postMergerPayload: {...postMergerPayload, layerMergers: []}\n        })\n      : updatedState;\n\n  // center the map once the dataset is created\n  if (newLayers.length && (options || {}).centerMap) {\n    const bounds = findMapBounds(newLayers);\n    if (bounds) {\n      const fitBoundsTask = ACTION_TASK_FIT_BOUNDS().map(() => {\n        return fitMapBounds(bounds);\n      });\n      updatedState = withTask(updatedState, fitBoundsTask);\n    }\n  }\n\n  // need to center map here if we have new layers\n  return updatedState;\n}\n\n/**\n * Rename an existing dataset in `visState`\n * @memberof visStateUpdaters\n * @public\n */\nexport function renameDatasetUpdater(\n  state: VisState,\n  action: VisStateActions.RenameDatasetUpdaterAction\n): VisState {\n  return updateDatasetPropsUpdater(state, {dataId: action.dataId, props: {label: action.label}});\n}\n\nconst ALLOWED_UPDATE_DATASET_PROPS = ['label', 'color', 'metadata'];\n\n/**\n * Validates properties before updating the dataset.\n * Makes sure each property is in the allowed list\n * Makes sure color value is RGB\n * Performs deep merge when updating metadata\n */\nconst validateDatasetUpdateProps = (props, dataset) => {\n  const validatedProps = Object.entries(props).reduce((acc, entry) => {\n    const [key, value] = entry;\n    // is it allowed ?\n    if (!ALLOWED_UPDATE_DATASET_PROPS.includes(key)) {\n      return acc;\n    }\n\n    // if we are adding a color but it is not RGB we don't accept the value\n    // in the future as we add more props we should change this if into a switch\n    if (key === 'color' && !isRgbColor(value)) {\n      return acc;\n    }\n\n    // do we need deep merge ?\n    return {...acc, [key]: isPlainObject(value) ? deepmerge(dataset[key] || {}, value) : value};\n  }, {});\n\n  return validatedProps;\n};\n\n/**\n * Update Dataset props (label, color, meta). Do not use to update data or any related properties\n * @memberof visStateUpdaters\n * @public\n */\nexport function updateDatasetPropsUpdater(\n  state: VisState,\n  action: VisStateActions.UpdateDatasetPropsUpdaterAction\n): VisState {\n  const {dataId, props} = action;\n  const {datasets} = state;\n  const existing = datasets[dataId];\n\n  if (existing) {\n    const validatedProps = validateDatasetUpdateProps(props, existing);\n    //  validate props: just color for now\n    //  we only allow label, color and meta to be updated\n    // const newTable = copyTableAndUpdate(existing, validatedProps);\n    return {\n      ...state,\n      datasets: {\n        ...datasets,\n        [dataId]: copyTableAndUpdate(existing, validatedProps)\n      }\n    };\n  }\n\n  return state;\n}\n\n/**\n * When a user clicks on the specific map closing icon\n * the application will close the selected map\n * and will merge the remaining one with the global state\n * TODO: i think in the future this action should be called merge map layers with global settings\n * @param {Object} state `visState`\n * @param {Object} action action\n * @returns {Object} nextState\n */\nexport function closeSpecificMapAtIndex<S extends VisState>(\n  state: S,\n  action: MapStateActions.ToggleSplitMapUpdaterAction\n): S {\n  // retrieve layers meta data from the remaining map that we need to keep\n  const indexToRetrieve = 1 - action.payload;\n  const mapLayers = state.splitMaps[indexToRetrieve]?.layers;\n  const {layers} = state;\n\n  // update layer visibility\n  const newLayers = layers.map(layer =>\n    mapLayers && !mapLayers[layer.id] && layer.config.isVisible\n      ? layer.updateLayerConfig({\n          // if layer.id is not in mapLayers, it should be inVisible\n          isVisible: false\n        })\n      : layer\n  );\n\n  // delete map\n  return {\n    ...state,\n    layers: newLayers,\n    splitMaps: []\n  };\n}\n\n/**\n * Trigger file loading dispatch `addDataToMap` if succeed, or `loadFilesErr` if failed\n * @memberof visStateUpdaters\n * @public\n */\nexport const loadFilesUpdater = (\n  state: VisState,\n  action: VisStateActions.LoadFilesUpdaterAction\n): VisState => {\n  const {files, onFinish = loadFilesSuccess} = action;\n  if (!files.length) {\n    return state;\n  }\n\n  const fileLoadingProgress = Array.from(files).reduce(\n    (accu, f, i) => merge_(initialFileLoadingProgress(f, i))(accu),\n    {}\n  );\n\n  const fileLoading = {\n    fileCache: [],\n    filesToLoad: files,\n    onFinish\n  };\n\n  const nextState = merge_({fileLoadingProgress, fileLoading})(state);\n\n  return loadNextFileUpdater(nextState);\n};\n\n/**\n * Sucessfully loaded one file, move on to the next one\n * @memberof visStateUpdaters\n * @public\n */\nexport function loadFileStepSuccessUpdater(\n  state: VisState,\n  action: VisStateActions.LoadFileStepSuccessAction\n): VisState {\n  if (!state.fileLoading) {\n    return state;\n  }\n  const {fileName, fileCache} = action;\n  const {filesToLoad, onFinish} = state.fileLoading;\n  const stateWithProgress = updateFileLoadingProgressUpdater(state, {\n    fileName,\n    progress: {percent: 1, message: 'Done'}\n  });\n\n  // save processed file to fileCache\n  const stateWithCache = pick_('fileLoading')(merge_({fileCache}))(stateWithProgress);\n\n  return withTask(\n    stateWithCache,\n    DELAY_TASK(200).map(filesToLoad.length ? loadNextFile : () => onFinish(fileCache))\n  );\n}\n\n// withTask<T>(state: T, task: any): T\n\n/**\n *\n * @memberof visStateUpdaters\n * @public\n */\nexport function loadNextFileUpdater(state: VisState): VisState {\n  if (!state.fileLoading) {\n    return state;\n  }\n  const {filesToLoad} = state.fileLoading;\n  const [file, ...remainingFilesToLoad] = filesToLoad;\n\n  // save filesToLoad to state\n  const nextState = pick_('fileLoading')(merge_({filesToLoad: remainingFilesToLoad}))(state);\n\n  const stateWithProgress = updateFileLoadingProgressUpdater(nextState, {\n    fileName: file.name,\n    progress: {percent: 0, message: 'loading...'}\n  });\n\n  const {loaders, loadOptions} = state;\n  return withTask(\n    stateWithProgress,\n    makeLoadFileTask(\n      file,\n      nextState.fileLoading && nextState.fileLoading.fileCache,\n      loaders,\n      loadOptions\n    )\n  );\n}\n\nexport function makeLoadFileTask(file, fileCache, loaders: Loader[] = [], loadOptions = {}) {\n  return LOAD_FILE_TASK({file, fileCache, loaders, loadOptions}).bimap(\n    // prettier ignore\n    // success\n    gen =>\n      nextFileBatch({\n        gen,\n        fileName: file.name,\n        onFinish: result =>\n          processFileContent({\n            content: result,\n            fileCache\n          })\n      }),\n\n    // error\n    err => loadFilesErr(file.name, err)\n  );\n}\n\n/**\n *\n * @memberof visStateUpdaters\n * @public\n */\nexport function processFileContentUpdater(\n  state: VisState,\n  action: VisStateActions.ProcessFileContentUpdaterAction\n): VisState {\n  const {content, fileCache} = action.payload;\n\n  const stateWithProgress = updateFileLoadingProgressUpdater(state, {\n    fileName: content.fileName,\n    progress: {percent: 1, message: 'processing...'}\n  });\n\n  return withTask(\n    stateWithProgress,\n    PROCESS_FILE_DATA({content, fileCache}).bimap(\n      result => loadFileStepSuccess({fileName: content.fileName, fileCache: result}),\n      err => loadFilesErr(content.fileName, err)\n    )\n  );\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function parseProgress(prevProgress = {}, progress) {\n  // This happens when receiving query metadata or other cases we don't\n  // have an update for the user.\n  if (!progress || !progress.percent) {\n    return {};\n  }\n\n  return {\n    percent: progress.percent\n  };\n}\n\n/**\n * gets called with payload = AsyncGenerator<???>\n * @memberof visStateUpdaters\n * @public\n */\nexport const nextFileBatchUpdater = (\n  state: VisState,\n  {\n    payload: {gen, fileName, progress, accumulated, onFinish}\n  }: VisStateActions.NextFileBatchUpdaterAction\n): VisState => {\n  const stateWithProgress = updateFileLoadingProgressUpdater(state, {\n    fileName,\n    progress: parseProgress(state.fileLoadingProgress[fileName], progress)\n  });\n\n  return withTask(stateWithProgress, [\n    ...(getApplicationConfig().useArrowProgressiveLoading &&\n    fileName.endsWith('arrow') &&\n    accumulated?.data?.length > 0\n      ? [\n          PROCESS_FILE_DATA({content: accumulated, fileCache: []}).bimap(\n            result => loadFilesSuccess(result),\n            err => loadFilesErr(fileName, err)\n          )\n        ]\n      : []),\n    UNWRAP_TASK(gen.next()).bimap(\n      ({value, done}) => {\n        return done\n          ? onFinish(accumulated)\n          : nextFileBatch({\n              gen,\n              fileName,\n              progress: value.progress,\n              accumulated: value,\n              onFinish\n            });\n      },\n      err => loadFilesErr(fileName, err)\n    )\n  ]);\n};\n\n/**\n * Trigger loading file error\n * @memberof visStateUpdaters\n * @public\n */\nexport const loadFilesErrUpdater = (\n  state: VisState,\n  {error, fileName}: VisStateActions.LoadFilesErrUpdaterAction\n): VisState => {\n  // update ui with error message\n  Console.warn(error);\n  if (!state.fileLoading) {\n    return state;\n  }\n  const {filesToLoad, onFinish, fileCache} = state.fileLoading;\n\n  const nextState = updateFileLoadingProgressUpdater(state, {\n    fileName,\n    progress: {error}\n  });\n\n  // kick off next file or finish\n  return withTask(\n    nextState,\n    DELAY_TASK(200).map(filesToLoad.length ? loadNextFile : () => onFinish(fileCache))\n  );\n};\n\n/**\n * When select dataset for export, apply cpu filter to selected dataset\n * @memberof visStateUpdaters\n * @public\n */\nexport const applyCPUFilterUpdater = (\n  state: VisState,\n  {dataId}: VisStateActions.ApplyCPUFilterUpdaterAction\n): VisState => {\n  // apply cpuFilter\n  const dataIds = toArray(dataId);\n\n  return dataIds.reduce((accu, id) => filterDatasetCPU(accu, id), state);\n};\n\n/**\n * User input to update the info of the map\n * @memberof visStateUpdaters\n * @public\n */\nexport const setMapInfoUpdater = (\n  state: VisState,\n  action: VisStateActions.SetMapInfoUpdaterAction\n): VisState => ({\n  ...state,\n  mapInfo: {\n    ...state.mapInfo,\n    ...action.info\n  }\n});\n/**\n * Helper function to update All layer domain and layer data of state\n */\nexport function addDefaultLayers(\n  state: VisState,\n  datasets: Datasets\n): {state: VisState; newLayers: Layer[]} {\n  const empty: Layer[] = [];\n  const defaultLayers = Object.values(datasets).reduce((accu: Layer[], dataset) => {\n    const foundLayers = findDefaultLayer(dataset, state.layerClasses);\n    return foundLayers && foundLayers.length ? accu.concat(foundLayers) : accu;\n  }, empty);\n\n  return {\n    state: {\n      ...state,\n      layers: [...state.layers, ...defaultLayers],\n      layerOrder: [\n        // put new layers on top of old ones in reverse\n        ...getLayerOrderFromLayers(defaultLayers),\n        ...state.layerOrder\n      ]\n    },\n    newLayers: defaultLayers\n  };\n}\n\n/**\n * helper function to find default tooltips\n * @param {Object} state\n * @param {Object} dataset\n * @returns {Object} nextState\n */\nexport function addDefaultTooltips(state, dataset) {\n  const tooltipFields = findFieldsToShow({\n    ...dataset,\n    maxDefaultTooltips: state.maxDefaultTooltips\n  });\n  const merged = {\n    ...state.interactionConfig.tooltip.config.fieldsToShow,\n    ...tooltipFields\n  };\n\n  return set(['interactionConfig', 'tooltip', 'config', 'fieldsToShow'], merged, state);\n}\n\nexport function initialFileLoadingProgress(file, index) {\n  const fileName = file.name || `Untitled File ${index}`;\n  return {\n    [fileName]: {\n      // percent of current file\n      percent: 0,\n      message: '',\n      fileName,\n      error: null\n    }\n  };\n}\n\nexport function updateFileLoadingProgressUpdater(state, {fileName, progress}) {\n  // @ts-expect-error\n  return pick_('fileLoadingProgress')(pick_(fileName)(merge_(progress)))(state);\n}\n/**\n * Helper function to update layer domains for an array of datasets\n */\nexport function updateAllLayerDomainData<S extends VisState>(\n  state: S,\n  dataId: string | string[],\n  updatedFilter?: Filter\n): S {\n  const dataIds = typeof dataId === 'string' ? [dataId] : dataId;\n  const newLayers: Layer[] = [];\n  const newLayerData: any[] = [];\n\n  state.layers.forEach((oldLayer, i) => {\n    if (oldLayer.config.dataId && dataIds.includes(oldLayer.config.dataId)) {\n      // No need to recalculate layer domain if filter has fixed domain\n      const newLayer =\n        updatedFilter && updatedFilter.fixedDomain\n          ? oldLayer\n          : oldLayer.updateLayerDomain(state.datasets, updatedFilter);\n\n      const {layerData, layer} = calculateLayerData(newLayer, state, state.layerData[i]);\n\n      newLayers.push(layer);\n      newLayerData.push(layerData);\n    } else {\n      newLayers.push(oldLayer);\n      newLayerData.push(state.layerData[i]);\n    }\n  });\n\n  const newState = {\n    ...state,\n    layers: newLayers,\n    layerData: newLayerData\n  };\n\n  return newState;\n}\n\nexport function updateAnimationDomain<S extends VisState>(state: S): S {\n  // merge all animatable layer domain and update global config\n  const animatableLayers = getAnimatableVisibleLayers(state.layers);\n\n  if (!animatableLayers.length) {\n    return {\n      ...state,\n      animationConfig: {\n        ...state.animationConfig,\n        domain: null,\n        isAnimating: false,\n        timeSteps: null,\n        defaultTimeFormat: null\n      }\n    };\n  }\n\n  const layerDomains = animatableLayers.map(l => l.config.animation.domain || []);\n  // @ts-ignore\n  const mergedDomain = mergeTimeDomains(layerDomains);\n  const defaultTimeFormat = getTimeWidgetTitleFormatter(mergedDomain);\n\n  // merge timeSteps\n  let mergedTimeSteps: number[] | null = uniq<number>(\n    animatableLayers.reduce((accu, layer) => {\n      accu.push(...(layer.config.animation.timeSteps || []));\n      return accu;\n    }, [])\n  ).sort();\n\n  mergedTimeSteps = mergedTimeSteps.length ? mergedTimeSteps : null;\n\n  // TODO: better handling of duration calculation\n  const duration = mergedTimeSteps\n    ? (BASE_SPEED * (1000 / FPS)) / mergedTimeSteps.length / (state.animationConfig.speed || 1)\n    : null;\n\n  const nextState = {\n    ...state,\n    animationConfig: {\n      ...state.animationConfig,\n      domain: mergedDomain,\n      defaultTimeFormat,\n      duration,\n      timeSteps: mergedTimeSteps\n    }\n  };\n\n  // reset currentTime based on new domain\n  const syncedFilter = state.filters?.find(f => (f as TimeRangeFilter).syncedWithLayerTimeline) as\n    | TimeRangeFilter\n    | undefined;\n\n  // if synced filter exist wee need to merge animationConfig and filter domains\n  // and validate the current time against the new merged domain\n  const newAnimationDomain = syncedFilter\n    ? mergeTimeDomains([mergedDomain, syncedFilter.domain])\n    : mergedDomain;\n  const currentTime = isInRange(state.animationConfig.currentTime, newAnimationDomain)\n    ? state.animationConfig.currentTime\n    : newAnimationDomain[0];\n\n  if (currentTime !== state.animationConfig.currentTime) {\n    // if currentTime changed, need to call animationTimeUpdater to re call formatLayerData\n    return setLayerAnimationTimeUpdater(nextState, {value: currentTime});\n  }\n\n  return nextState;\n}\n\n/**\n * Update the status of the editor\n * @memberof visStateUpdaters\n */\nexport const setEditorModeUpdater = (\n  state: VisState,\n  {mode}: VisStateActions.SetEditorModeUpdaterAction\n): VisState => ({\n  ...state,\n  editor: {\n    ...state.editor,\n    mode,\n    selectedFeature: null\n  }\n});\n\n// const featureToFilterValue = (feature) => ({...feature, id: feature.id});\n/**\n * Update editor features\n * @memberof visStateUpdaters\n */\nexport function setFeaturesUpdater(\n  state: VisState,\n  {features = []}: VisStateActions.SetFeaturesUpdaterAction\n): VisState {\n  const lastFeature = features.length && features[features.length - 1];\n\n  const newState = {\n    ...state,\n    editor: {\n      ...state.editor,\n      // only save none filter features to editor\n      features: features.filter(f => !getFilterIdInFeature(f)),\n      mode: lastFeature && lastFeature.properties?.isClosed ? EDITOR_MODES.EDIT : state.editor.mode\n    }\n  };\n\n  // Retrieve existing feature\n  const {selectedFeature} = state.editor;\n\n  // If no feature is selected we can simply return since no operations\n  if (!selectedFeature) {\n    return newState;\n  }\n\n  // TODO: check if the feature has changed\n  const feature = features.find(f => f.id === selectedFeature.id);\n\n  // if feature is part of a filter\n  const filterId = feature && getFilterIdInFeature(feature);\n  if (filterId && feature) {\n    // add bbox for polygon filter to speed up filtering\n    if (feature.properties) feature.properties.bbox = bbox(feature);\n    const featureValue = featureToFilterValue(feature, filterId);\n    const filterIdx = state.filters.findIndex(fil => fil.id === filterId);\n    // @ts-ignore\n    return setFilterUpdater(newState, {\n      idx: filterIdx,\n      prop: 'value',\n      value: featureValue\n    });\n  }\n\n  return newState;\n}\n\n/**\n * Set the current selected feature\n * @memberof uiStateUpdaters\n */\nexport const setSelectedFeatureUpdater = (\n  state: VisState,\n  {feature, selectionContext}: VisStateActions.SetSelectedFeatureUpdaterAction\n): VisState => {\n  // add bbox for polygon filter to speed up filtering\n  let selectedFeature = feature;\n  if (feature?.properties) {\n    selectedFeature = {\n      ...feature,\n      properties: {\n        ...feature.properties,\n        bbox: bbox(feature)\n      }\n    };\n  }\n  return {\n    ...state,\n    editor: {\n      ...state.editor,\n      selectedFeature,\n      selectionContext\n    }\n  };\n};\n\n/**\n * Delete existing feature from filters\n * @memberof visStateUpdaters\n */\nexport function deleteFeatureUpdater(\n  state: VisState,\n  {feature}: VisStateActions.DeleteFeatureUpdaterAction\n): VisState {\n  if (!feature) {\n    return state;\n  }\n\n  const newState = {\n    ...state,\n    editor: {\n      ...state.editor,\n      selectedFeature: null\n    }\n  };\n\n  if (getFilterIdInFeature(feature)) {\n    const filterIdx = newState.filters.findIndex(f => f.id === getFilterIdInFeature(feature));\n\n    return filterIdx > -1 ? removeFilterUpdater(newState, {idx: filterIdx}) : newState;\n  }\n\n  // modify editor object\n  const newEditor = {\n    ...state.editor,\n    features: state.editor.features.filter(f => f.id !== feature.id),\n    selectedFeature: null\n  };\n\n  return {\n    ...state,\n    editor: newEditor\n  };\n}\n\n/**\n * Toggle feature as layer filter\n * @memberof visStateUpdaters\n */\nexport function setPolygonFilterLayerUpdater(\n  state: VisState,\n  payload: VisStateActions.SetPolygonFilterLayerUpdaterAction\n): VisState {\n  const {layer, feature} = payload;\n  const filterId = getFilterIdInFeature(feature);\n\n  // let newFilter = null;\n  let filterIdx;\n  let newLayerId = [layer.id];\n  let newState = state;\n  // If polygon filter already exists, we need to find out if the current layer is already included\n  if (filterId) {\n    filterIdx = state.filters.findIndex(f => f.id === filterId);\n\n    if (!state.filters[filterIdx]) {\n      // what if filter doesn't exist?... not possible.\n      // because features in the editor is passed in from filters and editors.\n      // but we will move this feature back to editor just in case\n      const noneFilterFeature = {\n        ...feature,\n        properties: {\n          ...feature.properties,\n          filterId: null\n        }\n      };\n\n      return {\n        ...state,\n        editor: {\n          ...state.editor,\n          features: [...state.editor.features, noneFilterFeature],\n          selectedFeature: noneFilterFeature\n        }\n      };\n    }\n    const filter = state.filters[filterIdx];\n    const {layerId = []} = filter;\n    const isLayerIncluded = layerId.includes(layer.id);\n\n    newLayerId = isLayerIncluded\n      ? // if layer is included, remove it\n        layerId.filter(l => l !== layer.id)\n      : [...layerId, layer.id];\n  } else {\n    // if we haven't create the polygon filter, create it\n    const newFilter = generatePolygonFilter([], feature);\n    filterIdx = state.filters.length;\n\n    // add feature, remove feature from eidtor\n    newState = {\n      ...state,\n      filters: [...state.filters, newFilter],\n      editor: {\n        ...state.editor,\n        features: state.editor.features.filter(f => f.id !== feature.id),\n        selectedFeature: newFilter.value\n      }\n    };\n  }\n\n  return setFilterUpdater(newState, {\n    idx: filterIdx,\n    prop: 'layerId',\n    value: newLayerId\n  });\n}\n\n/**\n * @memberof visStateUpdaters\n * @public\n */\nexport function sortTableColumnUpdater(\n  state: VisState,\n  {dataId, column, mode}: VisStateActions.SortTableColumnUpdaterAction\n): VisState {\n  const dataset = state.datasets[dataId];\n  if (!dataset) {\n    return state;\n  }\n  let sortMode = mode;\n  if (!sortMode) {\n    const currentMode = get(dataset, ['sortColumn', column]);\n    // @ts-ignore - should be fixable in a TS file\n    sortMode = currentMode\n      ? Object.keys(SORT_ORDER).find(m => m !== currentMode)\n      : SORT_ORDER.ASCENDING;\n  }\n\n  const sorted = sortDatasetByColumn(dataset, column, sortMode);\n  return set(['datasets', dataId], sorted, state);\n}\n\n/**\n * @memberof visStateUpdaters\n * @public\n */\nexport function pinTableColumnUpdater(\n  state: VisState,\n  {dataId, column}: VisStateActions.PinTableColumnUpdaterAction\n): VisState {\n  const dataset = state.datasets[dataId];\n  if (!dataset) {\n    return state;\n  }\n  const newDataset = pinTableColumns(dataset, column);\n\n  return set(['datasets', dataId], newDataset, state);\n}\n\n/**\n * Copy column content as strings\n * @memberof visStateUpdaters\n * @public\n */\nexport function copyTableColumnUpdater(\n  state: VisState,\n  {dataId, column}: VisStateActions.CopyTableColumnUpdaterAction\n): VisState {\n  const dataset = state.datasets[dataId];\n  if (!dataset) {\n    return state;\n  }\n  const fieldIdx = dataset.fields.findIndex(f => f.name === column);\n  if (fieldIdx < 0) {\n    return state;\n  }\n  const {type} = dataset.fields[fieldIdx];\n  const text = dataset.dataContainer\n    .map(row => parseFieldValue(row.valueAt(fieldIdx), type), true)\n    .join('\\n');\n\n  copy(text);\n\n  return state;\n}\n\n/**\n * Set display format from columns from user selection\n * @memberof visStateUpdaters\n * @public\n */\nexport function setColumnDisplayFormatUpdater(\n  state: VisState,\n  {dataId, formats}: VisStateActions.SetColumnDisplayFormatUpdaterAction\n): VisState {\n  const dataset = state.datasets[dataId];\n  if (!dataset) {\n    return state;\n  }\n  let newFields = dataset.fields;\n  Object.keys(formats).forEach(column => {\n    const fieldIdx = dataset.fields.findIndex(f => f.name === column);\n    if (fieldIdx >= 0) {\n      const displayFormat = formats[column];\n      const field = newFields[fieldIdx];\n      newFields = swap_(merge_({displayFormat})(field) as {id: string})(\n        newFields as {id: string}[]\n      ) as Field[];\n    }\n  });\n\n  const newDataset = copyTableAndUpdate(dataset, {fields: newFields as Field[]});\n  let newState = pick_('datasets')(merge_({[dataId]: newDataset}))(state);\n\n  // update colorField displayFormat\n  newState = {\n    ...newState,\n    layers: newState.layers.map(layer =>\n      layer.config?.colorField?.name && layer.config.colorField.name in formats\n        ? layer.updateLayerConfig({\n            colorField: {\n              ...layer.config.colorField,\n              displayFormat: formats[layer.config.colorField.name]\n            }\n          })\n        : layer\n    )\n  };\n\n  return newState;\n}\n\n/**\n * Update editor\n */\nexport function toggleEditorVisibilityUpdater(\n  state: VisState,\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  action: VisStateActions.ToggleEditorVisibilityUpdaterAction\n): VisState {\n  return {\n    ...state,\n    editor: {\n      ...state.editor,\n      visible: !state.editor.visible\n    }\n  };\n}\n\nexport function setFilterAnimationTimeConfigUpdater(\n  state: VisState,\n  {idx, config}: VisStateActions.SetFilterAnimationTimeConfigAction\n): VisState {\n  const oldFilter = state.filters[idx];\n  if (!oldFilter) {\n    Console.error(`filters.${idx} is undefined`);\n    return state;\n  }\n  if (oldFilter.type !== FILTER_TYPES.timeRange) {\n    Console.error(\n      `setFilterAnimationTimeConfig can only be called to update a time filter. check filter.type === 'timeRange'`\n    );\n    return state;\n  }\n\n  const updates = checkTimeConfigArgs(config);\n\n  return pick_('filters')(swap_(merge_(updates)(oldFilter)))(state);\n}\n\nfunction checkTimeConfigArgs(config) {\n  const allowed = ['timeFormat', 'timezone'];\n  return Object.keys(config).reduce((accu, prop) => {\n    if (!allowed.includes(prop)) {\n      Console.error(\n        `setLayerAnimationTimeConfig takes timeFormat and/or timezone as options, found ${prop}`\n      );\n      return accu;\n    }\n\n    // here we are NOT checking if timezone or timeFormat input is valid\n    accu[prop] = config[prop];\n    return accu;\n  }, {});\n}\n\n/**\n * Update editor\n */\nexport function setLayerAnimationTimeConfigUpdater(\n  state: VisState,\n  {config}: VisStateActions.SetLayerAnimationTimeConfigAction\n): VisState {\n  if (!config) {\n    return state;\n  }\n  const updates = checkTimeConfigArgs(config);\n  return pick_('animationConfig')(merge_(updates))(state);\n}\n\n/**\n * Update editor\n */\nexport function layerFilteredItemsChangeUpdater<S extends VisState>(\n  state: S,\n  action: VisStateActions.LayerFilteredItemsChangeAction\n): S {\n  const {event, layer} = action;\n  const {id: deckglLayerId, count} = event;\n  if (!layer) {\n    Console.warn(`layerFilteredItems layer doesnt exists`);\n    return state;\n  }\n  if (layer.filteredItemCount?.[deckglLayerId] === count) {\n    return state;\n  }\n\n  layer.filteredItemCount = {\n    ...layer.filteredItemCount,\n    [deckglLayerId]: count\n  };\n\n  return {\n    ...state,\n    layers: swap_(layer)(state.layers)\n  };\n}\n\n/**\n * Update WMS feature info and pin tooltip\n */\nexport function wmsFeatureInfoUpdater<S extends VisState>(\n  state: S,\n  action: VisStateActions.WMSFeatureInfoAction\n): S {\n  const {layer, featureInfo, coordinate} = action;\n  if (!layer) {\n    console.warn(`WMS feature info layer doesn't exist`);\n    return state;\n  }\n\n  // Create a clicked object similar to layerClickUpdater to pin the tooltip\n  const clicked = featureInfo\n    ? {\n        picked: true,\n        object: {\n          wmsFeatureInfo: featureInfo\n        },\n        coordinate: coordinate || [0, 0],\n        layer: {\n          props: {\n            idx: state.layers.findIndex(l => l.id === layer.id),\n            id: layer.id\n          }\n        }\n      }\n    : null;\n\n  return {\n    ...state,\n    clicked\n  };\n}\n\n// eslint-disable-next-line max-statements\nexport function syncTimeFilterWithLayerTimelineUpdater<S extends VisState>(\n  state: S,\n  action: VisStateActions.SyncTimeFilterWithLayerTimelineAction\n): S {\n  const {idx: filterIdx, enable = false} = action;\n\n  const filter = state.filters[filterIdx] as TimeRangeFilter;\n\n  let newState = state;\n  let newFilter = filter;\n\n  // if we enable sync we are going to merge filter and animationConfig domains and store into filter.domain\n  if (enable) {\n    const animatableLayers = getAnimatableVisibleLayers(newState.layers);\n    // if no animatableLayers are present we simply return\n    if (!animatableLayers.length) {\n      return newState;\n    }\n\n    const intervalBasedAnimationLayers = getIntervalBasedAnimationLayers(animatableLayers);\n    const hasIntervalBasedAnimationLayer = Boolean(intervalBasedAnimationLayers.length);\n\n    const newFilterDomain = mergeTimeDomains([filter.domain, newState.animationConfig.domain]);\n\n    // we only update animationWindow if we have interval based animation layers with defined intervals and the current filter animation window is not interval\n    if (hasIntervalBasedAnimationLayer) {\n      if (filter.animationWindow !== ANIMATION_WINDOW.interval) {\n        newState = setFilterAnimationWindowUpdater(newState, {\n          id: filter.id,\n          animationWindow: ANIMATION_WINDOW.interval\n        });\n      }\n\n      newFilter = newState.filters[filterIdx] as TimeRangeFilter;\n\n      // adjust time filter interval\n      newFilter = adjustTimeFilterInterval(newState, newFilter);\n\n      // replace filter in state with newFilter\n      newState = {\n        ...newState,\n        filters: swap_<Filter>(newFilter)(newState.filters)\n      };\n    }\n\n    newFilter = newState.filters[filterIdx] as TimeRangeFilter;\n\n    // adjust value based on new domain\n    const newFilterValue = adjustValueToFilterDomain(\n      newFilter.animationWindow === ANIMATION_WINDOW.interval\n        ? [newFilterDomain[0], newFilterDomain[0]]\n        : newFilterDomain,\n      {...newFilter, domain: newFilterDomain}\n    );\n\n    newState = setFilterUpdater(newState, {\n      idx: filterIdx,\n      prop: 'value',\n      value: newFilterValue\n    });\n\n    newFilter = {\n      ...(newState.filters[filterIdx] as TimeRangeFilter),\n      syncedWithLayerTimeline: true\n    };\n\n    // replace filter in state with newFilter\n    newState = {\n      ...newState,\n      filters: swap_<Filter>(newFilter)(newState.filters)\n    };\n\n    newState = setTimeFilterTimelineModeUpdater(newState, {\n      id: newFilter.id,\n      mode: getSyncAnimationMode(newFilter)\n    });\n\n    newFilter = newState.filters[filterIdx] as TimeRangeFilter;\n\n    // set the animation config value to match filter value\n    return setLayerAnimationTimeUpdater(newState, {\n      value: newFilter.value[newFilter.syncTimelineMode]\n    });\n  }\n\n  // set domain and step\n  newFilter = {\n    ...filter,\n    syncedWithLayerTimeline: false\n  };\n\n  // replace filter in state with newFilter\n  newState = {\n    ...newState,\n    filters: swap_<Filter>(newFilter)(newState.filters)\n  };\n\n  // reset sync timeline mode\n  newState = setTimeFilterTimelineModeUpdater(newState, {\n    id: newFilter.id,\n    mode: SYNC_TIMELINE_MODES.end\n  });\n\n  newFilter = newState.filters[filterIdx] as TimeRangeFilter;\n\n  // reset filter value\n  const newFilterValue = adjustValueToFilterDomain(newFilter.domain, newFilter);\n\n  newState = setFilterUpdater(newState, {\n    idx: filterIdx,\n    prop: 'value',\n    value: newFilterValue\n  });\n\n  newState = setTimeFilterTimelineModeUpdater(newState, {\n    id: newFilter.id,\n    mode: getSyncAnimationMode(newFilter)\n  });\n\n  // reset animation config current time to\n  return setLayerAnimationTimeUpdater(newState, {\n    value: newState.animationConfig.domain?.[0] ?? null\n  });\n}\n\nexport function setTimeFilterTimelineModeUpdater<S extends VisState>(\n  state: S,\n  action: VisStateActions.setTimeFilterSyncTimelineModeAction\n) {\n  const {id: filterId, mode: syncTimelineMode} = action;\n\n  const filterIdx = state.filters.findIndex(f => f.id === filterId);\n  if (filterIdx === -1) {\n    return state;\n  }\n\n  const filter = state.filters[filterIdx] as TimeRangeFilter;\n\n  if (!validateSyncAnimationMode(filter, syncTimelineMode)) {\n    return state;\n  }\n\n  const newFilter = {\n    ...filter,\n    syncTimelineMode\n  };\n\n  const newState = {\n    ...state,\n    filters: swap_<Filter>(newFilter)(state.filters)\n  };\n\n  return adjustAnimationConfigWithFilter(newState, filterIdx);\n}\n\n/**\n * Update state of the loading indicator.\n * @memberof visStateUpdaters\n * @param state visState\n * @param action\n * @param action.payload Payload with change of number of active loading actions.\n * @returns nextState\n * @public\n */\nexport const setLoadingIndicatorUpdater = (\n  state: VisState,\n  {payload: {change}}: {payload: SetLoadingIndicatorPayload}\n): VisState => {\n  let {loadingIndicatorValue} = state;\n  if (!loadingIndicatorValue) {\n    loadingIndicatorValue = 0;\n  }\n\n  return {\n    ...state,\n    loadingIndicatorValue: Math.max(loadingIndicatorValue + change, 0)\n  };\n};\n\nfunction adjustAnimationConfigWithFilter<S extends VisState>(state: S, filterIdx: number): S {\n  const filter = state.filters[filterIdx];\n  if ((filter as TimeRangeFilter).syncedWithLayerTimeline) {\n    const timelineValue = getTimelineValueFromFilter(filter);\n    const value = state.animationConfig.timeSteps\n      ? snapToMarks(timelineValue, state.animationConfig.timeSteps)\n      : timelineValue;\n    return setLayerAnimationTimeUpdater(state, {value});\n  }\n  return state;\n}\n\nfunction getTimelineValueFromFilter(filter) {\n  return filter.value[filter.syncTimelineMode];\n}\n\nfunction getSyncAnimationMode(filter: TimeRangeFilter) {\n  if (filter.animationWindow === ANIMATION_WINDOW.free) {\n    return filter.syncTimelineMode ?? SYNC_TIMELINE_MODES.end;\n  }\n\n  return SYNC_TIMELINE_MODES.end;\n}\n\nfunction validateSyncAnimationMode(filter: TimeRangeFilter, newMode: number) {\n  return !(\n    filter.animationWindow !== ANIMATION_WINDOW.free && newMode === SYNC_TIMELINE_MODES.start\n  );\n}\n\nfunction adjustTimeFilterInterval(state, filter) {\n  const intervalBasedAnimationLayers = getIntervalBasedAnimationLayers(state.layers);\n\n  let interval: string | null = null;\n  if (intervalBasedAnimationLayers.length > 0) {\n    // @ts-ignore\n    const intervalIndex = intervalBasedAnimationLayers.reduce((currentIndex, l) => {\n      if (l.meta.targetTimeInterval) {\n        const newIndex = TIME_INTERVALS_ORDERED.findIndex(i => i === l.meta.targetTimeInterval);\n        return newIndex > -1 && newIndex < currentIndex ? newIndex : currentIndex;\n      }\n    }, TIME_INTERVALS_ORDERED.length - 1);\n    // @ts-ignore\n    const hexTileInterval = TIME_INTERVALS_ORDERED[intervalIndex];\n    interval = LayerToFilterTimeInterval[hexTileInterval];\n  }\n\n  if (!interval) {\n    return filter;\n  }\n\n  // adjust filter\n  const timeFormat = getDefaultTimeFormat(interval);\n  const updatedPlotType = {...filter.plotType, interval, timeFormat};\n  const newFilter = updateTimeFilterPlotType(filter, updatedPlotType, state.datasets);\n  return adjustValueToAnimationWindow(state, newFilter);\n}\n\n// Find dataId from a saved visState property:\n// layers, filters, interactions, layerBlending, overlayBlending, splitMaps, animationConfig, editor\n// replace it with another dataId\nfunction defaultReplaceParentDatasetIds(value: any, dataId: string, dataIdToReplace: string) {\n  if (Array.isArray(value)) {\n    // for layers, filters, call defaultReplaceParentDatasetIds on each item in array\n    const replaced = value\n      .map(v => defaultReplaceParentDatasetIds(v, dataId, dataIdToReplace))\n      .filter(d => d);\n    return replaced.length ? replaced : null;\n  }\n  if (typeof value.dataId === 'string' && value.dataId === dataId) {\n    // others\n    return {\n      ...value,\n      dataId: dataIdToReplace\n    };\n  } else if (Array.isArray(value.dataId) && value.dataId.includes(dataId)) {\n    // filter\n    return {\n      ...value,\n      dataId: value.dataId.map(d => (d === dataId ? dataIdToReplace : d))\n    };\n  } else if (value.config?.dataId && value.config?.dataId === dataId) {\n    // layer\n    return {\n      ...value,\n      config: {\n        ...value.config,\n        dataId: dataIdToReplace\n      }\n    };\n  } else if (isObject(value) && Object.prototype.hasOwnProperty.call(value, dataId)) {\n    // for value saved as {[dataId]: {...}}\n    return {[dataIdToReplace]: value[dataId]};\n  }\n\n  return null;\n}\n\n// Find datasetIds derived a saved visState Property;\nfunction findChildDatasetIds(value) {\n  if (Array.isArray(value)) {\n    // for layers, filters, call defaultReplaceParentDatasetIds on each item in array\n    const childDataIds = value.map(findChildDatasetIds).filter(d => d);\n    return childDataIds.length ? childDataIds : null;\n  }\n\n  // child data id usually stores in the derived dataset info\n  return value?.newDataset?.info.id || null;\n}\n\n// moved unmerged layers, filters, interactions\nfunction moveValueToBeMerged(state, propValues, {prop, toMergeProp, saveUnmerged}) {\n  // remove prop value from state\n  // TODO: should we add remove updater to merger as well?\n  if (!propValues) {\n    return state;\n  }\n  const stateRemoved =\n    prop === 'layers'\n      ? propValues.reduce((accu, propValue) => removeLayerUpdater(accu, {id: propValue.id}), state)\n      : Array.isArray(state[prop])\n      ? {\n          ...state,\n          [prop]: state[prop].filter(p => !propValues.find(propValue => p.id === propValue.id))\n        }\n      : // if not array, we won't remove it, remove dataset should handle it\n        state;\n\n  // move to stateToBeMerged\n  const toBeMerged = {\n    [toMergeProp]: saveUnmerged\n      ? // call merge saveUnmerged method\n        saveUnmerged(stateRemoved, propValues)\n      : // if toMergeProp is araay, append to it\n      Array.isArray(stateRemoved[toMergeProp])\n      ? [...stateRemoved[toMergeProp], ...propValues]\n      : // save propValues to toMerge\n      isObject(stateRemoved[toMergeProp])\n      ? {\n          ...stateRemoved[toMergeProp],\n          ...propValues\n        }\n      : stateRemoved[toMergeProp]\n  };\n\n  return {\n    ...stateRemoved,\n    ...toBeMerged\n  };\n}\n\nfunction replaceDatasetAndDeps<T extends VisState>(\n  state: T,\n  dataId: string,\n  dataIdToUse: string\n): T {\n  return compose_<T>([\n    apply_(replaceDatasetDepsInState, {dataId, dataIdToUse}),\n    apply_(removeDatasetUpdater, {dataId})\n  ])(state);\n}\n\nexport function prepareStateForDatasetReplace<T extends VisState>(\n  state: T,\n  dataId: string,\n  dataIdToUse: string\n): T {\n  const serializedState = serializeVisState(state, state.schema);\n  const nextState = replaceDatasetAndDeps(state, dataId, dataIdToUse);\n  // make a copy of layerOrder, because layer id will be removed from it by calling removeLayerUpdater\n  const preserveLayerOrder = [...state.layerOrder];\n\n  // preserve dataset order\n  nextState.preserveDatasetOrder = Object.keys(state.datasets).map(d =>\n    d === dataId ? dataIdToUse : d\n  );\n\n  // preserveLayerOrder\n  if (nextState.layerToBeMerged?.length) {\n    // copy split maps to be merged, because it will be reset in remove layer\n    nextState.splitMapsToBeMerged = serializedState?.splitMaps ?? [];\n    nextState.layerOrder = [...preserveLayerOrder];\n  }\n\n  return nextState;\n}\n\nexport function replaceDatasetDepsInState<T extends VisState>(\n  state: T,\n  {dataId, dataIdToUse}: {dataId: string; dataIdToUse: string}\n): T {\n  const serializedState = serializeVisState(state, state.schema);\n\n  const nextState = state.mergers.reduce(\n    (\n      accuState,\n      {prop, toMergeProp, replaceParentDatasetIds, getChildDatasetIds, saveUnmerged, preserveOrder}\n    ) => {\n      // get dataset ids that are depends on this dataset\n      const props = toArray(prop);\n      const toMergeProps = toArray(toMergeProp);\n      const savedProps = serializedState ? props.map(p => serializedState[p]) : [];\n\n      let replacedState = accuState;\n      savedProps.forEach((propValue, i) => {\n        const mergerOptions = {\n          prop: props[i],\n          toMergeProp: toMergeProps[i],\n          getChildDatasetIds,\n          saveUnmerged\n        };\n\n        const replacedItem =\n          replaceParentDatasetIds?.(propValue, dataId, dataIdToUse) ||\n          defaultReplaceParentDatasetIds(propValue, dataId, dataIdToUse);\n        replacedState = replacedItem\n          ? replacePropValueInState(replacedState, replacedItem, mergerOptions)\n          : replacedState;\n\n        if (\n          mergerOptions.toMergeProp !== undefined &&\n          replacedState[mergerOptions.toMergeProp]?.length &&\n          preserveOrder\n        ) {\n          replacedState[preserveOrder] = propValue.map(item => item.id);\n        }\n      });\n\n      return replacedState;\n    },\n    state\n  );\n\n  return nextState;\n}\n\nfunction replacePropValueInState(\n  state,\n  replacedItem,\n  {prop, toMergeProp, getChildDatasetIds, saveUnmerged}\n) {\n  // prop is depends on the dataset to be replaced\n  // remove prop from state, and move it to toBeMerged\n  let nextState = moveValueToBeMerged(state, replacedItem, {prop, toMergeProp, saveUnmerged});\n  const childDataIds = getChildDatasetIds?.(replacedItem) || findChildDatasetIds(replacedItem);\n\n  if (childDataIds) {\n    nextState = toArray(childDataIds).reduce((accu, childDataId) => {\n      // shouldn't need to change child dataset id,\n      // but still need to move out of state and merge back in\n      return replaceDatasetAndDeps(accu, childDataId, childDataId);\n    }, nextState);\n  }\n  return nextState;\n}\n"
  },
  {
    "path": "src/reducers/src/vis-state.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ActionTypes} from '@kepler.gl/actions';\nimport {handleActions} from 'redux-actions';\nimport * as visStateUpdaters from './vis-state-updaters';\n\n/**\n * Important: Do not rename `actionHandler` or the assignment pattern of property value.\n * It is used to generate documentation\n */\nconst actionHandler = {\n  [ActionTypes.ADD_FILTER]: visStateUpdaters.addFilterUpdater,\n\n  [ActionTypes.CREATE_OR_UPDATE_FILTER]: visStateUpdaters.createOrUpdateFilterUpdater,\n\n  [ActionTypes.ADD_LAYER]: visStateUpdaters.addLayerUpdater,\n\n  [ActionTypes.APPLY_FILTER_CONFIG]: visStateUpdaters.applyFilterConfigUpdater,\n\n  [ActionTypes.APPLY_LAYER_CONFIG]: visStateUpdaters.applyLayerConfigUpdater,\n\n  [ActionTypes.DUPLICATE_LAYER]: visStateUpdaters.duplicateLayerUpdater,\n\n  [ActionTypes.SET_FILTER_VIEW]: visStateUpdaters.setFilterViewUpdater,\n\n  [ActionTypes.INTERACTION_CONFIG_CHANGE]: visStateUpdaters.interactionConfigChangeUpdater,\n\n  [ActionTypes.LAYER_CLICK]: visStateUpdaters.layerClickUpdater,\n\n  [ActionTypes.LAYER_CONFIG_CHANGE]: visStateUpdaters.layerConfigChangeUpdater,\n\n  [ActionTypes.LAYER_SET_IS_VALID]: visStateUpdaters.layerSetIsValidUpdater,\n\n  [ActionTypes.LAYER_HOVER]: visStateUpdaters.layerHoverUpdater,\n\n  [ActionTypes.LAYER_TYPE_CHANGE]: visStateUpdaters.layerTypeChangeUpdater,\n\n  [ActionTypes.LAYER_VIS_CONFIG_CHANGE]: visStateUpdaters.layerVisConfigChangeUpdater,\n\n  [ActionTypes.LAYER_TOGGLE_VISIBILITY]: visStateUpdaters.layerToggleVisibilityUpdater,\n\n  [ActionTypes.LAYER_TEXT_LABEL_CHANGE]: visStateUpdaters.layerTextLabelChangeUpdater,\n\n  [ActionTypes.LAYER_VISUAL_CHANNEL_CHANGE]: visStateUpdaters.layerVisualChannelChangeUpdater,\n\n  [ActionTypes.LAYER_COLOR_UI_CHANGE]: visStateUpdaters.layerColorUIChangeUpdater,\n\n  [ActionTypes.TOGGLE_LAYER_ANIMATION]: visStateUpdaters.toggleLayerAnimationUpdater,\n\n  [ActionTypes.TOGGLE_LAYER_ANIMATION_CONTROL]: visStateUpdaters.toggleLayerAnimationControlUpdater,\n\n  [ActionTypes.LOAD_FILES]: visStateUpdaters.loadFilesUpdater,\n\n  [ActionTypes.LOAD_FILES_ERR]: visStateUpdaters.loadFilesErrUpdater,\n\n  [ActionTypes.LOAD_NEXT_FILE]: visStateUpdaters.loadNextFileUpdater,\n\n  [ActionTypes.LOAD_FILE_STEP_SUCCESS]: visStateUpdaters.loadFileStepSuccessUpdater,\n\n  [ActionTypes.MAP_CLICK]: visStateUpdaters.mapClickUpdater,\n\n  [ActionTypes.MOUSE_MOVE]: visStateUpdaters.mouseMoveUpdater,\n\n  [ActionTypes.RECEIVE_MAP_CONFIG]: visStateUpdaters.receiveMapConfigUpdater,\n\n  [ActionTypes.REMOVE_DATASET]: visStateUpdaters.removeDatasetUpdater,\n\n  [ActionTypes.REMOVE_FILTER]: visStateUpdaters.removeFilterUpdater,\n\n  [ActionTypes.REMOVE_LAYER]: visStateUpdaters.removeLayerUpdater,\n\n  [ActionTypes.REORDER_LAYER]: visStateUpdaters.reorderLayerUpdater,\n\n  [ActionTypes.RESET_MAP_CONFIG]: visStateUpdaters.resetMapConfigUpdater,\n\n  [ActionTypes.SET_FILTER]: visStateUpdaters.setFilterUpdater,\n\n  [ActionTypes.SET_FILTER_ANIMATION_TIME]: visStateUpdaters.setFilterAnimationTimeUpdater,\n\n  [ActionTypes.SET_FILTER_ANIMATION_TIME_CONFIG]:\n    visStateUpdaters.setFilterAnimationTimeConfigUpdater,\n\n  [ActionTypes.SET_FILTER_ANIMATION_WINDOW]: visStateUpdaters.setFilterAnimationWindowUpdater,\n\n  [ActionTypes.SET_FILTER_PLOT]: visStateUpdaters.setFilterPlotUpdater,\n\n  [ActionTypes.SET_MAP_INFO]: visStateUpdaters.setMapInfoUpdater,\n\n  [ActionTypes.SHOW_DATASET_TABLE]: visStateUpdaters.showDatasetTableUpdater,\n\n  [ActionTypes.UPDATE_TABLE_COLOR]: visStateUpdaters.updateTableColorUpdater,\n\n  [ActionTypes.TOGGLE_FILTER_ANIMATION]: visStateUpdaters.toggleFilterAnimationUpdater,\n\n  [ActionTypes.UPDATE_FILTER_ANIMATION_SPEED]: visStateUpdaters.updateFilterAnimationSpeedUpdater,\n\n  [ActionTypes.SET_ANIMATION_CONFIG]: visStateUpdaters.setAnimationConfigUpdater,\n\n  [ActionTypes.SET_LAYER_ANIMATION_TIME]: visStateUpdaters.setLayerAnimationTimeUpdater,\n\n  [ActionTypes.UPDATE_LAYER_ANIMATION_SPEED]: visStateUpdaters.updateLayerAnimationSpeedUpdater,\n\n  [ActionTypes.TOGGLE_LAYER_FOR_MAP]: visStateUpdaters.toggleLayerForMapUpdater,\n\n  [ActionTypes.TOGGLE_SPLIT_MAP]: visStateUpdaters.toggleSplitMapUpdater,\n\n  [ActionTypes.UPDATE_LAYER_BLENDING]: visStateUpdaters.updateLayerBlendingUpdater,\n\n  [ActionTypes.UPDATE_OVERLAY_BLENDING]: visStateUpdaters.updateOverlayBlendingUpdater,\n\n  [ActionTypes.UPDATE_VIS_DATA]: visStateUpdaters.updateVisDataUpdater,\n\n  [ActionTypes.RENAME_DATASET]: visStateUpdaters.renameDatasetUpdater,\n\n  [ActionTypes.UPDATE_DATASET_PROPS]: visStateUpdaters.updateDatasetPropsUpdater,\n\n  [ActionTypes.SET_FEATURES]: visStateUpdaters.setFeaturesUpdater,\n\n  [ActionTypes.DELETE_FEATURE]: visStateUpdaters.deleteFeatureUpdater,\n\n  [ActionTypes.SET_POLYGON_FILTER_LAYER]: visStateUpdaters.setPolygonFilterLayerUpdater,\n\n  [ActionTypes.SET_SELECTED_FEATURE]: visStateUpdaters.setSelectedFeatureUpdater,\n\n  [ActionTypes.SET_EDITOR_MODE]: visStateUpdaters.setEditorModeUpdater,\n\n  [ActionTypes.TOGGLE_EDITOR_VISIBILITY]: visStateUpdaters.toggleEditorVisibilityUpdater,\n\n  [ActionTypes.TOGGLE_FILTER_FEATURE]: visStateUpdaters.toggleFilterFeatureUpdater,\n\n  [ActionTypes.APPLY_CPU_FILTER]: visStateUpdaters.applyCPUFilterUpdater,\n\n  [ActionTypes.SORT_TABLE_COLUMN]: visStateUpdaters.sortTableColumnUpdater,\n\n  [ActionTypes.PIN_TABLE_COLUMN]: visStateUpdaters.pinTableColumnUpdater,\n\n  [ActionTypes.COPY_TABLE_COLUMN]: visStateUpdaters.copyTableColumnUpdater,\n\n  [ActionTypes.SET_COLUMN_DISPLAY_FORMAT]: visStateUpdaters.setColumnDisplayFormatUpdater,\n\n  [ActionTypes.NEXT_FILE_BATCH]: visStateUpdaters.nextFileBatchUpdater,\n\n  [ActionTypes.PROCESS_FILE_CONTENT]: visStateUpdaters.processFileContentUpdater,\n\n  [ActionTypes.SET_LAYER_ANIMATION_TIME_CONFIG]:\n    visStateUpdaters.setLayerAnimationTimeConfigUpdater,\n\n  [ActionTypes.LAYER_FILTERED_ITEMS_CHANGE]: visStateUpdaters.layerFilteredItemsChangeUpdater,\n\n  [ActionTypes.WMS_FEATURE_INFO]: visStateUpdaters.wmsFeatureInfoUpdater,\n\n  [ActionTypes.SYNC_TIME_FILTER_WITH_LAYER_TIMELINE]:\n    visStateUpdaters.syncTimeFilterWithLayerTimelineUpdater,\n\n  [ActionTypes.SYNC_TIME_FILTER_TIMELINE_MODE]: visStateUpdaters.setTimeFilterTimelineModeUpdater,\n\n  [ActionTypes.ADD_EFFECT]: visStateUpdaters.addEffectUpdater,\n\n  [ActionTypes.REORDER_EFFECT]: visStateUpdaters.reorderEffectUpdater,\n\n  [ActionTypes.REMOVE_EFFECT]: visStateUpdaters.removeEffectUpdater,\n\n  [ActionTypes.UPDATE_EFFECT]: visStateUpdaters.updateEffectUpdater,\n\n  [ActionTypes.CREATE_NEW_DATASET_SUCCESS]: visStateUpdaters.createNewDatasetSuccessUpdater,\n\n  [ActionTypes.SET_LOADING_INDICATOR]: visStateUpdaters.setLoadingIndicatorUpdater\n};\n\n// construct vis-state reducer\nexport const visStateReducerFactory = (initialState = {}) =>\n  // @ts-expect-error\n  handleActions(actionHandler, {\n    ...visStateUpdaters.INITIAL_VIS_STATE,\n    ...initialState,\n    initialState\n  });\n\nexport default visStateReducerFactory();\n"
  },
  {
    "path": "src/reducers/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/schemas/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/schemas/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/schemas\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl schemas used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/layers\": \"3.2.6\",\n    \"@kepler.gl/table\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/loader-utils\": \"^4.3.2\",\n    \"@types/keymirror\": \"^0.1.1\",\n    \"@types/lodash\": \"4.17.5\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"global\": \"^4.3.0\",\n    \"keymirror\": \"^0.1.1\",\n    \"lodash\": \"4.17.21\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/schemas/src/dataset-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport pick from 'lodash/pick';\nimport {console as globalConsole} from 'global/window';\nimport * as arrow from 'apache-arrow';\n\nimport {ALL_FIELD_TYPES, DATASET_FORMATS} from '@kepler.gl/constants';\nimport {ProtoDataset, RGBColor, JsonObject} from '@kepler.gl/types';\nimport {KeplerTable} from '@kepler.gl/table';\nimport {VERSIONS} from './versions';\nimport Schema from './schema';\nimport {getFieldsFromData, getSampleForTypeAnalyze} from '@kepler.gl/common-utils';\nimport {ArrowDataContainer, DataContainerInterface, FIELD_DISPLAY_FORMAT} from '@kepler.gl/utils';\n\nexport type SavedField = {\n  name: string;\n  type: string;\n  format?: string;\n  analyzerType?: string;\n};\n\nexport type ParsedField = {\n  name: string;\n  type: string;\n  format: string;\n  analyzerType: string;\n};\n\nexport type SavedDatasetV1 = {\n  version: 'v1';\n  data: {\n    id: string;\n    label: string;\n    color: RGBColor;\n    allData: any[][];\n    fields: SavedField[];\n    type?: string;\n    metadata?: JsonObject;\n    disableDataOperation?: boolean;\n  };\n};\n\nexport type ParsedDataset = {\n  data: {\n    fields: ParsedField[];\n    rows: any[][];\n  };\n  info: {\n    id?: string;\n    label?: string;\n    color?: RGBColor;\n  };\n};\n\n// version v0\nexport const fieldPropertiesV0 = {\n  name: null,\n  type: null\n};\n\nexport const fieldPropertiesV1 = {\n  name: null,\n  type: null,\n  format: null,\n  analyzerType: null,\n  metadata: null\n};\n\nexport class FieldSchema extends Schema {\n  save(fields) {\n    return {\n      [this.key]: fields.map(f => this.savePropertiesOrApplySchema(f)[this.key])\n    };\n  }\n  load(fields) {\n    return {[this.key]: fields};\n  }\n}\n\nexport const propertiesV0 = {\n  id: null,\n  label: null,\n  color: null,\n  allData: null,\n  fields: new FieldSchema({\n    key: 'fields',\n    version: VERSIONS.v0,\n    properties: fieldPropertiesV0\n  })\n};\n\nexport const propertiesV1 = {\n  ...propertiesV0,\n  fields: new FieldSchema({\n    key: 'fields',\n    version: VERSIONS.v1,\n    properties: fieldPropertiesV1\n  }),\n  type: null,\n  metadata: null,\n  disableDataOperation: null\n};\n\n/**\n * TODO Consider moving this cast to ArrowDataContainer?\n * Prepare a data container for export as part of json / html files.\n * 1) Arrow tables can store Timestamps as BigInts, so convert numbers to ISOStrings compatible with Kepler.gl's TIMESTAMP.\n * 2) Geoarrow binary buffers converted to hex wkb\n * @param dataContainer A data container to flatten.\n * @returns Row based data.\n */\nconst getAllDataForSaving = (dataContainer: DataContainerInterface): any[][] => {\n  const allData = dataContainer.flattenData();\n\n  if (dataContainer instanceof ArrowDataContainer) {\n    const numColumns = dataContainer.numColumns();\n\n    for (let columnIndex = 0; columnIndex < numColumns; ++columnIndex) {\n      const column = dataContainer.getColumn(columnIndex);\n      const field = dataContainer.getField(columnIndex);\n\n      if (\n        arrow.DataType.isTimestamp(column.type) ||\n        arrow.DataType.isDate(column.type) ||\n        arrow.DataType.isTime(column.type)\n      ) {\n        allData.forEach(row => {\n          row[columnIndex] = new Date(row[columnIndex]).toISOString();\n        });\n      } else if (field?.type === ALL_FIELD_TYPES.geoarrow) {\n        const formatter = FIELD_DISPLAY_FORMAT[ALL_FIELD_TYPES.geoarrow];\n        allData.forEach(row => {\n          row[columnIndex] = formatter(row[columnIndex], field);\n        });\n      }\n    }\n  }\n\n  return allData;\n};\n\n/**\n * Transforms fields for saving as part of json / html files.\n * @param fields The array of fields from a Kepler table.\n * @returns The transformed fields array with GeoArrow types updated to GeoJSON.\n */\nconst getFieldsForSaving = (fields: KeplerTable['fields']) => {\n  return fields.map(field => {\n    if (field.type === ALL_FIELD_TYPES.geoarrow) {\n      // geoarrow binary data is transformed to hex wkb in getAllDataForSaving, so update the field accordingly\n      return {\n        name: field.name,\n        type: ALL_FIELD_TYPES.geojson,\n        format: '',\n        analyzerType: 'GEOMETRY'\n      };\n    }\n    return field;\n  });\n};\n\nexport class DatasetSchema extends Schema {\n  key = 'dataset';\n\n  save(dataset: KeplerTable): SavedDatasetV1['data'] {\n    const datasetFlattened = dataset.dataContainer\n      ? {\n          ...dataset,\n          allData: getAllDataForSaving(dataset.dataContainer),\n          fields: getFieldsForSaving(dataset.fields),\n          // we use flattenData to save arrow tables,\n          // but once flattened it's not an arrow file anymore.\n          metadata: {\n            ...dataset.metadata,\n            ...(dataset.metadata.format === DATASET_FORMATS.arrow\n              ? {format: DATASET_FORMATS.row}\n              : {})\n          }\n        }\n      : dataset;\n\n    return this.savePropertiesOrApplySchema(datasetFlattened)[this.key];\n  }\n  load(dataset: SavedDatasetV1['data']): ProtoDataset {\n    const {fields, allData} = dataset;\n    let updatedFields = fields;\n\n    // recalculate field type\n    // because we have updated type-analyzer\n    // we need to add format to each field\n    const needCalculateMeta =\n      fields[0] &&\n      (!Object.prototype.hasOwnProperty.call(fields[0], 'format') ||\n        !Object.prototype.hasOwnProperty.call(fields[0], 'analyzerType'));\n\n    if (needCalculateMeta) {\n      const fieldOrder = fields.map(f => f.name);\n\n      const sampleData = getSampleForTypeAnalyze({\n        fields: fieldOrder,\n        rows: allData\n      });\n      const meta = getFieldsFromData(sampleData, fieldOrder);\n\n      updatedFields = meta.map((f, i) => ({\n        ...pick(meta[i], ['name', 'type', 'format']),\n        analyzerType: meta[i].analyzerType\n      }));\n\n      updatedFields.forEach((f, i) => {\n        if (fields[i].type !== f.type) {\n          // if newly detected field type is different from saved type\n          // we log it but won't update it, cause we don't want to break people's map\n          globalConsole.warn(`detect ${f.name} type is now ${f.type} instead of ${fields[i].type}`);\n        }\n      });\n    }\n\n    // get format of all fields\n    return {\n      data: {fields: updatedFields, rows: dataset.allData},\n      info: pick(dataset, ['id', 'label', 'color', 'type']),\n      ...(dataset.metadata ? {metadata: dataset.metadata} : {}),\n      ...(dataset.disableDataOperation ? {disableDataOperation: dataset.disableDataOperation} : {})\n    };\n  }\n}\n\nexport const datasetSchema = {\n  [VERSIONS.v0]: new DatasetSchema({\n    key: 'dataset',\n    version: VERSIONS.v0,\n    properties: propertiesV0\n  }),\n  [VERSIONS.v1]: new DatasetSchema({\n    key: 'dataset',\n    version: VERSIONS.v1,\n    properties: propertiesV1\n  })\n};\n\nexport default datasetSchema;\n"
  },
  {
    "path": "src/schemas/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Schemas\nexport {\n  default,\n  default as KeplerGlSchema,\n  reducerSchema,\n  KeplerGLSchema as KeplerGLSchemaClass\n} from './schema-manager';\n\n// eslint-disable-next-line prettier/prettier\nexport type {SavedConfigV1, SavedMap, LoadedMap, SavedMapState} from './schema-manager';\nexport {CURRENT_VERSION, VERSIONS} from './versions';\nexport {\n  visStateSchemaV1,\n  FilterSchemaV0,\n  LayerSchemaV0,\n  InteractionSchemaV1,\n  DimensionFieldSchema,\n  SplitMapsSchema,\n  filterPropsV1,\n  default as visStateSchema,\n  layerPropsV1,\n  layerPropsV0,\n  effectPropsV1\n} from './vis-state-schema';\nexport type {SavedField, ParsedField, SavedDatasetV1, ParsedDataset} from './dataset-schema';\nexport {\n  default as datasetSchema,\n  DatasetSchema,\n  fieldPropertiesV1,\n  propertiesV1 as datasetPropertiesV1\n} from './dataset-schema';\nexport * from './vis-state-schema';\n/** NOTE: `MapStyleSchemaV1` is actually for `mapStyle.mapStyles` (original naming can be unclear) */\nexport {\n  default as mapStyleSchema,\n  MapStyleSchemaV1,\n  CustomMapStyleSchema\n} from './map-style-schema';\nexport {default as mapStateSchema} from './map-state-schema';\nexport {default as uiStateSchema} from './ui-state-schema';\nexport {default as Schema} from './schema';\n"
  },
  {
    "path": "src/schemas/src/map-state-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VERSIONS} from './versions';\nimport Schema from './schema';\n\n// version v0\nexport const propertiesV0 = {\n  bearing: null,\n  dragRotate: null,\n  latitude: null,\n  longitude: null,\n  pitch: null,\n  zoom: null\n};\n\nexport const propertiesV1 = {\n  ...propertiesV0,\n  isSplit: null,\n  isViewportSynced: null,\n  isZoomLocked: null,\n  splitMapViewports: null\n};\n\nconst mapStateSchema = {\n  [VERSIONS.v0]: new Schema({\n    version: VERSIONS.v0,\n    properties: propertiesV0,\n    key: 'mapState'\n  }),\n  [VERSIONS.v1]: new Schema({\n    version: VERSIONS.v1,\n    properties: propertiesV1,\n    key: 'mapState'\n  })\n};\n\nexport default mapStateSchema;\n"
  },
  {
    "path": "src/schemas/src/map-style-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VERSIONS} from './versions';\nimport Schema from './schema';\n\nexport const customMapStylePropsV1 = {\n  accessToken: null,\n  custom: null,\n  icon: null,\n  id: null,\n  label: null,\n  url: null\n};\n\nexport const CustomMapStyleSchema = new Schema({\n  version: VERSIONS.v1,\n  key: 'customStyle',\n  properties: customMapStylePropsV1\n});\n\nexport class MapStyleSchemaV1 extends Schema {\n  version = VERSIONS.v1;\n  key = 'mapStyles';\n  save(mapStyles) {\n    // save all custom styles\n    const saveCustomStyle = Object.keys(mapStyles).reduce(\n      (accu, key) => ({\n        ...accu,\n        ...(mapStyles[key].custom\n          ? {[key]: CustomMapStyleSchema.save(mapStyles[key]).customStyle}\n          : {})\n      }),\n      {}\n    );\n\n    return {[this.key]: saveCustomStyle};\n  }\n\n  load(mapStyles) {\n    // If mapStyle is an empty object, do not load it\n    return typeof mapStyles === 'object' && Object.keys(mapStyles).length\n      ? {[this.key]: mapStyles}\n      : {};\n  }\n}\n\n// version v0\nexport const propertiesV0 = {\n  styleType: null,\n  topLayerGroups: null,\n  visibleLayerGroups: null,\n  buildingLayer: null,\n  mapStyles: new MapStyleSchemaV1()\n};\n\nexport const propertiesV1 = {\n  styleType: null,\n  topLayerGroups: null,\n  visibleLayerGroups: null,\n  threeDBuildingColor: null,\n  backgroundColor: null,\n  mapStyles: new MapStyleSchemaV1()\n};\n\nconst mapStyleSchema = {\n  [VERSIONS.v0]: new Schema({\n    version: VERSIONS.v0,\n    properties: propertiesV0,\n    key: 'mapStyle'\n  }),\n  [VERSIONS.v1]: new Schema({\n    version: VERSIONS.v1,\n    properties: propertiesV1,\n    key: 'mapStyle'\n  })\n};\n\nexport default mapStyleSchema;\n"
  },
  {
    "path": "src/schemas/src/schema-manager.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {console as Console} from 'global/window';\n\nimport {Datasets} from '@kepler.gl/table';\nimport datasetSchema from './dataset-schema';\nimport mapStyleSchema from './map-style-schema';\nimport mapStateSchema from './map-state-schema';\nimport {SavedDatasetV1, ParsedDataset} from './dataset-schema';\nimport {visStateSchema} from './vis-state-schema';\nimport uiStateSchema from './ui-state-schema';\n\nimport {CURRENT_VERSION, VERSIONS} from './versions';\nimport {isPlainObject} from '@kepler.gl/utils';\n\nimport {MapInfo, SavedVisState, SavedMapStyle, ParsedConfig, BaseMapStyle} from '@kepler.gl/types';\n\nexport type SavedMapState = {\n  bearing: number;\n  dragRotate: boolean;\n  latitude: number;\n  longitude: number;\n  pitch: number;\n  zoom: number;\n  isSplit: boolean;\n  isViewportSynced?: true;\n  isZoomLocked?: false;\n  splitMapViewports?: [];\n};\n\nexport type SavedLayerGroups = {\n  [key: string]: boolean;\n};\n\nexport type SavedCustomMapStyle = {\n  [key: string]: {\n    accessToken?: string;\n    custom: BaseMapStyle['custom'];\n    icon: string;\n    id: string;\n    label: string;\n    url: string;\n  };\n};\n\n/** Schema for v1 saved configuration */\nexport type SavedConfigV1 = {\n  version: 'v1';\n  config: {\n    visState: SavedVisState;\n    mapState: SavedMapState;\n    mapStyle: SavedMapStyle;\n  };\n};\n\nexport type SavedMap = {\n  datasets: SavedDatasetV1[];\n  config: SavedConfigV1;\n  info: {\n    app: string;\n    created_at: string;\n    title: string;\n    description: string;\n  };\n};\n\nexport type LoadedMap = {datasets?: ParsedDataset[] | null; config?: ParsedConfig | null};\n\nexport const reducerSchema: {\n  [key: string]: typeof mapStateSchema | typeof visStateSchema | typeof mapStyleSchema;\n} = {\n  visState: visStateSchema,\n  mapState: mapStateSchema,\n  mapStyle: mapStyleSchema,\n  uiState: uiStateSchema\n};\n\nexport interface ComposedReducerSchema {\n  save(state: any): any;\n  load(config: SavedMap): SavedMap;\n}\n\nexport type KeplerGLSchemaProps = {\n  reducers?: typeof reducerSchema;\n  datasets?: typeof datasetSchema;\n  validVersions?: typeof VERSIONS;\n  version?: 'v1';\n  composedReducerSchema?: ComposedReducerSchema;\n};\n\nexport class KeplerGLSchema {\n  _validVersions: typeof VERSIONS;\n  _version: 'v1';\n  _reducerSchemas: typeof reducerSchema;\n  _datasetSchema: typeof datasetSchema;\n  _datasetLastSaved: SavedDatasetV1[] | null;\n  _savedDataset: SavedDatasetV1[] | null;\n  _composedReducerSchema: ComposedReducerSchema | null;\n\n  constructor({\n    reducers = reducerSchema,\n    datasets = datasetSchema,\n    validVersions = VERSIONS,\n    version = CURRENT_VERSION,\n    composedReducerSchema\n  }: KeplerGLSchemaProps = {}) {\n    this._validVersions = validVersions;\n    this._version = version;\n    this._reducerSchemas = reducers;\n    this._datasetSchema = datasets;\n    this._composedReducerSchema = composedReducerSchema || null;\n\n    this._datasetLastSaved = null;\n    this._savedDataset = null;\n  }\n\n  /**\n   * stateToSave = {\n   *   datasets: [\n   *     {\n   *       version: 'v0',\n   *       data: {id, label, color, allData, fields}\n   *     },\n   *     {\n   *       version: 'v0',\n   *       data: {id, label, color, allData, fields}\n   *     }\n   *   ],\n   *   config: {\n   *     version: 'v0',\n   *     config: {}\n   *   },\n   *   info: {\n   *     app: 'kepler.gl',\n   *     create_at: 'Mon May 28 2018 21:04:46 GMT-0700 (PDT)'\n   *   }\n   * }\n   *\n   * Get config and data of current map to save\n   * @param state\n   * @returns app state to save\n   */\n  save(state: any): SavedMap {\n    return {\n      datasets: this.getDatasetToSave(state),\n      config: this.getConfigToSave(state),\n      info: {\n        app: 'kepler.gl',\n        created_at: new Date().toString(),\n        ...this.getMapInfo(state)\n      }\n    };\n  }\n\n  getMapInfo(state: any): MapInfo {\n    return state.visState.mapInfo;\n  }\n  /**\n   *  Load saved map, argument can be (datasets, config) or ({datasets, config})\n   * @param savedDatasets\n   * @param savedConfig\n   */\n  load(\n    ...args:\n      | [\n          savedDatasets: SavedMap | SavedMap['datasets'] | any,\n          savedConfig: SavedMap['config'] | any\n        ]\n      | [{datasets: SavedMap['datasets'] | any; config: SavedMap['config'] | any}]\n  ): LoadedMap {\n    // if pass dataset and config in as a single object\n    if (\n      args.length === 1 &&\n      isPlainObject(args[0]) &&\n      (Array.isArray(args[0].datasets) || isPlainObject(args[0].config))\n    ) {\n      return this.load(args[0].datasets, args[0].config);\n    }\n\n    return {\n      ...(Array.isArray(args[0]) ? {datasets: this.parseSavedData(args[0])} : {}),\n      ...(args[1] ? {config: this.parseSavedConfig(args[1])} : {})\n    };\n  }\n\n  /**\n   * Get data to save\n   * @param state - app state\n   * @returns - dataset to save\n   */\n  getDatasetToSave(state: any): SavedDatasetV1[] {\n    const dataChangedSinceLastSave = this.hasDataChanged(state);\n    if (!dataChangedSinceLastSave) {\n      // @ts-expect-error\n      return this._savedDataset;\n    }\n\n    const {visState} = state;\n\n    const datasets = Object.values(visState.datasets as Datasets).map(ds => ({\n      version: this._version,\n      data: this._datasetSchema[this._version].save(ds)\n    }));\n\n    // keep a copy of formatted datasets to save\n    this._datasetLastSaved = visState.datasets;\n    this._savedDataset = datasets;\n\n    return datasets;\n  }\n\n  /**\n   * Get App config to save\n   * @param state - app state\n   * @returns - config to save\n   */\n  getConfigToSave(state: any): SavedConfigV1 {\n    const toSave =\n      typeof this._composedReducerSchema?.save === 'function'\n        ? this._composedReducerSchema.save(state)\n        : state;\n    const config = Object.keys(this._reducerSchemas).reduce(\n      (accu, key) => ({\n        ...accu,\n        ...(toSave[key] ? this._reducerSchemas[key][this._version].save(toSave[key]) : {})\n      }),\n      {}\n    );\n\n    return {\n      version: this._version,\n      // @ts-expect-error\n      config\n    };\n  }\n\n  /**\n   * Parse saved data\n   * @param datasets\n   * @returns - dataset to pass to addDataToMap\n   */\n  parseSavedData(datasets: any): ParsedDataset[] | null {\n    return datasets.reduce((accu, ds) => {\n      const validVersion = this.validateVersion(ds.version);\n      if (!validVersion) {\n        return accu;\n      }\n      accu.push(this._datasetSchema[validVersion].load(ds.data));\n      return accu;\n    }, []);\n  }\n\n  /**\n   * Parse saved App config\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  parseSavedConfig({version, config}, state = {}): ParsedConfig | null {\n    const validVersion = this.validateVersion(version);\n    if (!validVersion) {\n      return null;\n    }\n\n    const toLoad =\n      typeof this._composedReducerSchema?.load === 'function'\n        ? this._composedReducerSchema.load(config)\n        : config;\n\n    return Object.keys(toLoad).reduce(\n      (accu, key) => ({\n        ...accu,\n        ...(key in this._reducerSchemas\n          ? this._reducerSchemas[key][validVersion].load(toLoad[key])\n          : {})\n      }),\n      {}\n    );\n  }\n\n  /**\n   * Validate version\n   * @param version\n   * @returns validVersion\n   */\n  validateVersion(version: any): string | null {\n    if (!version) {\n      Console.error('There is no version number associated with this saved map');\n      return null;\n    }\n\n    if (!this._validVersions[version]) {\n      Console.error(`${version} is not a valid version`);\n      return null;\n    }\n\n    return version;\n  }\n\n  /**\n   * Check if data has changed since last save\n   * @param state\n   * @returns - whether data has changed or not\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  hasDataChanged(state: any): boolean {\n    return true;\n  }\n}\n\nconst KeplerGLSchemaManager = new KeplerGLSchema();\n\nexport default KeplerGLSchemaManager;\n"
  },
  {
    "path": "src/schemas/src/schema-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Recursively save / load value for state based on property keys,\n * if property[key] is another schema\n * Use is to get value to save\n * @param {Object} state - state to save\n * @param {Object} properties - properties schema\n * @returns {Object} - saved state\n */\nexport function savePropertiesOrApplySchema(state, properties) {\n  return getPropertyValueFromSchema('save', state, properties);\n}\n\nexport function loadPropertiesOrApplySchema(state, properties) {\n  return getPropertyValueFromSchema('load', state, properties);\n}\n\nexport function getPropertyValueFromSchema(operation, state, properties) {\n  return Object.keys(properties).reduce(\n    (accu, key) => ({\n      ...accu,\n      ...(key in state\n        ? properties[key]\n          ? // if it's another schema\n            properties[key][operation]\n            ? // call save or load\n              properties[key][operation](state[key], state)\n            : // if it's another property object\n              getPropertyValueFromSchema(operation, state[key], properties[key])\n          : {[key]: state[key]}\n        : {})\n    }),\n    {}\n  );\n}\n"
  },
  {
    "path": "src/schemas/src/schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {console as Console} from 'global/window';\n\nimport {CURRENT_VERSION} from './versions';\n\nexport default class Schema {\n  version: string;\n  key: string;\n  properties:\n    | {\n        [key: string]: null | Schema;\n      }\n    | string[]\n    | null;\n\n  constructor({\n    version = CURRENT_VERSION,\n    key = '',\n    properties = null\n  }: {\n    version?: string;\n    key?: string;\n    properties?:\n      | {\n          [key: string]: null | Schema;\n        }\n      | string[]\n      | null;\n  } = {}) {\n    this.version = version;\n    this.properties = properties;\n    this.key = key;\n  }\n\n  loadPropertiesOrApplySchema(\n    node: any,\n    parents: any[] = [],\n    accumulator?: any\n  ): {[key: string]: any} {\n    return this._getPropertyValueFromSchema('load', node, parents, accumulator);\n  }\n\n  savePropertiesOrApplySchema(\n    node: any,\n    parents: object[] = [],\n    accumulator?: any\n  ): {[key: string]: any} {\n    return this._getPropertyValueFromSchema('save', node, parents, accumulator);\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  _getPropertyValueFromSchema(operation, node: any, parents: object[] = [], accumulator) {\n    const internal = `_${operation}`;\n    return {\n      [this.key]: this.properties\n        ? Object.keys(this.properties).reduce((accu, key) => {\n            return {\n              ...accu,\n              ...(key in node\n                ? // @ts-expect-error\n                  this.properties[key]\n                  ? // if it's another schema\n                    // @ts-expect-error\n                    this.properties[key][operation]\n                    ? // call save or load\n                      // @ts-expect-error\n                      this.properties[key][internal](node[key], [...parents, node], accu)\n                    : {}\n                  : {[key]: node[key]}\n                : {})\n            };\n          }, {})\n        : node\n    };\n  }\n\n  _isCurrentVersion() {\n    return this.version === CURRENT_VERSION;\n  }\n\n  outdatedVersionError() {\n    if (!this._isCurrentVersion()) {\n      Console.error(`${this.key} ${this.version} is outdated. save should not be called anymore`);\n    }\n  }\n\n  _save(...args) {\n    // make sure nothing is saved to an outdated version\n    this.outdatedVersionError();\n    // @ts-expect-error\n    return this.save(...args);\n  }\n\n  save(node: any, parents: object[] = [], accumulator: any = {}): {[key: string]: any} {\n    return this.savePropertiesOrApplySchema(node, parents, accumulator);\n  }\n\n  _load(...args) {\n    // @ts-expect-error\n    return this.load(...args);\n  }\n\n  load(node: any, parents: object[] = [], accumulator: any = {}): {[key: string]: any} {\n    return this.loadPropertiesOrApplySchema(node, parents, accumulator);\n  }\n}\n"
  },
  {
    "path": "src/schemas/src/ui-state-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VERSIONS} from './versions';\nimport Schema from './schema';\n\nexport const propertiesV1 = {\n  mapControls: new Schema({\n    version: VERSIONS.v1,\n    properties: {\n      mapLegend: new Schema({\n        version: VERSIONS.v1,\n        properties: {\n          active: null,\n          settings: new Schema({\n            version: VERSIONS.v1,\n            properties: {\n              position: null,\n              contentHeight: null\n            },\n            key: 'settings'\n          })\n        },\n        key: 'mapLegend'\n      })\n    },\n    key: 'mapControls'\n  })\n};\n\nconst uiStateSchema = {\n  [VERSIONS.v0]: new Schema({\n    version: VERSIONS.v0,\n    properties: propertiesV1,\n    key: 'uiState'\n  }),\n  [VERSIONS.v1]: new Schema({\n    version: VERSIONS.v1,\n    properties: propertiesV1,\n    key: 'uiState'\n  })\n};\n\nexport default uiStateSchema;\n"
  },
  {
    "path": "src/schemas/src/versions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\n\nexport const VERSIONS = keyMirror({\n  v0: null,\n  v1: null\n});\n\nexport const CURRENT_VERSION = VERSIONS.v1;\n"
  },
  {
    "path": "src/schemas/src/vis-state-schema.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport pick from 'lodash/pick';\nimport {VERSIONS} from './versions';\nimport {LAYER_VIS_CONFIGS, FILTER_VIEW_TYPES} from '@kepler.gl/constants';\nimport {colorRangeBackwardCompatibility, isFilterValidToSave, findById} from '@kepler.gl/utils';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport Schema from './schema';\nimport cloneDeep from 'lodash/cloneDeep';\nimport {\n  AddDataToMapOptions,\n  AnimationConfig,\n  Editor,\n  FileLoading,\n  FileLoadingProgress,\n  Filter,\n  InteractionConfig,\n  MapInfo,\n  ParsedFilter,\n  ParsedLayer,\n  ParsedVisState,\n  SavedFilter,\n  MinSavedFilter,\n  SavedInteractionConfig,\n  SavedLayer,\n  MinSavedLayer,\n  SavedVisState,\n  SplitMap,\n  ValueOf,\n  Effect\n} from '@kepler.gl/types';\nimport {Datasets} from '@kepler.gl/table';\nimport {Layer, LayerClassesType} from '@kepler.gl/layers';\nimport {Loader} from '@loaders.gl/loader-utils';\nimport KeplerGLSchema from './schema-manager';\n\n/**\n * V0 Schema\n */\n\nexport const dimensionPropsV0 = ['name', 'type'];\n\nexport type modifiedType = {\n  strokeColor?: any;\n  strokeColorRange?: any;\n  filled?: boolean;\n  stroked?: boolean;\n};\n\nexport interface VisState {\n  mapInfo: MapInfo;\n  layers: Layer[];\n  layerData: any[];\n  layerToBeMerged: any[];\n  layerOrder: string[];\n  effects: Effect[];\n  effectOrder: string[];\n  filters: Filter[];\n  filterToBeMerged: any[];\n  datasets: Datasets;\n  editingDataset: string | undefined;\n  interactionConfig: InteractionConfig;\n  interactionToBeMerged: any;\n  layerBlending: string;\n  overlayBlending?: string;\n  hoverInfo: any;\n  clicked: any;\n  mousePos: any;\n  maxDefaultTooltips: number;\n  layerClasses: LayerClassesType;\n  animationConfig: AnimationConfig;\n  editor: Editor;\n  splitMaps: SplitMap[];\n  splitMapsToBeMerged: SplitMap[];\n  fileLoading: FileLoading | false;\n  fileLoadingProgress: FileLoadingProgress;\n  loadingIndicatorValue: number;\n  loaders: Loader[];\n  loadOptions: object;\n  initialState?: Partial<VisState>;\n  mergers: VisStateMergers<any>;\n  schema: typeof KeplerGLSchema;\n  preserveLayerOrder?: string[];\n  preserveFilterOrder?: string[];\n  preserveDatasetOrder?: string[];\n  isMergingDatasets: {\n    [datasetId: string]: boolean;\n  };\n}\n\nexport type PostMergerPayload = {\n  newDataIds: string[];\n  options?: AddDataToMapOptions;\n  layerMergers?: Merger<any>[];\n};\nexport type MergerActionPayload<S extends object> = {\n  mergers: Merger<S>[];\n  postMergerPayload: PostMergerPayload;\n};\nexport type MergerMergeFunc<S extends object> = (\n  state: S,\n  config: any,\n  fromConfig: boolean,\n  mergerActionPayload?: MergerActionPayload<S>\n) => S;\nexport type ReplaceParentDatasetIdsFunc<T> = (\n  item: T,\n  dataId: string,\n  dataIdToReplace: string\n) => T | null;\nexport type Merger<S extends object> = {\n  merge: MergerMergeFunc<S>;\n  prop: string | string[];\n  toMergeProp?: string | string[];\n  preserveOrder?: string;\n  waitToFinish?: boolean;\n  waitForLayerData?: boolean;\n  replaceParentDatasetIds?: ReplaceParentDatasetIdsFunc<ValueOf<S>>;\n  saveUnmerged?: (state: S, unmerged: any) => S;\n  combineConfigs?: (configs: S[]) => S;\n  getChildDatasetIds?: any;\n};\nexport type VisStateMergers<S extends object> = Merger<S>[];\n\n// in v0 geojson there is only sizeField\n\n// in v1 geojson\n// stroke base on -> sizeField\n// height based on -> heightField\n// radius based on -> radiusField\n// here we make our wiredst guess on which channel sizeField belongs to\nfunction geojsonSizeFieldV0ToV1(config) {\n  const defaultRaiuds = 10;\n  const defaultRadiusRange = [0, 50];\n\n  // if extruded, sizeField is most likely used for height\n  if (config.visConfig.extruded) {\n    return 'heightField';\n  }\n\n  // if show stroke enabled, sizeField is most likely used for stroke\n  if (config.visConfig.stroked) {\n    return 'sizeField';\n  }\n\n  // if radius changed, or radius Range Changed, sizeField is most likely used for radius\n  // this is the most unreliable guess, that's why we put it in the end\n  if (\n    config.visConfig.radius !== defaultRaiuds ||\n    config.visConfig.radiusRange.some((d, i) => d !== defaultRadiusRange[i])\n  ) {\n    return 'radiusField';\n  }\n\n  return 'sizeField';\n}\n\n// convert v0 to v1 layer config\nclass DimensionFieldSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  save(field) {\n    // should not be called anymore\n    return {\n      [this.key]: field !== null ? this.savePropertiesOrApplySchema(field)[this.key] : null\n    };\n  }\n\n  load(field, parents, accumulated) {\n    const [config] = parents.slice(-1);\n    let fieldName = this.key;\n    if (config.type === 'geojson' && this.key === 'sizeField' && field) {\n      fieldName = geojsonSizeFieldV0ToV1(config);\n    }\n    // fold into visualChannels to be load by VisualChannelSchemaV1\n    return {\n      visualChannels: {\n        ...(accumulated.visualChannels || {}),\n        [fieldName]: field\n      }\n    };\n  }\n}\n\nclass DimensionScaleSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  save(scale) {\n    return {[this.key]: scale};\n  }\n  load(scale, parents, accumulated) {\n    const [config] = parents.slice(-1);\n    // fold into visualChannels to be load by VisualChannelSchemaV1\n    if (this.key === 'sizeScale' && config.type === 'geojson') {\n      // sizeScale now split into radiusScale, heightScale\n      // no user customization, just use default\n      return {};\n    }\n\n    return {\n      visualChannels: {\n        ...(accumulated.visualChannels || {}),\n        [this.key]: scale\n      }\n    };\n  }\n}\n\n// used to convert v0 to v1 layer config\nclass LayerConfigSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  load(saved, parents, accumulated) {\n    // fold v0 layer property into config.key\n    return {\n      config: {\n        ...(accumulated.config || {}),\n        [this.key]: saved\n      }\n    };\n  }\n}\n\n// used to convert v0 to v1 layer columns\n// only return column value for each column\nclass LayerColumnsSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  load(saved, parents, accumulated) {\n    // fold v0 layer property into config.key, flatten columns\n    return {\n      config: {\n        ...(accumulated.config || {}),\n        columns: Object.keys(saved).reduce(\n          (accu, key) => ({\n            ...accu,\n            [key]: saved[key].value\n          }),\n          {}\n        )\n      }\n    };\n  }\n}\n\n// used to convert v0 to v1 layer config.visConfig\nclass LayerConfigToVisConfigSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  load(saved, parents, accumulated) {\n    // fold v0 layer property into config.visConfig\n    const accumulatedConfig = accumulated.config || {};\n    return {\n      config: {\n        ...accumulatedConfig,\n        visConfig: {\n          ...(accumulatedConfig.visConfig || {}),\n          [this.key]: saved\n        }\n      }\n    };\n  }\n}\n\nclass LayerVisConfigSchemaV0 extends Schema {\n  version = VERSIONS.v0;\n  key = 'visConfig';\n\n  load(visConfig, parents, accumulator) {\n    const [config] = parents.slice(-1);\n    const rename = {\n      geojson: {\n        extruded: 'enable3d',\n        elevationRange: 'heightRange'\n      }\n    };\n\n    if (config.type in rename) {\n      const propToRename = rename[config.type];\n      return {\n        config: {\n          ...(accumulator.config || {}),\n          visConfig: Object.keys(visConfig).reduce(\n            (accu, key) => ({\n              ...accu,\n              ...(propToRename[key]\n                ? {[propToRename[key]]: visConfig[key]}\n                : {[key]: visConfig[key]})\n            }),\n            {}\n          )\n        }\n      };\n    }\n\n    return {\n      config: {\n        ...(accumulator.config || {}),\n        visConfig\n      }\n    };\n  }\n}\n\nclass LayerConfigSchemaDeleteV0 extends Schema {\n  version = VERSIONS.v0;\n  load() {\n    return {};\n  }\n}\n\n/**\n * V0 -> V1 Changes\n * - layer is now a class\n * - config saved in a config object\n * - id, type, isAggregated is outside layer.config\n * - visualChannels is outside config, it defines available visual channel and\n *   property names for field, scale, domain and range of each visual chanel.\n * - enable3d, colorAggregation and sizeAggregation are moved into visConfig\n * - GeojsonLayer - added height, radius specific properties\n */\n\nexport const layerPropsV0 = {\n  id: null,\n  type: null,\n\n  // move into layer.config\n  dataId: new LayerConfigSchemaV0({key: 'dataId'}),\n  label: new LayerConfigSchemaV0({key: 'label'}),\n  color: new LayerConfigSchemaV0({key: 'color'}),\n  isVisible: new LayerConfigSchemaV0({key: 'isVisible'}),\n  hidden: new LayerConfigSchemaV0({key: 'hidden'}),\n\n  // convert visConfig\n  visConfig: new LayerVisConfigSchemaV0({key: 'visConfig'}),\n\n  // move into layer.config\n  // flatten\n  columns: new LayerColumnsSchemaV0(),\n\n  // save into visualChannels\n  colorField: new DimensionFieldSchemaV0({\n    properties: dimensionPropsV0,\n    key: 'colorField'\n  }),\n  colorScale: new DimensionScaleSchemaV0({\n    key: 'colorScale'\n  }),\n  sizeField: new DimensionFieldSchemaV0({\n    properties: dimensionPropsV0,\n    key: 'sizeField'\n  }),\n  sizeScale: new DimensionScaleSchemaV0({\n    key: 'sizeScale'\n  }),\n\n  // move into config.visConfig\n  enable3d: new LayerConfigToVisConfigSchemaV0({key: 'enable3d'}),\n  colorAggregation: new LayerConfigToVisConfigSchemaV0({\n    key: 'colorAggregation'\n  }),\n  sizeAggregation: new LayerConfigToVisConfigSchemaV0({key: 'sizeAggregation'}),\n\n  // delete\n  isAggregated: new LayerConfigSchemaDeleteV0()\n};\n\n/**\n * V1 Schema\n */\nclass ColumnSchemaV1 extends Schema {\n  save(columns) {\n    // starting from v1, only save column value\n    // fieldIdx will be calculated during merge\n    return {\n      [this.key]: Object.keys(columns).reduce(\n        (accu, ckey) => ({\n          ...accu,\n          // if value is null, don't save it\n          ...(columns[ckey]?.value ? {[ckey]: columns[ckey].value} : {})\n        }),\n        {}\n      )\n    };\n  }\n\n  load(columns) {\n    return {columns};\n  }\n}\n\nclass TextLabelSchemaV1 extends Schema {\n  save(textLabel) {\n    return {\n      [this.key]: textLabel.map(tl => ({\n        ...tl,\n        field: tl.field ? pick(tl.field, ['name', 'type']) : null\n      }))\n    };\n  }\n\n  load(textLabel) {\n    return {textLabel: Array.isArray(textLabel) ? textLabel : [textLabel]};\n  }\n}\n\nconst visualChannelModificationV1 = {\n  geojson: (vc, parents) => {\n    const [layer] = parents.slice(-1);\n    const isOld = !Object.prototype.hasOwnProperty.call(vc, 'strokeColorField');\n    // make our best guess if this geojson layer contains point\n    const isPoint =\n      vc.radiusField || layer.config.visConfig.radius !== LAYER_VIS_CONFIGS.radius?.defaultValue;\n\n    if (isOld && !isPoint && layer.config.visConfig.stroked) {\n      // if stroked is true, copy color config to stroke color config\n      return {\n        strokeColorField: vc.colorField,\n        strokeColorScale: vc.colorScale\n      };\n    }\n    return {};\n  }\n};\n/**\n * V1: save [field]: {name, type}, [scale]: '' for each channel\n */\nclass VisualChannelSchemaV1 extends Schema {\n  save(visualChannels, parents) {\n    // only save field and scale of each channel\n    const [layer] = parents.slice(-1);\n    return {\n      [this.key]: Object.keys(visualChannels).reduce(\n        //  save channel to null if didn't select any field\n        (accu, key) => ({\n          ...accu,\n          [visualChannels[key].field]: layer.config[visualChannels[key].field]\n            ? pick(layer.config[visualChannels[key].field], ['name', 'type'])\n            : null,\n          [visualChannels[key].scale]: layer.config[visualChannels[key].scale]\n        }),\n        {}\n      )\n    };\n  }\n  load(vc, parents, accumulator) {\n    // fold channels into config\n    const [layer] = parents.slice(-1);\n    const modified = visualChannelModificationV1[layer.type]\n      ? visualChannelModificationV1[layer.type](vc, parents, accumulator)\n      : {};\n\n    return {\n      ...accumulator,\n      config: {\n        ...(accumulator.config || {}),\n        ...vc,\n        ...modified\n      }\n    };\n  }\n}\nconst visConfigModificationV1 = {\n  point: (visConfig, parents) => {\n    const modified: modifiedType = {};\n    const [layer] = parents.slice(-2, -1);\n    const isOld =\n      !Object.prototype.hasOwnProperty.call(visConfig, 'filled') &&\n      !visConfig.strokeColor &&\n      !visConfig.strokeColorRange;\n    if (isOld) {\n      // color color & color range to stroke color\n      modified.strokeColor = layer.config.color;\n      modified.strokeColorRange = cloneDeep(visConfig.colorRange);\n      if (visConfig.outline) {\n        // point layer now supports both outline and fill\n        // for older schema where filled has not been added to point layer\n        // set it to false\n        modified.filled = false;\n      }\n    }\n\n    return modified;\n  },\n  geojson: (visConfig, parents) => {\n    // is points?\n    const modified: modifiedType = {};\n    const [layer] = parents.slice(-2, -1);\n    const isOld =\n      layer.visualChannels &&\n      !Object.prototype.hasOwnProperty.call(layer.visualChannels, 'strokeColorField') &&\n      !visConfig.strokeColor &&\n      !visConfig.strokeColorRange;\n    // make our best guess if this geojson layer contains point\n    const isPoint =\n      (layer.visualChannels && layer.visualChannels.radiusField) ||\n      (visConfig && visConfig.radius !== LAYER_VIS_CONFIGS.radius?.defaultValue);\n\n    if (isOld) {\n      // color color & color range to stroke color\n      modified.strokeColor = layer.config.color;\n      modified.strokeColorRange = cloneDeep(visConfig.colorRange);\n      if (isPoint) {\n        // if is point, set stroke to false\n        modified.filled = true;\n        modified.stroked = false;\n      }\n    }\n\n    return modified;\n  }\n};\n\nclass VisConfigSchemaV1 extends Schema {\n  key = 'visConfig';\n  // layer.config.visConfig\n  // colorRange\n  load(visConfig, parents, accumulated) {\n    const [layer] = parents.slice(-2, -1);\n    const modified = visConfigModificationV1[layer.type]\n      ? visConfigModificationV1[layer.type](visConfig, parents, accumulated)\n      : {};\n    const loadedVisConfig = {...visConfig, ...modified};\n\n    // backward compatibility, load colorRange and change the name to match new color Palette\n    ['colorRange', 'strokeColorRange'].forEach(prop => {\n      if (loadedVisConfig?.[prop]) {\n        loadedVisConfig[prop] = colorRangeBackwardCompatibility(loadedVisConfig[prop]);\n      }\n    });\n\n    return {\n      visConfig: loadedVisConfig\n    };\n  }\n}\n\nexport const layerPropsV1 = {\n  id: null,\n  type: null,\n  config: new Schema({\n    version: VERSIONS.v1,\n    key: 'config',\n    properties: {\n      dataId: null,\n      columnMode: null,\n      label: null,\n      color: null,\n      highlightColor: null,\n      columns: new ColumnSchemaV1({\n        version: VERSIONS.v1,\n        key: 'columns'\n      }),\n      isVisible: null,\n      visConfig: new VisConfigSchemaV1({\n        version: VERSIONS.v1\n      }),\n      hidden: null,\n      textLabel: new TextLabelSchemaV1({\n        version: VERSIONS.v1,\n        key: 'textLabel'\n      })\n    }\n  }),\n  visualChannels: new VisualChannelSchemaV1({\n    version: VERSIONS.v1,\n    key: 'visualChannels'\n  })\n};\n\nexport class LayerSchemaV0 extends Schema {\n  key = 'layers';\n\n  save(layers: Layer[], parents: [VisState]): {layers: SavedLayer[]} {\n    const [visState] = parents.slice(-1);\n\n    return {\n      [this.key as 'layers']: visState.layerOrder.reduce((saved, layerId) => {\n        // save layers according to their rendering order\n        const layer = findById(layerId)(layers);\n        if (layer?.isValidToSave()) {\n          saved.push(this.savePropertiesOrApplySchema(layer).layers);\n        }\n        return saved;\n      }, [] as SavedLayer[])\n    };\n  }\n\n  load(layers: SavedLayer[] | MinSavedLayer[] | undefined): {\n    layers: ParsedLayer[] | undefined;\n  } {\n    return {\n      [this.key as 'layers']: layers\n        ? layers.map(layer => this.loadPropertiesOrApplySchema(layer, layers).layers)\n        : []\n    };\n  }\n}\n\nexport class FilterSchemaV0 extends Schema {\n  key = 'filters';\n  save(filters: Filter[]): {filters: SavedFilter[]} {\n    return {\n      filters: filters\n        .filter(isFilterValidToSave)\n        .map(filter => this.savePropertiesOrApplySchema(filter).filters)\n    };\n  }\n  load(filters: SavedFilter[] | MinSavedFilter[] | undefined): {\n    filters: ParsedFilter[] | undefined;\n  } {\n    return {\n      filters: filters\n        ?.map(filter => this.loadPropertiesOrApplySchema(filter).filters)\n        // backward compatible convert enlarged to view\n        .map(filter => {\n          const {enlarged, view, ...filterProps} = filter;\n\n          const newFilter = {\n            ...filterProps,\n            // if view exist use it otherwise check for enlarged\n            view: view ? view : enlarged ? FILTER_VIEW_TYPES.enlarged : FILTER_VIEW_TYPES.side\n          };\n\n          return newFilter;\n        })\n    };\n  }\n}\n\nconst interactionPropsV0 = ['tooltip', 'brush'];\n\nclass InteractionSchemaV0 extends Schema {\n  key = 'interactionConfig';\n\n  save(interactionConfig) {\n    return Array.isArray(this.properties)\n      ? {\n          [this.key]: this.properties.reduce(\n            (accu, key) => ({\n              ...accu,\n              ...(interactionConfig[key].enabled ? {[key]: interactionConfig[key].config} : {})\n            }),\n            {}\n          )\n        }\n      : {};\n  }\n\n  load(interactionConfig) {\n    // convert v0 -> v1\n    // return enabled: false if disabled,\n    return Array.isArray(this.properties)\n      ? {\n          [this.key]: this.properties.reduce(\n            (accu, key) => ({\n              ...accu,\n              ...{\n                [key]: {\n                  ...(interactionConfig[key] || {}),\n                  enabled: Boolean(interactionConfig[key])\n                }\n              }\n            }),\n            {}\n          )\n        }\n      : {};\n  }\n}\n\nconst interactionPropsV1 = [...interactionPropsV0, 'geocoder', 'coordinate'];\n\nexport class InteractionSchemaV1 extends Schema {\n  key = 'interactionConfig';\n\n  save(interactionConfig: InteractionConfig):\n    | {\n        interactionConfig: SavedInteractionConfig;\n      }\n    | Record<string, any> {\n    // save config even if disabled,\n    return Array.isArray(this.properties)\n      ? {\n          [this.key]: this.properties.reduce(\n            (accu, key) => ({\n              ...accu,\n              [key]: {\n                ...interactionConfig[key].config,\n                enabled: interactionConfig[key].enabled\n              }\n            }),\n            {}\n          )\n        }\n      : {};\n  }\n  load(interactionConfig: SavedInteractionConfig): {\n    interactionConfig: Partial<SavedInteractionConfig>;\n  } {\n    const modifiedConfig = cloneDeep(interactionConfig);\n    Object.keys(interactionConfig).forEach(configType => {\n      if (configType === 'tooltip') {\n        const fieldsToShow = modifiedConfig[configType].fieldsToShow;\n        if (!notNullorUndefined(fieldsToShow)) {\n          return {[this.key]: modifiedConfig};\n        }\n        Object.keys(fieldsToShow).forEach(key => {\n          // @ts-expect-error name: fieldData should be string\n          fieldsToShow[key] = fieldsToShow[key].map(fieldData => {\n            if (!fieldData.name) {\n              return {\n                name: fieldData,\n                format: null\n              };\n            }\n            return fieldData;\n          });\n        });\n      }\n      return;\n    });\n    return {[this.key as 'interactionConfig']: modifiedConfig};\n  }\n}\n\nexport const filterPropsV0 = {\n  dataId: null,\n  id: null,\n  name: null,\n  type: null,\n  value: null,\n  // deprecated\n  enlarged: null\n};\n\nexport class DimensionFieldSchema extends Schema {\n  save(field) {\n    return {\n      [this.key]: field ? this.savePropertiesOrApplySchema(field)[this.key] : null\n    };\n  }\n\n  load(field) {\n    return {[this.key]: field};\n  }\n}\n\nexport class SplitMapsSchema extends Schema {\n  convertLayerSettings(accu, [key, value]) {\n    if (typeof value === 'boolean') {\n      return {\n        ...accu,\n        [key]: value\n      };\n    } else if (value && typeof value === 'object' && value.isAvailable) {\n      return {\n        ...accu,\n        [key]: Boolean(value.isVisible)\n      };\n    }\n    return accu;\n  }\n\n  load(splitMaps) {\n    // previous splitMaps Schema {layers: {layerId: {isVisible, isAvailable}}}\n\n    if (!Array.isArray(splitMaps) || !splitMaps.length) {\n      return {splitMaps: []};\n    }\n\n    return {\n      splitMaps: splitMaps.map(settings => ({\n        ...settings,\n        layers: Object.entries(settings.layers || {}).reduce(this.convertLayerSettings, {})\n      }))\n    };\n  }\n}\nexport class PlotTypeSchema extends Schema {\n  key = 'plotType';\n  load(plotType) {\n    if (typeof plotType === 'string') {\n      return {\n        plotType: {\n          type: plotType\n        }\n      };\n    }\n    return {plotType};\n  }\n}\n\nexport const effectPropsV1 = {\n  id: null,\n  type: null,\n  isEnabled: null,\n  parameters: null\n};\nexport class EffectsSchema extends Schema {\n  key = 'effects';\n\n  save(effects, parents) {\n    const [visState] = parents.slice(-1);\n\n    return {\n      [this.key]: visState.effectOrder.reduce((saved, effectId) => {\n        // save effects according to their rendering order\n        /**\n         * @type {Effect | undefined}\n         */\n        const effect = findById(effectId)(effects as Effect[]);\n        if (effect?.isValidToSave()) {\n          saved.push(this.savePropertiesOrApplySchema(effect).effects);\n        }\n        return saved;\n      }, [])\n    };\n  }\n\n  load(effects) {\n    return {\n      [this.key]: effects.map(effect => {\n        // handle older configs\n        const outEffect = effect.config\n          ? {\n              id: effect.id,\n              ...effect.config,\n              parameters: {...(effect.config.params || {})}\n            }\n          : effect;\n        return this.loadPropertiesOrApplySchema(outEffect, effects).effects;\n      })\n    };\n  }\n}\n\nexport const filterPropsV1 = {\n  ...filterPropsV0,\n  plotType: new PlotTypeSchema({\n    version: VERSIONS.v1\n  }),\n  animationWindow: null,\n  yAxis: new DimensionFieldSchema({\n    version: VERSIONS.v1,\n    key: 'yAxis',\n    properties: {\n      name: null,\n      type: null\n    }\n  }),\n  // this replaced enlarged\n  view: null,\n\n  // polygon filter properties\n  layerId: null,\n  speed: null,\n\n  // layer timeline\n  syncedWithLayerTimeline: null,\n  syncTimelineMode: null,\n\n  enabled: null,\n\n  invertTrendColor: null,\n  timezone: null\n};\n\nexport const propertiesV0 = {\n  filters: new FilterSchemaV0({\n    version: VERSIONS.v0,\n    properties: filterPropsV0\n  }),\n  layers: new LayerSchemaV0({\n    version: VERSIONS.v0,\n    properties: layerPropsV0\n  }),\n  interactionConfig: new InteractionSchemaV0({\n    version: VERSIONS.v0,\n    properties: interactionPropsV0\n  }),\n  layerBlending: null,\n  overlayBlending: null\n};\n\nexport const propertiesV1 = {\n  filters: new FilterSchemaV0({\n    version: VERSIONS.v1,\n    properties: filterPropsV1\n  }),\n  layers: new LayerSchemaV0({\n    version: VERSIONS.v1,\n    properties: layerPropsV1\n  }),\n  effects: new EffectsSchema({\n    version: VERSIONS.v1,\n    properties: effectPropsV1\n  }),\n  interactionConfig: new InteractionSchemaV1({\n    version: VERSIONS.v1,\n    properties: interactionPropsV1\n  }),\n  layerBlending: null,\n  overlayBlending: null,\n  splitMaps: new SplitMapsSchema({\n    key: 'splitMaps',\n    version: VERSIONS.v1\n  }),\n  animationConfig: new Schema({\n    version: VERSIONS.v1,\n    properties: {\n      currentTime: null,\n      speed: null\n    },\n    key: 'animationConfig'\n  }),\n  editor: new Schema({\n    version: VERSIONS.v1,\n    properties: {\n      features: null,\n      visible: null\n    },\n    key: 'editor'\n  })\n};\n\nexport class VisStateSchemaV1 extends Schema {\n  save(node: VisState, parents: any[] = [], accumulator?: any): {visState: SavedVisState} {\n    // @ts-expect-error\n    return this.savePropertiesOrApplySchema(node, parents, accumulator);\n  }\n\n  load(node?: SavedVisState): {\n    visState: ParsedVisState | undefined;\n  } {\n    // @ts-expect-error\n    return this.loadPropertiesOrApplySchema(node);\n  }\n}\n\nexport const visStateSchemaV0 = new Schema({\n  version: VERSIONS.v0,\n  properties: propertiesV0,\n  key: 'visState'\n});\n\nexport const visStateSchemaV1 = new VisStateSchemaV1({\n  version: VERSIONS.v1,\n  properties: propertiesV1,\n  key: 'visState'\n});\n\nexport const visStateSchema: {\n  v0: typeof visStateSchemaV0;\n  v1: typeof visStateSchemaV1;\n} = {\n  // @ts-expect-error\n  [VERSIONS.v0]: {\n    save: toSave => visStateSchemaV0.save(toSave),\n    load: toLoad => visStateSchemaV1.load(visStateSchemaV0.load(toLoad)?.visState)\n  },\n  [VERSIONS.v1]: visStateSchemaV1\n};\n\n// test load v0\nexport default visStateSchema;\n"
  },
  {
    "path": "src/schemas/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": []\n}\n"
  },
  {
    "path": "src/schemas/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/styles/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/styles/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/styles\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:umd && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@types/styled-components\": \"^5.1.32\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/styles/src/base.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {css} from 'styled-components';\nimport {DIMENSIONS} from '@kepler.gl/constants';\n\ntype InputProps = {\n  active: boolean;\n  disabled: boolean;\n  error: string;\n  size: string;\n  type: string;\n};\n\ntype SecondaryInputProps = {\n  error: string;\n};\n\ntype SwitchableProps = {\n  checked: boolean;\n};\n\nexport const transition = 'all .4s ease';\nexport const transitionFast = 'all .2s ease';\nexport const transitionSlow = 'all .8s ease';\n\nexport const boxShadow = '0 1px 2px 0 rgba(0,0,0,0.10)';\nexport const boxSizing = 'border-box';\nexport const borderRadius = '1px';\nexport const borderColor = '#3A414C';\nexport const borderColorLT = '#F1F1F1';\n\n// TEXT\nexport const fontFamily = `ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif`;\nexport const fontWeight = 400;\nexport const fontSize = '0.875em';\nexport const lineHeight = 1.71429;\nexport const labelColor = '#6A7485';\nexport const labelHoverColor = '#C6C6C6';\nexport const labelColorLT = '#6A7485';\n\nexport const textColor = '#A0A7B4';\nexport const textColorLT = '#3A414C';\nexport const dataTableTextColor = textColorLT;\nexport const titleColorLT = '#29323C';\n\nexport const subtextColor = '#6A7485';\nexport const subtextColorLT = '#A0A7B4';\nexport const subtextColorActive = '#FFFFFF';\nexport const fontWhiteColor = '#54638c';\nexport const panelToggleBorderColor = '#FFFFFF';\nexport const panelTabWidth = '30px';\n\nexport const titleTextColor = '#FFFFFF';\nexport const textColorHl = '#F0F0F0';\nexport const textColorHlLT = '#000000';\nexport const activeColor = '#1FBAD6';\nexport const activeColorLT = '#2473BD';\nexport const activeColorHover = '#108188';\nexport const errorColor = '#F9042C';\nexport const logoColor = activeColor;\n\n// Button\nexport const btnFontFamily = fontFamily;\nexport const primaryBtnBgd = '#0F9668';\nexport const primaryBtnActBgd = '#13B17B';\nexport const primaryBtnColor = '#FFFFFF';\nexport const primaryBtnActColor = '#FFFFFF';\nexport const primaryBtnBgdHover = '#13B17B';\nexport const primaryBtnRadius = '2px';\nexport const primaryBtnFontSizeDefault = '11px';\nexport const primaryBtnFontSizeSmall = '10px';\nexport const primaryBtnFontSizeLarge = '14px';\nexport const primaryBtnBorder = '0';\n\nexport const secondaryBtnBgd = '#6A7485';\nexport const secondaryBtnActBgd = '#A0A7B4';\nexport const secondaryBtnColor = '#FFFFFF';\nexport const secondaryBtnActColor = '#FFFFFF';\nexport const secondaryBtnBgdHover = '#A0A7B4';\nexport const secondaryBtnBorder = '0';\n\nexport const ctaBtnBgd = '#0F9668';\nexport const ctaBtnBgdHover = '#13B17B';\nexport const ctaBtnActBgd = '#13B17B';\nexport const ctaBtnColor = '#FFFFFF';\nexport const ctaBtnActColor = '#FFFFFF';\n\nexport const linkBtnBgd = 'transparent';\nexport const linkBtnActBgd = linkBtnBgd;\nexport const linkBtnColor = '#A0A7B4';\nexport const linkBtnActColor = '#F1F1F1';\nexport const linkBtnActBgdHover = linkBtnBgd;\nexport const linkBtnBorder = '0';\n\nexport const negativeBtnBgd = errorColor;\nexport const negativeBtnActBgd = '#FF193E';\nexport const negativeBtnBgdHover = '#FF193E';\nexport const negativeBtnBorder = '0';\nexport const negativeBtnColor = '#FFFFFF';\nexport const negativeBtnActColor = '#FFFFFF';\n\nexport const floatingBtnBgd = '#29323C';\nexport const floatingBtnActBgd = '#3A4552';\nexport const floatingBtnBgdHover = '#3A4552';\nexport const floatingBtnBorder = '0';\nexport const floatingBtnBorderHover = '0';\nexport const floatingBtnColor = subtextColor;\nexport const floatingBtnActColor = subtextColorActive;\nexport const floatingBtnRadius = '0px';\n\nexport const selectionBtnBgd = 'transparent';\nexport const selectionBtnActBgd = 'transparent';\nexport const selectionBtnColor = '#D3D8E0';\nexport const selectionBtnActColor = '#0F9668';\nexport const selectionBtnBgdHover = '#0F9668';\nexport const selectionBtnBorder = '1';\nexport const selectionBtnBorderColor = '#D3D8E0';\nexport const selectionBtnBorderActColor = '#0F9668';\n// Scrollbar\nexport const scrollbarThumbColorLT = labelColorLT;\nexport const scrollbarThumbColorHoverLT = textColorHlLT;\n// Input\nexport const inputBoxHeight = '34px';\nexport const inputBoxHeightSmall = '24px';\nexport const inputBoxHeightTiny = '18px';\nexport const inputPadding = '4px 10px';\nexport const inputPaddingSmall = '4px 6px';\nexport const inputPaddingTiny = '2px 4px';\nexport const inputFontSize = '11px';\nexport const inputFontSizeSmall = '10px';\nexport const inputFontWeight = 500;\nexport const inputBgd = '#29323C';\nexport const inputBgdHover = '#3A414C';\nexport const inputBgdActive = '#3A414C';\nexport const inputBgdActiveLT = '#FFFFFF';\n\nexport const inputBorderColor = '#29323C';\nexport const inputBorderHoverColor = '#3A414C';\nexport const inputBorderHoverColorLT = subtextColor;\nexport const inputBorderActiveColor = '#3A414C';\nexport const inputBorderActiveColorLT = textColorLT;\n\nexport const inputColor = '#A0A7B4';\nexport const inputBorderRadius = '1px';\nexport const inputPlaceholderColor = '#6A7485';\nexport const inputPlaceholderColorLT = subtextColorLT;\nexport const inputPlaceholderFontWeight = 400;\nexport const inputBoxShadow = 'none';\nexport const inputBoxShadowActive = 'none';\nexport const inputBoxShadowActiveLT = 'none';\nexport const secondaryInputBgd = '#242730';\nexport const secondaryInputBgdHover = '#3A414C';\nexport const secondaryInputBgdActive = '#3A414C';\nexport const secondaryInputColor = '#A0A7B4';\nexport const secondaryInputBorderColor = '#242730';\nexport const secondaryInputBorderActiveColor = '#D3D8E0';\nexport const dropdownSelectHeight = 30;\n\n// Select\nexport const selectColor = inputColor;\nexport const selectColorLT = titleColorLT;\n\nexport const selectActiveBorderColor = '#D3D8E0';\nexport const selectFontSize = '11px';\nexport const selectFontWeight = '400';\nexport const selectFontWeightBold = '500';\n\nexport const selectColorPlaceHolder = '#6A7485';\nexport const selectColorPlaceHolderLT = selectColorLT;\nexport const selectBackground = inputBgd;\nexport const selectBackgroundHover = inputBgdHover;\nexport const selectBackgroundLT = '#FFFFFF';\nexport const selectBackgroundHoverLT = '#F8F8F9';\nexport const selectBorderColor = '#D3D8E0';\nexport const selectBorderColorLT = '#D3D8E0';\nexport const selectBorderRadius = '1px';\nexport const selectBorder = 0;\nexport const panelTabColor = subtextColor;\nexport const dropdownListHighlightBg = '#6A7485';\nexport const dropdownListHighlightBgLT = '#F8F8F9';\nexport const dropdownListShadow = '0 6px 12px 0 rgba(0,0,0,0.16)';\nexport const dropdownListBgd = '#29323C';\nexport const toolbarItemBgdHover = '#3A4552';\nexport const toolbarItemIconHover = textColorHl;\nexport const toolbarItemBorderHover = 'transparent';\nexport const toolbarItemBorderRaddius = '0px';\nexport const dropdownListBgdLT = '#FFFFFF';\nexport const dropdownListBorderTop = '#242730';\nexport const dropdownListBorderTopLT = '#D3D8E0';\nexport const dropdownListLineHeight = 20;\nexport const dropdownWrapperZ = 100;\nexport const dropdownWapperMargin = 4;\nexport const dndOverBackgroundColor = 'rgba(128, 128, 128, 0.2)';\n\n// Switch\nexport const switchWidth = 24;\nexport const switchHeight = 12;\nexport const switchLabelMargin = 12;\n\nexport const switchTrackBgd = '#29323C';\nexport const switchTrackBgdActive = activeColor;\nexport const switchTrackBorderRadius = '1px';\nexport const switchBtnBgd = '#6A7485';\nexport const switchBtnBgdActive = '#D3D8E0';\nexport const switchBtnBoxShadow = '0 2px 4px 0 rgba(0,0,0,0.40)';\nexport const switchBtnBorderRadius = '0';\nexport const switchBtnWidth = 12;\nexport const switchBtnHeight = 12;\n\nexport const secondarySwitchTrackBgd = '#242730';\nexport const secondarySwitchBtnBgd = '#3A414C';\n\n// Checkbox\nexport const checkboxWidth = 16;\nexport const checkboxHeight = 16;\nexport const checkboxMargin = 12;\nexport const checkboxBorderColor = selectBorderColor;\nexport const checkboxBorderRadius = '2px';\nexport const checkboxBorderColorLT = selectBorderColorLT;\nexport const checkboxBoxBgd = 'white';\nexport const checkboxBoxBgdChecked = primaryBtnBgd;\n\n// Radio\nexport const radioRadius = 8;\nexport const radioBorderRadius = 100;\nexport const radioBorderColor = 'transparent';\nexport const radioButtonRadius = 4;\nexport const radioButtonBgdColor = switchBtnBgdActive;\n\n// Side Panel\nexport const sidePanelHeaderBg = '#29323C';\nexport const sidePanelHeaderBorder = 'transparent';\nexport const layerConfigGroupMarginBottom = 12;\nexport const layerConfigGroupPaddingLeft = 18;\n\nexport const sidePanelInnerPadding = 16;\nexport const sidePanelBorder = 0;\nexport const sidePanelBorderColor = 'transparent';\nexport const sidePanelBg = '#242730';\nexport const sidePanelScrollBarWidth = 10;\nexport const sidePanelScrollBarHeight = 10;\nexport const sideBarCloseBtnBgd = secondaryBtnBgd;\nexport const sideBarCloseBtnColor = '#29323C';\nexport const sideBarCloseBtnBgdHover = secondaryBtnActBgd;\nexport const sidePanelTitleFontsize = '20px';\nexport const sidePanelTitleLineHeight = '1.71429';\nexport const panelBackground = '#29323C';\nexport const panelContentBackground = '#292E36';\nexport const panelBackgroundHover = '#3A4552';\nexport const panelHeaderBorderRadius = '0px';\nexport const chickletBgd = '#3A4552';\nexport const chickletBgdLT = '#D3D8E0';\nexport const panelHeaderIcon = '#6A7485';\nexport const panelHeaderIconActive = '#A0A7B4';\nexport const panelHeaderIconHover = textColorHl;\nexport const panelHeaderHeight = 48;\nexport const layerPanelHeaderHeight = 48;\nexport const panelBoxShadow = '0 6px 12px 0 rgba(0,0,0,0.16)';\nexport const panelBorderRadius = '2px';\nexport const panelBackgroundLT = '#F8F8F9';\nexport const panelToggleMarginRight = 12;\nexport const panelToggleBottomPadding = 6;\n\nexport const panelBorderColor = '#3A414C';\nexport const panelBorder = `1px solid ${borderColor}`;\nexport const panelBorderLT = `1px solid ${borderColorLT}`;\n\nexport const mapPanelBackgroundColor = '#242730';\nexport const mapPanelHeaderBackgroundColor = '#29323C';\nexport const tooltipBg = '#3A414C';\nexport const tooltipBgLT = '#1869B5';\nexport const tooltipColor = textColorHl;\nexport const tooltipColorLT = '#FFFFFF';\nexport const tooltipBoxShadow = boxShadow;\nexport const tooltipFontSize = '10px';\n\nexport const layerTypeIconSizeL = 50;\nexport const layerTypeIconPdL = 12;\nexport const layerTypeIconSizeSM = 28;\n\nexport const layerPanelToggleOptionColor = '#6A7485';\nexport const layerPanelToggleOptionColorActive = '#F0F0F0';\n\n// Sidepanel divider\nexport const sidepanelDividerBorder = '1px';\nexport const sidepanelDividerMargin = 12;\nexport const sidepanelDividerHeight = 12;\n\n// Bottom Panel\nexport const bottomInnerPdSide = 32;\nexport const bottomInnerPdVert = 6;\nexport const bottomPanelGap = 20;\nexport const bottomPanelGapPalm = 20;\nexport const bottomWidgetPaddingTop = 20;\nexport const bottomWidgetPaddingRight = 20;\nexport const bottomWidgetPaddingBottom = 30;\nexport const bottomWidgetPaddingLeft = 20;\nexport const bottomWidgetBgd = '#29323C';\n// Modal\nexport const modalTitleColor = '#3A414C';\nexport const modalTitleFontSize = '24px';\nexport const modalTitleFontSizeSmaller = '18px';\nexport const modalFooterBgd = '#F8F8F9';\nexport const modalImagePlaceHolder = '#DDDFE3';\nexport const modalPadding = '10px 0';\nexport const modalLateralPadding = '72px';\nexport const modalPortableLateralPadding = '36px';\n\nexport const modalOverLayZ = 1001;\nexport const modalOverlayBgd = 'rgba(0, 0, 0, 0.5)';\nexport const modalContentZ = 10002;\nexport const modalFooterZ = 10001;\nexport const modalTitleZ = 10003;\nexport const modalButtonZ = 10005;\nexport const modalDropdownBackground = '#FFFFFF';\n\n// Modal Dialog (Dark)\nexport const modalDialogBgd = '#3A414C';\nexport const modalDialogColor = textColorHl;\n\n// Slider\nexport const sliderBarColor = '#6A7485';\nexport const sliderBarBgd = '#3A414C';\nexport const sliderBarHoverColor = '#D3D8E0';\nexport const sliderBarRadius = '1px';\nexport const sliderBarHeight = 4;\nexport const sliderHandleHeight = 12;\nexport const sliderHandleWidth = 12;\nexport const sliderHandleColor = '#D3D8E0';\nexport const sliderHandleTextColor = sliderHandleColor;\nexport const sliderInactiveBorderColor = sliderHandleColor;\nexport const sliderBorderRadius = '0';\n\nexport const sliderHandleHoverColor = '#FFFFFF';\nexport const sliderHandleAfterContent = '';\nexport const sliderHandleShadow = '0 2px 4px 0 rgba(0,0,0,0.40)';\nexport const sliderInputHeight = 24;\nexport const sliderInputWidth = 56;\nexport const sliderInputFontSize = '10px';\nexport const sliderInputPadding = '4px 6px';\nexport const sliderMarginTopIsTime = -12;\nexport const sliderMarginTop = 12;\nexport const sliderMarginBottom = 12;\n\n// Custom Range Input\nexport const customRangeInputWidth = 100;\n\n// Geocoder\nexport const geocoderWidth = 360;\nexport const geocoderTop = 20;\nexport const geocoderRight = 12;\nexport const geocoderInputHeight = 36;\n\n// Map Control\nexport const mapControlTop = 52;\n\n// Plot\nexport const rangeBrushBgd = '#3A414C';\nexport const histogramFillInRange = activeColor;\nexport const histogramFillOutRange = sliderBarColor;\nexport const histogramOverlayColor = '#999999';\nexport const histogramBreakLineColor = '#505b7c';\nexport const axisFontSize = '10px';\nexport const axisFontColor = textColor;\nexport const timeTitleFontSize = '10px';\nexport const rangePlotMargin = {top: 12, bottom: 0, left: 0, right: 0};\nexport const rangePlotMarginLarge = {top: 18, bottom: 0, left: 0, right: 0};\nexport const rangePlotH = 62;\nexport const rangePlotContainerH = 78;\nexport const rangePlotHLarge = 102;\nexport const rangePlotHLargePalm = 102;\nexport const rangePlotContainerHLarge = 120;\nexport const rangePlotContainerHLargePalm = 120;\n\n// Notification\nexport const notificationColors = {\n  info: '#276ef1',\n  error: '#f25138',\n  success: '#47b275',\n  warning: '#ffc043'\n};\n\nexport const notificationPanelWidth = 240;\nexport const notificationPanelItemWidth = notificationPanelWidth - 60;\nexport const notificationPanelItemHeight = 60;\n\n// Data Table\nconst headerRowHeight = 70;\nconst headerStatsControlHeight = 32;\nconst headerRowWStatsHeight = 364;\nconst rowHeight = 32;\nconst headerPaddingTop = 6;\nconst headerPaddingBottom = 8;\nconst cellPaddingSide = 10;\nconst edgeCellPaddingSide = 10;\nconst cellFontSize = 10;\nconst gridPaddingSide = 24;\nconst headerCellBackground = '#FFFFFF';\nconst headerCellBorderColor = '#E0E0E0';\nconst headerCellStatsBackground = '#F8FAFF';\nconst headerCellStatsControlBackground = '#EAF0FF';\nconst headerCellIconColor = '#666666';\nconst cellBorderColor = '#E0E0E0';\nconst evenRowBackground = '#FFFFFF';\nconst oddRowBackground = '#F7F7F7';\nconst optionButtonColor = '#6A7485';\nconst pinnedGridBorderColor = '#E0E0E0';\n\n// Floating Time display\nconst timeDisplayBorderRadius = 32;\nconst timeDisplayHeight = 64;\nconst timeDisplayMinWidth = 176;\nconst timeDisplayOpacity = 0.8;\nconst timeDisplayPadding = '0 24px';\n\n// Export map modal\nconst exportIntraSectionMargin = '8';\n\n// progress bar\nconst progressBarColor = primaryBtnBgd;\nconst progressBarTrackColor = '#E8E8E8';\n// Action Panel\nexport const actionPanelWidth = 110;\nexport const actionPanelHeight = 32;\n\n// Styled Token\nexport const fieldTokenRightMargin = 4;\nexport const fieldTokenHeight = 20;\nexport const fieldTokenWidth = 40;\n\nexport const textTruncate = {\n  maxWidth: '100%',\n  overflow: 'hidden',\n  textOverflow: 'ellipsis',\n  whiteSpace: 'nowrap',\n  wordWrap: 'normal'\n};\n\n// layerConfigGroupLabel\nexport const layerConfigGroupLabelBorderLeft = '2px';\nexport const layerConfigGroupLabelMargin = '-12px';\nexport const layerConfigGroupLabelPadding = '10px';\nexport const layerConfigGroupColor = 'transparent';\n\n// layerConfigGroupLabel label\nexport const layerConfigGroupLabelLabelMargin = '0';\nexport const layerConfigGroupLabelLabelFontSize = '12px';\n\n// styledConfigGroupHeader\nexport const styledConfigGroupHeaderBorder = '2px';\n\n// layerConfigurator\n\nexport const layerConfiguratorBorder = '0';\nexport const layerConfiguratorBorderColor = '';\nexport const layerConfiguratorMargin = '12px';\nexport const layerConfiguratorPadding = '12px 0 8px 0';\n// This breakpoints are used for responsive design\nexport const breakPoints = {\n  palm: 588,\n  desk: 768\n};\n\nexport const aiAssistantPanelWidth = 415;\n\n// effect manager\nexport const effectConfiguratorMargin = '18px 0 18px 0';\nexport const effectConfiguratorPadding = '0 0 0 18px';\nexport const effectPanelWidth = 345;\nexport const effectPanelHeight = 180;\nexport const effectPanelPaddingSide = 16;\nexport const effectPanelPaddingTop = 16;\nexport const effectPanelAddEffectFontFamily = btnFontFamily;\n\nexport const effectTypeIconMarginSide = 6;\nexport const effectTypeIconSizeL = 56;\n\nexport const effectTypeIconBgHoverColor = '#262D40';\nexport const effectPanelTextMain = '#F7F7F7';\nexport const effectPanelTextSecondary1 = '#A0A7B4';\nexport const effectPanelTextSecondary2 = '#6A7485';\nexport const effectPanelTextSecondary3 = '#5A6475';\n\nexport const effectPanelElementColor = inputBgd;\nexport const effectPanelElementColorActive = '#323843';\nexport const effectPanelElementColorHovered = inputBgdHover;\nexport const effectPanelElementColorSelected = '#454e5d';\nexport const effectPanelElementColorSun = '#F7B26B';\n\n// right panel\nexport const rightPanelMarginTop = 12;\nexport const rightPanelMarginRight = 12;\n\n// theme is passed to kepler.gl when it's mounted,\n// it is used by styled-components to pass along to\n// all child components\n\nconst input = css<InputProps>`\n  &::placeholder {\n    color: ${props => props.theme.inputPlaceholderColor};\n    font-weight: ${props => props.theme.inputPlaceholderFontWeight};\n  }\n\n  /* Disable Arrows on Number Inputs */\n  &::-webkit-inner-spin-button,\n  &::-webkit-outer-spin-button {\n    -webkit-appearance: none;\n    margin: 0;\n  }\n\n  align-items: center;\n  background-color: ${props => props.theme.inputBgd};\n  border: 1px solid\n    ${props =>\n      props.active\n        ? props.theme.inputBorderActiveColor\n        : props.error\n        ? props.theme.errorColor\n        : props.theme.inputBgd};\n  border-radius: 2px;\n  caret-color: ${props => props.theme.inputBorderActiveColor};\n  color: ${props => props.theme.inputColor};\n  display: flex;\n  font-size: ${props =>\n    ['small', 'tiny'].includes(props.size)\n      ? props.theme.inputFontSizeSmall\n      : props.theme.inputFontSize};\n  font-weight: ${props => props.theme.inputFontWeight};\n  font-family: ${props => props.theme.fontFamily};\n  height: ${props =>\n    props.size === 'small'\n      ? props.theme.inputBoxHeightSmall\n      : props.size === 'tiny'\n      ? props.theme.inputBoxHeightTiny\n      : props.theme.inputBoxHeight};\n  justify-content: space-between;\n  outline: none;\n  overflow: hidden;\n  padding: ${props =>\n    props.size === 'small'\n      ? props.theme.inputPaddingSmall\n      : props.size === 'tiny'\n      ? props.theme.inputPaddingTiny\n      : props.theme.inputPadding};\n  text-overflow: ellipsis;\n  transition: ${props => props.theme.transition};\n  white-space: nowrap;\n  width: 100%;\n  word-wrap: normal;\n  pointer-events: ${props => (props.disabled ? 'none' : 'all')};\n  opacity: ${props => (props.disabled ? 0.5 : 1)};\n  box-shadow: ${props => props.theme.inputBoxShadow};\n\n  &:hover {\n    cursor: ${props => (props.type === 'number' || props.type === 'text' ? 'text' : 'pointer')};\n    background-color: ${props =>\n      props.active ? props.theme.inputBgdActive : props.theme.inputBgdHover};\n    border-color: ${props =>\n      props.active ? props.theme.inputBorderActiveColor : props.theme.inputBorderHoverColor};\n  }\n\n  &:active,\n  &:focus,\n  &.focus,\n  &.active {\n    background-color: ${props => props.theme.inputBgdActive};\n    border-color: ${props => props.theme.inputBorderActiveColor};\n    box-shadow: ${props => props.theme.inputBoxShadowActive};\n  }\n`;\n\nconst inputLT = css<InputProps>`\n  &::placeholder {\n    color: ${props => props.theme.inputPlaceholderColorLT};\n    font-weight: 400;\n  }\n  ${input}\n\n  background-color: ${props => props.theme.selectBackgroundLT};\n  border: 1px solid\n    ${props =>\n      props.active\n        ? props.theme.selectActiveBorderColor\n        : props.error\n        ? props.theme.errorColor\n        : props.theme.selectBorderColorLT};\n  color: ${props => props.theme.selectColorLT};\n  caret-color: ${props => props.theme.inputBorderActiveColorLT};\n\n  &:hover {\n    background-color: ${props => props.theme.inputBgdActiveLT};\n    cursor: ${props => (['number', 'text'].includes(props.type) ? 'text' : 'pointer')};\n    border-color: ${props =>\n      props.active ? props.theme.inputBorderActiveColorLT : props.theme.inputBorderHoverColorLT};\n  }\n\n  &:active,\n  &:focus,\n  &.focus,\n  &.active {\n    background-color: ${props => props.theme.inputBgdActiveLT};\n    border-color: ${props => props.theme.inputBorderActiveColorLT};\n    box-shadow: ${props => props.theme.inputBoxShadowActiveLT};\n  }\n`;\n\nconst secondaryInput = css<SecondaryInputProps>`\n  ${props => props.theme.input}\n  color: ${props => props.theme.secondaryInputColor};\n  background-color: ${props => props.theme.secondaryInputBgd};\n  border: 1px solid\n    ${props => (props.error ? props.theme.errorColor : props.theme.secondaryInputBorderColor)};\n\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.secondaryInputBgdHover};\n    border-color: ${props => props.theme.secondaryInputBgdHover};\n  }\n\n  &:active,\n  &:focus,\n  &.focus,\n  &.active {\n    background-color: ${props => props.theme.secondaryInputBgdActive};\n    border-color: ${props => props.theme.secondaryInputBorderActiveColor};\n  }\n`;\n\nconst chickletedInputContainer = css`\n  cursor: pointer;\n  flex-wrap: wrap;\n  height: auto;\n  justify-content: start;\n  margin-bottom: 2px;\n  padding: 0px 7px 0px 4px;\n  white-space: normal;\n\n  .chickleted-input__placeholder {\n    line-height: 24px;\n    margin: 4px;\n  }\n`;\n\nconst chickletedInput = css`\n  ${props => props.theme.input} ${props => props.theme.chickletedInputContainer};\n`;\n\nconst chickletedInputLT = css`\n  ${props => props.theme.inputLT} ${props => props.theme.chickletedInputContainer};\n`;\n\nconst secondaryChickletedInput = css`\n  ${props => props.theme.secondaryInput} ${props => props.theme.chickletedInputContainer};\n`;\n\nconst inlineInput = css`\n  ${props => props.theme.input} color: ${props => props.theme.textColor};\n  font-size: 13px;\n  letter-spacing: 0.43px;\n  line-height: 18px;\n  height: 24px;\n  font-weight: 400;\n  padding-left: 4px;\n  margin-left: -4px;\n  background-color: transparent;\n  border: 1px solid transparent;\n\n  &:hover {\n    height: 24px;\n    cursor: text;\n    background-color: transparent;\n    border: 1px solid ${props => props.theme.labelColor};\n  }\n\n  &:active,\n  .active,\n  &:focus {\n    background-color: transparent;\n    border: 1px solid ${props => props.theme.inputBorderActiveColor};\n  }\n`;\n\nconst switchTrack = css<SwitchableProps>`\n  background: ${props =>\n    props.checked ? props.theme.switchTrackBgdActive : props.theme.switchTrackBgd};\n  position: absolute;\n  top: 0;\n  left: ${props => -props.theme.switchLabelMargin}px;\n  content: '';\n  display: block;\n  width: ${props => props.theme.switchWidth}px;\n  height: ${props => props.theme.switchHeight}px;\n  border-radius: ${props => props.theme.switchTrackBorderRadius};\n`;\n\nconst switchButton = css<SwitchableProps>`\n  transition: ${props => props.theme.transition};\n  position: absolute;\n  top: ${props => (props.theme.switchHeight - props.theme.switchBtnHeight) / 2}px;\n  left: ${props =>\n    (props.checked\n      ? props.theme.switchWidth / 2\n      : (props.theme.switchHeight - props.theme.switchBtnHeight) / 2) -\n    props.theme.switchLabelMargin}px;\n  content: '';\n  display: block;\n  height: ${props => props.theme.switchBtnHeight}px;\n  width: ${props => props.theme.switchBtnWidth}px;\n  background: ${props =>\n    props.checked ? props.theme.switchBtnBgdActive : props.theme.switchBtnBgd};\n  box-shadow: ${props => props.theme.switchBtnBoxShadow};\n  border-radius: ${props => props.theme.switchBtnBorderRadius};\n`;\n\nconst inputSwitch = css`\n  user-select: none;\n  cursor: pointer;\n  line-height: ${props => props.theme.switchHeight}px;\n  font-weight: 500;\n  font-size: 12px;\n  color: ${props => props.theme.labelColor};\n  position: relative;\n  display: inline-block;\n  padding-top: 0;\n  padding-right: 0;\n  padding-bottom: 0;\n  padding-left: ${props => props.theme.switchWidth}px;\n\n  &:before {\n    ${props => props.theme.switchTrack};\n  }\n\n  &:after {\n    ${props => props.theme.switchButton};\n  }\n`;\n\n// This is a light version checkbox\nconst checkboxBox = css<SwitchableProps>`\n  display: block;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: ${props => props.theme.checkboxWidth}px;\n  height: ${props => props.theme.checkboxHeight}px;\n  background: ${props =>\n    props.checked ? props.theme.checkboxBoxBgdChecked : props.theme.checkboxBoxBgd};\n  border: 1px solid\n    ${props =>\n      props.checked ? props.theme.checkboxBoxBgdChecked : props.theme.checkboxBorderColor};\n  border-radius: 2px;\n  content: '';\n`;\n\nconst checkboxCheck = css<SwitchableProps>`\n  width: 10px;\n  height: 5px;\n  border-bottom: 2px solid white;\n  border-left: 2px solid white;\n  top: 4px;\n  left: 3px;\n  transform: rotate(-45deg);\n  display: block;\n  position: absolute;\n  opacity: ${props => (props.checked ? 1 : 0)};\n  content: '';\n`;\n\nconst inputCheckbox = css`\n  display: inline-block;\n  position: relative;\n  padding-left: 32px;\n  margin-bottom: 24px;\n  line-height: 20px;\n  vertical-align: middle;\n  cursor: pointer;\n  font-size: 12px;\n  color: ${props => props.theme.labelColor};\n  margin-left: -${props => props.theme.switchLabelMargin}px;\n\n  &:before {\n    ${props => props.theme.checkboxBox};\n  }\n\n  &:after {\n    ${props => props.theme.checkboxCheck};\n  }\n`;\n\nconst radioTrack = css`\n  ${props => props.theme.checkboxBox}\n  width: ${props => props.theme.radioRadius * 2}px;\n  height: ${props => props.theme.radioRadius * 2}px;\n  border-radius: ${props => props.theme.radioBorderRadius}px;\n  background-color: ${props => props.theme.switchTrackBgd};\n  border-color: ${props => props.theme.radioBorderColor};\n`;\n\nconst radioButton = css`\n  border: 0;\n  display: table;\n  top: ${props => props.theme.radioRadius - props.theme.radioButtonRadius}px;\n  left: ${props => props.theme.radioRadius - props.theme.radioButtonRadius}px;\n  width: ${props => props.theme.radioButtonRadius * 2}px;\n  height: ${props => props.theme.radioButtonRadius * 2}px;\n  border-radius: ${props => props.theme.radioButtonRadius * 2}px;\n  background-color: ${props => props.theme.radioButtonBgdColor};\n`;\n\nconst inputRadio = css`\n  ${props => props.theme.inputCheckbox}\n  padding-left: ${props => props.theme.radioRadius * 2 + 8}px;\n  margin-bottom: 0;\n  margin-left: 0;\n  line-height: ${props => props.theme.radioRadius * 2}px;\n  color: ${props => props.theme.textColorHl};\n  cursor: pointer;\n\n  &:before {\n    ${props => props.theme.radioTrack}\n  }\n\n  &:after {\n    ${props => props.theme.radioButton}\n  }\n`;\n\nconst secondaryRadio = css<SwitchableProps>`\n  ${props => props.theme.inputRadio}\n\n  &:before {\n    ${props => props.theme.radioTrack} background: ${props => props.theme.secondarySwitchTrackBgd};\n  }\n\n  &:after {\n    ${props => props.theme.radioButton}\n    background: ${props =>\n      props.checked ? props.theme.switchBtnBgdActive : props.theme.secondarySwitchBtnBgd};\n  }\n`;\n\nconst secondarySwitch = css<SwitchableProps>`\n  ${props => props.theme.inputSwitch}\n\n  &:before {\n    ${props => props.theme.switchTrack} background: ${props =>\n      props.checked ? props.theme.switchTrackBgdActive : props.theme.secondarySwitchTrackBgd};\n  }\n\n  &:after {\n    ${props => props.theme.switchButton}\n    background: ${props =>\n      props.checked ? props.theme.switchBtnBgdActive : props.theme.secondarySwitchBtnBgd};\n  }\n`;\n\nconst dropdownScrollBar = css`\n  &::-webkit-scrollbar {\n    height: 10px;\n    width: 10px;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: ${props => props.theme.dropdownListBgd};\n  }\n\n  &::-webkit-scrollbar-track {\n    background: ${props => props.theme.dropdownListBgd};\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: ${props => props.theme.labelColor};\n    border: 3px solid ${props => props.theme.dropdownListBgd};\n  }\n\n  &:vertical:hover {\n    background: ${props => props.theme.textColorHl};\n    cursor: pointer;\n  }\n`;\n\nconst dropdownScrollBarLT = css`\n  ${dropdownScrollBar} ::-webkit-scrollbar-corner {\n    background: ${props => props.theme.dropdownListBgdLT};\n  }\n\n  &::-webkit-scrollbar-track {\n    background: ${props => props.theme.dropdownListBgdLT};\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: ${props => props.theme.scrollbarThumbColorLT};\n    border: 3px solid ${props => props.theme.dropdownListBgdLT};\n  }\n\n  &:vertical:hover {\n    background: ${props => props.theme.scrollbarThumbColorHoverLT};\n    cursor: pointer;\n  }\n`;\n\nconst dropdownListAnchor = css`\n  color: ${props => props.theme.selectColor};\n  padding-left: 3px;\n  font-size: ${props => props.theme.selectFontSize};\n  line-height: ${props => props.theme.dropdownListLineHeight}px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  &.disabled {\n    color: ${props => props.theme.subtextColor};\n  }\n`;\n\nconst dropdownListAnchorLT = css`\n  ${dropdownListAnchor}\n  color: ${props => props.theme.selectColorLT};\n`;\n\nconst dropdownListSize = css`\n  font-size: 11px;\n  padding: 3px 9px;\n  font-weight: 500;\n  white-space: nowrap;\n`;\n\nconst dropdownListItem = css`\n  ${dropdownListSize} &.hover,\n  &:hover {\n    cursor: pointer;\n    background-color: ${props => props.theme.dropdownListHighlightBg};\n\n    .list__item__anchor {\n      color: ${props => props.theme.textColorHl};\n    }\n  }\n  &.selected {\n    background-color: ${props => props.theme.dropdownListHighlightBg};\n  }\n`;\n\nconst dropdownListItemLT = css`\n  ${dropdownListSize}\n  color: ${props => props.theme.textColorLT};\n\n  &.hover,\n  &:hover {\n    cursor: pointer;\n    color: ${props => props.theme.textColorHlLT};\n    background-color: ${props => props.theme.dropdownListHighlightBgLT};\n\n    .list__item__anchor {\n      color: ${props => props.theme.selectColorLT};\n    }\n  }\n  &.selected {\n    background-color: ${props => props.theme.dropdownListHighlightBgLT};\n  }\n`;\n\nconst dropdownListHeader = css`\n  font-size: 11px;\n  padding: 5px 9px;\n  color: ${props => props.theme.labelColor};\n`;\n\nconst dropdownListSection = css`\n  padding: 0 0 4px 0;\n  margin-bottom: 4px;\n  border-bottom: 1px solid ${props => props.theme.labelColor};\n`;\n\nconst dropdownList = css`\n  overflow-y: auto;\n  max-height: 280px;\n  box-shadow: ${props => props.theme.dropdownListShadow};\n  border-radius: 2px;\n\n  .list__section {\n    ${props => props.theme.dropdownListSection};\n  }\n  .list__header {\n    ${props => props.theme.dropdownListHeader};\n  }\n\n  .list__item {\n    ${props => props.theme.dropdownListItem};\n  }\n\n  .list__item__anchor {\n    ${props => props.theme.dropdownListAnchor};\n  }\n\n  ${props => props.theme.dropdownScrollBar};\n`;\n\nconst dropdownListLT = css`\n  ${dropdownList} .list__item {\n    ${props => props.theme.dropdownListItemLT};\n  }\n\n  .list__item__anchor {\n    ${props => props.theme.dropdownListAnchorLT};\n  }\n\n  ${props => props.theme.dropdownScrollBarLT};\n`;\nconst sidePanelScrollBar = css`\n  &::-webkit-scrollbar {\n    height: ${props => props.theme.sidePanelScrollBarHeight}px;\n    width: ${props => props.theme.sidePanelScrollBarWidth}px;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: ${props => props.theme.sidePanelBg};\n  }\n\n  &::-webkit-scrollbar-track {\n    background: ${props => props.theme.sidePanelBg};\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: ${props => props.theme.panelBackgroundHover};\n    border: 3px solid ${props => props.theme.sidePanelBg};\n\n    &:hover {\n      background: ${props => props.theme.labelColor};\n      cursor: pointer;\n    }\n  }\n`;\n\nconst panelDropdownScrollBar = css`\n  &::-webkit-scrollbar {\n    height: 10px;\n    width: 10px;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: ${props => props.theme.panelBackground};\n  }\n\n  &::-webkit-scrollbar-track {\n    background: ${props => props.theme.panelBackground};\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: ${props => props.theme.panelBackgroundHover};\n    border: 3px solid ${props => props.theme.panelBackground};\n    &:hover {\n      background: ${props => props.theme.labelColor};\n      cursor: pointer;\n    }\n  }\n`;\n\nconst scrollBar = css`\n  &::-webkit-scrollbar {\n    height: 10px;\n    width: 10px;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: ${props => props.theme.panelBackground};\n  }\n\n  &::-webkit-scrollbar-track {\n    background: ${props => props.theme.panelBackground};\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: ${props => props.theme.labelColor};\n    border: 3px solid ${props => props.theme.panelBackground};\n\n    :vertical:hover {\n      background: ${props => props.theme.textColorHl};\n      cursor: pointer;\n    }\n\n    :horizontal:hover {\n      background: ${props => props.theme.textColorHl};\n      cursor: pointer;\n    }\n  }\n`;\n\nexport const modalScrollBar = css`\n  &::-webkit-scrollbar {\n    width: 14px;\n    height: 16px;\n  }\n\n  &::-webkit-scrollbar-track {\n    background: white;\n  }\n  &::-webkit-scrollbar-track:horizontal {\n    background: ${props => props.theme.textColorHl};\n  }\n  &::-webkit-scrollbar-thumb {\n    background: ${props => props.theme.scrollbarThumbColorLT};\n    border: 4px solid white;\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: ${props => props.theme.textColorHl};\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background: ${props => props.theme.scrollbarThumbColorHoverLT};\n  }\n\n  &::-webkit-scrollbar-thumb:vertical {\n    border-radius: 7px;\n  }\n\n  &::-webkit-scrollbar-thumb:horizontal {\n    border-radius: 9px;\n    border: 4px solid ${props => props.theme.textColorHl};\n  }\n`;\n\nexport const theme = {\n  ...DIMENSIONS,\n  // templates\n  input,\n  inputLT,\n  inlineInput,\n  chickletedInput,\n  chickletedInputLT,\n  chickletedInputContainer,\n  secondaryChickletedInput,\n\n  borderColor,\n  borderColorLT,\n\n  secondaryInput,\n  dropdownScrollBar,\n  dropdownScrollBarLT,\n  dropdownList,\n  dropdownListLT,\n  dropdownListItem,\n  dropdownListItemLT,\n  dropdownListAnchor,\n  dropdownListAnchorLT,\n  dropdownListHeader,\n  dropdownListSection,\n  dropdownListShadow,\n  dropdownWrapperZ,\n  dropdownWapperMargin,\n  dndOverBackgroundColor,\n  modalScrollBar,\n  scrollBar,\n  sidePanelScrollBar,\n  inputSwitch,\n  secondarySwitch,\n  switchTrack,\n  switchButton,\n  inputCheckbox,\n  inputRadio,\n  checkboxBox,\n  checkboxCheck,\n\n  // Transitions\n  transition,\n  transitionFast,\n  transitionSlow,\n\n  // styles\n  activeColor,\n  activeColorHover,\n  borderRadius,\n  boxShadow,\n  errorColor,\n  dropdownListHighlightBg,\n  dropdownListHighlightBgLT,\n  dropdownListBgd,\n  toolbarItemBgdHover,\n  toolbarItemBorderHover,\n  toolbarItemIconHover,\n  toolbarItemBorderRaddius,\n  dropdownListBgdLT,\n  dropdownListBorderTop,\n  dropdownListBorderTopLT,\n  dropdownListLineHeight,\n\n  labelColor,\n  labelColorLT,\n  labelHoverColor,\n  mapPanelBackgroundColor,\n  mapPanelHeaderBackgroundColor,\n\n  // Select\n  selectActiveBorderColor,\n  selectBackground,\n  selectBackgroundLT,\n  selectBackgroundHover,\n  selectBackgroundHoverLT,\n  selectBorder,\n  selectBorderColor,\n  selectBorderRadius,\n  selectBorderColorLT,\n  selectColor,\n  selectColorPlaceHolder,\n  selectColorPlaceHolderLT,\n  selectFontSize,\n  selectFontWeight,\n  selectColorLT,\n  selectFontWeightBold,\n  panelTabColor,\n\n  // Input\n  inputBgd,\n  inputBgdHover,\n  inputBgdActive,\n  inputBgdActiveLT,\n  inputBoxHeight,\n  inputBoxHeightSmall,\n  inputBoxHeightTiny,\n  inputBorderColor,\n  inputBorderActiveColor,\n  inputBorderHoverColor,\n  inputBorderRadius,\n  inputColor,\n  inputPadding,\n  inputPaddingSmall,\n  inputPaddingTiny,\n  inputFontSize,\n  inputFontSizeSmall,\n  inputFontWeight,\n  inputPlaceholderColor,\n  inputPlaceholderColorLT,\n  inputPlaceholderFontWeight,\n  inputBoxShadow,\n  inputBoxShadowActive,\n  inputBoxShadowActiveLT,\n  secondaryInputBgd,\n  secondaryInputBgdHover,\n  secondaryInputBgdActive,\n  secondaryInputColor,\n  secondaryInputBorderColor,\n  secondaryInputBorderActiveColor,\n  dropdownSelectHeight,\n\n  // Switch\n  switchWidth,\n  switchHeight,\n  switchTrackBgd,\n  switchTrackBgdActive,\n  switchTrackBorderRadius,\n  switchBtnBgd,\n  switchBtnBgdActive,\n  switchBtnBoxShadow,\n  switchBtnBorderRadius,\n  switchBtnWidth,\n  switchBtnHeight,\n  switchLabelMargin,\n\n  secondarySwitchTrackBgd,\n  secondarySwitchBtnBgd,\n\n  // Checkbox\n  checkboxWidth,\n  checkboxHeight,\n  checkboxMargin,\n  checkboxBorderColor,\n  checkboxBorderRadius,\n  checkboxBorderColorLT,\n  checkboxBoxBgd,\n  checkboxBoxBgdChecked,\n\n  // Radio\n  radioRadius,\n  radioBorderRadius,\n  radioBorderColor,\n  radioButtonRadius,\n  radioButtonBgdColor,\n  radioTrack,\n  radioButton,\n  secondaryRadio,\n\n  // Button\n  btnFontFamily,\n  primaryBtnBgd,\n  primaryBtnActBgd,\n  primaryBtnColor,\n  primaryBtnActColor,\n  primaryBtnBgdHover,\n  primaryBtnRadius,\n  primaryBtnFontSizeDefault,\n  primaryBtnFontSizeSmall,\n  primaryBtnFontSizeLarge,\n  primaryBtnBorder,\n\n  secondaryBtnBgd,\n  secondaryBtnActBgd,\n  secondaryBtnBgdHover,\n  secondaryBtnColor,\n  secondaryBtnActColor,\n  secondaryBtnBorder,\n\n  negativeBtnBgd,\n  negativeBtnActBgd,\n  negativeBtnBgdHover,\n  negativeBtnBorder,\n  negativeBtnColor,\n  negativeBtnActColor,\n\n  linkBtnBgd,\n  linkBtnActBgd,\n  linkBtnColor,\n  linkBtnActColor,\n  linkBtnActBgdHover,\n  linkBtnBorder,\n\n  floatingBtnBgd,\n  floatingBtnActBgd,\n  floatingBtnBgdHover,\n  floatingBtnBorder,\n  floatingBtnBorderHover,\n  floatingBtnColor,\n  floatingBtnActColor,\n  floatingBtnRadius,\n\n  ctaBtnBgd,\n  ctaBtnBgdHover,\n  ctaBtnActBgd,\n  ctaBtnColor,\n  ctaBtnActColor,\n\n  selectionBtnBgd,\n  selectionBtnActBgd,\n  selectionBtnColor,\n  selectionBtnActColor,\n  selectionBtnBgdHover,\n  selectionBtnBorder,\n  selectionBtnBorderColor,\n  selectionBtnBorderActColor,\n\n  scrollbarThumbColorLT,\n  scrollbarThumbColorHoverLT,\n\n  // Modal\n  modalTitleColor,\n  modalTitleFontSize,\n  modalTitleFontSizeSmaller,\n  modalFooterBgd,\n  modalImagePlaceHolder,\n  modalPadding,\n\n  modalDialogBgd,\n  modalDialogColor,\n\n  modalLateralPadding,\n  modalPortableLateralPadding,\n  modalOverLayZ,\n  modalOverlayBgd,\n  modalContentZ,\n  modalFooterZ,\n  modalTitleZ,\n  modalButtonZ,\n  modalDropdownBackground,\n\n  // Side Panel\n  sidePanelBg,\n  sidePanelInnerPadding,\n  sideBarCloseBtnBgd,\n  sideBarCloseBtnColor,\n  sideBarCloseBtnBgdHover,\n  sidePanelHeaderBg,\n  sidePanelHeaderBorder,\n  sidePanelScrollBarWidth,\n  sidePanelScrollBarHeight,\n  sidePanelTitleFontsize,\n  sidePanelTitleLineHeight,\n  panelHeaderBorderRadius,\n  sidePanelBorder,\n  sidePanelBorderColor,\n\n  layerConfigGroupLabelLabelFontSize,\n  layerConfigGroupMarginBottom,\n  layerConfigGroupPaddingLeft,\n\n  // Side Panel Panel\n  chickletBgd,\n  chickletBgdLT,\n  panelBackground,\n  panelContentBackground,\n  panelBackgroundHover,\n  panelBackgroundLT,\n  panelToggleMarginRight,\n  panelToggleBottomPadding,\n  panelBoxShadow,\n  panelBorderRadius,\n  panelBorder,\n  panelBorderColor,\n  panelBorderLT,\n  panelHeaderIcon,\n  panelHeaderIconActive,\n  panelHeaderIconHover,\n  panelHeaderHeight,\n  layerPanelHeaderHeight,\n  panelDropdownScrollBar,\n\n  layerTypeIconSizeL,\n  layerTypeIconPdL,\n  layerTypeIconSizeSM,\n\n  layerPanelToggleOptionColor,\n  layerPanelToggleOptionColorActive,\n\n  // Text\n  fontFamily,\n  fontWeight,\n  fontSize,\n  lineHeight,\n  textColor,\n  textColorLT,\n  dataTableTextColor,\n  textColorHl,\n  titleTextColor,\n  subtextColor,\n  subtextColorLT,\n  subtextColorActive,\n  fontWhiteColor,\n  panelToggleBorderColor,\n  panelTabWidth,\n  textTruncate,\n  titleColorLT,\n  tooltipBg,\n  tooltipBgLT,\n  tooltipColor,\n  tooltipColorLT,\n  tooltipBoxShadow,\n  tooltipFontSize,\n  logoColor,\n\n  // Sidepanel divider\n  sidepanelDividerBorder,\n  sidepanelDividerMargin,\n  sidepanelDividerHeight,\n\n  // Bottom Panel\n  bottomInnerPdSide,\n  bottomInnerPdVert,\n  bottomPanelGap,\n  bottomPanelGapPalm,\n  bottomWidgetPaddingTop,\n  bottomWidgetPaddingRight,\n  bottomWidgetPaddingBottom,\n  bottomWidgetPaddingLeft,\n  bottomWidgetBgd,\n\n  // Slider\n  sliderBarColor,\n  sliderBarBgd,\n  sliderBarHoverColor,\n  sliderBarRadius,\n  sliderBarHeight,\n  sliderHandleHeight,\n  sliderHandleWidth,\n  sliderHandleColor,\n  sliderHandleTextColor,\n  sliderInactiveBorderColor,\n  sliderBorderRadius,\n  sliderHandleHoverColor,\n  sliderHandleAfterContent,\n  sliderHandleShadow,\n  sliderInputHeight,\n  sliderInputWidth,\n  sliderMarginTopIsTime,\n  sliderMarginTop,\n  sliderMarginBottom,\n\n  // Custom Range Input\n  customRangeInputWidth,\n\n  // Geocoder\n  geocoderWidth,\n  geocoderTop,\n  geocoderRight,\n  geocoderInputHeight,\n\n  // Map Control\n  mapControlTop,\n\n  // Plot\n  rangeBrushBgd,\n  histogramFillInRange,\n  histogramFillOutRange,\n  histogramOverlayColor,\n  histogramBreakLineColor,\n  axisFontSize,\n  axisFontColor,\n  timeTitleFontSize,\n  rangePlotMargin,\n  rangePlotMarginLarge,\n  rangePlotH,\n  rangePlotHLarge,\n  rangePlotHLargePalm,\n  rangePlotContainerH,\n  rangePlotContainerHLarge,\n  rangePlotContainerHLargePalm,\n\n  // Notifications\n  notificationColors,\n  notificationPanelWidth,\n  notificationPanelItemWidth,\n  notificationPanelItemHeight,\n\n  // Data Table\n  headerRowHeight,\n  headerRowWStatsHeight,\n  headerStatsControlHeight,\n  rowHeight,\n  headerPaddingTop,\n  headerPaddingBottom,\n  cellPaddingSide,\n  edgeCellPaddingSide,\n  cellFontSize,\n  gridPaddingSide,\n  optionButtonColor,\n  headerCellBackground,\n  headerCellBorderColor,\n  headerCellStatsBackground,\n  headerCellStatsControlBackground,\n  headerCellIconColor,\n  cellBorderColor,\n  evenRowBackground,\n  oddRowBackground,\n  pinnedGridBorderColor,\n  // time display\n  timeDisplayBorderRadius,\n  timeDisplayHeight,\n  timeDisplayMinWidth,\n  timeDisplayOpacity,\n  timeDisplayPadding,\n\n  // export map\n  exportIntraSectionMargin,\n\n  // Action Panel\n  actionPanelWidth,\n  actionPanelHeight,\n\n  // Breakpoints\n  breakPoints,\n\n  // progressbar\n  progressBarColor,\n  progressBarTrackColor,\n\n  // layerConfigGroupLabel\n  layerConfigGroupLabelBorderLeft,\n  layerConfigGroupLabelMargin,\n  layerConfigGroupLabelPadding,\n  layerConfigGroupColor,\n\n  // layerConfigGroupLabel label\n  layerConfigGroupLabelLabelMargin,\n\n  // StyledConfigGroupHeader\n  styledConfigGroupHeaderBorder,\n\n  // layerConfigurator\n  layerConfiguratorBorder,\n  layerConfiguratorBorderColor,\n  layerConfiguratorMargin,\n  layerConfiguratorPadding,\n\n  // Styled token\n  fieldTokenRightMargin,\n  fieldTokenHeight,\n  fieldTokenWidth,\n\n  // COLORS\n  BLUE2: 'rgba(85, 88, 219, 0.2)',\n\n  // AI Assistant Panel\n  aiAssistantPanelWidth,\n\n  // Effect panel\n  effectPanelWidth,\n  effectPanelHeight,\n  effectPanelPaddingSide,\n  effectPanelPaddingTop,\n  rightPanelMarginTop,\n  rightPanelMarginRight,\n  effectPanelAddEffectFontFamily,\n\n  // effect type selector\n  effectTypeIconMarginSide,\n  effectTypeIconSizeL,\n  effectTypeIconBgHoverColor,\n\n  // effectConfigurator\n  effectConfiguratorMargin,\n  effectConfiguratorPadding,\n  effectPanelTextMain,\n  effectPanelTextSecondary1,\n  effectPanelTextSecondary2,\n  effectPanelTextSecondary3,\n\n  effectPanelElementColor,\n  effectPanelElementColorActive,\n  effectPanelElementColorHovered,\n  effectPanelElementColorSun,\n  effectPanelElementColorSelected\n};\n\nexport const themeLT = {\n  ...theme,\n  // template\n  activeColor: activeColorLT,\n  input: inputLT,\n  textColor: textColorLT,\n  sidePanelBg: '#FFFFFF',\n  selectColor: selectColorLT,\n  titleTextColor: '#000000',\n  sidePanelHeaderBg: '#F7F7F7',\n  subtextColorActive: activeColorLT,\n  tooltipBg: tooltipBgLT,\n  tooltipColor: tooltipColorLT,\n  dropdownListBgd: '#FFFFFF',\n  toolbarItemBgdHover: '#F7F7F7',\n  textColorHl: activeColorLT,\n\n  inputBgd: '#F7F7F7',\n  inputBgdHover: '#FFFFFF',\n  inputBgdActive: '#FFFFFF',\n  inputBgdActiveLT: '#FFFFFF',\n  dropdownListHighlightBg: '#F0F0F0',\n  toolbarItemIconHover: activeColorLT,\n  panelBackground: '#F7F7F7',\n  panelContentBackground: '#F7F7F7',\n  bottomWidgetBgd: '#F7F7F7',\n  panelBackgroundHover: '#F7F7F7',\n  panelBorderColor: '#D3D8E0',\n  panelHeaderIconActive: '#000000',\n  panelHeaderIconHover: '#000000',\n  sideBarCloseBtnBgd: '#F7F7F7',\n  sideBarCloseBtnColor: textColorLT,\n  sideBarCloseBtnBgdHover: '#F7F7F7',\n\n  secondaryInputBgd: '#F7F7F7',\n  secondaryInputBgdActive: '#F7F7F7',\n  secondaryInputBgdHover: '#FFFFFF',\n  secondaryInputBorderActiveColor: '#000000',\n  secondaryInputBorderColor: 'none',\n  secondaryInputColor: '#545454',\n\n  chickletBgd: '#F7F7F7',\n  mapPanelBackgroundColor: '#FFFFFF',\n  mapPanelHeaderBackgroundColor: '#F7F7F7',\n\n  sliderBarColor: '#A0A7B4',\n  sliderBarBgd: '#D3D8E0',\n  sliderHandleColor: '#F7F7F7',\n  sliderInactiveBorderColor: '#F7F7F7',\n  sliderHandleTextColor: '#F7F7F7',\n  sliderHandleHoverColor: '#F7F7F7',\n\n  subtextColor: subtextColorLT,\n  switchBtnBgd: '#F7F7F7',\n  secondarySwitchBtnBgd: '#F7F7F7',\n  secondarySwitchTrackBgd: '#D3D8E0',\n  switchBtnBgdActive: '#F7F7F7',\n  switchTrackBgd: '#D3D8E0',\n  switchTrackBgdActive: activeColorLT,\n\n  // button switch background and hover color\n  primaryBtnBgd: primaryBtnActBgd,\n  primaryBtnActBgd: primaryBtnBgd,\n  primaryBtnBgdHover: primaryBtnBgd,\n\n  secondaryBtnBgd: secondaryBtnActBgd,\n  secondaryBtnActBgd: secondaryBtnBgd,\n  secondaryBtnBgdHover: secondaryBtnBgd,\n\n  floatingBtnBgd: '#F7F7F7',\n  floatingBtnActBgd: '#F7F7F7',\n  floatingBtnBgdHover: '#F7F7F7',\n  floatingBtnColor: subtextColor,\n  floatingBtnActColor: activeColorLT,\n\n  linkBtnActColor: textColorLT,\n\n  rangeBrushBgd: '#D3D8E0',\n  histogramFillInRange: activeColorLT,\n  histogramFillOutRange: '#A0A7B4',\n  axisFontColor: textColorLT\n};\n\nexport const themeBS = {\n  ...theme,\n  activeColor: '#E2E2E2',\n  dropdownListBgd: '#FFFFFF',\n  toolbarItemBgdHover: '#F7F7F7',\n  dropdownListBorderTop: 'none',\n  dropdownListHighlightBg: '#F6F6F6',\n  toolbarItemIconHover: '#000000',\n  inputBgd: '#E2E2E2',\n  inputBgdActive: '#E2E2E2',\n  inputBgdHover: 'inherit',\n  inputBorderActiveColor: '#000000',\n  inputColor: '#000000',\n  chickletBgd: '#E2E2E2',\n  panelBackground: '#FFFFFF',\n  panelBackgroundHover: '#EEEEEE',\n  panelContentBackground: '#FFFFFF',\n  bottomWidgetBgd: '#F7F7F7',\n  panelHeaderIconActive: '#000000',\n  panelHeaderIconHover: '#000000',\n  panelBorderColor: '#E2E2E2',\n  primaryBtnBgd: '#E2E2E2',\n  primaryBtnBgdHover: '#333333',\n  primaryBtnColor: '#000000',\n  secondaryBtnActBgd: '#EEEEEE',\n  secondaryBtnActColor: '#000000',\n  secondaryBtnBgd: '#E2E2E2',\n  secondaryBtnBgdHover: '#CBCBCB',\n  ctnBtnBgd: '#E2E2E2',\n  ctnBtnBgdHover: '333333',\n  ctnBtnColor: '#000000',\n  ctnBtnActColor: '#000000',\n\n  sideBarCloseBtnBgd: '#E2E2E2',\n  sideBarCloseBtnColor: '#000000',\n  sideBarCloseBtnBgdHover: '#FFFFFF',\n\n  floatingBtnBgd: '#FFFFFF',\n  floatingBtnActBgd: '#EEEEEE',\n  floatingBtnBgdHover: '#EEEEEE',\n  floatingBtnColor: '#757575',\n  floatingBtnActColor: '#000000',\n\n  secondaryBtnColor: '#000000',\n  secondaryInputBgd: '#F6F6F6',\n  secondaryInputBgdActive: '#F6F6F6',\n  secondaryInputBgdHover: '#F6F6F6',\n  secondaryInputBorderActiveColor: '#000000',\n  secondaryInputBorderColor: 'none',\n  secondaryInputColor: '#545454',\n  sidePanelBg: '#F6F6F6',\n  sidePanelHeaderBg: '#FFFFFF',\n  subtextColor: '#AFAFAF',\n  panelTabColor: '#AFAFAF',\n  subtextColorActive: '#000000',\n  textColor: '#000000',\n  textColorHl: '#545454',\n  mapPanelBackgroundColor: '#F6F6F6',\n  mapPanelHeaderBackgroundColor: '#FFFFFF',\n  titleTextColor: '#000000',\n  switchBtnBgdActive: '#000000',\n  switchBtnBgd: '#FFFFFF',\n  switchTrackBgdActive: '#E2E2E2',\n  secondarySwitchTrackBgd: '#E2E2E2',\n  switchTrackBgd: '#E2E2E2',\n  secondarySwitchBtnBgd: '#FFFFFF',\n  histogramFillInRange: '#000000',\n  histogramFillOutRange: '#E2E2E2',\n  rangeBrushBgd: '#E2E2E2',\n  sliderBarBgd: '#E2E2E2',\n  sliderHandleColor: '#FFFFFF',\n  sliderInactiveBorderColor: '#FFFFFF',\n  sliderHandleTextColor: '#FFFFFF',\n  sliderBarColor: '#000000'\n};\n"
  },
  {
    "path": "src/styles/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './base';\nexport * from './media-breakpoints';\n"
  },
  {
    "path": "src/styles/src/media-breakpoints.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {css} from 'styled-components';\n\n// These are useful for test or when theme doesn't define them\nexport const breakPointValues = {\n  palm: 588,\n  desk: 768\n};\n\n/**\n * Contains media rules for different device types\n * @namespace\n * @property {object}  media\n * @property {string}  media.palm - rule for palm devices\n * @property {string}  media.portable - rule for portable devices\n * @property {string}  media.desk - rule for desktops\n */\n\nexport const media = {\n  palm: (first, ...args) => css`\n    @media (max-width: ${props => (props.theme.breakPoints || breakPointValues).palm}px) {\n      ${css(first, ...args)};\n    }\n  `,\n\n  portable: (first, ...args) => css`\n    @media (max-width: ${props => (props.theme.breakPoints || breakPointValues).desk}px) {\n      ${css(first, ...args)};\n    }\n  `,\n\n  desk: (first, ...args) => css`\n    @media (min-width: ${props => (props.theme.breakPoints || breakPointValues).desk + 1}px) {\n      ${css(first, ...args)};\n    }\n  `\n};\n"
  },
  {
    "path": "src/styles/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\" //TODO change once all dependencies are isolated\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/styles/webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.ts')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "src/table/UPGRADE-data-container.md",
    "content": "# Upgrade Guide (Data Containers)\n\n### Major changes\n- `KeplerTable.allData: any[][]` is substituted with `KeplerTable.dataContainer: DataContainerInterface`.\n\n### Step by step upgrade\n| File | Function | Change |\n| ---- | --- | --- |\n| base-layer.js & extended layers | accessors | Accessors in default Kepler layers now expect a data container as one of parameters: ```const pointPosAccessor = config => dc => d => {return ...}``` where `dc` is an instance of `DataContainerInterface`, and `d` is an object that is expected to contain an index of a row in the data container. | \n|            | updateLayerMeta() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | getPointsBounds() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | calculateDataAttribute() | 1st parameter has to be an instance of `KeplerTable`. |\n|            | getAttributeAccessors() | 1st parameter is now an object that to contains `dataContainer: : DataContainerInterface`. |\n|            | getHoverData() |  New 2nd parameter, an instance of `DataContainerInterface`. |\n|            | getPositionAccessor() | 1st parameter has to be an instance of `DataContainerInterface`. |\n|            | setInitialLayerConfig() | 1st parameter is now object expected to contain `dataContainer: : DataContainerInterface`.  |\n|            | findDefaultLayerProps() | 2nd parameter has to be an instance of `DataContainerInterface`.  |\n| cell-size.js | renderedSize() | `text.rows` is substituted with `text.dataContainer: DataContainerInterface`. |\n| data-table/index.js |  | `DataTable.props.rows` is substituted with `DataTable.props.dataContainer: DataContainerInterface`. |\n| layer-text-label.js | formatTextLabelData() | `dataContainer: DataContainerInterface` is expected as part of the 1st parameter. |\n| geojson-utils.js | getGeojsonDataMaps() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| h3-utils.js | isHexField() | 3rd parameter has to be an instance of `DataContainerInterface`. |\n|             | getHexFields() | 2nd parameter has to be an instance of `DataContainerInterface`. |\n| mapbox-utils.js | geoJsonFromData() | `allData` is removed from the parameter list. `getGeometry`, `getProperties` parameters expect `{index}` object as input parameter. |\n| trip-utils.js | isTripGeoJsonField() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| data-processor.js | formatCsv() | `data` parameter has to be an instance of `DataContainerInterface`. |\n| data-scale-utils.js | getOrdinalDomain() | 1st parameter has to be an instance of `DataContainerInterface`. |\n| filter-utils.js | getFilterFunction() | New 5th parameter, an instance of `DataContainerInterface`. Returned function expects `{index}` object as 1st parameter.  |\n|                 | filterDataByFilterTypes() | 2nd parameter has to be an instance of `DataContainerInterface`. |\t\n|                 | getTimestampFieldDomain() | 1st parameter has to be an instance of `DataContainerInterface`. valueAccessor parameter expects a function that accepts `{index}` object as 1st parameter. |\t\t\n|                 | getNumericFieldDomain() |\t1st parameter has to be an instance of `DataContainerInterface`. valueAccessor parameter expects a function that accepts `{index}` object as 1st parameter. |\n|                 | getPolygonFilterFunctor() | New 3rd parameter, an instance of `DataContainerInterface`. |\n| interaction-utils.js | getTooltipDisplayDeltaValue() | data and primaryData parameters are now of `DataRow` type. |\n|                      | getTooltipDisplayValue() | data parameter is now of `DataRow` type. |\n| comparison-utils.js |\tcmpGpuFilterProp() | New 4th parameter, an instance of `DataContainerInterface`. |\n| gpu-filter-utils.js | getGpuFilterProps() | Returned function now expects a data container. ```dc => (getIndex, getData) => d => {...}``` as parameter to the first call. |\n| data-utils.js | maybeToDate() | New 4th parameter, an instance of `DataContainerInterface`. |\n| kepler-table.js | Field.valueAccessor() | 1st parameter is expected to be an object that contain index property. | "
  },
  {
    "path": "src/table/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/table/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/table\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@kepler.gl/utils\": \"3.2.6\",\n    \"@loaders.gl/mvt\": \"^4.3.2\",\n    \"@loaders.gl/pmtiles\": \"^4.3.2\",\n    \"@types/d3-array\": \"^2.8.0\",\n    \"@types/lodash\": \"4.17.5\",\n    \"d3-array\": \"^2.8.0\",\n    \"global\": \"^4.3.0\",\n    \"lodash\": \"4.17.21\",\n    \"moment\": \"^2.10.6\",\n    \"react-palm\": \"^3.3.8\",\n    \"type-analyzer\": \"0.4.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/table/src/dataset-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport uniq from 'lodash/uniq';\nimport KeplerTable, {Datasets} from './kepler-table';\nimport {ProtoDataset, RGBColor} from '@kepler.gl/types';\nimport Task from 'react-palm/tasks';\n\nimport {\n  DatasetType,\n  RasterTileDatasetMetadata,\n  PMTilesType,\n  RemoteTileFormat,\n  VectorTileDatasetMetadata,\n  WMSDatasetMetadata\n} from '@kepler.gl/constants';\nimport {\n  hexToRgb,\n  validateInputData,\n  datasetColorMaker,\n  getApplicationConfig\n} from '@kepler.gl/utils';\n\nimport {load} from '@loaders.gl/core';\nimport {/* MVTSource,*/ TileJSON} from '@loaders.gl/mvt';\nimport {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';\nimport {WMSCapabilities, WMSCapabilitiesLoader} from '@loaders.gl/wms';\n\nimport {getMVTMetadata} from './tileset/tileset-utils';\nimport {parseRasterMetadata} from './tileset/raster-tile-utils';\nimport {\n  parseVectorMetadata,\n  getFieldsFromTile,\n  VectorTileMetadata\n} from './tileset/vector-tile-utils';\n\n// apply a color for each dataset\n// to use as label colors\nconst datasetColors = [\n  '#8F2FBF',\n  '#005CFF',\n  '#C06C84',\n  '#F8B195',\n  '#547A82',\n  '#3EACA8',\n  '#A2D4AB'\n].map(hexToRgb);\n\nexport function getNewDatasetColor(datasets: Datasets): RGBColor {\n  const presetColors = datasetColors.map(String);\n  const usedColors = uniq(Object.values(datasets).map(d => String(d.color))).filter(c =>\n    presetColors.includes(c)\n  );\n\n  if (usedColors.length === presetColors.length) {\n    // if we already depleted the pool of color\n    return datasetColorMaker.next().value;\n  }\n\n  let color = datasetColorMaker.next().value;\n  while (usedColors.includes(String(color))) {\n    color = datasetColorMaker.next().value;\n  }\n\n  return color;\n}\n\n/**\n * Take datasets payload from addDataToMap, create datasets entry save to visState\n */\nexport function createNewDataEntry(\n  {info, data, ...opts}: ProtoDataset,\n  datasets: Datasets = {}\n): Datasets | null {\n  const TableClass = getApplicationConfig().table ?? KeplerTable;\n  let dataValidator = validateInputData;\n  if (typeof TableClass.getInputDataValidator === 'function') {\n    dataValidator = TableClass.getInputDataValidator();\n  }\n\n  const validatedData = dataValidator(data);\n  if (!validatedData) {\n    return null;\n  }\n\n  // check if dataset already exists, and update it when loading data by batches incrementally\n  if (info && info.id && datasets[info.id]) {\n    // get keplerTable from datasets\n    const keplerTable = datasets[info.id];\n    // update the data in keplerTable\n    return UPDATE_TABLE_TASK({table: keplerTable, data: validatedData});\n  }\n\n  info = info || {};\n  const color = info.color || getNewDatasetColor(datasets);\n\n  return CREATE_TABLE_TASK({\n    info,\n    color,\n    opts,\n    data: data.arrowTable ? {...validatedData, arrowTable: data.arrowTable} : validatedData\n  });\n}\n\nasync function updateTable({table, data}) {\n  const updated = await table.update(data); // Assuming `table` has an `update` method\n  return updated;\n}\n\ntype CreateTableProps = {\n  info: any;\n  color: RGBColor;\n  opts: {\n    metadata?: Record<string, unknown>;\n  };\n  data: any;\n};\n\nasync function createTable(datasetInfo: CreateTableProps) {\n  const {info, color, opts, data} = datasetInfo;\n\n  // update metadata for remote tiled datasets\n  const refreshedMetadata = await refreshRemoteData(datasetInfo);\n  let metadata = opts.metadata;\n  if (refreshedMetadata) {\n    metadata = {...opts.metadata, ...refreshedMetadata};\n    if (metadata.fields) {\n      data.fields = metadata.fields;\n    }\n  }\n\n  const TableClass = getApplicationConfig().table ?? KeplerTable;\n  const table = new TableClass({\n    info,\n    color,\n    ...opts,\n    metadata\n  });\n  try {\n    await table.importData({data});\n  } catch (error) {\n    console.error('Failed to create table', error);\n    throw error;\n  }\n\n  return table;\n}\nconst UPDATE_TABLE_TASK = Task.fromPromise(updateTable, 'UPDATE_TABLE_TASK');\nconst CREATE_TABLE_TASK = Task.fromPromise(createTable, 'CREATE_TABLE_TASK');\n\n/**\n * Fetch metadata for vector tile layers using tilesetMetadataUrl from metadata\n * @param datasetInfo\n * @returns\n */\nasync function refreshRemoteData(datasetInfo: CreateTableProps): Promise<object | null> {\n  const {type} = datasetInfo.info;\n  switch (type) {\n    case DatasetType.VECTOR_TILE:\n      return await refreshVectorTileMetadata(datasetInfo);\n    case DatasetType.RASTER_TILE:\n      return await refreshRasterTileMetadata(datasetInfo);\n    case DatasetType.WMS_TILE:\n      return await refreshWMSMetadata(datasetInfo);\n    default:\n      return null;\n  }\n}\n\nasync function refreshVectorTileMetadata(\n  datasetInfo: CreateTableProps\n): Promise<VectorTileMetadata | null> {\n  const {remoteTileFormat, tilesetMetadataUrl, tilesetDataUrl} =\n    (datasetInfo.opts.metadata as VectorTileDatasetMetadata) || {};\n\n  if (\n    !(remoteTileFormat === RemoteTileFormat.PMTILES || remoteTileFormat === RemoteTileFormat.MVT) ||\n    typeof tilesetMetadataUrl !== 'string' ||\n    typeof tilesetDataUrl !== 'string'\n  ) {\n    return null;\n  }\n\n  try {\n    let rawMetadata: PMTilesMetadata | TileJSON | null = null;\n    if (remoteTileFormat === RemoteTileFormat.MVT) {\n      rawMetadata = await getMVTMetadata(tilesetMetadataUrl);\n    } else {\n      const tileSource = PMTilesSource.createDataSource(tilesetMetadataUrl, {});\n      rawMetadata = await tileSource.metadata;\n    }\n\n    if (rawMetadata) {\n      const metadata = parseVectorMetadata(rawMetadata);\n\n      await getFieldsFromTile({\n        remoteTileFormat,\n        tilesetUrl: tilesetDataUrl,\n        metadataUrl: tilesetMetadataUrl,\n        metadata\n      });\n\n      return metadata;\n    }\n  } catch (err) {\n    // ignore for now, and use old metadata\n  }\n  return null;\n}\n\nasync function refreshRasterTileMetadata(datasetInfo: CreateTableProps): Promise<any | null> {\n  const {metadataUrl, pmtilesType} = (datasetInfo.opts.metadata as RasterTileDatasetMetadata) || {};\n\n  if (typeof metadataUrl !== 'string') {\n    return null;\n  }\n\n  try {\n    if (pmtilesType === PMTilesType.RASTER) {\n      const tileSource = PMTilesSource.createDataSource(metadataUrl, {});\n      const rawMetadata: PMTilesMetadata = await tileSource.metadata;\n\n      if (rawMetadata) {\n        return parseVectorMetadata(rawMetadata);\n      }\n    } else {\n      // it's stac raster tiles\n      const response = await fetch(metadataUrl);\n      if (!response.ok) {\n        throw new Error(`Failed Fetch ${metadataUrl}`);\n      }\n      const rawMetadata = await response.json();\n\n      const metadata = parseRasterMetadata(rawMetadata, {allowCollections: true});\n      if (metadata instanceof Error) {\n        throw new Error(`Failed to parse metadata ${metadata.message}`);\n      }\n\n      return metadata;\n    }\n  } catch (err) {\n    // ignore for now, and use old metadata\n  }\n  return null;\n}\n\nasync function refreshWMSMetadata(datasetInfo: CreateTableProps): Promise<any | null> {\n  const {remoteTileFormat, tilesetDataUrl} =\n    (datasetInfo.opts.metadata as WMSDatasetMetadata) || {};\n\n  if (remoteTileFormat !== RemoteTileFormat.WMS || typeof tilesetDataUrl !== 'string') {\n    return null;\n  }\n\n  try {\n    const data = await getWMSCapabilities(tilesetDataUrl);\n    return wmsCapabilitiesToDatasetMetadata(data);\n  } catch (err) {\n    // ignore for now, and use old metadata\n  }\n  return null;\n}\n\nexport async function getWMSCapabilities(wsmUrl: string): Promise<WMSCapabilities> {\n  return (await load(\n    `${wsmUrl}?service=WMS&request=GetCapabilities`,\n    WMSCapabilitiesLoader\n  )) as WMSCapabilities;\n}\n\nexport function wmsCapabilitiesToDatasetMetadata(capabilities: WMSCapabilities): any | null {\n  // Flatten layers if they are nested\n  const layers = capabilities.layers.flatMap(layer => {\n    if (layer.layers && layer.layers.length > 0) {\n      return layer.layers;\n    }\n    return layer;\n  });\n\n  let availableLayers: WMSDatasetMetadata['layers'] = [];\n  if (Array.isArray(layers)) {\n    availableLayers = layers.map((layer: any) => {\n      const bb = layer.geographicBoundingBox;\n\n      let boundingBox: number[] | null = null;\n      if (Array.isArray(bb) && Array.isArray(bb[0]) && Array.isArray(bb[1])) {\n        boundingBox = [bb[0][0], bb[0][1], bb[1][0], bb[1][1]];\n      }\n\n      return {\n        name: layer.name,\n        title: layer.title || layer.name,\n        boundingBox,\n        queryable: layer.queryable\n      };\n    });\n  }\n\n  return {\n    layers: availableLayers,\n    version: capabilities.version || '1.3.0'\n  };\n}\n"
  },
  {
    "path": "src/table/src/gpu-filter-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport moment from 'moment';\nimport {MAX_GPU_FILTERS, FILTER_TYPES} from '@kepler.gl/constants';\nimport {Field, Filter} from '@kepler.gl/types';\nimport {set, DataContainerInterface} from '@kepler.gl/utils';\nimport {toArray, notNullorUndefined} from '@kepler.gl/common-utils';\n\nimport {GpuFilter} from './kepler-table';\n\n/**\n * Set gpu mode based on current number of gpu filters exists\n */\nexport function setFilterGpuMode(filter: Filter, filters: Filter[]) {\n  // filter can be applied to multiple datasets, hence gpu filter mode should also be\n  // an array, however, to keep us sane, for now, we only check if there is available channel for every dataId,\n  // if all of them has, we set gpu mode to true\n  // TODO: refactor filter so we don't keep an array of everything\n\n  filter.dataId.forEach(dataId => {\n    const gpuFilters = filters.filter(f => f.dataId.includes(dataId) && f.gpu);\n\n    if (filter.gpu && gpuFilters.length === MAX_GPU_FILTERS) {\n      set(['gpu'], false, filter);\n    }\n  });\n\n  return filter;\n}\n\n/**\n * Scan though all filters and assign gpu chanel to gpu filter\n */\nexport function assignGpuChannels(allFilters: Filter[]) {\n  return allFilters.reduce((accu, f, index) => {\n    let filters = accu;\n\n    // if gpu is true assign and validate gpu Channel\n    if (f.gpu) {\n      f = assignGpuChannel(f, accu);\n      filters = set([index], f, accu);\n    }\n\n    return filters;\n  }, allFilters);\n}\n/**\n * Assign a new gpu filter a channel based on first availability\n */\nexport function assignGpuChannel(filter: Filter, filters: Filter[]) {\n  // find first available channel\n  if (!filter.gpu) {\n    return filter;\n  }\n\n  const gpuChannel = filter.gpuChannel || [];\n\n  filter.dataId.forEach((dataId, datasetIdx) => {\n    const findGpuChannel = channel => f => {\n      const dataIdx = toArray(f.dataId).indexOf(dataId);\n      return (\n        f.id !== filter.id && dataIdx > -1 && f.gpu && toArray(f.gpuChannel)[dataIdx] === channel\n      );\n    };\n\n    if (\n      Number.isFinite(gpuChannel[datasetIdx]) &&\n      !filters.find(findGpuChannel(gpuChannel[datasetIdx]))\n    ) {\n      // if value is already assigned and valid\n      return;\n    }\n\n    let i = 0;\n\n    while (i < MAX_GPU_FILTERS) {\n      if (!filters.find(findGpuChannel(i))) {\n        gpuChannel[datasetIdx] = i;\n        return;\n      }\n      i++;\n    }\n  });\n\n  // if cannot find channel for all dataid, set gpu back to false\n  // TODO: refactor filter to handle same filter different gpu mode\n  if (!gpuChannel.length || !gpuChannel.every(Number.isFinite)) {\n    return {\n      ...filter,\n      gpu: false\n    };\n  }\n\n  return {\n    ...filter,\n    gpuChannel\n  };\n}\n/**\n * Edit filter.gpu to ensure that only\n * X number of gpu filers can coexist.\n */\nexport function resetFilterGpuMode(filters: Filter[]): Filter[] {\n  const gpuPerDataset = {};\n\n  return filters.map(f => {\n    if (f.gpu) {\n      let gpu = true;\n      toArray(f.dataId).forEach(dataId => {\n        const count = gpuPerDataset[dataId];\n\n        if (count === MAX_GPU_FILTERS) {\n          gpu = false;\n        } else {\n          gpuPerDataset[dataId] = count ? count + 1 : 1;\n        }\n      });\n\n      if (!gpu) {\n        return set(['gpu'], false, f);\n      }\n    }\n\n    return f;\n  });\n}\n\n/**\n * Initial filter uniform\n */\nfunction getEmptyFilterRange() {\n  return new Array(MAX_GPU_FILTERS).fill(0).map(() => [0, 0]);\n}\n\n/**\n * Returns index of the data element.\n * @param {any} d Data element with row index info.\n * @returns number\n */\nconst defaultGetIndex = d => d.index;\n\n/**\n * Returns value at the specified row from the data container.\n * @param dc Data container.\n * @param d Data element with row index info.\n * @param fieldIndex Column index in the data container.\n * @returns\n */\nconst defaultGetData = (dc: DataContainerInterface, d: any, fieldIndex: number) => {\n  return dc.valueAt(d.index, fieldIndex);\n};\n\nconst getFilterValueAccessor =\n  (channels: (Filter | undefined)[], dataId: string, fields: any[]) =>\n  (dc: DataContainerInterface) =>\n  (getIndex = defaultGetIndex, getData = defaultGetData) =>\n  (d, objectInfo?: {index: number}) => {\n    // for empty channel, value is 0 and min max would be [0, 0]\n    const channelValues = channels.map(filter => {\n      if (!filter) {\n        return 0;\n      }\n      const fieldIndex = getDatasetFieldIndexForFilter(dataId, filter);\n      const field = fields[fieldIndex];\n\n      let value;\n      // d can be undefined when called from attribute updater from deck,\n      // when data is an ArrowTable, so use objectInfo instead.\n      const data = getData(dc, d || objectInfo, fieldIndex);\n      if (typeof data === 'function') {\n        value = data(field);\n      } else {\n        value =\n          filter.type === FILTER_TYPES.timeRange\n            ? field.filterProps && Array.isArray(field.filterProps.mappedValue)\n              ? field.filterProps.mappedValue[getIndex(d)]\n              : moment.utc(data).valueOf()\n            : data;\n      }\n\n      return notNullorUndefined(value)\n        ? Array.isArray(value)\n          ? value.map(v => v - filter.domain?.[0])\n          : value - filter.domain?.[0]\n        : Number.MIN_SAFE_INTEGER;\n    });\n\n    // TODO: can we refactor the above to avoid the transformation below?\n    const arrChannel = channelValues.find(v => Array.isArray(v));\n    if (Array.isArray(arrChannel)) {\n      // Convert info form supported by DataFilterExtension (relevant for TripLayer)\n      const vals: number[][] = [];\n      // if there are multiple arrays, they should have the same length\n      for (let i = 0; i < arrChannel.length; i++) {\n        vals.push(channelValues.map(v => (Array.isArray(v) ? v[i] : v)));\n      }\n      return vals;\n    }\n\n    return channelValues;\n  };\n\nfunction isFilterTriggerEqual(a, b) {\n  return a === b || (a?.name === b?.name && a?.domain0 === b?.domain0);\n}\n\n/**\n * Get filter properties for gpu filtering\n */\nexport function getGpuFilterProps(\n  filters: Filter[],\n  dataId: string,\n  fields: Field[],\n  oldGpuFilter?: GpuFilter\n): GpuFilter {\n  const filterRange = getEmptyFilterRange();\n  const triggers: GpuFilter['filterValueUpdateTriggers'] = {};\n\n  // array of filter for each channel, undefined, if no filter is assigned to that channel\n  const channels: (Filter | undefined)[] = [];\n\n  for (let i = 0; i < MAX_GPU_FILTERS; i++) {\n    const filter = filters.find(\n      f =>\n        f.gpu &&\n        f.dataId.includes(dataId) &&\n        f.gpuChannel &&\n        f.gpuChannel[f.dataId.indexOf(dataId)] === i\n    );\n\n    filterRange[i][0] = filter ? filter.value[0] - filter.domain?.[0] : 0;\n    filterRange[i][1] = filter ? filter.value[1] - filter.domain?.[0] : 0;\n    const oldFilterTrigger = oldGpuFilter?.filterValueUpdateTriggers?.[`gpuFilter_${i}`] || null;\n\n    const trigger = filter\n      ? {\n          name: filter.name[filter.dataId.indexOf(dataId)],\n          domain0: filter.domain?.[0]\n        }\n      : null;\n    // don't create a new object, cause deck.gl use shallow compare\n    triggers[`gpuFilter_${i}`] = isFilterTriggerEqual(trigger, oldFilterTrigger)\n      ? oldFilterTrigger\n      : trigger;\n    channels.push(filter);\n  }\n\n  const filterValueAccessor = getFilterValueAccessor(channels, dataId, fields);\n\n  return {\n    filterRange,\n    filterValueUpdateTriggers: triggers,\n    filterValueAccessor\n  };\n}\n\n/**\n * Return dataset field index from filter.fieldIdx\n * The index matches the same dataset index for filter.dataId\n */\nexport function getDatasetFieldIndexForFilter(dataId: string, filter: Filter): number {\n  const datasetIndex = toArray(filter.dataId).indexOf(dataId);\n  if (datasetIndex < 0) {\n    return -1;\n  }\n\n  const fieldIndex = filter.fieldIdx[datasetIndex];\n\n  return notNullorUndefined(fieldIndex) ? fieldIndex : -1;\n}\n"
  },
  {
    "path": "src/table/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {\n  default,\n  default as KeplerTable,\n  findPointFieldPairs,\n  copyTableAndUpdate,\n  pinTableColumns,\n  sortDatasetByColumn,\n  copyTable,\n  maybeToDate\n} from './kepler-table';\n/* eslint-disable prettier/prettier */\nexport type {\n  BooleanFieldFilterProps,\n  Datasets,\n  FilterProps,\n  GpuFilter,\n  NumericFieldFilterProps,\n  StringFieldFilterProps,\n  TimeFieldFilterProps\n} from './kepler-table';\nexport * from './gpu-filter-utils';\nexport * from './dataset-utils';\nexport * from './tileset/tileset-utils';\nexport * from './tileset/vector-tile-utils';\nexport * from './tileset/raster-tile-utils';\n"
  },
  {
    "path": "src/table/src/kepler-table.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Console from 'global/console';\nimport {ascending, descending} from 'd3-array';\n\nimport {\n  TRIP_POINT_FIELDS,\n  SORT_ORDER,\n  ALL_FIELD_TYPES,\n  ALTITUDE_FIELDS,\n  SCALE_TYPES\n} from '@kepler.gl/constants';\nimport {\n  RGBColor,\n  Field,\n  FieldPair,\n  FieldDomain,\n  Filter,\n  ProtoDataset,\n  FilterRecord,\n  FilterDatasetOpt,\n  RangeFieldDomain,\n  SelectFieldDomain,\n  MultiSelectFieldDomain,\n  TimeRangeFieldDomain\n} from '@kepler.gl/types';\n\nimport {getGpuFilterProps, getDatasetFieldIndexForFilter} from './gpu-filter-utils';\n\nimport {\n  getSortingFunction,\n  timeToUnixMilli,\n  createDataContainer,\n  DataForm,\n  diffFilters,\n  filterDataByFilterTypes,\n  FilterResult,\n  getFilterFunction,\n  getFilterProps,\n  getFilterRecord,\n  getNumericFieldDomain,\n  getTimestampFieldDomain,\n  getLinearDomain,\n  getLogDomain,\n  getOrdinalDomain,\n  getQuantileDomain,\n  DataContainerInterface,\n  FilterChanged\n} from '@kepler.gl/utils';\nimport {generateHashId, notNullorUndefined} from '@kepler.gl/common-utils';\n\n// TODO isolate layer type, depends on @kepler.gl/layers\ntype Layer = any;\n\nexport type GpuFilter = {\n  filterRange: number[][];\n  filterValueUpdateTriggers: {\n    [id: string]: {name: string; domain0: number} | null;\n  };\n  filterValueAccessor: (\n    dc: DataContainerInterface\n  ) => (\n    getIndex?: (any) => number,\n    getData?: (dc_: DataContainerInterface, d: any, fieldIndex: number) => any\n  ) => (d: any, objectInfo?: {index: number}) => (number | number[])[];\n};\n\nexport type FilterProps =\n  | NumericFieldFilterProps\n  | BooleanFieldFilterProps\n  | StringFieldFilterProps\n  | TimeFieldFilterProps;\n\nexport type NumericFieldFilterProps = RangeFieldDomain & {\n  value: [number, number];\n  type: string;\n  typeOptions: string[];\n  gpu: boolean;\n  columnStats?: Record<string, any>;\n};\nexport type BooleanFieldFilterProps = SelectFieldDomain & {\n  type: string;\n  value: boolean;\n  gpu: boolean;\n  columnStats?: Record<string, any>;\n};\nexport type StringFieldFilterProps = MultiSelectFieldDomain & {\n  type: string;\n  value: string[];\n  gpu: boolean;\n  columnStats?: Record<string, any>;\n};\nexport type TimeFieldFilterProps = TimeRangeFieldDomain & {\n  type: string;\n  view: Filter['view'];\n  fixedDomain: boolean;\n  value: number[];\n  gpu: boolean;\n  columnStats?: Record<string, any>;\n};\n\n// Unique identifier of each field\nconst FID_KEY = 'name';\n\nexport function maybeToDate(\n  isTime: boolean,\n  fieldIdx: number,\n  format: string,\n  dc: DataContainerInterface,\n  // An object with row index or a materialized row array (for materialized hover info from trip layer)\n  d: {index: number} | any[]\n) {\n  if (isTime) {\n    return timeToUnixMilli(Array.isArray(d) ? d[fieldIdx] : dc.valueAt(d.index, fieldIdx), format);\n  }\n\n  return Array.isArray(d) ? d[fieldIdx] : dc.valueAt(d.index, fieldIdx);\n}\n\nclass KeplerTable<F extends Field = Field> {\n  readonly id: string;\n\n  type?: string;\n  label: string;\n  color: RGBColor;\n\n  // fields and data\n  fields: F[] = [];\n\n  dataContainer: DataContainerInterface;\n\n  allIndexes: number[] = [];\n  filteredIndex: number[] = [];\n  filteredIdxCPU?: number[];\n  filteredIndexForDomain: number[] = [];\n  fieldPairs: FieldPair[] = [];\n  gpuFilter: GpuFilter;\n  filterRecord?: FilterRecord;\n  filterRecordCPU?: FilterRecord;\n  changedFilters?: FilterChanged;\n\n  // table-injected metadata\n  sortColumn?: {\n    // column name: sorted idx\n    [key: string]: string; // ASCENDING | DESCENDING | UNSORT\n  };\n  sortOrder?: number[] | null;\n\n  pinnedColumns?: string[];\n  supportedFilterTypes?: string[] | null;\n  disableDataOperation?: boolean;\n\n  // table-injected metadata\n  metadata: Record<string, any>;\n\n  getFileProcessor?: (data: any, inputFormat?: string) => {data: any; format: string};\n\n  constructor({\n    info,\n    color,\n    metadata,\n    supportedFilterTypes = null,\n    disableDataOperation = false\n  }: {\n    info?: ProtoDataset['info'];\n    color: RGBColor;\n    metadata?: ProtoDataset['metadata'];\n    supportedFilterTypes?: ProtoDataset['supportedFilterTypes'];\n    disableDataOperation?: ProtoDataset['disableDataOperation'];\n  }) {\n    // TODO - what to do if validation fails? Can kepler handle exceptions?\n    // const validatedData = validateInputData(data);\n    // if (!validatedData) {\n    //   return this;\n    // }\n\n    const datasetInfo = {\n      id: generateHashId(4),\n      label: 'new dataset',\n      type: '',\n      ...info\n    };\n\n    const defaultMetadata = {\n      id: datasetInfo.id,\n      // @ts-ignore\n      format: datasetInfo.format || '',\n      label: datasetInfo.label || ''\n    };\n\n    this.id = datasetInfo.id;\n    this.type = datasetInfo.type;\n    this.label = datasetInfo.label;\n    this.color = color;\n    this.metadata = {\n      ...defaultMetadata,\n      ...metadata\n    };\n\n    this.supportedFilterTypes = supportedFilterTypes;\n    this.disableDataOperation = disableDataOperation;\n\n    this.dataContainer = createDataContainer([]);\n    this.gpuFilter = getGpuFilterProps([], this.id, [], undefined);\n  }\n\n  async importData({data}: {data: ProtoDataset['data']}) {\n    const dataContainerData = data.cols ? data.cols : data.rows;\n    const inputDataFormat = data.cols ? DataForm.COLS_ARRAY : DataForm.ROWS_ARRAY;\n\n    const dataContainer = createDataContainer(dataContainerData, {\n      fields: data.fields,\n      arrowTable: data.arrowTable,\n      inputDataFormat\n    });\n\n    const fields: Field[] = data.fields.map((f, i) => ({\n      ...f,\n      fieldIdx: i,\n      id: f.name,\n      displayName: f.displayName || f.name,\n      analyzerType: f.analyzerType || ALL_FIELD_TYPES.string,\n      format: f.format || '',\n      valueAccessor: getFieldValueAccessor(f, i, dataContainer)\n    }));\n\n    const allIndexes = dataContainer.getPlainIndex();\n    this.dataContainer = dataContainer;\n    this.allIndexes = allIndexes;\n    this.filteredIndex = allIndexes;\n    this.filteredIndexForDomain = allIndexes;\n    this.fieldPairs = findPointFieldPairs(fields);\n    // @ts-expect-error Make sure that fields satisfies F extends Field\n    this.fields = fields;\n    this.gpuFilter = getGpuFilterProps([], this.id, fields, undefined);\n  }\n\n  /**\n   * update table with new data\n   * @param data - new data e.g. the arrow data with new batches loaded\n   */\n  async update(data: ProtoDataset['data']) {\n    const dataContainerData = data.arrowTable ?? data.cols ?? data.rows;\n    this.dataContainer.update?.(dataContainerData);\n    this.allIndexes = this.dataContainer.getPlainIndex();\n    this.filteredIndex = this.allIndexes;\n    this.filteredIndexForDomain = this.allIndexes;\n\n    return this;\n  }\n\n  get length() {\n    return this.dataContainer.numRows();\n  }\n\n  /**\n   * Get field\n   * @param columnName\n   */\n  getColumnField(columnName: string): F | undefined {\n    const field = this.fields.find(fd => fd[FID_KEY] === columnName);\n    this._assetField(columnName, field);\n    return field;\n  }\n\n  /**\n   * Get fieldIdx\n   * @param columnName\n   */\n  getColumnFieldIdx(columnName: string): number {\n    const fieldIdx = this.fields.findIndex(fd => fd[FID_KEY] === columnName);\n    this._assetField(columnName, Boolean(fieldIdx > -1));\n    return fieldIdx;\n  }\n\n  /**\n   * Get displayFormat\n   * @param columnName\n   */\n  getColumnDisplayFormat(columnName) {\n    const field = this.fields.find(fd => fd[FID_KEY] === columnName);\n    this._assetField(columnName, field);\n    return field?.displayFormat;\n  }\n\n  /**\n   * Get the value of a cell\n   */\n  getValue(columnName: string, rowIdx: number): any {\n    const field = this.getColumnField(columnName);\n    return field ? field.valueAccessor({index: rowIdx}) : null;\n  }\n\n  /**\n   * Updates existing field with a new object\n   * @param fieldIdx\n   * @param newField\n   */\n  updateColumnField(fieldIdx: number, newField: F): void {\n    this.fields = Object.assign([...this.fields], {[fieldIdx]: newField});\n  }\n\n  /**\n   * Update dataset color by custom color\n   * @param newColor\n   */\n  updateTableColor(newColor: RGBColor): void {\n    this.color = newColor;\n  }\n\n  /**\n   * Save filterProps to field and retrieve it\n   * @param columnName\n   */\n  getColumnFilterProps(columnName: string): F['filterProps'] | null | undefined {\n    const fieldIdx = this.getColumnFieldIdx(columnName);\n    if (fieldIdx < 0) {\n      return null;\n    }\n    const field = this.fields[fieldIdx];\n    if (Object.prototype.hasOwnProperty.call(field, 'filterProps')) {\n      return field.filterProps;\n    }\n\n    const fieldDomain = this.getColumnFilterDomain(field);\n    if (!fieldDomain) {\n      return null;\n    }\n\n    const filterProps = getFilterProps(field, fieldDomain);\n    const newField = {\n      ...field,\n      filterProps\n    };\n\n    this.updateColumnField(fieldIdx, newField);\n\n    return filterProps;\n  }\n\n  /**\n   * Apply filters to dataset, return the filtered dataset with updated `gpuFilter`, `filterRecord`, `filteredIndex`, `filteredIndexForDomain`\n   * @param filters\n   * @param layers\n   * @param opt\n   */\n  filterTable(filters: Filter[], layers: Layer[], opt?: FilterDatasetOpt): KeplerTable<Field> {\n    const {dataContainer, id: dataId, filterRecord: oldFilterRecord, fields} = this;\n\n    // if there is no filters\n    const filterRecord = getFilterRecord(dataId, filters, opt || {});\n\n    this.filterRecord = filterRecord;\n    this.gpuFilter = getGpuFilterProps(filters, dataId, fields, this.gpuFilter);\n\n    this.changedFilters = diffFilters(filterRecord, oldFilterRecord);\n\n    if (!filters.length) {\n      this.filteredIndex = this.allIndexes;\n      this.filteredIndexForDomain = this.allIndexes;\n      return this;\n    }\n\n    // generate 2 sets of filter result\n    // filteredIndex used to calculate layer data\n    // filteredIndexForDomain used to calculate layer Domain\n    const shouldCalDomain = Boolean(this.changedFilters.dynamicDomain);\n    const shouldCalIndex = Boolean(this.changedFilters.cpu);\n\n    let filterResult: FilterResult = {};\n    if (shouldCalDomain || shouldCalIndex) {\n      const dynamicDomainFilters = shouldCalDomain ? filterRecord.dynamicDomain : null;\n      const cpuFilters = shouldCalIndex ? filterRecord.cpu : null;\n\n      const filterFuncs = filters.reduce((acc, filter) => {\n        const fieldIndex = getDatasetFieldIndexForFilter(this.id, filter);\n        const field = fieldIndex !== -1 ? fields[fieldIndex] : null;\n\n        return {\n          ...acc,\n          [filter.id]: getFilterFunction(field, this.id, filter, layers, dataContainer)\n        };\n      }, {});\n\n      filterResult = filterDataByFilterTypes(\n        {dynamicDomainFilters, cpuFilters, filterFuncs},\n        dataContainer\n      );\n    }\n\n    this.filteredIndex = filterResult.filteredIndex || this.filteredIndex;\n    this.filteredIndexForDomain =\n      filterResult.filteredIndexForDomain || this.filteredIndexForDomain;\n\n    return this;\n  }\n\n  /**\n   * Apply filters to a dataset all on CPU, assign to `filteredIdxCPU`, `filterRecordCPU`\n   * @param filters\n   * @param layers\n   */\n  filterTableCPU(filters: Filter[], layers: Layer[]): KeplerTable<Field> {\n    const opt = {\n      cpuOnly: true,\n      ignoreDomain: true\n    };\n\n    // no filter\n    if (!filters.length) {\n      this.filteredIdxCPU = this.allIndexes;\n      this.filterRecordCPU = getFilterRecord(this.id, filters, opt);\n      return this;\n    }\n\n    // no gpu filter\n    if (!filters.find(f => f.gpu)) {\n      this.filteredIdxCPU = this.filteredIndex;\n      this.filterRecordCPU = getFilterRecord(this.id, filters, opt);\n      return this;\n    }\n\n    // make a copy for cpu filtering\n    const copied = copyTable(this);\n\n    copied.filterRecord = this.filterRecordCPU;\n    copied.filteredIndex = this.filteredIdxCPU || [];\n\n    const filtered = copied.filterTable(filters, layers, opt);\n\n    this.filteredIdxCPU = filtered.filteredIndex;\n    this.filterRecordCPU = filtered.filterRecord;\n\n    return this;\n  }\n\n  /**\n   * Calculate field domain based on field type and data\n   * for Filter\n   */\n  getColumnFilterDomain(field: F): FieldDomain {\n    const {dataContainer} = this;\n    const {valueAccessor} = field;\n\n    let domain;\n\n    switch (field.type) {\n      case ALL_FIELD_TYPES.real:\n      case ALL_FIELD_TYPES.integer:\n        // calculate domain and step\n        return getNumericFieldDomain(dataContainer, valueAccessor);\n\n      case ALL_FIELD_TYPES.boolean:\n        return {domain: [true, false]};\n\n      case ALL_FIELD_TYPES.string:\n      case ALL_FIELD_TYPES.h3:\n      case ALL_FIELD_TYPES.date:\n        domain = getOrdinalDomain(dataContainer, valueAccessor);\n        return {domain};\n\n      case ALL_FIELD_TYPES.timestamp:\n        return getTimestampFieldDomain(dataContainer, valueAccessor);\n\n      default:\n        return {domain: getOrdinalDomain(dataContainer, valueAccessor)};\n    }\n  }\n\n  /**\n   *  Get the domain of this column based on scale type\n   */\n  getColumnLayerDomain(field: F, scaleType: string): number[] | string[] | [number, number] | null {\n    const {dataContainer, filteredIndexForDomain} = this;\n\n    if (!SCALE_TYPES[scaleType]) {\n      Console.error(`scale type ${scaleType} not supported`);\n      return null;\n    }\n\n    const {valueAccessor} = field;\n    const indexValueAccessor = i => valueAccessor({index: i});\n    const sortFunction = getSortingFunction(field.type);\n\n    switch (scaleType) {\n      case SCALE_TYPES.ordinal:\n      case SCALE_TYPES.customOrdinal:\n      case SCALE_TYPES.point:\n        // do not recalculate ordinal domain based on filtered data\n        // don't need to update ordinal domain every time\n        return getOrdinalDomain(dataContainer, valueAccessor);\n\n      case SCALE_TYPES.quantile:\n        return getQuantileDomain(filteredIndexForDomain, indexValueAccessor, sortFunction);\n\n      case SCALE_TYPES.log:\n        return getLogDomain(filteredIndexForDomain, indexValueAccessor);\n\n      case SCALE_TYPES.quantize:\n      case SCALE_TYPES.linear:\n      case SCALE_TYPES.sqrt:\n      case SCALE_TYPES.custom:\n      default:\n        return getLinearDomain(filteredIndexForDomain, indexValueAccessor);\n    }\n  }\n\n  /**\n   * Get a sample of rows to calculate layer boundaries\n   */\n  // getSampleData(rows)\n\n  /**\n   * Parse cell value based on column type and return a string representation\n   * Value the field value, type the field type\n   */\n  // parseFieldValue(value, type)\n\n  // sortDatasetByColumn()\n\n  /**\n   * Assert whether field exist\n   * @param fieldName\n   * @param condition\n   */\n  _assetField(fieldName: string, condition: any): void {\n    if (!condition) {\n      Console.error(`${fieldName} doesnt exist in dataset ${this.id}`);\n    }\n  }\n}\n\nexport type Datasets = {\n  [key: string]: KeplerTable<Field>;\n};\n\n// HELPER FUNCTIONS (MAINLY EXPORTED FOR TEST...)\n// have to double excape\nconst specialCharacterSet = `[#_&@\\\\.\\\\-\\\\ ]`;\n\nfunction foundMatchingFields(re, suffixPair, allNames, fieldName) {\n  const partnerIdx = allNames.findIndex(\n    d => d === fieldName.replace(re, match => match.replace(suffixPair[0], suffixPair[1]))\n  );\n  let altIdx = -1;\n  if (partnerIdx > -1) {\n    // if found partner, go on and look for altitude\n    ALTITUDE_FIELDS.some(alt => {\n      altIdx = allNames.findIndex(\n        d => d === fieldName.replace(re, match => match.replace(suffixPair[0], alt))\n      );\n      return altIdx > -1;\n    });\n  }\n  return {partnerIdx, altIdx};\n}\n/**\n * Find point fields pairs from fields\n *\n * @param fields\n * @returns found point fields\n */\nexport function findPointFieldPairs(fields: Field[]): FieldPair[] {\n  const allNames = fields.map(f => f.name.toLowerCase());\n\n  // get list of all fields with matching suffixes\n  const acc: FieldPair[] = [];\n  return allNames.reduce((carry, fieldName, idx) => {\n    // This search for pairs will early exit if found.\n    for (const suffixPair of TRIP_POINT_FIELDS) {\n      // match first suffix\n      // (^|[#_&@\\.\\-\\ ])lat([#_&@\\.\\-\\ ]|$)\n      const re = new RegExp(`(^|${specialCharacterSet})${suffixPair[0]}(${specialCharacterSet}|$)`);\n\n      if (re.test(fieldName)) {\n        const {partnerIdx, altIdx} = foundMatchingFields(re, suffixPair, allNames, fieldName);\n\n        if (partnerIdx > -1) {\n          const trimName = fieldName.replace(re, '').trim();\n\n          carry.push({\n            defaultName: trimName || 'point',\n            pair: {\n              lat: {\n                fieldIdx: idx,\n                value: fields[idx].name\n              },\n              lng: {\n                fieldIdx: partnerIdx,\n                value: fields[partnerIdx].name\n              },\n              ...(altIdx > -1\n                ? {\n                    altitude: {\n                      fieldIdx: altIdx,\n                      value: fields[altIdx].name\n                    }\n                  }\n                : {})\n            },\n            suffix: suffixPair\n          });\n          return carry;\n        }\n      }\n    }\n    return carry;\n  }, acc);\n}\n\n/**\n *\n * @param dataset\n * @param column\n * @param mode\n * @type\n */\nexport function sortDatasetByColumn(\n  dataset: KeplerTable<Field>,\n  column: string,\n  mode?: string\n): KeplerTable<Field> {\n  const {allIndexes, fields, dataContainer} = dataset;\n  const fieldIndex = fields.findIndex(f => f.name === column);\n  if (fieldIndex < 0) {\n    return dataset;\n  }\n\n  const sortBy = SORT_ORDER[mode || ''] || SORT_ORDER.ASCENDING;\n\n  if (sortBy === SORT_ORDER.UNSORT) {\n    dataset.sortColumn = {};\n    dataset.sortOrder = null;\n\n    return dataset;\n  }\n\n  const sortFunction = sortBy === SORT_ORDER.ASCENDING ? ascending : descending;\n  const sortOrder = allIndexes.slice().sort((a, b) => {\n    const value1 = dataContainer.valueAt(a, fieldIndex);\n    const value2 = dataContainer.valueAt(b, fieldIndex);\n    if (!notNullorUndefined(value1) && notNullorUndefined(value2)) {\n      return 1;\n    } else if (notNullorUndefined(value1) && !notNullorUndefined(value2)) {\n      return -1;\n    }\n    return sortFunction(value1, value2);\n  });\n\n  dataset.sortColumn = {\n    [column]: sortBy\n  };\n  dataset.sortOrder = sortOrder;\n\n  return dataset;\n}\n\nexport function pinTableColumns(dataset: KeplerTable<Field>, column: string): KeplerTable<Field> {\n  const field = dataset.getColumnField(column);\n  if (!field) {\n    return dataset;\n  }\n\n  let pinnedColumns;\n  if (Array.isArray(dataset.pinnedColumns) && dataset.pinnedColumns.includes(field.name)) {\n    // unpin it\n    pinnedColumns = dataset.pinnedColumns.filter(co => co !== field.name);\n  } else {\n    pinnedColumns = (dataset.pinnedColumns || []).concat(field.name);\n  }\n\n  // @ts-ignore\n  return copyTableAndUpdate(dataset, {pinnedColumns});\n}\n\nexport function copyTable(original: KeplerTable<Field>): KeplerTable<Field> {\n  return Object.assign(Object.create(Object.getPrototypeOf(original)), original);\n}\n\n/**\n * @type\n * @returns\n */\nexport function copyTableAndUpdate(\n  original: KeplerTable<Field>,\n  options: Partial<KeplerTable<Field>> = {}\n): KeplerTable<Field> {\n  return Object.entries(options).reduce((acc, entry) => {\n    acc[entry[0]] = entry[1];\n    return acc;\n  }, copyTable(original));\n}\n\nexport function getFieldValueAccessor<\n  F extends {\n    type?: Field['type'];\n    format?: Field['format'];\n  }\n>(f: F, i: number, dc: DataContainerInterface) {\n  return maybeToDate.bind(\n    null,\n    // is time\n    f.type === ALL_FIELD_TYPES.timestamp,\n    i,\n    f.format || '',\n    dc\n  );\n}\n\nexport default KeplerTable;\n"
  },
  {
    "path": "src/table/src/tileset/raster-tile-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {JsonObjectOrArray, StacTypes} from '@kepler.gl/types';\n\n// Define these as regex to allow any semver major version 1\nconst EO_EXT_ID = /https:\\/\\/stac-extensions.github.io\\/eo\\/v1[\\d.]+\\/schema.json/;\nconst RASTER_EXT_ID = /https:\\/\\/stac-extensions.github.io\\/raster\\/v1[\\d.]+\\/schema.json/;\nconst ITEM_ASSETS_EXT_ID =\n  /https:\\/\\/stac-extensions.github.io\\/item-assets\\/v1[\\d.]+\\/schema.json/;\n\nexport function parseRasterMetadata(\n  metadata: JsonObjectOrArray,\n  options: {allowCollections: boolean}\n): StacTypes.CompleteSTACObject | Error | null {\n  if (!metadata || typeof metadata !== 'object') {\n    return null;\n  }\n\n  const error = validateSTAC(metadata, options);\n  if (error !== null) {\n    return error;\n  }\n\n  return metadata as StacTypes.CompleteSTACObject;\n}\n\n// eslint-disable-next-line complexity\nfunction validateSTAC(stac: JsonObjectOrArray, options: {allowCollections: boolean}): Error | null {\n  const {allowCollections = false} = options;\n\n  // Note: If a STAC object fails validation, it will silently fail\n  if (\n    typeof stac === 'string' ||\n    typeof stac === 'boolean' ||\n    typeof stac === 'number' ||\n    Array.isArray(stac) ||\n    !stac\n  ) {\n    return Error('Metadata must be an object.');\n  }\n\n  if ((!allowCollections && stac?.type === 'Collection') || stac?.type === 'Catalog') {\n    return Error('Custom STAC Collections and Catalogs are not supported.');\n  }\n\n  if (stac?.stac_version?.[0] !== '1') {\n    return Error('STAC versions before 1.0.0 not supported.');\n  }\n\n  if (stac?.type !== 'Feature' && stac?.type !== 'Collection') {\n    return Error('Not a STAC Item or Collection.');\n  }\n\n  const isCollection = stac?.type === 'Collection';\n\n  const required_extensions = [EO_EXT_ID, RASTER_EXT_ID];\n  if (isCollection) {\n    required_extensions.push(ITEM_ASSETS_EXT_ID);\n  }\n\n  if (\n    !Array.isArray(stac.stac_extensions) ||\n    !stac.stac_extensions.some(ext => typeof ext === 'string' && EO_EXT_ID.exec(ext)) ||\n    !stac.stac_extensions.some(ext => typeof ext === 'string' && RASTER_EXT_ID.exec(ext))\n  ) {\n    return Error('EO and Raster STAC extensions are required.');\n  }\n\n  if (\n    isCollection &&\n    !stac.stac_extensions.some(ext => typeof ext === 'string' && EO_EXT_ID.exec(ext))\n  ) {\n    return Error('item-assets STAC extension is required.');\n  }\n\n  const assets = isCollection ? stac?.item_assets : stac?.assets;\n  if (!assets || typeof assets !== 'object') {\n    return Error('STAC object is missing asset information.');\n  }\n\n  if (\n    !Object.values(assets).some(\n      asset => Array.isArray(asset?.['eo:bands']) && Array.isArray(asset?.['raster:bands'])\n    )\n  ) {\n    return Error('At least one STAC asset must have both eo:bands and raster:bands data.');\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "src/table/src/tileset/tileset-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {TileJSONLoader, TileJSON} from '@loaders.gl/mvt';\n\n/**\n * MVTSource in current loaders ignores attribution\n * TODO: remove once MVTSource is ready\n */\nexport async function getMVTMetadata(metadataURL: string | null): Promise<TileJSON | null> {\n  if (!metadataURL) return null;\n\n  let response: Response;\n  try {\n    // Annoyingly, on CORS errors, fetch doesn't use the response status/ok mechanism but instead throws\n    // CORS errors are common when requesting an unavailable sub resource such as a metadata file or an unavailable tile)\n    response = await fetch(metadataURL);\n  } catch (error: unknown) {\n    // eslint-disable-next-line no-console\n    console.error((error as TypeError).message);\n    return null;\n  }\n  if (!response.ok) {\n    // eslint-disable-next-line no-console\n    console.error(response.statusText);\n    return null;\n  }\n  const tileJSON = await response.text();\n  const metadata = TileJSONLoader.parseTextSync?.(tileJSON) || null;\n\n  const rawMetadata = JSON.parse(tileJSON);\n  if (rawMetadata?.attribution) {\n    metadata.attributions = [rawMetadata.attribution];\n  }\n\n  return metadata;\n}\n"
  },
  {
    "path": "src/table/src/tileset/vector-tile-utils.spec.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DATA_TYPES} from 'type-analyzer';\n\nimport {ALL_FIELD_TYPES, FILTER_TYPES} from '@kepler.gl/constants';\n\nimport {getTileUrl, getMetaUrl, parseVectorMetadata as parseMetadata} from './vector-tile-utils';\nimport {PMTILES_METADATA, MVT_METADATA} from '../../../../test/fixtures/tile-metadata';\n\ndescribe('getTileUrl', () => {\n  [\n    {name: 'empty string', input: '', expected: null},\n    {name: 'invalid URL', input: '/foo/bar/baz.mvt', expected: null},\n    {\n      name: 'Valid URL, no placeholders',\n      input: 'http://www.example.com',\n      expected: 'http://www.example.com/{z}/{x}/{y}.pbf'\n    },\n    {\n      name: 'Valid URL, no placeholders, trailing slash',\n      input: 'http://www.example.com/',\n      expected: 'http://www.example.com/{z}/{x}/{y}.pbf'\n    },\n    {\n      name: 'Valid URL, placeholders',\n      input: 'http://www.example.com/{z}/{x}/{y}.pbf',\n      expected: 'http://www.example.com/{z}/{x}/{y}.pbf'\n    },\n    {\n      name: 'Valid URL, placeholders, mvt',\n      input: 'http://www.example.com/{z}/{x}/{y}.mvt',\n      expected: 'http://www.example.com/{z}/{x}/{y}.mvt'\n    },\n    {\n      name: 'Valid URL, placeholders, mvt, access token',\n      input: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt?access_token=sk.fobar.baz',\n      expected: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt?access_token=sk.fobar.baz'\n    }\n  ].forEach(({name, input, expected}) => {\n    test(name, () => {\n      expect(getTileUrl(input)).toBe(expected);\n    });\n  });\n});\n\ndescribe('getMetaUrl', () => {\n  [\n    {name: 'empty string', input: '', expected: null},\n    {name: 'invalid URL', input: '/foo/bar/baz.mvt', expected: null},\n    {\n      name: 'Valid URL, unknown domain',\n      input: 'http://www.example.com/some_tiles',\n      expected: 'http://www.example.com/some_tiles/metadata.json'\n    },\n    {\n      name: 'Valid URL, unknown domain, trailing slash',\n      input: 'http://www.example.com/some_tiles/',\n      expected: 'http://www.example.com/some_tiles/metadata.json'\n    },\n    {\n      name: 'Valid URL, Mapbox domain',\n      input: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt',\n      expected: 'https://api.mapbox.com/v4/spam.json'\n    },\n    {\n      name: 'Valid URL, placeholders, mvt, access token',\n      input: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt?access_token=sk.fobar.baz',\n      expected: 'https://api.mapbox.com/v4/spam.json?access_token=sk.fobar.baz'\n    },\n    {\n      name: 'Valid URL, placeholders, mvt, access token, published style version',\n      input:\n        'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt?style=mapbox://styles/user/styleId@00&access_token=pk.xxx.zzz',\n      expected:\n        'https://api.mapbox.com/v4/spam.json?style=mapbox://styles/user/styleId@00&access_token=pk.xxx.zzz'\n    }\n  ].forEach(({name, input, expected}) => {\n    test(name, () => {\n      expect(getMetaUrl(input)).toBe(expected);\n    });\n  });\n});\n\ntest('parseMetadata, metadata from MVTSource and Mapbox URL', () => {\n  expect(\n    parseMetadata(MVT_METADATA, {tileUrl: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt'})\n  ).toEqual({\n    attributions: [],\n    metaJson: null,\n    bounds: [-180, -85, 180, 85],\n    center: [0, 0, 0],\n    maxZoom: 16,\n    minZoom: 0,\n    fields: [\n      {\n        name: 'class',\n        id: 'class',\n        format: '',\n        filterProps: {\n          domain: [],\n          value: [],\n          type: 'multiSelect',\n          gpu: false\n        },\n        type: 'string',\n        analyzerType: 'STRING'\n      }\n    ],\n    name: 'Mapbox Streets v8',\n    description: ''\n  });\n});\n\ntest('parseMetadata, PMTiles from PMTileSource', () => {\n  expect(\n    parseMetadata(PMTILES_METADATA, {\n      tileUrl:\n        'https://4sq-studio-data-staging.s3.us-west-2.amazonaws.com/some_path/some_file.pmtiles'\n    })\n  ).toEqual({\n    attributions: [],\n    name: 'My Custom Tiles',\n    description: 'My Custom Tiles Description',\n    metaJson: null,\n    bounds: [-150.1122219, -51.8952777, 179.3577783, 69.6043747],\n    center: [14.0625, 50.7026397, 6],\n    maxZoom: 6,\n    minZoom: 0,\n    fields: [\n      {\n        id: 'metric',\n        name: 'metric',\n        format: '',\n        type: ALL_FIELD_TYPES.real,\n        analyzerType: DATA_TYPES.FLOAT,\n        filterProps: {\n          domain: [-1, 10],\n          value: [-1, 10],\n          type: FILTER_TYPES.range,\n          typeOptions: [FILTER_TYPES.range],\n          gpu: true,\n          step: 0.01\n        }\n      },\n      {\n        id: 'continent',\n        name: 'continent',\n        format: '',\n        type: ALL_FIELD_TYPES.string,\n        analyzerType: DATA_TYPES.STRING,\n        filterProps: {\n          domain: ['Africa', 'Asia', 'Europe', 'North America', 'Oceania', 'South America'],\n          value: ['Africa', 'Asia', 'Europe', 'North America', 'Oceania', 'South America'],\n          type: FILTER_TYPES.multiSelect,\n          gpu: false\n        }\n      }\n    ],\n    pmtilesType: 'mvt'\n  });\n});\n\ntest('parseMetadata, empty input', () => {\n  expect(parseMetadata({})).toEqual({\n    attributions: [],\n    name: '',\n    description: '',\n    metaJson: null,\n    bounds: null,\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    fields: []\n  });\n\n  expect(\n    parseMetadata({}, {tileUrl: 'http://xyz.api.here.com/some_id/tile/web/{z}_{x}_{y}.pbf'})\n  ).toEqual({\n    attributions: [],\n    name: '',\n    description: '',\n    metaJson: null,\n    bounds: null,\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    fields: []\n  });\n\n  expect(parseMetadata({}, {tileUrl: 'https://api.mapbox.com/v4/spam/{z}/{x}/{y}.mvt'})).toEqual({\n    attributions: [],\n    name: '',\n    description: '',\n    metaJson: null,\n    bounds: null,\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    fields: []\n  });\n});\n\ndescribe('parseMetadata, bad cases', () => {\n  [\n    {name: 'empty string', input: '', expected: null},\n    {name: 'null', input: null, expected: null},\n    {name: 'undefined', input: undefined, expected: null},\n    {name: 'string', input: 'spam', expected: null}\n  ].forEach(({name, input, expected}) => {\n    test(name, () => {\n      expect(parseMetadata(input as any)).toBe(expected);\n    });\n  });\n});\n\ndescribe('getTileUrl', () => {\n  test.each([\n    ['', null],\n    ['foo', null],\n    ['http', null],\n    ['http://', null],\n    ['https://', null],\n    ['http://mytiles.com', 'http://mytiles.com/{z}/{x}/{y}.pbf'],\n    ['https://mytiles.com', 'https://mytiles.com/{z}/{x}/{y}.pbf'],\n    ['http://mytiles.com/', 'http://mytiles.com/{z}/{x}/{y}.pbf'],\n    ['http://mytiles.com/foo', 'http://mytiles.com/foo/{z}/{x}/{y}.pbf'],\n    ['http://mytiles.com/foo/', 'http://mytiles.com/foo/{z}/{x}/{y}.pbf'],\n    ['https://mytiles.com/foo/bar.baz', 'https://mytiles.com/foo/bar.baz/{z}/{x}/{y}.pbf'],\n    ['http://localhost', 'http://localhost/{z}/{x}/{y}.pbf'],\n    ['http://localhost/', 'http://localhost/{z}/{x}/{y}.pbf'],\n    ['http://localhost:8080', 'http://localhost:8080/{z}/{x}/{y}.pbf'],\n    ['http://localhost:8080/', 'http://localhost:8080/{z}/{x}/{y}.pbf'],\n    ['http://mytiles.com/foo/{z}/{x}/{y}.mvt', 'http://mytiles.com/foo/{z}/{x}/{y}.mvt']\n  ])('%s', (input, expected) => {\n    expect(getTileUrl(input)).toEqual(expected);\n  });\n});\n\ndescribe('getMetaUrl', () => {\n  test.each([\n    ['', null],\n    ['foo', null],\n    ['http', null],\n    ['http://', null],\n    ['https://', null],\n    ['http://mytiles.com', 'http://mytiles.com/metadata.json'],\n    ['https://mytiles.com', 'https://mytiles.com/metadata.json'],\n    ['http://mytiles.com/', 'http://mytiles.com/metadata.json'],\n    ['http://mytiles.com/foo', 'http://mytiles.com/foo/metadata.json'],\n    ['http://mytiles.com/foo/', 'http://mytiles.com/foo/metadata.json'],\n    ['https://mytiles.com/foo/bar.baz', 'https://mytiles.com/foo/bar.baz/metadata.json'],\n    ['http://localhost', 'http://localhost/metadata.json'],\n    ['http://localhost/', 'http://localhost/metadata.json'],\n    ['http://localhost:8080', 'http://localhost:8080/metadata.json'],\n    ['http://localhost:8080/', 'http://localhost:8080/metadata.json'],\n    ['http://mytiles.com/foo/{z}/{x}/{y}.mvt', 'http://mytiles.com/foo/metadata.json']\n  ])('%s', (input, expected) => {\n    expect(getMetaUrl(input)).toEqual(expected);\n  });\n});\n"
  },
  {
    "path": "src/table/src/tileset/vector-tile-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DATA_TYPES as ANALYZER_DATA_TYPES} from 'type-analyzer';\nimport {ascending} from 'd3-array';\nimport Console from 'global/console';\nimport uniq from 'lodash/uniq';\nimport {DATA_TYPES} from 'type-analyzer';\n\nimport {MVTSource, TileJSON} from '@loaders.gl/mvt';\nimport {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';\n\nimport {\n  analyzerTypeToFieldType,\n  containValidTime,\n  notNullorUndefined as notNullOrUndefined,\n  parseUri,\n  getFieldsFromData\n} from '@kepler.gl/common-utils';\nimport {\n  DatasetType,\n  ALL_FIELD_TYPES,\n  FILTER_TYPES,\n  PMTilesType,\n  RemoteTileFormat\n} from '@kepler.gl/constants';\n\nimport {Feature, Field as KeplerField, KeplerLayer} from '@kepler.gl/types';\nimport {clamp, formatNumberByStep, getNumericStepSize, timeToUnixMilli} from '@kepler.gl/utils';\n\nimport {\n  FilterProps,\n  NumericFieldFilterProps,\n  StringFieldFilterProps,\n  default as KeplerDataset\n} from '../kepler-table';\n\nexport function isTileDataset(dataset: KeplerDataset | {type: string}): boolean {\n  return Boolean(dataset?.type === DatasetType.VECTOR_TILE);\n}\n\ntype VectorTileField = {\n  analyzerType: string;\n  name: string;\n  id: string;\n  format: string;\n  type: string;\n  filterProps?: FilterProps;\n};\n\nexport type VectorTileMetadata = {\n  attributions?: unknown[];\n  metaJson: any | null;\n  bounds: number[] | null;\n  center: number[] | null;\n  maxZoom: number | null;\n  minZoom: number | null;\n  name?: string;\n  description?: string;\n  fields: VectorTileField[];\n\n  // if the tileset is of pmtiles format then include info about type of pmtiles\n  pmtilesType?: PMTilesType;\n};\n\ntype TilesetMetadata = VectorTileMetadata;\n\nexport type TippecanoeLayerAttribute = {\n  attribute?: string;\n  type?: string;\n  min?: number;\n  max?: number;\n  values: string[] | number[];\n};\n\ntype TippecanoeLayer = {\n  attributes?: TippecanoeLayerAttribute[];\n};\ntype TilesetFunction = {\n  getMetaUrl: (s: string) => string | null;\n  parseMetadata: (d: any, options?: any) => TilesetMetadata | null;\n};\n// https://github.com/mapbox/tilejson-spec/tree/master/2.2.0\nfunction isLat(num: any): boolean {\n  return Number.isFinite(num) && num <= 90 && num >= -90;\n}\nfunction isLng(num: any): boolean {\n  return Number.isFinite(num) && num <= 180 && num >= -180;\n}\nfunction isZoom(num: any): boolean {\n  return Number.isFinite(num) && num >= 0 && num <= 22;\n}\nfunction fromArrayOrString(data: string | number[]): number[] | null {\n  if (typeof data === 'string') {\n    return data.split(',').map(parseFloat);\n  } else if (Array.isArray(data)) {\n    return data;\n  }\n  return null;\n}\n\nfunction parseCenter(center: string | number[]): number[] | null {\n  // supported formats\n  // string: \"-96.657715,40.126127,-90.140061,43.516689\",\n  // array: [-91.505127,41.615442,14]\n  const result = fromArrayOrString(center);\n  if (\n    Array.isArray(result) &&\n    result.length === 3 &&\n    isLng(result[0]) &&\n    isLat(result[1]) &&\n    isZoom(result[2])\n  ) {\n    return result;\n  }\n  return null;\n}\n\n/**\n * bounds should be [minLng, minLat, maxLng, maxLat]\n * @param {*} bounds\n */\nfunction parseBounds(bounds: string | number[]): number[] | null {\n  // supported formats\n  // string: \"-96.657715,40.126127,-90.140061,43.516689\",\n  // array: [ -180, -85.05112877980659, 180, 85.0511287798066 ]\n  const result = fromArrayOrString(bounds);\n  // validate bounds\n  if (\n    Array.isArray(result) &&\n    result.length === 4 &&\n    [result[0], result[2]].every(isLng) &&\n    [result[1], result[3]].every(isLat)\n  ) {\n    return result;\n  }\n  return null;\n}\n\nconst X_PATT = /\\{x\\}/;\nconst Y_PATT = /\\{y\\}/;\nconst Z_PATT = /\\{z\\}/;\n\nfunction isFullyQualifiedTileUrl(tileUrl: string): boolean {\n  return X_PATT.test(tileUrl) && Y_PATT.test(tileUrl) && Z_PATT.test(tileUrl);\n}\n\n/**\n * Normalize tile URL\n * @param  {string} tileUrl Initial tile URL, which may be either the root URL for the\n *                          tileset or a fully qualified template\n * @param  {function} validateUrl function to validate tile URL\n * @return {string|null}    Fully qualified tile URL template, or null if input does not\n *                          appear to be a valid URL\n */\nexport function getTileUrl(\n  tileUrl: string,\n  validateUrl: (s: string) => boolean = isFullyQualifiedTileUrl\n): string | null {\n  // Check for a valid URL. Ideally we'd have a simple method here.\n  const uriParts = parseUri(tileUrl);\n  if (!uriParts.protocol || !uriParts.host) {\n    return null;\n  }\n  if (validateUrl(tileUrl)) {\n    return tileUrl;\n  }\n  return `${tileUrl.replace(/\\/$/, '')}/{z}/{x}/{y}.pbf`;\n}\n\n/**\n * Map of util functions for different tileset types, keyed by host\n */\nconst TILESET_FUNCTIONS: {[key: string]: TilesetFunction} = {\n  'api.mapbox.com': {\n    getMetaUrl: getMetaUrlMapbox,\n    parseMetadata: parseMetadataTileJSON\n  },\n  default: {\n    getMetaUrl: getMetaUrlTippecanoe,\n    parseMetadata: parseMetadataTileJSON\n  }\n};\n\nfunction getTilesetFunctions(tileUrl: string | null): TilesetFunction {\n  let host = '';\n  try {\n    host = new URL(tileUrl || '').hostname;\n  } catch (error) {\n    // do nothing\n  }\n\n  return TILESET_FUNCTIONS[host] || TILESET_FUNCTIONS.default;\n}\n\n/**\n * Get the metadata URL for a given tileset\n */\nexport function getMetaUrl(tileUrl: string): string | null {\n  return getTilesetFunctions(tileUrl).getMetaUrl(tileUrl);\n}\ntype ParseMetadataOption = {\n  tileUrl?: string | null;\n};\n/**\n * Parse the metadata for a given tileset\n */\nexport function parseVectorMetadata(\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  metadata: PMTilesMetadata | TileJSON,\n  option?: ParseMetadataOption\n): TilesetMetadata | null {\n  const {tileUrl = ''} = option || {};\n  return getTilesetFunctions(tileUrl).parseMetadata(metadata);\n}\n\nconst MAPBOX_URL_PATT = /\\/\\{z\\}\\/\\{x\\}\\/\\{y\\}\\.mvt/;\n\nfunction getMetaUrlMapbox(tileUrl = ''): string {\n  return tileUrl.replace(MAPBOX_URL_PATT, '.json');\n}\n\nfunction parseMetadataTileJSON(metadata: PMTilesMetadata | TileJSON): TilesetMetadata | null {\n  const parsed = parseMetadataTippecanoeFromDataSource(metadata);\n  if (!parsed) return null;\n\n  // PMTiles can potentially be in RasterTile format\n  const mimeType = (metadata as PMTilesMetadata).tileMIMEType;\n  if (mimeType) {\n    parsed.pmtilesType =\n      mimeType === 'application/vnd.mapbox-vector-tile' ? PMTilesType.MVT : PMTilesType.RASTER;\n  }\n\n  // Fields already parsed from `json` property\n  if (parsed.fields?.length) {\n    return parsed;\n  }\n\n  return parsed;\n}\n\nfunction getMetaUrlTippecanoe(tileUrl) {\n  const url = getTileUrl(tileUrl);\n  if (!url) return null;\n  // assumes the structure <url_base>/{z}...\n  const baseUrl = url.split(Z_PATT)[0].replace(/\\/$/, '');\n  return `${baseUrl}/metadata.json`;\n}\n\n/**\n * Special parsing for metadata returned by MVTSource and PMTilesSource.\n * @param metadata Tileset metadata parsed by a DataSouce\n * @returns Metadata in Kepler-friendly format.\n */\nfunction parseMetadataTippecanoeFromDataSource(\n  metadata: PMTilesMetadata | TileJSON | null\n): TilesetMetadata | null {\n  if (!metadata || typeof metadata !== 'object') {\n    return null;\n  }\n\n  let result: TilesetMetadata = {\n    attributions: [],\n    metaJson: null,\n    bounds: null,\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    fields: []\n  };\n\n  const mvtMetadata = metadata as TileJSON;\n  const pmTileMetadata = metadata as PMTilesMetadata;\n\n  // try to parse metaJson\n  if (typeof mvtMetadata.metaJson === 'string') {\n    try {\n      result.metaJson = JSON.parse(mvtMetadata.metaJson);\n    } catch (err) {\n      // do nothing\n    }\n  } else if (typeof mvtMetadata.metaJson === 'object') {\n    result.metaJson = mvtMetadata.metaJson;\n  }\n\n  result.bounds = parseBounds(\n    Array.isArray(metadata.boundingBox) ? metadata.boundingBox.flat() : ''\n  );\n\n  // PMTileSource has centerZoom and center [lon, lat], MVTSource - [lon, lat, zoom]\n  const center =\n    pmTileMetadata.centerZoom !== undefined && Array.isArray(metadata.center)\n      ? [...metadata.center, pmTileMetadata.centerZoom]\n      : metadata.center;\n  result.center = parseCenter(center || '');\n\n  result.maxZoom = safeParseFloat(metadata.maxZoom);\n  result.minZoom = safeParseFloat(metadata.minZoom);\n  result.name = metadata.name || '';\n  result.description = mvtMetadata.description || pmTileMetadata.tilejson?.description || '';\n\n  if (Array.isArray(pmTileMetadata.tilejson?.layers)) {\n    const layers = pmTilesLayerToTippecanoeLayer(pmTileMetadata.tilejson?.layers);\n    result.fields = collectAttributes(layers);\n  } else if (Array.isArray(mvtMetadata.layers)) {\n    const layers = pmTilesLayerToTippecanoeLayer(mvtMetadata.layers);\n    result.fields = collectAttributes(layers);\n  }\n\n  result = {\n    ...result,\n    attributions:\n      pmTileMetadata.attributions ||\n      (mvtMetadata.htmlAttribution ? [mvtMetadata.htmlAttribution] : undefined) ||\n      [],\n    ...parseMetaJson(result.metaJson)\n  };\n\n  return result;\n}\n\nfunction safeParseFloat(input: unknown): number | null {\n  const result =\n    typeof input === 'string' ? parseFloat(input) : typeof input === 'number' ? input : null;\n  return result === null || isNaN(result) ? null : result;\n}\n\nfunction parseMetaJson(metaJson: any): {fields: VectorTileField[]} | null {\n  if (!metaJson || typeof metaJson !== 'object') {\n    return null;\n  }\n\n  if (metaJson.tilestats && Array.isArray(metaJson.tilestats.layers)) {\n    // we are in luck!\n    return {fields: collectAttributes(metaJson.tilestats.layers)};\n  }\n  return null;\n}\n\nfunction getTimeAnimationDomain(mappedValue: number[]): {\n  domain: number[];\n  timeSteps: number[];\n  mappedValue: number[];\n  duration: number;\n} {\n  const timeSteps = uniq(mappedValue).sort(ascending).filter(notNullOrUndefined);\n\n  const domain = [timeSteps[0], timeSteps[timeSteps.length - 1]];\n\n  // if taks 10 * 1000 ms to finish the entire animation\n  const duration = 10000 / timeSteps.length;\n  const clamped = clamp([100, 2000], duration);\n\n  return {domain, timeSteps, duration: clamped, mappedValue};\n}\n\nconst pmTileTypeToAttrMap = {\n  float32: 'number',\n  string: 'string',\n  utf8: 'string',\n  int: 'int',\n  boolean: 'boolean'\n};\n\n/**\n * Transform TileJSON['layers'] back to TippecanoeLayer\n */\nfunction pmTilesLayerToTippecanoeLayer(layers: TileJSON['layers']): TippecanoeLayer[] {\n  if (!layers) return [];\n\n  const outLayers: TippecanoeLayer[] = [];\n  for (const layer of layers) {\n    const {fields = []} = layer || {};\n    for (const pmField of fields) {\n      const attribute = {\n        attribute: pmField.name,\n        type: pmTileTypeToAttrMap[pmField.type],\n        count: pmField.uniqueValueCount,\n        values: (pmField.values ?? []) as number[] | string[],\n        min: pmField.min,\n        max: pmField.max\n      };\n\n      outLayers.push({\n        attributes: [attribute]\n      });\n    }\n  }\n  return outLayers;\n}\n\nfunction collectAttributes(layers: TippecanoeLayer[] = []): VectorTileField[] {\n  const fields = {};\n  const indexedAttributes: {[key: string]: TippecanoeLayerAttribute[]} = {};\n\n  for (const layer of layers) {\n    const {attributes = []} = layer || {};\n    for (const attr of attributes) {\n      const name = attr.attribute;\n      if (typeof name === 'string') {\n        // eslint-disable-next-line max-depth\n        if (name.split('|').length > 1) {\n          // indexed field\n          const fname = name.split('|')[0];\n          indexedAttributes[fname] = indexedAttributes[fname] || [];\n          indexedAttributes[fname].push(attr);\n        } else if (!fields[name]) {\n          fields[name] = attributeToField(attr);\n        } else {\n          mergeAttributeDomain(fields[name], attr);\n        }\n      }\n    }\n  }\n\n  // parse indexed attribute, and put index key unidentified back as normal field\n  for (const [name, attrs] of Object.entries(indexedAttributes)) {\n    const {indexedField, unidentified} = parseIndexedField(name, attrs);\n    fields[indexedField.name] = indexedField;\n    for (const unidentifiedAttr of unidentified) {\n      if (unidentifiedAttr.attribute) {\n        fields[unidentifiedAttr.attribute] =\n          fields[unidentifiedAttr.attribute] || attributeToField(unidentifiedAttr);\n        mergeAttributeDomain(fields[unidentifiedAttr.attribute], unidentifiedAttr);\n      }\n    }\n  }\n\n  return Object.values(fields);\n}\n\nfunction getIndexKeyFromFieldName(name: string): string | null {\n  return name && name.split('|').length > 1 ? name.split('|')[1] : null;\n}\n\nexport type FieldIndexBy = {\n  format: string;\n  type: string;\n  mappedValue: {\n    [key: string]: string;\n  };\n};\n\nfunction parseIndexedField(\n  name: string,\n  attrs: TippecanoeLayerAttribute[]\n): {\n  unidentified: TippecanoeLayerAttribute[];\n  indexedField: VectorTileField;\n} {\n  const unidentified: TippecanoeLayerAttribute[] = [];\n  // analyze time format\n  let field;\n\n  for (const attr of attrs) {\n    const fieldName = attr.attribute;\n    const indexKey = getIndexKeyFromFieldName(fieldName || '');\n    const analyzedType = indexKey && containValidTime([indexKey]);\n    if (analyzedType) {\n      field = field || {\n        ...attributeToField(attr),\n        // overide name and id to truncated name\n        name,\n        id: name,\n        indexBy: {\n          format: analyzedType.format,\n          type: analyzerTypeToFieldType(analyzedType.type),\n          mappedValue: {}\n        }\n      };\n      mergeAttributeDomain(field, attr);\n      // save epoch time in mappedValue\n      let fieldTs: string | number | null = indexKey;\n      if (analyzedType.format === 'x' || analyzedType.format === 'X') {\n        fieldTs = Number(indexKey);\n      }\n      const epoch = timeToUnixMilli(fieldTs, analyzedType.format);\n      if (epoch) {\n        field.indexBy.mappedValue[epoch] = fieldName;\n      }\n    } else {\n      // key is not valid timestamp\n      unidentified.push(attr);\n    }\n  }\n\n  if (field.indexBy && field.indexBy.type === ALL_FIELD_TYPES.timestamp) {\n    field.indexBy.timeDomain = getTimeAnimationDomain(\n      Object.keys(field.indexBy.mappedValue).map(Number)\n    );\n  }\n\n  return {unidentified, indexedField: field};\n}\n\nfunction compare(num1: number | undefined, num2: number | undefined, operator: string): number {\n  return Number.isFinite(num1) && Number.isFinite(num2)\n    ? Math[operator](num1 as number, num2 as number)\n    : Number.isFinite(num1)\n    ? (num1 as number)\n    : Number.isFinite(num2)\n    ? (num2 as number)\n    : NaN;\n}\n\nexport function getFilterProps(\n  fieldType: string,\n  attribute: TippecanoeLayerAttribute\n): FilterProps {\n  switch (fieldType) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer: {\n      const [min, max] = getAttributeDomain(fieldType, attribute);\n      const diff = max - min;\n      const step = getNumericStepSize(diff) || 0.1;\n      const domain = [\n        formatNumberByStep(min, step, 'floor'),\n        formatNumberByStep(max, step, 'ceil')\n      ];\n      const filterProps: NumericFieldFilterProps = {\n        domain,\n        value: domain as [number, number],\n        type: FILTER_TYPES.range,\n        typeOptions: [FILTER_TYPES.range],\n        gpu: true,\n        step\n      };\n      return filterProps;\n    }\n\n    case ALL_FIELD_TYPES.boolean: {\n      // Represent boolean as numeric range [0,1] to enable GPU filtering for vector tiles\n      const domain: [number, number] = [0, 1];\n      const filterProps: NumericFieldFilterProps = {\n        domain,\n        value: domain,\n        type: FILTER_TYPES.range,\n        typeOptions: [FILTER_TYPES.range],\n        gpu: true,\n        step: 1\n      };\n      return filterProps;\n    }\n\n    default: {\n      // Assume string for all other fields\n      const filterProps: StringFieldFilterProps = {\n        domain: attribute.values as string[],\n        value: attribute.values as string[],\n        type: FILTER_TYPES.multiSelect,\n        gpu: false\n      };\n      return filterProps;\n    }\n  }\n}\n\nfunction attributeToField(attribute: TippecanoeLayerAttribute = {values: []}): VectorTileField {\n  // attribute: \"_season_peaks_color\"\n  // count: 1000\n  // max: 0.95\n  // min: 0.24375\n  // type: \"number\"\n  const fieldTypes = attributeTypeToFieldType(attribute.type);\n  return {\n    name: attribute.attribute as string,\n    id: attribute.attribute as string,\n    format: '',\n    filterProps: getFilterProps(fieldTypes.type, attribute),\n    ...fieldTypes\n  };\n}\n\nfunction getAttributeDomain(type: string | null, attribute: TippecanoeLayerAttribute): number[] {\n  switch (type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n      return [\n        Number.isFinite(attribute.min) ? (attribute.min as number) : NaN,\n        Number.isFinite(attribute.max) ? (attribute.max as number) : NaN\n      ];\n\n    case ALL_FIELD_TYPES.boolean:\n      return [0, 1];\n    default:\n      return [0, 1];\n  }\n}\n\n/**\n * @param field This function mutates the field parameter\n */\nfunction mergeAttributeDomain(field: KeplerField, attribute: TippecanoeLayerAttribute): void {\n  switch (field.type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer: {\n      const domain = field.metadata?.domain || field.filterProps?.domain;\n      if (domain) {\n        domain.min = compare(attribute.min, domain[0], 'min');\n        domain.max = compare(attribute.max, domain[1], 'max');\n      }\n      return;\n    }\n    default:\n      return;\n  }\n}\n\n// possible types https://github.com/mapbox/tippecanoe#modifying-feature-attributes\nconst attrTypeMap = {\n  number: {\n    type: ALL_FIELD_TYPES.real,\n    analyzerType: DATA_TYPES.FLOAT\n  },\n  numeric: {\n    type: ALL_FIELD_TYPES.real,\n    analyzerType: DATA_TYPES.FLOAT\n  },\n  string: {\n    type: ALL_FIELD_TYPES.string,\n    analyzerType: DATA_TYPES.STRING\n  },\n  vachar: {\n    type: ALL_FIELD_TYPES.string,\n    analyzerType: DATA_TYPES.STRING\n  },\n  float: {\n    type: ALL_FIELD_TYPES.real,\n    analyzerType: DATA_TYPES.FLOAT\n  },\n  int: {\n    type: ALL_FIELD_TYPES.integer,\n    analyzerType: DATA_TYPES.INT\n  },\n  int4: {\n    type: ALL_FIELD_TYPES.integer,\n    analyzerType: DATA_TYPES.INT\n  },\n  boolean: {\n    type: ALL_FIELD_TYPES.boolean,\n    analyzerType: DATA_TYPES.BOOLEAN\n  },\n  bool: {\n    type: ALL_FIELD_TYPES.boolean,\n    analyzerType: DATA_TYPES.BOOLEAN\n  }\n};\n\nfunction attributeTypeToFieldType(aType?: string): {type: string; analyzerType: string} {\n  const type = aType?.toLowerCase();\n  if (!type || !attrTypeMap[type]) {\n    Console.warn(\n      `cannot convert attribute type ${type} to kepler.gl data type, use string by default`\n    );\n    return attrTypeMap.string;\n  }\n\n  return attrTypeMap[type];\n}\n\n/**\n * Returns true if a dataset can be used as source data for a layer.\n * @param dataset A dataset.\n * @param layer A layer.\n * @returns Returns true if a dataset can be used as source data for a layer.\n */\nexport function matchDatasetType(dataset: KeplerDataset, layer: KeplerLayer): boolean {\n  // allow selection if type is not assigned yet\n  if (!layer.type) {\n    return true;\n  }\n  // allow selection if is current selected dataset\n  if (layer.config.dataId === dataset.id) {\n    return true;\n  }\n  // allow selection if layer doesn't have supportedDatasetTypes\n  if (!layer.supportedDatasetTypes) {\n    return true;\n  }\n\n  return (\n    Array.isArray(layer.supportedDatasetTypes) &&\n    layer.supportedDatasetTypes.includes(dataset.type || '')\n  );\n}\n\ntype GetFieldsFromTileProps = {\n  remoteTileFormat: RemoteTileFormat;\n  tilesetUrl: string | null;\n  metadataUrl: string | null;\n  metadata: VectorTileMetadata | null;\n};\n\n/**\n * Extracts fields from a tile and updates the metadata object with found fields.\n * Note: this function is for tilesets that don't include fields in metadata (most likely saved with older spec.).\n * @param params.remoteTileFormat The format of the remote tile (MVT or PMTiles).\n * @param params.tilesetUrl The URL of the tileset.\n * @param params.metadataUrl The URL of the metadata.\n * @param params.metadata The metadata object containing fields and tile properties.\n */\nexport const getFieldsFromTile = async ({\n  remoteTileFormat,\n  tilesetUrl,\n  metadataUrl,\n  metadata\n}: GetFieldsFromTileProps) => {\n  try {\n    if (\n      tilesetUrl &&\n      metadataUrl &&\n      metadata &&\n      metadata.fields?.length === 0 &&\n      metadata.minZoom &&\n      metadata.bounds?.length === 4 &&\n      (!metadata.pmtilesType || metadata.pmtilesType === PMTilesType.MVT)\n    ) {\n      const lon = (metadata.bounds[0] + metadata.bounds[2]) / 2;\n      const lat = (metadata.bounds[1] + metadata.bounds[3]) / 2;\n      const tileIndices = lonLatToTileIndex(lon, lat, metadata.minZoom);\n\n      const tileSource =\n        remoteTileFormat === RemoteTileFormat.MVT\n          ? MVTSource.createDataSource(decodeURIComponent(tilesetUrl), {\n              mvt: {\n                metadataUrl: decodeURIComponent(metadataUrl)\n              }\n            })\n          : PMTilesSource.createDataSource(tilesetUrl, {});\n      const tile = await tileSource.getTileData({index: tileIndices} as any);\n      const updatedFields = tileToFields(tile).map(f => {\n        return {\n          ...f,\n          analyzerType: f.analyzerType || ALL_FIELD_TYPES.string,\n          id: f.id || f.name\n        };\n      });\n\n      metadata.fields = updatedFields;\n    }\n  } catch {\n    // ignore, as this is experimental fallback\n  }\n};\n\n/**\n * Converts tile features into Kepler fields.\n * @param tile The tile object containing features.\n * @returns An array of Kepler fields derived from the tile's features.\n */\nconst tileToFields = (tile: {features: Feature[]}): KeplerField[] => {\n  if (tile?.features?.length > 0) {\n    const header = Object.keys(tile.features[0].properties);\n    const output = tile.features.map(f => {\n      const obj = {};\n      header.forEach(columnName => {\n        obj[columnName] = f.properties[columnName];\n      });\n      return obj;\n    });\n\n    const fields = getFieldsFromData(output, header);\n    // extra transformation of strings to numbers for tiles isn't implemented, so use string, not computed types\n    return fields.map(f => {\n      const forceString =\n        (f.type === 'integer' || f.type === 'float') &&\n        typeof tile.features[0].properties[f.name] === 'string';\n\n      return {\n        ...f,\n        analyzerType: forceString ? ANALYZER_DATA_TYPES.STRING : f.analyzerType,\n        type: forceString ? 'string' : f.type\n      };\n    });\n  }\n  return [];\n};\n\n/**\n * Converts longitude, latitude, and zoom level into vector tile indices (x, y, z).\n * @param lon Longitude in degrees, ranging from -180 to 180.\n * @param lat Latitude in degrees, ranging from -90 to 90.\n * @param zoom Zoom level (integer), where higher values provide more detail.\n * @returns Tile indices with x and y coordinates and zoom level z.\n */\nfunction lonLatToTileIndex(lon: number, lat: number, zoom: number) {\n  if (lat < -85.0511 || lat > 85.0511) {\n    throw new Error('Latitude out of range. Must be between -85.0511 and 85.0511.');\n  }\n  if (zoom < 0) {\n    throw new Error('Zoom level must be a non-negative integer.');\n  }\n\n  // 2^zoom (number of tiles per axis at given zoom level)\n  const scale = 1 << zoom;\n  // Convert longitude to tile X\n  const x = Math.floor(((lon + 180) / 360) * scale);\n  // Convert latitude to tile Y\n  const latRad = (lat * Math.PI) / 180;\n  const y = Math.floor(\n    ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * scale\n  );\n\n  return {x, y, z: zoom};\n}\n"
  },
  {
    "path": "src/table/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"**/*.spec.*\"]\n}\n"
  },
  {
    "path": "src/tasks/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/tasks/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/tasks\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@kepler.gl/processors\": \"3.2.6\",\n    \"react-palm\": \"^3.3.8\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/tasks/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Task, {taskCreator} from 'react-palm/tasks';\nimport {readFileInBatches, processFileData} from '@kepler.gl/processors';\n\nexport const LOAD_FILE_TASK = Task.fromPromise(\n  ({file, fileCache, loaders, loadOptions}) =>\n    readFileInBatches({file, fileCache, loaders, loadOptions}),\n  'LOAD_FILE_TASK'\n);\n\nexport const PROCESS_FILE_DATA = Task.fromPromise(processFileData, 'PROCESS_FILE_CONTENT');\n\nexport const LOAD_MAP_STYLE_TASK = taskCreator(\n  ({url, id}, success, error) =>\n    fetch(url)\n      .then(response => {\n        if (!response.ok) {\n          return response.text().then(text => {\n            error(text);\n          });\n        }\n        return response.json();\n      })\n      .then(result => {\n        if (!result) {\n          error(new Error('Map style response is empty'));\n        }\n        success({id, style: result});\n      }),\n\n  'LOAD_MAP_STYLE_TASK'\n);\n\n/**\n * task to upload file to cloud provider\n */\nexport const EXPORT_FILE_TO_CLOUD_TASK = Task.fromPromise(\n  ({provider, payload}) => provider.uploadMap(payload),\n\n  'EXPORT_FILE_TO_CLOUD_TASK'\n);\n\nexport const LOAD_CLOUD_MAP_TASK = Task.fromPromise(\n  ({provider, payload}) => provider.downloadMap(payload),\n\n  'LOAD_CLOUD_MAP_TASK'\n);\n\n/**\n *  task to dispatch a function as a task\n */\nexport const ACTION_TASK = Task.fromCallback(\n  (_, cb) => cb(),\n\n  'ACTION_TASK'\n);\n\nexport const DELAY_TASK = Task.fromCallback(\n  (delay, cb) => window.setTimeout(() => cb(), delay),\n\n  'DELAY_TASK'\n);\n\nexport const UNWRAP_TASK = Task.fromPromise(\n  promise => promise,\n\n  'UNWRAP'\n);\n"
  },
  {
    "path": "src/tasks/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": false, //TODO change once all dependencies are isolated\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "src/types/actions.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {SavedMap, ParsedConfig, SavedConfigV1, MinSavedConfigV1} from './schemas';\n\n/** EXPORT_FILE_TO_CLOUD */\nexport type MapData = {\n  map: SavedMap;\n  thumbnail: Blob | null;\n};\nexport type ExportFileOptions = {\n  isPublic?: boolean;\n  overwrite?: boolean;\n  mapIdToOverwrite?: string | null;\n};\nexport type OnErrorCallBack = (error: Error) => any;\nexport type OnSuccessCallBack = (p: {\n  response: any;\n  provider: Provider;\n  options: ExportFileOptions;\n}) => any;\n\nexport type ExportFileToCloudPayload = {\n  mapData: MapData;\n  provider: Provider;\n  options: ExportFileOptions;\n  onSuccess?: OnSuccessCallBack;\n  onError?: OnErrorCallBack;\n  closeModal?: boolean;\n};\n\n/**\n * Input dataset parsed to addDataToMap\n */\nexport type ProtoDatasetField = {\n  name: string;\n  type: string;\n  format?: string;\n  displayName?: string;\n  analyzerType?: string;\n  id?: string;\n  metadata?: Record<string, any>;\n};\nexport type ProtoDataset = {\n  info: {\n    id?: string;\n    label?: string;\n    format?: string;\n    color?: RGBColor;\n    type?: string;\n    hidden?: boolean;\n  };\n  data: {\n    fields: ProtoDatasetField[];\n    rows: any[][];\n    cols?: any[];\n    arrowTable?: arrow.Table;\n  };\n\n  // table-injected metadata\n  metadata?: any;\n  supportedFilterTypes?: string[] | null;\n  disableDataOperation?: boolean;\n};\n\nexport type AddDataToMapOptions = {\n  centerMap?: boolean;\n  readOnly?: boolean;\n  keepExistingConfig?: boolean;\n  autoCreateLayers?: boolean;\n  autoCreateTooltips?: boolean;\n};\n\nexport type AddDataToMapPayload = {\n  // TODO/ib - internally the code calls `toArray` a couple of layers deep\n  // so this function can actually accept both an array and an object\n  // recommend dropping such \"sloppy typing\" and enforcing array type\n  // as the field is called `datasets`\n  datasets: ProtoDataset[] | ProtoDataset;\n  options?: AddDataToMapOptions;\n  config?: ParsedConfig | SavedConfigV1 | MinSavedConfigV1;\n  info?: Partial<MapInfo>;\n};\n"
  },
  {
    "path": "src/types/components.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport type TimeLabelFormat = {\n  id: string;\n  format: string | null;\n  type: string;\n  label: string;\n};\n\ntype TooltipFields = {\n  format?: string;\n  id?: string;\n  name?: string;\n  fieldIdx?: number;\n  type?: string;\n  displayFormat?: string;\n  displayName: string;\n};\n\nexport type ColMetaProps = {\n  colIdx: number;\n  name: string;\n  displayName: string;\n  type: string;\n  format?: string;\n  columnStats?: any;\n  displayFormat?: string;\n};\n\nexport type ColMeta = {\n  [key: string]: ColMetaProps;\n};\n"
  },
  {
    "path": "src/types/datasets.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport type ZoomStops = number[][];\n\nexport type DomainStops = {\n  z: number[];\n  stops: ZoomStops;\n  interpolation: 'interpolate';\n};\n\nexport type ZoomStopsConfig = {\n  enabled?: boolean;\n  stops: ZoomStops | null;\n};\n\nexport type DatasetAttribution = {\n  title: string;\n  url: string | null;\n};\n\nexport type AttributionWithStyle = DatasetAttribution & {\n  logoUrl: string;\n  height?: number;\n  bottom?: number;\n};\n"
  },
  {
    "path": "src/types/effects.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\ntype EffectParameterDescription = {\n  name: string;\n  type?: 'number' | 'array' | 'color';\n  label?: string | false | (string | false)[];\n  min: number;\n  max: number;\n  defaultValue?: number | number[];\n};\n\nexport type EffectDescription = {\n  type: string;\n  name: string;\n  parameters: EffectParameterDescription[];\n};\n\nexport type EffectUpdateProps = {\n  isEnabled: boolean;\n  isConfigActive: boolean;\n  isJsonEditorActive: boolean;\n  // effect specific parameters for a deck.gl effect (uniforms)\n  parameters: {[key: string]: any};\n};\n\nexport type EffectProps = EffectUpdateProps & {\n  id: string;\n  type: string;\n};\n\nexport type EffectPropsPartial = Partial<EffectProps>;\n\nexport interface Effect {\n  id: string;\n  type: string;\n  isEnabled: boolean;\n  isConfigActive: boolean;\n  isJsonEditorActive: boolean;\n  // effect specific parameters for a deck.gl effect (uniforms)\n  parameters: {[key: string]: any};\n  deckEffect: any;\n  _uiConfig: EffectParameterDescription[];\n\n  getDefaultProps(props: Partial<EffectProps>): EffectProps;\n  setProps(props: Partial<EffectProps>): void;\n  isValidToSave(): boolean;\n  getParameterDescriptions(): EffectParameterDescription[];\n  clone(): Effect;\n}\n"
  },
  {
    "path": "src/types/index.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport * from './layers';\nexport * from './reducers';\nexport * from './actions';\nexport * from './schemas';\nexport * from './types';\nexport * from './components';\nexport * from './effects';\nexport * from './datasets';\n\nexport * as StacTypes from './stac';\n"
  },
  {
    "path": "src/types/layers.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {HexColor, RGBColor, RGBAColor} from './types';\n\nexport type LayerBaseConfig = {\n  dataId: string;\n  label: string;\n  color: RGBColor;\n\n  columns: LayerColumns;\n  isVisible: boolean;\n  isConfigActive: boolean;\n  highlightColor: RGBColor | RGBAColor;\n  hidden: boolean;\n\n  visConfig: LayerVisConfig;\n  textLabel: LayerTextLabel[];\n\n  colorUI: {\n    color: ColorUI;\n    colorRange: ColorUI;\n  };\n  animation: {\n    enabled: boolean;\n    domain?: [number, number] | null;\n  };\n\n  // for aggregate layer, aggregatedBins is returned for custom color scale\n  aggregatedBins?: AggregatedBin[];\n\n  columnMode?: string;\n  heightField?: VisualChannelField;\n  heightDomain?: VisualChannelDomain;\n  heightScale?: string;\n};\n\n/**\n * Used to configure geospatial data source columns like Longitude, Latitude, Geojson.\n */\nexport type LayerColumn = {value: string | null; fieldIdx: number; optional?: boolean};\nexport type LayerConfigColumn = LayerColumn;\n\nexport type LayerColumns = {\n  [key: string]: LayerConfigColumn;\n};\n\nexport type ColumnPair = {\n  pair: string | string[];\n  fieldPairKey: string | string[];\n};\n\nexport type ColumnPairs = {[key: string]: ColumnPair};\n\nexport type ColumnLabels = {\n  [key: string]: string;\n};\n\nexport type EnhancedFieldPair = {\n  name: string;\n  type: 'point';\n  pair: FieldPair['pair'];\n};\n\nexport type SupportedColumnMode = {\n  key: string;\n  label: string;\n  requiredColumns: string[];\n  optionalColumns?: string[];\n  hasHelp?: boolean;\n  verifyField?: (field: Field) => boolean;\n};\n\nexport type VisualChannelField = Field | null;\nexport type VisualChannelScale = keyof typeof SCALE_TYPES;\n\nexport type LayerColorConfig = {\n  colorField: VisualChannelField;\n  colorDomain: VisualChannelDomain;\n  colorScale: VisualChannelScale;\n};\nexport type LayerSizeConfig = {\n  // color by size, domain is set by filters, field, scale type\n  sizeDomain: VisualChannelDomain;\n  sizeScale: VisualChannelScale;\n  sizeField: VisualChannelField;\n};\nexport type LayerHeightConfig = {\n  heightField: VisualChannelField;\n  heightDomain: VisualChannelDomain;\n  heightScale: VisualChannelScale;\n};\nexport type LayerStrokeColorConfig = {\n  strokeColorField: VisualChannelField;\n  strokeColorDomain: VisualChannelDomain;\n  strokeColorScale: VisualChannelScale;\n};\nexport type LayerCoverageConfig = {\n  coverageField: VisualChannelField;\n  coverageDomain: VisualChannelDomain;\n  coverageScale: VisualChannelScale;\n};\nexport type LayerRadiusConfig = {\n  radiusField: VisualChannelField;\n  radiusDomain: VisualChannelDomain;\n  radiusScale: VisualChannelScale;\n};\nexport type LayerWeightConfig = {\n  weightField: VisualChannelField;\n};\n\nexport type IndexBy = {\n  format: string;\n  type: string;\n  mappedValue: Record<number, string>;\n  timeDomain: {\n    domain: [number, number];\n    timeSteps: number[];\n    duration?: number;\n  };\n};\n\nexport type Field = {\n  name: string;\n  displayName: string;\n  type: string;\n  fieldIdx: number;\n  valueAccessor(v: {index: number}): any;\n  analyzerType?: string;\n  id?: string;\n  format: string;\n  filterProps?: FilterProps;\n  metadata?: Record<string, any>;\n  displayFormat?: string;\n  isLoadingStats?: boolean;\n  indexBy?: IndexBy;\n};\n\nexport type FieldPair = {\n  defaultName: string;\n  pair: {\n    lat: {\n      fieldIdx: number;\n      value: string;\n    };\n    lng: {\n      fieldIdx: number;\n      value: string;\n    };\n    altitude?: {\n      fieldIdx: number;\n      value: string;\n    };\n  };\n  suffix: string[];\n};\n\nexport type SizeRange = [number, number];\n\nexport type LayerTextLabel = {\n  field: Field | null;\n  color: RGBColor;\n  background: boolean;\n  size: number;\n  offset: [number, number];\n  anchor: string;\n  alignment: string;\n  outlineWidth: number;\n  outlineColor: RGBAColor;\n  backgroundColor: RGBAColor;\n};\n\nexport type ColorRangeConfig = {\n  type: string;\n  steps: number;\n  reversed: boolean;\n  custom: boolean;\n  customBreaks: boolean;\n  colorBlindSafe: boolean;\n};\n\nexport type ColorMap = [string[] | number[] | string | number | null, HexColor][];\n// Key is HexColor but as key we can use only string\nexport type ColorLegends = {[key: HexColor]: string};\n\nexport type ColorRange = {\n  name?: string;\n  type?: string;\n  category?: string;\n  colors: HexColor[];\n  reversed?: boolean;\n  colorMap?: ColorMap | null;\n  colorLegends?: ColorLegends;\n};\n\nexport type MiniColorRange = {\n  name: string;\n  type: string;\n  category: string;\n  colors: HexColor[];\n};\n\nexport type ColorUI = {\n  // customPalette in edit\n  customPalette: ColorRange;\n  // show color sketcher modal\n  showSketcher: boolean | number;\n  // show color range selection panel\n  showDropdown: boolean | number;\n  // show color scale chart\n  showColorChart: boolean;\n  // color range selector config\n  colorRangeConfig: ColorRangeConfig;\n};\n\nexport type VisConfig = {\n  label:\n    | string\n    | ((\n        config: LayerBaseConfig &\n          Partial<LayerColorConfig> &\n          Partial<LayerHeightConfig> &\n          Partial<LayerSizeConfig> &\n          Partial<LayerWeightConfig>\n      ) => string);\n  group: keyof typeof PROPERTY_GROUPS;\n  property: string;\n  description?: string;\n  condition?: (\n    config: LayerBaseConfig &\n      Partial<LayerColorConfig> &\n      Partial<LayerHeightConfig> &\n      Partial<LayerSizeConfig> &\n      Partial<LayerWeightConfig>\n  ) => boolean;\n\n  allowCustomValue?: boolean;\n};\n\nexport type VisConfigNumber = VisConfig & {\n  type: 'number';\n  isRanged: false;\n  defaultValue: number;\n  range: SizeRange;\n  step: number;\n};\n\nexport type VisConfigBoolean = VisConfig & {\n  type: 'boolean';\n  defaultValue: boolean;\n};\n\nexport type VisConfigInput = VisConfig & {\n  type: 'input';\n  defaultValue: string | null;\n};\n\nexport type VisConfigSelection = VisConfig & {\n  type: 'select';\n  defaultValue: string;\n  options: string[];\n};\n\nexport type VisConfigObjectSelection = VisConfig & {\n  type: 'object-select';\n  // the value is id of one of the options\n  defaultValue: string | null;\n  // an array of objects with ids\n  options: object[];\n};\n\nexport type VisConfigRange = VisConfig & {\n  type: 'number';\n  isRanged: boolean;\n  range: SizeRange;\n  defaultValue: SizeRange;\n  step: number;\n};\n\nexport type VisConfigColorSelect = VisConfig & {\n  type: 'color-select';\n  defaultValue: null;\n};\n\nexport type VisConfigColorRange = VisConfig & {\n  type: 'color-range-select';\n  defaultValue: ColorRange;\n};\n\nexport type LayerVisConfigTypes =\n  | VisConfigBoolean\n  | VisConfigNumber\n  | VisConfigRange\n  | VisConfigColorRange\n  | VisConfigColorSelect\n  | VisConfigInput\n  | VisConfigSelect\n  | VisConfigObjectSelection;\n\nexport type LayerVisConfigSettings = {\n  thickness: VisConfigNumber;\n  strokeWidthRange: VisConfigRange;\n  trailLength: VisConfigNumber;\n  fadeTrail: VisConfigBoolean;\n  billboard: VisConfigBoolean;\n  radius: VisConfigNumber;\n  fixedRadius: VisConfigBoolean;\n  radiusRange: VisConfigRange;\n  clusterRadius: VisConfigNumber;\n  clusterRadiusRange: VisConfigRange;\n  opacity: VisConfigNumber;\n  coverage: VisConfigNumber;\n  outline: VisConfigBoolean;\n  colorRange: VisConfigColorRange;\n  strokeColorRange: VisConfigColorRange;\n  targetColor: VisConfigColorSelect;\n  strokeColor: VisConfigColorSelect;\n  colorAggregation: VisConfigSelection;\n  sizeAggregation: VisConfigSelection;\n  percentile: VisConfigRange;\n  elevationPercentile: VisConfigRange;\n  resolution: VisConfigNumber;\n  sizeScale: VisConfigNumber;\n  angle: VisConfigNumber;\n  worldUnitSize: VisConfigNumber;\n  elevationScale: VisConfigNumber;\n  enableElevationZoomFactor: VisConfigBoolean;\n  elevationRange: VisConfigRange;\n  heightRange: VisConfigRange;\n  coverageRange: VisConfigRange;\n  'hi-precision': VisConfigBoolean;\n  enable3d: VisConfigBoolean;\n  stroked: VisConfigBoolean;\n  filled: VisConfigBoolean;\n  extruded: VisConfigBoolean;\n  wireframe: VisConfigBoolean;\n  weight: VisConfigNumber;\n  heatmapRadius: VisConfigNumber;\n  darkBaseMapEnabled: VisConfigBoolean;\n  fixedHeight: VisConfigBoolean;\n  allowHover: VisConfigBoolean;\n  showNeighborOnHover: VisConfigBoolean;\n  showHighlightColor: VisConfigBoolean;\n  [key: string]: LayerVisConfigTypes;\n};\n\n// TODO: Move this to individual layers\nexport type LayerVisConfig = {\n  [key: string]: any;\n  // possible visConfigs\n\n  // thickness: number;\n  // sizeRange: number;\n  // trailLength: number;\n  // radius: number;\n  // fixedRadius: boolean;\n  // radiusRange: [number, number];\n  // clusterRadius: number;\n  // opacity: number;\n  // coverage: number;\n  // outline: boolean;\n  // colorRange: ColorRange;\n  // strokeColorRange: ColorRange;\n  // targetColor: any;\n  // strokeColor: any;\n  // colorAggregation: keyof typeof AGGREGATION_TYPES;\n  // sizeAggregation: keyof typeof AGGREGATION_TYPES;\n  // percentile: [number, number];\n  // elevationPercentile: [number, number];\n  // resolution: number;\n  // sizeScale: number;\n  // angle: number;\n  // worldUnitSize: number;\n  // elevationScale: number;\n  // enableElevationZoomFactor: boolean;\n  // heightRange: [number, number];\n  // coverageRange: [number, number];\n  // 'hi-precision': boolean;\n  // enable3d: boolean;\n  // stroked: boolean;\n  // filled: boolean;\n  // extruded: boolean;\n  // wireframe: boolean;\n  // weight: number;\n  // scenegraph: VisConfig;\n};\n\nexport type TextConfigSelect = {\n  type: 'select';\n  options: string[];\n  multiSelect: boolean;\n  searchable: boolean;\n};\n\nexport type TextConfigNumber = {\n  type: 'number';\n  range: number[];\n  value0: number;\n  step: number;\n  isRanged: boolean;\n  label: string;\n  showInput: boolean;\n};\n\nexport type LayerTextConfig = {\n  fontSize: TextConfigNumber;\n  outlineWidth: TextConfigNumber;\n  textAnchor: TextConfigSelect;\n  textAlignment: TextConfigSelect;\n};\n\nexport type LayerCallbacks = {\n  onLayerHover?: (idx: number, value: any) => void;\n  onSetLayerDomain?: (idx: number, value: any) => void;\n  onFilteredItemsChange?: (\n    idx: number,\n    event: {\n      id: string;\n      count: number;\n    }\n  ) => void;\n  onWMSFeatureInfo?: (\n    idx: number,\n    props: {\n      featureInfo: Array<{name: string; value: string}> | string | null;\n      coordinate?: [number, number] | null;\n    }\n  ) => void;\n  onRedrawNeeded?: (idx: number) => void;\n};\n\nexport type BindedLayerCallbacks = {\n  onLayerHover?: (value: any) => void;\n  onSetLayerDomain?: (value: any) => void;\n  onFilteredItemsChange?: (event: {id: string; count: number}) => void;\n  onWMSFeatureInfo?: (\n    featureInfo: Array<{name: string; value: string}> | string | null,\n    coordinate?: [number, number]\n  ) => void;\n  onRedrawNeeded?: () => void;\n};\n\nexport type VisualChannelAggregation = 'colorAggregation' | 'sizeAggregation';\n\nexport type SupportedFieldTypes =\n  | 'boolean'\n  | 'date'\n  | 'geojson'\n  | 'integer'\n  | 'real'\n  | 'string'\n  | 'timestamp'\n  | 'point'\n  | 'array'\n  | 'object'\n  | 'geoarrow'\n  | 'h3';\n\nexport type VisualChannel = {\n  property: string;\n  field: string;\n  scale: string;\n  domain: string;\n  range: string;\n  key: string;\n  channelScaleType: string;\n  nullValue?: any;\n  defaultMeasure?: any;\n  accessor?: string;\n  condition?: (config: any) => boolean;\n  defaultValue?: ((config: any) => any) | any;\n  getAttributeValue?: (config: any) => (d: any) => any;\n\n  // TODO: define fixed\n  fixed?: any;\n\n  supportedFieldTypes?: Array<SupportedFieldTypes>;\n\n  aggregation?: VisualChannelAggregation;\n};\n\nexport type VisualChannels = {[key: string]: VisualChannel};\n\nexport interface KeplerLayer {\n  id: string;\n  meta: Record<string, any>;\n  visConfigSettings: {\n    [key: string]: ValueOf<LayerVisConfigSettings>;\n  };\n  config: LayerBaseConfig;\n\n  get type(): string | null;\n  get supportedDatasetTypes(): string[] | null;\n\n  getColorScale(\n    colorScale: string,\n    colorDomain: VisualChannelDomain,\n    colorRange: ColorRange\n  ): GetVisChannelScaleReturnType;\n}\n\nexport type VisualChannelDomain = number[] | string[];\n\nexport type GetVisChannelScaleReturnType = {\n  (z: number): any;\n  byZoom?: boolean;\n} | null;\n\nexport type AggregatedBin = {\n  i: number;\n  value: number;\n  counts: number;\n};\n"
  },
  {
    "path": "src/types/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/types\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl types used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"types\": \"index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \" \",\n    \"stab\": \" \"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/types/reducers.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Field, Millisecond} from './types';\nimport type {MapViewState} from '@deck.gl/core/typed';\nimport type {ExportResolutionOption} from '@kepler.gl/constants';\n\nexport type MapState = {\n  pitch: number;\n  bearing: number;\n  latitude: number;\n  longitude: number;\n  zoom: number;\n  dragRotate: boolean;\n  width: number;\n  height: number;\n  minZoom?: number;\n  maxZoom?: number;\n  maxBounds?: Bounds;\n  initialState?: any;\n  scale?: number;\n\n  // the following 4 properties assist with split viewports that can optionally have (un)synced viewports and zooms\n  /**  Is the application split into 2 maps? */\n  isSplit: boolean;\n  /**  Are the 2 split maps having synced viewports? */\n  isViewportSynced: boolean;\n  /**  If split, are the zooms locked to each other or independent? */\n  isZoomLocked: boolean;\n  /**  An array of either 0 or 2 Viewport objects (index 0 for left map; index 1 for right map) */\n  splitMapViewports: Viewport[];\n};\n\nexport type Bounds = [number, number, number, number];\n\nexport type RangeFieldDomain = {\n  domain: number[];\n  step: number;\n  bins?: Bins;\n};\n\nexport type SelectFieldDomain = {\n  domain: boolean[];\n};\nexport type MultiSelectFieldDomain = {\n  domain: string[];\n};\n\nexport type TimeRangeFieldDomain = {\n  domain: [number, number];\n  step: number;\n  timeBins?: TimeBins;\n  mappedValue: (Millisecond | null)[];\n  // auto generated based on time domain\n  defaultTimeFormat?: string | null;\n  // custom ui input\n  timeFormat?: string | null;\n  // custom ui input\n  timezone?: string | null;\n};\nexport type FieldDomain =\n  | RangeFieldDomain\n  | TimeRangeFieldDomain\n  | SelectFieldDomain\n  | MultiSelectFieldDomain;\n\nexport interface LineDatum {\n  x: number;\n  y: number;\n  delta: string;\n  pct: number | null;\n}\n\nexport type LineChart = {\n  series: {x: number; y: number}[];\n  yDomain: number[] | undefined[];\n  xDomain: number[];\n\n  // Is this a valid part of LineChart?\n  aggregation: string;\n  interval: string;\n  yAxis: string;\n  bins?: Bins;\n};\n\ntype FilterViewType = 'side' | 'enlarged' | 'minified';\n\nexport type FilterBase<L extends LineChart> = {\n  dataId: string[];\n  id: string;\n  enabled: boolean;\n\n  // time range filter specific\n  fixedDomain: boolean;\n  view: FilterViewType;\n  isAnimating: boolean;\n  speed: number;\n  showTimeDisplay?: boolean;\n\n  // field specific\n  name: string[]; // string\n  type: string | null;\n  fieldIdx: number[]; // [integer]\n  domain: any[] | null;\n  value: any;\n  mappedValue?: number[];\n\n  // plot\n  yAxis: Field | null;\n  plotType: {\n    [key: string]: any;\n  };\n  lineChart?: L;\n  // gpu filter\n  gpu: boolean;\n  gpuChannel?: number[];\n  fieldType?: string;\n\n  // polygon\n  layerId?: string[];\n};\n\nexport type RangeFilter = FilterBase<LineChart> &\n  RangeFieldDomain & {\n    type: 'range';\n    fieldType: 'real' | 'integer';\n    value: [number, number];\n    fixedDomain: true;\n    typeOptions: ['range'];\n    plotType: {\n      [key: string]: any;\n    };\n  };\n\nexport type SelectFilter = FilterBase<LineChart> &\n  SelectFieldDomain & {\n    type: 'select';\n    fieldType: 'boolean';\n    value: boolean;\n  };\n\nexport type MultiSelectFilter = FilterBase<LineChart> &\n  MultiSelectFieldDomain & {\n    type: 'multiSelect';\n    fieldType: 'string' | 'date';\n    value: string[];\n  };\n\nexport type SyncTimelineMode = 0 | 1;\n\nexport type TimeRangeFilter = FilterBase<LineChart> &\n  TimeRangeFieldDomain & {\n    type: 'timeRange';\n    fieldType: 'timestamp';\n    fixedDomain: boolean;\n    value: [number, number];\n    plotType: {\n      [key: string]: any;\n    };\n    syncedWithLayerTimeline: boolean;\n    syncTimelineMode: SyncTimelineMode;\n    animationWindow: string;\n    invertTrendColor: boolean;\n  };\n\nexport type PolygonFilter = FilterBase<LineChart> & {\n  layerId: string[];\n  type: 'polygon';\n  fixedDomain: true;\n  value: Feature;\n};\n\nexport type Filter =\n  | FilterBase<LineChart>\n  | RangeFilter\n  | TimeRangeFilter\n  | SelectFilter\n  | MultiSelectFilter\n  | PolygonFilter;\n\nexport interface Bin {\n  count: number;\n  indexes: number[];\n  x0: number;\n  x1: number;\n}\n\nexport interface Bins {\n  [dataId: string]: Bin[];\n}\n\nexport interface TimeBins {\n  [dataId: string]: {\n    [interval: string]: Bin[];\n  };\n}\n\nexport interface PlotType {\n  interval: ValueOf<Interval>;\n  type: ValueOf<PlotTypes>;\n  aggregation: ValueOf<AggregationTypes>;\n  defaultTimeFormat: string;\n  colorsByDataId?: {[dataId: string]: string};\n}\n\nexport type Feature = {\n  id: string;\n  properties: any;\n  geometry: {\n    type: string;\n    coordinates: any;\n  };\n  type?: string;\n};\nexport type FeatureSelectionContext = {\n  rightClick: boolean;\n  position: {x: number; y: number};\n  mapIndex?: number;\n};\nexport type FeatureValue = {\n  id: string;\n  properties: {\n    filterId: string;\n  };\n  geometry: {\n    type: string;\n    coordinates: any;\n  };\n};\nexport type Editor = {\n  mode: string;\n  features: Feature[];\n  selectedFeature: any;\n  selectionContext?: FeatureSelectionContext;\n  visible: boolean;\n};\n\nexport type SplitMapLayers = {[key: string]: boolean};\nexport type SplitMap = {\n  id?: string;\n  layers: SplitMapLayers;\n};\n\n/** See \"Locale aware formats\" at https://momentjs.com/docs/#/parsing/string-format/ */\nexport type AnimationConfigTimeFormat = 'L' | 'L LT' | 'L LTS';\n\nexport type AnimationConfig = {\n  domain: [number, number] | null;\n  currentTime: number | null;\n  speed: number;\n  duration?: number | null;\n  isAnimating?: boolean;\n  timeSteps: number[] | null;\n  // auto generated based on time domain\n  defaultTimeFormat: AnimationTimeFormat | null;\n  // custom ui input\n  timeFormat?: AnimationTimeFormat | null;\n  // custom ui input\n  timezone?: string | null;\n  // hide or show control\n  hideControl?: boolean;\n};\n\nexport type FilterAnimationConfig = Pick<\n  TimeRangeFilter,\n  | 'dataId'\n  | 'value'\n  | 'animationWindow'\n  | 'speed'\n  | 'syncedWithLayerTimeline'\n  | 'syncTimelineMode'\n  | 'timezone'\n>;\n\nexport type Timeline = {\n  domain: [number, number] | null;\n  value: number | [number, number];\n  speed: number;\n  isAnimating: boolean;\n  step?: null | number;\n  timeSteps?: null | number[];\n  defaultTimeFormat?: null | string;\n  timeFormat?: null | string;\n  timezone?: null | string;\n  timeBins?: null | Record<string, any>;\n  animationWindow?: null | Filter['animationWindow'];\n  marks?: null | number[];\n} & Record<string, any>;\n\nexport type BaseInteraction = {\n  label: string;\n  enabled: boolean;\n};\nexport type TooltipField = {\n  name: string;\n  format: string | null;\n};\nexport type CompareType = string | null;\nexport type TooltipInfo = BaseInteraction & {\n  id: 'tooltip';\n  config: {\n    fieldsToShow: {\n      [key: string]: TooltipField[];\n    };\n    compareMode: boolean;\n    compareType: CompareType;\n  };\n};\nexport type Geocoder = BaseInteraction & {\n  id: 'geocoder';\n  position: number[] | null;\n};\nexport type Brush = BaseInteraction & {\n  id: 'brush';\n  config: {\n    size: number;\n  };\n};\nexport type Coordinate = BaseInteraction & {\n  id: 'coordinate';\n  position: number[] | null;\n};\nexport type InteractionConfig = {\n  tooltip: TooltipInfo;\n  geocoder: Geocoder;\n  brush: Brush;\n  coordinate: Coordinate;\n};\nexport type MapInfo = {\n  title: string;\n  description: string;\n};\nexport type FileLoading = {\n  filesToLoad: FileList;\n  onFinish: (payload: any) => any;\n  fileCache: any[];\n};\nexport type FileLoadingProgress = {\n  [key: string]: {\n    percent: number;\n    message: string;\n    fileName: string;\n    error: any;\n  };\n};\n\nexport type LayerGroup = {\n  slug: string;\n  filter(layer: {id: string}): boolean;\n  defaultVisibility: boolean;\n};\n\nexport type CustomStyleType =\n  | 'LOCAL'\n  | 'MANAGED'\n  // boolean for backwards compatability with previous map configs\n  | boolean;\n\nexport type BaseMapColorModes = 'NONE' | 'DARK' | 'LIGHT';\n\nexport type BaseMapStyle = {\n  id: string;\n  label: string;\n  url: string;\n  icon: string;\n  style?: object;\n  layerGroups: LayerGroup[];\n  accessToken?: string;\n  custom?: CustomStyleType;\n  colorMode?: BaseMapColorModes;\n  complimentaryStyleId?: string;\n};\n\nexport declare type ExportImage = {\n  ratio: EXPORT_IMG_RATIOS;\n  resolution: ExportResolutionOption;\n  legend: boolean;\n  mapH: number;\n  mapW: number;\n  imageSize: {\n    zoomOffset: number;\n    scale: number;\n    imageW: number;\n    imageH: number;\n  };\n  imageDataUri: string;\n  exporting: boolean;\n  processing: boolean;\n  error: Error | false;\n  center: boolean;\n  escapeXhtmlForWebpack?: boolean;\n};\n\nexport type ExportData = {\n  selectedDataset: string;\n  dataType: string;\n  filtered: boolean;\n};\n\nexport type ExportHtml = {\n  exportMapboxAccessToken: null | string;\n  userMapboxToken: string;\n  mode: string;\n};\nexport type ExportJson = {\n  hasData: boolean;\n};\nexport type ExportMap = {\n  HTML: ExportHtml;\n  JSON: ExportJson;\n  format: 'HTML' | 'JSON';\n};\n\nexport type MapControlItem = {\n  show: boolean;\n  active: boolean;\n  disableClose?: boolean;\n  activeMapIndex?: number;\n};\n\nexport type MapLegendControlSettings = {\n  position: {\n    x: number;\n    y: number;\n    anchorX: 'left' | 'right';\n    anchorY: 'top' | 'bottom';\n  };\n  contentHeight: number;\n};\n\nexport type MapControlMapLegend = MapControlItem & {\n  disableEdit?: boolean;\n  settings?: MapLegendControlSettings;\n};\nexport type MapControls = {\n  visibleLayers?: MapControlItem;\n  mapLegend?: MapControlMapLegend;\n  toggle3d?: MapControlItem;\n  splitMap?: MapControlItem;\n  mapDraw?: MapControlItem;\n  mapLocale?: MapControlItem;\n  effect?: MapControlItem;\n  aiAssistant?: MapControlItem;\n};\n\nexport type LoadFiles = {\n  fileLoading: boolean;\n};\n\nexport type Notifications = {\n  message: string;\n  type: string;\n  topic: string;\n  id: string;\n  count: number;\n  isExpanded?: boolean;\n};\n\nexport type Locale = string;\n\nexport type PanelListView = string;\n\nexport type UiState = {\n  readOnly: boolean;\n  activeSidePanel: string | null;\n  currentModal: string | null;\n  datasetKeyToRemove: string | null;\n  visibleDropdown: string | null;\n  // export image modal ui\n  exportImage: ExportImage;\n  // export data modal ui\n  exportData: ExportData;\n  // html export\n  exportMap: ExportMap;\n  // map control panels\n  mapControls: MapControls;\n  // ui notifications\n  notifications: Notifications[];\n  // load files\n  loadFiles: LoadFiles;\n  // Locale of the UI\n  locale: Locale;\n  // view layers by list or dataset\n  layerPanelListView: PanelListView;\n  // view filters by list or dataset\n  filterPanelListView: PanelListView;\n  // side panel close button visibility\n  isSidePanelCloseButtonVisible: boolean | null;\n  // positive value indicates that something is loading\n  loadingIndicatorValue?: number;\n};\n\n/** Width of viewport */\nexport type Viewport = {\n  /**  Width of viewport */\n  width?: number;\n  /**  Height of viewport */\n  height?: number;\n  /**  Zoom of viewport */\n  zoom?: number;\n  /**  Camera angle in degrees (0 is straight down) */\n  pitch?: number;\n  /**  Map rotation in degrees (0 means north is up) */\n  bearing?: number;\n  /**  Latitude center of viewport on map in mercator projection */\n  latitude?: number;\n  /**  Longitude Center of viewport on map in mercator projection */\n  longitude?: number;\n  /**  Whether to enable drag and rotate map into perspective viewport */\n  dragRotate?: boolean;\n  /**  Minimum allowed viewport zoom */\n  minZoom?: number;\n  /**  Maximum allowed viewport zoom */\n  maxZoom?: number;\n  /**  Maximum geographical bounds, pan/zoom operations are constrained within those bounds */\n  maxBounds?: Bounds;\n  /** viewport transition duration use by geocoder panel **/\n  transitionDuration?: MapViewState['transitionDuration'];\n  /** viewport transition duration use by geocoder panel **/\n  transitionInterpolator?: MapViewState['transitionInterpolator'];\n};\n\nexport type MapStyles = {\n  [key: string]: BaseMapStyle;\n};\n\nexport type VisibleLayerGroups = {\n  [key: string]: boolean;\n};\n\nexport type InputStyle = {\n  id?: string | null;\n  accessToken: string | null;\n  error: boolean;\n  isValid: boolean;\n  label: string | null;\n  style: any | null;\n  url: string | null;\n  icon: string | null;\n  custom: CustomStyleType;\n  uploadedFile: File | null;\n};\n\nexport type FilterRecord = {\n  dynamicDomain: Filter[];\n  fixedDomain: Filter[];\n  cpu: Filter[];\n  gpu: Filter[];\n};\n\nexport type FilterDatasetOpt = {\n  // only allow cpu filtering\n  cpuOnly?: boolean;\n  // ignore filter for domain calculation\n  ignoreDomain?: boolean;\n};\n\n/* DUPLICATES OF FILTER TYPES ABOVE, REMOVE ONCE TYPES ABOVE ARE FIXED */\n\ntype FilterBaseOmitRedudant = Omit<FilterBase<LineChart>, 'type' | 'domain'>;\n\nexport type TypedRangeFilter = FilterBaseOmitRedudant &\n  RangeFieldDomain & {\n    type: 'range';\n    fieldType: 'real' | 'integer';\n    value: [number, number];\n    fixedDomain: true;\n    typeOptions: ['range'];\n  };\n\nexport type TypedSelectFilter = FilterBaseOmitRedudant &\n  SelectFieldDomain & {\n    type: 'select';\n    fieldType: 'boolean';\n    value: boolean;\n  };\n\nexport type TypedMultiSelectFilter = FilterBaseOmitRedudant &\n  MultiSelectFieldDomain & {\n    type: 'multiSelect';\n    fieldType: 'string' | 'date';\n    value: string[];\n  };\n\nexport type TypedTimeRangeFilter = FilterBaseOmitRedudant &\n  TimeRangeFieldDomain & {\n    type: 'timeRange';\n    fieldType: 'timestamp';\n    fixedDomain: true;\n    value: [number, number];\n    plotType: {\n      [key: string]: any;\n    };\n    animationWindow: string;\n    invertTrendColor: boolean;\n  };\n\nexport type TypedPolygonFilter = FilterBaseOmitRedudant & {\n  layerId: string[];\n  type: 'polygon';\n  fixedDomain: true;\n  value: Feature;\n};\n\nexport type TypedFilter =\n  | TypedRangeFilter\n  | TypedTimeRangeFilter\n  | TypedSelectFilter\n  | TypedMultiSelectFilter\n  | TypedPolygonFilter;"
  },
  {
    "path": "src/types/schemas.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {RGBColor, Merge, RequireFrom} from './types';\n\nimport {Filter, InteractionConfig, AnimationConfig, SplitMap, Feature} from './reducers';\n\nimport {LayerTextLabel} from './layers';\n\nexport type SavedFilter = {\n  dataId: Filter['dataId'];\n  id: Filter['id'];\n  name: Filter['name'];\n  type: Filter['type'];\n  value: Filter['value'];\n  // deprecated\n  enlarged?: Filter['enlarged'];\n  view?: Filter['view'];\n  plotType: Filter['plotType'];\n  yAxis: {\n    name: string;\n    type: string;\n  } | null;\n  speed: Filter['speed'];\n  layerId: Filter['layerId'];\n  syncedWithLayerTimeline: Filter['syncedWithLayerTimeline'];\n  syncTimelineMode: Filter['syncTimelineMode'];\n};\nexport type MinSavedFilter = RequireFrom<SavedFilter, 'dataId' | 'id' | 'name' | 'type' | 'value'>;\nexport type ParsedFilter = SavedFilter | MinSavedFilter;\n\nexport type SavedInteractionConfig = {\n  tooltip: InteractionConfig['tooltip']['config'] & {\n    enabled: boolean;\n  };\n  geocoder: {\n    enabled: boolean;\n  };\n  brush: InteractionConfig['brush']['config'] & {\n    enabled: boolean;\n  };\n  coordinate: {\n    enabled: boolean;\n  };\n};\n\nexport type SavedScale = string;\nexport type SavedVisualChannels = {\n  [key: string]: SavedField | SavedScale;\n};\n\nexport type SavedLayer = {\n  id: string;\n  type: string;\n  config: {\n    dataId: string;\n    label: string;\n    color: RGBColor;\n    highlightColor?: RGBColor;\n    columns: {\n      [key: string]: string;\n    };\n    columnMode: string;\n    isVisible: boolean;\n    visConfig: Record<string, any>;\n    hidden: boolean;\n    textLabel: Merge<LayerTextLabel, {field: {name: string; type: string} | null}>;\n  };\n  visualChannels: SavedVisualChannels;\n};\nexport type MinSavedLayerConfig = RequireFrom<SavedLayer['config'], 'dataId' | 'columns'>;\nexport type MinSavedLayer = {\n  id: string;\n  type: string;\n  config: MinSavedLayerConfig;\n  visualChannels?: SavedVisualChannels;\n};\nexport type ParsedLayer = SavedLayer | MinSavedLayer;\n\nexport type ParsedEffect = {\n  id: string;\n  type: string;\n  isEnabled: boolean;\n  parameters: Record<string, number | [number, number] | {value: number}>;\n};\n\nexport type SavedEffect = ParsedEffect;\n\nexport type SavedAnimationConfig = {\n  currentTime: AnimationConfig['currentTime'];\n  speed: AnimationConfig['speed'];\n};\n\nexport type SavedEditor = {\n  features: Feature[];\n  visible: boolean;\n};\n\nexport type SavedVisState = {\n  filters: SavedFilter[];\n  layers: SavedLayer[];\n  effects: SavedEffect[];\n  interactionConfig: SavedInteractionConfig;\n  layerBlending: string;\n  overlayBlending?: string;\n  splitMaps: SplitMap[];\n  animationConfig: SavedAnimationConfig;\n  editor?: SavedEditor;\n};\n\n// Min saved config can be passed to addDataToMap\nexport type MinSavedVisStateV1 = {\n  filters?: MinSavedFilter[];\n  layers?: MinSavedLayer[];\n  effects?: SavedEffect[];\n  interactionConfig?: Partial<SavedInteractionConfig>;\n  layerBlending?: string;\n  overlayBlending?: string;\n  splitMaps?: SplitMap[];\n  animationConfig?: SavedAnimationConfig;\n  editor?: SavedEditor;\n};\n\nexport type ParsedVisState = {\n  layers?: ParsedLayer[];\n  effects?: ParsedEffect[];\n  filters?: ParsedFilter[];\n  effects?: ParsedEffect[];\n  interactionConfig?: Partial<SavedInteractionConfig>;\n  layerBlending?: string;\n  overlayBlending?: string;\n  splitMaps?: SplitMap[];\n  animationConfig?: Partial<SavedAnimationConfig>;\n};\n\nexport type ParsedUiState = {\n  mapControls: {\n    mapLegend: {\n      active: boolean;\n      settings: {\n        position: string;\n        contentHeight: number;\n      };\n    };\n  };\n};\n\nexport type SavedMapState = {\n  bearing: number;\n  dragRotate: boolean;\n  latitude: number;\n  longitude: number;\n  pitch: number;\n  zoom: number;\n  isSplit: boolean;\n};\n\nexport type SavedMapStateV1 = SavedMapState;\nexport type MinSavedMapStateV1 = Partial<SavedMapState>;\nexport type ParsedMapState = Partial<SavedMapState>;\n\nexport type SavedLayerGroups = {\n  [key: string]: boolean;\n};\n\nexport type SavedCustomMapStyle = {\n  [key: string]: {\n    accessToken?: string;\n    custom: boolean;\n    icon: string;\n    id: string;\n    label: string;\n    url: string;\n  };\n};\n\nexport type SavedMapStyle = {\n  styleType: string;\n  topLayerGroups: SavedLayerGroups;\n  visibleLayerGroups: SavedLayerGroups;\n  threeDBuildingColor: RGBColor;\n  mapStyles: SavedCustomMapStyle;\n};\n\nexport type SavedMapStyleV1 = SavedMapStyle;\nexport type MinSavedMapStyleV1 = Partial<SavedMapStyleV1>;\n\nexport type ParsedMapStyle = Partial<SavedMapStyleV1>;\n\n/** Schema for v1 saved configuration */\nexport type SavedConfigV1 = {\n  version: 'v1';\n  config: {\n    visState: SavedVisState;\n    mapState: SavedMapState;\n    mapStyle: SavedMapStyle;\n  };\n};\n\n// supported by addDataToMap\nexport type MinSavedConfigV1 = {\n  version: 'v1';\n  config: {\n    visState?: MinSavedVisStateV1;\n    mapState?: MinSavedMapStateV1;\n    mapStyle?: MinSavedMapStyleV1;\n  };\n};\n\n/** Schema for a parsed configuration (\"normalized\" across versions) */\nexport type ParsedConfig = {\n  visState?: ParsedVisState;\n  mapState?: ParsedMapState;\n  mapStyle?: ParsedMapStyle;\n  uiState?: ParsedUiState;\n};\n\nexport type SavedField = {\n  name: string;\n  type: string;\n} | null;\nexport type ParsedField = {\n  name: string;\n  type: string;\n  format: string;\n  analyzerType: string;\n};\n\nexport type SavedDatasetV1 = {\n  version: 'v1';\n  data: {\n    id: string;\n    label: string;\n    color: RGBColor;\n    allData: any[][];\n    fields: SavedField[];\n  };\n};\n\nexport type ParsedDataset = {\n  data: {\n    fields: ParsedField[];\n    rows: any[][];\n  };\n  info: {\n    id?: string;\n    label?: string;\n    color?: RGBColor;\n  };\n};\n\nexport type SavedMap = {\n  datasets: SavedDatasetV1[];\n  config: SavedConfigV1;\n  info: {\n    app: string;\n    created_at: string;\n    title: string;\n    description: string;\n  };\n};\n\nexport type LoadedMap = {datasets?: ParsedDataset[] | null; config?: ParsedConfig | null};\n"
  },
  {
    "path": "src/types/stac.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * STAC EO Extension for STAC Items.\n */\nexport type EOExtension =\n  | ({\n      type: 'Feature';\n      properties: Fields;\n      assets: {\n        [k: string]: Fields;\n      };\n      [k: string]: unknown;\n    } & StacExtensions)\n  | ({\n      type: 'Collection';\n      assets?: {\n        [k: string]: Fields;\n      };\n      item_assets?: {\n        [k: string]: Fields;\n      };\n      [k: string]: unknown;\n    } & StacExtensions);\nexport type CloudCover = number;\nexport type Bands = [Band, ...Band[]];\nexport type NameOfTheBand = string;\nexport type CommonNameOfTheBand =\n  | 'coastal'\n  | 'blue'\n  | 'green'\n  | 'red'\n  | 'rededge'\n  | 'yellow'\n  | 'pan'\n  | 'nir'\n  | 'nir08'\n  | 'nir09'\n  | 'cirrus'\n  | 'swir16'\n  | 'swir22'\n  | 'lwir'\n  | 'lwir11'\n  | 'lwir12';\nexport type CenterWavelength = number;\nexport type FullWidthHalfMaxFWHM = number;\n\nexport interface Fields {\n  'eo:cloud_cover'?: CloudCover;\n  'eo:bands'?: Bands;\n  /**\n   * This interface was referenced by `Fields`'s JSON-Schema definition\n   * via the `patternProperty` \"^(?!eo:)\".\n   */\n  [k: string]: unknown;\n}\nexport interface Band {\n  name?: NameOfTheBand;\n  common_name?: CommonNameOfTheBand;\n  center_wavelength?: CenterWavelength;\n  full_width_half_max?: FullWidthHalfMaxFWHM;\n  [k: string]: unknown;\n}\nexport interface StacExtensions {\n  stac_extensions: unknown[];\n  [k: string]: unknown;\n}\n\nexport type STACItem = Core;\nexport type Core = GeoJSONFeature &\n  (\n    | {\n        geometry: GeoJSONGeometry;\n        bbox: {\n          [k: string]: unknown;\n        } & number[];\n        [k: string]: unknown;\n      }\n    | {\n        geometry: null;\n        bbox?: {\n          [k: string]: unknown;\n        };\n        [k: string]: unknown;\n      }\n  ) & {\n    stac_version: STACVersion;\n    stac_extensions?: STACExtensions;\n    id: ProviderID;\n    links: ItemLinks;\n    assets: AssetLinks;\n    properties: CommonMetadata &\n      (\n        | {\n            datetime: {\n              [k: string]: unknown;\n            };\n            [k: string]: unknown;\n          }\n        | {\n            [k: string]: unknown;\n          }\n      );\n    [k: string]: unknown;\n  };\nexport type GeoJSONGeometry =\n  | GeoJSONPoint2\n  | GeoJSONLineString2\n  | GeoJSONPolygon2\n  | GeoJSONMultiPoint2\n  | GeoJSONMultiLineString2\n  | GeoJSONMultiPolygon2;\nexport type STACVersion = '1.0.0';\nexport type ReferenceToAJSONSchema = string;\nexport type STACExtensions = ReferenceToAJSONSchema[];\n/**\n * Provider item ID\n */\nexport type ProviderID = string;\nexport type LinkReference = string;\nexport type LinkRelationType = string;\nexport type LinkType = string;\nexport type LinkTitle = string;\n/**\n * Links to item relations\n */\nexport type ItemLinks = Link[];\nexport type Asset = {\n  href: AssetReference;\n  title?: AssetTitle;\n  description?: AssetDescription;\n  type?: AssetType;\n  roles?: AssetRoles;\n  [k: string]: unknown;\n} & CommonMetadata;\nexport type AssetReference = string;\nexport type AssetTitle = string;\nexport type AssetDescription = string;\nexport type AssetType = string;\nexport type AssetRoles = string[];\nexport type CommonMetadata = BasicDescriptiveFields &\n  DateAndTimeFields &\n  InstrumentFields &\n  LicensingFields &\n  ProviderFields;\n/**\n * A human-readable title describing the Item.\n */\nexport type ItemTitle = string;\n/**\n * Detailed multi-line description to fully explain the Item.\n */\nexport type ItemDescription = string;\n/**\n * The searchable date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type DateAndTime = string | null;\n/**\n * The searchable start date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type StartDateAndTime = string;\n/**\n * The searchable end date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type EndDateAndTime = string;\nexport type CreationTime = string;\nexport type LastUpdateTime = string;\nexport type Platform = string;\nexport type Instruments = string[];\nexport type Constellation = string;\nexport type Mission = string;\nexport type GroundSampleDistance = number;\nexport type OrganizationName = string;\nexport type OrganizationDescription = string;\nexport type OrganizationRoles = ('producer' | 'licensor' | 'processor' | 'host')[];\nexport type OrganizationHomepage = string;\nexport type Providers = {\n  name: OrganizationName;\n  description?: OrganizationDescription;\n  roles?: OrganizationRoles;\n  url?: OrganizationHomepage;\n  [k: string]: unknown;\n}[];\n\nexport interface GeoJSONFeature {\n  type: 'Feature';\n  id?: number | string;\n  properties: null | {\n    [k: string]: unknown;\n  };\n  geometry:\n    | null\n    | GeoJSONPoint\n    | GeoJSONLineString\n    | GeoJSONPolygon\n    | GeoJSONMultiPoint\n    | GeoJSONMultiLineString\n    | GeoJSONMultiPolygon\n    | GeoJSONGeometryCollection;\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPoint {\n  type: 'Point';\n  coordinates: [number, number, ...number[]];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONLineString {\n  type: 'LineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPolygon {\n  type: 'Polygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPoint {\n  type: 'MultiPoint';\n  coordinates: [number, number, ...number[]][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiLineString {\n  type: 'MultiLineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPolygon {\n  type: 'MultiPolygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONGeometryCollection {\n  type: 'GeometryCollection';\n  geometries: (\n    | GeoJSONPoint1\n    | GeoJSONLineString1\n    | GeoJSONPolygon1\n    | GeoJSONMultiPoint1\n    | GeoJSONMultiLineString1\n    | GeoJSONMultiPolygon1\n  )[];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPoint1 {\n  type: 'Point';\n  coordinates: [number, number, ...number[]];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONLineString1 {\n  type: 'LineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPolygon1 {\n  type: 'Polygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPoint1 {\n  type: 'MultiPoint';\n  coordinates: [number, number, ...number[]][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiLineString1 {\n  type: 'MultiLineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPolygon1 {\n  type: 'MultiPolygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPoint2 {\n  type: 'Point';\n  coordinates: [number, number, ...number[]];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONLineString2 {\n  type: 'LineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONPolygon2 {\n  type: 'Polygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPoint2 {\n  type: 'MultiPoint';\n  coordinates: [number, number, ...number[]][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiLineString2 {\n  type: 'MultiLineString';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface GeoJSONMultiPolygon2 {\n  type: 'MultiPolygon';\n  coordinates: [\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    [number, number, ...number[]],\n    ...[number, number, ...number[]][]\n  ][][];\n  bbox?: [number, number, number, number, ...number[]];\n  [k: string]: unknown;\n}\nexport interface Link {\n  href: LinkReference;\n  rel: LinkRelationType;\n  type?: LinkType;\n  title?: LinkTitle;\n  [k: string]: unknown;\n}\n/**\n * Links to assets\n */\nexport interface AssetLinks {\n  [k: string]: Asset;\n}\nexport interface BasicDescriptiveFields {\n  title?: ItemTitle;\n  description?: ItemDescription;\n  [k: string]: unknown;\n}\nexport interface DateAndTimeFields {\n  datetime?: DateAndTime;\n  start_datetime?: StartDateAndTime;\n  end_datetime?: EndDateAndTime;\n  created?: CreationTime;\n  updated?: LastUpdateTime;\n  [k: string]: unknown;\n}\nexport interface InstrumentFields {\n  platform?: Platform;\n  instruments?: Instruments;\n  constellation?: Constellation;\n  mission?: Mission;\n  gsd?: GroundSampleDistance;\n  [k: string]: unknown;\n}\nexport interface LicensingFields {\n  license?: string;\n  [k: string]: unknown;\n}\nexport interface ProviderFields {\n  providers?: Providers;\n  [k: string]: unknown;\n}\n\n/**\n * This object represents Collections in a SpatioTemporal Asset Catalog.\n */\nexport type STACCollectionSpecification = STACCollection;\nexport type STACVersion = '1.0.0';\nexport type ReferenceToAJSONSchema = string;\nexport type STACExtensions = ReferenceToAJSONSchema[];\nexport type TypeOfSTACEntity = 'Collection';\nexport type Identifier = string;\nexport type Title = string;\nexport type Description = string;\nexport type Keywords = string[];\nexport type CollectionLicenseName = string;\nexport type OrganizationName = string;\nexport type OrganizationDescription = string;\nexport type OrganizationRoles = ('producer' | 'licensor' | 'processor' | 'host')[];\nexport type OrganizationHomepage = string;\nexport type SpatialExtents = [SpatialExtent, ...SpatialExtent3[]];\nexport type SpatialExtent = SpatialExtent1 & SpatialExtent2;\nexport type SpatialExtent1 =\n  | {\n      [k: string]: unknown;\n    }\n  | {\n      [k: string]: unknown;\n    };\nexport type SpatialExtent2 = number[];\nexport type SpatialExtent3 = SpatialExtent1 & SpatialExtent2;\nexport type TemporalExtents = [TemporalExtent, ...TemporalExtent[]];\nexport type TemporalExtent = [string | null, string | null];\nexport type AssetReference = string;\nexport type AssetTitle = string;\nexport type AssetDescription = string;\nexport type AssetType = string;\nexport type AssetRoles = string[];\n/**\n * A human-readable title describing the Item.\n */\nexport type ItemTitle = string;\n/**\n * Detailed multi-line description to fully explain the Item.\n */\nexport type ItemDescription = string;\n/**\n * The searchable date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type DateAndTime = string | null;\n/**\n * The searchable start date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type StartDateAndTime = string;\n/**\n * The searchable end date/time of the assets, in UTC (Formatted in RFC 3339)\n */\nexport type EndDateAndTime = string;\nexport type CreationTime = string;\nexport type LastUpdateTime = string;\nexport type Platform = string;\nexport type Instruments = string[];\nexport type Constellation = string;\nexport type Mission = string;\nexport type GroundSampleDistance = number;\nexport type OrganizationName1 = string;\nexport type OrganizationDescription1 = string;\nexport type OrganizationRoles1 = ('producer' | 'licensor' | 'processor' | 'host')[];\nexport type OrganizationHomepage1 = string;\nexport type Providers = {\n  name: OrganizationName1;\n  description?: OrganizationDescription1;\n  roles?: OrganizationRoles1;\n  url?: OrganizationHomepage1;\n  [k: string]: unknown;\n}[];\nexport type LinkReference = string;\nexport type LinkRelationType = string;\nexport type LinkType = string;\nexport type LinkTitle = string;\nexport type Links = Link[];\nexport type JSONSchema = CoreSchemaMetaSchema;\nexport type CoreSchemaMetaSchema =\n  | {\n      $id?: string;\n      $schema?: string;\n      $ref?: string;\n      $comment?: string;\n      title?: string;\n      description?: string;\n      default?: true;\n      readOnly?: boolean;\n      writeOnly?: boolean;\n      examples?: true[];\n      multipleOf?: number;\n      maximum?: number;\n      exclusiveMaximum?: number;\n      minimum?: number;\n      exclusiveMinimum?: number;\n      maxLength?: number;\n      minLength?: number;\n      pattern?: string;\n      additionalItems?: CoreSchemaMetaSchema;\n      items?: CoreSchemaMetaSchema | [CoreSchemaMetaSchema, ...CoreSchemaMetaSchema[]];\n      maxItems?: number;\n      minItems?: number;\n      uniqueItems?: boolean;\n      contains?: CoreSchemaMetaSchema;\n      maxProperties?: number;\n      minProperties?: number;\n      required?: string[];\n      additionalProperties?: CoreSchemaMetaSchema;\n      definitions?: {\n        [k: string]: CoreSchemaMetaSchema;\n      };\n      properties?: {\n        [k: string]: CoreSchemaMetaSchema;\n      };\n      patternProperties?: {\n        [k: string]: CoreSchemaMetaSchema;\n      };\n      dependencies?: {\n        [k: string]: CoreSchemaMetaSchema | string[];\n      };\n      propertyNames?: CoreSchemaMetaSchema;\n      const?: true;\n      enum?: [true, ...unknown[]];\n      type?:\n        | ('array' | 'boolean' | 'integer' | 'null' | 'number' | 'object' | 'string')\n        | [\n            'array' | 'boolean' | 'integer' | 'null' | 'number' | 'object' | 'string',\n            ...('array' | 'boolean' | 'integer' | 'null' | 'number' | 'object' | 'string')[]\n          ];\n      format?: string;\n      contentMediaType?: string;\n      contentEncoding?: string;\n      if?: CoreSchemaMetaSchema;\n      then?: CoreSchemaMetaSchema;\n      else?: CoreSchemaMetaSchema;\n      allOf?: [CoreSchemaMetaSchema, ...CoreSchemaMetaSchema[]];\n      anyOf?: [CoreSchemaMetaSchema, ...CoreSchemaMetaSchema[]];\n      oneOf?: [CoreSchemaMetaSchema, ...CoreSchemaMetaSchema[]];\n      not?: CoreSchemaMetaSchema;\n      [k: string]: unknown;\n    }\n  | boolean;\nexport type MinimumValue = number | string;\nexport type MaximumValue = number | string;\nexport type SetOfValues = [\n  {\n    [k: string]: unknown;\n  },\n  ...{\n    [k: string]: unknown;\n  }[]\n];\n\n/**\n * These are the fields specific to a STAC Collection. All other fields are inherited from STAC Catalog.\n */\nexport interface STACCollection {\n  stac_version: STACVersion;\n  stac_extensions?: STACExtensions;\n  type: TypeOfSTACEntity;\n  id: Identifier;\n  title?: Title;\n  description: Description;\n  keywords?: Keywords;\n  license: CollectionLicenseName;\n  providers?: {\n    name: OrganizationName;\n    description?: OrganizationDescription;\n    roles?: OrganizationRoles;\n    url?: OrganizationHomepage;\n    [k: string]: unknown;\n  }[];\n  extent: Extents;\n  assets?: AssetLinks;\n  links: Links;\n  summaries?: Summaries;\n  [k: string]: unknown;\n}\nexport interface Extents {\n  spatial: SpatialExtentObject;\n  temporal: TemporalExtentObject;\n  [k: string]: unknown;\n}\nexport interface SpatialExtentObject {\n  bbox: SpatialExtents;\n  [k: string]: unknown;\n}\nexport interface TemporalExtentObject {\n  interval: TemporalExtents;\n  [k: string]: unknown;\n}\n/**\n * Links to assets\n */\nexport interface AssetLinks {\n  [k: string]: {\n    href: AssetReference;\n    title?: AssetTitle;\n    description?: AssetDescription;\n    type?: AssetType;\n    roles?: AssetRoles;\n    [k: string]: unknown;\n  } & (BasicDescriptiveFields &\n    DateAndTimeFields &\n    InstrumentFields &\n    LicensingFields &\n    ProviderFields);\n}\nexport interface BasicDescriptiveFields {\n  title?: ItemTitle;\n  description?: ItemDescription;\n  [k: string]: unknown;\n}\nexport interface DateAndTimeFields {\n  datetime?: DateAndTime;\n  start_datetime?: StartDateAndTime;\n  end_datetime?: EndDateAndTime;\n  created?: CreationTime;\n  updated?: LastUpdateTime;\n  [k: string]: unknown;\n}\nexport interface InstrumentFields {\n  platform?: Platform;\n  instruments?: Instruments;\n  constellation?: Constellation;\n  mission?: Mission;\n  gsd?: GroundSampleDistance;\n  [k: string]: unknown;\n}\nexport interface LicensingFields {\n  license?: string;\n  [k: string]: unknown;\n}\nexport interface ProviderFields {\n  providers?: Providers;\n  [k: string]: unknown;\n}\nexport interface Link {\n  href: LinkReference;\n  rel: LinkRelationType;\n  type?: LinkType;\n  title?: LinkTitle;\n  [k: string]: unknown;\n}\nexport interface Summaries {\n  [k: string]: JSONSchema | Range | SetOfValues;\n}\nexport interface Range {\n  minimum: MinimumValue;\n  maximum: MaximumValue;\n  [k: string]: unknown;\n}\n\nexport type AssetTitle = string;\nexport type AssetDescription = string;\nexport type AssetType = string;\nexport type AssetRoles = string[];\n\n/**\n * STAC Item Assets Definition Extension for STAC Collections.\n */\nexport interface ItemAssetsDefinitionExtension {\n  stac_extensions: unknown[];\n  type: 'Collection';\n  item_assets: {\n    [k: string]: {\n      href?: DisallowHref;\n      title?: AssetTitle;\n      description?: AssetDescription;\n      type?: AssetType;\n      roles?: AssetRoles;\n      [k: string]: unknown;\n    };\n  };\n  [k: string]: unknown;\n}\nexport interface DisallowHref {\n  [k: string]: unknown;\n}\n\n/**\n * STAC Raster Extension for STAC Items.\n */\nexport type RasterExtension =\n  | ({\n      type: 'Feature';\n      assets: {\n        [k: string]: Assetfields;\n      };\n      [k: string]: unknown;\n    } & StacExtensions)\n  | {\n      type: 'Collection';\n      assets?: {\n        [k: string]: Assetfields;\n      };\n      item_assets?: {\n        [k: string]: Assetfields;\n      };\n      [k: string]: unknown;\n    };\nexport type Bands = [Band, ...Band[]];\nexport type DataTypeOfTheBand =\n  | 'int8'\n  | 'int16'\n  | 'int32'\n  | 'int64'\n  | 'uint8'\n  | 'uint16'\n  | 'uint32'\n  | 'uint64'\n  | 'float16'\n  | 'float32'\n  | 'float64'\n  | 'cint16'\n  | 'cint32'\n  | 'cfloat32'\n  | 'cfloat64'\n  | 'other';\nexport type UnitDenominationOfThePixelValue = string;\nexport type TheActualNumberOfBitsUsedForThisBand = number;\nexport type PixelSamplingInTheBand = 'area' | 'point';\nexport type NoDataPixelValue = number | ('nan' | 'inf' | '-inf');\nexport type MultiplicatorFactorOfThePixelValueToTransformIntoTheValue = number;\nexport type NumberToBeAddedToThePixelValueToTransformIntoTheValue = number;\nexport type AverageSpatialResolutionInMetersOfThePixelsInTheBand = number;\nexport type MeanValueOfAllThePixelsInTheBand = number;\nexport type MinimumValueOfAllThePixelsInTheBand = number;\nexport type MaximumValueOfAllThePixelsInTheBand = number;\nexport type StandardDeviationValueOfAllThePixelsInTheBand = number;\nexport type PercentageOfValidNotNodataPixel = number;\nexport type NumberOfBuckets = number;\nexport type MinimumValueOfTheBuckets = number;\nexport type MaximumValueOfTheBuckets = number;\nexport type DistributionBuckets = [\n  NumberOfPixelsInTheBucket,\n  NumberOfPixelsInTheBucket,\n  NumberOfPixelsInTheBucket,\n  ...NumberOfPixelsInTheBucket[]\n];\nexport type NumberOfPixelsInTheBucket = number;\n\nexport interface Assetfields {\n  'raster:bands'?: Bands;\n  /**\n   * This interface was referenced by `Assetfields`'s JSON-Schema definition\n   * via the `patternProperty` \"^(?!raster:)\".\n   */\n  [k: string]: {\n    [k: string]: unknown;\n  };\n}\nexport interface Band {\n  data_type?: DataTypeOfTheBand;\n  unit?: UnitDenominationOfThePixelValue;\n  bits_per_sample?: TheActualNumberOfBitsUsedForThisBand;\n  sampling?: PixelSamplingInTheBand;\n  nodata?: NoDataPixelValue;\n  scale?: MultiplicatorFactorOfThePixelValueToTransformIntoTheValue;\n  offset?: NumberToBeAddedToThePixelValueToTransformIntoTheValue;\n  spatial_resolution?: AverageSpatialResolutionInMetersOfThePixelsInTheBand;\n  statistics?: Statistics;\n  histogram?: Histogram;\n  [k: string]: unknown;\n}\nexport interface Statistics {\n  mean?: MeanValueOfAllThePixelsInTheBand;\n  minimum?: MinimumValueOfAllThePixelsInTheBand;\n  maximum?: MaximumValueOfAllThePixelsInTheBand;\n  stddev?: StandardDeviationValueOfAllThePixelsInTheBand;\n  valid_percent?: PercentageOfValidNotNodataPixel;\n}\nexport interface Histogram {\n  count: NumberOfBuckets;\n  min: MinimumValueOfTheBuckets;\n  max: MaximumValueOfTheBuckets;\n  buckets: DistributionBuckets;\n}\nexport interface StacExtensions {\n  stac_extensions: unknown[];\n  [k: string]: unknown;\n}\n\n/**\n * STAC Item or Collection\n */\nexport type STACObject = STACItem | STACCollection;\n\n/**\n * STAC Item including our required extensions: EO and Raster\n */\nexport type CompleteSTACItem = STACItem & EOExtension & RasterExtension;\n\n/**\n * STAC Collection including our required extensions: Item Assets, EO, and Raster\n */\nexport type CompleteSTACCollection = STACCollection &\n  ItemAssetsDefinitionExtension &\n  EOExtension &\n  RasterExtension;\n\nexport type CompleteSTACObject = CompleteSTACItem | CompleteSTACCollection;\n"
  },
  {
    "path": "src/types/types.d.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport type RGBColor = [number, number, number];\nexport type RGBAColor = [number, number, number, number];\nexport type HexColor = string; // this is the best typescript can do at the moment\nexport type Millisecond = number;\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type Merge<A, B> = {[K in keyof A]: K extends keyof B ? B[K] : A[K]} & B extends infer O\n  ? {[K in keyof O]: O[K]}\n  : never;\n\nexport type RequireFrom<T, K extends keyof T> = Merge<Required<Pick<T, K>>, Partial<Omit<T, K>>>;\n\nexport type Entries<T> = {\n  [K in keyof T]: [K, T[K]];\n}[keyof T][];\n\nexport type NestedPartial<T> = {\n  [P in keyof T]?: NestedPartial<T[P]>;\n};\n\nexport type RowData = {\n  [key: string]: string | null;\n}[];\n\nexport type ProcessorResult = {\n  info?: any;\n  fields: Field[];\n  rows: any[][];\n  cols?: any[];\n  metadata?: any;\n  /** Optional schema for arrow tables with metadata preserved. */\n  // TODO: Should we use a loaded arrow.Table in cols instead of an array of arrow.Vectors? Why was an array of arrow.Vectors chosen?\n  arrowSchema?: any;\n} | null;\n\nexport type Json = JsonScalar | JsonArray | JsonObject;\nexport type JsonObjectOrArray = JsonArray | JsonObject;\nexport type JsonScalar = string | number | boolean | null;\nexport type JsonArray = Json[];\nexport type JsonObject = {\n  [key: string]: Json | undefined;\n};\n\n/**\n * Types to mock apache-arrow types\n */\n\nexport interface ApacheVectorInterface {\n  length: number;\n}\n\nexport interface ArrowTableInterface {\n  get numRows(): number;\n\n  getChildAt: (index: number) => Vector;\n}\n"
  },
  {
    "path": "src/utils/babel.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('./package');\n\nconst PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'];\nconst PLUGINS = [\n  ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n  '@babel/plugin-transform-modules-commonjs',\n  '@babel/plugin-transform-class-properties',\n  '@babel/plugin-transform-optional-chaining',\n  '@babel/plugin-transform-logical-assignment-operators',\n  '@babel/plugin-transform-nullish-coalescing-operator',\n  '@babel/plugin-transform-export-namespace-from',\n  [\n    '@babel/transform-runtime',\n    {\n      regenerator: true\n    }\n  ],\n  [\n    'search-and-replace',\n    {\n      rules: [\n        {\n          search: '__PACKAGE_VERSION__',\n          replace: KeplerPackage.version\n        }\n      ]\n    }\n  ]\n];\nconst ENV = {\n  test: {\n    plugins: ['istanbul']\n  },\n  debug: {\n    sourceMaps: 'inline',\n    retainLines: true\n  }\n};\n\nmodule.exports = function babel(api) {\n  api.cache(true);\n\n  return {\n    presets: PRESETS,\n    plugins: PLUGINS,\n    env: ENV\n  };\n};\n"
  },
  {
    "path": "src/utils/map-utils.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport WebMercatorViewport from 'viewport-mercator-project';\n\nimport {TRANSITION_DURATION} from '@kepler.gl/constants';\nimport {\n  getMapLayersFromSplitMaps,\n  onViewPortChange,\n  getViewportFromMapState\n} from '@kepler.gl/utils';\n\nexport const INITIAL_MAP_STATE = {\n  pitch: 0,\n  bearing: 0,\n  latitude: 40,\n  longitude: -100,\n  zoom: 9,\n  dragRotate: false,\n  width: 800,\n  height: 800,\n  minZoom: undefined,\n  maxZoom: undefined,\n  maxBounds: undefined,\n  isSplit: false,\n  isViewportSynced: true,\n  isZoomLocked: false,\n  splitMapViewports: []\n};\n\ndescribe('mapUtils', () => {\n  describe('onViewPortChange', () => {\n    let mockOnUpdateMap;\n    let mockOnViewStateChange;\n\n    beforeEach(() => {\n      mockOnUpdateMap = jest.fn();\n      mockOnViewStateChange = jest.fn();\n    });\n\n    test('should use restViewState when width and height are 0', () => {\n      const viewState = {...INITIAL_MAP_STATE, width: 0, height: 0};\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange);\n\n      const {width: _width, height: _height, ...restViewState} = INITIAL_MAP_STATE;\n      expect(mockOnUpdateMap).toHaveBeenCalledWith({...restViewState, transitionDuration: 0}, 0);\n    });\n\n    test('should use viewState when width and height are greater than 0', () => {\n      const viewState = INITIAL_MAP_STATE;\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange);\n\n      expect(mockOnUpdateMap).toHaveBeenCalledWith({...viewState, transitionDuration: 0}, 0);\n    });\n\n    test('should set transitionDuration based on primary flag', () => {\n      const viewState = INITIAL_MAP_STATE;\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange, true);\n      expect(mockOnUpdateMap).toHaveBeenCalledWith(\n        {...viewState, transitionDuration: TRANSITION_DURATION},\n        0\n      );\n\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange, false);\n      expect(mockOnUpdateMap).toHaveBeenCalledWith({...viewState, transitionDuration: 0}, 0);\n    });\n\n    test('should call onViewStateChange if it is a function', () => {\n      const viewState = {width: 100, height: 100};\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange);\n      expect(mockOnViewStateChange).toHaveBeenCalledWith({...viewState, transitionDuration: 0});\n    });\n\n    test('should call onUpdateMap with correct arguments', () => {\n      const viewState = {width: 100, height: 100};\n      const mapIndex = 1;\n      onViewPortChange(viewState, mockOnUpdateMap, mockOnViewStateChange, false, mapIndex);\n      expect(mockOnUpdateMap).toHaveBeenCalledWith({...viewState, transitionDuration: 0}, mapIndex);\n    });\n  });\n  describe('getMapLayersFromSplitMaps', () => {\n    test('returns layers for valid index', () => {\n      const splitMaps = [{layers: ['Layer1', 'Layer2']}, {layers: ['Layer3', 'Layer4']}];\n      const mapIndex = 1;\n\n      expect(getMapLayersFromSplitMaps(splitMaps, mapIndex)).toEqual(['Layer3', 'Layer4']);\n    });\n\n    test('returns undefined for invalid index', () => {\n      const splitMaps = [{layers: ['Layer1', 'Layer2']}];\n      const mapIndex = 2;\n\n      expect(getMapLayersFromSplitMaps(splitMaps, mapIndex)).toBeUndefined();\n    });\n\n    test('returns undefined for empty splitMaps array', () => {\n      expect(getMapLayersFromSplitMaps([], 0)).toBeUndefined();\n    });\n\n    test('returns undefined if layers property does not exist', () => {\n      const splitMaps = [{}, {layers: ['Layer3', 'Layer4']}];\n      const mapIndex = 0;\n\n      expect(getMapLayersFromSplitMaps(splitMaps, mapIndex)).toBeUndefined();\n    });\n  });\n\n  describe('getViewportFromMapState', () => {\n    test('returns WebMercatorViewport when globe property is undefined', () => {\n      const mapState = {};\n      const result = getViewportFromMapState(mapState);\n\n      expect(result).toBeInstanceOf(WebMercatorViewport);\n    });\n\n    test('should not throw an exception if latitude is -90', () => {\n      const mapState = {\n        ...INITIAL_MAP_STATE,\n        latitude: -90\n      };\n      expect(() => getViewportFromMapState(mapState)).not.toThrow();\n    });\n\n    test('should not throw an exception if latitude is 90', () => {\n      const mapState = {\n        ...INITIAL_MAP_STATE,\n        latitude: 90\n      };\n      expect(() => getViewportFromMapState(mapState)).not.toThrow();\n    });\n  });\n});\n"
  },
  {
    "path": "src/utils/package.json",
    "content": "{\n  \"name\": \"@kepler.gl/utils\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"version\": \"3.2.6\",\n  \"description\": \"kepler.gl constants used by kepler.gl components, actions and reducers\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"keywords\": [\n    \"babel\",\n    \"es6\",\n    \"react\",\n    \"webgl\",\n    \"visualization\",\n    \"deck.gl\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/keplergl/kepler.gl.git\"\n  },\n  \"scripts\": {\n    \"build\": \"rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'\",\n    \"build:umd\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod\",\n    \"build:types\": \"tsc --project ./tsconfig.production.json\",\n    \"prepublishOnly\": \"babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types\",\n    \"stab\": \"mkdir -p dist && touch dist/index.js\"\n  },\n  \"files\": [\n    \"dist\",\n    \"umd\"\n  ],\n  \"dependencies\": {\n    \"@deck.gl/core\": \"^8.9.27\",\n    \"@kepler.gl/common-utils\": \"3.2.6\",\n    \"@kepler.gl/constants\": \"3.2.6\",\n    \"@kepler.gl/types\": \"3.2.6\",\n    \"@loaders.gl/arrow\": \"^4.3.2\",\n    \"@luma.gl/constants\": \"^8.5.20\",\n    \"@luma.gl/core\": \"^8.5.20\",\n    \"@mapbox/geo-viewport\": \"^0.4.1\",\n    \"@turf/boolean-within\": \"^6.0.1\",\n    \"@turf/helpers\": \"^6.1.4\",\n    \"@types/d3-array\": \"^2.8.0\",\n    \"@types/keymirror\": \"^0.1.1\",\n    \"@types/lodash\": \"4.17.5\",\n    \"apache-arrow\": \">=15.0.0\",\n    \"d3-array\": \"^2.8.0\",\n    \"d3-color\": \"^2.0.0\",\n    \"d3-format\": \"^2.0.0\",\n    \"d3-interpolate\": \"^2.0.1\",\n    \"decimal.js\": \"^10.2.0\",\n    \"global\": \"^4.3.0\",\n    \"h3-js\": \"^3.1.0\",\n    \"keymirror\": \"^0.1.1\",\n    \"lodash\": \"4.17.21\",\n    \"mapbox-gl\": \"^1.13.1\",\n    \"maplibre-gl\": \"^3.6.2\",\n    \"maplibregl-mapbox-request-transformer\": \"^0.0.2\",\n    \"moment\": \"^2.10.6\",\n    \"moment-timezone\": \"^0.5.35\",\n    \"react\": \"^18.2.0\",\n    \"react-map-gl\": \"^7.1.6\",\n    \"resize-observer-polyfill\": \"^1.5.1\",\n    \"suncalc\": \"^1.9.0\",\n    \"type-analyzer\": \"0.4.0\",\n    \"viewport-mercator-project\": \"^6.0.0\"\n  },\n  \"nyc\": {\n    \"sourceMap\": false,\n    \"instrument\": false\n  },\n  \"maintainers\": [\n    \"Shan He <heshan0131@gmail.com>\",\n    \"Giuseppe Macri <gmacri@uber.com>\"\n  ],\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "src/utils/src/aggregation.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {deviation, min, max, mean, median, sum, variance} from 'd3-array';\nimport {ValueOf} from '@kepler.gl/types';\nimport {AGGREGATION_TYPES, AggregationTypes} from '@kepler.gl/constants';\nconst identity = d => d;\n\nexport const getFrequency = (\n  data: any[],\n  accessor: (any: any) => any = identity\n): {[key: string | number]: number} => {\n  const occur = {};\n  for (let i = 0; i < data.length; i++) {\n    const val = accessor(data[i]);\n    occur[val] = (occur[val] || 0) + 1;\n  }\n  return occur;\n};\n\nexport const getMode = (data, accessor) => {\n  const occur = getFrequency(data, accessor);\n  return Object.keys(occur).reduce(\n    (prev, key) => (occur[prev] >= occur[key] ? prev : key),\n    Object.keys(occur)[0]\n  );\n};\n\nexport const countUnique = (data, accessor = identity) => {\n  return Object.keys(\n    data.reduce((uniques, d) => {\n      const val = accessor(d);\n      uniques[val] = uniques[val] || 0;\n      uniques[val] += 1;\n      return uniques;\n    }, {})\n  ).length;\n};\n\nexport const percentMean = (data, accessor) => {\n  const {getNumerator, getDenominator} = accessor;\n  const denominator = aggregate(data, AGGREGATION_TYPES.sum, getDenominator);\n  if (denominator <= 0) {\n    return 0;\n  }\n  const result = aggregate(data, AGGREGATION_TYPES.sum, getNumerator) / denominator;\n  return result;\n};\n\nexport function aggregate(\n  data: any[],\n  technique: ValueOf<AggregationTypes>,\n  accessor: (any: any) => any = identity\n): any {\n  switch (technique) {\n    case AGGREGATION_TYPES.average:\n      return mean(data, accessor);\n    case 'mean_of_percent':\n      return percentMean(data, accessor);\n    case AGGREGATION_TYPES.countUnique:\n      return countUnique(data, accessor);\n    case AGGREGATION_TYPES.mode:\n      return getMode(data, accessor);\n    case AGGREGATION_TYPES.maximum:\n      return max(data, accessor);\n    case AGGREGATION_TYPES.minimum:\n      return min(data, accessor);\n    case AGGREGATION_TYPES.median:\n      return median(data, accessor);\n    case AGGREGATION_TYPES.stdev:\n      return deviation(data, accessor);\n    case AGGREGATION_TYPES.sum:\n      return sum(data, accessor);\n    case AGGREGATION_TYPES.variance:\n      return variance(data, accessor);\n    default:\n      return data.length;\n  }\n}\n\nexport const AGGREGATION_NAME: {\n  [key: string]: string;\n} = {\n  [AGGREGATION_TYPES.average]: 'Average',\n  [AGGREGATION_TYPES.countUnique]: 'Number of Unique',\n  [AGGREGATION_TYPES.mode]: 'Most Often',\n  [AGGREGATION_TYPES.maximum]: 'Max',\n  [AGGREGATION_TYPES.minimum]: 'Min',\n  [AGGREGATION_TYPES.median]: 'Median',\n  [AGGREGATION_TYPES.stdev]: 'Std Deviation',\n  [AGGREGATION_TYPES.sum]: 'Total',\n  [AGGREGATION_TYPES.variance]: 'Variance'\n};\n\n// ratio: both denominator & numerator precent\n// average, min, max, median,\n"
  },
  {
    "path": "src/utils/src/application-config-types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\n\nexport interface DatabaseConnection {\n  query(statement: string): Promise<arrow.Table>;\n  insertArrowTable(arrowTable: arrow.Table, {name}: {name: string}): Promise<void>;\n  close();\n}\n\nexport interface DatabaseAdapter {\n  connect(): Promise<DatabaseConnection>;\n  registerFileText(name: string, text: string): Promise<void>;\n  registerFileHandle(name: string, handle: any, protocol: number, directIO: boolean): Promise<void>;\n}\n"
  },
  {
    "path": "src/utils/src/application-config.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {MapLib, MapRef} from 'react-map-gl';\n\nimport {KEPLER_UNFOLDED_BUCKET} from '@kepler.gl/constants';\nimport type {BaseMapLibraryType} from '@kepler.gl/constants';\n\nimport type {DatabaseAdapter} from './application-config-types';\n\n/**\n * Detect if running with webpack build tool\n */\nfunction isWebpackBuild(): boolean {\n  // @ts-ignore - __webpack_require__ is injected by webpack at runtime\n  return typeof __webpack_require__ !== 'undefined';\n}\n\nexport type MapLibInstance = MapLib<any>;\nexport type GetMapRef = ReturnType<MapRef['getMap']>;\n\nexport type BaseMapLibraryConfig = {\n  getMapLib: () => Promise<MapLibInstance>;\n  mapLibAttributionCssClass: string;\n  mapLibCssClass: string;\n  mapLibName: string;\n  mapLibUrl: string;\n};\n\n/**\n * A mechanism to override default Kepler values/settings so that we\n * without having to make application-specific changes to the kepler repo.\n */\nexport type KeplerApplicationConfig = {\n  /** Default name of export HTML file, can be overridden by user */\n  defaultHtmlName?: string;\n  defaultImageName?: string;\n  defaultJsonName?: string;\n  defaultDataName?: string;\n  defaultExportJsonSettings?: {\n    hasData?: boolean;\n  };\n  baseMapLibraryConfig?: Record<BaseMapLibraryType, BaseMapLibraryConfig>;\n  plugins?: any[];\n  // KeplerTable alternative\n  // TODO improve typing by exporting KeplerTable interface to @kepler.gl/types\n  table?: any;\n  database?: DatabaseAdapter | null;\n\n  // Disable progressive loading for arrow files\n  useArrowProgressiveLoading?: boolean;\n  // Show built-in banner associated with the current version of Kepler.gl\n  showReleaseBanner?: boolean;\n  // Use the onFilteredItemsChange callback for DataFilterExtension.\n  // Enabling this option may cause performance issues when dealing with a large number of layers or sublayers,\n  // especially if large Arrow files are split into relatively small batches (should be fixed in the future).\n  useOnFilteredItemsChange?: boolean;\n\n  // A URL to the CDN where the kepler.gl assets are hosted.\n  cdnUrl?: string;\n\n  // Raster Tile layer config\n  // Raster Tile layer is under development and not ready for production use. Disabled by default.\n  enableRasterTileLayer?: boolean;\n  /** Whether to use Titiler v0.21 API endpoints instead of v0.11 */\n  rasterServerUseLatestTitiler?: boolean;\n  /** An array of URLs to shards of the raster tile server to be used by the raster tile layer. */\n  rasterServerUrls?: string[];\n  /** If true then try to fetch quantized elevation meshes from raster servers */\n  rasterServerSupportsElevation?: boolean;\n  /** How many times to retry a request if case of rasterServerServerErrorsToRetry errors */\n  rasterServerMaxRetries?: number;\n  /** How long between retries */\n  rasterServerRetryDelay?: number;\n  /** An array of HTTP status codes that should be retried when encountered. */\n  rasterServerServerErrorsToRetry?: number[];\n  /** Maximum number of simultaneous requests per raster server. 0 - no limit */\n  rasterServerMaxPerServerRequests?: number;\n  /** Whether to show the server input field in the raster tile layer setup form */\n  rasterServerShowServerInput?: boolean;\n\n  // WMS layer config -- Experimental\n  // WMS layer is under development and not ready for production use. Disabled by default.\n  enableWMSLayer?: boolean;\n\n  // Image export config\n  /** Whether to apply fix for uglify error in dom-to-image (should be true for webpack builds, false for Vite) */\n  escapeXhtmlForWebpack?: boolean;\n};\n\nconst DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig> = {\n  defaultHtmlName: 'kepler.gl.html',\n  defaultImageName: 'kepler.gl.png',\n  defaultJsonName: 'kepler.gl.json',\n  defaultDataName: 'kepler.gl',\n  defaultExportJsonSettings: {\n    hasData: true\n  },\n\n  baseMapLibraryConfig: {\n    maplibre: {\n      getMapLib: () => import('maplibre-gl'),\n      mapLibCssClass: 'maplibregl',\n      mapLibAttributionCssClass: 'maplibre-attribution-container',\n      mapLibName: 'MapLibre',\n      mapLibUrl: 'https://www.maplibre.org/'\n    },\n    mapbox: {\n      getMapLib: () => import('mapbox-gl'),\n      mapLibCssClass: 'mapboxgl',\n      mapLibAttributionCssClass: 'mapbox-attribution-container',\n      mapLibName: 'Mapbox',\n      mapLibUrl: 'https://www.mapbox.com/'\n    }\n  },\n\n  cdnUrl: KEPLER_UNFOLDED_BUCKET,\n\n  plugins: [],\n  // The default table class is KeplerTable.\n  // TODO include KeplerTable here when the circular dependency with @kepler.gl/table and @kepler.gl/utils are resolved.\n  table: null,\n  database: null,\n\n  useArrowProgressiveLoading: true,\n  showReleaseBanner: true,\n  useOnFilteredItemsChange: false,\n\n  // Raster Tile layer config\n  enableRasterTileLayer: true,\n  rasterServerUseLatestTitiler: true,\n  rasterServerShowServerInput: true,\n  rasterServerUrls: [], // TODO: provide a default free server or leave blank\n  rasterServerSupportsElevation: true,\n  rasterServerMaxRetries: 1,\n  rasterServerRetryDelay: 10000,\n  rasterServerServerErrorsToRetry: [503],\n  rasterServerMaxPerServerRequests: 0,\n\n  // WMS layer config\n  enableWMSLayer: true,\n\n  // Image export config\n  // Default to true for webpack builds, false for other build tools (e.g., Vite)\n  escapeXhtmlForWebpack: isWebpackBuild()\n};\n\nconst applicationConfig: Required<KeplerApplicationConfig> = DEFAULT_APPLICATION_CONFIG;\n\nexport const getApplicationConfig = (): Required<KeplerApplicationConfig> => applicationConfig;\n\nexport function initApplicationConfig(appConfig: KeplerApplicationConfig = {}) {\n  Object.assign(applicationConfig, appConfig);\n}\n"
  },
  {
    "path": "src/utils/src/arrow-data-container.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport * as arrow from 'apache-arrow';\nimport {console as globalConsole} from 'global/window';\nimport {DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer';\n\nimport {DataContainerInterface, RangeOptions} from './data-container-interface';\nimport {DataRow, SharedRowOptions} from './data-row';\n\ntype ArrowDataContainerInput = {\n  cols: arrow.Vector[];\n  fields?: ProtoDatasetField[];\n  arrowTable?: arrow.Table;\n};\n\n/**\n * Check if table is an ArrowTable object.\n *\n * We use duck-typing instead of `instanceof arrow.Table` because DuckDB loads its own\n * bundled version of Apache Arrow. When DuckDB creates Arrow tables, they are instances\n * of DuckDB's Arrow.Table class, not the Arrow.Table class from our application's\n * apache-arrow package. This causes `instanceof` checks to fail even though the objects\n * are functionally equivalent Arrow tables.\n *\n * @param data - object to check\n * @returns true if data is an ArrowTable object (type guarded)\n */\nexport function isArrowTable(data: any): data is arrow.Table {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    'schema' in data &&\n    'getChildAt' in data &&\n    typeof data.getChildAt === 'function' &&\n    'batches' in data &&\n    Array.isArray(data.batches)\n  );\n}\n\n/**\n * Check if data is an ArrowVector object.\n * Uses duck-typing instead of `instanceof` to handle DuckDB's bundled Arrow version.\n *\n * @param data - object to check\n * @returns true if data is an ArrowVector object (type guarded)\n */\nexport function isArrowVector(data: any): data is arrow.Vector {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    'type' in data &&\n    'length' in data &&\n    typeof data.length === 'number' &&\n    'get' in data &&\n    typeof data.get === 'function' &&\n    'data' in data &&\n    Array.isArray(data.data)\n  );\n}\n\n/**\n * Check if data is an Arrow FixedSizeList DataType.\n * Uses duck-typing instead of `instanceof` to handle DuckDB's bundled Arrow version.\n *\n * @param data - object to check\n * @returns true if data is an Arrow FixedSizeList DataType (type guarded)\n */\nexport function isArrowFixedSizeList(data: any): data is arrow.FixedSizeList {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    'typeId' in data &&\n    'listSize' in data &&\n    typeof data.listSize === 'number' &&\n    'children' in data &&\n    Array.isArray(data.children)\n  );\n}\n\n/**\n * Check if data is an Arrow Struct DataType.\n * Uses duck-typing instead of `instanceof` to handle DuckDB's bundled Arrow version.\n *\n * @param data - object to check\n * @returns true if data is an Arrow Struct DataType (type guarded)\n */\nexport function isArrowStruct(data: any): data is arrow.Struct {\n  return (\n    typeof data === 'object' &&\n    data !== null &&\n    'typeId' in data &&\n    'children' in data &&\n    Array.isArray(data.children) &&\n    !('listSize' in data)\n  );\n}\n\n/**\n * @param dataContainer\n * @param sharedRow\n */\nfunction* rowsIterator(dataContainer: DataContainerInterface, sharedRow: SharedRowOptions) {\n  const numRows = dataContainer.numRows();\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    yield dataContainer.row(rowIndex, sharedRow);\n  }\n}\n\n/**\n * @param dataContainer\n * @param columnIndex\n */\nfunction* columnIterator(dataContainer: DataContainerInterface, columnIndex: number) {\n  const numRows = dataContainer.numRows();\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    yield dataContainer.valueAt(rowIndex, columnIndex);\n  }\n}\n\n/**\n * A data container where all data is stored in raw Arrow table\n */\nexport class ArrowDataContainer implements DataContainerInterface {\n  _cols: arrow.Vector[];\n  _numColumns: number;\n  _numRows: number;\n  _fields: ProtoDatasetField[];\n  _numChunks: number;\n  // cache column data to make valueAt() faster\n  // _colData: any[][];\n\n  /** An arrow table recreated from vectors */\n  _arrowTable: arrow.Table;\n\n  constructor(data: ArrowDataContainerInput) {\n    if (!data.cols) {\n      throw Error('ArrowDataContainer: no columns provided');\n    }\n\n    if (!Array.isArray(data.cols)) {\n      throw Error(\"ArrowDataContainer: columns object isn't an array\");\n    }\n\n    this._cols = data.cols;\n    this._numColumns = data.cols.length;\n    this._numRows = data.cols[0].length;\n    this._fields = data.fields || [];\n    this._numChunks = data.cols[0].data.length;\n    // this._colData = data.cols.map(c => c.toArray());\n\n    this._arrowTable = data.arrowTable || this._createTable();\n  }\n\n  /**\n   * Restores internal Arrow table from vectors.\n   * TODO: consider using original arrow table, as it could contain extra metadata, not passed to the fields.\n   */\n  private _createTable() {\n    const creaOpts = {};\n    this._fields.map((field, index) => {\n      creaOpts[field.name] = this._cols[index];\n    });\n    return new arrow.Table(creaOpts);\n  }\n\n  getTable() {\n    return this._arrowTable;\n  }\n\n  update(updateData: arrow.Vector<any>[] | arrow.Table) {\n    const isArrow = isArrowTable(updateData);\n    if (isArrow) {\n      this._cols = Array.from(\n        {length: updateData.numCols},\n        (_, i) => updateData.getChildAt(i) as arrow.Vector\n      ).filter(col => col);\n    } else {\n      this._cols = updateData;\n    }\n    this._numColumns = this._cols?.length ?? 0;\n    this._numRows = this._cols?.[0]?.length ?? 0;\n    this._numChunks = this._cols?.[0]?.data?.length ?? 0;\n    this._arrowTable = isArrow ? updateData : this._createTable();\n\n    // cache column data to make valueAt() faster\n    // this._colData = this._cols.map(c => c.toArray());\n  }\n\n  numChunks(): number {\n    return this._numChunks;\n  }\n\n  numRows(): number {\n    return this._numRows;\n  }\n\n  numColumns(): number {\n    return this._numColumns;\n  }\n\n  valueAt(rowIndex: number, columnIndex: number): any {\n    // return this._colData[columnIndex][rowIndex];\n    return this._cols[columnIndex].get(rowIndex);\n  }\n\n  row(rowIndex: number, sharedRow?: SharedRowOptions): DataRow {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n    if (tSharedRow) {\n      tSharedRow.setSource(this, rowIndex);\n      return tSharedRow;\n    }\n\n    return new DataRow(this, rowIndex);\n  }\n\n  rowAsArray(rowIndex: number): any[] {\n    // return this._colData.map(col => col[rowIndex]);\n    return this._cols.map(col => col.get(rowIndex));\n  }\n\n  rows(sharedRow: SharedRowOptions) {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n    return rowsIterator(this, tSharedRow);\n  }\n\n  column(columnIndex: number) {\n    return columnIterator(this, columnIndex);\n  }\n\n  getColumn(columnIndex: number): arrow.Vector {\n    return this._cols[columnIndex];\n  }\n\n  getField(columnIndex: number): ProtoDatasetField {\n    return this._fields[columnIndex];\n  }\n\n  flattenData(): any[][] {\n    const data: any[][] = [];\n    for (let i = 0; i < this._numRows; ++i) {\n      data.push(this.rowAsArray(i));\n    }\n    return data;\n  }\n\n  getPlainIndex(): number[] {\n    return [...Array(this._numRows).keys()];\n  }\n\n  map<T>(\n    func: (row: DataRow, index: number) => T,\n    sharedRow?: SharedRowOptions,\n    options: RangeOptions = {}\n  ): T[] {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      out.push(func(row, rowIndex));\n    }\n    return out;\n  }\n\n  mapIndex<T>(func: ({index}, dc: DataContainerInterface) => T, options: RangeOptions = {}): T[] {\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      out.push(func({index: rowIndex}, this));\n    }\n    return out;\n  }\n\n  find(\n    func: (row: DataRow, index: number) => boolean,\n    sharedRow?: SharedRowOptions\n  ): DataRow | undefined {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this._numRows; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      if (func(row, rowIndex)) {\n        return row;\n      }\n    }\n    return undefined;\n  }\n\n  reduce<T>(\n    func: (acc: T, row: DataRow, index: number) => T,\n    initialValue: T,\n    sharedRow?: SharedRowOptions\n  ): T {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this._numRows; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      initialValue = func(initialValue, row, rowIndex);\n    }\n    return initialValue;\n  }\n}\n\n/**\n * Convert arrow data type to kepler.gl field types\n *\n * @param arrowType the arrow data type\n * @returns corresponding type in `ALL_FIELD_TYPES`\n */\nexport function arrowDataTypeToFieldType(arrowType: arrow.DataType): string {\n  // Note: this function doesn't return ALL_FIELD_TYPES.geojson or ALL_FIELD_TYPES.array, which\n  // should be further detected by caller\n  if (arrow.DataType.isDate(arrowType)) {\n    return ALL_FIELD_TYPES.date;\n  } else if (arrow.DataType.isTimestamp(arrowType) || arrow.DataType.isTime(arrowType)) {\n    return ALL_FIELD_TYPES.timestamp;\n  } else if (arrow.DataType.isFloat(arrowType)) {\n    return ALL_FIELD_TYPES.real;\n  } else if (arrow.DataType.isInt(arrowType)) {\n    return ALL_FIELD_TYPES.integer;\n  } else if (arrow.DataType.isBool(arrowType)) {\n    return ALL_FIELD_TYPES.boolean;\n  } else if (arrow.DataType.isUtf8(arrowType) || arrow.DataType.isNull(arrowType)) {\n    return ALL_FIELD_TYPES.string;\n  } else if (\n    arrow.DataType.isBinary(arrowType) ||\n    arrow.DataType.isDictionary(arrowType) ||\n    arrow.DataType.isFixedSizeBinary(arrowType) ||\n    arrow.DataType.isFixedSizeList(arrowType) ||\n    arrow.DataType.isList(arrowType) ||\n    arrow.DataType.isMap(arrowType) ||\n    arrow.DataType.isStruct(arrowType)\n  ) {\n    return ALL_FIELD_TYPES.object;\n  }\n  globalConsole.warn(`Unsupported arrow type: ${arrowType}`);\n  return ALL_FIELD_TYPES.string;\n}\n\n/**\n * Convert arrow data type to analyzer type\n *\n * @param arrowType the arrow data type\n * @returns corresponding type in `AnalyzerDATA_TYPES`\n */\nexport function arrowDataTypeToAnalyzerDataType(\n  arrowType: arrow.DataType\n): typeof AnalyzerDATA_TYPES {\n  if (arrow.DataType.isDate(arrowType)) {\n    return AnalyzerDATA_TYPES.DATE;\n  } else if (arrow.DataType.isTimestamp(arrowType) || arrow.DataType.isTime(arrowType)) {\n    return AnalyzerDATA_TYPES.DATETIME;\n  } else if (arrow.DataType.isFloat(arrowType)) {\n    return AnalyzerDATA_TYPES.FLOAT;\n  } else if (arrow.DataType.isInt(arrowType)) {\n    return AnalyzerDATA_TYPES.INT;\n  } else if (arrow.DataType.isBool(arrowType)) {\n    return AnalyzerDATA_TYPES.BOOLEAN;\n  } else if (arrow.DataType.isUtf8(arrowType) || arrow.DataType.isNull(arrowType)) {\n    return AnalyzerDATA_TYPES.STRING;\n  } else if (\n    arrow.DataType.isBinary(arrowType) ||\n    arrow.DataType.isDictionary(arrowType) ||\n    arrow.DataType.isFixedSizeBinary(arrowType) ||\n    arrow.DataType.isFixedSizeList(arrowType) ||\n    arrow.DataType.isList(arrowType) ||\n    arrow.DataType.isMap(arrowType) ||\n    arrow.DataType.isStruct(arrowType)\n  ) {\n    return AnalyzerDATA_TYPES.OBJECT;\n  }\n  globalConsole.warn(`Unsupported arrow type: ${arrowType}`);\n  return AnalyzerDATA_TYPES.STRING;\n}\n"
  },
  {
    "path": "src/utils/src/browser-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Checks if the current device is an Apple device (Mac, iPhone, iPod, or iPad)\n * @returns {boolean} True if the current device is an Apple device\n */\nexport function isAppleDevice(): boolean {\n  return /Mac|iPhone|iPod|iPad/.test(navigator.userAgent);\n}\n"
  },
  {
    "path": "src/utils/src/color-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  CategoricalPalette,\n  ColorPalette,\n  DEFAULT_CUSTOM_PALETTE,\n  colorPaletteToColorRange\n} from '@kepler.gl/constants';\nimport {arrayMove} from '@kepler.gl/common-utils';\nimport {\n  ColorMap,\n  ColorRange,\n  ColorRangeConfig,\n  HexColor,\n  RGBAColor,\n  RGBColor\n} from '@kepler.gl/types';\nimport {rgb as d3Rgb} from 'd3-color';\nimport {interpolate} from 'd3-interpolate';\nimport {arrayInsert} from './utils';\nimport Console from 'global/console';\nimport {KEPLER_COLOR_PALETTES, PALETTE_TYPES, SCALE_TYPES} from '@kepler.gl/constants';\n\n/**\n * get r g b from hex code\n *\n * @param hex\n * @returns array of r g bs\n */\nexport function hexToRgb(hex: string): RGBColor {\n  const result = isHexColor(hex);\n\n  if (!result) {\n    return [0, 0, 0];\n  }\n\n  const r = parseInt(result[1], 16);\n  const g = parseInt(result[2], 16);\n  const b = parseInt(result[3], 16);\n\n  return [r, g, b];\n}\n\nexport function isHexColor(hex: string): RegExpExecArray | null {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\n  return result;\n}\n\nfunction PadNum(c) {\n  const hex = c.toString(16);\n  return hex.length === 1 ? `0${hex}` : hex;\n}\n\n/**\n * get hex from r g b\n *\n * @param rgb\n * @returns hex string\n */\nexport function rgbToHex([r, g, b]: RGBColor | RGBAColor): HexColor {\n  return `#${[r, g, b].map(n => PadNum(n)).join('')}`.toUpperCase();\n}\n\n/**\n * Whether color range has custom color map\n */\nexport function hasColorMap(colorRange: ColorRange): boolean {\n  return Array.isArray(colorRange.colorMap) && Boolean(colorRange.colorMap.length);\n}\n\n/**\n * given a list of rgb arrays it will generate a linear gradient css rule\n * @param direction\n * @param colors\n * @return\n */\nexport function createLinearGradient(direction: string, colors: RGBColor[]): string {\n  const step = parseFloat((100.0 / colors.length).toFixed(2));\n  const bands = colors.map((rgb, index) => {\n    return `rgba(${rgb.join(',')}, 1) ${step * index}%, rgba(${rgb.join(',')}, 1) ${\n      step * (index + 1)\n    }%`;\n  });\n\n  return `linear-gradient(to ${direction}, ${bands.join(',')})`;\n}\n\n/**\n * Convert color to RGB\n */\nexport function colorMaybeToRGB(color: unknown): RGBColor | null {\n  if (isRgbColor(color)) {\n    return color as RGBColor;\n  }\n\n  if (typeof color === 'string') {\n    const rgbObj = d3Rgb(color);\n    if (Number.isFinite(rgbObj?.r) && Number.isFinite(rgbObj?.g) && Number.isFinite(rgbObj?.b)) {\n      return [rgbObj.r, rgbObj.g, rgbObj.b];\n    }\n  }\n\n  return null;\n}\n\n/**\n * Whether color is rgb\n * @returns\n */\nexport function isRgbColor(color: unknown): boolean {\n  return Boolean(\n    color &&\n      Array.isArray(color) &&\n      color.length === 3 &&\n      color.every(n => Number.isFinite(n) && n <= 255 && n >= 0)\n  );\n}\n\n/**\n * Take color values in 0-255 range and map to [0, 1]\n */\nexport function normalizeColor(color: number[]): number[] {\n  return color.map(component => component / 255.0);\n}\n\n/**\n * Convert color to Hex\n */\nexport function colorMaybeToHex(color: unknown): HexColor {\n  const rgbColor = colorMaybeToRGB(color);\n  if (rgbColor) return rgbToHex(rgbColor);\n  return '#000000';\n}\n\n/**\n * Convert color to Hex\n */\n\nexport function interpolateHex(hex1: HexColor, hex2: HexColor): HexColor {\n  return d3Rgb(interpolate(hex1, hex2)(0.5)).hex().toUpperCase();\n}\n\nfunction addNewCategoricalStepAtIndex(colorMap, index, newColor) {\n  if (!Array.isArray(colorMap) || !colorMap.length) {\n    return colorMap;\n  }\n\n  let newColorMap = colorMap.map(([val, c]) => [Array.isArray(val) ? [...val] : val, c]);\n  newColorMap = arrayInsert(newColorMap, index + 1, [null, newColor]);\n\n  return newColorMap;\n}\n\nexport function addNewQuantativeColorBreakAtIndex(colorMap, index, newColors) {\n  if (!Array.isArray(colorMap) || !colorMap.length) {\n    return colorMap;\n  }\n\n  if (colorMap.length < 2) {\n    // less then 2, add 1 at end\n    // however shouldn't allow delete when there are 2\n    return newColors.map((c, i) => (i === 0 ? colorMap[i] : [null, c]));\n  }\n\n  // breaks should be 1 less than colors\n  const breaks = colorMap.map(cm => cm[0]).slice(0, colorMap.length - 1);\n\n  // insert new break\n  const newValue =\n    index >= breaks.length - 1\n      ? breaks[breaks.length - 1] +\n        (breaks.length > 1 ? breaks[breaks.length - 1] - breaks[breaks.length - 2] : 0)\n      : (breaks[index] + breaks[index + 1]) / 2;\n\n  const newBreaks = arrayInsert(breaks, index + 1, newValue);\n\n  // asign breaks to color\n  return newColors.map((c, i) =>\n    i === newColors.length - 1 ? [null, c] : [newBreaks[i] === undefined ? null : newBreaks[i], c]\n  );\n}\n\n/**\n * Add a new color to custom palette\n */\nexport function addCustomPaletteColor(customPalette: ColorRange, index: number): ColorRange {\n  const {colors, colorMap} = customPalette;\n  const update: Partial<ColorRange> = {};\n\n  const newColor =\n    index === colors.length - 1 ? colors[index] : interpolateHex(colors[index], colors[index + 1]);\n\n  update.colors = arrayInsert(colors, index + 1, newColor);\n\n  // add color to colorMap\n  if (colorMap) {\n    update.colorMap =\n      customPalette.type === 'customOrdinal'\n        ? addNewCategoricalStepAtIndex(colorMap, index, newColor)\n        : addNewQuantativeColorBreakAtIndex(colorMap, index, update.colors);\n  }\n\n  return {\n    ...customPalette,\n    ...update\n  };\n}\n\nfunction replaceColorsInColorRange(colorRange, newColors) {\n  const oldColors = colorRange.colors;\n  const updated = {\n    ...colorRange,\n    colors: newColors\n  };\n\n  // update color map\n  // keep value, replace color\n  if (Array.isArray(updated.colorMap)) {\n    updated.colorMap = updated.colorMap.map((cm, i) => [cm[0], newColors[i]]);\n  }\n  // update colorlegends\n  // keep value, replace color\n  if (updated.colorLegends) {\n    updated.colorLegends = Object.keys(updated.colorLegends).reduce((accu, key) => {\n      const colorIdx = oldColors.findIndex(c => c === key);\n      const newColor = newColors[colorIdx];\n\n      return newColor\n        ? {\n            ...accu,\n            [newColor]: updated.colorLegends[key]\n          }\n        : accu;\n    }, {});\n  }\n\n  return updated;\n}\n\n/**\n * Sort custom palette\n */\nexport function sortCustomPaletteColor(\n  customPalette: ColorRange,\n  oldIndex: number,\n  newIndex: number\n): ColorRange {\n  const {colors} = customPalette;\n\n  const newColors = arrayMove(colors, oldIndex, newIndex);\n  const update = replaceColorsInColorRange(customPalette, newColors);\n\n  // @ts-ignore\n  return {\n    ...customPalette,\n    ...update\n  };\n}\n\n/**\n * remove a color in custom palette at index\n */\nexport function removeCustomPaletteColor(customPalette: ColorRange, index: number): ColorRange {\n  const {colors, colorMap, colorLegends} = customPalette;\n  const oldValue = colors[index];\n  const update: Partial<ColorRange> = {};\n  update.colors = [...colors];\n\n  if (update.colors.length > 1) {\n    update.colors.splice(index, 1);\n  }\n  // update color map\n  if (Array.isArray(colorMap)) {\n    // find colorMap index\n    const colorMapIndex = colorMap.findIndex(cm => cm[1] === oldValue);\n    if (colorMapIndex >= 0) {\n      update.colorMap = [...colorMap];\n      update.colorMap.splice(colorMapIndex, 1);\n    }\n  }\n  // update color legend\n  if (colorLegends?.[oldValue]) {\n    update.colorLegends = {...colorLegends};\n    delete update.colorLegends[oldValue];\n  }\n\n  return {\n    ...customPalette,\n    ...update\n  };\n}\n\n/**\n * Update a color in custom palette at index\n */\nexport function updateCustomPaletteColor(\n  customPalette: ColorRange,\n  index: number,\n  newValue: HexColor\n): ColorRange {\n  const {colors} = customPalette;\n  const hex = newValue.toUpperCase();\n  const newColors = [...colors];\n  newColors[index] = hex;\n\n  const update = replaceColorsInColorRange(customPalette, newColors);\n\n  // @ts-ignore\n  return {\n    ...customPalette,\n    ...update\n  };\n}\n\n/**\n * Get a reversed colorRange\n */\nexport function reverseColorRange(reversed: boolean, colorRange: ColorRange): ColorRange {\n  const newColors = colorRange?.colors.slice().reverse();\n  const updated = replaceColorsInColorRange(colorRange, newColors);\n  updated.reversed = reversed;\n\n  return updated;\n}\n\n/**\n * Whether palette matches current ColorBlindSafe config\n */\nexport function paletteIsColorBlindSafe(palette: ColorPalette, colorBlindSafe: boolean) {\n  return !colorBlindSafe || (colorBlindSafe && palette.colorBlindSafe);\n}\n\n/**\n * Whether palette matches current steps config\n */\nexport function isQuaPalette(palette: ColorPalette): palette is CategoricalPalette {\n  return palette.type === PALETTE_TYPES.QUA;\n}\n\n/**\n * Whether palette matches current steps config\n */\nexport function paletteIsSteps(palette: ColorPalette, steps: number): boolean {\n  return !isQuaPalette(palette) || palette.maxStep >= steps;\n}\n\n/**\n * Whether palette matches current type config\n */\nexport function paletteIsType(palette: ColorPalette, type: string): boolean {\n  return type === 'all' || type === palette.type;\n}\n/**\n * Find best match palette based on config, update color range by it\n */\nexport function updateColorRangeByMatchingPalette(\n  currentColorRange: ColorRange,\n  config: ColorRangeConfig\n): ColorRange {\n  const {steps, colorBlindSafe, type} = config;\n\n  const matchingPalette = KEPLER_COLOR_PALETTES.filter(\n    palette =>\n      // palette match type\n      paletteIsType(palette, type) &&\n      // palette has same step\n      paletteIsSteps(palette, steps) &&\n      // palette is colorBlindSafe\n      paletteIsColorBlindSafe(palette, colorBlindSafe)\n  );\n\n  const bestMatch = matchingPalette.length\n    ? matchingPalette.find(p => p.name === currentColorRange.name) || matchingPalette[0]\n    : null;\n\n  if (bestMatch) {\n    return updateColorRangeBySelectedPalette(currentColorRange, bestMatch, config);\n  }\n  // we do nothing\n  Console.warn(\n    `we cant find any preset palette matches requirments: steps=${steps} && colorBlindSafe=${colorBlindSafe}`\n  );\n\n  return currentColorRange;\n}\n\n/**\n * Update custom palette when reverse the colors in custom palette, since changing 'steps',\n * 'colorBindSafe', 'type' should fall back to predefined palette.\n */\nexport function updateCustomColorRangeByColorUI(\n  oldColorRange: ColorRange,\n  colorConfig: ColorRangeConfig\n): ColorRange {\n  const {reversed} = colorConfig;\n  const colors = oldColorRange.colors;\n  // for custom palette, one can only 'reverse' the colors in custom palette.\n  colors.reverse();\n\n  const colorRange = {\n    name: oldColorRange.name,\n    type: oldColorRange.type,\n    category: oldColorRange.category,\n    colors,\n    ...(reversed ? {reversed} : {}),\n    ...(oldColorRange.colorMap ? {colorMap: oldColorRange.colorMap} : {}),\n    ...(oldColorRange.colorLegends ? {colorLegends: oldColorRange.colorLegends} : {})\n  };\n\n  return replaceColorsInColorRange(colorRange, colorRange.colors);\n}\n\n/**\n * Update color range after selecting a palette from color range selectoer\n * Copy over colorMap and colorLegends\n */\nexport function updateColorRangeBySelectedPalette(oldColorRange, colorPalette, colorConfig) {\n  const {colors: newColors, ...newColorRange} = colorPaletteToColorRange(colorPalette, colorConfig);\n\n  const colorRange = {\n    colors: oldColorRange.colors,\n    ...newColorRange,\n    ...(oldColorRange.colorMap ? {colorMap: oldColorRange.colorMap} : {}),\n    ...(oldColorRange.colorLegends ? {colorLegends: oldColorRange.colorLegends} : {})\n  };\n\n  return replaceColorsInColorRange(colorRange, newColors);\n}\n\nconst UberNameRegex = new RegExp(/^([A-Za-z ])+/g);\nconst ColorBrewerRegex = new RegExp(/^ColorBrewer ([A-Za-z1-9])+/g);\n\n/**\n * convert saved colorRange to colorPalette objevt type/name/category/isColorBlind\n */\nexport function colorRangeBackwardCompatibility(colorRange: ColorRange): ColorRange {\n  if (!colorRange || colorRange.type === 'custom' || colorRange.colorMap) {\n    // don't do anything to custom color palette, or palette with custom breaks\n    return colorRange;\n  }\n  let trimName;\n  if (colorRange.category === 'Uber') {\n    const matchName = (colorRange.name ?? '').match(UberNameRegex);\n    trimName = matchName ? matchName[0].trim() : null;\n    // match Uber Viz Qualitative 1.4 -> Uber Viz Qualitative\n  } else if (colorRange.category === 'ColorBrewer') {\n    const matchName = (colorRange.name ?? '').match(ColorBrewerRegex);\n    trimName = matchName ? matchName[0].replace('ColorBrewer ', '').trim() : null;\n  }\n\n  if (trimName) {\n    const matchingPalette = KEPLER_COLOR_PALETTES.find(p => p.name === trimName);\n    if (matchingPalette) {\n      return {\n        ...colorRange,\n        name: trimName,\n        type: matchingPalette?.type,\n        category: matchingPalette.category\n      };\n    }\n  }\n\n  return colorRange;\n}\n\n/**\n * Initialize custom palette from current standard color range object\n */\nexport function initializeCustomPalette(colorRange: ColorRange, colorMap?: ColorMap): ColorRange {\n  // TODO: check on `isReversed` key, whether we can remove it here\n  const customPalette = {\n    ...colorRange,\n    name: DEFAULT_CUSTOM_PALETTE.name,\n    type: DEFAULT_CUSTOM_PALETTE.type,\n    category: DEFAULT_CUSTOM_PALETTE.category,\n    ...(colorMap ? {colorMap} : {})\n  };\n\n  // only customPalette.colors are needed for custom palette editor with custom ordinal scale\n  if (!colorMap && colorRange.type === SCALE_TYPES.customOrdinal) {\n    delete customPalette.colorMap;\n  }\n  return customPalette;\n}\n"
  },
  {
    "path": "src/utils/src/data-container-interface.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport {DataRow, SharedRowOptions} from './data-row';\nimport * as arrow from 'apache-arrow';\n\n/**\n * Specifies a range of rows of a data container that should be processed.\n */\nexport type RangeOptions = {\n  start?: number;\n  end?: number;\n};\nexport interface DataContainerInterface {\n  /**\n   * Updates the data container with new data.\n   * @param updateData updated data, e.g. for arrow data container, it's an array of arrow columns or an arrow.Table; for row data container, it's an array of rows.\n   */\n  update?(updateData: any[] | arrow.Table): void;\n\n  /**\n   * Returns the number of rows in the data container.\n   * @returns Number of rows in the data container.\n   */\n  numRows(): number;\n\n  /**\n   * Returns the number of columns in the data container.\n   * @returns Number of columns in the data container.\n   */\n  numColumns(): number;\n\n  /**\n   * Returns the value stored at the specified cell in the data container.\n   * @param rowIndex Row index.\n   * @param columnIndex Column index.\n   * @returns Value at the specified cell.\n   */\n  valueAt(rowIndex: number, columnIndex: number): any;\n\n  /**\n   * Returns the row at the specified index.\n   * @param rowIndex Row index.\n   * @param sharedRow Passed row is filled with contents from the specified index and returned without extra row allocations.\n   * @returns A row object.\n   */\n  row(rowIndex: number, sharedRow?: SharedRowOptions): DataRow;\n\n  /**\n   * Returns the specified row of the data container represented as an array.\n   * @param rowIndex Row index.\n   * @returns The specified row represented as an array.\n   */\n  rowAsArray(rowIndex: number): any[];\n\n  /**\n   * Returns an iterator to sequentially access all rows in the data container.\n   * @param sharedRow Indicates that a temporary row object should be used.\n   * If enabled, the returned row object is shared between calls\n   * and mustn't be stored without cloning.\n   * Passed row is filled with contents of the specified row and returned on each iteration.\n   * @returns An iterator to sequentially access all rows in the data container.\n   */\n  rows(sharedRow?: SharedRowOptions): Generator<DataRow, void, any>;\n\n  /**\n   * Returns an iterator to sequentially access all values in the specified column of the data container.\n   * @param columnIndex Column index.\n   * @returns An iterator to all values in the specified column of the data container.\n   */\n  column(columnIndex: number): Generator<any, void, any>;\n\n  /**\n   * Returns the column object at the specified index.\n   * @param columnIndex Column index.\n   * @returns The column object at the specified index.\n   */\n  getColumn?(columnIndex: number): unknown;\n\n  /**\n   * Returns the field object at the specified index.\n   * @param columnIndex Column index.\n   * @returns The field object at the specified index.\n   */\n  getField?(columnIndex: number): ProtoDatasetField;\n\n  /**\n   * Returns the underlying data object.\n   * @returns Returns the underlying data object.\n   */\n  getTable?(): unknown;\n\n  /**\n   * Returns contents of the data container as a two-dimensional array.\n   * @returns Data.\n   */\n  flattenData(): any[][];\n\n  /**\n   * Generates an array of indices where each index represents a row in the data container.\n   * @returns An array of indices.\n   */\n  getPlainIndex(): number[];\n\n  /**\n   * Creates a new array populated with the results of calling the provided callback\n   * on every row in the data container.\n   * @param func Callback that is called for every row of the data container.\n   * The callback is called with the following arguments:\n   * - row: The current row being processed in the data container.\n   * - index: The index of the current row being processed in the data container.\n   * @param sharedRow Truthy value indicates that a shared row object should be used.\n   * Make sure the func callback doesn't store the row object in such case,\n   * and clone the row in case you need to store it.\n   * @param options Specify start and end row indices that should be mapped.\n   * @returns A new array with each element being the result of the func callback.\n   */\n  map(\n    func: (row: DataRow, index: number) => any,\n    sharedRow?: SharedRowOptions,\n    options?: RangeOptions\n  ): any[];\n\n  /**\n   * Creates a new array populated with the results of calling the provided callback.\n   * The callback is called for every row in the dataset, but only {index} object is passed.\n   * @param func Callback that is called for every row of the data container.\n   * @param options Specify start and end row indices that should be mapped.\n   * @returns A new array with each element being the result of the func callback.\n   */\n  mapIndex(\n    func: ({index}: {index: number}, dataContainer: DataContainerInterface) => any,\n    options?: RangeOptions\n  ): any[];\n\n  /**\n   * Returns the value of the first row in the provided data container that satisfies the provided testing function.\n   * @param func Function to execute on each value in the array.\n   * The function is called with the following arguments:\n   * - row: The current row being processed in the data container.\n   * - index: The index of the current row being processed in the data container.\n   * @param sharedRow Truthy value indicates that a shared row object should be used.\n   * Make sure the func callback doesn't store the row object in such case,\n   * and clone the row in case you need to store it.\n   * @returns First matching row or undefined if no rows satisfy the testing function.\n   */\n  find(\n    func: (row: DataRow, index: number) => any,\n    sharedRow?: SharedRowOptions\n  ): DataRow | undefined;\n\n  /**\n   * Executes a reducer function on each element of the data container, resulting in single output value.\n   * @param func A function to execute on each row in the data container.\n   * The function is called with the following arguments:\n   * - acc: The accumulator accumulates func's return values.\n   * - row: The current row being processed in the data container.\n   * - index: The index of the current row being processed in the data container.\n   * @param initialValue A value to use as the first argument to the first call of the func.\n   * @param sharedRow Truthy value indicates that a shared row object should be used.\n   * Make sure the func callback doesn't store the row object in such case,\n   * and clone the row in case you need to store it.\n   * @returns The single value that results from the reduction.\n   */\n  reduce(\n    func: (acc: any, row: DataRow, index: number) => any,\n    initialValue: any,\n    sharedRow?: SharedRowOptions\n  ): any;\n}\n"
  },
  {
    "path": "src/utils/src/data-container-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ArrowDataContainer} from './arrow-data-container';\nimport {RowDataContainer} from './row-data-container';\nimport {IndexedDataContainer} from './indexed-data-container';\n\nimport {DataContainerInterface} from './data-container-interface';\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport * as arrow from 'apache-arrow';\n\nexport type DataContainerOptions = {\n  inputDataFormat?: string; // one of DataForm\n  fields?: ProtoDatasetField[];\n  arrowTable?: arrow.Table;\n};\n\nexport const DataForm = {\n  ROWS_ARRAY: 'ROWS_ARRAY',\n  COLS_ARRAY: 'COLS_ARRAY'\n};\n\nconst defaultOptions: DataContainerOptions = {\n  inputDataFormat: DataForm.ROWS_ARRAY\n};\n\n/**\n * Creates a data container wrapper for the data.\n * @param data Data.\n * @param options Options.\n * @returns A data container object which is based on data and options.\n */\nexport function createDataContainer(\n  data: any[],\n  options: DataContainerOptions = defaultOptions\n): DataContainerInterface {\n  options = {...defaultOptions, ...options};\n\n  if (options.inputDataFormat === DataForm.ROWS_ARRAY) {\n    return new RowDataContainer({rows: data, fields: options.fields});\n  } else if (options.inputDataFormat === DataForm.COLS_ARRAY) {\n    return new ArrowDataContainer({cols: data, fields: options.fields, arrowTable: options.arrowTable});\n  }\n\n  throw Error('Failed to create a data container: not implemented format');\n}\n\n/**\n * Creates a data container wrapper around another data container.\n * @param dataContainer Parent data container.\n * @param indices An array of row indices in the parent data container.\n */\nexport function createIndexedDataContainer(\n  dataContainer: DataContainerInterface,\n  indices: number[]\n): DataContainerInterface {\n  return new IndexedDataContainer(dataContainer, indices);\n}\n\n/**\n * Get a sample of rows from a data container.\n * @param dataContainer Data container to get samples from.\n * @param sampleSize Max number of samples.\n * @returns A data container which contains samples from the original data container.\n */\nexport function getSampleData(\n  dataContainer: DataContainerInterface,\n  sampleSize = 500\n): DataContainerInterface {\n  const numberOfRows = dataContainer.numRows();\n  const sampleStep = Math.max(Math.floor(numberOfRows / sampleSize), 1);\n\n  const indices: number[] = [];\n  for (let i = 0; i < numberOfRows; i += sampleStep) {\n    indices.push(i);\n  }\n\n  return createIndexedDataContainer(dataContainer, indices);\n}\n"
  },
  {
    "path": "src/utils/src/data-row.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DataContainerInterface} from './data-container-interface';\n\n/**\n * Setting for shared row optimization.\n * - False/undefined indicates that unique row objects should be used (default).\n * - True indicates that a single temporary row object should be created and used without extra allocations.\n * - A DataRow object indicates that the row should be used as a temporary shared row.\n * When used, the content of the shared row isn't preserved between calls.\n */\nexport type SharedRowOptions = DataRow | boolean | undefined;\n/**\n * Return type for createSharedRow:\n * - DataRow object that should be used as shared row.\n * - Falsy values indicate that shared row object shouldn't be used.\n */\nexport type SharedRowOptionsResult = DataRow | false | undefined;\n\nexport class DataRow {\n  _dataContainer: DataContainerInterface | null;\n\n  _rowIndex: number;\n\n  /**\n   * Creates new DataRow.\n   * @param dataContainer Data container where data is stored. Can be initialized with null for shared rows.\n   * @param rowIndex Index of a row in the data container.\n   */\n  constructor(dataContainer: DataContainerInterface | null, rowIndex: number) {\n    this._dataContainer = dataContainer;\n    this._rowIndex = rowIndex;\n  }\n\n  /**\n   * Conditionally creates a DataRow object.\n   * @param sharedRowDesc Accepts forllowing options:\n   * - true indicates that new DataRow should be created.\n   * - falsy value or a DataRow object is passed through without any change.\n   * @returns A new DataRow object or unchanged input argument.\n   */\n  static createSharedRow(sharedRowDesc: SharedRowOptions): SharedRowOptionsResult {\n    if (sharedRowDesc === true) {\n      return new DataRow(null, 0);\n    }\n    return sharedRowDesc;\n  }\n\n  /**\n   * Returns the value stored at the specified index in the row.\n   * @param columnIndex Index of the requested field in the row.\n   * @returns Value at the index.\n   */\n  valueAt(columnIndex: number): any {\n    return this._dataContainer?.valueAt(this._rowIndex, columnIndex);\n  }\n\n  /**\n   * Returns the row represented as an array.\n   * @returns The row represented as an array.\n   */\n  values(): any[] {\n    return this._dataContainer ? this._dataContainer.rowAsArray(this._rowIndex) : [];\n  }\n\n  /**\n   * Setup a row object. The method is used by shared rows to prevent excessive allocations.\n   * @param dataContainer Data container.\n   * @param rowIndex Index of a row in the data container.\n   */\n  setSource(dataContainer: DataContainerInterface, rowIndex: number): void {\n    this._dataContainer = dataContainer;\n    this._rowIndex = rowIndex;\n  }\n\n  /**\n   * Creates a new array populated with the results of calling the provided function\n   * on every element of the row.\n   * @param handler The callback is called with the following arguments:\n   * - elem: The current element being processed in the row.\n   * - index: The index of the current element being processed in the row.\n   * @returns A new array with each element being the result of the func callback.\n   */\n  map(handler: (elem: any, index: number) => any): any[] {\n    const numColumns = this._dataContainer?.numColumns() || 0;\n    const out: any[] = [];\n    for (let column = 0; column < numColumns; ++column) {\n      out[column] = handler(this.valueAt(column), column);\n    }\n    return out;\n  }\n}\n"
  },
  {
    "path": "src/utils/src/data-scale-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {bisectLeft, quantileSorted as d3Quantile, extent} from 'd3-array';\nimport uniq from 'lodash/uniq';\nimport moment from 'moment';\n\nimport {notNullorUndefined, toArray} from '@kepler.gl/common-utils';\nimport {ALL_FIELD_TYPES, SCALE_FUNC, SCALE_TYPES, NO_VALUE_COLOR} from '@kepler.gl/constants';\n// import {FilterProps, KeplerTable} from '@kepler.gl/layers';\nimport {\n  AggregatedBin,\n  ColorMap,\n  ColorRange,\n  HexColor,\n  KeplerLayer as Layer,\n  MapState,\n  VisualChannel,\n  VisualChannelDomain,\n  RGBColor,\n  RGBAColor,\n  ColorUI,\n  Field\n} from '@kepler.gl/types';\n\nimport {isRgbColor, rgbToHex, hexToRgb} from './color-utils';\nimport {DataContainerInterface} from './data-container-interface';\nimport {formatNumber, isNumber, reverseFormatNumber, unique} from './data-utils';\nimport {getTimeWidgetHintFormatter} from './filter-utils';\nimport {isPlainObject} from './utils';\n\nexport type ColorBreak = {\n  data: HexColor;\n  label: string;\n  range: number[];\n  inputs: number[];\n};\nexport type ColorBreakOrdinal = {\n  data: HexColor;\n  label: string | number | string[] | number[] | null;\n};\n\nexport type D3ScaleFunction = Record<string, any> & ((x: any) => any);\n\n// TODO isolate types - depends on @kepler.gl/layers\ntype FilterProps = any;\ntype KeplerTable = any;\n\nexport type LabelFormat = (n: number, type?: string) => string;\ntype dataValueAccessor = <T>(param: T) => T;\ntype dataContainerValueAccessor = (d: {index: number}, dc: DataContainerInterface) => any;\ntype sort = (a: any, b: any) => any;\n/**\n * return quantile domain for an array of data\n */\nexport function getQuantileDomain(\n  data: any[],\n  valueAccessor?: dataValueAccessor,\n  sortFunc?: sort\n): number[] {\n  const values = typeof valueAccessor === 'function' ? data.map(valueAccessor) : data;\n\n  return values.filter(notNullorUndefined).sort(sortFunc);\n}\n\n/**\n * return ordinal domain for a data container\n */\nexport function getOrdinalDomain(\n  dataContainer: DataContainerInterface,\n  valueAccessor: dataContainerValueAccessor\n): string[] {\n  const values = dataContainer.mapIndex(valueAccessor);\n\n  return unique(values).filter(notNullorUndefined).sort();\n}\n\n/**\n * return linear domain for an array of data\n */\nexport function getLinearDomain(\n  data: number[],\n  valueAccessor?: dataValueAccessor\n): [number, number] {\n  const range = typeof valueAccessor === 'function' ? extent(data, valueAccessor) : extent(data);\n  return range.map((d: undefined | number, i: number) => (d === undefined ? i : d)) as [\n    number,\n    number\n  ];\n}\n\n/**\n * return linear domain for an array of data. A log scale domain cannot contain 0\n */\nexport function getLogDomain(data: any[], valueAccessor: dataValueAccessor): [number, number] {\n  const [d0, d1] = getLinearDomain(data, valueAccessor);\n  return [d0 === 0 ? 1e-5 : d0, d1];\n}\n\nexport type DomainStops = {\n  stops: number[];\n  z: number[];\n};\n\n/**\n * whether field domain is stops\n */\nexport function isDomainStops(domain: unknown): domain is DomainStops {\n  return isPlainObject(domain) && Array.isArray(domain.stops) && Array.isArray(domain.z);\n}\n\nexport type DomainQuantiles = {\n  quantiles: number[];\n  z: number[];\n};\n\n/**\n * whether field domain is quantiles\n */\nexport function isDomainQuantile(domain: any): domain is DomainQuantiles {\n  return isPlainObject(domain) && Array.isArray(domain.quantiles) && Array.isArray(domain.z);\n}\n\n/**\n * get the domain at zoom\n */\nexport function getThresholdsFromQuantiles(\n  quantiles: number[],\n  buckets: number\n): (number | undefined)[] {\n  const thresholds = [];\n  if (!Number.isFinite(buckets) || buckets < 1) {\n    return [quantiles[0], quantiles[quantiles.length - 1]];\n  }\n  for (let i = 1; i < buckets; i++) {\n    // position in sorted array\n    const position = i / buckets;\n    // @ts-ignore\n    thresholds.push(d3Quantile(quantiles, position));\n  }\n\n  return thresholds;\n}\n\n/**\n * get the domain at zoom\n */\nexport function getDomainStepsbyZoom(domain: any[], steps: number[], z: number): any {\n  const i = bisectLeft(steps, z);\n\n  if (steps[i] === z) {\n    // If z is an integer value exactly matching a step, return the corresponding domain\n    return domain[i];\n  }\n  // Otherwise, return the next coarsest domain\n  return domain[Math.max(i - 1, 0)];\n}\n\n/**\n * Get d3 scale function\n */\nexport function getScaleFunction(\n  scale: string,\n  range: any[] | IterableIterator<any>,\n  domain: (number | undefined)[] | string[] | IterableIterator<any>,\n  fixed?: boolean\n): D3ScaleFunction {\n  const scaleFunction = SCALE_FUNC[fixed ? 'linear' : scale]()\n    .domain(domain)\n    .range(fixed ? domain : range);\n  scaleFunction.scaleType = fixed ? 'linear' : scale;\n  return scaleFunction;\n}\n\n/**\n * Get threshold scale color labels\n */\nfunction getThresholdLabels(\n  scale: D3ScaleFunction,\n  labelFormat: LabelFormat\n): Omit<ColorBreak, 'data'>[] {\n  const genLength = scale.range().length;\n  return scale.range().map((d, i) => {\n    const invert = scale.invertExtent(d);\n    const inputs = [\n      i === 0 ? null : reverseFormatNumber(labelFormat(invert[0])),\n      i === genLength - 1 ? null : reverseFormatNumber(labelFormat(invert[1]))\n    ];\n    return {\n      // raw value\n      range: invert,\n      // formatted value\n      inputs,\n      label:\n        i === 0\n          ? `Less than ${labelFormat(invert[1])}`\n          : i === genLength - 1\n          ? `${labelFormat(invert[0])} or more`\n          : `${labelFormat(invert[0])} to ${labelFormat(invert[1])}`\n    };\n  });\n}\n\n/**\n * Get linear / quant scale color labels\n */\nfunction getScaleLabels(\n  scale: D3ScaleFunction,\n  labelFormat: LabelFormat\n): Omit<ColorBreak, 'data'>[] {\n  return scale.range().map(d => {\n    // @ts-ignore\n    const invert = scale.invertExtent(d);\n    const inputs = [\n      reverseFormatNumber(labelFormat(invert[0])),\n      reverseFormatNumber(labelFormat(invert[1]))\n    ];\n\n    return {\n      label: `${labelFormat(invert[0])} to ${labelFormat(invert[1])}`,\n      // raw value\n      range: invert,\n      // formatted value\n      inputs\n    };\n  });\n}\n\nconst customScaleLabelFormat = n => (n ? formatNumber(n, 'real') : 'no value');\n/**\n * Get linear / quant scale color breaks\n */\nexport function getQuantLegends(scale: D3ScaleFunction, labelFormat: LabelFormat): ColorBreak[] {\n  if (typeof scale.invertExtent !== 'function') {\n    return [];\n  }\n  const thresholdLabelFormat = (n, type) =>\n    n && labelFormat ? labelFormat(n) : n ? formatNumber(n, type) : 'no value';\n  const labels =\n    scale.scaleType === 'threshold'\n      ? getThresholdLabels(scale, thresholdLabelFormat)\n      : scale.scaleType === 'custom'\n      ? getThresholdLabels(scale, customScaleLabelFormat)\n      : getScaleLabels(scale, labelFormat);\n\n  const data = scale.range();\n\n  return labels.map((label, index) => ({\n    data: Array.isArray(data[index]) ? rgbToHex(data[index]) : data[index],\n    ...label\n  }));\n}\n\n/**\n * Get ordinal color scale legends\n */\nexport function getOrdinalLegends(scale: D3ScaleFunction): ColorBreakOrdinal[] {\n  const domain = scale.domain();\n  const labels = scale.domain();\n  const data = domain.map(scale);\n\n  return data.map((datum, index) => ({\n    data: isRgbColor(datum) ? rgbToHex(datum) : datum,\n    label: labels[index]\n  }));\n}\n\nconst defaultFormat = d => d;\n\nconst getTimeLabelFormat = domain => {\n  const formatter = getTimeWidgetHintFormatter(domain);\n  return val => moment.utc(val).format(formatter);\n};\n\nexport function getQuantLabelFormat(domain, fieldType) {\n  // quant scale can only be assigned to linear Fields: real, timestamp, integer\n  return fieldType === ALL_FIELD_TYPES.timestamp\n    ? getTimeLabelFormat(domain)\n    : !fieldType\n    ? defaultFormat\n    : n => (isNumber(n) ? formatNumber(n, fieldType) : 'no value');\n}\n\n/**\n * Get legends for scale\n */\nexport function getLegendOfScale({\n  scale,\n  scaleType,\n  labelFormat,\n  fieldType\n}: {\n  scale?: D3ScaleFunction | null;\n  scaleType: string;\n  labelFormat?: LabelFormat;\n  fieldType: string | null | undefined;\n}): ColorBreak[] | ColorBreakOrdinal[] {\n  if (!scale || scale.byZoom) {\n    return [];\n  }\n  if (\n    scaleType === SCALE_TYPES.ordinal ||\n    scaleType === SCALE_TYPES.customOrdinal ||\n    fieldType === ALL_FIELD_TYPES.string\n  ) {\n    return getOrdinalLegends(scale);\n  }\n\n  const formatLabel = labelFormat || getQuantLabelFormat(scale.domain(), fieldType);\n\n  return getQuantLegends(scale, formatLabel);\n}\n\n/**\n * Get color scale function\n */\nexport function getLayerColorScale({\n  range,\n  domain,\n  scaleType,\n  layer\n}: {\n  range: ColorRange | null | undefined;\n  domain: VisualChannelDomain;\n  scaleType: string;\n  layer: Layer;\n  isFixed?: boolean;\n}): D3ScaleFunction | null {\n  if (range && domain && scaleType) {\n    return layer.getColorScale(scaleType, domain, range);\n  }\n  return null;\n}\n\n/**\n * Convert colorRange.colorMap into color breaks UI input\n */\nexport function initializeLayerColorMap(layer: Layer, visualChannel: VisualChannel): ColorMap {\n  const domain = layer.config[visualChannel.domain];\n  const range = layer.config.visConfig[visualChannel.range];\n  const scaleType = layer.config[visualChannel.scale];\n  const field = layer.config[visualChannel.field];\n\n  const scale = getLayerColorScale({\n    range,\n    domain,\n    scaleType,\n    layer\n  });\n\n  const colorBreaks = getLegendOfScale({\n    scale: scale?.byZoom ? scale(0) : scale,\n    scaleType,\n    fieldType: field.type\n  });\n  return colorBreaksToColorMap(colorBreaks);\n}\n\n/**\n * Get visual chanel scale function if it's based on zoom\n */\nexport function getVisualChannelScaleByZoom({\n  scale,\n  layer,\n  mapState\n}: {\n  scale: D3ScaleFunction | null;\n  layer: Layer;\n  mapState?: MapState;\n}): D3ScaleFunction | null {\n  if (scale?.byZoom) {\n    const z = layer.meta?.getZoom ? layer.meta.getZoom(mapState) : mapState?.zoom;\n    scale = Number.isFinite(z) ? scale(z) : null;\n  }\n  return scale;\n}\n\n/**\n * Get categorical colorMap from colors and domain (unique values)\n */\nexport function getCategoricalColorMap(\n  colors: string[],\n  domain: (string | number | string[] | number[] | null)[]\n): any {\n  const uniqueValues = unique(domain).sort();\n  const colorMap = colors.map(color => [null, color]);\n\n  if (colors.length === 0 || uniqueValues.length === 0) {\n    return colorMap;\n  }\n\n  const lastIndex = colors.length - 1;\n  const assignCount = Math.min(lastIndex, uniqueValues.length);\n\n  // Assign first values one-to-one up to the penultimate color (if any)\n  for (let i = 0; i < assignCount; i++) {\n    // @ts-ignore tuple\n    colorMap[i][0] = uniqueValues[i];\n  }\n\n  if (uniqueValues.length > colors.length) {\n    // Aggregate the rest (including the value that would have gone to last color)\n    // Build aggregated array incrementally to match legacy behavior\n    const aggregatedValues: any[] = [];\n    for (let i = lastIndex; i < uniqueValues.length; i++) {\n      const value = uniqueValues[i];\n      if (Array.isArray(value)) {\n        // Spread array elements to match legacy flattening behavior\n        aggregatedValues.push(...(value as any[]));\n      } else {\n        aggregatedValues.push(value);\n      }\n    }\n    // @ts-ignore tuple\n    colorMap[lastIndex][0] = aggregatedValues;\n  } else if (uniqueValues.length === colors.length) {\n    // Exactly one per color\n    // @ts-ignore tuple\n    colorMap[lastIndex][0] = uniqueValues[lastIndex];\n  }\n\n  // @ts-ignore tuple\n  return colorMap;\n}\n\n/**\n * Get categorical colorBreaks from colorMap\n */\nexport function colorMapToCategoricalColorBreaks(\n  colorMap?: ColorMap | null\n): ColorBreakOrdinal[] | null {\n  if (!colorMap) {\n    return null;\n  }\n  const colorBreaks = colorMap.map(([value, color]) => {\n    return {\n      data: color,\n      label: value\n    };\n  });\n\n  return colorBreaks;\n}\n\n/**\n * create categorical colorMap from colorBreaks\n */\nexport function colorBreaksToCategoricalColorMap(colorBreaks: ColorBreakOrdinal[]): ColorMap {\n  // colorMap: [string | string[], hexstring]\n  const colors = uniq(colorBreaks.map(cb => cb.data));\n  const values = uniq(colorBreaks.map(cb => cb.label));\n\n  return getCategoricalColorMap(colors, values);\n}\n\n/**\n * Convert color breaks UI input into colorRange.colorMap\n */\nexport function colorBreaksToColorMap(colorBreaks: ColorBreak[] | ColorBreakOrdinal[]): ColorMap {\n  const colorMap = colorBreaks.map((colorBreak, i) => {\n    // [value, hex]\n    return [\n      colorBreak.inputs\n        ? i === colorBreaks.length - 1\n          ? null // last\n          : colorBreak.inputs[1]\n        : colorBreak.label,\n      colorBreak.data\n    ];\n  });\n\n  // @ts-ignore tuple\n  return colorMap;\n}\n\n/**\n * Convert colorRange.colorMap into color breaks UI input\n */\nexport function colorMapToColorBreaks(colorMap?: ColorMap | null): ColorBreak[] | null {\n  if (!colorMap) {\n    return null;\n  }\n  const colorBreaks = colorMap.map(([value, color], i) => {\n    const range =\n      i === 0\n        ? // first\n          [-Infinity, value]\n        : // last\n        i === colorMap.length - 1\n        ? [colorMap[i - 1][0], Infinity]\n        : // else\n          [colorMap[i - 1][0], value];\n    return {\n      data: color,\n      range,\n      inputs: range,\n      label:\n        // first\n        i === 0\n          ? `Less than ${value}`\n          : // last\n          i === colorMap.length - 1\n          ? `${colorMap[i - 1][0]} or more`\n          : `${colorMap[i - 1][0]} to ${value}`\n    };\n  });\n\n  // @ts-ignore implement conversion for ordinal\n  return colorBreaks;\n}\n\n/**\n * Whether color breaks is for numeric field\n */\nexport function isNumericColorBreaks(colorBreaks: unknown): colorBreaks is ColorBreak[] {\n  return Boolean(Array.isArray(colorBreaks) && colorBreaks.length && colorBreaks[0].inputs);\n}\n\n// return domainMin, domainMax, histogramMean\nexport function getHistogramDomain({\n  aggregatedBins,\n  columnStats,\n  dataset,\n  fieldValueAccessor\n}: {\n  aggregatedBins?: AggregatedBin[];\n  columnStats?: FilterProps['columnStats'];\n  dataset?: KeplerTable;\n  fieldValueAccessor: (idx: unknown) => number;\n}) {\n  let domainMin = Number.POSITIVE_INFINITY;\n  let domainMax = Number.NEGATIVE_INFINITY;\n  let nValid = 0;\n  let domainSum = 0;\n\n  if (aggregatedBins) {\n    Object.values(aggregatedBins).forEach(bin => {\n      const val = bin.value;\n      if (isNumber(val)) {\n        if (val < domainMin) domainMin = val;\n        if (val > domainMax) domainMax = val;\n        domainSum += val;\n        nValid += 1;\n      }\n    });\n  } else {\n    if (columnStats && columnStats.quantiles && columnStats.mean) {\n      // no need to recalcuate min/max/mean if its already in columnStats\n      return [\n        columnStats.quantiles[0].value,\n        columnStats.quantiles[columnStats.quantiles.length - 1].value,\n        columnStats.mean\n      ];\n    }\n    if (dataset && fieldValueAccessor) {\n      dataset.allIndexes.forEach(x => {\n        const val = fieldValueAccessor(x);\n        if (isNumber(val)) {\n          if (val < domainMin) domainMin = val;\n          if (val > domainMax) domainMax = val;\n          domainSum += val;\n          nValid += 1;\n        }\n      });\n    }\n  }\n  const histogramMean = nValid > 0 ? domainSum / nValid : 0;\n  return [nValid > 0 ? domainMin : 0, nValid > 0 ? domainMax : 0, histogramMean];\n}\n\nexport function resetCategoricalColorMapByIndex(colorMap: ColorMap, index: number): any {\n  if (!colorMap) {\n    return colorMap;\n  }\n  const newColorMap = colorMap.map((cm, i) => {\n    if (i === index) {\n      return [null, cm[1]];\n    }\n    return cm;\n  });\n  return newColorMap;\n}\n\n/**\n * select rest categorical values for a colorMap by its index\n */\nexport function selectRestCategoricalColorMapByIndex(\n  colorMap: ColorMap | null,\n  index: number,\n  uniqueValues?: number[] | string[]\n): ColorMap | undefined | null {\n  if (!colorMap || !uniqueValues) {\n    return colorMap;\n  }\n\n  // find unique values that has not been used in current colorMap\n  const uniqValueDict = Object.fromEntries(uniqueValues.map(val => [val, false]));\n  colorMap.forEach(cm => {\n    toArray(cm[0]).forEach(v => {\n      if (v) uniqValueDict[v] = true;\n    });\n  });\n  const rest = Object.keys(uniqValueDict).filter(v => !uniqValueDict[v]);\n\n  // use the not used unique values in the selected color map\n  const newColorMap = colorMap.map((cm, i) => {\n    if (i === index) {\n      return [[...rest, ...toArray(cm[0])], cm[1]];\n    }\n    return cm;\n  }) as ColorMap;\n\n  return newColorMap;\n}\n\n/**\n * remove a categorical value from a colorMap by its index\n */\nexport function removeCategoricalValueFromColorMap(\n  colorMap: ColorMap | null | undefined,\n  item: number | string,\n  index: number\n): ColorMap | null | undefined {\n  if (!colorMap) {\n    return colorMap;\n  }\n  const newColorMap = colorMap.map((cm, i) => {\n    if (i === index) {\n      if (!cm[0]) {\n        return [null, cm[1]];\n      }\n      const currentUniqueValues = toArray(cm[0]);\n      const updatedUniqueValues = currentUniqueValues.filter(v => v !== item);\n      return [updatedUniqueValues, cm[1]];\n    }\n    return cm;\n  }) as ColorMap;\n\n  return newColorMap;\n}\n\n/**\n * add categorical values (from multisel dropdown) to a colorMap by its index\n */\nexport function addCategoricalValuesToColorMap(\n  colorMap: ColorMap,\n  items: (string | number)[],\n  index: number\n): ColorMap {\n  if (!colorMap) {\n    return colorMap;\n  }\n\n  const newColorMap = colorMap.map((cm, i) => {\n    if (i === index) {\n      if (!cm[0]) {\n        return [items, cm[1]];\n      }\n      const currentUniqueValues = toArray(cm[0]);\n      const updatedUniqueValues = uniq(currentUniqueValues.concat(items));\n      return [updatedUniqueValues, cm[1]];\n    }\n    // remove value from other colorMap\n    const currentUniqueValues = cm[0];\n    if (Array.isArray(currentUniqueValues)) {\n      const updatedUniqueValues: string[] | number[] = (currentUniqueValues as any[]).filter(\n        v => !items.includes(v)\n      );\n      return [updatedUniqueValues, cm[1]];\n    } else if (currentUniqueValues && items.includes(currentUniqueValues)) {\n      return [null, cm[1]];\n    }\n\n    return cm;\n  }) as ColorMap;\n\n  return newColorMap;\n}\n\n/**\n * get a color scale func for categorical (custom ordinal) scale\n */\nexport function getCategoricalColorScale(\n  colorDomain: number[] | string[],\n  colorRange: ColorRange,\n  useRgb = true\n): (categoryValue: string | number) => RGBColor | RGBAColor {\n  const cMap = colorRange.colorMap\n    ? colorRange.colorMap\n    : getCategoricalColorMap(colorRange.colors, colorDomain);\n\n  const range: number[][] = [];\n  const domain: string[] = [];\n  cMap.forEach(cm => {\n    if (Array.isArray(cm[0])) {\n      cm[0].forEach(val => {\n        domain.push(val);\n        range.push(useRgb ? hexToRgb(cm[1]) : cm[1]);\n      });\n    } else {\n      domain.push(cm[0]);\n      range.push(useRgb ? hexToRgb(cm[1]) : cm[1]);\n    }\n  });\n\n  const scale = getScaleFunction(SCALE_TYPES.customOrdinal, range, domain, false);\n  scale.unknown(NO_VALUE_COLOR);\n  return scale as any;\n}\n\n/**\n * initialize customPalette by custom scale or customOrdinal scale\n */\nexport function initCustomPaletteByCustomScale({\n  scale,\n  field,\n  ordinalDomain,\n  range,\n  colorBreaks\n}: {\n  scale: string;\n  field?: Field;\n  ordinalDomain?: number[] | string[];\n  range: ColorRange;\n  colorBreaks: ColorBreakOrdinal[] | null;\n}): ColorUI['customPalette'] {\n  const customPaletteName = `color.customPalette.${scale}.${field?.name || 'point-count'}`;\n  // reuse range.colorMap if the field and scale not changed\n  const reuseColorMap = range.colorMap && range.name === customPaletteName && range.type === scale;\n  const colorMap = reuseColorMap\n    ? range.colorMap\n    : scale === SCALE_TYPES.customOrdinal && ordinalDomain\n    ? getCategoricalColorMap(range.colors, ordinalDomain)\n    : colorBreaks && isNumericColorBreaks(colorBreaks)\n    ? colorBreaksToColorMap(colorBreaks)\n    : null;\n  const colors = reuseColorMap ? range.colors : colorMap ? colorMap.map(cm => cm[1]) : range.colors;\n\n  // update custom breaks\n  const customPalette: ColorUI['customPalette'] = {\n    category: 'Custom',\n    name: customPaletteName,\n    type: scale,\n    colorMap,\n    colors: colors || []\n  };\n\n  return customPalette;\n}\n"
  },
  {
    "path": "src/utils/src/data-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport assert from 'assert';\nimport {format as d3Format} from 'd3-format';\nimport moment from 'moment-timezone';\n\nimport {parseGeometryFromArrow} from '@loaders.gl/arrow';\n\nimport {\n  ALL_FIELD_TYPES,\n  TOOLTIP_FORMATS,\n  TOOLTIP_FORMAT_TYPES,\n  TOOLTIP_KEY,\n  TooltipFormat\n} from '@kepler.gl/constants';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {Field, Millisecond, ProtoDatasetField} from '@kepler.gl/types';\n\nimport {snapToMarks} from './plot';\nimport {isPlainObject} from './utils';\nimport {isArrowVector} from './arrow-data-container';\n\nexport type FieldFormatter = (value: any, field?: ProtoDatasetField) => string;\n\n// We need threat latitude differently otherwise mercator project view throws\n// a projection matrix error\n// Uncaught Error: Pixel project matrix not invertible\n// at WebMercatorViewport16.Viewport6 (viewport.js:81:13)\nexport const MAX_LATITUDE = 89.9;\nexport const MIN_LATITUDE = -89.9;\nexport const MAX_LONGITUDE = 180;\nexport const MIN_LONGITUDE = -180;\n\n/**\n * Validates a latitude value.\n * Ensures that the latitude is within the defined minimum and maximum latitude bounds.\n * If the value is out of bounds, it returns the nearest bound value.\n * @param latitude - The latitude value to validate.\n * @returns The validated latitude value.\n */\nexport function validateLatitude(latitude: number | undefined): number {\n  return validateCoordinate(latitude ?? 0, MIN_LATITUDE, MAX_LATITUDE);\n}\n\n/**\n * Validates a longitude value.\n * Ensures that the longitude is within the defined minimum and maximum longitude bounds.\n * If the value is out of bounds, it returns the nearest bound value.\n * @param longitude - The longitude value to validate.\n * @returns The validated longitude value.\n */\nexport function validateLongitude(longitude: number | undefined): number {\n  return validateCoordinate(longitude ?? 0, MIN_LONGITUDE, MAX_LONGITUDE);\n}\n\n/**\n * Validates a coordinate value.\n * Ensures that the value is within the specified minimum and maximum bounds.\n * If the value is out of bounds, it returns the nearest bound value.\n * @param value - The coordinate value to validate.\n * @param minValue - The minimum bound for the value.\n * @param maxValue - The maximum bound for the value.\n * @returns The validated coordinate value.\n */\nexport function validateCoordinate(value: number, minValue: number, maxValue: number): number {\n  if (value <= minValue) {\n    return minValue;\n  }\n  if (value >= maxValue) {\n    return maxValue;\n  }\n\n  return value;\n}\n\n/**\n * simple getting unique values of an array\n * Note: filters out null and undefined values\n *\n * @param values\n * @returns unique values\n */\nexport function unique<T>(values: T[]) {\n  const results: T[] = [];\n  const uniqueSet = new Set(values);\n  uniqueSet.forEach(v => {\n    if (notNullorUndefined(v)) {\n      results.push(v);\n    }\n  });\n  return results;\n}\n\nexport function getLatLngBounds(\n  points: number[][],\n  idx: number,\n  limit: [number, number]\n): [number, number] | null {\n  const lats = points\n    .map(d => Number(Array.isArray(d)) && d[idx])\n    .filter(Number.isFinite)\n    .sort(numberSort);\n\n  if (!lats.length) {\n    return null;\n  }\n\n  // clamp to limit\n  return [Math.max(lats[0], limit[0]), Math.min(lats[lats.length - 1], limit[1])];\n}\n\nexport function clamp([min, max]: [number, number], val = 0): number {\n  return val <= min ? min : val >= max ? max : val;\n}\n\nexport function getSampleData(data, sampleSize = 500, getValue = d => d) {\n  const sampleStep = Math.max(Math.floor(data.length / sampleSize), 1);\n  const output: any[] = [];\n  for (let i = 0; i < data.length; i += sampleStep) {\n    output.push(getValue(data[i]));\n  }\n\n  return output;\n}\n\n/**\n * Convert different time format to unix milliseconds\n */\nexport function timeToUnixMilli(value: string | number | Date, format: string): Millisecond | null {\n  if (notNullorUndefined(value)) {\n    if (typeof value === 'string') {\n      return moment.utc(value, format).valueOf();\n    }\n    if (typeof value === 'number') {\n      return format === 'x' ? value * 1000 : value;\n    }\n    if (value instanceof Date) {\n      return value.valueOf();\n    }\n  }\n  return null;\n}\n\n/**\n * Whether d is a number, this filtered out NaN as well\n */\nexport function isNumber(d: unknown): d is number {\n  return Number.isFinite(d);\n}\n\n/**\n * whether object has property\n * @param {string} prop\n * @returns {boolean} - yes or no\n */\nexport function hasOwnProperty<X extends object, Y extends PropertyKey>(\n  obj: X,\n  prop: Y\n): obj is X & Record<Y, unknown> {\n  return Object.prototype.hasOwnProperty.call(obj, prop);\n}\n\nexport function numberSort(a: number, b: number): number {\n  return a - b;\n}\n\nexport function getSortingFunction(fieldType: string): typeof numberSort | undefined {\n  switch (fieldType) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n    case ALL_FIELD_TYPES.timestamp:\n      return numberSort;\n    default:\n      return undefined;\n  }\n}\n\n/**\n * round number with exact number of decimals\n * return as a string\n */\nexport function preciseRound(num: number, decimals: number): string {\n  const t = Math.pow(10, decimals);\n  return (\n    Math.round(\n      num * t + (decimals > 0 ? 1 : 0) * (Math.sign(num) * (10 / Math.pow(100, decimals)))\n    ) / t\n  ).toFixed(decimals);\n}\n\n/**\n * round a giving number at most 4 decimal places\n * e.g. 10 -> 10, 1.12345 -> 1.2345, 2.0 -> 2\n */\nexport function roundToFour(num: number): number {\n  // @ts-expect-error\n  return Number(`${Math.round(`${num}e+4`)}e-4`);\n}\n/**\n * get number of decimals to round to for slider from step\n * @param step\n * @returns- number of decimal\n */\nexport function getRoundingDecimalFromStep(step: number): number {\n  if (isNaN(step)) {\n    assert('step is not a number');\n    assert(step);\n  }\n\n  const stepStr = step.toString();\n\n  // in case the step is a very small number e.g. 1e-7, return decimal e.g. 7 directly\n  const splitExponential = stepStr.split('e-');\n  if (splitExponential.length === 2) {\n    const coeffZero = splitExponential[0].split('.');\n    const coeffDecimal = coeffZero.length === 1 ? 0 : coeffZero[1].length;\n    return parseInt(splitExponential[1], 10) + coeffDecimal;\n  }\n\n  const splitZero = stepStr.split('.');\n  if (splitZero.length === 1) {\n    return 0;\n  }\n  return splitZero[1].length;\n}\n\n/**\n * If marks is provided, snap to marks, if not normalize to step\n * @param val\n * @param minValue\n * @param step\n * @param marks\n */\nexport function normalizeSliderValue(\n  val: number,\n  minValue: number | undefined,\n  step: number,\n  marks?: number[] | null\n): number {\n  if (marks && marks.length) {\n    // Use in slider, given a number and an array of numbers, return the nears number from the array\n    return snapToMarks(val, marks);\n  }\n\n  return roundValToStep(minValue, step, val);\n}\n\n/**\n * round the value to step for the slider\n * @param minValue\n * @param step\n * @param val\n * @returns - rounded number\n */\nexport function roundValToStep(minValue: number | undefined, step: number, val: number): number {\n  if (!isNumber(step) || !isNumber(minValue)) {\n    return val;\n  }\n\n  const decimal = getRoundingDecimalFromStep(step);\n  const steps = Math.floor((val - minValue) / step);\n  let remain = val - (steps * step + minValue);\n\n  // has to round because javascript turns 0.1 into 0.9999999999999987\n  remain = Number(preciseRound(remain, 8));\n\n  let closest: number;\n  if (remain === 0) {\n    closest = val;\n  } else if (remain < step / 2) {\n    closest = steps * step + minValue;\n  } else {\n    closest = (steps + 1) * step + minValue;\n  }\n\n  // precise round return a string rounded to the defined decimal\n  const rounded = preciseRound(closest, decimal);\n\n  return Number(rounded);\n}\n\n/**\n * Get the value format based on field and format options\n * Used in render tooltip value\n */\nexport const defaultFormatter: FieldFormatter = v => (notNullorUndefined(v) ? String(v) : '');\n\nexport const floatFormatter = v => (isNumber(v) ? String(roundToFour(v)) : '');\n\n/**\n * Transforms a WKB in Uint8Array form into a hex WKB string.\n * @param uint8Array WKB in Uint8Array form.\n * @returns hex WKB string.\n */\nexport function uint8ArrayToHex(data: Uint8Array): string {\n  return Array.from(data)\n    .map(byte => (byte as any).toString(16).padStart(2, '0'))\n    .join('');\n}\n\nexport const FIELD_DISPLAY_FORMAT: {\n  [key: string]: FieldFormatter;\n} = {\n  [ALL_FIELD_TYPES.string]: defaultFormatter,\n  [ALL_FIELD_TYPES.timestamp]: defaultFormatter,\n  [ALL_FIELD_TYPES.integer]: defaultFormatter,\n  [ALL_FIELD_TYPES.real]: defaultFormatter,\n  [ALL_FIELD_TYPES.boolean]: defaultFormatter,\n  [ALL_FIELD_TYPES.date]: defaultFormatter,\n  [ALL_FIELD_TYPES.geojson]: d =>\n    typeof d === 'string'\n      ? d\n      : isPlainObject(d)\n      ? JSON.stringify(d)\n      : Array.isArray(d)\n      ? `[${String(d)}]`\n      : '',\n  [ALL_FIELD_TYPES.geoarrow]: (data, field) => {\n    if (isArrowVector(data)) {\n      try {\n        const encoding = field?.metadata?.get('ARROW:extension:name');\n        if (encoding) {\n          const geometry = parseGeometryFromArrow(data, encoding);\n          return JSON.stringify(geometry);\n        }\n      } catch (error) {\n        // ignore for now\n      }\n    } else if (data instanceof Uint8Array) {\n      return uint8ArrayToHex(data);\n    }\n    return data;\n  },\n  [ALL_FIELD_TYPES.object]: (value: any) => {\n    try {\n      return JSON.stringify(value);\n    } catch (e) {\n      return String(value);\n    }\n  },\n  [ALL_FIELD_TYPES.array]: d => JSON.stringify(d),\n  [ALL_FIELD_TYPES.h3]: defaultFormatter\n};\n\n/**\n * Parse field value and type and return a string representation\n */\nexport const parseFieldValue = (value: any, type: string, field?: Field): string => {\n  if (!notNullorUndefined(value)) {\n    return '';\n  }\n  // BigInt values cannot be serialized with JSON.stringify() directly\n  // We need to explicitly convert them to strings using .toString()\n  // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json\n  if (typeof value === 'bigint') {\n    return value.toString();\n  }\n  return FIELD_DISPLAY_FORMAT[type] ? FIELD_DISPLAY_FORMAT[type](value, field) : String(value);\n};\n\n/**\n * Get the value format based on field and format options\n * Used in render tooltip value\n * @param format\n * @param field\n */\nexport function getFormatter(\n  format?: string | Record<string, string> | null,\n  field?: Field\n): FieldFormatter {\n  if (!format) {\n    return defaultFormatter;\n  }\n  const tooltipFormat = Object.values(TOOLTIP_FORMATS).find(f => f[TOOLTIP_KEY] === format);\n\n  if (tooltipFormat) {\n    return applyDefaultFormat(tooltipFormat as TooltipFormat);\n  } else if (typeof format === 'string' && field) {\n    return applyCustomFormat(format, field);\n  }\n\n  return defaultFormatter;\n}\n\nexport function getColumnFormatter(\n  field: Pick<Field, 'type'> & Partial<Pick<Field, 'format' | 'displayFormat'>>\n): FieldFormatter {\n  const {format, displayFormat} = field;\n\n  if (!format && !displayFormat) {\n    return FIELD_DISPLAY_FORMAT[field.type];\n  }\n  const tooltipFormat = Object.values(TOOLTIP_FORMATS).find(f => f[TOOLTIP_KEY] === displayFormat);\n\n  if (tooltipFormat) {\n    return applyDefaultFormat(tooltipFormat);\n  } else if (typeof displayFormat === 'string' && field) {\n    return applyCustomFormat(displayFormat, field);\n  } else if (typeof displayFormat === 'object') {\n    return applyValueMap(displayFormat);\n  }\n\n  return defaultFormatter;\n}\n\nexport function applyValueMap(format) {\n  return v => format[v];\n}\n\nexport function applyDefaultFormat(tooltipFormat: TooltipFormat): (v: any) => string {\n  if (!tooltipFormat || !tooltipFormat.format) {\n    return defaultFormatter;\n  }\n\n  switch (tooltipFormat.type) {\n    case TOOLTIP_FORMAT_TYPES.DECIMAL:\n      return d3Format(tooltipFormat.format);\n    case TOOLTIP_FORMAT_TYPES.DATE:\n    case TOOLTIP_FORMAT_TYPES.DATE_TIME:\n      return datetimeFormatter(null)(tooltipFormat.format);\n    case TOOLTIP_FORMAT_TYPES.PERCENTAGE:\n      return v => `${d3Format(TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_2.format)(v)}%`;\n    case TOOLTIP_FORMAT_TYPES.BOOLEAN:\n      return getBooleanFormatter(tooltipFormat.format);\n    default:\n      return defaultFormatter;\n  }\n}\n\nexport function getBooleanFormatter(format: string): FieldFormatter {\n  switch (format) {\n    case '01':\n      return (v: boolean) => (v ? '1' : '0');\n    case 'yn':\n      return (v: boolean) => (v ? 'yes' : 'no');\n    default:\n      return defaultFormatter;\n  }\n}\n// Allow user to specify custom tooltip format via config\nexport function applyCustomFormat(format, field: {type?: string}): FieldFormatter {\n  switch (field.type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n      return d3Format(format);\n    case ALL_FIELD_TYPES.date:\n    case ALL_FIELD_TYPES.timestamp:\n      return datetimeFormatter(null)(format);\n    default:\n      return v => v;\n  }\n}\n\nfunction formatLargeNumber(n) {\n  // SI-prefix with 4 significant digits\n  return d3Format('.4~s')(n);\n}\n\nexport function formatNumber(n: number, type?: string): string {\n  switch (type) {\n    case ALL_FIELD_TYPES.integer:\n      if (n < 0) {\n        return `-${formatNumber(-n, 'integer')}`;\n      }\n      if (n < 1000) {\n        return `${Math.round(n)}`;\n      }\n      if (n < 10 * 1000) {\n        return d3Format(',')(Math.round(n));\n      }\n      return formatLargeNumber(n);\n    case ALL_FIELD_TYPES.real:\n      if (n < 0) {\n        return `-${formatNumber(-n, 'number')}`;\n      }\n      if (n < 1000) {\n        return d3Format('.4~r')(n);\n      }\n      if (n < 10 * 1000) {\n        return d3Format(',.2~f')(n);\n      }\n      return formatLargeNumber(n);\n\n    default:\n      return formatNumber(n, 'real');\n  }\n}\n\nconst transformation = {\n  Y: Math.pow(10, 24),\n  Z: Math.pow(10, 21),\n  E: Math.pow(10, 18),\n  P: Math.pow(10, 15),\n  T: Math.pow(10, 12),\n  G: Math.pow(10, 9),\n  M: Math.pow(10, 6),\n  k: Math.pow(10, 3),\n  h: Math.pow(10, 2),\n  da: Math.pow(10, 1),\n  d: Math.pow(10, -1),\n  c: Math.pow(10, -2),\n  m: Math.pow(10, -3),\n  μ: Math.pow(10, -6),\n  n: Math.pow(10, -9),\n  p: Math.pow(10, -12),\n  f: Math.pow(10, -15),\n  a: Math.pow(10, -18),\n  z: Math.pow(10, -21),\n  y: Math.pow(10, -24)\n};\n\n/**\n * Convert a formatted number from string back to number\n */\nexport function reverseFormatNumber(str: string): number {\n  let returnValue: number | null = null;\n  const strNum = str.trim().replace(/,/g, '');\n  Object.entries(transformation).forEach(d => {\n    if (strNum.includes(d[0])) {\n      returnValue = parseFloat(strNum) * d[1];\n      return true;\n    }\n    return false;\n  });\n\n  // if no transformer found, convert to nuber regardless\n  return returnValue === null ? Number(strNum) : returnValue;\n}\n\n/**\n * Format epoch milliseconds with a format string\n * @type timezone\n */\nexport function datetimeFormatter(\n  timezone?: string | null\n): (format?: string) => (ts: number) => string {\n  return timezone\n    ? format => ts => moment.utc(ts).tz(timezone).format(format)\n    : // return empty string instead of 'Invalid date' if ts is undefined/null\n      format => ts => ts ? moment.utc(ts).format(format) : '';\n}\n"
  },
  {
    "path": "src/utils/src/dataset-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport * as arrow from 'apache-arrow';\nimport {\n  ALL_FIELD_TYPES,\n  FIELD_OPTS,\n  TOOLTIP_FORMATS,\n  TOOLTIP_FORMAT_TYPES\n} from '@kepler.gl/constants';\nimport {\n  getSampleForTypeAnalyze,\n  getSampleForTypeAnalyzeArrow,\n  getFieldsFromData\n} from '@kepler.gl/common-utils';\nimport {Analyzer} from 'type-analyzer';\nimport assert from 'assert';\n\nimport {\n  ProcessorResult,\n  RGBColor,\n  Field,\n  FieldPair,\n  TimeLabelFormat,\n  TooltipFields,\n  ProtoDataset\n} from '@kepler.gl/types';\nimport {TooltipFormat} from '@kepler.gl/constants';\nimport {notNullorUndefined, h3IsValid} from '@kepler.gl/common-utils';\n\nimport {isPlainObject} from './utils';\nimport {getFormatter} from './data-utils';\nimport {getFormatValue} from './format';\nimport {hexToRgb} from './color-utils';\n\n// apply a color for each dataset\n// to use as label colors\nconst datasetColors = [\n  '#8F2FBF',\n  '#005CFF',\n  '#C06C84',\n  '#F8B195',\n  '#547A82',\n  '#3EACA8',\n  '#A2D4AB'\n].map(hexToRgb);\n\n/**\n * Random color generator\n */\nfunction* generateColor(): Generator<RGBColor> {\n  let index = 0;\n  while (index < datasetColors.length + 1) {\n    if (index === datasetColors.length) {\n      index = 0;\n    }\n    yield datasetColors[index++];\n  }\n}\n\nexport const datasetColorMaker = generateColor();\n\n/**\n * Field name prefixes and suffixes which should not be considered\n * as metrics. Fields will still be included if a 'metric word'\n * is found on the field name, however.\n */\nconst EXCLUDED_DEFAULT_FIELDS = [\n  // Serial numbers and identification numbers\n  '_id',\n  'id',\n  'index',\n  'uuid',\n  'guid',\n  'uid',\n  'gid',\n  'serial',\n  // Geographic IDs are unlikely to be interesting to color\n  'zip',\n  'code',\n  'post',\n  'region',\n  'fips',\n  'cbgs',\n  'h3',\n  's2',\n  // Geographic coords (but not z/elevation/altitude\n  // since that might be a metric)\n  'lat',\n  'lon',\n  'lng',\n  'latitude',\n  'longitude',\n  '_x',\n  '_y'\n];\n\n/**\n * Prefixes and suffixes that indicate a field is a metric.\n *\n * Note that these are in order of preference, first being\n * most preferred.\n */\nconst METRIC_DEFAULT_FIELDS = [\n  'metric',\n  'value',\n  'sum',\n  'count',\n  'unique',\n  'mean',\n  'mode',\n  'median',\n  'max',\n  'min',\n  'deviation',\n  'variance',\n  'p99',\n  'p95',\n  'p75',\n  'p50',\n  'p25',\n  'p05',\n  // Abbreviations are less preferred\n  'cnt',\n  'val'\n];\n\n/**\n * Choose a field to use as the default color field of a layer.\n *\n * The heuristic is:\n *\n * First, exclude fields that are on the exclusion list and don't\n * have names that suggest they contain metrics. Also exclude\n * field names that are blank.\n *\n * Next, look for a field that is of real type and contains one\n * of the preferred names (in order of the preferred names).\n *\n * Next, look for a field that is of integer type and contains\n * one of the preferred names (in order of the preferred names).\n *\n * Next, look for the first field that is of real type (in order\n * of field index).\n *\n * Next, look for the first field that is of integer type (in\n * order of field index).\n *\n * It's possible no field will be chosen (i.e. because all fields\n * are strings.)\n *\n * @param dataset\n */\nexport function findDefaultColorField({\n  fields,\n  fieldPairs = []\n}: {\n  fields: Field[];\n  fieldPairs: FieldPair[];\n}): null | Field {\n  const fieldsWithoutExcluded = fields.filter(field => {\n    if (field.type !== ALL_FIELD_TYPES.real) {\n      // Only select numeric fields.\n      return false;\n    }\n    if (\n      fieldPairs.find(\n        pair => pair.pair.lat.value === field.name || pair.pair.lng.value === field.name\n      )\n    ) {\n      // Do not permit lat, lon fields\n      return false;\n    }\n\n    const normalizedFieldName = field.name.toLowerCase();\n    if (normalizedFieldName === '') {\n      // Special case excluded name when the name is blank.\n      return false;\n    }\n    const hasExcluded = EXCLUDED_DEFAULT_FIELDS.find(\n      f => normalizedFieldName.startsWith(f) || normalizedFieldName.endsWith(f)\n    );\n    const hasInclusion = METRIC_DEFAULT_FIELDS.find(\n      f => normalizedFieldName.startsWith(f) || normalizedFieldName.endsWith(f)\n    );\n    return !hasExcluded || hasInclusion;\n  });\n\n  const sortedFields = fieldsWithoutExcluded.sort((left, right) => {\n    const normalizedLeft = left.name.toLowerCase();\n    const normalizedRight = right.name.toLowerCase();\n    const leftHasInclusion = METRIC_DEFAULT_FIELDS.findIndex(\n      f => normalizedLeft.startsWith(f) || normalizedLeft.endsWith(f)\n    );\n    const rightHasInclusion = METRIC_DEFAULT_FIELDS.findIndex(\n      f => normalizedRight.startsWith(f) || normalizedRight.endsWith(f)\n    );\n    if (leftHasInclusion !== rightHasInclusion) {\n      if (leftHasInclusion === -1) {\n        // Elements that do not have the inclusion list should go after those that do.\n        return 1;\n      } else if (rightHasInclusion === -1) {\n        // Elements that do have the inclusion list should go before those that don't.\n        return -1;\n      }\n      // Compare based on order in the inclusion list\n      return leftHasInclusion - rightHasInclusion;\n    }\n\n    // Compare based on type\n    if (left.type !== right.type) {\n      if (left.type === ALL_FIELD_TYPES.real) {\n        return -1;\n      }\n      // left is an integer and right is not\n      // and reals come before integers\n      return 1;\n    }\n\n    // Finally, order based on the order in the datasets columns\n    // @ts-expect-error\n    return left.index - right.index;\n  });\n\n  if (sortedFields.length) {\n    // There was a best match\n    return sortedFields[0];\n  }\n  // No matches\n  return null;\n}\n\n/**\n * Validate input data, adding missing field types, rename duplicate columns\n */\nexport function validateInputData(data: ProtoDataset['data']): ProcessorResult {\n  if (!isPlainObject(data)) {\n    assert('addDataToMap Error: dataset.data cannot be null');\n    return null;\n  } else if (!Array.isArray(data.fields)) {\n    assert('addDataToMap Error: expect dataset.data.fields to be an array');\n    return null;\n  } else if (!Array.isArray(data.rows) && !Array.isArray(data.cols)) {\n    assert('addDataToMap Error: expect dataset.data.rows or cols to be an array');\n    return null;\n  }\n\n  const {fields, rows, cols} = data;\n\n  // check if all fields has name, format and type\n  const allValid = fields.every((f, i) => {\n    if (!isPlainObject(f)) {\n      assert(`fields needs to be an array of object, but find ${typeof f}`);\n      fields[i] = {name: `column_${i}`, type: ALL_FIELD_TYPES.string};\n    }\n\n    if (!f.name) {\n      assert(`field.name is required but missing in ${JSON.stringify(f)}`);\n      // assign a name\n      fields[i].name = `column_${i}`;\n    }\n\n    if (!f.type || !ALL_FIELD_TYPES[f.type]) {\n      assert(`unknown field type ${f.type}`);\n      return false;\n    }\n\n    if (!f.analyzerType) {\n      assert(`field ${i} missing analyzerType`);\n      return false;\n    }\n\n    // check time format is correct based on first 10 not empty element\n    if (f.type === ALL_FIELD_TYPES.timestamp) {\n      const sample = (\n        cols ? findNonEmptyRowsAtFieldArrow(cols, i, 10) : findNonEmptyRowsAtField(rows, i, 10)\n      ).map(r => ({ts: r[i]}));\n      const analyzedType = Analyzer.computeColMeta(sample)[0];\n      return analyzedType && analyzedType.category === 'TIME' && analyzedType.format === f.format;\n    }\n\n    // check existing string field is H3 type\n    if (f.type === ALL_FIELD_TYPES.string) {\n      const sample = (\n        cols ? findNonEmptyRowsAtFieldArrow(cols, i, 10) : findNonEmptyRowsAtField(rows, i, 10)\n      ).map(r => r[i]);\n      return sample.every(item => !h3IsValid(item));\n    }\n\n    return true;\n  });\n\n  if (allValid) {\n    return {rows, fields, cols};\n  }\n\n  // if any field has missing type, recalculate it for everyone\n  // because we simply lost faith in humanity\n  const sampleData = cols\n    ? getSampleForTypeAnalyzeArrow(\n        cols,\n        fields.map(f => f.name)\n      )\n    : getSampleForTypeAnalyze({\n        fields: fields.map(f => f.name),\n        rows\n      });\n  const fieldOrder = fields.map(f => f.name);\n  const meta = getFieldsFromData(sampleData, fieldOrder);\n  const updatedFields = fields.map((f, i) => ({\n    ...f,\n    type: meta[i].type,\n    format: meta[i].format,\n    analyzerType: meta[i].analyzerType\n  }));\n\n  return {fields: updatedFields, rows, ...(cols ? {cols} : {})};\n}\n\nfunction findNonEmptyRowsAtField(rows: unknown[][], fieldIdx: number, total: number): any[] {\n  const sample: any[] = [];\n  let i = 0;\n  while (sample.length < total && i < rows.length) {\n    if (notNullorUndefined(rows[i]?.[fieldIdx])) {\n      sample.push(rows[i]);\n    }\n    i++;\n  }\n  return sample;\n}\n\nfunction findNonEmptyRowsAtFieldArrow(\n  cols: arrow.Vector[],\n  fieldIdx: number,\n  total: number\n): any[] {\n  const sample: any[] = [];\n  const numRows = cols[fieldIdx].length;\n  let i = 0;\n  while (sample.length < total && i < numRows) {\n    if (notNullorUndefined(cols[fieldIdx].get(i))) {\n      const row = cols.map(col => col.get(i));\n      sample.push(row);\n    }\n    i++;\n  }\n  return sample;\n}\n\nconst TIME_DISPLAY = '2020-05-11 14:00';\n\nexport const addTimeLabel = (formats: TimeLabelFormat[]) =>\n  formats.map(f => ({\n    ...f,\n    label:\n      f.type === TOOLTIP_FORMAT_TYPES.DATE_TIME || f.type === TOOLTIP_FORMAT_TYPES.DATE\n        ? getFormatter(getFormatValue(f))(TIME_DISPLAY)\n        : f.label\n  }));\n\nexport function getFieldFormatLabels(fieldType?: string): TooltipFormat[] {\n  const tooltipTypes = (fieldType && FIELD_OPTS[fieldType].format.tooltip) || [];\n  const formatLabels: TimeLabelFormat[] = Object.values(TOOLTIP_FORMATS).filter(t =>\n    tooltipTypes.includes(t.type)\n  );\n  return addTimeLabel(formatLabels);\n}\n\nexport const getFormatLabels = (fields: TooltipFields[], fieldName: string): TooltipFormat[] => {\n  const fieldType = fields.find(f => f.name === fieldName)?.type;\n  return getFieldFormatLabels(fieldType);\n};\n"
  },
  {
    "path": "src/utils/src/dom-to-image.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * This file is copied from https://github.com/tsayen/dom-to-image\n * Modified by heshan0131 to allow loading external stylesheets and inline webfonts\n */\n\nimport Window from 'global/window';\nimport document from 'global/document';\nimport Console from 'global/console';\nimport {IMAGE_EXPORT_ERRORS} from '@kepler.gl/constants';\n\nimport {\n  canvasToBlob,\n  escape,\n  escapeXhtml,\n  delay,\n  processClone,\n  asArray,\n  makeImage,\n  mimeType,\n  dataAsUrl,\n  isDataUrl,\n  isSrcAsDataUrl,\n  resolveUrl,\n  getWidth,\n  getHeight,\n  getAndEncode,\n  setStyleSheetBaseHref,\n  toStyleSheet\n} from './dom-utils';\n\nconst inliner = newInliner();\nconst fontFaces = newFontFaces();\nconst images = newImages();\n// Default impl options\nconst defaultOptions = {\n  // Default is to fail on error, no placeholder\n  imagePlaceholder: undefined,\n  // Default cache bust is false, it will use the cache\n  cacheBust: false\n};\n\nconst domtoimage = {\n  toSvg,\n  toPng,\n  toJpeg,\n  toBlob,\n  toPixelData,\n  impl: {\n    fontFaces,\n    images,\n    inliner,\n    options: {} as any\n  }\n};\n\n/**\n * @param {Node} node - The DOM Node object to render\n * @param {Object} options - Rendering options\n * @param {Function} [options.filter] - Should return true if passed node should be included in the output\n *          (excluding node means excluding it's children as well). Not called on the root node.\n * @param {String} [options.bgcolor] - color for the background, any valid CSS color value.\n * @param {Number} [options.width] - width to be applied to node before rendering.\n * @param {Number} [options.height] - height to be applied to node before rendering.\n * @param {Object} [options.style] - an object whose properties to be copied to node's style before rendering.\n * @param {Number} [options.quality] - a Number between 0 and 1 indicating image quality (applicable to JPEG only), defaults to 1.0.\n * @param {boolean} [options.escapeXhtmlForWebpack] - whether to apply fix for uglify error in dom-to-image (should be true for webpack builds), defaults to true.\n * @param {String} [options.imagePlaceholder] - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch\n * @param {Boolean} [options.cacheBust] - set to true to cache bust by appending the time to the request url\n * @return {Promise} - A promise that is fulfilled with a SVG image data URL\n * */\nfunction toSvg(node, options) {\n  options = options || {};\n  copyOptions(options);\n  return Promise.resolve(node)\n    .then(nd => cloneNode(nd, options.filter, true))\n    .then(embedFonts)\n    .then(inlineImages)\n    .then(applyOptions)\n    .then(clone =>\n      makeSvgDataUri(\n        clone,\n        options.width || getWidth(node),\n        options.height || getHeight(node),\n        options.escapeXhtmlForWebpack\n      )\n    );\n\n  function applyOptions(clone) {\n    if (options.bgcolor) clone.style.backgroundColor = options.bgcolor;\n\n    if (options.width) clone.style.width = `${options.width}px`;\n    if (options.height) clone.style.height = `${options.height}px`;\n\n    if (options.style)\n      Object.keys(options.style).forEach(property => {\n        clone.style[property] = options.style[property];\n      });\n\n    return clone;\n  }\n}\n\n/**\n * @param {Node} node - The DOM Node object to render\n * @param {Object} options - Rendering options\n * @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data.\n * */\nfunction toPixelData(node, options) {\n  return draw(node, options || {}).then(\n    canvas => canvas.getContext('2d').getImageData(0, 0, getWidth(node), getHeight(node)).data\n  );\n}\n\n/**\n * @param {Node} node - The DOM Node object to render\n * @param {Object} options - Rendering options\n * @return {Promise} - A promise that is fulfilled with a PNG image data URL\n * */\nfunction toPng(node, options) {\n  return draw(node, options || {}).then(canvas => canvas.toDataURL());\n}\n\n/**\n * @param {Node} node - The DOM Node object to render\n * @param {Object} options - Rendering options\n * @return {Promise} - A promise that is fulfilled with a JPEG image data URL\n * */\nfunction toJpeg(node, options) {\n  options = options || {};\n  return draw(node, options).then(canvas => canvas.toDataURL('image/jpeg', options.quality || 1.0));\n}\n\n/**\n * @param {Node} node - The DOM Node object to render\n * @param {Object} options - Rendering options\n * @return {Promise} - A promise that is fulfilled with a PNG image blob\n * */\nfunction toBlob(node, options) {\n  return draw(node, options || {}).then(canvasToBlob);\n}\n\nfunction copyOptions(options) {\n  // Copy options to impl options for use in impl\n  if (typeof options.imagePlaceholder === 'undefined') {\n    domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder;\n  } else {\n    domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;\n  }\n\n  if (typeof options.cacheBust === 'undefined') {\n    domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;\n  } else {\n    domtoimage.impl.options.cacheBust = options.cacheBust;\n  }\n}\n\nfunction draw(domNode, options) {\n  return toSvg(domNode, options)\n    .then(makeImage)\n    .then(delay(100))\n    .then(image => {\n      const canvas = newCanvas(domNode);\n      canvas.getContext('2d').drawImage(image, 0, 0);\n      return canvas;\n    });\n\n  function newCanvas(dNode) {\n    const canvas = document.createElement('canvas');\n    canvas.width = options.width || getWidth(dNode);\n    canvas.height = options.height || getHeight(dNode);\n\n    if (options.bgcolor) {\n      const ctx = canvas.getContext('2d');\n      ctx.fillStyle = options.bgcolor;\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\n    }\n\n    return canvas;\n  }\n}\n\nfunction cloneNode(node, filter, root) {\n  if (!root && filter && !filter(node)) {\n    return Promise.resolve();\n  }\n\n  return Promise.resolve(node)\n    .then(makeNodeCopy)\n    .then(clone => cloneChildren(node, clone, filter))\n    .then(clone => processClone(node, clone));\n\n  function makeNodeCopy(nd) {\n    if (nd instanceof Window.HTMLCanvasElement) {\n      return makeImage(nd.toDataURL());\n    }\n    return nd.cloneNode(false);\n  }\n\n  function cloneChildrenInOrder(parent, arrChildren, flt) {\n    let done = Promise.resolve();\n    arrChildren.forEach(child => {\n      done = done\n        .then(() => cloneNode(child, flt, null))\n        .then(childClone => {\n          if (childClone) {\n            parent.appendChild(childClone);\n          }\n        });\n    });\n    return done;\n  }\n\n  function cloneChildren(original, clone, flt) {\n    const children = original.childNodes;\n    if (children.length === 0) {\n      return Promise.resolve(clone);\n    }\n\n    return cloneChildrenInOrder(clone, asArray(children), flt).then(() => clone);\n  }\n}\n\nfunction embedFonts(node) {\n  return fontFaces.resolveAll().then(cssText => {\n    const styleNode = document.createElement('style');\n    node.appendChild(styleNode);\n    styleNode.appendChild(document.createTextNode(cssText));\n    return node;\n  });\n}\n\nfunction inlineImages(node) {\n  return images.inlineAll(node).then(() => node);\n}\n\nfunction makeSvgDataUri(node, width, height, escapeXhtmlForWebpack = true) {\n  return Promise.resolve(node).then(nd => {\n    nd.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');\n    const serializedString = new Window.XMLSerializer().serializeToString(nd);\n\n    const xhtml = escapeXhtmlForWebpack ? escapeXhtml(serializedString) : serializedString;\n    const foreignObject = `<foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">${xhtml}</foreignObject>`;\n    const svgStr = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">${foreignObject}</svg>`;\n\n    // Use base64 encoding\n    let base64Svg: string;\n    if (typeof Buffer !== 'undefined' && Buffer.from) {\n      base64Svg = Buffer.from(svgStr, 'utf8').toString('base64');\n    } else if (typeof TextEncoder !== 'undefined') {\n      // Modern browsers: TextEncoder handles Unicode → UTF-8 bytes\n      // Must chunk to avoid call stack limits with large arrays\n      const bytes = new TextEncoder().encode(svgStr);\n      const CHUNK_SIZE = 8192;\n      let binary = '';\n      for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {\n        binary += String.fromCharCode.apply(null, Array.from(bytes.slice(i, i + CHUNK_SIZE)));\n      }\n      base64Svg = Window.btoa(binary);\n    } else {\n      throw new Error('No base64 encoder found');\n    }\n    return `data:image/svg+xml;base64,${base64Svg}`;\n  });\n}\n\nfunction newInliner() {\n  const URL_REGEX = /url\\(['\"]?([^'\"]+?)['\"]?\\)/g;\n\n  return {\n    inlineAll,\n    shouldProcess,\n    impl: {\n      readUrls,\n      inline\n    }\n  };\n\n  function shouldProcess(string) {\n    return string.search(URL_REGEX) !== -1;\n  }\n\n  function readUrls(string) {\n    const result: string[] = [];\n    let match: null | RegExpExecArray;\n    while ((match = URL_REGEX.exec(string)) !== null) {\n      result.push(match[1]);\n    }\n    return result.filter(url => {\n      return !isDataUrl(url);\n    });\n  }\n\n  function urlAsRegex(url0) {\n    return new RegExp(`(url\\\\(['\"]?)(${escape(url0)})(['\"]?\\\\))`, 'g');\n  }\n\n  function inline(string, url, baseUrl, get) {\n    return Promise.resolve(url)\n      .then(ul => (baseUrl ? resolveUrl(ul, baseUrl) : ul))\n      .then(ul => (typeof get === 'function' ? get(ul) : getAndEncode(ul, domtoimage.impl.options)))\n      .then(data => dataAsUrl(data, mimeType(url)))\n      .then(dataUrl => string.replace(urlAsRegex(url), `$1${dataUrl}$3`));\n  }\n\n  function inlineAll(string, baseUrl, get) {\n    if (!shouldProcess(string) || isSrcAsDataUrl(string)) {\n      return Promise.resolve(string);\n    }\n    return Promise.resolve(string)\n      .then(readUrls)\n      .then(urls => {\n        let done = Promise.resolve(string);\n        urls.forEach(url => {\n          done = done.then(str => inline(str, url, baseUrl, get));\n        });\n        return done;\n      });\n  }\n}\n\nfunction newFontFaces() {\n  return {\n    resolveAll,\n    impl: {readAll}\n  };\n\n  function resolveAll() {\n    return readAll()\n      .then(webFonts => {\n        return Promise.all(webFonts.map(webFont => webFont.resolve()));\n      })\n      .then(cssStrings => cssStrings.join('\\n'));\n  }\n\n  function readAll() {\n    return Promise.resolve(asArray(document.styleSheets))\n      .then(loadExternalStyleSheets)\n      .then(getCssRules)\n      .then(selectWebFontRules)\n      .then(rules => rules.map(newWebFont));\n\n    function selectWebFontRules(cssRules) {\n      return cssRules\n        .filter(rule => rule.type === Window.CSSRule.FONT_FACE_RULE)\n        .filter(rule => inliner.shouldProcess(rule.style.getPropertyValue('src')));\n    }\n\n    function loadExternalStyleSheets(styleSheets) {\n      return Promise.all(\n        styleSheets.map(sheet => {\n          if (sheet.href) {\n            // cloudfont doesn't have allow origin header properly set\n            // error response will remain in cache\n            const cache = sheet.href.includes('uber-fonts') ? 'no-cache' : 'default';\n            return Window.fetch(sheet.href, {credentials: 'omit', cache})\n              .then(response => response.text())\n              .then(text => {\n                const result = setStyleSheetBaseHref(text, sheet.href);\n                return toStyleSheet(result);\n              })\n              .catch(err => {\n                // Handle any error that occurred in any of the previous\n                // promises in the chain. stylesheet failed to load should not stop\n                // the process, hence result in only a warning, instead of reject\n                Console.warn(IMAGE_EXPORT_ERRORS.styleSheet, sheet.href);\n                Console.log(err);\n                return;\n              });\n          }\n          return Promise.resolve(sheet);\n        })\n      );\n    }\n\n    function getCssRules(styleSheets) {\n      const cssRules: any[] = [];\n      styleSheets.forEach(sheet => {\n        // try...catch because browser may not able to enumerate rules for cross-domain sheets\n        if (!sheet) {\n          return;\n        }\n        let rules;\n        try {\n          rules = sheet.rules || sheet.cssRules;\n        } catch (e) {\n          Console.log(`'Can't read the css rules of: ${sheet.href}`, e);\n          return;\n        }\n\n        if (rules && typeof rules === 'object') {\n          try {\n            asArray(rules || []).forEach(cssRules.push.bind(cssRules));\n          } catch (e) {\n            Console.log(`Error while reading CSS rules from ${sheet.href}`, e);\n            return;\n          }\n        } else {\n          Console.log('getCssRules can not find cssRules');\n          return;\n        }\n      });\n\n      return cssRules;\n    }\n\n    function newWebFont(webFontRule) {\n      return {\n        resolve: () => {\n          const baseUrl = (webFontRule.parentStyleSheet || {}).href;\n          return inliner.inlineAll(webFontRule.cssText, baseUrl, null);\n        },\n        src: () => webFontRule.style.getPropertyValue('src')\n      };\n    }\n  }\n}\n\nfunction newImages() {\n  return {\n    inlineAll,\n    impl: {\n      newImage\n    }\n  };\n\n  function newImage(element) {\n    function inline(get) {\n      if (isDataUrl(element.src)) {\n        return Promise.resolve();\n      }\n      return Promise.resolve(element.src)\n        .then(ul =>\n          typeof get === 'function' ? get(ul) : getAndEncode(ul, domtoimage.impl.options)\n        )\n        .then(data => dataAsUrl(data, mimeType(element.src)))\n        .then(\n          dataUrl =>\n            new Promise((resolve, reject) => {\n              element.onload = resolve;\n              element.onerror = reject;\n              element.src = dataUrl;\n            })\n        );\n    }\n\n    return {\n      inline\n    };\n  }\n\n  function inlineAll(node) {\n    if (!(node instanceof Element)) {\n      return Promise.resolve(node);\n    }\n\n    return inlineBackground(node).then(() => {\n      if (node instanceof HTMLImageElement) {\n        return newImage(node).inline(null);\n      }\n      return Promise.all(asArray(node.childNodes).map(child => inlineAll(child)));\n    });\n\n    function inlineBackground(nd) {\n      const background = nd.style.getPropertyValue('background');\n\n      if (!background) {\n        return Promise.resolve(nd);\n      }\n\n      return inliner\n        .inlineAll(background, null, null)\n        .then(inlined => {\n          nd.style.setProperty('background', inlined, nd.style.getPropertyPriority('background'));\n        })\n        .then(() => nd);\n    }\n  }\n}\n\nexport default domtoimage;\n"
  },
  {
    "path": "src/utils/src/dom-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Console from 'global/console';\nimport Window from 'global/window';\nimport document from 'global/document';\nimport {IMAGE_EXPORT_ERRORS} from '@kepler.gl/constants';\n\nexport function processClone(original, clone) {\n  if (!(clone instanceof Window.Element)) {\n    return clone;\n  }\n\n  function copyProperties(sourceStyle, targetStyle) {\n    const propertyKeys = asArray(sourceStyle);\n    propertyKeys.forEach(name => {\n      targetStyle.setProperty(\n        name,\n        sourceStyle.getPropertyValue(name),\n        sourceStyle.getPropertyPriority(name)\n      );\n    });\n  }\n\n  function copyStyle(source, target) {\n    if (source.cssText) {\n      target.cssText = source.cssText;\n      // add additional copy of composite styles\n      if (source.font) {\n        target.font = source.font;\n      }\n    } else {\n      copyProperties(source, target);\n    }\n  }\n\n  function cloneStyle(og, cln) {\n    const originalStyle = Window.getComputedStyle(og);\n    copyStyle(originalStyle, cln.style);\n  }\n\n  function formatPseudoElementStyle(cln, elm, stl) {\n    const formatCssText = stl1 => {\n      const cnt = stl1.getPropertyValue('content');\n      return `${stl.cssText} content: ${cnt};`;\n    };\n\n    const formatProperty = name => {\n      return `${name}:${stl.getPropertyValue(name)}${\n        stl.getPropertyPriority(name) ? ' !important' : ''\n      }`;\n    };\n\n    const formatCssProperties = stl2 => {\n      return `${asArray(stl2).map(formatProperty).join('; ')};`;\n    };\n\n    const selector = `.${cln}:${elm}`;\n    const cssText = stl.cssText ? formatCssText(stl) : formatCssProperties(stl);\n\n    return document.createTextNode(`${selector}{${cssText}}`);\n  }\n\n  function clonePseudoElement(org, cln, element) {\n    const style = Window.getComputedStyle(org, element);\n    const content = style.getPropertyValue('content');\n\n    if (content === '' || content === 'none') {\n      return;\n    }\n\n    const className = uid();\n    cln.className = `${cln.className} ${className}`;\n    const styleElement = document.createElement('style');\n    styleElement.appendChild(formatPseudoElementStyle(className, element, style));\n    cln.appendChild(styleElement);\n  }\n\n  function clonePseudoElements([og, cln]) {\n    [':before', ':after'].forEach(element => clonePseudoElement(og, cln, element));\n  }\n\n  function copyUserInput([og, cln]) {\n    if (og instanceof Window.HTMLTextAreaElement) cln.innerHTML = og.value;\n    if (og instanceof Window.HTMLInputElement) cln.setAttribute('value', og.value);\n  }\n\n  function fixSvg(cln) {\n    if (!(cln instanceof Window.SVGElement)) return;\n    cln.setAttribute('xmlns', 'http://www.w3.org/2000/svg');\n\n    if (!(cln instanceof Window.SVGRectElement)) return;\n    ['width', 'height'].forEach(attribute => {\n      const value = cln.getAttribute(attribute);\n      if (!value) return;\n\n      cln.style.setProperty(attribute, value);\n    });\n  }\n\n  return (\n    Promise.resolve([original, clone])\n      .then(([og, cln]) => {\n        cloneStyle(og, cln);\n        return [og, cln];\n      })\n      .then(([og, cln]) => {\n        clonePseudoElements([og, cln]);\n        return [og, cln];\n      })\n      .then(([og, cln]) => {\n        copyUserInput([og, cln]);\n        return [og, cln];\n      })\n      .then(([og, cln]) => {\n        fixSvg(cln);\n        return [og, cln];\n      })\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      .then(([og, cln]) => cln)\n  );\n}\n\n/** **\n * UTILS\n ****/\nexport function asArray(arrayLike) {\n  const array: any[] = [];\n  const length = arrayLike.length;\n  for (let i = 0; i < length; i++) array.push(arrayLike[i]);\n  return array;\n}\n\nexport function fourRandomChars() {\n  return `0000${((Math.random() * Math.pow(36, 4)) << 0).toString(36)}`.slice(-4);\n}\n\nexport function uid() {\n  let index = 0;\n\n  return `u${fourRandomChars()}${index++}`;\n}\n\nexport function makeImage(uri) {\n  return new Promise((resolve, reject) => {\n    const image = new Window.Image();\n    image.onload = () => {\n      resolve(image);\n    };\n    image.onerror = err => {\n      const message = IMAGE_EXPORT_ERRORS.dataUri;\n      Console.log(uri);\n      // error is an Event Object\n      // https://www.w3schools.com/jsref/obj_event.asp\n      reject({event: err, message});\n    };\n    image.src = uri;\n  });\n}\n\nexport function isDataUrl(url) {\n  return url.search(/^(data:)/) !== -1;\n}\n\nfunction parseExtension(url) {\n  const match = /\\.([^./]*?)$/g.exec(url);\n  if (match) {\n    return match[1];\n  }\n  return '';\n}\n\nfunction mimes() {\n  /*\n   * Only WOFF and EOT mime types for fonts are 'real'\n   * see http://www.iana.org/assignments/media-types/media-types.xhtml\n   */\n  const WOFF = 'application/font-woff';\n  const JPEG = 'image/jpeg';\n\n  return {\n    woff: WOFF,\n    woff2: WOFF,\n    ttf: 'application/font-truetype',\n    eot: 'application/vnd.ms-fontobject',\n    png: 'image/png',\n    jpg: JPEG,\n    jpeg: JPEG,\n    gif: 'image/gif',\n    tiff: 'image/tiff',\n    svg: 'image/svg+xml'\n  };\n}\n\nexport function mimeType(url) {\n  const extension = parseExtension(url).toLowerCase();\n  return mimes()[extension] || '';\n}\n\nexport function dataAsUrl(content, type) {\n  return `data:${type};base64,${content}`;\n}\n\nexport function escape(string) {\n  return string.replace(/([.*+?^${}()|[\\]/\\\\])/g, '\\\\$1');\n}\n\nexport function delay(ms) {\n  return arg => {\n    return new Promise(resolve => {\n      Window.setTimeout(() => {\n        resolve(arg);\n      }, ms);\n    });\n  };\n}\n\nexport function isSrcAsDataUrl(text) {\n  const DATA_URL_REGEX = /url\\(['\"]?(data:)([^'\"]+?)['\"]?\\)/;\n  return text.search(DATA_URL_REGEX) !== -1;\n}\n\nfunction cvToBlob(canvas) {\n  return new Promise(resolve => {\n    const binaryString = Window.atob(canvas.toDataURL().split(',')[1]);\n    const length = binaryString.length;\n    const binaryArray = new Uint8Array(length);\n\n    for (let i = 0; i < length; i++) binaryArray[i] = binaryString.charCodeAt(i);\n\n    resolve(new Window.Blob([binaryArray], {type: 'image/png'}));\n  });\n}\n\nexport function canvasToBlob(canvas) {\n  if (canvas.toBlob)\n    return new Promise(resolve => {\n      canvas.toBlob(resolve);\n    });\n\n  return cvToBlob(canvas);\n}\n\nexport function escapeXhtml(string) {\n  return string.replace(/#/g, '%23').replace(/\\n/g, '%0A');\n}\n\nexport function getWidth(node) {\n  const leftBorder = px(node, 'border-left-width');\n  const rightBorder = px(node, 'border-right-width');\n  return node.scrollWidth + leftBorder + rightBorder;\n}\n\nexport function getHeight(node) {\n  const topBorder = px(node, 'border-top-width');\n  const bottomBorder = px(node, 'border-bottom-width');\n  return node.scrollHeight + topBorder + bottomBorder;\n}\n\nfunction px(node, styleProperty) {\n  const value = Window.getComputedStyle(node).getPropertyValue(styleProperty);\n  return parseFloat(value.replace('px', ''));\n}\n\nexport function resolveUrl(url, baseUrl) {\n  const doc = document.implementation.createHTMLDocument();\n  const base = doc.createElement('base');\n  doc.head.appendChild(base);\n  const a = doc.createElement('a');\n  doc.body.appendChild(a);\n  base.href = baseUrl;\n  a.href = url;\n  return a.href;\n}\n\nexport function getAndEncode(url, options) {\n  const TIMEOUT = 30000;\n  if (options.cacheBust) {\n    // Cache bypass so we dont have CORS issues with cached images\n    // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache\n    url += (/\\?/.test(url) ? '&' : '?') + new Date().getTime();\n  }\n\n  return new Promise(resolve => {\n    const request = new Window.XMLHttpRequest();\n\n    request.onreadystatechange = done;\n    request.ontimeout = timeout;\n    request.responseType = 'blob';\n    request.timeout = TIMEOUT;\n    request.open('GET', url, true);\n    request.send();\n\n    let placeholder;\n    if (options.imagePlaceholder) {\n      const split = options.imagePlaceholder.split(/,/);\n      if (split && split[1]) {\n        placeholder = split[1];\n      }\n    }\n\n    function done() {\n      if (request.readyState !== 4) return;\n\n      if (request.status !== 200) {\n        if (placeholder) {\n          resolve(placeholder);\n        } else {\n          fail(`cannot fetch resource: ${url}, status: ${request.status}`);\n        }\n\n        return;\n      }\n\n      const encoder = new Window.FileReader();\n      encoder.onloadend = () => {\n        const content = encoder.result.split(/,/)[1];\n        resolve(content);\n      };\n      encoder.readAsDataURL(request.response);\n    }\n\n    function timeout() {\n      if (placeholder) {\n        resolve(placeholder);\n      } else {\n        fail(`timeout of ${TIMEOUT}ms occurred while fetching resource: ${url}`);\n      }\n    }\n\n    function fail(message) {\n      Console.error(message);\n      resolve('');\n    }\n  });\n}\n\nexport function concatAndResolveUrl(base, url) {\n  return new URL(url, base).href;\n}\n\n// Set relative URL in stylesheet to absolute url\nexport function setStyleSheetBaseHref(text, base) {\n  function addBaseHrefToUrl(match, p1) {\n    const url = /^http/i.test(p1) ? p1 : concatAndResolveUrl(base, p1);\n    return `url('${url}')`;\n  }\n  return isSrcAsDataUrl(text)\n    ? text\n    : text.replace(/url\\(['\"]?([^'\"]+?)['\"]?\\)/g, addBaseHrefToUrl);\n}\n\nexport function toStyleSheet(text) {\n  const doc = document.implementation.createHTMLDocument('');\n  const styleElement = document.createElement('style');\n\n  styleElement.textContent = text;\n  doc.body.appendChild(styleElement);\n\n  return styleElement.sheet;\n}\n"
  },
  {
    "path": "src/utils/src/effect-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport SunCalc from 'suncalc';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {PostProcessEffect} from '@deck.gl/core/typed';\n\nimport {\n  LIGHT_AND_SHADOW_EFFECT,\n  LIGHT_AND_SHADOW_EFFECT_TIME_MODES,\n  FILTER_TYPES,\n  FILTER_VIEW_TYPES\n} from '@kepler.gl/constants';\nimport {arrayMove} from '@kepler.gl/common-utils';\nimport {MapState, Effect, EffectProps, EffectDescription} from '@kepler.gl/types';\nimport {findById} from './utils';\nimport {clamp} from './data-utils';\n\n// TODO isolate types - depends on @kepler.gl/schemas\ntype VisState = any;\n\nexport function computeDeckEffects({\n  visState,\n  mapState\n}: {\n  visState: VisState;\n  mapState: MapState;\n}): PostProcessEffect[] {\n  // TODO: 1) deck effects per deck context 2) preserved between draws\n  return visState.effectOrder\n    .map(effectId => {\n      const effect = findById(effectId)(visState.effects) as Effect | undefined;\n      if (effect?.isEnabled && effect.deckEffect) {\n        updateEffect({visState, mapState, effect});\n        return effect.deckEffect;\n      }\n      return null;\n    })\n    .filter(effect => effect);\n}\n\n/**\n * Always keep light & shadow effect at the top\n */\nexport const fixEffectOrder = (effects: Effect[], effectOrder: string[]): string[] => {\n  const lightShadowEffect = effects.find(effect => effect.type === LIGHT_AND_SHADOW_EFFECT.type);\n  if (lightShadowEffect) {\n    const ind = effectOrder.indexOf(lightShadowEffect.id);\n    if (ind > 0) {\n      effectOrder.splice(ind, 1);\n      effectOrder.unshift(lightShadowEffect.id);\n    }\n  }\n  return effectOrder;\n};\n\nexport function reorderEffectOrder(\n  effectOrder: string[],\n  originEffectId: string,\n  destinationEffectId: string\n): string[] {\n  const activeIndex = effectOrder.indexOf(originEffectId);\n  const overIndex = effectOrder.indexOf(destinationEffectId);\n  return arrayMove(effectOrder, activeIndex, overIndex);\n}\n\n/**\n * Check if the current time is daytime at the given location\n * @param {number} lat Latitude\n * @param {number} lon Longitude\n * @param {number} timestamp Milliseconds since the Unix Epoch\n * @returns boolean\n */\nfunction isDaytime(lat, lon, timestamp) {\n  const date = new Date(timestamp);\n  const {sunrise, sunset} = SunCalc.getTimes(date, lat, lon);\n  return date >= sunrise && date <= sunset;\n}\n\n/**\n * Update effect to match latest vis and map states\n */\nfunction updateEffect({visState, mapState, effect}) {\n  if (effect.type === LIGHT_AND_SHADOW_EFFECT.type) {\n    let {timestamp} = effect.parameters;\n    const {timeMode} = effect.parameters;\n    const sunLight = effect.deckEffect.directionalLights[0];\n\n    // set timestamp for shadow\n    if (timeMode === LIGHT_AND_SHADOW_EFFECT_TIME_MODES.current) {\n      timestamp = Date.now();\n      sunLight.timestamp = timestamp;\n    } else if (timeMode === LIGHT_AND_SHADOW_EFFECT_TIME_MODES.animation) {\n      timestamp = visState.animationConfig.currentTime ?? 0;\n      if (!timestamp) {\n        const filter = visState.filters.find(\n          filter =>\n            filter.type === FILTER_TYPES.timeRange &&\n            (filter.view === FILTER_VIEW_TYPES.enlarged || filter.syncedWithLayerTimeline)\n        );\n        if (filter) {\n          timestamp = filter.value?.[0] ?? 0;\n        }\n      }\n      sunLight.timestamp = timestamp;\n    }\n\n    // output uniform shadow during nighttime\n    if (isDaytime(mapState.latitude, mapState.longitude, timestamp)) {\n      effect.deckEffect.outputUniformShadow = false;\n      sunLight.intensity = effect.parameters.sunLightIntensity;\n    } else {\n      effect.deckEffect.outputUniformShadow = true;\n      sunLight.intensity = 0;\n    }\n  }\n}\n\n/**\n * Validates parameters for an effect, clamps numbers to allowed ranges\n * or applies default values in case of wrong non-numeric values.\n * All unknown properties aren't modified.\n * @param parameters Parameters candidate for an effect.\n * @param effectDescription Description of an effect.\n * @returns\n */\nexport function validateEffectParameters(\n  parameters: EffectProps['parameters'] = {},\n  effectDescription: EffectDescription['parameters']\n): EffectProps['parameters'] {\n  const result = cloneDeep(parameters);\n  effectDescription.forEach(description => {\n    const {defaultValue, name, type, min, max} = description;\n\n    if (!Object.prototype.hasOwnProperty.call(result, name)) return;\n    const property = result[name];\n\n    if (type === 'color' || type === 'array') {\n      if (!Array.isArray(defaultValue)) return;\n      if (property.length !== defaultValue?.length) {\n        result[name] = defaultValue;\n        return;\n      }\n      defaultValue.forEach((v, i) => {\n        let value = property[i];\n        value = Number.isFinite(value) ? clamp([min, max], value) : defaultValue[i] ?? min;\n        if (value !== undefined) {\n          property[i] = value;\n        }\n      });\n      return;\n    }\n\n    const value = Number.isFinite(property) ? clamp([min, max], property) : defaultValue ?? min;\n\n    if (value !== undefined) {\n      result[name] = value;\n    }\n  });\n  return result;\n}\n"
  },
  {
    "path": "src/utils/src/export-map-html.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\nimport {EXPORT_HTML_MAP_MODES, KEPLER_GL_VERSION} from '@kepler.gl/constants';\n\n/**\n * This method is used to create an html file which will inlcude kepler and map data\n * @param {Object} options Object that collects all necessary data to  create the html file\n * @param {string} options.mapboxApiAccessToken Mapbox token used to fetch mapbox tiles\n * @param {Array<Object>} options.datasets Data to include in the map\n * @param {Object} options.config this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}\n * @param {string} version which version of Kepler.gl to load.\n */\nexport const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {\n  return `\n    <!DOCTYPE html>\n    <html>\n      <head>\n        <meta charset=\"UTF-8\"/>\n        <title>Kepler.gl embedded map</title>\n\n        <!--Uber Font-->\n        <link rel=\"stylesheet\" href=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n\n        <!--Kepler css-->\n        <link href=\"https://unpkg.com/kepler.gl@${version}/umd/keplergl.min.css\" rel=\"stylesheet\">\n\n        <!--MapBox css-->\n        <link href=\"https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n        <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n\n        <!-— facebook open graph tags -->\n        <meta property=\"og:url\" content=\"http://kepler.gl/\" />\n        <meta property=\"og:title\" content=\"Large-scale WebGL-powered Geospatial Data Visualization Tool\" />\n        <meta property=\"og:description\" content=\"Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets.\" />\n        <meta property=\"og:site_name\" content=\"kepler.gl\" />\n        <meta property=\"og:image\" content=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png\" />\n        <meta property=\"og:image:type\" content=\"image/png\" />\n        <meta property=\"og:image:width\" content=\"800\" />\n        <meta property=\"og:image:height\" content=\"800\" />\n\n        <!-— twitter card tags -->\n        <meta name=\"twitter:card\" content=\"summary_large_image\">\n        <meta name=\"twitter:site\" content=\"@openjsf\">\n        <meta name=\"twitter:creator\" content=\"@openjsf\">\n        <meta name=\"twitter:title\" content=\"Large-scale WebGL-powered Geospatial Data Visualization Tool\">\n        <meta name=\"twitter:description\" content=\"Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets.\">\n        <meta name=\"twitter:image\" content=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png\" />\n\n        <!-- Load React/Redux -->\n        <script src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\" crossorigin></script>\n        <script src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\" crossorigin></script>\n        <script src=\"https://unpkg.com/redux@4.2.1/dist/redux.js\" crossorigin></script>\n        <script src=\"https://unpkg.com/react-redux@8.1.2/dist/react-redux.min.js\" crossorigin></script>\n        <script src=\"https://unpkg.com/styled-components@6.1.8/dist/styled-components.min.js\" crossorigin></script>\n\n        <!-- Load Kepler.gl -->\n        <script src=\"https://unpkg.com/kepler.gl@${version}/umd/keplergl.min.js\" crossorigin></script>\n\n        <style type=\"text/css\">\n          body {margin: 0; padding: 0; overflow: hidden;}\n        </style>\n\n        <!--MapBox token-->\n        <!--\n          SECURITY NOTE: Your Mapbox access token is embedded below in plain text.\n          Anyone with access to this HTML file can see and use this token.\n          Consider using a scoped token with URL restrictions to limit misuse.\n          See: https://docs.mapbox.com/accounts/guides/tokens/#url-restrictions\n        -->\n        <script>\n          /**\n           * Provide your MapBox Token\n           **/\n          const MAPBOX_TOKEN = '${options.mapboxApiAccessToken || 'PROVIDE_MAPBOX_TOKEN'}';\n          const WARNING_MESSAGE = 'Please Provide a Mapbox Token in order to use Kepler.gl. Edit this file and fill out MAPBOX_TOKEN with your access key';\n        </script>\n\n        <!-- GA: Delete this as you wish, However to pat ourselves on the back, we only track anonymous pageview to understand how many people are using kepler.gl. -->\n        <script>\n          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n          })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n          ga('create', 'UA-64694404-19', {\n            'storage': 'none',\n            'clientId': localStorage.getItem('ga:clientId')\n          });\n          ga(function(tracker) {\n              localStorage.setItem('ga:clientId', tracker.get('clientId'));\n          });\n          ga('set', 'checkProtocolTask', null); // Disable file protocol checking.\n          ga('set', 'checkStorageTask', null); // Disable cookie storage checking.\n          ga('set', 'historyImportTask', null); // Disable history checking (requires reading from cookies).\n          ga('set', 'page', 'keplergl-html');\n          ga('send', 'pageview');\n        </script>\n      </head>\n      <body>\n        <!-- We will put our React component inside this div. -->\n        <div id=\"app\">\n          <!-- Kepler.gl map will be placed here-->\n        </div>\n\n        <!-- Load our React component. -->\n        <script>\n          /* Validate Mapbox Token */\n          if ((MAPBOX_TOKEN || '') === '' || MAPBOX_TOKEN === 'PROVIDE_MAPBOX_TOKEN') {\n            alert(WARNING_MESSAGE);\n          }\n\n          /** STORE **/\n          const reducers = (function createReducers(redux, keplerGl) {\n            return redux.combineReducers({\n              // mount keplerGl reducer\n              keplerGl: keplerGl.keplerGlReducer.initialState({\n                uiState: {\n                  readOnly: ${options.mode === EXPORT_HTML_MAP_MODES.READ},\n                  currentModal: null\n                }\n              })\n            });\n          }(Redux, KeplerGl));\n\n          const middleWares = (function createMiddlewares(keplerGl) {\n            return keplerGl.enhanceReduxMiddleware([\n              // Add other middlewares here\n            ]);\n          }(KeplerGl));\n\n          const enhancers = (function craeteEnhancers(redux, middles) {\n            return redux.applyMiddleware(...middles);\n          }(Redux, middleWares));\n\n          const store = (function createStore(redux, enhancers) {\n            const initialState = {};\n\n            return redux.createStore(\n              reducers,\n              initialState,\n              redux.compose(enhancers)\n            );\n          }(Redux, enhancers));\n          /** END STORE **/\n\n          /** COMPONENTS **/\n          var KeplerElement = (function makeKeplerElement(react, keplerGl, mapboxToken) {\n            var LogoSvg = function LogoSvg() {\n              return react.createElement(\n                \"div\",\n                { className: \"logo-container\", style: {position: 'fixed', zIndex: 10000, padding: '4px'} },\n                  react.createElement(\n                    \"svg\",\n                    {\n                      className: \"kepler_gl__logo\",\n                      width: \"107px\",\n                      height: \"21px\",\n                      viewBox: \"0 0 124 24\"\n                    },\n                    react.createElement(\n                      \"g\",\n                      { transform: \"translate(13.500000, 13.500000) rotate(45.000000) translate(-13.500000, -13.500000) translate(4.000000, 4.000000)\" },\n                      react.createElement(\"rect\", { x: \"0\", y: \"6\", transform: \"matrix(2.535181e-06 1 -1 2.535181e-06 18.1107 6.0369)\", fill: \"#535C6C\", width: \"12.1\", height: \"12.1\" }),\n                      react.createElement(\"rect\", { x: \"6\", y: \"0\", transform: \"matrix(2.535182e-06 1 -1 2.535182e-06 18.1107 -6.0369)\", fill:\"#1FBAD6\", width: \"12.1\", height: \"12.1\" })\n                    ),\n                    react.createElement(\n                      \"g\",\n                      {},\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M39,8.7h2.2l-2.8,4.2l2.9,5.1H39l-2.4-4.2h-1.3V18h-2V5l2-0.1v7.3h1.3L39,8.7z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M42.4,13.3c0-1.5,0.4-2.7,1.1-3.5s1.8-1.2,3.1-1.2c1.3,0,2.2,0.4,2.8,1.1c0.6,0.7,0.9,1.8,0.9,3.3 c0,0.4,0,0.8,0,1.1h-5.8c0,1.6,0.8,2.4,2.4,2.4c1,0,2-0.2,2.9-0.6l0.2,1.7c-0.4,0.2-0.9,0.4-1.4,0.5s-1.1,0.2-1.7,0.2 c-1.5,0-2.6-0.4-3.3-1.2C42.8,16.1,42.4,14.9,42.4,13.3z M46.6,10.1c-0.7,0-1.2,0.2-1.5,0.5c-0.4,0.4-0.6,0.9-0.6,1.7h4 c0-0.8-0.2-1.4-0.5-1.7S47.2,10.1,46.6,10.1z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M57.1,18.2c-1,0-1.8-0.3-2.3-0.9l0,0l0,1.3v2.5h-2V8.7h1.5l0.3,0.9h0c0.3-0.3,0.7-0.6,1.2-0.7 c0.4-0.2,0.9-0.3,1.4-0.3c1.2,0,2.1,0.4,2.7,1.1c0.6,0.7,0.9,2,0.9,3.7c0,1.6-0.3,2.8-1,3.7C59.2,17.8,58.3,18.2,57.1,18.2z M56.7,10.3c-0.4,0-0.8,0.1-1.1,0.2c-0.3,0.2-0.6,0.4-0.8,0.7v4.3c0.2,0.3,0.4,0.5,0.7,0.7c0.3,0.2,0.7,0.3,1.1,0.3 c0.7,0,1.2-0.2,1.6-0.7c0.4-0.5,0.5-1.3,0.5-2.5c0-0.8-0.1-1.4-0.2-1.8s-0.4-0.7-0.7-0.9C57.6,10.4,57.2,10.3,56.7,10.3z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M63.2,16V5l2-0.1v10.8c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.6,0.2c0.3,0,0.6,0,0.9-0.1V18 c-0.4,0.1-1,0.2-1.6,0.2c-0.8,0-1.3-0.2-1.7-0.5S63.2,16.8,63.2,16z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M68.2,13.3c0-1.5,0.4-2.7,1.1-3.5c0.7-0.8,1.8-1.2,3.1-1.2c1.3,0,2.2,0.4,2.8,1.1c0.6,0.7,0.9,1.8,0.9,3.3 c0,0.4,0,0.8,0,1.1h-5.8c0,1.6,0.8,2.4,2.4,2.4c1,0,2-0.2,2.9-0.6l0.2,1.7c-0.4,0.2-0.9,0.4-1.4,0.5s-1.1,0.2-1.7,0.2 c-1.5,0-2.6-0.4-3.3-1.2C68.6,16.1,68.2,14.9,68.2,13.3z M72.4,10.1c-0.7,0-1.2,0.2-1.5,0.5c-0.4,0.4-0.6,0.9-0.6,1.7h4 c0-0.8-0.2-1.4-0.5-1.7S73,10.1,72.4,10.1z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M80.2,8.7l0.1,1.7h0c0.3-0.6,0.7-1.1,1.1-1.4c0.4-0.3,1-0.5,1.6-0.5c0.4,0,0.7,0,1,0.1l-0.1,2 c-0.3-0.1-0.7-0.2-1-0.2c-0.7,0-1.3,0.3-1.7,0.8c-0.4,0.5-0.7,1.2-0.7,2.1V18h-2V8.7H80.2z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M83.8,17c0-0.8,0.4-1.2,1.2-1.2c0.8,0,1.2,0.4,1.2,1.2c0,0.8-0.4,1.1-1.2,1.1C84.2,18.2,83.8,17.8,83.8,17z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M88.5,18.7c0-0.8,0.4-1.4,1.2-1.8c-0.6-0.3-0.9-0.8-0.9-1.5c0-0.7,0.4-1.2,1.1-1.6c-0.3-0.3-0.6-0.6-0.7-0.9 c-0.2-0.4-0.2-0.8-0.2-1.3c0-1,0.3-1.8,0.9-2.3c0.6-0.5,1.6-0.8,2.8-0.8c0.5,0,1,0,1.4,0.1c0.4,0.1,0.8,0.2,1.1,0.4l2.4-0.2v1.5 h-1.5c0.2,0.4,0.2,0.8,0.2,1.3c0,1-0.3,1.7-0.9,2.2s-1.5,0.8-2.7,0.8c-0.7,0-1.2-0.1-1.6-0.2c-0.1,0.1-0.2,0.2-0.3,0.3 c-0.1,0.1-0.1,0.2-0.1,0.4c0,0.2,0.1,0.3,0.2,0.4c0.1,0.1,0.3,0.2,0.6,0.2l2.7,0.2c1,0.1,1.7,0.3,2.2,0.6c0.5,0.3,0.8,0.9,0.8,1.7 c0,0.6-0.2,1.1-0.5,1.5c-0.4,0.4-0.9,0.8-1.5,1c-0.7,0.2-1.5,0.4-2.4,0.4c-1.3,0-2.3-0.2-3-0.6C88.8,20.1,88.5,19.5,88.5,18.7z M95.1,18.4c0-0.3-0.1-0.5-0.3-0.7s-0.6-0.2-1.1-0.3l-2.7-0.3c-0.2,0.1-0.4,0.3-0.5,0.5c-0.1,0.2-0.2,0.4-0.2,0.6 c0,0.4,0.2,0.8,0.5,1c0.4,0.2,1,0.3,1.8,0.3C94.2,19.5,95.1,19.2,95.1,18.4z M94.3,11.5c0-0.6-0.1-1-0.4-1.2 c-0.3-0.2-0.7-0.3-1.3-0.3c-0.7,0-1.1,0.1-1.4,0.3c-0.3,0.2-0.4,0.6-0.4,1.2s0.1,1,0.4,1.2c0.3,0.2,0.7,0.3,1.4,0.3 c0.6,0,1.1-0.1,1.3-0.4S94.3,12,94.3,11.5z\" }),\n                      react.createElement(\"path\", { fill:\"#1FBAD6\", d: \"M99.4,16V5l2-0.1v10.8c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.6,0.2c0.3,0,0.6,0,0.9-0.1V18 c-0.4,0.1-1,0.2-1.6,0.2c-0.8,0-1.3-0.2-1.7-0.5S99.4,16.8,99.4,16z\" })\n                    )\n                  )\n                );\n              };\n\n            return function App() {\n              var rootElm = react.useRef(null);\n              var _useState = react.useState({\n                width: window.innerWidth,\n                height: window.innerHeight\n              });\n              var windowDimension = _useState[0];\n              var setDimension = _useState[1];\n              react.useEffect(function sideEffect(){\n                function handleResize() {\n                  setDimension({width: window.innerWidth, height: window.innerHeight});\n                };\n                window.addEventListener('resize', handleResize);\n                return function() {window.removeEventListener('resize', handleResize);};\n              }, []);\n              return react.createElement(\n                'div',\n                {style: {position: 'absolute', left: 0, width: '100vw', height: '100vh'}},\n                ${options.mode === EXPORT_HTML_MAP_MODES.READ ? 'LogoSvg(),' : ''}\n                react.createElement(keplerGl.KeplerGl, {\n                  mapboxApiAccessToken: mapboxToken,\n                  id: \"map\",\n                  width: windowDimension.width,\n                  height: windowDimension.height\n                })\n              )\n            }\n          }(React, KeplerGl, MAPBOX_TOKEN));\n\n          const app = (function createReactReduxProvider(react, reactRedux, KeplerElement) {\n            return react.createElement(\n              reactRedux.Provider,\n              {store},\n              react.createElement(KeplerElement, null)\n            )\n          }(React, ReactRedux, KeplerElement));\n          /** END COMPONENTS **/\n\n          /** Render **/\n          (function render(react, reactDOM, app) {\n            const container = document.getElementById('app');\n            const root = reactDOM.createRoot(container);\n            root.render(app);\n          }(React, ReactDOM, app));\n        </script>\n        <!-- The next script will show how to interact directly with Kepler map store -->\n        <script>\n          /**\n           * Customize map.\n           * In the following section you can use the store object to dispatch Kepler.gl actions\n           * to add new data and customize behavior\n           */\n          (function customize(keplerGl, store) {\n            const datasets = ${JSON.stringify(options.datasets)};\n            const config = ${JSON.stringify(options.config)};\n\n            const loadedData = keplerGl.KeplerGlSchema.load(\n              datasets,\n              config\n            );\n\n            // For some reason Kepler overwrites the config without extra wait time\n            window.setTimeout(() => {\n              store.dispatch(\n                keplerGl.addDataToMap({\n                  datasets: loadedData.datasets,\n                  config: loadedData.config,\n                  options: {\n                    centerMap: false,\n                  },\n                })\n              );\n            }, 500);\n          }(KeplerGl, store))\n        </script>\n      </body>\n    </html>\n  `;\n};\n"
  },
  {
    "path": "src/utils/src/export-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Blob, URL, atob, Uint8Array, ArrayBuffer, document} from 'global/window';\nimport get from 'lodash/get';\n\nimport {\n  EXPORT_IMG_RESOLUTION_OPTIONS,\n  EXPORT_IMG_RATIO_OPTIONS,\n  EXPORT_IMG_RATIOS,\n  FourByThreeRatioOption,\n  OneXResolutionOption,\n  type ExportResolutionOption\n} from '@kepler.gl/constants';\nimport {ExportImage} from '@kepler.gl/types';\nimport {generateHashId} from '@kepler.gl/common-utils';\nimport domtoimage from './dom-to-image';\nimport {set} from './utils';\nimport {exportMapToHTML} from './export-map-html';\nimport {getApplicationConfig} from './application-config';\n\nconst defaultResolution = OneXResolutionOption;\n\nconst defaultRatio = FourByThreeRatioOption;\n\nexport function isMSEdge(window: Window): boolean {\n  // @ts-ignore msSaveOrOpenBlob was a proprietary addition to the Navigator object, added by Microsoft for Internet Explorer.\n  return Boolean(window.navigator && window.navigator.msSaveOrOpenBlob);\n}\n\nexport function getScaleFromImageSize(imageW = 0, imageH = 0, mapW = 0, mapH = 0) {\n  if ([imageW, imageH, mapW, mapH].some(d => d <= 0)) {\n    return 1;\n  }\n\n  const base = imageW / imageH > 1 ? imageW : imageH;\n  const mapBase = imageW / imageH > 1 ? mapW : mapH;\n  return base / mapBase;\n}\n\nexport function calculateExportImageSize({\n  mapW,\n  mapH,\n  ratio,\n  resolution\n}: {\n  mapW: number;\n  mapH: number;\n  ratio: keyof typeof EXPORT_IMG_RATIOS;\n  resolution: ExportResolutionOption;\n}) {\n  if (mapW <= 0 || mapH <= 0) {\n    return null;\n  }\n\n  const ratioItem = EXPORT_IMG_RATIO_OPTIONS.find(op => op.id === ratio) || defaultRatio;\n\n  const resolutionItem =\n    EXPORT_IMG_RESOLUTION_OPTIONS.find(op => op.id === resolution) || defaultResolution;\n\n  const {width: scaledWidth, height: scaledHeight} = resolutionItem.getSize(mapW, mapH);\n\n  const {width: imageW, height: imageH} = ratioItem.getSize(scaledWidth, scaledHeight);\n\n  const {scale} = ratioItem.id === EXPORT_IMG_RATIOS.CUSTOM ? {scale: undefined} : resolutionItem;\n\n  return {\n    scale,\n    imageW,\n    imageH\n  };\n}\n\nexport function convertToPng(sourceElem: HTMLElement, options) {\n  return domtoimage.toPng(sourceElem, options);\n}\n\nexport function dataURItoBlob(dataURI: string): Blob {\n  const binary = atob(dataURI.split(',')[1]);\n\n  // separate out the mime component\n  const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];\n\n  // write the bytes of the string to an ArrayBuffer\n  const ab = new ArrayBuffer(binary.length);\n\n  // create a view into the buffer\n  const ia = new Uint8Array(ab);\n\n  for (let i = 0; i < binary.length; i++) {\n    ia[i] = binary.charCodeAt(i);\n  }\n\n  return new Blob([ab], {type: mimeString});\n}\n\nexport function downloadFile(fileBlob: Blob, fileName: string) {\n  if (isMSEdge(window)) {\n    (window.navigator as any).msSaveOrOpenBlob(fileBlob, fileName);\n  } else {\n    const url = URL.createObjectURL(fileBlob);\n\n    const link = document.createElement('a');\n    link.setAttribute('href', url);\n    link.setAttribute('download', fileName);\n\n    document.body.appendChild(link);\n    // in some cases where maps are embedded, e.g. need to\n    // create and dispatch an event so that the browser downloads\n    // the file instead of navigating to the url\n    const evt = new MouseEvent('click', {\n      view: window,\n      bubbles: false,\n      cancelable: true\n    });\n    link.dispatchEvent(evt);\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n  }\n}\n\n/**\n * Whether color is rgb\n * @returns\n */\nexport function exportImage(\n  uiStateExportImage: ExportImage,\n  filename = getApplicationConfig().defaultImageName\n) {\n  const {imageDataUri} = uiStateExportImage;\n  if (imageDataUri) {\n    const file = dataURItoBlob(imageDataUri);\n    downloadFile(file, filename);\n  }\n}\n\nexport function exportToJsonString(data) {\n  try {\n    return JSON.stringify(data);\n  } catch (e) {\n    if (e instanceof TypeError) return e.message;\n    // Non-Standard Error Object Property\n    return (e as any).description;\n  }\n}\n\nexport function getMapJSON(state, options = getApplicationConfig().defaultExportJsonSettings) {\n  const {hasData} = options;\n  const schema = state.visState.schema;\n\n  if (!hasData) {\n    return schema.getConfigToSave(state);\n  }\n\n  let mapToSave = schema.save(state);\n  // add file name if title is not provided\n  const title = get(mapToSave, ['info', 'title']);\n  if (!title || !title.length) {\n    mapToSave = set(['info', 'title'], `keplergl_${generateHashId(6)}`, mapToSave);\n  }\n  return mapToSave;\n}\n\nexport function exportJson(state, options: any = {}) {\n  const map = getMapJSON(state, options);\n  map.info.source = 'kepler.gl';\n  const fileBlob = new Blob([exportToJsonString(map)], {type: 'application/json'});\n  const fileName = state.appName ? `${state.appName}.json` : getApplicationConfig().defaultJsonName;\n  downloadFile(fileBlob, fileName);\n}\n\nexport function exportHtml(state, options) {\n  const {userMapboxToken, exportMapboxAccessToken, mode} = options;\n\n  const data = {\n    ...getMapJSON(state),\n    mapboxApiAccessToken:\n      (userMapboxToken || '') !== '' ? userMapboxToken : exportMapboxAccessToken,\n    mode\n  };\n\n  const fileBlob = new Blob([exportMapToHTML(data)], {type: 'text/html'});\n  downloadFile(\n    fileBlob,\n    state.appName ? `${state.appName}.html` : getApplicationConfig().defaultHtmlName\n  );\n}\n\nexport function exportMap(state, options = getApplicationConfig().defaultExportJsonSettings) {\n  const {imageDataUri} = state.uiState.exportImage;\n  const thumbnail: Blob | null = imageDataUri ? dataURItoBlob(imageDataUri) : null;\n  const mapToSave = getMapJSON(state, options);\n\n  return {\n    map: mapToSave,\n    thumbnail\n  };\n}\n\nconst exporters = {\n  exportImage,\n  exportJson,\n  exportHtml\n};\n\nexport default exporters;"
  },
  {
    "path": "src/utils/src/filter-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport keyMirror from 'keymirror';\nimport get from 'lodash/get';\nimport isEqual from 'lodash/isEqual';\nimport {ascending, extent} from 'd3-array';\n\nimport booleanWithin from '@turf/boolean-within';\nimport {point as turfPoint} from '@turf/helpers';\nimport {Decimal} from 'decimal.js';\nimport {\n  ALL_FIELD_TYPES,\n  FILTER_TYPES,\n  ANIMATION_WINDOW,\n  PLOT_TYPES,\n  LAYER_TYPES,\n  FILTER_VIEW_TYPES\n} from '@kepler.gl/constants';\n// import {VisState} from '@kepler.gl/schemas';\nimport * as ScaleUtils from './data-scale-utils';\nimport {h3IsValid} from 'h3-js';\n\nimport {\n  Entries,\n  Field,\n  ParsedFilter,\n  Filter,\n  FilterBase,\n  PolygonFilter,\n  FieldDomain,\n  TimeRangeFieldDomain,\n  Feature,\n  FeatureValue,\n  LineChart,\n  RangeFilter,\n  TimeRangeFilter,\n  RangeFieldDomain,\n  FilterDatasetOpt,\n  FilterRecord,\n  AnimationConfig\n} from '@kepler.gl/types';\n\nimport {generateHashId, toArray, notNullorUndefined, getCentroid} from '@kepler.gl/common-utils';\nimport {DataContainerInterface} from './data-container-interface';\nimport {set} from './utils';\nimport {timeToUnixMilli, unique} from './data-utils';\nimport {updateTimeFilterPlotType, updateRangeFilterPlotType} from './plot';\nimport {KeplerTableModel} from './types';\n\nexport const durationSecond = 1000;\nexport const durationMinute = durationSecond * 60;\nexport const durationHour = durationMinute * 60;\nexport const durationDay = durationHour * 24;\nexport const durationWeek = durationDay * 7;\nexport const durationYear = durationDay * 365;\n\n// TODO isolate types - depends on @kepler.gl/schemas\ntype VisState = any;\n\nexport type FilterResult = {\n  filteredIndexForDomain?: number[];\n  filteredIndex?: number[];\n};\n\nexport type FilterChanged = {\n  // eslint-disable-next-line no-unused-vars\n  [key in keyof FilterRecord]: {\n    [key: string]: 'added' | 'deleted' | 'name_changed' | 'value_changed' | 'dataId_changed';\n  } | null;\n};\n\nexport type dataValueAccessor = (data: {index: number}) => number | null;\n\nexport const TimestampStepMap = [\n  {max: 1, step: 0.05},\n  {max: 10, step: 0.1},\n  {max: 100, step: 1},\n  {max: 500, step: 5},\n  {max: 1000, step: 10},\n  {max: 5000, step: 50},\n  {max: Number.POSITIVE_INFINITY, step: 1000}\n];\n\nexport const FILTER_UPDATER_PROPS = keyMirror({\n  dataId: null,\n  name: null,\n  layerId: null\n});\n\nexport const FILTER_COMPONENTS = {\n  [FILTER_TYPES.select]: 'SingleSelectFilter',\n  [FILTER_TYPES.multiSelect]: 'MultiSelectFilter',\n  [FILTER_TYPES.timeRange]: 'TimeRangeFilter',\n  [FILTER_TYPES.range]: 'RangeFilter',\n  [FILTER_TYPES.polygon]: 'PolygonFilter'\n};\n\nexport const DEFAULT_FILTER_STRUCTURE = {\n  dataId: [], // [string]\n  id: null,\n  enabled: true,\n\n  // time range filter specific\n  fixedDomain: false,\n  view: FILTER_VIEW_TYPES.side,\n  isAnimating: false,\n  animationWindow: ANIMATION_WINDOW.free,\n  speed: 1,\n\n  // field specific\n  name: [], // string\n  type: null,\n  fieldIdx: [], // [integer]\n  domain: null,\n  value: null,\n\n  // plot\n  plotType: {\n    type: PLOT_TYPES.histogram\n  },\n  yAxis: null,\n\n  // mode\n  gpu: false\n};\n\nexport const FILTER_ID_LENGTH = 4;\n\nexport const LAYER_FILTERS = [FILTER_TYPES.polygon];\n\n/**\n * Generates a filter with a dataset id as dataId\n */\nexport function getDefaultFilter({\n  dataId,\n  id\n}: {\n  dataId?: string | null | string[];\n  id?: string;\n} = {}): FilterBase<LineChart> {\n  return {\n    ...DEFAULT_FILTER_STRUCTURE,\n    // store it as dataId and it could be one or many\n    dataId: dataId ? toArray(dataId) : [],\n    id: id || generateHashId(FILTER_ID_LENGTH)\n  };\n}\n\n/**\n * Check if a filter is valid based on the given dataId\n * @param  filter to validate\n * @param  datasetId id to validate filter against\n * @return true if a filter is valid, false otherwise\n */\nexport function shouldApplyFilter(filter: Filter, datasetId: string): boolean {\n  const dataIds = toArray(filter.dataId);\n  return dataIds.includes(datasetId) && filter.value !== null;\n}\n\n/**\n * Validates and modifies polygon filter structure\n * @param dataset\n * @param filter\n * @param layers\n * @return - {filter, dataset}\n */\nexport function validatePolygonFilter<K extends KeplerTableModel<K, L>, L extends {id: string}>(\n  dataset: K,\n  filter: PolygonFilter,\n  layers: L[]\n): {filter: PolygonFilter | null; dataset: K} {\n  const failed = {dataset, filter: null};\n  const {value, layerId, type, dataId} = filter;\n\n  if (!layerId || !isValidFilterValue(type, value)) {\n    return failed;\n  }\n\n  const isValidDataset = dataId.includes(dataset.id);\n\n  if (!isValidDataset) {\n    return failed;\n  }\n\n  const layer = layers.find(l => layerId.includes(l.id));\n\n  if (!layer) {\n    return failed;\n  }\n\n  return {\n    filter: {\n      ...filter,\n      fieldIdx: []\n    },\n    dataset\n  };\n}\n\n/**\n * Custom filter validators\n */\nconst filterValidators = {\n  [FILTER_TYPES.polygon]: validatePolygonFilter\n};\n\n/**\n * Default validate filter function\n * @param datasets\n * @param datasetId\n * @param filter\n * @return - {filter, dataset}\n */\nexport function validateFilter<K extends KeplerTableModel<K, L>, L>(\n  datasets: Record<string, K>,\n  datasetId: string,\n  filter: ParsedFilter\n): {filter: Filter | null; dataset: K} {\n  const dataset = datasets[datasetId];\n  // match filter.dataId\n  const failed = {dataset, filter: null};\n  const filterDataId = toArray(filter.dataId);\n\n  const filterDatasetIndex = filterDataId.indexOf(dataset.id);\n  if (filterDatasetIndex < 0 || !toArray(filter.name)[filterDatasetIndex]) {\n    // the current filter is not mapped against the current dataset\n    return failed;\n  }\n\n  const initializeFilter: Filter = {\n    ...getDefaultFilter({dataId: filter.dataId}),\n    ...filter,\n    dataId: filterDataId,\n    name: toArray(filter.name)\n  };\n\n  const fieldName = initializeFilter.name[filterDatasetIndex];\n  const {filter: updatedFilter, dataset: updatedDataset} = applyFilterFieldName(\n    initializeFilter,\n    datasets,\n    datasetId,\n    fieldName,\n    filterDatasetIndex,\n    {mergeDomain: true}\n  );\n\n  if (!updatedFilter) {\n    return failed;\n  }\n\n  // don't adjust value yet before all datasets are loaded\n  updatedFilter.view = filter.view ?? updatedFilter.view;\n\n  if (updatedFilter.value === null) {\n    // cannot adjust saved value to filter\n    return failed;\n  }\n\n  return {\n    filter: validateFilterYAxis(updatedFilter, updatedDataset),\n    dataset: updatedDataset\n  };\n}\n\n/**\n * Validate saved filter config with new data\n *\n * @param datasets\n * @param datasetId\n * @param filter - filter to be validate\n * @param layers - layers\n * @return validated filter\n */\nexport function validateFilterWithData<K extends KeplerTableModel<K, L>, L>(\n  datasets: Record<string, K>,\n  datasetId: string,\n  filter: ParsedFilter,\n  layers: L[]\n): {filter: Filter; dataset: K} {\n  return filter.type && Object.prototype.hasOwnProperty.call(filterValidators, filter.type)\n    ? filterValidators[filter.type](datasets[datasetId], filter, layers)\n    : validateFilter(datasets, datasetId, filter);\n}\n\n/**\n * Validate YAxis\n * @param filter\n * @param dataset\n * @return {*}\n */\nfunction validateFilterYAxis(filter, dataset) {\n  // TODO: validate yAxis against other datasets\n\n  const {fields} = dataset;\n  const {yAxis} = filter;\n  // TODO: validate yAxis against other datasets\n  if (yAxis) {\n    const matchedAxis = fields.find(({name, type}) => name === yAxis.name && type === yAxis.type);\n\n    filter = matchedAxis\n      ? {\n          ...filter,\n          yAxis: matchedAxis\n        }\n      : filter;\n  }\n\n  return filter;\n}\n\n/**\n * Get default filter prop based on field type\n *\n * @param field\n * @param fieldDomain\n * @returns default filter\n */\nexport function getFilterProps(\n  field: Field,\n  fieldDomain: FieldDomain\n): Partial<Filter> & {fieldType: string} {\n  const filterProps = {\n    ...fieldDomain,\n    fieldType: field.type,\n    view: FILTER_VIEW_TYPES.side\n  };\n\n  switch (field.type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n      return {\n        ...filterProps,\n        value: fieldDomain.domain,\n        type: FILTER_TYPES.range,\n        // @ts-expect-error\n        typeOptions: [FILTER_TYPES.range],\n        gpu: true\n      };\n\n    case ALL_FIELD_TYPES.boolean:\n      // @ts-expect-error\n      return {\n        ...filterProps,\n        type: FILTER_TYPES.select,\n        value: true,\n        gpu: false\n      };\n\n    case ALL_FIELD_TYPES.string:\n    case ALL_FIELD_TYPES.h3:\n    case ALL_FIELD_TYPES.date:\n      // @ts-expect-error\n      return {\n        ...filterProps,\n        type: FILTER_TYPES.multiSelect,\n        value: [],\n        gpu: false\n      };\n\n    case ALL_FIELD_TYPES.timestamp:\n      // @ts-expect-error\n      return {\n        ...filterProps,\n        type: FILTER_TYPES.timeRange,\n        view: FILTER_VIEW_TYPES.enlarged,\n        fixedDomain: true,\n        value: filterProps.domain,\n        gpu: true,\n        plotType: {}\n      };\n\n    default:\n      // @ts-expect-error\n      return {};\n  }\n}\n\nexport const getPolygonFilterFunctor = (layer, filter, dataContainer) => {\n  const getPosition = layer.getPositionAccessor(dataContainer);\n\n  switch (layer.type) {\n    case LAYER_TYPES.point:\n    case LAYER_TYPES.icon:\n      return data => {\n        const pos = getPosition(data);\n        return pos.every(Number.isFinite) && isInPolygon(pos, filter.value);\n      };\n    case LAYER_TYPES.arc:\n    case LAYER_TYPES.line:\n      return data => {\n        const pos = getPosition(data);\n        return (\n          pos.every(Number.isFinite) &&\n          [\n            [pos[0], pos[1]],\n            [pos[3], pos[4]]\n          ].every(point => isInPolygon(point, filter.value))\n        );\n      };\n    case LAYER_TYPES.hexagonId:\n      if (layer.dataToFeature && layer.dataToFeature.centroids) {\n        return data => {\n          // null or getCentroid({id})\n          const centroid = layer.dataToFeature.centroids[data.index];\n          return centroid && isInPolygon(centroid, filter.value);\n        };\n      }\n      return data => {\n        const id = getPosition(data);\n        if (!h3IsValid(id)) {\n          return false;\n        }\n        const pos = getCentroid({id});\n        return pos.every(Number.isFinite) && isInPolygon(pos, filter.value);\n      };\n    case LAYER_TYPES.geojson:\n      return data => {\n        return layer.isInPolygon(data, data.index, filter.value);\n      };\n    default:\n      return () => true;\n  }\n};\n\n/**\n * Check if a GeoJSON feature filter can be applied to a layer\n */\nexport function canApplyFeatureFilter(feature: Feature | null): boolean {\n  return Boolean(feature?.geometry && ['Polygon', 'MultiPolygon'].includes(feature.geometry.type));\n}\n\n/**\n * @param param An object that represents a row record.\n * @param param.index Index of the row in data container.\n * @returns Returns true to keep the element, or false otherwise.\n */\ntype filterFunction = (data: {index: number}) => boolean;\n\n/**\n * @param field dataset Field\n * @param dataId Dataset id\n * @param filter Filter object\n * @param layers list of layers to filter upon\n * @param dataContainer Data container\n * @return filterFunction\n */\n/* eslint-disable complexity */\nexport function getFilterFunction<L extends {config: {dataId: string | null}; id: string}>(\n  field: Field | null,\n  dataId: string,\n  filter: Filter,\n  layers: L[],\n  dataContainer: DataContainerInterface\n): filterFunction {\n  // field could be null in polygon filter\n  const valueAccessor = field ? field.valueAccessor : () => null;\n  const defaultFunc = () => true;\n\n  if (filter.enabled === false) {\n    return defaultFunc;\n  }\n\n  switch (filter.type) {\n    case FILTER_TYPES.range:\n      return data => isInRange(valueAccessor(data), filter.value);\n    case FILTER_TYPES.multiSelect:\n      return data => filter.value.includes(valueAccessor(data));\n    case FILTER_TYPES.select:\n      return data => valueAccessor(data) === filter.value;\n    case FILTER_TYPES.timeRange: {\n      if (!field) {\n        return defaultFunc;\n      }\n      const mappedValue = get(field, ['filterProps', 'mappedValue']);\n      const accessor = Array.isArray(mappedValue)\n        ? data => mappedValue[data.index]\n        : data => timeToUnixMilli(valueAccessor(data), field.format);\n      return data => isInRange(accessor(data), filter.value);\n    }\n    case FILTER_TYPES.polygon: {\n      if (!layers || !layers.length || !filter.layerId) {\n        return defaultFunc;\n      }\n      const layerFilterFunctions = filter.layerId\n        .map(id => layers.find(l => l.id === id))\n        .filter(l => l && l.config.dataId === dataId)\n        .map(layer => getPolygonFilterFunctor(layer, filter, dataContainer));\n\n      return data => layerFilterFunctions.every(filterFunc => filterFunc(data));\n    }\n    default:\n      return defaultFunc;\n  }\n}\n\nexport function updateFilterDataId(dataId: string | string[]): FilterBase<LineChart> {\n  return getDefaultFilter({dataId});\n}\n\nexport function filterDataByFilterTypes(\n  {\n    dynamicDomainFilters,\n    cpuFilters,\n    filterFuncs\n  }: {\n    dynamicDomainFilters: Filter[] | null;\n    cpuFilters: Filter[] | null;\n    filterFuncs: {\n      [key: string]: filterFunction;\n    };\n  },\n  dataContainer: DataContainerInterface\n): FilterResult {\n  const filteredIndexForDomain: number[] = [];\n  const filteredIndex: number[] = [];\n\n  const filterContext = {index: -1, dataContainer};\n  const filterFuncCaller = (filter: Filter) => filterFuncs[filter.id](filterContext);\n\n  const numRows = dataContainer.numRows();\n  for (let i = 0; i < numRows; ++i) {\n    filterContext.index = i;\n\n    const matchForDomain = dynamicDomainFilters && dynamicDomainFilters.every(filterFuncCaller);\n    if (matchForDomain) {\n      filteredIndexForDomain.push(filterContext.index);\n    }\n\n    const matchForRender = cpuFilters && cpuFilters.every(filterFuncCaller);\n    if (matchForRender) {\n      filteredIndex.push(filterContext.index);\n    }\n  }\n\n  return {\n    ...(dynamicDomainFilters ? {filteredIndexForDomain} : {}),\n    ...(cpuFilters ? {filteredIndex} : {})\n  };\n}\n\n/**\n * Get a record of filters based on domain type and gpu / cpu\n */\nexport function getFilterRecord(\n  dataId: string,\n  filters: Filter[],\n  opt: FilterDatasetOpt = {}\n): FilterRecord {\n  const filterRecord: FilterRecord = {\n    dynamicDomain: [],\n    fixedDomain: [],\n    cpu: [],\n    gpu: []\n  };\n\n  filters.forEach(f => {\n    if (isValidFilterValue(f.type, f.value) && toArray(f.dataId).includes(dataId)) {\n      (f.fixedDomain || opt.ignoreDomain\n        ? filterRecord.fixedDomain\n        : filterRecord.dynamicDomain\n      ).push(f);\n\n      (f.gpu && !opt.cpuOnly ? filterRecord.gpu : filterRecord.cpu).push(f);\n    }\n  });\n\n  return filterRecord;\n}\n\n/**\n * Compare filter records to get what has changed\n */\nexport function diffFilters(\n  filterRecord: FilterRecord,\n  oldFilterRecord: FilterRecord | Record<string, never> = {}\n): FilterChanged {\n  let filterChanged: Partial<FilterChanged> = {};\n\n  (Object.entries(filterRecord) as Entries<FilterRecord>).forEach(([record, items]) => {\n    items.forEach(filter => {\n      const oldFilter: Filter | undefined = (oldFilterRecord[record] || []).find(\n        (f: Filter) => f.id === filter.id\n      );\n\n      if (!oldFilter) {\n        // added\n        filterChanged = set([record, filter.id], 'added', filterChanged);\n      } else {\n        // check  what has changed\n        ['name', 'value', 'dataId'].forEach(prop => {\n          if (filter[prop] !== oldFilter[prop]) {\n            filterChanged = set([record, filter.id], `${prop}_changed`, filterChanged);\n          }\n        });\n      }\n    });\n\n    (oldFilterRecord[record] || []).forEach((oldFilter: Filter) => {\n      // deleted\n      if (!items.find(f => f.id === oldFilter.id)) {\n        filterChanged = set([record, oldFilter.id], 'deleted', filterChanged);\n      }\n    });\n  });\n\n  return {...{dynamicDomain: null, fixedDomain: null, cpu: null, gpu: null}, ...filterChanged};\n}\n\n/**\n * Call by parsing filters from URL\n * Check if value of filter within filter domain, if not adjust it to match\n * filter domain\n *\n * @returns value - adjusted value to match filter or null to remove filter\n */\n// eslint-disable-next-line complexity\nexport function adjustValueToFilterDomain(value: Filter['value'], {domain, type}) {\n  if (!type) {\n    return false;\n  }\n  // if the current filter is a polygon it will not have any domain\n  // all other filter types require domain\n  if (type !== FILTER_TYPES.polygon && !domain) {\n    return false;\n  }\n\n  switch (type) {\n    case FILTER_TYPES.range:\n    case FILTER_TYPES.timeRange:\n      if (!Array.isArray(value) || value.length !== 2) {\n        return domain.map(d => d);\n      }\n\n      return value.map((d, i) => (notNullorUndefined(d) && isInRange(d, domain) ? d : domain[i]));\n\n    case FILTER_TYPES.multiSelect: {\n      if (!Array.isArray(value)) {\n        return [];\n      }\n      const filteredValue = value.filter(d => domain.includes(d));\n      return filteredValue.length ? filteredValue : [];\n    }\n    case FILTER_TYPES.select:\n      return domain.includes(value) ? value : true;\n    case FILTER_TYPES.polygon:\n      return value;\n\n    default:\n      return null;\n  }\n}\n\n/**\n * Calculate numeric domain and suitable step\n */\nexport function getNumericFieldDomain(\n  dataContainer: DataContainerInterface,\n  valueAccessor: dataValueAccessor\n): RangeFieldDomain {\n  let domain: [number, number] = [0, 1];\n  let step = 0.1;\n\n  const mappedValue = dataContainer.mapIndex(valueAccessor);\n\n  if (dataContainer.numRows() > 1) {\n    domain = ScaleUtils.getLinearDomain(mappedValue);\n    const diff = domain[1] - domain[0];\n\n    // in case equal domain, [96, 96], which will break quantize scale\n    if (!diff) {\n      domain[1] = domain[0] + 1;\n    }\n\n    step = getNumericStepSize(diff) || step;\n    domain[0] = formatNumberByStep(domain[0], step, 'floor');\n    domain[1] = formatNumberByStep(domain[1], step, 'ceil');\n  }\n\n  return {domain, step};\n}\n\n/**\n * Calculate step size for range and timerange filter\n */\nexport function getNumericStepSize(diff: number): number {\n  diff = Math.abs(diff);\n\n  if (diff > 100) {\n    return 1;\n  } else if (diff > 3) {\n    return 0.01;\n  } else if (diff > 1) {\n    return 0.001;\n  }\n  // Try to get at least 1000 steps - and keep the step size below that of\n  // the (diff > 1) case.\n  const x = diff / 1000;\n  // Find the exponent and truncate to 10 to the power of that exponent\n\n  const exponentialForm = x.toExponential();\n  const exponent = parseFloat(exponentialForm.split('e')[1]);\n\n  // Getting ready for node 12\n  // this is why we need decimal.js\n  // Math.pow(10, -5) = 0.000009999999999999999\n  // the above result shows in browser and node 10\n  // node 12 behaves correctly\n  return new Decimal(10).pow(exponent).toNumber();\n}\n\n/**\n * Calculate timestamp domain and suitable step\n */\nexport function getTimestampFieldDomain(\n  dataContainer: DataContainerInterface,\n  valueAccessor: dataValueAccessor\n): TimeRangeFieldDomain {\n  // to avoid converting string format time to epoch\n  // every time we compare we store a value mapped to int in filter domain\n  const mappedValue = dataContainer.mapIndex(valueAccessor);\n\n  const domain = ScaleUtils.getLinearDomain(mappedValue);\n  const defaultTimeFormat = getTimeWidgetTitleFormatter(domain);\n\n  let step = 0.01;\n\n  const diff = domain[1] - domain[0];\n  // in case equal timestamp add 1 second padding to prevent break\n  if (!diff) {\n    domain[1] = domain[0] + 1000;\n  }\n  const entry = TimestampStepMap.find(f => f.max >= diff);\n  if (entry) {\n    step = entry.step;\n  }\n\n  return {\n    domain,\n    step,\n    mappedValue,\n    defaultTimeFormat\n  };\n}\n\n/**\n * round number based on step\n *\n * @param {Number} val\n * @param {Number} step\n * @param {string} bound\n * @returns {Number} rounded number\n */\nexport function formatNumberByStep(val: number, step: number, bound: 'floor' | 'ceil'): number {\n  if (bound === 'floor') {\n    return Math.floor(val * (1 / step)) / (1 / step);\n  }\n\n  return Math.ceil(val * (1 / step)) / (1 / step);\n}\n\nexport function isInRange(val: any, domain: number[]): boolean {\n  if (!Array.isArray(domain)) {\n    return false;\n  }\n\n  if (Array.isArray(val)) {\n    return domain[0] <= val[0] && val[1] <= domain[1];\n  }\n\n  return val >= domain[0] && val <= domain[1];\n}\n\n/**\n * Determines whether a point is within the provided polygon\n *\n * @param point as input search [lat, lng]\n * @param polygon Points must be within these (Multi)Polygon(s)\n * @return {boolean}\n */\nexport function isInPolygon(point: number[], polygon: any): boolean {\n  return booleanWithin(turfPoint(point), polygon);\n}\nexport function getTimeWidgetTitleFormatter(domain: [number, number]): string | null {\n  if (!isValidTimeDomain(domain)) {\n    return null;\n  }\n\n  const diff = domain[1] - domain[0];\n\n  // Local aware formats\n  // https://momentjs.com/docs/#/parsing/string-format\n  return diff > durationYear ? 'L' : diff > durationDay ? 'L LT' : 'L LTS';\n}\n\n/**\n * Sanity check on filters to prepare for save\n * @type {typeof import('./filter-utils').isFilterValidToSave}\n */\nexport function isFilterValidToSave(filter: any): boolean {\n  return (\n    filter?.type && Array.isArray(filter?.name) && (filter?.name.length || filter?.layerId.length)\n  );\n}\n\n/**\n * Sanity check on filters to prepare for save\n * @type {typeof import('./filter-utils').isValidFilterValue}\n */\n/* eslint-disable complexity */\nexport function isValidFilterValue(type: string | null, value: any): boolean {\n  if (!type) {\n    return false;\n  }\n  switch (type) {\n    case FILTER_TYPES.select:\n      return value === true || value === false;\n\n    case FILTER_TYPES.range:\n    case FILTER_TYPES.timeRange:\n      return Array.isArray(value) && value.every(v => v !== null && !isNaN(v));\n\n    case FILTER_TYPES.multiSelect:\n      return Array.isArray(value) && Boolean(value.length);\n\n    case FILTER_TYPES.input:\n      return Boolean(value.length);\n\n    case FILTER_TYPES.polygon: {\n      const coordinates = get(value, ['geometry', 'coordinates']);\n      return Boolean(value && value.id && coordinates);\n    }\n    default:\n      return true;\n  }\n}\n\nexport function getColumnFilterProps<K extends KeplerTableModel<K, L>, L>(\n  filter: Filter,\n  dataset: K\n): {lineChart: LineChart; yAxs: Field} | Record<string, any> {\n  if (filter.plotType?.type === PLOT_TYPES.histogram || !filter.yAxis) {\n    // histogram should be calculated when create filter\n    return {};\n  }\n\n  const {mappedValue = []} = filter;\n  const {yAxis} = filter;\n  const fieldIdx = dataset.getColumnFieldIdx(yAxis.name);\n  if (fieldIdx < 0) {\n    // Console.warn(`yAxis ${yAxis.name} does not exist in dataset`);\n    return {lineChart: {}, yAxis};\n  }\n\n  // return lineChart\n  const series = dataset.dataContainer\n    .map(\n      (row, rowIndex) => ({\n        x: mappedValue[rowIndex],\n        y: row.valueAt(fieldIdx)\n      }),\n      true\n    )\n    .filter(({x, y}) => Number.isFinite(x) && Number.isFinite(y))\n    .sort((a, b) => ascending(a.x, b.x));\n\n  const yDomain = extent(series, d => d.y) as [number, number];\n  const xDomain = [series[0].x, series[series.length - 1].x] as [number, number];\n\n  return {lineChart: {series, yDomain, xDomain}, yAxis};\n}\n\nexport function updateFilterPlot<K extends KeplerTableModel<K, any>>(\n  datasets: {[id: string]: K},\n  filter: Filter,\n  dataId: string | undefined = undefined\n) {\n  if (dataId) {\n    filter = removeFilterPlot(filter, dataId);\n  }\n\n  if (filter.type === FILTER_TYPES.timeRange) {\n    return updateTimeFilterPlotType(filter as TimeRangeFilter, filter.plotType, datasets);\n  } else if (filter.type === FILTER_TYPES.range) {\n    return updateRangeFilterPlotType(filter as RangeFilter, filter.plotType, datasets);\n  }\n  return filter;\n}\n\n/**\n *\n * @param datasetIds list of dataset ids to be filtered\n * @param datasets all datasets\n * @param filters all filters to be applied to datasets\n * @return datasets - new updated datasets\n */\nexport function applyFiltersToDatasets<\n  K extends KeplerTableModel<K, L>,\n  L extends {config: {dataId: string | null}}\n>(\n  datasetIds: string[],\n  datasets: {[id: string]: K},\n  filters: Filter[],\n  layers?: L[]\n): {[id: string]: K} {\n  const dataIds = toArray(datasetIds);\n  return dataIds.reduce((acc, dataId) => {\n    const layersToFilter = (layers || []).filter(l => l.config.dataId === dataId);\n    const appliedFilters = filters.filter(d => shouldApplyFilter(d, dataId));\n    const table = datasets[dataId];\n\n    return {\n      ...acc,\n      [dataId]: table.filterTable(appliedFilters, layersToFilter, {})\n    };\n  }, datasets);\n}\n\n/**\n * Applies a new field name value to filter and update both filter and dataset\n * @param filter - to be applied the new field name on\n * @param datasets - All datasets\n * @param datasetId - Id of the dataset the field belongs to\n * @param fieldName - field.name\n * @param filterDatasetIndex - field.name\n * @param option\n * @return - {filter, datasets}\n */\nexport function applyFilterFieldName<K extends KeplerTableModel<K, L>, L>(\n  filter: Filter,\n  datasets: Record<string, K>,\n  datasetId: string,\n  fieldName: string,\n  filterDatasetIndex = 0,\n  option?: {mergeDomain: boolean}\n): {\n  filter: Filter | null;\n  dataset: K;\n} {\n  // using filterDatasetIndex we can filter only the specified dataset\n  const mergeDomain =\n    option && Object.prototype.hasOwnProperty.call(option, 'mergeDomain')\n      ? option.mergeDomain\n      : false;\n\n  const dataset = datasets[datasetId];\n  const fieldIndex = dataset?.getColumnFieldIdx(fieldName);\n  // if no field with same name is found, move to the next datasets\n  if (!dataset || fieldIndex === -1) {\n    // throw new Error(`fieldIndex not found. Dataset must contain a property with name: ${fieldName}`);\n    return {filter: null, dataset};\n  }\n\n  // TODO: validate field type\n  const filterProps = dataset.getColumnFilterProps(fieldName);\n\n  let newFilter = {\n    ...filter,\n    ...filterProps,\n    name: Object.assign([...toArray(filter.name)], {[filterDatasetIndex]: fieldName}),\n    fieldIdx: Object.assign([...toArray(filter.fieldIdx)], {\n      [filterDatasetIndex]: fieldIndex\n    }),\n    // Make sure plotType is not overwritten by the default empty plotType\n    ...(filter.plotType ? {plotType: filter.plotType} : {})\n  };\n\n  if (mergeDomain) {\n    const domainSteps: (Filter & {step?: number}) | null =\n      mergeFilterDomain(newFilter, datasets) ?? ({} as Filter);\n    if (domainSteps) {\n      const {domain, step} = domainSteps;\n      newFilter.domain = domain;\n      if (newFilter.step !== step) {\n        newFilter.step = step;\n      }\n    }\n  }\n\n  // TODO: if we don't set filter value in filterProps, we don't need to do this\n  if (filterDatasetIndex > 0) {\n    // don't reset the filter value if we are just adding a synced dataset\n    newFilter = {\n      ...newFilter,\n      value: filter.value\n    };\n  }\n\n  return {\n    filter: newFilter,\n    dataset\n  };\n}\n\n/**\n * Merge the domains of a filter in case it applies to multiple datasets\n */\nexport function mergeFilterDomain(\n  filter: Filter,\n  datasets: Record<string, KeplerTableModel<any, any>>\n): (Filter & {step?: number}) | null {\n  let domainSteps: (Filter & {step?: number}) | null = null;\n  if (!filter?.dataId?.length) {\n    return filter;\n  }\n  filter.dataId.forEach((filterDataId, idx) => {\n    const dataset = datasets[filterDataId];\n    const filterProps = dataset.getColumnFilterProps(filter.name[idx]);\n    domainSteps = mergeFilterDomainStep(domainSteps ?? ({} as Filter), filterProps);\n  });\n  return domainSteps;\n}\n\n/**\n * Merge one filter with other filter prop domain\n */\n/* eslint-disable complexity */\nexport function mergeFilterDomainStep(\n  filter: Filter | null,\n  filterProps?: Partial<Filter>\n): (Filter & {step?: number}) | null {\n  if (!filter) {\n    return null;\n  }\n\n  if (!filterProps) {\n    return filter;\n  }\n\n  if ((filter.fieldType && filter.fieldType !== filterProps.fieldType) || !filterProps.domain) {\n    return filter;\n  }\n\n  const sortedDomain = !filter.domain\n    ? filterProps.domain\n    : [...(filter.domain || []), ...(filterProps.domain || [])].sort((a, b) => a - b);\n\n  const newFilter = {\n    ...filter,\n    ...filterProps,\n    // use min max as default domain\n    domain: [sortedDomain[0], sortedDomain[sortedDomain.length - 1]]\n  };\n\n  switch (filterProps.fieldType) {\n    case ALL_FIELD_TYPES.string:\n    case ALL_FIELD_TYPES.h3:\n    case ALL_FIELD_TYPES.date:\n      return {\n        ...newFilter,\n        domain: unique(sortedDomain)\n      };\n\n    case ALL_FIELD_TYPES.timestamp: {\n      const step =\n        (filter as TimeRangeFilter).step < (filterProps as TimeRangeFieldDomain).step\n          ? (filter as TimeRangeFilter).step\n          : (filterProps as TimeRangeFieldDomain).step;\n\n      return {\n        ...newFilter,\n        step\n      };\n    }\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n    default:\n      return newFilter;\n  }\n}\n/* eslint-enable complexity */\n\n/**\n * Generates polygon filter\n */\nexport const featureToFilterValue = (\n  feature: Feature,\n  filterId: string,\n  properties?: Record<string, any>\n): FeatureValue => ({\n  ...feature,\n  id: feature.id,\n  properties: {\n    ...feature.properties,\n    ...properties,\n    filterId\n  }\n});\n\nexport const getFilterIdInFeature = (f: FeatureValue): string => get(f, ['properties', 'filterId']);\n\n/**\n * Generates polygon filter\n */\nexport function generatePolygonFilter<\n  L extends {config: {dataId: string | null; label: string}; id: string}\n>(layers: L[], feature: Feature): PolygonFilter {\n  const dataId = layers.map(l => l.config.dataId).filter(notNullorUndefined);\n  const layerId = layers.map(l => l.id);\n  const name = layers.map(l => l.config.label);\n  const filter = getDefaultFilter({dataId});\n  return {\n    ...filter,\n    fixedDomain: true,\n    type: FILTER_TYPES.polygon,\n    name,\n    layerId,\n    value: featureToFilterValue(feature, filter.id, {isVisible: true})\n  };\n}\n\n/**\n * Run filter entirely on CPU\n */\ninterface StateType<K extends KeplerTableModel<K, L>, L> {\n  layers: L[];\n  filters: Filter[];\n  datasets: {[id: string]: K};\n}\n\nexport function filterDatasetCPU<T extends StateType<K, L>, K extends KeplerTableModel<K, L>, L>(\n  state: T,\n  dataId: string\n): T {\n  const datasetFilters = state.filters.filter(f => f.dataId.includes(dataId));\n  const dataset = state.datasets[dataId];\n\n  if (!dataset) {\n    return state;\n  }\n\n  const cpuFilteredDataset = dataset.filterTableCPU(datasetFilters, state.layers);\n\n  return set(['datasets', dataId], cpuFilteredDataset, state);\n}\n\n/**\n * Validate parsed filters with datasets and add filterProps to field\n */\ntype MinVisStateForFilter = Pick<VisState, 'layers' | 'datasets' | 'isMergingDatasets'>;\nexport function validateFiltersUpdateDatasets<\n  S extends MinVisStateForFilter,\n  K extends KeplerTableModel<K, L>,\n  L extends {config: {dataId: string | null; label: string}; id: string}\n>(\n  state: S,\n  filtersToValidate: ParsedFilter[] = []\n): {\n  validated: Filter[];\n  failed: Filter[];\n  updatedDatasets: S['datasets'];\n} {\n  // TODO Better Typings here\n  const validated: any[] = [];\n  const failed: any[] = [];\n  const {datasets, layers} = state;\n  let updatedDatasets = datasets;\n\n  // merge filters\n  filtersToValidate.forEach(filterToValidate => {\n    // we can only look for datasets define in the filter dataId\n    const datasetIds = toArray(filterToValidate.dataId);\n\n    // we can merge a filter only if all datasets in filter.dataId are loaded\n    if (datasetIds.every(d => datasets[d] && !state.isMergingDatasets[d])) {\n      // all datasetIds in filter must be present the state datasets\n      const {validatedFilter, applyToDatasets, augmentedDatasets} = datasetIds.reduce<{\n        validatedFilter: Filter | null;\n        applyToDatasets: string[];\n        augmentedDatasets: {[datasetId: string]: any};\n      }>(\n        (acc, datasetId) => {\n          const dataset = updatedDatasets[datasetId];\n          const datasetLayers = layers.filter(l => l.config.dataId === dataset.id);\n          const toValidate = acc.validatedFilter || filterToValidate;\n\n          const {filter: updatedFilter, dataset: updatedDataset} = validateFilterWithData(\n            {\n              ...updatedDatasets,\n              ...acc.augmentedDatasets,\n              [datasetId]: acc.augmentedDatasets[datasetId] || dataset\n            },\n            datasetId,\n            toValidate,\n            datasetLayers\n          );\n\n          if (updatedFilter) {\n            // merge filter domain step\n            return {\n              validatedFilter: updatedFilter,\n\n              applyToDatasets: [...acc.applyToDatasets, datasetId],\n\n              augmentedDatasets: {\n                ...acc.augmentedDatasets,\n                [datasetId]: updatedDataset\n              }\n            };\n          }\n\n          return acc;\n        },\n        {\n          validatedFilter: null,\n          applyToDatasets: [],\n          augmentedDatasets: {}\n        }\n      );\n\n      if (validatedFilter && isEqual(datasetIds, applyToDatasets)) {\n        let domain = validatedFilter.domain;\n        if ((validatedFilter as TimeRangeFilter).syncedWithLayerTimeline) {\n          const animatableLayers = getAnimatableVisibleLayers(layers);\n          domain = mergeTimeDomains([\n            ...animatableLayers.map(l => l.config.animation.domain || [0, 0]),\n            validatedFilter.domain\n          ]);\n        }\n\n        validatedFilter.value = adjustValueToFilterDomain(filterToValidate.value, {\n          ...validatedFilter,\n          domain\n        });\n\n        validated.push(updateFilterPlot(datasets, validatedFilter));\n        updatedDatasets = {\n          ...updatedDatasets,\n          ...augmentedDatasets\n        };\n      } else {\n        failed.push(filterToValidate);\n      }\n    } else {\n      failed.push(filterToValidate);\n    }\n  });\n\n  return {validated, failed, updatedDatasets};\n}\n\nexport function removeFilterPlot(filter: Filter, dataId: string) {\n  let nextFilter = filter;\n\n  const rangeFilter = filter as RangeFilter;\n  if (rangeFilter.bins && rangeFilter.bins[dataId]) {\n    const {[dataId]: _delete, ...nextBins} = rangeFilter.bins;\n    nextFilter = {\n      ...rangeFilter,\n      bins: nextBins\n    };\n  }\n\n  const timeFilter = filter as TimeRangeFilter;\n  if (timeFilter.timeBins && timeFilter.timeBins[dataId]) {\n    const {[dataId]: __delete, ...nextTimeBins} = timeFilter.timeBins;\n    nextFilter = {\n      ...nextFilter,\n      timeBins: nextTimeBins\n    } as Filter;\n  }\n\n  return nextFilter;\n}\n\nexport function isValidTimeDomain(domain) {\n  return Array.isArray(domain) && domain.every(Number.isFinite);\n}\n\nexport function getTimeWidgetHintFormatter(domain: [number, number]): string | undefined {\n  if (!isValidTimeDomain(domain)) {\n    return undefined;\n  }\n\n  const diff = domain[1] - domain[0];\n  return diff > durationWeek\n    ? 'L'\n    : diff > durationDay\n    ? 'L LT'\n    : diff > durationHour\n    ? 'LT'\n    : 'LTS';\n}\n\nexport function isSideFilter(filter: Filter): boolean {\n  return filter.view === FILTER_VIEW_TYPES.side;\n}\n\nexport function mergeTimeDomains(domains: ([number, number] | null)[]): [number, number] {\n  return domains.reduce(\n    (acc: [number, number], domain) => [\n      Math.min(acc[0], domain?.[0] ?? Infinity),\n      Math.max(acc[1], domain?.[1] ?? -Infinity)\n    ],\n    [Number(Infinity), -Infinity]\n  ) as [number, number];\n}\n\n/**\n * @param {Layer} layer\n */\nexport function isLayerAnimatable(layer: any): boolean {\n  return layer.config.animation?.enabled && Array.isArray(layer.config.animation.domain);\n}\n\n/**\n * @param {Layer[]} layers\n * @returns {Layer[]}\n */\nexport function getAnimatableVisibleLayers(layers: any[]): any[] {\n  return layers.filter(l => isLayerAnimatable(l) && l.config.isVisible);\n}\n\n/**\n * @param {Layer[]} layers\n * @param {string} type\n * @returns {Layer[]}\n */\nexport function getAnimatableVisibleLayersByType(layers: any[], type: string): any[] {\n  return getAnimatableVisibleLayers(layers).filter(l => l.type === type);\n}\n\n/**\n * @param {Layer[]} layers\n * @returns {Layer[]}\n */\nexport function getIntervalBasedAnimationLayers(layers: any[]): any[] {\n  // @ts-ignore\n  return getAnimatableVisibleLayers(layers).filter(l => l.config.animation?.timeSteps);\n}\n\nexport function mergeFilterWithTimeline(\n  filter: TimeRangeFilter,\n  animationConfig: AnimationConfig\n): {filter: TimeRangeFilter; animationConfig: AnimationConfig} {\n  if (\n    filter?.type === FILTER_TYPES.timeRange &&\n    filter.syncedWithLayerTimeline &&\n    animationConfig &&\n    Array.isArray(animationConfig.domain)\n  ) {\n    const domain = mergeTimeDomains([filter.domain, animationConfig.domain as [number, number]]);\n    return {\n      filter: {\n        ...filter,\n        domain\n      },\n      animationConfig: {\n        ...animationConfig,\n        domain\n      }\n    };\n  }\n  return {filter, animationConfig};\n}\n\nexport function scaleSourceDomainToDestination(\n  sourceDomain: [number, number],\n  destinationDomain: [number, number]\n): [number, number] {\n  // 0 -> 100: merged domains t1 - t0 === 100% filter may already have this info which is good\n  const sourceDomainSize = sourceDomain[1] - sourceDomain[0];\n  // 10 -> 20: animationConfig domain d1 - d0 === animationConfig size\n  const destinationDomainSize = destinationDomain[1] - destinationDomain[0];\n  // scale animationConfig size using domain size\n  const scaledSourceDomainSize = (sourceDomainSize / destinationDomainSize) * 100;\n  // scale d0 - t0 using domain size to find starting point\n  const offset = sourceDomain[0] - destinationDomain[0];\n  const scaledOffset = (offset / destinationDomainSize) * 100;\n  return [scaledOffset, scaledSourceDomainSize + scaledOffset];\n}\n\nexport function getFilterScaledTimeline(filter, animationConfig): [number, number] | [] {\n  if (!(filter.syncedWithLayerTimeline && animationConfig?.domain)) {\n    return [];\n  }\n\n  return scaleSourceDomainToDestination(animationConfig.domain, filter.domain);\n}\n"
  },
  {
    "path": "src/utils/src/format.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {TOOLTIP_KEY, TICK_INTERVALS} from '@kepler.gl/constants';\nimport {TimeLabelFormat} from '@kepler.gl/types';\n\nexport function getDefaultTimeFormat(interval?: string): string {\n  const timeInterval = interval ? TICK_INTERVALS[interval] : {interval: 'none'};\n\n  switch ((timeInterval || {}).interval) {\n    case 'year':\n      // 2020\n      return 'YYYY';\n    case 'month':\n    case 'week':\n    case 'day':\n      return 'L';\n    case 'hour':\n      return 'L  H A';\n    case 'minute':\n      return 'L  LT';\n    case 'millisecond':\n      return 'L  LTS.SSS';\n\n    case 'second':\n    default:\n      return 'L  LTS';\n  }\n}\n\nexport const getFormatValue = (fmt: TimeLabelFormat): string | null => fmt[TOOLTIP_KEY];\n"
  },
  {
    "path": "src/utils/src/gl-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {setParameters} from '@luma.gl/core';\nimport {LAYER_BLENDINGS} from '@kepler.gl/constants';\nimport GL from '@luma.gl/constants';\n\nconst getGlConst = d => GL[d];\n\nexport function setLayerBlending(gl, layerBlending) {\n  const blending = LAYER_BLENDINGS[layerBlending];\n  const {blendFunc, blendEquation} = blending;\n\n  setParameters(gl, {\n    [GL.BLEND]: true,\n    ...(blendFunc\n      ? {\n          blendFunc: blendFunc.map(getGlConst),\n          blendEquation: Array.isArray(blendEquation)\n            ? blendEquation.map(getGlConst)\n            : getGlConst(blendEquation)\n        }\n      : {})\n  });\n}\n"
  },
  {
    "path": "src/utils/src/index.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// UTILS\n\nexport {\n  colorMaybeToRGB,\n  colorRangeBackwardCompatibility,\n  createLinearGradient,\n  hasColorMap,\n  hexToRgb,\n  isHexColor,\n  isRgbColor,\n  normalizeColor,\n  reverseColorRange,\n  rgbToHex,\n  addCustomPaletteColor,\n  removeCustomPaletteColor,\n  sortCustomPaletteColor,\n  updateCustomPaletteColor,\n  updateColorRangeBySelectedPalette,\n  paletteIsSteps,\n  paletteIsType,\n  paletteIsColorBlindSafe,\n  updateColorRangeByMatchingPalette,\n  updateCustomColorRangeByColorUI,\n  initializeCustomPalette\n} from './color-utils';\nexport {errorNotification} from './notifications-utils';\n\nexport {createNotification, exportImageError, successNotification} from './notifications-utils';\n\nexport {setStyleSheetBaseHref} from './dom-utils';\nexport {default as domtoimage} from './dom-to-image';\nexport {getFrequency, getMode, aggregate} from './aggregation';\nexport {\n  adjustValueToAnimationWindow,\n  getBinThresholds,\n  histogramFromThreshold,\n  histogramFromValues,\n  histogramFromDomain,\n  histogramFromOrdinal,\n  runGpuFilterForPlot,\n  updateTimeFilterPlotType\n} from './plot';\n// eslint-disable-next-line prettier/prettier\nexport * from './data-utils';\nexport type {FieldFormatter} from './data-utils';\nexport * from './strings';\nexport {\n  SAMPLE_TIMELINE,\n  TIMELINE_MODES,\n  TIME_INTERVALS_ORDERED,\n  LayerToFilterTimeInterval,\n  TileTimeInterval,\n  getTimelineFromAnimationConfig,\n  getTimelineFromFilter\n} from './time';\nexport {maybeHexToGeo, getPositionFromHexValue} from './position-utils';\n\nexport {\n  datasetColorMaker,\n  findDefaultColorField,\n  getFieldFormatLabels,\n  getFormatLabels,\n  validateInputData\n} from './dataset-utils';\nexport {exportMapToHTML} from './export-map-html';\nexport {\n  calculateExportImageSize,\n  convertToPng,\n  dataURItoBlob,\n  downloadFile,\n  exportHtml,\n  exportImage,\n  exportJson,\n  exportMap,\n  exportToJsonString,\n  default as exporters,\n  getMapJSON,\n  getScaleFromImageSize,\n  isMSEdge\n} from './export-utils';\nexport {getFormatValue, getDefaultTimeFormat} from './format';\nexport {setLayerBlending} from './gl-utils';\nexport {flattenMessages, mergeMessages} from './locale-utils';\nexport {isValidMapInfo} from './map-info-utils';\nexport {\n  editBottomMapStyle,\n  editTopMapStyle,\n  getDefaultLayerGroupVisibility,\n  getStyleDownloadUrl,\n  getStyleImageIcon,\n  mergeLayerGroupVisibility,\n  scaleMapStyleByResolution\n} from './map-style-utils/mapbox-gl-style-editor';\nexport {validateToken} from './mapbox-utils';\nexport {\n  default as useDimensions,\n  observeDimensions,\n  unobserveDimensions\n} from './observe-dimensions';\nexport type {Dimensions} from './observe-dimensions';\nexport {snapToMarks, getTimeBins} from './plot';\nexport * from './projection-utils';\nexport * from './split-map-utils';\nexport * from './utils';\n\nexport {\n  computeDeckEffects,\n  fixEffectOrder,\n  reorderEffectOrder,\n  validateEffectParameters\n} from './effect-utils';\n\n// Mapbox\nexport {\n  isStyleUsingMapboxTiles,\n  isStyleUsingOpenStreetMapTiles,\n  getBaseMapLibrary,\n  transformRequest\n} from './map-style-utils/mapbox-utils';\n\n// Map\nexport * from './map-utils';\n\nexport {\n  ArrowDataContainer,\n  arrowDataTypeToAnalyzerDataType,\n  arrowDataTypeToFieldType,\n  isArrowTable,\n  isArrowFixedSizeList,\n  isArrowStruct,\n  isArrowVector\n} from './arrow-data-container';\nexport type {DataContainerInterface} from './data-container-interface';\nexport {\n  DataForm,\n  createDataContainer,\n  createIndexedDataContainer,\n  getSampleData as getSampleContainerData\n} from './data-container-utils';\nexport * from './filter-utils';\nexport type {FilterChanged, FilterResult, dataValueAccessor} from './filter-utils';\n\nexport {\n  colorMapToColorBreaks,\n  colorBreaksToColorMap,\n  getLayerColorScale,\n  getLegendOfScale,\n  getLinearDomain,\n  getLogDomain,\n  getOrdinalDomain,\n  getQuantileDomain,\n  getScaleFunction,\n  getVisualChannelScaleByZoom,\n  initializeLayerColorMap,\n  isNumericColorBreaks,\n  isDomainStops,\n  isDomainQuantile,\n  getDomainStepsbyZoom,\n  getThresholdsFromQuantiles,\n  getQuantLabelFormat,\n  getHistogramDomain,\n  getQuantLegends,\n  getCategoricalColorMap,\n  getCategoricalColorScale,\n  initCustomPaletteByCustomScale,\n  colorMapToCategoricalColorBreaks,\n  resetCategoricalColorMapByIndex,\n  selectRestCategoricalColorMapByIndex,\n  removeCategoricalValueFromColorMap,\n  addCategoricalValuesToColorMap\n} from './data-scale-utils';\nexport type {ColorBreak, ColorBreakOrdinal, DomainQuantiles, DomainStops} from './data-scale-utils';\n\nexport {DataRow} from './data-row';\n\n// Application config\nexport {getApplicationConfig, initApplicationConfig} from './application-config';\nexport type {\n  KeplerApplicationConfig,\n  BaseMapLibraryConfig,\n  MapLibInstance,\n  GetMapRef\n} from './application-config';\nexport type {DatabaseAdapter, DatabaseConnection} from './application-config-types';\n\n// Browser utils\nexport {isAppleDevice} from './browser-utils';\n\nexport {default as quickInsertionSort} from './quick-insertion-sort';\n\nexport type {KeplerTableModel} from './types';\n"
  },
  {
    "path": "src/utils/src/indexed-data-container.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DataContainerInterface, RangeOptions} from './data-container-interface';\nimport {DataRow, SharedRowOptions} from './data-row';\n\n/**\n * @param dataContainer\n * @param indices\n * @param sharedRow\n * @returns\n */\nfunction* rowsIterator(\n  dataContainer: DataContainerInterface,\n  indices: number[],\n  sharedRow: SharedRowOptions\n): Generator<DataRow> {\n  const numRows = indices.length;\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    const mappedRowIndex = indices[rowIndex];\n    yield dataContainer.row(mappedRowIndex, sharedRow);\n  }\n}\n\n/**\n * @param dataContainer\n * @param indices\n * @param columnIndex\n * @returns\n */\nfunction* columnIterator(\n  dataContainer: DataContainerInterface,\n  indices: number[],\n  columnIndex: number\n): Generator<any> {\n  const numRows = indices.length;\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    const mappedRowIndex = indices[rowIndex];\n    yield dataContainer.valueAt(mappedRowIndex, columnIndex);\n  }\n}\n\n/**\n * A data container wrapper around another data container.\n * You have to pass an array of indices to reference rows in the parent data container.\n * For example indices [3, 4, 6, 8] means that IndexedDataContainer is going to have\n * 4 rows and row(2) points to 6th row in the referenced data container.\n */\nexport class IndexedDataContainer implements DataContainerInterface {\n  _parentDataContainer: DataContainerInterface;\n  _indices: number[];\n\n  constructor(parentDataContainer: DataContainerInterface, indices: number[]) {\n    this._parentDataContainer = parentDataContainer;\n    this._indices = indices;\n  }\n\n  numRows(): number {\n    return this._indices.length;\n  }\n\n  numColumns(): number {\n    return this._parentDataContainer.numColumns();\n  }\n\n  /**\n   * Remaps a local index to an index in the parent dataset\n   * @param rowIndex\n   * @returns number\n   */\n  _mappedRowIndex(rowIndex: number): number {\n    return this._indices[rowIndex];\n  }\n\n  valueAt(rowIndex: number, columnIndex: number): any {\n    return this._parentDataContainer.valueAt(this._mappedRowIndex(rowIndex), columnIndex);\n  }\n\n  row(rowIndex: number, sharedRow?: SharedRowOptions): DataRow {\n    return this._parentDataContainer.row(this._mappedRowIndex(rowIndex), sharedRow);\n  }\n\n  rowAsArray(rowIndex: number): any[] {\n    return this._parentDataContainer.rowAsArray(this._mappedRowIndex(rowIndex));\n  }\n\n  rows(sharedRow?: SharedRowOptions) {\n    return rowsIterator(this._parentDataContainer, this._indices, sharedRow);\n  }\n\n  column(columnIndex: number) {\n    return columnIterator(this._parentDataContainer, this._indices, columnIndex);\n  }\n\n  getPlainIndex(): number[] {\n    return this._indices.map((_, i) => i);\n  }\n\n  flattenData(): any[][] {\n    const tSharedRow = DataRow.createSharedRow(true);\n\n    return this._indices.map((_, i) => {\n      return this.row(i, tSharedRow).values();\n    }, this);\n  }\n\n  map<T>(\n    func: (row: DataRow, index: number) => T,\n    sharedRow?: SharedRowOptions,\n    options: RangeOptions = {}\n  ): T[] {\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      out.push(func(row, rowIndex));\n    }\n    return out;\n  }\n\n  mapIndex<T>(\n    func: ({index}: {index: number}, dc: DataContainerInterface) => T,\n    options: RangeOptions = {}\n  ): T[] {\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      out.push(func({index: this._mappedRowIndex(rowIndex)}, this._parentDataContainer));\n    }\n    return out;\n  }\n\n  find(\n    func: (row: DataRow, index: number) => boolean,\n    sharedRow?: SharedRowOptions\n  ): DataRow | undefined {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this.numRows(); ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      if (func(row, rowIndex)) {\n        return row;\n      }\n    }\n    return undefined;\n  }\n\n  reduce<T>(\n    func: (acc: T, row: DataRow, index: number) => T,\n    initialValue: T,\n    sharedRow?: SharedRowOptions\n  ): T {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this._indices.length; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      initialValue = func(initialValue, row, rowIndex);\n    }\n    return initialValue;\n  }\n}\n"
  },
  {
    "path": "src/utils/src/locale-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {isObject} from './utils';\nimport Console from 'global/console';\n\n// Flat messages since react-intl does not seem to support nested structures\n// Adapted from https://medium.com/siren-apparel-press/internationalization-and-localization-of-sirenapparel-eu-sirenapparel-us-and-sirenapparel-asia-ddee266066a2\nexport const flattenMessages = (nestedMessages, prefix = '') => {\n  return Object.keys(nestedMessages).reduce((messages, key) => {\n    const value = nestedMessages[key];\n    const prefixedKey = prefix ? `${prefix}.${key}` : key;\n    if (typeof value === 'string') {\n      messages[prefixedKey] = value;\n    } else {\n      Object.assign(messages, flattenMessages(value, prefixedKey));\n    }\n    return messages;\n  }, {});\n};\n\nexport const mergeMessages = (defaultMessages, userMessages) => {\n  if (!isObject(userMessages) || !isObject(userMessages.en)) {\n    Console.error(\n      'message should be an object and contain at least the `en` translation. Read more at https://docs.kepler.gl/docs/api-reference/localization'\n    );\n\n    return defaultMessages;\n  }\n\n  const userEnFlat = flattenMessages(userMessages.en);\n  return Object.keys(defaultMessages).reduce(\n    (acc, key) => ({\n      ...acc,\n      [key]:\n        key === 'en'\n          ? {...defaultMessages.en, ...userEnFlat}\n          : {...defaultMessages[key], ...userEnFlat, ...flattenMessages(userMessages[key] || {})}\n    }),\n    {}\n  );\n};\n"
  },
  {
    "path": "src/utils/src/map-info-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {MAP_INFO_CHARACTER} from '@kepler.gl/constants';\n\nexport function isValidMapInfo(mapInfo) {\n  return (\n    mapInfo.title.length &&\n    mapInfo.title.length <= MAP_INFO_CHARACTER.title &&\n    (!mapInfo.description.length || mapInfo.description.length <= MAP_INFO_CHARACTER.description)\n  );\n}\n"
  },
  {
    "path": "src/utils/src/map-style-utils/mapbox-gl-style-editor.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport memoize from 'lodash/memoize';\nimport clondDeep from 'lodash/cloneDeep';\nimport {\n  DEFAULT_LAYER_GROUPS,\n  DEFAULT_MAPBOX_API_URL,\n  NO_MAP_ID,\n  EMPTY_MAPBOX_STYLE\n} from '@kepler.gl/constants';\nimport {BaseMapStyle, LayerGroup, MapState} from '@kepler.gl/types';\n\nexport function getDefaultLayerGroupVisibility({layerGroups = []}: {layerGroups: LayerGroup[]}) {\n  return layerGroups.reduce(\n    (accu, layer) => ({\n      ...accu,\n      [layer.slug]: layer.defaultVisibility\n    }),\n    {}\n  );\n}\n\nconst resolver = ({\n  id,\n  visibleLayerGroups = {}\n}: {\n  id?: string;\n  mapStyle: BaseMapStyle;\n  visibleLayerGroups: {[id: string]: LayerGroup | boolean} | false;\n}) =>\n  `${id}:${Object.keys(visibleLayerGroups)\n    .filter(d => visibleLayerGroups[d])\n    .sort()\n    .join('-')}`;\n\n/**\n * Edit preset map style to keep only visible layers\n *\n * @param mapStyle - preset map style\n * @param visibleLayerGroups - visible layers of top map\n * @returns top map style\n */\nexport const editTopMapStyle = memoize(\n  ({\n    mapStyle,\n    visibleLayerGroups\n  }: {\n    id?: string;\n    mapStyle: BaseMapStyle;\n    visibleLayerGroups: {[id: string]: LayerGroup | boolean} | false;\n  }) => {\n    const visibleFilters = (mapStyle.layerGroups || [])\n      .filter(lg => visibleLayerGroups[lg.slug])\n      .map(lg => lg.filter);\n\n    // if top map\n    // keep only visible layers\n    // @ts-expect-error\n    const filteredLayers = mapStyle.style.layers.filter(layer =>\n      visibleFilters.some(match => match(layer))\n    );\n\n    return {\n      ...mapStyle.style,\n      layers: filteredLayers\n    };\n  },\n  resolver\n);\n\n/**\n * Edit preset map style to filter out invisible layers\n *\n * @param {Object} mapStyle - preset map style\n * @param {Object} visibleLayerGroups - visible layers of bottom map\n * @returns {Object} bottom map style\n */\nexport const editBottomMapStyle = memoize(({id, mapStyle, visibleLayerGroups}) => {\n  if (id === NO_MAP_ID) {\n    return EMPTY_MAPBOX_STYLE;\n  }\n\n  const invisibleFilters = (mapStyle.layerGroups || [])\n    .filter(lg => !visibleLayerGroups[lg.slug])\n    .map(lg => lg.filter);\n\n  // if bottom map\n  // filter out invisible layers\n  const filteredLayers = mapStyle.style.layers.filter(layer =>\n    invisibleFilters.every(match => !match(layer))\n  );\n\n  return {\n    ...mapStyle.style,\n    layers: filteredLayers\n  };\n}, resolver);\n\nexport function getStyleDownloadUrl(styleUrl, accessToken, mapboxApiUrl) {\n  if (styleUrl.startsWith('http')) {\n    return styleUrl;\n  }\n\n  // mapbox://styles/jckr/cjhcl0lxv13di2rpfoytdbdyj\n  if (styleUrl.startsWith('mapbox://styles')) {\n    const styleId = styleUrl.replace('mapbox://styles/', '');\n\n    // https://api.mapbox.com/styles/v1/heshan0131/cjg1bfumo1cwm2rlrjxkinfgw?pluginName=Keplergl&access_token=<token>\n    return `${\n      mapboxApiUrl || DEFAULT_MAPBOX_API_URL\n    }/styles/v1/${styleId}?pluginName=Keplergl&access_token=${accessToken}`;\n  }\n\n  // style url not recognized\n  return null;\n}\n\n/**\n * Generate static map image from style Url to be used as icon\n * @param param\n * @param param.styleUrl\n * @param param.mapboxApiAccessToken\n * @param param.mapboxApiUrl\n * @param param.mapState\n * @param param.mapW\n * @param param.mapH\n */\nexport function getStyleImageIcon({\n  styleUrl,\n  mapboxApiAccessToken,\n  mapboxApiUrl = DEFAULT_MAPBOX_API_URL,\n  mapState = {\n    longitude: -122.3391,\n    latitude: 37.7922,\n    zoom: 9\n  },\n  mapW = 400,\n  mapH = 300\n}: {\n  styleUrl: string;\n  mapboxApiAccessToken: string;\n  mapboxApiUrl?: string;\n  mapState?: Partial<MapState>;\n  mapW?: number;\n  mapH?: number;\n}) {\n  const styleId = styleUrl.replace('mapbox://styles/', '');\n\n  return (\n    `${mapboxApiUrl}/styles/v1/${styleId}/static/` +\n    `${mapState.longitude},${mapState.latitude},${mapState.zoom},0,0/` +\n    `${mapW}x${mapH}` +\n    `?access_token=${mapboxApiAccessToken}&logo=false&attribution=false`\n  );\n}\n\nexport function scaleMapStyleByResolution(mapboxStyle, scale) {\n  if (scale !== 1 && mapboxStyle) {\n    const labelLayerGroup = DEFAULT_LAYER_GROUPS.find(lg => lg.slug === 'label');\n    // @ts-ignore\n    const {filter: labelLayerFilter} = labelLayerGroup;\n    const zoomOffset = Math.log2(scale);\n\n    const copyStyle = clondDeep(mapboxStyle);\n    (copyStyle.layers || []).forEach(d => {\n      // edit minzoom and maxzoom\n      if (d.maxzoom) {\n        d.maxzoom = Math.max(d.maxzoom + zoomOffset, 1);\n\n        // The maximum zoom is 24\n        // https://github.com/visgl/react-map-gl/blob/master/docs/api-reference/map.md#maxzoom-number-maxzoom\n        if (d.maxzoom > 24) {\n          d.maxzoom = 24;\n        }\n      }\n\n      if (d.minzoom) {\n        d.minzoom = Math.max(d.minzoom + zoomOffset, 1);\n      }\n\n      // edit text size\n      if (labelLayerFilter(d)) {\n        if (d.layout && d.layout['text-size'] && Array.isArray(d.layout['text-size'].stops)) {\n          d.layout['text-size'].stops.forEach(stop => {\n            // zoom\n            stop[0] = Math.max(stop[0] + zoomOffset, 1);\n            // size\n            stop[1] *= scale;\n          });\n        }\n      }\n    });\n\n    return copyStyle;\n  }\n\n  return mapboxStyle;\n}\n\n/**\n * When switch to a new style, try to keep current layer group visibility\n * by merging default and current\n * @param {Object} defaultLayerGroup\n * @param {Object} currentLayerGroup\n * @return {Object} mergedLayerGroups\n */\nexport function mergeLayerGroupVisibility(defaultLayerGroup, currentLayerGroup) {\n  return Object.keys(defaultLayerGroup).reduce(\n    (accu, key) => ({\n      ...accu,\n      ...(Object.prototype.hasOwnProperty.call(currentLayerGroup, key)\n        ? {[key]: currentLayerGroup[key]}\n        : {})\n    }),\n    defaultLayerGroup\n  );\n}\n"
  },
  {
    "path": "src/utils/src/map-style-utils/mapbox-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// import {isMapboxURL, transformMapboxUrl} from 'maplibregl-mapbox-request-transformer';\n\nimport {MAP_LIB_OPTIONS, BaseMapLibraryType} from '@kepler.gl/constants';\n\n/**\n * Determines whether a Map Style is using Mapbox Tiles\n * @param {any} mapStyle the mapStyle to check\n * @returns true if the style is using Mapbox tiles\n */\nexport function isStyleUsingMapboxTiles(mapStyle) {\n  const sources = mapStyle.stylesheet?.sources || {};\n\n  return Object.keys(sources).some(sourceId => {\n    const {url, tiles} = sources[sourceId] || {};\n\n    if (url) {\n      return url.toLowerCase().startsWith('mapbox://');\n    }\n\n    if (tiles) {\n      return tiles.some(tileUrl => tileUrl.toLowerCase().startsWith('mapbox://'));\n    }\n\n    return false;\n  });\n}\n\nexport function isStyleUsingOpenStreetMapTiles(mapStyle: any) {\n  const sources = mapStyle?.stylesheet?.sources || {};\n  return Object.keys(sources).some(sourceId => {\n    const {attribution} = sources[sourceId] || {};\n    if (typeof attribution?.attribution === 'string') {\n      return attribution.attribution.includes('openstreetmap.org');\n    }\n    return false;\n  });\n}\n\n/**\n * Transform mapbox protocol so can be used with maplibre\n * @param mapboxKey mapbox api key\n * @returns transformed url\n */\nexport const transformRequest = (\n  _mapboxKey: string\n): ((url: string, resourceType: string) => {url: string}) => {\n  return (url: string, _resourceType: string) => {\n    /*\n    if (isMapboxURL(url)) {\n      // ! TODO - use MapBox directly?\n      return transformMapboxUrl(url, resourceType, mapboxKey);\n    }\n    */\n\n    return {url};\n  };\n};\n\ntype StyleWithSources = {\n  sources?: Record<string, any>;\n};\n\nexport const getBaseMapLibrary = (baseMapStyle?: {\n  url?: string | null;\n  style?: any;\n}): BaseMapLibraryType => {\n  if (baseMapStyle) {\n    if (baseMapStyle.url?.startsWith('mapbox://') || baseMapStyle.url?.includes('mapbox.com')) {\n      return MAP_LIB_OPTIONS.MAPBOX;\n    }\n\n    if ((baseMapStyle.style as StyleWithSources)?.sources?.['mapbox'])\n      return MAP_LIB_OPTIONS.MAPBOX;\n  }\n\n  return MAP_LIB_OPTIONS.MAPLIBRE;\n};\n"
  },
  {
    "path": "src/utils/src/map-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport WebMercatorViewport from 'viewport-mercator-project';\n\nimport {TRANSITION_DURATION} from '@kepler.gl/constants';\nimport {SplitMapLayers, SplitMap, Viewport, MapState} from '@kepler.gl/types';\n\nimport {validateLatitude, validateLongitude} from './data-utils';\n\n/**\n * Validates a ViewPort object.\n * It retains all properties of the original ViewPort object,\n * but ensures that the latitude is within the defined bounds.\n * @param viewport - The ViewPort object to validate.\n * @returns A new ViewPort object with validated latitude.\n */\nexport const validateViewPort = <T extends Pick<Viewport, 'latitude' | 'longitude'>>(\n  viewport: T\n): T => {\n  return {\n    ...viewport,\n    // make sure to process latitude to avoid 90, -90 values\n    // Uncaught Error: Pixel project matrix not invertible\n    ...(viewport.latitude ? {latitude: validateLatitude(viewport.latitude)} : {}),\n    ...(viewport.longitude ? {longitude: validateLongitude(viewport.longitude)} : {})\n  };\n};\n\nexport const onViewPortChange = (\n  viewState: Viewport,\n  onUpdateMap: (next: any, mapIndex: number) => any,\n  onViewStateChange?: (next: any) => void | null,\n  primary = false,\n  mapIndex = 0\n): void => {\n  const {width = 0, height = 0, ...restViewState} = viewState;\n  // react-map-gl sends 0,0 dimensions during initialization\n  // after we have received proper dimensions from observeDimensions\n  const next = {\n    ...(width > 0 && height > 0 ? viewState : restViewState),\n    // enabling transition in two maps may lead to endless update loops\n    transitionDuration: primary ? TRANSITION_DURATION : 0\n  };\n  if (onViewStateChange && typeof onViewStateChange === 'function') {\n    onViewStateChange(next);\n  }\n\n  onUpdateMap(next, mapIndex);\n};\n\nexport const getMapLayersFromSplitMaps = (\n  splitMaps: SplitMap[],\n  mapIndex?: number\n): SplitMapLayers | undefined | null => {\n  return splitMaps[mapIndex || 0]?.layers;\n};\n\n/**\n * Generates a viewport from a map state.\n * @param mapState\n * @returns A viewport.\n */\nexport const getViewportFromMapState = (mapState: MapState): Viewport => {\n  // Make sure we capture error\n  // e.g. Error message: \"Pixel project matrix not invertible\"\n  let viewPort;\n  try {\n    viewPort = new WebMercatorViewport(mapState);\n  } catch {\n    // catch error and fallback to default map state\n    viewPort = new WebMercatorViewport(validateViewPort(mapState));\n  }\n\n  return viewPort;\n};\n"
  },
  {
    "path": "src/utils/src/mapbox-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * This method validates mapbox token.\n * This token provides a simple synchronous validation\n * @param {string} token the Mapbox token to validate\n * @return {boolean} true if token is valid, false otherwise\n */\nexport const validateToken = (token: string): boolean => (token || '') !== '';\n"
  },
  {
    "path": "src/utils/src/noop.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default function noop() {\n  return;\n}\n"
  },
  {
    "path": "src/utils/src/notifications-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {generateHashId} from '@kepler.gl/common-utils';\n\nimport {\n  DEFAULT_NOTIFICATION_MESSAGE,\n  DEFAULT_NOTIFICATION_TOPICS,\n  DEFAULT_NOTIFICATION_TYPES,\n  DEFAULT_UUID_COUNT,\n  BUG_REPORT_LINK\n} from '@kepler.gl/constants';\n\nexport type Notification = Record<string, any> & {\n  id: string;\n  message: string;\n  type: 'info' | 'error' | 'warning' | 'success';\n  topic: 'global' | 'file';\n  count: number;\n};\n\n/**\n * Creates a notification\n */\nexport function createNotification({\n  message = DEFAULT_NOTIFICATION_MESSAGE,\n  type = DEFAULT_NOTIFICATION_TYPES.info,\n  topic = DEFAULT_NOTIFICATION_TOPICS.global,\n  id = generateHashId(DEFAULT_UUID_COUNT),\n  count = 1,\n  ...options\n}: Partial<Notification>): Notification {\n  return {\n    ...options,\n    id,\n    message,\n    type,\n    topic,\n    count: count > 99 ? 99 : count // no more than 2 digits\n  };\n}\n\n/**\n * Creates an error notification\n * @param options\n * @returns {{topic, id, message, type: (null|number)}}\n */\nexport const errorNotification = options => ({\n  ...createNotification(options),\n  type: DEFAULT_NOTIFICATION_TYPES.error\n});\n\n/**\n * Creates a success notification\n * @param options\n * @returns {{topic, id, message, type: null}}\n */\nexport const successNotification = options => ({\n  ...createNotification(options),\n  type: DEFAULT_NOTIFICATION_TYPES.success\n});\n\nexport const exportImageError = options =>\n  errorNotification({\n    ...options,\n    id: 'EXPORT_IMAGE_ERROR_ID',\n    topic: DEFAULT_NOTIFICATION_TOPICS.global,\n    message: `Failed to export image, please take a screenshot of the javascript console, report the with [this link](${BUG_REPORT_LINK}) `\n  });\n"
  },
  {
    "path": "src/utils/src/observe-dimensions.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport throttle from 'lodash/throttle';\nimport {useEffect, useRef, useState, RefObject} from 'react';\nimport ResizeObserver from 'resize-observer-polyfill';\n\nexport interface Dimensions {\n  readonly height: number;\n  readonly width: number;\n}\n\nconst DEFAULT_THROTTLE_DELAY = 100;\n\n// Using a single ResizeObserver for all elements can be 10x\n// more performant than using a separate ResizeObserver per element\n// https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/z6ienONUb5A/F5-VcUZtBAAJ\nlet _observerRegistry;\n\nfunction getObserverRegistry() {\n  if (_observerRegistry === undefined) {\n    const callbacks = new Map();\n    const resizeObserver = new ResizeObserver((entries, observer) => {\n      for (const entry of entries) {\n        callbacks.get(entry.target)?.(entry, observer);\n      }\n    });\n    _observerRegistry = {\n      subscribe(target, callback) {\n        if (target instanceof Element) {\n          resizeObserver.observe(target);\n          callbacks.set(target, callback);\n        }\n      },\n      unsubscribe(target) {\n        if (target instanceof Element) {\n          resizeObserver.unobserve(target);\n          callbacks.delete(target);\n        }\n      }\n    };\n  }\n  return _observerRegistry;\n}\n\nexport function observeDimensions(\n  target: Element,\n  handleResize: (size: Dimensions | null) => void,\n  throttleDelay = DEFAULT_THROTTLE_DELAY\n) {\n  const registry = getObserverRegistry();\n  const handler = throttleDelay > 0 ? throttle(handleResize, throttleDelay) : handleResize;\n  registry.subscribe(target, entry => handler(getSize(target, entry)));\n}\n\nexport function unobserveDimensions(target: Element) {\n  const registry = getObserverRegistry();\n  registry.unsubscribe(target);\n}\n\nfunction getSize(node, entry): Dimensions | null {\n  if (entry.contentRect) {\n    const {width, height} = entry.contentRect;\n    return {width, height};\n  }\n  if (node.getBoundingClientRect) {\n    const {width, height} = node.getBoundingClientRect();\n    return {width, height};\n  }\n  return null;\n}\n\n/**\n * Usage example:\n * const [ref, dimensions] = useDimensions<HTMLDivElement>();\n *\n * @param throttleDelay\n * @returns\n */\nexport function useDimensions<T extends Element>(\n  nodeRef?: RefObject<T>,\n  throttleDelay = DEFAULT_THROTTLE_DELAY\n): [RefObject<T>, Dimensions | null] {\n  let ref = useRef<T>(null);\n  if (nodeRef) {\n    ref = nodeRef;\n  }\n  const [size, setSize] = useState(null);\n\n  useEffect(() => {\n    const {current} = ref || {};\n    if (!current) {\n      return;\n    }\n\n    let didUnobserve = false;\n    observeDimensions(\n      current,\n      entry => {\n        if (didUnobserve) return;\n        const newSize = getSize(current, entry);\n        if (newSize) {\n          // @ts-ignore\n          setSize(newSize);\n        }\n      },\n      throttleDelay\n    );\n    return () => {\n      didUnobserve = true;\n      unobserveDimensions(current);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [throttleDelay, ref?.current]);\n\n  return [ref as RefObject<T>, size];\n}\n\nexport default useDimensions;\n"
  },
  {
    "path": "src/utils/src/plot.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {bisectLeft, extent, histogram as d3Histogram, ticks} from 'd3-array';\nimport isEqual from 'lodash/isEqual';\nimport {getFilterMappedValue, getInitialInterval, intervalToFunction} from './time';\nimport moment from 'moment';\nimport {\n  Bin,\n  TimeBins,\n  Millisecond,\n  TimeRangeFilter,\n  RangeFilter,\n  PlotType,\n  Filter,\n  LineChart,\n  Field,\n  ValueOf,\n  LineDatum\n} from '@kepler.gl/types';\nimport {notNullorUndefined} from '@kepler.gl/common-utils';\nimport {\n  ANIMATION_WINDOW,\n  BINS,\n  durationDay,\n  TIME_AGGREGATION,\n  AGGREGATION_TYPES,\n  PLOT_TYPES,\n  AggregationTypes\n} from '@kepler.gl/constants';\n\nimport {isNumber, roundValToStep} from './data-utils';\nimport {aggregate, AGGREGATION_NAME} from './aggregation';\nimport {capitalizeFirstLetter} from './strings';\nimport {getDefaultTimeFormat} from './format';\nimport {rgbToHex} from './color-utils';\nimport {DataContainerInterface} from '.';\nimport {KeplerTableModel} from './types';\n\n// TODO kepler-table module isn't accessible from utils. Add compatible interface to types\ntype Datasets = any;\n\n/**\n *\n * @param thresholds\n * @param values\n * @param indexes\n */\nexport function histogramFromThreshold(\n  thresholds: number[],\n  values: number[],\n  valueAccessor?: (d: unknown) => number,\n  filterEmptyBins = true\n): Bin[] {\n  const getBins = d3Histogram()\n    .domain([thresholds[0], thresholds[thresholds.length - 1]])\n    .thresholds(thresholds);\n\n  if (valueAccessor) {\n    getBins.value(valueAccessor);\n  }\n\n  // @ts-ignore\n  const bins = getBins(values).map(bin => ({\n    count: bin.length,\n    indexes: bin,\n    x0: bin.x0,\n    x1: bin.x1\n  }));\n\n  // d3-histogram ignores threshold values outside the domain\n  // The first bin.x0 is always equal to the minimum domain value, and the last bin.x1 is always equal to the maximum domain value.\n\n  // bins[0].x0 = thresholds[0];\n  // bins[bins.length - 1].x1 = thresholds[thresholds.length - 1];\n\n  // @ts-ignore\n  return filterEmptyBins ? bins.filter(b => b.count > 0) : bins;\n}\n\n/**\n *\n * @param values\n * @param numBins\n * @param valueAccessor\n */\nexport function histogramFromValues(\n  values: (Millisecond | null | number)[],\n  numBins: number,\n  valueAccessor?: (d: number) => number\n) {\n  const getBins = d3Histogram().thresholds(numBins);\n\n  if (valueAccessor) {\n    getBins.value(valueAccessor);\n  }\n\n  // @ts-ignore d3-array types doesn't match\n  return getBins(values)\n    .map(bin => ({\n      count: bin.length,\n      indexes: bin,\n      x0: bin.x0,\n      x1: bin.x1\n    }))\n    .filter(b => {\n      const {x0, x1} = b;\n      return isNumber(x0) && isNumber(x1);\n    });\n}\n\nexport function histogramFromOrdinal(\n  domain: [string],\n  values: (Millisecond | null | number)[],\n  valueAccessor?: (d: unknown) => string\n): Bin[] {\n  // @ts-expect-error to typed to expect strings\n  const getBins = d3Histogram().thresholds(domain);\n  if (valueAccessor) {\n    // @ts-expect-error to typed to expect strings\n    getBins.value(valueAccessor);\n  }\n\n  // @ts-expect-error null values aren't expected\n  const bins = getBins(values);\n\n  // @ts-ignore d3-array types doesn't match\n  return bins.map(bin => ({\n    count: bin.length,\n    indexes: bin,\n    x0: bin.x0,\n    x1: bin.x0\n  }));\n}\n\n/**\n *\n * @param domain\n * @param values\n * @param numBins\n * @param valueAccessor\n */\nexport function histogramFromDomain(\n  domain: [number, number],\n  values: (Millisecond | null | number)[],\n  numBins: number,\n  valueAccessor?: (d: unknown) => number\n): Bin[] {\n  const getBins = d3Histogram().thresholds(ticks(domain[0], domain[1], numBins)).domain(domain);\n  if (valueAccessor) {\n    getBins.value(valueAccessor);\n  }\n\n  // @ts-ignore d3-array types doesn't match\n  return getBins(values).map(bin => ({\n    count: bin.length,\n    indexes: bin,\n    x0: bin.x0,\n    x1: bin.x1\n  }));\n}\n\n/**\n * @param filter\n * @param datasets\n * @param interval\n */\nexport function getTimeBins(\n  filter: TimeRangeFilter,\n  datasets: Datasets,\n  interval: PlotType['interval']\n): TimeBins {\n  let bins = filter.timeBins || {};\n\n  filter.dataId.forEach(dataId => {\n    // reuse bins if filterData did not change\n    if (bins[dataId] && bins[dataId][interval]) {\n      return;\n    }\n    const dataset = datasets[dataId];\n\n    // do not apply current filter\n    const indexes = runGpuFilterForPlot(dataset, filter);\n\n    bins = {\n      ...bins,\n      [dataId]: {\n        ...bins[dataId],\n        [interval]: binByTime(indexes, dataset, interval, filter)\n      }\n    };\n  });\n\n  return bins;\n}\n\nexport function binByTime(indexes, dataset, interval, filter) {\n  // gpuFilters need to be apply to filteredIndex\n  const mappedValue = getFilterMappedValue(dataset, filter);\n  if (!mappedValue) {\n    return null;\n  }\n  const intervalBins = getBinThresholds(interval, filter.domain);\n  const valueAccessor = idx => mappedValue[idx];\n  const bins = histogramFromThreshold(intervalBins, indexes, valueAccessor);\n\n  return bins;\n}\n\nexport function getBinThresholds(interval: string, domain: number[]): number[] {\n  const timeInterval = intervalToFunction(interval);\n  const [t0, t1] = domain;\n  const floor = timeInterval.floor(t0).getTime();\n  const ceiling = timeInterval.ceil(t1).getTime();\n\n  if (!timeInterval) {\n    // if time interval is not defined\n    // this should not happen\n    return [t0, t0 + durationDay];\n  }\n  const binThresholds = timeInterval.range(floor, ceiling + 1).map(t => moment.utc(t).valueOf());\n  const lastStep = binThresholds[binThresholds.length - 1];\n  if (lastStep === t1) {\n    // when last step equal to domain max, add one more step\n    binThresholds.push(moment.utc(timeInterval.offset(lastStep)).valueOf());\n  }\n\n  return binThresholds;\n}\n\n/**\n * Run GPU filter on current filter result to generate indexes for ploting chart\n * Skip ruuning for the same field\n * @param dataset\n * @param filter\n */\nexport function runGpuFilterForPlot<K extends KeplerTableModel<K, L>, L>(\n  dataset: K,\n  filter?: Filter\n): number[] {\n  const skipIndexes = getSkipIndexes(dataset, filter);\n\n  const {\n    gpuFilter: {filterValueUpdateTriggers, filterRange, filterValueAccessor},\n    filteredIndex\n  } = dataset;\n  const getFilterValue = filterValueAccessor(dataset.dataContainer)();\n\n  const allChannels = Object.keys(filterValueUpdateTriggers)\n    .map((_, i) => i)\n    .filter(i => Object.values(filterValueUpdateTriggers)[i]);\n  const skipAll = !allChannels.filter(i => !skipIndexes.includes(i)).length;\n  if (skipAll) {\n    return filteredIndex;\n  }\n\n  const filterData = getFilterDataFunc(\n    filterRange,\n    getFilterValue,\n    dataset.dataContainer,\n    skipIndexes\n  );\n\n  return filteredIndex.filter(filterData);\n}\n\nfunction getSkipIndexes(dataset, filter) {\n  // array of gpu filter names\n  if (!filter) {\n    return [];\n  }\n  const gpuFilters = Object.values(dataset.gpuFilter.filterValueUpdateTriggers) as ({\n    name: string;\n  } | null)[];\n  const valueIndex = filter.dataId.findIndex(id => id === dataset.id);\n  const filterColumn = filter.name[valueIndex];\n\n  return gpuFilters.reduce((accu, item, idx) => {\n    if (item && filterColumn === item.name) {\n      accu.push(idx);\n    }\n    return accu;\n  }, [] as number[]);\n}\n\nexport function getFilterDataFunc(\n  filterRange,\n  getFilterValue,\n  dataContainer: DataContainerInterface,\n  skips\n) {\n  return index =>\n    getFilterValue({index}).every(\n      (val, i) => skips.includes(i) || (val >= filterRange[i][0] && val <= filterRange[i][1])\n    );\n}\n\nexport function validBin(b) {\n  return b.x0 !== undefined && b.x1 !== undefined;\n}\n\n/**\n * Use in slider, given a number and an array of numbers, return the nears number from the array.\n * Takes a value, timesteps and return the actual step.\n * @param value\n * @param marks\n */\nexport function snapToMarks(value: number, marks: number[]): number {\n  // always use bin x0\n  if (!marks.length) {\n    // @ts-expect-error looking at the usage null return value isn't expected and requires extra handling in a lot of places\n    return null;\n  }\n  const i = bisectLeft(marks, value);\n  if (i === 0) {\n    return marks[i];\n  } else if (i === marks.length) {\n    return marks[i - 1];\n  }\n  const idx = marks[i] - value < value - marks[i - 1] ? i : i - 1;\n  return marks[idx];\n}\n\nexport function normalizeValue(val, minValue, step, marks) {\n  if (marks && marks.length) {\n    return snapToMarks(val, marks);\n  }\n\n  return roundValToStep(minValue, step, val);\n}\n\nexport function isPercentField(field) {\n  return field.metadata && field.metadata.numerator && field.metadata.denominator;\n}\n\nexport function updateAggregationByField(field: Field, aggregation: ValueOf<AggregationTypes>) {\n  // shouldn't apply sum to percent fiele type\n  // default aggregation is average\n  return field && isPercentField(field)\n    ? AGGREGATION_TYPES.average\n    : aggregation || AGGREGATION_TYPES.average;\n}\n\nconst getAgregationType = (field, aggregation) => {\n  if (isPercentField(field)) {\n    return 'mean_of_percent';\n  }\n  return aggregation;\n};\n\nconst getAggregationAccessor = (field, dataContainer: DataContainerInterface, fields) => {\n  if (isPercentField(field)) {\n    const numeratorIdx = fields.findIndex(f => f.name === field.metadata.numerator);\n    const denominatorIdx = fields.findIndex(f => f.name === field.metadata.denominator);\n\n    return {\n      getNumerator: i => dataContainer.valueAt(i, numeratorIdx),\n      getDenominator: i => dataContainer.valueAt(i, denominatorIdx)\n    };\n  }\n\n  return i => field.valueAccessor({index: i});\n};\n\nexport const getValueAggrFunc = (\n  field: Field | string | null,\n  aggregation: string,\n  dataset: KeplerTableModel<any, any>\n): ((bin: Bin) => number) => {\n  const {dataContainer, fields} = dataset;\n\n  // The passed-in field might not have all the fields set (e.g. valueAccessor)\n  const datasetField = fields.find(\n    f => field && (f.name === field || f.name === (field as Field).name)\n  );\n\n  return datasetField && aggregation\n    ? bin =>\n        aggregate(\n          bin.indexes,\n          getAgregationType(datasetField, aggregation),\n          // @ts-expect-error can return {getNumerator, getDenominator}\n          getAggregationAccessor(datasetField, dataContainer, fields)\n        )\n    : bin => bin.count;\n};\n\nexport const getAggregationOptiosnBasedOnField = field => {\n  if (isPercentField(field)) {\n    // don't show sum\n    return TIME_AGGREGATION.filter(({id}) => id !== AGGREGATION_TYPES.sum);\n  }\n  return TIME_AGGREGATION;\n};\n\nfunction getDelta(\n  bins: LineDatum[],\n  y: number,\n  _interval: PlotType['interval']\n): Partial<LineDatum> & {delta: 'last'; pct: number | null} {\n  // if (WOW[interval]) return getWow(bins, y, interval);\n  const lastBin = bins[bins.length - 1];\n\n  return {\n    delta: 'last',\n    pct: lastBin ? getPctChange(y, lastBin.y) : null\n  };\n}\n\nexport function getPctChange(y: unknown, y0: unknown): number | null {\n  if (Number.isFinite(y) && Number.isFinite(y0) && y0 !== 0) {\n    return ((y as number) - (y0 as number)) / (y0 as number);\n  }\n  return null;\n}\n\n/**\n *\n * @param datasets\n * @param filter\n */\nexport function getLineChart(datasets: Datasets, filter: Filter): LineChart {\n  const {dataId, yAxis, plotType, lineChart} = filter;\n  const {aggregation, interval} = plotType;\n  const seriesDataId = dataId[0];\n  const bins = (filter as TimeRangeFilter).timeBins?.[seriesDataId]?.[interval];\n\n  if (\n    lineChart &&\n    lineChart.aggregation === aggregation &&\n    lineChart.interval === interval &&\n    lineChart.yAxis === yAxis?.name &&\n    // we need to make sure we validate bins because of cross filter data changes\n    isEqual(bins, lineChart?.bins)\n  ) {\n    // don't update lineChart if plotType hasn't change\n    return lineChart;\n  }\n\n  const dataset = datasets[seriesDataId];\n  const getYValue = getValueAggrFunc(yAxis, aggregation, dataset);\n\n  const init: LineDatum[] = [];\n  const series = (bins || []).reduce((accu, bin) => {\n    const y = getYValue(bin);\n    const delta = getDelta(accu, y, interval);\n    accu.push({\n      x: bin.x0,\n      y,\n      ...delta\n    });\n    return accu;\n  }, init);\n\n  const yDomain = extent<{y: any}>(series, d => d.y);\n  const xDomain = bins ? [bins[0].x0, bins[bins.length - 1].x1] : [];\n\n  // treat missing data as another series\n  const split = splitSeries(series);\n  const aggrName = AGGREGATION_NAME[aggregation];\n\n  return {\n    // @ts-ignore\n    yDomain,\n    // @ts-ignore\n    xDomain,\n    interval,\n    aggregation,\n    // @ts-ignore\n    series: split,\n    title: `${aggrName}${' of '}${yAxis ? yAxis.name : 'Count'}`,\n    fieldType: yAxis ? yAxis.type : 'integer',\n    yAxis: yAxis ? yAxis.name : null,\n    allTime: {\n      title: `All Time Average`,\n      value: aggregate(series, AGGREGATION_TYPES.average, d => d.y)\n    },\n    // @ts-expect-error bins is Bins[], not a Bins map. Refactor to use correct types.\n    bins\n  };\n}\n\n// split into multiple series when see missing data\nexport function splitSeries(series) {\n  const lines: any[] = [];\n  let temp: any[] = [];\n  for (let i = 0; i < series.length; i++) {\n    const d = series[i];\n    if (!notNullorUndefined(d.y) && temp.length) {\n      // ends temp\n      lines.push(temp);\n      temp = [];\n    } else if (notNullorUndefined(d.y)) {\n      temp.push(d);\n    }\n\n    if (i === series.length - 1 && temp.length) {\n      lines.push(temp);\n    }\n  }\n\n  const markers = lines.length > 1 ? series.filter(d => notNullorUndefined(d.y)) : [];\n\n  return {lines, markers};\n}\n\ntype MinVisStateForAnimationWindow = {\n  datasets: Datasets;\n};\n\nexport function adjustValueToAnimationWindow<S extends MinVisStateForAnimationWindow>(\n  state: S,\n  filter: TimeRangeFilter\n) {\n  const {\n    plotType,\n    value: [value0, value1],\n    animationWindow\n  } = filter;\n\n  const interval = plotType.interval || getInitialInterval(filter, state.datasets);\n  const bins = getTimeBins(filter, state.datasets, interval);\n  const datasetBins = bins && Object.keys(bins).length && Object.values(bins)[0][interval];\n  const thresholds = (datasetBins || []).map(b => b.x0);\n\n  let val0 = value0;\n  let val1 = value1;\n  let idx;\n  if (animationWindow === ANIMATION_WINDOW.interval) {\n    val0 = snapToMarks(value1, thresholds);\n    idx = thresholds.indexOf(val0);\n    val1 = idx > -1 ? datasetBins[idx].x1 : NaN;\n  } else {\n    // fit current value to window\n    val0 = snapToMarks(value0, thresholds);\n    val1 = snapToMarks(value1, thresholds);\n\n    if (val0 === val1) {\n      idx = thresholds.indexOf(val0);\n      if (idx === thresholds.length - 1) {\n        val0 = thresholds[idx - 1];\n      } else {\n        val1 = thresholds[idx + 1];\n      }\n    }\n  }\n\n  const updatedFilter = {\n    ...filter,\n    plotType: {\n      ...filter.plotType,\n      interval\n    },\n    timeBins: bins,\n    value: [val0, val1]\n  };\n\n  return updatedFilter;\n}\n\n/**\n * Create or update colors for a filter plot\n * @param filter\n * @param datasets\n * @param oldColorsByDataId\n */\nfunction getFilterPlotColorsByDataId(filter, datasets, oldColorsByDataId) {\n  let colorsByDataId = oldColorsByDataId || {};\n  for (const dataId of filter.dataId) {\n    if (!colorsByDataId[dataId] && datasets[dataId]) {\n      colorsByDataId = {\n        ...colorsByDataId,\n        [dataId]: rgbToHex(datasets[dataId].color)\n      };\n    }\n  }\n  return colorsByDataId;\n}\n\n/**\n *\n * @param filter\n * @param plotType\n * @param datasets\n * @param dataId\n */\nexport function updateTimeFilterPlotType(\n  filter: TimeRangeFilter,\n  plotType: TimeRangeFilter['plotType'],\n  datasets: Datasets,\n  _dataId?: string\n): TimeRangeFilter {\n  let nextFilter = filter;\n  let nextPlotType = plotType;\n  if (typeof nextPlotType !== 'object' || !nextPlotType.aggregation || !nextPlotType.interval) {\n    nextPlotType = getDefaultPlotType(filter, datasets);\n  }\n\n  if (filter.dataId.length > 1) {\n    nextPlotType = {\n      ...nextPlotType,\n      colorsByDataId: getFilterPlotColorsByDataId(filter, datasets, nextPlotType.colorsByDataId)\n    };\n  }\n  nextFilter = {\n    ...nextFilter,\n    plotType: nextPlotType\n  };\n\n  const bins = getTimeBins(nextFilter, datasets, nextPlotType.interval);\n\n  nextFilter = {\n    ...nextFilter,\n    timeBins: bins\n  };\n\n  if (plotType.type === PLOT_TYPES.histogram) {\n    // Histogram is calculated and memoized in the chart itself\n  } else if (plotType.type === PLOT_TYPES.lineChart) {\n    // we should be able to move this into its own component so react will do the shallow comparison for us.\n    nextFilter = {\n      ...nextFilter,\n      lineChart: getLineChart(datasets, nextFilter)\n    };\n  }\n\n  return nextFilter;\n}\n\nexport function getRangeFilterBins(filter, datasets, numBins) {\n  const {domain} = filter;\n  if (!filter.dataId) return null;\n\n  return filter.dataId.reduce((acc, dataId, datasetIdx) => {\n    if (filter.bins?.[dataId]) {\n      // don't recalculate bins\n      acc[dataId] = filter.bins[dataId];\n      return acc;\n    }\n    const fieldName = filter.name[datasetIdx];\n    if (dataId && fieldName) {\n      const dataset = datasets[dataId];\n      const field = dataset?.getColumnField(fieldName);\n      if (dataset && field) {\n        const indexes = runGpuFilterForPlot(dataset, filter);\n        const valueAccessor = index => field.valueAccessor({index});\n        acc[dataId] = histogramFromDomain(domain, indexes, numBins, valueAccessor);\n      }\n    }\n    return acc;\n  }, {});\n}\n\nexport function updateRangeFilterPlotType(\n  filter: RangeFilter,\n  plotType: RangeFilter['plotType'],\n  datasets: Datasets,\n  _dataId?: string\n): RangeFilter {\n  const nextFilter = {\n    ...filter,\n    plotType\n  };\n\n  // if (dataId) {\n  //   // clear bins\n  //   nextFilter = {\n  //     ...nextFilter,\n  //     bins: {\n  //       ...nextFilter.bins,\n  //       [dataId]: null\n  //     }\n  //   };\n  // }\n\n  return {\n    ...filter,\n    plotType,\n    bins: getRangeFilterBins(nextFilter, datasets, BINS)\n  };\n}\n\nexport function getChartTitle(yAxis: Field, plotType: PlotType): string {\n  const yAxisName = yAxis?.displayName;\n  const {aggregation} = plotType;\n\n  if (yAxisName) {\n    return capitalizeFirstLetter(`${aggregation} ${yAxisName} over Time`);\n  }\n\n  return `Count of Rows over Time`;\n}\n\nexport function getDefaultPlotType(filter, datasets) {\n  const interval = getInitialInterval(filter, datasets);\n  const defaultTimeFormat = getDefaultTimeFormat(interval);\n  return {\n    interval,\n    defaultTimeFormat,\n    type: PLOT_TYPES.histogram,\n    aggregation: AGGREGATION_TYPES.sum\n  };\n}\n"
  },
  {
    "path": "src/utils/src/position-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {h3ToGeo} from 'h3-js';\n\nimport {LayerColumn} from '@kepler.gl/types';\n\nimport {DataContainerInterface} from './data-container-interface';\n\nexport function getPositionFromHexValue(token) {\n  const pos = h3ToGeo(token);\n\n  if (Array.isArray(pos) && pos.every(Number.isFinite)) {\n    return [pos[1], pos[0]];\n  }\n  return null;\n}\n\nexport function maybeHexToGeo(\n  dc: DataContainerInterface,\n  d: {index: number},\n  lat: LayerColumn,\n  lng: LayerColumn\n) {\n  // lat or lng column could be hex column\n  // we assume string value is hex and try to convert it to geo lat lng\n  const latVal = dc.valueAt(d.index, lat.fieldIdx);\n  const lngVal = dc.valueAt(d.index, lng.fieldIdx);\n\n  return typeof latVal === 'string'\n    ? getPositionFromHexValue(latVal)\n    : typeof lngVal === 'string'\n    ? getPositionFromHexValue(lngVal)\n    : null;\n}\n"
  },
  {
    "path": "src/utils/src/projection-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport geoViewport from '@mapbox/geo-viewport';\n\nimport WebMercatorViewport from 'viewport-mercator-project';\nimport Console from 'global/console';\n\nexport const MAPBOX_TILE_SIZE = 512;\n\nfunction isLat(num) {\n  return Number.isFinite(num) && num <= 90 && num >= -90;\n}\nfunction isLng(num) {\n  return Number.isFinite(num) && num <= 180 && num >= -180;\n}\n\n/**\n * bounds should be [minLng, minLat, maxLng, maxLat]\n * @param {*} bounds\n */\nexport function validateBounds(bounds) {\n  // array: [ -180, -85.05112877980659, 180, 85.0511287798066 ]\n  // validate bounds\n  if (\n    Array.isArray(bounds) &&\n    bounds.length === 4 &&\n    [bounds[0], bounds[2]].every(isLng) &&\n    [bounds[1], bounds[3]].every(isLat)\n  ) {\n    return bounds;\n  }\n  return null;\n}\n\nexport function getCenterAndZoomFromBounds(bounds, {width, height}) {\n  const validBounds = validateBounds(bounds);\n  if (!validBounds) {\n    Console.warn('invalid map bounds provided');\n    return null;\n  }\n\n  // viewport(bounds, dimensions, minzoom, maxzoom, tileSize, allowFloat)\n  let {zoom} = geoViewport.viewport(\n    bounds,\n    [width, height],\n    undefined,\n    undefined,\n    MAPBOX_TILE_SIZE,\n    true\n  );\n  // center being calculated by geo-vieweport.viewport has a complex logic that\n  // projects and then unprojects the coordinates to determine the center\n  // Calculating a simple average instead as that is the expected behavior in most of cases\n  const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2];\n\n  // NOTE: this logic is used in deck.gl normalizeViewportProps\n  // This is required in order to prevent projection matrix mismatch between basemap and layers\n  const minZoom = Math.log2(height / MAPBOX_TILE_SIZE);\n  if (zoom <= minZoom) {\n    zoom = minZoom;\n    center[1] = 0;\n  }\n\n  return {zoom, center};\n}\n\n/**\n * Add extra info about screen space position and world position to the event.\n * @param {*} event Event to normalize.\n * @param {*} viewport Current viewport.\n * @returns Normalized event with extra information compatible with React-map-gl MapLayerMouseEvent\n * https://visgl.github.io/react-map-gl/docs/api-reference/types#maplayermouseevent\n */\nexport function normalizeEvent(event: any, viewport: WebMercatorViewport) {\n  const bounds = event.target?.getBoundingClientRect();\n  if (!bounds) {\n    return event;\n  }\n\n  const x = event.clientX - bounds.left;\n  const y = event.clientY - bounds.top;\n  if (!Number.isFinite(x) || !Number.isFinite(y)) {\n    return event;\n  }\n\n  event.point = [x, y];\n  const location = viewport.unproject(event.point, {targetZ: 0});\n  event.lngLat = [location[0], location[1]];\n\n  return event;\n}\n"
  },
  {
    "path": "src/utils/src/quick-insertion-sort.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Inplace quick insertion sorting algorithm for numeric values\n * 1. using a stack to eliminate recursion\n * 2. sorting inplace to reduce memory usage\n * 3. using insertion sort for small partition sizes\n * The original source code is from:\n * https://www.measurethat.net/Benchmarks/Show/3549/0/javascript-sorting-algorithms\n * https://quick.work/?page=view-blog&id=24\n *\n * @param arr\n */\nexport default function quickInsertionSort(arr: number[]): void {\n  if (!arr || arr.length < 1) {\n    return;\n  }\n  const stack: number[][] = [];\n  stack.push([0, arr.length, 2 * Math.floor(Math.log(arr.length) / Math.log(2))]);\n\n  while (stack.length > 0) {\n    const [start, end, depth] = stack[stack.length - 1];\n    stack.pop();\n\n    if (depth === 0) {\n      // for worst case of quick sort: too many partitions\n      shellSortBound(arr, start, end);\n    } else {\n      const pivot = Math.round((start + end) / 2);\n      const pivotNewIndex = inplaceQuicksortPartition(arr, start, end, pivot);\n      // more than 32 elements: faster to be sorted in QuickSort partition\n      // less than 32 elements: faster to be sorted in InsertionSort\n      if (end - pivotNewIndex > 16) {\n        stack.push([pivotNewIndex, end, depth - 1]);\n      }\n      if (pivotNewIndex - start > 16) {\n        stack.push([start, pivotNewIndex, depth - 1]);\n      }\n    }\n  }\n  insertionSort(arr);\n}\n\n/**\n * shellsort is a generalization of insertion sort\n * shellsort perform best on partially sorted array\n * Don't use shellsort on array (>10k)\n * @param arr\n * @param start\n * @param end\n *\n * Learn more about Shellsort at https://en.wikipedia.org/wiki/Shellsort\n */\nfunction shellSortBound(arr: number[], start: number, end: number) {\n  if (arr.length <= 1) return;\n  let inc = Math.round((start + end) / 2);\n  let i;\n  let j;\n  let t;\n\n  while (inc > start) {\n    for (i = inc; i < end; i++) {\n      t = arr[i];\n      j = i;\n      while (j >= inc && arr[j - inc] > t) {\n        arr[j] = arr[j - inc];\n        j -= inc;\n      }\n      arr[j] = t;\n    }\n    inc = Math.round((inc - start) / 2.2 + start);\n  }\n}\n\n/**\n * Insertion sort\n * @param arr\n */\nfunction insertionSort(arr: number[]) {\n  for (let i = 1, l = arr.length; i < l; i++) {\n    const value = arr[i];\n    let j;\n    for (j = i - 1; j >= 0; j--) {\n      if (arr[j] <= value) break;\n      arr[j + 1] = arr[j];\n    }\n    arr[j + 1] = value;\n  }\n}\n\n/**\n * In-place quick sort\n * @param arr\n * @param start\n * @param end\n * @param pivotIndex\n * @returns number\n */\nfunction inplaceQuicksortPartition(\n  arr: number[],\n  start: number,\n  end: number,\n  pivotIndex: number\n): number {\n  let i = start;\n  let j = end;\n  const pivot = arr[pivotIndex];\n  const partition = true;\n  while (partition) {\n    while (arr[i] < pivot) {\n      i++;\n    }\n    j--;\n    while (pivot < arr[j]) {\n      j--;\n    }\n    if (!(i < j)) {\n      return i;\n    }\n    // swap(arr, i, j);\n    const t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n    i++;\n  }\n  return i;\n}\n"
  },
  {
    "path": "src/utils/src/row-data-container.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DataRow, SharedRowOptions} from './data-row';\nimport {ProtoDatasetField} from '@kepler.gl/types';\nimport {DataContainerInterface, RangeOptions} from './data-container-interface';\n\ntype RowDataContainerInput = {\n  rows: any[][];\n  fields?: ProtoDatasetField[];\n};\n\n/**\n * @param dataContainer\n * @param sharedRow\n */\nfunction* rowsIterator(dataContainer: DataContainerInterface, sharedRow: SharedRowOptions) {\n  const numRows = dataContainer.numRows();\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    yield dataContainer.row(rowIndex, sharedRow);\n  }\n}\n\n/**\n * @param dataContainer\n * @param columnIndex\n */\nfunction* columnIterator(dataContainer: DataContainerInterface, columnIndex: number) {\n  const numRows = dataContainer.numRows();\n  for (let rowIndex = 0; rowIndex < numRows; ++rowIndex) {\n    yield dataContainer.valueAt(rowIndex, columnIndex);\n  }\n}\n\n/**\n * A data container where all data is stored internally as a 2D array.\n */\nexport class RowDataContainer implements DataContainerInterface {\n  _rows: any[][];\n  _numColumns: number;\n\n  constructor(data: RowDataContainerInput) {\n    if (!data.rows) {\n      throw Error('RowDataContainer: no rows provided');\n    }\n\n    if (!Array.isArray(data.rows)) {\n      throw Error(\"RowDataContainer: rows object isn't an array\");\n    }\n\n    this._rows = data.rows;\n    this._numColumns = data.rows[0]?.length || 0;\n  }\n\n  numRows(): number {\n    return this._rows.length;\n  }\n\n  numColumns(): number {\n    return this._numColumns;\n  }\n\n  valueAt(rowIndex: number, columnIndex: number): any {\n    if (this._rows[rowIndex] === null) {\n      return null;\n    }\n    return this._rows[rowIndex][columnIndex];\n  }\n\n  row(rowIndex: number, sharedRow?: SharedRowOptions): DataRow {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n    if (tSharedRow) {\n      tSharedRow.setSource(this, rowIndex);\n      return tSharedRow;\n    }\n\n    return new DataRow(this, rowIndex);\n  }\n\n  rowAsArray(rowIndex: number): any[] {\n    return this._rows[rowIndex];\n  }\n\n  rows(sharedRow: SharedRowOptions) {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n    return rowsIterator(this, tSharedRow);\n  }\n\n  column(columnIndex: number) {\n    return columnIterator(this, columnIndex);\n  }\n\n  flattenData(): any[][] {\n    return this._rows;\n  }\n\n  getPlainIndex(): number[] {\n    return this._rows.map((_, i) => i);\n  }\n\n  map<T>(\n    func: (row: DataRow, index: number) => T,\n    sharedRow?: SharedRowOptions,\n    options: RangeOptions = {}\n  ): T[] {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      out.push(func(row, rowIndex));\n    }\n    return out;\n  }\n\n  mapIndex<T>(\n    func: ({index}: {index: number}, dc: DataContainerInterface) => T,\n    options: RangeOptions = {}\n  ): T[] {\n    const {start = 0, end = this.numRows()} = options;\n    const endRow = Math.min(this.numRows(), end);\n\n    const out: T[] = [];\n    for (let rowIndex = start; rowIndex < endRow; ++rowIndex) {\n      out.push(func({index: rowIndex}, this));\n    }\n    return out;\n  }\n\n  find(\n    func: (row: DataRow, index: number) => boolean,\n    sharedRow?: SharedRowOptions\n  ): DataRow | undefined {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this._rows.length; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      if (func(row, rowIndex)) {\n        return row;\n      }\n    }\n    return undefined;\n  }\n\n  reduce<T>(\n    func: (acc: T, row: DataRow, index: number) => T,\n    initialValue: T,\n    sharedRow?: SharedRowOptions\n  ): T {\n    const tSharedRow = DataRow.createSharedRow(sharedRow);\n\n    for (let rowIndex = 0; rowIndex < this._rows.length; ++rowIndex) {\n      const row = this.row(rowIndex, tSharedRow);\n      initialValue = func(initialValue, row, rowIndex);\n    }\n    return initialValue;\n  }\n}\n"
  },
  {
    "path": "src/utils/src/searcher-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable callback-return */\nexport function simpleSearcher(autofillValues, page, query, cb) {\n  const regex = new RegExp(query, 'i');\n  let foundQuery = false;\n  const matches = (autofillValues || []).filter(function _filter(item) {\n    const tag = item.name;\n    foundQuery = foundQuery || tag === query;\n    return tag && regex.test(tag);\n  });\n\n  if (cb) {\n    cb(matches);\n  }\n\n  return matches;\n}\n/* eslint-enable callback-return */\n"
  },
  {
    "path": "src/utils/src/split-map-utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport cloneDeep from 'lodash/cloneDeep';\n\n/**\n * Add new layers to both existing maps\n * @param {Object} splitMaps\n * @param {Object|Array<Object>} layers\n * @returns {Array<Object>} new splitMaps\n */\nexport function addNewLayersToSplitMap(splitMaps, layers) {\n  const newLayers = Array.isArray(layers) ? layers : [layers];\n\n  if (!splitMaps.length || !newLayers.length) {\n    return splitMaps;\n  }\n\n  // add new layer to both maps,\n  // don't override, if layer.id is already in splitMaps\n  return splitMaps.map(settings => ({\n    ...settings,\n    layers: {\n      ...settings.layers,\n      ...newLayers.reduce(\n        (accu, newLayer) =>\n          // @ts-ignore\n          newLayer.id in settings.layers || !newLayer.config.isVisible\n            ? accu\n            : {\n                ...accu,\n                [newLayer.id]: newLayer.config.isVisible\n              },\n        {}\n      )\n    }\n  }));\n}\n\n/**\n * Remove an existing layer from split map settings\n * @param {Object} splitMaps\n * @param {Object} layer\n * @returns {Object} Maps of custom layer objects\n */\nexport function removeLayerFromSplitMaps(splitMaps, layer) {\n  if (!splitMaps.length) {\n    return splitMaps;\n  }\n  return splitMaps.map(settings => {\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const {[layer.id]: _, ...newLayers} = settings.layers;\n    return {\n      ...settings,\n      layers: newLayers\n    };\n  });\n}\n\n/**\n * This method will compute the default maps layer settings\n * based on the current layers visibility\n * @param {Array<Object>} layers\n * @returns {Array<Object>} layer visibility for each panel\n */\nexport function getInitialMapLayersForSplitMap(layers) {\n  return layers\n    .filter(layer => layer.config.isVisible)\n    .reduce(\n      (newLayers, currentLayer) => ({\n        ...newLayers,\n        [currentLayer.id]: currentLayer.config.isVisible\n      }),\n      {}\n    );\n}\n\n/**\n * This method will get default splitMap settings based on existing layers\n * @param {Array<Object>} layers\n * @param {Object} options\n * @returns {Array<Object>} split map settings\n */\nexport function computeSplitMapLayers(layers, options?: {duplicate: boolean}) {\n  const mapLayers = getInitialMapLayersForSplitMap(layers);\n  const {duplicate} = options || {};\n  // show all visible layers in left map, leave right map empty\n  return [{layers: mapLayers}, {layers: duplicate ? cloneDeep(mapLayers) : {}}];\n}\n"
  },
  {
    "path": "src/utils/src/strings.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/**\n * Capitalize first letter of a string\n */\nexport function capitalizeFirstLetter(str: string): string {\n  return typeof str === 'string' ? str.charAt(0).toUpperCase() + str.slice(1) : str;\n}\n"
  },
  {
    "path": "src/utils/src/time.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {ascending, bisector, tickStep} from 'd3-array';\nimport moment from 'moment';\n\nimport {\n  TICK_INTERVALS,\n  BINS_LARGE,\n  DURATIONS,\n  TIME_INTERVALS,\n  durationYear,\n  INTERVAL,\n  TickInterval\n} from '@kepler.gl/constants';\nimport {toArray} from '@kepler.gl/common-utils';\nimport {AnimationConfig, Timeline, TimeRangeFilter, Filter} from '@kepler.gl/types';\n\nimport {getFrequency} from './aggregation';\nimport {getBinThresholds} from './plot';\nimport {KeplerTableModel} from './types';\n\nexport const TIMELINE_MODES = {\n  inner: 'inner',\n  outer: 'outer'\n};\n\nexport const TileTimeInterval = {\n  YEAR: 'Y',\n  MONTH: 'M',\n  DAY: 'D',\n  HOUR: 'H',\n  MINUTE: 'T'\n};\n\nexport const TIME_INTERVALS_ORDERED = [\n  TileTimeInterval.MINUTE,\n  TileTimeInterval.HOUR,\n  TileTimeInterval.DAY,\n  TileTimeInterval.MONTH,\n  TileTimeInterval.YEAR\n];\n\nexport const LayerToFilterTimeInterval = {\n  [TileTimeInterval.MINUTE]: INTERVAL['1-minute'],\n  [TileTimeInterval.HOUR]: INTERVAL['1-hour'],\n  [TileTimeInterval.DAY]: INTERVAL['1-day'],\n  [TileTimeInterval.MONTH]: INTERVAL['1-month'],\n  [TileTimeInterval.YEAR]: INTERVAL['1-year']\n};\n\nexport const SAMPLE_TIMELINE = {\n  // value: [15], // represent 15% of the all width\n  value: [5, 15], // represent start at 5% and ends at 15%\n  domain: [1, 100], // represent the total domain\n  speed: 1,\n  enableInteraction: false, // can use interact with this timeline\n  isAnimating: false,\n  step: null,\n  // @todo: giuseppe coverType: 'inner' | 'outer'\n  mode: TIMELINE_MODES.inner\n  //  ....\n};\n\nexport const getTimelineFromAnimationConfig = (animationConfig: AnimationConfig): Timeline => {\n  const {\n    currentTime,\n    domain,\n    speed,\n    isAnimating,\n    timeSteps,\n    defaultTimeFormat,\n    timeFormat,\n    timezone\n  } = animationConfig;\n\n  return {\n    // @ts-expect-error\n    value: toArray(currentTime),\n    enableInteraction: true,\n    domain,\n    speed,\n    isAnimating: isAnimating || false,\n    timeSteps,\n    defaultTimeFormat,\n    timeFormat,\n    timezone,\n    timeBins: null,\n    marks: null\n  };\n};\n\n// check if the data inherent default time interval\n\n// https://github.com/d3/d3-scale/blob/732ed4b1cd5c643700571d1089c7deb8472242a6/src/time.js#L69\n// given number of ticks, calculate a reasonable interval\nexport function getIntervalByTicks(ticks, start, stop) {\n  if (ticks === null) ticks = 10;\n  const tickIntervals = Object.values(TICK_INTERVALS);\n  let interval;\n  let step;\n  // If a desired tick count is specified, pick a reasonable tick interval\n  // based on the extent of the domain and a rough estimate of tick size.\n  // Otherwise, assume interval is already a time interval and use it.\n  if (typeof ticks === 'number') {\n    const target = Math.abs(stop - start) / ticks;\n    const i = bisector((d: TickInterval) => d.duration).right(tickIntervals, target);\n    if (i === tickIntervals.length) {\n      step = tickStep(start / durationYear, stop / durationYear, ticks);\n      interval = 'year';\n    } else if (i) {\n      const tickInterval =\n        tickIntervals[\n          target / tickIntervals[i - 1].duration < tickIntervals[i].duration / target ? i - 1 : i\n        ];\n      // @ts-ignore TODO/ib\n      step = tickInterval.step;\n      // @ts-ignore TODO/ib\n      interval = tickInterval.interval;\n    } else {\n      step = Math.max(tickStep(start, stop, ticks), 1);\n      interval = 'millisecond';\n    }\n  }\n\n  return `${step}-${interval}`;\n}\n\n// get a  number of unique samples\nfunction getUniqueSamples(values, count) {\n  let i = -1;\n  const samples: any[] = [];\n  const sampleMap = {};\n  while (i++ < values.length && samples.length < count) {\n    const v = values[i];\n    if (v !== undefined && v !== null && !sampleMap[v]) {\n      sampleMap[v] = true;\n      samples.push(v);\n    }\n  }\n\n  return Object.values(samples);\n}\n\n/**\n * Given an array of epoch timestamp. sort it, if number of element\n * share the same time interval exceed thresholf, and total steps smaller than 100, return it, else return null\n * @param values\n */\nfunction detectInterval(values: number[] = [], domain, maxSteps = 10) {\n  const threshold = 0.7;\n\n  const sorted = values.sort(ascending);\n\n  // get first 100 unique sorted ts\n  const samples = getUniqueSamples(sorted, 100);\n  if (samples.length < 2) {\n    return null;\n  }\n\n  // get all intervals\n  const intervals = samples.reduce((accu, d, i) => {\n    if (i > 0) {\n      const duration = moment.duration(moment.utc(samples[i]).diff(moment.utc(samples[i - 1])));\n      const [dur, c] = getDurationUnit(duration);\n      accu.push(`${c}-${dur}`);\n    }\n    return accu;\n  }, []);\n\n  // find the most occured interval\n  const occur = getFrequency(intervals);\n  const maxOccr = Object.keys(occur).reduce(\n    (prev, key) => (occur[prev] >= occur[key] ? prev : key),\n    Object.keys(occur)[0]\n  );\n\n  // if occurance passed threshold\n  const mostOccur = occur[maxOccr] / intervals.length;\n  if (mostOccur >= threshold) {\n    const [step, dur] = maxOccr.split('-');\n    const durationSecond = DURATIONS[dur] * parseInt(step); // eslint-disable-line radix\n    const totalSteps = (domain[1] - domain[0]) / durationSecond;\n\n    if (totalSteps < maxSteps) {\n      // duration function is .days interval is day\n      return maxOccr.substring(0, maxOccr.length - 1);\n    }\n  }\n\n  return null;\n}\n\n/**\n * mappedValue is saved to dataset.fields.filterProps\n * @param dataset {KeplerTable}\n * @param filter\n */\nexport function getFilterMappedValue(\n  dataset: KeplerTableModel<any, any>,\n  filter: Filter\n): Filter['mappedValue'] | null {\n  const dataId = dataset.id;\n  const fieldName = filter.name[filter.dataId.indexOf(dataId)];\n  const field = dataset.getColumnField(fieldName);\n\n  if (!field) {\n    // eslint-disable-next-line no-console, no-undef\n    console.warn(`field ${fieldName} does not exist on dataset`);\n    return null;\n  }\n  const mappedValue = (field.filterProps || {}).mappedValue;\n  if (!mappedValue) {\n    // eslint-disable-next-line no-console, no-undef\n    console.warn(`mappedValue doesnt exist on filter field ${filter.name}`);\n    return null;\n  }\n  return mappedValue;\n}\n/**\n * Find the round unit of given durmostOccurtion: x years | months | days\n * @param duration\n */\nfunction getDurationUnit(duration) {\n  const durFuncs = Object.keys(DURATIONS);\n  for (let i = 0; i < durFuncs.length; i++) {\n    const c = duration[durFuncs[i]]();\n    if (c > 0) {\n      return [durFuncs[i], c];\n    }\n  }\n  return ['milliseconds', 1];\n}\n\nexport function intervalToFunction(id: string) {\n  const [stepStr, interval] = id.split('-');\n  const step = parseInt(stepStr); // eslint-disable-line radix\n  if (!step) {\n    // eslint-disable-next-line no-console, no-undef\n    console.warn('Step is not an integer');\n    return null;\n  }\n\n  if (!TIME_INTERVALS[interval]) {\n    // eslint-disable-next-line no-console, no-undef\n    console.warn(`Undefined time interval ${interval}`);\n    return null;\n  }\n\n  return TIME_INTERVALS[interval].every(step);\n}\n\n/**\n * Get initial interval from filter and datasets\n * @param filter\n * @param datasets\n * @returns\n */\nexport function getInitialInterval(\n  filter: Filter,\n  datasets: Record<string, KeplerTableModel<any, any>>\n): string {\n  const {domain} = filter;\n  const mergeMappedValue = filter.dataId.reduce((accu, dataId) => {\n    const mappedValue = getFilterMappedValue(datasets[dataId], filter);\n    if (!mappedValue) {\n      return accu;\n    }\n\n    for (let i = 0; i < mappedValue.length; i++) {\n      accu.push(mappedValue[i]);\n    }\n    return accu;\n  }, [] as any[]);\n\n  // check if data has predefined interval\n  let interval = detectInterval(mergeMappedValue, domain);\n\n  if (!interval) {\n    // @ts-expect-error need better types for domain\n    const [t0, t1] = domain;\n    interval = getIntervalByTicks(BINS_LARGE, t0, t1);\n  }\n\n  return interval;\n}\n\n// Filter interval options by time filter domain\n// max number of interval is 1000\n/**\n *\n * @param options\n * @param domain\n */\nexport function filterIntervalOptions(options, domain) {\n  const maxBins = 1000;\n  const minBins = 2;\n  const timeSpan = domain[1] - domain[0];\n\n  return options.filter(op => {\n    const {id} = op;\n    if (!TICK_INTERVALS[id]) {\n      return false;\n    }\n\n    const interval = TICK_INTERVALS[id];\n\n    // rough count on bins\n    const count = timeSpan / (interval.step * interval.duration);\n\n    return count >= minBins && count <= maxBins;\n  });\n}\n\n/**\n * Get timeline from filter\n * @param filter TimeRangeFilter filter\n * @returns Timeline\n */\nexport const getTimelineFromFilter = (filter: TimeRangeFilter): Timeline => {\n  const {\n    value,\n    domain,\n    speed,\n    isAnimating,\n    step,\n    // @ts-expect-error\n    timeSteps,\n    defaultTimeFormat,\n    timeFormat,\n    timezone,\n    timeBins,\n    animationWindow,\n    plotType\n  } = filter;\n\n  return {\n    value,\n    enableInteraction: true,\n    domain,\n    speed,\n    isAnimating,\n    step,\n    timeSteps,\n    defaultTimeFormat,\n    timeFormat,\n    timezone,\n    timeBins,\n    animationWindow,\n    marks: getBinThresholds(plotType?.interval, domain)\n  };\n};\n"
  },
  {
    "path": "src/utils/src/types.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {Filter, Field, FilterDatasetOpt} from '@kepler.gl/types';\n\nimport {DataContainerInterface} from './data-container-interface';\n\nexport interface KeplerTableModel<K, L, F extends Field = any> {\n  id: string;\n  fields: F[];\n  getColumnFieldIdx(columnName: string): number;\n  filterTable(filters: Filter[], layers: L[], opt?: FilterDatasetOpt): K;\n  getColumnFilterProps(columnName: string): Field['filterProps'] | null | undefined;\n  dataContainer: DataContainerInterface;\n  filterTableCPU(filters: Filter[], layers: L[]): K;\n  getColumnField(fieldName: string): Field | undefined;\n  gpuFilter: {\n    filterRange: number[][];\n    filterValueUpdateTriggers: any;\n    filterValueAccessor: (\n      dc: DataContainerInterface\n    ) => (\n      getIndex?: (any) => number,\n      getData?: (dc_: DataContainerInterface, d: any, fieldIndex: number) => any\n    ) => (d: any) => (number | number[])[];\n  };\n  filteredIndex: number[];\n}\n"
  },
  {
    "path": "src/utils/src/utils.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport Window from 'global/window';\nimport {capitalizeFirstLetter} from './strings';\n\n/**\n * Generate a hash string based on string\n * @param str\n * @returns\n */\nexport function generateHashIdFromString(str: string): string {\n  // generate hash string based on string\n  let hash = 0;\n  let i;\n  let chr;\n  let len;\n  if (str.length === 0) return hash.toString();\n  for (i = 0, len = str.length; i < len; i++) {\n    chr = str.charCodeAt(i);\n    // eslint-disable-next-line no-bitwise\n    hash = (hash << 5) - hash + chr;\n    // eslint-disable-next-line no-bitwise\n    hash |= 0; // Convert to 32bit integer\n  }\n  return hash.toString(36);\n}\n\n/**\n * Detect chrome\n * @returns {boolean} - yes or no\n */\nexport function isChrome(): boolean {\n  // Chrome 1+\n  return Window.chrome && Window.chrome.webstore;\n}\n\n/**\n * Convert camel style names to title\n * strokeColor -> Stroke Color\n * @param {string} str\n * @returns {string}\n */\nexport function camelToTitle(str: string): string {\n  const breakWord = str.replace(/([A-Z])/g, ' $1');\n  return capitalizeFirstLetter(breakWord);\n}\n\n/**\n * Convert names to camel style\n * Stroke Color -> strokeColor\n * @param {string} str\n * @returns {string}\n */\nexport const camelize = (str: string): string => {\n  return str.replace(/(?:^\\w|[A-Z]|\\b\\w|\\s+)/g, (match, index) => {\n    if (Number(match) === 0) return ''; // or if (/\\s+/.test(match)) for white spaces\n    return index === 0 ? match.toLowerCase() : match.toUpperCase();\n  });\n};\n\n/**\n * immutably insert value to an Array or Object\n * @param {Array|Object} obj\n * @param {Number|String} key\n * @param {*} value\n * @returns {Array|Object}\n */\nexport const insertValue = <T extends any[] | object>(\n  obj: T,\n  key: number | string,\n  value: any\n): T => {\n  if (Array.isArray(obj) && typeof key === 'number') {\n    return [...obj.slice(0, key), value, ...obj.slice(key + 1, obj.length)] as T;\n  }\n\n  return {...obj, [key]: value};\n};\n\n/**\n * check if value is a loose object including a plain object, array, function\n * @param {*} value\n */\nexport function isObject(value): boolean {\n  return value !== null && (typeof value === 'object' || typeof value === 'function');\n}\n\n/**\n * whether is an object\n * @returns {boolean} - yes or no\n */\nexport function isPlainObject(obj: unknown): obj is Record<string, unknown> {\n  return obj === Object(obj) && typeof obj !== 'function' && !Array.isArray(obj);\n}\n\nconst setPath = <T extends any[] | object>(\n  [key, ...next]: (string | number)[],\n  value: any,\n  obj: object | any[]\n): T => {\n  // is Object allows js object, array and function\n  if (!isObject(obj)) {\n    return obj as T;\n  }\n\n  if (next.length === 0) {\n    return insertValue(obj, key, value) as T;\n  }\n\n  // @ts-ignore\n  return insertValue(\n    obj,\n    key,\n    setPath(next, value, Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : {})\n  );\n};\n\n/**\n * Immutable version of _.set\n * @param {Array<String|Number>} path\n * @param {*} value\n * @param {Object} obj\n * @returns {Object}\n */\n// @ts-ignore\nexport const set = <T extends any[] | object>(path: (string | number)[], value: any, obj: T): T =>\n  obj === null ? obj : setPath(path, value, obj);\n\ntype ErrorObject = {\n  error?: any;\n  err?: any;\n  message?: any;\n};\n\nexport const DEFAULT_ERROR_MESSAGE = 'Something went wrong';\n\n/**\n * Get error information of unknown type\n * Extracts as much human readable information as possible\n * Ensure result is an Error object suitable for throw or promise rejection\n *\n * @private\n * @param {*}  err - Unknown error\n * @return {string} - human readable error msg\n */\nexport function getError(\n  err?: Error | ErrorObject | string,\n  defaultMessage: string = DEFAULT_ERROR_MESSAGE\n): string {\n  if (!err) {\n    return defaultMessage;\n  }\n\n  if (typeof err === 'string') {\n    return err;\n  } else if (err instanceof Error) {\n    return err.message;\n  } else if (typeof err === 'object') {\n    return Object.prototype.hasOwnProperty.call(err, 'message')\n      ? getError(err.message)\n      : Object.prototype.hasOwnProperty.call(err, 'error')\n      ? getError(err.error)\n      : Object.prototype.hasOwnProperty.call(err, 'err')\n      ? getError(err.err)\n      : JSON.stringify(err);\n  }\n\n  return defaultMessage;\n}\n\nexport function arrayInsert<T>(arr: T[], index: number, val: T): T[] {\n  if (!Array.isArray(arr)) {\n    return arr;\n  }\n\n  return [...arr.slice(0, index), val, ...arr.slice(index)];\n}\n\nexport function hasMobileWidth(breakPointValues: {palm: number; desk: number}): boolean {\n  const mobileWidth = Window.matchMedia(`(max-width: ${breakPointValues.palm}px)`);\n  return mobileWidth.matches;\n}\n\nexport function hasPortableWidth(breakPointValues: {palm: number; desk: number}): boolean {\n  const mobileWidth = Window.matchMedia(`(max-width: ${breakPointValues.desk}px)`);\n  return mobileWidth.matches;\n}\n\nexport function isTest(): boolean {\n  return globalThis.process?.env?.NODE_ENV === 'test';\n}\n\n/**\n * Filters an object by an arbitrary predicate\n * Returns a new object containing all elements that match the predicate\n * @param {Object} obj Object to be filtered\n * @param {Function} predicate Predicate by which the object will be filtered\n * @returns {Object}\n */\nexport function filterObjectByPredicate(obj, predicate) {\n  return Object.entries(obj).reduce(\n    (acc, entry) => (predicate(entry[0], entry[1]) ? {...acc, [entry[0]]: entry[1]} : acc),\n    {}\n  );\n}\n\nexport function isFunction(func: unknown): boolean {\n  return typeof func === 'function';\n}\n\nexport function findById(id: string): <X extends {id: string}>(arr: X[]) => X | undefined {\n  return arr => arr.find(a => a.id === id);\n}\n\n/**\n * Returns array difference from\n */\nexport function arrayDifference<X extends {id: string}>(source: X[]): (compare: X[]) => X[] {\n  const initial: X[] = [];\n  return compare =>\n    source.reduce((acc, element) => {\n      const foundElement = findById(element.id)(compare);\n      return foundElement ? [...acc, foundElement] : acc;\n    }, initial);\n}\n"
  },
  {
    "path": "src/utils/tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "test/browser/components/bottom-widget-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {\n  BottomWidgetFactory,\n  TimeWidgetFactory,\n  AnimationControlFactory,\n  appInjector\n} from '@kepler.gl/components';\nimport {VisStateActions} from '@kepler.gl/actions';\n\nconst BottomWidget = appInjector.get(BottomWidgetFactory);\nconst TimeWidget = appInjector.get(TimeWidgetFactory);\nconst AnimationControl = appInjector.get(AnimationControlFactory);\n\n// mock state\nimport {InitialState} from 'test/helpers/mock-state';\n\n// default props from initial state\nconst defaultProps = {\n  datasets: InitialState.visState.datasets,\n  filters: InitialState.visState.filters,\n  layers: InitialState.visState.layers,\n  animationConfig: InitialState.visState.animationConfig,\n  uiState: InitialState.uiState,\n  containerW: 900,\n  sidePanelWidth: 300,\n  visStateActions: VisStateActions\n};\n\ntest('Components -> BottomWidget.mount -> initial state', t => {\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <BottomWidget {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'BottomWidget should not fail without props');\n\n  t.equal(wrapper.find(TimeWidget).length, 0, 'should not render');\n  t.equal(wrapper.find(AnimationControl).length, 0, 'should not render');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/animation-control-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport moment from 'moment';\nimport {setLayerAnimationTimeConfig} from '@kepler.gl/actions';\nimport {getTimelineFromAnimationConfig} from '@kepler.gl/utils';\n\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {\n  AnimationControlFactory,\n  PlaybackControlsFactory,\n  FloatingTimeDisplayFactory,\n  appInjector,\n  IconButton\n} from '@kepler.gl/components';\nimport {StateWTripGeojson} from 'test/helpers/mock-state';\n\nimport {visStateReducer as reducer} from '@kepler.gl/reducers';\n\nconst AnimationControl = appInjector.get(AnimationControlFactory);\nconst PlaybackControls = appInjector.get(PlaybackControlsFactory);\nconst FloatingTimeDisplay = appInjector.get(FloatingTimeDisplayFactory);\n\ntest('Components -> AnimationControl.render', t => {\n  t.doesNotThrow(() => {\n    mountWithTheme(<AnimationControl />);\n  }, 'Should not fail without props');\n\n  t.end();\n});\n\ntest('Components -> AnimationControl -> render with props', t => {\n  let wrapper;\n  const toggleAnimation = sinon.spy();\n  const setLayerAnimationTime = sinon.spy();\n\n  const timeline = getTimelineFromAnimationConfig(StateWTripGeojson.visState.animationConfig);\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        {/* Error: Uncaught [Error: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.] */}\n        <AnimationControl\n          isAnimatable\n          setTimelineValue={setLayerAnimationTime}\n          toggleAnimation={toggleAnimation}\n          timeline={timeline}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail with trip layer props');\n\n  t.equal(\n    wrapper.find('.animation-window-control').length,\n    0,\n    'should not render AnimationWindowControl'\n  );\n  t.ok(wrapper.find(PlaybackControls), 'should render PlaybackControls');\n  t.ok(wrapper.find(FloatingTimeDisplay), 'should render FloatingTimeDisplay');\n\n  wrapper.find(IconButton).at(0).simulate('click');\n  t.ok(toggleAnimation.calledOnce, 'should call toggleAnimation');\n  t.end();\n});\n\ntest('Components -> AnimationControl -> time display', t => {\n  let wrapper;\n\n  const timeline = getTimelineFromAnimationConfig(StateWTripGeojson.visState.animationConfig);\n  // because we are using locale based formats, we set a locale here to make sure\n  // result are always the same\n  moment.locale('en');\n  const toggleAnimation = () => {};\n  const setLayerAnimationTime = () => {};\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        {/* Error: Uncaught [Error: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.] */}\n        <AnimationControl\n          isAnimatable\n          setTimelineValue={setLayerAnimationTime}\n          toggleAnimation={toggleAnimation}\n          timeline={timeline}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail with props');\n\n  const timeDisplay = wrapper.find(FloatingTimeDisplay);\n  const timeDomainStart = wrapper.find('.animation-control__time-domain.domain-start');\n  const timeDomainEnd = wrapper.find('.animation-control__time-domain.domain-end');\n  t.equal(timeDisplay.length, 1, 'should render FloatingTimeDisplay');\n  t.equal(timeDomainStart.length, 1, 'should render timeDomainStart');\n  t.equal(timeDomainEnd.length, 1, 'should render timeDomainEnd');\n\n  t.equal(\n    timeDomainStart.find('span').at(0).text(),\n    '08/12/2019 2:34:21 AM',\n    'should render current domain start'\n  );\n  t.equal(\n    timeDomainEnd.find('span').at(0).text(),\n    '08/12/2019 3:00:36 AM',\n    'should render current domain end'\n  );\n\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__top').length,\n    1,\n    'should render 1 top row time'\n  );\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__top').at(0).find('.time-value').text(),\n    '08/12/2019',\n    'should render correct date'\n  );\n\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__bottom').length,\n    1,\n    'should render 1 top row time'\n  );\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__bottom').at(0).find('.time-value').text(),\n    '2:34:21 AM',\n    'should render correct time'\n  );\n\n  t.end();\n});\n\ntest('Components -> AnimationControl -> time display -> custom timezone and timeFormat', t => {\n  let wrapper;\n\n  const nextState = reducer(\n    StateWTripGeojson.visState,\n    setLayerAnimationTimeConfig({\n      timezone: 'America/New_York',\n      timeFormat: 'YYYY MMM DD hh:mm'\n    })\n  );\n\n  const timeline = getTimelineFromAnimationConfig(nextState.animationConfig);\n\n  const toggleAnimation = () => {};\n  const setLayerAnimationTime = () => {};\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        {/* Error: Uncaught [Error: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.] */}\n        <AnimationControl\n          isAnimatable\n          setTimelineValue={setLayerAnimationTime}\n          toggleAnimation={toggleAnimation}\n          timeline={timeline}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail with props');\n  const timeDisplay = wrapper.find(FloatingTimeDisplay);\n  const timeDomainStart = wrapper.find('.animation-control__time-domain.domain-start');\n  const timeDomainEnd = wrapper.find('.animation-control__time-domain.domain-end');\n  t.equal(timeDisplay.length, 1, 'should render FloatingTimeDisplay');\n  t.equal(timeDomainStart.length, 1, 'should render timeDomainStart');\n  t.equal(timeDomainEnd.length, 1, 'should render timeDomainEnd');\n\n  t.equal(\n    timeDomainStart.find('span').at(0).text(),\n    '2019 Aug 11 10:34',\n    'should render current domain start'\n  );\n  t.equal(\n    timeDomainEnd.find('span').at(0).text(),\n    '2019 Aug 11 11:00',\n    'should render current domain end'\n  );\n\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__bottom').length,\n    1,\n    'should render 1 bottom row time'\n  );\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__bottom').at(0).find('.time-value').text(),\n    '2019 Aug 11 10:34',\n    'should render correct date'\n  );\n\n  t.equal(\n    timeDisplay.find('.animation-control__time-display__top').length,\n    0,\n    'should render 0 bottom row'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/color-legend-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport {mountWithTheme} from 'test/helpers/component-utils';\n\nimport {ColorLegendFactory, LegendRowFactory, appInjector} from '@kepler.gl/components';\nimport {StateWLayerCustomColorBreaks} from 'test/helpers/mock-state';\n\nconst ColorLegend = appInjector.get(ColorLegendFactory);\nconst LegendRow = appInjector.get(LegendRowFactory);\n\ntest('Components -> ColorLegend.render ColorMap', t => {\n  const layer = StateWLayerCustomColorBreaks.visState.layers[0];\n\n  t.doesNotThrow(() => {\n    mountWithTheme(<ColorLegend />);\n  }, 'Show not fail without props');\n\n  const width = 180;\n\n  const props = {\n    layer,\n    domain: layer.config.colorDomain,\n    range: layer.config.visConfig.colorRange,\n    scaleType: layer.config.colorScale,\n    fieldType: layer.config.colorField.type,\n    displayLabel: true,\n    disableEdit: false,\n    width\n  };\n\n  const wrapper = mountWithTheme(<ColorLegend {...props} />);\n  t.equal(wrapper.find(LegendRow).length, 3, 'Should render 3 legends');\n\n  const expectedLegends = [\n    {color: '#FF0000', label: 'Less than 1', customLabel: 'hello'},\n    {color: '#00FF00', label: '1 to 3'},\n    {color: '#0000FF', label: '3 or more'}\n  ];\n  for (let i = 0; i < 3; i++) {\n    const row = wrapper.find(LegendRow).at(i);\n    t.equal(\n      row.find('.legend-row-color').at(0).props().style.backgroundColor,\n      expectedLegends[i].color,\n      'should render color rect'\n    );\n    row.debug();\n    t.equal(\n      row.find('.legend__label__title__editor').at(0).props().value,\n      expectedLegends[i].customLabel ?? expectedLegends[i].label,\n      'should render color text based on colorLegend'\n    );\n  }\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/column-stats-chart-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {layerVisualChannelConfigChange} from '@kepler.gl/actions';\nimport {\n  ColorBreaksDisplay,\n  ColorBreaksPanelFactory,\n  ColorChartHeader,\n  ColorChartTick,\n  ColumnStatsChartFactory,\n  EditButton,\n  HISTOGRAM_HEIGHT,\n  HISTOGRAM_WIDTH,\n  appInjector\n} from '@kepler.gl/components';\nimport {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers';\nimport {getLayerColorScale, getLegendOfScale} from '@kepler.gl/utils';\nimport {scaleLinear} from 'd3-scale';\nimport cloneDeep from 'lodash/cloneDeep';\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {StateWFilesFiltersLayerColor} from 'test/helpers/mock-state';\nimport {histogramFromThreshold, histogramFromValues, getHistogramDomain} from '@kepler.gl/utils';\n\nconst ColorBreaksPanel = appInjector.get(ColorBreaksPanelFactory);\nconst ColumnStatsChart = appInjector.get(ColumnStatsChartFactory);\n\nconst TEST_PALETTE_COLORS = ['#FFF7F3', '#F369A3', '#49006A'];\n\nconst ColumnStatsChartProps = (filterData = false) => {\n  const InitialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const {layers, datasets} = InitialState.visState;\n  const pointLayer = layers[0];\n  const dataset = datasets[pointLayer.config.dataId];\n\n  // use uid as colorField (contains null values)\n  const colorField = datasets[pointLayer.config.dataId].fields[6];\n  const updatedState = coreReducer(\n    InitialState,\n    layerVisualChannelConfigChange(pointLayer, {colorField}, 'color')\n  );\n\n  if (filterData) {\n    dataset.filteredIndexForDomain = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];\n  }\n\n  const pointLayer1 = updatedState.visState.layers[0];\n\n  const domain = pointLayer1.config.colorDomain;\n  const scaleType = 'quantize';\n  const range = {\n    colors: TEST_PALETTE_COLORS,\n    name: 'RdPu',\n    type: 'sequential',\n    category: 'ColorBrewer'\n  };\n  const colorScale = getLayerColorScale({range, domain, scaleType, layer: pointLayer1});\n  const colorBreaks = getLegendOfScale({scale: colorScale, scaleType, fieldType: colorField.type});\n  const fieldValueAccessor = idx => dataset.getValue(colorField.name, idx);\n  const histogramDomain = getHistogramDomain({dataset, fieldValueAccessor});\n  const allBins = histogramFromValues(dataset.allIndexes, 30, fieldValueAccessor);\n  const filteredBins = !filterData\n    ? allBins\n    : histogramFromThreshold(\n        allBins.map(b => b.x0),\n        dataset.filteredIndexForDomain,\n        fieldValueAccessor,\n        false\n      );\n  const isFiltered = filterData;\n  return {\n    colorField,\n    dataset,\n    colorBreaks,\n    allBins,\n    filteredBins,\n    isFiltered,\n    histogramDomain,\n    onChangedUpdater: sinon.spy()\n  };\n};\n\nconst GetTickPositions = colorBreaks => {\n  const x = scaleLinear().domain([1, 12124]).range([0, HISTOGRAM_WIDTH]);\n  return colorBreaks.map(cb => x(cb.range[1]));\n};\n\ntest('Components -> ColumnStatsChart -> ColorChartTick', t => {\n  const ExpectedTickPositions = [70, 140];\n  let MovedTickPositions = null;\n\n  const InitialProps = {\n    colors: ['#FFF7F3', '#F369A3'],\n    positions: ExpectedTickPositions,\n    onTickMoving: positions => {\n      MovedTickPositions = positions;\n    },\n    onTickChanged: sinon.spy()\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorChartTick {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColorChartTick');\n\n  const colorChartTickWrapper = wrapper.find('.color-chart-tick-container');\n  t.equal(colorChartTickWrapper.length, 1, 'should render 1 ColorChartTickContainer');\n  t.equal(colorChartTickWrapper.children().length, 2, 'should render 2 ticks');\n\n  const TestTickPositions = colorChartTickWrapper.children().map(x => {\n    const style = x.props().style;\n    return (\n      parseInt(style.left, 10) + parseInt(style.borderWidth, 10) + parseInt(style.width, 10) / 2\n    );\n  });\n\n  t.deepEqual(\n    ExpectedTickPositions,\n    TestTickPositions,\n    'should position 2 ticks at correct locations'\n  );\n\n  // simulate drag tick to move\n  const firstTick = colorChartTickWrapper.childAt(0);\n  firstTick.simulate('mousedown');\n  firstTick.simulate('mousemove', {clientX: 80});\n  firstTick.simulate('mouseup');\n\n  t.deepEqual(MovedTickPositions, [80, 140], 'should move 2 ticks to correct locations');\n\n  firstTick.simulate('mousedown');\n  firstTick.simulate('mousemove', {clientX: -10});\n  firstTick.simulate('mouseup');\n\n  t.deepEqual(MovedTickPositions, [0, 140], 'should move the first tick to correct locations');\n\n  firstTick.simulate('mousedown');\n  firstTick.simulate('mousemove', {clientX: 150});\n  firstTick.simulate('mouseup');\n\n  t.deepEqual(\n    MovedTickPositions,\n    [139, 140],\n    'should move the first tick 1 pixel left to the second tick'\n  );\n\n  t.end();\n});\n\ntest('Components -> ColumnStatsChart -> ColorChartHeader', t => {\n  const InitialProps = {\n    minVal: 1,\n    maxVal: 100,\n    meanVal: 50\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorChartHeader {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColorChartHeader');\n\n  const colorChartHeader = wrapper.find('.color-chart-header');\n  t.equal(colorChartHeader.length, 1, 'should render 1 ColorChartHeader');\n  t.equal(colorChartHeader.children().length, 3, 'should render 3 divs in color chart header');\n  t.equal(\n    colorChartHeader.children().at(0).text(),\n    'MIN: 1',\n    'should render MIN value in color chart header'\n  );\n  t.equal(\n    colorChartHeader.children().at(1).text(),\n    'MEAN: 50',\n    'should render MEAN value in color chart header'\n  );\n  t.equal(\n    colorChartHeader.children().at(2).text(),\n    'MAX: 100',\n    'should render MAX value in color chart header'\n  );\n\n  t.end();\n});\n\nconst GetColorPaletteWidths = tickPositions => {\n  const n = tickPositions.length;\n  const widths = [tickPositions[0]];\n  for (let i = 1; i < n; ++i) {\n    widths.push(tickPositions[i] - tickPositions[i - 1]);\n  }\n  return widths;\n};\n\ntest('Components -> ColumnStatsChart -> IsLoading', t => {\n  const InitialProps = ColumnStatsChartProps();\n  const modifiedDataset = InitialProps.dataset;\n  modifiedDataset.fields = modifiedDataset.fields.map(f => {\n    // hack to test case of loading stats\n    f.isLoadingStats = true;\n    return f;\n  });\n  const InitialPropsWithLoading = {\n    ...InitialProps,\n    dataset: modifiedDataset\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColumnStatsChart {...InitialPropsWithLoading} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColumnStatsChart');\n\n  t.equal(wrapper.find('.color-chart-header').length, 0, 'should not render ColorChartHeader');\n  t.end();\n});\n\ntest('Components -> ColumnStatsChart', t => {\n  const InitialProps = ColumnStatsChartProps();\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColumnStatsChart {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColumnStatsChart');\n\n  // test ColorChartHeader\n  const colorChartHeader = wrapper.find('.color-chart-header');\n  t.equal(colorChartHeader.length, 1, 'should render 1 ColorChartHeader');\n  t.equal(colorChartHeader.children().length, 3, 'should render 3 divs in color chart header');\n\n  // test ColorPalette\n  const colorChartPalette = wrapper.find('.color-range-palette__inner');\n  t.equal(colorChartPalette.length, 1, 'should render 1 ColorChartPalette');\n\n  const ExpectedTickPositions = GetTickPositions(InitialProps.colorBreaks);\n  const ExpectedColorPaletteWidths = GetColorPaletteWidths(ExpectedTickPositions);\n\n  const TestColorPaletteWidths = colorChartPalette.children().map(x => x.props().style.width);\n  t.deepEqual(\n    TestColorPaletteWidths,\n    ExpectedColorPaletteWidths,\n    'should render color palette widths correctly.'\n  );\n\n  const TestColorPaletteColors = colorChartPalette\n    .children()\n    .map(x => x.props().style.backgroundColor);\n  t.deepEqual(\n    TestColorPaletteColors,\n    TEST_PALETTE_COLORS,\n    'should render color palette colors correctly'\n  );\n\n  // test histogram\n  const colorChartHistogram = wrapper.find('.color-chart-histogram');\n  const histogramSVG = colorChartHistogram.find('svg');\n  t.equal(histogramSVG.length, 1, 'should render 1 histogram');\n  t.equal(histogramSVG.props().width, HISTOGRAM_WIDTH, 'should render histogram with 210 width');\n  t.equal(histogramSVG.props().height, HISTOGRAM_HEIGHT, 'should render histogram with 210 width');\n  t.equal(\n    histogramSVG.find('mask').props().id,\n    'histogram-mask',\n    'should render a mask using histogram bins'\n  );\n  t.equal(\n    histogramSVG.find('.histogram-bars').children().length,\n    25,\n    'should render histogram bars'\n  );\n\n  t.end();\n});\n\ntest('Components -> ColumnStatsChart -> FilteredBins', t => {\n  const InitialProps = ColumnStatsChartProps(true);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColumnStatsChart {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColumnStatsChart');\n\n  const colorChartHistogram = wrapper.find('.color-chart-histogram');\n  t.equal(\n    colorChartHistogram.find('mask').props().id,\n    'histogram-mask',\n    'should render a mask using histogram bins'\n  );\n  t.equal(\n    colorChartHistogram.find('.histogram-bars').children().length,\n    25,\n    'should render histogram bars'\n  );\n  t.equal(\n    colorChartHistogram.find('.overlay-histogram-bars').children().length,\n    25,\n    'should render additional overlayed histogram bars'\n  );\n  t.end();\n});\n\ntest('Components -> ColorBreaksPanel -> ColorBreaksDisplay', t => {\n  let wrapper;\n\n  const InitialProps1 = {\n    currentBreaks: [],\n    onEdit: false\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorBreaksDisplay {...InitialProps1} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColorBreaksDisplay even with empty colorBreaks');\n\n  const {colorBreaks} = ColumnStatsChartProps();\n  const InitialProps = {\n    currentBreaks: colorBreaks,\n    onEdit: true\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorBreaksDisplay {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColorBreaksDisplay');\n\n  t.ok(wrapper.find(EditButton).length, 'should render EditButton');\n\n  t.end();\n});\n\ntest('Components -> ColorBreaksPanel -> ColumnStatsChart', t => {\n  const InitialProps = {\n    ...ColumnStatsChartProps(),\n    colorUIConfig: {\n      customPalette: {},\n      showSketcher: false,\n      colorRangeConfig: {\n        customBreaks: false\n      }\n    },\n    isCustomBreaks: false,\n    setColorUI: sinon.spy(),\n    onApply: sinon.spy(),\n    onCancel: sinon.spy(),\n    onScaleChange: sinon.spy()\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorBreaksPanel {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'should not fail rendering ColorBreaksPanel');\n\n  const colorChartTickWrapper = wrapper.find('.color-chart-tick-container');\n\n  // simulate drag tick to move\n  const firstTick = colorChartTickWrapper.childAt(0);\n  firstTick.simulate('mousedown');\n  firstTick.simulate('mousemove', {clientX: 80});\n  firstTick.simulate('mouseup');\n\n  wrapper.update();\n\n  const TestTickPositions = wrapper\n    .find('.color-chart-tick-container')\n    .children()\n    .map(x => {\n      const style = x.props().style;\n      return (\n        parseInt(style.left, 10) + parseInt(style.borderWidth, 10) + parseInt(style.width, 10) / 2\n      );\n    });\n\n  t.deepEqual(TestTickPositions, [80, 140], 'should move ticks to correct positions');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/file-uploader-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nimport {FileUpload, WarningMsg, FileDrop, UploadButton} from '@kepler.gl/components';\n\ntest('Components -> FileUploader.render', t => {\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FileUpload />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without data');\n\n  t.equal(wrapper.find(FileDrop).length, 1, 'should render FileUploader');\n  t.equal(wrapper.find(UploadButton).length, 1, 'should render UploadButton');\n\n  t.end();\n});\n\ntest('Components -> FileUpload.onDrop', t => {\n  const mockFiles = [{type: 'text/csv', name: 'tst-file.csv'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n  const stopPropagation = sinon.spy();\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload onFileUpload={onFileUpload} fileExtensions={['csv']} />\n    </IntlWrapper>\n  );\n\n  t.equal(wrapper.find(FileDrop).length, 1, 'should render FileUploader');\n\n  const FileDropDiv = wrapper.find('.file-uploader__file-drop').at(0);\n  // mock file drop event\n  const mockEvent = {\n    stopPropagation,\n    dataTransfer: {\n      types: ['Files'],\n      files: mockFiles\n    }\n  };\n\n  FileDropDiv.simulate('drop', mockEvent);\n\n  t.ok(onFileUpload.called, 'onFileUpload should get called');\n  t.ok(stopPropagation.called, 'stopPropagation should get called');\n  const files = wrapper.find(FileUpload).children().first().state().files;\n\n  t.deepEqual(files, mockFiles, 'should set files to state');\n\n  t.end();\n});\n\ntest('Components -> FileUpload.onDrop -> render loading msg', t => {\n  const mockFiles = [{type: 'text/csv', name: 'tst-file.csv'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n\n  const mockFileProgress = {\n    'tst-file.csv': {\n      fileName: 'tst-file.csv',\n      percent: 1,\n      message: 'Done'\n    }\n  };\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload\n        fileExtensions={['csv', 'json', 'geojson']}\n        onFileUpload={onFileUpload}\n        fileLoading={{fileCache: [], filesToLoad: [], onFinish: () => {}}}\n        fileLoadingProgress={mockFileProgress}\n      />\n    </IntlWrapper>\n  );\n\n  const FileDropDiv = wrapper.find('.file-uploader__file-drop').at(0);\n  // mock file drop event\n  const mockEvent = {\n    stopPropagation: () => {},\n    dataTransfer: {\n      types: ['Files'],\n      files: mockFiles\n    }\n  };\n\n  FileDropDiv.simulate('drop', mockEvent);\n\n  t.ok(onFileUpload.called, 'onFileUpload should get called');\n\n  const uploadMsg = wrapper.find('.file-upload-progress__message').at(0).html();\n  t.comment(uploadMsg);\n  t.ok(uploadMsg.includes('tst-file.csv', 'should render upload file msg'));\n\n  t.end();\n});\n\ntest('Components -> FileUpload.onDrop -> render error msg', t => {\n  const mockFiles = [{type: 'png', name: 'tst-file.png'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload onFileUpload={onFileUpload} />\n    </IntlWrapper>\n  );\n\n  const FileDropDiv = wrapper.find('.file-uploader__file-drop').at(0);\n  // mock file drop event\n  const mockEvent = {\n    stopPropagation: () => {},\n    dataTransfer: {\n      types: ['Files'],\n      files: mockFiles\n    }\n  };\n\n  FileDropDiv.simulate('drop', mockEvent);\n\n  t.ok(onFileUpload.notCalled, 'onFileUpload should not get called');\n  t.ok(wrapper.find(WarningMsg), 'should render WarningMsg');\n\n  const errorFiles = wrapper.find(FileUpload).children().first().state().errorFiles;\n  t.deepEqual(errorFiles, ['tst-file.png'], 'should save files to errorFiles');\n\n  t.end();\n});\n\ntest('Components -> FileUpload.dragOver', t => {\n  const mockFiles = [{type: 'text/csv', name: 'tst-file.csv'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload onFileUpload={onFileUpload} />\n    </IntlWrapper>\n  );\n\n  t.equal(wrapper.find(FileDrop).length, 1, 'should render FileUploader');\n\n  const FileDropDiv = wrapper.find('.file-uploader__file-drop').at(0);\n  // mock file drop event\n  const mockEvent = {\n    dataTransfer: {\n      types: ['Files'],\n      files: mockFiles\n    }\n  };\n\n  FileDropDiv.simulate('dragover', mockEvent);\n  const dragOver = wrapper.find(FileUpload).children().first().state().dragOver;\n  t.ok(dragOver, 'dragOver should be set to true');\n  t.end();\n});\n\ntest('Components -> FileUpload.dragLeave', t => {\n  const mockFiles = [{type: 'text/csv', name: 'tst-file.csv'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload onFileUpload={onFileUpload} />\n    </IntlWrapper>\n  );\n\n  t.equal(wrapper.find(FileDrop).length, 1, 'should render FileUploader');\n\n  const FileDropDiv = wrapper.find('.file-uploader__file-drop').at(0);\n  // mock file drop event\n  const mockEvent = {\n    dataTransfer: {\n      types: ['Files'],\n      files: mockFiles\n    }\n  };\n\n  FileDropDiv.simulate('dragleave', mockEvent);\n  const dragOver = wrapper.find(FileUpload).children().first().state().dragOver;\n  t.notOk(dragOver, 'dragOver should be set to false');\n  t.end();\n});\n\ntest('Components -> UploadButton fileInput', t => {\n  const mockFiles = [{type: 'text/csv', name: 'tst-file.csv'}];\n  const onFileUpload = sinon.spy(arg => {\n    t.deepEqual(arg, mockFiles, 'should call onFileUpload with files');\n  });\n  const wrapper = mountWithTheme(\n    <IntlWrapper>\n      <FileUpload onFileUpload={onFileUpload} fileExtensions={['csv']} />\n    </IntlWrapper>\n  );\n  const uploadButton = wrapper.find(UploadButton);\n  t.equal(uploadButton.length, 1, 'should render UploadButton');\n  const input = uploadButton.find('input');\n\n  // simulate click\n  uploadButton.find('.file-upload__upload-button-span').simulate('click');\n\n  // mock file drop event\n  const mockEvent = {\n    target: {\n      files: mockFiles\n    }\n  };\n\n  // change iwthout file?\n  input.simulate('change', {target: {files: null}});\n  t.ok(onFileUpload.notCalled, 'onFileUpload should not get called');\n\n  input.simulate('change', mockEvent);\n  t.ok(onFileUpload.called, 'onFileUpload should get called');\n  const files = wrapper.find(FileUpload).children().first().state().files;\n\n  t.deepEqual(files, mockFiles, 'should set files to state');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './file-uploader-test';\nimport './color-legend-test';\nimport './range-slider-test';\nimport './item-selector-test';\nimport './range-plot-test';\nimport './animation-control-test';\nimport './column-stats-chart-test';\n"
  },
  {
    "path": "test/browser/components/common/item-selector-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {act} from 'react-dom/test-utils';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {ItemSelector, Typeahead, DropdownList, ListItem} from '@kepler.gl/components';\n\ntest('Components -> ItemSelector.render', t => {\n  let wrapper;\n  const onChange = sinon.spy();\n  const props = {\n    selectedItems: 'normal',\n    options: ['additive', 'normal', 'subtractive'],\n    multiSelect: false,\n    searchable: false,\n    onChange\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ItemSelector {...props} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  t.equal(wrapper.find('.item-selector__dropdown').length, 1, 'should render DropdownSelect');\n  t.equal(wrapper.find(Typeahead).length, 0, 'should not render Typeahead');\n  t.equal(\n    wrapper.find('.item-selector__dropdown').at(0).find('.list__item__anchor').text(),\n    'normal',\n    'should render selected value'\n  );\n\n  // click dropdown select\n  wrapper.find('.item-selector__dropdown').at(0).simulate('click');\n\n  t.equal(wrapper.find(Typeahead).length, 1, 'should render Typeahead');\n  t.equal(wrapper.find(DropdownList).length, 1, 'should render 1 Typeahead');\n  t.equal(wrapper.find(DropdownList).at(0).find(ListItem).length, 3, 'should render 3 ListItem');\n\n  t.equal(\n    wrapper.find(DropdownList).at(0).find('.list__item__anchor').at(0).text(),\n    'additive',\n    'should render additive'\n  );\n\n  wrapper.find(DropdownList).at(0).find('.list__item').at(0).simulate('click');\n\n  t.deepEqual(onChange.args[0], ['additive'], 'should call additive');\n  t.end();\n});\n\ntest('Components -> ItemSelector.render over 100 options', t => {\n  let wrapper;\n  const onChange = sinon.spy();\n  const randomOptions = Array.from({length: 120}, () => Math.random().toString(16).substr(2, 8));\n\n  const props = {\n    selectedItems: '',\n    options: randomOptions,\n    multiSelect: false,\n    searchable: false,\n    onChange\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ItemSelector {...props} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  t.equal(wrapper.find('.item-selector__dropdown').length, 1, 'should render DropdownSelect');\n  t.equal(wrapper.find(Typeahead).length, 0, 'should not render Typeahead');\n  t.equal(\n    wrapper.find('.item-selector__dropdown').at(0).find('.list__item__anchor').text(),\n    '',\n    'should render no select value'\n  );\n\n  // click dropdown select\n  wrapper.find('.item-selector__dropdown').at(0).simulate('click');\n\n  t.equal(wrapper.find(Typeahead).length, 1, 'should render Typeahead');\n  t.equal(wrapper.find(DropdownList).length, 1, 'should render 1 Typeahead');\n  t.equal(\n    wrapper.find(DropdownList).at(0).find(ListItem).length,\n    100,\n    'should render first 100 ListItem'\n  );\n\n  // mockup scroll by triggering handleObserver() of IntersectionObserver\n  const mockedEntries = [\n    {\n      isIntersecting: true,\n      boundingClientRect: {\n        x: 77,\n        y: 77,\n        width: 231,\n        height: 0,\n        top: 777,\n        right: 308,\n        bottom: 777,\n        left: 77\n      }\n    }\n  ];\n  const dropdown = wrapper.find(DropdownList).instance();\n  dropdown.prevY = 100;\n  act(() => {\n    dropdown.handleObserver(mockedEntries);\n  });\n\n  wrapper.update();\n\n  t.equal(\n    wrapper.find(DropdownList).at(0).find(ListItem).length,\n    120,\n    'should render all 120 ListItem'\n  );\n\n  // mockup scroll again\n  dropdown.prevY = 110;\n  act(() => {\n    dropdown.handleObserver(mockedEntries);\n  });\n\n  wrapper.update();\n  t.equal(\n    wrapper.find(DropdownList).at(0).find(ListItem).length,\n    120,\n    'should still render all 120 ListItem'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/range-plot-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable no-unused-vars */\n\nimport React from 'react';\nimport test from 'tape';\nimport {mountWithTheme} from 'test/helpers/component-utils';\nimport {appInjector, RangePlotFactory, LoadingSpinner} from '@kepler.gl/components';\nimport sinon from 'sinon';\n\nconst RangePlot = appInjector.get(RangePlotFactory);\n\ntest('Components -> RangePlot.render', t => {\n  const setFilterPlot = sinon.spy();\n  const props = {\n    isEnlarged: true,\n    isRanged: true,\n    onBrush: () => {},\n    plotType: {type: 'histogram'},\n    range: [1421315219000, 1421348744000],\n    value: [1421315219000, 1421348744000],\n    width: 137,\n    setFilterPlot\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(<RangePlot {...props} />);\n  }, 'Show not fail without histogram');\n\n  // t.ok(setFilterPlot.calledOnce, 'should call setFilterPlot');\n  // t.deepEqual(\n  //   setFilterPlot.getCall(0).args,\n  //   [{plotType: {type: 'histogram'}}],\n  //   'should call setfilterPlot with plotType'\n  // );\n  // t.equal(wrapper.find(LoadingSpinner).length, 1, 'should render loading spinner');\n\n  const propsWLineChart = {\n    isEnlarged: true,\n    isRanged: true,\n    onBrush: () => {},\n    plotType: {type: 'lineChart'},\n    range: [1421315219000, 1421348744000],\n    value: [1421315219000, 1421348744000],\n    width: 137,\n    setFilterPlot\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(<RangePlot {...propsWLineChart} />);\n  }, 'Show not fail without lineChart');\n\n  t.ok(setFilterPlot.calledTwice, 'should call setFilterPlot');\n  t.deepEqual(\n    setFilterPlot.getCall(1).args,\n    [{plotType: {type: 'lineChart'}}],\n    'should call setfilterPlot with plotType'\n  );\n  t.equal(wrapper.find(LoadingSpinner).length, 1, 'should render loading spinner');\n\n  // cant test D3 in jsDom for now\n  // props.histogram = [\n  //   {count: 20, x0: 1421315219000, x1: 1421315500000},\n  //   {count: 0, x0: 1421315500000, x1: 1421316000000},\n  //   {count: 0, x0: 1421316000000, x1: 1421316500000},\n  //   {count: 0, x0: 1421316500000, x1: 1421317000000},\n  //   {count: 0, x0: 1421317000000, x1: 1421317500000},\n  //   {count: 21, x0: 1421317500000, x1: 1421318000000}\n  // ];\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/common/range-slider-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {RangeSlider, Slider, SliderHandle, SliderBarHandle} from '@kepler.gl/components';\n\ntest('Components -> RangeSlider.render', t => {\n  let wrapper;\n  const onChange = () => {};\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <RangeSlider range={[0, 10]} value0={1} value1={3} onChange={onChange} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  t.equal(wrapper.find(Slider).length, 1, 'should render Slider');\n  t.equal(wrapper.find(SliderHandle).length, 2, 'should render 2 Slider handle');\n  t.equal(wrapper.find(SliderBarHandle).length, 1, 'should render 1 Slider bar');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/container-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements, enzyme-deprecation/no-mount */\nimport React from 'react';\nimport test from 'tape';\nimport {mount} from 'enzyme';\nimport {combineReducers} from 'redux';\nimport configureStore from 'redux-mock-store';\nimport {Provider} from 'react-redux';\nimport sinon from 'sinon';\nimport {console as Console} from 'global/window';\nimport {\n  keplerGlReducer as rootReducer,\n  keplerGlReducerCore as coreReducer\n} from '@kepler.gl/reducers';\nimport {keplerGlInit} from '@kepler.gl/actions';\n\nimport Container, {ERROR_MSG} from '@kepler.gl/components';\nimport {DEFAULT_MAPBOX_API_URL} from '@kepler.gl/constants';\nconst initialCoreState = coreReducer(undefined, keplerGlInit());\nconst initialState = {\n  keplerGl: {}\n};\nconst mockStore = configureStore();\n\ntest('Components -> Container -> Mount with mint:true', t => {\n  // mount with empty store\n  let store = mockStore({});\n  const spy = sinon.spy(Console, 'error');\n\n  // mount without id or a kepler.gl state\n  mount(\n    <Provider store={store}>\n      <Container />\n    </Provider>\n  );\n\n  t.ok(spy.calledOnce, 'should call console.error once');\n  t.equal(\n    spy.getCall(0).args[0],\n    ERROR_MSG.noState,\n    'should warn when cannot find kepler.gl state'\n  );\n\n  // mount with kepler.gl state\n  store = mockStore(initialState);\n  let appReducer = combineReducers({\n    keplerGl: rootReducer\n  });\n  t.doesNotThrow(() => {\n    mount(\n      <Provider store={store}>\n        <Container />\n      </Provider>\n    );\n  }, 'Should not throw error');\n\n  let actions = store.getActions();\n\n  let expectedActions0 = {\n    type: '@@kepler.gl/REGISTER_ENTRY',\n    payload: {\n      id: 'map',\n      mint: true,\n      mapboxApiAccessToken: undefined,\n      mapboxApiUrl: undefined,\n      mapStylesReplaceDefault: undefined,\n      initialUiState: undefined\n    }\n  };\n\n  t.deepEqual(actions, [expectedActions0], 'should register entry and request map style');\n\n  // dispatch register action to reducer\n  let nextState = appReducer({}, expectedActions0);\n  let expectedState = {\n    keplerGl: {\n      map: initialCoreState\n    }\n  };\n\n  t.deepEqual(nextState, expectedState, 'should register map to root reducer by default');\n\n  // mount with custom state\n  store = mockStore({smoothie: {}});\n  appReducer = combineReducers({\n    smoothie: rootReducer\n  });\n\n  let wrapper;\n  const testId = {\n    id: 'milkshake',\n    mapboxApiAccessToken: 'pk.smoothie'\n  };\n\n  // mount with mint: true\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <Container\n          getState={s => s.smoothie}\n          id={testId.id}\n          mapboxApiAccessToken={testId.mapboxApiAccessToken}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount');\n\n  actions = store.getActions();\n\n  expectedActions0 = {\n    type: '@@kepler.gl/REGISTER_ENTRY',\n    payload: {\n      id: 'milkshake',\n      mint: true,\n      mapboxApiAccessToken: 'pk.smoothie',\n      mapboxApiUrl: undefined,\n      mapStylesReplaceDefault: undefined,\n      initialUiState: undefined\n    }\n  };\n\n  t.deepEqual(actions, [expectedActions0], 'should register entry and request map style');\n  actions.splice(0, 2);\n\n  nextState = appReducer({}, expectedActions0);\n  expectedState = {\n    smoothie: {\n      milkshake: {\n        ...initialCoreState,\n        mapStyle: {\n          ...initialCoreState.mapStyle,\n          mapboxApiAccessToken: 'pk.smoothie'\n        }\n      }\n    }\n  };\n\n  t.deepEqual(nextState, expectedState, 'should register milkshake to root reducer');\n\n  // unmount\n  wrapper.unmount();\n  expectedActions0 = {\n    type: '@@kepler.gl/DELETE_ENTRY',\n    payload: {\n      id: 'milkshake'\n    }\n  };\n\n  actions = store.getActions();\n  t.deepEqual(actions, [expectedActions0], 'should call unmount');\n\n  nextState = appReducer(nextState, expectedActions0);\n  expectedState = {\n    smoothie: {}\n  };\n  t.deepEqual(nextState, expectedState, 'should delete milkshake from root reducer');\n\n  spy.restore();\n  t.end();\n});\n\ntest('Components -> Container -> Mount with mint:false', t => {\n  const spy = sinon.spy(Console, 'error');\n\n  // mount with custom state\n  const store = mockStore({smoothie: {}});\n  const appReducer = combineReducers({\n    smoothie: rootReducer\n  });\n\n  let wrapper;\n  const testId = {\n    id: 'milkshake'\n  };\n\n  // mount with mint: false\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <Container\n          getState={s => s.smoothie}\n          id={testId.id}\n          mint={false}\n          mapboxApiAccessToken=\"hello.world\"\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount');\n\n  let actions = store.getActions();\n\n  const expectedActions0 = {\n    type: '@@kepler.gl/REGISTER_ENTRY',\n    payload: {\n      id: 'milkshake',\n      mint: false,\n      mapboxApiAccessToken: 'hello.world',\n      mapboxApiUrl: undefined,\n      mapStylesReplaceDefault: undefined,\n      initialUiState: undefined\n    }\n  };\n\n  t.deepEqual(actions, [expectedActions0], 'should register entry and request map style');\n  actions.splice(0, 2);\n\n  const nextState = appReducer({}, expectedActions0);\n  const expectedState = {\n    smoothie: {\n      milkshake: {\n        ...initialCoreState,\n        mapStyle: {\n          ...initialCoreState.mapStyle,\n          // should replace access token\n          mapboxApiAccessToken: 'hello.world',\n          mapboxApiUrl: DEFAULT_MAPBOX_API_URL\n        }\n      }\n    }\n  };\n  t.deepEqual(nextState, expectedState, 'should register milkshake to root reducer');\n\n  // unmount\n  wrapper.unmount();\n\n  actions = store.getActions();\n  t.deepEqual(actions, [], 'should not call unmount');\n\n  spy.restore();\n  t.end();\n});\n\ntest('Components -> Container -> Mount then rename', t => {\n  const dispatch = sinon.spy();\n\n  // mount with custom state\n  const store = mockStore({smoothie: {}});\n  const appReducer = combineReducers({\n    smoothie: rootReducer\n  });\n\n  let wrapper;\n  const testId = {\n    id: 'milkshake'\n  };\n\n  // mount with mint: false\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <Container\n          getState={s => s.smoothie}\n          id={testId.id}\n          mapboxApiAccessToken=\"hello.world\"\n          dispatch={dispatch}\n          store={store}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount');\n\n  const expectedActions0 = {\n    type: '@@kepler.gl/REGISTER_ENTRY',\n    payload: {\n      id: 'milkshake',\n      mint: true,\n      mapboxApiAccessToken: 'hello.world',\n      mapboxApiUrl: undefined,\n      mapStylesReplaceDefault: undefined,\n      initialUiState: undefined\n    }\n  };\n\n  t.deepEqual(store.getActions().pop(), expectedActions0, 'should register entry');\n\n  const nextState = appReducer({}, expectedActions0);\n  const expectedState = {\n    smoothie: {\n      milkshake: {\n        ...initialCoreState,\n        mapStyle: {\n          ...initialCoreState.mapStyle,\n          // should replace access token\n          mapboxApiAccessToken: 'hello.world'\n        }\n      }\n    }\n  };\n  t.deepEqual(nextState, expectedState, 'should register milkshake to root reducer');\n\n  wrapper.setProps({\n    children: (\n      <Container\n        getState={s => s.smoothie}\n        id={'milkshake-2'}\n        mapboxApiAccessToken=\"hello.world\"\n        dispatch={dispatch}\n        store={store}\n      />\n    )\n  });\n\n  const expectedActions1 = {\n    type: '@@kepler.gl/RENAME_ENTRY',\n    payload: {oldId: 'milkshake', newId: 'milkshake-2'}\n  };\n\n  t.deepEqual(store.getActions().pop(), expectedActions1, 'should rename entry ');\n\n  const nextState1 = appReducer(nextState, expectedActions1);\n  const expectedState1 = {\n    smoothie: {\n      'milkshake-2': nextState.smoothie.milkshake\n    }\n  };\n\n  t.deepEqual(nextState1, expectedState1, 'should rename milkshake to milkshake-2');\n  // unmount\n  wrapper.unmount();\n\n  const expectedActions2 = {\n    type: '@@kepler.gl/DELETE_ENTRY',\n    payload: {\n      id: 'milkshake-2'\n    }\n  };\n\n  t.deepEqual(store.getActions().pop(), expectedActions2, 'should call unmount milkshake-2');\n\n  t.end();\n});\n/* eslint-enable max-statements */\n"
  },
  {
    "path": "test/browser/components/editor/feature-action-panel-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {PureFeatureActionPanelFactory} from '@kepler.gl/components';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nconst FeatureActionPanel = PureFeatureActionPanelFactory();\n\ntest('FeatureActionPanel -> display layers', t => {\n  const layers = [\n    {\n      config: {\n        label: 'layer 1',\n        dataId: 'puppy'\n      }\n    },\n    {\n      config: {\n        label: 'layer 2',\n        dataId: 'puppy'\n      }\n    }\n  ];\n\n  const datasets = {\n    puppy: {\n      color: [123, 123, 123]\n    }\n  };\n\n  const selectedFeature = {type: 'Feature', geometry: {type: 'Polygon', coordinates: []}};\n\n  const onToggleLayer = sinon.spy();\n  const onDeleteFeature = sinon.spy();\n\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FeatureActionPanel\n          className=\"action-item-test\"\n          layers={layers}\n          datasets={datasets}\n          selectedFeature={selectedFeature}\n          onToggleLayer={onToggleLayer}\n          onDeleteFeature={onDeleteFeature}\n          position={{x: 0, y: 0}}\n        />\n      </IntlWrapper>\n    );\n  }, 'FeatureActionPanel should not fail mount');\n\n  t.equal(wrapper.find('Checkbox').length, 2, 'We should display only 2 layer checkbox');\n  for (let i = 0; i < wrapper.find('Checkbox').length; i++) {\n    t.equal(\n      wrapper.find('Checkbox').at(i).find('label').text(),\n      `layer ${i + 1}`,\n      'should render correct layer label'\n    );\n  }\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/editor/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './feature-action-panel-test';\n"
  },
  {
    "path": "test/browser/components/effects/effect-configurator-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\nimport test from 'tape';\n\nimport {appInjector, EffectConfiguratorFactory} from '@kepler.gl/components';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {visStateReducer} from '@kepler.gl/reducers';\n\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\nimport {StateWEffects, InitialState} from 'test/helpers/mock-state';\n\nconst EffectConfigurator = appInjector.get(EffectConfiguratorFactory);\n\ntest('Components -> EffectConfigurator -> render -> post processing effect', t => {\n  const testCases = [\n    {\n      // configurator for magnify effect with [x, y] and plain number uniforms\n      effect: StateWEffects.visState.effects[2],\n      expectedSliders: 5\n    },\n    {\n      // configurator for ink effect with uniforom.value\n      effect: StateWEffects.visState.effects[1],\n      expectedSliders: 1\n    }\n  ];\n\n  testCases.forEach(({effect, expectedSliders}) => {\n    let wrapper;\n    t.doesNotThrow(() => {\n      wrapper = mountWithTheme(\n        <IntlWrapper>\n          <EffectConfigurator\n            {...{\n              effect,\n              updateEffectConfig: () => {}\n            }}\n          />\n        </IntlWrapper>\n      );\n    }, `EffectConfigurator for ${effect.type} should not fail`);\n\n    t.equal(\n      wrapper.find('RangeSlider').length,\n      expectedSliders,\n      `should render ${expectedSliders} RangeSlider(s)`\n    );\n  });\n\n  t.end();\n});\n\nconst mockStore = configureStore();\nconst ititialState = {mapState: {latitude: 0, longitude: 0}};\n\ntest('Components -> EffectConfigurator -> render -> light & shadow effect', t => {\n  const store = mockStore(ititialState);\n\n  const nextState = visStateReducer(\n    InitialState.visState,\n    VisStateActions.addEffect({\n      type: 'lightAndShadow'\n    })\n  );\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectConfigurator\n            {...{\n              effect: nextState.effects[0],\n              updateEffectConfig: () => {}\n            }}\n          />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectConfigurator for ${nextState.effects[0].type} should not fail`);\n\n  t.equal(wrapper.find('RangeSlider').length, 4, `should render 4 RangeSliders`);\n  t.equal(wrapper.find('CompactColorPicker').length, 3, `should render 3 CompactColorPickers`);\n  t.equal(\n    wrapper.find('EffectTimeConfigurator').length,\n    1,\n    `should render 1 EffectTimeConfigurator`\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/effects/effect-manager-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\n\nimport {\n  EffectManagerFactory,\n  EffectListFactory,\n  SidePanelTitleFactory,\n  EffectTypeSelectorFactory,\n  appInjector\n} from '@kepler.gl/components';\n\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\nimport {StateWEffects} from 'test/helpers/mock-state';\n\nconst EffectManager = appInjector.get(EffectManagerFactory);\nconst EffectList = appInjector.get(EffectListFactory);\nconst SidePanelTitle = appInjector.get(SidePanelTitleFactory);\nconst EffectTypeSelector = appInjector.get(EffectTypeSelectorFactory);\n\nconst mockStore = configureStore();\nconst initialState = {\n  visState: {\n    effects: StateWEffects.visState.effects,\n    effectOrder: StateWEffects.visState.effectOrder\n  },\n  mapState: {latitude: 0, longitude: 0}\n};\n\ntest('Components -> EffectManager -> render', t => {\n  const store = mockStore(initialState);\n\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectManager />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, 'EffectManager should not fail');\n\n  t.equal(wrapper.find(EffectList).length, 1, 'should render EffectList');\n  t.equal(wrapper.find(SidePanelTitle).length, 1, 'should render SidePanelTitle');\n  t.equal(wrapper.find(EffectTypeSelector).length, 1, 'should render EffectTypeSelector');\n  t.equal(wrapper.find('EffectPanel').length, 4, 'should render 3 EffectPanels');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/effects/effect-time-configurator-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\n\nimport {LIGHT_AND_SHADOW_EFFECT_TIME_MODES} from '@kepler.gl/constants';\nimport {appInjector, EffectTimeConfiguratorFactory} from '@kepler.gl/components';\n\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\n\nconst EffectTimeConfigurator = appInjector.get(EffectTimeConfiguratorFactory);\n\nconst TEST_TIMESTAMP = 1690303570534;\n\nconst mockStore = configureStore();\nconst ititialState = {mapState: {latitude: 0, longitude: 0}};\n\ntest('Components -> EffectTimeConfigurator -> render', t => {\n  const store = mockStore(ititialState);\n\n  const props = {\n    timestamp: TEST_TIMESTAMP,\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  t.equal(wrapper.find('Checkbox').length, 3, `should render 3 Checkboxes`);\n  t.equal(wrapper.find('Button').length, 1, `should render 1 Button`);\n  t.equal(wrapper.find('DatePicker').length, 1, `should render 1 DatePicker`);\n  t.equal(wrapper.find('TimePicker').length, 1, `should render 1 TimePicker`);\n\n  t.end();\n});\n\ntest('Components -> EffectTimeConfigurator -> time type', t => {\n  const store = mockStore(ititialState);\n\n  const onTimeModeChange = sinon.spy();\n  const props = {\n    timestamp: TEST_TIMESTAMP,\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick,\n    onChange: onTimeModeChange\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  wrapper.find('Checkbox').at(0).invoke('onChange')();\n  t.ok(\n    onTimeModeChange.calledWith({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick}),\n    `Should set ${LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick} mode`\n  );\n\n  wrapper.find('Checkbox').at(1).invoke('onChange')();\n  t.ok(\n    onTimeModeChange.calledWith({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.current}),\n    `Should set ${LIGHT_AND_SHADOW_EFFECT_TIME_MODES.current} mode`\n  );\n\n  wrapper.find('Checkbox').at(2).invoke('onChange')();\n  t.ok(\n    onTimeModeChange.calledWith({timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.animation}),\n    `Should set ${LIGHT_AND_SHADOW_EFFECT_TIME_MODES.animation} mode`\n  );\n\n  t.ok(onTimeModeChange.calledThrice, 'should call onTimeModeChange thrice');\n\n  t.end();\n});\n\ntest('Components -> EffectTimeConfigurator -> pick time', t => {\n  const store = mockStore(ititialState);\n\n  const onDateTimeChange = sinon.spy();\n  const props = {\n    timestamp: TEST_TIMESTAMP,\n    timezone: 'UTC',\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick,\n    onChange: onDateTimeChange\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  // date\n  const inputs = wrapper.find('input');\n  inputs\n    .find('.react-date-picker__inputGroup__month')\n    .at(0)\n    .simulate('change', {target: {value: '2', name: 'month'}});\n  inputs\n    .find('.react-date-picker__inputGroup__day')\n    .at(0)\n    .simulate('change', {target: {value: '2', name: 'day'}});\n  inputs\n    .find('.react-date-picker__inputGroup__year')\n    .at(0)\n    .simulate('change', {target: {value: '2022', name: 'year'}})\n    // properly propagate in tests\n    .simulate('change', {target: {value: '2022', name: 'year'}});\n\n  t.deepEqual(\n    onDateTimeChange.getCall(3).args[0],\n    {timestamp: 1643820360000},\n    `Date should be updated`\n  );\n\n  // time\n  wrapper\n    .find('.react-time-picker__inputGroup__hour')\n    .at(0)\n    .simulate('change', {target: {value: '20', name: 'hour24'}});\n  wrapper\n    .find('.react-time-picker__inputGroup__minute')\n    .at(0)\n    .simulate('change', {target: {value: '30', name: 'minute'}})\n    // properly propagate in tests\n    .simulate('change', {target: {value: '30', name: 'minute'}});\n\n  t.deepEqual(\n    onDateTimeChange.getCall(6).args[0],\n    {timestamp: 1690317000000},\n    `Time should be updated`\n  );\n\n  // pick current time button\n  wrapper.find('Button').at(0).simulate('click');\n  t.ok(\n    Math.abs(onDateTimeChange.getCall(7).args[0].timestamp - Date.now()) < 1000,\n    `Should pick current date & time`\n  );\n\n  t.end();\n});\n\nconst getDisplayedDateTimeValues = wrapper => {\n  return {\n    year: wrapper.find('.react-date-picker__inputGroup__year').at(0).props().value,\n    month: wrapper.find('.react-date-picker__inputGroup__month').at(0).props().value,\n    day: wrapper.find('.react-date-picker__inputGroup__day').at(0).props().value,\n    hour: wrapper.find('.react-time-picker__inputGroup__hour').at(0).props().value,\n    minute: wrapper.find('.react-time-picker__inputGroup__minute').at(0).props().value,\n    amPm: wrapper.find('.react-time-picker__inputGroup__amPm').at(0).props().value\n  };\n};\n\ntest('Components -> EffectTimeConfigurator -> time zone update', t => {\n  const store = mockStore(ititialState);\n\n  const onDateTimeChange = sinon.spy();\n  const props = {\n    timestamp: 1690851835534,\n    timezone: 'America/Los_Angeles',\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick,\n    onChange: onDateTimeChange\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  // Los Angeles timezone is 7 hours ahead of UTC\n  // UTC time: 2023 Aug 1 01:03:55\n  // Displayed time: 2023 July 31 06:03:00 pm\n\n  let values = getDisplayedDateTimeValues(wrapper);\n  t.equal(values.year, '2023', `Year should be correctly set`);\n  t.equal(values.month, '7', `Month should be correctly set`);\n  t.equal(values.day, '31', `Day should be correctly set`);\n  t.equal(values.hour, '6', `Hour should be correctly set`);\n  t.equal(values.minute, '3', `Minute should be correctly set`);\n  t.equal(values.amPm, 'pm', `AM/PM should be correctly set`);\n\n  // Change time zone\n  wrapper.find('.item-selector__dropdown').at(1).simulate('click');\n  wrapper.find({children: 'America/Barbados'}).at(0).simulate('click');\n\n  t.deepEqual(\n    onDateTimeChange.getCall(0).args[0],\n    {timestamp: 1690840980000, timezone: 'America/Barbados'},\n    `New time zone should be selected and timestamp updated`\n  );\n\n  // Timestamp should be updated internally,\n  // but displayed date and time shouldn't change\n  values = getDisplayedDateTimeValues(wrapper);\n  t.equal(values.year, '2023', `Year should be correctly set`);\n  t.equal(values.month, '7', `Month should be correctly set`);\n  t.equal(values.day, '31', `Day should be correctly set`);\n  t.equal(values.hour, '6', `Hour should be correctly set`);\n  t.equal(values.minute, '3', `Minute should be correctly set`);\n  t.equal(values.amPm, 'pm', `AM/PM should be correctly set`);\n\n  t.end();\n});\n\ntest('Components -> EffectTimeConfigurator -> time with custom timezone update', t => {\n  const store = mockStore(ititialState);\n\n  const onDateTimeChange = sinon.spy();\n  const props = {\n    timestamp: 1690840980000,\n    timezone: 'America/Barbados',\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick,\n    onChange: onDateTimeChange\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  // Internal UTC timestamp should take time zone into account\n  wrapper\n    .find('.react-time-picker__inputGroup__hour')\n    .at(0)\n    .simulate('change', {target: {value: '20', name: 'hour24'}})\n    // Call twice to propagate chagne in date-time-picker\n    .simulate('change', {target: {value: '20', name: 'hour24'}});\n  t.deepEqual(\n    onDateTimeChange.getCall(1).args[0],\n    {timestamp: 1690848180000},\n    `time changed & timezone taken into account`\n  );\n\n  t.end();\n});\n\ntest('Components -> EffectTimeConfigurator -> date with custom timezone update', t => {\n  const store = mockStore(ititialState);\n\n  const onDateTimeChange = sinon.spy();\n  const props = {\n    timestamp: 1690840980000,\n    timezone: 'America/Barbados',\n    timeMode: LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick,\n    onChange: onDateTimeChange\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <EffectTimeConfigurator {...props} />\n        </Provider>\n      </IntlWrapper>\n    );\n  }, `EffectTimeConfigurator should not fail`);\n\n  // Internal UTC timestamp should take time zone into account\n  wrapper\n    .find('input')\n    .find('.react-date-picker__inputGroup__day')\n    .at(0)\n    .simulate('change', {target: {value: '10', name: 'day'}})\n    // Call twice to propagate chagne in date-time-picker\n    .simulate('change', {target: {value: '10', name: 'day'}});\n\n  t.deepEqual(\n    onDateTimeChange.getCall(1).args[0],\n    {timestamp: 1689026580000},\n    `date changed & timezone taken into account`\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/effects/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './effect-manager-test';\nimport './effect-configurator-test';\nimport './effect-time-configurator-test';\n"
  },
  {
    "path": "test/browser/components/filters/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './time-widget-test';\n"
  },
  {
    "path": "test/browser/components/filters/time-widget-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport moment from 'moment';\n\nimport {IntlWrapper, mountWithTheme, mockHTMLElementClientSize} from 'test/helpers/component-utils';\nimport {setFilterAnimationTimeConfig} from '@kepler.gl/actions';\nimport {visStateReducer as reducer, DEFAULT_ANIMATION_CONFIG} from '@kepler.gl/reducers';\n\nimport {\n  TimeWidgetFactory,\n  FloatingTimeDisplayFactory,\n  TimeRangeSliderFactory,\n  RangeSliderFactory,\n  FieldSelectorFactory,\n  PlaybackControlsFactory,\n  AnimationSpeedSliderFactory,\n  Icons,\n  TimeSliderMarkerFactory,\n  TimeRangeSliderTimeTitleFactory,\n  IconButton,\n  SliderHandle,\n  Typeahead,\n  appInjector\n} from '@kepler.gl/components';\n\nconst TimeWidget = appInjector.get(TimeWidgetFactory);\nconst TimeRangeSlider = appInjector.get(TimeRangeSliderFactory);\nconst RangeSlider = appInjector.get(RangeSliderFactory);\nconst FloatingTimeDisplay = appInjector.get(FloatingTimeDisplayFactory);\nconst FieldSelector = appInjector.get(FieldSelectorFactory);\nconst PlaybackControls = appInjector.get(PlaybackControlsFactory);\nconst AnimationSpeedSlider = appInjector.get(AnimationSpeedSliderFactory);\nconst TimeSliderMarker = appInjector.get(TimeSliderMarkerFactory);\nconst TimeRangeSliderTimeTitle = appInjector.get(TimeRangeSliderTimeTitleFactory);\n\n// mock state\nimport {StateWFilters} from 'test/helpers/mock-state';\n\nconst nop = () => {};\n// default props from initial state\nconst defaultProps = {\n  datasets: StateWFilters.visState.datasets,\n  filter: StateWFilters.visState.filters[0],\n  index: 0,\n  readOnly: false,\n  showTimeDisplay: true,\n  isAnimatable: true,\n  setFilterAnimationTime: nop,\n  resetAnimation: nop,\n  updateAnimationSpeed: nop,\n  toggleAnimation: nop,\n  onClose: nop,\n  onToggleMinify: nop,\n  setFilterPlot: nop,\n  setFilterAnimationWindow: nop,\n  animationConfig: DEFAULT_ANIMATION_CONFIG\n};\n\n// call to set filter timezone and timeformat\nconst nextState = reducer(\n  StateWFilters.visState,\n  setFilterAnimationTimeConfig(0, {\n    timezone: 'America/New_York',\n    timeFormat: 'YYYY MMM DD hh:mm a'\n  })\n);\n\ntest('Components -> TimeWidget.mount -> with time filter', t => {\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'TimeWidget should not fail without props');\n\n  t.equal(wrapper.find(TimeWidget).length, 1, 'should render TimeWidget');\n  t.equal(wrapper.find(FloatingTimeDisplay).length, 1, 'should render FloatingTimeDisplay');\n  t.equal(wrapper.find(TimeRangeSlider).length, 1, 'should render TimeRangeSlider');\n  t.equal(wrapper.find(FieldSelector).length, 1, 'should render FieldSelector');\n  t.equal(wrapper.find(PlaybackControls).length, 1, 'should render PlaybackControls');\n\n  // check yAxisFields\n  const yAxisFields = wrapper.find(FieldSelector).at(0).props().fields;\n  t.deepEqual(\n    yAxisFields.map(f => f.name),\n    ['gps_data.lat', 'gps_data.lng', 'uid'],\n    'should only pass real / integer fields to yAxis'\n  );\n\n  t.end();\n});\n\ntest('Components -> TimeWidget.mount -> test actions', t => {\n  const onClose = sinon.spy();\n  const toggleAnimation = sinon.spy();\n  const updateAnimationSpeed = sinon.spy();\n  const setFilterAnimationWindow = sinon.spy();\n  const setFilterPlot = sinon.spy();\n  const setFilterAnimationTime = sinon.spy();\n\n  // mock slider client size width so that value calculation works\n  const clientSizeStub = mockHTMLElementClientSize('offsetWidth', 500);\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget\n          {...defaultProps}\n          onClose={onClose}\n          toggleAnimation={toggleAnimation}\n          updateAnimationSpeed={updateAnimationSpeed}\n          setFilterAnimationWindow={setFilterAnimationWindow}\n          setFilterPlot={setFilterPlot}\n          setFilterAnimationTime={setFilterAnimationTime}\n        />\n      </IntlWrapper>,\n      {\n        attachTo: document.body\n      }\n    );\n  }, 'mount TimeWidget should not fail');\n\n  // t.ok(setFilterPlot.calledOnce, 'should call setFilterPlot when 1st mount to set plotType');\n  // t.deepEqual(\n  //   setFilterPlot.getCall(0).args,\n  //   [0, {plotType: {type: 'histogram'}}, undefined],\n  //   'should set plotType to linechart'\n  // );\n  t.equal(wrapper.find(Icons.Close).length, 1, 'should render Close icon');\n  t.equal(wrapper.find(Icons.Reset).length, 1, 'should render Reset icon');\n  t.equal(wrapper.find(Icons.Play).length, 1, 'should render Play icon');\n  t.equal(wrapper.find(Icons.FreeWindow).length, 1, 'should render Window icon');\n  t.equal(\n    wrapper.find(AnimationSpeedSlider).length,\n    0,\n    'should  not render AnimationSpeedSlider iniitally'\n  );\n  t.equal(\n    wrapper.find('.animation-window-control').length,\n    0,\n    'should not render AnimationWindowControl initially'\n  );\n\n  // hit play\n  wrapper.find(Icons.Play).at(0).simulate('click');\n  t.deepEqual(toggleAnimation.args[0], [0], 'should call toggle animation');\n\n  // hit speed icon\n  wrapper.find(Icons.Speed).at(0).simulate('click');\n\n  t.equal(wrapper.find(AnimationSpeedSlider).length, 1, 'should render AnimationSpeedSlider');\n  t.equal(\n    wrapper.find(AnimationSpeedSlider).at(0).find('.kg-range-slider__handle').length,\n    2,\n    'should render 2 speed slider handle'\n  );\n  const sliderHandle = wrapper\n    .find(AnimationSpeedSlider)\n    .at(0)\n    .find('.kg-range-slider__handle')\n    .at(1);\n\n  sliderHandle.simulate('mousedown', {clientX: 0});\n\n  // simulate slider move\n  const mouseMove = new window.MouseEvent('mousemove', {clientX: 100});\n  document.dispatchEvent(mouseMove);\n\n  // test updateAnimationSpeed\n  t.deepEqual(updateAnimationSpeed.args[0], [0, 2], 'should call updateAnimationSpeed with speed');\n\n  // hit animation window\n  wrapper.find(Icons.FreeWindow).at(0).simulate('click');\n  t.equal(\n    wrapper.find('.animation-window-control').length,\n    1,\n    'should render AnimationWindowControl initially'\n  );\n  t.equal(\n    wrapper.find('.animation-window-control').at(0).find(IconButton).length,\n    1,\n    'should render 1 animate window options'\n  );\n\n  // select an animation option\n  wrapper.find('.animation-window-control').at(0).find(IconButton).at(0).simulate('click');\n\n  t.deepEqual(\n    setFilterAnimationWindow.args[0],\n    [{id: StateWFilters.visState.filters[0].id, animationWindow: 'incremental'}],\n    'should call setFilterAnimationWindow'\n  );\n\n  // click yaxis select\n  wrapper.find('.item-selector__dropdown').at(0).simulate('click');\n  t.equal(wrapper.find(Typeahead).length, 1, 'should render dropdown select');\n\n  wrapper.find(Typeahead).find('.field-selector_list-item').at(0).simulate('click');\n  t.ok(setFilterPlot.calledOnce, 'should call setFilterPlot again');\n  t.equal(setFilterPlot.getCall(0).args[0], 0, 'should pass filteridx to setFilterPlot');\n  t.equal(\n    setFilterPlot.getCall(0).args[1].yAxis.name,\n    'gps_data.lat',\n    'should pass correct yAxis to setFilterPlot'\n  );\n\n  // hit close\n  wrapper.find(Icons.Close).at(0).simulate('click');\n\n  t.deepEqual(onClose.calledOnce, true, 'should call enlarged filter to close');\n\n  wrapper.detach();\n  clientSizeStub.restore();\n  t.end();\n});\n\ntest('Components -> TimeWidget.mount -> test setFilterAnimationTime', t => {\n  const setFilterAnimationTime = sinon.spy();\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget {...defaultProps} setFilterAnimationTime={setFilterAnimationTime} />\n      </IntlWrapper>,\n      {\n        attachTo: document.body\n      }\n    );\n  }, 'mount TimeWidget should not fail');\n\n  // mock slider client size width so that value calculation works\n  const clientSizeStub = mockHTMLElementClientSize('offsetWidth', 500);\n  t.equal(wrapper.find(RangeSlider).length, 1, 'should render RangeSlider');\n  const rangeSlider = wrapper.find(RangeSlider).at(0);\n\n  t.equal(rangeSlider.find(SliderHandle).length, 2, 'should render 2 slider handle');\n\n  rangeSlider\n    .find(SliderHandle)\n    .at(0)\n    .find('.kg-range-slider__handle')\n    .at(0)\n    .simulate('mousedown', {clientX: 0});\n  // simulate slider move\n  const mouseMove = new window.MouseEvent('mousemove', {clientX: 100});\n  document.dispatchEvent(mouseMove);\n\n  // test updateAnimationSpeed\n  t.deepEqual(\n    setFilterAnimationTime.args[0],\n    [0, 'value', [1474594560000, 1474617600000]],\n    'should call setFilterAnimationTime with new value'\n  );\n\n  // cleanup\n  wrapper.detach();\n  clientSizeStub.restore();\n  t.end();\n});\n\ntest('Components -> TimeWidget.mount -> test floating time display', t => {\n  const topSelector = '.animation-control__time-display__top';\n  const bottomSelector = '.animation-control__time-display__bottom';\n  let wrapper;\n  // because we are using locale based formats, we set a locale here to make sure\n  // result are always the same\n  moment.locale('en');\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'mount TimeWidget should not fail');\n\n  const timeDisplay = wrapper.find(FloatingTimeDisplay);\n  t.equal(timeDisplay.find(topSelector).length, 1, 'should render 1 top row');\n  t.equal(\n    timeDisplay.find(topSelector).at(0).find('.time-value').text(),\n    '09/23/2016',\n    'should render correct date'\n  );\n\n  t.equal(timeDisplay.find(bottomSelector).length, 1, 'should render 1 bottom row');\n  t.equal(\n    timeDisplay.find(bottomSelector).at(0).find('.time-value').length,\n    2,\n    'should render 2 time value'\n  );\n  t.equal(\n    timeDisplay.find(bottomSelector).at(0).find('.time-value').at(0).text(),\n    '5:00:00 AM',\n    'should render correct time'\n  );\n  t.equal(\n    timeDisplay.find(bottomSelector).at(0).find('.time-value').at(1).text(),\n    '8:00:00 AM',\n    'should render correct time'\n  );\n\n  // set TimeWidget prop\n  // filter with timezone and custom Format\n  wrapper.setProps({\n    children: <TimeWidget {...defaultProps} filter={nextState.filters[0]} />\n  });\n\n  // check time display again\n  const timeDisplay2 = wrapper.find(FloatingTimeDisplay);\n  t.equal(\n    timeDisplay2.find(bottomSelector).at(0).find('.time-value').at(0).text(),\n    '2016 Sep 23 01:00 am',\n    'should render correct time format and timezone'\n  );\n\n  t.equal(\n    timeDisplay2.find(bottomSelector).at(0).find('.time-value').at(1).text(),\n    '2016 Sep 23 04:00 am',\n    'should render correct time format and timezone'\n  );\n\n  t.end();\n});\n\ntest('Components -> TimeWidget.mount -> TimeSliderMarker', t => {\n  const clientSizeStub = mockHTMLElementClientSize('offsetWidth', 500);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'mount TimeWidget should not fail');\n\n  t.equal(wrapper.find(TimeSliderMarker).length, 1, 'should render TimeSliderMarker');\n  let d3Html = wrapper.find(TimeSliderMarker).at(0).find('.x.axis').html();\n\n  // moment.utc(1474588800000) -> \"2016-09-23 00:00\"\n  // moment.utc(1474617600000) -> \"2016-09-23 08:00\"\n  // Enzyme cant detect element appended by d3\n  const expectedMarks = [\n    'Fri 23',\n    '01 AM',\n    '02 AM',\n    '03 AM',\n    '04 AM',\n    '05 AM',\n    '06 AM',\n    '07 AM',\n    '08 AM'\n  ];\n\n  expectedMarks.forEach(mark => {\n    t.ok(\n      d3Html.includes(`<text fill=\"currentColor\" y=\"8\" dy=\"0.71em\">${mark}</text>`),\n      `should render correct time marker ${mark}`\n    );\n  });\n\n  // set TimeWidget prop\n  wrapper.setProps({\n    children: <TimeWidget {...defaultProps} filter={nextState.filters[0]} />\n  });\n\n  d3Html = wrapper.find(TimeSliderMarker).at(0).find('.x.axis').html();\n\n  // moment.utc(1474588800000).tz('America/New_York') -> \"2016-09-22 20:00\"\n  // moment.utc(1474617600000).tz('America/New_York') -> \"2016-09-23 04:00\"\n  // Enyme cant detect element appended by d3\n  const expectedMarks2 = [\n    '20 PM',\n    '21 PM',\n    '22 PM',\n    '23 PM',\n    'Fri 23',\n    '01 AM',\n    '02 AM',\n    '03 AM',\n    '04 AM'\n  ];\n\n  expectedMarks2.forEach(mark => {\n    t.ok(\n      d3Html.includes(`<text fill=\"currentColor\" y=\"8\" dy=\"0.71em\">${mark}</text>`),\n      `should render correct time marker ${mark}`\n    );\n  });\n\n  const invalidFilter = {\n    ...StateWFilters.visState.filters[0],\n    domain: null\n  };\n\n  // set TimeWidget prop\n  t.doesNotThrow(() => {\n    wrapper.setProps({\n      children: <TimeWidget {...defaultProps} filter={invalidFilter} />\n    });\n  }, 'mount TimeWidget with invalid filter should not fail');\n\n  clientSizeStub.restore();\n  t.end();\n});\n\ntest('Components -> TimeWidget.mount -> TimeTitle', t => {\n  const selector = '.time-range-slider__time-title .time-value span';\n  let wrapper;\n  // because we are using locale based formats, we set a locale here to make sure\n  // result are always the same\n  moment.locale('en');\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TimeWidget {...defaultProps} showTimeDisplay={false} />\n      </IntlWrapper>\n    );\n  }, 'mount TimeWidget with showTimeDisplay false');\n\n  t.equal(\n    wrapper.find(TimeRangeSliderTimeTitle).length,\n    1,\n    'should render TimeRangeSliderTimeTitle'\n  );\n  t.equal(wrapper.find(selector).length, 2, 'Should render 2 time title value');\n\n  let expected = ['09/23/2016 5:00:00 AM', '09/23/2016 8:00:00 AM'];\n  wrapper.find(selector).forEach((span, i) => {\n    t.equal(span.text(), expected[i], `should render correct time value ${expected[i]}`);\n  });\n\n  // set TimeWidget prop with timezone and custom Format time filter\n  wrapper.setProps({\n    children: <TimeWidget {...defaultProps} showTimeDisplay={false} filter={nextState.filters[0]} />\n  });\n\n  expected = ['2016 Sep 23 01:00 am', '2016 Sep 23 04:00 am'];\n  wrapper.find(selector).forEach((span, i) => {\n    t.equal(span.text(), expected[i], `should render correct time value ${expected[i]}`);\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/geocoder-panel-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {GeocoderPanelFactory, appInjector, testForCoordinates} from '@kepler.gl/components';\nimport {cmpDatasetData, cmpObjectKeys} from '../../helpers/comparison-utils';\n\nimport {InitialState} from 'test/helpers/mock-state';\n\nconst GeocoderPanel = appInjector.get(GeocoderPanelFactory);\nconst MAPBOX_TOKEN = 'pk.eyJ1Ijoi.d9YD6z';\n\ntest('GeocoderPanel - render', t => {\n  const enabled = true;\n  const updateVisData = sinon.spy();\n  const removeDataset = sinon.spy();\n  const updateMap = sinon.spy();\n  const mockMapState = {\n    latitude: 33,\n    longitude: 127,\n    zoom: 8,\n    width: 800,\n    height: 800\n  };\n  const layerOrder = ['layer_1', 'layer_2'];\n  const mockUiState = InitialState.uiState;\n\n  const mockGeoItem = {\n    center: [1, 55],\n    text: 'mock',\n    bbox: [-1, 50, 4, 65]\n  };\n\n  const mockGeoItemWithOutBbox = {\n    center: [1, 55],\n    text: 'mock'\n  };\n\n  const mockPayload = [\n    [\n      {\n        data: {\n          fields: [\n            {\n              name: 'lt',\n              id: 'lt',\n              displayName: 'lt',\n              format: '',\n              fieldIdx: 0,\n              type: 'integer',\n              analyzerType: 'INT',\n              valueAccessor: values => values[0]\n            },\n            {\n              name: 'ln',\n              id: 'ln',\n              displayName: 'ln',\n              format: '',\n              fieldIdx: 1,\n              type: 'integer',\n              analyzerType: 'INT',\n              valueAccessor: values => values[1]\n            },\n            {\n              name: 'icon',\n              id: 'icon',\n              displayName: 'icon',\n              format: '',\n              fieldIdx: 2,\n              type: 'string',\n              analyzerType: 'STRING',\n              valueAccessor: values => values[2]\n            },\n            {\n              name: 'text',\n              id: 'text',\n              displayName: 'text',\n              format: '',\n              fieldIdx: 3,\n              type: 'string',\n              analyzerType: 'STRING',\n              valueAccessor: values => values[3]\n            }\n          ],\n          rows: [[55, 1, 'place', 'mock']]\n        },\n        info: {\n          hidden: true,\n          id: 'geocoder_dataset',\n          label: 'geocoder_dataset'\n        }\n      }\n    ],\n    {\n      keepExistingConfig: true\n    },\n    {\n      visState: {\n        layers: [\n          {\n            id: 'geocoder_layer',\n            type: 'icon',\n            config: {\n              label: 'Geocoder Layer',\n              color: [255, 0, 0],\n              dataId: 'geocoder_dataset',\n              columns: {\n                lat: 'lt',\n                lng: 'ln',\n                icon: 'icon',\n                label: 'text'\n              },\n              isVisible: true,\n              hidden: true,\n              visConfig: {\n                radius: 80\n              }\n            }\n          }\n        ],\n        layerOrder: ['geocoder_layer', 'layer_1', 'layer_2']\n      }\n    }\n  ];\n\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <GeocoderPanel\n          isGeocoderEnabled={enabled}\n          mapboxApiAccessToken={MAPBOX_TOKEN}\n          mapState={mockMapState}\n          uiState={mockUiState}\n          updateVisData={updateVisData}\n          removeDataset={removeDataset}\n          updateMap={updateMap}\n          layerOrder={layerOrder}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  t.equal(wrapper.find(GeocoderPanel).length, 1, 'Should display 1 GeoCoderPanel');\n  t.equal(wrapper.find('GeoCoder').length, 1, 'Should display 1 Geocoder');\n\n  const geoCoderInstance = wrapper.find('GeoCoder');\n  const onSelected = geoCoderInstance.props().onSelected;\n  const onDeleteMarker = geoCoderInstance.props().onDeleteMarker;\n\n  onSelected(null, mockGeoItem);\n  t.deepEqual(\n    removeDataset.args,\n    [['geocoder_dataset']],\n    'Should call removeDataset on onSelected'\n  );\n\n  cmpObjectKeys(\n    t,\n    [mockPayload],\n    updateVisData.args,\n    'onSelected payload should have the correct params'\n  );\n  const actualDatasets = updateVisData.args[0][0];\n  const mockDatasets = mockPayload[0];\n\n  mockDatasets.forEach(mockDataset => {\n    const {data: mockDatasetData, ...restMockDataset} = mockDataset;\n    const {data: actualDatasetData, ...restActualDataset} = actualDatasets[0];\n    cmpDatasetData(t, mockDatasetData, actualDatasetData, mockDataset.id);\n    t.deepEqual(\n      restActualDataset,\n      restMockDataset,\n      `onSelected options dataset.${mockDataset.id} should be the same`\n    );\n  });\n\n  t.deepEqual(updateVisData.args[0][1], mockPayload[1], 'onSelected options should be correct');\n  t.deepEqual(\n    updateVisData.args[0][2],\n    mockPayload[2],\n    'onSelected configuration should be correct'\n  );\n  const newVP = updateMap.args[0][0];\n\n  t.deepEqual(\n    {latitude: newVP.latitude, longitude: newVP.longitude, zoom: newVP.zoom},\n    {latitude: 57.5, longitude: 1.5, zoom: 4.307606395110668},\n    'Should call updateMap action on onSelected w/ new viewport'\n  );\n\n  t.ok(newVP.transitionInterpolator, 'Should call updateMap action with transitionInterpolator');\n\n  onSelected(null, mockGeoItemWithOutBbox);\n\n  const newVP2 = updateMap.args[1][0];\n  t.deepEqual(\n    {latitude: newVP2.latitude, longitude: newVP2.longitude, zoom: newVP2.zoom},\n    {latitude: 55, longitude: 1, zoom: 11.655698543267773},\n    'Should call updateMapaction on onSelected w/o bbox'\n  );\n\n  onDeleteMarker();\n  t.deepEqual(\n    removeDataset.args[1],\n    ['geocoder_dataset'],\n    'Should be dispatching removeDataset action on removeMarker'\n  );\n\n  t.end();\n});\n\ntest('Geocoder -> testForCoordinates', t => {\n  t.deepEqual(\n    testForCoordinates('21.22,-138.0'),\n    [true, -138.0, 21.22],\n    'should recognize valid coordinates'\n  );\n\n  t.deepEqual(\n    testForCoordinates(' -21.122, -123.4321 '),\n    [true, -123.4321, -21.122],\n    'should recognize valid coordinates and trim spaces, and a whitespace after the comma'\n  );\n\n  t.deepEqual(\n    testForCoordinates('san francisco'),\n    [false, 'san francisco'],\n    'should recognize invalid coordinates'\n  );\n\n  t.deepEqual(\n    testForCoordinates('91,123'),\n    [false, '91,123'],\n    'should recognize invalid integer coordinates'\n  );\n\n  t.deepEqual(\n    testForCoordinates('91.0,-138.0'),\n    [false, '91.0,-138.0'],\n    'should recognize out of bounds latitude'\n  );\n\n  t.deepEqual(\n    testForCoordinates('50.0,200.0'),\n    [false, '50.0,200.0'],\n    'should recognize out of bounds longitude'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/helpers.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {useEffect, useRef} from 'react';\n\n// Hook to check which prop change causing component rerender\nexport function useTraceUpdate(props) {\n  const prev = useRef(props);\n  useEffect(() => {\n    const changedProps = Object.entries(props).reduce((ps, [k, v]) => {\n      if (prev.current[k] !== v) {\n        ps[k] = [prev.current[k], v];\n      }\n      return ps;\n    }, {});\n    if (Object.keys(changedProps).length > 0) {\n      // eslint-disable-next-line no-console\n      console.log('Changed props:', changedProps);\n    }\n    prev.current = props;\n  });\n}\n\nexport function useTraceUpdateClass(props, prev) {\n  const changedProps = Object.entries(props).reduce((ps, [k, v]) => {\n    if (prev[k] !== v) {\n      ps[k] = [prev[k], v];\n    }\n    return ps;\n  }, {});\n  if (Object.keys(changedProps).length > 0) {\n    // eslint-disable-next-line no-console\n    console.log('Changed props:', changedProps);\n  }\n}\n"
  },
  {
    "path": "test/browser/components/hooks/use-dnd-effects.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {renderHook, act} from '@testing-library/react';\nimport {useDispatch} from 'react-redux';\nimport {useDndEffects} from '@kepler.gl/components';\nimport {reorderEffect, updateEffect} from '@kepler.gl/actions';\nimport {\n  SORTABLE_EFFECT_PANEL_TYPE,\n  SORTABLE_EFFECT_TYPE\n} from '@kepler.gl/components/common/dnd-layer-items';\nimport {reorderEffectOrder} from '@kepler.gl/utils';\n\n// Mock useDispatch hook\njest.mock('react-redux', () => ({\n  ...jest.requireActual('react-redux'),\n  useDispatch: jest.fn()\n}));\n\n// Mock dependencies\njest.mock('@kepler.gl/actions', () => ({\n  ...jest.requireActual('@kepler.gl/actions'),\n  reorderEffect: jest.fn(),\n  updateEffect: jest.fn()\n}));\n\njest.mock('@kepler.gl/utils', () => ({\n  ...jest.requireActual('@kepler.gl/utils'),\n  reorderEffectOrder: jest.fn()\n}));\n\ndescribe('useDndEffects', () => {\n  const effects = [\n    {id: 1, isConfigActive: false},\n    {id: 2, isConfigActive: false},\n    {id: 3, isConfigActive: false}\n  ];\n\n  const effectOrder = [2, 1, 3];\n\n  const dispatchMock = jest.fn();\n\n  beforeEach(() => {\n    useDispatch.mockReturnValue(dispatchMock);\n  });\n\n  afterEach(() => {\n    useDispatch.mockReset();\n    dispatchMock.mockReset();\n    reorderEffect.mockReset();\n    updateEffect.mockReset();\n    reorderEffectOrder.mockReset();\n  });\n\n  test('should initialize with correct initial state', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    expect(result.current.activeEffect).toBeUndefined();\n    expect(result.current.onDragStart).toBeInstanceOf(Function);\n    expect(result.current.onDragEnd).toBeInstanceOf(Function);\n  });\n\n  test('onDragStart should update activeEffect and dispatch updateEffect if config is active', () => {\n    const currentEffects = [\n      {id: 1, isConfigActive: false},\n      {id: 2, isConfigActive: true},\n      {id: 3, isConfigActive: false}\n    ];\n    const {result} = renderHook(() => useDndEffects(currentEffects, effectOrder));\n\n    const event = {\n      active: {id: 2}\n    };\n\n    act(() => {\n      result.current.onDragStart(event);\n    });\n\n    expect(result.current.activeEffect).toEqual(currentEffects[1]);\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(updateEffect).toHaveBeenCalledWith(currentEffects[1].id, {isConfigActive: false});\n  });\n\n  test('onDragStart should not dispatch updateEffect if config is not active', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    const event = {\n      active: {id: 1}\n    };\n\n    act(() => {\n      result.current.onDragStart(event);\n    });\n\n    expect(result.current.activeEffect).toEqual(effects[0]);\n    expect(dispatchMock).not.toHaveBeenCalled();\n    expect(updateEffect).not.toHaveBeenCalled();\n  });\n\n  test('onDragEnd should reset activeEffect if overType is not defined', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {}\n    };\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(result.current.activeEffect).toBeUndefined();\n  });\n\n  test('onDragEnd should dispatch reorderEffect when overType is SORTABLE_EFFECT_TYPE', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: SORTABLE_EFFECT_TYPE\n          }\n        }\n      }\n    };\n\n    const newEffectOrder = [1, 2, 3];\n    reorderEffectOrder.mockReturnValue(newEffectOrder);\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(reorderEffect).toHaveBeenCalledWith(newEffectOrder);\n  });\n\n  test('onDragEnd should dispatch reorderEffect when overType is SORTABLE_EFFECT_PANEL_TYPE', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: SORTABLE_EFFECT_PANEL_TYPE\n          }\n        }\n      }\n    };\n\n    const newEffectOrder = [1, 3, 2];\n    reorderEffectOrder.mockReturnValue(newEffectOrder);\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(reorderEffect).toHaveBeenCalledWith(newEffectOrder);\n  });\n\n  test('onDragEnd should do nothing if overType does not match any case', () => {\n    const {result} = renderHook(() => useDndEffects(effects, effectOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: 'UNKNOWN_TYPE'\n          }\n        }\n      }\n    };\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).not.toHaveBeenCalled();\n    expect(reorderEffect).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "test/browser/components/hooks/use-dnd-layers.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {renderHook, act} from '@testing-library/react';\nimport {useDispatch} from 'react-redux';\nimport {useDndLayers} from '@kepler.gl/components';\nimport {layerConfigChange, reorderLayer, toggleLayerForMap} from '@kepler.gl/actions';\nimport {\n  DROPPABLE_MAP_CONTAINER_TYPE,\n  SORTABLE_LAYER_TYPE,\n  SORTABLE_SIDE_PANEL_TYPE\n} from '@kepler.gl/components/common/dnd-layer-items';\nimport {reorderLayerOrder} from '@kepler.gl/reducers';\n\n// Mock useDispatch hook\njest.mock('react-redux', () => ({\n  ...jest.requireActual('react-redux'),\n  useDispatch: jest.fn()\n}));\n\n// Mock dependencies\njest.mock('@kepler.gl/actions', () => ({\n  ...jest.requireActual('@kepler.gl/actions'),\n  layerConfigChange: jest.fn(),\n  reorderLayer: jest.fn(),\n  toggleLayerForMap: jest.fn()\n}));\n\njest.mock('@kepler.gl/reducers', () => ({\n  reorderLayerOrder: jest.fn()\n}));\n\ndescribe('useDndLayers', () => {\n  const layers = [\n    {id: 1, name: 'Layer 1', config: {isConfigActive: false}},\n    {id: 2, name: 'Layer 2', config: {isConfigActive: false}},\n    {id: 3, name: 'Layer 3', config: {isConfigActive: false}}\n  ];\n\n  const layerOrder = [2, 1, 3];\n\n  const dispatchMock = jest.fn();\n\n  beforeEach(() => {\n    useDispatch.mockReturnValue(dispatchMock);\n  });\n\n  afterEach(() => {\n    useDispatch.mockReset();\n    dispatchMock.mockReset();\n    layerConfigChange.mockReset();\n    reorderLayer.mockReset();\n    toggleLayerForMap.mockReset();\n    reorderLayerOrder.mockReset();\n  });\n\n  test('should initialize with correct initial state', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    expect(result.current.activeLayer).toBeUndefined();\n    expect(result.current.onDragStart).toBeInstanceOf(Function);\n    expect(result.current.onDragEnd).toBeInstanceOf(Function);\n  });\n\n  test('onDragStart should update activeLayer and dispatch layerConfigChange if config is active', () => {\n    const currentLayers = [\n      {id: 1, name: 'Layer 1', config: {isConfigActive: false}},\n      {id: 2, name: 'Layer 2', config: {isConfigActive: true}},\n      {id: 3, name: 'Layer 3', config: {isConfigActive: false}}\n    ];\n    const {result} = renderHook(() => useDndLayers(currentLayers, layerOrder));\n\n    const event = {\n      active: {id: 2}\n    };\n\n    act(() => {\n      result.current.onDragStart(event);\n    });\n\n    expect(result.current.activeLayer).toEqual(currentLayers[1]);\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(layerConfigChange).toHaveBeenCalledWith(currentLayers[1], {isConfigActive: false});\n  });\n\n  test('onDragStart should not dispatch layerConfigChange if config is not active', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 1}\n    };\n\n    act(() => {\n      result.current.onDragStart(event);\n    });\n\n    expect(result.current.activeLayer).toEqual(layers[0]);\n    expect(dispatchMock).not.toHaveBeenCalled();\n    expect(layerConfigChange).not.toHaveBeenCalled();\n  });\n\n  test('onDragEnd should reset activeLayer if overType is not defined', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {}\n    };\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(result.current.activeLayer).toBeUndefined();\n  });\n\n  test('onDragEnd should dispatch toggleLayerForMap when overType is DROPPABLE_MAP_CONTAINER_TYPE', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 1},\n      over: {\n        data: {\n          current: {\n            type: DROPPABLE_MAP_CONTAINER_TYPE,\n            index: 1\n          }\n        }\n      }\n    };\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(toggleLayerForMap).toHaveBeenCalledWith(1, 1);\n  });\n\n  test('onDragEnd should dispatch reorderLayer when overType is SORTABLE_LAYER_TYPE', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: SORTABLE_LAYER_TYPE\n          }\n        }\n      }\n    };\n\n    const newLayerOrder = [1, 2, 3];\n    reorderLayerOrder.mockReturnValue(newLayerOrder);\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(reorderLayer).toHaveBeenCalledWith(newLayerOrder);\n  });\n\n  test('onDragEnd should dispatch reorderLayer when overType is SORTABLE_SIDE_PANEL_TYPE', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: SORTABLE_SIDE_PANEL_TYPE\n          }\n        }\n      }\n    };\n\n    const newLayerOrder = [1, 3, 2];\n    reorderLayerOrder.mockReturnValue(newLayerOrder);\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).toHaveBeenCalledTimes(1);\n    expect(reorderLayer).toHaveBeenCalledWith(newLayerOrder);\n  });\n\n  test('onDragEnd should do nothing if overType does not match any case', () => {\n    const {result} = renderHook(() => useDndLayers(layers, layerOrder));\n\n    const event = {\n      active: {id: 2},\n      over: {\n        data: {\n          current: {\n            type: 'UNKNOWN_TYPE'\n          }\n        }\n      }\n    };\n\n    act(() => {\n      result.current.onDragEnd(event);\n    });\n\n    expect(dispatchMock).not.toHaveBeenCalled();\n    expect(reorderLayer).not.toHaveBeenCalled();\n    expect(toggleLayerForMap).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "test/browser/components/hooks/use-legend-position.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {renderHook} from '@testing-library/react';\nimport {useLegendPosition} from '@kepler.gl/components';\n\nconst THEME = {\n  sidePanel: {width: 100}\n};\n\ndescribe('useLegendPosition', () => {\n  beforeEach(() => {\n    document.body.innerHTML = `\n    <div\n      class=\"kepler-gl\"\n      style=\"position: absolute; width: 800px; height: 600px\"\n    >\n      <div id=\"map-legend\">\n        <div id=\"map-legend-content\"/>\n      </div>\n    </div>`;\n  });\n\n  test('should return default position', () => {\n    const {\n      result: {\n        current: {positionStyles}\n      }\n    } = renderHook(() =>\n      useLegendPosition({\n        legendContentRef: {current: document.querySelector('#map-legend-content')},\n        isSidePanelShown: false,\n        settings: {},\n        onChangeSettings: jest.fn(),\n        theme: THEME\n      })\n    );\n    expect(positionStyles).toEqual({right: 10, bottom: 30});\n  });\n\n  test('should respect position from settings', () => {\n    const {\n      result: {\n        current: {positionStyles}\n      }\n    } = renderHook(() =>\n      useLegendPosition({\n        legendContentRef: {current: document.querySelector('#map-legend-content')},\n        isSidePanelShown: false,\n        settings: {\n          position: {x: 100, y: 200, anchorX: 'left', anchorY: 'top'}\n        },\n        onChangeSettings: jest.fn(),\n        theme: THEME\n      })\n    );\n    expect(positionStyles).toEqual({left: 100, top: 200});\n  });\n\n  test('should calculate maxContentHeight from mapWidth and mapHeight', () => {\n    const {\n      result: {\n        current: {maxContentHeight}\n      }\n    } = renderHook(() =>\n      useLegendPosition({\n        legendContentRef: {current: document.querySelector('#map-legend-content')},\n        isSidePanelShown: false,\n        settings: {},\n        onChangeSettings: jest.fn(),\n        theme: THEME,\n        mapHeight: 600,\n        mapWidth: 800\n      })\n    );\n    // maxContentHeight = height - MARGIN.top - MARGIN.bottom - MAP_CONTROL_HEADER_FULL_HEIGHT\n    // = 600 - 10 - 30 - 34 = 526\n    expect(maxContentHeight).toBe(526);\n  });\n\n  test('should return undefined maxContentHeight when mapWidth and mapHeight not provided', () => {\n    const {\n      result: {\n        current: {maxContentHeight}\n      }\n    } = renderHook(() =>\n      useLegendPosition({\n        legendContentRef: {current: document.querySelector('#map-legend-content')},\n        isSidePanelShown: false,\n        settings: {},\n        onChangeSettings: jest.fn(),\n        theme: THEME\n      })\n    );\n    expect(maxContentHeight).toBeUndefined();\n  });\n\n  test('should clamp contentHeight when it exceeds maxContentHeight', () => {\n    const onChangeSettings = jest.fn();\n    const legendContent = document.querySelector('#map-legend-content');\n    legendContent.getBoundingClientRect = jest.fn(() => ({\n      width: 200,\n      height: 300,\n      top: 0,\n      left: 0,\n      bottom: 300,\n      right: 200\n    }));\n\n    renderHook(() =>\n      useLegendPosition({\n        legendContentRef: {current: legendContent},\n        isSidePanelShown: false,\n        settings: {\n          position: {x: 100, y: 100, anchorX: 'left', anchorY: 'top'},\n          contentHeight: 1000 // Exceeds maxContentHeight\n        },\n        onChangeSettings,\n        theme: THEME,\n        mapHeight: 600,\n        mapWidth: 800\n      })\n    );\n\n    // Should clamp contentHeight to maxContentHeight (526)\n    expect(onChangeSettings).toHaveBeenCalled();\n    const callsWithContentHeight = onChangeSettings.mock.calls.filter(\n      call => call[0].contentHeight !== undefined\n    );\n    if (callsWithContentHeight.length > 0) {\n      expect(\n        callsWithContentHeight[callsWithContentHeight.length - 1][0].contentHeight\n      ).toBeLessThanOrEqual(526);\n    }\n  });\n});\n"
  },
  {
    "path": "test/browser/components/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './injector-test';\nimport './container-test';\nimport './kepler-gl-test';\n\nimport './modals';\nimport './notifications';\nimport './map';\nimport './side-panel';\n\nimport './common';\nimport './editor';\nimport './filters';\nimport './geocoder-panel-test';\nimport './tooltip-config-test';\nimport './bottom-widget-test';\nimport './plot-container-test';\nimport './effects';\n"
  },
  {
    "path": "test/browser/components/injector-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport React from 'react';\nimport test from 'tape';\nimport {mount} from 'enzyme';\nimport configureStore from 'redux-mock-store';\nimport {Provider} from 'react-redux';\nimport sinon from 'sinon';\nimport {console as Console} from 'global/window';\n\nimport {\n  withState,\n  injector,\n  injectComponents,\n  PanelHeaderFactory,\n  provideRecipesToInjector\n} from '@kepler.gl/components';\n\nimport {keplerGlInit} from '@kepler.gl/actions';\nimport {\n  keplerGlReducerCore as coreReducer,\n  visStateLens,\n  uiStateLens,\n  mapStateLens,\n  mapStyleLens\n} from '@kepler.gl/reducers';\n\nconst mockStore = configureStore();\nconst initialCoreState = coreReducer(undefined, keplerGlInit({}));\n\ntest('Components -> injector -> injectComponents', t => {\n  const CustomHeader = () => <div className=\"my-test-header\">smoothie</div>;\n  const myCustomHeaderFactory = () => CustomHeader;\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  // test if custom header is rendered\n  t.ok(wrapper.find('.my-test-header').length, 'should render custom header');\n  t.end();\n});\n\ntest('Components -> injector -> missing deps', t => {\n  const spy = sinon.spy(Console, 'error');\n  // eslint-disable-next-line react/display-name\n  const myCustomNameFactory = () => () => <div className=\"my-test-header-name\">name</div>;\n  // eslint-disable-next-line react/display-name\n  const myCustomHeaderFactory = Name => () =>\n    (\n      <div className=\"my-test-header-1\">\n        <Name />\n        smoothie\n      </div>\n    );\n  myCustomHeaderFactory.deps = [myCustomNameFactory];\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  t.ok(spy.notCalled, 'Should automatically add custom deps and not call console.error');\n\n  t.ok(wrapper.find('.my-test-header-name').length, 'should still render custom header name');\n\n  spy.restore();\n  t.end();\n});\n\ntest('Components -> injector -> wrong factory type', t => {\n  const spy = sinon.spy(Console, 'error');\n  // eslint-disable-next-line react/display-name\n  const myCustomHeaderFactory = Name => () =>\n    (\n      <div className=\"my-test-header-2\">\n        <Name />\n        smoothie\n      </div>\n    );\n\n  const KeplerGl = injectComponents([[undefined, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  t.equal(\n    spy.getCall(0).args[0],\n    'Error injecting factory: ',\n    'should warn when default factory is not provided'\n  );\n\n  t.equal(\n    spy.getCall(1).args[0],\n    'factory and its replacement should be a function',\n    'should warn when default factory is not provided'\n  );\n  // test if custom header is rendered\n  t.ok(wrapper.find('.side-panel__panel-header').length, 'should render default header');\n\n  spy.restore();\n  t.end();\n});\n\ntest('Components -> injector -> wrong replacement type', t => {\n  const spy = sinon.spy(Console, 'error');\n  // const spy = sinon.spy(Console, 'error');\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, undefined]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  t.ok(spy.calledTwice, 'should call console.error twice');\n  t.equal(\n    spy.getCall(0).args[0],\n    'Error injecting replacement for: ',\n    'should warn when replace factory is not provided'\n  );\n\n  // test if custom header is rendered\n  t.ok(wrapper.find('.side-panel__panel-header').length, 'should render default header');\n\n  spy.restore();\n  t.end();\n});\n\ntest('Components -> injector -> replace and render existing', t => {\n  myCustomHeaderFactory.deps = PanelHeaderFactory.deps;\n\n  function myCustomHeaderFactory(...deps) {\n    const PanelHeader = PanelHeaderFactory(...deps);\n    PanelHeader.defaultProps;\n    const MyHeader = props => {\n      return <PanelHeader {...props} appName=\"taro\" />;\n    };\n    return MyHeader;\n  }\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n      </Provider>\n    );\n  }, 'should not throw error when replace and render existing component');\n\n  t.equal(wrapper.find('.logo__link').text(), 'taro', 'should render provided prop');\n  t.end();\n});\n\ntest('Components -> injector -> withState.lens', t => {\n  const CustomHeader = () => <div className=\"my-test-header-3\">smoothie</div>;\n  const myCustomHeaderFactory = () =>\n    withState([visStateLens, mapStateLens, uiStateLens, mapStyleLens])(CustomHeader);\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  const header = wrapper.find(CustomHeader).at(0);\n  const props = header.props();\n\n  t.ok(header, 'should render CustomHeader');\n  // test if custom header is rendered\n  t.ok(props.visState, 'should add visState to props');\n  t.ok(props.mapState, 'should add mapState to props');\n  t.ok(props.mapStyle, 'should add mapStyle to props');\n  t.ok(props.uiState, 'should add uiState to props');\n\n  t.end();\n});\n\ntest('Components -> injector -> withState.mapStateToProps', t => {\n  const CustomHeader = () => <div className=\"my-test-header-3\">smoothie</div>;\n  const myCustomHeaderFactory = () =>\n    withState([], state => ({ids: Object.keys(state)}))(CustomHeader);\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  // test if custom header is rendered\n  const header = wrapper.find(CustomHeader).at(0);\n  const props = header.props();\n\n  t.ok(header, 'should render CustomHeader');\n  t.deepEqual(props.ids, ['keplerGl'], 'should run mapStateToProps');\n\n  t.end();\n});\n\ntest('Components -> injector -> actions', t => {\n  const CustomHeader = ({add}) => (\n    <div className=\"my-test-header-3\" onClick={add}>\n      smoothie\n    </div>\n  );\n\n  const testAction = () => ({type: 'ADD'});\n\n  const myCustomHeaderFactory = () =>\n    withState([], state => state, {add: testAction})(CustomHeader);\n\n  const KeplerGl = injectComponents([[PanelHeaderFactory, myCustomHeaderFactory]]);\n\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  const wrapper = mount(\n    <Provider store={store}>\n      <KeplerGl id=\"foo\" mapboxApiAccessToken=\"smoothie_and_milkshake\" />\n    </Provider>\n  );\n\n  // test if custom header is rendered\n  const header = wrapper.find(CustomHeader).at(0);\n  const props = header.props();\n\n  t.ok(header, 'should render CustomHeader');\n  t.ok(typeof props.add === 'function', 'should add actions');\n\n  wrapper.find(CustomHeader).simulate('click');\n\n  const lastAction = store.getActions().pop();\n  t.deepEqual(lastAction, {type: 'ADD'}, 'should dispatch custom actions');\n\n  t.end();\n});\n\ntest('Components -> injector -> provideRecipesToInjector', t => {\n  // Header1 -> Header 2 -> Header 3\n  const spyMyHeader3Factory = sinon.spy();\n  const spyMyHeader2Factory = sinon.spy();\n  const spyMyHeader1Factory = sinon.spy();\n\n  const myHeader3Factory = () => {\n    spyMyHeader3Factory('getHeader3');\n    const Header3 = () => <div className=\"my-test-header-3\">hello world</div>;\n    return Header3;\n  };\n\n  const myHeader2Factory = MyHeader3 => {\n    spyMyHeader2Factory('getHeader2');\n    const Header2 = () => (\n      <div className=\"my-test-header-2\">\n        <MyHeader3 />\n      </div>\n    );\n    return Header2;\n  };\n  myHeader2Factory.deps = [myHeader3Factory];\n\n  const myHeader1Factory = MyCustomHeader2 => {\n    spyMyHeader1Factory('getHeader1');\n    const Header1 = () => (\n      <div className=\"my-test-header-1\">\n        <MyCustomHeader2 />\n      </div>\n    );\n    return Header1;\n  };\n\n  myHeader1Factory.deps = [myHeader2Factory];\n\n  const recipe1 = [myHeader1Factory, myHeader1Factory];\n\n  provideRecipesToInjector([recipe1], injector());\n\n  t.equal(spyMyHeader1Factory.called, true, 'Should call myHeader1Factory');\n  t.equal(spyMyHeader2Factory.called, true, 'Should call myHeader2Factory');\n  t.equal(spyMyHeader3Factory.called, true, 'Should call myHeader3Factory');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/kepler-gl-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport React from 'react';\nimport test from 'tape';\nimport {mount} from 'enzyme';\nimport sinon from 'sinon';\nimport {drainTasksForTesting, succeedTaskWithValues} from 'react-palm/tasks';\nimport configureStore from 'redux-mock-store';\nimport {Provider} from 'react-redux';\n\nimport {keplerGlReducerCore as coreReducer, getDefaultMapStyles} from '@kepler.gl/reducers';\nimport {keplerGlInit, ActionTypes} from '@kepler.gl/actions';\nimport {\n  appInjector,\n  KeplerGlFactory,\n  SidePanelFactory,\n  MapContainerFactory,\n  BottomWidgetFactory,\n  ModalContainerFactory,\n  PlotContainerFactory,\n  GeocoderPanelFactory,\n  NotificationPanelFactory\n} from '@kepler.gl/components';\nimport {\n  DEFAULT_MAP_STYLES,\n  EXPORT_IMAGE_ID,\n  GEOCODER_DATASET_NAME,\n  KEPLER_UNFOLDED_BUCKET\n} from '@kepler.gl/constants';\n// mock state\nimport {StateWithGeocoderDataset} from 'test/helpers/mock-state';\n\nconst KeplerGl = appInjector.get(KeplerGlFactory);\nconst SidePanel = appInjector.get(SidePanelFactory);\nconst MapContainer = appInjector.get(MapContainerFactory);\nconst BottomWidget = appInjector.get(BottomWidgetFactory);\nconst ModalContainer = appInjector.get(ModalContainerFactory);\nconst PlotContainer = appInjector.get(PlotContainerFactory);\nconst GeocoderPanel = appInjector.get(GeocoderPanelFactory);\nconst NotificationPanel = appInjector.get(NotificationPanelFactory);\n\nconst initialCoreState = coreReducer(\n  undefined,\n  keplerGlInit({mapboxApiAccessToken: 'smoothie-the-cat'})\n);\n\nconst initialState = {\n  keplerGl: {\n    map: initialCoreState\n  }\n};\n\nconst mockStore = configureStore();\n\ntest('Components -> KeplerGl -> Mount', t => {\n  drainTasksForTesting();\n  // mount with empty store\n  const store = mockStore(initialState);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  t.equal(wrapper.find(KeplerGl).length, 1, 'should render KeplerGl');\n  t.equal(wrapper.find(SidePanel).length, 1, 'should render SidePanel');\n  t.equal(wrapper.find(MapContainer).length, 1, 'should render MapContainer');\n  t.equal(wrapper.find(BottomWidget).length, 1, 'should render BottomWidget');\n  t.equal(wrapper.find(ModalContainer).length, 1, 'should render ModalContainer');\n  t.equal(wrapper.find(PlotContainer).length, 0, 'should not render PlotContainer');\n  t.equal(wrapper.find(NotificationPanel).length, 1, 'should render NotificationPanel');\n  t.equal(wrapper.find(GeocoderPanel).length, 0, 'should not render GeocoderPanel');\n\n  t.end();\n});\n\ntest('Components -> KeplerGl -> Mount -> readOnly', t => {\n  drainTasksForTesting();\n  // mount with readOnly true\n  const initialStateReadonly = {\n    keplerGl: {\n      map: {\n        ...initialCoreState,\n        uiState: {\n          ...initialCoreState.uiState,\n          readOnly: true\n        }\n      }\n    }\n  };\n\n  const store = mockStore(initialStateReadonly);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  t.equal(wrapper.find(KeplerGl).length, 1, 'should render KeplerGl');\n  t.equal(wrapper.find(SidePanel).length, 0, 'should not render SidePanel');\n  t.equal(wrapper.find(MapContainer).length, 1, 'should render MapContainer');\n  t.equal(wrapper.find(BottomWidget).length, 1, 'should render BottomWidget');\n  t.equal(wrapper.find(ModalContainer).length, 1, 'should render ModalContainer');\n  t.equal(wrapper.find(PlotContainer).length, 0, 'should not render PlotContainer');\n  t.equal(wrapper.find(NotificationPanel).length, 1, 'should render NotificationPanel');\n  t.equal(wrapper.find(GeocoderPanel).length, 0, 'should not render GeocoderPanel');\n\n  t.end();\n});\n\ntest('Components -> KeplerGl -> Mount -> Plot', t => {\n  drainTasksForTesting();\n  // mount with readOnly true\n  const initialStatePlots = {\n    keplerGl: {\n      map: {\n        ...initialCoreState,\n        uiState: {\n          ...initialCoreState.uiState,\n          currentModal: EXPORT_IMAGE_ID,\n          exportImage: {\n            ...initialCoreState.uiState.exportImage,\n            exporting: true\n          }\n        }\n      }\n    }\n  };\n  const store = mockStore(initialStatePlots);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  t.equal(wrapper.find(KeplerGl).length, 1, 'should render KeplerGl');\n  t.equal(wrapper.find(SidePanel).length, 1, 'should render SidePanel');\n  t.equal(wrapper.find(MapContainer).length, 2, 'should render 2 MapContainer');\n  t.equal(wrapper.find(BottomWidget).length, 1, 'should render BottomWidget');\n  t.equal(wrapper.find(ModalContainer).length, 1, 'should render ModalContainer');\n  t.equal(wrapper.find(PlotContainer).length, 1, 'should render PlotContainer');\n  t.equal(wrapper.find(NotificationPanel).length, 1, 'should render NotificationPanel');\n  t.equal(wrapper.find(GeocoderPanel).length, 0, 'should not render GeocoderPanel');\n\n  t.end();\n});\n\ntest('Components -> KeplerGl -> Mount -> Split Maps', t => {\n  drainTasksForTesting();\n  // mount with readOnly true\n  const initialStateSplitMap = {\n    keplerGl: {\n      map: {\n        ...initialCoreState,\n        visState: {\n          ...initialCoreState.visState,\n          splitMaps: [{layers: {}}, {layers: {}}]\n        }\n      }\n    }\n  };\n\n  const store = mockStore(initialStateSplitMap);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  t.equal(wrapper.find(KeplerGl).length, 1, 'should render KeplerGl');\n  t.equal(wrapper.find(SidePanel).length, 1, 'should render SidePanel');\n  t.equal(wrapper.find(MapContainer).length, 2, 'should render 2 MapContainer');\n  t.equal(wrapper.find(BottomWidget).length, 1, 'should render BottomWidget');\n  t.equal(wrapper.find(ModalContainer).length, 1, 'should render ModalContainer');\n  t.equal(wrapper.find(PlotContainer).length, 0, 'should render PlotContainer');\n  t.equal(wrapper.find(NotificationPanel).length, 1, 'should render NotificationPanel');\n  t.equal(wrapper.find(GeocoderPanel).length, 0, 'should not render GeocoderPanel');\n\n  t.end();\n});\n\ntest('Components -> KeplerGl -> Mount -> Load default map style task', t => {\n  // mount with empty store\n  drainTasksForTesting();\n  const store = mockStore(initialState);\n\n  t.doesNotThrow(() => {\n    mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  const actions = store.getActions();\n  const expectedActions = [\n    {\n      type: ActionTypes.LOAD_MAP_STYLES,\n      payload: {\n        newStyles: getDefaultMapStyles(KEPLER_UNFOLDED_BUCKET),\n        onSuccess: undefined\n      }\n    }\n  ];\n  t.deepEqual(\n    actions,\n    expectedActions,\n    'Should mount kepler.gl and dispatch 1 action to load map styles'\n  );\n\n  // const tmpState0 = coreReducer(initialCoreState, actions[0]);\n  const tmpState = coreReducer(initialCoreState, actions[0]);\n  const [actionTask1, ...more] = drainTasksForTesting();\n  t.equal(more.length, 0, 'should dispatch 1 tasks');\n\n  const expectedTask = {\n    payload: [\n      {id: 'dark-matter', url: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'}\n    ]\n  };\n\n  t.deepEqual(actionTask1.payload, expectedTask.payload, 'should create task to load map styles');\n  t.deepEqual(tmpState.mapStyle.isLoading, {'dark-matter': true}, 'should set mapStyle isLoading');\n\n  const resultState1 = coreReducer(\n    tmpState,\n    succeedTaskWithValues(actionTask1, [{id: 'dark-matter', style: {layers: [], name: 'dark'}}])\n  );\n\n  const expectedStateMapStyles = {\n    ...tmpState.mapStyle.mapStyles,\n    'dark-matter': {\n      ...DEFAULT_MAP_STYLES[1],\n      icon: `${KEPLER_UNFOLDED_BUCKET}/${DEFAULT_MAP_STYLES[1].icon}`,\n      style: {layers: [], name: 'dark'}\n    }\n  };\n\n  t.deepEqual(\n    resultState1.mapStyle.mapStyles,\n    expectedStateMapStyles,\n    'should update state with loaded map styles'\n  );\n\n  t.deepEqual(\n    resultState1.mapStyle.isLoading,\n    {\n      'dark-matter': false\n    },\n    'should update state isLoading'\n  );\n\n  t.end();\n});\n\ntest('Components -> KeplerGl -> Mount -> Load custom map style task', t => {\n  drainTasksForTesting();\n  // mount with empty store\n  // set initialState to custom styleType\n  const initialCoreState1 = {\n    ...initialCoreState,\n    mapStyle: {\n      ...initialCoreState.mapStyle,\n      styleType: 'chai'\n    }\n  };\n  const initialState1 = {\n    keplerGl: {\n      map: initialCoreState1\n    }\n  };\n\n  const store = mockStore(initialState1);\n\n  //\n  const customStyle1 = {\n    id: 'smoothie',\n    url: 'mapbox://styles/smoothie/thecat'\n  };\n  const customStyle2 = {\n    id: 'milkshake',\n    style: {\n      id: 'mm',\n      layers: []\n    }\n  };\n  const customStyle3 = {\n    id: 'chai',\n    style: {\n      id: 'chai',\n      layers: []\n    },\n    layerGroups: [\n      {\n        slug: 'label',\n        defaultVisibility: true\n      }\n    ]\n  };\n  t.doesNotThrow(() => {\n    mount(\n      <Provider store={store}>\n        <KeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n          mapStyles={[customStyle1, customStyle2, customStyle3]}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  const actions = store.getActions();\n  const expectedActions = [\n    {\n      type: ActionTypes.LOAD_MAP_STYLES,\n      payload: {\n        newStyles: {\n          ...initialCoreState1.mapStyle.mapStyles,\n          milkshake: customStyle2,\n          chai: customStyle3,\n          smoothie: customStyle1\n        },\n        onSuccess: undefined\n      }\n    }\n  ];\n  t.deepEqual(\n    actions,\n    expectedActions,\n    'Should mount kepler.gl and dispatch 1 actions to load map styles'\n  );\n\n  const resultState1 = coreReducer(initialCoreState1, actions[0]);\n\n  const expectedMapStyleState1 = {\n    ...initialCoreState1.mapStyle,\n    mapStyles: {\n      ...initialCoreState1.mapStyle.mapStyles,\n      milkshake: {\n        id: 'milkshake',\n        style: {\n          id: 'mm',\n          layers: []\n        },\n        layerGroups: []\n      },\n      chai: customStyle3,\n      smoothie: {\n        id: 'smoothie',\n        url: 'mapbox://styles/smoothie/thecat',\n        layerGroups: []\n      }\n    },\n    visibleLayerGroups: {\n      label: true\n    },\n    threeDBuildingColor: [194.6103322548211, 191.81688250953655, 185.2988331038727],\n    bottomMapStyle: {id: 'chai', layers: []},\n    topMapStyle: null,\n    editable: 1\n  };\n\n  t.deepEqual(\n    resultState1.mapStyle,\n    expectedMapStyleState1,\n    'Should load map style into reducer and create layer groups'\n  );\n\n  Object.keys(resultState1.mapStyle).forEach(key => {\n    t.deepEqual(\n      resultState1.mapStyle[key],\n      expectedMapStyleState1[key],\n      `mapStyle[${key}] should be correct`\n    );\n  });\n\n  // Do not remove this. Necessary for testing flow\n  // eslint-disable-next-line no-unused-vars\n  const actionTask1 = drainTasksForTesting();\n\n  t.equal(actionTask1.length, 0, 'should not dispatch action to load styles');\n\n  t.end();\n});\n\n// Test data has only the 'geocoder_dataset' dataset\n// This function will return its name if it finds the dataset\n// in other case it will return null\nfunction findGeocoderDatasetName(wrapper) {\n  const datasetTitleContainer = wrapper.find('.dataset-name');\n  let result;\n  try {\n    result = datasetTitleContainer.text();\n  } catch (e) {\n    result = null;\n  }\n  return result;\n}\n\ntest('Components -> KeplerGl -> SidePanel -> Geocoder dataset display', t => {\n  drainTasksForTesting();\n\n  const toggleSidePanel = sinon.spy();\n\n  // Create custom SidePanel that will accept toggleSidePanel as a spy\n  function CustomSidePanelFactory(...deps) {\n    const OriginalSidePanel = SidePanelFactory(...deps);\n    const CustomSidePanel = props => {\n      const customUIStateActions = {\n        ...props.uiStateActions,\n        toggleSidePanel\n      };\n      return <OriginalSidePanel {...props} uiStateActions={customUIStateActions} />;\n    };\n    return CustomSidePanel;\n  }\n  CustomSidePanelFactory.deps = SidePanelFactory.deps;\n\n  const CustomKeplerGl = appInjector\n    .provide(SidePanelFactory, CustomSidePanelFactory)\n    .get(KeplerGlFactory);\n\n  // Create initial state based on mocked state with geocoder dataset and use that for mocking the store\n  const store = mockStore({\n    keplerGl: {\n      map: StateWithGeocoderDataset\n    }\n  });\n\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <Provider store={store}>\n        <CustomKeplerGl\n          id=\"map\"\n          mapboxApiAccessToken=\"smoothie-the-cat\"\n          selector={state => state.keplerGl.map}\n          dispatch={store.dispatch}\n        />\n      </Provider>\n    );\n  }, 'Should not throw error when mount KeplerGl');\n\n  // Check if we have 4 sidepanel tabs\n  t.equal(wrapper.find('.side-panel__tab').length, 4, 'should render 4 panel tabs');\n\n  // click layer tab\n  const layerTab = wrapper.find('.side-panel__tab').at(0);\n  layerTab.simulate('click');\n  t.ok(toggleSidePanel.calledWith('layer'), 'should call toggleSidePanel with layer');\n  t.notEqual(\n    findGeocoderDatasetName(wrapper),\n    GEOCODER_DATASET_NAME,\n    `should not be equal to ${GEOCODER_DATASET_NAME}`\n  );\n\n  // click filters tab\n  const filterTab = wrapper.find('.side-panel__tab').at(1);\n  filterTab.simulate('click');\n  t.ok(toggleSidePanel.calledWith('filter'), 'should call toggleSidePanel with filter');\n  t.notEqual(\n    findGeocoderDatasetName(wrapper),\n    GEOCODER_DATASET_NAME,\n    `should not be equal to ${GEOCODER_DATASET_NAME}`\n  );\n\n  // click interaction tab\n  const interactionTab = wrapper.find('.side-panel__tab').at(2);\n  interactionTab.simulate('click');\n  t.ok(toggleSidePanel.calledWith('interaction'), 'should call toggleSidePanel with interaction');\n  t.notEqual(\n    findGeocoderDatasetName(wrapper),\n    GEOCODER_DATASET_NAME,\n    `should not be equal to ${GEOCODER_DATASET_NAME}`\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/map/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './map-legend-test';\nimport './map-control-test';\nimport './map-popover-test';\n"
  },
  {
    "path": "test/browser/components/map/map-control-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount,enzyme-deprecation/no-shallow,max-statements */\nimport React from 'react';\nimport {shallow} from 'enzyme';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport configureStore from 'redux-mock-store';\nimport {Provider} from 'react-redux';\n\nimport {\n  MapControlButton,\n  ToolbarItem,\n  mapFieldsSelector,\n  appInjector,\n  MapLayerSelector,\n  MapContainerFactory,\n  MapLegendFactory,\n  MapControlFactory,\n  MapControlToolbarFactory,\n  Icons,\n  MapViewStateContextProvider\n} from '@kepler.gl/components';\nimport {LOCALE_CODES, LOCALES} from '@kepler.gl/localization';\nimport {toggleMapControl} from '@kepler.gl/actions';\nimport {keplerGlReducerCore} from '@kepler.gl/reducers';\n\nimport {IntlWrapper, mountWithTheme} from '../../../helpers/component-utils';\nimport {\n  mockKeplerProps,\n  mockKeplerPropsWithState,\n  StateWSplitMaps,\n  StateWFiles\n} from '../../../helpers/mock-state';\n\nconst {Cube3d, Split, Legend, DrawPolygon, Layers, Delete} = Icons;\nconst MapControl = appInjector.get(MapControlFactory);\nconst MapContainer = appInjector.get(MapContainerFactory);\nconst MapLegend = appInjector.get(MapLegendFactory);\nconst MapControlToolbar = appInjector.get(MapControlToolbarFactory);\n\nconst initialProps = mapFieldsSelector(mockKeplerProps);\n\nconst mockStore = configureStore();\n\ntest('MapControlFactory - display all options', t => {\n  const onToggleSplitMap = sinon.spy();\n  const onTogglePerspective = sinon.spy();\n  const onToggleMapControl = sinon.spy();\n  const onSetEditorMode = sinon.spy();\n  const onToggleEditorVisibility = sinon.spy();\n  const onSetLocale = sinon.spy();\n  const $ = shallow(\n    <MapControl\n      mapControls={{\n        splitMap: {show: true},\n        visibleLayers: {show: true},\n        toggle3d: {show: true},\n        mapLegend: {show: true},\n        mapDraw: {show: true},\n        mapLocale: {show: true},\n        effect: {show: true}\n      }}\n      datasets={{}}\n      layers={[]}\n      locale={'en'}\n      layersToRender={{}}\n      dragRotate={true}\n      mapIndex={0}\n      onToggleSplitMap={onToggleSplitMap}\n      onTogglePerspective={onTogglePerspective}\n      onToggleMapControl={onToggleMapControl}\n      onSetEditorMode={onSetEditorMode}\n      onToggleEditorVisibility={onToggleEditorVisibility}\n      onSetLocale={onSetLocale}\n    />\n  );\n  t.equal($.find('.map-control-action').length, 6, 'Should show 6 action panels');\n  t.end();\n});\n\ntest('MapControlFactory - display options', t => {\n  const store = mockStore({\n    uiState: StateWSplitMaps.uiState\n  });\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <MapViewStateContextProvider mapState={initialProps.mapState}>\n            <MapContainer {...initialProps} />\n          </MapViewStateContextProvider>\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'Map Container should not fail without props');\n\n  // wrapper\n  // MapControl\n  t.equal(wrapper.find(MapControl).length, 1, 'Should render MapControl');\n\n  // layer selector is not active\n  t.equal(wrapper.find(MapControlButton).length, 5, 'Should show 5 MapControlButton');\n\n  t.equal(wrapper.find(Split).length, 1, 'Should show 1 split map button');\n  t.equal(wrapper.find(Cube3d).length, 1, 'Should show 1 toggle 3d button');\n  t.equal(wrapper.find(Legend).length, 1, 'Should show 1 map legend button');\n  t.equal(wrapper.find(DrawPolygon).length, 1, 'Should show 1 map draw button');\n  t.equal(wrapper.find('.map-control-button__locale').length, 1, 'Should show 1 locale  button');\n\n  // with split map and active layer selector\n  const propsWithSplitMap = mapFieldsSelector(mockKeplerPropsWithState({state: StateWSplitMaps}));\n\n  wrapper.setProps({\n    children: (\n      <Provider store={store}>\n        <IntlWrapper>\n          <MapViewStateContextProvider mapState={propsWithSplitMap.mapState}>\n            <MapContainer {...propsWithSplitMap} />\n          </MapViewStateContextProvider>\n        </IntlWrapper>\n      </Provider>\n    )\n  });\n\n  // 6 control buttons as legend is opened automatically in split map mode\n  t.equal(wrapper.find(MapControlButton).length, 6, 'Should show 6 MapControlButton');\n  t.equal(wrapper.find(Split).length, 0, 'Should show 0 split map split button');\n  t.equal(wrapper.find(Delete).length, 1, 'Should show 1 split map delete button');\n  t.equal(wrapper.find(Layers).length, 1, 'Should show 1 Layer button');\n\n  // with 0 mapcontrols\n  wrapper.setProps({\n    children: (\n      <MapViewStateContextProvider mapState={propsWithSplitMap.mapState}>\n        <MapContainer {...propsWithSplitMap} mapControls={{}} />\n      </MapViewStateContextProvider>\n    )\n  });\n\n  t.equal(wrapper.find(MapControlButton).length, 0, 'Should show 0 MapControlButton');\n\n  t.end();\n});\n\ntest('MapControlFactory - click options', t => {\n  const onToggleSplitMap = sinon.spy();\n  const onTogglePerspective = sinon.spy();\n  const onToggleMapControl = sinon.spy();\n  const onSetEditorMode = sinon.spy();\n  const onToggleEditorVisibility = sinon.spy();\n  const onSetLocale = sinon.spy();\n\n  const visStateActions = {\n    setEditorMode: onSetEditorMode,\n    toggleEditorVisibility: onToggleEditorVisibility\n  };\n  const mapStateActions = {\n    toggleSplitMap: onToggleSplitMap,\n    togglePerspective: onTogglePerspective\n  };\n  const uiStateActions = {\n    toggleMapControl: onToggleMapControl,\n    setLocale: onSetLocale\n  };\n  // const updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapLegend', 0));\n  const mapContainerProps = mapFieldsSelector(\n    mockKeplerPropsWithState({\n      state: StateWSplitMaps,\n      visStateActions,\n      mapStateActions,\n      uiStateActions\n    })\n  );\n\n  const store = mockStore({\n    uiState: StateWSplitMaps.uiState\n  });\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <MapViewStateContextProvider mapState={mapContainerProps.mapState}>\n            <MapContainer {...mapContainerProps} />\n          </MapViewStateContextProvider>\n        </Provider>\n      </IntlWrapper>\n    );\n  }, 'MapContainer should not fail without props');\n\n  // layer selector is not active\n  t.equal(wrapper.find(MapControlButton).length, 6, 'Should show 6 MapControlButton');\n\n  t.equal(wrapper.find(Delete).length, 1, 'Should show 1 delete split map button');\n  // click split Map\n  wrapper.find('.map-control-button.split-map').at(0).simulate('click');\n  t.ok(onToggleSplitMap.calledOnce, 'should call onToggleSplitMap');\n  t.deepEqual(onToggleSplitMap.args[0], [0], 'should call onToggleSplitMap with mapindex');\n\n  // click toggle3d\n  wrapper.find('.map-control-button.toggle-3d').at(0).simulate('click');\n  t.ok(onTogglePerspective.calledOnce, 'should call onTogglePerspective');\n\n  // click map legend\n  wrapper.find('.map-control-button.show-legend').at(0).simulate('click');\n\n  t.equal(wrapper.find(MapLegend).length, 1, 'should render MapLegend');\n\n  // click map draw\n  wrapper.find('.map-control-button.map-draw').at(0).simulate('click');\n\n  t.equal(wrapper.find(MapLegend).length, 1, 'should render MapLegend');\n\n  /*\n  t.ok(onToggleMapControl.calledOnce, 'should call onToggleMapControl');\n  t.deepEqual(\n    onToggleMapControl.args[0],\n    ['mapDraw', 0],\n    'should call onToggleMapControl with mapDraw'\n  );\n\n  // click locale\n  wrapper.find('.map-control-button.locale-panel').at(0).simulate('click');\n  t.ok(onToggleMapControl.calledTwice, 'should call onToggleMapControl');\n  t.deepEqual(\n    onToggleMapControl.args[1],\n    ['mapLocale', 0],\n    'should call onToggleMapControl with mapLocale'\n  );\n\n  // click layer selector\n  wrapper.find('.map-control-button.toggle-layer').at(0).simulate('click');\n  t.ok(onToggleMapControl.calledThrice, 'should call onToggleMapControl');\n  t.deepEqual(\n    onToggleMapControl.args[2],\n    ['visibleLayers', 0],\n    'should call onToggleMapControl with visibleLayers'\n  );\n  */\n\n  t.end();\n});\n\ntest('MapControlFactory - show panels', t => {\n  // show legend\n  let updateState = keplerGlReducerCore(StateWFiles, toggleMapControl('mapLegend', 0));\n  let mapContainerProps = mapFieldsSelector(mockKeplerPropsWithState({state: updateState}));\n\n  const store = mockStore({\n    uiState: updateState.uiState\n  });\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <Provider store={store}>\n          <MapViewStateContextProvider mapState={mapContainerProps.mapState}>\n            <MapContainer {...mapContainerProps} />\n          </MapViewStateContextProvider>\n        </Provider>\n      </IntlWrapper>\n    );\n  }, 'MapContainer should not fail without props');\n\n  // show legend\n  t.equal(wrapper.find(MapLegend).length, 1, 'should render 1 MapLegend');\n\n  // show layer selector\n  const toggleLayerForMap = sinon.spy();\n  const visStateActions = {\n    toggleLayerForMap\n  };\n  updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('visibleLayers', 1));\n  mapContainerProps = mapFieldsSelector(\n    mockKeplerPropsWithState({state: updateState, visStateActions})\n  );\n  wrapper.setProps({\n    children: (\n      <Provider store={store}>\n        <MapViewStateContextProvider mapState={mapContainerProps.mapState}>\n          <MapContainer {...mapContainerProps} index={1} />\n        </MapViewStateContextProvider>\n      </Provider>\n    )\n  });\n\n  // click layer selector\n  t.equal(wrapper.find(MapLayerSelector).length, 1, 'should render 1 MapLayerSelector');\n\n  t.equal(\n    wrapper.find('.map-layer-selector__item').length,\n    Object.keys(updateState.visState.splitMaps[1].layers).length,\n    'MapLayerSelector should render correct number of layers'\n  );\n\n  wrapper.find('input').at(0).simulate('change');\n  t.ok(toggleLayerForMap.calledOnce, 'should call toggleLayerForMap');\n  t.deepEqual(\n    toggleLayerForMap.args[0],\n    [1, 'point-0'],\n    'should call toggleLayerForMap with mapIndex and layerid'\n  );\n  // show map draw panel\n  updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapDraw', 1));\n  mapContainerProps = mapFieldsSelector(mockKeplerPropsWithState({state: updateState}));\n  wrapper.setProps({\n    children: (\n      <Provider store={store}>\n        <MapViewStateContextProvider mapState={mapContainerProps.mapState}>\n          <MapContainer {...mapContainerProps} index={1} />\n        </MapViewStateContextProvider>\n      </Provider>\n    )\n  });\n\n  t.equal(wrapper.find(MapControlToolbar).length, 1, 'should render 1 MapControlToolbar');\n  t.equal(\n    wrapper.find(MapControlToolbar).at(0).find(ToolbarItem).length,\n    3,\n    'should render 3 ToolbarItem'\n  );\n\n  // show locale\n  updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapLocale', 1));\n  mapContainerProps = mapFieldsSelector(mockKeplerPropsWithState({state: updateState}));\n  wrapper.setProps({\n    children: (\n      <Provider store={store}>\n        <MapViewStateContextProvider mapState={mapContainerProps.mapState}>\n          <MapContainer {...mapContainerProps} index={1} />\n        </MapViewStateContextProvider>\n      </Provider>\n    )\n  });\n\n  t.equal(wrapper.find(MapControlToolbar).length, 1, 'should render 1 MapControlToolbar');\n  t.equal(\n    wrapper.find(MapControlToolbar).at(0).find(ToolbarItem).length,\n    Object.keys(LOCALE_CODES).length,\n    `should render ${Object.keys(LOCALE_CODES).length} LOCALE_CODES`\n  );\n\n  Object.keys(LOCALE_CODES).forEach((locale, index) => {\n    t.equal(\n      wrapper\n        .find(MapControlToolbar)\n        .at(0)\n        .find(ToolbarItem)\n        .at(index)\n        .find('.toolbar-item__title')\n        .text(),\n      LOCALES[locale],\n      'should render correct locale'\n    );\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/map/map-legend-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport React from 'react';\nimport test from 'tape';\nimport {mount} from 'enzyme';\nimport cloneDeep from 'lodash/cloneDeep';\nimport sinon from 'sinon';\n\nimport {\n  MapLegendFactory,\n  StyledMapControlLegend,\n  VisualChannelMetric,\n  LayerDefaultLegend,\n  SingleColorLegendFactory,\n  LayerColorLegendFactory,\n  LegendRowFactory,\n  ResetColorLabelFactory,\n  appInjector\n} from '@kepler.gl/components';\nimport {\n  StateWFilesFiltersLayerColor,\n  expectedSavedLayer1 as pointLayer,\n  expectedSavedLayer0 as hexagonLayer,\n  expectedSavedLayer2 as geojsonLayer\n} from 'test/helpers/mock-state';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nconst {PointLayer} = KeplerGlLayers;\n\nconst MapLegend = appInjector.get(MapLegendFactory);\nconst LayerColorLegend = appInjector.get(LayerColorLegendFactory);\nconst SingleColorLegend = appInjector.get(SingleColorLegendFactory);\nconst LegendRow = appInjector.get(LegendRowFactory);\nconst ResetColorLabel = appInjector.get(ResetColorLabelFactory);\n\ntest('Components -> MapLegend.render', t => {\n  t.doesNotThrow(() => {\n    mount(<MapLegend />);\n  }, 'Show not fail without data');\n\n  t.end();\n});\n\ntest('Components -> MapLegend.render -> with layers', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const onLayerVisConfigChange = sinon.spy();\n  const {layers} = initialState.visState;\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapLegend layers={layers} onLayerVisConfigChange={onLayerVisConfigChange} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail with layers');\n\n  t.equal(\n    wrapper.find(StyledMapControlLegend).length,\n    layers.length,\n    'should render 3 layer legends'\n  );\n\n  const geojsonLegend = wrapper.find(StyledMapControlLegend).at(1);\n  const hexagonLegend = wrapper.find(StyledMapControlLegend).at(2);\n  const pointLegend = wrapper.find(StyledMapControlLegend).at(0);\n\n  testGeojsonLegend(t, geojsonLegend);\n  testHexagonLayerLegend(t, hexagonLegend);\n  testPointLayerLegend(t, pointLegend, onLayerVisConfigChange);\n\n  t.end();\n});\n\nfunction testGeojsonLegend(t, geojsonLegend) {\n  t.equal(\n    geojsonLegend.find('.legend--layer_name').at(0).text(),\n    geojsonLayer.config.label,\n    'geojson layer legend should render label'\n  );\n  t.equal(\n    geojsonLegend.find(LayerColorLegend).length,\n    2,\n    'geojson layer legend should render 2 color legend'\n  );\n  t.equal(\n    geojsonLegend.find(LayerDefaultLegend).length,\n    0,\n    'geojson layer legend should render 1 size legend'\n  );\n  const fillColorLegend = geojsonLegend.find(LayerColorLegend).at(0);\n  const strokeColorLegend = geojsonLegend.find(LayerColorLegend).at(1);\n\n  t.equal(\n    fillColorLegend.find(SingleColorLegend).length,\n    1,\n    'geojson fill color should render SingleColorLegend'\n  );\n  t.deepEqual(\n    fillColorLegend.find(SingleColorLegend).at(0).props().color,\n    geojsonLayer.config.color,\n    'geojson color legend should be correct color'\n  );\n\n  t.equal(\n    strokeColorLegend.find(SingleColorLegend).length,\n    1,\n    'geojson stroke color should render SingleColorLegend'\n  );\n  t.deepEqual(\n    strokeColorLegend.find(SingleColorLegend).at(0).props().color,\n    geojsonLayer.config.visConfig.strokeColor,\n    'geojson color legend should be correct color'\n  );\n}\n\nfunction testPointLayerLegend(t, pointLegend, onLayerVisConfigChange) {\n  // layer[0] point layer\n  // color by: gps_data.types\n  // point layer has 2 color channels, only fill is enabled\n  t.equal(\n    pointLegend.find('.legend--layer_name').at(0).text(),\n    pointLayer.config.label,\n    'point layer legend should render point label'\n  );\n\n  t.equal(\n    pointLegend.find(LayerColorLegend).length,\n    1,\n    'point layer legend should render 1 point layer color legend'\n  );\n  t.equal(\n    pointLegend.find(LayerDefaultLegend).length,\n    0,\n    'point layer legend should render 0 point layer size legend'\n  );\n  const pointColorLegend = pointLegend.find(LayerColorLegend).at(0);\n\n  t.equal(\n    pointColorLegend.find(VisualChannelMetric).length,\n    1,\n    'point layer legend should render metric name'\n  );\n  t.equal(\n    pointColorLegend\n      .find(VisualChannelMetric)\n      .at(0)\n      .find('.legend--layer_color_field')\n      .at(0)\n      .text(),\n    pointLayer.visualChannels.colorField.name,\n    'point layer legend should render color by measure: gps_data.types'\n  );\n\n  // colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA']\n  // colorDomain [\"driver_analytics\", \"driver_analytics_0\", \"driver_gps\"]\n  t.equal(pointLegend.find(LegendRow).length, 3, 'Should render 3 legends');\n  const expectedLegend = [\n    ['#00939C', 'driver_analytics'],\n    ['#6BB5B9', 'driver_analytics_0'],\n    ['#AAD7D9', 'driver_gps']\n  ];\n  for (let i = 0; i < 3; i++) {\n    const rect = pointLegend.find(LegendRow).at(i).find('.legend-row-color').at(0);\n    t.equal(\n      rect.props().style.backgroundColor,\n      expectedLegend[i][0],\n      'should render correct legend color'\n    );\n    const input = pointLegend.find(LegendRow).at(i).find('input').at(0);\n    t.equal(input.props().value, expectedLegend[i][1], 'should render correct legend label');\n  }\n\n  // test change input\n  const firstLegendInput = pointLegend.find(LegendRow).at(0).find('input');\n  const event = {target: {name: 'input-legend-label', value: 'taro'}};\n  firstLegendInput.simulate('change', event);\n\n  t.ok(onLayerVisConfigChange.calledOnce, 'should call onLayerVisConfigChange');\n\n  t.ok(onLayerVisConfigChange.args[0][0] instanceof PointLayer, 'first arg should be Layer');\n  t.deepEqual(\n    Object.keys(onLayerVisConfigChange.args[0][1]),\n    ['colorRange'],\n    'second arg should be colorRange'\n  );\n  t.deepEqual(\n    onLayerVisConfigChange.args[0][1].colorRange.colorLegends,\n    {'#00939C': 'taro'},\n    'second arg should contain colorLegends'\n  );\n}\n\nfunction testHexagonLayerLegend(t, hexagonLegend) {\n  t.equal(\n    hexagonLegend.find('.legend--layer_name').at(0).text(),\n    hexagonLayer.config.label,\n    'hexagon layer legend should render label'\n  );\n  t.equal(\n    hexagonLegend.find(LayerColorLegend).length,\n    1,\n    'hexagon layer legend should render 1 color legend'\n  );\n  t.equal(\n    hexagonLegend.find(LayerDefaultLegend).length,\n    0,\n    'hexagon layer legend should render 0 size legend'\n  );\n  const hexagonColorLegend = hexagonLegend.find(LayerColorLegend).at(0);\n\n  t.equal(\n    hexagonColorLegend.find(VisualChannelMetric).length,\n    1,\n    'hexagon layer legend should render metric name'\n  );\n  t.equal(\n    hexagonColorLegend\n      .find(VisualChannelMetric)\n      .at(0)\n      .find('.legend--layer_color_field')\n      .at(0)\n      .text(),\n    'Point Count',\n    'hexagon layer legend should render color by Point Count'\n  );\n}\n\ntest('Components -> MapLegend.render -> with colorLegends', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const onLayerVisConfigChange = sinon.spy();\n  const {layers} = initialState.visState;\n  const ptLayer = layers[0];\n\n  ptLayer.config.visConfig.colorRange = {\n    ...ptLayer.config.visConfig.colorRange,\n    colorLegends: {\n      '#00939C': 'taro'\n    }\n  };\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapLegend layers={[ptLayer]} onLayerVisConfigChange={onLayerVisConfigChange} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail with layers');\n\n  t.equal(wrapper.find(StyledMapControlLegend).length, 1, 'should render 1 layer legends');\n\n  const pointLegend = wrapper.find(StyledMapControlLegend).at(0);\n  const firstLegend = pointLegend.find(LegendRow).at(0);\n  const input = firstLegend.find('input').at(0);\n\n  t.equal(input.props().value, 'taro', 'should render custom legend label');\n  t.equal(firstLegend.find(ResetColorLabel).length, 1, 'should render reset');\n\n  // click reset\n  firstLegend.find(ResetColorLabel).at(0).simulate('click');\n  t.ok(onLayerVisConfigChange.calledOnce, 'should call onLayerVisConfigChange');\n\n  t.ok(onLayerVisConfigChange.args[0][0] instanceof PointLayer, 'first arg should be Layer');\n  t.deepEqual(\n    Object.keys(onLayerVisConfigChange.args[0][1]),\n    ['colorRange'],\n    'second arg should be colorRange'\n  );\n  t.deepEqual(\n    onLayerVisConfigChange.args[0][1].colorRange.colorLegends,\n    {},\n    'second arg should be empty'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/map/map-popover-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\n\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {\n  Icons,\n  MapPopoverFactory,\n  appInjector,\n  LayerHoverInfoFactory,\n  CoordinateInfoFactory\n} from '@kepler.gl/components';\nimport {\n  expectedLayerHoverProp as layerHoverProp,\n  expectedGeojsonLayerHoverProp as geojsonLayerHoverProp\n} from 'test/helpers/mock-state';\n\nconst {Pin} = Icons;\nconst MapPopover = appInjector.get(MapPopoverFactory);\nconst LayerHoverInfo = appInjector.get(LayerHoverInfoFactory);\nconst CoordinateInfo = appInjector.get(CoordinateInfoFactory);\n\nconst defaultProps = {\n  x: 400,\n  y: 300,\n  zoom: 13,\n  onClose: null,\n  coordinate: null,\n  layerHoverProp: {},\n  isBase: false,\n  frozen: false\n};\n\ntest('Map Popover - render', t => {\n  const onClose = sinon.spy();\n  defaultProps.onClose = onClose;\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapPopover {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  t.equal(wrapper.find(MapPopover).length, 1, 'Should display 1 MapPopover');\n  t.equal(wrapper.find('.coordingate-hover-info').length, 0, 'Should display 0 coordinates');\n  t.equal(wrapper.find('.map-popover__layer-info').length, 0, 'Should display 0 layer info');\n  t.equal(wrapper.find(Pin).length, 0, 'Should display 0 pin');\n  t.equal(wrapper.find('.primary-label').length, 0, 'Should display 0 primary label');\n  t.equal(\n    wrapper.find('.select-geometry').length,\n    0,\n    'Should not display select geometry for point layer'\n  );\n  wrapper.setProps({\n    children: <MapPopover {...defaultProps} isBase={true} frozen={true} />\n  });\n\n  t.equal(wrapper.find(Pin).length, 1, 'Should display 1 pin');\n  t.equal(wrapper.find('.primary-label').length, 1, 'Should display 1 primary label');\n  t.equal(\n    wrapper.find('.select-geometry').length,\n    0,\n    'Should not display select geometry for point layer'\n  );\n\n  // click pin\n  wrapper.find('.popover-pin').at(0).simulate('click');\n  t.ok(onClose.called, 'should call onClose when click pin');\n  // render coordinate\n  wrapper.setProps({\n    children: (\n      <MapPopover {...defaultProps} coordinate={[127.12345678, -31.12345678]} zoom={12.123} />\n    )\n  });\n  t.equal(wrapper.find('CoordinateInfo').length, 1, 'Should render CoordinateInfo');\n  t.equal(wrapper.find('.row__value').at(0).text(), '-31.123457,', 'should render lat');\n  t.equal(wrapper.find('.row__value').at(1).text(), '127.123457,', 'should render longitude');\n  t.equal(wrapper.find('.row__value').at(2).text(), '12.1z', 'should render zoom');\n\n  t.end();\n});\n\ntest('Map Popover - render with layerHoverProp', t => {\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapPopover {...defaultProps} layerHoverProp={layerHoverProp} />\n      </IntlWrapper>\n    );\n  }, 'Should render map popover with layerHoverProps');\n  t.equal(wrapper.find(LayerHoverInfo).length, 1, 'Should render LayerHoverInfo');\n  t.equal(wrapper.find('table').length, 1, 'Should render 1 table');\n  const table = wrapper.find('table').at(0);\n  const rows = table.find('.layer-hover-info__row');\n  t.equal(rows.length, 5, 'should render 5 rows');\n  const expectedTooltips = [\n    ['gps_data.utc_timestamp', '1474071864000'],\n    ['gps_data.types', 'driver_analytics'],\n    ['epoch', '1472754400000'],\n    ['has_result', ''],\n    ['uid', '1']\n  ];\n  for (let i = 0; i < 5; i++) {\n    t.equal(\n      rows.at(i).find('.row__name').text(),\n      expectedTooltips[i][0],\n      'row name should be correct'\n    );\n    t.equal(\n      rows.at(i).find('.row__value').text(),\n      expectedTooltips[i][1],\n      'row value should be correct'\n    );\n  }\n\n  t.end();\n});\n\ntest('Map Popover - render with geojsonLayerHoverProp', t => {\n  const setSelectedFeature = sinon.spy();\n  const onSetFeatures = sinon.spy();\n  const onClose = sinon.spy();\n  defaultProps.setSelectedFeature = setSelectedFeature;\n  defaultProps.onSetFeatures = onSetFeatures;\n  defaultProps.onClose = onClose;\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapPopover\n          {...defaultProps}\n          isBase={true}\n          frozen={true}\n          layerHoverProp={geojsonLayerHoverProp}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should render map popover with geojsonLayerHoverProp');\n\n  t.equal(wrapper.find(MapPopover).length, 1, 'Should display 1 MapPopover');\n  t.equal(wrapper.find(Pin).length, 1, 'Should display 1 pin');\n  t.equal(wrapper.find('.primary-label').length, 1, 'Should display 1 primary label');\n\n  // click select geometry\n  wrapper.find('.select-geometry').at(0).simulate('click');\n\n  t.ok(setSelectedFeature.called, 'should call setSelectedFeature when click select geometry');\n  t.ok(onSetFeatures.called, 'should call onSetFeatures when click select geometry');\n\n  // click pin\n  wrapper.find('.popover-pin').at(0).simulate('click');\n  t.ok(onClose.called, 'should call onClose when click pin');\n\n  // render coordinate\n  wrapper.setProps({\n    children: (\n      <MapPopover {...defaultProps} coordinate={[127.12345678, -31.12345678]} zoom={12.123} />\n    )\n  });\n  t.equal(wrapper.find(CoordinateInfo).length, 1, 'Should render CoordinateInfo');\n\n  t.equal(wrapper.find('.row__value').at(0).text(), '-31.123457,', 'should render lat');\n\n  t.equal(wrapper.find('.row__value').at(1).text(), '127.123457,', 'should render longitude');\n\n  t.equal(wrapper.find('.row__value').at(2).text(), '12.1z', 'should render zoom');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/map-container-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\n\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {\n  appInjector,\n  MapContainerFactory,\n  mapFieldsSelector,\n  MapViewStateContextProvider\n} from '@kepler.gl/components';\nimport {mockKeplerProps} from '../../helpers/mock-state';\n\nconst MapContainer = appInjector.get(MapContainerFactory);\nconst initialProps = mapFieldsSelector(mockKeplerProps);\n\nconst initialState = {mapState: {latitude: 0, longitude: 0}};\nconst mockStore = configureStore();\n\ntest('MapContainerFactory - display all options', t => {\n  const onMapStyleLoaded = sinon.spy();\n  const onLayerClick = sinon.spy();\n  const store = mockStore(initialState);\n\n  const props = {\n    ...initialProps,\n    mapStyle: {\n      bottomMapStyle: {layers: [], name: 'foo'},\n      visibleLayerGroups: {}\n    },\n    onMapStyleLoaded,\n    visStateActions: {\n      ...initialProps.visStateActions,\n      onLayerClick\n    }\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <MapViewStateContextProvider mapState={props.mapState}>\n            <MapContainer {...props} />\n          </MapViewStateContextProvider>\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'MapContainer should not fail without props');\n\n  t.equal(wrapper.find('MapControl').length, 1, 'Should display 1 MapControl');\n\n  t.equal(wrapper.find('Map').length, 1, 'Should display 1 Map');\n\n  // Editor\n  t.equal(wrapper.find('DeckGl').length, 1, 'Should display 1 DeckGl');\n\n  const instance = wrapper.find(MapContainer).instance();\n\n  instance._onMapboxStyleUpdate();\n\n  t.equal(onMapStyleLoaded.called, true, 'Should be calling onMapStyleLoaded');\n\n  instance._onCloseMapPopover();\n  t.equal(onLayerClick.called, true, 'Should be calling onLayerClick');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/modals/data-table-modal-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {act} from 'react-dom/test-utils';\nimport test from 'tape-catch';\nimport global from 'global';\nimport sinon from 'sinon';\nimport flatten from 'lodash/flattenDeep';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport CloneDeep from 'lodash/cloneDeep';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {visStateReducer} from '@kepler.gl/reducers';\n\nimport {TOOLTIP_FORMATS} from '@kepler.gl/constants';\nimport {\n  FieldTokenFactory,\n  Icons,\n  DataTableModalFactory,\n  DatasetTabs,\n  DatasetModalTab,\n  DataTableFactory,\n  OptionDropdown,\n  appInjector,\n  DropdownList,\n  InputLight,\n  DataTableConfigFactory,\n  NumberFormatConfig\n} from '@kepler.gl/components';\nimport {testFields, testAllData} from 'test/fixtures/test-csv-data';\nimport {geoStyleFields, geoStyleRows} from 'test/fixtures/geojson';\nimport {StateWFiles, testCsvDataId, testGeoJsonDataId} from 'test/helpers/mock-state';\nimport {STYLED_COMPONENTS_DUPLICATED_ENTRIES} from '../../../helpers/utils';\n\nimport {createDataContainer, getFieldFormatLabels} from '@kepler.gl/utils';\n\nconst {VertThreeDots} = Icons;\nconst DataTableModal = appInjector.get(DataTableModalFactory);\nconst DataTable = appInjector.get(DataTableFactory);\nconst FieldToken = appInjector.get(FieldTokenFactory);\n\nconst expectedCellSizeCache = {\n  'gps_data.utc_timestamp': {row: 145, header: 150},\n  'gps_data.lat': {row: 96, header: 108},\n  'gps_data.lng': {row: 96, header: 110},\n  'gps_data.types': {row: 125, header: 123},\n  epoch: {row: 121, header: 90},\n  has_result: {row: 75, header: 99},\n  uid: {row: 75, header: 90},\n  time: {row: 180, header: 90},\n  begintrip_ts_utc: {row: 182, header: 129},\n  begintrip_ts_local: {row: 182, header: 137},\n  date: {row: 95, header: 90}\n};\n\nconst expectedExpandedCellSize = {\n  cellSizeCache: {\n    'gps_data.utc_timestamp': 160,\n    'gps_data.lat': 108,\n    'gps_data.lng': 110,\n    'gps_data.types': 125,\n    epoch: 121,\n    has_result: 99,\n    uid: 90,\n    time: 180,\n    begintrip_ts_utc: 182,\n    begintrip_ts_local: 182,\n    date: 143\n  },\n  ghost: null\n};\n\n// This is to Mock Canvas.measureText response which we will not be testing\nconst testMeasure = [573, 17, 87, 566, 87, 447, 17, 87];\nconst texts = [\n  [\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522]]]}',\n    7.5,\n    'C_Medium_High'\n  ],\n  [\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.31687,40.656696]]]}',\n    null,\n    'C_Medium_High'\n  ],\n  [\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.387589,40.632238],[-74.387589,40.632238]]]}',\n    7.6,\n    'C_Medium_High'\n  ]\n];\nconst testColumns = ['_geojson', 'income level of people over 65', 'engagement'];\nconst testColumnMeasure = [46, 159, 65];\nfunction mockCanvas(globalWindow) {\n  globalWindow.HTMLCanvasElement.prototype.getContext = function mockGetContext() {\n    return {\n      measureText: text => {\n        const index = flatten(texts).indexOf(text);\n        if (index >= 0) {\n          return {width: testMeasure[index]};\n        }\n        const cIdx = testColumns.indexOf(text);\n        if (cIdx >= 0) {\n          return {width: testColumnMeasure[cIdx]};\n        }\n        return {width: 0};\n      }\n    };\n  };\n}\n\nlet oldGetContext;\nfunction prepareMockCanvas() {\n  oldGetContext = global.window.HTMLCanvasElement.prototype.getContext;\n  mockCanvas(global.window);\n}\n\nfunction restoreMockCanvas() {\n  global.window.HTMLCanvasElement.prototype.getContext = oldGetContext;\n}\n\n// eslint-disable-next-line max-statements\ntest('Components -> DataTableConfig', t => {\n  const expectedColumns = ['gps_data.utc_timestamp'];\n\n  const expectedColMeta = {\n    'gps_data.utc_timestamp': {\n      name: 'gps_data.utc_timestamp',\n      type: 'timestamp',\n      format: 'YYYY-M-D H:m:s'\n    }\n  };\n\n  const columns = expectedColumns;\n  const colMeta = expectedColMeta;\n  const setColumnDisplayFormat = sinon.spy();\n  const onClose = sinon.spy();\n\n  const DataTableConfig = DataTableConfigFactory();\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <DataTableConfig\n          columns={columns}\n          colMeta={colMeta}\n          setColumnDisplayFormat={setColumnDisplayFormat}\n          onClose={onClose}\n        />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  const numberFormatConfigInput = wrapper.find(NumberFormatConfig);\n  t.equal(numberFormatConfigInput.length, 5, 'should render 5 NumberFormatConfig');\n\n  numberFormatConfigInput.at(0).find(InputLight).simulate('click');\n\n  const formatDropdown = wrapper.find(DropdownList);\n  t.equal(formatDropdown.length, 1, 'should render 1 format dropdown');\n\n  const integerDisplayOptions = formatDropdown.at(0).props().options;\n\n  t.deepEqual(\n    integerDisplayOptions,\n    getFieldFormatLabels('integer'),\n    'should render integer type formats'\n  );\n\n  numberFormatConfigInput.at(1).find(InputLight).simulate('click');\n\n  const floatDisplayOptions = wrapper.find(DropdownList).at(1).props().options;\n\n  t.deepEqual(floatDisplayOptions, getFieldFormatLabels('real'), 'should render real type formats');\n\n  numberFormatConfigInput.at(2).find(InputLight).simulate('click');\n\n  const timeDisplayOptions = wrapper.find(DropdownList).at(2).props().options;\n\n  t.deepEqual(\n    timeDisplayOptions,\n    getFieldFormatLabels('timestamp'),\n    'should render time type formats'\n  );\n\n  numberFormatConfigInput.at(3).find(InputLight).simulate('click');\n\n  const dateDisplayOptions = wrapper.find(DropdownList).at(3).props().options;\n\n  t.deepEqual(dateDisplayOptions, getFieldFormatLabels('date'), 'should render date type formats');\n\n  t.end();\n});\n\n/* eslint-disable max-statements */\ntest('Components -> DataTableModal.render: csv 1', t => {\n  t.doesNotThrow(() => {\n    mountWithTheme(<DataTableModal />);\n  }, 'Show not fail without data');\n\n  const wrapper = mountWithTheme(\n    <DataTableModal datasets={StateWFiles.visState.datasets} dataId={testCsvDataId} />\n  );\n\n  t.equal(wrapper.find(DataTableModal).length, 1, 'should render DataTableModal data');\n  t.equal(wrapper.find(DatasetTabs).length, 1, 'should render DatasetTabs');\n  t.equal(wrapper.find(DatasetModalTab).length, 2, 'should render 1 DatasetModalTab');\n  t.equal(wrapper.find(DataTable).length, 1, 'should render 1 DataTable');\n\n  const props = wrapper.find(DataTable).at(0).props();\n  const expectedColumns = [\n    'gps_data.utc_timestamp',\n    'gps_data.lat',\n    'gps_data.lng',\n    'gps_data.types',\n    'epoch',\n    'has_result',\n    'uid',\n    'time',\n    'begintrip_ts_utc',\n    'begintrip_ts_local',\n    'date'\n  ];\n\n  const expectedColMeta = {\n    'gps_data.utc_timestamp': {\n      name: 'gps_data.utc_timestamp',\n      type: 'timestamp',\n      format: 'YYYY-M-D H:m:s'\n    },\n    'gps_data.lat': {name: 'gps_data.lat', type: 'real'},\n    'gps_data.lng': {name: 'gps_data.lng', type: 'real'},\n    'gps_data.types': {name: 'gps_data.types', type: 'string'},\n    epoch: {name: 'epoch', type: 'timestamp', format: 'X'},\n    has_result: {name: 'has_result', type: 'boolean'},\n    uid: {name: 'uid', type: 'integer'},\n    time: {name: 'time', type: 'timestamp', format: 'YYYY-M-DTHH:mm:ss.SSSS'},\n    begintrip_ts_utc: {name: 'begintrip_ts_utc', type: 'timestamp', format: 'YYYY-M-D HH:mm:ssZZ'},\n    begintrip_ts_local: {\n      name: 'begintrip_ts_local',\n      type: 'timestamp',\n      format: 'YYYY-M-D HH:mm:ssZZ'\n    },\n    date: {name: 'date', type: 'date', format: 'YYYY-M-D'}\n  };\n\n  t.deepEqual(props.columns, expectedColumns, 'DataTable should have the correct props.columns');\n  t.deepEqual(props.colMeta, expectedColMeta, 'DataTable should have the correct props.colMeta');\n\n  const datasetId = Object.keys(StateWFiles.visState.datasets)[0];\n\n  t.equal(\n    props.dataContainer,\n    StateWFiles.visState.datasets[datasetId].dataContainer,\n    'DataTable should have the correct props.dataContainer'\n  );\n  // we can't test it without mocking the canvas response\n  t.deepEqual(\n    Object.keys(props.cellSizeCache),\n    Object.keys(expectedColMeta),\n    'DataTable props.cellSizeCache should have all column keys'\n  );\n\n  t.end();\n});\n\ntest('Components -> DataTableModal -> click tab', t => {\n  const showDatasetTable = sinon.spy();\n\n  const wrapper = mountWithTheme(\n    <DataTableModal\n      datasets={StateWFiles.visState.datasets}\n      dataId={testCsvDataId}\n      showDatasetTable={showDatasetTable}\n    />\n  );\n\n  t.equal(wrapper.find(DatasetModalTab).length, 2, 'should render 2 DatasetModalTab');\n  t.equal(wrapper.find(DatasetModalTab).at(0).prop('active'), true, 'prop 0 active should be true');\n  t.equal(\n    wrapper.find(DatasetModalTab).at(0).find('.dataset-name').text(),\n    'hello.csv',\n    'dataset name should be correct'\n  );\n  t.equal(\n    wrapper.find(DatasetModalTab).at(1).prop('active'),\n    false,\n    'prop 1 active should be true'\n  );\n  t.equal(\n    wrapper.find(DatasetModalTab).at(1).find('.dataset-name').text(),\n    'zip.geojson',\n    'dataset name should be correct'\n  );\n\n  wrapper.find(DatasetModalTab).at(1).simulate('click');\n  t.ok(showDatasetTable.calledWith(testGeoJsonDataId));\n\n  t.end();\n});\n\ntest('Components -> DataTableModal -> render DataTable: csv 1', t => {\n  const wrapper = mountWithTheme(\n    <DataTableModal datasets={StateWFiles.visState.datasets} dataId={testCsvDataId} />\n  );\n  t.equal(wrapper.find(DataTable).length, 1, 'should render 1 DataTable');\n\n  const props = wrapper.find(DataTable).at(0).props();\n\n  // mock cellSizeCache, width and height\n  const enriched = {\n    ...props,\n    cellSizeCache: expectedCellSizeCache,\n    fixedWidth: 1500,\n    fixedHeight: 800,\n    theme: {}\n  };\n\n  const wrapper2 = mountWithTheme(<DataTable {...enriched} />);\n  const componentInstance = wrapper2.find('DataTable').instance();\n  const result = componentInstance.getCellSizeCache();\n\n  t.deepEqual(result, expectedExpandedCellSize, 'should calculate correct cell expansion');\n\n  // manually setting the state and update the component\n  act(() => {\n    componentInstance.setState(result);\n  });\n\n  wrapper2.update();\n\n  t.equal(\n    wrapper2.find('.header-cell').length,\n    testFields.length * STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    `should render ${testFields.length} headers`\n  );\n\n  t.equal(\n    wrapper2.find('.cell').length,\n    testFields.length * testAllData.length,\n    `should render ${testFields.length * testAllData.length} cells`\n  );\n\n  const expectedRows = {\n    0: [\n      '2016-09-17 00:09:55\\t',\n      '29.9900937\\t',\n      '31.2590542\\t',\n      'driver_analytics_0\\t',\n      '1472688000000\\t',\n      'false\\t',\n      '1\\t',\n      '2016-09-23T00:00:00.000Z\\t',\n      '2016-10-01 09:41:39+00:00\\t',\n      '2016-10-01 09:41:39+00:00\\t',\n      '2016-09-23\\n'\n    ],\n    1: [\n      '2016-09-17 00:10:56\\t',\n      '29.9927699\\t',\n      '31.2461142\\t',\n      'driver_analytics\\t',\n      '1472688000000\\t',\n      'false\\t',\n      '2\\t',\n      '2016-09-23T00:00:00.000Z\\t',\n      '2016-10-01 09:46:37+00:00\\t',\n      '2016-10-01 16:46:37+00:00\\t',\n      '2016-09-23\\n'\n    ],\n    2: [\n      '2016-09-17 00:11:56\\t',\n      '29.9907261\\t',\n      '31.2312742\\t',\n      'driver_analytics\\t',\n      '1472688000000\\t',\n      'false\\t',\n      '3\\t',\n      '2016-09-23T00:00:00.000Z\\t',\n      '\\t',\n      '\\t',\n      '2016-09-23\\n'\n    ],\n    13: [\n      '2016-09-17 00:22:20\\t',\n      '30.034391\\t',\n      '31.2193991\\t',\n      'driver_analytics\\t',\n      '1472754400000\\t',\n      '\\t',\n      '\\t',\n      '2016-09-23T06:00:00.000Z\\t',\n      '\\t',\n      '\\t',\n      '\\n'\n    ]\n  };\n\n  Object.entries(expectedRows).forEach(keyAndRow => {\n    const [index, expectedRow] = keyAndRow;\n    const cells = wrapper2.find(`.row-${index}`);\n\n    t.equal(cells.length, testFields.length, 'should render 11 cells');\n\n    for (let c = 0; c < cells.length; c++) {\n      const cellText = cells.at(c).text();\n      t.equal(cellText, expectedRow[c], `should render cell ${expectedRow[c]} correctly`);\n    }\n  });\n\n  t.end();\n});\n\ntest('Components -> DataTableModal -> render DataTable: sort, pin and display format', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n\n  // set display format\n  const formats = {\n    'gps_data.lat': TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_2.format\n  };\n  const nextState = visStateReducer(\n    initialState,\n    VisStateActions.setColumnDisplayFormat(testCsvDataId, formats)\n  );\n\n  // sort column\n  const nextState1 = visStateReducer(\n    nextState,\n    VisStateActions.sortTableColumn(testCsvDataId, 'gps_data.lat')\n  );\n\n  // pin column\n  const nextState2 = visStateReducer(\n    nextState1,\n    VisStateActions.pinTableColumn(testCsvDataId, 'has_result')\n  );\n\n  const wrapper = mountWithTheme(\n    <DataTableModal datasets={nextState2.datasets} dataId={testCsvDataId} />\n  );\n\n  const props = wrapper.find(DataTable).at(0).props();\n\n  // mock cellSizeCache, width and height\n  const enriched = {\n    ...props,\n    cellSizeCache: expectedCellSizeCache,\n    fixedWidth: 1300,\n    fixedHeight: 800,\n    theme: {}\n  };\n\n  const wrapper2 = mountWithTheme(<DataTable {...enriched} />);\n  const componentInstance = wrapper2.find('DataTable').instance();\n  const result = componentInstance.getCellSizeCache();\n  // manually setting the state and update the component\n  act(() => {\n    componentInstance.setState(result);\n  });\n\n  wrapper2.update();\n\n  const expectedHeaders = [\n    'has_result',\n    'gps_data.utc_timestamp',\n    'gps_data.lat',\n    'gps_data.lng',\n    'gps_data.types',\n    'epoch',\n    'uid',\n    'time',\n    'begintrip_ts_utc',\n    'begintrip_ts_local',\n    'date'\n  ];\n\n  t.equal(\n    wrapper2.find('.header-cell').length,\n    testFields.length * STYLED_COMPONENTS_DUPLICATED_ENTRIES, // number of duplicates create by styled-components\n    `should render ${testFields.length} headers`\n  );\n\n  t.ok(\n    wrapper2.find('.header-cell').at(0).hasClass('pinned-header-cell'),\n    'should assign pinned-header-cell class'\n  );\n\n  t.ok(\n    wrapper2.find('.header-cell').at(0).hasClass('first-cell'),\n    'should assign first-cell class'\n  );\n\n  new Array(testFields.length).fill(0).forEach((d, i) => {\n    t.equal(\n      wrapper2\n        .find('.header-cell')\n        .at(i * STYLED_COMPONENTS_DUPLICATED_ENTRIES)\n        .find('.col-name__name')\n        .text(),\n      expectedHeaders[i],\n      'should render corrected pinned columns'\n    );\n  });\n\n  t.end();\n});\n\ntest('Components -> DatableModal -> sort/pin/copy and display format should be called with the right params', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n  const copyTableColumn = sinon.spy();\n  const pinTableColumn = sinon.spy();\n  const sortTableColumn = sinon.spy();\n  const setColumnDisplayFormat = sinon.spy();\n  const column = 'gps_data.lat';\n\n  const wrapper = mountWithTheme(\n    <DataTableModal\n      datasets={initialState.datasets}\n      dataId={testCsvDataId}\n      copyTableColumn={copyTableColumn}\n      pinTableColumn={pinTableColumn}\n      sortTableColumn={sortTableColumn}\n      setColumnDisplayFormat={setColumnDisplayFormat}\n    />\n  );\n\n  const {\n    sortTableColumn: testSortColumn,\n    pinTableColumn: testPinColumn,\n    copyTableColumn: testCopyColumn,\n    setColumnDisplayFormat: testSetColumnDisplayFormat\n  } = wrapper.find('DataTable').props();\n\n  testSortColumn(column);\n  testPinColumn(column);\n  testCopyColumn(column);\n  testSetColumnDisplayFormat({[column]: TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_2.format});\n\n  t.equal(\n    sortTableColumn.calledWith(testCsvDataId, column),\n    true,\n    'should call sortTableColumn with dataId and column gps_data.lat'\n  );\n\n  t.equal(\n    pinTableColumn.calledWith(testCsvDataId, column),\n    true,\n    'should call pinTableColumn with dataId and column gps_data.lat'\n  );\n\n  t.equal(\n    copyTableColumn.calledWith(testCsvDataId, column),\n    true,\n    'should call copyTableColumn with dataId and column gps_data.lat'\n  );\n\n  t.equal(\n    setColumnDisplayFormat.calledWith(testCsvDataId, {\n      [column]: TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_2.format\n    }),\n    true,\n    'should call setColumnDisplayFormat with dataId, column gps_data.lat, and format'\n  );\n\n  t.end();\n});\n\ntest('Components -> cellSize -> renderedSize', t => {\n  prepareMockCanvas();\n\n  const fields = [\n    {name: testColumns[0], type: 'geojson'},\n    {name: testColumns[1], type: 'real'},\n    {name: testColumns[2], type: 'string'}\n  ];\n  const dataContainer = createDataContainer(texts, {fields});\n\n  const wrapper = mountWithTheme(\n    <DataTableModal\n      datasets={{\n        smoothie: {\n          id: 'smoothie',\n          dataContainer,\n          fields: [\n            {name: testColumns[0], type: 'geojson', displayName: testColumns[0]},\n            {name: testColumns[1], type: 'real', displayName: testColumns[1]},\n            {name: testColumns[2], type: 'string', displayName: testColumns[2]}\n          ],\n          color: [113, 113, 113]\n        }\n      }}\n      dataId=\"smoothie\"\n    />\n  );\n\n  const props = wrapper.find(DataTable).at(0).props();\n\n  const expected = {\n    _geojson: {row: 500, header: 186},\n    'income level of people over 65': {row: 162, header: 223},\n    engagement: {row: 162, header: 186}\n  };\n\n  t.deepEqual(\n    props.cellSizeCache,\n    expected,\n    'DataTable should have the correct props.cellSizeCache'\n  );\n\n  restoreMockCanvas();\n  t.end();\n});\n\ntest('Components -> DataTableModal.render: csv 2', t => {\n  const dataContainer = createDataContainer(geoStyleRows, {fields: geoStyleFields});\n\n  // manually set display format on the 5th field: radius (.2~e)\n  const fieldsWithDisplayFormat = geoStyleFields;\n  fieldsWithDisplayFormat[5].displayFormat = TOOLTIP_FORMATS.DECIMAL_SCIENTIFIC_FIXED_2.format;\n\n  const wrapper = mountWithTheme(\n    <DataTableModal\n      datasets={{\n        smoothie: {\n          id: 'smoothie',\n          dataContainer,\n          fields: fieldsWithDisplayFormat,\n          color: [113, 113, 113],\n          data: geoStyleRows\n        }\n      }}\n      dataId=\"smoothie\"\n    />\n  );\n  const props = wrapper.find(DataTable).at(0).props();\n  const cellSizeCache = {\n    _geojson: {row: 400, header: 91},\n    fillColor: {row: 75, header: 90},\n    lineColor: {row: 75, header: 94},\n    lineWidth: {row: 75, header: 98},\n    elevation: {row: 75, header: 94},\n    radius: {row: 75, header: 90}\n  };\n  const enriched = {\n    ...props,\n    cellSizeCache,\n    fixedWidth: 1200,\n    fixedHeight: 800,\n    theme: {}\n  };\n\n  const expectedExpandedCellSizeGeo = {\n    cellSizeCache: {\n      _geojson: 410,\n      fillColor: 90,\n      lineColor: 94,\n      lineWidth: 98,\n      elevation: 94,\n      radius: 90\n    },\n    ghost: 324\n  };\n  const wrapper2 = mountWithTheme(<DataTable {...enriched} />);\n  const componentInstance = wrapper2.find('DataTable').instance();\n\n  const result = componentInstance.getCellSizeCache();\n  t.deepEqual(result, expectedExpandedCellSizeGeo, 'should calculate correct cell expansion');\n\n  act(() => {\n    componentInstance.setState(result);\n  });\n  wrapper2.update();\n\n  t.equal(\n    wrapper2.find('.header-cell').length,\n    7 * STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    `should render 7 header cells`\n  );\n\n  // test header cell\n  const expectedHeaders = [\n    geoStyleFields[0].name,\n    geoStyleFields[1].name,\n    geoStyleFields[2].name,\n    geoStyleFields[3].name,\n    geoStyleFields[4].name,\n    geoStyleFields[5].name,\n    ''\n  ];\n\n  expectedHeaders.forEach((name, index) => {\n    const header = wrapper2.find(`.header-cell.column-${index}`);\n    t.equal(header.length, STYLED_COMPONENTS_DUPLICATED_ENTRIES, 'should render 1 header');\n\n    if (index < 6) {\n      const cellText = header.find('.col-name__name').text();\n      t.equal(cellText, expectedHeaders[index], 'should render correct column name');\n      const cellType = header.find(FieldToken);\n      t.equal(cellType.prop('type'), geoStyleFields[index].type, 'should render correct cell type');\n      t.equal(header.find(VertThreeDots).length, 1, 'should render more button');\n      t.equal(header.find(OptionDropdown).length, 1, 'should render OptionDropdown');\n    } else {\n      // if ghost cell\n      t.equal(header.at(1).text(), '', 'cell should be empty');\n    }\n  });\n\n  t.equal(wrapper2.find('.cell').length, 21, `should render 21 data cells`);\n\n  const expectedRows = {\n    0: [\n      '{\"type\":\"Feature\",\"properties\":{\"fillColor\":[1,2,3],\"lineColor\":[4,5,6],\"lineWidth\":1,\"elevation\":10,\"radius\":5},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-122.1,37.3]}}\\t',\n      '[1,2,3]\\t',\n      '[4,5,6]\\t',\n      '1\\t',\n      '10\\t',\n      '5e+0\\t',\n      '\\n'\n    ],\n    1: [\n      '{\"type\":\"Feature\",\"properties\":{\"fillColor\":[7,8,9],\"lineColor\":[4,5,6],\"lineWidth\":3,\"elevation\":10,\"radius\":5},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-122.2,37.2]}}\\t',\n      '[7,8,9]\\t',\n      '[4,5,6]\\t',\n      '3\\t',\n      '10\\t',\n      '5e+0\\t',\n      '\\n'\n    ],\n    2: [\n      '{\"type\":\"Feature\",\"properties\":{\"fillColor\":[1,2,3],\"lineColor\":[4,5,6],\"lineWidth\":4,\"elevation\":10,\"radius\":5},\"geometry\":{\"type\":\"Point\",\"coordinates\":[-122.3,37.1]}}\\t',\n      '[1,2,3]\\t',\n      '[4,5,6]\\t',\n      '4\\t',\n      '10\\t',\n      '5e+0\\t',\n      '\\n'\n    ]\n  };\n\n  Object.entries(expectedRows).forEach(keyAndRow => {\n    const [index, expectedRow] = keyAndRow;\n\n    const cells = wrapper2.find(`.row-${index}`);\n    t.equal(cells.length, 7, 'should render 6 cells and 1 ghost cell');\n\n    for (let c = 0; c < cells.length; c++) {\n      const cellText = cells.at(c).text();\n      t.equal(cellText, expectedRow[c], `should render cell ${expectedRow[c]} correctly`);\n    }\n  });\n\n  t.end();\n});\n/* eslint-enable max-statements */\n"
  },
  {
    "path": "test/browser/components/modals/export-image-modal-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape-catch';\nimport sinon from 'sinon';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nimport {\n  ExportImageModalFactory,\n  ImagePreview,\n  appInjector,\n  SelectionButton\n} from '@kepler.gl/components';\n\nimport {INITIAL_UI_STATE} from '@kepler.gl/reducers';\n\nconst ExportImageModal = appInjector.get(ExportImageModalFactory);\n\ntest('Components -> ExportImageModal.mount', t => {\n  const onUpdateImageSetting = sinon.spy();\n  const cleanupExportImage = () => {};\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ExportImageModal\n          mapW={500}\n          mapH={500}\n          exportImage={INITIAL_UI_STATE.exportImage}\n          onUpdateImageSetting={onUpdateImageSetting}\n          cleanupExportImage={cleanupExportImage}\n        />\n      </IntlWrapper>\n    );\n  }, 'Show not fail with props');\n\n  t.ok(onUpdateImageSetting.calledTwice, 'should call onUpdateImageSetting when mount');\n  t.deepEqual(onUpdateImageSetting.args, [[{exporting: true}], [{mapH: 500, mapW: 500}]]);\n\n  t.equal(wrapper.find(ImagePreview).length, 1, 'should render ImagePreview');\n\n  const ratioOpts = wrapper\n    .find('#export-image-modal__option_ratio')\n    .at(0)\n    .find(SelectionButton)\n    .map(c => c.text());\n\n  t.deepEqual(ratioOpts, ['Original Screen', '4:3', '16:9'], 'should render correct ratio options');\n\n  t.ok(\n    wrapper\n      .find('#export-image-modal__option_ratio')\n      .at(0)\n      .find(SelectionButton)\n      .at(0)\n      .props('selected'),\n    'first option should be selected'\n  );\n\n  wrapper\n    .find('#export-image-modal__option_ratio')\n    .at(0)\n    .find(SelectionButton)\n    .at(0)\n    .simulate('click');\n  t.ok(onUpdateImageSetting.calledWith({ratio: 'SCREEN'}), 'should call update ratio');\n\n  const resolutionOpts = wrapper\n    .find('#export-image-modal__option_resolution')\n    .at(0)\n    .find(SelectionButton)\n    .map(c => c.text());\n\n  t.deepEqual(resolutionOpts, ['1x', '2x'], 'should render correct ratio options');\n\n  t.ok(\n    wrapper\n      .find('#export-image-modal__option_resolution')\n      .at(0)\n      .find(SelectionButton)\n      .at(0)\n      .props('selected'),\n    'first option should be selected'\n  );\n\n  wrapper\n    .find('#export-image-modal__option_resolution')\n    .at(0)\n    .find(SelectionButton)\n    .at(1)\n    .simulate('click');\n\n  t.ok(onUpdateImageSetting.calledWith({resolution: 'TWO_X'}), 'should call update resolution');\n\n  t.end();\n});\n\ntest('Components -> ExportImageModal.preview image', t => {\n  const onUpdateImageSetting = sinon.spy();\n  const cleanupExportImage = () => {};\n\n  const imageDataUri = 'data:image/png;base64,2i3u';\n  const exportImage = {\n    ...INITIAL_UI_STATE.exportImage,\n    imageDataUri,\n    imageSize: {\n      scale: 1,\n      imageW: 500,\n      imageH: 500\n    }\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ExportImageModal\n          mapW={500}\n          mapH={500}\n          exportImage={exportImage}\n          onUpdateImageSetting={onUpdateImageSetting}\n          cleanupExportImage={cleanupExportImage}\n        />\n      </IntlWrapper>\n    );\n  }, 'Show not fail with exportImage.imageDataUri');\n\n  t.equal(\n    wrapper.find('.preview-image-placeholder').html(),\n    '<img class=\"preview-image-placeholder\" src=\"data:image/png;base64,2i3u\" alt=\"Map preview\">',\n    'should render image with src'\n  );\n\n  t.equal(\n    wrapper.find('.preview-image').html(),\n    '<div class=\"preview-image\"><div class=\"preview-image-container\"><img class=\"preview-image-placeholder\" src=\"data:image/png;base64,2i3u\" alt=\"Map preview\"></div></div>',\n    'should render preview image with src'\n  );\n\n  t.end();\n});\n\ntest('Components -> ExportImageModal.unmount', t => {\n  const onUpdateImageSetting = () => {};\n  const cleanupExportImage = sinon.spy();\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ExportImageModal\n          mapW={500}\n          mapH={500}\n          exportImage={INITIAL_UI_STATE.exportImage}\n          onUpdateImageSetting={onUpdateImageSetting}\n          cleanupExportImage={cleanupExportImage}\n        />\n      </IntlWrapper>\n    );\n  }, 'Show not fail with props');\n  t.ok(cleanupExportImage.notCalled, 'should not call cleanupExportImage when mount');\n\n  wrapper.unmount();\n  t.ok(cleanupExportImage.calledOnce, 'should call cleanupExportImage when unmount');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/modals/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './data-table-modal-test';\nimport './export-image-modal-test';\nimport './load-data-modal-test';\n"
  },
  {
    "path": "test/browser/components/modals/load-data-modal-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {STYLED_COMPONENTS_DUPLICATED_ENTRIES} from '../../../helpers/utils';\nimport {\n  LoadDataModalFactory,\n  ModalTabItem,\n  LoadStorageMapFactory,\n  appInjector\n} from '@kepler.gl/components';\n\nconst LoadDataModal = appInjector.get(LoadDataModalFactory);\nconst LoadStorageMap = appInjector.get(LoadStorageMapFactory);\n\ntest('Components -> LoadDataModal.mount', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LoadDataModal />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  t.equal(\n    wrapper.find('.file-uploader').length,\n    STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    'should render FileUpload'\n  );\n  t.equal(\n    wrapper.find('.load-data-modal__tab').length,\n    STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    'should render ModalTabs'\n  );\n  t.equal(wrapper.find(LoadStorageMap).length, 0, 'should not render LoadStorageMap');\n  t.end();\n});\n\ntest('Components -> LoadDataModal -> custom loading method', t => {\n  // mount\n  const MockComp = () => <div className=\"taro\" />;\n  const MockTabComp = () => <div className=\"taro's tab\" />;\n\n  const loadingMethods = [\n    {\n      id: 'taro',\n      label: 'Taro and Blue',\n      elementType: MockComp,\n      tabElementType: MockTabComp\n    }\n  ];\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LoadDataModal loadingMethods={loadingMethods} />\n      </IntlWrapper>\n    );\n  }, 'Show not fail without props');\n\n  t.equal(wrapper.find(ModalTabItem).length, 1, 'should render 1 ModalTabItem');\n  t.equal(wrapper.find(MockComp).length, 1, 'should render MockComp by default');\n  t.equal(wrapper.find(MockTabComp).length, 1, 'should render MockTabComp');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/notifications/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './notification-panel-test';\n"
  },
  {
    "path": "test/browser/components/notifications/notification-item.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nimport {screen} from '@testing-library/react';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {NotificationItemFactory, appInjector} from '@kepler.gl/components';\nimport {createNotification} from '@kepler.gl/utils';\n\nimport {renderWithTheme} from '../../../helpers/component-jest-utils';\n\nconst NotificationItem = appInjector.get(NotificationItemFactory);\n\ndescribe('Notification tests', () => {\n  it('display SUCCESS notification', () => {\n    const successNotification = createNotification({message: 'success', type: 'success'});\n    // render the component\n    renderWithTheme(<NotificationItem notification={successNotification} />);\n    const heading = screen.getByTestId(dataTestIds.successIcon);\n    expect(heading).toBeInTheDocument();\n  });\n\n  it('display ERROR notification', () => {\n    const errorNotification = createNotification({message: 'error', type: 'error'});\n    // render the component\n    renderWithTheme(<NotificationItem notification={errorNotification} />);\n    const heading = screen.getByTestId(dataTestIds.errorIcon);\n    expect(heading).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "test/browser/components/notifications/notification-panel-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount,enzyme-deprecation/no-shallow */\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {shallow} from 'enzyme';\nimport {NotificationItemFactory, NotificationPanelFactory} from '@kepler.gl/components';\nimport {createNotification} from '@kepler.gl/utils';\nimport {theme} from '@kepler.gl/styles';\n\nconst NotificationItem = NotificationItemFactory();\nconst NotificationPanel = NotificationPanelFactory(NotificationItem);\n\nconst notifications = [\n  createNotification({message: '1', type: 'success'}),\n  createNotification({message: '2', type: 'error'}),\n  createNotification({message: '3', topic: 'file'}),\n  createNotification({message: '4', type: 'info'})\n];\n\ntest('Notification Panel - Show notifications', t => {\n  const removeNotification = sinon.spy();\n  const $ = shallow(\n    <NotificationPanel\n      notifications={notifications}\n      removeNotification={removeNotification}\n      theme={theme}\n    />\n  );\n\n  // Check notifications\n  t.equal($.find('NotificationItem').length, 3, 'Should display only 3 Notifications');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/plot-container-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nimport test from 'tape';\nimport {appInjector, PlotContainerFactory, plotContainerSelector} from '@kepler.gl/components';\nimport {mockKeplerProps} from '../../helpers/mock-state';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\n\nconst PlotContainer = appInjector.get(PlotContainerFactory);\nconst initialProps = plotContainerSelector(mockKeplerProps);\nconst initialState = {mapState: {latitude: 0, longitude: 0}, uiState: mockKeplerProps.uiState};\nconst mockStore = configureStore();\n\ntest('PlotContainer -> mount', t => {\n  const store = mockStore(initialState);\n  t.doesNotThrow(() => {\n    mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <PlotContainer {...initialProps} />\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'PlotContainer should not fail without props');\n\n  t.end();\n});\n\ntest('PlotContainer -> mount -> imageSize', t => {\n  let wrapper;\n  const imageSize = {\n    scale: 1,\n    imageW: 800,\n    imageH: 600\n  };\n\n  const store = mockStore(initialState);\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <PlotContainer\n            {...initialProps}\n            imageSize={imageSize}\n            mapFields={initialProps.mapFields}\n            setExportImageSetting={() => {}}\n            setExportImageDataUri={() => {}}\n            setExportImageError={() => {}}\n            addNotification={() => {}}\n          />\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'PlotContainer should not fail without props');\n\n  let map = wrapper.find('MapContainer').instance();\n\n  t.equal(map.props.mapState.width, 800, 'should send imageW to mapState');\n  t.equal(map.props.mapState.height, 600, 'should send imageH to mapState');\n\n  // set center to be true\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <PlotContainer\n            {...initialProps}\n            imageSize={imageSize}\n            center={true}\n            mapFields={initialProps.mapFields}\n            setExportImageSetting={() => {}}\n            setExportImageDataUri={() => {}}\n            setExportImageError={() => {}}\n            addNotification={() => {}}\n          />\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'PlotContainer should not fail without props');\n\n  map = wrapper.find('MapContainer').instance();\n\n  t.equal(map.props.mapState.latitude, 33.89064297228446, 'should set latitude when center: true');\n  t.equal(\n    map.props.mapState.longitude,\n    -45.57105275929253,\n    'should set longitude when center: true'\n  );\n  t.equal(map.props.mapState.zoom, 1.8721094288367688, 'should set zoom when center: true');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/channel-by-value-selctor-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\n\nimport {layerColorUIChange, layerVisualChannelConfigChange} from '@kepler.gl/actions';\nimport {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers';\nimport {ColorBreaksDisplay} from '@kepler.gl/components';\nimport cloneDeep from 'lodash/cloneDeep';\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nimport {\n  DropdownSelect,\n  ColorBreaksPanelFactory,\n  ColorScaleSelectorFactory,\n  CustomPaletteFactory,\n  DimensionScaleSelectorFactory,\n  appInjector,\n  ChannelByValueSelectorFactory,\n  FieldSelectorFactory,\n  DropdownList,\n  ListItem,\n  Typeahead,\n  Button,\n  ColorPaletteItem,\n  ColorSwatch,\n  EditableColorRange\n} from '@kepler.gl/components';\n\nimport {StateWFilesFiltersLayerColor, expectedColorRangeInLayer} from 'test/helpers/mock-state';\n\nconst ChannelByValueSelector = appInjector.get(ChannelByValueSelectorFactory);\nconst DimensionScaleSelector = appInjector.get(DimensionScaleSelectorFactory);\nconst ColorScaleSelector = appInjector.get(ColorScaleSelectorFactory);\nconst FieldSelector = appInjector.get(FieldSelectorFactory);\nconst ColorBreaksPanel = appInjector.get(ColorBreaksPanelFactory);\nconst CustomPalette = appInjector.get(CustomPaletteFactory);\n\nfunction nop() {\n  return;\n}\n\nconst ExpectedCustomPalette = {\n  customPalette: {\n    name: 'color.customPalette.custom.uid',\n    type: 'custom',\n    category: 'Custom',\n    colors: expectedColorRangeInLayer.colors,\n    colorMap: [\n      [3032, expectedColorRangeInLayer.colors[0]],\n      [6063, expectedColorRangeInLayer.colors[1]],\n      [9093, expectedColorRangeInLayer.colors[2]],\n      [null, expectedColorRangeInLayer.colors[3]]\n    ]\n  },\n  showSketcher: false,\n  showDropdown: false,\n  showColorChart: true,\n  colorRangeConfig: {\n    type: 'all',\n    steps: 6,\n    reversed: false,\n    custom: false,\n    customBreaks: true,\n    colorBlindSafe: false\n  }\n};\n\n// const updateLayerVisualChannelConfig = sinon.spy();\n// const updateLayerColorUI = sinon.spy();\ntest('Components -> ChannelByValueSelector -> ColorScaleSelector -> disabled', t => {\n  const InitialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const {layers, datasets} = InitialState.visState;\n  const pointLayer = layers[0];\n  const updateLayerVisualChannelConfig = sinon.spy();\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ChannelByValueSelector\n          layer={pointLayer}\n          channel={pointLayer.visualChannels.color}\n          onChange={updateLayerVisualChannelConfig}\n          fields={datasets[pointLayer.config.dataId].fields}\n          dataset={datasets[pointLayer.config.dataId]}\n          setColorUI={nop}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail rendering point layer');\n\n  // colorField: string\n  t.equal(wrapper.find(DimensionScaleSelector).length, 1, 'Should render 1 DimensionScaleSelector');\n  t.equal(wrapper.find(ColorScaleSelector).length, 1, 'Should render 1 ColorScaleSelector');\n  t.equal(\n    wrapper.find(ColorScaleSelector).at(0).props().disabled,\n    false,\n    'Should enable color scale select for string field'\n  );\n\n  t.equal(\n    wrapper.find(ColorScaleSelector).at(0).find(DropdownSelect).length,\n    1,\n    'should render 1 DropdownSelect in ColorScaleSelector'\n  );\n  t.equal(\n    wrapper\n      .find(ColorScaleSelector)\n      .at(0)\n      .find(DropdownSelect)\n      .at(0)\n      .find(ListItem)\n      .at(0)\n      .find('span')\n      .text(),\n    'Ordinal',\n    'should render correct scale value'\n  );\n\n  // change color field\n  const fieldSelector = wrapper.find(FieldSelector).at(0);\n  fieldSelector.find('.item-selector__dropdown').at(0).simulate('click');\n\n  // dropdownSelect.simulate('click');\n\n  t.equal(wrapper.find(FieldSelector).at(0).find(Typeahead).length, 1, 'should render Typeahead');\n  t.equal(\n    wrapper.find(FieldSelector).at(0).find(DropdownList).length,\n    1,\n    'should render 1 DropdownList'\n  );\n  t.equal(\n    wrapper.find(FieldSelector).at(0).find('.list__item').length,\n    10,\n    'should render 10 ListItem'\n  );\n\n  t.equal(\n    wrapper.find(FieldSelector).at(0).find('.list__item').at(5).find('.list__item__anchor').text(),\n    'uid',\n    'Should render id field at index:5'\n  );\n  // select id field\n  wrapper.find(FieldSelector).at(0).find('.list__item').at(5).simulate('click');\n\n  t.ok(updateLayerVisualChannelConfig.calledOnce, 'Should call updateLayerVisualChannelConfig');\n\n  t.deepEqual(\n    updateLayerVisualChannelConfig.args[0],\n    [{colorField: datasets[pointLayer.config.dataId].fields[6]}, 'color'],\n    'should pass color field'\n  );\n\n  // set point layer color field to integar id\n  const updatedState = coreReducer(\n    InitialState,\n    layerVisualChannelConfigChange(pointLayer, ...updateLayerVisualChannelConfig.args[0])\n  );\n\n  t.equal(\n    updatedState.visState.layers[0].config.colorField.name,\n    'uid',\n    'should set color field to id'\n  );\n\n  t.end();\n});\n\ntest('Components -> ChannelByValueSelector -> ColorScaleSelector -> ColorBreakDisplay', t => {\n  const InitialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const {layers, datasets} = InitialState.visState;\n  const pointLayer = layers[0];\n  const updateLayerVisualChannelConfig = sinon.spy();\n  const setColorUI = sinon.spy();\n\n  const updatedState = coreReducer(\n    InitialState,\n    layerVisualChannelConfigChange(\n      pointLayer,\n      {colorField: datasets[pointLayer.config.dataId].fields[6]},\n      'color'\n    )\n  );\n\n  const pointLayer1 = updatedState.visState.layers[0];\n  t.equal(pointLayer1.config.colorField.name, 'uid', 'should set color field to id');\n  const InitialProps = {\n    layer: pointLayer1,\n    channel: pointLayer1.visualChannels.color,\n    onChange: updateLayerVisualChannelConfig,\n    fields: updatedState.visState.datasets[pointLayer1.config.dataId].fields,\n    dataset: updatedState.visState.datasets[pointLayer1.config.dataId],\n    setColorUI\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ChannelByValueSelector {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail rendering point layer');\n\n  // colorField: string\n  t.equal(wrapper.find(DimensionScaleSelector).length, 1, 'Should render 1 DimensionScaleSelector');\n  t.equal(wrapper.find(ColorScaleSelector).length, 1, 'Should render 1 ColorScaleSelector');\n  t.equal(\n    wrapper.find(ColorScaleSelector).at(0).props().disabled,\n    false,\n    'Should not disabled color scale select'\n  );\n\n  // scale optons\n  t.equal(\n    wrapper.find(ColorScaleSelector).at(0).find('.list__item').length,\n    4,\n    'should render 4 scale options'\n  );\n\n  t.equal(wrapper.find(ColorBreaksPanel).length, 1, 'Should render 1 ColorBreaksPanel');\n  t.equal(wrapper.find(ColorBreaksDisplay).length, 1, 'Should render 1 ColorBreaksDisplay');\n  t.equal(wrapper.find(CustomPalette).length, 0, 'Should not render CustomPalette');\n\n  t.equal(wrapper.find(ColorPaletteItem).length, 4, 'Should render 4 ColorPaletteItem');\n\n  const expectedText = [\n    [expectedColorRangeInLayer.colors[0], '1', '3032'],\n    [expectedColorRangeInLayer.colors[1], '3032', '6063'],\n    [expectedColorRangeInLayer.colors[2], '6063', '9093'],\n    [expectedColorRangeInLayer.colors[3], '9093', '12120']\n  ];\n\n  for (let i = 0; i < 4; i++) {\n    const inputTexts = wrapper\n      .find(ColorPaletteItem)\n      .at(i)\n      .find('div.custom-palette-hex__input__text');\n    t.equal(\n      wrapper.find(ColorPaletteItem).at(i).find(ColorSwatch).props().color,\n      expectedText[i][0],\n      'should display correct color'\n    );\n    t.equal(inputTexts.at(0).text(), expectedText[i][1], 'should display correct range');\n    t.equal(inputTexts.at(1).text(), expectedText[i][2], 'should display correct range');\n  }\n\n  // select custom scale\n  wrapper.find(ColorScaleSelector).at(0).find('.list__item').at(2).simulate('click');\n\n  t.ok(setColorUI.calledOnce, 'should call setColorUI');\n  const expectedArgs = [\n    'colorRange',\n    {\n      showColorChart: true,\n      colorRangeConfig: {\n        customBreaks: true\n      },\n      customPalette: {\n        name: 'color.customPalette.custom.uid',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA'],\n        colorMap: [\n          [3032, '#00939C'],\n          [6063, '#6BB5B9'],\n          [9093, '#AAD7D9'],\n          [null, '#E6FAFA']\n        ]\n      }\n    }\n  ];\n  const expectedArgs1 = [\n    {colorScale: 'custom'},\n    'color',\n    {\n      colorRange: {\n        name: 'color.customPalette.custom.uid',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA'],\n        colorMap: [\n          [3032, '#00939C'],\n          [6063, '#6BB5B9'],\n          [9093, '#AAD7D9'],\n          [null, '#E6FAFA']\n        ]\n      }\n    }\n  ];\n  t.deepEqual(setColorUI.args[0], expectedArgs, 'should set customBreaks to true');\n\n  t.ok(updateLayerVisualChannelConfig.calledOnce, 'should call updateLayerVisualChannelConfig');\n  t.deepEqual(updateLayerVisualChannelConfig.args[0], expectedArgs1, 'should pass custom scale');\n\n  const updatedStateTemp = coreReducer(\n    updatedState,\n    layerColorUIChange(pointLayer1, ...expectedArgs)\n  );\n  const updatedState1 = coreReducer(\n    updatedStateTemp,\n    layerVisualChannelConfigChange(pointLayer1, ...expectedArgs1)\n  );\n\n  t.deepEqual(\n    updatedState1.visState.layers[0].config.colorUI.colorRange,\n    ExpectedCustomPalette,\n    'Should set customPalette and colorMap'\n  );\n\n  wrapper.setProps({\n    children: <ChannelByValueSelector {...InitialProps} layer={updatedState1.visState.layers[0]} />\n  });\n\n  t.equal(wrapper.find(CustomPalette).length, 1, 'should render 1 CustomPalette');\n  t.equal(wrapper.find(ColorBreaksDisplay).length, 0, 'should not render ColorBreaksDisplay');\n  t.equal(wrapper.find(EditableColorRange).length, 4, 'Should render 4 EditableColorRange');\n  t.equal(wrapper.find(ColorSwatch).length, 4, 'Should render 4 ColorSwatch');\n\n  for (let i = 0; i < 4; i++) {\n    const inputs = wrapper.find(EditableColorRange).at(i).find('input');\n    if (i === 0 || i === 3) {\n      t.equal(inputs.length, 1, 'should render 1 input');\n      t.equal(\n        inputs.at(0).props().value,\n        expectedText[i][i === 0 ? 2 : 1],\n        `should display correct range i: ${i}`\n      );\n    } else {\n      t.equal(inputs.length, 2, 'should render 2 inputs');\n\n      t.equal(inputs.at(0).props().value, expectedText[i][1], `should display correct range ${i}`);\n\n      t.equal(inputs.at(1).props().value, expectedText[i][2], `should display correct range ${i}`);\n    }\n  }\n\n  wrapper\n    .find(EditableColorRange)\n    .at(1)\n    .find('input')\n    .at(1)\n    .simulate('change', {target: {value: '5000'}});\n  t.notOk(setColorUI.calledTwice, 'should not call setColorUI');\n\n  wrapper.find(EditableColorRange).at(1).find('input').at(1).simulate('blur');\n  t.ok(setColorUI.calledTwice, 'should call setColorUI');\n  const expectedCustomPalette1 = {\n    ...ExpectedCustomPalette.customPalette,\n    colorMap: [\n      [3032, expectedColorRangeInLayer.colors[0]],\n      [5000, expectedColorRangeInLayer.colors[1]],\n      [9093, expectedColorRangeInLayer.colors[2]],\n      [null, expectedColorRangeInLayer.colors[3]]\n    ]\n  };\n\n  const expectedArgs2 = [\n    'colorRange',\n    {\n      customPalette: expectedCustomPalette1\n    }\n  ];\n  t.deepEqual(setColorUI.args[1], expectedArgs2, 'should set customBreaks.colorMap');\n\n  // on confirm custom breaks\n  t.equal(wrapper.find(CustomPalette).find(Button).length, 2, 'should render 2 buttons');\n  const confirmButton = wrapper.find(CustomPalette).find(Button).at(0);\n  t.equal(confirmButton.text(), 'Confirm', 'should render confirm button');\n  confirmButton.simulate('click');\n\n  // Confirm should result in at least a second update call\n  t.ok(\n    updateLayerVisualChannelConfig.args && updateLayerVisualChannelConfig.args.length >= 2,\n    'should call updateLayerVisualChannelConfig'\n  );\n\n  const expectedArgs3 = [\n    {colorScale: 'custom'},\n    'color',\n    {colorRange: ExpectedCustomPalette.customPalette}\n  ];\n  t.deepEqual(\n    updateLayerVisualChannelConfig.args[1],\n    expectedArgs3,\n    'should pass custom scale and custom colorRange'\n  );\n  const expectedArgs4 = [\n    'colorRange',\n    {showSketcher: false, colorRangeConfig: {customBreaks: false}}\n  ];\n  t.deepEqual(setColorUI.args[2], expectedArgs4, 'should set customBreaks to false');\n\n  const pointLayer2 = updatedState1.visState.layers[0];\n  const updatedStateTemp1 = coreReducer(\n    updatedState1,\n    layerVisualChannelConfigChange(pointLayer2, ...expectedArgs3)\n  );\n  const updatedState2 = coreReducer(\n    updatedStateTemp1,\n    layerColorUIChange(pointLayer2, ...expectedArgs4)\n  );\n\n  t.deepEqual(\n    updatedState2.visState.layers[0].config.colorUI.colorRange,\n    {\n      ...ExpectedCustomPalette,\n      colorRangeConfig: {\n        ...ExpectedCustomPalette.colorRangeConfig,\n        customBreaks: false\n      }\n    },\n    'Should set customPalette and colorMap'\n  );\n  t.deepEqual(\n    updatedState2.visState.layers[0].config.colorScale,\n    'custom',\n    'Should set color scale to custom'\n  );\n  t.deepEqual(\n    updatedState2.visState.layers[0].config.visConfig.colorRange,\n    ExpectedCustomPalette.customPalette,\n    'Should set color range to custom palette'\n  );\n\n  wrapper.setProps({\n    children: <ChannelByValueSelector {...InitialProps} layer={updatedState2.visState.layers[0]} />\n  });\n  // set scale back to standard scale (:quantile)\n  const quantileOption = wrapper.find(ColorScaleSelector).at(0).find('.list__item').at(1);\n  t.equal(quantileOption.text(), 'Quantile', '2nd scale option should be quantile');\n  quantileOption.simulate('click');\n\n  const expectedArgs5 = [\n    {colorScale: 'quantize'},\n    'color',\n    {\n      colorRange: {\n        name: 'Uber Viz Sequential',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA']\n      }\n    }\n  ];\n\n  t.deepEqual(\n    updateLayerVisualChannelConfig.args[2],\n    expectedArgs5,\n    'should pass custom scale and custom colorRange'\n  );\n\n  t.deepEqual(setColorUI.args.length, 4, 'should not call setColorUI');\n\n  t.end();\n});\n\nconst GetTickPositions = wrapper => {\n  wrapper.find('.color-chart-tick-container').update();\n  return wrapper\n    .find('.color-chart-tick-container')\n    .children()\n    .map(x => {\n      const style = x.props().style;\n      return Math.round(\n        parseFloat(style.left) + parseFloat(style.borderWidth) + parseFloat(style.width) / 2\n      );\n    });\n};\n\nconst GetInitialPropsFromState = (layer, updatedState) => {\n  return {\n    layer,\n    channel: layer.visualChannels.color,\n    onChange: newConfig => layer.updateLayerConfig(newConfig),\n    fields: updatedState.visState.datasets[layer.config.dataId].fields,\n    dataset: updatedState.visState.datasets[layer.config.dataId],\n    setColorUI: (prop, newConfig) => layer.updateLayerColorUI(prop, newConfig)\n  };\n};\n\ntest('Components -> ChannelByValueSelector -> ColorScaleSelector -> ColumnStatsChart', t => {\n  const InitialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const {layers, datasets} = InitialState.visState;\n  const pointLayer = layers[0];\n\n  // set point layer color field to integar id\n  const colorField = datasets[pointLayer.config.dataId].fields[6];\n  const updatedState = coreReducer(\n    InitialState,\n    layerVisualChannelConfigChange(pointLayer, {colorField}, 'color')\n  );\n  const pointLayer1 = updatedState.visState.layers[0];\n  const InitialProps = GetInitialPropsFromState(pointLayer1, updatedState);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ChannelByValueSelector {...InitialProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail rendering point layer');\n\n  t.equal(\n    wrapper.find('.color-chart-container').length,\n    1,\n    'Should render color chart with histogram'\n  );\n\n  const quantizeTestCase = {\n    optionIndex: 0,\n    optionName: 'quantize',\n    expectedTickPositions: [53, 105, 158]\n  };\n\n  const quantileTestCase = {\n    optionIndex: 1,\n    optionName: 'quantile',\n    expectedTickPositions: [0, 0, 1]\n  };\n\n  const customTestCase = {\n    optionIndex: 2,\n    optionName: 'custom',\n    // uses tick positions from the previous test case!\n    expectedTickPositions: [0, 0, 1]\n  };\n\n  [quantizeTestCase, quantileTestCase, customTestCase].forEach(tc => {\n    // simulate click color scale option\n    wrapper.find(ColorScaleSelector).at(0).find('.list__item').at(tc.optionIndex).simulate('click');\n\n    // trigger action\n    const updatedState2 = coreReducer(\n      updatedState,\n      layerVisualChannelConfigChange(pointLayer1, {colorScale: tc.optionName}, 'color')\n    );\n    const pointLayer2 = updatedState2.visState.layers[0];\n    const InitialProps2 = GetInitialPropsFromState(pointLayer2, updatedState2);\n    wrapper.setProps({\n      children: <ChannelByValueSelector {...InitialProps2} />\n    });\n\n    t.deepEqual(\n      GetTickPositions(wrapper),\n      tc.expectedTickPositions,\n      `Should render three ticks for scale ${tc.optionName}`\n    );\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/color-selector-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements, enzyme-deprecation/no-mount */\nimport {mount} from 'enzyme';\nimport cloneDeep from 'lodash/cloneDeep';\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\n\nimport {appInjector, Button, ColorPalette} from '@kepler.gl/components';\n\nimport {\n  AddColorStop,\n  ArcLayerColorSelectorFactory,\n  ColorBlock,\n  ColorRangeSelectorFactory,\n  ColorSelectorFactory,\n  ColorSelectorInput,\n  CustomPaletteFactory,\n  CustomPicker,\n  DeleteColorStop,\n  EditableColorRange,\n  getLayerConfiguratorProps,\n  getVisConfiguratorProps,\n  LayerColorRangeSelectorFactory,\n  LayerColorSelectorFactory,\n  PaletteConfig,\n  Portaled,\n  SingleColorPalette\n} from '@kepler.gl/components';\n\nimport {KEPLER_COLOR_PALETTES} from '@kepler.gl/constants';\n\nimport {hexToRgb} from '@kepler.gl/utils';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {StateWFilesFiltersLayerColor, StateWTrips} from 'test/helpers/mock-state';\nimport {clickItemSelector} from '../../../helpers/component-utils';\nimport {STYLED_COMPONENTS_DUPLICATED_ENTRIES} from '../../../helpers/utils';\n\nconst ColorSelector = appInjector.get(ColorSelectorFactory);\nconst LayerColorSelector = appInjector.get(LayerColorSelectorFactory);\nconst LayerColorRangeSelector = appInjector.get(LayerColorRangeSelectorFactory);\nconst ArcLayerColorSelector = appInjector.get(ArcLayerColorSelectorFactory);\nconst ColorRangeSelector = appInjector.get(ColorRangeSelectorFactory);\nconst CustomPalette = appInjector.get(CustomPaletteFactory);\n\ntest('Components -> ColorSelector.render', t => {\n  t.doesNotThrow(() => {\n    mount(<ColorSelector />);\n  }, 'Should not fail without props');\n\n  t.end();\n});\n\ntest('Components -> LayerColorSelector.render -> render layer color', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor.visState);\n  const {layers} = initialState;\n  const pointLayer = layers[0];\n\n  let wrapper;\n\n  // create spies\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const layerConfiguratorProps = getLayerConfiguratorProps(mockProps);\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorSelector {...layerConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail with layers');\n  const CSInstance = wrapper.find(ColorSelector);\n\n  t.equal(CSInstance.length, 1, 'should render 1 ColorSelector');\n\n  const cSProps = CSInstance.at(0).props();\n  const expectedColorSets = [\n    {\n      selectedColor: pointLayer.config.color,\n      setColor: () => {}\n    }\n  ];\n  t.equal(cSProps.colorSets.length, expectedColorSets.length, 'should pass correct colorSets');\n  t.deepEqual(\n    Object.keys(cSProps.colorSets[0]),\n    Object.keys(expectedColorSets[0]),\n    'should pass correct colorSets[0]'\n  );\n  t.equal(\n    cSProps.colorSets[0].selectedColor,\n    expectedColorSets[0].selectedColor,\n    'should pass correct colorSets.selectedColor'\n  );\n\n  // color block\n  const cblk = wrapper.find(ColorBlock);\n  t.equal(cblk.length, 1, 'should render 1 ColorBlock');\n\n  const csInput = wrapper.find(ColorSelectorInput);\n  t.equal(csInput.length, 1, 'should render 1 ColorSelectorInput');\n  csInput.simulate('click');\n\n  t.ok(updateLayerColorUI.calledOnce, 'should call updateLayerColorUI');\n  t.ok(updateLayerVisConfig.notCalled, 'should not call updateLayerColorUI');\n  t.ok(updateLayerConfig.notCalled, 'should not call updateLayerConfig');\n\n  // should open dropdown\n  t.ok(updateLayerColorUI.calledWith('color', {showDropdown: 0}));\n\n  t.end();\n});\n\ntest('Components -> LayerColorSelector.render -> render single color click', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor.visState);\n  const {layers} = initialState;\n  // should dropdown for layer color select\n  const pointLayer = layers[0].updateLayerColorUI('color', {showDropdown: 0});\n\n  let wrapper;\n\n  // create spies\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const layerConfiguratorProps = getLayerConfiguratorProps(mockProps);\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorSelector {...layerConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color select');\n  const CSInstance = wrapper.find(ColorSelector);\n\n  t.equal(CSInstance.length, 1, 'should render 1 ColorSelector');\n\n  // open color dropdown\n  const scp = wrapper.find(SingleColorPalette);\n  t.equal(scp.length, 1, 'should render 1 SingleColorPalette');\n\n  t.ok(wrapper.find('.single-color-palette__block'), 'should render color blocks');\n\n  // click color block\n  wrapper.find('.single-color-palette__block').at(0).simulate('click');\n\n  t.ok(updateLayerConfig.calledOnce, 'should call updateLayerConfig');\n  t.ok(updateLayerConfig.calledWith({color: [255, 254, 230]}));\n\n  t.end();\n});\n\ntest('Components -> ArcLayerColorSelector.render -> render single color', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n  const arcLayer = layers.find(l => l.type === 'arc');\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: arcLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const layerConfiguratorProps = getLayerConfiguratorProps(mockProps);\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ArcLayerColorSelector\n          layer={arcLayer}\n          setColorUI={layerConfiguratorProps.setColorUI}\n          onChangeConfig={layerConfiguratorProps.onChange}\n          onChangeVisConfig={visConfiguratorProps.onChange}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render arc color select');\n\n  const CSInstance = wrapper.find(ColorSelector);\n  t.equal(CSInstance.length, 1, 'should render 1 ColorSelector');\n\n  const cSProps = CSInstance.at(0).props();\n  const expectedColorSets = [\n    {\n      selectedColor: arcLayer.config.color,\n      setColor: () => {},\n      label: 'Source'\n    },\n    {\n      selectedColor: arcLayer.config.color,\n      setColor: () => {},\n      label: 'Target'\n    }\n  ];\n  t.equal(cSProps.colorSets.length, expectedColorSets.length, 'should pass correct colorSets');\n  t.deepEqual(\n    Object.keys(cSProps.colorSets[0]),\n    Object.keys(expectedColorSets[0]),\n    'should pass correct colorSets[0]'\n  );\n  t.equal(\n    cSProps.colorSets[0].selectedColor,\n    expectedColorSets[0].selectedColor,\n    'should pass correct colorSets.selectedColor'\n  );\n\n  // color block\n  const cblk = wrapper.find(ColorBlock);\n  t.equal(cblk.length, 2, 'should render 2 ColorBlocks');\n\n  const csInput = wrapper.find(ColorSelectorInput);\n  t.equal(csInput.length, 2, 'should render 2 ColorSelectorInput');\n  csInput.at(1).simulate('click');\n\n  t.ok(updateLayerColorUI.calledOnce, 'should call updateLayerColorUI when mousedown 2nd block');\n  t.ok(\n    updateLayerVisConfig.notCalled,\n    'should not call updateLayerColorUI when mousedown 2nd block'\n  );\n  t.ok(updateLayerConfig.notCalled, 'should not call updateLayerConfig when mousedown 2nd block');\n\n  // should open dropdown\n  t.ok(updateLayerColorUI.calledWith('color', {showDropdown: 1}));\n\n  t.end();\n});\n\ntest('Components -> ArcLayerColorSelector.render -> render single color click', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor.visState);\n  const {layers} = initialState;\n  // should dropdown for arc layer target color select\n  const arcLayer = layers[1].updateLayerColorUI('color', {showDropdown: 1});\n\n  // create spies\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: arcLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const layerConfiguratorProps = getLayerConfiguratorProps(mockProps);\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ArcLayerColorSelector\n          layer={arcLayer}\n          setColorUI={layerConfiguratorProps.setColorUI}\n          onChangeConfig={layerConfiguratorProps.onChange}\n          onChangeVisConfig={visConfiguratorProps.onChange}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render arc color select');\n  const CSInstance = wrapper.find(ColorSelector);\n  t.equal(CSInstance.length, 1, 'should render 1 ColorSelector');\n\n  // open color dropdown\n  const scp = wrapper.find(SingleColorPalette);\n  t.equal(scp.length, 1, 'should render 1 SingleColorPalette');\n\n  t.ok(wrapper.find('.single-color-palette__block'), 'should render color blocks');\n\n  // click color block\n  wrapper.find('.single-color-palette__block').at(0).simulate('click');\n\n  t.ok(updateLayerVisConfig.calledOnce, 'should call updateLayerVisConfig');\n  t.ok(updateLayerVisConfig.calledWith({targetColor: [255, 254, 230]}));\n\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n  const pointLayer = layers.find(l => l.type === 'point');\n\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n  const CPInstance = wrapper.find(ColorPalette);\n  t.equal(CPInstance.length, 1, 'should render 1 ColorPalette');\n\n  // render color blocks\n  const cblks = CPInstance.at(0).find('.color-range-palette__block');\n  t.equal(cblks.length, 8, 'should render 8 ColorBlocks');\n\n  const expectedColor = `rgb(${hexToRgb('#7F1941').join(', ')})`;\n  const firstColor = cblks.at(0).getDOMNode().style.getPropertyValue('background-color');\n\n  t.equal(firstColor, expectedColor, 'should render correct background color');\n\n  // simulate click\n  const csInput = wrapper.find(ColorSelectorInput);\n  t.equal(csInput.length, 1, 'should render 2 ColorSelectorInput');\n  csInput.at(0).simulate('click');\n\n  t.ok(updateLayerColorUI.calledOnce, 'should call updateLayerColorUI when mousedown 2nd block');\n  t.ok(\n    updateLayerVisConfig.notCalled,\n    'should not call updateLayerColorUI when mousedown 2nd block'\n  );\n  t.ok(updateLayerConfig.notCalled, 'should not call updateLayerConfig when mousedown 2nd block');\n\n  // should open dropdown\n  t.ok(updateLayerColorUI.calledWith('colorRange', {showDropdown: 0}));\n\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector -> ColorRangeSelector -> select palette', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n\n  // set showDropdown to true\n  const pointLayer = layers\n    .find(l => l.type === 'point')\n    .updateLayerColorUI('colorRange', {showDropdown: 0});\n\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n\n  // open color dropdown\n  t.equal(wrapper.find('.color-range-selector').length, 1, 'should render 1 color-range-selector');\n\n  const crs = wrapper.find(ColorRangeSelector);\n  t.equal(crs.length, 1, 'should render 1 ColorRangeSelector');\n\n  const item = crs.find('ColorPaletteItem');\n  t.equal(item.length, 65, `should render ${KEPLER_COLOR_PALETTES.length} Palettes`);\n\n  t.equal(\n    item.at(0).find(ColorPalette).find('.color-range-palette__block').length,\n    8,\n    'should render 8 color block'\n  );\n\n  const expectedColor = `rgb(${hexToRgb('#00939C').join(', ')})`;\n  const firstColor = item\n    .at(0)\n    .find(ColorPalette)\n    .find('.color-range-palette__block')\n    .at(0)\n    .getDOMNode()\n    .style.getPropertyValue('background-color');\n\n  t.equal(firstColor, expectedColor, 'should render correct background color');\n\n  // click 1st item\n  item.at(0).find('.color-palette-outer').simulate('click');\n  t.ok(updateLayerVisConfig.calledOnce, 'should call updateLayerVisConfig');\n\n  t.deepEqual(\n    updateLayerVisConfig.args[0][0].colorRange,\n    {\n      name: 'Uber Viz Diverging',\n      type: 'diverging',\n      category: 'Uber',\n      colors: [\n        '#C22E00',\n        '#D9653D',\n        '#EB9373',\n        '#F8C0AC',\n        '#B9E0E1',\n        '#8BC6C9',\n        '#5AACB2',\n        '#00939C'\n      ],\n      reversed: true\n    },\n    'should call updateLayerVisConfig with updated colorRange'\n  );\n\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector -> ColorRangeSelector -> select type', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n\n  // set showDropdown to true\n  const pointLayer = layers\n    .find(l => l.type === 'point')\n    .updateLayerColorUI('colorRange', {showDropdown: 0});\n  // color range\n  // {\n  //   name: 'Ice And Fire 8',\n  //   type: 'diverging',\n  //   category: 'Uber',\n  //   colors: [\n  //     '#7F1941', '#D50255',\n  //     '#FEAD54', '#FEEDB1',\n  //     '#E8FEB5', '#49E3CE',\n  //     '#0198BD', '#007A99'\n  //   ],\n  //   reversed: true\n  // }\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n\n  // open color dropdown\n  t.equal(wrapper.find('.color-range-selector').length, 1, 'should render 1 color-range-selector');\n\n  const crs = wrapper.find(ColorRangeSelector);\n  t.equal(crs.length, 1, 'should render 1 ColorRangeSelector');\n\n  const pc = crs.find(PaletteConfig);\n  t.equal(pc.length, 5, 'should render 5 PaletteConfig');\n\n  const cpg = crs.find('ColorPaletteItem');\n  t.equal(cpg.length, 65, `should render ${KEPLER_COLOR_PALETTES.length} Palettes`);\n\n  const typeSelect = crs.find(PaletteConfig).at(0);\n  t.equal(typeSelect.find('.side-panel-panel__label').text(), 'Type', 'should render type');\n\n  // click on type select\n  t.equal(\n    typeSelect.find('.item-selector__dropdown').length,\n    1,\n    'should render item selector dropdown input'\n  );\n\n  clickItemSelector(typeSelect);\n\n  wrapper.update();\n\n  const listItem = wrapper.find('.list__item');\n  t.equal(listItem.length, 5, 'should render item selector typeahead');\n\n  t.deepEqual(\n    listItem.map(nd => nd.at(0).find('.list__item__anchor').at(0).text()),\n    ['Sequential', 'Qualitative', 'Diverging', 'Cyclical', 'All'],\n    'should render all types'\n  );\n\n  // click on 1 type\n  listItem.at(2).simulate('click');\n  t.ok(updateLayerColorUI.calledOnce, 'updateLayerColorUI should be called');\n  const expectedArgs = [\n    'colorRange',\n    {\n      colorRangeConfig: {\n        type: 'diverging'\n      }\n    }\n  ];\n\n  t.deepEqual(\n    updateLayerColorUI.args[0],\n    expectedArgs,\n    'should call updateLayerColorUI with correct args'\n  );\n\n  const reverseSwitch = crs.find(PaletteConfig).at(2);\n  t.equal(\n    reverseSwitch.find('.side-panel-panel__label').text(),\n    'Reversed',\n    'should render reversed switch'\n  );\n\n  const switchInput = reverseSwitch.find('input').at(0);\n  switchInput.simulate('change');\n\n  t.ok(\n    updateLayerColorUI.calledTwice,\n    'updateLayerColorUI should be called when click reserved switch'\n  );\n  const expectedArgs1 = [\n    'colorRange',\n    {\n      colorRangeConfig: {\n        reversed: false\n      }\n    }\n  ];\n  t.deepEqual(\n    updateLayerColorUI.args[1],\n    expectedArgs1,\n    'should call updateLayerColorUI with reversed: false'\n  );\n\n  const colorBlindSwitch = crs.find(PaletteConfig).at(3);\n  t.equal(\n    colorBlindSwitch.find('.side-panel-panel__label').text(),\n    'Colorblind Safe',\n    'should render Colorblind Safe switch'\n  );\n\n  colorBlindSwitch.find('input').at(0).simulate('change');\n\n  t.ok(\n    updateLayerColorUI.callCount === 3,\n    'updateLayerColorUI should be called when click custom switch'\n  );\n  const expectedArgs3 = [\n    'colorRange',\n    {\n      colorRangeConfig: {\n        colorBlindSafe: true\n      }\n    }\n  ];\n  t.deepEqual(\n    updateLayerColorUI.args[2],\n    expectedArgs3,\n    'should call updateLayerColorUI with colorBlindSwitch: true'\n  );\n\n  const customSwitch = crs.find(PaletteConfig).at(4);\n  t.equal(\n    customSwitch.find('.side-panel-panel__label').text(),\n    'Custom Palette',\n    'should render custom switch'\n  );\n\n  customSwitch.find('input').at(0).simulate('change');\n\n  t.ok(\n    updateLayerColorUI.callCount === 4,\n    'updateLayerColorUI should be called when click custom switch'\n  );\n  const expectedArgs4 = [\n    'colorRange',\n    {\n      colorRangeConfig: {\n        custom: true\n      }\n    }\n  ];\n  t.deepEqual(\n    updateLayerColorUI.args[3],\n    expectedArgs4,\n    'should call updateLayerColorUI with custom: true'\n  );\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector -> ColorRangeSelector -> CustomPalette.render', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n\n  // set showDropdown to true\n  const pointLayer = layers\n    .find(l => l.type === 'point')\n    .updateLayerColorUI('colorRange', {showDropdown: 0})\n    .updateLayerColorUI('colorRange', {colorRangeConfig: {custom: true}});\n\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n\n  // open color dropdown\n  t.equal(wrapper.find('.color-range-selector').length, 1, 'should render 1 color-range-selector');\n  const crs = wrapper.find(ColorRangeSelector);\n  t.equal(crs.length, 1, 'should render 1 ColorRangeSelector');\n\n  const pc = crs.find(PaletteConfig);\n  t.equal(pc.length, 1, 'should render 1 PaletteConfig');\n\n  const cp = crs.find(CustomPalette);\n  t.equal(cp.length, 1, 'should render 1 CustomPalette');\n\n  t.equal(\n    cp.find('.custom-palette__sortable-items').length,\n    pointLayer.config.colorUI.colorRange.customPalette.colors.length *\n      STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    'should render same number of custom palette swatch'\n  );\n\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector -> ColorRangeSelector -> CustomPalette.update', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n\n  // set showDropdown to true\n  const pointLayer = layers\n    .find(l => l.type === 'point')\n    .updateLayerColorUI('colorRange', {showDropdown: 0})\n    .updateLayerColorUI('colorRange', {colorRangeConfig: {custom: true}});\n\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n  // current colorRange\n  // { name: 'Ice And Fire 8',\n  // type: 'diverging',\n  // category: 'Uber',\n  // colors:\n  //  [ '#7F1941',\n  //    '#D50255',\n  //    '#FEAD54',\n  //    '#FEEDB1',\n  //    '#E8FEB5',\n  //    '#49E3CE',\n  //    '#0198BD',\n  //    '#007A99' ],\n  // reversed: true }\n  const cp = wrapper.find(CustomPalette);\n  t.equal(cp.length, 1, 'should render 1 CustomPalette');\n  t.equal(cp.find(EditableColorRange).length, 0, 'Should not render EditableColorRange');\n\n  // add step\n  t.equal(cp.find(Button).length, 2, 'should render 2 buttons');\n  t.equal(cp.find(AddColorStop).length, 8, 'Should render 8 add color buttons');\n  t.equal(cp.find(DeleteColorStop).length, 8, 'Should render 8 delete color buttons');\n\n  // click add step\n  cp.find(AddColorStop).at(0).simulate('click');\n\n  t.deepEqual(\n    updateLayerColorUI.args[0],\n    [\n      'colorRange',\n      {\n        customPalette: {\n          name: 'color.customPalette',\n          type: 'custom',\n          category: 'Custom',\n          reversed: true,\n          colors: [\n            '#7F1941',\n            '#AA0E4B',\n            '#D50255',\n            '#FEAD54',\n            '#FEEDB1',\n            '#E8FEB5',\n            '#49E3CE',\n            '#0198BD',\n            '#007A99'\n          ]\n        }\n      }\n    ],\n    'should add color to custom palette when click add step'\n  );\n\n  // click delete step\n  cp.find(DeleteColorStop).at(1).simulate('click');\n\n  t.deepEqual(\n    updateLayerColorUI.args[1],\n    [\n      'colorRange',\n      {\n        customPalette: {\n          name: 'color.customPalette',\n          type: 'custom',\n          category: 'Custom',\n          reversed: true,\n          colors: ['#7F1941', '#FEAD54', '#FEEDB1', '#E8FEB5', '#49E3CE', '#0198BD', '#007A99']\n        }\n      }\n    ],\n    'should delete color to custom palette when click add step'\n  );\n\n  // click cancel\n  cp.find(Button).at(0).simulate('click');\n\n  t.deepEqual(\n    updateLayerColorUI.args[2],\n    [\n      'colorRange',\n      {\n        colorRangeConfig: {custom: false},\n        showSketcher: false\n      }\n    ],\n    'should set custom: false when cancel'\n  );\n\n  // click apply\n  cp.find(Button).at(1).simulate('click');\n\n  t.deepEqual(\n    updateLayerColorUI.args[2],\n    [\n      'colorRange',\n      {\n        colorRangeConfig: {custom: false},\n        showSketcher: false\n      }\n    ],\n    'should set custom to false when click apply'\n  );\n\n  t.deepEqual(\n    updateLayerVisConfig.args[0],\n    [\n      {\n        colorRange: {\n          name: 'color.customPalette',\n          type: 'custom',\n          category: 'Custom',\n          reversed: true,\n          colors: [\n            '#7F1941',\n            '#D50255',\n            '#FEAD54',\n            '#FEEDB1',\n            '#E8FEB5',\n            '#49E3CE',\n            '#0198BD',\n            '#007A99'\n          ]\n        }\n      }\n    ],\n    'should set colorRange to custom'\n  );\n\n  // click swatch\n  wrapper.find('.custom-palette__swatch').at(3).simulate('click');\n\n  t.ok(\n    updateLayerColorUI.calledWith('colorRange', {\n      showSketcher: 3\n    }),\n    'should set showSketcher to swatch index'\n  );\n\n  wrapper.update();\n\n  t.end();\n});\n\ntest('Components -> LayerColorRangeSelector.render -> ColorSelector -> ColorRangeSelector -> CustomPalette -> CustomPicker', t => {\n  const initialState = StateWTrips.visState;\n  const {layers} = initialState;\n\n  // set showSketcher to true\n  const pointLayer = layers\n    .find(l => l.type === 'point')\n    .updateLayerColorUI('colorRange', {showDropdown: 0})\n    .updateLayerColorUI('colorRange', {colorRangeConfig: {custom: true}})\n    .updateLayerColorUI('colorRange', {showSketcher: 2});\n\n  const updateLayerConfig = sinon.spy();\n  const updateLayerVisConfig = sinon.spy();\n  const updateLayerColorUI = sinon.spy();\n\n  const mockProps = {\n    layer: pointLayer,\n    datasets: initialState.datasets,\n    updateLayerConfig,\n    updateLayerVisConfig,\n    updateLayerColorUI\n  };\n\n  const visConfiguratorProps = getVisConfiguratorProps(mockProps);\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerColorRangeSelector {...visConfiguratorProps} />\n      </IntlWrapper>\n    );\n  }, 'Should not fail render color range select');\n\n  const cp = wrapper.find(CustomPalette);\n  t.equal(cp.length, 1, 'should render 1 CustomPalette');\n\n  const picker = cp.find(CustomPicker);\n  t.equal(picker.length, 1, 'should render 1 CustomPicker');\n\n  // SecurityError (e.g. cross-origin error) should be handled in Portaled component\n  t.doesNotThrow(() => {\n    picker.simulateError({name: 'SecurityError', message: '', stack: []});\n    t.equal(wrapper.find(Portaled).length, 1);\n  }, 'Should not fail with SecurityError when close CustomPicker');\n\n  t.end();\n});\n\ntest('Components -> ColorSelector.opacity', t => {\n  const setColor = sinon.spy();\n\n  const colorSelectorProps = {\n    colorSets: [\n      {\n        selectedColor: [128, 64, 32, 100],\n        setColor\n      }\n    ],\n    useOpacity: true\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <ColorSelector {...colorSelectorProps} />\n      </IntlWrapper>\n    );\n  }, 'Mount should not fail');\n\n  // show palette\n  wrapper.find('.color-selector__selector').at(0).invoke('onClick')({\n    preventDefault() {},\n    stopPropagation() {}\n  });\n\n  // select color\n  wrapper.find('.single-color-palette__block').at(67).simulate('click');\n  t.ok(setColor.calledOnce, 'should call setColor once');\n  t.ok(setColor.calledWith([228, 155, 0, 100]), 'setColor called with correct color and opacity');\n\n  // change opacity via slider\n  wrapper.find('RangeSlider').at(0).invoke('onChange')([0.5, 0.5]);\n  t.ok(setColor.calledTwice, 'should call setColor twice');\n  t.ok(setColor.calledWith([128, 64, 32, 128]), 'setColor called with correct color and opacity');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/filter-manager-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {\n  FilterManagerFactory,\n  SourceDataCatalogFactory,\n  FilterPanelFactory,\n  FieldSelectorFactory,\n  Button,\n  FilterPanelHeaderFactory,\n  NewFilterPanelFactory,\n  appInjector,\n  AddFilterButtonFactory\n} from '@kepler.gl/components';\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\nimport {keplerGlReducerCore as keplerGlReducer} from '@kepler.gl/reducers';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {testFields, testAllData} from 'test/fixtures/test-csv-data';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {assertDatasetIsTable} from '../../../helpers/comparison-utils';\n\n// components\nconst FilterManager = appInjector.get(FilterManagerFactory);\nconst SourceDataCatalog = appInjector.get(SourceDataCatalogFactory);\nconst FilterPanel = appInjector.get(FilterPanelFactory);\nconst NewFilterPanel = appInjector.get(NewFilterPanelFactory);\nconst FilterPanelHeader = appInjector.get(FilterPanelHeaderFactory);\nconst FieldSelector = appInjector.get(FieldSelectorFactory);\nconst AddFilterButton = appInjector.get(AddFilterButtonFactory);\n\n// mock state\nimport {StateWFilters, InitialState, applyActions, csvInfo} from 'test/helpers/mock-state';\nimport {clickItemSelectList} from '../../../helpers/component-utils';\n\nconst nop = () => {};\n// default props from initial state\nconst defaultProps = {\n  filters: [],\n  datasets: {},\n  layers: [],\n  showDatasetTable: nop,\n  updateTableColor: nop,\n  removeDataset: nop,\n  showAddDataModal: nop,\n  panelMetadata: {\n    label: 'sidebar.panels.filter'\n  },\n  panelListView: 'list',\n  visStateActions: {\n    addFilter: nop,\n    setFilter: nop,\n    removeFilter: nop,\n    enlargeFilter: nop,\n    toggleAnimation: nop,\n    toggleFilterFeature: nop\n  },\n  uiStateActions: {\n    togglePanelListView: nop\n  }\n};\n\n// components\nconst filterManagerProps = {\n  ...defaultProps,\n  filters: StateWFilters.visState.filters,\n  datasets: StateWFilters.visState.datasets,\n  layers: StateWFilters.visState.layers\n};\n\ntest('Components -> FilterManager.mount -> no prop', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FilterManager {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'FilterManager should not fail without props');\n\n  t.ok(wrapper.find('.filter-manager').length === 1, 'should render Filter Manager');\n  t.ok(wrapper.find(SourceDataCatalog).length === 1, 'should render SourceDataCatalog');\n  t.equal(wrapper.find(AddFilterButton).length, 1, 'should render add filter button');\n\n  t.end();\n});\n\ntest('Components -> FilterManager.mount -> with prop', t => {\n  // mount\n  const addFilter = sinon.spy();\n  const newProps = {\n    ...filterManagerProps,\n    visStateActions: {\n      ...filterManagerProps.visStateActions,\n      addFilter\n    }\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FilterManager {...newProps} />\n      </IntlWrapper>\n    );\n  }, 'FilterManager should not fail w/ props');\n\n  t.ok(wrapper.find('.filter-manager').length === 1, 'should render Filter Manager');\n  t.ok(wrapper.find(SourceDataCatalog).length === 1, 'should render SourceDataCatalog');\n  t.equal(wrapper.find(Button).length, 2, 'should render 2 buttons');\n  t.ok(wrapper.find(FilterPanel).length === 2, 'should render 2 FilterPanel');\n\n  // stateless component don't have a instance()\n  const filter1Props = wrapper.find(FilterPanel).at(0).props();\n\n  t.equal(filter1Props.filter, filterManagerProps.filters[1], 'should render last filter first');\n  t.equal(filter1Props.isAnyFilterAnimating, false, 'isAnyFilterAnimating is false');\n\n  t.equal(wrapper.find('.list__item').length, 2, 'should render 2 options');\n  clickItemSelectList(wrapper, 1);\n\n  t.deepEqual(\n    addFilter.args,\n    [[Object.keys(StateWFilters.visState.datasets)[1]]],\n    'Should call addFilter with 1st dataset'\n  );\n\n  t.end();\n});\n\ntest('Components -> FilterManager.mount -> order by dataset view', t => {\n  const newProps = {\n    ...filterManagerProps,\n    panelListView: 'byDataset'\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FilterManager {...newProps} />\n      </IntlWrapper>\n    );\n  }, 'FilterManager should not fail w/ props');\n\n  t.equal(wrapper.find('DatasetFilterSection').length, 2, 'should render 2 DatasetFilterSection');\n\n  t.end();\n});\n\ntest('Components -> FilterManager.mount -> with supportedFilterTypes', t => {\n  // load csv and geojson\n  const updatedState = applyActions(keplerGlReducer, InitialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            info: csvInfo,\n            data: {fields: testFields, rows: testAllData},\n            supportedFilterTypes: [ALL_FIELD_TYPES.real, ALL_FIELD_TYPES.integer]\n          }\n        ]\n      ]\n    }\n  ]);\n\n  const {visState} = updatedState;\n  // test dataset is table\n  assertDatasetIsTable(t, visState.datasets[csvInfo.id]);\n  t.deepEqual(\n    visState.datasets[csvInfo.id].supportedFilterTypes,\n    [ALL_FIELD_TYPES.real, ALL_FIELD_TYPES.integer],\n    'supportedFilterTypes should be correct'\n  );\n\n  // call addFilter\n  const stateWithEmptyFilter = keplerGlReducer(updatedState, VisStateActions.addFilter(csvInfo.id));\n  // mount filter panel\n  // components\n  const props = {\n    filters: stateWithEmptyFilter.visState.filters,\n    datasets: stateWithEmptyFilter.visState.datasets,\n    layers: stateWithEmptyFilter.visState.layers,\n    showDatasetTable: nop,\n    panelMetadata: {\n      label: 'sidebar.panels.filter'\n    },\n    visStateActions: {\n      addFilter: nop,\n      setFilter: nop,\n      removeFilter: nop,\n      enlargeFilter: nop,\n      toggleAnimation: nop,\n      toggleFilterFeature: nop\n    },\n    uiStateActions: {\n      togglePanelListView: nop\n    }\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <FilterManager {...props} />\n      </IntlWrapper>\n    );\n  }, 'FilterManager should not fail when mount with dataset.supportedFilterTypes');\n\n  t.ok(wrapper.find(FilterPanel).length === 1, 'should render 1 FilterPanel');\n  t.ok(wrapper.find(NewFilterPanel).length === 1, 'should render 1 NewFilterPanel');\n  t.ok(wrapper.find(FilterPanelHeader).length === 1, 'should render 1 FilterPanelHeader');\n  t.ok(wrapper.find(FieldSelector).length === 1, 'should render FieldSelector');\n\n  // check field options\n  const fieldOptions = wrapper.find(FieldSelector).at(0).props().fields;\n\n  t.deepEqual(\n    fieldOptions.map(f => f.name),\n    ['gps_data.lat', 'gps_data.lng', 'uid'],\n    'should only pass real / integer fields'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './channel-by-value-selctor-test';\nimport './color-selector-test';\nimport './filter-manager-test';\nimport './layer-configurator-test';\nimport './layer-manager-test';\nimport './layer-panel-header-test';\nimport './save-export-dropdown-test';\nimport './side-panel-test';\n"
  },
  {
    "path": "test/browser/components/side-panel/layer-configurator-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\n\nimport {\n  LayerConfiguratorFactory,\n  LayerColumnModeConfigFactory,\n  LayerColumnConfigFactory,\n  LayerConfigGroupFactory,\n  FieldSelectorFactory,\n  ColumnSelectorFactory,\n  appInjector,\n  dropdownListClassList,\n  Checkbox,\n  getLayerFields,\n  getLayerDataset\n} from '@kepler.gl/components';\n\nimport {StateWFiles, StateWTripGeojson, testCsvDataId} from 'test/helpers/mock-state';\nimport {\n  IntlWrapper,\n  mountWithTheme,\n  clickItemSelector,\n  getItemSelectorListText,\n  clickItemSelectList\n} from 'test/helpers/component-utils';\nimport {act} from 'react-dom/test-utils';\nimport {STYLED_COMPONENTS_DUPLICATED_ENTRIES} from '../../../helpers/utils';\n\n// components\nconst LayerConfigurator = appInjector.get(LayerConfiguratorFactory);\nconst LayerColumnModeConfig = appInjector.get(LayerColumnModeConfigFactory);\nconst LayerColumnConfig = appInjector.get(LayerColumnConfigFactory);\nconst LayerConfigGroup = appInjector.get(LayerConfigGroupFactory);\nconst ColumnSelector = appInjector.get(ColumnSelectorFactory);\nconst FieldSelector = appInjector.get(FieldSelectorFactory);\n\n// components\nconst openModal = () => {};\nconst updateLayerColorUI = () => {};\nconst updateLayerConfig = () => {};\nconst updateLayerVisualChannelConfig = () => {};\nconst updateLayerType = () => {};\nconst updateLayerTextLabel = () => {};\nconst updateLayerVisConfig = () => {};\n\nconst defaultProps = {\n  layer: StateWFiles.visState.layers[0],\n  datasets: StateWFiles.visState.datasets,\n  layerTypeOptions: [],\n  openModal,\n  updateLayerColorUI,\n  updateLayerConfig,\n  updateLayerVisualChannelConfig,\n  updateLayerType,\n  updateLayerTextLabel,\n  updateLayerVisConfig\n};\n\ntest('Components -> LayerConfigurator.mount -> default prop 1', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerConfigurator {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'LayerConfigurator should not fail without props');\n\n  const component = wrapper.find(LayerConfigurator).instance();\n\n  const spy = sinon.spy(component, '_renderScatterplotLayerConfig');\n  act(() => {\n    component.forceUpdate();\n  });\n\n  wrapper.update();\n  t.ok(spy.calledOnce, 'should call _renderScatterplotLayerConfig');\n\n  const expectedDataset = StateWFiles.visState.datasets[testCsvDataId];\n  const expectedLayer = StateWFiles.visState.layers[0];\n\n  const expectedArgs = {\n    layer: expectedLayer,\n    dataset: expectedDataset,\n    visConfiguratorProps: {\n      layer: expectedLayer,\n      fields: expectedDataset.fields,\n      onChange: updateLayerVisConfig,\n      setColorUI: updateLayerColorUI\n    },\n    layerConfiguratorProps: {\n      layer: expectedLayer,\n      fields: expectedDataset.fields,\n      onChange: updateLayerConfig,\n      setColorUI: updateLayerColorUI\n    },\n    layerChannelConfigProps: {\n      layer: expectedLayer,\n      dataset: expectedDataset,\n      fields: expectedDataset.fields,\n      onChange: updateLayerVisualChannelConfig,\n      setColorUI: updateLayerColorUI\n    }\n  };\n\n  const args = spy.args[0][0];\n\n  t.deepEqual(\n    Object.keys(args).sort(),\n    Object.keys(expectedArgs).sort(),\n    'render layer method should receive 5 arguments'\n  );\n\n  t.equal(args.layer, expectedArgs.layer, 'render layer method should receive corrent layer arg');\n  t.equal(\n    args.dataset,\n    expectedArgs.dataset,\n    'render layer method should receive corrent dataset arg'\n  );\n  t.deepEqual(\n    args.visConfiguratorProps,\n    expectedArgs.visConfiguratorProps,\n    'render layer method should receive corrent visConfiguratorProps arg'\n  );\n  t.deepEqual(\n    args.layerConfiguratorProps,\n    expectedArgs.layerConfiguratorProps,\n    'render layer method should receive corrent layerConfiguratorProps arg'\n  );\n  t.deepEqual(\n    args.layerChannelConfigProps,\n    expectedArgs.layerChannelConfigProps,\n    'render layer method should receive correct layerChannelConfigProps arg'\n  );\n\n  t.end();\n});\n\ntest('Components -> LayerConfigurator.mount -> LayerColumnConfig', t => {\n  // mount\n  const updateLayerConfigSpy = sinon.spy();\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerConfigurator {...defaultProps} updateLayerConfig={updateLayerConfigSpy} />\n      </IntlWrapper>\n    );\n  }, 'LayerConfigurator should not fail without props');\n\n  const baseConfigGroup = wrapper.find(LayerConfigGroup).at(0);\n\n  t.equal(\n    baseConfigGroup.find(LayerColumnModeConfig).length,\n    1,\n    'should render 1 LayerColumnModeConfig'\n  );\n  t.equal(baseConfigGroup.find(LayerColumnConfig).length, 3, 'should render 2 LayerColumnConfig');\n\n  t.equal(\n    baseConfigGroup.find(LayerColumnConfig).at(0).find(ColumnSelector).length,\n    4,\n    'Should render 4 ColumnSelector for Point columns'\n  );\n\n  t.equal(\n    baseConfigGroup.find(LayerColumnConfig).at(1).find(ColumnSelector).length,\n    1,\n    'Should render 1 ColumnSelector for GeoJSON feature'\n  );\n\n  // open fieldSelector\n  // t.equal(\n  const fieldSelector = baseConfigGroup\n    .find(LayerColumnConfig)\n    .at(0)\n    .find(ColumnSelector)\n    .at(0)\n    .find(FieldSelector)\n    .at(0);\n\n  // open dropdown\n  clickItemSelector(fieldSelector);\n  const fieldSelector2 = wrapper\n    .find(LayerColumnConfig)\n    .at(0)\n    .find(ColumnSelector)\n    .at(0)\n    .find(FieldSelector)\n    .at(0);\n\n  t.equal(\n    fieldSelector2.find(`.list__item.${dropdownListClassList.listItemFixed}`).length,\n    1,\n    'should render 1 fixed item'\n  );\n\n  t.equal(\n    getItemSelectorListText(fieldSelector2, 0),\n    'gps_data',\n    'should render correct field pair name'\n  );\n\n  // click list item suggested field pair\n  clickItemSelectList(fieldSelector2, 0);\n\n  t.ok(updateLayerConfigSpy.calledOnce, 'should call updateLayerConfigSpy');\n  t.deepEqual(\n    updateLayerConfigSpy.args[0],\n    [\n      {\n        columns: {\n          lat: {\n            value: 'gps_data.lat',\n            fieldIdx: 1\n          },\n          lng: {\n            value: 'gps_data.lng',\n            fieldIdx: 2\n          },\n          altitude: {value: null, fieldIdx: -1, optional: true},\n          neighbors: {value: null, fieldIdx: -1, optional: true},\n          geojson: {value: null, fieldIdx: -1},\n          geoarrow: {value: null, fieldIdx: -1}\n        }\n      }\n    ],\n    'should update field pairs'\n  );\n  t.equal(\n    getItemSelectorListText(fieldSelector2, 2),\n    'gps_data.lng',\n    'should render correct field pair name'\n  );\n\n  // TODO: still need to fix this one\n  // for some reason the update config callback is only called once\n  // click single column\n  // clickItemSelectList(fieldSelector2, 2);\n\n  // t.ok(updateLayerConfigSpy.calledTwice, 'should call updateLayerConfigSpy twice');\n  // t.deepEqual(\n  //   updateLayerConfigSpy.args[1],\n  // [\n  //    {\n  //      columns: {\n  //        lat: {\n  //          value: 'gps_data.lng',\n  //          fieldIdx: 2\n  //        },\n  //        lng: {\n  //          value: 'gps_data.lng',\n  //          fieldIdx: 2\n  //        },\n  //        altitude: {value: null, fieldIdx: -1, optional: true},\n  //        neighbors: {value: null, fieldIdx: -1, optional: true},\n  //        geojson: {value: null, fieldIdx: -1}\n  //      }\n  //    }\n  //  ],\n  //  'should update single column'\n  // );\n  t.end();\n});\n\ntest('Components -> LayerConfigurator.mount -> collapsed / expand config group ', t => {\n  const propsWithTripLayer = {\n    ...defaultProps,\n    layer: StateWTripGeojson.visState.layers[0],\n    datasets: StateWTripGeojson.visState.datasets\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerConfigurator {...propsWithTripLayer} />\n      </IntlWrapper>\n    );\n  }, 'LayerConfigurator should not fail without props');\n\n  const component = wrapper.find(LayerConfigurator).instance();\n  t.equal(\n    wrapper.find(LayerConfigGroup).at(0).find('.layer-config-group.collapsed').length,\n    STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n    'LayerColumnModeConfig should be collapsed'\n  );\n\n  const spy = sinon.spy(component, '_renderScatterplotLayerConfig');\n  const spy2 = sinon.spy(component, '_renderTripLayerConfig');\n\n  act(() => {\n    component.forceUpdate();\n  });\n\n  wrapper.update();\n\n  t.ok(spy.notCalled, 'should not call _renderScatterplotLayerConfig');\n  t.ok(spy2.calledOnce, 'should call _renderTripLayerConfig');\n\n  // click layer config group header\n  wrapper.find('.layer-config-group__header').at(0).simulate('click');\n\n  t.equal(\n    wrapper.find(LayerConfigGroup).at(0).find('.layer-config-group.collapsed').length,\n    0,\n    'LayerColumnModeConfig should be expanded'\n  );\n\n  t.end();\n});\n\ntest('Components -> LayerConfigurator.mount -> LayerColumnModeConfig ', t => {\n  const updateLayerConfigSpy = sinon.spy();\n\n  const propsWithTripLayer = {\n    ...defaultProps,\n    updateLayerConfig: updateLayerConfigSpy,\n    layer: StateWTripGeojson.visState.layers[0],\n    datasets: StateWTripGeojson.visState.datasets\n  };\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerConfigurator {...propsWithTripLayer} />\n      </IntlWrapper>\n    );\n  }, 'LayerConfigurator should not fail without props');\n  const baseConfigGroup = wrapper.find(LayerConfigGroup).at(0);\n  t.equal(\n    baseConfigGroup.find(LayerColumnModeConfig).length,\n    1,\n    'should render 1 LayerColumnModeConfig'\n  );\n  t.equal(baseConfigGroup.find(LayerColumnConfig).length, 2, 'should render 2 LayerColumnConfig');\n\n  // 1 columne mode panel\n  const checkbox = baseConfigGroup\n    .find(LayerColumnModeConfig)\n    .find('.layer-column-mode-panel')\n    .at(0)\n    .find(Checkbox);\n\n  t.equal(checkbox.props().label, 'GeoJSON', 'should render correct checkbox prop');\n  t.equal(checkbox.props().checked, true, 'should render correct checkbox prop');\n\n  // check the other selection\n  const checkbox2 = baseConfigGroup\n    .find(LayerColumnModeConfig)\n    .find('.layer-column-mode-panel')\n    .at(1)\n    .find(Checkbox);\n\n  checkbox2.find('input').at(0).simulate('change', {target: {}});\n  t.ok(updateLayerConfigSpy.calledOnce, 'updateLayerConfig called');\n\n  t.deepEqual(\n    updateLayerConfigSpy.args[0],\n    [\n      {\n        columnMode: 'table'\n      }\n    ],\n    'should update columnMode'\n  );\n\n  t.end();\n});\n\ntest('Components -> LayerConfigurator -> getLayerFields', t => {\n  const layer = StateWTripGeojson.visState.layers[0];\n  const datasets = StateWTripGeojson.visState.datasets;\n\n  const fields = getLayerFields(datasets, layer);\n\n  t.equal(fields.length, 5, 'should get 4 fields');\n\n  const expectedFields = datasets.trip_data.fields;\n  t.deepEqual(fields, expectedFields, 'should get 4 fields from the first layer');\n\n  t.end();\n});\n\ntest('Components -> LayerConfigurator -> getLayerDataset', t => {\n  const layer = StateWTripGeojson.visState.layers[0];\n  const datasets = StateWTripGeojson.visState.datasets;\n\n  const ds = getLayerDataset(datasets, layer);\n\n  t.equal(ds.id, 'trip_data', 'should get 1 dataset: trip_data');\n\n  const expectedDS = datasets.trip_data;\n  t.deepEqual(ds, expectedDS, 'should get 1 dataset for the input layer');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/layer-list.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {fireEvent, screen} from '@testing-library/react';\nimport {dataTestIds} from '@kepler.gl/constants';\nimport {appInjector, LayerListFactory} from '@kepler.gl/components';\nimport {\n  VisStateActions,\n  UIStateActions,\n  MapStateActions,\n  addDataToMap,\n  keplerGlInit\n} from '@kepler.gl/actions';\nimport {processCsvData} from '@kepler.gl/processors';\nimport {keplerGlReducerCore as keplerGlReducer} from '@kepler.gl/reducers';\n\nimport {renderWithTheme} from '../../../helpers/component-jest-utils';\nimport testLayerData from '../../../fixtures/test-layer-data';\nimport {dataId as csvDataId} from '../../../fixtures/test-csv-data';\nimport {applyActions} from '../../../helpers/mock-state-utils';\n\n// TODO: need to be deleted and imported from raw-states\nconst InitialState = keplerGlReducer(undefined, keplerGlInit({}));\n\nfunction mockStateWithMultipleH3Layers() {\n  const initialState = cloneDeep(InitialState);\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {\n            info: {id: csvDataId},\n            data: processCsvData(testLayerData)\n          },\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [\n                  {\n                    id: 'h3-layer-1',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 1',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  },\n                  {\n                    id: 'h3-layer-2',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 2',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n  return prepareState;\n}\n\nconst LayerList = appInjector.get(LayerListFactory);\n\nconst StateWMultiH3Layers = mockStateWithMultipleH3Layers();\n\nconst defaultProps = {\n  datasets: StateWMultiH3Layers.visState.datasets,\n  layerClasses: StateWMultiH3Layers.visState.layerClasses,\n  layerOrder: StateWMultiH3Layers.visState.layerOrder,\n  layers: StateWMultiH3Layers.visState.layers,\n  uiStateActions: UIStateActions,\n  visStateActions: VisStateActions,\n  mapStateActions: MapStateActions\n};\n\n// jest.mock('@kepler.gl/actions');\n\ndescribe('Components -> SidePanel -> LayerPanel -> LayerList', () => {\n  it('render sortable list', () => {\n    renderWithTheme(<LayerList {...defaultProps} isSortable />);\n\n    expect(screen.getAllByTestId(dataTestIds.sortableLayerItem)).toHaveLength(\n      defaultProps.layers.length\n    );\n    screen\n      .getAllByTestId(dataTestIds.layerTitleEditor)\n      .forEach((item, index) => expect(item.value).toBe(defaultProps.layers[index].config.label));\n    expect(screen.getAllByTestId(dataTestIds.layerPanel)).toHaveLength(defaultProps.layers.length);\n  });\n\n  it('render non-sortable list', () => {\n    renderWithTheme(<LayerList {...defaultProps} isSortable={false} />);\n\n    // no sortable items\n    expect(screen.queryByTestId(dataTestIds.sortableLayerItem)).not.toBeInTheDocument();\n\n    // only static ones\n    expect(screen.getAllByTestId(dataTestIds.staticLayerItem)).toHaveLength(\n      defaultProps.layers.length\n    );\n    screen\n      .getAllByTestId(dataTestIds.layerTitleEditor)\n      .forEach((item, index) => expect(item.value).toBe(defaultProps.layers[index].config.label));\n    expect(screen.getAllByTestId(dataTestIds.layerPanel)).toHaveLength(defaultProps.layers.length);\n  });\n\n  it('pass null entries as layers', () => {\n    const layers = defaultProps.layers;\n    layers[0] = null;\n    const removeLayer = jest.fn();\n    renderWithTheme(\n      <LayerList\n        {...defaultProps}\n        isSortable={false}\n        layers={layers}\n        visStateActions={{\n          ...defaultProps.visStateActions,\n          removeLayer\n        }}\n      />\n    );\n\n    expect(screen.getAllByTestId(dataTestIds.layerPanel)).toHaveLength(\n      defaultProps.layers.length - 1\n    );\n    expect(screen.getByTestId(dataTestIds.removeLayerAction)).toBeInTheDocument();\n    const removeLayerButton = screen.getByTestId(dataTestIds.removeLayerAction);\n    fireEvent.click(removeLayerButton);\n\n    expect(removeLayer).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/layer-manager-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\n\nimport {\n  LayerManagerFactory,\n  LayerListFactory,\n  DatasetLayerGroupFactory,\n  DatasetSectionFactory,\n  PanelViewListToggleFactory,\n  PanelTitleFactory,\n  AddLayerButtonFactory,\n  appInjector,\n  Layers\n} from '@kepler.gl/components';\n\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\n\nimport {VisStateActions, UIStateActions, MapStateActions} from '@kepler.gl/actions';\n\nimport {StateWMultiH3Layers} from 'test/helpers/mock-state';\n\nconst LayerManager = appInjector.get(LayerManagerFactory);\nconst LayerList = appInjector.get(LayerListFactory);\nconst DatasetLayerGroup = appInjector.get(DatasetLayerGroupFactory);\nconst DatasetSection = appInjector.get(DatasetSectionFactory);\nconst PanelViewListToggle = appInjector.get(PanelViewListToggleFactory);\nconst PanelTitle = appInjector.get(PanelTitleFactory);\nconst AddLayerButton = appInjector.get(AddLayerButtonFactory);\n\nconst nop = () => {};\n\nconst defaultProps = {\n  datasets: StateWMultiH3Layers.visState.datasets,\n  layers: StateWMultiH3Layers.visState.layers,\n  layerOrder: StateWMultiH3Layers.visState.layerOrder,\n  layerClasses: StateWMultiH3Layers.visState.layerClasses,\n  intl: {},\n  showAddDataModal: nop,\n  updateTableColor: nop,\n  showDatasetTable: nop,\n  removeDataset: nop,\n  panelMetadata: {\n    id: 'layer',\n    label: 'sidebar.panels.layer',\n    iconComponent: Layers,\n    onClick: nop,\n    component: LayerManager\n  },\n  layerPanelListView: 'list',\n  uiStateActions: UIStateActions,\n  visStateActions: VisStateActions,\n  mapStateActions: MapStateActions,\n  layerBlending: 'normal',\n  overlayBlending: 'normal'\n};\n\ntest('Components -> LayerManager -> render -> list view', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerManager {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'LayerManager should not fail');\n\n  t.ok(wrapper.find(DatasetLayerGroup).length === 0, 'should not render DatasetLayerGroup');\n  t.ok(wrapper.find(LayerList).length === 1, 'should render LayerList');\n  t.ok(wrapper.find(AddLayerButton).length === 1, 'should render AddLayerButton');\n  t.ok(wrapper.find(DatasetSection).length === 1, 'should render DatasetSection');\n  t.ok(wrapper.find(PanelViewListToggle).length === 1, 'should render PanelViewListToggle');\n  t.ok(wrapper.find(PanelTitle).length === 1, 'should render PanelTitle');\n\n  const expectedTitles = ['H3 Hexagon 1', 'H3 Hexagon 1', 'H3 Hexagon 2', 'H3 Hexagon 2'];\n\n  const titles = wrapper.find('.layer__title__editor').map(item => item.getDOMNode().value);\n  t.deepEqual(titles, expectedTitles, 'should render panels in correct order');\n\n  const layers = wrapper.find('.layer-panel');\n  t.equal(layers.length, 4, 'should render 4 layer panels');\n\n  t.end();\n});\n\ntest('Components -> LayerManager -> render -> order by dataset view', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerManager {...defaultProps} panelListView=\"byDataset\" />\n      </IntlWrapper>\n    );\n  }, 'LayerManager should not fail');\n\n  t.ok(wrapper.find(DatasetLayerGroup).length === 1, 'should render DatasetLayerGroup');\n  t.ok(wrapper.find(LayerList).length === 1, 'should render LayerList');\n  t.ok(wrapper.find(AddLayerButton).length === 1, 'should render AddLayerButton');\n  t.ok(wrapper.find(DatasetSection).length === 1, 'should render DatasetSection');\n  t.ok(wrapper.find(PanelViewListToggle).length === 1, 'should render PanelViewListToggle');\n  t.ok(wrapper.find(PanelTitle).length === 1, 'should render PanelTitle');\n\n  //  we have two layers by styled-component will render two elements for each component with the same className\n  const expectedTitles = ['H3 Hexagon 1', 'H3 Hexagon 1', 'H3 Hexagon 2', 'H3 Hexagon 2'];\n  const titles = wrapper.find('.layer__title__editor').map(item => item.getDOMNode().value);\n  t.equal(titles.length, 4, 'Should display two tiles');\n  t.deepEqual(titles, expectedTitles, 'should render panels in correct order');\n\n  const layers = wrapper.find('.layer-panel');\n  t.equal(layers.length, 4, 'should render 6 layer panels');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/layer-panel-header-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\n\nimport React from 'react';\nimport test from 'tape';\n\nimport {LayerPanelHeaderFactory, DragHandle, appInjector} from '@kepler.gl/components';\nimport {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils';\n\n// components\nconst nop = () => {};\nconst defaultProps = {\n  layerId: 'taro',\n  isVisible: true,\n  isValid: true,\n  isConfigActive: false,\n  onToggleVisibility: nop,\n  onUpdateLayerLabel: nop,\n  onToggleEnableConfig: nop,\n  onRemoveLayer: nop,\n  onZoomToLayer: nop\n};\n\ntest('Components -> LayerPanelHeader.mount -> no prop', t => {\n  const LayerPanelHeader = appInjector.get(LayerPanelHeaderFactory);\n\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerPanelHeader {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'LayerPanelHeader should not fail without props');\n\n  t.ok(wrapper.find('.layer-panel__header').length, 'should render layer-panel__header');\n  t.ok(wrapper.find(DragHandle).length, 'should render drag handle');\n  t.ok(wrapper.find('.layer__title__editor').length, 'should render title eidtor');\n  t.ok(wrapper.find('.layer__visibility-toggle').length, 'should render visibility toggle');\n  t.ok(wrapper.find('.layer__enable-config').length, 'should render enable config toggle');\n\n  // mount\n  const layerAfterErrorProps = {...defaultProps, ...{isValid: false}};\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <LayerPanelHeader {...layerAfterErrorProps} />\n      </IntlWrapper>\n    );\n  }, 'LayerPanelHeader should not fail without props');\n\n  t.ok(!wrapper.find('.layer__visibility-toggle').length, \"shouldn't render visibility toggle\");\n  t.ok(wrapper.find('.layer__is-valid-refresh').length, 'should render validity refresh icon');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/save-export-dropdown-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount,enzyme-deprecation/no-shallow */\nimport React from 'react';\nimport test from 'tape';\nimport {mount} from 'enzyme';\nimport sinon from 'sinon';\nimport {\n  SaveExportDropdownFactory,\n  PanelHeaderDropdownFactory,\n  ToolbarItem\n} from '@kepler.gl/components';\nimport {IntlWrapper} from 'test/helpers/component-utils';\n\ntest('SaveExportDropdown', t => {\n  const PanelHeaderDropdown = PanelHeaderDropdownFactory();\n  const SaveExportDropdown = SaveExportDropdownFactory(PanelHeaderDropdown);\n\n  const onExportImage = sinon.spy();\n  const onExportData = sinon.spy();\n  const onExportConfig = sinon.spy();\n  const onExportMap = sinon.spy();\n  const onSaveMap = sinon.spy();\n  const onClose = sinon.spy();\n\n  const wrapper = mount(\n    <IntlWrapper>\n      <SaveExportDropdown\n        onExportImage={onExportImage}\n        onExportData={onExportData}\n        onExportConfig={onExportConfig}\n        onExportMap={onExportMap}\n        onSaveMap={onSaveMap}\n        show={true}\n        onClose={onClose}\n      />\n    </IntlWrapper>\n  );\n  t.equal(wrapper.find(PanelHeaderDropdown).length, 1, 'We should display 1 PanelHeaderDropdown');\n  t.equal(wrapper.find(ToolbarItem).length, 4, 'We should display 4 ToolbarItems');\n\n  wrapper.find('.toolbar-item').at(0).simulate('click');\n  t.equal(onExportImage.called, true, 'Should have called export image callback');\n\n  wrapper.find('.toolbar-item').at(1).simulate('click');\n  t.equal(onExportData.called, true, 'Should have called export data callback');\n\n  wrapper.find('.toolbar-item').at(2).simulate('click');\n  t.equal(onExportMap.called, true, 'Should have called export map callback');\n\n  wrapper.find('.toolbar-item').at(3).simulate('click');\n  t.equal(onSaveMap.called, true, 'Should have called save map callback');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/side-panel/side-panel-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {\n  SidePanelFactory,\n  SidebarFactory,\n  CollapseButtonFactory,\n  PanelHeaderFactory,\n  SaveExportDropdownFactory,\n  LayerManagerFactory,\n  FilterManagerFactory,\n  InteractionManagerFactory,\n  MapManagerFactory,\n  PanelToggleFactory,\n  CustomPanelsFactory,\n  ToolbarItem,\n  appInjector\n} from '@kepler.gl/components';\n\nimport {\n  VisStateActions,\n  MapStyleActions,\n  UIStateActions,\n  MapStateActions\n} from '@kepler.gl/actions';\n\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\n// components\nconst SidePanel = appInjector.get(SidePanelFactory);\nconst Sidebar = appInjector.get(SidebarFactory);\nconst SidebarCloseButton = appInjector.get(CollapseButtonFactory);\nconst PanelHeader = appInjector.get(PanelHeaderFactory);\nconst LayerManager = appInjector.get(LayerManagerFactory);\nconst FilterManager = appInjector.get(FilterManagerFactory);\nconst InteractionManager = appInjector.get(InteractionManagerFactory);\nconst MapManager = appInjector.get(MapManagerFactory);\nconst PanelToggle = appInjector.get(PanelToggleFactory);\nconst SaveExportDropdown = appInjector.get(SaveExportDropdownFactory);\n\n// mock state\nimport {InitialState} from 'test/helpers/mock-state';\n\n// Constants\nimport {EXPORT_DATA_ID, EXPORT_MAP_ID, EXPORT_IMAGE_ID} from '@kepler.gl/constants';\n\n// default props from initial state\nconst defaultProps = {\n  datasets: InitialState.visState.datasets,\n  filters: InitialState.visState.filters,\n  layerBlending: InitialState.visState.layerBlending,\n  overlayBlending: InitialState.visState.overlayBlending,\n  layerClasses: InitialState.visState.layerClasses,\n  layerOrder: InitialState.visState.layerOrder,\n  interactionConfig: InitialState.visState.interactionConfig,\n  layers: InitialState.visState.layers,\n  mapStyle: InitialState.mapStyle,\n  uiState: InitialState.uiState,\n  width: 300,\n  uiStateActions: UIStateActions,\n  visStateActions: VisStateActions,\n  mapStateActions: MapStateActions,\n  mapStyleActions: MapStyleActions,\n  availableProviders: {}\n};\n\ntest('Components -> SidePanel.mount -> no prop', t => {\n  // mount\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail without props');\n\n  t.ok(wrapper.find(PanelHeader).length === 1, 'should render PanelHeader');\n  t.ok(wrapper.find(PanelToggle).length === 1, 'should render PanelToggle');\n  t.ok(wrapper.find(Sidebar).length === 1, 'should render Sidebar');\n\n  // side bar close button\n  t.ok(wrapper.find(SidebarCloseButton).length === 1, 'should render SideBarCollapseButton');\n\n  t.end();\n});\n\ntest('Components -> SidePanel.mount -> hide CollapseButton', t => {\n  // mount\n  let wrapper;\n\n  const uiState = {\n    ...defaultProps.uiState,\n    isSidePanelCloseButtonVisible: false\n  };\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail without props');\n\n  t.ok(wrapper.find(PanelHeader).length === 1, 'should render PanelHeader');\n  t.ok(wrapper.find(PanelToggle).length === 1, 'should render PanelToggle');\n  t.ok(wrapper.find(Sidebar).length === 1, 'should render Sidebar');\n\n  // side bar close button\n  t.ok(wrapper.find(SidebarCloseButton).length === 0, 'should not render SideBarCollapseButton');\n\n  t.end();\n});\n\ntest('Components -> SidePanel -> toggle panel', t => {\n  const toggleSidePanel = sinon.spy();\n  const uiStateActions = {\n    ...UIStateActions,\n    toggleSidePanel\n  };\n\n  let wrapper;\n  // mount\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiStateActions={uiStateActions} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.ok(wrapper.find(PanelToggle).length === 1, 'should render PanelToggle');\n\n  t.equal(wrapper.find('.side-panel__tab').length, 4, 'should render 4 panel tabs');\n  const layerTab = wrapper.find('.side-panel__tab').at(0);\n\n  // click layer tab\n  layerTab.simulate('click');\n  t.ok(toggleSidePanel.calledWith('layer'), 'should call toggleSidePanel with layer');\n  t.end();\n});\n\ntest('Components -> SidePanel -> render panel', t => {\n  let wrapper;\n  let uiState = {\n    ...defaultProps.uiState,\n    activeSidePanel: 'layer'\n  };\n  // mount LayerManager\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.ok(wrapper.find(LayerManager).length === 1, 'should render LayerManager');\n\n  // mount FilterManager\n  uiState = {\n    ...defaultProps.uiState,\n    activeSidePanel: 'filter'\n  };\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n  t.ok(wrapper.find(FilterManager).length === 1, 'should render FilterManager');\n\n  // mount InteractionManager\n  uiState = {\n    ...defaultProps.uiState,\n    activeSidePanel: 'interaction'\n  };\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n  t.ok(wrapper.find(InteractionManager).length === 1, 'should render InteractionManager');\n\n  // mount MapManager\n  uiState = {\n    ...defaultProps.uiState,\n    activeSidePanel: 'map'\n  };\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n  t.ok(wrapper.find(MapManager).length === 1, 'should render MapManager');\n\n  t.end();\n});\n\ntest('Components -> SidePanel -> render custom panel', t => {\n  const RocketIcon = () => <div id=\"rocket-icon\" />;\n  const ChartIcon = () => <div id=\"chart-icon\" />;\n\n  const MyPanels = props => {\n    if (props.activeSidePanel === 'rocket') {\n      return <div className=\"rocket-panel\">Rocket</div>;\n    } else if (props.activeSidePanel === 'chart') {\n      return <div className=\"chart-panel\">Charts?</div>;\n    }\n\n    return null;\n  };\n\n  MyPanels.getProps = props => ({\n    layers: props.layers\n  });\n\n  function CustomSidePanelsFactory() {\n    return MyPanels;\n  }\n\n  function CustomSidePanelFactory(...deps) {\n    const SidePanel = SidePanelFactory(...deps);\n    const panels = [\n      ...SidePanel.defaultPanels,\n      {\n        id: 'rocket',\n        label: 'Rocket',\n        iconComponent: RocketIcon\n      },\n      {\n        id: 'chart',\n        label: 'Chart',\n        iconComponent: ChartIcon\n      }\n    ];\n\n    const CustomSidePanel = props => <SidePanel {...props} panels={panels} />;\n    return CustomSidePanel;\n  }\n\n  CustomSidePanelFactory.deps = SidePanelFactory.deps;\n\n  let wrapper;\n\n  const CustomSidePanel = appInjector\n    .provide(SidePanelFactory, CustomSidePanelFactory)\n    .provide(CustomPanelsFactory, CustomSidePanelsFactory)\n    .get(SidePanelFactory);\n\n  // mount CustomSidePanel\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <CustomSidePanel {...defaultProps} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.equal(wrapper.find('.side-panel__tab').length, 6, 'should render 6 panel tabs');\n\n  t.equal(wrapper.find(RocketIcon).length, 1, 'should render RocketIcon');\n  t.equal(wrapper.find(ChartIcon).length, 1, 'should render RocketIcon');\n\n  // // mount CustomSidePanel with 1 of the custom panel\n  const uiState = {...defaultProps.uiState, activeSidePanel: 'rocket'};\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <CustomSidePanel {...defaultProps} uiState={uiState} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail when mount with custom side panel activated');\n  t.equal(wrapper.find(MyPanels).length, 1, 'should render MyPanels');\n  t.end();\n});\n\ntest('Components -> SidePanel -> PanelHeader', t => {\n  const showExportDropdown = sinon.spy();\n  const uiStateActions = {\n    ...UIStateActions,\n    showExportDropdown\n  };\n\n  let wrapper;\n  // mount\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiStateActions={uiStateActions} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.ok(wrapper.find(PanelHeader).length === 1, 'should render PanelHeader');\n\n  const header = wrapper.find(PanelHeader);\n\n  // action item\n  t.equal(\n    header.find('.side-panel__panel-header__action').length,\n    1,\n    'should render 1 header action item'\n  );\n\n  // Share\n  t.equal(\n    header.find('.side-panel__panel-header__action').at(0).find('p').text(),\n    'Share',\n    'should only render Save action'\n  );\n\n  header.find('.side-panel__panel-header__action').at(0).simulate('click');\n\n  t.ok(showExportDropdown.calledWith('save'), 'should call toggleSidePanel with share');\n\n  // mound with exportDropdown\n  const uiState = {\n    ...defaultProps.uiState,\n    visibleDropdown: 'save'\n  };\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} uiStateActions={uiStateActions} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.end();\n});\n\ntest('Components -> SidePanel -> PanelHeader -> ExportDropDown', t => {\n  const toggleModal = sinon.spy();\n  const startExportingImage = sinon.spy();\n  const uiStateActions = {\n    ...UIStateActions,\n    toggleModal,\n    startExportingImage\n  };\n\n  // mound with exportDropdown\n  const uiState = {\n    ...defaultProps.uiState,\n    visibleDropdown: 'save'\n  };\n\n  let wrapper;\n  // mount\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <SidePanel {...defaultProps} uiState={uiState} uiStateActions={uiStateActions} />\n      </IntlWrapper>\n    );\n  }, 'SidePanel should not fail');\n\n  t.ok(wrapper.find(SaveExportDropdown).length === 1, 'should render SaveExportDropdown');\n  t.equal(wrapper.find(ToolbarItem).length, 3, 'should render 3 ToolbarItem');\n\n  // export image\n  t.equal(\n    wrapper.find(ToolbarItem).at(0).find('.toolbar-item__title').text(),\n    'Export Image',\n    'Should render Export Image'\n  );\n\n  wrapper.find(ToolbarItem).at(0).find('.toolbar-item').simulate('click');\n  t.ok(toggleModal.calledWith(EXPORT_IMAGE_ID), 'Should call toggleModal with EXPORT_IMAGE_ID');\n\n  // export data\n  t.equal(\n    wrapper.find(ToolbarItem).at(1).find('.toolbar-item__title').text(),\n    'Export Data',\n    'Should render Export Data'\n  );\n  wrapper.find(ToolbarItem).at(1).find('.toolbar-item').simulate('click');\n  t.ok(toggleModal.calledWith(EXPORT_DATA_ID), 'Should call toggleModal with EXPORT_DATA_ID');\n\n  // export map\n  t.equal(\n    wrapper.find(ToolbarItem).at(2).find('.toolbar-item__title').text(),\n    'Export Map',\n    'Should render Export Map'\n  );\n  wrapper.find(ToolbarItem).at(2).find('.toolbar-item').simulate('click');\n  t.ok(toggleModal.calledWith(EXPORT_MAP_ID), 'Should call toggleModal with EXPORT_MAP_ID');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/components/tooltip-config-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport sinon from 'sinon';\nimport test from 'tape';\nimport uniq from 'lodash/uniq';\n\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\nimport {\n  TooltipConfigFactory,\n  DatasetTagFactory,\n  FieldSelectorFactory,\n  ChickletedInput,\n  ChickletButton,\n  DropdownList,\n  Typeahead,\n  Icons,\n  appInjector\n} from '@kepler.gl/components';\n\nimport {StateWFiles, StateWithGeocoderDataset} from 'test/helpers/mock-state';\n\nconst TooltipConfig = appInjector.get(TooltipConfigFactory);\nconst DatasetTag = appInjector.get(DatasetTagFactory);\nconst {Hash, Delete} = Icons;\n\n// const tooltipConfig = {\n//   fieldsToShow: {\n//     '190vdll3di': [\n//       {name: 'gps_data.utc_timestamp', format: null},\n//       {name: 'gps_data.types', format: null},\n//       {name: 'epoch', format: null},\n//       {name: 'has_result', format: null},\n//       {name: 'id', format: null}\n//     ],\n//     ieukmgne: [\n//       {name: 'OBJECTID', format: null},\n//       {name: 'ZIP_CODE', format: null},\n//       {name: 'ID', format: null},\n//       {name: 'TRIPS', format: null},\n//       {name: 'RATE', format: null}\n//     ]\n//   },\n//   compareMode: false,\n//   compareType: 'absolute'\n// };\n\ntest('TooltipConfig - render', t => {\n  const datasets = StateWFiles.visState.datasets;\n  const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config;\n\n  const FieldSelector = appInjector.get(FieldSelectorFactory);\n  const onChange = sinon.spy();\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TooltipConfig onChange={onChange} config={tooltipConfig} datasets={datasets} />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  t.equal(wrapper.find(TooltipConfig).length, 1, 'Should render 1 TooltipConfig');\n  t.equal(wrapper.find(DatasetTag).length, 2, 'Should render 2 DatasetTag');\n  t.equal(wrapper.find(FieldSelector).length, 2, 'Should render 2 FieldSelector');\n  t.equal(wrapper.find(ChickletedInput).length, 2, 'Should render 2 ChickletedInput');\n\n  // tooltip chicklets\n  const tooltipButtons = wrapper.find(ChickletedInput).at(0).find(ChickletButton);\n\n  t.equal(tooltipButtons.length, 5, 'should render 6 tooltip buttons');\n  t.equal(tooltipButtons.at(0).find('span').at(0).text(), 'gps_data.utc_timestamp');\n\n  t.end();\n});\n\ntest('TooltipConfig - render -> onSelect', t => {\n  const datasets = StateWFiles.visState.datasets;\n  const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config;\n\n  const onChange = sinon.spy();\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TooltipConfig onChange={onChange} config={tooltipConfig} datasets={datasets} />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  t.equal(wrapper.find(ChickletedInput).length, 2, 'Should render 2 ChickletedInput');\n\n  // click chicklet input to open dropdown\n  wrapper.find(ChickletedInput).at(0).simulate('click');\n\n  const dropdownSelect = wrapper.find(Typeahead);\n  t.equal(dropdownSelect.length, 1, 'should render 1 Typeahead');\n\n  const listItems = dropdownSelect.find('.field-selector_list-item');\n\n  t.deepEqual(\n    dropdownSelect.find('.list__item__anchor').map(item => item.text()),\n    ['gps_data.lat', 'gps_data.lng', 'time', 'begintrip_ts_utc', 'begintrip_ts_local', 'date'],\n    'should filter out selected tooltip items'\n  );\n\n  // click 1 item to add\n  listItems.at(0).simulate('click');\n\n  const expectedArgs0 = {\n    ...tooltipConfig,\n    fieldsToShow: {\n      ...tooltipConfig.fieldsToShow,\n      '190vdll3di': [\n        ...tooltipConfig.fieldsToShow['190vdll3di'],\n        {name: 'gps_data.lat', format: null}\n      ]\n    }\n  };\n\n  t.deepEqual(onChange.args[0], [expectedArgs0], 'should call onchange with new tooltip appended');\n\n  // delete 1 item\n  const tooltipButtons = wrapper.find(ChickletedInput).at(0).find(ChickletButton);\n\n  tooltipButtons.at(0).find(Delete).simulate('click');\n  const expectedArgs1 = {\n    ...tooltipConfig,\n    fieldsToShow: {\n      ...tooltipConfig.fieldsToShow,\n      '190vdll3di': tooltipConfig.fieldsToShow['190vdll3di'].slice(\n        1,\n        tooltipConfig.fieldsToShow['190vdll3di'].length\n      )\n    }\n  };\n  t.deepEqual(onChange.args[1], [expectedArgs1], 'should call onchange with 1 item removed');\n\n  // clear All\n  t.equal(wrapper.find('.button.clear-all').length, 2, 'should render 2 clea all buttons');\n\n  // click to clear all\n  wrapper.find('.button.clear-all').at(0).simulate('click');\n  const expectedArgs2 = {\n    ...tooltipConfig,\n    fieldsToShow: {\n      ...tooltipConfig.fieldsToShow,\n      '190vdll3di': []\n    }\n  };\n  t.deepEqual(onChange.args[2], [expectedArgs2], 'should call onchange to clear all tooltips');\n\n  t.end();\n});\n\ntest('TooltipConfig - render -> tooltip format', t => {\n  const datasets = StateWFiles.visState.datasets;\n  const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config;\n\n  const onChange = sinon.spy();\n  const onDisplayFormatChange = sinon.spy();\n\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TooltipConfig\n          onChange={onChange}\n          onDisplayFormatChange={onDisplayFormatChange}\n          config={tooltipConfig}\n          datasets={datasets}\n        />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  const tooltipButtons = wrapper.find(ChickletedInput).at(0).find(ChickletButton);\n\n  // click on hash\n  tooltipButtons.at(0).find(Hash).simulate('click');\n\n  const formatDropdown = wrapper.find(DropdownList);\n  t.equal(formatDropdown.length, 1, 'should render 1 format dropdown');\n\n  const options = formatDropdown.at(0).props().options;\n\n  t.deepEqual(\n    uniq(options.map(op => op.type)),\n    ['none', 'date', 'date_time'],\n    'should render date type formats'\n  );\n\n  const option1 = options[1].format;\n\n  // click option1\n  formatDropdown.find('.list__item').at(1).simulate('click');\n  const expectedArgs0 = {\n    ...tooltipConfig,\n    fieldsToShow: {\n      ...tooltipConfig.fieldsToShow,\n      '190vdll3di': [\n        {name: 'gps_data.utc_timestamp', format: option1},\n        ...tooltipConfig.fieldsToShow['190vdll3di'].slice(\n          1,\n          tooltipConfig.fieldsToShow['190vdll3di'].length\n        )\n      ]\n    }\n  };\n  t.deepEqual(onChange.args[0], [expectedArgs0], 'should call onchange to set format');\n  t.end();\n});\n\ntest('TooltipConfig -> render -> do not display Geocoder dataset fields', t => {\n  // Contains only a single dataset which is the geocoder_dataset\n  const datasets = StateWithGeocoderDataset.visState.datasets;\n  const tooltipConfig = StateWithGeocoderDataset.visState.interactionConfig.tooltip.config;\n\n  const FieldSelector = appInjector.get(FieldSelectorFactory);\n  const onChange = sinon.spy();\n  let wrapper;\n\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <TooltipConfig onChange={onChange} config={tooltipConfig} datasets={datasets} />\n      </IntlWrapper>\n    );\n  }, 'Should render');\n\n  // Since only the geocoder_dataset is present, nothing should be rendered except the TooltipConfig\n  t.equal(wrapper.find(TooltipConfig).length, 1, 'Should render 1 TooltipConfig');\n  t.equal(wrapper.find(DatasetTag).length, 0, 'Should render 1 DatasetTag');\n  t.equal(wrapper.find(FieldSelector).length, 0, 'Should render 1 FieldSelector');\n  t.equal(wrapper.find(ChickletedInput).length, 0, 'Should render 1 ChickletedInput');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/file-handler-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\nimport test from 'tape';\nimport {generateHashIdFromString} from '@kepler.gl/utils';\nimport {processFileData, readFileInBatches, csvWithNull} from '@kepler.gl/processors';\nimport {dataWithNulls, testFields, parsedDataWithNulls} from 'test/fixtures/test-csv-data';\nimport geojsonString, {\n  featureString,\n  processedFeature,\n  processedFeatureRows,\n  processedFeatureFields,\n  processedFields as geojsonFields,\n  processedRows as geojsonRows\n} from 'test/fixtures/geojson-style';\nimport rowDataString, {parsedFields, parsedRows} from 'test/fixtures/row-object';\nimport {\n  savedStateV1InteractionCoordinate as keplerglMap,\n  parsedFields as parsedKeplerMapFields\n} from 'test/fixtures/state-saved-v1-7';\n\nimport {cmpField} from '../helpers/comparison-utils';\n\n// Install polyfills required for loaders.gl\nimport {installFilePolyfills} from '@loaders.gl/polyfills';\ninstallFilePolyfills();\n\ntest('#file-handler -> readFileInBatches.csv -> processFileData', async t => {\n  const csvFile = new File([dataWithNulls], 'text-data.csv', {type: 'text/csv'});\n\n  let gen = null;\n  try {\n    gen = await readFileInBatches({file: csvFile, fileList: []});\n  } catch (e) {\n    t.equal(true, false, 'Should read file correctly');\n    t.end();\n    return;\n  }\n\n  // metadata batch\n  const batch1 = await gen.next();\n\n  const expected1 = {\n    value: {\n      batchType: 'metadata',\n      metadata: {_loader: {}, _context: {}},\n      data: [],\n      bytesUsed: 0,\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName: 'text-data.csv',\n      shape: 'metadata'\n    },\n    done: false\n  };\n  t.deepEqual(\n    Object.keys(batch1.value).sort(),\n    Object.keys(expected1.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same');\n  t.equal(batch1.value.shape, expected1.value.shape, 'batch1.shape should be the same');\n  t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same');\n  t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same');\n  t.deepEqual(\n    batch1.value.progress,\n    expected1.value.progress,\n    'batch1.progress should be the same'\n  );\n\n  // data batch\n  const batch2 = await gen.next();\n\n  const expected2 = {\n    value: {\n      batchType: 'metadata',\n      bytesUsed: undefined,\n      count: 0,\n      cursor: 0,\n      data: csvWithNull,\n      fileName: 'text-data.csv',\n      headers: [\n        'gps_data.utc_timestamp',\n        'gps_data.lat',\n        'gps_data.lng',\n        'gps_data.types',\n        'epoch',\n        'has_result',\n        'uid',\n        'time',\n        'begintrip_ts_utc',\n        'begintrip_ts_local',\n        'date'\n      ],\n      length: 13,\n      progress: {rowCount: 13, rowCountInBatch: 13},\n      schema: {},\n      shape: 'object-row-table'\n    },\n    done: false\n  };\n\n  t.deepEqual(\n    Object.keys(batch2.value).sort(),\n    Object.keys(expected2.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch2.value.fileName, expected2.value.fileName, 'batch2.fileName should be the same');\n  // t.deepEqual(batch2.value.data, expected2.value.data, 'batch2.data should be the same');\n  t.deepEqual(batch2.value.headers, expected2.value.headers, 'batch2.headers should be the same');\n  t.deepEqual(\n    batch2.value.progress,\n    expected2.value.progress,\n    'batch2.progress should be the same'\n  );\n\n  const batch3 = await gen.next();\n  const expected3 = {value: undefined, done: true};\n  t.deepEqual(batch3, expected3, 'batch3 should be the final batch');\n\n  // go on to run processFileData\n  const processed = await processFileData({content: batch2.value, fileCache: []});\n  const expectedInfo = {\n    id: generateHashIdFromString('text-data.csv'),\n    label: 'text-data.csv',\n    format: 'row'\n  };\n\n  t.equal(processed.length, 1, 'processFileData should return 1 result');\n  t.ok(processed[0].info, 'processFileData should have info');\n  t.ok(processed[0].data, 'processFileData should have data');\n  t.deepEqual(processed[0].info, expectedInfo, 'info should be correct');\n\n  const {fields, rows} = processed[0].data;\n\n  fields.forEach((f, i) =>\n    cmpField(t, testFields[i], f, `should process correct field ${testFields[i].name}`)\n  );\n\n  rows.forEach((r, i) => {\n    t.deepEqual(r, parsedDataWithNulls[i], `should process row ${i} correctly`);\n  });\n\n  t.end();\n});\n\ntest('#file-handler -> readFileInBatches.GeoJSON FeatureCollection -> processFileData', async t => {\n  const geojsonFile = new File([geojsonString], 'text-data-1.geojson', {type: ''});\n\n  let gen = null;\n  try {\n    gen = await readFileInBatches({file: geojsonFile, fileList: []});\n  } catch (e) {\n    t.equal(true, false, 'Should read file correctly');\n    t.end();\n    return;\n  }\n\n  // metadata batch\n  const batch1 = await gen.next();\n\n  const expected1 = {\n    value: {\n      batchType: 'metadata',\n      metadata: {_loader: {}, _context: {}},\n      data: [],\n      bytesUsed: 0,\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName: 'text-data-1.geojson',\n      shape: 'metadata'\n    },\n    done: false\n  };\n\n  t.deepEqual(\n    Object.keys(batch1.value).sort(),\n    Object.keys(expected1.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same');\n  t.equal(batch1.value.shape, expected1.value.shape, 'batch1.shape should be the same');\n  t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same');\n  t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same');\n  t.deepEqual(\n    batch1.value.progress,\n    expected1.value.progress,\n    'batch1.progress should be the same'\n  );\n\n  // partial result batch\n  const batch2 = await gen.next();\n\n  const expected2 = {\n    value: {\n      shape: 'object-row-table',\n      batchType: 'partial-result',\n      container: {type: 'FeatureCollection', features: []},\n      data: [],\n      length: 0,\n      bytesUsed: 0,\n      jsonpath: '$.features',\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName: 'text-data-1.geojson'\n    },\n    done: false\n  };\n  t.deepEqual(batch2, expected2, 'batch2 should be partial-result');\n  // data batch\n  const batch3 = await gen.next();\n  const expected3 = {\n    value: {\n      data: [], // 13 features\n      schema: null,\n      length: 26,\n      cursor: 0,\n      count: 0,\n      bytesUsed: 4890,\n      jsonpath: '$.features',\n      progress: {rowCount: 26, rowCountInBatch: 26, percent: 1},\n      fileName: 'text-data-1.geojson'\n    },\n    done: false\n  };\n\n  t.equal(batch3.value.data.length, 26, 'batch3 should data length 26');\n  t.equal(batch3.value.jsonpath, expected3.value.jsonpath, 'batch3 should have jsonpath');\n  t.deepEqual(batch3.value.progress, expected3.value.progress, 'batch3 should have progress');\n  t.equal(batch3.value.fileName, expected3.value.fileName, 'batch3 should have fileName');\n  t.equal(batch3.value.length, expected3.value.length, 'batch3 should have length');\n\n  const batch4 = await gen.next();\n  const expected4 = {\n    value: {\n      batchType: 'final-result',\n      container: {type: 'FeatureCollection', features: []},\n      jsonpath: '$.features',\n      data: {type: 'FeatureCollection', features: []}, // feature should be length 26\n      schema: null,\n      progress: {rowCount: 26, rowCountInBatch: 0},\n      fileName: 'text-data-1.geojson'\n    },\n    done: false\n  };\n  t.equal(\n    batch4.value.data.type,\n    'FeatureCollection',\n    'batch4 should data be a geojson feature collection'\n  );\n  t.deepEqual(batch4.value.progress, expected4.value.progress, 'batch4 should have progress');\n  t.equal(batch4.value.fileName, expected4.value.fileName, 'batch4 should have fileName');\n  t.equal(batch4.value.data.features.length, 26, 'batch4 return 26 features');\n\n  const batch5 = await gen.next();\n  t.deepEqual(batch5, {value: undefined, done: true}, 'batch5 should be done');\n\n  // process geojson data received\n  const processed = await processFileData({content: batch4.value, fileCache: []});\n  const expectedInfo = {\n    id: generateHashIdFromString('text-data-1.geojson'),\n    label: 'text-data-1.geojson',\n    format: 'geojson'\n  };\n\n  t.equal(processed.length, 1, 'processFileData should return 1 result');\n  t.ok(processed[0].info, 'processFileData should have info');\n  t.ok(processed[0].data, 'processFileData should have data');\n  t.deepEqual(processed[0].info, expectedInfo, 'info should be correct');\n  const {fields, rows} = processed[0].data;\n  fields.forEach((f, i) =>\n    cmpField(\n      t,\n      geojsonFields[i],\n      f,\n      `should process correct geojson field ${geojsonFields[i].name}`\n    )\n  );\n  rows.forEach((r, i) => {\n    t.deepEqual(r, geojsonRows[i], `should process geojson row ${i} correctly`);\n  });\n\n  t.end();\n});\n\ntest('#file-handler -> readFileInBatches.GeoJSON Single Feature -> processFileData', async t => {\n  const geojsonFile = new File([featureString], 'text-data-1.geojson', {type: ''});\n  let gen = null;\n  try {\n    gen = await readFileInBatches({file: geojsonFile, fileList: []});\n  } catch (e) {\n    t.equal(true, false, 'Should read file correctly');\n    t.end();\n    return;\n  }\n\n  // metadata batch\n  const batch1 = await gen.next();\n\n  const expected1 = {\n    value: {\n      batchType: 'metadata',\n      metadata: {_loader: {}, _context: {}},\n      data: [],\n      bytesUsed: 0,\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName: 'text-data-1.geojson',\n      shape: 'metadata'\n    },\n    done: false\n  };\n\n  t.deepEqual(\n    Object.keys(batch1.value).sort(),\n    Object.keys(expected1.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same');\n  t.equal(batch1.value.shape, expected1.value.shape, 'batch1.shape should be the same');\n  t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same');\n  t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same');\n  t.deepEqual(\n    batch1.value.progress,\n    expected1.value.progress,\n    'batch1.progress should be the same'\n  );\n\n  // final result batch\n  const batch2 = await gen.next();\n  const expected2 = {\n    value: {\n      batchType: 'final-result',\n      container: processedFeature,\n      jsonpath: null,\n      data: processedFeature,\n      schema: null,\n      progress: {rowCount: 0, rowCountInBatch: 0},\n      fileName: 'text-data-1.geojson'\n    },\n    done: false\n  };\n\n  t.deepEqual(\n    batch2.value.batchType,\n    expected2.value.batchType,\n    'batch2 batchType should be a final-result'\n  );\n  t.deepEqual(\n    batch2.value.data,\n    expected2.value.data,\n    'batch2 data should be a single geojson feature'\n  );\n  t.deepEqual(\n    batch2.value.container,\n    expected2.value.container,\n    'batch2 container should be a single geojson feature'\n  );\n  t.equal(batch2.value.jsonpath, expected2.value.jsonpath, 'batch2 jsonpath should be null');\n  t.deepEqual(batch2.value.progress, expected2.value.progress, 'batch2 progress should be correct');\n\n  const batch3 = await gen.next();\n  t.deepEqual(batch3, {value: undefined, done: true}, 'batch3 should be done');\n\n  // process geojson data received\n  const processed = await processFileData({content: batch2.value, fileCache: []});\n  const expectedInfo = {\n    id: generateHashIdFromString('text-data-1.geojson'),\n    label: 'text-data-1.geojson',\n    format: 'geojson'\n  };\n\n  t.equal(processed.length, 1, 'processFileData should return 1 result');\n  t.ok(processed[0].info, 'processFileData should have info');\n  t.ok(processed[0].data, 'processFileData should have data');\n  t.deepEqual(processed[0].info, expectedInfo, 'info should be correct');\n  const {fields, rows} = processed[0].data;\n\n  fields.forEach((f, i) =>\n    cmpField(\n      t,\n      processedFeatureFields[i],\n      f,\n      `should process correct geojson field ${processedFeatureFields[i].name}`\n    )\n  );\n  rows.forEach((r, i) => {\n    t.deepEqual(r, processedFeatureRows[i], `should process geojson row ${i} correctly`);\n  });\n\n  t.end();\n});\n\ntest('#file-handler -> readFileInBatches.row -> processFileData', async t => {\n  const fileName = 'row-data.json';\n  const rowFile = new File([rowDataString], fileName, {type: ''});\n\n  let gen = null;\n  try {\n    gen = await readFileInBatches({file: rowFile, fileList: []});\n  } catch (e) {\n    t.equal(true, false, 'Should read file correctly');\n    t.end();\n    return;\n  }\n\n  // metadata batch\n  const batch1 = await gen.next();\n\n  const expected1 = {\n    value: {\n      batchType: 'metadata',\n      metadata: {_loader: {}, _context: {}},\n      data: [],\n      bytesUsed: 0,\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName,\n      shape: 'metadata'\n    },\n    done: false\n  };\n  t.deepEqual(\n    Object.keys(batch1.value).sort(),\n    Object.keys(expected1.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same');\n  t.equal(batch1.value.shape, expected1.value.shape, 'batch1.shape should be the same');\n  t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same');\n  t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same');\n  t.deepEqual(\n    batch1.value.progress,\n    expected1.value.progress,\n    'batch1.progress should be the same'\n  );\n\n  const batch2 = await gen.next();\n  const expected2 = {\n    value: {\n      batchType: 'partial-result',\n      container: {}, // do not test\n      data: [],\n      bytesUsed: 0,\n      schema: null,\n      jsonpath: '$',\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName\n    },\n    done: false\n  };\n  t.deepEqual(batch2.value.batchType, expected2.value.batchType, 'batch2 should be partial-result');\n  t.deepEqual(batch2.value.jsonpath, expected2.value.jsonpath, 'batch2 should be partial-result');\n  t.deepEqual(batch2.value.progress, expected2.value.progress, 'batch2 progress should be correct');\n\n  const batch3 = await gen.next();\n  const expected3 = {\n    value: {\n      data: [],\n      schema: null,\n      length: 8,\n      cursor: 0,\n      count: 0,\n      bytesUsed: 4552,\n      jsonpath: '$',\n      progress: {rowCount: 8, rowCountInBatch: 8, percent: 1},\n      fileName\n    },\n    done: false\n  };\n  t.equal(batch3.value.data.length, 8, 'batch3 should data length 26');\n  t.deepEqual(batch3.value.progress, expected3.value.progress, 'batch3 should have progress');\n  t.equal(batch3.value.fileName, expected3.value.fileName, 'batch3 should have fileName');\n  t.equal(batch3.value.length, expected3.value.length, 'batch3 should have length');\n\n  const batch4 = await gen.next();\n  const expected4 = {\n    value: {\n      batchType: 'final-result',\n      container: {}, // do not test\n      jsonpath: '$',\n      data: [], // do not test\n      schema: null,\n      progress: {rowCount: 8, rowCountInBatch: 0},\n      fileName\n    },\n    done: false\n  };\n\n  t.deepEqual(batch4.value.progress, expected4.value.progress, 'batch4 should have progress');\n  t.equal(batch4.value.fileName, expected4.value.fileName, 'batch4 should have fileName');\n  t.equal(batch4.value.data.length, 8, 'batch4 return 8 rows');\n\n  const batch5 = await gen.next();\n  t.deepEqual(batch5, {value: undefined, done: true}, 'batch5 should be done');\n\n  const processed = await processFileData({content: batch4.value, fileCache: []});\n  const expectedFileCache = [\n    {\n      data: {\n        fields: parsedFields,\n        rows: parsedRows\n      },\n      info: {id: generateHashIdFromString(fileName), label: fileName, format: 'row'}\n    }\n  ];\n  t.equal(processed.length, 1, 'processFileData should return 1 result');\n  t.ok(processed[0].info, 'processFileData should have info');\n  t.ok(processed[0].data, 'processFileData should have data');\n  t.deepEqual(processed[0].info, expectedFileCache[0].info, 'info should be correct');\n\n  const {fields, rows} = processed[0].data;\n  fields.forEach((f, i) =>\n    cmpField(\n      t,\n      expectedFileCache[0].data.fields[i],\n      f,\n      `should process correct row object field ${parsedFields[i].name}`\n    )\n  );\n  rows.forEach((r, i) => {\n    t.deepEqual(r, expectedFileCache[0].data.rows[i], `should process row ${i} correctly`);\n  });\n\n  t.end();\n});\n\ntest('#file-handler -> readFileInBatches.keplerMap -> processFileData', async t => {\n  const fileName = 'keplergl.json';\n  const keplerGlMap = new File([JSON.stringify(keplerglMap)], fileName, {type: ''});\n\n  let gen = null;\n  try {\n    gen = await readFileInBatches({file: keplerGlMap, fileList: []});\n  } catch (e) {\n    t.equal(true, false, 'Should read file correctly');\n    t.end();\n    return;\n  }\n\n  // metadata batch\n  const batch1 = await gen.next();\n\n  const expected1 = {\n    value: {\n      batchType: 'metadata',\n      metadata: {_loader: {}, _context: {}},\n      data: [],\n      bytesUsed: 0,\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName,\n      shape: 'metadata'\n    },\n    done: false\n  };\n  t.deepEqual(\n    Object.keys(batch1.value).sort(),\n    Object.keys(expected1.value).sort(),\n    'value should have same keys'\n  );\n\n  t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same');\n  t.equal(batch1.value.shape, expected1.value.shape, 'batch1.shape should be the same');\n  t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same');\n  t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same');\n  t.deepEqual(\n    batch1.value.progress,\n    expected1.value.progress,\n    'batch1.progress should be the same'\n  );\n\n  const batch2 = await gen.next();\n  const expected2 = {\n    value: {\n      batchType: 'partial-result',\n      container: {}, // do not test\n      data: [],\n      bytesUsed: 0,\n      jsonpath: '$.datasets',\n      progress: {rowCount: 0, rowCountInBatch: 0, percent: 0},\n      fileName\n    },\n    done: false\n  };\n  t.deepEqual(batch2.value.batchType, expected2.value.batchType, 'batch2 should be partial-result');\n  t.deepEqual(batch2.value.jsonpath, expected2.value.jsonpath, 'batch2 jsonapth should be correct');\n  t.deepEqual(batch2.value.schema, expected2.value.schema, 'batch2 schema should be correct');\n  t.deepEqual(batch2.value.progress, expected2.value.progress, 'batch2 progress should be correct');\n\n  const batch3 = await gen.next();\n  const expected3 = {\n    value: {\n      data: [],\n      length: 1,\n      cursor: 0,\n      count: 0,\n      bytesUsed: 4552,\n      jsonpath: '$.datasets',\n      progress: {rowCount: 1, rowCountInBatch: 1, percent: 1},\n      fileName\n    },\n    done: false\n  };\n  t.equal(batch3.value.data.length, 1, 'batch3 should data length 1');\n  t.deepEqual(batch3.value.progress, expected3.value.progress, 'batch3 should have progress');\n  t.equal(batch3.value.fileName, expected3.value.fileName, 'batch3 should have fileName');\n  t.equal(batch3.value.length, expected3.value.length, 'batch3 should have length');\n\n  const batch4 = await gen.next();\n  const expected4 = {\n    value: {\n      batchType: 'final-result',\n      container: {}, // do not test\n      jsonpath: '$.datasets',\n      data: [], // do not test\n      progress: {rowCount: 1, rowCountInBatch: 0},\n      fileName\n    },\n    done: false\n  };\n\n  t.deepEqual(batch4.value.progress, expected4.value.progress, 'batch4 should have progress');\n  t.deepEqual(batch4.value.batchType, expected4.value.batchType, 'batch4 should have batchType');\n  t.equal(batch4.value.fileName, expected4.value.fileName, 'batch4 should have fileName');\n  t.deepEqual(batch4.value.data, keplerglMap, 'batch4 should return saved kepler.gl map json');\n\n  const batch5 = await gen.next();\n  t.deepEqual(batch5, {value: undefined, done: true}, 'batch5 should be done');\n\n  // process file\n  const processed = await processFileData({content: batch4.value, fileCache: []});\n  const expectedInfo = {\n    id: generateHashIdFromString(fileName),\n    label: fileName,\n    format: 'keplergl'\n  };\n\n  const expectedFileCache = [\n    {\n      data: {\n        datasets: [\n          {\n            data: {\n              fields: parsedKeplerMapFields,\n              rows: keplerglMap.datasets[0].data.allData\n            },\n            info: {id: 'a5ybmwl2d', label: 'geojson_as_string_small.csv', color: [53, 92, 125]}\n          }\n        ],\n        config: keplerglMap.config\n      },\n      info: expectedInfo\n    }\n  ];\n\n  t.equal(processed.length, 1, 'processFileData should return 1 result');\n  t.ok(processed[0].info, 'processFileData should have info');\n  t.ok(processed[0].data, 'processFileData should have data');\n  t.deepEqual(processed[0].info, expectedInfo, 'info should be correct');\n\n  t.deepEqual(\n    Object.keys(processed[0].data),\n    ['datasets', 'config'],\n    'processFileData of keplergl json should have datasets and config'\n  );\n  t.deepEqual(\n    Object.keys(processed[0].data.datasets[0].data),\n    ['fields', 'rows'],\n    'dataset should have fields and rows'\n  );\n\n  t.deepEqual(\n    processed[0].data.datasets[0].data.rows,\n    expectedFileCache[0].data.datasets[0].data.rows,\n    'should load datasets rows'\n  );\n\n  t.deepEqual(\n    processed[0].data.datasets[0].data.fields,\n    expectedFileCache[0].data.datasets[0].data.fields,\n    'should load and analyze datasets'\n  );\n\n  t.deepEqual(\n    processed[0].data.datasets[0].info,\n    expectedFileCache[0].data.datasets[0].info,\n    'should load correct file info'\n  );\n  t.deepEqual(\n    Object.keys(processed[0].data.config),\n    ['visState', 'mapStyle', 'mapState'],\n    'should prepare parsed config'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst configure = require('enzyme').configure;\nconst Adapter = require('@cfaester/enzyme-adapter-react-18').default;\nconfigure({adapter: new Adapter()});\n\n// component tests\nrequire('./components');\n\n// test layers\nrequire('./layer-tests');\n\n// test reducers\nrequire('./reducers');\n\n// test processors\nrequire('./file-handler-test');\n"
  },
  {
    "path": "test/browser/layer-tests/arc-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  dataId,\n  testRows,\n  preparedDataset,\n  preparedFilterDomain0,\n  arcLayerMeta\n} from 'test/helpers/layer-utils';\nimport testArcData, {arcFromHex, arcFromNeighbor} from 'test/fixtures/test-arc-data';\nimport {StateWArcNeighbors} from 'test/helpers/mock-state';\nimport {PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\nimport {h3ToGeo} from 'h3-js';\n\nconst {ArcLayer} = KeplerGlLayers;\nconst columns = {\n  lat0: 'lat',\n  lng0: 'lng',\n  lat1: 'lat_1',\n  lng1: 'lng_1'\n};\n\ntest('#ArcLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test arc layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'ArcLayer dataId should be correct');\n          t.ok(layer.type === 'arc', 'type should be arc');\n          t.ok(layer.isAggregated === false, 'ArcLayer is not aggregated');\n          t.ok(layer.config.label === 'test arc layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, ArcLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#ArcLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  // filter.domain: [ 1474071056000, 1474071489000 ]\n  // filterRange: [ [ 39000, 552000 ]\n  // create a clone from preparedDataset\n  const TEST_CASES = [\n    {\n      name: 'Arc trip data.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip arcs',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'arc',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [testRows[0][2], testRows[0][1], 0],\n              targetPosition: [testRows[0][4], testRows[0][3], 0]\n            },\n            {\n              index: 4,\n              sourcePosition: [testRows[4][2], testRows[4][1], 0],\n              targetPosition: [testRows[4][4], testRows[4][3], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getSourceColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct arc layerData data'\n        );\n        // getSourceColor\n        t.deepEqual(\n          layerData.getSourceColor,\n          layer.config.color,\n          'getSourceColor should be a constant'\n        );\n        // getSourceColor\n        t.deepEqual(\n          layerData.getTargetColor,\n          layer.config.color,\n          'getTargetColors should be a constant'\n        );\n        // getWidth\n        t.equal(layerData.getWidth, 1, 'getWidth should be a constant');\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n\n        // layerMeta\n        t.deepEqual(layer.meta, arcLayerMeta, 'should format correct arc layer meta');\n      }\n    },\n    {\n      name: 'Arc trip data.2 targetColor',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip arcs',\n          columns,\n          color: [10, 10, 10],\n          visConfig: {\n            targetColor: [1, 2, 3]\n          }\n        },\n        type: 'arc',\n        id: 'test_layer_2'\n      },\n      datasets: {\n        [dataId]: preparedDataset\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getSourceColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n\n        // getSourceColor\n        t.deepEqual(\n          layerData.getSourceColor,\n          layer.config.color,\n          'getSourceColor should be a constant'\n        );\n        // getSourceColor\n        t.deepEqual(layerData.getTargetColor, [1, 2, 3], 'getTargetColors should be a constant');\n      }\n    },\n    {\n      name: 'Arc trip data. with colorField and sizeField',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip arcs',\n          columns,\n          color: [10, 10, 10],\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by trip_distance(real)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            sizeRange: [10, 20]\n          }\n        },\n        type: 'arc',\n        id: 'test_layer_1'\n      },\n      // modify preparedDataset\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [testRows[0][2], testRows[0][1], 0],\n              targetPosition: [testRows[0][4], testRows[0][3], 0]\n            },\n            {\n              index: 4,\n              sourcePosition: [testRows[4][2], testRows[4][1], 0],\n              targetPosition: [testRows[4][4], testRows[4][3], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getSourceColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct arc layerData data'\n        );\n        // getSourceColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getSourceColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getSourceColor should be correct'\n        );\n        // getTargetColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getTargetColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getTargetColors  be correct'\n        );\n        // getWidth\n        // domain: [1.59, 11]\n        // range: [10, 20]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getWidth),\n          [10, (2.37 - 1.59) * (10 / 9.41) + 10],\n          'getWidth should be a constant'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    },\n    {\n      name: 'Arc data from neighbors',\n      layer: arcFromNeighbor,\n      datasets: StateWArcNeighbors.visState.datasets,\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [testArcData[0].longitude, testArcData[0].latitude, 0],\n              targetPosition: [testArcData[1].longitude, testArcData[1].latitude, 0]\n            },\n            {\n              index: 0,\n              sourcePosition: [testArcData[0].longitude, testArcData[0].latitude, 0],\n              targetPosition: [testArcData[2].longitude, testArcData[2].latitude, 0]\n            },\n            {\n              index: 1,\n              sourcePosition: [testArcData[1].longitude, testArcData[1].latitude, 0],\n              targetPosition: [testArcData[2].longitude, testArcData[2].latitude, 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getSourceColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        // index 18, 19 does not have valid neighbors\n        t.equal(layerData.data.length, 38, 'should format 41 rows');\n\n        for (let i = 0; i < 3; i++) {\n          t.deepEqual(\n            layerData.data[i],\n            expectedLayerData.data[i],\n            'should format correct arc layerData data'\n          );\n        }\n      }\n    },\n    {\n      name: 'Arc data from hex',\n      layer: arcFromHex,\n      datasets: StateWArcNeighbors.visState.datasets,\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [...h3ToGeo(testArcData[0]['source hex_id']).reverse(), 0],\n              targetPosition: [...h3ToGeo(testArcData[0]['target hex_id']).reverse(), 0]\n            },\n            {\n              index: 1,\n              sourcePosition: [...h3ToGeo(testArcData[1]['source hex_id']).reverse(), 0],\n              targetPosition: [...h3ToGeo(testArcData[1]['target hex_id']).reverse(), 0]\n            },\n            {\n              index: 2,\n              sourcePosition: [...h3ToGeo(testArcData[2]['source hex_id']).reverse(), 0],\n              targetPosition: [...h3ToGeo(testArcData[2]['target hex_id']).reverse(), 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getSourceColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        // index 18, 19 does not have valid neighbors\n        t.equal(layerData.data.length, 20, 'should format 20 rows');\n\n        for (let i = 0; i < 3; i++) {\n          t.deepEqual(\n            layerData.data[i],\n            expectedLayerData.data[i],\n            'should format correct arc layerData data'\n          );\n        }\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, ArcLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#ArcLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Arc render layer.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip arcs',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'arc',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          widthScale: layer.config.visConfig.thickness * PROJECTED_PIXEL_SIZE_MULTIPLIER,\n          filterRange: preparedDataset.gpuFilter.filterRange\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.equal(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    },\n    {\n      name: 'Arc render layer.2 brushing',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip arcs',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'arc',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        interactionConfig: {\n          brush: {\n            enabled: true,\n            config: {\n              size: 2.5\n            }\n          }\n        }\n      },\n      assert: deckLayers => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n        // test instancePositions\n        const expectedProps = {\n          brushingRadius: 2500,\n          brushingEnabled: true,\n          brushingTarget: 'source_target'\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.equal(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, ArcLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/base-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape-catch';\nimport {Layer, AggregationLayer} from '@kepler.gl/layers';\nimport {KeplerTable} from '@kepler.gl/table';\n\ntest('#BaseLayer -> updateLayerDomain', t => {\n  const rows = [\n    ['a', 3],\n    ['b', 4],\n    ['c', 1],\n    ['d', null],\n    ['e', 5],\n    ['f', 0]\n  ];\n  const fields = [\n    {\n      name: 'f1'\n    },\n    {\n      name: 'f2'\n    }\n  ];\n  const dataset = new KeplerTable({\n    info: {\n      id: 'test'\n    }\n  });\n  dataset.importData({\n    data: {\n      rows,\n      fields\n    }\n  });\n\n  dataset.filteredIndex = [0, 1, 2, 3];\n  dataset.filteredIndexForDomain = [0, 1, 2, 3, 4];\n\n  const mockLayer = new Layer({dataId: 'test'});\n\n  mockLayer.updateLayerConfig({\n    colorField: dataset.fields[1],\n    colorDomain: [0, 1],\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeDomain: [0, 1]\n  });\n\n  const expectedDomain = [1, 3, 4, 5];\n  let updatedLayer = mockLayer.updateLayerDomain({\n    test: dataset\n  });\n\n  t.deepEqual(\n    updatedLayer.config.colorDomain,\n    expectedDomain,\n    'should calculate layer color domain'\n  );\n\n  t.deepEqual(updatedLayer.config.sizeDomain, [0, 1], 'should not calculate layer size domain');\n\n  mockLayer.updateLayerConfig({\n    colorField: dataset.fields[0],\n    colorDomain: [0, 1],\n    colorScale: 'ordinal',\n    sizeField: null,\n    sizeDomain: [0, 1]\n  });\n\n  updatedLayer = mockLayer.updateLayerDomain({\n    test: dataset\n  });\n\n  const expectedOrdinalDomain = ['a', 'b', 'c', 'd', 'e', 'f'];\n\n  t.deepEqual(\n    updatedLayer.config.colorDomain,\n    expectedOrdinalDomain,\n    'should calculate layer color domain based on ordinal domain'\n  );\n\n  const dataset2 = new KeplerTable({\n    info: {\n      id: 'test'\n    }\n  });\n  dataset2.importData({\n    data: {\n      rows: [\n        ['a', 3],\n        ['b', 4],\n        ['c', 1],\n        ['d', null],\n        ['e', 5],\n        ['f', 0],\n        ['g', 6]\n      ],\n      fields\n    }\n  });\n\n  dataset2.filteredIndex = [0, 1, 2, 3];\n  dataset2.filteredIndexForDomain = [0, 1, 2, 3, 4];\n  updatedLayer = mockLayer.updateLayerDomain({test: dataset2}, {id: 'newFilter'});\n\n  t.deepEqual(\n    updatedLayer.config.colorDomain,\n    expectedOrdinalDomain,\n    'should skip domain calculation if field is ordinal'\n  );\n\n  t.end();\n});\n\ntest('#BaseLayer -> getVisChannelScale, by zoom', t => {\n  const scale = 'quantize';\n  const domain = {\n    z: [2, 3, 4, 5],\n    stops: [\n      [0, 20],\n      [0, 30],\n      [0, 40],\n      [0, 50]\n    ]\n  };\n  const range = ['#010101', '#020202', '#030303'];\n\n  const mockLayer = new Layer({dataId: 'test'});\n\n  const scaleFunc = mockLayer.getVisChannelScale(scale, domain, range);\n\n  t.ok(scaleFunc.byZoom, true, 'should set by zoom to be truthy');\n  const scale1 = scaleFunc(1);\n  const scale2 = scaleFunc(3.2);\n  const scale3 = scaleFunc(9);\n\n  t.deepEqual(scale1.domain(), [0, 20], 'should set domain by zoom');\n  t.deepEqual(scale1.range(), range, 'should set range');\n  t.deepEqual(scale2.domain(), [0, 30], 'should set domain by zoom');\n  t.deepEqual(scale3.domain(), [0, 50], 'should set domain by zoom');\n\n  t.end();\n});\n\ntest('#AggregationLayer -> updateLayerDomain', t => {\n  const rows = [\n    ['a', 3],\n    ['b', 4],\n    ['c', 1],\n    ['d', null]\n  ];\n\n  const fields = [\n    {\n      name: 'f1'\n    },\n    {\n      name: 'f2'\n    }\n  ];\n  const dataset = new KeplerTable({\n    info: {\n      id: 'test'\n    }\n  });\n  dataset.importData({\n    data: {\n      rows,\n      fields\n    }\n  });\n\n  const mockLayer = new AggregationLayer({dataId: 'test'});\n\n  mockLayer.updateLayerConfig({\n    colorField: dataset.fields[1],\n    colorDomain: [0, 1],\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeDomain: [0, 1]\n  });\n\n  const updatedLayer = mockLayer.updateLayerDomain({test: dataset});\n  t.deepEqual(\n    updatedLayer.config.colorDomain,\n    [0, 1],\n    'should not calculate aggregation layer domain'\n  );\n\n  t.end();\n});\n\ntest('#BaseLayer -> getAllPossibleColumnPairs', t => {\n  const columnes1 = {\n    a: [1, 2],\n    b: [3, 4]\n  };\n  const columnes2 = {\n    a: [1],\n    b: [3, 4]\n  };\n\n  const columnes3 = {\n    a: [1]\n  };\n  t.equal(Layer.getAllPossibleColumnPairs(columnes1).length, 4, 'should find 4 pairs');\n  t.equal(Layer.getAllPossibleColumnPairs(columnes2).length, 2, 'should find 4 pairs');\n  t.equal(Layer.getAllPossibleColumnPairs(columnes3).length, 1, 'should find 4 pairs');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/cluster-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport sinon from 'sinon';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta\n} from 'test/helpers/layer-utils';\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {INITIAL_MAP_STATE} from '@kepler.gl/reducers';\n\nconst {ClusterLayer} = KeplerGlLayers;\n\nconst columns = {\n  lat: 'lat',\n  lng: 'lng'\n};\n\ntest('#ClusterLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'taro',\n          isVisible: true,\n          label: 'test cluster layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'taro', 'clusterLayer dataId should be correct');\n          t.ok(layer.type === 'cluster', 'type should be cluster');\n          t.ok(layer.isAggregated === true, 'clusterLayer is aggregated');\n          t.ok(layer.config.label === 'test cluster layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, ClusterLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#ClusterLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 1, 2, 4, 5, 7];\n\n  const TEST_CASES = [\n    {\n      name: 'Cluster gps point.1',\n      layer: {\n        type: 'cluster',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3]\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n        t.deepEqual(layerData.data, expectedLayerData.data, 'should format correct grid layerData');\n        // test getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1]],\n          'getPosition should return correct position'\n        );\n        // test getColorValue  [1474071095000, 1474071608000]\n        // 0: Null - 0\n        // 1: 2016-09-17 00:10:56 1474071056000 - 0\n        // 4: 2016-09-17 00:14:00 1474071240000 - 1\n        // 5: 2016-09-17 00:15:01 1474071301000 - 1\n        // 7: 2016-09-17 00:17:05 1474071425000 - 1\n        t.equal(\n          // assume all points fall into one bin\n          layerData.getColorValue(expectedLayerData.data),\n          5,\n          'should return unfiltered point count'\n        );\n        t.equal(\n          // assume all points fall into one bin\n          layerData.getElevationValue(expectedLayerData.data),\n          5,\n          'should return unfiltered point count'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData._filterData),\n          [false, false, true, true, true],\n          '_filterData should filter data correctly'\n        );\n        // test layer.meta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct grid layer meta');\n      }\n    },\n    {\n      name: 'Cluster gps point.2',\n      layer: {\n        type: 'cluster',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n        // test getColorValue aggregate by mode\n        // 0: driver_analytics_0 - 0\n        // 1: null  - 0\n        // 4: driver_analytics - 1\n        // 5: driver_analytics - 1\n        // 7: driver_analytics - 1\n        t.equal(\n          // assume all points fall into one bin\n          layerData.getColorValue(expectedLayerData.data),\n          'driver_analytics',\n          'should return filtered mode of (types)'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData._filterData),\n          [false, false, true, true, true],\n          '_filterData should filter data correctly'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, ClusterLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#ClusterLayer -> renderLayer', t => {\n  const filteredIndex = [0, 1, 2, 4, 5, 7];\n  const spyLayerCallbacks = sinon.spy();\n\n  // 0 2016-09-17 00:09:55: 1474070995000 - false\n  // 1 2016-09-17 00:10:56: 1474071056000 - false\n  // 2 2016-09-17 00:11:56: 1474071116000 - true\n  // 4 2016-09-17 00:14:00: 1474071240000 - true\n  // 5 2016-09-17 00:15:01: 1474071301000 - true\n  // 7 2016-09-17 00:17:05: 1474071425000 - true\n\n  // timeFilter [1474071095000, 1474071608000]\n  const TEST_CASES = [\n    {\n      name: 'Cluster gps point.1',\n      layer: {\n        type: 'cluster',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#080808', '#090909', '#070707']\n            }\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        layerCallbacks: {\n          onSetLayerDomain: spyLayerCallbacks\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_1', 'test_layer_1-cluster'],\n          'Should create 2 deck.gl layers'\n        );\n        const [clusterLayer, scatterplotLayer] = deckLayers;\n        const {props} = clusterLayer;\n        const scatterplotLayerProp = scatterplotLayer.props;\n\n        const expectedProps = {\n          colorRange: [\n            [8, 8, 8],\n            [9, 9, 9],\n            [7, 7, 7]\n          ],\n          radiusScale: 1,\n          radiusRange: layer.config.visConfig.radiusRange,\n          clusterRadius: layer.config.visConfig.clusterRadius,\n          colorScaleType: layer.config.colorScale,\n          zoom: Math.round(INITIAL_MAP_STATE.zoom),\n          width: INITIAL_MAP_STATE.width,\n          height: INITIAL_MAP_STATE.height\n        };\n\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n\n        const expectedScatterplotData = [\n          {\n            points: [\n              {\n                index: 4\n              }\n            ],\n            position: [-122.136795, 37.456535],\n            index: 0,\n            filteredPoints: null\n          },\n          {\n            points: [\n              {\n                index: 5\n              }\n            ],\n            position: [-122.10239, 37.40066],\n            index: 1,\n            filteredPoints: null\n          },\n          {\n            points: [\n              {\n                index: 7\n              }\n            ],\n            position: [-122.26108, 37.879066],\n            index: 2,\n            filteredPoints: null\n          }\n        ];\n        const expectedColorBins = [\n          {i: 0, value: 1, counts: 1},\n          {i: 1, value: 1, counts: 1},\n          {i: 2, value: 1, counts: 1}\n        ];\n\n        t.deepEqual(\n          scatterplotLayerProp.data,\n          expectedScatterplotData,\n          'should pass correct data to cluster layer'\n        );\n\n        const expectedColorDomain = {\n          domain: [1, 1, 1],\n          aggregatedBins: {\n            0: {i: 0, value: 1, counts: 1},\n            1: {i: 1, value: 1, counts: 1},\n            2: {i: 2, value: 1, counts: 1}\n          }\n        };\n        t.deepEqual(\n          spyLayerCallbacks.args[0][0],\n          expectedColorDomain,\n          'should call onSetLayerDomain with correct domain'\n        );\n\n        // fillColor\n        t.deepEqual(\n          clusterLayer.state.aggregatorState.dimensions.fillColor.sortedBins.sortedBins,\n          expectedColorBins,\n          'should create correct color bins sortedBins'\n        );\n        t.deepEqual(\n          clusterLayer.state.aggregatorState.dimensions.fillColor.valueDomain,\n          [1, 1, 1],\n          'should create correct radius valueDomain based on filteredPoints'\n        );\n\n        // radius\n        t.deepEqual(\n          clusterLayer.state.aggregatorState.dimensions.radius.valueDomain,\n          [0, 1],\n          'should create correct radius valueDomain based on filteredPoints'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, ClusterLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/geojson-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {defaultElevation, defaultLineWidth, defaultRadius, KeplerGlLayers} from '@kepler.gl/layers';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\n\nconst {GeojsonLayer} = KeplerGlLayers;\n\nimport {updatedLayerV2} from 'test/fixtures/test-csv-data';\nimport {\n  dataId,\n  preparedGeoDataset,\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  prepareGeojsonDataset,\n  geoFilterDomain0,\n  geojsonFilterDomain0\n} from 'test/helpers/layer-utils';\nimport {createNewDataEntryMock} from 'test/helpers/table-utils';\nimport {\n  updatedGeoJsonLayer,\n  geoJsonWithStyle,\n  geoStyleDataToFeature,\n  geoStyleMeta\n} from 'test/fixtures/geojson';\nimport {processGeojson} from '@kepler.gl/processors';\n\ntest('#GeojsonLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test geojson layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'geojsonLayer dataId should be correct');\n          t.ok(layer.type === 'geojson', 'type should be geojson');\n          t.ok(layer.isAggregated === false, 'geojsonLayer is not aggregated');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, GeojsonLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#GeojsonLayer -> formatLayerData', async t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Geojson wkt polygon.1',\n      layer: {\n        type: 'geojson',\n        id: 'test_geojson_layer_1',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columns: {\n            geojson: 'simplified_shape_v2'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedGeoDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [updatedLayerV2.dataToFeature[2], updatedLayerV2.dataToFeature[4]]\n        };\n        const expectedDataKeys = [\n          'data',\n          'getElevation',\n          'getFillColor',\n          'getFilterValue',\n          'getFiltered',\n          'getLineColor',\n          'getLineWidth',\n          'getPointRadius'\n        ];\n        const expectedLayerMeta = updatedLayerV2.meta;\n        const expectedDataToFeature = updatedLayerV2.dataToFeature;\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getElevation),\n          [defaultElevation, defaultElevation],\n          'getElevation should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [\n            [1, 2, 3],\n            [1, 2, 3]\n          ],\n          'getFillColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [11.8 - geoFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineColor),\n          [\n            [1, 2, 3],\n            [1, 2, 3]\n          ],\n          'getLineColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineWidth),\n          [defaultLineWidth, defaultLineWidth],\n          'getLineWidth should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getPointRadius),\n          [defaultRadius, defaultRadius],\n          'getPointRadius should return correct value'\n        );\n        // meta\n        t.deepEqual(layer.meta, expectedLayerMeta, 'should format correct geojson layer meta');\n        // dataToFeature\n        t.equal(\n          layer.dataToFeature.length,\n          expectedDataToFeature.length,\n          'should format correct geojson dataToFeature length'\n        );\n\n        layer.dataToFeature.forEach((feature, i) => {\n          t.deepEqual(\n            feature,\n            expectedDataToFeature[i],\n            `should format correct geojson dataToFeature[${i}]`\n          );\n        });\n      }\n    },\n    {\n      name: 'Geojson wkt polygon.2',\n      layer: {\n        type: 'geojson',\n        id: 'test_geojson_layer_2',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns: {\n            geojson: 'simplified_shape'\n          },\n          visConfig: {\n            stroked: true,\n            enable3d: true,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            strokeColorRange: {\n              colors: ['#040404', '#050505', '#060606']\n            }\n          },\n          // color by c_zip_type(string)\n          colorField: preparedGeoDataset.fields.find(f => f.name === 'c_zip_type'),\n          strokeColorField: preparedGeoDataset.fields.find(f => f.name === 'c_zip_type'),\n\n          // stroke by c_number(real)\n          sizeField: preparedGeoDataset.fields.find(f => f.name === 'c_number'),\n          // stroke by a_zip(int)\n          heightField: preparedGeoDataset.fields.find(f => f.name === 'a_zip')\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedGeoDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [updatedLayerV2.dataToFeature[2], updatedLayerV2.dataToFeature[4]]\n        };\n        const expectedDataKeys = [\n          'data',\n          'getElevation',\n          'getFillColor',\n          'getFilterValue',\n          'getFiltered',\n          'getLineColor',\n          'getLineWidth',\n          'getPointRadius'\n        ];\n        const expectedLayerMeta = updatedLayerV2.meta;\n        const expectedDataToFeature = updatedLayerV2.dataToFeature;\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        t.deepEqual(\n          // by a_zip\n          // domain [7014, 7416]\n          // [7016, 7029] -> [0, 500]\n          layerData.data.map(layerData.getElevation),\n          [(2 / 402) * 500, (15 / 402) * 500],\n          'getElevation should return correct value'\n        );\n        t.deepEqual(\n          // by c_zip_type\n          // 'C_Medium_High' null  'A_Low_Rural',\n          layerData.data.map(layerData.getFillColor),\n          [\n            [2, 2, 2],\n            [2, 2, 2]\n          ],\n          'getFillColor should return correct value'\n        );\n\n        t.deepEqual(\n          // by m_rate\n          // 7.5 null 10\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [11.8 - geoFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return correct value'\n        );\n        t.deepEqual(\n          // by c_zip_type\n          // 'C_Medium_High' null  'A_Low_Rural',\n          layerData.data.map(layerData.getLineColor),\n          [\n            [5, 5, 5],\n            [5, 5, 5]\n          ],\n          'getLineColor should return correct value'\n        );\n        t.deepEqual(\n          // c_number\n          // domain [13.8, 29.2]\n          // range [0, 10]\n          // 27.6, 39.1 -> [0, 10]\n          layerData.data.map(layerData.getLineWidth),\n          [8.961038961038962, 16.42857142857143],\n          'getLineWidth should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getPointRadius),\n          [1, 1],\n          'getPointRadius should return correct value'\n        );\n        // meta\n        t.deepEqual(layer.meta, expectedLayerMeta, 'should format correct geojson layerData');\n        // dataToFeature\n        t.deepEqual(\n          layer.dataToFeature,\n          expectedDataToFeature,\n          'should format correct geojson layerData'\n        );\n      }\n    },\n    {\n      name: 'Geojson wkt polygon.3',\n      layer: {\n        type: 'geojson',\n        id: 'test_geojson_layer_3',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns: {\n            geojson: '_geojson'\n          },\n          color: [5, 5, 5]\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(prepareGeojsonDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            updatedGeoJsonLayer.dataToFeature[0],\n            updatedGeoJsonLayer.dataToFeature[2],\n            updatedGeoJsonLayer.dataToFeature[4]\n          ]\n        };\n        const expectedDataKeys = [\n          'data',\n          'getElevation',\n          'getFillColor',\n          'getFilterValue',\n          'getFiltered',\n          'getLineColor',\n          'getLineWidth',\n          'getPointRadius'\n        ];\n        const expectedLayerMeta = updatedGeoJsonLayer.meta;\n        const expectedDataToFeature = updatedGeoJsonLayer.dataToFeature;\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 7 keys'\n        );\n        // // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getElevation),\n          [defaultElevation, defaultElevation, defaultElevation],\n          'getElevation should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [\n            [5, 5, 5],\n            [5, 5, 5],\n            [5, 5, 5]\n          ],\n          'getFillColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [11 - geojsonFilterDomain0, 0, 0, 0],\n            [20 - geojsonFilterDomain0, 0, 0, 0],\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0]\n          ],\n          'getFilterValue should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineColor),\n          [\n            [5, 5, 5],\n            [5, 5, 5],\n            [5, 5, 5]\n          ],\n          'getLineColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineWidth),\n          [1, 1, 1],\n          'getLineWidth should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getPointRadius),\n          [defaultLineWidth, defaultLineWidth, defaultLineWidth],\n          'getPointRadius should return correct value'\n        );\n\n        // meta\n        t.deepEqual(layer.meta, expectedLayerMeta, 'should format correct geojson layer meta');\n        // dataToFeature\n        t.deepEqual(\n          layer.dataToFeature,\n          expectedDataToFeature,\n          'should format correct geojson layer dataToFeature'\n        );\n      }\n    },\n    // test case 3\n    {\n      name: 'Geojson with style properties',\n      layer: {\n        type: 'geojson',\n        id: 'test_geojson_layer_4',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns: {\n            geojson: '_geojson'\n          },\n          color: [5, 5, 5]\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: processGeojson(geoJsonWithStyle)\n      }),\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: geoStyleDataToFeature\n        };\n        const expectedDataKeys = [\n          'data',\n          'getElevation',\n          'getFillColor',\n          'getFilterValue',\n          'getFiltered',\n          'getLineColor',\n          'getLineWidth',\n          'getPointRadius'\n        ];\n        const expectedLayerMeta = geoStyleMeta;\n\n        const expectedDataToFeature = geoStyleDataToFeature;\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 7 keys'\n        );\n        // // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getElevation),\n          [10, 10, 10],\n          'getElevation should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [\n            [1, 2, 3],\n            [7, 8, 9],\n            [1, 2, 3]\n          ],\n          'getFillColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [0, 0, 0, 0],\n            [0, 0, 0, 0],\n            [0, 0, 0, 0]\n          ],\n          'getFilterValue should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineColor),\n          [\n            [4, 5, 6],\n            [4, 5, 6],\n            [4, 5, 6]\n          ],\n          'getLineColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getLineWidth),\n          [1, 3, 4],\n          'getLineWidth should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getPointRadius),\n          [5, 5, 5],\n          'getPointRadius should return correct value'\n        );\n\n        // meta\n        t.deepEqual(layer.meta, expectedLayerMeta, 'should format correct geojson layer meta');\n        // dataToFeature\n        t.deepEqual(\n          layer.dataToFeature,\n          expectedDataToFeature,\n          'should format correct geojson layer dataToFeature'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, GeojsonLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#GeojsonLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n  const TEST_CASES = [\n    {\n      name: 'Test render geojson.1',\n      layer: {\n        id: 'test_layer_1',\n        type: 'geojson',\n        config: {\n          dataId,\n          color: [1, 2, 3],\n          label: 'gps point',\n          columns: {\n            geojson: '_geojson'\n          },\n          isVisible: true,\n          visConfig: {\n            strokeColor: [4, 5, 6],\n            strokeOpacity: 0.1\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...prepareGeojsonDataset,\n          filteredIndex\n        }\n      },\n      assert: deckLayers => {\n        const ids = ['test_layer_1', 'test_layer_1-polygons-fill', 'test_layer_1-polygons-stroke'];\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ids,\n          'Should render 3 deck layers'\n        );\n        // polygon fill attributes;\n        const {attributes} = deckLayers[1].state.attributeManager;\n        const indices = attributes.indices.value;\n\n        const {props: fillLayerProp} = deckLayers[1];\n        const {props: strokeLayerProp} = deckLayers[2];\n\n        const expectedFillLayerProp = {\n          extruded: false,\n          elevationScale: 5,\n          filled: false,\n          wireframe: false,\n          opacity: 0.8,\n          parameters: {depthTest: false},\n          visible: true,\n          autoHighlight: false,\n          wrapLongitude: false,\n          id: 'test_layer_1-polygons-fill',\n          filterRange: [\n            [0, 8],\n            [0, 0],\n            [0, 0],\n            [0, 0]\n          ]\n        };\n\n        const expectedStrokeLayerProp = {\n          widthScale: 128,\n          jointRounded: false,\n          capRounded: false,\n          miterLimit: 2,\n          opacity: 0.1,\n          visible: true,\n          wrapLongitude: false,\n          id: 'test_layer_1-polygons-stroke'\n        };\n\n        Object.keys(expectedFillLayerProp).forEach(key => {\n          t.deepEqual(\n            fillLayerProp[key],\n            expectedFillLayerProp[key],\n            `should have correct fillLayerProp.${key}`\n          );\n        });\n\n        Object.keys(expectedStrokeLayerProp).forEach(key => {\n          t.deepEqual(\n            strokeLayerProp[key],\n            expectedStrokeLayerProp[key],\n            `should have correct strokeLayerProp.${key}`\n          );\n        });\n\n        // test instanceFilterValues\n        const expectedFilterValues = new Float32Array([\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 0\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 4\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 8\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 12\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 16\n          11 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 20\n          20 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 24\n          20 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 28\n          20 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 32\n          20 - geojsonFilterDomain0,\n          0,\n          0,\n          0, // 36\n          Number.MIN_SAFE_INTEGER,\n          0,\n          0,\n          0, // 40\n          Number.MIN_SAFE_INTEGER,\n          0,\n          0,\n          0, // 44\n          Number.MIN_SAFE_INTEGER,\n          0,\n          0,\n          0, // 48\n          Number.MIN_SAFE_INTEGER,\n          0,\n          0,\n          0 // 52\n        ]);\n        t.deepEqual(\n          attributes.filterValues.value.slice(0, (indices.length - 1) * 4),\n          expectedFilterValues,\n          'Should have correct filterValues'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, GeojsonLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/grid-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport sinon from 'sinon';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta\n} from 'test/helpers/layer-utils';\n\nimport {pointToPolygonGeo, KeplerGlLayers} from '@kepler.gl/layers';\nconst {GridLayer} = KeplerGlLayers;\nimport {INITIAL_MAP_STATE} from '@kepler.gl/reducers';\n\nconst columns = {\n  lat: 'lat',\n  lng: 'lng'\n};\nconst filteredIndex = [0, 1, 2, 4, 5, 7];\n\nconst expectedGridCellData = [\n  {\n    index: 0,\n    position: [-122.59661271087748, 37.743177277521255],\n    count: 2,\n    points: [\n      {\n        source: {\n          index: 0\n        },\n        index: 0\n      },\n      {\n        source: {\n          index: 1\n        },\n        index: 1\n      }\n    ],\n    lonIdx: 253,\n    latIdx: 711,\n    filteredPoints: []\n  },\n  {\n    index: 1,\n    position: [-122.14283099317691, 37.38384344551697],\n    count: 2,\n    points: [\n      {\n        source: {\n          index: 4\n        },\n        index: 2\n      },\n      {\n        source: {\n          index: 5\n        },\n        index: 3\n      }\n    ],\n    lonIdx: 255,\n    latIdx: 709,\n    filteredPoints: [\n      {\n        source: {\n          index: 4\n        },\n        index: 2\n      },\n      {\n        source: {\n          index: 5\n        },\n        index: 3\n      }\n    ]\n  },\n  {\n    index: 2,\n    position: [-122.3697218520272, 37.743177277521255],\n    count: 1,\n    points: [\n      {\n        source: {\n          index: 7\n        },\n\n        index: 4\n      }\n    ],\n    lonIdx: 254,\n    latIdx: 711,\n    filteredPoints: [\n      {\n        source: {\n          index: 7\n        },\n        index: 4\n      }\n    ]\n  }\n];\n\ntest('#GridLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'taro',\n          isVisible: true,\n          label: 'test grid layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'taro', 'gridLayer dataId should be correct');\n          t.ok(layer.type === 'grid', 'type should be grid');\n          t.ok(layer.isAggregated === true, 'gridLayer is aggregated');\n          t.ok(layer.config.label === 'test grid layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, GridLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#GridLayer -> formatLayerData', t => {\n  const TEST_CASES = [\n    {\n      name: 'Grid gps point.1',\n      layer: {\n        type: 'grid',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3]\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n        t.deepEqual(layerData.data, expectedLayerData.data, 'should format correct grid layerData');\n        // test getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1]],\n          'getPosition should return correct position'\n        );\n        // test getColorValue  [1474071095000, 1474071608000]\n        // 0: Null - 0\n        // 1: 2016-09-17 00:10:56 1474071056000 - 0\n        // 4: 2016-09-17 00:14:00 1474071240000 - 1\n        // 5: 2016-09-17 00:15:01 1474071301000 - 1\n        // 7: 2016-09-17 00:17:05 1474071425000 - 1\n        // test layer.meta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct grid layer meta');\n      }\n    },\n    {\n      name: 'Grid gps point.2',\n      layer: {\n        type: 'grid',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, GridLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#GridLayer -> renderLayer', t => {\n  const spyLayerCallbacks = sinon.spy();\n\n  const TEST_CASES = [\n    {\n      name: 'Grid gps point.1',\n      layer: {\n        type: 'grid',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            }\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        layerCallbacks: {\n          onSetLayerDomain: spyLayerCallbacks\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_1', 'test_layer_1-grid-cell'],\n          'Should create 2 deck.gl layers'\n        );\n        const [cpuGridLayer, gridCellLayer] = deckLayers;\n        const {props} = cpuGridLayer;\n        const gridCellLayerProp = gridCellLayer.props;\n        const {attributes} = gridCellLayer.state.attributeManager;\n        const {instanceFillColors, instancePositions, instanceElevations} = attributes;\n\n        const expectedProps = {\n          coverage: layer.config.visConfig.coverage,\n          cellSize: layer.config.visConfig.worldUnitSize * 1000,\n          colorRange: [\n            [1, 1, 1],\n            [2, 2, 2],\n            [3, 3, 3]\n          ],\n          colorScaleType: layer.config.colorScale,\n          elevationScaleType: layer.config.sizeScale,\n          elevationScale: layer.config.visConfig.elevationScale,\n          upperPercentile: layer.config.visConfig.percentile[1],\n          lowerPercentile: layer.config.visConfig.percentile[0]\n        };\n\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n\n        // filterRange [39000, 552000]\n        // filter.domain [1474071056000, 1474071489000]\n        const expectedColorBins = [\n          // i = 0 is filtered out because empty\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n\n        const expectedElevationBins = [\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n\n        t.deepEqual(\n          gridCellLayerProp.data.length,\n          expectedGridCellData.length,\n          'should pass correct data to grid cell layer'\n        );\n        gridCellLayerProp.data.forEach((ac, i) => {\n          t.deepEqual(\n            gridCellLayerProp.data[i],\n            expectedGridCellData[i],\n            `should pass correct data:${i} to grid cell layer`\n          );\n        });\n\n        const expectedLayerDomain = {\n          domain: [1, 2],\n          aggregatedBins: {1: {i: 1, value: 2, counts: 2}, 2: {i: 2, value: 1, counts: 1}}\n        };\n        t.deepEqual(\n          spyLayerCallbacks.args[0][0],\n          expectedLayerDomain,\n          'should call onSetLayerDomain with correct domain'\n        );\n\n        t.deepEqual(\n          cpuGridLayer.state.aggregatorState.dimensions.fillColor.sortedBins.sortedBins,\n          expectedColorBins,\n          'should create correct color bins'\n        );\n\n        t.deepEqual(\n          cpuGridLayer.state.aggregatorState.dimensions.elevation.sortedBins.sortedBins,\n          expectedElevationBins,\n          'should create correct elevation bins'\n        );\n\n        // instancePositions\n        t.deepEqual(\n          instancePositions.value.slice(0, 12),\n          // position of each bin\n          [\n            -122.59661271087748, 37.743177277521255, 0, -122.14283099317691, 37.38384344551697, 0,\n            -122.3697218520272, 37.743177277521255, 0, 0, 0, 0\n          ],\n          'should create correct attribute.instanceFillColors'\n        );\n        // instanceFillColors\n        t.deepEqual(\n          instanceFillColors.value.slice(0, 16),\n          // color by filtered points count: [0, 2, 1]\n          [0, 0, 0, 0, 3, 3, 3, 255, 1, 1, 1, 255, 0, 0, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n        // instanceElevations\n        t.deepEqual(\n          instanceElevations.value.slice(0, 4),\n          // elevation by filtered points count: [0, 2, 1], range: [0, 500]\n          [-1, 500, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n      }\n    },\n    {\n      name: 'Grid gps point.color by',\n      layer: {\n        type: 'grid',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          colorField: {\n            name: 'trip_distance',\n            type: 'real'\n          },\n          colorScale: 'quantize',\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            colorAggregation: 'maximum'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        layerCallbacks: {\n          onSetLayerDomain: spyLayerCallbacks\n        }\n      },\n      assert: deckLayers => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_2', 'test_layer_2-grid-cell'],\n          'Should create 2 deck.gl layers'\n        );\n        const [cpuGridLayer, gridCellLayer] = deckLayers;\n        const {props} = cpuGridLayer;\n        const gridCellLayerProp = gridCellLayer.props;\n        const {attributes} = gridCellLayer.state.attributeManager;\n        const {instanceFillColors} = attributes;\n\n        t.equal(props.colorScaleType, 'quantize', 'should pass colorScaleType');\n\n        t.deepEqual(\n          gridCellLayerProp.data.length,\n          expectedGridCellData.length,\n          'should pass correct data to grid cell layer'\n        );\n        gridCellLayerProp.data.forEach((ac, i) => {\n          t.deepEqual(\n            gridCellLayerProp.data[i],\n            expectedGridCellData[i],\n            `should pass correct data:${i} to grid cell layer`\n          );\n        });\n        const expectedColorBins = [\n          // i = 0 is filtered out because empty\n          // bins are sorted\n          {i: 1, value: 7.13, counts: 2},\n          {i: 2, value: 11, counts: 1}\n        ];\n        const expectedElevationBins = [\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n        const expectedLayerDomain = {\n          domain: [7.13, 11],\n          aggregatedBins: {1: {i: 1, value: 7.13, counts: 2}, 2: {i: 2, value: 11, counts: 1}}\n        };\n        t.deepEqual(\n          spyLayerCallbacks.args[1][0],\n          expectedLayerDomain,\n          'should call onSetLayerDomain with correct domain'\n        );\n        t.deepEqual(\n          cpuGridLayer.state.aggregatorState.dimensions.fillColor.sortedBins.sortedBins,\n          expectedColorBins,\n          'should create correct color bins'\n        );\n\n        t.deepEqual(\n          cpuGridLayer.state.aggregatorState.dimensions.elevation.sortedBins.sortedBins,\n          expectedElevationBins,\n          'should create correct elevation bins'\n        );\n\n        // instanceFillColors\n        t.deepEqual(\n          instanceFillColors.value.slice(0, 16),\n          // color by filtered points color value: [0, 7.13, 11]\n          [0, 0, 0, 0, 1, 1, 1, 255, 3, 3, 3, 255, 0, 0, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, GridLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#GridLayer -> pointToPolygonGeo', t => {\n  const polygonGeo = pointToPolygonGeo({\n    object: {\n      position: [-122.39096, 37.769897]\n    },\n    cellSize: 20000,\n    coverage: 1,\n    properties: {\n      name: 'a'\n    },\n    mapState: INITIAL_MAP_STATE\n  });\n\n  const expected = {\n    geometry: {\n      coordinates: [\n        [-122.39095999999999, 37.769897000000014],\n        [-122.1634200224827, 37.769897000000014],\n        [-122.16286655375738, 37.94954323177784],\n        [-122.39095999999999, 37.94954323177784],\n        [-122.39095999999999, 37.769897000000014]\n      ],\n      type: 'LineString'\n    },\n    properties: {name: 'a'}\n  };\n\n  t.deepEqual(polygonGeo, expected, 'should create correct polygonGeo');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/h3-hexagon-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  preparedFilterDomain0,\n  hexagonIdLayerMeta\n} from 'test/helpers/layer-utils';\nimport {KeplerGlLayers, h3DefaultElevation as defaultElevation} from '@kepler.gl/layers';\nimport {getCentroid, idToPolygonGeo} from '@kepler.gl/common-utils';\n\nimport {copyTableAndUpdate} from '@kepler.gl/table';\n\nconst {H3Layer} = KeplerGlLayers;\nconst columns = {\n  lat: 'lat',\n  lng: 'lng',\n  hex_id: 'hex_id'\n};\n\ntest('#H3Layer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test h3 layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'H3Layer dataId should be correct');\n          t.ok(layer.type === 'hexagonId', 'type should be h3');\n          t.ok(layer.isAggregated === false, 'H3Layer is not aggregated');\n          t.ok(layer.config.label === 'test h3 layer', 'label should be correct');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, H3Layer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#H3Layer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'h3.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point h3',\n          columns,\n          color: [2, 3, 4]\n        },\n        type: 'hexagonId',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              id: '89283082c2fffff',\n              centroid: getCentroid({id: '89283082c2fffff'})\n            },\n            {\n              index: 4,\n              id: '89283082c3bffff',\n              centroid: getCentroid({id: '89283082c3bffff'})\n            }\n          ],\n          getElevation: () => {},\n          getFilterValue: () => {},\n          getFillColor: () => {},\n          getLineColor: () => {},\n          getHexId: () => {},\n          getCoverage: () => {},\n          getPosition: () => {},\n          textLabels: () => {}\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          `layerData should have ${expectedLayerData.length} keys`\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        // getFillColor\n        t.deepEqual(layerData.getFillColor, [2, 3, 4], 'getFillColor should be a constant');\n        // getLineColor\n        t.deepEqual(layerData.getLineColor, [2, 3, 4], 'getLineColor should be a constant');\n        // getElevation\n        t.deepEqual(layerData.getElevation, defaultElevation, 'getElevation should be a constant');\n        // getHexId\n        t.deepEqual(\n          layerData.data.map(layerData.getHexId),\n          ['89283082c2fffff', '89283082c3bffff'],\n          'getHexId should return correct hex id'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n\n        // layerMeta\n        t.deepEqual(layer.meta, hexagonIdLayerMeta, 'should format correct point layer meta');\n      }\n    },\n    {\n      name: 'H3 layer format data. with colorField, strokeColorField, and sizeField',\n      layer: {\n        config: {\n          dataId,\n          label: 'h3.2',\n          columns,\n          color: [10, 10, 10],\n          // fill color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // stroke color by types(string)\n          strokeColorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            strokeColorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            elevationRange: [10, 20],\n            enable3d: true\n          }\n        },\n        type: 'hexagonId',\n        id: 'test_layer_2'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData} = result;\n\n        // getSourceColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getFillColor should be correct'\n        );\n\n        t.deepEqual(\n          layerData.data.map(layerData.getLineColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getLineColor should be correct'\n        );\n\n        // getElevation\n        // domain: [1.59, 11]\n        // range: [0, 500]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getElevation),\n          [0, 41.445270988310305],\n          'getElevation should correct'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, H3Layer, TEST_CASES);\n  t.end();\n});\n\ntest('#H3Layer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Test render h3.2',\n      layer: {\n        id: 'test_layer_1',\n        type: 'hexagonId',\n        config: {\n          dataId,\n          label: 'h3 hex',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 0.5,\n            elevationScale: 5\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 'hexagonId', 'should create 1 hexagonId layer');\n        t.equal(deckLayers.length, 2, 'Should create 2 deck.gl layers');\n        const expectedLayerIds = ['test_layer_1', 'test_layer_1-hexagon-cell'];\n\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          expectedLayerIds,\n          'should create 1 composite, 1 hexagon-cell layer'\n        );\n\n        const {props} = deckLayers[0];\n\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          filterRange: preparedDataset.gpuFilter.filterRange,\n          wrapLongitude: false,\n          coverage: 1,\n          autoHighlight: false,\n          highlightColor: [255, 255, 255, 60],\n          extruded: false,\n          elevationScale: 5,\n          filled: true,\n          stroked: false\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    },\n    {\n      name: 'Test render h3.2 with text label',\n      layer: {\n        id: 'test_layer_2',\n        type: 'hexagonId',\n        config: {\n          dataId,\n          label: 'h3 hex',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 0.5,\n            elevationScale: 5\n          },\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                format: ''\n              },\n              format: ''\n            }\n          ]\n        }\n      },\n      datasets: {\n        [dataId]: preparedDataset\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 'hexagonId', 'should create 1 hexagonId layer');\n\n        const expectedTextLabels = [\n          {\n            field: {\n              name: 'types',\n              id: 'types',\n              displayName: 'types',\n              format: '',\n              fieldIdx: 5,\n              type: 'string',\n              analyzerType: 'STRING',\n              valueAccessor: preparedDataset.fields[5].valueAccessor\n            },\n            color: [255, 255, 255],\n            size: 18,\n            offset: [0, 0],\n            anchor: 'middle',\n            alignment: 'center',\n            outlineWidth: 0,\n            outlineColor: [255, 0, 0, 255],\n            background: false,\n            backgroundColor: [0, 0, 200, 255]\n          }\n        ];\n\n        t.deepEqual(\n          layer.config.textLabel,\n          expectedTextLabels,\n          'should create textLabel using field \"types\"'\n        );\n\n        t.equal(deckLayers.length, 4, 'Should create 4 deck.gl layers');\n        const expectedLayerIds = [\n          'test_layer_2',\n          'test_layer_2-hexagon-cell',\n          'test_layer_2-label-types',\n          'test_layer_2-label-types-characters'\n        ];\n\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          expectedLayerIds,\n          'should create 1 composite, 1 hexagon-cell layer, 1 text layer, 1 multi-icon layer'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, H3Layer, TEST_CASES);\n  t.end();\n});\n\ntest('#H3Layer -> idToPolygonGeo', t => {\n  const h3Object = {\n    index: 411,\n    id: '882a100d01fffff'\n  };\n\n  const hexagonOutline = idToPolygonGeo(h3Object);\n  const expectedCoordinates = [\n    [-73.9556604529423, 40.747207768842344],\n    [-73.96203986286912, 40.746155567377926],\n    [-73.96400718290255, 40.74169679631741],\n    [-73.95959600220597, 40.73829055843986],\n    [-73.95321761814058, 40.73934254051138],\n    [-73.9512493890226, 40.74380097982341],\n    [-73.9556604529423, 40.747207768842344]\n  ];\n\n  const expectedHexagonOutline = {\n    type: 'Feature',\n    geometry: {\n      coordinates: expectedCoordinates,\n      type: 'LineString'\n    },\n    properties: undefined\n  };\n  t.deepEqual(\n    hexagonOutline,\n    expectedHexagonOutline,\n    'should generate geojson object of hexagon outline (LineString)'\n  );\n\n  const properties = {isClosed: true};\n  const hexagonPolygon = idToPolygonGeo(h3Object, properties);\n\n  const expectedHexagonPolygon = {\n    type: 'Feature',\n    geometry: {\n      coordinates: [expectedCoordinates],\n      type: 'Polygon'\n    },\n    properties: {isClosed: true}\n  };\n  t.deepEqual(\n    hexagonPolygon,\n    expectedHexagonPolygon,\n    'should generate geojson object of hexagon outline (LineString)'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/heatmap-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  preparedDataset,\n  dataId,\n  pointLayerMeta\n} from 'test/helpers/layer-utils';\nimport {StateWFiles, testCsvDataId} from 'test/helpers/mock-state';\nimport {gpsPointBounds} from 'test/fixtures/test-csv-data';\n\nimport {MAX_ZOOM_LEVEL, KeplerGlLayers} from '@kepler.gl/layers';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\n\nconst {HeatmapLayer} = KeplerGlLayers;\n\nconst columns = {\n  lat: 'lat',\n  lng: 'lng'\n};\n\ntest('#HeatmapLayer -> contructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'taro',\n          isVisible: true,\n          label: 'test heatmap layer'\n        },\n        test: layer => {\n          // test constructor\n          t.equal(layer.config.visConfig.radius, 20, 'Heatmap default radius should be 20');\n          t.ok(layer.config.dataId === 'taro', 'heatmaplayer dataId should be correct');\n          t.ok(layer.type === 'heatmap', 'type should be heatmap');\n          t.ok(layer.isAggregated === true, 'heatmaplayer is aggregated');\n          t.ok(layer.config.label === 'test heatmap layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, HeatmapLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#Heatmaplayer -> formatLayerData -> w/ GpuFilter', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const expectedConfig = {\n    type: 'heatmap',\n    id: 'heatmap-test-1',\n    source: `${dataId}-points-1-2--1`,\n    layout: {visibility: 'visible'},\n    filter: ['all', ['>=', 'gpu:utc_timestamp', 39000], ['<=', 'gpu:utc_timestamp', 552000]],\n    paint: {\n      'heatmap-weight': ['interpolate', ['linear'], ['get', 'id'], 1, 0, 345, 1],\n      'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, 18, 3],\n      'heatmap-color': [\n        'interpolate',\n        ['linear'],\n        ['heatmap-density'],\n        0,\n        'rgba(0,0,0,0)',\n        0.14285714285714285,\n        'rgb(76,0,53)',\n        0.2857142857142857,\n        'rgb(136,0,48)',\n        0.42857142857142855,\n        'rgb(183,47,21)',\n        0.5714285714285714,\n        'rgb(214,97,10)',\n        0.7142857142857143,\n        'rgb(239,145,0)',\n        0.8571428571428571,\n        'rgb(255,195,0)'\n      ],\n      'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 0, 2, 18, 20],\n      'heatmap-opacity': 0.8\n    }\n  };\n\n  const TEST_CASES = [\n    {\n      name: 'Heatmap gps point.1',\n      layer: {\n        type: 'heatmap',\n        id: 'heatmap-test-1',\n        config: {\n          dataId,\n          label: 'mapbox heatmap',\n          isVisible: true,\n          columns,\n          weightField: {\n            type: 'integer',\n            name: 'id'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          columns: {\n            lat: {value: 'lat', fieldIdx: 1},\n            lng: {value: 'lng', fieldIdx: 2},\n            geoarrow: {value: null, fieldIdx: -1}\n          },\n          config: expectedConfig,\n          data: {\n            type: 'FeatureCollection',\n            features: [\n              {\n                type: 'Feature',\n                properties: {\n                  index: 0,\n                  'gpu:utc_timestamp': Number.MIN_SAFE_INTEGER,\n                  id: 1\n                },\n                geometry: {type: 'Point', coordinates: [-122.39096, 37.778564]}\n              },\n              {\n                type: 'Feature',\n                properties: {\n                  index: 4,\n                  'gpu:utc_timestamp': 184000,\n                  id: 5\n                },\n                geometry: {type: 'Point', coordinates: [-122.136795, 37.456535]}\n              }\n            ]\n          },\n          weightField: null,\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'lheatmap ayerData should have correct keys'\n        );\n        t.deepEqual(\n          layerData.columns,\n          expectedLayerData.columns,\n          'should format correct heatmap layerData.columns'\n        );\n\n        // test data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct heatmap layerData.data'\n        );\n\n        // test columns,\n        expectedLayerData.config.id = layer.id;\n        // test config\n        t.deepEqual(\n          layerData.config,\n          expectedLayerData.config,\n          'should format correct heatmap layerData.config'\n        );\n\n        // test layer.meta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct heatmap layer.meta');\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, HeatmapLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#Heatmaplayer -> formatLayerData -> w/o GpuFilter', t => {\n  const testData = StateWFiles.visState.datasets[testCsvDataId];\n  const gpsColumns = {\n    lat: 'gps_data.lat',\n    lng: 'gps_data.lng'\n  };\n\n  const expectedConfig = {\n    type: 'heatmap',\n    id: 'heatmap-test-1',\n    source: `${testCsvDataId}-points-1-2--1`,\n    layout: {visibility: 'visible'},\n    paint: {\n      'heatmap-weight': 1,\n      'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, MAX_ZOOM_LEVEL, 3],\n      'heatmap-color': [\n        'interpolate',\n        ['linear'],\n        ['heatmap-density'],\n        0,\n        'rgba(0,0,0,0)',\n        0.25,\n        'rgb(1,1,1)',\n        0.5,\n        'rgb(2,2,2)',\n        0.75,\n        'rgb(3,3,3)'\n      ],\n      'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 0, 2, MAX_ZOOM_LEVEL, 100],\n      'heatmap-opacity': 0.2\n    }\n  };\n\n  const TEST_CASES = [\n    {\n      name: 'Heatmap gps point.1',\n      layer: {\n        type: 'heatmap',\n        id: 'heatmap-test-1',\n        config: {\n          dataId: testCsvDataId,\n          label: 'mapbox heatmap',\n          isVisible: true,\n          columns: gpsColumns,\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            radius: 100,\n            opacity: 0.2\n          }\n        }\n      },\n      datasets: StateWFiles.visState.datasets,\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          columns: {\n            lat: {value: 'gps_data.lat', fieldIdx: 1},\n            lng: {value: 'gps_data.lng', fieldIdx: 2},\n            geoarrow: {value: null, fieldIdx: -1}\n          },\n          config: expectedConfig,\n          weightField: null,\n          getPosition: () => {}\n        };\n        const expectedLayerMeta = {bounds: gpsPointBounds};\n\n        // test columns,\n        t.deepEqual(\n          layerData.columns,\n          expectedLayerData.columns,\n          'should format correct heatmap layerData.columns'\n        );\n\n        // test data\n        t.equal(\n          layerData.data.features.length,\n          testData.dataContainer.numRows(),\n          'should have same number of features'\n        );\n\n        t.deepEqual(\n          layerData.data.features[0],\n          {\n            type: 'Feature',\n            properties: {\n              index: 0\n            },\n            geometry: {type: 'Point', coordinates: [31.2590542, 29.9900937]}\n          },\n          'should format correct feature 0'\n        );\n        // test id\n        expectedLayerData.config.id = layer.id;\n\n        // test config\n        t.deepEqual(\n          layerData.config,\n          expectedLayerData.config,\n          'should format correct heatmap layerData.config'\n        );\n\n        // test layer.meta\n        t.deepEqual(layer.meta, expectedLayerMeta, 'should format correct heatmap layer.meta');\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, HeatmapLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/hexagon-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport sinon from 'sinon';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta\n} from 'test/helpers/layer-utils';\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nconst {HexagonLayer} = KeplerGlLayers;\n\nconst columns = {\n  lat: 'lat',\n  lng: 'lng'\n};\nconst {dataContainer} = preparedDataset;\nconst filteredIndex = [0, 1, 2, 4, 5, 7];\n\n// deckGl default pointToHexbin reads viewport set to width: 1 and height: 1 on initial render\nconst pt0 = {\n  screenCoord: [81.93285590277779, 314.10787407420145],\n  index: 0,\n  source: {\n    index: 0\n  }\n};\nconst pt1 = {\n  screenCoord: [81.90728081597221, 314.125282797666],\n  index: 1,\n  source: {\n    index: 1\n  }\n};\nconst pt4 = {\n  screenCoord: [82.29433593749998, 313.5296684059477],\n  index: 2,\n  source: {\n    index: 4\n  }\n};\nconst pt5 = {\n  screenCoord: [82.34327256944445, 313.4296004913215],\n  index: 3,\n  source: {\n    index: 5\n  }\n};\nconst pt7 = {\n  screenCoord: [82.11757812499998, 314.2888411459387],\n  index: 4,\n  source: {\n    index: 7\n  }\n};\n\nconst expectedHexCellData = [\n  {\n    position: [-122.56068191457787, 37.71853775731428],\n    points: [pt0, pt1],\n    index: 0,\n    filteredPoints: []\n  },\n  {\n    position: [-121.9705519342482, 37.44853622864796],\n    points: [pt4, pt5],\n    index: 1,\n    filteredPoints: [pt4, pt5]\n  },\n  {\n    position: [-122.36397192113466, 37.98755881236511],\n    points: [pt7],\n    index: 2,\n    filteredPoints: [pt7]\n  }\n];\n\n// assigned by d3-hexbin\nexpectedHexCellData[0].points.x = 81.69147461037814;\nexpectedHexCellData[0].points.y = 313.99990548499255;\nexpectedHexCellData[1].points.x = 82.53077058240257;\nexpectedHexCellData[1].points.y = 313.5153377296145;\nexpectedHexCellData[2].points.x = 81.97123993438628;\nexpectedHexCellData[2].points.y = 314.4844732403706;\n\ntest('#HexagonLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'blue',\n          isVisible: true,\n          label: 'test hexagon layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'blue', 'HexagonLayer dataId should be correct');\n          t.ok(layer.type === 'hexagon', 'type should be hexagon');\n          t.ok(layer.isAggregated === true, 'HexagonLayer is aggregated');\n          t.ok(layer.config.label === 'test hexagon layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, HexagonLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#HexagonLayer -> formatLayerData', t => {\n  const TEST_CASES = [\n    {\n      name: 'hexagon layer gps point.1',\n      layer: {\n        type: 'hexagon',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3]\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct hexagon layerData'\n        );\n        // test getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1]],\n          'getPosition should return correct position'\n        );\n        // test getColorValue  [1474071095000, 1474071608000]\n        // 0: Null - 0\n        // 1: 2016-09-17 00:10:56 1474071056000 - 0\n        // 4: 2016-09-17 00:14:00 1474071240000 - 1\n        // 5: 2016-09-17 00:15:01 1474071301000 - 1\n        // 7: 2016-09-17 00:17:05 1474071425000 - 1\n        // test layer.meta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct hexagon layer meta');\n      }\n    },\n    {\n      name: 'Hexagon layer gps point.2',\n      layer: {\n        type: 'hexagon',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [0, 1, 4, 5, 7].map(index => ({\n            index\n          })),\n          _filterData: () => {},\n          getColorValue: () => {},\n          getElevationValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 4 keys'\n        );\n        // test getColorValue aggregate by mode\n        // 0: driver_analytics_0 - 0\n        // 1: null  - 0\n        // 4: driver_analytics - 1\n        // 5: driver_analytics - 1\n        // 7: driver_analytics - 1\n\n        // test getColorValue aggregate by avg\n        // 0: 1.59 - 0\n        // 1: 2.38  - 0\n        // 4: 2.37 - 1\n        // 5: 7.13 - 1\n        // 7: 11 - 1\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, HexagonLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#HexagonLayer -> renderLayer', t => {\n  const spyLayerCallbacks = sinon.spy();\n\n  const TEST_CASES = [\n    {\n      name: 'Hexagon gps point.1',\n      layer: {\n        type: 'hexagon',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            }\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        layerCallbacks: {\n          onSetLayerDomain: spyLayerCallbacks\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_1', 'test_layer_1-hexagon-cell'],\n          'Should create 2 deck.gl layers'\n        );\n\n        const [deckHexLayer, hexCellLayer] = deckLayers;\n        const {props, state} = deckHexLayer;\n        const {attributes} = hexCellLayer.state.attributeManager;\n        const {instanceFillColors, instanceElevations} = attributes;\n\n        const expectedProps = {\n          coverage: layer.config.visConfig.coverage,\n          radius: layer.config.visConfig.worldUnitSize * 1000,\n          colorRange: [\n            [1, 1, 1],\n            [2, 2, 2],\n            [3, 3, 3]\n          ],\n          colorScaleType: layer.config.colorScale,\n          elevationScaleType: layer.config.sizeScale,\n          elevationScale: layer.config.visConfig.elevationScale,\n          upperPercentile: layer.config.visConfig.percentile[1],\n          lowerPercentile: layer.config.visConfig.percentile[0]\n        };\n\n        const expectedColorBins = [\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n        const expectedElevationBins = [\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n\n        t.deepEqual(\n          hexCellLayer.props.data,\n          expectedHexCellData,\n          'should pass correct data to hexagon cell layer'\n        );\n        expectedHexCellData.forEach((d, i) => {\n          t.deepEqual(\n            hexCellLayer.props.data[i],\n            expectedHexCellData[i],\n            'should pass correct data to hexagon cell layer'\n          );\n        });\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n        const expectedLayerDomain = {\n          domain: [1, 2],\n          aggregatedBins: {1: {i: 1, value: 2, counts: 2}, 2: {i: 2, value: 1, counts: 1}}\n        };\n        t.deepEqual(\n          spyLayerCallbacks.args[0][0],\n          expectedLayerDomain,\n          'should call onSetLayerDomain with correct domain'\n        );\n\n        t.deepEqual(\n          state.aggregatorState.dimensions.fillColor.sortedBins.sortedBins,\n          expectedColorBins,\n          'should create correct color bins'\n        );\n\n        t.deepEqual(\n          state.aggregatorState.dimensions.elevation.sortedBins.sortedBins,\n          expectedElevationBins,\n          'should create correct elevation bins'\n        );\n        // instanceFillColors\n        t.deepEqual(\n          instanceFillColors.value.slice(0, 16),\n          // color by filtered points count: [0, 2, 1]\n          [0, 0, 0, 0, 3, 3, 3, 255, 1, 1, 1, 255, 0, 0, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n        // instanceElevations\n        t.deepEqual(\n          instanceElevations.value.slice(0, 4),\n          // elevation by filtered points count: [0, 2, 1], range: [0, 500]\n          [-1, 500, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n      }\n    },\n    {\n      name: 'Hexagon gps point.1',\n      layer: {\n        type: 'hexagon',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          colorField: {\n            name: 'trip_distance',\n            type: 'real'\n          },\n          colorScale: 'quantize',\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            colorAggregation: 'maximum'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        layerCallbacks: {\n          onSetLayerDomain: spyLayerCallbacks\n        }\n      },\n      assert: deckLayers => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_1', 'test_layer_1-hexagon-cell'],\n          'Should create 2 deck.gl layers'\n        );\n\n        const [deckHexLayer, hexCellLayer] = deckLayers;\n        const {props, state} = deckHexLayer;\n        const {attributes} = hexCellLayer.state.attributeManager;\n        const {instanceFillColors} = attributes;\n        t.equal(props.colorScaleType, 'quantize', 'should pass colorScaleType');\n        t.deepEqual(\n          hexCellLayer.props.data,\n          expectedHexCellData,\n          'should pass correct data to hexagon cell layer'\n        );\n        expectedHexCellData.forEach((d, i) => {\n          t.deepEqual(\n            hexCellLayer.props.data[i],\n            expectedHexCellData[i],\n            'should pass correct data to hexagon cell layer'\n          );\n        });\n\n        const expectedColorBins = [\n          // i = 0 is filtered out because empty\n          // bins are sorted\n          {i: 1, value: 7.13, counts: 2},\n          {i: 2, value: 11, counts: 1}\n        ];\n        const expectedElevationBins = [\n          {i: 2, value: 1, counts: 1},\n          {i: 1, value: 2, counts: 2}\n        ];\n        const expectedLayerDomain = {\n          domain: [7.13, 11],\n          aggregatedBins: {1: {i: 1, value: 7.13, counts: 2}, 2: {i: 2, value: 11, counts: 1}}\n        };\n        t.deepEqual(\n          spyLayerCallbacks.args[1][0],\n          expectedLayerDomain,\n          'should call onSetLayerDomain with correct domain'\n        );\n        t.deepEqual(\n          state.aggregatorState.dimensions.fillColor.sortedBins.sortedBins,\n          expectedColorBins,\n          'should create correct color bins'\n        );\n\n        t.deepEqual(\n          state.aggregatorState.dimensions.elevation.sortedBins.sortedBins,\n          expectedElevationBins,\n          'should create correct elevation bins'\n        );\n\n        // instanceFillColors\n        t.deepEqual(\n          instanceFillColors.value.slice(0, 16),\n          // color by filtered points color value: [0, 7.13, 11]\n          [0, 0, 0, 0, 1, 1, 1, 255, 3, 3, 3, 255, 0, 0, 0, 0],\n          'should create correct attribute.instanceFillColors'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, HexagonLayer, TEST_CASES);\n  t.end();\n});\n\nfunction creatLayerObjectHovered({layerId, data, object}) {\n  return {\n    color: [],\n    layer: {\n      props: {id: layerId}\n    },\n    index: data.index,\n    picked: true,\n    // mock\n    x: 200,\n    y: 200,\n    pixel: [200, 200],\n    coordinate: [-117.986, 34.173],\n    lngLat: [-117.986, 34.173],\n    devicePixel: [1449, 759],\n    pixelRatio: 2,\n    object,\n    handled: false\n  };\n}\n\ntest('#HexagonLayer -> renderHover', t => {\n  const testObjectHovered = creatLayerObjectHovered({\n    layerId: 'test_layer_1',\n    data: dataContainer.row(0),\n    object: {\n      colorValue: 1,\n      elevationValue: 1,\n      position: [-122.56068191457787, 37.71853775731428],\n      index: 0,\n      points: [{}, {}]\n    }\n  });\n\n  const TEST_CASES = [\n    {\n      name: 'Hexagon gps point.1',\n      layer: {\n        type: 'hexagon',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'some geometry file',\n          columns,\n          color: [1, 2, 3],\n          visConfig: {\n            worldUnitSize: 20,\n            colorRange: {\n              colors: ['#080808', '#090909', '#070707']\n            }\n          }\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        objectHovered: testObjectHovered\n      },\n      assert: deckLayers => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          [\n            'test_layer_1',\n            'test_layer_1-hexagon-cell',\n            'test_layer_1-hovered',\n            'test_layer_1-hovered-linestrings'\n          ],\n          'Should create 4 deck.gl layers'\n        );\n        const expectedHoverData = [\n          {\n            geometry: {\n              coordinates: [\n                [-122.36376320555154, 37.80841570626016],\n                [-122.56068191457787, 37.898184393157855],\n                [-122.75760062360423, 37.80841570626016],\n                [-122.75760062360423, 37.628550634764665],\n                [-122.56068191457787, 37.538454428239675],\n                [-122.36376320555154, 37.628550634764665],\n                [-122.36376320555154, 37.80841570626016]\n              ],\n              type: 'LineString'\n            },\n            properties: {}\n          }\n        ];\n\n        const hoverLayer = deckLayers[2];\n        t.deepEqual(\n          hoverLayer.props.data,\n          expectedHoverData,\n          'should send correct hover layer data'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, HexagonLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/icon-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport test from 'tape';\nimport React from 'react';\nimport {mount} from 'enzyme';\nimport sinon from 'sinon';\nimport sinonStubPromise from 'sinon-stub-promise';\nimport {getDistanceScales} from 'viewport-mercator-project';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {DEFAULT_TEXT_LABEL, PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\n\nsinonStubPromise(sinon);\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta,\n  iconGeometry\n} from 'test/helpers/layer-utils';\nimport {INITIAL_MAP_STATE} from '@kepler.gl/reducers';\nimport {IntlWrapper} from '../../helpers/component-utils';\n\nconst {IconLayer} = KeplerGlLayers;\nconst columns = {\n  lat: 'lat',\n  lng: 'lng',\n  icon: 'icon'\n};\n\nconst mockSvgIcons = [\n  {\n    id: 'alert',\n    mesh: {\n      positions: [\n        [1, -0.5, 0],\n        [0.9, -0.52, 0],\n        [0.97, -0.5, 0],\n        [0.3, -0.4, 0]\n      ],\n      cells: [\n        [0, 1, 3],\n        [1, 2, 3]\n      ]\n    }\n  }\n];\n\ntest('#IconLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test icon layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'IconLayer dataId should be correct');\n          t.ok(layer.type === 'icon', 'type should be icon');\n          t.ok(layer.isAggregated === false, 'IconLayer is not aggregated');\n          t.ok(layer.config.label === 'test icon layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n\n          // t.ok(spy.calledOnce, 'should call window.fetch once');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, IconLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#IconLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'gps point with icon.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point icon',\n          columns,\n          color: [2, 3, 4],\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                type: 'string'\n              }\n            },\n            {\n              field: {\n                name: 'has_result',\n                type: 'boolean'\n              }\n            }\n          ]\n        },\n        type: 'icon',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              icon: 'accel'\n            }\n          ],\n          textLabels: [\n            {\n              characterSet: [],\n              getText: () => {}\n            },\n            {\n              characterSet: [],\n              getText: () => {}\n            }\n          ],\n          getFilterValue: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {}\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 5 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1], 0],\n          'getPosition should return correct position'\n        );\n        // getFillColor\n        t.deepEqual(layerData.getFillColor, [2, 3, 4], 'getFillColor should be a constant');\n        // getRadius\n        t.equal(layerData.getRadius, 1, 'getRadius should be a constant');\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [[Number.MIN_SAFE_INTEGER, 0, 0, 0]],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n        // textLabels\n        t.deepEqual(\n          layerData.textLabels.length,\n          expectedLayerData.textLabels.length,\n          'textLabels should have 2 items'\n        );\n        t.deepEqual(\n          layerData.textLabels[0].characterSet,\n          ['d', 'r', 'i', 'v', 'e', '_', 'a', 'n', 'l', 'y', 't', 'c', 's', '0'],\n          'textLabels should have correct characterSet'\n        );\n        t.deepEqual(\n          layerData.textLabels[0].getText(layerData.data[0]),\n          'driver_analytics_0',\n          'textLabels getText should have correct text'\n        );\n        // layerMeta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct point layer meta');\n      }\n    },\n    {\n      name: 'Icon data. with colorField and sizeField',\n      layer: {\n        config: {\n          dataId,\n          label: 'icons',\n          columns,\n          color: [10, 10, 10],\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            radiusRange: [10, 20]\n          }\n        },\n        type: 'icon',\n        id: 'test_layer_2'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData} = result;\n\n        // getSourceColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [[2, 2, 2]],\n          'getFillColor should be correct'\n        );\n        // getRadius\n        // domain: [1.59, 11]\n        // range: [10, 20]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getRadius),\n          [10],\n          'getRadius should be a constant'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [[Number.MIN_SAFE_INTEGER, 0, 0, 0]],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, IconLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#IconLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4, 5];\n\n  const TEST_CASES = [\n    {\n      name: 'Test render icon.1 -> no icon Geometry',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point icon',\n          columns,\n          color: [2, 3, 4]\n        },\n        type: 'icon',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 'icon', 'should create 1 icon layer');\n        t.equal(\n          deckLayers.length,\n          3,\n          'Should create 3 deck.gl layer when default icon geometry is not provided or missing'\n        );\n      }\n    },\n    {\n      name: 'Test render icon.2 -> has icon geometry',\n      layer: {\n        id: 'test_layer_1',\n        type: 'icon',\n        config: {\n          dataId,\n          label: 'gps point icon',\n          columns,\n          color: [1, 2, 3]\n        }\n      },\n      afterLayerInitialized: layer => {\n        layer.iconGeometry = iconGeometry;\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 'icon', 'should create 1 icon layer');\n        t.equal(\n          deckLayers.length,\n          3,\n          'Should create 3 deck.gl layer when icon geometry is provided'\n        );\n        const expectedLayerIds = [\n          'test_layer_1_0',\n          'test_layer_1_0-accel',\n          'test_layer_1_0-attach'\n        ];\n\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          expectedLayerIds,\n          'should create 1 composite, 2 svg icon layer'\n        );\n\n        const {props} = deckLayers[0];\n\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          radiusMaxPixels: 500,\n          radiusScale: layer.getRadiusScaleByZoom(INITIAL_MAP_STATE),\n          filterRange: preparedDataset.gpuFilter.filterRange,\n          brushingEnabled: false\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    },\n    {\n      name: 'Test render icon.2 -> has icon geometry -> brushing',\n      layer: {\n        id: 'test_layer_1',\n        type: 'icon',\n        config: {\n          dataId,\n          label: 'gps point icon',\n          columns,\n          color: [1, 2, 3]\n        }\n      },\n      afterLayerInitialized: layer => {\n        layer.iconGeometry = iconGeometry;\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      renderArgs: {\n        interactionConfig: {\n          brush: {\n            enabled: true,\n            config: {\n              size: 2.5\n            }\n          }\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 'icon', 'should create 1 icon layer');\n        t.equal(\n          deckLayers.length,\n          3,\n          'Should create 3 deck.gl layer when icon geometry is provided'\n        );\n        const expectedLayerIds = [\n          'test_layer_1_0',\n          'test_layer_1_0-accel',\n          'test_layer_1_0-attach'\n        ];\n\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          expectedLayerIds,\n          'should create 1 composite, 2 svg icon layer'\n        );\n\n        const {props} = deckLayers[0];\n\n        const expectedProps = {\n          brushingRadius: 2500,\n          brushingTarget: 'source',\n          brushingEnabled: true\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    },\n    {\n      name: 'Test render icon.1 -> with text labels',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point icon',\n          columns,\n          color: [2, 3, 4],\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                type: 'string'\n              }\n              // default anchor: start, alignment: center\n            },\n            {\n              field: {\n                name: 'has_result',\n                type: 'boolean'\n              },\n              anchor: 'middle',\n              alignment: 'bottom'\n            }\n          ]\n        },\n        type: 'icon',\n        id: 'test_layer_1'\n      },\n      afterLayerInitialized: layer => {\n        layer.iconGeometry = iconGeometry;\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer, layerData) => {\n        t.equal(deckLayers.length, 7, 'Should create 7 deck.gl layer');\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          [\n            'test_layer_1_0',\n            'test_layer_1_0-accel',\n            'test_layer_1_0-attach',\n            'test_layer_1-label-types',\n            'test_layer_1-label-types-characters',\n            'test_layer_1-label-has_result',\n            'test_layer_1-label-has_result-characters'\n          ],\n          'Should create 5 deck.gl layers'\n        );\n        // test test_layer_1-label-types\n        const {getPosition, getColor, getSize, getPixelOffset, getFilterValue} =\n          deckLayers[4].props;\n        const {getPixelOffset: getPixelOffset1} = deckLayers[6].props;\n\n        const distanceScale = getDistanceScales(INITIAL_MAP_STATE);\n        const radiusScale = layer.getRadiusScaleByZoom(INITIAL_MAP_STATE);\n        const pixelRadius = radiusScale * distanceScale.pixelsPerMeter[0];\n\n        const padding = 20;\n\n        // anchor: start, alignment: center\n        const expectedPixelOffset0 = [1 * (pixelRadius + padding), 0 * (pixelRadius + padding + 0)];\n\n        // anchor: 'middle', alignment: 'bottom'\n        const expectedPixelOffset1 = [\n          0 * (pixelRadius + padding),\n          1 * (pixelRadius + padding + DEFAULT_TEXT_LABEL.size)\n        ];\n\n        t.deepEqual(\n          getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1], 0],\n          'Should calculate correct getPosition'\n        );\n        t.deepEqual(getColor, DEFAULT_TEXT_LABEL.color, 'Should calculate correct getColor');\n        t.deepEqual(getSize, PROJECTED_PIXEL_SIZE_MULTIPLIER, 'Should calculate correct getSize');\n        t.deepEqual(\n          getPixelOffset,\n          expectedPixelOffset0,\n          'Should calculate correct instancePixelOffset'\n        );\n        t.deepEqual(\n          getPixelOffset1,\n          expectedPixelOffset1,\n          'Should calculate correct instancePixelOffset'\n        );\n        t.deepEqual(\n          getFilterValue(layerData.data[0]),\n          [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n          'Should calculate correct instancePixelOffset'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, IconLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#IconLayer -> svg icons as constructor props -> renderIconModal', t => {\n  // initialize iconLayer\n  const iconLayer = new IconLayer({dataId: '', svgIcons: mockSvgIcons});\n  t.deepEqual(iconLayer.iconGeometry, iconLayer.iconGeometry, 'should create correct iconGeometry');\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mount(\n      <IntlWrapper>\n        <iconLayer.layerInfoModal.template />\n      </IntlWrapper>\n    );\n  }, 'mount layer info modal with icons should not fail');\n\n  t.equal(wrapper.find('.icon-table__item').length, 2, 'should render 1 icon');\n  t.equal(\n    wrapper.find('.icon-table_item__name').at(0).find('code').text(),\n    'alert',\n    'should render alert icon'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './base-layer-specs';\nimport './arc-layer-specs';\nimport './cluster-layer-specs';\nimport './geojson-layer-specs';\nimport './grid-layer-specs';\nimport './h3-hexagon-layer-specs';\nimport './heatmap-layer-specs';\nimport './hexagon-layer-specs';\nimport './icon-layer-specs';\nimport './line-layer-specs';\nimport './point-layer-specs';\nimport './raster-tile-layer-specs';\nimport './scenegraph-layer-specs';\nimport './trip-layer-specs';\nimport './s2-geometry-layer-specs';\nimport './wms-layer-specs';\n"
  },
  {
    "path": "test/browser/layer-tests/line-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  dataId,\n  testRows,\n  preparedDataset,\n  arcLayerMeta\n} from 'test/helpers/layer-utils';\n\nimport {PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {copyTable, copyTableAndUpdate} from '@kepler.gl/table';\n\nconst {LineLayer} = KeplerGlLayers;\nconst columns = {\n  lat0: 'lat',\n  lng0: 'lng',\n  lat1: 'lat_1',\n  lng1: 'lng_1'\n};\n\ntest('#LineLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test line layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'LineLayer dataId should be correct');\n          t.ok(layer.type === 'line', 'type should be line');\n          t.ok(layer.isAggregated === false, 'LineLayer is not aggregated');\n          t.ok(layer.config.label === 'test line layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, LineLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#LineLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n  const filterDomain0 = 1474071056000;\n\n  const TEST_CASES = [\n    {\n      name: 'Line trip data.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip lines',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'line',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [testRows[0][2], testRows[0][1], 0],\n              targetPosition: [testRows[0][4], testRows[0][3], 0]\n            },\n            {\n              index: 4,\n              sourcePosition: [testRows[4][2], testRows[4][1], 0],\n              targetPosition: [testRows[4][4], testRows[4][3], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(layerData.data, expectedLayerData.data, 'should format correct line layerData');\n        // getColor\n        t.deepEqual(layerData.getColor, layer.config.color, 'getColor should be a constant');\n        // getTargetColor\n        t.deepEqual(\n          layerData.getTargetColor,\n          layer.config.color,\n          'getTargetColors should be a constant'\n        );\n        // getWidth\n        t.equal(layerData.getWidth, 1, 'getWidth should be a constant');\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - filterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n\n        // layerMeta\n        t.deepEqual(layer.meta, arcLayerMeta, 'should format correct arc layer meta');\n      }\n    },\n    {\n      name: 'Lind trip data.2 targetColor',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip lines',\n          columns,\n          color: [10, 10, 10],\n          visConfig: {\n            targetColor: [1, 2, 3]\n          }\n        },\n        type: 'line',\n        id: 'test_layer_2'\n      },\n      datasets: {\n        [dataId]: copyTable(preparedDataset)\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n        // getColor\n        t.deepEqual(layerData.getColor, layer.config.color, 'getColor should be a constant');\n        // getColor\n        t.deepEqual(layerData.getTargetColor, [1, 2, 3], 'getTargetColors should be a constant');\n      }\n    },\n    {\n      name: 'Line trip data. with colorField and sizeField',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip lines',\n          columns,\n          color: [10, 10, 10],\n          // color by id(integer)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            sizeRange: [10, 20]\n          }\n        },\n        type: 'line',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              sourcePosition: [testRows[0][2], testRows[0][1], 0],\n              targetPosition: [testRows[0][4], testRows[0][3], 0]\n            },\n            {\n              index: 4,\n              sourcePosition: [testRows[4][2], testRows[4][1], 0],\n              targetPosition: [testRows[4][4], testRows[4][3], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getColor: () => {},\n          getTargetColor: () => {},\n          getWidth: () => {}\n        };\n        const expectedDataKeys = Object.keys(expectedLayerData).sort();\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys,\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct line layerData data'\n        );\n        // getColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getColor should be correct'\n        );\n        // getTargetColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getTargetColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getTargetColors  be correct'\n        );\n        // getWidth\n        // domain: [1.59, 11]\n        // range: [10, 20]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getWidth),\n          [10, (2.37 - 1.59) * (10 / 9.41) + 10],\n          'getWidth should be a constant'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - filterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, LineLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#LineLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Line render layer.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip lines',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'line',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n        // test instancePositions\n        t.equal(props.opacity, layer.config.visConfig.opacity, 'should calculate correct opacity');\n        t.equal(\n          props.widthScale,\n          layer.config.visConfig.thickness * PROJECTED_PIXEL_SIZE_MULTIPLIER,\n          'should apply correct widthScale'\n        );\n        t.equal(\n          props.filterRange,\n          preparedDataset.gpuFilter.filterRange,\n          'should supply correct filterRange'\n        );\n      }\n    },\n    {\n      name: 'Line render layer.2 brushing',\n      layer: {\n        config: {\n          dataId,\n          label: 'trip lines',\n          columns,\n          color: [10, 10, 10]\n        },\n        type: 'line',\n        id: 'test_layer_0'\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      renderArgs: {\n        interactionConfig: {\n          brush: {\n            enabled: true,\n            config: {\n              size: 2.5\n            }\n          }\n        }\n      },\n      assert: deckLayers => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n        // test instancePositions\n\n        t.equal(props.brushingRadius, 2500, 'should supply brushingRadius');\n        t.equal(props.brushingEnabled, true, 'should have brushingEnabled: true');\n        t.equal(props.brushingTarget, 'source_target', 'brushingTarget: should be source');\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, LineLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/point-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\nimport {getDistanceScales} from 'viewport-mercator-project';\nimport {scaleQuantize} from 'd3-scale';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {DEFAULT_TEXT_LABEL, PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {processGeojson} from '@kepler.gl/processors';\nimport {INITIAL_MAP_STATE} from '@kepler.gl/reducers';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\nimport {hexToRgb} from '@kepler.gl/utils';\n\nimport {geoJsonWithStyle, geojsonData} from 'test/fixtures/geojson';\nimport testArcData, {pointFromNeighbor} from 'test/fixtures/test-arc-data';\nimport {StateWArcNeighbors} from 'test/helpers/mock-state';\nimport {createNewDataEntryMock} from 'test/helpers/table-utils';\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  testUpdateLayer,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta,\n  fieldDomain\n} from 'test/helpers/layer-utils';\n\nconst {PointLayer} = KeplerGlLayers;\n\ntest('#PointLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test point layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'PointLayer dataId should be correct');\n          t.ok(layer.type === 'point', 'type should be point');\n          t.ok(layer.isAggregated === false, 'PointLayer is not aggregated');\n          t.ok(layer.config.label === 'test point layer', 'label should be correct');\n          t.deepEqual(\n            layer.columnPairs,\n            {\n              lat: {pair: ['lng', 'altitude'], fieldPairKey: 'lat'},\n              lng: {pair: ['lat', 'altitude'], fieldPairKey: 'lng'},\n              altitude: {pair: ['lng', 'lat'], fieldPairKey: 'altitude'}\n            },\n            'columnPairs should be correct'\n          );\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, PointLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#PointLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n  const filterDomain0 = 1474071056000;\n  const TEST_CASES = [\n    {\n      name: 'Point gps point.1',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point',\n          columns: {\n            lat: 'lat',\n            lng: 'lng',\n            altitude: 'id'\n          },\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                type: 'string'\n              }\n            },\n            {\n              field: {\n                name: 'has_result',\n                type: 'boolean'\n              }\n            }\n          ],\n          visConfig: {\n            strokeColor: [1, 2, 3]\n          }\n        },\n        type: 'point',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          textLabels: [\n            {\n              characterSet: [],\n              getText: () => {}\n            },\n            {\n              characterSet: [],\n              getText: () => {}\n            }\n          ],\n          data: [\n            {\n              index: 0,\n              position: [testRows[0][2], testRows[0][1], testRows[0][7]]\n            },\n            {\n              index: 4,\n              position: [testRows[4][2], testRows[4][1], testRows[4][7]]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getFillColor: () => {},\n          getLineColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 7 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1], testRows[0][7]],\n          'getPosition should return correct position'\n        );\n        // getFillColor\n        t.deepEqual(\n          layerData.getFillColor,\n          layer.config.color,\n          'getFillColor should be a constant'\n        );\n        // getLineColor\n        t.deepEqual(layerData.getLineColor, [1, 2, 3], 'getLineColor should be a constant');\n        // getRadius\n        t.equal(layerData.getRadius, 1, 'getRadius should be a constant');\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - filterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [0, 0, 0, 0]'\n        );\n        // textLabels\n        t.deepEqual(\n          layerData.textLabels.length,\n          expectedLayerData.textLabels.length,\n          'textLabels should have 2 items'\n        );\n        t.deepEqual(\n          layerData.textLabels[0].characterSet,\n          ['d', 'r', 'i', 'v', 'e', '_', 'a', 'n', 'l', 'y', 't', 'c', 's', '0'],\n          'textLabels should have correct characterSet'\n        );\n        t.deepEqual(\n          layerData.textLabels[0].getText(layerData.data[0]),\n          'driver_analytics_0',\n          'textLabels getText should have correct text'\n        );\n        // layerMeta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct point layer meta');\n      }\n    },\n    {\n      name: 'Test gps point.2 Data colorFields. sizeField and fixedRadius',\n      layer: {\n        type: 'point',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some point file',\n          columns: {\n            lat: 'lat',\n            lng: 'lng'\n          },\n          visConfig: {\n            outline: true,\n            fixedRadius: true,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            strokeColor: [4, 5, 6]\n          },\n          // color by id(integer)\n          colorField: {\n            type: 'integer',\n            name: 'id'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'integer',\n            name: 'id'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [testRows[0][2], testRows[0][1], 0]\n            },\n            {\n              index: 4,\n              position: [testRows[4][2], testRows[4][1], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        t.deepEqual(layer.config.colorDomain, fieldDomain.id, 'should update layer color domain');\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should filter out nulls, format correct point layerData'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1], 0],\n          'getPosition should return correct lat lng'\n        );\n        // layerMeta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct layerMeta');\n        // getFillColor\n        t.deepEqual(\n          layerData.getFillColor(layerData.data[0]),\n          [1, 1, 1],\n          'getFillColor should return correct color'\n        );\n        // getLineColor\n        t.deepEqual(layerData.getLineColor, [4, 5, 6], 'getLineColor should return correct color');\n        // getRadius\n        // domain [1, 12124]\n        t.equal(layerData.getRadius(layerData.data[0]), 1, 'getRadius should return fixed radius');\n      }\n    },\n    {\n      name: 'Test gps point.2 Data strokeColorFields and SizeField',\n      layer: {\n        type: 'point',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some point file',\n          columns: {\n            lat: 'lat',\n            lng: 'lng'\n          },\n          color: [10, 10, 10],\n          visConfig: {\n            outline: true,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            strokeColorRange: {\n              colors: ['#040404', '#050505', '#060606']\n            },\n            strokeColor: [4, 5, 6],\n            radiusRange: [10, 100]\n          },\n          // color by id(integer)\n          strokeColorField: {\n            type: 'integer',\n            name: 'id'\n          },\n          // size by id(integer)\n          sizeField: {\n            type: 'integer',\n            name: 'id'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [testRows[0][2], testRows[0][1], 0]\n            },\n            {\n              index: 4,\n              position: [testRows[4][2], testRows[4][1], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        t.deepEqual(\n          layer.config.sizeDomain,\n          [fieldDomain.id[0], fieldDomain.id[fieldDomain.id.length - 1]],\n          'should update layer sizeDomain'\n        );\n        // getFillColor\n        t.deepEqual(\n          layerData.getFillColor,\n          [10, 10, 10],\n          'getFillColor should return correct color'\n        );\n        // getLineColor\n        t.deepEqual(\n          layerData.getLineColor(layerData.data[0]),\n          [4, 4, 4],\n          'getLineColor should return correct color'\n        );\n        // getRadius\n        // domain [1, 12124]\n        // range [10, 100]\n        t.equal(\n          layerData.getRadius(layerData.data[0]),\n          10,\n          'getRadius should return corrent radius'\n        );\n      }\n    },\n    {\n      name: 'Test gps point.2 Data with fixed radius and null SizeField',\n      layer: {\n        type: 'point',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some point file',\n          columns: {\n            lat: 'lat',\n            lng: 'lng'\n          },\n          color: [10, 10, 10],\n          visConfig: {\n            outline: true,\n            fixedRadius: true\n          },\n          // size by id(integer)\n          sizeField: null\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [testRows[0][2], testRows[0][1], 0]\n            },\n            {\n              index: 4,\n              position: [testRows[4][2], testRows[4][1], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getFiltered: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 6 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        t.deepEqual(layer.config.sizeDomain, [0, 1], 'should update layer sizeDomain');\n        // getRadius should be a constant because sizeField is null\n        t.equal(layerData.getRadius, 1, 'getRadius should return current radius');\n      }\n    },\n    {\n      name: 'Arc data from neighbors',\n      layer: pointFromNeighbor,\n      datasets: StateWArcNeighbors.visState.datasets,\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [testArcData[0].longitude, testArcData[0].latitude, 0],\n              neighbors: testArcData[0].neighbors\n            },\n            {\n              index: 1,\n              position: [testArcData[1].longitude, testArcData[1].latitude, 0],\n              neighbors: testArcData[1].neighbors\n            }\n          ]\n        };\n\n        for (let i = 0; i < 2; i++) {\n          t.deepEqual(\n            layerData.data[i],\n            expectedLayerData.data[i],\n            'should format correct point layerData data with neighbors'\n          );\n        }\n      }\n    },\n    {\n      name: 'Test gps point.2 Data colorFields, domainStops',\n      layer: {\n        type: 'point',\n        id: 'test_layer_2',\n        config: {\n          dataId,\n          label: 'some point file',\n          columns: {\n            lat: 'lat',\n            lng: 'lng'\n          },\n          visConfig: {\n            outline: true,\n            fixedRadius: true,\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303', '#040404', '#050505', '#060606', '#070707']\n            },\n            strokeColor: [4, 5, 6]\n          },\n          // color by id(integer)\n          colorField: {\n            type: 'integer',\n            name: 'id'\n          },\n          colorScale: 'quantize'\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layer} = result;\n        layer.config.colorDomain = {\n          z: [2, 3, 4, 5],\n          stops: [\n            [0, 20],\n            [0, 30],\n            [0, 40],\n            [0, 50]\n          ]\n        };\n\n        const layerData = layer.formatLayerData({\n          [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n        });\n\n        // get scale function\n        const getFillColor0 = layerData.getFillColor(1);\n        const getFillColor = layerData.getFillColor(1);\n        t.equal(layerData.getFillColorByZoom, true, 'should set getFillColorByZoom to truthy');\n        t.equal(getFillColor0, getFillColor, 'function should be memoized');\n\n        const expectedScale = scaleQuantize()\n          .domain([0, 20])\n          .range(layer.config.visConfig.colorRange.colors);\n\n        const expectedColors = [\n          hexToRgb(expectedScale(preparedDataset.dataContainer.row(0).valueAt(7))),\n          [0, 0, 0, 0],\n          hexToRgb(expectedScale(preparedDataset.dataContainer.row(2).valueAt(7))),\n          hexToRgb(expectedScale(preparedDataset.dataContainer.row(3).valueAt(7))),\n          hexToRgb(expectedScale(preparedDataset.dataContainer.row(4).valueAt(7))),\n          hexToRgb(expectedScale(preparedDataset.dataContainer.row(5).valueAt(7)))\n        ];\n        for (let i = 0; i < 6; i++) {\n          t.deepEqual(\n            getFillColor({\n              data: preparedDataset.dataContainer.row(i),\n              index: i\n            }),\n            expectedColors[i],\n            `Should get corrent value from zoom scale for ${preparedDataset.dataContainer\n              .row(i)\n              .valueAt(7)}`\n          );\n        }\n        // should use domain [0, 20]\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, PointLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#PointLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n  const TEST_CASES = [\n    {\n      name: 'Test render point.1',\n      layer: {\n        id: 'test_layer_1',\n        type: 'point',\n        config: {\n          dataId,\n          label: 'gps point',\n          columns: {\n            lat: 'lat',\n            lng: 'lng',\n            altitude: null\n          },\n          strokeColorField: {\n            type: 'string',\n            name: 'types'\n          },\n          color: [1, 2, 3],\n          visConfig: {\n            strokeColorRange: {\n              colors: ['#040404', '#050505', '#060606']\n            },\n            thickness: 3\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          stroked: layer.config.visConfig.outline,\n          filled: layer.config.visConfig.filled,\n          radiusScale: layer.getRadiusScaleByZoom(INITIAL_MAP_STATE),\n          lineWidthScale: layer.config.visConfig.thickness,\n          filterRange: preparedDataset.gpuFilter.filterRange\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    },\n    {\n      name: 'Point gps point.1 with text labels',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point',\n          columns: {\n            lat: 'lat',\n            lng: 'lng',\n            altitude: 'id'\n          },\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                type: 'string'\n              }\n              // default anchor: start, alignment: center\n            },\n            {\n              field: {\n                name: 'has_result',\n                type: 'boolean'\n              },\n              anchor: 'middle',\n              alignment: 'bottom'\n            }\n          ],\n          visConfig: {\n            strokeColor: [1, 2, 3]\n          }\n        },\n        type: 'point',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer, layerData) => {\n        t.equal(deckLayers.length, 5, 'Should create 5 deck.gl layer');\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          [\n            'test_layer_1',\n            'test_layer_1-label-types',\n            'test_layer_1-label-types-characters',\n            'test_layer_1-label-has_result',\n            'test_layer_1-label-has_result-characters'\n          ],\n          'Should create 5 deck.gl layers'\n        );\n        // test test_layer_1-label-types-characters\n        const {getPosition, getColor, getSize, getPixelOffset, getFilterValue} =\n          deckLayers[2].props;\n        const {getPixelOffset: getPixelOffset1} = deckLayers[4].props;\n\n        const distanceScale = getDistanceScales(INITIAL_MAP_STATE);\n        const radiusScale = layer.getRadiusScaleByZoom(INITIAL_MAP_STATE);\n        const pixelRadius = radiusScale * distanceScale.pixelsPerMeter[0];\n\n        const padding = 20;\n\n        // anchor: start, alignment: center\n        const expectedPixelOffset0 = [1 * (pixelRadius + padding), 0 * (pixelRadius + padding + 0)];\n\n        // anchor: 'middle', alignment: 'bottom'\n        const expectedPixelOffset1 = [\n          0 * (pixelRadius + padding),\n          1 * (pixelRadius + padding + DEFAULT_TEXT_LABEL.size)\n        ];\n\n        t.deepEqual(\n          getPosition(layerData.data[0]),\n          [testRows[0][2], testRows[0][1], 1],\n          'Should calculate correct getPosition'\n        );\n        t.deepEqual(getColor, DEFAULT_TEXT_LABEL.color, 'Should calculate correct getColor');\n        t.deepEqual(getSize, PROJECTED_PIXEL_SIZE_MULTIPLIER, 'Should calculate correct getSize');\n        t.deepEqual(\n          getPixelOffset,\n          expectedPixelOffset0,\n          'Should calculate correct instancePixelOffset'\n        );\n        t.deepEqual(\n          getPixelOffset1,\n          expectedPixelOffset1,\n          'Should calculate correct instancePixelOffset'\n        );\n        t.deepEqual(\n          getFilterValue(layerData.data[0]),\n          [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n          'Should calculate correct instancePixelOffset'\n        );\n      }\n    },\n    {\n      name: 'Point gps point.1 with text labels, color and sizeField',\n      layer: {\n        config: {\n          dataId,\n          label: 'gps point',\n          columns: {\n            lat: 'lat',\n            lng: 'lng',\n            altitude: 'id'\n          },\n          textLabel: [\n            {\n              field: {\n                name: 'types',\n                type: 'string'\n              },\n              color: [2, 2, 2],\n              size: 10,\n              anchor: 'start',\n              alignment: 'bottom'\n            }\n          ],\n          // size by id(integer)\n          sizeField: {\n            type: 'integer',\n            name: 'id'\n          },\n          visConfig: {\n            strokeColor: [1, 2, 3]\n          }\n        },\n        type: 'point',\n        id: 'test_layer_1'\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: (deckLayers, layer, layerData) => {\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ['test_layer_1', 'test_layer_1-label-types', 'test_layer_1-label-types-characters'],\n          'Should create 3 deck.gl layers'\n        );\n        // test test_layer_1-label-types\n        const {getColor, getSize, getPixelOffset} = deckLayers[2].props;\n\n        const distanceScale = getDistanceScales(INITIAL_MAP_STATE);\n        const radiusScale = layer.getRadiusScaleByZoom(INITIAL_MAP_STATE);\n        const pixelRadius = radiusScale * distanceScale.pixelsPerMeter[0];\n\n        const padding = 20;\n\n        // anchor: start, alignment: center\n        const getRadius = layerData.getRadius(layerData.data[1]);\n\n        const expectedPixelOffset1 = [\n          getRadius * pixelRadius + padding,\n          getRadius * pixelRadius + padding + 10\n        ];\n        t.deepEqual(getColor, [2, 2, 2], 'Should calculate correct getColor');\n        t.deepEqual(getSize, PROJECTED_PIXEL_SIZE_MULTIPLIER, 'Should calculate correct getSize');\n        t.deepEqual(\n          getPixelOffset(layerData.data[1]),\n          expectedPixelOffset1,\n          'Should calculate correct instancePixelOffset multiplied by getRadius'\n        );\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, PointLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#PointLayer -> updateLayer', t => {\n  const layerConfig = {\n    id: 'test1',\n    type: 'point',\n    config: {\n      dataId,\n      label: 'some point file',\n      columns: {\n        lat: 'lat',\n        lng: 'lng'\n      },\n      visConfig: {\n        outline: true,\n        filled: true,\n        colorRange: {\n          colors: ['#010101', '#020202', '#030303']\n        },\n        strokeColor: [4, 5, 6]\n      }\n    },\n    visualChannels: {\n      // color by id(integer)\n      colorField: {\n        type: 'string',\n        name: 'types'\n      },\n      // size by id(integer)\n      sizeField: {\n        type: 'integer',\n        name: 'id'\n      }\n    }\n  };\n\n  const shouldUpdate = {\n    gpuFilter: {},\n    dynamicGpuFilter: {instanceRadius: true}\n  };\n\n  testUpdateLayer(t, {layerConfig, shouldUpdate});\n  t.end();\n});\n\ntest('#PointLayer -> formatLayerData -> Geojson column mode', async t => {\n  // create a mockup GeoJson dataset with Point and MultiPoint geometry\n  const geoJsonWithMultiPoint = cloneDeep(geoJsonWithStyle);\n  geoJsonWithMultiPoint.features.push({\n    type: 'Feature',\n    properties: {\n      fillColor: [1, 2, 3],\n      lineColor: [4, 5, 6],\n      lineWidth: 1,\n      elevation: 10,\n      radius: 5\n    },\n    geometry: {\n      type: 'MultiPoint',\n      coordinates: [\n        [-122.0, 37.4],\n        [-121.9, 37.5]\n      ]\n    }\n  });\n\n  const geoJsonWithGeometryCollection = cloneDeep(geoJsonWithStyle);\n  geoJsonWithGeometryCollection.features.push({\n    type: 'Feature',\n    properties: {\n      fillColor: [1, 2, 3],\n      lineColor: [4, 5, 6],\n      lineWidth: 1,\n      elevation: 10,\n      radius: 5\n    },\n    geometry: {\n      type: 'GeometryCollection',\n      geometries: [\n        {\n          type: 'Point',\n          coordinates: [-121.8, 37.6]\n        },\n        {\n          type: 'MultiPoint',\n          coordinates: [\n            [-122.0, 37.4],\n            [-121.9, 37.5]\n          ]\n        }\n      ]\n    }\n  });\n\n  const geoJsonWithNull = cloneDeep(geoJsonWithStyle);\n\n  const geojsonPointDataset = processGeojson(geoJsonWithStyle);\n  const geojsonPolygonDataset = processGeojson(geojsonData);\n  const geojsonMultiPolygonDataset = processGeojson(geoJsonWithMultiPoint);\n  const geojsonGeometryCollectionDataset = processGeojson(geoJsonWithGeometryCollection);\n\n  // mockup invalid geojson object or string geojson object\n  const geojsonPointWithNullDataset = processGeojson(geoJsonWithNull);\n  geojsonPointWithNullDataset.rows.push([undefined, [7, 8, 9], [4, 5, 6], 3, 10, 5]);\n  geojsonPointWithNullDataset.rows.push(['', [7, 8, 9], [4, 5, 6], 3, 10, 5]);\n\n  const TEST_CASES = [\n    {\n      name: 'Geojson point.1',\n      layer: {\n        type: 'point',\n        id: 'test_geojson_layer_1',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columnMode: 'geojson',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: geojsonPointDataset\n      }),\n      assert: result => {\n        const {layerData} = result;\n        // ! layerData is empty here\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [-122.1, 37.3]\n            },\n            {\n              index: 1,\n              position: [-122.2, 37.2]\n            },\n            {\n              index: 2,\n              position: [-122.3, 37.1]\n            }\n          ],\n          getFilterValue: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [-122.1, 37.3],\n          'getPosition should return correct lat lng'\n        );\n      }\n    },\n    {\n      name: 'Geojson polygon.2',\n      layer: {\n        type: 'point',\n        id: 'test_geojson_layer_2',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columnMode: 'geojson',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: geojsonPolygonDataset\n      }),\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [],\n          getFilterValue: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        // empty data since Point layer only supports GeoJson column with Point geometries\n        t.equal(layerData.data.length, 0, 'geojson layerData.data should be empty');\n      }\n    },\n    {\n      name: 'Geojson point.3',\n      layer: {\n        type: 'point',\n        id: 'test_geojson_layer_3',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columnMode: 'geojson',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: geojsonMultiPolygonDataset\n      }),\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [-122.1, 37.3]\n            },\n            {\n              index: 1,\n              position: [-122.2, 37.2]\n            },\n            {\n              index: 2,\n              position: [-122.3, 37.1]\n            },\n            {\n              index: 3,\n              position: [-122.0, 37.4]\n            },\n            {\n              index: 3,\n              position: [-121.9, 37.5]\n            }\n          ],\n          getFilterValue: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [-122.1, 37.3],\n          'getPosition should return correct lat lng'\n        );\n      }\n    },\n    {\n      name: 'Geojson GeometryCollection point.4',\n      layer: {\n        type: 'point',\n        id: 'test_geojson_layer_4',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columnMode: 'geojson',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: geojsonGeometryCollectionDataset\n      }),\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [-122.1, 37.3]\n            },\n            {\n              index: 1,\n              position: [-122.2, 37.2]\n            },\n            {\n              index: 2,\n              position: [-122.3, 37.1]\n            },\n            {\n              index: 3,\n              position: [-121.8, 37.6]\n            },\n            {\n              index: 3,\n              position: [-122.0, 37.4]\n            },\n            {\n              index: 3,\n              position: [-121.9, 37.5]\n            }\n          ],\n          getFilterValue: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [-122.1, 37.3],\n          'getPosition should return correct lat lng'\n        );\n      }\n    },\n    {\n      name: 'Geojson with null point.5',\n      layer: {\n        type: 'point',\n        id: 'test_geojson_layer_5',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some geometry file',\n          columnMode: 'geojson',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: await createNewDataEntryMock({\n        info: {id: dataId},\n        data: geojsonPointWithNullDataset\n      }),\n      assert: result => {\n        const {layerData} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [-122.1, 37.3]\n            },\n            {\n              index: 1,\n              position: [-122.2, 37.2]\n            },\n            {\n              index: 2,\n              position: [-122.3, 37.1]\n            }\n          ],\n          getFilterValue: () => {},\n          getLineColor: () => {},\n          getFillColor: () => {},\n          getRadius: () => {},\n          getPosition: () => {},\n          textLabels: []\n        };\n        t.deepEqual(\n          Object.keys(layerData).sort,\n          Object.keys(expectedLayerData).sort,\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        // getPosition\n        t.deepEqual(\n          layerData.getPosition(layerData.data[0]),\n          [-122.1, 37.3],\n          'getPosition should return correct lat lng'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, PointLayer, TEST_CASES);\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/raster-tile-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {testCreateCases} from 'test/helpers/layer-utils';\n\nconst {RasterTileLayer} = KeplerGlLayers;\n\n// Mock data for raster tests\nconst MOCK_STAC_METADATA = {\n  type: 'Feature',\n  stac_version: '1.0.0',\n  stac_extensions: ['https://stac-extensions.github.io/eo/v1.0.0/schema.json'],\n  assets: {\n    red: {\n      href: 'https://example.com/red.tif',\n      type: 'image/tiff',\n      'eo:bands': [{name: 'red', common_name: 'red'}]\n    },\n    green: {\n      href: 'https://example.com/green.tif',\n      type: 'image/tiff',\n      'eo:bands': [{name: 'green', common_name: 'green'}]\n    },\n    blue: {\n      href: 'https://example.com/blue.tif',\n      type: 'image/tiff',\n      'eo:bands': [{name: 'blue', common_name: 'blue'}]\n    }\n  },\n  bounds: [-122.5, 37.5, -122.0, 38.0],\n  properties: {\n    datetime: '2023-01-01T00:00:00Z'\n  }\n};\n\nconst MOCK_PMTILES_METADATA = {\n  metadataUrl: 'https://example.com/raster.pmtiles',\n  pmtilesType: 'raster',\n  minZoom: 0,\n  maxZoom: 18,\n  bounds: [-122.5, 37.5, -122.0, 38.0]\n};\n\nconst MOCK_STAC_DATASET = {\n  type: 'raster-tile',\n  metadata: MOCK_STAC_METADATA,\n  label: 'Test STAC Dataset'\n};\n\nconst MOCK_PMTILES_DATASET = {\n  type: 'raster-tile',\n  metadata: MOCK_PMTILES_METADATA,\n  label: 'Test PMTiles Dataset'\n};\n\n// Shared render options for tests\nconst createRenderOpts = (dataset, mapState = {dragRotate: false, bearing: 0, pitch: 0}) => ({\n  data: {\n    dataset,\n    tileSource: dataset.metadata.pmtilesType ? {getTileData: () => Promise.resolve(null)} : null\n  },\n  mapState,\n  interactionConfig: {tooltip: {enabled: true}}\n});\n\ntest('#RasterTileLayer -> constructor and basic properties', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'raster-test-data',\n          isVisible: true,\n          label: 'test raster layer'\n        },\n        test: layer => {\n          t.equal(layer.config.dataId, 'raster-test-data', 'dataId should be correct');\n          t.equal(layer.type, 'rasterTile', 'type should be rasterTile');\n          t.equal(layer.isAggregated, false, 'should not be aggregated');\n          t.equal(layer.config.label, 'test raster layer', 'label should be correct');\n          t.equal(layer.config.isVisible, true, 'should be visible');\n          t.equal(layer.config.visConfig.opacity, 1, 'should have default opacity');\n          t.equal(\n            layer.config.visConfig.enableTerrain,\n            true,\n            'should have terrain enabled by default'\n          );\n          t.equal(\n            layer.config.visConfig.enableTerrainTopView,\n            false,\n            'should have terrain top view disabled'\n          );\n          t.equal(layer.requireData, true, 'should require data');\n          t.deepEqual(\n            layer.supportedDatasetTypes,\n            ['raster-tile'],\n            'should support raster-tile dataset type'\n          );\n          t.equal(layer.name, 'Raster Tile', 'should have correct layer name');\n          t.ok(typeof layer.layerIcon === 'function', 'should have layerIcon function');\n          t.ok(layer.isValidToSave(), 'should be valid to save');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, RasterTileLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#RasterTileLayer -> data formatting and rendering', t => {\n  const stacLayer = new RasterTileLayer({id: 'stac-layer', dataId: 'stac-data'});\n  const pmtilesLayer = new RasterTileLayer({id: 'pmtiles-layer', dataId: 'pmtiles-data'});\n\n  // Test STAC data formatting\n  const stacLayerData = stacLayer.formatLayerData({'stac-data': MOCK_STAC_DATASET});\n  t.ok(stacLayerData, 'should return STAC layer data');\n  t.equal(stacLayerData.dataset, MOCK_STAC_DATASET, 'should have correct STAC dataset');\n  t.equal(stacLayerData.tileSource, null, 'should have null tileSource for STAC');\n\n  // Test PMTiles data formatting\n  const pmtilesLayerData = pmtilesLayer.formatLayerData({'pmtiles-data': MOCK_PMTILES_DATASET});\n  t.ok(pmtilesLayerData, 'should return PMTiles layer data');\n  t.equal(pmtilesLayerData.dataset, MOCK_PMTILES_DATASET, 'should have correct PMTiles dataset');\n  t.ok(pmtilesLayerData.tileSource, 'should create tileSource for PMTiles');\n\n  // Test rendering\n  const stacLayers = stacLayer.renderLayer(createRenderOpts(MOCK_STAC_DATASET));\n  const pmtilesLayers = pmtilesLayer.renderLayer(createRenderOpts(MOCK_PMTILES_DATASET));\n\n  t.ok(Array.isArray(stacLayers), 'should return array for STAC');\n  t.ok(Array.isArray(pmtilesLayers), 'should return array for PMTiles');\n  t.equal(pmtilesLayers.length, 1, 'should render one deck layer for PMTiles');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> configuration and visual settings', t => {\n  const layer = new RasterTileLayer({id: 'test-layer', dataId: 'stac-data'});\n\n  // Test layer config updates\n  layer.updateLayerConfig({isVisible: false, label: 'Updated Layer'});\n  t.equal(layer.config.isVisible, false, 'should update visibility');\n  t.equal(layer.config.label, 'Updated Layer', 'should update label');\n\n  // Test visual config updates\n  layer.updateLayerVisConfig({\n    opacity: 0.7,\n    enableTerrain: false,\n    enableTerrainTopView: true\n  });\n  t.equal(layer.config.visConfig.opacity, 0.7, 'should update opacity');\n  t.equal(layer.config.visConfig.enableTerrain, false, 'should update terrain');\n  t.equal(layer.config.visConfig.enableTerrainTopView, true, 'should update terrain top view');\n\n  // Test shouldRenderLayer\n  t.notOk(layer.shouldRenderLayer(), 'should not render when not visible');\n  layer.updateLayerConfig({isVisible: true});\n  t.ok(layer.shouldRenderLayer(), 'should render when visible');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> metadata handling', t => {\n  const layer = new RasterTileLayer({id: 'test-layer', dataId: 'stac-data'});\n\n  // Test with non-raster dataset (should not throw)\n  t.doesNotThrow(() => {\n    layer.updateLayerMeta({type: 'csv'});\n  }, 'should handle non-raster dataset');\n\n  // Test with STAC and PMTiles datasets (should not throw)\n  t.doesNotThrow(() => {\n    layer.updateLayerMeta(MOCK_STAC_DATASET);\n    layer.updateLayerMeta(MOCK_PMTILES_DATASET);\n  }, 'should handle STAC and PMTiles datasets');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> pixel value tracking', t => {\n  const layer = new RasterTileLayer({id: 'test-layer', dataId: 'stac-data'});\n\n  // Test initial state\n  t.equal(layer.minViewportPixelValue, Infinity, 'should have Infinity as initial min');\n  t.equal(layer.maxViewportPixelValue, -Infinity, 'should have -Infinity as initial max');\n\n  // Test with valid tiles\n  const mockTiles = [\n    {data: {minPixelValue: 25, maxPixelValue: 100}},\n    {data: {minPixelValue: 10, maxPixelValue: 150}}\n  ];\n  layer.updateMinMaxPixelValue(mockTiles);\n  t.equal(layer.minViewportPixelValue, 10, 'should set correct min pixel value');\n  t.equal(layer.maxViewportPixelValue, 150, 'should set correct max pixel value');\n\n  // Test with empty tiles\n  layer.updateMinMaxPixelValue([]);\n  t.equal(layer.minViewportPixelValue, Infinity, 'should reset to Infinity for empty tiles');\n  t.equal(layer.maxViewportPixelValue, -Infinity, 'should reset to -Infinity for empty tiles');\n\n  // Test with null data\n  layer.updateMinMaxPixelValue([\n    {data: null},\n    {data: {minPixelValue: 30, maxPixelValue: 120}},\n    null\n  ]);\n  t.equal(layer.minViewportPixelValue, 30, 'should handle null data gracefully');\n  t.equal(layer.maxViewportPixelValue, 120, 'should handle null data gracefully');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> findDefaultLayerProps', t => {\n  // Test with raster dataset\n  const result = RasterTileLayer.findDefaultLayerProps(MOCK_STAC_DATASET);\n  t.ok(result.props, 'should return props object');\n  t.equal(result.props.length, 1, 'should return one layer prop');\n  t.equal(result.props[0].label, 'Test STAC Dataset', 'should use dataset label');\n  t.equal(result.props[0].isVisible, true, 'should be visible by default');\n\n  // Test with non-raster dataset\n  const nonRasterResult = RasterTileLayer.findDefaultLayerProps({type: 'csv'});\n  t.deepEqual(nonRasterResult.props, [], 'should return empty props for non-raster dataset');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> edge cases and error handling', t => {\n  const layer = new RasterTileLayer({id: 'test-layer', dataId: 'stac-data'});\n\n  // Test formatLayerData with missing dataset\n  t.throws(() => {\n    layer.formatLayerData({});\n  }, 'should throw error when dataset missing');\n\n  // Test layer without dataId\n  const layerWithoutDataId = new RasterTileLayer({id: 'test-layer'});\n  layerWithoutDataId.config.dataId = undefined;\n  const layerData = layerWithoutDataId.formatLayerData({});\n  t.deepEqual(layerData, {}, 'should return empty object when no dataId');\n\n  // Test rendering with invalid/missing data\n  const invalidLayers = layer.renderLayer(createRenderOpts({metadata: {type: 'Feature'}}));\n  t.deepEqual(invalidLayers, [], 'should return empty array for invalid STAC data');\n\n  const noDataLayers = layer.renderLayer({...createRenderOpts(MOCK_STAC_DATASET), data: null});\n  t.deepEqual(noDataLayers, [], 'should return empty array when no data');\n\n  // Test malformed metadata (should not throw)\n  t.doesNotThrow(() => {\n    layer.updateLayerMeta({type: 'raster-tile', metadata: {type: 'Feature'}});\n    layer.updateLayerMeta({type: 'raster-tile', metadata: {pmtilesType: 'raster'}});\n  }, 'should handle malformed metadata gracefully');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> data type variations', t => {\n  const layer = new RasterTileLayer({id: 'test-layer', dataId: 'stac-data'});\n\n  // Test multi-asset STAC\n  const multiAssetStac = {\n    type: 'raster-tile',\n    metadata: {\n      ...MOCK_STAC_METADATA,\n      assets: {\n        ...MOCK_STAC_METADATA.assets,\n        nir: {\n          href: 'https://example.com/nir.tif',\n          type: 'image/tiff',\n          'eo:bands': [{name: 'nir', common_name: 'nir'}]\n        }\n      }\n    }\n  };\n  const multiAssetData = layer.formatLayerData({'stac-data': multiAssetStac});\n  t.ok(multiAssetData, 'should handle multi-asset STAC data');\n\n  // Test single asset STAC\n  const singleAssetStac = {\n    type: 'raster-tile',\n    metadata: {\n      type: 'Feature',\n      stac_version: '1.0.0',\n      assets: {\n        visual: {href: 'https://example.com/visual.tif', type: 'image/tiff'}\n      },\n      bounds: [-122.5, 37.5, -122.0, 38.0]\n    }\n  };\n  const singleAssetData = layer.formatLayerData({'stac-data': singleAssetStac});\n  t.ok(singleAssetData, 'should handle single-asset STAC data');\n\n  t.end();\n});\n\ntest('#RasterTileLayer -> layer serialization', t => {\n  const layer = new RasterTileLayer({\n    id: 'test-layer',\n    dataId: 'stac-data',\n    label: 'Test Raster Layer'\n  });\n\n  // Configure layer\n  layer.updateLayerVisConfig({\n    opacity: 0.8,\n    enableTerrain: false,\n    colorRange: {\n      colors: ['#FF0000', '#00FF00'],\n      colorLegends: {water: [0, 0, 255]}\n    }\n  });\n\n  // Test layer properties\n  const config = layer.config;\n  t.ok(layer.id, 'should have id on layer');\n  t.ok(config.dataId, 'should have dataId in config');\n  t.ok(config.label, 'should have label in config');\n  t.equal(config.visConfig.opacity, 0.8, 'should preserve opacity');\n  t.equal(config.visConfig.enableTerrain, false, 'should preserve terrain setting');\n  t.ok(layer.isValidToSave(), 'should be valid to save');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/s2-geometry-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  preparedFilterDomain0\n} from 'test/helpers/layer-utils';\nimport {s2DefaultElevation as defaultElevation, KeplerGlLayers} from '@kepler.gl/layers';\nimport {copyTableAndUpdate} from '@kepler.gl/table';\n\nconst {S2GeometryLayer} = KeplerGlLayers;\n\ntest('#S2Geometry -> constructor', t => {\n  const TEST_CASES = [\n    {\n      props: {\n        dataId: 'smoothie',\n        isVisible: true,\n        label: 'text s2geometry layer'\n      },\n      test: layer => {\n        t.ok(layer.config.dataId === 'smoothie', 'S2GeometryLayer dataId should be correct');\n        t.ok(layer.type === 's2', 'type should be s2');\n        t.deepEqual(\n          Object.keys(layer.visConfigSettings),\n          [\n            'opacity',\n            'colorRange',\n            'filled',\n            'thickness',\n            'strokeColor',\n            'strokeColorRange',\n            'sizeRange',\n            'stroked',\n            'enable3d',\n            'elevationScale',\n            'enableElevationZoomFactor',\n            'fixedHeight',\n            'heightRange',\n            'wireframe'\n          ],\n          'should provide the correct visConfigSettings properties'\n        );\n      }\n    }\n  ];\n\n  testCreateCases(t, S2GeometryLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#S2Geometry -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 's2 layer',\n      layer: {\n        type: 's2',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'S2',\n          color: [2, 3, 4],\n          columns: {\n            token: 's2x'\n          },\n          isVisible: true\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [\n            {index: 0, token: '80858004'},\n            {index: 4, token: '8085801c'}\n          ],\n          getElevation: () => {},\n          getFillColor: () => {},\n          getFilterValue: () => {},\n          getS2Token: () => {},\n          getLineColor: () => {},\n          getLineWidth: () => {}\n        };\n\n        // test layer.meta\n        t.deepEqual(\n          layer.meta,\n          {\n            bounds: [-122.38442501650697, 37.79109908631398, -122.3617617587711, 37.81968456730113]\n          },\n          'should format correct S2 layer meta'\n        );\n\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct s2-geometry layerData'\n        );\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          `layerData should have ${expectedLayerData.length} keys`\n        );\n\n        t.deepEqual(layerData.getFillColor, [2, 3, 4], 'getFillColor should be a constant');\n        t.deepEqual(layerData.getElevation, defaultElevation, 'getElevation should be a constant');\n        t.deepEqual(\n          layerData.data.map(layerData.getS2Token),\n          ['80858004', '8085801c'],\n          'getS2Token should return correct token'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    },\n    {\n      name: 's2 layer',\n      layer: {\n        type: 's2',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'S2',\n          color: [2, 3, 4],\n          columns: {\n            token: 's2x'\n          },\n          isVisible: true,\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by id(integer)\n          heightField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            elevationRange: [10, 20],\n            sizeRange: [10, 20],\n            enable3d: true\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(preparedDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData} = result;\n        // getFillColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getFillColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1]\n          ],\n          'getFillColor should be correct'\n        );\n\n        // getElevation\n        // domain: [1.59, 11]\n        // range: [0, 500]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getElevation),\n          [0, 41.445270988310305],\n          'getElevation should correct'\n        );\n\n        // getLineWidth\n        // domain: [1.59, 11]\n        // range: [10, 20]\n        // value [1.59, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getLineWidth),\n          [10, (2.37 - 1.59) * (10 / 9.41) + 10],\n          'getLineWidth should be correct'\n        );\n\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, S2GeometryLayer, TEST_CASES);\n\n  t.end();\n});\n\ntest('#S2Geometry -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 's2 layer',\n      layer: {\n        type: 's2',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'S2',\n          color: [2, 3, 4],\n          columns: {\n            token: 's2x'\n          },\n          isVisible: true\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(layer.type, 's2', 'should create 1 s2 layer');\n        t.equal(deckLayers.length, 4, 'Should create 4 deck.gl layers');\n\n        const expectedLayerIds = [\n          'test_layer_1',\n          'test_layer_1-cell',\n          'test_layer_1-cell-fill',\n          'test_layer_1-cell-stroke'\n        ];\n\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          expectedLayerIds,\n          'should create 1 composite, 1 hexagon-cell layer'\n        );\n\n        const {props: layerProps} = deckLayers[0];\n\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          filterRange: preparedDataset.gpuFilter.filterRange,\n          filled: true,\n          wrapLongitude: false,\n          autoHighlight: false,\n          highlightColor: [255, 255, 255, 60],\n          extruded: false,\n          elevationScale: 5\n        };\n\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(layerProps[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, S2GeometryLayer, TEST_CASES);\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/scenegraph-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\nimport global from 'global';\nimport sinon from 'sinon';\nimport sinonStubPromise from 'sinon-stub-promise';\nsinonStubPromise(sinon);\n\nimport {\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  preparedDataset,\n  dataId,\n  testRows,\n  pointLayerMeta,\n  preparedFilterDomain0\n} from 'test/helpers/layer-utils';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nconst {ScenegraphLayer} = KeplerGlLayers;\nconst columns = {lat: 'lat', lng: 'lng'};\n\ntest('#ScenegraphLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test 3d layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'ScenegraphLayer dataId should be correct');\n          t.ok(layer.type === '3D', 'type should be 3D');\n          t.ok(layer.isAggregated === false, 'ScenegraphLayer is not aggregated');\n          t.ok(layer.config.label === 'test 3d layer', 'label should be correct');\n          t.ok(Object.keys(layer.columnPairs).length, 'should have columnPairs');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, ScenegraphLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#ScenegraphLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Scenegraph gps point.1',\n      layer: {\n        type: '3D',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'gps 3d',\n          columns\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [\n            {\n              index: 0,\n              position: [testRows[0][2], testRows[0][1], 0]\n            },\n            {\n              index: 4,\n              position: [testRows[4][2], testRows[4][1], 0]\n            }\n          ],\n          getFilterValue: () => {},\n          getPosition: () => {}\n        };\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          Object.keys(expectedLayerData).sort(),\n          'layerData should have 3 keys'\n        );\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct point layerData data'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getPosition),\n          [\n            [testRows[0][2], testRows[0][1], 0],\n            [testRows[4][2], testRows[4][1], 0]\n          ],\n          'getPosition should return correct lat lng'\n        );\n        // getFilterValue\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [moment.utc(testRows[4][0]).valueOf() - preparedFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return [value, 0, 0, 0]'\n        );\n        // layerMeta\n        t.deepEqual(layer.meta, pointLayerMeta, 'should format correct point layer meta');\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, ScenegraphLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#ScenegraphLayer -> renderLayer', t => {\n  // TODO: mock actual gltf response\n  const mockSuccessResponse = {};\n  const mockJsonPromise = sinon.stub().returnsPromise();\n  mockJsonPromise.resolves(mockSuccessResponse);\n  const stubedFetch = sinon.stub(global, 'fetch').returnsPromise();\n\n  stubedFetch.resolves({\n    json: mockJsonPromise\n  });\n\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Scenegraph gps point.1',\n      layer: {\n        type: '3D',\n        id: 'test_layer_1',\n        config: {\n          dataId,\n          label: 'gps 3d',\n          columns\n        }\n      },\n      datasets: {\n        [dataId]: {\n          ...preparedDataset,\n          filteredIndex\n        }\n      },\n      assert: (deckLayers, layer) => {\n        t.equal(deckLayers.length, 1, 'Should create 1 deck.gl layer');\n        const {props} = deckLayers[0];\n\n        const expectedProps = {\n          opacity: layer.config.visConfig.opacity,\n          sizeScale: layer.config.visConfig.sizeScale,\n          filterRange: preparedDataset.gpuFilter.filterRange\n        };\n        Object.keys(expectedProps).forEach(key => {\n          t.equal(props[key], expectedProps[key], `should have correct props.${key}`);\n        });\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, ScenegraphLayer, TEST_CASES);\n\n  stubedFetch.restore();\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/trip-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {\n  tripDefaultLineWidth as defaultLineWidth,\n  parseTripGeoJsonTimestamp,\n  KeplerGlLayers\n} from '@kepler.gl/layers';\n\nimport {copyTableAndUpdate} from '@kepler.gl/table';\nconst {TripLayer} = KeplerGlLayers;\n\nimport {\n  dataId,\n  testCreateCases,\n  testFormatLayerDataCases,\n  testRenderLayerCases,\n  prepareTripGeoDataset,\n  valueFilterDomain0,\n  animationConfig\n} from 'test/helpers/layer-utils';\nimport {TripLayerMeta, dataToFeature, dataToTimeStamp} from 'test/fixtures/trip-geojson';\n\ntest('#TripLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'smoothie',\n          isVisible: true,\n          label: 'test trip layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'smoothie', 'tripnLayer dataId should be correct');\n          t.ok(layer.type === 'trip', 'type should be trip');\n          t.ok(layer.isAggregated === false, 'tripLayer is not aggregated');\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, TripLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('#TripLayer -> formatLayerData', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Trip Geojson.1',\n      layer: {\n        type: 'trip',\n        id: 'test_trip_layer_1',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some trips',\n          columns: {\n            geojson: '_geojson'\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(prepareTripGeoDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n        const expectedLayerData = {\n          data: [dataToFeature[0], dataToFeature[2], dataToFeature[4]]\n        };\n        const expectedDataKeys = [\n          'data',\n          'getPath',\n          'getTimestamps',\n          'getFilterValue',\n          'getColor',\n          'getWidth'\n        ];\n\n        t.deepEqual(\n          Object.keys(layerData).sort(),\n          expectedDataKeys.sort(),\n          'layerData should have 7 keys'\n        );\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getColor),\n          [\n            [1, 2, 3],\n            [1, 2, 3],\n            [1, 2, 3]\n          ],\n          'getColor should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getFilterValue),\n          [\n            [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n            [7 - valueFilterDomain0, 0, 0, 0],\n            [6 - valueFilterDomain0, 0, 0, 0]\n          ],\n          'getFilterValue should return correct value'\n        );\n        t.deepEqual(\n          layerData.data.map(layerData.getWidth),\n          [defaultLineWidth, defaultLineWidth, defaultLineWidth],\n          'getWidth should return correct value'\n        );\n        t.deepEqual(\n          layerData.getPath(layerData.data[0]),\n          dataToFeature[0].geometry.coordinates,\n          'getPath should return correct coordinates'\n        );\n        t.deepEqual(\n          layerData.getTimestamps(layerData.data[0]),\n          dataToTimeStamp[0],\n          'getWidth should return getTimestamps'\n        );\n        // meta\n        t.deepEqual(\n          Object.keys(layer.meta).sort(),\n          ['bounds', 'featureTypes', 'getFeature'],\n          'should format correct geojson layer meta keys'\n        );\n        t.deepEqual(\n          layer.meta.bounds,\n          TripLayerMeta.bounds,\n          'should format correct geojson layer meta bounds'\n        );\n        t.deepEqual(\n          layer.meta.featureTypes,\n          TripLayerMeta.featureTypes,\n          'should format correct geojson layer meta featureTypes'\n        );\n        // dataToFeature\n        t.deepEqual(\n          layer.dataToTimeStamp,\n          dataToTimeStamp,\n          'should format correct geojson dataToFeature'\n        );\n\n        t.deepEqual(\n          layer.dataToFeature,\n          dataToFeature,\n          'should format correct geojson dataToFeature'\n        );\n      }\n    },\n    {\n      name: 'Trip Geojson.2',\n      layer: {\n        type: 'trip',\n        id: 'test_trip_layer_2',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some trips',\n          columns: {\n            geojson: '_geojson'\n          },\n          // color by types(string)\n          colorField: {\n            type: 'string',\n            name: 'types'\n          },\n          // size by trip_distance(real)\n          sizeField: {\n            type: 'real',\n            name: 'trip_distance'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#010101', '#020202', '#030303']\n            },\n            sizeRange: [10, 20]\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(prepareTripGeoDataset, {filteredIndex})\n      },\n      assert: result => {\n        const {layerData, layer} = result;\n\n        const expectedLayerData = {\n          data: [dataToFeature[0], dataToFeature[2], dataToFeature[4]]\n        };\n        // data\n        t.deepEqual(\n          layerData.data,\n          expectedLayerData.data,\n          'should format correct geojson layerData'\n        );\n        // getColor\n        // domain: ['driver_analytics', 'driver_analytics_0', 'driver_gps']\n        // range ['#010101', '#020202', '#030303']\n        t.deepEqual(\n          layerData.data.map(layerData.getColor),\n          [\n            [2, 2, 2],\n            [1, 1, 1],\n            [1, 1, 1]\n          ],\n          'getColor should return correct value'\n        );\n        // getWidth\n        // domain: [2.37, 8.33]\n        // range: [10, 20]\n        // value [1.59, 2.83, 2.37]\n        t.deepEqual(\n          layerData.data.map(layerData.getWidth),\n          [8.691275167785234, 10.771812080536913, 10],\n          'getWidth should return correct value'\n        );\n\n        // dataToFeature\n        t.deepEqual(\n          layer.dataToTimeStamp,\n          dataToTimeStamp,\n          'should format correct geojson dataToFeature'\n        );\n\n        t.deepEqual(\n          layer.dataToFeature,\n          dataToFeature,\n          'should format correct geojson dataToFeature'\n        );\n      }\n    }\n  ];\n\n  testFormatLayerDataCases(t, TripLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#TripLayer -> renderLayer', t => {\n  const filteredIndex = [0, 2, 4];\n\n  const TEST_CASES = [\n    {\n      name: 'Trip Geojson.1',\n      layer: {\n        type: 'trip',\n        id: 'test_trip_layer_1',\n        config: {\n          color: [1, 2, 3],\n          dataId,\n          label: 'some trips',\n          columns: {\n            geojson: '_geojson'\n          },\n          visConfig: {\n            colorRange: {\n              colors: ['#080808', '#090909', '#070707']\n            },\n            trailLength: 2\n          }\n        }\n      },\n      datasets: {\n        [dataId]: copyTableAndUpdate(prepareTripGeoDataset, {filteredIndex})\n      },\n      renderArgs: {\n        animationConfig\n      },\n      assert: deckLayers => {\n        const ids = ['test_trip_layer_1'];\n        t.deepEqual(\n          deckLayers.map(l => l.id),\n          ids,\n          'Should render 1 deck layers'\n        );\n\n        const deckTripLayer = deckLayers[0];\n        const expectedProps = {\n          trailLength: 2000,\n          capRounded: true,\n          jointRounded: true,\n          widthScale: 128,\n          currentTime: 0,\n          parameters: {depthTest: false, depthMask: false},\n          filterRange: [\n            [3, 11],\n            [0, 0],\n            [0, 0],\n            [0, 0]\n          ],\n          opacity: 0.8\n        };\n\n        Object.keys(expectedProps).forEach(key => {\n          t.deepEqual(\n            deckTripLayer.props[key],\n            expectedProps[key],\n            `should have correct props.${key}`\n          );\n        });\n\n        t.deepEqual(\n          deckTripLayer.props.extensions.map(cl => cl.constructor.name),\n          ['DataFilterExtension'],\n          'Should provide DataFilterExtension'\n        );\n        // test attributes\n        const {attributes} = deckTripLayer.state.attributeManager;\n\n        const fvs = [\n          [Number.MIN_SAFE_INTEGER, 0, 0, 0],\n          [7 - valueFilterDomain0, 0, 0, 0],\n          [6 - valueFilterDomain0, 0, 0, 0]\n        ];\n        // use picking Colors to determine number numVertexs\n        const numVertexs = [];\n        let currentIdx = 0;\n        let count = 0;\n\n        for (let c = 0; c < attributes.instancePickingColors.value.length; c += 3) {\n          if (\n            attributes.instancePickingColors.value[c] > currentIdx ||\n            c === attributes.instancePickingColors.value.length - 3\n          ) {\n            currentIdx = attributes.instancePickingColors.value[c];\n            if (count > 0) {\n              numVertexs.push(count);\n            }\n            count = 1;\n          } else if (attributes.instancePickingColors.value[c] === currentIdx) {\n            count += 1;\n          }\n        }\n\n        const testLen = numVertexs.reduce((accu, c) => accu + c, 0);\n        const expectedFilterValues = new Float32Array(testLen * 4);\n        const expectedColors = new Float32Array(testLen * 4);\n\n        let c = 0;\n        for (let i = 0; i < 3; i++) {\n          for (let n = 0; n < numVertexs[i]; n++) {\n            expectedFilterValues[c + 0] = fvs[i][0];\n            expectedFilterValues[c + 1] = fvs[i][1];\n            expectedFilterValues[c + 2] = fvs[i][2];\n            expectedFilterValues[c + 3] = fvs[i][3];\n            expectedColors[c + 0] = 1;\n            expectedColors[c + 1] = 2;\n            expectedColors[c + 2] = 3;\n            expectedColors[c + 3] = 255;\n            c += 4;\n          }\n        }\n\n        const expectedStroke = new Float32Array(testLen).fill(1);\n        t.deepEqual(\n          attributes.filterValues.value.slice(0, testLen * 4),\n          expectedFilterValues,\n          'Should have correct filterValues'\n        );\n\n        t.deepEqual(\n          attributes.instanceColors.value.slice(0, testLen * 4),\n          expectedColors,\n          'Should have correct instanceColors'\n        );\n\n        t.deepEqual(\n          attributes.instanceStrokeWidths.value.slice(0, testLen),\n          expectedStroke,\n          'Should have correct instanceStrokeWidths'\n        );\n        // TODO: test UpdateTriggers\n      }\n    }\n  ];\n\n  testRenderLayerCases(t, TripLayer, TEST_CASES);\n  t.end();\n});\n\ntest('#TripLayer -> parseTripGeoJsonTimestamp', t => {\n  // mix with illegal character\n  const dataToFeature1 = cloneDeep(dataToFeature);\n  dataToFeature1[0].geometry.coordinates[0][3] = 0;\n  dataToFeature1[1].geometry.coordinates[0][3] = NaN;\n  dataToFeature1[2].geometry.coordinates[0][3] = null;\n  dataToFeature1[3].geometry.coordinates[0][3] = undefined;\n  dataToFeature1[4].geometry.coordinates[0][3] = 'a';\n\n  dataToFeature1[0].geometry.coordinates[dataToFeature1[0].geometry.coordinates.length - 1][3] =\n    NaN;\n\n  const result = parseTripGeoJsonTimestamp(dataToFeature1);\n\n  t.deepEqual(result.animationDomain, [1565577261, 1565578836], 'should filter out illugal value');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/layer-tests/wms-layer-specs.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape-catch';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {testCreateCases} from 'test/helpers/layer-utils';\n\nconst {WMSLayer} = KeplerGlLayers;\n\n// Shared test fixtures\nconst MOCK_WMS_LAYER_CONFIG = {\n  name: 'test_layer',\n  title: 'Test WMS Layer',\n  boundingBox: [\n    [-180, -90],\n    [180, 90]\n  ],\n  queryable: true\n};\n\nconst MOCK_WMS_DATASET = {\n  type: 'wms-tile',\n  metadata: {\n    tilesetDataUrl: 'http://example.com/wms',\n    label: 'Test WMS Dataset',\n    layers: [MOCK_WMS_LAYER_CONFIG]\n  }\n};\n\nconst MOCK_RENDER_OPTS = {\n  data: {\n    tilesetDataUrl: 'http://example.com/wms',\n    metadata: {layers: [MOCK_WMS_LAYER_CONFIG]}\n  },\n  mapState: {dragRotate: false},\n  interactionConfig: {tooltip: {enabled: true}},\n  layerCallbacks: {onWMSFeatureInfo: () => {}}\n};\n\n// Mock DOMParser for browser environment\nglobal.DOMParser = class MockDOMParser {\n  parseFromString(str) {\n    const createMockElement = (tagName, textContent = '') => ({\n      tagName,\n      textContent,\n      children: [],\n      getElementsByTagName: name =>\n        Array.from(this.children).filter(child => child.tagName === name)\n    });\n\n    // Simple mock for the specific XML structure used in tests\n    if (str.includes('conus:RED_BAND')) {\n      return {\n        getElementsByTagName: name => {\n          if (name === 'gml:featureMember') {\n            return [\n              {\n                children: [\n                  {\n                    children: [\n                      createMockElement('conus:RED_BAND', '255.0'),\n                      createMockElement('conus:GREEN_BAND', '195.0'),\n                      createMockElement('conus:BLUE_BAND', '0.0'),\n                      createMockElement('conus:ALPHA_BAND', '255.0')\n                    ]\n                  }\n                ]\n              }\n            ];\n          }\n          return [];\n        }\n      };\n    }\n\n    return {getElementsByTagName: () => []};\n  }\n};\n\n// Helper function to create a configured WMS layer\nfunction createWMSLayer(config = {}) {\n  const layer = new WMSLayer({\n    id: 'test-wms-layer',\n    dataId: 'wms-dataset',\n    ...config\n  });\n\n  layer.updateLayerVisConfig({\n    wmsLayer: MOCK_WMS_LAYER_CONFIG,\n    opacity: 0.8,\n    transparent: true,\n    ...config.visConfig\n  });\n\n  return layer;\n}\n\ntest('#WMSLayer -> constructor', t => {\n  const TEST_CASES = {\n    CREATE: [\n      {\n        props: {\n          dataId: 'wms-test-data',\n          isVisible: true,\n          label: 'test wms layer'\n        },\n        test: layer => {\n          t.ok(layer.config.dataId === 'wms-test-data', 'WMSLayer dataId should be correct');\n          t.ok(layer.type === 'wms', 'type should be wms');\n          t.ok(layer.isAggregated === false, 'WMSLayer is not aggregated');\n          t.ok(layer.config.label === 'test wms layer', 'label should be correct');\n          t.ok(layer.config.isVisible === true, 'should be visible by default');\n          t.ok(layer.config.visConfig, 'should have visConfig');\n          t.equal(layer.config.visConfig.opacity, 0.8, 'should have default opacity');\n          t.equal(layer.config.visConfig.transparent, true, 'should be transparent by default');\n          t.equal(layer.config.visConfig.wmsLayer, null, 'should have null wmsLayer initially');\n          t.ok(typeof layer.layerIcon === 'function', 'should have layerIcon');\n          t.deepEqual(\n            layer.supportedDatasetTypes,\n            ['wms-tile'],\n            'should support wms-tile dataset type'\n          );\n        }\n      }\n    ]\n  };\n\n  testCreateCases(t, WMSLayer, TEST_CASES.CREATE);\n  t.end();\n});\n\ntest('WMSLayer -> basic layer functionality', t => {\n  const layer = createWMSLayer();\n\n  // Test layer configuration\n  t.deepEqual(\n    layer.config.visConfig.wmsLayer,\n    MOCK_WMS_LAYER_CONFIG,\n    'should update wmsLayer config'\n  );\n  t.equal(layer.config.visConfig.opacity, 0.8, 'should have correct opacity');\n  t.equal(layer.config.visConfig.transparent, true, 'should be transparent');\n\n  // Test layer properties\n  t.equal(layer.name, 'WMS Tile', 'should have correct layer name');\n  t.ok(layer.layerIcon, 'should have layer icon');\n  t.equal(layer.requireData, true, 'WMS layer should require data');\n  t.ok(layer.isValidToSave(), 'should be valid to save');\n\n  // Test _getCurrentServiceLayer\n  const serviceLayer = layer._getCurrentServiceLayer();\n  t.ok(serviceLayer, 'should return service layer when configured');\n  t.equal(serviceLayer.name, 'test_layer', 'should return correct service layer');\n\n  t.end();\n});\n\ntest('WMSLayer -> hover functionality', t => {\n  const layer = createWMSLayer();\n\n  // Test hasHoveredObject\n  const mockObjectInfo = {\n    picked: true,\n    layer: {props: {id: 'test-wms-layer'}}\n  };\n\n  const hoveredObject = layer.hasHoveredObject(mockObjectInfo);\n  t.ok(hoveredObject, 'should return hovered object when layer is picked');\n  t.equal(hoveredObject.index, 0, 'should return index 0 for WMS layer');\n\n  // Test hasHoveredObject with no picked object\n  const hoveredObjectNotPicked = layer.hasHoveredObject({...mockObjectInfo, picked: false});\n  t.equal(hoveredObjectNotPicked, null, 'should return null when layer is not picked');\n\n  // Test getHoverData with hover coordinates\n  const hoverData = layer.getHoverData(null, null, [], null, {index: 0, x: 100, y: 200});\n  t.ok(hoverData, 'should return hover data for WMS layer');\n  t.ok(hoverData.fieldValues, 'should have fieldValues array');\n  t.equal(hoverData.fieldValues.length, 1, 'should have 1 field value');\n  t.equal(hoverData.fieldValues[0].labelMessage, 'layer.wms.hover', 'should have WMS hover label');\n\n  // Test getHoverData with WMS feature info - string format\n  const wmsFeatureInfoObject = {\n    wmsFeatureInfo: '<FeatureInfo><Feature>Test feature data</Feature></FeatureInfo>'\n  };\n  const wmsHoverData = layer.getHoverData(wmsFeatureInfoObject, null, [], null, {index: 0});\n  t.ok(wmsHoverData.wmsFeatureData, 'should have wmsFeatureData array');\n  t.equal(wmsHoverData.wmsFeatureData.length, 1, 'should have 1 feature data item');\n\n  // Test getHoverData with WMS feature info - array format\n  const wmsFeatureInfoArrayObject = {\n    wmsFeatureInfo: [\n      {name: 'RED BAND', value: '255.0'},\n      {name: 'GREEN BAND', value: '195.0'},\n      {name: 'BLUE BAND', value: '0.0'}\n    ]\n  };\n  const wmsArrayHoverData = layer.getHoverData(wmsFeatureInfoArrayObject, null, [], null, {\n    index: 0\n  });\n  t.equal(wmsArrayHoverData.wmsFeatureData.length, 3, 'should have 3 feature data items');\n  t.equal(\n    wmsArrayHoverData.wmsFeatureData[0].name,\n    'RED BAND',\n    'should have correct attribute name'\n  );\n\n  t.end();\n});\n\ntest('#WMSLayer -> renderLayer variations', t => {\n  const baseLayer = createWMSLayer();\n\n  // Test normal rendering\n  const deckLayers = baseLayer.renderLayer(MOCK_RENDER_OPTS);\n  t.ok(deckLayers, 'should create deck layers');\n  t.equal(deckLayers.length, 1, 'should render one deck layer');\n\n  const deckLayer = deckLayers[0];\n  t.equal(deckLayer.id, 'test-wms-layer-WMSLayer', 'should have correct layer id');\n  t.equal(deckLayer.props.pickable, true, 'should be pickable when tooltips enabled and queryable');\n  t.equal(deckLayer.props.serviceType, 'wms', 'should have correct service type');\n  t.deepEqual(deckLayer.props.layers, ['test_layer'], 'should have correct layers');\n  t.equal(deckLayer.props.opacity, 0.8, 'should have correct opacity');\n  t.ok(typeof deckLayer.props.onClick === 'function', 'should have onClick handler');\n\n  // Test with tooltips disabled\n  const tooltipsDisabledOpts = {\n    ...MOCK_RENDER_OPTS,\n    interactionConfig: {tooltip: {enabled: false}}\n  };\n  const nonPickableLayers = baseLayer.renderLayer(tooltipsDisabledOpts);\n  t.equal(\n    nonPickableLayers[0].props.pickable,\n    false,\n    'should not be pickable when tooltips disabled'\n  );\n\n  // Test with non-queryable layer\n  const nonQueryableLayer = createWMSLayer({\n    visConfig: {wmsLayer: {...MOCK_WMS_LAYER_CONFIG, queryable: false}}\n  });\n  const nonQueryableLayers = nonQueryableLayer.renderLayer(MOCK_RENDER_OPTS);\n  t.equal(\n    nonQueryableLayers[0].props.pickable,\n    false,\n    'should not be pickable when layer is not queryable'\n  );\n\n  // Test without wmsLayer config\n  const unconfiguredLayer = new WMSLayer({id: 'test-wms-layer', dataId: 'wms-dataset'});\n  const emptyLayers = unconfiguredLayer.renderLayer(MOCK_RENDER_OPTS);\n  t.equal(emptyLayers.length, 0, 'should not render any layers without wmsLayer config');\n\n  t.end();\n});\n\ntest('#WMSLayer -> formatLayerData', t => {\n  const layer = createWMSLayer();\n\n  const mockDatasets = {'wms-dataset': MOCK_WMS_DATASET};\n  const layerData = layer.formatLayerData(mockDatasets);\n  t.ok(layerData, 'should return layer data object');\n  t.equal(layerData.tilesetDataUrl, 'http://example.com/wms', 'should have tileset data URL');\n  t.ok(layerData.metadata, 'should have metadata');\n\n  t.end();\n});\n\ntest('#WMSLayer -> updateLayerMeta', t => {\n  const layer = createWMSLayer();\n\n  // Test with non-WMS dataset\n  t.doesNotThrow(() => {\n    layer.updateLayerMeta({type: 'csv'});\n  }, 'should not throw with non-WMS dataset');\n\n  // Test with WMS dataset\n  t.doesNotThrow(() => {\n    layer.updateLayerMeta({type: 'wms-tile'});\n  }, 'should update meta with WMS dataset');\n\n  t.deepEqual(\n    layer.meta.bounds,\n    [\n      [-180, -90],\n      [180, 90]\n    ],\n    'should set bounds from bounding box'\n  );\n\n  t.end();\n});\n\ntest('#WMSLayer -> parseWMSFeatureInfo', t => {\n  const layer = createWMSLayer();\n\n  const xmlString = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<wfs:FeatureCollection xmlns=\"http://www.opengis.net/wfs\" xmlns:wfs=\"http://www.opengis.net/wfs\" xmlns:gml=\"http://www.opengis.net/gml\" xmlns:conus=\"http://conus\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.opengis.net/wfs https://opengeo.ncep.noaa.gov/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd\">\n  <gml:featureMember>\n    <conus:conus_cref_qcd fid=\"\">\n      <conus:RED_BAND>255.0</conus:RED_BAND>\n      <conus:GREEN_BAND>195.0</conus:GREEN_BAND>\n      <conus:BLUE_BAND>0.0</conus:BLUE_BAND>\n      <conus:ALPHA_BAND>255.0</conus:ALPHA_BAND>\n    </conus:conus_cref_qcd>\n  </gml:featureMember>\n</wfs:FeatureCollection>`;\n\n  const parsedAttributes = layer.parseWMSFeatureInfo(xmlString);\n  t.ok(Array.isArray(parsedAttributes), 'should return an array');\n  t.equal(parsedAttributes.length, 4, 'should parse 4 attributes');\n  t.equal(parsedAttributes[0].name, 'RED BAND', 'should parse RED_BAND attribute name correctly');\n  t.equal(parsedAttributes[0].value, '255.0', 'should parse RED_BAND attribute value correctly');\n  t.equal(\n    parsedAttributes[1].name,\n    'GREEN BAND',\n    'should parse GREEN_BAND attribute name correctly'\n  );\n  t.equal(parsedAttributes[1].value, '195.0', 'should parse GREEN_BAND attribute value correctly');\n\n  // Test edge cases\n  t.deepEqual(layer.parseWMSFeatureInfo(''), [], 'should return empty array for empty XML');\n  t.deepEqual(\n    layer.parseWMSFeatureInfo('<invalid>xml</invalid>'),\n    [],\n    'should return empty array for invalid XML'\n  );\n\n  t.end();\n});\n\ntest('#WMSLayer -> findDefaultLayerProps', t => {\n  // Test with valid WMS dataset\n  const result = WMSLayer.findDefaultLayerProps(MOCK_WMS_DATASET);\n  t.ok(result.props, 'should return props object');\n  t.equal(result.props.length, 1, 'should return one layer prop for WMS dataset');\n  t.equal(result.props[0].label, 'Test WMS Dataset', 'should use dataset label');\n  t.deepEqual(\n    result.props[0].layers,\n    MOCK_WMS_DATASET.metadata.layers,\n    'should pass through all layers'\n  );\n\n  // Test with non-WMS dataset\n  const nonWmsResult = WMSLayer.findDefaultLayerProps({\n    type: 'csv',\n    metadata: {label: 'CSV Dataset'}\n  });\n  t.deepEqual(nonWmsResult.props, [], 'should return empty props for non-WMS dataset');\n\n  // Test with empty WMS dataset\n  const emptyWmsDataset = {type: 'wms-tile', metadata: {label: 'Empty WMS Dataset', layers: []}};\n  const emptyResult = WMSLayer.findDefaultLayerProps(emptyWmsDataset);\n  t.equal(\n    emptyResult.props.length,\n    1,\n    'should return one prop even for WMS dataset with no layers'\n  );\n  t.deepEqual(emptyResult.props[0].layers, [], 'should have empty layers array');\n\n  t.end();\n});\n\ntest('#WMSLayer -> edge cases and error handling', t => {\n  const layer = createWMSLayer();\n\n  // Test formatLayerData with missing dataset\n  t.doesNotThrow(() => {\n    const emptyLayerData = layer.formatLayerData({}, {}, {});\n    t.ok(emptyLayerData, 'should return layer data object even with missing dataset');\n    t.equal(emptyLayerData.tilesetDataUrl, null, 'should handle missing dataset gracefully');\n  }, 'should not throw error when formatting with missing dataset');\n\n  // Test layer visibility\n  const invisibleLayer = createWMSLayer({isVisible: false});\n  t.equal(invisibleLayer.config.isVisible, false, 'should respect initial visibility setting');\n\n  // Test hasHoveredObject when layer is not visible\n  const mockObjectInfo = {picked: true, layer: {props: {id: 'test-wms-layer'}}};\n  const hoveredObject = invisibleLayer.hasHoveredObject(mockObjectInfo);\n  t.ok(hoveredObject, 'should return hovered object even when layer is not visible');\n\n  // Test getWMSFeatureInfo with no deckLayerRef\n  layer['getWMSFeatureInfo'](100, 200)\n    .then(result => {\n      t.equal(result, null, 'should return null when no deckLayerRef');\n    })\n    .catch(_error => {\n      t.fail('should not throw error when no deckLayerRef');\n    });\n\n  // Test deckLayerRef storage\n  layer.renderLayer(MOCK_RENDER_OPTS);\n  t.ok(layer['deckLayerRef'], 'should store deckLayerRef after first render');\n\n  const firstRef = layer['deckLayerRef'];\n  layer.renderLayer(MOCK_RENDER_OPTS);\n  t.ok(layer['deckLayerRef'], 'should have deckLayerRef after second render');\n  t.notEqual(layer['deckLayerRef'], firstRef, 'should update deckLayerRef on new render');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser/reducers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {visStateReducer as reducer} from '@kepler.gl/reducers';\nimport CloneDeep from 'lodash/cloneDeep';\nimport test from 'tape-catch';\nimport {StateWFiles, testCsvDataId, testGeoJsonDataId} from 'test/helpers/mock-state';\nimport {VisStateActions} from '@kepler.gl/actions';\n\ntest('#visStateReducer -> COPY_TABLE_COLUMN', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n  const nextState = reducer(initialState, VisStateActions.copyTableColumn());\n  t.equal(nextState, initialState, 'state should not change when input is not given');\n\n  reducer(nextState, VisStateActions.copyTableColumn(testCsvDataId, 'gps_data.lat'));\n  const expectedCopy =\n    '29.9900937\\n29.9927699\\n29.9907261\\n29.9870074\\n29.9923041\\n29.9968249\\n30.0037217\\n30.0116207\\n30.0208925\\n30.0218999\\n30.0229344\\n30.0264237\\n30.0292134\\n30.034391\\n30.0352752\\n30.0395918\\n30.0497387\\n30.0538936\\n30.060911\\n30.060334\\n30.0554663\\n30.0614122\\n30.0612697\\n30.0610977';\n\n  t.equal(window.clipboardData.data().text, expectedCopy, 'should copy to clipboard');\n\n  reducer(nextState, VisStateActions.copyTableColumn(testGeoJsonDataId, '_geojson'));\n\n  const expectedCopy2 =\n    `{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-122.40115971858505,37.78202426695214],[-122.40037436684331,37.78264451554517],[-122.40001902006377,37.782925153640136],[-122.39989147796784,37.783025880124256],[-122.398930331093,37.783784933304034]]]},\"properties\":{\"OBJECTID\":1,\"ZIP_CODE\":94107,\"ID\":94107,\"TRIPS\":11,\"RATE\":\"a\",\"OBJ\":{\"id\":1}}}\\n` +\n    `{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-122.39249932896719,37.79376881413398],[-122.39189026034138,37.79427854456892],[-122.39166672864975,37.794132425256194],[-122.39172303426619,37.79410061945832],[-122.39249932896719,37.79376881413398]]]},\"properties\":{\"OBJECTID\":2,\"ZIP_CODE\":94105,\"ID\":94105,\"TRIPS\":4,\"RATE\":\"b\",\"OBJ\":{\"id\":2}}}\\n` +\n    `{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-122.39249932896719,37.79376881413398],[-122.39189026034138,37.79427854456892],[-122.39198201510793,37.79387190612868],[-122.39249932896719,37.79376881413398]]]},\"properties\":{\"OBJECTID\":3,\"ZIP_CODE\":94109,\"ID\":94109,\"TRIPS\":20,\"RATE\":null,\"OBJ\":{\"id\":3}}}\\n` +\n    `{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-122.39249932896719,37.79376881413398],[-122.39166672864975,37.794132425256194],[-122.39172303426619,37.79410061945832],[-122.3916732283519,37.7940478541246],[-122.39198201510793,37.79387190612868],[-122.39249932896719,37.79376881413398]]]},\"properties\":{\"OBJECTID\":4,\"ZIP_CODE\":94111,\"ID\":94111,\"RATE\":\"c\",\"OBJ\":{\"id\":4},\"TRIPS\":null}}\\n` +\n    `{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-122.39249932896719,37.79376881413398],[-122.39189026034138,37.79427854456892],[-122.39178886557242,37.794170982455135],[-122.39249932896719,37.79376881413398]]]},\"properties\":{\"OBJECTID\":5,\"ZIP_CODE\":94107,\"ID\":9409,\"RATE\":\"c\",\"OBJ\":{\"id\":5},\"TRIPS\":null}}`;\n\n  t.equal(window.clipboardData.data().text, expectedCopy2, 'should copy to clipboard');\n  t.end();\n});\n"
  },
  {
    "path": "test/browser-debug.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// test in puppeteer browser\n// require('@probe.gl/test-utils/polyfill');\nimport Console from 'global/console';\n\nconst test = require('tape-catch');\nconst enableDOMLogging = require('@probe.gl/test-utils')._enableDOMLogging;\nenableDOMLogging();\n\ntest.onFinish(window.browserTestDriver_finish);\ntest.onFailure(args => {\n  Console.log(args);\n  window.browserTestDriver_fail();\n});\n\ntest('Browser tests', t => {\n  // running all tests in browser for debugging\n  require('./node/index.js');\n  // require('./browser/index.js');\n  // require('./browser-headless/index.js');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/browser-drive.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst fs = require('fs');\nconst resolve = require('path').resolve;\nconst BrowserTestDriver = require('@probe.gl/test-utils').BrowserTestDriver;\n\nconst configPath = resolve(__dirname, './webpack.config.js');\nconst myArgs = process.argv.slice(2);\nconst debug = myArgs.length && myArgs[0] === 'debug';\n\nfunction getExecutablePath(dir) {\n  try {\n    const puppeteer = require(dir ? `${dir}/node_modules/puppeteer` : 'puppeteer');\n    const executablePath = puppeteer.executablePath();\n    if (fs.existsSync(executablePath)) {\n      return executablePath;\n    }\n  } catch (err) {\n    // ignore\n  }\n  return null;\n}\n\nfunction getOptions(opt) {\n  const entryPath = opt.debug\n    ? resolve(__dirname, './browser-debug.js')\n    : resolve(__dirname, './browser-headless.js');\n  const headless = !opt.debug;\n\n  const options = {\n    server: {\n      command: 'webpack-dev-server',\n      arguments: ['--entry', entryPath, '--config', configPath, '--env.mode=test']\n    },\n    browser: {\n      defaultViewport: {width: 1200, height: 800}\n    },\n    headless,\n    executablePath: getExecutablePath()\n  };\n\n  // eslint-disable-next-line no-console\n  console.log(options);\n  return options;\n}\n\nfunction runBrowserTest() {\n  const options = getOptions({debug});\n  return new BrowserTestDriver().run(options);\n}\n\nrunBrowserTest();\n"
  },
  {
    "path": "test/browser-headless/component/map-container-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils';\n\nimport sinon from 'sinon';\nimport test from 'tape';\nimport {\n  appInjector,\n  MapContainerFactory,\n  MapControlFactory,\n  MapPopoverFactory,\n  mapFieldsSelector,\n  MapViewStateContextProvider\n} from '@kepler.gl/components';\n// import {Map} from 'react-map-gl'; // see other TODO below\nimport {gl, InteractionTestRunner} from '@deck.gl/test-utils';\n\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\n\nimport {mockKeplerProps, expectedLayerHoverProp} from '../../helpers/mock-state';\nimport {act} from 'react-dom/test-utils';\nimport {STYLED_COMPONENTS_DUPLICATED_ENTRIES} from '../../helpers/utils';\n\nconst MapContainer = appInjector.get(MapContainerFactory);\nconst MapPopover = appInjector.get(MapPopoverFactory);\nconst MapControl = appInjector.get(MapControlFactory);\nconst initialProps = mapFieldsSelector(mockKeplerProps);\n\nconst initialState = {mapState: {latitude: 0, longitude: 0}};\nconst mockStore = configureStore();\n\ntest('MapContainerFactory - display all options', t => {\n  const onMapStyleLoaded = sinon.spy();\n  const onLayerClick = sinon.spy();\n  const store = mockStore(initialState);\n\n  const props = {\n    ...initialProps,\n    mapStyle: {\n      bottomMapStyle: {layers: [], name: 'foo'},\n      visibleLayerGroups: {}\n    },\n    onMapStyleLoaded,\n    visStateActions: {\n      ...initialProps.visStateActions,\n      onLayerClick\n    },\n    mapboxApiAccessToken: 'pyx-11'\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <MapViewStateContextProvider mapState={props.mapState}>\n            <MapContainer {...props} />\n          </MapViewStateContextProvider>\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'MapContainer should not fail');\n\n  t.equal(wrapper.find(MapControl).length, 1, 'Should display 1 MapControl');\n\n  // TODO: why is this failing without Map in quotes\n  t.equal(wrapper.find('Map').length, 1, 'Should display 1 Map');\n  // Can't test overlay because mapboxgl is not supported in chromium\n  t.equal(wrapper.find('Attribution').length, 1, 'Should display 1 Attribution');\n\n  const instance = wrapper.find('MapContainer').instance();\n\n  instance._onMapboxStyleUpdate();\n\n  t.equal(onMapStyleLoaded.called, true, 'Should be calling onMapStyleLoaded');\n\n  instance._onCloseMapPopover();\n  t.equal(onLayerClick.called, true, 'Should be calling onLayerClick');\n\n  t.end();\n});\n\ntest('MapContainerFactory - _renderDeckOverlay', t => {\n  const props = {\n    ...initialProps,\n    mapboxApiAccessToken: 'pyx-11'\n  };\n  const store = mockStore(initialState);\n\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <Provider store={store}>\n        <IntlWrapper>\n          <MapViewStateContextProvider mapState={props.mapState}>\n            <MapContainer {...props} />\n          </MapViewStateContextProvider>\n        </IntlWrapper>\n      </Provider>\n    );\n  }, 'MapContainer should not fail');\n\n  const instance = wrapper.find('MapContainer').instance();\n  const _renderDeckOverlay = sinon.spy(instance, '_renderDeckOverlay');\n  act(() => instance.forceUpdate());\n  wrapper.update();\n\n  t.ok(_renderDeckOverlay.calledOnce, '_renderDeckOverlay be called once');\n\n  const args = _renderDeckOverlay.args[0];\n  _renderDeckOverlay.restore();\n\n  // Getting the DeckGl instance\n  const divWrapper = instance._renderDeckOverlay(...args);\n  const DeckGl = divWrapper.props.children;\n\n  t.ok(typeof DeckGl.props.getTooltip === 'function', 'DeckGl should receive getTooltip prop');\n  t.equal(\n    DeckGl.props.getTooltip({x: -1, y: -1, pixel: [-1, -1]}),\n    null,\n    'getTooltip should return null for invalid hover coordinates'\n  );\n\n  const clickEvents = [];\n  const hoverEvents = [];\n\n  function onBeforeEvents() {\n    // reset logs\n    clickEvents.length = 0;\n    hoverEvents.length = 0;\n  }\n  const expectedCoordinate = [31.21911171679643, 30.040037294002644];\n  const expectedInfo = {\n    color: {0: 16, 1: 0, 2: 0, 3: 1},\n    coordinate: expectedCoordinate,\n    index: 15,\n    // layer: {}, only test id\n    // object: {data: allData[15], position: [], index: 15}, // test seperately\n    picked: true,\n    pixel: [192, 187],\n    // viewport: {}, // not tested\n    x: 192,\n    y: 187\n  };\n\n  const testCase = {\n    title: 'Picking',\n    props: {\n      ...DeckGl.props,\n      gl,\n      width: 800,\n      height: 800,\n      onClick: (info, event) => clickEvents.push({info, event}),\n      onHover: (info, event) => {\n        info.mapIndex = 0;\n        hoverEvents.push({info, event});\n      }\n    },\n    getTestCases: assert => [\n      {\n        name: 'hover',\n        events: [{type: 'mousemove', x: 200, y: 200}, {wait: 100}],\n        onBeforeEvents,\n        // eslint-disable-next-line max-statements\n        onAfterEvents: () => {\n          assert.is(hoverEvents.length, 1, 'onHover is called');\n          assert.is(hoverEvents[0].info.index, 15, 'object is picked');\n          assert.is(hoverEvents[0].info.picked, true, 'object is picked');\n          assert.is(hoverEvents[0].info.mapIndex, 0, 'onHover includes mapIndex value');\n\n          [\n            // test picking info\n            ('color', 'coordinate', 'pixel', 'x', 'y')\n          ].forEach(key => {\n            assert.deepEqual(\n              hoverEvents[0].info[key],\n              expectedInfo[key],\n              `picking info.${key} should be correct`\n            );\n          });\n\n          // test picking info.object\n          t.ok(hoverEvents[0].info.object, 'should have info.object');\n          t.deepEqual(\n            hoverEvents[0].info.object,\n            props.visState.layerData[0].data[15],\n            'object should be layer data'\n          );\n          t.is(\n            hoverEvents[0].info.layer.id,\n            props.visState.layers[0].id,\n            'layer id should be correct'\n          );\n\n          // asign info object to to state and test map popover\n          const propsWithHoverInfo = {\n            ...initialProps,\n            mapboxApiAccessToken: 'pyx-11',\n            visState: {\n              ...initialProps.visState,\n              hoverInfo: hoverEvents[0].info,\n              mousePos: {\n                ...initialProps.visState.mousePos,\n                coordinate: expectedCoordinate,\n                mousePosition: [200, 200]\n              }\n            }\n          };\n\n          t.doesNotThrow(() => {\n            wrapper = mountWithTheme(\n              <Provider store={store}>\n                <IntlWrapper>\n                  <MapViewStateContextProvider mapState={propsWithHoverInfo.mapState}>\n                    <MapContainer {...propsWithHoverInfo} />\n                  </MapViewStateContextProvider>\n                </IntlWrapper>\n              </Provider>\n            );\n          }, 'render map container with map popover should not fail');\n\n          t.equal(wrapper.find(MapPopover).length, 1, 'should render 1 MapPopover');\n          const mapPopoverProps = wrapper.find(MapPopover).at(0).props();\n\n          // test MapPopoverProp\n          testMapPopoverProp(t, mapPopoverProps);\n          t.equal(\n            wrapper.find('.map-popover').length,\n            STYLED_COMPONENTS_DUPLICATED_ENTRIES,\n            'should render .map-popover'\n          );\n          t.equal(wrapper.find('table').length, 1, 'should render 1 table');\n\n          const table = wrapper.find('table').at(0);\n          const rows = table.find('.layer-hover-info__row');\n          t.equal(rows.length, 5, 'should render 5 rows');\n\n          const expectedTooltips = [\n            ['gps_data.utc_timestamp', '2016-09-17 00:24:24'],\n            ['gps_data.types', 'driver_analytics'],\n            ['epoch', '1472754400000'],\n            ['has_result', ''],\n            ['uid', '1']\n          ];\n\n          for (let i = 0; i < 5; i++) {\n            t.equal(\n              rows.at(i).find('.row__name').text(),\n              expectedTooltips[i][0],\n              'row name should be correct'\n            );\n            t.equal(\n              rows.at(i).find('.row__value').text(),\n              expectedTooltips[i][1],\n              'row value should be correct'\n            );\n          }\n        }\n      }\n    ]\n  };\n\n  new InteractionTestRunner(testCase.props)\n    .add(testCase.getTestCases(t))\n    .run({\n      onTestStart: tc => t.comment(tc.name)\n    })\n    .then(() => t.end());\n});\n\nfunction testMapPopoverProp(t, mapPopoverProp) {\n  const expected = {\n    container: 'test exist',\n    coordinate: false,\n    frozen: false,\n    layerHoverProp: 'test seperate',\n    onClose: 'test exist',\n    x: 200,\n    y: 200,\n    zoom: 13,\n    onSetFeatures: sinon.spy(),\n    setSelectedFeature: sinon.spy(),\n    featureCollection: {type: 'FeatureCollection', features: []}\n  };\n  t.deepEqual(\n    Object.keys(mapPopoverProp).sort(),\n    Object.keys(expected).sort(),\n    'MapPopover should receive corrent props'\n  );\n\n  t.equal(\n    mapPopoverProp.coordinate,\n    expected.coordinate,\n    `MapPopover.props.coordinate should be correct`\n  );\n  t.equal(mapPopoverProp.frozen, expected.frozen, `MapPopover.props.frozen should be correct`);\n  t.equal(mapPopoverProp.x, expected.x, `MapPopover.props.x should be correct`);\n  t.equal(mapPopoverProp.y, expected.y, `MapPopover.props.y should be correct`);\n  t.equal(mapPopoverProp.zoom, expected.zoom, `MapPopover.props.zoom should be correct`);\n\n  testlayerHoverProp(t, mapPopoverProp.layerHoverProp);\n}\n\nfunction testlayerHoverProp(t, layerHoverProp) {\n  t.deepEqual(\n    Object.keys(layerHoverProp).sort(),\n    Object.keys(expectedLayerHoverProp).sort(),\n    'layerHoverProp should have corrent props'\n  );\n\n  t.equal(\n    layerHoverProp.currentTime,\n    expectedLayerHoverProp.currentTime,\n    `layerHoverProp.currentTime should be correct`\n  );\n\n  t.equal(\n    layerHoverProp.data.values(),\n    expectedLayerHoverProp.data.values(),\n    `layerHoverProp.data should be correct`\n  );\n\n  t.equal(\n    layerHoverProp.fields,\n    expectedLayerHoverProp.fields,\n    `layerHoverProp.fields should be correct`\n  );\n\n  t.equal(\n    layerHoverProp.fieldsToShow,\n    expectedLayerHoverProp.fieldsToShow,\n    `layerHoverProp.fieldsToShow should be correct`\n  );\n  t.equal(\n    layerHoverProp.layer,\n    expectedLayerHoverProp.layer,\n    `layerHoverProp.layer should be correct`\n  );\n}\n\n/*\ntest('MapContainerFactory - _renderEditorContextMenu', t => {\n  const props = {\n    ...initialProps,\n    mapboxApiAccessToken: 'pyx-11'\n  };\n  let wrapper;\n  t.doesNotThrow(() => {\n    wrapper = mountWithTheme(\n      <IntlWrapper>\n        <MapViewStateContextProvider mapState={props.mapState}>\n          <MapContainer {...props} />\n        </MapViewStateContextProvider>\n      </IntlWrapper>\n    );\n  }, 'MapContainer should not fail with intial props');\n\n  const instance = wrapper.find('MapContainer').instance();\n  const _renderEditorContextMenu = sinon.spy(instance, '_renderEditorContextMenu');\n  instance.forceUpdate();\n  wrapper.update();\n\n  t.ok(_renderEditorContextMenu.calledOnce, '_renderEditorContextMenu be called once');\n\n  const args = _renderEditorContextMenu.args[0];\n  _renderEditorContextMenu.restore();\n\n  // React-map-gl doesnt render overlays when mapboxgl is not supported\n  // here we manually calling the render function to test Editor\n  const editor = instance._renderEditorContextMenu(...args);\n\n  let editorWrapper;\n  t.doesNotThrow(() => {\n    editorWrapper = mountWithTheme(<IntlWrapper>{editor}</IntlWrapper>);\n  }, 'Editor render should not fail');\n\n  t.equal(editorWrapper.find(Editor).length, 1, 'should render 1 Editor');\n  t.end();\n});\n*/\n"
  },
  {
    "path": "test/browser-headless/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst configure = require('enzyme').configure;\nconst Adapter = require('@cfaester/enzyme-adapter-react-18').default;\nconfigure({adapter: new Adapter()});\n\n// test components\nimport './component/map-container-test';\n"
  },
  {
    "path": "test/browser-headless.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// test in puppeteer browser\nrequire('@probe.gl/test-utils/polyfill');\n// ensure probe gets access to pure console first\nrequire('@probe.gl/log');\n\nconst test = require('tape-catch');\nconst enableDOMLogging = require('@probe.gl/test-utils')._enableDOMLogging;\nlet failed = false;\ntest.onFailure(() => {\n  failed = true;\n  window.browserTestDriver_fail();\n});\ntest.onFinish(window.browserTestDriver_finish || (() => {}));\n\n// tap-browser-color alternative\nenableDOMLogging({\n  getStyle: () => ({background: failed ? '#F28E82' : '#8ECA6C'})\n});\n\ntest('Browser tests', t => {\n  require('./browser-headless/index.js');\n  t.end();\n});\n"
  },
  {
    "path": "test/fixtures/config_v0_arc_cluster_point.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedConfigV0 = {\n  version: 'v0',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: '5em',\n          dataId: 'something',\n          label: 'trip arc',\n          color: [18, 147, 154],\n          columns: {\n            lat0: {\n              value: 'begintrip_lat',\n              fieldIdx: 3\n            },\n            lng0: {\n              value: 'begintrip_lng',\n              fieldIdx: 4\n            },\n            lat1: {\n              value: 'dropoff_lat',\n              fieldIdx: 27\n            },\n            lng1: {\n              value: 'dropoff_lng',\n              fieldIdx: 28\n            }\n          },\n          isVisible: false,\n          isAggregated: false,\n          type: 'arc',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            opacity: 0.8,\n            targetColor: null,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            sizeRange: [0, 10],\n            'hi-precision': false\n          }\n        },\n        {\n          id: 'gtyf',\n          dataId: 'something',\n          label: 'begintrip cluster',\n          color: [241, 92, 23],\n          columns: {\n            lat: {\n              value: 'begintrip_lat',\n              fieldIdx: 3\n            },\n            lng: {\n              value: 'begintrip_lng',\n              fieldIdx: 4\n            }\n          },\n          isVisible: false,\n          isAggregated: true,\n          type: 'cluster',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            clusterRadius: 40,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [1, 40],\n            'hi-precision': false\n          },\n          enable3d: false,\n          colorAggregation: 'average',\n          sizeAggregation: 'average'\n        },\n        {\n          id: 'rj',\n          dataId: 'something',\n          label: 'begintrip',\n          color: [218, 112, 191],\n          columns: {\n            lat: {\n              value: 'begintrip_lat',\n              fieldIdx: 3\n            },\n            lng: {\n              value: 'begintrip_lng',\n              fieldIdx: 4\n            },\n            altitude: {\n              value: null,\n              fieldIdx: -1,\n              optional: true\n            }\n          },\n          isVisible: true,\n          isAggregated: false,\n          type: 'point',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        },\n        {\n          id: '9e7',\n          dataId: 'something',\n          label: 'dropoff',\n          color: [255, 153, 31],\n          columns: {\n            lat: {\n              value: 'dropoff_lat',\n              fieldIdx: 27\n            },\n            lng: {\n              value: 'dropoff_lng',\n              fieldIdx: 28\n            },\n            altitude: {\n              value: null,\n              fieldIdx: -1,\n              optional: true\n            }\n          },\n          isVisible: false,\n          isAggregated: false,\n          type: 'point',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        },\n        {\n          id: 'ej4',\n          dataId: 'something',\n          label: 'request',\n          color: [130, 154, 227],\n          columns: {\n            lat: {\n              value: 'request_lat',\n              fieldIdx: 60\n            },\n            lng: {\n              value: 'request_lng',\n              fieldIdx: 61\n            },\n            altitude: {\n              value: null,\n              fieldIdx: -1,\n              optional: true\n            }\n          },\n          isVisible: false,\n          isAggregated: false,\n          type: 'point',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            something: ['begintrip_timestamp_local', 'dropoff_timestamp_local']\n          }\n        }\n      },\n      layerBlending: 'normal'\n    },\n    mapStyle: {\n      styleType: 'dark',\n      topLayerGroups: {},\n      visibleLayerGroups: {\n        label: true,\n        places: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      },\n      buildingLayer: {\n        isVisible: false,\n        color: [32, 32, 37],\n        opacity: 0.7\n      }\n    },\n    mapState: {\n      bearing: 0,\n      dragRotate: false,\n      latitude: 37.785337133852245,\n      longitude: -122.31216420269313,\n      pitch: 0,\n      zoom: 10.007195441591573\n    }\n  }\n};\n\nexport const parsedConfigV0 = {\n  visState: {\n    filters: [],\n    layers: [\n      {\n        id: '5em',\n        type: 'arc',\n        config: {\n          dataId: 'something',\n          label: 'trip arc',\n          color: [18, 147, 154],\n          columns: {\n            lat0: 'begintrip_lat',\n            lng0: 'begintrip_lng',\n            lat1: 'dropoff_lat',\n            lng1: 'dropoff_lng'\n          },\n          isVisible: false,\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            opacity: 0.8,\n            targetColor: null,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            sizeRange: [0, 10],\n            'hi-precision': false\n          }\n        }\n      },\n      {\n        id: 'gtyf',\n        type: 'cluster',\n        config: {\n          dataId: 'something',\n          label: 'begintrip cluster',\n          color: [241, 92, 23],\n          columns: {\n            lat: 'begintrip_lat',\n            lng: 'begintrip_lng'\n          },\n          isVisible: false,\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            clusterRadius: 40,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [1, 40],\n            'hi-precision': false,\n            enable3d: false,\n            colorAggregation: 'average',\n            sizeAggregation: 'average'\n          }\n        }\n      },\n      {\n        id: 'rj',\n        type: 'point',\n        config: {\n          dataId: 'something',\n          label: 'begintrip',\n          color: [218, 112, 191],\n          columns: {\n            lat: 'begintrip_lat',\n            lng: 'begintrip_lng',\n            altitude: null\n          },\n          isVisible: true,\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            strokeColorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            strokeColor: [218, 112, 191],\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        }\n      },\n      {\n        id: '9e7',\n        type: 'point',\n        config: {\n          dataId: 'something',\n          label: 'dropoff',\n          color: [255, 153, 31],\n          columns: {\n            lat: 'dropoff_lat',\n            lng: 'dropoff_lng',\n            altitude: null\n          },\n          isVisible: false,\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            strokeColor: [255, 153, 31],\n            strokeColorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        }\n      },\n      {\n        id: 'ej4',\n        type: 'point',\n        config: {\n          dataId: 'something',\n          label: 'request',\n          color: [130, 154, 227],\n          columns: {\n            lat: 'request_lat',\n            lng: 'request_lng',\n            altitude: null\n          },\n          isVisible: false,\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            radius: 10,\n            fixedRadius: false,\n            opacity: 0.8,\n            outline: false,\n            thickness: 2,\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            strokeColor: [130, 154, 227],\n            strokeColorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            radiusRange: [0, 50],\n            'hi-precision': false\n          }\n        }\n      }\n    ],\n    interactionConfig: {\n      tooltip: {\n        enabled: true,\n        fieldsToShow: {\n          something: [\n            {\n              name: 'begintrip_timestamp_local',\n              format: null\n            },\n            {\n              name: 'dropoff_timestamp_local',\n              format: null\n            }\n          ]\n        }\n      },\n      brush: {\n        enabled: false\n      }\n    },\n    layerBlending: 'normal'\n  },\n  mapStyle: {\n    styleType: 'dark',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      places: true,\n      road: true,\n      border: false,\n      building: true,\n      water: true,\n      land: true\n    },\n    buildingLayer: {\n      isVisible: false,\n      color: [32, 32, 37],\n      opacity: 0.7\n    }\n  },\n  mapState: {\n    bearing: 0,\n    dragRotate: false,\n    latitude: 37.785337133852245,\n    longitude: -122.31216420269313,\n    pitch: 0,\n    zoom: 10.007195441591573\n  }\n};\n"
  },
  {
    "path": "test/fixtures/config_v0_geojson_point.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedConfigV0 = {\n  version: 'v0',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: 'z2',\n          dataId: '9sayb4m9r',\n          label: 'addresses_no_r',\n          color: [174, 14, 127],\n          columns: {\n            geojson: {\n              value: '_geojson',\n              fieldIdx: 0\n            }\n          },\n          isVisible: true,\n          isAggregated: false,\n          type: 'geojson',\n          colorField: {\n            name: 'addresses',\n            type: 'string'\n          },\n          colorScale: 'ordinal',\n          sizeField: null,\n          sizeScale: 'linear',\n          visConfig: {\n            colorRange: {\n              name: 'ColorBrewer Dark2-6',\n              type: 'qualitative',\n              category: 'ColorBrewer',\n              colors: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02'],\n              reversed: false\n            },\n            opacity: 0.8,\n            thickness: 2,\n            radius: 15.8,\n            sizeRange: [0, 10],\n            radiusRange: [0, 50],\n            elevationRange: [0, 500],\n            elevationScale: 5,\n            stroked: false,\n            filled: true,\n            extruded: false,\n            wireframe: false,\n            'hi-precision': false\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            '9sayb4m9r': ['fillColor', 'hi-precision', 'addresses', 'id']\n          }\n        }\n      },\n      layerBlending: 'normal'\n    },\n    mapStyle: {\n      styleType: 'dark',\n      topLayerGroups: {},\n      visibleLayerGroups: {\n        label: true,\n        places: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      },\n      buildingLayer: {\n        isVisible: false,\n        color: [32, 32, 37],\n        opacity: 0.7\n      }\n    },\n    mapState: {\n      bearing: 0,\n      dragRotate: false,\n      latitude: 39.98437808125723,\n      longitude: -105.15546105385808,\n      pitch: 0,\n      zoom: 16.060632707350504\n    }\n  }\n};\n\nexport const parsedConfigV0 = {\n  visState: {\n    filters: [],\n    layers: [\n      {\n        id: 'z2',\n        type: 'geojson',\n        config: {\n          dataId: '9sayb4m9r',\n          label: 'addresses_no_r',\n          color: [174, 14, 127],\n          columns: {\n            geojson: '_geojson'\n          },\n          isVisible: true,\n          colorField: {\n            name: 'addresses',\n            type: 'string'\n          },\n          colorScale: 'ordinal',\n          sizeField: null,\n          visConfig: {\n            colorRange: {\n              name: 'Dark2',\n              type: 'qualitative',\n              category: 'ColorBrewer',\n              colors: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02'],\n              reversed: false\n            },\n            strokeColorRange: {\n              name: 'Dark2',\n              type: 'qualitative',\n              category: 'ColorBrewer',\n              colors: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02'],\n              reversed: false\n            },\n            strokeColor: [174, 14, 127],\n            opacity: 0.8,\n            thickness: 2,\n            radius: 15.8,\n            sizeRange: [0, 10],\n            radiusRange: [0, 50],\n            heightRange: [0, 500],\n            elevationScale: 5,\n            stroked: false,\n            filled: true,\n            enable3d: false,\n            wireframe: false,\n            'hi-precision': false\n          }\n        }\n      }\n    ],\n    interactionConfig: {\n      tooltip: {\n        enabled: true,\n        fieldsToShow: {\n          '9sayb4m9r': [\n            {\n              name: 'fillColor',\n              format: null\n            },\n            {\n              name: 'hi-precision',\n              format: null\n            },\n            {\n              name: 'addresses',\n              format: null\n            },\n            {\n              name: 'id',\n              format: null\n            }\n          ]\n        }\n      },\n      brush: {\n        enabled: false\n      }\n    },\n    layerBlending: 'normal'\n  },\n  mapStyle: {\n    styleType: 'dark',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      places: true,\n      road: true,\n      border: false,\n      building: true,\n      water: true,\n      land: true\n    },\n    buildingLayer: {\n      isVisible: false,\n      color: [32, 32, 37],\n      opacity: 0.7\n    }\n  },\n  mapState: {\n    bearing: 0,\n    dragRotate: false,\n    latitude: 39.98437808125723,\n    longitude: -105.15546105385808,\n    pitch: 0,\n    zoom: 16.060632707350504\n  }\n};\n"
  },
  {
    "path": "test/fixtures/config_v0_geojson_poly_fill_ele.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedConfigV0 = {\n  config: {\n    mapState: {\n      bearing: 24,\n      dragRotate: true,\n      latitude: 37.76155771615539,\n      longitude: -122.47016491117604,\n      pitch: 50,\n      zoom: 11.821729469300175\n    },\n    mapStyle: {\n      buildingLayer: {\n        color: [32, 32, 37],\n        isVisible: false,\n        opacity: 0.7\n      },\n      styleType: 'dark',\n      topLayerGroups: {},\n      visibleLayerGroups: {\n        border: false,\n        building: true,\n        label: true,\n        land: true,\n        places: true,\n        road: true,\n        water: true\n      }\n    },\n    visState: {\n      filters: [],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            c0pd4bocg: ['ZIP_CODE', 'ID']\n          }\n        }\n      },\n      layerBlending: 'normal',\n      layers: [\n        {\n          color: [18, 147, 154, 255],\n          colorField: {\n            name: 'ZIP_CODE',\n            type: 'integer'\n          },\n          colorScale: 'quantize',\n          columns: {\n            geojson: {\n              fieldIdx: 0,\n              value: '_geojson'\n            }\n          },\n          dataId: 'c0pd4bocg',\n          id: '7oe',\n          isAggregated: false,\n          isVisible: true,\n          label: 'sf.zip.geo',\n          sizeField: {\n            name: 'ID',\n            type: 'integer'\n          },\n          sizeScale: 'linear',\n          type: 'geojson',\n          visConfig: {\n            colorRange: {\n              category: 'ColorBrewer',\n              colors: ['#ffffcc', '#d9f0a3', '#addd8e', '#78c679', '#31a354', '#006837'],\n              name: 'ColorBrewer YlGn-6',\n              reversed: false,\n              type: 'sequential'\n            },\n            elevationRange: [0, 500],\n            elevationScale: 5,\n            extruded: true,\n            filled: true,\n            'hi-precision': false,\n            opacity: 0.8,\n            radius: 10,\n            radiusRange: [0, 50],\n            sizeRange: [0, 10],\n            stroked: false,\n            thickness: 2,\n            wireframe: false\n          }\n        }\n      ]\n    }\n  },\n  version: 'v0'\n};\n\nexport const parsedConfigV0 = {\n  mapState: {\n    bearing: 24,\n    dragRotate: true,\n    latitude: 37.76155771615539,\n    longitude: -122.47016491117604,\n    pitch: 50,\n    zoom: 11.821729469300175\n  },\n  mapStyle: {\n    buildingLayer: {\n      color: [32, 32, 37],\n      isVisible: false,\n      opacity: 0.7\n    },\n    styleType: 'dark',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      border: false,\n      building: true,\n      label: true,\n      land: true,\n      places: true,\n      road: true,\n      water: true\n    }\n  },\n  visState: {\n    filters: [],\n    interactionConfig: {\n      tooltip: {\n        enabled: true,\n        fieldsToShow: {\n          c0pd4bocg: [\n            {\n              name: 'ZIP_CODE',\n              format: null\n            },\n            {\n              name: 'ID',\n              format: null\n            }\n          ]\n        }\n      },\n      brush: {\n        enabled: false\n      }\n    },\n    layerBlending: 'normal',\n    layers: [\n      {\n        id: '7oe',\n        type: 'geojson',\n        config: {\n          color: [18, 147, 154, 255],\n          colorField: {\n            name: 'ZIP_CODE',\n            type: 'integer'\n          },\n          colorScale: 'quantize',\n          columns: {\n            geojson: '_geojson'\n          },\n          dataId: 'c0pd4bocg',\n          isVisible: true,\n          label: 'sf.zip.geo',\n          heightField: {\n            name: 'ID',\n            type: 'integer'\n          },\n          visConfig: {\n            colorRange: {\n              category: 'ColorBrewer',\n              colors: ['#ffffcc', '#d9f0a3', '#addd8e', '#78c679', '#31a354', '#006837'],\n              name: 'YlGn',\n              reversed: false,\n              type: 'sequential'\n            },\n            strokeColorRange: {\n              category: 'ColorBrewer',\n              colors: ['#ffffcc', '#d9f0a3', '#addd8e', '#78c679', '#31a354', '#006837'],\n              name: 'YlGn',\n              reversed: false,\n              type: 'sequential'\n            },\n            strokeColor: [18, 147, 154, 255],\n            heightRange: [0, 500],\n            elevationScale: 5,\n            enable3d: true,\n            filled: true,\n            'hi-precision': false,\n            opacity: 0.8,\n            radius: 10,\n            radiusRange: [0, 50],\n            sizeRange: [0, 10],\n            stroked: false,\n            thickness: 2,\n            wireframe: false\n          }\n        }\n      }\n    ]\n  }\n};\n"
  },
  {
    "path": "test/fixtures/config_v0_geojson_polygon.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedConfigV0 = {\n  version: 'v0',\n  config: {\n    visState: {\n      filters: [],\n      layers: [\n        {\n          id: 'mml',\n          dataId: 'pjcln7ts',\n          label: 'geojson_as_string',\n          color: [221, 178, 124, 255],\n          columns: {\n            geojson: {\n              value: 'simplified_shape',\n              fieldIdx: 4\n            }\n          },\n          isVisible: true,\n          isAggregated: false,\n          type: 'geojson',\n          colorField: {\n            name: 'c_riders',\n            type: 'integer'\n          },\n          colorScale: 'quantile',\n          sizeField: {\n            name: 'd_population',\n            type: 'integer'\n          },\n          sizeScale: 'linear',\n          visConfig: {\n            colorRange: {\n              name: 'Pink Wine',\n              type: 'sequential',\n              category: 'Uber',\n              colors: [\n                '#2C1E3D',\n                '#50315E',\n                '#764476',\n                '#9A5B88',\n                '#B77495',\n                '#CF91A3',\n                '#E0B1B3',\n                '#EDD1CA'\n              ],\n              reversed: false\n            },\n            opacity: 0.8,\n            thickness: 13,\n            radius: 10,\n            sizeRange: [0, 10],\n            radiusRange: [0, 50],\n            elevationRange: [0, 500],\n            elevationScale: 34,\n            stroked: false,\n            filled: true,\n            extruded: true,\n            wireframe: false,\n            'hi-precision': false\n          }\n        },\n        {\n          id: '2oc',\n          dataId: 'pjcln7ts',\n          label: 'new layer',\n          color: [136, 87, 44, 255],\n          columns: {\n            geojson: {\n              value: 'simplified_shape_v2',\n              fieldIdx: 3\n            }\n          },\n          isVisible: true,\n          isAggregated: false,\n          type: 'geojson',\n          colorField: null,\n          colorScale: 'quantile',\n          sizeField: {\n            name: 'c_avg_trips_per_rider',\n            type: 'real'\n          },\n          sizeScale: 'linear',\n          visConfig: {\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            opacity: 0.8,\n            thickness: 8,\n            radius: 10,\n            sizeRange: [0, 36.6],\n            radiusRange: [0, 50],\n            elevationRange: [0, 500],\n            elevationScale: 5,\n            stroked: true,\n            filled: false,\n            extruded: false,\n            wireframe: false,\n            'hi-precision': false\n          }\n        }\n      ],\n      interactionConfig: {},\n      layerBlending: 'additive'\n    },\n    mapStyle: {\n      styleType: 'dark',\n      topLayerGroups: {},\n      visibleLayerGroups: {\n        label: true,\n        places: true,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true\n      },\n      buildingLayer: {\n        isVisible: false,\n        color: [32, 32, 37],\n        opacity: 0.7\n      }\n    },\n    mapState: {\n      bearing: -23.571428571428555,\n      dragRotate: true,\n      latitude: 40.19134928087266,\n      longitude: -74.70082543743959,\n      pitch: 13.654118895255545,\n      zoom: 8.304174758743489\n    }\n  }\n};\n\nexport const parsedConfigV0 = {\n  visState: {\n    filters: [],\n    layers: [\n      {\n        id: 'mml',\n        type: 'geojson',\n        config: {\n          dataId: 'pjcln7ts',\n          label: 'geojson_as_string',\n          color: [221, 178, 124, 255],\n          columns: {\n            geojson: 'simplified_shape'\n          },\n          isVisible: true,\n          colorField: {\n            name: 'c_riders',\n            type: 'integer'\n          },\n          colorScale: 'quantile',\n          heightField: {\n            name: 'd_population',\n            type: 'integer'\n          },\n          visConfig: {\n            colorRange: {\n              name: 'Pink Wine',\n              type: 'sequential',\n              category: 'Uber',\n              colors: [\n                '#2C1E3D',\n                '#50315E',\n                '#764476',\n                '#9A5B88',\n                '#B77495',\n                '#CF91A3',\n                '#E0B1B3',\n                '#EDD1CA'\n              ],\n              reversed: false\n            },\n            strokeColor: [221, 178, 124, 255],\n            strokeColorRange: {\n              name: 'Pink Wine',\n              type: 'sequential',\n              category: 'Uber',\n              colors: [\n                '#2C1E3D',\n                '#50315E',\n                '#764476',\n                '#9A5B88',\n                '#B77495',\n                '#CF91A3',\n                '#E0B1B3',\n                '#EDD1CA'\n              ],\n              reversed: false\n            },\n            opacity: 0.8,\n            thickness: 13,\n            radius: 10,\n            sizeRange: [0, 10],\n            radiusRange: [0, 50],\n            heightRange: [0, 500],\n            elevationScale: 34,\n            stroked: false,\n            filled: true,\n            enable3d: true,\n            wireframe: false,\n            'hi-precision': false\n          }\n        }\n      },\n      {\n        id: '2oc',\n        type: 'geojson',\n        config: {\n          dataId: 'pjcln7ts',\n          label: 'new layer',\n          color: [136, 87, 44, 255],\n          columns: {\n            geojson: 'simplified_shape_v2'\n          },\n          isVisible: true,\n          colorField: null,\n          colorScale: 'quantile',\n          strokeColorField: null,\n          strokeColorScale: 'quantile',\n          sizeField: {\n            name: 'c_avg_trips_per_rider',\n            type: 'real'\n          },\n          visConfig: {\n            colorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            strokeColor: [136, 87, 44, 255],\n            strokeColorRange: {\n              name: 'Global Warming',\n              type: 'sequential',\n              category: 'Uber',\n              colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n            },\n            opacity: 0.8,\n            thickness: 8,\n            radius: 10,\n            sizeRange: [0, 36.6],\n            radiusRange: [0, 50],\n            heightRange: [0, 500],\n            elevationScale: 5,\n            stroked: true,\n            filled: false,\n            enable3d: false,\n            wireframe: false,\n            'hi-precision': false\n          }\n        }\n      }\n    ],\n    interactionConfig: {\n      tooltip: {\n        enabled: false\n      },\n      brush: {\n        enabled: false\n      }\n    },\n    layerBlending: 'additive'\n  },\n  mapStyle: {\n    styleType: 'dark',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      places: true,\n      road: true,\n      border: false,\n      building: true,\n      water: true,\n      land: true\n    },\n    buildingLayer: {\n      isVisible: false,\n      color: [32, 32, 37],\n      opacity: 0.7\n    }\n  },\n  mapState: {\n    bearing: -23.571428571428555,\n    dragRotate: true,\n    latitude: 40.19134928087266,\n    longitude: -74.70082543743959,\n    pitch: 13.654118895255545,\n    zoom: 8.304174758743489\n  }\n};\n"
  },
  {
    "path": "test/fixtures/geojson-style.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst geojsonString = `{\"type\":\"FeatureCollection\",\"features\":[{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15471672508885,39.98626910199207,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"radius\":1,\"id\":\"a1398a11-d1ce-421c-bf66-a456ff525de9\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1549804351595,39.98397605319894,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"94ed2c0f-27ce-416e-b3b4-6c00954b41f0\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15478982047146,39.98296589543148,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"radius\":3,\"id\":\"1f59be4c-82e8-4644-b3cf-4c1c0510cbb2\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15449343321012,39.98437157892626,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"radius\":5,\"id\":\"de9210a0-c48c-41bf-8463-4b0734d402c0\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15484576666667,39.98312416666666,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"9438eea5-dde0-4e5e-bdf5-7420eedbc419\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15494596666667,39.98422703333333,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"radius\":0.5,\"id\":\"9ecefa7c-4fc6-46b7-936c-580a6084ff47\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15522212737501,39.98433057912913,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"696a33f6-3ff2-45a9-8a11-ab541aa2152f\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15447046666667,39.9834028,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"a5b27f57-37a4-4576-ac4e-24dfb57730c3\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15487273333333,39.98379046666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"7a640dbe-e0ca-4c50-a43a-b65ec33305b5\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.154786,39.986418799999996,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"154cc349-c8b3-4b41-a008-364df1e6d83d\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15456956666667,39.984760566666665,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"932d45a2-bc4b-4825-9b9f-70b2415753b5\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15503543214001,39.983469561355626,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"a178d24c-1ea3-46cd-8762-3669d7b9d722\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15462816666667,39.984541766666666,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"41f8b94e-266c-450a-a7dc-60c159370ddb\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15494077274344,39.98493993797259,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"99ee26ca-13e1-4bef-bcd2-12cfc290d6ae\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15451193333332,39.98379226666666,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"de5103bb-2808-46b4-8012-76ceb5511fa9\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1547839,39.98589206666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"c5caef3d-abc9-4044-bb14-2f5999979d8d\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15513263333332,39.98269536666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"e0162a54-e2b2-4076-be24-4f550f0ac651\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1546226,39.98516726666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"e53ab272-7f6a-49b8-93f1-693bb57aa9f8\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1545185,39.98417893333333,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"fdc3fff3-5c35-430b-8bb4-9517473a8dc1\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15476356666666,39.98555806666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"f342e7bd-771d-4bf1-ad57-40d435f54fa0\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15468952992738,39.985742351897976,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"dbda33a4-b194-40e8-88a6-5e8816a97377\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1545091,39.983995666666665,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"5564b2fb-8368-4f81-a8b4-bebb30cbaaed\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15508416666667,39.985163,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"835f956f-6a86-4a7c-b1d6-3f32e4955a31\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15513636666667,39.983126066666664,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"8f8effff-f89d-417c-8e44-c8561f9f1eec\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.15478573333333,39.98606776666667,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"59552665-bdd6-4c0c-bd0d-5ee79ea4848b\"}},{\"geometry\":{\"type\":\"Point\",\"coordinates\":[-105.1547872,39.9866052,0]},\"type\":\"Feature\",\"properties\":{\"fillColor\":[77,193,156],\"id\":\"32df5401-38d1-4e96-8d1e-a29d2c7c3955\"}}]}`;\nexport default geojsonString;\n\nexport const featureString = `{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[41.8817441,-87.6335398],[41.88237441,-87.6331298]]},\"properties\":{\"lat\":41.8817441,\"lng\":-87.6335398,\"icon\":\"place\"}}`;\n\nexport const processedFeature = {\n  type: 'Feature',\n  geometry: {\n    type: 'LineString',\n    coordinates: [\n      [41.8817441, -87.6335398],\n      [41.88237441, -87.6331298]\n    ]\n  },\n  properties: {\n    lat: 41.8817441,\n    lng: -87.6335398,\n    icon: 'place'\n  }\n};\n\nexport const processedFeatureFields = [\n  {\n    name: '_geojson',\n    id: '_geojson',\n    displayName: '_geojson',\n    format: '',\n    fieldIdx: 0,\n    type: 'geojson',\n    analyzerType: 'GEOMETRY',\n    valueAccessor: values => values[0]\n  },\n  {\n    name: 'lat',\n    id: 'lat',\n    displayName: 'lat',\n    format: '',\n    fieldIdx: 1,\n    type: 'real',\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'lng',\n    id: 'lng',\n    displayName: 'lng',\n    format: '',\n    fieldIdx: 2,\n    type: 'real',\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[2]\n  },\n  {\n    name: 'icon',\n    id: 'icon',\n    displayName: 'icon',\n    format: '',\n    fieldIdx: 3,\n    type: 'string',\n    analyzerType: 'STRING',\n    valueAccessor: values => values[3]\n  }\n];\n\nexport const processedFeatureRows = [\n  [\n    {\n      type: 'Feature',\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [41.8817441, -87.6335398],\n          [41.88237441, -87.6331298]\n        ]\n      },\n      properties: {\n        lat: 41.8817441,\n        lng: -87.6335398,\n        icon: 'place'\n      }\n    },\n    41.8817441,\n    -87.6335398,\n    'place'\n  ]\n];\n\nexport const processedFields = [\n  {\n    name: '_geojson',\n    id: '_geojson',\n    displayName: '_geojson',\n    format: '',\n    fieldIdx: 0,\n    type: 'geojson',\n    analyzerType: 'GEOMETRY',\n    valueAccessor: values => values[0]\n  },\n  {\n    name: 'fillColor',\n    id: 'fillColor',\n    displayName: 'fillColor',\n    format: '',\n    fieldIdx: 1,\n    type: 'array',\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'radius',\n    id: 'radius',\n    displayName: 'radius',\n    format: '',\n    fieldIdx: 2,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[2]\n  },\n  {\n    name: 'id',\n    id: 'id',\n    displayName: 'id',\n    format: '',\n    fieldIdx: 3,\n    type: 'string',\n    analyzerType: 'STRING',\n    valueAccessor: values => values[3]\n  }\n];\n\nexport const processedRows = [\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15471672508885, 39.98626910199207, 0]},\n      type: 'Feature',\n      properties: {fillColor: [77, 193, 156], radius: 1, id: 'a1398a11-d1ce-421c-bf66-a456ff525de9'}\n    },\n    [77, 193, 156],\n    1,\n    'a1398a11-d1ce-421c-bf66-a456ff525de9'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1549804351595, 39.98397605319894, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '94ed2c0f-27ce-416e-b3b4-6c00954b41f0',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '94ed2c0f-27ce-416e-b3b4-6c00954b41f0'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15478982047146, 39.98296589543148, 0]},\n      type: 'Feature',\n      properties: {fillColor: [77, 193, 156], radius: 3, id: '1f59be4c-82e8-4644-b3cf-4c1c0510cbb2'}\n    },\n    [77, 193, 156],\n    3,\n    '1f59be4c-82e8-4644-b3cf-4c1c0510cbb2'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15449343321012, 39.98437157892626, 0]},\n      type: 'Feature',\n      properties: {fillColor: [77, 193, 156], radius: 5, id: 'de9210a0-c48c-41bf-8463-4b0734d402c0'}\n    },\n    [77, 193, 156],\n    5,\n    'de9210a0-c48c-41bf-8463-4b0734d402c0'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15484576666667, 39.98312416666666, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '9438eea5-dde0-4e5e-bdf5-7420eedbc419',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '9438eea5-dde0-4e5e-bdf5-7420eedbc419'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15494596666667, 39.98422703333333, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        radius: 0.5,\n        id: '9ecefa7c-4fc6-46b7-936c-580a6084ff47'\n      }\n    },\n    [77, 193, 156],\n    0.5,\n    '9ecefa7c-4fc6-46b7-936c-580a6084ff47'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15522212737501, 39.98433057912913, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '696a33f6-3ff2-45a9-8a11-ab541aa2152f',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '696a33f6-3ff2-45a9-8a11-ab541aa2152f'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15447046666667, 39.9834028, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'a5b27f57-37a4-4576-ac4e-24dfb57730c3',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'a5b27f57-37a4-4576-ac4e-24dfb57730c3'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15487273333333, 39.98379046666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '7a640dbe-e0ca-4c50-a43a-b65ec33305b5',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '7a640dbe-e0ca-4c50-a43a-b65ec33305b5'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.154786, 39.986418799999996, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '154cc349-c8b3-4b41-a008-364df1e6d83d',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '154cc349-c8b3-4b41-a008-364df1e6d83d'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15456956666667, 39.984760566666665, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '932d45a2-bc4b-4825-9b9f-70b2415753b5',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '932d45a2-bc4b-4825-9b9f-70b2415753b5'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15503543214001, 39.983469561355626, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'a178d24c-1ea3-46cd-8762-3669d7b9d722',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'a178d24c-1ea3-46cd-8762-3669d7b9d722'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15462816666667, 39.984541766666666, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '41f8b94e-266c-450a-a7dc-60c159370ddb',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '41f8b94e-266c-450a-a7dc-60c159370ddb'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15494077274344, 39.98493993797259, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '99ee26ca-13e1-4bef-bcd2-12cfc290d6ae',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '99ee26ca-13e1-4bef-bcd2-12cfc290d6ae'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15451193333332, 39.98379226666666, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'de5103bb-2808-46b4-8012-76ceb5511fa9',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'de5103bb-2808-46b4-8012-76ceb5511fa9'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1547839, 39.98589206666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'c5caef3d-abc9-4044-bb14-2f5999979d8d',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'c5caef3d-abc9-4044-bb14-2f5999979d8d'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15513263333332, 39.98269536666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'e0162a54-e2b2-4076-be24-4f550f0ac651',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'e0162a54-e2b2-4076-be24-4f550f0ac651'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1546226, 39.98516726666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'e53ab272-7f6a-49b8-93f1-693bb57aa9f8',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'e53ab272-7f6a-49b8-93f1-693bb57aa9f8'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1545185, 39.98417893333333, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'fdc3fff3-5c35-430b-8bb4-9517473a8dc1',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'fdc3fff3-5c35-430b-8bb4-9517473a8dc1'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15476356666666, 39.98555806666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'f342e7bd-771d-4bf1-ad57-40d435f54fa0',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'f342e7bd-771d-4bf1-ad57-40d435f54fa0'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15468952992738, 39.985742351897976, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: 'dbda33a4-b194-40e8-88a6-5e8816a97377',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    'dbda33a4-b194-40e8-88a6-5e8816a97377'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1545091, 39.983995666666665, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '5564b2fb-8368-4f81-a8b4-bebb30cbaaed',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '5564b2fb-8368-4f81-a8b4-bebb30cbaaed'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15508416666667, 39.985163, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '835f956f-6a86-4a7c-b1d6-3f32e4955a31',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '835f956f-6a86-4a7c-b1d6-3f32e4955a31'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15513636666667, 39.983126066666664, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '8f8effff-f89d-417c-8e44-c8561f9f1eec',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '8f8effff-f89d-417c-8e44-c8561f9f1eec'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.15478573333333, 39.98606776666667, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '59552665-bdd6-4c0c-bd0d-5ee79ea4848b',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '59552665-bdd6-4c0c-bd0d-5ee79ea4848b'\n  ],\n  [\n    {\n      geometry: {type: 'Point', coordinates: [-105.1547872, 39.9866052, 0]},\n      type: 'Feature',\n      properties: {\n        fillColor: [77, 193, 156],\n        id: '32df5401-38d1-4e96-8d1e-a29d2c7c3955',\n        radius: null\n      }\n    },\n    [77, 193, 156],\n    null,\n    '32df5401-38d1-4e96-8d1e-a29d2c7c3955'\n  ]\n];\n"
  },
  {
    "path": "test/fixtures/geojson.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {extent} from 'd3-array';\nimport {histogramFromDomain} from '@kepler.gl/utils';\nimport {BINS} from '@kepler.gl/constants';\n\nexport const geoJsonDataId = 'ieukmgne';\n\n/**\n *\n * GeoJSON with Polygons\n */\nconst feature0 = {\n  type: 'Feature',\n  properties: {\n    OBJECTID: 1,\n    ZIP_CODE: 94107,\n    ID: 94107,\n    TRIPS: 11,\n    RATE: 'a',\n    OBJ: {\n      id: 1\n    }\n  },\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.401159718585049, 37.782024266952142],\n        [-122.400374366843309, 37.782644515545172],\n        [-122.400019020063766, 37.782925153640136],\n        [-122.399891477967842, 37.783025880124256],\n        [-122.398930331092998, 37.783784933304034]\n      ]\n    ]\n  }\n};\n\nconst feature0Parsed = {\n  type: 'Feature',\n  geometry: feature0.geometry,\n  properties: {\n    ...feature0.properties,\n    OBJ: {id: 1}\n  }\n};\n\nconst feature1 = {\n  type: 'Feature',\n  properties: {\n    OBJECTID: 2,\n    ZIP_CODE: 94105,\n    ID: 94105,\n    TRIPS: 4,\n    RATE: 'b',\n    OBJ: {\n      id: 2\n    }\n  },\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.39249932896719, 37.793768814133983],\n        [-122.391890260341384, 37.794278544568918],\n        [-122.391666728649753, 37.794132425256194],\n        [-122.391723034266192, 37.79410061945832],\n        [-122.39249932896719, 37.793768814133983]\n      ]\n    ]\n  }\n};\n\nconst feature1Parsed = {\n  type: 'Feature',\n  geometry: feature1.geometry,\n  properties: {\n    ...feature1.properties,\n    OBJ: {id: 2}\n  }\n};\n\nconst feature2 = {\n  type: 'Feature',\n  properties: {\n    OBJECTID: 3,\n    ZIP_CODE: 94109,\n    ID: 94109,\n    TRIPS: 20,\n    RATE: null,\n    OBJ: {\n      id: 3\n    }\n  },\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.39249932896719, 37.793768814133983],\n        [-122.391890260341384, 37.794278544568918],\n        [-122.391982015107928, 37.793871906128679],\n        [-122.39249932896719, 37.793768814133983]\n      ]\n    ]\n  }\n};\n\nconst feature2Parsed = {\n  type: 'Feature',\n  geometry: feature2.geometry,\n  properties: {\n    ...feature2.properties,\n    OBJ: {id: 3}\n  }\n};\n\nconst feature3 = {\n  type: 'Feature',\n  properties: {\n    OBJECTID: 4,\n    ZIP_CODE: 94111,\n    ID: 94111,\n    RATE: 'c',\n    OBJ: {\n      id: 4\n    }\n  },\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.39249932896719, 37.793768814133983],\n        [-122.391666728649753, 37.794132425256194],\n        [-122.391723034266192, 37.79410061945832],\n        [-122.391673228351905, 37.794047854124599],\n        [-122.391982015107928, 37.793871906128679],\n        [-122.39249932896719, 37.793768814133983]\n      ]\n    ]\n  }\n};\n\nconst feature3Parsed = {\n  type: 'Feature',\n  geometry: feature3.geometry,\n  properties: {\n    ...feature3.properties,\n    TRIPS: null,\n    OBJ: {id: 4}\n  }\n};\n\nconst feature4 = {\n  type: 'Feature',\n  properties: {\n    OBJECTID: 5,\n    ZIP_CODE: 94107,\n    ID: 9409,\n    RATE: 'c',\n    OBJ: {\n      id: 5\n    }\n  },\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.39249932896719, 37.793768814133983],\n        [-122.391890260341384, 37.794278544568918],\n        [-122.391788865572423, 37.794170982455135],\n        [-122.39249932896719, 37.793768814133983]\n      ]\n    ]\n  }\n};\n\nconst feature4Parsed = {\n  type: 'Feature',\n  geometry: feature4.geometry,\n  properties: {\n    ...feature4.properties,\n    TRIPS: null,\n    OBJ: {id: 5}\n  }\n};\n\nexport const geojsonData = {\n  type: 'FeatureCollection',\n  features: [feature0, feature1, feature2, feature3, feature4]\n};\n\nexport const fields = [\n  {\n    type: 'geojson',\n    name: '_geojson',\n    id: '_geojson',\n    displayName: '_geojson',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'GEOMETRY',\n    valueAccessor: values => values[0]\n  },\n  {\n    type: 'integer',\n    name: 'OBJECTID',\n    id: 'OBJECTID',\n    displayName: 'OBJECTID',\n    format: '',\n    fieldIdx: 1,\n    analyzerType: 'INT',\n    valueAccessor: values => values[1]\n  },\n  {\n    type: 'integer',\n    name: 'ZIP_CODE',\n    id: 'ZIP_CODE',\n    displayName: 'ZIP_CODE',\n    format: '',\n    fieldIdx: 2,\n    analyzerType: 'INT',\n    valueAccessor: values => values[2]\n  },\n  {\n    type: 'integer',\n    name: 'ID',\n    id: 'ID',\n    displayName: 'ID',\n    format: '',\n    fieldIdx: 3,\n    analyzerType: 'INT',\n    valueAccessor: values => values[3]\n  },\n  {\n    type: 'integer',\n    name: 'TRIPS',\n    id: 'TRIPS',\n    displayName: 'TRIPS',\n    format: '',\n    fieldIdx: 4,\n    analyzerType: 'INT',\n    valueAccessor: values => values[4]\n  },\n  {\n    type: 'string',\n    name: 'RATE',\n    id: 'RATE',\n    displayName: 'RATE',\n    format: '',\n    fieldIdx: 5,\n    analyzerType: 'STRING',\n    valueAccessor: values => values[5]\n  },\n  {\n    type: 'object',\n    name: 'OBJ',\n    id: 'OBJ',\n    displayName: 'OBJ',\n    format: '',\n    fieldIdx: 6,\n    analyzerType: 'OBJECT',\n    valueAccessor: values => values[6]\n  }\n];\n\nexport const rows = [\n  [feature0Parsed, 1, 94107, 94107, 11, 'a', {id: 1}],\n  [feature1Parsed, 2, 94105, 94105, 4, 'b', {id: 2}],\n  [feature2Parsed, 3, 94109, 94109, 20, null, {id: 3}],\n  [feature3Parsed, 4, 94111, 94111, null, 'c', {id: 4}],\n  [feature4Parsed, 5, 94107, 9409, null, 'c', {id: 5}]\n];\n\n// add index to properties\nexport const datasetAllData = rows;\n\nexport const geoJsonTripFilterProps = {\n  domain: [4, 20],\n  fieldType: 'integer',\n  step: 0.01,\n  type: 'range',\n  typeOptions: ['range'],\n  value: [4, 20],\n  gpu: true,\n  view: 'side'\n};\n\nexport const mergedTripFilter = {\n  ...geoJsonTripFilterProps,\n  animationWindow: 'free',\n  dataId: [geoJsonDataId],\n  id: 'TRIPS-3',\n  enabled: true,\n  fixedDomain: false,\n  view: 'side',\n  isAnimating: false,\n  speed: 1,\n  name: ['TRIPS'],\n  fieldIdx: [4],\n  value: [4, 12],\n  plotType: {type: 'histogram'},\n  yAxis: null,\n  gpuChannel: [0],\n  bins: {\n    [geoJsonDataId]: histogramFromDomain(\n      [4, 20],\n      rows.map(r => r[4]),\n      BINS\n    )\n  }\n};\n\nexport const geoJsonRateFilterProps = {\n  domain: ['a', 'b', 'c'],\n  fieldType: 'string',\n  type: 'multiSelect',\n  value: [],\n  gpu: false,\n  view: 'side'\n};\n\nexport const mergedRateFilter = {\n  ...geoJsonRateFilterProps,\n  animationWindow: 'free',\n  name: ['RATE'],\n  dataId: [geoJsonDataId],\n  id: 'RATE-1',\n  enabled: true,\n  fixedDomain: false,\n  view: 'side',\n  isAnimating: false,\n  speed: 1,\n  fieldIdx: [5],\n  value: ['a'],\n  plotType: {\n    type: 'histogram'\n  },\n  yAxis: null\n};\n\nexport const geoBounds = [\n  -122.40115971858505, 37.78202426695214, -122.39166672864975, 37.79427854456892\n];\n\nexport const expectedDataToFeature = [\n  feature0Parsed,\n  feature1Parsed,\n  feature2Parsed,\n  feature3Parsed,\n  feature4Parsed\n].map((f, i) => ({\n  ...f,\n  properties: {...f.properties, index: i}\n}));\n\nexport const updatedGeoJsonLayer = {\n  dataToFeature: expectedDataToFeature,\n  meta: {\n    featureTypes: {polygon: true},\n    bounds: geoBounds,\n    fixedRadius: false\n  }\n};\n\nexport const mappedTripValue = geojsonData.features.map(f => f.properties.TRIPS);\n\nexport const tripDomain = extent(mappedTripValue);\n\n/**\n * GeoJSON with style properties\n */\nexport const geoJsonWithStyle = {\n  type: 'FeatureCollection',\n  features: [\n    {\n      type: 'Feature',\n      properties: {\n        fillColor: [1, 2, 3],\n        lineColor: [4, 5, 6],\n        lineWidth: 1,\n        elevation: 10,\n        radius: 5\n      },\n      geometry: {type: 'Point', coordinates: [-122.1, 37.3]}\n    },\n    {\n      type: 'Feature',\n      properties: {\n        fillColor: [7, 8, 9],\n        lineColor: [4, 5, 6],\n        lineWidth: 3,\n        elevation: 10,\n        radius: 5\n      },\n      geometry: {type: 'Point', coordinates: [-122.2, 37.2]}\n    },\n    {\n      type: 'Feature',\n      properties: {\n        fillColor: [1, 2, 3],\n        lineColor: [4, 5, 6],\n        lineWidth: 4,\n        elevation: 10,\n        radius: 5\n      },\n      geometry: {type: 'Point', coordinates: [-122.3, 37.1]}\n    }\n  ]\n};\n\n// parsed fields and rows\nexport const geoStyleFields = [\n  {\n    name: '_geojson',\n    id: '_geojson',\n    displayName: '_geojson',\n    format: '',\n    fieldIdx: 0,\n    type: 'geojson',\n    analyzerType: 'GEOMETRY',\n    valueAccessor: values => values[0]\n  },\n  {\n    name: 'fillColor',\n    id: 'fillColor',\n    displayName: 'fillColor',\n    format: '',\n    fieldIdx: 1,\n    type: 'array',\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'lineColor',\n    id: 'lineColor',\n    displayName: 'lineColor',\n    format: '',\n    fieldIdx: 2,\n    type: 'array',\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[2]\n  },\n  {\n    name: 'lineWidth',\n    id: 'lineWidth',\n    displayName: 'lineWidth',\n    format: '',\n    fieldIdx: 3,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[3]\n  },\n  {\n    name: 'elevation',\n    id: 'elevation',\n    displayName: 'elevation',\n    format: '',\n    fieldIdx: 4,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[4]\n  },\n  {\n    name: 'radius',\n    id: 'radius',\n    displayName: 'radius',\n    format: '',\n    fieldIdx: 5,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[5]\n  }\n];\n\nexport const geoStyleRows = [\n  [geoJsonWithStyle.features[0], [1, 2, 3], [4, 5, 6], 1, 10, 5],\n  [geoJsonWithStyle.features[1], [7, 8, 9], [4, 5, 6], 3, 10, 5],\n  [geoJsonWithStyle.features[2], [1, 2, 3], [4, 5, 6], 4, 10, 5]\n];\n\nexport const geoStyleDataToFeature = geoJsonWithStyle.features.map((f, i) => ({\n  ...f,\n  properties: {...f.properties, index: i}\n}));\n\nexport const geoStyleBounds = [-122.3, 37.1, -122.1, 37.3];\n\nexport const geoStyleMeta = {\n  featureTypes: {point: true},\n  bounds: geoStyleBounds,\n  fixedRadius: true\n};\n"
  },
  {
    "path": "test/fixtures/points-with-polygon-filter-map.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {processKeplerglJSON} from '@kepler.gl/processors';\nimport CloneDeep from 'lodash/cloneDeep';\nimport {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {InitialState} from '../helpers/mock-state';\n\nexport const polygonFilterMap = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: '20pde67d8r',\n        label: 'really-few-points.csv',\n        color: [0, 92, 255],\n        allData: [\n          [\n            '2009/11/25 09:11:21.39',\n            35.75367,\n            -117.7255,\n            8.394,\n            2.67,\n            'Md',\n            9,\n            128,\n            7,\n            0.04,\n            'NCSN',\n            71318991\n          ],\n          [\n            '2009/11/26 14:59:48.35',\n            36.32267,\n            -117.894,\n            8.719,\n            2.63,\n            'Md',\n            4,\n            267,\n            42,\n            0.01,\n            'NCSN',\n            71319526\n          ],\n          [\n            '2009/11/27 19:57:47.45',\n            36.395,\n            -117.8295,\n            -1.482,\n            3.48,\n            'ML',\n            131,\n            143,\n            51,\n            0.57,\n            'NCSN',\n            71320021\n          ],\n          [\n            '2009/12/01 14:58:28.91',\n            35.74683,\n            -118.002,\n            8.108,\n            2.64,\n            'Md',\n            8,\n            112,\n            14,\n            0.05,\n            'NCSN',\n            71321416\n          ],\n          [\n            '2009/12/02 00:59:55.43',\n            35.9555,\n            -117.33317,\n            5.564,\n            2.86,\n            'Md',\n            10,\n            221,\n            28,\n            0.07,\n            'NCSN',\n            71321716\n          ],\n          [\n            '2009/12/04 22:54:10.77',\n            36.38533,\n            -117.87334,\n            10.498,\n            2.56,\n            'Md',\n            8,\n            175,\n            47,\n            0.06,\n            'NCSN',\n            71318015\n          ],\n          [\n            '2009/12/13 02:34:34.38',\n            36.00433,\n            -117.88483,\n            5.708,\n            2.59,\n            'Md',\n            10,\n            158,\n            18,\n            0.03,\n            'NCSN',\n            71321500\n          ],\n          [\n            '2009/12/15 09:52:19.86',\n            36.40933,\n            -117.83667,\n            4.858,\n            2.95,\n            'Md',\n            19,\n            180,\n            51,\n            0.06,\n            'NCSN',\n            71322420\n          ],\n          [\n            '2009/12/17 10:48:31.79',\n            36.383,\n            -117.87583,\n            -1.362,\n            2.58,\n            'Md',\n            33,\n            168,\n            47,\n            0.27,\n            'NCSN',\n            71323125\n          ],\n          [\n            '2009/12/21 13:25:42.26',\n            35.98583,\n            -117.88617,\n            13.148,\n            2.53,\n            'Md',\n            6,\n            164,\n            16,\n            0.09,\n            'NCSN',\n            71324715\n          ],\n          [\n            '2009/12/24 20:22:34.04',\n            35.8975,\n            -118.31483,\n            17.292,\n            2.5,\n            'Md',\n            10,\n            101,\n            22,\n            0.04,\n            'NCSN',\n            71325835\n          ],\n          [\n            '2009/12/29 09:38:42.06',\n            36.14167,\n            -118.05317,\n            4.868,\n            2.56,\n            'Md',\n            16,\n            128,\n            23,\n            0.03,\n            'NCSN',\n            71327655\n          ],\n          [\n            '2009/12/30 11:44:37.22',\n            35.98417,\n            -117.36166,\n            6.614,\n            3.57,\n            'Mw',\n            11,\n            242,\n            26,\n            0.05,\n            'NCSN',\n            71328065\n          ],\n          [\n            '2009/12/31 19:57:42.94',\n            36.01133,\n            -117.7815,\n            3.441,\n            2.95,\n            'Md',\n            10,\n            150,\n            30,\n            0.03,\n            'NCSN',\n            71328545\n          ],\n          [\n            '2010/01/01 02:55:04.29',\n            35.966,\n            -117.2965,\n            0.454,\n            2.77,\n            'Md',\n            18,\n            176,\n            32,\n            0.16,\n            'NCSN',\n            71328730\n          ],\n          [\n            '2010/01/01 03:25:29.90',\n            36.03683,\n            -117.777,\n            5.818,\n            2.93,\n            'Md',\n            14,\n            186,\n            15,\n            0.04,\n            'NCSN',\n            71328750\n          ],\n          [\n            '2010/01/04 20:07:59.49',\n            35.709,\n            -117.48133,\n            11.454,\n            3.25,\n            'ML',\n            11,\n            155,\n            12,\n            0.09,\n            'NCSN',\n            71335381\n          ],\n          [\n            '2010/01/04 20:11:25.36',\n            35.7165,\n            -117.4725,\n            9.824,\n            3.02,\n            'Md',\n            10,\n            158,\n            13,\n            0.05,\n            'NCSN',\n            71335386\n          ],\n          [\n            '2010/01/10 02:55:05.94',\n            36.40067,\n            -117.87167,\n            -1.011,\n            3.18,\n            'ML',\n            41,\n            158,\n            48,\n            0.17,\n            'NCSN',\n            71338191\n          ],\n          [\n            '2010/01/10 17:45:48.58',\n            36.0145,\n            -117.86967,\n            7.528,\n            2.53,\n            'Md',\n            8,\n            177,\n            19,\n            0.05,\n            'NCSN',\n            71338616\n          ],\n          [\n            '2010/01/13 02:43:38.52',\n            36.40617,\n            -117.86684,\n            8.249,\n            2.61,\n            'Md',\n            16,\n            176,\n            19,\n            0.06,\n            'NCSN',\n            71339666\n          ],\n          [\n            '2010/01/14 11:08:47.75',\n            36.03383,\n            -117.84184,\n            -1.522,\n            2.62,\n            'Md',\n            12,\n            134,\n            20,\n            0.26,\n            'NCSN',\n            71340181\n          ],\n          [\n            '2010/01/14 12:10:05.13',\n            36.037,\n            -117.82983,\n            5.358,\n            3.6,\n            'ML',\n            13,\n            137,\n            19,\n            0.04,\n            'NCSN',\n            71340211\n          ],\n          [\n            '2010/01/14 12:57:12.40',\n            36.03817,\n            -117.82533,\n            6.318,\n            3.19,\n            'ML',\n            13,\n            138,\n            19,\n            0.06,\n            'NCSN',\n            71340241\n          ],\n          [\n            '2010/01/14 13:37:00.76',\n            36.036,\n            -117.82867,\n            5.738,\n            3.42,\n            'ML',\n            13,\n            175,\n            19,\n            0.04,\n            'NCSN',\n            71340261\n          ],\n          [\n            '2010/01/15 08:23:27.15',\n            36.03067,\n            -117.83483,\n            5.428,\n            4.24,\n            'Mw',\n            16,\n            173,\n            19,\n            0.04,\n            'NCSN',\n            71340636\n          ],\n          [\n            '2010/01/15 08:43:41.25',\n            36.0255,\n            -117.83767,\n            5.508,\n            2.88,\n            'Md',\n            12,\n            132,\n            19,\n            0.04,\n            'NCSN',\n            71340651\n          ],\n          [\n            '2010/01/15 08:44:38.51',\n            36.02817,\n            -117.83366,\n            5.008,\n            2.78,\n            'Md',\n            9,\n            189,\n            19,\n            0.04,\n            'NCSN',\n            71034094\n          ],\n          [\n            '2010/01/15 09:25:13.79',\n            36.032,\n            -117.8375,\n            6.948,\n            2.9,\n            'Md',\n            14,\n            135,\n            19,\n            0.04,\n            'NCSN',\n            71340686\n          ],\n          [\n            '2010/01/15 09:28:18.84',\n            36.02883,\n            -117.83067,\n            5.348,\n            3.59,\n            'Mw',\n            14,\n            135,\n            19,\n            0.07,\n            'NCSN',\n            71340701\n          ],\n          [\n            '2010/01/15 10:29:58.43',\n            36.02934,\n            -117.8315,\n            5.298,\n            3.58,\n            'Mw',\n            12,\n            173,\n            19,\n            0.06,\n            'NCSN',\n            71340731\n          ],\n          [\n            '2010/01/15 13:39:31.26',\n            36.03183,\n            -117.8395,\n            5.368,\n            3.36,\n            'ML',\n            9,\n            172,\n            20,\n            0.05,\n            'NCSN',\n            71340796\n          ],\n          [\n            '2010/01/15 14:20:49.31',\n            36.03233,\n            -117.83884,\n            6.258,\n            2.93,\n            'Md',\n            11,\n            173,\n            20,\n            0.05,\n            'NCSN',\n            71340826\n          ],\n          [\n            '2010/01/16 06:36:18.42',\n            36.0345,\n            -117.83767,\n            6.918,\n            3.4,\n            'Mw',\n            17,\n            143,\n            20,\n            0.07,\n            'NCSN',\n            71341241\n          ],\n          [\n            '2010/01/17 18:48:30.52',\n            36.02967,\n            -117.84483,\n            14.158,\n            2.53,\n            'Md',\n            11,\n            140,\n            20,\n            0.21,\n            'NCSN',\n            71341861\n          ],\n          [\n            '2010/01/18 04:50:45.53',\n            36.03067,\n            -117.84,\n            5.608,\n            2.97,\n            'Md',\n            15,\n            133,\n            20,\n            0.06,\n            'NCSN',\n            71341971\n          ],\n          [\n            '2010/01/18 06:34:55.59',\n            36.03233,\n            -117.83617,\n            6.418,\n            2.95,\n            'Md',\n            15,\n            143,\n            19,\n            0.06,\n            'NCSN',\n            71342001\n          ],\n          [\n            '2010/01/30 11:25:59.52',\n            36.08917,\n            -117.89417,\n            5.998,\n            2.9,\n            'Md',\n            24,\n            137,\n            27,\n            0.09,\n            'NCSN',\n            71346216\n          ],\n          [\n            '2010/01/31 13:31:59.50',\n            36.11867,\n            -117.711,\n            5.813,\n            2.81,\n            'Md',\n            7,\n            246,\n            20,\n            0.06,\n            'NCSN',\n            71346731\n          ],\n          [\n            '2010/02/07 12:34:37.39',\n            36.02017,\n            -117.785,\n            5.258,\n            2.9,\n            'Md',\n            15,\n            178,\n            15,\n            0.04,\n            'NCSN',\n            71350156\n          ],\n          [\n            '2010/02/14 00:37:57.82',\n            36.112,\n            -117.73666,\n            -1.417,\n            3,\n            'ML',\n            22,\n            140,\n            20,\n            0.15,\n            'NCSN',\n            71353401\n          ],\n          [\n            '2010/02/14 12:44:06.09',\n            36.051,\n            -117.87267,\n            6.748,\n            2.63,\n            'Md',\n            10,\n            111,\n            23,\n            0.02,\n            'NCSN',\n            71353756\n          ],\n          [\n            '2010/02/14 17:57:26.20',\n            36.06667,\n            -117.86816,\n            -0.102,\n            2.58,\n            'Md',\n            14,\n            115,\n            24,\n            0.17,\n            'NCSN',\n            71353881\n          ],\n          [\n            '2010/02/15 00:07:09.35',\n            36.068,\n            -117.86767,\n            4.093,\n            4.09,\n            'ML',\n            21,\n            137,\n            24,\n            0.13,\n            'NCSN',\n            71354001\n          ],\n          [\n            '2010/02/15 00:10:56.25',\n            36.05217,\n            -117.9365,\n            2.723,\n            2.77,\n            'Md',\n            17,\n            213,\n            23,\n            0.14,\n            'NCSN',\n            71354011\n          ],\n          [\n            '2010/02/19 22:11:29.84',\n            35.94817,\n            -117.996,\n            27.204,\n            3.14,\n            'Md',\n            17,\n            155,\n            79,\n            0.13,\n            'NCSN',\n            71357086\n          ],\n          [\n            '2010/02/24 00:30:21.56',\n            36.03083,\n            -118.03416,\n            10.161,\n            2.8,\n            'Md',\n            29,\n            231,\n            82,\n            0.35,\n            'NCSN',\n            71353675\n          ],\n          [\n            '2010/02/26 12:46:42.66',\n            36.06767,\n            -117.8725,\n            5.358,\n            3,\n            'Md',\n            12,\n            178,\n            24,\n            0.11,\n            'NCSN',\n            71355090\n          ],\n          [\n            '2010/02/26 13:22:54.30',\n            36.04984,\n            -117.9875,\n            7.374,\n            2.79,\n            'Md',\n            16,\n            229,\n            87,\n            0.14,\n            'NCSN',\n            71355105\n          ],\n          [\n            '2010/02/26 20:12:04.40',\n            36.0605,\n            -117.8735,\n            5.058,\n            3.02,\n            'ML',\n            13,\n            176,\n            24,\n            0.1,\n            'NCSN',\n            71355210\n          ],\n          [\n            '2010/02/27 06:56:03.08',\n            36.0595,\n            -117.8745,\n            4.988,\n            3.04,\n            'Md',\n            12,\n            175,\n            24,\n            0.09,\n            'NCSN',\n            71355385\n          ],\n          [\n            '2010/02/27 07:10:47.91',\n            36.06833,\n            -117.87466,\n            5.608,\n            3.58,\n            'ML',\n            15,\n            178,\n            24,\n            0.1,\n            'NCSN',\n            71355400\n          ],\n          [\n            '2010/02/27 13:56:38.61',\n            36.06583,\n            -117.88116,\n            4.978,\n            3.28,\n            'ML',\n            15,\n            177,\n            25,\n            0.12,\n            'NCSN',\n            71355585\n          ],\n          [\n            '2010/02/27 15:01:35.68',\n            36.06467,\n            -117.8785,\n            4.958,\n            3.35,\n            'ML',\n            15,\n            177,\n            24,\n            0.11,\n            'NCSN',\n            71355605\n          ],\n          [\n            '2010/02/27 20:23:53.71',\n            36.06467,\n            -117.88233,\n            4.978,\n            2.96,\n            'Md',\n            9,\n            196,\n            25,\n            0.12,\n            'NCSN',\n            71355735\n          ],\n          [\n            '2010/02/28 02:26:14.90',\n            36.06033,\n            -117.87067,\n            7.348,\n            2.62,\n            'Md',\n            6,\n            196,\n            24,\n            0.05,\n            'NCSN',\n            71355895\n          ],\n          [\n            '2010/02/28 06:30:17.24',\n            36.06733,\n            -117.87933,\n            5.108,\n            3.51,\n            'ML',\n            14,\n            198,\n            25,\n            0.13,\n            'NCSN',\n            71355955\n          ],\n          [\n            '2010/02/28 06:56:28.60',\n            36.06116,\n            -117.87733,\n            5.268,\n            3.1,\n            'ML',\n            14,\n            135,\n            24,\n            0.13,\n            'NCSN',\n            71355960\n          ],\n          [\n            '2010/02/28 07:08:47.79',\n            36.04033,\n            -117.8805,\n            -0.958,\n            2.81,\n            'Md',\n            11,\n            115,\n            23,\n            0.17,\n            'NCSN',\n            71355970\n          ],\n          [\n            '2010/03/01 09:06:03.18',\n            36.048,\n            -117.886,\n            5.578,\n            4.35,\n            'Mw',\n            22,\n            130,\n            24,\n            0.25,\n            'NCSN',\n            71356400\n          ],\n          [\n            '2010/03/01 09:16:04.22',\n            36.06384,\n            -117.8675,\n            5.428,\n            2.79,\n            'Md',\n            12,\n            145,\n            24,\n            0.1,\n            'NCSN',\n            71356420\n          ],\n          [\n            '2010/03/01 09:24:05.43',\n            36.06567,\n            -117.86967,\n            6.848,\n            2.53,\n            'Md',\n            9,\n            178,\n            24,\n            0.08,\n            'NCSN',\n            71356425\n          ],\n          [\n            '2010/03/01 10:32:07.35',\n            36.06517,\n            -117.87117,\n            4.548,\n            2.91,\n            'ML',\n            13,\n            178,\n            24,\n            0.12,\n            'NCSN',\n            71356455\n          ],\n          [\n            '2010/03/01 10:39:40.04',\n            36.06517,\n            -117.8675,\n            5.138,\n            2.85,\n            'ML',\n            12,\n            178,\n            24,\n            0.08,\n            'NCSN',\n            71356460\n          ],\n          [\n            '2010/03/01 10:50:57.37',\n            36.0695,\n            -117.86833,\n            5.118,\n            2.81,\n            'Md',\n            13,\n            115,\n            24,\n            0.13,\n            'NCSN',\n            71356465\n          ],\n          [\n            '2010/03/02 07:22:53.73',\n            36.06617,\n            -117.8685,\n            4.918,\n            3.55,\n            'ML',\n            15,\n            179,\n            24,\n            0.11,\n            'NCSN',\n            71356975\n          ],\n          [\n            '2010/03/05 03:46:24.60',\n            36.13283,\n            -117.849,\n            0.668,\n            2.97,\n            'ML',\n            17,\n            149,\n            27,\n            0.17,\n            'NCSN',\n            71358640\n          ],\n          [\n            '2010/03/08 03:43:20.70',\n            36.06517,\n            -117.87417,\n            4.977,\n            3.29,\n            'ML',\n            13,\n            178,\n            24,\n            0.11,\n            'NCSN',\n            71360230\n          ],\n          [\n            '2010/03/08 07:00:45.04',\n            36.06033,\n            -117.878,\n            5.11,\n            3.27,\n            'ML',\n            16,\n            112,\n            24,\n            0.08,\n            'NCSN',\n            71360265\n          ],\n          [\n            '2010/03/08 07:18:21.79',\n            36.06267,\n            -117.87733,\n            5.807,\n            2.95,\n            'Md',\n            10,\n            136,\n            24,\n            0.13,\n            'NCSN',\n            71360270\n          ],\n          [\n            '2010/03/08 10:15:09.63',\n            36.05733,\n            -117.87483,\n            6.438,\n            2.61,\n            'Md',\n            7,\n            194,\n            24,\n            0.06,\n            'NCSN',\n            71360315\n          ],\n          [\n            '2010/03/09 07:54:27.71',\n            36.05533,\n            -117.8785,\n            5.698,\n            3.44,\n            'ML',\n            13,\n            174,\n            24,\n            0.08,\n            'NCSN',\n            71360720\n          ],\n          [\n            '2010/03/09 07:56:55.41',\n            36.05783,\n            -117.87634,\n            6.868,\n            2.7,\n            'Md',\n            7,\n            175,\n            24,\n            0.05,\n            'NCSN',\n            71360725\n          ],\n          [\n            '2010/03/09 09:06:34.97',\n            36.0585,\n            -117.88284,\n            7.208,\n            2.93,\n            'ML',\n            10,\n            174,\n            24,\n            0.09,\n            'NCSN',\n            71360740\n          ],\n          [\n            '2010/03/09 09:14:37.86',\n            36.05483,\n            -117.882,\n            5.308,\n            2.6,\n            'Md',\n            9,\n            174,\n            24,\n            0.06,\n            'NCSN',\n            71360750\n          ],\n          [\n            '2010/03/09 09:20:45.85',\n            36.057,\n            -117.87783,\n            8.348,\n            2.59,\n            'Md',\n            7,\n            174,\n            24,\n            0.05,\n            'NCSN',\n            71360760\n          ],\n          [\n            '2010/03/09 20:05:12.62',\n            36.06384,\n            -117.86967,\n            5.348,\n            3.66,\n            'ML',\n            14,\n            138,\n            24,\n            0.11,\n            'NCSN',\n            71360980\n          ],\n          [\n            '2010/03/09 20:45:44.09',\n            36.0525,\n            -117.876,\n            6.328,\n            2.62,\n            'Md',\n            8,\n            174,\n            24,\n            0.07,\n            'NCSN',\n            71361005\n          ],\n          [\n            '2010/03/10 04:36:38.09',\n            36.06283,\n            -117.8745,\n            4.938,\n            2.99,\n            'ML',\n            16,\n            137,\n            24,\n            0.12,\n            'NCSN',\n            71361155\n          ],\n          [\n            '2010/03/11 04:09:07.38',\n            36.05633,\n            -117.88017,\n            3.228,\n            2.69,\n            'Md',\n            6,\n            193,\n            24,\n            0.05,\n            'NCSN',\n            71361590\n          ],\n          [\n            '2010/03/12 01:19:36.94',\n            36.37967,\n            -117.85567,\n            -1.362,\n            2.55,\n            'Md',\n            32,\n            170,\n            48,\n            0.25,\n            'NCSN',\n            71362150\n          ],\n          [\n            '2010/03/12 09:18:51.30',\n            35.98183,\n            -117.6495,\n            2.708,\n            2.52,\n            'ML',\n            8,\n            191,\n            4,\n            0.06,\n            'NCSN',\n            71362290\n          ],\n          [\n            '2010/03/18 22:02:56.22',\n            36.09833,\n            -117.98217,\n            -0.625,\n            2.67,\n            'Md',\n            25,\n            259,\n            71,\n            0.35,\n            'NCSN',\n            71365285\n          ],\n          [\n            '2010/03/20 20:28:06.56',\n            36.08484,\n            -117.8395,\n            7.948,\n            2.69,\n            'Md',\n            10,\n            188,\n            23,\n            0.09,\n            'NCSN',\n            71366240\n          ],\n          [\n            '2010/03/26 21:39:59.32',\n            36.44433,\n            -117.94617,\n            -1.321,\n            2.74,\n            'Md',\n            37,\n            163,\n            46,\n            0.31,\n            'NCSN',\n            71368805\n          ],\n          [\n            '2010/03/29 19:15:57.79',\n            35.9685,\n            -117.85983,\n            6.168,\n            2.56,\n            'Md',\n            7,\n            159,\n            15,\n            0.03,\n            'NCSN',\n            71370220\n          ],\n          [\n            '2010/04/01 05:26:40.83',\n            36.08633,\n            -117.90667,\n            3.908,\n            2.58,\n            'Md',\n            7,\n            181,\n            27,\n            0.05,\n            'NCSN',\n            71376286\n          ],\n          [\n            '2010/04/05 13:13:13.57',\n            36.11883,\n            -117.86317,\n            1.918,\n            2.57,\n            'Md',\n            10,\n            218,\n            27,\n            0.05,\n            'NCSN',\n            71377991\n          ],\n          [\n            '2010/04/10 01:27:33.54',\n            36.37017,\n            -117.85883,\n            -1.595,\n            2.7,\n            'Md',\n            33,\n            125,\n            26,\n            0.24,\n            'NCSN',\n            71380051\n          ],\n          [\n            '2010/04/10 02:47:23.41',\n            36.378,\n            -117.85117,\n            3.425,\n            2.85,\n            'Md',\n            21,\n            134,\n            26,\n            0.1,\n            'NCSN',\n            71380081\n          ],\n          [\n            '2010/04/10 20:34:05.45',\n            36.323,\n            -117.86,\n            -0.875,\n            2.66,\n            'Md',\n            8,\n            218,\n            45,\n            0.04,\n            'NCSN',\n            71380451\n          ],\n          [\n            '2010/04/11 19:41:36.52',\n            36.0725,\n            -117.85384,\n            8.394,\n            2.63,\n            'Md',\n            12,\n            101,\n            23,\n            0.06,\n            'NCSN',\n            71380726\n          ],\n          [\n            '2010/04/14 00:40:02.53',\n            36.09583,\n            -117.87984,\n            -0.47,\n            2.9,\n            'Md',\n            27,\n            165,\n            55,\n            0.41,\n            'NCSN',\n            71381696\n          ],\n          [\n            '2010/04/19 23:39:08.72',\n            36.07633,\n            -117.88783,\n            5.404,\n            2.68,\n            'Md',\n            16,\n            126,\n            26,\n            0.11,\n            'NCSN',\n            71385691\n          ],\n          [\n            '2010/04/20 00:41:32.76',\n            36.07433,\n            -117.886,\n            5.094,\n            2.64,\n            'Md',\n            9,\n            126,\n            26,\n            0.02,\n            'NCSN',\n            71385701\n          ],\n          [\n            '2010/04/23 20:27:42.23',\n            36.07633,\n            -117.8845,\n            5.254,\n            2.73,\n            'Md',\n            8,\n            126,\n            26,\n            0.04,\n            'NCSN',\n            71387166\n          ],\n          [\n            '2010/04/23 20:33:53.25',\n            36.07617,\n            -117.88683,\n            5.034,\n            2.98,\n            'Md',\n            19,\n            74,\n            26,\n            0.11,\n            'NCSN',\n            71387176\n          ],\n          [\n            '2010/04/23 23:02:58.77',\n            36.07633,\n            -117.88617,\n            5.134,\n            2.55,\n            'Md',\n            10,\n            105,\n            26,\n            0.05,\n            'NCSN',\n            71387246\n          ],\n          [\n            '2010/04/24 00:43:09.40',\n            36.07383,\n            -117.886,\n            7.134,\n            2.56,\n            'Md',\n            10,\n            105,\n            26,\n            0.06,\n            'NCSN',\n            71387306\n          ],\n          [\n            '2010/04/25 00:02:23.56',\n            36.098,\n            -117.85117,\n            7.524,\n            2.53,\n            'Md',\n            10,\n            107,\n            25,\n            0.04,\n            'NCSN',\n            71387786\n          ],\n          [\n            '2010/04/28 20:17:36.60',\n            36.173,\n            -118.07816,\n            4.295,\n            2.58,\n            'Md',\n            15,\n            82,\n            21,\n            0.07,\n            'NCSN',\n            71389576\n          ],\n          [\n            '2010/04/28 21:43:09.58',\n            36.17216,\n            -118.08033,\n            5.205,\n            2.61,\n            'Md',\n            13,\n            81,\n            21,\n            0.09,\n            'NCSN',\n            71389656\n          ],\n          [\n            '2010/05/06 04:16:48.50',\n            35.92733,\n            -117.34917,\n            2.673,\n            3.59,\n            'ML',\n            25,\n            169,\n            27,\n            0.2,\n            'NCSN',\n            71387920\n          ],\n          [\n            '2010/05/07 22:56:41.84',\n            36.17883,\n            -118.07117,\n            -0.465,\n            2.6,\n            'Md',\n            28,\n            82,\n            22,\n            0.17,\n            'NCSN',\n            71388875\n          ],\n          [\n            '2010/05/08 00:08:17.55',\n            36.17983,\n            -118.06983,\n            -1.492,\n            2.76,\n            'Md',\n            22,\n            137,\n            22,\n            0.17,\n            'NCSN',\n            71388910\n          ],\n          [\n            '2010/05/14 11:35:16.74',\n            35.966,\n            -117.32433,\n            5.923,\n            3.36,\n            'ML',\n            23,\n            177,\n            29,\n            0.11,\n            'NCSN',\n            71397891\n          ],\n          [\n            '2010/05/14 15:53:52.69',\n            36.07417,\n            -117.88834,\n            7.928,\n            2.57,\n            'Md',\n            11,\n            178,\n            26,\n            0.06,\n            'NCSN',\n            71398011\n          ],\n          [\n            '2010/05/16 17:23:27.45',\n            36.399,\n            -117.8535,\n            8.211,\n            2.85,\n            'Md',\n            19,\n            139,\n            21,\n            0.08,\n            'NCSN',\n            71399251\n          ],\n          [\n            '2010/05/19 21:50:41.88',\n            36.01867,\n            -117.82017,\n            -0.866,\n            2.61,\n            'Md',\n            8,\n            106,\n            17,\n            0.04,\n            'NCSN',\n            71401221\n          ],\n          [\n            '2010/05/28 07:43:54.73',\n            36.03567,\n            -117.777,\n            4.984,\n            2.99,\n            'Md',\n            15,\n            186,\n            15,\n            0.04,\n            'NCSN',\n            71405501\n          ],\n          [\n            '2010/06/06 10:34:53.56',\n            36.14783,\n            -118.0575,\n            2.341,\n            2.88,\n            'ML',\n            29,\n            73,\n            23,\n            0.15,\n            'NCSN',\n            71410481\n          ],\n          [\n            '2010/07/09 01:30:46.13',\n            36.10467,\n            -117.70284,\n            6.869,\n            3.12,\n            'ML',\n            13,\n            110,\n            18,\n            0.07,\n            'NCSN',\n            71421360\n          ],\n          [\n            '2010/07/10 03:33:41.30',\n            36.78533,\n            -118.205,\n            10.779,\n            3.24,\n            'ML',\n            58,\n            138,\n            30,\n            0.12,\n            'NCSN',\n            71422445\n          ],\n          [\n            '2010/07/11 16:33:41.75',\n            36.0775,\n            -117.88267,\n            8.629,\n            2.58,\n            'Md',\n            9,\n            75,\n            26,\n            0.06,\n            'NCSN',\n            71423320\n          ],\n          [\n            '2010/07/13 19:43:40.94',\n            36.07867,\n            -117.88333,\n            7.209,\n            2.87,\n            'Md',\n            16,\n            75,\n            26,\n            0.08,\n            'NCSN',\n            71424375\n          ],\n          [\n            '2010/07/19 02:46:52.80',\n            36.04617,\n            -117.77317,\n            4.099,\n            2.51,\n            'Md',\n            17,\n            101,\n            16,\n            0.06,\n            'NCSN',\n            71427770\n          ],\n          [\n            '2010/07/19 20:24:26.98',\n            36.3995,\n            -117.86667,\n            6.631,\n            2.59,\n            'Md',\n            16,\n            136,\n            20,\n            0.11,\n            'NCSN',\n            71428200\n          ],\n          [\n            '2010/07/25 18:39:59.15',\n            35.736,\n            -117.6745,\n            8.629,\n            2.5,\n            'Md',\n            8,\n            127,\n            11,\n            0.06,\n            'NCSN',\n            71437031\n          ],\n          [\n            '2010/08/07 11:45:57.23',\n            36.451,\n            -118.31,\n            12.952,\n            2.65,\n            'Md',\n            17,\n            87,\n            21,\n            0.1,\n            'NCSN',\n            71442911\n          ],\n          [\n            '2010/08/18 06:39:18.89',\n            36.05133,\n            -117.77666,\n            3.704,\n            2.86,\n            'Md',\n            30,\n            246,\n            192,\n            0.19,\n            'NCSN',\n            71442900\n          ],\n          [\n            '2010/08/25 03:14:52.11',\n            36.07883,\n            -117.884,\n            5.359,\n            2.99,\n            'Md',\n            17,\n            75,\n            26,\n            0.06,\n            'NCSN',\n            71446385\n          ],\n          [\n            '2010/08/25 06:23:07.40',\n            36.07717,\n            -117.88,\n            6.599,\n            2.58,\n            'Md',\n            10,\n            75,\n            25,\n            0.03,\n            'NCSN',\n            71446415\n          ],\n          [\n            '2010/08/25 09:41:00.53',\n            36.07767,\n            -117.8785,\n            7.129,\n            2.73,\n            'Md',\n            10,\n            75,\n            25,\n            0.04,\n            'NCSN',\n            71446470\n          ],\n          [\n            '2010/08/30 02:57:21.53',\n            36.07784,\n            -117.8805,\n            8.479,\n            2.66,\n            'Md',\n            10,\n            126,\n            25,\n            0.06,\n            'NCSN',\n            71448365\n          ],\n          [\n            '2010/09/04 07:40:10.61',\n            35.94883,\n            -117.66116,\n            4.674,\n            2.72,\n            'Md',\n            8,\n            80,\n            1,\n            0.07,\n            'NCSN',\n            71450670\n          ],\n          [\n            '2010/09/05 01:55:47.93',\n            35.951,\n            -117.6365,\n            6.584,\n            2.58,\n            'Md',\n            8,\n            286,\n            1,\n            0.06,\n            'NCSN',\n            71450995\n          ],\n          [\n            '2010/09/13 16:12:56.63',\n            35.84184,\n            -117.67117,\n            6.954,\n            2.75,\n            'Md',\n            8,\n            120,\n            9,\n            0.09,\n            'NCSN',\n            71454895\n          ],\n          [\n            '2010/09/15 20:45:38.17',\n            36.38,\n            -117.85667,\n            3.311,\n            2.65,\n            'Md',\n            15,\n            132,\n            21,\n            0.08,\n            'NCSN',\n            71455830\n          ],\n          [\n            '2010/09/16 15:54:55.28',\n            35.84333,\n            -117.67267,\n            6.344,\n            3.5,\n            'Md',\n            10,\n            119,\n            9,\n            0.09,\n            'NCSN',\n            71456225\n          ],\n          [\n            '2010/09/16 22:10:07.53',\n            36.03367,\n            -117.77067,\n            4.964,\n            2.64,\n            'Md',\n            14,\n            100,\n            15,\n            0.05,\n            'NCSN',\n            71456365\n          ],\n          [\n            '2010/10/10 22:08:46.44',\n            36.01317,\n            -117.81184,\n            5.194,\n            2.61,\n            'Md',\n            8,\n            88,\n            16,\n            0.01,\n            'NCSN',\n            71466950\n          ],\n          [\n            '2010/10/12 22:24:13.45',\n            35.96433,\n            -117.65483,\n            3.224,\n            2.69,\n            'Md',\n            13,\n            125,\n            2,\n            0.1,\n            'NCSN',\n            71467795\n          ],\n          [\n            '2010/10/12 23:45:17.05',\n            35.98883,\n            -117.5925,\n            5.444,\n            2.76,\n            'Md',\n            9,\n            292,\n            7,\n            0.05,\n            'NCSN',\n            71467855\n          ],\n          [\n            '2010/10/13 02:09:10.98',\n            36.08517,\n            -117.85783,\n            5.404,\n            2.57,\n            'Md',\n            15,\n            80,\n            24,\n            0.12,\n            'NCSN',\n            71467900\n          ],\n          [\n            '2010/10/23 08:55:41.53',\n            35.9645,\n            -117.665,\n            2.174,\n            2.54,\n            'Md',\n            9,\n            123,\n            2,\n            0.23,\n            'NCSN',\n            71477781\n          ],\n          [\n            '2010/10/24 01:54:01.10',\n            36.37634,\n            -117.9415,\n            11.571,\n            2.59,\n            'Md',\n            10,\n            169,\n            14,\n            0.05,\n            'NCSN',\n            71478071\n          ],\n          [\n            '2010/10/24 08:31:06.53',\n            36.12883,\n            -117.84033,\n            8.344,\n            2.85,\n            'ML',\n            19,\n            84,\n            26,\n            0.1,\n            'NCSN',\n            71478171\n          ],\n          [\n            '2010/10/24 09:06:51.71',\n            36.1265,\n            -117.8345,\n            7.734,\n            2.73,\n            'Md',\n            21,\n            82,\n            26,\n            0.11,\n            'NCSN',\n            71478191\n          ],\n          [\n            '2010/10/29 08:22:23.57',\n            36.1255,\n            -117.66734,\n            2.754,\n            2.83,\n            'Md',\n            9,\n            118,\n            18,\n            0.05,\n            'NCSN',\n            71480766\n          ],\n          [\n            '2010/10/29 15:47:34.31',\n            36.0365,\n            -117.7745,\n            4.594,\n            2.91,\n            'Md',\n            16,\n            99,\n            15,\n            0.06,\n            'NCSN',\n            71480871\n          ],\n          [\n            '2010/10/29 15:53:50.46',\n            36.03983,\n            -117.77233,\n            4.409,\n            2.66,\n            'Md',\n            15,\n            100,\n            15,\n            0.04,\n            'NCSN',\n            71480881\n          ],\n          [\n            '2010/11/04 04:42:56.43',\n            35.79633,\n            -118.0405,\n            10.122,\n            2.53,\n            'Md',\n            11,\n            138,\n            10,\n            0.05,\n            'NCSN',\n            71483606\n          ],\n          [\n            '2010/11/06 12:20:55.23',\n            36.36517,\n            -117.847,\n            4.091,\n            2.55,\n            'Md',\n            9,\n            181,\n            23,\n            0.07,\n            'NCSN',\n            71484836\n          ],\n          [\n            '2010/11/07 08:00:15.85',\n            36.0375,\n            -117.77433,\n            5.188,\n            2.71,\n            'Md',\n            13,\n            161,\n            15,\n            0.07,\n            'NCSN',\n            71485386\n          ],\n          [\n            '2010/11/12 11:01:10.85',\n            36.38133,\n            -117.87434,\n            3.641,\n            2.95,\n            'ML',\n            13,\n            135,\n            28,\n            0.14,\n            'NCSN',\n            71487546\n          ],\n          [\n            '2010/11/18 06:13:53.70',\n            36.06116,\n            -117.869,\n            7.694,\n            2.92,\n            'Md',\n            13,\n            76,\n            24,\n            0.07,\n            'NCSN',\n            71484675\n          ],\n          [\n            '2010/11/25 04:01:15.44',\n            36.03617,\n            -117.77917,\n            5.204,\n            3.72,\n            'ML',\n            15,\n            98,\n            15,\n            0.06,\n            'NCSN',\n            71488000\n          ],\n          [\n            '2010/11/25 05:04:35.42',\n            36.03283,\n            -117.78933,\n            3.494,\n            2.68,\n            'Md',\n            11,\n            94,\n            16,\n            0.13,\n            'NCSN',\n            71488025\n          ],\n          [\n            '2010/12/01 12:16:14.47',\n            35.96267,\n            -117.65583,\n            3.644,\n            2.71,\n            'ML',\n            13,\n            143,\n            2,\n            0.09,\n            'NCSN',\n            71490865\n          ],\n          [\n            '2010/12/01 20:53:10.16',\n            35.96183,\n            -117.65317,\n            3.224,\n            3.21,\n            'ML',\n            10,\n            144,\n            2,\n            0.08,\n            'NCSN',\n            71491065\n          ],\n          [\n            '2010/12/01 20:53:36.48',\n            35.979,\n            -117.64417,\n            4.815,\n            2.99,\n            'ML',\n            9,\n            233,\n            3,\n            0.04,\n            'NCSN',\n            71046449\n          ],\n          [\n            '2010/12/08 02:36:56.48',\n            36.38833,\n            -117.8725,\n            2.165,\n            2.51,\n            'Md',\n            30,\n            134,\n            28,\n            0.16,\n            'NCSN',\n            71493990\n          ],\n          [\n            '2010/12/20 15:24:52.23',\n            36.64617,\n            -118.06216,\n            -0.36,\n            2.86,\n            'Md',\n            41,\n            172,\n            59,\n            0.11,\n            'NCSN',\n            71499175\n          ],\n          [\n            '2010/12/22 14:27:46.45',\n            36.00217,\n            -118.0585,\n            13.048,\n            2.5,\n            'Md',\n            9,\n            101,\n            14,\n            0.05,\n            'NCSN',\n            71499935\n          ],\n          [\n            '2011/01/08 10:31:42.90',\n            36.05333,\n            -117.85583,\n            -0.996,\n            2.56,\n            'Md',\n            22,\n            238,\n            96,\n            0.16,\n            'NCSN',\n            71507050\n          ],\n          [\n            '2011/02/05 21:34:49.57',\n            36.037,\n            -117.76833,\n            2.548,\n            2.64,\n            'Md',\n            11,\n            188,\n            15,\n            0.04,\n            'NCSN',\n            71525101\n          ],\n          [\n            '2011/02/23 03:16:14.18',\n            35.98883,\n            -117.61667,\n            5.458,\n            2.96,\n            'Md',\n            7,\n            286,\n            5,\n            0.03,\n            'NCSN',\n            71527310\n          ],\n          [\n            '2011/02/23 05:33:53.20',\n            35.97066,\n            -117.66183,\n            5.594,\n            2.78,\n            'Md',\n            11,\n            123,\n            20,\n            0.09,\n            'NCSN',\n            71527425\n          ],\n          [\n            '2011/02/24 18:28:06.95',\n            36.042,\n            -118.34766,\n            5.98,\n            2.64,\n            'Md',\n            8,\n            85,\n            13,\n            0.06,\n            'NCSN',\n            71528135\n          ],\n          [\n            '2011/02/24 20:05:47.43',\n            36.04083,\n            -118.34483,\n            2.723,\n            3.5,\n            'ML',\n            61,\n            62,\n            13,\n            0.16,\n            'NCSN',\n            71528180\n          ],\n          [\n            '2011/02/27 02:03:56.67',\n            36.03217,\n            -117.7735,\n            4.169,\n            2.66,\n            'Md',\n            11,\n            252,\n            24,\n            0.07,\n            'NCSN',\n            71529200\n          ],\n          [\n            '2011/02/27 09:49:56.23',\n            36.033,\n            -118.34583,\n            3.843,\n            2.65,\n            'Md',\n            11,\n            103,\n            14,\n            0.07,\n            'NCSN',\n            71529350\n          ],\n          [\n            '2011/03/02 00:38:22.54',\n            35.9745,\n            -117.66233,\n            4.421,\n            2.74,\n            'Md',\n            11,\n            123,\n            21,\n            0.04,\n            'NCSN',\n            71531325\n          ],\n          [\n            '2011/03/09 18:25:12.54',\n            36.145,\n            -117.92033,\n            9.67,\n            2.64,\n            'Md',\n            6,\n            96,\n            33,\n            0.05,\n            'NCSN',\n            71540331\n          ],\n          [\n            '2011/03/10 15:56:24.75',\n            36.03167,\n            -117.42567,\n            5.531,\n            3.08,\n            'ML',\n            11,\n            261,\n            31,\n            0.07,\n            'NCSN',\n            71540846\n          ],\n          [\n            '2011/03/14 20:02:32.96',\n            35.79633,\n            -117.97717,\n            8.111,\n            2.68,\n            'Md',\n            11,\n            111,\n            13,\n            0.05,\n            'NCSN',\n            71543146\n          ]\n        ],\n        fields: [\n          {\n            name: 'DateTime',\n            type: 'timestamp',\n            format: 'YYYY/M/D HH:mm:ss.SSSS',\n            analyzerType: 'DATETIME'\n          },\n          {name: 'Latitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Longitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Depth', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Magnitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'MagType', type: 'string', format: '', analyzerType: 'STRING'},\n          {name: 'NbStations', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'Gap', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'Distance', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'RMS', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Source', type: 'string', format: '', analyzerType: 'STRING'},\n          {name: 'EventID', type: 'integer', format: '', analyzerType: 'INT'}\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: ['20pde67d8r'],\n            id: 'co92ohnaw',\n            name: [],\n            type: 'polygon',\n            value: {\n              type: 'Feature',\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-118.20767308297478, 36.23374452687357],\n                    [-118.20767308297478, 35.886447523728584],\n                    [-117.51631123920194, 35.886447523728584],\n                    [-117.51631123920194, 36.23374452687357],\n                    [-118.20767308297478, 36.23374452687357]\n                  ]\n                ]\n              },\n              properties: {\n                renderType: 'Rectangle',\n                isClosed: true,\n                bbox: {xmin: -118.20767308297478, xmax: null, ymin: 36.229154589774176, ymax: null},\n                isVisible: true,\n                filterId: 'co92ohnaw'\n              },\n              id: 'c3222d4d-30d5-4c2d-beca-db557b6ce965'\n            },\n            enlarged: false,\n            plotType: 'histogram',\n            animationWindow: 'free',\n            yAxis: null,\n            layerId: ['06xco1x'],\n            speed: 1\n          }\n        ],\n        layers: [\n          {\n            id: '06xco1x',\n            type: 'point',\n            config: {\n              dataId: '20pde67d8r',\n              label: 'Point',\n              color: [18, 92, 119],\n              highlightColor: [252, 242, 26, 255],\n              columns: {lat: 'Latitude', lng: 'Longitude', altitude: null},\n              isVisible: true,\n              visConfig: {\n                radius: 10,\n                fixedRadius: false,\n                opacity: 0.8,\n                outline: false,\n                thickness: 2,\n                strokeColor: null,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                strokeColorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radiusRange: [0, 50],\n                filled: true\n              },\n              hidden: false,\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: {name: 'Depth', type: 'real'},\n              colorScale: 'quantile',\n              strokeColorField: null,\n              strokeColorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              '20pde67d8r': [\n                {name: 'DateTime', format: null},\n                {name: 'Latitude', format: null},\n                {name: 'Longitude', format: null},\n                {name: 'Depth', format: null},\n                {name: 'Magnitude', format: null}\n              ]\n            },\n            compareMode: false,\n            compareType: 'absolute',\n            enabled: true\n          },\n          brush: {size: 0.5, enabled: false},\n          geocoder: {enabled: false},\n          coordinate: {enabled: false}\n        },\n        layerBlending: 'normal',\n        splitMaps: [],\n        animationConfig: {currentTime: null, speed: 1}\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 36.245218190831125,\n        longitude: -117.80651250695874,\n        pitch: 0,\n        zoom: 7.949152711295885,\n        isSplit: false\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true,\n          '3d building': false\n        },\n        threeDBuildingColor: [9.665468314072013, 17.18305478057247, 31.1442867897876],\n        mapStyles: {}\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Thu Oct 21 2021 09:12:14 GMT-0400 (Eastern Daylight Time)',\n    title: 'keplergl_rvxe1dv',\n    description: ''\n  }\n};\n\nexport const mockStateWithPolygonFilter = () => {\n  const initialState = CloneDeep(InitialState);\n  const result = processKeplerglJSON(polygonFilterMap);\n  const newState = coreReducer(initialState, addDataToMap(result));\n\n  return newState;\n};\n"
  },
  {
    "path": "test/fixtures/polygon-filter-map.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const polygonFilterMap = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: '9bluyz5s2',\n        label: 'kepler-gl_new dataset (3).csv',\n        color: [143, 47, 191],\n        allData: [\n          [\n            '1967/08/01 10:33:50.47',\n            36.08,\n            -121.07083,\n            80.339,\n            2.5,\n            'Mx',\n            10,\n            292,\n            42,\n            0.25,\n            'NCSN',\n            1000872\n          ],\n          [\n            '1967/08/02 02:49:12.55',\n            35.63433,\n            -120.75716,\n            3.98,\n            2.6,\n            'Mx',\n            9,\n            322,\n            108,\n            0.24,\n            'NCSN',\n            1000887\n          ],\n          [\n            '1967/08/03 05:55:26.73',\n            36.37967,\n            -121.0085,\n            39.609,\n            2.7,\n            'Mx',\n            10,\n            298,\n            21,\n            0.41,\n            'NCSN',\n            1000912\n          ],\n          [\n            '1967/08/03 06:57:01.25',\n            36.3955,\n            -121.01667,\n            40.159,\n            2.7,\n            'Mx',\n            10,\n            293,\n            19,\n            0.46,\n            'NCSN',\n            1000916\n          ],\n          [\n            '1967/08/03 20:21:26.52',\n            36.5435,\n            -121.17216,\n            5.945,\n            2.6,\n            'Mx',\n            10,\n            132,\n            6,\n            0.07,\n            'NCSN',\n            1000926\n          ],\n          [\n            '1967/08/09 04:53:59.73',\n            35.4015,\n            -120.58783,\n            4.06,\n            2.6,\n            'Mx',\n            7,\n            331,\n            131,\n            0.26,\n            'NCSN',\n            1000979\n          ],\n          [\n            '1967/08/09 07:27:32.82',\n            36.15567,\n            -121.09367,\n            68.79,\n            2.7,\n            'Mx',\n            10,\n            284,\n            33,\n            0.39,\n            'NCSN',\n            1000981\n          ],\n          [\n            '1967/08/13 20:29:14.27',\n            35.4195,\n            -121.109,\n            4.05,\n            2.7,\n            'Mx',\n            9,\n            325,\n            124,\n            0.28,\n            'NCSN',\n            1001030\n          ],\n          [\n            '1967/08/15 16:53:24.26',\n            35.48417,\n            -121.01167,\n            4.585,\n            2.9,\n            'Mx',\n            8,\n            322,\n            107,\n            0.77,\n            'NCSN',\n            1001047\n          ],\n          [\n            '1967/08/22 08:04:34.06',\n            37.65333,\n            -122.4295,\n            39.675,\n            2.5,\n            'Mx',\n            25,\n            329,\n            30,\n            0.24,\n            'NCSN',\n            1001119\n          ]\n        ],\n        fields: [\n          {\n            name: 'DateTime',\n            type: 'timestamp',\n            format: 'YYYY/M/D HH:mm:ss.SSSS',\n            analyzerType: 'DATETIME'\n          },\n          {\n            name: 'Latitude',\n            type: 'real',\n            format: '',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'Longitude',\n            type: 'real',\n            format: '',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'Depth',\n            type: 'real',\n            format: '',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'Magnitude',\n            type: 'real',\n            format: '',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'MagType',\n            type: 'string',\n            format: '',\n            analyzerType: 'STRING'\n          },\n          {\n            name: 'NbStations',\n            type: 'integer',\n            format: '',\n            analyzerType: 'INT'\n          },\n          {\n            name: 'Gap',\n            type: 'integer',\n            format: '',\n            analyzerType: 'INT'\n          },\n          {\n            name: 'Distance',\n            type: 'integer',\n            format: '',\n            analyzerType: 'INT'\n          },\n          {\n            name: 'RMS',\n            type: 'real',\n            format: '',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'Source',\n            type: 'string',\n            format: '',\n            analyzerType: 'STRING'\n          },\n          {\n            name: 'EventID',\n            type: 'integer',\n            format: '',\n            analyzerType: 'INT'\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: ['9bluyz5s2'],\n            id: '1545pmr0s',\n            name: ['Point'],\n            type: 'polygon',\n            value: {\n              type: 'Feature',\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-121.20301917819414, 36.19054006644038],\n                    [-121.20301917819414, 36.05580399351024],\n                    [-120.91706956830599, 36.05580399351024],\n                    [-120.91706956830599, 36.19054006644038],\n                    [-121.20301917819414, 36.19054006644038]\n                  ]\n                ]\n              },\n              properties: {\n                renderType: 'Rectangle',\n                isClosed: true,\n                bbox: {\n                  xmin: -121.20518546311551,\n                  xmax: null,\n                  ymin: 36.19054006644038,\n                  ymax: null\n                }\n              },\n              id: '3f29ec21-4c67-4741-9504-1d6f0eb831c4'\n            },\n            enlarged: false,\n            plotType: 'histogram',\n            yAxis: null,\n            fixedDomain: true,\n            layerId: ['i1w1f0m']\n          }\n        ],\n        layers: [\n          {\n            id: 'i1w1f0m',\n            type: 'point',\n            config: {\n              dataId: '9bluyz5s2',\n              label: 'Point',\n              color: [183, 136, 94],\n              columns: {\n                lat: 'Latitude',\n                lng: 'Longitude',\n                altitude: null\n              },\n              isVisible: true,\n              visConfig: {\n                radius: 10,\n                fixedRadius: false,\n                opacity: 0.8,\n                outline: false,\n                thickness: 2,\n                strokeColor: null,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                strokeColorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radiusRange: [0, 50],\n                filled: true\n              },\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              strokeColorField: null,\n              strokeColorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              '9bluyz5s2': ['DateTime', 'Latitude', 'Longitude', 'Depth', 'Magnitude']\n            },\n            enabled: true\n          },\n          brush: {\n            size: 0.5,\n            enabled: false\n          }\n        },\n        layerBlending: 'normal',\n        splitMaps: [],\n        animationConfig: {\n          currentTime: null,\n          speed: 1\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 35.89276276972923,\n        longitude: -121.07954093755023,\n        pitch: 0,\n        zoom: 8.342414373255385,\n        isSplit: false\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true,\n          '3d building': false\n        },\n        threeDBuildingColor: [9.665468314072013, 17.18305478057247, 31.1442867897876],\n        mapStyles: {}\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Thu Dec 26 2019 16:00:54 GMT+0100 (Central European Standard Time)'\n  }\n};\n"
  },
  {
    "path": "test/fixtures/polygon.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const mockPolygonFeature = {\n  type: 'Feature',\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [28.66601562502387, 15.343585399424173],\n        [28.66601562502387, 11.109477534559822],\n        [32.75292968752382, 11.109477534559822],\n        [32.75292968752382, 15.343585399424173],\n        [28.66601562502387, 15.343585399424173]\n      ]\n    ]\n  },\n  properties: {\n    renderType: 'Polygon',\n    isClosed: true,\n    bbox: {\n      xmin: 28.094726562479867,\n      xmax: null,\n      ymin: 10.872211827249156,\n      ymax: null\n    }\n  },\n  id: 'bf5be428-f522-4a52-8ea5-3e90e86aad51'\n};\n\nexport const mockPolygonData = {\n  fields: [\n    {\n      name: 'start_point_lat',\n      type: 'real'\n    },\n    {\n      name: 'start_point_lng',\n      type: 'real'\n    },\n    {\n      name: 'end_point_lat',\n      type: 'real'\n    },\n    {\n      name: 'end_point_lng',\n      type: 'real'\n    }\n  ],\n  data: [\n    [12.25, 30.5, 45.5, 32.5],\n    [12.25, 35.5, 45.5, 36.5],\n    [14.25, 30.5, 47.5, 32.5],\n    [14.25, 35.5, 47.5, 36.5]\n  ],\n  layers: [\n    {\n      id: 'm1buc6o',\n      type: 'point',\n      config: {\n        dataId: 'puppy',\n        label: 'start point',\n        color: [227, 26, 26],\n        columns: {\n          lat: 'start_point_lat',\n          lng: 'start_point_lng',\n          altitude: null\n        },\n        isVisible: true,\n        visConfig: {\n          radius: 10,\n          fixedRadius: false,\n          opacity: 0.8,\n          outline: false,\n          thickness: 2,\n          strokeColor: null,\n          colorRange: {\n            name: 'Global Warming',\n            type: 'sequential',\n            category: 'Uber',\n            colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n          },\n          strokeColorRange: {\n            name: 'Global Warming',\n            type: 'sequential',\n            category: 'Uber',\n            colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n          },\n          radiusRange: [0, 50],\n          filled: true\n        },\n        textLabel: [\n          {\n            field: null,\n            color: [255, 255, 255],\n            size: 18,\n            offset: [0, 0],\n            anchor: 'start',\n            alignment: 'center'\n          }\n        ]\n      },\n      visualChannels: {\n        colorField: null,\n        colorScale: 'quantile',\n        strokeColorField: null,\n        strokeColorScale: 'quantile',\n        sizeField: null,\n        sizeScale: 'linear'\n      }\n    },\n    {\n      id: 'jcotm1p',\n      type: 'point',\n      config: {\n        dataId: 'puppy',\n        label: 'end point',\n        color: [255, 203, 153],\n        columns: {\n          lat: 'end_point_lat',\n          lng: 'end_point_lng',\n          altitude: null\n        },\n        isVisible: true,\n        visConfig: {\n          radius: 10,\n          fixedRadius: false,\n          opacity: 0.8,\n          outline: false,\n          thickness: 2,\n          strokeColor: null,\n          colorRange: {\n            name: 'Global Warming',\n            type: 'sequential',\n            category: 'Uber',\n            colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n          },\n          strokeColorRange: {\n            name: 'Global Warming',\n            type: 'sequential',\n            category: 'Uber',\n            colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n          },\n          radiusRange: [0, 50],\n          filled: true\n        },\n        textLabel: [\n          {\n            field: null,\n            color: [255, 255, 255],\n            size: 18,\n            offset: [0, 0],\n            anchor: 'start',\n            alignment: 'center'\n          }\n        ]\n      },\n      visualChannels: {\n        colorField: null,\n        colorScale: 'quantile',\n        strokeColorField: null,\n        strokeColorScale: 'quantile',\n        sizeField: null,\n        sizeScale: 'linear'\n      }\n    }\n  ],\n  interactionConfig: {\n    tooltip: {\n      fieldsToShow: {\n        zgi2h4mkn: []\n      },\n      enabled: true\n    },\n    brush: {\n      size: 0.5,\n      enabled: false\n    }\n  }\n};\n\nexport const mockPolygonFeature2 = {\n  type: 'Feature',\n  geometry: {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [-122.447837, 37.768506],\n        [-122.402435, 37.798517],\n        [-122.379596, 37.776771],\n        [-122.400234, 37.755671],\n        [-122.447837, 37.768506]\n      ]\n    ]\n  },\n  properties: {},\n  id: '5afe6042-b0f7-4249-8a59-5911901912ad'\n};\n"
  },
  {
    "path": "test/fixtures/row-object.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst data = `[{\"bedrooms\": \"Null\", \"bathrooms\": \"N/A\", \"effectiveyearbuilt\": \"Null\", \"roll_landbaseyear\": \"N/\", \"roll_totlandimp\": \"37827477\", \"nettaxablevalue\": \"37827477\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10627524\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"27199953\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"37827477\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"37085763\", \"nettaxablevalue\": \"37097927\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10419142\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"26666621\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"37097927\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"35463324\", \"nettaxablevalue\": \"35463324\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"9963324\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"25500000\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"35463324\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"36172590\", \"nettaxablevalue\": \"36172590\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10162590\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"26010000\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"36172590\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"38758435\", \"nettaxablevalue\": \"38775032\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10889073\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"27869362\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"38775032\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"36086860\", \"nettaxablevalue\": \"36092822\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10138504\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"25948356\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"36092822\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"37999212\", \"nettaxablevalue\": \"38012236\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10675772\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"27323440\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"38012236\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}, {\"bedrooms\": \"99\", \"bathrooms\": \"99\", \"effectiveyearbuilt\": \"2006\", \"roll_landbaseyear\": \"2007\", \"roll_totlandimp\": \"36358593\", \"nettaxablevalue\": \"36370757\", \"center_lon\": \"-118.40032195\", \"roll_landvalue\": \"10214846\", \"usecodedescchar1\": \"Residential\", \"usetype\": \"R-I\", \"yearbuiltTS\": \"1136073600000\", \"yearbuilt\": \"2006\", \"roll_impvalue\": \"26143747\", \"istaxableparcel\": \"true\", \"propertylocation\": \"3762 CLARINGTON AVE  LOS ANGELES CA  90034\", \"units\": \"116\", \"sqftmain\": \"130693\", \"roll_totalvalue\": \"36370757\", \"situszip5\": \"90034\", \"center_lat\": \"34.02322471\"}]`;\n\nexport const parsedFields = [\n  {\n    name: 'bedrooms',\n    id: 'bedrooms',\n    displayName: 'bedrooms',\n    format: '',\n    fieldIdx: 0,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[0]\n  },\n  {\n    name: 'bathrooms',\n    id: 'bathrooms',\n    displayName: 'bathrooms',\n    format: '',\n    fieldIdx: 1,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'effectiveyearbuilt',\n    id: 'effectiveyearbuilt',\n    displayName: 'effectiveyearbuilt',\n    format: '',\n    fieldIdx: 2,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[2]\n  },\n  {\n    name: 'roll_landbaseyear',\n    id: 'roll_landbaseyear',\n    displayName: 'roll_landbaseyear',\n    format: '',\n    fieldIdx: 3,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[3]\n  },\n  {\n    name: 'roll_totlandimp',\n    id: 'roll_totlandimp',\n    displayName: 'roll_totlandimp',\n    format: '',\n    fieldIdx: 4,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[4]\n  },\n  {\n    name: 'nettaxablevalue',\n    id: 'nettaxablevalue',\n    displayName: 'nettaxablevalue',\n    format: '',\n    fieldIdx: 5,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[5]\n  },\n  {\n    name: 'center_lon',\n    id: 'center_lon',\n    displayName: 'center_lon',\n    format: '',\n    fieldIdx: 6,\n    type: 'real',\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[6]\n  },\n  {\n    name: 'roll_landvalue',\n    id: 'roll_landvalue',\n    displayName: 'roll_landvalue',\n    format: '',\n    fieldIdx: 7,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[7]\n  },\n  {\n    name: 'usecodedescchar1',\n    id: 'usecodedescchar1',\n    displayName: 'usecodedescchar1',\n    format: '',\n    fieldIdx: 8,\n    type: 'string',\n    analyzerType: 'STRING',\n    valueAccessor: values => values[8]\n  },\n  {\n    name: 'usetype',\n    id: 'usetype',\n    displayName: 'usetype',\n    format: '',\n    fieldIdx: 9,\n    type: 'string',\n    analyzerType: 'STRING',\n    valueAccessor: values => values[9]\n  },\n  {\n    name: 'yearbuiltTS',\n    id: 'yearbuiltTS',\n    displayName: 'yearbuiltTS',\n    format: 'X',\n    fieldIdx: 10,\n    type: 'timestamp',\n    analyzerType: 'TIME',\n    valueAccessor: values => values[10]\n  },\n  {\n    name: 'yearbuilt',\n    id: 'yearbuilt',\n    displayName: 'yearbuilt',\n    format: '',\n    fieldIdx: 11,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[11]\n  },\n  {\n    name: 'roll_impvalue',\n    id: 'roll_impvalue',\n    displayName: 'roll_impvalue',\n    format: '',\n    fieldIdx: 12,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[12]\n  },\n  {\n    name: 'istaxableparcel',\n    id: 'istaxableparcel',\n    displayName: 'istaxableparcel',\n    format: '',\n    fieldIdx: 13,\n    type: 'boolean',\n    analyzerType: 'BOOLEAN',\n    valueAccessor: values => values[13]\n  },\n  {\n    name: 'propertylocation',\n    id: 'propertylocation',\n    displayName: 'propertylocation',\n    format: '',\n    fieldIdx: 14,\n    type: 'string',\n    analyzerType: 'STRING',\n    valueAccessor: values => values[14]\n  },\n  {\n    name: 'units',\n    id: 'units',\n    displayName: 'units',\n    format: '',\n    fieldIdx: 15,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[15]\n  },\n  {\n    name: 'sqftmain',\n    id: 'sqftmain',\n    displayName: 'sqftmain',\n    format: '',\n    fieldIdx: 16,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[16]\n  },\n  {\n    name: 'roll_totalvalue',\n    id: 'roll_totalvalue',\n    displayName: 'roll_totalvalue',\n    format: '',\n    fieldIdx: 17,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[17]\n  },\n  {\n    name: 'situszip5',\n    id: 'situszip5',\n    displayName: 'situszip5',\n    format: '',\n    fieldIdx: 18,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[18]\n  },\n  {\n    name: 'center_lat',\n    id: 'center_lat',\n    displayName: 'center_lat',\n    format: '',\n    fieldIdx: 19,\n    type: 'real',\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[19]\n  }\n];\n\nexport const parsedRows = [\n  [\n    null,\n    NaN,\n    null,\n    NaN,\n    37827477,\n    37827477,\n    -118.40032195,\n    10627524,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    27199953,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    37827477,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    37085763,\n    37097927,\n    -118.40032195,\n    10419142,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    26666621,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    37097927,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    35463324,\n    35463324,\n    -118.40032195,\n    9963324,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    25500000,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    35463324,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    36172590,\n    36172590,\n    -118.40032195,\n    10162590,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    26010000,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    36172590,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    38758435,\n    38775032,\n    -118.40032195,\n    10889073,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    27869362,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    38775032,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    36086860,\n    36092822,\n    -118.40032195,\n    10138504,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    25948356,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    36092822,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    37999212,\n    38012236,\n    -118.40032195,\n    10675772,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    27323440,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    38012236,\n    90034,\n    34.02322471\n  ],\n  [\n    99,\n    99,\n    2006,\n    2007,\n    36358593,\n    36370757,\n    -118.40032195,\n    10214846,\n    'Residential',\n    'R-I',\n    1136073600000,\n    2006,\n    26143747,\n    true,\n    '3762 CLARINGTON AVE  LOS ANGELES CA  90034',\n    116,\n    130693,\n    36370757,\n    90034,\n    34.02322471\n  ]\n];\n\nexport default data;\n"
  },
  {
    "path": "test/fixtures/s2-geometry.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const data =\n  's2,value\\n' +\n  '80858004,0.5979242952642347\\n' +\n  '8085800c,0.5446256069712141\\n' +\n  '80858014,0.1187171597109975\\n' +\n  '8085801c,0.2859146314037557\\n' +\n  '80858024,0.19549012367504126\\n' +\n  '80858034,0.3373452974230604\\n' +\n  '8085803c,0.9218176408795662\\n' +\n  '80858044,0.23470692356446143\\n' +\n  '8085804c,0.1580509670379684\\n' +\n  '80858054,0.15992745628743954';\n"
  },
  {
    "path": "test/fixtures/state-saved-v0.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nconst {PointLayer, ArcLayer, HexagonLayer, GeojsonLayer} = KeplerGlLayers;\nimport {DEFAULT_TEXT_LABEL, DEFAULT_COLOR_UI, BINS} from '@kepler.gl/constants';\nimport {defaultInteractionConfig} from '@kepler.gl/reducers';\nimport {getBinThresholds, histogramFromThreshold, histogramFromDomain} from '@kepler.gl/utils';\n\nexport const savedStateV0 = {\n  config: {\n    version: 'v0',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: '9h10t7fyb',\n            id: 'vxzfwyg2v',\n            name: 'song_name',\n            type: 'multiSelect',\n            value: ['3.68.4', '2.117.1', '2.103.2', '2.116.2'],\n            enlarged: false\n          },\n          {\n            dataId: '9h10t7fyb',\n            id: 'fo9tjm2unl',\n            name: 'timestamp_local',\n            type: 'timeRange',\n            value: [1453770124000, 1453770415000],\n            enlarged: true\n          },\n          {\n            dataId: '9h10t7fyb',\n            id: 'aesy96t5',\n            name: 'type_boolean',\n            type: 'select',\n            value: false,\n            enlarged: false\n          },\n          {\n            dataId: '9h10t7fyb',\n            id: 's1bhgjt1',\n            name: 'int_range',\n            type: 'range',\n            value: [78, 309],\n            enlarged: false\n          },\n          {\n            dataId: 'v79816te8',\n            id: '5nfmxjjzl',\n            name: 'ZIP_CODE',\n            type: 'range',\n            value: [94115.3, 94132],\n            histogram: [\n              {count: 1, x0: 94107, x1: 94107.2},\n              {count: 0, x0: 94107.2, x1: 94107.4},\n              {count: 0, x0: 94107.4, x1: 94107.6},\n              {count: 0, x0: 94107.6, x1: 94107.8},\n              {count: 0, x0: 94107.8, x1: 94108},\n              {count: 0, x0: 94108, x1: 94108.2},\n              {count: 0, x0: 94108.2, x1: 94108.4},\n              {count: 0, x0: 94108.4, x1: 94108.6},\n              {count: 0, x0: 94108.6, x1: 94108.8},\n              {count: 0, x0: 94108.8, x1: 94109},\n              {count: 0, x0: 94109, x1: 94109.2},\n              {count: 0, x0: 94109.2, x1: 94109.4},\n              {count: 0, x0: 94109.4, x1: 94109.6},\n              {count: 0, x0: 94109.6, x1: 94109.8},\n              {count: 0, x0: 94109.8, x1: 94110},\n              {count: 0, x0: 94110, x1: 94110.2},\n              {count: 0, x0: 94110.2, x1: 94110.4},\n              {count: 0, x0: 94110.4, x1: 94110.6},\n              {count: 0, x0: 94110.6, x1: 94110.8},\n              {count: 0, x0: 94110.8, x1: 94111},\n              {count: 0, x0: 94111, x1: 94111.2},\n              {count: 0, x0: 94111.2, x1: 94111.4},\n              {count: 0, x0: 94111.4, x1: 94111.6},\n              {count: 0, x0: 94111.6, x1: 94111.8},\n              {count: 0, x0: 94111.8, x1: 94112},\n              {count: 0, x0: 94112, x1: 94112.2},\n              {count: 0, x0: 94112.2, x1: 94112.4},\n              {count: 0, x0: 94112.4, x1: 94112.6},\n              {count: 0, x0: 94112.6, x1: 94112.8},\n              {count: 0, x0: 94112.8, x1: 94113},\n              {count: 0, x0: 94113, x1: 94113.2},\n              {count: 0, x0: 94113.2, x1: 94113.4},\n              {count: 0, x0: 94113.4, x1: 94113.6},\n              {count: 0, x0: 94113.6, x1: 94113.8},\n              {count: 0, x0: 94113.8, x1: 94114},\n              {count: 0, x0: 94114, x1: 94114.2},\n              {count: 0, x0: 94114.2, x1: 94114.4},\n              {count: 0, x0: 94114.4, x1: 94114.6},\n              {count: 0, x0: 94114.6, x1: 94114.8},\n              {count: 0, x0: 94114.8, x1: 94115},\n              {count: 0, x0: 94115, x1: 94115.2},\n              {count: 0, x0: 94115.2, x1: 94115.4},\n              {count: 0, x0: 94115.4, x1: 94115.6},\n              {count: 0, x0: 94115.6, x1: 94115.8},\n              {count: 0, x0: 94115.8, x1: 94116},\n              {count: 0, x0: 94116, x1: 94116.2},\n              {count: 0, x0: 94116.2, x1: 94116.4},\n              {count: 0, x0: 94116.4, x1: 94116.6},\n              {count: 0, x0: 94116.6, x1: 94116.8},\n              {count: 0, x0: 94116.8, x1: 94117},\n              {count: 0, x0: 94117, x1: 94117.2},\n              {count: 0, x0: 94117.2, x1: 94117.4},\n              {count: 0, x0: 94117.4, x1: 94117.6},\n              {count: 0, x0: 94117.6, x1: 94117.8},\n              {count: 0, x0: 94117.8, x1: 94118},\n              {count: 0, x0: 94118, x1: 94118.2},\n              {count: 0, x0: 94118.2, x1: 94118.4},\n              {count: 0, x0: 94118.4, x1: 94118.6},\n              {count: 0, x0: 94118.6, x1: 94118.8},\n              {count: 0, x0: 94118.8, x1: 94119},\n              {count: 0, x0: 94119, x1: 94119.2},\n              {count: 0, x0: 94119.2, x1: 94119.4},\n              {count: 0, x0: 94119.4, x1: 94119.6},\n              {count: 0, x0: 94119.6, x1: 94119.8},\n              {count: 0, x0: 94119.8, x1: 94120},\n              {count: 0, x0: 94120, x1: 94120.2},\n              {count: 0, x0: 94120.2, x1: 94120.4},\n              {count: 0, x0: 94120.4, x1: 94120.6},\n              {count: 0, x0: 94120.6, x1: 94120.8},\n              {count: 0, x0: 94120.8, x1: 94121},\n              {count: 0, x0: 94121, x1: 94121.2},\n              {count: 0, x0: 94121.2, x1: 94121.4},\n              {count: 0, x0: 94121.4, x1: 94121.6},\n              {count: 0, x0: 94121.6, x1: 94121.8},\n              {count: 0, x0: 94121.8, x1: 94122},\n              {count: 0, x0: 94122, x1: 94122.2},\n              {count: 0, x0: 94122.2, x1: 94122.4},\n              {count: 0, x0: 94122.4, x1: 94122.6},\n              {count: 0, x0: 94122.6, x1: 94122.8},\n              {count: 0, x0: 94122.8, x1: 94123},\n              {count: 0, x0: 94123, x1: 94123.2},\n              {count: 0, x0: 94123.2, x1: 94123.4},\n              {count: 0, x0: 94123.4, x1: 94123.6},\n              {count: 0, x0: 94123.6, x1: 94123.8},\n              {count: 0, x0: 94123.8, x1: 94124},\n              {count: 0, x0: 94124, x1: 94124.2},\n              {count: 0, x0: 94124.2, x1: 94124.4},\n              {count: 0, x0: 94124.4, x1: 94124.6},\n              {count: 0, x0: 94124.6, x1: 94124.8},\n              {count: 0, x0: 94124.8, x1: 94125},\n              {count: 0, x0: 94125, x1: 94125.2},\n              {count: 0, x0: 94125.2, x1: 94125.4},\n              {count: 0, x0: 94125.4, x1: 94125.6},\n              {count: 0, x0: 94125.6, x1: 94125.8},\n              {count: 0, x0: 94125.8, x1: 94126},\n              {count: 0, x0: 94126, x1: 94126.2},\n              {count: 0, x0: 94126.2, x1: 94126.4},\n              {count: 0, x0: 94126.4, x1: 94126.6},\n              {count: 0, x0: 94126.6, x1: 94126.8},\n              {count: 0, x0: 94126.8, x1: 94127},\n              {count: 0, x0: 94127, x1: 94127.2},\n              {count: 0, x0: 94127.2, x1: 94127.4},\n              {count: 0, x0: 94127.4, x1: 94127.6},\n              {count: 0, x0: 94127.6, x1: 94127.8},\n              {count: 0, x0: 94127.8, x1: 94128},\n              {count: 0, x0: 94128, x1: 94128.2},\n              {count: 0, x0: 94128.2, x1: 94128.4},\n              {count: 0, x0: 94128.4, x1: 94128.6},\n              {count: 0, x0: 94128.6, x1: 94128.8},\n              {count: 0, x0: 94128.8, x1: 94129},\n              {count: 1, x0: 94129, x1: 94129.2},\n              {count: 0, x0: 94129.2, x1: 94129.4},\n              {count: 0, x0: 94129.4, x1: 94129.6},\n              {count: 0, x0: 94129.6, x1: 94129.8},\n              {count: 0, x0: 94129.8, x1: 94130},\n              {count: 0, x0: 94130, x1: 94130.2},\n              {count: 0, x0: 94130.2, x1: 94130.4},\n              {count: 0, x0: 94130.4, x1: 94130.6},\n              {count: 0, x0: 94130.6, x1: 94130.8},\n              {count: 0, x0: 94130.8, x1: 94131},\n              {count: 0, x0: 94131, x1: 94131.2},\n              {count: 0, x0: 94131.2, x1: 94131.4},\n              {count: 0, x0: 94131.4, x1: 94131.6},\n              {count: 0, x0: 94131.6, x1: 94131.8},\n              {count: 0, x0: 94131.8, x1: 94132},\n              {count: 1, x0: 94132, x1: 94132}\n            ],\n            enlarged: false\n          }\n        ],\n        layers: [\n          {\n            id: '1eh',\n            dataId: '9h10t7fyb',\n            label: 'dropoff',\n            color: [76, 154, 78],\n            columns: {\n              lat: {\n                value: 'dropoff_lat',\n                fieldIdx: 6\n              },\n              lng: {\n                value: 'dropoff_lng',\n                fieldIdx: 7\n              }\n            },\n            isVisible: false,\n            isAggregated: false,\n            type: 'point',\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            visConfig: {\n              radius: 270.4,\n              opacity: 0.8,\n              outline: false,\n              thickness: 2,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              radiusRange: [1, 100],\n              'hi-precision': false\n            }\n          },\n          {\n            id: 'tud',\n            dataId: '9h10t7fyb',\n            label: 'trip arc',\n            color: [18, 147, 154],\n            columns: {\n              lat0: {\n                value: 'begintrip_lat',\n                fieldIdx: 1\n              },\n              lng0: {\n                value: 'begintrip_lng',\n                fieldIdx: 2\n              },\n              lat1: {\n                value: 'dropoff_lat',\n                fieldIdx: 6\n              },\n              lng1: {\n                value: 'dropoff_lng',\n                fieldIdx: 7\n              }\n            },\n            isVisible: true,\n            isAggregated: false,\n            type: 'arc',\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            visConfig: {\n              opacity: 0.41,\n              thickness: 2,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              sizeRange: [0, 10],\n              'hi-precision': false\n            }\n          },\n          {\n            id: 'pwl',\n            dataId: '9h10t7fyb',\n            label: 'begintrip',\n            color: [218, 112, 191],\n            columns: {\n              lat: {\n                value: 'begintrip_lat',\n                fieldIdx: 1\n              },\n              lng: {\n                value: 'begintrip_lng',\n                fieldIdx: 2\n              }\n            },\n            isVisible: true,\n            isAggregated: false,\n            type: 'point',\n            colorField: {\n              name: 'song_name',\n              type: 'string'\n            },\n            colorScale: 'ordinal',\n            sizeField: {\n              name: 'int_range',\n              type: 'integer'\n            },\n            sizeScale: 'linear',\n            visConfig: {\n              radius: 10,\n              opacity: 0.8,\n              outline: false,\n              thickness: 2,\n              colorRange: {\n                name: 'Sunrise',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#355C7D', '#6C5B7B', '#C06C84', '#F67280', '#F8B195'],\n                reversed: false\n              },\n              radiusRange: [1, 854.16],\n              'hi-precision': false\n            }\n          },\n          {\n            id: 'p5',\n            dataId: '9h10t7fyb',\n            label: 'begintrip_hex',\n            color: [241, 92, 23],\n            columns: {\n              lat: {\n                value: 'begintrip_lat',\n                fieldIdx: 1\n              },\n              lng: {\n                value: 'begintrip_lng',\n                fieldIdx: 2\n              }\n            },\n            isVisible: true,\n            isAggregated: true,\n            type: 'hexagon',\n            colorField: {\n              name: 'int_range',\n              type: 'integer'\n            },\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear',\n            visConfig: {\n              opacity: 0.8,\n              worldUnitSize: 0.5,\n              resolution: 8,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              coverage: 1,\n              sizeRange: [0, 50],\n              upperPercentile: 100,\n              elevationScale: 10\n            },\n            enable3d: true,\n            colorAggregation: 'maximum',\n            sizeAggregation: 'average'\n          },\n          {\n            id: 'vta',\n            dataId: 'v79816te8',\n            label: 'zip',\n            color: [255, 153, 31, 255],\n            columns: {\n              geojson: {\n                value: '_geojson',\n                fieldIdx: 0\n              }\n            },\n            isVisible: true,\n            isAggregated: false,\n            type: 'geojson',\n            colorField: {\n              name: 'ID',\n              type: 'integer'\n            },\n            colorScale: 'quantize',\n            sizeField: null,\n            sizeScale: 'linear',\n            visConfig: {\n              colorRange: {\n                name: 'Uber Viz Qualitative 3',\n                type: 'qualitative',\n                category: 'Uber',\n                colors: [\n                  '#12939A',\n                  '#DDB27C',\n                  '#88572C',\n                  '#FF991F',\n                  '#F15C17',\n                  '#223F9A',\n                  '#DA70BF',\n                  '#125C77',\n                  '#4DC19C',\n                  '#776E57',\n                  '#17B8BE',\n                  '#F6D18A',\n                  '#B7885E',\n                  '#FFCB99',\n                  '#F89570'\n                ],\n                reversed: false\n              },\n              opacity: 0.8,\n              thickness: 2,\n              sizeRange: [0, 10],\n              stroked: false,\n              filled: true,\n              extruded: false,\n              wireframe: false,\n              'hi-precision': false\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              '9h10t7fyb': ['int_range', 'detail', 'type_boolean'],\n              v79816te8: ['ID', 'ZIP_CODE']\n            }\n          }\n        },\n        layerBlending: 'normal'\n      },\n      mapStyle: {\n        styleType: 'muted',\n        topLayerGroups: {\n          label: true\n        },\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: false,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.766239122943205,\n        longitude: -122.48468972813009,\n        pitch: 0,\n        zoom: 11.433560418908408\n      }\n    }\n  },\n  datasets: [\n    {\n      version: 'v0',\n      data: {\n        id: '9h10t7fyb',\n        label: 'small_dataset.csv',\n        color: [53, 92, 125],\n        allData: [\n          [\n            '3.68.4',\n            38.4163786,\n            -121.7922809,\n            '2016-01-26T01:13:30.000Z',\n            -83.7,\n            'blue',\n            38.5590766,\n            -121.3686062,\n            '2016-01-26T02:42:13.000Z',\n            694,\n            false\n          ],\n          [\n            '2.117.1',\n            37.75159991,\n            -122.4761712,\n            '2016-01-26T01:04:39.000Z',\n            -6.79,\n            'red',\n            37.79887225,\n            -122.419046,\n            '2016-01-26T01:20:42.000Z',\n            335,\n            false\n          ],\n          [\n            '2.117.1',\n            37.8323825,\n            -122.2736475,\n            '2016-01-26T01:05:58.000Z',\n            -12,\n            'red',\n            37.8983639,\n            -122.2906993,\n            '2016-01-26T01:21:27.000Z',\n            363,\n            false\n          ],\n          [\n            '2.116.2',\n            37.77565501,\n            -122.4403984,\n            '2016-01-26T01:02:04.000Z',\n            -8.75,\n            'red',\n            37.78188901,\n            -122.4784396,\n            '2016-01-26T01:12:15.000Z',\n            78,\n            false\n          ],\n          [\n            '2.103.2',\n            37.79659543,\n            -122.4219072,\n            '2016-01-26T01:02:11.000Z',\n            -10.58,\n            'red',\n            37.78444762,\n            -122.470581,\n            '2016-01-26T01:18:12.000Z',\n            192,\n            false\n          ],\n          [\n            '2.117.1',\n            37.7980847,\n            -122.4050548,\n            '2016-01-26T01:06:35.000Z',\n            -5.23,\n            'red',\n            37.80089,\n            -122.4268962,\n            '2016-01-26T01:13:40.000Z',\n            242,\n            false\n          ],\n          [\n            '2.117.1',\n            37.79242329,\n            -122.3986584,\n            '2016-01-26T01:02:53.000Z',\n            -7.81,\n            'red',\n            37.7832852,\n            -122.4252576,\n            '2016-01-26T01:13:02.000Z',\n            175,\n            false\n          ],\n          [\n            '2.107.3',\n            37.79669585,\n            -122.4219416,\n            '2016-01-26T01:06:34.000Z',\n            -5.76,\n            'red',\n            37.79929617,\n            -122.4182267,\n            '2016-01-26T01:14:24.000Z',\n            223,\n            false\n          ],\n          [\n            '2.117.1',\n            37.6169644,\n            -122.384047,\n            '2016-01-26T01:09:00.000Z',\n            -17.77,\n            'red',\n            37.7838334,\n            -122.4329964,\n            '2016-01-26T01:30:14.000Z',\n            298,\n            false\n          ]\n        ],\n        fields: [\n          {\n            name: 'song_name',\n            type: 'string'\n          },\n          {\n            name: 'begintrip_lat',\n            type: 'real'\n          },\n          {\n            name: 'begintrip_lng',\n            type: 'real'\n          },\n          {\n            name: 'timestamp_local',\n            type: 'timestamp'\n          },\n          {\n            name: 'counting',\n            type: 'real'\n          },\n          {\n            name: 'detail',\n            type: 'string'\n          },\n          {\n            name: 'dropoff_lat',\n            type: 'real'\n          },\n          {\n            name: 'dropoff_lng',\n            type: 'real'\n          },\n          {\n            name: 'dropoff_timestamp_local',\n            type: 'timestamp'\n          },\n          {\n            name: 'int_range',\n            type: 'integer'\n          },\n          {\n            name: 'type_boolean',\n            type: 'boolean'\n          }\n        ]\n      }\n    },\n    {\n      version: 'v0',\n      data: {\n        id: 'v79816te8',\n        label: 'zip_geo.json',\n        color: [192, 108, 132],\n        allData: [\n          [\n            {\n              type: 'Feature',\n              properties: {\n                OBJECTID: 1,\n                ZIP_CODE: 94107,\n                ID: 94107,\n                index: 0\n              },\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-122.40115971858505, 37.78202426695214],\n                    [-122.40037436684331, 37.78264451554517],\n                    [-122.40001902006377, 37.782925153640136],\n                    [-122.39989147796784, 37.783025880124256],\n                    [-122.40115971858505, 37.78202426695214]\n                  ]\n                ]\n              }\n            },\n            1,\n            94107,\n            94107\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                OBJECTID: 3,\n                ZIP_CODE: 94129,\n                ID: 94129,\n                index: 1\n              },\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-122.47099072114979, 37.787534455433345],\n                    [-122.47229053418182, 37.78739591716227],\n                    [-122.47240190249055, 37.787384046501884],\n                    [-122.47254342327507, 37.787368961705944],\n                    [-122.47099072114979, 37.787534455433345]\n                  ]\n                ]\n              }\n            },\n            3,\n            94129,\n            94129\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                OBJECTID: 25,\n                ZIP_CODE: 94132,\n                ID: 94132,\n                index: 2\n              },\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-122.50828762723958, 37.733266432915535],\n                    [-122.50828361009823, 37.7333097667607],\n                    [-122.50825842264912, 37.73333766599817],\n                    [-122.50825465624375, 37.73339026610973],\n                    [-122.50826138138635, 37.73344715152078],\n                    [-122.50826063283236, 37.73354742941749],\n                    [-122.50826859537165, 37.73358609492605],\n                    [-122.50828762723958, 37.733266432915535]\n                  ]\n                ]\n              }\n            },\n            25,\n            94132,\n            94132\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                OBJECTID: 25,\n                ZIP_CODE: 94132,\n                ID: 94132,\n                index: 3\n              },\n              geometry: {\n                type: 'Polygon',\n                coordinates: [\n                  [\n                    [-122.50828762723958, 37.733266432915535],\n                    [-122.50828361009823, 37.7333097667607],\n                    [-122.50825842264912, 37.73333766599817],\n                    [-122.50825465624375, 37.73339026610973],\n                    [-122.50826138138635, 37.73344715152078],\n                    [-122.50826063283236, 37.73354742941749],\n                    [-122.50826859537165, 37.73358609492605],\n                    [-122.50828762723958, 37.733266432915535]\n                  ]\n                ]\n              }\n            },\n            25,\n            94132,\n            94132\n          ]\n        ],\n        fields: [\n          {\n            name: '_geojson',\n            type: 'geojson'\n          },\n          {\n            name: 'OBJECTID',\n            type: 'integer'\n          },\n          {\n            name: 'ZIP_CODE',\n            type: 'integer'\n          },\n          {\n            name: 'ID',\n            type: 'integer'\n          }\n        ]\n      }\n    }\n  ]\n};\n\nexport const expectedInfo0 = {\n  id: '9h10t7fyb',\n  label: 'small_dataset.csv',\n  color: [53, 92, 125]\n};\n\nexport const expectedFields0 = [\n  {\n    name: 'song_name',\n    type: 'string',\n    format: '',\n    analyzerType: 'STRING'\n  },\n  {\n    name: 'begintrip_lat',\n    type: 'real',\n    format: '',\n    analyzerType: 'FLOAT'\n  },\n  {\n    name: 'begintrip_lng',\n    type: 'real',\n    format: '',\n    analyzerType: 'FLOAT'\n  },\n  {\n    name: 'timestamp_local',\n    type: 'timestamp',\n    format: 'YYYY-M-DTHH:mm:ss.SSSS',\n    analyzerType: 'DATETIME'\n  },\n  {\n    name: 'counting',\n    type: 'real',\n    format: '',\n    analyzerType: 'FLOAT'\n  },\n  {\n    name: 'detail',\n    type: 'string',\n    format: '',\n    analyzerType: 'STRING'\n  },\n  {\n    name: 'dropoff_lat',\n    type: 'real',\n    format: '',\n    analyzerType: 'FLOAT'\n  },\n  {\n    name: 'dropoff_lng',\n    type: 'real',\n    format: '',\n    analyzerType: 'FLOAT'\n  },\n  {\n    name: 'dropoff_timestamp_local',\n    type: 'timestamp',\n    format: 'YYYY-M-DTHH:mm:ss.SSSS',\n    analyzerType: 'DATETIME'\n  },\n  {\n    name: 'int_range',\n    type: 'integer',\n    format: '',\n    analyzerType: 'INT'\n  },\n  {\n    name: 'type_boolean',\n    type: 'boolean',\n    format: '',\n    analyzerType: 'BOOLEAN'\n  }\n];\n\nexport const expectedInfo1 = {\n  id: 'v79816te8',\n  label: 'zip_geo.json',\n  color: [192, 108, 132]\n};\n\nexport const expectedFields1 = [\n  {\n    name: '_geojson',\n    type: 'geojson',\n    format: '',\n    analyzerType: 'GEOMETRY'\n  },\n  {\n    name: 'OBJECTID',\n    type: 'integer',\n    format: '',\n    analyzerType: 'INT'\n  },\n  {\n    name: 'ZIP_CODE',\n    type: 'integer',\n    format: '',\n    analyzerType: 'INT'\n  },\n  {\n    name: 'ID',\n    type: 'integer',\n    format: '',\n    analyzerType: 'INT'\n  }\n];\n\nexport const mergedFilters = [\n  {\n    dataId: ['9h10t7fyb'],\n    id: 'vxzfwyg2v',\n    enabled: true,\n    view: 'side',\n    isAnimating: false,\n    animationWindow: 'free',\n    name: ['song_name'],\n    type: 'multiSelect',\n    fieldIdx: [0],\n    domain: ['2.103.2', '2.107.3', '2.116.2', '2.117.1', '3.68.4'],\n    value: ['3.68.4', '2.117.1', '2.103.2', '2.116.2'],\n    fieldType: 'string',\n    plotType: {\n      type: 'histogram'\n    },\n    yAxis: null,\n    speed: 1,\n    fixedDomain: false,\n    gpu: false\n  },\n  {\n    dataId: ['9h10t7fyb'],\n    id: 'fo9tjm2unl',\n    enabled: true,\n    view: 'enlarged',\n    isAnimating: false,\n    animationWindow: 'free',\n    name: ['timestamp_local'],\n    type: 'timeRange',\n    fieldIdx: [3],\n    plotType: {\n      interval: '5-second',\n      defaultTimeFormat: 'L  LTS',\n      type: 'histogram',\n      aggregation: 'sum'\n    },\n    yAxis: null,\n    domain: [1453770124000, 1453770810000],\n    value: [1453770124000, 1453770415000],\n    step: 1000,\n    speed: 1,\n    mappedValue: [\n      1453770810000, 1453770279000, 1453770358000, 1453770124000, 1453770131000, 1453770395000,\n      1453770173000, 1453770394000, 1453770540000\n    ],\n    fieldType: 'timestamp',\n    fixedDomain: true,\n    gpu: true,\n    gpuChannel: [0],\n    defaultTimeFormat: 'L LTS',\n    timeBins: {\n      '9h10t7fyb': {\n        '5-second': histogramFromThreshold(\n          getBinThresholds('5-second', [1453770124000, 1453770810000]),\n          [\n            1453770810000, 1453770279000, 1453770358000, 1453770124000, 1453770131000,\n            1453770395000, 1453770173000, 1453770394000, 1453770540000\n          ]\n        )\n      }\n    }\n  },\n  {\n    dataId: ['9h10t7fyb'],\n    id: 'aesy96t5',\n    enabled: true,\n    name: ['type_boolean'],\n    type: 'select',\n    value: false,\n    view: 'side',\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldIdx: [10],\n    domain: [true, false],\n    fieldType: 'boolean',\n    plotType: {\n      type: 'histogram'\n    },\n    yAxis: null,\n    speed: 1,\n    fixedDomain: false,\n    gpu: false\n  },\n  {\n    dataId: ['9h10t7fyb'],\n    id: 's1bhgjt1',\n    enabled: true,\n    name: ['int_range'],\n    type: 'range',\n    value: [78, 309],\n    view: 'side',\n    plotType: {\n      type: 'histogram'\n    },\n    yAxis: null,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldIdx: [9],\n    domain: [78, 694],\n    step: 1,\n    speed: 1,\n    fieldType: 'integer',\n    typeOptions: ['range'],\n    fixedDomain: false,\n    gpu: true,\n    gpuChannel: [1],\n    bins: {\n      '9h10t7fyb': histogramFromDomain(\n        [78, 694],\n        [694, 335, 363, 78, 192, 242, 175, 223, 298],\n        BINS\n      )\n    }\n  },\n  {\n    dataId: ['v79816te8'],\n    id: '5nfmxjjzl',\n    enabled: true,\n    view: 'side',\n    isAnimating: false,\n    animationWindow: 'free',\n    name: ['ZIP_CODE'],\n    type: 'range',\n    fieldIdx: [2],\n    plotType: {\n      type: 'histogram'\n    },\n    yAxis: null,\n    domain: [94107, 94132],\n    value: [94115.3, 94132],\n    step: 0.01,\n    speed: 1,\n    fieldType: 'integer',\n    typeOptions: ['range'],\n    fixedDomain: false,\n    gpu: true,\n    gpuChannel: [0],\n    bins: {\n      v79816te8: histogramFromDomain([94107, 94132], [94107, 94129, 94132, 94132], BINS)\n    }\n  }\n];\n\nconst mergedLayer0 = new PointLayer({\n  id: '1eh',\n  dataId: '9h10t7fyb',\n  label: 'dropoff',\n  columnMode: 'points',\n  color: [76, 154, 78],\n  columns: {\n    lat: {\n      value: 'dropoff_lat',\n      fieldIdx: 6\n    },\n    lng: {\n      value: 'dropoff_lng',\n      fieldIdx: 7\n    },\n    altitude: {\n      value: null,\n      fieldIdx: -1,\n      optional: true\n    }\n  },\n  isVisible: false\n});\n\nmergedLayer0.config = {\n  ...mergedLayer0.config,\n  colorField: null,\n  colorScale: 'quantile',\n  colorDomain: [0, 1],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  strokeColorDomain: [0, 1],\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  hidden: false,\n  visConfig: {\n    radius: 270.4,\n    billboard: false,\n    opacity: 0.8,\n    outline: false,\n    filled: true,\n    thickness: 2,\n    fixedRadius: false,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    strokeColorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    strokeColor: [76, 154, 78],\n    radiusRange: [1, 100],\n    allowHover: true,\n    showNeighborOnHover: false,\n    showHighlightColor: true\n  }\n};\n\nmergedLayer0.meta = {\n  bounds: [-122.4784396, 37.78188901, -121.3686062, 38.5590766]\n};\n\nconst mergedLayer1 = new ArcLayer({\n  id: 'tud',\n  dataId: '9h10t7fyb',\n  label: 'trip arc',\n  color: [18, 147, 154],\n  columns: {\n    lat0: {\n      value: 'begintrip_lat',\n      fieldIdx: 1\n    },\n    lng0: {\n      value: 'begintrip_lng',\n      fieldIdx: 2\n    },\n    lat1: {\n      value: 'dropoff_lat',\n      fieldIdx: 6\n    },\n    lng1: {\n      value: 'dropoff_lng',\n      fieldIdx: 7\n    }\n  },\n  isVisible: true\n});\n\nmergedLayer1.config = {\n  ...mergedLayer1.config,\n  colorField: null,\n  colorScale: 'quantile',\n  colorDomain: [0, 1],\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI\n  },\n  hidden: false,\n  visConfig: {\n    opacity: 0.41,\n    thickness: 2,\n    targetColor: null,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    sizeRange: [0, 10]\n  }\n};\n\nconst mergedLayer2 = new PointLayer({\n  id: 'pwl'\n});\n\nmergedLayer2.config = {\n  dataId: '9h10t7fyb',\n  label: 'begintrip',\n  columnMode: 'points',\n  color: [218, 112, 191],\n  columns: {\n    lat: {\n      value: 'begintrip_lat',\n      fieldIdx: 1\n    },\n    lng: {\n      value: 'begintrip_lng',\n      fieldIdx: 2\n    },\n    altitude: {\n      value: null,\n      fieldIdx: -1,\n      optional: true\n    }\n  },\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  isVisible: true,\n  colorField: {\n    name: 'song_name',\n    id: 'song_name',\n    displayName: 'song_name',\n    type: 'string',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'STRING',\n    valueAccessor: values => values[0]\n  },\n  colorScale: 'ordinal',\n  colorDomain: ['2.103.2', '2.107.3', '2.116.2', '2.117.1', '3.68.4'],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  strokeColorDomain: [0, 1],\n  sizeField: {\n    name: 'int_range',\n    id: 'int_range',\n    displayName: 'int_range',\n    format: '',\n    type: 'integer',\n    fieldIdx: 9,\n    analyzerType: 'INT',\n    valueAccessor: values => values[9]\n  },\n  sizeDomain: [78, 694],\n  sizeScale: 'sqrt',\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  hidden: false,\n  visConfig: {\n    radius: 10,\n    billboard: false,\n    opacity: 0.8,\n    outline: false,\n    filled: true,\n    thickness: 2,\n    colorRange: {\n      name: 'Sunrise',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#355C7D', '#6C5B7B', '#C06C84', '#F67280', '#F8B195'],\n      reversed: false\n    },\n    strokeColorRange: {\n      name: 'Sunrise',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#355C7D', '#6C5B7B', '#C06C84', '#F67280', '#F8B195'],\n      reversed: false\n    },\n    strokeColor: [218, 112, 191],\n    fixedRadius: false,\n    radiusRange: [1, 854.16],\n    allowHover: true,\n    showNeighborOnHover: false,\n    showHighlightColor: true\n  },\n  animation: {enabled: false}\n};\n\nmergedLayer2.meta = {\n  bounds: [-122.4761712, 37.6169644, -121.7922809, 38.4163786]\n};\n\nconst mergedLayer3 = new HexagonLayer({\n  id: 'p5'\n});\n\nmergedLayer3.config = {\n  dataId: '9h10t7fyb',\n  label: 'begintrip_hex',\n  color: [241, 92, 23],\n  columns: {\n    lat: {\n      value: 'begintrip_lat',\n      fieldIdx: 1\n    },\n    lng: {\n      value: 'begintrip_lng',\n      fieldIdx: 2\n    }\n  },\n  hidden: false,\n  isVisible: true,\n  colorField: {\n    name: 'int_range',\n    displayName: 'int_range',\n    id: 'int_range',\n    type: 'integer',\n    format: '',\n    fieldIdx: 9,\n    analyzerType: 'INT',\n    valueAccessor: values => values[9]\n  },\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  colorScale: 'quantile',\n  colorDomain: [0, 1],\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    colorAggregation: 'maximum',\n    sizeAggregation: 'count',\n    enable3d: true,\n    opacity: 0.8,\n    worldUnitSize: 0.5,\n    resolution: 8,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    coverage: 1,\n    sizeRange: [0, 50],\n    percentile: [0, 100],\n    elevationPercentile: [0, 100],\n    elevationScale: 10,\n    enableElevationZoomFactor: true,\n    fixedHeight: false\n  },\n  animation: {enabled: false}\n};\n\nmergedLayer3.meta = {\n  bounds: [-122.4761712, 37.6169644, -121.7922809, 38.4163786]\n};\n\nconst mergedLayer4 = new GeojsonLayer({id: 'vta'});\n\nmergedLayer4.config = {\n  dataId: 'v79816te8',\n  label: 'zip',\n  color: [255, 153, 31, 255],\n  columns: {\n    geojson: {\n      value: '_geojson',\n      fieldIdx: 0,\n      optional: false\n    },\n    id: {value: null, fieldIdx: -1, optional: true},\n    lat: {value: null, fieldIdx: -1, optional: true},\n    lng: {value: null, fieldIdx: -1, optional: true},\n    altitude: {value: null, fieldIdx: -1, optional: true},\n    sortBy: {value: null, fieldIdx: -1, optional: true}\n  },\n  columnMode: 'geojson',\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  hidden: false,\n  isVisible: true,\n  colorField: {\n    name: 'ID',\n    displayName: 'ID',\n    id: 'ID',\n    type: 'integer',\n    format: '',\n    fieldIdx: 3,\n    analyzerType: 'INT',\n    valueAccessor: values => values[3]\n  },\n  colorScale: 'quantize',\n  colorDomain: [94107, 94132],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  strokeColorDomain: [0, 1],\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  heightField: null,\n  heightDomain: [0, 1],\n  heightScale: 'linear',\n\n  // add radius visual channel\n  radiusField: null,\n  radiusDomain: [0, 1],\n  radiusScale: 'linear',\n  visConfig: {\n    colorRange: {\n      name: 'Uber Viz Qualitative',\n      type: 'qualitative',\n      category: 'Uber',\n      colors: [\n        '#12939A',\n        '#DDB27C',\n        '#88572C',\n        '#FF991F',\n        '#F15C17',\n        '#223F9A',\n        '#DA70BF',\n        '#125C77',\n        '#4DC19C',\n        '#776E57',\n        '#17B8BE',\n        '#F6D18A',\n        '#B7885E',\n        '#FFCB99',\n        '#F89570'\n      ],\n      reversed: false\n    },\n    strokeColorRange: {\n      name: 'Uber Viz Qualitative',\n      type: 'qualitative',\n      category: 'Uber',\n      colors: [\n        '#12939A',\n        '#DDB27C',\n        '#88572C',\n        '#FF991F',\n        '#F15C17',\n        '#223F9A',\n        '#DA70BF',\n        '#125C77',\n        '#4DC19C',\n        '#776E57',\n        '#17B8BE',\n        '#F6D18A',\n        '#B7885E',\n        '#FFCB99',\n        '#F89570'\n      ],\n      reversed: false\n    },\n    strokeColor: [255, 153, 31, 255],\n    opacity: 0.8,\n    thickness: 2,\n    radius: 10,\n    sizeRange: [0, 10],\n    radiusRange: [0, 50],\n    heightRange: [0, 500],\n    elevationScale: 5,\n    fixedHeight: false,\n    stroked: false,\n    filled: true,\n    enable3d: false,\n    wireframe: false,\n    strokeOpacity: 0.8\n  },\n  animation: {enabled: false}\n};\n\nmergedLayer4.dataToFeature = {\n  0: {\n    type: 'Feature',\n    properties: {\n      OBJECTID: 1,\n      ZIP_CODE: 94107,\n      ID: 94107,\n      index: 0\n    },\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-122.40115971858505, 37.78202426695214],\n          [-122.40037436684331, 37.78264451554517],\n          [-122.40001902006377, 37.782925153640136],\n          [-122.39989147796784, 37.783025880124256],\n          [-122.40115971858505, 37.78202426695214]\n        ]\n      ]\n    }\n  },\n  1: {\n    type: 'Feature',\n    properties: {\n      OBJECTID: 3,\n      ZIP_CODE: 94129,\n      ID: 94129,\n      index: 1\n    },\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-122.47099072114979, 37.787534455433345],\n          [-122.47229053418182, 37.78739591716227],\n          [-122.47240190249055, 37.787384046501884],\n          [-122.47254342327507, 37.787368961705944],\n          [-122.47099072114979, 37.787534455433345]\n        ]\n      ]\n    }\n  },\n  2: {\n    type: 'Feature',\n    properties: {\n      OBJECTID: 25,\n      ZIP_CODE: 94132,\n      ID: 94132,\n      index: 2\n    },\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-122.50828762723958, 37.733266432915535],\n          [-122.50828361009823, 37.7333097667607],\n          [-122.50825842264912, 37.73333766599817],\n          [-122.50825465624375, 37.73339026610973],\n          [-122.50826138138635, 37.73344715152078],\n          [-122.50826063283236, 37.73354742941749],\n          [-122.50826859537165, 37.73358609492605],\n          [-122.50828762723958, 37.733266432915535]\n        ]\n      ]\n    }\n  }\n};\n\nmergedLayer4.meta = {\n  bounds: [-122.50828762723958, 37.733266432915535, -122.39989147796784, 37.787534455433345],\n  featureTypes: {polygon: true},\n  fp64: false,\n  fixedRadius: false\n};\n\nexport const mergedLayers = [mergedLayer0, mergedLayer1, mergedLayer2, mergedLayer3, mergedLayer4];\n\nexport const mergedInteractions = {\n  ...defaultInteractionConfig,\n  tooltip: {\n    ...defaultInteractionConfig.tooltip,\n    enabled: true,\n    config: {\n      compareMode: false,\n      compareType: 'absolute',\n      fieldsToShow: {\n        '9h10t7fyb': [\n          {\n            name: 'int_range',\n            format: null\n          },\n          {\n            name: 'detail',\n            format: null\n          },\n          {\n            name: 'type_boolean',\n            format: null\n          }\n        ],\n        v79816te8: [\n          {\n            name: 'ID',\n            format: null\n          },\n          {\n            name: 'ZIP_CODE',\n            format: null\n          }\n        ]\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-1.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {DEFAULT_TEXT_LABEL, DEFAULT_COLOR_UI, BINS} from '@kepler.gl/constants';\n\nconst {GeojsonLayer} = KeplerGlLayers;\nimport {defaultInteractionConfig} from '@kepler.gl/reducers';\n\nimport {histogramFromDomain} from '@kepler.gl/utils';\n\nexport const savedStateV1 = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'a5ybmwl2d',\n        label: 'geojson_as_string_small.csv',\n        color: [53, 92, 125],\n        allData: [\n          [\n            7014,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522],[-74.146471,40.836645],[-74.142598,40.833128],[-74.140177,40.832492],[-74.136732,40.836791],[-74.142706,40.840604],[-74.144667,40.84312],[-74.142936,40.844974],[-74.136054,40.84119],[-74.1356,40.841703],[-74.133665,40.840712],[-74.133028,40.841321],[-74.13274,40.839586],[-74.121853,40.834098],[-74.121087,40.831],[-74.124447,40.822169],[-74.130031,40.819962],[-74.149242,40.830537],[-74.148818,40.830916],[-74.150888,40.833143],[-74.151923,40.832074],[-74.158491,40.835947]]]}',\n            'POLYGON ((-74.158491 40.835947, -74.157914 40.83902, -74.148473 40.834522, -74.146471 40.836645, -74.142598 40.833128, -74.140177 40.832492, -74.136732 40.836791, -74.142706 40.840604, -74.144667 40.84312, -74.142936 40.844974, -74.136054 40.84119, -74.1356 40.841703, -74.133665 40.840712, -74.133028 40.841321, -74.13274 40.839586, -74.121853 40.834098, -74.121087 40.831, -74.124447 40.822169, -74.130031 40.819962, -74.149242 40.830537, -74.148818 40.830916, -74.150888 40.833143, -74.151923 40.832074, -74.158491 40.835947))',\n            1.420645391,\n            2.32754338,\n            'C_Medium_High',\n            7014,\n            0.719251337,\n            1496,\n            29.07437458,\n            540.4402429,\n            0.681149733,\n            0.988636364,\n            17.92821726,\n            13.56510275,\n            0.283\n          ],\n          [\n            7016,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.326141,40.656094],[-74.326593,40.657702],[-74.327693,40.662904],[-74.325199,40.667596],[-74.326093,40.668202],[-74.323615,40.670664],[-74.323228,40.672276],[-74.324391,40.67444],[-74.322371,40.677443],[-74.310392,40.678402],[-74.312993,40.674703],[-74.312093,40.674304],[-74.314552,40.673889],[-74.312827,40.673282],[-74.307888,40.674387],[-74.306792,40.672102],[-74.290976,40.673465],[-74.29185,40.671802],[-74.290689,40.670646],[-74.288727,40.670925],[-74.285991,40.667001],[-74.284275,40.658075],[-74.283511,40.658187],[-74.283369,40.657406],[-74.285786,40.655022],[-74.282693,40.653502],[-74.280691,40.645712],[-74.282183,40.644572],[-74.281687,40.643381],[-74.287325,40.639999],[-74.29577,40.638581],[-74.304907,40.633459],[-74.319678,40.641989],[-74.316485,40.645194],[-74.320663,40.647344],[-74.312298,40.654042],[-74.31687,40.656696]]]}',\n            'POLYGON ((-74.31687 40.656696, -74.319449 40.658154, -74.326141 40.656094, -74.326593 40.657702, -74.327693 40.662904, -74.325199 40.667596, -74.326093 40.668202, -74.323615 40.670664, -74.323228 40.672276, -74.324391 40.67444, -74.322371 40.677443, -74.310392 40.678402, -74.312993 40.674703, -74.312093 40.674304, -74.314552 40.673889, -74.312827 40.673282, -74.307888 40.674387, -74.306792 40.672102, -74.290976 40.673465, -74.29185 40.671802, -74.290689 40.670646, -74.288727 40.670925, -74.285991 40.667001, -74.284275 40.658075, -74.283511 40.658187, -74.283369 40.657406, -74.285786 40.655022, -74.282693 40.653502, -74.280691 40.645712, -74.282183 40.644572, -74.281687 40.643381, -74.287325 40.639999, -74.29577 40.638581, -74.304907 40.633459, -74.319678 40.641989, -74.316485 40.645194, -74.320663 40.647344, -74.312298 40.654042, -74.31687 40.656696))',\n            4.873351888,\n            2.125379919,\n            'C_Medium_High',\n            7016,\n            0.621409029,\n            5848,\n            27.60106201,\n            528.1274711,\n            0.678522572,\n            0.998290014,\n            19.05388261,\n            12.47290995,\n            0.256\n          ],\n          [\n            7023,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.387589,40.632238],[-74.401749,40.640393],[-74.386745,40.653316],[-74.374682,40.646553],[-74.372634,40.6468],[-74.371828,40.644885],[-74.374122,40.642352],[-74.37327,40.641829],[-74.375777,40.636295],[-74.37717,40.634842],[-74.382333,40.632417],[-74.384457,40.630453],[-74.387589,40.632238]]]}',\n            'POLYGON ((-74.387589 40.632238, -74.401749 40.640393, -74.386745 40.653316, -74.374682 40.646553, -74.372634 40.6468, -74.371828 40.644885, -74.374122 40.642352, -74.37327 40.641829, -74.375777 40.636295, -74.37717 40.634842, -74.382333 40.632417, -74.384457 40.630453, -74.387589 40.632238))',\n            1.352750157,\n            1.975548142,\n            'C_Medium_High',\n            7023,\n            0.580808081,\n            1782,\n            23.85239437,\n            457.401356,\n            0.673400673,\n            0.996071829,\n            18.41337089,\n            11.17832315,\n            0.241\n          ],\n          [\n            7029,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.165995,40.747969],[-74.164799,40.754718],[-74.155877,40.751722],[-74.154327,40.753966],[-74.145563,40.751975],[-74.144976,40.748161],[-74.14586,40.748031],[-74.140783,40.742694],[-74.139386,40.743314],[-74.137492,40.741398],[-74.142688,40.739301],[-74.145288,40.735701],[-74.147188,40.734401],[-74.155687,40.733501],[-74.15998,40.734485],[-74.162093,40.736109],[-74.163788,40.739201],[-74.165987,40.745199],[-74.165995,40.747969]]]}',\n            'POLYGON ((-74.165995 40.747969, -74.164799 40.754718, -74.155877 40.751722, -74.154327 40.753966, -74.145563 40.751975, -74.144976 40.748161, -74.14586 40.748031, -74.140783 40.742694, -74.139386 40.743314, -74.137492 40.741398, -74.142688 40.739301, -74.145288 40.735701, -74.147188 40.734401, -74.155687 40.733501, -74.15998 40.734485, -74.162093 40.736109, -74.163788 40.739201, -74.165987 40.745199, -74.165995 40.747969))',\n            1.446889939,\n            2.701389429,\n            'C_Medium_High',\n            7029,\n            0.814644137,\n            7812,\n            39.13775443,\n            545.4891226,\n            0.653481823,\n            0.974782386,\n            17.88895163,\n            13.72188322,\n            0.474\n          ],\n          [\n            7045,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.361356,40.939299],[-74.370498,40.926937],[-74.37404,40.927298],[-74.374216,40.921969],[-74.371645,40.92135],[-74.366428,40.918141],[-74.356676,40.915791],[-74.349697,40.916512],[-74.347883,40.91351],[-74.349593,40.91185],[-74.353183,40.910446],[-74.350384,40.908593],[-74.351179,40.907344],[-74.349633,40.906311],[-74.351883,40.90419],[-74.351747,40.902647],[-74.349573,40.903845],[-74.349271,40.902641],[-74.344758,40.902596],[-74.345589,40.900045],[-74.339928,40.899308],[-74.339554,40.895904],[-74.338613,40.895769],[-74.33944,40.893881],[-74.335743,40.893239],[-74.3389,40.892087],[-74.339694,40.889839],[-74.339646,40.888758],[-74.338118,40.887173],[-74.339575,40.883893],[-74.340271,40.879528],[-74.338075,40.879652],[-74.337614,40.878206],[-74.33507,40.877426],[-74.33531,40.876555],[-74.337696,40.87591],[-74.337808,40.875101],[-74.334259,40.873471],[-74.337288,40.872611],[-74.337418,40.87179],[-74.335937,40.870521],[-74.33806,40.870059],[-74.336714,40.869079],[-74.33765,40.868402],[-74.33768,40.866752],[-74.339806,40.866539],[-74.34018,40.868365],[-74.339545,40.874876],[-74.341626,40.877524],[-74.340485,40.882283],[-74.342874,40.885385],[-74.346198,40.887367],[-74.35009,40.887702],[-74.352792,40.886265],[-74.355507,40.888913],[-74.359916,40.886643],[-74.360096,40.887329],[-74.361792,40.887158],[-74.36087,40.883379],[-74.358905,40.883365],[-74.358888,40.880786],[-74.357662,40.880344],[-74.357789,40.879259],[-74.358676,40.879136],[-74.358091,40.878002],[-74.362636,40.877577],[-74.360456,40.872665],[-74.361843,40.87065],[-74.365778,40.871801],[-74.366753,40.874063],[-74.37127,40.876723],[-74.369921,40.878054],[-74.370331,40.879192],[-74.371574,40.879878],[-74.37187,40.878855],[-74.373453,40.878978],[-74.373051,40.880533],[-74.374017,40.882604],[-74.3772,40.886221],[-74.376296,40.888095],[-74.376308,40.890713],[-74.374745,40.893264],[-74.374127,40.898616],[-74.374902,40.901059],[-74.378188,40.901844],[-74.378509,40.900151],[-74.383293,40.899546],[-74.384277,40.902503],[-74.383622,40.902564],[-74.383706,40.903409],[-74.384609,40.903338],[-74.387159,40.909755],[-74.390966,40.909837],[-74.392542,40.913437],[-74.393499,40.91741],[-74.390968,40.919447],[-74.394272,40.926232],[-74.388795,40.930394],[-74.389726,40.930836],[-74.38883,40.931932],[-74.389795,40.931872],[-74.390036,40.932698],[-74.374793,40.940133],[-74.374929,40.941559],[-74.37655,40.942487],[-74.381516,40.942622],[-74.380974,40.946451],[-74.374397,40.954036],[-74.367575,40.955078],[-74.365156,40.954335],[-74.363667,40.955394],[-74.364141,40.954467],[-74.362181,40.951667],[-74.36267,40.944287],[-74.360703,40.940588],[-74.361356,40.939299]]]}',\n            'POLYGON ((-74.361356 40.939299, -74.370498 40.926937, -74.37404 40.927298, -74.374216 40.921969, -74.371645 40.92135, -74.366428 40.918141, -74.356676 40.915791, -74.349697 40.916512, -74.347883 40.91351, -74.349593 40.91185, -74.353183 40.910446, -74.350384 40.908593, -74.351179 40.907344, -74.349633 40.906311, -74.351883 40.90419, -74.351747 40.902647, -74.349573 40.903845, -74.349271 40.902641, -74.344758 40.902596, -74.345589 40.900045, -74.339928 40.899308, -74.339554 40.895904, -74.338613 40.895769, -74.33944 40.893881, -74.335743 40.893239, -74.3389 40.892087, -74.339694 40.889839, -74.339646 40.888758, -74.338118 40.887173, -74.339575 40.883893, -74.340271 40.879528, -74.338075 40.879652, -74.337614 40.878206, -74.33507 40.877426, -74.33531 40.876555, -74.337696 40.87591, -74.337808 40.875101, -74.334259 40.873471, -74.337288 40.872611, -74.337418 40.87179, -74.335937 40.870521, -74.33806 40.870059, -74.336714 40.869079, -74.33765 40.868402, -74.33768 40.866752, -74.339806 40.866539, -74.34018 40.868365, -74.339545 40.874876, -74.341626 40.877524, -74.340485 40.882283, -74.342874 40.885385, -74.346198 40.887367, -74.35009 40.887702, -74.352792 40.886265, -74.355507 40.888913, -74.359916 40.886643, -74.360096 40.887329, -74.361792 40.887158, -74.36087 40.883379, -74.358905 40.883365, -74.358888 40.880786, -74.357662 40.880344, -74.357789 40.879259, -74.358676 40.879136, -74.358091 40.878002, -74.362636 40.877577, -74.360456 40.872665, -74.361843 40.87065, -74.365778 40.871801, -74.366753 40.874063, -74.37127 40.876723, -74.369921 40.878054, -74.370331 40.879192, -74.371574 40.879878, -74.37187 40.878855, -74.373453 40.878978, -74.373051 40.880533, -74.374017 40.882604, -74.3772 40.886221, -74.376296 40.888095, -74.376308 40.890713, -74.374745 40.893264, -74.374127 40.898616, -74.374902 40.901059, -74.378188 40.901844, -74.378509 40.900151, -74.383293 40.899546, -74.384277 40.902503, -74.383622 40.902564, -74.383706 40.903409, -74.384609 40.903338, -74.387159 40.909755, -74.390966 40.909837, -74.392542 40.913437, -74.393499 40.91741, -74.390968 40.919447, -74.394272 40.926232, -74.388795 40.930394, -74.389726 40.930836, -74.38883 40.931932, -74.389795 40.931872, -74.390036 40.932698, -74.374793 40.940133, -74.374929 40.941559, -74.37655 40.942487, -74.381516 40.942622, -74.380974 40.946451, -74.374397 40.954036, -74.367575 40.955078, -74.365156 40.954335, -74.363667 40.955394, -74.364141 40.954467, -74.362181 40.951667, -74.36267 40.944287, -74.360703 40.940588, -74.361356 40.939299))',\n            7.403173506,\n            2.253640666,\n            'B_Low_Suburban',\n            7045,\n            0.477082503,\n            2509,\n            28.18247299,\n            519.2220835,\n            0.672777999,\n            0.996014348,\n            18.72091503,\n            12.48069003,\n            0.258\n          ],\n          [\n            7073,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.087219,40.799287],[-74.105261,40.838083],[-74.113924,40.842218],[-74.121404,40.847184],[-74.119291,40.851674],[-74.119462,40.85335],[-74.11538,40.850927],[-74.109,40.845092],[-74.078747,40.827548],[-74.070783,40.822082],[-74.066115,40.817041],[-74.061984,40.810171],[-74.060685,40.8057],[-74.065135,40.80302],[-74.068585,40.7986],[-74.071399,40.797241],[-74.077,40.796379],[-74.077787,40.792796],[-74.081794,40.787994],[-74.087219,40.799287]]]}',\n            'POLYGON ((-74.087219 40.799287, -74.105261 40.838083, -74.113924 40.842218, -74.121404 40.847184, -74.119291 40.851674, -74.119462 40.85335, -74.11538 40.850927, -74.109 40.845092, -74.078747 40.827548, -74.070783 40.822082, -74.066115 40.817041, -74.061984 40.810171, -74.060685 40.8057, -74.065135 40.80302, -74.068585 40.7986, -74.071399 40.797241, -74.077 40.796379, -74.077787 40.792796, -74.081794 40.787994, -74.087219 40.799287))',\n            4.076259122,\n            2.483658001,\n            'B_Low_Suburban',\n            7073,\n            0.745130406,\n            3029,\n            35.66977674,\n            638.4631914,\n            0.671838891,\n            0.990756025,\n            18.39194713,\n            15.62142573,\n            0.333\n          ],\n          [\n            7081,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.347067,40.691722],[-74.36039,40.699821],[-74.352171,40.70148],[-74.342221,40.707846],[-74.333594,40.716901],[-74.330619,40.716055],[-74.332088,40.720176],[-74.325596,40.717075],[-74.321408,40.719072],[-74.315991,40.719199],[-74.311691,40.715601],[-74.309791,40.715501],[-74.308793,40.714508],[-74.302092,40.713301],[-74.299496,40.713889],[-74.29869,40.71479],[-74.296151,40.712434],[-74.297212,40.710298],[-74.301677,40.707498],[-74.301078,40.704678],[-74.306692,40.703802],[-74.309119,40.702625],[-74.310906,40.700392],[-74.310666,40.699622],[-74.307393,40.698801],[-74.307893,40.695999],[-74.30664,40.694304],[-74.308655,40.691171],[-74.312226,40.689892],[-74.310772,40.683794],[-74.307292,40.682506],[-74.308989,40.680407],[-74.307753,40.680292],[-74.310392,40.678402],[-74.322575,40.677433],[-74.347067,40.691722]]]}',\n            'POLYGON ((-74.347067 40.691722, -74.36039 40.699821, -74.352171 40.70148, -74.342221 40.707846, -74.333594 40.716901, -74.330619 40.716055, -74.332088 40.720176, -74.325596 40.717075, -74.321408 40.719072, -74.315991 40.719199, -74.311691 40.715601, -74.309791 40.715501, -74.308793 40.714508, -74.302092 40.713301, -74.299496 40.713889, -74.29869 40.71479, -74.296151 40.712434, -74.297212 40.710298, -74.301677 40.707498, -74.301078 40.704678, -74.306692 40.703802, -74.309119 40.702625, -74.310906 40.700392, -74.310666 40.699622, -74.307393 40.698801, -74.307893 40.695999, -74.30664 40.694304, -74.308655 40.691171, -74.312226 40.689892, -74.310772 40.683794, -74.307292 40.682506, -74.308989 40.680407, -74.307753 40.680292, -74.310392 40.678402, -74.322575 40.677433, -74.347067 40.691722))',\n            5.142008449,\n            2.352650655,\n            'C_Medium_High',\n            7081,\n            0.65038271,\n            4442,\n            29.37274368,\n            514.8178812,\n            0.693156236,\n            0.997748762,\n            18.36100331,\n            12.61739583,\n            0.273\n          ],\n          [\n            7102,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.184345,40.727604],[-74.183145,40.730677],[-74.181958,40.730397],[-74.181105,40.732722],[-74.181977,40.732938],[-74.181504,40.734135],[-74.18328,40.734558],[-74.182741,40.735778],[-74.179407,40.737014],[-74.182582,40.73798],[-74.181787,40.739633],[-74.179055,40.737705],[-74.178029,40.739555],[-74.179295,40.739935],[-74.17785,40.742469],[-74.18066,40.74342],[-74.179821,40.744703],[-74.177068,40.743847],[-74.175752,40.745597],[-74.17646,40.745849],[-74.175989,40.747417],[-74.173784,40.746929],[-74.173566,40.747955],[-74.170978,40.747424],[-74.167568,40.748054],[-74.167484,40.747446],[-74.165976,40.748091],[-74.165987,40.745199],[-74.163788,40.739201],[-74.161669,40.735781],[-74.166099,40.732923],[-74.175517,40.72346],[-74.176772,40.723054],[-74.17736,40.723261],[-74.176965,40.723961],[-74.180517,40.724365],[-74.179633,40.725829],[-74.180319,40.726113],[-74.184236,40.724266],[-74.183892,40.72506],[-74.185205,40.725405],[-74.184345,40.727604]]]}',\n            'POLYGON ((-74.184345 40.727604, -74.183145 40.730677, -74.181958 40.730397, -74.181105 40.732722, -74.181977 40.732938, -74.181504 40.734135, -74.18328 40.734558, -74.182741 40.735778, -74.179407 40.737014, -74.182582 40.73798, -74.181787 40.739633, -74.179055 40.737705, -74.178029 40.739555, -74.179295 40.739935, -74.17785 40.742469, -74.18066 40.74342, -74.179821 40.744703, -74.177068 40.743847, -74.175752 40.745597, -74.17646 40.745849, -74.175989 40.747417, -74.173784 40.746929, -74.173566 40.747955, -74.170978 40.747424, -74.167568 40.748054, -74.167484 40.747446, -74.165976 40.748091, -74.165987 40.745199, -74.163788 40.739201, -74.161669 40.735781, -74.166099 40.732923, -74.175517 40.72346, -74.176772 40.723054, -74.17736 40.723261, -74.176965 40.723961, -74.180517 40.724365, -74.179633 40.725829, -74.180319 40.726113, -74.184236 40.724266, -74.183892 40.72506, -74.185205 40.725405, -74.184345 40.727604))',\n            1.203818912,\n            3.301076417,\n            'C_Medium_High',\n            7102,\n            0.832516129,\n            3875,\n            55.20608285,\n            769.5874437,\n            0.645935484,\n            0.984258065,\n            18.16678028,\n            19.06305599,\n            0.3\n          ],\n          [\n            7110,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.1404,40.805639],[-74.146391,40.806291],[-74.149012,40.80766],[-74.149521,40.806858],[-74.148617,40.806521],[-74.175644,40.809531],[-74.183836,40.812225],[-74.179511,40.816664],[-74.178999,40.818771],[-74.176443,40.821854],[-74.174992,40.821802],[-74.162628,40.837869],[-74.151923,40.832074],[-74.150888,40.833143],[-74.148818,40.830916],[-74.149242,40.830537],[-74.130031,40.819962],[-74.136487,40.8182],[-74.137287,40.8173],[-74.138787,40.8074],[-74.1404,40.805639]]]}',\n            'POLYGON ((-74.1404 40.805639, -74.146391 40.806291, -74.149012 40.80766, -74.149521 40.806858, -74.148617 40.806521, -74.175644 40.809531, -74.183836 40.812225, -74.179511 40.816664, -74.178999 40.818771, -74.176443 40.821854, -74.174992 40.821802, -74.162628 40.837869, -74.151923 40.832074, -74.150888 40.833143, -74.148818 40.830916, -74.149242 40.830537, -74.130031 40.819962, -74.136487 40.8182, -74.137287 40.8173, -74.138787 40.8074, -74.1404 40.805639))',\n            3.418972265,\n            2.350813772,\n            'C_Medium_High',\n            7110,\n            0.740131579,\n            7600,\n            28.24510978,\n            494.0816839,\n            0.686578947,\n            0.988815789,\n            18.33537813,\n            12.12610704,\n            0.268\n          ],\n          [\n            7416,\n            '{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-74.566993,41.087294],[-74.564908,41.089339],[-74.56559,41.090507],[-74.564446,41.091931],[-74.562675,41.093801],[-74.560495,41.094523],[-74.559465,41.093899],[-74.558011,41.094312],[-74.556055,41.091781],[-74.554015,41.092675],[-74.552353,41.091647],[-74.552998,41.09013],[-74.556709,41.087409],[-74.558078,41.085348],[-74.56136,41.08293],[-74.563205,41.082847],[-74.565111,41.081667],[-74.567341,41.082366],[-74.566524,41.085226],[-74.566993,41.087294]]],[[[-74.593264,41.088526],[-74.601504,41.094096],[-74.602269,41.092905],[-74.606875,41.096689],[-74.606957,41.097715],[-74.638551,41.118611],[-74.634186,41.132604],[-74.633166,41.133324],[-74.629712,41.131934],[-74.625246,41.136424],[-74.620677,41.139411],[-74.613785,41.139652],[-74.6101,41.137965],[-74.610517,41.136736],[-74.606433,41.135423],[-74.60466,41.136534],[-74.598719,41.137644],[-74.595925,41.139251],[-74.593358,41.139231],[-74.579959,41.144635],[-74.576893,41.136939],[-74.573312,41.136907],[-74.571246,41.137717],[-74.569941,41.136878],[-74.575411,41.101456],[-74.577128,41.101248],[-74.574449,41.101263],[-74.567456,41.103641],[-74.562238,41.103369],[-74.55789,41.106705],[-74.552862,41.10746],[-74.557344,41.105958],[-74.56193,41.102739],[-74.569487,41.100323],[-74.575935,41.095058],[-74.578362,41.09142],[-74.583558,41.087448],[-74.585763,41.083491],[-74.593264,41.088526]]]]}',\n            'MULTIPOLYGON (((-74.566993 41.087294, -74.564908 41.089339, -74.56559 41.090507, -74.564446 41.091931, -74.562675 41.093801, -74.560495 41.094523, -74.559465 41.093899, -74.558011 41.094312, -74.556055 41.091781, -74.554015 41.092675, -74.552353 41.091647, -74.552998 41.09013, -74.556709 41.087409, -74.558078 41.085348, -74.56136 41.08293, -74.563205 41.082847, -74.565111 41.081667, -74.567341 41.082366, -74.566524 41.085226, -74.566993 41.087294)), ((-74.593264 41.088526, -74.601504 41.094096, -74.602269 41.092905, -74.606875 41.096689, -74.606957 41.097715, -74.638551 41.118611, -74.634186 41.132604, -74.633166 41.133324, -74.629712 41.131934, -74.625246 41.136424, -74.620677 41.139411, -74.613785 41.139652, -74.6101 41.137965, -74.610517 41.136736, -74.606433 41.135423, -74.60466 41.136534, -74.598719 41.137644, -74.595925 41.139251, -74.593358 41.139231, -74.579959 41.144635, -74.576893 41.136939, -74.573312 41.136907, -74.571246 41.137717, -74.569941 41.136878, -74.575411 41.101456, -74.577128 41.101248, -74.574449 41.101263, -74.567456 41.103641, -74.562238 41.103369, -74.55789 41.106705, -74.552862 41.10746, -74.557344 41.105958, -74.56193 41.102739, -74.569487 41.100323, -74.575935 41.095058, -74.578362 41.09142, -74.583558 41.087448, -74.585763 41.083491, -74.593264 41.088526)))',\n            9.599149618,\n            2.546774404,\n            'A_Low_Rural',\n            7416,\n            0.362944162,\n            394,\n            13.81679389,\n            234.9184295,\n            0.748730964,\n            0.997461929,\n            14.28320611,\n            7.401229981,\n            0.066\n          ],\n          [\n            7481,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158531,41.025141],[-74.155604,41.02585],[-74.155079,41.0217],[-74.147648,41.022058],[-74.149441,41.013336],[-74.151076,41.010084],[-74.142115,41.008711],[-74.141446,41.007704],[-74.137536,41.007495],[-74.141401,41.005903],[-74.145988,41.001919],[-74.155782,41.001404],[-74.157154,40.998933],[-74.153255,40.996719],[-74.151282,40.993204],[-74.153874,40.98999],[-74.149887,40.987538],[-74.142812,40.986407],[-74.141437,40.984207],[-74.14121,40.982761],[-74.142619,40.981621],[-74.149088,40.971137],[-74.17021,40.983865],[-74.170988,40.976997],[-74.191835,40.978688],[-74.189987,40.992282],[-74.189546,41.003824],[-74.18806,41.00691],[-74.187244,41.01685],[-74.16758,41.023781],[-74.163392,41.022212],[-74.158531,41.025141]]]}',\n            'POLYGON ((-74.158531 41.025141, -74.155604 41.02585, -74.155079 41.0217, -74.147648 41.022058, -74.149441 41.013336, -74.151076 41.010084, -74.142115 41.008711, -74.141446 41.007704, -74.137536 41.007495, -74.141401 41.005903, -74.145988 41.001919, -74.155782 41.001404, -74.157154 40.998933, -74.153255 40.996719, -74.151282 40.993204, -74.153874 40.98999, -74.149887 40.987538, -74.142812 40.986407, -74.141437 40.984207, -74.14121 40.982761, -74.142619 40.981621, -74.149088 40.971137, -74.17021 40.983865, -74.170988 40.976997, -74.191835 40.978688, -74.189987 40.992282, -74.189546 41.003824, -74.18806 41.00691, -74.187244 41.01685, -74.16758 41.023781, -74.163392 41.022212, -74.158531 41.025141))',\n            6.59865985,\n            2.21913015,\n            'B_Low_Suburban',\n            7481,\n            0.507943713,\n            4406,\n            37.98110631,\n            779.7471722,\n            0.639809351,\n            0.997049478,\n            20.78244176,\n            16.88378255,\n            0.262\n          ],\n          [\n            7495,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.164572,41.104182],[-74.166137,41.104468],[-74.163755,41.105993],[-74.16128,41.105606],[-74.162067,41.104822],[-74.161454,41.103924],[-74.162067,41.102889],[-74.163932,41.102848],[-74.164572,41.104182]]]}',\n            'POLYGON ((-74.164572 41.104182, -74.166137 41.104468, -74.163755 41.105993, -74.16128 41.105606, -74.162067 41.104822, -74.161454 41.103924, -74.162067 41.102889, -74.163932 41.102848, -74.164572 41.104182))',\n            0.03213684,\n            3.361538462,\n            'A_Low_Rural',\n            7495,\n            0.355555556,\n            45,\n            30.4,\n            709.541378,\n            0.8,\n            1,\n            18.01481481,\n            17.72394684,\n            0\n          ],\n          [\n            7506,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.151143,40.97231],[-74.141048,40.966348],[-74.148059,40.94159],[-74.149587,40.941398],[-74.153938,40.937972],[-74.16151,40.937498],[-74.167127,40.934519],[-74.171588,40.941998],[-74.169018,40.947413],[-74.167486,40.95733],[-74.16921,40.969444],[-74.170814,40.970178],[-74.17021,40.983865],[-74.151143,40.97231]]]}',\n            'POLYGON ((-74.151143 40.97231, -74.141048 40.966348, -74.148059 40.94159, -74.149587 40.941398, -74.153938 40.937972, -74.16151 40.937498, -74.167127 40.934519, -74.171588 40.941998, -74.169018 40.947413, -74.167486 40.95733, -74.16921 40.969444, -74.170814 40.970178, -74.17021 40.983865, -74.151143 40.97231))',\n            3.374654059,\n            2.479267611,\n            'C_Medium_High',\n            7506,\n            0.677288685,\n            3703,\n            26.69672353,\n            436.3732942,\n            0.703483662,\n            0.997299487,\n            16.88975539,\n            11.62645508,\n            0.196\n          ],\n          [\n            7605,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99863,40.866791],[-73.992942,40.873032],[-73.994052,40.874607],[-73.993799,40.876601],[-73.983367,40.875127],[-73.978515,40.870534],[-73.97569,40.868903],[-73.977687,40.8605],[-73.979382,40.860799],[-73.980514,40.857975],[-73.987985,40.850884],[-73.997417,40.856176],[-73.99829,40.855105],[-74.006059,40.85697],[-74.002723,40.864981],[-73.99863,40.866791]]]}',\n            'POLYGON ((-73.99863 40.866791, -73.992942 40.873032, -73.994052 40.874607, -73.993799 40.876601, -73.983367 40.875127, -73.978515 40.870534, -73.97569 40.868903, -73.977687 40.8605, -73.979382 40.860799, -73.980514 40.857975, -73.987985 40.850884, -73.997417 40.856176, -73.99829 40.855105, -74.006059 40.85697, -74.002723 40.864981, -73.99863 40.866791))',\n            1.642585048,\n            2.453964783,\n            'C_Medium_High',\n            7605,\n            0.656508653,\n            2658,\n            33.78153495,\n            611.0165724,\n            0.656884876,\n            0.990218209,\n            18.13150963,\n            15.16462023,\n            0.293\n          ],\n          [\n            7631,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99002,40.887038],[-73.994572,40.888663],[-73.98955,40.900569],[-73.989916,40.906341],[-73.989286,40.911479],[-73.988717,40.911674],[-73.972202,40.912892],[-73.971972,40.913617],[-73.949229,40.900817],[-73.959169,40.885014],[-73.95583,40.883117],[-73.957173,40.881425],[-73.957839,40.882492],[-73.958321,40.882085],[-73.957902,40.880512],[-73.969825,40.865568],[-73.978515,40.870534],[-73.983367,40.875127],[-73.993933,40.876539],[-73.992783,40.879099],[-73.98739,40.886003],[-73.99002,40.887038]]]}',\n            'POLYGON ((-73.99002 40.887038, -73.994572 40.888663, -73.98955 40.900569, -73.989916 40.906341, -73.989286 40.911479, -73.988717 40.911674, -73.972202 40.912892, -73.971972 40.913617, -73.949229 40.900817, -73.959169 40.885014, -73.95583 40.883117, -73.957173 40.881425, -73.957839 40.882492, -73.958321 40.882085, -73.957902 40.880512, -73.969825 40.865568, -73.978515 40.870534, -73.983367 40.875127, -73.993933 40.876539, -73.992783 40.879099, -73.98739 40.886003, -73.99002 40.887038))',\n            4.926323412,\n            2.742937844,\n            'C_Medium_High',\n            7631,\n            0.695602572,\n            9642,\n            39.65661571,\n            777.8438479,\n            0.669155777,\n            0.983717071,\n            18.88216131,\n            18.53758825,\n            0.354\n          ],\n          [\n            7643,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.04531,40.854103],[-74.044779,40.853794],[-74.044816,40.85746],[-74.043745,40.857547],[-74.043757,40.85919],[-74.032018,40.858647],[-74.028884,40.855216],[-74.028418,40.852282],[-74.029969,40.847658],[-74.029185,40.843427],[-74.030484,40.8389],[-74.029084,40.8344],[-74.030084,40.832],[-74.035384,40.8288],[-74.036918,40.82855],[-74.040269,40.830288],[-74.03909,40.830425],[-74.039014,40.832249],[-74.040669,40.832316],[-74.039777,40.834102],[-74.04154,40.833922],[-74.041217,40.834652],[-74.042195,40.834792],[-74.042262,40.836937],[-74.043267,40.838359],[-74.042435,40.840983],[-74.043045,40.843654],[-74.045484,40.843596],[-74.045798,40.846145],[-74.051577,40.847894],[-74.052563,40.848853],[-74.052518,40.850817],[-74.053981,40.853863],[-74.051677,40.857692],[-74.04531,40.854103]]]}',\n            'POLYGON ((-74.04531 40.854103, -74.044779 40.853794, -74.044816 40.85746, -74.043745 40.857547, -74.043757 40.85919, -74.032018 40.858647, -74.028884 40.855216, -74.028418 40.852282, -74.029969 40.847658, -74.029185 40.843427, -74.030484 40.8389, -74.029084 40.8344, -74.030084 40.832, -74.035384 40.8288, -74.036918 40.82855, -74.040269 40.830288, -74.03909 40.830425, -74.039014 40.832249, -74.040669 40.832316, -74.039777 40.834102, -74.04154 40.833922, -74.041217 40.834652, -74.042195 40.834792, -74.042262 40.836937, -74.043267 40.838359, -74.042435 40.840983, -74.043045 40.843654, -74.045484 40.843596, -74.045798 40.846145, -74.051577 40.847894, -74.052563 40.848853, -74.052518 40.850817, -74.053981 40.853863, -74.051677 40.857692, -74.04531 40.854103))',\n            1.698675619,\n            2.552779817,\n            'C_Medium_High',\n            7643,\n            0.787673552,\n            2953,\n            28.46145797,\n            457.6531695,\n            0.686420589,\n            0.970877074,\n            16.71062667,\n            12.32412945,\n            0.275\n          ],\n          [\n            7645,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.033649,41.039572],[-74.03703,41.043696],[-74.051378,41.049278],[-74.063774,41.044631],[-74.069675,41.041541],[-74.071014,41.046613],[-74.072234,41.046686],[-74.072452,41.048935],[-74.079369,41.048492],[-74.078814,41.055144],[-74.07699,41.061365],[-74.071915,41.072883],[-74.012583,41.046396],[-74.013197,41.041442],[-74.012452,41.038698],[-74.028831,41.036591],[-74.030807,41.037199],[-74.029374,41.039144],[-74.029546,41.040261],[-74.031631,41.037894],[-74.033649,41.039572]]]}',\n            'POLYGON ((-74.033649 41.039572, -74.03703 41.043696, -74.051378 41.049278, -74.063774 41.044631, -74.069675 41.041541, -74.071014 41.046613, -74.072234 41.046686, -74.072452 41.048935, -74.079369 41.048492, -74.078814 41.055144, -74.07699 41.061365, -74.071915 41.072883, -74.012583 41.046396, -74.013197 41.041442, -74.012452 41.038698, -74.028831 41.036591, -74.030807 41.037199, -74.029374 41.039144, -74.029546 41.040261, -74.031631 41.037894, -74.033649 41.039572))',\n            3.99137919,\n            2.383176018,\n            'B_Low_Suburban',\n            7645,\n            0.464448496,\n            2194,\n            31.67185355,\n            625.3501419,\n            0.667730173,\n            0.995897903,\n            18.75967963,\n            15.00065936,\n            0.277\n          ],\n          [\n            7405,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.424256,41.026137],[-74.423728,41.027596],[-74.421458,41.02854],[-74.417803,41.028693],[-74.414744,41.027759],[-74.41085,41.026199],[-74.40977,41.024847],[-74.40592,41.024094],[-74.403388,41.021833],[-74.402254,41.019233],[-74.399391,41.017289],[-74.390997,41.015688],[-74.389678,41.014254],[-74.389303,41.012431],[-74.388223,41.012046],[-74.386295,41.011688],[-74.382247,41.013959],[-74.379565,41.014282],[-74.371373,41.011399],[-74.367459,41.011994],[-74.364124,41.011293],[-74.36235,41.01383],[-74.355441,41.013943],[-74.351481,41.01099],[-74.351029,41.007174],[-74.352168,41.005987],[-74.350926,41.004335],[-74.34612,41.0046],[-74.342264,41.006904],[-74.340618,41.005845],[-74.336429,41.007529],[-74.334805,41.006464],[-74.334456,41.003592],[-74.329578,41.002512],[-74.328411,41.001419],[-74.330477,40.997535],[-74.329601,40.995845],[-74.329855,40.991939],[-74.332185,40.990168],[-74.331401,40.988262],[-74.330459,40.966053],[-74.336465,40.960102],[-74.340902,40.953453],[-74.339763,40.95213],[-74.336043,40.951261],[-74.335478,40.949418],[-74.341094,40.946298],[-74.364296,40.957929],[-74.397898,40.959829],[-74.410065,40.958717],[-74.428155,40.958911],[-74.428555,40.958226],[-74.428574,40.959286],[-74.431175,40.959368],[-74.430749,40.984608],[-74.432097,40.984008],[-74.432025,40.992541],[-74.430602,40.993339],[-74.430178,41.018128],[-74.429211,41.017679],[-74.425375,41.019271],[-74.426013,41.020166],[-74.423504,41.025734],[-74.424256,41.026137]]]}',\n            'POLYGON ((-74.424256 41.026137, -74.423728 41.027596, -74.421458 41.02854, -74.417803 41.028693, -74.414744 41.027759, -74.41085 41.026199, -74.40977 41.024847, -74.40592 41.024094, -74.403388 41.021833, -74.402254 41.019233, -74.399391 41.017289, -74.390997 41.015688, -74.389678 41.014254, -74.389303 41.012431, -74.388223 41.012046, -74.386295 41.011688, -74.382247 41.013959, -74.379565 41.014282, -74.371373 41.011399, -74.367459 41.011994, -74.364124 41.011293, -74.36235 41.01383, -74.355441 41.013943, -74.351481 41.01099, -74.351029 41.007174, -74.352168 41.005987, -74.350926 41.004335, -74.34612 41.0046, -74.342264 41.006904, -74.340618 41.005845, -74.336429 41.007529, -74.334805 41.006464, -74.334456 41.003592, -74.329578 41.002512, -74.328411 41.001419, -74.330477 40.997535, -74.329601 40.995845, -74.329855 40.991939, -74.332185 40.990168, -74.331401 40.988262, -74.330459 40.966053, -74.336465 40.960102, -74.340902 40.953453, -74.339763 40.95213, -74.336043 40.951261, -74.335478 40.949418, -74.341094 40.946298, -74.364296 40.957929, -74.397898 40.959829, -74.410065 40.958717, -74.428155 40.958911, -74.428555 40.958226, -74.428574 40.959286, -74.431175 40.959368, -74.430749 40.984608, -74.432097 40.984008, -74.432025 40.992541, -74.430602 40.993339, -74.430178 41.018128, -74.429211 41.017679, -74.425375 41.019271, -74.426013 41.020166, -74.423504 41.025734, -74.424256 41.026137))',\n            20.89261574,\n            2.276330461,\n            'A_Low_Rural',\n            7405,\n            0.480898221,\n            3429,\n            23.42811222,\n            430.9574125,\n            0.721201516,\n            0.997958589,\n            18.08334307,\n            10.72428006,\n            0.193\n          ],\n          [\n            7712,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.105049,40.269701],[-74.103488,40.272418],[-74.106018,40.274773],[-74.101607,40.280574],[-74.100748,40.279887],[-74.100161,40.280996],[-74.098821,40.28123],[-74.082891,40.274113],[-74.082201,40.270348],[-74.072487,40.275582],[-74.071041,40.274346],[-74.070161,40.270832],[-74.069033,40.270664],[-74.067579,40.27089],[-74.065924,40.272621],[-74.061569,40.273615],[-74.051627,40.272795],[-74.049539,40.271935],[-74.043385,40.273422],[-74.039322,40.25183],[-74.019689,40.253485],[-74.019866,40.251435],[-74.01764,40.249613],[-74.018373,40.247723],[-74.020904,40.246901],[-74.022762,40.240874],[-74.015502,40.23916],[-74.015218,40.237693],[-74.01292,40.236238],[-74.008684,40.235364],[-74.009389,40.232973],[-74.007068,40.232283],[-74.006199,40.231174],[-73.994705,40.230067],[-73.994681,40.224512],[-73.998481,40.216812],[-74.002556,40.21709],[-74.018647,40.213377],[-74.024998,40.213125],[-74.025508,40.219099],[-74.02618,40.219151],[-74.025518,40.219641],[-74.025516,40.223269],[-74.077409,40.234395],[-74.076503,40.240881],[-74.078391,40.240398],[-74.079541,40.242058],[-74.07912,40.243797],[-74.075526,40.245243],[-74.073864,40.257239],[-74.075693,40.259189],[-74.081411,40.258528],[-74.082119,40.251457],[-74.08462,40.252719],[-74.08778,40.250225],[-74.095925,40.249682],[-74.096515,40.245582],[-74.097686,40.245471],[-74.110838,40.252683],[-74.10372,40.255887],[-74.106884,40.259944],[-74.108802,40.260613],[-74.108582,40.264424],[-74.106872,40.265512],[-74.105049,40.269701]]]}',\n            'POLYGON ((-74.105049 40.269701, -74.103488 40.272418, -74.106018 40.274773, -74.101607 40.280574, -74.100748 40.279887, -74.100161 40.280996, -74.098821 40.28123, -74.082891 40.274113, -74.082201 40.270348, -74.072487 40.275582, -74.071041 40.274346, -74.070161 40.270832, -74.069033 40.270664, -74.067579 40.27089, -74.065924 40.272621, -74.061569 40.273615, -74.051627 40.272795, -74.049539 40.271935, -74.043385 40.273422, -74.039322 40.25183, -74.019689 40.253485, -74.019866 40.251435, -74.01764 40.249613, -74.018373 40.247723, -74.020904 40.246901, -74.022762 40.240874, -74.015502 40.23916, -74.015218 40.237693, -74.01292 40.236238, -74.008684 40.235364, -74.009389 40.232973, -74.007068 40.232283, -74.006199 40.231174, -73.994705 40.230067, -73.994681 40.224512, -73.998481 40.216812, -74.002556 40.21709, -74.018647 40.213377, -74.024998 40.213125, -74.025508 40.219099, -74.02618 40.219151, -74.025518 40.219641, -74.025516 40.223269, -74.077409 40.234395, -74.076503 40.240881, -74.078391 40.240398, -74.079541 40.242058, -74.07912 40.243797, -74.075526 40.245243, -74.073864 40.257239, -74.075693 40.259189, -74.081411 40.258528, -74.082119 40.251457, -74.08462 40.252719, -74.08778 40.250225, -74.095925 40.249682, -74.096515 40.245582, -74.097686 40.245471, -74.110838 40.252683, -74.10372 40.255887, -74.106884 40.259944, -74.108802 40.260613, -74.108582 40.264424, -74.106872 40.265512, -74.105049 40.269701))',\n            12.20511482,\n            2.181023176,\n            'C_Medium_High',\n            7712,\n            0.586637182,\n            6331,\n            25.68419385,\n            428.1136837,\n            0.683936187,\n            0.9973148,\n            18.24031781,\n            10.56183119,\n            0.162\n          ],\n          [\n            7036,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.293808,40.633387],[-74.291091,40.636102],[-74.290991,40.638102],[-74.28982,40.639951],[-74.287325,40.639999],[-74.281687,40.643381],[-74.282183,40.644572],[-74.280691,40.645712],[-74.281494,40.647271],[-74.281488,40.650145],[-74.264342,40.637244],[-74.258956,40.643217],[-74.251701,40.645117],[-74.248772,40.647293],[-74.241341,40.649791],[-74.232513,40.654927],[-74.200469,40.63263],[-74.202247,40.630903],[-74.203737,40.624227],[-74.201864,40.618557],[-74.203128,40.614109],[-74.203813,40.605961],[-74.199408,40.600201],[-74.19952,40.597539],[-74.203688,40.592691],[-74.210741,40.597032],[-74.212032,40.598372],[-74.21287,40.602035],[-74.215101,40.604074],[-74.220216,40.600555],[-74.231748,40.598881],[-74.234067,40.600345],[-74.234767,40.601945],[-74.236609,40.60272],[-74.242593,40.599754],[-74.25381,40.600989],[-74.255822,40.602194],[-74.258114,40.60225],[-74.255608,40.607618],[-74.278844,40.629365],[-74.285491,40.635102],[-74.290491,40.632903],[-74.292494,40.630803],[-74.296379,40.628711],[-74.297134,40.62933],[-74.293808,40.633387]]]}',\n            'POLYGON ((-74.293808 40.633387, -74.291091 40.636102, -74.290991 40.638102, -74.28982 40.639951, -74.287325 40.639999, -74.281687 40.643381, -74.282183 40.644572, -74.280691 40.645712, -74.281494 40.647271, -74.281488 40.650145, -74.264342 40.637244, -74.258956 40.643217, -74.251701 40.645117, -74.248772 40.647293, -74.241341 40.649791, -74.232513 40.654927, -74.200469 40.63263, -74.202247 40.630903, -74.203737 40.624227, -74.201864 40.618557, -74.203128 40.614109, -74.203813 40.605961, -74.199408 40.600201, -74.19952 40.597539, -74.203688 40.592691, -74.210741 40.597032, -74.212032 40.598372, -74.21287 40.602035, -74.215101 40.604074, -74.220216 40.600555, -74.231748 40.598881, -74.234067 40.600345, -74.234767 40.601945, -74.236609 40.60272, -74.242593 40.599754, -74.25381 40.600989, -74.255822 40.602194, -74.258114 40.60225, -74.255608 40.607618, -74.278844 40.629365, -74.285491 40.635102, -74.290491 40.632903, -74.292494 40.630803, -74.296379 40.628711, -74.297134 40.62933, -74.293808 40.633387))',\n            11.53874114,\n            2.699232307,\n            'C_Medium_High',\n            7036,\n            0.801162911,\n            9631,\n            28.86601273,\n            389.7025478,\n            0.719862943,\n            0.995016094,\n            15.60384361,\n            11.23865061,\n            0.228\n          ],\n          [\n            7723,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.013788,40.249179],[-74.01764,40.249613],[-74.019302,40.250582],[-74.019689,40.253485],[-74.012618,40.253141],[-74.011201,40.257],[-74.003013,40.256831],[-74.002888,40.258313],[-74.000094,40.257407],[-74.0002,40.26345],[-73.992354,40.262405],[-73.986681,40.260211],[-73.992881,40.237266],[-74.008001,40.239851],[-74.008447,40.242576],[-74.009543,40.243782],[-74.009733,40.248425],[-74.013788,40.249179]]]}',\n            'POLYGON ((-74.013788 40.249179, -74.01764 40.249613, -74.019302 40.250582, -74.019689 40.253485, -74.012618 40.253141, -74.011201 40.257, -74.003013 40.256831, -74.002888 40.258313, -74.000094 40.257407, -74.0002 40.26345, -73.992354 40.262405, -73.986681 40.260211, -73.992881 40.237266, -74.008001 40.239851, -74.008447 40.242576, -74.009543 40.243782, -74.009733 40.248425, -74.013788 40.249179))',\n            1.726296628,\n            2.781885387,\n            'A_Low_Rural',\n            7723,\n            0.28340081,\n            247,\n            40.21138211,\n            761.4119818,\n            0.71659919,\n            0.995951417,\n            20.89796748,\n            16.39563236,\n            0.213\n          ],\n          [\n            7822,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.680775,41.141961],[-74.694975,41.131348],[-74.69885,41.135537],[-74.701359,41.136794],[-74.703406,41.13429],[-74.706486,41.126963],[-74.713138,41.120327],[-74.711489,41.118891],[-74.720408,41.112296],[-74.746949,41.119559],[-74.745694,41.120599],[-74.745157,41.124768],[-74.741896,41.12892],[-74.737953,41.129147],[-74.737676,41.128104],[-74.735155,41.126436],[-74.728554,41.128919],[-74.733121,41.130496],[-74.735764,41.13238],[-74.739,41.137338],[-74.733352,41.138875],[-74.731341,41.143627],[-74.721084,41.150262],[-74.716727,41.151405],[-74.715427,41.151356],[-74.712447,41.147925],[-74.705516,41.147476],[-74.704311,41.147987],[-74.703071,41.146962],[-74.694816,41.157602],[-74.690193,41.159813],[-74.684027,41.164539],[-74.681418,41.165103],[-74.67785,41.163684],[-74.674644,41.163581],[-74.67129,41.168764],[-74.667272,41.172585],[-74.660801,41.166128],[-74.66382,41.163773],[-74.663566,41.162153],[-74.66462,41.159589],[-74.663429,41.157321],[-74.664679,41.155926],[-74.670442,41.154203],[-74.668458,41.152808],[-74.668539,41.151161],[-74.680775,41.141961]]]}',\n            'POLYGON ((-74.680775 41.141961, -74.694975 41.131348, -74.69885 41.135537, -74.701359 41.136794, -74.703406 41.13429, -74.706486 41.126963, -74.713138 41.120327, -74.711489 41.118891, -74.720408 41.112296, -74.746949 41.119559, -74.745694 41.120599, -74.745157 41.124768, -74.741896 41.12892, -74.737953 41.129147, -74.737676 41.128104, -74.735155 41.126436, -74.728554 41.128919, -74.733121 41.130496, -74.735764 41.13238, -74.739 41.137338, -74.733352 41.138875, -74.731341 41.143627, -74.721084 41.150262, -74.716727 41.151405, -74.715427 41.151356, -74.712447 41.147925, -74.705516 41.147476, -74.704311 41.147987, -74.703071 41.146962, -74.694816 41.157602, -74.690193 41.159813, -74.684027 41.164539, -74.681418 41.165103, -74.67785 41.163684, -74.674644 41.163581, -74.67129 41.168764, -74.667272 41.172585, -74.660801 41.166128, -74.66382 41.163773, -74.663566 41.162153, -74.66462 41.159589, -74.663429 41.157321, -74.664679 41.155926, -74.670442 41.154203, -74.668458 41.152808, -74.668539 41.151161, -74.680775 41.141961))',\n            6.647012773,\n            2.433311133,\n            'A_Low_Rural',\n            7822,\n            0.273684211,\n            95,\n            17.43157895,\n            349.6450282,\n            0.768421053,\n            1,\n            16.78491228,\n            9.373910335,\n            0.118\n          ],\n          [\n            7054,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.39366,40.830496],[-74.405638,40.838928],[-74.422925,40.8447],[-74.451083,40.838346],[-74.463181,40.843465],[-74.467565,40.846525],[-74.465964,40.848547],[-74.464617,40.848757],[-74.46076,40.846768],[-74.454244,40.85264],[-74.452162,40.852242],[-74.449605,40.854737],[-74.45126,40.859271],[-74.449707,40.860541],[-74.451701,40.860499],[-74.461277,40.86634],[-74.46095,40.867844],[-74.449226,40.866302],[-74.443807,40.866626],[-74.447036,40.867209],[-74.439733,40.866511],[-74.43882,40.870782],[-74.440454,40.869142],[-74.445241,40.867982],[-74.450476,40.869008],[-74.450185,40.872275],[-74.44921,40.874111],[-74.451584,40.873513],[-74.451567,40.876515],[-74.447705,40.876866],[-74.448803,40.879174],[-74.450256,40.879959],[-74.433632,40.874255],[-74.433403,40.877506],[-74.43068,40.881706],[-74.425693,40.882707],[-74.424699,40.879771],[-74.425187,40.878315],[-74.419916,40.879254],[-74.420486,40.875395],[-74.419362,40.871206],[-74.414728,40.868737],[-74.410267,40.877077],[-74.405039,40.875898],[-74.404027,40.878969],[-74.4017,40.881459],[-74.396071,40.881961],[-74.392244,40.873568],[-74.39051,40.874087],[-74.390446,40.87614],[-74.384095,40.876199],[-74.383996,40.875199],[-74.380159,40.876783],[-74.376464,40.876885],[-74.376099,40.875662],[-74.375014,40.876373],[-74.372986,40.875671],[-74.371975,40.870267],[-74.36966,40.871168],[-74.368983,40.869851],[-74.369611,40.869488],[-74.365509,40.869788],[-74.365091,40.868735],[-74.366462,40.867373],[-74.366416,40.864756],[-74.36514,40.866106],[-74.362694,40.863887],[-74.358996,40.863608],[-74.354787,40.86599],[-74.35609,40.867062],[-74.358298,40.866561],[-74.361219,40.867003],[-74.361668,40.867421],[-74.357831,40.866738],[-74.355927,40.867299],[-74.353829,40.86556],[-74.352328,40.866215],[-74.349994,40.864479],[-74.349303,40.86285],[-74.348177,40.862628],[-74.34716,40.859279],[-74.347658,40.856795],[-74.346489,40.856411],[-74.345626,40.857625],[-74.344092,40.856842],[-74.340904,40.852353],[-74.341387,40.85158],[-74.344485,40.850715],[-74.341838,40.849039],[-74.341288,40.847784],[-74.341951,40.846224],[-74.35252,40.844081],[-74.354781,40.840973],[-74.360222,40.840871],[-74.367495,40.836199],[-74.371095,40.835699],[-74.379803,40.828479],[-74.380291,40.830042],[-74.381487,40.828478],[-74.380893,40.827515],[-74.383959,40.824991],[-74.388727,40.825989],[-74.39366,40.830496]]]}',\n            'POLYGON ((-74.39366 40.830496, -74.405638 40.838928, -74.422925 40.8447, -74.451083 40.838346, -74.463181 40.843465, -74.467565 40.846525, -74.465964 40.848547, -74.464617 40.848757, -74.46076 40.846768, -74.454244 40.85264, -74.452162 40.852242, -74.449605 40.854737, -74.45126 40.859271, -74.449707 40.860541, -74.451701 40.860499, -74.461277 40.86634, -74.46095 40.867844, -74.449226 40.866302, -74.443807 40.866626, -74.447036 40.867209, -74.439733 40.866511, -74.43882 40.870782, -74.440454 40.869142, -74.445241 40.867982, -74.450476 40.869008, -74.450185 40.872275, -74.44921 40.874111, -74.451584 40.873513, -74.451567 40.876515, -74.447705 40.876866, -74.448803 40.879174, -74.450256 40.879959, -74.433632 40.874255, -74.433403 40.877506, -74.43068 40.881706, -74.425693 40.882707, -74.424699 40.879771, -74.425187 40.878315, -74.419916 40.879254, -74.420486 40.875395, -74.419362 40.871206, -74.414728 40.868737, -74.410267 40.877077, -74.405039 40.875898, -74.404027 40.878969, -74.4017 40.881459, -74.396071 40.881961, -74.392244 40.873568, -74.39051 40.874087, -74.390446 40.87614, -74.384095 40.876199, -74.383996 40.875199, -74.380159 40.876783, -74.376464 40.876885, -74.376099 40.875662, -74.375014 40.876373, -74.372986 40.875671, -74.371975 40.870267, -74.36966 40.871168, -74.368983 40.869851, -74.369611 40.869488, -74.365509 40.869788, -74.365091 40.868735, -74.366462 40.867373, -74.366416 40.864756, -74.36514 40.866106, -74.362694 40.863887, -74.358996 40.863608, -74.354787 40.86599, -74.35609 40.867062, -74.358298 40.866561, -74.361219 40.867003, -74.361668 40.867421, -74.357831 40.866738, -74.355927 40.867299, -74.353829 40.86556, -74.352328 40.866215, -74.349994 40.864479, -74.349303 40.86285, -74.348177 40.862628, -74.34716 40.859279, -74.347658 40.856795, -74.346489 40.856411, -74.345626 40.857625, -74.344092 40.856842, -74.340904 40.852353, -74.341387 40.85158, -74.344485 40.850715, -74.341838 40.849039, -74.341288 40.847784, -74.341951 40.846224, -74.35252 40.844081, -74.354781 40.840973, -74.360222 40.840871, -74.367495 40.836199, -74.371095 40.835699, -74.379803 40.828479, -74.380291 40.830042, -74.381487 40.828478, -74.380893 40.827515, -74.383959 40.824991, -74.388727 40.825989, -74.39366 40.830496))',\n            14.37823635,\n            2.515879321,\n            'B_Low_Suburban',\n            7054,\n            0.5693362,\n            7442,\n            27.18441628,\n            491.6000821,\n            0.709486697,\n            0.99677506,\n            17.45079087,\n            12.67679148,\n            0.256\n          ],\n          [\n            7832,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-75.097586,40.843042],[-75.095784,40.847082],[-75.090962,40.849187],[-75.076684,40.849875],[-75.07083,40.847392],[-75.067655,40.847329],[-75.064328,40.848338],[-75.063343,40.851499],[-75.060491,40.85302],[-75.059239,40.856102],[-75.053294,40.8599],[-75.051692,40.863363],[-75.050839,40.868067],[-75.053664,40.87366],[-75.058655,40.877654],[-75.062073,40.882188],[-75.060998,40.882237],[-75.060556,40.883385],[-75.059124,40.884072],[-75.060651,40.889189],[-75.064166,40.893064],[-75.063612,40.895385],[-75.066827,40.895434],[-75.067646,40.896657],[-75.067452,40.899412],[-75.071768,40.898989],[-75.072097,40.898253],[-75.073515,40.900408],[-75.07391,40.899656],[-75.075231,40.899947],[-75.07609,40.907039],[-75.07929,40.913886],[-75.095528,40.924148],[-75.104809,40.935247],[-75.106148,40.93967],[-75.111689,40.948127],[-75.117767,40.953013],[-75.119116,40.958161],[-75.118076,40.959633],[-75.118346,40.963676],[-75.120435,40.968302],[-75.122603,40.970152],[-75.130166,40.968961],[-75.132622,40.970005],[-75.134701,40.972038],[-75.135531,40.973803],[-75.135521,40.976865],[-75.132522,40.981235],[-75.131096,40.990464],[-75.127196,40.993954],[-75.110595,41.002174],[-75.109113,41.004102],[-75.095556,41.008874],[-75.088596,41.015024],[-75.081101,41.016838],[-75.074999,41.01713],[-75.069277,41.019348],[-75.068369,41.018018],[-75.037857,41.030485],[-75.029696,41.035357],[-75.027882,41.037279],[-75.024165,41.038536],[-75.017392,41.042515],[-75.015185,41.04423],[-75.009346,41.051893],[-75.003538,41.057084],[-75.000477,41.058017],[-74.998034,41.057699],[-74.995569,41.058848],[-74.992703,41.058564],[-74.992174,41.05969],[-74.984424,41.064889],[-74.984063,41.062832],[-74.980707,41.061781],[-74.964688,41.063446],[-74.963232,41.060643],[-74.964228,41.057935],[-74.961406,41.055613],[-74.96458,41.051797],[-74.99578,41.033212],[-75.002831,41.030399],[-75.003773,41.031045],[-75.004225,41.032144],[-75.002834,41.033539],[-74.998731,41.035666],[-74.998954,41.038437],[-74.998147,41.038507],[-74.998255,41.037117],[-74.997435,41.037002],[-74.995356,41.040166],[-74.991659,41.042442],[-74.994705,41.041671],[-75.004298,41.035416],[-75.005265,41.031919],[-75.003411,41.030082],[-75.016649,41.022886],[-75.021761,41.020728],[-75.036997,41.017017],[-75.04378,41.014322],[-75.03456,41.008186],[-75.034725,41.004652],[-75.033729,41.002569],[-75.034213,41.001403],[-75.032281,40.999595],[-75.032341,40.996951],[-75.02898,40.99561],[-75.011902,40.998459],[-75.008874,40.99009],[-75.010378,40.985846],[-75.010075,40.984303],[-75.002953,40.981862],[-75.005732,40.980256],[-75.021928,40.975764],[-75.02123,40.970357],[-75.024322,40.969825],[-75.021013,40.968997],[-75.020256,40.963611],[-75.018195,40.964426],[-75.019502,40.961433],[-75.018391,40.959758],[-75.038299,40.952908],[-75.03083,40.946719],[-75.029307,40.946706],[-75.020847,40.940258],[-75.019403,40.941591],[-75.019535,40.939241],[-75.021033,40.939612],[-75.022159,40.938123],[-75.015041,40.928458],[-75.027891,40.920693],[-75.02861,40.915329],[-75.031276,40.912284],[-75.024568,40.908851],[-75.0223,40.905808],[-75.03156,40.896317],[-75.030145,40.89056],[-75.024001,40.891451],[-75.022183,40.892446],[-75.020636,40.891426],[-75.017441,40.891496],[-75.01407,40.890293],[-75.008894,40.885907],[-75.010391,40.884111],[-75.014797,40.882787],[-75.019161,40.881981],[-75.024044,40.883433],[-75.025249,40.882262],[-75.02744,40.88172],[-75.021561,40.875098],[-75.014017,40.868236],[-75.016147,40.867869],[-75.016559,40.868533],[-75.020503,40.866673],[-75.021161,40.867805],[-75.022549,40.86763],[-75.023738,40.863747],[-75.025591,40.860958],[-75.02513,40.858498],[-75.024052,40.857718],[-75.028967,40.856352],[-75.030658,40.857634],[-75.032149,40.85736],[-75.029716,40.863273],[-75.031074,40.869309],[-75.035994,40.868633],[-75.051176,40.860567],[-75.049486,40.860618],[-75.055534,40.855224],[-75.057455,40.852435],[-75.057422,40.848045],[-75.062178,40.847418],[-75.061305,40.843848],[-75.064167,40.842773],[-75.06489,40.843711],[-75.068656,40.844534],[-75.070267,40.842345],[-75.070187,40.837954],[-75.07707,40.840947],[-75.088876,40.832533],[-75.096301,40.83839],[-75.097572,40.840967],[-75.097586,40.843042]]]}',\n            'POLYGON ((-75.097586 40.843042, -75.095784 40.847082, -75.090962 40.849187, -75.076684 40.849875, -75.07083 40.847392, -75.067655 40.847329, -75.064328 40.848338, -75.063343 40.851499, -75.060491 40.85302, -75.059239 40.856102, -75.053294 40.8599, -75.051692 40.863363, -75.050839 40.868067, -75.053664 40.87366, -75.058655 40.877654, -75.062073 40.882188, -75.060998 40.882237, -75.060556 40.883385, -75.059124 40.884072, -75.060651 40.889189, -75.064166 40.893064, -75.063612 40.895385, -75.066827 40.895434, -75.067646 40.896657, -75.067452 40.899412, -75.071768 40.898989, -75.072097 40.898253, -75.073515 40.900408, -75.07391 40.899656, -75.075231 40.899947, -75.07609 40.907039, -75.07929 40.913886, -75.095528 40.924148, -75.104809 40.935247, -75.106148 40.93967, -75.111689 40.948127, -75.117767 40.953013, -75.119116 40.958161, -75.118076 40.959633, -75.118346 40.963676, -75.120435 40.968302, -75.122603 40.970152, -75.130166 40.968961, -75.132622 40.970005, -75.134701 40.972038, -75.135531 40.973803, -75.135521 40.976865, -75.132522 40.981235, -75.131096 40.990464, -75.127196 40.993954, -75.110595 41.002174, -75.109113 41.004102, -75.095556 41.008874, -75.088596 41.015024, -75.081101 41.016838, -75.074999 41.01713, -75.069277 41.019348, -75.068369 41.018018, -75.037857 41.030485, -75.029696 41.035357, -75.027882 41.037279, -75.024165 41.038536, -75.017392 41.042515, -75.015185 41.04423, -75.009346 41.051893, -75.003538 41.057084, -75.000477 41.058017, -74.998034 41.057699, -74.995569 41.058848, -74.992703 41.058564, -74.992174 41.05969, -74.984424 41.064889, -74.984063 41.062832, -74.980707 41.061781, -74.964688 41.063446, -74.963232 41.060643, -74.964228 41.057935, -74.961406 41.055613, -74.96458 41.051797, -74.99578 41.033212, -75.002831 41.030399, -75.003773 41.031045, -75.004225 41.032144, -75.002834 41.033539, -74.998731 41.035666, -74.998954 41.038437, -74.998147 41.038507, -74.998255 41.037117, -74.997435 41.037002, -74.995356 41.040166, -74.991659 41.042442, -74.994705 41.041671, -75.004298 41.035416, -75.005265 41.031919, -75.003411 41.030082, -75.016649 41.022886, -75.021761 41.020728, -75.036997 41.017017, -75.04378 41.014322, -75.03456 41.008186, -75.034725 41.004652, -75.033729 41.002569, -75.034213 41.001403, -75.032281 40.999595, -75.032341 40.996951, -75.02898 40.99561, -75.011902 40.998459, -75.008874 40.99009, -75.010378 40.985846, -75.010075 40.984303, -75.002953 40.981862, -75.005732 40.980256, -75.021928 40.975764, -75.02123 40.970357, -75.024322 40.969825, -75.021013 40.968997, -75.020256 40.963611, -75.018195 40.964426, -75.019502 40.961433, -75.018391 40.959758, -75.038299 40.952908, -75.03083 40.946719, -75.029307 40.946706, -75.020847 40.940258, -75.019403 40.941591, -75.019535 40.939241, -75.021033 40.939612, -75.022159 40.938123, -75.015041 40.928458, -75.027891 40.920693, -75.02861 40.915329, -75.031276 40.912284, -75.024568 40.908851, -75.0223 40.905808, -75.03156 40.896317, -75.030145 40.89056, -75.024001 40.891451, -75.022183 40.892446, -75.020636 40.891426, -75.017441 40.891496, -75.01407 40.890293, -75.008894 40.885907, -75.010391 40.884111, -75.014797 40.882787, -75.019161 40.881981, -75.024044 40.883433, -75.025249 40.882262, -75.02744 40.88172, -75.021561 40.875098, -75.014017 40.868236, -75.016147 40.867869, -75.016559 40.868533, -75.020503 40.866673, -75.021161 40.867805, -75.022549 40.86763, -75.023738 40.863747, -75.025591 40.860958, -75.02513 40.858498, -75.024052 40.857718, -75.028967 40.856352, -75.030658 40.857634, -75.032149 40.85736, -75.029716 40.863273, -75.031074 40.869309, -75.035994 40.868633, -75.051176 40.860567, -75.049486 40.860618, -75.055534 40.855224, -75.057455 40.852435, -75.057422 40.848045, -75.062178 40.847418, -75.061305 40.843848, -75.064167 40.842773, -75.06489 40.843711, -75.068656 40.844534, -75.070267 40.842345, -75.070187 40.837954, -75.07707 40.840947, -75.088876 40.832533, -75.096301 40.83839, -75.097572 40.840967, -75.097586 40.843042))',\n            48.08259073,\n            2.222012926,\n            'A_Low_Rural',\n            7832,\n            0.291390728,\n            302,\n            13.33670034,\n            237.9317288,\n            0.801324503,\n            0.983443709,\n            15.91447811,\n            6.727790706,\n            0.081\n          ],\n          [\n            7924,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.624966,40.729228],[-74.623454,40.730678],[-74.622391,40.735039],[-74.620469,40.736001],[-74.627104,40.737727],[-74.628747,40.736844],[-74.630137,40.735166],[-74.631305,40.734548],[-74.632923,40.732801],[-74.633441,40.732418],[-74.633871,40.732237],[-74.632255,40.733644],[-74.630508,40.736973],[-74.631292,40.738187],[-74.629319,40.739683],[-74.63127,40.741111],[-74.555922,40.75831],[-74.555203,40.756999],[-74.553197,40.756439],[-74.551914,40.754863],[-74.550924,40.751073],[-74.552886,40.749578],[-74.548756,40.746131],[-74.549383,40.745092],[-74.548999,40.740994],[-74.550988,40.741647],[-74.551958,40.739985],[-74.55856,40.736104],[-74.556731,40.73478],[-74.560814,40.731351],[-74.563545,40.730267],[-74.564013,40.728933],[-74.552547,40.722803],[-74.555477,40.721368],[-74.5533,40.719188],[-74.556246,40.717896],[-74.567761,40.706455],[-74.607957,40.694414],[-74.609197,40.696081],[-74.611319,40.695338],[-74.61599,40.695721],[-74.618622,40.695149],[-74.618226,40.704919],[-74.620821,40.710878],[-74.620235,40.712269],[-74.615067,40.715173],[-74.612185,40.719403],[-74.612003,40.72373],[-74.61545,40.723053],[-74.620932,40.716648],[-74.623654,40.715129],[-74.625684,40.715004],[-74.634205,40.722291],[-74.631262,40.72482],[-74.626871,40.725334],[-74.626784,40.727547],[-74.625149,40.72802],[-74.625639,40.728948],[-74.624966,40.729228]]]}',\n            'POLYGON ((-74.624966 40.729228, -74.623454 40.730678, -74.622391 40.735039, -74.620469 40.736001, -74.627104 40.737727, -74.628747 40.736844, -74.630137 40.735166, -74.631305 40.734548, -74.632923 40.732801, -74.633441 40.732418, -74.633871 40.732237, -74.632255 40.733644, -74.630508 40.736973, -74.631292 40.738187, -74.629319 40.739683, -74.63127 40.741111, -74.555922 40.75831, -74.555203 40.756999, -74.553197 40.756439, -74.551914 40.754863, -74.550924 40.751073, -74.552886 40.749578, -74.548756 40.746131, -74.549383 40.745092, -74.548999 40.740994, -74.550988 40.741647, -74.551958 40.739985, -74.55856 40.736104, -74.556731 40.73478, -74.560814 40.731351, -74.563545 40.730267, -74.564013 40.728933, -74.552547 40.722803, -74.555477 40.721368, -74.5533 40.719188, -74.556246 40.717896, -74.567761 40.706455, -74.607957 40.694414, -74.609197 40.696081, -74.611319 40.695338, -74.61599 40.695721, -74.618622 40.695149, -74.618226 40.704919, -74.620821 40.710878, -74.620235 40.712269, -74.615067 40.715173, -74.612185 40.719403, -74.612003 40.72373, -74.61545 40.723053, -74.620932 40.716648, -74.623654 40.715129, -74.625684 40.715004, -74.634205 40.722291, -74.631262 40.72482, -74.626871 40.725334, -74.626784 40.727547, -74.625149 40.72802, -74.625639 40.728948, -74.624966 40.729228))',\n            12.13371074,\n            2.402380687,\n            'A_Low_Rural',\n            7924,\n            0.384324324,\n            1850,\n            33.00813449,\n            677.5939771,\n            0.667027027,\n            0.996756757,\n            19.17525307,\n            15.90160445,\n            0.242\n          ],\n          [\n            7928,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.409178,40.741059],[-74.405471,40.73989],[-74.402552,40.742258],[-74.402172,40.741487],[-74.391635,40.75198],[-74.389873,40.751394],[-74.383649,40.754173],[-74.38351,40.755403],[-74.380255,40.755386],[-74.381513,40.754696],[-74.373581,40.75015],[-74.370371,40.752139],[-74.363072,40.750403],[-74.367572,40.748824],[-74.364765,40.746788],[-74.366562,40.743889],[-74.371979,40.742359],[-74.37256,40.741587],[-74.370829,40.735753],[-74.371246,40.734627],[-74.374669,40.733545],[-74.377932,40.734319],[-74.379732,40.73396],[-74.380295,40.7333],[-74.379853,40.732492],[-74.377503,40.730032],[-74.384382,40.724385],[-74.388232,40.726165],[-74.39069,40.725499],[-74.390769,40.718294],[-74.39509,40.717273],[-74.402254,40.718001],[-74.404574,40.715245],[-74.40735,40.713461],[-74.408466,40.710106],[-74.410057,40.709517],[-74.412495,40.7103],[-74.413818,40.707248],[-74.4154,40.705781],[-74.426897,40.701711],[-74.430005,40.697838],[-74.429578,40.697004],[-74.430781,40.693453],[-74.434551,40.693575],[-74.437847,40.6919],[-74.43797,40.690247],[-74.444333,40.687969],[-74.449736,40.718831],[-74.447841,40.718904],[-74.437223,40.723493],[-74.426842,40.729088],[-74.424382,40.734726],[-74.426048,40.733344],[-74.427376,40.733482],[-74.426785,40.735839],[-74.429199,40.735878],[-74.430449,40.734831],[-74.430765,40.735285],[-74.428298,40.736522],[-74.430213,40.7377],[-74.43032,40.736951],[-74.432798,40.736704],[-74.434311,40.735493],[-74.437467,40.735121],[-74.437348,40.73587],[-74.439214,40.73673],[-74.44084,40.73642],[-74.439595,40.736006],[-74.440803,40.734753],[-74.441744,40.735486],[-74.441043,40.736868],[-74.442891,40.735942],[-74.444308,40.738929],[-74.438047,40.741641],[-74.432156,40.746447],[-74.434702,40.747732],[-74.438198,40.752308],[-74.436616,40.752849],[-74.435314,40.752306],[-74.436024,40.751602],[-74.433267,40.750069],[-74.432271,40.751089],[-74.428827,40.749474],[-74.430019,40.746001],[-74.41515,40.742751],[-74.409916,40.740501],[-74.409178,40.741059]]]}',\n            'POLYGON ((-74.409178 40.741059, -74.405471 40.73989, -74.402552 40.742258, -74.402172 40.741487, -74.391635 40.75198, -74.389873 40.751394, -74.383649 40.754173, -74.38351 40.755403, -74.380255 40.755386, -74.381513 40.754696, -74.373581 40.75015, -74.370371 40.752139, -74.363072 40.750403, -74.367572 40.748824, -74.364765 40.746788, -74.366562 40.743889, -74.371979 40.742359, -74.37256 40.741587, -74.370829 40.735753, -74.371246 40.734627, -74.374669 40.733545, -74.377932 40.734319, -74.379732 40.73396, -74.380295 40.7333, -74.379853 40.732492, -74.377503 40.730032, -74.384382 40.724385, -74.388232 40.726165, -74.39069 40.725499, -74.390769 40.718294, -74.39509 40.717273, -74.402254 40.718001, -74.404574 40.715245, -74.40735 40.713461, -74.408466 40.710106, -74.410057 40.709517, -74.412495 40.7103, -74.413818 40.707248, -74.4154 40.705781, -74.426897 40.701711, -74.430005 40.697838, -74.429578 40.697004, -74.430781 40.693453, -74.434551 40.693575, -74.437847 40.6919, -74.43797 40.690247, -74.444333 40.687969, -74.449736 40.718831, -74.447841 40.718904, -74.437223 40.723493, -74.426842 40.729088, -74.424382 40.734726, -74.426048 40.733344, -74.427376 40.733482, -74.426785 40.735839, -74.429199 40.735878, -74.430449 40.734831, -74.430765 40.735285, -74.428298 40.736522, -74.430213 40.7377, -74.43032 40.736951, -74.432798 40.736704, -74.434311 40.735493, -74.437467 40.735121, -74.437348 40.73587, -74.439214 40.73673, -74.44084 40.73642, -74.439595 40.736006, -74.440803 40.734753, -74.441744 40.735486, -74.441043 40.736868, -74.442891 40.735942, -74.444308 40.738929, -74.438047 40.741641, -74.432156 40.746447, -74.434702 40.747732, -74.438198 40.752308, -74.436616 40.752849, -74.435314 40.752306, -74.436024 40.751602, -74.433267 40.750069, -74.432271 40.751089, -74.428827 40.749474, -74.430019 40.746001, -74.41515 40.742751, -74.409916 40.740501, -74.409178 40.741059))',\n            8.953905337,\n            2.250793062,\n            'B_Low_Suburban',\n            7928,\n            0.539032204,\n            5931,\n            39.01961447,\n            858.5168842,\n            0.613050076,\n            0.997133704,\n            20.95072145,\n            18.44006178,\n            0.31\n          ],\n          [\n            7979,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.746315,40.699842],[-74.738518,40.708428],[-74.73803,40.711868],[-74.734191,40.71672],[-74.729987,40.717241],[-74.732448,40.720062],[-74.724418,40.719455],[-74.720152,40.720857],[-74.721834,40.718984],[-74.720881,40.716657],[-74.71557,40.714933],[-74.715093,40.713903],[-74.717147,40.714164],[-74.716015,40.710283],[-74.717798,40.709288],[-74.714601,40.703286],[-74.714347,40.699368],[-74.721624,40.690735],[-74.722079,40.688939],[-74.72065,40.687797],[-74.72303,40.68265],[-74.725555,40.683786],[-74.729034,40.68356],[-74.730184,40.682581],[-74.732786,40.683785],[-74.730782,40.686112],[-74.73592,40.688406],[-74.732475,40.692508],[-74.733375,40.693151],[-74.734169,40.699729],[-74.744802,40.697551],[-74.746315,40.699842]]]}',\n            'POLYGON ((-74.746315 40.699842, -74.738518 40.708428, -74.73803 40.711868, -74.734191 40.71672, -74.729987 40.717241, -74.732448 40.720062, -74.724418 40.719455, -74.720152 40.720857, -74.721834 40.718984, -74.720881 40.716657, -74.71557 40.714933, -74.715093 40.713903, -74.717147 40.714164, -74.716015 40.710283, -74.717798 40.709288, -74.714601 40.703286, -74.714347 40.699368, -74.721624 40.690735, -74.722079 40.688939, -74.72065 40.687797, -74.72303 40.68265, -74.725555 40.683786, -74.729034 40.68356, -74.730184 40.682581, -74.732786 40.683785, -74.730782 40.686112, -74.73592 40.688406, -74.732475 40.692508, -74.733375 40.693151, -74.734169 40.699729, -74.744802 40.697551, -74.746315 40.699842))',\n            2.439857168,\n            2.316265052,\n            'A_Low_Rural',\n            7979,\n            0.139534884,\n            215,\n            22.95774648,\n            284.0477961,\n            0.693023256,\n            0.990697674,\n            14.85117371,\n            8.606828708,\n            0.354\n          ],\n          [\n            7838,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.96258,40.908359],[-74.959171,40.909348],[-74.958166,40.910924],[-74.955046,40.911941],[-74.952035,40.914182],[-74.947947,40.912938],[-74.945624,40.911373],[-74.945686,40.909849],[-74.944298,40.909232],[-74.940447,40.911676],[-74.93977,40.916014],[-74.93583,40.915965],[-74.93084,40.917595],[-74.927867,40.906805],[-74.887969,40.933194],[-74.887988,40.931636],[-74.886846,40.929954],[-74.882716,40.930239],[-74.874369,40.932296],[-74.873234,40.934085],[-74.86866,40.936267],[-74.866899,40.933617],[-74.859938,40.93051],[-74.852375,40.930101],[-74.848303,40.933399],[-74.847051,40.933475],[-74.844269,40.930927],[-74.844978,40.927355],[-74.840887,40.923065],[-74.840416,40.921211],[-74.84412,40.919599],[-74.842955,40.919474],[-74.844491,40.91793],[-74.844531,40.915532],[-74.859914,40.909303],[-74.860956,40.904959],[-74.862287,40.903064],[-74.858839,40.900314],[-74.858959,40.899168],[-74.848263,40.892969],[-74.851169,40.889839],[-74.85923,40.885743],[-74.859366,40.887143],[-74.860511,40.887838],[-74.863545,40.883912],[-74.867125,40.884417],[-74.872203,40.882492],[-74.872768,40.883398],[-74.876052,40.884353],[-74.882058,40.883988],[-74.882891,40.882731],[-74.887871,40.879998],[-74.889572,40.87633],[-74.894028,40.872274],[-74.89991,40.869372],[-74.903521,40.865869],[-74.905344,40.866523],[-74.903426,40.864607],[-74.903128,40.86326],[-74.900129,40.862348],[-74.896917,40.858397],[-74.894792,40.857851],[-74.894754,40.856634],[-74.893744,40.8561],[-74.894932,40.856353],[-74.895533,40.851298],[-74.907884,40.855752],[-74.915047,40.852114],[-74.924079,40.851947],[-74.929062,40.848559],[-74.934249,40.85086],[-74.933294,40.852222],[-74.935853,40.852373],[-74.941042,40.846725],[-74.94384,40.846406],[-74.950587,40.842517],[-74.951581,40.839414],[-74.956373,40.834365],[-74.971647,40.842927],[-74.971381,40.843883],[-74.968743,40.844635],[-74.960144,40.855778],[-74.963608,40.855651],[-74.963127,40.858348],[-74.964627,40.85919],[-74.96689,40.85904],[-74.968542,40.860945],[-74.97156,40.860844],[-74.968929,40.864416],[-74.969525,40.86504],[-74.971744,40.861518],[-74.972369,40.86245],[-74.9725,40.861514],[-74.973998,40.861535],[-74.974787,40.860622],[-74.978735,40.87158],[-74.979484,40.87194],[-74.97877,40.873364],[-74.979931,40.874228],[-74.976368,40.876708],[-74.982155,40.879127],[-74.980311,40.880401],[-74.980521,40.881626],[-74.976513,40.883126],[-74.978397,40.88719],[-74.976715,40.890538],[-74.9757,40.883461],[-74.977469,40.879267],[-74.97593,40.878446],[-74.976152,40.879675],[-74.974987,40.881253],[-74.969259,40.884146],[-74.963281,40.885689],[-74.957982,40.890776],[-74.957476,40.893947],[-74.961516,40.894095],[-74.95829,40.900182],[-74.962611,40.906121],[-74.959793,40.907294],[-74.96258,40.908359]]]}',\n            'POLYGON ((-74.96258 40.908359, -74.959171 40.909348, -74.958166 40.910924, -74.955046 40.911941, -74.952035 40.914182, -74.947947 40.912938, -74.945624 40.911373, -74.945686 40.909849, -74.944298 40.909232, -74.940447 40.911676, -74.93977 40.916014, -74.93583 40.915965, -74.93084 40.917595, -74.927867 40.906805, -74.887969 40.933194, -74.887988 40.931636, -74.886846 40.929954, -74.882716 40.930239, -74.874369 40.932296, -74.873234 40.934085, -74.86866 40.936267, -74.866899 40.933617, -74.859938 40.93051, -74.852375 40.930101, -74.848303 40.933399, -74.847051 40.933475, -74.844269 40.930927, -74.844978 40.927355, -74.840887 40.923065, -74.840416 40.921211, -74.84412 40.919599, -74.842955 40.919474, -74.844491 40.91793, -74.844531 40.915532, -74.859914 40.909303, -74.860956 40.904959, -74.862287 40.903064, -74.858839 40.900314, -74.858959 40.899168, -74.848263 40.892969, -74.851169 40.889839, -74.85923 40.885743, -74.859366 40.887143, -74.860511 40.887838, -74.863545 40.883912, -74.867125 40.884417, -74.872203 40.882492, -74.872768 40.883398, -74.876052 40.884353, -74.882058 40.883988, -74.882891 40.882731, -74.887871 40.879998, -74.889572 40.87633, -74.894028 40.872274, -74.89991 40.869372, -74.903521 40.865869, -74.905344 40.866523, -74.903426 40.864607, -74.903128 40.86326, -74.900129 40.862348, -74.896917 40.858397, -74.894792 40.857851, -74.894754 40.856634, -74.893744 40.8561, -74.894932 40.856353, -74.895533 40.851298, -74.907884 40.855752, -74.915047 40.852114, -74.924079 40.851947, -74.929062 40.848559, -74.934249 40.85086, -74.933294 40.852222, -74.935853 40.852373, -74.941042 40.846725, -74.94384 40.846406, -74.950587 40.842517, -74.951581 40.839414, -74.956373 40.834365, -74.971647 40.842927, -74.971381 40.843883, -74.968743 40.844635, -74.960144 40.855778, -74.963608 40.855651, -74.963127 40.858348, -74.964627 40.85919, -74.96689 40.85904, -74.968542 40.860945, -74.97156 40.860844, -74.968929 40.864416, -74.969525 40.86504, -74.971744 40.861518, -74.972369 40.86245, -74.9725 40.861514, -74.973998 40.861535, -74.974787 40.860622, -74.978735 40.87158, -74.979484 40.87194, -74.97877 40.873364, -74.979931 40.874228, -74.976368 40.876708, -74.982155 40.879127, -74.980311 40.880401, -74.980521 40.881626, -74.976513 40.883126, -74.978397 40.88719, -74.976715 40.890538, -74.9757 40.883461, -74.977469 40.879267, -74.97593 40.878446, -74.976152 40.879675, -74.974987 40.881253, -74.969259 40.884146, -74.963281 40.885689, -74.957982 40.890776, -74.957476 40.893947, -74.961516 40.894095, -74.95829 40.900182, -74.962611 40.906121, -74.959793 40.907294, -74.96258 40.908359))',\n            25.10592549,\n            2.007636482,\n            'A_Low_Rural',\n            7838,\n            0.346875,\n            320,\n            11.98119122,\n            211.9244058,\n            0.771875,\n            0.996875,\n            15.3553814,\n            6.210590289,\n            0.082\n          ],\n          [\n            7044,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.261032,40.832085],[-74.259357,40.836363],[-74.257107,40.838124],[-74.256523,40.841781],[-74.251892,40.849829],[-74.2334,40.844389],[-74.237214,40.836034],[-74.227445,40.83331],[-74.227147,40.836396],[-74.22099,40.834799],[-74.225755,40.827381],[-74.224352,40.827071],[-74.227054,40.824221],[-74.227725,40.824867],[-74.229113,40.822199],[-74.229079,40.820019],[-74.232573,40.814863],[-74.250795,40.824461],[-74.264305,40.825431],[-74.261032,40.832085]]]}',\n            'POLYGON ((-74.261032 40.832085, -74.259357 40.836363, -74.257107 40.838124, -74.256523 40.841781, -74.251892 40.849829, -74.2334 40.844389, -74.237214 40.836034, -74.227445 40.83331, -74.227147 40.836396, -74.22099 40.834799, -74.225755 40.827381, -74.224352 40.827071, -74.227054 40.824221, -74.227725 40.824867, -74.229113 40.822199, -74.229079 40.820019, -74.232573 40.814863, -74.250795 40.824461, -74.264305 40.825431, -74.261032 40.832085))',\n            2.791550146,\n            2.282754753,\n            'C_Medium_High',\n            7044,\n            0.652425579,\n            3628,\n            31.61574586,\n            596.5287053,\n            0.677508269,\n            0.997794928,\n            19.71404236,\n            13.61658418,\n            0.263\n          ]\n        ],\n        fields: [\n          {\n            name: 'a_zip',\n            type: 'integer'\n          },\n          {\n            name: 'simplified_shape_v2',\n            type: 'geojson'\n          },\n          {\n            name: 'simplified_shape',\n            type: 'geojson'\n          },\n          {\n            name: 'zip_area',\n            type: 'real'\n          },\n          {\n            name: 'avg_number',\n            type: 'real'\n          },\n          {\n            name: 'str_type',\n            type: 'string'\n          },\n          {\n            name: 'int_type',\n            type: 'integer'\n          },\n          {\n            name: 'real_type',\n            type: 'real'\n          },\n          {\n            name: 'c_m_r',\n            type: 'integer'\n          },\n          {\n            name: 'c_m_t',\n            type: 'real'\n          },\n          {\n            name: 'c_a_v',\n            type: 'real'\n          },\n          {\n            name: 'c_ch',\n            type: 'real'\n          },\n          {\n            name: 'c_ta',\n            type: 'real'\n          },\n          {\n            name: 'c_k_a',\n            type: 'real'\n          },\n          {\n            name: 'c_ltv',\n            type: 'real'\n          },\n          {\n            name: 'b_r_p',\n            type: 'real'\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: 'a5ybmwl2d',\n            id: '9ca0l7p2a',\n            name: 'zip_area',\n            type: 'range',\n            value: [0, 21.8],\n            enlarged: false\n          }\n        ],\n        layers: [\n          {\n            id: '8zr03we',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'extruded polygon',\n              color: [181, 18, 65],\n              columns: {\n                geojson: 'simplified_shape_v2'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 2,\n                colorRange: {\n                  name: 'ColorBrewer YlGnBu-9',\n                  type: 'sequential',\n                  category: 'ColorBrewer',\n                  colors: [\n                    '#081d58',\n                    '#253494',\n                    '#225ea8',\n                    '#1d91c0',\n                    '#41b6c4',\n                    '#7fcdbb',\n                    '#c7e9b4',\n                    '#edf8b1',\n                    '#ffffd9'\n                  ],\n                  reversed: true\n                },\n                radius: 10,\n                sizeRange: [0, 10],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 18,\n                stroked: true,\n                filled: true,\n                enable3d: true,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: {\n                name: 'c_m_r',\n                type: 'integer'\n              },\n              colorScale: 'quantize',\n              sizeField: null,\n              sizeScale: 'linear',\n              heightField: {\n                name: 'c_a_v',\n                type: 'real'\n              },\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          },\n          {\n            id: 'u7apppt',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'stroke by pop',\n              color: [221, 178, 124],\n              columns: {\n                geojson: 'simplified_shape'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 7.6,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radius: 10,\n                sizeRange: [18.9, 47.6],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 5,\n                'hi-precision': false,\n                stroked: true,\n                filled: false,\n                enable3d: false,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: {\n                name: 'c_ta',\n                type: 'real'\n              },\n              sizeScale: 'linear',\n              heightField: null,\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              a5ybmwl2d: ['a_zip', 'str_type', 'int_type']\n            },\n            enabled: false\n          },\n          brush: {\n            size: 1,\n            enabled: false\n          }\n        },\n        layerBlending: 'normal'\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        buildingLayer: {\n          isVisible: false,\n          color: [209, 206, 199],\n          opacity: 0.7\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.75043,\n        longitude: -122.34679,\n        pitch: 0,\n        zoom: 9\n      },\n      queryStatus: {\n        pastQueries: [\n          {\n            id: 'a5ybmwl2d',\n            label: 'geojson_as_string_small.csv',\n            queryType: 'file',\n            queryOption: 'csv',\n            params: {\n              file: null\n            },\n            size: 67502\n          }\n        ]\n      }\n    }\n  }\n};\n\nexport const v0ExpectedInfo = {\n  id: 'a5ybmwl2d',\n  label: 'geojson_as_string_small.csv',\n  color: [53, 92, 125]\n};\n\nexport const v0ExpectedFields = [\n  {name: 'a_zip', type: 'integer', format: '', analyzerType: 'INT'},\n  {\n    name: 'simplified_shape_v2',\n    type: 'geojson',\n    format: '',\n    analyzerType: 'PAIR_GEOMETRY_FROM_STRING'\n  },\n  {name: 'simplified_shape', type: 'geojson', format: '', analyzerType: 'GEOMETRY_FROM_STRING'},\n  {name: 'zip_area', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'avg_number', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'str_type', type: 'string', format: '', analyzerType: 'STRING'},\n  {name: 'int_type', type: 'integer', format: '', analyzerType: 'INT'},\n  {name: 'real_type', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_m_r', type: 'integer', format: '', analyzerType: 'INT'},\n  {name: 'c_m_t', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_a_v', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ch', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ta', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_k_a', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ltv', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'b_r_p', type: 'real', format: '', analyzerType: 'FLOAT'}\n];\n\nexport const mergedFilters = [\n  {\n    dataId: ['a5ybmwl2d'],\n    id: '9ca0l7p2a',\n    enabled: true,\n    view: 'side',\n    isAnimating: false,\n    animationWindow: 'free',\n    name: ['zip_area'],\n    type: 'range',\n    fieldIdx: [3],\n    domain: [0.03, 48.09],\n    value: [0.03, 21.8],\n    step: 0.01,\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    speed: 1,\n    fieldType: 'real',\n    typeOptions: ['range'],\n    fixedDomain: false,\n    gpu: true,\n    gpuChannel: [0],\n    bins: {\n      a5ybmwl2d: histogramFromDomain(\n        [0.03, 48.09],\n        savedStateV1.datasets[0].data.allData.map(d => d[3]),\n        BINS\n      )\n    }\n  }\n];\n\nconst mergedLayer0 = new GeojsonLayer({\n  id: '8zr03we'\n});\n\nmergedLayer0.config = {\n  dataId: 'a5ybmwl2d',\n  label: 'extruded polygon',\n  color: [181, 18, 65],\n  columns: {\n    geojson: {\n      fieldIdx: 1,\n      value: 'simplified_shape_v2',\n      optional: false\n    },\n    id: {value: null, fieldIdx: -1, optional: true},\n    lat: {value: null, fieldIdx: -1, optional: true},\n    lng: {value: null, fieldIdx: -1, optional: true},\n    altitude: {value: null, fieldIdx: -1, optional: true},\n    sortBy: {value: null, fieldIdx: -1, optional: true}\n  },\n  columnMode: 'geojson',\n  hidden: false,\n  isVisible: true,\n  isConfigActive: false,\n  highlightColor: [252, 242, 26, 255],\n  colorField: {\n    name: 'c_m_r',\n    id: 'c_m_r',\n    displayName: 'c_m_r',\n    type: 'integer',\n    format: '',\n    fieldIdx: 8,\n    analyzerType: 'INT',\n    valueAccessor: values => values[8]\n  },\n  colorScale: 'quantize',\n  colorDomain: [45, 9642],\n  strokeColorField: {\n    name: 'c_m_r',\n    id: 'c_m_r',\n    displayName: 'c_m_r',\n    type: 'integer',\n    format: '',\n    fieldIdx: 8,\n    analyzerType: 'INT',\n    valueAccessor: values => values[8]\n  },\n  strokeColorScale: 'quantize',\n  strokeColorDomain: [45, 9642],\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  heightField: {\n    name: 'c_a_v',\n    id: 'c_a_v',\n    displayName: 'c_a_v',\n    type: 'real',\n    format: '',\n    fieldIdx: 10,\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[10]\n  },\n  heightScale: 'linear',\n  heightDomain: [211.9244058, 858.5168842],\n  radiusField: null,\n  radiusScale: 'linear',\n  radiusDomain: [0, 1],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    opacity: 0.8,\n    thickness: 2,\n    colorRange: {\n      name: 'YlGnBu',\n      type: 'sequential',\n      category: 'ColorBrewer',\n      colors: [\n        '#081d58',\n        '#253494',\n        '#225ea8',\n        '#1d91c0',\n        '#41b6c4',\n        '#7fcdbb',\n        '#c7e9b4',\n        '#edf8b1',\n        '#ffffd9'\n      ],\n      reversed: true\n    },\n    strokeColorRange: {\n      name: 'YlGnBu',\n      type: 'sequential',\n      category: 'ColorBrewer',\n      colors: [\n        '#081d58',\n        '#253494',\n        '#225ea8',\n        '#1d91c0',\n        '#41b6c4',\n        '#7fcdbb',\n        '#c7e9b4',\n        '#edf8b1',\n        '#ffffd9'\n      ],\n      reversed: true\n    },\n    radius: 10,\n    sizeRange: [0, 10],\n    radiusRange: [0, 50],\n    heightRange: [0, 500],\n    elevationScale: 18,\n    fixedHeight: false,\n    stroked: true,\n    filled: true,\n    strokeColor: [181, 18, 65],\n    strokeOpacity: 0.8,\n    enable3d: true,\n    wireframe: false\n  },\n  animation: {enabled: false}\n};\n\nmergedLayer0.meta = {\n  bounds: [-75.135531, 40.213125, -73.949229, 41.172585],\n  fixedRadius: false,\n  featureTypes: {polygon: true}\n};\n\nmergedLayer0.dataToFeature = {\n  0: {\n    type: 'Feature',\n    properties: {index: 0},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.158491, 40.835947],\n          [-74.157914, 40.83902],\n          [-74.148473, 40.834522],\n          [-74.146471, 40.836645],\n          [-74.142598, 40.833128],\n          [-74.140177, 40.832492],\n          [-74.136732, 40.836791],\n          [-74.142706, 40.840604],\n          [-74.144667, 40.84312],\n          [-74.142936, 40.844974],\n          [-74.136054, 40.84119],\n          [-74.1356, 40.841703],\n          [-74.133665, 40.840712],\n          [-74.133028, 40.841321],\n          [-74.13274, 40.839586],\n          [-74.121853, 40.834098],\n          [-74.121087, 40.831],\n          [-74.124447, 40.822169],\n          [-74.130031, 40.819962],\n          [-74.149242, 40.830537],\n          [-74.148818, 40.830916],\n          [-74.150888, 40.833143],\n          [-74.151923, 40.832074],\n          [-74.158491, 40.835947]\n        ]\n      ]\n    }\n  },\n  1: {\n    type: 'Feature',\n    properties: {index: 1},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.31687, 40.656696],\n          [-74.319449, 40.658154],\n          [-74.326141, 40.656094],\n          [-74.326593, 40.657702],\n          [-74.327693, 40.662904],\n          [-74.325199, 40.667596],\n          [-74.326093, 40.668202],\n          [-74.323615, 40.670664],\n          [-74.323228, 40.672276],\n          [-74.324391, 40.67444],\n          [-74.322371, 40.677443],\n          [-74.310392, 40.678402],\n          [-74.312993, 40.674703],\n          [-74.312093, 40.674304],\n          [-74.314552, 40.673889],\n          [-74.312827, 40.673282],\n          [-74.307888, 40.674387],\n          [-74.306792, 40.672102],\n          [-74.290976, 40.673465],\n          [-74.29185, 40.671802],\n          [-74.290689, 40.670646],\n          [-74.288727, 40.670925],\n          [-74.285991, 40.667001],\n          [-74.284275, 40.658075],\n          [-74.283511, 40.658187],\n          [-74.283369, 40.657406],\n          [-74.285786, 40.655022],\n          [-74.282693, 40.653502],\n          [-74.280691, 40.645712],\n          [-74.282183, 40.644572],\n          [-74.281687, 40.643381],\n          [-74.287325, 40.639999],\n          [-74.29577, 40.638581],\n          [-74.304907, 40.633459],\n          [-74.319678, 40.641989],\n          [-74.316485, 40.645194],\n          [-74.320663, 40.647344],\n          [-74.312298, 40.654042],\n          [-74.31687, 40.656696]\n        ]\n      ]\n    }\n  },\n  2: {\n    type: 'Feature',\n    properties: {index: 2},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.387589, 40.632238],\n          [-74.401749, 40.640393],\n          [-74.386745, 40.653316],\n          [-74.374682, 40.646553],\n          [-74.372634, 40.6468],\n          [-74.371828, 40.644885],\n          [-74.374122, 40.642352],\n          [-74.37327, 40.641829],\n          [-74.375777, 40.636295],\n          [-74.37717, 40.634842],\n          [-74.382333, 40.632417],\n          [-74.384457, 40.630453],\n          [-74.387589, 40.632238]\n        ]\n      ]\n    }\n  },\n  3: {\n    type: 'Feature',\n    properties: {index: 3},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.165995, 40.747969],\n          [-74.164799, 40.754718],\n          [-74.155877, 40.751722],\n          [-74.154327, 40.753966],\n          [-74.145563, 40.751975],\n          [-74.144976, 40.748161],\n          [-74.14586, 40.748031],\n          [-74.140783, 40.742694],\n          [-74.139386, 40.743314],\n          [-74.137492, 40.741398],\n          [-74.142688, 40.739301],\n          [-74.145288, 40.735701],\n          [-74.147188, 40.734401],\n          [-74.155687, 40.733501],\n          [-74.15998, 40.734485],\n          [-74.162093, 40.736109],\n          [-74.163788, 40.739201],\n          [-74.165987, 40.745199],\n          [-74.165995, 40.747969]\n        ]\n      ]\n    }\n  },\n  4: {\n    type: 'Feature',\n    properties: {index: 4},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.361356, 40.939299],\n          [-74.370498, 40.926937],\n          [-74.37404, 40.927298],\n          [-74.374216, 40.921969],\n          [-74.371645, 40.92135],\n          [-74.366428, 40.918141],\n          [-74.356676, 40.915791],\n          [-74.349697, 40.916512],\n          [-74.347883, 40.91351],\n          [-74.349593, 40.91185],\n          [-74.353183, 40.910446],\n          [-74.350384, 40.908593],\n          [-74.351179, 40.907344],\n          [-74.349633, 40.906311],\n          [-74.351883, 40.90419],\n          [-74.351747, 40.902647],\n          [-74.349573, 40.903845],\n          [-74.349271, 40.902641],\n          [-74.344758, 40.902596],\n          [-74.345589, 40.900045],\n          [-74.339928, 40.899308],\n          [-74.339554, 40.895904],\n          [-74.338613, 40.895769],\n          [-74.33944, 40.893881],\n          [-74.335743, 40.893239],\n          [-74.3389, 40.892087],\n          [-74.339694, 40.889839],\n          [-74.339646, 40.888758],\n          [-74.338118, 40.887173],\n          [-74.339575, 40.883893],\n          [-74.340271, 40.879528],\n          [-74.338075, 40.879652],\n          [-74.337614, 40.878206],\n          [-74.33507, 40.877426],\n          [-74.33531, 40.876555],\n          [-74.337696, 40.87591],\n          [-74.337808, 40.875101],\n          [-74.334259, 40.873471],\n          [-74.337288, 40.872611],\n          [-74.337418, 40.87179],\n          [-74.335937, 40.870521],\n          [-74.33806, 40.870059],\n          [-74.336714, 40.869079],\n          [-74.33765, 40.868402],\n          [-74.33768, 40.866752],\n          [-74.339806, 40.866539],\n          [-74.34018, 40.868365],\n          [-74.339545, 40.874876],\n          [-74.341626, 40.877524],\n          [-74.340485, 40.882283],\n          [-74.342874, 40.885385],\n          [-74.346198, 40.887367],\n          [-74.35009, 40.887702],\n          [-74.352792, 40.886265],\n          [-74.355507, 40.888913],\n          [-74.359916, 40.886643],\n          [-74.360096, 40.887329],\n          [-74.361792, 40.887158],\n          [-74.36087, 40.883379],\n          [-74.358905, 40.883365],\n          [-74.358888, 40.880786],\n          [-74.357662, 40.880344],\n          [-74.357789, 40.879259],\n          [-74.358676, 40.879136],\n          [-74.358091, 40.878002],\n          [-74.362636, 40.877577],\n          [-74.360456, 40.872665],\n          [-74.361843, 40.87065],\n          [-74.365778, 40.871801],\n          [-74.366753, 40.874063],\n          [-74.37127, 40.876723],\n          [-74.369921, 40.878054],\n          [-74.370331, 40.879192],\n          [-74.371574, 40.879878],\n          [-74.37187, 40.878855],\n          [-74.373453, 40.878978],\n          [-74.373051, 40.880533],\n          [-74.374017, 40.882604],\n          [-74.3772, 40.886221],\n          [-74.376296, 40.888095],\n          [-74.376308, 40.890713],\n          [-74.374745, 40.893264],\n          [-74.374127, 40.898616],\n          [-74.374902, 40.901059],\n          [-74.378188, 40.901844],\n          [-74.378509, 40.900151],\n          [-74.383293, 40.899546],\n          [-74.384277, 40.902503],\n          [-74.383622, 40.902564],\n          [-74.383706, 40.903409],\n          [-74.384609, 40.903338],\n          [-74.387159, 40.909755],\n          [-74.390966, 40.909837],\n          [-74.392542, 40.913437],\n          [-74.393499, 40.91741],\n          [-74.390968, 40.919447],\n          [-74.394272, 40.926232],\n          [-74.388795, 40.930394],\n          [-74.389726, 40.930836],\n          [-74.38883, 40.931932],\n          [-74.389795, 40.931872],\n          [-74.390036, 40.932698],\n          [-74.374793, 40.940133],\n          [-74.374929, 40.941559],\n          [-74.37655, 40.942487],\n          [-74.381516, 40.942622],\n          [-74.380974, 40.946451],\n          [-74.374397, 40.954036],\n          [-74.367575, 40.955078],\n          [-74.365156, 40.954335],\n          [-74.363667, 40.955394],\n          [-74.364141, 40.954467],\n          [-74.362181, 40.951667],\n          [-74.36267, 40.944287],\n          [-74.360703, 40.940588],\n          [-74.361356, 40.939299]\n        ]\n      ]\n    }\n  },\n  5: {\n    type: 'Feature',\n    properties: {index: 5},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.087219, 40.799287],\n          [-74.105261, 40.838083],\n          [-74.113924, 40.842218],\n          [-74.121404, 40.847184],\n          [-74.119291, 40.851674],\n          [-74.119462, 40.85335],\n          [-74.11538, 40.850927],\n          [-74.109, 40.845092],\n          [-74.078747, 40.827548],\n          [-74.070783, 40.822082],\n          [-74.066115, 40.817041],\n          [-74.061984, 40.810171],\n          [-74.060685, 40.8057],\n          [-74.065135, 40.80302],\n          [-74.068585, 40.7986],\n          [-74.071399, 40.797241],\n          [-74.077, 40.796379],\n          [-74.077787, 40.792796],\n          [-74.081794, 40.787994],\n          [-74.087219, 40.799287]\n        ]\n      ]\n    }\n  },\n  6: {\n    type: 'Feature',\n    properties: {index: 6},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.347067, 40.691722],\n          [-74.36039, 40.699821],\n          [-74.352171, 40.70148],\n          [-74.342221, 40.707846],\n          [-74.333594, 40.716901],\n          [-74.330619, 40.716055],\n          [-74.332088, 40.720176],\n          [-74.325596, 40.717075],\n          [-74.321408, 40.719072],\n          [-74.315991, 40.719199],\n          [-74.311691, 40.715601],\n          [-74.309791, 40.715501],\n          [-74.308793, 40.714508],\n          [-74.302092, 40.713301],\n          [-74.299496, 40.713889],\n          [-74.29869, 40.71479],\n          [-74.296151, 40.712434],\n          [-74.297212, 40.710298],\n          [-74.301677, 40.707498],\n          [-74.301078, 40.704678],\n          [-74.306692, 40.703802],\n          [-74.309119, 40.702625],\n          [-74.310906, 40.700392],\n          [-74.310666, 40.699622],\n          [-74.307393, 40.698801],\n          [-74.307893, 40.695999],\n          [-74.30664, 40.694304],\n          [-74.308655, 40.691171],\n          [-74.312226, 40.689892],\n          [-74.310772, 40.683794],\n          [-74.307292, 40.682506],\n          [-74.308989, 40.680407],\n          [-74.307753, 40.680292],\n          [-74.310392, 40.678402],\n          [-74.322575, 40.677433],\n          [-74.347067, 40.691722]\n        ]\n      ]\n    }\n  },\n  7: {\n    type: 'Feature',\n    properties: {index: 7},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.184345, 40.727604],\n          [-74.183145, 40.730677],\n          [-74.181958, 40.730397],\n          [-74.181105, 40.732722],\n          [-74.181977, 40.732938],\n          [-74.181504, 40.734135],\n          [-74.18328, 40.734558],\n          [-74.182741, 40.735778],\n          [-74.179407, 40.737014],\n          [-74.182582, 40.73798],\n          [-74.181787, 40.739633],\n          [-74.179055, 40.737705],\n          [-74.178029, 40.739555],\n          [-74.179295, 40.739935],\n          [-74.17785, 40.742469],\n          [-74.18066, 40.74342],\n          [-74.179821, 40.744703],\n          [-74.177068, 40.743847],\n          [-74.175752, 40.745597],\n          [-74.17646, 40.745849],\n          [-74.175989, 40.747417],\n          [-74.173784, 40.746929],\n          [-74.173566, 40.747955],\n          [-74.170978, 40.747424],\n          [-74.167568, 40.748054],\n          [-74.167484, 40.747446],\n          [-74.165976, 40.748091],\n          [-74.165987, 40.745199],\n          [-74.163788, 40.739201],\n          [-74.161669, 40.735781],\n          [-74.166099, 40.732923],\n          [-74.175517, 40.72346],\n          [-74.176772, 40.723054],\n          [-74.17736, 40.723261],\n          [-74.176965, 40.723961],\n          [-74.180517, 40.724365],\n          [-74.179633, 40.725829],\n          [-74.180319, 40.726113],\n          [-74.184236, 40.724266],\n          [-74.183892, 40.72506],\n          [-74.185205, 40.725405],\n          [-74.184345, 40.727604]\n        ]\n      ]\n    }\n  },\n  8: {\n    type: 'Feature',\n    properties: {index: 8},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.1404, 40.805639],\n          [-74.146391, 40.806291],\n          [-74.149012, 40.80766],\n          [-74.149521, 40.806858],\n          [-74.148617, 40.806521],\n          [-74.175644, 40.809531],\n          [-74.183836, 40.812225],\n          [-74.179511, 40.816664],\n          [-74.178999, 40.818771],\n          [-74.176443, 40.821854],\n          [-74.174992, 40.821802],\n          [-74.162628, 40.837869],\n          [-74.151923, 40.832074],\n          [-74.150888, 40.833143],\n          [-74.148818, 40.830916],\n          [-74.149242, 40.830537],\n          [-74.130031, 40.819962],\n          [-74.136487, 40.8182],\n          [-74.137287, 40.8173],\n          [-74.138787, 40.8074],\n          [-74.1404, 40.805639]\n        ]\n      ]\n    }\n  },\n  9: {\n    type: 'Feature',\n    properties: {index: 9},\n    geometry: {\n      type: 'MultiPolygon',\n      coordinates: [\n        [\n          [\n            [-74.566993, 41.087294],\n            [-74.564908, 41.089339],\n            [-74.56559, 41.090507],\n            [-74.564446, 41.091931],\n            [-74.562675, 41.093801],\n            [-74.560495, 41.094523],\n            [-74.559465, 41.093899],\n            [-74.558011, 41.094312],\n            [-74.556055, 41.091781],\n            [-74.554015, 41.092675],\n            [-74.552353, 41.091647],\n            [-74.552998, 41.09013],\n            [-74.556709, 41.087409],\n            [-74.558078, 41.085348],\n            [-74.56136, 41.08293],\n            [-74.563205, 41.082847],\n            [-74.565111, 41.081667],\n            [-74.567341, 41.082366],\n            [-74.566524, 41.085226],\n            [-74.566993, 41.087294]\n          ]\n        ],\n        [\n          [\n            [-74.593264, 41.088526],\n            [-74.601504, 41.094096],\n            [-74.602269, 41.092905],\n            [-74.606875, 41.096689],\n            [-74.606957, 41.097715],\n            [-74.638551, 41.118611],\n            [-74.634186, 41.132604],\n            [-74.633166, 41.133324],\n            [-74.629712, 41.131934],\n            [-74.625246, 41.136424],\n            [-74.620677, 41.139411],\n            [-74.613785, 41.139652],\n            [-74.6101, 41.137965],\n            [-74.610517, 41.136736],\n            [-74.606433, 41.135423],\n            [-74.60466, 41.136534],\n            [-74.598719, 41.137644],\n            [-74.595925, 41.139251],\n            [-74.593358, 41.139231],\n            [-74.579959, 41.144635],\n            [-74.576893, 41.136939],\n            [-74.573312, 41.136907],\n            [-74.571246, 41.137717],\n            [-74.569941, 41.136878],\n            [-74.575411, 41.101456],\n            [-74.577128, 41.101248],\n            [-74.574449, 41.101263],\n            [-74.567456, 41.103641],\n            [-74.562238, 41.103369],\n            [-74.55789, 41.106705],\n            [-74.552862, 41.10746],\n            [-74.557344, 41.105958],\n            [-74.56193, 41.102739],\n            [-74.569487, 41.100323],\n            [-74.575935, 41.095058],\n            [-74.578362, 41.09142],\n            [-74.583558, 41.087448],\n            [-74.585763, 41.083491],\n            [-74.593264, 41.088526]\n          ]\n        ]\n      ]\n    }\n  },\n  10: {\n    type: 'Feature',\n    properties: {index: 10},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.158531, 41.025141],\n          [-74.155604, 41.02585],\n          [-74.155079, 41.0217],\n          [-74.147648, 41.022058],\n          [-74.149441, 41.013336],\n          [-74.151076, 41.010084],\n          [-74.142115, 41.008711],\n          [-74.141446, 41.007704],\n          [-74.137536, 41.007495],\n          [-74.141401, 41.005903],\n          [-74.145988, 41.001919],\n          [-74.155782, 41.001404],\n          [-74.157154, 40.998933],\n          [-74.153255, 40.996719],\n          [-74.151282, 40.993204],\n          [-74.153874, 40.98999],\n          [-74.149887, 40.987538],\n          [-74.142812, 40.986407],\n          [-74.141437, 40.984207],\n          [-74.14121, 40.982761],\n          [-74.142619, 40.981621],\n          [-74.149088, 40.971137],\n          [-74.17021, 40.983865],\n          [-74.170988, 40.976997],\n          [-74.191835, 40.978688],\n          [-74.189987, 40.992282],\n          [-74.189546, 41.003824],\n          [-74.18806, 41.00691],\n          [-74.187244, 41.01685],\n          [-74.16758, 41.023781],\n          [-74.163392, 41.022212],\n          [-74.158531, 41.025141]\n        ]\n      ]\n    }\n  },\n  11: {\n    type: 'Feature',\n    properties: {index: 11},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.164572, 41.104182],\n          [-74.166137, 41.104468],\n          [-74.163755, 41.105993],\n          [-74.16128, 41.105606],\n          [-74.162067, 41.104822],\n          [-74.161454, 41.103924],\n          [-74.162067, 41.102889],\n          [-74.163932, 41.102848],\n          [-74.164572, 41.104182]\n        ]\n      ]\n    }\n  },\n  12: {\n    type: 'Feature',\n    properties: {index: 12},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.151143, 40.97231],\n          [-74.141048, 40.966348],\n          [-74.148059, 40.94159],\n          [-74.149587, 40.941398],\n          [-74.153938, 40.937972],\n          [-74.16151, 40.937498],\n          [-74.167127, 40.934519],\n          [-74.171588, 40.941998],\n          [-74.169018, 40.947413],\n          [-74.167486, 40.95733],\n          [-74.16921, 40.969444],\n          [-74.170814, 40.970178],\n          [-74.17021, 40.983865],\n          [-74.151143, 40.97231]\n        ]\n      ]\n    }\n  },\n  13: {\n    type: 'Feature',\n    properties: {index: 13},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-73.99863, 40.866791],\n          [-73.992942, 40.873032],\n          [-73.994052, 40.874607],\n          [-73.993799, 40.876601],\n          [-73.983367, 40.875127],\n          [-73.978515, 40.870534],\n          [-73.97569, 40.868903],\n          [-73.977687, 40.8605],\n          [-73.979382, 40.860799],\n          [-73.980514, 40.857975],\n          [-73.987985, 40.850884],\n          [-73.997417, 40.856176],\n          [-73.99829, 40.855105],\n          [-74.006059, 40.85697],\n          [-74.002723, 40.864981],\n          [-73.99863, 40.866791]\n        ]\n      ]\n    }\n  },\n  14: {\n    type: 'Feature',\n    properties: {index: 14},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-73.99002, 40.887038],\n          [-73.994572, 40.888663],\n          [-73.98955, 40.900569],\n          [-73.989916, 40.906341],\n          [-73.989286, 40.911479],\n          [-73.988717, 40.911674],\n          [-73.972202, 40.912892],\n          [-73.971972, 40.913617],\n          [-73.949229, 40.900817],\n          [-73.959169, 40.885014],\n          [-73.95583, 40.883117],\n          [-73.957173, 40.881425],\n          [-73.957839, 40.882492],\n          [-73.958321, 40.882085],\n          [-73.957902, 40.880512],\n          [-73.969825, 40.865568],\n          [-73.978515, 40.870534],\n          [-73.983367, 40.875127],\n          [-73.993933, 40.876539],\n          [-73.992783, 40.879099],\n          [-73.98739, 40.886003],\n          [-73.99002, 40.887038]\n        ]\n      ]\n    }\n  },\n  15: {\n    type: 'Feature',\n    properties: {index: 15},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.04531, 40.854103],\n          [-74.044779, 40.853794],\n          [-74.044816, 40.85746],\n          [-74.043745, 40.857547],\n          [-74.043757, 40.85919],\n          [-74.032018, 40.858647],\n          [-74.028884, 40.855216],\n          [-74.028418, 40.852282],\n          [-74.029969, 40.847658],\n          [-74.029185, 40.843427],\n          [-74.030484, 40.8389],\n          [-74.029084, 40.8344],\n          [-74.030084, 40.832],\n          [-74.035384, 40.8288],\n          [-74.036918, 40.82855],\n          [-74.040269, 40.830288],\n          [-74.03909, 40.830425],\n          [-74.039014, 40.832249],\n          [-74.040669, 40.832316],\n          [-74.039777, 40.834102],\n          [-74.04154, 40.833922],\n          [-74.041217, 40.834652],\n          [-74.042195, 40.834792],\n          [-74.042262, 40.836937],\n          [-74.043267, 40.838359],\n          [-74.042435, 40.840983],\n          [-74.043045, 40.843654],\n          [-74.045484, 40.843596],\n          [-74.045798, 40.846145],\n          [-74.051577, 40.847894],\n          [-74.052563, 40.848853],\n          [-74.052518, 40.850817],\n          [-74.053981, 40.853863],\n          [-74.051677, 40.857692],\n          [-74.04531, 40.854103]\n        ]\n      ]\n    }\n  },\n  16: {\n    type: 'Feature',\n    properties: {index: 16},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.033649, 41.039572],\n          [-74.03703, 41.043696],\n          [-74.051378, 41.049278],\n          [-74.063774, 41.044631],\n          [-74.069675, 41.041541],\n          [-74.071014, 41.046613],\n          [-74.072234, 41.046686],\n          [-74.072452, 41.048935],\n          [-74.079369, 41.048492],\n          [-74.078814, 41.055144],\n          [-74.07699, 41.061365],\n          [-74.071915, 41.072883],\n          [-74.012583, 41.046396],\n          [-74.013197, 41.041442],\n          [-74.012452, 41.038698],\n          [-74.028831, 41.036591],\n          [-74.030807, 41.037199],\n          [-74.029374, 41.039144],\n          [-74.029546, 41.040261],\n          [-74.031631, 41.037894],\n          [-74.033649, 41.039572]\n        ]\n      ]\n    }\n  },\n  17: {\n    type: 'Feature',\n    properties: {index: 17},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.424256, 41.026137],\n          [-74.423728, 41.027596],\n          [-74.421458, 41.02854],\n          [-74.417803, 41.028693],\n          [-74.414744, 41.027759],\n          [-74.41085, 41.026199],\n          [-74.40977, 41.024847],\n          [-74.40592, 41.024094],\n          [-74.403388, 41.021833],\n          [-74.402254, 41.019233],\n          [-74.399391, 41.017289],\n          [-74.390997, 41.015688],\n          [-74.389678, 41.014254],\n          [-74.389303, 41.012431],\n          [-74.388223, 41.012046],\n          [-74.386295, 41.011688],\n          [-74.382247, 41.013959],\n          [-74.379565, 41.014282],\n          [-74.371373, 41.011399],\n          [-74.367459, 41.011994],\n          [-74.364124, 41.011293],\n          [-74.36235, 41.01383],\n          [-74.355441, 41.013943],\n          [-74.351481, 41.01099],\n          [-74.351029, 41.007174],\n          [-74.352168, 41.005987],\n          [-74.350926, 41.004335],\n          [-74.34612, 41.0046],\n          [-74.342264, 41.006904],\n          [-74.340618, 41.005845],\n          [-74.336429, 41.007529],\n          [-74.334805, 41.006464],\n          [-74.334456, 41.003592],\n          [-74.329578, 41.002512],\n          [-74.328411, 41.001419],\n          [-74.330477, 40.997535],\n          [-74.329601, 40.995845],\n          [-74.329855, 40.991939],\n          [-74.332185, 40.990168],\n          [-74.331401, 40.988262],\n          [-74.330459, 40.966053],\n          [-74.336465, 40.960102],\n          [-74.340902, 40.953453],\n          [-74.339763, 40.95213],\n          [-74.336043, 40.951261],\n          [-74.335478, 40.949418],\n          [-74.341094, 40.946298],\n          [-74.364296, 40.957929],\n          [-74.397898, 40.959829],\n          [-74.410065, 40.958717],\n          [-74.428155, 40.958911],\n          [-74.428555, 40.958226],\n          [-74.428574, 40.959286],\n          [-74.431175, 40.959368],\n          [-74.430749, 40.984608],\n          [-74.432097, 40.984008],\n          [-74.432025, 40.992541],\n          [-74.430602, 40.993339],\n          [-74.430178, 41.018128],\n          [-74.429211, 41.017679],\n          [-74.425375, 41.019271],\n          [-74.426013, 41.020166],\n          [-74.423504, 41.025734],\n          [-74.424256, 41.026137]\n        ]\n      ]\n    }\n  },\n  18: {\n    type: 'Feature',\n    properties: {index: 18},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.105049, 40.269701],\n          [-74.103488, 40.272418],\n          [-74.106018, 40.274773],\n          [-74.101607, 40.280574],\n          [-74.100748, 40.279887],\n          [-74.100161, 40.280996],\n          [-74.098821, 40.28123],\n          [-74.082891, 40.274113],\n          [-74.082201, 40.270348],\n          [-74.072487, 40.275582],\n          [-74.071041, 40.274346],\n          [-74.070161, 40.270832],\n          [-74.069033, 40.270664],\n          [-74.067579, 40.27089],\n          [-74.065924, 40.272621],\n          [-74.061569, 40.273615],\n          [-74.051627, 40.272795],\n          [-74.049539, 40.271935],\n          [-74.043385, 40.273422],\n          [-74.039322, 40.25183],\n          [-74.019689, 40.253485],\n          [-74.019866, 40.251435],\n          [-74.01764, 40.249613],\n          [-74.018373, 40.247723],\n          [-74.020904, 40.246901],\n          [-74.022762, 40.240874],\n          [-74.015502, 40.23916],\n          [-74.015218, 40.237693],\n          [-74.01292, 40.236238],\n          [-74.008684, 40.235364],\n          [-74.009389, 40.232973],\n          [-74.007068, 40.232283],\n          [-74.006199, 40.231174],\n          [-73.994705, 40.230067],\n          [-73.994681, 40.224512],\n          [-73.998481, 40.216812],\n          [-74.002556, 40.21709],\n          [-74.018647, 40.213377],\n          [-74.024998, 40.213125],\n          [-74.025508, 40.219099],\n          [-74.02618, 40.219151],\n          [-74.025518, 40.219641],\n          [-74.025516, 40.223269],\n          [-74.077409, 40.234395],\n          [-74.076503, 40.240881],\n          [-74.078391, 40.240398],\n          [-74.079541, 40.242058],\n          [-74.07912, 40.243797],\n          [-74.075526, 40.245243],\n          [-74.073864, 40.257239],\n          [-74.075693, 40.259189],\n          [-74.081411, 40.258528],\n          [-74.082119, 40.251457],\n          [-74.08462, 40.252719],\n          [-74.08778, 40.250225],\n          [-74.095925, 40.249682],\n          [-74.096515, 40.245582],\n          [-74.097686, 40.245471],\n          [-74.110838, 40.252683],\n          [-74.10372, 40.255887],\n          [-74.106884, 40.259944],\n          [-74.108802, 40.260613],\n          [-74.108582, 40.264424],\n          [-74.106872, 40.265512],\n          [-74.105049, 40.269701]\n        ]\n      ]\n    }\n  },\n  19: {\n    type: 'Feature',\n    properties: {index: 19},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.293808, 40.633387],\n          [-74.291091, 40.636102],\n          [-74.290991, 40.638102],\n          [-74.28982, 40.639951],\n          [-74.287325, 40.639999],\n          [-74.281687, 40.643381],\n          [-74.282183, 40.644572],\n          [-74.280691, 40.645712],\n          [-74.281494, 40.647271],\n          [-74.281488, 40.650145],\n          [-74.264342, 40.637244],\n          [-74.258956, 40.643217],\n          [-74.251701, 40.645117],\n          [-74.248772, 40.647293],\n          [-74.241341, 40.649791],\n          [-74.232513, 40.654927],\n          [-74.200469, 40.63263],\n          [-74.202247, 40.630903],\n          [-74.203737, 40.624227],\n          [-74.201864, 40.618557],\n          [-74.203128, 40.614109],\n          [-74.203813, 40.605961],\n          [-74.199408, 40.600201],\n          [-74.19952, 40.597539],\n          [-74.203688, 40.592691],\n          [-74.210741, 40.597032],\n          [-74.212032, 40.598372],\n          [-74.21287, 40.602035],\n          [-74.215101, 40.604074],\n          [-74.220216, 40.600555],\n          [-74.231748, 40.598881],\n          [-74.234067, 40.600345],\n          [-74.234767, 40.601945],\n          [-74.236609, 40.60272],\n          [-74.242593, 40.599754],\n          [-74.25381, 40.600989],\n          [-74.255822, 40.602194],\n          [-74.258114, 40.60225],\n          [-74.255608, 40.607618],\n          [-74.278844, 40.629365],\n          [-74.285491, 40.635102],\n          [-74.290491, 40.632903],\n          [-74.292494, 40.630803],\n          [-74.296379, 40.628711],\n          [-74.297134, 40.62933],\n          [-74.293808, 40.633387]\n        ]\n      ]\n    }\n  },\n  20: {\n    type: 'Feature',\n    properties: {index: 20},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.013788, 40.249179],\n          [-74.01764, 40.249613],\n          [-74.019302, 40.250582],\n          [-74.019689, 40.253485],\n          [-74.012618, 40.253141],\n          [-74.011201, 40.257],\n          [-74.003013, 40.256831],\n          [-74.002888, 40.258313],\n          [-74.000094, 40.257407],\n          [-74.0002, 40.26345],\n          [-73.992354, 40.262405],\n          [-73.986681, 40.260211],\n          [-73.992881, 40.237266],\n          [-74.008001, 40.239851],\n          [-74.008447, 40.242576],\n          [-74.009543, 40.243782],\n          [-74.009733, 40.248425],\n          [-74.013788, 40.249179]\n        ]\n      ]\n    }\n  },\n  21: {\n    type: 'Feature',\n    properties: {index: 21},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.680775, 41.141961],\n          [-74.694975, 41.131348],\n          [-74.69885, 41.135537],\n          [-74.701359, 41.136794],\n          [-74.703406, 41.13429],\n          [-74.706486, 41.126963],\n          [-74.713138, 41.120327],\n          [-74.711489, 41.118891],\n          [-74.720408, 41.112296],\n          [-74.746949, 41.119559],\n          [-74.745694, 41.120599],\n          [-74.745157, 41.124768],\n          [-74.741896, 41.12892],\n          [-74.737953, 41.129147],\n          [-74.737676, 41.128104],\n          [-74.735155, 41.126436],\n          [-74.728554, 41.128919],\n          [-74.733121, 41.130496],\n          [-74.735764, 41.13238],\n          [-74.739, 41.137338],\n          [-74.733352, 41.138875],\n          [-74.731341, 41.143627],\n          [-74.721084, 41.150262],\n          [-74.716727, 41.151405],\n          [-74.715427, 41.151356],\n          [-74.712447, 41.147925],\n          [-74.705516, 41.147476],\n          [-74.704311, 41.147987],\n          [-74.703071, 41.146962],\n          [-74.694816, 41.157602],\n          [-74.690193, 41.159813],\n          [-74.684027, 41.164539],\n          [-74.681418, 41.165103],\n          [-74.67785, 41.163684],\n          [-74.674644, 41.163581],\n          [-74.67129, 41.168764],\n          [-74.667272, 41.172585],\n          [-74.660801, 41.166128],\n          [-74.66382, 41.163773],\n          [-74.663566, 41.162153],\n          [-74.66462, 41.159589],\n          [-74.663429, 41.157321],\n          [-74.664679, 41.155926],\n          [-74.670442, 41.154203],\n          [-74.668458, 41.152808],\n          [-74.668539, 41.151161],\n          [-74.680775, 41.141961]\n        ]\n      ]\n    }\n  },\n  22: {\n    type: 'Feature',\n    properties: {index: 22},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.39366, 40.830496],\n          [-74.405638, 40.838928],\n          [-74.422925, 40.8447],\n          [-74.451083, 40.838346],\n          [-74.463181, 40.843465],\n          [-74.467565, 40.846525],\n          [-74.465964, 40.848547],\n          [-74.464617, 40.848757],\n          [-74.46076, 40.846768],\n          [-74.454244, 40.85264],\n          [-74.452162, 40.852242],\n          [-74.449605, 40.854737],\n          [-74.45126, 40.859271],\n          [-74.449707, 40.860541],\n          [-74.451701, 40.860499],\n          [-74.461277, 40.86634],\n          [-74.46095, 40.867844],\n          [-74.449226, 40.866302],\n          [-74.443807, 40.866626],\n          [-74.447036, 40.867209],\n          [-74.439733, 40.866511],\n          [-74.43882, 40.870782],\n          [-74.440454, 40.869142],\n          [-74.445241, 40.867982],\n          [-74.450476, 40.869008],\n          [-74.450185, 40.872275],\n          [-74.44921, 40.874111],\n          [-74.451584, 40.873513],\n          [-74.451567, 40.876515],\n          [-74.447705, 40.876866],\n          [-74.448803, 40.879174],\n          [-74.450256, 40.879959],\n          [-74.433632, 40.874255],\n          [-74.433403, 40.877506],\n          [-74.43068, 40.881706],\n          [-74.425693, 40.882707],\n          [-74.424699, 40.879771],\n          [-74.425187, 40.878315],\n          [-74.419916, 40.879254],\n          [-74.420486, 40.875395],\n          [-74.419362, 40.871206],\n          [-74.414728, 40.868737],\n          [-74.410267, 40.877077],\n          [-74.405039, 40.875898],\n          [-74.404027, 40.878969],\n          [-74.4017, 40.881459],\n          [-74.396071, 40.881961],\n          [-74.392244, 40.873568],\n          [-74.39051, 40.874087],\n          [-74.390446, 40.87614],\n          [-74.384095, 40.876199],\n          [-74.383996, 40.875199],\n          [-74.380159, 40.876783],\n          [-74.376464, 40.876885],\n          [-74.376099, 40.875662],\n          [-74.375014, 40.876373],\n          [-74.372986, 40.875671],\n          [-74.371975, 40.870267],\n          [-74.36966, 40.871168],\n          [-74.368983, 40.869851],\n          [-74.369611, 40.869488],\n          [-74.365509, 40.869788],\n          [-74.365091, 40.868735],\n          [-74.366462, 40.867373],\n          [-74.366416, 40.864756],\n          [-74.36514, 40.866106],\n          [-74.362694, 40.863887],\n          [-74.358996, 40.863608],\n          [-74.354787, 40.86599],\n          [-74.35609, 40.867062],\n          [-74.358298, 40.866561],\n          [-74.361219, 40.867003],\n          [-74.361668, 40.867421],\n          [-74.357831, 40.866738],\n          [-74.355927, 40.867299],\n          [-74.353829, 40.86556],\n          [-74.352328, 40.866215],\n          [-74.349994, 40.864479],\n          [-74.349303, 40.86285],\n          [-74.348177, 40.862628],\n          [-74.34716, 40.859279],\n          [-74.347658, 40.856795],\n          [-74.346489, 40.856411],\n          [-74.345626, 40.857625],\n          [-74.344092, 40.856842],\n          [-74.340904, 40.852353],\n          [-74.341387, 40.85158],\n          [-74.344485, 40.850715],\n          [-74.341838, 40.849039],\n          [-74.341288, 40.847784],\n          [-74.341951, 40.846224],\n          [-74.35252, 40.844081],\n          [-74.354781, 40.840973],\n          [-74.360222, 40.840871],\n          [-74.367495, 40.836199],\n          [-74.371095, 40.835699],\n          [-74.379803, 40.828479],\n          [-74.380291, 40.830042],\n          [-74.381487, 40.828478],\n          [-74.380893, 40.827515],\n          [-74.383959, 40.824991],\n          [-74.388727, 40.825989],\n          [-74.39366, 40.830496]\n        ]\n      ]\n    }\n  },\n  23: {\n    type: 'Feature',\n    properties: {index: 23},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-75.097586, 40.843042],\n          [-75.095784, 40.847082],\n          [-75.090962, 40.849187],\n          [-75.076684, 40.849875],\n          [-75.07083, 40.847392],\n          [-75.067655, 40.847329],\n          [-75.064328, 40.848338],\n          [-75.063343, 40.851499],\n          [-75.060491, 40.85302],\n          [-75.059239, 40.856102],\n          [-75.053294, 40.8599],\n          [-75.051692, 40.863363],\n          [-75.050839, 40.868067],\n          [-75.053664, 40.87366],\n          [-75.058655, 40.877654],\n          [-75.062073, 40.882188],\n          [-75.060998, 40.882237],\n          [-75.060556, 40.883385],\n          [-75.059124, 40.884072],\n          [-75.060651, 40.889189],\n          [-75.064166, 40.893064],\n          [-75.063612, 40.895385],\n          [-75.066827, 40.895434],\n          [-75.067646, 40.896657],\n          [-75.067452, 40.899412],\n          [-75.071768, 40.898989],\n          [-75.072097, 40.898253],\n          [-75.073515, 40.900408],\n          [-75.07391, 40.899656],\n          [-75.075231, 40.899947],\n          [-75.07609, 40.907039],\n          [-75.07929, 40.913886],\n          [-75.095528, 40.924148],\n          [-75.104809, 40.935247],\n          [-75.106148, 40.93967],\n          [-75.111689, 40.948127],\n          [-75.117767, 40.953013],\n          [-75.119116, 40.958161],\n          [-75.118076, 40.959633],\n          [-75.118346, 40.963676],\n          [-75.120435, 40.968302],\n          [-75.122603, 40.970152],\n          [-75.130166, 40.968961],\n          [-75.132622, 40.970005],\n          [-75.134701, 40.972038],\n          [-75.135531, 40.973803],\n          [-75.135521, 40.976865],\n          [-75.132522, 40.981235],\n          [-75.131096, 40.990464],\n          [-75.127196, 40.993954],\n          [-75.110595, 41.002174],\n          [-75.109113, 41.004102],\n          [-75.095556, 41.008874],\n          [-75.088596, 41.015024],\n          [-75.081101, 41.016838],\n          [-75.074999, 41.01713],\n          [-75.069277, 41.019348],\n          [-75.068369, 41.018018],\n          [-75.037857, 41.030485],\n          [-75.029696, 41.035357],\n          [-75.027882, 41.037279],\n          [-75.024165, 41.038536],\n          [-75.017392, 41.042515],\n          [-75.015185, 41.04423],\n          [-75.009346, 41.051893],\n          [-75.003538, 41.057084],\n          [-75.000477, 41.058017],\n          [-74.998034, 41.057699],\n          [-74.995569, 41.058848],\n          [-74.992703, 41.058564],\n          [-74.992174, 41.05969],\n          [-74.984424, 41.064889],\n          [-74.984063, 41.062832],\n          [-74.980707, 41.061781],\n          [-74.964688, 41.063446],\n          [-74.963232, 41.060643],\n          [-74.964228, 41.057935],\n          [-74.961406, 41.055613],\n          [-74.96458, 41.051797],\n          [-74.99578, 41.033212],\n          [-75.002831, 41.030399],\n          [-75.003773, 41.031045],\n          [-75.004225, 41.032144],\n          [-75.002834, 41.033539],\n          [-74.998731, 41.035666],\n          [-74.998954, 41.038437],\n          [-74.998147, 41.038507],\n          [-74.998255, 41.037117],\n          [-74.997435, 41.037002],\n          [-74.995356, 41.040166],\n          [-74.991659, 41.042442],\n          [-74.994705, 41.041671],\n          [-75.004298, 41.035416],\n          [-75.005265, 41.031919],\n          [-75.003411, 41.030082],\n          [-75.016649, 41.022886],\n          [-75.021761, 41.020728],\n          [-75.036997, 41.017017],\n          [-75.04378, 41.014322],\n          [-75.03456, 41.008186],\n          [-75.034725, 41.004652],\n          [-75.033729, 41.002569],\n          [-75.034213, 41.001403],\n          [-75.032281, 40.999595],\n          [-75.032341, 40.996951],\n          [-75.02898, 40.99561],\n          [-75.011902, 40.998459],\n          [-75.008874, 40.99009],\n          [-75.010378, 40.985846],\n          [-75.010075, 40.984303],\n          [-75.002953, 40.981862],\n          [-75.005732, 40.980256],\n          [-75.021928, 40.975764],\n          [-75.02123, 40.970357],\n          [-75.024322, 40.969825],\n          [-75.021013, 40.968997],\n          [-75.020256, 40.963611],\n          [-75.018195, 40.964426],\n          [-75.019502, 40.961433],\n          [-75.018391, 40.959758],\n          [-75.038299, 40.952908],\n          [-75.03083, 40.946719],\n          [-75.029307, 40.946706],\n          [-75.020847, 40.940258],\n          [-75.019403, 40.941591],\n          [-75.019535, 40.939241],\n          [-75.021033, 40.939612],\n          [-75.022159, 40.938123],\n          [-75.015041, 40.928458],\n          [-75.027891, 40.920693],\n          [-75.02861, 40.915329],\n          [-75.031276, 40.912284],\n          [-75.024568, 40.908851],\n          [-75.0223, 40.905808],\n          [-75.03156, 40.896317],\n          [-75.030145, 40.89056],\n          [-75.024001, 40.891451],\n          [-75.022183, 40.892446],\n          [-75.020636, 40.891426],\n          [-75.017441, 40.891496],\n          [-75.01407, 40.890293],\n          [-75.008894, 40.885907],\n          [-75.010391, 40.884111],\n          [-75.014797, 40.882787],\n          [-75.019161, 40.881981],\n          [-75.024044, 40.883433],\n          [-75.025249, 40.882262],\n          [-75.02744, 40.88172],\n          [-75.021561, 40.875098],\n          [-75.014017, 40.868236],\n          [-75.016147, 40.867869],\n          [-75.016559, 40.868533],\n          [-75.020503, 40.866673],\n          [-75.021161, 40.867805],\n          [-75.022549, 40.86763],\n          [-75.023738, 40.863747],\n          [-75.025591, 40.860958],\n          [-75.02513, 40.858498],\n          [-75.024052, 40.857718],\n          [-75.028967, 40.856352],\n          [-75.030658, 40.857634],\n          [-75.032149, 40.85736],\n          [-75.029716, 40.863273],\n          [-75.031074, 40.869309],\n          [-75.035994, 40.868633],\n          [-75.051176, 40.860567],\n          [-75.049486, 40.860618],\n          [-75.055534, 40.855224],\n          [-75.057455, 40.852435],\n          [-75.057422, 40.848045],\n          [-75.062178, 40.847418],\n          [-75.061305, 40.843848],\n          [-75.064167, 40.842773],\n          [-75.06489, 40.843711],\n          [-75.068656, 40.844534],\n          [-75.070267, 40.842345],\n          [-75.070187, 40.837954],\n          [-75.07707, 40.840947],\n          [-75.088876, 40.832533],\n          [-75.096301, 40.83839],\n          [-75.097572, 40.840967],\n          [-75.097586, 40.843042]\n        ]\n      ]\n    }\n  },\n  24: {\n    type: 'Feature',\n    properties: {index: 24},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.624966, 40.729228],\n          [-74.623454, 40.730678],\n          [-74.622391, 40.735039],\n          [-74.620469, 40.736001],\n          [-74.627104, 40.737727],\n          [-74.628747, 40.736844],\n          [-74.630137, 40.735166],\n          [-74.631305, 40.734548],\n          [-74.632923, 40.732801],\n          [-74.633441, 40.732418],\n          [-74.633871, 40.732237],\n          [-74.632255, 40.733644],\n          [-74.630508, 40.736973],\n          [-74.631292, 40.738187],\n          [-74.629319, 40.739683],\n          [-74.63127, 40.741111],\n          [-74.555922, 40.75831],\n          [-74.555203, 40.756999],\n          [-74.553197, 40.756439],\n          [-74.551914, 40.754863],\n          [-74.550924, 40.751073],\n          [-74.552886, 40.749578],\n          [-74.548756, 40.746131],\n          [-74.549383, 40.745092],\n          [-74.548999, 40.740994],\n          [-74.550988, 40.741647],\n          [-74.551958, 40.739985],\n          [-74.55856, 40.736104],\n          [-74.556731, 40.73478],\n          [-74.560814, 40.731351],\n          [-74.563545, 40.730267],\n          [-74.564013, 40.728933],\n          [-74.552547, 40.722803],\n          [-74.555477, 40.721368],\n          [-74.5533, 40.719188],\n          [-74.556246, 40.717896],\n          [-74.567761, 40.706455],\n          [-74.607957, 40.694414],\n          [-74.609197, 40.696081],\n          [-74.611319, 40.695338],\n          [-74.61599, 40.695721],\n          [-74.618622, 40.695149],\n          [-74.618226, 40.704919],\n          [-74.620821, 40.710878],\n          [-74.620235, 40.712269],\n          [-74.615067, 40.715173],\n          [-74.612185, 40.719403],\n          [-74.612003, 40.72373],\n          [-74.61545, 40.723053],\n          [-74.620932, 40.716648],\n          [-74.623654, 40.715129],\n          [-74.625684, 40.715004],\n          [-74.634205, 40.722291],\n          [-74.631262, 40.72482],\n          [-74.626871, 40.725334],\n          [-74.626784, 40.727547],\n          [-74.625149, 40.72802],\n          [-74.625639, 40.728948],\n          [-74.624966, 40.729228]\n        ]\n      ]\n    }\n  },\n  25: {\n    type: 'Feature',\n    properties: {index: 25},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.409178, 40.741059],\n          [-74.405471, 40.73989],\n          [-74.402552, 40.742258],\n          [-74.402172, 40.741487],\n          [-74.391635, 40.75198],\n          [-74.389873, 40.751394],\n          [-74.383649, 40.754173],\n          [-74.38351, 40.755403],\n          [-74.380255, 40.755386],\n          [-74.381513, 40.754696],\n          [-74.373581, 40.75015],\n          [-74.370371, 40.752139],\n          [-74.363072, 40.750403],\n          [-74.367572, 40.748824],\n          [-74.364765, 40.746788],\n          [-74.366562, 40.743889],\n          [-74.371979, 40.742359],\n          [-74.37256, 40.741587],\n          [-74.370829, 40.735753],\n          [-74.371246, 40.734627],\n          [-74.374669, 40.733545],\n          [-74.377932, 40.734319],\n          [-74.379732, 40.73396],\n          [-74.380295, 40.7333],\n          [-74.379853, 40.732492],\n          [-74.377503, 40.730032],\n          [-74.384382, 40.724385],\n          [-74.388232, 40.726165],\n          [-74.39069, 40.725499],\n          [-74.390769, 40.718294],\n          [-74.39509, 40.717273],\n          [-74.402254, 40.718001],\n          [-74.404574, 40.715245],\n          [-74.40735, 40.713461],\n          [-74.408466, 40.710106],\n          [-74.410057, 40.709517],\n          [-74.412495, 40.7103],\n          [-74.413818, 40.707248],\n          [-74.4154, 40.705781],\n          [-74.426897, 40.701711],\n          [-74.430005, 40.697838],\n          [-74.429578, 40.697004],\n          [-74.430781, 40.693453],\n          [-74.434551, 40.693575],\n          [-74.437847, 40.6919],\n          [-74.43797, 40.690247],\n          [-74.444333, 40.687969],\n          [-74.449736, 40.718831],\n          [-74.447841, 40.718904],\n          [-74.437223, 40.723493],\n          [-74.426842, 40.729088],\n          [-74.424382, 40.734726],\n          [-74.426048, 40.733344],\n          [-74.427376, 40.733482],\n          [-74.426785, 40.735839],\n          [-74.429199, 40.735878],\n          [-74.430449, 40.734831],\n          [-74.430765, 40.735285],\n          [-74.428298, 40.736522],\n          [-74.430213, 40.7377],\n          [-74.43032, 40.736951],\n          [-74.432798, 40.736704],\n          [-74.434311, 40.735493],\n          [-74.437467, 40.735121],\n          [-74.437348, 40.73587],\n          [-74.439214, 40.73673],\n          [-74.44084, 40.73642],\n          [-74.439595, 40.736006],\n          [-74.440803, 40.734753],\n          [-74.441744, 40.735486],\n          [-74.441043, 40.736868],\n          [-74.442891, 40.735942],\n          [-74.444308, 40.738929],\n          [-74.438047, 40.741641],\n          [-74.432156, 40.746447],\n          [-74.434702, 40.747732],\n          [-74.438198, 40.752308],\n          [-74.436616, 40.752849],\n          [-74.435314, 40.752306],\n          [-74.436024, 40.751602],\n          [-74.433267, 40.750069],\n          [-74.432271, 40.751089],\n          [-74.428827, 40.749474],\n          [-74.430019, 40.746001],\n          [-74.41515, 40.742751],\n          [-74.409916, 40.740501],\n          [-74.409178, 40.741059]\n        ]\n      ]\n    }\n  },\n  26: {\n    type: 'Feature',\n    properties: {index: 26},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.746315, 40.699842],\n          [-74.738518, 40.708428],\n          [-74.73803, 40.711868],\n          [-74.734191, 40.71672],\n          [-74.729987, 40.717241],\n          [-74.732448, 40.720062],\n          [-74.724418, 40.719455],\n          [-74.720152, 40.720857],\n          [-74.721834, 40.718984],\n          [-74.720881, 40.716657],\n          [-74.71557, 40.714933],\n          [-74.715093, 40.713903],\n          [-74.717147, 40.714164],\n          [-74.716015, 40.710283],\n          [-74.717798, 40.709288],\n          [-74.714601, 40.703286],\n          [-74.714347, 40.699368],\n          [-74.721624, 40.690735],\n          [-74.722079, 40.688939],\n          [-74.72065, 40.687797],\n          [-74.72303, 40.68265],\n          [-74.725555, 40.683786],\n          [-74.729034, 40.68356],\n          [-74.730184, 40.682581],\n          [-74.732786, 40.683785],\n          [-74.730782, 40.686112],\n          [-74.73592, 40.688406],\n          [-74.732475, 40.692508],\n          [-74.733375, 40.693151],\n          [-74.734169, 40.699729],\n          [-74.744802, 40.697551],\n          [-74.746315, 40.699842]\n        ]\n      ]\n    }\n  },\n  27: {\n    type: 'Feature',\n    properties: {index: 27},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.96258, 40.908359],\n          [-74.959171, 40.909348],\n          [-74.958166, 40.910924],\n          [-74.955046, 40.911941],\n          [-74.952035, 40.914182],\n          [-74.947947, 40.912938],\n          [-74.945624, 40.911373],\n          [-74.945686, 40.909849],\n          [-74.944298, 40.909232],\n          [-74.940447, 40.911676],\n          [-74.93977, 40.916014],\n          [-74.93583, 40.915965],\n          [-74.93084, 40.917595],\n          [-74.927867, 40.906805],\n          [-74.887969, 40.933194],\n          [-74.887988, 40.931636],\n          [-74.886846, 40.929954],\n          [-74.882716, 40.930239],\n          [-74.874369, 40.932296],\n          [-74.873234, 40.934085],\n          [-74.86866, 40.936267],\n          [-74.866899, 40.933617],\n          [-74.859938, 40.93051],\n          [-74.852375, 40.930101],\n          [-74.848303, 40.933399],\n          [-74.847051, 40.933475],\n          [-74.844269, 40.930927],\n          [-74.844978, 40.927355],\n          [-74.840887, 40.923065],\n          [-74.840416, 40.921211],\n          [-74.84412, 40.919599],\n          [-74.842955, 40.919474],\n          [-74.844491, 40.91793],\n          [-74.844531, 40.915532],\n          [-74.859914, 40.909303],\n          [-74.860956, 40.904959],\n          [-74.862287, 40.903064],\n          [-74.858839, 40.900314],\n          [-74.858959, 40.899168],\n          [-74.848263, 40.892969],\n          [-74.851169, 40.889839],\n          [-74.85923, 40.885743],\n          [-74.859366, 40.887143],\n          [-74.860511, 40.887838],\n          [-74.863545, 40.883912],\n          [-74.867125, 40.884417],\n          [-74.872203, 40.882492],\n          [-74.872768, 40.883398],\n          [-74.876052, 40.884353],\n          [-74.882058, 40.883988],\n          [-74.882891, 40.882731],\n          [-74.887871, 40.879998],\n          [-74.889572, 40.87633],\n          [-74.894028, 40.872274],\n          [-74.89991, 40.869372],\n          [-74.903521, 40.865869],\n          [-74.905344, 40.866523],\n          [-74.903426, 40.864607],\n          [-74.903128, 40.86326],\n          [-74.900129, 40.862348],\n          [-74.896917, 40.858397],\n          [-74.894792, 40.857851],\n          [-74.894754, 40.856634],\n          [-74.893744, 40.8561],\n          [-74.894932, 40.856353],\n          [-74.895533, 40.851298],\n          [-74.907884, 40.855752],\n          [-74.915047, 40.852114],\n          [-74.924079, 40.851947],\n          [-74.929062, 40.848559],\n          [-74.934249, 40.85086],\n          [-74.933294, 40.852222],\n          [-74.935853, 40.852373],\n          [-74.941042, 40.846725],\n          [-74.94384, 40.846406],\n          [-74.950587, 40.842517],\n          [-74.951581, 40.839414],\n          [-74.956373, 40.834365],\n          [-74.971647, 40.842927],\n          [-74.971381, 40.843883],\n          [-74.968743, 40.844635],\n          [-74.960144, 40.855778],\n          [-74.963608, 40.855651],\n          [-74.963127, 40.858348],\n          [-74.964627, 40.85919],\n          [-74.96689, 40.85904],\n          [-74.968542, 40.860945],\n          [-74.97156, 40.860844],\n          [-74.968929, 40.864416],\n          [-74.969525, 40.86504],\n          [-74.971744, 40.861518],\n          [-74.972369, 40.86245],\n          [-74.9725, 40.861514],\n          [-74.973998, 40.861535],\n          [-74.974787, 40.860622],\n          [-74.978735, 40.87158],\n          [-74.979484, 40.87194],\n          [-74.97877, 40.873364],\n          [-74.979931, 40.874228],\n          [-74.976368, 40.876708],\n          [-74.982155, 40.879127],\n          [-74.980311, 40.880401],\n          [-74.980521, 40.881626],\n          [-74.976513, 40.883126],\n          [-74.978397, 40.88719],\n          [-74.976715, 40.890538],\n          [-74.9757, 40.883461],\n          [-74.977469, 40.879267],\n          [-74.97593, 40.878446],\n          [-74.976152, 40.879675],\n          [-74.974987, 40.881253],\n          [-74.969259, 40.884146],\n          [-74.963281, 40.885689],\n          [-74.957982, 40.890776],\n          [-74.957476, 40.893947],\n          [-74.961516, 40.894095],\n          [-74.95829, 40.900182],\n          [-74.962611, 40.906121],\n          [-74.959793, 40.907294],\n          [-74.96258, 40.908359]\n        ]\n      ]\n    }\n  },\n  28: {\n    type: 'Feature',\n    properties: {index: 28},\n    geometry: {\n      type: 'Polygon',\n      coordinates: [\n        [\n          [-74.261032, 40.832085],\n          [-74.259357, 40.836363],\n          [-74.257107, 40.838124],\n          [-74.256523, 40.841781],\n          [-74.251892, 40.849829],\n          [-74.2334, 40.844389],\n          [-74.237214, 40.836034],\n          [-74.227445, 40.83331],\n          [-74.227147, 40.836396],\n          [-74.22099, 40.834799],\n          [-74.225755, 40.827381],\n          [-74.224352, 40.827071],\n          [-74.227054, 40.824221],\n          [-74.227725, 40.824867],\n          [-74.229113, 40.822199],\n          [-74.229079, 40.820019],\n          [-74.232573, 40.814863],\n          [-74.250795, 40.824461],\n          [-74.264305, 40.825431],\n          [-74.261032, 40.832085]\n        ]\n      ]\n    }\n  }\n};\n\nconst mergedLayer1 = new GeojsonLayer({id: 'u7apppt'});\nmergedLayer1.config = {\n  dataId: 'a5ybmwl2d',\n  label: 'stroke by pop',\n  color: [221, 178, 124],\n  columns: {\n    geojson: {value: 'simplified_shape', fieldIdx: 2, optional: false},\n    id: {value: null, fieldIdx: -1, optional: true},\n    lat: {value: null, fieldIdx: -1, optional: true},\n    lng: {value: null, fieldIdx: -1, optional: true},\n    altitude: {value: null, fieldIdx: -1, optional: true},\n    sortBy: {value: null, fieldIdx: -1, optional: true}\n  },\n  columnMode: 'geojson',\n  isVisible: true,\n  isConfigActive: false,\n  highlightColor: [252, 242, 26, 255],\n  colorField: null,\n  colorScale: 'quantile',\n  colorDomain: [0, 1],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  strokeColorDomain: [0, 1],\n  sizeField: {\n    name: 'c_ta',\n    id: 'c_ta',\n    displayName: 'c_ta',\n    type: 'real',\n    fieldIdx: 12,\n    format: '',\n    analyzerType: 'FLOAT',\n    valueAccessor: values => values[12]\n  },\n  hidden: false,\n  sizeScale: 'linear',\n  sizeDomain: [0.970877074, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  heightField: null,\n  heightScale: 'linear',\n  heightDomain: [0, 1],\n  radiusField: null,\n  radiusScale: 'linear',\n  radiusDomain: [0, 1],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    opacity: 0.8,\n    thickness: 7.6,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    strokeColorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    radius: 10,\n    sizeRange: [18.9, 47.6],\n    radiusRange: [0, 50],\n    heightRange: [0, 500],\n    elevationScale: 5,\n    fixedHeight: false,\n    stroked: true,\n    filled: false,\n    enable3d: false,\n    wireframe: false,\n    strokeColor: [221, 178, 124],\n    strokeOpacity: 0.8\n  },\n  animation: {enabled: false}\n};\n\nmergedLayer1.meta = {\n  bounds: [-75.135531, 40.213125, -73.949229, 41.172585],\n  fixedRadius: false,\n  featureTypes: {\n    polygon: true\n  }\n};\n\nmergedLayer1.dataToFeature = mergedLayer0.dataToFeature;\n\nexport const mergedLayers = [mergedLayer0, mergedLayer1];\n\nexport const mergedInteraction = {\n  ...defaultInteractionConfig,\n  tooltip: {\n    ...defaultInteractionConfig.tooltip,\n    enabled: false,\n    config: {\n      compareMode: false,\n      compareType: 'absolute',\n      fieldsToShow: {\n        a5ybmwl2d: [\n          {\n            name: 'a_zip',\n            format: null\n          },\n          {\n            name: 'str_type',\n            format: null\n          },\n          {\n            name: 'int_type',\n            format: null\n          }\n        ]\n      }\n    }\n  },\n  brush: {\n    ...defaultInteractionConfig.brush,\n    enabled: false,\n    config: {\n      size: 1\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-2.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const stateSavedV1_2 = {\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: 'test_phone_data',\n            id: 'me',\n            name: 'epoch',\n            type: 'timeRange',\n            value: [1477646491000, 1477646695000],\n            enlarged: true,\n            plotType: 'lineChart',\n            yAxis: {name: 'metric', type: 'real'}\n          },\n          {\n            dataId: 'test_phone_data',\n            id: '7ihrp33kx',\n            name: 'time string',\n            type: 'timeRange',\n            value: [1509533725000, 1509534560000],\n            enlarged: false,\n            plotType: 'histogram',\n            yAxis: null\n          }\n        ],\n        layers: [\n          {\n            id: 'b18ve1q',\n            type: 'point',\n            config: {\n              dataId: 'test_phone_data',\n              label: 'Point',\n              color: [18, 147, 154],\n              columns: {lat: 'lat', lng: 'lng', altitude: null},\n              isVisible: true,\n              visConfig: {\n                radius: 10,\n                fixedRadius: false,\n                opacity: 0.8,\n                outline: false,\n                thickness: 2,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radiusRange: [0, 50],\n                'hi-precision': false\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              test_phone_data: ['uuid', 'Latitude', 'Longitude', 'at_Latitude', 'at_Longitude']\n            },\n            enabled: true\n          },\n          brush: {size: 0.5, enabled: false}\n        },\n        layerBlending: 'normal',\n        splitMaps: []\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        buildingLayer: {\n          isVisible: false,\n          color: [32, 32, 37],\n          opacity: 0.7\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 40.739854859310576,\n        longitude: -73.95072736262398,\n        pitch: 0,\n        zoom: 13.015607264274948,\n        isSplit: false\n      },\n      queryStatus: {\n        pastQueries: [{label: 'sample phone data', id: 'test_phone_data'}]\n      }\n    }\n  },\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'test_phone_data',\n        label: 'sample phone data',\n        color: [53, 92, 125],\n        allData: [\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7504998,\n            -73.9858009,\n            40.7504998,\n            -73.9858009,\n            40.7504998,\n            -73.9858009,\n            0,\n            1477646131,\n            1477646131000,\n            0,\n            1477646130,\n            1477646140,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7504998,\n            -73.9858009,\n            40.7504998,\n            -73.9858009,\n            40.7504998,\n            -73.9858009,\n            0,\n            1477646133,\n            1477646133000,\n            0,\n            1477646130,\n            1477646140,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7504838,\n            -73.9857678,\n            40.7504838,\n            -73.9857678,\n            40.7504838,\n            -73.9857678,\n            2.3134842,\n            1477646134,\n            1477646134000,\n            0,\n            1477646130,\n            1477646140,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7504231,\n            -73.9856432,\n            40.7504231,\n            -73.9856432,\n            40.7504231,\n            -73.9856432,\n            4.863105,\n            1477646137,\n            1477646137000,\n            0,\n            1477646130,\n            1477646140,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.75036,\n            -73.9855336,\n            40.75036,\n            -73.9855336,\n            40.75036,\n            -73.9855336,\n            7.373735,\n            1477646142,\n            1477646142000,\n            0,\n            1477646140,\n            1477646150,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7502398,\n            -73.9852918,\n            40.7502398,\n            -73.9852918,\n            40.7502398,\n            -73.9852918,\n            8.513238,\n            1477646144,\n            1477646144000,\n            0,\n            1477646140,\n            1477646150,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7501249,\n            -73.9850585,\n            40.7501249,\n            -73.9850585,\n            40.7501249,\n            -73.9850585,\n            12.516513,\n            1477646146,\n            1477646146000,\n            0,\n            1477646140,\n            1477646150,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7499997,\n            -73.9847462,\n            40.7499997,\n            -73.9847462,\n            40.7499997,\n            -73.9847462,\n            10.460479,\n            1477646147,\n            1477646147000,\n            0,\n            1477646140,\n            1477646150,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7498623,\n            -73.9841722,\n            40.7498623,\n            -73.9841722,\n            40.7498623,\n            -73.9841722,\n            10.121932,\n            1477646154,\n            1477646154000,\n            0,\n            1477646150,\n            1477646160,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7497829,\n            -73.983867,\n            40.7497829,\n            -73.983867,\n            40.7497829,\n            -73.983867,\n            8.882398,\n            1477646156,\n            1477646156000,\n            0,\n            1477646150,\n            1477646160,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7497127,\n            -73.9836725,\n            40.7497127,\n            -73.9836725,\n            40.7497127,\n            -73.9836725,\n            9.106246,\n            1477646158,\n            1477646158000,\n            0,\n            1477646150,\n            1477646160,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7495674,\n            -73.9833727,\n            40.7495674,\n            -73.9833727,\n            40.7495674,\n            -73.9833727,\n            10.073572,\n            1477646161,\n            1477646161000,\n            0,\n            1477646160,\n            1477646170,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7494574,\n            -73.9831373,\n            40.7494574,\n            -73.9831373,\n            40.7494574,\n            -73.9831373,\n            11.328796,\n            1477646163,\n            1477646163000,\n            0,\n            1477646160,\n            1477646170,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7493774,\n            -73.9829227,\n            40.7493774,\n            -73.9829227,\n            40.7493774,\n            -73.9829227,\n            9.637881,\n            1477646164,\n            1477646164000,\n            0,\n            1477646160,\n            1477646170,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492847,\n            -73.9826511,\n            40.7492847,\n            -73.9826511,\n            40.7492847,\n            -73.9826511,\n            5.911331,\n            1477646167,\n            1477646167000,\n            0,\n            1477646160,\n            1477646170,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492451,\n            -73.9825461,\n            40.7492451,\n            -73.9825461,\n            40.7492451,\n            -73.9825461,\n            2.557522,\n            1477646169,\n            1477646169000,\n            0,\n            1477646160,\n            1477646170,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492377,\n            -73.9825278,\n            40.7492377,\n            -73.9825278,\n            40.7492377,\n            -73.9825278,\n            0.69229937,\n            1477646171,\n            1477646171000,\n            0,\n            1477646170,\n            1477646180,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            1.1836067,\n            1477646175,\n            1477646175000,\n            0,\n            1477646170,\n            1477646180,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646178,\n            1477646178000,\n            0,\n            1477646170,\n            1477646180,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646181,\n            1477646181000,\n            0,\n            1477646180,\n            1477646190,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646184,\n            1477646184000,\n            0,\n            1477646180,\n            1477646190,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646188,\n            1477646188000,\n            0,\n            1477646180,\n            1477646190,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646192,\n            1477646192000,\n            0,\n            1477646190,\n            1477646200,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646195,\n            1477646195000,\n            0,\n            1477646190,\n            1477646200,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646197,\n            1477646197000,\n            0,\n            1477646190,\n            1477646200,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646199,\n            1477646199000,\n            0,\n            1477646190,\n            1477646200,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            40.7492326,\n            -73.9825152,\n            0,\n            1477646201,\n            1477646201000,\n            0,\n            1477646200,\n            1477646210,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7491192,\n            -73.9822488,\n            40.7491192,\n            -73.9822488,\n            40.7491192,\n            -73.9822488,\n            5.7204247,\n            1477646205,\n            1477646205000,\n            0,\n            1477646200,\n            1477646210,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7490291,\n            -73.9820764,\n            40.7490291,\n            -73.9820764,\n            40.7490291,\n            -73.9820764,\n            7.251427,\n            1477646207,\n            1477646207000,\n            0,\n            1477646200,\n            1477646210,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7489464,\n            -73.9819092,\n            40.7489464,\n            -73.9819092,\n            40.7489464,\n            -73.9819092,\n            10.329027,\n            1477646209,\n            1477646209000,\n            0,\n            1477646200,\n            1477646210,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7487989,\n            -73.9815884,\n            40.7487989,\n            -73.9815884,\n            40.7487989,\n            -73.9815884,\n            9.959309,\n            1477646214,\n            1477646214000,\n            0,\n            1477646210,\n            1477646220,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7487105,\n            -73.9813701,\n            40.7487105,\n            -73.9813701,\n            40.7487105,\n            -73.9813701,\n            10.941365,\n            1477646216,\n            1477646216000,\n            0,\n            1477646210,\n            1477646220,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7485925,\n            -73.9811102,\n            40.7485925,\n            -73.9811102,\n            40.7485925,\n            -73.9811102,\n            0,\n            1477646218,\n            1477646218000,\n            0,\n            1477646210,\n            1477646220,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7484258,\n            -73.9807382,\n            40.7484258,\n            -73.9807382,\n            40.7484258,\n            -73.9807382,\n            11.978285,\n            1477646219,\n            1477646219000,\n            0,\n            1477646210,\n            1477646220,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7482774,\n            -73.9803614,\n            40.7482774,\n            -73.9803614,\n            40.7482774,\n            -73.9803614,\n            11.851179,\n            1477646222,\n            1477646222000,\n            0,\n            1477646220,\n            1477646230,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7481752,\n            -73.9801143,\n            40.7481752,\n            -73.9801143,\n            40.7481752,\n            -73.9801143,\n            11.236809,\n            1477646224,\n            1477646224000,\n            0,\n            1477646220,\n            1477646230,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7480729,\n            -73.97988,\n            40.7480729,\n            -73.97988,\n            40.7480729,\n            -73.97988,\n            11.487315,\n            1477646226,\n            1477646226000,\n            0,\n            1477646220,\n            1477646230,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478419,\n            -73.9793727,\n            40.7478419,\n            -73.9793727,\n            40.7478419,\n            -73.9793727,\n            5.9004717,\n            1477646234,\n            1477646234000,\n            0,\n            1477646230,\n            1477646240,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478181,\n            -73.9793179,\n            40.7478181,\n            -73.9793179,\n            40.7478181,\n            -73.9793179,\n            0.34833142,\n            1477646236,\n            1477646236000,\n            0,\n            1477646230,\n            1477646240,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646239,\n            1477646239000,\n            0,\n            1477646230,\n            1477646240,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646241,\n            1477646241000,\n            0,\n            1477646240,\n            1477646250,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646243,\n            1477646243000,\n            0,\n            1477646240,\n            1477646250,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646247,\n            1477646247000,\n            0,\n            1477646240,\n            1477646250,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646248,\n            1477646248000,\n            0,\n            1477646240,\n            1477646250,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646251,\n            1477646251000,\n            0,\n            1477646250,\n            1477646260,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646254,\n            1477646254000,\n            0,\n            1477646250,\n            1477646260,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646256,\n            1477646256000,\n            0,\n            1477646250,\n            1477646260,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646258,\n            1477646258000,\n            0,\n            1477646250,\n            1477646260,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646264,\n            1477646264000,\n            0,\n            1477646260,\n            1477646270,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646265,\n            1477646265000,\n            0,\n            1477646260,\n            1477646270,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646268,\n            1477646268000,\n            0,\n            1477646260,\n            1477646270,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646271,\n            1477646271000,\n            0,\n            1477646270,\n            1477646280,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            40.7478149,\n            -73.9793104,\n            0,\n            1477646273,\n            1477646273000,\n            0,\n            1477646270,\n            1477646280,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.747795,\n            -73.9792619,\n            40.747795,\n            -73.9792619,\n            40.747795,\n            -73.9792619,\n            2.7623723,\n            1477646275,\n            1477646275000,\n            0,\n            1477646270,\n            1477646280,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7477287,\n            -73.979076,\n            40.7477287,\n            -73.979076,\n            40.7477287,\n            -73.979076,\n            0,\n            1477646277,\n            1477646277000,\n            0,\n            1477646270,\n            1477646280,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7476501,\n            -73.9788568,\n            40.7476501,\n            -73.9788568,\n            40.7476501,\n            -73.9788568,\n            7.7189913,\n            1477646279,\n            1477646279000,\n            0,\n            1477646270,\n            1477646280,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7475827,\n            -73.9786797,\n            40.7475827,\n            -73.9786797,\n            40.7475827,\n            -73.9786797,\n            8.895677,\n            1477646281,\n            1477646281000,\n            0,\n            1477646280,\n            1477646290,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7474731,\n            -73.9783971,\n            40.7474731,\n            -73.9783971,\n            40.7474731,\n            -73.9783971,\n            9.336869,\n            1477646285,\n            1477646285000,\n            0,\n            1477646280,\n            1477646290,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7474006,\n            -73.9782137,\n            40.7474006,\n            -73.9782137,\n            40.7474006,\n            -73.9782137,\n            7.701453,\n            1477646287,\n            1477646287000,\n            0,\n            1477646280,\n            1477646290,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7473185,\n            -73.978056,\n            40.7473185,\n            -73.978056,\n            40.7473185,\n            -73.978056,\n            7.371801,\n            1477646289,\n            1477646289000,\n            0,\n            1477646280,\n            1477646290,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7472603,\n            -73.9779182,\n            40.7472603,\n            -73.9779182,\n            40.7472603,\n            -73.9779182,\n            6.3743296,\n            1477646291,\n            1477646291000,\n            0,\n            1477646290,\n            1477646300,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471888,\n            -73.9777441,\n            40.7471888,\n            -73.9777441,\n            40.7471888,\n            -73.9777441,\n            4.381162,\n            1477646294,\n            1477646294000,\n            0,\n            1477646290,\n            1477646300,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            1.2524523,\n            1477646298,\n            1477646298000,\n            0,\n            1477646290,\n            1477646300,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            0,\n            1477646301,\n            1477646301000,\n            0,\n            1477646300,\n            1477646310,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            0,\n            1477646303,\n            1477646303000,\n            0,\n            1477646300,\n            1477646310,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            0,\n            1477646305,\n            1477646305000,\n            0,\n            1477646300,\n            1477646310,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            0,\n            1477646307,\n            1477646307000,\n            0,\n            1477646300,\n            1477646310,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            40.7471598,\n            -73.9776657,\n            0,\n            1477646309,\n            1477646309000,\n            0,\n            1477646300,\n            1477646310,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7471554,\n            -73.9776514,\n            40.7471554,\n            -73.9776514,\n            40.7471554,\n            -73.9776514,\n            1.153652,\n            1477646311,\n            1477646311000,\n            0,\n            1477646310,\n            1477646320,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7470848,\n            -73.9774972,\n            40.7470848,\n            -73.9774972,\n            40.7470848,\n            -73.9774972,\n            4.964964,\n            1477646313,\n            1477646313000,\n            0,\n            1477646310,\n            1477646320,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7470246,\n            -73.9773576,\n            40.7470246,\n            -73.9773576,\n            40.7470246,\n            -73.9773576,\n            7.7313733,\n            1477646315,\n            1477646315000,\n            0,\n            1477646310,\n            1477646320,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7469558,\n            -73.977182,\n            40.7469558,\n            -73.977182,\n            40.7469558,\n            -73.977182,\n            9.398899,\n            1477646318,\n            1477646318000,\n            0,\n            1477646310,\n            1477646320,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7467818,\n            -73.9767237,\n            40.7467818,\n            -73.9767237,\n            40.7467818,\n            -73.9767237,\n            11.618991,\n            1477646324,\n            1477646324000,\n            0,\n            1477646320,\n            1477646330,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7465333,\n            -73.9761115,\n            40.7465333,\n            -73.9761115,\n            40.7465333,\n            -73.9761115,\n            11.881396,\n            1477646326,\n            1477646326000,\n            0,\n            1477646320,\n            1477646330,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7464436,\n            -73.9758651,\n            40.7464436,\n            -73.9758651,\n            40.7464436,\n            -73.9758651,\n            11.311087,\n            1477646328,\n            1477646328000,\n            0,\n            1477646320,\n            1477646330,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7463027,\n            -73.975514,\n            40.7463027,\n            -73.975514,\n            40.7463027,\n            -73.975514,\n            11.1060915,\n            1477646332,\n            1477646332000,\n            0,\n            1477646330,\n            1477646340,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7461633,\n            -73.9751873,\n            40.7461633,\n            -73.9751873,\n            40.7461633,\n            -73.9751873,\n            10.482742,\n            1477646334,\n            1477646334000,\n            0,\n            1477646330,\n            1477646340,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7460954,\n            -73.9749928,\n            40.7460954,\n            -73.9749928,\n            40.7460954,\n            -73.9749928,\n            8.920771,\n            1477646336,\n            1477646336000,\n            0,\n            1477646330,\n            1477646340,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7460008,\n            -73.9746995,\n            40.7460008,\n            -73.9746995,\n            40.7460008,\n            -73.9746995,\n            9.171494,\n            1477646341,\n            1477646341000,\n            0,\n            1477646340,\n            1477646350,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7459243,\n            -73.9743702,\n            40.7459243,\n            -73.9743702,\n            40.7459243,\n            -73.9743702,\n            9.917238,\n            1477646343,\n            1477646343000,\n            0,\n            1477646340,\n            1477646350,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7458846,\n            -73.9741343,\n            40.7458846,\n            -73.9741343,\n            40.7458846,\n            -73.9741343,\n            9.391925,\n            1477646345,\n            1477646345000,\n            0,\n            1477646340,\n            1477646350,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7457771,\n            -73.9738108,\n            40.7457771,\n            -73.9738108,\n            40.7457771,\n            -73.9738108,\n            9.963142,\n            1477646349,\n            1477646349000,\n            0,\n            1477646340,\n            1477646350,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7455663,\n            -73.9731593,\n            40.7455663,\n            -73.9731593,\n            40.7455663,\n            -73.9731593,\n            8.678049,\n            1477646353,\n            1477646353000,\n            0,\n            1477646350,\n            1477646360,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7455892,\n            -73.9727863,\n            40.7455892,\n            -73.9727863,\n            40.7455892,\n            -73.9727863,\n            10.395492,\n            1477646357,\n            1477646357000,\n            0,\n            1477646350,\n            1477646360,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7456771,\n            -73.9724278,\n            40.7456771,\n            -73.9724278,\n            40.7456771,\n            -73.9724278,\n            10.022741,\n            1477646362,\n            1477646362000,\n            0,\n            1477646360,\n            1477646370,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7454237,\n            -73.973315,\n            40.7454237,\n            -73.973315,\n            40.7454237,\n            -73.973315,\n            0,\n            1477646407,\n            1477646407000,\n            0,\n            1477646400,\n            1477646410,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7441281,\n            -73.9655551,\n            40.7441281,\n            -73.9655551,\n            40.7441281,\n            -73.9655551,\n            0,\n            1477646423,\n            1477646423000,\n            0,\n            1477646420,\n            1477646430,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7412123,\n            -73.9493915,\n            40.7412123,\n            -73.9493915,\n            40.7412123,\n            -73.9493915,\n            0,\n            1477646474,\n            1477646474000,\n            0,\n            1477646470,\n            1477646480,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413539,\n            -73.9529713,\n            40.7413539,\n            -73.9529713,\n            40.7413539,\n            -73.9529713,\n            8.257094,\n            1477646483,\n            1477646483000,\n            0,\n            1477646480,\n            1477646490,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413283,\n            -73.9528125,\n            40.7413283,\n            -73.9528125,\n            40.7413283,\n            -73.9528125,\n            5.7617188,\n            1477646487,\n            1477646487000,\n            0,\n            1477646480,\n            1477646490,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413151,\n            -73.9525884,\n            40.7413151,\n            -73.9525884,\n            40.7413151,\n            -73.9525884,\n            3.0065663,\n            1477646492,\n            1477646492000,\n            0,\n            1477646490,\n            1477646500,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413078,\n            -73.9525147,\n            40.7413078,\n            -73.9525147,\n            40.7413078,\n            -73.9525147,\n            2.148815,\n            1477646494,\n            1477646494000,\n            0,\n            1477646490,\n            1477646500,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413058,\n            -73.9524018,\n            40.7413058,\n            -73.9524018,\n            40.7413058,\n            -73.9524018,\n            5.3495803,\n            1477646496,\n            1477646496000,\n            0,\n            1477646490,\n            1477646500,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7413358,\n            -73.9515823,\n            40.7413358,\n            -73.9515823,\n            40.7413358,\n            -73.9515823,\n            13.522032,\n            1477646501,\n            1477646501000,\n            0,\n            1477646500,\n            1477646510,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7414609,\n            -73.9510389,\n            40.7414609,\n            -73.9510389,\n            40.7414609,\n            -73.9510389,\n            16.217,\n            1477646504,\n            1477646504000,\n            0,\n            1477646500,\n            1477646510,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.741549,\n            -73.9504947,\n            40.741549,\n            -73.9504947,\n            40.741549,\n            -73.9504947,\n            16.721155,\n            1477646508,\n            1477646508000,\n            0,\n            1477646500,\n            1477646510,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7415677,\n            -73.9498967,\n            40.7415677,\n            -73.9498967,\n            40.7415677,\n            -73.9498967,\n            17.827305,\n            1477646511,\n            1477646511000,\n            0,\n            1477646510,\n            1477646520,\n            '11/1/17 11:01'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7415418,\n            -73.9494483,\n            40.7415418,\n            -73.9494483,\n            40.7415418,\n            -73.9494483,\n            18.955433,\n            1477646513,\n            1477646513000,\n            0,\n            1477646510,\n            1477646520,\n            '11/1/17 11:02'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7414799,\n            -73.949008,\n            40.7414799,\n            -73.949008,\n            40.7414799,\n            -73.949008,\n            19.612692,\n            1477646515,\n            1477646515000,\n            0,\n            1477646510,\n            1477646520,\n            '11/1/17 11:03'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7414082,\n            -73.9485429,\n            40.7414082,\n            -73.9485429,\n            40.7414082,\n            -73.9485429,\n            20.748034,\n            1477646517,\n            1477646517000,\n            0,\n            1477646510,\n            1477646520,\n            '11/1/17 11:04'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7412702,\n            -73.947779,\n            40.7412702,\n            -73.947779,\n            40.7412702,\n            -73.947779,\n            22.42561,\n            1477646521,\n            1477646521000,\n            0,\n            1477646520,\n            1477646530,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7411235,\n            -73.9469953,\n            40.7411235,\n            -73.9469953,\n            40.7411235,\n            -73.9469953,\n            23.08805,\n            1477646523,\n            1477646523000,\n            0,\n            1477646520,\n            1477646530,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7410223,\n            -73.9464731,\n            40.7410223,\n            -73.9464731,\n            40.7410223,\n            -73.9464731,\n            22.99983,\n            1477646525,\n            1477646525000,\n            0,\n            1477646520,\n            1477646530,\n            '11/1/17 11:05'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7409133,\n            -73.9459422,\n            40.7409133,\n            -73.9459422,\n            40.7409133,\n            -73.9459422,\n            22.93067,\n            1477646528,\n            1477646528000,\n            0,\n            1477646520,\n            1477646530,\n            '11/1/17 11:10'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7405695,\n            -73.9446514,\n            40.7405695,\n            -73.9446514,\n            40.7405695,\n            -73.9446514,\n            23.105522,\n            1477646532,\n            1477646532000,\n            0,\n            1477646530,\n            1477646540,\n            '11/1/17 11:00'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7403907,\n            -73.9441615,\n            40.7403907,\n            -73.9441615,\n            40.7403907,\n            -73.9441615,\n            22.983301,\n            1477646534,\n            1477646534000,\n            0,\n            1477646530,\n            1477646540,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7401085,\n            -73.9434534,\n            40.7401085,\n            -73.9434534,\n            40.7401085,\n            -73.9434534,\n            23.045782,\n            1477646538,\n            1477646538000,\n            0,\n            1477646530,\n            1477646540,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7395878,\n            -73.9422209,\n            40.7395878,\n            -73.9422209,\n            40.7395878,\n            -73.9422209,\n            23.27081,\n            1477646542,\n            1477646542000,\n            0,\n            1477646540,\n            1477646550,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7392932,\n            -73.9415291,\n            40.7392932,\n            -73.9415291,\n            40.7392932,\n            -73.9415291,\n            23.16073,\n            1477646544,\n            1477646544000,\n            0,\n            1477646540,\n            1477646550,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7390901,\n            -73.9410361,\n            40.7390901,\n            -73.9410361,\n            40.7390901,\n            -73.9410361,\n            24.436981,\n            1477646546,\n            1477646546000,\n            0,\n            1477646540,\n            1477646550,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.738776,\n            -73.9402473,\n            40.738776,\n            -73.9402473,\n            40.738776,\n            -73.9402473,\n            25.41179,\n            1477646551,\n            1477646551000,\n            0,\n            1477646550,\n            1477646560,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7384664,\n            -73.9394387,\n            40.7384664,\n            -73.9394387,\n            40.7384664,\n            -73.9394387,\n            25.119013,\n            1477646553,\n            1477646553000,\n            0,\n            1477646550,\n            1477646560,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7382883,\n            -73.9388887,\n            40.7382883,\n            -73.9388887,\n            40.7382883,\n            -73.9388887,\n            24.935415,\n            1477646555,\n            1477646555000,\n            0,\n            1477646550,\n            1477646560,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7380675,\n            -73.9380285,\n            40.7380675,\n            -73.9380285,\n            40.7380675,\n            -73.9380285,\n            25.291101,\n            1477646559,\n            1477646559000,\n            0,\n            1477646550,\n            1477646560,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7378185,\n            -73.936549,\n            40.7378185,\n            -73.936549,\n            40.7378185,\n            -73.936549,\n            25.382856,\n            1477646563,\n            1477646563000,\n            0,\n            1477646560,\n            1477646570,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7375965,\n            -73.9351032,\n            40.7375965,\n            -73.9351032,\n            40.7375965,\n            -73.9351032,\n            24.748867,\n            1477646568,\n            1477646568000,\n            0,\n            1477646560,\n            1477646570,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.73743,\n            -73.9342532,\n            40.73743,\n            -73.9342532,\n            40.73743,\n            -73.9342532,\n            24.7851,\n            1477646572,\n            1477646572000,\n            0,\n            1477646570,\n            1477646580,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7371306,\n            -73.9325934,\n            40.7371306,\n            -73.9325934,\n            40.7371306,\n            -73.9325934,\n            23.329536,\n            1477646576,\n            1477646576000,\n            0,\n            1477646570,\n            1477646580,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7368275,\n            -73.9310825,\n            40.7368275,\n            -73.9310825,\n            40.7368275,\n            -73.9310825,\n            19.574614,\n            1477646583,\n            1477646583000,\n            0,\n            1477646580,\n            1477646590,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.736728,\n            -73.9306084,\n            40.736728,\n            -73.9306084,\n            40.736728,\n            -73.9306084,\n            20.056524,\n            1477646585,\n            1477646585000,\n            0,\n            1477646580,\n            1477646590,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7366351,\n            -73.9301129,\n            40.7366351,\n            -73.9301129,\n            40.7366351,\n            -73.9301129,\n            20.795082,\n            1477646586,\n            1477646586000,\n            0,\n            1477646580,\n            1477646590,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7364946,\n            -73.9294031,\n            40.7364946,\n            -73.9294031,\n            40.7364946,\n            -73.9294031,\n            20.3169,\n            1477646589,\n            1477646589000,\n            0,\n            1477646580,\n            1477646590,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7364007,\n            -73.9289478,\n            40.7364007,\n            -73.9289478,\n            40.7364007,\n            -73.9289478,\n            19.846592,\n            1477646593,\n            1477646593000,\n            0,\n            1477646590,\n            1477646600,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7361271,\n            -73.9276136,\n            40.7361271,\n            -73.9276136,\n            40.7361271,\n            -73.9276136,\n            20.346718,\n            1477646597,\n            1477646597000,\n            0,\n            1477646590,\n            1477646600,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7360359,\n            -73.927155,\n            40.7360359,\n            -73.927155,\n            40.7360359,\n            -73.927155,\n            19.627155,\n            1477646602,\n            1477646602000,\n            0,\n            1477646600,\n            1477646610,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7358998,\n            -73.9264952,\n            40.7358998,\n            -73.9264952,\n            40.7358998,\n            -73.9264952,\n            18.476479,\n            1477646604,\n            1477646604000,\n            0,\n            1477646600,\n            1477646610,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7358147,\n            -73.9260823,\n            40.7358147,\n            -73.9260823,\n            40.7358147,\n            -73.9260823,\n            17.66287,\n            1477646606,\n            1477646606000,\n            0,\n            1477646600,\n            1477646610,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7356621,\n            -73.9255063,\n            40.7356621,\n            -73.9255063,\n            40.7356621,\n            -73.9255063,\n            16.96675,\n            1477646608,\n            1477646608000,\n            0,\n            1477646600,\n            1477646610,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7354302,\n            -73.9247205,\n            40.7354302,\n            -73.9247205,\n            40.7354302,\n            -73.9247205,\n            18.212366,\n            1477646611,\n            1477646611000,\n            0.4,\n            1477646610,\n            1477646620,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7352334,\n            -73.9241493,\n            40.7352334,\n            -73.9241493,\n            40.7352334,\n            -73.9241493,\n            18.156546,\n            1477646614,\n            1477646614000,\n            0.4,\n            1477646610,\n            1477646620,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7349952,\n            -73.9236143,\n            40.7349952,\n            -73.9236143,\n            40.7349952,\n            -73.9236143,\n            17.546999,\n            1477646619,\n            1477646619000,\n            0.4,\n            1477646610,\n            1477646620,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646631,\n            1477646631000,\n            0.545454545,\n            1477646630,\n            1477646640,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646636,\n            1477646636000,\n            0.545454545,\n            1477646630,\n            1477646640,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646638,\n            1477646638000,\n            0.545454545,\n            1477646630,\n            1477646640,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646641,\n            1477646641000,\n            1,\n            1477646640,\n            1477646650,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646644,\n            1477646644000,\n            1,\n            1477646640,\n            1477646650,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646648,\n            1477646648000,\n            1,\n            1477646640,\n            1477646650,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646649,\n            1477646649000,\n            1,\n            1477646640,\n            1477646650,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646652,\n            1477646652000,\n            0.9,\n            1477646650,\n            1477646660,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646657,\n            1477646657000,\n            0.9,\n            1477646650,\n            1477646660,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646659,\n            1477646659000,\n            0.9,\n            1477646650,\n            1477646660,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646661,\n            1477646661000,\n            1,\n            1477646660,\n            1477646670,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646662,\n            1477646662000,\n            1,\n            1477646660,\n            1477646670,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646665,\n            1477646665000,\n            1,\n            1477646660,\n            1477646670,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646669,\n            1477646669000,\n            1,\n            1477646660,\n            1477646670,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646671,\n            1477646671000,\n            0.9,\n            1477646670,\n            1477646680,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646674,\n            1477646674000,\n            0.9,\n            1477646670,\n            1477646680,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646675,\n            1477646675000,\n            0.9,\n            1477646670,\n            1477646680,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646678,\n            1477646678000,\n            0.9,\n            1477646670,\n            1477646680,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646682,\n            1477646682000,\n            1,\n            1477646680,\n            1477646690,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646685,\n            1477646685000,\n            1,\n            1477646680,\n            1477646690,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646687,\n            1477646687000,\n            1,\n            1477646680,\n            1477646690,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646689,\n            1477646689000,\n            1,\n            1477646680,\n            1477646690,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646691,\n            1477646691000,\n            1,\n            1477646690,\n            1477646700,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646693,\n            1477646693000,\n            1,\n            1477646690,\n            1477646700,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646695,\n            1477646695000,\n            1,\n            1477646690,\n            1477646700,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646699,\n            1477646699000,\n            1,\n            1477646690,\n            1477646700,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646702,\n            1477646702000,\n            0.9,\n            1477646700,\n            1477646710,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646704,\n            1477646704000,\n            0.9,\n            1477646700,\n            1477646710,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646705,\n            1477646705000,\n            0.9,\n            1477646700,\n            1477646710,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646708,\n            1477646708000,\n            0.9,\n            1477646700,\n            1477646710,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646712,\n            1477646712000,\n            1,\n            1477646710,\n            1477646720,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646714,\n            1477646714000,\n            1,\n            1477646710,\n            1477646720,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646717,\n            1477646717000,\n            1,\n            1477646710,\n            1477646720,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646721,\n            1477646721000,\n            1,\n            1477646720,\n            1477646730,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646723,\n            1477646723000,\n            1,\n            1477646720,\n            1477646730,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646725,\n            1477646725000,\n            1,\n            1477646720,\n            1477646730,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646727,\n            1477646727000,\n            1,\n            1477646720,\n            1477646730,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646729,\n            1477646729000,\n            1,\n            1477646720,\n            1477646730,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646734,\n            1477646734000,\n            0.8,\n            1477646730,\n            1477646740,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646736,\n            1477646736000,\n            0.8,\n            1477646730,\n            1477646740,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646738,\n            1477646738000,\n            0.8,\n            1477646730,\n            1477646740,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646742,\n            1477646742000,\n            1,\n            1477646740,\n            1477646750,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646747,\n            1477646747000,\n            1,\n            1477646740,\n            1477646750,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646748,\n            1477646748000,\n            1,\n            1477646740,\n            1477646750,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646751,\n            1477646751000,\n            1,\n            1477646750,\n            1477646760,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646753,\n            1477646753000,\n            1,\n            1477646750,\n            1477646760,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646755,\n            1477646755000,\n            1,\n            1477646750,\n            1477646760,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646756,\n            1477646756000,\n            1,\n            1477646750,\n            1477646760,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646759,\n            1477646759000,\n            1,\n            1477646750,\n            1477646760,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646764,\n            1477646764000,\n            1,\n            1477646760,\n            1477646770,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646765,\n            1477646765000,\n            1,\n            1477646760,\n            1477646770,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646768,\n            1477646768000,\n            1,\n            1477646760,\n            1477646770,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646769,\n            1477646769000,\n            1,\n            1477646760,\n            1477646770,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646772,\n            1477646772000,\n            1,\n            1477646770,\n            1477646780,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646777,\n            1477646777000,\n            1,\n            1477646770,\n            1477646780,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646778,\n            1477646778000,\n            1,\n            1477646770,\n            1477646780,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646781,\n            1477646781000,\n            1,\n            1477646780,\n            1477646790,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646783,\n            1477646783000,\n            1,\n            1477646780,\n            1477646790,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646785,\n            1477646785000,\n            1,\n            1477646780,\n            1477646790,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646791,\n            1477646791000,\n            1,\n            1477646790,\n            1477646800,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646794,\n            1477646794000,\n            1,\n            1477646790,\n            1477646800,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646795,\n            1477646795000,\n            1,\n            1477646790,\n            1477646800,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646798,\n            1477646798000,\n            1,\n            1477646790,\n            1477646800,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646802,\n            1477646802000,\n            0.8,\n            1477646800,\n            1477646810,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646807,\n            1477646807000,\n            0.8,\n            1477646800,\n            1477646810,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646809,\n            1477646809000,\n            0.8,\n            1477646800,\n            1477646810,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646811,\n            1477646811000,\n            0.5,\n            1477646810,\n            1477646820,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646812,\n            1477646812000,\n            0.5,\n            1477646810,\n            1477646820,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646815,\n            1477646815000,\n            0.5,\n            1477646810,\n            1477646820,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646822,\n            1477646822000,\n            1,\n            1477646820,\n            1477646830,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646824,\n            1477646824000,\n            1,\n            1477646820,\n            1477646830,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646826,\n            1477646826000,\n            1,\n            1477646820,\n            1477646830,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646828,\n            1477646828000,\n            1,\n            1477646820,\n            1477646830,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646832,\n            1477646832000,\n            1,\n            1477646830,\n            1477646840,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646834,\n            1477646834000,\n            1,\n            1477646830,\n            1477646840,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646837,\n            1477646837000,\n            1,\n            1477646830,\n            1477646840,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646841,\n            1477646841000,\n            1,\n            1477646840,\n            1477646850,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646842,\n            1477646842000,\n            1,\n            1477646840,\n            1477646850,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646845,\n            1477646845000,\n            1,\n            1477646840,\n            1477646850,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646846,\n            1477646846000,\n            1,\n            1477646840,\n            1477646850,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646849,\n            1477646849000,\n            1,\n            1477646840,\n            1477646850,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646854,\n            1477646854000,\n            1,\n            1477646850,\n            1477646860,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646856,\n            1477646856000,\n            1,\n            1477646850,\n            1477646860,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646858,\n            1477646858000,\n            1,\n            1477646850,\n            1477646860,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646862,\n            1477646862000,\n            0.9,\n            1477646860,\n            1477646870,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646867,\n            1477646867000,\n            0.9,\n            1477646860,\n            1477646870,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646868,\n            1477646868000,\n            0.9,\n            1477646860,\n            1477646870,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646871,\n            1477646871000,\n            1,\n            1477646870,\n            1477646880,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646873,\n            1477646873000,\n            1,\n            1477646870,\n            1477646880,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646875,\n            1477646875000,\n            1,\n            1477646870,\n            1477646880,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646879,\n            1477646879000,\n            1,\n            1477646870,\n            1477646880,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646882,\n            1477646882000,\n            0.9,\n            1477646880,\n            1477646890,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646884,\n            1477646884000,\n            0.9,\n            1477646880,\n            1477646890,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646888,\n            1477646888000,\n            0.9,\n            1477646880,\n            1477646890,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646889,\n            1477646889000,\n            0.9,\n            1477646880,\n            1477646890,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646892,\n            1477646892000,\n            1,\n            1477646890,\n            1477646900,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646894,\n            1477646894000,\n            1,\n            1477646890,\n            1477646900,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646896,\n            1477646896000,\n            1,\n            1477646890,\n            1477646900,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646901,\n            1477646901000,\n            1,\n            1477646900,\n            1477646910,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646903,\n            1477646903000,\n            1,\n            1477646900,\n            1477646910,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646905,\n            1477646905000,\n            1,\n            1477646900,\n            1477646910,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646906,\n            1477646906000,\n            1,\n            1477646900,\n            1477646910,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646909,\n            1477646909000,\n            1,\n            1477646900,\n            1477646910,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646911,\n            1477646911000,\n            0.8,\n            1477646910,\n            1477646920,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646913,\n            1477646913000,\n            0.8,\n            1477646910,\n            1477646920,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646918,\n            1477646918000,\n            0.8,\n            1477646910,\n            1477646920,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646919,\n            1477646919000,\n            0.8,\n            1477646910,\n            1477646920,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646922,\n            1477646922000,\n            0.6,\n            1477646920,\n            1477646930,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646926,\n            1477646926000,\n            0.6,\n            1477646920,\n            1477646930,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646929,\n            1477646929000,\n            0.6,\n            1477646920,\n            1477646930,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646931,\n            1477646931000,\n            0.8,\n            1477646930,\n            1477646940,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646932,\n            1477646932000,\n            0.8,\n            1477646930,\n            1477646940,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646935,\n            1477646935000,\n            0.8,\n            1477646930,\n            1477646940,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646939,\n            1477646939000,\n            0.8,\n            1477646930,\n            1477646940,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646943,\n            1477646943000,\n            1,\n            1477646940,\n            1477646950,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646948,\n            1477646948000,\n            1,\n            1477646940,\n            1477646950,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646952,\n            1477646952000,\n            1,\n            1477646950,\n            1477646960,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646953,\n            1477646953000,\n            1,\n            1477646950,\n            1477646960,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646956,\n            1477646956000,\n            1,\n            1477646950,\n            1477646960,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646959,\n            1477646959000,\n            1,\n            1477646950,\n            1477646960,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646961,\n            1477646961000,\n            0.9,\n            1477646960,\n            1477646970,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646965,\n            1477646965000,\n            0.9,\n            1477646960,\n            1477646970,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646966,\n            1477646966000,\n            0.9,\n            1477646960,\n            1477646970,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646969,\n            1477646969000,\n            0.9,\n            1477646960,\n            1477646970,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646973,\n            1477646973000,\n            1,\n            1477646970,\n            1477646980,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646976,\n            1477646976000,\n            1,\n            1477646970,\n            1477646980,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646978,\n            1477646978000,\n            1,\n            1477646970,\n            1477646980,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646979,\n            1477646979000,\n            1,\n            1477646970,\n            1477646980,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646982,\n            1477646982000,\n            1,\n            1477646980,\n            1477646990,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646986,\n            1477646986000,\n            1,\n            1477646980,\n            1477646990,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646987,\n            1477646987000,\n            1,\n            1477646980,\n            1477646990,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646995,\n            1477646995000,\n            1,\n            1477646990,\n            1477647000,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646996,\n            1477646996000,\n            1,\n            1477646990,\n            1477647000,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477646999,\n            1477646999000,\n            1,\n            1477646990,\n            1477647000,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647003,\n            1477647003000,\n            0.2,\n            1477647000,\n            1477647010,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647005,\n            1477647005000,\n            0.2,\n            1477647000,\n            1477647010,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647007,\n            1477647007000,\n            0.2,\n            1477647000,\n            1477647010,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647011,\n            1477647011000,\n            0,\n            1477647010,\n            1477647020,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647016,\n            1477647016000,\n            0,\n            1477647010,\n            1477647020,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647018,\n            1477647018000,\n            0,\n            1477647010,\n            1477647020,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647022,\n            1477647022000,\n            0,\n            1477647020,\n            1477647030,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647024,\n            1477647024000,\n            0,\n            1477647020,\n            1477647030,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647029,\n            1477647029000,\n            0,\n            1477647020,\n            1477647030,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647033,\n            1477647033000,\n            0,\n            1477647030,\n            1477647040,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647035,\n            1477647035000,\n            0,\n            1477647030,\n            1477647040,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647037,\n            1477647037000,\n            0,\n            1477647030,\n            1477647040,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647042,\n            1477647042000,\n            0,\n            1477647040,\n            1477647050,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647046,\n            1477647046000,\n            0,\n            1477647040,\n            1477647050,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647048,\n            1477647048000,\n            0,\n            1477647040,\n            1477647050,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647054,\n            1477647054000,\n            0,\n            1477647050,\n            1477647060,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647056,\n            1477647056000,\n            0,\n            1477647050,\n            1477647060,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647058,\n            1477647058000,\n            0,\n            1477647050,\n            1477647060,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647061,\n            1477647061000,\n            0,\n            1477647060,\n            1477647070,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647063,\n            1477647063000,\n            0,\n            1477647060,\n            1477647070,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            40.7347789,\n            -73.9231919,\n            0,\n            1477647067,\n            1477647067000,\n            0,\n            1477647060,\n            1477647070,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            0,\n            1477647069,\n            1477647069000,\n            0,\n            1477647060,\n            1477647070,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            0,\n            1477647071,\n            1477647071000,\n            0,\n            1477647070,\n            1477647080,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            0,\n            1477647074,\n            1477647074000,\n            0,\n            1477647070,\n            1477647080,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            0,\n            1477647076,\n            1477647076000,\n            0,\n            1477647070,\n            1477647080,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            40.7347536,\n            -73.9231675,\n            0,\n            1477647081,\n            1477647081000,\n            0,\n            1477647080,\n            1477647090,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647084,\n            1477647084000,\n            0,\n            1477647080,\n            1477647090,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647088,\n            1477647088000,\n            0.5,\n            1477647080,\n            1477647090,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647091,\n            1477647091000,\n            0,\n            1477647090,\n            1477647100,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647093,\n            1477647093000,\n            0,\n            1477647090,\n            1477647100,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647094,\n            1477647094000,\n            0.9,\n            1477647090,\n            1477647100,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647097,\n            1477647097000,\n            0.8,\n            1477647090,\n            1477647100,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647101,\n            1477647101000,\n            0,\n            1477647100,\n            1477647110,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647102,\n            1477647102000,\n            0,\n            1477647100,\n            1477647110,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647105,\n            1477647105000,\n            0,\n            1477647100,\n            1477647110,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647111,\n            1477647111000,\n            1,\n            1477647110,\n            1477647120,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647114,\n            1477647114000,\n            1,\n            1477647110,\n            1477647120,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647115,\n            1477647115000,\n            1,\n            1477647110,\n            1477647120,\n            '11/1/17 10:30'\n          ],\n          [\n            '0824eb2b-bfcf-4fd8-9fc1-6d7573611792',\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            40.7347486,\n            -73.9232025,\n            0,\n            1477647118,\n            1477647118000,\n            1,\n            1477647110,\n            1477647120,\n            '11/1/17 10:30'\n          ]\n        ],\n        fields: [\n          {name: 'uuid', type: 'string', format: ''},\n          {name: 'lat', type: 'real', format: ''},\n          {name: 'lng', type: 'real', format: ''},\n          {name: 'Latitude', type: 'real', format: ''},\n          {name: 'Longitude', type: 'real', format: ''},\n          {name: 'at_Latitude', type: 'real', format: ''},\n          {name: 'at_Longitude', type: 'real', format: ''},\n          {name: 'metric', type: 'real', format: ''},\n          {name: 'epoch', type: 'timestamp', format: 'x'},\n          {name: 'epoch_ms', type: 'timestamp', format: 'X'},\n          {name: 'meta_data', type: 'real', format: ''},\n          {name: 'start_ts', type: 'timestamp', format: 'x'},\n          {name: 'end_ts', type: 'timestamp', format: 'x'},\n          {name: 'time string', type: 'timestamp', format: 'YYYY/M/D H:m'}\n        ]\n      }\n    }\n  ]\n};\n\nexport const v1expectedInfo_2 = {\n  id: 'test_phone_data',\n  label: 'sample phone data',\n  color: [53, 92, 125]\n};\n\nexport const v1expectedFields_2 = [\n  {name: 'uuid', type: 'string', format: '', analyzerType: 'STRING'},\n  {name: 'lat', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'lng', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'Latitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'Longitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'at_Latitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'at_Longitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'metric', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'epoch', type: 'timestamp', format: 'x', analyzerType: 'TIME'},\n  {name: 'epoch_ms', type: 'timestamp', format: 'X', analyzerType: 'TIME'},\n  {name: 'meta_data', type: 'integer', format: '', analyzerType: 'INT'},\n  {name: 'start_ts', type: 'timestamp', format: 'x', analyzerType: 'TIME'},\n  {name: 'end_ts', type: 'timestamp', format: 'x', analyzerType: 'TIME'},\n  {name: 'time string', type: 'timestamp', format: 'YYYY/M/D H:m', analyzerType: 'DATETIME'}\n];\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-3.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {DEFAULT_TEXT_LABEL, DEFAULT_COLOR_UI} from '@kepler.gl/constants';\nconst {PointLayer, HexagonLayer} = KeplerGlLayers;\n\n// saved state v1 with split maps\nexport const savedStateV1 = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'fm8v2jcza',\n        label: 'eats_res_radius.csv',\n        color: [53, 92, 125],\n        allData: [\n          [\n            1000,\n            38.4163786,\n            -121.7922809,\n            false,\n            false,\n            1,\n            false,\n            'Hello',\n            '11/1/17 11:00',\n            1512780000000\n          ],\n          [\n            1000,\n            37.75159991,\n            -122.4761712,\n            false,\n            false,\n            2,\n            false,\n            'Smoothie',\n            '11/1/17 11:01',\n            1512780000000\n          ],\n          [\n            2000,\n            37.8323825,\n            -122.2736475,\n            false,\n            false,\n            3,\n            false,\n            'Milkshake',\n            '11/1/17 11:02',\n            1512770000000\n          ],\n          [\n            2000,\n            37.77565501,\n            -122.4403984,\n            false,\n            false,\n            4,\n            false,\n            'Amsterdam',\n            '11/1/17 11:03',\n            1512770000000\n          ],\n          [\n            3000,\n            37.79659543,\n            -122.4219072,\n            false,\n            false,\n            0,\n            false,\n            'Smoothie',\n            '11/1/17 11:04',\n            1512770000000\n          ],\n          [\n            3000,\n            37.7980847,\n            -122.4050548,\n            false,\n            false,\n            1,\n            false,\n            'Smoothie',\n            '11/1/17 11:00',\n            1512770000000\n          ],\n          [\n            3000,\n            37.79242329,\n            -122.3986584,\n            false,\n            false,\n            0,\n            false,\n            'World',\n            '11/1/17 11:00',\n            1512270000000\n          ],\n          [\n            3000,\n            37.79669585,\n            -122.4219416,\n            false,\n            false,\n            1,\n            false,\n            'yo',\n            '11/1/17 11:05',\n            1512270000000\n          ],\n          [\n            2000,\n            37.6169644,\n            -122.384047,\n            false,\n            false,\n            0,\n            false,\n            'really',\n            '11/1/17 11:10',\n            1512270000000\n          ]\n        ],\n        fields: [\n          {\n            name: 'deliver_radius',\n            type: 'integer',\n            format: ''\n          },\n          {\n            name: 'restaurant_lat',\n            type: 'real',\n            format: ''\n          },\n          {\n            name: 'restaurant_lng',\n            type: 'real',\n            format: ''\n          },\n          {\n            name: 'boolean',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'num_boolean',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'int',\n            type: 'integer',\n            format: ''\n          },\n          {\n            name: 'boolean_1',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'name',\n            type: 'string',\n            format: ''\n          },\n          {\n            name: 'time',\n            type: 'timestamp',\n            format: 'M/D/YYYY H:m'\n          },\n          {\n            name: 'epoch',\n            type: 'timestamp',\n            format: 'X'\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [],\n        layers: [\n          {\n            id: 'f24uw1',\n            type: 'point',\n            config: {\n              dataId: 'fm8v2jcza',\n              label: 'restaurant',\n              columnMode: 'points',\n              color: [18, 147, 154],\n              columns: {\n                lat: 'restaurant_lat',\n                lng: 'restaurant_lng',\n                altitude: null\n              },\n              isVisible: true,\n              visConfig: {\n                radius: 10,\n                fixedRadius: true,\n                opacity: 0.29,\n                outline: false,\n                thickness: 2,\n                colorRange: {\n                  name: 'ColorBrewer YlGn-6',\n                  type: 'sequential',\n                  category: 'ColorBrewer',\n                  colors: ['#006837', '#31a354', '#78c679', '#addd8e', '#d9f0a3', '#ffffcc'],\n                  reversed: true\n                },\n                radiusRange: [0, 50]\n              }\n            },\n            visualChannels: {\n              colorField: {name: 'deliver_radius', type: 'integer'},\n              colorScale: 'quantile',\n              sizeField: {name: 'deliver_radius', type: 'integer'},\n              sizeScale: 'sqrt'\n            }\n          },\n          {\n            id: '9x77w7h',\n            type: 'hexagon',\n            config: {\n              dataId: 'fm8v2jcza',\n              label: 'hexagon',\n              color: [221, 178, 124],\n              columns: {lat: 'restaurant_lat', lng: 'restaurant_lng'},\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                worldUnitSize: 2.73,\n                resolution: 8,\n                colorRange: {\n                  name: 'Uber Viz Diverging 1',\n                  type: 'diverging',\n                  category: 'Uber',\n                  colors: ['#00939C', '#85C4C8', '#FEEEE8', '#EC9370', '#C22E00'],\n                  reversed: false\n                },\n                coverage: 1,\n                sizeRange: [0, 500],\n                percentile: [0, 96.54],\n                elevationPercentile: [0, 100],\n                elevationScale: 5,\n                colorAggregation: 'count', // if associated visualChannel colorField value is null, it can only default to 'count'\n                sizeAggregation: 'average',\n                enable3d: false\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantize',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              fm8v2jcza: ['deliver_radius', 'boolean', 'num_boolean', 'int', 'boolean_1']\n            },\n            enabled: true\n          },\n          brush: {size: 0.5, enabled: false}\n        },\n        layerBlending: 'normal',\n        splitMaps: [\n          {\n            layers: {\n              f24uw1: {isAvailable: true, isVisible: false},\n              '9x77w7h': {isAvailable: true, isVisible: true}\n            }\n          },\n          {\n            layers: {\n              f24uw1: {isAvailable: true, isVisible: true},\n              '9x77w7h': {isAvailable: true, isVisible: false}\n            }\n          }\n        ]\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        buildingLayer: {\n          isVisible: false,\n          color: [32, 32, 37],\n          opacity: 0.7\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.77321745781781,\n        longitude: -122.566946177778,\n        pitch: 0,\n        zoom: 10.39910631173387,\n        isSplit: true\n      }\n    }\n  }\n};\n\nconst mergedLayer0 = new PointLayer({\n  id: 'f24uw1'\n});\n\nmergedLayer0.config = {\n  dataId: 'fm8v2jcza',\n  label: 'restaurant',\n  columnMode: 'points',\n  color: [18, 147, 154],\n  columns: {\n    lat: {\n      value: 'restaurant_lat',\n      fieldIdx: 1\n    },\n    lng: {\n      value: 'restaurant_lng',\n      fieldIdx: 2\n    },\n    altitude: {\n      fieldIdx: -1,\n      optional: true,\n      value: null\n    }\n  },\n  hidden: false,\n  isVisible: true,\n  colorField: {\n    name: 'deliver_radius',\n    id: 'deliver_radius',\n    displayName: 'deliver_radius',\n    type: 'integer',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'INT',\n    valueAccessor: values => values[0]\n  },\n  colorDomain: [1000, 1000, 2000, 2000, 2000, 3000, 3000, 3000, 3000],\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  colorScale: 'quantile',\n  strokeColorField: null,\n  strokeColorDomain: [0, 1],\n  strokeColorScale: 'quantile',\n  sizeField: {\n    name: 'deliver_radius',\n    id: 'deliver_radius',\n    displayName: 'deliver_radius',\n    type: 'integer',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'INT',\n    valueAccessor: values => values[0]\n  },\n  sizeScale: 'sqrt',\n  sizeDomain: [1000, 3000],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    radius: 10,\n    fixedRadius: true,\n    opacity: 0.29,\n    billboard: false,\n    outline: false,\n    filled: true,\n    thickness: 2,\n    colorRange: {\n      name: 'YlGn',\n      type: 'sequential',\n      category: 'ColorBrewer',\n      colors: ['#006837', '#31a354', '#78c679', '#addd8e', '#d9f0a3', '#ffffcc'],\n      reversed: true\n    },\n    strokeColorRange: {\n      name: 'YlGn',\n      type: 'sequential',\n      category: 'ColorBrewer',\n      colors: ['#006837', '#31a354', '#78c679', '#addd8e', '#d9f0a3', '#ffffcc'],\n      reversed: true\n    },\n    radiusRange: [0, 50],\n    strokeColor: [18, 147, 154],\n    allowHover: true,\n    showNeighborOnHover: false,\n    showHighlightColor: true\n  },\n  animation: {enabled: false}\n};\n\nconst mergedLayer1 = new HexagonLayer({\n  id: '9x77w7h'\n});\n\nmergedLayer1.config = {\n  dataId: 'fm8v2jcza',\n  label: 'hexagon',\n  color: [221, 178, 124],\n  columns: {\n    lat: {\n      value: 'restaurant_lat',\n      fieldIdx: 1\n    },\n    lng: {\n      value: 'restaurant_lng',\n      fieldIdx: 2\n    }\n  },\n  hidden: false,\n  isVisible: true,\n  colorField: null,\n  colorDomain: [0, 1],\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  colorScale: 'quantize',\n  sizeField: null,\n  sizeScale: 'linear',\n  sizeDomain: [0, 1],\n  textLabel: [DEFAULT_TEXT_LABEL],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    opacity: 0.8,\n    worldUnitSize: 2.73,\n    resolution: 8,\n    colorRange: {\n      name: 'Uber Viz Diverging',\n      type: 'diverging',\n      category: 'Uber',\n      colors: ['#00939C', '#85C4C8', '#FEEEE8', '#EC9370', '#C22E00'],\n      reversed: false\n    },\n    coverage: 1,\n    sizeRange: [0, 500],\n    percentile: [0, 96.54],\n    elevationPercentile: [0, 100],\n    elevationScale: 5,\n    enableElevationZoomFactor: true,\n    fixedHeight: false,\n    colorAggregation: 'count', // if associated colorField value is null, it can only default to 'count'\n    sizeAggregation: 'count', // if associated sizeField value is null, it can only default to 'count'\n    enable3d: false\n  },\n  animation: {enabled: false}\n};\n\nexport const mergedLayers = [mergedLayer0, mergedLayer1];\n\nexport const mergedSplitMaps = [\n  {\n    layers: {\n      f24uw1: false,\n      '9x77w7h': true\n    }\n  },\n  {\n    layers: {\n      f24uw1: true,\n      '9x77w7h': false\n    }\n  }\n];\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-4.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {DEFAULT_COLOR_UI} from '@kepler.gl/constants';\nconst {PointLayer} = KeplerGlLayers;\n\nexport const stateSavedV1 = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: '8ppj5gfrs',\n        label: 'eats_res_radius.csv',\n        color: [143, 47, 191],\n        allData: [\n          [1000, 38.4163786, -121.7922809, false, true, 1, false, 'Hello'],\n          [1000, 37.75159991, -122.4761712, false, false, 2, false, 'Smoothie'],\n          [2000, 37.8323825, -122.2736475, false, true, 3, false, 'Milkshake'],\n          [2000, 37.77565501, -122.4403984, false, true, 4, false, 'Amsterdam'],\n          [3000, 37.79659543, -122.4219072, false, true, 0, false, 'Smoothie'],\n          [3000, 37.7980847, -122.4050548, false, false, 1, false, 'Smoothie'],\n          [3000, 37.79242329, -122.3986584, false, false, 0, false, 'World'],\n          [3000, 37.79669585, -122.4219416, false, false, 1, false, 'yo'],\n          [2000, 37.6169644, -122.384047, false, false, 0, false, 'really']\n        ],\n        fields: [\n          {\n            name: 'radius',\n            type: 'integer',\n            format: ''\n          },\n          {\n            name: 'point_lat',\n            type: 'real',\n            format: ''\n          },\n          {\n            name: 'point_lng',\n            type: 'real',\n            format: ''\n          },\n          {\n            name: 'boolean',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'num_boolean',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'int_num',\n            type: 'integer',\n            format: ''\n          },\n          {\n            name: 'boolean_1',\n            type: 'boolean',\n            format: ''\n          },\n          {\n            name: 'name',\n            type: 'string',\n            format: ''\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [],\n        layers: [\n          {\n            id: 'k2ghkng',\n            type: 'point',\n            config: {\n              dataId: '8ppj5gfrs',\n              label: 'point',\n              columnMode: 'points',\n              color: [23, 184, 190, 255],\n              columns: {\n                lat: 'point_lat',\n                lng: 'point_lng',\n                altitude: null\n              },\n              isVisible: true,\n              visConfig: {\n                radius: 12.5,\n                fixedRadius: false,\n                opacity: 0.8,\n                outline: false,\n                thickness: 2,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radiusRange: [0, 50],\n                'hi-precision': false\n              },\n              textLabel: {\n                field: {\n                  name: 'name',\n                  type: 'string'\n                },\n                color: [184, 15, 135, 255],\n                size: 27,\n                offset: [-10, 0],\n                anchor: 'end'\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              '8ppj5gfrs': ['radius', 'boolean', 'num_boolean', 'int_num', 'boolean_1']\n            },\n            enabled: true\n          },\n          brush: {\n            size: 0.5,\n            enabled: false\n          }\n        },\n        layerBlending: 'normal',\n        splitMaps: []\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.871422572545065,\n        longitude: -122.32925002428057,\n        pitch: 0,\n        zoom: 10.386420916638542,\n        isSplit: false\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        mapStyles: {}\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Tue Oct 02 2018 16:11:32 GMT-0700 (Pacific Daylight Time)'\n  }\n};\n\nconst mergedLayer0 = new PointLayer({\n  id: 'k2ghkng'\n});\n\nmergedLayer0.config = {\n  dataId: '8ppj5gfrs',\n  label: 'point',\n  columnMode: 'points',\n  color: [23, 184, 190, 255],\n  columns: {\n    lat: {\n      value: 'point_lat',\n      fieldIdx: 1\n    },\n    lng: {\n      value: 'point_lng',\n      fieldIdx: 2\n    },\n    altitude: {\n      fieldIdx: -1,\n      optional: true,\n      value: null\n    }\n  },\n  hidden: false,\n  isVisible: true,\n  colorField: null,\n  colorScale: 'quantile',\n  colorDomain: [0, 1],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  strokeColorDomain: [0, 1],\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  sizeField: null,\n  sizeDomain: [0, 1],\n  sizeScale: 'linear',\n  textLabel: [\n    {\n      field: {\n        name: 'name',\n        id: 'name',\n        displayName: 'name',\n        type: 'string',\n        format: '',\n        fieldIdx: 7,\n        analyzerType: 'STRING',\n        valueAccessor: values => values[7]\n      },\n      color: [184, 15, 135, 255],\n      size: 27,\n      offset: [-10, 0],\n      anchor: 'end',\n      alignment: 'center',\n      outlineWidth: 0,\n      outlineColor: [255, 0, 0, 255],\n      background: false,\n      backgroundColor: [0, 0, 200, 255]\n    }\n  ],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    radius: 12.5,\n    billboard: false,\n    fixedRadius: false,\n    opacity: 0.8,\n    outline: false,\n    thickness: 2,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    filled: true,\n    strokeColorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    strokeColor: [23, 184, 190, 255],\n    radiusRange: [0, 50],\n    allowHover: true,\n    showNeighborOnHover: false,\n    showHighlightColor: true\n  },\n  animation: {enabled: false}\n};\n\nexport const mergedLayers = [mergedLayer0];\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-5.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport tripGeojson, {\n  timeStampDomain,\n  tripBounds,\n  dataToTimeStamp\n} from 'test/fixtures/trip-geojson';\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {DEFAULT_LAYER_OPACITY, DEFAULT_TEXT_LABEL, DEFAULT_COLOR_UI} from '@kepler.gl/constants';\nconst {TripLayer} = KeplerGlLayers;\n\nexport const savedStateV1TripGeoJson = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'trip_data',\n        label: 'Trip Data',\n        color: [162, 212, 171],\n        allData: [\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 0\n              },\n              geometry: tripGeojson.features[0].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'B',\n                index: 1\n              },\n              geometry: tripGeojson.features[1].geometry\n            },\n            'B'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 2\n              },\n              geometry: tripGeojson.features[2].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 3\n              },\n              geometry: tripGeojson.features[3].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 4\n              },\n              geometry: tripGeojson.features[4].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 5\n              },\n              geometry: tripGeojson.features[5].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'B',\n                index: 6\n              },\n              geometry: tripGeojson.features[6].geometry\n            },\n            'B'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 7\n              },\n              geometry: tripGeojson.features[7].geometry\n            },\n            'A'\n          ],\n          [\n            {\n              type: 'Feature',\n              properties: {\n                vendor: 'A',\n                index: 8\n              },\n              geometry: tripGeojson.features[8].geometry\n            },\n            'A'\n          ]\n        ],\n        fields: [\n          {\n            name: '_geojson',\n            type: 'geojson',\n            format: ''\n          },\n          {\n            name: 'vendor',\n            type: 'string',\n            format: ''\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [],\n        layers: [\n          {\n            id: 'trip-0',\n            type: 'trip',\n            config: {\n              dataId: 'trip_data',\n              columnMode: 'geojson',\n              label: 'Trip Data',\n              color: [0, 0, 0],\n              columns: {\n                geojson: '_geojson'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 0.5,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                billboard: false,\n                sizeRange: [0, 10]\n              },\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              trip_data: ['vendor']\n            },\n            enabled: true\n          },\n          brush: {\n            size: 0.5,\n            enabled: false\n          }\n        },\n        layerBlending: 'normal',\n        splitMaps: [],\n        animationConfig: {\n          currentTime: 1565577261000\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.75043,\n        longitude: -122.34679,\n        pitch: 0,\n        zoom: 9,\n        isSplit: false\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {},\n        threeDBuildingColor: [209, 206, 199],\n        mapStyles: {}\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Sun Sep 15 2019 18:49:29 GMT-0700 (PDT)'\n  }\n};\n\nexport const mergedLayer0 = new TripLayer({\n  id: 'trip-0'\n});\n\nmergedLayer0.config = {\n  dataId: 'trip_data',\n  label: 'Trip Data',\n  columnMode: 'geojson',\n  color: [0, 0, 0],\n  columns: {\n    altitude: {value: null, fieldIdx: -1, optional: true},\n    geojson: {value: '_geojson', fieldIdx: 0, optional: false},\n    id: {value: null, fieldIdx: -1, optional: true},\n    lat: {value: null, fieldIdx: -1, optional: true},\n    lng: {value: null, fieldIdx: -1, optional: true},\n    timestamp: {value: null, fieldIdx: -1, optional: true}\n  },\n  hidden: false,\n  isVisible: true,\n  isConfigActive: false,\n  highlightColor: [252, 242, 26, 255],\n  colorField: null,\n  colorDomain: [0, 1],\n  colorScale: 'quantile',\n  sizeDomain: [0, 1],\n  sizeScale: 'linear',\n  sizeField: null,\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI\n  },\n  visConfig: {\n    opacity: DEFAULT_LAYER_OPACITY,\n    thickness: 0.5,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    trailLength: 180,\n    fadeTrail: true,\n    billboard: false,\n    sizeRange: [0, 10]\n  },\n  textLabel: [DEFAULT_TEXT_LABEL],\n  animation: {enabled: true, domain: timeStampDomain}\n};\n\nmergedLayer0.dataToFeature = [\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 0\n    },\n    geometry: tripGeojson.features[0].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'B',\n      index: 1\n    },\n    geometry: tripGeojson.features[1].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 2\n    },\n    geometry: tripGeojson.features[2].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 3\n    },\n    geometry: tripGeojson.features[3].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 4\n    },\n    geometry: tripGeojson.features[4].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 5\n    },\n    geometry: tripGeojson.features[5].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'B',\n      index: 6\n    },\n    geometry: tripGeojson.features[6].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 7\n    },\n    geometry: tripGeojson.features[7].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {\n      vendor: 'A',\n      index: 8\n    },\n    geometry: tripGeojson.features[8].geometry\n  }\n];\nmergedLayer0.dataToTimeStamp = dataToTimeStamp;\nmergedLayer0.meta.bounds = tripBounds;\nmergedLayer0.meta.featureTypes = {line: true};\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-6.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedStateWIthNonValidFilters = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'a5ybmwl2d',\n        label: 'geojson_as_string_small.csv',\n        color: [53, 92, 125],\n        allData: [\n          [\n            7014,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522],[-74.146471,40.836645],[-74.142598,40.833128],[-74.140177,40.832492],[-74.136732,40.836791],[-74.142706,40.840604],[-74.144667,40.84312],[-74.142936,40.844974],[-74.136054,40.84119],[-74.1356,40.841703],[-74.133665,40.840712],[-74.133028,40.841321],[-74.13274,40.839586],[-74.121853,40.834098],[-74.121087,40.831],[-74.124447,40.822169],[-74.130031,40.819962],[-74.149242,40.830537],[-74.148818,40.830916],[-74.150888,40.833143],[-74.151923,40.832074],[-74.158491,40.835947]]]}',\n            'POLYGON ((-74.158491 40.835947, -74.157914 40.83902, -74.148473 40.834522, -74.146471 40.836645, -74.142598 40.833128, -74.140177 40.832492, -74.136732 40.836791, -74.142706 40.840604, -74.144667 40.84312, -74.142936 40.844974, -74.136054 40.84119, -74.1356 40.841703, -74.133665 40.840712, -74.133028 40.841321, -74.13274 40.839586, -74.121853 40.834098, -74.121087 40.831, -74.124447 40.822169, -74.130031 40.819962, -74.149242 40.830537, -74.148818 40.830916, -74.150888 40.833143, -74.151923 40.832074, -74.158491 40.835947))',\n            1.420645391,\n            2.32754338,\n            'C_Medium_High',\n            7014,\n            0.719251337,\n            1496,\n            29.07437458,\n            540.4402429,\n            0.681149733,\n            0.988636364,\n            17.92821726,\n            13.56510275,\n            0.283\n          ],\n          [\n            7016,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.326141,40.656094],[-74.326593,40.657702],[-74.327693,40.662904],[-74.325199,40.667596],[-74.326093,40.668202],[-74.323615,40.670664],[-74.323228,40.672276],[-74.324391,40.67444],[-74.322371,40.677443],[-74.310392,40.678402],[-74.312993,40.674703],[-74.312093,40.674304],[-74.314552,40.673889],[-74.312827,40.673282],[-74.307888,40.674387],[-74.306792,40.672102],[-74.290976,40.673465],[-74.29185,40.671802],[-74.290689,40.670646],[-74.288727,40.670925],[-74.285991,40.667001],[-74.284275,40.658075],[-74.283511,40.658187],[-74.283369,40.657406],[-74.285786,40.655022],[-74.282693,40.653502],[-74.280691,40.645712],[-74.282183,40.644572],[-74.281687,40.643381],[-74.287325,40.639999],[-74.29577,40.638581],[-74.304907,40.633459],[-74.319678,40.641989],[-74.316485,40.645194],[-74.320663,40.647344],[-74.312298,40.654042],[-74.31687,40.656696]]]}',\n            'POLYGON ((-74.31687 40.656696, -74.319449 40.658154, -74.326141 40.656094, -74.326593 40.657702, -74.327693 40.662904, -74.325199 40.667596, -74.326093 40.668202, -74.323615 40.670664, -74.323228 40.672276, -74.324391 40.67444, -74.322371 40.677443, -74.310392 40.678402, -74.312993 40.674703, -74.312093 40.674304, -74.314552 40.673889, -74.312827 40.673282, -74.307888 40.674387, -74.306792 40.672102, -74.290976 40.673465, -74.29185 40.671802, -74.290689 40.670646, -74.288727 40.670925, -74.285991 40.667001, -74.284275 40.658075, -74.283511 40.658187, -74.283369 40.657406, -74.285786 40.655022, -74.282693 40.653502, -74.280691 40.645712, -74.282183 40.644572, -74.281687 40.643381, -74.287325 40.639999, -74.29577 40.638581, -74.304907 40.633459, -74.319678 40.641989, -74.316485 40.645194, -74.320663 40.647344, -74.312298 40.654042, -74.31687 40.656696))',\n            4.873351888,\n            2.125379919,\n            'C_Medium_High',\n            7016,\n            0.621409029,\n            5848,\n            27.60106201,\n            528.1274711,\n            0.678522572,\n            0.998290014,\n            19.05388261,\n            12.47290995,\n            0.256\n          ],\n          [\n            7023,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.387589,40.632238],[-74.401749,40.640393],[-74.386745,40.653316],[-74.374682,40.646553],[-74.372634,40.6468],[-74.371828,40.644885],[-74.374122,40.642352],[-74.37327,40.641829],[-74.375777,40.636295],[-74.37717,40.634842],[-74.382333,40.632417],[-74.384457,40.630453],[-74.387589,40.632238]]]}',\n            'POLYGON ((-74.387589 40.632238, -74.401749 40.640393, -74.386745 40.653316, -74.374682 40.646553, -74.372634 40.6468, -74.371828 40.644885, -74.374122 40.642352, -74.37327 40.641829, -74.375777 40.636295, -74.37717 40.634842, -74.382333 40.632417, -74.384457 40.630453, -74.387589 40.632238))',\n            1.352750157,\n            1.975548142,\n            'C_Medium_High',\n            7023,\n            0.580808081,\n            1782,\n            23.85239437,\n            457.401356,\n            0.673400673,\n            0.996071829,\n            18.41337089,\n            11.17832315,\n            0.241\n          ],\n          [\n            7029,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.165995,40.747969],[-74.164799,40.754718],[-74.155877,40.751722],[-74.154327,40.753966],[-74.145563,40.751975],[-74.144976,40.748161],[-74.14586,40.748031],[-74.140783,40.742694],[-74.139386,40.743314],[-74.137492,40.741398],[-74.142688,40.739301],[-74.145288,40.735701],[-74.147188,40.734401],[-74.155687,40.733501],[-74.15998,40.734485],[-74.162093,40.736109],[-74.163788,40.739201],[-74.165987,40.745199],[-74.165995,40.747969]]]}',\n            'POLYGON ((-74.165995 40.747969, -74.164799 40.754718, -74.155877 40.751722, -74.154327 40.753966, -74.145563 40.751975, -74.144976 40.748161, -74.14586 40.748031, -74.140783 40.742694, -74.139386 40.743314, -74.137492 40.741398, -74.142688 40.739301, -74.145288 40.735701, -74.147188 40.734401, -74.155687 40.733501, -74.15998 40.734485, -74.162093 40.736109, -74.163788 40.739201, -74.165987 40.745199, -74.165995 40.747969))',\n            1.446889939,\n            2.701389429,\n            'C_Medium_High',\n            7029,\n            0.814644137,\n            7812,\n            39.13775443,\n            545.4891226,\n            0.653481823,\n            0.974782386,\n            17.88895163,\n            13.72188322,\n            0.474\n          ],\n          [\n            7045,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.361356,40.939299],[-74.370498,40.926937],[-74.37404,40.927298],[-74.374216,40.921969],[-74.371645,40.92135],[-74.366428,40.918141],[-74.356676,40.915791],[-74.349697,40.916512],[-74.347883,40.91351],[-74.349593,40.91185],[-74.353183,40.910446],[-74.350384,40.908593],[-74.351179,40.907344],[-74.349633,40.906311],[-74.351883,40.90419],[-74.351747,40.902647],[-74.349573,40.903845],[-74.349271,40.902641],[-74.344758,40.902596],[-74.345589,40.900045],[-74.339928,40.899308],[-74.339554,40.895904],[-74.338613,40.895769],[-74.33944,40.893881],[-74.335743,40.893239],[-74.3389,40.892087],[-74.339694,40.889839],[-74.339646,40.888758],[-74.338118,40.887173],[-74.339575,40.883893],[-74.340271,40.879528],[-74.338075,40.879652],[-74.337614,40.878206],[-74.33507,40.877426],[-74.33531,40.876555],[-74.337696,40.87591],[-74.337808,40.875101],[-74.334259,40.873471],[-74.337288,40.872611],[-74.337418,40.87179],[-74.335937,40.870521],[-74.33806,40.870059],[-74.336714,40.869079],[-74.33765,40.868402],[-74.33768,40.866752],[-74.339806,40.866539],[-74.34018,40.868365],[-74.339545,40.874876],[-74.341626,40.877524],[-74.340485,40.882283],[-74.342874,40.885385],[-74.346198,40.887367],[-74.35009,40.887702],[-74.352792,40.886265],[-74.355507,40.888913],[-74.359916,40.886643],[-74.360096,40.887329],[-74.361792,40.887158],[-74.36087,40.883379],[-74.358905,40.883365],[-74.358888,40.880786],[-74.357662,40.880344],[-74.357789,40.879259],[-74.358676,40.879136],[-74.358091,40.878002],[-74.362636,40.877577],[-74.360456,40.872665],[-74.361843,40.87065],[-74.365778,40.871801],[-74.366753,40.874063],[-74.37127,40.876723],[-74.369921,40.878054],[-74.370331,40.879192],[-74.371574,40.879878],[-74.37187,40.878855],[-74.373453,40.878978],[-74.373051,40.880533],[-74.374017,40.882604],[-74.3772,40.886221],[-74.376296,40.888095],[-74.376308,40.890713],[-74.374745,40.893264],[-74.374127,40.898616],[-74.374902,40.901059],[-74.378188,40.901844],[-74.378509,40.900151],[-74.383293,40.899546],[-74.384277,40.902503],[-74.383622,40.902564],[-74.383706,40.903409],[-74.384609,40.903338],[-74.387159,40.909755],[-74.390966,40.909837],[-74.392542,40.913437],[-74.393499,40.91741],[-74.390968,40.919447],[-74.394272,40.926232],[-74.388795,40.930394],[-74.389726,40.930836],[-74.38883,40.931932],[-74.389795,40.931872],[-74.390036,40.932698],[-74.374793,40.940133],[-74.374929,40.941559],[-74.37655,40.942487],[-74.381516,40.942622],[-74.380974,40.946451],[-74.374397,40.954036],[-74.367575,40.955078],[-74.365156,40.954335],[-74.363667,40.955394],[-74.364141,40.954467],[-74.362181,40.951667],[-74.36267,40.944287],[-74.360703,40.940588],[-74.361356,40.939299]]]}',\n            'POLYGON ((-74.361356 40.939299, -74.370498 40.926937, -74.37404 40.927298, -74.374216 40.921969, -74.371645 40.92135, -74.366428 40.918141, -74.356676 40.915791, -74.349697 40.916512, -74.347883 40.91351, -74.349593 40.91185, -74.353183 40.910446, -74.350384 40.908593, -74.351179 40.907344, -74.349633 40.906311, -74.351883 40.90419, -74.351747 40.902647, -74.349573 40.903845, -74.349271 40.902641, -74.344758 40.902596, -74.345589 40.900045, -74.339928 40.899308, -74.339554 40.895904, -74.338613 40.895769, -74.33944 40.893881, -74.335743 40.893239, -74.3389 40.892087, -74.339694 40.889839, -74.339646 40.888758, -74.338118 40.887173, -74.339575 40.883893, -74.340271 40.879528, -74.338075 40.879652, -74.337614 40.878206, -74.33507 40.877426, -74.33531 40.876555, -74.337696 40.87591, -74.337808 40.875101, -74.334259 40.873471, -74.337288 40.872611, -74.337418 40.87179, -74.335937 40.870521, -74.33806 40.870059, -74.336714 40.869079, -74.33765 40.868402, -74.33768 40.866752, -74.339806 40.866539, -74.34018 40.868365, -74.339545 40.874876, -74.341626 40.877524, -74.340485 40.882283, -74.342874 40.885385, -74.346198 40.887367, -74.35009 40.887702, -74.352792 40.886265, -74.355507 40.888913, -74.359916 40.886643, -74.360096 40.887329, -74.361792 40.887158, -74.36087 40.883379, -74.358905 40.883365, -74.358888 40.880786, -74.357662 40.880344, -74.357789 40.879259, -74.358676 40.879136, -74.358091 40.878002, -74.362636 40.877577, -74.360456 40.872665, -74.361843 40.87065, -74.365778 40.871801, -74.366753 40.874063, -74.37127 40.876723, -74.369921 40.878054, -74.370331 40.879192, -74.371574 40.879878, -74.37187 40.878855, -74.373453 40.878978, -74.373051 40.880533, -74.374017 40.882604, -74.3772 40.886221, -74.376296 40.888095, -74.376308 40.890713, -74.374745 40.893264, -74.374127 40.898616, -74.374902 40.901059, -74.378188 40.901844, -74.378509 40.900151, -74.383293 40.899546, -74.384277 40.902503, -74.383622 40.902564, -74.383706 40.903409, -74.384609 40.903338, -74.387159 40.909755, -74.390966 40.909837, -74.392542 40.913437, -74.393499 40.91741, -74.390968 40.919447, -74.394272 40.926232, -74.388795 40.930394, -74.389726 40.930836, -74.38883 40.931932, -74.389795 40.931872, -74.390036 40.932698, -74.374793 40.940133, -74.374929 40.941559, -74.37655 40.942487, -74.381516 40.942622, -74.380974 40.946451, -74.374397 40.954036, -74.367575 40.955078, -74.365156 40.954335, -74.363667 40.955394, -74.364141 40.954467, -74.362181 40.951667, -74.36267 40.944287, -74.360703 40.940588, -74.361356 40.939299))',\n            7.403173506,\n            2.253640666,\n            'B_Low_Suburban',\n            7045,\n            0.477082503,\n            2509,\n            28.18247299,\n            519.2220835,\n            0.672777999,\n            0.996014348,\n            18.72091503,\n            12.48069003,\n            0.258\n          ],\n          [\n            7073,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.087219,40.799287],[-74.105261,40.838083],[-74.113924,40.842218],[-74.121404,40.847184],[-74.119291,40.851674],[-74.119462,40.85335],[-74.11538,40.850927],[-74.109,40.845092],[-74.078747,40.827548],[-74.070783,40.822082],[-74.066115,40.817041],[-74.061984,40.810171],[-74.060685,40.8057],[-74.065135,40.80302],[-74.068585,40.7986],[-74.071399,40.797241],[-74.077,40.796379],[-74.077787,40.792796],[-74.081794,40.787994],[-74.087219,40.799287]]]}',\n            'POLYGON ((-74.087219 40.799287, -74.105261 40.838083, -74.113924 40.842218, -74.121404 40.847184, -74.119291 40.851674, -74.119462 40.85335, -74.11538 40.850927, -74.109 40.845092, -74.078747 40.827548, -74.070783 40.822082, -74.066115 40.817041, -74.061984 40.810171, -74.060685 40.8057, -74.065135 40.80302, -74.068585 40.7986, -74.071399 40.797241, -74.077 40.796379, -74.077787 40.792796, -74.081794 40.787994, -74.087219 40.799287))',\n            4.076259122,\n            2.483658001,\n            'B_Low_Suburban',\n            7073,\n            0.745130406,\n            3029,\n            35.66977674,\n            638.4631914,\n            0.671838891,\n            0.990756025,\n            18.39194713,\n            15.62142573,\n            0.333\n          ],\n          [\n            7081,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.347067,40.691722],[-74.36039,40.699821],[-74.352171,40.70148],[-74.342221,40.707846],[-74.333594,40.716901],[-74.330619,40.716055],[-74.332088,40.720176],[-74.325596,40.717075],[-74.321408,40.719072],[-74.315991,40.719199],[-74.311691,40.715601],[-74.309791,40.715501],[-74.308793,40.714508],[-74.302092,40.713301],[-74.299496,40.713889],[-74.29869,40.71479],[-74.296151,40.712434],[-74.297212,40.710298],[-74.301677,40.707498],[-74.301078,40.704678],[-74.306692,40.703802],[-74.309119,40.702625],[-74.310906,40.700392],[-74.310666,40.699622],[-74.307393,40.698801],[-74.307893,40.695999],[-74.30664,40.694304],[-74.308655,40.691171],[-74.312226,40.689892],[-74.310772,40.683794],[-74.307292,40.682506],[-74.308989,40.680407],[-74.307753,40.680292],[-74.310392,40.678402],[-74.322575,40.677433],[-74.347067,40.691722]]]}',\n            'POLYGON ((-74.347067 40.691722, -74.36039 40.699821, -74.352171 40.70148, -74.342221 40.707846, -74.333594 40.716901, -74.330619 40.716055, -74.332088 40.720176, -74.325596 40.717075, -74.321408 40.719072, -74.315991 40.719199, -74.311691 40.715601, -74.309791 40.715501, -74.308793 40.714508, -74.302092 40.713301, -74.299496 40.713889, -74.29869 40.71479, -74.296151 40.712434, -74.297212 40.710298, -74.301677 40.707498, -74.301078 40.704678, -74.306692 40.703802, -74.309119 40.702625, -74.310906 40.700392, -74.310666 40.699622, -74.307393 40.698801, -74.307893 40.695999, -74.30664 40.694304, -74.308655 40.691171, -74.312226 40.689892, -74.310772 40.683794, -74.307292 40.682506, -74.308989 40.680407, -74.307753 40.680292, -74.310392 40.678402, -74.322575 40.677433, -74.347067 40.691722))',\n            5.142008449,\n            2.352650655,\n            'C_Medium_High',\n            7081,\n            0.65038271,\n            4442,\n            29.37274368,\n            514.8178812,\n            0.693156236,\n            0.997748762,\n            18.36100331,\n            12.61739583,\n            0.273\n          ],\n          [\n            7102,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.184345,40.727604],[-74.183145,40.730677],[-74.181958,40.730397],[-74.181105,40.732722],[-74.181977,40.732938],[-74.181504,40.734135],[-74.18328,40.734558],[-74.182741,40.735778],[-74.179407,40.737014],[-74.182582,40.73798],[-74.181787,40.739633],[-74.179055,40.737705],[-74.178029,40.739555],[-74.179295,40.739935],[-74.17785,40.742469],[-74.18066,40.74342],[-74.179821,40.744703],[-74.177068,40.743847],[-74.175752,40.745597],[-74.17646,40.745849],[-74.175989,40.747417],[-74.173784,40.746929],[-74.173566,40.747955],[-74.170978,40.747424],[-74.167568,40.748054],[-74.167484,40.747446],[-74.165976,40.748091],[-74.165987,40.745199],[-74.163788,40.739201],[-74.161669,40.735781],[-74.166099,40.732923],[-74.175517,40.72346],[-74.176772,40.723054],[-74.17736,40.723261],[-74.176965,40.723961],[-74.180517,40.724365],[-74.179633,40.725829],[-74.180319,40.726113],[-74.184236,40.724266],[-74.183892,40.72506],[-74.185205,40.725405],[-74.184345,40.727604]]]}',\n            'POLYGON ((-74.184345 40.727604, -74.183145 40.730677, -74.181958 40.730397, -74.181105 40.732722, -74.181977 40.732938, -74.181504 40.734135, -74.18328 40.734558, -74.182741 40.735778, -74.179407 40.737014, -74.182582 40.73798, -74.181787 40.739633, -74.179055 40.737705, -74.178029 40.739555, -74.179295 40.739935, -74.17785 40.742469, -74.18066 40.74342, -74.179821 40.744703, -74.177068 40.743847, -74.175752 40.745597, -74.17646 40.745849, -74.175989 40.747417, -74.173784 40.746929, -74.173566 40.747955, -74.170978 40.747424, -74.167568 40.748054, -74.167484 40.747446, -74.165976 40.748091, -74.165987 40.745199, -74.163788 40.739201, -74.161669 40.735781, -74.166099 40.732923, -74.175517 40.72346, -74.176772 40.723054, -74.17736 40.723261, -74.176965 40.723961, -74.180517 40.724365, -74.179633 40.725829, -74.180319 40.726113, -74.184236 40.724266, -74.183892 40.72506, -74.185205 40.725405, -74.184345 40.727604))',\n            1.203818912,\n            3.301076417,\n            'C_Medium_High',\n            7102,\n            0.832516129,\n            3875,\n            55.20608285,\n            769.5874437,\n            0.645935484,\n            0.984258065,\n            18.16678028,\n            19.06305599,\n            0.3\n          ],\n          [\n            7110,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.1404,40.805639],[-74.146391,40.806291],[-74.149012,40.80766],[-74.149521,40.806858],[-74.148617,40.806521],[-74.175644,40.809531],[-74.183836,40.812225],[-74.179511,40.816664],[-74.178999,40.818771],[-74.176443,40.821854],[-74.174992,40.821802],[-74.162628,40.837869],[-74.151923,40.832074],[-74.150888,40.833143],[-74.148818,40.830916],[-74.149242,40.830537],[-74.130031,40.819962],[-74.136487,40.8182],[-74.137287,40.8173],[-74.138787,40.8074],[-74.1404,40.805639]]]}',\n            'POLYGON ((-74.1404 40.805639, -74.146391 40.806291, -74.149012 40.80766, -74.149521 40.806858, -74.148617 40.806521, -74.175644 40.809531, -74.183836 40.812225, -74.179511 40.816664, -74.178999 40.818771, -74.176443 40.821854, -74.174992 40.821802, -74.162628 40.837869, -74.151923 40.832074, -74.150888 40.833143, -74.148818 40.830916, -74.149242 40.830537, -74.130031 40.819962, -74.136487 40.8182, -74.137287 40.8173, -74.138787 40.8074, -74.1404 40.805639))',\n            3.418972265,\n            2.350813772,\n            'C_Medium_High',\n            7110,\n            0.740131579,\n            7600,\n            28.24510978,\n            494.0816839,\n            0.686578947,\n            0.988815789,\n            18.33537813,\n            12.12610704,\n            0.268\n          ],\n          [\n            7416,\n            '{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-74.566993,41.087294],[-74.564908,41.089339],[-74.56559,41.090507],[-74.564446,41.091931],[-74.562675,41.093801],[-74.560495,41.094523],[-74.559465,41.093899],[-74.558011,41.094312],[-74.556055,41.091781],[-74.554015,41.092675],[-74.552353,41.091647],[-74.552998,41.09013],[-74.556709,41.087409],[-74.558078,41.085348],[-74.56136,41.08293],[-74.563205,41.082847],[-74.565111,41.081667],[-74.567341,41.082366],[-74.566524,41.085226],[-74.566993,41.087294]]],[[[-74.593264,41.088526],[-74.601504,41.094096],[-74.602269,41.092905],[-74.606875,41.096689],[-74.606957,41.097715],[-74.638551,41.118611],[-74.634186,41.132604],[-74.633166,41.133324],[-74.629712,41.131934],[-74.625246,41.136424],[-74.620677,41.139411],[-74.613785,41.139652],[-74.6101,41.137965],[-74.610517,41.136736],[-74.606433,41.135423],[-74.60466,41.136534],[-74.598719,41.137644],[-74.595925,41.139251],[-74.593358,41.139231],[-74.579959,41.144635],[-74.576893,41.136939],[-74.573312,41.136907],[-74.571246,41.137717],[-74.569941,41.136878],[-74.575411,41.101456],[-74.577128,41.101248],[-74.574449,41.101263],[-74.567456,41.103641],[-74.562238,41.103369],[-74.55789,41.106705],[-74.552862,41.10746],[-74.557344,41.105958],[-74.56193,41.102739],[-74.569487,41.100323],[-74.575935,41.095058],[-74.578362,41.09142],[-74.583558,41.087448],[-74.585763,41.083491],[-74.593264,41.088526]]]]}',\n            'MULTIPOLYGON (((-74.566993 41.087294, -74.564908 41.089339, -74.56559 41.090507, -74.564446 41.091931, -74.562675 41.093801, -74.560495 41.094523, -74.559465 41.093899, -74.558011 41.094312, -74.556055 41.091781, -74.554015 41.092675, -74.552353 41.091647, -74.552998 41.09013, -74.556709 41.087409, -74.558078 41.085348, -74.56136 41.08293, -74.563205 41.082847, -74.565111 41.081667, -74.567341 41.082366, -74.566524 41.085226, -74.566993 41.087294)), ((-74.593264 41.088526, -74.601504 41.094096, -74.602269 41.092905, -74.606875 41.096689, -74.606957 41.097715, -74.638551 41.118611, -74.634186 41.132604, -74.633166 41.133324, -74.629712 41.131934, -74.625246 41.136424, -74.620677 41.139411, -74.613785 41.139652, -74.6101 41.137965, -74.610517 41.136736, -74.606433 41.135423, -74.60466 41.136534, -74.598719 41.137644, -74.595925 41.139251, -74.593358 41.139231, -74.579959 41.144635, -74.576893 41.136939, -74.573312 41.136907, -74.571246 41.137717, -74.569941 41.136878, -74.575411 41.101456, -74.577128 41.101248, -74.574449 41.101263, -74.567456 41.103641, -74.562238 41.103369, -74.55789 41.106705, -74.552862 41.10746, -74.557344 41.105958, -74.56193 41.102739, -74.569487 41.100323, -74.575935 41.095058, -74.578362 41.09142, -74.583558 41.087448, -74.585763 41.083491, -74.593264 41.088526)))',\n            9.599149618,\n            2.546774404,\n            'A_Low_Rural',\n            7416,\n            0.362944162,\n            394,\n            13.81679389,\n            234.9184295,\n            0.748730964,\n            0.997461929,\n            14.28320611,\n            7.401229981,\n            0.066\n          ],\n          [\n            7481,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158531,41.025141],[-74.155604,41.02585],[-74.155079,41.0217],[-74.147648,41.022058],[-74.149441,41.013336],[-74.151076,41.010084],[-74.142115,41.008711],[-74.141446,41.007704],[-74.137536,41.007495],[-74.141401,41.005903],[-74.145988,41.001919],[-74.155782,41.001404],[-74.157154,40.998933],[-74.153255,40.996719],[-74.151282,40.993204],[-74.153874,40.98999],[-74.149887,40.987538],[-74.142812,40.986407],[-74.141437,40.984207],[-74.14121,40.982761],[-74.142619,40.981621],[-74.149088,40.971137],[-74.17021,40.983865],[-74.170988,40.976997],[-74.191835,40.978688],[-74.189987,40.992282],[-74.189546,41.003824],[-74.18806,41.00691],[-74.187244,41.01685],[-74.16758,41.023781],[-74.163392,41.022212],[-74.158531,41.025141]]]}',\n            'POLYGON ((-74.158531 41.025141, -74.155604 41.02585, -74.155079 41.0217, -74.147648 41.022058, -74.149441 41.013336, -74.151076 41.010084, -74.142115 41.008711, -74.141446 41.007704, -74.137536 41.007495, -74.141401 41.005903, -74.145988 41.001919, -74.155782 41.001404, -74.157154 40.998933, -74.153255 40.996719, -74.151282 40.993204, -74.153874 40.98999, -74.149887 40.987538, -74.142812 40.986407, -74.141437 40.984207, -74.14121 40.982761, -74.142619 40.981621, -74.149088 40.971137, -74.17021 40.983865, -74.170988 40.976997, -74.191835 40.978688, -74.189987 40.992282, -74.189546 41.003824, -74.18806 41.00691, -74.187244 41.01685, -74.16758 41.023781, -74.163392 41.022212, -74.158531 41.025141))',\n            6.59865985,\n            2.21913015,\n            'B_Low_Suburban',\n            7481,\n            0.507943713,\n            4406,\n            37.98110631,\n            779.7471722,\n            0.639809351,\n            0.997049478,\n            20.78244176,\n            16.88378255,\n            0.262\n          ],\n          [\n            7495,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.164572,41.104182],[-74.166137,41.104468],[-74.163755,41.105993],[-74.16128,41.105606],[-74.162067,41.104822],[-74.161454,41.103924],[-74.162067,41.102889],[-74.163932,41.102848],[-74.164572,41.104182]]]}',\n            'POLYGON ((-74.164572 41.104182, -74.166137 41.104468, -74.163755 41.105993, -74.16128 41.105606, -74.162067 41.104822, -74.161454 41.103924, -74.162067 41.102889, -74.163932 41.102848, -74.164572 41.104182))',\n            0.03213684,\n            3.361538462,\n            'A_Low_Rural',\n            7495,\n            0.355555556,\n            45,\n            30.4,\n            709.541378,\n            0.8,\n            1,\n            18.01481481,\n            17.72394684,\n            0\n          ],\n          [\n            7506,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.151143,40.97231],[-74.141048,40.966348],[-74.148059,40.94159],[-74.149587,40.941398],[-74.153938,40.937972],[-74.16151,40.937498],[-74.167127,40.934519],[-74.171588,40.941998],[-74.169018,40.947413],[-74.167486,40.95733],[-74.16921,40.969444],[-74.170814,40.970178],[-74.17021,40.983865],[-74.151143,40.97231]]]}',\n            'POLYGON ((-74.151143 40.97231, -74.141048 40.966348, -74.148059 40.94159, -74.149587 40.941398, -74.153938 40.937972, -74.16151 40.937498, -74.167127 40.934519, -74.171588 40.941998, -74.169018 40.947413, -74.167486 40.95733, -74.16921 40.969444, -74.170814 40.970178, -74.17021 40.983865, -74.151143 40.97231))',\n            3.374654059,\n            2.479267611,\n            'C_Medium_High',\n            7506,\n            0.677288685,\n            3703,\n            26.69672353,\n            436.3732942,\n            0.703483662,\n            0.997299487,\n            16.88975539,\n            11.62645508,\n            0.196\n          ],\n          [\n            7605,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99863,40.866791],[-73.992942,40.873032],[-73.994052,40.874607],[-73.993799,40.876601],[-73.983367,40.875127],[-73.978515,40.870534],[-73.97569,40.868903],[-73.977687,40.8605],[-73.979382,40.860799],[-73.980514,40.857975],[-73.987985,40.850884],[-73.997417,40.856176],[-73.99829,40.855105],[-74.006059,40.85697],[-74.002723,40.864981],[-73.99863,40.866791]]]}',\n            'POLYGON ((-73.99863 40.866791, -73.992942 40.873032, -73.994052 40.874607, -73.993799 40.876601, -73.983367 40.875127, -73.978515 40.870534, -73.97569 40.868903, -73.977687 40.8605, -73.979382 40.860799, -73.980514 40.857975, -73.987985 40.850884, -73.997417 40.856176, -73.99829 40.855105, -74.006059 40.85697, -74.002723 40.864981, -73.99863 40.866791))',\n            1.642585048,\n            2.453964783,\n            'C_Medium_High',\n            7605,\n            0.656508653,\n            2658,\n            33.78153495,\n            611.0165724,\n            0.656884876,\n            0.990218209,\n            18.13150963,\n            15.16462023,\n            0.293\n          ],\n          [\n            7631,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99002,40.887038],[-73.994572,40.888663],[-73.98955,40.900569],[-73.989916,40.906341],[-73.989286,40.911479],[-73.988717,40.911674],[-73.972202,40.912892],[-73.971972,40.913617],[-73.949229,40.900817],[-73.959169,40.885014],[-73.95583,40.883117],[-73.957173,40.881425],[-73.957839,40.882492],[-73.958321,40.882085],[-73.957902,40.880512],[-73.969825,40.865568],[-73.978515,40.870534],[-73.983367,40.875127],[-73.993933,40.876539],[-73.992783,40.879099],[-73.98739,40.886003],[-73.99002,40.887038]]]}',\n            'POLYGON ((-73.99002 40.887038, -73.994572 40.888663, -73.98955 40.900569, -73.989916 40.906341, -73.989286 40.911479, -73.988717 40.911674, -73.972202 40.912892, -73.971972 40.913617, -73.949229 40.900817, -73.959169 40.885014, -73.95583 40.883117, -73.957173 40.881425, -73.957839 40.882492, -73.958321 40.882085, -73.957902 40.880512, -73.969825 40.865568, -73.978515 40.870534, -73.983367 40.875127, -73.993933 40.876539, -73.992783 40.879099, -73.98739 40.886003, -73.99002 40.887038))',\n            4.926323412,\n            2.742937844,\n            'C_Medium_High',\n            7631,\n            0.695602572,\n            9642,\n            39.65661571,\n            777.8438479,\n            0.669155777,\n            0.983717071,\n            18.88216131,\n            18.53758825,\n            0.354\n          ],\n          [\n            7643,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.04531,40.854103],[-74.044779,40.853794],[-74.044816,40.85746],[-74.043745,40.857547],[-74.043757,40.85919],[-74.032018,40.858647],[-74.028884,40.855216],[-74.028418,40.852282],[-74.029969,40.847658],[-74.029185,40.843427],[-74.030484,40.8389],[-74.029084,40.8344],[-74.030084,40.832],[-74.035384,40.8288],[-74.036918,40.82855],[-74.040269,40.830288],[-74.03909,40.830425],[-74.039014,40.832249],[-74.040669,40.832316],[-74.039777,40.834102],[-74.04154,40.833922],[-74.041217,40.834652],[-74.042195,40.834792],[-74.042262,40.836937],[-74.043267,40.838359],[-74.042435,40.840983],[-74.043045,40.843654],[-74.045484,40.843596],[-74.045798,40.846145],[-74.051577,40.847894],[-74.052563,40.848853],[-74.052518,40.850817],[-74.053981,40.853863],[-74.051677,40.857692],[-74.04531,40.854103]]]}',\n            'POLYGON ((-74.04531 40.854103, -74.044779 40.853794, -74.044816 40.85746, -74.043745 40.857547, -74.043757 40.85919, -74.032018 40.858647, -74.028884 40.855216, -74.028418 40.852282, -74.029969 40.847658, -74.029185 40.843427, -74.030484 40.8389, -74.029084 40.8344, -74.030084 40.832, -74.035384 40.8288, -74.036918 40.82855, -74.040269 40.830288, -74.03909 40.830425, -74.039014 40.832249, -74.040669 40.832316, -74.039777 40.834102, -74.04154 40.833922, -74.041217 40.834652, -74.042195 40.834792, -74.042262 40.836937, -74.043267 40.838359, -74.042435 40.840983, -74.043045 40.843654, -74.045484 40.843596, -74.045798 40.846145, -74.051577 40.847894, -74.052563 40.848853, -74.052518 40.850817, -74.053981 40.853863, -74.051677 40.857692, -74.04531 40.854103))',\n            1.698675619,\n            2.552779817,\n            'C_Medium_High',\n            7643,\n            0.787673552,\n            2953,\n            28.46145797,\n            457.6531695,\n            0.686420589,\n            0.970877074,\n            16.71062667,\n            12.32412945,\n            0.275\n          ],\n          [\n            7645,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.033649,41.039572],[-74.03703,41.043696],[-74.051378,41.049278],[-74.063774,41.044631],[-74.069675,41.041541],[-74.071014,41.046613],[-74.072234,41.046686],[-74.072452,41.048935],[-74.079369,41.048492],[-74.078814,41.055144],[-74.07699,41.061365],[-74.071915,41.072883],[-74.012583,41.046396],[-74.013197,41.041442],[-74.012452,41.038698],[-74.028831,41.036591],[-74.030807,41.037199],[-74.029374,41.039144],[-74.029546,41.040261],[-74.031631,41.037894],[-74.033649,41.039572]]]}',\n            'POLYGON ((-74.033649 41.039572, -74.03703 41.043696, -74.051378 41.049278, -74.063774 41.044631, -74.069675 41.041541, -74.071014 41.046613, -74.072234 41.046686, -74.072452 41.048935, -74.079369 41.048492, -74.078814 41.055144, -74.07699 41.061365, -74.071915 41.072883, -74.012583 41.046396, -74.013197 41.041442, -74.012452 41.038698, -74.028831 41.036591, -74.030807 41.037199, -74.029374 41.039144, -74.029546 41.040261, -74.031631 41.037894, -74.033649 41.039572))',\n            3.99137919,\n            2.383176018,\n            'B_Low_Suburban',\n            7645,\n            0.464448496,\n            2194,\n            31.67185355,\n            625.3501419,\n            0.667730173,\n            0.995897903,\n            18.75967963,\n            15.00065936,\n            0.277\n          ],\n          [\n            7405,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.424256,41.026137],[-74.423728,41.027596],[-74.421458,41.02854],[-74.417803,41.028693],[-74.414744,41.027759],[-74.41085,41.026199],[-74.40977,41.024847],[-74.40592,41.024094],[-74.403388,41.021833],[-74.402254,41.019233],[-74.399391,41.017289],[-74.390997,41.015688],[-74.389678,41.014254],[-74.389303,41.012431],[-74.388223,41.012046],[-74.386295,41.011688],[-74.382247,41.013959],[-74.379565,41.014282],[-74.371373,41.011399],[-74.367459,41.011994],[-74.364124,41.011293],[-74.36235,41.01383],[-74.355441,41.013943],[-74.351481,41.01099],[-74.351029,41.007174],[-74.352168,41.005987],[-74.350926,41.004335],[-74.34612,41.0046],[-74.342264,41.006904],[-74.340618,41.005845],[-74.336429,41.007529],[-74.334805,41.006464],[-74.334456,41.003592],[-74.329578,41.002512],[-74.328411,41.001419],[-74.330477,40.997535],[-74.329601,40.995845],[-74.329855,40.991939],[-74.332185,40.990168],[-74.331401,40.988262],[-74.330459,40.966053],[-74.336465,40.960102],[-74.340902,40.953453],[-74.339763,40.95213],[-74.336043,40.951261],[-74.335478,40.949418],[-74.341094,40.946298],[-74.364296,40.957929],[-74.397898,40.959829],[-74.410065,40.958717],[-74.428155,40.958911],[-74.428555,40.958226],[-74.428574,40.959286],[-74.431175,40.959368],[-74.430749,40.984608],[-74.432097,40.984008],[-74.432025,40.992541],[-74.430602,40.993339],[-74.430178,41.018128],[-74.429211,41.017679],[-74.425375,41.019271],[-74.426013,41.020166],[-74.423504,41.025734],[-74.424256,41.026137]]]}',\n            'POLYGON ((-74.424256 41.026137, -74.423728 41.027596, -74.421458 41.02854, -74.417803 41.028693, -74.414744 41.027759, -74.41085 41.026199, -74.40977 41.024847, -74.40592 41.024094, -74.403388 41.021833, -74.402254 41.019233, -74.399391 41.017289, -74.390997 41.015688, -74.389678 41.014254, -74.389303 41.012431, -74.388223 41.012046, -74.386295 41.011688, -74.382247 41.013959, -74.379565 41.014282, -74.371373 41.011399, -74.367459 41.011994, -74.364124 41.011293, -74.36235 41.01383, -74.355441 41.013943, -74.351481 41.01099, -74.351029 41.007174, -74.352168 41.005987, -74.350926 41.004335, -74.34612 41.0046, -74.342264 41.006904, -74.340618 41.005845, -74.336429 41.007529, -74.334805 41.006464, -74.334456 41.003592, -74.329578 41.002512, -74.328411 41.001419, -74.330477 40.997535, -74.329601 40.995845, -74.329855 40.991939, -74.332185 40.990168, -74.331401 40.988262, -74.330459 40.966053, -74.336465 40.960102, -74.340902 40.953453, -74.339763 40.95213, -74.336043 40.951261, -74.335478 40.949418, -74.341094 40.946298, -74.364296 40.957929, -74.397898 40.959829, -74.410065 40.958717, -74.428155 40.958911, -74.428555 40.958226, -74.428574 40.959286, -74.431175 40.959368, -74.430749 40.984608, -74.432097 40.984008, -74.432025 40.992541, -74.430602 40.993339, -74.430178 41.018128, -74.429211 41.017679, -74.425375 41.019271, -74.426013 41.020166, -74.423504 41.025734, -74.424256 41.026137))',\n            20.89261574,\n            2.276330461,\n            'A_Low_Rural',\n            7405,\n            0.480898221,\n            3429,\n            23.42811222,\n            430.9574125,\n            0.721201516,\n            0.997958589,\n            18.08334307,\n            10.72428006,\n            0.193\n          ],\n          [\n            7712,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.105049,40.269701],[-74.103488,40.272418],[-74.106018,40.274773],[-74.101607,40.280574],[-74.100748,40.279887],[-74.100161,40.280996],[-74.098821,40.28123],[-74.082891,40.274113],[-74.082201,40.270348],[-74.072487,40.275582],[-74.071041,40.274346],[-74.070161,40.270832],[-74.069033,40.270664],[-74.067579,40.27089],[-74.065924,40.272621],[-74.061569,40.273615],[-74.051627,40.272795],[-74.049539,40.271935],[-74.043385,40.273422],[-74.039322,40.25183],[-74.019689,40.253485],[-74.019866,40.251435],[-74.01764,40.249613],[-74.018373,40.247723],[-74.020904,40.246901],[-74.022762,40.240874],[-74.015502,40.23916],[-74.015218,40.237693],[-74.01292,40.236238],[-74.008684,40.235364],[-74.009389,40.232973],[-74.007068,40.232283],[-74.006199,40.231174],[-73.994705,40.230067],[-73.994681,40.224512],[-73.998481,40.216812],[-74.002556,40.21709],[-74.018647,40.213377],[-74.024998,40.213125],[-74.025508,40.219099],[-74.02618,40.219151],[-74.025518,40.219641],[-74.025516,40.223269],[-74.077409,40.234395],[-74.076503,40.240881],[-74.078391,40.240398],[-74.079541,40.242058],[-74.07912,40.243797],[-74.075526,40.245243],[-74.073864,40.257239],[-74.075693,40.259189],[-74.081411,40.258528],[-74.082119,40.251457],[-74.08462,40.252719],[-74.08778,40.250225],[-74.095925,40.249682],[-74.096515,40.245582],[-74.097686,40.245471],[-74.110838,40.252683],[-74.10372,40.255887],[-74.106884,40.259944],[-74.108802,40.260613],[-74.108582,40.264424],[-74.106872,40.265512],[-74.105049,40.269701]]]}',\n            'POLYGON ((-74.105049 40.269701, -74.103488 40.272418, -74.106018 40.274773, -74.101607 40.280574, -74.100748 40.279887, -74.100161 40.280996, -74.098821 40.28123, -74.082891 40.274113, -74.082201 40.270348, -74.072487 40.275582, -74.071041 40.274346, -74.070161 40.270832, -74.069033 40.270664, -74.067579 40.27089, -74.065924 40.272621, -74.061569 40.273615, -74.051627 40.272795, -74.049539 40.271935, -74.043385 40.273422, -74.039322 40.25183, -74.019689 40.253485, -74.019866 40.251435, -74.01764 40.249613, -74.018373 40.247723, -74.020904 40.246901, -74.022762 40.240874, -74.015502 40.23916, -74.015218 40.237693, -74.01292 40.236238, -74.008684 40.235364, -74.009389 40.232973, -74.007068 40.232283, -74.006199 40.231174, -73.994705 40.230067, -73.994681 40.224512, -73.998481 40.216812, -74.002556 40.21709, -74.018647 40.213377, -74.024998 40.213125, -74.025508 40.219099, -74.02618 40.219151, -74.025518 40.219641, -74.025516 40.223269, -74.077409 40.234395, -74.076503 40.240881, -74.078391 40.240398, -74.079541 40.242058, -74.07912 40.243797, -74.075526 40.245243, -74.073864 40.257239, -74.075693 40.259189, -74.081411 40.258528, -74.082119 40.251457, -74.08462 40.252719, -74.08778 40.250225, -74.095925 40.249682, -74.096515 40.245582, -74.097686 40.245471, -74.110838 40.252683, -74.10372 40.255887, -74.106884 40.259944, -74.108802 40.260613, -74.108582 40.264424, -74.106872 40.265512, -74.105049 40.269701))',\n            12.20511482,\n            2.181023176,\n            'C_Medium_High',\n            7712,\n            0.586637182,\n            6331,\n            25.68419385,\n            428.1136837,\n            0.683936187,\n            0.9973148,\n            18.24031781,\n            10.56183119,\n            0.162\n          ],\n          [\n            7036,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.293808,40.633387],[-74.291091,40.636102],[-74.290991,40.638102],[-74.28982,40.639951],[-74.287325,40.639999],[-74.281687,40.643381],[-74.282183,40.644572],[-74.280691,40.645712],[-74.281494,40.647271],[-74.281488,40.650145],[-74.264342,40.637244],[-74.258956,40.643217],[-74.251701,40.645117],[-74.248772,40.647293],[-74.241341,40.649791],[-74.232513,40.654927],[-74.200469,40.63263],[-74.202247,40.630903],[-74.203737,40.624227],[-74.201864,40.618557],[-74.203128,40.614109],[-74.203813,40.605961],[-74.199408,40.600201],[-74.19952,40.597539],[-74.203688,40.592691],[-74.210741,40.597032],[-74.212032,40.598372],[-74.21287,40.602035],[-74.215101,40.604074],[-74.220216,40.600555],[-74.231748,40.598881],[-74.234067,40.600345],[-74.234767,40.601945],[-74.236609,40.60272],[-74.242593,40.599754],[-74.25381,40.600989],[-74.255822,40.602194],[-74.258114,40.60225],[-74.255608,40.607618],[-74.278844,40.629365],[-74.285491,40.635102],[-74.290491,40.632903],[-74.292494,40.630803],[-74.296379,40.628711],[-74.297134,40.62933],[-74.293808,40.633387]]]}',\n            'POLYGON ((-74.293808 40.633387, -74.291091 40.636102, -74.290991 40.638102, -74.28982 40.639951, -74.287325 40.639999, -74.281687 40.643381, -74.282183 40.644572, -74.280691 40.645712, -74.281494 40.647271, -74.281488 40.650145, -74.264342 40.637244, -74.258956 40.643217, -74.251701 40.645117, -74.248772 40.647293, -74.241341 40.649791, -74.232513 40.654927, -74.200469 40.63263, -74.202247 40.630903, -74.203737 40.624227, -74.201864 40.618557, -74.203128 40.614109, -74.203813 40.605961, -74.199408 40.600201, -74.19952 40.597539, -74.203688 40.592691, -74.210741 40.597032, -74.212032 40.598372, -74.21287 40.602035, -74.215101 40.604074, -74.220216 40.600555, -74.231748 40.598881, -74.234067 40.600345, -74.234767 40.601945, -74.236609 40.60272, -74.242593 40.599754, -74.25381 40.600989, -74.255822 40.602194, -74.258114 40.60225, -74.255608 40.607618, -74.278844 40.629365, -74.285491 40.635102, -74.290491 40.632903, -74.292494 40.630803, -74.296379 40.628711, -74.297134 40.62933, -74.293808 40.633387))',\n            11.53874114,\n            2.699232307,\n            'C_Medium_High',\n            7036,\n            0.801162911,\n            9631,\n            28.86601273,\n            389.7025478,\n            0.719862943,\n            0.995016094,\n            15.60384361,\n            11.23865061,\n            0.228\n          ],\n          [\n            7723,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.013788,40.249179],[-74.01764,40.249613],[-74.019302,40.250582],[-74.019689,40.253485],[-74.012618,40.253141],[-74.011201,40.257],[-74.003013,40.256831],[-74.002888,40.258313],[-74.000094,40.257407],[-74.0002,40.26345],[-73.992354,40.262405],[-73.986681,40.260211],[-73.992881,40.237266],[-74.008001,40.239851],[-74.008447,40.242576],[-74.009543,40.243782],[-74.009733,40.248425],[-74.013788,40.249179]]]}',\n            'POLYGON ((-74.013788 40.249179, -74.01764 40.249613, -74.019302 40.250582, -74.019689 40.253485, -74.012618 40.253141, -74.011201 40.257, -74.003013 40.256831, -74.002888 40.258313, -74.000094 40.257407, -74.0002 40.26345, -73.992354 40.262405, -73.986681 40.260211, -73.992881 40.237266, -74.008001 40.239851, -74.008447 40.242576, -74.009543 40.243782, -74.009733 40.248425, -74.013788 40.249179))',\n            1.726296628,\n            2.781885387,\n            'A_Low_Rural',\n            7723,\n            0.28340081,\n            247,\n            40.21138211,\n            761.4119818,\n            0.71659919,\n            0.995951417,\n            20.89796748,\n            16.39563236,\n            0.213\n          ],\n          [\n            7822,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.680775,41.141961],[-74.694975,41.131348],[-74.69885,41.135537],[-74.701359,41.136794],[-74.703406,41.13429],[-74.706486,41.126963],[-74.713138,41.120327],[-74.711489,41.118891],[-74.720408,41.112296],[-74.746949,41.119559],[-74.745694,41.120599],[-74.745157,41.124768],[-74.741896,41.12892],[-74.737953,41.129147],[-74.737676,41.128104],[-74.735155,41.126436],[-74.728554,41.128919],[-74.733121,41.130496],[-74.735764,41.13238],[-74.739,41.137338],[-74.733352,41.138875],[-74.731341,41.143627],[-74.721084,41.150262],[-74.716727,41.151405],[-74.715427,41.151356],[-74.712447,41.147925],[-74.705516,41.147476],[-74.704311,41.147987],[-74.703071,41.146962],[-74.694816,41.157602],[-74.690193,41.159813],[-74.684027,41.164539],[-74.681418,41.165103],[-74.67785,41.163684],[-74.674644,41.163581],[-74.67129,41.168764],[-74.667272,41.172585],[-74.660801,41.166128],[-74.66382,41.163773],[-74.663566,41.162153],[-74.66462,41.159589],[-74.663429,41.157321],[-74.664679,41.155926],[-74.670442,41.154203],[-74.668458,41.152808],[-74.668539,41.151161],[-74.680775,41.141961]]]}',\n            'POLYGON ((-74.680775 41.141961, -74.694975 41.131348, -74.69885 41.135537, -74.701359 41.136794, -74.703406 41.13429, -74.706486 41.126963, -74.713138 41.120327, -74.711489 41.118891, -74.720408 41.112296, -74.746949 41.119559, -74.745694 41.120599, -74.745157 41.124768, -74.741896 41.12892, -74.737953 41.129147, -74.737676 41.128104, -74.735155 41.126436, -74.728554 41.128919, -74.733121 41.130496, -74.735764 41.13238, -74.739 41.137338, -74.733352 41.138875, -74.731341 41.143627, -74.721084 41.150262, -74.716727 41.151405, -74.715427 41.151356, -74.712447 41.147925, -74.705516 41.147476, -74.704311 41.147987, -74.703071 41.146962, -74.694816 41.157602, -74.690193 41.159813, -74.684027 41.164539, -74.681418 41.165103, -74.67785 41.163684, -74.674644 41.163581, -74.67129 41.168764, -74.667272 41.172585, -74.660801 41.166128, -74.66382 41.163773, -74.663566 41.162153, -74.66462 41.159589, -74.663429 41.157321, -74.664679 41.155926, -74.670442 41.154203, -74.668458 41.152808, -74.668539 41.151161, -74.680775 41.141961))',\n            6.647012773,\n            2.433311133,\n            'A_Low_Rural',\n            7822,\n            0.273684211,\n            95,\n            17.43157895,\n            349.6450282,\n            0.768421053,\n            1,\n            16.78491228,\n            9.373910335,\n            0.118\n          ],\n          [\n            7054,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.39366,40.830496],[-74.405638,40.838928],[-74.422925,40.8447],[-74.451083,40.838346],[-74.463181,40.843465],[-74.467565,40.846525],[-74.465964,40.848547],[-74.464617,40.848757],[-74.46076,40.846768],[-74.454244,40.85264],[-74.452162,40.852242],[-74.449605,40.854737],[-74.45126,40.859271],[-74.449707,40.860541],[-74.451701,40.860499],[-74.461277,40.86634],[-74.46095,40.867844],[-74.449226,40.866302],[-74.443807,40.866626],[-74.447036,40.867209],[-74.439733,40.866511],[-74.43882,40.870782],[-74.440454,40.869142],[-74.445241,40.867982],[-74.450476,40.869008],[-74.450185,40.872275],[-74.44921,40.874111],[-74.451584,40.873513],[-74.451567,40.876515],[-74.447705,40.876866],[-74.448803,40.879174],[-74.450256,40.879959],[-74.433632,40.874255],[-74.433403,40.877506],[-74.43068,40.881706],[-74.425693,40.882707],[-74.424699,40.879771],[-74.425187,40.878315],[-74.419916,40.879254],[-74.420486,40.875395],[-74.419362,40.871206],[-74.414728,40.868737],[-74.410267,40.877077],[-74.405039,40.875898],[-74.404027,40.878969],[-74.4017,40.881459],[-74.396071,40.881961],[-74.392244,40.873568],[-74.39051,40.874087],[-74.390446,40.87614],[-74.384095,40.876199],[-74.383996,40.875199],[-74.380159,40.876783],[-74.376464,40.876885],[-74.376099,40.875662],[-74.375014,40.876373],[-74.372986,40.875671],[-74.371975,40.870267],[-74.36966,40.871168],[-74.368983,40.869851],[-74.369611,40.869488],[-74.365509,40.869788],[-74.365091,40.868735],[-74.366462,40.867373],[-74.366416,40.864756],[-74.36514,40.866106],[-74.362694,40.863887],[-74.358996,40.863608],[-74.354787,40.86599],[-74.35609,40.867062],[-74.358298,40.866561],[-74.361219,40.867003],[-74.361668,40.867421],[-74.357831,40.866738],[-74.355927,40.867299],[-74.353829,40.86556],[-74.352328,40.866215],[-74.349994,40.864479],[-74.349303,40.86285],[-74.348177,40.862628],[-74.34716,40.859279],[-74.347658,40.856795],[-74.346489,40.856411],[-74.345626,40.857625],[-74.344092,40.856842],[-74.340904,40.852353],[-74.341387,40.85158],[-74.344485,40.850715],[-74.341838,40.849039],[-74.341288,40.847784],[-74.341951,40.846224],[-74.35252,40.844081],[-74.354781,40.840973],[-74.360222,40.840871],[-74.367495,40.836199],[-74.371095,40.835699],[-74.379803,40.828479],[-74.380291,40.830042],[-74.381487,40.828478],[-74.380893,40.827515],[-74.383959,40.824991],[-74.388727,40.825989],[-74.39366,40.830496]]]}',\n            'POLYGON ((-74.39366 40.830496, -74.405638 40.838928, -74.422925 40.8447, -74.451083 40.838346, -74.463181 40.843465, -74.467565 40.846525, -74.465964 40.848547, -74.464617 40.848757, -74.46076 40.846768, -74.454244 40.85264, -74.452162 40.852242, -74.449605 40.854737, -74.45126 40.859271, -74.449707 40.860541, -74.451701 40.860499, -74.461277 40.86634, -74.46095 40.867844, -74.449226 40.866302, -74.443807 40.866626, -74.447036 40.867209, -74.439733 40.866511, -74.43882 40.870782, -74.440454 40.869142, -74.445241 40.867982, -74.450476 40.869008, -74.450185 40.872275, -74.44921 40.874111, -74.451584 40.873513, -74.451567 40.876515, -74.447705 40.876866, -74.448803 40.879174, -74.450256 40.879959, -74.433632 40.874255, -74.433403 40.877506, -74.43068 40.881706, -74.425693 40.882707, -74.424699 40.879771, -74.425187 40.878315, -74.419916 40.879254, -74.420486 40.875395, -74.419362 40.871206, -74.414728 40.868737, -74.410267 40.877077, -74.405039 40.875898, -74.404027 40.878969, -74.4017 40.881459, -74.396071 40.881961, -74.392244 40.873568, -74.39051 40.874087, -74.390446 40.87614, -74.384095 40.876199, -74.383996 40.875199, -74.380159 40.876783, -74.376464 40.876885, -74.376099 40.875662, -74.375014 40.876373, -74.372986 40.875671, -74.371975 40.870267, -74.36966 40.871168, -74.368983 40.869851, -74.369611 40.869488, -74.365509 40.869788, -74.365091 40.868735, -74.366462 40.867373, -74.366416 40.864756, -74.36514 40.866106, -74.362694 40.863887, -74.358996 40.863608, -74.354787 40.86599, -74.35609 40.867062, -74.358298 40.866561, -74.361219 40.867003, -74.361668 40.867421, -74.357831 40.866738, -74.355927 40.867299, -74.353829 40.86556, -74.352328 40.866215, -74.349994 40.864479, -74.349303 40.86285, -74.348177 40.862628, -74.34716 40.859279, -74.347658 40.856795, -74.346489 40.856411, -74.345626 40.857625, -74.344092 40.856842, -74.340904 40.852353, -74.341387 40.85158, -74.344485 40.850715, -74.341838 40.849039, -74.341288 40.847784, -74.341951 40.846224, -74.35252 40.844081, -74.354781 40.840973, -74.360222 40.840871, -74.367495 40.836199, -74.371095 40.835699, -74.379803 40.828479, -74.380291 40.830042, -74.381487 40.828478, -74.380893 40.827515, -74.383959 40.824991, -74.388727 40.825989, -74.39366 40.830496))',\n            14.37823635,\n            2.515879321,\n            'B_Low_Suburban',\n            7054,\n            0.5693362,\n            7442,\n            27.18441628,\n            491.6000821,\n            0.709486697,\n            0.99677506,\n            17.45079087,\n            12.67679148,\n            0.256\n          ],\n          [\n            7832,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-75.097586,40.843042],[-75.095784,40.847082],[-75.090962,40.849187],[-75.076684,40.849875],[-75.07083,40.847392],[-75.067655,40.847329],[-75.064328,40.848338],[-75.063343,40.851499],[-75.060491,40.85302],[-75.059239,40.856102],[-75.053294,40.8599],[-75.051692,40.863363],[-75.050839,40.868067],[-75.053664,40.87366],[-75.058655,40.877654],[-75.062073,40.882188],[-75.060998,40.882237],[-75.060556,40.883385],[-75.059124,40.884072],[-75.060651,40.889189],[-75.064166,40.893064],[-75.063612,40.895385],[-75.066827,40.895434],[-75.067646,40.896657],[-75.067452,40.899412],[-75.071768,40.898989],[-75.072097,40.898253],[-75.073515,40.900408],[-75.07391,40.899656],[-75.075231,40.899947],[-75.07609,40.907039],[-75.07929,40.913886],[-75.095528,40.924148],[-75.104809,40.935247],[-75.106148,40.93967],[-75.111689,40.948127],[-75.117767,40.953013],[-75.119116,40.958161],[-75.118076,40.959633],[-75.118346,40.963676],[-75.120435,40.968302],[-75.122603,40.970152],[-75.130166,40.968961],[-75.132622,40.970005],[-75.134701,40.972038],[-75.135531,40.973803],[-75.135521,40.976865],[-75.132522,40.981235],[-75.131096,40.990464],[-75.127196,40.993954],[-75.110595,41.002174],[-75.109113,41.004102],[-75.095556,41.008874],[-75.088596,41.015024],[-75.081101,41.016838],[-75.074999,41.01713],[-75.069277,41.019348],[-75.068369,41.018018],[-75.037857,41.030485],[-75.029696,41.035357],[-75.027882,41.037279],[-75.024165,41.038536],[-75.017392,41.042515],[-75.015185,41.04423],[-75.009346,41.051893],[-75.003538,41.057084],[-75.000477,41.058017],[-74.998034,41.057699],[-74.995569,41.058848],[-74.992703,41.058564],[-74.992174,41.05969],[-74.984424,41.064889],[-74.984063,41.062832],[-74.980707,41.061781],[-74.964688,41.063446],[-74.963232,41.060643],[-74.964228,41.057935],[-74.961406,41.055613],[-74.96458,41.051797],[-74.99578,41.033212],[-75.002831,41.030399],[-75.003773,41.031045],[-75.004225,41.032144],[-75.002834,41.033539],[-74.998731,41.035666],[-74.998954,41.038437],[-74.998147,41.038507],[-74.998255,41.037117],[-74.997435,41.037002],[-74.995356,41.040166],[-74.991659,41.042442],[-74.994705,41.041671],[-75.004298,41.035416],[-75.005265,41.031919],[-75.003411,41.030082],[-75.016649,41.022886],[-75.021761,41.020728],[-75.036997,41.017017],[-75.04378,41.014322],[-75.03456,41.008186],[-75.034725,41.004652],[-75.033729,41.002569],[-75.034213,41.001403],[-75.032281,40.999595],[-75.032341,40.996951],[-75.02898,40.99561],[-75.011902,40.998459],[-75.008874,40.99009],[-75.010378,40.985846],[-75.010075,40.984303],[-75.002953,40.981862],[-75.005732,40.980256],[-75.021928,40.975764],[-75.02123,40.970357],[-75.024322,40.969825],[-75.021013,40.968997],[-75.020256,40.963611],[-75.018195,40.964426],[-75.019502,40.961433],[-75.018391,40.959758],[-75.038299,40.952908],[-75.03083,40.946719],[-75.029307,40.946706],[-75.020847,40.940258],[-75.019403,40.941591],[-75.019535,40.939241],[-75.021033,40.939612],[-75.022159,40.938123],[-75.015041,40.928458],[-75.027891,40.920693],[-75.02861,40.915329],[-75.031276,40.912284],[-75.024568,40.908851],[-75.0223,40.905808],[-75.03156,40.896317],[-75.030145,40.89056],[-75.024001,40.891451],[-75.022183,40.892446],[-75.020636,40.891426],[-75.017441,40.891496],[-75.01407,40.890293],[-75.008894,40.885907],[-75.010391,40.884111],[-75.014797,40.882787],[-75.019161,40.881981],[-75.024044,40.883433],[-75.025249,40.882262],[-75.02744,40.88172],[-75.021561,40.875098],[-75.014017,40.868236],[-75.016147,40.867869],[-75.016559,40.868533],[-75.020503,40.866673],[-75.021161,40.867805],[-75.022549,40.86763],[-75.023738,40.863747],[-75.025591,40.860958],[-75.02513,40.858498],[-75.024052,40.857718],[-75.028967,40.856352],[-75.030658,40.857634],[-75.032149,40.85736],[-75.029716,40.863273],[-75.031074,40.869309],[-75.035994,40.868633],[-75.051176,40.860567],[-75.049486,40.860618],[-75.055534,40.855224],[-75.057455,40.852435],[-75.057422,40.848045],[-75.062178,40.847418],[-75.061305,40.843848],[-75.064167,40.842773],[-75.06489,40.843711],[-75.068656,40.844534],[-75.070267,40.842345],[-75.070187,40.837954],[-75.07707,40.840947],[-75.088876,40.832533],[-75.096301,40.83839],[-75.097572,40.840967],[-75.097586,40.843042]]]}',\n            'POLYGON ((-75.097586 40.843042, -75.095784 40.847082, -75.090962 40.849187, -75.076684 40.849875, -75.07083 40.847392, -75.067655 40.847329, -75.064328 40.848338, -75.063343 40.851499, -75.060491 40.85302, -75.059239 40.856102, -75.053294 40.8599, -75.051692 40.863363, -75.050839 40.868067, -75.053664 40.87366, -75.058655 40.877654, -75.062073 40.882188, -75.060998 40.882237, -75.060556 40.883385, -75.059124 40.884072, -75.060651 40.889189, -75.064166 40.893064, -75.063612 40.895385, -75.066827 40.895434, -75.067646 40.896657, -75.067452 40.899412, -75.071768 40.898989, -75.072097 40.898253, -75.073515 40.900408, -75.07391 40.899656, -75.075231 40.899947, -75.07609 40.907039, -75.07929 40.913886, -75.095528 40.924148, -75.104809 40.935247, -75.106148 40.93967, -75.111689 40.948127, -75.117767 40.953013, -75.119116 40.958161, -75.118076 40.959633, -75.118346 40.963676, -75.120435 40.968302, -75.122603 40.970152, -75.130166 40.968961, -75.132622 40.970005, -75.134701 40.972038, -75.135531 40.973803, -75.135521 40.976865, -75.132522 40.981235, -75.131096 40.990464, -75.127196 40.993954, -75.110595 41.002174, -75.109113 41.004102, -75.095556 41.008874, -75.088596 41.015024, -75.081101 41.016838, -75.074999 41.01713, -75.069277 41.019348, -75.068369 41.018018, -75.037857 41.030485, -75.029696 41.035357, -75.027882 41.037279, -75.024165 41.038536, -75.017392 41.042515, -75.015185 41.04423, -75.009346 41.051893, -75.003538 41.057084, -75.000477 41.058017, -74.998034 41.057699, -74.995569 41.058848, -74.992703 41.058564, -74.992174 41.05969, -74.984424 41.064889, -74.984063 41.062832, -74.980707 41.061781, -74.964688 41.063446, -74.963232 41.060643, -74.964228 41.057935, -74.961406 41.055613, -74.96458 41.051797, -74.99578 41.033212, -75.002831 41.030399, -75.003773 41.031045, -75.004225 41.032144, -75.002834 41.033539, -74.998731 41.035666, -74.998954 41.038437, -74.998147 41.038507, -74.998255 41.037117, -74.997435 41.037002, -74.995356 41.040166, -74.991659 41.042442, -74.994705 41.041671, -75.004298 41.035416, -75.005265 41.031919, -75.003411 41.030082, -75.016649 41.022886, -75.021761 41.020728, -75.036997 41.017017, -75.04378 41.014322, -75.03456 41.008186, -75.034725 41.004652, -75.033729 41.002569, -75.034213 41.001403, -75.032281 40.999595, -75.032341 40.996951, -75.02898 40.99561, -75.011902 40.998459, -75.008874 40.99009, -75.010378 40.985846, -75.010075 40.984303, -75.002953 40.981862, -75.005732 40.980256, -75.021928 40.975764, -75.02123 40.970357, -75.024322 40.969825, -75.021013 40.968997, -75.020256 40.963611, -75.018195 40.964426, -75.019502 40.961433, -75.018391 40.959758, -75.038299 40.952908, -75.03083 40.946719, -75.029307 40.946706, -75.020847 40.940258, -75.019403 40.941591, -75.019535 40.939241, -75.021033 40.939612, -75.022159 40.938123, -75.015041 40.928458, -75.027891 40.920693, -75.02861 40.915329, -75.031276 40.912284, -75.024568 40.908851, -75.0223 40.905808, -75.03156 40.896317, -75.030145 40.89056, -75.024001 40.891451, -75.022183 40.892446, -75.020636 40.891426, -75.017441 40.891496, -75.01407 40.890293, -75.008894 40.885907, -75.010391 40.884111, -75.014797 40.882787, -75.019161 40.881981, -75.024044 40.883433, -75.025249 40.882262, -75.02744 40.88172, -75.021561 40.875098, -75.014017 40.868236, -75.016147 40.867869, -75.016559 40.868533, -75.020503 40.866673, -75.021161 40.867805, -75.022549 40.86763, -75.023738 40.863747, -75.025591 40.860958, -75.02513 40.858498, -75.024052 40.857718, -75.028967 40.856352, -75.030658 40.857634, -75.032149 40.85736, -75.029716 40.863273, -75.031074 40.869309, -75.035994 40.868633, -75.051176 40.860567, -75.049486 40.860618, -75.055534 40.855224, -75.057455 40.852435, -75.057422 40.848045, -75.062178 40.847418, -75.061305 40.843848, -75.064167 40.842773, -75.06489 40.843711, -75.068656 40.844534, -75.070267 40.842345, -75.070187 40.837954, -75.07707 40.840947, -75.088876 40.832533, -75.096301 40.83839, -75.097572 40.840967, -75.097586 40.843042))',\n            48.08259073,\n            2.222012926,\n            'A_Low_Rural',\n            7832,\n            0.291390728,\n            302,\n            13.33670034,\n            237.9317288,\n            0.801324503,\n            0.983443709,\n            15.91447811,\n            6.727790706,\n            0.081\n          ],\n          [\n            7924,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.624966,40.729228],[-74.623454,40.730678],[-74.622391,40.735039],[-74.620469,40.736001],[-74.627104,40.737727],[-74.628747,40.736844],[-74.630137,40.735166],[-74.631305,40.734548],[-74.632923,40.732801],[-74.633441,40.732418],[-74.633871,40.732237],[-74.632255,40.733644],[-74.630508,40.736973],[-74.631292,40.738187],[-74.629319,40.739683],[-74.63127,40.741111],[-74.555922,40.75831],[-74.555203,40.756999],[-74.553197,40.756439],[-74.551914,40.754863],[-74.550924,40.751073],[-74.552886,40.749578],[-74.548756,40.746131],[-74.549383,40.745092],[-74.548999,40.740994],[-74.550988,40.741647],[-74.551958,40.739985],[-74.55856,40.736104],[-74.556731,40.73478],[-74.560814,40.731351],[-74.563545,40.730267],[-74.564013,40.728933],[-74.552547,40.722803],[-74.555477,40.721368],[-74.5533,40.719188],[-74.556246,40.717896],[-74.567761,40.706455],[-74.607957,40.694414],[-74.609197,40.696081],[-74.611319,40.695338],[-74.61599,40.695721],[-74.618622,40.695149],[-74.618226,40.704919],[-74.620821,40.710878],[-74.620235,40.712269],[-74.615067,40.715173],[-74.612185,40.719403],[-74.612003,40.72373],[-74.61545,40.723053],[-74.620932,40.716648],[-74.623654,40.715129],[-74.625684,40.715004],[-74.634205,40.722291],[-74.631262,40.72482],[-74.626871,40.725334],[-74.626784,40.727547],[-74.625149,40.72802],[-74.625639,40.728948],[-74.624966,40.729228]]]}',\n            'POLYGON ((-74.624966 40.729228, -74.623454 40.730678, -74.622391 40.735039, -74.620469 40.736001, -74.627104 40.737727, -74.628747 40.736844, -74.630137 40.735166, -74.631305 40.734548, -74.632923 40.732801, -74.633441 40.732418, -74.633871 40.732237, -74.632255 40.733644, -74.630508 40.736973, -74.631292 40.738187, -74.629319 40.739683, -74.63127 40.741111, -74.555922 40.75831, -74.555203 40.756999, -74.553197 40.756439, -74.551914 40.754863, -74.550924 40.751073, -74.552886 40.749578, -74.548756 40.746131, -74.549383 40.745092, -74.548999 40.740994, -74.550988 40.741647, -74.551958 40.739985, -74.55856 40.736104, -74.556731 40.73478, -74.560814 40.731351, -74.563545 40.730267, -74.564013 40.728933, -74.552547 40.722803, -74.555477 40.721368, -74.5533 40.719188, -74.556246 40.717896, -74.567761 40.706455, -74.607957 40.694414, -74.609197 40.696081, -74.611319 40.695338, -74.61599 40.695721, -74.618622 40.695149, -74.618226 40.704919, -74.620821 40.710878, -74.620235 40.712269, -74.615067 40.715173, -74.612185 40.719403, -74.612003 40.72373, -74.61545 40.723053, -74.620932 40.716648, -74.623654 40.715129, -74.625684 40.715004, -74.634205 40.722291, -74.631262 40.72482, -74.626871 40.725334, -74.626784 40.727547, -74.625149 40.72802, -74.625639 40.728948, -74.624966 40.729228))',\n            12.13371074,\n            2.402380687,\n            'A_Low_Rural',\n            7924,\n            0.384324324,\n            1850,\n            33.00813449,\n            677.5939771,\n            0.667027027,\n            0.996756757,\n            19.17525307,\n            15.90160445,\n            0.242\n          ],\n          [\n            7928,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.409178,40.741059],[-74.405471,40.73989],[-74.402552,40.742258],[-74.402172,40.741487],[-74.391635,40.75198],[-74.389873,40.751394],[-74.383649,40.754173],[-74.38351,40.755403],[-74.380255,40.755386],[-74.381513,40.754696],[-74.373581,40.75015],[-74.370371,40.752139],[-74.363072,40.750403],[-74.367572,40.748824],[-74.364765,40.746788],[-74.366562,40.743889],[-74.371979,40.742359],[-74.37256,40.741587],[-74.370829,40.735753],[-74.371246,40.734627],[-74.374669,40.733545],[-74.377932,40.734319],[-74.379732,40.73396],[-74.380295,40.7333],[-74.379853,40.732492],[-74.377503,40.730032],[-74.384382,40.724385],[-74.388232,40.726165],[-74.39069,40.725499],[-74.390769,40.718294],[-74.39509,40.717273],[-74.402254,40.718001],[-74.404574,40.715245],[-74.40735,40.713461],[-74.408466,40.710106],[-74.410057,40.709517],[-74.412495,40.7103],[-74.413818,40.707248],[-74.4154,40.705781],[-74.426897,40.701711],[-74.430005,40.697838],[-74.429578,40.697004],[-74.430781,40.693453],[-74.434551,40.693575],[-74.437847,40.6919],[-74.43797,40.690247],[-74.444333,40.687969],[-74.449736,40.718831],[-74.447841,40.718904],[-74.437223,40.723493],[-74.426842,40.729088],[-74.424382,40.734726],[-74.426048,40.733344],[-74.427376,40.733482],[-74.426785,40.735839],[-74.429199,40.735878],[-74.430449,40.734831],[-74.430765,40.735285],[-74.428298,40.736522],[-74.430213,40.7377],[-74.43032,40.736951],[-74.432798,40.736704],[-74.434311,40.735493],[-74.437467,40.735121],[-74.437348,40.73587],[-74.439214,40.73673],[-74.44084,40.73642],[-74.439595,40.736006],[-74.440803,40.734753],[-74.441744,40.735486],[-74.441043,40.736868],[-74.442891,40.735942],[-74.444308,40.738929],[-74.438047,40.741641],[-74.432156,40.746447],[-74.434702,40.747732],[-74.438198,40.752308],[-74.436616,40.752849],[-74.435314,40.752306],[-74.436024,40.751602],[-74.433267,40.750069],[-74.432271,40.751089],[-74.428827,40.749474],[-74.430019,40.746001],[-74.41515,40.742751],[-74.409916,40.740501],[-74.409178,40.741059]]]}',\n            'POLYGON ((-74.409178 40.741059, -74.405471 40.73989, -74.402552 40.742258, -74.402172 40.741487, -74.391635 40.75198, -74.389873 40.751394, -74.383649 40.754173, -74.38351 40.755403, -74.380255 40.755386, -74.381513 40.754696, -74.373581 40.75015, -74.370371 40.752139, -74.363072 40.750403, -74.367572 40.748824, -74.364765 40.746788, -74.366562 40.743889, -74.371979 40.742359, -74.37256 40.741587, -74.370829 40.735753, -74.371246 40.734627, -74.374669 40.733545, -74.377932 40.734319, -74.379732 40.73396, -74.380295 40.7333, -74.379853 40.732492, -74.377503 40.730032, -74.384382 40.724385, -74.388232 40.726165, -74.39069 40.725499, -74.390769 40.718294, -74.39509 40.717273, -74.402254 40.718001, -74.404574 40.715245, -74.40735 40.713461, -74.408466 40.710106, -74.410057 40.709517, -74.412495 40.7103, -74.413818 40.707248, -74.4154 40.705781, -74.426897 40.701711, -74.430005 40.697838, -74.429578 40.697004, -74.430781 40.693453, -74.434551 40.693575, -74.437847 40.6919, -74.43797 40.690247, -74.444333 40.687969, -74.449736 40.718831, -74.447841 40.718904, -74.437223 40.723493, -74.426842 40.729088, -74.424382 40.734726, -74.426048 40.733344, -74.427376 40.733482, -74.426785 40.735839, -74.429199 40.735878, -74.430449 40.734831, -74.430765 40.735285, -74.428298 40.736522, -74.430213 40.7377, -74.43032 40.736951, -74.432798 40.736704, -74.434311 40.735493, -74.437467 40.735121, -74.437348 40.73587, -74.439214 40.73673, -74.44084 40.73642, -74.439595 40.736006, -74.440803 40.734753, -74.441744 40.735486, -74.441043 40.736868, -74.442891 40.735942, -74.444308 40.738929, -74.438047 40.741641, -74.432156 40.746447, -74.434702 40.747732, -74.438198 40.752308, -74.436616 40.752849, -74.435314 40.752306, -74.436024 40.751602, -74.433267 40.750069, -74.432271 40.751089, -74.428827 40.749474, -74.430019 40.746001, -74.41515 40.742751, -74.409916 40.740501, -74.409178 40.741059))',\n            8.953905337,\n            2.250793062,\n            'B_Low_Suburban',\n            7928,\n            0.539032204,\n            5931,\n            39.01961447,\n            858.5168842,\n            0.613050076,\n            0.997133704,\n            20.95072145,\n            18.44006178,\n            0.31\n          ],\n          [\n            7979,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.746315,40.699842],[-74.738518,40.708428],[-74.73803,40.711868],[-74.734191,40.71672],[-74.729987,40.717241],[-74.732448,40.720062],[-74.724418,40.719455],[-74.720152,40.720857],[-74.721834,40.718984],[-74.720881,40.716657],[-74.71557,40.714933],[-74.715093,40.713903],[-74.717147,40.714164],[-74.716015,40.710283],[-74.717798,40.709288],[-74.714601,40.703286],[-74.714347,40.699368],[-74.721624,40.690735],[-74.722079,40.688939],[-74.72065,40.687797],[-74.72303,40.68265],[-74.725555,40.683786],[-74.729034,40.68356],[-74.730184,40.682581],[-74.732786,40.683785],[-74.730782,40.686112],[-74.73592,40.688406],[-74.732475,40.692508],[-74.733375,40.693151],[-74.734169,40.699729],[-74.744802,40.697551],[-74.746315,40.699842]]]}',\n            'POLYGON ((-74.746315 40.699842, -74.738518 40.708428, -74.73803 40.711868, -74.734191 40.71672, -74.729987 40.717241, -74.732448 40.720062, -74.724418 40.719455, -74.720152 40.720857, -74.721834 40.718984, -74.720881 40.716657, -74.71557 40.714933, -74.715093 40.713903, -74.717147 40.714164, -74.716015 40.710283, -74.717798 40.709288, -74.714601 40.703286, -74.714347 40.699368, -74.721624 40.690735, -74.722079 40.688939, -74.72065 40.687797, -74.72303 40.68265, -74.725555 40.683786, -74.729034 40.68356, -74.730184 40.682581, -74.732786 40.683785, -74.730782 40.686112, -74.73592 40.688406, -74.732475 40.692508, -74.733375 40.693151, -74.734169 40.699729, -74.744802 40.697551, -74.746315 40.699842))',\n            2.439857168,\n            2.316265052,\n            'A_Low_Rural',\n            7979,\n            0.139534884,\n            215,\n            22.95774648,\n            284.0477961,\n            0.693023256,\n            0.990697674,\n            14.85117371,\n            8.606828708,\n            0.354\n          ],\n          [\n            7838,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.96258,40.908359],[-74.959171,40.909348],[-74.958166,40.910924],[-74.955046,40.911941],[-74.952035,40.914182],[-74.947947,40.912938],[-74.945624,40.911373],[-74.945686,40.909849],[-74.944298,40.909232],[-74.940447,40.911676],[-74.93977,40.916014],[-74.93583,40.915965],[-74.93084,40.917595],[-74.927867,40.906805],[-74.887969,40.933194],[-74.887988,40.931636],[-74.886846,40.929954],[-74.882716,40.930239],[-74.874369,40.932296],[-74.873234,40.934085],[-74.86866,40.936267],[-74.866899,40.933617],[-74.859938,40.93051],[-74.852375,40.930101],[-74.848303,40.933399],[-74.847051,40.933475],[-74.844269,40.930927],[-74.844978,40.927355],[-74.840887,40.923065],[-74.840416,40.921211],[-74.84412,40.919599],[-74.842955,40.919474],[-74.844491,40.91793],[-74.844531,40.915532],[-74.859914,40.909303],[-74.860956,40.904959],[-74.862287,40.903064],[-74.858839,40.900314],[-74.858959,40.899168],[-74.848263,40.892969],[-74.851169,40.889839],[-74.85923,40.885743],[-74.859366,40.887143],[-74.860511,40.887838],[-74.863545,40.883912],[-74.867125,40.884417],[-74.872203,40.882492],[-74.872768,40.883398],[-74.876052,40.884353],[-74.882058,40.883988],[-74.882891,40.882731],[-74.887871,40.879998],[-74.889572,40.87633],[-74.894028,40.872274],[-74.89991,40.869372],[-74.903521,40.865869],[-74.905344,40.866523],[-74.903426,40.864607],[-74.903128,40.86326],[-74.900129,40.862348],[-74.896917,40.858397],[-74.894792,40.857851],[-74.894754,40.856634],[-74.893744,40.8561],[-74.894932,40.856353],[-74.895533,40.851298],[-74.907884,40.855752],[-74.915047,40.852114],[-74.924079,40.851947],[-74.929062,40.848559],[-74.934249,40.85086],[-74.933294,40.852222],[-74.935853,40.852373],[-74.941042,40.846725],[-74.94384,40.846406],[-74.950587,40.842517],[-74.951581,40.839414],[-74.956373,40.834365],[-74.971647,40.842927],[-74.971381,40.843883],[-74.968743,40.844635],[-74.960144,40.855778],[-74.963608,40.855651],[-74.963127,40.858348],[-74.964627,40.85919],[-74.96689,40.85904],[-74.968542,40.860945],[-74.97156,40.860844],[-74.968929,40.864416],[-74.969525,40.86504],[-74.971744,40.861518],[-74.972369,40.86245],[-74.9725,40.861514],[-74.973998,40.861535],[-74.974787,40.860622],[-74.978735,40.87158],[-74.979484,40.87194],[-74.97877,40.873364],[-74.979931,40.874228],[-74.976368,40.876708],[-74.982155,40.879127],[-74.980311,40.880401],[-74.980521,40.881626],[-74.976513,40.883126],[-74.978397,40.88719],[-74.976715,40.890538],[-74.9757,40.883461],[-74.977469,40.879267],[-74.97593,40.878446],[-74.976152,40.879675],[-74.974987,40.881253],[-74.969259,40.884146],[-74.963281,40.885689],[-74.957982,40.890776],[-74.957476,40.893947],[-74.961516,40.894095],[-74.95829,40.900182],[-74.962611,40.906121],[-74.959793,40.907294],[-74.96258,40.908359]]]}',\n            'POLYGON ((-74.96258 40.908359, -74.959171 40.909348, -74.958166 40.910924, -74.955046 40.911941, -74.952035 40.914182, -74.947947 40.912938, -74.945624 40.911373, -74.945686 40.909849, -74.944298 40.909232, -74.940447 40.911676, -74.93977 40.916014, -74.93583 40.915965, -74.93084 40.917595, -74.927867 40.906805, -74.887969 40.933194, -74.887988 40.931636, -74.886846 40.929954, -74.882716 40.930239, -74.874369 40.932296, -74.873234 40.934085, -74.86866 40.936267, -74.866899 40.933617, -74.859938 40.93051, -74.852375 40.930101, -74.848303 40.933399, -74.847051 40.933475, -74.844269 40.930927, -74.844978 40.927355, -74.840887 40.923065, -74.840416 40.921211, -74.84412 40.919599, -74.842955 40.919474, -74.844491 40.91793, -74.844531 40.915532, -74.859914 40.909303, -74.860956 40.904959, -74.862287 40.903064, -74.858839 40.900314, -74.858959 40.899168, -74.848263 40.892969, -74.851169 40.889839, -74.85923 40.885743, -74.859366 40.887143, -74.860511 40.887838, -74.863545 40.883912, -74.867125 40.884417, -74.872203 40.882492, -74.872768 40.883398, -74.876052 40.884353, -74.882058 40.883988, -74.882891 40.882731, -74.887871 40.879998, -74.889572 40.87633, -74.894028 40.872274, -74.89991 40.869372, -74.903521 40.865869, -74.905344 40.866523, -74.903426 40.864607, -74.903128 40.86326, -74.900129 40.862348, -74.896917 40.858397, -74.894792 40.857851, -74.894754 40.856634, -74.893744 40.8561, -74.894932 40.856353, -74.895533 40.851298, -74.907884 40.855752, -74.915047 40.852114, -74.924079 40.851947, -74.929062 40.848559, -74.934249 40.85086, -74.933294 40.852222, -74.935853 40.852373, -74.941042 40.846725, -74.94384 40.846406, -74.950587 40.842517, -74.951581 40.839414, -74.956373 40.834365, -74.971647 40.842927, -74.971381 40.843883, -74.968743 40.844635, -74.960144 40.855778, -74.963608 40.855651, -74.963127 40.858348, -74.964627 40.85919, -74.96689 40.85904, -74.968542 40.860945, -74.97156 40.860844, -74.968929 40.864416, -74.969525 40.86504, -74.971744 40.861518, -74.972369 40.86245, -74.9725 40.861514, -74.973998 40.861535, -74.974787 40.860622, -74.978735 40.87158, -74.979484 40.87194, -74.97877 40.873364, -74.979931 40.874228, -74.976368 40.876708, -74.982155 40.879127, -74.980311 40.880401, -74.980521 40.881626, -74.976513 40.883126, -74.978397 40.88719, -74.976715 40.890538, -74.9757 40.883461, -74.977469 40.879267, -74.97593 40.878446, -74.976152 40.879675, -74.974987 40.881253, -74.969259 40.884146, -74.963281 40.885689, -74.957982 40.890776, -74.957476 40.893947, -74.961516 40.894095, -74.95829 40.900182, -74.962611 40.906121, -74.959793 40.907294, -74.96258 40.908359))',\n            25.10592549,\n            2.007636482,\n            'A_Low_Rural',\n            7838,\n            0.346875,\n            320,\n            11.98119122,\n            211.9244058,\n            0.771875,\n            0.996875,\n            15.3553814,\n            6.210590289,\n            0.082\n          ],\n          [\n            7044,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.261032,40.832085],[-74.259357,40.836363],[-74.257107,40.838124],[-74.256523,40.841781],[-74.251892,40.849829],[-74.2334,40.844389],[-74.237214,40.836034],[-74.227445,40.83331],[-74.227147,40.836396],[-74.22099,40.834799],[-74.225755,40.827381],[-74.224352,40.827071],[-74.227054,40.824221],[-74.227725,40.824867],[-74.229113,40.822199],[-74.229079,40.820019],[-74.232573,40.814863],[-74.250795,40.824461],[-74.264305,40.825431],[-74.261032,40.832085]]]}',\n            'POLYGON ((-74.261032 40.832085, -74.259357 40.836363, -74.257107 40.838124, -74.256523 40.841781, -74.251892 40.849829, -74.2334 40.844389, -74.237214 40.836034, -74.227445 40.83331, -74.227147 40.836396, -74.22099 40.834799, -74.225755 40.827381, -74.224352 40.827071, -74.227054 40.824221, -74.227725 40.824867, -74.229113 40.822199, -74.229079 40.820019, -74.232573 40.814863, -74.250795 40.824461, -74.264305 40.825431, -74.261032 40.832085))',\n            2.791550146,\n            2.282754753,\n            'C_Medium_High',\n            7044,\n            0.652425579,\n            3628,\n            31.61574586,\n            596.5287053,\n            0.677508269,\n            0.997794928,\n            19.71404236,\n            13.61658418,\n            0.263\n          ]\n        ],\n        fields: [\n          {\n            name: 'a_zip',\n            type: 'integer'\n          },\n          {\n            name: 'simplified_shape_v2',\n            type: 'geojson'\n          },\n          {\n            name: 'simplified_shape',\n            type: 'geojson'\n          },\n          {\n            name: 'zip_area',\n            type: 'real'\n          },\n          {\n            name: 'avg_number',\n            type: 'real'\n          },\n          {\n            name: 'str_type',\n            type: 'string'\n          },\n          {\n            name: 'int_type',\n            type: 'integer'\n          },\n          {\n            name: 'real_type',\n            type: 'real'\n          },\n          {\n            name: 'c_m_r',\n            type: 'integer'\n          },\n          {\n            name: 'c_m_t',\n            type: 'real'\n          },\n          {\n            name: 'c_a_v',\n            type: 'real'\n          },\n          {\n            name: 'c_ch',\n            type: 'real'\n          },\n          {\n            name: 'c_ta',\n            type: 'real'\n          },\n          {\n            name: 'c_k_a',\n            type: 'real'\n          },\n          {\n            name: 'c_ltv',\n            type: 'real'\n          },\n          {\n            name: 'b_r_p',\n            type: 'real'\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: ['a5ybmwl2d', 'not-valid'],\n            id: ['9ca0l7p2a'],\n            name: 'zip_area',\n            type: 'range',\n            value: [0, 21.8],\n            enlarged: false\n          }\n        ],\n        layers: [\n          {\n            id: '8zr03we',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'extruded polygon',\n              color: [181, 18, 65],\n              columns: {\n                geojson: 'simplified_shape_v2'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 2,\n                colorRange: {\n                  name: 'ColorBrewer YlGnBu-9',\n                  type: 'sequential',\n                  category: 'ColorBrewer',\n                  colors: [\n                    '#081d58',\n                    '#253494',\n                    '#225ea8',\n                    '#1d91c0',\n                    '#41b6c4',\n                    '#7fcdbb',\n                    '#c7e9b4',\n                    '#edf8b1',\n                    '#ffffd9'\n                  ],\n                  reversed: true\n                },\n                radius: 10,\n                sizeRange: [0, 10],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 18,\n                stroked: true,\n                filled: true,\n                enable3d: true,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: {\n                name: 'c_m_r',\n                type: 'integer'\n              },\n              colorScale: 'quantize',\n              sizeField: null,\n              sizeScale: 'linear',\n              heightField: {\n                name: 'c_a_v',\n                type: 'real'\n              },\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          },\n          {\n            id: 'u7apppt',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'stroke by pop',\n              color: [221, 178, 124],\n              columns: {\n                geojson: 'simplified_shape'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 7.6,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radius: 10,\n                sizeRange: [18.9, 47.6],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 5,\n                'hi-precision': false,\n                stroked: true,\n                filled: false,\n                enable3d: false,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: {\n                name: 'c_ta',\n                type: 'real'\n              },\n              sizeScale: 'linear',\n              heightField: null,\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              a5ybmwl2d: ['a_zip', 'str_type', 'int_type']\n            },\n            enabled: false\n          },\n          brush: {\n            size: 1,\n            enabled: false\n          }\n        },\n        layerBlending: 'normal'\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        buildingLayer: {\n          isVisible: false,\n          color: [209, 206, 199],\n          opacity: 0.7\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.75043,\n        longitude: -122.34679,\n        pitch: 0,\n        zoom: 9\n      },\n      queryStatus: {\n        pastQueries: [\n          {\n            id: 'a5ybmwl2d',\n            label: 'geojson_as_string_small.csv',\n            queryType: 'file',\n            queryOption: 'csv',\n            params: {\n              file: null\n            },\n            size: 67502\n          }\n        ]\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/state-saved-v1-7.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const savedStateV1InteractionCoordinate = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'a5ybmwl2d',\n        label: 'geojson_as_string_small.csv',\n        color: [53, 92, 125],\n        allData: [\n          [\n            7014,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522],[-74.146471,40.836645],[-74.142598,40.833128],[-74.140177,40.832492],[-74.136732,40.836791],[-74.142706,40.840604],[-74.144667,40.84312],[-74.142936,40.844974],[-74.136054,40.84119],[-74.1356,40.841703],[-74.133665,40.840712],[-74.133028,40.841321],[-74.13274,40.839586],[-74.121853,40.834098],[-74.121087,40.831],[-74.124447,40.822169],[-74.130031,40.819962],[-74.149242,40.830537],[-74.148818,40.830916],[-74.150888,40.833143],[-74.151923,40.832074],[-74.158491,40.835947]]]}',\n            'POLYGON ((-74.158491 40.835947, -74.157914 40.83902, -74.148473 40.834522, -74.146471 40.836645, -74.142598 40.833128, -74.140177 40.832492, -74.136732 40.836791, -74.142706 40.840604, -74.144667 40.84312, -74.142936 40.844974, -74.136054 40.84119, -74.1356 40.841703, -74.133665 40.840712, -74.133028 40.841321, -74.13274 40.839586, -74.121853 40.834098, -74.121087 40.831, -74.124447 40.822169, -74.130031 40.819962, -74.149242 40.830537, -74.148818 40.830916, -74.150888 40.833143, -74.151923 40.832074, -74.158491 40.835947))',\n            1.420645391,\n            2.32754338,\n            'C_Medium_High',\n            7014,\n            0.719251337,\n            1496,\n            29.07437458,\n            540.4402429,\n            0.681149733,\n            0.988636364,\n            17.92821726,\n            13.56510275,\n            0.283\n          ],\n          [\n            7016,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.326141,40.656094],[-74.326593,40.657702],[-74.327693,40.662904],[-74.325199,40.667596],[-74.326093,40.668202],[-74.323615,40.670664],[-74.323228,40.672276],[-74.324391,40.67444],[-74.322371,40.677443],[-74.310392,40.678402],[-74.312993,40.674703],[-74.312093,40.674304],[-74.314552,40.673889],[-74.312827,40.673282],[-74.307888,40.674387],[-74.306792,40.672102],[-74.290976,40.673465],[-74.29185,40.671802],[-74.290689,40.670646],[-74.288727,40.670925],[-74.285991,40.667001],[-74.284275,40.658075],[-74.283511,40.658187],[-74.283369,40.657406],[-74.285786,40.655022],[-74.282693,40.653502],[-74.280691,40.645712],[-74.282183,40.644572],[-74.281687,40.643381],[-74.287325,40.639999],[-74.29577,40.638581],[-74.304907,40.633459],[-74.319678,40.641989],[-74.316485,40.645194],[-74.320663,40.647344],[-74.312298,40.654042],[-74.31687,40.656696]]]}',\n            'POLYGON ((-74.31687 40.656696, -74.319449 40.658154, -74.326141 40.656094, -74.326593 40.657702, -74.327693 40.662904, -74.325199 40.667596, -74.326093 40.668202, -74.323615 40.670664, -74.323228 40.672276, -74.324391 40.67444, -74.322371 40.677443, -74.310392 40.678402, -74.312993 40.674703, -74.312093 40.674304, -74.314552 40.673889, -74.312827 40.673282, -74.307888 40.674387, -74.306792 40.672102, -74.290976 40.673465, -74.29185 40.671802, -74.290689 40.670646, -74.288727 40.670925, -74.285991 40.667001, -74.284275 40.658075, -74.283511 40.658187, -74.283369 40.657406, -74.285786 40.655022, -74.282693 40.653502, -74.280691 40.645712, -74.282183 40.644572, -74.281687 40.643381, -74.287325 40.639999, -74.29577 40.638581, -74.304907 40.633459, -74.319678 40.641989, -74.316485 40.645194, -74.320663 40.647344, -74.312298 40.654042, -74.31687 40.656696))',\n            4.873351888,\n            2.125379919,\n            'C_Medium_High',\n            7016,\n            0.621409029,\n            5848,\n            27.60106201,\n            528.1274711,\n            0.678522572,\n            0.998290014,\n            19.05388261,\n            12.47290995,\n            0.256\n          ],\n          [\n            7023,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.387589,40.632238],[-74.401749,40.640393],[-74.386745,40.653316],[-74.374682,40.646553],[-74.372634,40.6468],[-74.371828,40.644885],[-74.374122,40.642352],[-74.37327,40.641829],[-74.375777,40.636295],[-74.37717,40.634842],[-74.382333,40.632417],[-74.384457,40.630453],[-74.387589,40.632238]]]}',\n            'POLYGON ((-74.387589 40.632238, -74.401749 40.640393, -74.386745 40.653316, -74.374682 40.646553, -74.372634 40.6468, -74.371828 40.644885, -74.374122 40.642352, -74.37327 40.641829, -74.375777 40.636295, -74.37717 40.634842, -74.382333 40.632417, -74.384457 40.630453, -74.387589 40.632238))',\n            1.352750157,\n            1.975548142,\n            'C_Medium_High',\n            7023,\n            0.580808081,\n            1782,\n            23.85239437,\n            457.401356,\n            0.673400673,\n            0.996071829,\n            18.41337089,\n            11.17832315,\n            0.241\n          ],\n          [\n            7029,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.165995,40.747969],[-74.164799,40.754718],[-74.155877,40.751722],[-74.154327,40.753966],[-74.145563,40.751975],[-74.144976,40.748161],[-74.14586,40.748031],[-74.140783,40.742694],[-74.139386,40.743314],[-74.137492,40.741398],[-74.142688,40.739301],[-74.145288,40.735701],[-74.147188,40.734401],[-74.155687,40.733501],[-74.15998,40.734485],[-74.162093,40.736109],[-74.163788,40.739201],[-74.165987,40.745199],[-74.165995,40.747969]]]}',\n            'POLYGON ((-74.165995 40.747969, -74.164799 40.754718, -74.155877 40.751722, -74.154327 40.753966, -74.145563 40.751975, -74.144976 40.748161, -74.14586 40.748031, -74.140783 40.742694, -74.139386 40.743314, -74.137492 40.741398, -74.142688 40.739301, -74.145288 40.735701, -74.147188 40.734401, -74.155687 40.733501, -74.15998 40.734485, -74.162093 40.736109, -74.163788 40.739201, -74.165987 40.745199, -74.165995 40.747969))',\n            1.446889939,\n            2.701389429,\n            'C_Medium_High',\n            7029,\n            0.814644137,\n            7812,\n            39.13775443,\n            545.4891226,\n            0.653481823,\n            0.974782386,\n            17.88895163,\n            13.72188322,\n            0.474\n          ],\n          [\n            7045,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.361356,40.939299],[-74.370498,40.926937],[-74.37404,40.927298],[-74.374216,40.921969],[-74.371645,40.92135],[-74.366428,40.918141],[-74.356676,40.915791],[-74.349697,40.916512],[-74.347883,40.91351],[-74.349593,40.91185],[-74.353183,40.910446],[-74.350384,40.908593],[-74.351179,40.907344],[-74.349633,40.906311],[-74.351883,40.90419],[-74.351747,40.902647],[-74.349573,40.903845],[-74.349271,40.902641],[-74.344758,40.902596],[-74.345589,40.900045],[-74.339928,40.899308],[-74.339554,40.895904],[-74.338613,40.895769],[-74.33944,40.893881],[-74.335743,40.893239],[-74.3389,40.892087],[-74.339694,40.889839],[-74.339646,40.888758],[-74.338118,40.887173],[-74.339575,40.883893],[-74.340271,40.879528],[-74.338075,40.879652],[-74.337614,40.878206],[-74.33507,40.877426],[-74.33531,40.876555],[-74.337696,40.87591],[-74.337808,40.875101],[-74.334259,40.873471],[-74.337288,40.872611],[-74.337418,40.87179],[-74.335937,40.870521],[-74.33806,40.870059],[-74.336714,40.869079],[-74.33765,40.868402],[-74.33768,40.866752],[-74.339806,40.866539],[-74.34018,40.868365],[-74.339545,40.874876],[-74.341626,40.877524],[-74.340485,40.882283],[-74.342874,40.885385],[-74.346198,40.887367],[-74.35009,40.887702],[-74.352792,40.886265],[-74.355507,40.888913],[-74.359916,40.886643],[-74.360096,40.887329],[-74.361792,40.887158],[-74.36087,40.883379],[-74.358905,40.883365],[-74.358888,40.880786],[-74.357662,40.880344],[-74.357789,40.879259],[-74.358676,40.879136],[-74.358091,40.878002],[-74.362636,40.877577],[-74.360456,40.872665],[-74.361843,40.87065],[-74.365778,40.871801],[-74.366753,40.874063],[-74.37127,40.876723],[-74.369921,40.878054],[-74.370331,40.879192],[-74.371574,40.879878],[-74.37187,40.878855],[-74.373453,40.878978],[-74.373051,40.880533],[-74.374017,40.882604],[-74.3772,40.886221],[-74.376296,40.888095],[-74.376308,40.890713],[-74.374745,40.893264],[-74.374127,40.898616],[-74.374902,40.901059],[-74.378188,40.901844],[-74.378509,40.900151],[-74.383293,40.899546],[-74.384277,40.902503],[-74.383622,40.902564],[-74.383706,40.903409],[-74.384609,40.903338],[-74.387159,40.909755],[-74.390966,40.909837],[-74.392542,40.913437],[-74.393499,40.91741],[-74.390968,40.919447],[-74.394272,40.926232],[-74.388795,40.930394],[-74.389726,40.930836],[-74.38883,40.931932],[-74.389795,40.931872],[-74.390036,40.932698],[-74.374793,40.940133],[-74.374929,40.941559],[-74.37655,40.942487],[-74.381516,40.942622],[-74.380974,40.946451],[-74.374397,40.954036],[-74.367575,40.955078],[-74.365156,40.954335],[-74.363667,40.955394],[-74.364141,40.954467],[-74.362181,40.951667],[-74.36267,40.944287],[-74.360703,40.940588],[-74.361356,40.939299]]]}',\n            'POLYGON ((-74.361356 40.939299, -74.370498 40.926937, -74.37404 40.927298, -74.374216 40.921969, -74.371645 40.92135, -74.366428 40.918141, -74.356676 40.915791, -74.349697 40.916512, -74.347883 40.91351, -74.349593 40.91185, -74.353183 40.910446, -74.350384 40.908593, -74.351179 40.907344, -74.349633 40.906311, -74.351883 40.90419, -74.351747 40.902647, -74.349573 40.903845, -74.349271 40.902641, -74.344758 40.902596, -74.345589 40.900045, -74.339928 40.899308, -74.339554 40.895904, -74.338613 40.895769, -74.33944 40.893881, -74.335743 40.893239, -74.3389 40.892087, -74.339694 40.889839, -74.339646 40.888758, -74.338118 40.887173, -74.339575 40.883893, -74.340271 40.879528, -74.338075 40.879652, -74.337614 40.878206, -74.33507 40.877426, -74.33531 40.876555, -74.337696 40.87591, -74.337808 40.875101, -74.334259 40.873471, -74.337288 40.872611, -74.337418 40.87179, -74.335937 40.870521, -74.33806 40.870059, -74.336714 40.869079, -74.33765 40.868402, -74.33768 40.866752, -74.339806 40.866539, -74.34018 40.868365, -74.339545 40.874876, -74.341626 40.877524, -74.340485 40.882283, -74.342874 40.885385, -74.346198 40.887367, -74.35009 40.887702, -74.352792 40.886265, -74.355507 40.888913, -74.359916 40.886643, -74.360096 40.887329, -74.361792 40.887158, -74.36087 40.883379, -74.358905 40.883365, -74.358888 40.880786, -74.357662 40.880344, -74.357789 40.879259, -74.358676 40.879136, -74.358091 40.878002, -74.362636 40.877577, -74.360456 40.872665, -74.361843 40.87065, -74.365778 40.871801, -74.366753 40.874063, -74.37127 40.876723, -74.369921 40.878054, -74.370331 40.879192, -74.371574 40.879878, -74.37187 40.878855, -74.373453 40.878978, -74.373051 40.880533, -74.374017 40.882604, -74.3772 40.886221, -74.376296 40.888095, -74.376308 40.890713, -74.374745 40.893264, -74.374127 40.898616, -74.374902 40.901059, -74.378188 40.901844, -74.378509 40.900151, -74.383293 40.899546, -74.384277 40.902503, -74.383622 40.902564, -74.383706 40.903409, -74.384609 40.903338, -74.387159 40.909755, -74.390966 40.909837, -74.392542 40.913437, -74.393499 40.91741, -74.390968 40.919447, -74.394272 40.926232, -74.388795 40.930394, -74.389726 40.930836, -74.38883 40.931932, -74.389795 40.931872, -74.390036 40.932698, -74.374793 40.940133, -74.374929 40.941559, -74.37655 40.942487, -74.381516 40.942622, -74.380974 40.946451, -74.374397 40.954036, -74.367575 40.955078, -74.365156 40.954335, -74.363667 40.955394, -74.364141 40.954467, -74.362181 40.951667, -74.36267 40.944287, -74.360703 40.940588, -74.361356 40.939299))',\n            7.403173506,\n            2.253640666,\n            'B_Low_Suburban',\n            7045,\n            0.477082503,\n            2509,\n            28.18247299,\n            519.2220835,\n            0.672777999,\n            0.996014348,\n            18.72091503,\n            12.48069003,\n            0.258\n          ],\n          [\n            7073,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.087219,40.799287],[-74.105261,40.838083],[-74.113924,40.842218],[-74.121404,40.847184],[-74.119291,40.851674],[-74.119462,40.85335],[-74.11538,40.850927],[-74.109,40.845092],[-74.078747,40.827548],[-74.070783,40.822082],[-74.066115,40.817041],[-74.061984,40.810171],[-74.060685,40.8057],[-74.065135,40.80302],[-74.068585,40.7986],[-74.071399,40.797241],[-74.077,40.796379],[-74.077787,40.792796],[-74.081794,40.787994],[-74.087219,40.799287]]]}',\n            'POLYGON ((-74.087219 40.799287, -74.105261 40.838083, -74.113924 40.842218, -74.121404 40.847184, -74.119291 40.851674, -74.119462 40.85335, -74.11538 40.850927, -74.109 40.845092, -74.078747 40.827548, -74.070783 40.822082, -74.066115 40.817041, -74.061984 40.810171, -74.060685 40.8057, -74.065135 40.80302, -74.068585 40.7986, -74.071399 40.797241, -74.077 40.796379, -74.077787 40.792796, -74.081794 40.787994, -74.087219 40.799287))',\n            4.076259122,\n            2.483658001,\n            'B_Low_Suburban',\n            7073,\n            0.745130406,\n            3029,\n            35.66977674,\n            638.4631914,\n            0.671838891,\n            0.990756025,\n            18.39194713,\n            15.62142573,\n            0.333\n          ],\n          [\n            7081,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.347067,40.691722],[-74.36039,40.699821],[-74.352171,40.70148],[-74.342221,40.707846],[-74.333594,40.716901],[-74.330619,40.716055],[-74.332088,40.720176],[-74.325596,40.717075],[-74.321408,40.719072],[-74.315991,40.719199],[-74.311691,40.715601],[-74.309791,40.715501],[-74.308793,40.714508],[-74.302092,40.713301],[-74.299496,40.713889],[-74.29869,40.71479],[-74.296151,40.712434],[-74.297212,40.710298],[-74.301677,40.707498],[-74.301078,40.704678],[-74.306692,40.703802],[-74.309119,40.702625],[-74.310906,40.700392],[-74.310666,40.699622],[-74.307393,40.698801],[-74.307893,40.695999],[-74.30664,40.694304],[-74.308655,40.691171],[-74.312226,40.689892],[-74.310772,40.683794],[-74.307292,40.682506],[-74.308989,40.680407],[-74.307753,40.680292],[-74.310392,40.678402],[-74.322575,40.677433],[-74.347067,40.691722]]]}',\n            'POLYGON ((-74.347067 40.691722, -74.36039 40.699821, -74.352171 40.70148, -74.342221 40.707846, -74.333594 40.716901, -74.330619 40.716055, -74.332088 40.720176, -74.325596 40.717075, -74.321408 40.719072, -74.315991 40.719199, -74.311691 40.715601, -74.309791 40.715501, -74.308793 40.714508, -74.302092 40.713301, -74.299496 40.713889, -74.29869 40.71479, -74.296151 40.712434, -74.297212 40.710298, -74.301677 40.707498, -74.301078 40.704678, -74.306692 40.703802, -74.309119 40.702625, -74.310906 40.700392, -74.310666 40.699622, -74.307393 40.698801, -74.307893 40.695999, -74.30664 40.694304, -74.308655 40.691171, -74.312226 40.689892, -74.310772 40.683794, -74.307292 40.682506, -74.308989 40.680407, -74.307753 40.680292, -74.310392 40.678402, -74.322575 40.677433, -74.347067 40.691722))',\n            5.142008449,\n            2.352650655,\n            'C_Medium_High',\n            7081,\n            0.65038271,\n            4442,\n            29.37274368,\n            514.8178812,\n            0.693156236,\n            0.997748762,\n            18.36100331,\n            12.61739583,\n            0.273\n          ],\n          [\n            7102,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.184345,40.727604],[-74.183145,40.730677],[-74.181958,40.730397],[-74.181105,40.732722],[-74.181977,40.732938],[-74.181504,40.734135],[-74.18328,40.734558],[-74.182741,40.735778],[-74.179407,40.737014],[-74.182582,40.73798],[-74.181787,40.739633],[-74.179055,40.737705],[-74.178029,40.739555],[-74.179295,40.739935],[-74.17785,40.742469],[-74.18066,40.74342],[-74.179821,40.744703],[-74.177068,40.743847],[-74.175752,40.745597],[-74.17646,40.745849],[-74.175989,40.747417],[-74.173784,40.746929],[-74.173566,40.747955],[-74.170978,40.747424],[-74.167568,40.748054],[-74.167484,40.747446],[-74.165976,40.748091],[-74.165987,40.745199],[-74.163788,40.739201],[-74.161669,40.735781],[-74.166099,40.732923],[-74.175517,40.72346],[-74.176772,40.723054],[-74.17736,40.723261],[-74.176965,40.723961],[-74.180517,40.724365],[-74.179633,40.725829],[-74.180319,40.726113],[-74.184236,40.724266],[-74.183892,40.72506],[-74.185205,40.725405],[-74.184345,40.727604]]]}',\n            'POLYGON ((-74.184345 40.727604, -74.183145 40.730677, -74.181958 40.730397, -74.181105 40.732722, -74.181977 40.732938, -74.181504 40.734135, -74.18328 40.734558, -74.182741 40.735778, -74.179407 40.737014, -74.182582 40.73798, -74.181787 40.739633, -74.179055 40.737705, -74.178029 40.739555, -74.179295 40.739935, -74.17785 40.742469, -74.18066 40.74342, -74.179821 40.744703, -74.177068 40.743847, -74.175752 40.745597, -74.17646 40.745849, -74.175989 40.747417, -74.173784 40.746929, -74.173566 40.747955, -74.170978 40.747424, -74.167568 40.748054, -74.167484 40.747446, -74.165976 40.748091, -74.165987 40.745199, -74.163788 40.739201, -74.161669 40.735781, -74.166099 40.732923, -74.175517 40.72346, -74.176772 40.723054, -74.17736 40.723261, -74.176965 40.723961, -74.180517 40.724365, -74.179633 40.725829, -74.180319 40.726113, -74.184236 40.724266, -74.183892 40.72506, -74.185205 40.725405, -74.184345 40.727604))',\n            1.203818912,\n            3.301076417,\n            'C_Medium_High',\n            7102,\n            0.832516129,\n            3875,\n            55.20608285,\n            769.5874437,\n            0.645935484,\n            0.984258065,\n            18.16678028,\n            19.06305599,\n            0.3\n          ],\n          [\n            7110,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.1404,40.805639],[-74.146391,40.806291],[-74.149012,40.80766],[-74.149521,40.806858],[-74.148617,40.806521],[-74.175644,40.809531],[-74.183836,40.812225],[-74.179511,40.816664],[-74.178999,40.818771],[-74.176443,40.821854],[-74.174992,40.821802],[-74.162628,40.837869],[-74.151923,40.832074],[-74.150888,40.833143],[-74.148818,40.830916],[-74.149242,40.830537],[-74.130031,40.819962],[-74.136487,40.8182],[-74.137287,40.8173],[-74.138787,40.8074],[-74.1404,40.805639]]]}',\n            'POLYGON ((-74.1404 40.805639, -74.146391 40.806291, -74.149012 40.80766, -74.149521 40.806858, -74.148617 40.806521, -74.175644 40.809531, -74.183836 40.812225, -74.179511 40.816664, -74.178999 40.818771, -74.176443 40.821854, -74.174992 40.821802, -74.162628 40.837869, -74.151923 40.832074, -74.150888 40.833143, -74.148818 40.830916, -74.149242 40.830537, -74.130031 40.819962, -74.136487 40.8182, -74.137287 40.8173, -74.138787 40.8074, -74.1404 40.805639))',\n            3.418972265,\n            2.350813772,\n            'C_Medium_High',\n            7110,\n            0.740131579,\n            7600,\n            28.24510978,\n            494.0816839,\n            0.686578947,\n            0.988815789,\n            18.33537813,\n            12.12610704,\n            0.268\n          ],\n          [\n            7416,\n            '{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-74.566993,41.087294],[-74.564908,41.089339],[-74.56559,41.090507],[-74.564446,41.091931],[-74.562675,41.093801],[-74.560495,41.094523],[-74.559465,41.093899],[-74.558011,41.094312],[-74.556055,41.091781],[-74.554015,41.092675],[-74.552353,41.091647],[-74.552998,41.09013],[-74.556709,41.087409],[-74.558078,41.085348],[-74.56136,41.08293],[-74.563205,41.082847],[-74.565111,41.081667],[-74.567341,41.082366],[-74.566524,41.085226],[-74.566993,41.087294]]],[[[-74.593264,41.088526],[-74.601504,41.094096],[-74.602269,41.092905],[-74.606875,41.096689],[-74.606957,41.097715],[-74.638551,41.118611],[-74.634186,41.132604],[-74.633166,41.133324],[-74.629712,41.131934],[-74.625246,41.136424],[-74.620677,41.139411],[-74.613785,41.139652],[-74.6101,41.137965],[-74.610517,41.136736],[-74.606433,41.135423],[-74.60466,41.136534],[-74.598719,41.137644],[-74.595925,41.139251],[-74.593358,41.139231],[-74.579959,41.144635],[-74.576893,41.136939],[-74.573312,41.136907],[-74.571246,41.137717],[-74.569941,41.136878],[-74.575411,41.101456],[-74.577128,41.101248],[-74.574449,41.101263],[-74.567456,41.103641],[-74.562238,41.103369],[-74.55789,41.106705],[-74.552862,41.10746],[-74.557344,41.105958],[-74.56193,41.102739],[-74.569487,41.100323],[-74.575935,41.095058],[-74.578362,41.09142],[-74.583558,41.087448],[-74.585763,41.083491],[-74.593264,41.088526]]]]}',\n            'MULTIPOLYGON (((-74.566993 41.087294, -74.564908 41.089339, -74.56559 41.090507, -74.564446 41.091931, -74.562675 41.093801, -74.560495 41.094523, -74.559465 41.093899, -74.558011 41.094312, -74.556055 41.091781, -74.554015 41.092675, -74.552353 41.091647, -74.552998 41.09013, -74.556709 41.087409, -74.558078 41.085348, -74.56136 41.08293, -74.563205 41.082847, -74.565111 41.081667, -74.567341 41.082366, -74.566524 41.085226, -74.566993 41.087294)), ((-74.593264 41.088526, -74.601504 41.094096, -74.602269 41.092905, -74.606875 41.096689, -74.606957 41.097715, -74.638551 41.118611, -74.634186 41.132604, -74.633166 41.133324, -74.629712 41.131934, -74.625246 41.136424, -74.620677 41.139411, -74.613785 41.139652, -74.6101 41.137965, -74.610517 41.136736, -74.606433 41.135423, -74.60466 41.136534, -74.598719 41.137644, -74.595925 41.139251, -74.593358 41.139231, -74.579959 41.144635, -74.576893 41.136939, -74.573312 41.136907, -74.571246 41.137717, -74.569941 41.136878, -74.575411 41.101456, -74.577128 41.101248, -74.574449 41.101263, -74.567456 41.103641, -74.562238 41.103369, -74.55789 41.106705, -74.552862 41.10746, -74.557344 41.105958, -74.56193 41.102739, -74.569487 41.100323, -74.575935 41.095058, -74.578362 41.09142, -74.583558 41.087448, -74.585763 41.083491, -74.593264 41.088526)))',\n            9.599149618,\n            2.546774404,\n            'A_Low_Rural',\n            7416,\n            0.362944162,\n            394,\n            13.81679389,\n            234.9184295,\n            0.748730964,\n            0.997461929,\n            14.28320611,\n            7.401229981,\n            0.066\n          ],\n          [\n            7481,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158531,41.025141],[-74.155604,41.02585],[-74.155079,41.0217],[-74.147648,41.022058],[-74.149441,41.013336],[-74.151076,41.010084],[-74.142115,41.008711],[-74.141446,41.007704],[-74.137536,41.007495],[-74.141401,41.005903],[-74.145988,41.001919],[-74.155782,41.001404],[-74.157154,40.998933],[-74.153255,40.996719],[-74.151282,40.993204],[-74.153874,40.98999],[-74.149887,40.987538],[-74.142812,40.986407],[-74.141437,40.984207],[-74.14121,40.982761],[-74.142619,40.981621],[-74.149088,40.971137],[-74.17021,40.983865],[-74.170988,40.976997],[-74.191835,40.978688],[-74.189987,40.992282],[-74.189546,41.003824],[-74.18806,41.00691],[-74.187244,41.01685],[-74.16758,41.023781],[-74.163392,41.022212],[-74.158531,41.025141]]]}',\n            'POLYGON ((-74.158531 41.025141, -74.155604 41.02585, -74.155079 41.0217, -74.147648 41.022058, -74.149441 41.013336, -74.151076 41.010084, -74.142115 41.008711, -74.141446 41.007704, -74.137536 41.007495, -74.141401 41.005903, -74.145988 41.001919, -74.155782 41.001404, -74.157154 40.998933, -74.153255 40.996719, -74.151282 40.993204, -74.153874 40.98999, -74.149887 40.987538, -74.142812 40.986407, -74.141437 40.984207, -74.14121 40.982761, -74.142619 40.981621, -74.149088 40.971137, -74.17021 40.983865, -74.170988 40.976997, -74.191835 40.978688, -74.189987 40.992282, -74.189546 41.003824, -74.18806 41.00691, -74.187244 41.01685, -74.16758 41.023781, -74.163392 41.022212, -74.158531 41.025141))',\n            6.59865985,\n            2.21913015,\n            'B_Low_Suburban',\n            7481,\n            0.507943713,\n            4406,\n            37.98110631,\n            779.7471722,\n            0.639809351,\n            0.997049478,\n            20.78244176,\n            16.88378255,\n            0.262\n          ],\n          [\n            7495,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.164572,41.104182],[-74.166137,41.104468],[-74.163755,41.105993],[-74.16128,41.105606],[-74.162067,41.104822],[-74.161454,41.103924],[-74.162067,41.102889],[-74.163932,41.102848],[-74.164572,41.104182]]]}',\n            'POLYGON ((-74.164572 41.104182, -74.166137 41.104468, -74.163755 41.105993, -74.16128 41.105606, -74.162067 41.104822, -74.161454 41.103924, -74.162067 41.102889, -74.163932 41.102848, -74.164572 41.104182))',\n            0.03213684,\n            3.361538462,\n            'A_Low_Rural',\n            7495,\n            0.355555556,\n            45,\n            30.4,\n            709.541378,\n            0.8,\n            1,\n            18.01481481,\n            17.72394684,\n            0\n          ],\n          [\n            7506,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.151143,40.97231],[-74.141048,40.966348],[-74.148059,40.94159],[-74.149587,40.941398],[-74.153938,40.937972],[-74.16151,40.937498],[-74.167127,40.934519],[-74.171588,40.941998],[-74.169018,40.947413],[-74.167486,40.95733],[-74.16921,40.969444],[-74.170814,40.970178],[-74.17021,40.983865],[-74.151143,40.97231]]]}',\n            'POLYGON ((-74.151143 40.97231, -74.141048 40.966348, -74.148059 40.94159, -74.149587 40.941398, -74.153938 40.937972, -74.16151 40.937498, -74.167127 40.934519, -74.171588 40.941998, -74.169018 40.947413, -74.167486 40.95733, -74.16921 40.969444, -74.170814 40.970178, -74.17021 40.983865, -74.151143 40.97231))',\n            3.374654059,\n            2.479267611,\n            'C_Medium_High',\n            7506,\n            0.677288685,\n            3703,\n            26.69672353,\n            436.3732942,\n            0.703483662,\n            0.997299487,\n            16.88975539,\n            11.62645508,\n            0.196\n          ],\n          [\n            7605,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99863,40.866791],[-73.992942,40.873032],[-73.994052,40.874607],[-73.993799,40.876601],[-73.983367,40.875127],[-73.978515,40.870534],[-73.97569,40.868903],[-73.977687,40.8605],[-73.979382,40.860799],[-73.980514,40.857975],[-73.987985,40.850884],[-73.997417,40.856176],[-73.99829,40.855105],[-74.006059,40.85697],[-74.002723,40.864981],[-73.99863,40.866791]]]}',\n            'POLYGON ((-73.99863 40.866791, -73.992942 40.873032, -73.994052 40.874607, -73.993799 40.876601, -73.983367 40.875127, -73.978515 40.870534, -73.97569 40.868903, -73.977687 40.8605, -73.979382 40.860799, -73.980514 40.857975, -73.987985 40.850884, -73.997417 40.856176, -73.99829 40.855105, -74.006059 40.85697, -74.002723 40.864981, -73.99863 40.866791))',\n            1.642585048,\n            2.453964783,\n            'C_Medium_High',\n            7605,\n            0.656508653,\n            2658,\n            33.78153495,\n            611.0165724,\n            0.656884876,\n            0.990218209,\n            18.13150963,\n            15.16462023,\n            0.293\n          ],\n          [\n            7631,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-73.99002,40.887038],[-73.994572,40.888663],[-73.98955,40.900569],[-73.989916,40.906341],[-73.989286,40.911479],[-73.988717,40.911674],[-73.972202,40.912892],[-73.971972,40.913617],[-73.949229,40.900817],[-73.959169,40.885014],[-73.95583,40.883117],[-73.957173,40.881425],[-73.957839,40.882492],[-73.958321,40.882085],[-73.957902,40.880512],[-73.969825,40.865568],[-73.978515,40.870534],[-73.983367,40.875127],[-73.993933,40.876539],[-73.992783,40.879099],[-73.98739,40.886003],[-73.99002,40.887038]]]}',\n            'POLYGON ((-73.99002 40.887038, -73.994572 40.888663, -73.98955 40.900569, -73.989916 40.906341, -73.989286 40.911479, -73.988717 40.911674, -73.972202 40.912892, -73.971972 40.913617, -73.949229 40.900817, -73.959169 40.885014, -73.95583 40.883117, -73.957173 40.881425, -73.957839 40.882492, -73.958321 40.882085, -73.957902 40.880512, -73.969825 40.865568, -73.978515 40.870534, -73.983367 40.875127, -73.993933 40.876539, -73.992783 40.879099, -73.98739 40.886003, -73.99002 40.887038))',\n            4.926323412,\n            2.742937844,\n            'C_Medium_High',\n            7631,\n            0.695602572,\n            9642,\n            39.65661571,\n            777.8438479,\n            0.669155777,\n            0.983717071,\n            18.88216131,\n            18.53758825,\n            0.354\n          ],\n          [\n            7643,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.04531,40.854103],[-74.044779,40.853794],[-74.044816,40.85746],[-74.043745,40.857547],[-74.043757,40.85919],[-74.032018,40.858647],[-74.028884,40.855216],[-74.028418,40.852282],[-74.029969,40.847658],[-74.029185,40.843427],[-74.030484,40.8389],[-74.029084,40.8344],[-74.030084,40.832],[-74.035384,40.8288],[-74.036918,40.82855],[-74.040269,40.830288],[-74.03909,40.830425],[-74.039014,40.832249],[-74.040669,40.832316],[-74.039777,40.834102],[-74.04154,40.833922],[-74.041217,40.834652],[-74.042195,40.834792],[-74.042262,40.836937],[-74.043267,40.838359],[-74.042435,40.840983],[-74.043045,40.843654],[-74.045484,40.843596],[-74.045798,40.846145],[-74.051577,40.847894],[-74.052563,40.848853],[-74.052518,40.850817],[-74.053981,40.853863],[-74.051677,40.857692],[-74.04531,40.854103]]]}',\n            'POLYGON ((-74.04531 40.854103, -74.044779 40.853794, -74.044816 40.85746, -74.043745 40.857547, -74.043757 40.85919, -74.032018 40.858647, -74.028884 40.855216, -74.028418 40.852282, -74.029969 40.847658, -74.029185 40.843427, -74.030484 40.8389, -74.029084 40.8344, -74.030084 40.832, -74.035384 40.8288, -74.036918 40.82855, -74.040269 40.830288, -74.03909 40.830425, -74.039014 40.832249, -74.040669 40.832316, -74.039777 40.834102, -74.04154 40.833922, -74.041217 40.834652, -74.042195 40.834792, -74.042262 40.836937, -74.043267 40.838359, -74.042435 40.840983, -74.043045 40.843654, -74.045484 40.843596, -74.045798 40.846145, -74.051577 40.847894, -74.052563 40.848853, -74.052518 40.850817, -74.053981 40.853863, -74.051677 40.857692, -74.04531 40.854103))',\n            1.698675619,\n            2.552779817,\n            'C_Medium_High',\n            7643,\n            0.787673552,\n            2953,\n            28.46145797,\n            457.6531695,\n            0.686420589,\n            0.970877074,\n            16.71062667,\n            12.32412945,\n            0.275\n          ],\n          [\n            7645,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.033649,41.039572],[-74.03703,41.043696],[-74.051378,41.049278],[-74.063774,41.044631],[-74.069675,41.041541],[-74.071014,41.046613],[-74.072234,41.046686],[-74.072452,41.048935],[-74.079369,41.048492],[-74.078814,41.055144],[-74.07699,41.061365],[-74.071915,41.072883],[-74.012583,41.046396],[-74.013197,41.041442],[-74.012452,41.038698],[-74.028831,41.036591],[-74.030807,41.037199],[-74.029374,41.039144],[-74.029546,41.040261],[-74.031631,41.037894],[-74.033649,41.039572]]]}',\n            'POLYGON ((-74.033649 41.039572, -74.03703 41.043696, -74.051378 41.049278, -74.063774 41.044631, -74.069675 41.041541, -74.071014 41.046613, -74.072234 41.046686, -74.072452 41.048935, -74.079369 41.048492, -74.078814 41.055144, -74.07699 41.061365, -74.071915 41.072883, -74.012583 41.046396, -74.013197 41.041442, -74.012452 41.038698, -74.028831 41.036591, -74.030807 41.037199, -74.029374 41.039144, -74.029546 41.040261, -74.031631 41.037894, -74.033649 41.039572))',\n            3.99137919,\n            2.383176018,\n            'B_Low_Suburban',\n            7645,\n            0.464448496,\n            2194,\n            31.67185355,\n            625.3501419,\n            0.667730173,\n            0.995897903,\n            18.75967963,\n            15.00065936,\n            0.277\n          ],\n          [\n            7405,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.424256,41.026137],[-74.423728,41.027596],[-74.421458,41.02854],[-74.417803,41.028693],[-74.414744,41.027759],[-74.41085,41.026199],[-74.40977,41.024847],[-74.40592,41.024094],[-74.403388,41.021833],[-74.402254,41.019233],[-74.399391,41.017289],[-74.390997,41.015688],[-74.389678,41.014254],[-74.389303,41.012431],[-74.388223,41.012046],[-74.386295,41.011688],[-74.382247,41.013959],[-74.379565,41.014282],[-74.371373,41.011399],[-74.367459,41.011994],[-74.364124,41.011293],[-74.36235,41.01383],[-74.355441,41.013943],[-74.351481,41.01099],[-74.351029,41.007174],[-74.352168,41.005987],[-74.350926,41.004335],[-74.34612,41.0046],[-74.342264,41.006904],[-74.340618,41.005845],[-74.336429,41.007529],[-74.334805,41.006464],[-74.334456,41.003592],[-74.329578,41.002512],[-74.328411,41.001419],[-74.330477,40.997535],[-74.329601,40.995845],[-74.329855,40.991939],[-74.332185,40.990168],[-74.331401,40.988262],[-74.330459,40.966053],[-74.336465,40.960102],[-74.340902,40.953453],[-74.339763,40.95213],[-74.336043,40.951261],[-74.335478,40.949418],[-74.341094,40.946298],[-74.364296,40.957929],[-74.397898,40.959829],[-74.410065,40.958717],[-74.428155,40.958911],[-74.428555,40.958226],[-74.428574,40.959286],[-74.431175,40.959368],[-74.430749,40.984608],[-74.432097,40.984008],[-74.432025,40.992541],[-74.430602,40.993339],[-74.430178,41.018128],[-74.429211,41.017679],[-74.425375,41.019271],[-74.426013,41.020166],[-74.423504,41.025734],[-74.424256,41.026137]]]}',\n            'POLYGON ((-74.424256 41.026137, -74.423728 41.027596, -74.421458 41.02854, -74.417803 41.028693, -74.414744 41.027759, -74.41085 41.026199, -74.40977 41.024847, -74.40592 41.024094, -74.403388 41.021833, -74.402254 41.019233, -74.399391 41.017289, -74.390997 41.015688, -74.389678 41.014254, -74.389303 41.012431, -74.388223 41.012046, -74.386295 41.011688, -74.382247 41.013959, -74.379565 41.014282, -74.371373 41.011399, -74.367459 41.011994, -74.364124 41.011293, -74.36235 41.01383, -74.355441 41.013943, -74.351481 41.01099, -74.351029 41.007174, -74.352168 41.005987, -74.350926 41.004335, -74.34612 41.0046, -74.342264 41.006904, -74.340618 41.005845, -74.336429 41.007529, -74.334805 41.006464, -74.334456 41.003592, -74.329578 41.002512, -74.328411 41.001419, -74.330477 40.997535, -74.329601 40.995845, -74.329855 40.991939, -74.332185 40.990168, -74.331401 40.988262, -74.330459 40.966053, -74.336465 40.960102, -74.340902 40.953453, -74.339763 40.95213, -74.336043 40.951261, -74.335478 40.949418, -74.341094 40.946298, -74.364296 40.957929, -74.397898 40.959829, -74.410065 40.958717, -74.428155 40.958911, -74.428555 40.958226, -74.428574 40.959286, -74.431175 40.959368, -74.430749 40.984608, -74.432097 40.984008, -74.432025 40.992541, -74.430602 40.993339, -74.430178 41.018128, -74.429211 41.017679, -74.425375 41.019271, -74.426013 41.020166, -74.423504 41.025734, -74.424256 41.026137))',\n            20.89261574,\n            2.276330461,\n            'A_Low_Rural',\n            7405,\n            0.480898221,\n            3429,\n            23.42811222,\n            430.9574125,\n            0.721201516,\n            0.997958589,\n            18.08334307,\n            10.72428006,\n            0.193\n          ],\n          [\n            7712,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.105049,40.269701],[-74.103488,40.272418],[-74.106018,40.274773],[-74.101607,40.280574],[-74.100748,40.279887],[-74.100161,40.280996],[-74.098821,40.28123],[-74.082891,40.274113],[-74.082201,40.270348],[-74.072487,40.275582],[-74.071041,40.274346],[-74.070161,40.270832],[-74.069033,40.270664],[-74.067579,40.27089],[-74.065924,40.272621],[-74.061569,40.273615],[-74.051627,40.272795],[-74.049539,40.271935],[-74.043385,40.273422],[-74.039322,40.25183],[-74.019689,40.253485],[-74.019866,40.251435],[-74.01764,40.249613],[-74.018373,40.247723],[-74.020904,40.246901],[-74.022762,40.240874],[-74.015502,40.23916],[-74.015218,40.237693],[-74.01292,40.236238],[-74.008684,40.235364],[-74.009389,40.232973],[-74.007068,40.232283],[-74.006199,40.231174],[-73.994705,40.230067],[-73.994681,40.224512],[-73.998481,40.216812],[-74.002556,40.21709],[-74.018647,40.213377],[-74.024998,40.213125],[-74.025508,40.219099],[-74.02618,40.219151],[-74.025518,40.219641],[-74.025516,40.223269],[-74.077409,40.234395],[-74.076503,40.240881],[-74.078391,40.240398],[-74.079541,40.242058],[-74.07912,40.243797],[-74.075526,40.245243],[-74.073864,40.257239],[-74.075693,40.259189],[-74.081411,40.258528],[-74.082119,40.251457],[-74.08462,40.252719],[-74.08778,40.250225],[-74.095925,40.249682],[-74.096515,40.245582],[-74.097686,40.245471],[-74.110838,40.252683],[-74.10372,40.255887],[-74.106884,40.259944],[-74.108802,40.260613],[-74.108582,40.264424],[-74.106872,40.265512],[-74.105049,40.269701]]]}',\n            'POLYGON ((-74.105049 40.269701, -74.103488 40.272418, -74.106018 40.274773, -74.101607 40.280574, -74.100748 40.279887, -74.100161 40.280996, -74.098821 40.28123, -74.082891 40.274113, -74.082201 40.270348, -74.072487 40.275582, -74.071041 40.274346, -74.070161 40.270832, -74.069033 40.270664, -74.067579 40.27089, -74.065924 40.272621, -74.061569 40.273615, -74.051627 40.272795, -74.049539 40.271935, -74.043385 40.273422, -74.039322 40.25183, -74.019689 40.253485, -74.019866 40.251435, -74.01764 40.249613, -74.018373 40.247723, -74.020904 40.246901, -74.022762 40.240874, -74.015502 40.23916, -74.015218 40.237693, -74.01292 40.236238, -74.008684 40.235364, -74.009389 40.232973, -74.007068 40.232283, -74.006199 40.231174, -73.994705 40.230067, -73.994681 40.224512, -73.998481 40.216812, -74.002556 40.21709, -74.018647 40.213377, -74.024998 40.213125, -74.025508 40.219099, -74.02618 40.219151, -74.025518 40.219641, -74.025516 40.223269, -74.077409 40.234395, -74.076503 40.240881, -74.078391 40.240398, -74.079541 40.242058, -74.07912 40.243797, -74.075526 40.245243, -74.073864 40.257239, -74.075693 40.259189, -74.081411 40.258528, -74.082119 40.251457, -74.08462 40.252719, -74.08778 40.250225, -74.095925 40.249682, -74.096515 40.245582, -74.097686 40.245471, -74.110838 40.252683, -74.10372 40.255887, -74.106884 40.259944, -74.108802 40.260613, -74.108582 40.264424, -74.106872 40.265512, -74.105049 40.269701))',\n            12.20511482,\n            2.181023176,\n            'C_Medium_High',\n            7712,\n            0.586637182,\n            6331,\n            25.68419385,\n            428.1136837,\n            0.683936187,\n            0.9973148,\n            18.24031781,\n            10.56183119,\n            0.162\n          ],\n          [\n            7036,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.293808,40.633387],[-74.291091,40.636102],[-74.290991,40.638102],[-74.28982,40.639951],[-74.287325,40.639999],[-74.281687,40.643381],[-74.282183,40.644572],[-74.280691,40.645712],[-74.281494,40.647271],[-74.281488,40.650145],[-74.264342,40.637244],[-74.258956,40.643217],[-74.251701,40.645117],[-74.248772,40.647293],[-74.241341,40.649791],[-74.232513,40.654927],[-74.200469,40.63263],[-74.202247,40.630903],[-74.203737,40.624227],[-74.201864,40.618557],[-74.203128,40.614109],[-74.203813,40.605961],[-74.199408,40.600201],[-74.19952,40.597539],[-74.203688,40.592691],[-74.210741,40.597032],[-74.212032,40.598372],[-74.21287,40.602035],[-74.215101,40.604074],[-74.220216,40.600555],[-74.231748,40.598881],[-74.234067,40.600345],[-74.234767,40.601945],[-74.236609,40.60272],[-74.242593,40.599754],[-74.25381,40.600989],[-74.255822,40.602194],[-74.258114,40.60225],[-74.255608,40.607618],[-74.278844,40.629365],[-74.285491,40.635102],[-74.290491,40.632903],[-74.292494,40.630803],[-74.296379,40.628711],[-74.297134,40.62933],[-74.293808,40.633387]]]}',\n            'POLYGON ((-74.293808 40.633387, -74.291091 40.636102, -74.290991 40.638102, -74.28982 40.639951, -74.287325 40.639999, -74.281687 40.643381, -74.282183 40.644572, -74.280691 40.645712, -74.281494 40.647271, -74.281488 40.650145, -74.264342 40.637244, -74.258956 40.643217, -74.251701 40.645117, -74.248772 40.647293, -74.241341 40.649791, -74.232513 40.654927, -74.200469 40.63263, -74.202247 40.630903, -74.203737 40.624227, -74.201864 40.618557, -74.203128 40.614109, -74.203813 40.605961, -74.199408 40.600201, -74.19952 40.597539, -74.203688 40.592691, -74.210741 40.597032, -74.212032 40.598372, -74.21287 40.602035, -74.215101 40.604074, -74.220216 40.600555, -74.231748 40.598881, -74.234067 40.600345, -74.234767 40.601945, -74.236609 40.60272, -74.242593 40.599754, -74.25381 40.600989, -74.255822 40.602194, -74.258114 40.60225, -74.255608 40.607618, -74.278844 40.629365, -74.285491 40.635102, -74.290491 40.632903, -74.292494 40.630803, -74.296379 40.628711, -74.297134 40.62933, -74.293808 40.633387))',\n            11.53874114,\n            2.699232307,\n            'C_Medium_High',\n            7036,\n            0.801162911,\n            9631,\n            28.86601273,\n            389.7025478,\n            0.719862943,\n            0.995016094,\n            15.60384361,\n            11.23865061,\n            0.228\n          ],\n          [\n            7723,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.013788,40.249179],[-74.01764,40.249613],[-74.019302,40.250582],[-74.019689,40.253485],[-74.012618,40.253141],[-74.011201,40.257],[-74.003013,40.256831],[-74.002888,40.258313],[-74.000094,40.257407],[-74.0002,40.26345],[-73.992354,40.262405],[-73.986681,40.260211],[-73.992881,40.237266],[-74.008001,40.239851],[-74.008447,40.242576],[-74.009543,40.243782],[-74.009733,40.248425],[-74.013788,40.249179]]]}',\n            'POLYGON ((-74.013788 40.249179, -74.01764 40.249613, -74.019302 40.250582, -74.019689 40.253485, -74.012618 40.253141, -74.011201 40.257, -74.003013 40.256831, -74.002888 40.258313, -74.000094 40.257407, -74.0002 40.26345, -73.992354 40.262405, -73.986681 40.260211, -73.992881 40.237266, -74.008001 40.239851, -74.008447 40.242576, -74.009543 40.243782, -74.009733 40.248425, -74.013788 40.249179))',\n            1.726296628,\n            2.781885387,\n            'A_Low_Rural',\n            7723,\n            0.28340081,\n            247,\n            40.21138211,\n            761.4119818,\n            0.71659919,\n            0.995951417,\n            20.89796748,\n            16.39563236,\n            0.213\n          ],\n          [\n            7822,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.680775,41.141961],[-74.694975,41.131348],[-74.69885,41.135537],[-74.701359,41.136794],[-74.703406,41.13429],[-74.706486,41.126963],[-74.713138,41.120327],[-74.711489,41.118891],[-74.720408,41.112296],[-74.746949,41.119559],[-74.745694,41.120599],[-74.745157,41.124768],[-74.741896,41.12892],[-74.737953,41.129147],[-74.737676,41.128104],[-74.735155,41.126436],[-74.728554,41.128919],[-74.733121,41.130496],[-74.735764,41.13238],[-74.739,41.137338],[-74.733352,41.138875],[-74.731341,41.143627],[-74.721084,41.150262],[-74.716727,41.151405],[-74.715427,41.151356],[-74.712447,41.147925],[-74.705516,41.147476],[-74.704311,41.147987],[-74.703071,41.146962],[-74.694816,41.157602],[-74.690193,41.159813],[-74.684027,41.164539],[-74.681418,41.165103],[-74.67785,41.163684],[-74.674644,41.163581],[-74.67129,41.168764],[-74.667272,41.172585],[-74.660801,41.166128],[-74.66382,41.163773],[-74.663566,41.162153],[-74.66462,41.159589],[-74.663429,41.157321],[-74.664679,41.155926],[-74.670442,41.154203],[-74.668458,41.152808],[-74.668539,41.151161],[-74.680775,41.141961]]]}',\n            'POLYGON ((-74.680775 41.141961, -74.694975 41.131348, -74.69885 41.135537, -74.701359 41.136794, -74.703406 41.13429, -74.706486 41.126963, -74.713138 41.120327, -74.711489 41.118891, -74.720408 41.112296, -74.746949 41.119559, -74.745694 41.120599, -74.745157 41.124768, -74.741896 41.12892, -74.737953 41.129147, -74.737676 41.128104, -74.735155 41.126436, -74.728554 41.128919, -74.733121 41.130496, -74.735764 41.13238, -74.739 41.137338, -74.733352 41.138875, -74.731341 41.143627, -74.721084 41.150262, -74.716727 41.151405, -74.715427 41.151356, -74.712447 41.147925, -74.705516 41.147476, -74.704311 41.147987, -74.703071 41.146962, -74.694816 41.157602, -74.690193 41.159813, -74.684027 41.164539, -74.681418 41.165103, -74.67785 41.163684, -74.674644 41.163581, -74.67129 41.168764, -74.667272 41.172585, -74.660801 41.166128, -74.66382 41.163773, -74.663566 41.162153, -74.66462 41.159589, -74.663429 41.157321, -74.664679 41.155926, -74.670442 41.154203, -74.668458 41.152808, -74.668539 41.151161, -74.680775 41.141961))',\n            6.647012773,\n            2.433311133,\n            'A_Low_Rural',\n            7822,\n            0.273684211,\n            95,\n            17.43157895,\n            349.6450282,\n            0.768421053,\n            1,\n            16.78491228,\n            9.373910335,\n            0.118\n          ],\n          [\n            7054,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.39366,40.830496],[-74.405638,40.838928],[-74.422925,40.8447],[-74.451083,40.838346],[-74.463181,40.843465],[-74.467565,40.846525],[-74.465964,40.848547],[-74.464617,40.848757],[-74.46076,40.846768],[-74.454244,40.85264],[-74.452162,40.852242],[-74.449605,40.854737],[-74.45126,40.859271],[-74.449707,40.860541],[-74.451701,40.860499],[-74.461277,40.86634],[-74.46095,40.867844],[-74.449226,40.866302],[-74.443807,40.866626],[-74.447036,40.867209],[-74.439733,40.866511],[-74.43882,40.870782],[-74.440454,40.869142],[-74.445241,40.867982],[-74.450476,40.869008],[-74.450185,40.872275],[-74.44921,40.874111],[-74.451584,40.873513],[-74.451567,40.876515],[-74.447705,40.876866],[-74.448803,40.879174],[-74.450256,40.879959],[-74.433632,40.874255],[-74.433403,40.877506],[-74.43068,40.881706],[-74.425693,40.882707],[-74.424699,40.879771],[-74.425187,40.878315],[-74.419916,40.879254],[-74.420486,40.875395],[-74.419362,40.871206],[-74.414728,40.868737],[-74.410267,40.877077],[-74.405039,40.875898],[-74.404027,40.878969],[-74.4017,40.881459],[-74.396071,40.881961],[-74.392244,40.873568],[-74.39051,40.874087],[-74.390446,40.87614],[-74.384095,40.876199],[-74.383996,40.875199],[-74.380159,40.876783],[-74.376464,40.876885],[-74.376099,40.875662],[-74.375014,40.876373],[-74.372986,40.875671],[-74.371975,40.870267],[-74.36966,40.871168],[-74.368983,40.869851],[-74.369611,40.869488],[-74.365509,40.869788],[-74.365091,40.868735],[-74.366462,40.867373],[-74.366416,40.864756],[-74.36514,40.866106],[-74.362694,40.863887],[-74.358996,40.863608],[-74.354787,40.86599],[-74.35609,40.867062],[-74.358298,40.866561],[-74.361219,40.867003],[-74.361668,40.867421],[-74.357831,40.866738],[-74.355927,40.867299],[-74.353829,40.86556],[-74.352328,40.866215],[-74.349994,40.864479],[-74.349303,40.86285],[-74.348177,40.862628],[-74.34716,40.859279],[-74.347658,40.856795],[-74.346489,40.856411],[-74.345626,40.857625],[-74.344092,40.856842],[-74.340904,40.852353],[-74.341387,40.85158],[-74.344485,40.850715],[-74.341838,40.849039],[-74.341288,40.847784],[-74.341951,40.846224],[-74.35252,40.844081],[-74.354781,40.840973],[-74.360222,40.840871],[-74.367495,40.836199],[-74.371095,40.835699],[-74.379803,40.828479],[-74.380291,40.830042],[-74.381487,40.828478],[-74.380893,40.827515],[-74.383959,40.824991],[-74.388727,40.825989],[-74.39366,40.830496]]]}',\n            'POLYGON ((-74.39366 40.830496, -74.405638 40.838928, -74.422925 40.8447, -74.451083 40.838346, -74.463181 40.843465, -74.467565 40.846525, -74.465964 40.848547, -74.464617 40.848757, -74.46076 40.846768, -74.454244 40.85264, -74.452162 40.852242, -74.449605 40.854737, -74.45126 40.859271, -74.449707 40.860541, -74.451701 40.860499, -74.461277 40.86634, -74.46095 40.867844, -74.449226 40.866302, -74.443807 40.866626, -74.447036 40.867209, -74.439733 40.866511, -74.43882 40.870782, -74.440454 40.869142, -74.445241 40.867982, -74.450476 40.869008, -74.450185 40.872275, -74.44921 40.874111, -74.451584 40.873513, -74.451567 40.876515, -74.447705 40.876866, -74.448803 40.879174, -74.450256 40.879959, -74.433632 40.874255, -74.433403 40.877506, -74.43068 40.881706, -74.425693 40.882707, -74.424699 40.879771, -74.425187 40.878315, -74.419916 40.879254, -74.420486 40.875395, -74.419362 40.871206, -74.414728 40.868737, -74.410267 40.877077, -74.405039 40.875898, -74.404027 40.878969, -74.4017 40.881459, -74.396071 40.881961, -74.392244 40.873568, -74.39051 40.874087, -74.390446 40.87614, -74.384095 40.876199, -74.383996 40.875199, -74.380159 40.876783, -74.376464 40.876885, -74.376099 40.875662, -74.375014 40.876373, -74.372986 40.875671, -74.371975 40.870267, -74.36966 40.871168, -74.368983 40.869851, -74.369611 40.869488, -74.365509 40.869788, -74.365091 40.868735, -74.366462 40.867373, -74.366416 40.864756, -74.36514 40.866106, -74.362694 40.863887, -74.358996 40.863608, -74.354787 40.86599, -74.35609 40.867062, -74.358298 40.866561, -74.361219 40.867003, -74.361668 40.867421, -74.357831 40.866738, -74.355927 40.867299, -74.353829 40.86556, -74.352328 40.866215, -74.349994 40.864479, -74.349303 40.86285, -74.348177 40.862628, -74.34716 40.859279, -74.347658 40.856795, -74.346489 40.856411, -74.345626 40.857625, -74.344092 40.856842, -74.340904 40.852353, -74.341387 40.85158, -74.344485 40.850715, -74.341838 40.849039, -74.341288 40.847784, -74.341951 40.846224, -74.35252 40.844081, -74.354781 40.840973, -74.360222 40.840871, -74.367495 40.836199, -74.371095 40.835699, -74.379803 40.828479, -74.380291 40.830042, -74.381487 40.828478, -74.380893 40.827515, -74.383959 40.824991, -74.388727 40.825989, -74.39366 40.830496))',\n            14.37823635,\n            2.515879321,\n            'B_Low_Suburban',\n            7054,\n            0.5693362,\n            7442,\n            27.18441628,\n            491.6000821,\n            0.709486697,\n            0.99677506,\n            17.45079087,\n            12.67679148,\n            0.256\n          ],\n          [\n            7832,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-75.097586,40.843042],[-75.095784,40.847082],[-75.090962,40.849187],[-75.076684,40.849875],[-75.07083,40.847392],[-75.067655,40.847329],[-75.064328,40.848338],[-75.063343,40.851499],[-75.060491,40.85302],[-75.059239,40.856102],[-75.053294,40.8599],[-75.051692,40.863363],[-75.050839,40.868067],[-75.053664,40.87366],[-75.058655,40.877654],[-75.062073,40.882188],[-75.060998,40.882237],[-75.060556,40.883385],[-75.059124,40.884072],[-75.060651,40.889189],[-75.064166,40.893064],[-75.063612,40.895385],[-75.066827,40.895434],[-75.067646,40.896657],[-75.067452,40.899412],[-75.071768,40.898989],[-75.072097,40.898253],[-75.073515,40.900408],[-75.07391,40.899656],[-75.075231,40.899947],[-75.07609,40.907039],[-75.07929,40.913886],[-75.095528,40.924148],[-75.104809,40.935247],[-75.106148,40.93967],[-75.111689,40.948127],[-75.117767,40.953013],[-75.119116,40.958161],[-75.118076,40.959633],[-75.118346,40.963676],[-75.120435,40.968302],[-75.122603,40.970152],[-75.130166,40.968961],[-75.132622,40.970005],[-75.134701,40.972038],[-75.135531,40.973803],[-75.135521,40.976865],[-75.132522,40.981235],[-75.131096,40.990464],[-75.127196,40.993954],[-75.110595,41.002174],[-75.109113,41.004102],[-75.095556,41.008874],[-75.088596,41.015024],[-75.081101,41.016838],[-75.074999,41.01713],[-75.069277,41.019348],[-75.068369,41.018018],[-75.037857,41.030485],[-75.029696,41.035357],[-75.027882,41.037279],[-75.024165,41.038536],[-75.017392,41.042515],[-75.015185,41.04423],[-75.009346,41.051893],[-75.003538,41.057084],[-75.000477,41.058017],[-74.998034,41.057699],[-74.995569,41.058848],[-74.992703,41.058564],[-74.992174,41.05969],[-74.984424,41.064889],[-74.984063,41.062832],[-74.980707,41.061781],[-74.964688,41.063446],[-74.963232,41.060643],[-74.964228,41.057935],[-74.961406,41.055613],[-74.96458,41.051797],[-74.99578,41.033212],[-75.002831,41.030399],[-75.003773,41.031045],[-75.004225,41.032144],[-75.002834,41.033539],[-74.998731,41.035666],[-74.998954,41.038437],[-74.998147,41.038507],[-74.998255,41.037117],[-74.997435,41.037002],[-74.995356,41.040166],[-74.991659,41.042442],[-74.994705,41.041671],[-75.004298,41.035416],[-75.005265,41.031919],[-75.003411,41.030082],[-75.016649,41.022886],[-75.021761,41.020728],[-75.036997,41.017017],[-75.04378,41.014322],[-75.03456,41.008186],[-75.034725,41.004652],[-75.033729,41.002569],[-75.034213,41.001403],[-75.032281,40.999595],[-75.032341,40.996951],[-75.02898,40.99561],[-75.011902,40.998459],[-75.008874,40.99009],[-75.010378,40.985846],[-75.010075,40.984303],[-75.002953,40.981862],[-75.005732,40.980256],[-75.021928,40.975764],[-75.02123,40.970357],[-75.024322,40.969825],[-75.021013,40.968997],[-75.020256,40.963611],[-75.018195,40.964426],[-75.019502,40.961433],[-75.018391,40.959758],[-75.038299,40.952908],[-75.03083,40.946719],[-75.029307,40.946706],[-75.020847,40.940258],[-75.019403,40.941591],[-75.019535,40.939241],[-75.021033,40.939612],[-75.022159,40.938123],[-75.015041,40.928458],[-75.027891,40.920693],[-75.02861,40.915329],[-75.031276,40.912284],[-75.024568,40.908851],[-75.0223,40.905808],[-75.03156,40.896317],[-75.030145,40.89056],[-75.024001,40.891451],[-75.022183,40.892446],[-75.020636,40.891426],[-75.017441,40.891496],[-75.01407,40.890293],[-75.008894,40.885907],[-75.010391,40.884111],[-75.014797,40.882787],[-75.019161,40.881981],[-75.024044,40.883433],[-75.025249,40.882262],[-75.02744,40.88172],[-75.021561,40.875098],[-75.014017,40.868236],[-75.016147,40.867869],[-75.016559,40.868533],[-75.020503,40.866673],[-75.021161,40.867805],[-75.022549,40.86763],[-75.023738,40.863747],[-75.025591,40.860958],[-75.02513,40.858498],[-75.024052,40.857718],[-75.028967,40.856352],[-75.030658,40.857634],[-75.032149,40.85736],[-75.029716,40.863273],[-75.031074,40.869309],[-75.035994,40.868633],[-75.051176,40.860567],[-75.049486,40.860618],[-75.055534,40.855224],[-75.057455,40.852435],[-75.057422,40.848045],[-75.062178,40.847418],[-75.061305,40.843848],[-75.064167,40.842773],[-75.06489,40.843711],[-75.068656,40.844534],[-75.070267,40.842345],[-75.070187,40.837954],[-75.07707,40.840947],[-75.088876,40.832533],[-75.096301,40.83839],[-75.097572,40.840967],[-75.097586,40.843042]]]}',\n            'POLYGON ((-75.097586 40.843042, -75.095784 40.847082, -75.090962 40.849187, -75.076684 40.849875, -75.07083 40.847392, -75.067655 40.847329, -75.064328 40.848338, -75.063343 40.851499, -75.060491 40.85302, -75.059239 40.856102, -75.053294 40.8599, -75.051692 40.863363, -75.050839 40.868067, -75.053664 40.87366, -75.058655 40.877654, -75.062073 40.882188, -75.060998 40.882237, -75.060556 40.883385, -75.059124 40.884072, -75.060651 40.889189, -75.064166 40.893064, -75.063612 40.895385, -75.066827 40.895434, -75.067646 40.896657, -75.067452 40.899412, -75.071768 40.898989, -75.072097 40.898253, -75.073515 40.900408, -75.07391 40.899656, -75.075231 40.899947, -75.07609 40.907039, -75.07929 40.913886, -75.095528 40.924148, -75.104809 40.935247, -75.106148 40.93967, -75.111689 40.948127, -75.117767 40.953013, -75.119116 40.958161, -75.118076 40.959633, -75.118346 40.963676, -75.120435 40.968302, -75.122603 40.970152, -75.130166 40.968961, -75.132622 40.970005, -75.134701 40.972038, -75.135531 40.973803, -75.135521 40.976865, -75.132522 40.981235, -75.131096 40.990464, -75.127196 40.993954, -75.110595 41.002174, -75.109113 41.004102, -75.095556 41.008874, -75.088596 41.015024, -75.081101 41.016838, -75.074999 41.01713, -75.069277 41.019348, -75.068369 41.018018, -75.037857 41.030485, -75.029696 41.035357, -75.027882 41.037279, -75.024165 41.038536, -75.017392 41.042515, -75.015185 41.04423, -75.009346 41.051893, -75.003538 41.057084, -75.000477 41.058017, -74.998034 41.057699, -74.995569 41.058848, -74.992703 41.058564, -74.992174 41.05969, -74.984424 41.064889, -74.984063 41.062832, -74.980707 41.061781, -74.964688 41.063446, -74.963232 41.060643, -74.964228 41.057935, -74.961406 41.055613, -74.96458 41.051797, -74.99578 41.033212, -75.002831 41.030399, -75.003773 41.031045, -75.004225 41.032144, -75.002834 41.033539, -74.998731 41.035666, -74.998954 41.038437, -74.998147 41.038507, -74.998255 41.037117, -74.997435 41.037002, -74.995356 41.040166, -74.991659 41.042442, -74.994705 41.041671, -75.004298 41.035416, -75.005265 41.031919, -75.003411 41.030082, -75.016649 41.022886, -75.021761 41.020728, -75.036997 41.017017, -75.04378 41.014322, -75.03456 41.008186, -75.034725 41.004652, -75.033729 41.002569, -75.034213 41.001403, -75.032281 40.999595, -75.032341 40.996951, -75.02898 40.99561, -75.011902 40.998459, -75.008874 40.99009, -75.010378 40.985846, -75.010075 40.984303, -75.002953 40.981862, -75.005732 40.980256, -75.021928 40.975764, -75.02123 40.970357, -75.024322 40.969825, -75.021013 40.968997, -75.020256 40.963611, -75.018195 40.964426, -75.019502 40.961433, -75.018391 40.959758, -75.038299 40.952908, -75.03083 40.946719, -75.029307 40.946706, -75.020847 40.940258, -75.019403 40.941591, -75.019535 40.939241, -75.021033 40.939612, -75.022159 40.938123, -75.015041 40.928458, -75.027891 40.920693, -75.02861 40.915329, -75.031276 40.912284, -75.024568 40.908851, -75.0223 40.905808, -75.03156 40.896317, -75.030145 40.89056, -75.024001 40.891451, -75.022183 40.892446, -75.020636 40.891426, -75.017441 40.891496, -75.01407 40.890293, -75.008894 40.885907, -75.010391 40.884111, -75.014797 40.882787, -75.019161 40.881981, -75.024044 40.883433, -75.025249 40.882262, -75.02744 40.88172, -75.021561 40.875098, -75.014017 40.868236, -75.016147 40.867869, -75.016559 40.868533, -75.020503 40.866673, -75.021161 40.867805, -75.022549 40.86763, -75.023738 40.863747, -75.025591 40.860958, -75.02513 40.858498, -75.024052 40.857718, -75.028967 40.856352, -75.030658 40.857634, -75.032149 40.85736, -75.029716 40.863273, -75.031074 40.869309, -75.035994 40.868633, -75.051176 40.860567, -75.049486 40.860618, -75.055534 40.855224, -75.057455 40.852435, -75.057422 40.848045, -75.062178 40.847418, -75.061305 40.843848, -75.064167 40.842773, -75.06489 40.843711, -75.068656 40.844534, -75.070267 40.842345, -75.070187 40.837954, -75.07707 40.840947, -75.088876 40.832533, -75.096301 40.83839, -75.097572 40.840967, -75.097586 40.843042))',\n            48.08259073,\n            2.222012926,\n            'A_Low_Rural',\n            7832,\n            0.291390728,\n            302,\n            13.33670034,\n            237.9317288,\n            0.801324503,\n            0.983443709,\n            15.91447811,\n            6.727790706,\n            0.081\n          ],\n          [\n            7924,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.624966,40.729228],[-74.623454,40.730678],[-74.622391,40.735039],[-74.620469,40.736001],[-74.627104,40.737727],[-74.628747,40.736844],[-74.630137,40.735166],[-74.631305,40.734548],[-74.632923,40.732801],[-74.633441,40.732418],[-74.633871,40.732237],[-74.632255,40.733644],[-74.630508,40.736973],[-74.631292,40.738187],[-74.629319,40.739683],[-74.63127,40.741111],[-74.555922,40.75831],[-74.555203,40.756999],[-74.553197,40.756439],[-74.551914,40.754863],[-74.550924,40.751073],[-74.552886,40.749578],[-74.548756,40.746131],[-74.549383,40.745092],[-74.548999,40.740994],[-74.550988,40.741647],[-74.551958,40.739985],[-74.55856,40.736104],[-74.556731,40.73478],[-74.560814,40.731351],[-74.563545,40.730267],[-74.564013,40.728933],[-74.552547,40.722803],[-74.555477,40.721368],[-74.5533,40.719188],[-74.556246,40.717896],[-74.567761,40.706455],[-74.607957,40.694414],[-74.609197,40.696081],[-74.611319,40.695338],[-74.61599,40.695721],[-74.618622,40.695149],[-74.618226,40.704919],[-74.620821,40.710878],[-74.620235,40.712269],[-74.615067,40.715173],[-74.612185,40.719403],[-74.612003,40.72373],[-74.61545,40.723053],[-74.620932,40.716648],[-74.623654,40.715129],[-74.625684,40.715004],[-74.634205,40.722291],[-74.631262,40.72482],[-74.626871,40.725334],[-74.626784,40.727547],[-74.625149,40.72802],[-74.625639,40.728948],[-74.624966,40.729228]]]}',\n            'POLYGON ((-74.624966 40.729228, -74.623454 40.730678, -74.622391 40.735039, -74.620469 40.736001, -74.627104 40.737727, -74.628747 40.736844, -74.630137 40.735166, -74.631305 40.734548, -74.632923 40.732801, -74.633441 40.732418, -74.633871 40.732237, -74.632255 40.733644, -74.630508 40.736973, -74.631292 40.738187, -74.629319 40.739683, -74.63127 40.741111, -74.555922 40.75831, -74.555203 40.756999, -74.553197 40.756439, -74.551914 40.754863, -74.550924 40.751073, -74.552886 40.749578, -74.548756 40.746131, -74.549383 40.745092, -74.548999 40.740994, -74.550988 40.741647, -74.551958 40.739985, -74.55856 40.736104, -74.556731 40.73478, -74.560814 40.731351, -74.563545 40.730267, -74.564013 40.728933, -74.552547 40.722803, -74.555477 40.721368, -74.5533 40.719188, -74.556246 40.717896, -74.567761 40.706455, -74.607957 40.694414, -74.609197 40.696081, -74.611319 40.695338, -74.61599 40.695721, -74.618622 40.695149, -74.618226 40.704919, -74.620821 40.710878, -74.620235 40.712269, -74.615067 40.715173, -74.612185 40.719403, -74.612003 40.72373, -74.61545 40.723053, -74.620932 40.716648, -74.623654 40.715129, -74.625684 40.715004, -74.634205 40.722291, -74.631262 40.72482, -74.626871 40.725334, -74.626784 40.727547, -74.625149 40.72802, -74.625639 40.728948, -74.624966 40.729228))',\n            12.13371074,\n            2.402380687,\n            'A_Low_Rural',\n            7924,\n            0.384324324,\n            1850,\n            33.00813449,\n            677.5939771,\n            0.667027027,\n            0.996756757,\n            19.17525307,\n            15.90160445,\n            0.242\n          ],\n          [\n            7928,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.409178,40.741059],[-74.405471,40.73989],[-74.402552,40.742258],[-74.402172,40.741487],[-74.391635,40.75198],[-74.389873,40.751394],[-74.383649,40.754173],[-74.38351,40.755403],[-74.380255,40.755386],[-74.381513,40.754696],[-74.373581,40.75015],[-74.370371,40.752139],[-74.363072,40.750403],[-74.367572,40.748824],[-74.364765,40.746788],[-74.366562,40.743889],[-74.371979,40.742359],[-74.37256,40.741587],[-74.370829,40.735753],[-74.371246,40.734627],[-74.374669,40.733545],[-74.377932,40.734319],[-74.379732,40.73396],[-74.380295,40.7333],[-74.379853,40.732492],[-74.377503,40.730032],[-74.384382,40.724385],[-74.388232,40.726165],[-74.39069,40.725499],[-74.390769,40.718294],[-74.39509,40.717273],[-74.402254,40.718001],[-74.404574,40.715245],[-74.40735,40.713461],[-74.408466,40.710106],[-74.410057,40.709517],[-74.412495,40.7103],[-74.413818,40.707248],[-74.4154,40.705781],[-74.426897,40.701711],[-74.430005,40.697838],[-74.429578,40.697004],[-74.430781,40.693453],[-74.434551,40.693575],[-74.437847,40.6919],[-74.43797,40.690247],[-74.444333,40.687969],[-74.449736,40.718831],[-74.447841,40.718904],[-74.437223,40.723493],[-74.426842,40.729088],[-74.424382,40.734726],[-74.426048,40.733344],[-74.427376,40.733482],[-74.426785,40.735839],[-74.429199,40.735878],[-74.430449,40.734831],[-74.430765,40.735285],[-74.428298,40.736522],[-74.430213,40.7377],[-74.43032,40.736951],[-74.432798,40.736704],[-74.434311,40.735493],[-74.437467,40.735121],[-74.437348,40.73587],[-74.439214,40.73673],[-74.44084,40.73642],[-74.439595,40.736006],[-74.440803,40.734753],[-74.441744,40.735486],[-74.441043,40.736868],[-74.442891,40.735942],[-74.444308,40.738929],[-74.438047,40.741641],[-74.432156,40.746447],[-74.434702,40.747732],[-74.438198,40.752308],[-74.436616,40.752849],[-74.435314,40.752306],[-74.436024,40.751602],[-74.433267,40.750069],[-74.432271,40.751089],[-74.428827,40.749474],[-74.430019,40.746001],[-74.41515,40.742751],[-74.409916,40.740501],[-74.409178,40.741059]]]}',\n            'POLYGON ((-74.409178 40.741059, -74.405471 40.73989, -74.402552 40.742258, -74.402172 40.741487, -74.391635 40.75198, -74.389873 40.751394, -74.383649 40.754173, -74.38351 40.755403, -74.380255 40.755386, -74.381513 40.754696, -74.373581 40.75015, -74.370371 40.752139, -74.363072 40.750403, -74.367572 40.748824, -74.364765 40.746788, -74.366562 40.743889, -74.371979 40.742359, -74.37256 40.741587, -74.370829 40.735753, -74.371246 40.734627, -74.374669 40.733545, -74.377932 40.734319, -74.379732 40.73396, -74.380295 40.7333, -74.379853 40.732492, -74.377503 40.730032, -74.384382 40.724385, -74.388232 40.726165, -74.39069 40.725499, -74.390769 40.718294, -74.39509 40.717273, -74.402254 40.718001, -74.404574 40.715245, -74.40735 40.713461, -74.408466 40.710106, -74.410057 40.709517, -74.412495 40.7103, -74.413818 40.707248, -74.4154 40.705781, -74.426897 40.701711, -74.430005 40.697838, -74.429578 40.697004, -74.430781 40.693453, -74.434551 40.693575, -74.437847 40.6919, -74.43797 40.690247, -74.444333 40.687969, -74.449736 40.718831, -74.447841 40.718904, -74.437223 40.723493, -74.426842 40.729088, -74.424382 40.734726, -74.426048 40.733344, -74.427376 40.733482, -74.426785 40.735839, -74.429199 40.735878, -74.430449 40.734831, -74.430765 40.735285, -74.428298 40.736522, -74.430213 40.7377, -74.43032 40.736951, -74.432798 40.736704, -74.434311 40.735493, -74.437467 40.735121, -74.437348 40.73587, -74.439214 40.73673, -74.44084 40.73642, -74.439595 40.736006, -74.440803 40.734753, -74.441744 40.735486, -74.441043 40.736868, -74.442891 40.735942, -74.444308 40.738929, -74.438047 40.741641, -74.432156 40.746447, -74.434702 40.747732, -74.438198 40.752308, -74.436616 40.752849, -74.435314 40.752306, -74.436024 40.751602, -74.433267 40.750069, -74.432271 40.751089, -74.428827 40.749474, -74.430019 40.746001, -74.41515 40.742751, -74.409916 40.740501, -74.409178 40.741059))',\n            8.953905337,\n            2.250793062,\n            'B_Low_Suburban',\n            7928,\n            0.539032204,\n            5931,\n            39.01961447,\n            858.5168842,\n            0.613050076,\n            0.997133704,\n            20.95072145,\n            18.44006178,\n            0.31\n          ],\n          [\n            7979,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.746315,40.699842],[-74.738518,40.708428],[-74.73803,40.711868],[-74.734191,40.71672],[-74.729987,40.717241],[-74.732448,40.720062],[-74.724418,40.719455],[-74.720152,40.720857],[-74.721834,40.718984],[-74.720881,40.716657],[-74.71557,40.714933],[-74.715093,40.713903],[-74.717147,40.714164],[-74.716015,40.710283],[-74.717798,40.709288],[-74.714601,40.703286],[-74.714347,40.699368],[-74.721624,40.690735],[-74.722079,40.688939],[-74.72065,40.687797],[-74.72303,40.68265],[-74.725555,40.683786],[-74.729034,40.68356],[-74.730184,40.682581],[-74.732786,40.683785],[-74.730782,40.686112],[-74.73592,40.688406],[-74.732475,40.692508],[-74.733375,40.693151],[-74.734169,40.699729],[-74.744802,40.697551],[-74.746315,40.699842]]]}',\n            'POLYGON ((-74.746315 40.699842, -74.738518 40.708428, -74.73803 40.711868, -74.734191 40.71672, -74.729987 40.717241, -74.732448 40.720062, -74.724418 40.719455, -74.720152 40.720857, -74.721834 40.718984, -74.720881 40.716657, -74.71557 40.714933, -74.715093 40.713903, -74.717147 40.714164, -74.716015 40.710283, -74.717798 40.709288, -74.714601 40.703286, -74.714347 40.699368, -74.721624 40.690735, -74.722079 40.688939, -74.72065 40.687797, -74.72303 40.68265, -74.725555 40.683786, -74.729034 40.68356, -74.730184 40.682581, -74.732786 40.683785, -74.730782 40.686112, -74.73592 40.688406, -74.732475 40.692508, -74.733375 40.693151, -74.734169 40.699729, -74.744802 40.697551, -74.746315 40.699842))',\n            2.439857168,\n            2.316265052,\n            'A_Low_Rural',\n            7979,\n            0.139534884,\n            215,\n            22.95774648,\n            284.0477961,\n            0.693023256,\n            0.990697674,\n            14.85117371,\n            8.606828708,\n            0.354\n          ],\n          [\n            7838,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.96258,40.908359],[-74.959171,40.909348],[-74.958166,40.910924],[-74.955046,40.911941],[-74.952035,40.914182],[-74.947947,40.912938],[-74.945624,40.911373],[-74.945686,40.909849],[-74.944298,40.909232],[-74.940447,40.911676],[-74.93977,40.916014],[-74.93583,40.915965],[-74.93084,40.917595],[-74.927867,40.906805],[-74.887969,40.933194],[-74.887988,40.931636],[-74.886846,40.929954],[-74.882716,40.930239],[-74.874369,40.932296],[-74.873234,40.934085],[-74.86866,40.936267],[-74.866899,40.933617],[-74.859938,40.93051],[-74.852375,40.930101],[-74.848303,40.933399],[-74.847051,40.933475],[-74.844269,40.930927],[-74.844978,40.927355],[-74.840887,40.923065],[-74.840416,40.921211],[-74.84412,40.919599],[-74.842955,40.919474],[-74.844491,40.91793],[-74.844531,40.915532],[-74.859914,40.909303],[-74.860956,40.904959],[-74.862287,40.903064],[-74.858839,40.900314],[-74.858959,40.899168],[-74.848263,40.892969],[-74.851169,40.889839],[-74.85923,40.885743],[-74.859366,40.887143],[-74.860511,40.887838],[-74.863545,40.883912],[-74.867125,40.884417],[-74.872203,40.882492],[-74.872768,40.883398],[-74.876052,40.884353],[-74.882058,40.883988],[-74.882891,40.882731],[-74.887871,40.879998],[-74.889572,40.87633],[-74.894028,40.872274],[-74.89991,40.869372],[-74.903521,40.865869],[-74.905344,40.866523],[-74.903426,40.864607],[-74.903128,40.86326],[-74.900129,40.862348],[-74.896917,40.858397],[-74.894792,40.857851],[-74.894754,40.856634],[-74.893744,40.8561],[-74.894932,40.856353],[-74.895533,40.851298],[-74.907884,40.855752],[-74.915047,40.852114],[-74.924079,40.851947],[-74.929062,40.848559],[-74.934249,40.85086],[-74.933294,40.852222],[-74.935853,40.852373],[-74.941042,40.846725],[-74.94384,40.846406],[-74.950587,40.842517],[-74.951581,40.839414],[-74.956373,40.834365],[-74.971647,40.842927],[-74.971381,40.843883],[-74.968743,40.844635],[-74.960144,40.855778],[-74.963608,40.855651],[-74.963127,40.858348],[-74.964627,40.85919],[-74.96689,40.85904],[-74.968542,40.860945],[-74.97156,40.860844],[-74.968929,40.864416],[-74.969525,40.86504],[-74.971744,40.861518],[-74.972369,40.86245],[-74.9725,40.861514],[-74.973998,40.861535],[-74.974787,40.860622],[-74.978735,40.87158],[-74.979484,40.87194],[-74.97877,40.873364],[-74.979931,40.874228],[-74.976368,40.876708],[-74.982155,40.879127],[-74.980311,40.880401],[-74.980521,40.881626],[-74.976513,40.883126],[-74.978397,40.88719],[-74.976715,40.890538],[-74.9757,40.883461],[-74.977469,40.879267],[-74.97593,40.878446],[-74.976152,40.879675],[-74.974987,40.881253],[-74.969259,40.884146],[-74.963281,40.885689],[-74.957982,40.890776],[-74.957476,40.893947],[-74.961516,40.894095],[-74.95829,40.900182],[-74.962611,40.906121],[-74.959793,40.907294],[-74.96258,40.908359]]]}',\n            'POLYGON ((-74.96258 40.908359, -74.959171 40.909348, -74.958166 40.910924, -74.955046 40.911941, -74.952035 40.914182, -74.947947 40.912938, -74.945624 40.911373, -74.945686 40.909849, -74.944298 40.909232, -74.940447 40.911676, -74.93977 40.916014, -74.93583 40.915965, -74.93084 40.917595, -74.927867 40.906805, -74.887969 40.933194, -74.887988 40.931636, -74.886846 40.929954, -74.882716 40.930239, -74.874369 40.932296, -74.873234 40.934085, -74.86866 40.936267, -74.866899 40.933617, -74.859938 40.93051, -74.852375 40.930101, -74.848303 40.933399, -74.847051 40.933475, -74.844269 40.930927, -74.844978 40.927355, -74.840887 40.923065, -74.840416 40.921211, -74.84412 40.919599, -74.842955 40.919474, -74.844491 40.91793, -74.844531 40.915532, -74.859914 40.909303, -74.860956 40.904959, -74.862287 40.903064, -74.858839 40.900314, -74.858959 40.899168, -74.848263 40.892969, -74.851169 40.889839, -74.85923 40.885743, -74.859366 40.887143, -74.860511 40.887838, -74.863545 40.883912, -74.867125 40.884417, -74.872203 40.882492, -74.872768 40.883398, -74.876052 40.884353, -74.882058 40.883988, -74.882891 40.882731, -74.887871 40.879998, -74.889572 40.87633, -74.894028 40.872274, -74.89991 40.869372, -74.903521 40.865869, -74.905344 40.866523, -74.903426 40.864607, -74.903128 40.86326, -74.900129 40.862348, -74.896917 40.858397, -74.894792 40.857851, -74.894754 40.856634, -74.893744 40.8561, -74.894932 40.856353, -74.895533 40.851298, -74.907884 40.855752, -74.915047 40.852114, -74.924079 40.851947, -74.929062 40.848559, -74.934249 40.85086, -74.933294 40.852222, -74.935853 40.852373, -74.941042 40.846725, -74.94384 40.846406, -74.950587 40.842517, -74.951581 40.839414, -74.956373 40.834365, -74.971647 40.842927, -74.971381 40.843883, -74.968743 40.844635, -74.960144 40.855778, -74.963608 40.855651, -74.963127 40.858348, -74.964627 40.85919, -74.96689 40.85904, -74.968542 40.860945, -74.97156 40.860844, -74.968929 40.864416, -74.969525 40.86504, -74.971744 40.861518, -74.972369 40.86245, -74.9725 40.861514, -74.973998 40.861535, -74.974787 40.860622, -74.978735 40.87158, -74.979484 40.87194, -74.97877 40.873364, -74.979931 40.874228, -74.976368 40.876708, -74.982155 40.879127, -74.980311 40.880401, -74.980521 40.881626, -74.976513 40.883126, -74.978397 40.88719, -74.976715 40.890538, -74.9757 40.883461, -74.977469 40.879267, -74.97593 40.878446, -74.976152 40.879675, -74.974987 40.881253, -74.969259 40.884146, -74.963281 40.885689, -74.957982 40.890776, -74.957476 40.893947, -74.961516 40.894095, -74.95829 40.900182, -74.962611 40.906121, -74.959793 40.907294, -74.96258 40.908359))',\n            25.10592549,\n            2.007636482,\n            'A_Low_Rural',\n            7838,\n            0.346875,\n            320,\n            11.98119122,\n            211.9244058,\n            0.771875,\n            0.996875,\n            15.3553814,\n            6.210590289,\n            0.082\n          ],\n          [\n            7044,\n            '{\"type\":\"Polygon\",\"coordinates\":[[[-74.261032,40.832085],[-74.259357,40.836363],[-74.257107,40.838124],[-74.256523,40.841781],[-74.251892,40.849829],[-74.2334,40.844389],[-74.237214,40.836034],[-74.227445,40.83331],[-74.227147,40.836396],[-74.22099,40.834799],[-74.225755,40.827381],[-74.224352,40.827071],[-74.227054,40.824221],[-74.227725,40.824867],[-74.229113,40.822199],[-74.229079,40.820019],[-74.232573,40.814863],[-74.250795,40.824461],[-74.264305,40.825431],[-74.261032,40.832085]]]}',\n            'POLYGON ((-74.261032 40.832085, -74.259357 40.836363, -74.257107 40.838124, -74.256523 40.841781, -74.251892 40.849829, -74.2334 40.844389, -74.237214 40.836034, -74.227445 40.83331, -74.227147 40.836396, -74.22099 40.834799, -74.225755 40.827381, -74.224352 40.827071, -74.227054 40.824221, -74.227725 40.824867, -74.229113 40.822199, -74.229079 40.820019, -74.232573 40.814863, -74.250795 40.824461, -74.264305 40.825431, -74.261032 40.832085))',\n            2.791550146,\n            2.282754753,\n            'C_Medium_High',\n            7044,\n            0.652425579,\n            3628,\n            31.61574586,\n            596.5287053,\n            0.677508269,\n            0.997794928,\n            19.71404236,\n            13.61658418,\n            0.263\n          ]\n        ],\n        fields: [\n          {\n            name: 'a_zip',\n            type: 'integer'\n          },\n          {\n            name: 'simplified_shape_v2',\n            type: 'geojson'\n          },\n          {\n            name: 'simplified_shape',\n            type: 'geojson'\n          },\n          {\n            name: 'zip_area',\n            type: 'real'\n          },\n          {\n            name: 'avg_number',\n            type: 'real'\n          },\n          {\n            name: 'str_type',\n            type: 'string'\n          },\n          {\n            name: 'int_type',\n            type: 'integer'\n          },\n          {\n            name: 'real_type',\n            type: 'real'\n          },\n          {\n            name: 'c_m_r',\n            type: 'integer'\n          },\n          {\n            name: 'c_m_t',\n            type: 'real'\n          },\n          {\n            name: 'c_a_v',\n            type: 'real'\n          },\n          {\n            name: 'c_ch',\n            type: 'real'\n          },\n          {\n            name: 'c_ta',\n            type: 'real'\n          },\n          {\n            name: 'c_k_a',\n            type: 'real'\n          },\n          {\n            name: 'c_ltv',\n            type: 'real'\n          },\n          {\n            name: 'b_r_p',\n            type: 'real'\n          }\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: 'a5ybmwl2d',\n            id: '9ca0l7p2a',\n            name: 'zip_area',\n            type: 'range',\n            value: [0, 21.8],\n            enlarged: false\n          }\n        ],\n        layers: [\n          {\n            id: '8zr03we',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'extruded polygon',\n              color: [181, 18, 65],\n              columns: {\n                geojson: 'simplified_shape_v2'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 2,\n                colorRange: {\n                  name: 'ColorBrewer YlGnBu-9',\n                  type: 'sequential',\n                  category: 'ColorBrewer',\n                  colors: [\n                    '#081d58',\n                    '#253494',\n                    '#225ea8',\n                    '#1d91c0',\n                    '#41b6c4',\n                    '#7fcdbb',\n                    '#c7e9b4',\n                    '#edf8b1',\n                    '#ffffd9'\n                  ],\n                  reversed: true\n                },\n                radius: 10,\n                sizeRange: [0, 10],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 18,\n                stroked: true,\n                filled: true,\n                enable3d: true,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: {\n                name: 'c_m_r',\n                type: 'integer'\n              },\n              colorScale: 'quantize',\n              sizeField: null,\n              sizeScale: 'linear',\n              heightField: {\n                name: 'c_a_v',\n                type: 'real'\n              },\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          },\n          {\n            id: 'u7apppt',\n            type: 'geojson',\n            config: {\n              dataId: 'a5ybmwl2d',\n              label: 'stroke by pop',\n              color: [221, 178, 124],\n              columns: {\n                geojson: 'simplified_shape'\n              },\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 7.6,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radius: 10,\n                sizeRange: [18.9, 47.6],\n                radiusRange: [0, 50],\n                heightRange: [0, 500],\n                elevationScale: 5,\n                'hi-precision': false,\n                stroked: true,\n                filled: false,\n                enable3d: false,\n                wireframe: false\n              }\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: {\n                name: 'c_ta',\n                type: 'real'\n              },\n              sizeScale: 'linear',\n              heightField: null,\n              heightScale: 'linear',\n              radiusField: null,\n              radiusScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {},\n            enabled: false\n          },\n          brush: {\n            size: 1,\n            enabled: false\n          },\n          coordinate: {\n            enabled: true\n          }\n        },\n        layerBlending: 'normal'\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          places: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true\n        },\n        buildingLayer: {\n          isVisible: false,\n          color: [209, 206, 199],\n          opacity: 0.7\n        }\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.75043,\n        longitude: -122.34679,\n        pitch: 0,\n        zoom: 9\n      },\n      queryStatus: {\n        pastQueries: [\n          {\n            id: 'a5ybmwl2d',\n            label: 'geojson_as_string_small.csv',\n            queryType: 'file',\n            queryOption: 'csv',\n            params: {\n              file: null\n            },\n            size: 67502\n          }\n        ]\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Sun Nov 17 2019 18:37:59 GMT-0800 (Pacific Standard Time)'\n  }\n};\n\nexport const parsedFields = [\n  {name: 'a_zip', type: 'integer', format: '', analyzerType: 'INT'},\n  {\n    name: 'simplified_shape_v2',\n    type: 'geojson',\n    format: '',\n    analyzerType: 'PAIR_GEOMETRY_FROM_STRING'\n  },\n  {name: 'simplified_shape', type: 'geojson', format: '', analyzerType: 'GEOMETRY_FROM_STRING'},\n  {name: 'zip_area', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'avg_number', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'str_type', type: 'string', format: '', analyzerType: 'STRING'},\n  {name: 'int_type', type: 'integer', format: '', analyzerType: 'INT'},\n  {name: 'real_type', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_m_r', type: 'integer', format: '', analyzerType: 'INT'},\n  {name: 'c_m_t', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_a_v', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ch', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ta', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_k_a', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'c_ltv', type: 'real', format: '', analyzerType: 'FLOAT'},\n  {name: 'b_r_p', type: 'real', format: '', analyzerType: 'FLOAT'}\n];\n"
  },
  {
    "path": "test/fixtures/synced-filter-with-trip-layer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {processKeplerglJSON} from '@kepler.gl/processors';\nimport CloneDeep from 'lodash/cloneDeep';\nimport {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {InitialState, applyActions} from '../helpers/mock-state';\n\nexport const syncedFilterWithTripLayerMap = {\n  datasets: [\n    {\n      version: 'v1',\n      data: {\n        id: 'points-dataset',\n        label: 'few-points.csv',\n        color: [143, 47, 191],\n        allData: [\n          [\n            '2019/07/26 21:32:28.23',\n            34.5585,\n            -117.13116,\n            5.358,\n            3.67,\n            'Md',\n            13,\n            255,\n            65,\n            0.05,\n            'NCSN',\n            330724\n          ],\n          [\n            '2019/07/26 21:41:00.22',\n            35.99,\n            -120.12033,\n            12.058,\n            2.98,\n            'Md',\n            41,\n            127,\n            8,\n            0.08,\n            'NCSN',\n            330782\n          ],\n          [\n            '2019/07/26 22:04:22.72',\n            40.50533,\n            -123.50616,\n            10.046,\n            2.74,\n            'Md',\n            20,\n            49,\n            4,\n            0.14,\n            'NCSN',\n            330793\n          ],\n          [\n            '2019/07/26 22:05:35.14',\n            38.80083,\n            -122.8005,\n            0.577,\n            2.66,\n            'Md',\n            27,\n            58,\n            1,\n            0.04,\n            'NCSN',\n            331469\n          ],\n          [\n            '2019/07/26 23:04:44.55',\n            40.15017,\n            -123.81734,\n            19.269,\n            2.88,\n            'Md',\n            21,\n            160,\n            6,\n            0.1,\n            'NCSN',\n            331395\n          ],\n          [\n            '2019/07/26 23:30:11.18',\n            38.03183,\n            -118.73933,\n            1.896,\n            2.62,\n            'Md',\n            40,\n            206,\n            25,\n            0.07,\n            'NCSN',\n            331581\n          ],\n          [\n            '2019/07/26 23:37:25.34',\n            38.04317,\n            -118.72916,\n            6.125,\n            2.89,\n            'Md',\n            37,\n            209,\n            26,\n            0.07,\n            'NCSN',\n            331585\n          ],\n          [\n            '2019/07/26 23:38:51.29',\n            37.13117,\n            -121.529,\n            7.697,\n            2.7,\n            'Md',\n            80,\n            89,\n            2,\n            0.06,\n            'NCSN',\n            331584\n          ],\n          [\n            '2019/07/26 23:38:56.37',\n            36.55633,\n            -121.149,\n            7.151,\n            3.36,\n            'Md',\n            64,\n            33,\n            4,\n            0.06,\n            'NCSN',\n            331636\n          ],\n          [null, null, null, null, null, null, null, null, null, null, null, null]\n        ],\n        fields: [\n          {\n            name: 'DateTime',\n            type: 'timestamp',\n            format: 'YYYY/M/D HH:mm:ss.SSSS',\n            analyzerType: 'DATETIME'\n          },\n          {name: 'Latitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Longitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Depth', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Magnitude', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'MagType', type: 'string', format: '', analyzerType: 'STRING'},\n          {name: 'NbStations', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'Gap', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'Distance', type: 'integer', format: '', analyzerType: 'INT'},\n          {name: 'RMS', type: 'real', format: '', analyzerType: 'FLOAT'},\n          {name: 'Source', type: 'string', format: '', analyzerType: 'STRING'},\n          {name: 'EventID', type: 'integer', format: '', analyzerType: 'INT'}\n        ]\n      }\n    },\n    {\n      version: 'v1',\n      data: {\n        id: 'trip-dataset',\n        label: 'trips.json',\n        color: [0, 92, 255],\n        allData: [\n          [\n            {\n              type: 'Feature',\n              properties: {vendor: 'A'},\n              geometry: {\n                type: 'LineString',\n                coordinates: [\n                  [-74.20986, 40.81743, 0, 1564174363],\n                  [-74.20987, 40.81755, 0, 1564174596],\n                  [-74.20998, 40.81766, 0, 1564174709],\n                  [-74.20986, 40.81773, 0, 1564174963],\n                  [-74.20987, 40.81785, 0, 1564175196],\n                  [-74.20998, 40.81806, 0, 1564175309],\n                  [-74.20986, 40.81813, 0, 1564175563],\n                  [-74.20987, 40.81825, 0, 1564175796],\n                  [-74.20998, 40.81846, 0, 1564175909],\n                  [-74.20986, 40.81853, 0, 1564176163],\n                  [-74.20987, 40.81865, 0, 1564176396],\n                  [-74.20998, 40.81876, 0, 1564176509],\n                  [-74.20986, 40.81883, 0, 1564176763],\n                  [-74.20987, 40.81895, 0, 1564176996],\n                  [-74.20998, 40.81906, 0, 1564177109],\n                  [-74.20986, 40.81913, 0, 1564177363],\n                  [-74.20987, 40.81925, 0, 1564177596],\n                  [-74.20998, 40.81936, 0, 1564177709],\n                  [-74.20986, 40.81943, 0, 1564177963],\n                  [-74.20987, 40.81955, 0, 1564178196],\n                  [-74.20998, 40.81966, 0, 1564178309],\n                  [-74.20986, 40.81973, 0, 1564178563],\n                  [-74.20987, 40.81985, 0, 1564178796],\n                  [-74.20998, 40.81996, 0, 1564179109]\n                ]\n              }\n            },\n            'A'\n          ]\n        ],\n        fields: [\n          {name: '_geojson', type: 'geojson', format: '', analyzerType: 'GEOMETRY'},\n          {name: 'vendor', type: 'string', format: '', analyzerType: 'STRING'}\n        ]\n      }\n    }\n  ],\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: ['points-dataset'],\n            id: 'vxwjjz1sf',\n            name: ['DateTime'],\n            type: 'timeRange',\n            value: [1564176748230, 1564184336370],\n            enlarged: true,\n            plotType: 'histogram',\n            animationWindow: 'free',\n            yAxis: null,\n            speed: 1\n          }\n        ],\n        layers: [\n          {\n            id: 'proypi',\n            type: 'point',\n            config: {\n              dataId: 'points-dataset',\n              label: 'Point',\n              color: [255, 203, 153],\n              highlightColor: [252, 242, 26, 255],\n              columns: {lat: 'Latitude', lng: 'Longitude', altitude: null},\n              isVisible: true,\n              visConfig: {\n                radius: 10,\n                fixedRadius: false,\n                opacity: 0.8,\n                outline: false,\n                thickness: 2,\n                strokeColor: null,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                strokeColorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                radiusRange: [0, 50],\n                filled: true\n              },\n              hidden: false,\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: {name: 'Depth', type: 'real'},\n              colorScale: 'quantile',\n              strokeColorField: null,\n              strokeColorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          },\n          {\n            id: 'p4jyzm7',\n            type: 'trip',\n            config: {\n              dataId: 'trip-dataset',\n              label: 'un_2573-trips',\n              color: [248, 149, 112],\n              highlightColor: [252, 242, 26, 255],\n              columns: {geojson: '_geojson'},\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                thickness: 0.5,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                trailLength: 180,\n                sizeRange: [0, 10]\n              },\n              hidden: false,\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: null,\n              colorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              'point-dataset': [\n                {name: 'DateTime', format: null},\n                {name: 'Latitude', format: null},\n                {name: 'Longitude', format: null},\n                {name: 'Depth', format: null},\n                {name: 'Magnitude', format: null}\n              ],\n              'trip-dataset': [{name: 'vendor', format: null}]\n            },\n            compareMode: false,\n            compareType: 'absolute',\n            enabled: true\n          },\n          brush: {size: 0.5, enabled: false},\n          geocoder: {enabled: false},\n          coordinate: {enabled: false}\n        },\n        layerBlending: 'normal',\n        splitMaps: [],\n        animationConfig: {currentTime: 1564174363000, speed: 1}\n      },\n      mapState: {\n        bearing: 0,\n        dragRotate: false,\n        latitude: 37.68923,\n        longitude: -99.0136,\n        pitch: 0,\n        zoom: 4,\n        isSplit: false\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true,\n          '3d building': false\n        },\n        threeDBuildingColor: [9.665468314072013, 17.18305478057247, 31.1442867897876],\n        mapStyles: {}\n      }\n    }\n  },\n  info: {\n    app: 'kepler.gl',\n    created_at: 'Wed Oct 20 2021 16:38:55 GMT-0400 (Eastern Daylight Time)',\n    title: 'keplergl_acitcdlh',\n    description: ''\n  }\n};\n\nexport const mockStateWithSyncedFilterAndTripLayer = () => {\n  const initialState = CloneDeep(InitialState);\n  const result = processKeplerglJSON(syncedFilterWithTripLayerMap);\n\n  const newState = applyActions(coreReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [result]\n    }\n  ]);\n\n  return newState;\n};\n"
  },
  {
    "path": "test/fixtures/test-arc-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default [\n  {\n    venue_id: '7c69e9',\n    count: 10,\n    latitude: 41.852015,\n    longitude: -87.616047,\n    neighbors: [1, 2],\n    'source hex_id': '8a2664c1b74ffff',\n    'target latitude': 41.946969,\n    'target longitude': -87.693607,\n    'target hex_id': '8a2664ca51b7fff'\n  },\n  {\n    venue_id: 'fe26e3',\n    count: 22,\n    latitude: 41.946969,\n    longitude: -87.693607,\n    neighbors: [2, 3],\n    'source hex_id': '8a2664ca51b7fff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: '5f3ec9',\n    count: 5,\n    latitude: 41.9228189446294,\n    longitude: -87.7459144592286,\n    neighbors: [3, 4],\n    'source hex_id': '8a2664ca22dffff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: 'fe625f',\n    count: 11,\n    latitude: 41.9458364,\n    longitude: -87.7474061,\n    neighbors: [4, 5, 6],\n    'source hex_id': '8a2664ca66c7fff',\n    'target latitude': 41.990071,\n    'target longitude': -87.710537,\n    'target hex_id': '8a2664d8ad97fff'\n  },\n  {\n    venue_id: '7b1fe3',\n    count: 14,\n    latitude: 41.879519,\n    longitude: -87.6335512,\n    neighbors: [5, 6],\n    'source hex_id': '8a2664c1a89ffff',\n    'target latitude': 41.8801791917474,\n    'target longitude': -87.7507109194994,\n    'target hex_id': '8a2664c81567fff'\n  },\n  {\n    venue_id: 'd16c1b',\n    count: 8,\n    latitude: 41.7552452681605,\n    longitude: -87.6322426600563,\n    neighbors: [6, 7],\n    'source hex_id': '8a2664cc3757fff',\n    'target latitude': 41.9760432,\n    'target longitude': -87.7082406,\n    'target hex_id': '8a2664d8a71ffff'\n  },\n  {\n    venue_id: '5f3be3',\n    count: 6,\n    latitude: 41.990071,\n    longitude: -87.710537,\n    neighbors: [7, 8],\n    'source hex_id': '8a2664d8ad97fff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: 'eeea20',\n    count: 5,\n    latitude: 41.8801791917474,\n    longitude: -87.7507109194994,\n    neighbors: [8, 9],\n    'source hex_id': '8a2664c81567fff',\n    'target latitude': 41.7753234651629,\n    'target longitude': -87.6832077690278,\n    'target hex_id': '8a2664cd1197fff'\n  },\n  {\n    venue_id: '352fe3',\n    count: 10,\n    latitude: 41.9760432,\n    longitude: -87.7082406,\n    neighbors: [9, 10],\n    'source hex_id': '8a2664d8a71ffff',\n    'target latitude': 41.8667365110101,\n    'target longitude': -87.7061694860458,\n    'target hex_id': '8a2664cab41ffff'\n  },\n  {\n    venue_id: 'd16c1b',\n    count: 9,\n    latitude: 41.7552452681605,\n    longitude: -87.6322426600563,\n    neighbors: [10, 11],\n    'source hex_id': '8a2664cc3757fff',\n    'target latitude': 41.9458364,\n    'target longitude': -87.7474061,\n    'target hex_id': '8a2664ca66c7fff'\n  },\n  {\n    venue_id: '059ad8',\n    count: 6,\n    latitude: 41.7753234651629,\n    longitude: -87.6832077690278,\n    neighbors: [11, 12],\n    'source hex_id': '8a2664cd1197fff',\n    'target latitude': 41.9307572879845,\n    'target longitude': -87.6434218883514,\n    'target hex_id': '8a2664c1009ffff'\n  },\n  {\n    venue_id: '80172c',\n    count: 5,\n    latitude: 41.8667365110101,\n    longitude: -87.7061694860458,\n    neighbors: [12, 13],\n    'source hex_id': '8a2664cab41ffff',\n    'target latitude': 41.852015,\n    'target longitude': -87.616047,\n    'target hex_id': '8a2664c1b74ffff'\n  },\n  {\n    venue_id: 'fe625f',\n    count: 14,\n    latitude: 41.9458364,\n    longitude: -87.7474061,\n    neighbors: [13, 14],\n    'source hex_id': '8a2664ca66c7fff',\n    'target latitude': 41.946969,\n    'target longitude': -87.693607,\n    'target hex_id': '8a2664ca51b7fff'\n  },\n  {\n    venue_id: 'e41fe3',\n    count: 22,\n    latitude: 41.9307572879845,\n    longitude: -87.6434218883514,\n    neighbors: [14, 15],\n    'source hex_id': '8a2664c1009ffff',\n    'target latitude': 41.9228189446294,\n    'target longitude': -87.7459144592286,\n    'target hex_id': '8a2664ca22dffff'\n  },\n  {\n    venue_id: 'e12ee3',\n    count: 7,\n    latitude: 41.99558699394,\n    longitude: -87.6599389314652,\n    neighbors: [15, 16],\n    'source hex_id': '8a2664d8c2f7fff',\n    'target latitude': 41.9458364,\n    'target longitude': -87.7474061,\n    'target hex_id': '8a2664ca66c7fff'\n  },\n  {\n    venue_id: 'ac29e3',\n    count: 18,\n    latitude: 41.8008800318507,\n    longitude: -87.5880885869265,\n    neighbors: [16, 17],\n    'source hex_id': '8a2664ce3637fff',\n    'target latitude': 41.7552452681605,\n    'target longitude': -87.6322426600563,\n    'target hex_id': '8a2664cc3757fff'\n  },\n  {\n    venue_id: 'c236e3',\n    count: 8,\n    latitude: 41.9641381521923,\n    longitude: -87.7086424036042,\n    neighbors: [17, 18],\n    'source hex_id': '8a2664ca489ffff',\n    'target latitude': 41.7753234651629,\n    'target longitude': -87.6832077690278,\n    'target hex_id': '8a2664cd1197fff'\n  },\n  {\n    venue_id: '1b21e3',\n    count: 20,\n    latitude: 41.9171668642571,\n    longitude: -87.6869366271512,\n    neighbors: [18, 19],\n    'source hex_id': '8a2664cacd97fff',\n    'target latitude': 41.8667365110101,\n    'target longitude': -87.7061694860458,\n    'target hex_id': '8a2664cab41ffff'\n  },\n  {\n    venue_id: '6a9d00',\n    count: 13,\n    latitude: 41.7923696161345,\n    longitude: -87.7899415791035,\n    neighbors: [19, 20],\n    'source hex_id': '8a2664520797fff',\n    'target latitude': 41.9458364,\n    'target longitude': -87.7474061,\n    'target hex_id': '8a2664ca66c7fff'\n  },\n  {\n    venue_id: 'fd31e3',\n    count: 8,\n    latitude: 41.887104,\n    longitude: -87.623222,\n    neighbors: [20, 21],\n    'source hex_id': '8a2664c1e21ffff',\n    'target latitude': 41.9307572879845,\n    'target longitude': -87.6434218883514,\n    'target hex_id': '8a2664c1009ffff'\n  }\n];\n\nexport const dataId = 'test-arc-data';\nexport const arcDataInfo = {\n  id: dataId,\n  label: 'Arc Row Data',\n  color: [155, 155, 155]\n};\n\nexport const pointFromNeighbor = {\n  id: 'point-1',\n  type: 'point',\n  config: {\n    dataId,\n    label: 'point',\n    columns: {\n      lat: 'latitude',\n      lng: 'longitude',\n      neighbors: 'neighbors'\n    },\n    isVisible: true,\n    visConfig: {\n      radius: 20,\n      opacity: 1,\n      allowHover: true,\n      showNeighborOnHover: true,\n      showHighlightColor: false\n    }\n  }\n};\n\nexport const arcFromHex = {\n  id: 'arc-1',\n  type: 'arc',\n  config: {\n    dataId,\n    label: 'arc',\n    columnMode: 'points',\n    columns: {\n      lat0: 'source hex_id',\n      lat1: 'target hex_id'\n    },\n    isVisible: true\n  }\n};\n\nexport const arcFromNeighbor = {\n  id: 'arc-2',\n  type: 'arc',\n  config: {\n    dataId,\n    columnMode: 'neighbors',\n    label: 'arc from neighbors',\n    columns: {\n      lat: 'latitude',\n      lng: 'longitude',\n      neighbors: 'neighbors'\n    },\n    isVisible: true\n  }\n};\n\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      layers: [pointFromNeighbor, arcFromHex, arcFromNeighbor]\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/test-csv-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {getBinThresholds, histogramFromThreshold} from '@kepler.gl/utils';\n\n/* eslint-disable max-len */\nconst data = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,uid,time,begintrip_ts_utc,begintrip_ts_local,date\n2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics_0,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23\n2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23\n2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23\n2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23\n2016-09-17 00:14:00,29.9923041,31.2154899,driver_analytics,1472688000000,False,5,2016-09-23T00:00:00.000Z,2016-10-01 09:47:37+00:00,2016-10-01 16:47:37+00:00,\n2016-09-17 00:15:01,29.9968249,31.2149361,driver_analytics,1472688000000,False,12124,2016-09-23T05:00:00.000Z,,,\n2016-09-17 00:16:03,30.0037217,31.2164035,driver_analytics,1472688000000,False,222,2016-09-23T05:00:00.000Z,,,\n2016-09-17 00:17:05,30.0116207,31.2179346,driver_analytics,1472688000000,False,345,2016-09-23T00:00:00.000Z,,,2016-09-24\n2016-09-17 00:18:09,30.0208925,31.2179556,driver_analytics,1472708000000,False,,2016-09-23T00:00:00.000Z,,,2016-09-24\n2016-09-17 00:19:12,30.0218999,31.2178842,driver_analytics,1472708000000,False,,2016-09-23T06:00:00.000Z,,,2016-09-24\n2016-09-17 00:19:27,30.0229344,31.2179138,driver_analytics,1472708000000,False,,2016-09-23T05:00:00.000Z,,,2016-09-24\n2016-09-17 00:20:14,30.0264237,31.2179415,driver_analytics,1472708000000,False,,,,,2016-09-24\n2016-09-17 00:21:17,30.0292134,31.2181809,driver_analytics,1472754400000,False,,,,,2016-09-24\n2016-09-17 00:22:20,30.034391,31.2193991,driver_analytics,1472754400000,,,2016-09-23T06:00:00.000Z,,,\n2016-09-17 00:23:22,30.0352752,31.2181803,driver_analytics,1472754400000,,,2016-09-23T06:00:00.000Z,2016-10-01 10:01:54+00:00,2016-10-01 17:01:54+00:00,\n2016-09-17 00:24:24,30.0395918,31.2195902,driver_analytics,1472754400000,,1,2016-09-23T00:00:00.000Z,2016-10-01 09:53:04+00:00,2016-10-01 16:53:04+00:00,\n2016-09-17 00:25:28,30.0497387,31.2174421,driver_analytics,1472774400000,,,2016-09-23T07:00:00.000Z,2016-10-01 09:55:23+00:00,2016-10-01 16:55:23+00:00,\n2016-09-17 00:26:29,30.0538936,31.2165983,driver_analytics,1472774400000,,43,2016-09-23T07:00:00.000Z,2016-10-01 09:59:53+00:00,2016-10-01 16:59:53+00:00,2016-10-10\n2016-09-17 00:27:31,30.060911,31.2148748,driver_analytics,1472774400000,,4,2016-09-23T07:00:00.000Z,2016-10-01 09:57:11+00:00,2016-10-01 16:57:11+00:00,2016-10-10\n2016-09-17 00:28:35,30.060334,31.2212278,driver_analytics,1472774400000,,5,2016-09-23T07:00:00.000Z,2016-10-01 09:59:27+00:00,2016-10-01 16:59:27+00:00,2016-10-10\n2016-09-17 00:29:40,30.0554663,31.2288985,driver_analytics,1472774400000,True,,2016-09-23T07:00:00.000Z,2016-10-01 09:46:36+00:00,2016-10-01 16:46:36+00:00,2016-10-10\n2016-09-17 00:30:03,30.0614122,31.2187021,driver_gps,1472774400000,True,6,2016-09-23T08:00:00.000Z,2016-10-01 09:54:31+00:00,2016-10-01 16:54:31+00:00,2016-10-10\n2016-09-17 00:30:03,30.0612697,31.2191059,driver_gps,1472774400000,True,7,2016-09-23T08:00:00.000Z,2016-10-01 09:53:35+00:00,2016-10-01 16:53:35+00:00,2016-10-10\n2016-09-17 00:30:08,30.0610977,31.2194728,driver_gps,1472774400000,True,,2016-09-23T08:00:00.000Z,,,`;\n\nexport const dataId = '190vdll3di';\nexport const gpsPointBounds = [31.2148748, 29.9870074, 31.2590542, 30.0614122];\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      filters: [\n        {\n          dataId,\n          id: 'c75x65vlb',\n          name: 'gps_data.utc_timestamp',\n          type: 'timeRange',\n          value: [1474071773000, 1474072208000],\n          enlarged: true,\n          plotType: 'histogram',\n          yAxis: null\n        }\n      ],\n      layers: [\n        {\n          id: '3zucml7',\n          type: 'point',\n          config: {\n            dataId,\n            label: 'gps data',\n            color: [18, 147, 154],\n            columns: {\n              lat: 'gps_data.lat',\n              lng: 'gps_data.lng',\n              altitude: null\n            },\n            hidden: false,\n            isVisible: true,\n            visConfig: {\n              radius: 39.2,\n              fixedRadius: false,\n              opacity: 0.74,\n              outline: true,\n              thickness: 2,\n              strokeColor: [254, 242, 26],\n              colorRange: {\n                name: 'UberPool 3',\n                type: 'diverging',\n                category: 'Uber',\n                colors: ['#213E9A', '#CA168E', '#F9E200'],\n                reversed: false\n              },\n              strokeColorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              radiusRange: [28.5, 194.7],\n              filled: true\n            },\n            textLabel: [\n              {\n                field: null,\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: {\n              name: 'gps_data.types',\n              type: 'string'\n            },\n            colorScale: 'ordinal',\n            strokeColorField: null,\n            strokeColorScale: 'quantile',\n            sizeField: {\n              name: 'id',\n              type: 'integer'\n            },\n            sizeScale: 'sqrt'\n          }\n        },\n        {\n          id: 'nob639j',\n          type: 'hexagon',\n          config: {\n            dataId,\n            label: 'hexbin',\n            color: [34, 63, 154],\n            columns: {\n              lat: 'gps_data.lat',\n              lng: 'gps_data.lng'\n            },\n            hidden: false,\n            isVisible: true,\n            visConfig: {\n              opacity: 0.8,\n              worldUnitSize: 1,\n              resolution: 8,\n              colorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              },\n              coverage: 1,\n              sizeRange: [0, 500],\n              percentile: [0, 100],\n              elevationPercentile: [0, 100],\n              elevationScale: 5,\n              enableElevationZoomFactor: true,\n              fixedHeight: false,\n              colorAggregation: 'count',\n              sizeAggregation: 'count',\n              enable3d: false\n            },\n            textLabel: [\n              {\n                field: null,\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: null,\n            colorScale: 'quantile',\n            sizeField: null,\n            sizeScale: 'linear'\n          }\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            [dataId]: ['gps_data.types', 'has_result', 'id', 'time']\n          },\n          enabled: true\n        },\n        brush: {\n          size: 0.5,\n          enabled: false\n        },\n        geocoder: {\n          enabled: false\n        }\n      },\n      layerBlending: 'normal',\n      splitMaps: [\n        {\n          layers: {\n            '3zucml7': {\n              isAvailable: true,\n              isVisible: true\n            },\n            nob639j: {\n              isAvailable: true,\n              isVisible: false\n            }\n          }\n        },\n        {\n          layers: {\n            '3zucml7': {\n              isAvailable: true,\n              isVisible: false\n            },\n            nob639j: {\n              isAvailable: true,\n              isVisible: true\n            }\n          }\n        }\n      ]\n    },\n    mapState: {\n      bearing: 24,\n      dragRotate: true,\n      latitude: 30.028974821846045,\n      longitude: 31.205476105912005,\n      pitch: 50,\n      zoom: 12.456401285586372,\n      isSplit: true\n    },\n    mapStyle: {\n      styleType: 'light',\n      topLayerGroups: {\n        road: true\n      },\n      visibleLayerGroups: {\n        label: false,\n        road: true,\n        border: false,\n        building: true,\n        water: true,\n        land: true,\n        '3d building': false\n      },\n      mapStyles: {}\n    }\n  }\n};\n\nexport const sampleConfig = {\n  dataId,\n  config\n};\n\nexport const dataWithNulls = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,uid,time,begintrip_ts_utc,begintrip_ts_local,date\nNull,29.9900937,31.2590542,driver_analytics_0,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23\n2016-09-17 00:10:56,29.9927699,31.2461142,null,1472688000000,False,null,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23\n2016-09-17 00:11:56,29.9907261,NaN,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23\n2016-09-17 00:12:58,29.9870074,31.2175827,driver_gps,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23\n2016-09-17 00:14:00,29.9923041,31.2154899,driver_analytics,1472688000000,1,5,2016-09-23T00:00:00.000Z,2016-10-01 09:47:37+00:00,2016-10-01 16:47:37+00:00,\n2016-09-17 00:15:01,29.9968249,31.2149361,driver_analytics,1472688000000,False,12124,2016-09-23T05:00:00.000Z,,,\n2016-09-17 00:16:03,Null,31.2164035,driver_analytics,1472688000000,False,222,2016-09-23T05:00:00.000Z,,,\n2016-09-17 00:17:05,30.0116207,31.2179346,driver_analytics,1472688000000,False,345,2016-09-23T00:00:00.000Z,,,2016-09-24\n2016-09-17 00:18:09,30.0208925,31.2179556,driver_analytics,1472708000000,False,,2016-09-23T00:00:00.000Z,,,2016-09-24\n2016-09-17 00:19:12,30.0218999,31.2178842,driver_analytics,NULL,0,,2016-09-23T06:00:00.000Z,,,2016-09-24\n2016-09-17 00:19:27,30.0229344,31.2179138,driver_gps,1472708000000,False,,2016-09-23T05:00:00.000Z,,,2016-09-24\n2016-09-17 00:20:14,30.0264237,31.2179415,driver_gps,1472708000000,False,,,,,2016-09-24\n2016-09-17 00:21:17,30.0292134,31.2181809,driver_gps,1472754400000,False,,,,,2016-09-24`;\n\nexport const parsedDataWithNulls = [\n  [\n    null,\n    29.9900937,\n    31.2590542,\n    'driver_analytics_0',\n    1472688000000,\n    false,\n    1,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:41:39+00:00',\n    '2016-10-01 09:41:39+00:00',\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:10:56',\n    29.9927699,\n    31.2461142,\n    null,\n    1472688000000,\n    false,\n    null,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:46:37+00:00',\n    '2016-10-01 16:46:37+00:00',\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:11:56',\n    29.9907261,\n    null,\n    'driver_analytics',\n    1472688000000,\n    false,\n    3,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:12:58',\n    29.9870074,\n    31.2175827,\n    'driver_gps',\n    1472688000000,\n    false,\n    4,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:14:00',\n    29.9923041,\n    31.2154899,\n    'driver_analytics',\n    1472688000000,\n    true,\n    5,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:47:37+00:00',\n    '2016-10-01 16:47:37+00:00',\n    null\n  ],\n  [\n    '2016-09-17 00:15:01',\n    29.9968249,\n    31.2149361,\n    'driver_analytics',\n    1472688000000,\n    false,\n    12124,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    null\n  ],\n  [\n    '2016-09-17 00:16:03',\n    null,\n    31.2164035,\n    'driver_analytics',\n    1472688000000,\n    false,\n    222,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    null\n  ],\n  [\n    '2016-09-17 00:17:05',\n    30.0116207,\n    31.2179346,\n    'driver_analytics',\n    1472688000000,\n    false,\n    345,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:18:09',\n    30.0208925,\n    31.2179556,\n    'driver_analytics',\n    1472708000000,\n    false,\n    null,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:19:12',\n    30.0218999,\n    31.2178842,\n    'driver_analytics',\n    null,\n    false,\n    null,\n    '2016-09-23T06:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:19:27',\n    30.0229344,\n    31.2179138,\n    'driver_gps',\n    1472708000000,\n    false,\n    null,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:20:14',\n    30.0264237,\n    31.2179415,\n    'driver_gps',\n    1472708000000,\n    false,\n    null,\n    null,\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:21:17',\n    30.0292134,\n    31.2181809,\n    'driver_gps',\n    1472754400000,\n    false,\n    null,\n    null,\n    null,\n    null,\n    '2016-09-24'\n  ]\n];\n\nexport const wktCsv = `a_zip,simplified_shape_v2,simplified_shape,m_rate,c_zip_type,c_number\n7015,,,7,C_Medium_High,22.22\n7014,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522]]]}\",\"POLYGON ((-74.158491 40.835947, -74.157914 40.83902, -74.148473 40.834522))\",7.5,C_Medium_High,29.1\n7016,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.31687,40.656696]]]}\",\"POLYGON ((-74.31687 40.656696, -74.319449 40.658154, -74.31687 40.656696))\",,C_Medium_High,27.6\n7023,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.387589,40.632238],[-74.387589,40.632238]]]}\",\"POLYGON ((-74.387589 40.632238, -74.387589 40.632238))\",7.6,C_Medium_High,23.8\n7029,\"{\"\"type\"\":\"\"Polygon\"\",\"\"coordinates\"\":[[[-74.165995,40.747969],[-74.165987,40.745199],[-74.165995,40.747969]]]}\",\"POLYGON ((-74.165995 40.747969, -74.165987 40.745199, -74.165995 40.747969))\",11.8,C_Medium_High,39.1\n7416,\"{\"\"type\"\":\"\"MultiPolygon\"\",\"\"coordinates\"\":[[[[-74.566993,41.087294],[-74.564908,41.089339],[-74.552353,41.091647],[-74.566993,41.087294]]],[[[-74.593264,41.088526],[-74.593264,41.088526]]]]}\",\"MULTIPOLYGON (((-74.566993 41.087294, -74.564908 41.089339, -74.552353 41.091647, -74.566993 41.087294)),((-74.593264 41.088526, -74.593264 41.088526)))\",10,A_Low_Rural,13.8\n7023,\"{\"\"type\"\":\"\"LineString\"\",\"\"coordinates\"\":[[-74.387589,40.632238],[-74.387589,40.632238]]}\",\"LINESTRING (-74.387589 40.632238, -74.387589 40.632238)\",7.6,C_Medium_High,29.2\n`;\n\n// output of processCsvData\nexport const testFields = [\n  {\n    type: 'timestamp',\n    fieldIdx: 0,\n    name: 'gps_data.utc_timestamp',\n    id: 'gps_data.utc_timestamp',\n    displayName: 'gps_data.utc_timestamp',\n    format: 'YYYY-M-D H:m:s',\n    analyzerType: 'DATETIME',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 0);\n    }\n  },\n  {\n    type: 'real',\n    fieldIdx: 1,\n    name: 'gps_data.lat',\n    id: 'gps_data.lat',\n    displayName: 'gps_data.lat',\n    format: '',\n    analyzerType: 'FLOAT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 1);\n    }\n  },\n  {\n    type: 'real',\n    fieldIdx: 2,\n    name: 'gps_data.lng',\n    id: 'gps_data.lng',\n    displayName: 'gps_data.lng',\n    format: '',\n    analyzerType: 'FLOAT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 2);\n    }\n  },\n  {\n    type: 'string',\n    fieldIdx: 3,\n    name: 'gps_data.types',\n    id: 'gps_data.types',\n    displayName: 'gps_data.types',\n    format: '',\n    analyzerType: 'STRING',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 3);\n    }\n  },\n  {\n    type: 'timestamp',\n    fieldIdx: 4,\n    name: 'epoch',\n    id: 'epoch',\n    displayName: 'epoch',\n    format: 'X',\n    analyzerType: 'TIME',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 4);\n    }\n  },\n  {\n    type: 'boolean',\n    fieldIdx: 5,\n    name: 'has_result',\n    id: 'has_result',\n    displayName: 'has_result',\n    format: '',\n    analyzerType: 'BOOLEAN',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 5);\n    }\n  },\n  {\n    type: 'integer',\n    fieldIdx: 6,\n    name: 'uid',\n    id: 'uid',\n    displayName: 'uid',\n    format: '',\n    analyzerType: 'INT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 6);\n    }\n  },\n  {\n    type: 'timestamp',\n    fieldIdx: 7,\n    name: 'time',\n    id: 'time',\n    displayName: 'time',\n    format: 'YYYY-M-DTHH:mm:ss.SSSS',\n    analyzerType: 'DATETIME',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 7);\n    }\n  },\n  {\n    type: 'timestamp',\n    fieldIdx: 8,\n    name: 'begintrip_ts_utc',\n    id: 'begintrip_ts_utc',\n    displayName: 'begintrip_ts_utc',\n    format: 'YYYY-M-D HH:mm:ssZZ',\n    analyzerType: 'DATETIME',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 8);\n    }\n  },\n  {\n    type: 'timestamp',\n    fieldIdx: 9,\n    name: 'begintrip_ts_local',\n    id: 'begintrip_ts_local',\n    displayName: 'begintrip_ts_local',\n    format: 'YYYY-M-D HH:mm:ssZZ',\n    analyzerType: 'DATETIME',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 9);\n    }\n  },\n  {\n    type: 'date',\n    fieldIdx: 10,\n    name: 'date',\n    id: 'date',\n    displayName: 'date',\n    format: 'YYYY-M-D',\n    analyzerType: 'DATE',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 10);\n    }\n  }\n];\n\nexport const testCsvFieldPairs = [\n  {\n    defaultName: 'gps_data',\n    pair: {\n      lat: {\n        value: 'gps_data.lat',\n        fieldIdx: 1\n      },\n      lng: {\n        value: 'gps_data.lng',\n        fieldIdx: 2\n      }\n    },\n    suffix: ['lat', 'lng']\n  }\n];\nexport const timeMappedValue = [\n  1474588800000,\n  1474588800000,\n  1474588800000,\n  1474588800000,\n  1474588800000,\n  1474606800000,\n  1474606800000,\n  1474588800000,\n  1474588800000,\n  1474610400000,\n  1474606800000,\n  null,\n  null,\n  1474610400000,\n  1474610400000,\n  1474588800000,\n  1474614000000,\n  1474614000000,\n  1474614000000,\n  1474614000000,\n  1474614000000,\n  1474617600000,\n  1474617600000,\n  1474617600000\n];\n\nexport const epochMappedValue = [\n  1472688000000, 1472688000000, 1472688000000, 1472688000000, 1472688000000, 1472688000000,\n  1472688000000, 1472688000000, 1472708000000, 1472708000000, 1472708000000, 1472708000000,\n  1472754400000, 1472754400000, 1472754400000, 1472754400000, 1472774400000, 1472774400000,\n  1472774400000, 1472774400000, 1472774400000, 1472774400000, 1472774400000, 1472774400000\n];\n\nexport const timeStampmappedValue = [\n  1474070995000, 1474071056000, 1474071116000, 1474071178000, 1474071240000, 1474071301000,\n  1474071363000, 1474071425000, 1474071489000, 1474071552000, 1474071567000, 1474071614000,\n  1474071677000, 1474071740000, 1474071802000, 1474071864000, 1474071928000, 1474071989000,\n  1474072051000, 1474072115000, 1474072180000, 1474072203000, 1474072203000, 1474072208000\n];\n\nexport const timeFilterProps = {\n  domain: [1474588800000, 1474617600000],\n  step: 1000,\n  mappedValue: timeMappedValue,\n  fieldType: 'timestamp',\n  type: 'timeRange',\n  view: 'enlarged',\n  fixedDomain: true,\n  gpu: true,\n  value: [1474588800000, 1474617600000],\n  defaultTimeFormat: 'L LTS',\n  plotType: {}\n};\n\nexport const mergedTimeFilter = {\n  ...timeFilterProps,\n  animationWindow: 'free',\n  dataId: [dataId],\n  id: 'time-0',\n  enabled: true,\n  fixedDomain: true,\n  view: 'enlarged',\n  isAnimating: false,\n  speed: 4,\n  name: ['time'],\n  type: 'timeRange',\n  fieldIdx: [7],\n  plotType: {\n    interval: '1-hour',\n    defaultTimeFormat: 'L  H A',\n    type: 'histogram',\n    aggregation: 'sum'\n  },\n  yAxis: null,\n  value: [1474606800000, 1474617600000],\n  gpuChannel: [0],\n  timeBins: {\n    [dataId]: {\n      '1-hour': [\n        {count: 8, indexes: [0, 1, 2, 3, 4, 7, 8, 15], x0: 1474588800000, x1: 1474592400000},\n        {count: 3, indexes: [5, 6, 10], x0: 1474606800000, x1: 1474610400000},\n        {count: 3, indexes: [9, 13, 14], x0: 1474610400000, x1: 1474614000000},\n        {count: 5, indexes: [16, 17, 18, 19, 20], x0: 1474614000000, x1: 1474617600000},\n        {count: 3, indexes: [21, 22, 23], x0: 1474617600000, x1: 1474621200000}\n      ]\n    }\n  }\n};\n\nexport const epochFilterProps = {\n  domain: [1472688000000, 1472774400000],\n  step: 1000,\n  mappedValue: epochMappedValue,\n  fieldType: 'timestamp',\n  type: 'timeRange',\n  view: 'enlarged',\n  fixedDomain: true,\n  gpu: true,\n  value: [1472688000000, 1472774400000],\n  defaultTimeFormat: 'L LTS',\n  plotType: {}\n};\n\n// value set mockStateWithFilters 1472700000000, 1472760000000\nexport const mergedEpochFilter = {\n  ...epochFilterProps,\n  animationWindow: 'free',\n  dataId: [dataId],\n  id: 'epoch-1',\n  enabled: true,\n  fixedDomain: true,\n  view: 'side',\n  isAnimating: false,\n  speed: 1,\n  name: ['epoch'],\n  type: 'timeRange',\n  fieldIdx: [4],\n  plotType: {\n    interval: '15-minute',\n    defaultTimeFormat: 'L  LT',\n    type: 'histogram',\n    aggregation: 'sum'\n  },\n  yAxis: null,\n  value: [1472700000000, 1472760000000],\n  // time filter is in channel 0\n  gpuChannel: [1],\n  timeBins: {\n    [dataId]: {\n      '15-minute': histogramFromThreshold(\n        getBinThresholds('15-minute', [1472688000000, 1472774400000]),\n        epochMappedValue\n      )\n    }\n  }\n};\n\nexport const expectedSyncedTsFilter = {\n  id: 'filter-0',\n  enabled: true,\n  dataId: ['test-csv-data-1', 'test-csv-data-2'],\n  name: ['gps_data.utc_timestamp', 'gps_data.utc_timestamp'],\n  fieldIdx: [0, 0],\n  // domain\n  // 2016-09-17 00:09:55 // 1474070995000\n  // 2016-09-17 00:30:08 // 1474072208000\n  domain: [1474070995000, 1474072208000],\n  value: [1474071116000, 1474072188000],\n  animationWindow: 'free',\n  defaultTimeFormat: 'L LTS',\n  view: 'enlarged',\n  fieldType: 'timestamp',\n  fixedDomain: true,\n  gpu: true,\n  gpuChannel: [0, 0],\n  isAnimating: false,\n  plotType: {\n    interval: '15-second',\n    defaultTimeFormat: 'L  LTS',\n    type: 'histogram',\n    aggregation: 'sum',\n    colorsByDataId: {\n      'test-csv-data-1': '#FF0000',\n      'test-csv-data-2': '#00FF00'\n    }\n  },\n  speed: 1,\n  step: 1000,\n  type: 'timeRange',\n  yAxis: null,\n  timeBins: {\n    'test-csv-data-1': {\n      '15-second': histogramFromThreshold(\n        getBinThresholds('15-second', [1474070995000, 1474072208000]),\n        timeStampmappedValue.slice(0, 20)\n      )\n    },\n    'test-csv-data-2': {\n      '15-second': histogramFromThreshold(\n        getBinThresholds('15-second', [1474070995000, 1474072208000]),\n        timeStampmappedValue.slice(5, timeStampmappedValue.length)\n      )\n    }\n  },\n  mappedValue: [\n    1474071301000, 1474071363000, 1474071425000, 1474071489000, 1474071552000, 1474071567000,\n    1474071614000, 1474071677000, 1474071740000, 1474071802000, 1474071864000, 1474071928000,\n    1474071989000, 1474072051000, 1474072115000, 1474072180000, 1474072203000, 1474072203000,\n    1474072208000\n  ]\n};\n\nexport const dateFilterProps = {\n  domain: ['2016-09-23', '2016-09-24', '2016-10-10'],\n  fieldType: 'date',\n  type: 'multiSelect',\n  gpu: false,\n  value: [],\n  view: 'side'\n};\n\nexport const mergedDateFilter = {\n  ...dateFilterProps,\n  animationWindow: 'free',\n  dataId: [dataId],\n  id: 'date-2',\n  enabled: true,\n  fixedDomain: false,\n  view: 'side',\n  isAnimating: false,\n  speed: 1,\n  name: ['date'],\n  type: 'multiSelect',\n  fieldIdx: [10],\n  value: ['2016-09-24', '2016-09-23'],\n  plotType: {\n    type: 'histogram'\n  },\n  yAxis: null\n};\n\nexport const wktCsvFields = [\n  {\n    type: 'integer',\n    name: 'a_zip',\n    id: 'a_zip',\n    displayName: 'a_zip',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'INT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 0);\n    }\n  },\n  {\n    type: 'geojson',\n    name: 'simplified_shape_v2',\n    id: 'simplified_shape_v2',\n    displayName: 'simplified_shape_v2',\n    format: '',\n    fieldIdx: 1,\n    analyzerType: 'PAIR_GEOMETRY_FROM_STRING',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 1);\n    }\n  },\n  {\n    type: 'geojson',\n    name: 'simplified_shape',\n    id: 'simplified_shape',\n    displayName: 'simplified_shape',\n    format: '',\n    fieldIdx: 2,\n    analyzerType: 'GEOMETRY_FROM_STRING',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 2);\n    }\n  },\n  {\n    type: 'real',\n    name: 'm_rate',\n    id: 'm_rate',\n    displayName: 'm_rate',\n    format: '',\n    fieldIdx: 3,\n    analyzerType: 'FLOAT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 3);\n    }\n  },\n  {\n    type: 'string',\n    name: 'c_zip_type',\n    id: 'c_zip_type',\n    displayName: 'c_zip_type',\n    format: '',\n    fieldIdx: 4,\n    analyzerType: 'STRING',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 4);\n    }\n  },\n  {\n    type: 'real',\n    name: 'c_number',\n    id: 'c_number',\n    displayName: 'c_number',\n    format: '',\n    fieldIdx: 5,\n    analyzerType: 'FLOAT',\n    valueAccessor: dc => d => {\n      return dc.valueAt(d.index, 5);\n    }\n  }\n];\n\nexport const wktCsvRows = [\n  [7015, null, null, 7, 'C_Medium_High', 22.22],\n  [\n    7014,\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.158491,40.835947],[-74.157914,40.83902],[-74.148473,40.834522]]]}',\n    'POLYGON ((-74.158491 40.835947, -74.157914 40.83902, -74.148473 40.834522))',\n    7.5,\n    'C_Medium_High',\n    29.1\n  ],\n  [\n    7016,\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.31687,40.656696],[-74.319449,40.658154],[-74.31687,40.656696]]]}',\n    'POLYGON ((-74.31687 40.656696, -74.319449 40.658154, -74.31687 40.656696))',\n    null,\n    'C_Medium_High',\n    27.6\n  ],\n  [\n    7023,\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.387589,40.632238],[-74.387589,40.632238]]]}',\n    'POLYGON ((-74.387589 40.632238, -74.387589 40.632238))',\n    7.6,\n    'C_Medium_High',\n    23.8\n  ],\n  [\n    7029,\n    '{\"type\":\"Polygon\",\"coordinates\":[[[-74.165995,40.747969],[-74.165987,40.745199],[-74.165995,40.747969]]]}',\n    'POLYGON ((-74.165995 40.747969, -74.165987 40.745199, -74.165995 40.747969))',\n    11.8,\n    'C_Medium_High',\n    39.1\n  ],\n  [\n    7416,\n    '{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-74.566993,41.087294],[-74.564908,41.089339],[-74.552353,41.091647],[-74.566993,41.087294]]],[[[-74.593264,41.088526],[-74.593264,41.088526]]]]}',\n    'MULTIPOLYGON (((-74.566993 41.087294, -74.564908 41.089339, -74.552353 41.091647, -74.566993 41.087294)),((-74.593264 41.088526, -74.593264 41.088526)))',\n    10,\n    'A_Low_Rural',\n    13.8\n  ],\n  [\n    7023,\n    '{\"type\":\"LineString\",\"coordinates\":[[-74.387589,40.632238],[-74.387589,40.632238]]}',\n    'LINESTRING (-74.387589 40.632238, -74.387589 40.632238)',\n    7.6,\n    'C_Medium_High',\n    29.2\n  ]\n];\n\nexport const testAllData = [\n  [\n    '2016-09-17 00:09:55',\n    29.9900937,\n    31.2590542,\n    'driver_analytics_0',\n    1472688000000,\n    false,\n    1,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:41:39+00:00',\n    '2016-10-01 09:41:39+00:00',\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:10:56',\n    29.9927699,\n    31.2461142,\n    'driver_analytics',\n    1472688000000,\n    false,\n    2,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:46:37+00:00',\n    '2016-10-01 16:46:37+00:00',\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:11:56',\n    29.9907261,\n    31.2312742,\n    'driver_analytics',\n    1472688000000,\n    false,\n    3,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:12:58',\n    29.9870074,\n    31.2175827,\n    'driver_analytics',\n    1472688000000,\n    false,\n    4,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-23'\n  ],\n  [\n    '2016-09-17 00:14:00',\n    29.9923041,\n    31.2154899,\n    'driver_analytics',\n    1472688000000,\n    false,\n    5,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:47:37+00:00',\n    '2016-10-01 16:47:37+00:00',\n    null\n  ],\n  [\n    '2016-09-17 00:15:01',\n    29.9968249,\n    31.2149361,\n    'driver_analytics',\n    1472688000000,\n    false,\n    12124,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    null\n  ],\n  [\n    '2016-09-17 00:16:03',\n    30.0037217,\n    31.2164035,\n    'driver_analytics',\n    1472688000000,\n    false,\n    222,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    null\n  ],\n  [\n    '2016-09-17 00:17:05',\n    30.0116207,\n    31.2179346,\n    'driver_analytics',\n    1472688000000,\n    false,\n    345,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:18:09',\n    30.0208925,\n    31.2179556,\n    'driver_analytics',\n    1472708000000,\n    false,\n    null,\n    '2016-09-23T00:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:19:12',\n    30.0218999,\n    31.2178842,\n    'driver_analytics',\n    1472708000000,\n    false,\n    null,\n    '2016-09-23T06:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:19:27',\n    30.0229344,\n    31.2179138,\n    'driver_analytics',\n    1472708000000,\n    false,\n    null,\n    '2016-09-23T05:00:00.000Z',\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:20:14',\n    30.0264237,\n    31.2179415,\n    'driver_analytics',\n    1472708000000,\n    false,\n    null,\n    null,\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:21:17',\n    30.0292134,\n    31.2181809,\n    'driver_analytics',\n    1472754400000,\n    false,\n    null,\n    null,\n    null,\n    null,\n    '2016-09-24'\n  ],\n  [\n    '2016-09-17 00:22:20',\n    30.034391,\n    31.2193991,\n    'driver_analytics',\n    1472754400000,\n    null,\n    null,\n    '2016-09-23T06:00:00.000Z',\n    null,\n    null,\n    null\n  ],\n  [\n    '2016-09-17 00:23:22',\n    30.0352752,\n    31.2181803,\n    'driver_analytics',\n    1472754400000,\n    null,\n    null,\n    '2016-09-23T06:00:00.000Z',\n    '2016-10-01 10:01:54+00:00',\n    '2016-10-01 17:01:54+00:00',\n    null\n  ],\n  [\n    '2016-09-17 00:24:24',\n    30.0395918,\n    31.2195902,\n    'driver_analytics',\n    1472754400000,\n    null,\n    1,\n    '2016-09-23T00:00:00.000Z',\n    '2016-10-01 09:53:04+00:00',\n    '2016-10-01 16:53:04+00:00',\n    null\n  ],\n  [\n    '2016-09-17 00:25:28',\n    30.0497387,\n    31.2174421,\n    'driver_analytics',\n    1472774400000,\n    null,\n    null,\n    '2016-09-23T07:00:00.000Z',\n    '2016-10-01 09:55:23+00:00',\n    '2016-10-01 16:55:23+00:00',\n    null\n  ],\n  [\n    '2016-09-17 00:26:29',\n    30.0538936,\n    31.2165983,\n    'driver_analytics',\n    1472774400000,\n    null,\n    43,\n    '2016-09-23T07:00:00.000Z',\n    '2016-10-01 09:59:53+00:00',\n    '2016-10-01 16:59:53+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:27:31',\n    30.060911,\n    31.2148748,\n    'driver_analytics',\n    1472774400000,\n    null,\n    4,\n    '2016-09-23T07:00:00.000Z',\n    '2016-10-01 09:57:11+00:00',\n    '2016-10-01 16:57:11+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:28:35',\n    30.060334,\n    31.2212278,\n    'driver_analytics',\n    1472774400000,\n    null,\n    5,\n    '2016-09-23T07:00:00.000Z',\n    '2016-10-01 09:59:27+00:00',\n    '2016-10-01 16:59:27+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:29:40',\n    30.0554663,\n    31.2288985,\n    'driver_analytics',\n    1472774400000,\n    true,\n    null,\n    '2016-09-23T07:00:00.000Z',\n    '2016-10-01 09:46:36+00:00',\n    '2016-10-01 16:46:36+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:30:03',\n    30.0614122,\n    31.2187021,\n    'driver_gps',\n    1472774400000,\n    true,\n    6,\n    '2016-09-23T08:00:00.000Z',\n    '2016-10-01 09:54:31+00:00',\n    '2016-10-01 16:54:31+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:30:03',\n    30.0612697,\n    31.2191059,\n    'driver_gps',\n    1472774400000,\n    true,\n    7,\n    '2016-09-23T08:00:00.000Z',\n    '2016-10-01 09:53:35+00:00',\n    '2016-10-01 16:53:35+00:00',\n    '2016-10-10'\n  ],\n  [\n    '2016-09-17 00:30:08',\n    30.0610977,\n    31.2194728,\n    'driver_gps',\n    1472774400000,\n    true,\n    null,\n    '2016-09-23T08:00:00.000Z',\n    null,\n    null,\n    null\n  ]\n];\n\nexport const testCsvDataSlice1 = testAllData.slice(0, 20);\nexport const testCsvDataSlice1Id = 'test-csv-data-1';\nexport const timeFieldDomainSlice1 = [1474070995000, 1474072115000];\n\nexport const testCsvDataSlice2 = testAllData.slice(5, testAllData.length);\nexport const timeFieldDomainSlice2 = [1474071301000, 1474072208000];\nexport const testCsvDataSlice2Id = 'test-csv-data-2';\n\n// the synced value intersect domain of both slices\nexport const syncTimeFilterValue = [1474071116000, 1474072188000];\n\nexport default data;\n\n// geojson layer from wktcsv\nexport const updatedLayerV2 = {\n  dataToFeature: [\n    null,\n    {\n      type: 'Feature',\n      properties: {\n        index: 1\n      },\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-74.158491, 40.835947],\n            [-74.157914, 40.83902],\n            [-74.148473, 40.834522]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        index: 2\n      },\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-74.31687, 40.656696],\n            [-74.319449, 40.658154],\n            [-74.31687, 40.656696]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        index: 3\n      },\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-74.387589, 40.632238],\n            [-74.387589, 40.632238]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        index: 4\n      },\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [-74.165995, 40.747969],\n            [-74.165987, 40.745199],\n            [-74.165995, 40.747969]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        index: 5\n      },\n      geometry: {\n        type: 'MultiPolygon',\n        coordinates: [\n          [\n            [\n              [-74.566993, 41.087294],\n              [-74.564908, 41.089339],\n              [-74.552353, 41.091647],\n              [-74.566993, 41.087294]\n            ]\n          ],\n          [\n            [\n              [-74.593264, 41.088526],\n              [-74.593264, 41.088526]\n            ]\n          ]\n        ]\n      }\n    },\n    {\n      type: 'Feature',\n      properties: {\n        index: 6\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.387589, 40.632238],\n          [-74.387589, 40.632238]\n        ]\n      }\n    }\n  ],\n  meta: {\n    featureTypes: {polygon: true, line: true},\n    bounds: [-74.593264, 40.632238, -74.148473, 41.091647],\n    fixedRadius: false\n  }\n};\n/* eslint-enable max-len */\n\nexport const numericRangesCsv = `smallest,small,negative,medium,large\n0.00000,0,-10,10,0\n0.00001,1,-20,20,200\n0.00005,1.5,-30,30,400\n0.00099,2,-40,40,800\n`;\n"
  },
  {
    "path": "test/fixtures/test-csv-object.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const rawData = `tooltip,num_array,str_array,bool_array\n\"{\"\"b\"\": 1}\",\"[1,2]\",\"[\"\"a\"\", \"\"b\"\", \"\"c\"\"]\",\"[true]\"\n\"{\"\"c\"\": 1}\",\"[2,3]\",\"[\"\"d\"\", \"\"e\"\"]\",\"[false, true]\"\n\"{\"\"a\"\": 2}\",\"[3,4]\",\"[\"\"f\"\", \"\"g\"\"]\",\"[true]\"\n\"{\"\"a\"\": 3}\",\"[4,5,6]\",\"[]\",\"[true]\"\n\"{\"\"a\"\": 4}\",\"[5,6]\",,\"[true]\"\n\"{\"\"d\"\": 1}\",\"[6,7]\",\"[\"\"h\"\"]\",\"[false]\"\n\"{\"\"e\"\": 1}\",\"[7,8]\",\"[\"\"i\"\", \"\"j\"\"]\",\"[true]\"\n\"{\"\"f\"\": 1}\",\"[8,9]\",\"[\"\"k\"\"]\",\"[true]\"`;\n\nexport const csvObjectDataId = 'test-csv-object';\n\nexport const objCsvFields = [\n  {\n    type: 'object',\n    name: 'tooltip',\n    id: 'tooltip',\n    displayName: 'tooltip',\n    format: '',\n    fieldIdx: 0,\n    analyzerType: 'OBJECT',\n    valueAccessor: values => values[0]\n  },\n  {\n    type: 'array',\n    name: 'num_array',\n    id: 'num_array',\n    displayName: 'num_array',\n    format: '',\n    fieldIdx: 1,\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[1]\n  },\n  {\n    type: 'array',\n    name: 'str_array',\n    id: 'str_array',\n    displayName: 'str_array',\n    format: '',\n    fieldIdx: 2,\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[2]\n  },\n  {\n    type: 'array',\n    name: 'bool_array',\n    id: 'bool_array',\n    displayName: 'bool_array',\n    format: '',\n    fieldIdx: 3,\n    analyzerType: 'ARRAY',\n    valueAccessor: values => values[3]\n  }\n];\n\nexport const objCsvRows = [\n  [{b: 1}, [1, 2], ['a', 'b', 'c'], [true]],\n  [{c: 1}, [2, 3], ['d', 'e'], [false, true]],\n  [{a: 2}, [3, 4], ['f', 'g'], [true]],\n  [{a: 3}, [4, 5, 6], [], [true]],\n  [{a: 4}, [5, 6], null, [true]],\n  [{d: 1}, [6, 7], ['h'], [false]],\n  [{e: 1}, [7, 8], ['i', 'j'], [true]],\n  [{f: 1}, [8, 9], ['k'], [true]]\n];\nexport default rawData;\n"
  },
  {
    "path": "test/fixtures/test-hex-id-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {KeplerGlLayers} from '@kepler.gl/layers';\nimport {createDataContainer, histogramFromDomain} from '@kepler.gl/utils';\nimport {DEFAULT_COLOR_UI, BINS} from '@kepler.gl/constants';\nconst {H3Layer} = KeplerGlLayers;\n\nexport default `hex_id,value\n89283082c2fffff,64\n8928308288fffff,73\n89283082c07ffff,65\n89283082817ffff,74\n89283082c3bffff,66\n89283082883ffff,76\n89283082c33ffff,43\n89283082c23ffff,40\n89283082887ffff,36\n89283082ca7ffff,27\n89283082cb3ffff,32\n89283082c0bffff,26\n89283082ca3ffff,19\n89283082dcfffff,18\n89283082d8fffff,1\n89283095347ffff,3\n89283095363ffff,2\n8928309537bffff,4\n89283082d93ffff,6\n89283082d73ffff,1\n8928309530bffff,1\n8928309532bffff,1`;\n\nexport const dataId = 'h3-hex-id';\n\nexport const expectedFields = [\n  {\n    analyzerType: 'H3',\n    displayName: 'hex_id',\n    fieldIdx: 0,\n    format: '',\n    id: 'hex_id',\n    name: 'hex_id',\n    type: 'h3',\n    valueAccessor: values => values[0]\n  },\n  {\n    analyzerType: 'INT',\n    displayName: 'value',\n    fieldIdx: 1,\n    format: '',\n    id: 'value',\n    name: 'value',\n    type: 'integer',\n    valueAccessor: values => values[1]\n  }\n];\n\nexport const hexIdDataConfig = {\n  dataId,\n  config: {\n    version: 'v1',\n    config: {\n      visState: {\n        filters: [\n          {\n            dataId: [dataId],\n            id: 'byjasfp0u',\n            name: 'value',\n            type: 'range',\n            value: [11.2, 28],\n            enlarged: false,\n            plotType: 'histogram',\n            yAxis: null\n          }\n        ],\n        layers: [\n          {\n            id: 'avlgol',\n            type: 'hexagonId',\n            config: {\n              dataId,\n              label: 'H3 Hexagon',\n              color: [241, 92, 23],\n              columns: {\n                hex_id: 'hex_id'\n              },\n              hidden: false,\n              isVisible: true,\n              visConfig: {\n                opacity: 0.8,\n                colorRange: {\n                  name: 'Global Warming',\n                  type: 'sequential',\n                  category: 'Uber',\n                  colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n                },\n                coverage: 1,\n                enable3d: false,\n                sizeRange: [0, 500],\n                coverageRange: [0, 1],\n                elevationScale: 5,\n                enableElevationZoomFactor: true,\n                fixedHeight: false\n              },\n              textLabel: [\n                {\n                  field: null,\n                  color: [255, 255, 255],\n                  size: 18,\n                  offset: [0, 0],\n                  anchor: 'start',\n                  alignment: 'center'\n                }\n              ]\n            },\n            visualChannels: {\n              colorField: {\n                name: 'value',\n                type: 'integer'\n              },\n              colorScale: 'quantile',\n              sizeField: null,\n              sizeScale: 'linear',\n              coverageField: null,\n              coverageScale: 'linear',\n              strokeColorDomain: [0, 1],\n              strokeColorField: 'something',\n              strokeColorScale: 'something'\n            }\n          }\n        ],\n        interactionConfig: {\n          tooltip: {\n            fieldsToShow: {\n              [dataId]: ['hex_id', 'value']\n            },\n            enabled: true\n          },\n          brush: {\n            size: 0.5,\n            enabled: false\n          },\n          geocoder: {\n            enabled: false\n          }\n        },\n        layerBlending: 'normal',\n        splitMaps: []\n      },\n      mapStyle: {\n        styleType: 'dark',\n        topLayerGroups: {},\n        visibleLayerGroups: {\n          label: true,\n          road: true,\n          border: false,\n          building: true,\n          water: true,\n          land: true,\n          '3d building': false\n        },\n        mapStyles: {}\n      }\n    }\n  }\n};\n\nexport const mergedFilters = [\n  {\n    dataId: [dataId],\n    id: 'byjasfp0u',\n    enabled: true,\n    name: ['value'],\n    type: 'range',\n    value: [11.2, 28],\n    view: 'side',\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldIdx: [1],\n    domain: [1, 76],\n    step: 0.01,\n    speed: 1,\n    fieldType: 'integer',\n    typeOptions: ['range'],\n    fixedDomain: false,\n    gpuChannel: [0],\n    gpu: true,\n    bins: {\n      'h3-hex-id': histogramFromDomain(\n        [1, 76],\n        [64, 73, 65, 74, 66, 76, 43, 40, 36, 27, 32, 26, 19, 18, 1, 3, 2, 4, 6, 1, 1, 1],\n        BINS\n      )\n    }\n  }\n];\n\nconst mergedFields = [\n  {\n    name: 'hex_id',\n    id: 'hex_id',\n    displayName: 'hex_id',\n    format: '',\n    fieldIdx: 0,\n    type: 'h3',\n    analyzerType: 'H3',\n    valueAccessor: values => values[0]\n  },\n  {\n    name: 'value',\n    id: 'value',\n    displayName: 'value',\n    format: '',\n    fieldIdx: 1,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[1],\n    filterProps: {\n      domain: [1, 76],\n      step: 0.01,\n      fieldType: 'integer',\n      value: [1, 76],\n      type: 'range',\n      typeOptions: ['range'],\n      gpu: true,\n      view: 'side'\n    }\n  }\n];\n\nexport const mergedH3Layer = new H3Layer({\n  id: 'avlgol'\n});\n\nmergedH3Layer.config = {\n  dataId,\n  label: 'H3 Hexagon',\n  color: [241, 92, 23],\n  columns: {\n    hex_id: {\n      value: 'hex_id',\n      fieldIdx: 0\n    }\n  },\n  hidden: false,\n  isVisible: true,\n  highlightColor: [252, 242, 26, 255],\n  isConfigActive: false,\n  colorField: {\n    name: 'value',\n    id: 'value',\n    displayName: 'value',\n    format: '',\n    fieldIdx: 1,\n    type: 'integer',\n    analyzerType: 'INT',\n    valueAccessor: values => values[1]\n  },\n  colorScale: 'quantile',\n  colorDomain: [18, 19, 26, 27],\n  sizeField: null,\n  sizeDomain: [0, 1],\n  sizeScale: 'linear',\n  coverageField: null,\n  coverageScale: 'linear',\n  coverageDomain: [0, 1],\n  strokeColorDomain: [0, 1],\n  strokeColorField: null,\n  strokeColorScale: 'quantile',\n  visConfig: {\n    opacity: 0.8,\n    colorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n    },\n    filled: true,\n    outline: false,\n    strokeColor: null,\n    strokeColorRange: {\n      name: 'Global Warming',\n      type: 'sequential',\n      category: 'Uber',\n      colors: ['#4C0035', '#880030', '#B72F15', '#D6610A', '#EF9100', '#FFC300']\n    },\n    strokeOpacity: 0.8,\n    thickness: 2,\n    coverage: 1,\n    sizeRange: [0, 500],\n    coverageRange: [0, 1],\n    enable3d: false,\n    elevationScale: 5,\n    enableElevationZoomFactor: true,\n    fixedHeight: false\n  },\n  textLabel: [\n    {\n      field: null,\n      color: [255, 255, 255],\n      size: 18,\n      offset: [0, 0],\n      anchor: 'start',\n      alignment: 'center',\n      outlineWidth: 0,\n      outlineColor: [255, 0, 0, 255],\n      background: false,\n      backgroundColor: [0, 0, 200, 255]\n    }\n  ],\n  colorUI: {\n    color: DEFAULT_COLOR_UI,\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  },\n  animation: {\n    enabled: false\n  }\n};\n\nconst expectedMergedDatasetData = [\n  ['89283082c2fffff', 64],\n  ['8928308288fffff', 73],\n  ['89283082c07ffff', 65],\n  ['89283082817ffff', 74],\n  ['89283082c3bffff', 66],\n  ['89283082883ffff', 76],\n  ['89283082c33ffff', 43],\n  ['89283082c23ffff', 40],\n  ['89283082887ffff', 36],\n  ['89283082ca7ffff', 27],\n  ['89283082cb3ffff', 32],\n  ['89283082c0bffff', 26],\n  ['89283082ca3ffff', 19],\n  ['89283082dcfffff', 18],\n  ['89283082d8fffff', 1],\n  ['89283095347ffff', 3],\n  ['89283095363ffff', 2],\n  ['8928309537bffff', 4],\n  ['89283082d93ffff', 6],\n  ['89283082d73ffff', 1],\n  ['8928309530bffff', 1],\n  ['8928309532bffff', 1]\n];\n\nconst dataContainer = createDataContainer(expectedMergedDatasetData, {fields: mergedFields});\nconst indices = dataContainer.getPlainIndex();\n\nexport const expectedMergedDataset = {\n  id: 'h3-hex-id',\n  label: 'new dataset',\n  color: 'dont test me',\n  metadata: {\n    id: 'h3-hex-id',\n    label: 'new dataset',\n    format: ''\n  },\n  type: '',\n  supportedFilterTypes: null,\n  disableDataOperation: false,\n  dataContainer,\n  allIndexes: indices,\n  filteredIndex: indices,\n  filteredIndexForDomain: [9, 11, 12, 13],\n  fieldPairs: [],\n  fields: mergedFields,\n  gpuFilter: {\n    filterRange: [\n      [10.2, 27],\n      [0, 0],\n      [0, 0],\n      [0, 0]\n    ],\n    filterValueUpdateTriggers: {\n      gpuFilter_0: {name: 'value', domain0: 1},\n      gpuFilter_1: null,\n      gpuFilter_2: null,\n      gpuFilter_3: null\n    },\n    filterValueAccessor: {\n      inputs: [{index: 6}],\n      result: [42, 0, 0, 0]\n    }\n  },\n  filterRecord: {\n    dynamicDomain: [mergedFilters[0]],\n    fixedDomain: [],\n    cpu: [],\n    gpu: [mergedFilters[0]]\n  },\n  changedFilters: {\n    dynamicDomain: {byjasfp0u: 'added'},\n    fixedDomain: null,\n    cpu: null,\n    gpu: {byjasfp0u: 'added'}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/test-layer-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport default `utc_timestamp,lat,lng,lat_1,lng_1,types,has_result,id,hex_id,trip_distance,icon,s2x\nNull,37.778564,-122.39096,37.769897,-122.41168,driver_analytics_0,FALSE,1,89283082c2fffff,1.59,accel,80858004\n2016-09-17 00:10:56,37.78824,-122.40894,37.798237,-122.41889,null,FALSE,null,8928308288fffff,2.38,add-person,8085800c\n2016-09-17 00:11:56,38.281445,NaN,37.76018,NaN,driver_analytics,FALSE,3,,2.83,alert,\n2016-09-17 00:12:58,37.79354,-122.40121,37.37006,-121.96353,driver_gps,FALSE,4,89283082817ffff,8.33,android,80858014\n2016-09-17 00:14:00,37.456535,-122.136795,37.418655,-122.149734,driver_analytics,1,5,89283082c3bffff,2.37,,8085801c\n2016-09-17 00:15:01,37.40066,-122.10239,37.787052,-122.41089,driver_analytics,FALSE,34,89283082883ffff,7.13,attach,80858024\n2016-09-17 00:16:03,Null,-122.42459,Null,-122.40495,driver_analytics,FALSE,222,89283082c33ffff,3.22,,80858034\n2016-09-17 00:17:05,37.879066,-122.26108,37.753033,-122.42929,driver_analytics,TRUE,345,89283082c23ffff,11,car-suv,8085803c\n2016-09-17 00:18:09,37.775578,-122.39363,37.749577,-122.41829,driver_analytics,FALSE,43,89283082887ffff,4.12,car-taxi,80858044\n`;\n\nexport const iconGeometry = {\n  accel: [1, 2, 3, 4],\n  'add-person': [1, 2, 3, 4],\n  alert: [1, 2, 3, 4],\n  android: [1, 2, 3, 4],\n  attach: [1, 2, 3, 4],\n  'car-suv': [1, 2, 3, 4],\n  'car-taxi': [1, 2, 3, 4]\n};\n\nexport const bounds = {\n  'lat-lng': [-122.42459, 37.40066, -122.10239, 38.281445],\n  'lat_1-lng_1': [-122.42929, 37.37006, -121.96353, 37.798237],\n  arc: [-122.42929, 37.37006, -121.96353, 38.281445],\n  h3: [-122.42488652083654, 37.75385576067885, -122.42073136602062, 37.771654262080524]\n};\n\nexport const fieldDomain = {\n  id: [1, 3, 4, 5, 34, 43, 222, 345]\n};\n"
  },
  {
    "path": "test/fixtures/test-trip-csv-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {DEFAULT_TEXT_LABEL, DEFAULT_COLOR_RANGE, DEFAULT_LAYER_OPACITY} from '@kepler.gl/constants';\n\nconst gps = `timestamp,location-lng,location-lat,ground-speed,heading,name,location-alt\n2014-08-01 00:00:23.000,90.2266981,27.6162803,0.22,0.0,Thuub,3217.0\n2014-08-01 00:10:07.000,,,0.27,140.9,Thuub,3212.3\n2014-08-01 00:20:07.000,90.2267115,27.6162102,0.27,110.1,Thuub,3209.1\n2014-08-01 00:30:07.000,90.2267409,27.6162514,0.32,0.0,Thuub,3206.2\n2014-08-01 00:40:07.000,90.2265545,27.6163481,0.46,0.0,Thuub,3231.3\n2014-08-01 00:50:07.000,90.2269491,27.6161884,0.51,0.0,Thuub,3178.8\n2014-08-01 01:00:07.000,90.2265336,27.6163122,0.3,99.61,Thuub,3257.2\n2014-08-01 01:10:07.000,90.2267568,27.6162647,0.33,0.0,Thuub,3192.5\n2014-08-01 01:20:07.000,90.2267296,27.6163003,0.43,0.0,Thuub,3199.2\n2014-08-01 06:42:41.000,82.1138274,27.4378974,15.95,64.23,Ngang Ka,966.3\n,82.1139649,27.4379582,14.9,62.91,Ngang Ka,969.9\n2014-08-01 06:42:43.000,82.1140881,27.4380211,13.67,58.33,Ngang Ka,972.8\n2014-08-01 06:42:44.000,82.1141907,27.4380927,12.54,48.5,Ngang Ka,974.6\n2014-08-01 06:42:45.000,82.114263,27.4381765,11.44,32.11,Ngang Ka,975.2\n2014-08-01 06:42:46.000,82.1143013,27.438272,11.35,13.76,Ngang Ka,975.8\n2014-08-01 06:42:47.000,82.11431,27.4383692,10.8,0.66,Ngang Ka,977.7\n2014-08-01 06:42:48.000,82.1142962,27.4384518,8.76,346.69,Ngang Ka,980.6\n2014-08-01 06:42:49.000,82.1142637,27.4385091,6.63,325.71,Ngang Ka,983.6\n2014-08-01 06:42:50.000,82.1142176,27.4385368,5.34,292.95,Ngang Ka,985.9`;\n\nexport const tripCsvDataInfo = {\n  id: 'trip_csv_data',\n  label: 'Trip Csv Data',\n  color: [100, 100, 100]\n};\n\n// test first 2\nexport const expectedCoordinates = [\n  [90.2266981, 27.6162803, 3217, '2014-08-01 00:00:23.000'],\n  [90.2267115, 27.6162102, 3209.1, '2014-08-01 00:20:07.000']\n];\nexpectedCoordinates[0].datumIndex = 0;\nexpectedCoordinates[0].datum = [\n  '2014-08-01 00:00:23.000',\n  90.2266981,\n  27.6162803,\n  0.22,\n  0,\n  'Thuub',\n  3217\n];\n\nexpectedCoordinates[1].datumIndex = 2;\nexpectedCoordinates[1].datum = [\n  '2014-08-01 00:20:07.000',\n  90.2267115,\n  27.6162102,\n  0.27,\n  110.1,\n  'Thuub',\n  3209.1\n];\n\nexport const expectedTripLayerConfig = {\n  id: 'dont_test_me',\n  type: 'trip',\n  config: {\n    dataId: 'trip_csv_data',\n    columnMode: 'table',\n    label: 'location',\n    color: [130, 154, 227],\n    columns: {\n      id: 'name',\n      lat: 'location-lat',\n      lng: 'location-lng',\n      timestamp: 'timestamp',\n      altitude: 'location-alt'\n    },\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      thickness: 2,\n      colorRange: DEFAULT_COLOR_RANGE,\n      trailLength: 180,\n      fadeTrail: true,\n      billboard: false,\n      sizeRange: [0, 10]\n    },\n    hidden: false,\n    textLabel: [DEFAULT_TEXT_LABEL]\n  },\n  visualChannels: {\n    colorField: {\n      name: 'ground-speed',\n      type: 'real'\n    },\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear'\n  }\n};\nexport default gps;\n"
  },
  {
    "path": "test/fixtures/test-trip-data.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const fields = [\n  {\n    name: 'tpep_pickup_datetime',\n    format: 'YYYY-M-D H:m:s',\n    type: 'timestamp'\n  },\n  {\n    name: 'tpep_dropoff_datetime',\n    format: 'YYYY-M-D H:m:s',\n    type: 'timestamp'\n  },\n  {\n    name: 'passenger_count',\n    format: '',\n    type: 'integer'\n  },\n  {name: 'trip_distance', format: '', type: 'real'},\n  {name: 'pickup_longitude', format: '', type: 'real'},\n  {name: 'pickup_latitude', format: '', type: 'real'},\n  {name: 'dropoff_longitude', format: '', type: 'real'},\n  {name: 'dropoff_latitude', format: '', type: 'real'},\n  {name: 'fare_amount', format: '', type: 'real'},\n  {name: 'is_completed', format: '', type: 'boolean'},\n  {name: 'fare_type', format: '', type: 'string'}\n];\n\nexport const rows = [\n  [\n    '2015-01-15 19:05:39',\n    '2015-01-15 19:23:42',\n    1,\n    1.59,\n    -73.99389648,\n    40.75011063,\n    -73.97478485,\n    40.75061798,\n    12,\n    true,\n    'orange peel'\n  ],\n  [\n    '2015-01-15 19:05:39',\n    '2015-01-15 19:32:00',\n    0,\n    2.38,\n    -73.97642517,\n    40.73981094,\n    -73.98397827,\n    40.75788879,\n    16.5,\n    false,\n    'banana peel'\n  ],\n  [\n    '2015-01-15 19:05:40',\n    '2015-01-15 19:21:00',\n    5,\n    2.83,\n    -73.96870422,\n    40.75424576,\n    -73.9551239,\n    40.7868576,\n    12.5,\n    false,\n    'apple tree'\n  ],\n  [\n    '2015-01-15 19:05:40',\n    '2015-01-15 19:28:18',\n    5,\n    8.33,\n    -73.86306,\n    40.76958084,\n    -73.95271301,\n    40.78578186,\n    26,\n    true,\n    'orange peel'\n  ],\n  [\n    '2015-01-15 19:05:41',\n    '2015-01-15 19:20:36',\n    1,\n    2.37,\n    -73.94554138,\n    40.77942276,\n    -73.98085022,\n    40.78608322,\n    11.5,\n    true,\n    'apple tree'\n  ],\n  [\n    '2015-01-15 19:05:41',\n    '2015-01-15 19:20:22',\n    2,\n    7.13,\n    -73.87445831,\n    40.7740097,\n    -73.95237732,\n    40.71858978,\n    21.5,\n    true,\n    'orange peel'\n  ]\n];\n\nexport const dataId = 'test_trip_data';\nexport const config = {\n  version: 'v1',\n  config: {\n    visState: {\n      layers: [\n        {\n          type: 'heatmap',\n          config: {\n            dataId,\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            hidden: false,\n            isVisible: true\n          }\n        },\n        {\n          type: 'point',\n          config: {\n            dataId,\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            hidden: false,\n            isVisible: true,\n            visConfig: {\n              colorRange: {\n                name: 'Ice And Fire 8',\n                type: 'diverging',\n                category: 'Uber',\n                colors: [\n                  '#7F1941',\n                  '#D50255',\n                  '#FEAD54',\n                  '#FEEDB1',\n                  '#E8FEB5',\n                  '#49E3CE',\n                  '#0198BD',\n                  '#007A99'\n                ],\n                reversed: true\n              },\n              strokeColorRange: {\n                name: 'Global Warming',\n                type: 'sequential',\n                category: 'Uber',\n                colors: ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']\n              }\n            },\n            textLabel: [\n              {\n                field: {\n                  name: 'pickup_latitude',\n                  type: 'real'\n                },\n                color: [255, 255, 255],\n                size: 18,\n                offset: [0, 0],\n                anchor: 'start',\n                alignment: 'center'\n              }\n            ]\n          },\n          visualChannels: {\n            colorField: {\n              name: 'trip_distance',\n              type: 'real'\n            },\n            sizeField: {\n              name: 'passenger_count',\n              type: 'integer'\n            }\n          }\n        },\n        {\n          type: 'arc',\n          config: {\n            dataId,\n            columns: {\n              lat0: 'pickup_latitude',\n              lng0: 'pickup_longitude',\n              lat1: 'dropoff_latitude',\n              lng1: 'dropoff_longitude'\n            },\n            hidden: false,\n            isVisible: true\n          }\n        },\n        {\n          id: '38f7j6',\n          type: 'hexagon',\n          config: {\n            dataId,\n            label: 'new layer',\n            columns: {\n              lat: 'pickup_latitude',\n              lng: 'pickup_longitude'\n            },\n            hidden: false,\n            isVisible: true,\n            visConfig: {\n              opacity: 0.76,\n              worldUnitSize: 0.3,\n              colorRange: {\n                name: 'ColorBrewer PuOr-10',\n                type: 'diverging',\n                category: 'ColorBrewer',\n                colors: [\n                  '#7f3b08',\n                  '#b35806',\n                  '#e08214',\n                  '#fdb863',\n                  '#fee0b6',\n                  '#d8daeb',\n                  '#b2abd2',\n                  '#8073ac',\n                  '#542788',\n                  '#2d004b'\n                ]\n              }\n            }\n          }\n        }\n      ],\n      filters: [\n        {\n          id: 'me',\n          dataId,\n          name: 'tpep_pickup_datetime',\n          type: 'timeRange',\n          enlarged: true\n        }\n      ],\n      interactionConfig: {\n        tooltip: {\n          fieldsToShow: {\n            [dataId]: ['tpep_pickup_datetime', 'tpep_dropoff_datetime']\n          },\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/tile-metadata.ts",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const MVT_METADATA = {\n  name: 'Mapbox Streets v8',\n  description: '',\n  boundingBox: [\n    [-180, -85],\n    [180, 85]\n  ] as [[number, number], [number, number]],\n  center: [0, 0, 0],\n  maxZoom: 16,\n  minZoom: 0,\n  layers: [\n    {\n      name: 'landuse',\n      description: '',\n      id: 'landuse',\n      minzoom: 5,\n      source: 'mapbox.mapbox-streets-v8',\n      source_name: 'Mapbox Streets v8',\n      fields: [\n        {\n          name: 'class',\n          type: 'string'\n        }\n      ]\n    }\n  ]\n};\n\nexport const PMTILES_METADATA = {\n  name: 'My Custom Tiles',\n  format: 'pmtiles' as const,\n  formatVersion: 3,\n  attributions: [],\n  tileMIMEType: 'application/vnd.mapbox-vector-tile' as const,\n  minZoom: 0,\n  maxZoom: 6,\n  boundingBox: [\n    [-150.1122219, -51.8952777],\n    [179.3577783, 69.6043747]\n  ] as [[number, number], [number, number]],\n  center: [14.0625, 50.7026397],\n  centerZoom: 6,\n  tilejson: {\n    name: 'My Custom Tiles',\n    description: 'My Custom Tiles Description',\n    generator: 'tippecanoe v2.37.1',\n    generatorOptions:\n      'tippecanoe -zg -o output.pmtiles --drop-densest-as-needed ne_10m_railroads.geojson',\n    boundingBox: [\n      [-150.112222, -51.895278],\n      [179.357778, 69.604375]\n    ],\n    center: null,\n    maxZoom: null,\n    minZoom: null,\n    layers: [\n      {\n        name: 'ne_10m_railroads',\n        id: 'ne_10m_railroads',\n        description: '',\n        minzoom: 0,\n        maxzoom: 6,\n        dominantGeometry: 'LineString',\n        fields: [\n          {\n            name: 'metric',\n            type: 'float32',\n            min: -1,\n            max: 10,\n            uniqueValueCount: 2,\n            values: [-1, 10]\n          },\n          {\n            name: 'continent',\n            type: 'utf8',\n            uniqueValueCount: 6,\n            values: ['Africa', 'Asia', 'Europe', 'North America', 'Oceania', 'South America']\n          }\n        ]\n      }\n    ]\n  }\n};\n"
  },
  {
    "path": "test/fixtures/trip-geojson.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const tripDataInfo = {\n  id: 'trip_data',\n  label: 'Trip Data'\n};\n\nconst tripGeoJson = {\n  type: 'FeatureCollection',\n  features: [\n    // 0\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', types: 'driver_analytics_0', trip_distance: 1.59},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.78966, 40.6429, 0, 1565578338],\n          [-73.7895, 40.64267, 0, 1565578346],\n          [-73.78923, 40.6424, 0, 1565578356],\n          [-73.78905, 40.64222, 0, 1565578364],\n          [-73.7889, 40.64209, 0, 1565578365],\n          [-73.78859, 40.64191, 0, 1565578367],\n          [-73.78836, 40.64181, 0, 1565578368],\n          [-73.78793, 40.6417, 0, 1565578371],\n          [-73.78756, 40.64163, 0, 1565578373],\n          [-73.78707, 40.64159, 0, 1565578375]\n        ]\n      }\n    },\n    // 1\n    {\n      type: 'Feature',\n      properties: {vendor: 'B', value: 4, trip_distance: 2.38},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.33223, 40.64375, 0, 1565578213],\n          [-74.33242, 40.64353, 0, 1565578217],\n          [-74.33001, 40.64222, 0, 1565578243],\n          [-74.32882, 40.64154, 0, 1565578256],\n          [-74.32682, 40.64039, 0, 1565578278],\n          [-74.32589, 40.63985, 0, 1565578288],\n          [-74.31725, 40.63485, 0, 1565578382],\n          [-74.31404, 40.63302, 0, 1565578417],\n          [-74.30616, 40.6283, 0, 1565578504]\n        ]\n      }\n    },\n    // 2\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', value: 7, types: 'driver_analytics', trip_distance: 2.83},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.87893, 40.64672, 0, 1565578095],\n          [-73.87969, 40.64624, 0, 1565578123],\n          [-73.87976, 40.64619, 0, 1565578125],\n          [-73.88064, 40.64697, 0, 1565578156],\n          [-73.88138, 40.64765, 0, 1565578181],\n          [-73.88234, 40.64849, 0, 1565578214],\n          [-73.883, 40.64911, 0, 1565578237],\n          [-73.88338, 40.64943, 0, 1565578250]\n        ]\n      }\n    },\n    // 3\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', value: 11, types: 'driver_gps', trip_distance: 8.33},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.18532, 40.69402, 0, 1565577961],\n          [-74.18499, 40.6945, 0, 1565577967],\n          [-74.18472, 40.69501, 0, 1565577973],\n          [-74.1846, 40.69533, 0, 1565577976],\n          [-74.18415, 40.69665, 0, 1565577991],\n          [-74.18391, 40.6975, 0, 1565578000],\n          [-74.18372, 40.6983, 0, 1565578008],\n          [-74.18364, 40.69882, 0, 1565578014],\n          [-74.18354, 40.6994, 0, 1565578018]\n        ]\n      }\n    },\n    // 4\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', value: 6, types: 'driver_analytics', trip_distance: 2.37},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.97301, 40.67601, 0, 1565578666],\n          [-73.97161, 40.67546, 0, 1565578694],\n          [-73.97142, 40.67575, 0, 1565578712],\n          [-73.9719, 40.67641, 0, 1565578732],\n          [-73.97244, 40.67716, 0, 1565578756],\n          [-73.97234, 40.67744, 0, 1565578778],\n          [-73.96917, 40.67678, 0, 1565578836]\n        ]\n      }\n    },\n    // 5\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', value: 1, types: 'driver_analytics', trip_distance: 7.13},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.03806, 40.74578, 0, 1565578252],\n          [-74.03893, 40.74289, 0, 1565578322],\n          [-74.03934, 40.74158, 0, 1565578354],\n          [-74.0384, 40.74142, 0, 1565578376],\n          [-74.03746, 40.74126, 0, 1565578399],\n          [-74.03824, 40.73872, 0, 1565578465],\n          [-74.03878, 40.73693, 0, 1565578511],\n          [-74.03893, 40.73693, 0, 1565578513],\n          [-74.03974, 40.73704, 0, 1565578524],\n          [-74.03979, 40.7369, 0, 1565578528],\n          [-74.03964, 40.73677, 0, 1565578531]\n        ]\n      }\n    },\n    // 6\n    {\n      type: 'Feature',\n      properties: {vendor: 'B', value: 7, types: 'driver_analytics', trip_distance: 3.22},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.79007, 40.64681, 0, 1565577697],\n          [-73.79092, 40.64643, 0, 1565577720],\n          [-73.79108, 40.64631, 0, 1565577726],\n          [-73.7912, 40.64613, 0, 1565577732],\n          [-73.79121, 40.64597, 0, 1565577737],\n          [-73.79116, 40.64581, 0, 1565577743],\n          [-73.79106, 40.6457, 0, 1565577747],\n          [-73.79094, 40.64557, 0, 1565577749],\n          [-73.79072, 40.64542, 0, 1565577751],\n          [-73.79057, 40.64534, 0, 1565577753]\n        ]\n      }\n    },\n    // 7\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', value: 15, types: 'driver_analytics', trip_distance: 11},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-73.87815, 40.88362, 0, 1565577773],\n          [-73.87772, 40.88345, 0, 1565577786],\n          [-73.87812, 40.88215, 0, 1565577814],\n          [-73.87467, 40.88156, 0, 1565577872],\n          [-73.87479, 40.88007, 0, 1565577904],\n          [-73.87494, 40.87994, 0, 1565577907],\n          [-73.87307, 40.88013, 0, 1565577938],\n          [-73.87191, 40.88023, 0, 1565577957],\n          [-73.87254, 40.87874, 0, 1565577974],\n          [-73.87159, 40.87864, 0, 1565577993],\n          [-73.87133, 40.8786, 0, 1565577999],\n          [-73.87046, 40.87838, 0, 1565578017],\n          [-73.87029, 40.87834, 0, 1565578020]\n        ]\n      }\n    },\n    // 8\n    {\n      type: 'Feature',\n      properties: {vendor: 'A', types: 'driver_analytics', trip_distance: 4.12},\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [-74.00227, 40.62526, 0, 1565577261],\n          [-74.00272, 40.62482, 0, 1565577277],\n          [-74.00052, 40.62348, 0, 1565577315],\n          [-74.01522, 40.60935, 0, 1565577685],\n          [-74.01697, 40.60769, 0, 1565577728],\n          [-74.01623, 40.60723, 0, 1565577750],\n          [-74.01549, 40.60684, 0, 1565577771],\n          [-74.0173, 40.60513, 0, 1565577796],\n          [-74.01746, 40.60496, 0, 1565577799],\n          [-74.01788, 40.60471, 0, 1565577802],\n          [-74.01826, 40.60455, 0, 1565577805],\n          [-74.01855, 40.6044, 0, 1565577807],\n          [-74.01874, 40.60428, 0, 1565577809]\n        ]\n      }\n    }\n  ]\n};\n\nconst fts = tripGeoJson.features;\nexport const dataToFeature = [\n  {\n    type: 'Feature',\n    properties: {...fts[0].properties, value: null, index: 0},\n    geometry: fts[0].geometry\n  },\n  {\n    type: 'Feature',\n    properties: {...fts[1].properties, types: null, index: 1},\n    geometry: fts[1].geometry\n  },\n  {type: 'Feature', properties: {...fts[2].properties, index: 2}, geometry: fts[2].geometry},\n  {type: 'Feature', properties: {...fts[3].properties, index: 3}, geometry: fts[3].geometry},\n  {type: 'Feature', properties: {...fts[4].properties, index: 4}, geometry: fts[4].geometry},\n  {type: 'Feature', properties: {...fts[5].properties, index: 5}, geometry: fts[5].geometry},\n  {type: 'Feature', properties: {...fts[6].properties, index: 6}, geometry: fts[6].geometry},\n  {type: 'Feature', properties: {...fts[7].properties, index: 7}, geometry: fts[7].geometry},\n  {\n    type: 'Feature',\n    properties: {...fts[8].properties, value: null, index: 8},\n    geometry: fts[8].geometry\n  }\n];\n\nexport const dataToTimeStamp = [\n  [\n    1565578338000, 1565578346000, 1565578356000, 1565578364000, 1565578365000, 1565578367000,\n    1565578368000, 1565578371000, 1565578373000, 1565578375000\n  ],\n  [\n    1565578213000, 1565578217000, 1565578243000, 1565578256000, 1565578278000, 1565578288000,\n    1565578382000, 1565578417000, 1565578504000\n  ],\n  [\n    1565578095000, 1565578123000, 1565578125000, 1565578156000, 1565578181000, 1565578214000,\n    1565578237000, 1565578250000\n  ],\n  [\n    1565577961000, 1565577967000, 1565577973000, 1565577976000, 1565577991000, 1565578000000,\n    1565578008000, 1565578014000, 1565578018000\n  ],\n  [\n    1565578666000, 1565578694000, 1565578712000, 1565578732000, 1565578756000, 1565578778000,\n    1565578836000\n  ],\n  [\n    1565578252000, 1565578322000, 1565578354000, 1565578376000, 1565578399000, 1565578465000,\n    1565578511000, 1565578513000, 1565578524000, 1565578528000, 1565578531000\n  ],\n  [\n    1565577697000, 1565577720000, 1565577726000, 1565577732000, 1565577737000, 1565577743000,\n    1565577747000, 1565577749000, 1565577751000, 1565577753000\n  ],\n  [\n    1565577773000, 1565577786000, 1565577814000, 1565577872000, 1565577904000, 1565577907000,\n    1565577938000, 1565577957000, 1565577974000, 1565577993000, 1565577999000, 1565578017000,\n    1565578020000\n  ],\n  [\n    1565577261000, 1565577277000, 1565577315000, 1565577685000, 1565577728000, 1565577750000,\n    1565577771000, 1565577796000, 1565577799000, 1565577802000, 1565577805000, 1565577807000,\n    1565577809000\n  ]\n];\nexport const timeStampDomain = [1565577261000, 1565578836000];\nexport const tripBounds = [-74.33242, 40.60428, -73.78707, 40.88362];\nexport const valueDomain = [1, 15];\n\nexport const TripLayerMeta = {\n  bounds: tripBounds,\n  featureTypes: {line: true},\n  getFeature: 'test separate'\n};\n\nexport default tripGeoJson;\n"
  },
  {
    "path": "test/helpers/comparison-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {FILTER_TYPES} from '@kepler.gl/constants';\nimport {toArray} from '@kepler.gl/common-utils';\nimport {KeplerTable} from '@kepler.gl/table';\n\nexport function cmpObjectKeys(t, expectedObj, actualObj, name) {\n  t.deepEqual(\n    Object.keys(actualObj)\n      .filter(key => actualObj[key] !== undefined)\n      .sort(),\n    Object.keys(expectedObj)\n      .filter(key => expectedObj[key] !== undefined)\n      .sort(),\n    `${name} should have same keys`\n  );\n}\nexport function cmpBins(t, expected, actual) {\n  t.equal(actual.length, expected.length, 'bins should have same length');\n  actual.forEach((b, i) => {\n    t.equal(b.count, expected[i].count, 'bins count should be same');\n    t.equal(b.x0, expected[i].x0, 'bins x0 should be same');\n    t.equal(b.x1, expected[i].x1, 'bins x1 should be same');\n  });\n}\nexport function cmpFilters(t, expectedFilter, actualFilter, opt = {}, idx = '', name = '') {\n  t.equal(typeof actualFilter, typeof expectedFilter, `${name}filters should be same type`);\n  if (Array.isArray(expectedFilter) && Array.isArray(actualFilter)) {\n    t.equal(\n      actualFilter.length,\n      expectedFilter.length,\n      `${name} should have same number of filters`\n    );\n    expectedFilter.forEach((f, i) => {\n      cmpFilters(t, expectedFilter[i], actualFilter[i], opt, String(i), name);\n    });\n  } else {\n    cmpObjectKeys(\n      t,\n      expectedFilter,\n      actualFilter,\n      `idx:${idx} | ${actualFilter.type} filter ${actualFilter.name}`\n    );\n\n    Object.keys(actualFilter).forEach(key => {\n      switch (key) {\n        case 'bins':\n          if (actualFilter.type === FILTER_TYPES.range) {\n            cmpObjectKeys(t, expectedFilter.bins, actualFilter.bins, 'filter.bins');\n            Object.keys(expectedFilter.bins).forEach(binDataId => {\n              // cmp bins\n              cmpBins(t, expectedFilter.bins[binDataId], actualFilter.bins[binDataId]);\n            });\n            // t.ok(actualFilter[key].length, `${name}.filter.${key} should not be empty`);\n          }\n          break;\n        case 'timeBins':\n          if (actualFilter.type === FILTER_TYPES.timeRange) {\n            cmpObjectKeys(t, expectedFilter.timeBins, actualFilter.timeBins, 'filter.timeBins');\n            Object.keys(expectedFilter.timeBins).forEach(binDataId => {\n              // cmp bins\n              cmpObjectKeys(\n                t,\n                expectedFilter.timeBins[binDataId],\n                actualFilter.timeBins[binDataId],\n                'filter.timeBins[binDataId'\n              );\n              Object.keys(expectedFilter.timeBins[binDataId]).forEach(interval => {\n                cmpBins(\n                  t,\n                  expectedFilter.timeBins[binDataId][interval],\n                  actualFilter.timeBins[binDataId][interval]\n                );\n              });\n            });\n            // t.ok(actualFilter[key].length, `${name}.filter.${key} should not be empty`);\n          }\n          break;\n        case 'yAxis':\n          // yAxis is a field\n          cmpField(\n            t,\n            expectedFilter[key],\n            actualFilter[key],\n            `${name}.filter.${key} should be the same`\n          );\n          break;\n        case 'lineChart': {\n          const {bins: actualBins, ...actualLineChart} = actualFilter[key];\n          const {bins: expectedBins, ...expectedLineChart} = expectedFilter[key];\n\n          t.deepEqual(\n            actualLineChart,\n            expectedLineChart,\n            `${name}.idx:${idx} |  ${actualFilter.type} filter ${actualFilter.name} ${key} should be correct`\n          );\n          cmpBins(t, actualBins, expectedBins);\n          break;\n        }\n        default:\n          if (key !== 'id' || opt.id) {\n            // test everything except id, which is auto generated\n            t.deepEqual(\n              actualFilter[key],\n              expectedFilter[key],\n              `${name}.idx:${idx} |  ${actualFilter.type} filter ${actualFilter.name} ${key} should be correct`\n            );\n          }\n      }\n    });\n  }\n}\n\nexport function cmpLayers(t, expectedLayer, actualLayer, opt = {}) {\n  t.ok(actualLayer.constructor === expectedLayer.constructor, 'layer should be same class');\n\n  // if is array of layers\n  if (Array.isArray(expectedLayer) && Array.isArray(actualLayer)) {\n    t.equal(actualLayer.length, expectedLayer.length, 'should have same number of layers');\n    expectedLayer.forEach((_, i) => {\n      cmpLayers(t, expectedLayer[i], actualLayer[i]);\n    });\n  } else {\n    cmpObjectKeys(t, expectedLayer.config, actualLayer.config, `layer.${actualLayer.id}`);\n\n    // eslint-disable-next-line complexity\n    Object.keys(expectedLayer.config).forEach(key => {\n      // test everything except color and id, which are auto generated\n      // also skip functions\n      switch (key) {\n        // list of fields\n        case 'textLabel': {\n          cmpObjectKeys(\n            t,\n            expectedLayer.config[key],\n            actualLayer.config[key],\n            `${actualLayer.type} layer config ${key} should be the same`\n          );\n\n          const actualTextLabel = actualLayer.config[key];\n          const expectedTextLabel = expectedLayer.config[key];\n\n          toArray(actualTextLabel).forEach((actualConfig, index) => {\n            const {field: actualField, ...actualRestConfigLayer} = actualConfig;\n            const {field: expectedField, ...expectedRestConfigLayer} = expectedTextLabel[index];\n            cmpField(\n              t,\n              expectedField,\n              actualField,\n              `${actualLayer.type} layer config ${key} should be correct`\n            );\n\n            t.deepEqual(\n              actualRestConfigLayer,\n              expectedRestConfigLayer,\n              `${actualLayer.type} layer config ${key} should be the same textLabel`\n            );\n          });\n          break;\n        }\n        // colorField is a field\n        case 'colorField':\n        case 'sizeField':\n        case 'heightField':\n        case 'columns':\n          break;\n        case 'strokeColorField':\n          cmpField(\n            t,\n            expectedLayer.config[key],\n            actualLayer.config[key],\n            `${actualLayer.type} layer config ${key} should be correct`\n          );\n          break;\n        default:\n          if (\n            (key !== 'id' || opt.id) &&\n            (key !== 'color' || opt.color) &&\n            typeof expectedLayer.config[key] !== 'function'\n          ) {\n            t.deepEqual(\n              actualLayer.config[key],\n              expectedLayer.config[key],\n              `${actualLayer.type} layer ${key} should be correct`\n            );\n          }\n      }\n    });\n  }\n}\n\nexport function cmpSavedLayers(t, expectedLayer, actualLayer, opt = {}, idx = '') {\n  // if is array of layers\n  if (Array.isArray(expectedLayer) && Array.isArray(actualLayer)) {\n    t.equal(actualLayer.length, expectedLayer.length, 'should have same number of layers');\n    expectedLayer.forEach((_, i) => {\n      cmpSavedLayers(t, expectedLayer[i], actualLayer[i], opt, String(i));\n    });\n  } else {\n    cmpObjectKeys(t, expectedLayer, actualLayer, `idx:${idx} | layer.${actualLayer.type}`);\n\n    t.deepEqual(\n      actualLayer,\n      expectedLayer,\n      `idx:${idx} | layer.${actualLayer.type} should be saved correctly`\n    );\n\n    Object.keys(expectedLayer).forEach(key => {\n      t.deepEqual(\n        actualLayer[key],\n        expectedLayer[key],\n        `idx:${idx} | ${actualLayer.type} layer ${key} should be correct`\n      );\n\n      if (key === 'config') {\n        cmpObjectKeys(\n          t,\n          expectedLayer.config,\n          actualLayer.config,\n          `idx:${idx} | layer.${actualLayer.type}`\n        );\n\n        Object.keys(actualLayer.config).forEach(ck => {\n          t.deepEqual(\n            actualLayer.config[ck],\n            expectedLayer.config[ck],\n            `idx:${idx} | ${actualLayer.type} layer.config ${ck} should be correct`\n          );\n        });\n      }\n    });\n  }\n}\n\nexport function cmpEffects(t, expectedEffect, actualEffect, opts = {}) {\n  if (Array.isArray(expectedEffect) && Array.isArray(actualEffect)) {\n    t.equal(actualEffect.length, expectedEffect.length, 'should have same number of effects');\n    expectedEffect.forEach((_, i) => {\n      cmpEffects(t, expectedEffect[i], actualEffect[i], opts);\n    });\n  } else {\n    if (!expectedEffect || !actualEffect) {\n      t.equal(actualEffect, expectedEffect, `Effects don't match`);\n      return;\n    }\n\n    if (opts.id) {\n      t.equal(actualEffect.id, expectedEffect.id, `${actualEffect.type} effect.id should be same`);\n    }\n\n    Object.keys(expectedEffect).forEach(key => {\n      // test everything except id, which can be auto generated\n      switch (key) {\n        case 'parameters':\n          t.deepEqual(\n            actualEffect[key],\n            expectedEffect[key],\n            `${actualEffect.type} effect.${key} values should be identical`\n          );\n          break;\n        default:\n          if (typeof expectedEffect[key] !== 'function') {\n            t.deepEqual(\n              actualEffect[key],\n              expectedEffect[key],\n              `${actualEffect.type} effect.${key} should be correct`\n            );\n          }\n      }\n    });\n  }\n}\n\nexport function cmpDatasetData(t, expectedDatasetData, actualDatasetData, datasetId = '') {\n  if (expectedDatasetData && actualDatasetData) {\n    cmpObjectKeys(\n      t,\n      expectedDatasetData,\n      actualDatasetData,\n      `dataset.${datasetId}.data should have same keys`\n    );\n    const {fields: actualFields, ...actualRestData} = actualDatasetData;\n    const {fields: expectedFields, ...expectedRestData} = expectedDatasetData;\n    cmpFields(t, expectedFields, actualFields, `dataset.${datasetId}.data should have same fields`);\n    t.deepEqual(\n      actualRestData,\n      expectedRestData,\n      `dataset.${datasetId}.data should have same props`\n    );\n  }\n}\n\nexport function cmpDatasets(t, expectedDatasets, actualDatasets) {\n  cmpObjectKeys(t, expectedDatasets, actualDatasets, 'datasets');\n\n  Object.keys(actualDatasets).forEach(dataId => {\n    cmpDataset(t, expectedDatasets[dataId], actualDatasets[dataId]);\n  });\n}\n\nexport function assertDatasetIsTable(t, dataset) {\n  t.ok(dataset instanceof KeplerTable, `${dataset.label || 'dataset'} should be a KeplerTable`);\n}\nexport function cmpColumns(t, expectedColumns, actualColumns, layerName) {\n  cmpObjectKeys(t, expectedColumns, actualColumns, `${layerName}.config.columns`);\n\n  Object.keys(expectedColumns).forEach(key => {\n    if (expectedColumns[key].validator) {\n      // validator is a function, we only test its existence\n      t.ok(\n        actualColumns[key].validator,\n        `${layerName}.config.columns.${key} should have validator`\n      );\n\n      // eslint-disable-next-line no-unused-vars\n      const {validater: _a, ...restPropsA} = actualColumns[key];\n      // eslint-disable-next-line no-unused-vars\n      const {validater: _e, ...restPropsE} = expectedColumns[key];\n\n      t.deepEqual(restPropsA, restPropsE, `${layerName}.config.columns.${key} should have correct`);\n    }\n  });\n}\n\nexport function cmpDataset(t, expectedDataset, actualDataset, opt = {}) {\n  assertDatasetIsTable(t, actualDataset);\n  cmpObjectKeys(t, expectedDataset, actualDataset, `dataset:${expectedDataset.id}`);\n\n  // test everything except auto generated color\n  Object.keys(actualDataset)\n    .filter(key => actualDataset[key] !== undefined)\n    .forEach(key => {\n      switch (key) {\n        case 'fields':\n          cmpFields(t, expectedDataset.fields, actualDataset.fields, expectedDataset.id);\n          break;\n        case 'gpuFilter':\n          // test gpuFilter props\n          cmpGpuFilterProp(\n            t,\n            expectedDataset.gpuFilter,\n            actualDataset.gpuFilter,\n            actualDataset.dataContainer\n          );\n          break;\n        case 'filterRecord':\n        case 'filterRecordCPU':\n          cmpObjectKeys(\n            t,\n            expectedDataset[key],\n            actualDataset[key],\n            `dataset.${expectedDataset.id}.${key}`\n          );\n          Object.keys(expectedDataset[key]).forEach(item => {\n            t.ok(\n              Array.isArray(expectedDataset[key][item]),\n              `dataset.${expectedDataset.id}[key].${item} should be an array`\n            );\n            // compare filter name\n            t.deepEqual(\n              actualDataset[key][item].map(f => f.name),\n              expectedDataset[key][item].map(f => f.name),\n              `dataset.${expectedDataset.id}.${key}.${item} should contain correct filter`\n            );\n          });\n          break;\n        default:\n          if (key !== 'color' || opt.color) {\n            t.deepEqual(\n              actualDataset[key],\n              expectedDataset[key],\n              `dataset.${expectedDataset.id}.${key} should be correct`\n            );\n          }\n      }\n    });\n}\n\nexport function cmpGpuFilterProp(t, expectedGpuFilter, actualGpuFilter, dataContainer) {\n  cmpObjectKeys(t, expectedGpuFilter, actualGpuFilter, 'gpu filter');\n\n  Object.keys(expectedGpuFilter).forEach(key => {\n    if (key === 'filterValueAccessor' && expectedGpuFilter.filterValueAccessor.inputs) {\n      const {inputs, result} = expectedGpuFilter.filterValueAccessor;\n      t.deepEqual(\n        actualGpuFilter.filterValueAccessor(dataContainer)()(...inputs),\n        result,\n        'getFilterValue should be correct'\n      );\n    } else if (\n      key === 'filterValueAccessor' &&\n      typeof expectedGpuFilter.filterValueAccessor === 'function'\n    ) {\n      // don't compare filter value accessor\n      return;\n    } else {\n      t.deepEqual(\n        actualGpuFilter[key],\n        expectedGpuFilter[key],\n        `getFilterValue.${key} should be correct`\n      );\n    }\n  });\n}\n\nexport function cmpInteraction(t, expectedInt, actualInt) {\n  cmpObjectKeys(t, expectedInt, actualInt, 'interaction');\n  Object.keys(actualInt).forEach(key => {\n    t.equal(\n      typeof actualInt[key],\n      typeof expectedInt[key],\n      `interaction.${key} should be same type`\n    );\n\n    if (\n      typeof actualInt[key] === 'object' &&\n      actualInt[key] !== null &&\n      !Array.isArray(actualInt[key])\n    ) {\n      cmpInteraction(t, expectedInt[key], actualInt[key]);\n    } else {\n      t.deepEqual(actualInt[key], expectedInt[key], `interaction.${key} should be correct`);\n    }\n  });\n}\n\nexport function cmpParsedAppConfigs(t, expectedConfig, actualConfig, {name = ''} = {}) {\n  t.deepEqual(actualConfig, expectedConfig, `${name} should be expected`);\n\n  Object.keys(actualConfig).forEach(key => {\n    if (key === 'visState') {\n      cmpObjectKeys(t, expectedConfig.visState, actualConfig.visState, 'visState');\n      // for visConfig go through each entry\n      cmpParsedAppConfigs(t, expectedConfig[key], actualConfig[key], {\n        name: key\n      });\n    } else if (key === 'layers') {\n      cmpSavedLayers(t, expectedConfig[key], actualConfig[key], {id: true});\n    } else if (key === 'filters') {\n      cmpFilters(t, expectedConfig[key], actualConfig[key], {id: true});\n    } else {\n      // for each reducer entry\n      t.deepEqual(actualConfig[key], expectedConfig[key], `${key} should be correct`);\n    }\n  });\n}\n\nexport function cmpFields(t, expected, actual, name, opt = {}) {\n  t.equal(expected.length, actual.length, `dataset.${name} should have same number of fields`);\n  actual.forEach((actualField, i) => {\n    cmpField(t, expected[i], actualField, `dataset.${name} fields ${actualField.name}`, opt);\n  });\n}\n\nexport function cmpField(t, expected, actual, name, opt = {}) {\n  if (expected && actual) {\n    cmpObjectKeys(t, expected, actual, name);\n\n    Object.keys(expected).forEach(k => {\n      if (k === 'filterProps') {\n        // compare filterProps\n        t.ok(typeof actual[k] === 'object', `${name} should have filterProps`);\n\n        if (actual[k]) {\n          cmpObjectKeys(t, expected[k], actual[k], `${name} filterProps`);\n          // compare filterProps key\n          Object.keys(expected[k]).forEach(key => {\n            if (key === 'histogram' || key === 'enlargedHistogram') {\n              t.ok(actual[k][key].length, `${name}.filterProps.${key} should not be empty`);\n            } else {\n              t.deepEqual(\n                actual[k][key],\n                expected[k][key],\n                `${name}.filterProps.${key} should be correct`\n              );\n            }\n          });\n        }\n      } else if (k === 'valueAccessor') {\n        // compare value accessor\n        if (opt.dataset) {\n          // test valueAccessor with first value\n\n          t.equal(\n            actual.valueAccessor(opt.dataset.dataContainer.rowAsArray(0)),\n            getFieldValueAccessor(\n              expected,\n              expected.fieldIdx\n            )(opt.dataset.dataContainer.rowAsArray(0)),\n            `${name} have correct valueAccessor function`\n          );\n        } else {\n          // assert it is a\n          t.ok(typeof actual[k] === 'function', `${name}.valueAccessor should be a function`);\n        }\n      } else {\n        t.deepEqual(actual[k], expected[k], `${name}.${k} should be the same`);\n      }\n    });\n  } else {\n    t.deepEqual(expected, actual, `${name} should be the same`);\n  }\n}\n"
  },
  {
    "path": "test/helpers/component-jest-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Provider} from 'react-redux';\nimport configureStore from 'redux-mock-store';\nimport {ThemeProvider} from 'styled-components';\nimport {IntlProvider} from 'react-intl';\n\nimport {render} from '@testing-library/react';\nimport {theme} from '@kepler.gl/styles';\nimport {messages} from '@kepler.gl/localization';\nimport {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers';\nimport {keplerGlInit} from '@kepler.gl/actions';\n\nconst mockStore = configureStore();\nconst initialCoreState = coreReducer(undefined, keplerGlInit({}));\n\nexport function renderWithTheme(component, options) {\n  return render(\n    <ThemeProvider theme={theme}>\n      <IntlProvider locale=\"en\" messages={messages}>\n        {component}\n      </IntlProvider>\n    </ThemeProvider>,\n    options\n  );\n}\n\nexport function renderWithStore(component, options) {\n  // assume instance reducer is already mounted\n  const store = mockStore({\n    keplerGl: {\n      foo: initialCoreState\n    }\n  });\n\n  return render(<Provider store={store}>{component}</Provider>, options);\n}\n\nexport function IntlWrapper({children, locale = 'en'}) {\n  return (\n    <IntlProvider locale={locale} messages={messages[locale]}>\n      {children}\n    </IntlProvider>\n  );\n}\n"
  },
  {
    "path": "test/helpers/component-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/*\n THIS FILE IS DEPRECATED: do not use enzyme for your tests but only jest.\n Use component-test-utils.js\n */\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport React from 'react';\nimport sinon from 'sinon';\nimport {mount} from 'enzyme';\nimport {theme} from '@kepler.gl/styles';\nimport {ThemeProvider} from 'styled-components';\nimport {IntlProvider} from 'react-intl';\nimport {messages} from '@kepler.gl/localization';\nimport {Typeahead} from '@kepler.gl/components';\n\nexport function mountWithTheme(node, options) {\n  return mount(node, {\n    wrappingComponent: ThemeProvider,\n    wrappingComponentProps: {theme},\n    ...options\n  });\n}\n\nexport const IntlWrapper = ({children, locale = 'en'}) => (\n  <IntlProvider locale={locale} messages={messages[locale]}>\n    {children}\n  </IntlProvider>\n);\n\nexport function mockHTMLElementClientSize(prop, value) {\n  return sinon.stub(HTMLElement.prototype, prop).get(() => value);\n}\n\nexport function clickItemSelector(itemSelector) {\n  itemSelector.find('.item-selector__dropdown').at(0).simulate('click');\n}\n\nexport function clickItemSelectList(itemSelector, itemIndex) {\n  itemSelector.find(Typeahead).at(0).find('.list__item').at(itemIndex).simulate('click');\n}\n\nexport function getItemSelectorListText(itemSelector, itemIndex) {\n  return itemSelector\n    .find(Typeahead)\n    .at(0)\n    .find('.list__item')\n    .at(itemIndex)\n    .find('.list__item__anchor')\n    .text();\n}\n"
  },
  {
    "path": "test/helpers/layer-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable enzyme-deprecation/no-mount */\nimport {LayerManager, MapView} from '@deck.gl/core';\nimport React from 'react';\nimport {gl} from '@deck.gl/test-utils';\nimport sinon from 'sinon';\nimport {mount} from 'enzyme';\nimport {console as Console} from 'global/window';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {\n  INITIAL_MAP_STATE,\n  INITIAL_VIS_STATE,\n  renderDeckGlLayer,\n  validateLayerWithData,\n  mapStateReducer as mapState,\n  visStateReducer,\n  keplerGlReducerCore\n} from '@kepler.gl/reducers';\nimport {getGpuFilterProps} from '@kepler.gl/table';\nimport {VisStateActions, addDataToMap} from '@kepler.gl/actions';\n\nimport {colorMaker, layerColors, LayerClasses as KeplerLayerClasses} from '@kepler.gl/layers';\nimport {processCsvData, processGeojson} from '@kepler.gl/processors';\nimport {applyActions, InitialState} from '../helpers/mock-state';\n// Fixtures\nimport csvData, {wktCsv} from '../fixtures/test-csv-data';\nimport testLayerData, {bounds, fieldDomain, iconGeometry} from '../fixtures/test-layer-data';\nimport {geojsonData} from '../fixtures/geojson';\nimport tripGeoJson from '../fixtures/trip-geojson';\nimport {IntlWrapper} from './component-utils';\n\nimport {logStep} from '../../scripts/log';\n\nexport const dataId = '0dj3h';\nexport const timeFilter = [{name: 'utc_timestamp', value: [1474071095000, 1474071608000]}];\n\nexport function testCreateLayer(t, LayerClass, props = {}) {\n  let layer;\n\n  t.doesNotThrow(() => {\n    layer = new LayerClass(props);\n    t.ok(layer instanceof LayerClass, `${layer.type} layer created`);\n  }, `creating layer should not fail`);\n\n  return layer;\n}\n\nexport function testCreateLayerFromConfig(t, tc, LayerClasses = KeplerLayerClasses) {\n  const {datasets, layer: layerConfig = {}} = tc;\n  let layer;\n\n  t.doesNotThrow(() => {\n    layer = validateLayerWithData(datasets[layerConfig.config.dataId], layerConfig, LayerClasses);\n    t.ok(layer instanceof LayerClasses[layerConfig.type], `${layerConfig.type} layer created`);\n    layer.updateLayerDomain(datasets);\n    if (tc.afterLayerInitialized) {\n      tc.afterLayerInitialized(layer);\n    }\n  }, `create a ${layerConfig.type} layer from config should not fail`);\n\n  return layer;\n}\n\nexport function testFormatLayerData(t, layer, datasets) {\n  let result;\n\n  t.doesNotThrow(() => {\n    result = layer.formatLayerData(datasets);\n    t.ok(result, 'has layer data');\n    t.ok(layer, 'has updated layer');\n  }, `format ${layer.type} layerData should not fail`);\n\n  return result;\n}\n\nexport function testCreateCases(t, LayerClass, testCases) {\n  testCases.forEach(tc => {\n    const layer = testCreateLayer(t, LayerClass, tc.props);\n    if (layer) {\n      t.ok(typeof layer.type === 'string', 'layer type should be string');\n      t.ok(typeof layer.id === 'string', 'layer id should be string');\n      t.doesNotThrow(() => {\n        mount(<layer.layerIcon />);\n      }, 'layer icon should be mountable');\n\n      if (layer.layerInfoModal) {\n        if (layer.layerInfoModal.template) {\n          t.doesNotThrow(() => {\n            mount(\n              <IntlWrapper>\n                <layer.layerInfoModal.template />\n              </IntlWrapper>\n            );\n          }, 'layer info modal should be mountable');\n        } else if (Object.keys(layer.layerInfoModal).length) {\n          // layerInfoModal is based on columnMode\n          Object.keys(layer.layerInfoModal).forEach(mode => {\n            const Template = layer.layerInfoModal[mode].template;\n            t.doesNotThrow(() => {\n              mount(\n                <IntlWrapper>\n                  <Template />\n                </IntlWrapper>\n              );\n            }, 'layer info modal should be mountable');\n          });\n        }\n      }\n    }\n    if (layer && tc.test) {\n      tc.test(layer);\n    }\n  });\n}\n\nexport function testFormatLayerDataCases(t, LayerClass, testCases) {\n  testCases.forEach(tc => {\n    logStep(`---> Test Format Layer Data ${tc.name}`);\n\n    // use provided LayerClass if present, otherwise default to KeplerLayerClasses\n    const layer = LayerClass\n      ? testCreateLayerFromConfig(t, tc, {[tc.layer.type]: LayerClass})\n      : testCreateLayerFromConfig(t, tc);\n    let updatedLayer = layer;\n\n    // if provided updates\n    if (layer && tc.updates) {\n      const applyUpdates = Array.isArray(tc.updates) ? tc.updates : [tc.updates];\n\n      // apply 1 or multiple updates\n      applyUpdates.forEach(update => {\n        const updated = testLayerUpdate(t, updatedLayer, update.method, update.args);\n        updatedLayer = updated.layer;\n      });\n    }\n\n    if (updatedLayer) {\n      const result = testFormatLayerData(t, updatedLayer, tc.datasets);\n      t.ok(result, `${tc.name} Should have format layer data result`);\n      if (result && tc.assert) {\n        tc.assert({layerData: result, layer: updatedLayer});\n      }\n    }\n  });\n}\n\nexport function testRenderLayerCases(t, LayerClass, testCases) {\n  testCases.forEach(tc => {\n    logStep(`---> Test Render Layer ${tc.name}`);\n\n    // use provided LayerClass if present, otherwise default to KeplerLayerClasses\n    const layer = LayerClass\n      ? testCreateLayerFromConfig(t, tc, {[tc.layer.type]: LayerClass})\n      : testCreateLayerFromConfig(t, tc);\n\n    let result;\n    let deckLayers;\n    let viewport = INITIAL_MAP_STATE;\n\n    if (layer) {\n      result = testFormatLayerData(t, layer, tc.datasets);\n    }\n\n    if (result) {\n      t.doesNotThrow(() => {\n        deckLayers = layer.renderLayer({\n          data: result,\n          idx: 0,\n          layerInteraction: {},\n          mapState: INITIAL_MAP_STATE,\n          gpuFilter:\n            tc.datasets[layer.config.dataId].gpuFilter ||\n            getGpuFilterProps(\n              [],\n              layer.config.dataId,\n              tc.datasets[layer.config.dataId].fields,\n              layer.gpuFilter\n            ),\n          interactionConfig: INITIAL_VIS_STATE.interactionConfig,\n          visible: true,\n          layerCallbacks: {},\n          ...(tc.renderArgs || {})\n        });\n\n        if (tc.renderArgs && tc.renderArgs.viewport) {\n          viewport = {...viewport, ...tc.renderArgs.viewport};\n        }\n      }, `${layer.type}.renderLayer should not fail`);\n    }\n\n    if (deckLayers) {\n      const initialDeckLayers = testRenderDeckLayer(t, layer.type, deckLayers, {viewport});\n\n      if (tc.assert) {\n        tc.assert(initialDeckLayers, layer, result);\n      }\n    }\n  });\n}\n\nexport function testRenderDeckLayer(t, layerType, deckLayers, {viewport, layerManager}) {\n  let deckLayerManager = layerManager;\n  if (!layerManager) {\n    const testViewport = new MapView().makeViewport({\n      width: viewport.width,\n      height: viewport.height,\n      viewState: viewport\n    });\n    deckLayerManager = new LayerManager(gl, {viewport: testViewport});\n  }\n\n  const spy = sinon.spy(Console, 'error');\n\n  t.doesNotThrow(\n    () => deckLayerManager.setLayers(Array.isArray(deckLayers) ? deckLayers : [deckLayers]),\n    `initialization of ${layerType} layer render should not fail`\n  );\n\n  // listen on console.error in editShader, fail the test if any error is logged\n  t.deepEqual(spy.args, [], 'should not call console.error during layer initialization');\n\n  spy.restore();\n  return deckLayerManager.getLayers();\n}\n\nexport function testLayerUpdate(t, layer, updateMethod, updateArgs) {\n  let result;\n  t.doesNotThrow(() => {\n    result = layer[updateMethod](...updateArgs);\n    t.ok(layer, `layer ${updateMethod} called`);\n  }, 'update layer should not fail');\n\n  return {result, layer};\n}\n\nfunction testAttributeUpdate(t, attributeValues, layer, shouldUpdate) {\n  Object.keys(attributeValues).forEach(key => {\n    const newValue = layer.state.attributeManager.attributes[key].value;\n    if (shouldUpdate[key]) {\n      t.notDeepEqual(attributeValues[key], newValue, `attribute${key} should update`);\n    } else {\n      t.deepEqual(attributeValues[key], newValue, `attribute${key} should not update`);\n    }\n\n    attributeValues[key] = [...newValue];\n  });\n}\n\nexport function renderLayerByState(t, state, layerManager) {\n  const layerCallbacks = () => {};\n  const layer = state.visState.layers[0];\n  const data = state.visState.layerData[0];\n\n  let layerOverlay;\n  t.doesNotThrow(() => {\n    layerOverlay = renderDeckGlLayer(\n      {\n        ...state.visState,\n        layer,\n        data,\n        mapState: state.mapState\n      },\n      layerCallbacks,\n      0\n    );\n  }, `${layer.type}.renderLayer should not fail`);\n  testRenderDeckLayer(t, layer.type, layerOverlay, {layerManager});\n\n  return {\n    layerManager,\n    deckGlLayers: layerManager.getLayers()\n  };\n}\n\nexport function testUpdateLayer(t, {layerConfig, shouldUpdate}) {\n  const initialState = cloneDeep(InitialState);\n  const filter0 = {\n    dataId,\n    id: 'me',\n    type: 'timeRange',\n    ...timeFilter[0]\n  };\n  const filter1 = {\n    dataId,\n    id: 'me2',\n    type: 'range',\n    name: 'trip_distance',\n    value: [3, 8.33]\n  };\n\n  const addDataToMapPayload = {\n    datasets: {\n      info: {id: dataId},\n      data: processCsvData(testLayerData)\n    },\n    config: {\n      version: 'v1',\n      config: {\n        visState: {\n          layers: [layerConfig],\n          filters: [filter0, filter1]\n        }\n      }\n    }\n  };\n  const stateWithLayer = applyActions(keplerGlReducerCore, initialState, [\n    {\n      action: addDataToMap,\n      payload: [addDataToMapPayload]\n    }\n  ]);\n\n  const layer = stateWithLayer.visState.layers[0];\n  t.ok(layer, 'should create layer');\n\n  const testViewport = new MapView().makeViewport({\n    width: mapState.width,\n    height: mapState.height,\n    viewState: mapState\n  });\n\n  const layerManager = new LayerManager(gl, {viewport: testViewport});\n\n  let rendered = renderLayerByState(t, stateWithLayer, layerManager);\n  const attributeValues = Object.keys(\n    rendered.deckGlLayers[0].state.attributeManager.attributes\n  ).reduce(\n    (accu, key) => ({\n      ...accu,\n      [key]: [...rendered.deckGlLayers[0].state.attributeManager.attributes[key].value]\n    }),\n    {}\n  );\n\n  // update Gpu Filter\n  let stateWithFilterUpdate = keplerGlReducerCore(\n    stateWithLayer,\n    VisStateActions.setFilter(0, 'value', [filter0.value[0] + 1, filter0.value[1]], 0)\n  );\n\n  rendered = renderLayerByState(t, stateWithFilterUpdate, rendered.layerManager);\n  logStep(`---> test: set GPU filter value`);\n  testAttributeUpdate(t, attributeValues, rendered.deckGlLayers[0], shouldUpdate.gpuFilter);\n\n  // update dynamic domain gpu Filter\n  stateWithFilterUpdate = keplerGlReducerCore(\n    stateWithFilterUpdate,\n    VisStateActions.setFilter(1, 'value', [filter1.value[0] + 0.5, filter1.value[1]], 0)\n  );\n  rendered = renderLayerByState(t, stateWithFilterUpdate, rendered.layerManager);\n  logStep(`---> test: set dynamic domain GPU filter value`);\n  testAttributeUpdate(t, attributeValues, rendered.deckGlLayers[0], shouldUpdate.dynamicGpuFilter);\n}\n\n/**\n * Predict which color maker value would be next, allow skip couple\n * Should be used right before calling function to be tested\n * @param {Number} skip\n */\nexport function getNextColorMakerValue(num = 1) {\n  const results = [];\n  const colorNow = colorMaker.next().value;\n  const index = layerColors.findIndex(c => c === colorNow);\n\n  for (let n = 0; n < num; n++) {\n    const next = index + n + 1;\n    const nextIndex = next < layerColors.length ? next : next - layerColors.length;\n    results.push(layerColors[nextIndex]);\n  }\n\n  return results;\n}\n\nfunction addFilterToData(data, id, filters) {\n  const filterActions = filters.reduce(\n    (accu, f, i) => [\n      ...accu,\n      // add filter\n      {action: VisStateActions.addFilter, payload: [id]},\n      // set filter name\n      {action: VisStateActions.setFilter, payload: [i, 'name', f.name]},\n      // set filter value\n      {\n        action: VisStateActions.setFilter,\n        payload: [i, 'value', f.value]\n      }\n    ],\n    []\n  );\n\n  return applyActions(visStateReducer, INITIAL_VIS_STATE, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        {\n          info: {id},\n          data\n        }\n      ]\n    },\n    ...filterActions\n  ]);\n}\n\nexport const {rows, fields} = processCsvData(csvData);\nexport const {rows: testRows, fields: testFields} = processCsvData(testLayerData);\n\nexport {\n  dataId as tripDataId,\n  fields as tripFields,\n  rows as tripRows\n} from '../fixtures/test-trip-data';\n\n/*\n * point, arc, hex, csv dataset from with gpu time filter\n */\n\nconst stateWithTimeFilter = addFilterToData(\n  {fields: testFields, rows: testRows},\n  dataId,\n  timeFilter\n);\n\nexport const preparedDataset = stateWithTimeFilter.datasets[dataId];\nexport const gpuTimeFilter = stateWithTimeFilter.filters[0];\nexport const preparedFilterDomain0 = gpuTimeFilter.domain[0];\n\nexport const pointLayerMeta = {\n  bounds: bounds['lat-lng']\n};\nexport const arcLayerMeta = {\n  bounds: bounds.arc\n};\nexport const hexagonIdLayerMeta = {\n  bounds: bounds.h3\n};\nexport {iconGeometry, fieldDomain};\n\n/*\n * Geo csv dataset from wkt with gpu filter\n */\nexport const {rows: geoCsvRows, fields: geoCsvFields} = processCsvData(wktCsv);\n\nconst stateWithGeoFilter = addFilterToData({fields: geoCsvFields, rows: geoCsvRows}, dataId, [\n  {name: 'm_rate', value: [7, 10]}\n]);\n\nexport const preparedGeoDataset = stateWithGeoFilter.datasets[dataId];\nexport const preparedGeoDatasetFilter = stateWithGeoFilter.filters[0];\n\n/*\n * GeoJson dataset with gpu filter\n */\nexport const {rows: geoJsonRows, fields: geoJsonFields} = processGeojson(cloneDeep(geojsonData));\n\nconst stateWithGeojsonFilter = addFilterToData({fields: geoJsonFields, rows: geoJsonRows}, dataId, [\n  {name: 'TRIPS', value: [4, 12]}\n]);\n\nexport const prepareGeojsonDataset = stateWithGeojsonFilter.datasets[dataId];\nexport const prepareGeojsonDatasetFilter = stateWithGeojsonFilter.filters[0];\nexport const geoFilterDomain0 = preparedGeoDatasetFilter.domain[0];\nexport const geojsonFilterDomain0 = prepareGeojsonDatasetFilter.domain[0];\n\n/*\n * trip GeoJson dataset with gpu filter\n */\nexport const {rows: tripGeoRows, fields: tripGeoFields} = processGeojson(cloneDeep(tripGeoJson));\n\nconst stateWithTripGeojsonFilter = addFilterToData(\n  {fields: tripGeoFields, rows: tripGeoRows},\n  dataId,\n  [{name: 'value', value: [4, 12]}]\n);\n\nexport const prepareTripGeoDataset = stateWithTripGeojsonFilter.datasets[dataId];\nexport const prepareTripGeoDatasetFilter = stateWithTripGeojsonFilter.filters[0];\nexport const valueFilterDomain0 = prepareTripGeoDatasetFilter.domain[0];\nexport const {animationConfig} = stateWithTripGeojsonFilter;\n"
  },
  {
    "path": "test/helpers/mock-map-styles.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {BASE_MAP_COLOR_MODES, DEFAULT_LAYER_GROUPS} from '@kepler.gl/constants';\n\n/** @type {import('@kepler.gl/reducers').BaseMapStyle} */\nexport const MOCK_MAP_STYLE = {\n  id: 'dark-matter',\n  label: 'DarkMatter',\n  url: 'mapbox://styles/xxxxx/abcdefg',\n  icon: 'https://my.icon.net/kepler.gl/test/taro.png',\n  layerGroups: DEFAULT_LAYER_GROUPS,\n  colorMode: BASE_MAP_COLOR_MODES.DARK,\n  complimentaryStyleId: 'positron',\n  style: {\n    version: 8,\n    name: 'Dark Matter',\n    metadata: {'maputnik:renderer': 'mbgljs'},\n    sources: {\n      carto: {\n        type: 'vector',\n        url: 'https://tiles.basemaps.cartocdn.com/vector/carto.streets/v1/tiles.json'\n      }\n    },\n    sprite: 'https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/sprite',\n    glyphs: 'https://tiles.basemaps.cartocdn.com/fonts/{fontstack}/{range}.pbf',\n    layers: [\n      {\n        id: 'background',\n        type: 'background',\n        layout: {visibility: 'visible'},\n        paint: {'background-color': '#0e0e0e', 'background-opacity': 1}\n      },\n      {\n        id: 'road_path',\n        type: 'line',\n        source: 'carto',\n        'source-layer': 'transportation'\n      },\n      {\n        id: 'roadname_minor',\n        minzoom: 13,\n        source: 'carto',\n        type: 'symbol',\n        'source-layer': 'transportation_name'\n      },\n      {\n        type: 'symbol',\n        source: 'composite',\n        id: 'country-label-sm',\n        'source-layer': 'country_label'\n      }\n    ],\n    id: 'voyager',\n    owner: 'Carto'\n  }\n};\n\n/** @type {import('@kepler.gl/reducers').BaseMapStyle} */\nexport const MOCK_MAP_STYLE_LIGHT = {\n  id: 'positron',\n  label: 'Positron',\n  url: 'mapbox://styles/xxxxx/hijklmn',\n  icon: 'https://my.icon.net/kepler.gl/test/blue.png',\n  layerGroups: DEFAULT_LAYER_GROUPS,\n  colorMode: BASE_MAP_COLOR_MODES.LIGHT,\n  complimentaryStyleId: 'dark-matter',\n  style: {\n    version: 8,\n    name: 'Positron',\n    metadata: {},\n    sources: {\n      carto: {\n        type: 'vector',\n        url: 'https://tiles.basemaps.cartocdn.com/vector/carto.streets/v1/tiles.json'\n      }\n    },\n    sprite: 'https://tiles.basemaps.cartocdn.com/gl/positron-gl-style/sprite',\n    glyphs: 'https://tiles.basemaps.cartocdn.com/fonts/{fontstack}/{range}.pbf',\n    layers: [\n      {\n        id: 'background',\n        type: 'background',\n        layout: {visibility: 'visible'},\n        paint: {'background-color': '#ff0000', 'background-opacity': 1}\n      },\n\n      {\n        id: 'road_path',\n        type: 'line',\n        source: 'carto',\n        'source-layer': 'transportation'\n      },\n      {\n        id: 'roadname_minor',\n        minzoom: 13,\n        source: 'carto',\n        type: 'symbol',\n        'source-layer': 'transportation_name'\n      },\n      {\n        type: 'symbol',\n        source: 'composite',\n        id: 'admin-label-lg',\n        'source-layer': 'country_label'\n      }\n    ],\n    id: 'voyager',\n    owner: 'Carto'\n  }\n};\n\n/** @type {import('@kepler.gl/reducers').MapStyles} */\nexport const MOCK_MAP_STYLES = {\n  [MOCK_MAP_STYLE.id]: MOCK_MAP_STYLE,\n  [MOCK_MAP_STYLE_LIGHT.id]: MOCK_MAP_STYLE_LIGHT\n};\n"
  },
  {
    "path": "test/helpers/mock-provider.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// SPDX-License-Identifier: MIT\n// Copyriht contributors to the kepler.gl project\n\nimport React from 'react';\n\nconst MockIcon = () => <div id=\"provider-icon\" />;\n\nexport default class MockProvider {\n  constructor() {\n    // All cloud-providers providers must implement the following properties\n    this.name = 'taro';\n    this.displayName = 'Taro';\n    this.icon = MockIcon;\n    this.thumbnail = {width: 100, height: 60};\n  }\n  login(onSuccess) {\n    onSuccess();\n    return;\n  }\n  logout(onSuccess) {\n    onSuccess();\n    return;\n  }\n  hasPrivateStorage() {\n    return true;\n  }\n  hasSharingUrl() {\n    return true;\n  }\n  getAccessToken() {\n    return true;\n  }\n  async uploadMap() {\n    const promise = new Promise(resolve => {\n      () => resolve('done!')();\n    });\n    await promise;\n  }\n}\n"
  },
  {
    "path": "test/helpers/mock-state-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {drainTasksForTesting, succeedTaskWithValues} from 'react-palm/tasks';\n\nimport {KeplerTable} from '@kepler.gl/table';\n\n/**\n * Applies actions one by one using the reducer.\n * After each action drain Tasks and automarically resolve tasks\n * of type CREATE_TABLE_TASK in order to create datasets.\n * @param {Reducer} reducer A reducer to use.\n * @param {State} initialState Initial state.\n * @param {Action | Action[]}actions An array of actions.\n * @returns\n */\nexport function applyActions(reducer, initialState, actions) {\n  const actionQ = Array.isArray(actions) ? actions : [actions];\n\n  // remove any existing tasks before actions\n  drainTasksForTesting();\n\n  let updatedState = actionQ.reduce((updatedState, {action, payload}) => {\n    let newState = reducer(updatedState, action(...payload));\n    const tasks = drainTasksForTesting();\n    newState = applyCreateTableTasks(tasks, reducer, newState);\n    return newState;\n  }, initialState);\n\n  return updatedState;\n}\n\n/**\n * Execute existing tasts and mock CREATE_TABLE_TASK with success.\n * @param {VisStateReducer} reducer\n * @param {VisState} initialState\n * @returns\n */\nexport function applyExistingDatasetTasks(reducer, initialState) {\n  const tasks = drainTasksForTesting();\n  return applyCreateTableTasks(tasks, reducer, initialState);\n}\n\n/**\n * Execute tasks and mock CREATE_TABLE_TASK with success.\n * @param {Task[]} tasks\n * @param {Reducer} reducer\n * @param {VisState} initialState\n * @returns\n */\nexport const applyCreateTableTasks = (tasks, reducer, initialState) => {\n  return tasks.reduce((updatedState, task) => {\n    if (!task.label.includes('CREATE_TABLE_TASK')) return updatedState;\n    const tables = task.payload.map(payload => mockCreateNewDataEntry(payload));\n    return reducer(updatedState, succeedTaskWithValues(task, tables));\n  }, initialState);\n};\n\n/**\n * Mock instant sync result of createNewDataEntry.\n */\nconst mockCreateNewDataEntry = ({info, color, opts, data}) => {\n  const table = new KeplerTable({info, color, ...opts});\n  table.importData({data});\n  return table;\n};\n"
  },
  {
    "path": "test/helpers/mock-state.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// @ts-nocheck\n\nimport cloneDeep from 'lodash/cloneDeep';\nimport {colorPaletteToColorRange} from '@kepler.gl/constants';\nimport {\n  getInitialInputStyle,\n  keplerGlReducerCore as keplerGlReducer,\n  syncTimeFilterWithLayerTimelineUpdater\n} from '@kepler.gl/reducers';\nimport {\n  KEPLER_COLOR_PALETTES,\n  COMPARE_TYPES,\n  DEFAULT_LAYER_OPACITY,\n  DEFAULT_TEXT_LABEL,\n  DEFAULT_COLOR_RANGE,\n  DEFAULT_HIGHLIGHT_COLOR,\n  DEFAULT_LAYER_LABEL\n} from '@kepler.gl/constants';\nimport {DEFAULT_KEPLER_GL_PROPS, getUpdateVisDataPayload} from '@kepler.gl/components';\nimport {\n  addDataToMap,\n  VisStateActions,\n  MapStateActions,\n  MapStyleActions,\n  UIStateActions,\n  ProviderActions\n} from '@kepler.gl/actions';\n\n// fixtures\nimport {\n  dataId as csvDataId,\n  testFields,\n  testAllData,\n  testCsvDataSlice1,\n  testCsvDataSlice1Id,\n  testCsvDataSlice2,\n  testCsvDataSlice2Id,\n  syncTimeFilterValue\n} from '../fixtures/test-csv-data';\nimport testLayerData from '../fixtures/test-layer-data';\n\nimport {fields, rows, geoJsonDataId} from '../fixtures/geojson';\nimport {\n  fields as tripFields,\n  rows as tripRows,\n  config as tripConfig,\n  dataId as tripDataId\n} from '../fixtures/test-trip-data';\nimport tripCsvData, {\n  tripCsvDataInfo,\n  expectedTripLayerConfig\n} from '../fixtures/test-trip-csv-data';\nimport testArcData, {arcDataInfo, config as arcDataConfig} from '../fixtures/test-arc-data';\nimport tripGeojson, {tripDataInfo} from '../fixtures/trip-geojson';\nimport {processCsvData, processGeojson, processRowObject} from '@kepler.gl/processors';\nimport {MOCK_MAP_STYLE} from './mock-map-styles';\nimport {\n  applyActions,\n  applyCreateTableTasks,\n  applyExistingDatasetTasks,\n  mockCreateNewDataEntry\n} from './mock-state-utils';\n\nexport {applyActions, applyCreateTableTasks, applyExistingDatasetTasks, mockCreateNewDataEntry};\n\nconst geojsonFields = cloneDeep(fields);\nconst geojsonRows = cloneDeep(rows);\nexport const testCsvDataId = csvDataId;\nexport const testGeoJsonDataId = geoJsonDataId;\n\nexport const csvInfo = {\n  id: testCsvDataId,\n  label: 'hello.csv',\n  queryType: 'file',\n  queryOption: 'csv',\n  params: {file: null}\n};\n\nexport const geojsonInfo = {\n  id: testGeoJsonDataId,\n  label: 'zip.geojson',\n  queryType: 'file',\n  queryOption: 'geojson',\n  params: {file: null}\n};\n\nexport const mockColorRange = {\n  colors: ['#FF000', '#00FF00', '#0000FF'],\n  colorMap: [\n    [1, '#FF0000'],\n    [3, '#00FF00'],\n    [5, '#0000FF']\n  ],\n  colorLegends: {\n    '#FF0000': 'hello'\n  }\n};\n\n// TODO: need to be deleted and imported from raw-states\nexport const InitialState = keplerGlReducer(undefined, {});\n\n/**\n * Mock app state with uploaded geojson and csv file\n * @returns {Immutable} appState\n */\nexport function mockStateWithFileUpload() {\n  const initialState = cloneDeep(InitialState);\n\n  // load csv and geojson\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: MapStyleActions.loadMapStyles,\n      payload: [{dark: MOCK_MAP_STYLE}]\n    },\n    {\n      action: VisStateActions.updateVisData,\n      payload: [[{info: csvInfo, data: {fields: testFields, rows: testAllData}}]]\n    },\n    {\n      action: VisStateActions.updateVisData,\n      payload: [[{info: geojsonInfo, data: {fields: geojsonFields, rows: geojsonRows}}]]\n    }\n  ]);\n\n  // replace layer id and color with controlled value for testing\n  updatedState.visState.layers.forEach((l, i) => {\n    const oldLayerId = l.id;\n    l.id = `${l.type}-${i}`;\n    l.config.color = [i, i, i];\n    if (l.config.visConfig.strokeColor) {\n      l.config.visConfig.strokeColor = [i + 10, i + 10, i + 10];\n    }\n    // update layerOrder with newly created IDs\n    updatedState.visState.layerOrder = updatedState.visState.layerOrder.map(layerId =>\n      layerId === oldLayerId ? l.id : layerId\n    );\n  });\n\n  return updatedState;\n}\n\nfunction mockStateWithLayerCustomColorBreaksLegends() {\n  const initialState = cloneDeep(InitialState);\n\n  // load csv and geojson\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: MapStyleActions.loadMapStyles,\n      payload: [{dark: MOCK_MAP_STYLE}]\n    },\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: [\n            {\n              info: csvInfo,\n              data: {fields: testFields, rows: testAllData}\n            }\n          ],\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [\n                  {\n                    type: 'point',\n                    id: 'point-layer-0',\n                    config: {\n                      dataId: csvInfo.id,\n                      columns: {\n                        lat: 'gps_data.lat',\n                        lng: 'gps_data.lng'\n                      },\n                      color: [255, 0, 0],\n                      label: 'pickup',\n                      isVisible: true,\n                      visConfig: {\n                        colorRange: mockColorRange\n                      }\n                    },\n                    visualChannels: {\n                      colorField: {\n                        name: 'uid',\n                        type: 'integer'\n                      },\n                      colorScale: 'custom'\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n\n  return updatedState;\n}\n\nfunction mockStateWithSyncedTimeFilter() {\n  const initialState = cloneDeep(InitialState);\n  // load 2 csv data\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: MapStyleActions.loadMapStyles,\n      payload: [{dark: MOCK_MAP_STYLE}]\n    },\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            info: {\n              id: testCsvDataSlice1Id,\n              label: 'hello.csv',\n              color: [255, 0, 0]\n            },\n            data: {fields: testFields, rows: testCsvDataSlice1}\n          }\n        ]\n      ]\n    },\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            info: {\n              id: testCsvDataSlice2Id,\n              label: 'world.csv',\n              color: [0, 255, 0]\n            },\n            data: {fields: testFields, rows: testCsvDataSlice2}\n          }\n        ]\n      ]\n    },\n    // add 1 filter\n    {action: VisStateActions.addFilter, payload: ['test-csv-data-1']},\n\n    // set filter name to 'time', and value\n    {\n      action: VisStateActions.setFilter,\n      // ['2016-09-17 00:11:56', '2016-09-17 00:21:17']\n      payload: [0, ['name', 'value'], ['gps_data.utc_timestamp', [1474071116000, 1474071677000]]]\n    },\n\n    // set filter name and dataId and index 1\n    {\n      action: VisStateActions.setFilter,\n      payload: [0, ['dataId', 'name'], ['test-csv-data-2', 'gps_data.utc_timestamp'], 1]\n    },\n\n    // set filter name to 'time', and value\n    {\n      action: VisStateActions.setFilter,\n      // ['2016-09-17 00:11:56', '2016-09-17 00:21:17']\n      payload: [0, ['value'], [syncTimeFilterValue]]\n    }\n  ]);\n\n  return updatedState;\n}\n\nexport function mockStateWithTripGeojson(state) {\n  const initialState = cloneDeep(state || InitialState);\n\n  // load csv and geojson\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [[{info: tripDataInfo, data: processGeojson(cloneDeep(tripGeojson))}]]\n    }\n  ]);\n\n  // replace layer id and color with controlled value for testing\n  updatedState.visState.layers.forEach((l, i) => {\n    const oldLayerId = l.id;\n    l.id = `${l.type}-${i}`;\n    l.config.color = [i, i, i];\n    // update layerOrder with newly created IDs\n    updatedState.visState.layerOrder = updatedState.visState.layerOrder.map(layerId =>\n      layerId === oldLayerId ? l.id : layerId\n    );\n  });\n\n  return updatedState;\n}\n\nexport function mockStateWithTripTable(state) {\n  const initialState = cloneDeep(state || InitialState);\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {info: tripCsvDataInfo, data: processCsvData(tripCsvData)},\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [expectedTripLayerConfig]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n\n  return updatedState;\n}\n\nexport function mockStateWithArcNeighbors(state) {\n  const initialState = cloneDeep(state || InitialState);\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {info: arcDataInfo, data: processRowObject(testArcData)},\n          config: arcDataConfig\n        }\n      ]\n    }\n  ]);\n\n  return updatedState;\n}\n\n// export function mockStateWith\n\nexport function mockStateWithFilters(state) {\n  const initialState = state || mockStateWithFileUpload();\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    // add filter\n    {action: VisStateActions.addFilter, payload: [testCsvDataId]},\n\n    // set filter name to 'time', and value\n    {\n      action: VisStateActions.setFilter,\n      payload: [0, ['name', 'value'], ['time', [1474606800000, 1474617600000]]]\n    },\n\n    // set filter animation speed\n    {\n      action: VisStateActions.updateFilterAnimationSpeed,\n      payload: [0, 4]\n    },\n\n    // add another filter\n    {action: VisStateActions.addFilter, payload: [testGeoJsonDataId]},\n\n    // set filter to 'RATE'\n    {action: VisStateActions.setFilter, payload: [1, ['name', 'value'], ['RATE', ['a']]]}\n  ]);\n\n  // replace filter id with controlled value for easy testing\n  prepareState.visState.filters.forEach((f, i) => {\n    f.id = `${f.name}-${i}`;\n  });\n\n  return prepareState;\n}\n\nfunction mockStateWithMultiFilters() {\n  const initialState = mockStateWithFilters();\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    // add filter\n    {action: VisStateActions.addFilter, payload: [testCsvDataId]},\n\n    // set filter to 'time'\n    {action: VisStateActions.setFilter, payload: [2, 'name', 'date']},\n\n    // set filter value\n    {\n      action: VisStateActions.setFilter,\n      payload: [2, 'value', ['2016-09-24', '2016-09-23']]\n    },\n\n    // add another filter\n    {action: VisStateActions.addFilter, payload: [testGeoJsonDataId]},\n\n    // set filter to 'TRIPS'\n    {action: VisStateActions.setFilter, payload: [3, 'name', 'TRIPS']},\n\n    // set filter value\n    {action: VisStateActions.setFilter, payload: [3, 'value', [4, 12]]},\n\n    // add another gpu filter\n    {action: VisStateActions.addFilter, payload: [testCsvDataId]},\n\n    // set filter to 'time'\n    {action: VisStateActions.setFilter, payload: [4, 'name', 'epoch']},\n\n    // set filter value\n    {\n      action: VisStateActions.setFilter,\n      payload: [4, 'value', [1472700000000, 1472760000000]]\n    }\n  ]);\n\n  // replace filter id with controlled value for easy testing\n  prepareState.visState.filters.forEach((f, i) => {\n    f.id = `${f.name}-${i}`;\n  });\n\n  return prepareState;\n}\n\nfunction mockStateWithEffects() {\n  const initialState = cloneDeep(InitialState);\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: VisStateActions.addEffect,\n      payload: [{id: 'e_1'}]\n    },\n    {\n      action: VisStateActions.addEffect,\n      payload: [{id: 'e_2', type: 'sepia'}]\n    },\n    {\n      action: VisStateActions.addEffect,\n      payload: [{id: 'e_3', type: 'magnify'}]\n    },\n    {\n      action: VisStateActions.addEffect,\n      payload: [{id: 'e_4', type: 'lightAndShadow', parameters: {timestamp: 100, timezone: 'UTC'}}]\n    }\n  ]);\n  return prepareState;\n}\n\nfunction mockStateWithH3Layer() {\n  const initialState = cloneDeep(InitialState);\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {\n            info: {id: csvDataId},\n            data: processCsvData(testLayerData)\n          },\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [\n                  {\n                    id: 'h3-layer',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n  return prepareState;\n}\n\nexport function mockStateWithMultipleH3Layers() {\n  const initialState = cloneDeep(InitialState);\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {\n            info: {id: csvDataId},\n            data: processCsvData(testLayerData)\n          },\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [\n                  {\n                    id: 'h3-layer-1',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 1',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  },\n                  {\n                    id: 'h3-layer-2',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 2',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n  return prepareState;\n}\n\n/**\n * Mock state will contain 1 heatmap, 1 point and 1 arc layer, 1 hexbin layer and 1 time filter\n * @param {*} state\n */\nfunction mockStateWithTripData() {\n  const initialState = cloneDeep(InitialState);\n\n  const addDataPayload = {\n    datasets: {\n      info: {\n        label: 'Sample Trips',\n        id: tripDataId\n      },\n      data: {fields: tripFields, rows: tripRows}\n    },\n    config: tripConfig\n  };\n  return applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [addDataPayload]\n    }\n  ]);\n}\n\nexport function mockStateWithLayerDimensions(state) {\n  const initialState = state || mockStateWithFileUpload();\n\n  const layer0 = initialState.visState.layers.find(\n    l => l.config.dataId === testCsvDataId && l.type === 'point'\n  );\n\n  const colorField = initialState.visState.datasets[testCsvDataId].fields.find(\n    f => f.name === 'gps_data.types'\n  );\n\n  const colorFieldPayload = [layer0, {colorField}, 'color'];\n  const colorRange = colorPaletteToColorRange(\n    KEPLER_COLOR_PALETTES.find(({name}) => name === 'Uber Viz Sequential'),\n    {steps: 4}\n  );\n  const colorRangePayload = [layer0, {colorRange}, 'color'];\n\n  const textLabelField = initialState.visState.datasets[testCsvDataId].fields.find(\n    f => f.name === 'date'\n  );\n\n  const textLabelPayload1 = [layer0, 0, 'field', textLabelField];\n  const textLabelPayload2 = [layer0, 0, 'color', [255, 0, 0]];\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    // change colorField\n    {\n      action: VisStateActions.layerVisualChannelConfigChange,\n      payload: colorFieldPayload\n    },\n\n    // change colorRange\n    {action: VisStateActions.layerVisConfigChange, payload: colorRangePayload},\n\n    // change textLabel\n    {action: VisStateActions.layerTextLabelChange, payload: textLabelPayload1},\n    {action: VisStateActions.layerTextLabelChange, payload: textLabelPayload2},\n\n    // add layer from config\n    {\n      action: VisStateActions.addLayer,\n      payload: [\n        {\n          id: 'hexagon-2',\n          type: 'hexagon',\n          config: {\n            color: [2, 2, 2],\n            isVisible: true,\n            dataId: testCsvDataId,\n            columns: {\n              lat: 'gps_data.lat',\n              lng: 'gps_data.lng'\n            }\n          }\n        }\n      ]\n    }\n  ]);\n\n  const reorderPayload = [['hexagon-2', 'point-0', 'geojson-1']];\n\n  const resultState = applyActions(keplerGlReducer, prepareState, [\n    // reorder Layer\n    {action: VisStateActions.reorderLayer, payload: reorderPayload}\n  ]);\n\n  return resultState;\n}\n\nfunction mockStateWithCustomMapStyle(customType = 'LOCAL') {\n  const initialState = cloneDeep(InitialState);\n  const testCustomMapStyle = {\n    ...getInitialInputStyle(),\n    id: 'smoothie_the_cat',\n    accessToken: 'secret_token',\n    isValid: true,\n    label: 'Smoothie the Cat',\n    style: {\n      version: 'v8',\n      id: 'smoothie_the_cat',\n      layers: [{id: 'background'}, {id: 'road'}, {id: 'label'}],\n      name: 'Smoothie the Cat'\n    },\n    url: 'mapbox://styles/shanhe/smoothie.the.cat',\n    icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n    custom: customType\n  };\n\n  // add custom map style\n  const updatedState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: MapStyleActions.inputMapStyle,\n      payload: [testCustomMapStyle]\n    },\n    {\n      action: MapStyleActions.loadCustomMapStyle,\n      payload: [\n        {\n          style: testCustomMapStyle.style\n        }\n      ]\n    },\n    {\n      action: MapStyleActions.addCustomMapStyle,\n      payload: [{}]\n    },\n    {\n      action: MapStyleActions.set3dBuildingColor,\n      payload: [[1, 2, 3]]\n    }\n  ]);\n\n  return updatedState;\n}\n\nfunction mockStateWithSplitMaps(state) {\n  const initialState = state || mockStateWithFileUpload();\n\n  const firstLayer = initialState.visState.layers[0];\n  const secondLayer = initialState.visState.layers[1];\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    // toggle splitMaps\n    {action: MapStateActions.toggleSplitMap, payload: []},\n\n    // toggleLayerForMap\n    {action: VisStateActions.toggleLayerForMap, payload: [0, firstLayer.id]},\n\n    // open geojson layer\n    {action: VisStateActions.toggleLayerForMap, payload: [1, secondLayer.id]},\n\n    {action: VisStateActions.toggleLayerForMap, payload: [1, firstLayer.id]}\n  ]);\n\n  return prepareState;\n}\n\nfunction mockStateWithTooltipFormat() {\n  const initialState = mockStateWithFileUpload();\n\n  const oldConfig = initialState.visState.interactionConfig.tooltip;\n  const newConfig = {\n    ...oldConfig,\n    config: {\n      ...oldConfig.config,\n      compareMode: true,\n      compareType: COMPARE_TYPES.RELATIVE,\n      fieldsToShow: {\n        ...oldConfig.config.fieldsToShow,\n        [testCsvDataId]: [{name: 'gps_data.utc_timestamp', format: 'LL'}],\n        [testGeoJsonDataId]: [\n          {name: 'OBJECTID', format: null},\n          {name: 'ZIP_CODE', format: null},\n          {name: 'ID', format: null},\n          {name: 'TRIPS', format: '.3f'},\n          {name: 'RATE', format: null}\n        ]\n      }\n    }\n  };\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {action: VisStateActions.interactionConfigChange, payload: [newConfig]}\n  ]);\n\n  return prepareState;\n}\n\nfunction mockStateWithGeocoderDataset() {\n  const initialState = cloneDeep(InitialState);\n\n  const oldInteractionConfig = initialState.visState.interactionConfig.tooltip;\n  const newInteractionConfig = {\n    ...oldInteractionConfig,\n    config: {\n      ...oldInteractionConfig.config,\n      fieldsToShow: {\n        ...oldInteractionConfig.config.fieldsToShow,\n        geocoder_dataset: [\n          {\n            name: 'lt',\n            format: null\n          },\n          {\n            name: 'ln',\n            format: null\n          },\n          {\n            name: 'icon',\n            format: null\n          },\n          {\n            name: 'text',\n            format: null\n          }\n        ]\n      },\n      compareMode: false,\n      compareType: COMPARE_TYPES.ABSOLUTE\n    }\n  };\n  const geocoderDataset = getUpdateVisDataPayload(48.85658, 2.35183, 'Paris');\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: geocoderDataset\n    },\n    {action: VisStateActions.interactionConfigChange, payload: [newInteractionConfig]}\n  ]);\n\n  return prepareState;\n}\n\nfunction mockStateWithLayerStyling() {\n  const initialState = mockStateWithFileUpload();\n  const layer0 = initialState.visState.layers.find(\n    l => l.config.dataId === testCsvDataId && l.type === 'point'\n  );\n  const mapCenter = {\n    longitude: 31.2369645,\n    latitude: 30.0242098,\n    zoom: 13\n  };\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    // make radius bigger\n    {\n      action: VisStateActions.layerVisConfigChange,\n      payload: [layer0, {radius: 100}]\n    },\n    // center map\n    {\n      action: MapStateActions.updateMap,\n      payload: [mapCenter]\n    }\n  ]);\n\n  return prepareState;\n}\n\n// saved hexagon layer\nexport const expectedSavedLayer0 = {\n  id: 'hexagon-2',\n  type: 'hexagon',\n  config: {\n    dataId: testCsvDataId,\n    label: DEFAULT_LAYER_LABEL,\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [2, 2, 2],\n    columns: {\n      lat: 'gps_data.lat',\n      lng: 'gps_data.lng'\n    },\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      worldUnitSize: 1,\n      resolution: 8,\n      colorRange: DEFAULT_COLOR_RANGE,\n      coverage: 1,\n      sizeRange: [0, 500],\n      percentile: [0, 100],\n      elevationPercentile: [0, 100],\n      elevationScale: 5,\n      enableElevationZoomFactor: true,\n      fixedHeight: false,\n      colorAggregation: 'count',\n      sizeAggregation: 'count',\n      enable3d: false\n    },\n    textLabel: [DEFAULT_TEXT_LABEL]\n  },\n  visualChannels: {\n    colorField: null,\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear'\n  }\n};\n\nexport const expectedLoadedLayer0 = {\n  id: 'hexagon-2',\n  type: 'hexagon',\n  config: {\n    dataId: testCsvDataId,\n    label: DEFAULT_LAYER_LABEL,\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [2, 2, 2],\n    columns: {\n      lat: 'gps_data.lat',\n      lng: 'gps_data.lng'\n    },\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      worldUnitSize: 1,\n      resolution: 8,\n      colorRange: DEFAULT_COLOR_RANGE,\n      coverage: 1,\n      sizeRange: [0, 500],\n      percentile: [0, 100],\n      elevationPercentile: [0, 100],\n      elevationScale: 5,\n      enableElevationZoomFactor: true,\n      fixedHeight: false,\n      // LAYER_VIS_CONFIGS.aggregation.defaultValue is AGGREGATION_TYPES.average,\n      colorAggregation: 'count',\n      sizeAggregation: 'count',\n      enable3d: false\n    },\n    colorField: null,\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear',\n    textLabel: [DEFAULT_TEXT_LABEL]\n  }\n};\n\n// result of Uber Viz Sequential step 4\nexport const expectedColorRangeInLayer = {\n  name: 'Uber Viz Sequential',\n  type: 'sequential',\n  category: 'Uber',\n  colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA']\n};\n\nexport const expectedSavedLayer1 = {\n  id: 'point-0',\n  type: 'point',\n  config: {\n    dataId: testCsvDataId,\n    label: 'gps_data',\n    columnMode: 'points',\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [0, 0, 0],\n    columns: {\n      lat: 'gps_data.lat',\n      lng: 'gps_data.lng'\n    },\n    textLabel: [\n      {\n        ...DEFAULT_TEXT_LABEL,\n        field: {\n          name: 'date',\n          type: 'date'\n        },\n        color: [255, 0, 0]\n      }\n    ],\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      radius: 10,\n      billboard: false,\n      fixedRadius: false,\n      opacity: DEFAULT_LAYER_OPACITY,\n      outline: false,\n      filled: true,\n      thickness: 2,\n      colorRange: expectedColorRangeInLayer,\n      strokeColorRange: DEFAULT_COLOR_RANGE,\n      strokeColor: null,\n      radiusRange: [0, 50],\n      allowHover: true,\n      showNeighborOnHover: false,\n      showHighlightColor: true\n    }\n  },\n  visualChannels: {\n    colorField: {\n      name: 'gps_data.types',\n      type: 'string'\n    },\n    colorScale: 'ordinal',\n    sizeField: null,\n    sizeScale: 'linear',\n    strokeColorField: null,\n    strokeColorScale: 'quantile'\n  }\n};\n\nexport const expectedLoadedLayer1 = {\n  id: 'point-0',\n  type: 'point',\n  config: {\n    dataId: testCsvDataId,\n    label: 'gps_data',\n    columnMode: 'points',\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [0, 0, 0],\n    columns: {\n      lat: 'gps_data.lat',\n      lng: 'gps_data.lng'\n    },\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      radius: 10,\n      billboard: false,\n      fixedRadius: false,\n      opacity: DEFAULT_LAYER_OPACITY,\n      outline: false,\n      thickness: 2,\n      colorRange: expectedColorRangeInLayer,\n      strokeColorRange: DEFAULT_COLOR_RANGE,\n      filled: true,\n      strokeColor: null,\n      radiusRange: [0, 50],\n      allowHover: true,\n      showNeighborOnHover: false,\n      showHighlightColor: true\n    },\n    colorField: {\n      name: 'gps_data.types',\n      type: 'string'\n    },\n    colorScale: 'ordinal',\n    strokeColorField: null,\n    strokeColorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear',\n    textLabel: [\n      {\n        ...DEFAULT_TEXT_LABEL,\n        field: {\n          name: 'date',\n          type: 'date'\n        },\n        color: [255, 0, 0]\n      }\n    ]\n  }\n};\n\nexport const expectedSavedLayer2 = {\n  id: 'geojson-1',\n  type: 'geojson',\n  config: {\n    dataId: testGeoJsonDataId,\n    label: 'zip',\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [1, 1, 1],\n    columns: {\n      geojson: '_geojson'\n    },\n    columnMode: 'geojson',\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      thickness: 0.5,\n      colorRange: DEFAULT_COLOR_RANGE,\n      strokeColorRange: DEFAULT_COLOR_RANGE,\n      strokeColor: [11, 11, 11],\n      radius: 10,\n      sizeRange: [0, 10],\n      radiusRange: [0, 50],\n      heightRange: [0, 500],\n      elevationScale: 5,\n      fixedHeight: false,\n      stroked: true,\n      filled: true,\n      enable3d: false,\n      wireframe: false,\n      strokeOpacity: 0.8\n    },\n    textLabel: [DEFAULT_TEXT_LABEL]\n  },\n  visualChannels: {\n    colorField: null,\n    colorScale: 'quantile',\n    strokeColorField: null,\n    strokeColorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear',\n    heightField: null,\n    heightScale: 'linear',\n    radiusField: null,\n    radiusScale: 'linear'\n  }\n};\n\nexport const expectedLoadedLayer2 = {\n  id: 'geojson-1',\n  type: 'geojson',\n  config: {\n    dataId: testGeoJsonDataId,\n    label: 'zip',\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [1, 1, 1],\n    columns: {\n      geojson: '_geojson'\n    },\n    columnMode: 'geojson',\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      thickness: 0.5,\n      colorRange: DEFAULT_COLOR_RANGE,\n      strokeColorRange: DEFAULT_COLOR_RANGE,\n      strokeColor: [11, 11, 11],\n      strokeOpacity: 0.8,\n      radius: 10,\n      sizeRange: [0, 10],\n      radiusRange: [0, 50],\n      heightRange: [0, 500],\n      elevationScale: 5,\n      fixedHeight: false,\n      stroked: true,\n      filled: true,\n      enable3d: false,\n      wireframe: false\n    },\n    colorField: null,\n    colorScale: 'quantile',\n    strokeColorField: null,\n    strokeColorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear',\n    heightField: null,\n    heightScale: 'linear',\n    radiusField: null,\n    radiusScale: 'linear',\n    textLabel: [DEFAULT_TEXT_LABEL]\n  }\n};\n\nexport const StateWFiles = mockStateWithFileUpload();\nexport const StateWFilters = mockStateWithFilters();\nexport const StateWEffects = mockStateWithEffects();\nexport const StateWFilesFiltersLayerColor = mockStateWithLayerDimensions(StateWFilters);\nexport const StateWMultiFilters = mockStateWithMultiFilters();\n\nexport const StateWCustomMapStyleLegacy = mockStateWithCustomMapStyle(true);\nexport const StateWCustomMapStyleLocal = mockStateWithCustomMapStyle('LOCAL');\nexport const StateWCustomMapStyleManaged = mockStateWithCustomMapStyle('MANAGED');\nexport const StateWSplitMaps = mockStateWithSplitMaps();\nexport const StateWTrips = mockStateWithTripData();\nexport const StateWTripGeojson = mockStateWithTripGeojson();\nexport const StateWTooltipFormat = mockStateWithTooltipFormat();\nexport const StateWH3Layer = mockStateWithH3Layer();\nexport const StateWMultiH3Layers = mockStateWithMultipleH3Layers();\nexport const StateWithGeocoderDataset = mockStateWithGeocoderDataset();\nexport const StateWLayerStyle = mockStateWithLayerStyling();\nexport const StateWLayerCustomColorBreaks = mockStateWithLayerCustomColorBreaksLegends();\nexport const StateWTripTable = mockStateWithTripTable();\nexport const StateWArcNeighbors = mockStateWithArcNeighbors();\nexport const StateWSyncedTimeFilter = mockStateWithSyncedTimeFilter();\n\nexport const expectedSavedTripLayer = {\n  id: 'trip-0',\n  type: 'trip',\n  config: {\n    dataId: 'trip_data',\n    label: 'Trip Data',\n    highlightColor: DEFAULT_HIGHLIGHT_COLOR,\n    color: [0, 0, 0],\n    columnMode: 'geojson',\n    columns: {\n      geojson: '_geojson'\n    },\n    hidden: false,\n    isVisible: true,\n    visConfig: {\n      opacity: DEFAULT_LAYER_OPACITY,\n      thickness: 0.5,\n      colorRange: DEFAULT_COLOR_RANGE,\n      trailLength: 180,\n      fadeTrail: true,\n      billboard: false,\n      sizeRange: [0, 10]\n    },\n    textLabel: [DEFAULT_TEXT_LABEL]\n  },\n  visualChannels: {\n    colorField: null,\n    colorScale: 'quantile',\n    sizeField: null,\n    sizeScale: 'linear'\n  }\n};\n\nexport function mockKeplerPropsWithState({\n  state,\n  keplerProps = DEFAULT_KEPLER_GL_PROPS,\n  visStateActions = {},\n  mapStateActions = {},\n  mapStyleActions = {},\n  uiStateActions = {},\n  providerActions = {}\n}) {\n  return {\n    ...state,\n    ...keplerProps,\n    visStateActions: {\n      ...VisStateActions,\n      ...visStateActions\n    },\n    mapStateActions: {\n      ...MapStateActions,\n      ...mapStateActions\n    },\n    mapStyleActions: {\n      ...MapStyleActions,\n      ...mapStyleActions\n    },\n    uiStateActions: {\n      ...UIStateActions,\n      ...uiStateActions\n    },\n    providerActions: {\n      ...ProviderActions,\n      ...providerActions\n    }\n  };\n}\n\nexport const mockKeplerProps = mockKeplerPropsWithState({state: StateWLayerStyle});\n\n// mount map with mockKeplerProps\n// hover over data index 15\n// tested in map-container-test\nexport const expectedLayerHoverProp = {\n  currentTime: null,\n  data: mockKeplerProps.visState.datasets[testCsvDataId].dataContainer.row(15),\n  fields: mockKeplerProps.visState.datasets[testCsvDataId].fields,\n  fieldsToShow:\n    mockKeplerProps.visState.interactionConfig.tooltip.config.fieldsToShow[testCsvDataId],\n  layer: mockKeplerProps.visState.layers[0]\n};\n\nexport const expectedGeojsonLayerHoverProp = {\n  data: mockKeplerProps.visState.datasets[testGeoJsonDataId].dataContainer.row(0),\n  fields: mockKeplerProps.visState.datasets[testGeoJsonDataId].fields,\n  fieldsToShow:\n    mockKeplerProps.visState.interactionConfig.tooltip.config.fieldsToShow[testGeoJsonDataId],\n  layer: mockKeplerProps.visState.layers[1]\n};\n\nexport function stateWithTimeFilterAndTripLayer() {\n  const initialState = mockStateWithTripData();\n\n  return mockStateWithTripGeojson(initialState);\n}\n\nexport function stateWithTimeFilterSyncedWithTripLayer() {\n  const initialState = stateWithTimeFilterAndTripLayer();\n  return {\n    ...initialState,\n    visState: syncTimeFilterWithLayerTimelineUpdater(initialState.visState, {\n      idx: 0,\n      enable: true\n    })\n  };\n}\n"
  },
  {
    "path": "test/helpers/raw-states.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport {keplerGlReducer} from '@kepler.gl/reducers';\nimport {addDataToMap} from '@kepler.gl/actions';\nimport {processCsvData} from '@kepler.gl/processors';\n\nimport {dataId as csvDataId} from '../fixtures/test-csv-data';\nimport testLayerData from '../fixtures/test-layer-data';\n\nexport function applyActions(reducer, initialState, actions) {\n  const actionQ = Array.isArray(actions) ? actions : [actions];\n\n  return actionQ.reduce(\n    (updatedState, {action, payload}) => reducer(updatedState, action(...payload)),\n    initialState\n  );\n}\n\nexport const InitialState = keplerGlReducer(undefined, {});\n\nfunction mockStateWithMultipleH3Layers() {\n  const initialState = cloneDeep(InitialState);\n\n  const prepareState = applyActions(keplerGlReducer, initialState, [\n    {\n      action: addDataToMap,\n      payload: [\n        {\n          datasets: {\n            info: {id: csvDataId},\n            data: processCsvData(testLayerData)\n          },\n          config: {\n            version: 'v1',\n            config: {\n              visState: {\n                layers: [\n                  {\n                    id: 'h3-layer-1',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 1',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  },\n                  {\n                    id: 'h3-layer-2',\n                    type: 'hexagonId',\n                    config: {\n                      dataId: csvDataId,\n                      label: 'H3 Hexagon 2',\n                      color: [255, 153, 31],\n                      columns: {hex_id: 'hex_id'},\n                      isVisible: true\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      ]\n    }\n  ]);\n  return prepareState;\n}\n\nexport const StateWMultiH3Layers = mockStateWithMultipleH3Layers();\n"
  },
  {
    "path": "test/helpers/table-utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {createNewDataEntry} from '@kepler.gl/table';\n\n/**\n * A test function to execute the task from createNewDataEntry with await syntax.\n * @param {ProtoDataset} props\n * @returns {Datasets}\n */\nexport const createNewDataEntryMock = async props => {\n  const newDataEntry = createNewDataEntry(props);\n  let table = null;\n  await newDataEntry.run(\n    async (effectorPrime, success, error) => {\n      const res = effectorPrime(success, error);\n      await res;\n    },\n    value => {\n      table = value;\n    }\n  );\n  return {\n    [props.info.id]: table\n  };\n};\n"
  },
  {
    "path": "test/helpers/utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const STYLED_COMPONENTS_DUPLICATED_ENTRIES = 2;\n\nexport const cloneClassInstance = classInstance =>\n  Object.assign(Object.create(Object.getPrototypeOf(classInstance)), classInstance);\n\nexport const cloneAndUpdate = (classInstance, key, value) => {\n  const instance = cloneClassInstance(classInstance);\n  instance[key] = value;\n\n  return instance;\n};\n"
  },
  {
    "path": "test/js-dom.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Run browser tests in js-dom\nrequire('./browser/index.js');\n"
  },
  {
    "path": "test/node/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/*eslint-disable */\nimport './utils';\n\n// test reducers\nimport './reducers';\n\n// test schemas\nimport './schemas';\n\n// test processers\nimport './processors';\n"
  },
  {
    "path": "test/node/processors/file-handler-fixtures.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// csv restul returned by loaders.gl\n// Nulls are returned as string\nimport {geojsonData} from 'test/fixtures/geojson';\n\n// TODO: this is result returned by loaders.gl\n// ideally we would test loaders.gl from start to end\n// need to upgrade JSDom to\nexport const csvWithNull = [\n  {\n    'gps_data.utc_timestamp': 'Null',\n    'gps_data.lat': 29.9900937,\n    'gps_data.lng': 31.2590542,\n    'gps_data.types': 'driver_analytics_0',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 1,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: '2016-10-01 09:41:39+00:00',\n    begintrip_ts_local: '2016-10-01 09:41:39+00:00',\n    date: '2016-09-23'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:10:56',\n    'gps_data.lat': 29.9927699,\n    'gps_data.lng': 31.2461142,\n    'gps_data.types': 'null',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 'null',\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: '2016-10-01 09:46:37+00:00',\n    begintrip_ts_local: '2016-10-01 16:46:37+00:00',\n    date: '2016-09-23'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:11:56',\n    'gps_data.lat': 29.9907261,\n    'gps_data.lng': 'NaN',\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 3,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-23'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:12:58',\n    'gps_data.lat': 29.9870074,\n    'gps_data.lng': 31.2175827,\n    'gps_data.types': 'driver_gps',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 4,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-23'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:14:00',\n    'gps_data.lat': 29.9923041,\n    'gps_data.lng': 31.2154899,\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472688000000,\n    has_result: 1,\n    id: 5,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: '2016-10-01 09:47:37+00:00',\n    begintrip_ts_local: '2016-10-01 16:47:37+00:00',\n    date: null\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:15:01',\n    'gps_data.lat': 29.9968249,\n    'gps_data.lng': 31.2149361,\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 12124,\n    time: '2016-09-23T05:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: null\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:16:03',\n    'gps_data.lat': 'Null',\n    'gps_data.lng': 31.2164035,\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 222,\n    time: '2016-09-23T05:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: null\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:17:05',\n    'gps_data.lat': 30.0116207,\n    'gps_data.lng': 31.2179346,\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472688000000,\n    has_result: 'False',\n    id: 345,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:18:09',\n    'gps_data.lat': 30.0208925,\n    'gps_data.lng': 31.2179556,\n    'gps_data.types': 'driver_analytics',\n    epoch: 1472708000000,\n    has_result: 'False',\n    id: null,\n    time: '2016-09-23T00:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:19:12',\n    'gps_data.lat': 30.0218999,\n    'gps_data.lng': 31.2178842,\n    'gps_data.types': 'driver_analytics',\n    epoch: 'NULL',\n    has_result: 0,\n    id: null,\n    time: '2016-09-23T06:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:19:27',\n    'gps_data.lat': 30.0229344,\n    'gps_data.lng': 31.2179138,\n    'gps_data.types': 'driver_gps',\n    epoch: 1472708000000,\n    has_result: 'False',\n    id: null,\n    time: '2016-09-23T05:00:00.000Z',\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:20:14',\n    'gps_data.lat': 30.0264237,\n    'gps_data.lng': 31.2179415,\n    'gps_data.types': 'driver_gps',\n    epoch: 1472708000000,\n    has_result: 'False',\n    id: null,\n    time: null,\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  },\n  {\n    'gps_data.utc_timestamp': '2016-09-17 00:21:17',\n    'gps_data.lat': 30.0292134,\n    'gps_data.lng': 31.2181809,\n    'gps_data.types': 'driver_gps',\n    epoch: 1472754400000,\n    has_result: 'False',\n    id: null,\n    time: null,\n    begintrip_ts_utc: null,\n    begintrip_ts_local: null,\n    date: '2016-09-24'\n  }\n];\n\n// metaData batch csv\nexport const csvMetaDataBatch = {\n  batchType: 'metadata',\n  bytesUsed: 0,\n  data: [],\n  metadata: {\n    _context: {},\n    _loader: {}\n  }\n};\n\nexport const csvSchema = {\n  'gps_data.utc_timestamp': {\n    name: 'gps_data.utc_timestamp',\n    index: 0\n  },\n  'gps_data.lat': {\n    name: 'gps_data.lat',\n    index: 1\n  },\n  'gps_data.lng': {\n    name: 'gps_data.lng',\n    index: 2\n  },\n  'gps_data.types': {\n    name: 'gps_data.types',\n    index: 3\n  },\n  epoch: {\n    name: 'epoch',\n    index: 4\n  },\n  has_result: {\n    name: 'has_result',\n    index: 5\n  },\n  id: {\n    name: 'id',\n    index: 6\n  },\n  time: {\n    name: 'time',\n    index: 7\n  },\n  begintrip_ts_utc: {\n    name: 'begintrip_ts_utc',\n    index: 8\n  },\n  begintrip_ts_local: {\n    name: 'begintrip_ts_local',\n    index: 9\n  },\n  date: {\n    name: 'date',\n    index: 10\n  }\n};\n\n// data batch csv\nexport const csvDataBatch0 = {\n  bytesUsed: 2000,\n  count: 0,\n  // cursor: 0\n  data: csvWithNull.slice(0, 6),\n  length: 6,\n  schema: csvSchema\n};\n\nexport const csvDataBatch1 = {\n  bytesUsed: 3000,\n  count: 1,\n  // cursor: 0\n  data: csvWithNull.slice(6, 13),\n  length: 7,\n  schema: csvSchema\n};\n\nexport const geojsonMetaBatch = {\n  batchType: 'metadata',\n  bytesUsed: 0,\n  // data is empty in metaBatch\n  data: [],\n  // omit loader\n  metadata: {_loader: {}, _context: {}},\n  progress: {\n    percent: 0,\n    rowCount: 0,\n    rowCountInBatch: 0\n  }\n};\nexport const geoJsonPartialBatch = {\n  batchType: 'partial-result',\n  bytesUsed: 0,\n  container: {type: 'FeatureCollection', features: []},\n  data: [],\n  jsonpath: '$.features',\n  schema: {}\n};\n\nexport const geoJsonDataBatch0 = {\n  bytesUsed: 2000,\n  count: 1,\n  cursor: 0,\n  data: geojsonData.features.slice(0, 2),\n  jsonpath: '$.features',\n  length: 5,\n  schema: null\n};\n\nexport const geoJsonDataBatch1 = {\n  bytesUsed: 4000,\n  count: 2,\n  cursor: 0,\n  data: geojsonData.features.slice(2, 5),\n  jsonpath: '$.features',\n  length: 3,\n  schema: null\n};\n\nexport const geoJsonFinalBatch = {\n  batchType: 'final-result',\n  container: {type: 'FeatureCollection', features: []},\n  data: [],\n  jsonpath: '$.features',\n  schema: null\n};\n"
  },
  {
    "path": "test/node/processors/file-handler-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {isKeplerGlMap, makeProgressIterator, filesToDataPayload} from '@kepler.gl/processors';\nimport {parsedFields, parsedRows} from 'test/fixtures/row-object';\nimport {\n  savedStateV1InteractionCoordinate as keplerglMap,\n  parsedFields as parsedKeplerMapFields\n} from 'test/fixtures/state-saved-v1-7';\n\ntest('#file-handler -> isKeplerGlMap', t => {\n  t.equal(\n    isKeplerGlMap('{datasets: [], info: {app: \"kepler.gl\"}, config: {}}'),\n    false,\n    'Should return false when passing a json string'\n  );\n\n  t.equal(\n    isKeplerGlMap({datasets: [], info: {app: 'kepler.gl'}, config: {}}),\n    true,\n    'Should return true when object is a kepler map'\n  );\n\n  t.equal(\n    isKeplerGlMap({datasets: [], info: {app: 'kepler.gl'}}),\n    false,\n    'Should return false when object is not a kepler map'\n  );\n\n  t.end();\n});\n\ntest('#file-handler -> makeProgressIterator', async t => {\n  // mock AsyncIterator returned by loarder.gl patchInBatches\n  // Ideally should run this in browser-headless\n  async function* mock() {\n    let bytesUsed = 0;\n    let value = 0;\n    let b = 0;\n    await new Promise(resolve => setTimeout(resolve, 100));\n    while (b < 2) {\n      b += 1;\n      bytesUsed += 10;\n      value += 1;\n      yield {\n        data: [{value}],\n        bytesUsed\n      };\n    }\n  }\n  const asyncIterator = mock();\n  const info = {size: 100};\n\n  const progress = makeProgressIterator(asyncIterator, info);\n\n  const batch1 = await progress.next();\n  const batch2 = await progress.next();\n  const batch3 = await progress.next();\n\n  const expected1 = {\n    value: {\n      data: [{value: 1}],\n      bytesUsed: 10,\n      progress: {rowCount: 1, rowCountInBatch: 1, percent: 0.1}\n    },\n    done: false\n  };\n\n  const expected2 = {\n    value: {\n      data: [{value: 2}],\n      bytesUsed: 20,\n      progress: {rowCount: 2, rowCountInBatch: 1, percent: 0.2}\n    },\n    done: false\n  };\n  const expected3 = {\n    value: undefined,\n    done: true\n  };\n\n  t.deepEqual(batch1, expected1, 'batch1 should be correct');\n  t.deepEqual(batch2, expected2, 'batch2 should be correct');\n  t.deepEqual(batch3, expected3, 'batch3 should be correct');\n\n  t.end();\n});\n\ntest('#file-handler -> filesToDataPayload', t => {\n  const fileCache = [\n    {\n      data: {\n        fields: parsedFields,\n        rows: parsedRows\n      },\n      info: {label: 'rows-data.json', format: 'row'}\n    },\n    {\n      data: {\n        datasets: [\n          {\n            data: {\n              fields: parsedKeplerMapFields,\n              rows: keplerglMap.datasets[0].data.allData\n            },\n            info: {id: 'a5ybmwl2d', label: 'geojson_as_string_small.csv', color: [53, 92, 125]}\n          }\n        ],\n        config: keplerglMap.config\n      },\n      info: {label: 'keplergl-map.json', format: 'keplergl'}\n    }\n  ];\n\n  const result = filesToDataPayload(fileCache);\n\n  // const expectedResults = [\n  //   {\n  //     datasets: [{data, info}],\n  //     config: {\n  //       version: 'v1',\n  //       config: {}\n  //     },\n  //     options: {centerMap: true}\n  //   },\n  //   {datasets: [{data, info}]}\n  // ];\n\n  t.equal(result.length, 2, 'result shoud have 2 entries');\n  t.deepEqual(\n    Object.keys(result[0]),\n    ['datasets', 'config', 'options'],\n    'result[0] should have 3 keys'\n  );\n  t.equal(result[0].datasets, fileCache[1].data.datasets, 'should save keplergl map datasets');\n  t.equal(result[0].config, fileCache[1].data.config, 'should save keplergl map config');\n  t.deepEqual(\n    result[0].options,\n    {centerMap: true},\n    'should save keplergl map set {centerMap: true}'\n  );\n\n  t.deepEqual(Object.keys(result[1]), ['datasets'], 'result[0] should have 1 key');\n  t.deepEqual(\n    result[1].datasets[0].data,\n    fileCache[0].data,\n    'should pass file data to datasets only'\n  );\n  t.deepEqual(\n    Object.keys(result[1].datasets[0].info),\n    ['id', 'label', 'format'],\n    'result[0] datasets[0].info should have 3 key'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/processors/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './file-handler-test';\n"
  },
  {
    "path": "test/node/reducers/composer-state-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\n\nimport {drainTasksForTesting, succeedTaskWithValues} from 'react-palm/tasks';\nimport test from 'tape';\n\nimport {registerEntry} from '@kepler.gl/actions';\nimport {processCsvData} from '@kepler.gl/processors';\nimport keplerGlReducer, {\n  addDataToMapUpdater,\n  replaceDataInMapUpdater,\n  fitBoundsUpdater,\n  INITIAL_UI_STATE,\n  visStateReducer,\n  mapStateReducer\n} from '@kepler.gl/reducers';\nimport {getTimeBins} from '@kepler.gl/utils';\n\nimport testCsvData, {sampleConfig, dataWithNulls} from 'test/fixtures/test-csv-data';\nimport testHexIdData, {\n  hexIdDataConfig,\n  mergedH3Layer,\n  mergedFilters,\n  expectedMergedDataset\n} from 'test/fixtures/test-hex-id-data';\nimport {cmpLayers, cmpFilters, cmpDataset, cmpInteraction} from 'test/helpers/comparison-utils';\nimport {applyExistingDatasetTasks} from 'test/helpers/mock-state';\n\nimport {StateWSyncedTimeFilter} from '../../helpers/mock-state';\nimport {testCsvDataSlice1Id, testCsvDataSlice2Id} from '../../fixtures/test-csv-data';\n\nconst mockRawData = {\n  fields: [\n    {\n      name: 'start_point_lat',\n      id: 'start_point_lat',\n      displayName: 'start_point_lat',\n      type: 'real',\n      fieldIdx: 0\n    },\n    {\n      name: 'start_point_lng',\n      id: 'start_point_lng',\n      displayName: 'start_point_lng',\n      type: 'real',\n      fieldIdx: 2\n    },\n    {\n      name: 'end_point_lat',\n      id: 'end_point_lat',\n      displayName: 'end_point_lat',\n      type: 'real',\n      fieldIdx: 3\n    },\n    {\n      name: 'end_point_lng',\n      id: 'end_point_lng',\n      displayName: 'end_point_lng',\n      type: 'real',\n      fieldIdx: 4\n    }\n  ],\n  rows: [\n    [12.25, 37.75, 45.21, 100.12],\n    [null, 35.2, 45.0, 21.3],\n    [12.29, 37.64, 46.21, 99.127],\n    [null, null, 33.1, 29.34]\n  ]\n};\n\ntest('#composerStateReducer - addDataToMapUpdater: mapStyle', t => {\n  // init kepler.gl root and instance\n  const state = keplerGlReducer(undefined, registerEntry({id: 'test'})).test;\n\n  const newState = addDataToMapUpdater(state, {\n    payload: {\n      datasets: {\n        data: mockRawData,\n        info: {\n          id: 'foo'\n        }\n      },\n      options: null,\n      config: {\n        mapStyle: {\n          styleType: 'light'\n        }\n      }\n    }\n  });\n\n  drainTasksForTesting();\n\n  t.equal(newState.mapStyle.styleType, 'light', 'Map style is set correctly');\n\n  t.end();\n});\n\ntest('#composerStateReducer - addDataToMapUpdater: mapState should be centered (after dataset tasts are completed)', t => {\n  // init kepler.gl root and instance\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n  const mapStateProperties = {\n    latitude: 33.88608913680742,\n    longitude: -84.43459130456425\n  };\n  let newState = addDataToMapUpdater(state, {\n    payload: {\n      datasets: {\n        data: mockRawData,\n        info: {\n          id: 'foo'\n        }\n      },\n      options: {\n        centerMap: true\n      },\n      config: {\n        mapState: mapStateProperties\n      }\n    }\n  });\n\n  // create datasets from existing tasks, trigger auto create layers\n  newState.visState = applyExistingDatasetTasks(visStateReducer, newState.visState);\n\n  // layers should generate a fit bounds task\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'Should create one fit bounds task');\n\n  newState.mapState = mapStateReducer(newState.mapState, succeedTaskWithValues(tasks[0], {}));\n\n  t.equal(newState.mapState.latitude, 29.23, 'centerMap: true should override mapState config');\n  t.equal(newState.mapState.longitude, 60.71, 'centerMap: true should override mapState config');\n\n  t.end();\n});\n\ntest('#composerStateReducer - addDataToMapUpdater: uiState', t => {\n  // init kepler.gl root and instance\n  const state = keplerGlReducer(undefined, registerEntry({id: 'test'})).test;\n\n  const newState = addDataToMapUpdater(state, {\n    payload: {\n      datasets: {\n        data: mockRawData,\n        info: {\n          id: 'foo'\n        }\n      }\n    }\n  });\n\n  drainTasksForTesting();\n\n  const expectedUIState = {\n    ...INITIAL_UI_STATE,\n    initialState: {},\n    readOnly: false,\n    currentModal: null\n  };\n\n  t.deepEqual(\n    newState.uiState,\n    expectedUIState,\n    'ui state should be set readOnly:false,currentModal: null'\n  );\n\n  t.end();\n});\n\ntest('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => {\n  const data = processCsvData(testCsvData);\n\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n\n  // old state contain splitMaps\n  let oldState = addDataToMapUpdater(state, {\n    payload: {\n      datasets: {\n        data,\n        info: {\n          id: sampleConfig.dataId\n        }\n      },\n      config: sampleConfig.config\n    }\n  });\n\n  // create datasets from existing tasks, trigger auto create layers\n  oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)};\n\n  const {\n    layers: oldLayers,\n    filters: oldFilters,\n    datasets: oldDatasets,\n    interactionConfig: oldInteractionConfig,\n    splitMaps: oldSplitMaps\n  } = oldState.visState;\n\n  const hexData = processCsvData(testHexIdData);\n  const hexDataId = hexIdDataConfig.dataId;\n\n  // keepExistingConfig is not defined, default to false\n  let nextState1 = addDataToMapUpdater(oldState, {\n    payload: {\n      datasets: {\n        data: hexData,\n        info: {\n          id: hexDataId\n        }\n      },\n      config: hexIdDataConfig.config\n    }\n  });\n\n  // create datasets from existing tasks, trigger auto create layers\n  nextState1 = {\n    ...nextState1,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState1.visState)\n  };\n\n  t.deepEqual(nextState1.visState.layerOrder, ['avlgol'], 'Should contain nextState1 layer order');\n\n  cmpDataset(t, expectedMergedDataset, nextState1.visState.datasets[hexDataId]);\n\n  t.deepEqual(nextState1.visState.splitMaps, [], 'should clear out splitMaps');\n\n  cmpLayers(t, [mergedH3Layer], nextState1.visState.layers);\n  cmpFilters(t, mergedFilters, nextState1.visState.filters);\n\n  // add data and config keep existing data and config\n  let nextState2 = addDataToMapUpdater(oldState, {\n    payload: {\n      datasets: {\n        data: hexData,\n        info: {\n          id: hexDataId\n        }\n      },\n      config: hexIdDataConfig.config,\n      options: {\n        keepExistingConfig: true\n      }\n    }\n  });\n\n  // create datasets from existing tasks, trigger auto create layers\n  nextState2 = {\n    ...nextState2,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState2.visState)\n  };\n\n  const actualVisState = nextState2.visState;\n\n  const newLayers = [...oldLayers, mergedH3Layer];\n  const expectedVisState = {\n    layers: newLayers,\n    filters: [...oldFilters, ...mergedFilters],\n    datasets: 'test seperate',\n    interactionConfig: {\n      ...oldInteractionConfig,\n      tooltip: {\n        ...oldInteractionConfig.tooltip,\n        config: {\n          compareMode: false,\n          compareType: 'absolute',\n          fieldsToShow: {\n            ...oldInteractionConfig.tooltip.config.fieldsToShow,\n            [hexDataId]: [\n              {\n                name: 'hex_id',\n                format: null\n              },\n              {\n                name: 'value',\n                format: null\n              }\n            ]\n          }\n        }\n      }\n    },\n    splitMaps: [\n      {\n        layers: {\n          ...oldSplitMaps[0].layers,\n          avlgol: true\n        }\n      },\n      {\n        layers: {\n          ...oldSplitMaps[1].layers,\n          avlgol: true\n        }\n      }\n    ],\n    layerOrder: [newLayers[2].id, newLayers[0].id, newLayers[1].id]\n  };\n\n  cmpLayers(t, expectedVisState.layers, actualVisState.layers);\n  cmpFilters(t, expectedVisState.filters, actualVisState.filters);\n  // test datasets\n  t.deepEqual(\n    Object.keys(actualVisState.datasets),\n    [sampleConfig.dataId, hexDataId],\n    'should save 2 datasets to state'\n  );\n\n  t.equal(\n    actualVisState.datasets[sampleConfig.dataId],\n    oldDatasets[sampleConfig.dataId],\n    'should keep oldDataset same'\n  );\n\n  cmpDataset(\n    t,\n    expectedMergedDataset,\n    actualVisState.datasets[hexDataId],\n    'should merge and filter hexdata'\n  );\n\n  cmpInteraction(t, expectedVisState.interactionConfig, actualVisState.interactionConfig);\n  t.deepEqual(\n    expectedVisState.layerOrder,\n    actualVisState.layerOrder,\n    'Should create new layer, move it to the top'\n  );\n\n  t.deepEqual(\n    expectedVisState.splitMaps,\n    actualVisState.splitMaps,\n    'Should keep existing splitMaps, add new layers to splitMaps'\n  );\n\n  t.end();\n});\n\ntest('#composerStateReducer - addDataToMapUpdater: readOnly', t => {\n  const datasets = {\n    data: processCsvData(testCsvData),\n    info: {\n      id: sampleConfig.dataId\n    }\n  };\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n\n  // old state contain splitMaps\n  const nextState = addDataToMapUpdater(state, {\n    payload: {\n      datasets,\n      options: {\n        readOnly: true\n      }\n    }\n  });\n  t.equal(nextState.uiState.readOnly, true, 'should set readonly to be true');\n\n  const nextState1 = addDataToMapUpdater(state, {\n    payload: {\n      datasets\n    }\n  });\n  t.equal(nextState1.uiState.readOnly, false, 'should set readonly to be false');\n\n  const nextState2 = addDataToMapUpdater(state, {\n    payload: {\n      datasets,\n      options: {\n        readOnly: false\n      }\n    }\n  });\n  t.equal(nextState2.uiState.readOnly, false, 'should set readonly to be false');\n  t.end();\n});\n\ntest('#composerStateReducer - addDataToMapUpdater: autoCreateLayers', t => {\n  const datasets = {\n    data: processCsvData(testCsvData),\n    info: {\n      id: sampleConfig.dataId\n    }\n  };\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n\n  // old state contain splitMaps\n  const nextState = addDataToMapUpdater(state, {\n    payload: {\n      datasets,\n      options: {\n        autoCreateLayers: false\n      }\n    }\n  });\n  t.equal(nextState.visState.layers.length, 0, 'should not create layers');\n\n  t.end();\n});\n\ntest('#composerStateReducer - replaceDataInMapUpdater', t => {\n  const dataIdToReplace = 'dataset_to_replace';\n  const datasets = {\n    data: processCsvData(testCsvData),\n    info: {\n      id: sampleConfig.dataId\n    }\n  };\n  const datasetToUse = {\n    data: processCsvData(dataWithNulls),\n    info: {\n      id: dataIdToReplace\n    }\n  };\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n\n  // old state contain splitMaps\n  let oldState = addDataToMapUpdater(state, {\n    payload: {\n      datasets,\n      config: sampleConfig.config\n    }\n  });\n\n  // create datasets from existing tasks, trigger auto create layers\n  oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)};\n\n  const oldSavedConfig = state.visState.schema.getConfigToSave(oldState).config;\n  let nextState = replaceDataInMapUpdater(oldState, {\n    payload: {\n      datasetToReplaceId: sampleConfig.dataId,\n      datasetToUse\n    }\n  });\n  // create datasets from existing tasks, trigger auto create layers\n  nextState = {\n    ...nextState,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState.visState)\n  };\n\n  // layers should generate a fit bounds task\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'Should create one fit bounds task');\n  nextState = {\n    ...nextState,\n    mapState: mapStateReducer(nextState.mapState, succeedTaskWithValues(tasks[0], {}))\n  };\n\n  const nextSavedConfig = nextState.visState.schema.getConfigToSave(nextState).config;\n\n  const expectedLayers = oldSavedConfig.visState.layers.map(l => ({\n    ...l,\n    config: {\n      ...l.config,\n      dataId: dataIdToReplace\n    }\n  }));\n\n  const bounds = nextState.visState.layers[0].meta.bounds;\n  const expectedMapState = fitBoundsUpdater(oldState.mapState, {payload: bounds});\n\n  const expectedInteractionConfig = {\n    ...oldSavedConfig.visState.interactionConfig,\n    tooltip: {\n      ...oldSavedConfig.visState.interactionConfig.tooltip,\n      fieldsToShow: {\n        [dataIdToReplace]:\n          oldSavedConfig.visState.interactionConfig.tooltip.fieldsToShow[sampleConfig.dataId]\n      }\n    }\n  };\n\n  // dataWithNulls gps_data.utc_timestamp domain\n  const expectedFilterDomain = [1474071056000, 1474071677000];\n  const expectedFilter = {\n    ...oldSavedConfig.visState.filters[0],\n    dataId: [dataIdToReplace],\n    // reset vaue to bonded by domain\n    value: expectedFilterDomain\n  };\n\n  t.deepEqual(\n    nextState.visState.filters[0].domain,\n    expectedFilterDomain,\n    'Should set corect filter domain'\n  );\n  // compare replaced state with old state\n  Object.keys(oldSavedConfig).forEach(key => {\n    if (key === 'mapState') {\n      // should center map\n      t.deepEqual(nextState.mapState, expectedMapState, 'should center map to new layer;');\n    } else if (key === 'visState') {\n      Object.keys(oldSavedConfig.visState).forEach(prop => {\n        if (prop === 'layers') {\n          t.deepEqual(\n            nextSavedConfig.visState.layers,\n            expectedLayers,\n            'should replace layer dataId'\n          );\n        } else if (prop === 'filters') {\n          t.deepEqual(\n            nextSavedConfig.visState.filters,\n            [expectedFilter],\n            'should replace filter dataId and reset value'\n          );\n        } else if (prop === 'interactionConfig') {\n          t.deepEqual(\n            nextSavedConfig.visState.interactionConfig,\n            expectedInteractionConfig,\n            'should replace interactionConfig dataId'\n          );\n        } else {\n          t.deepEqual(\n            nextSavedConfig.visState[prop],\n            oldSavedConfig.visState[prop],\n            `visState.${prop} should not change`\n          );\n        }\n      });\n    } else {\n      // mapStyle\n      t.deepEqual(nextSavedConfig[key], oldSavedConfig[key], 'mapStyle should not change');\n    }\n  });\n\n  t.deepEqual(nextState.visState.layerToBeMerged, [], 'should reset layerToBeMerged');\n  t.deepEqual(nextState.visState.filterToBeMerged, [], 'should reset filterToBeMerged');\n  t.deepEqual(nextState.visState.interactionToBeMerged, {}, 'should reset interactionToBeMerged');\n  t.deepEqual(nextState.visState.splitMapsToBeMerged, [], 'should reset splitMapsToBeMerged');\n  t.end();\n});\n\ntest('#composerStateReducer - replaceDataInMapUpdater: same dataId', t => {\n  const datasets = {\n    data: processCsvData(testCsvData),\n    info: {\n      id: sampleConfig.dataId\n    }\n  };\n  const datasetToUse = {\n    data: processCsvData(dataWithNulls),\n    info: {\n      id: sampleConfig.dataId\n    }\n  };\n  const state = keplerGlReducer({}, registerEntry({id: 'test'})).test;\n\n  // old state contain splitMaps\n  let oldState = addDataToMapUpdater(state, {\n    payload: {\n      datasets,\n      config: sampleConfig.config\n    }\n  });\n  // create datasets from existing tasks, trigger auto create layers\n  oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)};\n\n  const oldSavedConfig = state.visState.schema.getConfigToSave(oldState).config;\n\n  let nextState = replaceDataInMapUpdater(oldState, {\n    payload: {\n      datasetToReplaceId: sampleConfig.dataId,\n      datasetToUse\n    }\n  });\n  // create datasets from existing tasks, trigger auto create layers\n  nextState = {\n    ...nextState,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState.visState)\n  };\n\n  // dataset should be replaced\n  t.ok(nextState.visState.datasets[sampleConfig.dataId], ' dataset should be replaced');\n  const nextSavedConfig = nextState.visState.schema.getConfigToSave(nextState).config;\n\n  const expectedLayers = oldSavedConfig.visState.layers.map(l => ({\n    ...l,\n    config: {\n      ...l.config,\n      dataId: sampleConfig.dataId\n    }\n  }));\n  t.deepEqual(nextSavedConfig.visState.layers, expectedLayers, 'should replace layer dataId');\n  t.end();\n});\n\ntest('#composerStateReducer - replaceDataInMapUpdater: syncedTimeFilter & match', t => {\n  const oldState = StateWSyncedTimeFilter;\n  const dataIdToReplace = 'dataset_to_replace';\n\n  const datasetToUse = {\n    data: processCsvData(dataWithNulls),\n    info: {\n      id: dataIdToReplace\n    }\n  };\n\n  let nextState = replaceDataInMapUpdater(oldState, {\n    payload: {\n      datasetToReplaceId: testCsvDataSlice1Id,\n      datasetToUse\n    }\n  });\n  // create datasets from existing tasks, trigger auto create layers\n  nextState = {\n    ...nextState,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState.visState)\n  };\n\n  const oldFilter = oldState.visState.filters[0];\n  const expectedFilter = {\n    ...oldState.visState.filters[0],\n    dataId: [dataIdToReplace, testCsvDataSlice2Id],\n    // union [147407 1056000, 147407 1677000]; [147407 1301000, 1474072208000];\n    domain: [1474071056000, 1474072208000],\n    // value [1474071116000, 1474072188000] is still within so it doesnt change\n    value: oldState.visState.filters[0].value,\n    timeBins: {},\n    plotType: {\n      ...oldState.visState.filters[0].plotType,\n      colorsByDataId: {\n        [dataIdToReplace]: '#FF0000',\n        'test-csv-data-2': '#00FF00'\n      }\n    }\n  };\n\n  // should recalculate timeBins based on new datasets\n  expectedFilter.timeBins = getTimeBins(\n    expectedFilter,\n    nextState.visState.datasets,\n    oldFilter.plotType.interval\n  );\n\n  t.deepEqual(\n    nextState.visState.filters,\n    [expectedFilter],\n    'should replace dataset with syncd filter'\n  );\n  const expectedDomain0 = 1474071056000;\n  const expectedGpuFilter = {\n    filterRange: [\n      [expectedFilter.value[0] - expectedDomain0, expectedFilter.value[1] - expectedDomain0],\n      [0, 0],\n      [0, 0],\n      [0, 0]\n    ],\n    filterValueUpdateTriggers: {\n      gpuFilter_0: {name: 'gps_data.utc_timestamp', domain0: expectedDomain0},\n      gpuFilter_1: null,\n      gpuFilter_2: null,\n      gpuFilter_3: null\n    },\n    filterValueAccessor: 'dont test me'\n  };\n  t.deepEqual(\n    nextState.visState.datasets[dataIdToReplace].gpuFilter.filterRange,\n    expectedGpuFilter.filterRange,\n    'gpu filterRange should be correct'\n  );\n  t.deepEqual(\n    nextState.visState.datasets[dataIdToReplace].gpuFilter.filterValueUpdateTriggers,\n    expectedGpuFilter.filterValueUpdateTriggers,\n    'gpu filterValueUpdateTriggers should be correct'\n  );\n  t.deepEqual(\n    nextState.visState.datasets[testCsvDataSlice2Id].gpuFilter.filterRange,\n    expectedGpuFilter.filterRange,\n    'gpu filterRange should be correct'\n  );\n  t.deepEqual(\n    nextState.visState.datasets[testCsvDataSlice2Id].gpuFilter.filterValueUpdateTriggers,\n    expectedGpuFilter.filterValueUpdateTriggers,\n    'gpu filterValueUpdateTriggers should be correct'\n  );\n\n  t.end();\n});\n\ntest('#composerStateReducer - replaceDataInMapUpdater: syncedTimeFilter & no match', t => {\n  const oldState = StateWSyncedTimeFilter;\n  const dataIdToReplace = 'dataset_to_replace';\n\n  const datasetToUse = {\n    // a different dataset with no match\n    data: processCsvData(testHexIdData),\n    info: {\n      id: dataIdToReplace\n    }\n  };\n\n  let nextState = replaceDataInMapUpdater(oldState, {\n    payload: {\n      datasetToReplaceId: testCsvDataSlice1Id,\n      datasetToUse\n    }\n  });\n  // create datasets from existing tasks, trigger auto create layers\n  nextState = {\n    ...nextState,\n    visState: applyExistingDatasetTasks(visStateReducer, nextState.visState)\n  };\n\n  t.equal(nextState.visState.filters.length, 0, 'should not merge filter if no match');\n  t.equal(\n    nextState.visState.layers.length,\n    1,\n    'should only keep 1 layer (not able to merge the other)'\n  );\n\n  t.deepEqual(oldState.visState.layers[1], nextState.visState.layers[0], 'should keep 1 layer');\n\n  t.equal(nextState.visState.layerToBeMerged.length, 1, 'should keep unmerged in layerToBeMerged');\n  t.equal(\n    nextState.visState.filterToBeMerged.length,\n    1,\n    'should keep unmerged in filterToBeMerged'\n  );\n\n  t.deepEqual(nextState.visState.interactionToBeMerged, {}, 'should reset interactionToBeMerged');\n  t.deepEqual(nextState.visState.splitMapsToBeMerged, [], 'should reset splitMapsToBeMerged');\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// test reducers\nimport './map-state-test';\nimport './map-style-test';\nimport './vis-state-test';\nimport './ui-state-test';\nimport './composer-state-test';\nimport './provider-state-test';\nimport './root-test';\n\n// test mergers\nimport './vis-state-merger-test';\n"
  },
  {
    "path": "test/node/reducers/map-state-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {\n  updateMap,\n  togglePerspective,\n  fitBounds,\n  toggleSplitMap,\n  toggleSplitMapViewport,\n  receiveMapConfig\n} from '@kepler.gl/actions';\n\nimport {\n  mapStateReducer as reducer,\n  mapStateReducerFactory,\n  INITIAL_MAP_STATE\n} from '@kepler.gl/reducers';\n\nconst InitialMapState = reducer(undefined, {});\ntest('#mapStateReducer', t => {\n  const newState = reducer(undefined, {});\n\n  t.deepEqual(\n    newState,\n    {...INITIAL_MAP_STATE, initialState: {}},\n    'should return the initial state'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducerFactory', t => {\n  const mapStateReducer = mapStateReducerFactory({dragRotate: true});\n  const newState = mapStateReducer(undefined, {});\n\n  t.deepEqual(\n    newState,\n    {...INITIAL_MAP_STATE, dragRotate: true, initialState: {dragRotate: true}},\n    'should return the initial state'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> UPDATE_MAP', t => {\n  const mapUpdate = {\n    latitude: 24.123,\n    longitude: 120.839,\n    zoom: 2.3\n  };\n  const expectedState = {...InitialMapState, ...mapUpdate};\n\n  const newState = reducer(undefined, updateMap(mapUpdate, 0));\n\n  t.deepEqual(newState, expectedState, 'should update map longitude and latitude');\n\n  t.end();\n});\n\n// eslint-disable-next-line max-statements\ntest('#mapStateReducer -> UPDATE_MAP - minZoom/maxZoom', t => {\n  let mapUpdate = {\n    zoom: 9,\n    maxZoom: 12\n  };\n  let expectedState = {...InitialMapState, ...mapUpdate};\n  let newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'If zoom < maxZoom - zoom should stay the same');\n\n  mapUpdate = {\n    zoom: 14,\n    maxZoom: 12\n  };\n  expectedState = {...InitialMapState, ...mapUpdate, ...{zoom: mapUpdate.maxZoom}};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'If zoom > maxZoom - zoom should be equal to maxZoom');\n\n  mapUpdate = {\n    zoom: 15,\n    minZoom: 12\n  };\n  expectedState = {...InitialMapState, ...mapUpdate};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'If zoom > minZoom - zoom should stay the same');\n\n  mapUpdate = {\n    zoom: 9,\n    minZoom: 12\n  };\n  expectedState = {...InitialMapState, ...mapUpdate, ...{zoom: mapUpdate.minZoom}};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'If zoom < minZoom - zoom should be equal to minZoom');\n\n  mapUpdate = {\n    zoom: 9,\n    minZoom: 3,\n    maxZoom: 15\n  };\n  expectedState = {...InitialMapState, ...mapUpdate};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'If minZoom < zoom < maxZoom - zoom should stay the same');\n\n  mapUpdate = {\n    zoom: 9,\n    minZoom: 9,\n    maxZoom: 9\n  };\n  expectedState = {...InitialMapState, ...mapUpdate};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(\n    newState,\n    expectedState,\n    'If minZoom === zoom === maxZoom - zoom should stay the same'\n  );\n\n  mapUpdate = {\n    zoom: 15,\n    minZoom: 3,\n    maxZoom: 12\n  };\n  expectedState = {...InitialMapState, ...mapUpdate, ...{zoom: mapUpdate.maxZoom}};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(\n    newState,\n    expectedState,\n    'If minZoom < maxZoom < zoom - zoom should be equal to maxZoom'\n  );\n\n  mapUpdate = {\n    zoom: 3,\n    minZoom: 6,\n    maxZoom: 12\n  };\n  expectedState = {...InitialMapState, ...mapUpdate, ...{zoom: mapUpdate.minZoom}};\n  newState = reducer(undefined, updateMap(mapUpdate, 0));\n  t.deepEqual(\n    newState,\n    expectedState,\n    'If zoom < minZoom < maxZoom - zoom should be equal to minZoom'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> UPDATE_MAP - maxBounds', t => {\n  let state = {\n    ...InitialMapState,\n    latitude: 37.685430657228906,\n    longitude: -122.20643775128097,\n    zoom: 5,\n    width: 640,\n    height: 480\n  };\n  let mapUpdate = {\n    maxBounds: [-122.47705311445556, 37.52481163037179, -121.93582238810639, 37.846049684086026]\n  };\n  let expectedState = {\n    ...state,\n    ...mapUpdate,\n    ...{\n      latitude: 37.68560457001023,\n      longitude: -122.20643775128097,\n      zoom: 9.699465540852673\n    }\n  };\n  let newState = reducer(state, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'maxBounds is snapped to the viewport');\n\n  state = {\n    ...InitialMapState,\n    latitude: 37.685430657228906,\n    longitude: -122.20643775128097,\n    zoom: 9,\n    width: 640,\n    height: 480,\n    maxBounds: [-122.47705311445556, 37.52481163037179, -121.93582238810639, 37.846049684086026]\n  };\n  mapUpdate = {\n    zoom: 12\n  };\n  expectedState = {...state, ...mapUpdate};\n  newState = reducer(state, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'Viewport is within maxBounds - zoomed in');\n\n  state = {\n    ...InitialMapState,\n    latitude: 37.685430657228906,\n    longitude: -122.20643775128097,\n    zoom: 9,\n    width: 640,\n    height: 480,\n    maxBounds: [-122.47705311445556, 37.52481163037179, -121.93582238810639, 37.846049684086026]\n  };\n  mapUpdate = {\n    zoom: 8\n  };\n  expectedState = {...state};\n  newState = reducer(state, updateMap(mapUpdate, 0));\n  t.deepEqual(newState, expectedState, 'Viewport is outside the maxBounds - zoomed out');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> UPDATE_MAP - split map and unsynced viewports', t => {\n  // toggle to split mode\n  let newState = reducer(INITIAL_MAP_STATE, toggleSplitMap());\n  // change to unsynced viewports and retain default isZoomLocked\n  newState = reducer(newState, toggleSplitMapViewport({isViewportSynced: false}));\n  // and then update map for unsynced viewport of mapIndex 0\n  let mapUpdate = {\n    latitude: 24.123,\n    longitude: 120.839,\n    zoom: 2.3\n  };\n  const firstUnsyncedSplitMapViewportBeforeUpdatingMapState = {...newState.splitMapViewports[0]};\n  newState = reducer(newState, updateMap(mapUpdate, 0));\n\n  t.notDeepEqual(\n    newState.splitMapViewports[0],\n    firstUnsyncedSplitMapViewportBeforeUpdatingMapState,\n    'unlocked zoom: updating mapIndex 0 should change the same split map viewport'\n  );\n\n  t.deepEqual(\n    newState.splitMapViewports[1],\n    firstUnsyncedSplitMapViewportBeforeUpdatingMapState,\n    'unlocked zoom: updating mapIndex 0 should not change the other split map viewport'\n  );\n\n  t.notEqual(\n    newState.splitMapViewports[0].zoom,\n    newState.splitMapViewports[1].zoom,\n    'unlocked zoom: should not set both viewports to the same zoom prop'\n  );\n\n  // retain unsycned viewports and change to isZoomLocked true\n  newState = reducer(newState, toggleSplitMapViewport({isZoomLocked: true}));\n  // and then update map for unsynced viewport and locked zoom of mapIndex 1\n  mapUpdate = {\n    latitude: 25,\n    longitude: 123,\n    zoom: 10\n  };\n  const expectedFistUnsyncedSplitMapViewportWithLockedZoom = {\n    ...newState.splitMapViewports[0],\n    zoom: mapUpdate.zoom\n  };\n  const secondUnsyncedSplitMapViewportBeforeUpdatingMapState = {...newState.splitMapViewports[1]};\n  newState = reducer(newState, updateMap(mapUpdate, 1));\n\n  t.notDeepEqual(\n    newState.splitMapViewports[1],\n    secondUnsyncedSplitMapViewportBeforeUpdatingMapState,\n    'locked zoom: updating mapIndex 1 should change the same split map viewport'\n  );\n\n  t.deepEqual(\n    newState.splitMapViewports[0],\n    expectedFistUnsyncedSplitMapViewportWithLockedZoom,\n    'locked zoom: updating mapIndex 1 should only change the zoom property of the other split map viewport but not other properties'\n  );\n\n  t.equal(\n    newState.splitMapViewports[0].zoom,\n    newState.splitMapViewports[1].zoom,\n    'locked zoom: should set both viewports to the same zoom'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> TOGGLE_PERSPECTIVE', t => {\n  const newState = reducer(undefined, {});\n  t.equal(newState.dragRotate, false, 'dragRotate should default to false');\n\n  const newState2 = reducer(undefined, togglePerspective());\n  t.equal(newState2.dragRotate, true, 'dragRotate toggle should set it to true');\n  t.equal(newState2.pitch, 50, 'pitch should set to default');\n  t.equal(newState2.bearing, 24, 'bearing should set to default');\n\n  const newState3 = reducer(newState2, togglePerspective());\n  t.equal(newState3.dragRotate, false, 'dragRotate 2nd toggle should set it to false');\n  t.equal(newState3.pitch, 0, 'pitch should set to zero');\n  t.equal(newState3.bearing, 0, 'bearing should set to zero');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> TOGGLE_PERSPECTIVE - split map and unsynced viewports', t => {\n  // toggle to split mode\n  let newState = reducer(INITIAL_MAP_STATE, toggleSplitMap());\n  // change to unsynced viewports\n  newState = reducer(\n    newState,\n    toggleSplitMapViewport({isViewportSynced: false, isZoomLocked: false})\n  );\n  // and then toggle perspective\n  newState = reducer(newState, togglePerspective());\n\n  t.equal(\n    newState.dragRotate,\n    newState.splitMapViewports[0].dragRotate,\n    'split map with unsynced viewports: dragRotate should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.dragRotate,\n    newState.splitMapViewports[1].dragRotate,\n    'split map with unsynced viewports: dragRotate should be copied to the second split viewport'\n  );\n  t.equal(\n    newState.pitch,\n    newState.splitMapViewports[0].pitch,\n    'split map with unsynced viewports: pitch should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.pitch,\n    newState.splitMapViewports[1].pitch,\n    'split map with unsynced viewports: pitch should be copied to the second split viewport'\n  );\n  t.equal(\n    newState.bearing,\n    newState.splitMapViewports[0].bearing,\n    'split map with unsynced viewports: bearing should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.bearing,\n    newState.splitMapViewports[1].bearing,\n    'split map with unsynced viewports: bearing should be copied to the second split viewport'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> FIT_BOUNDS', t => {\n  // default input and output in @mapbox/geo-viewport\n  // https://github.com/mapbox/geo-viewport\n\n  const bounds = [5.668343999999995, 45.111511000000014, 5.852471999999996, 45.26800200000002];\n\n  const mapUpdate = {\n    width: 640,\n    height: 480\n  };\n\n  const expected = {\n    center: [5.7604079999999955, 45.189756500000016],\n    zoom: 10.569800116329509\n  };\n\n  const stateWidthMapDimension = reducer(undefined, updateMap(mapUpdate, 0));\n  const updatedState = reducer(stateWidthMapDimension, fitBounds(bounds));\n\n  t.equal(updatedState.latitude, expected.center[1], 'should fit latitude');\n  t.equal(updatedState.longitude, expected.center[0], 'should fit longitude');\n  t.equal(updatedState.zoom, expected.zoom, 'should fit zoom');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> FIT_BOUNDS - split map and unsynced viewports', t => {\n  // default input and output in @mapbox/geo-viewport\n  // https://github.com/mapbox/geo-viewport\n\n  const bounds = [5.668343999999995, 45.111511000000014, 5.852471999999996, 45.26800200000002];\n\n  // toggle to split mode\n  let newState = reducer(INITIAL_MAP_STATE, toggleSplitMap());\n  // change to unsynced viewports\n  newState = reducer(\n    newState,\n    toggleSplitMapViewport({isViewportSynced: false, isZoomLocked: false})\n  );\n  // and then fit bounds\n  newState = reducer(newState, fitBounds(bounds));\n\n  t.equal(\n    newState.latitude,\n    newState.splitMapViewports[0].latitude,\n    'split map with unsynced viewports: latitude should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.latitude,\n    newState.splitMapViewports[1].latitude,\n    'split map with unsynced viewports: latitude should be copied to the second split viewport'\n  );\n  t.equal(\n    newState.longitude,\n    newState.splitMapViewports[0].longitude,\n    'split map with unsynced viewports: longitude should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.longitude,\n    newState.splitMapViewports[1].longitude,\n    'split map with unsynced viewports: longitude should be copied to the second split viewport'\n  );\n  t.equal(\n    newState.zoom,\n    newState.splitMapViewports[0].zoom,\n    'split map with unsynced viewports: zoom should be copied to the first split viewport'\n  );\n  t.equal(\n    newState.zoom,\n    newState.splitMapViewports[1].zoom,\n    'split map with unsynced viewports: zoom should be copied to the second split viewport'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> FIT_BOUNDS.invalid', t => {\n  // default input and output in @mapbox/geo-viewport\n  // https://github.com/mapbox/geo-viewport\n\n  const mapUpdate = {\n    width: 640,\n    height: 480\n  };\n\n  const stateWidthMapDimension = reducer(undefined, updateMap(mapUpdate, 0));\n  const updatedState = reducer(stateWidthMapDimension, fitBounds(null));\n  t.equal(updatedState, stateWidthMapDimension, 'should not update state when bounds is invalid');\n  const updatedState2 = reducer(stateWidthMapDimension, fitBounds([500, -100, 322, 9]));\n  t.equal(updatedState2, stateWidthMapDimension, 'should not update state when bounds is invalid');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> SPLIT_MAP: toggle', t => {\n  let newState = reducer(INITIAL_MAP_STATE, toggleSplitMap());\n\n  const expectedState = {\n    ...INITIAL_MAP_STATE,\n    isSplit: true,\n    width: 400\n  };\n\n  // validate the first split\n  t.deepEqual(newState, expectedState, 'should validate toggle split view');\n\n  // go back to single view\n  newState = reducer(newState, toggleSplitMap());\n  t.deepEqual(newState, INITIAL_MAP_STATE, 'should validate toggle back from split view');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> SPLIT_MAP: upload mapState config to update split map state', t => {\n  let state = {\n    ...INITIAL_MAP_STATE,\n    isSplit: true,\n    width: 400\n  };\n\n  // cases:\n\n  // 1. state split: true - isSplit: true\n  // do nothing\n  let newState = reducer(state, receiveMapConfig({mapState: {isSplit: true}}));\n  t.deepEqual(\n    newState,\n    state,\n    'setting isSplit to true when state is already split should not change the state'\n  );\n\n  // 2. state split: false - isSplit: false\n  // do nothing\n  state = {\n    ...state,\n    isSplit: false,\n    width: 800\n  };\n  newState = reducer(state, receiveMapConfig({mapState: {isSplit: false}}));\n  t.deepEqual(\n    newState,\n    state,\n    'setting isSplit to false when state is not split should not change the state'\n  );\n\n  // 3. state split: true - isSplit: false\n  // double width\n  state = {\n    ...state,\n    isSplit: true,\n    width: 400\n  };\n  newState = reducer(state, receiveMapConfig({mapState: {isSplit: false}}));\n  t.deepEqual(\n    newState,\n    {\n      ...state,\n      width: 800,\n      isSplit: false\n    },\n    'setting isSplit to false when state is already split should double width'\n  );\n\n  // 4. state split: false - isSplit: true\n  // split width\n  state = {\n    ...state,\n    isSplit: false,\n    width: 800\n  };\n  newState = reducer(state, receiveMapConfig({mapState: {isSplit: true}}));\n  t.deepEqual(\n    newState,\n    {\n      ...state,\n      width: 400,\n      isSplit: true\n    },\n    'setting isSplit to true when state is already split should reduce width by half'\n  );\n\n  t.end();\n});\n\ntest('#mapStateReducer -> SPLIT_MAP: close map at specific point', t => {\n  let newState = reducer(INITIAL_MAP_STATE, toggleSplitMap());\n\n  const expectedState = {\n    ...INITIAL_MAP_STATE,\n    isSplit: true,\n    width: 400\n  };\n\n  // validate the first split\n  t.deepEqual(newState, expectedState, 'should validate toggle split view');\n\n  // go back to single view\n  newState = reducer(newState, toggleSplitMap(1));\n  t.deepEqual(newState, INITIAL_MAP_STATE, 'should validate toggle back from split view');\n\n  t.end();\n});\n\ntest('#mapStateReducer -> TOGGLE_SPLIT_MAP_VIEWPORT', t => {\n  let newState = reducer(INITIAL_MAP_STATE, {});\n\n  let expectedState = {\n    ...INITIAL_MAP_STATE,\n    isSplit: false,\n    isViewportSynced: true,\n    isZoomLocked: false,\n    splitMapViewports: []\n  };\n\n  // validate defaults before making state changes\n  t.deepEqual(\n    newState,\n    expectedState,\n    'should retain default initial state values for isSplit, isViewportSynced, isZoomLocked, and splitMapViewports'\n  );\n\n  // toggle to split mode and retain defaults of synced viewports and unlocked zoom\n  newState = reducer(newState, toggleSplitMap());\n  newState = reducer(\n    newState,\n    toggleSplitMapViewport({isViewportSynced: true, isZoomLocked: false})\n  );\n\n  expectedState = {\n    ...newState,\n    isSplit: true,\n    isViewportSynced: true,\n    isZoomLocked: false,\n    splitMapViewports: []\n  };\n\n  t.deepEqual(\n    newState,\n    expectedState,\n    'toggling isSplit to true and setting isViewportSynced to true and isZoomLocked to false should retain default related initial state values'\n  );\n\n  // keep split mode and change synced viewports, but keep zoom lock as false\n  const splitMapViewportsBeforeOnlyChangingIsViewportSyncedFalse = [...newState.splitMapViewports];\n  newState = reducer(newState, toggleSplitMapViewport({isViewportSynced: false}));\n\n  t.equal(\n    newState.isViewportSynced,\n    false,\n    'changing isViewportSynced to false should update the same prop in next state'\n  );\n\n  t.notDeepEqual(\n    newState.splitMapViewports,\n    splitMapViewportsBeforeOnlyChangingIsViewportSyncedFalse,\n    'changing isViewportSynced to false while retaining isZoomLocked as false should modify the splitMapViewports array'\n  );\n\n  // change one of the split viewports' zoom levels\n  // then switch on locked zoom while retaining unsynced\n  newState = reducer(newState, updateMap({zoom: 5}, 1));\n  newState = reducer(newState, toggleSplitMapViewport({isZoomLocked: true}));\n\n  t.equal(\n    newState.isZoomLocked,\n    true,\n    'changing isZoomLocked to true should update the same prop in next state'\n  );\n\n  // and test if they both now have the same zoom\n  t.equal(\n    newState.splitMapViewports[0].zoom,\n    newState.splitMapViewports[1].zoom,\n    'while isViewportSynced is false, changing isZoomLocked to true should modify the splitMapViewports array to have matching zoom values'\n  );\n\n  // switch off locked zoom while retaining unsynced\n  const splitMapViewportsBeforeOnlyChangingIsZoomLockedFalse = [...newState.splitMapViewports];\n  newState = reducer(newState, toggleSplitMapViewport({isZoomLocked: false}));\n\n  t.equal(\n    newState.isZoomLocked,\n    false,\n    'changing isZoomLocked to false should update the same prop in next state'\n  );\n\n  t.deepEqual(\n    newState.splitMapViewports,\n    splitMapViewportsBeforeOnlyChangingIsZoomLockedFalse,\n    'while isViewportSynced is false, changing isZoomLocked to false should not modify the splitMapViewports array'\n  );\n\n  // toggle from unsynced to synced viewports while retaining zoom lock as false\n  newState = reducer(newState, toggleSplitMapViewport({isViewportSynced: true}));\n\n  t.deepEqual(\n    newState.splitMapViewports,\n    [],\n    'changing isViewportSynced to false should change the splitMapViewports array to be empty'\n  );\n\n  t.equal(\n    newState.isZoomLocked,\n    false,\n    'changing isViewportSynced to false should also retain isZoomLocked as false'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/map-style-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {drainTasksForTesting, succeedTaskWithValues, errorTaskInTest} from 'react-palm/tasks';\n\nimport {\n  mapStyleReducer as reducer,\n  INITIAL_MAP_STYLE,\n  loadMapStylesUpdater,\n  getDefaultMapStyles,\n  getInitialInputStyle\n} from '@kepler.gl/reducers';\nimport {\n  keplerGlInit,\n  receiveMapConfig,\n  loadMapStyles,\n  mapConfigChange,\n  mapStyleChange,\n  inputMapStyle,\n  editCustomMapStyle,\n  removeCustomMapStyle\n} from '@kepler.gl/actions';\nimport SchemaManager from '@kepler.gl/schemas';\nimport {DEFAULT_MAPBOX_API_URL, KEPLER_UNFOLDED_BUCKET, NO_MAP_ID} from '@kepler.gl/constants';\n\n// helpers\nimport {\n  StateWCustomMapStyleLegacy,\n  StateWCustomMapStyleLocal,\n  StateWCustomMapStyleManaged\n} from 'test/helpers/mock-state';\nimport {MOCK_MAP_STYLE, MOCK_MAP_STYLE_LIGHT} from 'test/helpers/mock-map-styles';\n\nconst InitialMapStyle = reducer(undefined, {});\n\ntest('#mapStyleReducer', t => {\n  const newState = reducer(undefined, {});\n\n  t.deepEqual(\n    newState,\n    {\n      ...INITIAL_MAP_STYLE,\n      initialState: {}\n    },\n    'should return the initial map style'\n  );\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> INIT', t => {\n  const initialState = reducer(InitialMapStyle, keplerGlInit());\n  t.deepEqual(\n    initialState,\n    {\n      ...INITIAL_MAP_STYLE,\n      initialState: {}\n    },\n    'initialize map style with no argument'\n  );\n\n  const newState = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token',\n      mapboxApiUrl: 'http://mydomain.com'\n    })\n  );\n\n  t.deepEqual(\n    newState,\n    {\n      ...INITIAL_MAP_STYLE,\n      initialState: {},\n      mapboxApiAccessToken: 'smoothies_secret_token',\n      mapboxApiUrl: 'http://mydomain.com'\n    },\n    'initialize map style with mapboxApiAccessToken'\n  );\n  t.end();\n});\n\ntest('#mapStyleReducer -> INIT & LOAD_MAP_STYLES', t => {\n  const newState = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  t.deepEqual(\n    newState,\n    {\n      ...INITIAL_MAP_STYLE,\n      initialState: {},\n      mapboxApiAccessToken: 'smoothies_secret_token',\n      mapboxApiUrl: DEFAULT_MAPBOX_API_URL\n    },\n    'initialize map style with mapboxApiAccessToken and mapStylesReplaceDefault; mapStyles empty'\n  );\n\n  const finalState = loadMapStylesUpdater(newState, {\n    payload: {newStyles: INITIAL_MAP_STYLE.mapStyles}\n  });\n\n  // should start loading default dark style\n  t.deepEqual(\n    finalState,\n    {\n      ...newState,\n      isLoading: {\n        'dark-matter': true\n      }\n    },\n    'user provided mapStyles are populated, defaults ignored'\n  );\n\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'should dispatch request map style task');\n\n  const expectedTask = {\n    payload: [\n      {\n        id: 'dark-matter',\n        url: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'\n      }\n    ]\n  };\n\n  t.deepEqual(tasks[0].payload, expectedTask.payload, 'should have correct load map style task');\n  t.end();\n});\n\ntest('#mapStyleReducer -> INIT & LOAD_MAP_STYLES ->  mapStylesReplaceDefault: true', t => {\n  const myMapStyle = {\n    id: 'default dark v9',\n    label: 'default dark v9',\n    url: 'mapbox://styles/mapbox/dark-v9',\n    icon: `images/light.png`,\n    layerGroups: []\n  };\n  const newState = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token',\n      mapStylesReplaceDefault: true\n    })\n  );\n\n  t.deepEqual(\n    newState,\n    {\n      ...INITIAL_MAP_STYLE,\n      mapStyles: {},\n      initialState: {},\n      mapboxApiAccessToken: 'smoothies_secret_token',\n      mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n      mapStylesReplaceDefault: true\n    },\n    'initialize map style with mapboxApiAccessToken and mapStylesReplaceDefault; mapStyles empty'\n  );\n\n  const mapStyles = {\n    [myMapStyle.id]: myMapStyle\n  };\n\n  const finalState = loadMapStylesUpdater(newState, {payload: {newStyles: mapStyles}});\n\n  t.deepEqual(\n    finalState,\n    {...newState, mapStyles},\n    'user provided mapStyles are populated, defaults ignored'\n  );\n\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 0, 'should not dispatch request map style task');\n\n  t.end();\n});\n\ntest(\"#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: 'LOCAL')\", t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'})\n  );\n\n  const stateToSave = StateWCustomMapStyleLocal;\n\n  // save state\n  const savedState = SchemaManager.getConfigToSave(stateToSave);\n\n  // load state\n  const stateLoaded = SchemaManager.parseSavedConfig(savedState);\n\n  const tmpStateWithConfig = reducer(stateWithToken, receiveMapConfig(stateLoaded));\n\n  const defaultMapStyles = getDefaultMapStyles(KEPLER_UNFOLDED_BUCKET);\n\n  const expectedStateWithConfig = {\n    styleType: 'smoothie_the_cat',\n    visibleLayerGroups: {label: true, road: true},\n    topLayerGroups: {},\n    mapStyles: {\n      smoothie_the_cat: {\n        accessToken: 'secret_token',\n        custom: 'LOCAL',\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        id: 'smoothie_the_cat',\n        label: 'Smoothie the Cat',\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      },\n      ...defaultMapStyles\n    },\n    isLoading: {\n      smoothie_the_cat: true\n    },\n    mapboxApiAccessToken: 'smoothies_secret_token',\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: [1, 2, 3],\n    custom3DBuildingColor: true,\n    backgroundColor: [0, 0, 0],\n    initialState: {},\n    bottomMapStyle: undefined,\n    topMapStyle: undefined\n  };\n\n  t.deepEqual(tmpStateWithConfig, expectedStateWithConfig, 'should load saved map style config');\n  Object.keys(tmpStateWithConfig).forEach(key => {\n    t.deepEqual(\n      tmpStateWithConfig[key],\n      expectedStateWithConfig[key],\n      'should load saved map style config'\n    );\n  });\n  const [actionTask1, ...more1] = drainTasksForTesting();\n  t.equal(more1.length, 0, 'should return 1 tasks');\n\n  const expectedTask = {\n    payload: [\n      {\n        id: 'smoothie_the_cat',\n        url: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat?pluginName=Keplergl&access_token=secret_token'\n      }\n    ]\n  };\n\n  t.deepEqual(actionTask1.payload, expectedTask.payload, 'should create task to load map styles');\n\n  const resultState1 = reducer(\n    tmpStateWithConfig,\n    succeedTaskWithValues(actionTask1, [\n      {id: 'smoothie_the_cat', style: {layers: [], name: 'smoothie_the_cat'}}\n    ])\n  );\n\n  const expectedMapStyles = {\n    smoothie_the_cat: {\n      accessToken: 'secret_token',\n      custom: 'LOCAL',\n      icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n      id: 'smoothie_the_cat',\n      label: 'Smoothie the Cat',\n      url: 'mapbox://styles/shanhe/smoothie.the.cat',\n      style: {layers: [], name: 'smoothie_the_cat'},\n      layerGroups: []\n    },\n    ...defaultMapStyles\n  };\n\n  const expectedMapStyleState = {\n    styleType: 'smoothie_the_cat',\n    visibleLayerGroups: {},\n    topLayerGroups: {},\n    mapStyles: expectedMapStyles,\n    mapboxApiAccessToken: 'smoothies_secret_token',\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: [1, 2, 3],\n    custom3DBuildingColor: true,\n    backgroundColor: [0, 0, 0],\n    initialState: {},\n    bottomMapStyle: {layers: [], name: 'smoothie_the_cat'},\n    topMapStyle: null,\n    editable: 0,\n    isLoading: {\n      smoothie_the_cat: false\n    }\n  };\n\n  t.deepEqual(\n    Object.keys(resultState1).sort(),\n    Object.keys(expectedMapStyleState).sort(),\n    'mapStyle state should have same keys'\n  );\n\n  Object.keys(resultState1).forEach(key => {\n    t.deepEqual(\n      resultState1[key],\n      expectedMapStyleState[key],\n      `should update state,${key} with loaded map styles`\n    );\n  });\n\n  const savedConfig = SchemaManager.getConfigToSave({mapStyle: resultState1});\n  const expectedSaved = {\n    version: 'v1',\n    config: {\n      mapStyle: {\n        styleType: 'smoothie_the_cat',\n        topLayerGroups: {},\n        visibleLayerGroups: {},\n        threeDBuildingColor: [1, 2, 3],\n        backgroundColor: [0, 0, 0],\n        mapStyles: {\n          smoothie_the_cat: {\n            accessToken: 'secret_token',\n            custom: 'LOCAL',\n            icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n            id: 'smoothie_the_cat',\n            label: 'Smoothie the Cat',\n            url: 'mapbox://styles/shanhe/smoothie.the.cat'\n          }\n        }\n      }\n    }\n  };\n\n  t.deepEqual(\n    Object.keys(savedConfig).sort(),\n    Object.keys(expectedSaved).sort(),\n    'mapStyle state saved should have same keys'\n  );\n\n  Object.keys(savedConfig).forEach(key => {\n    t.deepEqual(savedConfig[key], expectedSaved[key], `should save state.${key} with correctly`);\n  });\n\n  t.end();\n});\n\ntest(\"#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: 'MANAGED')\", t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'})\n  );\n\n  const stateToSave = StateWCustomMapStyleManaged;\n\n  // save state\n  const savedState = SchemaManager.getConfigToSave(stateToSave);\n\n  // load state\n  const stateLoaded = SchemaManager.parseSavedConfig(savedState);\n\n  const tmpStateWithConfig = reducer(stateWithToken, receiveMapConfig(stateLoaded));\n\n  const defaultMapStyles = getDefaultMapStyles(KEPLER_UNFOLDED_BUCKET);\n\n  const expectedStateWithConfig = {\n    styleType: 'smoothie_the_cat',\n    visibleLayerGroups: {label: true, road: true},\n    topLayerGroups: {},\n    mapStyles: {\n      smoothie_the_cat: {\n        accessToken: 'secret_token',\n        custom: 'MANAGED',\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        id: 'smoothie_the_cat',\n        label: 'Smoothie the Cat',\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      },\n      ...defaultMapStyles\n    },\n    isLoading: {\n      smoothie_the_cat: true\n    },\n    mapboxApiAccessToken: 'smoothies_secret_token',\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: [1, 2, 3],\n    custom3DBuildingColor: true,\n    backgroundColor: [0, 0, 0],\n    initialState: {},\n    bottomMapStyle: undefined,\n    topMapStyle: undefined\n  };\n\n  t.deepEqual(tmpStateWithConfig, expectedStateWithConfig, 'should load saved map style config');\n  Object.keys(tmpStateWithConfig).forEach(key => {\n    t.deepEqual(\n      tmpStateWithConfig[key],\n      expectedStateWithConfig[key],\n      'should load saved map style config'\n    );\n  });\n\n  const savedConfig = SchemaManager.getConfigToSave({mapStyle: stateToSave.mapStyle});\n  const expectedSaved = {\n    version: 'v1',\n    config: {\n      mapStyle: {\n        styleType: 'smoothie_the_cat',\n        topLayerGroups: {},\n        visibleLayerGroups: {label: true, road: true},\n        threeDBuildingColor: [1, 2, 3],\n        backgroundColor: [0, 0, 0],\n        mapStyles: {\n          smoothie_the_cat: {\n            accessToken: 'secret_token',\n            custom: 'MANAGED',\n            icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n            id: 'smoothie_the_cat',\n            label: 'Smoothie the Cat',\n            url: 'mapbox://styles/shanhe/smoothie.the.cat'\n          }\n        }\n      }\n    }\n  };\n\n  t.deepEqual(\n    savedConfig.mapStyles,\n    expectedSaved.mapStyles,\n    'mapStyle state saved should have same keys in mapStyle.mapStyles'\n  );\n\n  Object.keys(savedConfig).forEach(key => {\n    t.deepEqual(savedConfig[key], expectedSaved[key], `should save state.${key} with correctly`);\n  });\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: true (legacy backwards support))', t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'})\n  );\n\n  const stateToSave = StateWCustomMapStyleLegacy;\n\n  // save state\n  const savedState = SchemaManager.getConfigToSave(stateToSave);\n\n  // load state\n  const stateLoaded = SchemaManager.parseSavedConfig(savedState);\n\n  const stateWithConfig = reducer(stateWithToken, receiveMapConfig(stateLoaded));\n\n  const defaultMapStyles = getDefaultMapStyles(KEPLER_UNFOLDED_BUCKET);\n\n  const expectedStateWithConfig = {\n    styleType: 'smoothie_the_cat',\n    visibleLayerGroups: {label: true, road: true},\n    topLayerGroups: {},\n    mapStyles: {\n      smoothie_the_cat: {\n        accessToken: 'secret_token',\n        custom: true,\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        id: 'smoothie_the_cat',\n        label: 'Smoothie the Cat',\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      },\n      ...defaultMapStyles\n    },\n    isLoading: {\n      smoothie_the_cat: true\n    },\n    mapboxApiAccessToken: 'smoothies_secret_token',\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: [1, 2, 3],\n    custom3DBuildingColor: true,\n    backgroundColor: [0, 0, 0],\n    bottomMapStyle: undefined,\n    topMapStyle: undefined,\n    initialState: {}\n  };\n\n  t.deepEqual(stateWithConfig, expectedStateWithConfig, 'should load saved map style config');\n\n  Object.keys(stateWithConfig).forEach(key => {\n    t.deepEqual(\n      stateWithConfig[key],\n      expectedStateWithConfig[key],\n      'should load saved map style config'\n    );\n  });\n  const [task1, ...more1] = drainTasksForTesting();\n  t.equal(more1.length, 0, 'should return 1 tasks');\n\n  const expectedTask = {\n    payload: [\n      {\n        id: 'smoothie_the_cat',\n        url: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat?pluginName=Keplergl&access_token=secret_token'\n      }\n    ]\n  };\n\n  t.deepEqual(task1.payload, expectedTask.payload, 'should create task to load map styles');\n\n  const resultState1 = reducer(\n    stateWithConfig,\n    succeedTaskWithValues(task1, [\n      {id: 'smoothie_the_cat', style: {layers: [], name: 'smoothie_the_cat'}}\n    ])\n  );\n\n  const expectedMapStyles = {\n    smoothie_the_cat: {\n      accessToken: 'secret_token',\n      custom: true,\n      icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n      id: 'smoothie_the_cat',\n      label: 'Smoothie the Cat',\n      url: 'mapbox://styles/shanhe/smoothie.the.cat',\n      style: {layers: [], name: 'smoothie_the_cat'},\n      layerGroups: []\n    },\n    ...defaultMapStyles\n  };\n\n  const expectedMapStyleState = {\n    styleType: 'smoothie_the_cat',\n    visibleLayerGroups: {},\n    topLayerGroups: {},\n    mapStyles: expectedMapStyles,\n    mapboxApiAccessToken: 'smoothies_secret_token',\n    mapboxApiUrl: DEFAULT_MAPBOX_API_URL,\n    mapStylesReplaceDefault: false,\n    inputStyle: getInitialInputStyle(),\n    threeDBuildingColor: [1, 2, 3],\n    custom3DBuildingColor: true,\n    backgroundColor: [0, 0, 0],\n    initialState: {},\n    bottomMapStyle: {layers: [], name: 'smoothie_the_cat'},\n    topMapStyle: null,\n    editable: 0,\n    isLoading: {\n      smoothie_the_cat: false\n    }\n  };\n\n  t.deepEqual(\n    Object.keys(resultState1).sort(),\n    Object.keys(expectedMapStyleState).sort(),\n    'mapStyle state should have same keys'\n  );\n\n  Object.keys(resultState1).forEach(key => {\n    t.deepEqual(\n      resultState1[key],\n      expectedMapStyleState[key],\n      `should update state,${key} with loaded map styles`\n    );\n  });\n\n  const savedConfig = SchemaManager.getConfigToSave({mapStyle: resultState1});\n  const expectedSaved = {\n    version: 'v1',\n    config: {\n      mapStyle: {\n        styleType: 'smoothie_the_cat',\n        topLayerGroups: {},\n        visibleLayerGroups: {},\n        threeDBuildingColor: [1, 2, 3],\n        backgroundColor: [0, 0, 0],\n        mapStyles: {\n          smoothie_the_cat: {\n            accessToken: 'secret_token',\n            custom: true,\n            icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n            id: 'smoothie_the_cat',\n            label: 'Smoothie the Cat',\n            url: 'mapbox://styles/shanhe/smoothie.the.cat'\n          }\n        }\n      }\n    }\n  };\n\n  t.deepEqual(\n    Object.keys(savedConfig).sort(),\n    Object.keys(expectedSaved).sort(),\n    'mapStyle state saved should have same keys'\n  );\n\n  Object.keys(savedConfig).forEach(key => {\n    t.deepEqual(savedConfig[key], expectedSaved[key], `should save state.${key} with correctly`);\n  });\n\n  t.end();\n});\n\n// eslint-disable-next-line max-statements\ntest('#mapStyleReducer -> MAP_STYLE_CHANGE', t => {\n  const initialState = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  // loadMapStyles\n  const nextState = reducer(initialState, loadMapStyles({'dark-matter': MOCK_MAP_STYLE}));\n  t.deepEqual(nextState.mapStyles['dark-matter'], MOCK_MAP_STYLE, 'should load map style');\n  t.deepEqual(nextState.bottomMapStyle, MOCK_MAP_STYLE.style, 'bottomMapStyle should be set');\n  t.equal(nextState.topMapStyle, null, 'topMapStyle should be set');\n  t.equal(nextState.editable, 7, 'editable should be set');\n  t.deepEqual(\n    nextState.visibleLayerGroups,\n    {\n      label: true,\n      road: true,\n      border: false,\n      building: true,\n      water: true,\n      land: true,\n      '3d building': false\n    },\n    'should set visibleLayerGroups'\n  );\n\n  // move label to be on top\n  const nextState1 = reducer(\n    nextState,\n    mapConfigChange({topLayerGroups: {label: true, road: true}})\n  );\n  const expectedTopStyle = {\n    ...MOCK_MAP_STYLE.style,\n    layers: [\n      {\n        id: 'road_path',\n        type: 'line',\n        source: 'carto',\n        'source-layer': 'transportation'\n      },\n      {\n        id: 'roadname_minor',\n        minzoom: 13,\n        source: 'carto',\n        type: 'symbol',\n        'source-layer': 'transportation_name'\n      },\n      {\n        type: 'symbol',\n        source: 'composite',\n        id: 'country-label-sm',\n        'source-layer': 'country_label'\n      }\n    ]\n  };\n  t.deepEqual(nextState1.topMapStyle, expectedTopStyle, 'topMapStyle should be set correctly');\n\n  // hide road layer\n  const nextState2 = reducer(\n    nextState1,\n    mapConfigChange({visibleLayerGroups: {...nextState1.visibleLayerGroups, road: false}})\n  );\n\n  const expectedTopStyle2 = {\n    ...MOCK_MAP_STYLE.style,\n    layers: [\n      {\n        id: 'roadname_minor',\n        minzoom: 13,\n        source: 'carto',\n        type: 'symbol',\n        'source-layer': 'transportation_name'\n      },\n      {type: 'symbol', source: 'composite', id: 'country-label-sm', 'source-layer': 'country_label'}\n    ]\n  };\n\n  t.deepEqual(\n    nextState2.topMapStyle,\n    expectedTopStyle2,\n    'topMapStyle should be set correctly - road: false'\n  );\n\n  // set style type to light\n  const nextState3 = reducer(nextState2, mapStyleChange('positron'));\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'should dispatch 1 request map style task');\n\n  const expectedNextState3 = {\n    ...nextState2,\n    styleType: 'positron',\n    isLoading: {\n      positron: true\n    }\n  };\n  const expectedTaskPayload = [\n    {\n      id: 'positron',\n      url: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'\n    }\n  ];\n  t.deepEqual(nextState3, expectedNextState3, 'state should be correct');\n  t.deepEqual(tasks[0].payload, expectedTaskPayload, 'should dispatch load light map style task');\n\n  // successfully load light map style\n  const succeedState = reducer(\n    nextState3,\n    succeedTaskWithValues(tasks[0], [{id: 'positron', style: MOCK_MAP_STYLE_LIGHT.style}])\n  );\n\n  const expectedMapStyles = {\n    ...nextState3.mapStyles,\n    positron: {\n      ...nextState3.mapStyles.positron,\n      style: MOCK_MAP_STYLE_LIGHT.style\n    }\n  };\n\n  t.deepEqual(succeedState.mapStyles, expectedMapStyles, 'should save map styles');\n\n  const expectedTopStyle3 = {\n    ...MOCK_MAP_STYLE_LIGHT.style,\n    layers: [\n      {\n        id: 'roadname_minor',\n        minzoom: 13,\n        source: 'carto',\n        type: 'symbol',\n        'source-layer': 'transportation_name'\n      },\n      {\n        type: 'symbol',\n        source: 'composite',\n        id: 'admin-label-lg',\n        'source-layer': 'country_label'\n      }\n    ]\n  };\n\n  t.deepEqual(succeedState.topMapStyle, expectedTopStyle3, 'topMapStyle should be set correctly');\n  t.deepEqual(succeedState.isLoading, {positron: false}, 'should set isLoading correctly');\n  t.deepEqual(\n    succeedState.threeDBuildingColor,\n    [237.4432283491836, 0, 0],\n    'should set threeDBuildingColor correctly'\n  );\n\n  // error load light map style\n  const erroredState = reducer(nextState3, errorTaskInTest(tasks[0], new Error('hello')));\n  t.deepEqual(erroredState.isLoading, {positron: false}, 'should set isLoading correctly');\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> MAP_STYLE_CHANGE -> dark basemap to no basemap', t => {\n  const initialState = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  // loadMapStyles\n  const nextState = reducer(initialState, loadMapStyles({dark: MOCK_MAP_STYLE}));\n\n  t.deepEqual(\n    initialState.backgroundColor,\n    nextState.backgroundColor,\n    'backgroundColor should remain the same when NOT switching to the no basemap option'\n  );\n\n  // set style type to no basemap\n  const nextState2 = reducer(nextState, mapStyleChange(NO_MAP_ID));\n\n  const expectedNextState2 = {\n    ...nextState2,\n    styleType: NO_MAP_ID\n  };\n\n  t.deepEqual(\n    nextState2,\n    expectedNextState2,\n    'state should be correct when switching to no basemap option'\n  );\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> EDIT_CUSTOM_MAP_STYLE', t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  // state with custom style\n  let nextState = reducer(stateWithToken, receiveMapConfig(StateWCustomMapStyleLocal));\n\n  const styleIdToEdit = 'smoothie_the_cat';\n  const {label: originalLabel, url, accessToken, icon, custom} = nextState.mapStyles[styleIdToEdit];\n  const inputMapStyleToEdit = {\n    id: styleIdToEdit,\n    label: 'Smoothie the Super Cool Cat',\n    url,\n    accessToken,\n    icon,\n    custom\n  };\n\n  // set input custom map style and provide an edited label property\n  nextState = reducer(nextState, inputMapStyle(inputMapStyleToEdit));\n\n  t.deepEqual(\n    nextState.inputStyle,\n    {...inputMapStyleToEdit, error: false, isValid: true, style: null, uploadedFile: null},\n    'should set the inputStyle'\n  );\n\n  // edit custom map style\n  nextState = reducer(nextState, editCustomMapStyle());\n\n  t.notEqual(\n    nextState.mapStyles[styleIdToEdit].label,\n    originalLabel,\n    'should commit edit to the label property for custom style with id \"smoothie_the_cat\"'\n  );\n\n  t.equal(\n    nextState.mapStyles[styleIdToEdit].id,\n    styleIdToEdit,\n    'should not make any changes to the id property of the custom style after committing label property edits'\n  );\n\n  t.deepEqual(\n    nextState.inputStyle,\n    getInitialInputStyle(),\n    'should reset the inputStyle after committing edits'\n  );\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> REMOVE_CUSTOM_MAP_STYLE -> removal of currently selected custom style', t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  // state with custom style which is also the active styleType\n  let nextState = reducer(stateWithToken, receiveMapConfig(StateWCustomMapStyleLocal));\n\n  t.equal(\n    nextState.styleType,\n    'smoothie_the_cat',\n    'active styleType is currently the custom style'\n  );\n\n  // remove custom map style\n  nextState = reducer(\n    nextState,\n    removeCustomMapStyle({\n      id: 'smoothie_the_cat'\n    })\n  );\n\n  t.equal(\n    nextState.mapStyles.smoothie_the_cat,\n    undefined,\n    'should remove custom style with id \"smoothie_the_cat\"'\n  );\n\n  t.equal(\n    nextState.styleType,\n    'dark-matter',\n    'should change active styleType to default \"dark-matter\" when removing a custom style that was the active styleType'\n  );\n\n  t.end();\n});\n\ntest('#mapStyleReducer -> REMOVE_CUSTOM_MAP_STYLE -> removal of not currently selected custom style', t => {\n  drainTasksForTesting();\n  const stateWithToken = reducer(\n    InitialMapStyle,\n    keplerGlInit({\n      mapboxApiAccessToken: 'smoothies_secret_token'\n    })\n  );\n\n  // state with custom style\n  let nextState = reducer(stateWithToken, receiveMapConfig(StateWCustomMapStyleLocal));\n\n  // set style type to no basemap\n  nextState = reducer(nextState, mapStyleChange(NO_MAP_ID));\n\n  t.equal(nextState.styleType, NO_MAP_ID, 'active styleType is currently the no basemap style');\n\n  // remove custom map style\n  nextState = reducer(\n    nextState,\n    removeCustomMapStyle({\n      id: 'smoothie_the_cat'\n    })\n  );\n\n  t.equal(\n    nextState.mapStyles.smoothie_the_cat,\n    undefined,\n    'should remove custom style with id \"smoothie_the_cat\"'\n  );\n\n  t.equal(\n    nextState.styleType,\n    NO_MAP_ID,\n    'should not change the active styleType when removing a custom style that was not the active styleType'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/provider-state-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {drainTasksForTesting, succeedTaskInTest, errorTaskInTest} from 'react-palm/tasks';\nimport sinon from 'sinon';\nimport {default as Console} from 'global/console';\n\nimport {ActionTypes, exportFileToCloud, resetProviderStatus} from '@kepler.gl/actions';\nimport {\n  providerReducer as reducer,\n  providerStateReducerFactory,\n  INITIAL_PROVIDER_STATE\n} from '@kepler.gl/reducers';\nimport MockProvider from 'test/helpers/mock-provider';\n\ntest('#providerStateReducer', t => {\n  t.deepEqual(\n    reducer(undefined, {}),\n    {...INITIAL_PROVIDER_STATE, initialState: {}},\n    'should return the initial provider state'\n  );\n  t.end();\n});\n\ntest('#providerStateReducerFactory', t => {\n  const providerStateReducer = providerStateReducerFactory({\n    currentProvider: 'taro'\n  });\n\n  t.deepEqual(\n    providerStateReducer(undefined, {}),\n    {\n      ...INITIAL_PROVIDER_STATE,\n      currentProvider: 'taro',\n      initialState: {currentProvider: 'taro'}\n    },\n    'should return the initial state'\n  );\n  t.end();\n});\n\ntest('#providerStateReducer -> EXPORT_FILE_TO_CLOUD', t => {\n  const errSpy = sinon.spy(Console, 'error');\n  drainTasksForTesting();\n  // null\n  reducer(undefined, exportFileToCloud({provider: null}));\n  t.ok(errSpy.calledOnce, 'should call console.error if provider is undefined');\n  t.equal(\n    errSpy.getCall(0).args[0],\n    'provider is not defined',\n    'should warn when cannot find kepler.gl state'\n  );\n  reducer(undefined, exportFileToCloud({provider: {name: 'taro', hello: true}}));\n\n  // uploadFile\n  t.ok(errSpy.calledTwice, 'should call console.error if provider does not have uploadFile');\n  t.equal(\n    errSpy.getCall(1).args[0],\n    'uploadMap is not a function of Cloud provider: taro',\n    'should warn when cannot find uploadMap function'\n  );\n\n  // mapData\n  const mockProvider = new MockProvider();\n  const nextState = reducer(\n    undefined,\n    exportFileToCloud({\n      mapData: {data: []},\n      provider: mockProvider,\n      options: {\n        isPublic: false\n      }\n    })\n  );\n\n  const [task1, ...more] = drainTasksForTesting();\n\n  t.ok(more.length === 0, 'should create 1 task');\n  t.comment(JSON.stringify(nextState));\n  t.deepEqual(\n    nextState,\n    {\n      isProviderLoading: true,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      successInfo: {},\n      initialState: {},\n      mapSaved: null,\n      savedMapId: null,\n      visualizations: []\n    },\n    'Should set isProviderLoading and current provider'\n  );\n\n  t.equal(task1.type, 'EXPORT_FILE_TO_CLOUD_TASK', 'should create export file tasks');\n  t.deepEqual(\n    task1.payload,\n    {\n      provider: mockProvider,\n      payload: {\n        mapData: {data: []},\n        options: {\n          isPublic: false\n        }\n      }\n    },\n    'should call upload file with correct payload'\n  );\n\n  // success\n  const resultState1 = reducer(nextState, succeedTaskInTest(task1, {url: 'taro_and_blue'}));\n\n  t.deepEqual(\n    resultState1,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      initialState: {},\n      mapSaved: 'taro',\n      savedMapId: null,\n      successInfo: {url: 'taro_and_blue'},\n      visualizations: []\n    },\n    'Should set isProviderLoading to false and successInfo, mapSaved to taro'\n  );\n  const task2 = drainTasksForTesting();\n  t.ok(task2.length === 0, 'should create 0 task');\n\n  // error\n  const resultState2 = reducer(nextState, errorTaskInTest(task1, new Error('hello')));\n  t.deepEqual(\n    resultState2,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: 'hello',\n      currentProvider: 'taro',\n      initialState: {},\n      mapSaved: null,\n      savedMapId: null,\n      successInfo: {},\n      visualizations: []\n    },\n    'Should set isLoading to false and error'\n  );\n\n  errSpy.restore();\n  t.end();\n});\n\n// eslint-disable-next-line max-statements\ntest('#providerStateReducer -> EXPORT_FILE_TO_CLOUD -> onSuccess : onError', t => {\n  const mockResponse = {url: 'taro_and_blue'};\n  const mockProvider = new MockProvider();\n  const mockError = new Error('ooops');\n\n  const testUpdaters = {\n    [ActionTypes.TOGGLE_MODAL]: (state, action) => ({\n      ...state,\n      modalId: action.payload\n    }),\n    [ActionTypes.ADD_NOTIFICATION]: (state, action) => ({\n      ...state,\n      notification: action.payload\n    }),\n    [ActionTypes.REMOVE_NOTIFICATION]: state => ({\n      ...state,\n      remove: true\n    })\n  };\n\n  const composedReducer = (state, action) => {\n    if (testUpdaters[action.type]) {\n      return testUpdaters[action.type](state, action);\n    }\n    return reducer(state, action);\n  };\n\n  const onSuccess = args => {\n    t.deepEqual(\n      args,\n      {\n        response: mockResponse,\n        provider: mockProvider,\n        options: {isPublic: false}\n      },\n      'should call onSuccess with arguments'\n    );\n    return {};\n  };\n  const onError = args => {\n    t.deepEqual(args, mockError, 'should call onError with arguments');\n    return {};\n  };\n\n  // mapData\n  const state = reducer(\n    undefined,\n    exportFileToCloud({\n      mapData: {data: []},\n      provider: mockProvider,\n      onSuccess,\n      onError,\n      closeModal: true,\n      options: {\n        isPublic: false\n      }\n    })\n  );\n  const [task1, ...more] = drainTasksForTesting();\n  t.ok(more.length === 0, 'should create 1 task');\n  t.equal(task1.type, 'EXPORT_FILE_TO_CLOUD_TASK', 'should create export file tasks');\n\n  // success\n  const nextState = reducer(state, succeedTaskInTest(task1, mockResponse));\n  const [task2, task3, ...more2] = drainTasksForTesting();\n  t.ok(more2.length === 0, 'should create 1 task');\n  t.ok(task2.type === 'ACTION_TASK', 'should create 2 ACTION_TASK');\n  t.ok(task3.type === 'ACTION_TASK', 'should create 2 ACTION_TASK');\n\n  t.deepEqual(\n    nextState,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      mapSaved: 'taro',\n      savedMapId: null,\n      initialState: {},\n      successInfo: {url: 'taro_and_blue'},\n      visualizations: []\n    },\n    'Should set isProviderLoading to false and successInfo'\n  );\n\n  const resultState1 = reducer(nextState, succeedTaskInTest(task2, undefined));\n  const resultState2 = reducer(resultState1, succeedTaskInTest(task3, undefined));\n\n  // saveToCloudSuccess\n  const [task4, task5, task6, task7, ...more3] = drainTasksForTesting();\n  t.ok(more3.length === 0, 'should create 4 tasks');\n\n  t.ok(task4.type === 'ACTION_TASK', 'should create 3 ACTION_TASKS');\n  t.ok(task5.type === 'ACTION_TASK', 'should create 3 ACTION_TASKS');\n  t.ok(task6.type === 'ACTION_TASK', 'should create 3 ACTION_TASKS');\n  t.ok(task7.type === 'DELAY_TASK', 'should create 1 DELAY_TASK');\n\n  // toggleModal(null),\n  const resultState3 = composedReducer(resultState2, succeedTaskInTest(task4, undefined));\n  t.deepEqual(\n    resultState3,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      mapSaved: 'taro',\n      savedMapId: null,\n      initialState: {},\n      successInfo: {url: 'taro_and_blue'},\n      modalId: null,\n      visualizations: []\n    },\n    'Should call toggleModal(null'\n  );\n\n  const resultState4 = composedReducer(resultState3, succeedTaskInTest(task5, undefined));\n  t.deepEqual(\n    resultState4,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      mapSaved: 'taro',\n      savedMapId: null,\n      initialState: {},\n      successInfo: {},\n      modalId: null,\n      visualizations: []\n    },\n    'Should call resetProviderStatus'\n  );\n\n  const resultState5 = composedReducer(resultState4, succeedTaskInTest(task6, undefined));\n  t.equal(\n    resultState5.notification.type,\n    'success',\n    'Should call addNotification with successNote'\n  );\n\n  const resultState6 = composedReducer(resultState5, succeedTaskInTest(task7, undefined));\n  t.equal(resultState6.remove, true, 'Should call removeNotification');\n\n  t.end();\n});\n\ntest('#providerStateReducer -> RESET_PROVIDER_STATUS', t => {\n  const mockProvider = new MockProvider();\n  const nextState = reducer(\n    undefined,\n    exportFileToCloud({\n      mapData: {data: []},\n      provider: mockProvider,\n      options: {\n        isPublic: false\n      }\n    })\n  );\n  const nextState1 = reducer(nextState, resetProviderStatus());\n\n  t.deepEqual(\n    nextState1,\n    {\n      isProviderLoading: false,\n      isCloudMapLoading: false,\n      providerError: null,\n      currentProvider: 'taro',\n      successInfo: {},\n      initialState: {},\n      mapSaved: null,\n      savedMapId: null,\n      visualizations: []\n    },\n    'Should resetProviderStatus'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/root-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {\n  registerEntry,\n  resetMapConfig,\n  receiveMapConfig,\n  toggleSplitMap,\n  toggleMapControl,\n  layerTypeChange,\n  addDataToMap,\n  ActionTypes\n} from '@kepler.gl/actions';\nimport {createAction, handleActions} from 'redux-actions';\nimport {applyActions} from 'test/helpers/mock-state';\n\ntest('keplerGlReducer.initialState', t => {\n  const test1Reducer = keplerGlReducer.initialState({\n    visState: {\n      layerClasses: []\n    },\n    mapStyle: {\n      styleType: 'light'\n    }\n  });\n\n  const test1ReducerInitialState = test1Reducer(undefined, registerEntry({id: 'test'}));\n  t.deepEqual(\n    test1ReducerInitialState.test.visState.layerClasses,\n    [],\n    'should override initialState'\n  );\n  t.equal(\n    test1ReducerInitialState.test.mapStyle.styleType,\n    'light',\n    'should override initialState'\n  );\n  t.deepEqual(\n    test1ReducerInitialState.test.visState.initialState,\n    {layerClasses: []},\n    'should save initialState'\n  );\n  t.deepEqual(\n    test1ReducerInitialState.test.mapStyle.initialState,\n    {styleType: 'light'},\n    'should save initialState'\n  );\n\n  // change it\n  const newConfig = {\n    visState: {layerBlending: 'additive'},\n    mapStyle: {styleType: 'beautiful'}\n  };\n\n  const modifiedReducer = test1Reducer(test1ReducerInitialState, receiveMapConfig(newConfig));\n  t.deepEqual(modifiedReducer.test.visState.layerBlending, 'additive', 'should apply config');\n  t.equal(modifiedReducer.test.mapStyle.styleType, 'beautiful', 'should apply config');\n  t.deepEqual(\n    modifiedReducer.test.visState.initialState,\n    {layerClasses: []},\n    'should save initialState'\n  );\n\n  // reset reducer\n  const restTestReducer = test1Reducer(modifiedReducer, resetMapConfig());\n  t.deepEqual(restTestReducer.test.visState.layerClasses, [], 'should override initialState');\n  t.equal(restTestReducer.test.mapStyle.styleType, 'light', 'should override initialState');\n  t.deepEqual(\n    restTestReducer.test.visState.initialState,\n    {layerClasses: []},\n    'should save initialState'\n  );\n  t.deepEqual(\n    restTestReducer.test.mapStyle.initialState,\n    {styleType: 'light'},\n    'should save initialState'\n  );\n\n  t.end();\n});\n\ntest('keplerGlReducer.initialState.2', t => {\n  const test1Reducer = keplerGlReducer.initialState({\n    visState: {\n      layerClasses: []\n    },\n    mapStyle: {\n      styleType: 'light'\n    }\n  });\n\n  // call initialState one more time\n  const test2Reducer = test1Reducer.initialState({\n    mapStyle: {\n      styleType: 'smoothie',\n      hello: 'kitty'\n    },\n    mapState: {\n      dragRotate: true\n    }\n  });\n\n  const test2InitialState = test2Reducer(undefined, registerEntry({id: 'test2'}));\n\n  t.deepEqual(\n    test2InitialState.test2.visState.layerClasses,\n    [],\n    'should keep previous initialState'\n  );\n  t.equal(test2InitialState.test2.mapStyle.styleType, 'smoothie', 'should overide initialState');\n  t.equal(test2InitialState.test2.mapStyle.hello, 'kitty', 'should provide initialState');\n  t.equal(test2InitialState.test2.mapState.dragRotate, true, 'should provide initialState');\n\n  // change it\n  const newConfig2 = {\n    visState: {layerBlending: 'subtractive'},\n    mapStyle: {styleType: 'earth'}\n  };\n\n  const modifiedReducer2 = test2Reducer(test2InitialState, receiveMapConfig(newConfig2));\n\n  t.deepEqual(modifiedReducer2.test2.visState.layerBlending, 'subtractive', 'should apply config');\n  t.equal(modifiedReducer2.test2.mapStyle.styleType, 'earth', 'should apply config');\n  t.deepEqual(\n    modifiedReducer2.test2.visState.initialState,\n    {layerClasses: []},\n    'should save initialState'\n  );\n\n  // reset reducer\n  const restTestReducer2 = test2Reducer(modifiedReducer2, resetMapConfig());\n  t.deepEqual(restTestReducer2.test2.visState.layerClasses, [], 'should reset initialState');\n  t.equal(restTestReducer2.test2.mapStyle.styleType, 'smoothie', 'should reset initialState');\n  t.equal(restTestReducer2.test2.mapState.dragRotate, true, 'should reset initialState');\n  t.deepEqual(\n    restTestReducer2.test2.visState.initialState,\n    {layerClasses: []},\n    'should save initialState'\n  );\n  t.deepEqual(\n    restTestReducer2.test2.mapStyle.initialState,\n    {styleType: 'smoothie', hello: 'kitty'},\n    'should save initialState'\n  );\n\n  t.end();\n});\n\ntest('keplerGlReducer.initialState extrareducers', t => {\n  const INITIAL_STATE = {\n    panels: []\n  };\n\n  const insightReducer = handleActions(\n    {\n      ADD_PANEL_TO_SECTION: state => ({\n        ...state,\n        panels: ['first']\n      })\n    },\n    INITIAL_STATE\n  );\n\n  const addPanelToSectionAction = createAction('ADD_PANEL_TO_SECTION');\n\n  const test1Reducer = keplerGlReducer.initialState(\n    {\n      visState: {\n        layerClasses: []\n      },\n      mapStyle: {\n        styleType: 'light'\n      }\n    },\n    {\n      insightState: insightReducer\n    }\n  );\n\n  const test1ReducerInitialState = test1Reducer(undefined, registerEntry({id: 'test'}));\n\n  t.deepEqual(test1ReducerInitialState.test.insightState.panels, [], 'should have extra state');\n\n  const actionModifiedState = test1Reducer(test1ReducerInitialState, addPanelToSectionAction());\n\n  t.deepEqual(\n    actionModifiedState.test.insightState.panels,\n    ['first'],\n    'should have modified panels'\n  );\n\n  t.end();\n});\n\ntest('keplerGlReducer.plugin', t => {\n  // custom actions\n  const hideAndShowSidePanel = createAction('HIDE_AND_SHOW_SIDE_PANEL');\n  const hideMapControls = createAction('HIDE_MAP_CONTROLS');\n\n  const hiddenMapControl = {\n    visibleLayers: {\n      show: false,\n      active: false\n    },\n    mapLegend: {\n      show: false,\n      active: false\n    },\n    toggle3d: {\n      show: false\n    },\n    splitMap: {\n      show: false\n    }\n  };\n\n  // plugin 2 actions\n  const testReducer = keplerGlReducer\n    // 1. as reducer map\n    .plugin({\n      HIDE_AND_SHOW_SIDE_PANEL: state => ({\n        ...state,\n        uiState: {\n          ...state.uiState,\n          readOnly: !state.uiState.readOnly\n        }\n      })\n    })\n    .plugin(\n      handleActions(\n        {\n          // 2. as reducer\n          HIDE_MAP_CONTROLS: state => ({\n            ...state,\n            uiState: {\n              ...state.uiState,\n              mapControls: hiddenMapControl\n            }\n          })\n        },\n        {}\n      )\n    );\n\n  const testInitialState = testReducer(undefined, registerEntry({id: 'test3'}));\n  const previousValue = testInitialState.test3.uiState.readOnly;\n\n  // dispatch action\n  const updatedState = testReducer(testInitialState, hideAndShowSidePanel());\n  t.equal(updatedState.test3.uiState.readOnly, !previousValue, 'should call hideAndShowSidePanel');\n\n  // dispatch action 2\n  const updatedState2 = testReducer(testInitialState, hideMapControls());\n  t.equal(updatedState2.test3.uiState.mapControls, hiddenMapControl, 'should call hideMapControls');\n\n  t.end();\n});\n\ntest('keplerGlReducer.plugin override', t => {\n  // custom actions\n  const mockRawData = {\n    fields: [\n      {\n        name: 'start_point_lat',\n        id: 'start_point_lat',\n        displayName: 'start_point_lat',\n        type: 'real',\n        fieldIdx: 0\n      },\n      {\n        name: 'start_point_lng',\n        id: 'start_point_lng',\n        displayName: 'start_point_lng',\n        type: 'real',\n        fieldIdx: 2\n      },\n      {\n        name: 'end_point_lat',\n        id: 'end_point_lat',\n        displayName: 'end_point_lat',\n        type: 'real',\n        fieldIdx: 3\n      },\n      {\n        name: 'end_point_lng',\n        id: 'end_point_lng',\n        displayName: 'end_point_lng',\n        type: 'real',\n        fieldIdx: 4\n      }\n    ],\n    rows: [\n      [12.25, 37.75, 45.21, 100.12],\n      [null, 35.2, 45.0, 21.3],\n      [12.29, 37.64, 46.21, 99.127],\n      [null, null, 33.1, 29.34]\n    ]\n  };\n\n  const testReducer = keplerGlReducer\n    // 1. as reducer map\n    .plugin(\n      {\n        [ActionTypes.LAYER_TYPE_CHANGE]: state => {\n          return {\n            ...state,\n            visState: {\n              ...state.visState,\n              // do the default behavior and update layerOrder to empty\n              layerOrder: []\n            }\n          };\n        }\n      },\n      {override: {[ActionTypes.LAYER_TYPE_CHANGE]: true}}\n    );\n\n  let nextState = testReducer(undefined, registerEntry({id: 'test3'}));\n\n  const addDataToMapPayload = {\n    datasets: {\n      data: mockRawData,\n      info: {\n        id: 'foo'\n      }\n    }\n  };\n  nextState = applyActions(testReducer, nextState, [\n    {\n      action: addDataToMap,\n      payload: [addDataToMapPayload]\n    }\n  ]);\n\n  t.equal(nextState.test3.visState.layers.length, 4, 'Should have 4 layer');\n\n  nextState = testReducer(nextState, layerTypeChange(nextState.test3.visState.layers[0], 'arc'));\n  t.equal(\n    nextState.test3.visState.layers[0].type,\n    'point',\n    'Should have not changed layer type to arc'\n  );\n  t.deepEqual(nextState.test3.visState.layerOrder, [], 'Should have changed layerOrder to empty');\n\n  t.end();\n});\n\ntest('keplerGlReducer - splitMap and mapControl interaction', t => {\n  // init kepler.gl root and instance\n  let state = keplerGlReducer(undefined, registerEntry({id: 'test'}));\n\n  state = keplerGlReducer(state, toggleMapControl('mapDraw'));\n\n  t.equal(state.test.uiState.mapControls.mapDraw.active, true, 'Map draw should now be active');\n\n  t.equal(\n    state.test.uiState.mapControls.mapDraw.activeMapIndex,\n    0,\n    'Map draw split index should be 0'\n  );\n\n  state = keplerGlReducer(state, toggleMapControl('mapDraw'));\n\n  t.equal(\n    state.test.uiState.mapControls.mapDraw.active,\n    false,\n    'Map draw should now be non active'\n  );\n\n  state = keplerGlReducer(state, toggleSplitMap());\n\n  t.equal(state.test.mapState.isSplit, true, 'Should have split map');\n\n  t.equal(state.test.uiState.mapControls.mapLegend.active, true, 'Should open map legend');\n\n  state = keplerGlReducer(state, toggleMapControl('mapDraw', 1));\n\n  t.equal(\n    state.test.uiState.mapControls.mapDraw.active,\n    true,\n    'Split View - Map draw should now be active'\n  );\n\n  t.equal(\n    state.test.uiState.mapControls.mapDraw.activeMapIndex,\n    1,\n    'Split View - Map draw split index should be 1'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/ui-state-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {\n  toggleSidePanel,\n  toggleModal,\n  toggleSidePanelCloseButton,\n  openDeleteModal,\n  setExportImageSetting,\n  toggleMapControl,\n  setMapControlVisibility,\n  setExportSelectedDataset,\n  setExportDataType,\n  setExportFiltered,\n  startExportingImage,\n  addNotification,\n  removeNotification,\n  loadFiles,\n  loadFilesErr,\n  keplerGlInit\n} from '@kepler.gl/actions';\nimport {\n  uiStateReducer as reducer,\n  uiStateReducerFactory,\n  INITIAL_UI_STATE\n} from '@kepler.gl/reducers';\nimport {\n  EXPORT_DATA_TYPE,\n  RESOLUTIONS,\n  DEFAULT_NOTIFICATION_TOPICS,\n  DEFAULT_NOTIFICATION_TYPES\n} from '@kepler.gl/constants';\n\ntest('#uiStateReducer', t => {\n  t.deepEqual(\n    reducer(undefined, {}),\n    {...INITIAL_UI_STATE, initialState: {}},\n    'should return the initial state'\n  );\n  t.end();\n});\n\ntest('#uiStateReducer -> INIT', t => {\n  const uiStateReducer = uiStateReducerFactory();\n\n  const newState = reducer(\n    uiStateReducer(undefined, {}),\n    keplerGlInit({\n      initialUiState: {readOnly: true}\n    })\n  );\n  t.deepEqual(\n    newState,\n    {...INITIAL_UI_STATE, readOnly: true, initialState: {}},\n    'should apply initialUiState'\n  );\n  t.end();\n});\n\ntest('#uiStateReducerFactory', t => {\n  const uiStateReducer = uiStateReducerFactory({readOnly: true});\n\n  t.deepEqual(\n    uiStateReducer(undefined, {}),\n    {...INITIAL_UI_STATE, readOnly: true, initialState: {readOnly: true}},\n    'should return the initial state'\n  );\n  t.end();\n});\n\ntest('#uiStateReducer -> TOGGLE_SIDE_PANEL', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, toggleSidePanel('foo'));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    activeSidePanel: 'foo'\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should update side panel');\n\n  const nextState2 = reducer(expectedState, toggleModal(null));\n\n  const expectedNextState2 = {\n    ...expectedState,\n    currentModal: null\n  };\n\n  t.deepEqual(nextState2, expectedNextState2, 'should close modal');\n\n  const nextState3 = reducer(expectedState, toggleSidePanel(null));\n\n  const expectedNextState3 = {\n    ...expectedState,\n    activeSidePanel: null\n  };\n\n  t.deepEqual(nextState3, expectedNextState3, 'should close panel');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> TOGGLE_SIDE_PANEL_CLOSE_BUTTON', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, toggleSidePanelCloseButton(false));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    isSidePanelCloseButtonVisible: false\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should hide side panel close button');\n\n  const nextState2 = reducer(expectedState, toggleSidePanelCloseButton(true));\n\n  const expectedNextState2 = {\n    ...expectedState,\n    isSidePanelCloseButtonVisible: true\n  };\n\n  t.deepEqual(nextState2, expectedNextState2, 'should show side panel close button');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> OPEN_DELETE_MODAL', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, openDeleteModal('chai'));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    currentModal: 'deleteData',\n    datasetKeyToRemove: 'chai'\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should open delete data modal and save key to remove');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_EXPORT_IMAGE_SETTING', t => {\n  const newReducer = reducer(\n    INITIAL_UI_STATE,\n    setExportImageSetting({resolution: RESOLUTIONS.TWO_X})\n  );\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    exportImage: {\n      ...INITIAL_UI_STATE.exportImage,\n      resolution: RESOLUTIONS.TWO_X\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set the resolution to TWO_X');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> START_EXPORTING_IMAGE', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, startExportingImage());\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    exportImage: {\n      ...INITIAL_UI_STATE.exportImage,\n      exporting: true\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set exporting to true and modal to export image');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> TOGGLE_MAP_CONTROL', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, toggleMapControl('mapLegend'));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    mapControls: {\n      ...INITIAL_UI_STATE.mapControls,\n      mapLegend: {\n        ...INITIAL_UI_STATE.mapControls.mapLegend,\n        active: !INITIAL_UI_STATE.mapControls.mapLegend.active,\n        activeMapIndex: 0,\n        disableEdit: false,\n        disableClose: false\n      }\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set map legend to be seen');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_MAP_CONTROL_VISIBILITY', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, setMapControlVisibility('mapLegend', false));\n\n  const expectedMapControl = {\n    ...INITIAL_UI_STATE.mapControls,\n    mapLegend: {\n      ...INITIAL_UI_STATE.mapControls.mapLegend,\n      show: false\n    }\n  };\n\n  t.deepEqual(newReducer.mapControls, expectedMapControl, 'should set map legend show to be false');\n\n  const newReducer1 = reducer(newReducer, setMapControlVisibility('mapLegend', true));\n\n  const expectedMapControl1 = {\n    ...INITIAL_UI_STATE.mapControls,\n    mapLegend: {\n      ...INITIAL_UI_STATE.mapControls.mapLegend,\n      show: true\n    }\n  };\n\n  t.deepEqual(newReducer1.mapControls, expectedMapControl1, 'should set map legend show to true');\n\n  const newReducer2 = reducer(newReducer1, setMapControlVisibility('something', true));\n  t.equal(newReducer1, newReducer2, 'should note update state');\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_EXPORT_SELECTED_DATASET', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, setExportSelectedDataset('a'));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    exportData: {\n      ...INITIAL_UI_STATE.exportData,\n      selectedDataset: 'a'\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set the selectedDataset to a');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_EXPORT_DATA_TYPE', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, setExportDataType(EXPORT_DATA_TYPE.JSON));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    exportData: {\n      ...INITIAL_UI_STATE.exportData,\n      dataType: EXPORT_DATA_TYPE.JSON\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set the dataType to json');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_EXPORT_FILTERED', t => {\n  const newReducer = reducer(INITIAL_UI_STATE, setExportFiltered(false));\n\n  const expectedState = {\n    ...INITIAL_UI_STATE,\n    exportData: {\n      ...INITIAL_UI_STATE.exportData,\n      filtered: false\n    }\n  };\n\n  t.deepEqual(newReducer, expectedState, 'should set the filtered to false');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> ADD_NOTIFICATION', t => {\n  const sharedNotificationId = 'test-notification-id';\n\n  const notification1 = {\n    type: DEFAULT_NOTIFICATION_TYPES.error,\n    message: 'TEST',\n    topic: DEFAULT_NOTIFICATION_TOPICS.global,\n    id: 'test-1',\n    count: 1\n  };\n  const state0 = reducer(INITIAL_UI_STATE, addNotification(notification1));\n  t.equal(state0.notifications.length, 1, 'AddNotification should add one new notification');\n  t.deepEqual(\n    state0.notifications[0],\n    notification1,\n    'AddNotification should have propagated data correctly'\n  );\n\n  const notification2 = {\n    type: DEFAULT_NOTIFICATION_TYPES.info,\n    message: 'TEST',\n    topic: DEFAULT_NOTIFICATION_TOPICS.file,\n    id: sharedNotificationId,\n    count: 1\n  };\n  const state1 = reducer(state0, addNotification(notification2));\n  t.equal(state1.notifications.length, 2, 'AddNotification should add second notification');\n  t.deepEqual(\n    state1.notifications[1],\n    notification2,\n    'AddNotification should have propagated data correctly '\n  );\n\n  const updatedNotification = {\n    type: DEFAULT_NOTIFICATION_TYPES.error,\n    message: 'TEST-updated-message',\n    topic: DEFAULT_NOTIFICATION_TOPICS.global,\n    id: sharedNotificationId,\n    count: 2\n  };\n  const state2 = reducer(state1, addNotification(updatedNotification));\n  t.equal(\n    state2.notifications.length,\n    2,\n    \"addNotification shouldn't add new notification with same id\"\n  );\n  t.deepEqual(\n    state2.notifications[1],\n    updatedNotification,\n    'AddNotification should have propagated data correctly '\n  );\n\n  t.end();\n});\n\ntest('#uiStateReducer -> REMOVE_NOTIFICATION', t => {\n  const newState = reducer(\n    INITIAL_UI_STATE,\n    addNotification({\n      type: DEFAULT_NOTIFICATION_TYPES.error,\n      message: 'TEST',\n      topic: DEFAULT_NOTIFICATION_TOPICS.global,\n      id: 'test-1'\n    })\n  );\n\n  t.equal(newState.notifications.length, 1, 'AddNotification should add one new notification');\n  t.deepEqual(\n    newState.notifications[0],\n    {\n      type: DEFAULT_NOTIFICATION_TYPES.error,\n      message: 'TEST',\n      topic: DEFAULT_NOTIFICATION_TOPICS.global,\n      id: 'test-1',\n      count: 1\n    },\n    'AddNotification should have propagated data correctly '\n  );\n\n  const nextState = reducer(newState, removeNotification('test-1'));\n\n  t.equal(nextState.notifications.length, 0, 'RemoveNotification removed one notification');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> LOAD_FILES_ERR', t => {\n  const newState = reducer(INITIAL_UI_STATE, loadFiles());\n  t.equal(newState.loadFiles.fileLoading, true, 'should set fileLoading to true');\n\n  const newState1 = reducer(newState, loadFilesErr('file.csv', new Error('this is an error')));\n  const expectedId = newState1.notifications.length ? newState1.notifications[0].id : 'error';\n  t.equal(\n    newState1.loadFiles.fileLoading,\n    false,\n    'should set fileLoading to false when loadFilesErr'\n  );\n  t.deepEqual(\n    newState1.notifications,\n    [\n      {\n        type: 'error',\n        topic: 'global',\n        message: 'this is an error',\n        id: expectedId,\n        count: 1\n      }\n    ],\n    'should add an error notification'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/vis-state-merger-combine-configs-test.spec.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {VIS_STATE_MERGERS} from '@kepler.gl/reducers';\n\nconst TEST_CASES = [\n  {\n    testMessage:\n      \"interactionConfig (with input configs' tooltips.fieldsToShow with no repeated fields)\",\n    propName: 'interactionConfig',\n    configsToMerge: [\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {\n            'point-dataset': [\n              {name: 'DateTime', format: null},\n              {name: 'Latitude', format: null},\n              {name: 'Longitude', format: null}\n            ],\n            'trip-dataset': [{name: 'vendor', format: null}]\n          },\n          compareMode: true,\n          compareType: 'absolute'\n        },\n        geocoder: {enabled: true},\n        brush: {enabled: true, size: 0.5},\n        coordinate: {enabled: false}\n      },\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {\n            'trip-dataset': [{name: 'customer', format: null}]\n          },\n          compareMode: true,\n          compareType: 'relative'\n        },\n        geocoder: {enabled: true},\n        brush: {enabled: true, size: 2},\n        coordinate: {enabled: false}\n      },\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {},\n          compareMode: true,\n          compareType: 'relative'\n        },\n        geocoder: {enabled: false},\n        brush: {enabled: true, size: 3},\n        coordinate: {enabled: false}\n      }\n    ],\n    expected: {\n      tooltip: {\n        enabled: true,\n        fieldsToShow: {\n          'point-dataset': [\n            {name: 'DateTime', format: null},\n            {name: 'Latitude', format: null},\n            {name: 'Longitude', format: null}\n          ],\n          'trip-dataset': [\n            {name: 'vendor', format: null},\n            {name: 'customer', format: null}\n          ]\n        },\n        compareMode: true,\n        compareType: 'relative'\n      },\n      geocoder: {enabled: true},\n      brush: {enabled: true, size: 3},\n      coordinate: {enabled: false}\n    }\n  },\n  {\n    testMessage:\n      \"interactionConfig (with input configs' tooltips.fieldsToShow with some repeated fields)\",\n    propName: 'interactionConfig',\n    configsToMerge: [\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {\n            'point-dataset': [\n              {name: 'DateTime', format: null},\n              {name: 'Latitude', format: null},\n              {name: 'Longitude', format: null}\n            ],\n            'trip-dataset': [{name: 'vendor', format: null}]\n          },\n          compareMode: true,\n          compareType: 'absolute'\n        },\n        geocoder: {enabled: true},\n        brush: {enabled: true, size: 0.5},\n        coordinate: {enabled: false}\n      },\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {\n            'trip-dataset': [\n              {name: 'customer', format: null},\n              {name: 'vendor', format: null}\n            ],\n            'point-dataset': [\n              {name: 'DateTime', format: null},\n              {name: 'Latitude', format: null},\n              {name: 'Longitude', format: null}\n            ]\n          },\n          compareMode: true,\n          compareType: 'relative'\n        },\n        geocoder: {enabled: true},\n        brush: {enabled: true, size: 2},\n        coordinate: {enabled: false}\n      },\n      {\n        tooltip: {\n          enabled: true,\n          fieldsToShow: {},\n          compareMode: true,\n          compareType: 'relative'\n        },\n        geocoder: {enabled: false},\n        brush: {enabled: true, size: 3},\n        coordinate: {enabled: false}\n      }\n    ],\n    expected: {\n      tooltip: {\n        enabled: true,\n        fieldsToShow: {\n          'point-dataset': [\n            {name: 'DateTime', format: null},\n            {name: 'Latitude', format: null},\n            {name: 'Longitude', format: null}\n          ],\n          'trip-dataset': [\n            {name: 'vendor', format: null},\n            {name: 'customer', format: null}\n          ]\n        },\n        compareMode: true,\n        compareType: 'relative'\n      },\n      geocoder: {enabled: true},\n      brush: {enabled: true, size: 3},\n      coordinate: {enabled: false}\n    }\n  },\n  {\n    testMessage: 'layerBlending (with a majority of \"additive\" values)',\n    propName: 'layerBlending',\n    configsToMerge: ['normal', 'additive', 'additive'],\n    expected: 'additive'\n  },\n  {\n    testMessage: 'layerBlending (with all undefined or null values)',\n    propName: 'layerBlending',\n    configsToMerge: [undefined, null, undefined],\n    expected: null\n  },\n  {\n    testMessage: 'overlayBlending (with 3 unique values)',\n    propName: 'overlayBlending',\n    configsToMerge: ['normal', 'screen', 'darken'],\n    expected: 'normal'\n  },\n  {\n    testMessage: 'overlayBlending (with some undefined values)',\n    propName: 'overlayBlending',\n    configsToMerge: [undefined, 'darken', undefined, 'darken', 'screen', undefined],\n    expected: 'darken'\n  },\n  {\n    testMessage: 'animationConfig',\n    propName: 'animationConfig',\n    configsToMerge: [\n      {currentTime: 500, speed: 1},\n      {currentTime: 100, speed: 5}\n    ],\n    expected: {currentTime: 100, speed: 1}\n  },\n  {\n    testMessage: 'editor',\n    propName: 'editor',\n    configsToMerge: [\n      {features: [], visible: undefined},\n      {features: [{foo: 'bar'}], visible: true},\n      {features: [{foo: 'bar'}, {abc: 'def'}], visible: false}\n    ],\n    expected: {\n      features: [{foo: 'bar'}, {foo: 'bar'}, {abc: 'def'}],\n      visible: true\n    }\n  }\n];\n\ndescribe('VisStateMergers: combineConfigs', () => {\n  test.each(TEST_CASES)('$testMessage', ({propName, configsToMerge, expected}) => {\n    const {combineConfigs} = VIS_STATE_MERGERS.find(\n      m => m.prop === propName && typeof m.combineConfigs === 'function'\n    );\n\n    expect(combineConfigs(configsToMerge)).toEqual(expected);\n  });\n});\n"
  },
  {
    "path": "test/node/reducers/vis-state-merger-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport Task, {withTask, drainTasksForTesting, succeedTaskInTest} from 'react-palm/tasks';\nimport CloneDeep from 'lodash/cloneDeep';\n\nimport keplerGlReducer, {\n  mergeFilters,\n  mergeLayers,\n  mergeInteractions,\n  mergeLayerBlending,\n  mergeSplitMaps,\n  insertLayerAtRightOrder,\n  VIS_STATE_MERGERS,\n  createLayerFromConfig,\n  applyMergersUpdater,\n  visStateReducer,\n  keplerGlReducerCore as coreReducer,\n  defaultInteractionConfig,\n  getLayerOrderFromLayers,\n  setFilterAnimationTimeUpdater,\n  syncTimeFilterWithLayerTimelineUpdater\n} from '@kepler.gl/reducers';\nimport {SYNC_TIMELINE_MODES} from '@kepler.gl/constants';\n\nimport SchemaManager, {CURRENT_VERSION, visStateSchema} from '@kepler.gl/schemas';\nimport {processKeplerglJSON} from '@kepler.gl/processors';\nimport {updateVisData, receiveMapConfig, addDataToMap, registerEntry} from '@kepler.gl/actions';\n\nimport {createDataContainer, findById} from '@kepler.gl/utils';\n\n// fixtures\nimport {\n  savedStateV0,\n  mergedFilters as mergedFiltersV0,\n  mergedLayers as mergedLayersV0,\n  mergedInteractions as mergedInteractionsV0\n} from 'test/fixtures/state-saved-v0';\n\nimport {\n  savedStateV1,\n  mergedFilters as mergedFiltersV1,\n  mergedLayers as mergedLayersV1,\n  mergedInteraction as MergedInteractionV1\n} from 'test/fixtures/state-saved-v1-1';\n\nimport {\n  savedStateV1 as savedStateV1Split,\n  mergedLayers as mergedLayersV1Split,\n  mergedSplitMaps as mergedSplitMapsV1\n} from 'test/fixtures/state-saved-v1-3';\n\nimport {\n  stateSavedV1 as savedStateV1Label,\n  mergedLayers as mergedLayersV1Label\n} from 'test/fixtures/state-saved-v1-4';\n\nimport {\n  savedStateV1TripGeoJson,\n  mergedLayer0 as mergedTripLayer\n} from 'test/fixtures/state-saved-v1-5';\n\nimport {savedStateV1InteractionCoordinate} from 'test/fixtures/state-saved-v1-7';\n\nimport {savedStateWIthNonValidFilters as NonValidFilterState} from 'test/fixtures/state-saved-v1-6';\n\nimport {polygonFilterMap} from 'test/fixtures/polygon-filter-map';\n\n// helpers\nimport {cmpFilters, cmpLayers, cmpDatasets} from 'test/helpers/comparison-utils';\n\n// mock app state\nimport {\n  InitialState,\n  StateWFilters,\n  StateWMultiFilters,\n  StateWFilesFiltersLayerColor,\n  StateWSyncedTimeFilter,\n  StateWSplitMaps,\n  testCsvDataId,\n  testGeoJsonDataId,\n  StateWFiles,\n  applyActions\n} from 'test/helpers/mock-state';\n\nimport {\n  testFields,\n  testAllData,\n  timeFilterProps,\n  dateFilterProps,\n  epochFilterProps,\n  mergedTimeFilter,\n  mergedDateFilter,\n  mergedEpochFilter,\n  expectedSyncedTsFilter\n} from 'test/fixtures/test-csv-data';\n\nimport {\n  fields,\n  datasetAllData as testGeoJsonAllData,\n  geoJsonRateFilterProps,\n  geoJsonTripFilterProps,\n  mergedTripFilter,\n  mergedRateFilter\n} from 'test/fixtures/geojson';\nimport {mockStateWithPolygonFilter} from 'test/fixtures/points-with-polygon-filter-map';\nimport {mockStateWithSyncedFilterAndTripLayer} from 'test/fixtures/synced-filter-with-trip-layer';\n\ntest('VisStateMerger.v0 -> mergeFilters -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV0);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed filters\n  cmpFilters(t, mergedFiltersV0, stateWData.filters);\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n  const expectedMergedFilterV1 = mergedFiltersV1;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed filters\n  cmpFilters(t, expectedMergedFilterV1, stateWData.filters);\n  t.end();\n});\n\ntest('VisStateMerger.v0 -> mergeFilters -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV0);\n  const oldState = cloneDeep(StateWFilters);\n\n  const oldVisState = oldState.visState;\n  const oldFilters = [...oldState.visState.filters];\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed filters\n  cmpFilters(t, [...oldFilters, ...mergedFiltersV0], stateWData.filters);\n  t.deepEqual(stateWData.filterToBeMerged, [], 'should clear up filterToBeMerged');\n\n  // should filter data\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const oldState = cloneDeep(StateWFilters);\n\n  const oldVisState = oldState.visState;\n  const oldFilters = [...oldState.visState.filters];\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed filters\n  cmpFilters(t, [...oldFilters, ...mergedFiltersV1], stateWData.filters);\n  t.deepEqual(stateWData.filterToBeMerged, [], 'should clear up filterToBeMerged');\n\n  // should filter data\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> empty filter', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  // set an empty filter\n  savedConfig.config.config.visState.filters[0].name = [];\n  const oldState = cloneDeep(InitialState);\n\n  const oldVisState = oldState.visState;\n  const oldFilters = [...oldState.visState.filters];\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = visStateReducer(mergedState, updateVisData(parsedData));\n\n  // test parsed filters\n  cmpFilters(t, oldFilters, stateWData.filters);\n  t.deepEqual(\n    stateWData.filterToBeMerged,\n    parsedFilters,\n    'should save filters failed to merge to filterToBeMerged'\n  );\n\n  // should filter data\n  t.end();\n});\n\ntest('VisStateMerger -> mergeLayers -> invalid layer config', t => {\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const layers = [\n    {id: 'abc'},\n    {type: 'taro'}, // no type\n    {type: 'point', id: 'yes'} // no config\n  ];\n  const mergedState = mergeLayers(oldVisState, layers, true);\n\n  t.equal(mergedState.layers, oldVisState.layers, 'merge invalid layer should not error');\n  t.deepEqual(mergedState.layerToBeMerged, layers, 'layerToBeMerged should contain invalid layers');\n  t.end();\n});\n\ntest('VisStateMerger.current -> mergeLayers -> toEmptyState', t => {\n  const stateToSave = cloneDeep(StateWFilesFiltersLayerColor);\n  const appStateToSave = SchemaManager.save(stateToSave);\n  const configToSave = appStateToSave.config;\n  const configParsed = SchemaManager.parseSavedConfig(configToSave);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedLayers = configParsed.visState.layers;\n  // mergeLayers\n  const mergedState = mergeLayers(oldState.visState, parsedLayers, true);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n  const parsedData = SchemaManager.parseSavedData(appStateToSave.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed layers\n  const genericLayersByOrder = stateToSave.visState.layerOrder.map(id =>\n    findById(id)(stateToSave.visState.layers)\n  );\n\n  cmpLayers(t, genericLayersByOrder, stateWData.layers, {id: true});\n  t.end();\n});\n\ntest('visStateMerger -> mergeLayer -> incremental load', t => {\n  const stateToSave = cloneDeep(StateWFilesFiltersLayerColor);\n  const appStateToSave = SchemaManager.save(stateToSave);\n  const {datasets, config} = appStateToSave;\n\n  const [dataset1, dataset2] = datasets;\n  // load config first\n  const stateWithConfig = coreReducer(stateToSave, addDataToMap({config}));\n\n  t.deepEqual(\n    stateWithConfig.visState.preserveLayerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'shoud preserve layer order'\n  );\n\n  t.deepEqual(\n    stateWithConfig.visState.layerToBeMerged.map(l => l.id),\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'should save to layerToBeMerged'\n  );\n\n  // load dataset2\n  const parsedData2 = SchemaManager.parseSavedData([dataset2]);\n  const stateWithData2 = applyActions(coreReducer, stateWithConfig, [\n    {action: addDataToMap, payload: [{datasets: parsedData2}]}\n  ]);\n\n  t.deepEqual(\n    stateWithData2.visState.preserveLayerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'shoud preserve layer order'\n  );\n\n  t.deepEqual(\n    stateWithData2.visState.layers.map(l => l.id),\n    ['geojson-1'],\n    'shoud load geojeon layer'\n  );\n\n  t.deepEqual(\n    stateWithData2.visState.layerOrder,\n    [stateWithData2.visState.layers[0].id],\n    'layerOrder should be correct'\n  );\n  t.deepEqual(\n    stateWithData2.visState.layerToBeMerged.map(l => l.id),\n    ['hexagon-2', 'point-0'],\n    'should save to layerToBeMerged'\n  );\n\n  // load dataset1\n  const parsedData1 = SchemaManager.parseSavedData([dataset1]);\n  const stateWithData1 = applyActions(coreReducer, stateWithData2, [\n    {action: addDataToMap, payload: [{datasets: parsedData1}]}\n  ]);\n\n  t.deepEqual(\n    stateWithData1.visState.preserveLayerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'shoud preserve layer order'\n  );\n  t.deepEqual(\n    stateWithData1.visState.layers.map(l => l.id),\n    ['geojson-1', 'hexagon-2', 'point-0'],\n    'shoud load 2 layers'\n  );\n  t.deepEqual(\n    stateWithData1.visState.layerOrder,\n    [\n      stateWithData1.visState.layers[1].id,\n      stateWithData1.visState.layers[2].id,\n      stateWithData1.visState.layers[0].id\n    ],\n    'layerOrder should be correct'\n  );\n  t.deepEqual(\n    stateWithData1.visState.layerToBeMerged.map(l => l.id),\n    [],\n    'layerToBeMerged should be empty'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeLayers -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedLayers = parsedConfig.visState.layers;\n\n  // mergeLayers\n  const mergedState = mergeLayers(oldState.visState, parsedLayers, true);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n  const parsedData = SchemaManager.parseSavedData(savedStateV1.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed layers\n  cmpLayers(t, mergedLayersV1, stateWData.layers, {id: true, color: true});\n  t.end();\n});\n\ntest('VisStateMerger.v1.label -> mergeLayers -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV1Label);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedLayers = parsedConfig.visState.layers;\n\n  // mergeLayers\n  const mergedState = mergeLayers(oldState.visState, parsedLayers, true);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n  const parsedData = SchemaManager.parseSavedData(savedStateV1Label.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed layers\n  cmpLayers(t, mergedLayersV1Label, stateWData.layers, {id: true});\n  t.end();\n});\n\ntest('VisStateMerger.v1.split -> mergeLayers -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV1Split);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n  const expectedConfig = mergedSplitMapsV1;\n  const oldState = cloneDeep(InitialState);\n\n  const oldVisState = oldState.visState;\n\n  const parsedLayers = parsedConfig.visState.layers;\n\n  // merge State\n  const mergedState = visStateReducer(oldVisState, receiveMapConfig(parsedConfig));\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else if (key === 'splitMaps') {\n      t.deepEqual(mergedState.splitMaps, [], 'Should wait to merge splitMaps');\n    } else if (key === 'splitMapsToBeMerged') {\n      t.deepEqual(\n        mergedState.splitMapsToBeMerged,\n        expectedConfig,\n        'Should save to splitMapsToBeMerged'\n      );\n    } else if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        {tooltip: parsedConfig.visState.interactionConfig.tooltip},\n        'Should save interactionConfig to interactionToBeMerged'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], `Should keep ${key} the same`);\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test split Maps\n  t.deepEqual(stateWData.splitMaps, expectedConfig, 'should merge splitMaps');\n\n  // test parsed layers\n  cmpLayers(t, mergedLayersV1Split, stateWData.layers, {id: true});\n  t.end();\n});\n\ntest('VisStateMerger.v0 -> mergeLayers -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV0);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n\n  const oldState = cloneDeep(StateWFilesFiltersLayerColor);\n\n  const oldVisState = oldState.visState;\n  t.deepEqual(\n    oldVisState.layerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'layer order should be correct'\n  );\n\n  const oldLayers = [...oldVisState.layers];\n\n  const parsedLayers = parsedConfig.visState.layers;\n\n  // mergeLayers\n  const mergedState = mergeLayers(oldState.visState, parsedLayers, true);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  t.deepEqual(\n    mergedState.layerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'layer order should not change'\n  );\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed layers\n  cmpLayers(t, [...oldLayers, ...mergedLayersV0], stateWData.layers);\n\n  t.deepEqual(\n    stateWData.layerOrder,\n    [...getLayerOrderFromLayers(mergedLayersV0), 'hexagon-2', 'point-0', 'geojson-1'],\n    'should put new layers on top of old ones'\n  );\n  t.deepEqual(stateWData.layerToBeMerged, [], 'should clean up layer to be merged');\n  t.equal(stateWData.layerData.length, 8, 'should calculate layer data');\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeLayers -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n\n  const oldState = cloneDeep(StateWFilesFiltersLayerColor);\n  const oldVisState = oldState.visState;\n  t.deepEqual(\n    oldVisState.layerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'layer order should be correct'\n  );\n  const oldLayers = [...oldVisState.layers];\n\n  const parsedLayers = parsedConfig.visState.layers;\n\n  // mergeLayers\n  const mergedState = mergeLayers(oldState.visState, parsedLayers, true);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'layerToBeMerged') {\n      t.deepEqual(\n        mergedState.layerToBeMerged,\n        parsedLayers,\n        'Should save layers to layerToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  t.deepEqual(\n    mergedState.layerOrder,\n    ['hexagon-2', 'point-0', 'geojson-1'],\n    'layer order should not change'\n  );\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed filters\n  cmpLayers(t, [...oldLayers, ...mergedLayersV1], stateWData.layers);\n\n  t.deepEqual(\n    stateWData.layerOrder,\n    [\n      stateWData.layers[3].id,\n      stateWData.layers[4].id,\n      stateWData.layers[2].id,\n      stateWData.layers[0].id,\n      stateWData.layers[1].id\n    ],\n    'should put new layers on top of old ones'\n  );\n  t.deepEqual(stateWData.layerToBeMerged, [], 'should clean up layer to be merged');\n  t.equal(stateWData.layerData.length, 5, 'should calculate layer data');\n\n  t.end();\n});\n\ntest('VisStateMerger.v0 -> mergeInteractions -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV0);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedInteraction = parsedConfig.visState.interactionConfig;\n\n  // merge interactions\n  const mergedState = mergeInteractions(oldState.visState, parsedInteraction);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        {\n          tooltip: {\n            fieldsToShow: {\n              '9h10t7fyb': [\n                {\n                  name: 'int_range',\n                  format: null\n                },\n                {\n                  name: 'detail',\n                  format: null\n                },\n                {\n                  name: 'type_boolean',\n                  format: null\n                }\n              ],\n              v79816te8: [\n                {\n                  name: 'ID',\n                  format: null\n                },\n                {\n                  name: 'ZIP_CODE',\n                  format: null\n                }\n              ]\n            },\n            enabled: true\n          }\n        },\n        'Should save interactions to interactionToBeMerged before data loaded'\n      );\n    } else if (key === 'interactionConfig') {\n      t.deepEqual(\n        mergedState.interactionConfig,\n        oldState.visState.interactionConfig,\n        'Should disable interaction: null'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed interactions\n  t.deepEqual(stateWData.interactionConfig, mergedInteractionsV0, 'should merge interactionConfig');\n  t.deepEqual(stateWData.interactionToBeMerged, {}, 'should clear interaction');\n\n  t.end();\n});\n\ntest('VisStateMerger.v0 -> mergeInteractions -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV0);\n  const oldState = cloneDeep(StateWFilesFiltersLayerColor);\n  const oldVisState = oldState.visState;\n\n  const milkShake = [\n    {\n      name: 'have',\n      format: null\n    },\n    {\n      name: 'a',\n      format: null\n    },\n    {\n      name: 'good',\n      format: null\n    },\n    {\n      name: 'day',\n      format: null\n    }\n  ];\n  // add random items to interactionToBeMerged\n  oldVisState.interactionToBeMerged = {\n    ...oldVisState.interactionToBeMerged,\n    tooltip: {\n      fieldsToShow: {\n        milkShake\n      }\n    }\n  };\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n\n  const parsedInteraction = parsedConfig.visState.interactionConfig;\n\n  // merge interactions\n  const mergedState = mergeInteractions(oldState.visState, parsedInteraction);\n\n  const expectedInteractionToBeMerged = {\n    tooltip: {\n      fieldsToShow: {\n        milkShake,\n        '9h10t7fyb': [\n          {\n            name: 'int_range',\n            format: null\n          },\n          {\n            name: 'detail',\n            format: null\n          },\n          {\n            name: 'type_boolean',\n            format: null\n          }\n        ],\n        v79816te8: [\n          {\n            name: 'ID',\n            format: null\n          },\n          {\n            name: 'ZIP_CODE',\n            format: null\n          }\n        ]\n      },\n      enabled: true\n    }\n  };\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        expectedInteractionToBeMerged,\n        'Should save interactions to interactionToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  const expectedInteractions = {\n    ...defaultInteractionConfig,\n    tooltip: {\n      ...defaultInteractionConfig.tooltip,\n      enabled: true,\n      config: {\n        compareMode: false,\n        compareType: 'absolute',\n        fieldsToShow: {\n          [testCsvDataId]: [\n            {\n              name: 'gps_data.utc_timestamp',\n              format: null\n            },\n            {\n              name: 'gps_data.types',\n              format: null\n            },\n            {\n              name: 'epoch',\n              format: null\n            },\n            {\n              name: 'has_result',\n              format: null\n            },\n            {\n              name: 'uid',\n              format: null\n            }\n          ],\n          [testGeoJsonDataId]: [\n            {\n              name: 'OBJECTID',\n              format: null\n            },\n            {\n              name: 'ZIP_CODE',\n              format: null\n            },\n            {\n              name: 'ID',\n              format: null\n            },\n            {\n              name: 'TRIPS',\n              format: null\n            },\n            {\n              name: 'RATE',\n              format: null\n            }\n          ],\n          '9h10t7fyb': [\n            {\n              name: 'int_range',\n              format: null\n            },\n            {\n              name: 'detail',\n              format: null\n            },\n            {\n              name: 'type_boolean',\n              format: null\n            }\n          ],\n          v79816te8: [\n            {\n              name: 'ID',\n              format: null\n            },\n            {\n              name: 'ZIP_CODE',\n              format: null\n            }\n          ]\n        }\n      }\n    }\n  };\n\n  // test parsed interactions\n  t.deepEqual(stateWData.interactionConfig, expectedInteractions, 'should merge interactionconfig');\n  t.deepEqual(\n    stateWData.interactionToBeMerged,\n    {\n      tooltip: {\n        fieldsToShow: {\n          milkShake\n        },\n        enabled: true\n      }\n    },\n    'should clear interaction'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeInteractions -> toEmptyState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedInteraction = parsedConfig.visState.interactionConfig;\n\n  // merge interactions\n  const mergedState = mergeInteractions(oldState.visState, parsedInteraction);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        {\n          tooltip: {\n            fieldsToShow: {\n              a5ybmwl2d: [\n                {\n                  name: 'a_zip',\n                  format: null\n                },\n                {\n                  name: 'str_type',\n                  format: null\n                },\n                {\n                  name: 'int_type',\n                  format: null\n                }\n              ]\n            },\n            enabled: false\n          }\n        },\n        'Should save nothing interactions to interactionToBeMerged before data loaded'\n      );\n    } else if (key === 'interactionConfig') {\n      t.deepEqual(\n        mergedState.interactionConfig,\n        {\n          ...defaultInteractionConfig,\n          tooltip: {\n            ...defaultInteractionConfig.tooltip,\n            enabled: false,\n            config: {\n              fieldsToShow: {},\n              compareMode: false,\n              compareType: 'absolute'\n            }\n          },\n          brush: {\n            ...defaultInteractionConfig.brush,\n            enabled: false,\n            config: {\n              size: 1\n            }\n          }\n        },\n        'Should disable tooltip'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  // test parsed interactions\n  t.deepEqual(stateWData.interactionConfig, MergedInteractionV1, 'should merge interactionConfig');\n  t.deepEqual(stateWData.interactionToBeMerged, {}, 'should clear interaction');\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeInteractions -> toWorkingState', t => {\n  const savedConfig = cloneDeep(savedStateV1);\n  const oldState = cloneDeep(StateWFilesFiltersLayerColor);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n\n  const parsedInteraction = parsedConfig.visState.interactionConfig;\n\n  // merge interactions\n  const mergedState = mergeInteractions(oldState.visState, parsedInteraction);\n\n  const expectedInteractionToBeMerged = {\n    tooltip: {\n      fieldsToShow: {\n        a5ybmwl2d: [\n          {\n            name: 'a_zip',\n            format: null\n          },\n          {\n            name: 'str_type',\n            format: null\n          },\n          {\n            name: 'int_type',\n            format: null\n          }\n        ]\n      },\n      enabled: false\n    }\n  };\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        expectedInteractionToBeMerged,\n        'Should save interactions to interactionToBeMerged before data loaded'\n      );\n    } else if (key === 'interactionConfig') {\n      t.deepEqual(\n        mergedState.interactionConfig,\n        {\n          ...defaultInteractionConfig,\n          tooltip: {\n            ...defaultInteractionConfig.tooltip,\n            enabled: false,\n            config: {\n              compareMode: false,\n              compareType: 'absolute',\n              fieldsToShow: {\n                [testCsvDataId]: [\n                  {\n                    name: 'gps_data.utc_timestamp',\n                    format: null\n                  },\n                  {\n                    name: 'gps_data.types',\n                    format: null\n                  },\n                  {\n                    name: 'epoch',\n                    format: null\n                  },\n                  {\n                    name: 'has_result',\n                    format: null\n                  },\n                  {\n                    name: 'uid',\n                    format: null\n                  }\n                ],\n                [testGeoJsonDataId]: [\n                  {\n                    name: 'OBJECTID',\n                    format: null\n                  },\n                  {\n                    name: 'ZIP_CODE',\n                    format: null\n                  },\n                  {\n                    name: 'ID',\n                    format: null\n                  },\n                  {\n                    name: 'TRIPS',\n                    format: null\n                  },\n                  {\n                    name: 'RATE',\n                    format: null\n                  }\n                ]\n              }\n            }\n          },\n\n          brush: {\n            ...defaultInteractionConfig.brush,\n            enabled: false,\n            config: {\n              size: 1\n            }\n          },\n\n          coordinate: {\n            ...defaultInteractionConfig.coordinate,\n            enabled: false\n          }\n        },\n        'Should disable interaction: null'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  const expectedInteractions = {\n    ...defaultInteractionConfig,\n    tooltip: {\n      ...defaultInteractionConfig.tooltip,\n      enabled: false,\n      config: {\n        compareMode: false,\n        compareType: 'absolute',\n        fieldsToShow: {\n          [testCsvDataId]: [\n            {\n              name: 'gps_data.utc_timestamp',\n              format: null\n            },\n            {\n              name: 'gps_data.types',\n              format: null\n            },\n            {\n              name: 'epoch',\n              format: null\n            },\n            {\n              name: 'has_result',\n              format: null\n            },\n            {\n              name: 'uid',\n              format: null\n            }\n          ],\n          [testGeoJsonDataId]: [\n            {\n              name: 'OBJECTID',\n              format: null\n            },\n            {\n              name: 'ZIP_CODE',\n              format: null\n            },\n            {\n              name: 'ID',\n              format: null\n            },\n            {\n              name: 'TRIPS',\n              format: null\n            },\n            {\n              name: 'RATE',\n              format: null\n            }\n          ],\n          a5ybmwl2d: [\n            {\n              name: 'a_zip',\n              format: null\n            },\n            {\n              name: 'str_type',\n              format: null\n            },\n            {\n              name: 'int_type',\n              format: null\n            }\n          ]\n        }\n      }\n    },\n    brush: {\n      ...defaultInteractionConfig.brush,\n      enabled: false,\n      config: {size: 1}\n    }\n  };\n\n  // test parsed interactions\n  t.deepEqual(stateWData.interactionConfig, expectedInteractions, 'should merge interactionConfig');\n  t.deepEqual(stateWData.interactionToBeMerged, {}, 'should clear interaction');\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeInteractions -> coordinate', t => {\n  const savedConfig = cloneDeep(savedStateV1InteractionCoordinate);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n\n  const parsedInteraction = parsedConfig.visState.interactionConfig;\n\n  // merge interactions\n  const mergedState = mergeInteractions(oldState.visState, parsedInteraction);\n\n  const expectedInteractionToBeMerged = {};\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'interactionToBeMerged') {\n      t.deepEqual(\n        mergedState.interactionToBeMerged,\n        expectedInteractionToBeMerged,\n        'Should save interactions to interactionToBeMerged before data loaded'\n      );\n    } else if (key === 'interactionConfig') {\n      t.deepEqual(\n        mergedState.interactionConfig,\n        {\n          ...defaultInteractionConfig,\n\n          tooltip: {\n            ...defaultInteractionConfig.tooltip,\n            enabled: false\n          },\n\n          brush: {\n            ...defaultInteractionConfig.brush,\n            config: {\n              size: 1\n            }\n          },\n\n          coordinate: {\n            ...defaultInteractionConfig.coordinate,\n            enabled: true\n          }\n        },\n        'Should disable interaction: null'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = applyActions(visStateReducer, mergedState, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  const expectedInteractions = {\n    ...defaultInteractionConfig,\n    tooltip: {\n      ...defaultInteractionConfig.tooltip,\n      enabled: false,\n      config: {\n        compareMode: false,\n        compareType: 'absolute',\n        fieldsToShow: {\n          a5ybmwl2d: [\n            {\n              name: 'a_zip',\n              format: null\n            },\n            {\n              name: 'zip_area',\n              format: null\n            },\n            {\n              name: 'avg_number',\n              format: null\n            },\n            {\n              name: 'str_type',\n              format: null\n            },\n            {\n              name: 'int_type',\n              format: null\n            }\n          ]\n        }\n      }\n    },\n    brush: {\n      ...defaultInteractionConfig.brush,\n      enabled: false,\n      config: {size: 1}\n    },\n    coordinate: {\n      ...defaultInteractionConfig.coordinate,\n      enabled: true\n    }\n  };\n\n  // test parsed interactions\n  t.deepEqual(stateWData.interactionConfig, expectedInteractions, 'should merge interactionConfig');\n\n  t.deepEqual(stateWData.interactionToBeMerged, {}, 'should clear interaction');\n\n  t.end();\n});\n\ntest('VisStateMerger - mergeLayerBlending', t => {\n  const preState = {\n    layerBlending: 'additive'\n  };\n\n  t.deepEqual(\n    mergeLayerBlending(preState, 'normal'),\n    {layerBlending: 'normal'},\n    'should merge layerBlending'\n  );\n  t.deepEqual(\n    mergeLayerBlending(preState, undefined),\n    {layerBlending: 'additive'},\n    'should merge layerBlending'\n  );\n  t.deepEqual(\n    mergeLayerBlending(preState, 'not_exist'),\n    {layerBlending: 'additive'},\n    'should merge layerBlending'\n  );\n  t.deepEqual(\n    mergeLayerBlending(preState, null),\n    {layerBlending: 'additive'},\n    'should merge layerBlending'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger - mergeSplitMaps -> split to split', t => {\n  // state with splitMaps\n  const oldState = cloneDeep(StateWSplitMaps).visState;\n  // saved config with splitMaps\n  const savedConfig = cloneDeep(savedStateV1Split);\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config);\n\n  // 1. merge State reset current splitMaps\n  const mergedState = visStateReducer(oldState, receiveMapConfig(parsedConfig));\n\n  const expectedToMerge = [\n    {\n      layers: {\n        f24uw1: false,\n        '9x77w7h': true\n      }\n    },\n    {\n      layers: {\n        f24uw1: true,\n        '9x77w7h': false\n      }\n    }\n  ];\n\n  const expectedToMergeAll = [\n    {\n      layers: {\n        f24uw1: false,\n        '9x77w7h': true,\n        'point-0': false,\n        'geojson-1': true\n      }\n    },\n    {\n      layers: {\n        f24uw1: true,\n        '9x77w7h': false,\n        'point-0': true,\n        'geojson-1': true\n      }\n    }\n  ];\n  t.deepEqual(mergedState.splitMaps, [], 'Should reset splitMaps');\n  t.deepEqual(mergedState.splitMapsToBeMerged, expectedToMerge);\n\n  // 2. merge State keep current splitMaps\n  const mergedState2 = visStateReducer(\n    oldState,\n    receiveMapConfig(parsedConfig, {keepExistingConfig: true})\n  );\n  t.deepEqual(mergedState2.splitMaps, oldState.splitMaps, 'Should keep current splitMaps');\n  t.deepEqual(\n    mergedState2.splitMapsToBeMerged,\n    expectedToMerge,\n    'Should save unmerged to splitMapsToBeMerged'\n  );\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n  // 3. load data into reducer\n  const mergedState3 = applyActions(visStateReducer, mergedState2, [\n    {action: updateVisData, payload: [parsedData]}\n  ]);\n\n  t.deepEqual(mergedState3.splitMaps, expectedToMergeAll, 'Should merge all splitMaps');\n  t.deepEqual(mergedState3.splitMapsToBeMerged, [], 'Should empty splitMapsToBeMerged');\n\n  t.end();\n});\n\ntest('VisStateMerger - mergeSplitMaps', t => {\n  const testState1 = {\n    layers: [],\n    splitMaps: [{layers: {a: true}}, {layers: {a: false}}],\n    splitMapsToBeMerged: []\n  };\n\n  t.deepEqual(\n    mergeSplitMaps(testState1, []),\n    {...testState1, splitMapsToBeMerged: []},\n    'should return empty'\n  );\n\n  const testSM = [{layers: {c: true}}, {layers: {c: false}}];\n\n  t.deepEqual(\n    mergeSplitMaps(testState1, testSM),\n    {...testState1, splitMapsToBeMerged: testSM},\n    'should save non-exist layers to splitMapsToBeMerged'\n  );\n\n  const testState2 = {\n    layers: [{id: 'c', config: {isVisible: true}}],\n    splitMaps: [{layers: {a: true}}, {layers: {a: false}}],\n    splitMapsToBeMerged: []\n  };\n\n  t.deepEqual(\n    mergeSplitMaps(testState2, testSM),\n    {\n      ...testState2,\n      splitMaps: [{layers: {a: true, c: true}}, {layers: {a: false, c: false}}],\n      splitMapsToBeMerged: []\n    },\n    'should merge split maps'\n  );\n\n  const testState3 = {\n    layers: [{id: 'c', config: {isVisible: true}}],\n    splitMaps: [],\n    splitMapsToBeMerged: []\n  };\n  t.deepEqual(\n    mergeSplitMaps(testState3, testSM),\n    {\n      ...testState3,\n      splitMaps: [{layers: {c: true}}, {layers: {c: false}}],\n      splitMapsToBeMerged: []\n    },\n    'should create split maps panel and merge split maps'\n  );\n\n  const testState4 = {\n    layers: [\n      {id: 'a', config: {isVisible: true}},\n      {id: 'b', config: {isVisible: false}},\n      {id: 'c', config: {isVisible: true}}\n    ],\n    splitMaps: [],\n    splitMapsToBeMerged: []\n  };\n  t.deepEqual(\n    mergeSplitMaps(testState4, testSM),\n    {\n      ...testState4,\n      splitMaps: [{layers: {a: true, c: true}}, {layers: {a: true, c: false}}],\n      splitMapsToBeMerged: []\n    },\n    'should create split maps panel, add current layer to splitMaps and merge split maps'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger - mergeTripGeojson', t => {\n  const initialState = cloneDeep(InitialState);\n\n  // processKeplerglJSON\n  const result = processKeplerglJSON(savedStateV1TripGeoJson);\n  const updatedCore = applyActions(coreReducer, initialState, [\n    {action: addDataToMap, payload: [result]}\n  ]);\n\n  const mergedVieState = updatedCore.visState;\n\n  t.equal(mergedVieState.layers.length, 1, 'should create 1 layer');\n  const tripLayer = mergedVieState.layers[0];\n\n  t.equal(tripLayer.type, 'trip', 'should create 1 trip layer');\n\n  cmpLayers(t, mergedTripLayer, tripLayer, {id: true, color: true});\n\n  t.deepEqual(\n    tripLayer.dataToFeature,\n    mergedTripLayer.dataToFeature,\n    'dataToFeature should be correct'\n  );\n\n  t.deepEqual(\n    tripLayer.dataToTimeStamp,\n    mergedTripLayer.dataToTimeStamp,\n    'dataToTimeStamp should be correct'\n  );\n\n  t.deepEqual(tripLayer.meta.bounds, mergedTripLayer.meta.bounds, 'meta.bounds should be correct');\n\n  t.deepEqual(\n    tripLayer.meta.featureTypes,\n    mergedTripLayer.meta.featureTypes,\n    'meta.featureTypes should be correct'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> nonValidFilter', t => {\n  const savedConfig = cloneDeep(NonValidFilterState);\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = visStateReducer(mergedState, updateVisData(parsedData));\n\n  // parsed filters must be empty\n  cmpFilters(t, [], stateWData.filters);\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> multiFilters', t => {\n  const stateToSave = cloneDeep(StateWMultiFilters);\n  const oldCsvData = stateToSave.visState.datasets[testCsvDataId];\n  const oldGeoJsonData = stateToSave.visState.datasets[testGeoJsonDataId];\n  const appStateToSave = SchemaManager.save(stateToSave);\n  const stateParsed = SchemaManager.load(appStateToSave);\n\n  const oldState = cloneDeep(InitialState);\n  const oldVisState = oldState.visState;\n\n  const mergedState = applyActions(visStateReducer, oldVisState, [\n    {action: updateVisData, payload: [stateParsed.datasets, {}, stateParsed.config]}\n  ]);\n  // check datasets is filtered\n  // and field has filterProps\n\n  const tFields0 = testFields.map(f => ({\n    ...f,\n    ...(f.name === 'time'\n      ? {filterProps: timeFilterProps}\n      : f.name === 'date'\n      ? {filterProps: dateFilterProps}\n      : f.name === 'epoch'\n      ? {filterProps: epochFilterProps}\n      : {})\n  }));\n  const dc0 = createDataContainer(testAllData, {fields: tFields0});\n\n  const tFields1 = fields.map(f => ({\n    ...f,\n    ...(f.name === 'TRIPS'\n      ? {filterProps: geoJsonTripFilterProps}\n      : f.name === 'RATE'\n      ? {filterProps: geoJsonRateFilterProps}\n      : {})\n  }));\n  const dc1 = createDataContainer(testGeoJsonAllData, {fields: tFields1});\n\n  const expectedDatasets = {\n    [testCsvDataId]: {\n      metadata: {\n        id: testCsvDataId,\n        label: 'hello.csv',\n        format: ''\n      },\n      type: '',\n      supportedFilterTypes: null,\n      disableDataOperation: false,\n      fields: tFields0,\n      dataContainer: dc0,\n      allIndexes: dc0.getPlainIndex(),\n      id: testCsvDataId,\n      label: 'hello.csv',\n      color: 'donot test me',\n      filteredIndex: [0, 1, 2, 3, 7, 8, 9, 10, 11, 12],\n      filteredIndexForDomain: [0, 1, 2, 3, 7, 8, 9, 10, 11, 12],\n      fieldPairs: oldCsvData.fieldPairs,\n      filterRecord: {\n        dynamicDomain: [mergedDateFilter],\n        fixedDomain: [mergedTimeFilter, mergedEpochFilter],\n        cpu: [mergedDateFilter],\n        gpu: [mergedTimeFilter, mergedEpochFilter]\n      },\n      gpuFilter: {\n        filterRange: [\n          [1474606800000 - 1474588800000, 1474617600000 - 1474588800000],\n          [1472700000000 - 1472688000000, 1472760000000 - 1472688000000],\n          [0, 0],\n          [0, 0]\n        ],\n        filterValueUpdateTriggers: {\n          gpuFilter_0: {name: 'time', domain0: 1474588800000},\n          gpuFilter_1: {name: 'epoch', domain0: 1472688000000},\n          gpuFilter_2: null,\n          gpuFilter_3: null\n        },\n        filterValueAccessor: {\n          inputs: [\n            {\n              index: 1\n            }\n          ],\n          result: [1474588800000 - 1474588800000, 1472688000000 - 1472688000000, 0, 0]\n        }\n      },\n      changedFilters: {\n        dynamicDomain: {'date-2': 'added'},\n        fixedDomain: {'time-0': 'added', 'epoch-4': 'added'},\n        cpu: {'date-2': 'added'},\n        gpu: {'time-0': 'added', 'epoch-4': 'added'}\n      }\n    },\n    [testGeoJsonDataId]: {\n      metadata: {\n        id: testGeoJsonDataId,\n        label: 'zip.geojson',\n        format: ''\n      },\n      type: '',\n      supportedFilterTypes: null,\n      disableDataOperation: false,\n      fields: tFields1,\n      filterRecord: {\n        dynamicDomain: [mergedRateFilter, mergedTripFilter],\n        fixedDomain: [],\n        cpu: [mergedRateFilter],\n        gpu: [mergedTripFilter]\n      },\n      gpuFilter: {\n        filterRange: [\n          [0, 8],\n          [0, 0],\n          [0, 0],\n          [0, 0]\n        ],\n        filterValueUpdateTriggers: {\n          gpuFilter_0: {name: 'TRIPS', domain0: 4},\n          gpuFilter_1: null,\n          gpuFilter_2: null,\n          gpuFilter_3: null\n        },\n        filterValueAccessor: {\n          inputs: [\n            {\n              index: 1\n            }\n          ],\n          result: [0, 0, 0, 0]\n        }\n      },\n      dataContainer: dc1,\n      allIndexes: [0, 1, 2, 3, 4],\n      id: testGeoJsonDataId,\n      label: 'zip.geojson',\n      color: 'donot test me',\n      filteredIndex: [0],\n      filteredIndexForDomain: [0],\n      fieldPairs: oldGeoJsonData.fieldPairs,\n      changedFilters: {\n        dynamicDomain: {'RATE-1': 'added', 'TRIPS-3': 'added'},\n        fixedDomain: null,\n        cpu: {'RATE-1': 'added'},\n        gpu: {'TRIPS-3': 'added'}\n      }\n    }\n  };\n\n  cmpDatasets(t, expectedDatasets, mergedState.datasets);\n\n  const expectedFilters = [\n    mergedTimeFilter,\n    mergedRateFilter,\n    mergedDateFilter,\n    mergedTripFilter,\n    mergedEpochFilter\n  ];\n\n  cmpFilters(t, expectedFilters, mergedState.filters);\n  t.end();\n});\n\ntest('VisStateMerger.v1 -> mergeFilters -> syncedFilters', t => {\n  const stateToSave = cloneDeep(StateWSyncedTimeFilter);\n  const appStateToSave = SchemaManager.save(stateToSave);\n  const {datasets, config} = appStateToSave;\n  t.equal(datasets.length, 2, 'should save 2 datasets');\n\n  // load config to initial state\n  const stateWithConfig = applyActions(coreReducer, InitialState, [\n    {action: addDataToMap, payload: [{config}]}\n  ]);\n\n  t.equal(stateWithConfig.visState.filters.length, 0, 'should not load filter without data');\n  t.equal(\n    stateWithConfig.visState.filterToBeMerged.length,\n    1,\n    'should save filter to filterToBeMerged'\n  );\n\n  const parsedDatasets = SchemaManager.parseSavedData(datasets);\n\n  // load data 1\n  const stateWithData1 = applyActions(coreReducer, stateWithConfig, [\n    {action: addDataToMap, payload: [{datasets: parsedDatasets[0]}]}\n  ]);\n\n  t.equal(Object.keys(stateWithData1.visState.datasets).length, 1, 'should load 1 dataset');\n  t.equal(stateWithData1.visState.filters.length, 0, 'should not load filter without all datasets');\n\n  // load data 2\n  const stateWithData2 = applyActions(coreReducer, stateWithData1, [\n    {action: addDataToMap, payload: [{datasets: parsedDatasets[1]}]}\n  ]);\n  t.equal(Object.keys(stateWithData2.visState.datasets).length, 2, 'should load 2 datasets');\n  t.equal(\n    stateWithData2.visState.filters.length,\n    1,\n    'should load filter when all datasets are ready'\n  );\n\n  cmpFilters(t, expectedSyncedTsFilter, stateWithData2.visState.filters[0]);\n\n  t.end();\n});\n\ntest('VisStateMerger -> import polygon filter map', t => {\n  const oldState = cloneDeep(InitialState);\n  const savedConfig = cloneDeep(polygonFilterMap);\n  const oldVisState = oldState.visState;\n\n  const parsedConfig = SchemaManager.parseSavedConfig(savedConfig.config, oldState);\n\n  const parsedFilters = parsedConfig.visState.filters;\n\n  const mergedState = mergeFilters(oldState.visState, parsedFilters);\n\n  Object.keys(oldVisState).forEach(key => {\n    if (key === 'filterToBeMerged') {\n      t.deepEqual(\n        mergedState.filterToBeMerged,\n        parsedFilters,\n        'Should save filters to filterToBeMerged before data loaded'\n      );\n    } else {\n      t.deepEqual(mergedState[key], oldVisState[key], 'Should keep the rest of state same');\n    }\n  });\n\n  const parsedData = SchemaManager.parseSavedData(savedConfig.datasets);\n\n  // load data into reducer\n  const stateWData = visStateReducer(mergedState, updateVisData(parsedData));\n\n  // parsed filters must be empty\n  cmpFilters(t, [], stateWData.filters);\n  t.end();\n});\n\ntest('VisStateMerger -> insertLayerAtRightOrder -> to empty config', t => {\n  const testCases = [\n    {\n      preservedOrder: ['a', 'b', 'c', 'd'],\n      batches: [\n        {\n          load: [{id: 'b'}],\n          expectedLayers: [{id: 'b'}],\n          expectedOrder: ['b']\n        },\n        {\n          load: [{id: 'a'}, {id: 'c'}],\n          expectedLayers: [{id: 'b'}, {id: 'a'}, {id: 'c'}],\n          expectedOrder: ['a', 'b', 'c']\n        },\n        {\n          load: [{id: 'd'}],\n          expectedLayers: [{id: 'b'}, {id: 'a'}, {id: 'c'}, {id: 'd'}],\n          expectedOrder: ['a', 'b', 'c', 'd']\n        }\n      ],\n      expectedLayers: [{id: 'b'}, {id: 'a'}, {id: 'c'}, {id: 'd'}],\n      expectedOrder: ['a', 'b', 'c', 'd']\n    },\n    {\n      preservedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h'],\n      batches: [\n        {\n          load: [{id: '6'}],\n          expectedLayers: [{id: '6'}],\n          expectedOrder: ['6']\n        },\n        {\n          load: [{id: 'e'}],\n          expectedLayers: [{id: '6'}, {id: 'e'}],\n          expectedOrder: ['e', '6']\n        },\n        {\n          load: [{id: 'x'}, {id: 'p'}, {id: 'w'}, {id: 'h'}],\n          expectedLayers: [{id: '6'}, {id: 'e'}, {id: 'x'}, {id: 'p'}, {id: 'w'}, {id: 'h'}],\n          expectedOrder: ['x', 'e', 'p', 'w', '6', 'h']\n        },\n        {\n          load: [{id: 'm'}],\n          expectedLayers: [\n            {id: '6'},\n            {id: 'e'},\n            {id: 'x'},\n            {id: 'p'},\n            {id: 'w'},\n            {id: 'h'},\n            {id: 'm'}\n          ],\n          expectedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h']\n        }\n      ],\n      expectedLayers: [{id: '6'}, {id: 'e'}, {id: 'x'}, {id: 'p'}, {id: 'w'}, {id: 'h'}, {id: 'm'}],\n      expectedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h']\n    },\n    {\n      preservedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h'],\n      batches: [\n        {\n          load: [{id: 'x'}, {id: 'm'}],\n          expectedLayers: [{id: 'x'}, {id: 'm'}],\n          expectedOrder: ['x', 'm']\n        },\n        {\n          load: [{id: 'w'}],\n          expectedLayers: [{id: 'x'}, {id: 'm'}, {id: 'w'}],\n          expectedOrder: ['x', 'm', 'w']\n        },\n        {\n          load: [{id: 'p'}],\n          expectedLayers: [{id: 'x'}, {id: 'm'}, {id: 'w'}, {id: 'p'}],\n          expectedOrder: ['x', 'm', 'p', 'w']\n        },\n        {\n          load: [{id: 'e'}, {id: 'h'}, {id: '6'}],\n          expectedLayers: [\n            {id: 'x'},\n            {id: 'm'},\n            {id: 'w'},\n            {id: 'p'},\n            {id: 'e'},\n            {id: 'h'},\n            {id: '6'}\n          ],\n          expectedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h']\n        }\n      ],\n      expectedOrder: ['x', 'm', 'e', 'p', 'w', '6', 'h'],\n      expectedLayers: [{id: 'x'}, {id: 'm'}, {id: 'w'}, {id: 'p'}, {id: 'e'}, {id: 'h'}, {id: '6'}]\n    }\n  ];\n\n  testCases.forEach(({preservedOrder, batches, expectedOrder, expectedLayers}) => {\n    let currentLayers = [];\n    let currentOrder = [];\n\n    // add layers in batch\n    for (const batch of batches) {\n      const {newLayerOrder, newLayers} = insertLayerAtRightOrder(\n        currentLayers,\n        batch.load,\n        currentOrder,\n        preservedOrder\n      );\n      currentLayers = newLayers;\n      currentOrder = newLayerOrder;\n\n      t.deepEqual(currentLayers, batch.expectedLayers, 'Should insert layer at correct Order');\n      t.deepEqual(currentOrder, batch.expectedOrder, 'Should reconstruct layer order');\n    }\n\n    t.deepEqual(currentLayers, expectedLayers, 'Should insert layer at correct Order');\n    t.deepEqual(currentOrder, expectedOrder, 'Should reconstruct layer order');\n  });\n\n  t.end();\n});\n\ntest('VisStateMerger -> insertLayerAtRightOrder -> to empty config', t => {\n  const preservedOrder = ['a', 'b', 'c', 'd'];\n\n  const batches = [\n    {load: [{id: 'b'}], expectedLayers: [{id: 'm'}, {id: 'b'}], expectedOrder: ['b', 'm']},\n    {\n      load: [{id: 'a'}, {id: 'c'}],\n      expectedLayers: [{id: 'm'}, {id: 'b'}, {id: 'a'}, {id: 'c'}],\n      expectedOrder: ['a', 'b', 'c', 'm']\n    },\n    {\n      load: [{id: 'd'}],\n      expectedLayers: [{id: 'm'}, {id: 'b'}, {id: 'a'}, {id: 'c'}, {id: 'd'}],\n      expectedOrder: ['a', 'b', 'c', 'd', 'm']\n    }\n  ];\n\n  let currentLayers = [{id: 'm'}];\n  let currentOrder = ['m'];\n\n  // add layers in batch\n  for (const batch of batches) {\n    const {newLayerOrder, newLayers} = insertLayerAtRightOrder(\n      currentLayers,\n      batch.load,\n      currentOrder,\n      preservedOrder\n    );\n    currentLayers = newLayers;\n    currentOrder = newLayerOrder;\n\n    t.deepEqual(currentLayers, batch.expectedLayers, 'Should insert layer at correct Order');\n    t.deepEqual(currentOrder, batch.expectedOrder, 'Should reconstruct layer order');\n  }\n\n  t.end();\n});\n\ntest('VisStateMerger -> load polygon filter map', t => {\n  const oldState = mockStateWithPolygonFilter();\n\n  const oldFilter = oldState.visState.filters[0];\n\n  const appStateToSave = SchemaManager.save(oldState);\n  const stateParsed = SchemaManager.load(appStateToSave);\n  const initialState = cloneDeep(InitialState);\n  const initialVisState = initialState.visState;\n\n  const visState = visStateReducer(\n    initialVisState,\n    updateVisData(stateParsed.datasets, {}, stateParsed.config)\n  );\n\n  const newFilter = visState.filters[0];\n\n  t.deepEqual(newFilter, oldFilter, 'Should have loaded the polygon filter correctly');\n  t.end();\n});\n\ntest('VisStateMerger -> load time filter/trip layer synced map', t => {\n  const oldState = mockStateWithSyncedFilterAndTripLayer();\n  oldState.visState = syncTimeFilterWithLayerTimelineUpdater(oldState.visState, {\n    idx: 0,\n    enable: true\n  });\n\n  let filter = oldState.visState.filters[0];\n\n  oldState.visState = setFilterAnimationTimeUpdater(oldState.visState, {\n    idx: 0,\n    prop: 'value',\n    value: [filter.domain[0], filter.domain[0] + 1000]\n  });\n\n  filter = oldState.visState.filters[0];\n\n  const appStateToSave = SchemaManager.save(oldState);\n  const stateParsed = SchemaManager.load(appStateToSave);\n  const initialState = cloneDeep(InitialState);\n  const initialVisState = initialState.visState;\n\n  const visState = applyActions(visStateReducer, initialVisState, [\n    {action: updateVisData, payload: [stateParsed.datasets, {}, stateParsed.config]}\n  ]);\n\n  const newFilter = visState.filters[0];\n\n  t.deepEqual(filter.value, newFilter.value, 'Should have loaded the same filter value');\n\n  // check syncedWithLayerTimeline\n  t.equal(\n    newFilter.syncedWithLayerTimeline,\n    true,\n    'Should have set syncedWithLayerTimeline to true'\n  );\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'Should have set syncTimelineMode to SYNC_TIMELINE_MODES.end'\n  );\n\n  // check animationConfig value\n  t.equal(\n    visState.animationConfig.currentTime,\n    oldState.visState.animationConfig.currentTime,\n    'Should have set animationConfig value to filter value[0]'\n  );\n\n  t.end();\n});\n\ntest('VisStateMerger -> createLayerFromConfig with Parsed Layer', t => {\n  const oldState = CloneDeep(StateWFiles);\n\n  t.equal(oldState.visState.layers.length, 2, 'Should have layers');\n\n  // mock an exported layer config with visual channels\n  const savedLayer = visStateSchema[CURRENT_VERSION].save({\n    layers: [oldState.visState.layers[0]],\n    layerOrder: [oldState.visState.layers[0].id]\n  }).visState.layers[0];\n\n  t.ok(savedLayer.visualChannels, 'Should have visualChannels');\n\n  const addedLayer = createLayerFromConfig(oldState.visState, savedLayer);\n\n  t.ok(addedLayer.visConfigSettings, 'Should have visConfig settings loaded correctly');\n\n  t.end();\n});\n\nconst MOCK_MERGE_TASK = Task.fromPromise(\n  time => new Promise(resolve => window.setTimeout(resolve, time)),\n  'MOCK_MERGE_TASK'\n);\nconst mergeSuccess = payload => ({\n  type: 'MERGE_SUCCESS',\n  payload\n});\nconst mergeError = payload => ({\n  type: 'MERGE_ERROR',\n  payload\n});\n\nfunction mockMergeSuccessUpdater(state, action) {\n  const {mergerActionPayload, dataId} = action.payload;\n  const nextState = {\n    ...state,\n    visState: {\n      ...state.visState,\n      isMergingDatasets: {\n        ...state.isMergingDatasets,\n        [dataId]: false\n      }\n    }\n  };\n  return {\n    ...nextState,\n    visState: applyMergersUpdater(nextState.visState, mergerActionPayload)\n  };\n}\n\nfunction asyncMerger(\n  state,\n  {processToBeMerged, operationsToBeMerged},\n  fromConfig,\n  mergerActionPayload\n) {\n  if (!processToBeMerged) {\n    return state;\n  }\n  const {dataId} = processToBeMerged;\n\n  if (!state.datasets[dataId] && (processToBeMerged || operationsToBeMerged)) {\n    return {\n      ...state,\n      processToBeMerged,\n      operationsToBeMerged\n    };\n  }\n  const nextState = {\n    ...state,\n    isMergingDatasets: {\n      ...state.isMergingDatasets,\n      [dataId]: true\n    }\n  };\n\n  const mergeMergeTasks = MOCK_MERGE_TASK(100).bimap(\n    () => mergeSuccess({mergerActionPayload, dataId}),\n    err => mergeError({mergerActionPayload, dataId, err})\n  );\n\n  return withTask(nextState, mergeMergeTasks);\n}\nconst mockMerger = {\n  merge: asyncMerger,\n  // test props being an array\n  prop: ['process', 'operations'],\n  toMergeProp: ['processToBeMerged', 'operationsToBeMerged'],\n  waitToFinish: true\n};\n\n// prepare reducers\nconst mockReducer = keplerGlReducer\n  .initialState({\n    visState: {\n      process: undefined,\n      processToBeMerged: undefined,\n      mergers: [mockMerger, ...VIS_STATE_MERGERS]\n    }\n  })\n  .plugin({\n    MERGE_SUCCESS: mockMergeSuccessUpdater,\n    MERGE_ERROR: mockMergeSuccessUpdater\n  });\n\n// eslint-disable-next-line max-statements\ntest('VisStateMerger -> asynс mergers', t => {\n  // adding mock process to state\n  const stateToSave = cloneDeep(StateWMultiFilters);\n  const appStateToSave = SchemaManager.save(stateToSave);\n  const stateParsed = SchemaManager.load(appStateToSave);\n\n  const configWithProcess = {\n    ...stateParsed.config,\n    visState: {\n      ...stateParsed.config.visState,\n      process: {dataId: testCsvDataId}\n    }\n  };\n  drainTasksForTesting();\n  const initialState = mockReducer(undefined, registerEntry({id: 'test'}));\n\n  // apply config with process to merge\n  const nextState = applyActions(mockReducer, initialState, [\n    {\n      // add csv data first\n      action: updateVisData,\n      payload: [stateParsed.datasets.find(d => d.info.id === testCsvDataId), {}, configWithProcess]\n    }\n  ]);\n\n  t.deepEqual(\n    nextState.test.visState.isMergingDatasets,\n    {[testCsvDataId]: true},\n    'should set test dataId to true'\n  );\n  t.equal(nextState.test.visState.layers.length, 0, 'should not add any layers');\n  t.equal(nextState.test.visState.filters.length, 0, 'should not add any filters');\n  t.equal(\n    nextState.test.visState.layerToBeMerged.length,\n    2,\n    'should have 2 layers waiting to be merged'\n  );\n\n  t.ok(nextState.test.visState.datasets[testCsvDataId], 'should add csv data');\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'Should create one fit bounds task');\n  t.equal(tasks[0].type, 'MOCK_MERGE_TASK', 'should create merger task');\n\n  // add another dataset will async merger is in process\n  const nextState1 = applyActions(mockReducer, nextState, [\n    {\n      // add geojson data\n      action: updateVisData,\n      payload: [stateParsed.datasets.find(d => d.info.id === testGeoJsonDataId)]\n    }\n  ]);\n\n  t.ok(nextState1.test.visState.datasets[testGeoJsonDataId], 'should add geojson data');\n\n  t.deepEqual(\n    nextState1.test.visState.isMergingDatasets,\n    {[testCsvDataId]: true},\n    'isMergingDatasets of dataId is still true'\n  );\n\n  t.equal(nextState1.test.visState.layers.length, 1, 'should merge 1 layer');\n  t.equal(\n    nextState1.test.visState.layers[0].config.dataId,\n    testGeoJsonDataId,\n    'should only merge layer of geojson data'\n  );\n\n  t.equal(nextState1.test.visState.filters.length, 2, 'should merge 2 filters');\n  t.deepEqual(\n    nextState1.test.visState.filters.map(f => f.dataId),\n    [[testGeoJsonDataId], [testGeoJsonDataId]],\n    'should merge 2 filters of geojson data'\n  );\n\n  // async merge succeed\n  const nextState2 = mockReducer(nextState1, succeedTaskInTest(tasks[0], undefined));\n  t.deepEqual(\n    nextState2.test.visState.isMergingDatasets,\n    {[testCsvDataId]: false},\n    'should set isMerging data to false'\n  );\n\n  t.equal(nextState2.test.visState.layers.length, 2, 'should merge 2 layers');\n  t.equal(\n    nextState2.test.visState.layers[1].config.dataId,\n    testCsvDataId,\n    'should merge layer of csv data'\n  );\n  t.equal(nextState2.test.visState.filters.length, 5, 'should merge 5 filters');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/reducers/vis-state-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* eslint-disable max-statements */\nimport test from 'tape-catch';\nimport sinon from 'sinon';\nimport {console as Console} from 'global/window';\n\nimport {drainTasksForTesting, succeedTaskInTest, errorTaskInTest} from 'react-palm/tasks';\nimport CloneDeep from 'lodash/cloneDeep';\n\nimport SchemaManager from '@kepler.gl/schemas';\nimport {VisStateActions, MapStateActions} from '@kepler.gl/actions';\nimport {\n  visStateReducer as reducer,\n  INITIAL_VIS_STATE,\n  DEFAULT_ANIMATION_CONFIG,\n  serializeLayer,\n  validateLayerWithData,\n  defaultInteractionConfig,\n  prepareStateForDatasetReplace,\n  syncTimeFilterWithLayerTimelineUpdater,\n  setTimeFilterTimelineModeUpdater,\n  setFilterAnimationTimeUpdater,\n  setFilterAnimationWindowUpdater\n} from '@kepler.gl/reducers';\n\nimport {processCsvData, processGeojson} from '@kepler.gl/processors';\nimport {Layer, KeplerGlLayers, COLUMN_MODE_TABLE} from '@kepler.gl/layers';\nimport {maybeToDate} from '@kepler.gl/table';\nimport {\n  createDataContainer,\n  applyFilterFieldName,\n  getAnimatableVisibleLayers,\n  getDefaultFilter,\n  histogramFromDomain,\n  TileTimeInterval\n} from '@kepler.gl/utils';\nimport {\n  ALL_FIELD_TYPES,\n  EDITOR_MODES,\n  LAYER_VIS_CONFIGS,\n  DEFAULT_TEXT_LABEL,\n  DEFAULT_COLOR_UI,\n  FILTER_VIEW_TYPES,\n  LIGHT_AND_SHADOW_EFFECT,\n  ANIMATION_WINDOW,\n  BINS,\n  INTERVAL,\n  LAYER_TYPES,\n  SYNC_TIMELINE_MODES,\n  KEPLER_COLOR_PALETTES,\n  colorPaletteToColorRange\n} from '@kepler.gl/constants';\n\nconst {ArcLayer, PointLayer, GeojsonLayer, LineLayer, TripLayer} = KeplerGlLayers;\n\n// fixtures\nimport testData, {\n  mergedTimeFilter,\n  testFields,\n  testAllData,\n  expectedSyncedTsFilter,\n  testCsvFieldPairs\n} from 'test/fixtures/test-csv-data';\nimport {\n  geojsonData,\n  geoJsonTripFilterProps,\n  expectedDataToFeature,\n  updatedGeoJsonLayer,\n  fields as geojsonFields,\n  rows as geojsonRows\n} from 'test/fixtures/geojson';\nimport tripCsvData, {tripCsvDataInfo, expectedCoordinates} from 'test/fixtures/test-trip-csv-data';\nimport tripGeojson, {timeStampDomain, tripDataInfo} from 'test/fixtures/trip-geojson';\nimport {mockPolygonFeature, mockPolygonFeature2, mockPolygonData} from 'test/fixtures/polygon';\n\n// test helpers\nimport {\n  cmpFilters,\n  cmpLayers,\n  cmpEffects,\n  cmpDatasets,\n  cmpDataset,\n  cmpObjectKeys,\n  cmpField,\n  assertDatasetIsTable\n} from 'test/helpers/comparison-utils';\nimport {\n  applyActions,\n  StateWTripGeojson,\n  StateWSplitMaps,\n  StateWFilters,\n  StateWFiles,\n  StateWFilesFiltersLayerColor,\n  StateWSyncedTimeFilter,\n  StateWH3Layer,\n  testCsvDataId,\n  testGeoJsonDataId,\n  InitialState,\n  stateWithTimeFilterAndTripLayer\n} from 'test/helpers/mock-state';\nimport {getNextColorMakerValue} from 'test/helpers/layer-utils';\nimport {expectedTripLayerConfig} from '../../fixtures/test-trip-csv-data';\nimport {\n  testCsvDataSlice1,\n  testCsvDataSlice2,\n  testCsvDataSlice1Id,\n  testCsvDataSlice2Id\n} from '../../fixtures/test-csv-data';\nimport {mockStateWithSyncedFilterAndTripLayer} from '../../fixtures/synced-filter-with-trip-layer';\nimport {createNewDataEntryMock} from 'test/helpers/table-utils';\n\nconst mockData = {\n  fields: [\n    {\n      name: 'start_point_lat',\n      type: 'real'\n    },\n    {\n      name: 'start_point_lng',\n      type: 'real'\n    },\n    {\n      name: 'end_point_lat',\n      type: 'real'\n    },\n    {\n      name: 'end_point_lng',\n      type: 'real'\n    }\n  ],\n  data: [\n    [12.25, 37.75, 45.21, 100.12],\n    [null, 35.2, 45.0, 21.3],\n    [12.29, 37.64, 46.21, 99.127],\n    [null, null, 33.1, 29.34]\n  ]\n};\n\nconst expectedFields = [\n  {\n    name: 'start_point_lat',\n    displayName: 'start_point_lat',\n    id: 'start_point_lat',\n    type: 'real',\n    fieldIdx: 0,\n    analyzerType: 'FLOAT',\n    format: '',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'start_point_lng',\n    displayName: 'start_point_lng',\n    id: 'start_point_lng',\n    type: 'real',\n    fieldIdx: 1,\n    analyzerType: 'FLOAT',\n    format: '',\n    valueAccessor: values => values[1]\n  },\n  {\n    name: 'end_point_lat',\n    displayName: 'end_point_lat',\n    id: 'end_point_lat',\n    type: 'real',\n    fieldIdx: 2,\n    analyzerType: 'FLOAT',\n    format: '',\n    valueAccessor: values => values[2]\n  },\n  {\n    name: 'end_point_lng',\n    displayName: 'end_point_lng',\n    id: 'end_point_lng',\n    type: 'real',\n    fieldIdx: 3,\n    analyzerType: 'FLOAT',\n    format: '',\n    valueAccessor: values => values[3]\n  }\n];\n\nconst expectedFieldParis = [\n  {\n    defaultName: 'start_point',\n    pair: {\n      lat: {\n        value: 'start_point_lat',\n        fieldIdx: 0\n      },\n      lng: {\n        value: 'start_point_lng',\n        fieldIdx: 1\n      }\n    },\n    suffix: ['lat', 'lng']\n  },\n  {\n    defaultName: 'end_point',\n    pair: {\n      lat: {\n        value: 'end_point_lat',\n        fieldIdx: 2\n      },\n      lng: {\n        value: 'end_point_lng',\n        fieldIdx: 3\n      }\n    },\n    suffix: ['lat', 'lng']\n  }\n];\nconst mockFilter = {\n  fieldIdx: 0,\n  name: mockData.fields[0].name,\n  displayName: mockData.fields[0].name,\n  id: mockData.fields[0].name,\n  type: 'range',\n  fieldType: 'real',\n  value: [12.25, 12.28]\n};\n\nconst mockRawData = {\n  fields: [\n    {\n      name: 'start_point_lat',\n      id: 'start_point_lat',\n      displayName: 'start_point_lat',\n      type: 'real',\n      fieldIdx: 0\n    },\n    {\n      name: 'start_point_lng',\n      id: 'start_point_lng',\n      displayName: 'start_point_lng',\n      type: 'real',\n      fieldIdx: 1\n    },\n    {\n      name: 'end_point_lat',\n      id: 'end_point_lat',\n      displayName: 'end_point_lat',\n      type: 'real',\n      fieldIdx: 2\n    },\n    {\n      name: 'end_point_lng',\n      id: 'end_point_lng',\n      displayName: 'end_point_lng',\n      type: 'real',\n      fieldIdx: 3\n    }\n  ],\n  rows: [\n    [12.25, 37.75, 45.21, 100.12],\n    [null, 35.2, 45.0, 21.3],\n    [12.29, 37.64, 46.21, 99.127],\n    [null, null, 33.1, 29.34]\n  ]\n};\n\ntest('#visStateReducer', t => {\n  t.deepEqual(\n    reducer(undefined, {}),\n    {...INITIAL_VIS_STATE, initialState: {}},\n    'should return the initial state'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> ADD_FILTER', t => {\n  const dataId = 'kitten';\n  const newFilter = getDefaultFilter({dataId});\n  const newReducer = reducer({filters: [mockFilter]}, VisStateActions.addFilter(dataId));\n\n  const expectedReducer = {filters: [mockFilter, newFilter]};\n\n  t.equal(newReducer.filters.length, expectedReducer.filters.length, 'should add a default filter');\n\n  t.deepEqual(newReducer.filters[0], expectedReducer.filters[0], 'should add a default filter');\n\n  cmpFilters(t, newReducer.filters[1], expectedReducer.filters[1]);\n  t.end();\n});\n\ntest('#visStateReducer -> ADD_LAYER.1', t => {\n  const oldState = {\n    ...INITIAL_VIS_STATE,\n    datasets: {\n      puppy: {\n        data: mockData.data,\n        fields: mockData.fields\n      }\n    },\n    layers: [{id: 'existing_layer'}],\n    layerData: [[{data: [1, 2, 3]}, {data: [4, 5, 6]}]],\n    layerOrder: ['existing_layer'],\n    splitMaps: [\n      {\n        layers: {existing_layer: false}\n      },\n      {\n        layers: {existing_layer: false}\n      }\n    ]\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.addLayer());\n  const newId = newReducer.layers[1].id;\n  const expectedSplitMaps = [\n    {\n      layers: {\n        existing_layer: false,\n        [newId]: true\n      }\n    },\n    {\n      layers: {\n        existing_layer: false,\n        [newId]: true\n      }\n    }\n  ];\n\n  t.equal(newReducer.layers.length, 2, 'should have 2 layers');\n  t.equal(newReducer.layers[1].config.isVisible, true, 'newLayer visibility should be set to true');\n  t.equal(\n    newReducer.layers[1].config.isConfigActive,\n    true,\n    'newLayer isConfigActive should be set to true'\n  );\n  t.equal(newReducer.layers[1].config.dataId, 'puppy', 'newLayer dataId should be set to default');\n  t.deepEqual(\n    newReducer.layerData,\n    [oldState.layerData[0], {}],\n    'newState should have empty layer datat'\n  );\n  t.deepEqual(\n    newReducer.layerOrder,\n    [newReducer.layers[1].id, newReducer.layers[0].id],\n    'should add to layerOrder'\n  );\n  t.deepEqual(newReducer.splitMaps, expectedSplitMaps, 'should add to SplitMaps');\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_TYPE_CHANGE.0', t => {\n  const layer = new Layer({id: 'blue'});\n\n  const oldState = {\n    ...INITIAL_VIS_STATE\n  };\n\n  const nextState = reducer(oldState, VisStateActions.layerTypeChange());\n\n  t.equal(oldState, nextState, 'should return state when no argument is given');\n\n  const nextState2 = reducer(oldState, VisStateActions.layerTypeChange(layer, 'no_type'));\n  t.equal(oldState, nextState2, 'should return state when pass a none existing type');\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_TYPE_CHANGE.1', async t => {\n  const layer = new Layer({id: 'more_layer'});\n  const oldState = {\n    ...INITIAL_VIS_STATE,\n    datasets: await createNewDataEntryMock({\n      info: {id: 'puppy', label: 'puppy'},\n      data: {\n        rows: mockData.data,\n        fields: mockData.fields\n      }\n    }),\n    layers: [{id: 'existing_layer'}, layer],\n    layerData: [[{data: [1, 2, 3]}, {data: [4, 5, 6]}]],\n    layerOrder: ['more_layer', 'existing_layer'],\n    hoverInfo: {\n      layer: {props: {id: 'more_layer'}},\n      picked: true\n    },\n    clicked: {\n      layer: {props: {id: 'more_layer'}},\n      picked: true\n    },\n    splitMaps: [\n      {\n        layers: {\n          existing_layer: false,\n          more_layer: true\n        }\n      },\n      {\n        layers: {\n          existing_layer: false,\n          more_layer: false\n        }\n      }\n    ]\n  };\n\n  const nextState = reducer(oldState, VisStateActions.layerTypeChange(layer, 'point'));\n  const newId = nextState.layers[1].id;\n  const expectedSplitMaps = [\n    {\n      layers: {\n        existing_layer: false,\n        [newId]: true\n      }\n    },\n    {\n      layers: {\n        existing_layer: false,\n        [newId]: false\n      }\n    }\n  ];\n\n  t.ok(nextState.layers[1].id !== 'more_layer', 'should update layer id');\n\n  t.equal(nextState.layers[1].type, 'point', 'should update type to point');\n\n  t.deepEqual(\n    nextState.splitMaps,\n    expectedSplitMaps,\n    'should add newId to SplitMaps, and replace old id'\n  );\n\n  t.ok(!nextState.clicked, 'should reset clicked');\n  t.ok(!nextState.hoverInfo, 'should reset hoverInfo');\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_TYPE_CHANGE.2', async t => {\n  const pointLayer = new PointLayer({id: 'a', dataId: 'smoothie'});\n  const mockColorRange = {\n    name: 'abc',\n    isReversed: true,\n    colors: ['a', 'b', 'c']\n  };\n  const datasets = await createNewDataEntryMock({\n    info: {id: 'smoothie'},\n    data: {\n      rows: testAllData,\n      fields: testFields\n    }\n  });\n  const oldState = {\n    ...INITIAL_VIS_STATE,\n    datasets,\n    layers: [pointLayer],\n    layerData: []\n  };\n\n  // set point layer colorField to a string field\n  let stringField = testFields.find(f => f.type === 'string');\n  stringField = {\n    ...stringField,\n    valueAccessor: stringField.valueAccessor(datasets.smoothie.dataContainer)\n  };\n\n  let nextState = reducer(\n    oldState,\n    VisStateActions.layerVisualChannelConfigChange(pointLayer, {colorField: stringField}, 'color')\n  );\n\n  nextState = reducer(\n    nextState,\n    VisStateActions.layerVisConfigChange(nextState.layers[0], {\n      colorRange: mockColorRange\n    })\n  );\n  const newLayer = nextState.layers[0];\n  t.equal(newLayer.config.colorField, stringField, 'should update colorField');\n  t.equal(newLayer.config.colorScale, 'ordinal', 'should scale to ordinal');\n  t.deepEqual(\n    newLayer.config.colorDomain,\n    ['driver_analytics', 'driver_analytics_0', 'driver_gps'],\n    'should calculate color domain'\n  );\n  t.equal(newLayer.config.visConfig.colorRange, mockColorRange, 'should update color range');\n\n  // set point layer sizeField to a int field\n  let intField = testFields.find(f => f.type === 'integer');\n  intField = {...intField, valueAccessor: intField.valueAccessor(datasets.smoothie.dataContainer)};\n  let nextState2 = reducer(\n    nextState,\n    VisStateActions.layerVisualChannelConfigChange(newLayer, {sizeField: intField}, 'size')\n  );\n  nextState2 = reducer(\n    nextState2,\n    VisStateActions.layerVisConfigChange(nextState2.layers[0], {\n      radiusRange: [5, 10]\n    })\n  );\n  const newLayer2 = nextState2.layers[0];\n  t.equal(newLayer2.config.sizeField, intField, 'should update sizeField');\n  t.equal(newLayer2.config.sizeScale, 'sqrt', 'should scale to sqrt');\n  t.deepEqual(newLayer2.config.sizeDomain, [1, 12124], 'should calculate size domain');\n  t.deepEqual(newLayer2.config.visConfig.radiusRange, [5, 10], 'should update size range');\n\n  // change point layer type to hexagon\n  const nextState3 = reducer(nextState2, VisStateActions.layerTypeChange(newLayer2, 'hexagon'));\n\n  const newLayer3 = nextState3.layers[0];\n  t.equal(newLayer3.type, 'hexagon', 'should change type to hexagon');\n  t.equal(newLayer3.config.colorField, stringField, 'should keep colorField');\n  t.deepEqual(\n    newLayer3.config.colorDomain,\n    [0, 1],\n    'should set colorDomain to default, it is calculated inside deck.gl layer'\n  );\n  t.equal(newLayer3.config.colorScale, 'ordinal', 'should set colorScale to ordinal');\n  t.equal(newLayer3.config.sizeScale, 'sqrt', 'should set sizeScale to default');\n  t.deepEqual(newLayer3.config.sizeDomain, [0, 1], 'should set sizeDomain to default');\n  t.equal(newLayer3.config.sizeField, intField, 'should keep sizeField');\n  t.notEqual(newLayer3.id, newLayer2.id, 'should change id');\n  t.equal(newLayer3.config.visConfig.colorRange, mockColorRange, 'should not deep copy colorRange');\n  t.equal(\n    newLayer3.config.visConfig.sizeRange,\n    LAYER_VIS_CONFIGS.elevationRange.defaultValue,\n    'should set sizeRange back to default'\n  );\n\n  // change point layer type to icon\n  const nextState4 = reducer(nextState2, VisStateActions.layerTypeChange(newLayer2, 'icon'));\n  const newLayer4 = nextState4.layers[0];\n  t.equal(newLayer4.type, 'icon', 'should change type to icon');\n  t.notEqual(newLayer4.id, newLayer2.id, 'should change id');\n  t.equal(newLayer4.config.colorField, stringField, 'should keep colorField');\n  t.deepEqual(\n    newLayer4.config.colorDomain,\n    ['driver_analytics', 'driver_analytics_0', 'driver_gps'],\n    'should calculate color domain'\n  );\n  t.equal(newLayer4.config.colorScale, 'ordinal', 'should keep color scale');\n  t.equal(newLayer4.config.sizeField, intField, 'should keep sizeField');\n  t.equal(newLayer4.config.sizeScale, 'sqrt', 'should scale to linear');\n  t.deepEqual(newLayer4.config.sizeDomain, [1, 12124], 'should keep size domain');\n  t.deepEqual(newLayer4.config.visConfig.radiusRange, [5, 10], 'should keep size range');\n  t.equal(newLayer4.config.visConfig.colorRange, mockColorRange, 'should not deep copy colorRange');\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_TYPE_CHANGE.3 -> animationConfig', async t => {\n  const layer = new GeojsonLayer({\n    label: 'taro and blue',\n    dataId: 'taro',\n    columns: {geojson: {fieldIdx: 0, value: '_geojson'}},\n    isVisible: true,\n    color: [1, 1, 1],\n    id: 'taro'\n  });\n\n  const dataset = await createNewDataEntryMock({\n    info: {id: 'taro'},\n    data: processGeojson(tripGeojson)\n  });\n\n  const oldState = {\n    ...INITIAL_VIS_STATE,\n    datasets: dataset,\n    layers: [layer],\n    layerData: [{}],\n    layerOrder: [0]\n  };\n\n  // change GeoJson layer to Trip layer\n  const nextState = reducer(oldState, VisStateActions.layerTypeChange(layer, 'trip'));\n\n  const foundLayer = nextState.layers[0];\n  const foundLayerId = foundLayer.id;\n  t.ok(foundLayerId !== 'taro', 'should update layer id');\n  t.equal(foundLayer.type, 'trip', 'should update type to trip');\n  t.deepEqual(\n    foundLayer.config.animation,\n    {enabled: true, domain: timeStampDomain},\n    'should set correct animation domain'\n  );\n\n  t.deepEqual(\n    nextState.animationConfig,\n    {\n      ...DEFAULT_ANIMATION_CONFIG,\n      domain: timeStampDomain,\n      currentTime: timeStampDomain[0],\n      duration: null,\n      timeSteps: null,\n      defaultTimeFormat: 'L LTS'\n    },\n    'should update visState.animationConfig'\n  );\n\n  // change Trip layer to Geojson layer\n  const nextState2 = reducer(oldState, VisStateActions.layerTypeChange(foundLayer, 'geojson'));\n  const foundLayer2 = nextState2.layers[0];\n  t.ok(foundLayer2.id !== foundLayerId, 'should update layer id');\n  t.equal(foundLayer2.type, 'geojson', 'should update type to trip');\n\n  t.deepEqual(\n    foundLayer2.config.animation,\n    {enabled: false},\n    'should set correct animation domain fro Geojson layer'\n  );\n\n  t.deepEqual(\n    nextState2.animationConfig,\n    DEFAULT_ANIMATION_CONFIG,\n    'should set animationConfig to default'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_CONFIG_CHANGE -> isVisible -> animationConfig', t => {\n  const initialState = StateWTripGeojson;\n  const layer = initialState.visState.layers[0];\n\n  // change Trip layer isVisible\n  const nextState = reducer(\n    StateWTripGeojson.visState,\n    VisStateActions.layerConfigChange(layer, {isVisible: false})\n  );\n\n  t.deepEqual(\n    nextState.animationConfig,\n    {\n      ...DEFAULT_ANIMATION_CONFIG,\n      domain: null,\n      currentTime: 1565577261000,\n      speed: 1,\n      isAnimating: false,\n      duration: null,\n      timeSteps: null,\n      defaultTimeFormat: null,\n      timeFormat: null,\n      timezone: null\n    },\n    'should set animationConfig to default'\n  );\n\n  const nextState2 = reducer(\n    nextState,\n    VisStateActions.layerConfigChange(nextState.layers[0], {isVisible: true})\n  );\n\n  t.deepEqual(\n    nextState2.animationConfig,\n    {\n      ...nextState2.animationConfig,\n      domain: timeStampDomain,\n      currentTime: timeStampDomain[0],\n      duration: null,\n      timeSteps: null,\n      defaultTimeFormat: 'L LTS'\n    },\n    'should set animationConfig domain and currentTime'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_CONFIG_CHANGE -> isVisible -> splitMaps', t => {\n  const initialState = StateWSplitMaps.visState;\n  const layer = initialState.layers[0];\n\n  const initialSplitMaps = [\n    {layers: {'point-0': false, 'geojson-1': true}},\n    {layers: {'point-0': true, 'geojson-1': true}}\n  ];\n  const expectedSplitMaps = [{layers: {'geojson-1': true}}, {layers: {'geojson-1': true}}];\n  t.deepEqual(initialState.splitMaps, initialSplitMaps, 'should has the same initial splitMaps');\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerConfigChange(layer, {isVisible: false})\n  );\n\n  t.equal(nextState.layers[0].config.isVisible, false, 'should set layer 0 visibility to false');\n  t.equal(initialState.layerData[0], nextState.layerData[0], 'should not update layerData');\n  t.deepEqual(nextState.splitMaps, expectedSplitMaps, 'should remove layer from splitMaps');\n\n  const nextState2 = reducer(\n    nextState,\n    VisStateActions.layerConfigChange(layer, {isVisible: true})\n  );\n\n  const initialSplitMaps2 = [\n    {layers: {'point-0': true, 'geojson-1': true}},\n    {layers: {'point-0': true, 'geojson-1': true}}\n  ];\n  t.deepEqual(nextState2.splitMaps, initialSplitMaps2, 'should add layer to splitMaps');\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_CONFIG_CHANGE -> columnMode', t => {\n  const initialState = InitialState.visState;\n  // const initialState = cloneDeep(state || InitialState);\n  const updatedState = applyActions(reducer, initialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [{info: tripCsvDataInfo, data: processCsvData(tripCsvData)}]\n    }\n  ]);\n\n  const pointLayer = updatedState.layers[0];\n  // change layer type to trip\n  const updatedState2 = reducer(updatedState, VisStateActions.layerTypeChange(pointLayer, 'trip'));\n  // trip Layer\n  const tripLayer = updatedState2.layers[0];\n\n  // update trip layer column mode\n  const nextState = reducer(\n    updatedState2,\n    VisStateActions.layerConfigChange(tripLayer, {\n      columnMode: COLUMN_MODE_TABLE\n    })\n  );\n\n  const expectedLayerConfigColumns = {\n    geojson: {value: null, fieldIdx: -1},\n    id: {value: null, fieldIdx: -1},\n    lat: {value: 'location-lat', fieldIdx: 2},\n    lng: {value: 'location-lng', fieldIdx: 1},\n    timestamp: {value: null, fieldIdx: -1},\n    altitude: {value: 'location-alt', fieldIdx: 6, optional: true}\n  };\n  t.deepEqual(\n    nextState.layers[0].config.columns,\n    expectedLayerConfigColumns,\n    'should update layer columns'\n  );\n  t.equal(\n    nextState.layers[0].config.columnMode,\n    COLUMN_MODE_TABLE,\n    'should update layer columnMode'\n  );\n\n  t.deepEqual(nextState.layerData[0], {}, 'should not format layer data without all columns');\n  // update trip layer column mode and columns id, timestap\n  const nextState1 = reducer(\n    nextState,\n    VisStateActions.layerConfigChange(nextState.layers[0], {\n      columns: {\n        ...expectedLayerConfigColumns,\n        timestamp: {value: 'timestamp', fieldIdx: 0},\n        id: {value: 'name', fieldIdx: 5}\n      }\n    })\n  );\n  t.ok(nextState1.layerData[0].data, 'should format layer data with columns');\n  t.equal(nextState1.layerData[0].data.length, 2, 'Should format 2 geojson features');\n\n  t.deepEqual(\n    nextState1.layerData[0].data[0].geometry.coordinates.slice(0, 2),\n    expectedCoordinates,\n    'feature[0] coordinates should be correct'\n  );\n  t.deepEqual(\n    nextState1.layerData[0].data[0].properties.index,\n    0,\n    'feature[0] properties index should be correct'\n  );\n  t.deepEqual(\n    nextState1.layerData[0].data[0].properties.values.length,\n    8,\n    'feature[0] properties values should have correct length'\n  );\n  const stateToSave = SchemaManager.save({visState: nextState1});\n  const savedTripLayer = stateToSave.config.config.visState.layers[0];\n  t.equal(savedTripLayer.config.columnMode, COLUMN_MODE_TABLE, 'should save columnMode');\n  t.deepEqual(\n    savedTripLayer.config.columns,\n    expectedTripLayerConfig.config.columns,\n    'should save trip layer config columns'\n  );\n\n  // console.log(JSON.stringify(stateToSave.config.config.visState.layers[0], null, 2));\n  t.end();\n});\n\ntest('visStateReducer -> layerDataIdChangeUpdater', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor).visState;\n  const pointLayer = initialState.layers[0];\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerConfigChange(pointLayer, {\n      dataId: testGeoJsonDataId\n    })\n  );\n  const updatedLayer = nextState.layers[0];\n\n  t.equal(updatedLayer.config.dataId, testGeoJsonDataId, 'should update point layer dataId');\n  const expectedLayerColumns = new PointLayer({}).config.columns;\n  t.deepEqual(updatedLayer.config.columns, expectedLayerColumns, 'should reset point layer column');\n  t.equal(updatedLayer.config.colorField, null, 'should not assign point layer colorField');\n\n  // add layer\n  const nextState1 = reducer(nextState, VisStateActions.addLayer());\n  const newLayer = nextState1.layers[nextState1.layers.length - 1];\n\n  const nextState2 = reducer(\n    nextState1,\n    VisStateActions.layerConfigChange(newLayer, {\n      dataId: testGeoJsonDataId,\n      color: [1, 2, 3]\n    })\n  );\n\n  t.equal(\n    nextState2.layers[nextState1.layers.length - 1].config.dataId,\n    testGeoJsonDataId,\n    'should update new layer dataId'\n  );\n  t.deepEqual(\n    nextState2.layers[nextState1.layers.length - 1].config.color,\n    [1, 2, 3],\n    'should update new layer color'\n  );\n\n  t.end();\n});\n\ntest('visStateReducer -> layerDataIdChangeUpdater -> geojson', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor).visState;\n  const nextState = applyActions(reducer, initialState, [\n    // add another geojson\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            info: {id: 'geojson2', label: 'Some Geojson'},\n            data: {fields: geojsonFields, rows: geojsonRows.slice(0, 3)}\n          }\n        ]\n      ]\n    }\n  ]);\n\n  // find geojson layer\n  const index = nextState.layers.findIndex(l => l.type === 'geojson');\n  const geojsonLayer = nextState.layers[index];\n  const id = geojsonLayer.id;\n  // change dataId\n  const nextState1 = reducer(\n    nextState,\n    VisStateActions.layerConfigChange(geojsonLayer, {\n      dataId: 'geojson2'\n    })\n  );\n\n  const neextGeojsonLayer = nextState1.layers.find(l => l.id === id);\n\n  t.equal(neextGeojsonLayer.config.dataId, 'geojson2', 'should update layer dataId');\n  t.equal(neextGeojsonLayer.dataToFeature.length, 3, 'should calculate dataToFeature');\n  t.equal(nextState1.layerData[index].data.length, 3, 'should calculate layerData');\n\n  t.end();\n});\n\ntest('visStateReducer -> layerDataIdChangeUpdater -> validation', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor).visState;\n  const pointLayer = initialState.layers[0];\n  const textField = pointLayer.config.textLabel[0].field;\n\n  const newDataInfo = {\n    id: 'new-data-csv',\n    label: 'new Data'\n  };\n  const fieldIdx = testFields.findIndex(f => f.name === textField.name);\n\n  // remove 1 field from sample data\n  const fields = testFields.filter(f => f.name !== textField.name);\n  const rows = testAllData.map(row => [\n    ...row.slice(0, fieldIdx),\n    ...row.slice(fieldIdx + 1, row.length)\n  ]);\n  // add another dataset\n  const nextState = applyActions(reducer, initialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [[{info: newDataInfo, data: {fields, rows}}]]\n    }\n  ]);\n\n  const nextState1 = reducer(\n    nextState,\n    VisStateActions.layerConfigChange(pointLayer, {\n      dataId: 'new-data-csv'\n    })\n  );\n\n  const updatedLayer = nextState1.layers[0];\n  t.equal(updatedLayer.config.dataId, 'new-data-csv', 'should update point layer dataId');\n  t.equal(\n    updatedLayer.config.colorField.name,\n    'gps_data.types',\n    'should update point layer colorField'\n  );\n\n  t.deepEqual(\n    updatedLayer.config.columns,\n    {\n      altitude: {value: null, fieldIdx: -1, optional: true},\n      lat: {value: 'gps_data.lat', fieldIdx: 1},\n      lng: {value: 'gps_data.lng', fieldIdx: 2},\n      neighbors: {value: null, fieldIdx: -1, optional: true},\n      geojson: {value: null, fieldIdx: -1},\n      geoarrow: {value: null, fieldIdx: -1}\n    },\n    'should update point layer column'\n  );\n\n  t.equal(\n    updatedLayer.config.textLabel[0].field,\n    undefined,\n    'should assign fields not exists to undefined'\n  );\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_VIS_CONFIG_CHANGE -> opacity', t => {\n  const initialState = StateWFiles.visState;\n  const layer = initialState.layers[0];\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerVisConfigChange(layer, {opacity: 0.3})\n  );\n\n  t.equal(nextState.layers[0].config.visConfig.opacity, 0.3, 'should update layer opacity');\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_TEXT_LABEL_CHANGE', t => {\n  const initialState = StateWFiles.visState;\n  // point layer\n  const layer = initialState.layers[0];\n\n  t.deepEqual(layer.config.textLabel, [DEFAULT_TEXT_LABEL], 'should set initial textLabel');\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerTextLabelChange(layer, 0, 'random', 1)\n  );\n\n  t.equal(\n    nextState.layers[0].config.textLabel,\n    layer.config.textLabel,\n    'should not update textLabel if prop is not in textLabel'\n  );\n\n  const nextState2 = reducer(\n    nextState,\n    VisStateActions.layerTextLabelChange(nextState.layers[0], 0, 'anchor', 'start')\n  );\n\n  t.deepEqual(\n    nextState2.layers[0].config.textLabel[0],\n    {...DEFAULT_TEXT_LABEL, anchor: 'start'},\n    'should start text label prop'\n  );\n\n  const valueAccessor = () => 1;\n  const expectedField = {\n    name: 'taro',\n    valueAccessor\n  };\n\n  // set text label field\n  const nextState3 = reducer(\n    nextState2,\n    VisStateActions.layerTextLabelChange(nextState2.layers[0], 0, 'field', expectedField)\n  );\n\n  const expectedTextLabel1 = {\n    ...DEFAULT_TEXT_LABEL,\n    anchor: 'start',\n    field: expectedField\n  };\n\n  t.deepEqual(\n    nextState3.layers[0].config.textLabel[0],\n    expectedTextLabel1,\n    'should set text field'\n  );\n\n  // add empty field\n  const nextState4 = reducer(\n    nextState3,\n    VisStateActions.layerTextLabelChange(nextState3.layers[0], 1)\n  );\n\n  t.deepEqual(\n    nextState4.layers[0].config.textLabel,\n    [expectedTextLabel1, DEFAULT_TEXT_LABEL],\n    'should add text label'\n  );\n\n  // add or remove labels\n  const nextState5 = reducer(\n    nextState4,\n    VisStateActions.layerTextLabelChange(nextState4.layers[0], 'all', 'fields', [\n      {name: 'blue', valueAccessor},\n      {name: 'taro', valueAccessor}\n    ])\n  );\n\n  const expected5 = [\n    expectedTextLabel1,\n    {...DEFAULT_TEXT_LABEL, field: {name: 'blue', valueAccessor}}\n  ];\n  t.deepEqual(nextState5.layers[0].config.textLabel, expected5, 'should add text label taro');\n\n  // // add 1 more label\n  const nextState6 = reducer(\n    nextState5,\n    VisStateActions.layerTextLabelChange(nextState5.layers[0], 2, 'field', {\n      name: 'cat',\n      valueAccessor\n    })\n  );\n  const expected6 = [\n    expectedTextLabel1,\n    {...DEFAULT_TEXT_LABEL, field: {name: 'blue', valueAccessor}},\n    {...DEFAULT_TEXT_LABEL, field: {name: 'cat', valueAccessor}}\n  ];\n  t.deepEqual(nextState6.layers[0].config.textLabel, expected6, 'should add text label cat');\n\n  // remove label\n  const nextState7 = reducer(\n    nextState6,\n    VisStateActions.layerTextLabelChange(nextState6.layers[0], 2, 'field', null)\n  );\n  const expected7 = [\n    expectedTextLabel1,\n    {...DEFAULT_TEXT_LABEL, field: {name: 'blue', valueAccessor}}\n  ];\n  t.deepEqual(nextState7.layers[0].config.textLabel, expected7, 'should remove text label cat');\n\n  // remove label with all\n  const nextState8 = reducer(\n    nextState7,\n    VisStateActions.layerTextLabelChange(nextState7.layers[0], 'all', 'fields', [\n      {name: 'blue', valueAccessor}\n    ])\n  );\n  const expected8 = [{...DEFAULT_TEXT_LABEL, field: {name: 'blue', valueAccessor}}];\n  t.deepEqual(nextState8.layers[0].config.textLabel, expected8, 'should remove text label blue');\n\n  t.end();\n});\n\ntest('#visStateReducer -> REORDER_LAYER', t => {\n  const newReducer = reducer(\n    {layers: [], layerOrder: [0, 1, 2]},\n    VisStateActions.reorderLayer([0, 2, 1])\n  );\n\n  t.deepEqual(newReducer, {layers: [], layerOrder: [0, 2, 1]}, 'should re order layers');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_LAYER_BLENDING', t => {\n  const newReducer = reducer(\n    {layerBlending: 'none'},\n    VisStateActions.updateLayerBlending('additive')\n  );\n\n  t.deepEqual(newReducer, {layerBlending: 'additive'}, 'should update layerBlending');\n\n  t.end();\n});\n\ntest('#visStateReducer -> REMOVE_FILTER', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  // filter[0]: 'time' testCsvData\n  // filter[1]: 'RATE' testGeoJsonData\n\n  const dataset0 = initialState.datasets[testCsvDataId];\n  const dataset1 = initialState.datasets[testGeoJsonDataId];\n\n  const expectedData0 = {\n    ...dataset0,\n    gpuFilter: {\n      filterRange: [\n        [1474606800000 - 1474588800000, 1474617600000 - 1474588800000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {name: 'time', domain0: 1474588800000},\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [\n          {\n            index: 0\n          }\n        ],\n        result: [0, 0, 0, 0]\n      }\n    },\n    filterRecord: {\n      dynamicDomain: [],\n      fixedDomain: [initialState.filters[0]],\n      cpu: [],\n      gpu: [initialState.filters[0]]\n    }\n  };\n\n  const expectedData1 = {\n    ...dataset1,\n    filteredIndex: dataset1.allIndexes,\n    filteredIndexForDomain: dataset1.allIndexes,\n    changedFilters: {\n      dynamicDomain: {'RATE-1': 'deleted'},\n      fixedDomain: null,\n      cpu: {'RATE-1': 'deleted'},\n      gpu: null\n    },\n    filterRecord: {\n      dynamicDomain: [],\n      fixedDomain: [],\n      cpu: [],\n      gpu: []\n    }\n  };\n\n  const expectedFilters = [mergedTimeFilter];\n\n  // remove smoothie filter - gpu: true, fixedDomain: false\n  const newReducer = reducer(initialState, VisStateActions.removeFilter(1));\n\n  const expectedLayerData1 = {data: dataset1.dataContainer.mapIndex(d => d)};\n  const expectedState = {\n    ...initialState,\n    filters: expectedFilters,\n    datasets: {\n      [testCsvDataId]: expectedData0,\n      [testGeoJsonDataId]: expectedData1\n    },\n    layerData: [initialState.layerData[0], expectedLayerData1]\n  };\n\n  cmpObjectKeys(t, expectedState, newReducer, 'After removing filter, visState');\n\n  Object.keys(newReducer).forEach(key => {\n    switch (key) {\n      case 'datasets':\n        cmpDatasets(t, expectedState.datasets, newReducer.datasets);\n        break;\n      case 'filters':\n        cmpFilters(t, expectedState.filters, newReducer.filters);\n        break;\n      case 'layers':\n        cmpLayers(t, expectedState.layers, newReducer.layers);\n        break;\n      case 'layerData':\n        // only compare length\n\n        t.equal(\n          expectedState.layerData.length,\n          newReducer.layerData.length,\n          'should have same number of layerData'\n        );\n        newReducer.layerData.forEach((ld, i) => {\n          t.equal(\n            expectedState.layerData[i].data.length,\n            newReducer.layerData[i].data.length,\n            'layerData.data should have same length'\n          );\n        });\n        break;\n      default:\n        t.deepEqual(\n          newReducer[key],\n          expectedState[key],\n          `visState.${key} should be correct after removing filter`\n        );\n        break;\n    }\n  });\n\n  t.end();\n});\n\ntest('#visStateReducer -> REMOVE_LAYER', t => {\n  const layer1 = new PointLayer({id: 'a'});\n  const layer2 = new PointLayer({id: 'b'});\n  const oldState = {\n    layers: [layer1, layer2],\n    layerData: [{data: 1}, {data: 2}],\n    layerOrder: ['b', 'a'],\n    hoverInfo: {\n      layer: {props: {id: 'b'}},\n      picked: true\n    },\n    clicked: {\n      layer: {props: {id: 'a'}},\n      picked: true\n    },\n    splitMaps: [],\n    animationConfig: DEFAULT_ANIMATION_CONFIG\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.removeLayer('b'));\n\n  t.deepEqual(\n    newReducer,\n    {\n      layers: [layer1],\n      layerData: [{data: 1}],\n      layerOrder: ['a'],\n      hoverInfo: undefined,\n      clicked: {\n        layer: {props: {id: 'a'}},\n        picked: true\n      },\n      splitMaps: [],\n      animationConfig: DEFAULT_ANIMATION_CONFIG\n    },\n    'should remove layer and layerData'\n  );\n\n  // test remove\n  t.end();\n});\n\ntest('#visStateReducer -> DUPLICATE_LAYER', t => {\n  const oldState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  // layers: ['point-0', 'geojson-1', 'hexagon-2'],\n  const layerToCopy = serializeLayer(oldState.layers[0], oldState.schema);\n  t.equal(oldState.layers.length, 3, 'should have 3 layers to begin');\n  t.deepEqual(\n    oldState.layerOrder,\n    [oldState.layers[2].id, oldState.layers[0].id, oldState.layers[1].id],\n    'should have 3 layers to begin'\n  );\n\n  const nextState = reducer(oldState, VisStateActions.duplicateLayer(0));\n  t.equal(nextState.layers.length, 4, 'should add 1 layer');\n  const layerCopied = serializeLayer(nextState.layers[3], nextState.schema);\n\n  const expectedLayer = {\n    ...layerToCopy,\n    id: layerCopied.id,\n    config: {\n      ...layerToCopy.config,\n      label: 'Copy of gps_data'\n    }\n  };\n\n  t.deepEqual(layerCopied, expectedLayer, 'should copy layer config correctly');\n  t.deepEqual(\n    nextState.layerData[3].data,\n    nextState.layerData[0].data,\n    'should copy layer data correctly'\n  );\n  t.deepEqual(\n    nextState.layerOrder,\n    [\n      nextState.layers[2].id,\n      nextState.layers[3].id,\n      nextState.layers[0].id,\n      nextState.layers[1].id\n    ],\n    'should insert copied layer in front of older layer'\n  );\n\n  // copy again\n  const nextState1 = reducer(nextState, VisStateActions.duplicateLayer(0));\n  t.equal(nextState1.layers.length, 5, 'should add 1 layer');\n  const layerCopied1 = serializeLayer(nextState1.layers[4], nextState1.schema);\n\n  const expectedLayer1 = {\n    ...layerToCopy,\n    id: layerCopied1.id,\n    config: {\n      ...layerToCopy.config,\n      label: 'Copy of gps_data 1'\n    }\n  };\n  t.deepEqual(layerCopied1, expectedLayer1, 'should copy layer config correctly');\n  t.deepEqual(\n    nextState1.layerData[4].data,\n    nextState1.layerData[0].data,\n    'should copy layer data correctly'\n  );\n  t.deepEqual(\n    nextState1.layerOrder,\n    [\n      nextState1.layers[2].id,\n      nextState1.layers[3].id,\n      nextState1.layers[4].id,\n      nextState1.layers[0].id,\n      nextState1.layers[1].id\n    ],\n    'should insert copied layer in front of older layer'\n  );\n\n  // copy again\n  const nextState2 = reducer(nextState1, VisStateActions.duplicateLayer(0));\n  t.equal(nextState2.layers.length, 6, 'should add 1 layer');\n  const layerCopied2 = serializeLayer(nextState2.layers[5], nextState2.schema);\n\n  const expectedLayer2 = {\n    ...layerToCopy,\n    id: layerCopied2.id,\n    config: {\n      ...layerToCopy.config,\n      label: 'Copy of gps_data 2'\n    }\n  };\n  t.deepEqual(layerCopied2, expectedLayer2, 'should copy layer config correctly');\n\n  // test remove\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.1 -> No data', t => {\n  const oldState = CloneDeep(InitialState).visState;\n  const nextState1 = reducer(oldState, VisStateActions.updateVisData([{info: null, data: null}]));\n  t.deepEqual(nextState1, oldState, 'should return current state if no data');\n\n  const nextState2 = reducer(\n    oldState,\n    VisStateActions.updateVisData([\n      {\n        data: {\n          fields: null,\n          rows: [1, 2]\n        }\n      }\n    ])\n  );\n  t.deepEqual(nextState2, oldState, 'should return current state if no fields');\n\n  Object.keys(oldState).forEach(prop => {\n    t.deepEqual(nextState2[prop], oldState[prop], `${prop} should be the same`);\n  });\n  t.deepEqual(\n    reducer(\n      oldState,\n      VisStateActions.updateVisData([\n        {\n          data: {\n            fields: [{name: 'a'}],\n            rows: null\n          }\n        }\n      ])\n    ),\n    oldState,\n    'should return current state if no rows'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.2 -> to empty state', t => {\n  const oldState = INITIAL_VIS_STATE;\n\n  const newState = applyActions(reducer, oldState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            data: mockRawData,\n            info: {id: 'smoothie', label: 'exciting dataset'},\n            metadata: {album: 'taro_and_blue'}\n          }\n        ]\n      ]\n    }\n  ]);\n\n  const expectedDatasets = {\n    smoothie: {\n      fields: expectedFields,\n      filteredIndex: mockRawData.rows.map((_, i) => i),\n      filteredIndexForDomain: mockRawData.rows.map((_, i) => i),\n      allIndexes: mockRawData.rows.map((_, i) => i),\n      dataContainer: createDataContainer(mockRawData.rows, {fields: mockRawData.fields}),\n      gpuFilter: {\n        filterRange: [\n          [0, 0],\n          [0, 0],\n          [0, 0],\n          [0, 0]\n        ],\n        filterValueUpdateTriggers: {\n          gpuFilter_0: null,\n          gpuFilter_1: null,\n          gpuFilter_2: null,\n          gpuFilter_3: null\n        },\n        filterValueAccessor: {\n          inputs: ['a', 'b', 'c', 'd', 'e'],\n          result: [0, 0, 0, 0]\n        }\n      },\n      color: 'donnot test me',\n      id: 'smoothie',\n      label: 'exciting dataset',\n      fieldPairs: expectedFieldParis,\n      metadata: {id: 'smoothie', label: 'exciting dataset', album: 'taro_and_blue', format: ''},\n      type: '',\n      supportedFilterTypes: null,\n      disableDataOperation: false\n    }\n  };\n\n  t.deepEqual(Object.keys(newState.datasets), ['smoothie'], 'should save data to smoothie');\n\n  cmpDatasets(t, expectedDatasets, newState.datasets);\n\n  const expectedArcLayer = new ArcLayer({\n    dataId: 'smoothie',\n    label: 'start_point -> end_point arc',\n    isVisible: false,\n    columns: {\n      lat0: {fieldIdx: 0, value: 'start_point_lat'},\n      lng0: {fieldIdx: 1, value: 'start_point_lng'},\n      lat1: {fieldIdx: 2, value: 'end_point_lat'},\n      lng1: {fieldIdx: 3, value: 'end_point_lng'}\n    }\n  });\n\n  const expectedLineLayer = new LineLayer({\n    dataId: 'smoothie',\n    label: 'start_point -> end_point line',\n    isVisible: false,\n    columns: {\n      lat0: {fieldIdx: 0, value: 'start_point_lat'},\n      lng0: {fieldIdx: 1, value: 'start_point_lng'},\n      lat1: {fieldIdx: 2, value: 'end_point_lat'},\n      lng1: {fieldIdx: 3, value: 'end_point_lng'},\n      alt0: {\n        value: null,\n        fieldIdx: -1,\n        optional: true\n      },\n      alt1: {\n        value: null,\n        fieldIdx: -1,\n        optional: true\n      }\n    }\n  });\n\n  const expectedPointLayer1 = new PointLayer({\n    dataId: 'smoothie',\n    label: 'start_point',\n    columns: {\n      lat: {fieldIdx: 0, value: 'start_point_lat'},\n      lng: {fieldIdx: 1, value: 'start_point_lng'},\n      altitude: {fieldIdx: -1, value: null, optional: true}\n    }\n  });\n\n  expectedPointLayer1.meta = {\n    bounds: [35.2, 12.25, 37.75, 12.29]\n  };\n\n  const expectedPointLayer2 = new PointLayer({\n    dataId: 'smoothie',\n    label: 'end_point',\n    columns: {\n      lat: {fieldIdx: 2, value: 'end_point_lat'},\n      lng: {fieldIdx: 3, value: 'end_point_lng'},\n      altitude: {fieldIdx: -1, value: null, optional: true}\n    }\n  });\n\n  expectedPointLayer2.meta = {\n    bounds: [21.3, 33.1, 100.12, 46.21]\n  };\n\n  const expectedLayers = [\n    expectedPointLayer1,\n    expectedPointLayer2,\n    expectedArcLayer,\n    expectedLineLayer\n  ];\n\n  const newStateLayers = CloneDeep(newState.layers);\n\n  cmpLayers(t, expectedLayers, newStateLayers);\n\n  t.equal(newState.layerData.length, expectedLayers.length, 'should calculate layerdata');\n  t.deepEqual(\n    newState.layerOrder,\n    [newStateLayers[0].id, newStateLayers[1].id, newStateLayers[2].id, newStateLayers[3].id],\n    'should calculate layerOrder'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.3 -> merge w/ existing state', t => {\n  const oldState = {\n    ...StateWFiles.visState,\n    layerBlending: 'additive'\n  };\n\n  const expectedDatasets = {\n    ...StateWFiles.visState.datasets,\n    smoothie: {\n      metadata: {\n        id: 'smoothie',\n        label: 'smoothie and milkshake',\n        format: ''\n      },\n      type: '',\n      supportedFilterTypes: null,\n      disableDataOperation: false,\n      fields: expectedFields,\n      dataContainer: createDataContainer(mockRawData.rows, {fields: mockRawData.fields}),\n      color: 'donnot test me',\n      filteredIndex: mockRawData.rows.map((_, i) => i),\n      filteredIndexForDomain: mockRawData.rows.map((_, i) => i),\n      allIndexes: mockRawData.rows.map((_, i) => i),\n      gpuFilter: {\n        filterRange: [\n          [0, 0],\n          [0, 0],\n          [0, 0],\n          [0, 0]\n        ],\n        filterValueUpdateTriggers: {\n          gpuFilter_0: null,\n          gpuFilter_1: null,\n          gpuFilter_2: null,\n          gpuFilter_3: null\n        },\n        filterValueAccessor: {\n          inputs: [{data: mockData.data[0], index: 0}],\n          result: [0, 0, 0, 0]\n        }\n      },\n      id: 'smoothie',\n      label: 'smoothie and milkshake',\n      fieldPairs: expectedFieldParis\n    }\n  };\n\n  const expectedTooltip = {\n    ...oldState.interactionConfig.tooltip.config.fieldsToShow,\n    smoothie: []\n  };\n\n  const newState = applyActions(reducer, oldState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            data: mockRawData,\n            info: {id: 'smoothie', label: 'smoothie and milkshake'}\n          }\n        ]\n      ]\n    }\n  ]);\n\n  Object.keys(expectedDatasets).forEach(key =>\n    cmpDataset(t, expectedDatasets[key], newState.datasets[key])\n  );\n  t.equal(newState.layers.length, 6, 'should find 1 arc 1 line and 2 point layers');\n  t.deepEqual(\n    newState.layerOrder,\n    [\n      newState.layers[2].id,\n      newState.layers[3].id,\n      newState.layers[4].id,\n      newState.layers[5].id,\n      newState.layers[1].id,\n      newState.layers[0].id\n    ],\n    'should add new layer index to layer order, put them on top'\n  );\n  t.equal(newState.layers[2].config.dataId, 'smoothie', 'should save dataId to layer');\n  t.equal(newState.layers[3].config.dataId, 'smoothie', 'should save dataId to layer');\n  t.equal(newState.layers[4].config.dataId, 'smoothie', 'should save dataId to layer');\n  t.equal(newState.layers[5].config.dataId, 'smoothie', 'should save dataId to layer');\n  t.equal(newState.layerData.length, 6, 'should calculate layerData');\n\n  t.deepEqual(\n    newState.interactionConfig.tooltip.config.fieldsToShow,\n    expectedTooltip,\n    'should set interaction config back to default'\n  );\n  t.equal(newState.layerBlending, 'additive', 'should keep layerBlending');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> geojson data', t => {\n  const initialVisState = CloneDeep(INITIAL_VIS_STATE);\n\n  const {fields, rows} = processGeojson(CloneDeep(geojsonData));\n\n  const payload = [\n    {\n      info: {\n        id: 'milkshake',\n        label: 'king milkshake'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  const [layer1Color, layer1StrokeColor] = getNextColorMakerValue(2);\n\n  // receive data\n  const initialState = applyActions(reducer, initialVisState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [payload]\n    }\n  ]);\n\n  const expectedDatasets = {\n    metadata: {\n      id: 'milkshake',\n      label: 'king milkshake',\n      format: ''\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false,\n    id: 'milkshake',\n    label: 'king milkshake',\n    color: 'donnot test me',\n    dataContainer: createDataContainer(rows, {fields}),\n    filteredIndex: rows.map((_, i) => i),\n    filteredIndexForDomain: rows.map((_, i) => i),\n    allIndexes: rows.map((_, i) => i),\n    fields,\n    fieldPairs: [],\n    gpuFilter: {\n      filterRange: [\n        [0, 0],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: null,\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [{data: mockData.data[0], index: 0}],\n        result: [0, 0, 0, 0]\n      }\n    }\n  };\n\n  const expectedLayer = new GeojsonLayer({\n    label: 'king milkshake',\n    dataId: 'milkshake',\n    columns: {geojson: {fieldIdx: 0, value: '_geojson'}},\n    isVisible: true,\n    color: layer1Color\n  });\n\n  expectedLayer.updateLayerVisConfig({\n    stroked: true,\n    filled: true,\n    strokeColor: layer1StrokeColor\n  });\n  expectedLayer.dataToFeature = expectedDataToFeature;\n  expectedLayer.meta = updatedGeoJsonLayer.meta;\n\n  const expectedLayerData = {\n    data: geojsonData.features.map((f, i) => ({\n      ...f,\n      properties: {...f.properties, TRIPS: f.properties.TRIPS || null, index: i}\n    }))\n  };\n\n  t.deepEqual(Object.keys(initialState.datasets), ['milkshake'], 'should save geojson to datasets');\n  cmpDataset(\n    t,\n    expectedDatasets,\n    initialState.datasets.milkshake,\n    'should save correct geojson to datasets'\n  );\n\n  t.equal(initialState.layers.length, 1, 'should find 1 geojson layer');\n  cmpLayers(t, expectedLayer, initialState.layers[0], 'should save dataFeature to geojson layer');\n\n  t.deepEqual(\n    initialState.layerData[0].data,\n    expectedLayerData.data,\n    'should save geojson to layer data'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> with config', t => {\n  const initialVisState = CloneDeep(INITIAL_VIS_STATE);\n\n  const {fields, rows} = processGeojson(CloneDeep(geojsonData));\n\n  const payload = [\n    {\n      info: {\n        id: 'milkshake',\n        label: 'king milkshake'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  // receive data\n  const initialState = applyActions(reducer, initialVisState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [payload]\n    }\n  ]);\n\n  t.equal(initialState.layers.length, 1, 'should create 1 layer');\n\n  // add data and config again\n\n  // data\n  const datasets = [\n    {\n      info: {\n        id: 'milkshake2',\n        label: 'king milkshake'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  const config = {\n    visState: {\n      layers: [\n        {\n          id: 'test_layer_2',\n          type: 'geojson',\n          config: {\n            dataId: 'milkshake2',\n            columns: {\n              geojson: '_geojson'\n            }\n          }\n        }\n      ]\n    }\n  };\n\n  const testState = applyActions(reducer, initialState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [datasets, {}, config]\n    }\n  ]);\n\n  t.deepEqual(\n    Object.keys(testState.datasets),\n    ['milkshake2'],\n    'should reset state, and load dataset'\n  );\n  t.equal(testState.layers.length, 1, 'should create 1 layer');\n  t.equal(testState.layers[0].id, 'test_layer_2', 'should merge 1 layer from config');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA -> mergeFilters', t => {\n  const oldState = CloneDeep(INITIAL_VIS_STATE);\n  oldState.filterToBeMerged = [\n    {\n      dataId: 'smoothie',\n      id: '38chejr',\n      view: FILTER_VIEW_TYPES.enlarged,\n      name: mockFilter.name,\n      type: mockFilter.type,\n      value: mockFilter.value\n    },\n    {\n      dataId: 'nothing_here',\n      id: 'vuey55d',\n      view: FILTER_VIEW_TYPES.enlarged,\n      name: 'test_test',\n      type: 'select',\n      value: true\n    }\n  ];\n\n  const expectedFilterProps = {\n    domain: [12.249990000000002, 12.290000000000001],\n    step: 0.00001,\n    fieldType: 'real',\n    view: FILTER_VIEW_TYPES.side,\n    type: mockFilter.type,\n    gpu: true,\n    typeOptions: ['range'],\n    value: [12.249990000000002, 12.290000000000001]\n  };\n\n  const expectedFilter = {\n    ...expectedFilterProps,\n    animationWindow: 'free',\n    dataId: ['smoothie'],\n    fieldIdx: [0],\n    id: '38chejr',\n    fixedDomain: false,\n    view: FILTER_VIEW_TYPES.enlarged,\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    gpu: true,\n    gpuChannel: [0],\n    name: [mockFilter.name],\n    speed: 1,\n    isAnimating: false,\n    typeOptions: ['range'],\n    value: mockFilter.value\n  };\n\n  const newState = applyActions(reducer, oldState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            data: mockRawData,\n            info: {id: 'smoothie', label: 'smoothie and milkshake'}\n          }\n        ]\n      ]\n    }\n  ]);\n\n  const dc = createDataContainer(mockRawData.rows, {fields: mockRawData.fields});\n  const allIndexes = dc.getPlainIndex();\n\n  const expectedDatasets = {\n    smoothie: {\n      metadata: {\n        id: 'smoothie',\n        label: 'smoothie and milkshake',\n        format: ''\n      },\n      type: '',\n      supportedFilterTypes: null,\n      disableDataOperation: false,\n      fields: expectedFields.map(f =>\n        f.name === mockFilter.name\n          ? {\n              ...f,\n              filterProps: expectedFilterProps\n            }\n          : f\n      ),\n      // gpu filter in place, filteredIndex should not be updated\n      filteredIndex: allIndexes,\n      filteredIndexForDomain: [0],\n      filterRecord: {\n        dynamicDomain: [newState.filters.find(f => f.id === '38chejr')],\n        fixedDomain: [],\n        cpu: [],\n        gpu: [newState.filters.find(f => f.id === '38chejr')]\n      },\n      gpuFilter: {\n        filterRange: [\n          [\n            mockFilter.value[0] - expectedFilter.domain[0],\n            mockFilter.value[1] - expectedFilter.domain[0]\n          ],\n          [0, 0],\n          [0, 0],\n          [0, 0]\n        ],\n        filterValueUpdateTriggers: {\n          gpuFilter_0: {\n            name: mockFilter.name,\n            domain0: 12.249990000000002\n          },\n          gpuFilter_1: null,\n          gpuFilter_2: null,\n          gpuFilter_3: null\n        },\n        filterValueAccessor: {\n          inputs: [{index: 0}],\n          result: [12.25 - expectedFilter.domain[0], 0, 0, 0]\n        }\n      },\n      allIndexes,\n      dataContainer: dc,\n      color: 'donot test me',\n      id: 'smoothie',\n      label: 'smoothie and milkshake',\n      fieldPairs: expectedFieldParis,\n      changedFilters: {\n        dynamicDomain: {'38chejr': 'added'},\n        fixedDomain: null,\n        cpu: null,\n        gpu: {'38chejr': 'added'}\n      }\n    }\n  };\n\n  const expectedState = {\n    filterToBeMerged: [oldState.filterToBeMerged[1]],\n    filters: [expectedFilter],\n    datasets: expectedDatasets\n  };\n\n  cmpDatasets(t, expectedState.datasets, newState.datasets);\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_VIS_DATA.SPLIT_MAPS', t => {\n  const layer0 = new PointLayer({\n    dataId: 'snowflake',\n    id: 'a',\n    isVisible: true\n  });\n\n  const layer1 = new PointLayer({\n    dataId: 'milkshake',\n    id: 'b',\n    isVisible: true\n  });\n\n  const layer2 = new PointLayer({\n    dataId: 'milkshake',\n    id: 'c',\n    isVisible: false\n  });\n\n  const layer3 = new PointLayer({\n    dataId: 'milkshake',\n    id: 'd',\n    isVisible: false\n  });\n\n  const layers = [layer0, layer1, layer2, layer3];\n\n  const oldState = {\n    ...INITIAL_VIS_STATE,\n    layers: [layer0, layer1, layer2, layer3],\n    splitMaps: [\n      {\n        layers: {\n          a: true,\n          b: false\n        }\n      },\n      {\n        layers: {\n          a: false,\n          b: true\n        }\n      }\n    ],\n    interactionConfig: {\n      tooltip: {\n        config: {\n          fieldsToShow: {\n            milkshake: []\n          }\n        }\n      }\n    },\n    layerData: [],\n    layerOrder: [layers[2].id, layers[1].id, layers[0].id, layers[3].id]\n  };\n\n  const newState = applyActions(reducer, oldState, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [\n        [\n          {\n            data: mockRawData,\n            info: {id: 'smoothie', label: 'smoothie and milkshake'}\n          }\n        ]\n      ]\n    }\n  ]);\n\n  // first visible layer should be point\n  const id1 = newState.layers[4].id;\n  // 2nd visible layer\n  const id2 = newState.layers[5].id;\n  const expectedSplitMaps = [\n    {\n      layers: {\n        a: true,\n        b: false,\n        [id1]: true,\n        [id2]: true\n      }\n    },\n    {\n      layers: {\n        a: false,\n        b: true,\n        [id1]: true,\n        [id2]: true\n      }\n    }\n  ];\n\n  t.equal(newState.layers.length, 8, 'should create 1 arc 1 line and 2 point layers');\n  t.deepEqual(\n    newState.layerOrder,\n    [\n      newState.layers[4].id,\n      newState.layers[5].id,\n      newState.layers[6].id,\n      newState.layers[7].id,\n      newState.layers[2].id,\n      newState.layers[1].id,\n      newState.layers[0].id,\n      newState.layers[3].id\n    ],\n    'should move new layers to front'\n  );\n  t.deepEqual(newState.splitMaps, expectedSplitMaps, 'should add new layers to split maps');\n\n  t.end();\n});\n\ntest('#visStateReducer -> setFilter.dynamicDomain & cpu', t => {\n  // get test data\n  const {fields, rows} = processCsvData(testData);\n  const payload = [\n    {\n      info: {\n        id: 'smoothie',\n        label: 'queen smoothie'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  // receive data\n  const initialState = applyActions(reducer, INITIAL_VIS_STATE, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [payload]\n    }\n  ]);\n\n  const expectedLayer1 = new PointLayer({\n    isVisible: true,\n    dataId: 'smoothie',\n    columnMode: 'points',\n    label: 'gps_data',\n    columns: {\n      lat: {value: 'gps_data.lat', fieldIdx: 1},\n      lng: {value: 'gps_data.lng', fieldIdx: 2},\n      altitude: {value: null, fieldIdx: -1, optional: true},\n      neighbors: {value: null, fieldIdx: -1, optional: true}\n    }\n  });\n\n  expectedLayer1.meta = {\n    bounds: [31.2148748, 29.9870074, 31.2590542, 30.0614122]\n  };\n\n  const expectedLayers = [expectedLayer1];\n  // test default layer\n  t.equal(initialState.layers.length, 1, 'should find one layer');\n\n  cmpLayers(t, expectedLayers, initialState.layers);\n\n  // add filter\n  const stateWithFilter = reducer(initialState, VisStateActions.addFilter('smoothie'));\n\n  const expectedFilter = {\n    dataId: ['smoothie'],\n    id: 'donnot test me yet',\n    enabled: true,\n    name: [],\n    type: null,\n    fixedDomain: false,\n    domain: null,\n    value: null,\n    view: FILTER_VIEW_TYPES.side,\n    isAnimating: false,\n    animationWindow: 'free',\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    speed: 1,\n    gpu: false,\n    fieldIdx: []\n  };\n\n  cmpFilters(t, expectedFilter, stateWithFilter.filters[0]);\n  const filterId = stateWithFilter.filters[0].id;\n\n  // set filter 'name'\n  const stateWithFilterName = reducer(\n    stateWithFilter,\n    VisStateActions.setFilter(0, 'name', 'date')\n  );\n\n  const expectedFilterWName = {\n    dataId: ['smoothie'],\n    id: filterId,\n    enabled: true,\n    name: ['date'],\n    type: 'multiSelect',\n    fieldIdx: [10],\n    fixedDomain: false,\n    domain: ['2016-09-23', '2016-09-24', '2016-10-10'],\n    value: [],\n    view: FILTER_VIEW_TYPES.side,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldType: 'date',\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    speed: 1,\n    gpu: false\n  };\n\n  // test filter\n  cmpFilters(t, expectedFilterWName, stateWithFilterName.filters[0]);\n\n  const updatedField = {\n    ...initialState.datasets.smoothie.fields[10],\n    filterProps: {\n      type: 'multiSelect',\n      value: [],\n      fieldType: 'date',\n      domain: ['2016-09-23', '2016-09-24', '2016-10-10'],\n      gpu: false,\n      view: FILTER_VIEW_TYPES.side\n    }\n  };\n\n  const fieldsEx = [...initialState.datasets.smoothie.fields.slice(0, 10), updatedField];\n  const {dataContainer} = initialState.datasets.smoothie;\n\n  // test dataset\n  const expectedDataset = {\n    metadata: {\n      id: 'smoothie',\n      label: 'queen smoothie',\n      format: ''\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false,\n    id: 'smoothie',\n    label: 'queen smoothie',\n    color: 'donnot test me',\n    dataContainer,\n    fields: fieldsEx,\n    filteredIndex: dataContainer.getPlainIndex(),\n    filteredIndexForDomain: dataContainer.getPlainIndex(),\n    allIndexes: dataContainer.getPlainIndex(),\n    filterRecord: {\n      dynamicDomain: [],\n      fixedDomain: [],\n      cpu: [],\n      gpu: []\n    },\n    gpuFilter: {\n      filterRange: [\n        [0, 0],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: null,\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [{index: 0}],\n        result: [0, 0, 0, 0]\n      }\n    },\n    fieldPairs: testCsvFieldPairs,\n    changedFilters: {dynamicDomain: null, fixedDomain: null, cpu: null, gpu: null}\n  };\n\n  cmpDataset(t, expectedDataset, stateWithFilterName.datasets.smoothie);\n\n  // set filter value\n  const stateWithFilterValue = reducer(\n    stateWithFilterName,\n    VisStateActions.setFilter(0, 'value', ['2016-10-10'])\n  );\n\n  const expectedFilterWValue = {\n    ...expectedFilterWName,\n    value: ['2016-10-10']\n  };\n\n  // test filter\n  cmpFilters(t, expectedFilterWValue, stateWithFilterValue.filters[0]);\n\n  const updatedFilterWValue = stateWithFilterValue.filters[0];\n  const expectedFilteredDataset = {\n    ...expectedDataset,\n    filterRecord: {\n      dynamicDomain: [updatedFilterWValue],\n      fixedDomain: [],\n      cpu: [updatedFilterWValue],\n      gpu: []\n    },\n    dataContainer,\n    filteredIndex: [17, 18, 19, 20, 21, 22],\n    filteredIndexForDomain: [17, 18, 19, 20, 21, 22],\n    changedFilters: {\n      dynamicDomain: {[updatedFilterWValue.id]: 'added'},\n      fixedDomain: null,\n      cpu: {[updatedFilterWValue.id]: 'added'},\n      gpu: null\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false\n  };\n\n  cmpDataset(t, expectedFilteredDataset, stateWithFilterValue.datasets.smoothie);\n\n  const expectedLayerData1 = {\n    data: [\n      {index: 17, position: [31.2165983, 30.0538936, 0]},\n      {index: 18, position: [31.2148748, 30.060911, 0]},\n      {index: 19, position: [31.2212278, 30.060334, 0]},\n      {index: 20, position: [31.2288985, 30.0554663, 0]},\n      {index: 21, position: [31.2187021, 30.0614122, 0]},\n      {index: 22, position: [31.2191059, 30.0612697, 0]}\n    ],\n    getPosition: () => {},\n    getColor: () => {},\n    getRadius: () => {}\n  };\n\n  t.deepEqual(\n    stateWithFilterValue.layerData[0].data,\n    expectedLayerData1.data,\n    'should format layer data correctly'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> RENAME_DATASET', t => {\n  const initialState = StateWTripGeojson.visState;\n\n  t.equal(\n    initialState.datasets[tripDataInfo.id].label,\n    tripDataInfo.label,\n    'Initial label as expected'\n  );\n\n  const newLabel = 'New label!!!11';\n  const updated = reducer(initialState, VisStateActions.renameDataset(tripDataInfo.id, newLabel));\n\n  assertDatasetIsTable(t, updated.datasets[tripDataInfo.id]);\n  t.equal(updated.datasets[tripDataInfo.id].label, newLabel, 'Updated label as expected');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_COLOR_TABLE', t => {\n  const initialState = StateWTripGeojson.visState;\n\n  t.deepEqual(\n    initialState.datasets[tripDataInfo.id].color,\n    [192, 108, 132],\n    'Initial color as expected'\n  );\n\n  const newColor = [255, 255, 255];\n  const updated = reducer(\n    initialState,\n    VisStateActions.updateTableColor(tripDataInfo.id, newColor)\n  );\n\n  assertDatasetIsTable(t, updated.datasets[tripDataInfo.id]);\n  t.equal(updated.datasets[tripDataInfo.id].color, newColor, 'Updated color as expected');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_TABLE_PROPS', t => {\n  const initialState = StateWTripGeojson.visState;\n\n  // update label\n  t.equal(\n    initialState.datasets[tripDataInfo.id].label,\n    tripDataInfo.label,\n    'Initial label as expected'\n  );\n\n  const newLabel = 'New label!!!11';\n  let updated = reducer(\n    initialState,\n    VisStateActions.updateDatasetProps(tripDataInfo.id, {label: newLabel})\n  );\n\n  assertDatasetIsTable(t, updated.datasets[tripDataInfo.id]);\n  t.equal(updated.datasets[tripDataInfo.id].label, newLabel, 'Updated label as expected');\n\n  // update color\n  t.deepEqual(\n    updated.datasets[tripDataInfo.id].color,\n    [192, 108, 132],\n    'Initial color as expected'\n  );\n\n  const newColor = [255, 255, 255];\n  updated = reducer(\n    updated,\n    VisStateActions.updateDatasetProps(tripDataInfo.id, {color: newColor})\n  );\n  assertDatasetIsTable(t, updated.datasets[tripDataInfo.id]);\n  t.equal(updated.datasets[tripDataInfo.id].label, newLabel, 'Updated color as expected');\n\n  // update meta\n  updated = reducer(\n    updated,\n    VisStateActions.updateDatasetProps(tripDataInfo.id, {metadata: {test: true}})\n  );\n  assertDatasetIsTable(t, updated.datasets[tripDataInfo.id]);\n  t.deepEqual(\n    updated.datasets[tripDataInfo.id].metadata,\n    {\n      ...updated.datasets[tripDataInfo.id].metadata,\n      test: true\n    },\n    'Updated color as expected'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER.name', t => {\n  const oldState = CloneDeep(StateWFilters.visState);\n  const oldFilter0 = oldState.filters[0];\n  // change filter name from RATE to ZIP_CODE\n  const updated = reducer(oldState, VisStateActions.setFilter(1, 'name', 'ZIP_CODE', 0));\n\n  const expectedFilter0 = oldFilter0;\n  const expectedFilter1 = {\n    dataId: [testGeoJsonDataId],\n    id: 'RATE-1',\n    enabled: true,\n    fixedDomain: false,\n    view: FILTER_VIEW_TYPES.side,\n    isAnimating: false,\n    animationWindow: 'free',\n    speed: 1,\n    name: ['ZIP_CODE'],\n    type: 'range',\n    fieldIdx: [2],\n    domain: [94105, 94111],\n    value: [94105, 94111],\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    fieldType: 'integer',\n    step: 0.01,\n    typeOptions: ['range'],\n    gpu: true,\n    gpuChannel: [0],\n    bins: {\n      [testGeoJsonDataId]: histogramFromDomain(\n        [94105, 94111],\n        geojsonRows.map(d => d[2]),\n        BINS\n      )\n    }\n  };\n\n  cmpFilters(t, [expectedFilter0, expectedFilter1], updated.filters);\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER.dataId', t => {\n  const oldState = CloneDeep(StateWFilters.visState);\n  const oldFilter = {...oldState.filters[1]};\n  let newState = reducer(oldState, VisStateActions.setFilter(1, 'dataId', testCsvDataId));\n\n  let newFilter = newState.filters[1];\n  let expectedFilter = getDefaultFilter({dataId: testCsvDataId, id: oldFilter.id});\n\n  t.deepEqual(newFilter, expectedFilter, 'Should create a new filter using the provided dataId');\n\n  // Using an array of dataId\n  newState = reducer(newState, VisStateActions.setFilter(1, 'dataId', testCsvDataId, 0));\n\n  newFilter = newState.filters[1];\n\n  expectedFilter = getDefaultFilter({dataId: testCsvDataId, id: newFilter.id});\n\n  t.deepEqual(\n    newFilter,\n    expectedFilter,\n    'Should create a new filter using the provided list of dataId'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER synced', t => {\n  const initialState = StateWSyncedTimeFilter.visState;\n  const oldFilter = {...initialState.filters[0]};\n  const filterId = oldFilter.id;\n\n  // test synced filter\n  expectedSyncedTsFilter.id = filterId;\n\n  const expectedFilteredDataset1 = {\n    ...initialState.datasets[testCsvDataSlice1Id],\n    changedFilters: {\n      dynamicDomain: null,\n      fixedDomain: {\n        [filterId]: 'value_changed'\n      },\n      cpu: null,\n      gpu: {\n        [filterId]: 'value_changed'\n      }\n    },\n    filterRecord: {\n      cpu: [],\n      dynamicDomain: [],\n      fixedDomain: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp', 'gps_data.utc_timestamp']\n        }\n      ],\n      gpu: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp', 'gps_data.utc_timestamp']\n        }\n      ]\n    },\n    gpuFilter: {\n      filterRange: [\n        [121000, 1193000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueAccessor: {\n        inputs: [\n          {\n            data: testCsvDataSlice1[1],\n            index: 1\n          }\n        ],\n        result: [1474071056000 - 1474070995000, 0, 0, 0]\n      },\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {\n          name: 'gps_data.utc_timestamp',\n          domain0: 1474070995000\n        },\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      }\n    }\n  };\n\n  const expectedFilteredDataset2 = {\n    ...initialState.datasets[testCsvDataSlice2Id],\n    changedFilters: {\n      dynamicDomain: null,\n      fixedDomain: {\n        [filterId]: 'value_changed'\n      },\n      cpu: null,\n      gpu: {\n        [filterId]: 'value_changed'\n      }\n    },\n    filterRecord: {\n      cpu: [],\n      dynamicDomain: [],\n      fixedDomain: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp', 'gps_data.utc_timestamp']\n        }\n      ],\n      gpu: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp', 'gps_data.utc_timestamp']\n        }\n      ]\n    },\n    gpuFilter: {\n      filterRange: [\n        [121000, 1193000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueAccessor: {\n        inputs: [\n          {\n            data: testCsvDataSlice2[1],\n            index: 1\n          }\n        ],\n        // 1474071056000\n        result: [1474071363000 - 1474070995000, 0, 0, 0]\n      },\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {\n          name: 'gps_data.utc_timestamp',\n          domain0: 1474070995000\n        },\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      }\n    }\n  };\n  cmpFilters(t, expectedSyncedTsFilter, initialState.filters[0]);\n  cmpDataset(t, expectedFilteredDataset1, initialState.datasets[testCsvDataSlice1Id]);\n  cmpDataset(t, expectedFilteredDataset2, initialState.datasets[testCsvDataSlice2Id]);\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER synced -> remove 1', t => {\n  const initialState = StateWSyncedTimeFilter.visState;\n  const oldFilter = {...initialState.filters[0]};\n  const filterId = oldFilter.id;\n  // remove dataset at filter\n  const resultState = reducer(initialState, VisStateActions.setFilter(0, 'dataId', null, 0));\n  const expectedFilter = {\n    ...expectedSyncedTsFilter,\n    id: oldFilter.id,\n    dataId: ['test-csv-data-2'],\n    name: ['gps_data.utc_timestamp'],\n    fieldIdx: [0],\n    // reset domain\n    domain: [1474071301000, 1474072208000],\n    // adjust value\n    value: [1474071301000, 1474072188000],\n    gpuChannel: [0],\n    timeBins: {\n      'test-csv-data-2': expectedSyncedTsFilter.timeBins['test-csv-data-2']\n    }\n  };\n\n  const expectedFilteredDataset1 = {\n    ...initialState.datasets[testCsvDataSlice1Id],\n    changedFilters: {\n      dynamicDomain: null,\n      fixedDomain: {\n        [filterId]: 'deleted'\n      },\n      cpu: null,\n      gpu: {\n        [filterId]: 'deleted'\n      }\n    },\n    filterRecord: {\n      cpu: [],\n      dynamicDomain: [],\n      fixedDomain: [],\n      gpu: []\n    },\n    gpuFilter: {\n      filterRange: [\n        [0, 0],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueAccessor: {\n        inputs: [\n          {\n            data: testCsvDataSlice1[1],\n            index: 1\n          }\n        ],\n        result: [0, 0, 0, 0]\n      },\n      filterValueUpdateTriggers: {\n        gpuFilter_0: null,\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      }\n    }\n  };\n\n  const expectedFilteredDataset2 = {\n    ...initialState.datasets[testCsvDataSlice2Id],\n    changedFilters: {\n      dynamicDomain: null,\n      fixedDomain: {\n        [filterId]: 'dataId_changed'\n      },\n      cpu: null,\n      gpu: {\n        [filterId]: 'dataId_changed'\n      }\n    },\n    filterRecord: {\n      cpu: [],\n      dynamicDomain: [],\n      fixedDomain: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp']\n        }\n      ],\n      gpu: [\n        // filterRecord cmparison util only check the name\n        {\n          name: ['gps_data.utc_timestamp']\n        }\n      ]\n    },\n    gpuFilter: {\n      filterRange: [\n        [0, 887000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueAccessor: {\n        inputs: [\n          {\n            data: testCsvDataSlice2[1],\n            index: 1\n          }\n        ],\n        // 1474071056000\n        result: [1474071363000 - 1474071301000, 0, 0, 0]\n      },\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {\n          name: 'gps_data.utc_timestamp',\n          domain0: 1474071301000\n        },\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      }\n    }\n  };\n\n  cmpFilters(t, expectedFilter, resultState.filters[0]);\n  cmpDataset(t, expectedFilteredDataset1, resultState.datasets[testCsvDataSlice1Id]);\n  cmpDataset(t, expectedFilteredDataset2, resultState.datasets[testCsvDataSlice2Id]);\n\n  t.end();\n});\n\nfunction testSetFilterDynamicDomainGPU(t, setFilter) {\n  const {fields, rows} = processGeojson(CloneDeep(geojsonData));\n  const payload = [\n    {\n      info: {\n        id: 'milkshake',\n        label: 'king milkshake'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  // receive data\n  const initialState = applyActions(reducer, INITIAL_VIS_STATE, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [payload]\n    }\n  ]);\n\n  // add filter\n  const stateWithFilter = reducer(initialState, VisStateActions.addFilter('milkshake'));\n\n  // set filter 'name'\n  const stateWithFilterName = reducer(stateWithFilter, setFilter(0, 'name', 'TRIPS'));\n\n  const expectedFilterWName = {\n    dataId: ['milkshake'],\n    enabled: true,\n    id: stateWithFilter.filters[0].id,\n    name: ['TRIPS'],\n    type: 'range',\n    fieldIdx: [4],\n    domain: [4, 20],\n    step: 0.01,\n    value: [4, 20],\n    view: FILTER_VIEW_TYPES.side,\n    fixedDomain: false,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldType: 'integer',\n    typeOptions: ['range'],\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    speed: 1,\n    gpu: true,\n    gpuChannel: [0],\n    bins: {\n      milkshake: [\n        {count: 1, x0: 4, x1: 4.5},\n        {count: 0, x0: 4.5, x1: 5},\n        {count: 0, x0: 5, x1: 5.5},\n        {count: 0, x0: 5.5, x1: 6},\n        {count: 0, x0: 6, x1: 6.5},\n        {count: 0, x0: 6.5, x1: 7},\n        {count: 0, x0: 7, x1: 7.5},\n        {count: 0, x0: 7.5, x1: 8},\n        {count: 0, x0: 8, x1: 8.5},\n        {count: 0, x0: 8.5, x1: 9},\n        {count: 0, x0: 9, x1: 9.5},\n        {count: 0, x0: 9.5, x1: 10},\n        {count: 0, x0: 10, x1: 10.5},\n        {count: 0, x0: 10.5, x1: 11},\n        {count: 1, x0: 11, x1: 11.5},\n        {count: 0, x0: 11.5, x1: 12},\n        {count: 0, x0: 12, x1: 12.5},\n        {count: 0, x0: 12.5, x1: 13},\n        {count: 0, x0: 13, x1: 13.5},\n        {count: 0, x0: 13.5, x1: 14},\n        {count: 0, x0: 14, x1: 14.5},\n        {count: 0, x0: 14.5, x1: 15},\n        {count: 0, x0: 15, x1: 15.5},\n        {count: 0, x0: 15.5, x1: 16},\n        {count: 0, x0: 16, x1: 16.5},\n        {count: 0, x0: 16.5, x1: 17},\n        {count: 0, x0: 17, x1: 17.5},\n        {count: 0, x0: 17.5, x1: 18},\n        {count: 0, x0: 18, x1: 18.5},\n        {count: 0, x0: 18.5, x1: 19},\n        {count: 0, x0: 19, x1: 19.5},\n        {count: 0, x0: 19.5, x1: 20},\n        {count: 1, x0: 20, x1: 20}\n      ]\n    }\n  };\n  // test filter\n  cmpFilters(t, expectedFilterWName, stateWithFilterName.filters[0]);\n\n  // set filter value\n  const stateWithFilterValue = reducer(stateWithFilterName, setFilter(0, 'value', [8, 20]));\n  const filterId = stateWithFilterName.filters[0].id;\n  const expectedFilterWValue = {\n    ...expectedFilterWName,\n    value: [8, 20]\n  };\n\n  // test filter\n  cmpFilters(t, expectedFilterWValue, stateWithFilterValue.filters[0]);\n\n  const expectedFilteredDataset = {\n    ...initialState.datasets.milkshake,\n\n    // receive Vis Data will add id to fields\n    // filter will add filterProps to fields\n    fields: geojsonFields.map((f, i) => ({\n      ...f,\n      valueAccessor: maybeToDate.bind(\n        null,\n        // is time\n        f.type === ALL_FIELD_TYPES.timestamp,\n        i,\n        f.format,\n        initialState.datasets.milkshake.dataContainer\n      ),\n      ...(f.name === 'TRIPS' ? {filterProps: geoJsonTripFilterProps} : {})\n    })),\n    gpuFilter: {\n      filterRange: [\n        [4, 16],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {name: 'TRIPS', domain0: 4},\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [{index: 0}],\n        result: [7, 0, 0, 0]\n      }\n    },\n    filterRecord: {\n      dynamicDomain: [stateWithFilterValue.filters[0]],\n      fixedDomain: [],\n      cpu: [],\n      gpu: [stateWithFilterValue.filters[0]]\n    },\n    filteredIndex: geojsonData.features.map((_, i) => i),\n    filteredIndexForDomain: [0, 2],\n    allIndexes: geojsonData.features.map((_, i) => i),\n    changedFilters: {\n      dynamicDomain: {[filterId]: 'value_changed'},\n      fixedDomain: null,\n      cpu: null,\n      gpu: {[filterId]: 'value_changed'}\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false\n  };\n\n  const actualTripField = stateWithFilterValue.datasets.milkshake.fields[4];\n  const expectedField = expectedFilteredDataset.fields[4];\n\n  cmpField(t, expectedField, actualTripField, 'trip field should be same');\n  cmpDataset(t, expectedFilteredDataset, stateWithFilterValue.datasets.milkshake);\n}\ntest('#visStateReducer -> setFilter.dynamicDomain & gpu', t => {\n  testSetFilterDynamicDomainGPU(t, VisStateActions.setFilter);\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER_ANIMATION_TIME', t => {\n  testSetFilterDynamicDomainGPU(t, VisStateActions.setFilterAnimationTime);\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER_ANIMATION_WINDOW', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  const nextState = reducer(\n    initialState,\n    VisStateActions.setFilterAnimationWindow({\n      id: initialState.filters[0].id,\n      animationWindow: 'incremental'\n    })\n  );\n\n  t.equal(nextState.filters[0].animationWindow, 'incremental', 'should update ANIMATIONWINDOW');\n\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_FILTER_ANIMATION_SPEED', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n\n  const nextState = reducer(initialState, VisStateActions.updateFilterAnimationSpeed(0, 4));\n\n  t.equal(nextState.filters[0].speed, 4, 'should update filter animation speed');\n\n  t.end();\n});\n\ntest('#visStateReducer -> setFilter.fixedDomain & DynamicDomain & gpu & cpu', async t => {\n  // get test data\n  const {fields, rows} = processCsvData(testData);\n  const payload = [\n    {\n      info: {\n        id: 'smoothie',\n        label: 'queen smoothie'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  const datasetSmoothie = (\n    await createNewDataEntryMock({\n      info: {id: 'smoothie', label: 'queen smoothie'},\n      data: {\n        rows: testAllData,\n        fields: testFields\n      }\n    })\n  ).smoothie;\n\n  // add fixedDomain & gpu filter\n  const stateWidthTsFilter = applyActions(reducer, INITIAL_VIS_STATE, [\n    // receive data\n    {action: VisStateActions.updateVisData, payload: [payload]},\n\n    // add ts filter\n    {action: VisStateActions.addFilter, payload: ['smoothie']},\n\n    // set ts filter name\n    {\n      action: VisStateActions.setFilter,\n      payload: [0, 'name', 'gps_data.utc_timestamp']\n    },\n\n    // set ts filter value\n    {\n      action: VisStateActions.setFilter,\n      payload: [0, 'value', [1474071425000, 1474071740000]]\n    }\n  ]);\n\n  const filterId = stateWidthTsFilter.filters[0].id;\n\n  const expectedFilterTs = {\n    dataId: ['smoothie'],\n    fixedDomain: true,\n    id: filterId,\n    name: ['gps_data.utc_timestamp'],\n    type: 'timeRange',\n    fieldIdx: [0],\n    domain: [1474070995000, 1474072208000],\n    value: [1474071425000, 1474071740000],\n    step: 1000,\n    plotType: {\n      type: 'histogram',\n      interval: '15-second',\n      aggregation: 'sum',\n      defaultTimeFormat: 'L  LTS'\n    },\n    yAxis: null,\n    speed: 1,\n    mappedValue: [\n      1474070995000, 1474071056000, 1474071116000, 1474071178000, 1474071240000, 1474071301000,\n      1474071363000, 1474071425000, 1474071489000, 1474071552000, 1474071567000, 1474071614000,\n      1474071677000, 1474071740000, 1474071802000, 1474071864000, 1474071928000, 1474071989000,\n      1474072051000, 1474072115000, 1474072180000, 1474072203000, 1474072203000, 1474072208000\n    ],\n    view: FILTER_VIEW_TYPES.enlarged,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldType: 'timestamp',\n    gpu: true,\n    gpuChannel: [0],\n    defaultTimeFormat: 'L LTS',\n    timeBins: {\n      smoothie: {\n        '15-second': [\n          {\n            count: 1,\n            x0: 1474070985000,\n            x1: 1474071000000\n          },\n          {\n            count: 1,\n            x0: 1474071045000,\n            x1: 1474071060000\n          },\n          {\n            count: 1,\n            x0: 1474071105000,\n            x1: 1474071120000\n          },\n          {\n            count: 1,\n            x0: 1474071165000,\n            x1: 1474071180000\n          },\n          {\n            count: 1,\n            x0: 1474071240000,\n            x1: 1474071255000\n          },\n          {\n            count: 1,\n            x0: 1474071300000,\n            x1: 1474071315000\n          },\n          {\n            count: 1,\n            x0: 1474071360000,\n            x1: 1474071375000\n          },\n          {\n            count: 1,\n            x0: 1474071420000,\n            x1: 1474071435000\n          },\n          {\n            count: 1,\n            x0: 1474071480000,\n            x1: 1474071495000\n          },\n          {\n            count: 1,\n            x0: 1474071540000,\n            x1: 1474071555000\n          },\n          {\n            count: 1,\n            x0: 1474071555000,\n            x1: 1474071570000\n          },\n          {\n            count: 1,\n            x0: 1474071600000,\n            x1: 1474071615000\n          },\n          {\n            count: 1,\n            x0: 1474071675000,\n            x1: 1474071690000\n          },\n          {\n            count: 1,\n            x0: 1474071735000,\n            x1: 1474071750000\n          },\n          {\n            count: 1,\n            x0: 1474071795000,\n            x1: 1474071810000\n          },\n          {\n            count: 1,\n            x0: 1474071855000,\n            x1: 1474071870000\n          },\n          {\n            count: 1,\n            x0: 1474071915000,\n            x1: 1474071930000\n          },\n          {\n            count: 1,\n            x0: 1474071975000,\n            x1: 1474071990000\n          },\n          {\n            count: 1,\n            x0: 1474072050000,\n            x1: 1474072065000\n          },\n          {\n            count: 1,\n            x0: 1474072110000,\n            x1: 1474072125000\n          },\n          {\n            count: 1,\n            x0: 1474072170000,\n            x1: 1474072185000\n          },\n          {\n            count: 3,\n            x0: 1474072200000,\n            x1: 1474072215000\n          }\n        ]\n      }\n    }\n  };\n\n  // cmpFilters(t, expectedFilterTs, stateWidthTsFilter.filters[0]);\n\n  const expectedDatasetSmoothie = {\n    ...datasetSmoothie,\n    // add filter prop to fields\n    fields: datasetSmoothie.fields.map(f =>\n      f.name === 'gps_data.utc_timestamp'\n        ? {\n            ...f,\n            filterProps: {\n              domain: [1474070995000, 1474072208000],\n              step: 1000,\n              mappedValue: expectedFilterTs.mappedValue,\n              fieldType: 'timestamp',\n              type: 'timeRange',\n              view: FILTER_VIEW_TYPES.enlarged,\n              fixedDomain: true,\n              plotType: {},\n              value: [1474070995000, 1474072208000],\n              gpu: true,\n              defaultTimeFormat: 'L LTS'\n            }\n          }\n        : f\n    ),\n    filterRecord: {\n      dynamicDomain: [],\n      fixedDomain: [stateWidthTsFilter.filters[0]],\n      cpu: [],\n      gpu: [stateWidthTsFilter.filters[0]]\n    },\n    gpuFilter: {\n      filterRange: [\n        [1474071425000 - 1474070995000, 1474071740000 - 1474070995000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {name: 'gps_data.utc_timestamp', domain0: 1474070995000},\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [{index: 1}],\n        result: [61000, 0, 0, 0]\n      }\n    },\n    // copy everything\n    filteredIndex: datasetSmoothie.dataContainer.getPlainIndex(),\n    filteredIndexForDomain: datasetSmoothie.dataContainer.getPlainIndex(),\n    changedFilters: {\n      dynamicDomain: null,\n      fixedDomain: {[filterId]: 'value_changed'},\n      cpu: null,\n      gpu: {[filterId]: 'value_changed'}\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false\n  };\n\n  // check filter by ts\n  cmpDataset(t, expectedDatasetSmoothie, stateWidthTsFilter.datasets.smoothie);\n\n  const stateWidthTsAndNameFilter = applyActions(reducer, stateWidthTsFilter, [\n    // add ordinal filter\n    {action: VisStateActions.addFilter, payload: ['smoothie']},\n\n    // set ordinal filter name\n    {\n      action: VisStateActions.setFilter,\n      payload: [1, 'name', 'date']\n    },\n\n    // set ordinal filter value\n    {\n      action: VisStateActions.setFilter,\n      payload: [1, 'value', ['2016-09-24', '2016-10-10']]\n    }\n  ]);\n\n  const filterId1 = stateWidthTsAndNameFilter.filters[1].id;\n\n  const expectedFilteredDataset = {\n    ...stateWidthTsFilter.datasets.smoothie,\n    fields: stateWidthTsFilter.datasets.smoothie.fields.map(f =>\n      f.name === 'date'\n        ? {\n            ...f,\n            filterProps: {\n              domain: ['2016-09-23', '2016-09-24', '2016-10-10'],\n              fieldType: 'date',\n              type: 'multiSelect',\n              value: [],\n              gpu: false,\n              view: FILTER_VIEW_TYPES.side\n            }\n          }\n        : f\n    ),\n    gpuFilter: {\n      filterRange: [\n        [1474071425000 - 1474070995000, 1474071740000 - 1474070995000],\n        [0, 0],\n        [0, 0],\n        [0, 0]\n      ],\n      filterValueUpdateTriggers: {\n        gpuFilter_0: {name: 'gps_data.utc_timestamp', domain0: 1474070995000},\n        gpuFilter_1: null,\n        gpuFilter_2: null,\n        gpuFilter_3: null\n      },\n      filterValueAccessor: {\n        inputs: [{index: 1}],\n        result: [61000, 0, 0, 0]\n      }\n    },\n    filterRecord: {\n      dynamicDomain: [stateWidthTsAndNameFilter.filters[1]],\n      fixedDomain: [stateWidthTsAndNameFilter.filters[0]],\n      cpu: [stateWidthTsAndNameFilter.filters[1]],\n      gpu: [stateWidthTsAndNameFilter.filters[0]]\n    },\n    filteredIndex: [7, 8, 9, 10, 11, 12, 17, 18, 19, 20, 21, 22],\n    filteredIndexForDomain: [7, 8, 9, 10, 11, 12, 17, 18, 19, 20, 21, 22],\n    changedFilters: {\n      dynamicDomain: {[filterId1]: 'added'},\n      fixedDomain: null,\n      cpu: {[filterId1]: 'added'},\n      gpu: null\n    },\n    type: '',\n    supportedFilterTypes: null,\n    disableDataOperation: false\n  };\n\n  cmpDataset(t, expectedFilteredDataset, stateWidthTsAndNameFilter.datasets.smoothie);\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER_PLOT.yAxis', t => {\n  // get test data\n  const {fields, rows} = processCsvData(testData);\n  const payload = [\n    {\n      info: {\n        id: 'smoothie',\n        label: 'queen smoothie'\n      },\n      data: {fields, rows}\n    }\n  ];\n\n  // receive data\n  const initialState = applyActions(reducer, INITIAL_VIS_STATE, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [payload]\n    }\n  ]);\n\n  // add filter\n  const stateWithFilter = reducer(initialState, VisStateActions.addFilter('smoothie'));\n  const filterId = stateWithFilter.filters[0].id;\n\n  // set filter 'name' to timestamp field\n  const stateWithFilterName = reducer(\n    stateWithFilter,\n    VisStateActions.setFilter(0, 'name', 'gps_data.utc_timestamp')\n  );\n\n  // find id which is an integer field\n  const yAxisField = stateWithFilterName.datasets.smoothie.fields.find(f => f.name === 'uid');\n\n  // set filterPlot yAxis\n  const stateWithFilterPlot = reducer(\n    stateWithFilterName,\n    VisStateActions.setFilterPlot(0, {yAxis: yAxisField})\n  );\n\n  const bins = [\n    {\n      count: 1,\n      x0: 1474070985000,\n      x1: 1474071000000\n    },\n    {\n      count: 1,\n      x0: 1474071045000,\n      x1: 1474071060000\n    },\n    {\n      count: 1,\n      x0: 1474071105000,\n      x1: 1474071120000\n    },\n    {\n      count: 1,\n      x0: 1474071165000,\n      x1: 1474071180000\n    },\n    {\n      count: 1,\n      x0: 1474071240000,\n      x1: 1474071255000\n    },\n    {\n      count: 1,\n      x0: 1474071300000,\n      x1: 1474071315000\n    },\n    {\n      count: 1,\n      x0: 1474071360000,\n      x1: 1474071375000\n    },\n    {\n      count: 1,\n      x0: 1474071420000,\n      x1: 1474071435000\n    },\n    {\n      count: 1,\n      x0: 1474071480000,\n      x1: 1474071495000\n    },\n    {\n      count: 1,\n      x0: 1474071540000,\n      x1: 1474071555000\n    },\n    {\n      count: 1,\n      x0: 1474071555000,\n      x1: 1474071570000\n    },\n    {\n      count: 1,\n      x0: 1474071600000,\n      x1: 1474071615000\n    },\n    {\n      count: 1,\n      x0: 1474071675000,\n      x1: 1474071690000\n    },\n    {\n      count: 1,\n      x0: 1474071735000,\n      x1: 1474071750000\n    },\n    {\n      count: 1,\n      x0: 1474071795000,\n      x1: 1474071810000\n    },\n    {\n      count: 1,\n      x0: 1474071855000,\n      x1: 1474071870000\n    },\n    {\n      count: 1,\n      x0: 1474071915000,\n      x1: 1474071930000\n    },\n    {\n      count: 1,\n      x0: 1474071975000,\n      x1: 1474071990000\n    },\n    {\n      count: 1,\n      x0: 1474072050000,\n      x1: 1474072065000\n    },\n    {\n      count: 1,\n      x0: 1474072110000,\n      x1: 1474072125000\n    },\n    {\n      count: 1,\n      x0: 1474072170000,\n      x1: 1474072185000\n    },\n    {\n      count: 3,\n      x0: 1474072200000,\n      x1: 1474072215000\n    }\n  ];\n\n  const expectedFilterWName = {\n    ...getDefaultFilter({dataId: 'smoothie', id: filterId}),\n    fixedDomain: true,\n    name: ['gps_data.utc_timestamp'],\n    type: 'timeRange',\n    fieldIdx: [0],\n    domain: [1474070995000, 1474072208000],\n    value: [1474070995000, 1474072208000],\n    step: 1000,\n    plotType: {\n      interval: '15-second',\n      defaultTimeFormat: 'L  LTS',\n      type: 'lineChart',\n      aggregation: 'sum'\n    },\n    yAxis: yAxisField,\n    lineChart: {\n      yDomain: [0, 12124],\n      xDomain: [1474070985000, 1474072215000],\n      interval: '15-second',\n      aggregation: 'sum',\n      series: {\n        lines: [\n          [\n            {x: 1474070985000, y: 1, delta: 'last', pct: null},\n            {x: 1474071045000, y: 2, delta: 'last', pct: 1},\n            {x: 1474071105000, y: 3, delta: 'last', pct: 0.5},\n            {x: 1474071165000, y: 4, delta: 'last', pct: 0.3333333333333333},\n            {x: 1474071240000, y: 5, delta: 'last', pct: 0.25},\n            {x: 1474071300000, y: 12124, delta: 'last', pct: 2423.8},\n            {x: 1474071360000, y: 222, delta: 'last', pct: -0.9816892114813592},\n            {x: 1474071420000, y: 345, delta: 'last', pct: 0.5540540540540541},\n            {x: 1474071480000, y: 0, delta: 'last', pct: -1},\n            {x: 1474071540000, y: 0, delta: 'last', pct: null},\n            {x: 1474071555000, y: 0, delta: 'last', pct: null},\n            {x: 1474071600000, y: 0, delta: 'last', pct: null},\n            {x: 1474071675000, y: 0, delta: 'last', pct: null},\n            {x: 1474071735000, y: 0, delta: 'last', pct: null},\n            {x: 1474071795000, y: 0, delta: 'last', pct: null},\n            {x: 1474071855000, y: 1, delta: 'last', pct: null},\n            {x: 1474071915000, y: 0, delta: 'last', pct: -1},\n            {x: 1474071975000, y: 43, delta: 'last', pct: null},\n            {x: 1474072050000, y: 4, delta: 'last', pct: -0.9069767441860465},\n            {x: 1474072110000, y: 5, delta: 'last', pct: 0.25},\n            {x: 1474072170000, y: 0, delta: 'last', pct: -1},\n            {x: 1474072200000, y: 13, delta: 'last', pct: null}\n          ]\n        ],\n        markers: []\n      },\n      yAxis: 'uid',\n      title: 'Total of uid',\n      fieldType: 'integer',\n      allTime: {title: 'All Time Average', value: 580.5454545454545},\n      bins\n    },\n    speed: 1,\n    mappedValue: [\n      1474070995000, 1474071056000, 1474071116000, 1474071178000, 1474071240000, 1474071301000,\n      1474071363000, 1474071425000, 1474071489000, 1474071552000, 1474071567000, 1474071614000,\n      1474071677000, 1474071740000, 1474071802000, 1474071864000, 1474071928000, 1474071989000,\n      1474072051000, 1474072115000, 1474072180000, 1474072203000, 1474072203000, 1474072208000\n    ],\n    view: FILTER_VIEW_TYPES.enlarged,\n    isAnimating: false,\n    animationWindow: 'free',\n    fieldType: 'timestamp',\n    gpu: true,\n    gpuChannel: [0],\n    defaultTimeFormat: 'L LTS',\n    timeBins: {\n      smoothie: {\n        '15-second': bins\n      }\n    }\n  };\n\n  t.deepEqual(\n    stateWithFilterPlot.filters[0].lineChart.bins,\n    stateWithFilterPlot.filters[0].timeBins.smoothie['15-second'],\n    'Timebins and lineChart bins should have the same value'\n  );\n\n  // stateWithFilterPlot.filters[0].lineChart.bins = [];\n  // expectedFilterWName.lineChart.bins = [];\n  // test filter\n  cmpFilters(t, expectedFilterWName, stateWithFilterPlot.filters[0]);\n\n  // set filterPlot yAxis again\n  // const yAxisField2 = stateWithFilterName.datasets.smoothie.fields.find(\n  //   f => f.name === 'gps_data.lat'\n  // );\n  // const stateWithFilterPlot2 = reducer(\n  //   stateWithFilterPlot,\n  //   VisStateActions.setFilterPlot(0, {yAxis: yAxisField2})\n  // );\n  // const expectedFilterWName2 = {\n  //   ...expectedFilterWName,\n  //   yAxis: yAxisField2,\n  //   lineChart: {\n  //     yDomain: [29.9870074, 90.18377960000001],\n  //     xDomain: [1474070985000, 1474072215000],\n  //     interval: '15-second',\n  //     aggregation: 'sum',\n  //     series: {\n  //       lines: [\n  //         [\n  //           {x: 1474070985000, y: 29.9900937, delta: 'last', pct: null},\n  //           {x: 1474071045000, y: 29.9927699, delta: 'last', pct: 0.00008923613333024682},\n  //           {x: 1474071105000, y: 29.9907261, delta: 'last', pct: -0.00006814308937832221},\n  //           {x: 1474071165000, y: 29.9870074, delta: 'last', pct: -0.0001239949972401734},\n  //           {x: 1474071240000, y: 29.9923041, delta: 'last', pct: 0.00017663316413490536},\n  //           {x: 1474071300000, y: 29.9968249, delta: 'last', pct: 0.00015073200061350596},\n  //           {x: 1474071360000, y: 30.0037217, delta: 'last', pct: 0.0002299176670528158},\n  //           {x: 1474071420000, y: 30.0116207, delta: 'last', pct: 0.00026326733993142846},\n  //           {x: 1474071480000, y: 30.0208925, delta: 'last', pct: 0.0003089403299035078},\n  //           {x: 1474071540000, y: 30.0218999, delta: 'last', pct: 0.000033556630603251856},\n  //           {x: 1474071555000, y: 30.0229344, delta: 'last', pct: 0.00003445817897751978},\n  //           {x: 1474071600000, y: 30.0264237, delta: 'last', pct: 0.0001162211512542309},\n  //           {x: 1474071675000, y: 30.0292134, delta: 'last', pct: 0.00009290816741525582},\n  //           {x: 1474071735000, y: 30.034391, delta: 'last', pct: 0.00017241876871805346},\n  //           {x: 1474071795000, y: 30.0352752, delta: 'last', pct: 0.000029439584774718954},\n  //           {x: 1474071855000, y: 30.0395918, delta: 'last', pct: 0.00014371767767252643},\n  //           {x: 1474071915000, y: 30.0497387, delta: 'last', pct: 0.00033778421716099144},\n  //           {x: 1474071975000, y: 30.0538936, delta: 'last', pct: 0.00013826742526714978},\n  //           {x: 1474072050000, y: 30.060911, delta: 'last', pct: 0.0002334938724878657},\n  //           {x: 1474072110000, y: 30.060334, delta: 'last', pct: -0.000019194361741060598},\n  //           {x: 1474072170000, y: 30.0554663, delta: 'last', pct: -0.0001619310018312477},\n  //           {x: 1474072200000, y: 90.18377960000001, delta: 'last', pct: 2.0005782874844305}\n  //         ]\n  //       ],\n  //       markers: []\n  //     },\n  //     yAxis: 'gps_data.lat',\n  //     title: 'Total of gps_data.lat',\n  //     fieldType: 'real',\n  //     allTime: {title: 'All Time Average', value: 32.757264254545454}\n  //   }\n  // };\n  // // gps_data.lat\n  // cmpFilters(t, expectedFilterWName2, stateWithFilterPlot2.filters[0]);\n  //\n  // // set filterPlot type\n  // const stateWithFilterPlotHistogram = reducer(\n  //   stateWithFilterPlot2,\n  //   VisStateActions.setFilterPlot(0, {plotType: {type: 'histogram'}})\n  // );\n  // t.deepEqual(\n  //   stateWithFilterPlotHistogram.filters[0].plotType,\n  //   {interval: '15-second', defaultTimeFormat: 'L  LTS', type: 'histogram', aggregation: 'sum'},\n  //   'should set filter plotType to histogram'\n  // );\n\n  t.end();\n});\n\ntest('#visStateReducer -> TOGGLE_FILTER_ANIMATION', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n\n  const nextState = reducer(initialState, VisStateActions.toggleFilterAnimation(0));\n  t.equal(nextState.filters[0].isAnimating, true, 'should set filter to isAnimating: true');\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_FILTER_VIEW', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n\n  const nextState = reducer(initialState, VisStateActions.setFilterView(0, FILTER_VIEW_TYPES.side));\n\n  t.equal(\n    nextState.filters[0].view,\n    FILTER_VIEW_TYPES.side,\n    'should toggle time filter view to be side'\n  );\n\n  const nextState2 = reducer(\n    nextState,\n    VisStateActions.setFilterView(0, FILTER_VIEW_TYPES.enlarged)\n  );\n\n  t.equal(\n    nextState2.filters[0].view,\n    FILTER_VIEW_TYPES.enlarged,\n    'should toggle time filter view to be bottom'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> REMOVE_DATASET', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  const nextState = reducer(initialState, VisStateActions.removeDataset('not_me'));\n\n  t.equal(initialState, nextState, 'should return state if datasetKey doesnot exist');\n  t.end();\n});\n\ntest('#visStateReducer -> REMOVE_DATASET w filter and layer', t => {\n  const oldState = CloneDeep(StateWFilters.visState);\n\n  const expectedState = {\n    ...oldState,\n    layers: [oldState.layers[1]],\n    filters: [oldState.filters[1]],\n    layerData: [oldState.layerData[1]],\n    layerOrder: [oldState.layers[1].id],\n    datasets: {\n      [testGeoJsonDataId]: oldState.datasets[testGeoJsonDataId]\n    },\n    effects: [],\n    effectOrder: [],\n\n    interactionConfig: {\n      ...oldState.interactionConfig,\n      tooltip: {\n        id: 'tooltip',\n        label: 'interactions.tooltip',\n        enabled: true,\n        config: {\n          compareMode: false,\n          compareType: 'absolute',\n          fieldsToShow: {\n            [testGeoJsonDataId]: [\n              {\n                name: 'OBJECTID',\n                format: null\n              },\n              {\n                name: 'ZIP_CODE',\n                format: null\n              },\n              {\n                name: 'ID',\n                format: null\n              },\n              {\n                name: 'TRIPS',\n                format: null\n              },\n              {\n                name: 'RATE',\n                format: null\n              }\n            ]\n          }\n        }\n      }\n    }\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.removeDataset(testCsvDataId));\n\n  t.deepEqual(\n    Object.keys(newReducer).sort(),\n    Object.keys(expectedState).sort(),\n    `visState should have same keys`\n  );\n  Object.keys(expectedState).forEach(key => {\n    t.deepEqual(newReducer[key], expectedState[key], `newReducer.${key} should be correct`);\n  });\n\n  t.end();\n});\n\ntest('#visStateReducer -> REMOVE_DATASET w synced filter', t => {\n  const initialState = CloneDeep(StateWSyncedTimeFilter.visState);\n  const oldFilter = {...initialState.filters[0]};\n\n  const resultState = reducer(initialState, VisStateActions.removeDataset(testCsvDataSlice1Id));\n\n  t.equal(resultState.filters.length, 1, 'should still have 1 filter');\n  const expectedFilter = {\n    ...expectedSyncedTsFilter,\n    id: oldFilter.id,\n    dataId: ['test-csv-data-2'],\n    name: ['gps_data.utc_timestamp'],\n    fieldIdx: [0],\n    // reset domain\n    domain: [1474071301000, 1474072208000],\n    // adjust value\n    value: [1474071301000, 1474072188000],\n    gpuChannel: [0],\n    timeBins: {\n      'test-csv-data-2': expectedSyncedTsFilter.timeBins['test-csv-data-2']\n    }\n  };\n  cmpFilters(t, expectedFilter, resultState.filters[0]);\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: TOGGLE', t => {\n  const layer0 = new ArcLayer({id: 'a', dataId: 'puppy_0', isVisible: true});\n  const layer1 = new ArcLayer({id: 'b', dataId: 'puppy_0', isVisible: false});\n\n  const oldState = {\n    layers: [layer0, layer1],\n    splitMaps: []\n  };\n\n  const newReducer = reducer(oldState, MapStateActions.toggleSplitMap());\n\n  t.deepEqual(\n    newReducer.splitMaps,\n    [\n      {\n        layers: {\n          a: true\n        }\n      },\n      {layers: {}}\n    ],\n    'should split map'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: REMOVE_LAYER', t => {\n  const layer1 = new PointLayer({id: 'a'});\n  const layer2 = new PointLayer({id: 'b'});\n  const layers = [layer1, layer2];\n  const oldState = {\n    layers,\n    layerData: [{data: 1}, {data: 2}],\n    layerOrder: [layers[1].id, layers[0].id],\n    hoverInfo: {\n      layer: {props: {id: 'b'}},\n      picked: true\n    },\n    clicked: {\n      layer: {props: {id: 'a'}},\n      picked: true\n    },\n    splitMaps: [\n      {\n        layers: {\n          a: true,\n          b: true\n        }\n      },\n      {\n        layers: {\n          a: true,\n          b: true\n        }\n      }\n    ],\n    animationConfig: DEFAULT_ANIMATION_CONFIG\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.removeLayer('b'));\n\n  t.deepEqual(\n    newReducer,\n    {\n      layers: [layer1],\n      layerData: [{data: 1}],\n      layerOrder: [layer1.id],\n      hoverInfo: undefined,\n      clicked: {\n        layer: {props: {id: 'a'}},\n        picked: true\n      },\n      splitMaps: [\n        {\n          layers: {\n            a: true\n          }\n        },\n        {\n          layers: {\n            a: true\n          }\n        }\n      ],\n      animationConfig: DEFAULT_ANIMATION_CONFIG\n    },\n    'should remove layer and layerData in split mode'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: REMOVE_LAYER. set animation domain', t => {\n  const layer1 = new PointLayer({id: 'a'});\n  const layer2 = new PointLayer({id: 'b'});\n  const layer3 = new TripLayer({id: 't1', isVisible: true});\n  const layer4 = new TripLayer({id: 't2', isVisible: true});\n\n  layer3.updateAnimationDomain([1568502710000, 1568502960000]);\n  layer4.updateAnimationDomain([1568502810000, 1568503060000]);\n\n  const oldState = {\n    layers: [layer1, layer2, layer3, layer4],\n    layerData: [{data: 1}, {data: 2}, {data: 3}, {data: 4}],\n    layerOrder: [1, 0, 2, 3],\n    hoverInfo: null,\n    clicked: null,\n    splitMaps: [],\n    animationConfig: {\n      domain: [1568502710000, 1568503060000],\n      currentTime: 1568502970000,\n      duration: null,\n      timeSteps: null\n    }\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.removeLayer('t1'));\n  const expectedAnimationConfig = {\n    domain: [1568502810000, 1568503060000],\n    currentTime: 1568502970000,\n    duration: null,\n    timeSteps: null,\n    defaultTimeFormat: 'L LTS'\n  };\n\n  t.deepEqual(\n    newReducer.animationConfig,\n    expectedAnimationConfig,\n    'should remove animation layer and adjust animation domain'\n  );\n\n  const newReducer2 = reducer(oldState, VisStateActions.removeLayer('t2'));\n  const expectedAnimationConfig2 = {\n    domain: [1568502710000, 1568502960000],\n    currentTime: 1568502710000,\n    duration: null,\n    timeSteps: null,\n    defaultTimeFormat: 'L LTS'\n  };\n  t.deepEqual(\n    newReducer2.animationConfig,\n    expectedAnimationConfig2,\n    'should remove animation layer and adjust animation domain'\n  );\n\n  const newReducer3 = reducer(newReducer2, VisStateActions.removeLayer('t1'));\n  t.deepEqual(\n    newReducer3.animationConfig,\n    {\n      domain: null,\n      currentTime: 1568502710000,\n      isAnimating: false,\n      duration: null,\n      timeSteps: null,\n      defaultTimeFormat: null\n    },\n    'remove last animation layer and set animation config to default'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: REMOVE_DATASET', t => {\n  const oldState = StateWSplitMaps.visState;\n\n  const expectedState = {\n    ...oldState,\n    layers: [oldState.layers[0]],\n    layerData: [oldState.layerData[0]],\n    layerOrder: [oldState.layers[0].id],\n    datasets: {\n      [testCsvDataId]: oldState.datasets[testCsvDataId]\n    },\n    filters: [],\n    effects: [],\n    effectOrder: [],\n    interactionConfig: {\n      ...oldState.interactionConfig,\n      tooltip: {\n        id: 'tooltip',\n        label: 'interactions.tooltip',\n        enabled: true,\n        config: {\n          compareMode: false,\n          compareType: 'absolute',\n          fieldsToShow: {\n            [testCsvDataId]: [\n              {\n                name: 'gps_data.utc_timestamp',\n                format: null\n              },\n              {\n                name: 'gps_data.types',\n                format: null\n              },\n              {\n                name: 'epoch',\n                format: null\n              },\n              {\n                name: 'has_result',\n                format: null\n              },\n              {\n                name: 'uid',\n                format: null\n              }\n            ]\n          }\n        }\n      }\n    },\n    splitMaps: [{layers: {'point-0': false}}, {layers: {'point-0': true}}]\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.removeDataset(testGeoJsonDataId));\n\n  t.deepEqual(\n    Object.keys(newReducer).sort(),\n    Object.keys(expectedState).sort(),\n    `visState should have same keys`\n  );\n  Object.keys(expectedState).forEach(key => {\n    t.deepEqual(newReducer[key], expectedState[key], `newReducer.${key} should be correct`);\n  });\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: ADD_LAYER', t => {\n  const oldState = {\n    datasets: {\n      puppy: {\n        data: mockData.data,\n        fields: mockData.fields\n      }\n    },\n    layers: [{id: 'existing_layer'}],\n    layerData: [[{data: [1, 2, 3]}, {data: [4, 5, 6]}]],\n    layerOrder: [0],\n    splitMaps: [\n      {\n        layers: {\n          existing_layer: true\n        }\n      },\n      {\n        layers: {\n          existing_layer: true\n        }\n      }\n    ]\n  };\n\n  const newReducer = reducer(oldState, VisStateActions.addLayer());\n\n  t.equal(newReducer.layers[1].config.isVisible, true, 'newLayer visibility should be set to true');\n  t.equal(\n    newReducer.layers[1].config.isConfigActive,\n    true,\n    'newLayer isConfigActive should be set to true'\n  );\n  t.equal(newReducer.layers[1].config.dataId, 'puppy', 'newLayer dataId should be set to default');\n  t.equal(newReducer.splitMaps.length, 2, 'newLayer was added into splitMaps layers');\n  t.deepEqual(\n    newReducer.splitMaps[0],\n    {\n      layers: {\n        existing_layer: true,\n        [newReducer.layers[1].id]: true\n      }\n    },\n    'newLayer map meta data settings are correct'\n  );\n  t.deepEqual(\n    newReducer.splitMaps[1],\n    {\n      layers: {\n        existing_layer: true,\n        [newReducer.layers[1].id]: true\n      }\n    },\n    'newLayer map meta data settings are correct'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: TOGGLE_SPLIT_MAP', t => {\n  const layer0 = new ArcLayer({id: 'a', dataId: 'puppy_0', isVisible: true});\n  const layer1 = new PointLayer({id: 'b', dataId: 'puppy_0', isVisible: true});\n\n  const oldState = {\n    layers: [layer0, layer1],\n    splitMaps: [\n      {\n        layers: {\n          a: true,\n          b: true\n        }\n      },\n      {\n        layers: {\n          a: true,\n          b: false\n        }\n      }\n    ]\n  };\n\n  const newReducer = reducer(oldState, MapStateActions.toggleSplitMap(0));\n\n  t.equal(newReducer.layers.length, 2, 'should have 2 global layers');\n  t.equal(\n    newReducer.layers[0].config.isVisible,\n    true,\n    'global layer should have changed to reflect specific map meta info'\n  );\n  t.equal(\n    newReducer.layers[1].config.isVisible,\n    false,\n    'global layer should have changed to reflect specific map meta info'\n  );\n  t.end();\n});\n\ntest('#visStateReducer -> SPLIT_MAP: HIDE LAYER', t => {\n  const oldState = {\n    splitMaps: [\n      {\n        layers: {\n          a: true,\n          b: true\n        }\n      },\n      {\n        layers: {\n          a: true,\n          b: false\n        }\n      }\n    ]\n  };\n\n  const newState = reducer(oldState, VisStateActions.toggleLayerForMap(1, 'a'));\n\n  const expectedState = {\n    splitMaps: [\n      {\n        layers: {\n          a: true,\n          b: true\n        }\n      },\n      {\n        layers: {\n          a: false,\n          b: false\n        }\n      }\n    ]\n  };\n\n  t.deepEqual(newState.splitMaps, expectedState.splitMaps, 'should hide layer B in split map');\n\n  t.end();\n});\n\ntest('#visStateReducer -> SET_LAYER_ANIMATION_TIME', t => {\n  const initialState = StateWTripGeojson.visState;\n  const newState = reducer(initialState, VisStateActions.setLayerAnimationTime(1000));\n\n  t.equal(newState.animationConfig.currentTime, 1000, 'should update animation time');\n  t.end();\n});\n\ntest('#visStateReducer -> UPDATE_LAYER_ANIMATION_SPEED', t => {\n  const initialState = StateWTripGeojson.visState;\n  const newState = reducer(initialState, VisStateActions.updateLayerAnimationSpeed(1.23));\n\n  t.equal(newState.animationConfig.speed, 1.23, 'should update animation speed');\n\n  t.end();\n});\n\ntest('#visStateReducer -> TOGGLE_LAYER_ANIMATION', t => {\n  const initialState = StateWTripGeojson.visState;\n  const newState = reducer(initialState, VisStateActions.toggleLayerAnimation());\n  t.equal(newState.animationConfig.isAnimating, true, 'should update animationConfig');\n  t.end();\n});\n\ntest('#visStateReducer -> INTERACTION_CONFIG_CHANGE', t => {\n  const brushConfig = {\n    ...defaultInteractionConfig.brush,\n    enabled: true\n  };\n\n  const expectedConfig = {\n    ...defaultInteractionConfig,\n    brush: brushConfig,\n    tooltip: {\n      ...defaultInteractionConfig.tooltip,\n      enabled: false\n    },\n    geocoder: {\n      ...defaultInteractionConfig.geocoder,\n      enabled: false\n    }\n  };\n\n  const nextState = reducer(\n    INITIAL_VIS_STATE,\n    VisStateActions.interactionConfigChange(brushConfig)\n  );\n\n  t.deepEqual(nextState.interactionConfig, expectedConfig, 'should disable tooltip');\n\n  t.end();\n});\n\ntest('#visStateReducer -> SHOW_DATASET_TABLE', t => {\n  const initialState = StateWFiles.visState;\n  const nextState = reducer(initialState, VisStateActions.showDatasetTable('abc'));\n\n  t.equal(nextState.editingDataset, 'abc', 'should set editingDataset');\n  t.end();\n});\n\ntest('#visStateReducer -> MAP_CLICK', t => {\n  const initialState = StateWFiles.visState;\n  const nextState = reducer(\n    initialState,\n    VisStateActions.onLayerClick({picked: true, object: 'he'})\n  );\n\n  t.deepEqual(\n    nextState,\n    {...nextState, clicked: {picked: true, object: 'he'}},\n    'should set clicked'\n  );\n\n  const nextState2 = reducer(nextState, VisStateActions.onMapClick());\n\n  t.equal(nextState2.clicked, null, 'should unset clicked');\n\n  t.end();\n});\n\ntest('#visStateReducer -> MOUSE_MOVE', t => {\n  const initialState = StateWFiles.visState;\n  const evt = {\n    point: [10, 20],\n    lngLat: [37, -122]\n  };\n\n  const nextState = reducer(initialState, VisStateActions.onMouseMove(evt));\n\n  t.deepEqual(\n    nextState.mousePos,\n    {\n      ...initialState.mousePos,\n      mousePosition: [10, 20],\n      coordinate: [37, -122]\n    },\n    'should set mousePos'\n  );\n\n  // disable tooltip\n  const tooltipConfig = {\n    ...defaultInteractionConfig.tooltip,\n    enabled: false\n  };\n\n  const nextState1 = reducer(nextState, VisStateActions.interactionConfigChange(tooltipConfig));\n\n  const nextState2 = reducer(\n    nextState1,\n    VisStateActions.onMouseMove({point: [1, 2], lngLat: [90, 90]})\n  );\n\n  t.deepEqual(\n    nextState2.mousePos,\n    {\n      ...initialState.mousePos,\n      mousePosition: [10, 20],\n      coordinate: [37, -122]\n    },\n    'should not set mousePos'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. show dropdown', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const oldColorRange = CloneDeep(pointLayer.config.visConfig.colorRange);\n  // show dropdown\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'color', {\n      showDropdown: 0\n    })\n  );\n\n  const expectedColorUI = {\n    color: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0\n    },\n    colorRange: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI\n  };\n\n  t.deepEqual(\n    nextState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should update colorUI.showDropdown'\n  );\n  t.deepEqual(\n    nextState.layers[0].config.visConfig.colorRange,\n    oldColorRange,\n    'should not change colorRange'\n  );\n\n  const nextState1 = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(pointLayer, 'color', {\n      showDropdown: false\n    })\n  );\n  t.deepEqual(\n    nextState1.layers[0].config.colorUI,\n    {color: DEFAULT_COLOR_UI, colorRange: DEFAULT_COLOR_UI, strokeColorRange: DEFAULT_COLOR_UI},\n    'should update colorUI.showDropdown'\n  );\n\n  const nextState2 = reducer(\n    nextState1,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      showDropdown: 0\n    })\n  );\n\n  const expectedColorUI2 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n  t.deepEqual(\n    nextState2.layers[0].config.colorUI,\n    expectedColorUI2,\n    'should update colorUI.showDropdown, set colorRangeConfig step and reversed'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. colorRangeConfig.step', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n  const oldColorRange = CloneDeep(pointLayer.config.visConfig.colorRange);\n\n  t.equal(oldColorRange.colors.length, 4, 'old color range should have 4 colors');\n  // show dropdown\n  const prepareState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      showDropdown: 0\n    })\n  );\n\n  // set color range steps\n  const nextState = reducer(\n    prepareState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {steps: 6}\n    })\n  );\n\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 6,\n        reversed: false,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  const expectedColorRange = colorPaletteToColorRange(\n    KEPLER_COLOR_PALETTES.find(({name}) => name === 'Uber Viz Sequential'),\n    {steps: 6, reversed: false}\n  );\n\n  t.deepEqual(\n    nextState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should update colorUI.colorRangeConfig.steps'\n  );\n  t.deepEqual(\n    nextState.layers[0].config.visConfig.colorRange,\n    expectedColorRange,\n    'should update visConfig.colorRange based on step'\n  );\n\n  // set color range reverse\n  const nextState2 = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(nextState.layers[0], 'colorRange', {\n      colorRangeConfig: {reversed: true}\n    })\n  );\n\n  const expectedColorUI2 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 6,\n        reversed: true,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  const expectedColorRange2 = colorPaletteToColorRange(\n    KEPLER_COLOR_PALETTES.find(({name}) => name === 'Uber Viz Sequential'),\n    {steps: 6, reversed: true}\n  );\n\n  t.deepEqual(\n    nextState2.layers[0].config.colorUI,\n    expectedColorUI2,\n    'should update colorUI.colorRangeConfig.reversed'\n  );\n  t.deepEqual(\n    nextState2.layers[0].config.visConfig.colorRange,\n    expectedColorRange2,\n    'should update visConfig.colorRange based on reversed'\n  );\n\n  // update step when reversed is true\n  const nextState3 = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(nextState2.layers[0], 'colorRange', {\n      colorRangeConfig: {steps: 8}\n    })\n  );\n\n  const expectedColorUI3 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 8,\n        reversed: true,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  const expectedColorRange3 = colorPaletteToColorRange(\n    KEPLER_COLOR_PALETTES.find(({name}) => name === 'Uber Viz Sequential'),\n    {steps: 8, reversed: true}\n  );\n\n  t.deepEqual(\n    nextState3.layers[0].config.colorUI,\n    expectedColorUI3,\n    'should update colorUI.colorRangeConfig.steps'\n  );\n  t.deepEqual(\n    nextState3.layers[0].config.visConfig.colorRange,\n    expectedColorRange3,\n    'should update visConfig.colorRange based on match and set reversed'\n  );\n\n  // set to a step that has no match\n  const nextState4 = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(nextState3.layers[0], 'colorRange', {\n      colorRangeConfig: {type: 'diverging'}\n    })\n  );\n\n  const expectedColorUI4 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'diverging',\n        steps: 8,\n        reversed: true,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n  const expectedColorRange8 = colorPaletteToColorRange(\n    KEPLER_COLOR_PALETTES.find(({name}) => name === 'Uber Viz Diverging'),\n    {steps: 8, reversed: true}\n  );\n\n  t.deepEqual(\n    nextState4.layers[0].config.colorUI,\n    expectedColorUI4,\n    'should update colorUI.colorRangeConfig to step 11'\n  );\n  t.deepEqual(\n    nextState4.layers[0].config.visConfig.colorRange,\n    expectedColorRange8,\n    'should get first valid visConfig.colorRange when no match'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. custom Palette', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const oldColorRange = CloneDeep(pointLayer.config.visConfig.colorRange);\n  // show dropdown\n  const prepareState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      showDropdown: 0\n    })\n  );\n\n  // enable custom\n  const nextState = reducer(\n    prepareState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: true}\n    })\n  );\n\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: oldColorRange.colors\n      },\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should update colorUI.customPalette with current colorRange colors'\n  );\n\n  const nextState2 = reducer(\n    prepareState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      customPalette: {colors: ['aaa', 'bbb', 'ccc']}\n    })\n  );\n\n  const expectedColorUI2 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['aaa', 'bbb', 'ccc']\n      },\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState2.layers[0].config.colorUI,\n    expectedColorUI2,\n    'should update colorUI.customPalette colors'\n  );\n\n  // show sketcher\n  const nextState3 = reducer(\n    nextState2,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      showSketcher: 1\n    })\n  );\n\n  const expectedColorUI3 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showSketcher: 1,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['aaa', 'bbb', 'ccc']\n      },\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(nextState3.layers[0].config.colorUI, expectedColorUI3, 'should set showSketcher: 1');\n\n  // edit color\n  const nextState4 = reducer(\n    nextState3,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      customPalette: {\n        colors: ['bbb', 'ccc', 'aaa']\n      }\n    })\n  );\n\n  const expectedColorUI4 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showSketcher: 1,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['bbb', 'ccc', 'aaa']\n      },\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState4.layers[0].config.colorUI,\n    expectedColorUI4,\n    'should update colorUI.customPalette colors'\n  );\n\n  // apply color\n  const nextState5 = reducer(\n    nextState4,\n    VisStateActions.layerVisConfigChange(nextState4.layers[0], {\n      colorRange: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['bbb', 'ccc', 'aaa']\n      }\n    })\n  );\n\n  // close custom palette\n  const nextState6 = reducer(\n    nextState5,\n    VisStateActions.layerColorUIChange(nextState5.layers[0], 'colorRange', {\n      colorRangeConfig: {\n        custom: false\n      }\n    })\n  );\n\n  const expectedColorUI6 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      showSketcher: 1,\n      // keep the customPalette\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['bbb', 'ccc', 'aaa']\n      },\n      showDropdown: 0,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState6.layers[0].config.colorUI,\n    expectedColorUI6,\n    'should set colorRangeConfig.custom false'\n  );\n\n  t.deepEqual(\n    nextState6.layers[0].config.visConfig.colorRange,\n    {\n      name: 'color.customPalette',\n      type: 'custom',\n      category: 'Custom',\n      colors: ['bbb', 'ccc', 'aaa']\n    },\n    'should set visConfig.colorRange'\n  );\n\n  // color colorMap and colorLegends to colorRange\n  nextState6.layers[0].config.visConfig.colorRange = {\n    ...nextState6.layers[0].config.visConfig.colorRange,\n    colorLegends: {\n      bbb: 'custom legend'\n    },\n    colorMap: [\n      [1, 'bbb'],\n      [2, 'ccc'],\n      [null, 'aaa']\n    ]\n  };\n\n  // open it again\n  const nextState7 = reducer(\n    nextState6,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {\n        custom: true\n      }\n    })\n  );\n\n  const expectedColorUI7 = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      showSketcher: 1,\n      // keep the customPalette\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['bbb', 'ccc', 'aaa'],\n        colorLegends: {\n          bbb: 'custom legend'\n        },\n        colorMap: [\n          [1, 'bbb'],\n          [2, 'ccc'],\n          [null, 'aaa']\n        ]\n      },\n      showDropdown: 0,\n      showColorChart: false,\n      colorRangeConfig: {\n        colorBlindSafe: false,\n        type: 'all',\n        steps: 4,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState7.layers[0].config.colorUI,\n    expectedColorUI7,\n    'should set colorRangeConfig.custom true'\n  );\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. custom breaks', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {customBreaks: true}\n    })\n  );\n\n  // Explain: with redesigned color range using chormajs/d3, create customBreaks\n  // will lead to customPalette, which won't be overrided by predefined colorPalette\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA'],\n        colorMap: [\n          ['driver_analytics', '#00939C'],\n          ['driver_analytics_0', '#6BB5B9'],\n          ['driver_gps', '#AAD7D9']\n        ]\n      },\n      colorRangeConfig: {\n        type: 'all',\n        colorBlindSafe: false,\n        steps: 6,\n        reversed: false,\n        custom: false,\n        customBreaks: true\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should set customBreaks: true and update colorUI.customPalette with default colorMap'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> setFeatures/delete', t => {\n  const expectedFeatures = [mockPolygonFeature];\n  let newReducer = reducer(INITIAL_VIS_STATE, VisStateActions.setFeatures([mockPolygonFeature]));\n\n  t.deepEqual(newReducer.editor.features, expectedFeatures, 'should add new feature');\n\n  newReducer = reducer(newReducer, VisStateActions.deleteFeature(mockPolygonFeature));\n\n  t.deepEqual(newReducer.editor.features, [], 'Should not have features');\n\n  t.end();\n});\n\ntest('#visStateReducer -> POLYGON: Add/Remove new polygon feature', t => {\n  const expectedFeatures = [mockPolygonFeature];\n  let newReducer = reducer(INITIAL_VIS_STATE, VisStateActions.setFeatures([mockPolygonFeature]));\n\n  t.deepEqual(newReducer.editor.features, expectedFeatures, 'should add new feature');\n\n  newReducer = reducer(newReducer, VisStateActions.setSelectedFeature(mockPolygonFeature));\n\n  const updatedFeature = {\n    ...mockPolygonFeature,\n    geometry: {\n      ...mockPolygonFeature.geometry,\n      coordinates: [\n        [\n          [12.0, 30.0],\n          [12.0, 36.0],\n          [12.5, 36.0],\n          [12.0, 30.0]\n        ]\n      ]\n    }\n  };\n\n  newReducer = reducer(newReducer, VisStateActions.setFeatures([updatedFeature]));\n\n  t.deepEqual(\n    newReducer.editor.selectedFeature.id,\n    mockPolygonFeature.id,\n    'should set selected feature'\n  );\n\n  newReducer = reducer(newReducer, VisStateActions.deleteFeature(mockPolygonFeature));\n\n  t.deepEqual(\n    newReducer.editor,\n    {\n      features: [],\n      selectedFeature: null,\n      selectionContext: undefined,\n      visible: true,\n      mode: 'EDIT_VERTEX'\n    },\n    'Should remove existing feature and set selected feature to null'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> POLYGON: Create polygon filter', t => {\n  const state = {\n    ...INITIAL_VIS_STATE\n  };\n\n  const datasets = [\n    {\n      data: {\n        fields: [\n          {\n            name: 'start_point_lat',\n            format: '',\n            fieldIdx: 0,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'start_point_lng',\n            format: '',\n            fieldIdx: 1,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lat',\n            format: '',\n            fieldIdx: 2,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lng',\n            format: '',\n            fieldIdx: 3,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          }\n        ],\n        rows: mockPolygonData.data\n      },\n      info: {\n        label: 'test.csv',\n        size: 144\n      }\n    }\n  ];\n\n  const options = {\n    centerMap: true,\n    keepExistingConfig: false\n  };\n\n  // visStateUpdateVisDataUpdater - creates 4 layers\n  let newReducer = applyActions(reducer, state, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [datasets, options, {}]\n    }\n  ]);\n\n  // add new polygon feature\n  newReducer = reducer(newReducer, VisStateActions.setFeatures([mockPolygonFeature]));\n\n  // set selected feature\n  newReducer = reducer(newReducer, VisStateActions.setSelectedFeature(mockPolygonFeature));\n\n  // set it as filter\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[0], mockPolygonFeature)\n  );\n\n  const newFilter = newReducer.filters[0];\n\n  const firstDataset = Object.keys(newReducer.datasets)[0];\n  const expectedFilter = {\n    id: newFilter.id,\n    dataId: [firstDataset],\n    enabled: true,\n    fixedDomain: true,\n    view: FILTER_VIEW_TYPES.side,\n    isAnimating: false,\n    animationWindow: 'free',\n    speed: 1,\n    name: [],\n    type: 'polygon',\n    fieldIdx: [],\n    domain: null,\n    value: {\n      ...mockPolygonFeature,\n      properties: {\n        ...mockPolygonFeature.properties,\n        isVisible: true,\n        filterId: newFilter.id\n      }\n    },\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    layerId: [newReducer.layers[0].id],\n    gpu: false\n  };\n\n  t.deepEqual(newFilter, expectedFilter, 'Should have created a polygon filter');\n\n  t.equal(newReducer.layerData[0].data.length, 2, 'Layer Point 1 should only show 2 points');\n\n  t.equal(newReducer.layerData[1].data.length, 2, 'Layer Point 2 should only show 2 points');\n\n  const filterFeature = newReducer.filters[0].value;\n\n  // set polygon filter for the second layer\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[1], filterFeature)\n  );\n\n  t.equal(newReducer.filters.length, 1, 'Should still have 1 polygon filter');\n\n  t.equal(newReducer.filters[0].layerId.length, 2, 'Should have two values in filter.layerId');\n\n  t.equal(newReducer.layerData[0].data.length, 0, 'Layer Point 1 should show 0 points');\n\n  t.equal(newReducer.layerData[1].data.length, 0, 'Layer Point 2 show show 0 points');\n\n  // Adding a new dataset - creates extra 4 layers\n  newReducer = applyActions(reducer, newReducer, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [datasets, options, {}]\n    }\n  ]);\n\n  t.equal(newReducer.layerData[4].data.length, 4, 'Layer Point 5 should full data');\n\n  // Set polygon for a different dataset layer\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[4], filterFeature)\n  );\n\n  t.equal(newReducer.filters[0].layerId.length, 3, 'Should 3 values in filter.layerId');\n\n  t.equal(newReducer.filters[0].dataId.length, 2, 'Should have two values in filter.dataId');\n\n  t.equal(newReducer.layerData[4].data.length, 2, 'Layer Point 5 should 2 points because filtered');\n\n  // Remove second layer from filter\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[1], filterFeature)\n  );\n\n  t.equal(newReducer.filters[0].layerId.length, 2, 'Should 3 values in filter.layerId');\n\n  t.equal(newReducer.filters[0].dataId.length, 2, 'Should have two values in filter.dataId');\n\n  t.equal(\n    newReducer.layerData[0].data.length,\n    2,\n    'Layer Point 1 show 2 points because we removed layer 2'\n  );\n\n  t.equal(newReducer.layerData[4].data.length, 2, 'Layer Point 5 should 2 points because filtered');\n\n  t.equal(\n    newReducer.layerData[2].data.length,\n    2,\n    'Layer Point 2 should still show 2 filters because layer 1 is still filtered'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> POLYGON: Toggle filter feature', t => {\n  const state = {\n    ...INITIAL_VIS_STATE\n  };\n\n  const datasets = [\n    {\n      data: {\n        fields: [\n          {\n            name: 'start_point_lat',\n            format: '',\n            fieldIdx: 0,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'start_point_lng',\n            format: '',\n            fieldIdx: 1,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lat',\n            format: '',\n            fieldIdx: 2,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lng',\n            format: '',\n            fieldIdx: 3,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          }\n        ],\n        rows: mockPolygonData.data\n      },\n      info: {\n        label: 'test.csv',\n        size: 144,\n        id: 'puppy'\n      }\n    },\n    {\n      data: {\n        fields: [\n          {\n            name: 'start_point_lat',\n            format: '',\n            fieldIdx: 0,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'start_point_lng',\n            format: '',\n            fieldIdx: 1,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lat',\n            format: '',\n            fieldIdx: 2,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lng',\n            format: '',\n            fieldIdx: 3,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          }\n        ],\n        rows: mockPolygonData.data\n      },\n      info: {\n        label: 'test.csv',\n        size: 144,\n        id: 'cat'\n      }\n    }\n  ];\n\n  const options = {\n    centerMap: true,\n    keepExistingConfig: false\n  };\n\n  // visStateUpdateVisDataUpdater - creates 4 layers\n  let newReducer = applyActions(reducer, state, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [datasets, options, {}]\n    }\n  ]);\n\n  newReducer = reducer(newReducer, VisStateActions.addLayer());\n\n  t.equal(newReducer.layers.length, 9, 'Should have created a new layer');\n\n  // add new polygon feature\n  newReducer = reducer(newReducer, VisStateActions.setFeatures([mockPolygonFeature]));\n\n  // set selected feature\n  newReducer = reducer(newReducer, VisStateActions.setSelectedFeature(mockPolygonFeature));\n\n  // set it as filter\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[0], mockPolygonFeature)\n  );\n\n  const newFilter = newReducer.filters[0];\n\n  const expectedFilter = {\n    id: newFilter.id,\n    dataId: ['puppy'],\n    enabled: true,\n    fixedDomain: true,\n    view: FILTER_VIEW_TYPES.side,\n    isAnimating: false,\n    animationWindow: 'free',\n    speed: 1,\n    name: [],\n    type: 'polygon',\n    fieldIdx: [],\n    domain: null,\n    value: {\n      ...mockPolygonFeature,\n      properties: {\n        ...mockPolygonFeature.properties,\n        isVisible: true,\n        filterId: newFilter.id\n      }\n    },\n    plotType: {type: 'histogram'},\n    yAxis: null,\n    layerId: [newReducer.layers[0].id],\n    gpu: false\n  };\n\n  t.deepEqual(newFilter, expectedFilter, 'Should have created a polygon filter');\n\n  let filterFeature = newReducer.filters[0].value;\n\n  t.deepEqual(\n    filterFeature.properties.isVisible,\n    true,\n    'Should have feature visibility set to true'\n  );\n  t.deepEqual(\n    newReducer.datasets.puppy.filteredIndex,\n    [0, 2],\n    'The polygon filter should be applied'\n  );\n\n  newReducer = reducer(newReducer, VisStateActions.toggleFilterFeature(0));\n\n  filterFeature = newReducer.filters[0].value;\n  t.deepEqual(filterFeature.properties.isVisible, false, 'Should hide filter feature');\n  t.deepEqual(newReducer.filters[0].enabled, false, 'Should disable the filter');\n  t.deepEqual(\n    newReducer.datasets.puppy.filteredIndex,\n    [0, 1, 2, 3],\n    \"The polygon filter shouldn't be applied\"\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> POLYGON: delete polygon filter', t => {\n  const state = {\n    ...INITIAL_VIS_STATE\n  };\n\n  const datasets = [\n    {\n      data: {\n        fields: [\n          {\n            name: 'start_point_lat',\n            format: '',\n            fieldIdx: 0,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'start_point_lng',\n            format: '',\n            fieldIdx: 1,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lat',\n            format: '',\n            fieldIdx: 2,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lng',\n            format: '',\n            fieldIdx: 3,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          }\n        ],\n        rows: mockPolygonData.data\n      },\n      info: {\n        label: 'test.csv',\n        size: 144,\n        id: 'puppy'\n      }\n    },\n    {\n      data: {\n        fields: [\n          {\n            name: 'start_point_lat',\n            format: '',\n            fieldIdx: 0,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'start_point_lng',\n            format: '',\n            fieldIdx: 1,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lat',\n            format: '',\n            fieldIdx: 2,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          },\n          {\n            name: 'end_point_lng',\n            format: '',\n            fieldIdx: 3,\n            type: 'real',\n            analyzerType: 'FLOAT'\n          }\n        ],\n        rows: mockPolygonData.data\n      },\n      info: {\n        label: 'test.csv',\n        size: 144,\n        id: 'cat'\n      }\n    }\n  ];\n\n  const options = {\n    centerMap: true,\n    keepExistingConfig: false\n  };\n\n  // visStateUpdateVisDataUpdater - creates 4 layers\n  let newReducer = applyActions(reducer, state, [\n    {\n      action: VisStateActions.updateVisData,\n      payload: [datasets, options, {}]\n    }\n  ]);\n\n  newReducer = reducer(newReducer, VisStateActions.addLayer());\n\n  t.equal(newReducer.layers.length, 9, 'Should have created a new layer');\n\n  // add new polygon feature\n  newReducer = reducer(newReducer, VisStateActions.setFeatures([mockPolygonFeature]));\n\n  // set selected feature\n  newReducer = reducer(newReducer, VisStateActions.setSelectedFeature(mockPolygonFeature));\n\n  // set it as filter\n  newReducer = reducer(\n    newReducer,\n    VisStateActions.setPolygonFilterLayer(newReducer.layers[0], mockPolygonFeature)\n  );\n\n  // Update filters using setFilter\n  newReducer = reducer(newReducer, VisStateActions.setFilter(0, 'layerId', []));\n\n  t.deepEqual(newReducer.filters[0].layerId, [], 'Should have removed layers from filter');\n\n  t.deepEqual(newReducer.filters[0].dataId, [], 'Should have removed datasets from filter');\n\n  // unset it as filter\n  newReducer = reducer(newReducer, VisStateActions.removeFilter(0));\n\n  t.deepEqual(newReducer.filters, [], 'Should have removed the created polygon filter');\n\n  // deleting the filter will also delete the feature\n  t.deepEqual(newReducer.editor.features.length, 0, 'Should have removed the feature');\n\n  t.end();\n});\n\ntest('#visStateReducer -> POLYGON: setPolygonFilterLayer: H3', t => {\n  const initialState = CloneDeep(StateWH3Layer).visState;\n  const newState = reducer(\n    initialState,\n    VisStateActions.setPolygonFilterLayer(initialState.layers[0], mockPolygonFeature2)\n  );\n\n  const expectedFilteredIndex = [1, 3, 5, 8];\n  t.deepEqual(\n    newState.datasets['190vdll3di'].filteredIndex,\n    expectedFilteredIndex,\n    'should filter data based on h3 layer'\n  );\n  t.deepEqual(\n    newState.layerData[0].data.map(d => d.index),\n    [1, 3, 5, 8],\n    'should filter layer data'\n  );\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_EDITOR_MODE', t => {\n  const newState = reducer(INITIAL_VIS_STATE, VisStateActions.setEditorMode(EDITOR_MODES.EDIT));\n\n  t.equal(newState.editor.mode, EDITOR_MODES.EDIT, 'Editor mode should be set to vertex');\n\n  t.end();\n});\n\ntest('#uiStateReducer -> TOGGLE_EDITOR_VISIBILITY', t => {\n  let newState = reducer(INITIAL_VIS_STATE, VisStateActions.toggleEditorVisibility());\n\n  t.equal(newState.editor.visible, false, 'Should set editor visibility to false');\n\n  newState = reducer(newState, VisStateActions.toggleEditorVisibility());\n\n  t.equal(newState.editor.visible, true, 'Should set editor visibility to true');\n\n  t.end();\n});\n\ntest('#visStateReducer -> APPLY_CPU_FILTER. no filter', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n  const dataId = testCsvDataId;\n  const previousDataset = initialState.datasets[dataId];\n\n  const nextState = reducer(initialState, VisStateActions.applyCPUFilter(dataId));\n\n  const expectedDataset = {\n    ...previousDataset,\n    filteredIdxCPU: previousDataset.allIndexes,\n    filterRecordCPU: {\n      dynamicDomain: [],\n      fixedDomain: [],\n      cpu: [],\n      gpu: []\n    }\n  };\n\n  cmpDataset(t, expectedDataset, nextState.datasets[dataId]);\n  t.end();\n});\n\ntest('#visStateReducer -> APPLY_CPU_FILTER has gpu filter', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  // dataset has gpu filter\n  const dataId = testCsvDataId;\n  const previousDataset = initialState.datasets[dataId];\n  const gpuFilter = initialState.filters[0];\n\n  const nextState = reducer(initialState, VisStateActions.applyCPUFilter(dataId));\n\n  const expectedDataset = {\n    ...previousDataset,\n    filteredIdxCPU: [5, 6, 9, 10, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23],\n    filterRecordCPU: {\n      dynamicDomain: [],\n      fixedDomain: [gpuFilter],\n      cpu: [gpuFilter],\n      gpu: []\n    }\n  };\n\n  cmpDataset(t, expectedDataset, nextState.datasets[dataId]);\n\n  // calling it again\n  const nextState2 = reducer(nextState, VisStateActions.applyCPUFilter(dataId));\n\n  t.equal(\n    nextState.datasets[dataId].filteredIdxCPU,\n    nextState2.datasets[dataId].filteredIdxCPU,\n    'should directly copy filter result when filter has not changed'\n  );\n  t.end();\n});\n\ntest('#visStateReducer -> APPLY_CPU_FILTER has cpu filter', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  // dataset has gpu filter\n  const dataId = testGeoJsonDataId;\n  const previousDataset2 = initialState.datasets[dataId];\n  const ordinalFilter = initialState.filters[1];\n\n  const nextState = reducer(initialState, VisStateActions.applyCPUFilter(dataId));\n\n  const expectedDataset = {\n    ...previousDataset2,\n    filteredIdxCPU: [0],\n    filterRecordCPU: {\n      dynamicDomain: [],\n      fixedDomain: [ordinalFilter],\n      cpu: [ordinalFilter],\n      gpu: []\n    }\n  };\n\n  cmpDataset(t, expectedDataset, nextState.datasets[dataId]);\n\n  const nextState2 = reducer(nextState, VisStateActions.applyCPUFilter(dataId));\n\n  t.equal(\n    nextState.datasets[dataId].filteredIdxCPU,\n    nextState2.datasets[dataId].filteredIdxCPU,\n    'should directly copy filter result when filter has not changed'\n  );\n\n  t.end();\n});\n\ntest('#uiStateReducer -> SET_FEATURES/SET_SELECTED_FEATURE/DELETE_FEATURE', t => {\n  let newState = reducer(INITIAL_VIS_STATE, VisStateActions.setFeatures([]));\n\n  t.deepEqual(\n    newState,\n    INITIAL_VIS_STATE,\n    'Editor should not have features and return the same state'\n  );\n\n  newState = reducer(\n    INITIAL_VIS_STATE,\n    VisStateActions.setFeatures([\n      {\n        ...mockPolygonFeature,\n        properties: {\n          ...mockPolygonFeature.properties,\n          isClosed: false\n        }\n      }\n    ])\n  );\n\n  t.equal(\n    newState.editor.mode,\n    INITIAL_VIS_STATE.editor.mode,\n    'Editor mode should not change because feature is not closed'\n  );\n\n  newState = reducer(\n    newState,\n    VisStateActions.setFeatures([\n      {\n        ...mockPolygonFeature,\n        properties: {\n          ...mockPolygonFeature.properties,\n          isClosed: false\n        }\n      },\n      mockPolygonFeature\n    ])\n  );\n\n  t.equal(newState.editor.mode, EDITOR_MODES.EDIT, 'Editor mode should be set to edit_vertex');\n  t.end();\n});\n\ntest('#visStateReducer -> APPLY_CPU_FILTER has multi datasets', t => {\n  const initialState = CloneDeep(StateWFilters.visState);\n  const previousDataset1 = initialState.datasets[testCsvDataId];\n  const previousDataset2 = initialState.datasets[testGeoJsonDataId];\n  const gpuFilter = initialState.filters[0];\n  const ordinalFilter = initialState.filters[1];\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.applyCPUFilter([testCsvDataId, testGeoJsonDataId])\n  );\n\n  const expectedDataset1 = {\n    ...previousDataset1,\n    filteredIdxCPU: [5, 6, 9, 10, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23],\n    filterRecordCPU: {\n      dynamicDomain: [],\n      fixedDomain: [gpuFilter],\n      cpu: [gpuFilter],\n      gpu: []\n    }\n  };\n\n  const expectedDataset2 = {\n    ...previousDataset2,\n    filteredIdxCPU: [0],\n    filterRecordCPU: {\n      dynamicDomain: [],\n      fixedDomain: [ordinalFilter],\n      cpu: [ordinalFilter],\n      gpu: []\n    }\n  };\n\n  const expectedDatasets = {\n    [testCsvDataId]: expectedDataset1,\n    [testGeoJsonDataId]: expectedDataset2\n  };\n\n  cmpDatasets(t, expectedDatasets, nextState.datasets);\n\n  t.end();\n});\n\ntest('#visStateReducer -> SORT_TABLE_COLUMN', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n\n  // sort with default mode\n  const nextState = reducer(initialState, VisStateActions.sortTableColumn());\n  t.equal(nextState, initialState, 'state should not change when input is given');\n\n  // sort with\n  const nextState2 = reducer(\n    initialState,\n    VisStateActions.sortTableColumn(testCsvDataId, 'gps_data.lat')\n  );\n  t.ok(nextState2.datasets[testCsvDataId].sortOrder, 'should create sortOrder');\n\n  const expectedOrder = [\n    3, 0, 2, 4, 1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 19, 18, 23, 22, 21\n  ];\n  t.deepEqual(nextState2.datasets[testCsvDataId].sortOrder, expectedOrder, 'should sort correctly');\n  t.deepEqual(\n    nextState2.datasets[testCsvDataId].sortColumn,\n    {'gps_data.lat': 'ASCENDING'},\n    'should save sortOrder'\n  );\n\n  // sort again\n  const nextState3 = reducer(\n    nextState2,\n    VisStateActions.sortTableColumn(testCsvDataId, 'gps_data.lat')\n  );\n\n  const expectedOrder3 = [...expectedOrder].reverse();\n\n  t.deepEqual(\n    nextState3.datasets[testCsvDataId].sortOrder,\n    expectedOrder3,\n    'should sort correctly'\n  );\n  t.deepEqual(\n    nextState3.datasets[testCsvDataId].sortColumn,\n    {'gps_data.lat': 'DESCENDING'},\n    'should correctly sort'\n  );\n\n  // unsort\n  const nextState4 = reducer(\n    nextState3,\n    VisStateActions.sortTableColumn(testCsvDataId, 'gps_data.lat', 'UNSORT')\n  );\n  t.deepEqual(nextState4.datasets[testCsvDataId].sortOrder, null, 'should reset sortOrder');\n  t.deepEqual(nextState4.datasets[testCsvDataId].sortColumn, {}, 'should reset sortColumn');\n\n  // sort with mode\n  const nextState5 = reducer(\n    nextState4,\n    VisStateActions.sortTableColumn(testCsvDataId, 'gps_data.lat', 'DESCENDING')\n  );\n\n  t.deepEqual(\n    nextState5.datasets[testCsvDataId].sortOrder,\n    expectedOrder3,\n    'should sort correctly'\n  );\n  t.deepEqual(\n    nextState5.datasets[testCsvDataId].sortColumn,\n    {'gps_data.lat': 'DESCENDING'},\n    'should correctly sort'\n  );\n  assertDatasetIsTable(t, nextState5.datasets[testCsvDataId]);\n\n  t.end();\n});\n\ntest('#visStateReducer -> PIN_TABLE_COLUMN', t => {\n  const initialState = CloneDeep(StateWFiles.visState);\n\n  // pin with empty arg\n  const nextState = reducer(initialState, VisStateActions.pinTableColumn());\n  t.equal(nextState, initialState, 'state should not change when input is not given');\n\n  // pin gps_data.lat\n  const nextState1 = reducer(\n    nextState,\n    VisStateActions.pinTableColumn(testCsvDataId, 'gps_data.lat')\n  );\n\n  assertDatasetIsTable(t, nextState1.datasets[testCsvDataId]);\n\n  t.deepEqual(\n    nextState1.datasets[testCsvDataId].pinnedColumns,\n    ['gps_data.lat'],\n    'should add to pinned columns'\n  );\n\n  // unpin\n  const nextState2 = reducer(\n    nextState1,\n    VisStateActions.pinTableColumn(testCsvDataId, 'gps_data.lat')\n  );\n  assertDatasetIsTable(t, nextState2.datasets[testCsvDataId]);\n\n  t.deepEqual(\n    nextState2.datasets[testCsvDataId].pinnedColumns,\n    [],\n    'should remove from pinned columns'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LOAD_FILES', async t => {\n  drainTasksForTesting();\n\n  const loadFilesSuccessSpy = sinon.spy(VisStateActions, 'loadFilesSuccess');\n  const loadFileErrSpy = sinon.spy(Console, 'warn');\n  const initialState = CloneDeep(InitialState).visState;\n  const mockResults = {\n    data: [\n      {value1: 'a', value2: 1},\n      {value1: 'b', value2: 2}\n    ]\n  };\n  const mockFiles = [\n    {type: 'text/csv', name: 'test-file.csv'},\n    {type: 'text/csv', name: 'test-file-2.csv'}\n  ];\n  // mock async generator\n  async function* run(fileName) {\n    // Sleep for 100ms, see: https://masteringjs.io/tutorials/fundamentals/sleep\n    let percent = 0;\n    await new Promise(resolve => setTimeout(resolve, 100));\n    while (percent < 1) {\n      percent += 1;\n      yield {progress: {percent}, fileName, ...mockResults};\n    }\n  }\n\n  const nextState = reducer(initialState, VisStateActions.loadFiles(mockFiles));\n\n  const tasks = drainTasksForTesting();\n  t.equal(tasks.length, 1, 'should create 1 task');\n  const task1 = tasks[0];\n\n  const expectedTask1 = {\n    type: 'LOAD_FILE_TASK',\n    payload: {\n      file: {type: 'text/csv', name: 'test-file.csv'},\n      fileCache: [],\n      loaders: [],\n      loadOptions: {}\n    }\n  };\n\n  t.comment(JSON.stringify(task1));\n  t.equal(task1.type, expectedTask1.type, 'should create LOAD_FILE_TASK task');\n  t.deepEqual(task1.payload, expectedTask1.payload, 'should create LOAD_FILE_TASK correct payload');\n\n  const expectedFileLoading = {\n    fileCache: [],\n    filesToLoad: [{type: 'text/csv', name: 'test-file-2.csv'}],\n    onFinish: VisStateActions.loadFilesSuccess\n  };\n  const expectedFileLoadingProgress = {\n    'test-file.csv': {percent: 0, message: 'loading...', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 0, message: '', fileName: 'test-file-2.csv', error: null}\n  };\n\n  t.deepEqual(nextState.fileLoading, expectedFileLoading, 'should save fileLoading in state');\n  t.deepEqual(\n    nextState.fileLoadingProgress,\n    expectedFileLoadingProgress,\n    'should save fileLoadingProgress in state'\n  );\n\n  const asyncIterator = run('test-file.csv');\n\n  // test nextFileBatchUpdater\n  const nextState2 = reducer(nextState, succeedTaskInTest(task1, asyncIterator));\n  // test LOAD_FILE_TASK success\n  const [task2, ...more2] = drainTasksForTesting();\n  t.equal(more2.length, 0, 'should ceate 1 task');\n  t.equal(task2.type, 'UNWRAP', 'should return an UNWRAP task');\n  t.ok(task2.payload instanceof Promise, 'task 2 payload should be a Promise');\n\n  t.equal(\n    nextState2.fileLoading,\n    nextState.fileLoading,\n    'fileLoading should not change for the first batch'\n  );\n  t.deepEqual(\n    nextState2.fileLoadingProgress,\n    nextState.fileLoadingProgress,\n    'fileLoadingProgress should not change for the first batch'\n  );\n\n  // test LOAD_FILE_TASK error\n  const err1 = {message: 'error 1'};\n  const nextState2Err = reducer(nextState, errorTaskInTest(task1, err1));\n  const [task2Err, ...more2err] = drainTasksForTesting();\n  t.equal(more2err.length, 0, 'should ceate 1 task');\n  const expectedErrProgress = {\n    'test-file.csv': {percent: 0, message: 'loading...', fileName: 'test-file.csv', error: err1},\n    'test-file-2.csv': {percent: 0, message: '', fileName: 'test-file-2.csv', error: null}\n  };\n  t.deepEqual(\n    nextState2Err.fileLoadingProgress,\n    expectedErrProgress,\n    'should save error to fileLoadingProgress'\n  );\n  loadFileErrSpy.calledWith(err1);\n\n  t.equal(\n    task2Err.type,\n    'DELAY_TASK',\n    'should create a DELAY_TASK task when loadFileErr is triggered'\n  );\n  const nextState3Err = reducer(nextState2Err, succeedTaskInTest(task2Err));\n  const [task3Err, ...more3err] = drainTasksForTesting();\n  t.equal(more3err.length, 0, 'should ceate 1 task');\n  t.equal(task3Err.type, 'LOAD_FILE_TASK', 'should return an LOAD_FILE_TASK');\n  t.deepEqual(\n    task3Err.payload,\n    {\n      file: {type: 'text/csv', name: 'test-file-2.csv'},\n      fileCache: [],\n      loaders: [],\n      loadOptions: {}\n    },\n    'should return an LOAD_FILE_TASK with 2nd file to load'\n  );\n  const expectedErrFileLoadingProgress = {\n    'test-file.csv': {percent: 0, message: 'loading...', fileName: 'test-file.csv', error: err1},\n    'test-file-2.csv': {percent: 0, message: 'loading...', fileName: 'test-file-2.csv', error: null}\n  };\n  t.deepEqual(\n    nextState3Err.fileLoadingProgress,\n    expectedErrFileLoadingProgress,\n    'fileLoadingProgress should update whe calling loadNextfileUpdater'\n  );\n  t.deepEqual(\n    nextState3Err.fileLoading,\n    {\n      fileCache: [],\n      filesToLoad: [],\n      onFinish: VisStateActions.loadFilesSuccess\n    },\n    'fileLoading should not add result to fileCache when error'\n  );\n\n  // UNWRAP Task success\n  const unwrapSuccess = await task2.payload;\n  const resultState3 = reducer(nextState2, succeedTaskInTest(task2, unwrapSuccess));\n\n  // should return another unwrap task\n  const [task3, ...more3] = drainTasksForTesting();\n  t.equal(more3.length, 0, 'should ceate 1 task');\n  t.equal(task3.type, 'UNWRAP', 'should return an UNWRAP task');\n  t.ok(task3.payload instanceof Promise, 'task 3 payload should be a Promise');\n  const expectedFileLoadingProgress3 = {\n    'test-file.csv': {percent: 1, message: 'loading...', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 0, message: '', fileName: 'test-file-2.csv', error: null}\n  };\n  t.equal(\n    resultState3.fileLoading,\n    nextState2.fileLoading,\n    'fileLoading should not change when loadingis not finished'\n  );\n  t.deepEqual(\n    resultState3.fileLoadingProgress,\n    expectedFileLoadingProgress3,\n    'fileLoadingProgress should update with percent'\n  );\n\n  // calling UNWRAP_TASK sucess to create File Process task\n  const unwrapSuccess3 = await task3.payload;\n  const resultState4 = reducer(resultState3, succeedTaskInTest(task3, unwrapSuccess3));\n  const [task4, ...more4] = drainTasksForTesting();\n\n  const expectedFileLoadingProgress4 = {\n    'test-file.csv': {percent: 1, message: 'processing...', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 0, message: '', fileName: 'test-file-2.csv', error: null}\n  };\n  t.equal(resultState3.fileLoading, resultState4.fileLoading, 'fileLoading should be the same');\n  t.deepEqual(\n    resultState4.fileLoadingProgress,\n    expectedFileLoadingProgress4,\n    'fileLoadingProgress should update to reflect processing'\n  );\n\n  t.equal(more4.length, 0, 'should ceate 1 task');\n  const expectedpayload = {\n    content: {progress: {percent: 1}, fileName: 'test-file.csv', ...mockResults},\n    fileCache: []\n  };\n  t.equal(task4.type, 'PROCESS_FILE_CONTENT', 'should return an PROCESS_FILE_CONTENT task');\n  t.deepEqual(\n    task4.payload,\n    expectedpayload,\n    'PROCESS_FILE_CONTENT task payload should be correct'\n  );\n\n  const fileProcessResult = [\n    {\n      data: [],\n      info: {\n        label: 'test_data.csv',\n        format: 'csv'\n      }\n    }\n  ];\n\n  // calling loadFileStepSuccess with file process result\n  const resultState5 = reducer(resultState4, succeedTaskInTest(task4, fileProcessResult));\n\n  const expectedFileLoadingProgress5 = {\n    'test-file.csv': {percent: 1, message: 'Done', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 0, message: '', fileName: 'test-file-2.csv', error: null}\n  };\n  t.deepEqual(\n    resultState5.fileLoadingProgress,\n    expectedFileLoadingProgress5,\n    'fileLoadingProgress should update to show finish'\n  );\n  t.deepEqual(\n    resultState5.fileLoading,\n    {\n      fileCache: fileProcessResult,\n      filesToLoad: [{type: 'text/csv', name: 'test-file-2.csv'}],\n      onFinish: VisStateActions.loadFilesSuccess\n    },\n    'fileLoading should update to add result to fileCache 1'\n  );\n  const [task5, ...more5] = drainTasksForTesting();\n  t.equal(more5.length, 0, 'should ceate 1 task');\n  t.equal(task5.type, 'DELAY_TASK', 'should return an DELAY_TASK');\n\n  // calling delayed task succeed to trigger load next file\n  const resultState6 = reducer(resultState5, succeedTaskInTest(task5));\n  const [task6, ...more6] = drainTasksForTesting();\n  t.equal(more6.length, 0, 'should ceate 1 task');\n  t.equal(task6.type, 'LOAD_FILE_TASK', 'should return an LOAD_FILE_TASK');\n  t.deepEqual(\n    task6.payload,\n    {\n      file: {type: 'text/csv', name: 'test-file-2.csv'},\n      fileCache: fileProcessResult,\n      loaders: [],\n      loadOptions: {}\n    },\n    'should return an LOAD_FILE_TASK with 2nd file to load 2'\n  );\n  const expectedFileLoadingProgress6 = {\n    'test-file.csv': {percent: 1, message: 'Done', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 0, message: 'loading...', fileName: 'test-file-2.csv', error: null}\n  };\n  t.deepEqual(\n    resultState6.fileLoadingProgress,\n    expectedFileLoadingProgress6,\n    'fileLoadingProgress should update whe calling loadNextfileUpdater'\n  );\n  t.deepEqual(\n    resultState6.fileLoading,\n    {\n      fileCache: fileProcessResult,\n      filesToLoad: [],\n      onFinish: VisStateActions.loadFilesSuccess\n    },\n    'fileLoading should update to add result to fileCache 3'\n  );\n  // fast forward so we can test final result\n  const asyncIterator2 = run('test-file-2.csv');\n  const resultState7 = reducer(resultState6, succeedTaskInTest(task6, asyncIterator2));\n  const [task7] = drainTasksForTesting();\n\n  const unwrapSuccess7 = await task7.payload;\n  const resultState8 = reducer(resultState7, succeedTaskInTest(task7, unwrapSuccess7));\n  const [task8] = drainTasksForTesting();\n  const unwrapSuccess8 = await task8.payload;\n  const resultState9 = reducer(resultState8, succeedTaskInTest(task8, unwrapSuccess8));\n  // process task\n  const [task9] = drainTasksForTesting();\n  const file2ProcessResult = [\n    ...fileProcessResult,\n    {\n      data: [{value: 1}],\n      info: {\n        label: 'test_data_2.csv',\n        format: 'csv'\n      }\n    }\n  ];\n  const resultState10 = reducer(resultState9, succeedTaskInTest(task9, file2ProcessResult));\n\n  const expectedFileLoadingProgress10 = {\n    'test-file.csv': {percent: 1, message: 'Done', fileName: 'test-file.csv', error: null},\n    'test-file-2.csv': {percent: 1, message: 'Done', fileName: 'test-file-2.csv', error: null}\n  };\n  t.deepEqual(\n    resultState10.fileLoadingProgress,\n    expectedFileLoadingProgress10,\n    'fileLoadingProgress should update to show finish both files'\n  );\n  t.deepEqual(\n    resultState10.fileLoading,\n    {\n      fileCache: file2ProcessResult,\n      filesToLoad: [],\n      onFinish: VisStateActions.loadFilesSuccess\n    },\n    'fileLoading should update to add 2nd file result to fileCache'\n  );\n  const [task10, ...more10] = drainTasksForTesting();\n  t.equal(more10.length, 0, 'should ceate 1 task');\n  t.equal(task10.type, 'DELAY_TASK', 'should return an DELAY_TASK for onFinish');\n\n  // calling delayed task succeed to trigger load next file\n  const _resultState11 = reducer(resultState10, succeedTaskInTest(task10));\n\n  t.ok(loadFilesSuccessSpy.calledOnce);\n  const expectedArgs = [\n    {data: [], info: {label: 'test_data.csv', format: 'csv'}},\n    {data: [{value: 1}], info: {label: 'test_data_2.csv', format: 'csv'}}\n  ];\n  t.ok(loadFilesSuccessSpy.calledWith(expectedArgs));\n  loadFilesSuccessSpy.restore();\n\n  loadFileErrSpy.restore();\n  t.end();\n});\n\ntest('#visStateReducer -> setLayerAnimationTimeConfig', t => {\n  // change Trip layer isVisible\n  const nextState = reducer(\n    StateWTripGeojson.visState,\n    VisStateActions.setLayerAnimationTimeConfig({\n      timezone: 'America/New_York',\n      timeFormat: 'YYYY-MM-DD',\n      random: 1\n    })\n  );\n\n  t.equal(\n    nextState.animationConfig.timezone,\n    'America/New_York',\n    'should set animationConfig timezone'\n  );\n  t.equal(\n    nextState.animationConfig.timeFormat,\n    'YYYY-MM-DD',\n    'should set animationConfig timeFormat'\n  );\n  t.equal(nextState.animationConfig.random, undefined, 'should not set unknown key');\n  t.end();\n});\n\ntest('#visStateReducer -> setFilterAnimationTimeConfig', t => {\n  // change Trip layer isVisible\n  const nextState = reducer(\n    StateWFilters.visState,\n    VisStateActions.setFilterAnimationTimeConfig(1, {timezone: 'America/New_York'})\n  );\n\n  t.equal(nextState, StateWFilters.visState, 'should not change if is not time filter');\n\n  const nextState1 = reducer(\n    StateWFilters.visState,\n    VisStateActions.setFilterAnimationTimeConfig(0, {timezone: 'America/New_York'})\n  );\n  t.equal(nextState1.filters[0].timezone, 'America/New_York', 'should set filter timeFormat');\n  t.end();\n});\n\ntest('#visStateReducer -> layerFilteredItemsChange', t => {\n  const mockEvent = {\n    id: 'point-layer-1',\n    count: 100\n  };\n  const layer = StateWFiles.visState.layers[0];\n  const _nextState = reducer(\n    StateWFiles.visState,\n    VisStateActions.layerFilteredItemsChange(layer, mockEvent)\n  );\n  const expected = {'point-layer-1': 100};\n\n  t.deepEqual(layer.filteredItemCount, expected, 'should set filteredItemCount on layer');\n\n  t.end();\n});\n\ntest('#visStateReducer -> applyFilterFieldName', t => {\n  const stateToSave = CloneDeep(StateWFilters);\n\n  const oldFilter = stateToSave.visState.filters[0];\n  const datasets = stateToSave.visState.datasets;\n  const {filter: newFilter} = applyFilterFieldName(\n    oldFilter,\n    datasets,\n    oldFilter.dataId,\n    oldFilter.name[0]\n  );\n  t.deepEqual(\n    oldFilter.plotType,\n    newFilter.plotType,\n    'Should not overwrite plotType (by the default empty object)'\n  );\n\n  t.end();\n});\n\nfunction mockStateWithFilterAndIntervalBasedAnimationLayer() {\n  let visState = stateWithTimeFilterAndTripLayer().visState;\n  visState = reducer(visState, VisStateActions.addLayer());\n  const mockedMetaData = {\n    minZoom: 0,\n    maxZoom: 4,\n    fields: [\n      {\n        id: 'Fires',\n        name: 'Fires',\n        type: 'real',\n        analyzerType: 'FLOAT',\n        format: '',\n        filterProps: {\n          fieldType: 'real',\n          domain: [1, 400],\n          domainStops: {\n            z: [0, 1, 2, 3],\n            stops: [\n              [1, 100],\n              [1, 200],\n              [1, 300],\n              [1, 400]\n            ],\n            interpolation: 'interpolate'\n          },\n          domainQuantiles: {\n            z: [0, 1, 2, 3],\n            quantiles: Array.from({length: 4}).map(() => [0, 10])\n          },\n          histogram: null,\n          value: [1, 400],\n          type: 'range',\n          typeOptions: ['range'],\n          gpu: true,\n          step: 1\n        },\n        indexBy: {\n          format: 'x',\n          type: 'timestamp',\n          mappedValue: {\n            1580515200000: 'Fires|1580515200000',\n            1583020800000: 'Fires|1583020800000',\n            1585699200000: 'Fires|1585699200000',\n            1588291200000: 'Fires|1588291200000'\n          },\n          timeDomain: {\n            domain: [1580515200000, 1588291200000],\n            timeSteps: [1580515200000, 1583020800000, 1585699200000, 1588291200000],\n            duration: 1000\n          }\n        }\n      }\n    ],\n    resolutionOffset: 4,\n    targetTimeInterval: TileTimeInterval.DAY,\n    tilesetIndex: undefined,\n    zipUrl: undefined\n  };\n\n  const lastLayerIndex = visState.layers.length - 1;\n  const layer = visState.layers[lastLayerIndex];\n  layer.meta = mockedMetaData;\n  layer.config = {\n    ...visState.layers[lastLayerIndex].config,\n    animation: {\n      domain: visState.animationConfig.domain,\n      timeSteps: visState.animationConfig.domain,\n      duration: 1000,\n      enabled: true,\n      startTime: visState.animationConfig.domain[0]\n    }\n  };\n\n  visState.layers[lastLayerIndex] = layer;\n\n  return visState;\n}\n\ntest('#visStateReducer -> sync with time filter with trip layer', t => {\n  let visState = mockStateWithSyncedFilterAndTripLayer().visState;\n\n  const animatableLayers = getAnimatableVisibleLayers(visState.layers);\n  t.equal(animatableLayers.length, 1, 'Should find 1 animatable layer');\n  t.equal(animatableLayers[0].type, LAYER_TYPES.trip, 'Should find 1 animatable trip layer');\n\n  const originalDomain = [...visState.filters[0].domain];\n\n  // ============\n  // Enable sync\n  // ============\n  visState = syncTimeFilterWithLayerTimelineUpdater(visState, {\n    idx: 0,\n    enable: true\n  });\n\n  let newFilter = visState.filters[0];\n\n  // check syncedWithLayerTimeline\n  t.equal(\n    newFilter.syncedWithLayerTimeline,\n    true,\n    'Should have set syncedWithLayerTimeline to true'\n  );\n\n  // check animation window wasn't updated\n  t.equal(newFilter.animationWindow, visState.filters[0].animationWindow);\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'Should have set syncTimelineMode to SYNC_TIMELINE_MODES.end'\n  );\n\n  // check filter domains\n  t.deepEqual(newFilter.domain, originalDomain, 'Should not change the domain value');\n\n  // check filter value\n  t.deepEqual(\n    newFilter.value,\n    [1564174363000, 1564184336370],\n    'Should have set filter value by combining filter and animationConfig domains'\n  );\n\n  // check animationConfig value\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'Should have set animationConfig value to filter value[0]'\n  );\n\n  visState = setFilterAnimationTimeUpdater(visState, {\n    idx: 0,\n    prop: 'value',\n    value: [1474588800000, 1474588800010]\n  });\n\n  newFilter = visState.filters[0];\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'Animation config current time value should match the filter value newFilter.syncTimelineMode=SYNC_TIMELINE_MODES.end'\n  );\n\n  // update syncTimelineMode\n  visState = setTimeFilterTimelineModeUpdater(visState, {\n    id: newFilter.id,\n    mode: SYNC_TIMELINE_MODES.start\n  });\n\n  newFilter = visState.filters[0];\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.start,\n    'Should have set syncTimelineMode to SYNC_TIMELINE_MODES.start'\n  );\n  // check animation config new value\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'Animation config current time value should match the filter value newFilter.syncTimelineMode=SYNC_TIMELINE_MODES.start'\n  );\n\n  // update filter animation window to INCREMENTAL\n  visState = setFilterAnimationWindowUpdater(visState, {\n    id: newFilter.id,\n    animationWindow: ANIMATION_WINDOW.incremental\n  });\n  newFilter = visState.filters[0];\n\n  // check syncTimelineMode is back to SYNC_TIMELINE_MODES.end\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'SyncTimelineMode should be set to SYNC_TIMELINE_MODES.end when we switch to incremental mode'\n  );\n\n  // check animationConfig currentTime should be set to filter.value[filter.syncTimelineMode]\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'SyncTimelineMode should be set to SYNC_TIMELINE_MODES.end when we switch to incremental mode'\n  );\n\n  // update filter animation window to INCREMENTAL\n  visState = setFilterAnimationWindowUpdater(visState, {\n    id: newFilter.id,\n    animationWindow: ANIMATION_WINDOW.interval\n  });\n  newFilter = visState.filters[0];\n\n  // check syncTimelineMode should stay the same\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'SyncTimelineMode should be set to SYNC_TIMELINE_MODES.end when we switch to incremental mode'\n  );\n\n  // check animationConfig currentTime value should stay the same\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'SyncTimelineMode should remain the same with interval mode'\n  );\n\n  // ============\n  // Disable sync\n  // ============\n\n  visState = syncTimeFilterWithLayerTimelineUpdater(visState, {\n    idx: 0,\n    enable: false\n  });\n\n  newFilter = visState.filters[0];\n\n  t.equal(\n    newFilter.animationWindow,\n    ANIMATION_WINDOW.interval,\n    'Should keep the same filter animationWindow value'\n  );\n\n  // check syncedWithLayerTimeline\n  t.equal(\n    newFilter.syncedWithLayerTimeline,\n    false,\n    'Should have set syncedWithLayerTimeline to false'\n  );\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'Should have set syncTimelineMode to end (1)'\n  );\n\n  // check filter domains\n  t.deepEqual(newFilter.domain, originalDomain, 'Should not change the domain value');\n\n  // check filter value\n  t.deepEqual(newFilter.value, newFilter.domain, 'Should have set filter value to match domain');\n\n  // check animationConfig value\n  t.equal(\n    visState.animationConfig.currentTime,\n    visState.animationConfig.domain[0],\n    'Should have set animationConfig value to filter value[0]'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> sync with time filter with hextile layer', t => {\n  let visState = mockStateWithFilterAndIntervalBasedAnimationLayer();\n  const animatableLayers = getAnimatableVisibleLayers(visState.layers);\n  t.equal(animatableLayers.length, 2, 'Should find 1 animatable layer');\n  t.equal(animatableLayers[0].type, LAYER_TYPES.trip, 'Should find 1 animatable trip layer');\n\n  const originalDomain = [...visState.filters[0].domain];\n\n  // ============\n  // Enable sync\n  // ============\n  visState = syncTimeFilterWithLayerTimelineUpdater(visState, {\n    idx: 0,\n    enable: true\n  });\n\n  let newFilter = visState.filters[0];\n\n  // check syncedWithLayerTimeline\n  t.equal(\n    newFilter.syncedWithLayerTimeline,\n    true,\n    'Should have set syncedWithLayerTimeline to true'\n  );\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'Should have set syncTimelineMode to SYNC_TIMELINE_MODES.end (1)'\n  );\n\n  // check animation window to interval\n  t.equal(\n    newFilter.animationWindow,\n    ANIMATION_WINDOW.interval,\n    'Should have set filter animation window to interval'\n  );\n\n  // check plotType interval to match hextile interval\n  t.equal(\n    newFilter.plotType.interval,\n    INTERVAL['1-day'],\n    'Should have set plotType interval to 1-day'\n  );\n\n  // check filter domains\n  t.deepEqual(newFilter.domain, originalDomain, 'Should not change the domain value');\n\n  // check filter value\n  t.deepEqual(\n    newFilter.value,\n    [1421348739000, 1421348739000],\n    'Should have set filter value to the first interval step'\n  );\n\n  // check animationConfig value\n  t.equal(\n    visState.animationConfig.currentTime,\n    newFilter.value[newFilter.syncTimelineMode],\n    'Should have set animationConfig value to filter value[0]'\n  );\n\n  // ============\n  // Disable sync\n  // ============\n  visState = syncTimeFilterWithLayerTimelineUpdater(visState, {\n    idx: 0,\n    enable: false\n  });\n\n  newFilter = visState.filters[0];\n\n  // check syncedWithLayerTimeline\n  t.equal(\n    newFilter.syncedWithLayerTimeline,\n    false,\n    'Should have set syncedWithLayerTimeline to false'\n  );\n\n  // check syncTimelineMode\n  t.equal(\n    newFilter.syncTimelineMode,\n    SYNC_TIMELINE_MODES.end,\n    'Should have set syncTimelineMode to end (1)'\n  );\n\n  // check filter domains\n  t.deepEqual(newFilter.domain, originalDomain, 'Should not change the domain value');\n\n  // check filter value\n  t.deepEqual(newFilter.value, newFilter.domain, 'Should have set filter value to match domain');\n\n  // check animationConfig value\n  t.equal(\n    visState.animationConfig.currentTime,\n    visState.animationConfig.domain[0],\n    'Should have set animationConfig value to filter value[0]'\n  );\n\n  t.end();\n});\n\ntest('VisStateUpdater -> prepareStateForDatasetReplace', t => {\n  const oldTooltipConfigFields =\n    StateWFilters.visState.interactionConfig.tooltip.config.fieldsToShow;\n  const dataIdToUse = 'taro_and_blue';\n  const nextState = prepareStateForDatasetReplace(\n    StateWFilters.visState,\n    testCsvDataId,\n    dataIdToUse\n  );\n\n  // layers\n  t.equal(nextState.layers.length, 1, 'should keep 1 layer');\n  t.equal(nextState.layers[0], StateWFilters.visState.layers[1], 'should keep 1 layer');\n  t.equal(nextState.layerToBeMerged.length, 1, 'should move 1 layer to layerToBeMerged');\n  t.equal(\n    nextState.layerToBeMerged[0].id,\n    StateWFilters.visState.layers[0].id,\n    'should move 1 layer to layerToBeMerged'\n  );\n  t.equal(nextState.layerToBeMerged[0].config.dataId, dataIdToUse, 'should replace layer dataId');\n\n  // filers\n  t.equal(nextState.filters.length, 1, 'should keep 1 filter');\n  t.equal(nextState.filters[0], StateWFilters.visState.filters[1], 'should keep 1 filter');\n  t.equal(nextState.filterToBeMerged.length, 1, 'should move 1 filter to filterToBeMerged');\n  t.equal(\n    nextState.filterToBeMerged[0].id,\n    StateWFilters.visState.filters[0].id,\n    'should move 1 filter to filterToBeMerged'\n  );\n\n  t.deepEqual(nextState.filterToBeMerged[0].dataId, [dataIdToUse], 'should replace filter dataId');\n\n  // preserveLayerOrder\n  t.deepEqual(\n    nextState.layerOrder,\n    ['geojson-1', 'point-0'],\n    'should not remove layer from layer order'\n  );\n  t.deepEqual(\n    nextState.preserveLayerOrder,\n    ['geojson-1', 'point-0'],\n    'should save preserved layer order'\n  );\n\n  // interactionConfig\n  t.deepEqual(\n    nextState.interactionConfig.tooltip.config.fieldsToShow,\n    {[testGeoJsonDataId]: oldTooltipConfigFields[testGeoJsonDataId]},\n    'Should only keep geojson dataset tooltip'\n  );\n  t.deepEqual(\n    nextState.interactionToBeMerged,\n    {\n      tooltip: {enabled: true, fieldsToShow: {[dataIdToUse]: oldTooltipConfigFields[testCsvDataId]}}\n    },\n    'should move tooltip config to interactionToBeMerged'\n  );\n\n  // preserveDatasetOrder\n  t.deepEqual(\n    nextState.preserveDatasetOrder,\n    [dataIdToUse, testGeoJsonDataId],\n    'should save dataset id to preserveDatasetOrder'\n  );\n\n  // preserveFilterOrder\n  t.deepEqual(\n    nextState.preserveFilterOrder,\n    ['time-0', 'RATE-1'],\n    'should save filter id to preserveFilterOrder'\n  );\n\n  t.end();\n});\n\ntest('VisStateUpdater -> addLayer with empty column', t => {\n  const initialState = StateWFiles.visState;\n  const oldLayers = initialState.layers;\n  let nextState;\n  t.doesNotThrow(() => {\n    nextState = reducer(\n      initialState,\n      VisStateActions.addLayer({\n        type: 'point',\n        id: 'taro-xxx',\n        config: {\n          dataId: testCsvDataId\n          // no column\n        }\n      })\n    );\n  }, 'should not throw error when add layer with empty column');\n\n  t.equal(nextState.layers.length, oldLayers.length + 1, 'should create 1 layer');\n  const newLayer = nextState.layers[nextState.layers.length - 1];\n\n  t.equal(newLayer.id, 'taro-xxx', 'newlayer should have correct id');\n  t.equal(newLayer.type, 'point', 'newlayer should have correct type');\n  t.deepEqual(\n    newLayer.config.columns.lat,\n    {value: null, fieldIdx: -1},\n    'newlayer column should be value: null'\n  );\n\n  t.deepEqual(\n    nextState.layerData[nextState.layerData.length - 1],\n    {},\n    'newlayer layerData should be empty'\n  );\n\n  t.end();\n});\n\ntest('VisStateUpdater -> applyLayerConfig', t => {\n  const initialState = StateWFiles.visState;\n  const oldLayers = initialState.layers;\n\n  const oldLayerIndex = 0;\n  const oldLayer = oldLayers[oldLayerIndex];\n  const oldLayerId = oldLayer.id;\n  const {schema} = initialState;\n  const layerToJson = layer =>\n    schema.getConfigToSave({\n      visState: {layers: [layer], layerOrder: [layer.id]}\n    }).config.visState.layers?.[0];\n  const oldLayerDataset = initialState.datasets[oldLayer.config.dataId];\n  const transformConfig = transform => transform(layerToJson(oldLayer, schema));\n  const getUpdatedLayerJson = state => layerToJson(state.layers[oldLayerIndex], schema);\n\n  let nextState;\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.config.isVisible = false;\n        layer.config.label = 'New label';\n        return layer;\n      })\n    )\n  );\n  t.equal(getUpdatedLayerJson(nextState).config.isVisible, false, 'should change isVisible');\n  t.equal(getUpdatedLayerJson(nextState).config.label, 'New label', 'should change label');\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.config.columns = {\n          lng: 'gps_data.lat',\n          lat: 'gps_data.lng'\n        };\n        return layer;\n      })\n    )\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).config.columns.lng,\n    'gps_data.lat',\n    'should change lng column'\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).config.columns.lat,\n    'gps_data.lng',\n    'should change lat column'\n  );\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.config.textLabel = {field: {name: 'gps_data.types', type: 'string'}};\n        return layer;\n      })\n    )\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).config.textLabel?.[0]?.field?.name,\n    'gps_data.types',\n    'should change text label'\n  );\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.visualChannels.colorField = {name: 'gps_data.lat', type: 'real'};\n        layer.visualChannels.colorScale = 'quantize';\n        return layer;\n      })\n    )\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).visualChannels.colorField?.name,\n    'gps_data.lat',\n    'should change visualChannel colorField'\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).visualChannels.colorScale,\n    'quantize',\n    'should change visualChannel colorScale'\n  );\n\n  t.throws(() => {\n    validateLayerWithData(\n      oldLayerDataset,\n      transformConfig(layer => {\n        layer.config.textLabel = {field: {name: 'INVALID', type: 'string'}};\n        return layer;\n      }),\n      initialState.layerClasses,\n      {throwOnError: true}\n    );\n  }, 'validation should throw error for invalid text label');\n\n  t.throws(() => {\n    validateLayerWithData(\n      oldLayerDataset,\n      transformConfig(layer => {\n        layer.config.columns = {\n          lng: 'INVALID COLUMN'\n        };\n        return layer;\n      }),\n      initialState.layerClasses,\n      {throwOnError: true}\n    );\n  }, 'validation should throw error for invalid columns');\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.config.dataId = 'INVALID DATA ID';\n        return layer;\n      })\n    )\n  );\n  t.equal(\n    getUpdatedLayerJson(nextState).config.dataId,\n    oldLayer.config.dataId,\n    'should not change data id to invalid one'\n  );\n  t.deepEqual(\n    getUpdatedLayerJson(nextState),\n    layerToJson(oldLayer, schema),\n    'should not change on invalid data id'\n  );\n\n  nextState = reducer(\n    initialState,\n    VisStateActions.applyLayerConfig(\n      oldLayerId,\n      transformConfig(layer => {\n        layer.type = '3D';\n        return layer;\n      })\n    )\n  );\n  t.equal(getUpdatedLayerJson(nextState).type, '3D', 'should change layer type');\n\n  t.end();\n});\n\ntest('#VisStateUpdater -> addEffect', t => {\n  const initialState = InitialState.visState;\n\n  let nextState = reducer(initialState, VisStateActions.addEffect({}));\n\n  t.equal(nextState.effects.length, 1, 'should add a default effect');\n\n  const expectedEffect = {\n    type: 'ink',\n    isEnabled: true,\n    isConfigActive: true,\n    parameters: {strength: 0.25}\n  };\n\n  cmpEffects(t, expectedEffect, nextState.effects[0]);\n\n  nextState = reducer(\n    nextState,\n    VisStateActions.addEffect({\n      id: 'e_shadow',\n      type: LIGHT_AND_SHADOW_EFFECT.type,\n      parameters: {timestamp: 1689280466362, timezone: 'UTC'}\n    })\n  );\n\n  t.equal(nextState.effects.length, 2, 'should add second effect');\n\n  nextState = reducer(\n    nextState,\n    VisStateActions.addEffect({\n      type: LIGHT_AND_SHADOW_EFFECT.type\n    })\n  );\n\n  t.equal(\n    nextState.effects.length,\n    2,\n    `shouldn't add second ${LIGHT_AND_SHADOW_EFFECT.name} effect`\n  );\n\n  const expectedEffect2 = {\n    id: 'e_shadow',\n    type: 'lightAndShadow',\n    isConfigActive: true,\n    isEnabled: true,\n    parameters: {\n      timestamp: 1689280466362,\n      timezone: 'UTC',\n      timeMode: 'pick',\n      shadowIntensity: 0.5,\n      shadowColor: [0, 0, 0],\n      sunLightColor: [255, 255, 255],\n      sunLightIntensity: 1,\n      ambientLightColor: [255, 255, 255],\n      ambientLightIntensity: 1\n    }\n  };\n\n  cmpEffects(t, expectedEffect2, nextState.effects[1], {id: true});\n\n  t.equal(\n    nextState.effects[0].isConfigActive,\n    false,\n    \"first effect's configurator should be collapsed\"\n  );\n\n  nextState = reducer(nextState, VisStateActions.addEffect({}));\n\n  t.equal(nextState.effectOrder.length, 3, 'should be 3 ids in effectOrder');\n\n  t.equal(nextState.effectOrder[0], 'e_shadow', 'shadow effect should stay as first effect');\n\n  t.end();\n});\n\ntest('#VisStateUpdater -> updateEffect', t => {\n  const initialState = InitialState.visState;\n\n  let nextState = reducer(initialState, VisStateActions.addEffect({id: 'e_1'}));\n  nextState = reducer(nextState, VisStateActions.addEffect({}));\n  nextState = reducer(nextState, VisStateActions.addEffect({id: 'e_3'}));\n\n  const expectedEffect = {\n    id: 'e_1',\n    type: 'ink',\n    isEnabled: false,\n    isConfigActive: false,\n    parameters: {\n      strength: 0.06\n    }\n  };\n\n  nextState = reducer(nextState, VisStateActions.updateEffect('e_1', expectedEffect));\n\n  cmpEffects(t, expectedEffect, nextState.effects[0], {id: true});\n\n  // non-existing id, shouldn't fail\n  nextState = reducer(nextState, VisStateActions.updateEffect('fake_id', {}));\n\n  // update effect id\n  nextState = reducer(nextState, VisStateActions.updateEffect('e_1', {id: 'e_2'}));\n  cmpEffects(t, {...expectedEffect, id: 'e_2'}, nextState.effects[0], {id: true});\n  t.equal(nextState.effectOrder[2], 'e_2', 'Effect id should be updated');\n\n  // update effect to an existing effect id\n  nextState = reducer(nextState, VisStateActions.updateEffect('e_2', {id: 'e_3'}));\n  cmpEffects(t, {...expectedEffect, id: 'e_2'}, nextState.effects[0], {id: true});\n  t.equal(nextState.effectOrder[2], 'e_2', \"Effect id shouldn't be updated\");\n\n  t.end();\n});\n\ntest('#VisStateUpdater -> addEffect: invalid effect parameters', t => {\n  const initialState = InitialState.visState;\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.addEffect({\n      id: 'e_1',\n      type: 'lightAndShadow',\n      parameters: {\n        timestamp: 'x',\n        shadowIntensity: 2,\n        sunLightIntensity: [0, 1],\n        shadowColor: [0, 1],\n        sunLightColor: 2,\n        ambientLightColor: [300, 128, 128],\n        timezone: 'Not a timezone'\n      }\n    })\n  );\n\n  const expectedEffect = {\n    id: 'e_1',\n    type: 'lightAndShadow',\n    parameters: {\n      timestamp: 0,\n      timeMode: 'pick',\n      shadowIntensity: 1,\n      sunLightIntensity: 1,\n      shadowColor: [0, 0, 0],\n      sunLightColor: [255, 255, 255],\n      ambientLightColor: [255, 128, 128],\n      ambientLightIntensity: 1,\n      timezone: 'Not a timezone'\n    }\n  };\n\n  cmpEffects(t, expectedEffect, nextState.effects[0], {id: true});\n\n  t.end();\n});\n\ntest('#VisStateUpdater -> reorderEffect', t => {\n  const initialState = InitialState.visState;\n\n  let nextState = reducer(initialState, VisStateActions.addEffect({id: 'e_1'}));\n  nextState = reducer(nextState, VisStateActions.addEffect({id: 'e_2'}));\n  nextState = reducer(nextState, VisStateActions.addEffect({id: 'e_3'}));\n\n  t.deepEqual(nextState.effectOrder, ['e_3', 'e_2', 'e_1'], 'effect order should be correct order');\n\n  nextState = reducer(nextState, VisStateActions.reorderEffect(['e_2', 'e_1', 'e_3']));\n\n  t.deepEqual(\n    nextState.effectOrder,\n    ['e_2', 'e_1', 'e_3'],\n    'effect order should be correct after '\n  );\n\n  t.end();\n});\n\ntest('#VisStateUpdater -> removeEffect', t => {\n  const initialState = InitialState.visState;\n\n  let nextState = reducer(initialState, VisStateActions.addEffect({id: 'e_1'}));\n  nextState = reducer(nextState, VisStateActions.addEffect({id: 'e_2'}));\n  nextState = reducer(nextState, VisStateActions.addEffect({id: 'e_3'}));\n\n  nextState = reducer(nextState, VisStateActions.removeEffect('e_2'));\n\n  t.deepEqual(nextState.effects.length, 2, 'effect should be removed');\n\n  t.deepEqual(nextState.effects[0].id, 'e_1', 'e_1 effect should be available');\n  t.deepEqual(nextState.effects[1].id, 'e_3', 'e_3 effect should be available');\n\n  // bad id, shouldn't fail\n  nextState = reducer(nextState, VisStateActions.removeEffect('e_2'));\n\n  t.equal(nextState.effectOrder.length, 2, 'should be 2 ids in effectOrder');\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. enable custom palette', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: true}\n    })\n  );\n\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA']\n      },\n      colorRangeConfig: {\n        type: 'all',\n        colorBlindSafe: false,\n        steps: 6,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    nextState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should set customPalette when one clicks Custom Palette and Confirm button'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. custom palette - delete color item', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const customPalette = {\n    customPalette: {\n      colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA'],\n      name: 'color.customPalette',\n      type: 'custom',\n      category: 'Custom'\n    }\n  };\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: true}\n    })\n  );\n\n  const deleteState = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', customPalette)\n  );\n\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA']\n      },\n      colorRangeConfig: {\n        type: 'all',\n        colorBlindSafe: false,\n        steps: 3,\n        reversed: false,\n        custom: true,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    deleteState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should set customPalette when one deletes a color item from Custom Palette'\n  );\n\n  let confirmState = reducer(\n    deleteState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: false}\n    })\n  );\n\n  // simulate Confirm button action\n  confirmState = reducer(\n    confirmState,\n    VisStateActions.layerVisConfigChange(pointLayer, {\n      colorRange: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA']\n      }\n    })\n  );\n\n  const reverseState = reducer(\n    confirmState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {reversed: true}\n    })\n  );\n\n  const expectedReversedColorUI = {\n    ...expectedColorUI,\n    colorRange: {\n      ...expectedColorUI.colorRange,\n      colorRangeConfig: {\n        type: 'all',\n        colorBlindSafe: false,\n        steps: 3,\n        reversed: true,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  t.deepEqual(\n    reverseState.layers[0].config.colorUI,\n    expectedReversedColorUI,\n    'should set customPalette when one deletes a color item from Custom Palette and reverse it'\n  );\n\n  const expectedReversedVisConfigColorRange = {\n    category: 'Custom',\n    colors: ['#E6FAFA', '#AAD7D9', '#6BB5B9'],\n    name: 'color.customPalette',\n    reversed: true,\n    type: 'custom'\n  };\n\n  t.deepEqual(\n    reverseState.layers[0].config.visConfig.colorRange,\n    expectedReversedVisConfigColorRange,\n    'should set visConfig.ColorRange when one deletes a color item from Custom Palette and reverse it'\n  );\n\n  t.end();\n});\n\ntest('#visStateReducer -> LAYER_COLOR_UI_CHANGE. custom palette - select new steps', t => {\n  const initialState = CloneDeep(StateWFilesFiltersLayerColor.visState);\n  const pointLayer = initialState.layers[0];\n\n  const customPalette = {\n    customPalette: {\n      colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA'],\n      name: 'color.customPalette',\n      type: 'custom',\n      category: 'Custom'\n    }\n  };\n\n  const nextState = reducer(\n    initialState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: true}\n    })\n  );\n\n  const deleteState = reducer(\n    nextState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', customPalette)\n  );\n\n  let confirmState = reducer(\n    deleteState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {custom: false}\n    })\n  );\n\n  // simulate Confirm button action\n  confirmState = reducer(\n    confirmState,\n    VisStateActions.layerVisConfigChange(pointLayer, {\n      colorRange: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA']\n      }\n    })\n  );\n\n  const stepsState = reducer(\n    confirmState,\n    VisStateActions.layerColorUIChange(pointLayer, 'colorRange', {\n      colorRangeConfig: {steps: 4}\n    })\n  );\n\n  const expectedColorUI = {\n    color: DEFAULT_COLOR_UI,\n    strokeColorRange: DEFAULT_COLOR_UI,\n    colorRange: {\n      ...DEFAULT_COLOR_UI,\n      customPalette: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#6BB5B9', '#AAD7D9', '#E6FAFA']\n      },\n      colorRangeConfig: {\n        type: 'all',\n        colorBlindSafe: false,\n        steps: 4,\n        reversed: false,\n        custom: false,\n        customBreaks: false\n      }\n    }\n  };\n\n  const expectedVisConfigColorRange = {\n    colors: ['#00939C', '#8BC6C9', '#EB9373', '#C22E00'],\n    name: 'Uber Viz Diverging',\n    type: 'diverging',\n    category: 'Uber'\n  };\n\n  t.deepEqual(\n    stepsState.layers[0].config.colorUI,\n    expectedColorUI,\n    'should set correct step in colorRangeConfig when one deletes a color item from Custom Palette and select new steps'\n  );\n\n  t.deepEqual(\n    stepsState.layers[0].config.visConfig.colorRange,\n    expectedVisConfigColorRange,\n    'should set predefined palette in visConfig.colorRange when one deletes a color item from Custom Palette and select new steps'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/schemas/dataset-schema-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport SchemaManager from '@kepler.gl/schemas';\n\n// fixtures\nimport {\n  savedStateV0,\n  expectedInfo0,\n  expectedFields0,\n  expectedFields1,\n  expectedInfo1\n} from 'test/fixtures/state-saved-v0';\nimport {savedStateV1, v0ExpectedInfo, v0ExpectedFields} from 'test/fixtures/state-saved-v1-1';\nimport {stateSavedV1_2, v1expectedInfo_2, v1expectedFields_2} from 'test/fixtures/state-saved-v1-2';\n\n/* eslint-disable max-statements */\ntest('#DatasetSchema -> SchemaManager.parseSavedData', t => {\n  const dataSaved = cloneDeep(savedStateV0).datasets;\n  const parsedValid = SchemaManager.parseSavedData(dataSaved);\n\n  const expectedRows0 = dataSaved[0].data.allData;\n\n  const expectedDataset0 = {\n    info: expectedInfo0,\n    data: {\n      fields: expectedFields0,\n      rows: expectedRows0\n    }\n  };\n\n  const expectedRows1 = dataSaved[1].data.allData;\n\n  const expectedDataset1 = {\n    info: expectedInfo1,\n    data: {\n      fields: expectedFields1,\n      rows: expectedRows1\n    }\n  };\n\n  t.equal(parsedValid.length, 2, 'should have 2 datasets');\n  t.deepEqual(parsedValid[0], expectedDataset0, 'should parse dataset correctly');\n  t.deepEqual(parsedValid[0].info, expectedInfo0, 'should parse info correctly');\n  t.deepEqual(parsedValid[0].data.fields, expectedFields0, 'should parse fields correctly');\n  t.deepEqual(parsedValid[0].data.rows, expectedRows0, 'should parse rows correctly');\n\n  t.deepEqual(parsedValid[1], expectedDataset1, 'should parse dataset correctly');\n  t.deepEqual(parsedValid[1].info, expectedInfo1, 'should parse info correctly');\n  t.deepEqual(parsedValid[1].data.fields, expectedFields1, 'should parse fields correctly');\n  t.deepEqual(parsedValid[1].data.rows, expectedRows1, 'should parse rows correctly');\n\n  t.end();\n});\n/* eslint-enable max-statements */\n\ntest('#DatasetSchema -> SchemaManager.parseSavedData.v1', t => {\n  const dataSaved = cloneDeep(savedStateV1).datasets;\n  const parsedValid = SchemaManager.parseSavedData(dataSaved);\n\n  const expectedRows = dataSaved[0].data.allData;\n\n  const expectedDataset = {\n    info: v0ExpectedInfo,\n    data: {\n      fields: v0ExpectedFields,\n      rows: expectedRows\n    }\n  };\n\n  t.equal(parsedValid.length, 1, 'should have 1 dataset');\n\n  t.deepEqual(parsedValid[0], expectedDataset, 'should parse dataset correctly');\n  t.deepEqual(parsedValid[0].info, v0ExpectedInfo, 'should parse info correctly');\n  t.deepEqual(parsedValid[0].data.fields, v0ExpectedFields, 'should parse fields correctly');\n  t.deepEqual(parsedValid[0].data.rows, expectedRows, 'should parse rows correctly');\n\n  t.end();\n});\n\ntest('#DatasetSchema -> SchemaManager.parseSavedData.v1 with ts', t => {\n  const dataSaved = cloneDeep(stateSavedV1_2).datasets;\n  const parsedValid = SchemaManager.parseSavedData(dataSaved);\n\n  const expectedRows = dataSaved[0].data.allData;\n  const expectedDataset = {\n    info: v1expectedInfo_2,\n    data: {\n      fields: v1expectedFields_2,\n      rows: expectedRows\n    }\n  };\n\n  t.equal(parsedValid.length, 1, 'should have 1 dataset');\n\n  t.deepEqual(parsedValid[0], expectedDataset, 'should parse dataset correctly');\n\n  t.deepEqual(parsedValid[0].info, v1expectedInfo_2, 'should parse info correctly');\n\n  t.equal(\n    parsedValid[0].data.fields.length,\n    v1expectedFields_2.length,\n    'should have same number of fields'\n  );\n\n  parsedValid[0].data.fields.forEach((actualField, i) => {\n    t.deepEqual(\n      actualField,\n      v1expectedFields_2[i],\n      `fields ${actualField.name} should be the same`\n    );\n  });\n\n  t.deepEqual(parsedValid[0].data.rows, expectedRows, 'should parse rows correctly');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/schemas/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// test schemas\nimport './vis-state-schema-test';\nimport './map-state-schema-test';\nimport './map-style-schema-test';\nimport './dataset-schema-test';\nimport './schema-conversion-test';\n"
  },
  {
    "path": "test/node/schemas/map-state-schema-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport SchemaManager from '@kepler.gl/schemas';\nimport {InitialState} from 'test/helpers/mock-state';\n\ntest('#mapStateSchema -> v1 -> save load mapState', t => {\n  const initialState = cloneDeep(InitialState);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const msToSave = savedState.config.mapState;\n  const msLoaded = SchemaManager.parseSavedConfig(savedState).mapState;\n\n  t.deepEqual(\n    Object.keys(msToSave),\n    [\n      'bearing',\n      'dragRotate',\n      'latitude',\n      'longitude',\n      'pitch',\n      'zoom',\n      'isSplit',\n      'isViewportSynced',\n      'isZoomLocked',\n      'splitMapViewports'\n    ],\n    'mapState should have all 6 entries'\n  );\n\n  const expected = {\n    pitch: 0,\n    bearing: 0,\n    latitude: 37.75043,\n    longitude: -122.34679,\n    zoom: 9,\n    dragRotate: false,\n    isSplit: false,\n    isViewportSynced: true,\n    isZoomLocked: false,\n    splitMapViewports: []\n  };\n\n  t.deepEqual(msToSave, expected, 'save mapState should be current');\n  t.deepEqual(msLoaded, expected, 'load mapState should be current');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/schemas/map-style-schema-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport SchemaManager from '@kepler.gl/schemas';\nimport {\n  InitialState,\n  StateWCustomMapStyleLocal,\n  StateWCustomMapStyleManaged,\n  StateWCustomMapStyleLegacy\n} from 'test/helpers/mock-state';\n\ntest('#mapStyleSchema -> v1 -> save load mapStyle', t => {\n  const initialState = cloneDeep(InitialState);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const msToSave = savedState.config.mapStyle;\n  const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle;\n\n  t.deepEqual(\n    Object.keys(msToSave),\n    [\n      'styleType',\n      'topLayerGroups',\n      'visibleLayerGroups',\n      'threeDBuildingColor',\n      'backgroundColor',\n      'mapStyles'\n    ],\n    'mapStyle should have all 6 entries'\n  );\n\n  const expectedSaved = {\n    styleType: 'dark-matter',\n    topLayerGroups: {},\n    visibleLayerGroups: {},\n    mapStyles: {},\n    threeDBuildingColor: [209, 206, 199],\n    backgroundColor: [0, 0, 0]\n  };\n\n  const expectedLoaded = {\n    styleType: 'dark-matter',\n    topLayerGroups: {},\n    visibleLayerGroups: {},\n    threeDBuildingColor: [209, 206, 199],\n    backgroundColor: [0, 0, 0]\n  };\n\n  t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current');\n  t.deepEqual(msLoaded, expectedLoaded, 'loaded mapStyle should be current');\n  t.end();\n});\n\ntest('#mapStyleSchema -> v1 -> save load mapStyle with custom local style', t => {\n  const initialState = cloneDeep(StateWCustomMapStyleLocal);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const msToSave = savedState.config.mapStyle;\n  const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle;\n\n  const expectedSaved = {\n    styleType: 'smoothie_the_cat',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      road: true\n    },\n    threeDBuildingColor: [1, 2, 3],\n    backgroundColor: [0, 0, 0],\n    mapStyles: {\n      smoothie_the_cat: {\n        id: 'smoothie_the_cat',\n        accessToken: 'secret_token',\n        label: 'Smoothie the Cat',\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        custom: 'LOCAL',\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      }\n    }\n  };\n\n  t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current');\n  t.deepEqual(msLoaded, expectedSaved, 'loaded mapStyle should be current');\n  t.end();\n});\n\ntest('#mapStyleSchema -> v1 -> save load mapStyle with custom managed style', t => {\n  const initialState = cloneDeep(StateWCustomMapStyleManaged);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const msToSave = savedState.config.mapStyle;\n  const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle;\n\n  const expectedSaved = {\n    styleType: 'smoothie_the_cat',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      road: true\n    },\n    threeDBuildingColor: [1, 2, 3],\n    backgroundColor: [0, 0, 0],\n    mapStyles: {\n      smoothie_the_cat: {\n        id: 'smoothie_the_cat',\n        accessToken: 'secret_token',\n        label: 'Smoothie the Cat',\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        custom: 'MANAGED',\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      }\n    }\n  };\n\n  t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current');\n  t.deepEqual(msLoaded, expectedSaved, 'loaded mapStyle should be current');\n  t.end();\n});\n\ntest('#mapStyleSchema -> v1 -> save load mapStyle with custom local style (custom: true (legacy backwards support))', t => {\n  const initialState = cloneDeep(StateWCustomMapStyleLegacy);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const msToSave = savedState.config.mapStyle;\n  const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle;\n\n  const expectedSaved = {\n    styleType: 'smoothie_the_cat',\n    topLayerGroups: {},\n    visibleLayerGroups: {\n      label: true,\n      road: true\n    },\n    threeDBuildingColor: [1, 2, 3],\n    backgroundColor: [0, 0, 0],\n    mapStyles: {\n      smoothie_the_cat: {\n        id: 'smoothie_the_cat',\n        accessToken: 'secret_token',\n        label: 'Smoothie the Cat',\n        icon: 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false',\n        custom: true,\n        url: 'mapbox://styles/shanhe/smoothie.the.cat'\n      }\n    }\n  };\n\n  t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current');\n  t.deepEqual(msLoaded, expectedSaved, 'loaded mapStyle should be current');\n  t.end();\n});\n"
  },
  {
    "path": "test/node/schemas/schema-conversion-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport SchemaManager from '@kepler.gl/schemas';\nimport {cmpParsedAppConfigs} from 'test/helpers/comparison-utils';\nimport {logStep} from '../../../scripts/log';\n\nimport {\n  savedConfigV0 as savedConfig0,\n  parsedConfigV0 as parsedConfig0\n} from 'test/fixtures/config_v0_arc_cluster_point';\n\nimport {\n  savedConfigV0 as savedConfig1,\n  parsedConfigV0 as parsedConfig1\n} from 'test/fixtures/config_v0_geojson_poly_fill_ele';\n\nimport {\n  savedConfigV0 as savedConfig2,\n  parsedConfigV0 as parsedConfig2\n} from 'test/fixtures/config_v0_geojson_point';\n\nimport {\n  savedConfigV0 as savedConfig3,\n  parsedConfigV0 as parsecConfig3\n} from 'test/fixtures/config_v0_geojson_polygon';\n\nconst TEST_CASES = [\n  {\n    name: 'v0 -> v1',\n    cases: [\n      {\n        name: 'load config with arc cluster point layer',\n        saved: savedConfig0,\n        parsed: parsedConfig0\n      },\n      {\n        name: 'load config with geojson layer',\n        saved: savedConfig1,\n        parsed: parsedConfig1\n      },\n      {\n        name: 'load config with geojson contains points',\n        saved: savedConfig2,\n        parsed: parsedConfig2\n      },\n      {\n        name: 'load config with geojson contains 3d and stroked polygon',\n        saved: savedConfig3,\n        parsed: parsecConfig3\n      }\n    ]\n  }\n];\n\ntest('#appSchema -> Convert Saved Configs', t => {\n  TEST_CASES.forEach(({name, cases}) => {\n    logStep(`---> test: ${name}`);\n\n    cases.forEach(cs => {\n      logStep(`------> test: ${cs.name}`);\n\n      const parsed = SchemaManager.parseSavedConfig(cs.saved);\n      cmpParsedAppConfigs(t, cs.parsed, parsed, {name: cs.name});\n    });\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/schemas/vis-state-schema-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport {cmpFilters, cmpSavedLayers} from 'test/helpers/comparison-utils';\nimport SchemaManager, {CURRENT_VERSION, visStateSchema} from '@kepler.gl/schemas';\n\nimport {\n  StateWArcNeighbors,\n  StateWEffects,\n  StateWFiles,\n  StateWFilesFiltersLayerColor,\n  StateWLayerCustomColorBreaks,\n  StateWTooltipFormat,\n  StateWTripGeojson,\n  StateWTripTable,\n  expectedSavedLayer0,\n  expectedLoadedLayer0,\n  expectedSavedLayer1,\n  expectedLoadedLayer1,\n  expectedSavedLayer2,\n  expectedLoadedLayer2,\n  expectedSavedTripLayer,\n  mockColorRange,\n  testCsvDataId,\n  testGeoJsonDataId\n} from 'test/helpers/mock-state';\nimport {keplerGlReducerCore as keplerGlReducer} from '@kepler.gl/reducers';\nimport {VisStateActions} from '@kepler.gl/actions';\n\nconst expectedVisStateEntries = [\n  'filters',\n  'layers',\n  'effects',\n  'interactionConfig',\n  'layerBlending',\n  'overlayBlending',\n  'splitMaps',\n  'animationConfig',\n  'editor'\n];\n\ntest('#visStateSchema -> v1 -> save layers', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n\n  // save state\n  const vsToSave = SchemaManager.getConfigToSave(initialState).config.visState;\n\n  t.deepEqual(\n    Object.keys(vsToSave),\n    expectedVisStateEntries,\n    `visState should have all ${expectedVisStateEntries.length} entries`\n  );\n\n  const exptectedSavedLayers = [expectedSavedLayer0, expectedSavedLayer1, expectedSavedLayer2];\n\n  const layersToSave = vsToSave.layers;\n\n  cmpSavedLayers(t, exptectedSavedLayers, layersToSave);\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> load layers', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n\n  // save state\n  const savedState = SchemaManager.getConfigToSave(initialState);\n  const vsLoaded = SchemaManager.parseSavedConfig(savedState).visState;\n\n  t.deepEqual(\n    Object.keys(vsLoaded),\n    expectedVisStateEntries,\n    `visState should have all ${expectedVisStateEntries.length} entries`\n  );\n\n  const loadedLayers = vsLoaded.layers;\n\n  const expectedLoadedLayers = [expectedLoadedLayer0, expectedLoadedLayer1, expectedLoadedLayer2];\n\n  cmpSavedLayers(t, expectedLoadedLayers, loadedLayers, {id: true});\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load filters', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const vsToSave = savedState.config.visState;\n  const vsLoaded = SchemaManager.parseSavedConfig(savedState).visState;\n  const loadedFilters = vsLoaded.filters;\n\n  // test saved filters\n  const filtersToSave = vsToSave.filters;\n\n  const expectedSavedFilters = [\n    {\n      dataId: [testCsvDataId],\n      id: 'hjpn8frza',\n      enabled: true,\n      name: ['time'],\n      type: 'timeRange',\n      value: [1474606800000, 1474617600000],\n      view: 'enlarged',\n      plotType: {\n        interval: '1-hour',\n        defaultTimeFormat: 'L  H A',\n        type: 'histogram',\n        aggregation: 'sum'\n      },\n      yAxis: null,\n      animationWindow: 'free',\n      speed: 4\n    },\n    {\n      dataId: [testGeoJsonDataId],\n      id: 'vpk2466o',\n      enabled: true,\n      name: ['RATE'],\n      type: 'multiSelect',\n      value: ['a'],\n      view: 'side',\n      plotType: {\n        type: 'histogram'\n      },\n      yAxis: null,\n      animationWindow: 'free',\n      speed: 1\n    }\n  ];\n\n  cmpFilters(t, expectedSavedFilters, filtersToSave);\n  cmpFilters(t, expectedSavedFilters, loadedFilters);\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save and validate filters', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n\n  // add empty filter\n  const nextStte = keplerGlReducer(initialState, VisStateActions.addFilter(testCsvDataId));\n  const savedState = SchemaManager.getConfigToSave(nextStte);\n\n  t.equal(nextStte.visState.filters.length, 3, 'should have 3 filters');\n  t.equal(savedState.config.visState.filters.length, 2, 'should only save 2 filters');\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load interaction', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const interactionToSave = savedState.config.visState.interactionConfig;\n  const interactionLoaded = SchemaManager.parseSavedConfig(savedState).visState.interactionConfig;\n\n  const expectedSaved = {\n    tooltip: {\n      enabled: true,\n      compareMode: false,\n      compareType: 'absolute',\n      fieldsToShow: {\n        [testCsvDataId]: [\n          {\n            name: 'gps_data.utc_timestamp',\n            format: null\n          },\n          {\n            name: 'gps_data.types',\n            format: null\n          },\n          {\n            name: 'epoch',\n            format: null\n          },\n          {\n            name: 'has_result',\n            format: null\n          },\n          {\n            name: 'uid',\n            format: null\n          }\n        ],\n        [testGeoJsonDataId]: [\n          {\n            name: 'OBJECTID',\n            format: null\n          },\n          {\n            name: 'ZIP_CODE',\n            format: null\n          },\n          {\n            name: 'ID',\n            format: null\n          },\n          {\n            name: 'TRIPS',\n            format: null\n          },\n          {\n            name: 'RATE',\n            format: null\n          }\n        ]\n      }\n    },\n    brush: {\n      enabled: false,\n      size: 0.5\n    },\n    coordinate: {\n      enabled: false\n    },\n    geocoder: {\n      enabled: false\n    }\n  };\n\n  t.deepEqual(interactionToSave, expectedSaved);\n  t.deepEqual(interactionLoaded, expectedSaved);\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load interaction -> tooltip format', t => {\n  const initialState = cloneDeep(StateWTooltipFormat);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const interactionToSave = savedState.config.visState.interactionConfig;\n  const interactionLoaded = SchemaManager.parseSavedConfig(savedState).visState.interactionConfig;\n\n  const expectedSaved = {\n    tooltip: {\n      enabled: true,\n      compareMode: true,\n      compareType: 'relative',\n      fieldsToShow: {\n        [testCsvDataId]: [{name: 'gps_data.utc_timestamp', format: 'LL'}],\n        [testGeoJsonDataId]: [\n          {\n            name: 'OBJECTID',\n            format: null\n          },\n          {\n            name: 'ZIP_CODE',\n            format: null\n          },\n          {\n            name: 'ID',\n            format: null\n          },\n          {\n            name: 'TRIPS',\n            format: '.3f'\n          },\n          {\n            name: 'RATE',\n            format: null\n          }\n        ]\n      }\n    },\n    brush: {\n      enabled: false,\n      size: 0.5\n    },\n    coordinate: {\n      enabled: false\n    },\n    geocoder: {\n      enabled: false\n    }\n  };\n\n  t.deepEqual(interactionToSave, expectedSaved);\n  t.deepEqual(interactionLoaded, expectedSaved);\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load layerBlending', t => {\n  const initialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const savedState = SchemaManager.getConfigToSave(initialState);\n\n  // save state\n  const layerBlendingToSave = savedState.config.visState.layerBlending;\n  const layerBlendingLoaded = SchemaManager.parseSavedConfig(savedState).visState.layerBlending;\n\n  const expectedSaved = 'normal';\n\n  t.deepEqual(layerBlendingToSave, expectedSaved);\n  t.deepEqual(layerBlendingLoaded, expectedSaved);\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save animation', t => {\n  const initialState = cloneDeep(StateWTripGeojson);\n\n  // save state\n  const vsToSave = SchemaManager.getConfigToSave(initialState).config.visState;\n\n  t.deepEqual(\n    Object.keys(vsToSave),\n    expectedVisStateEntries,\n    `visState should have all ${expectedVisStateEntries.length} entries`\n  );\n\n  const expectedSavedLayers = [expectedSavedTripLayer];\n  const expectedAnimationConfig = {currentTime: 1565577261000, speed: 1};\n  cmpSavedLayers(t, expectedSavedLayers, vsToSave.layers);\n\n  t.deepEqual(vsToSave.animationConfig, expectedAnimationConfig, 'should save animationConfig');\n  t.end();\n});\n\ntest('visStateSchema -> saving with a null column value does not throw an error', t => {\n  const testLayer = cloneDeep(StateWFiles).visState.layers[0];\n\n  // set a column to null\n  testLayer.config.columns.altitude = null;\n\n  // test that it doesn't fail ColumnSchemaV1.save\n  t.doesNotThrow(() => {\n    visStateSchema[CURRENT_VERSION].save({\n      layers: [testLayer],\n      layerOrder: [testLayer.id]\n    });\n  }, 'saving with a null column value should not fail');\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load effects', t => {\n  const expectedEffects = [\n    {\n      id: 'e_4',\n      type: 'lightAndShadow',\n      isEnabled: true,\n      parameters: {\n        timestamp: 100,\n        timezone: 'UTC',\n        timeMode: 'pick',\n        shadowIntensity: 0.5,\n        shadowColor: [0, 0, 0],\n        sunLightColor: [255, 255, 255],\n        sunLightIntensity: 1,\n        ambientLightColor: [255, 255, 255],\n        ambientLightIntensity: 1\n      }\n    },\n    {\n      id: 'e_3',\n      type: 'magnify',\n      isEnabled: true,\n      parameters: {\n        screenXY: [0.5, 0.5],\n        radiusPixels: 200,\n        zoom: 2,\n        borderWidthPixels: 3,\n        borderColor: [255, 255, 255, 255]\n      }\n    },\n    {\n      id: 'e_2',\n      type: 'sepia',\n      isEnabled: true,\n      parameters: {amount: 0.5}\n    },\n    {\n      id: 'e_1',\n      type: 'ink',\n      isEnabled: true,\n      parameters: {strength: 0.25}\n    }\n  ];\n\n  const initialState = cloneDeep(StateWEffects);\n\n  const savedState = SchemaManager.getConfigToSave(initialState);\n  const savedEffects = savedState.config.visState.effects;\n\n  const loadedState = SchemaManager.parseSavedConfig(savedState);\n  const loadedEffects = loadedState.visState.effects;\n\n  t.deepEqual(savedEffects, expectedEffects, 'Effects should be saved as expected');\n  t.deepEqual(loadedEffects, expectedEffects, 'Effects should be loaded as expected');\n\n  t.end();\n});\n\ntest('#visStateSchema -> v1 -> save load effects (deprecated beta config)', t => {\n  const deprecatedEffects = [\n    {\n      id: 'e_5',\n      config: {\n        type: 'magnify',\n        name: 'Magnify',\n        isEnabled: false,\n        isConfigActive: true,\n        params: {\n          screenXY: [0, 0],\n          radiusPixels: 200,\n          zoom: 2,\n          borderWidthPixels: 0,\n          borderColor: [255, 255, 255, 255]\n        }\n      }\n    }\n  ];\n\n  const expectedLoadedEffects = [\n    {\n      id: 'e_5',\n      type: 'magnify',\n      isEnabled: false,\n      parameters: {\n        screenXY: [0, 0],\n        radiusPixels: 200,\n        zoom: 2,\n        borderWidthPixels: 0,\n        borderColor: [255, 255, 255, 255]\n      }\n    }\n  ];\n\n  const initialState = cloneDeep(StateWEffects);\n  const deprecatedSavedState = SchemaManager.getConfigToSave(initialState);\n  deprecatedSavedState.config.visState.effects = deprecatedEffects;\n  deprecatedSavedState.config.visState.effectOrder = [deprecatedEffects[0].id];\n\n  const loadedState = SchemaManager.parseSavedConfig(deprecatedSavedState);\n\n  t.deepEqual(\n    loadedState.visState.effects,\n    expectedLoadedEffects,\n    'Effects should be loaded as expected'\n  );\n\n  t.end();\n});\n\ntest('#visStateSchema -> auto create layers', t => {\n  const state = cloneDeep(StateWFiles);\n  t.equal(state.visState.layers.length, 2, 'should auto create 2 layers');\n  t.end();\n});\n\ntest('#visStateSchema -> color properties', t => {\n  const state = cloneDeep(StateWLayerCustomColorBreaks);\n  t.equal(state.visState.layers.length, 1, 'should load 1 layer');\n  t.deepEqual(\n    state.visState.layers[0].config.visConfig.colorRange,\n    mockColorRange,\n    'should load layer colorRange correctly'\n  );\n  t.deepEqual(\n    state.visState.layers[0].config.colorScale,\n    'custom',\n    'should load layer color Scale correctly'\n  );\n\n  t.deepEqual(\n    state.visState.layers[0].config.colorField.name,\n    'uid',\n    'should load colorField correctly'\n  );\n\n  t.end();\n});\n\ntest('#visStateSchema -> create trip layers', t => {\n  const state = cloneDeep(StateWTripTable);\n  t.equal(state.visState.layers.length, 1, 'should create 1 trip layer');\n  t.equal(state.visState.layers[0].type, 'trip', 'should create trip layer');\n  t.end();\n});\n\ntest('#visStateSchema -> create point and arc layers', t => {\n  const state = cloneDeep(StateWArcNeighbors);\n  t.equal(state.visState.layers.length, 3, 'should create 3 layers');\n  t.equal(state.visState.layers[0].type, 'point', 'should create point layer');\n  t.equal(state.visState.layers[1].type, 'arc', 'should create arc layer');\n  t.equal(state.visState.layers[2].type, 'arc', 'should create arc layer');\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/aggregation-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {getFrequency, getMode, aggregate} from '@kepler.gl/utils';\nimport {AGGREGATION_TYPES} from '@kepler.gl/constants';\n\ntest('Aggregate - GetFrequency', t => {\n  t.deepEqual(\n    getFrequency([2, 1, 2, 1]),\n    {\n      1: 2,\n      2: 2\n    },\n    'Should compute frequency corerctly'\n  );\n\n  t.deepEqual(getFrequency([]), {}, 'Should return an empty object');\n\n  t.end();\n});\n\ntest('Aggregate - GetMode', t => {\n  t.deepEqual(getMode([2, 1, 2, 1]), '1', 'should return 1 as Mode');\n\n  t.end();\n});\n\ntest('Aggregate - aggregate', t => {\n  const data = [1, 2, 3, 1, 2, 3, 4, 3];\n  const results = [8, 2.375, 4, 1, 2.5, 1.0606601717798212, 19, 1.125, '3', 4];\n  Object.keys(AGGREGATION_TYPES).map((technique, index) => {\n    t.equal(\n      aggregate(data, technique),\n      results[index],\n      `Should compute the right aggregation using ${technique} - ${aggregate(data, technique)}`\n    );\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/color-util-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  addCustomPaletteColor,\n  colorRangeBackwardCompatibility,\n  createLinearGradient,\n  removeCustomPaletteColor,\n  sortCustomPaletteColor,\n  updateCustomPaletteColor\n} from '@kepler.gl/utils';\nimport test from 'tape';\nimport {KEPLER_COLOR_PALETTES, colorPaletteToColorRange} from '@kepler.gl/constants';\n\ntest('createLinearGradient', t => {\n  let colors = [[100, 100, 100]];\n\n  t.deepEqual(\n    createLinearGradient('bottom', colors),\n    'linear-gradient(to bottom, rgba(100,100,100, 1) 0%, rgba(100,100,100, 1) 100%)',\n    'Should create a solid gradient with 1 color'\n  );\n\n  colors = [\n    [100, 100, 100],\n    [200, 200, 200],\n    [300, 300, 300]\n  ];\n\n  t.deepEqual(\n    createLinearGradient('bottom', colors),\n    'linear-gradient(to bottom, rgba(100,100,100, 1) 0%, rgba(100,100,100, 1) 33.33%,rgba(200,200,200, 1) 33.33%, rgba(200,200,200, 1) 66.66%,rgba(300,300,300, 1) 66.66%, rgba(300,300,300, 1) 99.99%)',\n    'Should create a linear gradient'\n  );\n\n  colors = [\n    [10, 10, 10],\n    [20, 20, 20],\n    [30, 30, 30],\n    [40, 40, 40]\n  ];\n\n  t.deepEqual(\n    createLinearGradient('bottom', colors),\n    'linear-gradient(to bottom, rgba(10,10,10, 1) 0%, rgba(10,10,10, 1) 25%,rgba(20,20,20, 1) 25%, rgba(20,20,20, 1) 50%,rgba(30,30,30, 1) 50%, rgba(30,30,30, 1) 75%,rgba(40,40,40, 1) 75%, rgba(40,40,40, 1) 100%)',\n    'Should create a linear gradient'\n  );\n\n  t.end();\n});\n\nconst CUSTOM_PALETTE = {\n  name: 'Custom Palette',\n  type: 'custom',\n  category: 'Custom',\n  colors: ['#010101', '#030303', '#050505', '#070707'],\n  colorMap: [\n    [28, '#010101'],\n    [33, '#030303'],\n    [41, '#050505'],\n    [null, '#070707']\n  ],\n  colorLegends: {\n    '#010101': 'A',\n    '#030303': 'B',\n    '#050505': 'C',\n    '#070707': 'D'\n  }\n};\n\nconst CUSTOM_PALETTE_1 = {\n  name: 'Custom Palette',\n  type: 'custom',\n  category: 'Custom',\n  colors: ['#010101', '#070707'],\n  colorMap: [\n    [28, '#010101'],\n    [null, '#070707']\n  ],\n  colorLegends: {\n    '#010101': 'A'\n  }\n};\n\ntest('ColorUtil -> addCustomPaletteColor', t => {\n  const TEST_CASES = [\n    {\n      input: [CUSTOM_PALETTE, 0],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#010101', '#020202', '#030303', '#050505', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [30.5, '#020202'],\n          [33, '#030303'],\n          [41, '#050505'],\n          [null, '#070707']\n        ]\n      },\n      msg: 'add 1 at index:0 should be correct'\n    },\n    {\n      input: [CUSTOM_PALETTE, 3],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#010101', '#030303', '#050505', '#070707', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [33, '#030303'],\n          [41, '#050505'],\n          [49, '#070707'],\n          [null, '#070707']\n        ]\n      },\n      msg: 'add 1 at end should be correct'\n    },\n    {\n      input: [CUSTOM_PALETTE_1, 0],\n      expected: {\n        ...CUSTOM_PALETTE_1,\n        colors: ['#010101', '#040404', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [28, '#040404'],\n          [null, '#070707']\n        ]\n      },\n      msg: 'add 1 at the beginning should be correct'\n    },\n    {\n      input: [CUSTOM_PALETTE_1, 1],\n      expected: {\n        ...CUSTOM_PALETTE_1,\n        colors: ['#010101', '#070707', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [28, '#070707'],\n          [null, '#070707']\n        ]\n      },\n      msg: 'add 1 at end should be correct'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    t.deepEqual(addCustomPaletteColor(...tc.input), tc.expected, tc.msg);\n  });\n\n  t.end();\n});\n\ntest('ColorUtil -> sortCustomPaletteColor', t => {\n  const TEST_CASES = [\n    {\n      input: [CUSTOM_PALETTE, 0, 2],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#030303', '#050505', '#010101', '#070707'],\n        colorMap: [\n          [28, '#030303'],\n          [33, '#050505'],\n          [41, '#010101'],\n          [null, '#070707']\n        ],\n        colorLegends: {\n          '#030303': 'A',\n          '#050505': 'B',\n          '#010101': 'C',\n          '#070707': 'D'\n        }\n      },\n      msg: 'should sort customPalette correctly'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    t.deepEqual(sortCustomPaletteColor(...tc.input), tc.expected, tc.msg);\n  });\n\n  t.end();\n});\n\ntest('ColorUtil -> removeCustomPaletteColor', t => {\n  const TEST_CASES = [\n    {\n      input: [CUSTOM_PALETTE, 1],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#010101', '#050505', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [41, '#050505'],\n          [null, '#070707']\n        ],\n        colorLegends: {\n          '#010101': 'A',\n          '#050505': 'C',\n          '#070707': 'D'\n        }\n      },\n      msg: 'Should remove 1 color'\n    },\n    {\n      input: [CUSTOM_PALETTE, 3],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#010101', '#030303', '#050505'],\n        colorMap: [\n          [28, '#010101'],\n          [33, '#030303'],\n          [41, '#050505']\n        ],\n        colorLegends: {\n          '#010101': 'A',\n          '#030303': 'B',\n          '#050505': 'C'\n        }\n      },\n      msg: 'Should remove 1 color at the end'\n    },\n    {\n      input: [CUSTOM_PALETTE, 0],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#030303', '#050505', '#070707'],\n        colorMap: [\n          [33, '#030303'],\n          [41, '#050505'],\n          [null, '#070707']\n        ],\n        colorLegends: {\n          '#030303': 'B',\n          '#050505': 'C',\n          '#070707': 'D'\n        }\n      },\n      msg: 'Should remove 1 color at the end'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    t.deepEqual(removeCustomPaletteColor(...tc.input), tc.expected, tc.msg);\n  });\n\n  t.end();\n});\n\ntest('ColorUtil -> updateCustomPaletteColor', t => {\n  const TEST_CASES = [\n    {\n      input: [CUSTOM_PALETTE, 1, '#FFFFFF'],\n      expected: {\n        ...CUSTOM_PALETTE,\n        colors: ['#010101', '#FFFFFF', '#050505', '#070707'],\n        colorMap: [\n          [28, '#010101'],\n          [33, '#FFFFFF'],\n          [41, '#050505'],\n          [null, '#070707']\n        ],\n        colorLegends: {\n          '#010101': 'A',\n          '#FFFFFF': 'B',\n          '#050505': 'C',\n          '#070707': 'D'\n        }\n      },\n      msg: 'Should update 1 color'\n    },\n    {\n      input: [\n        {\n          name: 'Custom Palette',\n          type: 'custom',\n          category: 'Custom',\n          colors: ['#010101', '#030303', '#050505', '#070707']\n        },\n        1,\n        '#FFFFFF'\n      ],\n      expected: {\n        name: 'Custom Palette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#010101', '#FFFFFF', '#050505', '#070707']\n      },\n      msg: 'Should update 1 color'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    t.deepEqual(updateCustomPaletteColor(...tc.input), tc.expected, tc.msg);\n  });\n\n  t.end();\n});\n\ntest('ColorUtil -> colorPaletteToColorRange', t => {\n  const TEST_CASES = [\n    {\n      name: 'YlOrRd',\n      steps: 8,\n      colors: [\n        '#FFFFCC',\n        '#FFEA9A',\n        '#FECD6A',\n        '#FEA246',\n        '#FC6932',\n        '#E92A21',\n        '#C00624',\n        '#800026'\n      ]\n    },\n    {name: 'Cividis', steps: 4, colors: ['#002051', '#575C6E', '#A49D78', '#FDEA45']},\n    {name: 'Paired', steps: 4, colors: ['#A6CEE3', '#1F78B4', '#B2DF8A', '#33A02C']},\n    {\n      name: 'Accent',\n      steps: 10,\n      colors: [\n        '#7FC97F',\n        '#BEAED4',\n        '#FDC086',\n        '#FFFF99',\n        '#386CB0',\n        '#F0027F',\n        '#BF5B17',\n        '#666666'\n      ]\n    },\n    {name: 'Uber Viz Sequential', steps: 4, colors: ['#00939C', '#6BB5B9', '#AAD7D9', '#E6FAFA']},\n    {\n      name: 'Uber Viz Diverging',\n      steps: 8,\n      colors: [\n        '#00939C',\n        '#5AACB2',\n        '#8BC6C9',\n        '#B9E0E1',\n        '#F8C0AC',\n        '#EB9373',\n        '#D9653D',\n        '#C22E00'\n      ]\n    },\n    {\n      name: 'Tol Vibrant',\n      steps: 6,\n      colors: ['#EE7733', '#0077BB', '#33BBEE', '#EE3377', '#CC3311', '#009988']\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    const colorRange = colorPaletteToColorRange(\n      KEPLER_COLOR_PALETTES.find(p => p.name === tc.name),\n      {steps: tc.steps}\n    );\n\n    t.deepEqual(colorRange.colors, tc.colors, `Should build correct palette: ${tc.name}`);\n  });\n\n  t.end();\n});\n\ntest('ColorUtil -> colorRangeBackwardCompatibility', t => {\n  const OLD_COLOR_RANGES_CUSTOM = [\n    {\n      savedColorRange: {\n        name: 'Uber Viz Qualitative 0',\n        type: 'qualitative',\n        category: 'Uber',\n        colors: ['#12939A', '#DDB27C']\n      },\n      loadedColorRange: {\n        name: 'Uber Viz Qualitative',\n        type: 'qualitative',\n        category: 'Uber',\n        colors: ['#12939A', '#DDB27C']\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'Uber Viz Diverging 0.5',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#000000', '#111111', '#222222']\n      },\n      loadedColorRange: {\n        name: 'Uber Viz Diverging',\n        type: 'diverging',\n        category: 'Uber',\n        colors: ['#000000', '#111111', '#222222']\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'Uber Viz Sequential 1',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#333333', '#444444', '#555555']\n      },\n      loadedColorRange: {\n        name: 'Uber Viz Sequential',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#333333', '#444444', '#555555']\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'UberPool',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#AAAAAA', '#BBBBBB', '#CCCCCC']\n      },\n      loadedColorRange: {\n        name: 'UberPool',\n        type: 'diverging',\n        category: 'Uber',\n        colors: ['#AAAAAA', '#BBBBBB', '#CCCCCC']\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'UberPool 9',\n        type: 'sequential',\n        category: 'Uber',\n        colors: ['#DDDDDD', '#EEEEEE', '#FFFFFF']\n      },\n      loadedColorRange: {\n        name: 'UberPool',\n        type: 'diverging',\n        category: 'Uber',\n        colors: ['#DDDDDD', '#EEEEEE', '#FFFFFF']\n      }\n    }\n  ];\n\n  const OLD_COLOR_RANGES_COLORBREWER = [\n    {\n      savedColorRange: {\n        name: 'ColorBrewer YlGn-3',\n        category: 'ColorBrewer',\n        colors: ['#000000', '#1111111'],\n        type: 'sequential'\n      },\n      loadedColorRange: {\n        name: 'YlGn',\n        category: 'ColorBrewer',\n        colors: ['#000000', '#1111111'],\n        type: 'sequential'\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'ColorBrewer RdYlBu-11',\n        category: 'ColorBrewer',\n        colors: ['#222222', '#333333'],\n        type: 'diverging'\n      },\n      loadedColorRange: {\n        name: 'RdYlBu',\n        category: 'ColorBrewer',\n        colors: ['#222222', '#333333'],\n        type: 'diverging'\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'ColorBrewer Accent-3',\n        category: 'ColorBrewer',\n        colors: ['#444444', '#555555'],\n        type: 'sequantial'\n      },\n      loadedColorRange: {\n        name: 'Accent',\n        category: 'ColorBrewer',\n        colors: ['#444444', '#555555'],\n        type: 'qualitative'\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'ColorBrewer Set3-4',\n        category: 'ColorBrewer',\n        colors: ['#666666', '#777777'],\n        type: 'sequantial'\n      },\n      loadedColorRange: {\n        name: 'Set3',\n        category: 'ColorBrewer',\n        colors: ['#666666', '#777777'],\n        type: 'qualitative'\n      }\n    }\n  ];\n\n  const CUSTOM_COLOR_BREAK = [\n    {\n      savedColorRange: {\n        category: 'Uber',\n        colors: ['#2FA7AE', '#C22E00'],\n        name: 'Uber Viz Diverging 3.5',\n        reversed: false,\n        type: 'diverging',\n        colorMap: [\n          [200500, '#2FA7AE'],\n          [null, '#C22E00']\n        ]\n      },\n      loadedColorRange: {\n        category: 'Uber',\n        colors: ['#2FA7AE', '#C22E00'],\n        name: 'Uber Viz Diverging 3.5',\n        reversed: false,\n        type: 'diverging',\n        colorMap: [\n          [200500, '#2FA7AE'],\n          [null, '#C22E00']\n        ]\n      }\n    },\n    {\n      savedColorRange: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#631C80', '#E6FAFA'],\n        colorLegends: {'#631C80': 'purple'}\n      },\n      loadedColorRange: {\n        name: 'color.customPalette',\n        type: 'custom',\n        category: 'Custom',\n        colors: ['#631C80', '#E6FAFA'],\n        colorLegends: {'#631C80': 'purple'}\n      }\n    }\n  ];\n\n  const CUSTOM_COLOR_LEGENDS = [\n    {\n      savedColorRange: {\n        name: 'Ice And Fire 3',\n        type: 'diverging',\n        category: 'Uber',\n        colors: ['#0198BD', '#FAFEB3', '#D50255'],\n        colorLegends: {'#0198BD': 'hwllo'}\n      },\n      loadedColorRange: {\n        name: 'Ice And Fire',\n        type: 'diverging',\n        category: 'Uber',\n        colors: ['#0198BD', '#FAFEB3', '#D50255'],\n        colorLegends: {'#0198BD': 'hwllo'}\n      }\n    }\n  ];\n\n  const NEW_COLOR_RANGES = [\n    {\n      name: 'Uber Viz Diverging',\n      type: 'diverging',\n      category: 'Uber',\n      colors: ['#00939C', '#8BC6C9', '#EB9373', '#C22E00']\n    },\n    {\n      name: 'Okabe Ito',\n      type: 'qualitative',\n      category: 'ColorBlind',\n      colors: ['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2'],\n      colorLegends: {}\n    },\n    {\n      name: 'PuBu',\n      type: 'sequential',\n      category: 'ColorBrewer',\n      colors: ['#FFF7FB', '#CED1E6', '#72A8CF', '#0D72AD', '#023858'],\n      colorLegends: {}\n    }\n  ];\n\n  // load old color range\n  [\n    ...OLD_COLOR_RANGES_CUSTOM,\n    ...OLD_COLOR_RANGES_COLORBREWER,\n    ...CUSTOM_COLOR_BREAK,\n    ...CUSTOM_COLOR_LEGENDS\n  ].forEach(tc => {\n    t.deepEqual(\n      colorRangeBackwardCompatibility(tc.savedColorRange),\n      tc.loadedColorRange,\n      `should rename and load ${tc.savedColorRange.name} type ${tc.savedColorRange.type} as ${tc.loadedColorRange.name}`\n    );\n  });\n\n  // load new color palettes\n  NEW_COLOR_RANGES.forEach(tc => {\n    t.deepEqual(\n      colorRangeBackwardCompatibility(tc),\n      tc,\n      `should keep load new color palette ${tc.name}`\n    );\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/composer-helpers-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {filterOutById, removeElementAtIndex} from '@kepler.gl/reducers';\n\ntest('#composeHelpers -> RemoveElementAtIndex', t => {\n  const list = [1, 2, 3, 4, 5];\n  t.deepEqual(removeElementAtIndex(1)(list), [1, 3, 4, 5], 'Should remove element at index');\n  t.end();\n});\n\ntest('#composeHelpers -> filterOutById', t => {\n  const list = [{id: 1}, {id: 2}, {id: 3}];\n  t.deepEqual(filterOutById(1)(list), [{id: 2}, {id: 3}], 'Should remove element with specific id');\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/data-container-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {createDataContainer, createIndexedDataContainer} from '@kepler.gl/utils';\n\nconst data = [\n  [10, 20], // 0\n  [30, 40], // 1\n  [50, 60], // 2\n  [80, 90], // 3\n  [100, 110], // 4\n  [120, 130] // 5\n];\n\nconst indices = [1, 3, 5];\n\ntest('RowDataContainer', t => {\n  const dc = createDataContainer(data);\n\n  t.deepEqual(dc.numRows(), 6, `RowDataContainer should have expected number of rows`);\n  t.deepEqual(dc.numColumns(), 2, `RowDataContainer should have expected number of columns`);\n  t.deepEqual(dc.valueAt(2, 1), 60, `RowDataContainer.valueAt should return expected value`);\n  t.deepEqual(dc.row(2).valueAt(1), 60, `RowDataContainer.row should return expected value`);\n  t.deepEqual(\n    dc.rowAsArray(2),\n    [50, 60],\n    `RowDataContainer.rowAsArray should return expected value`\n  );\n  t.deepEqual(dc.flattenData(), data, `RowDataContainer.flattenData should return expected data`);\n  t.deepEqual(\n    dc.getPlainIndex(),\n    [0, 1, 2, 3, 4, 5],\n    `RowDataContainer.getPlainIndex should return expected indices`\n  );\n\n  t.deepEqual(\n    dc.map(row => row.valueAt(1)),\n    [20, 40, 60, 90, 110, 130],\n    `RowDataContainer.map should return expected array`\n  );\n\n  t.deepEqual(\n    dc.mapIndex(d => d),\n    [{index: 0}, {index: 1}, {index: 2}, {index: 3}, {index: 4}, {index: 5}],\n    `RowDataContainer.mapIndex should return expected array`\n  );\n\n  t.deepEqual(\n    dc.find(row => {\n      return row.valueAt(1) === 60;\n    }),\n    dc.row(2),\n    `RowDataContainer.find should return expected row`\n  );\n\n  t.deepEqual(\n    dc.reduce((acc, row) => {\n      return acc + row.valueAt(1);\n    }, 10),\n    460,\n    `RowDataContainer.reduce should return expected value`\n  );\n\n  t.end();\n});\n\ntest('IndexedDataContainer', t => {\n  const dc = createIndexedDataContainer(createDataContainer(data), indices);\n\n  t.deepEqual(dc.numRows(), 3, `IndexedDataContainer should have expected number of rows`);\n  t.deepEqual(dc.numColumns(), 2, `IndexedDataContainer should have expected number of columns`);\n  t.deepEqual(dc.valueAt(2, 1), 130, `IndexedDataContainer.valueAt should return expected value`);\n  t.deepEqual(dc.row(2).valueAt(1), 130, `IndexedDataContainer.row should return expected value`);\n  t.deepEqual(\n    dc.rowAsArray(2),\n    [120, 130],\n    `IndexedDataContainer.rowAsArray should return expected value`\n  );\n\n  t.deepEqual(\n    dc.flattenData(),\n    [data[indices[0]], data[indices[1]], data[indices[2]]],\n    `IndexedDataContainer.flattenData should return expected data`\n  );\n  t.deepEqual(\n    dc.getPlainIndex(),\n    [0, 1, 2],\n    `IndexedDataContainer.getPlainIndex should return expected indices`\n  );\n\n  t.deepEqual(\n    dc.map(row => row.valueAt(1)),\n    [40, 90, 130],\n    `IndexedDataContainer.map should return expected array`\n  );\n\n  t.deepEqual(\n    dc.mapIndex(d => d),\n    [{index: 1}, {index: 3}, {index: 5}],\n    `IndexedDataContainer.mapIndex should return expected array`\n  );\n\n  t.deepEqual(\n    dc.find(row => {\n      return row.valueAt(1) === 90;\n    }),\n    dc.row(1),\n    `IndexedDataContainer.find should return expected row`\n  );\n\n  t.deepEqual(\n    dc.reduce((acc, row) => {\n      return acc + row.valueAt(1);\n    }, 10),\n    270,\n    `RowDataContainer.reduce should return expected value`\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/data-processor-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport sinon from 'sinon';\nimport {console as Console} from 'global/window';\nimport {DATA_TYPES} from 'type-analyzer';\nimport cloneDeep from 'lodash/cloneDeep';\n\nimport testData, {\n  dataWithNulls,\n  parsedDataWithNulls,\n  testFields,\n  testAllData,\n  wktCsv,\n  wktCsvFields,\n  wktCsvRows\n} from 'test/fixtures/test-csv-data';\nimport {\n  geojsonData,\n  geoJsonWithStyle,\n  geoStyleFields,\n  geoStyleRows,\n  fields as geojsonFields,\n  rows as geojsonRows\n} from 'test/fixtures/geojson';\nimport testCsvObjectData, {objCsvFields, objCsvRows} from 'test/fixtures/test-csv-object';\nimport testCsvH3Data, {expectedFields as expectedHexFields} from 'test/fixtures/test-hex-id-data';\nimport {\n  parseCsvRowsByFieldType,\n  processCsvData,\n  processGeojson,\n  processRowObject\n} from '@kepler.gl/processors';\n\nimport {validateInputData, createDataContainer} from '@kepler.gl/utils';\n\nimport {\n  ACCEPTED_ANALYZER_TYPES,\n  analyzerTypeToFieldType,\n  getFieldsFromData,\n  getSampleForTypeAnalyze\n} from '@kepler.gl/common-utils';\n\nimport {formatCsv} from '@kepler.gl/reducers';\n\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport {cmpFields} from '../../helpers/comparison-utils';\n\ntest('Processor -> getFieldsFromData', t => {\n  const data = [\n    {\n      time: '2016-09-17 00:09:55',\n      trip_epoch: '1472688000000',\n      time_str: 'January 1st 2017 11:00pm ',\n      value: '4',\n      novalue: null,\n      surge: '1.2',\n      isTrip: 'true',\n      zeroOnes: '0',\n      h3_9: '89268cd80b3ffff',\n      geojson: '{\"type\":\"Point\",\"coordinates\":[-122.4194155,37.7749295]}',\n      wkt: 'POINT (-122.4194155 37.7749295)',\n      wkb: '0101000020E6100000E17A14AE47D25EC0F6F3F6F2F7F94040'\n    },\n    {\n      time: '2016-09-17 00:30:08',\n      trip_epoch: '1472860800000',\n      time_str: 'January 1st 2017 11:01pm ',\n      value: '3',\n      novalue: null,\n      surge: null,\n      isTrip: 'false',\n      zeroOnes: '1',\n      h3_9: '89268cd8103ffff',\n      geojson:\n        '{\"type\":\"Polygon\",\"coordinates\":[[[-122.4194155,37.7749295],[-122.4194155,37.7749295],[-122.4194155,37.7749295]]]}',\n      wkt: 'POLYGON ((-122.4194155 37.7749295, -122.4194155 37.7749295, -122.4194155 37.7749295))',\n      wkb: '0103000020E61000000100000005000000E17A14AE47D25EC0F6F3F6F2F7F940400000000E17A14AE47D25EC0F6F3F6F2F7F940400000000E17A14AE47D25EC0F6F3F6F2F7F94040'\n    },\n    {\n      time: null,\n      trip_epoch: null,\n      time_str: 'January 1st, 2017 11:02pm ',\n      value: '2',\n      novalue: null,\n      surge: '1.3',\n      isTrip: null,\n      zeroOnes: '1',\n      h3_9: '89268cd8107ffff',\n      geojson:\n        '{\"type\":\"LineString\",\"coordinates\":[[-122.4194155,37.7749295],[-122.4194155,37.7749295]]}',\n      wkt: 'LINESTRING (-122.4194155 37.7749295, -122.4194155 37.7749295)',\n      wkb: '0102000020E610000002000000E17A14AE47D25EC0F6F3F6F2F7F94040E17A14AE47D25EC0F6F3F6F2F7F94040'\n    },\n    {\n      time: null,\n      trip_epoch: null,\n      time_str: 'January 1st, 2017 11:02pm ',\n      value: '0',\n      novalue: null,\n      surge: '1.4',\n      isTrip: null,\n      zeroOnes: '0',\n      h3_9: '89268cd8113ffff',\n      geojson:\n        '{\"type\":\"MultiPoint\",\"coordinates\":[[-122.4194155,37.7749295],[-122.4194155,37.7749295]]}',\n      wkt: 'MULTIPOINT (-122.4194155 37.7749295, -122.4194155 37.7749295)',\n      wkb: '0104000020E6100000020000000101000000E17A14AE47D25EC0F6F3F6F2F7F94040101000000E17A14AE47D25EC0F6F3F6F2F7F94040'\n    }\n  ];\n\n  const headerRow = Object.keys(data[0]);\n  const fields = getFieldsFromData(data, headerRow);\n  const expectedFieldTypes = [\n    'timestamp',\n    'timestamp',\n    'timestamp',\n    'integer',\n    'string',\n    'real',\n    'boolean',\n    'integer',\n    'h3',\n    'geojson',\n    'geojson',\n    'geojson'\n  ];\n\n  fields.forEach((f, i) =>\n    t.equal(f.type, expectedFieldTypes[i], `should find field type as ${expectedFieldTypes[i]}`)\n  );\n  t.end();\n});\n\ntest('Processor -> processCsvData', t => {\n  t.throws(() => processCsvData(''), 'should throw if csv is empty');\n\n  // load sample dataset csv as text\n  const {fields, rows} = processCsvData(cloneDeep(testData));\n\n  t.equal(rows.length, testAllData.length, `should return ${testAllData.length} rows`);\n\n  cmpFields(t, testFields, fields, 'should parse rows correctly');\n\n  t.deepEqual(rows, testAllData, 'should parse rows correctly');\n\n  rows.forEach((r, i) => {\n    t.deepEqual(r, testAllData[i], `should parse row ${i} correctly`);\n  });\n\n  t.end();\n});\n\ntest('Processor -> processCsvData -> duplicated field name', t => {\n  const testData1 = `column1,column1,column1,column2\\na,b,c,d\\nc,d,e,f`;\n\n  // load sample dataset csv as text\n  const result = processCsvData(testData1);\n\n  const expectedResult = {\n    fields: [\n      {\n        name: 'column1',\n        id: 'column1',\n        format: '',\n        fieldIdx: 0,\n        displayName: 'column1',\n        type: 'string',\n        analyzerType: 'STRING',\n        valueAccessor: values => values[0]\n      },\n      {\n        name: 'column1-0',\n        id: 'column1-0',\n        format: '',\n        fieldIdx: 1,\n        displayName: 'column1-0',\n        type: 'string',\n        analyzerType: 'STRING',\n        valueAccessor: values => values[1]\n      },\n      {\n        name: 'column1-1',\n        id: 'column1-1',\n        format: '',\n        fieldIdx: 2,\n        displayName: 'column1-1',\n        type: 'string',\n        analyzerType: 'STRING',\n        valueAccessor: values => values[2]\n      },\n      {\n        name: 'column2',\n        id: 'column2',\n        format: '',\n        fieldIdx: 3,\n        displayName: 'column2',\n        type: 'string',\n        analyzerType: 'STRING',\n        valueAccessor: values => values[3]\n      }\n    ],\n    rows: [\n      ['a', 'b', 'c', 'd'],\n      ['c', 'd', 'e', 'f']\n    ]\n  };\n\n  cmpFields(t, result.fields, expectedResult.fields, 'should have created non duplicated fields');\n  t.deepEqual(\n    result.rows,\n    expectedResult.rows,\n    'should have computed rows with non duplicated fields'\n  );\n\n  t.end();\n});\n\ntest('Processor -> processCsvData -> with nulls', t => {\n  const {fields, rows} = processCsvData(dataWithNulls);\n  cmpFields(t, fields, testFields, 'should parse fields correctly');\n\n  t.deepEqual(rows, parsedDataWithNulls, 'should parse rows correctly');\n  rows.forEach((r, i) => {\n    t.deepEqual(r, parsedDataWithNulls[i], `should parse row ${i} correctly`);\n  });\n  t.end();\n});\n\ntest('Processor -> processCsvData -> wkt', t => {\n  const {fields, rows} = processCsvData(wktCsv);\n\n  cmpFields(t, fields, wktCsvFields, 'should find geometry fields as type:geojson');\n\n  rows.forEach((r, i) => {\n    t.deepEqual(r, wktCsvRows[i], `should process wkt rows[${i}] correctly`);\n  });\n  t.deepEqual(rows, wktCsvRows, 'should process wkt rows correctly');\n\n  t.end();\n});\n\ntest('Processor -> processCsvData -> w/ array and object', t => {\n  const {fields, rows} = processCsvData(testCsvObjectData);\n  cmpFields(t, fields, objCsvFields, 'should find csv object fields as type:object');\n\n  t.equal(rows.length, objCsvRows.length, 'should have same row length');\n  rows.forEach((r, i) => {\n    t.deepEqual(r, objCsvRows[i], 'should format correct csv object rows');\n  });\n  t.end();\n});\n\ntest('Processor -> processCsvData -> w/ hex id', t => {\n  const {fields, rows} = processCsvData(testCsvH3Data);\n  cmpFields(t, fields, expectedHexFields, 'should find csv object fields as h3');\n\n  t.equal(rows.length, 22, 'should have same row length');\n  t.end();\n});\n\ntest('Processor => processGeojson', t => {\n  const {fields, rows} = processGeojson(cloneDeep(geojsonData));\n\n  cmpFields(t, fields, geojsonFields, 'should have same field length');\n\n  t.equal(rows.length, geojsonRows.length, 'should have same row length');\n  rows.forEach((r, i) => {\n    t.deepEqual(r, geojsonRows[i], 'should format correct geojson rows');\n  });\n  t.end();\n});\n\ntest('Processor => processGeojson: with style property', t => {\n  const {fields, rows} = processGeojson(cloneDeep(geoJsonWithStyle));\n\n  cmpFields(t, fields, geoStyleFields, 'should preserve objects in geojson properties');\n  t.deepEqual(rows, geoStyleRows, 'should preserve objects in geojson properties');\n  t.end();\n});\n\ntest('Processor => processGeojson:invalid geojson', t => {\n  t.throws(\n    () => processGeojson({fields: [], rows: []}),\n    'Should throw error with invalid geojson type'\n  );\n\n  t.end();\n});\n\ntest('Processor => processGeojson: parse rows', t => {\n  const testGeoData = {\n    type: 'FeatureCollection',\n    features: [\n      {\n        type: 'Feature',\n        properties: {TRIPS: '11', RATE: 'a', TIME: '1570749707'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      {\n        type: 'Feature',\n        properties: {TRIPS: '10', RATE: 'b', TIME: '1570749707'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      {\n        type: 'Feature',\n        properties: {TRIPS: '9', RATE: 'b', TIME: '1570749707'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      }\n    ]\n  };\n\n  const expectedFields = [\n    {\n      name: '_geojson',\n      id: '_geojson',\n      displayName: '_geojson',\n      format: '',\n      fieldIdx: 0,\n      type: 'geojson',\n      analyzerType: 'GEOMETRY',\n      valueAccessor: values => values[0]\n    },\n    {\n      name: 'TRIPS',\n      id: 'TRIPS',\n      displayName: 'TRIPS',\n      format: '',\n      fieldIdx: 1,\n      type: 'integer',\n      analyzerType: 'INT',\n      valueAccessor: values => values[1]\n    },\n    {\n      name: 'RATE',\n      id: 'RATE',\n      displayName: 'RATE',\n      format: '',\n      fieldIdx: 2,\n      type: 'string',\n      analyzerType: 'STRING',\n      valueAccessor: values => values[2]\n    },\n    {\n      name: 'TIME',\n      id: 'TIME',\n      displayName: 'TIME',\n      format: 'x',\n      fieldIdx: 3,\n      type: 'timestamp',\n      analyzerType: 'TIME',\n      valueAccessor: values => values[3]\n    }\n  ];\n\n  const expectedRows = [\n    [\n      {\n        type: 'Feature',\n        properties: {TRIPS: 11, RATE: 'a', TIME: 1570749707},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      11,\n      'a',\n      1570749707\n    ],\n    [\n      {\n        type: 'Feature',\n        properties: {TRIPS: 10, RATE: 'b', TIME: 1570749707},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      10,\n      'b',\n      1570749707\n    ],\n    [\n      {\n        type: 'Feature',\n        properties: {TRIPS: 9, RATE: 'b', TIME: 1570749707},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      9,\n      'b',\n      1570749707\n    ]\n  ];\n  const result = processGeojson(testGeoData);\n\n  t.deepEqual(Object.keys(result), ['fields', 'rows'], 'should contain fields and rows');\n  cmpFields(\n    t,\n    result.fields,\n    expectedFields,\n    'should parse correct fields when geojson input contain string'\n  );\n\n  t.deepEqual(\n    result.rows,\n    expectedRows,\n    'should parse correct rows when geojson input contain string'\n  );\n\n  t.end();\n});\n\ntest('Processor -> parseCsvRowsByFieldType -> real', t => {\n  const field = {\n    type: ALL_FIELD_TYPES.real\n  };\n\n  const rows = [\n    ['0.0'],\n    ['1.0'],\n    ['-1.0'],\n    ['155.0'],\n    ['1.55e3'],\n    ['1.55e-3'],\n    [' 1.55e-3 '],\n    [' 1.55e+3 xyz']\n  ];\n\n  const expected = [[0.0], [1.0], [-1.0], [155.0], [1550.0], [0.00155], [0.00155], [1550.0]];\n\n  parseCsvRowsByFieldType(rows, -1, field, 0);\n  t.same(rows, expected, 'should parsed reals properly');\n  t.end();\n});\n\ntest('Processor -> parseCsvRowsByFieldType -> integer', t => {\n  const field = {\n    type: ALL_FIELD_TYPES.integer\n  };\n\n  const rows = [['0'], ['1'], ['-1'], ['155'], [' 155 '], [' 155 xyz']];\n\n  const expected = [[0], [1], [-1], [155], [155], [155]];\n\n  parseCsvRowsByFieldType(rows, -1, field, 0);\n  t.same(rows, expected, 'should parsed ints properly');\n  t.end();\n});\n\ntest('Processor -> parseCsvRowsByFieldType -> boolean', t => {\n  const field = {\n    type: ALL_FIELD_TYPES.boolean\n  };\n\n  const rows = [[null], ['0'], ['1'], ['True'], ['False'], ['0'], ['1'], ['true']];\n\n  // is parsing '' meaningful, why not false\n  const expected = [[null], [false], [true], [true], [false], [false], [true], [true]];\n\n  parseCsvRowsByFieldType(rows, -1, field, 0);\n  t.same(rows, expected, 'should parsed boolean properly');\n  t.end();\n});\n\ntest('Processor -> getSampleForTypeAnalyze', t => {\n  const fields = ['string', 'int', 'bool', 'time'];\n\n  const rows = [\n    ['a', 0, true, null],\n    ['b', 2, false, null],\n    ['c', 3, true, '2017-01-01'],\n    [null, 1, false, '2017-01-02'],\n    ['d', 6, false, '2017-01-03'],\n    ['e', 4, true, null],\n    ['f', 5, true, undefined],\n    ['g', null, true, null],\n    ['h', undefined, true, '2017-01-04']\n  ];\n\n  const sample = getSampleForTypeAnalyze({fields, rows, sampleCount: 5});\n  const expected = [\n    {\n      string: 'a',\n      int: 0,\n      bool: true,\n      time: '2017-01-01'\n    },\n    {\n      string: 'b',\n      int: 2,\n      bool: false,\n      time: '2017-01-02'\n    },\n    {\n      string: 'c',\n      int: 3,\n      bool: true,\n      time: '2017-01-03'\n    },\n    {\n      string: 'd',\n      int: 1,\n      bool: false,\n      time: '2017-01-04'\n    },\n    {\n      string: 'e',\n      int: 6,\n      bool: false,\n      time: null\n    }\n  ];\n\n  t.deepEqual(sample, expected, 'Should find correct sample for type analyzer');\n  t.end();\n});\n\ntest('Processor -> validateInputData', t => {\n  t.equal(validateInputData(null), null, 'Should throw error if data is null');\n  t.equal(validateInputData({rows: 'hello'}), null, 'Should throw error if data.rows is null');\n  t.equal(\n    validateInputData({rows: [], fields: null}),\n    null,\n    'Should throw error if data.fields is null'\n  );\n\n  const cases = [\n    {\n      input: {\n        rows: [[1], [2], [3]],\n        fields: ['a']\n      },\n      expected: {\n        rows: [[1], [2], [3]],\n        fields: [{type: ALL_FIELD_TYPES.integer, format: '', name: 'column_0', analyzerType: 'INT'}]\n      },\n      msg: 'should re generate field if not an object'\n    },\n    {\n      input: {\n        rows: [[1], [2], [3]],\n        fields: [{type: ALL_FIELD_TYPES.integer, format: ''}]\n      },\n      expected: {\n        rows: [[1], [2], [3]],\n        fields: [{type: ALL_FIELD_TYPES.integer, format: '', name: 'column_0', analyzerType: 'INT'}]\n      },\n      msg: 'should reassign field name'\n    },\n    {\n      input: {\n        rows: [[1], [2], [3]],\n        fields: [{type: 'hello', format: '', name: 'taro'}]\n      },\n      expected: {\n        rows: [[1], [2], [3]],\n        fields: [\n          {\n            type: ALL_FIELD_TYPES.integer,\n            format: '',\n            name: 'taro',\n            analyzerType: 'INT'\n          }\n        ]\n      },\n      msg: 'should reassign field type'\n    },\n    {\n      input: {\n        rows: [['2018-09-01 00:00'], ['2018-09-01 01:00'], ['2018-09-01 02:00']],\n        fields: [{type: ALL_FIELD_TYPES.timestamp, format: '', name: 'taro'}]\n      },\n      expected: {\n        rows: [['2018-09-01 00:00'], ['2018-09-01 01:00'], ['2018-09-01 02:00']],\n        fields: [\n          {\n            type: ALL_FIELD_TYPES.timestamp,\n            format: 'YYYY-M-D H:m',\n            name: 'taro',\n            analyzerType: 'DATETIME'\n          }\n        ]\n      },\n      msg: 'should reassign field type'\n    }\n  ];\n\n  cases.forEach(({input, expected, msg}) => {\n    t.deepEqual(validateInputData(input), expected, msg);\n  });\n\n  t.end();\n});\n\ntest('Processor -> processRowObject', t => {\n  t.equal(processRowObject({}), null, 'Should return null when rawData is empty');\n\n  const cases = [\n    {\n      input: [\n        {a: 1, b: 'c', c: true, d: '1.2'},\n        {a: 2, b: 'c', c: false, d: '1.3'},\n        {a: 3, b: 'd', c: true, d: '1.4'}\n      ],\n      expected: {\n        rows: [\n          [1, 'c', true, 1.2],\n          [2, 'c', false, 1.3],\n          [3, 'd', true, 1.4]\n        ],\n        fields: [\n          {\n            name: 'a',\n            id: 'a',\n            displayName: 'a',\n            type: 'integer',\n            format: '',\n            fieldIdx: 0,\n            analyzerType: 'INT',\n            valueAccessor: values => values[0]\n          },\n          {\n            name: 'b',\n            id: 'b',\n            displayName: 'b',\n            type: 'string',\n            format: '',\n            fieldIdx: 1,\n            analyzerType: 'STRING',\n            valueAccessor: values => values[1]\n          },\n          {\n            name: 'c',\n            id: 'c',\n            displayName: 'c',\n            type: 'boolean',\n            format: '',\n            fieldIdx: 2,\n            analyzerType: 'BOOLEAN',\n            valueAccessor: values => values[2]\n          },\n          {\n            name: 'd',\n            id: 'd',\n            displayName: 'd',\n            type: 'real',\n            format: '',\n            fieldIdx: 3,\n            analyzerType: 'FLOAT',\n            valueAccessor: values => values[3]\n          }\n        ]\n      },\n      msg: 'should parse correct row objects'\n    }\n  ];\n\n  cases.forEach(({input, expected, msg}) => {\n    const {fields, rows} = processRowObject(input);\n    cmpFields(t, fields, expected.fields, `${msg} fields`);\n    t.deepEqual(rows, expected.rows, msg);\n  });\n  t.end();\n});\n\ntest('Processor -> formatCsv', t => {\n  const geojsonFc = {\n    type: 'FeatureCollection',\n    features: [\n      {\n        type: 'Feature',\n        properties: {TRIPS: '11'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      {\n        type: 'Feature',\n        properties: {TRIPS: '10'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      },\n      {\n        type: 'Feature',\n        properties: {TRIPS: '9'},\n        geometry: {type: 'Point', coordinates: [[-122, 37]]}\n      }\n    ]\n  };\n\n  const cases = [\n    {\n      input:\n        `gps_data.lat,gps_data.types,epoch,has_result,id,begintrip_ts_local,date\\n` +\n        `29.9900937,driver_analytics,1472688000000,False,1,2016-10-01 09:41:39+00:00,2016-09-23\\n` +\n        `29.9927699,driver_analytics,1472688000000,False,2,2016-10-01 16:46:37+00:00,2016-09-23\\n` +\n        `29.9907261,driver_analytics,1472688000000,False,3,,2016-09-23`,\n      expected:\n        `gps_data.lat,gps_data.types,epoch,has_result,id,begintrip_ts_local,date\\n` +\n        `29.9900937,driver_analytics,1472688000000,false,1,2016-10-01 09:41:39+00:00,2016-09-23\\n` +\n        `29.9927699,driver_analytics,1472688000000,false,2,2016-10-01 16:46:37+00:00,2016-09-23\\n` +\n        `29.9907261,driver_analytics,1472688000000,false,3,,2016-09-23`,\n      processor: processCsvData,\n      msg: 'should format correct csv'\n    },\n    {\n      input: geojsonFc,\n      expected:\n        '_geojson,TRIPS\\n\"{\"\"type\"\":\"\"Feature\"\",\"\"properties\"\":{\"\"TRIPS\"\":11},\"\"geometry\"\":{\"\"type\"\":\"\"Point\"\",\"\"coordinates\"\":[[-122,37]]}}\",11\\n\"{\"\"type\"\":\"\"Feature\"\",\"\"properties\"\":{\"\"TRIPS\"\":10},\"\"geometry\"\":{\"\"type\"\":\"\"Point\"\",\"\"coordinates\"\":[[-122,37]]}}\",10\\n\"{\"\"type\"\":\"\"Feature\"\",\"\"properties\"\":{\"\"TRIPS\"\":9},\"\"geometry\"\":{\"\"type\"\":\"\"Point\"\",\"\"coordinates\"\":[[-122,37]]}}\",9',\n      processor: processGeojson,\n      msg: 'should format correct csv from geojsonInput'\n    }\n  ];\n\n  cases.forEach(({input, expected, msg, processor}) => {\n    const {fields, rows} = processor(input);\n    const dataContainer = createDataContainer(rows, {fields});\n\n    t.deepEqual(formatCsv(dataContainer, fields), expected, msg);\n  });\n  t.end();\n});\n\ntest('Processor -> analyzerTypeToFieldType', t => {\n  Object.keys(DATA_TYPES).forEach(atype => {\n    const spy = sinon.spy(Console, 'warn');\n\n    if (!ACCEPTED_ANALYZER_TYPES.includes(atype)) {\n      t.equal(\n        analyzerTypeToFieldType(atype),\n        'string',\n        'should return string if type not recognized'\n      );\n      t.ok(spy.calledOnce, `should warn when pass unrecognized type ${atype}`);\n    } else {\n      const fieldType = analyzerTypeToFieldType(atype);\n      t.ok(ALL_FIELD_TYPES[fieldType], `should assign ${atype} to one of ALL_FIELD_TYPES`);\n    }\n\n    spy.restore();\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/data-scale-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport {format as d3Format} from 'd3-format';\n\nimport {\n  getOrdinalDomain,\n  getQuantileDomain,\n  getLinearDomain,\n  getLogDomain,\n  createDataContainer,\n  getThresholdsFromQuantiles,\n  getDomainStepsbyZoom,\n  getQuantLabelFormat,\n  getHistogramDomain,\n  getQuantLegends,\n  getCategoricalColorMap\n} from '@kepler.gl/utils';\nimport {StateWFilesFiltersLayerColor} from 'test/helpers/mock-state';\nimport {SCALE_FUNC} from '@kepler.gl/constants';\n\nfunction numberSort(a, b) {\n  return a - b;\n}\n\ntest('DataScaleUtils -> getOrdinalDomain', t => {\n  const data = [['a'], ['a'], ['b'], [undefined], [null], [0], null];\n\n  function valueAccessor(d, dc) {\n    return dc.valueAt(d.index, 0);\n  }\n\n  t.deepEqual(\n    getOrdinalDomain(createDataContainer(data), valueAccessor),\n    [0, 'a', 'b'],\n    'should get correct ordinal domain'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getQuantileDomain', t => {\n  const data = ['a', 'b', 'c', 'b', undefined, null];\n  const quanData = [1, 4, 2, 3, 1, undefined, null, 0];\n  function valueAccessor(d) {\n    return d.value;\n  }\n\n  const values = [{value: 'a'}, {value: 'b'}, {value: 'b'}];\n\n  t.deepEqual(\n    getQuantileDomain(data, undefined, undefined),\n    ['a', 'b', 'b', 'c'],\n    'should get correct quantile domain'\n  );\n\n  t.deepEqual(\n    getQuantileDomain(quanData, undefined, numberSort),\n    [0, 1, 1, 2, 3, 4],\n    'should get correct quantile domain'\n  );\n\n  t.deepEqual(\n    getQuantileDomain(values, valueAccessor),\n    ['a', 'b', 'b'],\n    'should get correct quantile domain'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getLinearDomain', t => {\n  const quanData = [1, 4, 2, 3, 1, undefined, null, 0];\n  function valueAccessor(d) {\n    return d.value;\n  }\n\n  const values = [{value: 1}, {value: 0}, {value: -3}];\n\n  t.deepEqual(getLinearDomain(quanData, undefined), [0, 4], 'should get correct Linear domain');\n\n  t.deepEqual(getLinearDomain([10, 10]), [10, 10], 'should get correct Linear domain');\n\n  t.deepEqual(getLinearDomain([10, undefined]), [10, 10], 'should get correct Linear domain');\n\n  t.deepEqual(\n    getLinearDomain([undefined, undefined, null]),\n    [0, 1],\n    'should get correct Linear domain'\n  );\n\n  t.deepEqual(getLinearDomain(values, valueAccessor), [-3, 1], 'should get correct Linear domain');\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getLogDomain', t => {\n  function valueAccessor(d) {\n    return d.value;\n  }\n\n  t.deepEqual(\n    getLogDomain([{value: 1}, {value: 0}, {value: -3}], valueAccessor),\n    [-3, 1],\n    'should get correct Log domain with negative numbers'\n  );\n\n  t.deepEqual(\n    getLogDomain([{value: 1}, {value: 0}, {value: 3}], valueAccessor),\n    [0.00001, 3],\n    'should not contain a 0 in domain'\n  );\n\n  t.deepEqual(\n    getLogDomain([], valueAccessor),\n    [0.00001, 1],\n    'should have undefined domain for empty set'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getThresholdsFromQuantiles', t => {\n  t.deepEqual(\n    getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], 3),\n    [1.6666666666666665, 3.333333333333333],\n    'should get correct thresholds from quantiles'\n  );\n\n  t.deepEqual(\n    getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], 1),\n    [],\n    'should get correct thresholds from quantiles'\n  );\n\n  t.deepEqual(\n    getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], undefined),\n    [0, 5],\n    'should get correct thresholds from quantiles'\n  );\n  t.end();\n});\n\ntest('DataScaleUtils -> getDomainStepsbyZoom', t => {\n  const domain = [\n    [0, 1],\n    [0, 2],\n    [0, 3]\n  ];\n  const steps = [0, 2, 4];\n  [\n    {z: 0, expected: [0, 1]},\n    {z: 0.5, expected: [0, 1]},\n    {z: 1, expected: [0, 1]},\n    {z: 1.2, expected: [0, 1]},\n    {z: 2, expected: [0, 2]},\n    {z: 3.5, expected: [0, 2]},\n    {z: 4, expected: [0, 3]},\n    {z: 4.5, expected: [0, 3]},\n    {z: 10, expected: [0, 3]}\n  ].forEach(({z, expected}) => {\n    t.deepEqual(\n      getDomainStepsbyZoom(domain, steps, z),\n      expected,\n      `should get correct domain from zoom ${z}`\n    );\n  });\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getQuantLabelFormat', t => {\n  const fieldType = 'real';\n  const domain = [1, 2];\n\n  const format = getQuantLabelFormat(domain, fieldType);\n\n  t.deepEqual(format(0), '0', 'should get correct quant label format for 0');\n  t.deepEqual(format(null), 'no value', 'should get correct quant label format for null');\n  t.deepEqual(format(undefined), 'no value', 'should get correct quant label format for undefined');\n  t.deepEqual(format(1.0), '1', 'should get correct quant label format for number');\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getHistogramDomain', t => {\n  let aggregatedBins = [\n    {i: 0, value: 1, count: 1},\n    {i: 1, value: 2, count: 1},\n    {i: 2, value: 3, count: 1},\n    {i: 3, value: 4, count: 1}\n  ];\n  let histogramDomain = getHistogramDomain({aggregatedBins});\n\n  t.deepEqual(histogramDomain, [1, 4, 2.5]);\n\n  aggregatedBins = null;\n  histogramDomain = getHistogramDomain({aggregatedBins});\n\n  t.deepEqual(histogramDomain, [0, 0, 0]);\n\n  // test dataset\n  const InitialState = cloneDeep(StateWFilesFiltersLayerColor);\n  const {layers, datasets} = InitialState.visState;\n  const pointLayer = layers[0];\n  const dataset = datasets[pointLayer.config.dataId];\n  const field = dataset.fields[6];\n  const fieldValueAccessor = idx => dataset.getValue(field.name, idx);\n\n  histogramDomain = getHistogramDomain({dataset, fieldValueAccessor});\n  t.deepEqual(histogramDomain, [1, 12124, 912.2857142857143]);\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getQuantLegends', t => {\n  // scale, labelFormat\n  const labelFormat = d3Format('.1s');\n  const domain = [3.1, 5.2, 7.3];\n\n  const scaleFunction = SCALE_FUNC.threshold().domain(domain).range(domain);\n  scaleFunction.scaleType = 'linear';\n\n  const legends = getQuantLegends(scaleFunction, labelFormat);\n\n  const expectedColorBreaks = [\n    {data: 3.1, label: 'NaN to 3', range: [undefined, 3.1], inputs: [NaN, 3]},\n    {data: 5.2, label: '3 to 5', range: [3.1, 5.2], inputs: [3, 5]},\n    {data: 7.3, label: '5 to 7', range: [5.2, 7.3], inputs: [5, 7]}\n  ];\n\n  t.deepEqual(legends, expectedColorBreaks, 'should create correct threshold legends');\n\n  const customDomain = [3.1, 5.2, 7.3, null];\n  const customScaleFunction = SCALE_FUNC.threshold().domain(customDomain).range(customDomain);\n  customScaleFunction.scaleType = 'custom';\n  const customLegends = getQuantLegends(customScaleFunction, labelFormat);\n\n  const expectedCustomColorBreaks = [\n    {data: 3.1, label: 'Less than 3.1', range: [undefined, 3.1], inputs: [null, 3.1]},\n    {data: 5.2, label: '3.1 to 5.2', range: [3.1, 5.2], inputs: [3.1, 5.2]},\n    {data: 7.3, label: '5.2 to 7.3', range: [5.2, 7.3], inputs: [5.2, 7.3]},\n    {data: null, label: '7.3 or more', range: [7.3, null], inputs: [7.3, null]}\n  ];\n\n  t.deepEqual(\n    customLegends,\n    expectedCustomColorBreaks,\n    'should create correct threshold legends from custom breaks'\n  );\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> basic functionality', t => {\n  // Test empty inputs\n  t.deepEqual(\n    getCategoricalColorMap([], []),\n    [],\n    'should return empty array for empty colors and domain'\n  );\n\n  // Test empty colors with domain\n  t.deepEqual(\n    getCategoricalColorMap([], ['a', 'b']),\n    [],\n    'should return empty array for empty colors'\n  );\n\n  // Test empty domain with colors\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], []),\n    [\n      [null, '#ff0000'],\n      [null, '#00ff00']\n    ],\n    'should return colors with null values for empty domain'\n  );\n\n  // Test single color, single value\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000'], ['a']),\n    [['a', '#ff0000']],\n    'should map single value to single color'\n  );\n\n  // Test single color, multiple values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000'], ['a', 'b', 'c']),\n    [[['a', 'b', 'c'], '#ff0000']],\n    'should assign all values as array to single color when domain is larger'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> equal colors and domain', t => {\n  // Test equal number of colors and domain values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['a', 'b', 'c']),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00'],\n      ['c', '#0000ff']\n    ],\n    'should map each value to corresponding color when counts are equal'\n  );\n\n  // Test with numbers\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [2, 1]),\n    [\n      [1, '#ff0000'],\n      [2, '#00ff00']\n    ],\n    'should map sorted numeric values to colors'\n  );\n\n  // Test with mixed types (should be sorted)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], [2, 'a', 1]),\n    [\n      [1, '#ff0000'],\n      [2, '#00ff00'],\n      ['a', '#0000ff']\n    ],\n    'should sort mixed types and map to colors'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> more colors than domain', t => {\n  // Test more colors than domain values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['a', 'b']),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00'],\n      [null, '#0000ff']\n    ],\n    'should assign values one-to-one and leave extra colors with null'\n  );\n\n  // Test with single domain value and multiple colors\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['z']),\n    [\n      ['z', '#ff0000'],\n      [null, '#00ff00'],\n      [null, '#0000ff']\n    ],\n    'should assign single value to first color and leave others null'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> more domain than colors', t => {\n  // Test more domain values than colors\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['a', 'b', 'c', 'd']),\n    [\n      ['a', '#ff0000'],\n      [['b', 'c', 'd'], '#00ff00']\n    ],\n    'should assign remaining values as array to last color'\n  );\n\n  // Test with many domain values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['a', 'b', 'c', 'd', 'e', 'f']),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00'],\n      [['c', 'd', 'e', 'f'], '#0000ff']\n    ],\n    'should assign first values one-to-one up to penultimate color, then aggregate rest'\n  );\n\n  // Test with numeric values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [5, 1, 3, 2, 4]),\n    [\n      [1, '#ff0000'],\n      [[2, 3, 4, 5], '#00ff00']\n    ],\n    'should sort numeric values and aggregate excess to last color'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> data types and null handling', t => {\n  // Test with null and undefined values in domain\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['a', null, 'b', undefined]),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00']\n    ],\n    'should filter out null and undefined from domain'\n  );\n\n  // Test with only null/undefined in domain\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [null, undefined]),\n    [\n      [null, '#ff0000'],\n      [null, '#00ff00']\n    ],\n    'should return colors with null values when domain only contains null/undefined'\n  );\n\n  // Test with zero value\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [0, 1, 2]),\n    [\n      [0, '#ff0000'],\n      [[1, 2], '#00ff00']\n    ],\n    'should handle zero as valid value'\n  );\n\n  // Test with boolean values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [true, false]),\n    [\n      [false, '#ff0000'],\n      [true, '#00ff00']\n    ],\n    'should handle boolean values and sort them correctly'\n  );\n\n  // Test with string numbers\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['1', '10', '2']),\n    [\n      ['1', '#ff0000'],\n      [['10', '2'], '#00ff00']\n    ],\n    'should treat string numbers as strings and sort lexicographically'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> duplicate values', t => {\n  // Test with duplicate values in domain (should be deduplicated)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['a', 'b', 'a', 'b', 'c']),\n    [\n      ['a', '#ff0000'],\n      [['b', 'c'], '#00ff00']\n    ],\n    'should deduplicate domain values and sort them'\n  );\n\n  // Test with all duplicate values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['a', 'a', 'a']),\n    [\n      ['a', '#ff0000'],\n      [null, '#00ff00']\n    ],\n    'should handle all duplicate values correctly'\n  );\n\n  // Test with numeric duplicates\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [1, 2, 1, 3, 2]),\n    [\n      [1, '#ff0000'],\n      [[2, 3], '#00ff00']\n    ],\n    'should deduplicate and sort numeric values'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> sorting behavior', t => {\n  // Test string sorting\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['zebra', 'apple', 'banana']),\n    [\n      ['apple', '#ff0000'],\n      ['banana', '#00ff00'],\n      ['zebra', '#0000ff']\n    ],\n    'should sort strings alphabetically'\n  );\n\n  // Test numeric sorting (JavaScript default sort converts to strings)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], [10, 2, 1]),\n    [\n      [1, '#ff0000'],\n      [10, '#00ff00'],\n      [2, '#0000ff']\n    ],\n    'should sort numbers lexicographically (as strings)'\n  );\n\n  // Test lexicographic sorting with multi-digit numbers\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [100, 20, 3]),\n    [\n      [100, '#ff0000'],\n      [[20, 3], '#00ff00']\n    ],\n    'should sort multi-digit numbers lexicographically: 100 comes before 20'\n  );\n\n  // Test mixed string-number sorting (numbers come first when converted to strings)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['b', 1, 'a', 2]),\n    [\n      [1, '#ff0000'],\n      [2, '#00ff00'],\n      [['a', 'b'], '#0000ff']\n    ],\n    'should sort mixed types with numbers first'\n  );\n\n  // Test case sensitivity\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['B', 'a', 'A', 'b']),\n    [\n      ['A', '#ff0000'],\n      [['B', 'a', 'b'], '#00ff00']\n    ],\n    'should be case sensitive in sorting'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> edge cases', t => {\n  // Test with empty string\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['', 'a', 'b']),\n    [\n      ['', '#ff0000'],\n      [['a', 'b'], '#00ff00']\n    ],\n    'should handle empty string as valid value'\n  );\n\n  // Test with whitespace strings\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [' ', '  ', 'a']),\n    [\n      [' ', '#ff0000'],\n      [['  ', 'a'], '#00ff00']\n    ],\n    'should handle whitespace strings as distinct values'\n  );\n\n  // Test with very long arrays\n  const longDomain = Array.from({length: 100}, (_, i) => `item${i}`);\n  const result = getCategoricalColorMap(['#ff0000', '#00ff00'], longDomain);\n  t.equal(result.length, 2, 'should handle large domain arrays');\n  t.equal(result[0][0], 'item0', 'should assign first value correctly for large arrays');\n  t.true(\n    Array.isArray(result[1][0]),\n    'should aggregate remaining values into array for large domains'\n  );\n  t.equal(result[1][0].length, 99, 'should aggregate correct number of remaining values');\n\n  // Test with nested arrays/objects in domain (Set doesn't deduplicate by content)\n  const nestedArray = ['nested'];\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [nestedArray, 'string', nestedArray]),\n    [\n      [nestedArray, '#ff0000'],\n      ['string', '#00ff00']\n    ],\n    'should handle complex types in domain (same reference gets deduplicated)'\n  );\n\n  // Test with different array instances (no deduplication)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], [['nested'], 'string', ['nested']]),\n    [\n      [['nested'], '#ff0000'],\n      [['nested', 'string'], '#00ff00']\n    ],\n    'should not deduplicate arrays with same content but different references'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> real-world scenarios', t => {\n  // Test typical categorical data (countries)\n  const countries = ['USA', 'Canada', 'Mexico', 'Brazil', 'Argentina'];\n  const countryColors = ['#ff0000', '#00ff00', '#0000ff'];\n  t.deepEqual(\n    getCategoricalColorMap(countryColors, countries),\n    [\n      ['Argentina', '#ff0000'],\n      ['Brazil', '#00ff00'],\n      [['Canada', 'Mexico', 'USA'], '#0000ff']\n    ],\n    'should handle typical country categorical data'\n  );\n\n  // Test status/category data\n  const statuses = ['active', 'inactive', 'pending', 'archived'];\n  const statusColors = ['#28a745', '#dc3545', '#ffc107', '#6c757d'];\n  t.deepEqual(\n    getCategoricalColorMap(statusColors, statuses),\n    [\n      ['active', '#28a745'],\n      ['archived', '#dc3545'],\n      ['inactive', '#ffc107'],\n      ['pending', '#6c757d']\n    ],\n    'should handle status/category data with exact color mapping'\n  );\n\n  // Test product categories\n  const categories = ['Electronics', 'Clothing', 'Books', 'Home & Garden', 'Sports'];\n  const categoryColors = ['#ff6b6b', '#4ecdc4'];\n  t.deepEqual(\n    getCategoricalColorMap(categoryColors, categories),\n    [\n      ['Books', '#ff6b6b'],\n      [['Clothing', 'Electronics', 'Home & Garden', 'Sports'], '#4ecdc4']\n    ],\n    'should handle product categories with insufficient colors'\n  );\n\n  // Test age groups (numeric ranges as strings)\n  const ageGroups = ['18-25', '26-35', '36-45', '46-55', '55+'];\n  const ageColors = ['#e8f5e8', '#a8d8a8', '#68b968'];\n  t.deepEqual(\n    getCategoricalColorMap(ageColors, ageGroups),\n    [\n      ['18-25', '#e8f5e8'],\n      ['26-35', '#a8d8a8'],\n      [['36-45', '46-55', '55+'], '#68b968']\n    ],\n    'should handle age group ranges correctly'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> color format variations', t => {\n  // Test with different color formats (hex colors)\n  t.deepEqual(\n    getCategoricalColorMap(['#FF0000', '#00FF00', '#0000FF'], ['a', 'b', 'c']),\n    [\n      ['a', '#FF0000'],\n      ['b', '#00FF00'],\n      ['c', '#0000FF']\n    ],\n    'should handle uppercase hex colors'\n  );\n\n  // Test with short hex colors\n  t.deepEqual(\n    getCategoricalColorMap(['#f00', '#0f0', '#00f'], ['x', 'y', 'z']),\n    [\n      ['x', '#f00'],\n      ['y', '#0f0'],\n      ['z', '#00f']\n    ],\n    'should handle short hex color format'\n  );\n\n  // Test with named colors (if the function accepts them)\n  t.deepEqual(\n    getCategoricalColorMap(['red', 'green', 'blue'], ['first', 'second', 'third']),\n    [\n      ['first', 'red'],\n      ['second', 'green'],\n      ['third', 'blue']\n    ],\n    'should handle named color strings'\n  );\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> stress tests', t => {\n  // Test with single color and many domain values\n  const manyValues = Array.from({length: 50}, (_, i) => `value${i}`);\n  const singleColorResult = getCategoricalColorMap(['#ff0000'], manyValues);\n  t.equal(singleColorResult.length, 1, 'should return single color mapping for many values');\n  t.true(\n    Array.isArray(singleColorResult[0][0]),\n    'should aggregate all values into array for single color'\n  );\n  t.equal(singleColorResult[0][0].length, 50, 'should contain all 50 values in the array');\n  t.equal(singleColorResult[0][0][0], 'value0', 'should have first sorted value at start of array');\n\n  // Test with many colors and single domain value\n  const manyColors = Array.from({length: 20}, (_, i) => `#${i.toString(16).padStart(6, '0')}`);\n  const singleValueResult = getCategoricalColorMap(manyColors, ['onlyValue']);\n  t.equal(singleValueResult.length, 20, 'should return all colors when only one domain value');\n  t.equal(singleValueResult[0][0], 'onlyValue', 'should assign value to first color');\n  for (let i = 1; i < 20; i++) {\n    t.equal(singleValueResult[i][0], null, `color ${i} should have null value`);\n  }\n\n  // Test performance with moderately large datasets\n  const largeDomain = Array.from({length: 1000}, (_, i) => `item${i}`);\n  const largeColors = Array.from({length: 10}, (_, i) => `#color${i}`);\n  const largeResult = getCategoricalColorMap(largeColors, largeDomain);\n  t.equal(largeResult.length, 10, 'should handle large datasets efficiently');\n  t.true(Array.isArray(largeResult[9][0]), 'should aggregate many values to last color');\n\n  t.end();\n});\n\ntest('DataScaleUtils -> getCategoricalColorMap -> boundary conditions', t => {\n  // Test with exactly 2 colors and 2 values (boundary case)\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['b', 'a']),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00']\n    ],\n    'should handle boundary case of 2 colors and 2 values'\n  );\n\n  // Test with exactly 3 colors and 3 values\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['z', 'a', 'm']),\n    [\n      ['a', '#ff0000'],\n      ['m', '#00ff00'],\n      ['z', '#0000ff']\n    ],\n    'should handle boundary case of 3 colors and 3 values'\n  );\n\n  // Test transitioning from equal to more domains than colors\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00'], ['a', 'b', 'c']),\n    [\n      ['a', '#ff0000'],\n      [['b', 'c'], '#00ff00']\n    ],\n    'should transition correctly from equal to excess domain values'\n  );\n\n  // Test transitioning from more colors to equal\n  t.deepEqual(\n    getCategoricalColorMap(['#ff0000', '#00ff00', '#0000ff'], ['b', 'a']),\n    [\n      ['a', '#ff0000'],\n      ['b', '#00ff00'],\n      [null, '#0000ff']\n    ],\n    'should transition correctly from excess colors to matching'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/data-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {\n  clamp,\n  getRoundingDecimalFromStep,\n  preciseRound,\n  normalizeSliderValue,\n  roundValToStep,\n  snapToMarks,\n  getFormatter,\n  defaultFormatter,\n  formatNumber,\n  roundToFour\n} from '@kepler.gl/utils';\nimport {processLayerBounds} from '@kepler.gl/reducers';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\n\ntest('dataUtils -> clamp', t => {\n  t.equal(clamp([0, 1], 2), 1, 'should clamp 2 to 1 for [0,1]');\n  t.equal(clamp([0, 1], 0.5), 0.5, 'should not clamp 0.5 for [0,1]');\n  t.equal(clamp([-1, 1], -2), -1, 'should clamp -2 to -1 for [-1,1]');\n  t.equal(clamp([0, 10], 11), 10, 'should clamp 11 to 10 for [0,10]');\n  t.equal(clamp([0, 0], 1), 0, 'should clamp 1 to 0 for [0,0]');\n  t.end();\n});\n\ntest('dataUtils -> preciseRound', t => {\n  t.equal(preciseRound(1.234, 2), '1.23', 'should round 1.234 correctly');\n  t.equal(preciseRound(13.234, 0), '13', 'should round 13.234 correctly');\n  t.equal(preciseRound(13, 2), '13.00', 'should round 13 correctly');\n  t.equal(preciseRound(1.437, 2), '1.44', 'should round 1.437 correctly');\n  t.equal(preciseRound(0.09999999999999987, 8), '0.10000000', 'should round 0.10000000 correctly');\n  t.end();\n});\n\ntest('dataUtils -> roundToFour', t => {\n  t.equal(roundToFour(1.2344), 1.2344, 'should round 1.2344 to 4 decimals correctly');\n  t.equal(roundToFour(13.23445), 13.2345, 'should round 13.23445 to 4 decimals correctly');\n  t.equal(roundToFour(13), 13, 'should round 13 to 4 decimals correctly');\n  t.equal(roundToFour(1.437), 1.437, 'should round 1.437 to 4 decimals correctly');\n  t.equal(\n    roundToFour(0.09999999999999987),\n    0.1,\n    'should round 0.09999999999999987 to 4 decimals correctly'\n  );\n  t.end();\n});\n\ntest('dataUtils -> getRoundingDecimalFromStep', t => {\n  t.equal(getRoundingDecimalFromStep(1), 0, 'decimal of step=int should be 0');\n  t.equal(getRoundingDecimalFromStep(0.1), 1, 'decimal of step=0.1 should be 1');\n  t.equal(getRoundingDecimalFromStep(0.01), 2, 'decimal of step=0.01 should be 2');\n  t.equal(getRoundingDecimalFromStep(0.2), 1, 'decimal of step=0.2 should be 1');\n  t.equal(getRoundingDecimalFromStep(0.001), 3, 'decimal of step=0.001 should be 3');\n  t.equal(getRoundingDecimalFromStep(10), 0, 'decimal of step=10 should be 0');\n  t.equal(getRoundingDecimalFromStep(0.5), 1, 'decimal of step=0.5 should be 0');\n  t.equal(getRoundingDecimalFromStep(1.5), 1, 'decimal of step=1.5 should be 1');\n  t.equal(getRoundingDecimalFromStep(0.0000001), 7, 'decimal of step=1e-7 should be 7');\n  t.equal(getRoundingDecimalFromStep(0.0000000000123), 13, 'decimal of step=1.23e-11 should be 13');\n  t.end();\n});\n\ntest('dataUtils -> getRoundingDecimalFromStep', t => {\n  t.equal(roundValToStep(0, 0.1, 0.11), 0.1, 'should round 0.11 to 0.1');\n  t.equal(roundValToStep(0, 0.1, 0.1), 0.1, 'should round 0.1 to 0.1');\n  t.equal(roundValToStep(1, 0.1, 1.16), 1.2, 'should round 1.16 to 1.2');\n  t.equal(roundValToStep(1, 1, 1.6), 2, 'should round 1.6 to 2');\n  t.equal(roundValToStep(1, 1, 1.32), 1, 'should round 1.32 to 1');\n  t.equal(roundValToStep(1, 0.01, 1.435), 1.44, 'should round 1.435 to 1.44');\n  t.equal(roundValToStep(1, 0.001, 1.4357), 1.436, 'should round 1.4357 to 1.436');\n  t.equal(roundValToStep(0, 0.2, 1.5), 1.6, 'should round 1.5 to 1.6');\n  t.equal(roundValToStep(0, 0.5, 20.25), 20.5, 'should round 20.25 to 20.5');\n  t.equal(roundValToStep(0.3, 0.3, 12.77), 12.9, 'should round 12.77 to 12.9');\n  t.equal(roundValToStep(-13, 0.1, -10.77), -10.8, 'should round -10.77 to -10.8');\n  t.equal(roundValToStep(-30, 1, -14.5), -14, 'should round -14.5 to -14');\n  t.end();\n});\n\ntest('dataUtils -> snapToMarks', t => {\n  const marks = [0, 1, 3, 4.7, 5, 6.4, 10];\n  t.equal(snapToMarks(0, marks), 0, 'should snap to nearest mark');\n  t.equal(snapToMarks(-1, marks), 0, 'should snap to nearest mark');\n  t.equal(snapToMarks(2.2, marks), 3, 'should snap to nearest mark');\n  t.equal(snapToMarks(10, marks), 10, 'should snap to nearest mark');\n  t.equal(snapToMarks(11, marks), 10, 'should snap to nearest mark');\n  t.equal(snapToMarks(5, marks), 5, 'should snap to nearest mark');\n  t.equal(snapToMarks(2, marks), 1, 'should snap to nearest mark');\n\n  t.end();\n});\n\ntest('dataUtils -> normalizeSliderValue', t => {\n  t.equal(normalizeSliderValue(-1.1, 0, 1), -1, 'should normalize slider value based on step');\n  t.equal(normalizeSliderValue(4.4, 0, 1), 4, 'should round Val To Step');\n  t.equal(normalizeSliderValue(4.4, 0, 1, [1, 2, 3, 4.1, 5]), 4.1, 'should snap To Marks');\n  t.equal(normalizeSliderValue(4.4, 0, undefined), 4.4, 'is step is not defined return value');\n  t.equal(normalizeSliderValue(4.4, undefined, 1), 4.4, 'is minValue is not defined return value');\n\n  t.end();\n});\n\ntest('dataUtils -> defaultFormatter', t => {\n  t.equal(defaultFormatter(1), '1', 'defaultFormatter should be correct');\n  t.equal(defaultFormatter('a'), 'a', 'defaultFormatter should be correct');\n  t.equal(defaultFormatter(undefined), '', 'defaultFormatter should be correct');\n  t.equal(defaultFormatter(NaN), 'NaN', 'defaultFormatter should be correct');\n  t.equal(defaultFormatter(null), '', 'defaultFormatter should be correct');\n\n  t.end();\n});\n\ntest('dataUtils -> getFormatter', t => {\n  const TEST_CASES = [\n    {\n      input: [undefined],\n      assert: formatter => {\n        t.equal(formatter, defaultFormatter, 'should return defaultformatter');\n      }\n    },\n    {\n      input: ['.1s'],\n      assert: [134, '100']\n    },\n    {\n      input: ['~%'],\n      assert: ['12.345', '12.35%']\n    },\n    {\n      input: ['none exist'],\n      assert: formatter => {\n        t.equal(formatter, defaultFormatter, 'should return defaultformatter');\n      }\n    },\n    {\n      // custom format\n      input: [\n        'YYYY-MM-DD',\n        {\n          type: ALL_FIELD_TYPES.timestamp\n        }\n      ],\n      assert: [1593724860289, '2020-07-02']\n    },\n    {\n      // custom format\n      input: [\n        ',.2r',\n        {\n          type: ALL_FIELD_TYPES.integer\n        }\n      ],\n      assert: [4223, '4,200']\n    },\n    {\n      input: ['01'],\n      assert: [true, '1']\n    },\n    {\n      input: ['01'],\n      assert: [false, '0']\n    },\n    {\n      input: ['yn'],\n      assert: [false, 'no']\n    },\n    {\n      input: ['yn'],\n      assert: [true, 'yes']\n    },\n    {\n      input: ['L LT'],\n      assert: ['2011-04-10 00:00', '04/10/2011 12:00 AM']\n    },\n    {\n      input: ['L LT'],\n      assert: [null, '']\n    },\n    {\n      input: ['L LT'],\n      assert: [undefined, '']\n    },\n    {\n      input: ['L LT'],\n      assert: ['', '']\n    },\n    {\n      input: ['L'],\n      assert: ['2011-04-10', '04/10/2011']\n    },\n    {\n      input: ['L'],\n      assert: [null, '']\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    const formatter = getFormatter(...tc.input);\n    if (typeof tc.assert === 'function') {\n      tc.assert(formatter);\n    } else {\n      t.equal(formatter(tc.assert[0]), tc.assert[1], 'should return correct formatter');\n    }\n  });\n  t.end();\n});\n\ntest('dataUtils -> formatNumber', t => {\n  const TEST_CASES = [\n    {input: ['3.14'], output: '3.14', message: 'field type is not given'},\n    {input: ['3.14123'], output: '3.141', message: 'field type is not given'},\n    {input: ['3'], output: '3', message: 'field type is not given'},\n    {input: ['331', 'integer'], output: '331', message: 'format integer'},\n    {input: ['-33.1', 'integer'], output: '-33', message: 'format integer'},\n    {input: ['1234', 'integer'], output: '1,234', message: 'format integer'},\n    {input: ['123456', 'integer'], output: '123.5k', message: 'format integer'},\n    {input: ['123456.2', 'real'], output: '123.5k', message: 'format real'},\n    {input: ['123.23', 'real'], output: '123.2', message: 'format real'},\n    {input: ['12.3', 'real'], output: '12.3', message: 'format real'},\n    {input: ['12.345', 'real'], output: '12.35', message: 'format real'}\n  ];\n\n  TEST_CASES.forEach(tc => {\n    const output = formatNumber(...tc.input);\n    t.equal(output, tc.output, `formatNumber should be correct when ${tc.message}`);\n  });\n\n  t.end();\n});\n\ntest('dataUtils -> validateBounds', t => {\n  const TEST_CASES = [\n    {\n      input: [[10, -10, 20, -20]],\n      output: [10, -10, 20, -20],\n      message: 'should return the same bound for a single bound'\n    },\n    {\n      input: [\n        [10, -10, 20, -20],\n        [15, -15, 25, -25]\n      ],\n      output: [10, -15, 25, -20],\n      message: 'should return a combined bound for multiple bounds'\n    },\n    {\n      input: [\n        [10, -90, 20, -20],\n        [10, -10, 90, -90]\n      ],\n      output: [10, -89.9, 90, -20],\n      message: 'should handle latitude -90,90 correctly'\n    },\n    {\n      input: [\n        [-180, -90, 20, -20],\n        [15, -190, 25, -25]\n      ],\n      output: [-180, -89.9, 25, -20],\n      message: 'should handle extreme longitude values'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    const output = processLayerBounds(tc.input);\n    t.deepEqual(output, tc.output, tc.message);\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/dataset-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {findDefaultColorField, createNewDataEntry} from '@kepler.gl/utils';\nimport {isHexWkb} from '@kepler.gl/common-utils';\nimport {processCsvData} from '@kepler.gl/processors';\n\nimport csvData from 'test/fixtures/test-layer-data';\n\nconst DEFAULT_FIELD_TEST_CASES = [\n  {\n    name: 'excluded lat',\n    csv: csvData,\n    expected: 'trip_distance'\n  },\n  {\n    name: 'empty',\n    csv: 'a\\na',\n    expected: null\n  },\n  {\n    name: 'integer only',\n    csv: 'a,b\\na,0\\na,1',\n    expected: 'b'\n  },\n  {\n    name: 'integer and real',\n    csv: 'a,b,c\\na,0,0.5\\na,1,0.5',\n    expected: 'c'\n  },\n  {\n    name: 'excluded real',\n    csv: 'zipcode,b,c\\n0.5,0,0.5\\n0.5,1,0.5',\n    expected: 'c'\n  },\n  {\n    name: 'included real',\n    csv: 'zipcode mean,b,c\\n0.5,0,0.5\\n0.5,1,0.5',\n    expected: 'zipcode mean'\n  },\n  {\n    name: 'included real, with inclusion ordering',\n    csv: 'zipcode mean,a metric,b,c\\n0.5,0.1,0,0.5\\n0.5,0.1,1,0.5',\n    expected: 'a metric'\n  }\n];\n\ntest('datasetUtils.findDefaultColorField', t => {\n  for (const tc of DEFAULT_FIELD_TEST_CASES) {\n    const dataset = createNewDataEntry({\n      info: {id: 'taro'},\n      data: processCsvData(tc.csv)\n    }).taro;\n\n    const defaultField = findDefaultColorField(dataset);\n    if (!tc.expected) {\n      t.notOk(defaultField, `${tc.name}: default field is null`);\n    } else {\n      t.equals(defaultField.name, tc.expected, `${tc.name}: default field name is OK`);\n    }\n  }\n  t.end();\n});\n\ntest('datasetUtils.isHexWkb', t => {\n  t.notOk(isHexWkb(''), 'empty string is not a valid hex wkb');\n\n  t.notOk(isHexWkb(null), 'null is not a valid hex wkb');\n\n  const countyFIPS = '06075';\n  t.notOk(isHexWkb(countyFIPS), 'FIPS code should not be a valid hex wkb');\n\n  const h3Code = '8a2a1072b59ffff';\n  t.notOk(isHexWkb(h3Code), 'H3 code should not be a valid hex wkb');\n\n  const randomHexStr = '8a2a1072b59ffff';\n  t.notOk(isHexWkb(randomHexStr), 'A random hex string should not be a valid hex wkb');\n\n  const validWkt = '0101000000000000000000f03f0000000000000040';\n  t.ok(isHexWkb(validWkt), 'A valid hex wkb should be valid');\n\n  const validEWkt = '0101000020e6100000000000000000f03f0000000000000040';\n  t.ok(isHexWkb(validEWkt), 'A valid hex ewkb should be valid');\n\n  const validWktNDR = '00000000013ff0000000000000400000000000000040';\n  t.ok(isHexWkb(validWktNDR), 'A valid hex wkb in NDR should be valid');\n\n  const validEWktNDR = '0020000001000013ff0000000000400000000000000040';\n  t.ok(isHexWkb(validEWktNDR), 'A valid hex ewkb in NDR should be valid');\n});\n"
  },
  {
    "path": "test/node/utils/dom-to-image.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {setStyleSheetBaseHref} from '@kepler.gl/utils';\n\nconst MOCK_CSS = `div#header { \n    background-image: url('images/header-background.jpg');\n}`;\nconst EXPECTED_CSS = `div#header { \n    background-image: url('http://mock.kepler.com/js/v1.1.1/images/header-background.jpg');\n}`;\nconst MOCK_CSS_1 = `div#header { \n    background-image: url('./images/header-background.jpg');\n}`;\nconst EXPECTED_CSS_1 = `div#header { \n    background-image: url('http://mock.kepler.com/js/v1.1.1/images/header-background.jpg');\n}`;\nconst MOCK_CSS_2 = `div#header { \n    background-image: url('../images/header-background.jpg');\n}`;\nconst EXPECTED_CSS_2 = `div#header { \n    background-image: url('http://mock.kepler.com/js/images/header-background.jpg');\n}`;\n\nconst MOCK_CSS_3 = `@font-face {\n    src: url(\"data:application/font-woff;base64,d09GRg\");\n}`;\nconst MOCK_CSS_4 = `div#header { \n    background-image: url('http://mock.kepler.com/js/images/header-background.jpg');\n}`;\nconst BASE_HREF = 'http://mock.kepler.com/js/v1.1.1/main.css';\n\ntest('dom-to-image: setStyleSheetBaseHref', t => {\n  const TEST_CASES = [\n    {\n      args: {\n        text: MOCK_CSS,\n        base: BASE_HREF\n      },\n      expected: EXPECTED_CSS,\n      msg: 'should replace relative path'\n    },\n    {\n      args: {\n        text: MOCK_CSS_1,\n        base: BASE_HREF\n      },\n      expected: EXPECTED_CSS_1,\n      msg: 'should replace relative path'\n    },\n    {\n      args: {\n        text: MOCK_CSS_2,\n        base: BASE_HREF\n      },\n      expected: EXPECTED_CSS_2,\n      msg: 'should replace relative path'\n    },\n    {\n      args: {\n        text: MOCK_CSS_3,\n        base: BASE_HREF\n      },\n      expected: MOCK_CSS_3,\n      msg: 'should not change data url'\n    },\n    {\n      args: {\n        text: MOCK_CSS_4,\n        base: BASE_HREF\n      },\n      expected: MOCK_CSS_4,\n      msg: 'should not change absolute url'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    const result = setStyleSheetBaseHref(tc.args.text, tc.args.base);\n    t.equal(result, tc.expected, tc.msg);\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/duckdb-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport * as arrow from 'apache-arrow';\n\nimport {\n  SUPPORTED_DUCKDB_DROP_EXTENSIONS,\n  getDuckDBColumnTypesMap,\n  castDuckDBTypesForKepler,\n  setGeoArrowWKBExtension,\n  removeUnsupportedExtensions,\n  restoreUnsupportedExtensions,\n  isGeoArrowPoint,\n  isGeoArrowLineString,\n  isGeoArrowPolygon,\n  isGeoArrowMultiPoint,\n  isGeoArrowMultiLineString,\n  isGeoArrowMultiPolygon,\n  splitSqlStatements,\n  removeSQLComments,\n  sanitizeDuckDBTableName,\n  quoteTableName\n} from '../../../src/duckdb/src/table/duckdb-table-utils';\n\nimport {GEOARROW_EXTENSIONS, GEOARROW_METADATA_KEY} from '@kepler.gl/constants';\n\n// Test getDuckDBColumnTypesMap\ntest('duckdb-utils -> getDuckDBColumnTypesMap', t => {\n  const columns = [\n    {name: 'id', type: 'INTEGER'},\n    {name: 'name', type: 'VARCHAR'},\n    {name: 'location', type: 'GEOMETRY'}\n  ];\n\n  const result = getDuckDBColumnTypesMap(columns);\n\n  t.equal(result.id, 'INTEGER', 'should map id to INTEGER');\n  t.equal(result.name, 'VARCHAR', 'should map name to VARCHAR');\n  t.equal(result.location, 'GEOMETRY', 'should map location to GEOMETRY');\n  t.equal(Object.keys(result).length, 3, 'should have correct number of mappings');\n\n  t.end();\n});\n\n// Test getDuckDBColumnTypesMap with empty array\ntest('duckdb-utils -> getDuckDBColumnTypesMap empty', t => {\n  const columns = [];\n  const result = getDuckDBColumnTypesMap(columns);\n\n  t.deepEqual(result, {}, 'should return empty object for empty array');\n  t.end();\n});\n\n// Test castDuckDBTypesForKepler\ntest('duckdb-utils -> castDuckDBTypesForKepler', t => {\n  const tableName = 'test_table';\n  const columns = [\n    {name: 'id', type: 'INTEGER'},\n    {name: 'name', type: 'VARCHAR'},\n    {name: 'location', type: 'GEOMETRY'},\n    {name: 'big_number', type: 'BIGINT'}\n  ];\n\n  const result = castDuckDBTypesForKepler(tableName, columns);\n\n  const resultMock = `SELECT \"id\", \"name\", ST_AsWKB(\"location\") as \"location\", CAST(\"big_number\" AS DOUBLE) as \"big_number\" FROM \"test_table\"`;\n  t.equal(result, resultMock, 'should return correct SQL');\n\n  t.end();\n});\n\n// Test castDuckDBTypesForKepler with options\ntest('duckdb-utils -> castDuckDBTypesForKepler options', t => {\n  const tableName = 'test_table';\n  const columns = [\n    {name: 'location', type: 'GEOMETRY'},\n    {name: 'big_number', type: 'BIGINT'}\n  ];\n\n  const resultNoGeometry = castDuckDBTypesForKepler(tableName, columns, {\n    geometryToWKB: false,\n    bigIntToDouble: true\n  });\n  const resultNoGeometryMock = `SELECT \"location\", CAST(\"big_number\" AS DOUBLE) as \"big_number\" FROM \"test_table\"`;\n  t.equal(resultNoGeometry, resultNoGeometryMock, 'should return correct SQL');\n\n  const resultNoBigInt = castDuckDBTypesForKepler(tableName, columns, {\n    geometryToWKB: true,\n    bigIntToDouble: false\n  });\n  const resultNoBigIntMock = `SELECT ST_AsWKB(\"location\") as \"location\", \"big_number\" FROM \"test_table\"`;\n  t.equal(resultNoBigInt, resultNoBigIntMock, 'should return correct SQL');\n\n  t.end();\n});\n\n// Test castDuckDBTypesForKepler with DECIMAL types\ntest('duckdb-utils -> castDuckDBTypesForKepler with DECIMAL', t => {\n  const tableName = 'test_table';\n  const columns = [\n    {name: 'id', type: 'INTEGER'},\n    {name: 'price', type: 'DECIMAL(18,2)'},\n    {name: 'amount', type: 'DECIMAL'},\n    {name: 'rate', type: 'DECIMAL(10,5)'}\n  ];\n\n  const result = castDuckDBTypesForKepler(tableName, columns);\n\n  const resultMock = `SELECT \"id\", CAST(\"price\" AS DOUBLE) as \"price\", CAST(\"amount\" AS DOUBLE) as \"amount\", CAST(\"rate\" AS DOUBLE) as \"rate\" FROM \"test_table\"`;\n  t.equal(result, resultMock, 'should cast DECIMAL types to DOUBLE');\n\n  t.end();\n});\n\n// Test castDuckDBTypesForKepler\ntest('duckdb-utils -> castDuckDBTypesForKepler with complex table name', t => {\n  const tableName = '\"memory\".\"main\".\"earthquakes\"';\n  const columns = [{name: 'id', type: 'INTEGER'}];\n\n  const result = castDuckDBTypesForKepler(tableName, columns);\n\n  const resultMock = `SELECT \"id\" FROM \"memory\".\"main\".\"earthquakes\"`;\n  t.equal(result, resultMock, 'should return correct SQL');\n\n  t.end();\n});\n\n// Test setGeoArrowWKBExtension\ntest('duckdb-utils -> setGeoArrowWKBExtension', t => {\n  const columns = [\n    {name: 'id', type: 'INTEGER'},\n    {name: 'location', type: 'GEOMETRY'}\n  ];\n\n  // Create a mock arrow table with schema\n  const schema = new arrow.Schema([\n    new arrow.Field('id', new arrow.Int32()),\n    new arrow.Field('location', new arrow.Binary())\n  ]);\n\n  const table = new arrow.Table(schema);\n\n  setGeoArrowWKBExtension(table, columns);\n\n  // Check if the metadata was set for the geometry field\n  const locationField = table.schema.fields.find(f => f.name === 'location');\n  t.equal(\n    locationField.metadata.get(GEOARROW_METADATA_KEY),\n    GEOARROW_EXTENSIONS.WKB,\n    'should set WKB extension for GEOMETRY fields'\n  );\n\n  const idField = table.schema.fields.find(f => f.name === 'id');\n  t.equal(\n    idField.metadata.get(GEOARROW_METADATA_KEY),\n    undefined,\n    'should not set extension for non-GEOMETRY fields'\n  );\n\n  t.end();\n});\n\n// Test removeUnsupportedExtensions\ntest('duckdb-utils -> removeUnsupportedExtensions', t => {\n  const schema = new arrow.Schema([\n    new arrow.Field('id', new arrow.Int32()),\n    new arrow.Field('location', new arrow.Binary())\n  ]);\n\n  const table = new arrow.Table(schema);\n\n  // Add geoarrow extension to location field\n  table.schema.fields[1].metadata.set(GEOARROW_METADATA_KEY, GEOARROW_EXTENSIONS.WKB);\n\n  const removedExtensions = removeUnsupportedExtensions(table);\n\n  t.equal(removedExtensions.location, GEOARROW_EXTENSIONS.WKB, 'should return removed extension');\n  t.equal(\n    table.schema.fields[1].metadata.get(GEOARROW_METADATA_KEY),\n    undefined,\n    'should remove extension from field'\n  );\n\n  t.end();\n});\n\n// Test restoreUnsupportedExtensions\ntest('duckdb-utils -> restoreUnsupportedExtensions', t => {\n  const schema = new arrow.Schema([\n    new arrow.Field('id', new arrow.Int32()),\n    new arrow.Field('location', new arrow.Binary())\n  ]);\n\n  const table = new arrow.Table(schema);\n\n  const removedExtensions = {\n    location: GEOARROW_EXTENSIONS.WKB\n  };\n\n  restoreUnsupportedExtensions(table, removedExtensions);\n\n  t.equal(\n    table.schema.fields[1].metadata.get(GEOARROW_METADATA_KEY),\n    GEOARROW_EXTENSIONS.WKB,\n    'should restore extension to field'\n  );\n\n  t.end();\n});\n\n// Test isGeoArrowPoint\ntest('duckdb-utils -> isGeoArrowPoint', t => {\n  // Create a valid point type (FixedSizeList of 2 float64s)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  t.ok(isGeoArrowPoint(pointType), 'should recognize valid 2D point');\n\n  // Create a valid 3D point type (FixedSizeList of 3 float64s)\n  const point3DType = new arrow.FixedSizeList(3, new arrow.Field('', new arrow.Float64()));\n  t.ok(isGeoArrowPoint(point3DType), 'should recognize valid 3D point');\n\n  // Create an invalid point type (FixedSizeList of 5 float64s)\n  const invalidPointType = new arrow.FixedSizeList(5, new arrow.Field('', new arrow.Float64()));\n  t.notOk(isGeoArrowPoint(invalidPointType), 'should not recognize invalid point with size 5');\n\n  // Create a non-FixedSizeList type\n  const nonPointType = new arrow.Utf8();\n  t.notOk(isGeoArrowPoint(nonPointType), 'should not recognize non-FixedSizeList type');\n\n  // Create a FixedSizeList with non-float child\n  const nonFloatPointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Int32()));\n  t.notOk(isGeoArrowPoint(nonFloatPointType), 'should not recognize point with non-float child');\n\n  t.end();\n});\n\n// Test isGeoArrowLineString\ntest('duckdb-utils -> isGeoArrowLineString', t => {\n  // Create a valid linestring type (List of points)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  const lineStringType = new arrow.List(new arrow.Field('', pointType));\n  t.ok(isGeoArrowLineString(lineStringType), 'should recognize valid linestring');\n\n  // Create an invalid linestring type (List of non-points)\n  const invalidLineStringType = new arrow.List(new arrow.Field('', new arrow.Int32()));\n  t.notOk(isGeoArrowLineString(invalidLineStringType), 'should not recognize list of non-points');\n\n  // Create a non-List type\n  const nonListType = new arrow.Utf8();\n  t.notOk(isGeoArrowLineString(nonListType), 'should not recognize non-List type');\n\n  t.end();\n});\n\n// Test isGeoArrowPolygon\ntest('duckdb-utils -> isGeoArrowPolygon', t => {\n  // Create a valid polygon type (List of linestrings)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  const lineStringType = new arrow.List(new arrow.Field('', pointType));\n  const polygonType = new arrow.List(new arrow.Field('', lineStringType));\n  t.ok(isGeoArrowPolygon(polygonType), 'should recognize valid polygon');\n\n  // Create an invalid polygon type (List of non-linestrings)\n  const invalidPolygonType = new arrow.List(new arrow.Field('', new arrow.Int32()));\n  t.notOk(isGeoArrowPolygon(invalidPolygonType), 'should not recognize list of non-linestrings');\n\n  // Create a non-List type\n  const nonListType = new arrow.Utf8();\n  t.notOk(isGeoArrowPolygon(nonListType), 'should not recognize non-List type');\n\n  t.end();\n});\n\n// Test isGeoArrowMultiPoint\ntest('duckdb-utils -> isGeoArrowMultiPoint', t => {\n  // Create a valid multipoint type (List of points)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  const multiPointType = new arrow.List(new arrow.Field('', pointType));\n  t.ok(isGeoArrowMultiPoint(multiPointType), 'should recognize valid multipoint');\n\n  // Create an invalid multipoint type (List of non-points)\n  const invalidMultiPointType = new arrow.List(new arrow.Field('', new arrow.Int32()));\n  t.notOk(isGeoArrowMultiPoint(invalidMultiPointType), 'should not recognize list of non-points');\n\n  // Create a non-List type\n  const nonListType = new arrow.Utf8();\n  t.notOk(isGeoArrowMultiPoint(nonListType), 'should not recognize non-List type');\n\n  t.end();\n});\n\n// Test isGeoArrowMultiLineString\ntest('duckdb-utils -> isGeoArrowMultiLineString', t => {\n  // Create a valid multilinestring type (List of linestrings)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  const lineStringType = new arrow.List(new arrow.Field('', pointType));\n  const multiLineStringType = new arrow.List(new arrow.Field('', lineStringType));\n  t.ok(isGeoArrowMultiLineString(multiLineStringType), 'should recognize valid multilinestring');\n\n  // Create an invalid multilinestring type (List of non-linestrings)\n  const invalidMultiLineStringType = new arrow.List(new arrow.Field('', new arrow.Int32()));\n  t.notOk(\n    isGeoArrowMultiLineString(invalidMultiLineStringType),\n    'should not recognize list of non-linestrings'\n  );\n\n  // Create a non-List type\n  const nonListType = new arrow.Utf8();\n  t.notOk(isGeoArrowMultiLineString(nonListType), 'should not recognize non-List type');\n\n  t.end();\n});\n\n// Test isGeoArrowMultiPolygon\ntest('duckdb-utils -> isGeoArrowMultiPolygon', t => {\n  // Create a valid multipolygon type (List of polygons)\n  const pointType = new arrow.FixedSizeList(2, new arrow.Field('', new arrow.Float64()));\n  const lineStringType = new arrow.List(new arrow.Field('', pointType));\n  const polygonType = new arrow.List(new arrow.Field('', lineStringType));\n  const multiPolygonType = new arrow.List(new arrow.Field('', polygonType));\n  t.ok(isGeoArrowMultiPolygon(multiPolygonType), 'should recognize valid multipolygon');\n\n  // Create an invalid multipolygon type (List of non-polygons)\n  const invalidMultiPolygonType = new arrow.List(new arrow.Field('', new arrow.Int32()));\n  t.notOk(\n    isGeoArrowMultiPolygon(invalidMultiPolygonType),\n    'should not recognize list of non-polygons'\n  );\n\n  // Create a non-List type\n  const nonListType = new arrow.Utf8();\n  t.notOk(isGeoArrowMultiPolygon(nonListType), 'should not recognize non-List type');\n\n  t.end();\n});\n\n// Test splitSqlStatements\ntest('duckdb-utils -> splitSqlStatements', t => {\n  // Test simple case\n  const simpleSQL = 'SELECT * FROM table1; SELECT * FROM table2;';\n  const simpleResult = splitSqlStatements(simpleSQL);\n  t.equal(simpleResult.length, 2, 'should split simple statements');\n  t.equal(simpleResult[0], 'SELECT * FROM table1', 'should trim semicolon');\n  t.equal(simpleResult[1], 'SELECT * FROM table2', 'should trim semicolon');\n\n  // Test with quoted strings\n  const quotedSQL = 'SELECT \\'hello;world\\' FROM table1; SELECT \"col;name\" FROM table2;';\n  const quotedResult = splitSqlStatements(quotedSQL);\n  t.equal(quotedResult.length, 2, 'should handle quoted semicolons');\n  t.ok(quotedResult[0].includes('hello;world'), 'should preserve semicolon in single quotes');\n  t.ok(quotedResult[1].includes('col;name'), 'should preserve semicolon in double quotes');\n\n  // Test with comments\n  const commentSQL = 'SELECT * FROM table1; -- comment with ; semicolon\\nSELECT * FROM table2;';\n  const commentResult = splitSqlStatements(commentSQL);\n  t.equal(commentResult.length, 2, 'should handle line comments');\n\n  // Test with block comments\n  const blockCommentSQL =\n    'SELECT * FROM table1 /* block comment ; with semicolon */ SELECT * FROM table2;';\n  const blockCommentResult = splitSqlStatements(blockCommentSQL);\n  t.equal(blockCommentResult.length, 1, 'should handle block comments');\n\n  // Test with escaped quotes\n  const escapedSQL =\n    'SELECT \\'it\\'\\'s working\\' FROM table1; SELECT \"he said \"\"hello\"\"\" FROM table2;';\n  const escapedResult = splitSqlStatements(escapedSQL);\n  t.equal(escapedResult.length, 2, 'should handle escaped quotes');\n\n  // Test empty statements\n  const emptySQL = ';;; SELECT * FROM table1;; ; SELECT * FROM table2;;;';\n  const emptyResult = splitSqlStatements(emptySQL);\n  t.equal(emptyResult.length, 2, 'should filter out empty statements');\n\n  // Test single statement without semicolon\n  const singleSQL = 'SELECT * FROM table1';\n  const singleResult = splitSqlStatements(singleSQL);\n  t.equal(singleResult.length, 1, 'should handle single statement without semicolon');\n  t.equal(singleResult[0], 'SELECT * FROM table1', 'should return trimmed statement');\n\n  // Test empty string\n  const emptyString = '';\n  const emptyStringResult = splitSqlStatements(emptyString);\n  t.equal(emptyStringResult.length, 0, 'should return empty array for empty string');\n\n  t.end();\n});\n\n// Test removeSQLComments\ntest('duckdb-utils -> removeSQLComments', t => {\n  // Test line comments\n  const lineComment = 'SELECT * FROM table1; -- this is a comment';\n  const lineResult = removeSQLComments(lineComment);\n  t.equal(lineResult, 'SELECT * FROM table1;', 'should remove line comments');\n\n  // Test block comments\n  const blockComment = 'SELECT * FROM table1 /* this is a block comment */ WHERE id = 1;';\n  const blockResult = removeSQLComments(blockComment);\n  t.equal(blockResult, 'SELECT * FROM table1  WHERE id = 1;', 'should remove block comments');\n\n  // Test multiple line comments\n  const multiLineComment = 'SELECT * FROM table1; -- comment 1\\nSELECT * FROM table2; -- comment 2';\n  const multiLineResult = removeSQLComments(multiLineComment);\n  t.equal(\n    multiLineResult,\n    'SELECT * FROM table1; \\nSELECT * FROM table2;',\n    'should remove multiple line comments'\n  );\n\n  // Test multiline block comments\n  const multiBlockComment =\n    'SELECT * FROM table1 /* this is a\\nmultiline\\nblock comment */ WHERE id = 1;';\n  const multiBlockResult = removeSQLComments(multiBlockComment);\n  t.equal(\n    multiBlockResult,\n    'SELECT * FROM table1  WHERE id = 1;',\n    'should remove multiline block comments'\n  );\n\n  // Test mixed comments\n  const mixedComment = 'SELECT * FROM table1 /* block */ WHERE id = 1; -- line comment';\n  const mixedResult = removeSQLComments(mixedComment);\n  t.equal(mixedResult, 'SELECT * FROM table1  WHERE id = 1;', 'should remove mixed comments');\n\n  // Test no comments\n  const noComment = 'SELECT * FROM table1 WHERE id = 1;';\n  const noCommentResult = removeSQLComments(noComment);\n  t.equal(\n    noCommentResult,\n    'SELECT * FROM table1 WHERE id = 1;',\n    'should leave SQL without comments unchanged'\n  );\n\n  t.end();\n});\n\n// Test sanitizeDuckDBTableName\ntest('duckdb-utils -> sanitizeDuckDBTableName', t => {\n  // Test valid name\n  const validName = 'valid_table_name';\n  t.equal(sanitizeDuckDBTableName(validName), validName, 'should keep valid name unchanged');\n\n  // Test name with spaces\n  const spaceName = 'table name with spaces';\n  t.equal(\n    sanitizeDuckDBTableName(spaceName),\n    'table_name_with_spaces',\n    'should replace spaces with underscores'\n  );\n\n  // Test name with special characters\n  const specialName = 'table-name.with@special#chars';\n  t.equal(\n    sanitizeDuckDBTableName(specialName),\n    'table_name_with_special_chars',\n    'should replace special characters with underscores'\n  );\n\n  // Test name starting with number\n  const numberName = '123table';\n  t.equal(\n    sanitizeDuckDBTableName(numberName),\n    't_123table',\n    'should prepend t_ to names starting with number'\n  );\n\n  // Test empty string\n  const emptyName = '';\n  t.equal(\n    sanitizeDuckDBTableName(emptyName),\n    'default_table',\n    'should return default_table for empty string'\n  );\n\n  // Test file extensions\n  const csvFile = 'data.csv';\n  t.equal(sanitizeDuckDBTableName(csvFile), 'data_csv', 'should handle file extensions');\n\n  // Test complex case\n  const complexName = '123-data file.csv';\n  t.equal(\n    sanitizeDuckDBTableName(complexName),\n    't_123_data_file_csv',\n    'should handle complex cases'\n  );\n\n  t.end();\n});\n\n// Test quoteTableName\ntest('duckdb-utils -> quoteTableName', t => {\n  // Test simple table names (should get quoted)\n  t.equal(quoteTableName('simple_table'), '\"simple_table\"', 'should quote simple table name');\n  t.equal(quoteTableName('table'), '\"table\"', 'should quote single word table name');\n\n  // Test table names with special characters (should get quoted)\n  t.equal(quoteTableName('table name'), '\"table name\"', 'should quote table name with spaces');\n  t.equal(quoteTableName('table-name'), '\"table-name\"', 'should quote table name with hyphens');\n  t.equal(\n    quoteTableName('table@name'),\n    '\"table@name\"',\n    'should quote table name with special chars'\n  );\n\n  // Test already quoted simple names (should get quoted again with proper escaping)\n  t.equal(\n    quoteTableName('already\"quoted'),\n    '\"already\"\"quoted\"',\n    'should escape quotes in table name'\n  );\n  t.equal(quoteTableName('\"quoted\"'), '\"quoted\"', 'should preserve already quoted table name');\n  t.equal(\n    quoteTableName('\"table with spaces\"'),\n    '\"table with spaces\"',\n    'should preserve already quoted table name with spaces'\n  );\n  t.equal(\n    quoteTableName('\"table-with-hyphens\"'),\n    '\"table-with-hyphens\"',\n    'should preserve already quoted table name with hyphens'\n  );\n\n  t.equal(\n    quoteTableName('\"memory\".\"main\".\"earthquakes\"'),\n    '\"memory\".\"main\".\"earthquakes\"',\n    'should preserve complex qualified name'\n  );\n\n  t.equal(\n    quoteTableName('\"my schema\".\"db-name\".\"schema_name\".\"table-name\"'),\n    '\"my schema\".\"db-name\".\"schema_name\".\"table-name\"',\n    'should preserve qualified name with special chars'\n  );\n\n  // Test qualified names with dots inside quotes (would break old parsing logic)\n  t.equal(\n    quoteTableName('\"my.schema\".\"table.name\"'),\n    '\"my.schema\".\"table.name\"',\n    'should preserve qualified name with dots inside quotes'\n  );\n\n  // Test invalid fully qualified names (should get quoted)\n  t.equal(quoteTableName('schema.table'), '\"schema.table\"', 'should quote unquoted qualified name');\n  t.equal(\n    quoteTableName('\"schema\".table'),\n    '\"schema\".table',\n    'should preserve partially quoted name (trust user intent)'\n  );\n  t.equal(\n    quoteTableName('schema.\"table\"'),\n    'schema.\"table\"',\n    'should preserve partially quoted name (trust user intent)'\n  );\n  t.equal(\n    quoteTableName('bad.\"format'),\n    'bad.\"format',\n    'should preserve name with dots and quotes (trust user intent)'\n  );\n\n  // Test edge cases\n  t.equal(quoteTableName(''), '\"\"', 'should handle empty string');\n  t.equal(quoteTableName('123table'), '\"123table\"', 'should quote name starting with number');\n  t.equal(quoteTableName('table.'), '\"table.\"', 'should quote name ending with dot');\n  t.equal(quoteTableName('.table'), '\".table\"', 'should quote name starting with dot');\n\n  // Test cases that should NOT be treated as qualified names\n  t.equal(\n    quoteTableName('table.with.dots'),\n    '\"table.with.dots\"',\n    'should quote name with dots but no quotes'\n  );\n  t.equal(\n    quoteTableName('\"single.quoted.name\"'),\n    '\"single.quoted.name\"',\n    'should preserve single quoted name with dots as simple identifier'\n  );\n\n  t.end();\n});\n\n// Test SUPPORTED_DUCKDB_DROP_EXTENSIONS constant\ntest('duckdb-utils -> SUPPORTED_DUCKDB_DROP_EXTENSIONS', t => {\n  t.ok(Array.isArray(SUPPORTED_DUCKDB_DROP_EXTENSIONS), 'should be an array');\n  t.ok(SUPPORTED_DUCKDB_DROP_EXTENSIONS.includes('csv'), 'should include csv');\n  t.ok(SUPPORTED_DUCKDB_DROP_EXTENSIONS.includes('json'), 'should include json');\n  t.ok(SUPPORTED_DUCKDB_DROP_EXTENSIONS.includes('parquet'), 'should include parquet');\n  t.ok(SUPPORTED_DUCKDB_DROP_EXTENSIONS.includes('geojson'), 'should include geojson');\n  t.ok(SUPPORTED_DUCKDB_DROP_EXTENSIONS.includes('arrow'), 'should include arrow');\n  t.equal(SUPPORTED_DUCKDB_DROP_EXTENSIONS.length, 5, 'should have 5 supported extensions');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/editor-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {EditableGeoJsonLayer} from '@nebula.gl/layers';\n\nimport {INITIAL_VIS_STATE} from '@kepler.gl/reducers';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {EDITOR_LAYER_ID, EDITOR_MODES} from '@kepler.gl/constants';\nimport {EditorLayerUtils, getEditorLayer} from '@kepler.gl/layers';\n\ntest('editorLayerUtils -> isDrawingActive', t => {\n  t.equal(\n    EditorLayerUtils.isDrawingActive(true, EDITOR_MODES.EDIT),\n    false,\n    'Should return false for non-drawing mode'\n  );\n  t.equal(\n    EditorLayerUtils.isDrawingActive(false, EDITOR_MODES.DRAW_POLYGON),\n    false,\n    'Should return false as editor UI is disactivated'\n  );\n  t.equal(\n    EditorLayerUtils.isDrawingActive(true, EDITOR_MODES.DRAW_POLYGON),\n    true,\n    'Should return true for activated editor UI and draw mode'\n  );\n  t.end();\n});\n\ntest('editorLayerUtils -> getCursor', t => {\n  const {editor} = INITIAL_VIS_STATE;\n  const mockSettings = {\n    editorMenuActive: true,\n    editor\n  };\n  t.equal(\n    EditorLayerUtils.getCursor(mockSettings),\n    'crosshair',\n    'Should return crosshair for active drawing mode'\n  );\n\n  mockSettings.editorMenuActive = false;\n  t.equal(\n    EditorLayerUtils.getCursor(mockSettings),\n    null,\n    'Should return null as editor has no suggestions about cursor'\n  );\n\n  mockSettings.hoverInfo = {\n    layer: {\n      id: EDITOR_LAYER_ID\n    }\n  };\n  mockSettings.editor = {...mockSettings.editor, selectedFeature: {}};\n  t.equal(EditorLayerUtils.getCursor(mockSettings), 'move', 'Should return move cursor');\n\n  t.end();\n});\n\ntest('editorLayerUtils -> getTooltip', t => {\n  const {editor} = INITIAL_VIS_STATE;\n  const info = {\n    layer: {state: {mode: {_clickSequence: null}}},\n    object: {}\n  };\n\n  t.equal(\n    EditorLayerUtils.getTooltip(info, {\n      editor: {...editor, selectionContext: {rightClick: true}},\n      theme: {},\n      editorMenuActive: true\n    }),\n    null,\n    'Should return null when the feature menu is visible'\n  );\n\n  info.layer.state.mode._clickSequence = [1];\n  t.equal(\n    EditorLayerUtils.getTooltip(info, {editor, theme: {}, editorMenuActive: true}),\n    null,\n    'Should return null as drawing is active and started'\n  );\n\n  info.layer.state.mode._clickSequence = [];\n  t.equal(\n    EditorLayerUtils.getTooltip(info, {editor, theme: {}, editorMenuActive: true})?.text,\n    'Click to start new feature',\n    'Should return a tooltip as drawing is active and started'\n  );\n\n  info.layer.id = EDITOR_LAYER_ID;\n\n  t.deepEqual(\n    EditorLayerUtils.getTooltip(\n      {...info, object: {id: 1}},\n      {editor: {...editor, selectedFeature: {id: 1}}, theme: {}, editorMenuActive: false}\n    )?.text,\n    'Right click to view options\\nDrag to move the feature',\n    'Should return a tooltip for selected feature'\n  );\n\n  t.equal(\n    EditorLayerUtils.getTooltip(\n      {...info, object: {geometry: {type: 'Point'}}},\n      {editor, theme: {}, editorMenuActive: false}\n    )?.text,\n    'Drag to move the point',\n    'Should return a tooltip for hovered point'\n  );\n\n  t.equal(\n    EditorLayerUtils.getTooltip(\n      {...info, object: {properties: {guideType: 'tentative'}}},\n      {editor, theme: {}, editorMenuActive: false}\n    )?.text,\n    'Drag to move the point',\n    'Should return a tooltip for hovered tentative point'\n  );\n\n  t.equal(\n    EditorLayerUtils.getTooltip(\n      {...info, object: {properties: {editHandleType: 'intermediate'}}},\n      {editor, theme: {}, editorMenuActive: false}\n    )?.text,\n    'Click to insert a point',\n    'Should return a tooltip for hovered lines'\n  );\n\n  t.equal(\n    EditorLayerUtils.getTooltip(info, {editor, theme: {}, editorMenuActive: false})?.text,\n    'Click to select the feature\\nRight click to view options',\n    'Should return a tooltip for not selected feature'\n  );\n\n  info.layer.id = 'any';\n\n  t.equal(\n    EditorLayerUtils.getTooltip(info, {\n      editor,\n      theme: {},\n      editorMenuActive: false\n    }),\n    null,\n    'Shouldnt return tooltip'\n  );\n\n  t.end();\n});\n\ntest('editorLayerUtils -> onHover', t => {\n  const {editor} = INITIAL_VIS_STATE;\n  const info = {\n    layer: {},\n    object: {}\n  };\n  const hoverInfo = {\n    layer: {}\n  };\n\n  t.equal(\n    EditorLayerUtils.onHover(info, {editor, editorMenuActive: true, hoverInfo}),\n    true,\n    'Should return true as drawing is active'\n  );\n\n  t.equal(\n    EditorLayerUtils.onHover(info, {editor, editorMenuActive: false, hoverInfo}),\n    false,\n    \"Should return false as drawing isn't active\"\n  );\n\n  info.layer.id = EDITOR_LAYER_ID;\n  t.equal(\n    EditorLayerUtils.onHover(info, {editor, editorMenuActive: false, hoverInfo}),\n    false,\n    \"Should return false as info and hoverInfo aren't yet synced\"\n  );\n\n  hoverInfo.layer.id = EDITOR_LAYER_ID;\n  t.equal(\n    EditorLayerUtils.onHover(info, {editor, editorMenuActive: false, hoverInfo}),\n    true,\n    'Should return true for editor layer'\n  );\n\n  t.end();\n});\n\ntest('editorLayerUtils -> onClick', t => {\n  const {editor} = INITIAL_VIS_STATE;\n  const info = {\n    layer: {},\n    object: {}\n  };\n  const event = {};\n\n  const {onLayerClick, setSelectedFeature} = VisStateActions;\n\n  t.equal(\n    EditorLayerUtils.onClick(info, event, {\n      editor,\n      editorMenuActive: true,\n      onLayerClick,\n      setSelectedFeature\n    }),\n    true,\n    'Should return true - onClick is handled as drawing is active'\n  );\n\n  t.equal(\n    EditorLayerUtils.onClick(info, event, {\n      editor,\n      editorMenuActive: false,\n      onLayerClick,\n      setSelectedFeature\n    }),\n    false,\n    \"Should return false - onClick isn't handled\"\n  );\n\n  info.layer.id = EDITOR_LAYER_ID;\n  t.equal(\n    EditorLayerUtils.onClick(info, event, {\n      editor,\n      editorMenuActive: false,\n      onLayerClick,\n      setSelectedFeature\n    }),\n    true,\n    'Should return true - onClick is handled'\n  );\n\n  t.end();\n});\n\ntest('editorLayerUtils -> getEditorLayer', t => {\n  const {editor} = INITIAL_VIS_STATE;\n\n  const editorLayer = getEditorLayer({\n    editorMenuActive: false,\n    editor,\n    onSetFeatures: VisStateActions.setFeatures,\n    setSelectedFeature: VisStateActions.setSelectedFeature,\n    featureCollection: {\n      features: [],\n      type: 'FeatureCollection'\n    },\n    selectedFeatureIndexes: [],\n    viewport: null\n  });\n  t.ok(editorLayer instanceof EditableGeoJsonLayer, 'Should return an editable layer');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/effect-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {LightingEffect, PostProcessEffect} from '@deck.gl/core';\nimport test from 'tape';\n\nimport {computeDeckEffects, validateEffectParameters} from '@kepler.gl/utils';\nimport {VisStateActions} from '@kepler.gl/actions';\nimport {visStateReducer} from '@kepler.gl/reducers';\nimport {createEffect} from '@kepler.gl/effects';\nimport {\n  POSTPROCESSING_EFFECTS,\n  LIGHT_AND_SHADOW_EFFECT,\n  DEFAULT_POST_PROCESSING_EFFECT_TYPE\n} from '@kepler.gl/constants';\n\nimport {InitialState} from 'test/helpers/mock-state';\n\ntest('effectUtils -> computeDeckEffects', t => {\n  const initialState = InitialState.visState;\n  let nextState = visStateReducer(\n    initialState,\n    VisStateActions.addEffect({id: 'e_1', type: 'sepia', isEnabled: false})\n  );\n  nextState = visStateReducer(initialState, VisStateActions.addEffect({id: 'e_1', type: 'ink'}));\n  nextState = visStateReducer(\n    nextState,\n    VisStateActions.addEffect({\n      id: 'e_shadow',\n      type: 'lightAndShadow',\n      parameters: {timestamp: 1689383452635}\n    })\n  );\n\n  let deckEffects = computeDeckEffects({\n    visState: nextState,\n    mapState: {latitude: 51.033105, longitude: 0.348512}\n  });\n\n  t.equal(deckEffects.length, 2, \"disabled deck effects aren't not generated\");\n  t.ok(deckEffects[0] instanceof LightingEffect, 'lighting effect should be generated');\n  t.ok(deckEffects[1] instanceof PostProcessEffect, 'post-processing effect should be generated');\n\n  // nighttime\n  t.equal(deckEffects[0].outputUniformShadow, true, 'shadows should be applied uniformly');\n  t.equal(deckEffects[0].directionalLights[0].intensity, 0, 'directional light should be disabled');\n\n  // daytime\n  nextState.effects[1].setProps({parameters: {timestamp: 1689415852635}});\n  deckEffects = computeDeckEffects({\n    visState: nextState,\n    mapState: {latitude: 51.033105, longitude: 0.348512}\n  });\n  t.equal(deckEffects[0].shadowColor[3], 0.5, 'shadows should be enabled');\n  t.equal(deckEffects[0].directionalLights[0].intensity, 1, 'directional light should be enabled');\n\n  t.end();\n});\n\ntest('effectUtils -> createEffect', t => {\n  const defaultEffect = createEffect({});\n  const postProcessingEffect = createEffect({\n    type: POSTPROCESSING_EFFECTS.hueSaturation.type\n  });\n  const lightEffect = createEffect({type: LIGHT_AND_SHADOW_EFFECT.type});\n\n  t.equal(\n    defaultEffect.type,\n    DEFAULT_POST_PROCESSING_EFFECT_TYPE,\n    'should create default ink effect'\n  );\n  t.equal(\n    postProcessingEffect.type,\n    POSTPROCESSING_EFFECTS.hueSaturation.type,\n    'should create hueSaturation effect'\n  );\n  t.equal(lightEffect.type, LIGHT_AND_SHADOW_EFFECT.type, 'should create Light&Shadow effect');\n\n  t.end();\n});\n\ntest('effectUtils -> validateEffectParameters', t => {\n  const testCases = [\n    {\n      input: {\n        parameters: {},\n        config: POSTPROCESSING_EFFECTS.magnify.parameters\n      },\n      expected: {}\n    },\n    {\n      input: {\n        parameters: {\n          screenXY: [10, 'x'],\n          radiusPixels: 1000,\n          zoom: 0,\n          borderWidthPixels: 'str'\n        },\n        config: POSTPROCESSING_EFFECTS.magnify.parameters\n      },\n      expected: {\n        screenXY: [1, 0.5],\n        radiusPixels: 500,\n        zoom: 0.5,\n        borderWidthPixels: 3\n      }\n    },\n    {\n      input: {\n        parameters: {\n          blurRadius: null,\n          gradientRadius: [20, 10],\n          start: [10],\n          end: 'str'\n        },\n        config: POSTPROCESSING_EFFECTS.tiltShift.parameters\n      },\n      expected: {blurRadius: 0, gradientRadius: 0, start: [0, 0], end: [1, 1]}\n    }\n  ];\n\n  testCases.forEach((testCase, index) => {\n    const result = validateEffectParameters(testCase.input.parameters, testCase.input.config);\n    t.deepEqual(result, testCase.expected, `parameters should be property validated ${index}`);\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/export-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {registerEntry} from '@kepler.gl/actions';\nimport keplerGlReducer from '@kepler.gl/reducers';\nimport {\n  getMapJSON,\n  exportToJsonString,\n  getScaleFromImageSize,\n  isMSEdge,\n  calculateExportImageSize\n} from '@kepler.gl/utils';\nimport {EXPORT_IMG_RATIOS, RESOLUTIONS} from '@kepler.gl/constants';\n\ntest('exportUtils -> ExportJson', t => {\n  const state = keplerGlReducer(undefined, registerEntry({id: 'test'})).test;\n  const body = exportToJsonString(getMapJSON(state));\n  getScaleFromImageSize;\n  t.equal(typeof body, 'string', 'Should validate the type of body to be a string');\n\n  t.doesNotThrow(() => {\n    JSON.parse(body);\n  }, 'Should not throw when trying to parse body');\n\n  t.end();\n});\n\ntest('exportUtils -> getScaleFromImageSize', t => {\n  t.equal(\n    getScaleFromImageSize(800, 600, 1400, 990),\n    0.5714285714285714,\n    'Should compute the right scale'\n  );\n\n  t.equal(\n    getScaleFromImageSize(800, 600, 1400),\n    1,\n    'Should return 1 because we are not passing mapH'\n  );\n\n  t.equal(getScaleFromImageSize(800, 600, 1400, -1), 1, 'Should return 1 because mapH is negative');\n\n  t.equal(getScaleFromImageSize(800, 600, 1400, 0), 1, 'Should return 1 because mapH is 0');\n\n  t.end();\n});\n\ntest('exportUtils -> calculateExportImageSize', t => {\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1400,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: RESOLUTIONS.ONE_X\n    }),\n    {scale: 1, imageW: 1400, imageH: 990},\n    'Should calculate the correct export image size'\n  );\n\n  t.equal(\n    calculateExportImageSize({\n      mapW: -1,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: RESOLUTIONS.ONE_X\n    }),\n    null,\n    'Should return null because mapW is negative'\n  );\n\n  t.equal(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: -1,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: RESOLUTIONS.ONE_X\n    }),\n    null,\n    'Should return null because mapH is negative'\n  );\n\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.CUSTOM,\n      resolution: RESOLUTIONS.ONE_X\n    }),\n    {scale: undefined, imageW: 1440, imageH: 990},\n    'Should return scale null because of custom ratio'\n  );\n\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: 'not-valid',\n      resolution: RESOLUTIONS.ONE_X\n    }),\n    {scale: 1, imageW: 1440, imageH: 1080},\n    'Should return a correct valid with a non valid ratio param'\n  );\n\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: 'not-valid'\n    }),\n    {scale: 1, imageW: 1440, imageH: 990},\n    'Should return a correct valid with a non valid resolution param'\n  );\n\n  // Test fixed resolution options\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: '1920x1080'\n    }),\n    {scale: undefined, imageW: 1920, imageH: 1080},\n    'Should return fixed resolution 1920x1080'\n  );\n\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: '1280x720'\n    }),\n    {scale: undefined, imageW: 1280, imageH: 720},\n    'Should return fixed resolution 1280x720'\n  );\n\n  t.deepEqual(\n    calculateExportImageSize({\n      mapW: 1440,\n      mapH: 990,\n      ratio: EXPORT_IMG_RATIOS.SCREEN,\n      resolution: '2560x1440'\n    }),\n    {scale: undefined, imageW: 2560, imageH: 1440},\n    'Should return fixed resolution 2560x1440'\n  );\n\n  t.end();\n});\n\ntest('exportUtils -> isMSEdge', t => {\n  t.equal(isMSEdge({}), false, 'Should return false because no navigator is defined');\n  t.equal(\n    isMSEdge({navigator: {}}),\n    false,\n    'Should return false because msSaveOrOpenBlob is not defined'\n  );\n  t.equal(\n    isMSEdge({navigator: {msSaveOrOpenBlob: () => {}}}),\n    true,\n    'Should return true because both navigator and msSaveOrOpenBlob are defined'\n  );\n  t.end();\n});\n\ntest('exportUtils -> exportToJsonString', t => {\n  t.equal(exportToJsonString({test: 1}), '{\"test\":1}', 'Should convert object to string');\n  t.end();\n});"
  },
  {
    "path": "test/node/utils/filter-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\n\nimport {getDatasetFieldIndexForFilter} from '@kepler.gl/table';\n\nimport {\n  isValidFilterValue,\n  adjustValueToFilterDomain,\n  getFilterFunction,\n  getDefaultFilter,\n  validatePolygonFilter,\n  generatePolygonFilter,\n  isInPolygon,\n  diffFilters,\n  getTimestampFieldDomain,\n  scaleSourceDomainToDestination,\n  mergeFilterWithTimeline,\n  createDataContainer\n} from '@kepler.gl/utils';\n\nimport {FILTER_TYPES} from '@kepler.gl/constants';\nimport {mockPolygonFeature, mockPolygonData} from '../../fixtures/polygon';\n\n/* eslint-disable max-statements */\ntest('filterUtils -> adjustValueToFilterDomain', t => {\n  // TODO: needs id\n  const rangeFilter = getDefaultFilter();\n  rangeFilter.type = FILTER_TYPES.range;\n  rangeFilter.domain = [0, 1];\n\n  t.deepEqual(\n    adjustValueToFilterDomain([0, 0.5], rangeFilter),\n    [0, 0.5],\n    'should return value matched to range filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain([-1, 0.5], rangeFilter),\n    [0, 0.5],\n    'should return value adjust to range filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain([0.1, 1.5], rangeFilter),\n    [0.1, 1],\n    'should return value adjust to range filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain([1.1, 2], rangeFilter),\n    [0, 1],\n    'should return value adjust to range filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain(null, rangeFilter),\n    [0, 1],\n    'should return value adjust to range filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain([undefined, 0.5], rangeFilter),\n    [0, 0.5],\n    'should return value adjust to range filter'\n  );\n\n  // TODO needs id\n  const multiSelectFilter = getDefaultFilter();\n  multiSelectFilter.type = FILTER_TYPES.multiSelect;\n  multiSelectFilter.domain = ['a', 'b', 'c'];\n\n  t.deepEqual(\n    adjustValueToFilterDomain(['a', 'b'], multiSelectFilter),\n    ['a', 'b'],\n    'should return value matched to multiSelect filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain(['a', 'b', 'd'], multiSelectFilter),\n    ['a', 'b'],\n    'should return value matched to multiSelect filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain(['a', 'b', null], multiSelectFilter),\n    ['a', 'b'],\n    'should return value matched to multiSelect filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain(null, multiSelectFilter),\n    [],\n    'should return [] if nothing matched to multiSelect filter'\n  );\n\n  t.deepEqual(\n    adjustValueToFilterDomain([1, 2], multiSelectFilter),\n    [],\n    'should return [] if nothing matched to multiSelect filter'\n  );\n\n  // TODO needs id\n  const selectFilter = getDefaultFilter();\n  selectFilter.type = FILTER_TYPES.select;\n  selectFilter.domain = ['a', 'b', 'c'];\n\n  t.equal(\n    adjustValueToFilterDomain('a', selectFilter),\n    'a',\n    'should return value matched to select filter'\n  );\n\n  t.equal(\n    adjustValueToFilterDomain(['a', 'b'], selectFilter),\n    true,\n    'should return true if nothing matched to select filter'\n  );\n\n  t.equal(\n    adjustValueToFilterDomain(null, selectFilter),\n    true,\n    'should return true if nothing matched to select filter'\n  );\n\n  t.end();\n});\n\ntest('filterUtils -> getDatasetFieldIndexForFilter', t => {\n  const dataId = 'test-this-id';\n\n  let fieldIndex = getDatasetFieldIndexForFilter(dataId, {\n    dataId: [dataId],\n    fieldIdx: [3]\n  });\n\n  t.equal(fieldIndex, 3, 'FieldIndex should be 3');\n\n  fieldIndex = getDatasetFieldIndexForFilter(dataId, {\n    dataId: ['different-id', dataId],\n    fieldIdx: [3, 5]\n  });\n\n  t.equal(fieldIndex, 5, 'FieldIndex should be 5');\n\n  fieldIndex = getDatasetFieldIndexForFilter(dataId, {dataId: ['different-id']});\n  t.equal(fieldIndex, -1, 'FieldIndex should be -1');\n\n  t.end();\n});\n\ntest('filterUtils -> isValidFilterValue', t => {\n  t.equal(isValidFilterValue(null, true), false, 'Should return false because type is null');\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.select, true),\n    true,\n    'Should return true because type is select and value is true'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.select, false),\n    true,\n    'Should return true because type is select and value is true'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.timeRange, false),\n    false,\n    'Should return false because type is timeRange and value is not an array'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.timeRange, []),\n    true,\n    'Should return true because type is timeRange and value is an empty array'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.timeRange, [1]),\n    true,\n    'Should return false because type is timeRange and value is an array'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.multiSelect, true),\n    false,\n    'Should return false because type is multiSelect and value is not an array'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.multiSelect, []),\n    false,\n    'Should return false because type is multiSelect and value is an empty array'\n  );\n\n  t.equal(\n    isValidFilterValue(FILTER_TYPES.multiSelect, [1]),\n    true,\n    'Should return false because type is multiSelect and value is an array'\n  );\n\n  t.end();\n});\n\ntest('filterUtils -> isInPolygon', t => {\n  t.equal(\n    isInPolygon([120.47448, 23.667604], {\n      type: 'Feature',\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [120.21949418752885, 23.755486652156186],\n            [120.21949418752885, 23.221461105318184],\n            [121.05994828909135, 23.221461105318184],\n            [121.05994828909135, 23.755486652156186],\n            [120.21949418752885, 23.755486652156186]\n          ]\n        ]\n      },\n      properties: {\n        renderType: 'Rectangle',\n        isClosed: true,\n        bbox: {\n          xmin: 120.21949418752885,\n          xmax: null,\n          ymin: 23.755486652156186,\n          ymax: null\n        },\n        isVisible: true,\n        filterId: 'z1ilfjv6'\n      },\n      id: '036d9e21-af6b-4350-aab9-f1ce37c35cce'\n    }),\n    true,\n    'Should return true because the point is within the polygon'\n  );\n\n  t.equal(\n    isInPolygon([119.47448, 23.667604], {\n      type: 'Feature',\n      geometry: {\n        type: 'Polygon',\n        coordinates: [\n          [\n            [120.21949418752885, 23.755486652156186],\n            [120.21949418752885, 23.221461105318184],\n            [121.05994828909135, 23.221461105318184],\n            [121.05994828909135, 23.755486652156186],\n            [120.21949418752885, 23.755486652156186]\n          ]\n        ]\n      },\n      properties: {\n        renderType: 'Rectangle',\n        isClosed: true,\n        bbox: {\n          xmin: 120.21949418752885,\n          xmax: null,\n          ymin: 23.755486652156186,\n          ymax: null\n        },\n        isVisible: true,\n        filterId: 'z1ilfjv6'\n      },\n      id: '036d9e21-af6b-4350-aab9-f1ce37c35cce'\n    }),\n    false,\n    'Should return false because the point is not within the polygon'\n  );\n\n  t.end();\n});\n\ntest('filterUtils -> validatePolygonFilter', t => {\n  const filter = {\n    layerId: ['layer1'],\n    dataId: ['puppy'],\n    value: {\n      id: 'feature_1',\n      geometry: {\n        coordinates: []\n      }\n    },\n    type: 'polygon'\n  };\n\n  const dataset = {\n    id: 'puppy'\n  };\n\n  const layers = [\n    {\n      id: 'layer1'\n    }\n  ];\n\n  t.deepEqual(\n    validatePolygonFilter(dataset, filter, layers).filter,\n    {\n      ...filter,\n      fieldIdx: []\n    },\n    'Should positively validate filter'\n  );\n\n  t.equal(\n    validatePolygonFilter(dataset, filter, [{id: 'layer2'}]).filter,\n    null,\n    'Should not validate the filter since layers are not matched'\n  );\n\n  t.equal(\n    validatePolygonFilter(dataset, {}, layers).filter,\n    null,\n    'Should not validate empty filter'\n  );\n\n  t.deepEqual(\n    validatePolygonFilter(\n      dataset,\n      {\n        ...filter,\n        dataId: ['non_valid']\n      },\n      layers\n    ).filter,\n    null,\n    'Should not validate filter with non existing dataId'\n  );\n\n  t.deepEqual(\n    validatePolygonFilter(\n      dataset,\n      {\n        ...filter,\n        value: {\n          id: 'wrong-value-for-polygon-type'\n        }\n      },\n      layers\n    ).filter,\n    null,\n    'Should not validate filter given type and value without corresponding layer'\n  );\n\n  t.end();\n});\n\ntest('filterUtils -> Polygon getFilterFunction ', t => {\n  const dataset = {\n    id: 'puppy',\n    data: mockPolygonData.data,\n    fields: mockPolygonData.fields\n  };\n\n  const dataContainer = createDataContainer(dataset.data);\n\n  const {layers, data} = mockPolygonData;\n\n  const polygonFilter = generatePolygonFilter(layers, mockPolygonFeature);\n\n  let filterFunction = getFilterFunction(null, dataset.id, polygonFilter, [], dataContainer);\n\n  t.equal(filterFunction(data[0], 0), true, `Should return true because layer list is empty`);\n\n  filterFunction = getFilterFunction(null, 'puppy-2', polygonFilter, layers, dataContainer);\n\n  t.equal(\n    filterFunction(data[0], 0),\n    true,\n    `${data[0][0]} - ${data[0][1]} should be inside the range`\n  );\n\n  t.end();\n});\n\n/* eslint-enable max-statements */\n\ntest('filterUtils -> diffFilters', t => {\n  const testCases = [\n    {\n      filterRecord: {\n        dynamicDomain: [],\n        fixedDomain: [],\n        cpu: [],\n        gpu: []\n      },\n      oldFilterRecord: undefined,\n      result: {\n        dynamicDomain: null,\n        fixedDomain: null,\n        cpu: null,\n        gpu: null\n      }\n    },\n    {\n      filterRecord: {\n        dynamicDomain: [],\n        fixedDomain: [],\n        cpu: [],\n        gpu: []\n      },\n      oldFilterRecord: {\n        dynamicDomain: [],\n        fixedDomain: [],\n        cpu: [],\n        gpu: []\n      },\n      result: {\n        dynamicDomain: null,\n        fixedDomain: null,\n        cpu: null,\n        gpu: null\n      }\n    },\n    {\n      filterRecord: {\n        dynamicDomain: [{id: 'aa', name: 'hello', value: 'bb'}],\n        fixedDomain: [{id: 'bb', name: 'ab', value: 'ab'}],\n        cpu: [\n          {id: 'dd', name: 'hey', value: 'ee'},\n          {id: 'ee', name: 'ee', value: 'ff'}\n        ],\n        gpu: []\n      },\n      oldFilterRecord: {\n        dynamicDomain: [{id: 'aa', name: 'hello', value: 'bb'}],\n        fixedDomain: [\n          {id: 'bb', name: 'cd', value: 'ab'},\n          {id: 'cc', name: 'world', value: 'dd'}\n        ],\n        cpu: [{id: 'ee', name: 'ee', value: 'gg'}],\n        gpu: []\n      },\n      result: {\n        dynamicDomain: null,\n        fixedDomain: {bb: 'name_changed', cc: 'deleted'},\n        cpu: {dd: 'added', ee: 'value_changed'},\n        gpu: null\n      }\n    }\n  ];\n\n  testCases.forEach(({filterRecord, oldFilterRecord, result}) => {\n    t.deepEqual(\n      diffFilters(filterRecord, oldFilterRecord),\n      result,\n      'diff filters should be correct'\n    );\n  });\n\n  t.end();\n});\n\ntest('filterUtils -> getTimestampFieldDomain', t => {\n  const timeData = {\n    zero: {\n      input: ['2016-10-01 09:45:39', '2016-10-01 09:45:39'],\n      expect: {\n        domain: [1475315139000, 1475315140000],\n        mappedValue: [1475315139000, 1475315139000],\n        step: 0.05,\n        defaultTimeFormat: 'L LTS'\n      }\n    },\n    tiny: {\n      input: ['2016-10-01 09:45:39.001', '2016-10-01 09:45:39.002', '2016-10-01 09:45:39.003'],\n      expect: {\n        domain: [1475315139001, 1475315139003],\n        mappedValue: [1475315139001, 1475315139002, 1475315139003],\n        step: 0.1,\n        defaultTimeFormat: 'L LTS'\n      }\n    },\n    small: {\n      input: ['2016-10-01 09:45:39.010', '2016-10-01 09:45:39.020', '2016-10-01 09:45:39.030'],\n      expect: {\n        domain: [1475315139010, 1475315139030],\n        mappedValue: [1475315139010, 1475315139020, 1475315139030],\n        step: 1,\n        defaultTimeFormat: 'L LTS'\n      }\n    },\n    medium: {\n      input: ['2016-10-01 09:45:39.100', '2016-10-01 09:45:39.200', '2016-10-01 09:45:39.300'],\n      expect: {\n        domain: [1475315139100, 1475315139300],\n        mappedValue: [1475315139100, 1475315139200, 1475315139300],\n        step: 5,\n        defaultTimeFormat: 'L LTS'\n      }\n    },\n    large: {\n      input: ['2016-10-01 09:45:39', '2016-10-01 09:45:45'],\n      expect: {\n        domain: [1475315139000, 1475315145000],\n        mappedValue: [1475315139000, 1475315145000],\n        step: 1000,\n        defaultTimeFormat: 'L LTS'\n      }\n    }\n  };\n\n  Object.keys(timeData).forEach(key => {\n    const dataContainer = createDataContainer(timeData[key].input.map(d => [d]));\n    const valueAccessor = dc => d => moment.utc(dc.valueAt(d.index, 0)).valueOf();\n    const tsFieldDomain = getTimestampFieldDomain(dataContainer, valueAccessor(dataContainer));\n\n    t.deepEqual(\n      Object.keys(tsFieldDomain).sort(),\n      Object.keys(timeData[key].expect).sort(),\n      'domain should have same keys'\n    );\n\n    Object.keys(timeData[key].expect).forEach(k => {\n      // histogram is created by d3, only need to test they exist\n      t.deepEqual(tsFieldDomain[k], timeData[key].expect[k], `time domain ${k} should be the same`);\n    });\n  });\n\n  t.end();\n});\n\ntest('filterUtils -> scaleSourceDomainToDestination', t => {\n  const sourceDomain = [1564174363000, 1564179109000];\n  const destinationDomain = [1564174363000, 1564184336370];\n\n  t.deepEqual(\n    scaleSourceDomainToDestination(sourceDomain, destinationDomain),\n    [0, 47.586723444532794]\n  );\n\n  t.end();\n});\n\ntest('filterUtils -> mergeFilterWithTimeline', t => {\n  const animationConfig = {\n    domain: [1564174363000, 1564179109000],\n    currentTime: 1564178316089.6846,\n    speed: 1,\n    isAnimating: false,\n    timeSteps: null,\n    timeFormat: null,\n    timezone: null,\n    defaultTimeFormat: 'L LTS',\n    duration: null\n  };\n\n  const filter = {\n    dataId: ['fe422b77-b0fd-4c0e-848c-190e7bf94a72'],\n    id: 'daqu9ulv',\n    fixedDomain: true,\n    enlarged: true,\n    isAnimating: false,\n    animationWindow: 'free',\n    speed: 1,\n    name: ['DateTime'],\n    type: 'timeRange',\n    fieldIdx: [0],\n    domain: [1564176748230, 1564184336370],\n    value: [1564176936089.6848, 1564178316089.6846],\n    plotType: {\n      interval: '1-minute',\n      defaultTimeFormat: 'L  LT',\n      type: 'histogram',\n      aggregation: 'sum'\n    },\n    yAxis: null,\n    gpu: true,\n    syncedWithLayerTimeline: true,\n    syncTimelineMode: 1,\n    step: 1000,\n    mappedValue: [\n      1564176748230, 1564177260220, 1564178662720, 1564178735140, 1564182284550, 1564183811180,\n      1564184245340, 1564184331290, 1564184336370\n    ],\n    defaultTimeFormat: 'L LTS',\n    fieldType: 'timestamp',\n    timeBins: {\n      'fe422b77-b0fd-4c0e-848c-190e7bf94a72': {\n        '1-minute': [\n          {\n            count: 1,\n            indexes: [0],\n            x0: 1564176720000,\n            x1: 1564176780000\n          },\n          {\n            count: 1,\n            indexes: [1],\n            x0: 1564177260000,\n            x1: 1564177320000\n          },\n          {\n            count: 1,\n            indexes: [2],\n            x0: 1564178640000,\n            x1: 1564178700000\n          },\n          {\n            count: 1,\n            indexes: [3],\n            x0: 1564178700000,\n            x1: 1564178760000\n          },\n          {\n            count: 1,\n            indexes: [4],\n            x0: 1564182240000,\n            x1: 1564182300000\n          },\n          {\n            count: 1,\n            indexes: [5],\n            x0: 1564183800000,\n            x1: 1564183860000\n          },\n          {\n            count: 1,\n            indexes: [6],\n            x0: 1564184220000,\n            x1: 1564184280000\n          },\n          {\n            count: 2,\n            indexes: [7, 8],\n            x0: 1564184280000,\n            x1: 1564184340000\n          }\n        ]\n      }\n    },\n    gpuChannel: [0]\n  };\n\n  const {filter: newFilter, animationConfig: newAnimationConfig} = mergeFilterWithTimeline(\n    filter,\n    animationConfig\n  );\n\n  t.deepEqual(\n    newFilter.domain,\n    [1564174363000, 1564184336370],\n    'Merged filter should have the same domain'\n  );\n\n  t.deepEqual(\n    newAnimationConfig.domain,\n    [1564174363000, 1564184336370],\n    'Merged animationConfig should have the same domain'\n  );\n\n  t.deepEqual(\n    newFilter.domain,\n    newAnimationConfig.domain,\n    'New filter and animationConfig should have the same domain'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/gpu-filter-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {\n  resetFilterGpuMode,\n  assignGpuChannel,\n  assignGpuChannels,\n  getDatasetFieldIndexForFilter\n} from '@kepler.gl/table';\n\ntest('gpuFilterUtils -> resetFilterGpuMode', t => {\n  const testFilters = [\n    {id: '1', dataId: ['smoothie'], gpu: true},\n    {id: '2', dataId: ['smoothie'], gpu: true},\n    {id: '3', dataId: ['smoothie'], gpu: false},\n    {id: '4', dataId: ['smoothie'], gpu: true},\n    {id: '5', dataId: ['smoothie'], gpu: true},\n    {id: '6', dataId: ['smoothie'], gpu: true},\n    {id: '7', dataId: ['milkshake'], gpu: true},\n    {id: '8', dataId: ['milkshake'], gpu: false}\n  ];\n\n  const expectedFilters = [\n    {id: '1', dataId: ['smoothie'], gpu: true},\n    {id: '2', dataId: ['smoothie'], gpu: true},\n    {id: '3', dataId: ['smoothie'], gpu: false},\n    {id: '4', dataId: ['smoothie'], gpu: true},\n    {id: '5', dataId: ['smoothie'], gpu: true},\n    {id: '6', dataId: ['smoothie'], gpu: false},\n    {id: '7', dataId: ['milkshake'], gpu: true},\n    {id: '8', dataId: ['milkshake'], gpu: false}\n  ];\n\n  const result = resetFilterGpuMode(testFilters);\n  t.deepEqual(result, expectedFilters, 'should reset gpu mode');\n\n  t.end();\n});\n\ntest('gpuFilterUtils -> assignGpuChannel', t => {\n  const testCases = [\n    {\n      gpuFilter: {id: '3', dataId: ['a'], gpu: true},\n      filters: [\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [0]}\n      ],\n      result: {\n        id: '3',\n        dataId: ['a'],\n        gpu: true,\n        gpuChannel: [1]\n      }\n    },\n    {\n      gpuFilter: {id: '3', dataId: ['a'], gpu: true, gpuChannel: [1]},\n      filters: [\n        {id: '3', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [0]}\n      ],\n      result: {\n        id: '3',\n        dataId: ['a'],\n        gpu: true,\n        gpuChannel: [1]\n      }\n    },\n    {\n      gpuFilter: {id: '3', dataId: ['a'], gpu: true},\n      filters: [\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [1]}\n      ],\n      result: {id: '3', dataId: ['a'], gpu: true, gpuChannel: [0]}\n    },\n    {\n      gpuFilter: {id: '5', dataId: ['a', 'b'], gpu: true},\n      filters: [\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [3]},\n        {id: '3', dataId: ['a'], gpu: true, gpuChannel: [0]},\n        {id: '4', dataId: ['a'], gpu: true, gpuChannel: [2]}\n      ],\n      result: {id: '5', dataId: ['a', 'b'], gpu: true, gpuChannel: [1, 1]}\n    },\n    {\n      gpuFilter: {id: '5', dataId: ['a', 'b'], gpu: true, gpuChannel: [1]},\n      filters: [\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [3]},\n        {id: '3', dataId: ['a', 'b'], gpu: true, gpuChannel: [0, 2]},\n        {id: '4', dataId: ['a'], gpu: true, gpuChannel: [2]}\n      ],\n      result: {id: '5', dataId: ['a', 'b'], gpu: true, gpuChannel: [1, 1]}\n    },\n    {\n      gpuFilter: {id: '6', dataId: ['a'], gpu: true},\n      filters: [\n        {id: '1', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [3]},\n        {id: '3', dataId: ['a'], gpu: true, gpuChannel: [2]},\n        {id: '4', dataId: ['a'], gpu: true, gpuChannel: [0]},\n        {id: '5', dataId: ['a'], gpu: true, gpuChannel: [1]}\n      ],\n      result: {id: '6', dataId: ['a'], gpu: false}\n    }\n  ];\n\n  testCases.forEach(tc => {\n    t.deepEqual(\n      assignGpuChannel(tc.gpuFilter, tc.filters),\n      tc.result,\n      'should assign correct channel'\n    );\n  });\n\n  t.end();\n});\n\ntest('gpuFilterUtils -> assignGpuChannels', t => {\n  const testCases = [\n    {\n      filters: [\n        {id: '1', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '2', dataId: ['a'], gpu: true},\n        {id: '3', dataId: ['b'], gpu: true},\n        {id: '4', dataId: ['b'], gpu: false}\n      ],\n      result: [\n        {id: '1', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [0]},\n        {id: '3', dataId: ['b'], gpu: true, gpuChannel: [0]},\n        {id: '4', dataId: ['b'], gpu: false}\n      ]\n    },\n    {\n      filters: [\n        {id: '1', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '2', dataId: ['b'], gpu: true, gpuChannel: [1]},\n        {id: '3', dataId: ['a'], gpu: true, gpuChannel: [2]},\n        {id: '4', dataId: ['b'], gpu: false},\n        {id: '5', dataId: ['a'], gpu: true},\n        {id: '6', dataId: ['b'], gpu: true},\n        {id: '7', dataId: ['b'], gpu: true, gpuChannel: [0]}\n      ],\n      result: [\n        {id: '1', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '2', dataId: ['b'], gpu: true, gpuChannel: [1]},\n        {id: '3', dataId: ['a'], gpu: true, gpuChannel: [2]},\n        {id: '4', dataId: ['b'], gpu: false},\n        {id: '5', dataId: ['a'], gpu: true, gpuChannel: [0]},\n        {id: '6', dataId: ['b'], gpu: true, gpuChannel: [2]},\n        {id: '7', dataId: ['b'], gpu: true, gpuChannel: [0]}\n      ]\n    },\n    {\n      filters: [\n        {id: '1', dataId: ['a', 'b'], gpu: true, gpuChannel: [1, 0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '3', dataId: ['b', 'a'], gpu: true},\n        {id: '4', dataId: ['b'], gpu: true}\n      ],\n      result: [\n        {id: '1', dataId: ['a', 'b'], gpu: true, gpuChannel: [0, 0]},\n        {id: '2', dataId: ['a'], gpu: true, gpuChannel: [1]},\n        {id: '3', dataId: ['b', 'a'], gpu: true, gpuChannel: [1, 2]},\n        {id: '4', dataId: ['b'], gpu: true, gpuChannel: [2]}\n      ]\n    }\n  ];\n\n  testCases.forEach(tc => {\n    t.deepEqual(assignGpuChannels(tc.filters), tc.result, 'should assign correct channel');\n  });\n  t.end();\n});\n\ntest('gpuFilterUtils -> getDatasetFieldIndexForFilter', t => {\n  const dataId = 'test-this-id';\n\n  let fieldIndex = getDatasetFieldIndexForFilter(dataId, {\n    dataId: [dataId],\n    fieldIdx: [3]\n  });\n\n  t.equal(fieldIndex, 3, 'FieldIndex should be 3');\n\n  fieldIndex = getDatasetFieldIndexForFilter(dataId, {\n    dataId: ['different-id', dataId],\n    fieldIdx: [3, 5]\n  });\n\n  t.equal(fieldIndex, 5, 'FieldIndex should be 5');\n\n  fieldIndex = getDatasetFieldIndexForFilter(dataId, {dataId: ['different-id']});\n  t.equal(fieldIndex, -1, 'FieldIndex should be -1');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport './data-utils-test';\nimport './data-processor-test';\nimport './kepler-table-test';\nimport './kepler-table-utils-test';\nimport './data-container-test';\nimport './filter-utils-test';\nimport './gpu-filter-utils-test';\nimport './layer-utils-test';\nimport './data-scale-utils-test';\nimport './interaction-utils-test';\nimport './mapbox-gl-style-editor-test';\nimport './mapbox-utils-test';\nimport './notifications-utils-test';\nimport './aggregation-test';\nimport './color-util-test';\nimport './util-test';\nimport './export-utils-test';\nimport './s2-utils-test';\nimport './editor-utils-test';\nimport './kepler-gl-utils-test';\nimport './timeline-test';\nimport './plot-test';\nimport './composer-helpers-test';\nimport './dom-to-image';\nimport './effect-utils-test';\nimport './duckdb-utils-test';\n"
  },
  {
    "path": "test/node/utils/interaction-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {\n  findFieldsToShow,\n  getTooltipDisplayValue,\n  getTooltipDisplayDeltaValue,\n  TOOLTIP_MINUS_SIGN\n} from '@kepler.gl/reducers';\nimport {DEFAULT_TOOLTIP_FIELDS, COMPARE_TYPES} from '@kepler.gl/constants';\nimport {StateWTooltipFormat, testGeoJsonDataId} from 'test/helpers/mock-state';\n\nconst fields = [\n  {\n    name: 'lat'\n  },\n  {\n    name: '_lat'\n  },\n  {\n    name: 'se.longitude'\n  },\n  {\n    name: 'p longitude'\n  },\n  {\n    name: 'hex_id'\n  },\n  {\n    name: 'all_points',\n    type: 'geojson'\n  },\n  {\n    name: 'a'\n  },\n  {\n    name: 'b'\n  },\n  {\n    name: 'c'\n  },\n  {\n    name: 'd'\n  },\n  {\n    name: 'e'\n  },\n  {\n    name: 'f'\n  }\n];\n\ntest('interactionUtil -> findFieldsToShow', t => {\n  const dataId = 'random_stuff';\n\n  const someFields = [\n    ...DEFAULT_TOOLTIP_FIELDS.slice(0, 2).map(d => ({name: d})),\n    ...[\n      {\n        name: 'random'\n      },\n      {\n        name: 'something_else'\n      }\n    ]\n  ];\n\n  const expectedFields = [\n    {\n      name: 'random',\n      format: null\n    },\n    {\n      name: 'something_else',\n      format: null\n    }\n  ];\n\n  t.deepEqual(\n    findFieldsToShow({fields: someFields, id: dataId}),\n    {random_stuff: expectedFields},\n    'should find 2 default trip layers'\n  );\n\n  t.end();\n});\n\ntest('interactionUtil -> autoFindTooltipFields', t => {\n  const expectedFields = {\n    test: [\n      {\n        name: 'hex_id',\n        format: null\n      },\n      {\n        name: 'a',\n        format: null\n      },\n      {\n        name: 'b',\n        format: null\n      }\n    ]\n  };\n\n  t.deepEqual(\n    findFieldsToShow({fields, id: 'test', maxDefaultTooltips: 3}),\n    expectedFields,\n    'should filter out all default geometry fields and return first 3'\n  );\n\n  t.end();\n});\n\ntest('interactionUtil -> getTooltipDisplayDeltaValue', t => {\n  const tooltipConfig = StateWTooltipFormat.visState.interactionConfig.tooltip.config;\n  const dataset = StateWTooltipFormat.visState.datasets[testGeoJsonDataId];\n  const testFieldIdx = dataset.fields.findIndex(f => f.name === 'TRIPS');\n  const item = tooltipConfig.fieldsToShow[testGeoJsonDataId].find(fs => fs.name === 'TRIPS');\n\n  const TEST_CASES = [\n    {\n      input: {\n        primaryValue: dataset.dataContainer.row(0).valueAt(testFieldIdx),\n        // field.displayFormat has been used to replace tooltipConfig.format\n        field: {...dataset.fields[testFieldIdx], displayFormat: item.format},\n        compareType: COMPARE_TYPES.ABSOLUTE,\n        value: dataset.dataContainer.row(1).valueAt(testFieldIdx),\n        item\n      },\n      output: `${TOOLTIP_MINUS_SIGN}7.000`,\n      message: 'should display absolute delta value'\n    },\n    {\n      input: {\n        primaryValue: dataset.dataContainer.row(0).valueAt(testFieldIdx),\n        field: {...dataset.fields[testFieldIdx], displayFormat: item.format},\n        compareType: COMPARE_TYPES.RELATIVE,\n        value: dataset.dataContainer.row(1).valueAt(testFieldIdx),\n        item\n      },\n      output: `${TOOLTIP_MINUS_SIGN}63.64%`,\n      message: 'should display relative delta value'\n    },\n    {\n      input: {\n        primaryValue: dataset.dataContainer.row(3).valueAt(testFieldIdx),\n        field: {...dataset.fields[testFieldIdx], displayFormat: item.format},\n        compareType: COMPARE_TYPES.ABSOLUTE,\n        value: dataset.dataContainer.row(1).valueAt(testFieldIdx),\n        item\n      },\n      output: TOOLTIP_MINUS_SIGN,\n      message: 'should display - when primary is null'\n    },\n    {\n      input: {\n        primaryValue: dataset.dataContainer.row(0).valueAt(testFieldIdx),\n        field: {...dataset.fields[testFieldIdx], displayFormat: item.format},\n        compareType: COMPARE_TYPES.ABSOLUTE,\n        value: dataset.dataContainer.row(3).valueAt(testFieldIdx),\n        item\n      },\n      output: TOOLTIP_MINUS_SIGN,\n      message: 'should display - when data is null'\n    },\n    {\n      input: {\n        primaryValue: dataset.dataContainer.row(4).valueAt(testFieldIdx),\n        field: {...dataset.fields[testFieldIdx], displayFormat: item.format},\n        compareType: COMPARE_TYPES.ABSOLUTE,\n        value: dataset.dataContainer.row(3).valueAt(testFieldIdx),\n        item\n      },\n      output: TOOLTIP_MINUS_SIGN,\n      message: 'should display - when both are null'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    t.equal(getTooltipDisplayDeltaValue(tc.input), tc.output, tc.message);\n  });\n\n  t.end();\n});\n\ntest('interactionUtil -> getTooltipDisplayValue', t => {\n  const tooltipConfig = StateWTooltipFormat.visState.interactionConfig.tooltip.config;\n  const dataset = StateWTooltipFormat.visState.datasets[testGeoJsonDataId];\n  const items = tooltipConfig.fieldsToShow[testGeoJsonDataId];\n\n  const TEST_CASES = [\n    {\n      input: items[0], // OBJECTID\n      output: ['1', '2', '3', '4', '5'],\n      message: `should display correct tooltip value for ${items[0].name}`\n    },\n    {\n      input: items[3], // ID\n      output: ['11.000', '4.000', '20.000', '', ''],\n      message: `should display correct tooltip value for ${items[3].name}`\n    },\n    {\n      input: {name: 'OBJ', format: null}, // ID\n      output: ['{\"id\":1}', '{\"id\":2}', '{\"id\":3}', '{\"id\":4}', '{\"id\":5}'],\n      message: 'should display correct tooltip value for OBJ'\n    }\n  ];\n\n  TEST_CASES.forEach(tc => {\n    // field.displayFormat has been used to replace tooltipConfig.format\n    const field = {\n      ...dataset.fields.find(f => f.name === tc.input.name),\n      displayFormat: tc.input.format\n    };\n    const fieldIdx = dataset.fields.findIndex(f => f.name === tc.input.name);\n\n    t.deepEqual(\n      dataset.dataContainer.map(data =>\n        getTooltipDisplayValue({\n          field,\n          value: data.valueAt(fieldIdx),\n          item: tc.input\n        })\n      ),\n      tc.output,\n      tc.message\n    );\n  });\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/kepler-gl-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {GEOCODER_DATASET_NAME} from '@kepler.gl/constants';\nimport {getVisibleDatasets} from '@kepler.gl/components';\n\ntest('kepler-gl utils -> getVisibleDatasets', t => {\n  // Geocoder dataset mock can be an empty object since the filter function only cares about the key\n  // in the 'datasets' object and filters by it\n  const datasets = {\n    first: {},\n    second: {},\n    geocoder_dataset: {}\n  };\n\n  t.true(\n    datasets[GEOCODER_DATASET_NAME],\n    `${GEOCODER_DATASET_NAME} key should exist before being filtered`\n  );\n\n  const filteredResults = getVisibleDatasets(datasets);\n\n  t.isEqual(\n    filteredResults[GEOCODER_DATASET_NAME],\n    undefined,\n    `Should not exist after filtering out ${GEOCODER_DATASET_NAME} key`\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/kepler-table-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport moment from 'moment';\nimport testData, {numericRangesCsv, testFields} from 'test/fixtures/test-csv-data';\n\nimport {preciseRound, getFilterFunction} from '@kepler.gl/utils';\nimport {findPointFieldPairs} from '@kepler.gl/table';\nimport {processCsvData} from '@kepler.gl/processors';\nimport {FILTER_TYPES} from '@kepler.gl/constants';\n\nimport {cmpFields} from '../../helpers/comparison-utils';\nimport {createNewDataEntryMock} from '../../helpers/table-utils';\n\nfunction testGetTimeFieldDomain(table, t) {\n  const test_cases = [\n    {\n      name: 'default',\n      input: table.getColumnFilterDomain(table.fields[0]).domain,\n      output: [\n        moment.utc('2016-09-17 00:09:55').valueOf(),\n        moment.utc('2016-09-17 00:30:08').valueOf()\n      ],\n      msg: '2016-09-17 00:30:08'\n    },\n    {\n      name: 'epoch',\n      input: table.getColumnFilterDomain(table.fields[4]).domain,\n      output: [moment.utc(1472688000000).valueOf(), moment.utc(1472774400000).valueOf()],\n      msg: 1472688000000\n    },\n    {\n      name: 'T',\n      input: table.getColumnFilterDomain(table.fields[7]).domain,\n      output: [\n        moment.utc('2016-09-23T00:00:00.000Z').valueOf(),\n        moment.utc('2016-09-23T08:00:00.000Z').valueOf()\n      ],\n      msg: '2016-09-23T00:00:00.000Z'\n    },\n    {\n      name: 'UTC',\n      input: table.getColumnFilterDomain(table.fields[8]).domain,\n      output: [\n        moment.utc('2016-10-01 09:41:39+00:00').valueOf(),\n        moment.utc('2016-10-01 10:01:54+00:00').valueOf()\n      ],\n      msg: '2016-10-01 09:41:39+00:00'\n    },\n    {\n      name: 'local',\n      input: table.getColumnFilterDomain(table.fields[9]).domain,\n      output: [\n        moment.utc('2016-10-01 09:41:39+00:00').valueOf(),\n        moment.utc('2016-10-01 17:01:54+00:00').valueOf()\n      ],\n      msg: '2016-10-01 09:41:39+00:00'\n    }\n  ];\n\n  test_cases.forEach(tc =>\n    t.deepEqual(tc.input, tc.output, `should process correct domain for timestamp ${tc.msg}`)\n  );\n}\n\nfunction testGetNumericFieldStep(table, t) {\n  const test_cases = [\n    {\n      name: 'smallest',\n      input: table.getColumnFilterDomain(table.fields[0]).step,\n      output: 0.0000001\n    },\n    {\n      name: 'small',\n      input: table.getColumnFilterDomain(table.fields[1]).step,\n      output: 0.001\n    },\n    {\n      name: 'negative',\n      input: table.getColumnFilterDomain(table.fields[2]).step,\n      output: 0.01\n    },\n    {\n      name: 'medium',\n      input: table.getColumnFilterDomain(table.fields[3]).step,\n      output: 0.01\n    },\n    {\n      name: 'large',\n      input: table.getColumnFilterDomain(table.fields[4]).step,\n      output: 1\n    }\n  ];\n\n  test_cases.forEach(tc =>\n    t.equal(\n      preciseRound(tc.input, 5),\n      preciseRound(tc.output, 5),\n      `should process correct step for field ${tc.name}`\n    )\n  );\n}\n\nfunction testGetFilterFunction({fields, dataContainer}, t) {\n  const dataId = 'dataset-1';\n  const timeStringFilter = {\n    fieldIdx: [0],\n    type: FILTER_TYPES.timeRange,\n    value: [\n      moment.utc('2016-09-17 00:09:55').valueOf(),\n      moment.utc('2016-09-17 00:20:08').valueOf()\n    ],\n    id: 'filter-1',\n    dataId: [dataId]\n  };\n\n  let field = fields[timeStringFilter.fieldIdx[0]];\n\n  let filterFunction = getFilterFunction(field, dataId, timeStringFilter, [], dataContainer);\n\n  t.equal(\n    filterFunction({index: 10}),\n    true,\n    `${dataContainer.valueAt(10, 0)} should be inside the range`\n  );\n\n  t.equal(\n    filterFunction({index: 15}),\n    false,\n    `${dataContainer.valueAt(15, 0)} should be outside the range`\n  );\n\n  const epochFilter = {\n    fieldIdx: [4],\n    type: FILTER_TYPES.timeRange,\n    value: [moment.utc(1472688000000).valueOf(), moment.utc(1472734400000).valueOf()],\n    id: 'filter-2',\n    dataId: [dataId]\n  };\n\n  field = fields[epochFilter.fieldIdx[0]];\n\n  filterFunction = getFilterFunction(field, dataId, epochFilter, [], dataContainer);\n\n  t.equal(\n    filterFunction({index: 10}),\n    true,\n    `${dataContainer.valueAt(10, 1)} should be inside the range`\n  );\n\n  t.equal(\n    filterFunction({index: 15}),\n    false,\n    `${dataContainer.valueAt(15, 1)} should be outside the range`\n  );\n\n  const tzFilter = {\n    fieldIdx: [7],\n    type: FILTER_TYPES.timeRange,\n    value: [\n      moment.utc('2016-09-23T00:00:00.000Z').valueOf(),\n      moment.utc('2016-09-23T06:00:00.000Z').valueOf()\n    ],\n    id: 'filter-3',\n    dataId: [dataId]\n  };\n\n  field = fields[tzFilter.fieldIdx[0]];\n\n  filterFunction = getFilterFunction(field, dataId, tzFilter, [], dataContainer);\n\n  t.equal(\n    filterFunction({index: 10}),\n    true,\n    `${dataContainer.valueAt(10, 7)} should be inside the range`\n  );\n\n  t.equal(\n    filterFunction({index: 23}),\n    false,\n    `${dataContainer.valueAt(23, 7)} should be outside the range`\n  );\n\n  const utcFilter = {\n    fieldIdx: [8],\n    type: FILTER_TYPES.timeRange,\n    value: [\n      moment.utc('2016-10-01 09:45:39+00:00').valueOf(),\n      moment.utc('2016-10-01 10:00:00+00:00').valueOf()\n    ],\n    id: 'filter-4',\n    dataId: [dataId]\n  };\n\n  field = fields[utcFilter.fieldIdx[0]];\n\n  filterFunction = getFilterFunction(field, dataId, utcFilter, [], dataContainer);\n\n  t.equal(\n    filterFunction({index: 6}),\n    false,\n    `${dataContainer.valueAt(0, 8)} should be outside the range`\n  );\n\n  t.equal(\n    filterFunction({index: 4}),\n    true,\n    `${dataContainer.valueAt(4, 8)} should be inside the range`\n  );\n\n  t.equal(\n    filterFunction({index: 23}),\n    false,\n    `${dataContainer.valueAt(23, 8)} should be outside the range`\n  );\n}\n\ntest('KeplerTable -> getColumnFilterDomain -> time', async t => {\n  const expectedFields = testFields;\n\n  const data = processCsvData(testData);\n  const dataset = (\n    await createNewDataEntryMock({\n      info: {id: 'test'},\n      data\n    })\n  ).test;\n  cmpFields(t, expectedFields, dataset.fields, dataset.id);\n  testGetTimeFieldDomain(dataset, t);\n  testGetFilterFunction(dataset, t);\n\n  t.end();\n});\n\ntest('KeplerTable -> getColumnFilterDomain -> numeric', async t => {\n  const data = processCsvData(numericRangesCsv);\n  const dataset = (\n    await createNewDataEntryMock({\n      info: {id: 'test'},\n      data\n    })\n  ).test;\n\n  testGetNumericFieldStep(dataset, t);\n\n  t.end();\n});\n\ntest('KeplerTable -> findPointFieldPairs', t => {\n  const TASE_CASE = [\n    {\n      fields: [\n        'point-lat',\n        'point-lng',\n        'long',\n        'lat',\n        'poi_latitude',\n        'poi_longitude',\n        'latino',\n        'lngtino',\n        'lat.1',\n        'lng.1'\n      ],\n      expected: [\n        {\n          defaultName: 'point',\n          pair: {\n            lat: {\n              fieldIdx: 0,\n              value: 'point-lat'\n            },\n            lng: {\n              fieldIdx: 1,\n              value: 'point-lng'\n            }\n          },\n          suffix: ['lat', 'lng']\n        },\n        {\n          defaultName: 'point',\n          pair: {\n            lat: {\n              fieldIdx: 3,\n              value: 'lat'\n            },\n            lng: {\n              fieldIdx: 2,\n              value: 'long'\n            }\n          },\n          suffix: ['lat', 'long']\n        },\n        {\n          defaultName: 'poi',\n          pair: {\n            lat: {\n              fieldIdx: 4,\n              value: 'poi_latitude'\n            },\n            lng: {\n              fieldIdx: 5,\n              value: 'poi_longitude'\n            }\n          },\n          suffix: ['latitude', 'longitude']\n        },\n        {\n          defaultName: '1',\n          pair: {\n            lat: {\n              fieldIdx: 8,\n              value: 'lat.1'\n            },\n            lng: {\n              fieldIdx: 9,\n              value: 'lng.1'\n            }\n          },\n          suffix: ['lat', 'lng']\n        }\n      ]\n    },\n    {\n      fields: ['point.lat', 'point.long', 'point.altitude', 'latitude', 'longitude'],\n      expected: [\n        {\n          defaultName: 'point',\n          pair: {\n            lat: {\n              fieldIdx: 0,\n              value: 'point.lat'\n            },\n            lng: {\n              fieldIdx: 1,\n              value: 'point.long'\n            },\n            altitude: {\n              fieldIdx: 2,\n              value: 'point.altitude'\n            }\n          },\n          suffix: ['lat', 'long']\n        },\n        {\n          defaultName: 'point',\n          pair: {\n            lat: {\n              fieldIdx: 3,\n              value: 'latitude'\n            },\n            lng: {\n              fieldIdx: 4,\n              value: 'longitude'\n            }\n          },\n          suffix: ['latitude', 'longitude']\n        }\n      ]\n    },\n    {\n      fields: ['point_lat', 'point_lng', 'alt'],\n      expected: [\n        {\n          defaultName: 'point',\n          pair: {\n            // no matching \"alt\" altitude found for this pair\n            lat: {\n              fieldIdx: 0,\n              value: 'point_lat'\n            },\n            lng: {\n              fieldIdx: 1,\n              value: 'point_lng'\n            }\n          },\n          suffix: ['lat', 'lng']\n        }\n      ]\n    },\n    {\n      fields: ['point_lat', 'point_lng', 'point_alt'],\n      expected: [\n        {\n          defaultName: 'point',\n          pair: {\n            // a matching \"point_alt\" altitude was found for this pair\n            lat: {\n              fieldIdx: 0,\n              value: 'point_lat'\n            },\n            lng: {\n              fieldIdx: 1,\n              value: 'point_lng'\n            },\n            altitude: {\n              fieldIdx: 2,\n              value: 'point_alt'\n            }\n          },\n          suffix: ['lat', 'lng']\n        }\n      ]\n    }\n  ];\n\n  TASE_CASE.forEach(({fields, expected}) => {\n    const found = findPointFieldPairs(fields.map(f => ({name: f})));\n\n    t.equal(expected.length, found.length, `should found ${expected.length} pairs`);\n    expected.forEach((pair, index) => {\n      t.deepEqual(found[index], pair, 'should found correct point pair');\n    });\n  });\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/kepler-table-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\n\nimport {createNewDataEntry} from '@kepler.gl/table';\n\ntest('Dataset Utils -> createNewDataEntry', t => {\n  const task = createNewDataEntry({\n    info: {id: 'test', color: [0, 92, 255]},\n    data: {\n      rows: [[10], [20]],\n      fields: []\n    }\n  });\n\n  t.equal(task.label, 'CREATE_TABLE_TASK', 'should create CREATE_TABLE_TASK task');\n  t.equal(task.type, 'CREATE_TABLE_TASK', 'should create CREATE_TABLE_TASK task');\n  t.deepEqual(\n    task.payload,\n    {\n      info: {id: 'test', color: [0, 92, 255]},\n      color: [0, 92, 255],\n      opts: {},\n      data: {rows: [[10], [20]], fields: [], cols: undefined}\n    },\n    'should create correct CREATE_TABLE_TASK task payload'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/layer-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport cloneDeep from 'lodash/cloneDeep';\nimport {processCsvData, processGeojson} from '@kepler.gl/processors';\nimport {LayerClasses, KeplerGlLayers} from '@kepler.gl/layers';\nimport {GEOJSON_FIELDS} from '@kepler.gl/constants';\nimport {findDefaultLayer, getLayerHoverProp, getLayerOrderFromLayers} from '@kepler.gl/reducers';\nimport {StateWTripGeojson, StateWFiles} from 'test/helpers/mock-state';\n\nconst {PointLayer, ArcLayer, GeojsonLayer, LineLayer} = KeplerGlLayers;\n\nimport {wktCsv} from 'test/fixtures/test-csv-data';\nimport {cmpLayers} from 'test/helpers/comparison-utils';\nimport {getNextColorMakerValue} from 'test/helpers/layer-utils';\nimport {createNewDataEntryMock} from 'test/helpers/table-utils';\nimport tripGeojson, {timeStampDomain, tripBounds} from 'test/fixtures/trip-geojson';\nimport {geoJsonWithStyle} from 'test/fixtures/geojson';\nimport {KeplerTable, findPointFieldPairs} from '@kepler.gl/table';\nimport {createDataContainer} from '@kepler.gl/utils';\n\ntest('layerUtils -> findDefaultLayer.1', t => {\n  const inputFields = [\n    // layer 1\n    {\n      name: 'one_lat',\n      fieldIdx: 0\n    },\n    {\n      name: 'one_lng',\n      fieldIdx: 1\n    },\n    // layer 2\n    {\n      name: 'two_two.lng',\n      fieldIdx: 2\n    },\n    {\n      name: 'two_two.lat',\n      fieldIdx: 3\n    },\n    // layer 3\n    {\n      name: 'three longitude',\n      fieldIdx: 4\n    },\n    {\n      name: 'three latitude',\n      fieldIdx: 5\n    },\n    // layer 4\n    {\n      name: 'four._.lon',\n      fieldIdx: 6\n    },\n    {\n      name: 'four._.lat',\n      fieldIdx: 7\n    },\n    // layer 5\n    {\n      name: 'lat',\n      fieldIdx: 8\n    },\n    {\n      name: 'lon',\n      fieldIdx: 9\n    },\n    // non layer\n    //\n\n    {\n      name: 'non_layer_longitude.alt',\n      fieldIdx: 10\n    },\n    {\n      name: 'non_layer_latitude.alt',\n      fieldIdx: 11\n    },\n    {\n      name: 'non_layer_altitude.alt',\n      fieldIdx: 12\n    }\n  ];\n\n  const dataId = 'testtest';\n\n  const outputLayers = [\n    new PointLayer({\n      label: 'one',\n      dataId,\n      columns: {\n        lat: {\n          value: 'one_lat',\n          fieldIdx: 0\n        },\n        lng: {\n          value: 'one_lng',\n          fieldIdx: 1\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'two_two',\n      dataId,\n      columns: {\n        lat: {\n          value: 'two_two.lat',\n          fieldIdx: 3\n        },\n        lng: {\n          value: 'two_two.lng',\n          fieldIdx: 2\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'three',\n      dataId,\n      columns: {\n        lat: {\n          value: 'three latitude',\n          fieldIdx: 5\n        },\n        lng: {\n          value: 'three longitude',\n          fieldIdx: 4\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'four._',\n      dataId,\n      columns: {\n        lat: {\n          value: 'four._.lat',\n          fieldIdx: 7\n        },\n        lng: {\n          value: 'four._.lon',\n          fieldIdx: 6\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'Point',\n      dataId,\n      columns: {\n        lat: {\n          value: 'lat',\n          fieldIdx: 8\n        },\n        lng: {\n          value: 'lon',\n          fieldIdx: 9\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'non_layeralt',\n      dataId,\n      columns: {\n        lat: {\n          value: 'non_layer_latitude.alt',\n          fieldIdx: 11\n        },\n        lng: {\n          value: 'non_layer_longitude.alt',\n          fieldIdx: 10\n        },\n        altitude: {\n          value: 'non_layer_altitude.alt',\n          fieldIdx: 12,\n          optional: true\n        }\n      }\n    }),\n    new ArcLayer({\n      label: 'one -> two_two arc',\n      dataId,\n      isVisible: false,\n      columns: {\n        lat0: {\n          value: 'one_lat',\n          fieldIdx: 0\n        },\n        lng0: {\n          value: 'one_lng',\n          fieldIdx: 1\n        },\n        lat1: {\n          value: 'two_two.lat',\n          fieldIdx: 3\n        },\n        lng1: {\n          value: 'two_two.lng',\n          fieldIdx: 2\n        }\n      }\n    }),\n    new LineLayer({\n      label: 'one -> two_two line',\n      dataId,\n      isVisible: false,\n      columns: {\n        lat0: {\n          value: 'one_lat',\n          fieldIdx: 0\n        },\n        lng0: {\n          value: 'one_lng',\n          fieldIdx: 1\n        },\n        lat1: {\n          value: 'two_two.lat',\n          fieldIdx: 3\n        },\n        lng1: {\n          value: 'two_two.lng',\n          fieldIdx: 2\n        },\n        alt0: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        },\n        alt1: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    })\n  ];\n\n  const fieldPairs = findPointFieldPairs(inputFields);\n  const layers = findDefaultLayer({fields: inputFields, fieldPairs, id: dataId}, LayerClasses);\n\n  t.equal(layers.length, outputLayers.length, 'number of layers found');\n\n  layers.forEach((l, i) => cmpLayers(t, outputLayers[i], l));\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer.2', t => {\n  const inputFields = [\n    // layer 1\n    {\n      name: 'all_points',\n      fieldIdx: 0\n    }\n  ];\n  const dataId = 'milkshake';\n\n  const dataset = new KeplerTable({\n    info: {\n      id: dataId,\n      label: 'sf_zip_geo'\n    }\n  });\n  dataset.importData({\n    data: {\n      rows: [\n        [\n          {\n            type: 'Feature',\n            properties: {index: 0},\n            geometry: {type: 'Point', coordinates: []}\n          }\n        ],\n        [\n          {\n            type: 'Feature',\n            properties: {index: 1},\n            geometry: {type: 'Point', coordinates: []}\n          }\n        ]\n      ],\n      fields: inputFields\n    }\n  });\n\n  const expected = new GeojsonLayer({\n    label: 'sf_zip_geo',\n    isVisible: true,\n    dataId,\n    columns: {\n      geojson: {\n        value: 'all_points',\n        fieldIdx: 0\n      }\n    }\n  });\n\n  expected.updateLayerVisConfig({filled: true, stroked: false});\n\n  const layers = findDefaultLayer(dataset, LayerClasses);\n\n  t.equal(layers.length, 1, 'number of layers found');\n  cmpLayers(t, expected, layers[0]);\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer.3', t => {\n  const dataId = 'cool';\n\n  const inputFields = [\n    // layer 1 & 2\n    {\n      name: 'begintrip_lat',\n      fieldIdx: 0\n    },\n    {\n      name: 'begintrip_lng',\n      fieldIdx: 1\n    }\n  ];\n\n  const outputLayers = [\n    new PointLayer({\n      label: 'begintrip',\n      dataId,\n      columns: {\n        lat: {\n          value: 'begintrip_lat',\n          fieldIdx: 0\n        },\n        lng: {\n          value: 'begintrip_lng',\n          fieldIdx: 1\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    })\n  ];\n\n  const fieldPairs = findPointFieldPairs(inputFields);\n  const layers = findDefaultLayer({fields: inputFields, fieldPairs, id: dataId}, LayerClasses);\n\n  t.equal(layers.length, 1, 'number of layers found');\n  layers.forEach((l, i) => cmpLayers(t, outputLayers[i], l));\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer.4', t => {\n  // Since all defaults layers are scanned and they\n  // share field names or patterns.  This set produces\n  // multiple layers.\n  // Order determined by the order\n  // the defaults are scanned inside the function under test.\n  const inputFields = [\n    // layer 1 (grid), 2 (arc), 3 (point)\n    {\n      name: 'begintrip_lat',\n      fieldIdx: 0\n    },\n    {\n      name: 'begintrip_lng',\n      fieldIdx: 1\n    },\n    // layer 2 (arc), 4 (point)\n    {\n      name: 'dropoff_lat',\n      fieldIdx: 2\n    },\n    {\n      name: 'dropoff_lng',\n      fieldIdx: 3\n    }\n  ];\n\n  const dataId = 'yololo';\n  const outputLayers = [\n    new PointLayer({\n      label: 'begintrip',\n      dataId,\n      columns: {\n        lat: {\n          value: 'begintrip_lat',\n          fieldIdx: 0\n        },\n        lng: {\n          value: 'begintrip_lng',\n          fieldIdx: 1\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new PointLayer({\n      label: 'dropoff',\n      dataId,\n      columns: {\n        lat: {\n          value: 'dropoff_lat',\n          fieldIdx: 2\n        },\n        lng: {\n          value: 'dropoff_lng',\n          fieldIdx: 3\n        },\n        altitude: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    }),\n    new ArcLayer({\n      label: 'begintrip -> dropoff arc',\n      dataId,\n      isVisible: false,\n      columns: {\n        lat0: {\n          value: 'begintrip_lat',\n          fieldIdx: 0\n        },\n        lng0: {\n          value: 'begintrip_lng',\n          fieldIdx: 1\n        },\n        lat1: {\n          value: 'dropoff_lat',\n          fieldIdx: 2\n        },\n        lng1: {\n          value: 'dropoff_lng',\n          fieldIdx: 3\n        }\n      }\n    }),\n    new LineLayer({\n      label: 'begintrip -> dropoff line',\n      dataId,\n      isVisible: false,\n      columns: {\n        lat0: {\n          value: 'begintrip_lat',\n          fieldIdx: 0\n        },\n        lng0: {\n          value: 'begintrip_lng',\n          fieldIdx: 1\n        },\n        lat1: {\n          value: 'dropoff_lat',\n          fieldIdx: 2\n        },\n        lng1: {\n          value: 'dropoff_lng',\n          fieldIdx: 3\n        },\n        alt0: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        },\n        alt1: {\n          value: null,\n          fieldIdx: -1,\n          optional: true\n        }\n      }\n    })\n  ];\n\n  const fieldPairs = findPointFieldPairs(inputFields);\n  const layers = findDefaultLayer({fields: inputFields, fieldPairs, id: dataId}, LayerClasses);\n\n  t.equal(layers.length, outputLayers.length, 'number of layers found');\n  layers.forEach((l, i) => cmpLayers(t, outputLayers[i], l));\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer.5', t => {\n  const inputFields = [\n    // layer 1\n    {\n      name: 'one_late',\n      fieldIdx: 0\n    },\n    {\n      name: 'one_lng',\n      fieldIdx: 1\n    }\n  ];\n\n  const fieldPairs = findPointFieldPairs(inputFields);\n  const layers = findDefaultLayer({fields: inputFields, fieldPairs, id: 'yo'}, LayerClasses);\n\n  t.equal(layers.length, 0, 'number of layers found');\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer:GeojsonLayer', t => {\n  const dataset = new KeplerTable({\n    info: {\n      label: 'sf_zip_geo'\n    }\n  });\n  dataset.importData({\n    data: {\n      rows: [\n        [\n          {\n            type: 'Feature',\n            properties: {index: 0},\n            geometry: {type: 'Point', coordinates: []}\n          }\n        ],\n        [\n          {\n            type: 'Feature',\n            properties: {index: 1},\n            geometry: {type: 'Point', coordinates: []}\n          }\n        ]\n      ],\n      fields: [\n        {\n          name: 'random'\n        },\n        {\n          name: 'begintrip_lng'\n        },\n        {\n          name: 'cool'\n        },\n        {\n          name: 'dropoff_lng'\n        },\n        {\n          name: GEOJSON_FIELDS.geojson[0]\n        },\n        {\n          name: GEOJSON_FIELDS.geojson[1]\n        }\n      ]\n    }\n  });\n\n  const expected1 = new GeojsonLayer({\n    label: 'what',\n    dataId: 'smoothie',\n    isVisible: true,\n    columns: {\n      geojson: {value: GEOJSON_FIELDS.geojson[0], fieldIdx: 4}\n    }\n  });\n\n  const expected2 = new GeojsonLayer({\n    label: 'what',\n    dataId: 'smoothie',\n    isVisible: true,\n    columns: {\n      geojson: {value: GEOJSON_FIELDS.geojson[1], fieldIdx: 5}\n    }\n  });\n\n  // eslint-disable-next-line no-unused-vars\n  const [_layer1Color, _layer2Color, layer2Stroke] = getNextColorMakerValue(3);\n  expected1.updateLayerVisConfig({filled: true, stroked: false});\n  expected2.updateLayerVisConfig({\n    filled: true,\n    stroked: true,\n    strokeColor: layer2Stroke\n  });\n\n  const {fields} = dataset;\n\n  const dataContainer = createDataContainer([\n    [\n      0,\n      1,\n      2,\n      3,\n      {\n        type: 'Feature',\n        properties: {index: 0},\n        geometry: {type: 'Point', coordinates: []}\n      },\n      {\n        type: 'Feature',\n        properties: {index: 0},\n        geometry: {type: 'Polygon', coordinates: []}\n      }\n    ],\n    {fields}\n  ]);\n\n  const geojsonLayers = findDefaultLayer(\n    {\n      fields,\n      label: 'what',\n      id: 'smoothie',\n      fieldPairs: [],\n      dataContainer\n    },\n    LayerClasses\n  );\n\n  cmpLayers(t, [expected1, expected2], geojsonLayers);\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer:GeojsonLayer.wkt', t => {\n  const {fields, rows} = processCsvData(wktCsv);\n\n  const dataId = '0dj3h';\n  const label = 'some geometry file';\n\n  const expected1 = new GeojsonLayer({\n    dataId: '0dj3h',\n    label: 'some geometry file',\n    isVisible: true,\n    columns: {\n      geojson: {value: 'simplified_shape_v2', fieldIdx: 1}\n    }\n  });\n  const expected2 = new GeojsonLayer({\n    dataId: '0dj3h',\n    label: 'some geometry file',\n    isVisible: true,\n    columns: {\n      geojson: {value: 'simplified_shape', fieldIdx: 2}\n    }\n  });\n\n  const [_layer1Color, strokeColor1, _layer2Color, strokeColor2] = getNextColorMakerValue(4);\n  expected1.updateLayerVisConfig({\n    filled: true,\n    stroked: true,\n    strokeColor: strokeColor1\n  });\n  expected2.updateLayerVisConfig({\n    filled: true,\n    stroked: true,\n    strokeColor: strokeColor2\n  });\n\n  const dataContainer = createDataContainer(rows, {fields});\n\n  const geojsonLayers = findDefaultLayer(\n    {fields, id: dataId, label, fieldPairs: [], dataContainer},\n    LayerClasses\n  );\n\n  cmpLayers(t, [expected1, expected2], geojsonLayers);\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer:GeojsonWithStyle', t => {\n  const {fields, rows} = processGeojson(geoJsonWithStyle);\n\n  const dataContainer = createDataContainer(rows, {fields});\n\n  const geojsonLayers = findDefaultLayer(\n    {\n      fields,\n      id: 'test',\n      dataId: 'taro',\n      label: 'chubby prince',\n      fieldPairs: [],\n      dataContainer\n    },\n    LayerClasses\n  );\n\n  t.equal(geojsonLayers.length, 1, 'should find 1 layer');\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer:IconLayer', t => {\n  const inputFields = [\n    {\n      name: 'begintrip_lat',\n      fieldIdx: 0\n    },\n    {\n      name: 'begintrip_lng',\n      fieldIdx: 1\n    },\n    {\n      name: 'dropoff_lat',\n      fieldIdx: 2\n    },\n    {\n      name: 'dropoff_lng',\n      fieldIdx: 3\n    }\n  ];\n  const fieldPairs = findPointFieldPairs(inputFields);\n\n  const eventIcon = [{name: 'event_icon', fieldIdx: 4}];\n  const nameIcon = [{name: 'name.icon', fieldIdx: 4}];\n\n  t.equal(\n    findDefaultLayer(\n      {\n        fields: inputFields,\n        fieldPairs,\n        id: 'meow',\n        allData: []\n      },\n      LayerClasses\n    ).filter(l => l.type === 'icon').length,\n    0,\n    'should find no icon layer'\n  );\n\n  const fieldsWithIcon = [...inputFields, ...eventIcon];\n  const fieldPairsWIcon = findPointFieldPairs(fieldsWithIcon);\n\n  let iconLayers = findDefaultLayer(\n    {\n      fields: fieldsWithIcon,\n      fieldPairs: fieldPairsWIcon,\n      id: 'meow'\n    },\n    LayerClasses\n  ).filter(l => l.type === 'icon');\n\n  t.equal(iconLayers.length, 1, 'should find 1 icon layer');\n  t.equal(iconLayers[0].config.label, 'event icon', 'should find 1 icon layer');\n\n  const fieldsWith2Icon = [...inputFields, ...nameIcon, ...eventIcon];\n  const fieldPairsW2Icon = findPointFieldPairs(fieldsWith2Icon);\n\n  iconLayers = findDefaultLayer(\n    {\n      fields: fieldsWith2Icon,\n      fieldPairs: fieldPairsW2Icon,\n      id: 'meow'\n    },\n    LayerClasses\n  ).filter(l => l.type === 'icon');\n\n  t.equal(iconLayers.length, 2, 'should find 2 icon layers');\n  t.equal(iconLayers[0].config.label, 'name icon', 'should find 2 icon layer');\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer: TripLayer', t => {\n  const stateWTrip = StateWTripGeojson;\n  t.equal(stateWTrip.visState.layers.length, 1, 'should find one layer');\n  const foundLayer = stateWTrip.visState.layers[0];\n  t.equal(foundLayer.type, 'trip', 'should find a trip layer');\n\n  const expectedConfig = {\n    dataId: 'trip_data',\n    label: 'Trip Data',\n    columns: {\n      geojson: {value: '_geojson', fieldIdx: 0},\n      id: {value: null, fieldIdx: -1},\n      lat: {value: null, fieldIdx: -1},\n      lng: {value: null, fieldIdx: -1},\n      timestamp: {value: null, fieldIdx: -1},\n      altitude: {value: null, fieldIdx: -1, optional: true}\n    },\n    isVisible: true,\n    columnMode: 'geojson',\n    animation: {enabled: true, domain: timeStampDomain}\n  };\n\n  Object.keys(expectedConfig).forEach(key => {\n    t.deepEqual(\n      foundLayer.config[key],\n      expectedConfig[key],\n      `should set correct config.${key} domain`\n    );\n  });\n\n  t.deepEqual(foundLayer.meta.bounds, tripBounds, 'should set correct bounds');\n  t.deepEqual(foundLayer.meta.featureTypes, {line: true}, 'should set correct bounds');\n\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer: TripLayer.1 -> no ts', async t => {\n  // change 3rd coordinate to string\n  const modified = tripGeojson.features.map(f => ({\n    ...f,\n    geometry: {\n      ...f.geometry,\n      coordinates: f.geometry.coordinates.map(coord => [...coord.slice(0, 3), 'hello'])\n    }\n  }));\n\n  const noTripGeojson = {\n    type: 'FeatureCollection',\n    features: modified\n  };\n\n  const dataset = await createNewDataEntryMock({\n    info: {id: 'taro'},\n    data: processGeojson(noTripGeojson)\n  });\n\n  const layers = findDefaultLayer(dataset.taro, LayerClasses);\n\n  t.equal(layers.length, 1, 'should find 1 layer');\n  const foundLayer = layers[0];\n  t.equal(foundLayer.type, 'geojson', 'should find a geojson layer');\n  t.end();\n});\n\ntest('layerUtils -> findDefaultLayer: TripLayer.1 -> ts as string', async t => {\n  const tripData = {\n    type: 'FeatureCollection',\n    features: [\n      {\n        type: 'Feature',\n        geometry: {\n          type: 'LineString',\n          coordinates: [\n            [-73.78966, 40.6429, 0, '2018-09-01 11:00'],\n            [-73.7895, 40.64267, 0, '2018-09-01 11:01'],\n            [-73.78923, 40.6424, 0, '2018-09-01 11:02'],\n            [-73.78905, 40.64222, 0, '2018-09-01 11:03']\n          ]\n        }\n      }\n    ]\n  };\n\n  const dataset = await createNewDataEntryMock({\n    info: {id: 'taro'},\n    data: processGeojson(tripData)\n  });\n\n  const layers = findDefaultLayer(dataset.taro, LayerClasses);\n\n  t.equal(layers.length, 1, 'should find 1 layer');\n  const foundLayer = layers[0];\n  t.equal(foundLayer.type, 'trip', 'should find a geojson layer');\n\n  t.deepEqual(\n    foundLayer.config.animation,\n    {enabled: true, domain: [1535799600000, 1535799780000]},\n    'should set correct animation domain'\n  );\n  t.end();\n});\n\ntest('layerUtils -> getLayerHoverProp', t => {\n  const visState = cloneDeep(StateWFiles).visState;\n  const layer = visState.layers[0];\n  const layerData = visState.layerData[0];\n  const layersToRender = {\n    [layer.id]: layer\n  };\n\n  const obj = layerData.data[0];\n\n  const mockHoverInfo = {\n    object: obj,\n    picked: true,\n    layer: {\n      props: {\n        idx: 0\n      }\n    }\n  };\n  const mockHoverInfoNotHovered = {\n    picked: false,\n    object: null\n  };\n  const args = {\n    animationConfig: visState.animationConfig,\n    interactionConfig: visState.interactionConfig,\n    hoverInfo: mockHoverInfo,\n    layers: visState.layers,\n    layersToRender,\n    datasets: visState.datasets\n  };\n\n  const expectedDataset = visState.datasets[layer.config.dataId];\n  const expected = {\n    data: expectedDataset.dataContainer.row(obj.index),\n    fields: expectedDataset.fields,\n    fieldsToShow: visState.interactionConfig.tooltip.config.fieldsToShow[layer.config.dataId],\n    layer,\n    currentTime: visState.animationConfig.currentTime\n  };\n\n  t.deepEqual(getLayerHoverProp(args), expected, 'should get correct layerHoverProp');\n\n  args.hoverInfo = mockHoverInfoNotHovered;\n  t.deepEqual(getLayerHoverProp(args), null, 'should get correct layerHoverProp');\n\n  visState.interactionConfig.tooltip.enabled = false;\n  args.hoverInfo = mockHoverInfo;\n\n  t.deepEqual(getLayerHoverProp(args), null, 'should get correct layerHoverProp');\n\n  t.end();\n});\n\ntest('layerUtils -> getLayerOrderFromLayers', t => {\n  const visState = cloneDeep(StateWFiles).visState;\n  const layerOrder = getLayerOrderFromLayers(visState.layers);\n\n  t.deepEqual(layerOrder, ['point-0', 'geojson-1'], 'Should generate layerOrder correctly');\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/map-info-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {isValidMapInfo} from '@kepler.gl/utils';\n\ntest('mapInfoUtils -> isValidMapInfo', t => {\n  t.equal(\n    isValidMapInfo({title: 'example', description: ''}),\n    true,\n    'Should validate map info with no description'\n  );\n  t.equal(\n    isValidMapInfo({title: 'example', description: 'this is a map'}),\n    true,\n    'Should validate map info with description'\n  );\n  t.equal(\n    isValidMapInfo({\n      title:\n        'this is a really long title for a map that is not going to work because i really do not like this kind of long title',\n      description: 'this is a map'\n    }),\n    false,\n    'Should validate map with a really long title'\n  );\n  t.equal(\n    isValidMapInfo({\n      description:\n        'this is a really long description for a map that is not going to work because i really do not like this kind of long title',\n      title: 'this is a map'\n    }),\n    false,\n    'Should validate map with a really long description'\n  );\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/mapbox-gl-style-editor-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {mergeLayerGroupVisibility} from '@kepler.gl/utils';\n\ntest('mapbox.gl Style Editor -> mergeLayerGroupVisibility', t => {\n  const defaultLG = {\n    label: true,\n    road: true,\n    border: false,\n    building: true,\n    water: true,\n    land: true\n  };\n\n  const currentLG = {\n    label: false,\n    road: false,\n    border: true\n  };\n\n  const expected = {\n    label: false,\n    road: false,\n    border: true,\n    building: true,\n    water: true,\n    land: true\n  };\n\n  t.deepEqual(\n    mergeLayerGroupVisibility(defaultLG, currentLG),\n    expected,\n    'Should override default layer group visibility'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/mapbox-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {isStyleUsingMapboxTiles} from '@kepler.gl/utils';\n\ntest('mapbox-utils -> isStyleUsingMapboxTiles', t => {\n  t.notOk(isStyleUsingMapboxTiles({}), 'Empty style does not reference Mapbox');\n  t.notOk(\n    isStyleUsingMapboxTiles({stylesheet: {sources: {a: {}}}}),\n    'Source does not reference Mapbox'\n  );\n  t.ok(\n    isStyleUsingMapboxTiles({\n      stylesheet: {\n        sources: {\n          a: {url: 'some/url'},\n          b: {url: 'mapbox://mapbox-style.json'}\n        }\n      }\n    }),\n    'Source references Mapbox tiles using \"url\"'\n  );\n  t.ok(\n    isStyleUsingMapboxTiles({\n      stylesheet: {\n        sources: {\n          a: {url: 'some/url'},\n          b: {tiles: ['mapbox://mapbox-style.json']}\n        }\n      }\n    }),\n    'Source references Mapbox tiles using \"tiles\"'\n  );\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/notifications-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {errorNotification, successNotification} from '@kepler.gl/utils';\n\ntest('#notificationsUtils -> errorNotification', t => {\n  const notification = errorNotification({message: 'test', id: 'test-1'});\n\n  t.deepEqual(\n    notification,\n    {\n      id: 'test-1',\n      message: 'test',\n      type: 'error',\n      topic: 'global',\n      count: 1\n    },\n    'ErrorNotification creates an error notification'\n  );\n\n  t.end();\n});\n\ntest('#notificationsUtils -> successNotification', t => {\n  const notification = successNotification({message: 'test', id: 'test-1'});\n\n  t.deepEqual(\n    notification,\n    {\n      id: 'test-1',\n      message: 'test',\n      type: 'success',\n      topic: 'global',\n      count: 1\n    },\n    'SuccessNotification creates an error notification'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/plot-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {histogramFromThreshold, histogramFromValues} from '@kepler.gl/utils';\n\nconst values1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];\n\ntest('Utils -> histogramFromThreshold', t => {\n  const thresholds1 = [1, 3, 6, 13];\n\n  const bins1 = histogramFromThreshold(thresholds1, values1);\n  const expectedHistogram1 = [\n    {\n      count: 2,\n      indexes: [1, 2],\n      x0: 1,\n      x1: 3\n    },\n    {\n      count: 3,\n      indexes: [3, 4, 5],\n      x0: 3,\n      x1: 6\n    },\n    {\n      count: 7,\n      indexes: [6, 7, 8, 9, 10, 11, 12],\n      x0: 6,\n      x1: 13\n    },\n    {\n      count: 1,\n      indexes: [13],\n      x0: 13,\n      x1: 13\n    }\n  ];\n  expectedHistogram1.forEach(bin => {\n    bin.indexes.x0 = bin.x0;\n    bin.indexes.x1 = bin.x1;\n  });\n  t.deepEqual(bins1.length, 4, 'should create histogram with 4 bins.');\n  t.deepEqual(expectedHistogram1, bins1, 'should create histogram as expectedHistogram1.');\n\n  const bins2 = histogramFromThreshold([], values1);\n  t.deepEqual(bins2.length, 0, 'should create no histogram no threshold.');\n\n  const bins3 = histogramFromThreshold(thresholds1, []);\n  t.deepEqual(bins3.length, 0, 'should create no histogram no values.');\n\n  const valueAccessor = idx => values1[idx];\n  const filteredIndex = [0, 1, 3, 5, 7, 9, 11];\n  const bins4 = histogramFromThreshold(thresholds1, filteredIndex, valueAccessor);\n\n  const expectedHistogram4 = [\n    {\n      count: 2,\n      indexes: [0, 1],\n      x0: 1,\n      x1: 3\n    },\n    {\n      count: 1,\n      indexes: [3],\n      x0: 3,\n      x1: 6\n    },\n    {\n      count: 4,\n      indexes: [5, 7, 9, 11],\n      x0: 6,\n      x1: 13\n    }\n  ];\n  expectedHistogram4.forEach(bin => {\n    bin.indexes.x0 = bin.x0;\n    bin.indexes.x1 = bin.x1;\n  });\n\n  t.deepEqual(bins4, expectedHistogram4, 'should create histogram with valueAccessor.');\n\n  t.end();\n});\n\ntest('Utils -> histogramFromValues', t => {\n  const numBins = 4;\n  const bins5 = histogramFromValues(values1, numBins);\n  const expectedHistogram5 = [\n    {\n      count: 4,\n      indexes: [1, 2, 3, 4],\n      x0: 0,\n      x1: 5\n    },\n    {\n      count: 5,\n      indexes: [5, 6, 7, 8, 9],\n      x0: 5,\n      x1: 10\n    },\n    {\n      count: 4,\n      indexes: [10, 11, 12, 13],\n      x0: 10,\n      x1: 15\n    }\n  ];\n  expectedHistogram5.forEach(bin => {\n    bin.indexes.x0 = bin.x0;\n    bin.indexes.x1 = bin.x1;\n  });\n\n  // d3.histogram uses ticks() to find nice number of breaks (bins), so the\n  // number of returned bins may be different than the input number of bins\n  t.deepEqual(bins5, expectedHistogram5, 'should create histogram with 3 bins from values.');\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/s2-utils-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {getS2Center} from '@kepler.gl/layers';\n\ntest('Utils -> getS2Center', t => {\n  const s2Toekn = '8085873c';\n  t.deepEqual(getS2Center(s2Toekn), [-122.4637079795235, 37.78228912269449]);\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/timeline-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport test from 'tape';\nimport {getTimelineFromFilter, getTimelineFromAnimationConfig} from '@kepler.gl/utils';\n\ntest('#timeline -> getTimelineFromFilter', t => {\n  const filter = {\n    dataId: ['test_trip_data'],\n    id: 'lojgttzht',\n    fixedDomain: true,\n    enlarged: true,\n    isAnimating: false,\n    animationWindow: 'free',\n    speed: 1,\n    name: ['tpep_pickup_datetime'],\n    type: 'timeRange',\n    fieldIdx: [0],\n    domain: [1421315219000, 1421348744000],\n    value: [1421315219000, 1421348744000],\n    plotType: {\n      interval: '5-minute',\n      defaultTimeFormat: 'L  LT',\n      type: 'histogram',\n      aggregation: 'sum'\n    },\n    yAxis: null,\n    gpu: true,\n    step: 1000,\n    mappedValue: [\n      null,\n      null,\n      1421348740000,\n      null,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421348741000,\n      1421320174000\n    ],\n    defaultTimeFormat: 'L LTS',\n    fieldType: 'timestamp',\n    timeBins: {},\n    gpuChannel: [0]\n  };\n\n  t.deepEqual(\n    Object.keys(getTimelineFromFilter(filter)),\n    [\n      'value',\n      'enableInteraction',\n      'domain',\n      'speed',\n      'isAnimating',\n      'step',\n      'timeSteps',\n      'defaultTimeFormat',\n      'timeFormat',\n      'timezone',\n      'timeBins',\n      'animationWindow',\n      'marks'\n    ],\n    'Should generate the correct keys for filter timeline'\n  );\n  t.end();\n});\n\ntest('#timeline -> getTimelineFromAnimationConfig', t => {\n  const animationConfig = {\n    domain: [1565577697000, 1565578881000],\n    currentTime: 1565577697000,\n    speed: 1,\n    isAnimating: false,\n    timeSteps: null,\n    timeFormat: null,\n    timezone: null,\n    defaultTimeFormat: 'L LTS',\n    duration: null\n  };\n\n  t.deepEqual(\n    Object.keys(getTimelineFromAnimationConfig(animationConfig)),\n    [\n      'value',\n      'enableInteraction',\n      'domain',\n      'speed',\n      'isAnimating',\n      'timeSteps',\n      'defaultTimeFormat',\n      'timeFormat',\n      'timezone',\n      'timeBins',\n      'marks'\n    ],\n    'Should generate the correct keys for animationConfig timeline'\n  );\n\n  t.deepEqual(\n    getTimelineFromAnimationConfig(animationConfig).value,\n    [animationConfig.currentTime],\n    'Should have converted animationConfig currentTime to value and array shaped'\n  );\n\n  t.end();\n});\n"
  },
  {
    "path": "test/node/utils/util-test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  arrayInsert,\n  camelToTitle,\n  camelize,\n  capitalizeFirstLetter,\n  getError,\n  set\n} from '@kepler.gl/utils';\nimport {toArray, arrayMove} from '@kepler.gl/common-utils';\nimport test from 'tape';\n\ntest('Utils -> set', t => {\n  const obj1 = {map: {map1: 'world'}};\n  const obj2 = set(['map', 'map1'], 'hello', obj1);\n\n  t.notLooseEqual(obj1, obj2, 'set should create a new object');\n  t.equal(set(['map'], 'hello', null), null, 'set null should return null');\n  t.equal(set(['map'], 'hello', undefined), undefined, 'set undefined should return undefined');\n  t.deepEqual(\n    set(['map', 'map1'], 'hello', {map: {map1: 'world'}}),\n    {map: {map1: 'hello'}},\n    'set should set value'\n  );\n  t.deepEqual(set(['map'], 'hello', {}), {map: 'hello'}, 'set should create leave node');\n  t.deepEqual(\n    set(['map', 1], 'hello', {map: ['hello', 'world']}),\n    {map: ['hello', 'hello']},\n    'set should work with array'\n  );\n  t.end();\n});\n\ntest('Utils -> toArray', t => {\n  t.deepEqual(toArray(), [], 'Should return an empty array for undefined value');\n\n  t.deepEqual(toArray([1, 2]), [1, 2], 'Should not change an existing array');\n\n  t.deepEqual(toArray(null), [], 'Should return an empty array for a null value');\n\n  t.deepEqual(toArray('test'), ['test'], 'Should return an array with one element for a string');\n\n  t.end();\n});\n\ntest('Utils -> getError', t => {\n  t.equal(getError(new Error('oops')), 'oops', 'should find error message from Error object');\n  t.equal(getError('sorry'), 'sorry', 'should find error message from string');\n  t.equal(getError(), 'Something went wrong', 'should find error message from empty');\n\n  t.equal(\n    getError({error: {message: 'not good'}}),\n    'not good',\n    'should find error message from object'\n  );\n  t.equal(\n    getError({err: {error: 'not good'}}),\n    'not good',\n    'should find error message from object'\n  );\n  t.equal(getError({status: 400}), '{\"status\":400}', 'should find error message from object');\n\n  t.end();\n});\n\ntest('Utils -> camelToTitle', t => {\n  t.equal(camelToTitle('camelToTitle'), 'Camel To Title', 'should return titled string');\n  t.equal(camelToTitle('strokeColor'), 'Stroke Color', 'should return titled string');\n  t.end();\n});\n\ntest('Utils -> camelize', t => {\n  t.equal(camelize('hello world test string'), 'helloWorldTestString', 'should camelize string');\n  t.equal(camelize('Hello World test String'), 'helloWorldTestString', 'should camelize string');\n  t.end();\n});\n\ntest('Utils -> capitalizeFirstLetter', t => {\n  t.equal(capitalizeFirstLetter('hello world'), 'Hello world', 'should capitalize string');\n  t.equal(capitalizeFirstLetter(1), 1, 'should ignore other types than string');\n  t.end();\n});\n\ntest('Utils -> arrayInsert', t => {\n  t.deepEqual(arrayInsert([], 1, 0), [0], 'should insert val at index');\n  t.deepEqual(arrayInsert([1, 2, 3, 4], 1, 5), [1, 5, 2, 3, 4], 'should insert val at index');\n  t.deepEqual(arrayInsert([1, 2, 3], 0, 6), [6, 1, 2, 3], 'should insert val at index');\n  t.deepEqual(arrayInsert(null, 1, 0), null, 'should insert val at index');\n  t.deepEqual(arrayInsert([1, 2, 3], 3, 4), [1, 2, 3, 4], 'should insert val at index');\n  t.end();\n});\n\ntest('Utils -> arrayMove', t => {\n  const arr = [4, 1, 9, 3, 11];\n  t.deepEqual(arrayMove(arr, 2, 1), [4, 9, 1, 3, 11], 'should move array');\n  t.deepEqual(arrayMove(arr, 2, 5), [4, 1, 3, 11, 9], 'should move array');\n  t.deepEqual(arrayMove(arr, 2, -1), [4, 1, 3, 11, 9], 'should move array');\n  t.end();\n});\n"
  },
  {
    "path": "test/node.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// Run the tests\nrequire('@loaders.gl/polyfills');\nrequire('./node/index.js');\n"
  },
  {
    "path": "test/setup-browser-env.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n/* setup.js */\nimport {JSDOM, VirtualConsole} from 'jsdom';\nimport global from 'global';\nconst {gl} = require('@deck.gl/test-utils');\n\nconst virtualConsole = new VirtualConsole();\nvirtualConsole.sendTo(console);\n\nconst dom = new JSDOM('<!doctype html><html><body></body></html>', {\n  // JSDom 11.12 causes SecurityError: localStorage is not available for opaque origins\n  // https://github.com/jsdom/jsdom/issues/2304\n  url: 'http://localhost',\n  virtualConsole\n});\nconst {window} = dom;\nmockCanvas(window);\n\n// // issue: https://github.com/chromaui/chromatic-cli/issues/14\nObject.defineProperty(window, 'fetch', {\n  value: () =>\n    new Promise(() => {\n      // we just let this never resolve\n    }),\n  writable: true\n});\n\nObject.defineProperty(window, 'prompt', {\n  value: () => {},\n  writable: true\n});\n\n// TODO: This should be the right wat to mock matchMedia but matchMedia was still undefined so I moved to another way to mock it\n\n// Object.defineProperty(window, 'matchMedia', {\n//   value: () => ({\n//       matches: false,\n//       addListener: function() {},\n//       removeListener: function() {}\n//   }),\n//   writable: true\n// });\nwindow.matchMedia = () => {\n  return {\n    matches: false,\n    addListener: () => {},\n    removeListener: () => {}\n  };\n};\n\nfunction mockClipboardData() {\n  let data = null;\n  const obj = {};\n  obj.data = () => {\n    return data;\n  };\n\n  obj.setData = (format, text) => {\n    data = {format, text};\n  };\n  obj.clearData = () => {};\n\n  return obj;\n}\n\nObject.defineProperty(window, 'clipboardData', {\n  value: mockClipboardData(),\n  writable: true\n});\n\n// These do not seem to be present under jsdom v16, even though the documentation suggests that should be the case\n[\n  'addEventListener',\n  'removeEventListener',\n  'dispatchEvent',\n  'requestAnimationFrame',\n  'cancelAnimationFrame'\n].forEach(prop => {\n  window[prop] = () => {};\n});\n\nconst nop = () => {};\n\nfunction mockCanvas(globalWindow) {\n  globalWindow.HTMLCanvasElement.prototype.getContext = function mockGetContext() {\n    return {\n      fillRect: nop,\n      clearRect: nop,\n      getImageData: (x, y, w, h) => ({\n        data: new Array(w * h * 4)\n      }),\n      putImageData: nop,\n      createImageData: () => [],\n      setTransform: nop,\n      drawImage: nop,\n      save: nop,\n      fillText: nop,\n      restore: nop,\n      beginPath: nop,\n      moveTo: nop,\n      lineTo: nop,\n      closePath: nop,\n      stroke: nop,\n      translate: nop,\n      scale: nop,\n      rotate: nop,\n      arc: nop,\n      fill: nop,\n      measureText: () => ({width: 0}),\n      transform: nop,\n      rect: nop,\n      clip: nop\n    };\n  };\n\n  globalWindow.HTMLCanvasElement.prototype.toDataURL = () => '';\n}\n\nwindow.URL.createObjectURL = () => {};\n\nglobal.window = window;\nglobal.document = window.document;\nglobal.HTMLElement = window.HTMLElement;\nglobal.Element = window.Element;\nglobal.fetch = window.fetch;\n\n// Create a dummy canvas for the headless gl context\nconst canvas = global.document.createElement('canvas');\ncanvas.width = gl.drawingBufferWidth;\ncanvas.height = gl.drawingBufferHeight;\ngl.canvas = canvas;\n\nObject.keys(global.window).forEach(property => {\n  if (typeof global[property] === 'undefined') {\n    global[property] = global.window[property];\n  }\n});\n\nglobal.navigator = {\n  userAgent: 'node.js',\n  platform: 'mac',\n  appName: 'kepler.gl'\n};\n\nglobal.IntersectionObserver = class IntersectionObserver {\n  constructor() {}\n\n  disconnect() {\n    return null;\n  }\n\n  observe() {\n    return null;\n  }\n\n  takeRecords() {\n    return null;\n  }\n\n  unobserve() {\n    return null;\n  }\n};\n\n// Undefined once bumped to node v18 in @floating-ui\nglobal.Node = global.Node || (() => {});\n"
  },
  {
    "path": "test/webpack.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst {resolve} = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst SRC_DIR = resolve(__dirname, '../src');\nconst TEST_DIR = resolve(__dirname, './');\n\nconst COMMON_CONFIG = {\n  mode: 'development',\n\n  devServer: {\n    stats: {\n      warnings: false\n    },\n    clientLogLevel: 'debug'\n  },\n  output: {\n    filename: 'bundle.js'\n  },\n  devtool: 'inline-source-maps',\n\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: [SRC_DIR, 'node_modules']\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        use: ['source-map-loader'],\n        enforce: 'pre'\n      },\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR, TEST_DIR],\n        exclude: [/node_modules/],\n        options: {\n          rootMode: 'upward',\n          presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],\n          plugins: [\n            ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n            '@babel/plugin-transform-class-properties',\n            '@babel/plugin-transform-optional-chaining',\n            '@babel/plugin-transform-logical-assignment-operators',\n            '@babel/plugin-transform-nullish-coalescing-operator',\n            '@babel/plugin-transform-export-namespace-from'[\n              ('module-resolver',\n              {\n                root: [SRC_DIR],\n                alias: {\n                  test: TEST_DIR\n                }\n              })\n            ]\n          ]\n        }\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  },\n\n  plugins: [new HtmlWebpackPlugin()]\n};\n\nmodule.exports = () => {\n  return COMMON_CONFIG;\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"noEmit\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"build/dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      // Map all modules to their source\n      \"@kepler.gl/*\": [\"src/*/src\"]\n    }\n  },\n  \"include\": [\"src\"],\n\n  \"exclude\": [\n    \"src/deckgl-layers/cluster-layer\",\n\n    // build files\n    \"src/**/dist/**\",\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "tsconfig.production.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"jsx\": \"react\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"baseUrl\": \"./src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\n    \"src/deckgl-layers\",\n    \"src/layers\",\n    \"*.js\" //TODO remove one all translations finished\n  ]\n}\n"
  },
  {
    "path": "webpack/build_types.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst DtsBundleWebpack = require('dts-bundle-webpack');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../dist');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n\n  // let's put everything in\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR]\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  },\n\n  plugins: [\n    new DtsBundleWebpack({\n      name: 'kepler.gl',\n      main: `${SRC_DIR}/index.d.ts`,\n      out: `${OUTPUT_DIR}/types.d.ts`,\n      outputAsModuleFolder: true\n    })\n  ]\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "webpack/bundle.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\nconst webpack = require('webpack');\nconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst OUTPUT_DIR = resolve(__dirname, '../build');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.js')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'bundle.js',\n    publicPath: '/'\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  // let's put everything in\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [SRC_DIR]\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  },\n\n  plugins: [new webpack.EnvironmentPlugin(['MapboxAccessToken']), new BundleAnalyzerPlugin()]\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "webpack/shared-webpack-configuration.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst KeplerPackage = require('../package.json');\nconst {join, resolve} = require('path');\n\nconst LIB_DIR = resolve(__dirname, '..');\nconst SRC_DIR = resolve(LIB_DIR, './src');\nconst NODE_MODULES_DIR = resolve(__dirname, '../node_modules');\n\nconst resolveAlias = {\n  react: `${NODE_MODULES_DIR}/react`,\n  'react-dom': `${NODE_MODULES_DIR}/react-dom`,\n  'react-redux': `${NODE_MODULES_DIR}/react-redux/lib`,\n  'styled-components': `${NODE_MODULES_DIR}/styled-components`,\n  'react-intl': `${NODE_MODULES_DIR}/react-intl`,\n  'react-palm': `${NODE_MODULES_DIR}/react-palm`,\n  // Suppress useless warnings from react-date-picker's dep\n  'tiny-warning': `${SRC_DIR}/utils/src/noop.ts`,\n  // kepler.gl and loaders.gl need to use same apache-arrow\n  'apache-arrow': `${NODE_MODULES_DIR}/apache-arrow`\n};\n\n// add kepler.gl submodule aliases\nconst workspaces = KeplerPackage.workspaces;\nworkspaces.forEach(workspace => {\n  // workspace =  \"./src/types\",  \"./src/constants\", etc\n  const moduleName = workspace.split('/').pop();\n  resolveAlias[`@kepler.gl/${moduleName}`] = join(SRC_DIR, `${moduleName}/src`);\n});\n\nconst ENV_VARIABLES_WITH_INSTRUCTIONS = {\n  MapboxAccessToken: 'You can get the token at https://www.mapbox.com/help/how-access-tokens-work/',\n  DropboxClientId: 'You can get the token at https://www.dropbox.com/developers',\n  CartoClientId: 'You can get the token at https://www.mapbox.com/help/how-access-tokens-work/',\n  MapboxExportToken: 'You can get the token at https://location.foursquare.com/developer',\n  FoursquareClientId: 'You can get the token at https://location.foursquare.com/developer',\n  FoursquareDomain: 'You can get the token at https://location.foursquare.com/developer',\n  FoursquareAPIURL: 'You can get the token at https://location.foursquare.com/developer',\n  FoursquareUserMapsURL: 'You can get the token at https://location.foursquare.com/developer'\n};\n\nconst WEBPACK_ENV_VARIABLES = Object.keys(ENV_VARIABLES_WITH_INSTRUCTIONS).reduce(\n  (acc, key) => ({\n    ...acc,\n    [key]: null\n  }),\n  {}\n);\n\nmodule.exports = {\n  ENV_VARIABLES_WITH_INSTRUCTIONS,\n  WEBPACK_ENV_VARIABLES,\n  RESOLVE_ALIASES: resolveAlias\n};\n"
  },
  {
    "path": "webpack/umd.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\nconst join = require('path').join;\n\n// Import package.json to read version\nconst KeplerPackage = require('../package');\n\nconst SRC_DIR = resolve(__dirname, '../src');\nconst NODE_MODULES_DIR = resolve(__dirname, '../node_modules');\nconst OUTPUT_DIR = resolve(__dirname, '../umd');\n\nconst LIBRARY_BUNDLE_CONFIG = () => ({\n  entry: {\n    KeplerGl: join(SRC_DIR, 'index.js')\n  },\n\n  // Silence warnings about big bundles\n  stats: {\n    warnings: false\n  },\n\n  output: {\n    // Generate the bundle in dist folder\n    path: OUTPUT_DIR,\n    filename: 'keplergl.min.js',\n    globalObject: 'this',\n    library: '[name]',\n    libraryTarget: 'umd'\n  },\n\n  // let's put everything in\n  externals: {\n    react: {\n      root: 'React',\n      commonjs2: 'react',\n      commonjs: 'react',\n      amd: 'react',\n      umd: 'react'\n    },\n    'react-dom': {\n      root: 'ReactDOM',\n      commonjs2: 'react-dom',\n      commonjs: 'react-dom',\n      amd: 'react-dom',\n      umd: 'react-dom'\n    },\n    redux: {\n      root: 'Redux',\n      commonjs2: 'redux',\n      commonjs: 'redux',\n      amd: 'redux',\n      umd: 'redux'\n    },\n    'react-redux': {\n      root: 'ReactRedux',\n      commonjs2: 'react-redux',\n      commonjs: 'react-redux',\n      amd: 'react-redux',\n      umd: 'react-redux'\n    },\n    'styled-components': {\n      commonjs: 'styled-components',\n      commonjs2: 'styled-components',\n      amd: 'styled-components',\n      root: 'styled'\n    }\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n    modules: ['node_modules', SRC_DIR]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts|tsx)$/,\n        loader: 'babel-loader',\n        include: [\n          SRC_DIR,\n          `${NODE_MODULES_DIR}/@loaders.gl`,\n          `${NODE_MODULES_DIR}/@deck.gl`,\n          `${NODE_MODULES_DIR}/@math.gl`,\n          `${NODE_MODULES_DIR}/@geoarrow`\n        ],\n        options: {\n          plugins: [\n            [\n              'search-and-replace',\n              {\n                rules: [\n                  {\n                    search: '__PACKAGE_VERSION__',\n                    replace: KeplerPackage.version\n                  }\n                ]\n              }\n            ]\n          ]\n        }\n      },\n      // Add css loader for ai-assistant\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader']\n      },\n      // for compiling apache-arrow ESM module\n      {\n        test: /\\.mjs$/,\n        include: /node_modules\\/apache-arrow/,\n        type: 'javascript/auto'\n      },\n      {\n        test: /\\.js$/,\n        loader: require.resolve('@open-wc/webpack-import-meta-loader'),\n        include: [/node_modules\\/parquet-wasm/]\n      }\n    ]\n  },\n\n  node: {\n    fs: 'empty'\n  }\n});\n\nmodule.exports = env => LIBRARY_BUNDLE_CONFIG(env);\n"
  },
  {
    "path": "website/.babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-modules-commonjs\",\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-logical-assignment-operators\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\",\n    [\n      \"module-resolver\",\n      {\n        \"root\": [\n          \"../src\"\n        ],\n        \"alias\": {\n          \"test\": \"../test\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "website/.yarnrc.yml",
    "content": "# https://yarnpkg.com/configuration/yarnrc\nnodeLinker: node-modules\n# Define the registry to use when fetching packages.\nnpmRegistryServer: 'https://registry.yarnpkg.com'\n"
  },
  {
    "path": "website/README.md",
    "content": "This is the source code of the demo website for kepler.gl.\n\n## Developing\n\n    npm install // or yarn\n    export MapboxAccessToken=<insert_your_token> && npm start\n"
  },
  {
    "path": "website/esbuild.config.mjs",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport esbuild from 'esbuild';\nimport { replace } from 'esbuild-plugin-replace';\nimport process from 'node:process';\nimport { join } from 'node:path';\nimport { spawn } from 'node:child_process';\nimport WebsitePackage from '../package.json' assert {type: 'json'};\n\nconst args = process.argv;\nconst LIB_DIR = '../';\nconst NODE_MODULES_DIR = join(LIB_DIR, 'node_modules');\nconst SRC_DIR = join(LIB_DIR, 'src');\n\nconst port = 3003;\n\nconst RESOLVE_LOCAL_ALIASES = {\n  react: `${NODE_MODULES_DIR}/react`,\n  'react-dom': `${NODE_MODULES_DIR}/react-dom`,\n  'react-redux': `${NODE_MODULES_DIR}/react-redux/lib`,\n  'styled-components': `${NODE_MODULES_DIR}/styled-components`,\n  'react-intl': `${NODE_MODULES_DIR}/react-intl`,\n  'react-palm': `${NODE_MODULES_DIR}/react-palm`,\n  'tiny-warning': `${SRC_DIR}/utils/src/noop.ts`,\n  'apache-arrow': `${NODE_MODULES_DIR}/apache-arrow`,\n};\n\n// Add kepler.gl submodule aliases\nconst workspaces = WebsitePackage.workspaces;\nworkspaces.forEach(workspace => {\n  // workspace =  \"./src/types\",  \"./src/constants\", etc\n  const moduleName = workspace.split('/').pop();\n  RESOLVE_LOCAL_ALIASES[`@kepler.gl/${moduleName}`] = join(SRC_DIR, `${moduleName}/src`);\n});\n\nconst config = {\n  platform: 'browser',\n  format: 'iife',\n  logLevel: 'info',\n  loader: {\n    '.js': 'jsx',\n    '.css': 'css',\n    '.png': 'file',\n    '.jpg': 'file',\n    '.svg': 'file',\n    '.ttf': 'file',\n    '.woff': 'file',\n    '.woff2': 'file'\n  },\n  entryPoints: [\n    'src/main.js',\n  ],\n  outfile: 'dist/bundle.js',\n  bundle: true,\n  define: {\n    NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'production'),\n    'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),\n    'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),\n    'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),\n    'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),\n    'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),\n    'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),\n    'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),\n    'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || '')\n  },\n  plugins: [\n    replace({\n      __PACKAGE_VERSION__: WebsitePackage.version,\n      include: /constants\\/src\\/default-settings\\.ts/\n    })\n  ]\n};\n\nfunction openURL(url) {\n  const cmd = {\n    darwin: ['open'],\n    linux: ['xdg-open'],\n    win32: ['cmd', '/c', 'start']\n  };\n  const command = cmd[process.platform];\n  if (command) {\n    spawn(command[0], [...command.slice(1), url]);\n  }\n}\n\nfunction logError(msg) {\n  console.log('\\x1b[31m%s\\x1b[0m', msg);\n}\n\nfunction logInstruction(msg) {\n  console.log('\\x1b[36m%s\\x1b[0m', msg);\n}\n\nfunction validateEnvVariable(variable, instruction) {\n  if (!process.env[variable]) {\n    logError(`Error! ${variable} is not defined`);\n    logInstruction(`Make sure to run \"export ${variable}=<token>\" before deploy the website`);\n    logInstruction(instruction);\n    throw new Error(`Missing ${variable}`);\n  }\n}\n\n(async () => {\n  if (args.includes('--build')) {\n    // Validate environment variables before production build\n    const ENV_VARIABLES_WITH_INSTRUCTIONS = {\n      MapboxAccessToken: 'Get your Mapbox token at https://www.mapbox.com/help/how-access-tokens-work/',\n      DropboxClientId: 'Get your Dropbox key at https://www.dropbox.com/developers',\n      MapboxExportToken: 'Get your Mapbox token at https://www.mapbox.com/help/how-access-tokens-work/',\n      CartoClientId: 'Get your CARTO client id',\n      FoursquareClientId: 'Get your Foursquare client id',\n      FoursquareDomain: 'Set your Foursquare domain',\n      FoursquareAPIURL: 'Set your Foursquare API URL',\n      FoursquareUserMapsURL: 'Set your Foursquare User Maps URL'\n    };\n\n    // Validate all environment variables\n    Object.entries(ENV_VARIABLES_WITH_INSTRUCTIONS).forEach(([variable, instruction]) => {\n      validateEnvVariable(variable, instruction);\n    });\n\n    await esbuild\n      .build({\n        ...config,\n        minify: true,\n        sourcemap: false,\n        alias: RESOLVE_LOCAL_ALIASES\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n\n  if (args.includes('--start')) {\n    await esbuild\n      .context({\n        ...config,\n        minify: false,\n        sourcemap: true,\n        alias: RESOLVE_LOCAL_ALIASES,\n        banner: {\n          js: `new EventSource('/esbuild').addEventListener('change', () => location.reload());`\n        }\n      })\n      .then(async ctx => {\n        await ctx.watch();\n        await ctx.serve({\n          servedir: 'dist',\n          port,\n          fallback: 'dist/index.html',\n          onRequest: ({ remoteAddress, method, path, status, timeInMS }) => {\n            console.info(remoteAddress, status, `\"${method} ${path}\" [${timeInMS}ms]`);\n          }\n        });\n        console.info(`Website running at http://localhost:${port}, press Ctrl+C to stop`);\n        openURL(`http://localhost:${port}`);\n      })\n      .catch(e => {\n        console.error(e);\n        process.exit(1);\n      });\n  }\n})();\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"kepler.gl-website\",\n  \"description\": \"Website for kepler.gl\",\n  \"license\": \"MIT\",\n  \"author\": \"Shan He <shan@uber.com>\",\n  \"main\": \"dist/index.js\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"dev\": \"node esbuild.config.mjs --start\",\n    \"esbuild\": \"yarn build-clean && yarn build-static && node esbuild.config.mjs --build\",\n    \"start\": \"webpack serve --mode development --env prod --static-directory ./src/static --progress --open\",\n    \"start-prod\": \"webpack serve --mode production --env prod --static-directory ./src/static --progress --open\",\n    \"build-clean\": \"rm -rf ./dist && mkdir dist\",\n    \"build-static\": \"cp -r ./src/static/* dist\",\n    \"build-script\": \"NODE_OPTIONS=--openssl-legacy-provider webpack --mode production --env prod\",\n    \"build\": \"yarn build-clean && yarn build-static && yarn esbuild\",\n    \"lint\": \"eslint src\"\n  },\n  \"dependencies\": {\n    \"classnames\": \"^2.2.5\",\n    \"global\": \"^4.3.2\",\n    \"lodash\": \"4.17.21\",\n    \"prop-types\": \"^15.6.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-palm\": \"^3.1.2\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-router\": \"3.2.6\",\n    \"react-router-redux\": \"^4.0.8\",\n    \"react-swipeable-views\": \"^0.12.13\",\n    \"react-waypoint\": \"^9.0.1\",\n    \"redux\": \"^4.2.1\",\n    \"redux-actions\": \"^1.2.0\",\n    \"redux-thunk\": \"^1.0.0\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.12.1\",\n    \"@babel/plugin-transform-class-properties\": \"^7.12.1\",\n    \"@babel/plugin-transform-export-namespace-from\": \"^7.12.1\",\n    \"@babel/plugin-transform-modules-commonjs\": \"^7.12.1\",\n    \"@babel/plugin-transform-optional-chaining\": \"^7.12.1\",\n    \"@babel/plugin-transform-runtime\": \"^7.12.1\",\n    \"@babel/plugin-transform-typescript\": \"^7.16.8\",\n    \"@babel/preset-env\": \"^7.0.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@babel/preset-typescript\": \"^7.16.7\",\n    \"babel-eslint\": \"^9.0.0\",\n    \"babel-loader\": \"^8.0.0\",\n    \"babel-plugin-module-resolver\": \"^3.0.0\",\n    \"css-loader\": \"^6.8.1\",\n    \"esbuild\": \"^0.23.1\",\n    \"esbuild-plugin-replace\": \"^1.4.0\",\n    \"eslint\": \"^5.16.0\",\n    \"eslint-config-prettier\": \"^6.3.0\",\n    \"eslint-config-uber-es2015\": \"^3.1.2\",\n    \"eslint-config-uber-jsx\": \"^3.3.3\",\n    \"eslint-plugin-babel\": \"^5.3.0\",\n    \"eslint-plugin-prettier\": \"^3.1.2\",\n    \"eslint-plugin-react\": \"^7.13.0\",\n    \"file-loader\": \"^1.1.11\",\n    \"prettier\": \"1.19.1\",\n    \"source-map-loader\": \"^0.2.1\",\n    \"style-loader\": \"^3.3.3\",\n    \"url-loader\": \"^0.5.9\",\n    \"webpack\": \"^5.89.0\",\n    \"webpack-cli\": \"^5.1.4\",\n    \"webpack-dev-middleware\": \"^6.1.1\",\n    \"webpack-dev-server\": \"^4.15.1\",\n    \"webpack-hot-middleware\": \"^2.25.4\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"18.x\",\n    \"react-dom\": \"18.x\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"volta\": {\n    \"node\": \"18.18.2\",\n    \"yarn\": \"4.4.0\"\n  },\n  \"packageManager\": \"yarn@4.4.0\"\n}\n"
  },
  {
    "path": "website/src/README.md",
    "content": "This is the source code of the demo website for deck.gl.\n\n## Developing\n\n    npm install\n    export MapboxAccessToken=<insert_your_token> && npm start\n\n## About the blog\n\nThe deck.gl blog is a standalone, static website. Its contents are referenced by this demo app in the **blog** page. When publishing the demo site a build of the blog is pulled from the `origin/blog` branch to the `dist/` folder.\n\nWe use [Jekyll](https://jekyllrb.com/) to render the blog. It handles things such as templating, pagination, tagging and RSS feed for us. The source of the blog is on its own separate branch because:\n- It does not depend on anything in master\n- Only team members can publish articles, therefore it should not be pulled/forked with the deck.gl source code.\n"
  },
  {
    "path": "website/src/components/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\nimport {connect} from 'react-redux';\n\nconst GlobalStyleDiv = styled.div`\n  font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;\n  font-weight: 400;\n  font-size: 0.875em;\n  line-height: 1.71429;\n\n  *,\n  *:before,\n  *:after {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n\n  ul {\n    margin: 0;\n    padding: 0;\n  }\n\n  li {\n    margin: 0;\n  }\n\n  a {\n    text-decoration: none;\n  }\n`;\n\nclass App extends Component {\n  render() {\n    return <GlobalStyleDiv className=\"kg-web-content\">{this.props.children}</GlobalStyleDiv>;\n  }\n}\n\nexport default connect(state => state)(App);\n"
  },
  {
    "path": "website/src/components/common/card.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {css} from 'styled-components';\nimport {media} from '../../styles';\n\nconst containerStyles = css`\n  background: white;\n  box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.1);\n  border-radius: 4px;\n  overflow: hidden;\n`;\n\nconst VerticalContainer = styled.div`\n  ${containerStyles} width: 350px;\n  height: 400px;\n\n  ${media.palm`\n    width: 100%;\n    height: auto;\n  `};\n`;\n\nconst HorizontalContainer = styled.div`\n  ${containerStyles} height: 150px;\n  display: flex;\n  ${media.palm`\n    height: auto;\n    display: block;\n  `};\n`;\n\nconst Content = styled.div`\n  padding: ${props => props.theme.margins.medium};\n\n  ${media.palm`\n    padding: ${props => props.theme.margins.small};\n  `};\n`;\n\nconst VerticalCardImage = styled.img`\n  display: block;\n  width: 100%;\n  height: 200px;\n  object-fit: cover;\n`;\n\nconst HorizontalCardImage = styled.img`\n  height: 150px;\n  width: 150px;\n  object-fit: cover;\n  ${media.palm`\n    width: 100%;\n    height: 100px;\n  `};\n`;\n\nconst Title = styled.div`\n  font-size: 16px;\n  font-weight: 500;\n  margin-bottom: 8px;\n`;\n\nconst Description = styled.div`\n  font-size: 14px;\n  color: #777;\n  margin-bottom: 8px;\n  height: ${props => (props.size === 'small' ? '40px' : '80px')};\n  line-height: 1.5;\n`;\n\nconst Link = styled.div`\n  text-transform: uppercase;\n  font-weight: 700;\n  font-size: 12px;\n  color: black;\n`;\n\nexport const VerticalCard = ({title, description, image, linkText}) => (\n  <VerticalContainer>\n    <VerticalCardImage src={image} />\n    <Content>\n      <Title>{title}</Title>\n      <Description>{description}</Description>\n      <Link>{linkText}</Link>\n    </Content>\n  </VerticalContainer>\n);\n\nexport const HorizontalCard = ({title, description, image, linkText}) => (\n  <HorizontalContainer>\n    <HorizontalCardImage src={image} />\n    <Content>\n      <Title>{title}</Title>\n      <Description size=\"small\">{description}</Description>\n      <Link>{linkText}</Link>\n    </Content>\n  </HorizontalContainer>\n);\n"
  },
  {
    "path": "website/src/components/common/carousel.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {Waypoint} from 'react-waypoint';\n\nconst Container = styled.div`\n  position: relative;\n  display: flex;\n  justify-content: center;\n  min-height: 200px;\n`;\n\nconst Content = styled.div`\n  display: flex;\n  width: 100%;\n  justify-content: center;\n  transform-style: preserve-3d;\n  perspective-origin: center;\n`;\n\nconst Item = styled.div`\n  position: absolute;\n  transition: transform 1s;\n  cursor: pointer;\n  transform: perspective(600px) translate3d(${props => props.tX}%, 0, ${props => props.tZ}px);\n`;\n\nexport default class Carousel extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node,\n    selectedIndex: PropTypes.number.isRequired,\n    xOffset: PropTypes.number,\n    zOffset: PropTypes.number\n  };\n\n  static defaultProps = {\n    xOffset: 15,\n    zOffset: 60\n  };\n\n  state = {\n    isVisible: false\n  };\n\n  _onWaypointEnter = () => {\n    this.setState({isVisible: true});\n  };\n\n  _onWaypointLeave = () => {\n    this.setState({isVisible: false});\n  };\n\n  render() {\n    const {selectedIndex, children, xOffset, zOffset} = this.props;\n    const {isVisible} = this.state;\n    return (\n      <Waypoint onEnter={this._onWaypointEnter} onLeave={this._onWaypointLeave}>\n        <Container>\n          <Content>\n            {children.map((item, i) => {\n              const translateX = isVisible ? (i - selectedIndex) * xOffset : 0;\n              const translateZ = -Math.abs(i - selectedIndex) * zOffset;\n              return (\n                <Item\n                  key={`carousel-item-${i}`}\n                  tX={translateX}\n                  tZ={translateZ}\n                  onClick={() => {\n                    this.props.onChange(i);\n                  }}\n                >\n                  {item}\n                </Item>\n              );\n            })}\n          </Content>\n        </Container>\n      </Waypoint>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/common/section.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport StaggeredScrollAnimation from './staggered-scroll-animation';\n\nimport {media} from '../../styles';\n\nexport const SectionIcon = styled.img`\n  width: 64px;\n  ${media.palm`\n    width: 48px;\n  `};\n`;\n\nexport const SectionContainer = styled.div`\n  color: ${props => (props.isDark ? 'white' : 'black')};\n  background: ${props =>\n    props.isDark\n      ? props.theme.darkBackgroundColor\n      : props.background\n      ? `url(${props.background})`\n      : 'white'};\n  padding: ${props => props.theme.margins.huge};\n  // margin-bottom: ${props => props.theme.margins.large};\n  background-size: cover;\n\n  ${media.portable`\n    padding: ${props => props.theme.margins.large}\n  `} ${media.palm`\n    padding: ${props => props.theme.margins.medium}\n  `};\n`;\n\nexport const SectionHeader = styled.div`\n  text-align: center;\n  margin-bottom: ${props => props.theme.margins.large};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 40px;\n`;\n\nexport const SectionTitle = styled.div`\n  font-size: 30px;\n  font-weight: 500;\n  ${media.palm`\n    font-size: 24px;\n  `};\n`;\n\nexport const SectionDescription = styled.div`\n  font-size: 20px;\n  max-width: 700px;\n  ${media.palm`\n    font-size: 16px;\n  `};\n`;\n\nclass Section extends PureComponent {\n  static propTypes = {\n    title: PropTypes.string.isRequired,\n    description: PropTypes.string.isRequired,\n    icon: PropTypes.string.isRequired,\n    isDark: PropTypes.bool,\n    background: PropTypes.string,\n    children: PropTypes.node.isRequired\n  };\n\n  static defaultProps = {\n    isDark: false\n  };\n\n  render() {\n    const {icon, title, description, isDark, background, children} = this.props;\n    return (\n      <SectionContainer isDark={isDark} background={background}>\n        <StaggeredScrollAnimation Container={SectionHeader}>\n          <SectionIcon src={icon} />\n          <SectionTitle>{title}</SectionTitle>\n          <SectionDescription>{description}</SectionDescription>\n        </StaggeredScrollAnimation>\n        {children}\n      </SectionContainer>\n    );\n  }\n}\n\nexport default Section;\n"
  },
  {
    "path": "website/src/components/common/slideshow.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {setInterval, clearInterval} from 'global/window';\nimport {media} from '../../styles';\n\nconst imageRatio = 696 / 1080;\n\nconst StyledImgContainer = styled.div`\n  box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.5);\n  flex-shrink: 0;\n  opacity: 1;\n  position: relative;\n\n  width: calc(100vw - 60px);\n  height: ${imageRatio * 100}vw;\n\n  max-width: 800px;\n  max-height: ${800 * imageRatio}px;\n\n  ${media.palm`\n    width: calc(100vw - 60px);\n    height: ${imageRatio * 100}vw;\n  `};\n`;\n\nconst StyledImg = styled.img`\n  width: 100%;\n  height: 100%;\n  display: block;\n  position: absolute;\n  top: 0;\n  transition: opacity ${props => props.fadeDuration}ms;\n  opacity: ${props => (props.isVisible ? '1.0' : '0.0')};\n`;\n\nexport default class SlideShow extends PureComponent {\n  static propTypes = {\n    images: PropTypes.arrayOf(PropTypes.string).isRequired,\n    slideDuration: PropTypes.number,\n    fadeDuration: PropTypes.number\n  };\n\n  static defaultProps = {\n    slideDuration: 5000,\n    fadeDuration: 2000\n  };\n\n  constructor(props) {\n    super(props);\n    this._intervalId = null;\n  }\n\n  state = {\n    currentIndex: 0\n  };\n\n  componentDidMount() {\n    this._intervalId = setInterval(this._transitionImage, this.props.slideDuration);\n  }\n\n  componentWillUnmount() {\n    clearInterval(this._intervalId);\n  }\n\n  _transitionImage = () => {\n    const {images} = this.props;\n    const {currentIndex} = this.state;\n    this.setState({\n      currentIndex: currentIndex === images.length - 1 ? 0 : currentIndex + 1\n    });\n  };\n\n  render() {\n    const {images, fadeDuration} = this.props;\n    return (\n      <StyledImgContainer>\n        {images.map((src, i) => (\n          <StyledImg\n            key={src}\n            src={src}\n            fadeDuration={fadeDuration}\n            isVisible={i <= this.state.currentIndex}\n          />\n        ))}\n      </StyledImgContainer>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/common/staggered-scroll-animation.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport {Waypoint} from 'react-waypoint';\nimport styled from 'styled-components';\n\nconst FadeIn = styled.div`\n  opacity: ${props => (props.isVisible ? '1.0' : '0.0')};\n  transform: ${props => (props.isVisible ? undefined : 'translateY(10px)')};\n  transition: ${props => `opacity 350ms ${props.delay}ms, transform 350ms ${props.delay}ms`};\n`;\n\nexport default class StaggeredScrollAnimation extends PureComponent {\n  static propTypes = {\n    duration: PropTypes.number,\n    delay: PropTypes.number,\n    animateOnce: PropTypes.bool,\n    Container: PropTypes.oneOfType([\n      PropTypes.object,\n      PropTypes.element,\n      PropTypes.func,\n      PropTypes.string\n    ]),\n    scrollOffsetTop: PropTypes.number\n  };\n\n  static defaultProps = {\n    duration: 500,\n    delay: 150,\n    animateOnce: true,\n    Container: 'div',\n    scrollOffsetTop: -100\n  };\n\n  state = {\n    isVisible: false\n  };\n\n  _onWaypointEnter = () => {\n    this.setState({isVisible: true});\n  };\n\n  _onWaypointLeave = () => {\n    const {animateOnce} = this.props;\n    if (!animateOnce) {\n      this.setState({isVisible: false});\n    }\n  };\n\n  render() {\n    const {isVisible} = this.state;\n    const {delay, children, Container, scrollOffsetTop} = this.props;\n    return (\n      <Waypoint\n        onEnter={this._onWaypointEnter}\n        onLeave={this._onWaypointLeave}\n        topOffset={scrollOffsetTop}\n      >\n        <Container>\n          {React.Children.map(children, (item, i) => (\n            <FadeIn key={i} isVisible={isVisible} delay={delay * i}>\n              {item}\n            </FadeIn>\n          ))}\n        </Container>\n      </Waypoint>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/common/styled-components.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {css} from 'styled-components';\nimport {media} from '../../styles';\nimport {cdnUrl} from '../../utils';\n\nconst buttonStyles = css`\n  align-items: center;\n  background-color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnBgd\n      : props.secondary\n      ? props.theme.secondaryBtnBgd\n      : props.outline\n      ? props.theme.outlineBtnBgd\n      : props.outlineDark\n      ? props.outlineDarkBtnBgd\n      : props.link\n      ? props.theme.linkBtnBgd\n      : props.theme.primaryBtnBgd};\n  border-radius: ${props => props.theme.primaryBtnRadius};\n  color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnColor\n      : props.secondary\n      ? props.theme.secondaryBtnColor\n      : props.outline\n      ? props.outlineBtnColor\n      : props.outlineDark\n      ? props.theme.outlineDarkBtnColor\n      : props.link\n      ? props.theme.linkBtnColor\n      : props.theme.primaryBtnColor};\n  cursor: pointer;\n  display: inline-flex;\n  font-size: ${props => (props.large ? '14px' : '11px')};\n  font-weight: 500;\n  justify-content: center;\n  letter-spacing: 0.3px;\n  line-height: 14px;\n  outline: 0;\n  border: ${props =>\n    props.outline\n      ? `1px solid ${props.theme.outlineBtnColor}`\n      : props.outlineDark\n      ? `1px solid ${props.theme.outlineDarkBtnColor}`\n      : 'none'};\n  padding: ${props => (props.large ? '14px 32px' : '9px 12px')};\n  text-align: center;\n  transition: ${props => props.theme.btnTransition};\n  vertical-align: middle;\n  width: ${props => props.width || 'auto'};\n  text-transform: uppercase;\n\n  &:hover,\n  &:focus,\n  &:active,\n  &:visited,\n  &.active {\n    background-color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnBgdHover\n        : props.secondary\n        ? props.theme.secondaryBtnBgdHover\n        : props.outline\n        ? props.theme.outlineBtnBgdHover\n        : props.outlineDark\n        ? props.theme.outlineDarkBtnBgdHover\n        : props.link\n        ? props.theme.linkBtnActBgdHover\n        : props.theme.primaryBtnBgdHover};\n    color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnActColor\n        : props.secondary\n        ? props.theme.secondaryBtnActColor\n        : props.outline\n        ? props.theme.outlineBtnActColor\n        : props.outlineDark\n        ? props.theme.outlineDarkBtnActColor\n        : props.link\n        ? props.theme.linkBtnActColor\n        : props.theme.primaryBtnActColor};\n  }\n\n  svg,\n  img {\n    width: 14px;\n    margin-right: 8px;\n  }\n\n  ${media.palm`\n    font-size: 11px;\n    padding: 9px 12px;\n  `};\n`;\n\nexport const Button = styled.div.attrs({\n  className: 'kg-button'\n})`\n  ${buttonStyles};\n`;\n\nexport const LinkButton = styled.a`\n  color: black;\n  ${buttonStyles};\n`;\n\nexport const CenteredContent = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: center;\n`;\n\nexport const GithubButton = ({href, style}) => (\n  <LinkButton large outlineDark href={href} style={style}>\n    <img src={cdnUrl('icons/github.svg')} /> Github\n  </LinkButton>\n);\n"
  },
  {
    "path": "website/src/components/common/styles.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nimport styled from 'styled-components';\nimport {media} from '../../styles';\nimport {cdnUrl} from '../../utils';\n\nexport const StyledCaption = styled.div`\n  max-width: unset;\n  text-align: center;\n  margin-bottom: 32px;\n  margin-top: 6rem;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n\n  ${media.palm`\n    width: auto;\n    padding-top: 0;\n    margin-right: 0;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    margin-top: 0px;\n  `} .kg-home__caption__subtitle {\n    font-size: 36px;\n    font-weight: 600;\n    margin-bottom: 20px;\n    line-height: 1.3;\n\n    ${media.palm`\n      font-size: 20px;\n    `};\n  }\n\n  .kg-home__caption__description {\n    font-size: 14px;\n    color: ${props => props.theme.labelColor};\n    line-height: 24px;\n    margin-bottom: 32px;\n    max-width: 400px;\n\n    span.t-bold {\n      color: ${props => props.theme.textColor};\n      font-weight: 500;\n    }\n\n    ${media.palm`\n      margin-bottom: 32px;\n      text-align: center;\n      font-size: 12px;\n    `};\n  }\n`;\n\nexport const BackgroundImage = styled.img`\n  position: absolute;\n  left: 0px;\n  top: 0px;\n  width: 100%;\n  height: 70%;\n  object-fit: cover;\n  background: #0f1d29;\n`;\n\nexport const Container = styled.div`\n  padding: ${props => props.theme.margins.huge};\n  color: white;\n  position: relative;\n\n  ${media.palm`\n    padding-top: ${props => props.theme.margins.large};\n  `};\n`;\n\nexport const Content = styled.div`\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  ${media.palm`\n    margin-top: 3rem;\n  `};\n`;\n\nexport const Logo = styled.img`\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 120px;\n  ${media.palm`\n    position: inherit;\n    margin-top: ${props => props.theme.margins.normal};\n    margin-bottom: ${props => props.theme.margins.small};\n  `};\n`;\n\n/* eslint-disable react/display-name */\nexport const HeroImage = React.forwardRef((props, ref) => (\n  <BackgroundImage {...props} ref={ref} src={cdnUrl('hero/kepler.gl-background.png')} />\n));\n\nexport const LogoImage = React.forwardRef(props => (\n  <Logo {...props} src={cdnUrl('icons/kepler.gl-logo.png')} />\n));\n/* eslint-enable react/display-name */\n"
  },
  {
    "path": "website/src/components/common/swipeable.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\nimport SwipeableViews from 'react-swipeable-views';\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 72px;\n`;\n\nconst PaginationContainer = styled.div`\n  display: flex;\n  justify-content: center;\n`;\n\n// To increase click area for better usuablity\nconst PaginationBarWrapper = styled.div`\n  padding: 16px 0px;\n  cursor: pointer;\n  margin-left: 2px;\n  :first-child {\n    margin-left: 0px;\n  }\n`;\nconst PaginationBar = styled.div`\n  width: 50px;\n  height: 4px;\n  background: white;\n  opacity: ${props => (props.isActive ? '1.0' : '0.5')};\n  transition: opacity 200ms;\n`;\n\nconst Pagination = ({items, selectedIndex, onChange}) => (\n  <PaginationContainer>\n    {items.map((item, index) => (\n      <PaginationBarWrapper key={index} onClick={() => onChange(index)}>\n        <PaginationBar isActive={index === selectedIndex} />\n      </PaginationBarWrapper>\n    ))}\n  </PaginationContainer>\n);\n\nexport default class Swipeable extends PureComponent {\n  render() {\n    const {children, onChange, selectedIndex} = this.props;\n    return (\n      <Container>\n        <SwipeableViews enableMouseEvents index={selectedIndex} onChange={onChange}>\n          {children}\n        </SwipeableViews>\n        <div>\n          <Pagination items={children} selectedIndex={selectedIndex} onChange={onChange} />\n        </div>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/desktop.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled from 'styled-components';\nimport Swipeable from './common/swipeable';\nimport {fsqStudioUrl, fsqCdnDesktopUrl} from '../utils';\nimport {LinkButton} from './common/styled-components';\n\nconst DESKTOP_PRODUCT_URL = 'https://foursquare.com/products/spatial-desktop/';\n\n// Temporarily reuse Studio images/icons as placeholders. We'll swap in\n// Desktop-specific images when URLs are provided.\nconst CAROUSEL_GROUPS = [\n  [\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-1.png'),\n      icon: fsqStudioUrl('logos/Dataset_Operation.png'),\n      title: 'Browser-free with native DuckDB',\n      description:\n        'Run instantaneous, complex spatial queries on multi-GB datasets with native DuckDB in Spatial Desktop.',\n      link: DESKTOP_PRODUCT_URL\n    },\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-2.png'),\n      icon: fsqStudioUrl('logos/Administrative_boundaries.png'),\n      title: 'Built on SQLRooms',\n      description:\n        'A SQLRooms foundation offers community-driven extensibility through custom plug-ins - delivering full computational power for high-performance, visualization-intensive tasks.',\n      link: DESKTOP_PRODUCT_URL\n    },\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-3.png'),\n      icon: fsqStudioUrl('logos/Flow_layer.png'),\n      title: 'Cloud-native spatial formats',\n      description:\n        'Spatial Desktop has native support for the formats GIS pros need, including GeoParquet and PMTiles.',\n      link: DESKTOP_PRODUCT_URL\n    }\n  ],\n  [\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-4.png'),\n      icon: fsqStudioUrl('logos/Tile_Layers.png'),\n      title: 'Real-time rendering',\n      description:\n        'Harnessing Kepler.gl’s visualization excellence, you can easily render millions of points with interactive filtering and smooth animations – without browser memory limitations.',\n      link: DESKTOP_PRODUCT_URL\n    },\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-5.png'),\n      icon: fsqStudioUrl('logos/Analytics_modules.png'),\n      title: 'Flexible access & save',\n      description:\n        'Easily manage multiple projects and save them locally on your desktop or to your personal cloud storage.',\n      link: DESKTOP_PRODUCT_URL\n    },\n    {\n      imageUrl: fsqCdnDesktopUrl('screenshots/img-6.png'),\n      icon: fsqStudioUrl('logos/Charts.png'),\n      title: 'Spatial joins and aggregations',\n      description:\n        'Create buffers, perform spatial joins (intersects, within, distance), and aggregate with SQL group-bys—no external engine needed.',\n      link: DESKTOP_PRODUCT_URL\n    }\n  ]\n];\n\nconst Flex = styled.div`\n  display: flex;\n`;\n\nconst DesktopContainer = styled(Flex)`\n  width: 100%;\n  align-items: center;\n  flex-direction: column;\n  gap: 72px;\n  overflow-x: hidden;\n`;\n\nconst Section = styled(Flex)`\n  gap: 56px;\n  width: 100%;\n  justify-content: center;\n  justify-items: center;\n  padding-top: 8px;\n`;\n\nconst CardImage = styled.img`\n  height: 230px;\n  width: 386px;\n  max-width: none;\n  object-fit: cover;\n  box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.5);\n  transition: transform 350ms;\n`;\n\nconst Card = styled.a`\n  display: flex;\n  flex-direction: column;\n  gap: 56px;\n  align-items: center;\n  cursor: pointer;\n  color: white;\n  flex: 0 0 auto;\n\n  :visited {\n    color: white;\n  }\n\n  :hover {\n    ${CardImage} {\n      transform: scale3d(1.05, 1.05, 1.05);\n    }\n  }\n`;\n\nconst CardBody = styled(Flex)`\n  flex-direction: column;\n  gap: 16px;\n  max-width: 300px;\n  align-items: center;\n`;\n\nconst CardIcon = styled.img`\n  height: 24px;\n  width: 24px;\n`;\n\nconst CardTitle = styled.div`\n  font-size: 20px;\n  line-height: 28px;\n  text-align: center;\n`;\n\nconst CardDescription = styled.div`\n  font-size: 16px;\n  line-height: 26px;\n  text-align: center;\n  opacity: 0.7;\n`;\n\nconst CardSection = ({cards}) => (\n  <Section>\n    {cards.map(card => (\n      <Card key={card.imageUrl} href={card.link} target=\"_blank\">\n        <CardImage src={card.imageUrl} alt={card.title} />\n        <CardBody>\n          <CardIcon src={card.icon} alt={card.title} />\n          <CardTitle>{card.title}</CardTitle>\n          <CardDescription>{card.description}</CardDescription>\n        </CardBody>\n      </Card>\n    ))}\n  </Section>\n);\n\nconst WhiteLinkButton = styled(LinkButton)`\n  && {\n    border-color: white;\n    color: white;\n  }\n\n  &&:hover,\n  &&:focus,\n  &&:active,\n  &&:visited,\n  &&.active {\n    color: white;\n  }\n`;\n\nconst Desktop = () => {\n  const [selectedIndex, setSelectedIndex] = useState(0);\n  const onChange = useCallback(index => {\n    setSelectedIndex(index);\n  }, []);\n\n  return (\n    <DesktopContainer>\n      <Swipeable\n        onChange={onChange}\n        selectedIndex={selectedIndex}\n        containerStyle={{overflow: 'visible'}}\n      >\n        {CAROUSEL_GROUPS.map((cards, index) => (\n          <CardSection key={index} cards={cards} />\n        ))}\n      </Swipeable>\n      <WhiteLinkButton outline large href={DESKTOP_PRODUCT_URL} target=\"_blank\">\n        Learn More\n      </WhiteLinkButton>\n    </DesktopContainer>\n  );\n};\n\nexport default React.memo(Desktop);\n"
  },
  {
    "path": "website/src/components/ecosystems.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {ECOSYSTEM} from '../content';\nimport {media} from '../styles';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {GithubButton} from './common/styled-components';\n\nconst FeaturesContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: ${props => props.theme.margins.large};\n`;\n\nconst FeatureContainer = styled.div`\n  text-align: center;\n  width: 350px;\n  padding: 24px;\n  margin: ${props => props.theme.margins.small};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  ${media.palm`\n    margin: 0px;\n    margin-bottom: ${props => props.theme.margins.small}\n  `};\n`;\n\nconst FeatureImage = styled.img`\n  width: 75px;\n  height: 75px;\n`;\n\nconst FeatureTitle = styled.div`\n  font-size: 20px;\n  font-weight: 400;\n  margin-bottom: ${props => props.theme.margins.small};\n  margin-top: ${props => props.theme.margins.small};\n`;\n\nconst Feature = ({title, image, githubUrl}) => (\n  <FeatureContainer>\n    <FeatureImage src={image} />\n    <FeatureTitle>{title}</FeatureTitle>\n    <GithubButton href={githubUrl} style={{marginTop: '2rem'}} />\n  </FeatureContainer>\n);\n\nclass Ecosystems extends PureComponent {\n  render() {\n    return (\n      <div>\n        <StaggeredScrollAnimation Container={FeaturesContainer}>\n          {ECOSYSTEM.map(({title, description, image, githubUrl}, i) => (\n            <Feature\n              key={`feature-${i}`}\n              title={title}\n              description={description}\n              image={image}\n              githubUrl={githubUrl}\n            />\n          ))}\n        </StaggeredScrollAnimation>\n      </div>\n    );\n  }\n}\n\nexport default Ecosystems;\n"
  },
  {
    "path": "website/src/components/examples.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {EXAMPLES} from '../content';\nimport {media} from '../styles';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {VerticalCard} from './common/card';\nimport {LinkButton, CenteredContent} from './common/styled-components';\nimport {DEMO_LINK} from '../constants';\n\nconst CardsContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: ${props => props.theme.margins.large};\n  ${media.palm`\n    display: block;\n  `};\n`;\n\nconst StyledCardContainer = styled.a`\n  display: block;\n  margin: ${props => props.theme.margins.small};\n  color: black;\n  cursor: pointer;\n  transition: transform 350ms;\n  &:hover {\n    transform: scale3d(1.05, 1.05, 1.05);\n  }\n  ${media.palm`\n    margin: 0px;\n    margin-bottom: ${props => props.theme.margins.small}\n  `};\n`;\n\nconst CardContainer = ({linkUrl, children}) => (\n  <StyledCardContainer href={linkUrl} target=\"_blank\">\n    {children}\n  </StyledCardContainer>\n);\n\nclass Examples extends PureComponent {\n  render() {\n    return (\n      <div>\n        <StaggeredScrollAnimation Container={CardsContainer}>\n          {EXAMPLES.map(({title, description, image, url}, i) => (\n            <CardContainer linkUrl={url} key={`example-${i}`}>\n              <VerticalCard\n                title={title}\n                description={description}\n                image={image}\n                linkText=\"Explore Map\"\n              />\n            </CardContainer>\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton outline large href={DEMO_LINK}>\n            See More\n          </LinkButton>\n        </CenteredContent>\n      </div>\n    );\n  }\n}\n\nexport default Examples;\n"
  },
  {
    "path": "website/src/components/features.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {cdnUrl} from '../utils';\nimport {FEATURES} from '../content';\nimport {media} from '../styles';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {LinkButton, CenteredContent} from './common/styled-components';\n\nconst FeaturesContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: ${props => props.theme.margins.large};\n`;\n\nconst FeatureContainer = styled.div`\n  text-align: center;\n  width: 350px;\n  padding: 24px;\n  margin: ${props => props.theme.margins.small};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  ${media.palm`\n    margin: 0px;\n    margin-bottom: ${props => props.theme.margins.small}\n  `};\n`;\n\nconst FeatureImage = styled.img`\n  width: 75px;\n  height: 75px;\n`;\n\nconst FeatureTitle = styled.div`\n  font-size: 20px;\n  font-weight: 500;\n  margin-bottom: ${props => props.theme.margins.small};\n`;\n\nconst FeatureDescription = styled.div`\n  font-size: 16px;\n  color: #535353;\n`;\n\nconst Feature = ({title, description, image}) => (\n  <FeatureContainer>\n    <FeatureImage src={image} />\n    <FeatureTitle>{title}</FeatureTitle>\n    <FeatureDescription>{description}</FeatureDescription>\n  </FeatureContainer>\n);\n\nclass Features extends PureComponent {\n  render() {\n    return (\n      <div>\n        <StaggeredScrollAnimation Container={FeaturesContainer}>\n          {FEATURES.map(({title, description, image}, i) => (\n            <Feature key={`feature-${i}`} title={title} description={description} image={image} />\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton large outline href=\"https://github.com/keplergl/kepler.gl\">\n            <img src={cdnUrl('icons/github-black.svg')} /> Open Source\n          </LinkButton>\n        </CenteredContent>\n      </div>\n    );\n  }\n}\n\nexport default Features;\n"
  },
  {
    "path": "website/src/components/footer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {cdnUrl} from '../utils';\nimport {LinkButton} from './common/styled-components';\nimport {media} from '../styles';\nimport MapboxLogo from './mapbox-logo';\nimport NetlifyLogo from './netlify-logo';\nimport FoursquareLogo from './foursquare-logo';\nimport {DEMO_DUCKDB_LINK, DEMO_LINK} from '../constants';\n\nconst Container = styled.div`\n  background: #242730;\n  padding: ${props => props.theme.margins.huge};\n`;\n\nconst BrandingSection = styled.div`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  ${media.portable`\n    flex-direction: column;\n    gap: 32px;\n  `};\n`;\n\nconst BrandingContainer = styled.div`\n  ${media.portable`\n    flex-direction: column;\n  `};\n\n  display: flex;\n  gap: 20px;\n  align-items: center;\n`;\n\nconst CreatedBy = styled.div`\n  display: inline-flex;\n  align-items: center;\n  color: ${props => props.theme.footerColor};\n  font-size: 11px;\n  justify-content: center;\n  letter-spacing: 0.5px;\n  line-height: 14px;\n  width: 100%;\n  z-index: 101;\n`;\n\nconst StyledLogo = styled.span`\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 4px;\n  position: relative;\n  margin-left: 1.8rem;\n  margin-top: 0;\n  margin-bottom: 0;\n\n  a {\n    color: #e5e5e4;\n    letter-spacing: 2px;\n  }\n\n  :before {\n    content: '';\n    background: url(${cdnUrl('icons/viz_logo_bw.png')}) no-repeat;\n    background-size: cover;\n    height: 20px;\n    width: 24px;\n    position: absolute;\n    top: -5px;\n    left: -25px;\n  }\n`;\n\nconst ButtonContainer = styled.div`\n  display: flex;\n  height: 48px;\n  a {\n    width: 160px;\n  }\n\n  ${media.portable`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    a {\n      width: 50%;\n    }\n  `};\n`;\n\nconst CopyrightSection = styled.div`\n  display: flex;\n  margin-top: 32px;\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n    flex-direction: column;\n  `};\n`;\n\nconst CopyrightContainer = styled.div`\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  color: ${props => props.theme.footerColor};\n  text-align: center;\n\n  a {\n    color: ${props => props.theme.linkColor};\n    text-decoration: underline;\n  }\n\n  p:first-child {\n    margin-bottom: ${props => props.theme.margins.small};\n  }\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    margin-top: 12px;\n    width: 100%;\n  `};\n`;\n\nconst GITHUB_BUTTON_STYLE = {marginLeft: '5px'};\n\nexport default class Footer extends PureComponent {\n  render() {\n    return (\n      <Container>\n        <BrandingSection>\n          <BrandingContainer>\n            <img style={{width: '120px'}} src={cdnUrl('icons/kepler.gl-logo.png')} />\n            <ButtonContainer>\n              <LinkButton large href={DEMO_LINK}>\n                Get Started\n              </LinkButton>\n              <LinkButton\n                large\n                href={DEMO_DUCKDB_LINK}\n                style={{marginLeft: '5px', backgroundColor: '#20469c'}}\n              >\n                Try with DuckDB\n              </LinkButton>\n              <LinkButton\n                large\n                outlineDark\n                href=\"https://github.com/keplergl/kepler.gl\"\n                style={GITHUB_BUTTON_STYLE}\n              >\n                <img src={cdnUrl('icons/github.svg')} /> Github\n              </LinkButton>\n            </ButtonContainer>\n          </BrandingContainer>\n          <BrandingContainer>\n            <img src={cdnUrl('icons/uber.svg')} />\n            <MapboxLogo />\n            <NetlifyLogo />\n            <FoursquareLogo />\n            <CreatedBy>\n              created by\n              <StyledLogo className=\"fg\">\n                <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http://vis.gl\">\n                  VIS.GL\n                </a>\n              </StyledLogo>\n            </CreatedBy>\n          </BrandingContainer>\n        </BrandingSection>\n        <CopyrightSection>\n          <CopyrightContainer>\n            <p>Copyright <a href=\"https://openjsf.org\">OpenJS Foundation</a> and kepler.gl contributors. All rights reserved. The <a href=\"https://openjsf.org\">OpenJS Foundation</a> has registered trademarks and uses trademarks. For a list of trademarks of the <a href=\"https://openjsf.org\">OpenJS Foundation</a>, please see our <a href=\"https://trademark-policy.openjsf.org\">Trademark Policy</a> and <a href=\"https://trademark-list.openjsf.org\">Trademark List</a>. Trademarks and logos not indicated on the <a href=\"https://trademark-list.openjsf.org\">list of OpenJS Foundation trademarks</a> are trademarks&trade; or registered&reg; trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.</p>\n            <p><a href=\"https://openjsf.org\">The OpenJS Foundation</a> | <a href=\"https://terms-of-use.openjsf.org\">Terms of Use</a> | <a href=\"https://privacy-policy.openjsf.org\">Privacy Policy</a> | <a href=\"https://bylaws.openjsf.org\">Bylaws</a> | <a href=\"https://code-of-conduct.openjsf.org\">Code of Conduct</a> | <a href=\"https://trademark-policy.openjsf.org\">Trademark Policy</a> | <a href=\"https://trademark-list.openjsf.org\">Trademark List</a> | <a href=\"https://www.linuxfoundation.org/cookies\">Cookie Policy</a></p>\n          </CopyrightContainer>\n        </CopyrightSection>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/foursquare-logo.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {LOCATION_FOURSQUARE_LINK} from '../components/studio';\n\nconst LINK_STYLE = {display: 'flex'};\nconst FoursquareLogo = () => (\n  <a href={LOCATION_FOURSQUARE_LINK} style={LINK_STYLE}>\n    <svg\n      width=\"217\"\n      height=\"15\"\n      viewBox=\"0 0 217 15\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M92.8254 0.308594H102.114V1.83821H94.4375V5.82352H101.252V7.35314H94.4375V13.2253H92.8254V0.308594Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M103.016 6.81562C103.016 2.98639 105.635 0 109.653 0C113.652 0 116.235 3.00373 116.235 6.81562C116.235 10.6969 113.507 13.548 109.653 13.548C105.777 13.548 103.016 10.6969 103.016 6.81562ZM114.511 6.79828C114.511 3.81189 112.609 1.54349 109.649 1.54349C106.689 1.54349 104.769 3.80842 104.769 6.79828C104.769 9.85404 106.831 12.0184 109.649 12.0184C112.45 12.0184 114.511 9.87138 114.511 6.79828Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M118.206 8.34513V0.308594H119.836V8.34513C119.836 10.7488 121.108 12.0009 123.405 12.0009C125.756 12.0009 127.064 10.7661 127.064 8.36247V0.308594H128.694V8.36247C128.694 11.7096 126.687 13.5479 123.405 13.5479C120.144 13.5479 118.206 11.7443 118.206 8.34513Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M131.27 0.308594H136.867C139.468 0.308594 141.189 1.86942 141.189 4.01643C141.189 5.47667 140.381 6.65943 138.877 7.24561C140.598 8.20639 140.008 10.4054 141.533 13.2219H139.703C138.646 11.3003 138.877 7.65836 136.439 7.65836H132.889V13.2219H131.277V0.308594H131.27ZM136.831 6.12875C138.428 6.12875 139.537 5.23734 139.537 3.94706C139.537 2.67759 138.479 1.83474 136.831 1.83474H132.885V6.12875H136.831Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M142.928 9.52799L144.543 9.2193C144.866 11.0576 146.087 12.001 148.112 12.001C150.068 12.001 151.43 11.1096 151.43 9.83669C151.43 8.71983 150.372 7.94635 147.789 7.22837C144.652 6.36818 143.304 5.09871 143.304 3.31243C143.304 1.26947 145.061 0 147.876 0C150.619 0 152.271 1.20357 152.825 3.50319L151.158 3.84658C150.8 2.29962 149.724 1.52961 147.84 1.52961C146.083 1.52961 144.989 2.18169 144.989 3.29855C144.989 4.3287 145.902 5.03281 148.467 5.78894C151.84 6.76706 153.093 8.03654 153.093 9.84016C153.093 12.0912 151.173 13.548 148.195 13.548C145.217 13.548 143.377 12.1398 142.928 9.52799Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M154.51 6.81562C154.51 2.98639 157.129 0 161.146 0C165.146 0 167.729 3.00373 167.729 6.81562C167.729 8.70596 167.084 10.3535 165.972 11.5363C166.512 12.0496 167.03 12.5664 167.533 13.0971L166.385 14.1793C165.863 13.6486 165.309 13.0971 164.751 12.5491C163.729 13.1838 162.509 13.5445 161.146 13.5445C157.274 13.548 154.51 10.6969 154.51 6.81562ZM163.53 11.4183C162.99 10.9362 162.472 10.4576 161.933 10.0101L163.063 8.92794C163.635 9.41007 164.211 9.90606 164.766 10.4055C165.537 9.49678 166.004 8.25852 166.004 6.79828C166.004 3.81189 164.103 1.54349 161.143 1.54349C158.183 1.54349 156.263 3.80842 156.263 6.79828C156.263 9.85404 158.324 12.0184 161.143 12.0184C162.027 12.0184 162.831 11.8137 163.53 11.4183Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M169.776 8.34513V0.308594H171.406V8.34513C171.406 10.7488 172.678 12.0009 174.974 12.0009C177.326 12.0009 178.633 10.7661 178.633 8.36247V0.308594H180.264V8.36247C180.264 11.7096 178.257 13.5479 174.974 13.5479C171.71 13.5479 169.776 11.7443 169.776 8.34513Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M186.317 0.308594H188.219L193.457 13.2219H191.737L190.23 9.49667H184.292L182.803 13.2219H181.046L186.317 0.308594ZM189.581 7.9844L187.284 2.18159L184.933 7.9844H189.581Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M194.899 0.308594H200.496C203.098 0.308594 204.818 1.86942 204.818 4.01643C204.818 5.47667 204.011 6.65943 202.507 7.24561C204.228 8.20639 203.637 10.4054 205.163 13.2219H203.333C202.275 11.3003 202.507 7.65836 200.069 7.65836H196.519V13.2219H194.907V0.308594H194.899ZM200.457 6.12875C202.054 6.12875 203.163 5.23734 203.163 3.94706C203.163 2.67759 202.105 1.83474 200.457 1.83474H196.511V6.12875H200.457Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M207.173 0.308594H216.57V1.83821H208.789V5.82352H215.98V7.35314H208.789V11.6784H216.733V13.2253H207.173V0.308594Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M2.9395 11V4.444H3.7205L3.8415 5.148H3.8745C4.2485 4.642 4.8645 4.345 5.6235 4.345C6.4925 4.345 7.0755 4.719 7.3615 5.357H7.3945C7.7135 4.708 8.3845 4.345 9.2755 4.345C10.4965 4.345 11.3765 5.038 11.3765 6.633V11H10.3425V6.644C10.3425 5.709 9.8255 5.28 8.9785 5.28C8.2745 5.28 7.6805 5.698 7.6805 6.523V11H6.6465V6.523C6.6465 5.621 6.0855 5.28 5.3485 5.28C4.8095 5.28 4.2375 5.566 3.9735 6.017V11H2.9395ZM17.2398 10.175L17.2068 10.164C16.8438 10.736 16.1068 11.11 15.1828 11.11C13.9068 11.11 13.0928 10.472 13.0928 9.009C13.0928 7.777 13.8848 7.018 15.5238 7.018C16.1508 7.018 16.7008 7.128 17.0968 7.271V6.622C17.0968 5.577 16.6458 5.214 15.5018 5.214C14.7318 5.214 14.1598 5.379 13.5768 5.665L13.4778 4.763C14.1048 4.477 14.8198 4.323 15.6778 4.323C17.4708 4.323 18.1198 5.038 18.1198 6.776V11H17.3388L17.2398 10.175ZM17.0968 8.008C16.9318 7.909 16.4368 7.766 15.6998 7.766C14.5448 7.766 14.0718 8.206 14.0718 8.998C14.0718 9.944 14.6218 10.252 15.4688 10.252C16.3598 10.252 17.0968 9.713 17.0968 9.152V8.008ZM21.4767 2.464C21.4767 2.86 21.2457 3.047 20.7837 3.047C20.3327 3.047 20.0907 2.904 20.0907 2.464C20.0907 2.079 20.3327 1.859 20.7837 1.859C21.2457 1.859 21.4767 2.068 21.4767 2.464ZM20.2667 11V4.477L21.3007 4.422V11H20.2667ZM23.4141 11V4.444H24.1951L24.3051 5.214H24.3271C24.7561 4.686 25.3721 4.334 26.2851 4.334C27.6821 4.334 28.5181 5.06 28.5181 6.545V11H27.4841V6.666C27.4841 5.698 26.9891 5.258 26.0761 5.258C25.3501 5.258 24.7671 5.588 24.4481 6.061V11H23.4141ZM32.5916 11.11C31.3376 11.11 30.8646 10.527 30.8646 9.306V5.236H29.7536V4.444H30.8646L31.0296 2.431H31.9096V4.444H33.8456V5.236H31.9096V9.108C31.9096 9.955 32.1736 10.241 32.8446 10.241C33.2626 10.241 33.6586 10.142 33.8566 10.054V10.879C33.6366 10.978 33.1966 11.11 32.5916 11.11ZM39.0035 10.175L38.9705 10.164C38.6075 10.736 37.8705 11.11 36.9465 11.11C35.6705 11.11 34.8565 10.472 34.8565 9.009C34.8565 7.777 35.6485 7.018 37.2875 7.018C37.9145 7.018 38.4645 7.128 38.8605 7.271V6.622C38.8605 5.577 38.4095 5.214 37.2655 5.214C36.4955 5.214 35.9235 5.379 35.3405 5.665L35.2415 4.763C35.8685 4.477 36.5835 4.323 37.4415 4.323C39.2345 4.323 39.8835 5.038 39.8835 6.776V11H39.1025L39.0035 10.175ZM38.8605 8.008C38.6955 7.909 38.2005 7.766 37.4635 7.766C36.3085 7.766 35.8355 8.206 35.8355 8.998C35.8355 9.944 36.3855 10.252 37.2325 10.252C38.1235 10.252 38.8605 9.713 38.8605 9.152V8.008ZM43.2403 2.464C43.2403 2.86 43.0093 3.047 42.5473 3.047C42.0963 3.047 41.8543 2.904 41.8543 2.464C41.8543 2.079 42.0963 1.859 42.5473 1.859C43.0093 1.859 43.2403 2.068 43.2403 2.464ZM42.0303 11V4.477L43.0643 4.422V11H42.0303ZM45.1778 11V4.444H45.9588L46.0688 5.214H46.0908C46.5198 4.686 47.1358 4.334 48.0488 4.334C49.4458 4.334 50.2818 5.06 50.2818 6.545V11H49.2478V6.666C49.2478 5.698 48.7528 5.258 47.8398 5.258C47.1138 5.258 46.5308 5.588 46.2118 6.061V11H45.1778ZM54.9823 11.121C52.8703 11.121 52.0013 9.955 52.0013 7.623C52.0013 5.478 53.0243 4.334 54.8503 4.334C56.4893 4.334 57.3583 5.258 57.3583 7.348C57.3583 7.59 57.3583 7.799 57.3363 7.975H53.0793C53.0903 9.537 53.7283 10.197 55.0593 10.197C55.7853 10.197 56.4783 10.021 57.0283 9.768L57.1273 10.659C56.5773 10.934 55.8183 11.121 54.9823 11.121ZM56.3463 7.117C56.3463 5.698 55.7963 5.181 54.8283 5.181C53.7283 5.181 53.1343 5.797 53.0683 7.117H56.3463ZM63.3459 11L63.2249 10.296L63.1919 10.285C62.7849 10.835 62.1469 11.132 61.2559 11.132C59.4849 11.132 58.7259 10.054 58.7259 7.711C58.7259 5.5 59.5069 4.334 61.2559 4.334C61.9929 4.334 62.6639 4.587 63.1149 5.104L63.1369 5.082L63.1149 4.202V1.76L64.1489 1.694V11H63.3459ZM63.1149 6.028C62.8289 5.577 62.2569 5.28 61.5419 5.28C60.2659 5.28 59.8039 5.973 59.8039 7.755C59.8039 9.592 60.2219 10.274 61.5309 10.274C62.4549 10.274 62.9169 9.834 63.1149 9.471V6.028ZM74.5126 7.711C74.5126 10.175 73.5556 11.132 71.9056 11.132C71.0916 11.132 70.3986 10.824 69.9806 10.307H69.9476L69.7936 11H69.1006V1.76L70.1346 1.694V4.191L70.1126 5.071L70.1456 5.093C70.6076 4.587 71.2786 4.334 72.0266 4.334C73.5446 4.334 74.5126 5.225 74.5126 7.711ZM73.4566 7.733C73.4566 5.731 72.8296 5.258 71.7406 5.258C71.0476 5.258 70.4646 5.555 70.1346 6.039V9.471C70.3986 9.9 70.9486 10.274 71.7516 10.274C72.7746 10.274 73.4566 9.779 73.4566 7.733ZM76.5627 12.43C77.2227 12.43 77.6187 12.045 77.8827 11.363L78.0477 10.923L75.3637 4.444H76.5627L78.2677 9.009L78.5427 9.845H78.5757L78.8507 8.987L80.5557 4.444H81.6667L78.9277 11.385C78.4877 12.507 77.9487 13.31 76.6177 13.31C76.1667 13.31 75.8587 13.233 75.6277 13.145V12.232C75.8587 12.342 76.1557 12.43 76.5627 12.43Z\"\n        fill=\"white\"\n        fillOpacity=\"0.7\"\n      />\n    </svg>\n  </a>\n);\n\nexport default FoursquareLogo;\n"
  },
  {
    "path": "website/src/components/header.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\n\nimport {HEADER_NAVS} from '../content';\nimport {media} from '../styles';\n\nconst StyledLink = styled.a`\n  color: ${props => props.theme.navLinkColor};\n  text-transform: uppercase;\n  transition: color 500ms;\n\n  &:hover {\n    color: white;\n    cursor: pointer;\n  }\n\n  ${media.palm`\n    font-size: 12px;\n    line-height: 16px;\n  `};\n`;\n\nconst StyledLogo = styled.a`\n  height: 24px;\n  width: 84px;\n  background-image: url('/openjs-foundation.svg');\n  background-repeat: no-repeat;\n  background-size: 84px 24px;\n  display: flex;\n\n  ${media.palm`\n    height: 18px;\n    width: 76px;\n    background-size: 76px 18px;\n  `};\n`;\n\nconst StyledHeader = styled.div`\n  max-width: 100%;\n  padding: 0 36px;\n  margin: 0 auto;\n  width: 100%;\n  overflow: hidden;\n  position: absolute;\n  background: transparent;\n  z-index: 1000;\n  display: flex;\n  justify-content: flex-end;\n\n  .links {\n    margin-top: ${props => props.theme.margins.huge};\n    display: flex;\n    align-items: center;\n    gap: 32px;\n    text-align: center;\n  }\n\n  .icon-github:before {\n    content: '\\\\e904';\n  }\n\n  ${media.portable`\n    position: fixed;\n    flex-direction: column;\n    padding: 16px 36px;\n    background: ${props => props.theme.darkBackgroundColor};\n\n    .links {\n      margin-top: 0;\n      gap: 8px;\n      justify-content: space-between;\n    }\n  `};\n\n  ${media.palm`\n    padding: 16px 24px;\n  `};\n`;\n\nexport default class Header extends Component {\n  render() {\n    // const {isMenuOpen, opacity, toggleMenu} = this.props;\n\n    return (\n      <StyledHeader className=\"container stretch\">\n        <div className=\"links\">\n          {HEADER_NAVS.map((item, i) => (\n            <StyledLink key={i} href={item.link} target=\"_blank\">\n              {item.text}\n            </StyledLink>\n          ))}\n          <StyledLogo href=\"https://openvisualization.org\" target=\"_blank\"/>\n        </div>\n      </StyledHeader>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/hero.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled, {keyframes} from 'styled-components';\nimport {window} from 'global';\n\nimport {media, breakPoints} from '../styles';\nimport {HERO_IMAGES, HERO_IMAGES_SCALED} from '../content';\nimport SlideShow from './common/slideshow';\nimport {LinkButton, GithubButton} from './common/styled-components';\nimport {DEMO_DUCKDB_LINK, DEMO_LINK} from '../constants';\n\nimport {Container, Content, HeroImage, LogoImage, StyledCaption} from './common/styles';\n\nconst SlideShowAnimation = keyframes`\n  0% {\n    opacity: 0;\n    transform: translate(0, 20px);\n  }\n\n  100% {\n    opacity: 1.0\n  }\n`;\n\nconst FadeIn = styled.div`\n  animation-name: ${SlideShowAnimation};\n  animation-timing-function: ease-in-out;\n  animation-duration: 1s;\n  animation-delay: 500ms;\n  animation-fill-mode: both;\n`;\n\nconst ButtonContainer = styled.div`\n  display: flex;\n  justify-content: center;\n\n  a {\n    width: 160px;\n  }\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    a {\n      width: 50%;\n    }\n  `};\n`;\n\nexport default class Hero extends PureComponent {\n  state = {\n    window: window.innerWidth,\n    height: window.innerHeight\n  };\n\n  componentDidMount() {\n    window.addEventListener('resize', this.resize);\n    this.resize();\n  }\n\n  componentWillUnmount() {\n    window.removeEventListener('resize', this.resize);\n  }\n\n  resize = () => {\n    this.setState({\n      width: window.innerWidth,\n      height: window.innerHeight\n    });\n  };\n\n  render() {\n    const isPalm = this.state.width <= breakPoints.palm;\n    return (\n      <Container>\n        <HeroImage />\n        <Content>\n          <LogoImage />\n          <StyledCaption>\n            <div className=\"kg-home__caption__subtitle\">Make an impact with your location data</div>\n            <div className=\"kg-home__caption__description\">\n              <span>Kepler.gl is a powerful </span>\n              <span className=\"t-bold\"> open source </span>\n              <span>geospatial analysis tool&nbsp;for </span>\n              <span className=\"t-bold\">large-scale&nbsp;</span>\n              <span>data sets.</span>\n            </div>\n            <ButtonContainer>\n              <LinkButton large href={DEMO_LINK}>\n                Get Started\n              </LinkButton>\n              <LinkButton\n                large\n                href={DEMO_DUCKDB_LINK}\n                style={{marginLeft: '5px', backgroundColor: '#20469c'}}\n              >\n                Try with DuckDB\n              </LinkButton>\n              <GithubButton\n                href=\"https://github.com/keplergl/kepler.gl\"\n                style={{marginLeft: '5px'}}\n              />\n            </ButtonContainer>\n          </StyledCaption>\n          <FadeIn>\n            <SlideShow images={isPalm ? HERO_IMAGES_SCALED : HERO_IMAGES} />\n          </FadeIn>\n        </Content>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/home.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport {ThemeProvider} from 'styled-components';\nimport Window from 'global/window';\n\nimport {theme} from '../styles';\nimport {SECTIONS} from '../content';\nimport Hero from './hero';\nimport Showcase from './showcase';\nimport Examples from './examples';\nimport Tutorials from './tutorials';\nimport Walkthrough from './walkthrough';\nimport Features from './features';\nimport Ecosystems from './ecosystems';\nimport Studio from './studio';\nimport Desktop from './desktop';\nimport Footer from './footer';\nimport Section from './common/section';\nimport Header from './header';\nimport Banner from '../../../examples/demo-app/src/components/banner';\nimport Announcement from '../../../examples/demo-app/src/components/announcement';\n\nconst BannerKey = 'kgHideBanner-iiba';\nconst BannerHeight = 30;\nconst BACKGROUND_COLOR = '#2E7CF6';\n\nconst SECTION_CONTENT = {\n  showcase: Showcase,\n  walkthrough: Walkthrough,\n  features: Features,\n  examples: Examples,\n  tutorials: Tutorials,\n  ecosystems: Ecosystems,\n  studio: Studio,\n  desktop: Desktop\n};\n\nexport default class Home extends PureComponent {\n  state = {\n    showBanner: false\n  };\n\n  componentDidMount() {\n    // delay 2s to show the banner\n    // if (!window.localStorage.getItem(BannerKey)) {\n    //   window.setTimeout(this._showBanner, 3000);\n    // }\n  }\n\n  _showBanner = () => {\n    this.setState({showBanner: true});\n  };\n\n  _hideBanner = () => {\n    this.setState({showBanner: false});\n  };\n\n  _disableBanner = () => {\n    this._hideBanner();\n    Window.localStorage.setItem(BannerKey, 'true');\n  };\n\n  render() {\n    return (\n      <ThemeProvider theme={theme}>\n        <div>\n          <Banner\n            show={this.state.showBanner}\n            height={BannerHeight}\n            bgColor={BACKGROUND_COLOR}\n            onClose={this._hideBanner}\n          >\n            <Announcement onDisable={this._disableBanner} />\n          </Banner>\n          <Header />\n          <Hero />\n          {SECTIONS.map(({id, title, description, icon, isDark, background}, i) => {\n            const SectionContent = SECTION_CONTENT[id];\n            return (\n              <Section\n                key={`section-${i}`}\n                title={title}\n                description={description}\n                icon={icon}\n                isDark={isDark}\n                background={background}\n              >\n                <SectionContent />\n              </Section>\n            );\n          })}\n          <Footer />\n        </div>\n      </ThemeProvider>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/mapbox-logo.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\n\nconst MapboxLogo = styled.a`\n  background-image: url('data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 viewBox%3D%220 0 790 180%22%3E%3Cpath d%3D%22M89.1 1.8C39.9 1.8 0 41.7 0 90.9 0 140.1 39.9 180 89.1 180c49.2 0 89.1-39.9 89.1-89.1 0-49.2-39.9-89.1-89.1-89.1zm457.8 19.7c-1.2 0-2.2 1-2.2 2.2v103.2c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2v-7.1c6.9 7.2 16.4 11.3 26.3 11.3 20.9 0 37.9-18 37.9-40.3 0-22.3-17-40.2-37.9-40.2-10 0-19.5 4.1-26.3 11.3V23.7c0-1.2-1-2.2-2.2-2.2h-13.4zM98.3 36.4c11.4.3 22.9 4.8 31.7 13.7 17.7 17.7 18.3 45.7 1.4 62.7-30.5 30.5-84.8 20.7-84.8 20.7s-9.8-54.3 20.7-84.8c8.5-8.4 19.7-12.5 31-12.3zm160.3 14.2c-8.2 0-15.9 4-20.8 10.6v-6.4c0-1.2-1-2.2-2.2-2.2h-13.4c-1.2 0-2.2 1-2.2 2.2V127c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2V83.8c.5-9.7 7.2-17.3 15.4-17.3 8.5 0 15.6 7.1 15.6 16.5v44c0 1.2 1 2.2 2.2 2.2h13.5c1.2 0 2.2-1 2.2-2.2l-.1-44.9c1.2-8.8 7.6-15.6 15.3-15.6 8.5 0 15.6 7.1 15.6 16.5v44c0 1.2 1 2.2 2.2 2.2h13.5c1.2 0 2.2-1 2.2-2.2l-.1-49.6c.3-14.8-12.3-26.8-27.9-26.8-10 .1-19.2 5.9-23.5 15-5-9.3-14.7-15.1-25.3-15zm127.9 0c-20.9 0-37.9 18-37.9 40.3 0 22.3 17 40.3 37.9 40.3 10 0 19.5-4.1 26.3-11.3v7.1c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2V54.8c.1-1.2-.9-2.2-2.2-2.2H415c-1.2 0-2.2 1-2.2 2.2v7.1c-6.9-7.2-16.4-11.3-26.3-11.3zm106.1 0c-10 0-19.5 4.1-26.3 11.3v-7.1c0-1.2-1-2.2-2.2-2.2h-13.4c-1.2 0-2.2 1-2.2 2.2V158c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2v-38.2c6.9 7.2 16.4 11.3 26.3 11.3 20.9 0 37.9-18 37.9-40.3 0-22.3-17-40.2-37.9-40.2zm185.5 0c-22.7 0-41 18-41 40.3 0 22.3 18.4 40.3 41 40.3s41-18 41-40.3c0-22.3-18.3-40.3-41-40.3zm45.4 2c-1.1 0-2 .9-2 2 0 .4.1.8.3 1.1l23 35-23.3 35.4c-.6.9-.4 2.2.6 2.8.3.2.7.3 1.1.3h15.5c1.2 0 2.3-.6 2.9-1.6l13.8-23.1 13.8 23.1c.6 1 1.7 1.6 2.9 1.6h15.5c1.1 0 2-.9 2-2 0-.4-.1-.7-.3-1.1L766 90.7l23-35c.6-.9.4-2.2-.6-2.8-.3-.2-.7-.3-1.1-.3h-15.5c-1.2 0-2.3.6-2.9 1.6l-13.5 22.7-13.5-22.7c-.6-1-1.7-1.6-2.9-1.6h-15.5zM99.3 54l-8.7 18-17.9 8.7 17.9 8.7 8.7 18 8.8-18 17.9-8.7-17.9-8.7-8.8-18zm290.3 12.7c12.7 0 23 10.7 23.2 23.9v.6c-.1 13.2-10.5 23.9-23.2 23.9-12.8 0-23.2-10.8-23.2-24.2 0-13.4 10.4-24.2 23.2-24.2zm99.8 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.7 0-23-10.7-23.2-23.9v-.6c.2-13.2 10.5-23.9 23.2-23.9zm96.3 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.7 0-23-10.7-23.2-23.9v-.6c.2-13.2 10.5-23.9 23.2-23.9zm92.2 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.8 0-23.2-10.8-23.2-24.2 0-13.4 10.4-24.2 23.2-24.2z%22 fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E%0A');\n  background-size: 99px 28px;\n  background-repeat: no-repeat;\n  width: 100px;\n  display: flex;\n  height: 28px;\n  flex-shrink: 0;\n`;\n\nexport default MapboxLogo;\n"
  },
  {
    "path": "website/src/components/netlify-logo.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nconst LINK_STYLE = {display: 'flex'};\n\nconst NetlifyLogo = () => (\n  <a href=\"https://www.netlify.com\" style={LINK_STYLE}>\n    <svg width=\"114\" height=\"51\">\n      <g\n        xmlns=\"http://www.w3.org/2000/svg\"\n        stroke=\"none\"\n        strokeWidth=\"1\"\n        fill=\"none\"\n        fillRule=\"evenodd\"\n      >\n        <g>\n          <g transform=\"translate(8.000000, 8.000000)\" fill=\"#FFFFFF\">\n            <path\n              d=\"M23.6161838,12.0243902 C23.7871091,12.1101766 23.9295469,12.2245585 24.0434971,12.353238 C24.0577409,12.3675357 24.0577409,12.3675357 24.0719847,12.3675357 L24.0862285,12.3675357 L27.3622974,10.9520606 C27.3765412,10.9377628 27.3907849,10.9234651 27.3907849,10.9091674 C27.3907849,10.8948696 27.3907849,10.8805719 27.3765412,10.8662742 L24.3141289,7.79226241 C24.2998851,7.77796468 24.2856414,7.77796468 24.2856414,7.77796468 L24.2713976,7.77796468 C24.2571538,7.77796468 24.24291,7.79226241 24.24291,7.82085786 L23.5734525,11.9814971 C23.5876962,11.9957948 23.60194,12.0243902 23.6161838,12.0243902 Z M16.8219017,9.23633305 C16.9785833,9.47939445 17.0782897,9.76534903 17.1067773,10.0513036 C17.1067773,10.0656013 17.1210211,10.0798991 17.1352648,10.0941968 L22.0066369,12.195963 L22.0208807,12.195963 C22.0351244,12.195963 22.0493682,12.195963 22.0493682,12.1816653 C22.191806,12.0672834 22.3627313,11.9814971 22.5479004,11.9243061 C22.5621442,11.9243061 22.576388,11.9100084 22.576388,11.881413 L23.3740396,6.86291001 C23.3740396,6.84861228 23.3740396,6.83431455 23.3597958,6.82001682 L20.3116273,3.74600505 C20.2973835,3.73170732 20.2973835,3.73170732 20.2831398,3.73170732 C20.268896,3.73170732 20.2546522,3.74600505 20.2546522,3.76030278 L16.8219017,9.16484441 C16.8076579,9.19343987 16.8076579,9.22203532 16.8219017,9.23633305 Z M33.4586343,16.9714045 L28.2311678,11.7098402 C28.2169241,11.6955425 28.2026803,11.6955425 28.2026803,11.6955425 L28.1884365,11.6955425 L24.6417358,13.2253995 C24.627492,13.2396972 24.6132482,13.253995 24.6132482,13.2682927 C24.6132482,13.2825904 24.627492,13.3111859 24.6417358,13.3111859 L33.3874154,17.0714886 L33.4016592,17.0714886 C33.415903,17.0714886 33.4301468,17.0714886 33.4301468,17.0571909 L33.4586343,17.0285955 C33.4871219,17.0285955 33.4871219,16.9857023 33.4586343,16.9714045 Z M32.5897639,17.8292683 L24.2001787,14.2262405 L24.1859349,14.2262405 C24.1716911,14.2262405 24.1574474,14.2262405 24.1432036,14.2405383 C23.9153031,14.5550883 23.5734525,14.7552565 23.1746267,14.8124474 C23.1603829,14.8124474 23.1318953,14.8267452 23.1318953,14.8553406 L22.2345373,20.4457527 C22.2345373,20.4600505 22.2345373,20.4743482 22.2487811,20.4886459 C22.5621442,20.7317073 22.7473133,21.0891505 22.7900447,21.489487 C22.7900447,21.5180824 22.8042884,21.5323802 22.832776,21.5323802 L27.9035609,22.6047098 L27.9178047,22.6047098 C27.9320485,22.6047098 27.9462923,22.6047098 27.9462923,22.5904121 L32.5897639,17.9150547 C32.6040077,17.9007569 32.6040077,17.8864592 32.6040077,17.8721615 C32.6040077,17.8578638 32.6040077,17.843566 32.5897639,17.8292683 Z M21.4796171,13.0538267 L16.8931206,11.0807401 L16.8788768,11.0807401 C16.8646331,11.0807401 16.8503893,11.0950378 16.8361455,11.1093356 C16.5227824,11.5954584 15.9957626,11.881413 15.4260115,11.881413 C15.3405488,11.881413 15.2550862,11.8671152 15.1553797,11.8528175 L15.1411359,11.8528175 C15.1268922,11.8528175 15.1126484,11.8671152 15.0984046,11.881413 L11.3238034,17.8149706 C11.3095597,17.8292683 11.3095597,17.8578638 11.3238034,17.8721615 C11.3380472,17.8864592 11.352291,17.8864592 11.3665348,17.8864592 L11.3807786,17.8864592 L21.4511295,13.5256518 C21.4653733,13.5113541 21.4796171,13.4970563 21.4796171,13.4827586 L21.4796171,13.4255677 L21.4796171,13.3540791 C21.4796171,13.2682927 21.4938609,13.1825063 21.5081047,13.1110177 C21.5081047,13.0824222 21.4938609,13.0681245 21.4796171,13.0538267 Z M27.0062029,23.4339781 L22.5479004,22.5046257 L22.5336567,22.5046257 C22.5194129,22.5046257 22.5051691,22.5189235 22.4909253,22.5189235 C22.32,22.7333894 22.1063433,22.9049622 21.8499553,23.0050463 C21.8357116,23.0050463 21.8214678,23.0336417 21.8214678,23.0479394 L20.7531844,29.7106812 C20.7531844,29.7392767 20.7674282,29.7535744 20.781672,29.7678722 L20.8101595,29.7678722 C20.8244033,29.7678722 20.8386471,29.7678722 20.8386471,29.7535744 L27.0204467,23.5340622 C27.0346905,23.5197645 27.0346905,23.5054668 27.0346905,23.491169 C27.0346905,23.4482759 27.0204467,23.4339781 27.0062029,23.4339781 Z M20.781672,22.9764508 C20.3543586,22.804878 20.0409955,22.4188394 19.9270453,21.9756098 C19.9270453,21.961312 19.9128015,21.9470143 19.884314,21.9327166 L11.6229228,20.2026913 C11.6229228,20.2026913 11.6229228,20.2026913 11.608679,20.2026913 C11.5944352,20.2026913 11.5801914,20.2169891 11.5659477,20.2312868 C11.5232163,20.3027754 11.4947288,20.3599664 11.4519974,20.4171573 C11.4377537,20.431455 11.4377537,20.4600505 11.4519974,20.4743482 L18.9727122,31.5121951 C18.986956,31.5264929 18.986956,31.5264929 19.0011997,31.5264929 C19.0154435,31.5264929 19.0296873,31.5264929 19.0296873,31.5121951 L19.4854882,31.0546678 C19.4854882,31.0403701 19.499732,31.0403701 19.499732,31.0260723 L20.781672,23.019344 C20.8101595,23.019344 20.8101595,22.9907485 20.781672,22.9764508 Z M11.7938481,19.1875526 C11.7938481,19.216148 11.8080919,19.2304458 11.8365795,19.2304458 L20.0267518,20.9461733 L20.0409955,20.9461733 C20.0552393,20.9461733 20.0694831,20.9318755 20.0837269,20.9175778 C20.3258711,20.4886459 20.7531844,20.2026913 21.2374729,20.1740959 C21.2659604,20.1740959 21.2802042,20.1597981 21.2802042,20.1312027 L22.1633184,14.626577 C22.1633184,14.6122792 22.1633184,14.5836838 22.1348309,14.5836838 C22.0778558,14.5407906 22.0208807,14.4978974 21.9496618,14.4264087 C21.935418,14.412111 21.9211742,14.412111 21.9211742,14.412111 L21.9069304,14.412111 L11.7796043,18.8015139 C11.7511168,18.8158116 11.7511168,18.8301093 11.7511168,18.8587048 C11.7653606,18.9730866 11.7938481,19.0731707 11.7938481,19.1875526 Z M8.36109764,20.5744323 C8.31836631,20.5172414 8.27563497,20.4600505 8.23290364,20.3885618 C8.21865986,20.3742641 8.20441608,20.3599664 8.1901723,20.3599664 L8.17592853,20.3599664 L4.6434716,21.8898234 C4.62922782,21.8898234 4.61498405,21.9041211 4.61498405,21.9184188 C4.61498405,21.9327166 4.61498405,21.9470143 4.62922782,21.961312 L6.35272495,23.6913373 C6.36696873,23.705635 6.38121251,23.705635 6.38121251,23.705635 C6.39545629,23.705635 6.40970006,23.6913373 6.42394384,23.6770395 L8.37534142,20.6030278 C8.37534142,20.6030278 8.37534142,20.58873 8.36109764,20.5744323 Z M10.6970772,21.1320437 C10.6828334,21.117746 10.6685897,21.1034483 10.6543459,21.1034483 L10.6401021,21.1034483 C10.3837141,21.2178301 10.1273261,21.275021 9.85669432,21.275021 C9.64303765,21.275021 9.44362476,21.2464256 9.22996809,21.1749369 L9.21572431,21.1749369 C9.20148054,21.1749369 9.18723676,21.1892347 9.17299298,21.2035324 L7.12188896,24.4348192 L7.10764518,24.4491169 C7.0934014,24.4634146 7.0934014,24.4920101 7.10764518,24.5063078 L16.5370262,33.9857023 C16.5512699,34 16.5655137,34 16.5655137,34 C16.5797575,34 16.5940013,34 16.5940013,33.9857023 L18.2462795,32.312868 C18.2605233,32.2985702 18.2605233,32.2699748 18.2462795,32.255677 L10.6970772,21.1320437 Z M9.37240587,17.4003364 C9.38664965,17.4146341 9.40089343,17.4289319 9.4151372,17.4289319 L9.42938098,17.4289319 C9.57181876,17.4003364 9.72850032,17.371741 9.8709381,17.371741 C10.0276197,17.371741 10.198545,17.4003364 10.3552265,17.4432296 L10.3694703,17.4432296 C10.3837141,17.4432296 10.3979579,17.4289319 10.4122017,17.4146341 L14.2295341,11.4095879 C14.2437779,11.3952902 14.2437779,11.3666947 14.2295341,11.352397 C13.9304148,11.0378469 13.7594895,10.6232128 13.7594895,10.1799832 C13.7594895,10.0513036 13.7737332,9.92262405 13.8022208,9.79394449 C13.8022208,9.76534903 13.787977,9.7510513 13.7737332,9.73675357 C13.2894448,9.52228764 9.00206765,7.6921783 9.00206765,7.67788057 L8.98782387,7.67788057 C8.97358009,7.67788057 8.95933631,7.67788057 8.95933631,7.6921783 L5.32717294,11.352397 C5.31292916,11.3666947 5.31292916,11.3952902 5.32717294,11.4095879 L9.37240587,17.4003364 Z M9.78547543,6.93439865 C9.78547543,6.93439865 14.1155839,8.79310345 14.300753,8.87888982 L14.3149968,8.87888982 C14.3292406,8.87888982 14.3292406,8.87888982 14.3434844,8.86459209 C14.6426037,8.6215307 15.0271857,8.47855341 15.4117677,8.47855341 C15.5969368,8.47855341 15.7821059,8.50714886 15.967275,8.56433978 L15.9815188,8.56433978 C15.9957626,8.56433978 16.0100064,8.55004205 16.0242502,8.53574432 L19.5424633,3.00252313 C19.5567071,2.9882254 19.5567071,2.95962994 19.5424633,2.94533221 L16.6224888,0.0142977292 C16.6082451,0 16.6082451,0 16.5940013,0 C16.5797575,0 16.5655137,0 16.5655137,0.0142977292 L9.78547543,6.84861228 C9.77123165,6.86291001 9.77123165,6.87720774 9.77123165,6.89150547 C9.75698787,6.92010093 9.77123165,6.92010093 9.78547543,6.93439865 Z M8.10470964,18.4440706 C8.11895341,18.4440706 8.13319719,18.4297729 8.14744097,18.4154752 C8.23290364,18.2439024 8.36109764,18.0866274 8.48929164,17.9436501 C8.50353542,17.9293524 8.50353542,17.9007569 8.48929164,17.8864592 C8.44656031,17.8292683 4.58649649,12.1673675 4.58649649,12.1530698 C4.57225271,12.1387721 4.57225271,12.1387721 4.54376516,12.1244743 C4.52952138,12.1244743 4.5152776,12.1244743 4.5152776,12.1387721 L0.0142437779,16.6568545 C0,16.6711522 0,16.68545 0,16.6997477 C0,16.7140454 0.0142437779,16.7283431 0.0427313338,16.7283431 L8.10470964,18.4440706 C8.09046586,18.4440706 8.09046586,18.4440706 8.10470964,18.4440706 Z M7.73437141,19.430614 C7.73437141,19.4020185 7.72012763,19.3877208 7.69164008,19.3877208 L0.697945118,17.9150547 C0.697945118,17.9150547 0.697945118,17.9150547 0.68370134,17.9150547 C0.669457562,17.9150547 0.655213784,17.9293524 0.640970006,17.9436501 C0.626726228,17.9579479 0.640970006,17.9865433 0.655213784,18.000841 L3.77460115,21.1463415 C3.78884493,21.1606392 3.8030887,21.1606392 3.8030887,21.1606392 L3.81733248,21.1606392 L7.69164008,19.4878049 C7.72012763,19.4592094 7.73437141,19.4449117 7.73437141,19.430614 Z\"\n              id=\"Combined-Shape-Copy\"\n            />\n            <path\n              d=\"M67.5018666,14.7649667 L69.7772159,14.7649667 L69.7772159,29.3120637 L67.5018666,29.3120637 L67.5018666,14.7649667 Z M44.2221988,18.869426 C42.9991985,18.869426 42.0179541,19.3301306 41.2642447,20.237579 L41.19314,19.0509157 L39.06,19.0509157 L39.06,29.298103 L41.3353493,29.298103 L41.3353493,22.0105937 C41.7904192,21.172949 42.473024,20.7541266 43.3831637,20.7541266 C44.0088848,20.7541266 44.4639547,20.9076948 44.7483733,21.228792 C45.032792,21.5359284 45.1607804,22.0245545 45.1607804,22.6667488 L45.1607804,29.298103 L47.4361297,29.298103 L47.4361297,22.5271413 C47.4076878,20.0979716 46.3411178,18.869426 44.2221988,18.869426 Z M54.0488637,18.869426 C53.1813868,18.869426 52.3992354,19.0927979 51.6881888,19.5395418 C50.9771421,19.9862856 50.4367466,20.6145192 50.0385605,21.4242424 C49.6545953,22.2339657 49.4555022,23.1414141 49.4555022,24.1605486 L49.4555022,24.4397635 C49.4555022,25.9614848 49.9105721,27.1900304 50.8064909,28.1114396 C51.7024097,29.0328488 52.8685262,29.4935534 54.3190614,29.4935534 C55.1580965,29.4935534 55.9260269,29.3260245 56.6086317,28.9909666 C57.2912365,28.6559087 57.831632,28.1812433 58.2298181,27.5669705 L57.0068178,26.3803071 C56.3526549,27.2319126 55.4993989,27.6646957 54.4612708,27.6646957 C53.7217822,27.6646957 53.0960612,27.4134023 52.6125494,26.9247762 C52.1148168,26.4361501 51.844619,25.7660343 51.7735144,24.9144288 L58.4004693,24.9144288 L58.4004693,23.9930196 C58.4004693,22.3596124 58.0165041,21.1031453 57.2770156,20.2096576 C56.4948642,19.3161698 55.4282942,18.869426 54.0488637,18.869426 Z M56.12512,23.2251786 L51.7877353,23.2251786 C51.8872818,22.4294161 52.1432586,21.8151433 52.5272238,21.3823602 C52.911189,20.9356163 53.4231426,20.7262051 54.0488637,20.7262051 C54.6745848,20.7262051 55.1723174,20.9216556 55.5278408,21.3125565 C55.8833641,21.7034573 56.0824572,22.2898087 56.1393409,23.0576497 L56.1393409,23.2251786 L56.12512,23.2251786 Z M63.5200053,27.3296378 C63.363575,27.1621089 63.2924703,26.8689332 63.2924703,26.4780324 L63.2924703,20.7541266 L65.0843079,20.7541266 L65.0843079,19.0509157 L63.2924703,19.0509157 L63.2924703,16.5659029 L61.017121,16.5659029 L61.017121,19.0509157 L59.3532718,19.0509157 L59.3532718,20.7541266 L61.017121,20.7541266 L61.017121,26.5617968 C61.017121,28.5163012 61.8988189,29.4935534 63.6479937,29.4935534 C64.1315054,29.4935534 64.6292381,29.4237497 65.1554126,29.2701815 L65.1554126,27.483206 C64.8852149,27.5530098 64.6150171,27.5809313 64.3590403,27.5809313 C63.9466333,27.594892 63.6764355,27.5111275 63.5200053,27.3296378 Z M73.3040074,19.0648764 L75.5793567,19.0648764 L75.5793567,29.3120637 L73.3040074,29.3120637 L73.3040074,19.0648764 Z M88.4919641,26.0173277 L86.3446032,19.0648764 L83.8843818,19.0648764 L87.453836,29.2562207 L87.1267545,30.1357477 C86.9561033,30.6383346 86.7285684,30.9873532 86.4299288,31.1967644 C86.1455102,31.4061756 85.7046612,31.5178615 85.1358239,31.5178615 L84.7091959,31.4899401 L84.7091959,33.2769155 C85.107382,33.3886015 85.4771263,33.4444444 85.8042078,33.4444444 C87.2831848,33.4444444 88.3355339,32.5788782 88.9612549,30.8617065 L93,19.0648764 L90.5682204,19.0648764 L88.4919641,26.0173277 Z M80.0447298,15.4909255 C79.4190087,16.1051983 79.1061482,16.9847253 79.1061482,18.1295064 L79.1061482,19.0648764 L77.5560664,19.0648764 L77.5560664,20.7680874 L79.1061482,20.7680874 L79.1061482,29.3120637 L81.3814975,29.3120637 L81.3814975,20.7680874 L83.4435328,20.7680874 L83.4435328,19.0648764 L81.3814975,19.0648764 L81.3814975,18.1574279 C81.3814975,17.0266075 81.921893,16.4681777 83.0169048,16.4681777 C83.3439863,16.4681777 83.6426259,16.4960992 83.8843818,16.5379814 L83.9412655,14.7370452 C83.4861956,14.6253593 83.0737886,14.5695163 82.6613815,14.5695163 C81.5521487,14.5555556 80.6704508,14.8766527 80.0447298,15.4909255 Z M75.5793567,14.5555556 L75.5793567,16.5659029 L73.3040074,16.5659029 L73.3040074,14.5555556 L75.5793567,14.5555556 Z\"\n              id=\"Combined-Shape\"\n            />\n          </g>\n          <path\n            d=\"M47,18.7088317 L47,13.0811682 L48.9325768,13.0811682 C49.6205775,13.0811682 50.187461,13.3008356 50.6332443,13.7401769 C51.0790276,14.1795182 51.3019159,14.7431808 51.3019159,15.4311816 L51.3019159,16.3626836 C51.3019159,17.0532611 51.0790276,17.6169237 50.6332443,18.0536882 C50.187461,18.4904527 49.6205775,18.7088317 48.9325768,18.7088317 L47,18.7088317 Z M48.1286248,13.9508278 L48.1286248,17.8430374 L48.8745995,17.8430374 C49.2791542,17.8430374 49.5960936,17.70647 49.8254272,17.4333311 C50.0547608,17.1601922 50.1694259,16.8033133 50.1694259,16.3626836 L50.1694259,15.4234513 C50.1694259,14.9879751 50.0547608,14.6336729 49.8254272,14.360534 C49.5960936,14.0873952 49.2791542,13.9508278 48.8745995,13.9508278 L48.1286248,13.9508278 Z M56.6358277,16.2351335 L54.3051401,16.2351335 L54.3051401,17.8430374 L57.0300734,17.8430374 L57.0300734,18.7088317 L53.1765153,18.7088317 L53.1765153,13.0811682 L57.0223431,13.0811682 L57.0223431,13.9508278 L54.3051401,13.9508278 L54.3051401,15.3654739 L56.6358277,15.3654739 L56.6358277,16.2351335 Z M59.84004,16.6680307 L59.84004,18.7088317 L58.7114151,18.7088317 L58.7114151,13.0811682 L60.9532042,13.0811682 C61.5999764,13.0811682 62.1088832,13.2460798 62.4799398,13.5759079 C62.8509964,13.905736 63.0365219,14.3399172 63.0365219,14.8784646 C63.0365219,15.417012 62.8509964,15.8499049 62.4799398,16.1771562 C62.1088832,16.5044075 61.5999764,16.6680307 60.9532042,16.6680307 L59.84004,16.6680307 Z M59.84004,15.7983711 L60.9532042,15.7983711 C61.2675716,15.7983711 61.5059203,15.7126944 61.6682576,15.5413384 C61.8305948,15.3699824 61.9117623,15.1516034 61.9117623,14.8861949 C61.9117623,14.6156328 61.831239,14.3921003 61.6701902,14.2155908 C61.5091413,14.0390812 61.2701484,13.9508278 60.9532042,13.9508278 L59.84004,13.9508278 L59.84004,15.7983711 Z M65.9856341,17.8430374 L68.4902535,17.8430374 L68.4902535,18.7088317 L64.8570092,18.7088317 L64.8570092,13.0811682 L65.9856341,13.0811682 L65.9856341,17.8430374 Z M74.4271293,16.4013351 C74.4271293,17.0919126 74.2087503,17.6626613 73.7719858,18.1135981 C73.3352213,18.5645349 72.7676936,18.79 72.0693857,18.79 C71.3762314,18.79 70.813213,18.5645349 70.3803136,18.1135981 C69.9474142,17.6626613 69.7309678,17.0919126 69.7309678,16.4013351 L69.7309678,15.3886649 C69.7309678,14.7006641 69.9467701,14.1305597 70.378381,13.6783344 C70.809992,13.2261092 71.3723662,13 72.0655205,13 C72.7638284,13 73.3320003,13.2261092 73.7700532,13.6783344 C74.2081061,14.1305597 74.4271293,14.7006641 74.4271293,15.3886649 L74.4271293,16.4013351 Z M73.2985045,15.3809346 C73.2985045,14.9428816 73.1889929,14.583426 72.9699665,14.3025567 C72.75094,14.0216875 72.449461,13.881255 72.0655205,13.881255 C71.68158,13.881255 71.3839662,14.0210433 71.1726701,14.3006242 C70.961374,14.580205 70.8557275,14.9403049 70.8557275,15.3809346 L70.8557275,16.4013351 C70.8557275,16.8471184 70.9626623,17.2104392 71.1765352,17.4913084 C71.3904081,17.7721776 71.688022,17.9126101 72.0693857,17.9126101 C72.455903,17.9126101 72.7573819,17.7721776 72.9738316,17.4913084 C73.1902813,17.2104392 73.2985045,16.8471184 73.2985045,16.4013351 L73.2985045,15.3809346 Z M78.0642388,15.6746862 L78.0874297,15.6746862 L79.3165485,13.0811682 L80.5533976,13.0811682 L78.6208209,16.7298731 L78.6208209,18.7088317 L77.4960612,18.7088317 L77.4960612,16.6718958 L75.5982708,13.0811682 L76.83512,13.0811682 L78.0642388,15.6746862 Z M85.0524363,17.2400734 C85.0524363,17.0236237 84.9757782,16.8496936 84.8224597,16.7182777 C84.6691412,16.5868618 84.4005157,16.4631781 84.0165752,16.3472229 C83.3466119,16.1539643 82.8402819,15.9246342 82.4975699,15.6592256 C82.1548579,15.3938171 81.9835044,15.0304963 81.9835044,14.5692523 C81.9835044,14.1080084 82.1799811,13.7311597 82.5729404,13.4386949 C82.9658996,13.1462302 83.4677203,13 84.0784176,13 C84.6968453,13 85.2005986,13.1642674 85.5896927,13.4928071 C85.9787867,13.8213468 86.166889,14.2265396 86.1540051,14.7083979 L86.1462748,14.7315888 L85.0524363,14.7315888 C85.0524363,14.4713338 84.9654712,14.260685 84.7915385,14.0996362 C84.6176057,13.9385873 84.3734593,13.8580641 84.0590919,13.8580641 C83.7576084,13.8580641 83.5244131,13.9250594 83.3594991,14.0590521 C83.1945851,14.1930447 83.1121293,14.3643981 83.1121293,14.5731175 C83.1121293,14.7637993 83.2003827,14.9203365 83.3768923,15.0427336 C83.5534018,15.1651308 83.8581017,15.2946121 84.2910011,15.4311816 C84.9120055,15.6038259 85.3822612,15.8318677 85.7017821,16.1153137 C86.0213031,16.3987597 86.1810612,16.7710991 86.1810612,17.2323431 C86.1810612,17.7142013 85.9910263,18.0942709 85.610951,18.3725634 C85.2308757,18.6508558 84.7290549,18.79 84.1054737,18.79 C83.4921996,18.79 82.9575254,18.6321744 82.501435,18.3165187 C82.0453446,18.0008629 81.8237447,17.5583072 81.8366286,16.9888384 L81.8443589,16.9656475 L82.9420625,16.9656475 C82.9420625,17.3006291 83.0444881,17.5460639 83.2493422,17.7019592 C83.4541964,17.8578546 83.7395707,17.935801 84.1054737,17.935801 C84.4121108,17.935801 84.6465944,17.8726708 84.8089317,17.7464085 C84.9712689,17.6201462 85.0524363,17.4513695 85.0524363,17.2400734 Z M90.985447,18.7088317 L90.985447,13.0811682 L92.8871025,13.0811682 C93.5493354,13.0811682 94.0659725,13.2100054 94.4370291,13.4676836 C94.8080857,13.7253618 94.9936112,14.1105849 94.9936112,14.6233645 C94.9936112,14.8836194 94.9246833,15.1161705 94.7868255,15.3210247 C94.6489676,15.5258788 94.4486258,15.6798393 94.1857941,15.7829105 C94.5233525,15.8550604 94.7765175,16.009665 94.9452967,16.246729 C95.114076,16.4837929 95.1984643,16.758216 95.1984643,17.0700066 C95.1984643,17.6085541 95.020669,18.0163237 94.6650731,18.2933277 C94.3094772,18.5703318 93.8057239,18.7088317 93.1537981,18.7088317 L90.985447,18.7088317 Z M92.1140718,16.2196729 L92.1140718,17.8430374 L93.1537981,17.8430374 C93.4527048,17.8430374 93.6807466,17.7779746 93.8379303,17.6478471 C93.995114,17.5177196 94.0737046,17.3251081 94.0737046,17.0700066 C94.0737046,16.794291 94.0067093,16.5836422 93.8727166,16.438054 C93.738724,16.2924659 93.527431,16.2196729 93.2388315,16.2196729 L92.1140718,16.2196729 Z M92.1140718,15.4389119 L92.925754,15.4389119 C93.2272375,15.4389119 93.4591444,15.37707 93.6214817,15.2533845 C93.7838189,15.129699 93.8649863,14.9493269 93.8649863,14.712263 C93.8649863,14.452008 93.7831747,14.2600407 93.6195491,14.1363551 C93.4559234,14.0126696 93.211777,13.9508278 92.8871025,13.9508278 L92.1140718,13.9508278 L92.1140718,15.4389119 Z M98.5109009,15.6746862 L98.5340918,15.6746862 L99.7632106,13.0811682 L101.00006,13.0811682 L99.067483,16.7298731 L99.067483,18.7088317 L97.9427233,18.7088317 L97.9427233,16.6718958 L96.0449329,13.0811682 L97.281782,13.0811682 L98.5109009,15.6746862 Z\"\n            id=\"DEPLOYS-BY\"\n            fill=\"#BCBCBC\"\n          />\n        </g>\n      </g>\n    </svg>\n  </a>\n);\n\nexport default NetlifyLogo;\n"
  },
  {
    "path": "website/src/components/policy.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled, {ThemeProvider} from 'styled-components';\nimport {Container, Content, HeroImage, LogoImage, StyledCaption} from './common/styles';\nimport {theme} from '../styles';\nimport Header from './header';\n\nconst GITHUB_PROJECT = 'https://github.com/keplergl/kepler.gl';\nconst GITHUB_PROJECT_ISSUES = `${GITHUB_PROJECT}/issues`;\nconst MAILING_LIST_URL = 'https://groups.google.com/d/forum/kepler-gl';\nconst CONTRIBUTING_URL = 'https://docs.kepler.gl/contributing';\n\nconst StyledContainer = styled(Container)`\n  background-color: black;\n  height: 100vh;\n\n  max-width: unset;\n\n  .description {\n    max-width: 800px;\n  }\n\n  .description-content {\n    font-weight: 500;\n  }\n\n  h3 {\n    margin-bottom: 24px;\n  }\n\n  a {\n    text-decoration: underline;\n    color: white;\n  }\n\n  a:visited {\n    color: white;\n  }\n\n  img.hero-image {\n    height: 100vh;\n  }\n`;\n\nexport default class Home extends PureComponent {\n  render() {\n    return (\n      <ThemeProvider theme={theme}>\n        <div>\n          <Header />\n          <StyledContainer>\n            <HeroImage className=\"hero-image\" />\n            <Content>\n              <LogoImage />\n              <StyledCaption>\n                <div className=\"kg-home__caption__subtitle\">Support Policy</div>\n                <div className=\"description\">\n                  <h3>\n                    <a href=\"https://kepler.gl\">Kepler.gl</a> is an open source project. Its source\n                    code is available at <a href={GITHUB_PROJECT}>Kepler.gl Github project</a>.\n                  </h3>\n                  <span className=\"description-content\">\n                    As an open source project, questions and bug reports can be filed with the\n                    project community, and you can participate in the development of the project's\n                    source code. Bug reports and issues can be submitted at{' '}\n                    <a href={GITHUB_PROJECT_ISSUES}>Kepler.gl Github issues</a>. Questions can be\n                    asked on the project's mailing lists, which you can subscribe at{' '}\n                    <a href={MAILING_LIST_URL}>Kepler.gl Mailing list</a>\n                    Please review the applicable{' '}\n                    <a href={CONTRIBUTING_URL}>contributing guidelines</a> and procedures in\n                    connection with your submissions. Please note that the Kepler.gl project and\n                    OpenJS Foundation do not otherwise provide support for users of\n                    Kepler.gl software.\n                  </span>\n                </div>\n              </StyledCaption>\n            </Content>\n          </StyledContainer>\n        </div>\n      </ThemeProvider>\n    );\n  }\n}\n"
  },
  {
    "path": "website/src/components/showcase.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {SHOWCASE_ITEMS} from '../content';\nimport {media} from '../styles';\nimport Carousel from './common/carousel';\n\nconst CarouselContainer = styled.div`\n  height: 360px;\n  ${media.palm`\n    height: 240px;\n  `} ${media.desk`\n    height: 480px;\n  `};\n`;\n\nconst Image = styled.img`\n  width: 100%;\n  height: 100%;\n  box-shadow: 0px 12px 24px rgba(0, 0, 0, 0.25);\n  object-fit: cover;\n\n  width: 420px;\n  height: 320px;\n\n  ${media.palm`\n    width: 300px;\n    height: 200px;\n  `} ${media.desk`\n    width: 560px;\n    height: 420px;\n  `};\n`;\n\nconst NavContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  margin-top: 24px;\n`;\n\nconst NavItem = styled.div`\n  margin: ${props => props.theme.margins.tiny};\n  font-size: 10px;\n  text-align: center;\n  filter: ${props => props.isActive && 'brightness(300%)'};\n  transform: ${props => props.isActive && 'scale(1.1)'};\n  transition: transform 500ms, filter 500ms;\n  cursor: pointer;\n  &:hover {\n    transform: scale(1.1);\n  }\n\n  ${media.palm`\n    margin: 2px 4px;\n    font-size: 8px;\n  `};\n`;\n\nconst NavIcon = styled.img`\n  display: block;\n\n  width: 48px;\n  height: 48px;\n\n  ${media.palm`\n    width: 32px;\n    height: 32px;\n  `};\n`;\n\nconst Nav = ({items, selectedIndex, onClick}) => (\n  <NavContainer>\n    {items.map(({text, icon}, i) => (\n      <NavItem key={i} isActive={selectedIndex === i} onClick={() => onClick(i)}>\n        <NavIcon src={icon} />\n        {text}\n      </NavItem>\n    ))}\n  </NavContainer>\n);\n\nclass Showcase extends PureComponent {\n  state = {\n    selectedIndex: 3\n  };\n\n  render() {\n    return (\n      <div>\n        <CarouselContainer>\n          <Carousel\n            selectedIndex={this.state.selectedIndex}\n            onChange={i => this.setState({selectedIndex: i})}\n          >\n            {SHOWCASE_ITEMS.map(({image}, i) => (\n              <Image key={`showcase-image-${i}`} src={image} />\n            ))}\n          </Carousel>\n        </CarouselContainer>\n        <Nav\n          items={SHOWCASE_ITEMS}\n          selectedIndex={this.state.selectedIndex}\n          onClick={i => this.setState({selectedIndex: i})}\n        />\n      </div>\n    );\n  }\n}\n\nexport default Showcase;\n"
  },
  {
    "path": "website/src/components/studio.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {useCallback, useState} from 'react';\nimport styled from 'styled-components';\nimport Swipeable from './common/swipeable';\nimport {fsqStudioUrl} from '../utils';\nimport {LinkButton} from './common/styled-components';\n\nexport const LOCATION_FOURSQUARE_LINK =\n  'https://location.foursquare.com/products/studio/keplergl-vs-studio';\n\nconst getFSQLink = utmCampaign =>\n  `${LOCATION_FOURSQUARE_LINK}?utm_source=Kepler&&utm_medium=partner_site&utm_campaign=${utmCampaign}`;\n\nconst LEARN_MORE_LINK = getFSQLink('studio_signup');\n\nconst SECTIONS = [\n  [\n    {\n      imageUrl: fsqStudioUrl('screenshots/Flow_layer.png'),\n      icon: fsqStudioUrl('logos/Flow_layer.png'),\n      title: 'Flow layer',\n      description: 'Visualize origin-destination movement patterns',\n      link: getFSQLink('Keplermap1_Flow_layer')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/3D_tiles.png'),\n      icon: fsqStudioUrl('logos/3D_tiles.png'),\n      title: '3D tiles',\n      description: 'Stream and render massive 3D geospatial datasets',\n      link: getFSQLink('Keplermap2_3D_tiles')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Analytics_modules.png'),\n      icon: fsqStudioUrl('logos/Analytics_modules.png'),\n      title: 'Analytics modules',\n      description: 'Expedite spatial data analysis to extract valuable information',\n      link: getFSQLink('Keplermap3_Analytics_modules')\n    }\n  ],\n  [\n    {\n      imageUrl: fsqStudioUrl('screenshots/Charts.png'),\n      icon: fsqStudioUrl('logos/Charts.png'),\n      title: 'Charts',\n      description: 'Add statistics and charts to maps including tooltip, bar and line charts',\n      link: getFSQLink('Keplermap4_Charts')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Administrative_boundaries.png'),\n      icon: fsqStudioUrl('logos/Administrative_boundaries.png'),\n      title: 'Administrative boundaries',\n      description: 'Generate the boundary from columns containing administrative identifiers',\n      link: getFSQLink('Keplermap5_Adnministrative_boundaries')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Host_published_maps.png'),\n      icon: fsqStudioUrl('logos/Host_published_maps.png'),\n      title: 'Host published maps',\n      description: 'Host published maps that can be shared with a link',\n      link: getFSQLink('Keplermap6_Host_published_maps')\n    }\n  ],\n  [\n    {\n      imageUrl: fsqStudioUrl('screenshots/Fleet_Visualization.png'),\n      icon: fsqStudioUrl('logos/Fleet_Visualization.png'),\n      title: 'Fleet Visualization',\n      description: 'Playback and study of the movement of entire fleets',\n      link: getFSQLink('Keplermap7_Fleet_Visualization')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Hextile_Analysis.png'),\n      icon: fsqStudioUrl('logos/Hextile_Analysis.png'),\n      title: 'Hextile Analysis',\n      description: 'H3 based tiling system designed for spatial analytics',\n      link: getFSQLink('Keplermap8_Hextile_Analysis')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Tile_Layers.png'),\n      icon: fsqStudioUrl('logos/Tile_Layers.png'),\n      title: 'Tile Layers',\n      description: 'Add layers from Vector tile, Raster tile, and WMS format',\n      link: getFSQLink('Keplermap9_Tile_Layers')\n    }\n  ],\n  [\n    {\n      imageUrl: fsqStudioUrl('screenshots/Dataset_Operation.png'),\n      icon: fsqStudioUrl('logos/Dataset_Operation.png'),\n      title: 'Dataset Operation',\n      description: 'Apply expression, group-by, spatial join operations.',\n      link: getFSQLink('Keplermap10_Dataset_Operation')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Globe.png'),\n      icon: fsqStudioUrl('logos/Globe.png'),\n      title: 'Globe',\n      description: 'A 3D globe view of the Earth for planetary data.',\n      link: getFSQLink('Keplermap11_Globe')\n    },\n    {\n      imageUrl: fsqStudioUrl('screenshots/Remote_Sensing.png'),\n      icon: fsqStudioUrl('logos/Remote_Sensing.png'),\n      title: 'Remote Sensing',\n      description: 'Analysis-Ready high bit-depth raster data in your browser',\n      link: getFSQLink('Keplermap12_Remote_Sensing')\n    }\n  ]\n];\n\nconst Flex = styled.div`\n  display: flex;\n`;\n\nconst StudioContainer = styled(Flex)`\n  width: 100%;\n  align-items: center;\n  flex-direction: column;\n  gap: 72px;\n  overflow-x: hidden;\n`;\n\nconst Section = styled(Flex)`\n  gap: 56px;\n  width: 100%;\n  justify-content: center;\n  justify-items: center;\n  padding-top: 8px;\n`;\n\nconst MapCardImage = styled.img`\n  height: 230px;\n  width: 386px;\n  box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.5);\n  transition: transform 350ms;\n`;\n\nconst MapCard = styled.a`\n  display: flex;\n  flex-direction: column;\n  gap: 56px;\n  align-items: center;\n  cursor: pointer;\n  color: white;\n\n  :visited {\n    color: white;\n  }\n\n  :hover {\n    ${MapCardImage} {\n      transform: scale3d(1.05, 1.05, 1.05);\n    }\n  }\n`;\n\nconst MapCardBody = styled(Flex)`\n  flex-direction: column;\n  gap: 16px;\n  max-width: 300px;\n  align-items: center;\n`;\n\nconst MapCardIcon = styled.img`\n  height: 24px;\n  width: 24px;\n`;\n\nconst MapCardTitle = styled.div`\n  font-size: 20px;\n  line-height: 28px;\n  text-align: center;\n`;\n\nconst MapCardDescription = styled.div`\n  font-size: 16px;\n  line-height: 26px;\n  text-align: center;\n  opacity: 0.7;\n`;\n\nconst CardSection = ({cards}) => (\n  <Section>\n    {cards.map(card => (\n      <MapCard key={card.imageUrl} href={card.link} target=\"_blank\">\n        <MapCardImage src={card.imageUrl} alt={card.title} />\n        <MapCardBody>\n          <MapCardIcon src={card.icon} alt={card.title} />\n          <MapCardTitle>{card.title}</MapCardTitle>\n          <MapCardDescription>{card.description}</MapCardDescription>\n        </MapCardBody>\n      </MapCard>\n    ))}\n  </Section>\n);\n\nconst WhiteLinkButton = styled(LinkButton)`\n  border-color: white;\n  color: white;\n  :hover,\n  :visited {\n    color: white;\n  }\n`;\n\nconst Studio = () => {\n  const [selectedIndex, setSelectedIndex] = useState(0);\n  const onChange = useCallback(index => {\n    setSelectedIndex(index);\n  }, []);\n\n  return (\n    <StudioContainer>\n      <Swipeable\n        onChange={onChange}\n        selectedIndex={selectedIndex}\n        containerStyle={{overflow: 'visible'}}\n      >\n        {SECTIONS.map((cards, index) => (\n          <CardSection key={index} cards={cards} />\n        ))}\n      </Swipeable>\n      <WhiteLinkButton outline large href={LEARN_MORE_LINK} target=\"_blank\">\n        Learn More\n      </WhiteLinkButton>\n    </StudioContainer>\n  );\n};\n\nexport default React.memo(Studio);\n"
  },
  {
    "path": "website/src/components/tutorials.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {media} from '../styles';\nimport {TUTORIALS} from '../content';\nimport {HorizontalCard} from './common/card';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {LinkButton, CenteredContent} from './common/styled-components';\n\nconst Container = styled.div`\n  margin: 0 auto;\n  max-width: 1500px;\n`;\n\nconst CardsContainer = styled.div`\n  display: grid;\n  grid-gap: ${props => props.theme.margins.medium};\n  grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));\n  grid-auto-flow: dense;\n  margin-bottom: ${props => props.theme.margins.large};\n  ${media.palm`\n    display: block;\n  `};\n`;\n\nconst StyledCardContainer = styled.a`\n  display: block;\n  color: black;\n  cursor: pointer;\n  transition: transform 350ms;\n  &:hover {\n    transform: scale3d(1.05, 1.05, 1.05);\n  }\n  ${media.palm`\n    margin-bottom:  ${props => props.theme.margins.small};\n  `};\n`;\n\nconst CardContainer = ({linkUrl, children}) => (\n  <StyledCardContainer href={linkUrl} target=\"_blank\">\n    {children}\n  </StyledCardContainer>\n);\n\nclass Tutorials extends PureComponent {\n  render() {\n    return (\n      <Container>\n        <StaggeredScrollAnimation Container={CardsContainer}>\n          {TUTORIALS.map(({title, description, image, url}, i) => (\n            <CardContainer linkUrl={url} key={`tutorial-${i}`}>\n              <HorizontalCard\n                title={title}\n                description={description}\n                image={image}\n                linkText=\"Read Now\"\n              />\n            </CardContainer>\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton outline large target=\"_blank\" href=\"https://medium.com/vis-gl\">\n            Read More\n          </LinkButton>\n        </CenteredContent>\n      </Container>\n    );\n  }\n}\n\nexport default Tutorials;\n"
  },
  {
    "path": "website/src/components/walkthrough.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {WALKTHROUGH_ITEMS} from '../content';\nimport {media} from '../styles';\nimport Swipeable from './common/swipeable';\n\nconst WalkthroughItem = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  ${media.palm`\n    width: 100%;\n    font-size: 12px;\n  `};\n`;\n\nconst VideoContainer = styled.div`\n  width: 640px;\n  ${media.portable`\n    width: 500px;\n  `} ${media.palm`\n    width: 100%;\n  `};\n\n  video {\n    width: 100%;\n  }\n`;\n\nconst VideoDescription = styled.div`\n  margin-top: 16px;\n`;\n\nconst VideoWrapperHeader = styled.div`\n  height: 15px;\n  background: #e5e5e4;\n  border-radius: 3px 3px 0px 0px;\n  display: flex;\n  align-items: center;\n  padding: 0px 5px;\n`;\n\nconst VideoWrapperHeaderCircle = styled.div`\n  width: 6px;\n  height: 6px;\n  border-radius: 10px;\n  background: ${props => props.color};\n  margin-left: 5px;\n`;\n\nconst VideoWrapper = ({children}) => (\n  <div>\n    <VideoWrapperHeader>\n      <VideoWrapperHeaderCircle color=\"#12BB00\" />\n      <VideoWrapperHeaderCircle color=\"#D3AE00\" />\n      <VideoWrapperHeaderCircle color=\"#DE3131\" />\n    </VideoWrapperHeader>\n    {children}\n  </div>\n);\n\nclass Walkthrough extends PureComponent {\n  constructor(props) {\n    super(props);\n    this.videoElements = {};\n  }\n  state = {\n    selectedIndex: 0\n  };\n\n  _onChange = index => {\n    this.setState({selectedIndex: index});\n    const videoElement = this.videoElements[index];\n    videoElement.load();\n    videoElement.play();\n  };\n\n  _onVideoEnded = () => {\n    const {selectedIndex} = this.state;\n    const newIndex = (selectedIndex + 1) % WALKTHROUGH_ITEMS.length;\n    this._onChange(newIndex);\n  };\n\n  _assignVideoRef = (element, index) => {\n    this.videoElements[index] = element;\n  };\n\n  render() {\n    const {selectedIndex} = this.state;\n    return (\n      <div>\n        <Swipeable onChange={this._onChange} selectedIndex={selectedIndex}>\n          {WALKTHROUGH_ITEMS.map(({videoUrl, imageUrl, description}, i) => (\n            <WalkthroughItem key={i}>\n              <VideoWrapper key={videoUrl}>\n                <VideoContainer>\n                  <video\n                    muted\n                    src={videoUrl}\n                    poster={imageUrl}\n                    autoPlay={i === selectedIndex}\n                    ref={elt => this._assignVideoRef(elt, i)}\n                    onEnded={this._onVideoEnded}\n                  />\n                </VideoContainer>\n              </VideoWrapper>\n              <VideoDescription>{description}</VideoDescription>\n            </WalkthroughItem>\n          ))}\n        </Swipeable>\n      </div>\n    );\n  }\n}\n\nexport default Walkthrough;\n"
  },
  {
    "path": "website/src/constants.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const CLOUDFRONT = 'https://d1a3f4spazzrp4.cloudfront.net';\nexport const KEPLER_GL_BUCKET = 'kepler.gl';\nexport const WEBSITE_ASSET_FOLDER = 'website';\nexport const DEMO_LINK = '/demo';\nexport const DEMO_DUCKDB_LINK = 'https://kepler-preview.foursquare.com/';\nexport const KEPLER_FSQ_BUCKET =\n  'https://studio-public-data.foursquare.com/statics/keplergl/images';\nexport const KEPLER_FSQ_DESKTOP_BUCKET =\n  'https://spatial-desktop.foursquare.com/app-data/keplergl/images';\n"
  },
  {
    "path": "website/src/content.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {cdnUrl, fsqCdnUrl} from './utils';\n\nexport const SECTIONS = [\n  {\n    id: 'showcase',\n    title: 'Map Enthusiast?',\n    description: 'Make beautiful data-driven maps.',\n    icon: cdnUrl('icons/showcase.png')\n  },\n  {\n    id: 'walkthrough',\n    title: 'Data Scientist?',\n    description: 'Gain insights from location data and deliver business outcomes.',\n    icon: cdnUrl('icons/walkthrough.png'),\n    isDark: true\n  },\n  {\n    id: 'features',\n    title: 'Developer?',\n    description: 'A customizable geospatial toolbox to help make data-driven decisions.',\n    icon: cdnUrl('icons/features.png')\n  },\n  /*\n  {\n    id: 'studio',\n    title: 'Take The Next Step',\n    description: `Built on top of kepler.gl’s framework, Foursquare Studio is a free,\npowerful geospatial analytics and visualization tool, with new features and updates released every few weeks.`,\n    icon: fsqCdnUrl('fsqlogo.png'),\n    isDark: true\n  },*/\n  {\n    id: 'desktop',\n    title: 'Foursquare Spatial Desktop',\n    description:\n      'A native desktop app built on kepler.gl, DuckDB and SQLRooms for more power and fewer browser limitations.',\n    icon: fsqCdnUrl('fsqlogo.png'),\n    isDark: true\n  },\n  {\n    id: 'examples',\n    title: 'See What People Created',\n    description: 'See what others have been creating with Kepler.gl.',\n    icon: cdnUrl('icons/examples.png'),\n    background: cdnUrl('examples/section-background.png')\n  },\n  {\n    id: 'ecosystems',\n    title: 'Ecosystem',\n    description: 'A collection of kepler.gl plugins built for common data analytics tools',\n    icon: fsqCdnUrl('ecosystem.png'),\n    isDark: true\n  },\n  {\n    id: 'tutorials',\n    title: 'Getting Started',\n    description: 'Get started with tutorials created by our team.',\n    icon: cdnUrl('icons/tutorials.png')\n  }\n];\n\nexport const HERO_IMAGES = [\n  cdnUrl('hero/kepler.gl-hexagon.png'),\n  cdnUrl('hero/kepler.gl-points.png'),\n  cdnUrl('hero/kepler.gl-contours.png')\n];\n\nexport const HERO_IMAGES_SCALED = [\n  cdnUrl('hero/kepler.gl-hexagon_s.png'),\n  cdnUrl('hero/kepler.gl-points_s.png'),\n  cdnUrl('hero/kepler.gl-contours_s.png')\n];\n\nexport const HEADER_NAVS = [\n  {\n    text: 'User Guide',\n    link: 'https://docs.kepler.gl/docs/user-guides'\n  },\n  {\n    text: 'Docs',\n    link: 'https://docs.kepler.gl/docs/api-reference'\n  },\n  {\n    text: 'Github',\n    link: 'https://github.com/keplergl/kepler.gl'\n  },\n  {\n    text: 'Support Policy',\n    link: '/policy'\n  }\n];\n\nexport const SHOWCASE_ITEMS = [\n  {\n    text: 'Arc',\n    image: cdnUrl('showcase/arcs-s.png'),\n    icon: cdnUrl('showcase/icons/arc.png')\n  },\n  {\n    text: 'Line',\n    image: cdnUrl('showcase/lines-s.png'),\n    icon: cdnUrl('showcase/icons/line.png')\n  },\n  {\n    text: 'Hexagon',\n    image: cdnUrl('showcase/hexagons-s.png'),\n    icon: cdnUrl('showcase/icons/hexagon.png')\n  },\n  {\n    text: 'Point',\n    image: cdnUrl('showcase/points-s.png'),\n    icon: cdnUrl('showcase/icons/point.png')\n  },\n  // {\n  //   text: 'H3',\n  //   image: cdnUrl('showcase/h3.png'),\n  //   icon: cdnUrl('showcase/icons/h3.png')\n  // },\n  {\n    text: 'Heatmap',\n    image: cdnUrl('showcase/heatmap-s.png'),\n    icon: cdnUrl('showcase/icons/heatmap.png')\n  },\n  {\n    text: 'GeoJSON',\n    image: cdnUrl('showcase/geojson-s.png'),\n    icon: cdnUrl('showcase/icons/geojson.png')\n  },\n  {\n    text: 'Buildings',\n    image: cdnUrl('showcase/buildings-s.png'),\n    icon: cdnUrl('showcase/icons/cluster.png')\n  }\n];\n\nexport const WALKTHROUGH_ITEMS = [\n  {\n    videoUrl: cdnUrl('videos/0.upload_file.mp4'),\n    imageUrl: cdnUrl('videos/0.upload_file.png'),\n    description: 'Easily add data to map by drag and drop files'\n  },\n  {\n    videoUrl: cdnUrl('videos/1.time_filter.mp4'),\n    imageUrl: cdnUrl('videos/1.time_filter.png'),\n    description: 'Free form filtering with time playback'\n  },\n  {\n    videoUrl: cdnUrl('videos/2.aggregation.mp4'),\n    imageUrl: cdnUrl('videos/2.aggregation.png'),\n    description: 'Gain deeper insights by performing geo aggregation'\n  },\n  {\n    videoUrl: cdnUrl('videos/3.brushing.mp4'),\n    imageUrl: cdnUrl('videos/3.brushing.png'),\n    description: 'Explore origin-destination correlations with brushing'\n  }\n];\n\nexport const FEATURES = [\n  {\n    title: 'Performance',\n    description:\n      'Built with Deck.gl, Kepler.gl utilizes WebGL to render large datasets quickly and efficiently.',\n    image: cdnUrl('features/performance.svg')\n  },\n  {\n    title: 'Interaction',\n    description:\n      'You can easily drag and drop a dataset, add filters, apply scales, and do aggregation on the fly.',\n    image: cdnUrl('features/interaction.svg')\n  },\n  {\n    title: 'Embeddable',\n    description:\n      'Built on React & Redux, Kepler.gl can be embedded inside your own mapping applications.',\n    image: cdnUrl('features/embeddable.svg')\n  }\n];\n\nexport const ECOSYSTEM = [\n  {\n    title: 'Jupyter',\n    description:\n      'Built with Deck.gl, Kepler.gl utilizes WebGL to render large datasets quickly and efficiently.',\n    image: fsqCdnUrl('ecosystem-jupyter.png'),\n    githubUrl: 'https://github.com/keplergl/kepler.gl/tree/master/bindings/kepler.gl-jupyter'\n  },\n  {\n    title: 'Tableau',\n    description:\n      'You can easily drag and drop a dataset, add filters, apply scales, and do aggregation on the fly.',\n    image: fsqCdnUrl('ecosystem-tableau.png'),\n    githubUrl: 'https://github.com/keplergl/kepler.gl-tableau'\n  },\n  {\n    title: 'Visual Studio Code',\n    description: 'Geo Data Analytics tool for VS Code with Kepler.gl',\n    image: fsqCdnUrl('ecosystem-vscode.png'),\n    githubUrl: 'https://github.com/RandomFractals/geo-data-viewer'\n  }\n];\n\nexport const EXAMPLES = [\n  {\n    title: 'California Earthquakes',\n    description: 'Location, maginitude and magtype of 2.5+ magnitude earthquakes in california',\n    image: cdnUrl('examples/cali-earthquakes.png'),\n    url: '/demo/earthquakes'\n  },\n  {\n    title: 'New York City Cab Rides',\n    description: 'A small sample of yellow and green taxi trip records in New York City',\n    image: cdnUrl('examples/ny-taxis.png'),\n    url: '/demo/nyctrips'\n  },\n  {\n    title: 'San Francisco Elevation Contour',\n    description: 'Elevation contours of San Francisco mainland and Treasure Island/Yerba Island',\n    image: cdnUrl('examples/sf-elevation.png'),\n    url: '/demo/sfcontour'\n  },\n  {\n    title: 'Foursquare Places (PMTiles)',\n    description:\n      \"Foursquare's Open Source Places dataset with 100+ million locations in a single PMTiles file\",\n    image: fsqCdnUrl('pmtiles-fsq-places.png'),\n    url: '/demo/fsq_places'\n  },\n  {\n    title: 'Population (MVT Tiles)',\n    description:\n      'USA Population Mapbox Vector Tiles with total population per geometry and age statistics',\n    image: fsqCdnUrl('population-mvt.png'),\n    url: '/demo/mvt_population'\n  },\n  {\n    title: 'Travel Times from Uber Movement',\n    description: 'Pittsburgh travel times before and during heavy inclement weather conditions',\n    image: cdnUrl('examples/movement-pittsburgh.png'),\n    url: '/demo/movement_pittsburgh'\n  },\n  {\n    title: 'New york city population',\n    description: 'This dataset contains the 2010 Census tract population data of NYC',\n    image: cdnUrl('examples/ny-population.png'),\n    url: '/demo/nyc_census'\n  },\n  {\n    title: 'San Francisco Street Tree Map',\n    description: 'A 3d hexbin density map showing every single streets in San Francisco',\n    image: cdnUrl('examples/sf-street-trees.png'),\n    url: '/demo/sftrees'\n  },\n  {\n    title: 'Commute Patterns in the UK',\n    description:\n      'A origin destination map using 3d arcs to show commute patterns of England and Wales residence',\n    image: cdnUrl('examples/uk-commute.png'),\n    url: '/demo/ukcommute'\n  }\n];\n\nexport const TUTORIALS = [\n  {\n    title: 'How to create a map in 3 minutes',\n    description: 'Animating 40 years of California Earthquakes.',\n    image: cdnUrl('examples/earthquake.png'),\n    url: 'https://medium.com/vis-gl/animating-40-years-of-california-earthquakes-e4ffcdd4a289'\n  },\n  {\n    title: 'Adding altitude to points',\n    description: 'Mapping the Parisian trees.',\n    image: cdnUrl('examples/parisian.png'),\n    url: 'https://medium.com/vis-gl/mapping-the-parisian-trees-6dc30f6aabc7'\n  },\n  {\n    description: 'Visualizing U.S. County Unemployment with kepler.gl',\n    title: 'Making a choropleth map',\n    image: cdnUrl('examples/unemployment.png'),\n    url: 'https://medium.com/vis-gl/visualizing-u-s-county-unemployment-with-kepler-gl-c5f2ed31c71'\n  },\n  {\n    title: 'Uber Movement and kepler.gl',\n    description: 'Using kepler.gl and Movement data to Visualize Traffic Effects of a Rainstorm',\n    image: cdnUrl('examples/movement.png'),\n    url: 'https://medium.com/@uber_movement/movement-in-kepler-d00e843f464d'\n  }\n];\n"
  },
  {
    "path": "website/src/main.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// import 'babel-polyfill';\n// import 'babel-register';\n\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport {Provider} from 'react-redux';\nimport store from './reducers';\nimport Routes from './routes';\nimport document from 'global/document';\n\nrequire('./static/favicon.png');\n\nconst el = document.createElement('div');\ndocument.body.appendChild(el);\n\nconst root = ReactDOM.createRoot(el);\nroot.render(\n  <Provider store={store}>\n    <Routes />\n  </Provider>\n);\n"
  },
  {
    "path": "website/src/reducers/analytics.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// This file sends actions on the demo app to Google analytics\n\nimport {ActionTypes} from '@kepler.gl/actions';\nimport {LOCATION_CHANGE} from 'react-router-redux';\nimport Window from 'global/window';\nimport {ALL_FIELD_TYPES} from '@kepler.gl/constants';\nimport get from 'lodash/get';\n\nconst getPayload = action => (action ? action.payload : null);\n\n// Hack, because we don't have a way to access next state\nconst getFilterType = (store, idx, value) => {\n  const {visState} = store.getState().demo.keplerGl.map;\n  const filter = visState.filters[idx];\n  const {dataId} = filter;\n  if (!dataId) {\n    return {};\n  }\n  const {fields} = visState.datasets[dataId];\n  const field = fields.find(f => f.name === value);\n\n  // Hack\n  switch (field.type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n      return 'range';\n\n    case ALL_FIELD_TYPES.boolean:\n      return 'select';\n\n    case ALL_FIELD_TYPES.string:\n    case ALL_FIELD_TYPES.date:\n      return 'multiSelect';\n\n    case ALL_FIELD_TYPES.timestamp:\n      return 'timeRange';\n\n    default:\n      return null;\n  }\n};\n\nconst trackingInformation = {\n  [ActionTypes.LOAD_FILES]: payload => payload.files.map(({size, type}) => ({size, type})),\n  [ActionTypes.LAYER_TYPE_CHANGE]: ({newType}) => ({\n    newType\n  }),\n  [ActionTypes.MAP_STYLE_CHANGE]: getPayload,\n  [ActionTypes.TOGGLE_MODAL]: getPayload,\n  [ActionTypes.ADD_LAYER]: (payload, store) => ({\n    total: store.getState().demo.keplerGl.map.visState.layers.length + 1\n  }),\n  [ActionTypes.ADD_FILTER]: (payload, store) => ({\n    total: store.getState().demo.keplerGl.map.visState.filters.length + 1\n  }),\n  [ActionTypes.SET_FILTER]: ({prop, idx, value}, store) => {\n    if (prop !== 'name') {\n      return {};\n    }\n    return {\n      filterType: getFilterType(store, idx, value)\n    };\n  },\n  [ActionTypes.INTERACTION_CONFIG_CHANGE]: ({config: {id, enabled}}) => ({\n    [id]: enabled\n  }),\n  [LOCATION_CHANGE]: x => x,\n\n  // demo app actions\n  ['PUSHING_FILE']: payload => {\n    const size = get(payload, ['metadata', 'metadata', 'size']);\n    return {\n      isLoading: payload.isLoading,\n      status: payload.metadata.status,\n      filename: payload.metadata.filename,\n      size,\n      error: payload.error\n    };\n  }\n};\n\nconst EXCLUDED_ACTIONS = [ActionTypes.LAYER_HOVER, ActionTypes.UPDATE_MAP];\n\nconst analyticsMiddleware = store => next => action => {\n  if (Window.gtag && !EXCLUDED_ACTIONS.includes(action.type)) {\n    const payload = action.payload || action;\n\n    // eslint-disable-next-line no-undef\n    Window.gtag('event', 'action', {\n      event_category: action.type,\n      event_label: trackingInformation[action.type]\n        ? JSON.stringify(trackingInformation[action.type](payload, store))\n        : null\n    });\n  }\n\n  return next(action);\n};\n\nexport default analyticsMiddleware;\n"
  },
  {
    "path": "website/src/reducers/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\n\nconst DEFAULT_APP_STATE = {};\n\nexport default handleActions(\n  {\n    INIT: state => ({...state, ready: true})\n  },\n  DEFAULT_APP_STATE\n);\n"
  },
  {
    "path": "website/src/reducers/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {combineReducers, createStore, applyMiddleware, compose} from 'redux';\nimport {routerReducer, routerMiddleware} from 'react-router-redux';\nimport {taskMiddleware} from 'react-palm/tasks';\nimport thunk from 'redux-thunk';\n\nimport {browserHistory} from 'react-router';\nimport appReducer from './app';\nimport demoReducer from '../../../examples/demo-app/src/reducers';\nimport analyticsMiddleware from './analytics';\n\nconst initialState = {};\nconst reducers = {\n  demo: demoReducer,\n  app: appReducer,\n  routing: routerReducer\n};\n\nconst combinedReducers = combineReducers(reducers);\n\nexport const middlewares = [\n  taskMiddleware,\n  thunk,\n  routerMiddleware(browserHistory),\n  analyticsMiddleware\n];\n\nexport const enhancers = [applyMiddleware(...middlewares)];\n\nconst composeEnhancers = compose;\n// add redux devtools\n// const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\nexport default createStore(combinedReducers, initialState, composeEnhancers(...enhancers));\n"
  },
  {
    "path": "website/src/routes.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport {Router, Route, IndexRoute, browserHistory} from 'react-router';\nimport {syncHistoryWithStore} from 'react-router-redux';\nimport Window from 'global/window';\nimport store from './reducers';\nimport Home from './components/home';\nimport App from './components/app';\nimport Demo from '../../examples/demo-app/src/app';\nimport Policy from './components/policy';\n\nimport {buildAppRoutes} from '../../examples/demo-app/src/utils/routes';\n\nconst appRoute = buildAppRoutes(Demo);\n\nconst trackPageChange = location => {\n  const links = location.split('/');\n\n  if (links.length === 3) {\n    const sampleId = links[2];\n    Window.gtag('event', 'load_sample', {\n      event_label: sampleId,\n      value: sampleId\n    });\n  }\n};\n\nconst history = syncHistoryWithStore(browserHistory, store);\nhistory.listen(location => {\n  if (location.action === 'POP') {\n    trackPageChange(location.pathname);\n  }\n});\n\nfunction isOldUrl(location) {\n  return Boolean(location.pathname === '/' && location.hash && location.hash.startsWith('#/demo'));\n}\n\nfunction onEnter(nextState, replace, callback) {\n  /**\n   * For backward compatibility, when we see a url path starting with '#/demo/...'\n   * we redirect to '/demo/.../\n   **/\n  if (isOldUrl(nextState.location)) {\n    replace(location.hash.substring(1));\n  }\n  callback();\n}\n\n// eslint-disable-next-line react/display-name\nexport default () => (\n  <Router history={history}>\n    <Route path=\"/\" component={App} onEnter={onEnter}>\n      <IndexRoute component={Home} onEnter={onEnter} />\n      <Route path=\"/policy\" component={Policy} onEnter={onEnter} />\n      {appRoute}\n    </Route>\n  </Router>\n);\n"
  },
  {
    "path": "website/src/static/_redirects",
    "content": "/* / 200\n"
  },
  {
    "path": "website/src/static/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset='UTF-8' />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta name=\"description\" content=\"Kepler.gl is a data agnostic, WebGL empowered, high-performance web application for geospatial analytic visualizations.\">\n    <title>kepler.gl</title>\n    <link rel=\"shortcut icon\" href=\"/favicon.png\">\n    <link rel=\"stylesheet\" href=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css\">\n\n    <link href=\"https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\" rel=\"stylesheet\">\n    <link href=\"https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css\" rel=\"stylesheet\">\n\n    <!-— facebook open graph tags -->\n    <meta property=\"og:url\" content=\"http://kepler.gl/\" />\n    <meta property=\"og:title\" content=\"Large-scale WebGL-powered Geospatial Data Visualization Tool\" />\n    <meta property=\"og:description\" content=\"Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets.\" />\n    <meta property=\"og:site_name\" content=\"kepler.gl\" />\n    <meta property=\"og:image\" content=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png\" />\n    <meta property=\"og:image:type\" content=\"image/png\" />\n    <meta property=\"og:image:width\" content=\"800\" />\n    <meta property=\"og:image:height\" content=\"800\" />\n\n    <!-— twitter card tags -->\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n    <meta name=\"twitter:site\" content=\"@openjsf\">\n    <meta name=\"twitter:creator\" content=\"@openjsf\">\n    <meta name=\"twitter:title\" content=\"Large-scale WebGL-powered Geospatial Data Visualization Tool\">\n    <meta name=\"twitter:description\" content=\"Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets.\">\n    <meta name=\"twitter:image\" content=\"https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png\" />\n\n    <!-- Google Analytics -->\n    <!-- Global site tag (gtag.js) - Google Analytics -->\n    <script async src=\"https://www.googletagmanager.com/gtag/js?id=UA-64694404-19\"></script>\n    <script>\n      window.dataLayer = window.dataLayer || [];\n      function gtag(){dataLayer.push(arguments);}\n      gtag('js', new Date());\n      gtag('config', 'UA-64694404-19', {\n        page_path: location.pathname + location.search + location.hash\n      });\n    </script>\n\n    <link rel=\"stylesheet\" href=\"/bundle.css\">\n    <style>\n      body {margin: 0; padding: 0; overflow-x: hidden;}\n    </style>\n  </head>\n  <body>\n    <script src=\"/bundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "website/src/styles.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {css} from 'styled-components';\n\nexport const breakPoints = {\n  palm: 588,\n  desk: 768\n};\n\nexport const media = {\n  palm: (...args) => css`\n    @media (max-width: ${breakPoints.palm}px) {\n      ${css(...args)};\n    }\n  `,\n\n  portable: (...args) => css`\n    @media (max-width: ${breakPoints.desk}px) {\n      ${css(...args)};\n    }\n  `,\n\n  desk: (...args) => css`\n    @media (min-width: ${breakPoints.desk + 1}px) {\n      ${css(...args)};\n    }\n  `\n};\n\nexport const errorColor = '#F9042C';\n\nexport const theme = {\n  textColor: '#FFFFFF',\n  labelColor: '#D0D0D0',\n  mapBackground: '#0D1823',\n  mapBlocker: '#0B151F',\n  navLinkColor: '#8d9ba3',\n  footerColor: '#C0C0C0',\n  // button:\n  primaryBtnBgd: '#005CD2',\n  primaryBtnActBgd: '#13B17B',\n  primaryBtnColor: '#FFFFFF',\n  primaryBtnActColor: '#FFFFFF',\n  primaryBtnBgdHover: '#1f7cf4',\n  primaryBtnRadius: '2px',\n  secondaryBtnBgd: '#6A7485',\n  secondaryBtnActBgd: '#A0A7B4',\n  secondaryBtnColor: '#FFFFFF',\n  secondaryBtnActColor: '#FFFFFF',\n  secondaryBtnBgdHover: '#A0A7B4',\n  outlineBtnBgd: 'transparent',\n  outlineBtnBgdHover: 'rgba(0, 0, 0, 0.05)',\n  outlineBtnActColor: '#000000',\n  outlineBtnColor: '#000000',\n  outlineDarkBtnBgd: 'transparent',\n  outlineDarkBtnBgdHover: 'rgba(255, 255, 255, 0.05)',\n  outlineDarkBtnActColor: '#ffffff',\n  outlineDarkBtnColor: '#ffffff',\n  linkBtnBgd: 'transparent',\n  linkBtnActBgd: 'transparent',\n  linkBtnColor: '#A0A7B4',\n  linkBtnActColor: '#3A414C',\n  linkBtnActBgdHover: 'transparent',\n  negativeBtnBgd: errorColor,\n  negativeBtnActBgd: '#FF193E',\n  negativeBtnBgdHover: '#FF193E',\n  negativeBtnColor: '#FFFFFF',\n  negativeBtnActColor: '#FFFFFF',\n  btnTransition: '350ms color, 350ms background',\n  linkColor: '#A0A7B4',\n  darkBackgroundColor: '#242730',\n  margins: {\n    tiny: '0.5rem',\n    small: '1.0rem',\n    medium: '1.5rem',\n    large: '2.0rem',\n    huge: '2.5rem'\n  }\n};\n"
  },
  {
    "path": "website/src/utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {\n  CLOUDFRONT,\n  KEPLER_GL_BUCKET,\n  WEBSITE_ASSET_FOLDER,\n  KEPLER_FSQ_BUCKET,\n  KEPLER_FSQ_DESKTOP_BUCKET\n} from './constants';\n\nexport function cdnUrl(path) {\n  return `${CLOUDFRONT}/${KEPLER_GL_BUCKET}/${WEBSITE_ASSET_FOLDER}/${path}`;\n}\nexport function fsqCdnUrl(path) {\n  return `${KEPLER_FSQ_BUCKET}/${path}`;\n}\n\nexport function fsqStudioUrl(path) {\n  return fsqCdnUrl(`studio/${path}`);\n}\n\nexport function fsqCdnDesktopUrl(path) {\n  return `${KEPLER_FSQ_DESKTOP_BUCKET}/${path}`;\n}\n"
  },
  {
    "path": "website/webpack.config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst {resolve} = require('path');\nconst webpack = require('webpack');\n\nconst KeplerPackage = require('../package');\nconst {\n  WEBPACK_ENV_VARIABLES,\n  ENV_VARIABLES_WITH_INSTRUCTIONS,\n  RESOLVE_ALIASES\n} = require('../webpack/shared-webpack-configuration');\n\nconst LIB_DIR = resolve(__dirname, '..');\nconst SRC_DIR = resolve(LIB_DIR, './src');\n\nconst console = require('global/console');\n\nconst BABEL_CONFIG = {\n  presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],\n  plugins: [\n    ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}],\n    '@babel/plugin-transform-class-properties',\n    '@babel/plugin-transform-optional-chaining',\n    '@babel/plugin-transform-logical-assignment-operators',\n    '@babel/plugin-transform-nullish-coalescing-operator',\n    [\n      'search-and-replace',\n      {\n        rules: [\n          {\n            search: '__PACKAGE_VERSION__',\n            replace: KeplerPackage.version\n          }\n        ]\n      }\n    ]\n  ]\n};\n\nconst COMMON_CONFIG = {\n  entry: ['./src/main'],\n  output: {\n    path: resolve(__dirname, 'build'),\n    filename: 'bundle.js',\n    publicPath: '/'\n  },\n\n  resolve: {\n    extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],\n    modules: ['node_modules', SRC_DIR],\n    alias: {\n      ...RESOLVE_ALIASES\n    }\n  },\n\n  module: {\n    rules: [\n      {\n        // Compile ES2015 using babel\n        test: /\\.(js|jsx|ts|tsx|mjs)$/,\n        loader: 'babel-loader',\n        options: BABEL_CONFIG,\n        include: [\n          resolve(__dirname, './src'),\n          resolve(LIB_DIR, './examples'),\n          resolve(LIB_DIR, './src'),\n          /node_modules\\/@monaco-editor/,\n          /node_modules\\/@radix-ui/\n        ],\n        exclude: [/node_modules\\/(?!(@monaco-editor|@radix-ui))/]\n      },\n      // Add css loader for ai-assistant\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader']\n      },\n      {\n        test: /\\.(eot|svg|ico|ttf|woff|woff2|gif|jpe?g|png)$/,\n        type: 'asset/resource'\n      },\n      {\n        test: /\\.(svg|ico|gif|jpe?g|png)$/,\n        type: 'asset/inline'\n      },\n      // for compiling apache-arrow ESM module\n      {\n        test: /\\.mjs$/,\n        include: /node_modules[\\\\/]apache-arrow/,\n        type: 'javascript/auto'\n      },\n      {\n        test: /\\.js$/,\n        loader: require.resolve('@open-wc/webpack-import-meta-loader'),\n        include: [/node_modules\\/parquet-wasm/]\n      },\n      // for compiling @probe.gl, website build started to fail (March, 2024)\n      // netlify builder complains loader not found for these modules (April, 2024)\n      {\n        test: /\\.(js)$/,\n        loader: 'babel-loader',\n        options: BABEL_CONFIG,\n        include: [\n          /node_modules[\\\\/]@probe.gl/,\n          /node_modules[\\\\/]@loaders.gl/,\n          /node_modules[\\\\/]@math.gl/,\n          /node_modules[\\\\/]@geoarrow/\n        ]\n      },\n      {\n        test: /\\.m?js$/,\n        include: [\n          /node_modules\\/@duckdb\\/duckdb-wasm/,\n          /node_modules\\/@radix-ui/,\n          /node_modules\\/@monaco-editor\\/react/\n        ],\n        type: 'javascript/auto'\n      }\n    ]\n  },\n\n  devServer: {\n    historyApiFallback: true\n    // Add new options if needed\n  },\n\n  // Optional: Enables reading mapbox token from environment variable\n  plugins: [\n    // Provide default values to suppress warnings\n    new webpack.EnvironmentPlugin(WEBPACK_ENV_VARIABLES),\n    new webpack.DefinePlugin({\n      'process.env.NODE_ENV': JSON.stringify('production')\n    })\n  ],\n\n  // Update optimization settings\n  optimization: {\n    // Webpack 5 has better defaults for tree-shaking and module concatenation\n  }\n};\n\nconst addDevConfig = config => {\n  config.module.rules.push({\n    // Unfortunately, webpack doesn't import library sourcemaps on its own...\n    test: /\\.js$/,\n    use: ['source-map-loader'],\n    enforce: 'pre',\n    exclude: [/node_modules[\\\\/]react-palm/, /node_modules[\\\\/]react-data-grid/]\n  });\n\n  return Object.assign(config, {\n    devtool: 'source-maps',\n\n    plugins: config.plugins.concat([\n      // new webpack.HotModuleReplacementPlugin(),\n      new webpack.NoEmitOnErrorsPlugin()\n    ])\n  });\n};\n\nconst addProdConfig = config => {\n  return Object.assign(config, {\n    output: {\n      path: resolve(__dirname, './dist'),\n      filename: 'bundle.js',\n      publicPath: '/'\n    }\n  });\n};\n\nfunction logError(msg) {\n  console.log('\\x1b[31m%s\\x1b[0m', msg);\n}\n\nfunction logInstruction(msg) {\n  console.log('\\x1b[36m%s\\x1b[0m', msg);\n}\n\nfunction validateEnvVariable(variable, instruction) {\n  if (!process.env[variable]) {\n    logError(`Error! ${variable} is not defined`);\n    logInstruction(`Make sure to run \"export ${variable}=<token>\" before deploy the website`);\n    logInstruction(instruction);\n    throw new Error(`Missing ${variable}`);\n  }\n}\n\nmodule.exports = env => {\n  env = env || {};\n\n  let config = COMMON_CONFIG;\n\n  if (env.local) {\n    config = addDevConfig(config);\n  }\n\n  if (env.prod) {\n    Object.entries(ENV_VARIABLES_WITH_INSTRUCTIONS).forEach(entry => {\n      // we validate each entry [name, instruction]\n      validateEnvVariable(...entry);\n    });\n    config = addProdConfig(config);\n  }\n\n  return config;\n};\n"
  },
  {
    "path": "website-gatsby/.eslintignore",
    "content": "../modules\n\n# TODO remove when we have finished porting the old website\nsrc/components\nsrc/reducers\n"
  },
  {
    "path": "website-gatsby/.gitignore",
    "content": "public\n.cache\nreport*.json\nexamples\n"
  },
  {
    "path": "website-gatsby/gatsby-browser.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as wrapRootElement} from './src/state/redux-wrapper';\n"
  },
  {
    "path": "website-gatsby/gatsby-config.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst resolve = require('path').resolve;\n\nconst DOCS = require('../docs/table-of-contents.json');\nconst DEPENDENCIES = require('./package.json').dependencies;\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst ALIASES = require('ocular-dev-tools/config/ocular.config')({\n  root: resolve(__dirname, '..')\n}).aliases;\n\n// When duplicating example dependencies in website, autogenerate\n// aliases to ensure the website version is picked up\n// NOTE: module dependencies are automatically injected\n// TODO - should this be automatically done by ocular-gatsby?\nconst dependencyAliases = {};\nfor (const dependency in DEPENDENCIES) {\n  dependencyAliases[dependency] = `${__dirname}/node_modules/${dependency}`;\n}\n\nconst GATSBY_CONFIG = {\n  plugins: [\n    {\n      resolve: `gatsby-theme-ocular`,\n      options: {\n        logLevel: 2, // Adjusts amount of debug information from ocular-gatsby\n\n        // NOTE - we currently need custom excludes in gatsby-config.js to support multiple directories\n        DOC_FOLDERS: [`${__dirname}/../docs/`],\n        ROOT_FOLDER: `${__dirname}/../`,\n        DIR_NAME: `${__dirname}`,\n\n        DOCS,\n\n        PROJECT_TYPE: 'github',\n\n        PROJECT_NAME: 'kepler.gl',\n        PROJECT_ORG: 'keplergl',\n        PROJECT_URL: 'https://github.com/kepler/kepler.gl',\n        PROJECT_DESC: 'Open source exploratory geospatial data analysis',\n        PATH_PREFIX: '/',\n\n        FOOTER_LOGO: '',\n\n        GA_TRACKING: null,\n\n        // For showing star counts and contributors.\n        // Should be like btoa('YourUsername:YourKey') and should be readonly.\n        GITHUB_KEY: null,\n\n        HOME_PATH: '/',\n\n        HOME_HEADING: 'A powerful open source geospatial analysis tool for large-scale data sets',\n\n        HOME_RIGHT: null,\n\n        HOME_BULLETS: [\n          {\n            text: 'Performance',\n            desc: 'Built with deck.gl, kepler.gl utilizes WebGL to render large datasets quickly and efficiently.',\n            img: 'images/icon-high-precision.svg'\n            // TODO image: cdnUrl('features/performance.svg')\n          },\n          {\n            text: 'Interaction',\n            desc:\n              'You can easily drag and drop a dataset, add filters, apply scales, and do aggregation on the fly.',\n            // TODO image: cdnUrl('features/interaction.svg')\n            img: 'images/icon-high-precision.svg'\n          },\n          {\n            text: 'Embeddable',\n            desc:\n              'Built on React & Redux, Kepler.gl can be embedded inside your own mapping applications.',\n            // TODO image: cdnUrl('features/embeddable.svg')\n            img: 'images/icon-high-precision.svg'\n          }\n        ],\n\n        PROJECTS: [\n          {\n            name: 'deck.gl',\n            url: 'https://deck.gl'\n          },\n          {\n            name: 'luma.gl',\n            url: 'https://luma.gl'\n          },\n          {\n            name: 'react-map-gl',\n            url: 'https://visgl.github.io/react-map-gl'\n          },\n          {\n            name: 'nebula.gl',\n            url: 'https://nebula.gl/'\n          }\n        ],\n\n        LINK_TO_GET_STARTED: 'docs/developer-guide/get-started',\n\n        ADDITIONAL_LINKS: [],\n\n        INDEX_PAGE_URL: resolve(__dirname, './templates/index.jsx'),\n\n        EXAMPLES: [\n          // {\n          //   title: '3D Tiles',\n          //   image: 'images/example-3d-tiles.png',\n          //   componentUrl: resolve(__dirname, '../examples/deck.gl/3d-tiles/app.js'),\n          //   path: 'examples/3d-tiles'\n          // },\n        ],\n\n        // Ocular adds this to gatsby's webpack config\n        webpack: {\n          resolve: {\n            alias: Object.assign({}, ALIASES, dependencyAliases, {})\n          }\n        }\n      }\n    },\n    // {resolve: 'gatsby-plugin-no-sourcemaps'}\n  ]\n};\n\nmodule.exports = GATSBY_CONFIG;\n"
  },
  {
    "path": "website-gatsby/gatsby-node.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nconst {resolve} = require('path');\n\n// Looks like onCreateWebpackConfig does not receive theme options?\nfunction onCreateWebpackConfig(opts, ocularOptions = global.ocularOptions) {\n  const {\n    stage,     // build stage: ‘develop’, ‘develop-html’, ‘build-javascript’, or ‘build-html’\n    // rules,     // Object (map): set of preconfigured webpack config rules\n    // loaders,   // Object (map): set of preconfigured webpack config loaders\n    // plugins,    // Object (map): A set of preconfigured webpack config plugins\n    actions,\n    getConfig // Function that returns the current webpack config\n  } = opts;\n\n  const config = getConfig();\n\n  config.resolve = config.resolve || {};\n  config.resolve.alias = config.resolve.alias || {};\n  Object.assign(config.resolve.alias, {\n    'components': resolve(__dirname, '../src/components'),\n    'constants': resolve(__dirname, '../src/constants'),\n    'actions': resolve(__dirname, '../src/actions'),\n    'utils': resolve(__dirname, '../src/utils')\n    // 'kepler.gl/dist': libSources,\n    // // Imports the kepler.gl library from the src directory in this repo\n    // 'kepler.gl': libSources,\n    // react: resolve(rootDir, './node_modules/react'),\n    // 'styled-components': resolve(rootDir, './node_modules/styled-components'),\n    // 'react-redux': resolve(rootDir, './node_modules/react-redux'),\n    // 'react-palm': resolve(rootDir, './node_modules/react-palm')\n  });\n\n\n  // NOTE: actions.setWebpackConfig MERGES in the new config, actions.replaceWebpackConfig SETS it\n  actions.replaceWebpackConfig(config);\n}\n\nmodule.exports.onCreateWebpackConfig = onCreateWebpackConfig;"
  },
  {
    "path": "website-gatsby/gatsby-ssr.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport {default as wrapRootElement} from './src/state/redux-wrapper';\n"
  },
  {
    "path": "website-gatsby/package.json",
    "content": "{\n  \"name\": \"ocular-website-gatsby\",\n  \"version\": \"0.0.0\",\n  \"description\": \"A website for Ocular, built with Ocular (Gatsby version)\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"ocular\"\n  ],\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"yarn clean && yarn develop\",\n    \"build\": \"yarn clean-examples && yarn clean && gatsby build\",\n    \"clean\": \"rm -rf ./.cache ./public\",\n    \"clean-examples\": \"find ../examples -name node_modules -exec rm -r {} \\\\; || true\",\n    \"develop\": \"yarn clean-examples && gatsby develop\",\n    \"serve\": \"gatsby serve\",\n    \"deploy\": \"NODE_DEBUG=gh-pages gh-pages -d public\"\n  },\n  \"dependencies\": {\n    \"babel-plugin-version-inline\": \"^1.0.0\",\n    \"marked\": \"^0.7.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-map-gl\": \"^5.0.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-swipeable-views\": \"^0.13.3\",\n    \"react-waypoint\": \"^9.0.2\",\n    \"redux\": \"^4.2.1\",\n    \"styled-components\": \"6.1.8\"\n  },\n  \"devDependencies\": {\n    \"gatsby\": \"^2.18.0\",\n    \"gatsby-plugin-no-sourcemaps\": \"^2.0.2\",\n    \"gatsby-theme-ocular\": \"^1.1.0-alpha.5\",\n    \"gh-pages\": \"^2.1.0\"\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\nimport {connect} from 'react-redux';\n\nconst GlobalStyleDiv = styled.div`\n  font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;\n  font-weight: 400;\n  font-size: 0.875em;\n  line-height: 1.71429;\n\n  *,\n  *:before,\n  *:after {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n\n  ul {\n    margin: 0;\n    padding: 0;\n  }\n\n  li {\n    margin: 0;\n  }\n\n  a {\n    text-decoration: none;\n  }\n`;\n\nclass App extends Component {\n  render() {\n    return (\n      <GlobalStyleDiv className=\"kg-web-content\">\n        {this.props.children}\n      </GlobalStyleDiv>\n    );\n  }\n}\n\nexport default connect(state => state)(App);\n"
  },
  {
    "path": "website-gatsby/src/components/common/card.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport styled, {css} from 'styled-components';\nimport {media} from '../../styles';\n\nconst containerStyles = css`\n  background: white;\n  box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.1);\n  border-radius: 4px;\n  overflow: hidden;\n`;\n\nconst VerticalContainer = styled.div`\n  ${containerStyles} width: 350px;\n  height: 400px;\n\n  ${media.palm`\n    width: 100%;\n    height: auto;\n  `};\n`;\n\nconst HorizontalContainer = styled.div`\n  ${containerStyles} height: 150px;\n  display: flex;\n  ${media.palm`\n    height: auto;\n    display: block;\n  `};\n`;\n\nconst Content = styled.div`\n  padding: ${props => props.theme.margins.medium};\n\n  ${media.palm`\n    padding: ${props => props.theme.margins.small};\n  `};\n`;\n\nconst VerticalCardImage = styled.img`\n  display: block;\n  width: 100%;\n  height: 200px;\n  object-fit: cover;\n`;\n\nconst HorizontalCardImage = styled.img`\n  height: 150px;\n  width: 150px;\n  object-fit: cover;\n  ${media.palm`\n    width: 100%;\n    height: 100px;\n  `};\n`;\n\nconst Title = styled.div`\n  font-size: 16px;\n  font-weight: 500;\n  margin-bottom: 8px;\n`;\n\nconst Description = styled.div`\n  font-size: 14px;\n  color: #777;\n  margin-bottom: 8px;\n  height: ${props => (props.size === 'small' ? '40px' : '80px')};\n  line-height: 1.5;\n`;\n\nconst Link = styled.div`\n  text-transform: uppercase;\n  font-weight: 700;\n  font-size: 12px;\n  color: black;\n`;\n\nexport const VerticalCard = ({\n  title,\n  description,\n  image,\n  linkText,\n  linkUrl\n}) => (\n  <VerticalContainer>\n    <VerticalCardImage src={image} />\n    <Content>\n      <Title>{title}</Title>\n      <Description>{description}</Description>\n      <Link>{linkText}</Link>\n    </Content>\n  </VerticalContainer>\n);\n\nexport const HorizontalCard = ({\n  title,\n  description,\n  image,\n  linkText,\n  linkUrl\n}) => (\n  <HorizontalContainer>\n    <HorizontalCardImage src={image} />\n    <Content>\n      <Title>{title}</Title>\n      <Description size=\"small\">{description}</Description>\n      <Link>{linkText}</Link>\n    </Content>\n  </HorizontalContainer>\n);\n"
  },
  {
    "path": "website-gatsby/src/components/common/carousel.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {Waypoint} from 'react-waypoint';\n\nconst Container = styled.div`\n  position: relative;\n  display: flex;\n  justify-content: center;\n  min-height: 200px;\n`;\n\nconst Content = styled.div`\n  display: flex;\n  justify-content: center;\n  transform-style: preserve-3d;\n  perspective-origin: center;\n`;\n\nconst Item = styled.div`\n  position: absolute;\n  transition: transform 1s;\n  cursor: pointer;\n  transform: perspective(600px)\n    translate3d(${props => props.tX}%, 0, ${props => props.tZ}px);\n`;\n\nexport default class Carousel extends PureComponent {\n  static propTypes = {\n    children: PropTypes.node,\n    selectedIndex: PropTypes.number.isRequired,\n    xOffset: PropTypes.number,\n    zOffset: PropTypes.number\n  };\n\n  static defaultProps = {\n    xOffset: 15,\n    zOffset: 60\n  };\n\n  state = {\n    isVisible: false\n  };\n\n  _onWaypointEnter = () => {\n    this.setState({isVisible: true});\n  };\n\n  _onWaypointLeave = () => {\n    this.setState({isVisible: false});\n  };\n\n  render() {\n    const {selectedIndex, children, xOffset, zOffset} = this.props;\n    const {isVisible} = this.state;\n    return (\n      <Waypoint onEnter={this._onWaypointEnter} onLeave={this._onWaypointLeave}>\n        <Container>\n          <Content>\n            {children.map((item, i) => {\n              const translateX = isVisible ? (i - selectedIndex) * xOffset : 0;\n              const translateZ = -Math.abs(i - selectedIndex) * zOffset;\n              return (\n                <Item\n                  key={`carousel-item-${i}`}\n                  tX={translateX}\n                  tZ={translateZ}\n                  onClick={() => {\n                    this.props.onChange(i);\n                  }}\n                >\n                  {item}\n                </Item>\n              );\n            })}\n          </Content>\n        </Container>\n      </Waypoint>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/common/section.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport StaggeredScrollAnimation from './staggered-scroll-animation';\n\nimport {media} from '../../styles';\n\nexport const SectionIcon = styled.img`\n  width: 64px;\n  ${media.palm`\n    width: 48px;\n  `};\n`;\n\nexport const SectionContainer = styled.div`\n  color: ${props => (props.isDark ? 'white' : 'black')};\n  background: ${props =>\n    props.isDark\n      ? props.theme.darkBackgroundColor\n      : props.background\n        ? `url(${props.background})`\n        : 'white'};\n  padding: ${props => props.theme.margins.huge};\n  margin-bottom: ${props => props.theme.margins.large};\n  background-size: cover;\n\n  ${media.portable`\n    padding: ${props => props.theme.margins.large}\n  `} ${media.palm`\n    padding: ${props => props.theme.margins.medium}\n  `};\n`;\n\nexport const SectionHeader = styled.div`\n  text-align: center;\n  margin-bottom: ${props => props.theme.margins.large};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n`;\n\nexport const SectionTitle = styled.div`\n  font-size: 30px;\n  font-weight: 500;\n  ${media.palm`\n    font-size: 24px;\n  `};\n`;\n\nexport const SectionDescription = styled.div`\n  font-size: 20px;\n  max-width: 500px;\n  ${media.palm`\n    font-size: 16px;\n  `};\n`;\n\nclass Section extends PureComponent {\n  static propTypes = {\n    title: PropTypes.string.isRequired,\n    description: PropTypes.string.isRequired,\n    icon: PropTypes.string.isRequired,\n    isDark: PropTypes.bool,\n    background: PropTypes.string,\n    children: PropTypes.node.isRequired\n  };\n\n  static defaultProps = {\n    isDark: false\n  };\n\n  render() {\n    const {icon, title, description, isDark, background, children} = this.props;\n    return (\n      <SectionContainer isDark={isDark} background={background}>\n        <StaggeredScrollAnimation Container={SectionHeader}>\n          <SectionIcon src={icon} />\n          <SectionTitle>{title}</SectionTitle>\n          <SectionDescription>{description}</SectionDescription>\n        </StaggeredScrollAnimation>\n        {children}\n      </SectionContainer>\n    );\n  }\n}\n\nexport default Section;\n"
  },
  {
    "path": "website-gatsby/src/components/common/slideshow.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {setInterval, clearInterval} from 'global/window';\nimport {media} from '../../styles';\n\nconst imageRatio = 696 / 1080;\n\nconst StyledImgContainer = styled.div`\n  box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.5);\n  flex-shrink: 0;\n  opacity: 1;\n  position: relative;\n\n  width: calc(100vw - 60px);\n  height: ${imageRatio * 100}vw;\n\n  max-width: 800px;\n  max-height: ${800 * imageRatio}px;\n\n  ${media.palm`\n    width: calc(100vw - 60px);\n    height: ${imageRatio * 100}vw;\n  `};\n`;\n\nconst StyledImg = styled.img`\n  width: 100%;\n  height: 100%;\n  display: block;\n  position: absolute;\n  top: 0;\n  transition: opacity ${props => props.fadeDuration}ms;\n  opacity: ${props => (props.isVisible ? '1.0' : '0.0')};\n`;\n\nexport default class SlideShow extends PureComponent {\n  static propTypes = {\n    images: PropTypes.arrayOf(PropTypes.string).isRequired,\n    slideDuration: PropTypes.number,\n    fadeDuration: PropTypes.number\n  };\n\n  static defaultProps = {\n    slideDuration: 5000,\n    fadeDuration: 2000\n  };\n\n  constructor(props) {\n    super(props);\n    this._intervalId = null;\n  }\n\n  state = {\n    currentIndex: 0\n  };\n\n  componentDidMount() {\n    this._intervalId = setInterval(\n      this._transitionImage,\n      this.props.slideDuration\n    );\n  }\n\n  componentWillUnmount() {\n    clearInterval(this._intervalId);\n  }\n\n  _transitionImage = () => {\n    const {images} = this.props;\n    const {currentIndex} = this.state;\n    this.setState({\n      currentIndex: currentIndex === images.length - 1 ? 0 : currentIndex + 1\n    });\n  };\n\n  render() {\n    const {images, fadeDuration} = this.props;\n    return (\n      <StyledImgContainer>\n        {images.map((src, i) => (\n          <StyledImg\n            key={src}\n            src={src}\n            fadeDuration={fadeDuration}\n            isVisible={i <= this.state.currentIndex}\n          />\n        ))}\n      </StyledImgContainer>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/common/staggered-scroll-animation.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport PropTypes from 'prop-types';\nimport {Waypoint} from 'react-waypoint';\nimport styled from 'styled-components';\n\nconst FadeIn = styled.div`\n  opacity: ${props => (props.isVisible ? '1.0' : '0.0')};\n  transform: ${props => (props.isVisible ? undefined : 'translateY(10px)')};\n  transition: ${props =>\n    `opacity 350ms ${props.delay}ms, transform 350ms ${props.delay}ms`};\n`;\n\nexport default class StaggeredScrollAnimation extends PureComponent {\n  static propTypes = {\n    duration: PropTypes.number,\n    delay: PropTypes.number,\n    animateOnce: PropTypes.bool,\n    Container: PropTypes.oneOfType([PropTypes.object, PropTypes.element, PropTypes.func, PropTypes.string]),\n    scrollOffsetTop: PropTypes.number\n  };\n\n  static defaultProps = {\n    duration: 500,\n    delay: 150,\n    animateOnce: true,\n    Container: 'div',\n    scrollOffsetTop: -100\n  };\n\n  state = {\n    isVisible: false\n  };\n\n  _onWaypointEnter = () => {\n    this.setState({isVisible: true});\n  };\n\n  _onWaypointLeave = () => {\n    const {animateOnce} = this.props;\n    if (!animateOnce) {\n      this.setState({isVisible: false});\n    }\n  };\n\n  render() {\n    const {isVisible} = this.state;\n    const {delay, children, Container, scrollOffsetTop} = this.props;\n    return (\n      <Waypoint\n        onEnter={this._onWaypointEnter}\n        onLeave={this._onWaypointLeave}\n        topOffset={scrollOffsetTop}\n      >\n        <Container>\n          {React.Children.map(children, (item, i) => (\n            <FadeIn key={i} isVisible={isVisible} delay={delay * i}>\n              {item}\n            </FadeIn>\n          ))}\n        </Container>\n      </Waypoint>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/common/styled-components.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled, {css} from 'styled-components';\nimport {media} from '../../styles';\n\nconst buttonStyles = css`\n  align-items: center;\n  background-color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnBgd\n      : props.secondary\n        ? props.theme.secondaryBtnBgd\n        : props.outline\n          ? props.theme.outlineBtnBgd\n          : props.outlineDark\n            ? props.outlineDarkBtnBgd\n            : props.link\n              ? props.theme.linkBtnBgd\n              : props.theme.primaryBtnBgd};\n  border-radius: ${props => props.theme.primaryBtnRadius};\n  color: ${props =>\n    props.negative\n      ? props.theme.negativeBtnColor\n      : props.secondary\n        ? props.theme.secondaryBtnColor\n        : props.outline\n          ? props.outlineBtnColor\n          : props.outlineDark\n            ? props.theme.outlineDarkBtnColor\n            : props.link\n              ? props.theme.linkBtnColor\n              : props.theme.primaryBtnColor};\n  cursor: pointer;\n  display: inline-flex;\n  font-size: ${props => (props.large ? '14px' : '11px')};\n  font-weight: 500;\n  justify-content: center;\n  letter-spacing: 0.3px;\n  line-height: 14px;\n  outline: 0;\n  border: ${props =>\n    props.outline\n      ? `1px solid ${props.theme.outlineBtnColor}`\n      : props.outlineDark\n        ? `1px solid ${props.theme.outlineDarkBtnColor}`\n        : 'none'};\n  padding: ${props => (props.large ? '14px 32px' : '9px 12px')};\n  text-align: center;\n  transition: ${props => props.theme.btnTransition};\n  vertical-align: middle;\n  width: ${props => props.width || 'auto'};\n  text-transform: uppercase;\n\n  &:hover,\n  &:focus,\n  &:active,\n  &:visited,\n  &.active {\n    background-color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnBgdHover\n        : props.secondary\n          ? props.theme.secondaryBtnBgdHover\n          : props.outline\n            ? props.theme.outlineBtnBgdHover\n            : props.outlineDark\n              ? props.theme.outlineDarkBtnBgdHover\n              : props.link\n                ? props.theme.linkBtnActBgdHover\n                : props.theme.primaryBtnBgdHover};\n    color: ${props =>\n      props.negative\n        ? props.theme.negativeBtnActColor\n        : props.secondary\n          ? props.theme.secondaryBtnActColor\n          : props.outline\n            ? props.theme.outlineBtnActColor\n            : props.outlineDark\n              ? props.theme.outlineDarkBtnActColor\n              : props.link\n                ? props.theme.linkBtnActColor\n                : props.theme.primaryBtnActColor};\n  }\n\n  svg,\n  img {\n    width: 14px;\n    margin-right: 8px;\n  }\n\n  ${media.palm`\n    font-size: 11px;\n    padding: 9px 12px;\n  `};\n`;\n\nexport const Button = styled.div.attrs({\n  className: 'kg-button'\n})`\n  ${buttonStyles};\n`;\n\nexport const LinkButton = styled.a`\n  color: black;\n  ${buttonStyles};\n`;\n\nexport const CenteredContent = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: center;\n`;\n"
  },
  {
    "path": "website-gatsby/src/components/common/styles.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\n\nimport styled from 'styled-components';\nimport {media} from '../../styles';\nimport {cdnUrl} from '../../utils';\n\nexport const StyledCaption = styled.div`\n  max-width: unset;\n  text-align: center;\n  margin-bottom: 32px;\n  margin-top: 6rem;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n\n  ${media.palm`\n    width: auto;\n    padding-top: 0;\n    margin-right: 0;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    margin-top: 0px;\n  `} .kg-home__caption__subtitle {\n    font-size: 36px;\n    font-weight: 600;\n    margin-bottom: 20px;\n    line-height: 1.3;\n\n    ${media.palm`\n      font-size: 20px;\n    `};\n  }\n\n  .kg-home__caption__description {\n    font-size: 14px;\n    color: ${props => props.theme.labelColor};\n    line-height: 24px;\n    margin-bottom: 32px;\n    max-width: 400px;\n\n    span.t-bold {\n      color: ${props => props.theme.textColor};\n      font-weight: 500;\n    }\n\n    ${media.palm`\n      margin-bottom: 32px;\n      text-align: center;\n      font-size: 12px;\n    `};\n  }\n`;\n\nexport const BackgroundImage = styled.img`\n  position: absolute;\n  left: 0px;\n  top: 0px;\n  width: 100%;\n  height: 70%;\n  object-fit: cover;\n  background: #0f1d29;\n`;\n\nexport const Container = styled.div`\n  padding: ${props => props.theme.margins.huge};\n  color: white;\n  position: relative;\n\n  ${media.palm`\n    padding-top: ${props => props.theme.margins.large};\n  `}\n`;\n\nexport const Content = styled.div`\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  ${media.palm`\n    margin-top: 3rem;\n  `};\n`;\n\nexport const Logo = styled.img`\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 120px;\n  ${media.palm`\n    position: inherit;\n    margin-top: ${props => props.theme.margins.normal};\n    margin-bottom: ${props => props.theme.margins.small};\n  `};\n`;\n\n/* eslint-disable react/display-name */\nexport const HeroImage = React.forwardRef((props, ref) => (\n  <BackgroundImage {...props} ref={ref} src={cdnUrl('hero/kepler.gl-background.png')}/>\n));\n\nexport const LogoImage = React.forwardRef((props, ref) => (\n  <Logo {...props} src={cdnUrl('icons/kepler.gl-logo.png')} />\n));\n/* eslint-enable react/display-name */\n"
  },
  {
    "path": "website-gatsby/src/components/common/swipeable.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\nimport SwipeableViews from 'react-swipeable-views';\n\nconst PaginationContainer = styled.div`\n  display: flex;\n  justify-content: center;\n`;\n\n// To increase click area for better usuablity\nconst PaginationBarWrapper = styled.div`\n  padding: 16px 0px;\n  cursor: pointer;\n  margin-left: 2px;\n  :first-child {\n    margin-left: 0px;\n  }\n`;\nconst PaginationBar = styled.div`\n  width: 50px;\n  height: 4px;\n  background: white;\n  opacity: ${props => (props.isActive ? '1.0' : '0.5')};\n  transition: opacity 200ms;\n`;\n\nconst Pagination = ({items, selectedIndex, onChange}) => (\n  <PaginationContainer>\n    {items.map((item, index) => (\n      <PaginationBarWrapper key={index} onClick={() => onChange(index)}>\n        <PaginationBar isActive={index === selectedIndex} />\n      </PaginationBarWrapper>\n    ))}\n  </PaginationContainer>\n);\n\nexport default class Swipeable extends PureComponent {\n  render() {\n    const {children, onChange, selectedIndex} = this.props;\n    return (\n      <div>\n        <SwipeableViews\n          enableMouseEvents\n          index={selectedIndex}\n          onChange={onChange}>\n          {children}\n        </SwipeableViews>\n        <Pagination\n          items={children}\n          selectedIndex={selectedIndex}\n          onChange={onChange}\n        />\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/examples.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {EXAMPLES} from '../content';\nimport {media} from '../styles';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {VerticalCard} from './common/card';\nimport {LinkButton, CenteredContent} from './common/styled-components';\nimport {DEMO_LINK} from '../constants';\n\nconst CardsContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: ${props => props.theme.margins.large};\n  ${media.palm`\n    display: block;\n  `};\n`;\n\nconst StyledCardContainer = styled.a`\n  display: block;\n  margin: ${props => props.theme.margins.small};\n  color: black;\n  cursor: pointer;\n  transition: transform 350ms;\n  &:hover {\n    transform: scale3d(1.05, 1.05, 1.05);\n  }\n  ${media.palm`\n    margin: 0px;\n    margin-bottom: ${props => props.theme.margins.small}\n  `};\n`;\n\nconst CardContainer = ({linkUrl, children}) => (\n  <StyledCardContainer href={linkUrl} target=\"_blank\">\n    {children}\n  </StyledCardContainer>\n);\n\nclass Examples extends PureComponent {\n  render() {\n    return (\n      <div>\n        <StaggeredScrollAnimation Container={CardsContainer}>\n          {EXAMPLES.map(({title, description, image, url}, i) => (\n            <CardContainer linkUrl={url} key={`example-${i}`}>\n              <VerticalCard\n                title={title}\n                description={description}\n                image={image}\n                linkText=\"Explore Map\"\n              />\n            </CardContainer>\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton outline large href={DEMO_LINK}>\n            See More\n          </LinkButton>\n        </CenteredContent>\n      </div>\n    );\n  }\n}\n\nexport default Examples;\n"
  },
  {
    "path": "website-gatsby/src/components/features.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {cdnUrl} from '../utils';\nimport {FEATURES} from '../content';\nimport {media} from '../styles';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {LinkButton, CenteredContent} from './common/styled-components';\n\nconst FeaturesContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: ${props => props.theme.margins.large};\n`;\n\nconst FeatureContainer = styled.div`\n  text-align: center;\n  width: 350px;\n  padding: 24px;\n  margin: ${props => props.theme.margins.small};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  ${media.palm`\n    margin: 0px;\n    margin-bottom: ${props => props.theme.margins.small}\n  `};\n`;\n\nconst FeatureImage = styled.img`\n  width: 75px;\n  height: 75px;\n`;\n\nconst FeatureTitle = styled.div`\n  font-size: 20px;\n  font-weight: 500;\n  margin-bottom: ${props => props.theme.margins.small};\n`;\n\nconst FeatureDescription = styled.div`\n  font-size: 16px;\n  color: #535353;\n`;\n\nconst Feature = ({title, description, image}) => (\n  <FeatureContainer>\n    <FeatureImage src={image} />\n    <FeatureTitle>{title}</FeatureTitle>\n    <FeatureDescription>{description}</FeatureDescription>\n  </FeatureContainer>\n);\n\nclass Features extends PureComponent {\n  render() {\n    return (\n      <div>\n        <StaggeredScrollAnimation Container={FeaturesContainer}>\n          {FEATURES.map(({title, description, image}, i) => (\n            <Feature\n              key={`feature-${i}`}\n              title={title}\n              description={description}\n              image={image}\n            />\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton large outline href=\"https://github.com/keplergl/kepler.gl\">\n            <img src={cdnUrl('icons/github-black.svg')} /> Open Source\n          </LinkButton>\n        </CenteredContent>\n      </div>\n    );\n  }\n}\n\nexport default Features;\n"
  },
  {
    "path": "website-gatsby/src/components/footer.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\nimport {\n  FacebookIcon,\n  FacebookShareButton,\n  TwitterIcon,\n  TwitterShareButton\n} from 'react-share';\n\nimport {cdnUrl} from '../utils';\nimport {LinkButton} from './common/styled-components';\nimport {media} from '../styles';\nimport MapboxLogo from './mapbox-logo';\nimport {DEMO_LINK} from '../constants';\n\nconst Container = styled.div`\n  background: #242730;\n  padding: ${props => props.theme.margins.huge};\n`;\n\nconst LogosContainer = styled.div`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  ${media.palm`\n    flex-direction: column;\n  `};\n`;\n\nconst BrandingContainer = styled.div`\n  display: inline-flex;\n  align-items: center;\n  ${media.palm`\n    margin-top: ${props => props.theme.margins.small};\n  `};\n`;\n\nconst CreatedBy = styled.div`\n  display: inline-flex;\n  margin-left: 20px;\n  align-items: center;\n  color: ${props => props.theme.footerColor};\n  font-size: 11px;\n  justify-content: center;\n  letter-spacing: 0.5px;\n  line-height: 14px;\n  width: 100%;\n  z-index: 101;\n`;\n\nconst StyledLogo = styled.span`\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 4px;\n  position: relative;\n  margin-left: 1.8rem;\n  margin-top: 0;\n  margin-bottom: 0;\n\n  a {\n    color: #e5e5e4;\n    letter-spacing: 2px;\n  }\n\n  :before {\n    content: '';\n    background: url(${cdnUrl('icons/viz_logo_bw.png')}) no-repeat;\n    background-size: cover;\n    height: 20px;\n    width: 24px;\n    position: absolute;\n    top: -5px;\n    left: -25px;\n  }\n`;\n\nconst ButtonSection = styled.div`\n  display: flex;\n  margin-top: 32px;\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n    flex-direction: column;\n  `};\n`;\n\nconst ButtonContainer = styled.div`\n  display: flex;\n  a {\n    width: 160px;\n  }\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    a {\n      width: 50%;\n    }\n  `};\n`;\n\nconst SocialContainer = styled.div`\n  display: flex;\n  flex: 1;\n  justify-content: flex-end;\n\n  .SocialMediaShareButton {\n    margin-right: 5px;\n  }\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    margin-top: 12px;\n    width: 100%;\n  `};\n`;\n\nexport default class Footer extends PureComponent {\n  render() {\n    return (\n      <Container>\n        <LogosContainer>\n          <img style={{width: '120px'}} src={cdnUrl('icons/kepler.gl-logo.png')} />\n          <BrandingContainer>\n            <img src={cdnUrl('icons/uber.svg')} />\n            <MapboxLogo/>\n            <CreatedBy>\n              created by\n              <StyledLogo className=\"fg\">\n                <a\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                  href=\"http://vis.gl\"\n                >\n                  VIS.GL\n                </a>\n              </StyledLogo>\n            </CreatedBy>\n          </BrandingContainer>\n        </LogosContainer>\n        <ButtonSection>\n          <ButtonContainer>\n            <LinkButton large href={DEMO_LINK}>\n              Get Started\n            </LinkButton>\n            <LinkButton\n              large\n              outlineDark\n              href=\"https://github.com/keplergl/kepler.gl\"\n              style={{marginLeft: '5px'}}\n            >\n              <img src={cdnUrl('icons/github.svg')} /> Github\n            </LinkButton>\n          </ButtonContainer>\n          <SocialContainer>\n            <FacebookShareButton url=\"https://kepler.gl/\">\n              <FacebookIcon size={32} />\n            </FacebookShareButton>\n            {' '}\n            <TwitterShareButton url=\"https://kepler.gl/\" hashtags={['keplergl']}>\n              <TwitterIcon size={32} />\n            </TwitterShareButton>\n          </SocialContainer>\n        </ButtonSection>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/header.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {Component} from 'react';\nimport styled from 'styled-components';\n\nimport {HEADER_NAVS} from '../content';\n\nconst StyledLink = styled.a`\n  line-height: 58px;\n  color: #8d9ba3;\n  text-transform: uppercase;\n  margin-left: 40px;\n  transition: color 500ms;\n  display: inline-block;\n\n  &:hover {\n    color: white;\n    cursor: pointer;\n  }\n`;\n\nconst StyledHeader = styled.div`\n  max-width: 100%;\n  padding: 0 36px;\n  margin: 0 auto;\n  width: 100%;\n  overflow: hidden;\n  position: absolute;\n  background: transparent;\n  z-index: 1000;\n  display: flex;\n  justify-content: flex-end;\n\n  .links {\n    margin-top: 20px;\n  }\n\n  .icon-github:before {\n    content: \"\\\\e904\";\n  }\n`;\n\nexport default class Header extends Component {\n  render() {\n    // const {isMenuOpen, opacity, toggleMenu} = this.props;\n\n    return (\n      <StyledHeader className=\"container stretch\">\n        <div className=\"links\">\n          {HEADER_NAVS.map((item, i) => (\n            <StyledLink key={i} href={item.link} target=\"_blank\">\n              {item.text}\n            </StyledLink>\n          ))}\n        </div>\n      </StyledHeader>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/hero.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled, {keyframes} from 'styled-components';\nimport {window} from 'global';\n\nimport {cdnUrl} from '../utils';\nimport {media, breakPoints} from '../styles';\nimport {HERO_IMAGES, HERO_IMAGES_SCALED} from '../content';\nimport SlideShow from './common/slideshow';\nimport {LinkButton} from './common/styled-components';\nimport {DEMO_LINK} from '../constants';\n\nimport {Container, Content, HeroImage, LogoImage, StyledCaption} from './common/styles';\n\nconst SlideShowAnimation = keyframes`\n  0% {\n    opacity: 0;\n    transform: translate(0, 20px);\n  }\n\n  100% {\n    opacity: 1.0\n  }\n`;\n\nconst FadeIn = styled.div`\n  animation-name: ${SlideShowAnimation};\n  animation-timing-function: ease-in-out;\n  animation-duration: 1s;\n  animation-delay: 500ms;\n  animation-fill-mode: both;\n`;\n\nconst ButtonContainer = styled.div`\n  display: flex;\n  justify-content: center;\n\n  a {\n    width: 160px;\n  }\n\n  ${media.palm`\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    a {\n      width: 50%;\n    }\n  `};\n`;\n\nexport default class Hero extends PureComponent {\n  state = {\n    window: window.innerWidth,\n    height: window.innerHeight\n  };\n\n  componentDidMount() {\n    window.addEventListener('resize', this.resize);\n    this.resize();\n  }\n\n  componentWillUnmount() {\n    window.removeEventListener('resize', this.resize);\n  }\n\n  resize = () => {\n    this.setState({\n      width: window.innerWidth,\n      height: window.innerHeight\n    });\n  };\n\n  render() {\n    const isPalm = this.state.width <= breakPoints.palm;\n    return (\n      <Container>\n        <HeroImage />\n        <Content>\n          <LogoImage />\n          <StyledCaption>\n            <div className=\"kg-home__caption__subtitle\">\n              Make an impact with your location data\n            </div>\n            <div className=\"kg-home__caption__description\">\n              <span>Kepler.gl is a powerful </span>\n              <span className=\"t-bold\"> open source </span>\n              <span>geospatial analysis tool&nbsp;for </span>\n              <span className=\"t-bold\">large-scale&nbsp;</span>\n              <span>data sets.</span>\n            </div>\n            <ButtonContainer>\n              <LinkButton large href={DEMO_LINK}>\n                Get Started\n              </LinkButton>\n              <LinkButton\n                large\n                outlineDark\n                href=\"https://github.com/keplergl/kepler.gl\"\n                style={{marginLeft: '5px'}}\n              >\n                <img src={cdnUrl('icons/github.svg')} /> Github\n              </LinkButton>\n            </ButtonContainer>\n          </StyledCaption>\n          <FadeIn>\n            <SlideShow images={isPalm ? HERO_IMAGES_SCALED : HERO_IMAGES} />\n          </FadeIn>\n        </Content>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/home.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport {ThemeProvider} from 'styled-components';\nimport Window from 'global/window';\n\nimport {theme} from '../styles';\nimport {SECTIONS} from '../content';\nimport Hero from './hero';\nimport Showcase from './showcase';\nimport Examples from './examples';\nimport Tutorials from './tutorials';\nimport Walkthrough from './walkthrough';\nimport Features from './features';\nimport Footer from './footer';\nimport Section from './common/section';\nimport Header from './header';\nimport Banner from '../../../examples/demo-app/src/components/banner';\nimport Announcement from '../../../examples/demo-app/src/components/announcement';\n\nconst BannerKey = 'kgHideBanner-iiba';\nconst BannerHeight = 30;\nconst BACKGROUND_COLOR = '#82368c';\n\nconst SECTION_CONTENT = {\n  showcase: Showcase,\n  walkthrough: Walkthrough,\n  features: Features,\n  examples: Examples,\n  tutorials: Tutorials\n};\n\nexport default class Home extends PureComponent {\n  state = {\n    showBanner: false\n  };\n\n  componentDidMount() {\n    // delay 2s to show the banner\n    // if (!window.localStorage.getItem(BannerKey)) {\n    //  window.setTimeout(this._showBanner, 3000);\n    // }\n  }\n\n  _showBanner = () => {\n    this.setState({showBanner: true});\n  };\n\n  _hideBanner = () => {\n    this.setState({showBanner: false});\n  };\n\n  _disableBanner = () => {\n    this._hideBanner();\n    Window.localStorage.setItem(BannerKey, 'true');\n  };\n\n  render() {\n    return (\n      <ThemeProvider theme={theme}>\n        <div>\n          <Banner\n            show={this.state.showBanner}\n            height={BannerHeight}\n            bgColor={BACKGROUND_COLOR}\n            onClose={this._hideBanner}\n          >\n            <Announcement onDisable={this._disableBanner} />\n          </Banner>\n          <Header />\n          <Hero />\n          {SECTIONS.map(({id, title, description, icon, isDark, background}, i) => {\n            const SectionContent = SECTION_CONTENT[id];\n            return (\n              <Section\n                key={`section-${i}`}\n                title={title}\n                description={description}\n                icon={icon}\n                isDark={isDark}\n                background={background}\n              >\n                <SectionContent />\n              </Section>\n            );\n          })}\n          <Footer />\n        </div>\n      </ThemeProvider>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/info-panel.jsx",
    "content": "import React, {PureComponent} from 'react';\n\nexport default class InfoPanel extends PureComponent {\n  render() {\n    const { name, controls, sourceLink} = this.props;\n\n    return (\n      <div className=\"options-panel top-right\" tabIndex=\"0\">\n        <h3>{name}</h3>\n        <div className=\"control-panel\" dangerouslySetInnerHTML={{__html: controls}} />\n\n        {sourceLink && (\n          <div className=\"source-link\">\n            <a href={sourceLink} target=\"_new\">\n              {'View Code ↗'}\n            </a>\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/mapbox-logo.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport styled from 'styled-components';\n\nconst MapboxLogo = styled.a`\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 viewBox%3D%220 0 790 180%22%3E%3Cpath d%3D%22M89.1 1.8C39.9 1.8 0 41.7 0 90.9 0 140.1 39.9 180 89.1 180c49.2 0 89.1-39.9 89.1-89.1 0-49.2-39.9-89.1-89.1-89.1zm457.8 19.7c-1.2 0-2.2 1-2.2 2.2v103.2c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2v-7.1c6.9 7.2 16.4 11.3 26.3 11.3 20.9 0 37.9-18 37.9-40.3 0-22.3-17-40.2-37.9-40.2-10 0-19.5 4.1-26.3 11.3V23.7c0-1.2-1-2.2-2.2-2.2h-13.4zM98.3 36.4c11.4.3 22.9 4.8 31.7 13.7 17.7 17.7 18.3 45.7 1.4 62.7-30.5 30.5-84.8 20.7-84.8 20.7s-9.8-54.3 20.7-84.8c8.5-8.4 19.7-12.5 31-12.3zm160.3 14.2c-8.2 0-15.9 4-20.8 10.6v-6.4c0-1.2-1-2.2-2.2-2.2h-13.4c-1.2 0-2.2 1-2.2 2.2V127c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2V83.8c.5-9.7 7.2-17.3 15.4-17.3 8.5 0 15.6 7.1 15.6 16.5v44c0 1.2 1 2.2 2.2 2.2h13.5c1.2 0 2.2-1 2.2-2.2l-.1-44.9c1.2-8.8 7.6-15.6 15.3-15.6 8.5 0 15.6 7.1 15.6 16.5v44c0 1.2 1 2.2 2.2 2.2h13.5c1.2 0 2.2-1 2.2-2.2l-.1-49.6c.3-14.8-12.3-26.8-27.9-26.8-10 .1-19.2 5.9-23.5 15-5-9.3-14.7-15.1-25.3-15zm127.9 0c-20.9 0-37.9 18-37.9 40.3 0 22.3 17 40.3 37.9 40.3 10 0 19.5-4.1 26.3-11.3v7.1c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2V54.8c.1-1.2-.9-2.2-2.2-2.2H415c-1.2 0-2.2 1-2.2 2.2v7.1c-6.9-7.2-16.4-11.3-26.3-11.3zm106.1 0c-10 0-19.5 4.1-26.3 11.3v-7.1c0-1.2-1-2.2-2.2-2.2h-13.4c-1.2 0-2.2 1-2.2 2.2V158c0 1.2 1 2.2 2.2 2.2h13.4c1.2 0 2.2-1 2.2-2.2v-38.2c6.9 7.2 16.4 11.3 26.3 11.3 20.9 0 37.9-18 37.9-40.3 0-22.3-17-40.2-37.9-40.2zm185.5 0c-22.7 0-41 18-41 40.3 0 22.3 18.4 40.3 41 40.3s41-18 41-40.3c0-22.3-18.3-40.3-41-40.3zm45.4 2c-1.1 0-2 .9-2 2 0 .4.1.8.3 1.1l23 35-23.3 35.4c-.6.9-.4 2.2.6 2.8.3.2.7.3 1.1.3h15.5c1.2 0 2.3-.6 2.9-1.6l13.8-23.1 13.8 23.1c.6 1 1.7 1.6 2.9 1.6h15.5c1.1 0 2-.9 2-2 0-.4-.1-.7-.3-1.1L766 90.7l23-35c.6-.9.4-2.2-.6-2.8-.3-.2-.7-.3-1.1-.3h-15.5c-1.2 0-2.3.6-2.9 1.6l-13.5 22.7-13.5-22.7c-.6-1-1.7-1.6-2.9-1.6h-15.5zM99.3 54l-8.7 18-17.9 8.7 17.9 8.7 8.7 18 8.8-18 17.9-8.7-17.9-8.7-8.8-18zm290.3 12.7c12.7 0 23 10.7 23.2 23.9v.6c-.1 13.2-10.5 23.9-23.2 23.9-12.8 0-23.2-10.8-23.2-24.2 0-13.4 10.4-24.2 23.2-24.2zm99.8 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.7 0-23-10.7-23.2-23.9v-.6c.2-13.2 10.5-23.9 23.2-23.9zm96.3 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.7 0-23-10.7-23.2-23.9v-.6c.2-13.2 10.5-23.9 23.2-23.9zm92.2 0c12.8 0 23.2 10.8 23.2 24.2 0 13.4-10.4 24.2-23.2 24.2-12.8 0-23.2-10.8-23.2-24.2 0-13.4 10.4-24.2 23.2-24.2z%22 fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E%0A\");\n  background-size: 99px 28px;\n  background-repeat: no-repeat;\n  width: 100px;\n  display: inline-block;\n  height: 28px;\n  flex-shrink: 0;\n  margin-left: 30px;\n`;\n\nexport default MapboxLogo;\n"
  },
  {
    "path": "website-gatsby/src/components/policy.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled, {ThemeProvider} from 'styled-components';\nimport {Container, Content, HeroImage, LogoImage, StyledCaption} from './common/styles';\nimport {theme} from '../styles';\nimport Header from './header';\n\nconst GITHUB_PROJECT = 'https://github.com/keplergl/kepler.gl';\nconst GITHUB_PROJECT_ISSUES = `${GITHUB_PROJECT}/issues`;\nconst MAILING_LIST_URL = 'https://groups.google.com/d/forum/kepler-gl';\nconst CONTRIBUTING_URL = 'https://docs.kepler.gl/contributing';\n\nconst StyledContainer = styled(Container)`\n  background-color: black;\n  height: 100vh;\n\n  max-width: unset;\n\n  .description {\n    max-width: 800px\n  }\n\n  .description-content {\n    font-weight: 500;\n  }\n\n  h3 {\n    margin-bottom: 24px;\n  }\n\n  a {\n    text-decoration: underline;\n    color: white;\n  }\n\n  a:visited {\n    color: white;\n  }\n\n  img.hero-image {\n    height: 100vh;\n  }\n\n\n`;\n\nexport default class Home extends PureComponent {\n  render() {\n    return (\n      <ThemeProvider theme={theme}>\n        <div>\n\n          <Header />\n          <StyledContainer>\n            <HeroImage className=\"hero-image\" />\n            <Content>\n              <LogoImage />\n              <StyledCaption>\n                <div className=\"kg-home__caption__subtitle\">\n                  Support Policy\n                </div>\n                <div className=\"description\">\n                  <h3>\n                    <a href=\"https://kepler.gl\">Kepler.gl</a> is an open source project. Its source code is available at <a\n                    href={GITHUB_PROJECT}>Kepler.gl Github project</a>.\n                  </h3>\n                  <span className=\"description-content\">\n                    As an open source project, questions and bug reports can be filed with the project community,\n                    and you can participate in the development of the project's source code. Bug reports and\n                    issues can be submitted at <a href={GITHUB_PROJECT_ISSUES}>Kepler.gl Github issues</a>.\n                    Questions can be asked on the project's mailing lists, which you can subscribe at <a href={MAILING_LIST_URL}>Kepler.gl Mailing list</a>.\n                    Please review the applicable <a href={CONTRIBUTING_URL}>contributing guidelines</a> and procedures in connection with your submissions.\n                    Please note that the Kepler.gl project and OpenJS Foundation do not otherwise provide support for users of Kepler.gl software.\n                  </span>\n                </div>\n              </StyledCaption>\n            </Content>\n          </StyledContainer>\n        </div>\n      </ThemeProvider>\n    );\n  }\n}\n"
  },
  {
    "path": "website-gatsby/src/components/showcase.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {SHOWCASE_ITEMS} from '../content';\nimport {media} from '../styles';\nimport Carousel from './common/carousel';\n\nconst CarouselContainer = styled.div`\n  height: 360px;\n  ${media.palm`\n    height: 240px;\n  `} ${media.desk`\n    height: 480px;\n  `};\n`;\n\nconst Image = styled.img`\n  width: 100%;\n  height: 100%;\n  box-shadow: 0px 12px 24px rgba(0, 0, 0, 0.25);\n  object-fit: cover;\n\n  width: 420px;\n  height: 320px;\n\n  ${media.palm`\n    width: 300px;\n    height: 200px;\n  `} ${media.desk`\n    width: 560px;\n    height: 420px;\n  `};\n`;\n\nconst NavContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  margin-top: 24px;\n`;\n\nconst NavItem = styled.div`\n  margin: ${props => props.theme.margins.tiny};\n  font-size: 10px;\n  text-align: center;\n  filter: ${props => props.isActive && 'brightness(300%)'};\n  transform: ${props => props.isActive && 'scale(1.1)'};\n  transition: transform 500ms, filter 500ms;\n  cursor: pointer;\n  &:hover {\n    transform: scale(1.1);\n  }\n\n  ${media.palm`\n    margin: 2px 4px;\n    font-size: 8px;\n  `};\n`;\n\nconst NavIcon = styled.img`\n  display: block;\n\n  width: 48px;\n  height: 48px;\n\n  ${media.palm`\n    width: 32px;\n    height: 32px;\n  `};\n`;\n\nconst Nav = ({items, selectedIndex, onClick}) => (\n  <NavContainer>\n    {items.map(({text, icon}, i) => (\n      <NavItem key={i} isActive={selectedIndex === i} onClick={() => onClick(i)}>\n        <NavIcon src={icon} />\n        {text}\n      </NavItem>\n    ))}\n  </NavContainer>\n);\n\nclass Showcase extends PureComponent {\n  state = {\n    selectedIndex: 3\n  };\n\n  render() {\n    return (\n      <div>\n        <CarouselContainer>\n          <Carousel\n            selectedIndex={this.state.selectedIndex}\n            onChange={i => this.setState({selectedIndex: i})}\n          >\n            {SHOWCASE_ITEMS.map(({image}, i) => (\n              <Image key={`showcase-image-${i}`} src={image} />\n            ))}\n          </Carousel>\n        </CarouselContainer>\n        <Nav\n          items={SHOWCASE_ITEMS}\n          selectedIndex={this.state.selectedIndex}\n          onClick={i => this.setState({selectedIndex: i})}\n        />\n      </div>\n    );\n  }\n}\n\nexport default Showcase;\n"
  },
  {
    "path": "website-gatsby/src/components/tutorials.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {media} from '../styles';\nimport {TUTORIALS} from '../content';\nimport {HorizontalCard} from './common/card';\nimport StaggeredScrollAnimation from './common/staggered-scroll-animation';\nimport {LinkButton, CenteredContent} from './common/styled-components';\n\nconst Container = styled.div`\n  margin: 0 auto;\n  max-width: 1500px;\n`;\n\nconst CardsContainer = styled.div`\n  display: grid;\n  grid-gap: ${props => props.theme.margins.medium};\n  grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));\n  grid-auto-flow: dense;\n  margin-bottom: ${props => props.theme.margins.large};\n  ${media.palm`\n    display: block;\n  `};\n`;\n\nconst StyledCardContainer = styled.a`\n  display: block;\n  color: black;\n  cursor: pointer;\n  transition: transform 350ms;\n  &:hover {\n    transform: scale3d(1.05, 1.05, 1.05);\n  }\n  ${media.palm`\n    margin-bottom:  ${props => props.theme.margins.small};\n  `};\n`;\n\nconst CardContainer = ({linkUrl, children}) => (\n  <StyledCardContainer href={linkUrl} target=\"_blank\">\n    {children}\n  </StyledCardContainer>\n);\n\nclass Tutorials extends PureComponent {\n  render() {\n    return (\n      <Container>\n        <StaggeredScrollAnimation Container={CardsContainer}>\n          {TUTORIALS.map(({title, description, image, url}, i) => (\n            <CardContainer linkUrl={url} key={`tutorial-${i}`}>\n              <HorizontalCard\n                title={title}\n                description={description}\n                image={image}\n                linkText=\"Read Now\"\n              />\n            </CardContainer>\n          ))}\n        </StaggeredScrollAnimation>\n        <CenteredContent>\n          <LinkButton\n            outline\n            large\n            target=\"_blank\"\n            href=\"https://medium.com/vis-gl\"\n          >\n            Read More\n          </LinkButton>\n        </CenteredContent>\n      </Container>\n    );\n  }\n}\n\nexport default Tutorials;\n"
  },
  {
    "path": "website-gatsby/src/components/walkthrough.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React, {PureComponent} from 'react';\nimport styled from 'styled-components';\n\nimport {WALKTHROUGH_ITEMS} from '../content';\nimport {media} from '../styles';\nimport Swipeable from './common/swipeable';\n\nconst WalkthroughItem = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  ${media.palm`\n    width: 100%;\n    font-size: 12px;\n  `};\n`;\n\nconst VideoContainer = styled.div`\n  width: 640px;\n  ${media.portable`\n    width: 500px;\n  `}\n  ${media.palm`\n    width: 100%;\n  `};\n\n  video {\n    width: 100%;\n  }\n`;\n\nconst VideoDescription = styled.div`\n  margin-top: 16px;\n`;\n\nconst VideoWrapperHeader = styled.div`\n  height: 15px;\n  background: #E5E5E4;\n  border-radius: 3px 3px 0px 0px;\n  display: flex;\n  align-items: center;\n  padding: 0px 5px;\n`;\n\nconst VideoWrapperHeaderCircle = styled.div`\n  width: 6px;\n  height: 6px;\n  border-radius: 10px;\n  background: ${props => props.color};\n  margin-left: 5px;\n`;\n\nconst VideoWrapper = ({children}) => (\n  <div>\n    <VideoWrapperHeader>\n      <VideoWrapperHeaderCircle color=\"#12BB00\" />\n      <VideoWrapperHeaderCircle color=\"#D3AE00\" />\n      <VideoWrapperHeaderCircle color=\"#DE3131\" />\n    </VideoWrapperHeader>\n    {children}\n  </div>\n);\n\nclass Walkthrough extends PureComponent {\n  constructor(props) {\n    super(props);\n    this.videoElements = {};\n  }\n  state = {\n    selectedIndex: 0\n  };\n\n  _onChange = (index) => {\n    this.setState({selectedIndex: index});\n    const videoElement = this.videoElements[index];\n    videoElement.load();\n    videoElement.play();\n  };\n\n  _onVideoEnded = () => {\n    const {selectedIndex} = this.state;\n    const newIndex = (selectedIndex + 1) % WALKTHROUGH_ITEMS.length;\n    this._onChange(newIndex);\n  };\n\n  _assignVideoRef = (element, index) => {\n    this.videoElements[index] = element;\n  };\n\n  render() {\n    const {selectedIndex} = this.state;\n    return (\n      <div>\n        <Swipeable onChange={this._onChange} selectedIndex={selectedIndex}>\n          {WALKTHROUGH_ITEMS.map(({videoUrl, imageUrl, description}, i) => (\n            <WalkthroughItem key={i}>\n              <VideoWrapper key={videoUrl}>\n                <VideoContainer>\n                  <video\n                    muted\n                    src={videoUrl}\n                    poster={imageUrl}\n                    autoPlay={i === selectedIndex}\n                    ref={(elt) => this._assignVideoRef(elt, i)}\n                    onEnded={this._onVideoEnded}\n                  />\n                </VideoContainer>\n              </VideoWrapper>\n              <VideoDescription>{description}</VideoDescription>\n            </WalkthroughItem>\n          ))}\n        </Swipeable>\n      </div>\n    );\n  }\n}\n\nexport default Walkthrough;\n"
  },
  {
    "path": "website-gatsby/src/constants.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nexport const CLOUDFRONT = 'https://d1a3f4spazzrp4.cloudfront.net';\nexport const KEPLER_GL_BUCKET = 'kepler.gl';\nexport const WEBSITE_ASSET_FOLDER = 'website';\nexport const DEMO_LINK = '/demo';\n"
  },
  {
    "path": "website-gatsby/src/content.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {cdnUrl} from './utils';\n\nexport const SECTIONS = [\n  {\n    id: 'showcase',\n    title: 'Map Enthusiast?',\n    description: 'Make beautiful data-driven maps.',\n    icon: cdnUrl('icons/showcase.png')\n  },\n  {\n    id: 'walkthrough',\n    title: 'Data Scientist?',\n    description:\n      'Gain insights from location data and deliver business outcomes.',\n    icon: cdnUrl('icons/walkthrough.png'),\n    isDark: true\n  },\n  {\n    id: 'features',\n    title: 'Developer?',\n    description:\n      'A customizable geospatial toolbox to help make data-driven decisions.',\n    icon: cdnUrl('icons/features.png')\n  },\n  {\n    id: 'examples',\n    title: 'See What People Created',\n    description: 'See what others have been creating with Kepler.gl.',\n    icon: cdnUrl('icons/examples.png'),\n    background: cdnUrl('examples/section-background.png')\n  },\n  {\n    id: 'tutorials',\n    title: 'Getting Started',\n    description: 'Get started with tutorials created by our team.',\n    icon: cdnUrl('icons/tutorials.png')\n  }\n];\n\nexport const HERO_IMAGES = [\n  cdnUrl('hero/kepler.gl-hexagon.png'),\n  cdnUrl('hero/kepler.gl-points.png'),\n  cdnUrl('hero/kepler.gl-contours.png')\n];\n\nexport const HERO_IMAGES_SCALED = [\n  cdnUrl('hero/kepler.gl-hexagon_s.png'),\n  cdnUrl('hero/kepler.gl-points_s.png'),\n  cdnUrl('hero/kepler.gl-contours_s.png')\n];\n\nexport const HEADER_NAVS = [\n  {\n    text: 'User Guide',\n    link: 'https://docs.kepler.gl/docs/user-guides'\n  },\n  {\n    text: 'Documentation',\n    link: 'https://docs.kepler.gl/docs/api-reference'\n  },\n  {\n    text: 'Github',\n    link: 'https://github.com/keplergl/kepler.gl'\n  },\n  {\n    text: 'Support Policy',\n    link: '/policy'\n  }\n];\n\nexport const SHOWCASE_ITEMS = [\n  {\n    text: 'Arc',\n    image: cdnUrl('showcase/arcs-s.png'),\n    icon: cdnUrl('showcase/icons/arc.png')\n  },\n  {\n    text: 'Line',\n    image: cdnUrl('showcase/lines-s.png'),\n    icon: cdnUrl('showcase/icons/line.png')\n  },\n  {\n    text: 'Hexagon',\n    image: cdnUrl('showcase/hexagons-s.png'),\n    icon: cdnUrl('showcase/icons/hexagon.png')\n  },\n  {\n    text: 'Point',\n    image: cdnUrl('showcase/points-s.png'),\n    icon: cdnUrl('showcase/icons/point.png')\n  },\n  // {\n  //   text: 'H3',\n  //   image: cdnUrl('showcase/h3.png'),\n  //   icon: cdnUrl('showcase/icons/h3.png')\n  // },\n  {\n    text: 'Heatmap',\n    image: cdnUrl('showcase/heatmap-s.png'),\n    icon: cdnUrl('showcase/icons/heatmap.png')\n  },\n  {\n    text: 'GeoJSON',\n    image: cdnUrl('showcase/geojson-s.png'),\n    icon: cdnUrl('showcase/icons/geojson.png')\n  },\n  {\n    text: 'Buildings',\n    image: cdnUrl('showcase/buildings-s.png'),\n    icon: cdnUrl('showcase/icons/cluster.png')\n  }\n];\n\nexport const WALKTHROUGH_ITEMS = [\n  {\n    videoUrl: cdnUrl('videos/0.upload_file.mp4'),\n    imageUrl: cdnUrl('videos/0.upload_file.png'),\n    description:\n      'Easily add data to map by drag and drop files'\n  },\n  {\n    videoUrl: cdnUrl('videos/1.time_filter.mp4'),\n    imageUrl: cdnUrl('videos/1.time_filter.png'),\n    description:\n      'Free form filtering with time playback'\n  },\n  {\n    videoUrl: cdnUrl('videos/2.aggregation.mp4'),\n    imageUrl: cdnUrl('videos/2.aggregation.png'),\n    description:\n      'Gain deeper insights by performing geo aggregation'\n  },\n  {\n    videoUrl: cdnUrl('videos/3.brushing.mp4'),\n    imageUrl: cdnUrl('videos/3.brushing.png'),\n    description:\n      'Explore origin-destination correlations with brushing'\n  }\n];\n\nexport const FEATURES = [\n  {\n    title: 'Performance',\n    description:\n      'Built with Deck.gl, Kepler.gl utilizes WebGL to render large datasets quickly and efficiently.',\n    image: cdnUrl('features/performance.svg')\n  },\n  {\n    title: 'Interaction',\n    description:\n      'You can easily drag and drop a dataset, add filters, apply scales, and do aggregation on the fly.',\n    image: cdnUrl('features/interaction.svg')\n  },\n  {\n    title: 'Embeddable',\n    description:\n      'Built on React & Redux, Kepler.gl can be embedded inside your own mapping applications.',\n    image: cdnUrl('features/embeddable.svg')\n  }\n];\n\nexport const EXAMPLES = [\n  {\n    title: 'California Earthquakes',\n    description: 'Location, maginitude and magtype of 2.5+ magnitude earthquakes in california',\n    image: cdnUrl('examples/cali-earthquakes.png'),\n    url: '/demo/earthquakes'\n  },\n  {\n    title: 'New York City Cab Rides',\n    description: 'A small sample of yellow and green taxi trip records in New York City',\n    image: cdnUrl('examples/ny-taxis.png'),\n    url: '/demo/nyctrips'\n  },\n  {\n    title: 'San Francisco Elevation Contour',\n    description: 'Elevation contours of San Francisco mainland and Treasure Island/Yerba Island',\n    image: cdnUrl('examples/sf-elevation.png'),\n    url: '/demo/sfcontour'\n  },\n  {\n    title: 'Travel Times from Uber Movement',\n    description: 'Pittsburgh travel times before and during heavy inclement weather conditions',\n    image: cdnUrl('examples/movement-pittsburgh.png'),\n    url: '/demo/movement_pittsburgh'\n  },\n  {\n    title: 'New york city population',\n    description: 'This dataset contains the 2010 Census tract population data of NYC',\n    image: cdnUrl('examples/ny-population.png'),\n    url: '/demo/nyc_census'\n  },\n  {\n    title: 'San Francisco Street Tree Map',\n    description: 'A 3d hexbin density map showing every single streets in San Francisco',\n    image: cdnUrl('examples/sf-street-trees.png'),\n    url: '/demo/sftrees'\n  },\n  {\n    title: 'Commute Patterns in the UK',\n    description: 'A origin destination map using 3d arcs to show commute patterns of England and Wales residence',\n    image: cdnUrl('examples/uk-commute.png'),\n    url: '/demo/ukcommute'\n  }\n];\n\nexport const TUTORIALS = [\n  {\n    title: 'How to create a map in 3 minutes',\n    description: 'Animating 40 years of California Earthquakes.',\n    image: cdnUrl('examples/earthquake.png'),\n    url: 'https://medium.com/vis-gl/animating-40-years-of-california-earthquakes-e4ffcdd4a289'\n  },\n  {\n    title: 'Adding altitude to points',\n    description: 'Mapping the Parisian trees.',\n    image: cdnUrl('examples/parisian.png'),\n    url: 'https://medium.com/vis-gl/mapping-the-parisian-trees-6dc30f6aabc7'\n  },\n  {\n    description: 'Visualizing U.S. County Unemployment with kepler.gl',\n    title: 'Making a choropleth map',\n    image: cdnUrl('examples/unemployment.png'),\n    url: 'https://medium.com/vis-gl/visualizing-u-s-county-unemployment-with-kepler-gl-c5f2ed31c71'\n  },\n  {\n    title: 'Uber Movement and kepler.gl',\n    description: 'Using kepler.gl and Movement data to Visualize Traffic Effects of a Rainstorm',\n    image: cdnUrl('examples/movement.png'),\n    url: 'https://medium.com/@uber_movement/movement-in-kepler-d00e843f464d'\n  }\n];\n"
  },
  {
    "path": "website-gatsby/src/state/analytics.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// This file sends actions on the demo app to Google analytics\n\nimport {ActionTypes} from 'kepler.gl/actions';\nimport {LOCATION_CHANGE} from 'react-router-redux';\nimport Window from 'global/window';\nimport {ALL_FIELD_TYPES} from 'kepler.gl/constants';\nimport get from 'lodash/get';\n\nconst getPayload = action => (action ? action.payload : null);\n\n// Hack, because we don't have a way to access next state\nconst getFilterType = (store, idx, value) => {\n  const {visState} = store.getState().demo.keplerGl.map;\n  const filter = visState.filters[idx];\n  const {dataId} = filter;\n  if (!dataId) {\n    return {};\n  }\n  const {fields} = visState.datasets[dataId];\n  const field = fields.find(f => f.name === value);\n\n  // Hack\n  switch (field.type) {\n    case ALL_FIELD_TYPES.real:\n    case ALL_FIELD_TYPES.integer:\n      return 'range';\n\n    case ALL_FIELD_TYPES.boolean:\n      return 'select';\n\n    case ALL_FIELD_TYPES.string:\n    case ALL_FIELD_TYPES.date:\n      return 'multiSelect';\n\n    case ALL_FIELD_TYPES.timestamp:\n      return 'timeRange';\n\n    default:\n      return null;\n  }\n};\n\nconst trackingInformation = {\n  [ActionTypes.LOAD_FILES]: payload => payload.files.map(({size, type}) => ({size, type})),\n  [ActionTypes.LAYER_TYPE_CHANGE]: ({newType}) => ({\n    newType\n  }),\n  [ActionTypes.MAP_STYLE_CHANGE]: getPayload,\n  [ActionTypes.TOGGLE_MODAL]: getPayload,\n  [ActionTypes.ADD_LAYER]: (payload, store) => ({\n    total: store.getState().demo.keplerGl.map.visState.layers.length + 1\n  }),\n  [ActionTypes.ADD_FILTER]: (payload, store) => ({\n    total: store.getState().demo.keplerGl.map.visState.filters.length + 1\n  }),\n  [ActionTypes.SET_FILTER]: ({prop, idx, value}, store) => {\n    if (prop !== 'name') {\n      return {};\n    }\n    return {\n      filterType: getFilterType(store, idx, value)\n    };\n  },\n  [ActionTypes.INTERACTION_CONFIG_CHANGE]: ({config: {id, enabled}}) => ({\n    [id]: enabled\n  }),\n  [LOCATION_CHANGE]: x => x,\n\n  // demo app actions\n  ['PUSHING_FILE']: payload => {\n    const size = get(payload, ['metadata', 'metadata', 'size']);\n    return {\n      isLoading: payload.isLoading,\n      status: payload.metadata.status,\n      filename: payload.metadata.filename,\n      size,\n      error: payload.error\n    };\n  }\n};\n\nconst EXCLUDED_ACTIONS = [ActionTypes.LAYER_HOVER, ActionTypes.UPDATE_MAP];\n\nconst analyticsMiddleware = store => next => action => {\n  if (Window.gtag && !EXCLUDED_ACTIONS.includes(action.type)) {\n    const payload = action.payload || action;\n\n    // eslint-disable-next-line no-undef\n    Window.gtag('event', 'action', {\n      event_category: action.type,\n      event_label: trackingInformation[action.type]\n        ? JSON.stringify(trackingInformation[action.type](payload, store))\n        : null\n    });\n  }\n\n  return next(action);\n};\n\nexport default analyticsMiddleware;\n"
  },
  {
    "path": "website-gatsby/src/state/app.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {handleActions} from 'redux-actions';\n\nconst DEFAULT_APP_STATE = {};\n\nexport default handleActions({\n  INIT: (state, action) => ({...state, ready: true})\n}, DEFAULT_APP_STATE);\n"
  },
  {
    "path": "website-gatsby/src/state/index.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport { combineReducers } from 'redux';\nimport test from './test';\nimport app from './app';\n// import analytics from './analytics';\n\nexport default combineReducers({\n  test,\n  app\n  // analytics\n});\n"
  },
  {
    "path": "website-gatsby/src/state/redux-wrapper.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport React from 'react';\nimport { Provider } from 'react-redux';\nimport { createStore as reduxCreateStore } from 'redux';\nimport rootReducer from '.';\n\nconst createStore = () => reduxCreateStore(rootReducer);\n\n// Wraps the root-component in Gatsby\nexport default ({ element }) => (\n  <Provider store={createStore()}>{element}</Provider>\n);\n"
  },
  {
    "path": "website-gatsby/src/state/test.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\n// action creators\n\nconst TOGGLE_DARKMODE = 'TOGGLE_DARKMODE';\n\nexport const toggleDarkMode = () => ({\n  type: TOGGLE_DARKMODE\n});\n\n// initial state\n\nconst initialState = {\n  isDarkMode: false,\n};\n\n// reducer\n\nexport default (state = initialState, action) => {\n  switch (action.type) {\n    case TOGGLE_DARKMODE:\n      return { ...state, isDarkMode: !state.isDarkMode };\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "website-gatsby/src/styles.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {css} from 'styled-components';\n\nexport const breakPoints = {\n  palm: 588,\n  desk: 768\n};\n\nexport const media = {\n  palm: (...args) => css`\n    @media (max-width: ${breakPoints.palm}px) {\n      ${css(...args)};\n    }\n  `,\n\n  portable: (...args) => css`\n    @media (max-width: ${breakPoints.desk}px) {\n      ${css(...args)};\n    }\n  `,\n\n  desk: (...args) => css`\n    @media (min-width: ${breakPoints.desk + 1}px) {\n      ${css(...args)};\n    }\n  `\n};\n\nexport const errorColor = '#F9042C';\n\nexport const theme = {\n  textColor: '#FFFFFF',\n  labelColor: '#D0D0D0',\n  mapBackground: '#0D1823',\n  mapBlocker: '#0B151F',\n  footerColor: '#C0C0C0',\n  // button:\n  primaryBtnBgd: '#005CD2',\n  primaryBtnActBgd: '#13B17B',\n  primaryBtnColor: '#FFFFFF',\n  primaryBtnActColor: '#FFFFFF',\n  primaryBtnBgdHover: '#1f7cf4',\n  primaryBtnRadius: '2px',\n  secondaryBtnBgd: '#6A7485',\n  secondaryBtnActBgd: '#A0A7B4',\n  secondaryBtnColor: '#FFFFFF',\n  secondaryBtnActColor: '#FFFFFF',\n  secondaryBtnBgdHover: '#A0A7B4',\n  outlineBtnBgd: 'transparent',\n  outlineBtnBgdHover: 'rgba(0, 0, 0, 0.05)',\n  outlineBtnActColor: '#000000',\n  outlineBtnColor: '#000000',\n  outlineDarkBtnBgd: 'transparent',\n  outlineDarkBtnBgdHover: 'rgba(255, 255, 255, 0.05)',\n  outlineDarkBtnActColor: '#ffffff',\n  outlineDarkBtnColor: '#ffffff',\n  linkBtnBgd: 'transparent',\n  linkBtnActBgd: 'transparent',\n  linkBtnColor: '#A0A7B4',\n  linkBtnActColor: '#3A414C',\n  linkBtnActBgdHover: 'transparent',\n  negativeBtnBgd: errorColor,\n  negativeBtnActBgd: '#FF193E',\n  negativeBtnBgdHover: '#FF193E',\n  negativeBtnColor: '#FFFFFF',\n  negativeBtnActColor: '#FFFFFF',\n  btnTransition: '350ms color, 350ms background',\n  linkColor: '#A0A7B4',\n  darkBackgroundColor: '#242730',\n  margins: {\n    tiny: '0.5rem',\n    small: '1.0rem',\n    medium: '1.5rem',\n    large: '2.0rem',\n    huge: '2.5rem'\n  }\n};\n"
  },
  {
    "path": "website-gatsby/src/utils.js",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright contributors to the kepler.gl project\n\nimport {CLOUDFRONT, KEPLER_GL_BUCKET, WEBSITE_ASSET_FOLDER} from './constants';\n\nexport function cdnUrl(path) {\n  return `${CLOUDFRONT}/${KEPLER_GL_BUCKET}/${WEBSITE_ASSET_FOLDER}/${path}`;\n}\n"
  },
  {
    "path": "website-gatsby/static/CNAME",
    "content": "kepler.gl"
  },
  {
    "path": "website-gatsby/templates/index.jsx",
    "content": "import React from 'react';\nimport {connect} from 'react-redux';\nimport {Home} from 'gatsby-theme-ocular/components';\nimport './style.css';\n\nimport {toggleDarkMode} from '../src/state/test';\n\nimport HeroExample from '../src/components/home';\n// const HeroExample = 'div';\n\nif (typeof window !== 'undefined') {\n  window.website = true;\n}\n\nconst DarkButton = ({isDarkMode, dispatch}) => (\n  <button\n    style={isDarkMode ? {background: 'black', color: 'white'} : null}\n    onClick={() => dispatch(toggleDarkMode())}\n  >\n    Dark Mode\n  </button>\n);\n\nclass IndexPage extends React.Component {\n  render() {\n    const {isDarkMode, dispatch} = this.props;\n    return (\n      <div style={{top: 60, position: 'absolute'}}>\n        <DarkButton dispatch={dispatch} isDarkMode={isDarkMode} />\n        <Home HeroExample={HeroExample} />\n      </div>\n    );\n  }\n}\n\nexport default connect(state => ({\n  isDarkMode: state.app.isDarkMode\n}), null)(IndexPage);\n"
  },
  {
    "path": "website-gatsby/templates/style.css",
    "content": ".banner .container {\n  background: rgba(0, 0, 0, 0);\n}\n\n.banner .hero .options-panel {\n  display: none;\n}\n"
  }
]